Blender V5.0
interface_template_search_menu.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
11
12#include <cstdio>
13#include <cstring>
14#include <variant>
15
16#include "MEM_guardedalloc.h"
17
18#include "DNA_action_types.h"
19#include "DNA_node_types.h"
20
21#include "BLI_listbase.h"
22#include "BLI_map.hh"
23#include "BLI_math_matrix.h"
24#include "BLI_resource_scope.hh"
25#include "BLI_set.hh"
26#include "BLI_stack.hh"
27#include "BLI_string.h"
28#include "BLI_string_utf8.h"
29#include "BLI_utildefines.h"
30
31#include "BLT_translation.hh"
32
33#include "BKE_context.hh"
34#include "BKE_global.hh"
35#include "BKE_screen.hh"
36
37#include "ED_screen.hh"
38
39#include "RNA_access.hh"
40#include "RNA_prototypes.hh"
41
42#include "WM_api.hh"
43#include "WM_types.hh"
44
46#include "UI_string_search.hh"
47#include "interface_intern.hh"
48
49/* For key-map item access. */
50#include "wm_event_system.hh"
51
52#include <fmt/format.h>
53
56
57/* -------------------------------------------------------------------- */
60
75
83
87 int icon = 0;
88 int state = 0;
89 float weight = 0.0f;
90
92 MenuType *mt = nullptr;
93
94 struct OperatorData {
100 {
101 if (this->opptr != nullptr) {
102 WM_operator_properties_free(this->opptr);
103 MEM_delete(this->opptr);
104 }
105 MEM_delete(this->context);
106 }
107 };
115 std::variant<OperatorData, PropertyData> data;
116
119};
120
133
134static bool menu_item_sort_by_drawstr_full(const MenuSearch_Item &menu_item_a,
135 const MenuSearch_Item &menu_item_b)
136{
137 return menu_item_a.drawwstr_full < menu_item_b.drawwstr_full;
138}
139
142 MenuType *mt,
143 uiBut *but,
144 MenuSearch_Context *wm_context,
145 MenuSearch_Parent *menu_parent)
146{
147 using namespace blender;
148 MenuSearch_Item *item = nullptr;
149
150 /* Use override if the name is empty, this can happen with popovers. */
151 std::string drawstr_override;
152 const size_t sep_index = (but->flag & UI_BUT_HAS_SEP_CHAR) ? but->drawstr.find(UI_SEP_CHAR) :
153 std::string::npos;
154 const bool drawstr_is_empty = sep_index == 0 || but->drawstr.empty();
155
156 if (but->optype != nullptr) {
157 if (drawstr_is_empty) {
158 drawstr_override = WM_operatortype_name(but->optype, but->opptr);
159 }
160
161 item = &scope.construct<MenuSearch_Item>();
163 auto &op_data = std::get<MenuSearch_Item::OperatorData>(item->data);
164 op_data.type = but->optype;
165 op_data.opcontext = but->opcontext;
166 op_data.context = but->context ? MEM_new<bContextStore>(__func__, *but->context) : nullptr;
167 op_data.opptr = but->opptr;
168
169 item->weight = but->search_weight;
170
171 but->opptr = nullptr;
172 }
173 else if (but->rnaprop != nullptr) {
174 const int prop_type = RNA_property_type(but->rnaprop);
175
176 if (drawstr_is_empty) {
177 if (prop_type == PROP_ENUM) {
178 const int value_enum = int(but->hardmax);
179 EnumPropertyItem enum_item;
181 &but->rnapoin,
182 but->rnaprop,
183 value_enum,
184 &enum_item))
185 {
186 drawstr_override = enum_item.name;
187 }
188 else {
189 /* Should never happen. */
190 drawstr_override = "Unknown";
191 }
192 }
193 else {
194 drawstr_override = RNA_property_ui_name(but->rnaprop);
195 }
196 }
197
198 if (!ELEM(prop_type, PROP_BOOLEAN, PROP_ENUM)) {
199 /* Note that these buttons are not prevented,
200 * but aren't typically used in menus. */
201 printf("Button '%s' in menu '%s' is a menu item with unsupported RNA type %d\n",
202 but->drawstr.c_str(),
203 mt->idname,
204 prop_type);
205 }
206 else {
207 item = &scope.construct<MenuSearch_Item>();
208 item->weight = but->search_weight;
209
211 auto &rna_data = std::get<MenuSearch_Item::PropertyData>(item->data);
212 rna_data.ptr = but->rnapoin;
213 rna_data.prop = but->rnaprop;
214 rna_data.index = but->rnaindex;
215 if (prop_type == PROP_ENUM) {
216 rna_data.enum_value = int(but->hardmax);
217 }
218 }
219 }
220
221 if (item != nullptr) {
222 /* Handle shared settings. */
223 if (!drawstr_override.empty()) {
224 const StringRef drawstr_suffix = sep_index == std::string::npos ?
225 "" :
226 StringRef(but->drawstr).drop_prefix(sep_index);
227 std::string drawstr = std::string("(") + drawstr_override + ")" + drawstr_suffix;
228 item->drawstr = scope.allocator().copy_string(drawstr);
229 }
230 else {
231 item->drawstr = scope.allocator().copy_string(but->drawstr);
232 }
233
234 item->icon = ui_but_icon(but);
235 item->state = (but->flag &
237 item->mt = mt;
238
239 item->wm_context = wm_context;
240 item->menu_parent = menu_parent;
241
242 data->items.append(*item);
243 return true;
244 }
245
246 return false;
247}
248
253{
254 bool changed = false;
255 if (auto *op_data = std::get_if<MenuSearch_Item::OperatorData>(&item->data)) {
256 but->optype = op_data->type;
257 but->opcontext = op_data->opcontext;
258 but->context = op_data->context;
259 but->opptr = op_data->opptr;
260 changed = true;
261 }
262 else if (auto *rna_data = std::get_if<MenuSearch_Item::PropertyData>(&item->data)) {
263 const int prop_type = RNA_property_type(rna_data->prop);
264
265 but->rnapoin = rna_data->ptr;
266 but->rnaprop = rna_data->prop;
267 but->rnaindex = rna_data->index;
268
269 if (prop_type == PROP_ENUM) {
270 but->hardmax = rna_data->enum_value;
271 }
272 changed = true;
273 }
274
275 if (changed) {
276 but->drawstr = item->drawstr;
277 const size_t sep_index = but->drawstr.find(UI_SEP_CHAR);
278
279 if (sep_index != std::string::npos) {
280 but->drawstr.resize(sep_index);
281 }
282
283 but->icon = item->icon;
284 }
285
286 return changed;
287}
288
290 MenuType *mt = nullptr;
294 std::optional<bContextStore> context;
295};
296
301 wmWindow *win,
302 ScrArea *area,
303 ARegion *region,
306 blender::Set<MenuType *> &menu_tagged)
307{
309 ListBase *handlers[] = {
310 region ? &region->runtime->handlers : nullptr,
311 area ? &area->handlers : nullptr,
312 &win->handlers,
313 };
314
315 for (int handler_index = 0; handler_index < ARRAY_SIZE(handlers); handler_index++) {
316 if (handlers[handler_index] == nullptr) {
317 continue;
318 }
319 LISTBASE_FOREACH (wmEventHandler *, handler_base, handlers[handler_index]) {
320 /* During this loop, UI handlers for nested menus can tag multiple handlers free. */
321 if (handler_base->flag & WM_HANDLER_DO_FREE) {
322 continue;
323 }
324 if (handler_base->type != WM_HANDLER_TYPE_KEYMAP) {
325 continue;
326 }
327
328 if (handler_base->poll == nullptr || handler_base->poll(win, area, region, win->eventstate))
329 {
330 wmEventHandler_Keymap *handler = (wmEventHandler_Keymap *)handler_base;
332 WM_event_get_keymaps_from_handler(wm, win, handler, &km_result);
333 for (int km_index = 0; km_index < km_result.keymaps_len; km_index++) {
334 wmKeyMap *keymap = km_result.keymaps[km_index];
335 if (keymap && WM_keymap_poll(C, keymap)) {
336 LISTBASE_FOREACH (wmKeyMapItem *, kmi, &keymap->items) {
337 if (kmi->flag & KMI_INACTIVE) {
338 continue;
339 }
340 if (STR_ELEM(kmi->idname, "WM_OT_call_menu", "WM_OT_call_menu_pie")) {
341 char menu_idname[MAX_NAME];
342 RNA_string_get(kmi->ptr, "name", menu_idname);
343 MenuType *mt = WM_menutype_find(menu_idname, false);
344
345 if (mt && menu_tagged.add(mt)) {
346 /* Unlikely, but possible this will be included twice. */
347 menu_stack.push({mt});
348 menu_to_kmi.add(mt, kmi);
349 }
350 }
351 }
352 }
353 }
354 }
355 }
356 }
357}
358
363{
364 /* Add to temporary list so we can sort them separately. */
366
367 ResourceScope &scope = data->scope;
369 if ((ot->flag & OPTYPE_INTERNAL) && (G.debug & G_DEBUG_WM) == 0) {
370 continue;
371 }
372
373 if (WM_operator_poll(C, ot)) {
374 const char *ot_ui_name = CTX_IFACE_(ot->translation_context, ot->name);
375
378 auto &op_data = std::get<MenuSearch_Item::OperatorData>(item.data);
379 op_data.type = ot;
381 op_data.context = nullptr;
382
383 char idname_as_py[OP_MAX_TYPENAME];
384 char uiname[256];
385 WM_operator_py_idname(idname_as_py, ot->idname);
386
387 SNPRINTF_UTF8(uiname, "%s " UI_MENU_ARROW_SEP " %s", idname_as_py, ot_ui_name);
388
389 item.drawwstr_full = scope.allocator().copy_string(uiname);
390 item.drawstr = ot_ui_name;
391
392 item.wm_context = nullptr;
393
394 operator_items.append(item);
395 }
396 }
397
398 std::sort(operator_items.begin(), operator_items.end(), menu_item_sort_by_drawstr_full);
399
400 data->items.extend(operator_items);
401}
402
410 wmWindow *win,
411 ScrArea *area_init,
412 ARegion *region_init,
413 bool include_all_areas,
414 const char *single_menu_idname)
415{
416 blender::Map<MenuType *, const char *> menu_display_name_map;
417 const uiStyle *style = UI_style_get_dpi();
418
419 const bContextStore *old_context_store = CTX_store_get(C);
420 BLI_SCOPED_DEFER([&]() { CTX_store_set(C, old_context_store); });
421 bContextStore context_store;
422 if (old_context_store) {
423 context_store = *old_context_store;
424 }
425 context_store.entries.append({"is_menu_search", true});
426 CTX_store_set(C, &context_store);
427
428 /* Convert into non-ui structure. */
429 MenuSearch_Data *data = MEM_new<MenuSearch_Data>(__func__);
430 ResourceScope &scope = data->scope;
431
432 fmt::memory_buffer str_buf;
433
434 /* Use a stack of menus to handle and discover new menus in passes. */
436
437 /* Tag menu types not to add, either because they have already been added
438 * or they have been blacklisted. */
439 blender::Set<MenuType *> menu_tagged;
441
442 /* Blacklist menus we don't want to show. */
443 {
444 const char *idname_array[] = {
445 /* While we could include this, it's just showing filenames to load. */
446 (single_menu_idname && STREQ(single_menu_idname, "TOPBAR_MT_file_open_recent")) ?
447 nullptr :
448 "TOPBAR_MT_file_open_recent",
449 /* Showing undo history is not helpful since users may accidentally undo
450 * an action they intend to run. */
451 "TOPBAR_MT_undo_history",
452 };
453 for (int i = 0; i < ARRAY_SIZE(idname_array); i++) {
454 if (!idname_array[i]) {
455 continue;
456 }
457 MenuType *mt = WM_menutype_find(idname_array[i], false);
458 if (mt != nullptr) {
459 menu_tagged.add(mt);
460 }
461 }
462 }
463
464 if (!single_menu_idname) {
465 /* Exclude context menus (when not searching in a specific single menu) because:
466 * - The menu items are available elsewhere (and will show up multiple times).
467 * - Menu items depend on exact context, making search results unpredictable
468 * (exact number of items selected for example). See design doc #74158.
469 * There is one exception,
470 * as the outliner only exposes functionality via the context menu. */
472 if (BLI_str_endswith(mt->idname, "_context_menu")) {
473 menu_tagged.add(mt);
474 }
475 }
476 const char *idname_array[] = {
477 /* Add back some context menus. */
478 "OUTLINER_MT_context_menu",
479 };
480 for (int i = 0; i < ARRAY_SIZE(idname_array); i++) {
481 MenuType *mt = WM_menutype_find(idname_array[i], false);
482 if (mt != nullptr) {
483 menu_tagged.remove(mt);
484 }
485 }
486 }
487
488 /* Collect contexts, one for each 'ui_type'. */
489 MenuSearch_Context *wm_contexts = nullptr;
490
491 const EnumPropertyItem *space_type_ui_items = nullptr;
492 int space_type_ui_items_len = 0;
493 bool space_type_ui_items_free = false;
494
495 /* Text used as prefix for top-bar menu items. */
496 const char *global_menu_prefix = nullptr;
497
498 if (include_all_areas) {
500
501 /* First create arrays for ui_type. */
502 PropertyRNA *prop_ui_type = nullptr;
503 {
504 /* This must be a valid pointer, with only it's type checked. */
505 ScrArea area_dummy{};
506 /* Anything besides #SPACE_EMPTY is fine,
507 * as this value is only included in the enum when set. */
508 area_dummy.spacetype = SPACE_TOPBAR;
509 PointerRNA ptr = RNA_pointer_create_discrete(&screen->id, &RNA_Area, &area_dummy);
510 prop_ui_type = RNA_struct_find_property(&ptr, "ui_type");
512 &ptr,
513 prop_ui_type,
514 &space_type_ui_items,
515 &space_type_ui_items_len,
516 &space_type_ui_items_free);
517
518 wm_contexts =
519 scope.construct<blender::Array<MenuSearch_Context>>(space_type_ui_items_len).data();
520 for (int i = 0; i < space_type_ui_items_len; i++) {
521 wm_contexts[i].space_type_ui_index = -1;
522 }
523 }
524
525 LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) {
527 if (region != nullptr) {
528 PointerRNA ptr = RNA_pointer_create_discrete(&screen->id, &RNA_Area, area);
529 const int space_type_ui = RNA_property_enum_get(&ptr, prop_ui_type);
530
531 const int space_type_ui_index = RNA_enum_from_value(space_type_ui_items, space_type_ui);
532 if (space_type_ui_index == -1) {
533 continue;
534 }
535
536 if (wm_contexts[space_type_ui_index].space_type_ui_index != -1) {
537 ScrArea *area_best = wm_contexts[space_type_ui_index].area;
538 const uint value_best = uint(area_best->winx) * uint(area_best->winy);
539 const uint value_test = uint(area->winx) * uint(area->winy);
540 if (value_best > value_test) {
541 continue;
542 }
543 }
544
545 wm_contexts[space_type_ui_index].space_type_ui_index = space_type_ui_index;
546 wm_contexts[space_type_ui_index].area = area;
547 wm_contexts[space_type_ui_index].region = region;
548 }
549 }
550
551 global_menu_prefix = CTX_IFACE_(RNA_property_translation_context(prop_ui_type), "Top Bar");
552 }
553
554 for (int space_type_ui_index = -1; space_type_ui_index < space_type_ui_items_len;
555 space_type_ui_index += 1)
556 {
557
558 ScrArea *area = nullptr;
559 ARegion *region = nullptr;
560 MenuSearch_Context *wm_context = nullptr;
561
562 if (include_all_areas) {
563 if (space_type_ui_index == -1) {
564 /* First run without any context, to populate the top-bar without. */
565 wm_context = nullptr;
566 area = nullptr;
567 region = nullptr;
568 }
569 else {
570 wm_context = &wm_contexts[space_type_ui_index];
571 if (wm_context->space_type_ui_index == -1) {
572 continue;
573 }
574
575 area = wm_context->area;
576 region = wm_context->region;
577
578 CTX_wm_area_set(C, area);
579 CTX_wm_region_set(C, region);
580 }
581 }
582 else {
583 area = area_init;
584 region = region_init;
585 }
586
587 if (single_menu_idname) {
588 if (MenuType *mt = WM_menutype_find(single_menu_idname, false)) {
589 if (menu_tagged.add(mt)) {
590 menu_stack.push({mt});
591 }
592 }
593 }
594 else {
595 /* Populate menus from the editors,
596 * note that we could create a fake header, draw the header and extract the menus
597 * from the buttons, however this is quite involved and can be avoided as by convention
598 * each space-type has a single root-menu that headers use. */
599 const char *idname_array[2] = {nullptr};
600 int idname_array_len = 0;
601
602 /* Use negative for global (no area) context, populate the top-bar. */
603 if (space_type_ui_index == -1) {
604 idname_array[idname_array_len++] = "TOPBAR_MT_editor_menus";
605 }
606
607#define SPACE_MENU_MAP(space_type, menu_id) \
608 case space_type: \
609 idname_array[idname_array_len++] = menu_id; \
610 break
611#define SPACE_MENU_NOP(space_type) \
612 case space_type: \
613 break
614
615 if (area != nullptr) {
616 SpaceLink *sl = (SpaceLink *)area->spacedata.first;
617 switch ((eSpace_Type)area->spacetype) {
618 SPACE_MENU_MAP(SPACE_VIEW3D, "VIEW3D_MT_editor_menus");
619 SPACE_MENU_MAP(SPACE_GRAPH, "GRAPH_MT_editor_menus");
620 SPACE_MENU_MAP(SPACE_OUTLINER, "OUTLINER_MT_editor_menus");
622 SPACE_MENU_MAP(SPACE_FILE, "FILEBROWSER_MT_editor_menus");
623 SPACE_MENU_MAP(SPACE_IMAGE, "IMAGE_MT_editor_menus");
624 SPACE_MENU_MAP(SPACE_INFO, "INFO_MT_editor_menus");
625 SPACE_MENU_MAP(SPACE_SEQ, "SEQUENCER_MT_editor_menus");
626 SPACE_MENU_MAP(SPACE_TEXT, "TEXT_MT_editor_menus");
627 SPACE_MENU_MAP(SPACE_ACTION, "DOPESHEET_MT_editor_menus");
628 SPACE_MENU_MAP(SPACE_NLA, "NLA_MT_editor_menus");
629 SPACE_MENU_MAP(SPACE_NODE, "NODE_MT_editor_menus");
630 SPACE_MENU_MAP(SPACE_CONSOLE, "CONSOLE_MT_editor_menus");
631 SPACE_MENU_MAP(SPACE_USERPREF, "USERPREF_MT_editor_menus");
633 (((const SpaceClip *)sl)->mode == SC_MODE_TRACKING) ?
634 "CLIP_MT_tracking_editor_menus" :
635 "CLIP_MT_masking_editor_menus");
641 }
642 }
643 for (int i = 0; i < idname_array_len; i++) {
644 MenuType *mt = WM_menutype_find(idname_array[i], false);
645 if (mt != nullptr) {
646 /* Check if this exists because of 'include_all_areas'. */
647 if (menu_tagged.add(mt)) {
648 menu_stack.push({mt});
649 }
650 }
651 }
652 }
653#undef SPACE_MENU_MAP
654#undef SPACE_MENU_NOP
655
656 bool has_keymap_menu_items = false;
657
658 while (!menu_stack.is_empty()) {
659 MenuStackEntry current_menu = menu_stack.pop();
660 MenuType *mt = current_menu.mt;
661 if (!WM_menutype_poll(C, mt)) {
662 continue;
663 }
664
665 uiBlock *block = UI_block_begin(C, region, __func__, blender::ui::EmbossType::Emboss);
666 uiLayout &layout = blender::ui::block_layout(block,
669 0,
670 0,
671 200,
672 0,
674 style);
675
677
678 if (current_menu.context.has_value()) {
679 layout.context_copy(&*current_menu.context);
680 }
682 UI_menutype_draw(C, mt, &layout);
683
684 UI_block_end(C, block);
685
686 for (const int i : block->buttons.index_range()) {
687 const std::unique_ptr<uiBut> &but = block->buttons[i];
688 MenuType *mt_from_but = nullptr;
689 /* Support menu titles with dynamic from initial labels
690 * (used by edit-mesh context menu). */
691 if (but->type == ButType::Label) {
692
693 /* Check if the label is the title. */
694 const std::unique_ptr<uiBut> *but_test = block->buttons.begin() + i - 1;
695 while (but_test >= block->buttons.begin() && (*but_test)->type == ButType::Sepr) {
696 but_test--;
697 }
698
699 if (but_test < block->buttons.begin()) {
700 menu_display_name_map.add(mt, scope.allocator().copy_string(but->drawstr).c_str());
701 }
702 }
704 data, scope, mt, but.get(), wm_context, current_menu.self_as_parent))
705 {
706 /* pass */
707 }
708 else if ((mt_from_but = UI_but_menutype_get(but.get()))) {
709 const bool uses_context = but->context &&
710 bool(mt_from_but->flag & MenuTypeFlag::ContextDependent);
711 const bool tagged_first_time = menu_tagged.add(mt_from_but);
712 const bool scan_submenu = tagged_first_time || uses_context;
713
714 if (scan_submenu) {
715 MenuSearch_Parent *menu_parent = &scope.construct<MenuSearch_Parent>();
716 /* Use brackets for menu key shortcuts,
717 * converting "Text|Some-Shortcut" to "Text (Some-Shortcut)".
718 * This is needed so we don't right align sub-menu contents
719 * we only want to do that for the last menu item, not the path that leads to it.
720 */
721 const char *drawstr_sep = but->flag & UI_BUT_HAS_SEP_CHAR ?
722 strrchr(but->drawstr.c_str(), UI_SEP_CHAR) :
723 nullptr;
724 bool drawstr_is_empty = false;
725 if (drawstr_sep != nullptr) {
726 BLI_assert(str_buf.size() == 0);
727 /* Detect empty string, fall back to menu name. */
728 const char *drawstr = but->drawstr.c_str();
729 int drawstr_len = drawstr_sep - but->drawstr.c_str();
730 if (UNLIKELY(drawstr_len == 0)) {
731 drawstr = CTX_IFACE_(mt_from_but->translation_context, mt_from_but->label);
732 drawstr_len = strlen(drawstr);
733 if (drawstr[0] == '\0') {
734 drawstr_is_empty = true;
735 }
736 }
737 str_buf.append(StringRef(drawstr, drawstr_len));
738 fmt::format_to(fmt::appender(str_buf), " ({})", drawstr_sep + 1);
739 menu_parent->drawstr = scope.allocator().copy_string(
740 StringRef(str_buf.data(), str_buf.size()));
741 str_buf.clear();
742 }
743 else {
744 const char *drawstr = but->drawstr.c_str();
745 if (UNLIKELY(drawstr[0] == '\0')) {
746 drawstr = CTX_IFACE_(mt_from_but->translation_context, mt_from_but->label);
747 if (drawstr[0] == '\0') {
748 drawstr_is_empty = true;
749 }
750 }
751 menu_parent->drawstr = scope.allocator().copy_string(drawstr);
752 }
753 menu_parent->parent = current_menu.self_as_parent;
754
755 if (drawstr_is_empty) {
756 printf("Warning: '%s' menu has empty 'bl_label'.\n", mt_from_but->idname);
757 }
758
759 if (uses_context) {
760 menu_stack.push({mt_from_but, menu_parent, *but->context});
761 }
762 else {
763 menu_stack.push({mt_from_but, menu_parent});
764 }
765 }
766 }
767 else if (but->menu_create_func != nullptr) {
768 /* A non 'MenuType' menu button. */
769
770 /* +1 to avoid overlap with the current 'block'. */
771 uiBlock *sub_block = UI_block_begin(
772 C, region, __func__ + 1, blender::ui::EmbossType::Emboss);
773 uiLayout &sub_layout = blender::ui::block_layout(sub_block,
776 0,
777 0,
778 200,
779 0,
781 style);
782
784
786
787 /* If this is a panel, check it's poll function succeeds before drawing.
788 * otherwise draw(..) may be called in an unsupported context and crash, see: #130744.
789 *
790 * NOTE(@ideasman42): it would be good if the buttons #UI_BUT_DISABLED flag
791 * could be used as a more general way to know if poll succeeded,
792 * at this point it's not set - this could be further investigated. */
793 bool poll_success = true;
794 if (PanelType *pt = UI_but_paneltype_get(but.get())) {
795 if (pt->poll && (pt->poll(C, pt) == false)) {
796 poll_success = false;
797 }
798 }
799
800 if (poll_success) {
801 but->menu_create_func(C, &sub_layout, but->poin);
802 }
803
804 UI_block_end(C, sub_block);
805
806 if (poll_success) {
807 MenuSearch_Parent *menu_parent = &scope.construct<MenuSearch_Parent>();
808 menu_parent->drawstr = scope.allocator().copy_string(but->drawstr);
809 menu_parent->parent = current_menu.self_as_parent;
810
811 for (const std::unique_ptr<uiBut> &sub_but : sub_block->buttons) {
813 data, scope, mt, sub_but.get(), wm_context, menu_parent);
814 }
815 }
816
817 if (region) {
818 region->runtime->block_name_map.remove(sub_block->name);
819 BLI_remlink(&region->runtime->uiblocks, sub_block);
820 }
821 UI_block_free(nullptr, sub_block);
822 }
823 }
824 if (region) {
825 region->runtime->block_name_map.remove(block->name);
826 BLI_remlink(&region->runtime->uiblocks, block);
827 }
828 UI_block_free(nullptr, block);
829
830 if (single_menu_idname == nullptr) {
831 /* Add key-map items as a second pass, so all menus are accessed from the header & top-bar
832 * before key shortcuts are expanded. */
833 if (menu_stack.is_empty() && (has_keymap_menu_items == false)) {
834 has_keymap_menu_items = true;
836 C, win, area, region, menu_stack, menu_to_kmi, menu_tagged);
837 }
838 }
839 }
840 }
841
842 /* NOTE: currently this builds the full path for each menu item,
843 * that could be moved into the parent menu. */
844
845 /* Set names as full paths. */
846 for (MenuSearch_Item &item : data->items) {
847 BLI_assert(str_buf.size() == 0);
848
849 if (include_all_areas) {
850 fmt::format_to(fmt::appender(str_buf),
851 "{}: ",
852 (item.wm_context != nullptr) ?
853 space_type_ui_items[item.wm_context->space_type_ui_index].name :
854 global_menu_prefix);
855 }
856
857 if (item.menu_parent != nullptr) {
858 MenuSearch_Parent *menu_parent = item.menu_parent;
859 menu_parent->temp_child = nullptr;
860 while (menu_parent && menu_parent->parent) {
861 menu_parent->parent->temp_child = menu_parent;
862 menu_parent = menu_parent->parent;
863 }
864 while (menu_parent) {
865 str_buf.append(menu_parent->drawstr);
866 str_buf.append(StringRef(" " UI_MENU_ARROW_SEP " "));
867 menu_parent = menu_parent->temp_child;
868 }
869 }
870 else {
871 const char *drawstr = menu_display_name_map.lookup_default(item.mt, nullptr);
872 if (drawstr == nullptr) {
873 drawstr = CTX_IFACE_(item.mt->translation_context, item.mt->label);
874 }
875 str_buf.append(StringRef(drawstr));
876
877 wmKeyMapItem *kmi = menu_to_kmi.lookup_default(item.mt, nullptr);
878 if (kmi != nullptr) {
879 std::string kmi_str = WM_keymap_item_to_string(kmi, false).value_or("");
880 fmt::format_to(fmt::appender(str_buf), " ({})", kmi_str);
881 }
882
883 str_buf.append(StringRef(" " UI_MENU_ARROW_SEP " "));
884 }
885
886 str_buf.append(item.drawstr);
887
888 item.drawwstr_full = scope.allocator().copy_string(StringRef(str_buf.data(), str_buf.size()));
889 str_buf.clear();
890 }
891
892 /* Finally sort menu items.
893 *
894 * NOTE: we might want to keep the in-menu order, for now sort all. */
895 std::sort(data->items.begin(), data->items.end(), menu_item_sort_by_drawstr_full);
896
897 if (include_all_areas) {
898 CTX_wm_area_set(C, area_init);
899 CTX_wm_region_set(C, region_init);
900
901 if (space_type_ui_items_free) {
902 MEM_freeN(space_type_ui_items);
903 }
904 }
905
906 /* Include all operators for developers,
907 * since it can be handy to have a quick way to access any operator,
908 * including operators being developed which haven't yet been added into the interface.
909 *
910 * These are added after all menu items so developers still get normal behavior by default,
911 * unless searching for something that isn't already in a menu (or scroll down).
912 *
913 * Keep this behind a developer only check:
914 * - Many operators need options to be set to give useful results, see: #74157.
915 * - User who really prefer to list all operators can use #WM_OT_search_operator.
916 */
917 if ((U.flag & USER_DEVELOPER_UI) && single_menu_idname == nullptr) {
919 }
920
921 return data;
922}
923
924static void menu_search_arg_free_fn(void *data_v)
925{
926 MEM_delete(static_cast<MenuSearch_Data *>(data_v));
927}
928
929static void menu_search_exec_fn(bContext *C, void * /*arg1*/, void *arg2)
930{
931 MenuSearch_Item *item = (MenuSearch_Item *)arg2;
932 if (item == nullptr) {
933 return;
934 }
935 if (item->state & UI_BUT_DISABLED) {
936 return;
937 }
938
939 ScrArea *area_prev = CTX_wm_area(C);
940 ARegion *region_prev = CTX_wm_region(C);
941
942 if (item->wm_context != nullptr) {
945 }
946
947 if (auto *op_data = std::get_if<MenuSearch_Item::OperatorData>(&item->data)) {
948 CTX_store_set(C, op_data->context);
950 C, op_data->type, op_data->opcontext, op_data->opptr, nullptr, item->drawstr);
951 CTX_store_set(C, nullptr);
952 }
953 else if (auto *rna_data = std::get_if<MenuSearch_Item::PropertyData>(&item->data)) {
954 PointerRNA *ptr = &rna_data->ptr;
955 PropertyRNA *prop = rna_data->prop;
956 const int index = rna_data->index;
957 const int prop_type = RNA_property_type(prop);
958 bool changed = false;
959
960 if (prop_type == PROP_BOOLEAN) {
961 const bool is_array = RNA_property_array_check(prop);
962 if (is_array) {
963 const bool value = RNA_property_boolean_get_index(ptr, prop, index);
964 RNA_property_boolean_set_index(ptr, prop, index, !value);
965 }
966 else {
967 const bool value = RNA_property_boolean_get(ptr, prop);
968 RNA_property_boolean_set(ptr, prop, !value);
969 }
970 changed = true;
971 }
972 else if (prop_type == PROP_ENUM) {
973 RNA_property_enum_set(ptr, prop, rna_data->enum_value);
974 changed = true;
975 }
976
977 if (changed) {
978 RNA_property_update(C, ptr, prop);
979 }
980 }
981
982 if (item->wm_context != nullptr) {
983 CTX_wm_area_set(C, area_prev);
984 CTX_wm_region_set(C, region_prev);
985 }
986}
987
988static void menu_search_update_fn(const bContext * /*C*/,
989 void *arg,
990 const char *str,
991 uiSearchItems *items,
992 const bool /*is_first*/)
993{
995
997
998 for (MenuSearch_Item &item : data->items) {
999 search.add(item.drawwstr_full, &item, item.weight);
1000 }
1001
1002 const blender::Vector<MenuSearch_Item *> filtered_items = search.query(str);
1003
1004 for (MenuSearch_Item *item : filtered_items) {
1005 if (!UI_search_item_add(items, item->drawwstr_full, item, item->icon, item->state, 0)) {
1006 break;
1007 }
1008 }
1009}
1010
1012
1013/* -------------------------------------------------------------------- */
1020
1022 void *arg,
1023 void *active,
1024 const wmEvent *event)
1025{
1028 bool has_menu = false;
1029
1030 new (&data->context_menu_data.but) uiBut();
1031 new (&data->context_menu_data.block) uiBlock();
1032 uiBut *but = &data->context_menu_data.but;
1033 uiBlock *block = &data->context_menu_data.block;
1034
1035 but->block = block;
1036
1037 if (menu_items_to_ui_button(item, but)) {
1038 ScrArea *area_prev = CTX_wm_area(C);
1039 ARegion *region_prev = CTX_wm_region(C);
1040
1041 if (item->wm_context != nullptr) {
1044 }
1045
1046 if (ui_popup_context_menu_for_button(C, but, event)) {
1047 has_menu = true;
1048 }
1049
1050 if (item->wm_context != nullptr) {
1051 CTX_wm_area_set(C, area_prev);
1052 CTX_wm_region_set(C, region_prev);
1053 }
1054 }
1055
1056 return has_menu;
1057}
1058
1060
1061/* -------------------------------------------------------------------- */
1064
1066 bContext *C, ARegion *region, const rcti * /*item_rect*/, void *arg, void *active)
1067{
1070
1071 new (&data->context_menu_data.but) uiBut();
1072 new (&data->context_menu_data.block) uiBlock();
1073 uiBut *but = &data->context_menu_data.but;
1074 uiBlock *block = &data->context_menu_data.block;
1075 unit_m4(block->winmat);
1076 block->aspect = 1;
1077
1078 but->block = block;
1079
1080 /* Place the fake button at the cursor so the tool-tip is places properly. */
1081 float tip_init[2];
1082 const wmEvent *event = CTX_wm_window(C)->eventstate;
1083 tip_init[0] = event->xy[0];
1084 tip_init[1] = event->xy[1] - (UI_UNIT_Y / 2);
1085 ui_window_to_block_fl(region, block, &tip_init[0], &tip_init[1]);
1086
1087 but->rect.xmin = tip_init[0];
1088 but->rect.xmax = tip_init[0];
1089 but->rect.ymin = tip_init[1];
1090 but->rect.ymax = tip_init[1];
1091
1092 if (menu_items_to_ui_button(item, but)) {
1093 ScrArea *area_prev = CTX_wm_area(C);
1094 ARegion *region_prev = CTX_wm_region(C);
1095
1096 if (item->wm_context != nullptr) {
1099 }
1100
1101 ARegion *region_tip = UI_tooltip_create_from_button(C, region, but, false);
1102
1103 if (item->wm_context != nullptr) {
1104 CTX_wm_area_set(C, area_prev);
1105 CTX_wm_region_set(C, region_prev);
1106 }
1107 return region_tip;
1108 }
1109
1110 return nullptr;
1111}
1112
1114
1115/* -------------------------------------------------------------------- */
1118
1119void UI_but_func_menu_search(uiBut *but, const char *single_menu_idname)
1120{
1121 bContext *C = (bContext *)but->block->evil_C;
1122 wmWindow *win = CTX_wm_window(C);
1123 ScrArea *area = CTX_wm_area(C);
1124 ARegion *region = CTX_wm_region(C);
1125 /* When run from top-bar scan all areas in the current window. */
1126 const bool include_all_areas = (area && (area->spacetype == SPACE_TOPBAR)) &&
1127 !single_menu_idname;
1129 C, win, area, region, include_all_areas, single_menu_idname);
1131 /* Generic callback. */
1134 data,
1135 false,
1138 nullptr);
1139
1143}
1144
1146{
1147 uiBlock *block;
1148 uiBut *but;
1149 static char search[256] = "";
1150
1151 block = layout->block();
1153
1154 but = uiDefSearchBut(
1155 block, search, 0, ICON_VIEWZOOM, sizeof(search), 0, 0, UI_UNIT_X * 6, UI_UNIT_Y, "");
1157}
1158
ScrArea * CTX_wm_area(const bContext *C)
wmWindow * CTX_wm_window(const bContext *C)
const bContextStore * CTX_store_get(const bContext *C)
void CTX_store_set(bContext *C, const bContextStore *store)
void CTX_wm_area_set(bContext *C, ScrArea *area)
void CTX_wm_region_set(bContext *C, ARegion *region)
ARegion * CTX_wm_region(const bContext *C)
wmWindowManager * CTX_wm_manager(const bContext *C)
@ G_DEBUG_WM
ARegion * BKE_area_find_region_type(const ScrArea *area, int region_type)
Definition screen.cc:846
#define BLI_assert(a)
Definition BLI_assert.h:46
#define LISTBASE_FOREACH(type, var, list)
void BLI_remlink(ListBase *listbase, void *vlink) ATTR_NONNULL(1)
Definition listbase.cc:131
void unit_m4(float m[4][4])
#define BLI_SCOPED_DEFER(function_to_defer)
#define STR_ELEM(...)
Definition BLI_string.h:661
int bool bool BLI_str_endswith(const char *__restrict str, const char *__restrict end) ATTR_NONNULL(1
#define SNPRINTF_UTF8(dst, format,...)
unsigned int uint
#define ARRAY_SIZE(arr)
#define UNLIKELY(x)
#define ELEM(...)
#define STREQ(a, b)
#define CTX_IFACE_(context, msgid)
#define MAX_NAME
Definition DNA_defs.h:50
@ RGN_TYPE_WINDOW
eSpace_Type
@ SPACE_TEXT
@ SPACE_CLIP
@ SPACE_ACTION
@ SPACE_CONSOLE
@ SPACE_OUTLINER
@ SPACE_STATUSBAR
@ SPACE_TOPBAR
@ SPACE_NODE
@ SPACE_SPREADSHEET
@ SPACE_USERPREF
@ SPACE_FILE
@ SPACE_PROPERTIES
@ SPACE_NLA
@ SPACE_SEQ
@ SPACE_EMPTY
@ SPACE_SCRIPT
@ SPACE_IMAGE
@ SPACE_GRAPH
@ SPACE_VIEW3D
@ SPACE_INFO
@ SC_MODE_TRACKING
@ USER_DEVELOPER_UI
#define OP_MAX_TYPENAME
Read Guarded memory(de)allocation.
@ PROP_BOOLEAN
Definition RNA_types.hh:162
@ PROP_ENUM
Definition RNA_types.hh:166
#define C
Definition RandGen.cpp:29
#define UI_UNIT_Y
@ UI_BLOCK_SHOW_SHORTCUT_ALWAYS
uiBlock * UI_block_begin(const bContext *C, ARegion *region, std::string name, blender::ui::EmbossType emboss)
#define UI_SEP_CHAR
@ UI_BUT_REDALERT
@ UI_BUT_DISABLED
@ UI_BUT_INACTIVE
@ UI_BUT_HAS_SEP_CHAR
void UI_but_func_search_set(uiBut *but, uiButSearchCreateFn search_create_fn, uiButSearchUpdateFn search_update_fn, void *arg, bool free_arg, uiFreeArgFunc search_arg_free_fn, uiButHandleFunc search_exec_fn, void *active)
MenuType * UI_but_menutype_get(const uiBut *but)
const uiStyle * UI_style_get_dpi()
void UI_but_func_search_set_tooltip(uiBut *but, uiButSearchTooltipFn tooltip_fn)
uiBut * uiDefSearchBut(uiBlock *block, void *arg, int retval, int icon, int maxncpy, int x, int y, short width, short height, std::optional< blender::StringRef > tip)
ARegion * UI_tooltip_create_from_button(bContext *C, ARegion *butregion, uiBut *but, bool is_quick_tip)
bool UI_search_item_add(uiSearchItems *items, blender::StringRef name, void *poin, int iconid, int but_flag, uint8_t name_prefix_offset)
void UI_but_func_search_set_context_menu(uiBut *but, uiButSearchContextMenuFn context_menu_fn)
PanelType * UI_but_paneltype_get(const uiBut *but)
void UI_but_func_search_set_sep_string(uiBut *but, const char *search_sep_string)
void UI_block_free(const bContext *C, uiBlock *block)
#define UI_UNIT_X
void UI_block_flag_enable(uiBlock *block, int flag)
void UI_block_end(const bContext *C, uiBlock *block)
void UI_menutype_draw(bContext *C, MenuType *mt, uiLayout *layout)
@ WM_HANDLER_DO_FREE
Definition WM_api.hh:585
@ OPTYPE_INTERNAL
Definition WM_types.hh:202
#define U
BMesh const char void * data
void append(const T &value)
IndexRange index_range() const
T * begin()
StringRefNull copy_string(StringRef str)
bool add(const Key &key, const Value &value)
Definition BLI_map.hh:295
Value lookup_default(const Key &key, const Value &default_value) const
Definition BLI_map.hh:570
T & construct(Args &&...args)
LinearAllocator & allocator()
bool add(const Key &key)
Definition BLI_set.hh:248
bool remove(const Key &key)
Definition BLI_set.hh:385
bool is_empty() const
Definition BLI_stack.hh:308
void push(const T &value)
Definition BLI_stack.hh:213
constexpr const char * c_str() const
constexpr StringRef drop_prefix(int64_t n) const
void append(const T &value)
void add(const StringRef str, T *user_data, const int weight=0)
Vector< T * > query(const StringRef query) const
#define str(s)
#define active
#define printf(...)
void ui_window_to_block_fl(const ARegion *region, const uiBlock *block, float *x, float *y)
Definition interface.cc:191
bool ui_popup_context_menu_for_button(bContext *C, uiBut *but, const wmEvent *event)
ARegion * ui_searchbox_create_menu(bContext *C, ARegion *butregion, uiButSearch *search_but)
#define UI_MENU_PADDING
int ui_but_icon(const uiBut *but)
static void menu_search_update_fn(const bContext *, void *arg, const char *str, uiSearchItems *items, const bool)
static bool ui_search_menu_create_context_menu(bContext *C, void *arg, void *active, const wmEvent *event)
static void menu_items_from_all_operators(bContext *C, MenuSearch_Data *data)
static bool menu_items_to_ui_button(MenuSearch_Item *item, uiBut *but)
static void menu_search_exec_fn(bContext *C, void *, void *arg2)
static ARegion * ui_search_menu_create_tooltip(bContext *C, ARegion *region, const rcti *, void *arg, void *active)
static bool menu_item_sort_by_drawstr_full(const MenuSearch_Item &menu_item_a, const MenuSearch_Item &menu_item_b)
void uiTemplateMenuSearch(uiLayout *layout)
static bool menu_items_from_ui_create_item_from_button(MenuSearch_Data *data, blender::ResourceScope &scope, MenuType *mt, uiBut *but, MenuSearch_Context *wm_context, MenuSearch_Parent *menu_parent)
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)
#define SPACE_MENU_NOP(space_type)
void UI_but_func_menu_search(uiBut *but, const char *single_menu_idname)
static void menu_types_add_from_keymap_items(bContext *C, wmWindow *win, ScrArea *area, ARegion *region, blender::Stack< MenuStackEntry > &menu_stack, blender::Map< MenuType *, wmKeyMapItem * > &menu_to_kmi, blender::Set< MenuType * > &menu_tagged)
#define SPACE_MENU_MAP(space_type, menu_id)
static void menu_search_arg_free_fn(void *data_v)
void MEM_freeN(void *vmemh)
Definition mallocn.cc:113
#define G(x, y, z)
uiLayout & block_layout(uiBlock *block, LayoutDirection direction, LayoutType type, int x, int y, int size, int em, int padding, const uiStyle *style)
void block_layout_set_current(uiBlock *block, uiLayout *layout)
void RNA_property_boolean_set_index(PointerRNA *ptr, PropertyRNA *prop, int index, bool value)
bool RNA_property_array_check(PropertyRNA *prop)
PropertyRNA * RNA_struct_find_property(PointerRNA *ptr, const char *identifier)
PropertyType RNA_property_type(PropertyRNA *prop)
void RNA_property_enum_set(PointerRNA *ptr, PropertyRNA *prop, int value)
void RNA_property_update(bContext *C, PointerRNA *ptr, PropertyRNA *prop)
bool RNA_property_boolean_get(PointerRNA *ptr, PropertyRNA *prop)
const char * RNA_property_translation_context(const PropertyRNA *prop)
const char * RNA_property_ui_name(const PropertyRNA *prop, const PointerRNA *ptr)
void RNA_property_boolean_set(PointerRNA *ptr, PropertyRNA *prop, bool value)
std::string RNA_string_get(PointerRNA *ptr, const char *name)
bool RNA_property_enum_item_from_value_gettexted(bContext *C, PointerRNA *ptr, PropertyRNA *prop, const int value, EnumPropertyItem *r_item)
int RNA_property_enum_get(PointerRNA *ptr, PropertyRNA *prop)
int RNA_enum_from_value(const EnumPropertyItem *item, const int value)
void RNA_property_enum_items(bContext *C, PointerRNA *ptr, PropertyRNA *prop, const EnumPropertyItem **r_item, int *r_totitem, bool *r_free)
bool RNA_property_boolean_get_index(PointerRNA *ptr, PropertyRNA *prop, int index)
PointerRNA RNA_pointer_create_discrete(ID *id, StructRNA *type, void *data)
#define UI_MENU_ARROW_SEP
ARegionRuntimeHandle * runtime
const char * name
Definition RNA_types.hh:661
void * first
struct MenuSearch_Data::@077371351132301160132362340024237271103065102221 context_menu_data
blender::Vector< std::reference_wrapper< MenuSearch_Item > > items
std::variant< OperatorData, PropertyData > data
std::optional< bContextStore > context
MenuSearch_Parent * self_as_parent
MenuTypeFlag flag
char label[BKE_ST_MAXNAME]
char idname[BKE_ST_MAXNAME]
char translation_context[BKE_ST_MAXNAME]
ListBase handlers
ListBase spacedata
blender::Vector< bContextStoreEntry > entries
ListBase areabase
float xmax
float xmin
float ymax
float ymin
float winmat[4][4]
blender::Vector< std::unique_ptr< uiBut > > buttons
std::string name
PropertyRNA * rnaprop
wmOperatorType * optype
float search_weight
uiBlock * block
PointerRNA * opptr
blender::wm::OpCallContext opcontext
const bContextStore * context
std::string drawstr
BIFIconID icon
PointerRNA rnapoin
uiBlock * block() const
void operator_context_set(blender::wm::OpCallContext opcontext)
void context_copy(const bContextStore *context)
int xy[2]
Definition WM_types.hh:761
struct wmEvent * eventstate
i
Definition text_draw.cc:230
void WM_operator_name_call_ptr_with_depends_on_cursor(bContext *C, wmOperatorType *ot, blender::wm::OpCallContext opcontext, PointerRNA *properties, const wmEvent *event, const StringRef drawstr)
bool WM_operator_poll(bContext *C, wmOperatorType *ot)
void WM_event_get_keymaps_from_handler(wmWindowManager *wm, wmWindow *win, wmEventHandler_Keymap *handler, wmEventHandler_KeymapResult *km_result)
@ WM_HANDLER_TYPE_KEYMAP
PointerRNA * ptr
Definition wm_files.cc:4238
wmOperatorType * ot
Definition wm_files.cc:4237
bool WM_keymap_poll(bContext *C, wmKeyMap *keymap)
Definition wm_keymap.cc:470
std::optional< std::string > WM_keymap_item_to_string(const wmKeyMapItem *kmi, const bool compact)
MenuType * WM_menutype_find(const StringRef idname, bool quiet)
bool WM_menutype_poll(bContext *C, MenuType *mt)
blender::Span< MenuType * > WM_menutypes_registered_get()
blender::Span< wmOperatorType * > WM_operatortypes_registered_get()
std::string WM_operatortype_name(wmOperatorType *ot, PointerRNA *properties)
size_t WM_operator_py_idname(char *dst, const char *src)
void WM_operator_properties_free(PointerRNA *ptr)
bScreen * WM_window_get_active_screen(const wmWindow *win)