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 ? &region->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(&region->uiblocks, sub_block);
779           }
780           UI_block_free(NULL, sub_block);
781         }
782       }
783       if (region) {
784         BLI_remlink(&region->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