1 /*
2 * This program is free software; you can redistribute it and/or
3 * modify it under the terms of the GNU General Public License
4 * as published by the Free Software Foundation; either version 2
5 * of the License, or (at your option) any later version.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 *
12 * You should have received a copy of the GNU General Public License
13 * along with this program; if not, write to the Free Software Foundation,
14 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
15 *
16 * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV.
17 * All rights reserved.
18 */
19
20 /** \file
21 * \ingroup wm
22 *
23 * User level access for blend file read/write, file-history and user-preferences
24 * (including relevant operators).
25 */
26
27 /* placed up here because of crappy
28 * winsock stuff.
29 */
30 #include <errno.h>
31 #include <stddef.h>
32 #include <string.h>
33
34 #include "zlib.h" /* wm_read_exotic() */
35
36 #ifdef WIN32
37 /* Need to include windows.h so _WIN32_IE is defined. */
38 # include <windows.h>
39 # ifndef _WIN32_IE
40 /* Minimal requirements for SHGetSpecialFolderPath on MINGW MSVC has this defined already. */
41 # define _WIN32_IE 0x0400
42 # endif
43 /* For SHGetSpecialFolderPath, has to be done before BLI_winstuff
44 * because 'near' is disabled through BLI_windstuff */
45 # include "BLI_winstuff.h"
46 # include <shlobj.h>
47 #endif
48
49 #include "MEM_CacheLimiterC-Api.h"
50 #include "MEM_guardedalloc.h"
51
52 #include "BLI_blenlib.h"
53 #include "BLI_fileops_types.h"
54 #include "BLI_linklist.h"
55 #include "BLI_system.h"
56 #include "BLI_threads.h"
57 #include "BLI_timer.h"
58 #include "BLI_utildefines.h"
59 #include BLI_SYSTEM_PID_H
60
61 #include "BLT_translation.h"
62
63 #include "BLF_api.h"
64
65 #include "DNA_object_types.h"
66 #include "DNA_scene_types.h"
67 #include "DNA_screen_types.h"
68 #include "DNA_space_types.h"
69 #include "DNA_userdef_types.h"
70 #include "DNA_windowmanager_types.h"
71 #include "DNA_workspace_types.h"
72
73 #include "BKE_addon.h"
74 #include "BKE_appdir.h"
75 #include "BKE_autoexec.h"
76 #include "BKE_blender.h"
77 #include "BKE_blendfile.h"
78 #include "BKE_callbacks.h"
79 #include "BKE_context.h"
80 #include "BKE_global.h"
81 #include "BKE_idprop.h"
82 #include "BKE_lib_id.h"
83 #include "BKE_lib_override.h"
84 #include "BKE_main.h"
85 #include "BKE_packedFile.h"
86 #include "BKE_report.h"
87 #include "BKE_scene.h"
88 #include "BKE_screen.h"
89 #include "BKE_sound.h"
90 #include "BKE_undo_system.h"
91 #include "BKE_workspace.h"
92
93 #include "BLO_readfile.h"
94 #include "BLO_undofile.h" /* to save from an undo memfile */
95 #include "BLO_writefile.h"
96
97 #include "RNA_access.h"
98 #include "RNA_define.h"
99
100 #include "IMB_imbuf.h"
101 #include "IMB_imbuf_types.h"
102 #include "IMB_thumbs.h"
103
104 #include "ED_datafiles.h"
105 #include "ED_fileselect.h"
106 #include "ED_image.h"
107 #include "ED_outliner.h"
108 #include "ED_screen.h"
109 #include "ED_undo.h"
110 #include "ED_util.h"
111 #include "ED_view3d.h"
112 #include "ED_view3d_offscreen.h"
113
114 #include "GHOST_C-api.h"
115 #include "GHOST_Path-api.h"
116
117 #include "UI_interface.h"
118 #include "UI_interface_icons.h"
119 #include "UI_resources.h"
120 #include "UI_view2d.h"
121
122 /* only to report a missing engine */
123 #include "RE_engine.h"
124
125 #ifdef WITH_PYTHON
126 # include "BPY_extern_python.h"
127 # include "BPY_extern_run.h"
128 #endif
129
130 #include "DEG_depsgraph.h"
131
132 #include "WM_api.h"
133 #include "WM_message.h"
134 #include "WM_toolsystem.h"
135 #include "WM_types.h"
136
137 #include "wm.h"
138 #include "wm_event_system.h"
139 #include "wm_files.h"
140 #include "wm_window.h"
141
142 static RecentFile *wm_file_history_find(const char *filepath);
143 static void wm_history_file_free(RecentFile *recent);
144 static void wm_history_file_update(void);
145 static void wm_history_file_write(void);
146
147 /* -------------------------------------------------------------------- */
148 /** \name Misc Utility Functions
149 * \{ */
150
WM_file_tag_modified(void)151 void WM_file_tag_modified(void)
152 {
153 wmWindowManager *wm = G_MAIN->wm.first;
154 if (wm->file_saved) {
155 wm->file_saved = 0;
156 /* notifier that data changed, for save-over warning or header */
157 WM_main_add_notifier(NC_WM | ND_DATACHANGED, NULL);
158 }
159 }
160
wm_file_or_image_is_modified(const Main * bmain,const wmWindowManager * wm)161 bool wm_file_or_image_is_modified(const Main *bmain, const wmWindowManager *wm)
162 {
163 return !wm->file_saved || ED_image_should_save_modified(bmain);
164 }
165
166 /** \} */
167
168 /* -------------------------------------------------------------------- */
169 /** \name Window Matching for File Reading
170 * \{ */
171
172 /**
173 * To be able to read files without windows closing, opening, moving
174 * we try to prepare for worst case:
175 * - active window gets active screen from file
176 * - restoring the screens from non-active windows
177 * Best case is all screens match, in that case they get assigned to proper window.
178 */
wm_window_match_init(bContext * C,ListBase * wmlist)179 static void wm_window_match_init(bContext *C, ListBase *wmlist)
180 {
181 wmWindowManager *wm;
182 wmWindow *win, *active_win;
183
184 *wmlist = G_MAIN->wm;
185 BLI_listbase_clear(&G_MAIN->wm);
186
187 active_win = CTX_wm_window(C);
188
189 /* first wrap up running stuff */
190 /* code copied from wm_init_exit.c */
191 for (wm = wmlist->first; wm; wm = wm->id.next) {
192
193 WM_jobs_kill_all(wm);
194
195 for (win = wm->windows.first; win; win = win->next) {
196
197 CTX_wm_window_set(C, win); /* needed by operator close callbacks */
198 WM_event_remove_handlers(C, &win->handlers);
199 WM_event_remove_handlers(C, &win->modalhandlers);
200 ED_screen_exit(C, win, WM_window_get_active_screen(win));
201 }
202 }
203
204 /* reset active window */
205 CTX_wm_window_set(C, active_win);
206
207 /* XXX Hack! We have to clear context menu here, because removing all modalhandlers
208 * above frees the active menu (at least, in the 'startup splash' case),
209 * causing use-after-free error in later handling of the button callbacks in UI code
210 * (see ui_apply_but_funcs_after()).
211 * Tried solving this by always NULL-ing context's menu when setting wm/win/etc.,
212 * but it broke popups refreshing (see T47632),
213 * so for now just handling this specific case here. */
214 CTX_wm_menu_set(C, NULL);
215
216 ED_editors_exit(G_MAIN, true);
217 }
218
wm_window_substitute_old(wmWindowManager * oldwm,wmWindowManager * wm,wmWindow * oldwin,wmWindow * win)219 static void wm_window_substitute_old(wmWindowManager *oldwm,
220 wmWindowManager *wm,
221 wmWindow *oldwin,
222 wmWindow *win)
223 {
224 win->ghostwin = oldwin->ghostwin;
225 win->gpuctx = oldwin->gpuctx;
226 win->active = oldwin->active;
227 if (win->active) {
228 wm->winactive = win;
229 }
230 if (oldwm->windrawable == oldwin) {
231 oldwm->windrawable = NULL;
232 wm->windrawable = win;
233 }
234
235 /* File loading in background mode still calls this. */
236 if (!G.background) {
237 /* Pointer back. */
238 GHOST_SetWindowUserData(win->ghostwin, win);
239 }
240
241 oldwin->ghostwin = NULL;
242 oldwin->gpuctx = NULL;
243
244 win->eventstate = oldwin->eventstate;
245 oldwin->eventstate = NULL;
246
247 /* ensure proper screen rescaling */
248 win->sizex = oldwin->sizex;
249 win->sizey = oldwin->sizey;
250 win->posx = oldwin->posx;
251 win->posy = oldwin->posy;
252 }
253
wm_window_match_keep_current_wm(const bContext * C,ListBase * current_wm_list,const bool load_ui,ListBase * r_new_wm_list)254 static void wm_window_match_keep_current_wm(const bContext *C,
255 ListBase *current_wm_list,
256 const bool load_ui,
257 ListBase *r_new_wm_list)
258 {
259 Main *bmain = CTX_data_main(C);
260 wmWindowManager *wm = current_wm_list->first;
261 bScreen *screen = NULL;
262
263 /* match oldwm to new dbase, only old files */
264 wm->initialized &= ~WM_WINDOW_IS_INIT;
265
266 /* when loading without UI, no matching needed */
267 if (load_ui && (screen = CTX_wm_screen(C))) {
268 LISTBASE_FOREACH (wmWindow *, win, &wm->windows) {
269 WorkSpace *workspace;
270
271 BKE_workspace_layout_find_global(bmain, screen, &workspace);
272 BKE_workspace_active_set(win->workspace_hook, workspace);
273 win->scene = CTX_data_scene(C);
274
275 /* all windows get active screen from file */
276 if (screen->winid == 0) {
277 WM_window_set_active_screen(win, workspace, screen);
278 }
279 else {
280 WorkSpaceLayout *layout_old = WM_window_get_active_layout(win);
281 WorkSpaceLayout *layout_new = ED_workspace_layout_duplicate(
282 bmain, workspace, layout_old, win);
283
284 WM_window_set_active_layout(win, workspace, layout_new);
285 }
286
287 bScreen *win_screen = WM_window_get_active_screen(win);
288 win_screen->winid = win->winid;
289 }
290 }
291
292 *r_new_wm_list = *current_wm_list;
293 }
294
wm_window_match_replace_by_file_wm(bContext * C,ListBase * current_wm_list,ListBase * readfile_wm_list,ListBase * r_new_wm_list)295 static void wm_window_match_replace_by_file_wm(bContext *C,
296 ListBase *current_wm_list,
297 ListBase *readfile_wm_list,
298 ListBase *r_new_wm_list)
299 {
300 wmWindowManager *oldwm = current_wm_list->first;
301 wmWindowManager *wm = readfile_wm_list->first; /* will become our new WM */
302 bool has_match = false;
303
304 /* this code could move to setup_appdata */
305
306 /* preserve key configurations in new wm, to preserve their keymaps */
307 wm->keyconfigs = oldwm->keyconfigs;
308 wm->addonconf = oldwm->addonconf;
309 wm->defaultconf = oldwm->defaultconf;
310 wm->userconf = oldwm->userconf;
311
312 BLI_listbase_clear(&oldwm->keyconfigs);
313 oldwm->addonconf = NULL;
314 oldwm->defaultconf = NULL;
315 oldwm->userconf = NULL;
316
317 /* ensure making new keymaps and set space types */
318 wm->initialized = 0;
319 wm->winactive = NULL;
320
321 /* Clearing drawable of before deleting any context
322 * to avoid clearing the wrong wm. */
323 wm_window_clear_drawable(oldwm);
324
325 /* only first wm in list has ghostwins */
326 LISTBASE_FOREACH (wmWindow *, win, &wm->windows) {
327 LISTBASE_FOREACH (wmWindow *, oldwin, &oldwm->windows) {
328 if (oldwin->winid == win->winid) {
329 has_match = true;
330
331 wm_window_substitute_old(oldwm, wm, oldwin, win);
332 }
333 }
334 }
335 /* make sure at least one window is kept open so we don't lose the context, check T42303 */
336 if (!has_match) {
337 wm_window_substitute_old(oldwm, wm, oldwm->windows.first, wm->windows.first);
338 }
339
340 wm_close_and_free_all(C, current_wm_list);
341
342 *r_new_wm_list = *readfile_wm_list;
343 }
344
345 /**
346 * Match old WM with new, 4 cases:
347 * 1) No current WM, no WM in file: Make new default.
348 * 2) No current WM, but WM in file: Keep current WM, do nothing else.
349 * 3) Current WM, but not in file: Keep current WM, update windows with screens from file.
350 * 4) Current WM, and WM in file: Try to keep current GHOST windows, use WM from file.
351 *
352 * \param r_new_wm_list: Return argument for the wm list to be used from now on.
353 */
wm_window_match_do(bContext * C,ListBase * current_wm_list,ListBase * readfile_wm_list,ListBase * r_new_wm_list)354 static void wm_window_match_do(bContext *C,
355 ListBase *current_wm_list,
356 ListBase *readfile_wm_list,
357 ListBase *r_new_wm_list)
358 {
359 if (BLI_listbase_is_empty(current_wm_list)) {
360 /* case 1 */
361 if (BLI_listbase_is_empty(readfile_wm_list)) {
362 Main *bmain = CTX_data_main(C);
363 /* Neither current, no newly read file have a WM -> add the default one. */
364 wm_add_default(bmain, C);
365 *r_new_wm_list = bmain->wm;
366 }
367 /* case 2 */
368 else {
369 *r_new_wm_list = *readfile_wm_list;
370 }
371 }
372 else {
373 /* case 3 */
374 if (BLI_listbase_is_empty(readfile_wm_list)) {
375 /* We've read file without wm, keep current one entirely alive.
376 * Happens when reading pre 2.5 files (no WM back then) */
377 wm_window_match_keep_current_wm(
378 C, current_wm_list, (G.fileflags & G_FILE_NO_UI) == 0, r_new_wm_list);
379 }
380 /* case 4 */
381 else {
382 wm_window_match_replace_by_file_wm(C, current_wm_list, readfile_wm_list, r_new_wm_list);
383 }
384 }
385 }
386
387 /** \} */
388
389 /* -------------------------------------------------------------------- */
390 /** \name Preferences Initialization & Versioning
391 * \{ */
392
393 /**
394 * In case #UserDef was read, re-initialize values that depend on it.
395 */
wm_init_userdef(Main * bmain)396 static void wm_init_userdef(Main *bmain)
397 {
398 /* Not versioning, just avoid errors. */
399 #ifndef WITH_CYCLES
400 BKE_addon_remove_safe(&U.addons, "cycles");
401 #else
402 UNUSED_VARS(BKE_addon_remove_safe);
403 #endif
404
405 UI_init_userdef();
406
407 /* needed so loading a file from the command line respects user-pref T26156. */
408 SET_FLAG_FROM_TEST(G.fileflags, U.flag & USER_FILENOUI, G_FILE_NO_UI);
409
410 /* set the python auto-execute setting from user prefs */
411 /* enabled by default, unless explicitly enabled in the command line which overrides */
412 if ((G.f & G_FLAG_SCRIPT_OVERRIDE_PREF) == 0) {
413 SET_FLAG_FROM_TEST(G.f, (U.flag & USER_SCRIPT_AUTOEXEC_DISABLE) == 0, G_FLAG_SCRIPT_AUTOEXEC);
414 }
415
416 MEM_CacheLimiter_set_maximum(((size_t)U.memcachelimit) * 1024 * 1024);
417 BKE_sound_init(bmain);
418
419 /* Update the temporary directory from the preferences or fallback to the system default. */
420 BKE_tempdir_init(U.tempdir);
421
422 /* Update tablet API preference. */
423 WM_init_tablet_api();
424
425 BLO_sanitize_experimental_features_userpref_blend(&U);
426 }
427
428 /* return codes */
429 #define BKE_READ_EXOTIC_FAIL_PATH -3 /* file format is not supported */
430 #define BKE_READ_EXOTIC_FAIL_FORMAT -2 /* file format is not supported */
431 #define BKE_READ_EXOTIC_FAIL_OPEN -1 /* Can't open the file */
432 #define BKE_READ_EXOTIC_OK_BLEND 0 /* .blend file */
433 #if 0
434 # define BKE_READ_EXOTIC_OK_OTHER 1 /* other supported formats */
435 #endif
436
437 /** \} */
438
439 /* -------------------------------------------------------------------- */
440 /** \name Read Exotic File Formats
441 *
442 * Currently only supports '.blend' files,
443 * we could support registering other file formats and their loaders.
444 * \{ */
445
446 /* intended to check for non-blender formats but for now it only reads blends */
wm_read_exotic(const char * name)447 static int wm_read_exotic(const char *name)
448 {
449 int len;
450 gzFile gzfile;
451 char header[7];
452 int retval;
453
454 /* make sure we're not trying to read a directory.... */
455
456 len = strlen(name);
457 if (len > 0 && ELEM(name[len - 1], '/', '\\')) {
458 retval = BKE_READ_EXOTIC_FAIL_PATH;
459 }
460 else {
461 gzfile = BLI_gzopen(name, "rb");
462 if (gzfile == NULL) {
463 retval = BKE_READ_EXOTIC_FAIL_OPEN;
464 }
465 else {
466 len = gzread(gzfile, header, sizeof(header));
467 gzclose(gzfile);
468 if (len == sizeof(header) && STREQLEN(header, "BLENDER", 7)) {
469 retval = BKE_READ_EXOTIC_OK_BLEND;
470 }
471 else {
472 /* We may want to support loading other file formats
473 * from their header bytes or file extension.
474 * This used to be supported in the code below and may be added
475 * back at some point. */
476 #if 0
477 WM_cursor_wait(true);
478
479 if (is_foo_format(name)) {
480 read_foo(name);
481 retval = BKE_READ_EXOTIC_OK_OTHER;
482 }
483 else
484 #endif
485 {
486 retval = BKE_READ_EXOTIC_FAIL_FORMAT;
487 }
488 #if 0
489 WM_cursor_wait(false);
490 #endif
491 }
492 }
493 }
494
495 return retval;
496 }
497
498 /** \} */
499
500 /* -------------------------------------------------------------------- */
501 /** \name Read Blend-File Shared Utilities
502 * \{ */
503
WM_file_autoexec_init(const char * filepath)504 void WM_file_autoexec_init(const char *filepath)
505 {
506 if (G.f & G_FLAG_SCRIPT_OVERRIDE_PREF) {
507 return;
508 }
509
510 if (G.f & G_FLAG_SCRIPT_AUTOEXEC) {
511 char path[FILE_MAX];
512 BLI_split_dir_part(filepath, path, sizeof(path));
513 if (BKE_autoexec_match(path)) {
514 G.f &= ~G_FLAG_SCRIPT_AUTOEXEC;
515 }
516 }
517 }
518
wm_file_read_report(bContext * C,Main * bmain)519 void wm_file_read_report(bContext *C, Main *bmain)
520 {
521 ReportList *reports = NULL;
522 Scene *sce;
523
524 for (sce = bmain->scenes.first; sce; sce = sce->id.next) {
525 if (sce->r.engine[0] &&
526 BLI_findstring(&R_engines, sce->r.engine, offsetof(RenderEngineType, idname)) == NULL) {
527 if (reports == NULL) {
528 reports = CTX_wm_reports(C);
529 }
530
531 BKE_reportf(reports,
532 RPT_ERROR,
533 "Engine '%s' not available for scene '%s' (an add-on may need to be installed "
534 "or enabled)",
535 sce->r.engine,
536 sce->id.name + 2);
537 }
538 }
539
540 if (reports) {
541 if (!G.background) {
542 WM_report_banner_show();
543 }
544 }
545 }
546
547 /**
548 * Logic shared between #WM_file_read & #wm_homefile_read,
549 * call before loading a file.
550 * \note In the case of #WM_file_read the file may fail to load.
551 * Change here shouldn't cause user-visible changes in that case.
552 */
wm_file_read_pre(bContext * C,bool use_data,bool UNUSED (use_userdef))553 static void wm_file_read_pre(bContext *C, bool use_data, bool UNUSED(use_userdef))
554 {
555 if (use_data) {
556 BKE_callback_exec_null(CTX_data_main(C), BKE_CB_EVT_LOAD_PRE);
557 BLI_timer_on_file_load();
558 }
559
560 /* Always do this as both startup and preferences may have loaded in many font's
561 * at a different zoom level to the file being loaded. */
562 UI_view2d_zoom_cache_reset();
563 }
564
565 /**
566 * Logic shared between #WM_file_read & #wm_homefile_read,
567 * updates to make after reading a file.
568 */
wm_file_read_post(bContext * C,const bool is_startup_file,const bool is_factory_startup,const bool use_data,const bool use_userdef,const bool reset_app_template)569 static void wm_file_read_post(bContext *C,
570 const bool is_startup_file,
571 const bool is_factory_startup,
572 const bool use_data,
573 const bool use_userdef,
574 const bool reset_app_template)
575 {
576 bool addons_loaded = false;
577 wmWindowManager *wm = CTX_wm_manager(C);
578
579 if (use_data) {
580 if (!G.background) {
581 /* remove windows which failed to be added via WM_check */
582 wm_window_ghostwindows_remove_invalid(C, wm);
583 }
584 CTX_wm_window_set(C, wm->windows.first);
585 }
586
587 #ifdef WITH_PYTHON
588 if (is_startup_file) {
589 /* possible python hasn't been initialized */
590 if (CTX_py_init_get(C)) {
591 bool reset_all = use_userdef;
592 if (use_userdef || reset_app_template) {
593 /* Only run when we have a template path found. */
594 if (BKE_appdir_app_template_any()) {
595 BPY_run_string_eval(
596 C, (const char *[]){"bl_app_template_utils", NULL}, "bl_app_template_utils.reset()");
597 reset_all = true;
598 }
599 }
600 if (reset_all) {
601 /* sync addons, these may have changed from the defaults */
602 BPY_run_string_eval(C, (const char *[]){"addon_utils", NULL}, "addon_utils.reset_all()");
603 }
604 if (use_data) {
605 BPY_python_reset(C);
606 }
607 addons_loaded = true;
608 }
609 }
610 else {
611 /* run any texts that were loaded in and flagged as modules */
612 if (use_data) {
613 BPY_python_reset(C);
614 }
615 addons_loaded = true;
616 }
617 #else
618 UNUSED_VARS(is_startup_file, reset_app_template);
619 #endif /* WITH_PYTHON */
620
621 Main *bmain = CTX_data_main(C);
622
623 if (use_userdef) {
624 if (is_factory_startup) {
625 BKE_callback_exec_null(bmain, BKE_CB_EVT_LOAD_FACTORY_USERDEF_POST);
626 }
627 }
628
629 if (use_data) {
630 /* important to do before NULL'ing the context */
631 BKE_callback_exec_null(bmain, BKE_CB_EVT_VERSION_UPDATE);
632 BKE_callback_exec_null(bmain, BKE_CB_EVT_LOAD_POST);
633 if (is_factory_startup) {
634 BKE_callback_exec_null(bmain, BKE_CB_EVT_LOAD_FACTORY_STARTUP_POST);
635 }
636 }
637
638 if (use_data) {
639 WM_operatortype_last_properties_clear_all();
640
641 /* After load post, so for example the driver namespace can be filled
642 * before evaluating the depsgraph. */
643 wm_event_do_depsgraph(C, true);
644
645 ED_editors_init(C);
646
647 #if 1
648 WM_event_add_notifier(C, NC_WM | ND_FILEREAD, NULL);
649 #else
650 WM_msg_publish_static(CTX_wm_message_bus(C), WM_MSG_STATICTYPE_FILE_READ);
651 #endif
652 }
653
654 /* report any errors.
655 * currently disabled if addons aren't yet loaded */
656 if (addons_loaded) {
657 wm_file_read_report(C, bmain);
658 }
659
660 if (use_data) {
661 if (!G.background) {
662 if (wm->undo_stack == NULL) {
663 wm->undo_stack = BKE_undosys_stack_create();
664 }
665 else {
666 BKE_undosys_stack_clear(wm->undo_stack);
667 }
668 BKE_undosys_stack_init_from_main(wm->undo_stack, bmain);
669 BKE_undosys_stack_init_from_context(wm->undo_stack, C);
670 }
671 }
672
673 if (use_data) {
674 if (!G.background) {
675 /* in background mode this makes it hard to load
676 * a blend file and do anything since the screen
677 * won't be set to a valid value again */
678 CTX_wm_window_set(C, NULL); /* exits queues */
679
680 /* Ensure tools are registered. */
681 WM_toolsystem_init(C);
682 }
683 }
684 }
685
686 /** \} */
687
688 /* -------------------------------------------------------------------- */
689 /** \name Read Main Blend-File API
690 * \{ */
691
WM_file_read(bContext * C,const char * filepath,ReportList * reports)692 bool WM_file_read(bContext *C, const char *filepath, ReportList *reports)
693 {
694 /* assume automated tasks with background, don't write recent file list */
695 const bool do_history_file_update = (G.background == false) &&
696 (CTX_wm_manager(C)->op_undo_depth == 0);
697 bool success = false;
698
699 const bool use_data = true;
700 const bool use_userdef = false;
701
702 /* so we can get the error message */
703 errno = 0;
704
705 WM_cursor_wait(1);
706
707 wm_file_read_pre(C, use_data, use_userdef);
708
709 /* first try to append data from exotic file formats... */
710 /* it throws error box when file doesn't exist and returns -1 */
711 /* note; it should set some error message somewhere... (ton) */
712 const int retval = wm_read_exotic(filepath);
713
714 /* we didn't succeed, now try to read Blender file */
715 if (retval == BKE_READ_EXOTIC_OK_BLEND) {
716 const int G_f_orig = G.f;
717 ListBase wmbase;
718
719 /* put aside screens to match with persistent windows later */
720 /* also exit screens and editors */
721 wm_window_match_init(C, &wmbase);
722
723 /* confusing this global... */
724 G.relbase_valid = 1;
725 success = BKE_blendfile_read(
726 C,
727 filepath,
728 /* Loading preferences when the user intended to load a regular file is a security risk,
729 * because the excluded path list is also loaded.
730 * Further it's just confusing if a user loads a file and various preferences change. */
731 &(const struct BlendFileReadParams){
732 .is_startup = false,
733 .skip_flags = BLO_READ_SKIP_USERDEF,
734 },
735 reports);
736
737 /* BKE_file_read sets new Main into context. */
738 Main *bmain = CTX_data_main(C);
739
740 /* when loading startup.blend's, we can be left with a blank path */
741 if (BKE_main_blendfile_path(bmain)[0] != '\0') {
742 G.save_over = 1;
743 }
744 else {
745 G.save_over = 0;
746 G.relbase_valid = 0;
747 }
748
749 /* this flag is initialized by the operator but overwritten on read.
750 * need to re-enable it here else drivers + registered scripts wont work. */
751 if (G.f != G_f_orig) {
752 const int flags_keep = G_FLAG_ALL_RUNTIME;
753 G.f &= G_FLAG_ALL_READFILE;
754 G.f = (G.f & ~flags_keep) | (G_f_orig & flags_keep);
755 }
756
757 /* match the read WM with current WM */
758 wm_window_match_do(C, &wmbase, &bmain->wm, &bmain->wm);
759 WM_check(C); /* opens window(s), checks keymaps */
760
761 if (success) {
762 if (do_history_file_update) {
763 wm_history_file_update();
764 }
765 }
766
767 wm_file_read_post(C, false, false, use_data, use_userdef, false);
768 }
769 #if 0
770 else if (retval == BKE_READ_EXOTIC_OK_OTHER) {
771 BKE_undo_write(C, "Import file");
772 }
773 #endif
774 else if (retval == BKE_READ_EXOTIC_FAIL_OPEN) {
775 BKE_reportf(reports,
776 RPT_ERROR,
777 "Cannot read file '%s': %s",
778 filepath,
779 errno ? strerror(errno) : TIP_("unable to open the file"));
780 }
781 else if (retval == BKE_READ_EXOTIC_FAIL_FORMAT) {
782 BKE_reportf(reports, RPT_ERROR, "File format is not supported in file '%s'", filepath);
783 }
784 else if (retval == BKE_READ_EXOTIC_FAIL_PATH) {
785 BKE_reportf(reports, RPT_ERROR, "File path '%s' invalid", filepath);
786 }
787 else {
788 BKE_reportf(reports, RPT_ERROR, "Unknown error loading '%s'", filepath);
789 BLI_assert(!"invalid 'retval'");
790 }
791
792 if (success == false) {
793 /* remove from recent files list */
794 if (do_history_file_update) {
795 RecentFile *recent = wm_file_history_find(filepath);
796 if (recent) {
797 wm_history_file_free(recent);
798 wm_history_file_write();
799 }
800 }
801 }
802
803 WM_cursor_wait(0);
804
805 return success;
806 }
807
808 static struct {
809 char app_template[64];
810 bool override;
811 } wm_init_state_app_template = {{0}};
812
813 /**
814 * Used for setting app-template from the command line:
815 * - non-empty string: overrides.
816 * - empty string: override, using no app template.
817 * - NULL: clears override.
818 */
WM_init_state_app_template_set(const char * app_template)819 void WM_init_state_app_template_set(const char *app_template)
820 {
821 if (app_template) {
822 STRNCPY(wm_init_state_app_template.app_template, app_template);
823 wm_init_state_app_template.override = true;
824 }
825 else {
826 wm_init_state_app_template.app_template[0] = '\0';
827 wm_init_state_app_template.override = false;
828 }
829 }
830
WM_init_state_app_template_get(void)831 const char *WM_init_state_app_template_get(void)
832 {
833 return wm_init_state_app_template.override ? wm_init_state_app_template.app_template : NULL;
834 }
835
836 /** \} */
837
838 /* -------------------------------------------------------------------- */
839 /** \name Read Startup & Preferences Blend-File API
840 * \{ */
841
842 /**
843 * Called on startup, (context entirely filled with NULLs)
844 * or called for 'New File' both startup.blend and userpref.blend are checked.
845 *
846 * \param use_factory_settings:
847 * Ignore on-disk startup file, use bundled ``datatoc_startup_blend`` instead.
848 * Used for "Restore Factory Settings".
849 *
850 * \param use_userdef: Load factory settings as well as startup file.
851 * Disabled for "File New" we don't want to reload preferences.
852 *
853 * \param filepath_startup_override:
854 * Optional path pointing to an alternative blend file (may be NULL).
855 *
856 * \param app_template_override:
857 * Template to use instead of the template defined in user-preferences.
858 * When not-null, this is written into the user preferences.
859 */
wm_homefile_read(bContext * C,ReportList * reports,bool use_factory_settings,bool use_empty_data,bool use_data,bool use_userdef,const char * filepath_startup_override,const char * app_template_override,bool * r_is_factory_startup)860 void wm_homefile_read(bContext *C,
861 ReportList *reports,
862 bool use_factory_settings,
863 bool use_empty_data,
864 bool use_data,
865 bool use_userdef,
866 const char *filepath_startup_override,
867 const char *app_template_override,
868 bool *r_is_factory_startup)
869 {
870 Main *bmain = G_MAIN; /* Context does not always have valid main pointer here... */
871 ListBase wmbase;
872 bool success = false;
873
874 bool filepath_startup_is_factory = true;
875 char filepath_startup[FILE_MAX];
876 char filepath_userdef[FILE_MAX];
877
878 /* When 'app_template' is set:
879 * '{BLENDER_USER_CONFIG}/{app_template}' */
880 char app_template_system[FILE_MAX];
881 /* When 'app_template' is set:
882 * '{BLENDER_SYSTEM_SCRIPTS}/startup/bl_app_templates_system/{app_template}' */
883 char app_template_config[FILE_MAX];
884
885 eBLOReadSkip skip_flags = 0;
886
887 if (use_data == false) {
888 skip_flags |= BLO_READ_SKIP_DATA;
889 }
890 if (use_userdef == false) {
891 skip_flags |= BLO_READ_SKIP_USERDEF;
892 }
893
894 /* True if we load startup.blend from memory
895 * or use app-template startup.blend which the user hasn't saved. */
896 bool is_factory_startup = true;
897
898 const char *app_template = NULL;
899 bool update_defaults = false;
900
901 if (filepath_startup_override != NULL) {
902 /* pass */
903 }
904 else if (app_template_override) {
905 /* This may be clearing the current template by setting to an empty string. */
906 app_template = app_template_override;
907 }
908 else if (!use_factory_settings && U.app_template[0]) {
909 app_template = U.app_template;
910 }
911
912 const bool reset_app_template = ((!app_template && U.app_template[0]) ||
913 (app_template && !STREQ(app_template, U.app_template)));
914
915 /* options exclude eachother */
916 BLI_assert((use_factory_settings && filepath_startup_override) == 0);
917
918 if ((G.f & G_FLAG_SCRIPT_OVERRIDE_PREF) == 0) {
919 SET_FLAG_FROM_TEST(G.f, (U.flag & USER_SCRIPT_AUTOEXEC_DISABLE) == 0, G_FLAG_SCRIPT_AUTOEXEC);
920 }
921
922 if (use_data) {
923 if (reset_app_template) {
924 /* Always load UI when switching to another template. */
925 G.fileflags &= ~G_FILE_NO_UI;
926 }
927 }
928
929 if (use_userdef || reset_app_template) {
930 #ifdef WITH_PYTHON
931 /* This only runs once Blender has already started. */
932 if (CTX_py_init_get(C)) {
933 /* This is restored by 'wm_file_read_post', disable before loading any preferences
934 * so an add-on can read their own preferences when un-registering,
935 * and use new preferences if/when re-registering, see T67577.
936 *
937 * Note that this fits into 'wm_file_read_pre' function but gets messy
938 * since we need to know if 'reset_app_template' is true. */
939 BPY_run_string_eval(C, (const char *[]){"addon_utils", NULL}, "addon_utils.disable_all()");
940 }
941 #endif /* WITH_PYTHON */
942 }
943
944 wm_file_read_pre(C, use_data, use_userdef);
945
946 if (use_data) {
947 G.relbase_valid = 0;
948
949 /* put aside screens to match with persistent windows later */
950 wm_window_match_init(C, &wmbase);
951 }
952
953 filepath_startup[0] = '\0';
954 filepath_userdef[0] = '\0';
955 app_template_system[0] = '\0';
956 app_template_config[0] = '\0';
957
958 const char *const cfgdir = BKE_appdir_folder_id(BLENDER_USER_CONFIG, NULL);
959 if (!use_factory_settings) {
960 if (cfgdir) {
961 BLI_path_join(
962 filepath_startup, sizeof(filepath_startup), cfgdir, BLENDER_STARTUP_FILE, NULL);
963 filepath_startup_is_factory = false;
964 if (use_userdef) {
965 BLI_path_join(
966 filepath_userdef, sizeof(filepath_startup), cfgdir, BLENDER_USERPREF_FILE, NULL);
967 }
968 }
969 else {
970 use_factory_settings = true;
971 }
972
973 if (filepath_startup_override) {
974 BLI_strncpy(filepath_startup, filepath_startup_override, FILE_MAX);
975 filepath_startup_is_factory = false;
976 }
977 }
978
979 /* load preferences before startup.blend */
980 if (use_userdef) {
981 if (!use_factory_settings && BLI_exists(filepath_userdef)) {
982 UserDef *userdef = BKE_blendfile_userdef_read(filepath_userdef, NULL);
983 if (userdef != NULL) {
984 BKE_blender_userdef_data_set_and_free(userdef);
985 userdef = NULL;
986
987 skip_flags |= BLO_READ_SKIP_USERDEF;
988 printf("Read prefs: %s\n", filepath_userdef);
989 }
990 }
991 }
992
993 if ((app_template != NULL) && (app_template[0] != '\0')) {
994 if (!BKE_appdir_app_template_id_search(
995 app_template, app_template_system, sizeof(app_template_system))) {
996 /* Can safely continue with code below, just warn it's not found. */
997 BKE_reportf(reports, RPT_WARNING, "Application Template '%s' not found", app_template);
998 }
999
1000 /* Insert template name into startup file. */
1001
1002 /* note that the path is being set even when 'use_factory_settings == true'
1003 * this is done so we can load a templates factory-settings */
1004 if (!use_factory_settings) {
1005 BLI_path_join(app_template_config, sizeof(app_template_config), cfgdir, app_template, NULL);
1006 BLI_path_join(filepath_startup,
1007 sizeof(filepath_startup),
1008 app_template_config,
1009 BLENDER_STARTUP_FILE,
1010 NULL);
1011 filepath_startup_is_factory = false;
1012 if (BLI_access(filepath_startup, R_OK) != 0) {
1013 filepath_startup[0] = '\0';
1014 }
1015 }
1016 else {
1017 filepath_startup[0] = '\0';
1018 }
1019
1020 if (filepath_startup[0] == '\0') {
1021 BLI_path_join(filepath_startup,
1022 sizeof(filepath_startup),
1023 app_template_system,
1024 BLENDER_STARTUP_FILE,
1025 NULL);
1026 filepath_startup_is_factory = true;
1027
1028 /* Update defaults only for system templates. */
1029 update_defaults = true;
1030 }
1031 }
1032
1033 if (!use_factory_settings || (filepath_startup[0] != '\0')) {
1034 if (BLI_access(filepath_startup, R_OK) == 0) {
1035 success = BKE_blendfile_read_ex(C,
1036 filepath_startup,
1037 &(const struct BlendFileReadParams){
1038 .is_startup = true,
1039 .skip_flags = skip_flags | BLO_READ_SKIP_USERDEF,
1040 },
1041 NULL,
1042 update_defaults && use_data,
1043 app_template);
1044 }
1045 if (success) {
1046 is_factory_startup = filepath_startup_is_factory;
1047 }
1048 }
1049
1050 if (use_userdef) {
1051 if ((skip_flags & BLO_READ_SKIP_USERDEF) == 0) {
1052 UserDef *userdef_default = BKE_blendfile_userdef_from_defaults();
1053 BKE_blender_userdef_data_set_and_free(userdef_default);
1054 skip_flags |= BLO_READ_SKIP_USERDEF;
1055 }
1056 }
1057
1058 if (success == false && filepath_startup_override && reports) {
1059 /* We can not return from here because wm is already reset */
1060 BKE_reportf(reports, RPT_ERROR, "Could not read '%s'", filepath_startup_override);
1061 }
1062
1063 if (success == false) {
1064 success = BKE_blendfile_read_from_memory_ex(C,
1065 datatoc_startup_blend,
1066 datatoc_startup_blend_size,
1067 &(const struct BlendFileReadParams){
1068 .is_startup = true,
1069 .skip_flags = skip_flags,
1070 },
1071 NULL,
1072 true,
1073 NULL);
1074
1075 if (use_data && BLI_listbase_is_empty(&wmbase)) {
1076 wm_clear_default_size(C);
1077 }
1078 }
1079
1080 if (use_empty_data) {
1081 BKE_blendfile_read_make_empty(C);
1082 }
1083
1084 /* Load template preferences,
1085 * unlike regular preferences we only use some of the settings,
1086 * see: BKE_blender_userdef_set_app_template */
1087 if (app_template_system[0] != '\0') {
1088 char temp_path[FILE_MAX];
1089 temp_path[0] = '\0';
1090 if (!use_factory_settings) {
1091 BLI_path_join(
1092 temp_path, sizeof(temp_path), app_template_config, BLENDER_USERPREF_FILE, NULL);
1093 if (BLI_access(temp_path, R_OK) != 0) {
1094 temp_path[0] = '\0';
1095 }
1096 }
1097
1098 if (temp_path[0] == '\0') {
1099 BLI_path_join(
1100 temp_path, sizeof(temp_path), app_template_system, BLENDER_USERPREF_FILE, NULL);
1101 }
1102
1103 if (use_userdef) {
1104 UserDef *userdef_template = NULL;
1105 /* just avoids missing file warning */
1106 if (BLI_exists(temp_path)) {
1107 userdef_template = BKE_blendfile_userdef_read(temp_path, NULL);
1108 }
1109 if (userdef_template == NULL) {
1110 /* we need to have preferences load to overwrite preferences from previous template */
1111 userdef_template = BKE_blendfile_userdef_from_defaults();
1112 }
1113 if (userdef_template) {
1114 BKE_blender_userdef_app_template_data_set_and_free(userdef_template);
1115 userdef_template = NULL;
1116 }
1117 }
1118 }
1119
1120 if (app_template_override) {
1121 BLI_strncpy(U.app_template, app_template_override, sizeof(U.app_template));
1122 }
1123
1124 bmain = CTX_data_main(C);
1125
1126 if (use_userdef) {
1127 /* check userdef before open window, keymaps etc */
1128 wm_init_userdef(bmain);
1129 }
1130
1131 if (use_data) {
1132 /* match the read WM with current WM */
1133 wm_window_match_do(C, &wmbase, &bmain->wm, &bmain->wm);
1134 }
1135
1136 if (use_userdef) {
1137 /* Clear keymaps because the current default keymap may have been initialized
1138 * from user preferences, which have been reset. */
1139 for (wmWindowManager *wm = bmain->wm.first; wm; wm = wm->id.next) {
1140 if (wm->defaultconf) {
1141 wm->defaultconf->flag &= ~KEYCONF_INIT_DEFAULT;
1142 }
1143 }
1144 }
1145
1146 if (use_data) {
1147 WM_check(C); /* opens window(s), checks keymaps */
1148
1149 bmain->name[0] = '\0';
1150
1151 /* start with save preference untitled.blend */
1152 G.save_over = 0;
1153 }
1154
1155 wm_file_read_post(C, true, is_factory_startup, use_data, use_userdef, reset_app_template);
1156
1157 if (r_is_factory_startup) {
1158 *r_is_factory_startup = is_factory_startup;
1159 }
1160 }
1161
1162 /* -------------------------------------------------------------------- */
1163 /** \name Blend-File History API
1164 * \{ */
1165
wm_history_file_read(void)1166 void wm_history_file_read(void)
1167 {
1168 char name[FILE_MAX];
1169 LinkNode *l, *lines;
1170 struct RecentFile *recent;
1171 const char *line;
1172 int num;
1173 const char *const cfgdir = BKE_appdir_folder_id(BLENDER_USER_CONFIG, NULL);
1174
1175 if (!cfgdir) {
1176 return;
1177 }
1178
1179 BLI_join_dirfile(name, sizeof(name), cfgdir, BLENDER_HISTORY_FILE);
1180
1181 lines = BLI_file_read_as_lines(name);
1182
1183 BLI_listbase_clear(&G.recent_files);
1184
1185 /* read list of recent opened files from recent-files.txt to memory */
1186 for (l = lines, num = 0; l && (num < U.recent_files); l = l->next) {
1187 line = l->link;
1188 /* don't check if files exist, causes slow startup for remote/external drives */
1189 if (line[0]) {
1190 recent = (RecentFile *)MEM_mallocN(sizeof(RecentFile), "RecentFile");
1191 BLI_addtail(&(G.recent_files), recent);
1192 recent->filepath = BLI_strdup(line);
1193 num++;
1194 }
1195 }
1196
1197 BLI_file_free_lines(lines);
1198 }
1199
wm_history_file_new(const char * filepath)1200 static RecentFile *wm_history_file_new(const char *filepath)
1201 {
1202 RecentFile *recent = MEM_mallocN(sizeof(RecentFile), "RecentFile");
1203 recent->filepath = BLI_strdup(filepath);
1204 return recent;
1205 }
1206
wm_history_file_free(RecentFile * recent)1207 static void wm_history_file_free(RecentFile *recent)
1208 {
1209 BLI_assert(BLI_findindex(&G.recent_files, recent) != -1);
1210 MEM_freeN(recent->filepath);
1211 BLI_freelinkN(&G.recent_files, recent);
1212 }
1213
wm_file_history_find(const char * filepath)1214 static RecentFile *wm_file_history_find(const char *filepath)
1215 {
1216 return BLI_findstring_ptr(&G.recent_files, filepath, offsetof(RecentFile, filepath));
1217 }
1218
1219 /**
1220 * Write #BLENDER_HISTORY_FILE as-is, without checking the environment
1221 * (that's handled by #wm_history_file_update).
1222 */
wm_history_file_write(void)1223 static void wm_history_file_write(void)
1224 {
1225 const char *user_config_dir;
1226 char name[FILE_MAX];
1227 FILE *fp;
1228
1229 /* will be NULL in background mode */
1230 user_config_dir = BKE_appdir_folder_id_create(BLENDER_USER_CONFIG, NULL);
1231 if (!user_config_dir) {
1232 return;
1233 }
1234
1235 BLI_join_dirfile(name, sizeof(name), user_config_dir, BLENDER_HISTORY_FILE);
1236
1237 fp = BLI_fopen(name, "w");
1238 if (fp) {
1239 struct RecentFile *recent;
1240 for (recent = G.recent_files.first; recent; recent = recent->next) {
1241 fprintf(fp, "%s\n", recent->filepath);
1242 }
1243 fclose(fp);
1244 }
1245 }
1246
1247 /**
1248 * Run after saving a file to refresh the #BLENDER_HISTORY_FILE list.
1249 */
wm_history_file_update(void)1250 static void wm_history_file_update(void)
1251 {
1252 RecentFile *recent;
1253 const char *blendfile_name = BKE_main_blendfile_path_from_global();
1254
1255 /* no write history for recovered startup files */
1256 if (blendfile_name[0] == '\0') {
1257 return;
1258 }
1259
1260 recent = G.recent_files.first;
1261 /* refresh recent-files.txt of recent opened files, when current file was changed */
1262 if (!(recent) || (BLI_path_cmp(recent->filepath, blendfile_name) != 0)) {
1263
1264 recent = wm_file_history_find(blendfile_name);
1265 if (recent) {
1266 BLI_remlink(&G.recent_files, recent);
1267 }
1268 else {
1269 RecentFile *recent_next;
1270 for (recent = BLI_findlink(&G.recent_files, U.recent_files - 1); recent;
1271 recent = recent_next) {
1272 recent_next = recent->next;
1273 wm_history_file_free(recent);
1274 }
1275 recent = wm_history_file_new(blendfile_name);
1276 }
1277
1278 /* add current file to the beginning of list */
1279 BLI_addhead(&(G.recent_files), recent);
1280
1281 /* write current file to recent-files.txt */
1282 wm_history_file_write();
1283
1284 /* also update most recent files on System */
1285 GHOST_addToSystemRecentFiles(blendfile_name);
1286 }
1287 }
1288
1289 /** \} */
1290
1291 /* -------------------------------------------------------------------- */
1292 /** \name Save Main Blend-File (internal)
1293 * \{ */
1294
1295 /* screen can be NULL */
blend_file_thumb(const bContext * C,Scene * scene,bScreen * screen,BlendThumbnail ** thumb_pt)1296 static ImBuf *blend_file_thumb(const bContext *C,
1297 Scene *scene,
1298 bScreen *screen,
1299 BlendThumbnail **thumb_pt)
1300 {
1301 /* will be scaled down, but gives some nice oversampling */
1302 ImBuf *ibuf;
1303 BlendThumbnail *thumb;
1304 wmWindowManager *wm = CTX_wm_manager(C);
1305 const float pixelsize_old = U.pixelsize;
1306 wmWindow *windrawable_old = wm->windrawable;
1307 char err_out[256] = "unknown";
1308
1309 /* screen if no camera found */
1310 ScrArea *area = NULL;
1311 ARegion *region = NULL;
1312 View3D *v3d = NULL;
1313
1314 /* In case we are given a valid thumbnail data, just generate image from it. */
1315 if (*thumb_pt) {
1316 thumb = *thumb_pt;
1317 return BKE_main_thumbnail_to_imbuf(NULL, thumb);
1318 }
1319
1320 /* scene can be NULL if running a script at startup and calling the save operator */
1321 if (G.background || scene == NULL) {
1322 return NULL;
1323 }
1324
1325 if ((scene->camera == NULL) && (screen != NULL)) {
1326 area = BKE_screen_find_big_area(screen, SPACE_VIEW3D, 0);
1327 region = BKE_area_find_region_type(area, RGN_TYPE_WINDOW);
1328 if (region) {
1329 v3d = area->spacedata.first;
1330 }
1331 }
1332
1333 if (scene->camera == NULL && v3d == NULL) {
1334 return NULL;
1335 }
1336
1337 /* gets scaled to BLEN_THUMB_SIZE */
1338 Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
1339
1340 /* Note that with scaling, this ends up being 0.5,
1341 * as it's a thumbnail, we don't need object centers and friends to be 1:1 size. */
1342 U.pixelsize = 1.0f;
1343
1344 if (scene->camera) {
1345 ibuf = ED_view3d_draw_offscreen_imbuf_simple(depsgraph,
1346 scene,
1347 NULL,
1348 OB_SOLID,
1349 scene->camera,
1350 BLEN_THUMB_SIZE * 2,
1351 BLEN_THUMB_SIZE * 2,
1352 IB_rect,
1353 V3D_OFSDRAW_NONE,
1354 R_ALPHAPREMUL,
1355 NULL,
1356 NULL,
1357 err_out);
1358 }
1359 else {
1360 ibuf = ED_view3d_draw_offscreen_imbuf(depsgraph,
1361 scene,
1362 OB_SOLID,
1363 v3d,
1364 region,
1365 BLEN_THUMB_SIZE * 2,
1366 BLEN_THUMB_SIZE * 2,
1367 IB_rect,
1368 R_ALPHAPREMUL,
1369 NULL,
1370 NULL,
1371 err_out);
1372 }
1373
1374 U.pixelsize = pixelsize_old;
1375
1376 /* Reset to old drawable. */
1377 if (windrawable_old) {
1378 wm_window_make_drawable(wm, windrawable_old);
1379 }
1380 else {
1381 wm_window_clear_drawable(wm);
1382 }
1383
1384 if (ibuf) {
1385 float aspect = (scene->r.xsch * scene->r.xasp) / (scene->r.ysch * scene->r.yasp);
1386
1387 /* dirty oversampling */
1388 IMB_scaleImBuf(ibuf, BLEN_THUMB_SIZE, BLEN_THUMB_SIZE);
1389
1390 /* add pretty overlay */
1391 IMB_thumb_overlay_blend(ibuf->rect, ibuf->x, ibuf->y, aspect);
1392
1393 thumb = BKE_main_thumbnail_from_imbuf(NULL, ibuf);
1394 }
1395 else {
1396 /* '*thumb_pt' needs to stay NULL to prevent a bad thumbnail from being handled */
1397 fprintf(stderr, "blend_file_thumb failed to create thumbnail: %s\n", err_out);
1398 thumb = NULL;
1399 }
1400
1401 /* must be freed by caller */
1402 *thumb_pt = thumb;
1403
1404 return ibuf;
1405 }
1406
1407 /* easy access from gdb */
write_crash_blend(void)1408 bool write_crash_blend(void)
1409 {
1410 char path[FILE_MAX];
1411
1412 BLI_strncpy(path, BKE_main_blendfile_path_from_global(), sizeof(path));
1413 BLI_path_extension_replace(path, sizeof(path), "_crash.blend");
1414 if (BLO_write_file(G_MAIN, path, G.fileflags, &(const struct BlendFileWriteParams){0}, NULL)) {
1415 printf("written: %s\n", path);
1416 return 1;
1417 }
1418 printf("failed: %s\n", path);
1419 return 0;
1420 }
1421
1422 /**
1423 * \see #wm_homefile_write_exec wraps #BLO_write_file in a similar way.
1424 */
wm_file_write(bContext * C,const char * filepath,int fileflags,eBLO_WritePathRemap remap_mode,bool use_save_as_copy,ReportList * reports)1425 static bool wm_file_write(bContext *C,
1426 const char *filepath,
1427 int fileflags,
1428 eBLO_WritePathRemap remap_mode,
1429 bool use_save_as_copy,
1430 ReportList *reports)
1431 {
1432 Main *bmain = CTX_data_main(C);
1433 Library *li;
1434 int len;
1435 int ok = false;
1436 BlendThumbnail *thumb, *main_thumb;
1437 ImBuf *ibuf_thumb = NULL;
1438
1439 len = strlen(filepath);
1440
1441 if (len == 0) {
1442 BKE_report(reports, RPT_ERROR, "Path is empty, cannot save");
1443 return ok;
1444 }
1445
1446 if (len >= FILE_MAX) {
1447 BKE_report(reports, RPT_ERROR, "Path too long, cannot save");
1448 return ok;
1449 }
1450
1451 /* Check if file write permission is ok */
1452 if (BLI_exists(filepath) && !BLI_file_is_writable(filepath)) {
1453 BKE_reportf(reports, RPT_ERROR, "Cannot save blend file, path '%s' is not writable", filepath);
1454 return ok;
1455 }
1456
1457 /* note: used to replace the file extension (to ensure '.blend'),
1458 * no need to now because the operator ensures,
1459 * its handy for scripts to save to a predefined name without blender editing it */
1460
1461 /* send the OnSave event */
1462 for (li = bmain->libraries.first; li; li = li->id.next) {
1463 if (BLI_path_cmp(li->filepath_abs, filepath) == 0) {
1464 BKE_reportf(reports, RPT_ERROR, "Cannot overwrite used library '%.240s'", filepath);
1465 return ok;
1466 }
1467 }
1468
1469 /* Call pre-save callbacks before writing preview,
1470 * that way you can generate custom file thumbnail. */
1471 BKE_callback_exec_null(bmain, BKE_CB_EVT_SAVE_PRE);
1472
1473 /* Enforce full override check/generation on file save. */
1474 BKE_lib_override_library_main_operations_create(bmain, true);
1475
1476 /* blend file thumbnail */
1477 /* Save before exit_editmode, otherwise derivedmeshes for shared data corrupt T27765. */
1478 /* Main now can store a '.blend' thumbnail, useful for background mode
1479 * or thumbnail customization. */
1480 main_thumb = thumb = bmain->blen_thumb;
1481 if ((U.flag & USER_SAVE_PREVIEWS) && BLI_thread_is_main()) {
1482 ibuf_thumb = blend_file_thumb(C, CTX_data_scene(C), CTX_wm_screen(C), &thumb);
1483 }
1484
1485 /* operator now handles overwrite checks */
1486
1487 if (G.fileflags & G_FILE_AUTOPACK) {
1488 BKE_packedfile_pack_all(bmain, reports, false);
1489 }
1490
1491 /* don't forget not to return without! */
1492 WM_cursor_wait(1);
1493
1494 ED_editors_flush_edits(bmain);
1495
1496 /* first time saving */
1497 /* XXX temp solution to solve bug, real fix coming (ton) */
1498 if ((BKE_main_blendfile_path(bmain)[0] == '\0') && (use_save_as_copy == false)) {
1499 BLI_strncpy(bmain->name, filepath, sizeof(bmain->name));
1500 }
1501
1502 /* XXX temp solution to solve bug, real fix coming (ton) */
1503 bmain->recovered = 0;
1504
1505 if (BLO_write_file(CTX_data_main(C),
1506 filepath,
1507 fileflags,
1508 &(const struct BlendFileWriteParams){
1509 .remap_mode = remap_mode,
1510 .use_save_versions = true,
1511 .use_save_as_copy = use_save_as_copy,
1512 .thumb = thumb,
1513 },
1514 reports)) {
1515 const bool do_history_file_update = (G.background == false) &&
1516 (CTX_wm_manager(C)->op_undo_depth == 0);
1517
1518 if (use_save_as_copy == false) {
1519 G.relbase_valid = 1;
1520 BLI_strncpy(bmain->name, filepath, sizeof(bmain->name)); /* is guaranteed current file */
1521
1522 G.save_over = 1; /* disable untitled.blend convention */
1523 }
1524
1525 SET_FLAG_FROM_TEST(G.fileflags, fileflags & G_FILE_COMPRESS, G_FILE_COMPRESS);
1526
1527 /* prevent background mode scripts from clobbering history */
1528 if (do_history_file_update) {
1529 wm_history_file_update();
1530 }
1531
1532 BKE_callback_exec_null(bmain, BKE_CB_EVT_SAVE_POST);
1533
1534 /* run this function after because the file cant be written before the blend is */
1535 if (ibuf_thumb) {
1536 IMB_thumb_delete(filepath, THB_FAIL); /* without this a failed thumb overrides */
1537 ibuf_thumb = IMB_thumb_create(filepath, THB_LARGE, THB_SOURCE_BLEND, ibuf_thumb);
1538 }
1539
1540 /* Without this there is no feedback the file was saved. */
1541 BKE_reportf(reports, RPT_INFO, "Saved \"%s\"", BLI_path_basename(filepath));
1542
1543 /* Success. */
1544 ok = true;
1545 }
1546
1547 if (ibuf_thumb) {
1548 IMB_freeImBuf(ibuf_thumb);
1549 }
1550 if (thumb && thumb != main_thumb) {
1551 MEM_freeN(thumb);
1552 }
1553
1554 WM_cursor_wait(0);
1555
1556 return ok;
1557 }
1558
1559 /** \} */
1560
1561 /* -------------------------------------------------------------------- */
1562 /** \name Auto-Save API
1563 * \{ */
1564
wm_autosave_location(char * filepath)1565 void wm_autosave_location(char *filepath)
1566 {
1567 const int pid = abs(getpid());
1568 char path[1024];
1569 #ifdef WIN32
1570 const char *savedir;
1571 #endif
1572
1573 if (G_MAIN && G.relbase_valid) {
1574 const char *basename = BLI_path_basename(BKE_main_blendfile_path_from_global());
1575 int len = strlen(basename) - 6;
1576 BLI_snprintf(path, sizeof(path), "%.*s_%d_autosave.blend", len, basename, pid);
1577 }
1578 else {
1579 BLI_snprintf(path, sizeof(path), "%d_autosave.blend", pid);
1580 }
1581
1582 #ifdef WIN32
1583 /* XXX Need to investigate how to handle default location of '/tmp/'
1584 * This is a relative directory on Windows, and it may be
1585 * found. Example:
1586 * Blender installed on D:\ drive, D:\ drive has D:\tmp\
1587 * Now, BLI_exists() will find '/tmp/' exists, but
1588 * BLI_make_file_string will create string that has it most likely on C:\
1589 * through BLI_windows_get_default_root_dir().
1590 * If there is no C:\tmp autosave fails. */
1591 if (!BLI_exists(BKE_tempdir_base())) {
1592 savedir = BKE_appdir_folder_id_create(BLENDER_USER_AUTOSAVE, NULL);
1593 BLI_make_file_string("/", filepath, savedir, path);
1594 return;
1595 }
1596 #endif
1597
1598 BLI_join_dirfile(filepath, FILE_MAX, BKE_tempdir_base(), path);
1599 }
1600
WM_autosave_init(wmWindowManager * wm)1601 void WM_autosave_init(wmWindowManager *wm)
1602 {
1603 wm_autosave_timer_ended(wm);
1604
1605 if (U.flag & USER_AUTOSAVE) {
1606 wm->autosavetimer = WM_event_add_timer(wm, NULL, TIMERAUTOSAVE, U.savetime * 60.0);
1607 }
1608 }
1609
wm_autosave_timer(Main * bmain,wmWindowManager * wm,wmTimer * UNUSED (wt))1610 void wm_autosave_timer(Main *bmain, wmWindowManager *wm, wmTimer *UNUSED(wt))
1611 {
1612 char filepath[FILE_MAX];
1613
1614 WM_event_remove_timer(wm, NULL, wm->autosavetimer);
1615
1616 /* If a modal operator is running, don't autosave because we might not be in
1617 * a valid state to save. But try again in 10ms. */
1618 LISTBASE_FOREACH (wmWindow *, win, &wm->windows) {
1619 LISTBASE_FOREACH (wmEventHandler *, handler_base, &win->modalhandlers) {
1620 if (handler_base->type == WM_HANDLER_TYPE_OP) {
1621 wmEventHandler_Op *handler = (wmEventHandler_Op *)handler_base;
1622 if (handler->op) {
1623 wm->autosavetimer = WM_event_add_timer(wm, NULL, TIMERAUTOSAVE, 0.01);
1624 return;
1625 }
1626 }
1627 }
1628 }
1629
1630 wm_autosave_location(filepath);
1631
1632 if (U.uiflag & USER_GLOBALUNDO) {
1633 /* fast save of last undobuffer, now with UI */
1634 struct MemFile *memfile = ED_undosys_stack_memfile_get_active(wm->undo_stack);
1635 if (memfile) {
1636 BLO_memfile_write_file(memfile, filepath);
1637 }
1638 }
1639 else {
1640 /* Save as regular blend file. */
1641 const int fileflags = G.fileflags & ~G_FILE_COMPRESS;
1642
1643 ED_editors_flush_edits(bmain);
1644
1645 /* Error reporting into console. */
1646 BLO_write_file(bmain, filepath, fileflags, &(const struct BlendFileWriteParams){0}, NULL);
1647 }
1648 /* do timer after file write, just in case file write takes a long time */
1649 wm->autosavetimer = WM_event_add_timer(wm, NULL, TIMERAUTOSAVE, U.savetime * 60.0);
1650 }
1651
wm_autosave_timer_ended(wmWindowManager * wm)1652 void wm_autosave_timer_ended(wmWindowManager *wm)
1653 {
1654 if (wm->autosavetimer) {
1655 WM_event_remove_timer(wm, NULL, wm->autosavetimer);
1656 wm->autosavetimer = NULL;
1657 }
1658 }
1659
wm_autosave_delete(void)1660 void wm_autosave_delete(void)
1661 {
1662 char filename[FILE_MAX];
1663
1664 wm_autosave_location(filename);
1665
1666 if (BLI_exists(filename)) {
1667 char str[FILE_MAX];
1668 BLI_join_dirfile(str, sizeof(str), BKE_tempdir_base(), BLENDER_QUIT_FILE);
1669
1670 /* if global undo; remove tempsave, otherwise rename */
1671 if (U.uiflag & USER_GLOBALUNDO) {
1672 BLI_delete(filename, false, false);
1673 }
1674 else {
1675 BLI_rename(filename, str);
1676 }
1677 }
1678 }
1679
wm_autosave_read(bContext * C,ReportList * reports)1680 void wm_autosave_read(bContext *C, ReportList *reports)
1681 {
1682 char filename[FILE_MAX];
1683
1684 wm_autosave_location(filename);
1685 WM_file_read(C, filename, reports);
1686 }
1687
1688 /** \} */
1689
1690 /* -------------------------------------------------------------------- */
1691 /** \name Initialize WM_OT_open_xxx properties
1692 *
1693 * Check if load_ui was set by the caller.
1694 * Fall back to user preference when file flags not specified.
1695 *
1696 * \{ */
1697
wm_open_init_load_ui(wmOperator * op,bool use_prefs)1698 void wm_open_init_load_ui(wmOperator *op, bool use_prefs)
1699 {
1700 PropertyRNA *prop = RNA_struct_find_property(op->ptr, "load_ui");
1701 if (!RNA_property_is_set(op->ptr, prop)) {
1702 bool value = use_prefs ? ((U.flag & USER_FILENOUI) == 0) : ((G.fileflags & G_FILE_NO_UI) == 0);
1703
1704 RNA_property_boolean_set(op->ptr, prop, value);
1705 }
1706 }
1707
wm_open_init_use_scripts(wmOperator * op,bool use_prefs)1708 void wm_open_init_use_scripts(wmOperator *op, bool use_prefs)
1709 {
1710 PropertyRNA *prop = RNA_struct_find_property(op->ptr, "use_scripts");
1711 if (!RNA_property_is_set(op->ptr, prop)) {
1712 /* use G_FLAG_SCRIPT_AUTOEXEC rather than the userpref because this means if
1713 * the flag has been disabled from the command line, then opening
1714 * from the menu wont enable this setting. */
1715 bool value = use_prefs ? ((U.flag & USER_SCRIPT_AUTOEXEC_DISABLE) == 0) :
1716 ((G.f & G_FLAG_SCRIPT_AUTOEXEC) != 0);
1717
1718 RNA_property_boolean_set(op->ptr, prop, value);
1719 }
1720 }
1721
1722 /** \} */
1723
1724 /* -------------------------------------------------------------------- */
1725 /** \name Startup File Save Operator
1726 * \{ */
1727
1728 /**
1729 * \see #wm_file_write wraps #BLO_write_file in a similar way.
1730 * \return success.
1731 */
wm_homefile_write_exec(bContext * C,wmOperator * op)1732 static int wm_homefile_write_exec(bContext *C, wmOperator *op)
1733 {
1734 Main *bmain = CTX_data_main(C);
1735 wmWindowManager *wm = CTX_wm_manager(C);
1736 wmWindow *win = CTX_wm_window(C);
1737 char filepath[FILE_MAX];
1738 int fileflags;
1739
1740 const char *app_template = U.app_template[0] ? U.app_template : NULL;
1741 const char *const cfgdir = BKE_appdir_folder_id_create(BLENDER_USER_CONFIG, app_template);
1742 if (cfgdir == NULL) {
1743 BKE_report(op->reports, RPT_ERROR, "Unable to create user config path");
1744 return OPERATOR_CANCELLED;
1745 }
1746
1747 BKE_callback_exec_null(bmain, BKE_CB_EVT_SAVE_PRE);
1748
1749 /* check current window and close it if temp */
1750 if (win && WM_window_is_temp_screen(win)) {
1751 wm_window_close(C, wm, win);
1752 }
1753
1754 /* update keymaps in user preferences */
1755 WM_keyconfig_update(wm);
1756
1757 BLI_path_join(filepath, sizeof(filepath), cfgdir, BLENDER_STARTUP_FILE, NULL);
1758
1759 printf("Writing homefile: '%s' ", filepath);
1760
1761 ED_editors_flush_edits(bmain);
1762
1763 /* Force save as regular blend file. */
1764 fileflags = G.fileflags & ~G_FILE_COMPRESS;
1765
1766 if (BLO_write_file(bmain,
1767 filepath,
1768 fileflags,
1769 &(const struct BlendFileWriteParams){
1770 .remap_mode = BLO_WRITE_PATH_REMAP_RELATIVE,
1771 },
1772 op->reports) == 0) {
1773 printf("fail\n");
1774 return OPERATOR_CANCELLED;
1775 }
1776
1777 printf("ok\n");
1778
1779 G.save_over = 0;
1780
1781 BKE_callback_exec_null(bmain, BKE_CB_EVT_SAVE_POST);
1782
1783 return OPERATOR_FINISHED;
1784 }
1785
WM_OT_save_homefile(wmOperatorType * ot)1786 void WM_OT_save_homefile(wmOperatorType *ot)
1787 {
1788 ot->name = "Save Startup File";
1789 ot->idname = "WM_OT_save_homefile";
1790 ot->description = "Make the current file the default .blend file";
1791
1792 ot->invoke = WM_operator_confirm;
1793 ot->exec = wm_homefile_write_exec;
1794 }
1795
1796 /** \} */
1797
1798 /* -------------------------------------------------------------------- */
1799 /** \name Write Preferences Operator
1800 * \{ */
1801
1802 /* Only save the prefs block. operator entry */
wm_userpref_write_exec(bContext * C,wmOperator * op)1803 static int wm_userpref_write_exec(bContext *C, wmOperator *op)
1804 {
1805 wmWindowManager *wm = CTX_wm_manager(C);
1806
1807 /* Update keymaps in user preferences. */
1808 WM_keyconfig_update(wm);
1809
1810 const bool ok = BKE_blendfile_userdef_write_all(op->reports);
1811
1812 return ok ? OPERATOR_FINISHED : OPERATOR_CANCELLED;
1813 }
1814
WM_OT_save_userpref(wmOperatorType * ot)1815 void WM_OT_save_userpref(wmOperatorType *ot)
1816 {
1817 ot->name = "Save Preferences";
1818 ot->idname = "WM_OT_save_userpref";
1819 ot->description = "Make the current preferences default";
1820
1821 ot->invoke = WM_operator_confirm;
1822 ot->exec = wm_userpref_write_exec;
1823 }
1824
1825 /** \} */
1826
1827 /* -------------------------------------------------------------------- */
1828 /** \name Read Preferences Operator
1829 * \{ */
1830
1831 /**
1832 * When reading preferences, there are some exceptions for values which are reset.
1833 */
wm_userpref_read_exceptions(UserDef * userdef_curr,const UserDef * userdef_prev)1834 static void wm_userpref_read_exceptions(UserDef *userdef_curr, const UserDef *userdef_prev)
1835 {
1836 #define USERDEF_RESTORE(member) \
1837 { \
1838 userdef_curr->member = userdef_prev->member; \
1839 } \
1840 ((void)0)
1841
1842 /* Current visible preferences category. */
1843 USERDEF_RESTORE(space_data.section_active);
1844
1845 #undef USERDEF_RESTORE
1846 }
1847
rna_struct_update_when_changed(bContext * C,Main * bmain,PointerRNA * ptr_a,PointerRNA * ptr_b)1848 static void rna_struct_update_when_changed(bContext *C,
1849 Main *bmain,
1850 PointerRNA *ptr_a,
1851 PointerRNA *ptr_b)
1852 {
1853 CollectionPropertyIterator iter;
1854 PropertyRNA *iterprop = RNA_struct_iterator_property(ptr_a->type);
1855 BLI_assert(ptr_a->type == ptr_b->type);
1856 RNA_property_collection_begin(ptr_a, iterprop, &iter);
1857 for (; iter.valid; RNA_property_collection_next(&iter)) {
1858 PropertyRNA *prop = iter.ptr.data;
1859 if (STREQ(RNA_property_identifier(prop), "rna_type")) {
1860 continue;
1861 }
1862 switch (RNA_property_type(prop)) {
1863 case PROP_POINTER: {
1864 PointerRNA ptr_sub_a = RNA_property_pointer_get(ptr_a, prop);
1865 PointerRNA ptr_sub_b = RNA_property_pointer_get(ptr_b, prop);
1866 rna_struct_update_when_changed(C, bmain, &ptr_sub_a, &ptr_sub_b);
1867 break;
1868 }
1869 case PROP_COLLECTION:
1870 /* Don't handle collections. */
1871 break;
1872 default: {
1873 if (!RNA_property_equals(bmain, ptr_a, ptr_b, prop, RNA_EQ_STRICT)) {
1874 RNA_property_update(C, ptr_b, prop);
1875 }
1876 }
1877 }
1878 }
1879 RNA_property_collection_end(&iter);
1880 }
1881
wm_userpref_update_when_changed(bContext * C,Main * bmain,UserDef * userdef_prev,UserDef * userdef_curr)1882 static void wm_userpref_update_when_changed(bContext *C,
1883 Main *bmain,
1884 UserDef *userdef_prev,
1885 UserDef *userdef_curr)
1886 {
1887 PointerRNA ptr_a, ptr_b;
1888 RNA_pointer_create(NULL, &RNA_Preferences, userdef_prev, &ptr_a);
1889 RNA_pointer_create(NULL, &RNA_Preferences, userdef_curr, &ptr_b);
1890 const bool is_dirty = userdef_curr->runtime.is_dirty;
1891
1892 rna_struct_update_when_changed(C, bmain, &ptr_a, &ptr_b);
1893
1894 WM_reinit_gizmomap_all(bmain);
1895 WM_keyconfig_reload(C);
1896
1897 userdef_curr->runtime.is_dirty = is_dirty;
1898 }
1899
wm_userpref_read_exec(bContext * C,wmOperator * op)1900 static int wm_userpref_read_exec(bContext *C, wmOperator *op)
1901 {
1902 const bool use_data = false;
1903 const bool use_userdef = true;
1904 const bool use_factory_settings = STREQ(op->type->idname, "WM_OT_read_factory_userpref");
1905
1906 UserDef U_backup = U;
1907
1908 wm_homefile_read(C,
1909 op->reports,
1910 use_factory_settings,
1911 false,
1912 use_data,
1913 use_userdef,
1914 NULL,
1915 WM_init_state_app_template_get(),
1916 NULL);
1917
1918 wm_userpref_read_exceptions(&U, &U_backup);
1919 SET_FLAG_FROM_TEST(G.f, use_factory_settings, G_FLAG_USERPREF_NO_SAVE_ON_EXIT);
1920
1921 Main *bmain = CTX_data_main(C);
1922
1923 wm_userpref_update_when_changed(C, bmain, &U_backup, &U);
1924
1925 if (use_factory_settings) {
1926 U.runtime.is_dirty = true;
1927 }
1928
1929 /* Needed to recalculate UI scaling values (eg, #UserDef.inv_dpi_fac). */
1930 wm_window_clear_drawable(bmain->wm.first);
1931
1932 WM_event_add_notifier(C, NC_WINDOW, NULL);
1933
1934 return OPERATOR_FINISHED;
1935 }
1936
WM_OT_read_userpref(wmOperatorType * ot)1937 void WM_OT_read_userpref(wmOperatorType *ot)
1938 {
1939 ot->name = "Load Preferences";
1940 ot->idname = "WM_OT_read_userpref";
1941 ot->description = "Load last saved preferences";
1942
1943 ot->invoke = WM_operator_confirm;
1944 ot->exec = wm_userpref_read_exec;
1945 }
1946
WM_OT_read_factory_userpref(wmOperatorType * ot)1947 void WM_OT_read_factory_userpref(wmOperatorType *ot)
1948 {
1949 ot->name = "Load Factory Preferences";
1950 ot->idname = "WM_OT_read_factory_userpref";
1951 ot->description =
1952 "Load factory default preferences. "
1953 "To make changes to preferences permanent, use \"Save Preferences\"";
1954
1955 ot->invoke = WM_operator_confirm;
1956 ot->exec = wm_userpref_read_exec;
1957 }
1958
1959 /** \} */
1960
1961 /* -------------------------------------------------------------------- */
1962 /** \name Read File History Operator
1963 * \{ */
1964
wm_history_file_read_exec(bContext * UNUSED (C),wmOperator * UNUSED (op))1965 static int wm_history_file_read_exec(bContext *UNUSED(C), wmOperator *UNUSED(op))
1966 {
1967 ED_file_read_bookmarks();
1968 wm_history_file_read();
1969 return OPERATOR_FINISHED;
1970 }
1971
WM_OT_read_history(wmOperatorType * ot)1972 void WM_OT_read_history(wmOperatorType *ot)
1973 {
1974 ot->name = "Reload History File";
1975 ot->idname = "WM_OT_read_history";
1976 ot->description = "Reloads history and bookmarks";
1977
1978 ot->invoke = WM_operator_confirm;
1979 ot->exec = wm_history_file_read_exec;
1980
1981 /* this operator is only used for loading settings from a previous blender install */
1982 ot->flag = OPTYPE_INTERNAL;
1983 }
1984
1985 /** \} */
1986
1987 /* -------------------------------------------------------------------- */
1988 /** \name Read Startup & Preferences Operator
1989 *
1990 * Both #WM_OT_read_homefile & #WM_OT_read_factory_settings.
1991 * \{ */
1992
wm_homefile_read_exec(bContext * C,wmOperator * op)1993 static int wm_homefile_read_exec(bContext *C, wmOperator *op)
1994 {
1995 const bool use_factory_startup_and_userdef = STREQ(op->type->idname,
1996 "WM_OT_read_factory_settings");
1997 const bool use_factory_settings = use_factory_startup_and_userdef ||
1998 RNA_boolean_get(op->ptr, "use_factory_startup");
1999 bool use_userdef = false;
2000 char filepath_buf[FILE_MAX];
2001 const char *filepath = NULL;
2002 UserDef U_backup = U;
2003
2004 if (!use_factory_settings) {
2005 PropertyRNA *prop = RNA_struct_find_property(op->ptr, "filepath");
2006
2007 /* This can be used when loading of a start-up file should only change
2008 * the scene content but keep the blender UI as it is. */
2009 wm_open_init_load_ui(op, true);
2010 SET_FLAG_FROM_TEST(G.fileflags, !RNA_boolean_get(op->ptr, "load_ui"), G_FILE_NO_UI);
2011
2012 if (RNA_property_is_set(op->ptr, prop)) {
2013 RNA_property_string_get(op->ptr, prop, filepath_buf);
2014 filepath = filepath_buf;
2015 if (BLI_access(filepath, R_OK)) {
2016 BKE_reportf(
2017 op->reports, RPT_ERROR, "Can't read alternative start-up file: '%s'", filepath);
2018 return OPERATOR_CANCELLED;
2019 }
2020 }
2021 }
2022 else {
2023 if (use_factory_startup_and_userdef) {
2024 /* always load UI for factory settings (prefs will re-init) */
2025 G.fileflags &= ~G_FILE_NO_UI;
2026 /* Always load preferences with factory settings. */
2027 use_userdef = true;
2028 }
2029 }
2030
2031 char app_template_buf[sizeof(U.app_template)];
2032 const char *app_template;
2033 PropertyRNA *prop_app_template = RNA_struct_find_property(op->ptr, "app_template");
2034 const bool use_splash = !use_factory_settings && RNA_boolean_get(op->ptr, "use_splash");
2035 const bool use_empty_data = RNA_boolean_get(op->ptr, "use_empty");
2036
2037 if (prop_app_template && RNA_property_is_set(op->ptr, prop_app_template)) {
2038 RNA_property_string_get(op->ptr, prop_app_template, app_template_buf);
2039 app_template = app_template_buf;
2040
2041 if (!use_factory_settings) {
2042 /* Always load preferences when switching templates with own preferences. */
2043 use_userdef = BKE_appdir_app_template_has_userpref(app_template) ||
2044 BKE_appdir_app_template_has_userpref(U.app_template);
2045 }
2046
2047 /* Turn override off, since we're explicitly loading a different app-template. */
2048 WM_init_state_app_template_set(NULL);
2049 }
2050 else {
2051 /* Normally NULL, only set when overriding from the command-line. */
2052 app_template = WM_init_state_app_template_get();
2053 }
2054
2055 bool use_data = true;
2056 wm_homefile_read(C,
2057 op->reports,
2058 use_factory_settings,
2059 use_empty_data,
2060 use_data,
2061 use_userdef,
2062 filepath,
2063 app_template,
2064 NULL);
2065 if (use_splash) {
2066 WM_init_splash(C);
2067 }
2068
2069 if (use_userdef) {
2070 wm_userpref_read_exceptions(&U, &U_backup);
2071 SET_FLAG_FROM_TEST(G.f, use_factory_settings, G_FLAG_USERPREF_NO_SAVE_ON_EXIT);
2072
2073 if (use_factory_settings) {
2074 U.runtime.is_dirty = true;
2075 }
2076 }
2077
2078 if (G.fileflags & G_FILE_NO_UI) {
2079 ED_outliner_select_sync_from_all_tag(C);
2080 }
2081
2082 return OPERATOR_FINISHED;
2083 }
2084
wm_homefile_read_after_dialog_callback(bContext * C,void * user_data)2085 static void wm_homefile_read_after_dialog_callback(bContext *C, void *user_data)
2086 {
2087 WM_operator_name_call_with_properties(
2088 C, "WM_OT_read_homefile", WM_OP_EXEC_DEFAULT, (IDProperty *)user_data);
2089 }
2090
wm_free_operator_properties_callback(void * user_data)2091 static void wm_free_operator_properties_callback(void *user_data)
2092 {
2093 IDProperty *properties = (IDProperty *)user_data;
2094 IDP_FreeProperty(properties);
2095 }
2096
wm_homefile_read_invoke(bContext * C,wmOperator * op,const wmEvent * UNUSED (event))2097 static int wm_homefile_read_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
2098 {
2099 if (U.uiflag & USER_SAVE_PROMPT &&
2100 wm_file_or_image_is_modified(CTX_data_main(C), CTX_wm_manager(C))) {
2101 wmGenericCallback *callback = MEM_callocN(sizeof(*callback), __func__);
2102 callback->exec = wm_homefile_read_after_dialog_callback;
2103 callback->user_data = IDP_CopyProperty(op->properties);
2104 callback->free_user_data = wm_free_operator_properties_callback;
2105 wm_close_file_dialog(C, callback);
2106 return OPERATOR_INTERFACE;
2107 }
2108 return wm_homefile_read_exec(C, op);
2109 }
2110
read_homefile_props(wmOperatorType * ot)2111 static void read_homefile_props(wmOperatorType *ot)
2112 {
2113 PropertyRNA *prop;
2114
2115 prop = RNA_def_string(ot->srna, "app_template", "Template", sizeof(U.app_template), "", "");
2116 RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
2117
2118 prop = RNA_def_boolean(ot->srna, "use_empty", false, "Empty", "");
2119 RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
2120 }
2121
WM_OT_read_homefile(wmOperatorType * ot)2122 void WM_OT_read_homefile(wmOperatorType *ot)
2123 {
2124 PropertyRNA *prop;
2125 ot->name = "Reload Start-Up File";
2126 ot->idname = "WM_OT_read_homefile";
2127 ot->description = "Open the default file (doesn't save the current file)";
2128
2129 ot->invoke = wm_homefile_read_invoke;
2130 ot->exec = wm_homefile_read_exec;
2131
2132 prop = RNA_def_string_file_path(
2133 ot->srna, "filepath", NULL, FILE_MAX, "File Path", "Path to an alternative start-up file");
2134 RNA_def_property_flag(prop, PROP_HIDDEN);
2135
2136 /* So scripts can use an alternative start-up file without the UI */
2137 prop = RNA_def_boolean(
2138 ot->srna, "load_ui", true, "Load UI", "Load user interface setup from the .blend file");
2139 RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
2140
2141 /* So the splash can be kept open after loading a file (for templates). */
2142 prop = RNA_def_boolean(ot->srna, "use_splash", false, "Splash", "");
2143 RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
2144
2145 /* So scripts can load factory-startup without resetting preferences
2146 * (which has other implications such as reloading all add-ons).
2147 * Match naming for `--factory-startup` command line argument. */
2148 prop = RNA_def_boolean(ot->srna, "use_factory_startup", false, "Factory Startup", "");
2149 RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
2150
2151 read_homefile_props(ot);
2152
2153 /* omit poll to run in background mode */
2154 }
2155
WM_OT_read_factory_settings(wmOperatorType * ot)2156 void WM_OT_read_factory_settings(wmOperatorType *ot)
2157 {
2158 ot->name = "Load Factory Settings";
2159 ot->idname = "WM_OT_read_factory_settings";
2160 ot->description =
2161 "Load factory default startup file and preferences. "
2162 "To make changes permanent, use \"Save Startup File\" and \"Save Preferences\"";
2163
2164 ot->invoke = WM_operator_confirm;
2165 ot->exec = wm_homefile_read_exec;
2166
2167 read_homefile_props(ot);
2168 /* omit poll to run in background mode */
2169 }
2170
2171 /** \} */
2172
2173 /* -------------------------------------------------------------------- */
2174 /** \name Open Main .blend File Utilities
2175 * \{ */
2176
2177 /**
2178 * Wrap #WM_file_read, shared by file reading operators.
2179 */
wm_file_read_opwrap(bContext * C,const char * filepath,ReportList * reports,const bool autoexec_init)2180 static bool wm_file_read_opwrap(bContext *C,
2181 const char *filepath,
2182 ReportList *reports,
2183 const bool autoexec_init)
2184 {
2185 bool success;
2186
2187 /* XXX wm in context is not set correctly after WM_file_read -> crash */
2188 /* do it before for now, but is this correct with multiple windows? */
2189 WM_event_add_notifier(C, NC_WINDOW, NULL);
2190
2191 if (autoexec_init) {
2192 WM_file_autoexec_init(filepath);
2193 }
2194
2195 success = WM_file_read(C, filepath, reports);
2196
2197 return success;
2198 }
2199
2200 /* Generic operator state utilities */
2201
create_operator_state(wmOperatorType * ot,int first_state)2202 static void create_operator_state(wmOperatorType *ot, int first_state)
2203 {
2204 PropertyRNA *prop = RNA_def_int(
2205 ot->srna, "state", first_state, INT32_MIN, INT32_MAX, "State", "", INT32_MIN, INT32_MAX);
2206 RNA_def_property_flag(prop, PROP_SKIP_SAVE);
2207 RNA_def_property_flag(prop, PROP_HIDDEN);
2208 }
2209
get_operator_state(wmOperator * op)2210 static int get_operator_state(wmOperator *op)
2211 {
2212 return RNA_int_get(op->ptr, "state");
2213 }
2214
set_next_operator_state(wmOperator * op,int state)2215 static void set_next_operator_state(wmOperator *op, int state)
2216 {
2217 RNA_int_set(op->ptr, "state", state);
2218 }
2219
2220 typedef struct OperatorDispatchTarget {
2221 int state;
2222 int (*run)(bContext *C, wmOperator *op);
2223 } OperatorDispatchTarget;
2224
operator_state_dispatch(bContext * C,wmOperator * op,OperatorDispatchTarget * targets)2225 static int operator_state_dispatch(bContext *C, wmOperator *op, OperatorDispatchTarget *targets)
2226 {
2227 int state = get_operator_state(op);
2228 for (int i = 0; targets[i].run; i++) {
2229 OperatorDispatchTarget target = targets[i];
2230 if (target.state == state) {
2231 return target.run(C, op);
2232 }
2233 }
2234 BLI_assert(false);
2235 return OPERATOR_CANCELLED;
2236 }
2237
2238 /** \} */
2239
2240 /* -------------------------------------------------------------------- */
2241 /** \name Open Main .blend File Operator
2242 * \{ */
2243
2244 enum {
2245 OPEN_MAINFILE_STATE_DISCARD_CHANGES,
2246 OPEN_MAINFILE_STATE_SELECT_FILE_PATH,
2247 OPEN_MAINFILE_STATE_OPEN,
2248 };
2249
2250 static int wm_open_mainfile_dispatch(bContext *C, wmOperator *op);
2251
wm_open_mainfile_after_dialog_callback(bContext * C,void * user_data)2252 static void wm_open_mainfile_after_dialog_callback(bContext *C, void *user_data)
2253 {
2254 WM_operator_name_call_with_properties(
2255 C, "WM_OT_open_mainfile", WM_OP_INVOKE_DEFAULT, (IDProperty *)user_data);
2256 }
2257
wm_open_mainfile__discard_changes(bContext * C,wmOperator * op)2258 static int wm_open_mainfile__discard_changes(bContext *C, wmOperator *op)
2259 {
2260 if (RNA_boolean_get(op->ptr, "display_file_selector")) {
2261 set_next_operator_state(op, OPEN_MAINFILE_STATE_SELECT_FILE_PATH);
2262 }
2263 else {
2264 set_next_operator_state(op, OPEN_MAINFILE_STATE_OPEN);
2265 }
2266
2267 if (U.uiflag & USER_SAVE_PROMPT &&
2268 wm_file_or_image_is_modified(CTX_data_main(C), CTX_wm_manager(C))) {
2269 wmGenericCallback *callback = MEM_callocN(sizeof(*callback), __func__);
2270 callback->exec = wm_open_mainfile_after_dialog_callback;
2271 callback->user_data = IDP_CopyProperty(op->properties);
2272 callback->free_user_data = wm_free_operator_properties_callback;
2273 wm_close_file_dialog(C, callback);
2274 return OPERATOR_INTERFACE;
2275 }
2276 return wm_open_mainfile_dispatch(C, op);
2277 }
2278
wm_open_mainfile__select_file_path(bContext * C,wmOperator * op)2279 static int wm_open_mainfile__select_file_path(bContext *C, wmOperator *op)
2280 {
2281 set_next_operator_state(op, OPEN_MAINFILE_STATE_OPEN);
2282
2283 Main *bmain = CTX_data_main(C);
2284 const char *openname = BKE_main_blendfile_path(bmain);
2285
2286 if (CTX_wm_window(C) == NULL) {
2287 /* in rare cases this could happen, when trying to invoke in background
2288 * mode on load for example. Don't use poll for this because exec()
2289 * can still run without a window */
2290 BKE_report(op->reports, RPT_ERROR, "Context window not set");
2291 return OPERATOR_CANCELLED;
2292 }
2293
2294 /* if possible, get the name of the most recently used .blend file */
2295 if (G.recent_files.first) {
2296 struct RecentFile *recent = G.recent_files.first;
2297 openname = recent->filepath;
2298 }
2299
2300 RNA_string_set(op->ptr, "filepath", openname);
2301 wm_open_init_load_ui(op, true);
2302 wm_open_init_use_scripts(op, true);
2303 op->customdata = NULL;
2304
2305 WM_event_add_fileselect(C, op);
2306
2307 return OPERATOR_RUNNING_MODAL;
2308 }
2309
wm_open_mainfile__open(bContext * C,wmOperator * op)2310 static int wm_open_mainfile__open(bContext *C, wmOperator *op)
2311 {
2312 char filepath[FILE_MAX];
2313 bool success;
2314
2315 RNA_string_get(op->ptr, "filepath", filepath);
2316
2317 /* re-use last loaded setting so we can reload a file without changing */
2318 wm_open_init_load_ui(op, false);
2319 wm_open_init_use_scripts(op, false);
2320
2321 if (RNA_boolean_get(op->ptr, "load_ui")) {
2322 G.fileflags &= ~G_FILE_NO_UI;
2323 }
2324 else {
2325 G.fileflags |= G_FILE_NO_UI;
2326 }
2327
2328 if (RNA_boolean_get(op->ptr, "use_scripts")) {
2329 G.f |= G_FLAG_SCRIPT_AUTOEXEC;
2330 }
2331 else {
2332 G.f &= ~G_FLAG_SCRIPT_AUTOEXEC;
2333 }
2334
2335 success = wm_file_read_opwrap(C, filepath, op->reports, !(G.f & G_FLAG_SCRIPT_AUTOEXEC));
2336
2337 /* for file open also popup for warnings, not only errors */
2338 BKE_report_print_level_set(op->reports, RPT_WARNING);
2339
2340 if (success) {
2341 if (G.fileflags & G_FILE_NO_UI) {
2342 ED_outliner_select_sync_from_all_tag(C);
2343 }
2344 ED_view3d_local_collections_reset(C, (G.fileflags & G_FILE_NO_UI) != 0);
2345 return OPERATOR_FINISHED;
2346 }
2347 return OPERATOR_CANCELLED;
2348 }
2349
2350 static OperatorDispatchTarget wm_open_mainfile_dispatch_targets[] = {
2351 {OPEN_MAINFILE_STATE_DISCARD_CHANGES, wm_open_mainfile__discard_changes},
2352 {OPEN_MAINFILE_STATE_SELECT_FILE_PATH, wm_open_mainfile__select_file_path},
2353 {OPEN_MAINFILE_STATE_OPEN, wm_open_mainfile__open},
2354 {0, NULL},
2355 };
2356
wm_open_mainfile_dispatch(bContext * C,wmOperator * op)2357 static int wm_open_mainfile_dispatch(bContext *C, wmOperator *op)
2358 {
2359 return operator_state_dispatch(C, op, wm_open_mainfile_dispatch_targets);
2360 }
2361
wm_open_mainfile_invoke(bContext * C,wmOperator * op,const wmEvent * UNUSED (event))2362 static int wm_open_mainfile_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
2363 {
2364 return wm_open_mainfile_dispatch(C, op);
2365 }
2366
wm_open_mainfile_exec(bContext * C,wmOperator * op)2367 static int wm_open_mainfile_exec(bContext *C, wmOperator *op)
2368 {
2369 return wm_open_mainfile__open(C, op);
2370 }
2371
wm_open_mainfile_description(struct bContext * UNUSED (C),struct wmOperatorType * UNUSED (op),struct PointerRNA * params)2372 static char *wm_open_mainfile_description(struct bContext *UNUSED(C),
2373 struct wmOperatorType *UNUSED(op),
2374 struct PointerRNA *params)
2375 {
2376 if (!RNA_struct_property_is_set(params, "filepath")) {
2377 return NULL;
2378 }
2379
2380 /* Filepath. */
2381 char path[FILE_MAX];
2382 RNA_string_get(params, "filepath", path);
2383
2384 BLI_stat_t stats;
2385 if (BLI_stat(path, &stats) == -1) {
2386 return BLI_sprintfN("%s\n\n%s", path, N_("File Not Found"));
2387 }
2388
2389 /* Date. */
2390 char date_st[FILELIST_DIRENTRY_DATE_LEN];
2391 char time_st[FILELIST_DIRENTRY_TIME_LEN];
2392 bool is_today, is_yesterday;
2393 BLI_filelist_entry_datetime_to_string(
2394 NULL, (int64_t)stats.st_mtime, false, time_st, date_st, &is_today, &is_yesterday);
2395 if (is_today || is_yesterday) {
2396 BLI_strncpy(date_st, is_today ? N_("Today") : N_("Yesterday"), sizeof(date_st));
2397 }
2398
2399 /* Size. */
2400 char size_str[FILELIST_DIRENTRY_SIZE_LEN];
2401 BLI_filelist_entry_size_to_string(NULL, (uint64_t)stats.st_size, false, size_str);
2402
2403 return BLI_sprintfN(
2404 "%s\n\n%s: %s %s\n%s: %s", path, N_("Modified"), date_st, time_st, N_("Size"), size_str);
2405 }
2406
2407 /* currently fits in a pointer */
2408 struct FileRuntime {
2409 bool is_untrusted;
2410 };
2411 BLI_STATIC_ASSERT(sizeof(struct FileRuntime) <= sizeof(void *),
2412 "Struct must not exceed pointer size");
2413
wm_open_mainfile_check(bContext * UNUSED (C),wmOperator * op)2414 static bool wm_open_mainfile_check(bContext *UNUSED(C), wmOperator *op)
2415 {
2416 struct FileRuntime *file_info = (struct FileRuntime *)&op->customdata;
2417 PropertyRNA *prop = RNA_struct_find_property(op->ptr, "use_scripts");
2418 bool is_untrusted = false;
2419 char path[FILE_MAX];
2420 char *lslash;
2421
2422 RNA_string_get(op->ptr, "filepath", path);
2423
2424 /* get the dir */
2425 lslash = (char *)BLI_path_slash_rfind(path);
2426 if (lslash) {
2427 *(lslash + 1) = '\0';
2428 }
2429
2430 if ((U.flag & USER_SCRIPT_AUTOEXEC_DISABLE) == 0) {
2431 if (BKE_autoexec_match(path) == true) {
2432 RNA_property_boolean_set(op->ptr, prop, false);
2433 is_untrusted = true;
2434 }
2435 }
2436
2437 if (file_info) {
2438 file_info->is_untrusted = is_untrusted;
2439 }
2440
2441 return is_untrusted;
2442 }
2443
wm_open_mainfile_ui(bContext * UNUSED (C),wmOperator * op)2444 static void wm_open_mainfile_ui(bContext *UNUSED(C), wmOperator *op)
2445 {
2446 struct FileRuntime *file_info = (struct FileRuntime *)&op->customdata;
2447 uiLayout *layout = op->layout;
2448 uiLayout *col = op->layout;
2449 const char *autoexec_text;
2450
2451 uiItemR(layout, op->ptr, "load_ui", 0, NULL, ICON_NONE);
2452
2453 col = uiLayoutColumn(layout, false);
2454 if (file_info->is_untrusted) {
2455 autoexec_text = IFACE_("Trusted Source [Untrusted Path]");
2456 uiLayoutSetActive(col, false);
2457 uiLayoutSetEnabled(col, false);
2458 }
2459 else {
2460 autoexec_text = IFACE_("Trusted Source");
2461 }
2462
2463 uiItemR(col, op->ptr, "use_scripts", 0, autoexec_text, ICON_NONE);
2464 }
2465
WM_OT_open_mainfile(wmOperatorType * ot)2466 void WM_OT_open_mainfile(wmOperatorType *ot)
2467 {
2468 ot->name = "Open";
2469 ot->idname = "WM_OT_open_mainfile";
2470 ot->description = "Open a Blender file";
2471 ot->get_description = wm_open_mainfile_description;
2472
2473 ot->invoke = wm_open_mainfile_invoke;
2474 ot->exec = wm_open_mainfile_exec;
2475 ot->check = wm_open_mainfile_check;
2476 ot->ui = wm_open_mainfile_ui;
2477 /* omit window poll so this can work in background mode */
2478
2479 WM_operator_properties_filesel(ot,
2480 FILE_TYPE_FOLDER | FILE_TYPE_BLENDER,
2481 FILE_BLENDER,
2482 FILE_OPENFILE,
2483 WM_FILESEL_FILEPATH,
2484 FILE_DEFAULTDISPLAY,
2485 FILE_SORT_ALPHA);
2486
2487 RNA_def_boolean(
2488 ot->srna, "load_ui", true, "Load UI", "Load user interface setup in the .blend file");
2489 RNA_def_boolean(ot->srna,
2490 "use_scripts",
2491 true,
2492 "Trusted Source",
2493 "Allow .blend file to execute scripts automatically, default available from "
2494 "system preferences");
2495
2496 PropertyRNA *prop = RNA_def_boolean(
2497 ot->srna, "display_file_selector", true, "Display File Selector", "");
2498 RNA_def_property_flag(prop, PROP_SKIP_SAVE);
2499
2500 create_operator_state(ot, OPEN_MAINFILE_STATE_DISCARD_CHANGES);
2501 }
2502
2503 /** \} */
2504
2505 /* -------------------------------------------------------------------- */
2506 /** \name Reload (revert) Main .blend File Operator
2507 * \{ */
2508
wm_revert_mainfile_exec(bContext * C,wmOperator * op)2509 static int wm_revert_mainfile_exec(bContext *C, wmOperator *op)
2510 {
2511 Main *bmain = CTX_data_main(C);
2512 bool success;
2513 char filepath[FILE_MAX];
2514
2515 wm_open_init_use_scripts(op, false);
2516
2517 if (RNA_boolean_get(op->ptr, "use_scripts")) {
2518 G.f |= G_FLAG_SCRIPT_AUTOEXEC;
2519 }
2520 else {
2521 G.f &= ~G_FLAG_SCRIPT_AUTOEXEC;
2522 }
2523
2524 BLI_strncpy(filepath, BKE_main_blendfile_path(bmain), sizeof(filepath));
2525 success = wm_file_read_opwrap(C, filepath, op->reports, !(G.f & G_FLAG_SCRIPT_AUTOEXEC));
2526
2527 if (success) {
2528 return OPERATOR_FINISHED;
2529 }
2530 return OPERATOR_CANCELLED;
2531 }
2532
wm_revert_mainfile_poll(bContext * UNUSED (C))2533 static bool wm_revert_mainfile_poll(bContext *UNUSED(C))
2534 {
2535 return G.relbase_valid;
2536 }
2537
WM_OT_revert_mainfile(wmOperatorType * ot)2538 void WM_OT_revert_mainfile(wmOperatorType *ot)
2539 {
2540 ot->name = "Revert";
2541 ot->idname = "WM_OT_revert_mainfile";
2542 ot->description = "Reload the saved file";
2543
2544 ot->invoke = WM_operator_confirm;
2545 ot->exec = wm_revert_mainfile_exec;
2546 ot->poll = wm_revert_mainfile_poll;
2547
2548 RNA_def_boolean(ot->srna,
2549 "use_scripts",
2550 true,
2551 "Trusted Source",
2552 "Allow .blend file to execute scripts automatically, default available from "
2553 "system preferences");
2554 }
2555
2556 /** \} */
2557
2558 /* -------------------------------------------------------------------- */
2559 /** \name Recover Last Session Operator
2560 * \{ */
2561
WM_recover_last_session(bContext * C,ReportList * reports)2562 void WM_recover_last_session(bContext *C, ReportList *reports)
2563 {
2564 char filepath[FILE_MAX];
2565
2566 BLI_join_dirfile(filepath, sizeof(filepath), BKE_tempdir_base(), BLENDER_QUIT_FILE);
2567 /* if reports==NULL, it's called directly without operator, we add a quick check here */
2568 if (reports || BLI_exists(filepath)) {
2569 G.fileflags |= G_FILE_RECOVER;
2570
2571 wm_file_read_opwrap(C, filepath, reports, true);
2572
2573 G.fileflags &= ~G_FILE_RECOVER;
2574
2575 /* XXX bad global... fixme */
2576 Main *bmain = CTX_data_main(C);
2577 if (BKE_main_blendfile_path(bmain)[0] != '\0') {
2578 G.file_loaded = 1; /* prevents splash to show */
2579 }
2580 else {
2581 G.relbase_valid = 0;
2582 G.save_over = 0; /* start with save preference untitled.blend */
2583 }
2584 }
2585 }
2586
wm_recover_last_session_exec(bContext * C,wmOperator * op)2587 static int wm_recover_last_session_exec(bContext *C, wmOperator *op)
2588 {
2589 WM_recover_last_session(C, op->reports);
2590 return OPERATOR_FINISHED;
2591 }
2592
WM_OT_recover_last_session(wmOperatorType * ot)2593 void WM_OT_recover_last_session(wmOperatorType *ot)
2594 {
2595 ot->name = "Recover Last Session";
2596 ot->idname = "WM_OT_recover_last_session";
2597 ot->description = "Open the last closed file (\"" BLENDER_QUIT_FILE "\")";
2598
2599 ot->invoke = WM_operator_confirm;
2600 ot->exec = wm_recover_last_session_exec;
2601 }
2602
2603 /** \} */
2604
2605 /* -------------------------------------------------------------------- */
2606 /** \name Auto-Save Main .blend File Operator
2607 * \{ */
2608
wm_recover_auto_save_exec(bContext * C,wmOperator * op)2609 static int wm_recover_auto_save_exec(bContext *C, wmOperator *op)
2610 {
2611 char filepath[FILE_MAX];
2612 bool success;
2613
2614 RNA_string_get(op->ptr, "filepath", filepath);
2615
2616 G.fileflags |= G_FILE_RECOVER;
2617
2618 success = wm_file_read_opwrap(C, filepath, op->reports, true);
2619
2620 G.fileflags &= ~G_FILE_RECOVER;
2621
2622 if (success) {
2623 return OPERATOR_FINISHED;
2624 }
2625 return OPERATOR_CANCELLED;
2626 }
2627
wm_recover_auto_save_invoke(bContext * C,wmOperator * op,const wmEvent * UNUSED (event))2628 static int wm_recover_auto_save_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
2629 {
2630 char filename[FILE_MAX];
2631
2632 wm_autosave_location(filename);
2633 RNA_string_set(op->ptr, "filepath", filename);
2634 WM_event_add_fileselect(C, op);
2635
2636 return OPERATOR_RUNNING_MODAL;
2637 }
2638
WM_OT_recover_auto_save(wmOperatorType * ot)2639 void WM_OT_recover_auto_save(wmOperatorType *ot)
2640 {
2641 ot->name = "Recover Auto Save";
2642 ot->idname = "WM_OT_recover_auto_save";
2643 ot->description = "Open an automatically saved file to recover it";
2644
2645 ot->invoke = wm_recover_auto_save_invoke;
2646 ot->exec = wm_recover_auto_save_exec;
2647
2648 WM_operator_properties_filesel(ot,
2649 FILE_TYPE_BLENDER,
2650 FILE_BLENDER,
2651 FILE_OPENFILE,
2652 WM_FILESEL_FILEPATH,
2653 FILE_VERTICALDISPLAY,
2654 FILE_SORT_TIME);
2655 }
2656
2657 /** \} */
2658
2659 /* -------------------------------------------------------------------- */
2660 /** \name Save Main .blend File Operator
2661 *
2662 * Both #WM_OT_save_as_mainfile & #WM_OT_save_mainfile.
2663 * \{ */
2664
wm_filepath_default(char * filepath)2665 static void wm_filepath_default(char *filepath)
2666 {
2667 if (G.save_over == false) {
2668 BLI_path_filename_ensure(filepath, FILE_MAX, "untitled.blend");
2669 }
2670 }
2671
save_set_compress(wmOperator * op)2672 static void save_set_compress(wmOperator *op)
2673 {
2674 PropertyRNA *prop;
2675
2676 prop = RNA_struct_find_property(op->ptr, "compress");
2677 if (!RNA_property_is_set(op->ptr, prop)) {
2678 if (G.save_over) { /* keep flag for existing file */
2679 RNA_property_boolean_set(op->ptr, prop, (G.fileflags & G_FILE_COMPRESS) != 0);
2680 }
2681 else { /* use userdef for new file */
2682 RNA_property_boolean_set(op->ptr, prop, (U.flag & USER_FILECOMPRESS) != 0);
2683 }
2684 }
2685 }
2686
save_set_filepath(bContext * C,wmOperator * op)2687 static void save_set_filepath(bContext *C, wmOperator *op)
2688 {
2689 Main *bmain = CTX_data_main(C);
2690 PropertyRNA *prop;
2691 char name[FILE_MAX];
2692
2693 prop = RNA_struct_find_property(op->ptr, "filepath");
2694 if (!RNA_property_is_set(op->ptr, prop)) {
2695 /* if not saved before, get the name of the most recently used .blend file */
2696 if (BKE_main_blendfile_path(bmain)[0] == '\0' && G.recent_files.first) {
2697 struct RecentFile *recent = G.recent_files.first;
2698 BLI_strncpy(name, recent->filepath, FILE_MAX);
2699 }
2700 else {
2701 BLI_strncpy(name, bmain->name, FILE_MAX);
2702 }
2703
2704 wm_filepath_default(name);
2705 RNA_property_string_set(op->ptr, prop, name);
2706 }
2707 }
2708
wm_save_as_mainfile_invoke(bContext * C,wmOperator * op,const wmEvent * UNUSED (event))2709 static int wm_save_as_mainfile_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
2710 {
2711
2712 save_set_compress(op);
2713 save_set_filepath(C, op);
2714
2715 WM_event_add_fileselect(C, op);
2716
2717 return OPERATOR_RUNNING_MODAL;
2718 }
2719
2720 /* function used for WM_OT_save_mainfile too */
wm_save_as_mainfile_exec(bContext * C,wmOperator * op)2721 static int wm_save_as_mainfile_exec(bContext *C, wmOperator *op)
2722 {
2723 Main *bmain = CTX_data_main(C);
2724 char path[FILE_MAX];
2725 const bool is_save_as = (op->type->invoke == wm_save_as_mainfile_invoke);
2726 const bool use_save_as_copy = (RNA_struct_property_is_set(op->ptr, "copy") &&
2727 RNA_boolean_get(op->ptr, "copy"));
2728
2729 /* We could expose all options to the users however in most cases remapping
2730 * existing relative paths is a good default.
2731 * Users can manually make their paths relative & absolute if they wish. */
2732 const eBLO_WritePathRemap remap_mode = RNA_boolean_get(op->ptr, "relative_remap") ?
2733 BLO_WRITE_PATH_REMAP_RELATIVE :
2734 BLO_WRITE_PATH_REMAP_NONE;
2735 save_set_compress(op);
2736
2737 if (RNA_struct_property_is_set(op->ptr, "filepath")) {
2738 RNA_string_get(op->ptr, "filepath", path);
2739 }
2740 else {
2741 BLI_strncpy(path, BKE_main_blendfile_path(bmain), FILE_MAX);
2742 wm_filepath_default(path);
2743 }
2744
2745 const int fileflags_orig = G.fileflags;
2746 int fileflags = G.fileflags;
2747
2748 /* set compression flag */
2749 SET_FLAG_FROM_TEST(fileflags, RNA_boolean_get(op->ptr, "compress"), G_FILE_COMPRESS);
2750
2751 const bool ok = wm_file_write(C, path, fileflags, remap_mode, use_save_as_copy, op->reports);
2752
2753 if ((op->flag & OP_IS_INVOKE) == 0) {
2754 /* OP_IS_INVOKE is set when the operator is called from the GUI.
2755 * If it is not set, the operator is called from a script and
2756 * shouldn't influence G.fileflags. */
2757 G.fileflags = fileflags_orig;
2758 }
2759
2760 if (ok == false) {
2761 return OPERATOR_CANCELLED;
2762 }
2763
2764 WM_event_add_notifier(C, NC_WM | ND_FILESAVE, NULL);
2765
2766 if (!is_save_as && RNA_boolean_get(op->ptr, "exit")) {
2767 wm_exit_schedule_delayed(C);
2768 }
2769
2770 return OPERATOR_FINISHED;
2771 }
2772
2773 /* function used for WM_OT_save_mainfile too */
blend_save_check(bContext * UNUSED (C),wmOperator * op)2774 static bool blend_save_check(bContext *UNUSED(C), wmOperator *op)
2775 {
2776 char filepath[FILE_MAX];
2777 RNA_string_get(op->ptr, "filepath", filepath);
2778 if (!BLO_has_bfile_extension(filepath)) {
2779 /* some users would prefer BLI_path_extension_replace(),
2780 * we keep getting nitpicking bug reports about this - campbell */
2781 BLI_path_extension_ensure(filepath, FILE_MAX, ".blend");
2782 RNA_string_set(op->ptr, "filepath", filepath);
2783 return true;
2784 }
2785 return false;
2786 }
2787
WM_OT_save_as_mainfile(wmOperatorType * ot)2788 void WM_OT_save_as_mainfile(wmOperatorType *ot)
2789 {
2790 PropertyRNA *prop;
2791
2792 ot->name = "Save As";
2793 ot->idname = "WM_OT_save_as_mainfile";
2794 ot->description = "Save the current file in the desired location";
2795
2796 ot->invoke = wm_save_as_mainfile_invoke;
2797 ot->exec = wm_save_as_mainfile_exec;
2798 ot->check = blend_save_check;
2799 /* omit window poll so this can work in background mode */
2800
2801 WM_operator_properties_filesel(ot,
2802 FILE_TYPE_FOLDER | FILE_TYPE_BLENDER,
2803 FILE_BLENDER,
2804 FILE_SAVE,
2805 WM_FILESEL_FILEPATH,
2806 FILE_DEFAULTDISPLAY,
2807 FILE_SORT_ALPHA);
2808 RNA_def_boolean(ot->srna, "compress", false, "Compress", "Write compressed .blend file");
2809 RNA_def_boolean(ot->srna,
2810 "relative_remap",
2811 true,
2812 "Remap Relative",
2813 "Remap relative paths when saving to a different directory");
2814 prop = RNA_def_boolean(
2815 ot->srna,
2816 "copy",
2817 false,
2818 "Save Copy",
2819 "Save a copy of the actual working state but does not make saved file active");
2820 RNA_def_property_flag(prop, PROP_SKIP_SAVE);
2821 }
2822
wm_save_mainfile_invoke(bContext * C,wmOperator * op,const wmEvent * UNUSED (event))2823 static int wm_save_mainfile_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
2824 {
2825 int ret;
2826
2827 /* cancel if no active window */
2828 if (CTX_wm_window(C) == NULL) {
2829 return OPERATOR_CANCELLED;
2830 }
2831
2832 save_set_compress(op);
2833 save_set_filepath(C, op);
2834
2835 /* if we're saving for the first time and prefer relative paths -
2836 * any existing paths will be absolute,
2837 * enable the option to remap paths to avoid confusion T37240. */
2838 if ((G.relbase_valid == false) && (U.flag & USER_RELPATHS)) {
2839 PropertyRNA *prop = RNA_struct_find_property(op->ptr, "relative_remap");
2840 if (!RNA_property_is_set(op->ptr, prop)) {
2841 RNA_property_boolean_set(op->ptr, prop, true);
2842 }
2843 }
2844
2845 if (G.save_over) {
2846 char path[FILE_MAX];
2847
2848 RNA_string_get(op->ptr, "filepath", path);
2849 ret = wm_save_as_mainfile_exec(C, op);
2850 }
2851 else {
2852 WM_event_add_fileselect(C, op);
2853 ret = OPERATOR_RUNNING_MODAL;
2854 }
2855
2856 return ret;
2857 }
2858
WM_OT_save_mainfile(wmOperatorType * ot)2859 void WM_OT_save_mainfile(wmOperatorType *ot)
2860 {
2861 ot->name = "Save Blender File";
2862 ot->idname = "WM_OT_save_mainfile";
2863 ot->description = "Save the current Blender file";
2864
2865 ot->invoke = wm_save_mainfile_invoke;
2866 ot->exec = wm_save_as_mainfile_exec;
2867 ot->check = blend_save_check;
2868 /* omit window poll so this can work in background mode */
2869
2870 PropertyRNA *prop;
2871 WM_operator_properties_filesel(ot,
2872 FILE_TYPE_FOLDER | FILE_TYPE_BLENDER,
2873 FILE_BLENDER,
2874 FILE_SAVE,
2875 WM_FILESEL_FILEPATH,
2876 FILE_DEFAULTDISPLAY,
2877 FILE_SORT_ALPHA);
2878 RNA_def_boolean(ot->srna, "compress", false, "Compress", "Write compressed .blend file");
2879 RNA_def_boolean(ot->srna,
2880 "relative_remap",
2881 false,
2882 "Remap Relative",
2883 "Remap relative paths when saving to a different directory");
2884
2885 prop = RNA_def_boolean(ot->srna, "exit", false, "Exit", "Exit Blender after saving");
2886 RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
2887 }
2888
2889 /** \} */
2890
2891 /* -------------------------------------------------------------------- */
2892 /** \name Auto Script Execution Warning Dialog
2893 * \{ */
2894
wm_block_autorun_warning_ignore(bContext * C,void * arg_block,void * UNUSED (arg))2895 static void wm_block_autorun_warning_ignore(bContext *C, void *arg_block, void *UNUSED(arg))
2896 {
2897 wmWindow *win = CTX_wm_window(C);
2898 UI_popup_block_close(C, win, arg_block);
2899 }
2900
wm_block_autorun_warning_reload_with_scripts(bContext * C,void * arg_block,void * UNUSED (arg))2901 static void wm_block_autorun_warning_reload_with_scripts(bContext *C,
2902 void *arg_block,
2903 void *UNUSED(arg))
2904 {
2905 wmWindow *win = CTX_wm_window(C);
2906
2907 UI_popup_block_close(C, win, arg_block);
2908
2909 /* Save user preferences for permanent execution. */
2910 if ((U.flag & USER_SCRIPT_AUTOEXEC_DISABLE) == 0) {
2911 WM_operator_name_call(C, "WM_OT_save_userpref", WM_OP_EXEC_DEFAULT, NULL);
2912 }
2913
2914 /* Load file again with scripts enabled.
2915 * The reload is necessary to allow scripts to run when the files loads. */
2916 wmOperatorType *ot = WM_operatortype_find("WM_OT_revert_mainfile", false);
2917
2918 PointerRNA props_ptr;
2919 WM_operator_properties_create_ptr(&props_ptr, ot);
2920 RNA_boolean_set(&props_ptr, "use_scripts", true);
2921 WM_operator_name_call_ptr(C, ot, WM_OP_EXEC_DEFAULT, &props_ptr);
2922 WM_operator_properties_free(&props_ptr);
2923 }
2924
wm_block_autorun_warning_enable_scripts(bContext * C,void * arg_block,void * UNUSED (arg))2925 static void wm_block_autorun_warning_enable_scripts(bContext *C,
2926 void *arg_block,
2927 void *UNUSED(arg))
2928 {
2929 wmWindow *win = CTX_wm_window(C);
2930 Main *bmain = CTX_data_main(C);
2931
2932 UI_popup_block_close(C, win, arg_block);
2933
2934 /* Save user preferences for permanent execution. */
2935 if ((U.flag & USER_SCRIPT_AUTOEXEC_DISABLE) == 0) {
2936 WM_operator_name_call(C, "WM_OT_save_userpref", WM_OP_EXEC_DEFAULT, NULL);
2937 }
2938
2939 /* Force a full refresh, but without reloading the file. */
2940 LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) {
2941 BKE_scene_free_depsgraph_hash(scene);
2942 }
2943 }
2944
2945 /* Build the autorun warning dialog UI */
block_create_autorun_warning(struct bContext * C,struct ARegion * region,void * UNUSED (arg1))2946 static uiBlock *block_create_autorun_warning(struct bContext *C,
2947 struct ARegion *region,
2948 void *UNUSED(arg1))
2949 {
2950 wmWindowManager *wm = CTX_wm_manager(C);
2951 const uiStyle *style = UI_style_get_dpi();
2952 const int text_points_max = MAX2(style->widget.points, style->widgetlabel.points);
2953 const int dialog_width = text_points_max * 44 * U.dpi_fac;
2954
2955 uiBlock *block = UI_block_begin(C, region, "autorun_warning_popup", UI_EMBOSS);
2956
2957 UI_block_flag_enable(
2958 block, UI_BLOCK_KEEP_OPEN | UI_BLOCK_LOOP | UI_BLOCK_NO_WIN_CLIP | UI_BLOCK_NUMSELECT);
2959 UI_block_theme_style_set(block, UI_BLOCK_THEME_STYLE_POPUP);
2960 UI_block_emboss_set(block, UI_EMBOSS);
2961
2962 uiLayout *layout = UI_block_layout(
2963 block, UI_LAYOUT_VERTICAL, UI_LAYOUT_PANEL, 10, 2, dialog_width, 0, 0, style);
2964
2965 /* Text and some vertical space */
2966 uiLayout *col = uiLayoutColumn(layout, true);
2967 uiItemL_ex(col,
2968 TIP_("For security reasons, automatic execution of Python scripts "
2969 "in this file was disabled:"),
2970 ICON_ERROR,
2971 true,
2972 false);
2973 uiItemL_ex(col, G.autoexec_fail, ICON_BLANK1, false, true);
2974 uiItemL(col, TIP_("This may lead to unexpected behavior"), ICON_BLANK1);
2975
2976 uiItemS(layout);
2977
2978 PointerRNA pref_ptr;
2979 RNA_pointer_create(NULL, &RNA_PreferencesFilePaths, &U, &pref_ptr);
2980 uiItemR(layout,
2981 &pref_ptr,
2982 "use_scripts_auto_execute",
2983 0,
2984 TIP_("Permanently allow execution of scripts"),
2985 ICON_NONE);
2986
2987 uiItemS(layout);
2988
2989 /* Buttons */
2990 uiBut *but;
2991 uiLayout *split = uiLayoutSplit(layout, 0.0f, true);
2992 uiLayoutSetScaleY(split, 1.2f);
2993
2994 /* empty space */
2995 col = uiLayoutColumn(split, false);
2996 uiItemS(col);
2997
2998 col = uiLayoutColumn(split, false);
2999
3000 /* Allow reload if we have a saved file.
3001 * Otherwise just enable scripts and reset the depsgraphs. */
3002 if (G.relbase_valid && wm->file_saved) {
3003 but = uiDefIconTextBut(block,
3004 UI_BTYPE_BUT,
3005 0,
3006 ICON_NONE,
3007 IFACE_("Allow Execution"),
3008 0,
3009 0,
3010 50,
3011 UI_UNIT_Y,
3012 NULL,
3013 0,
3014 0,
3015 0,
3016 0,
3017 TIP_("Reload file with execution of Python scripts enabled"));
3018 UI_but_func_set(but, wm_block_autorun_warning_reload_with_scripts, block, NULL);
3019 }
3020 else {
3021 but = uiDefIconTextBut(block,
3022 UI_BTYPE_BUT,
3023 0,
3024 ICON_NONE,
3025 IFACE_("Allow Execution"),
3026 0,
3027 0,
3028 50,
3029 UI_UNIT_Y,
3030 NULL,
3031 0,
3032 0,
3033 0,
3034 0,
3035 TIP_("Enable scripts"));
3036 UI_but_func_set(but, wm_block_autorun_warning_enable_scripts, block, NULL);
3037 }
3038 UI_but_drawflag_disable(but, UI_BUT_TEXT_LEFT);
3039
3040 col = uiLayoutColumn(split, false);
3041 but = uiDefIconTextBut(block,
3042 UI_BTYPE_BUT,
3043 0,
3044 ICON_NONE,
3045 IFACE_("Ignore"),
3046 0,
3047 0,
3048 50,
3049 UI_UNIT_Y,
3050 NULL,
3051 0,
3052 0,
3053 0,
3054 0,
3055 TIP_("Continue using file without Python scripts"));
3056 UI_but_func_set(but, wm_block_autorun_warning_ignore, block, NULL);
3057 UI_but_drawflag_disable(but, UI_BUT_TEXT_LEFT);
3058 UI_but_flag_enable(but, UI_BUT_ACTIVE_DEFAULT);
3059
3060 UI_block_bounds_set_centered(block, 14 * U.dpi_fac);
3061
3062 return block;
3063 }
3064
wm_test_autorun_warning(bContext * C)3065 void wm_test_autorun_warning(bContext *C)
3066 {
3067 /* Test if any auto-execution of scripts failed. */
3068 if ((G.f & G_FLAG_SCRIPT_AUTOEXEC_FAIL) == 0) {
3069 return;
3070 }
3071
3072 /* Only show the warning once. */
3073 if (G.f & G_FLAG_SCRIPT_AUTOEXEC_FAIL_QUIET) {
3074 return;
3075 }
3076
3077 G.f |= G_FLAG_SCRIPT_AUTOEXEC_FAIL_QUIET;
3078
3079 wmWindowManager *wm = CTX_wm_manager(C);
3080 wmWindow *win = (wm->winactive) ? wm->winactive : wm->windows.first;
3081
3082 if (win) {
3083 wmWindow *prevwin = CTX_wm_window(C);
3084 CTX_wm_window_set(C, win);
3085 UI_popup_block_invoke(C, block_create_autorun_warning, NULL, NULL);
3086 CTX_wm_window_set(C, prevwin);
3087 }
3088 }
3089
3090 /** \} */
3091
3092 /* -------------------------------------------------------------------- */
3093 /** \name Close File Dialog
3094 * \{ */
3095
3096 static char save_images_when_file_is_closed = true;
3097
wm_block_file_close_cancel(bContext * C,void * arg_block,void * UNUSED (arg_data))3098 static void wm_block_file_close_cancel(bContext *C, void *arg_block, void *UNUSED(arg_data))
3099 {
3100 wmWindow *win = CTX_wm_window(C);
3101 UI_popup_block_close(C, win, arg_block);
3102 }
3103
wm_block_file_close_discard(bContext * C,void * arg_block,void * arg_data)3104 static void wm_block_file_close_discard(bContext *C, void *arg_block, void *arg_data)
3105 {
3106 wmGenericCallback *callback = WM_generic_callback_steal((wmGenericCallback *)arg_data);
3107
3108 /* Close the popup before executing the callback. Otherwise
3109 * the popup might be closed by the callback, which will lead
3110 * to a crash. */
3111 wmWindow *win = CTX_wm_window(C);
3112 UI_popup_block_close(C, win, arg_block);
3113
3114 callback->exec(C, callback->user_data);
3115 WM_generic_callback_free(callback);
3116 }
3117
wm_block_file_close_save(bContext * C,void * arg_block,void * arg_data)3118 static void wm_block_file_close_save(bContext *C, void *arg_block, void *arg_data)
3119 {
3120 const Main *bmain = CTX_data_main(C);
3121 wmGenericCallback *callback = WM_generic_callback_steal((wmGenericCallback *)arg_data);
3122 bool execute_callback = true;
3123
3124 wmWindow *win = CTX_wm_window(C);
3125 UI_popup_block_close(C, win, arg_block);
3126
3127 int modified_images_count = ED_image_save_all_modified_info(CTX_data_main(C), NULL);
3128 if (modified_images_count > 0 && save_images_when_file_is_closed) {
3129 if (ED_image_should_save_modified(bmain)) {
3130 ReportList *reports = CTX_wm_reports(C);
3131 ED_image_save_all_modified(C, reports);
3132 WM_report_banner_show();
3133 }
3134 else {
3135 execute_callback = false;
3136 }
3137 }
3138
3139 bool file_has_been_saved_before = BKE_main_blendfile_path(bmain)[0] != '\0';
3140
3141 if (file_has_been_saved_before) {
3142 WM_operator_name_call(C, "WM_OT_save_mainfile", WM_OP_EXEC_DEFAULT, NULL);
3143 }
3144 else {
3145 WM_operator_name_call(C, "WM_OT_save_mainfile", WM_OP_INVOKE_DEFAULT, NULL);
3146 execute_callback = false;
3147 }
3148
3149 if (execute_callback) {
3150 callback->exec(C, callback->user_data);
3151 }
3152 WM_generic_callback_free(callback);
3153 }
3154
wm_block_file_close_cancel_button(uiBlock * block,wmGenericCallback * post_action)3155 static void wm_block_file_close_cancel_button(uiBlock *block, wmGenericCallback *post_action)
3156 {
3157 uiBut *but = uiDefIconTextBut(
3158 block, UI_BTYPE_BUT, 0, 0, IFACE_("Cancel"), 0, 0, 0, UI_UNIT_Y, 0, 0, 0, 0, 0, "");
3159 UI_but_func_set(but, wm_block_file_close_cancel, block, post_action);
3160 UI_but_drawflag_disable(but, UI_BUT_TEXT_LEFT);
3161 }
3162
wm_block_file_close_discard_button(uiBlock * block,wmGenericCallback * post_action)3163 static void wm_block_file_close_discard_button(uiBlock *block, wmGenericCallback *post_action)
3164 {
3165 uiBut *but = uiDefIconTextBut(
3166 block, UI_BTYPE_BUT, 0, 0, IFACE_("Don't Save"), 0, 0, 0, UI_UNIT_Y, 0, 0, 0, 0, 0, "");
3167 UI_but_func_set(but, wm_block_file_close_discard, block, post_action);
3168 UI_but_drawflag_disable(but, UI_BUT_TEXT_LEFT);
3169 }
3170
wm_block_file_close_save_button(uiBlock * block,wmGenericCallback * post_action)3171 static void wm_block_file_close_save_button(uiBlock *block, wmGenericCallback *post_action)
3172 {
3173 uiBut *but = uiDefIconTextBut(
3174 block, UI_BTYPE_BUT, 0, 0, IFACE_("Save"), 0, 0, 0, UI_UNIT_Y, 0, 0, 0, 0, 0, "");
3175 UI_but_func_set(but, wm_block_file_close_save, block, post_action);
3176 UI_but_drawflag_disable(but, UI_BUT_TEXT_LEFT);
3177 UI_but_flag_enable(but, UI_BUT_ACTIVE_DEFAULT);
3178 }
3179
3180 static const char *close_file_dialog_name = "file_close_popup";
3181
block_create__close_file_dialog(struct bContext * C,struct ARegion * region,void * arg1)3182 static uiBlock *block_create__close_file_dialog(struct bContext *C,
3183 struct ARegion *region,
3184 void *arg1)
3185 {
3186 wmGenericCallback *post_action = (wmGenericCallback *)arg1;
3187 Main *bmain = CTX_data_main(C);
3188 const uiStyle *style = UI_style_get_dpi();
3189 const short icon_size = 64 * U.dpi_fac;
3190 const int text_points_max = MAX2(style->widget.points, style->widgetlabel.points);
3191 const int dialog_width = icon_size + (text_points_max * 34 * U.dpi_fac);
3192
3193 /* Calculate icon column factor. */
3194 const float split_factor = (float)icon_size / (float)(dialog_width - style->columnspace);
3195
3196 uiBlock *block = UI_block_begin(C, region, close_file_dialog_name, UI_EMBOSS);
3197
3198 UI_block_flag_enable(
3199 block, UI_BLOCK_KEEP_OPEN | UI_BLOCK_LOOP | UI_BLOCK_NO_WIN_CLIP | UI_BLOCK_NUMSELECT);
3200 UI_block_theme_style_set(block, UI_BLOCK_THEME_STYLE_POPUP);
3201 UI_block_emboss_set(block, UI_EMBOSS);
3202
3203 uiLayout *block_layout = UI_block_layout(
3204 block, UI_LAYOUT_VERTICAL, UI_LAYOUT_PANEL, 0, 0, dialog_width, 0, 0, style);
3205
3206 /* Split layout to put alert icon on left side. */
3207 uiLayout *split_block = uiLayoutSplit(block_layout, split_factor, false);
3208
3209 /* Alert Icon. */
3210 uiLayout *layout = uiLayoutColumn(split_block, false);
3211 uiDefButAlert(block, ALERT_ICON_QUESTION, 0, 0, 0, icon_size);
3212
3213 /* The rest of the content on the right. */
3214 layout = uiLayoutColumn(split_block, false);
3215
3216 /* Title. */
3217 uiItemL_ex(layout, TIP_("Save changes before closing?"), ICON_NONE, true, false);
3218
3219 /* Filename. */
3220 const char *blendfile_pathpath = BKE_main_blendfile_path(CTX_data_main(C));
3221 char filename[FILE_MAX];
3222 if (blendfile_pathpath[0] != '\0') {
3223 BLI_split_file_part(blendfile_pathpath, filename, sizeof(filename));
3224 BLI_path_extension_replace(filename, sizeof(filename), "");
3225 }
3226 else {
3227 STRNCPY(filename, IFACE_("Untitled"));
3228 }
3229 uiItemL(layout, filename, ICON_NONE);
3230
3231 /* Image Saving Warnings. */
3232 ReportList reports;
3233 BKE_reports_init(&reports, RPT_STORE);
3234 uint modified_images_count = ED_image_save_all_modified_info(bmain, &reports);
3235
3236 LISTBASE_FOREACH (Report *, report, &reports.list) {
3237 uiLayout *row = uiLayoutColumn(layout, false);
3238 uiLayoutSetScaleY(row, 0.6f);
3239 uiItemS_ex(row, 1.2f);
3240
3241 /* Error messages created in ED_image_save_all_modified_info() can be long,
3242 * but are made to separate into two parts at first colon between text and paths.
3243 */
3244 char *message = BLI_strdupn(report->message, report->len);
3245 char *path_info = strstr(message, ": ");
3246 if (path_info) {
3247 /* Terminate message string at colon. */
3248 path_info[1] = '\0';
3249 /* Skip over the ": " */
3250 path_info += 2;
3251 }
3252 uiItemL_ex(row, message, ICON_NONE, false, true);
3253 if (path_info) {
3254 uiItemL_ex(row, path_info, ICON_NONE, false, true);
3255 }
3256 MEM_freeN(message);
3257 }
3258
3259 /* Modified Images Checkbox. */
3260 if (modified_images_count > 0) {
3261 char message[64];
3262 BLI_snprintf(message,
3263 sizeof(message),
3264 (modified_images_count == 1) ? "Save %u modified image" :
3265 "Save %u modified images",
3266 modified_images_count);
3267 uiItemS_ex(layout, 2.0f);
3268 uiDefButBitC(block,
3269 UI_BTYPE_CHECKBOX,
3270 1,
3271 0,
3272 message,
3273 0,
3274 0,
3275 0,
3276 UI_UNIT_Y,
3277 &save_images_when_file_is_closed,
3278 0,
3279 0,
3280 0,
3281 0,
3282 "");
3283 }
3284
3285 BKE_reports_clear(&reports);
3286
3287 uiItemS_ex(layout, 1.0f);
3288
3289 /* Buttons. */
3290 #ifdef _WIN32
3291 const bool windows_layout = true;
3292 #else
3293 const bool windows_layout = false;
3294 #endif
3295
3296 if (windows_layout) {
3297 /* Windows standard layout. */
3298
3299 uiLayout *split = uiLayoutSplit(block_layout, 0.174f, true);
3300 uiLayoutSetScaleY(split, 1.2f);
3301
3302 uiLayoutColumn(split, false);
3303 uiItemS(layout);
3304
3305 uiLayoutColumn(split, false);
3306 wm_block_file_close_save_button(block, post_action);
3307
3308 uiLayoutColumn(split, false);
3309 wm_block_file_close_discard_button(block, post_action);
3310
3311 uiLayoutColumn(split, false);
3312 wm_block_file_close_cancel_button(block, post_action);
3313 }
3314 else {
3315 /* Non-Windows layout (macOS and Linux). */
3316
3317 uiLayout *split = uiLayoutSplit(block_layout, 0.167f, true);
3318 uiLayoutSetScaleY(split, 1.2f);
3319
3320 layout = uiLayoutColumn(split, false);
3321 uiItemS(layout);
3322
3323 /* Split button area into two sections: 40/60. */
3324 uiLayout *split_left = uiLayoutSplit(split, 0.40f, true);
3325
3326 /* First button uses 75% of left side (30% of original). */
3327 uiLayoutSplit(split_left, 0.75f, true);
3328 wm_block_file_close_discard_button(block, post_action);
3329
3330 /* The right side is split 50/50 (each 30% of original). */
3331 uiLayout *split_right = uiLayoutSplit(split_left, 0.50f, true);
3332
3333 uiLayoutColumn(split_right, false);
3334 wm_block_file_close_cancel_button(block, post_action);
3335
3336 uiLayoutColumn(split_right, false);
3337 wm_block_file_close_save_button(block, post_action);
3338 }
3339
3340 UI_block_bounds_set_centered(block, 14 * U.dpi_fac);
3341 return block;
3342 }
3343
free_post_file_close_action(void * arg)3344 static void free_post_file_close_action(void *arg)
3345 {
3346 wmGenericCallback *action = (wmGenericCallback *)arg;
3347 WM_generic_callback_free(action);
3348 }
3349
wm_close_file_dialog(bContext * C,wmGenericCallback * post_action)3350 void wm_close_file_dialog(bContext *C, wmGenericCallback *post_action)
3351 {
3352 if (!UI_popup_block_name_exists(CTX_wm_screen(C), close_file_dialog_name)) {
3353 UI_popup_block_invoke(
3354 C, block_create__close_file_dialog, post_action, free_post_file_close_action);
3355 }
3356 else {
3357 WM_generic_callback_free(post_action);
3358 }
3359 }
3360
3361 /** \} */
3362