diff options
author | Jacques Lucke <mail@jlucke.com> | 2019-05-14 15:13:02 +0300 |
---|---|---|
committer | Jacques Lucke <mail@jlucke.com> | 2019-05-14 15:21:17 +0300 |
commit | 687385b963f4389b3d714fed264bb0ff24bc43f5 (patch) | |
tree | b45b5c6492361425533230e4267e848d7c383715 /source | |
parent | 37f87ae81bc31134cf985277d877afb7b922dbbc (diff) |
UI: Confirm dialog when closing an unsaved file
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
Diffstat (limited to 'source')
-rw-r--r-- | source/blender/editors/interface/interface_templates.c | 17 | ||||
-rw-r--r-- | source/blender/editors/space_topbar/space_topbar.c | 7 | ||||
-rw-r--r-- | source/blender/windowmanager/WM_api.h | 3 | ||||
-rw-r--r-- | source/blender/windowmanager/intern/wm_files.c | 113 | ||||
-rw-r--r-- | source/blender/windowmanager/intern/wm_operators.c | 14 |
5 files changed, 135 insertions, 19 deletions
diff --git a/source/blender/editors/interface/interface_templates.c b/source/blender/editors/interface/interface_templates.c index 40c85217bb3..9e91505f5e8 100644 --- a/source/blender/editors/interface/interface_templates.c +++ b/source/blender/editors/interface/interface_templates.c @@ -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; diff --git a/source/blender/editors/space_topbar/space_topbar.c b/source/blender/editors/space_topbar/space_topbar.c index 8b290009a97..725a49e417e 100644 --- a/source/blender/editors/space_topbar/space_topbar.c +++ b/source/blender/editors/space_topbar/space_topbar.c @@ -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 { diff --git a/source/blender/windowmanager/WM_api.h b/source/blender/windowmanager/WM_api.h index f1037dadf85..780add8eb88 100644 --- a/source/blender/windowmanager/WM_api.h +++ b/source/blender/windowmanager/WM_api.h @@ -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 */ diff --git a/source/blender/windowmanager/intern/wm_files.c b/source/blender/windowmanager/intern/wm_files.c index cf02709f051..20266be0eac 100644 --- a/source/blender/windowmanager/intern/wm_files.c +++ b/source/blender/windowmanager/intern/wm_files.c @@ -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); } /** \} */ diff --git a/source/blender/windowmanager/intern/wm_operators.c b/source/blender/windowmanager/intern/wm_operators.c index 98b79fc75ce..6817a9c8719 100644 --- a/source/blender/windowmanager/intern/wm_operators.c +++ b/source/blender/windowmanager/intern/wm_operators.c @@ -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)) |