UI: naming of cursor options
[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 uiLayout *UI_popup_menu_layout(uiPopupMenu *pup)
462 {
463         return pup->layout;
464 }
465
466 /** \} */
467
468 /* -------------------------------------------------------------------- */
469 /** \name Standard Popup Menus
470  * \{ */
471
472 void UI_popup_menu_reports(bContext *C, ReportList *reports)
473 {
474         Report *report;
475
476         uiPopupMenu *pup = NULL;
477         uiLayout *layout;
478
479         if (!CTX_wm_window(C))
480                 return;
481
482         for (report = reports->list.first; report; report = report->next) {
483                 int icon;
484                 const char *msg, *msg_next;
485
486                 if (report->type < reports->printlevel) {
487                         continue;
488                 }
489
490                 if (pup == NULL) {
491                         char title[UI_MAX_DRAW_STR];
492                         BLI_snprintf(title, sizeof(title), "%s: %s", IFACE_("Report"), report->typestr);
493                         /* popup_menu stuff does just what we need (but pass meaningful block name) */
494                         pup = UI_popup_menu_begin_ex(C, title, __func__, ICON_NONE);
495                         layout = UI_popup_menu_layout(pup);
496                 }
497                 else {
498                         uiItemS(layout);
499                 }
500
501                 /* split each newline into a label */
502                 msg = report->message;
503                 icon = UI_icon_from_report_type(report->type);
504                 do {
505                         char buf[UI_MAX_DRAW_STR];
506                         msg_next = strchr(msg, '\n');
507                         if (msg_next) {
508                                 msg_next++;
509                                 BLI_strncpy(buf, msg, MIN2(sizeof(buf), msg_next - msg));
510                                 msg = buf;
511                         }
512                         uiItemL(layout, msg, icon);
513                         icon = ICON_NONE;
514                 } while ((msg = msg_next) && *msg);
515         }
516
517         if (pup) {
518                 UI_popup_menu_end(C, pup);
519         }
520 }
521
522 int UI_popup_menu_invoke(bContext *C, const char *idname, ReportList *reports)
523 {
524         uiPopupMenu *pup;
525         uiLayout *layout;
526         MenuType *mt = WM_menutype_find(idname, true);
527
528         if (mt == NULL) {
529                 BKE_reportf(reports, RPT_ERROR, "Menu \"%s\" not found", idname);
530                 return OPERATOR_CANCELLED;
531         }
532
533         if (WM_menutype_poll(C, mt) == false) {
534                 /* cancel but allow event to pass through, just like operators do */
535                 return (OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH);
536         }
537
538         pup = UI_popup_menu_begin(C, IFACE_(mt->label), ICON_NONE);
539         layout = UI_popup_menu_layout(pup);
540
541         UI_menutype_draw(C, mt, layout);
542
543         UI_popup_menu_end(C, pup);
544
545         return OPERATOR_INTERFACE;
546 }
547
548 /** \} */
549
550 /* -------------------------------------------------------------------- */
551 /** \name Popup Block API
552  * \{ */
553
554 void UI_popup_block_invoke_ex(bContext *C, uiBlockCreateFunc func, void *arg, const char *opname, int opcontext)
555 {
556         wmWindow *window = CTX_wm_window(C);
557         uiPopupBlockHandle *handle;
558
559         handle = ui_popup_block_create(C, NULL, NULL, func, NULL, arg);
560         handle->popup = true;
561         handle->can_refresh = true;
562         handle->optype = (opname) ? WM_operatortype_find(opname, 0) : NULL;
563         handle->opcontext = opcontext;
564
565         UI_popup_handlers_add(C, &window->modalhandlers, handle, 0);
566         WM_event_add_mousemove(C);
567 }
568
569 void UI_popup_block_invoke(bContext *C, uiBlockCreateFunc func, void *arg)
570 {
571         UI_popup_block_invoke_ex(C, func, arg, NULL, WM_OP_INVOKE_DEFAULT);
572 }
573
574 void UI_popup_block_ex(
575         bContext *C, uiBlockCreateFunc func, uiBlockHandleFunc popup_func, uiBlockCancelFunc cancel_func,
576         void *arg, wmOperator *op)
577 {
578         wmWindow *window = CTX_wm_window(C);
579         uiPopupBlockHandle *handle;
580
581         handle = ui_popup_block_create(C, NULL, NULL, func, NULL, arg);
582         handle->popup = true;
583         handle->retvalue = 1;
584         handle->can_refresh = true;
585
586         handle->popup_op = op;
587         handle->popup_arg = arg;
588         handle->popup_func = popup_func;
589         handle->cancel_func = cancel_func;
590         // handle->opcontext = opcontext;
591
592         UI_popup_handlers_add(C, &window->modalhandlers, handle, 0);
593         WM_event_add_mousemove(C);
594 }
595
596 #if 0 /* UNUSED */
597 void uiPupBlockOperator(bContext *C, uiBlockCreateFunc func, wmOperator *op, int opcontext)
598 {
599         wmWindow *window = CTX_wm_window(C);
600         uiPopupBlockHandle *handle;
601
602         handle = ui_popup_block_create(C, NULL, NULL, func, NULL, op);
603         handle->popup = 1;
604         handle->retvalue = 1;
605         handle->can_refresh = true;
606
607         handle->popup_arg = op;
608         handle->popup_func = operator_cb;
609         handle->cancel_func = confirm_cancel_operator;
610         handle->opcontext = opcontext;
611
612         UI_popup_handlers_add(C, &window->modalhandlers, handle, 0);
613         WM_event_add_mousemove(C);
614 }
615 #endif
616
617 void UI_popup_block_close(bContext *C, wmWindow *win, uiBlock *block)
618 {
619         /* if loading new .blend while popup is open, window will be NULL */
620         if (block->handle) {
621                 if (win) {
622                         const bScreen *screen = WM_window_get_active_screen(win);
623
624                         UI_popup_handlers_remove(&win->modalhandlers, block->handle);
625                         ui_popup_block_free(C, block->handle);
626
627                         /* In the case we have nested popups, closing one may need to redraw another, see: T48874 */
628                         for (ARegion *ar = screen->regionbase.first; ar; ar = ar->next) {
629                                 ED_region_tag_refresh_ui(ar);
630                         }
631                 }
632         }
633 }
634
635 /** \} */