UI: Improved Operator Confirmations #104670

Merged
Harley Acheson merged 23 commits from Harley/blender:ConfirmDialogs into main 2024-01-04 18:42:34 +01:00
9 changed files with 321 additions and 51 deletions
Showing only changes of commit 88a12eec17 - Show all commits

View File

@ -511,6 +511,13 @@ static PathUsersMap bake_simulation_get_path_users(bContext *C, const Span<Objec
return path_users;
}
static void bake_simulation_warning(bContext * /*C*/,
wmOperator * /*op*/,
wmWarningDetails *warning)
{
STRNCPY(warning->message, "Overwrite existing bake data");
}
static int bake_simulation_invoke(bContext *C, wmOperator *op, const wmEvent * /*event*/)
{
Vector<Object *> objects;
@ -554,7 +561,7 @@ static int bake_simulation_invoke(bContext *C, wmOperator *op, const wmEvent * /
return OPERATOR_CANCELLED;
}
if (has_existing_bake_data) {
return WM_operator_confirm_message(C, op, "Overwrite existing bake data");
return WM_operator_confirm(C, op, nullptr);
}
return bake_simulation_exec(C, op);
}
@ -651,6 +658,7 @@ void OBJECT_OT_simulation_nodes_cache_bake(wmOperatorType *ot)
ot->invoke = bake_simulation_invoke;
ot->modal = bake_simulation_modal;
ot->poll = bake_simulation_poll;
ot->warning = bake_simulation_warning;
RNA_def_boolean(ot->srna, "selected", false, "Selected", "Bake cache on all selected objects");
}

View File

@ -36,6 +36,8 @@
#include "BLI_string_utf8.h"
#include "BLI_utildefines.h"
#include "BLT_translation.h"
#include "BKE_DerivedMesh.h"
#include "BKE_animsys.h"
#include "BKE_armature.h"
@ -1804,6 +1806,14 @@ static int modifier_apply_exec(bContext *C, wmOperator *op)
return modifier_apply_exec_ex(C, op, MODIFIER_APPLY_DATA, false);
}
static void modifier_apply_warning(bContext * /*C*/,
wmOperator * /*op*/,
wmWarningDetails *warning)
{
STRNCPY(warning->message, IFACE_("Make object data single-user and apply modifier"));
STRNCPY(warning->confirm_button, IFACE_("Apply"));
}
static int modifier_apply_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
int retval;
@ -1817,8 +1827,7 @@ static int modifier_apply_invoke(bContext *C, wmOperator *op, const wmEvent *eve
RNA_property_boolean_set(op->ptr, prop, true);
}
if (RNA_property_boolean_get(op->ptr, prop)) {
return WM_operator_confirm_message(
C, op, "Make object data single-user and apply modifier");
return WM_operator_confirm(C, op, nullptr);
}
}
return modifier_apply_exec(C, op);
@ -1835,6 +1844,7 @@ void OBJECT_OT_modifier_apply(wmOperatorType *ot)
ot->invoke = modifier_apply_invoke;
ot->exec = modifier_apply_exec;
ot->poll = modifier_apply_poll;
ot->warning = modifier_apply_warning;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_INTERNAL;

View File

