UI: Confirm dialog when closing an unsaved file
authorJacques Lucke <mail@jlucke.com>
Tue, 14 May 2019 12:13:02 +0000 (14:13 +0200)
committerJacques Lucke <mail@jlucke.com>
Tue, 14 May 2019 12:21:17 +0000 (14:21 +0200)
The complexity in this patch comes from the fact
that the current operator system does not support
multi-step user interactions well.

More specifically, for this to work, we need to show
a confirm dialog and a file browser afterwards.
We decided that it is easier to keep everything in
a single operator, instead of creating separate
operators that invoke each other.

So, now the `WM_OT_open_mainfile` operator invokes
itself in different states. It implements a simple
finite state machine to manage the states.

The dialog itself is expected to be improved in
a future commit. See D4829 for more details.

Reviewers: brecht

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

release/scripts/startup/bl_operators/wm.py
source/blender/editors/interface/interface_templates.c
source/blender/editors/space_topbar/space_topbar.c
source/blender/windowmanager/WM_api.h
source/blender/windowmanager/intern/wm_files.c
source/blender/windowmanager/intern/wm_operators.c

index 85f4f89d01ec0bddbb67c539cd7a2fed65c7647b..0788e3bc51c8d19a59e3e0eed856e6db0f81b364 100644 (file)
@@ -1747,8 +1747,10 @@ class WM_OT_drop_blend_file(Operator):
         layout = menu.layout
 
         col = layout.column()
-        col.operator_context = 'EXEC_DEFAULT'
-        col.operator("wm.open_mainfile", text="Open", icon='FILE_FOLDER').filepath = self.filepath
+        col.operator_context = 'INVOKE_DEFAULT'
+        props = col.operator("wm.open_mainfile", text="Open", icon='FILE_FOLDER')
+        props.filepath = self.filepath
+        props.display_file_selector = False
 
         layout.separator()
         col = layout.column()
index 40c85217bb388e3003d480b567d80dfad21f766e..9e91505f5e8661d5872a078cb8641c21a92248fa 100644 (file)
@@ -6761,12 +6761,17 @@ int uiTemplateRecentFiles(uiLayout *layout, int rows)
 
   for (recent = G.recent_files.first, i = 0; (i < rows) && (recent); recent = recent->next, i++) {
     const char *filename = BLI_path_basename(recent->filepath);
-    uiItemStringO(layout,
-                  filename,
-                  BLO_has_bfile_extension(filename) ? ICON_FILE_BLEND : ICON_FILE_BACKUP,
-                  "WM_OT_open_mainfile",
-                  "filepath",
-                  recent->filepath);
+    PointerRNA ptr;
+    uiItemFullO(layout,
+                "WM_OT_open_mainfile",
+                filename,
+                BLO_has_bfile_extension(filename) ? ICON_FILE_BLEND : ICON_FILE_BACKUP,
+                NULL,
+                WM_OP_INVOKE_DEFAULT,
+                0,
+                &ptr);
+    RNA_string_set(&ptr, "filepath", recent->filepath);
+    RNA_boolean_set(&ptr, "display_file_selector", false);
   }
 
   return i;
