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:
parent
37f87ae81b
commit
687385b963
|
@ -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()
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
|
|
@ -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))
|
||||
|
|
Loading…
Reference in New Issue