UI: alternate fix for empty context menu
[blender.git] / source / blender / editors / interface / interface_region_menu_popup.c
1 /*
2  * ***** BEGIN GPL LICENSE BLOCK *****
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software Foundation,
16  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  *
18  * The Original Code is Copyright (C) 2008 Blender Foundation.
19  * All rights reserved.
20  *
21  * Contributor(s): Blender Foundation
22  *
23  * ***** END GPL LICENSE BLOCK *****
24  */
25
26 /** \file blender/editors/interface/interface_region_menu_popup.c
27  *  \ingroup edinterface
28  *
29  * PopUp Menu Region
30  */
31
32 #include <stdarg.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <assert.h>
36
37 #include "MEM_guardedalloc.h"
38
39 #include "DNA_userdef_types.h"
40
41 #include "BLI_math.h"
42 #include "BLI_listbase.h"
43
44 #include "BLI_string.h"
45 #include "BLI_rect.h"
46 #include "BLI_utildefines.h"
47 #include "BLI_ghash.h"
48
49 #include "BKE_context.h"
50 #include "BKE_screen.h"
51 #include "BKE_report.h"
52
53 #include "WM_api.h"
54 #include "WM_types.h"
55
56 #include "RNA_access.h"
57
58 #include "UI_interface.h"
59
60 #include "BLT_translation.h"
61
62 #include "ED_screen.h"
63
64 #include "interface_intern.h"
65 #include "interface_regions_intern.h"
66
67 /* -------------------------------------------------------------------- */
68 /** \name Utility Functions
69  * \{ */
70
71 bool ui_but_menu_step_poll(const uiBut *but)
72 {
73         BLI_assert(but->type == UI_BTYPE_MENU);
74
75         /* currently only RNA buttons */
76         return ((but->menu_step_func != NULL) ||
77                 (but->rnaprop && RNA_property_type(but->rnaprop) == PROP_ENUM));
78 }
79
80 int ui_but_menu_step(uiBut *but, int direction)
81 {
82         if (ui_but_menu_step_poll(but)) {
83                 if (but->menu_step_func) {
84                         return but->menu_step_func(but->block->evil_C, direction, but->poin);
85                 }
86                 else {
87                         const int curval = RNA_property_enum_get(&but->rnapoin, but->rnaprop);
88                         return RNA_property_enum_step(but->block->evil_C, &but->rnapoin, but->rnaprop, curval, direction);
89                 }
90         }
91
92         printf("%s: cannot cycle button '%s'\n", __func__, but->str);
93         return 0;
94 }
95
96 static uint ui_popup_string_hash(const char *str)
97 {
98         /* sometimes button contains hotkey, sometimes not, strip for proper compare */
99         int hash;
100         const char *delimit = strrchr(str, UI_SEP_CHAR);
101
102         if (delimit) {
103                 hash = BLI_ghashutil_strhash_n(str, delimit - str);
104         }
105         else {
106                 hash = BLI_ghashutil_strhash(str);
107         }
108
109         return hash;
110 }
111
112 uint ui_popup_menu_hash(const char *str)
113 {
114         return BLI_ghashutil_strhash(str);
115 }
116
117 /* but == NULL read, otherwise set */
118 static uiBut *ui_popup_menu_memory__internal(uiBlock *block, uiBut *but)
119 {
120         static uint mem[256];
121         static bool first = true;
122
123         const uint hash = block->puphash;
124         const uint hash_mod = hash & 255;
125
126         if (first) {
127                 /* init */
128                 memset(mem, -1, sizeof(mem));
129                 first = 0;
130         }
131
132         if (but) {
133                 /* set */
134                 mem[hash_mod] = ui_popup_string_hash(but->str);
135                 return NULL;
136         }
137         else {
138                 /* get */
139                 for (but = block->buttons.first; but; but = but->next) {
140                         if (ui_popup_string_hash(but->str) == mem[hash_mod]) {
141                                 return but;
142                         }
143                 }
144
145                 return NULL;
146         }
147 }
148
149 uiBut *ui_popup_menu_memory_get(uiBlock *block)
150 {
151         return ui_popup_menu_memory__internal(block, NULL);
152 }
153
154 void ui_popup_menu_memory_set(uiBlock *block, uiBut *but)
155 {
156         ui_popup_menu_memory__internal(block, but);
157 }
158
159 /** \} */
160
161 /* -------------------------------------------------------------------- */
162 /** \name Popup Menu with Callback or String
163  * \{ */
164
165 struct uiPopupMenu {
166         uiBlock *block;
167         uiLayout *layout;
168         uiBut *but;
169         ARegion *butregion;
170
171         int mx, my;
172         bool popup, slideout;
173
174         uiMenuCreateFunc menu_func;
175         void *menu_arg;
176 };
177
178 static uiBlock *ui_block_func_POPUP(bContext *C, uiPopupBlockHandle *handle, void *arg_pup)
179 {
180         uiBlock *block;
181         uiPopupMenu *pup = arg_pup;
182         int minwidth, width, height;
183         char direction;
184         bool flip;
185
186         if (pup->menu_func) {
187                 pup->block->handle = handle;
188                 pup->menu_func(C, pup->layout, pup->menu_arg);
189                 pup->block->handle = NULL;
190         }
191
192         if (pup->but) {
193                 /* minimum width to enforece */
194                 minwidth = BLI_rctf_size_x(&pup->but->rect);
195
196                 /* settings (typically rna-enum-popups) show above the button,
197                  * menu's like file-menu, show below */
198                 if (pup->block->direction != 0) {
199                         /* allow overriding the direction from menu_func */
200                         direction = pup->block->direction;
201                 }
202                 else if ((pup->but->type == UI_BTYPE_PULLDOWN) ||
203                          (UI_but_menutype_get(pup->but) != NULL))
204                 {
205                         direction = UI_DIR_DOWN;
206                 }
207                 else {
208                         direction = UI_DIR_UP;
209                 }
210         }
211         else {
212                 minwidth = 50;
213                 direction = UI_DIR_DOWN;
214         }
215
216         flip = (direction == UI_DIR_DOWN);
217
218         block = pup->block;
219
220         /* in some cases we create the block before the region,
221          * so we set it delayed here if necessary */
222         if (BLI_findindex(&handle->region->uiblocks, block) == -1) {
223                 UI_block_region_set(block, handle->region);
224         }
225
226         block->direction = direction;
227
228         UI_block_layout_resolve(block, &width, &height);
229
230         UI_block_flag_enable(block, UI_BLOCK_MOVEMOUSE_QUIT);
231
232         if (pup->popup) {
233                 uiBut *bt;
234                 int offset[2];
235
236                 uiBut *but_activate = NULL;
237                 UI_block_flag_enable(block, UI_BLOCK_LOOP | UI_BLOCK_NUMSELECT);
238                 UI_block_direction_set(block, direction);
239
240                 /* offset the mouse position, possibly based on earlier selection */
241                 if ((block->flag & UI_BLOCK_POPUP_MEMORY) &&
242                     (bt = ui_popup_menu_memory_get(block)))
243                 {
244                         /* position mouse on last clicked item, at 0.8*width of the
245                          * button, so it doesn't overlap the text too much, also note
246                          * the offset is negative because we are inverse moving the
247                          * block to be under the mouse */
248                         offset[0] = -(bt->rect.xmin + 0.8f * BLI_rctf_size_x(&bt->rect));
249                         offset[1] = -(bt->rect.ymin + 0.5f * UI_UNIT_Y);
250
251                         if (ui_but_is_editable(bt)) {
252                                 but_activate = bt;
253                         }
254                 }
255                 else {
256                         /* position mouse at 0.8*width of the button and below the tile
257                          * on the first item */
258                         offset[0] = 0;
259                         for (bt = block->buttons.first; bt; bt = bt->next) {
260                                 offset[0] = min_ii(offset[0], -(bt->rect.xmin + 0.8f * BLI_rctf_size_x(&bt->rect)));
261                         }
262
263                         offset[1] = 2.1 * UI_UNIT_Y;
264
265                         for (bt = block->buttons.first; bt; bt = bt->next) {
266                                 if (ui_but_is_editable(bt)) {
267                                         but_activate = bt;
268                                         break;
269                                 }
270                         }
271                 }
272
273                 /* in rare cases this is needed since moving the popup
274                  * to be within the window bounds may move it away from the mouse,
275                  * This ensures we set an item to be active. */
276                 if (but_activate) {
277                         ui_but_activate_over(C, handle->region, but_activate);
278                 }
279
280                 block->minbounds = minwidth;
281                 UI_block_bounds_set_menu(block, 1, offset[0], offset[1]);
282         }
283         else {
284                 /* for a header menu we set the direction automatic */
285                 if (!pup->slideout && flip) {
286                         ScrArea *sa = CTX_wm_area(C);
287                         if (sa && sa->headertype == HEADERDOWN) {
288                                 ARegion *ar = CTX_wm_region(C);
289                                 if (ar && ar->regiontype == RGN_TYPE_HEADER) {
290                                         UI_block_direction_set(block, UI_DIR_UP);
291                                         UI_block_order_flip(block);
292                                 }
293                         }
294                 }
295
296                 block->minbounds = minwidth;
297                 UI_block_bounds_set_text(block, 3.0f * UI_UNIT_X);
298         }
299
300         /* if menu slides out of other menu, override direction */
301         if (pup->slideout) {
302                 UI_block_direction_set(block, UI_DIR_RIGHT);
303         }
304
305         return pup->block;
306 }
307
308 uiPopupBlockHandle *ui_popup_menu_create(
309         bContext *C, ARegion *butregion, uiBut *but,
310         uiMenuCreateFunc menu_func, void *arg)
311 {
312         wmWindow *window = CTX_wm_window(C);
313         uiStyle *style = UI_style_get_dpi();
314         uiPopupBlockHandle *handle;
315         uiPopupMenu *pup;
316
317         pup = MEM_callocN(sizeof(uiPopupMenu), __func__);
318         pup->block = UI_block_begin(C, NULL, __func__, UI_EMBOSS_PULLDOWN);
319         pup->block->flag |= UI_BLOCK_NUMSELECT;  /* default menus to numselect */
320         pup->layout = UI_block_layout(pup->block, UI_LAYOUT_VERTICAL, UI_LAYOUT_MENU, 0, 0, 200, 0, MENU_PADDING, style);
321         pup->slideout = but ? ui_block_is_menu(but->block) : false;
322         pup->but = but;
323         uiLayoutSetOperatorContext(pup->layout, WM_OP_INVOKE_REGION_WIN);
324
325         if (!but) {
326                 /* no button to start from, means we are a popup */
327                 pup->mx = window->eventstate->x;
328                 pup->my = window->eventstate->y;
329                 pup->popup = true;
330                 pup->block->flag |= UI_BLOCK_NO_FLIP;
331         }
332         /* some enums reversing is strange, currently we have no good way to
333          * reverse some enum's but not others, so reverse all so the first menu
334          * items are always close to the mouse cursor */
335         else {
336 #if 0
337                 /* if this is an rna button then we can assume its an enum
338                  * flipping enums is generally not good since the order can be
339                  * important [#28786] */
340                 if (but->rnaprop && RNA_property_type(but->rnaprop) == PROP_ENUM) {
341                         pup->block->flag |= UI_BLOCK_NO_FLIP;
342                 }
343 #endif
344                 if (but->context) {
345                         uiLayoutContextCopy(pup->layout, but->context);
346                 }
347         }
348
349         /* menu is created from a callback */
350         pup->menu_func = menu_func;
351         pup->menu_arg = arg;
352
353         handle = ui_popup_block_create(C, butregion, but, NULL, ui_block_func_POPUP, pup);
354
355         if (!but) {
356                 handle->popup = true;
357
358                 UI_popup_handlers_add(C, &window->modalhandlers, handle, 0);
359                 WM_event_add_mousemove(C);
360         }
361
362         handle->can_refresh = false;
363         MEM_freeN(pup);
364
365         return handle;
366 }
367
368 /** \} */
369
370 /* -------------------------------------------------------------------- */
371 /** \name Popup Menu API with begin & end
372  * \{ */
373
374 /**
375  * Only return handler, and set optional title.
376  * \param block_name: Assigned to uiBlock.name (useful info for debugging).
377  */
378 uiPopupMenu *UI_popup_menu_begin_ex(bContext *C, const char *title, const char *block_name, int icon)
379 {
380         uiStyle *style = UI_style_get_dpi();
381         uiPopupMenu *pup = MEM_callocN(sizeof(uiPopupMenu), "popup menu");
382         uiBut *but;
383
384         pup->block = UI_block_begin(C, NULL, block_name, UI_EMBOSS_PULLDOWN);
385         pup->block->flag |= UI_BLOCK_POPUP_MEMORY | UI_BLOCK_IS_FLIP;
386         pup->block->puphash = ui_popup_menu_hash(title);
387         pup->layout = UI_block_layout(pup->block, UI_LAYOUT_VERTICAL, UI_LAYOUT_MENU, 0, 0, 200, 0, MENU_PADDING, style);
388
389         /* note, this intentionally differs from the menu & submenu default because many operators
390          * use popups like this to select one of their options - where having invoke doesn't make sense */
391         uiLayoutSetOperatorContext(pup->layout, WM_OP_EXEC_REGION_WIN);
392
393         /* create in advance so we can let buttons point to retval already */
394         pup->block->handle = MEM_callocN(sizeof(uiPopupBlockHandle), "uiPopupBlockHandle");
395
396         /* create title button */
397         if (title[0]) {
398                 char titlestr[256];
399
400                 if (icon) {
401                         BLI_snprintf(titlestr, sizeof(titlestr), " %s", title);
402                         uiDefIconTextBut(
403                                 pup->block, UI_BTYPE_LABEL, 0, icon, titlestr, 0, 0, 200, UI_UNIT_Y, NULL, 0.0, 0.0, 0, 0, "");
404                 }
405                 else {
406                         but = uiDefBut(pup->block, UI_BTYPE_LABEL, 0, title, 0, 0, 200, UI_UNIT_Y, NULL, 0.0, 0.0, 0, 0, "");
407                         but->drawflag = UI_BUT_TEXT_LEFT;
408                 }
409
410                 uiItemS(pup->layout);
411         }
412
413         return pup;
414 }
415
416 uiPopupMenu *UI_popup_menu_begin(bContext *C, const char *title, int icon)
417 {
418         return UI_popup_menu_begin_ex(C, title, __func__, icon);
419 }
420
421 /**
422  * Setting the button makes the popup open from the button instead of the cursor.
423  */
424 void UI_popup_menu_but_set(uiPopupMenu *pup, struct ARegion *butregion, uiBut *but)
425 {
426         pup->but = but;
427         pup->butregion = butregion;
428 }
429
430 /* set the whole structure to work */
431 void UI_popup_menu_end(bContext *C, uiPopupMenu *pup)
432 {
433         wmWindow *window = CTX_wm_window(C);
434         uiPopupBlockHandle *menu;
435         uiBut *but = NULL;
436         ARegion *butregion = NULL;
437
438         pup->popup = true;
439         pup->mx = window->eventstate->x;
440         pup->my = window->eventstate->y;
441
442         if (pup->but) {
443                 but = pup->but;
444                 butregion = pup->butregion;
445         }
446
447         menu = ui_popup_block_create(C, butregion, but, NULL, ui_block_func_POPUP, pup);
448         menu->popup = true;
449
450         UI_popup_handlers_add(C, &window->modalhandlers, menu, 0);
451         WM_event_add_mousemove(C);
452
453         menu->can_refresh = false;
454         MEM_freeN(pup);
455 }
456
457 bool UI_popup_menu_end_or_cancel(bContext *C, uiPopupMenu *pup)
458 {
459         if (!UI_block_is_empty(pup->block)) {
460                 UI_popup_menu_end(C, pup);
461                 return true;
462         }
463         else {
464                 UI_block_layout_resolve(pup->block, NULL, NULL);
465                 MEM_freeN(pup->block->handle);
466                 UI_block_free(C, pup->block);
467                 MEM_freeN(pup);
468                 return false;
469         }
470 }
471
472 uiLayout *UI_popup_menu_layout(uiPopupMenu *pup)
473 {
474         return pup->layout;
475 }
476
477 /** \} */
478
479 /* -------------------------------------------------------------------- */
480 /** \name Standard Popup Menus
481  * \{ */
482
483 void UI_popup_menu_reports(bContext *C, ReportList *reports)
484 {
485         Report *report;
486
487         uiPopupMenu *pup = NULL;
488         uiLayout *layout;
489
490         if (!CTX_wm_window(C))
491                 return;
492
493         for (report = reports->list.first; report; report = report->next) {
494                 int icon;
495                 const char *msg, *msg_next;
496
497                 if (report->type < reports->printlevel) {
498                         continue;
499                 }
500
501                 if (pup == NULL) {
502                         char title[UI_MAX_DRAW_STR];
503                         BLI_snprintf(title, sizeof(title), "%s: %s", IFACE_("Report"), report->typestr);
504                         /* popup_menu stuff does just what we need (but pass meaningful block name) */
505                         pup = UI_popup_menu_begin_ex(C, title, __func__, ICON_NONE);
506                         layout = UI_popup_menu_layout(pup);
507                 }
508                 else {
509                         uiItemS(layout);
510                 }
511
512                 /* split each newline into a label */
513                 msg = report->message;
514                 icon = UI_icon_from_report_type(report->type);
515                 do {
516                         char buf[UI_MAX_DRAW_STR];
517                         msg_next = strchr(msg, '\n');
518                         if (msg_next) {
519                                 msg_next++;
520                                 BLI_strncpy(buf, msg, MIN2(sizeof(buf), msg_next - msg));
521                                 msg = buf;
522                         }
523                         uiItemL(layout, msg, icon);
524                         icon = ICON_NONE;
525                 } while ((msg = msg_next) && *msg);
526         }
527
528         if (pup) {
529                 UI_popup_menu_end(C, pup);
530         }
531 }
532
533 int UI_popup_menu_invoke(bContext *C, const char *idname, ReportList *reports)
534 {
535         uiPopupMenu *pup;
536         uiLayout *layout;
537         MenuType *mt = WM_menutype_find(idname, true);
538
539         if (mt == NULL) {
540                 BKE_reportf(reports, RPT_ERROR, "Menu \"%s\" not found", idname);
541                 return OPERATOR_CANCELLED;
542         }
543
544         if (WM_menutype_poll(C, mt) == false) {
545                 /* cancel but allow event to pass through, just like operators do */
546                 return (OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH);
547         }
548
549         pup = UI_popup_menu_begin(C, IFACE_(mt->label), ICON_NONE);
550         layout = UI_popup_menu_layout(pup);
551
552         UI_menutype_draw(C, mt, layout);
553
554         UI_popup_menu_end(C, pup);
555
556         return OPERATOR_INTERFACE;
557 }
558
559 /** \} */
560
561 /* -------------------------------------------------------------------- */
562 /** \name Popup Block API
563  * \{ */
564
565 void UI_popup_block_invoke_ex(bContext *C, uiBlockCreateFunc func, void *arg, const char *opname, int opcontext)
566 {
567         wmWindow *window = CTX_wm_window(C);
568         uiPopupBlockHandle *handle;
569
570         handle = ui_popup_block_create(C, NULL, NULL, func, NULL, arg);
571         handle->popup = true;
572         handle->optype = (opname) ? WM_operatortype_find(opname, 0) : NULL;
573         handle->opcontext = opcontext;
574
575         UI_popup_handlers_add(C, &window->modalhandlers, handle, 0);
576         WM_event_add_mousemove(C);
577 }
578
579 void UI_popup_block_invoke(bContext *C, uiBlockCreateFunc func, void *arg)
580 {
581         UI_popup_block_invoke_ex(C, func, arg, NULL, WM_OP_INVOKE_DEFAULT);
582 }
583
584 void UI_popup_block_ex(
585         bContext *C, uiBlockCreateFunc func, uiBlockHandleFunc popup_func, uiBlockCancelFunc cancel_func,
586         void *arg, wmOperator *op)
587 {
588         wmWindow *window = CTX_wm_window(C);
589         uiPopupBlockHandle *handle;
590
591         handle = ui_popup_block_create(C, NULL, NULL, func, NULL, arg);
592         handle->popup = true;
593         handle->retvalue = 1;
594
595         handle->popup_op = op;
596         handle->popup_arg = arg;
597         handle->popup_func = popup_func;
598         handle->cancel_func = cancel_func;
599         // handle->opcontext = opcontext;
600
601         UI_popup_handlers_add(C, &window->modalhandlers, handle, 0);
602         WM_event_add_mousemove(C);
603 }
604
605 #if 0 /* UNUSED */
606 void uiPupBlockOperator(bContext *C, uiBlockCreateFunc func, wmOperator *op, int opcontext)
607 {
608         wmWindow *window = CTX_wm_window(C);
609         uiPopupBlockHandle *handle;
610
611         handle = ui_popup_block_create(C, NULL, NULL, func, NULL, op);
612         handle->popup = 1;
613         handle->retvalue = 1;
614
615         handle->popup_arg = op;
616         handle->popup_func = operator_cb;
617         handle->cancel_func = confirm_cancel_operator;
618         handle->opcontext = opcontext;
619
620         UI_popup_handlers_add(C, &window->modalhandlers, handle, 0);
621         WM_event_add_mousemove(C);
622 }
623 #endif
624
625 void UI_popup_block_close(bContext *C, wmWindow *win, uiBlock *block)
626 {
627         /* if loading new .blend while popup is open, window will be NULL */
628         if (block->handle) {
629                 if (win) {
630                         UI_popup_handlers_remove(&win->modalhandlers, block->handle);
631                         ui_popup_block_free(C, block->handle);
632
633                         /* In the case we have nested popups, closing one may need to redraw another, see: T48874 */
634                         for (ARegion *ar = win->screen->regionbase.first; ar; ar = ar->next) {
635                                 ED_region_tag_refresh_ui(ar);
636                         }
637                 }
638         }
639 }
640
641 /** \} */