Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--release/scripts/startup/bl_ui/space_topbar.py3
-rw-r--r--release/scripts/startup/bl_ui/space_userpref.py68
-rw-r--r--source/blender/editors/include/UI_interface.h11
-rw-r--r--source/blender/editors/interface/interface.c20
-rw-r--r--source/blender/editors/interface/interface_handlers.c12
-rw-r--r--source/blender/editors/interface/interface_intern.h4
-rw-r--r--source/blender/editors/interface/interface_layout.c2
-rw-r--r--source/blender/editors/interface/interface_region_search.c61
-rw-r--r--source/blender/editors/interface/interface_templates.c653
-rw-r--r--source/blender/editors/interface/interface_utils.c2
-rw-r--r--source/blender/editors/space_node/node_select.c4
-rw-r--r--source/blender/editors/space_outliner/outliner_tools.c4
-rw-r--r--source/blender/makesdna/DNA_userdef_types.h4
-rw-r--r--source/blender/makesrna/intern/rna_ui_api.c1
-rw-r--r--source/blender/makesrna/intern/rna_userdef.c4
-rw-r--r--source/blender/windowmanager/intern/wm_operators.c45
16 files changed, 827 insertions, 71 deletions
diff --git a/release/scripts/startup/bl_ui/space_topbar.py b/release/scripts/startup/bl_ui/space_topbar.py
index 1f52323f540..7ffb61fef5a 100644
--- a/release/scripts/startup/bl_ui/space_topbar.py
+++ b/release/scripts/startup/bl_ui/space_topbar.py
@@ -207,7 +207,8 @@ class TOPBAR_MT_editor_menus(Menu):
def draw(self, context):
layout = self.layout
- if context.area.show_menus:
+ # Allow calling this menu directly (this might not be a header area).
+ if getattr(context.area, "show_menus"):
layout.menu("TOPBAR_MT_app", text="", icon='BLENDER')
else:
layout.menu("TOPBAR_MT_app", text="Blender")
diff --git a/release/scripts/startup/bl_ui/space_userpref.py b/release/scripts/startup/bl_ui/space_userpref.py
index e6ee779d89b..165761254d0 100644
--- a/release/scripts/startup/bl_ui/space_userpref.py
+++ b/release/scripts/startup/bl_ui/space_userpref.py
@@ -2134,6 +2134,21 @@ class ExperimentalPanel:
url_prefix = "https://developer.blender.org/"
+ def _draw_items(self, context, items):
+ prefs = context.preferences
+ experimental = prefs.experimental
+
+ layout = self.layout
+ layout.use_property_split = True
+ layout.use_property_decorate = False
+
+ for prop_keywords, task in items:
+ split = layout.split(factor=0.66)
+ col = split.split()
+ col.prop(experimental, **prop_keywords)
+ col = split.split()
+ col.operator("wm.url_open", text=task, icon='URL').url = self.url_prefix + task
+
"""
# Example panel, leave it here so we always have a template to follow even
# after the features are gone from the experimental panel.
@@ -2142,46 +2157,34 @@ class USERPREF_PT_experimental_virtual_reality(ExperimentalPanel, Panel):
bl_label = "Virtual Reality"
def draw(self, context):
- prefs = context.preferences
- experimental = prefs.experimental
+ self._draw_items(
+ context, (
+ ({"property": "use_virtual_reality_scene_inspection"}, "T71347"),
+ ({"property": "use_virtual_reality_immersive_drawing"}, "T71348"),
+ )
+ )
+"""
- layout = self.layout
- layout.use_property_split = True
- layout.use_property_decorate = False
+class USERPREF_PT_experimental_ui(ExperimentalPanel, Panel):
+ bl_label = "UI"
- task = "T71347"
- split = layout.split(factor=0.66)
- col = split.split()
- col.prop(experimental, "use_virtual_reality_scene_inspection", text="Scene Inspection")
- col = split.split()
- col.operator("wm.url_open", text=task, icon='URL').url = self.url_prefix + task
-
- task = "T71348"
- split = layout.split(factor=0.66)
- col = split.column()
- col.prop(experimental, "use_virtual_reality_immersive_drawing", text="Continuous Immersive Drawing")
- col = split.column()
- col.operator("wm.url_open", text=task, icon='URL').url = self.url_prefix + task
-"""
+ def draw(self, context):
+ self._draw_items(
+ context, (
+ ({"property": "use_menu_search"}, "T74157"),
+ ),
+ )
class USERPREF_PT_experimental_system(ExperimentalPanel, Panel):
bl_label = "System"
def draw(self, context):
- prefs = context.preferences
- experimental = prefs.experimental
-
- layout = self.layout
- layout.use_property_split = True
- layout.use_property_decorate = False
-
- task = "T60695"
- split = layout.split(factor=0.66)
- col = split.split()
- col.prop(experimental, "use_undo_speedup")
- col = split.split()
- col.operator("wm.url_open", text=task, icon='URL').url = self.url_prefix + task
+ self._draw_items(
+ context, (
+ ({"property": "use_undo_speedup"}, "T60695"),
+ ),
+ )
# -----------------------------------------------------------------------------
@@ -2274,6 +2277,7 @@ classes = (
# Popovers.
USERPREF_PT_ndof_settings,
+ USERPREF_PT_experimental_ui,
USERPREF_PT_experimental_system,
# Add dynamically generated editor theme panels last,
diff --git a/source/blender/editors/include/UI_interface.h b/source/blender/editors/include/UI_interface.h
index 3e416cd8057..bb2edef8adc 100644
--- a/source/blender/editors/include/UI_interface.h
+++ b/source/blender/editors/include/UI_interface.h
@@ -506,6 +506,9 @@ typedef void (*uiButSearchFunc)(const struct bContext *C,
void *arg,
const char *str,
uiSearchItems *items);
+
+typedef void (*uiButSearchArgFreeFunc)(void *arg);
+
/* Must return allocated string. */
typedef char *(*uiButToolTipFunc)(struct bContext *C, void *argN, const char *tip);
typedef int (*uiButPushedStateFunc)(struct bContext *C, void *arg);
@@ -1565,13 +1568,13 @@ eAutoPropButsReturn uiDefAutoButsRNA(uiLayout *layout,
const bool compact);
/* use inside searchfunc to add items */
-bool UI_search_item_add(uiSearchItems *items, const char *name, void *poin, int iconid);
+bool UI_search_item_add(uiSearchItems *items, const char *name, void *poin, int iconid, int state);
/* bfunc gets search item *poin as arg2, or if NULL the old string */
void UI_but_func_search_set(uiBut *but,
uiButSearchCreateFunc cfunc,
uiButSearchFunc sfunc,
void *arg,
- bool free_arg,
+ uiButSearchArgFreeFunc search_arg_free_func,
uiButHandleFunc bfunc,
void *active);
/* height in pixels, it's using hardcoded values still */
@@ -2035,6 +2038,10 @@ void uiTemplateImageInfo(uiLayout *layout,
void uiTemplateRunningJobs(uiLayout *layout, struct bContext *C);
void UI_but_func_operator_search(uiBut *but);
void uiTemplateOperatorSearch(uiLayout *layout);
+
+void UI_but_func_menu_search(uiBut *but);
+void uiTemplateMenuSearch(uiLayout *layout);
+
eAutoPropButsReturn uiTemplateOperatorPropertyButs(const struct bContext *C,
uiLayout *layout,
struct wmOperator *op,
diff --git a/source/blender/editors/interface/interface.c b/source/blender/editors/interface/interface.c
index 2a4ca48a005..41b7683dff7 100644
--- a/source/blender/editors/interface/interface.c
+++ b/source/blender/editors/interface/interface.c
@@ -3207,8 +3207,9 @@ static void ui_but_free(const bContext *C, uiBut *but)
MEM_freeN(but->hold_argN);
}
- if (but->free_search_arg) {
- MEM_SAFE_FREE(but->search_arg);
+ if (but->search_arg_free_func) {
+ but->search_arg_free_func(but->search_arg);
+ but->search_arg = NULL;
}
if (but->active) {
@@ -6336,7 +6337,7 @@ void UI_but_func_search_set(uiBut *but,
uiButSearchCreateFunc search_create_func,
uiButSearchFunc search_func,
void *arg,
- bool free_arg,
+ uiButSearchArgFreeFunc search_arg_free_func,
uiButHandleFunc bfunc,
void *active)
{
@@ -6346,14 +6347,16 @@ void UI_but_func_search_set(uiBut *but,
search_create_func = ui_searchbox_create_generic;
}
- if (but->free_search_arg) {
- MEM_SAFE_FREE(but->search_arg);
+ if (but->search_arg_free_func != NULL) {
+ but->search_arg_free_func(but->search_arg);
+ but->search_arg = NULL;
}
but->search_create_func = search_create_func;
but->search_func = search_func;
+
but->search_arg = arg;
- but->free_search_arg = free_arg;
+ but->search_arg_free_func = search_arg_free_func;
if (bfunc) {
#ifdef DEBUG
@@ -6404,8 +6407,7 @@ static void operator_enum_search_cb(const struct bContext *C,
/* note: need to give the index rather than the
* identifier because the enum can be freed */
if (BLI_strcasestr(item->name, str)) {
- if (false ==
- UI_search_item_add(items, item->name, POINTER_FROM_INT(item->value), item->icon)) {
+ if (!UI_search_item_add(items, item->name, POINTER_FROM_INT(item->value), item->icon, 0)) {
break;
}
}
@@ -6462,7 +6464,7 @@ uiBut *uiDefSearchButO_ptr(uiBlock *block,
ui_searchbox_create_generic,
operator_enum_search_cb,
but,
- false,
+ NULL,
operator_enum_call_cb,
NULL);
diff --git a/source/blender/editors/interface/interface_handlers.c b/source/blender/editors/interface/interface_handlers.c
index 238223fddfd..2dfa29f5646 100644
--- a/source/blender/editors/interface/interface_handlers.c
+++ b/source/blender/editors/interface/interface_handlers.c
@@ -421,6 +421,9 @@ typedef struct uiAfterFunc {
PointerRNA rnapoin;
PropertyRNA *rnaprop;
+ void *search_arg;
+ uiButSearchArgFreeFunc search_arg_free_func;
+
bContextStore *context;
char undostr[BKE_UNDO_STR_MAX];
@@ -755,6 +758,11 @@ static void ui_apply_but_func(bContext *C, uiBut *but)
after->rnapoin = but->rnapoin;
after->rnaprop = but->rnaprop;
+ after->search_arg_free_func = but->search_arg_free_func;
+ after->search_arg = but->search_arg;
+ but->search_arg_free_func = NULL;
+ but->search_arg = NULL;
+
if (but->context) {
after->context = CTX_store_copy(but->context);
}
@@ -921,6 +929,10 @@ static void ui_apply_but_funcs_after(bContext *C)
MEM_freeN(after.rename_orig);
}
+ if (after.search_arg_free_func) {
+ after.search_arg_free_func(after.search_arg);
+ }
+
ui_afterfunc_update_preferences_dirty(&after);
if (after.undostr[0]) {
diff --git a/source/blender/editors/interface/interface_intern.h b/source/blender/editors/interface/interface_intern.h
index e2b4e8f2958..4a9c8a1ff54 100644
--- a/source/blender/editors/interface/interface_intern.h
+++ b/source/blender/editors/interface/interface_intern.h
@@ -203,8 +203,8 @@ struct uiBut {
uiButSearchCreateFunc search_create_func;
uiButSearchFunc search_func;
- bool free_search_arg;
void *search_arg;
+ uiButSearchArgFreeFunc search_arg_free_func;
uiButHandleRenameFunc rename_func;
void *rename_arg1;
@@ -649,6 +649,8 @@ ColorPicker *ui_block_colorpicker_create(struct uiBlock *block);
/* Searchbox for string button */
ARegion *ui_searchbox_create_generic(struct bContext *C, struct ARegion *butregion, uiBut *but);
ARegion *ui_searchbox_create_operator(struct bContext *C, struct ARegion *butregion, uiBut *but);
+ARegion *ui_searchbox_create_menu(struct bContext *C, struct ARegion *butregion, uiBut *but);
+
bool ui_searchbox_inside(struct ARegion *region, int x, int y);
int ui_searchbox_find_index(struct ARegion *region, const char *name);
void ui_searchbox_update(struct bContext *C, struct ARegion *region, uiBut *but, const bool reset);
diff --git a/source/blender/editors/interface/interface_layout.c b/source/blender/editors/interface/interface_layout.c
index 79b3c1a0a83..fed35ccff59 100644
--- a/source/blender/editors/interface/interface_layout.c
+++ b/source/blender/editors/interface/interface_layout.c
@@ -2610,7 +2610,7 @@ void ui_but_add_search(
ui_searchbox_create_generic,
ui_rna_collection_search_cb,
coll_search,
- true,
+ MEM_freeN,
NULL,
NULL);
}
diff --git a/source/blender/editors/interface/interface_region_search.c b/source/blender/editors/interface/interface_region_search.c
index dab42e47e0b..48779fd86dc 100644
--- a/source/blender/editors/interface/interface_region_search.c
+++ b/source/blender/editors/interface/interface_region_search.c
@@ -73,6 +73,7 @@ struct uiSearchItems {
char **names;
void **pointers;
int *icons;
+ int *states;
AutoComplete *autocpl;
void *active;
@@ -95,9 +96,18 @@ typedef struct uiSearchboxData {
#define SEARCH_ITEMS 10
-/* exported for use by search callbacks */
-/* returns zero if nothing to add */
-bool UI_search_item_add(uiSearchItems *items, const char *name, void *poin, int iconid)
+/**
+ * Public function exported for functions that use #UI_BTYPE_SEARCH_MENU.
+ *
+ * \param items: Stores the items.
+ * \param name: Text to display for the item.
+ * \param poin: Opaque pointer (for use by the caller).
+ * \param iconid: The icon, #ICON_NONE for no icon.
+ * \param state: The buttons state flag, compatible with #uiBut.flag,
+ * typically #UI_BUT_DISABLED / #UI_BUT_INACTIVE.
+ * \return false if there is nothing to add.
+ */
+bool UI_search_item_add(uiSearchItems *items, const char *name, void *poin, int iconid, int state)
{
/* hijack for autocomplete */
if (items->autocpl) {
@@ -135,6 +145,13 @@ bool UI_search_item_add(uiSearchItems *items, const char *name, void *poin, int
items->icons[items->totitem] = iconid;
}
+ /* Limit flags that can be set so flags such as 'UI_SELECT' aren't accidentally set
+ * which will cause problems, add others as needed. */
+ BLI_assert((state & ~(UI_BUT_DISABLED | UI_BUT_INACTIVE | UI_BUT_REDALERT)) == 0);
+ if (items->states) {
+ items->states[items->totitem] = state;
+ }
+
items->totitem++;
return true;
@@ -421,17 +438,16 @@ static void ui_searchbox_region_draw_cb(const bContext *C, ARegion *region)
if (data->preview) {
/* draw items */
for (a = 0; a < data->items.totitem; a++) {
+ const int state = ((a == data->active) ? UI_ACTIVE : 0) | data->items.states[a];
+
/* ensure icon is up-to-date */
ui_icon_ensure_deferred(C, data->items.icons[a], data->preview);
ui_searchbox_butrect(&rect, data, a);
/* widget itself */
- ui_draw_preview_item(&data->fstyle,
- &rect,
- data->items.names[a],
- data->items.icons[a],
- (a == data->active) ? UI_ACTIVE : 0);
+ ui_draw_preview_item(
+ &data->fstyle, &rect, data->items.names[a], data->items.icons[a], state);
}
/* indicate more */
@@ -451,6 +467,8 @@ static void ui_searchbox_region_draw_cb(const bContext *C, ARegion *region)
else {
/* draw items */
for (a = 0; a < data->items.totitem; a++) {
+ const int state = ((a == data->active) ? UI_ACTIVE : 0) | data->items.states[a];
+
ui_searchbox_butrect(&rect, data, a);
/* widget itself */
@@ -458,7 +476,7 @@ static void ui_searchbox_region_draw_cb(const bContext *C, ARegion *region)
&rect,
data->items.names[a],
data->items.icons[a],
- (a == data->active) ? UI_ACTIVE : 0,
+ state,
data->use_sep);
}
/* indicate more */
@@ -490,6 +508,7 @@ static void ui_searchbox_region_free_cb(ARegion *region)
MEM_freeN(data->items.names);
MEM_freeN(data->items.pointers);
MEM_freeN(data->items.icons);
+ MEM_freeN(data->items.states);
MEM_freeN(data);
region->regiondata = NULL;
@@ -657,6 +676,7 @@ ARegion *ui_searchbox_create_generic(bContext *C, ARegion *butregion, uiBut *but
data->items.names = MEM_callocN(data->items.maxitem * sizeof(void *), "search names");
data->items.pointers = MEM_callocN(data->items.maxitem * sizeof(void *), "search pointers");
data->items.icons = MEM_callocN(data->items.maxitem * sizeof(int), "search icons");
+ data->items.states = MEM_callocN(data->items.maxitem * sizeof(int), "search flags");
for (i = 0; i < data->items.maxitem; i++) {
data->items.names[i] = MEM_callocN(but->hardmax + 1, "search pointers");
}
@@ -718,9 +738,9 @@ static void ui_searchbox_region_draw_cb__operator(const bContext *UNUSED(C), ARe
/* widget itself */
/* NOTE: i18n messages extracting tool does the same, please keep it in sync. */
{
- wmOperatorType *ot = data->items.pointers[a];
+ const int state = ((a == data->active) ? UI_ACTIVE : 0) | data->items.states[a];
- int state = (a == data->active) ? UI_ACTIVE : 0;
+ wmOperatorType *ot = data->items.pointers[a];
char text_pre[128];
char *text_pre_p = strstr(ot->idname, "_OT_");
if (text_pre_p == NULL) {
@@ -780,6 +800,25 @@ void ui_searchbox_free(bContext *C, ARegion *region)
ui_region_temp_remove(C, CTX_wm_screen(C), region);
}
+static void ui_searchbox_region_draw_cb__menu(const bContext *UNUSED(C), ARegion *UNUSED(region))
+{
+ /* Currently unused. */
+}
+
+ARegion *ui_searchbox_create_menu(bContext *C, ARegion *butregion, uiBut *but)
+{
+ ARegion *region;
+
+ UI_but_drawflag_enable(but, UI_BUT_HAS_SHORTCUT);
+ region = ui_searchbox_create_generic(C, butregion, but);
+
+ if (false) {
+ region->type->draw = ui_searchbox_region_draw_cb__menu;
+ }
+
+ return region;
+}
+
/* sets red alert if button holds a string it can't find */
/* XXX weak: search_func adds all partial matches... */
void ui_but_search_refresh(uiBut *but)
diff --git a/source/blender/editors/interface/interface_templates.c b/source/blender/editors/interface/interface_templates.c
index 42e0aab8418..d7377a0e56e 100644
--- a/source/blender/editors/interface/interface_templates.c
+++ b/source/blender/editors/interface/interface_templates.c
@@ -38,13 +38,17 @@
#include "DNA_texture_types.h"
#include "BLI_alloca.h"
+#include "BLI_dynstr.h"
#include "BLI_fnmatch.h"
#include "BLI_ghash.h"
+#include "BLI_linklist.h"
#include "BLI_listbase.h"
#include "BLI_math.h"
+#include "BLI_memarena.h"
#include "BLI_path_util.h"
#include "BLI_rect.h"
#include "BLI_string.h"
+#include "BLI_string_utils.h"
#include "BLI_timecode.h"
#include "BLI_utildefines.h"
@@ -99,6 +103,9 @@
#include "PIL_time.h"
+/* For key-map item access. */
+#include "wm_event_system.h"
+
// #define USE_OP_RESET_BUT // we may want to make this optional, disable for now.
/* defines for templateID/TemplateSearch */
@@ -281,7 +288,7 @@ static uiBlock *template_common_search_menu(const bContext *C,
"");
}
UI_but_func_search_set(
- but, ui_searchbox_create_generic, search_func, search_arg, false, handle_func, active_item);
+ but, ui_searchbox_create_generic, search_func, search_arg, NULL, handle_func, active_item);
UI_block_bounds_set_normal(block, 0.3f * U.widget_unit);
UI_block_direction_set(block, UI_DIR_DOWN);
@@ -363,7 +370,7 @@ static bool id_search_add(const bContext *C,
int iconid = ui_id_icon_get(C, id, template_ui->preview);
- if (false == UI_search_item_add(items, name_ui, id, iconid)) {
+ if (!UI_search_item_add(items, name_ui, id, iconid, 0)) {
return false;
}
}
@@ -6643,7 +6650,7 @@ static void operator_search_cb(const bContext *C,
}
}
- if (false == UI_search_item_add(items, name, ot, 0)) {
+ if (!UI_search_item_add(items, name, ot, ICON_NONE, 0)) {
break;
}
}
@@ -6674,6 +6681,646 @@ void uiTemplateOperatorSearch(uiLayout *layout)
/** \} */
/* -------------------------------------------------------------------- */
+/** \name Menu Search Template
+ * \{ */
+
+struct MenuSearch_Parent {
+ struct MenuSearch_Parent *parent;
+ MenuType *parent_mt;
+ /* Set while writing menu items only. */
+ struct MenuSearch_Parent *temp_child;
+ const char *drawstr;
+};
+
+struct MenuSearch_Item {
+ struct MenuSearch_Item *next, *prev;
+ const char *drawstr;
+ const char *drawwstr_full;
+ /** Support a single level sub-menu nesting (for operator buttons that expand). */
+ const char *drawstr_submenu;
+ int icon;
+ int state;
+
+ struct MenuSearch_Parent *menu_parent;
+ MenuType *mt;
+
+ enum {
+ MENU_SEARCH_TYPE_OP = 1,
+ MENU_SEARCH_TYPE_RNA = 2,
+ } type;
+
+ union {
+ /* Operator menu item. */
+ struct {
+ wmOperatorType *type;
+ PointerRNA *opptr;
+ short opcontext;
+ bContextStore *context;
+ } op;
+
+ /* Property (only for check-boxe/boolean). */
+ struct {
+ PointerRNA ptr;
+ PropertyRNA *prop;
+ int index;
+ /** Only for enum buttons. */
+ int enum_value;
+ } rna;
+ };
+};
+
+struct MenuSearch_Data {
+ /** MenuSearch_Item */
+ ListBase items;
+ /** Use for all small allocations. */
+ MemArena *memarena;
+};
+
+static int menu_item_sort_by_drawstr_full(const void *menu_item_a_v, const void *menu_item_b_v)
+{
+ const struct MenuSearch_Item *menu_item_a = menu_item_a_v;
+ const struct MenuSearch_Item *menu_item_b = menu_item_b_v;
+ return strcmp(menu_item_a->drawwstr_full, menu_item_b->drawwstr_full);
+}
+
+static const char *strdup_memarena(MemArena *memarena, const char *str)
+{
+ const uint str_size = strlen(str) + 1;
+ char *str_dst = BLI_memarena_alloc(memarena, str_size);
+ memcpy(str_dst, str, str_size);
+ return str_dst;
+}
+
+static const char *strdup_memarena_from_dynstr(MemArena *memarena, DynStr *dyn_str)
+{
+ const uint str_size = BLI_dynstr_get_len(dyn_str) + 1;
+ char *str_dst = BLI_memarena_alloc(memarena, str_size);
+ BLI_dynstr_get_cstring_ex(dyn_str, str_dst);
+ return str_dst;
+}
+
+static bool menu_items_from_ui_create_item_from_button(struct MenuSearch_Data *data,
+ MemArena *memarena,
+ struct MenuType *mt,
+ const char *drawstr_submenu,
+ uiBut *but)
+{
+ struct MenuSearch_Item *item = NULL;
+ if (but->optype != NULL) {
+ item = BLI_memarena_calloc(memarena, sizeof(*item));
+ item->type = MENU_SEARCH_TYPE_OP;
+
+ item->op.type = but->optype;
+ item->op.opcontext = but->opcontext;
+ item->op.context = but->context;
+ item->op.opptr = but->opptr;
+ but->opptr = NULL;
+ }
+ else if (but->rnaprop != NULL) {
+ const int prop_type = RNA_property_type(but->rnaprop);
+ if (!ELEM(prop_type, PROP_BOOLEAN, PROP_ENUM)) {
+ /* Note that these buttons are not prevented,
+ * but aren't typically used in menus. */
+ printf("Button '%s' in menu '%s' is a menu item with unsupported RNA type %d\n",
+ but->drawstr,
+ mt->idname,
+ prop_type);
+ }
+ else {
+ item = BLI_memarena_calloc(memarena, sizeof(*item));
+ item->type = MENU_SEARCH_TYPE_RNA;
+
+ item->rna.ptr = but->rnapoin;
+ item->rna.prop = but->rnaprop;
+ item->rna.index = but->rnaindex;
+
+ if (prop_type == PROP_ENUM) {
+ item->rna.enum_value = (int)but->hardmax;
+ }
+ }
+ }
+
+ if (item != NULL) {
+ /* Handle shared settings. */
+ item->drawstr = strdup_memarena(memarena, but->drawstr);
+ item->icon = but->icon;
+ item->state = (but->flag & (UI_BUT_DISABLED | UI_BUT_INACTIVE | UI_BUT_REDALERT));
+ item->mt = mt;
+ item->drawstr_submenu = drawstr_submenu ? strdup_memarena(memarena, drawstr_submenu) : NULL;
+ BLI_addtail(&data->items, item);
+ return true;
+ }
+
+ return false;
+}
+
+/**
+ * Populate \a menu_stack with menus from inspecting active key-maps for this context.
+ */
+static void menu_types_add_from_keymap_items(bContext *C,
+ wmWindow *win,
+ ScrArea *sa,
+ ARegion *region,
+ LinkNode **menuid_stack_p,
+ GHash *menu_to_kmi,
+ GSet *menu_tagged)
+{
+ wmWindowManager *wm = CTX_wm_manager(C);
+ ListBase *handlers[] = {
+ &region->handlers,
+ &sa->handlers,
+ &win->handlers,
+ };
+
+ for (int handler_index = 0; handler_index < ARRAY_SIZE(handlers); handler_index++) {
+ LISTBASE_FOREACH (wmEventHandler *, handler_base, handlers[handler_index]) {
+ /* During this loop, ui handlers for nested menus can tag multiple handlers free. */
+ if (handler_base->flag & WM_HANDLER_DO_FREE) {
+ continue;
+ }
+ if (handler_base->type != WM_HANDLER_TYPE_KEYMAP) {
+ continue;
+ }
+
+ else if (handler_base->poll == NULL || handler_base->poll(region, win->eventstate)) {
+ wmEventHandler_Keymap *handler = (wmEventHandler_Keymap *)handler_base;
+ wmKeyMap *keymap = WM_event_get_keymap_from_handler(wm, handler);
+ if (keymap && WM_keymap_poll(C, keymap)) {
+ LISTBASE_FOREACH (wmKeyMapItem *, kmi, &keymap->items) {
+ if (kmi->flag & KMI_INACTIVE) {
+ continue;
+ }
+ if (STR_ELEM(kmi->idname, "WM_OT_call_menu", "WM_OT_call_menu_pie")) {
+ char menu_idname[MAX_NAME];
+ RNA_string_get(kmi->ptr, "name", menu_idname);
+ MenuType *mt = WM_menutype_find(menu_idname, false);
+
+ if (mt && BLI_gset_add(menu_tagged, mt)) {
+ /* Unlikely, but possible this will be included twice. */
+ BLI_linklist_prepend(menuid_stack_p, mt);
+
+ void **kmi_p;
+ if (!BLI_ghash_ensure_p(menu_to_kmi, mt, &kmi_p)) {
+ *kmi_p = kmi;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+/**
+ * Create #MenuSearch_Data by inspecting the current context, this uses two methods:
+ *
+ * - Look-up pre-defined editor-menus.
+ * - Look-up key-map items which call menus.
+ */
+static struct MenuSearch_Data *menu_items_from_ui_create(bContext *C,
+ wmWindow *win,
+ ScrArea *sa,
+ ARegion *region)
+{
+ MemArena *memarena = BLI_memarena_new(BLI_MEMARENA_STD_BUFSIZE, __func__);
+ /** Map (#MenuType to #MenuSearch_Parent) */
+ GHash *menu_parent_map = BLI_ghash_ptr_new(__func__);
+ GHash *menu_display_name_map = BLI_ghash_ptr_new(__func__);
+ const uiStyle *style = UI_style_get_dpi();
+
+ /* Convert into non-ui structure. */
+ struct MenuSearch_Data *data = MEM_callocN(sizeof(*data), __func__);
+
+ DynStr *dyn_str = BLI_dynstr_new_memarena();
+
+ /* Use a stack of menus to handle and discover new menus in passes. */
+ LinkNode *menu_stack = NULL;
+
+ /* Tag menu types not to add, either because they have already been added
+ * or they have been blacklisted.
+ * Set of #MenuType. */
+ GSet *menu_tagged = BLI_gset_ptr_new(__func__);
+ /** Map (#MenuType -> #wmKeyMapItem). */
+ GHash *menu_to_kmi = BLI_ghash_ptr_new(__func__);
+
+ /* Blacklist menus we don't want to show. */
+ {
+ const char *idname_array[] = {
+ /* While we could include this, it's just showing filenames to load. */
+ "TOPBAR_MT_file_open_recent",
+ };
+ for (int i = 0; i < ARRAY_SIZE(idname_array); i++) {
+ MenuType *mt = WM_menutype_find(idname_array[i], false);
+ if (mt != NULL) {
+ BLI_gset_add(menu_tagged, mt);
+ }
+ }
+ }
+
+ /* Populate menus from the editors,
+ * note that we could create a fake header, draw the header and extract the menus
+ * from the buttons, however this is quite involved and can be avoided as by convention
+ * each space-type has a single root-menu that headers use. */
+ {
+ const char *idname_array[] = {
+ "TOPBAR_MT_editor_menus",
+ /* Optional second menu for the space-type. */
+ NULL,
+ };
+ int idname_array_len = 1;
+
+#define SPACE_MENU_MAP(space_type, menu_id) \
+ case space_type: \
+ idname_array[idname_array_len++] = menu_id; \
+ break
+#define SPACE_MENU_NOP(space_type) \
+ case space_type: \
+ break
+
+ if (sa != NULL) {
+ switch (sa->spacetype) {
+ SPACE_MENU_MAP(SPACE_VIEW3D, "VIEW3D_MT_editor_menus");
+ SPACE_MENU_MAP(SPACE_GRAPH, "GRAPH_MT_editor_menus");
+ SPACE_MENU_MAP(SPACE_OUTLINER, "OUTLINER_MT_editor_menus");
+ SPACE_MENU_NOP(SPACE_PROPERTIES);
+ SPACE_MENU_MAP(SPACE_FILE, "FILE_MT_editor_menus");
+ SPACE_MENU_MAP(SPACE_IMAGE, "IMAGE_MT_editor_menus");
+ SPACE_MENU_MAP(SPACE_INFO, "INFO_MT_editor_menus");
+ SPACE_MENU_MAP(SPACE_SEQ, "SEQUENCER_MT_editor_menus");
+ SPACE_MENU_MAP(SPACE_TEXT, "TEXT_MT_editor_menus");
+ SPACE_MENU_MAP(SPACE_ACTION, "ACTION_MT_editor_menus");
+ SPACE_MENU_MAP(SPACE_NLA, "NLA_MT_editor_menus");
+ SPACE_MENU_MAP(SPACE_NODE, "NODE_MT_editor_menus");
+ SPACE_MENU_MAP(SPACE_CONSOLE, "CONSOLE_MT_editor_menus");
+ SPACE_MENU_MAP(SPACE_USERPREF, "USERPREF_MT_editor_menus");
+ SPACE_MENU_MAP(SPACE_CLIP,
+ (((const SpaceClip *)sa->spacedata.first)->mode == SC_MODE_TRACKING) ?
+ "CLIP_MT_tracking_editor_menus" :
+ "CLIP_MT_masking_editor_menus");
+ SPACE_MENU_NOP(SPACE_TOPBAR);
+ SPACE_MENU_NOP(SPACE_STATUSBAR);
+ default:
+ printf("Unknown space type '%d'\n", sa->spacetype);
+ }
+ }
+ for (int i = 0; i < idname_array_len; i++) {
+ MenuType *mt = WM_menutype_find(idname_array[i], false);
+ if (mt != NULL) {
+ BLI_linklist_prepend(&menu_stack, mt);
+ BLI_gset_add(menu_tagged, mt);
+ }
+ }
+ }
+#undef SPACE_MENU_MAP
+#undef SPACE_MENU_NOP
+
+ bool has_keymap_menu_items = false;
+
+ GHashIterator iter;
+
+ while (menu_stack != NULL) {
+ MenuType *mt = BLI_linklist_pop(&menu_stack);
+ if (!WM_menutype_poll(C, mt)) {
+ continue;
+ }
+
+ uiBlock *block = UI_block_begin(C, region, __func__, UI_EMBOSS);
+ uiLayout *layout = UI_block_layout(
+ block, UI_LAYOUT_VERTICAL, UI_LAYOUT_MENU, 0, 0, 200, 0, UI_MENU_PADDING, style);
+
+ UI_block_flag_enable(block, UI_BLOCK_SHOW_SHORTCUT_ALWAYS);
+
+ uiLayoutSetOperatorContext(layout, WM_OP_EXEC_REGION_WIN);
+ UI_menutype_draw(C, mt, layout);
+
+ UI_block_end(C, block);
+
+ LISTBASE_FOREACH (uiBut *, but, &block->buttons) {
+ MenuType *mt_from_but = NULL;
+ /* Support menu titles with dynamic from initial labels
+ * (used by edit-mesh context menu). */
+ if (but->type == UI_BTYPE_LABEL) {
+
+ /* Check if the label is the title. */
+ uiBut *but_test = but->prev;
+ while (but_test && but_test->type == UI_BTYPE_SEPR) {
+ but_test = but_test->prev;
+ }
+
+ if (but_test == NULL) {
+ BLI_ghash_insert(
+ menu_display_name_map, mt, (void *)strdup_memarena(memarena, but->drawstr));
+ }
+ }
+ else if (menu_items_from_ui_create_item_from_button(data, memarena, mt, NULL, but)) {
+ /* pass */
+ }
+ else if ((mt_from_but = UI_but_menutype_get(but))) {
+
+ if (BLI_gset_add(menu_tagged, mt_from_but)) {
+ BLI_linklist_prepend(&menu_stack, mt_from_but);
+ }
+
+ if (!BLI_ghash_haskey(menu_parent_map, mt_from_but)) {
+ struct MenuSearch_Parent *menu_parent = BLI_memarena_calloc(memarena,
+ sizeof(*menu_parent));
+ /* Use brackets for menu key shortcuts,
+ * converting "Text|Some-Shortcut" to "Text (Some-Shortcut)".
+ * This is needed so we don't right align sub-menu contents
+ * we only want to do that for the last menu item, not the path that leads to it.
+ */
+ const char *drawstr_sep = but->flag & UI_BUT_HAS_SEP_CHAR ?
+ strrchr(but->drawstr, UI_SEP_CHAR) :
+ NULL;
+ bool drawstr_is_empty = false;
+ if (drawstr_sep != NULL) {
+ BLI_assert(BLI_dynstr_get_len(dyn_str) == 0);
+ /* Detect empty string, fallback to menu name. */
+ const char *drawstr = but->drawstr;
+ int drawstr_len = drawstr_sep - but->drawstr;
+ if (UNLIKELY(drawstr_len == 0)) {
+ drawstr = CTX_IFACE_(mt_from_but->translation_context, mt_from_but->label);
+ drawstr_len = strlen(drawstr);
+ if (drawstr[0] == '\0') {
+ drawstr_is_empty = true;
+ }
+ }
+ BLI_dynstr_nappend(dyn_str, drawstr, drawstr_len);
+ BLI_dynstr_appendf(dyn_str, " (%s)", drawstr_sep + 1);
+ menu_parent->drawstr = strdup_memarena_from_dynstr(memarena, dyn_str);
+ BLI_dynstr_clear(dyn_str);
+ }
+ else {
+ const char *drawstr = but->drawstr;
+ if (UNLIKELY(drawstr[0] == '\0')) {
+ drawstr = CTX_IFACE_(mt_from_but->translation_context, mt_from_but->label);
+ if (drawstr[0] == '\0') {
+ drawstr_is_empty = true;
+ }
+ }
+ menu_parent->drawstr = strdup_memarena(memarena, drawstr);
+ }
+ menu_parent->parent_mt = mt;
+ BLI_ghash_insert(menu_parent_map, mt_from_but, menu_parent);
+
+ if (drawstr_is_empty) {
+ printf("Warning: '%s' menu has empty 'bl_label'.\n", mt_from_but->idname);
+ }
+ }
+ }
+ else if (but->menu_create_func != NULL) {
+ /* A non 'MenuType' menu button. */
+
+ /* Only expand one level deep, this is mainly for expanding operator menus. */
+ const char *drawstr_submenu = but->drawstr;
+
+ /* +1 to avoid overlap with the current 'block'. */
+ uiBlock *sub_block = UI_block_begin(C, region, __func__ + 1, UI_EMBOSS);
+ uiLayout *sub_layout = UI_block_layout(
+ sub_block, UI_LAYOUT_VERTICAL, UI_LAYOUT_MENU, 0, 0, 200, 0, UI_MENU_PADDING, style);
+
+ UI_block_flag_enable(sub_block, UI_BLOCK_SHOW_SHORTCUT_ALWAYS);
+
+ uiLayoutSetOperatorContext(sub_layout, WM_OP_INVOKE_REGION_WIN);
+
+ but->menu_create_func(C, sub_layout, but->poin);
+
+ UI_block_end(C, sub_block);
+
+ LISTBASE_FOREACH (uiBut *, sub_but, &sub_block->buttons) {
+ menu_items_from_ui_create_item_from_button(data, memarena, mt, drawstr_submenu, sub_but);
+ }
+
+ BLI_remlink(&region->uiblocks, sub_block);
+ UI_block_free(NULL, sub_block);
+ }
+ }
+ BLI_remlink(&region->uiblocks, block);
+ UI_block_free(NULL, block);
+
+ /* Add key-map items as a second pass,
+ * so all menus are accessed from the header & top-bar before key shortcuts are expanded. */
+ if ((menu_stack == NULL) && (has_keymap_menu_items == false)) {
+ has_keymap_menu_items = true;
+ menu_types_add_from_keymap_items(C, win, sa, region, &menu_stack, menu_to_kmi, menu_tagged);
+ }
+ }
+
+ LISTBASE_FOREACH (struct MenuSearch_Item *, item, &data->items) {
+ item->menu_parent = BLI_ghash_lookup(menu_parent_map, item->mt);
+ }
+
+ GHASH_ITER (iter, menu_parent_map) {
+ struct MenuSearch_Parent *menu_parent = BLI_ghashIterator_getValue(&iter);
+ menu_parent->parent = BLI_ghash_lookup(menu_parent_map, menu_parent->parent_mt);
+ }
+
+ /* NOTE: currently this builds the full path for each menu item,
+ * that could be moved into the parent menu. */
+
+ /* Unicode arrow. */
+#define MENU_SEP "\xe2\x86\x92"
+
+ /* Set names as full paths. */
+ LISTBASE_FOREACH (struct MenuSearch_Item *, item, &data->items) {
+ if (item->menu_parent != NULL) {
+ struct MenuSearch_Parent *menu_parent = item->menu_parent;
+ menu_parent->temp_child = NULL;
+ while (menu_parent && menu_parent->parent) {
+ menu_parent->parent->temp_child = menu_parent;
+ menu_parent = menu_parent->parent;
+ }
+ BLI_assert(BLI_dynstr_get_len(dyn_str) == 0);
+ while (menu_parent) {
+ BLI_dynstr_append(dyn_str, menu_parent->drawstr);
+ BLI_dynstr_append(dyn_str, " " MENU_SEP " ");
+ menu_parent = menu_parent->temp_child;
+ }
+ }
+ else {
+ BLI_assert(BLI_dynstr_get_len(dyn_str) == 0);
+ const char *drawstr = BLI_ghash_lookup(menu_display_name_map, item->mt);
+ if (drawstr == NULL) {
+ drawstr = CTX_IFACE_(item->mt->translation_context, item->mt->label);
+ }
+ BLI_dynstr_append(dyn_str, drawstr);
+
+ wmKeyMapItem *kmi = BLI_ghash_lookup(menu_to_kmi, item->mt);
+ if (kmi != NULL) {
+ char kmi_str[128];
+ WM_keymap_item_to_string(kmi, false, kmi_str, sizeof(kmi_str));
+ BLI_dynstr_appendf(dyn_str, " (%s)", kmi_str);
+ }
+
+ BLI_dynstr_append(dyn_str, " " MENU_SEP " ");
+ }
+
+ /* Optional nested menu. */
+ if (item->drawstr_submenu != NULL) {
+ BLI_dynstr_append(dyn_str, item->drawstr_submenu);
+ BLI_dynstr_append(dyn_str, " " MENU_SEP " ");
+ }
+
+ BLI_dynstr_append(dyn_str, item->drawstr);
+
+ item->drawwstr_full = strdup_memarena_from_dynstr(memarena, dyn_str);
+ BLI_dynstr_clear(dyn_str);
+ }
+ BLI_dynstr_free(dyn_str);
+#undef MENU_SEP
+
+ /* Finally sort menu items.
+ *
+ * Note: we might want to keep the in-menu order, for now sort all. */
+ BLI_listbase_sort(&data->items, menu_item_sort_by_drawstr_full);
+
+ BLI_ghash_free(menu_parent_map, NULL, NULL);
+ BLI_ghash_free(menu_display_name_map, NULL, NULL);
+
+ BLI_ghash_free(menu_to_kmi, NULL, NULL);
+
+ BLI_gset_free(menu_tagged, NULL);
+
+ data->memarena = memarena;
+
+ return data;
+}
+
+static void menu_items_from_ui_destroy(void *data_v)
+{
+ struct MenuSearch_Data *data = data_v;
+ LISTBASE_FOREACH (struct MenuSearch_Item *, item, &data->items) {
+ switch (item->type) {
+ case MENU_SEARCH_TYPE_OP: {
+ if (item->op.opptr != NULL) {
+ WM_operator_properties_free(item->op.opptr);
+ MEM_freeN(item->op.opptr);
+ }
+ }
+ case MENU_SEARCH_TYPE_RNA: {
+ break;
+ }
+ }
+ }
+
+ BLI_memarena_free(data->memarena);
+
+ MEM_freeN(data);
+}
+
+static void menu_call_fn(bContext *C, void *UNUSED(arg1), void *arg2)
+{
+ struct MenuSearch_Item *item = arg2;
+ if (item == NULL) {
+ return;
+ }
+ if (item->state & UI_BUT_DISABLED) {
+ return;
+ }
+
+ switch (item->type) {
+ case MENU_SEARCH_TYPE_OP: {
+ CTX_store_set(C, item->op.context);
+ WM_operator_name_call_ptr(C, item->op.type, item->op.opcontext, item->op.opptr);
+ CTX_store_set(C, NULL);
+ break;
+ }
+ case MENU_SEARCH_TYPE_RNA: {
+ PointerRNA *ptr = &item->rna.ptr;
+ PropertyRNA *prop = item->rna.prop;
+ int index = item->rna.index;
+ const int prop_type = RNA_property_type(prop);
+ bool changed = false;
+
+ if (prop_type == PROP_BOOLEAN) {
+ const bool is_array = RNA_property_array_check(prop);
+ if (is_array) {
+ const bool value = RNA_property_boolean_get_index(ptr, prop, index);
+ RNA_property_boolean_set_index(ptr, prop, index, !value);
+ }
+ else {
+ const bool value = RNA_property_boolean_get(ptr, prop);
+ RNA_property_boolean_set(ptr, prop, !value);
+ }
+ changed = true;
+ }
+ else if (prop_type == PROP_ENUM) {
+ RNA_property_enum_set(ptr, prop, item->rna.enum_value);
+ changed = true;
+ }
+
+ if (changed) {
+ RNA_property_update(C, ptr, prop);
+ }
+ break;
+ }
+ }
+}
+
+static void menu_search_cb(const bContext *UNUSED(C),
+ void *arg,
+ const char *str,
+ uiSearchItems *items)
+{
+ struct MenuSearch_Data *data = arg;
+ const size_t str_len = strlen(str);
+ const int words_max = (str_len / 2) + 1;
+ int(*words)[2] = BLI_array_alloca(words, words_max);
+
+ const int words_len = BLI_string_find_split_words(str, str_len, ' ', words, words_max);
+
+ for (struct MenuSearch_Item *item = data->items.first; item; item = item->next) {
+ int index;
+
+ /* match name against all search words */
+ for (index = 0; index < words_len; index++) {
+ if (!has_word_prefix(item->drawwstr_full, str + words[index][0], words[index][1])) {
+ break;
+ }
+ }
+
+ if (index == words_len) {
+ if (!UI_search_item_add(items, item->drawwstr_full, item, item->icon, item->state)) {
+ break;
+ }
+ }
+ }
+}
+
+void UI_but_func_menu_search(uiBut *but)
+{
+ bContext *C = but->block->evil_C;
+ wmWindow *win = CTX_wm_window(C);
+ ScrArea *sa = CTX_wm_area(C);
+ ARegion *region = CTX_wm_region(C);
+ struct MenuSearch_Data *data = menu_items_from_ui_create(C, win, sa, region);
+ UI_but_func_search_set(but,
+ ui_searchbox_create_menu,
+ menu_search_cb,
+ data,
+ menu_items_from_ui_destroy,
+ menu_call_fn,
+ NULL);
+}
+
+void uiTemplateMenuSearch(uiLayout *layout)
+{
+ uiBlock *block;
+ uiBut *but;
+ static char search[256] = "";
+
+ block = uiLayoutGetBlock(layout);
+ UI_block_layout_set_current(block, layout);
+
+ but = uiDefSearchBut(
+ block, search, 0, ICON_VIEWZOOM, sizeof(search), 0, 0, UI_UNIT_X * 6, UI_UNIT_Y, 0, 0, "");
+ UI_but_func_menu_search(but);
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
/** \name Operator Redo Properties Template
* \{ */
diff --git a/source/blender/editors/interface/interface_utils.c b/source/blender/editors/interface/interface_utils.c
index efa7ffb22d0..a69837e9b51 100644
--- a/source/blender/editors/interface/interface_utils.c
+++ b/source/blender/editors/interface/interface_utils.c
@@ -445,7 +445,7 @@ void ui_rna_collection_search_cb(const struct bContext *C,
/* add search items from temporary list */
for (cis = items_list->first; cis; cis = cis->next) {
- if (UI_search_item_add(items, cis->name, cis->data, cis->iconid) == false) {
+ if (!UI_search_item_add(items, cis->name, cis->data, cis->iconid, 0)) {
break;
}
}
diff --git a/source/blender/editors/space_node/node_select.c b/source/blender/editors/space_node/node_select.c
index 2c9e7a213c2..98fcf862290 100644
--- a/source/blender/editors/space_node/node_select.c
+++ b/source/blender/editors/space_node/node_select.c
@@ -1131,7 +1131,7 @@ static void node_find_cb(const struct bContext *C,
else {
BLI_strncpy(name, node->name, 256);
}
- if (false == UI_search_item_add(items, name, node, 0)) {
+ if (!UI_search_item_add(items, name, node, ICON_NONE, 0)) {
break;
}
}
@@ -1178,7 +1178,7 @@ static uiBlock *node_find_menu(bContext *C, ARegion *region, void *arg_op)
0,
0,
"");
- UI_but_func_search_set(but, NULL, node_find_cb, op->type, false, node_find_call_cb, NULL);
+ UI_but_func_search_set(but, NULL, node_find_cb, op->type, NULL, node_find_call_cb, NULL);
UI_but_flag_enable(but, UI_BUT_ACTIVATE_ON_INIT);
/* fake button, it holds space for search items */
diff --git a/source/blender/editors/space_outliner/outliner_tools.c b/source/blender/editors/space_outliner/outliner_tools.c
index 768ec17c1e7..12be08550af 100644
--- a/source/blender/editors/space_outliner/outliner_tools.c
+++ b/source/blender/editors/space_outliner/outliner_tools.c
@@ -528,7 +528,7 @@ static void merged_element_search_cb_recursive(
/* Don't allow duplicate named items */
if (UI_search_items_find_index(items, name) == -1) {
- if (!UI_search_item_add(items, name, te, iconid)) {
+ if (!UI_search_item_add(items, name, te, iconid, 0)) {
break;
}
}
@@ -589,7 +589,7 @@ static uiBlock *merged_element_search_menu(bContext *C, ARegion *region, void *d
but = uiDefSearchBut(
block, search, 0, ICON_VIEWZOOM, sizeof(search), 10, 10, menu_width, UI_UNIT_Y, 0, 0, "");
UI_but_func_search_set(
- but, NULL, merged_element_search_cb, data, false, merged_element_search_call_cb, NULL);
+ but, NULL, merged_element_search_cb, data, NULL, merged_element_search_call_cb, NULL);
UI_but_flag_enable(but, UI_BUT_ACTIVATE_ON_INIT);
/* Fake button to hold space for search items */
diff --git a/source/blender/makesdna/DNA_userdef_types.h b/source/blender/makesdna/DNA_userdef_types.h
index 30b4098c2bb..54470dc59fc 100644
--- a/source/blender/makesdna/DNA_userdef_types.h
+++ b/source/blender/makesdna/DNA_userdef_types.h
@@ -619,7 +619,9 @@ typedef struct UserDef_FileSpaceData {
typedef struct UserDef_Experimental {
char use_undo_speedup;
- char _pad0[7]; /* makesdna does not allow empty structs. */
+ char use_menu_search;
+ /** `makesdna` does not allow empty structs. */
+ char _pad0[6];
} UserDef_Experimental;
#define USER_EXPERIMENTAL_TEST(userdef, member) \
diff --git a/source/blender/makesrna/intern/rna_ui_api.c b/source/blender/makesrna/intern/rna_ui_api.c
index 5eadbaf55e4..a169e81237d 100644
--- a/source/blender/makesrna/intern/rna_ui_api.c
+++ b/source/blender/makesrna/intern/rna_ui_api.c
@@ -1474,6 +1474,7 @@ void RNA_api_ui_layout(StructRNA *srna)
RNA_def_function_flag(func, FUNC_USE_CONTEXT);
RNA_def_function(srna, "template_operator_search", "uiTemplateOperatorSearch");
+ RNA_def_function(srna, "template_menu_search", "uiTemplateMenuSearch");
func = RNA_def_function(srna, "template_header_3D_mode", "uiTemplateHeader3D_mode");
RNA_def_function_flag(func, FUNC_USE_CONTEXT);
diff --git a/source/blender/makesrna/intern/rna_userdef.c b/source/blender/makesrna/intern/rna_userdef.c
index f68c3c3bceb..f4bb2f8ef0f 100644
--- a/source/blender/makesrna/intern/rna_userdef.c
+++ b/source/blender/makesrna/intern/rna_userdef.c
@@ -6051,6 +6051,10 @@ static void rna_def_userdef_experimental(BlenderRNA *brna)
prop,
"Undo Speedup",
"Use new undo speedup (WARNING: can lead to crashes and serious .blend file corruption)");
+
+ prop = RNA_def_property(srna, "use_menu_search", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_sdna(prop, NULL, "use_menu_search", 1);
+ RNA_def_property_ui_text(prop, "Menu Search", "Search actions by menus instead of operators");
}
static void rna_def_userdef_addon_collection(BlenderRNA *brna, PropertyRNA *cprop)
diff --git a/source/blender/windowmanager/intern/wm_operators.c b/source/blender/windowmanager/intern/wm_operators.c
index 961854ac23e..2ae71eb2490 100644
--- a/source/blender/windowmanager/intern/wm_operators.c
+++ b/source/blender/windowmanager/intern/wm_operators.c
@@ -1687,10 +1687,15 @@ static void WM_OT_operator_defaults(wmOperatorType *ot)
/** \} */
/* -------------------------------------------------------------------- */
-/** \name Operator Search Menu
+/** \name Operator/Menu Search Operator
* \{ */
struct SearchPopupInit_Data {
+ enum {
+ SEARCH_TYPE_OPERATOR = 0,
+ SEARCH_TYPE_MENU = 1,
+ } search_type;
+
int size[2];
};
@@ -1717,7 +1722,17 @@ static uiBlock *wm_block_search_menu(bContext *C, ARegion *region, void *userdat
0,
0,
"");
- UI_but_func_operator_search(but);
+
+ if (init_data->search_type == SEARCH_TYPE_OPERATOR) {
+ UI_but_func_operator_search(but);
+ }
+ else if (init_data->search_type == SEARCH_TYPE_MENU) {
+ UI_but_func_menu_search(but);
+ }
+ else {
+ BLI_assert(0);
+ }
+
UI_but_flag_enable(but, UI_BUT_ACTIVATE_ON_INIT);
/* fake button, it holds space for search items */
@@ -1747,7 +1762,7 @@ static int wm_search_menu_exec(bContext *UNUSED(C), wmOperator *UNUSED(op))
return OPERATOR_FINISHED;
}
-static int wm_search_menu_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *event)
+static int wm_search_menu_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
/* Exception for launching via spacebar */
if (event->type == EVT_SPACEKEY) {
@@ -1775,9 +1790,20 @@ static int wm_search_menu_invoke(bContext *C, wmOperator *UNUSED(op), const wmEv
}
}
+ PropertyRNA *prop = op->type->prop;
+ int search_type;
+ if (RNA_property_is_set(op->ptr, prop)) {
+ search_type = RNA_property_enum_get(op->ptr, prop);
+ }
+ else {
+ search_type = U.experimental.use_menu_search ? SEARCH_TYPE_MENU : SEARCH_TYPE_OPERATOR;
+ }
+
static struct SearchPopupInit_Data data;
- data.size[0] = UI_searchbox_size_x() * 2;
- data.size[1] = UI_searchbox_size_y();
+ data = (struct SearchPopupInit_Data){
+ .search_type = search_type,
+ .size = {UI_searchbox_size_x() * 2, UI_searchbox_size_y()},
+ };
UI_popup_block_invoke(C, wm_block_search_menu, &data, NULL);
@@ -1793,6 +1819,15 @@ static void WM_OT_search_menu(wmOperatorType *ot)
ot->invoke = wm_search_menu_invoke;
ot->exec = wm_search_menu_exec;
ot->poll = WM_operator_winactive;
+
+ static const EnumPropertyItem search_type_items[] = {
+ {SEARCH_TYPE_OPERATOR, "OPERATOR", 0, "Operator", "Search all operators"},
+ {SEARCH_TYPE_MENU, "MENU", 0, "Menu", "Search active menu items"},
+ {0, NULL, 0, NULL, NULL},
+ };
+
+ /* properties */
+ ot->prop = RNA_def_enum(ot->srna, "type", search_type_items, SEARCH_TYPE_OPERATOR, "Type", "");
}
static int wm_call_menu_exec(bContext *C, wmOperator *op)