WM: Add WM_menutype_poll function
[blender-staging.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                 return NULL;
144         }
145 }
146
147 uiBut *ui_popup_menu_memory_get(uiBlock *block)
148 {
149         return ui_popup_menu_memory__internal(block, NULL);
150 }
151
152 void ui_popup_menu_memory_set(uiBlock *block, uiBut *but)
153 {
154         ui_popup_menu_memory__internal(block, but);
155 }
156
157 /** \} */
158
159 /* -------------------------------------------------------------------- */
160 /** \name Popup Menu with Callback or String
161  * \{ */
162
163 struct uiPopupMenu {
164         uiBlock *block;
165         uiLayout *layout;
166         uiBut *but;
167         ARegion *butregion;
168
169         int mx, my;
170         bool popup, slideout;
171
172         uiMenuCreateFunc menu_func;
173         void *menu_arg;
174 };
175
176 static uiBlock *ui_block_func_POPUP(bContext *C, uiPopupBlockHandle *handle, void *arg_pup)
177 {
178         uiBlock *block;
179         uiBut *bt;
180         uiPopupMenu *pup = arg_pup;
181         int offset[2], minwidth, width, height;
182         char direction;
183         bool flip;
184
185         if (pup->menu_func) {
186                 pup->block->handle = handle;
187                 pup->menu_func(C, pup->layout, pup->menu_arg);
188                 pup->block->handle = NULL;
189         }
190
191         if (pup->but) {
192                 /* minimum width to enforece */
193                 minwidth = BLI_rctf_size_x(&pup->but->rect);
194
195                 /* settings (typically rna-enum-popups) show above the button,
196                  * menu's like file-menu, show below */
197                 if (pup->block->direction != 0) {
198                         /* allow overriding the direction from menu_func */
199                         direction = pup->block->direction;
200                 }
201                 else if ((pup->but->type == UI_BTYPE_PULLDOWN) ||
202                          (UI_but_menutype_get(pup->but) != NULL))
203                 {
204                         direction = UI_DIR_DOWN;
205                 }
206                 else {
207                         direction = UI_DIR_UP;
208                 }
209         }
210         else {
211                 minwidth = 50;
212                 direction = UI_DIR_DOWN;
213         }
214
215         flip = (direction == UI_DIR_DOWN);
216
217         block = pup->block;
218
219         /* in some cases we create the block before the region,
220          * so we set it delayed here if necessary */
221         if (BLI_findindex(&handle->region->uiblocks, block) == -1)
222                 UI_block_region_set(block, handle->region);
223
224         block->direction = direction;
225
226         UI_block_layout_resolve(block, &width, &height);
227
228         UI_block_flag_enable(block, UI_BLOCK_MOVEMOUSE_QUIT);
229
230         if (pup->popup) {
231                 uiBut *but_activate = NULL;
232                 UI_block_flag_enable(block, UI_BLOCK_LOOP | UI_BLOCK_NUMSELECT);
233                 UI_block_direction_set(block, direction);
234
235                 /* offset the mouse position, possibly based on earlier selection */
236                 if ((block->flag & UI_BLOCK_POPUP_MEMORY) &&
237                     (bt = ui_popup_menu_memory_get(block)))
238                 {
239                         /* position mouse on last clicked item, at 0.8*width of the
240                          * button, so it doesn't overlap the text too much, also note
241                          * the offset is negative because we are inverse moving the
242                          * block to be under the mouse */
243                         offset[0] = -(bt->rect.xmin + 0.8f * BLI_rctf_size_x(&bt->rect));
244                         offset[1] = -(bt->rect.ymin + 0.5f * UI_UNIT_Y);
245
246                         if (ui_but_is_editable(bt)) {
247                                 but_activate = bt;
248                         }
249                 }
250                 else {
251                         /* position mouse at 0.8*width of the button and below the tile
252                          * on the first item */
253                         offset[0] = 0;
254                         for (bt = block->buttons.first; bt; bt = bt->next)
255                                 offset[0] = min_ii(offset[0], -(bt->rect.xmin + 0.8f * BLI_rctf_size_x(&bt->rect)));
256
257                         offset[1] = 2.1 * UI_UNIT_Y;
258
259                         for (bt = block->buttons.first; bt; bt = bt->next) {
260                                 if (ui_but_is_editable(bt)) {
261                                         but_activate = bt;
262                                         break;
263                                 }
264                         }
265                 }
266
267                 /* in rare cases this is needed since moving the popup
268                  * to be within the window bounds may move it away from the mouse,
269                  * This ensures we set an item to be active. */
270                 if (but_activate) {
271                         ui_but_activate_over(C, handle->region, but_activate);
272                 }
273
274                 block->minbounds = minwidth;
275                 UI_block_bounds_set_menu(block, 1, offset[0], offset[1]);
276         }
277         else {
278                 /* for a header menu we set the direction automatic */
279                 if (!pup->slideout && flip) {
280                         ScrArea *sa = CTX_wm_area(C);
281                         if (sa && sa->headertype == HEADERDOWN) {
282                                 ARegion *ar = CTX_wm_region(C);
283                                 if (ar && ar->regiontype == RGN_TYPE_HEADER) {
284                                         UI_block_direction_set(block, UI_DIR_UP);
285                                         UI_block_order_flip(block);
286                                 }
287                         }
288                 }
289
290                 block->minbounds = minwidth;
291                 UI_block_bounds_set_text(block, 3.0f * UI_UNIT_X);
292         }
293
294         /* if menu slides out of other menu, override direction */
295         if (pup->slideout)
296                 UI_block_direction_set(block, UI_DIR_RIGHT);
297
298         return pup->block;
299 }
300
301 uiPopupBlockHandle *ui_popup_menu_create(
302         bContext *C, ARegion *butregion, uiBut *but,
303         uiMenuCreateFunc menu_func, void *arg)
304 {
305         wmWindow *window = CTX_wm_window(C);
306         uiStyle *style = UI_style_get_dpi();
307         uiPopupBlockHandle *handle;
308         uiPopupMenu *pup;
309
310         pup = MEM_callocN(sizeof(uiPopupMenu), __func__);
311         pup->block = UI_block_begin(C, NULL, __func__, UI_EMBOSS_PULLDOWN);
312         pup->block->flag |= UI_BLOCK_NUMSELECT;  /* default menus to numselect */
313         pup->layout = UI_block_layout(pup->block, UI_LAYOUT_VERTICAL, UI_LAYOUT_MENU, 0, 0, 200, 0, MENU_PADDING, style);
314         pup->slideout = but ? ui_block_is_menu(but->block) : false;
315         pup->but = but;
316         uiLayoutSetOperatorContext(pup->layout, WM_OP_INVOKE_REGION_WIN);
317
318         if (!but) {
319                 /* no button to start from, means we are a popup */
320                 pup->mx = window->eventstate->x;
321                 pup->my = window->eventstate->y;
322                 pup->popup = true;
323                 pup->block->flag |= UI_BLOCK_NO_FLIP;
324         }
325         /* some enums reversing is strange, currently we have no good way to
326          * reverse some enum's but not others, so reverse all so the first menu
327          * items are always close to the mouse cursor */
328         else {
329 #if 0
330                 /* if this is an rna button then we can assume its an enum
331                  * flipping enums is generally not good since the order can be
332                  * important [#28786] */
333                 if (but->rnaprop && RNA_property_type(but->rnaprop) == PROP_ENUM) {
334                         pup->block->flag |= UI_BLOCK_NO_FLIP;
335                 }
336 #endif
337                 if (but->context)
338                         uiLayoutContextCopy(pup->layout, but->context);
339         }
340
341         /* menu is created from a callback */
342         pup->menu_func = menu_func;
343         pup->menu_arg = arg;
344
345         handle = ui_popup_block_create(C, butregion, but, NULL, ui_block_func_POPUP, pup);
346
347         if (!but) {
348                 handle->popup = true;
349
350                 UI_popup_handlers_add(C, &window->modalhandlers, handle, 0);
351                 WM_event_add_mousemove(C);
352         }
353
354         handle->can_refresh = false;
355         MEM_freeN(pup);
356
357         return handle;
358 }
359
360 /** \} */
361
362 /* -------------------------------------------------------------------- */
363 /** \name Popup Menu API with begin & end
364  * \{ */
365
366 /**
367  * Only return handler, and set optional title.
368  * \param block_name: Assigned to uiBlock.name (useful info for debugging).
369  */
370 uiPopupMenu *UI_popup_menu_begin_ex(bContext *C, const char *title, const char *block_name, int icon)
371 {
372         uiStyle *style = UI_style_get_dpi();
373         uiPopupMenu *pup = MEM_callocN(sizeof(uiPopupMenu), "popup menu");
374         uiBut *but;
375
376         pup->block = UI_block_begin(C, NULL, block_name, UI_EMBOSS_PULLDOWN);
377         pup->block->flag |= UI_BLOCK_POPUP_MEMORY | UI_BLOCK_IS_FLIP;
378         pup->block->puphash = ui_popup_menu_hash(title);
379         pup->layout = UI_block_layout(pup->block, UI_LAYOUT_VERTICAL, UI_LAYOUT_MENU, 0, 0, 200, 0, MENU_PADDING, style);
380
381         /* note, this intentionally differs from the menu & submenu default because many operators
382          * use popups like this to select one of their options - where having invoke doesn't make sense */
383         uiLayoutSetOperatorContext(pup->layout, WM_OP_EXEC_REGION_WIN);
384
385         /* create in advance so we can let buttons point to retval already */
386         pup->block->handle = MEM_callocN(sizeof(uiPopupBlockHandle), "uiPopupBlockHandle");
387
388         /* create title button */
389         if (title[0]) {
390                 char titlestr[256];
391
392                 if (icon) {
393                         BLI_snprintf(titlestr, sizeof(titlestr), " %s", title);
394                         uiDefIconTextBut(
395                                 pup->block, UI_BTYPE_LABEL, 0, icon, titlestr, 0, 0, 200, UI_UNIT_Y, NULL, 0.0, 0.0, 0, 0, "");
396                 }
397                 else {
398                         but = uiDefBut(pup->block, UI_BTYPE_LABEL, 0, title, 0, 0, 200, UI_UNIT_Y, NULL, 0.0, 0.0, 0, 0, "");
399                         but->drawflag = UI_BUT_TEXT_LEFT;
400                 }
401
402                 uiItemS(pup->layout);
403         }
404
405         return pup;
406 }
407
408 uiPopupMenu *UI_popup_menu_begin(bContext *C, const char *title, int icon)
409 {
410         return UI_popup_menu_begin_ex(C, title, __func__, icon);
411 }
412
413 /**
414  * Setting the button makes the popup open from the button instead of the cursor.
415  */
416 void UI_popup_menu_but_set(uiPopupMenu *pup, struct ARegion *butregion, uiBut *but)
417 {
418         pup->but = but;
419         pup->butregion = butregion;
420 }
421
422 /* set the whole structure to work */
423 void UI_popup_menu_end(bContext *C, uiPopupMenu *pup)
424 {
425         wmWindow *window = CTX_wm_window(C);
426         uiPopupBlockHandle *menu;
427         uiBut *but = NULL;
428         ARegion *butregion = NULL;
429
430         pup->popup = true;
431         pup->mx = window->eventstate->x;
432         pup->my = window->eventstate->y;
433
434         if (pup->but) {
435                 but = pup->but;
436                 butregion = pup->butregion;
437         }
438
439         menu = ui_popup_block_create(C, butregion, but, NULL, ui_block_func_POPUP, pup);
440         menu->popup = true;
441
442         UI_popup_handlers_add(C, &window->modalhandlers, menu, 0);
443         WM_event_add_mousemove(C);
444
445         menu->can_refresh = false;
446         MEM_freeN(pup);
447 }
448
449 uiLayout *UI_popup_menu_layout(uiPopupMenu *pup)
450 {
451         return pup->layout;
452 }
453
454 /** \} */
455
456 /* -------------------------------------------------------------------- */
457 /** \name Standard Popup Menus
458  * \{ */
459
460 void UI_popup_menu_reports(bContext *C, ReportList *reports)
461 {
462         Report *report;
463
464         uiPopupMenu *pup = NULL;
465         uiLayout *layout;
466
467         if (!CTX_wm_window(C))
468                 return;
469
470         for (report = reports->list.first; report; report = report->next) {
471                 int icon;
472                 const char *msg, *msg_next;
473
474                 if (report->type < reports->printlevel) {
475                         continue;
476                 }
477
478                 if (pup == NULL) {
479                         char title[UI_MAX_DRAW_STR];
480                         BLI_snprintf(title, sizeof(title), "%s: %s", IFACE_("Report"), report->typestr);
481                         /* popup_menu stuff does just what we need (but pass meaningful block name) */
482                         pup = UI_popup_menu_begin_ex(C, title, __func__, ICON_NONE);
483                         layout = UI_popup_menu_layout(pup);
484                 }
485                 else {
486                         uiItemS(layout);
487                 }
488
489                 /* split each newline into a label */
490                 msg = report->message;
491                 icon = UI_icon_from_report_type(report->type);
492                 do {
493                         char buf[UI_MAX_DRAW_STR];
494                         msg_next = strchr(msg, '\n');
495                         if (msg_next) {
496                                 msg_next++;
497                                 BLI_strncpy(buf, msg, MIN2(sizeof(buf), msg_next - msg));
498                                 msg = buf;
499                         }
500                         uiItemL(layout, msg, icon);
501                         icon = ICON_NONE;
502                 } while ((msg = msg_next) && *msg);
503         }
504
505         if (pup) {
506                 UI_popup_menu_end(C, pup);
507         }
508 }
509
510 int UI_popup_menu_invoke(bContext *C, const char *idname, ReportList *reports)
511 {
512         uiPopupMenu *pup;
513         uiLayout *layout;
514         MenuType *mt = WM_menutype_find(idname, true);
515
516         if (mt == NULL) {
517                 BKE_reportf(reports, RPT_ERROR, "Menu \"%s\" not found", idname);
518                 return OPERATOR_CANCELLED;
519         }
520
521         if (WM_menutype_poll(C, mt) == false) {
522                 /* cancel but allow event to pass through, just like operators do */
523                 return (OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH);
524         }
525
526         pup = UI_popup_menu_begin(C, IFACE_(mt->label), ICON_NONE);
527         layout = UI_popup_menu_layout(pup);
528
529         UI_menutype_draw(C, mt, layout);
530
531         UI_popup_menu_end(C, pup);
532
533         return OPERATOR_INTERFACE;
534 }
535
536 /** \} */
537
538 /* -------------------------------------------------------------------- */
539 /** \name Popup Block API
540  * \{ */
541
542 void UI_popup_block_invoke_ex(bContext *C, uiBlockCreateFunc func, void *arg, const char *opname, int opcontext)
543 {
544         wmWindow *window = CTX_wm_window(C);
545         uiPopupBlockHandle *handle;
546
547         handle = ui_popup_block_create(C, NULL, NULL, func, NULL, arg);
548         handle->popup = true;
549         handle->optype = (opname) ? WM_operatortype_find(opname, 0) : NULL;
550         handle->opcontext = opcontext;
551
552         UI_popup_handlers_add(C, &window->modalhandlers, handle, 0);
553         WM_event_add_mousemove(C);
554 }
555
556 void UI_popup_block_invoke(bContext *C, uiBlockCreateFunc func, void *arg)
557 {
558         UI_popup_block_invoke_ex(C, func, arg, NULL, WM_OP_INVOKE_DEFAULT);
559 }
560
561 void UI_popup_block_ex(
562         bContext *C, uiBlockCreateFunc func, uiBlockHandleFunc popup_func, uiBlockCancelFunc cancel_func,
563         void *arg, wmOperator *op)
564 {
565         wmWindow *window = CTX_wm_window(C);
566         uiPopupBlockHandle *handle;
567
568         handle = ui_popup_block_create(C, NULL, NULL, func, NULL, arg);
569         handle->popup = true;
570         handle->retvalue = 1;
571
572         handle->popup_op = op;
573         handle->popup_arg = arg;
574         handle->popup_func = popup_func;
575         handle->cancel_func = cancel_func;
576         // handle->opcontext = opcontext;
577
578         UI_popup_handlers_add(C, &window->modalhandlers, handle, 0);
579         WM_event_add_mousemove(C);
580 }
581
582 #if 0 /* UNUSED */
583 void uiPupBlockOperator(bContext *C, uiBlockCreateFunc func, wmOperator *op, int opcontext)
584 {
585         wmWindow *window = CTX_wm_window(C);
586         uiPopupBlockHandle *handle;
587
588         handle = ui_popup_block_create(C, NULL, NULL, func, NULL, op);
589         handle->popup = 1;
590         handle->retvalue = 1;
591
592         handle->popup_arg = op;
593         handle->popup_func = operator_cb;
594         handle->cancel_func = confirm_cancel_operator;
595         handle->opcontext = opcontext;
596
597         UI_popup_handlers_add(C, &window->modalhandlers, handle, 0);
598         WM_event_add_mousemove(C);
599 }
600 #endif
601
602 void UI_popup_block_close(bContext *C, wmWindow *win, uiBlock *block)
603 {
604         /* if loading new .blend while popup is open, window will be NULL */
605         if (block->handle) {
606                 if (win) {
607                         UI_popup_handlers_remove(&win->modalhandlers, block->handle);
608                         ui_popup_block_free(C, block->handle);
609
610                         /* In the case we have nested popups, closing one may need to redraw another, see: T48874 */
611                         for (ARegion *ar = win->screen->regionbase.first; ar; ar = ar->next) {
612                                 ED_region_tag_refresh_ui(ar);
613                         }
614                 }
615         }
616 }
617
618 /** \} */