doxygen: add newline after \file
[blender.git] / source / blender / editors / interface / interface_region_menu_popup.c
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  * The Original Code is Copyright (C) 2008 Blender Foundation.
17  * All rights reserved.
18  */
19
20 /** \file
21  * \ingroup edinterface
22  *
23  * PopUp Menu Region
24  */
25
26 #include <stdarg.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <assert.h>
30
31 #include "MEM_guardedalloc.h"
32
33 #include "DNA_userdef_types.h"
34
35 #include "BLI_math.h"
36 #include "BLI_listbase.h"
37
38 #include "BLI_string.h"
39 #include "BLI_rect.h"
40 #include "BLI_utildefines.h"
41 #include "BLI_ghash.h"
42
43 #include "BKE_context.h"
44 #include "BKE_screen.h"
45 #include "BKE_report.h"
46
47 #include "WM_api.h"
48 #include "WM_types.h"
49
50 #include "RNA_access.h"
51
52 #include "UI_interface.h"
53
54 #include "BLT_translation.h"
55
56 #include "ED_screen.h"
57
58 #include "interface_intern.h"
59 #include "interface_regions_intern.h"
60
61 /* -------------------------------------------------------------------- */
62 /** \name Utility Functions
63  * \{ */
64
65 bool ui_but_menu_step_poll(const uiBut *but)
66 {
67         BLI_assert(but->type == UI_BTYPE_MENU);
68
69         /* currently only RNA buttons */
70         return ((but->menu_step_func != NULL) ||
71                 (but->rnaprop && RNA_property_type(but->rnaprop) == PROP_ENUM));
72 }
73
74 int ui_but_menu_step(uiBut *but, int direction)
75 {
76         if (ui_but_menu_step_poll(but)) {
77                 if (but->menu_step_func) {
78                         return but->menu_step_func(but->block->evil_C, direction, but->poin);
79                 }
80                 else {
81                         const int curval = RNA_property_enum_get(&but->rnapoin, but->rnaprop);
82                         return RNA_property_enum_step(but->block->evil_C, &but->rnapoin, but->rnaprop, curval, direction);
83                 }
84         }
85
86         printf("%s: cannot cycle button '%s'\n", __func__, but->str);
87         return 0;
88 }
89
90 static uint ui_popup_string_hash(const char *str)
91 {
92         /* sometimes button contains hotkey, sometimes not, strip for proper compare */
93         int hash;
94         const char *delimit = strrchr(str, UI_SEP_CHAR);
95
96         if (delimit) {
97                 hash = BLI_ghashutil_strhash_n(str, delimit - str);
98         }
99         else {
100                 hash = BLI_ghashutil_strhash(str);
101         }
102
103         return hash;
104 }
105
106 uint ui_popup_menu_hash(const char *str)
107 {
108         return BLI_ghashutil_strhash(str);
109 }
110
111 /* but == NULL read, otherwise set */
112 static uiBut *ui_popup_menu_memory__internal(uiBlock *block, uiBut *but)
113 {
114         static uint mem[256];
115         static bool first = true;
116
117         const uint hash = block->puphash;
118         const uint hash_mod = hash & 255;
119
120         if (first) {
121                 /* init */
122                 memset(mem, -1, sizeof(mem));
123                 first = 0;
124         }
125
126         if (but) {
127                 /* set */
128                 mem[hash_mod] = ui_popup_string_hash(but->str);
129                 return NULL;
130         }
131         else {
132                 /* get */
133                 for (but = block->buttons.first; but; but = but->next) {
134                         if (ui_popup_string_hash(but->str) == mem[hash_mod]) {
135                                 return but;
136                         }
137                 }
138
139                 return NULL;
140         }
141 }
142
143 uiBut *ui_popup_menu_memory_get(uiBlock *block)
144 {
145         return ui_popup_menu_memory__internal(block, NULL);
146 }
147
148 void ui_popup_menu_memory_set(uiBlock *block, uiBut *but)
149 {
150         ui_popup_menu_memory__internal(block, but);
151 }
152
153 /** \} */
154
155 /* -------------------------------------------------------------------- */
156 /** \name Popup Menu with Callback or String
157  * \{ */
158
159 struct uiPopupMenu {
160         uiBlock *block;
161         uiLayout *layout;
162         uiBut *but;
163         ARegion *butregion;
164
165         int mx, my;
166         bool popup, slideout;
167
168         uiMenuCreateFunc menu_func;
169         void *menu_arg;
170 };
171
172 static uiBlock *ui_block_func_POPUP(bContext *C, uiPopupBlockHandle *handle, void *arg_pup)
173 {
174         uiBlock *block;
175         uiPopupMenu *pup = arg_pup;
176         int minwidth, width, height;
177         char direction;
178         bool flip;
179
180         if (pup->menu_func) {
181                 pup->block->handle = handle;
182                 pup->menu_func(C, pup->layout, pup->menu_arg);
183                 pup->block->handle = NULL;
184         }
185
186         if (pup->but) {
187                 /* minimum width to enforece */
188                 if (pup->but->drawstr[0]) {
189                         minwidth = BLI_rctf_size_x(&pup->but->rect);
190                 }
191                 else {
192                         /* For buttons with no text, use the minimum (typically icon only). */
193                         minwidth = UI_MENU_WIDTH_MIN;
194                 }
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 = UI_MENU_WIDTH_MIN;
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_theme_style_set(block, UI_BLOCK_THEME_STYLE_POPUP);
239                 UI_block_direction_set(block, direction);
240
241                 /* offset the mouse position, possibly based on earlier selection */
242                 if ((block->flag & UI_BLOCK_POPUP_MEMORY) &&
243                     (bt = ui_popup_menu_memory_get(block)))
244                 {
245                         /* position mouse on last clicked item, at 0.8*width of the
246                          * button, so it doesn't overlap the text too much, also note
247                          * the offset is negative because we are inverse moving the
248                          * block to be under the mouse */
249                         offset[0] = -(bt->rect.xmin + 0.8f * BLI_rctf_size_x(&bt->rect));
250                         offset[1] = -(bt->rect.ymin + 0.5f * UI_UNIT_Y);
251
252                         if (ui_but_is_editable(bt)) {
253                                 but_activate = bt;
254                         }
255                 }
256                 else {
257                         /* position mouse at 0.8*width of the button and below the tile
258                          * on the first item */
259                         offset[0] = 0;
260                         for (bt = block->buttons.first; bt; bt = bt->next) {
261                                 offset[0] = min_ii(offset[0], -(bt->rect.xmin + 0.8f * BLI_rctf_size_x(&bt->rect)));
262                         }
263
264                         offset[1] = 2.1 * UI_UNIT_Y;
265
266                         for (bt = block->buttons.first; bt; bt = bt->next) {
267                                 if (ui_but_is_editable(bt)) {
268                                         but_activate = bt;
269                                         break;
270                                 }
271                         }
272                 }
273
274                 /* in rare cases this is needed since moving the popup
275                  * to be within the window bounds may move it away from the mouse,
276                  * This ensures we set an item to be active. */
277                 if (but_activate) {
278                         ui_but_activate_over(C, handle->region, but_activate);
279                 }
280
281                 block->minbounds = minwidth;
282                 UI_block_bounds_set_menu(block, 1, offset[0], offset[1]);
283         }
284         else {
285                 /* for a header menu we set the direction automatic */
286                 if (!pup->slideout && flip) {
287                         ScrArea *sa = CTX_wm_area(C);
288                         if (sa && ED_area_header_alignment(sa) == RGN_ALIGN_BOTTOM) {
289                                 ARegion *ar = CTX_wm_region(C);
290                                 if (ar && ar->regiontype == RGN_TYPE_HEADER) {
291                                         UI_block_direction_set(block, UI_DIR_UP);
292                                         UI_block_order_flip(block);
293                                 }
294                         }
295                 }
296
297                 block->minbounds = minwidth;
298                 UI_block_bounds_set_text(block, 3.0f * UI_UNIT_X);
299         }
300
301         /* if menu slides out of other menu, override direction */
302         if (pup->slideout) {
303                 UI_block_direction_set(block, UI_DIR_RIGHT);
304         }
305
306         return pup->block;
307 }
308
309 uiPopupBlockHandle *ui_popup_menu_create(
310         bContext *C, ARegion *butregion, uiBut *but,
311         uiMenuCreateFunc menu_func, void *arg)
312 {
313         wmWindow *window = CTX_wm_window(C);
314         uiStyle *style = UI_style_get_dpi();
315         uiPopupBlockHandle *handle;
316         uiPopupMenu *pup;
317
318         pup = MEM_callocN(sizeof(uiPopupMenu), __func__);
319         pup->block = UI_block_begin(C, NULL, __func__, UI_EMBOSS_PULLDOWN);
320         pup->block->flag |= UI_BLOCK_NUMSELECT;  /* default menus to numselect */
321         pup->layout = UI_block_layout(pup->block, UI_LAYOUT_VERTICAL, UI_LAYOUT_MENU, 0, 0, 200, 0, MENU_PADDING, style);
322         pup->slideout = but ? ui_block_is_menu(but->block) : false;
323         pup->but = but;
324         uiLayoutSetOperatorContext(pup->layout, WM_OP_INVOKE_REGION_WIN);
325
326         if (!but) {
327                 /* no button to start from, means we are a popup */
328                 pup->mx = window->eventstate->x;
329                 pup->my = window->eventstate->y;
330                 pup->popup = true;
331                 pup->block->flag |= UI_BLOCK_NO_FLIP;
332         }
333         /* some enums reversing is strange, currently we have no good way to
334          * reverse some enum's but not others, so reverse all so the first menu
335          * items are always close to the mouse cursor */
336         else {
337 #if 0
338                 /* if this is an rna button then we can assume its an enum
339                  * flipping enums is generally not good since the order can be
340                  * important [#28786] */
341                 if (but->rnaprop && RNA_property_type(but->rnaprop) == PROP_ENUM) {
342                         pup->block->flag |= UI_BLOCK_NO_FLIP;
343                 }
344 #endif
345                 if (but->context) {
346                         uiLayoutContextCopy(pup->layout, but->context);
347                 }
348         }
349
350         /* menu is created from a callback */
351         pup->menu_func = menu_func;
352         pup->menu_arg = arg;
353
354         handle = ui_popup_block_create(C, butregion, but, NULL, ui_block_func_POPUP, pup);
355
356         if (!but) {
357                 handle->popup = true;
358
359                 UI_popup_handlers_add(C, &window->modalhandlers, handle, 0);
360                 WM_event_add_mousemove(C);
361         }
362
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 -
391          * where having invoke doesn't make sense */
392         uiLayoutSetOperatorContext(pup->layout, WM_OP_EXEC_REGION_WIN);
393
394         /* create in advance so we can let buttons point to retval already */
395         pup->block->handle = MEM_callocN(sizeof(uiPopupBlockHandle), "uiPopupBlockHandle");
396
397         /* create title button */
398         if (title[0]) {
399                 char titlestr[256];
400
401                 if (icon) {
402                         BLI_snprintf(titlestr, sizeof(titlestr), " %s", title);
403                         uiDefIconTextBut(
404                                 pup->block, UI_BTYPE_LABEL, 0, icon, titlestr, 0, 0, 200, UI_UNIT_Y, NULL, 0.0, 0.0, 0, 0, "");
405                 }
406                 else {
407                         but = uiDefBut(pup->block, UI_BTYPE_LABEL, 0, title, 0, 0, 200, UI_UNIT_Y, NULL, 0.0, 0.0, 0, 0, "");
408                         but->drawflag = UI_BUT_TEXT_LEFT;
409                 }
410
411                 uiItemS(pup->layout);
412         }
413
414         return pup;
415 }
416
417 uiPopupMenu *UI_popup_menu_begin(bContext *C, const char *title, int icon)
418 {
419         return UI_popup_menu_begin_ex(C, title, __func__, icon);
420 }
421
422 /**
423  * Setting the button makes the popup open from the button instead of the cursor.
424  */
425 void UI_popup_menu_but_set(uiPopupMenu *pup, struct ARegion *butregion, uiBut *but)
426 {
427         pup->but = but;
428         pup->butregion = butregion;
429 }
430
431 /* set the whole structure to work */
432 void UI_popup_menu_end(bContext *C, uiPopupMenu *pup)
433 {
434         wmWindow *window = CTX_wm_window(C);
435         uiPopupBlockHandle *menu;
436         uiBut *but = NULL;
437         ARegion *butregion = NULL;
438
439         pup->popup = true;
440         pup->mx = window->eventstate->x;
441         pup->my = window->eventstate->y;
442
443         if (pup->but) {
444                 but = pup->but;
445                 butregion = pup->butregion;
446         }
447
448         menu = ui_popup_block_create(C, butregion, but, NULL, ui_block_func_POPUP, pup);
449         menu->popup = true;
450
451         UI_popup_handlers_add(C, &window->modalhandlers, menu, 0);
452         WM_event_add_mousemove(C);
453
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->can_refresh = true;
573         handle->optype = (opname) ? WM_operatortype_find(opname, 0) : NULL;
574         handle->opcontext = opcontext;
575
576         UI_popup_handlers_add(C, &window->modalhandlers, handle, 0);
577         WM_event_add_mousemove(C);
578 }
579
580 void UI_popup_block_invoke(bContext *C, uiBlockCreateFunc func, void *arg)
581 {
582         UI_popup_block_invoke_ex(C, func, arg, NULL, WM_OP_INVOKE_DEFAULT);
583 }
584
585 void UI_popup_block_ex(
586         bContext *C, uiBlockCreateFunc func, uiBlockHandleFunc popup_func, uiBlockCancelFunc cancel_func,
587         void *arg, wmOperator *op)
588 {
589         wmWindow *window = CTX_wm_window(C);
590         uiPopupBlockHandle *handle;
591
592         handle = ui_popup_block_create(C, NULL, NULL, func, NULL, arg);
593         handle->popup = true;
594         handle->retvalue = 1;
595         handle->can_refresh = true;
596
597         handle->popup_op = op;
598         handle->popup_arg = arg;
599         handle->popup_func = popup_func;
600         handle->cancel_func = cancel_func;
601         // handle->opcontext = opcontext;
602
603         UI_popup_handlers_add(C, &window->modalhandlers, handle, 0);
604         WM_event_add_mousemove(C);
605 }
606
607 #if 0 /* UNUSED */
608 void uiPupBlockOperator(bContext *C, uiBlockCreateFunc func, wmOperator *op, int opcontext)
609 {
610         wmWindow *window = CTX_wm_window(C);
611         uiPopupBlockHandle *handle;
612
613         handle = ui_popup_block_create(C, NULL, NULL, func, NULL, op);
614         handle->popup = 1;
615         handle->retvalue = 1;
616         handle->can_refresh = true;
617
618         handle->popup_arg = op;
619         handle->popup_func = operator_cb;
620         handle->cancel_func = confirm_cancel_operator;
621         handle->opcontext = opcontext;
622
623         UI_popup_handlers_add(C, &window->modalhandlers, handle, 0);
624         WM_event_add_mousemove(C);
625 }
626 #endif
627
628 void UI_popup_block_close(bContext *C, wmWindow *win, uiBlock *block)
629 {
630         /* if loading new .blend while popup is open, window will be NULL */
631         if (block->handle) {
632                 if (win) {
633                         const bScreen *screen = WM_window_get_active_screen(win);
634
635                         UI_popup_handlers_remove(&win->modalhandlers, block->handle);
636                         ui_popup_block_free(C, block->handle);
637
638                         /* In the case we have nested popups,
639                          * closing one may need to redraw another, see: T48874 */
640                         for (ARegion *ar = screen->regionbase.first; ar; ar = ar->next) {
641                                 ED_region_tag_refresh_ui(ar);
642                         }
643                 }
644         }
645 }
646
647 bool UI_popup_block_name_exists(bContext *C, const char *name)
648 {
649         bScreen *sc = CTX_wm_screen(C);
650         uiBlock *block;
651         ARegion *ar;
652
653         for (ar = sc->regionbase.first; ar; ar = ar->next) {
654                 for (block = ar->uiblocks.first; block; block = block->next) {
655                         if (STREQ(block->name, name)) {
656                                 return true;
657                         }
658                 }
659         }
660         return false;
661 }
662
663 /** \} */