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  * Generic context popup menus.
21  */
22 
23 #include <string.h>
24 
25 #include "MEM_guardedalloc.h"
26 
27 #include "DNA_scene_types.h"
28 #include "DNA_screen_types.h"
29 
30 #include "BLI_path_util.h"
31 #include "BLI_string.h"
32 #include "BLI_utildefines.h"
33 
34 #include "BLT_translation.h"
35 
36 #include "BKE_addon.h"
37 #include "BKE_context.h"
38 #include "BKE_idprop.h"
39 #include "BKE_screen.h"
40 
41 #include "ED_keyframing.h"
42 #include "ED_screen.h"
43 
44 #include "UI_interface.h"
45 
46 #include "interface_intern.h"
47 
48 #include "RNA_access.h"
49 
50 #ifdef WITH_PYTHON
51 #  include "BPY_extern.h"
52 #  include "BPY_extern_run.h"
53 #endif
54 
55 #include "WM_api.h"
56 #include "WM_types.h"
57 
58 /* This hack is needed because we don't have a good way to
59  * re-reference keymap items once added: T42944 */
60 #define USE_KEYMAP_ADD_HACK
61 
62 /* -------------------------------------------------------------------- */
63 /** \name Button Context Menu
64  * \{ */
65 
shortcut_property_from_rna(bContext * C,uiBut * but)66 static IDProperty *shortcut_property_from_rna(bContext *C, uiBut *but)
67 {
68   /* Compute data path from context to property. */
69 
70   /* If this returns null, we won't be able to bind shortcuts to these RNA properties.
71    * Support can be added at #wm_context_member_from_ptr. */
72   const char *member_id = WM_context_member_from_ptr(C, &but->rnapoin);
73   if (member_id == NULL) {
74     return NULL;
75   }
76 
77   const char *data_path = RNA_path_from_ID_to_struct(&but->rnapoin);
78   const char *member_id_data_path = member_id;
79 
80   if (data_path) {
81     member_id_data_path = BLI_sprintfN("%s.%s", member_id, data_path);
82     MEM_freeN((void *)data_path);
83   }
84 
85   const char *prop_id = RNA_property_identifier(but->rnaprop);
86   const char *final_data_path = BLI_sprintfN("%s.%s", member_id_data_path, prop_id);
87 
88   if (member_id != member_id_data_path) {
89     MEM_freeN((void *)member_id_data_path);
90   }
91 
92   /* Create ID property of data path, to pass to the operator. */
93   IDProperty *prop;
94   const IDPropertyTemplate val = {0};
95   prop = IDP_New(IDP_GROUP, &val, __func__);
96   IDP_AddToGroup(prop, IDP_NewString(final_data_path, "data_path", strlen(final_data_path) + 1));
97 
98   MEM_freeN((void *)final_data_path);
99 
100   return prop;
101 }
102 
shortcut_get_operator_property(bContext * C,uiBut * but,IDProperty ** r_prop)103 static const char *shortcut_get_operator_property(bContext *C, uiBut *but, IDProperty **r_prop)
104 {
105   if (but->optype) {
106     /* Operator */
107     *r_prop = (but->opptr && but->opptr->data) ? IDP_CopyProperty(but->opptr->data) : NULL;
108     return but->optype->idname;
109   }
110 
111   if (but->rnaprop) {
112     const PropertyType rnaprop_type = RNA_property_type(but->rnaprop);
113 
114     if (rnaprop_type == PROP_BOOLEAN) {
115       /* Boolean */
116       *r_prop = shortcut_property_from_rna(C, but);
117       if (*r_prop == NULL) {
118         return NULL;
119       }
120       return "WM_OT_context_toggle";
121     }
122     if (rnaprop_type == PROP_ENUM) {
123       /* Enum */
124       *r_prop = shortcut_property_from_rna(C, but);
125       if (*r_prop == NULL) {
126         return NULL;
127       }
128       return "WM_OT_context_menu_enum";
129     }
130   }
131 
132   *r_prop = NULL;
133   return NULL;
134 }
135 
shortcut_free_operator_property(IDProperty * prop)136 static void shortcut_free_operator_property(IDProperty *prop)
137 {
138   if (prop) {
139     IDP_FreeProperty(prop);
140   }
141 }
142 
but_shortcut_name_func(bContext * C,void * arg1,int UNUSED (event))143 static void but_shortcut_name_func(bContext *C, void *arg1, int UNUSED(event))
144 {
145   uiBut *but = (uiBut *)arg1;
146   char shortcut_str[128];
147 
148   IDProperty *prop;
149   const char *idname = shortcut_get_operator_property(C, but, &prop);
150   if (idname == NULL) {
151     return;
152   }
153 
154   /* complex code to change name of button */
155   if (WM_key_event_operator_string(
156           C, idname, but->opcontext, prop, true, shortcut_str, sizeof(shortcut_str))) {
157     ui_but_add_shortcut(but, shortcut_str, true);
158   }
159   else {
160     /* simply strip the shortcut */
161     ui_but_add_shortcut(but, NULL, true);
162   }
163 
164   shortcut_free_operator_property(prop);
165 }
166 
menu_change_shortcut(bContext * C,ARegion * region,void * arg)167 static uiBlock *menu_change_shortcut(bContext *C, ARegion *region, void *arg)
168 {
169   wmWindowManager *wm = CTX_wm_manager(C);
170   uiBlock *block;
171   uiBut *but = (uiBut *)arg;
172   wmKeyMap *km;
173   wmKeyMapItem *kmi;
174   PointerRNA ptr;
175   uiLayout *layout;
176   const uiStyle *style = UI_style_get_dpi();
177   IDProperty *prop;
178   const char *idname = shortcut_get_operator_property(C, but, &prop);
179 
180   kmi = WM_key_event_operator(C,
181                               idname,
182                               but->opcontext,
183                               prop,
184                               EVT_TYPE_MASK_HOTKEY_INCLUDE,
185                               EVT_TYPE_MASK_HOTKEY_EXCLUDE,
186                               &km);
187   U.runtime.is_dirty = true;
188 
189   BLI_assert(kmi != NULL);
190 
191   RNA_pointer_create(&wm->id, &RNA_KeyMapItem, kmi, &ptr);
192 
193   block = UI_block_begin(C, region, "_popup", UI_EMBOSS);
194   UI_block_func_handle_set(block, but_shortcut_name_func, but);
195   UI_block_flag_enable(block, UI_BLOCK_MOVEMOUSE_QUIT);
196   UI_block_direction_set(block, UI_DIR_CENTER_Y);
197 
198   layout = UI_block_layout(block,
199                            UI_LAYOUT_VERTICAL,
200                            UI_LAYOUT_PANEL,
201                            0,
202                            0,
203                            U.widget_unit * 10,
204                            U.widget_unit * 2,
205                            0,
206                            style);
207 
208   uiItemL(layout, CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Change Shortcut"), ICON_HAND);
209   uiItemR(layout, &ptr, "type", UI_ITEM_R_FULL_EVENT | UI_ITEM_R_IMMEDIATE, "", ICON_NONE);
210 
211   UI_block_bounds_set_popup(
212       block, 6 * U.dpi_fac, (const int[2]){-100 * U.dpi_fac, 36 * U.dpi_fac});
213 
214   shortcut_free_operator_property(prop);
215 
216   return block;
217 }
218 
219 #ifdef USE_KEYMAP_ADD_HACK
220 static int g_kmi_id_hack;
221 #endif
222 
menu_add_shortcut(bContext * C,ARegion * region,void * arg)223 static uiBlock *menu_add_shortcut(bContext *C, ARegion *region, void *arg)
224 {
225   wmWindowManager *wm = CTX_wm_manager(C);
226   uiBlock *block;
227   uiBut *but = (uiBut *)arg;
228   wmKeyMap *km;
229   wmKeyMapItem *kmi;
230   PointerRNA ptr;
231   uiLayout *layout;
232   const uiStyle *style = UI_style_get_dpi();
233   int kmi_id;
234   IDProperty *prop;
235   const char *idname = shortcut_get_operator_property(C, but, &prop);
236 
237   /* XXX this guess_opname can potentially return a different keymap
238    * than being found on adding later... */
239   km = WM_keymap_guess_opname(C, idname);
240   kmi = WM_keymap_add_item(km, idname, EVT_AKEY, KM_PRESS, 0, 0);
241   kmi_id = kmi->id;
242 
243   /* This takes ownership of prop, or prop can be NULL for reset. */
244   WM_keymap_item_properties_reset(kmi, prop);
245 
246   /* update and get pointers again */
247   WM_keyconfig_update(wm);
248   U.runtime.is_dirty = true;
249 
250   km = WM_keymap_guess_opname(C, idname);
251   kmi = WM_keymap_item_find_id(km, kmi_id);
252 
253   RNA_pointer_create(&wm->id, &RNA_KeyMapItem, kmi, &ptr);
254 
255   block = UI_block_begin(C, region, "_popup", UI_EMBOSS);
256   UI_block_func_handle_set(block, but_shortcut_name_func, but);
257   UI_block_direction_set(block, UI_DIR_CENTER_Y);
258 
259   layout = UI_block_layout(block,
260                            UI_LAYOUT_VERTICAL,
261                            UI_LAYOUT_PANEL,
262                            0,
263                            0,
264                            U.widget_unit * 10,
265                            U.widget_unit * 2,
266                            0,
267                            style);
268 
269   uiItemL(layout, CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Assign Shortcut"), ICON_HAND);
270   uiItemR(layout, &ptr, "type", UI_ITEM_R_FULL_EVENT | UI_ITEM_R_IMMEDIATE, "", ICON_NONE);
271 
272   UI_block_bounds_set_popup(
273       block, 6 * U.dpi_fac, (const int[2]){-100 * U.dpi_fac, 36 * U.dpi_fac});
274 
275 #ifdef USE_KEYMAP_ADD_HACK
276   g_kmi_id_hack = kmi_id;
277 #endif
278 
279   return block;
280 }
281 
menu_add_shortcut_cancel(struct bContext * C,void * arg1)282 static void menu_add_shortcut_cancel(struct bContext *C, void *arg1)
283 {
284   uiBut *but = (uiBut *)arg1;
285   wmKeyMap *km;
286   wmKeyMapItem *kmi;
287   int kmi_id;
288 
289   IDProperty *prop;
290   const char *idname = shortcut_get_operator_property(C, but, &prop);
291 
292 #ifdef USE_KEYMAP_ADD_HACK
293   km = WM_keymap_guess_opname(C, idname);
294   kmi_id = g_kmi_id_hack;
295   UNUSED_VARS(but);
296 #else
297   kmi_id = WM_key_event_operator_id(C, idname, but->opcontext, prop, true, &km);
298 #endif
299 
300   shortcut_free_operator_property(prop);
301 
302   kmi = WM_keymap_item_find_id(km, kmi_id);
303   WM_keymap_remove_item(km, kmi);
304 }
305 
popup_change_shortcut_func(bContext * C,void * arg1,void * UNUSED (arg2))306 static void popup_change_shortcut_func(bContext *C, void *arg1, void *UNUSED(arg2))
307 {
308   uiBut *but = (uiBut *)arg1;
309   UI_popup_block_invoke(C, menu_change_shortcut, but, NULL);
310 }
311 
remove_shortcut_func(bContext * C,void * arg1,void * UNUSED (arg2))312 static void remove_shortcut_func(bContext *C, void *arg1, void *UNUSED(arg2))
313 {
314   uiBut *but = (uiBut *)arg1;
315   wmKeyMap *km;
316   wmKeyMapItem *kmi;
317   IDProperty *prop;
318   const char *idname = shortcut_get_operator_property(C, but, &prop);
319 
320   kmi = WM_key_event_operator(C,
321                               idname,
322                               but->opcontext,
323                               prop,
324                               EVT_TYPE_MASK_HOTKEY_INCLUDE,
325                               EVT_TYPE_MASK_HOTKEY_EXCLUDE,
326                               &km);
327   BLI_assert(kmi != NULL);
328 
329   WM_keymap_remove_item(km, kmi);
330   U.runtime.is_dirty = true;
331 
332   shortcut_free_operator_property(prop);
333   but_shortcut_name_func(C, but, 0);
334 }
335 
popup_add_shortcut_func(bContext * C,void * arg1,void * UNUSED (arg2))336 static void popup_add_shortcut_func(bContext *C, void *arg1, void *UNUSED(arg2))
337 {
338   uiBut *but = (uiBut *)arg1;
339   UI_popup_block_ex(C, menu_add_shortcut, NULL, menu_add_shortcut_cancel, but, NULL);
340 }
341 
ui_but_is_user_menu_compatible(bContext * C,uiBut * but)342 static bool ui_but_is_user_menu_compatible(bContext *C, uiBut *but)
343 {
344   return (but->optype ||
345           (but->rnaprop && (RNA_property_type(but->rnaprop) == PROP_BOOLEAN) &&
346            (WM_context_member_from_ptr(C, &but->rnapoin) != NULL)) ||
347           UI_but_menutype_get(but));
348 }
349 
ui_but_user_menu_find(bContext * C,uiBut * but,bUserMenu * um)350 static bUserMenuItem *ui_but_user_menu_find(bContext *C, uiBut *but, bUserMenu *um)
351 {
352   MenuType *mt = NULL;
353   if (but->optype) {
354     IDProperty *prop = (but->opptr) ? but->opptr->data : NULL;
355     return (bUserMenuItem *)ED_screen_user_menu_item_find_operator(
356         &um->items, but->optype, prop, but->opcontext);
357   }
358   if (but->rnaprop) {
359     const char *member_id = WM_context_member_from_ptr(C, &but->rnapoin);
360     const char *data_path = RNA_path_from_ID_to_struct(&but->rnapoin);
361     const char *member_id_data_path = member_id;
362     if (data_path) {
363       member_id_data_path = BLI_sprintfN("%s.%s", member_id, data_path);
364     }
365     const char *prop_id = RNA_property_identifier(but->rnaprop);
366     bUserMenuItem *umi = (bUserMenuItem *)ED_screen_user_menu_item_find_prop(
367         &um->items, member_id_data_path, prop_id, but->rnaindex);
368     if (data_path) {
369       MEM_freeN((void *)data_path);
370     }
371     if (member_id != member_id_data_path) {
372       MEM_freeN((void *)member_id_data_path);
373     }
374     return umi;
375   }
376   if ((mt = UI_but_menutype_get(but))) {
377     return (bUserMenuItem *)ED_screen_user_menu_item_find_menu(&um->items, mt);
378   }
379   return NULL;
380 }
381 
ui_but_user_menu_add(bContext * C,uiBut * but,bUserMenu * um)382 static void ui_but_user_menu_add(bContext *C, uiBut *but, bUserMenu *um)
383 {
384   BLI_assert(ui_but_is_user_menu_compatible(C, but));
385 
386   char drawstr[sizeof(but->drawstr)];
387   STRNCPY(drawstr, but->drawstr);
388   if (but->flag & UI_BUT_HAS_SEP_CHAR) {
389     char *sep = strrchr(drawstr, UI_SEP_CHAR);
390     if (sep) {
391       *sep = '\0';
392     }
393   }
394 
395   MenuType *mt = NULL;
396   if (but->optype) {
397     if (drawstr[0] == '\0') {
398       /* Hard code overrides for generic operators. */
399       if (UI_but_is_tool(but)) {
400         char idname[64];
401         RNA_string_get(but->opptr, "name", idname);
402 #ifdef WITH_PYTHON
403         {
404           const char *expr_imports[] = {"bpy", "bl_ui", NULL};
405           char expr[256];
406           SNPRINTF(expr,
407                    "bl_ui.space_toolsystem_common.item_from_id("
408                    "bpy.context, "
409                    "bpy.context.space_data.type, "
410                    "'%s').label",
411                    idname);
412           char *expr_result = NULL;
413           if (BPY_run_string_as_string(C, expr_imports, expr, __func__, &expr_result)) {
414             STRNCPY(drawstr, expr_result);
415             MEM_freeN(expr_result);
416           }
417           else {
418             BLI_assert(0);
419             STRNCPY(drawstr, idname);
420           }
421         }
422 #else
423         STRNCPY(drawstr, idname);
424 #endif
425       }
426     }
427     ED_screen_user_menu_item_add_operator(
428         &um->items, drawstr, but->optype, but->opptr ? but->opptr->data : NULL, but->opcontext);
429   }
430   else if (but->rnaprop) {
431     /* Note: 'member_id' may be a path. */
432     const char *member_id = WM_context_member_from_ptr(C, &but->rnapoin);
433     const char *data_path = RNA_path_from_ID_to_struct(&but->rnapoin);
434     const char *member_id_data_path = member_id;
435     if (data_path) {
436       member_id_data_path = BLI_sprintfN("%s.%s", member_id, data_path);
437     }
438     const char *prop_id = RNA_property_identifier(but->rnaprop);
439     /* Note, ignore 'drawstr', use property idname always. */
440     ED_screen_user_menu_item_add_prop(&um->items, "", member_id_data_path, prop_id, but->rnaindex);
441     if (data_path) {
442       MEM_freeN((void *)data_path);
443     }
444     if (member_id != member_id_data_path) {
445       MEM_freeN((void *)member_id_data_path);
446     }
447   }
448   else if ((mt = UI_but_menutype_get(but))) {
449     ED_screen_user_menu_item_add_menu(&um->items, drawstr, mt);
450   }
451 }
452 
popup_user_menu_add_or_replace_func(bContext * C,void * arg1,void * UNUSED (arg2))453 static void popup_user_menu_add_or_replace_func(bContext *C, void *arg1, void *UNUSED(arg2))
454 {
455   uiBut *but = arg1;
456   bUserMenu *um = ED_screen_user_menu_ensure(C);
457   U.runtime.is_dirty = true;
458   ui_but_user_menu_add(C, but, um);
459 }
460 
popup_user_menu_remove_func(bContext * UNUSED (C),void * arg1,void * arg2)461 static void popup_user_menu_remove_func(bContext *UNUSED(C), void *arg1, void *arg2)
462 {
463   bUserMenu *um = arg1;
464   bUserMenuItem *umi = arg2;
465   U.runtime.is_dirty = true;
466   ED_screen_user_menu_item_remove(&um->items, umi);
467 }
468 
ui_but_menu_add_path_operators(uiLayout * layout,PointerRNA * ptr,PropertyRNA * prop)469 static void ui_but_menu_add_path_operators(uiLayout *layout, PointerRNA *ptr, PropertyRNA *prop)
470 {
471   const PropertySubType subtype = RNA_property_subtype(prop);
472   wmOperatorType *ot = WM_operatortype_find("WM_OT_path_open", true);
473   char filepath[FILE_MAX];
474   char dir[FILE_MAXDIR];
475   char file[FILE_MAXFILE];
476   PointerRNA props_ptr;
477 
478   BLI_assert(ELEM(subtype, PROP_FILEPATH, PROP_DIRPATH));
479   UNUSED_VARS_NDEBUG(subtype);
480 
481   RNA_property_string_get(ptr, prop, filepath);
482   BLI_split_dirfile(filepath, dir, file, sizeof(dir), sizeof(file));
483 
484   if (file[0]) {
485     BLI_assert(subtype == PROP_FILEPATH);
486     uiItemFullO_ptr(layout,
487                     ot,
488                     CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Open File Externally"),
489                     ICON_NONE,
490                     NULL,
491                     WM_OP_INVOKE_DEFAULT,
492                     0,
493                     &props_ptr);
494     RNA_string_set(&props_ptr, "filepath", filepath);
495   }
496 
497   uiItemFullO_ptr(layout,
498                   ot,
499                   CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Open Location Externally"),
500                   ICON_NONE,
501                   NULL,
502                   WM_OP_INVOKE_DEFAULT,
503                   0,
504                   &props_ptr);
505   RNA_string_set(&props_ptr, "filepath", dir);
506 }
507 
ui_popup_context_menu_for_button(bContext * C,uiBut * but)508 bool ui_popup_context_menu_for_button(bContext *C, uiBut *but)
509 {
510   /* ui_but_is_interactive() may let some buttons through that should not get a context menu - it
511    * doesn't make sense for them. */
512   if (ELEM(but->type, UI_BTYPE_LABEL, UI_BTYPE_IMAGE)) {
513     return false;
514   }
515 
516   uiPopupMenu *pup;
517   uiLayout *layout;
518 
519   {
520     uiStringInfo label = {BUT_GET_LABEL, NULL};
521 
522     /* highly unlikely getting the label ever fails */
523     UI_but_string_info_get(C, but, &label, NULL);
524 
525     pup = UI_popup_menu_begin(C, label.strinfo ? label.strinfo : "", ICON_NONE);
526     layout = UI_popup_menu_layout(pup);
527     if (label.strinfo) {
528       MEM_freeN(label.strinfo);
529     }
530     uiLayoutSetOperatorContext(layout, WM_OP_INVOKE_DEFAULT);
531   }
532 
533   const bool is_disabled = but->flag & UI_BUT_DISABLED;
534 
535   if (is_disabled) {
536     /* Suppress editing commands. */
537   }
538   else if (but->type == UI_BTYPE_TAB) {
539     uiButTab *tab = (uiButTab *)but;
540     if (tab->menu) {
541       UI_menutype_draw(C, tab->menu, layout);
542       uiItemS(layout);
543     }
544   }
545   else if (but->rnapoin.data && but->rnaprop) {
546     PointerRNA *ptr = &but->rnapoin;
547     PropertyRNA *prop = but->rnaprop;
548     const PropertyType type = RNA_property_type(prop);
549     const PropertySubType subtype = RNA_property_subtype(prop);
550     bool is_anim = RNA_property_animateable(ptr, prop);
551     const bool is_editable = RNA_property_editable(ptr, prop);
552     const bool is_idprop = RNA_property_is_idprop(prop);
553     const bool is_set = RNA_property_is_set(ptr, prop);
554 
555     /* second slower test,
556      * saved people finding keyframe items in menus when its not possible */
557     if (is_anim) {
558       is_anim = RNA_property_path_from_ID_check(&but->rnapoin, but->rnaprop);
559     }
560 
561     /* determine if we can key a single component of an array */
562     const bool is_array = RNA_property_array_length(&but->rnapoin, but->rnaprop) != 0;
563     const bool is_array_component = (is_array && but->rnaindex != -1);
564     const bool is_whole_array = (is_array && but->rnaindex == -1);
565 
566     const uint override_status = RNA_property_override_library_status(
567         CTX_data_main(C), ptr, prop, -1);
568     const bool is_overridable = (override_status & RNA_OVERRIDE_STATUS_OVERRIDABLE) != 0;
569 
570     /* Set the (button_pointer, button_prop)
571      * and pointer data for Python access to the hovered ui element. */
572     uiLayoutSetContextFromBut(layout, but);
573 
574     /* Keyframes */
575     if (but->flag & UI_BUT_ANIMATED_KEY) {
576       /* replace/delete keyfraemes */
577       if (is_array_component) {
578         uiItemBooleanO(layout,
579                        CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Replace Keyframes"),
580                        ICON_KEY_HLT,
581                        "ANIM_OT_keyframe_insert_button",
582                        "all",
583                        1);
584         uiItemBooleanO(layout,
585                        CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Replace Single Keyframe"),
586                        ICON_NONE,
587                        "ANIM_OT_keyframe_insert_button",
588                        "all",
589                        0);
590         uiItemBooleanO(layout,
591                        CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Delete Keyframes"),
592                        ICON_NONE,
593                        "ANIM_OT_keyframe_delete_button",
594                        "all",
595                        1);
596         uiItemBooleanO(layout,
597                        CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Delete Single Keyframe"),
598                        ICON_NONE,
599                        "ANIM_OT_keyframe_delete_button",
600                        "all",
601                        0);
602       }
603       else {
604         uiItemBooleanO(layout,
605                        CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Replace Keyframe"),
606                        ICON_KEY_HLT,
607                        "ANIM_OT_keyframe_insert_button",
608                        "all",
609                        1);
610         uiItemBooleanO(layout,
611                        CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Delete Keyframe"),
612                        ICON_NONE,
613                        "ANIM_OT_keyframe_delete_button",
614                        "all",
615                        1);
616       }
617 
618       /* keyframe settings */
619       uiItemS(layout);
620     }
621     else if (but->flag & UI_BUT_DRIVEN) {
622       /* pass */
623     }
624     else if (is_anim) {
625       if (is_array_component) {
626         uiItemBooleanO(layout,
627                        CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Insert Keyframes"),
628                        ICON_KEY_HLT,
629                        "ANIM_OT_keyframe_insert_button",
630                        "all",
631                        1);
632         uiItemBooleanO(layout,
633                        CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Insert Single Keyframe"),
634                        ICON_NONE,
635                        "ANIM_OT_keyframe_insert_button",
636                        "all",
637                        0);
638       }
639       else {
640         uiItemBooleanO(layout,
641                        CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Insert Keyframe"),
642                        ICON_KEY_HLT,
643                        "ANIM_OT_keyframe_insert_button",
644                        "all",
645                        1);
646       }
647     }
648 
649     if ((but->flag & UI_BUT_ANIMATED) && (but->rnapoin.type != &RNA_NlaStrip)) {
650       if (is_array_component) {
651         uiItemBooleanO(layout,
652                        CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Clear Keyframes"),
653                        ICON_KEY_DEHLT,
654                        "ANIM_OT_keyframe_clear_button",
655                        "all",
656                        1);
657         uiItemBooleanO(layout,
658                        CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Clear Single Keyframes"),
659                        ICON_NONE,
660                        "ANIM_OT_keyframe_clear_button",
661                        "all",
662                        0);
663       }
664       else {
665         uiItemBooleanO(layout,
666                        CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Clear Keyframes"),
667                        ICON_KEY_DEHLT,
668                        "ANIM_OT_keyframe_clear_button",
669                        "all",
670                        1);
671       }
672     }
673 
674     /* Drivers */
675     if (but->flag & UI_BUT_DRIVEN) {
676       uiItemS(layout);
677 
678       if (is_array_component) {
679         uiItemBooleanO(layout,
680                        CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Delete Drivers"),
681                        ICON_X,
682                        "ANIM_OT_driver_button_remove",
683                        "all",
684                        1);
685         uiItemBooleanO(layout,
686                        CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Delete Single Driver"),
687                        ICON_NONE,
688                        "ANIM_OT_driver_button_remove",
689                        "all",
690                        0);
691       }
692       else {
693         uiItemBooleanO(layout,
694                        CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Delete Driver"),
695                        ICON_X,
696                        "ANIM_OT_driver_button_remove",
697                        "all",
698                        1);
699       }
700 
701       if (!is_whole_array) {
702         uiItemO(layout,
703                 CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Copy Driver"),
704                 ICON_NONE,
705                 "ANIM_OT_copy_driver_button");
706         if (ANIM_driver_can_paste()) {
707           uiItemO(layout,
708                   CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Paste Driver"),
709                   ICON_NONE,
710                   "ANIM_OT_paste_driver_button");
711         }
712 
713         uiItemO(layout,
714                 CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Edit Driver"),
715                 ICON_DRIVER,
716                 "ANIM_OT_driver_button_edit");
717       }
718 
719       uiItemO(layout,
720               CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Open Drivers Editor"),
721               ICON_NONE,
722               "SCREEN_OT_drivers_editor_show");
723     }
724     else if (but->flag & (UI_BUT_ANIMATED_KEY | UI_BUT_ANIMATED)) {
725       /* pass */
726     }
727     else if (is_anim) {
728       uiItemS(layout);
729 
730       uiItemO(layout,
731               CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Add Driver"),
732               ICON_DRIVER,
733               "ANIM_OT_driver_button_add");
734 
735       if (!is_whole_array) {
736         if (ANIM_driver_can_paste()) {
737           uiItemO(layout,
738                   CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Paste Driver"),
739                   ICON_NONE,
740                   "ANIM_OT_paste_driver_button");
741         }
742       }
743 
744       uiItemO(layout,
745               CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Open Drivers Editor"),
746               ICON_NONE,
747               "SCREEN_OT_drivers_editor_show");
748     }
749 
750     /* Keying Sets */
751     /* TODO: check on modifyability of Keying Set when doing this */
752     if (is_anim) {
753       uiItemS(layout);
754 
755       if (is_array_component) {
756         uiItemBooleanO(layout,
757                        CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Add All to Keying Set"),
758                        ICON_KEYINGSET,
759                        "ANIM_OT_keyingset_button_add",
760                        "all",
761                        1);
762         uiItemBooleanO(layout,
763                        CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Add Single to Keying Set"),
764                        ICON_NONE,
765                        "ANIM_OT_keyingset_button_add",
766                        "all",
767                        0);
768         uiItemO(layout,
769                 CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Remove from Keying Set"),
770                 ICON_NONE,
771                 "ANIM_OT_keyingset_button_remove");
772       }
773       else {
774         uiItemBooleanO(layout,
775                        CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Add to Keying Set"),
776                        ICON_KEYINGSET,
777                        "ANIM_OT_keyingset_button_add",
778                        "all",
779                        1);
780         uiItemO(layout,
781                 CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Remove from Keying Set"),
782                 ICON_NONE,
783                 "ANIM_OT_keyingset_button_remove");
784       }
785     }
786 
787     if (is_overridable) {
788       wmOperatorType *ot;
789       PointerRNA op_ptr;
790       /* Override Operators */
791       uiItemS(layout);
792 
793       if (but->flag & UI_BUT_OVERRIDEN) {
794         if (is_array_component) {
795 #if 0 /* Disabled for now. */
796           ot = WM_operatortype_find("UI_OT_override_type_set_button", false);
797           uiItemFullO_ptr(
798               layout, ot, "Overrides Type", ICON_NONE, NULL, WM_OP_INVOKE_DEFAULT, 0, &op_ptr);
799           RNA_boolean_set(&op_ptr, "all", true);
800           uiItemFullO_ptr(layout,
801                           ot,
802                           "Single Override Type",
803                           ICON_NONE,
804                           NULL,
805                           WM_OP_INVOKE_DEFAULT,
806                           0,
807                           &op_ptr);
808           RNA_boolean_set(&op_ptr, "all", false);
809 #endif
810           uiItemBooleanO(layout,
811                          CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Remove Overrides"),
812                          ICON_X,
813                          "UI_OT_override_remove_button",
814                          "all",
815                          true);
816           uiItemBooleanO(layout,
817                          CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Remove Single Override"),
818                          ICON_X,
819                          "UI_OT_override_remove_button",
820                          "all",
821                          false);
822         }
823         else {
824 #if 0 /* Disabled for now. */
825           uiItemFullO(layout,
826                       "UI_OT_override_type_set_button",
827                       "Override Type",
828                       ICON_NONE,
829                       NULL,
830                       WM_OP_INVOKE_DEFAULT,
831                       0,
832                       &op_ptr);
833           RNA_boolean_set(&op_ptr, "all", false);
834 #endif
835           uiItemBooleanO(layout,
836                          CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Remove Override"),
837                          ICON_X,
838                          "UI_OT_override_remove_button",
839                          "all",
840                          true);
841         }
842       }
843       else {
844         if (is_array_component) {
845           ot = WM_operatortype_find("UI_OT_override_type_set_button", false);
846           uiItemFullO_ptr(
847               layout, ot, "Define Overrides", ICON_NONE, NULL, WM_OP_INVOKE_DEFAULT, 0, &op_ptr);
848           RNA_boolean_set(&op_ptr, "all", true);
849           uiItemFullO_ptr(layout,
850                           ot,
851                           "Define Single Override",
852                           ICON_NONE,
853                           NULL,
854                           WM_OP_INVOKE_DEFAULT,
855                           0,
856                           &op_ptr);
857           RNA_boolean_set(&op_ptr, "all", false);
858         }
859         else {
860           uiItemFullO(layout,
861                       "UI_OT_override_type_set_button",
862                       "Define Override",
863                       ICON_NONE,
864                       NULL,
865                       WM_OP_INVOKE_DEFAULT,
866                       0,
867                       &op_ptr);
868           RNA_boolean_set(&op_ptr, "all", false);
869         }
870       }
871     }
872 
873     uiItemS(layout);
874 
875     /* Property Operators */
876 
877     /* Copy Property Value
878      * Paste Property Value */
879 
880     if (is_array_component) {
881       uiItemBooleanO(layout,
882                      CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Reset All to Default Values"),
883                      ICON_LOOP_BACK,
884                      "UI_OT_reset_default_button",
885                      "all",
886                      1);
887       uiItemBooleanO(layout,
888                      CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Reset Single to Default Value"),
889                      ICON_NONE,
890                      "UI_OT_reset_default_button",
891                      "all",
892                      0);
893     }
894     else {
895       uiItemBooleanO(layout,
896                      CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Reset to Default Value"),
897                      ICON_LOOP_BACK,
898                      "UI_OT_reset_default_button",
899                      "all",
900                      1);
901     }
902     if (is_editable /*&& is_idprop*/ && is_set) {
903       uiItemO(layout,
904               CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Unset"),
905               ICON_NONE,
906               "UI_OT_unset_property_button");
907     }
908 
909     if (is_idprop && !is_array && ELEM(type, PROP_INT, PROP_FLOAT)) {
910       uiItemO(layout,
911               CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Assign Value as Default"),
912               ICON_NONE,
913               "UI_OT_assign_default_button");
914 
915       uiItemS(layout);
916     }
917 
918     if (is_array_component) {
919       uiItemBooleanO(layout,
920                      CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Copy All to Selected"),
921                      ICON_NONE,
922                      "UI_OT_copy_to_selected_button",
923                      "all",
924                      true);
925       uiItemBooleanO(layout,
926                      CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Copy Single to Selected"),
927                      ICON_NONE,
928                      "UI_OT_copy_to_selected_button",
929                      "all",
930                      false);
931     }
932     else {
933       uiItemBooleanO(layout,
934                      CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Copy to Selected"),
935                      ICON_NONE,
936                      "UI_OT_copy_to_selected_button",
937                      "all",
938                      true);
939     }
940 
941     uiItemO(layout,
942             CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Copy Data Path"),
943             ICON_NONE,
944             "UI_OT_copy_data_path_button");
945 
946     if (ptr->owner_id && !is_whole_array &&
947         ELEM(type, PROP_BOOLEAN, PROP_INT, PROP_FLOAT, PROP_ENUM)) {
948       uiItemO(layout,
949               CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Copy as New Driver"),
950               ICON_NONE,
951               "UI_OT_copy_as_driver_button");
952     }
953 
954     uiItemS(layout);
955 
956     if (type == PROP_STRING && ELEM(subtype, PROP_FILEPATH, PROP_DIRPATH)) {
957       ui_but_menu_add_path_operators(layout, ptr, prop);
958       uiItemS(layout);
959     }
960   }
961 
962   /* Pointer properties and string properties with
963    * prop_search support jumping to target object/bone. */
964   if (but->rnapoin.data && but->rnaprop) {
965     const PropertyType prop_type = RNA_property_type(but->rnaprop);
966     if (((prop_type == PROP_POINTER) ||
967          (prop_type == PROP_STRING && but->type == UI_BTYPE_SEARCH_MENU &&
968           ((uiButSearch *)but)->items_update_fn == ui_rna_collection_search_update_fn)) &&
969         ui_jump_to_target_button_poll(C)) {
970       uiItemO(layout,
971               CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Jump to Target"),
972               ICON_NONE,
973               "UI_OT_jump_to_target_button");
974       uiItemS(layout);
975     }
976   }
977 
978   /* Favorites Menu */
979   if (ui_but_is_user_menu_compatible(C, but)) {
980     uiBlock *block = uiLayoutGetBlock(layout);
981     const int w = uiLayoutGetWidth(layout);
982     uiBut *but2;
983     bool item_found = false;
984 
985     uint um_array_len;
986     bUserMenu **um_array = ED_screen_user_menus_find(C, &um_array_len);
987     for (int um_index = 0; um_index < um_array_len; um_index++) {
988       bUserMenu *um = um_array[um_index];
989       if (um == NULL) {
990         continue;
991       }
992       bUserMenuItem *umi = ui_but_user_menu_find(C, but, um);
993       if (umi != NULL) {
994         but2 = uiDefIconTextBut(
995             block,
996             UI_BTYPE_BUT,
997             0,
998             ICON_MENU_PANEL,
999             CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Remove from Quick Favorites"),
1000             0,
1001             0,
1002             w,
1003             UI_UNIT_Y,
1004             NULL,
1005             0,
1006             0,
1007             0,
1008             0,
1009             "");
1010         UI_but_func_set(but2, popup_user_menu_remove_func, um, umi);
1011         item_found = true;
1012       }
1013     }
1014     if (um_array) {
1015       MEM_freeN(um_array);
1016     }
1017 
1018     if (!item_found) {
1019       but2 = uiDefIconTextBut(
1020           block,
1021           UI_BTYPE_BUT,
1022           0,
1023           ICON_MENU_PANEL,
1024           CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Add to Quick Favorites"),
1025           0,
1026           0,
1027           w,
1028           UI_UNIT_Y,
1029           NULL,
1030           0,
1031           0,
1032           0,
1033           0,
1034           "Add to a user defined context menu (stored in the user preferences)");
1035       UI_but_func_set(but2, popup_user_menu_add_or_replace_func, but, NULL);
1036     }
1037 
1038     uiItemS(layout);
1039   }
1040 
1041   /* Shortcut menu */
1042   IDProperty *prop;
1043   const char *idname = shortcut_get_operator_property(C, but, &prop);
1044   if (idname != NULL) {
1045     uiBlock *block = uiLayoutGetBlock(layout);
1046     uiBut *but2;
1047     const int w = uiLayoutGetWidth(layout);
1048     wmKeyMap *km;
1049 
1050     /* We want to know if this op has a shortcut, be it hotkey or not. */
1051     wmKeyMapItem *kmi = WM_key_event_operator(
1052         C, idname, but->opcontext, prop, EVT_TYPE_MASK_ALL, 0, &km);
1053 
1054     /* We do have a shortcut, but only keyboard ones are editable that way... */
1055     if (kmi) {
1056       if (ISKEYBOARD(kmi->type)) {
1057 #if 0 /* would rather use a block but, but gets weirdly positioned... */
1058         uiDefBlockBut(block,
1059                       menu_change_shortcut,
1060                       but,
1061                       "Change Shortcut",
1062                       0,
1063                       0,
1064                       uiLayoutGetWidth(layout),
1065                       UI_UNIT_Y,
1066                       "");
1067 #endif
1068 
1069         but2 = uiDefIconTextBut(block,
1070                                 UI_BTYPE_BUT,
1071                                 0,
1072                                 ICON_HAND,
1073                                 CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Change Shortcut"),
1074                                 0,
1075                                 0,
1076                                 w,
1077                                 UI_UNIT_Y,
1078                                 NULL,
1079                                 0,
1080                                 0,
1081                                 0,
1082                                 0,
1083                                 "");
1084         UI_but_func_set(but2, popup_change_shortcut_func, but, NULL);
1085       }
1086       else {
1087         but2 = uiDefIconTextBut(block,
1088                                 UI_BTYPE_BUT,
1089                                 0,
1090                                 ICON_HAND,
1091                                 IFACE_("Non-Keyboard Shortcut"),
1092                                 0,
1093                                 0,
1094                                 w,
1095                                 UI_UNIT_Y,
1096                                 NULL,
1097                                 0,
1098                                 0,
1099                                 0,
1100                                 0,
1101                                 TIP_("Only keyboard shortcuts can be edited that way, "
1102                                      "please use User Preferences otherwise"));
1103         UI_but_flag_enable(but2, UI_BUT_DISABLED);
1104       }
1105 
1106       but2 = uiDefIconTextBut(block,
1107                               UI_BTYPE_BUT,
1108                               0,
1109                               ICON_BLANK1,
1110                               CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Remove Shortcut"),
1111                               0,
1112                               0,
1113                               w,
1114                               UI_UNIT_Y,
1115                               NULL,
1116                               0,
1117                               0,
1118                               0,
1119                               0,
1120                               "");
1121       UI_but_func_set(but2, remove_shortcut_func, but, NULL);
1122     }
1123     /* only show 'assign' if there's a suitable key map for it to go in */
1124     else if (WM_keymap_guess_opname(C, idname)) {
1125       but2 = uiDefIconTextBut(block,
1126                               UI_BTYPE_BUT,
1127                               0,
1128                               ICON_HAND,
1129                               CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Assign Shortcut"),
1130                               0,
1131                               0,
1132                               w,
1133                               UI_UNIT_Y,
1134                               NULL,
1135                               0,
1136                               0,
1137                               0,
1138                               0,
1139                               "");
1140       UI_but_func_set(but2, popup_add_shortcut_func, but, NULL);
1141     }
1142 
1143     shortcut_free_operator_property(prop);
1144 
1145     /* Set the operator pointer for python access */
1146     uiLayoutSetContextFromBut(layout, but);
1147 
1148     uiItemS(layout);
1149   }
1150 
1151   { /* Docs */
1152     char buf[512];
1153 
1154     if (UI_but_online_manual_id(but, buf, sizeof(buf))) {
1155       PointerRNA ptr_props;
1156       uiItemO(layout,
1157               CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Online Manual"),
1158               ICON_URL,
1159               "WM_OT_doc_view_manual_ui_context");
1160 
1161       if (U.flag & USER_DEVELOPER_UI) {
1162         uiItemFullO(layout,
1163                     "WM_OT_doc_view",
1164                     CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Online Python Reference"),
1165                     ICON_NONE,
1166                     NULL,
1167                     WM_OP_EXEC_DEFAULT,
1168                     0,
1169                     &ptr_props);
1170         RNA_string_set(&ptr_props, "doc_id", buf);
1171       }
1172     }
1173   }
1174 
1175   if (but->optype && U.flag & USER_DEVELOPER_UI) {
1176     uiItemO(layout, NULL, ICON_NONE, "UI_OT_copy_python_command_button");
1177   }
1178 
1179   /* perhaps we should move this into (G.debug & G_DEBUG) - campbell */
1180   if (U.flag & USER_DEVELOPER_UI) {
1181     if (ui_block_is_menu(but->block) == false) {
1182       uiItemFullO(
1183           layout, "UI_OT_editsource", NULL, ICON_NONE, NULL, WM_OP_INVOKE_DEFAULT, 0, NULL);
1184     }
1185   }
1186 
1187   if (BKE_addon_find(&U.addons, "ui_translate")) {
1188     uiItemFullO(layout,
1189                 "UI_OT_edittranslation_init",
1190                 NULL,
1191                 ICON_NONE,
1192                 NULL,
1193                 WM_OP_INVOKE_DEFAULT,
1194                 0,
1195                 NULL);
1196   }
1197 
1198   /* Show header tools for header buttons. */
1199   if (ui_block_is_popup_any(but->block) == false) {
1200     const ARegion *region = CTX_wm_region(C);
1201 
1202     if (!region) {
1203       /* skip */
1204     }
1205     else if (ELEM(region->regiontype, RGN_TYPE_HEADER, RGN_TYPE_TOOL_HEADER)) {
1206       uiItemMenuF(layout, IFACE_("Header"), ICON_NONE, ED_screens_header_tools_menu_create, NULL);
1207     }
1208     else if (region->regiontype == RGN_TYPE_NAV_BAR) {
1209       uiItemMenuF(layout,
1210                   IFACE_("Navigation Bar"),
1211                   ICON_NONE,
1212                   ED_screens_navigation_bar_tools_menu_create,
1213                   NULL);
1214     }
1215     else if (region->regiontype == RGN_TYPE_FOOTER) {
1216       uiItemMenuF(layout, IFACE_("Footer"), ICON_NONE, ED_screens_footer_tools_menu_create, NULL);
1217     }
1218   }
1219 
1220   MenuType *mt = WM_menutype_find("WM_MT_button_context", true);
1221   if (mt) {
1222     UI_menutype_draw(C, mt, uiLayoutColumn(layout, false));
1223   }
1224 
1225   return UI_popup_menu_end_or_cancel(C, pup);
1226 }
1227 
1228 /** \} */
1229 
1230 /* -------------------------------------------------------------------- */
1231 /** \name Panel Context Menu
1232  * \{ */
1233 
1234 /**
1235  * menu to show when right clicking on the panel header
1236  */
ui_popup_context_menu_for_panel(bContext * C,ARegion * region,Panel * panel)1237 void ui_popup_context_menu_for_panel(bContext *C, ARegion *region, Panel *panel)
1238 {
1239   bScreen *screen = CTX_wm_screen(C);
1240   const bool has_panel_category = UI_panel_category_is_visible(region);
1241   const bool any_item_visible = has_panel_category;
1242   PointerRNA ptr;
1243   uiPopupMenu *pup;
1244   uiLayout *layout;
1245 
1246   if (!any_item_visible) {
1247     return;
1248   }
1249   if (panel->type->parent != NULL) {
1250     return;
1251   }
1252 
1253   RNA_pointer_create(&screen->id, &RNA_Panel, panel, &ptr);
1254 
1255   pup = UI_popup_menu_begin(C, IFACE_("Panel"), ICON_NONE);
1256   layout = UI_popup_menu_layout(pup);
1257 
1258   if (has_panel_category) {
1259     char tmpstr[80];
1260     BLI_snprintf(tmpstr,
1261                  sizeof(tmpstr),
1262                  "%s" UI_SEP_CHAR_S "%s",
1263                  IFACE_("Pin"),
1264                  IFACE_("Shift Left Mouse"));
1265     uiItemR(layout, &ptr, "use_pin", 0, tmpstr, ICON_NONE);
1266 
1267     /* evil, force shortcut flag */
1268     {
1269       uiBlock *block = uiLayoutGetBlock(layout);
1270       uiBut *but = block->buttons.last;
1271       but->flag |= UI_BUT_HAS_SEP_CHAR;
1272       but->drawflag |= UI_BUT_HAS_SHORTCUT;
1273     }
1274   }
1275   UI_popup_menu_end(C, pup);
1276 }
1277 
1278 /** \} */
1279