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