UI: File Close Dialog
authorJacques Lucke <mail@jlucke.com>
Fri, 17 May 2019 15:31:26 +0000 (17:31 +0200)
committerJacques Lucke <mail@jlucke.com>
Fri, 17 May 2019 15:43:36 +0000 (17:43 +0200)
This adds a new dialog that is shown whenever a file is closed.
So, either when a new file is opened, or when Blender quits.
The dialog allows to save unsaved changes. Furthermore it also
allows saving images that have been modified in Blender, but are
not saved yet.

Known limitations:
* Images that have no file path and have not been packed before,
  are not saved.
* On MacOS the old dialog is shown when Blender quits.

Reviewers: brecht, billreynish

Differential Revision: https://developer.blender.org/D4860

source/blender/editors/include/ED_image.h
source/blender/editors/space_image/image_ops.c
source/blender/windowmanager/WM_api.h
source/blender/windowmanager/intern/wm_event_system.c
source/blender/windowmanager/intern/wm_files.c
source/blender/windowmanager/intern/wm_window.c
source/blender/windowmanager/wm_files.h

index affe98e..a09e1d5 100644 (file)
@@ -111,6 +111,7 @@ void ED_image_draw_info(struct Scene *scene,
 
 bool ED_space_image_show_cache(struct SpaceImage *sima);
 
+bool ED_image_should_save_modified(const struct bContext *C);
 int ED_image_save_all_modified_info(const struct bContext *C, struct ReportList *reports);
 bool ED_image_save_all_modified(const struct bContext *C, struct ReportList *reports);
 
index b49fd92..533e39a 100644 (file)
@@ -2161,126 +2161,117 @@ void IMAGE_OT_save_sequence(wmOperatorType *ot)
 
 /********************** save all operator **********************/
 
-static int image_save_all_modified(const bContext *C,
-                                   ReportList *reports,
-                                   int *num_files,
-                                   const bool dry_run,
-                                   const bool ignore_dry_run_warnings)
+static bool image_should_be_saved_when_modified(Image *ima)
+{
+  return !ELEM(ima->type, IMA_TYPE_R_RESULT, IMA_TYPE_COMPOSITE);
+}
+
+static bool image_should_be_saved(Image *ima)
+{
+  if (BKE_image_is_dirty(ima) && (ima->source == IMA_SRC_FILE)) {
+    return image_should_be_saved_when_modified(ima);
+  }
+  else {
+    return false;
+  }
+}
+
+static bool image_has_valid_path(Image *ima)
+{
+  return strchr(ima->name, '\\') || strchr(ima->name, '/');
+}
+
+bool ED_image_should_save_modified(const bContext *C)
+{
+  return ED_image_save_all_modified_info(C, NULL) > 0;
+}
+
+int ED_image_save_all_modified_info(const bContext *C, ReportList *reports)
 {
   Main *bmain = CTX_data_main(C);
-  Scene *scene = CTX_data_scene(C);
   GSet *unique_paths = BLI_gset_str_new(__func__);
-  bool ok = true;
 
-  if (num_files) {
-    *num_files = 0;
-  }
+  int num_saveable_images = 0;
 
   for (Image *ima = bmain->images.first; ima; ima = ima->id.next) {
-    if (ELEM(ima->type, IMA_TYPE_R_RESULT, IMA_TYPE_COMPOSITE)) {
-      /* Don't save render results automatically. */
-    }
-    else if (BKE_image_is_dirty(ima) && (ima->source == IMA_SRC_FILE)) {
+    if (image_should_be_saved(ima)) {
       if (BKE_image_has_packedfile(ima)) {
         if (ima->id.lib == NULL) {
-          /* Re-pack. */
-          if (!dry_run) {
-            BKE_image_memorypack(ima);
-          }
-
-          if (num_files) {
-            (*num_files)++;
-          }
+          num_saveable_images++;
         }
-        else if (!ignore_dry_run_warnings) {
-          /* Can't pack to library data. */
+        else {
           BKE_reportf(reports,
                       RPT_WARNING,
                       "Packed library image: %s from library %s can't be saved",
                       ima->id.name,
                       ima->id.lib->name);
-          ok = false;
         }
       }
       else {
-        /* Save to file. */
-        const bool valid_path = strchr(ima->name, '\\') || strchr(ima->name, '/');
-
-        if (valid_path) {
-          ImageSaveOptions opts;
-
-          BKE_image_save_options_init(&opts, bmain, scene);
-
-          if (image_save_options_init(bmain, &opts, ima, NULL, false, false)) {
-            if (!BLI_gset_haskey(unique_paths, opts.filepath)) {
-              if (!dry_run) {
-                const bool save_ok = BKE_image_save(reports, bmain, ima, NULL, &opts);
-
-                if (save_ok) {
-                  BLI_gset_insert(unique_paths, BLI_strdup(opts.filepath));
-                }
-
-                ok = ok && save_ok;
-              }
-
-              if (num_files) {
-                (*num_files)++;
-              }
-            }
-            else if (!ignore_dry_run_warnings) {
-              BKE_reportf(reports,
-                          RPT_WARNING,
-                          "File path used by more than one saved image: %s",
-                          opts.filepath);
-              ok = false;
-            }
+        if (image_has_valid_path(ima)) {
+          num_saveable_images++;
+          if (BLI_gset_haskey(unique_paths, ima->name)) {
+            BKE_reportf(reports,
+                        RPT_WARNING,
+                        "File path used by more than one saved image: %s",
+                        ima->name);
+          }
+          else {
+            BLI_gset_insert(unique_paths, BLI_strdup(ima->name));
           }
         }
-        else if (!ignore_dry_run_warnings) {
+        else {
           BKE_reportf(reports,
                       RPT_WARNING,
                       "Image %s can't be saved, no valid file path: %s",
                       ima->id.name,
                       ima->name);
-          ok = false;
         }
       }
     }
   }
 
   BLI_gset_free(unique_paths, MEM_freeN);
-
-  return ok;
-}
-
-int ED_image_save_all_modified_info(const bContext *C, ReportList *reports)
-{
-  /* Dry run to get number of files, and any warnings we can detect in advance. */
-  int num_files;
-  image_save_all_modified(C, reports, &num_files, true, false);
-  return num_files;
+  return num_saveable_images;
 }
 
 bool ED_image_save_all_modified(const bContext *C, ReportList *reports)
 {
-  /* Save, and ignore any warnings that we already detected in
-   * ED_image_save_all_modified_info. */
-  return image_save_all_modified(C, reports, NULL, false, true);
+  ED_image_save_all_modified_info(C, reports);
+
+  Main *bmain = CTX_data_main(C);
+  bool ok = true;
+
+  for (Image *ima = bmain->images.first; ima; ima = ima->id.next) {
+    if (image_should_be_saved(ima)) {
+      if (BKE_image_has_packedfile(ima)) {
+        BKE_image_memorypack(ima);
+      }
+      else {
+        if (image_has_valid_path(ima)) {
+          ImageSaveOptions opts;
+          Scene *scene = CTX_data_scene(C);
+          BKE_image_save_options_init(&opts, bmain, scene);
+          if (image_save_options_init(bmain, &opts, ima, NULL, false, false)) {
+            bool saved_successfully = BKE_image_save(reports, bmain, ima, NULL, &opts);
+            ok = ok && saved_successfully;
+          }
+        }
+      }
+    }
+  }
+  return ok;
 }
 
 static bool image_save_all_modified_poll(bContext *C)
 {
-  /* Let operator run if there are any files to saved, or any warnings to
-   * report about files that we can't save. */
-  int num_files;
-  bool ok = image_save_all_modified(C, NULL, &num_files, true, false);
-  return (num_files > 0) || !ok;
+  int num_files = ED_image_save_all_modified_info(C, NULL);
+  return num_files > 0;
 }
 
 static int image_save_all_modified_exec(bContext *C, wmOperator *op)
 {
-  /* Save, and show all warnings. */
-  image_save_all_modified(C, op->reports, NULL, false, false);
+  ED_image_save_all_modified(C, op->reports);
   return OPERATOR_FINISHED;
 }
 
index bd02a1e..70f9867 100644 (file)
@@ -401,6 +401,10 @@ int WM_operator_name_call(struct bContext *C,
                           const char *opstring,
                           short context,
                           struct PointerRNA *properties);
+int WM_operator_name_call_with_properties(struct bContext *C,
+                                          const char *opstring,
+                                          short context,
+                                          struct IDProperty *properties);
 int WM_operator_call_py(struct bContext *C,
                         struct wmOperatorType *ot,
                         short context,
index 8ad23af..6683085 100644 (file)
@@ -1678,6 +1678,17 @@ int WM_operator_name_call(bContext *C, const char *opstring, short context, Poin
   return 0;
 }
 
+int WM_operator_name_call_with_properties(struct bContext *C,
+                                          const char *opstring,
+                                          short context,
+                                          struct IDProperty *properties)
+{
+  PointerRNA props_ptr;
+  wmOperatorType *ot = WM_operatortype_find(opstring, false);
+  RNA_pointer_create(NULL, ot->srna, properties, &props_ptr);
+  return WM_operator_name_call_ptr(C, ot, context, &props_ptr);
+}
+
 /**
  * Call an existent menu. The menu can be created in C or Python.
  */
index 329763a..fabdd71 100644 (file)
@@ -76,6 +76,7 @@
 #include "BKE_blender_undo.h"
 #include "BKE_context.h"
 #include "BKE_global.h"
+#include "BKE_idprop.h"
 #include "BKE_main.h"
 #include "BKE_packedFile.h"
 #include "BKE_report.h"
@@ -98,6 +99,7 @@
 
 #include "ED_datafiles.h"
 #include "ED_fileselect.h"
+#include "ED_image.h"
 #include "ED_screen.h"
 #include "ED_view3d.h"
 #include "ED_util.h"
@@ -1922,11 +1924,27 @@ static int wm_homefile_read_exec(bContext *C, wmOperator *op)
   return OPERATOR_FINISHED;
 }
 
+static void wm_homefile_read_after_dialog_callback(bContext *C, void *user_data)
+{
+  WM_operator_name_call_with_properties(
+      C, "WM_OT_read_homefile", WM_OP_EXEC_DEFAULT, (IDProperty *)user_data);
+}
+
+static void wm_free_operator_properties_callback(void *user_data)
+{
+  IDProperty *properties = (IDProperty *)user_data;
+  IDP_FreeProperty(properties);
+}
+
 static int wm_homefile_read_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
 {
-  wmWindowManager *wm = CTX_wm_manager(C);
-  if (U.uiflag & USER_SAVE_PROMPT && !wm->file_saved) {
-    return WM_operator_confirm_message(C, op, "Changes in current file will be lost. Continue?");
+  if (U.uiflag & USER_SAVE_PROMPT && wm_file_or_image_is_modified(C)) {
+    GenericCallback *callback = MEM_callocN(sizeof(*callback), __func__);
+    callback->exec = wm_homefile_read_after_dialog_callback;
+    callback->user_data = IDP_CopyProperty(op->properties);
+    callback->free_user_data = wm_free_operator_properties_callback;
+    wm_close_file_dialog(C, callback);
+    return OPERATOR_INTERFACE;
   }
   else {
     return wm_homefile_read_exec(C, op);
@@ -2071,6 +2089,12 @@ enum {
 
 static int wm_open_mainfile_dispatch(bContext *C, wmOperator *op);
 
+static void wm_open_mainfile_after_dialog_callback(bContext *C, void *user_data)
+{
+  WM_operator_name_call_with_properties(
+      C, "WM_OT_open_mainfile", WM_OP_INVOKE_DEFAULT, (IDProperty *)user_data);
+}
+
 static int wm_open_mainfile__discard_changes(bContext *C, wmOperator *op)
 {
   if (RNA_boolean_get(op->ptr, "display_file_selector")) {
@@ -2080,14 +2104,13 @@ static int wm_open_mainfile__discard_changes(bContext *C, wmOperator *op)
     set_next_operator_state(op, OPEN_MAINFILE_STATE_OPEN);
   }
 
-  wmWindowManager *wm = CTX_wm_manager(C);
-  if (U.uiflag & USER_SAVE_PROMPT && !wm->file_saved) {
-    return WM_operator_confirm_message_ex(C,
-                                          op,
-                                          "Warning",
-                                          ICON_INFO,
-                                          "Changes in current file will be lost. Continue?",
-                                          WM_OP_INVOKE_DEFAULT);
+  if (U.uiflag & USER_SAVE_PROMPT && wm_file_or_image_is_modified(C)) {
+    GenericCallback *callback = MEM_callocN(sizeof(*callback), __func__);
+    callback->exec = wm_open_mainfile_after_dialog_callback;
+    callback->user_data = IDP_CopyProperty(op->properties);
+    callback->free_user_data = wm_free_operator_properties_callback;
+    wm_close_file_dialog(C, callback);
+    return OPERATOR_INTERFACE;
   }
   else {
     return wm_open_mainfile_dispatch(C, op);
@@ -2819,4 +2842,234 @@ void wm_test_autorun_warning(bContext *C)
   }
 }
 
+/* Close File Dialog
+ *************************************/
+
+static char save_images_when_file_is_closed = true;
+
+static void wm_block_file_close_cancel(bContext *C, void *arg_block, void *UNUSED(arg_data))
+{
+  wmWindow *win = CTX_wm_window(C);
+  UI_popup_block_close(C, win, arg_block);
+}
+
+static void wm_block_file_close_discard(bContext *C, void *arg_block, void *arg_data)
+{
+  GenericCallback *callback = wm_generic_callback_steal((GenericCallback *)arg_data);
+
+  /* Close the popup before executing the callback. Otherwise
+   * the popup might be closed by the callback, which will lead
+   * to a crash. */
+  wmWindow *win = CTX_wm_window(C);
+  UI_popup_block_close(C, win, arg_block);
+
+  callback->exec(C, callback->user_data);
+  wm_generic_callback_free(callback);
+}
+
+static void wm_block_file_close_save(bContext *C, void *arg_block, void *arg_data)
+{
+  GenericCallback *callback = wm_generic_callback_steal((GenericCallback *)arg_data);
+  bool execute_callback = true;
+
+  wmWindow *win = CTX_wm_window(C);
+  UI_popup_block_close(C, win, arg_block);
+
+  if (save_images_when_file_is_closed) {
+    ReportList *reports = CTX_wm_reports(C);
+    if (!ED_image_save_all_modified(C, reports)) {
+      execute_callback = false;
+    }
+    WM_report_banner_show();
+  }
+
+  Main *bmain = CTX_data_main(C);
+  bool file_has_been_saved_before = BKE_main_blendfile_path(bmain)[0] != '\0';
+
+  if (file_has_been_saved_before) {
+    WM_operator_name_call(C, "WM_OT_save_mainfile", WM_OP_EXEC_DEFAULT, NULL);
+  }
+  else {
+    WM_operator_name_call(C, "WM_OT_save_mainfile", WM_OP_INVOKE_DEFAULT, NULL);
+    execute_callback = false;
+  }
+
+  if (execute_callback) {
+    callback->exec(C, callback->user_data);
+  }
+  wm_generic_callback_free(callback);
+}
+
+static uiBlock *block_create__close_file_dialog(struct bContext *C, struct ARegion *ar, void *arg1)
+{
+  GenericCallback *post_action = (GenericCallback *)arg1;
+  Main *bmain = CTX_data_main(C);
+
+  uiStyle *style = UI_style_get();
+  uiBlock *block = UI_block_begin(C, ar, "file_close_popup", UI_EMBOSS);
+
+  UI_block_flag_enable(
+      block, UI_BLOCK_KEEP_OPEN | UI_BLOCK_LOOP | UI_BLOCK_NO_WIN_CLIP | UI_BLOCK_NUMSELECT);
+  UI_block_theme_style_set(block, UI_BLOCK_THEME_STYLE_POPUP);
+  UI_block_emboss_set(block, UI_EMBOSS);
+
+  uiLayout *layout = UI_block_layout(block,
+                                     UI_LAYOUT_VERTICAL,
+                                     UI_LAYOUT_PANEL,
+                                     10,
+                                     2,
+                                     U.widget_unit * 24,
+                                     U.widget_unit * 6,
+                                     0,
+                                     style);
+
+  bool blend_file_is_saved = BKE_main_blendfile_path(bmain)[0] != '\0';
+  if (blend_file_is_saved) {
+    uiItemL(layout, "This file has unsaved changes.", ICON_NONE);
+  }
+  else {
+    uiItemL(layout, "This file has not been saved yet.", ICON_NONE);
+  }
+
+  ReportList reports;
+  BKE_reports_init(&reports, RPT_STORE);
+  uint modified_images_count = ED_image_save_all_modified_info(C, &reports);
+
+  if (modified_images_count > 0) {
+    char message[64];
+    BLI_snprintf(message,
+                 sizeof(message),
+                 (modified_images_count == 1) ? "Save %u modified image" :
+                                                "Save %u modified images",
+                 modified_images_count);
+    uiDefButBitC(block,
+                 UI_BTYPE_CHECKBOX,
+                 1,
+                 0,
+                 message,
+                 0,
+                 0,
+                 0,
+                 UI_UNIT_Y,
+                 &save_images_when_file_is_closed,
+                 0,
+                 0,
+                 0,
+                 0,
+                 "");
+
+    LISTBASE_FOREACH (Report *, report, &reports.list) {
+      uiItemL(layout, report->message, ICON_ERROR);
+    }
+  }
+
+  BKE_reports_clear(&reports);
+
+  uiItemL(layout, "", ICON_NONE);
+
+  uiBut *but;
+  uiLayout *split = uiLayoutSplit(layout, 0.0f, true);
+  uiLayout *col = uiLayoutColumn(split, false);
+
+  but = uiDefIconTextBut(block,
+                         UI_BTYPE_BUT,
+                         0,
+                         ICON_SCREEN_BACK,
+                         IFACE_("Cancel"),
+                         0,
+                         0,
+                         0,
+                         UI_UNIT_Y,
+                         NULL,
+                         0,
+                         0,
+                         0,
+                         0,
+                         "");
+  UI_but_func_set(but, wm_block_file_close_cancel, block, post_action);
+
+  /* empty space between buttons */
+  col = uiLayoutColumn(split, false);
+  uiItemS(col);
+
+  col = uiLayoutColumn(split, true);
+  but = uiDefIconTextBut(block,
+                         UI_BTYPE_BUT,
+                         0,
+                         ICON_CANCEL,
+                         IFACE_("Discard Changes"),
+                         0,
+                         0,
+                         50,
+                         UI_UNIT_Y,
+                         NULL,
+                         0,
+                         0,
+                         0,
+                         0,
+                         "");
+  UI_but_func_set(but, wm_block_file_close_discard, block, post_action);
+
+  col = uiLayoutColumn(split, true);
+  but = uiDefIconTextBut(block,
+                         UI_BTYPE_BUT,
+                         0,
+                         ICON_CANCEL,
+                         IFACE_("Save"),
+                         0,
+                         0,
+                         50,
+                         UI_UNIT_Y,
+                         NULL,
+                         0,
+                         0,
+                         0,
+                         0,
+                         "");
+  UI_but_func_set(but, wm_block_file_close_save, block, post_action);
+  UI_but_flag_enable(but, UI_BUT_ACTIVE_DEFAULT);
+
+  UI_block_bounds_set_centered(block, 10);
+  return block;
+}
+
+static void free_post_file_close_action(void *arg)
+{
+  GenericCallback *action = (GenericCallback *)arg;
+  wm_generic_callback_free(action);
+}
+
+void wm_close_file_dialog(bContext *C, GenericCallback *post_action)
+{
+  UI_popup_block_invoke(
+      C, block_create__close_file_dialog, post_action, free_post_file_close_action);
+}
+
+bool wm_file_or_image_is_modified(const bContext *C)
+{
+  wmWindowManager *wm = CTX_wm_manager(C);
+  return !wm->file_saved || ED_image_should_save_modified(C);
+}
+
+void wm_generic_callback_free(GenericCallback *callback)
+{
+  if (callback->free_user_data) {
+    callback->free_user_data(callback->user_data);
+  }
+  MEM_freeN(callback);
+}
+
+static void do_nothing(bContext *UNUSED(C), void *UNUSED(user_data))
+{
+}
+
+GenericCallback *wm_generic_callback_steal(GenericCallback *callback)
+{
+  GenericCallback *new_callback = MEM_dupallocN(callback);
+  callback->exec = do_nothing;
+  callback->free_user_data = NULL;
+  callback->user_data = NULL;
+  return new_callback;
+}
+
 /** \} */
index 409befd..c716867 100644 (file)
@@ -61,6 +61,7 @@
 #include "WM_types.h"
 #include "wm.h"
 #include "wm_draw.h"
+#include "wm_files.h"
 #include "wm_window.h"
 #include "wm_event_system.h"
 
@@ -356,150 +357,9 @@ wmWindow *wm_window_copy_test(bContext *C,
 /** \name Quit Confirmation Dialog
  * \{ */
 
-/** Cancel quitting and close the dialog */
-static void wm_block_confirm_quit_cancel(bContext *C, void *arg_block, void *UNUSED(arg))
+static void wm_save_file_on_quit_dialog_callback(bContext *C, void *UNUSED(user_data))
 {
-  wmWindow *win = CTX_wm_window(C);
-  UI_popup_block_close(C, win, arg_block);
-}
-
-/** Discard the file changes and quit */
-ATTR_NORETURN
-static void wm_block_confirm_quit_discard(bContext *C, void *arg_block, void *UNUSED(arg))
-{
-  wmWindow *win = CTX_wm_window(C);
-  UI_popup_block_close(C, win, arg_block);
-  WM_exit(C);
-}
-
-/* Save changes and quit */
-static void wm_block_confirm_quit_save(bContext *C, void *arg_block, void *UNUSED(arg))
-{
-  PointerRNA props_ptr;
-  wmWindow *win = CTX_wm_window(C);
-
-  UI_popup_block_close(C, win, arg_block);
-
-  wmOperatorType *ot = WM_operatortype_find("WM_OT_save_mainfile", false);
-
-  WM_operator_properties_create_ptr(&props_ptr, ot);
-  RNA_boolean_set(&props_ptr, "exit", true);
-  /* No need for second confirmation popup. */
-  RNA_boolean_set(&props_ptr, "check_existing", false);
-  WM_operator_name_call_ptr(C, ot, WM_OP_INVOKE_DEFAULT, &props_ptr);
-  WM_operator_properties_free(&props_ptr);
-}
-
-/* Build the confirm dialog UI */
-static uiBlock *block_create_confirm_quit(struct bContext *C,
-                                          struct ARegion *ar,
-                                          void *UNUSED(arg1))
-{
-  Main *bmain = CTX_data_main(C);
-
-  uiStyle *style = UI_style_get();
-  uiBlock *block = UI_block_begin(C, ar, "confirm_quit_popup", UI_EMBOSS);
-
-  UI_block_flag_enable(
-      block, UI_BLOCK_KEEP_OPEN | UI_BLOCK_LOOP | UI_BLOCK_NO_WIN_CLIP | UI_BLOCK_NUMSELECT);
-  UI_block_theme_style_set(block, UI_BLOCK_THEME_STYLE_POPUP);
-  UI_block_emboss_set(block, UI_EMBOSS);
-
-  uiLayout *layout = UI_block_layout(block,
-                                     UI_LAYOUT_VERTICAL,
-                                     UI_LAYOUT_PANEL,
-                                     10,
-                                     2,
-                                     U.widget_unit * 24,
-                                     U.widget_unit * 6,
-                                     0,
-                                     style);
-
-  /* Text and some vertical space */
-  {
-    char *message;
-    if (BKE_main_blendfile_path(bmain)[0] == '\0') {
-      message = BLI_strdup(IFACE_("This file has not been saved yet. Save before closing?"));
-    }
-    else {
-      const char *basename = BLI_path_basename(BKE_main_blendfile_path(bmain));
-      message = BLI_sprintfN(IFACE_("Save changes to \"%s\" before closing?"), basename);
-    }
-    uiItemL(layout, message, ICON_ERROR);
-    MEM_freeN(message);
-  }
-
-  uiItemS(layout);
-  uiItemS(layout);
-
-  /* Buttons */
-  uiBut *but;
-
-  uiLayout *split = uiLayoutSplit(layout, 0.0f, true);
-
-  uiLayout *col = uiLayoutColumn(split, false);
-
-  but = uiDefIconTextBut(block,
-                         UI_BTYPE_BUT,
-                         0,
-                         ICON_SCREEN_BACK,
-                         IFACE_("Cancel"),
-                         0,
-                         0,
-                         0,
-                         UI_UNIT_Y,
-                         NULL,
-                         0,
-                         0,
-                         0,
-                         0,
-                         TIP_("Do not quit"));
-  UI_but_func_set(but, wm_block_confirm_quit_cancel, block, NULL);
-
-  /* empty space between buttons */
-  col = uiLayoutColumn(split, false);
-  uiItemS(col);
-
-  col = uiLayoutColumn(split, 1);
-  but = uiDefIconTextBut(block,
-                         UI_BTYPE_BUT,
-                         0,
-                         ICON_CANCEL,
-                         IFACE_("Discard Changes"),
-                         0,
-                         0,
-                         50,
-                         UI_UNIT_Y,
-                         NULL,
-                         0,
-                         0,
-                         0,
-                         0,
-                         TIP_("Discard changes and quit"));
-  UI_but_func_set(but, wm_block_confirm_quit_discard, block, NULL);
-
-  col = uiLayoutColumn(split, 1);
-  but = uiDefIconTextBut(block,
-                         UI_BTYPE_BUT,
-                         0,
-                         ICON_FILE_TICK,
-                         IFACE_("Save & Quit"),
-                         0,
-                         0,
-                         50,
-                         UI_UNIT_Y,
-                         NULL,
-                         0,
-                         0,
-                         0,
-                         0,
-                         TIP_("Save and quit"));
-  UI_but_func_set(but, wm_block_confirm_quit_save, block, NULL);
-  UI_but_flag_enable(but, UI_BUT_ACTIVE_DEFAULT);
-
-  UI_block_bounds_set_centered(block, 10);
-
-  return block;
+  wm_exit_schedule_delayed(C);
 }
 
 /**
@@ -508,16 +368,9 @@ static uiBlock *block_create_confirm_quit(struct bContext *C,
  */
 static void wm_confirm_quit(bContext *C)
 {
-  wmWindow *win = CTX_wm_window(C);
-
-  if (GHOST_SupportsNativeDialogs() == 0) {
-    if (!UI_popup_block_name_exists(C, "confirm_quit_popup")) {
-      UI_popup_block_invoke(C, block_create_confirm_quit, NULL, NULL);
-    }
-  }
-  else if (GHOST_confirmQuit(win->ghostwin)) {
-    wm_exit_schedule_delayed(C);
-  }
+  GenericCallback *action = MEM_callocN(sizeof(*action), __func__);
+  action->exec = wm_save_file_on_quit_dialog_callback;
+  wm_close_file_dialog(C, action);
 }
 
 /**
@@ -529,7 +382,6 @@ static void wm_confirm_quit(bContext *C)
  */
 void wm_quit_with_optional_confirmation_prompt(bContext *C, wmWindow *win)
 {
-  wmWindowManager *wm = CTX_wm_manager(C);
   wmWindow *win_ctx = CTX_wm_window(C);
 
   /* The popup will be displayed in the context window which may not be set
@@ -537,7 +389,7 @@ void wm_quit_with_optional_confirmation_prompt(bContext *C, wmWindow *win)
   CTX_wm_window_set(C, win);
 
   if (U.uiflag & USER_SAVE_PROMPT) {
-    if (!wm->file_saved && !G.background) {
+    if (wm_file_or_image_is_modified(C) && !G.background) {
       wm_confirm_quit(C);
     }
     else {
index 2420950..8b3f1ea 100644 (file)
@@ -40,6 +40,17 @@ void wm_homefile_read(struct bContext *C,
                       bool *r_is_factory_startup);
 void wm_file_read_report(bContext *C, struct Main *bmain);
 
+typedef struct GenericCallback {
+  void (*exec)(bContext *C, void *user_data);
+  void *user_data;
+  void (*free_user_data)(void *user_data);
+} GenericCallback;
+
+GenericCallback *wm_generic_callback_steal(GenericCallback *callback);
+void wm_generic_callback_free(GenericCallback *callback);
+void wm_close_file_dialog(bContext *C, GenericCallback *post_action);
+bool wm_file_or_image_is_modified(const struct bContext *C);
+
 void WM_OT_save_homefile(struct wmOperatorType *ot);
 void WM_OT_userpref_autoexec_path_add(struct wmOperatorType *ot);
 void WM_OT_userpref_autoexec_path_remove(struct wmOperatorType *ot);