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