UI: add menu search functionality to operator search menu
authorCampbell Barton <ideasman42@gmail.com>
Tue, 24 Mar 2020 00:34:18 +0000 (11:34 +1100)
committerCampbell Barton <ideasman42@gmail.com>
Tue, 24 Mar 2020 02:41:18 +0000 (13:41 +1100)
This has some advantages over operator search:

- Some operators need options set to be usefully accessed.
- Shows key bindings to access menus
  (for actions that don't have key bindings themselves).
- Non operator actions such as check-boxes are also shown.
- Menu items can control execution context, using invoke or execute
  where appropriate so we can control how the operator runs.

Part of the design task T74157.

This can be tested using the 'Experimental' preferences section
or selected in the key-map editor.

16 files changed:
release/scripts/startup/bl_ui/space_topbar.py
release/scripts/startup/bl_ui/space_userpref.py
source/blender/editors/include/UI_interface.h
source/blender/editors/interface/interface.c
source/blender/editors/interface/interface_handlers.c
source/blender/editors/interface/interface_intern.h
source/blender/editors/interface/interface_layout.c
source/blender/editors/interface/interface_region_search.c
source/blender/editors/interface/interface_templates.c
source/blender/editors/interface/interface_utils.c
source/blender/editors/space_node/node_select.c
source/blender/editors/space_outliner/outliner_tools.c
source/blender/makesdna/DNA_userdef_types.h
source/blender/makesrna/intern/rna_ui_api.c
source/blender/makesrna/intern/rna_userdef.c
source/blender/windowmanager/intern/wm_operators.c

index 1f52323f5404511f01f57b9172c98fd72de1278c..7ffb61fef5addf1cd2c46fa91dea43437f853cd1 100644 (file)
@@ -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")
index e6ee779d89b64d4f132a0491e12669d6e2f82bb6..165761254d028b929bc5c7c710e79730a0421a7e 100644 (file)
@@ -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,
index 3e416cd80576e033479cb44b3b0110b2c83b5567..bb2edef8adcb177162a7313d9cb0e5350ce7f0f3 100644 (file)
@@ -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,
index 2a4ca48a0054d88629e61f18b1ceb4de871c842f..41b7683dff78509468f6a5c9387686afe29dd98c 100644 (file)
@@ -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);
 
index 238223fddfd83aefe9803afa91cb2e6602372888..2dfa29f5646b767c0768e994066e76c52f65c016 100644 (file)
@@ -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]) {
index e2b4e8f29586a104d38e723e1e038d5a093eb23b..4a9c8a1ff545dce64ac7f7514fc4c257ee9162e3 100644 (file)
@@ -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);
index 79b3c1a0a83941c126093546afb6d95ea1672134..fed35ccff59e6ffc9dcb10c95c798677048a4754 100644 (file)
@@ -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);
   }
index dab42e47e0bfc63a76ef71eb0b35aaa585f1f825..48779fd86dc53a05297391c8d7a8617aef850c0a 100644 (file)
@@ -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)
index 42e0aab8418e63df52bd2dd3b9361069ef4c05f3..d7377a0e56e6290f27cb71f0a824eed237baa88b 100644 (file)
 #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"
 
 
 #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;
         }
       }
@@ -6673,6 +6680,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
  * \{ */
index efa7ffb22d0004e22dcc4e6109445481527587ee..a69837e9b5148db822348d70e8afe4a2bdd2ffcd 100644 (file)
@@ -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;
     }
   }
index 2c9e7a213c2e7f65d29801633462097ac778ade5..98fcf8622900489e35cbdccabc5d5b6757a71e84 100644 (file)
@@ -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 */
index 768ec17c1e7a771a8933038096c98bc0729216cd..12be08550af40a50777c56410de89d2b07646f55 100644 (file)
@@ -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 */
index 30b4098c2bbfabfb15a095b9f6619423cb08d194..54470dc59fc4d56e45aa053771ad6c2b2f932f45 100644 (file)
@@ -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) \
index 5eadbaf55e48c506ca82eace19b40293913508a4..a169e81237d2a07b95f9476c33efd452117c9b54 100644 (file)
@@ -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);
index f68c3c3bceb3610a5ec42b7912056c534154e965..f4bb2f8ef0f08ca213a3c8cf48692aed74d6f3c7 100644 (file)
@@ -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)
index 961854ac23e0860d77b38de64942c6c845068466..2ae71eb2490c4378d8c35f6fbc7f484100fe042e 100644 (file)
@@ -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)