index 8b290009a976e3d9ceaea493f33dc83f7ce32002..725a49e417e13ae22072c95fefb2247eb93ca326 100644 (file)
@@ -213,12 +213,15 @@ static void recent_files_menu_draw(const bContext *UNUSED(C), Menu *menu)
 {
   struct RecentFile *recent;
   uiLayout *layout = menu->layout;
-  uiLayoutSetOperatorContext(layout, WM_OP_EXEC_REGION_WIN);
+  uiLayoutSetOperatorContext(layout, WM_OP_INVOKE_DEFAULT);
   if (!BLI_listbase_is_empty(&G.recent_files)) {
     for (recent = G.recent_files.first; (recent); recent = recent->next) {
       const char *file = BLI_path_basename(recent->filepath);
       const int icon = BLO_has_bfile_extension(file) ? ICON_FILE_BLEND : ICON_FILE_BACKUP;
-      uiItemStringO(layout, file, icon, "WM_OT_open_mainfile", "filepath", recent->filepath);
+      PointerRNA ptr;
+      uiItemFullO(layout, "WM_OT_open_mainfile", file, icon, NULL, WM_OP_INVOKE_DEFAULT, 0, &ptr);
+      RNA_string_set(&ptr, "filepath", recent->filepath);
+      RNA_boolean_set(&ptr, "display_file_selector", false);
     }
   }
   else {
index f1037dadf8592a739b702ccbb8abba8a9650b564..780add8eb88487fcc9c31d50779f3f632e711f46 100644 (file)
@@ -373,7 +373,8 @@ int WM_operator_confirm_message_ex(struct bContext *C,
                                    struct wmOperator *op,
                                    const char *title,
                                    const int icon,
-                                   const char *message);
+                                   const char *message,
+                                   const short opcontext);
 int WM_operator_confirm_message(struct bContext *C, struct wmOperator *op, const char *message);
 
 /* operator api */
index cf02709f051867581b3a639347fcb1396149ef0e..20266be0eac7f85e79adc639a770d8f0029fee0e 100644 (file)
@@ -2017,13 +2017,83 @@ static bool wm_file_read_opwrap(bContext *C,
   return success;
 }
 
-/* currently fits in a pointer */
-struct FileRuntime {
-  bool is_untrusted;
+/* Generic operator state utilities
+ *********************************************/
+
+static void create_operator_state(wmOperatorType *ot, int first_state)
+{
+  PropertyRNA *prop = RNA_def_int(
+      ot->srna, "state", first_state, INT32_MIN, INT32_MAX, "State", "", INT32_MIN, INT32_MAX);
+  RNA_def_property_flag(prop, PROP_SKIP_SAVE);
+  RNA_def_property_flag(prop, PROP_HIDDEN);
+}
+
+static int get_operator_state(wmOperator *op)
+{
+  return RNA_int_get(op->ptr, "state");
+}
+
+static void set_next_operator_state(wmOperator *op, int state)
+{
+  RNA_int_set(op->ptr, "state", state);
+}
+
+typedef struct OperatorDispatchTarget {
+  int state;
+  int (*run)(bContext *C, wmOperator *op);
+} OperatorDispatchTarget;
+
+static int operator_state_dispatch(bContext *C, wmOperator *op, OperatorDispatchTarget *targets)
+{
+  int state = get_operator_state(op);
+  for (int i = 0; targets[i].run; i++) {
+    OperatorDispatchTarget target = targets[i];
+    if (target.state == state) {
+      return target.run(C, op);
+    }
+  }
+  BLI_assert(false);
+  return OPERATOR_CANCELLED;
+}
+
+/* Open Mainfile operator
+ ********************************************/
+
+enum {
+  OPEN_MAINFILE_STATE_DISCARD_CHANGES,
+  OPEN_MAINFILE_STATE_SELECT_FILE_PATH,
+  OPEN_MAINFILE_STATE_OPEN,
 };
 
-static int wm_open_mainfile_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
+static int wm_open_mainfile_dispatch(bContext *C, wmOperator *op);
+
+static int wm_open_mainfile__discard_changes(bContext *C, wmOperator *op)
 {
+  if (RNA_boolean_get(op->ptr, "display_file_selector")) {
+    set_next_operator_state(op, OPEN_MAINFILE_STATE_SELECT_FILE_PATH);
+  }
+  else {
+    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);
+  }
+  else {
+    return wm_open_mainfile_dispatch(C, op);
+  }
+}
+
+static int wm_open_mainfile__select_file_path(bContext *C, wmOperator *op)
+{
+  set_next_operator_state(op, OPEN_MAINFILE_STATE_OPEN);
+
   Main *bmain = CTX_data_main(C);
   const char *openname = BKE_main_blendfile_path(bmain);
 
@@ -2051,7 +2121,7 @@ static int wm_open_mainfile_invoke(bContext *C, wmOperator *op, const wmEvent *U
   return OPERATOR_RUNNING_MODAL;
 }
 
-static int wm_open_mainfile_exec(bContext *C, wmOperator *op)
+static int wm_open_mainfile__open(bContext *C, wmOperator *op)
 {
   char filepath[FILE_MAX];
   bool success;
@@ -2089,6 +2159,33 @@ static int wm_open_mainfile_exec(bContext *C, wmOperator *op)
   }
 }
 
+static OperatorDispatchTarget wm_open_mainfile_dispatch_targets[] = {
+    {OPEN_MAINFILE_STATE_DISCARD_CHANGES, wm_open_mainfile__discard_changes},
+    {OPEN_MAINFILE_STATE_SELECT_FILE_PATH, wm_open_mainfile__select_file_path},
+    {OPEN_MAINFILE_STATE_OPEN, wm_open_mainfile__open},
+    {0, NULL},
+};
+
+static int wm_open_mainfile_dispatch(bContext *C, wmOperator *op)
+{
+  return operator_state_dispatch(C, op, wm_open_mainfile_dispatch_targets);
+}
+
+static int wm_open_mainfile_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
+{
+  return wm_open_mainfile_dispatch(C, op);
+}
+
+static int wm_open_mainfile_exec(bContext *C, wmOperator *op)
+{
+  return wm_open_mainfile__open(C, op);
+}
+
+/* currently fits in a pointer */
+struct FileRuntime {
+  bool is_untrusted;
+};
+
 static bool wm_open_mainfile_check(bContext *UNUSED(C), wmOperator *op)
 {
   struct FileRuntime *file_info = (struct FileRuntime *)&op->customdata;
@@ -2169,6 +2266,12 @@ void WM_OT_open_mainfile(wmOperatorType *ot)
                   "Trusted Source",
                   "Allow .blend file to execute scripts automatically, default available from "
                   "system preferences");
+
+  PropertyRNA *prop = RNA_def_boolean(
+      ot->srna, "display_file_selector", true, "Display File Selector", "");
+  RNA_def_property_flag(prop, PROP_SKIP_SAVE);
+
+  create_operator_state(ot, OPEN_MAINFILE_STATE_DISCARD_CHANGES);
 }
 
 /** \} */
