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