@ -31,6 +31,8 @@
#include "BLI_utildefines.h"
#include "BLI_vector.hh"
#include "BLT_translation.h"
#include "BKE_armature.h"
#include "BKE_context.h"
#include "BKE_curve.h"
@ -1153,6 +1155,14 @@ static int object_transform_apply_exec(bContext *C, wmOperator *op)
return OPERATOR_FINISHED;
}
static void object_transform_apply_warning(bContext * /*C*/,
wmOperator * /*op*/,
wmWarningDetails *warning)
{
STRNCPY(warning->message, IFACE_("Create new object-data users and apply transformation"));
STRNCPY(warning->confirm_button, IFACE_("Apply"));
}
static int object_transform_apply_invoke(bContext *C, wmOperator *op, const wmEvent * /*event*/)
{
Object *ob = ED_object_active_context(C);
@ -1166,8 +1176,7 @@ static int object_transform_apply_invoke(bContext *C, wmOperator *op, const wmEv
RNA_property_boolean_set(op->ptr, prop, true);
}
if (RNA_property_boolean_get(op->ptr, prop)) {
return WM_operator_confirm_message(
C, op, "Create new object-data users and apply transformation");
return WM_operator_confirm(C, op, nullptr);
}
}
return object_transform_apply_exec(C, op);
@ -1184,6 +1193,7 @@ void OBJECT_OT_transform_apply(wmOperatorType *ot)
ot->exec = object_transform_apply_exec;
ot->invoke = object_transform_apply_invoke;
ot->poll = ED_operator_objectmode;
ot->warning = object_transform_apply_warning;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;

View File