index 98b79fc75ce01c486fd8f259eb7502ad39023f96..6817a9c87195eca1ca54d2dec6115e49e2c9bf1f 100644 (file)
@@ -884,8 +884,12 @@ int WM_enum_search_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(eve
 }
 
 /* Can't be used as an invoke directly, needs message arg (can be NULL) */
-int WM_operator_confirm_message_ex(
-    bContext *C, wmOperator *op, const char *title, const int icon, const char *message)
+int WM_operator_confirm_message_ex(bContext *C,
+                                   wmOperator *op,
+                                   const char *title,
+                                   const int icon,
+                                   const char *message,
+                                   const short opcontext)
 {
   uiPopupMenu *pup;
   uiLayout *layout;
@@ -900,8 +904,7 @@ int WM_operator_confirm_message_ex(
 
   pup = UI_popup_menu_begin(C, title, icon);
   layout = UI_popup_menu_layout(pup);
-  uiItemFullO_ptr(
-      layout, op->type, message, ICON_NONE, properties, WM_OP_EXEC_REGION_WIN, 0, NULL);
+  uiItemFullO_ptr(layout, op->type, message, ICON_NONE, properties, opcontext, 0, NULL);
   UI_popup_menu_end(C, pup);
 
   return OPERATOR_INTERFACE;
@@ -909,7 +912,8 @@ int WM_operator_confirm_message_ex(
 
 int WM_operator_confirm_message(bContext *C, wmOperator *op, const char *message)
 {
-  return WM_operator_confirm_message_ex(C, op, IFACE_("OK?"), ICON_QUESTION, message);
+  return WM_operator_confirm_message_ex(
+      C, op, IFACE_("OK?"), ICON_QUESTION, message, WM_OP_EXEC_REGION_WIN);
 }
 
 int WM_operator_confirm(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))