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