@ -87,10 +87,13 @@ static int unpack_libraries_exec(bContext *C, wmOperator *op)
/** \name Unpack Blend File Libraries Operator
* \{ */
static int unpack_libraries_invoke(bContext *C, wmOperator *op, const wmEvent * /*event*/)
static void unpack_libraries_warning(bContext * /* C */,
wmOperator * /* op */,
wmWarningDetails *warning)
{
return WM_operator_confirm_message(
C, op, "Unpack Linked Libraries - creates directories, all new paths should work");
STRNCPY(warning->message, IFACE_("Creates directories, all new paths should work"));
STRNCPY(warning->confirm_button, IFACE_("Unpack"));
warning->icon = ALERT_ICON_INFO;
}
void FILE_OT_unpack_libraries(wmOperatorType *ot)
@ -101,8 +104,9 @@ void FILE_OT_unpack_libraries(wmOperatorType *ot)
ot->description = "Restore all packed linked data-blocks to their original locations";
/* api callbacks */
ot->invoke = unpack_libraries_invoke;
ot->invoke = WM_operator_confirm;
ot->exec = unpack_libraries_exec;
ot->warning = unpack_libraries_warning;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
@ -158,7 +162,14 @@ static int pack_all_exec(bContext *C, wmOperator *op)
return OPERATOR_FINISHED;
}
static int pack_all_invoke(bContext *C, wmOperator *op, const wmEvent * /*event*/)
static void pack_all_warning(bContext * /* C */, wmOperator * /* op */, wmWarningDetails *warning)
{
STRNCPY(warning->message,
IFACE_("Some images are mofified. These changes will be lost. Continue?"));
STRNCPY(warning->confirm_button, IFACE_("Pack"));
}
static int pack_all_invoke(bContext *C, wmOperator *op, const wmEvent * /* event */)
{
Main *bmain = CTX_data_main(C);
Image *ima;
@ -173,8 +184,7 @@ static int pack_all_invoke(bContext *C, wmOperator *op, const wmEvent * /*event*
}
if (ima) {
return WM_operator_confirm_message(
C, op, "Some images are painted on. These changes will be lost. Continue?");
return WM_operator_confirm(C, op, nullptr);
}
return pack_all_exec(C, op);
@ -190,6 +200,7 @@ void FILE_OT_pack_all(wmOperatorType *ot)
/* api callbacks */
ot->exec = pack_all_exec;
ot->invoke = pack_all_invoke;
ot->warning = pack_all_warning;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;

View File

@ -2131,6 +2131,43 @@ static bool ed_operator_outliner_id_orphans_active(bContext *C)
return true;
}
static void orphans_purge_warning(bContext *C, wmOperator *op, wmWarningDetails *warning)
{
Main *bmain = CTX_data_main(C);
int num_tagged[INDEX_ID_MAX] = {0};
const bool do_local_ids = RNA_boolean_get(op->ptr, "do_local_ids");
const bool do_linked_ids = RNA_boolean_get(op->ptr, "do_linked_ids");
const bool do_recursive_cleanup = RNA_boolean_get(op->ptr, "do_recursive");
/* Tag all IDs to delete. */
BKE_lib_query_unused_ids_tag(
bmain, LIB_TAG_DOIT, do_local_ids, do_linked_ids, do_recursive_cleanup, num_tagged);
DynStr *dyn_str = BLI_dynstr_new();
bool is_first = true;
for (int i = 0; i < INDEX_ID_MAX - 2; i++) {
if (num_tagged[i] != 0) {
if (!is_first) {
BLI_dynstr_append(dyn_str, ", ");
}
else {
is_first = false;
}
BLI_dynstr_appendf(dyn_str,
"%d %s",
num_tagged[i],
TIP_(BKE_idtype_idcode_to_name_plural(BKE_idtype_idcode_from_index(i))));
}
}
STRNCPY(warning->title, IFACE_("Remove Unused Data"));
STRNCPY(warning->message, BLI_dynstr_get_cstring(dyn_str));
STRNCPY(warning->confirm_button, IFACE_("Purge"));
BLI_dynstr_free(dyn_str);
}
static int outliner_orphans_purge_invoke(bContext *C, wmOperator *op, const wmEvent * /*event*/)
{
Main *bmain = CTX_data_main(C);
@ -2151,31 +2188,7 @@ static int outliner_orphans_purge_invoke(bContext *C, wmOperator *op, const wmEv
return OPERATOR_CANCELLED;
}
DynStr *dyn_str = BLI_dynstr_new();
BLI_dynstr_appendf(dyn_str, TIP_("Purging %d unused data-blocks ("), num_tagged[INDEX_ID_NULL]);
bool is_first = true;
for (int i = 0; i < INDEX_ID_MAX - 2; i++) {
if (num_tagged[i] != 0) {
if (!is_first) {
BLI_dynstr_append(dyn_str, ", ");
}
else {
is_first = false;
}
BLI_dynstr_appendf(dyn_str,
"%d %s",
num_tagged[i],
TIP_(BKE_idtype_idcode_to_name_plural(BKE_idtype_idcode_from_index(i))));
}
}
BLI_dynstr_append(dyn_str, TIP_("). Click here to proceed..."));
char *message = BLI_dynstr_get_cstring(dyn_str);
int ret = WM_operator_confirm_message(C, op, message);
MEM_freeN(message);
BLI_dynstr_free(dyn_str);
return ret;
return WM_operator_confirm(C, op, nullptr);
}
static int outliner_orphans_purge_exec(bContext *C, wmOperator *op)
@ -2232,6 +2245,7 @@ void OUTLINER_OT_orphans_purge(wmOperatorType *ot)
ot->invoke = outliner_orphans_purge_invoke;
ot->exec = outliner_orphans_purge_exec;
ot->poll = ed_operator_outliner_id_orphans_active;
ot->warning = orphans_purge_warning;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;

View File

@ -716,17 +716,6 @@ int WM_operator_props_dialog_popup(struct bContext *C, struct wmOperator *op, in
int WM_operator_redo_popup(struct bContext *C, struct wmOperator *op);
int WM_operator_ui_popup(struct bContext *C, struct wmOperator *op, int width);
/**
* Can't be used as an invoke directly, needs message arg (can be NULL).
*/
int WM_operator_confirm_message_ex(struct bContext *C,
struct wmOperator *op,
const char *title,
int icon,
const char *message,
wmOperatorCallContext opcontext);
int WM_operator_confirm_message(struct bContext *C, struct wmOperator *op, const char *message);
/* Operator API. */
void WM_operator_free(struct wmOperator *op);

View File

@ -907,6 +907,30 @@ typedef struct wmTimer {
bool sleep;
} wmTimer;
typedef enum wmWarningSize {
WM_WARNING_SIZE_SMALL = 0,
WM_WARNING_SIZE_LARGE,
} wmWarningSize;
typedef enum wmWarningPosition {
WM_WARNING_POSITION_MOUSE = 0,
WM_WARNING_POSITION_CENTER,
} wmWarningPosition;
typedef struct wmWarningDetails {
char title[1024];
char message[1024];
char confirm_button[256];
char cancel_button[256];
int icon;
wmWarningSize size;
wmWarningPosition position;
bool confirm_default;
bool cancel_default;
bool mouse_move_quit;
bool red_alert;
} wmWarningDetails;
typedef struct wmOperatorType {
/** Text for UI, undo (should not exceed #OP_MAX_TYPENAME). */
const char *name;
@ -990,6 +1014,11 @@ typedef struct wmOperatorType {
*/
char *(*get_description)(struct bContext *C, struct wmOperatorType *, struct PointerRNA *);
/**
* If using WM_operator_confirm the following can override all parts of the dialog.
*/
void (*warning)(struct bContext *C, struct wmOperator *, wmWarningDetails *warning);
/** rna for properties */
struct StructRNA *srna;

View File

@ -2275,6 +2275,13 @@ static int wm_homefile_write_exec(bContext *C, wmOperator *op)
return OPERATOR_CANCELLED;
}
static void save_homefile_warning(bContext * /*C*/, wmOperator * /*op*/, wmWarningDetails *warning)
{
STRNCPY(warning->confirm_button, IFACE_("Save"));
warning->size = WM_WARNING_SIZE_LARGE;
warning->position = WM_WARNING_POSITION_CENTER;
}
void WM_OT_save_homefile(wmOperatorType *ot)
{
ot->name = "Save Startup File";
@ -2283,6 +2290,7 @@ void WM_OT_save_homefile(wmOperatorType *ot)
ot->invoke = WM_operator_confirm;
ot->exec = wm_homefile_write_exec;
ot->warning = save_homefile_warning;
}
/** \} */
@ -2439,6 +2447,19 @@ void WM_OT_read_userpref(wmOperatorType *ot)
ot->exec = wm_userpref_read_exec;
}
static void wm_userpref_read_factory_warning(bContext * /*C*/,
wmOperator * /*op*/,
wmWarningDetails *warning)
{
STRNCPY(warning->title, IFACE_("Load factory default preferences"));
STRNCPY(warning->message,
IFACE_("To make changes to preferences permanent, use \"Save Preferences\""));
STRNCPY(warning->confirm_button, IFACE_("Load"));
warning->icon = ALERT_ICON_BLENDER;
warning->size = WM_WARNING_SIZE_LARGE;
warning->position = WM_WARNING_POSITION_CENTER;
}
void WM_OT_read_factory_userpref(wmOperatorType *ot)
{
ot->name = "Load Factory Preferences";
@ -2449,6 +2470,7 @@ void WM_OT_read_factory_userpref(wmOperatorType *ot)
ot->invoke = WM_operator_confirm;
ot->exec = wm_userpref_read_exec;
ot->warning = wm_userpref_read_factory_warning;
read_factory_reset_props(ot);
}
@ -2648,16 +2670,26 @@ void WM_OT_read_homefile(wmOperatorType *ot)
/* omit poll to run in background mode */
}
static void read_factory_warning(bContext * /*C*/, wmOperator * /*op*/, wmWarningDetails *warning)
{
STRNCPY(warning->title, IFACE_("Load factory default startup file and preferences."));
STRNCPY(warning->message,
IFACE_("To make changes permanent, use \"Save Startup File\" and \"Save Preferences\""));
STRNCPY(warning->confirm_button, IFACE_("Load"));
warning->icon = ALERT_ICON_BLENDER;
warning->size = WM_WARNING_SIZE_LARGE;
warning->position = WM_WARNING_POSITION_CENTER;
}
void WM_OT_read_factory_settings(wmOperatorType *ot)
{
ot->name = "Load Factory Settings";
ot->idname = "WM_OT_read_factory_settings";
ot->description =
"Load factory default startup file and preferences. "
"To make changes permanent, use \"Save Startup File\" and \"Save Preferences\"";
ot->description = "Load factory default startup file and preferences";
ot->invoke = WM_operator_confirm;
ot->exec = wm_homefile_read_exec;
ot->warning = read_factory_warning;
/* Omit poll to run in background mode. */
read_factory_reset_props(ot);

View File

@ -1170,6 +1170,168 @@ int WM_enum_search_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(eve
return OPERATOR_INTERFACE;
}
static void wm_operator_block_cancel(bContext *C, void *arg_op, void *arg_block)
{
wmOperator *op = arg_op;
uiBlock *block = arg_block;
UI_popup_block_close(C, CTX_wm_window(C), block);
WM_redraw_windows(C);
if (op) {
if (op->type->cancel) {
op->type->cancel(C, op);
}
WM_operator_free(op);
}
}
static void wm_operator_block_confirm(bContext *C, void *arg_op, void *arg_block)
{
wmOperator *op = arg_op;
uiBlock *block = arg_block;
UI_popup_block_close(C, CTX_wm_window(C), block);
WM_redraw_windows(C);
if (op) {
WM_operator_call_ex(C, op, true);
}
}
static uiBlock *wm_block_confirm_create(bContext *C, ARegion *region, void *arg_op)
{
wmOperator *op = arg_op;
wmWarningDetails warning = {0};
/* Operator description must be freed. */
char *desc = WM_operatortype_description(C, op->type, op->ptr);
STRNCPY_RLEN(warning.title, desc);
MEM_freeN(desc);
STRNCPY_RLEN(warning.confirm_button, WM_operatortype_name(op->type, op->ptr));
STRNCPY_RLEN(warning.cancel_button, IFACE_("Cancel"));
warning.icon = ALERT_ICON_WARNING;
warning.size = WM_WARNING_SIZE_SMALL;
warning.position = WM_WARNING_POSITION_MOUSE;
warning.confirm_default = true;
warning.cancel_default = false;
warning.mouse_move_quit = false;
warning.red_alert = false;
/* uiBlock.flag */
int block_flags = UI_BLOCK_KEEP_OPEN | UI_BLOCK_NO_WIN_CLIP | UI_BLOCK_NUMSELECT;
if (op->type->warning) {
op->type->warning(C, op, &warning);
}
if (warning.mouse_move_quit) {
block_flags |= UI_BLOCK_MOVEMOUSE_QUIT;
}
if (warning.icon < ALERT_ICON_WARNING || warning.icon >= ALERT_ICON_MAX) {
warning.icon = ALERT_ICON_QUESTION;
}
uiBlock *block = UI_block_begin(C, region, __func__, UI_EMBOSS);
UI_block_theme_style_set(block, UI_BLOCK_THEME_STYLE_POPUP);
UI_block_flag_enable(block, block_flags);
const uiStyle *style = UI_style_get_dpi();
int text_width = MAX3(
120 * UI_SCALE_FAC,
BLF_width(style->widget.uifont_id, warning.title, ARRAY_SIZE(warning.title)),
BLF_width(style->widget.uifont_id, warning.message, ARRAY_SIZE(warning.message)));
const bool small = warning.size == WM_WARNING_SIZE_SMALL;
const int padding = (small ? 7 : 14) * UI_SCALE_FAC;
const short icon_size = (small ? (warning.message[0] ? 48 : 32) : 64) * UI_SCALE_FAC;
const int dialog_width = icon_size + text_width + (style->columnspace * 2.5);
const float split_factor = (float)icon_size / (float)(dialog_width - style->columnspace);
uiLayout *block_layout = UI_block_layout(
block, UI_LAYOUT_VERTICAL, UI_LAYOUT_PANEL, 0, 0, dialog_width, UI_UNIT_Y, 0, style);
/* Split layout to put alert icon on left side. */
uiLayout *split_block = uiLayoutSplit(block_layout, split_factor, false);
/* Alert icon on the left. */
uiLayout *layout = uiLayoutRow(split_block, true);
/* Using 'align_left' with 'row' avoids stretching the icon along the width of column. */
uiLayoutSetAlignment(layout, UI_LAYOUT_ALIGN_LEFT);
uiDefButAlert(block, warning.icon, 0, 0, icon_size, icon_size);
/* The rest of the content on the right. */
layout = uiLayoutColumn(split_block, true);
if (warning.title[0]) {
if (!warning.message[0]) {
uiItemS(layout);
}
uiItemL_ex(layout, warning.title, ICON_NONE, true, false);
}
if (warning.message[0]) {
uiItemL(layout, warning.message, ICON_NONE);
}
uiItemS_ex(layout, small ? 0.5f : 4.0f);
/* Buttons. */
#ifdef _WIN32
const bool windows_layout = true;
#else
const bool windows_layout = false;
#endif
uiBut *confirm = NULL;
uiBut *cancel = NULL;
int height = UI_UNIT_Y;
uiLayout *split = uiLayoutSplit(small ? block_layout : layout, 0.0f, true);
uiLayoutSetScaleY(split, small ? 1.1f : 1.2f);
uiLayoutColumn(split, false);
if (windows_layout) {
confirm = uiDefIconTextBut(
block, UI_BTYPE_BUT, 0, 0, warning.confirm_button, 0, 0, 0, height, 0, 0, 0, 0, 0, NULL);
uiLayoutColumn(split, false);
}
cancel = uiDefIconTextBut(
block, UI_BTYPE_BUT, 0, 0, warning.cancel_button, 0, 0, 0, height, 0, 0, 0, 0, 0, NULL);
if (!windows_layout) {
uiLayoutColumn(split, false);
confirm = uiDefIconTextBut(
block, UI_BTYPE_BUT, 0, 0, warning.confirm_button, 0, 0, 0, height, 0, 0, 0, 0, 0, NULL);
}
UI_block_func_set(block, NULL, NULL, NULL);
UI_but_func_set(confirm, wm_operator_block_confirm, op, block);
UI_but_func_set(cancel, wm_operator_block_cancel, op, block);
UI_but_drawflag_disable(confirm, UI_BUT_TEXT_LEFT);
UI_but_drawflag_disable(cancel, UI_BUT_TEXT_LEFT);
if (warning.red_alert) {
UI_but_flag_enable(confirm, UI_BUT_REDALERT);
}
else {
if (warning.cancel_default) {
UI_but_flag_enable(cancel, UI_BUT_ACTIVE_DEFAULT);
}
else if (warning.confirm_default) {
UI_but_flag_enable(confirm, UI_BUT_ACTIVE_DEFAULT);
}
}
if (warning.position == WM_WARNING_POSITION_MOUSE) {
int x = uiLayoutGetWidth(layout) * (windows_layout ? -0.33f : -0.66f);
int y = UI_UNIT_Y * (warning.message[0] ? 3.1 : 2.5);
UI_block_bounds_set_popup(block, padding, (const int[2]){x, y});
}
else if (warning.position == WM_WARNING_POSITION_CENTER) {
UI_block_bounds_set_centered(block, padding);
}
return block;
}
int WM_operator_confirm_message_ex(bContext *C,
wmOperator *op,
const char *title,
@ -1196,6 +1358,11 @@ int WM_operator_confirm_message_ex(bContext *C,
int WM_operator_confirm_message(bContext *C, wmOperator *op, const char *message)
{
if (op->type->warning) {
UI_popup_block_invoke(C, wm_block_confirm_create, op, NULL);
return OPERATOR_RUNNING_MODAL;
}
return WM_operator_confirm_message_ex(
C, op, IFACE_("OK?"), ICON_QUESTION, message, WM_OP_EXEC_REGION_WIN);
}