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
This commit is contained in:
Jacques Lucke 2019-05-14 14:13:02 +02:00
parent 37f87ae81b
commit 687385b963
6 changed files with 139 additions and 21 deletions

View 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()

View 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;

View 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 {

View 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 */

View 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);
}
/** \} */

View 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))