1 /*
2 * This program is free software; you can redistribute it and/or
3 * modify it under the terms of the GNU General Public License
4 * as published by the Free Software Foundation; either version 2
5 * of the License, or (at your option) any later version.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 *
12 * You should have received a copy of the GNU General Public License
13 * along with this program; if not, write to the Free Software Foundation,
14 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
15 */
16
17 /** \file
18 * \ingroup edinterface
19 *
20 * Search available menu items via the user interface & key-maps.
21 * Accessed via the #WM_OT_search_menu operator.
22 */
23
24 #include <string.h>
25
26 #include "MEM_guardedalloc.h"
27
28 #include "DNA_action_types.h"
29 #include "DNA_gpencil_modifier_types.h"
30 #include "DNA_node_types.h"
31 #include "DNA_object_types.h"
32 #include "DNA_scene_types.h"
33 #include "DNA_shader_fx_types.h"
34 #include "DNA_texture_types.h"
35
36 #include "BLI_alloca.h"
37 #include "BLI_dynstr.h"
38 #include "BLI_ghash.h"
39 #include "BLI_linklist.h"
40 #include "BLI_listbase.h"
41 #include "BLI_math_matrix.h"
42 #include "BLI_memarena.h"
43 #include "BLI_string.h"
44 #include "BLI_string_search.h"
45 #include "BLI_string_utils.h"
46 #include "BLI_utildefines.h"
47
48 #include "BLT_translation.h"
49
50 #include "BKE_context.h"
51 #include "BKE_global.h"
52 #include "BKE_screen.h"
53
54 #include "ED_screen.h"
55
56 #include "RNA_access.h"
57
58 #include "WM_api.h"
59 #include "WM_types.h"
60
61 #include "UI_interface.h"
62 #include "interface_intern.h"
63
64 /* For key-map item access. */
65 #include "wm_event_system.h"
66
67 /* -------------------------------------------------------------------- */
68 /** \name Menu Search Template Implementation
69 * \{ */
70
71 /* Unicode arrow. */
72 #define MENU_SEP "\xe2\x96\xb6"
73
74 /**
75 * Use when #menu_items_from_ui_create is called with `include_all_areas`.
76 * so we can run the menu item in the area it was extracted from.
77 */
78 struct MenuSearch_Context {
79 /**
80 * Index into `Area.ui_type` #EnumPropertyItem or the top-bar when -1.
81 * Needed to get the display-name to use as a prefix for each menu item.
82 */
83 int space_type_ui_index;
84
85 ScrArea *area;
86 ARegion *region;
87 };
88
89 struct MenuSearch_Parent {
90 struct MenuSearch_Parent *parent;
91 MenuType *parent_mt;
92 const char *drawstr;
93
94 /** Set while writing menu items only. */
95 struct MenuSearch_Parent *temp_child;
96 };
97
98 struct MenuSearch_Item {
99 struct MenuSearch_Item *next, *prev;
100 const char *drawstr;
101 const char *drawwstr_full;
102 /** Support a single level sub-menu nesting (for operator buttons that expand). */
103 const char *drawstr_submenu;
104 int icon;
105 int state;
106
107 struct MenuSearch_Parent *menu_parent;
108 MenuType *mt;
109
110 enum {
111 MENU_SEARCH_TYPE_OP = 1,
112 MENU_SEARCH_TYPE_RNA = 2,
113 } type;
114
115 union {
116 /** Operator menu item. */
117 struct {
118 wmOperatorType *type;
119 PointerRNA *opptr;
120 short opcontext;
121 bContextStore *context;
122 } op;
123
124 /** Property (only for check-box/boolean). */
125 struct {
126 PointerRNA ptr;
127 PropertyRNA *prop;
128 int index;
129 /** Only for enum buttons. */
130 int enum_value;
131 } rna;
132 };
133
134 /** Set when we need each menu item to be able to set its own context. may be NULL. */
135 struct MenuSearch_Context *wm_context;
136 };
137
138 struct MenuSearch_Data {
139 /** MenuSearch_Item */
140 ListBase items;
141 /** Use for all small allocations. */
142 MemArena *memarena;
143
144 /** Use for context menu, to fake a button to create a context menu. */
145 struct {
146 uiBut but;
147 uiBlock block;
148 } context_menu_data;
149 };
150
menu_item_sort_by_drawstr_full(const void * menu_item_a_v,const void * menu_item_b_v)151 static int menu_item_sort_by_drawstr_full(const void *menu_item_a_v, const void *menu_item_b_v)
152 {
153 const struct MenuSearch_Item *menu_item_a = menu_item_a_v;
154 const struct MenuSearch_Item *menu_item_b = menu_item_b_v;
155 return strcmp(menu_item_a->drawwstr_full, menu_item_b->drawwstr_full);
156 }
157
strdup_memarena(MemArena * memarena,const char * str)158 static const char *strdup_memarena(MemArena *memarena, const char *str)
159 {
160 const uint str_size = strlen(str) + 1;
161 char *str_dst = BLI_memarena_alloc(memarena, str_size);
162 memcpy(str_dst, str, str_size);
163 return str_dst;
164 }
165
strdup_memarena_from_dynstr(MemArena * memarena,DynStr * dyn_str)166 static const char *strdup_memarena_from_dynstr(MemArena *memarena, DynStr *dyn_str)
167 {
168 const uint str_size = BLI_dynstr_get_len(dyn_str) + 1;
169 char *str_dst = BLI_memarena_alloc(memarena, str_size);
170 BLI_dynstr_get_cstring_ex(dyn_str, str_dst);
171 return str_dst;
172 }
173
menu_items_from_ui_create_item_from_button(struct MenuSearch_Data * data,MemArena * memarena,struct MenuType * mt,const char * drawstr_submenu,uiBut * but,struct MenuSearch_Context * wm_context)174 static bool menu_items_from_ui_create_item_from_button(struct MenuSearch_Data *data,
175 MemArena *memarena,
176 struct MenuType *mt,
177 const char *drawstr_submenu,
178 uiBut *but,
179 struct MenuSearch_Context *wm_context)
180 {
181 struct MenuSearch_Item *item = NULL;
182
183 /* Use override if the name is empty, this can happen with popovers. */
184 const char *drawstr_override = NULL;
185 const char *drawstr_sep = (but->flag & UI_BUT_HAS_SEP_CHAR) ?
186 strrchr(but->drawstr, UI_SEP_CHAR) :
187 NULL;
188 const bool drawstr_is_empty = (drawstr_sep == but->drawstr) || (but->drawstr[0] == '\0');
189
190 if (but->optype != NULL) {
191 if (drawstr_is_empty) {
192 drawstr_override = WM_operatortype_name(but->optype, but->opptr);
193 }
194
195 item = BLI_memarena_calloc(memarena, sizeof(*item));
196 item->type = MENU_SEARCH_TYPE_OP;
197
198 item->op.type = but->optype;
199 item->op.opcontext = but->opcontext;
200 item->op.context = but->context;
201 item->op.opptr = but->opptr;
202 but->opptr = NULL;
203 }
204 else if (but->rnaprop != NULL) {
205 const int prop_type = RNA_property_type(but->rnaprop);
206
207 if (drawstr_is_empty) {
208 if (prop_type == PROP_ENUM) {
209 const int value_enum = (int)but->hardmax;
210 EnumPropertyItem enum_item;
211 if (RNA_property_enum_item_from_value_gettexted(
212 but->block->evil_C, &but->rnapoin, but->rnaprop, value_enum, &enum_item)) {
213 drawstr_override = enum_item.name;
214 }
215 else {
216 /* Should never happen. */
217 drawstr_override = "Unknown";
218 }
219 }
220 else {
221 drawstr_override = RNA_property_ui_name(but->rnaprop);
222 }
223 }
224
225 if (!ELEM(prop_type, PROP_BOOLEAN, PROP_ENUM)) {
226 /* Note that these buttons are not prevented,
227 * but aren't typically used in menus. */
228 printf("Button '%s' in menu '%s' is a menu item with unsupported RNA type %d\n",
229 but->drawstr,
230 mt->idname,
231 prop_type);
232 }
233 else {
234 item = BLI_memarena_calloc(memarena, sizeof(*item));
235 item->type = MENU_SEARCH_TYPE_RNA;
236
237 item->rna.ptr = but->rnapoin;
238 item->rna.prop = but->rnaprop;
239 item->rna.index = but->rnaindex;
240
241 if (prop_type == PROP_ENUM) {
242 item->rna.enum_value = (int)but->hardmax;
243 }
244 }
245 }
246
247 if (item != NULL) {
248 /* Handle shared settings. */
249 if (drawstr_override != NULL) {
250 const char *drawstr_suffix = drawstr_sep ? drawstr_sep : "";
251 char *drawstr_alloc = BLI_string_joinN("(", drawstr_override, ")", drawstr_suffix);
252 item->drawstr = strdup_memarena(memarena, drawstr_alloc);
253 MEM_freeN(drawstr_alloc);
254 }
255 else {
256 item->drawstr = strdup_memarena(memarena, but->drawstr);
257 }
258
259 item->icon = ui_but_icon(but);
260 item->state = (but->flag &
261 (UI_BUT_DISABLED | UI_BUT_INACTIVE | UI_BUT_REDALERT | UI_BUT_HAS_SEP_CHAR));
262 item->mt = mt;
263 item->drawstr_submenu = drawstr_submenu ? strdup_memarena(memarena, drawstr_submenu) : NULL;
264
265 item->wm_context = wm_context;
266
267 BLI_addtail(&data->items, item);
268 return true;
269 }
270
271 return false;
272 }
273
274 /**
275 * Populate a fake button from a menu item (use for context menu).
276 */
menu_items_to_ui_button(struct MenuSearch_Item * item,uiBut * but)277 static bool menu_items_to_ui_button(struct MenuSearch_Item *item, uiBut *but)
278 {
279 bool changed = false;
280 switch (item->type) {
281 case MENU_SEARCH_TYPE_OP: {
282 but->optype = item->op.type;
283 but->opcontext = item->op.opcontext;
284 but->context = item->op.context;
285 but->opptr = item->op.opptr;
286 changed = true;
287 break;
288 }
289 case MENU_SEARCH_TYPE_RNA: {
290 const int prop_type = RNA_property_type(item->rna.prop);
291
292 but->rnapoin = item->rna.ptr;
293 but->rnaprop = item->rna.prop;
294 but->rnaindex = item->rna.index;
295
296 if (prop_type == PROP_ENUM) {
297 but->hardmax = item->rna.enum_value;
298 }
299 changed = true;
300 break;
301 }
302 }
303
304 if (changed) {
305 STRNCPY(but->drawstr, item->drawstr);
306 char *drawstr_sep = (item->state & UI_BUT_HAS_SEP_CHAR) ? strrchr(but->drawstr, UI_SEP_CHAR) :
307 NULL;
308 if (drawstr_sep) {
309 *drawstr_sep = '\0';
310 }
311
312 but->icon = item->icon;
313 but->str = but->strdata;
314 }
315
316 return changed;
317 }
318
319 /**
320 * Populate \a menu_stack with menus from inspecting active key-maps for this context.
321 */
menu_types_add_from_keymap_items(bContext * C,wmWindow * win,ScrArea * area,ARegion * region,LinkNode ** menuid_stack_p,GHash * menu_to_kmi,GSet * menu_tagged)322 static void menu_types_add_from_keymap_items(bContext *C,
323 wmWindow *win,
324 ScrArea *area,
325 ARegion *region,
326 LinkNode **menuid_stack_p,
327 GHash *menu_to_kmi,
328 GSet *menu_tagged)
329 {
330 wmWindowManager *wm = CTX_wm_manager(C);
331 ListBase *handlers[] = {
332 region ? ®ion->handlers : NULL,
333 area ? &area->handlers : NULL,
334 &win->handlers,
335 };
336
337 for (int handler_index = 0; handler_index < ARRAY_SIZE(handlers); handler_index++) {
338 if (handlers[handler_index] == NULL) {
339 continue;
340 }
341 LISTBASE_FOREACH (wmEventHandler *, handler_base, handlers[handler_index]) {
342 /* During this loop, UI handlers for nested menus can tag multiple handlers free. */
343 if (handler_base->flag & WM_HANDLER_DO_FREE) {
344 continue;
345 }
346 if (handler_base->type != WM_HANDLER_TYPE_KEYMAP) {
347 continue;
348 }
349
350 if (handler_base->poll == NULL || handler_base->poll(region, win->eventstate)) {
351 wmEventHandler_Keymap *handler = (wmEventHandler_Keymap *)handler_base;
352 wmKeyMap *keymap = WM_event_get_keymap_from_handler(wm, handler);
353 if (keymap && WM_keymap_poll(C, keymap)) {
354 LISTBASE_FOREACH (wmKeyMapItem *, kmi, &keymap->items) {
355 if (kmi->flag & KMI_INACTIVE) {
356 continue;
357 }
358 if (STR_ELEM(kmi->idname, "WM_OT_call_menu", "WM_OT_call_menu_pie")) {
359 char menu_idname[MAX_NAME];
360 RNA_string_get(kmi->ptr, "name", menu_idname);
361 MenuType *mt = WM_menutype_find(menu_idname, false);
362
363 if (mt && BLI_gset_add(menu_tagged, mt)) {
364 /* Unlikely, but possible this will be included twice. */
365 BLI_linklist_prepend(menuid_stack_p, mt);
366
367 void **kmi_p;
368 if (!BLI_ghash_ensure_p(menu_to_kmi, mt, &kmi_p)) {
369 *kmi_p = kmi;
370 }
371 }
372 }
373 }
374 }
375 }
376 }
377 }
378 }
379
380 /**
381 * Display all operators (last). Developer-only convenience feature.
382 */
menu_items_from_all_operators(bContext * C,struct MenuSearch_Data * data)383 static void menu_items_from_all_operators(bContext *C, struct MenuSearch_Data *data)
384 {
385 /* Add to temporary list so we can sort them separately. */
386 ListBase operator_items = {NULL, NULL};
387
388 MemArena *memarena = data->memarena;
389 GHashIterator iter;
390 for (WM_operatortype_iter(&iter); !BLI_ghashIterator_done(&iter);
391 BLI_ghashIterator_step(&iter)) {
392 wmOperatorType *ot = BLI_ghashIterator_getValue(&iter);
393
394 if ((ot->flag & OPTYPE_INTERNAL) && (G.debug & G_DEBUG_WM) == 0) {
395 continue;
396 }
397
398 if (WM_operator_poll((bContext *)C, ot)) {
399 const char *ot_ui_name = CTX_IFACE_(ot->translation_context, ot->name);
400
401 struct MenuSearch_Item *item = NULL;
402 item = BLI_memarena_calloc(memarena, sizeof(*item));
403 item->type = MENU_SEARCH_TYPE_OP;
404
405 item->op.type = ot;
406 item->op.opcontext = WM_OP_INVOKE_DEFAULT;
407 item->op.context = NULL;
408
409 char idname_as_py[OP_MAX_TYPENAME];
410 char uiname[256];
411 WM_operator_py_idname(idname_as_py, ot->idname);
412
413 SNPRINTF(uiname, "%s " MENU_SEP "%s", idname_as_py, ot_ui_name);
414
415 item->drawwstr_full = strdup_memarena(memarena, uiname);
416 item->drawstr = ot_ui_name;
417
418 item->wm_context = NULL;
419
420 BLI_addtail(&operator_items, item);
421 }
422 }
423
424 BLI_listbase_sort(&operator_items, menu_item_sort_by_drawstr_full);
425
426 BLI_movelisttolist(&data->items, &operator_items);
427 }
428
429 /**
430 * Create #MenuSearch_Data by inspecting the current context, this uses two methods:
431 *
432 * - Look-up pre-defined editor-menus.
433 * - Look-up key-map items which call menus.
434 */
menu_items_from_ui_create(bContext * C,wmWindow * win,ScrArea * area_init,ARegion * region_init,bool include_all_areas)435 static struct MenuSearch_Data *menu_items_from_ui_create(
436 bContext *C, wmWindow *win, ScrArea *area_init, ARegion *region_init, bool include_all_areas)
437 {
438 MemArena *memarena = BLI_memarena_new(BLI_MEMARENA_STD_BUFSIZE, __func__);
439 /** Map (#MenuType to #MenuSearch_Parent) */
440 GHash *menu_parent_map = BLI_ghash_ptr_new(__func__);
441 GHash *menu_display_name_map = BLI_ghash_ptr_new(__func__);
442 const uiStyle *style = UI_style_get_dpi();
443
444 /* Convert into non-ui structure. */
445 struct MenuSearch_Data *data = MEM_callocN(sizeof(*data), __func__);
446
447 DynStr *dyn_str = BLI_dynstr_new_memarena();
448
449 /* Use a stack of menus to handle and discover new menus in passes. */
450 LinkNode *menu_stack = NULL;
451
452 /* Tag menu types not to add, either because they have already been added
453 * or they have been blacklisted.
454 * Set of #MenuType. */
455 GSet *menu_tagged = BLI_gset_ptr_new(__func__);
456 /** Map (#MenuType -> #wmKeyMapItem). */
457 GHash *menu_to_kmi = BLI_ghash_ptr_new(__func__);
458
459 /* Blacklist menus we don't want to show. */
460 {
461 const char *idname_array[] = {
462 /* While we could include this, it's just showing filenames to load. */
463 "TOPBAR_MT_file_open_recent",
464 };
465 for (int i = 0; i < ARRAY_SIZE(idname_array); i++) {
466 MenuType *mt = WM_menutype_find(idname_array[i], false);
467 if (mt != NULL) {
468 BLI_gset_add(menu_tagged, mt);
469 }
470 }
471 }
472
473 {
474 /* Exclude context menus because:
475 * - The menu items are available elsewhere (and will show up multiple times).
476 * - Menu items depend on exact context, making search results unpredictable
477 * (exact number of items selected for example). See design doc T74158.
478 * There is one exception,
479 * as the outliner only exposes functionality via the context menu. */
480 GHashIterator iter;
481
482 for (WM_menutype_iter(&iter); (!BLI_ghashIterator_done(&iter));
483 (BLI_ghashIterator_step(&iter))) {
484 MenuType *mt = BLI_ghashIterator_getValue(&iter);
485 if (BLI_str_endswith(mt->idname, "_context_menu")) {
486 BLI_gset_add(menu_tagged, mt);
487 }
488 }
489 const char *idname_array[] = {
490 /* Add back some context menus. */
491 "OUTLINER_MT_context_menu",
492 };
493 for (int i = 0; i < ARRAY_SIZE(idname_array); i++) {
494 MenuType *mt = WM_menutype_find(idname_array[i], false);
495 if (mt != NULL) {
496 BLI_gset_remove(menu_tagged, mt, NULL);
497 }
498 }
499 }
500
501 /* Collect contexts, one for each 'ui_type'. */
502 struct MenuSearch_Context *wm_contexts = NULL;
503
504 const EnumPropertyItem *space_type_ui_items = NULL;
505 int space_type_ui_items_len = 0;
506 bool space_type_ui_items_free = false;
507
508 /* Text used as prefix for top-bar menu items. */
509 const char *global_menu_prefix = NULL;
510
511 if (include_all_areas) {
512 /* First create arrays for ui_type. */
513 PropertyRNA *prop_ui_type = NULL;
514 {
515 PointerRNA ptr;
516 RNA_pointer_create(NULL, &RNA_Area, NULL, &ptr);
517 prop_ui_type = RNA_struct_find_property(&ptr, "ui_type");
518 RNA_property_enum_items(C,
519 &ptr,
520 prop_ui_type,
521 &space_type_ui_items,
522 &space_type_ui_items_len,
523 &space_type_ui_items_free);
524
525 wm_contexts = BLI_memarena_calloc(memarena, sizeof(*wm_contexts) * space_type_ui_items_len);
526 for (int i = 0; i < space_type_ui_items_len; i++) {
527 wm_contexts[i].space_type_ui_index = -1;
528 }
529 }
530
531 bScreen *screen = WM_window_get_active_screen(win);
532 LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) {
533 ARegion *region = BKE_area_find_region_type(area, RGN_TYPE_WINDOW);
534 if (region != NULL) {
535 PointerRNA ptr;
536 RNA_pointer_create(&screen->id, &RNA_Area, area, &ptr);
537 const int space_type_ui = RNA_property_enum_get(&ptr, prop_ui_type);
538
539 const int space_type_ui_index = RNA_enum_from_value(space_type_ui_items, space_type_ui);
540 if (space_type_ui_index == -1) {
541 continue;
542 }
543
544 if (wm_contexts[space_type_ui_index].space_type_ui_index != -1) {
545 ScrArea *area_best = wm_contexts[space_type_ui_index].area;
546 const uint value_best = (uint)area_best->winx * (uint)area_best->winy;
547 const uint value_test = (uint)area->winx * (uint)area->winy;
548 if (value_best > value_test) {
549 continue;
550 }
551 }
552
553 wm_contexts[space_type_ui_index].space_type_ui_index = space_type_ui_index;
554 wm_contexts[space_type_ui_index].area = area;
555 wm_contexts[space_type_ui_index].region = region;
556 }
557 }
558
559 global_menu_prefix = CTX_IFACE_(RNA_property_translation_context(prop_ui_type), "Top Bar");
560 }
561
562 GHashIterator iter;
563
564 for (int space_type_ui_index = -1; space_type_ui_index < space_type_ui_items_len;
565 space_type_ui_index += 1) {
566
567 ScrArea *area = NULL;
568 ARegion *region = NULL;
569 struct MenuSearch_Context *wm_context = NULL;
570
571 if (include_all_areas) {
572 if (space_type_ui_index == -1) {
573 /* First run without any context, to populate the top-bar without. */
574 wm_context = NULL;
575 area = NULL;
576 region = NULL;
577 }
578 else {
579 wm_context = &wm_contexts[space_type_ui_index];
580 if (wm_context->space_type_ui_index == -1) {
581 continue;
582 }
583
584 area = wm_context->area;
585 region = wm_context->region;
586
587 CTX_wm_area_set(C, area);
588 CTX_wm_region_set(C, region);
589 }
590 }
591 else {
592 area = area_init;
593 region = region_init;
594 }
595
596 /* Populate menus from the editors,
597 * note that we could create a fake header, draw the header and extract the menus
598 * from the buttons, however this is quite involved and can be avoided as by convention
599 * each space-type has a single root-menu that headers use. */
600 {
601 const char *idname_array[2] = {NULL};
602 int idname_array_len = 0;
603
604 /* Use negative for global (no area) context, populate the top-bar. */
605 if (space_type_ui_index == -1) {
606 idname_array[idname_array_len++] = "TOPBAR_MT_editor_menus";
607 }
608
609 #define SPACE_MENU_MAP(space_type, menu_id) \
610 case space_type: \
611 idname_array[idname_array_len++] = menu_id; \
612 break
613 #define SPACE_MENU_NOP(space_type) \
614 case space_type: \
615 break
616
617 if (area != NULL) {
618 SpaceLink *sl = area->spacedata.first;
619 switch ((eSpace_Type)area->spacetype) {
620 SPACE_MENU_MAP(SPACE_VIEW3D, "VIEW3D_MT_editor_menus");
621 SPACE_MENU_MAP(SPACE_GRAPH, "GRAPH_MT_editor_menus");
622 SPACE_MENU_MAP(SPACE_OUTLINER, "OUTLINER_MT_editor_menus");
623 SPACE_MENU_NOP(SPACE_PROPERTIES);
624 SPACE_MENU_MAP(SPACE_FILE, "FILEBROWSER_MT_editor_menus");
625 SPACE_MENU_MAP(SPACE_IMAGE, "IMAGE_MT_editor_menus");
626 SPACE_MENU_MAP(SPACE_INFO, "INFO_MT_editor_menus");
627 SPACE_MENU_MAP(SPACE_SEQ, "SEQUENCER_MT_editor_menus");
628 SPACE_MENU_MAP(SPACE_TEXT, "TEXT_MT_editor_menus");
629 SPACE_MENU_MAP(SPACE_ACTION,
630 (((const SpaceAction *)sl)->mode == SACTCONT_TIMELINE) ?
631 "TIME_MT_editor_menus" :
632 "DOPESHEET_MT_editor_menus");
633 SPACE_MENU_MAP(SPACE_NLA, "NLA_MT_editor_menus");
634 SPACE_MENU_MAP(SPACE_NODE, "NODE_MT_editor_menus");
635 SPACE_MENU_MAP(SPACE_CONSOLE, "CONSOLE_MT_editor_menus");
636 SPACE_MENU_MAP(SPACE_USERPREF, "USERPREF_MT_editor_menus");
637 SPACE_MENU_MAP(SPACE_CLIP,
638 (((const SpaceClip *)sl)->mode == SC_MODE_TRACKING) ?
639 "CLIP_MT_tracking_editor_menus" :
640 "CLIP_MT_masking_editor_menus");
641 SPACE_MENU_NOP(SPACE_EMPTY);
642 SPACE_MENU_NOP(SPACE_SCRIPT);
643 SPACE_MENU_NOP(SPACE_STATUSBAR);
644 SPACE_MENU_NOP(SPACE_TOPBAR);
645 }
646 }
647 for (int i = 0; i < idname_array_len; i++) {
648 MenuType *mt = WM_menutype_find(idname_array[i], false);
649 if (mt != NULL) {
650 /* Check if this exists because of 'include_all_areas'. */
651 if (BLI_gset_add(menu_tagged, mt)) {
652 BLI_linklist_prepend(&menu_stack, mt);
653 }
654 }
655 }
656 }
657 #undef SPACE_MENU_MAP
658 #undef SPACE_MENU_NOP
659
660 bool has_keymap_menu_items = false;
661
662 while (menu_stack != NULL) {
663 MenuType *mt = BLI_linklist_pop(&menu_stack);
664 if (!WM_menutype_poll(C, mt)) {
665 continue;
666 }
667
668 uiBlock *block = UI_block_begin(C, region, __func__, UI_EMBOSS);
669 uiLayout *layout = UI_block_layout(
670 block, UI_LAYOUT_VERTICAL, UI_LAYOUT_MENU, 0, 0, 200, 0, UI_MENU_PADDING, style);
671
672 UI_block_flag_enable(block, UI_BLOCK_SHOW_SHORTCUT_ALWAYS);
673
674 uiLayoutSetOperatorContext(layout, WM_OP_INVOKE_REGION_WIN);
675 UI_menutype_draw(C, mt, layout);
676
677 UI_block_end(C, block);
678
679 LISTBASE_FOREACH (uiBut *, but, &block->buttons) {
680 MenuType *mt_from_but = NULL;
681 /* Support menu titles with dynamic from initial labels
682 * (used by edit-mesh context menu). */
683 if (but->type == UI_BTYPE_LABEL) {
684
685 /* Check if the label is the title. */
686 uiBut *but_test = but->prev;
687 while (but_test && but_test->type == UI_BTYPE_SEPR) {
688 but_test = but_test->prev;
689 }
690
691 if (but_test == NULL) {
692 BLI_ghash_insert(
693 menu_display_name_map, mt, (void *)strdup_memarena(memarena, but->drawstr));
694 }
695 }
696 else if (menu_items_from_ui_create_item_from_button(
697 data, memarena, mt, NULL, but, wm_context)) {
698 /* pass */
699 }
700 else if ((mt_from_but = UI_but_menutype_get(but))) {
701
702 if (BLI_gset_add(menu_tagged, mt_from_but)) {
703 BLI_linklist_prepend(&menu_stack, mt_from_but);
704 }
705
706 if (!BLI_ghash_haskey(menu_parent_map, mt_from_but)) {
707 struct MenuSearch_Parent *menu_parent = BLI_memarena_calloc(memarena,
708 sizeof(*menu_parent));
709 /* Use brackets for menu key shortcuts,
710 * converting "Text|Some-Shortcut" to "Text (Some-Shortcut)".
711 * This is needed so we don't right align sub-menu contents
712 * we only want to do that for the last menu item, not the path that leads to it.
713 */
714 const char *drawstr_sep = but->flag & UI_BUT_HAS_SEP_CHAR ?
715 strrchr(but->drawstr, UI_SEP_CHAR) :
716 NULL;
717 bool drawstr_is_empty = false;
718 if (drawstr_sep != NULL) {
719 BLI_assert(BLI_dynstr_get_len(dyn_str) == 0);
720 /* Detect empty string, fallback to menu name. */
721 const char *drawstr = but->drawstr;
722 int drawstr_len = drawstr_sep - but->drawstr;
723 if (UNLIKELY(drawstr_len == 0)) {
724 drawstr = CTX_IFACE_(mt_from_but->translation_context, mt_from_but->label);
725 drawstr_len = strlen(drawstr);
726 if (drawstr[0] == '\0') {
727 drawstr_is_empty = true;
728 }
729 }
730 BLI_dynstr_nappend(dyn_str, drawstr, drawstr_len);
731 BLI_dynstr_appendf(dyn_str, " (%s)", drawstr_sep + 1);
732 menu_parent->drawstr = strdup_memarena_from_dynstr(memarena, dyn_str);
733 BLI_dynstr_clear(dyn_str);
734 }
735 else {
736 const char *drawstr = but->drawstr;
737 if (UNLIKELY(drawstr[0] == '\0')) {
738 drawstr = CTX_IFACE_(mt_from_but->translation_context, mt_from_but->label);
739 if (drawstr[0] == '\0') {
740 drawstr_is_empty = true;
741 }
742 }
743 menu_parent->drawstr = strdup_memarena(memarena, drawstr);
744 }
745 menu_parent->parent_mt = mt;
746 BLI_ghash_insert(menu_parent_map, mt_from_but, menu_parent);
747
748 if (drawstr_is_empty) {
749 printf("Warning: '%s' menu has empty 'bl_label'.\n", mt_from_but->idname);
750 }
751 }
752 }
753 else if (but->menu_create_func != NULL) {
754 /* A non 'MenuType' menu button. */
755
756 /* Only expand one level deep, this is mainly for expanding operator menus. */
757 const char *drawstr_submenu = but->drawstr;
758
759 /* +1 to avoid overlap with the current 'block'. */
760 uiBlock *sub_block = UI_block_begin(C, region, __func__ + 1, UI_EMBOSS);
761 uiLayout *sub_layout = UI_block_layout(
762 sub_block, UI_LAYOUT_VERTICAL, UI_LAYOUT_MENU, 0, 0, 200, 0, UI_MENU_PADDING, style);
763
764 UI_block_flag_enable(sub_block, UI_BLOCK_SHOW_SHORTCUT_ALWAYS);
765
766 uiLayoutSetOperatorContext(sub_layout, WM_OP_INVOKE_REGION_WIN);
767
768 but->menu_create_func(C, sub_layout, but->poin);
769
770 UI_block_end(C, sub_block);
771
772 LISTBASE_FOREACH (uiBut *, sub_but, &sub_block->buttons) {
773 menu_items_from_ui_create_item_from_button(
774 data, memarena, mt, drawstr_submenu, sub_but, wm_context);
775 }
776
777 if (region) {
778 BLI_remlink(®ion->uiblocks, sub_block);
779 }
780 UI_block_free(NULL, sub_block);
781 }
782 }
783 if (region) {
784 BLI_remlink(®ion->uiblocks, block);
785 }
786 UI_block_free(NULL, block);
787
788 /* Add key-map items as a second pass,
789 * so all menus are accessed from the header & top-bar before key shortcuts are expanded. */
790 if ((menu_stack == NULL) && (has_keymap_menu_items == false)) {
791 has_keymap_menu_items = true;
792 menu_types_add_from_keymap_items(
793 C, win, area, region, &menu_stack, menu_to_kmi, menu_tagged);
794 }
795 }
796 }
797
798 LISTBASE_FOREACH (struct MenuSearch_Item *, item, &data->items) {
799 item->menu_parent = BLI_ghash_lookup(menu_parent_map, item->mt);
800 }
801
802 GHASH_ITER (iter, menu_parent_map) {
803 struct MenuSearch_Parent *menu_parent = BLI_ghashIterator_getValue(&iter);
804 menu_parent->parent = BLI_ghash_lookup(menu_parent_map, menu_parent->parent_mt);
805 }
806
807 /* NOTE: currently this builds the full path for each menu item,
808 * that could be moved into the parent menu. */
809
810 /* Set names as full paths. */
811 LISTBASE_FOREACH (struct MenuSearch_Item *, item, &data->items) {
812 BLI_assert(BLI_dynstr_get_len(dyn_str) == 0);
813
814 if (include_all_areas) {
815 BLI_dynstr_appendf(dyn_str,
816 "%s: ",
817 (item->wm_context != NULL) ?
818 space_type_ui_items[item->wm_context->space_type_ui_index].name :
819 global_menu_prefix);
820 }
821
822 if (item->menu_parent != NULL) {
823 struct MenuSearch_Parent *menu_parent = item->menu_parent;
824 menu_parent->temp_child = NULL;
825 while (menu_parent && menu_parent->parent) {
826 menu_parent->parent->temp_child = menu_parent;
827 menu_parent = menu_parent->parent;
828 }
829 while (menu_parent) {
830 BLI_dynstr_append(dyn_str, menu_parent->drawstr);
831 BLI_dynstr_append(dyn_str, " " MENU_SEP " ");
832 menu_parent = menu_parent->temp_child;
833 }
834 }
835 else {
836 const char *drawstr = BLI_ghash_lookup(menu_display_name_map, item->mt);
837 if (drawstr == NULL) {
838 drawstr = CTX_IFACE_(item->mt->translation_context, item->mt->label);
839 }
840 BLI_dynstr_append(dyn_str, drawstr);
841
842 wmKeyMapItem *kmi = BLI_ghash_lookup(menu_to_kmi, item->mt);
843 if (kmi != NULL) {
844 char kmi_str[128];
845 WM_keymap_item_to_string(kmi, false, kmi_str, sizeof(kmi_str));
846 BLI_dynstr_appendf(dyn_str, " (%s)", kmi_str);
847 }
848
849 BLI_dynstr_append(dyn_str, " " MENU_SEP " ");
850 }
851
852 /* Optional nested menu. */
853 if (item->drawstr_submenu != NULL) {
854 BLI_dynstr_append(dyn_str, item->drawstr_submenu);
855 BLI_dynstr_append(dyn_str, " " MENU_SEP " ");
856 }
857
858 BLI_dynstr_append(dyn_str, item->drawstr);
859
860 item->drawwstr_full = strdup_memarena_from_dynstr(memarena, dyn_str);
861 BLI_dynstr_clear(dyn_str);
862 }
863 BLI_dynstr_free(dyn_str);
864
865 /* Finally sort menu items.
866 *
867 * Note: we might want to keep the in-menu order, for now sort all. */
868 BLI_listbase_sort(&data->items, menu_item_sort_by_drawstr_full);
869
870 BLI_ghash_free(menu_parent_map, NULL, NULL);
871 BLI_ghash_free(menu_display_name_map, NULL, NULL);
872
873 BLI_ghash_free(menu_to_kmi, NULL, NULL);
874
875 BLI_gset_free(menu_tagged, NULL);
876
877 data->memarena = memarena;
878
879 if (include_all_areas) {
880 CTX_wm_area_set(C, area_init);
881 CTX_wm_region_set(C, region_init);
882
883 if (space_type_ui_items_free) {
884 MEM_freeN((void *)space_type_ui_items);
885 }
886 }
887
888 /* Include all operators for developers,
889 * since it can be handy to have a quick way to access any operator,
890 * including operators being developed which haven't yet been added into the interface.
891 *
892 * These are added after all menu items so developers still get normal behavior by default,
893 * unless searching for something that isn't already in a menu (or scroll down).
894 *
895 * Keep this behind a developer only check:
896 * - Many operators need options to be set to give useful results, see: T74157.
897 * - User who really prefer to list all operators can use #WM_OT_search_operator.
898 */
899 if (U.flag & USER_DEVELOPER_UI) {
900 menu_items_from_all_operators(C, data);
901 }
902
903 return data;
904 }
905
menu_search_arg_free_fn(void * data_v)906 static void menu_search_arg_free_fn(void *data_v)
907 {
908 struct MenuSearch_Data *data = data_v;
909 LISTBASE_FOREACH (struct MenuSearch_Item *, item, &data->items) {
910 switch (item->type) {
911 case MENU_SEARCH_TYPE_OP: {
912 if (item->op.opptr != NULL) {
913 WM_operator_properties_free(item->op.opptr);
914 MEM_freeN(item->op.opptr);
915 }
916 }
917 case MENU_SEARCH_TYPE_RNA: {
918 break;
919 }
920 }
921 }
922
923 BLI_memarena_free(data->memarena);
924
925 MEM_freeN(data);
926 }
927
menu_search_exec_fn(bContext * C,void * UNUSED (arg1),void * arg2)928 static void menu_search_exec_fn(bContext *C, void *UNUSED(arg1), void *arg2)
929 {
930 struct MenuSearch_Item *item = arg2;
931 if (item == NULL) {
932 return;
933 }
934 if (item->state & UI_BUT_DISABLED) {
935 return;
936 }
937
938 ScrArea *area_prev = CTX_wm_area(C);
939 ARegion *region_prev = CTX_wm_region(C);
940
941 if (item->wm_context != NULL) {
942 CTX_wm_area_set(C, item->wm_context->area);
943 CTX_wm_region_set(C, item->wm_context->region);
944 }
945
946 switch (item->type) {
947 case MENU_SEARCH_TYPE_OP: {
948 CTX_store_set(C, item->op.context);
949 WM_operator_name_call_ptr(C, item->op.type, item->op.opcontext, item->op.opptr);
950 CTX_store_set(C, NULL);
951 break;
952 }
953 case MENU_SEARCH_TYPE_RNA: {
954 PointerRNA *ptr = &item->rna.ptr;
955 PropertyRNA *prop = item->rna.prop;
956 const int index = item->rna.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, item->rna.enum_value);
974 changed = true;
975 }
976
977 if (changed) {
978 RNA_property_update(C, ptr, prop);
979 }
980 break;
981 }
982 }
983
984 if (item->wm_context != NULL) {
985 CTX_wm_area_set(C, area_prev);
986 CTX_wm_region_set(C, region_prev);
987 }
988 }
989
menu_search_update_fn(const bContext * UNUSED (C),void * arg,const char * str,uiSearchItems * items)990 static void menu_search_update_fn(const bContext *UNUSED(C),
991 void *arg,
992 const char *str,
993 uiSearchItems *items)
994 {
995 struct MenuSearch_Data *data = arg;
996
997 StringSearch *search = BLI_string_search_new();
998
999 LISTBASE_FOREACH (struct MenuSearch_Item *, item, &data->items) {
1000 BLI_string_search_add(search, item->drawwstr_full, item);
1001 }
1002
1003 struct MenuSearch_Item **filtered_items;
1004 int filtered_amount = BLI_string_search_query(search, str, (void ***)&filtered_items);
1005
1006 for (int i = 0; i < filtered_amount; i++) {
1007 struct MenuSearch_Item *item = filtered_items[i];
1008 if (!UI_search_item_add(items, item->drawwstr_full, item, item->icon, item->state, 0)) {
1009 break;
1010 }
1011 }
1012
1013 MEM_freeN(filtered_items);
1014 BLI_string_search_free(search);
1015 }
1016
1017 /** \} */
1018
1019 /* -------------------------------------------------------------------- */
1020 /** \name Context Menu
1021 *
1022 * This uses a fake button to create a context menu,
1023 * if this ever causes hard to solve bugs we may need to create
1024 * a separate context menu just for the search, however this is fairly involved.
1025 * \{ */
1026
ui_search_menu_create_context_menu(struct bContext * C,void * arg,void * active,const struct wmEvent * UNUSED (event))1027 static bool ui_search_menu_create_context_menu(struct bContext *C,
1028 void *arg,
1029 void *active,
1030 const struct wmEvent *UNUSED(event))
1031 {
1032 struct MenuSearch_Data *data = arg;
1033 struct MenuSearch_Item *item = active;
1034 bool has_menu = false;
1035
1036 memset(&data->context_menu_data, 0x0, sizeof(data->context_menu_data));
1037 uiBut *but = &data->context_menu_data.but;
1038 uiBlock *block = &data->context_menu_data.block;
1039
1040 but->block = block;
1041
1042 if (menu_items_to_ui_button(item, but)) {
1043 ScrArea *area_prev = CTX_wm_area(C);
1044 ARegion *region_prev = CTX_wm_region(C);
1045
1046 if (item->wm_context != NULL) {
1047 CTX_wm_area_set(C, item->wm_context->area);
1048 CTX_wm_region_set(C, item->wm_context->region);
1049 }
1050
1051 if (ui_popup_context_menu_for_button(C, but)) {
1052 has_menu = true;
1053 }
1054
1055 if (item->wm_context != NULL) {
1056 CTX_wm_area_set(C, area_prev);
1057 CTX_wm_region_set(C, region_prev);
1058 }
1059 }
1060
1061 return has_menu;
1062 }
1063
1064 /** \} */
1065
1066 /* -------------------------------------------------------------------- */
1067 /** \name Tooltip
1068 * \{ */
1069
ui_search_menu_create_tooltip(struct bContext * C,struct ARegion * region,void * arg,void * active)1070 static struct ARegion *ui_search_menu_create_tooltip(struct bContext *C,
1071 struct ARegion *region,
1072 void *arg,
1073 void *active)
1074 {
1075 struct MenuSearch_Data *data = arg;
1076 struct MenuSearch_Item *item = active;
1077
1078 memset(&data->context_menu_data, 0x0, sizeof(data->context_menu_data));
1079 uiBut *but = &data->context_menu_data.but;
1080 uiBlock *block = &data->context_menu_data.block;
1081 unit_m4(block->winmat);
1082 block->aspect = 1;
1083
1084 but->block = block;
1085
1086 /* Place the fake button at the cursor so the tool-tip is places properly. */
1087 float tip_init[2];
1088 const wmEvent *event = CTX_wm_window(C)->eventstate;
1089 tip_init[0] = event->x;
1090 tip_init[1] = event->y - (UI_UNIT_Y / 2);
1091 ui_window_to_block_fl(region, block, &tip_init[0], &tip_init[1]);
1092
1093 but->rect.xmin = tip_init[0];
1094 but->rect.xmax = tip_init[0];
1095 but->rect.ymin = tip_init[1];
1096 but->rect.ymax = tip_init[1];
1097
1098 if (menu_items_to_ui_button(item, but)) {
1099 ScrArea *area_prev = CTX_wm_area(C);
1100 ARegion *region_prev = CTX_wm_region(C);
1101
1102 if (item->wm_context != NULL) {
1103 CTX_wm_area_set(C, item->wm_context->area);
1104 CTX_wm_region_set(C, item->wm_context->region);
1105 }
1106
1107 ARegion *region_tip = UI_tooltip_create_from_button(C, region, but, false);
1108
1109 if (item->wm_context != NULL) {
1110 CTX_wm_area_set(C, area_prev);
1111 CTX_wm_region_set(C, region_prev);
1112 }
1113 return region_tip;
1114 }
1115
1116 return NULL;
1117 }
1118
1119 /** \} */
1120
1121 /* -------------------------------------------------------------------- */
1122 /** \name Menu Search Template Public API
1123 * \{ */
1124
UI_but_func_menu_search(uiBut * but)1125 void UI_but_func_menu_search(uiBut *but)
1126 {
1127 bContext *C = but->block->evil_C;
1128 wmWindow *win = CTX_wm_window(C);
1129 ScrArea *area = CTX_wm_area(C);
1130 ARegion *region = CTX_wm_region(C);
1131 /* When run from top-bar scan all areas in the current window. */
1132 const bool include_all_areas = (area && (area->spacetype == SPACE_TOPBAR));
1133 struct MenuSearch_Data *data = menu_items_from_ui_create(
1134 C, win, area, region, include_all_areas);
1135 UI_but_func_search_set(but,
1136 /* Generic callback. */
1137 ui_searchbox_create_menu,
1138 menu_search_update_fn,
1139 data,
1140 menu_search_arg_free_fn,
1141 menu_search_exec_fn,
1142 NULL);
1143
1144 UI_but_func_search_set_context_menu(but, ui_search_menu_create_context_menu);
1145 UI_but_func_search_set_tooltip(but, ui_search_menu_create_tooltip);
1146 UI_but_func_search_set_sep_string(but, MENU_SEP);
1147 }
1148
uiTemplateMenuSearch(uiLayout * layout)1149 void uiTemplateMenuSearch(uiLayout *layout)
1150 {
1151 uiBlock *block;
1152 uiBut *but;
1153 static char search[256] = "";
1154
1155 block = uiLayoutGetBlock(layout);
1156 UI_block_layout_set_current(block, layout);
1157
1158 but = uiDefSearchBut(
1159 block, search, 0, ICON_VIEWZOOM, sizeof(search), 0, 0, UI_UNIT_X * 6, UI_UNIT_Y, 0, 0, "");
1160 UI_but_func_menu_search(but);
1161 }
1162
1163 #undef MENU_SEP
1164
1165 /** \} */
1166