diff --git a/scripts/startup/bl_ui/properties_data_modifier.py b/scripts/startup/bl_ui/properties_data_modifier.py index 4486ced6d6c..218b927612e 100644 --- a/scripts/startup/bl_ui/properties_data_modifier.py +++ b/scripts/startup/bl_ui/properties_data_modifier.py @@ -29,6 +29,7 @@ class DATA_PT_modifiers(ModifierButtonsPanel, Panel): class OBJECT_MT_modifier_add(Menu): bl_label = "Add Modifier" + bl_options = {'SEARCH_ON_KEY_PRESS'} def draw(self, context): layout = self.layout diff --git a/scripts/startup/bl_ui/space_node.py b/scripts/startup/bl_ui/space_node.py index 8d4155b3595..e13f0255787 100644 --- a/scripts/startup/bl_ui/space_node.py +++ b/scripts/startup/bl_ui/space_node.py @@ -225,6 +225,7 @@ class NODE_MT_add(bpy.types.Menu): bl_space_type = 'NODE_EDITOR' bl_label = "Add" bl_translation_context = i18n_contexts.operator_default + bl_options = {'SEARCH_ON_KEY_PRESS'} def draw(self, context): import nodeitems_utils @@ -234,27 +235,16 @@ class NODE_MT_add(bpy.types.Menu): snode = context.space_data if snode.tree_type == 'GeometryNodeTree': - props = layout.operator("node.add_search", text="Search...", icon='VIEWZOOM') - layout.separator() layout.menu_contents("NODE_MT_geometry_node_add_all") elif snode.tree_type == 'CompositorNodeTree': - props = layout.operator("node.add_search", text="Search...", icon='VIEWZOOM') - layout.separator() layout.menu_contents("NODE_MT_compositor_node_add_all") elif snode.tree_type == 'ShaderNodeTree': - props = layout.operator("node.add_search", text="Search...", icon='VIEWZOOM') - layout.separator() layout.menu_contents("NODE_MT_shader_node_add_all") elif snode.tree_type == 'TextureNodeTree': props = layout.operator("node.add_search", text="Search...", icon='VIEWZOOM') layout.separator() layout.menu_contents("NODE_MT_texture_node_add_all") elif nodeitems_utils.has_node_categories(context): - props = layout.operator("node.add_search", text="Search...", icon='VIEWZOOM') - props.use_transform = True - - layout.separator() - # Actual node sub-menus are defined by draw functions from node categories. nodeitems_utils.draw_node_categories_menu(self, context) diff --git a/scripts/startup/bl_ui/space_view3d.py b/scripts/startup/bl_ui/space_view3d.py index cd8bb74f2ee..12f0c616b94 100644 --- a/scripts/startup/bl_ui/space_view3d.py +++ b/scripts/startup/bl_ui/space_view3d.py @@ -2448,6 +2448,7 @@ class VIEW3D_MT_grease_pencil_add(Menu): class VIEW3D_MT_add(Menu): bl_label = "Add" bl_translation_context = i18n_contexts.operator_default + bl_options = {'SEARCH_ON_KEY_PRESS'} def draw(self, context): layout = self.layout diff --git a/source/blender/blenkernel/BKE_screen.h b/source/blender/blenkernel/BKE_screen.h index dda91f24c9c..9d60a9fe199 100644 --- a/source/blender/blenkernel/BKE_screen.h +++ b/source/blender/blenkernel/BKE_screen.h @@ -405,6 +405,10 @@ enum class MenuTypeFlag { * dependent, menu search has to scan it in different contexts. */ ContextDependent = (1 << 0), + /** + * Automatically start searching in the menu when pressing a key. + */ + SearchOnKeyPress = (1 << 1), }; ENUM_OPERATORS(MenuTypeFlag, MenuTypeFlag::ContextDependent) diff --git a/source/blender/editors/include/UI_interface_c.hh b/source/blender/editors/include/UI_interface_c.hh index 3ba1abecc94..f61d1734fe2 100644 --- a/source/blender/editors/include/UI_interface_c.hh +++ b/source/blender/editors/include/UI_interface_c.hh @@ -175,6 +175,8 @@ enum { UI_BLOCK_SEARCH_ONLY = 1 << 25, /** Hack for quick setup (splash screen) to draw text centered. */ UI_BLOCK_QUICK_SETUP = 1 << 26, + /** Don't accelerator keys for the items in the block. */ + UI_BLOCK_NO_ACCELERATOR_KEYS = 1 << 27, }; /** #uiPopupBlockHandle.menuretval */ @@ -247,6 +249,15 @@ enum { UI_BUT_OVERRIDDEN = 1u << 31u, }; +enum { + /** + * This is used when `UI_BUT_ACTIVATE_ON_INIT` is used, which is used to activate e.g. a search + * box as soon as a popup opens. Usually, the text in the search box is selected by default. + * However, sometimes this behavior is not desired, so it can be disabled with this flag. + */ + UI_BUT2_ACTIVATE_ON_INIT_NO_SELECT = 1 << 0, +}; + /** #uiBut.dragflag */ enum { /** By default only the left part of a button triggers dragging. A questionable design to make @@ -887,6 +898,7 @@ bool UI_but_active_drop_color(bContext *C); void UI_but_flag_enable(uiBut *but, int flag); void UI_but_flag_disable(uiBut *but, int flag); bool UI_but_flag_is_set(uiBut *but, int flag); +void UI_but_flag2_enable(uiBut *but, int flag); void UI_but_drawflag_enable(uiBut *but, int flag); void UI_but_drawflag_disable(uiBut *but, int flag); @@ -2404,7 +2416,7 @@ void uiTemplateRunningJobs(uiLayout *layout, bContext *C); void UI_but_func_operator_search(uiBut *but); void uiTemplateOperatorSearch(uiLayout *layout); -void UI_but_func_menu_search(uiBut *but); +void UI_but_func_menu_search(uiBut *but, const char *single_menu_idname = nullptr); void uiTemplateMenuSearch(uiLayout *layout); /** diff --git a/source/blender/editors/interface/interface.cc b/source/blender/editors/interface/interface.cc index 63451deeaed..8958e086550 100644 --- a/source/blender/editors/interface/interface.cc +++ b/source/blender/editors/interface/interface.cc @@ -2001,7 +2001,9 @@ void UI_block_end_ex(const bContext *C, uiBlock *block, const int xy[2], int r_x UI_block_layout_resolve(block, nullptr, nullptr); } ui_block_align_calc(block, CTX_wm_region(C)); - if ((block->flag & UI_BLOCK_LOOP) && (block->flag & UI_BLOCK_NUMSELECT)) { + if ((block->flag & UI_BLOCK_LOOP) && (block->flag & UI_BLOCK_NUMSELECT) && + (block->flag & UI_BLOCK_NO_ACCELERATOR_KEYS) == 0) + { ui_menu_block_set_keyaccels(block); /* could use a different flag to check */ } @@ -5847,6 +5849,11 @@ void UI_but_flag_enable(uiBut *but, int flag) but->flag |= flag; } +void UI_but_flag2_enable(uiBut *but, int flag) +{ + but->flag2 |= flag; +} + void UI_but_flag_disable(uiBut *but, int flag) { but->flag &= ~flag; diff --git a/source/blender/editors/interface/interface_handlers.cc b/source/blender/editors/interface/interface_handlers.cc index e07368d335f..bafa2febca7 100644 --- a/source/blender/editors/interface/interface_handlers.cc +++ b/source/blender/editors/interface/interface_handlers.cc @@ -3480,7 +3480,12 @@ static void ui_textedit_begin(bContext *C, uiBut *but, uiHandleButtonData *data) /* set cursor pos to the end of the text */ but->editstr = data->str; but->pos = len; - but->selsta = 0; + if (bool(but->flag2 & UI_BUT2_ACTIVATE_ON_INIT_NO_SELECT)) { + but->selsta = len; + } + else { + but->selsta = 0; + } but->selend = len; /* Initialize undo history tracking. */ @@ -4316,6 +4321,9 @@ static void ui_block_open_begin(bContext *C, uiBut *but, uiHandleButtonData *dat } else if (menufunc) { data->menu = ui_popup_menu_create(C, data->region, but, menufunc, arg); + if (MenuType *mt = UI_but_menutype_get(but)) { + STRNCPY(data->menu->menu_idname, mt->idname); + } if (but->block->handle) { data->menu->popup = but->block->handle->popup; } @@ -10269,6 +10277,41 @@ float ui_block_calc_pie_segment(uiBlock *block, const float event_xy[2]) return len; } +static int ui_handle_menu_letter_press( + bContext *C, ARegion *region, uiPopupBlockHandle *menu, const wmEvent *event, uiBlock *block) +{ + /* Start menu search on key press if enabled. */ + if (menu->menu_idname[0]) { + MenuType *mt = WM_menutype_find(menu->menu_idname, false); + if (bool(mt->flag & MenuTypeFlag::SearchOnKeyPress)) { + uiAfterFunc *after = ui_afterfunc_new(); + wmOperatorType *ot = WM_operatortype_find("WM_OT_search_single_menu", false); + after->optype = ot; + after->opcontext = WM_OP_INVOKE_DEFAULT; + after->opptr = MEM_cnew(__func__); + WM_operator_properties_create_ptr(after->opptr, ot); + RNA_string_set(after->opptr, "menu_idname", menu->menu_idname); + RNA_string_set(after->opptr, "initial_query", event->utf8_buf); + menu->menuretval = UI_RETURN_OK; + return WM_UI_HANDLER_BREAK; + } + } + + /* Handle accelerator keys that allow "pressing" a menu entry by pressing a single key. */ + LISTBASE_FOREACH (uiBut *, but, &block->buttons) { + if (!(but->flag & UI_BUT_DISABLED) && but->menu_key == event->type) { + if (but->type == UI_BTYPE_BUT) { + UI_but_execute(C, region, but); + } + else { + ui_handle_button_activate_by_type(C, region, but); + } + return WM_UI_HANDLER_BREAK; + } + } + return WM_UI_HANDLER_CONTINUE; +} + static int ui_handle_menu_event(bContext *C, const wmEvent *event, uiPopupBlockHandle *menu, @@ -10709,20 +10752,7 @@ static int ui_handle_menu_event(bContext *C, menu, but, level, is_parent_menu, retval)) { break; } - - for (but = static_cast(block->buttons.first); but; but = but->next) { - if (!(but->flag & UI_BUT_DISABLED) && but->menu_key == event->type) { - if (but->type == UI_BTYPE_BUT) { - UI_but_execute(C, region, but); - } - else { - ui_handle_button_activate_by_type(C, region, but); - } - break; - } - } - - retval = WM_UI_HANDLER_BREAK; + retval = ui_handle_menu_letter_press(C, region, menu, event, block); } break; } diff --git a/source/blender/editors/interface/interface_intern.hh b/source/blender/editors/interface/interface_intern.hh index 5501782e5b9..f52f2d7e237 100644 --- a/source/blender/editors/interface/interface_intern.hh +++ b/source/blender/editors/interface/interface_intern.hh @@ -151,6 +151,7 @@ struct uiBut { /** Pointer back to the layout item holding this button. */ uiLayout *layout = nullptr; int flag = 0; + int flag2 = 0; int drawflag = 0; eButType type = eButType(0); eButPointerType pointype = UI_BUT_POIN_NONE; @@ -848,6 +849,8 @@ struct uiPopupBlockHandle { bool is_grab; int grab_xy_prev[2]; /* #endif */ + + char menu_idname[64]; }; /* -------------------------------------------------------------------- */ diff --git a/source/blender/editors/interface/interface_layout.cc b/source/blender/editors/interface/interface_layout.cc index 68b7b89814f..c3ca040fb0d 100644 --- a/source/blender/editors/interface/interface_layout.cc +++ b/source/blender/editors/interface/interface_layout.cc @@ -5918,9 +5918,12 @@ void UI_menutype_draw(bContext *C, MenuType *mt, uiLayout *layout) printf("%s: opening menu \"%s\"\n", __func__, mt->idname); } + uiBlock *block = uiLayoutGetBlock(layout); + if (bool(mt->flag & MenuTypeFlag::SearchOnKeyPress)) { + UI_block_flag_enable(block, UI_BLOCK_NO_ACCELERATOR_KEYS); + } if (mt->listener) { /* Forward the menu type listener to the block we're drawing in. */ - uiBlock *block = uiLayoutGetBlock(layout); uiBlockDynamicListener *listener = static_cast( MEM_mallocN(sizeof(*listener), __func__)); listener->listener_func = mt->listener; diff --git a/source/blender/editors/interface/interface_region_menu_popup.cc b/source/blender/editors/interface/interface_region_menu_popup.cc index 1d29c2e6cda..650801e8855 100644 --- a/source/blender/editors/interface/interface_region_menu_popup.cc +++ b/source/blender/editors/interface/interface_region_menu_popup.cc @@ -397,6 +397,12 @@ static uiPopupBlockHandle *ui_popup_menu_create( if (but) { pup->slideout = ui_block_is_menu(but->block); pup->but = but; + + if (MenuType *mt = UI_but_menutype_get(but)) { + if (bool(mt->flag & MenuTypeFlag::SearchOnKeyPress)) { + ED_workspace_status_text(C, TIP_("Type to search...")); + } + } } if (!but) { @@ -608,7 +614,12 @@ static void ui_popup_menu_create_from_menutype(bContext *C, ui_item_menutype_func(C, layout, mt); }); + STRNCPY(handle->menu_idname, mt->idname); handle->can_refresh = true; + + if (bool(mt->flag & MenuTypeFlag::SearchOnKeyPress)) { + ED_workspace_status_text(C, TIP_("Type to search...")); + } } int UI_popup_menu_invoke(bContext *C, const char *idname, ReportList *reports) diff --git a/source/blender/editors/interface/interface_region_popup.cc b/source/blender/editors/interface/interface_region_popup.cc index e9ef59b36a4..7a9ac6f01f3 100644 --- a/source/blender/editors/interface/interface_region_popup.cc +++ b/source/blender/editors/interface/interface_region_popup.cc @@ -575,6 +575,11 @@ uiBlock *ui_popup_block_refresh(bContext *C, block = handle_create_func(C, handle, arg); } + /* Don't create accelerator keys if the parent menu does not have them. */ + if (but && but->block->flag & UI_BLOCK_NO_ACCELERATOR_KEYS) { + block->flag |= UI_BLOCK_NO_ACCELERATOR_KEYS; + } + /* callbacks _must_ leave this for us, otherwise we can't call UI_block_update_from_old */ BLI_assert(!block->endblock); @@ -815,6 +820,10 @@ uiPopupBlockHandle *ui_popup_block_create(bContext *C, void ui_popup_block_free(bContext *C, uiPopupBlockHandle *handle) { + /* This disables the status bar text that is set when opening a menu that supports search (see + * #MenuTypeFlag::SearchOnKeyPress). */ + ED_workspace_status_text(C, nullptr); + /* If this popup is created from a popover which does NOT have keep-open flag set, * then close the popover too. We could extend this to other popup types too. */ ARegion *region = handle->popup_create_vars.butregion; diff --git a/source/blender/editors/interface/interface_template_search_menu.cc b/source/blender/editors/interface/interface_template_search_menu.cc index d070c2f5a7a..3743b6914ac 100644 --- a/source/blender/editors/interface/interface_template_search_menu.cc +++ b/source/blender/editors/interface/interface_template_search_menu.cc @@ -428,8 +428,12 @@ static void menu_items_from_all_operators(bContext *C, MenuSearch_Data *data) * - Look up predefined editor-menus. * - Look up key-map items which call menus. */ -static MenuSearch_Data *menu_items_from_ui_create( - bContext *C, wmWindow *win, ScrArea *area_init, ARegion *region_init, bool include_all_areas) +static MenuSearch_Data *menu_items_from_ui_create(bContext *C, + wmWindow *win, + ScrArea *area_init, + ARegion *region_init, + bool include_all_areas, + const char *single_menu_idname) { MemArena *memarena = BLI_memarena_new(BLI_MEMARENA_STD_BUFSIZE, __func__); blender::Map menu_display_name_map; @@ -591,11 +595,18 @@ static MenuSearch_Data *menu_items_from_ui_create( region = region_init; } - /* 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. */ - { + if (single_menu_idname) { + if (MenuType *mt = WM_menutype_find(single_menu_idname, false)) { + if (menu_tagged.add(mt)) { + menu_stack.push({mt}); + } + } + } + else { + /* 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[2] = {nullptr}; int idname_array_len = 0; @@ -798,12 +809,14 @@ static MenuSearch_Data *menu_items_from_ui_create( } UI_block_free(nullptr, 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.is_empty() && (has_keymap_menu_items == false)) { - has_keymap_menu_items = true; - menu_types_add_from_keymap_items( - C, win, area, region, menu_stack, menu_to_kmi, menu_tagged); + if (single_menu_idname == nullptr) { + /* 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.is_empty() && (has_keymap_menu_items == false)) { + has_keymap_menu_items = true; + menu_types_add_from_keymap_items( + C, win, area, region, menu_stack, menu_to_kmi, menu_tagged); + } } } } @@ -887,7 +900,7 @@ static MenuSearch_Data *menu_items_from_ui_create( * - Many operators need options to be set to give useful results, see: #74157. * - User who really prefer to list all operators can use #WM_OT_search_operator. */ - if (U.flag & USER_DEVELOPER_UI) { + if ((U.flag & USER_DEVELOPER_UI) && single_menu_idname == nullptr) { menu_items_from_all_operators(C, data); } @@ -1112,15 +1125,17 @@ static ARegion *ui_search_menu_create_tooltip( /** \name Menu Search Template Public API * \{ */ -void UI_but_func_menu_search(uiBut *but) +void UI_but_func_menu_search(uiBut *but, const char *single_menu_idname) { bContext *C = (bContext *)but->block->evil_C; wmWindow *win = CTX_wm_window(C); ScrArea *area = CTX_wm_area(C); ARegion *region = CTX_wm_region(C); /* When run from top-bar scan all areas in the current window. */ - const bool include_all_areas = (area && (area->spacetype == SPACE_TOPBAR)); - MenuSearch_Data *data = menu_items_from_ui_create(C, win, area, region, include_all_areas); + const bool include_all_areas = (area && (area->spacetype == SPACE_TOPBAR)) && + !single_menu_idname; + MenuSearch_Data *data = menu_items_from_ui_create( + C, win, area, region, include_all_areas, single_menu_idname); UI_but_func_search_set(but, /* Generic callback. */ ui_searchbox_create_menu, diff --git a/source/blender/makesrna/intern/rna_ui.cc b/source/blender/makesrna/intern/rna_ui.cc index ddae5ae6bf7..e53038af73d 100644 --- a/source/blender/makesrna/intern/rna_ui.cc +++ b/source/blender/makesrna/intern/rna_ui.cc @@ -1995,6 +1995,15 @@ static void rna_def_menu(BlenderRNA *brna) PropertyRNA *parm; FunctionRNA *func; + static const EnumPropertyItem menu_flag_items[] = { + {int(MenuTypeFlag::SearchOnKeyPress), + "SEARCH_ON_KEY_PRESS", + 0, + "Search on Key Press", + "Open a menu search when a key pressed while the menu is open"}, + {0, nullptr, 0, nullptr, nullptr}, + }; + srna = RNA_def_struct(brna, "Menu", nullptr); RNA_def_struct_ui_text(srna, "Menu", "Editor menu containing buttons"); RNA_def_struct_sdna(srna, "Menu"); @@ -2059,6 +2068,12 @@ static void rna_def_menu(BlenderRNA *brna) RNA_def_property_string_sdna(prop, nullptr, "type->owner_id"); RNA_def_property_flag(prop, PROP_REGISTER_OPTIONAL); + prop = RNA_def_property(srna, "bl_options", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, nullptr, "type->flag"); + RNA_def_property_enum_items(prop, menu_flag_items); + RNA_def_property_flag(prop, PROP_REGISTER_OPTIONAL | PROP_ENUM_FLAG); + RNA_def_property_ui_text(prop, "Options", "Options for this menu type"); + RNA_define_verify_sdna(true); } diff --git a/source/blender/windowmanager/intern/wm_operators.cc b/source/blender/windowmanager/intern/wm_operators.cc index d7998d98954..447d540baae 100644 --- a/source/blender/windowmanager/intern/wm_operators.cc +++ b/source/blender/windowmanager/intern/wm_operators.cc @@ -1741,27 +1741,30 @@ static void WM_OT_operator_defaults(wmOperatorType *ot) enum SearchType { SEARCH_TYPE_OPERATOR = 0, SEARCH_TYPE_MENU = 1, + SEARCH_TYPE_SINGLE_MENU = 2, }; struct SearchPopupInit_Data { SearchType search_type; int size[2]; + std::string single_menu_idname; }; +static char g_search_text[256] = ""; + static uiBlock *wm_block_search_menu(bContext *C, ARegion *region, void *userdata) { const SearchPopupInit_Data *init_data = static_cast(userdata); - static char search[256] = ""; uiBlock *block = UI_block_begin(C, region, "_popup", UI_EMBOSS); UI_block_flag_enable(block, UI_BLOCK_LOOP | UI_BLOCK_MOVEMOUSE_QUIT | UI_BLOCK_SEARCH_MENU); UI_block_theme_style_set(block, UI_BLOCK_THEME_STYLE_POPUP); uiBut *but = uiDefSearchBut(block, - search, + g_search_text, 0, ICON_VIEWZOOM, - sizeof(search), + sizeof(g_search_text), 10, 10, init_data->size[0], @@ -1776,6 +1779,10 @@ static uiBlock *wm_block_search_menu(bContext *C, ARegion *region, void *userdat else if (init_data->search_type == SEARCH_TYPE_MENU) { UI_but_func_menu_search(but); } + else if (init_data->search_type == SEARCH_TYPE_SINGLE_MENU) { + UI_but_func_menu_search(but, init_data->single_menu_idname.c_str()); + UI_but_flag2_enable(but, UI_BUT2_ACTIVATE_ON_INIT_NO_SELECT); + } else { BLI_assert_unreachable(); } @@ -1837,16 +1844,33 @@ static int wm_search_menu_invoke(bContext *C, wmOperator *op, const wmEvent *eve } } - int search_type; + SearchType search_type; if (STREQ(op->type->idname, "WM_OT_search_menu")) { search_type = SEARCH_TYPE_MENU; } + else if (STREQ(op->type->idname, "WM_OT_search_single_menu")) { + search_type = SEARCH_TYPE_SINGLE_MENU; + } else { search_type = SEARCH_TYPE_OPERATOR; } static SearchPopupInit_Data data{}; - data.search_type = SearchType(search_type); + + if (search_type == SEARCH_TYPE_SINGLE_MENU) { + { + char *buffer = RNA_string_get_alloc(op->ptr, "menu_idname", nullptr, 0, nullptr); + data.single_menu_idname = buffer; + MEM_SAFE_FREE(buffer); + } + { + char *buffer = RNA_string_get_alloc(op->ptr, "initial_query", nullptr, 0, nullptr); + STRNCPY(g_search_text, buffer); + MEM_SAFE_FREE(buffer); + } + } + + data.search_type = search_type; data.size[0] = UI_searchbox_size_x() * 2; data.size[1] = UI_searchbox_size_y(); @@ -1877,6 +1901,25 @@ static void WM_OT_search_operator(wmOperatorType *ot) ot->poll = WM_operator_winactive; } +static void WM_OT_search_single_menu(wmOperatorType *ot) +{ + ot->name = "Search Single Menu"; + ot->idname = "WM_OT_search_single_menu"; + ot->description = "Pop-up a search for a menu in current context"; + + ot->invoke = wm_search_menu_invoke; + ot->exec = wm_search_menu_exec; + ot->poll = WM_operator_winactive; + + RNA_def_string(ot->srna, "menu_idname", nullptr, 0, "Menu Name", "Menu to search in"); + RNA_def_string(ot->srna, + "initial_query", + nullptr, + 0, + "Initial Query", + "Query to insert into the search box"); +} + static int wm_call_menu_exec(bContext *C, wmOperator *op) { char idname[BKE_ST_MAXNAME]; @@ -3851,6 +3894,7 @@ void wm_operatortypes_register() WM_operatortype_append(WM_OT_splash_about); WM_operatortype_append(WM_OT_search_menu); WM_operatortype_append(WM_OT_search_operator); + WM_operatortype_append(WM_OT_search_single_menu); WM_operatortype_append(WM_OT_call_menu); WM_operatortype_append(WM_OT_call_menu_pie); WM_operatortype_append(WM_OT_call_panel);