Merge branch 'master' into blender2.8
[blender.git] / source / blender / editors / interface / interface_region_menu_pie.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_pie.c
27  *  \ingroup edinterface
28  *
29  * Pie 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_blenlib.h"
42 #include "BLI_utildefines.h"
43
44 #include "PIL_time.h"
45
46 #include "BKE_context.h"
47 #include "BKE_screen.h"
48
49 #include "WM_api.h"
50 #include "WM_types.h"
51
52 #include "RNA_access.h"
53
54 #include "UI_interface.h"
55
56 #include "BLT_translation.h"
57
58 #include "ED_screen.h"
59
60 #include "interface_intern.h"
61 #include "interface_regions_intern.h"
62
63 /* -------------------------------------------------------------------- */
64 /** \name Pie Menu
65  * \{ */
66
67 struct uiPieMenu {
68         uiBlock *block_radial; /* radial block of the pie menu (more could be added later) */
69         uiLayout *layout;
70         int mx, my;
71 };
72
73 static uiBlock *ui_block_func_PIE(bContext *UNUSED(C), uiPopupBlockHandle *handle, void *arg_pie)
74 {
75         uiBlock *block;
76         uiPieMenu *pie = arg_pie;
77         int minwidth, width, height;
78
79         minwidth = UI_MENU_WIDTH_MIN;
80         block = pie->block_radial;
81
82         /* in some cases we create the block before the region,
83          * so we set it delayed here if necessary */
84         if (BLI_findindex(&handle->region->uiblocks, block) == -1)
85                 UI_block_region_set(block, handle->region);
86
87         UI_block_layout_resolve(block, &width, &height);
88
89         UI_block_flag_enable(block, UI_BLOCK_LOOP | UI_BLOCK_NUMSELECT);
90         UI_block_theme_style_set(block, UI_BLOCK_THEME_STYLE_POPUP);
91
92         block->minbounds = minwidth;
93         block->bounds = 1;
94         block->mx = 0;
95         block->my = 0;
96         block->bounds_type = UI_BLOCK_BOUNDS_PIE_CENTER;
97
98         block->pie_data.pie_center_spawned[0] = pie->mx;
99         block->pie_data.pie_center_spawned[1] = pie->my;
100
101         return pie->block_radial;
102 }
103
104 static float ui_pie_menu_title_width(const char *name, int icon)
105 {
106         const uiFontStyle *fstyle = UI_FSTYLE_WIDGET;
107         return (UI_fontstyle_string_width(fstyle, name) +
108                  (UI_UNIT_X * (1.50f + (icon ? 0.25f : 0.0f))));
109 }
110
111 uiPieMenu *UI_pie_menu_begin(struct bContext *C, const char *title, int icon, const wmEvent *event)
112 {
113         uiStyle *style;
114         uiPieMenu *pie;
115         short event_type;
116
117         wmWindow *win = CTX_wm_window(C);
118
119         style = UI_style_get_dpi();
120         pie = MEM_callocN(sizeof(*pie), "pie menu");
121
122         pie->block_radial = UI_block_begin(C, NULL, __func__, UI_EMBOSS);
123         /* may be useful later to allow spawning pies
124          * from old positions */
125         /* pie->block_radial->flag |= UI_BLOCK_POPUP_MEMORY; */
126         pie->block_radial->puphash = ui_popup_menu_hash(title);
127         pie->block_radial->flag |= UI_BLOCK_RADIAL;
128
129         /* if pie is spawned by a left click, release or click event, it is always assumed to be click style */
130         if (event->type == LEFTMOUSE || ELEM(event->val, KM_RELEASE, KM_CLICK)) {
131                 pie->block_radial->pie_data.flags |= UI_PIE_CLICK_STYLE;
132                 pie->block_radial->pie_data.event = EVENT_NONE;
133                 win->lock_pie_event = EVENT_NONE;
134         }
135         else {
136                 if (win->last_pie_event != EVENT_NONE) {
137                         /* original pie key has been released, so don't propagate the event */
138                         if (win->lock_pie_event == EVENT_NONE) {
139                                 event_type = EVENT_NONE;
140                                 pie->block_radial->pie_data.flags |= UI_PIE_CLICK_STYLE;
141                         }
142                         else
143                                 event_type = win->last_pie_event;
144                 }
145                 else {
146                         event_type = event->type;
147                 }
148
149                 pie->block_radial->pie_data.event = event_type;
150                 win->lock_pie_event = event_type;
151         }
152
153         pie->layout = UI_block_layout(pie->block_radial, UI_LAYOUT_VERTICAL, UI_LAYOUT_PIEMENU, 0, 0, 200, 0, 0, style);
154
155         /* Open from where we started dragging. */
156         if (event->val == KM_CLICK_DRAG) {
157                 pie->mx = event->prevclickx;
158                 pie->my = event->prevclicky;
159         }
160         else {
161                 pie->mx = event->x;
162                 pie->my = event->y;
163         }
164
165         /* create title button */
166         if (title[0]) {
167                 uiBut *but;
168                 char titlestr[256];
169                 int w;
170                 if (icon) {
171                         BLI_snprintf(titlestr, sizeof(titlestr), " %s", title);
172                         w = ui_pie_menu_title_width(titlestr, icon);
173                         but = uiDefIconTextBut(
174                                 pie->block_radial, UI_BTYPE_LABEL, 0, icon, titlestr, 0, 0, w, UI_UNIT_Y,
175                                 NULL, 0.0, 0.0, 0, 0, "");
176                 }
177                 else {
178                         w = ui_pie_menu_title_width(title, 0);
179                         but = uiDefBut(
180                                 pie->block_radial, UI_BTYPE_LABEL, 0, title, 0, 0, w, UI_UNIT_Y,
181                                 NULL, 0.0, 0.0, 0, 0, "");
182                 }
183                 /* do not align left */
184                 but->drawflag &= ~UI_BUT_TEXT_LEFT;
185                 pie->block_radial->pie_data.title = but->str;
186                 pie->block_radial->pie_data.icon = icon;
187         }
188
189         return pie;
190 }
191
192 void UI_pie_menu_end(bContext *C, uiPieMenu *pie)
193 {
194         wmWindow *window = CTX_wm_window(C);
195         uiPopupBlockHandle *menu;
196
197         menu = ui_popup_block_create(C, NULL, NULL, NULL, ui_block_func_PIE, pie);
198         menu->popup = true;
199         menu->towardstime = PIL_check_seconds_timer();
200
201         UI_popup_handlers_add(
202                 C, &window->modalhandlers,
203                 menu, WM_HANDLER_ACCEPT_DBL_CLICK);
204         WM_event_add_mousemove(C);
205
206         MEM_freeN(pie);
207 }
208
209 uiLayout *UI_pie_menu_layout(uiPieMenu *pie)
210 {
211         return pie->layout;
212 }
213
214 int UI_pie_menu_invoke(struct bContext *C, const char *idname, const wmEvent *event)
215 {
216         uiPieMenu *pie;
217         uiLayout *layout;
218         MenuType *mt = WM_menutype_find(idname, true);
219
220         if (mt == NULL) {
221                 printf("%s: named menu \"%s\" not found\n", __func__, idname);
222                 return OPERATOR_CANCELLED;
223         }
224
225         if (WM_menutype_poll(C, mt) == false) {
226                 /* cancel but allow event to pass through, just like operators do */
227                 return (OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH);
228         }
229
230         pie = UI_pie_menu_begin(C, IFACE_(mt->label), ICON_NONE, event);
231         layout = UI_pie_menu_layout(pie);
232
233         UI_menutype_draw(C, mt, layout);
234
235         UI_pie_menu_end(C, pie);
236
237         return OPERATOR_INTERFACE;
238 }
239
240 int UI_pie_menu_invoke_from_operator_enum(
241         struct bContext *C, const char *title, const char *opname,
242         const char *propname, const wmEvent *event)
243 {
244         uiPieMenu *pie;
245         uiLayout *layout;
246
247         pie = UI_pie_menu_begin(C, IFACE_(title), ICON_NONE, event);
248         layout = UI_pie_menu_layout(pie);
249
250         layout = uiLayoutRadial(layout);
251         uiItemsEnumO(layout, opname, propname);
252
253         UI_pie_menu_end(C, pie);
254
255         return OPERATOR_INTERFACE;
256 }
257
258 int UI_pie_menu_invoke_from_rna_enum(
259         struct bContext *C, const char *title, const char *path,
260         const wmEvent *event)
261 {
262         PointerRNA ctx_ptr;
263         PointerRNA r_ptr;
264         PropertyRNA *r_prop;
265         uiPieMenu *pie;
266         uiLayout *layout;
267
268         RNA_pointer_create(NULL, &RNA_Context, C, &ctx_ptr);
269
270         if (!RNA_path_resolve(&ctx_ptr, path, &r_ptr, &r_prop)) {
271                 return OPERATOR_CANCELLED;
272         }
273
274         /* invalid property, only accept enums */
275         if (RNA_property_type(r_prop) != PROP_ENUM) {
276                 BLI_assert(0);
277                 return OPERATOR_CANCELLED;
278         }
279
280         pie = UI_pie_menu_begin(C, IFACE_(title), ICON_NONE, event);
281
282         layout = UI_pie_menu_layout(pie);
283
284         layout = uiLayoutRadial(layout);
285         uiItemFullR(layout, &r_ptr, r_prop, RNA_NO_INDEX, 0, UI_ITEM_R_EXPAND, NULL, 0);
286
287         UI_pie_menu_end(C, pie);
288
289         return OPERATOR_INTERFACE;
290 }
291
292 /** \} */
293
294 /* -------------------------------------------------------------------- */
295 /**
296  * \name Pie Menu Levels
297  *
298  * Pie menus can't contain more than 8 items (yet). When using #uiItemsFullEnumO, a "More" button is created that calls
299  * a new pie menu if the enum has too many items. We call this a new "level".
300  * Indirect recursion is used, so that a theoretically unlimited number of items is supported.
301  *
302  * This is a implementation specifically for operator enums, needed since the object mode pie now has more than 8
303  * items. Ideally we'd have some way of handling this for all kinds of pie items, but that's tricky.
304  *
305  * - Julian (Feb 2016)
306  *
307  * \{ */
308
309 typedef struct PieMenuLevelData {
310         char title[UI_MAX_NAME_STR]; /* parent pie title, copied for level */
311         int icon; /* parent pie icon, copied for level */
312         int totitem; /* total count of *remaining* items */
313
314         /* needed for calling uiItemsFullEnumO_array again for new level */
315         wmOperatorType *ot;
316         const char *propname;
317         IDProperty *properties;
318         int context, flag;
319 } PieMenuLevelData;
320
321 /**
322  * Invokes a new pie menu for a new level.
323  */
324 static void ui_pie_menu_level_invoke(bContext *C, void *argN, void *arg2)
325 {
326         EnumPropertyItem *item_array = (EnumPropertyItem *)argN;
327         PieMenuLevelData *lvl = (PieMenuLevelData *)arg2;
328         wmWindow *win = CTX_wm_window(C);
329
330         uiPieMenu *pie = UI_pie_menu_begin(C, IFACE_(lvl->title), lvl->icon, win->eventstate);
331         uiLayout *layout = UI_pie_menu_layout(pie);
332
333         layout = uiLayoutRadial(layout);
334
335         PointerRNA ptr;
336
337         WM_operator_properties_create_ptr(&ptr, lvl->ot);
338         /* so the context is passed to itemf functions (some need it) */
339         WM_operator_properties_sanitize(&ptr, false);
340         PropertyRNA *prop = RNA_struct_find_property(&ptr, lvl->propname);
341
342         if (prop) {
343                 uiItemsFullEnumO_items(
344                         layout, lvl->ot, ptr, prop, lvl->properties, lvl->context, lvl->flag,
345                         item_array, lvl->totitem);
346         }
347         else {
348                 RNA_warning("%s.%s not found", RNA_struct_identifier(ptr.type), lvl->propname);
349         }
350
351         UI_pie_menu_end(C, pie);
352 }
353
354 /**
355  * Set up data for defining a new pie menu level and add button that invokes it.
356  */
357 void ui_pie_menu_level_create(
358         uiBlock *block, wmOperatorType *ot, const char *propname, IDProperty *properties,
359         const EnumPropertyItem *items, int totitem, int context, int flag)
360 {
361         const int totitem_parent = PIE_MAX_ITEMS - 1;
362         const int totitem_remain = totitem - totitem_parent;
363         size_t array_size = sizeof(EnumPropertyItem) * totitem_remain;
364
365         /* used as but->func_argN so freeing is handled elsewhere */
366         EnumPropertyItem *remaining = MEM_mallocN(array_size + sizeof(EnumPropertyItem), "pie_level_item_array");
367         memcpy(remaining, items + totitem_parent, array_size);
368         /* a NULL terminating sentinal element is required */
369         memset(&remaining[totitem_remain], 0, sizeof(EnumPropertyItem));
370
371
372         /* yuk, static... issue is we can't reliably free this without doing dangerous changes */
373         static PieMenuLevelData lvl;
374         BLI_strncpy(lvl.title, block->pie_data.title, UI_MAX_NAME_STR);
375         lvl.totitem    = totitem_remain;
376         lvl.ot         = ot;
377         lvl.propname   = propname;
378         lvl.properties = properties;
379         lvl.context    = context;
380         lvl.flag       = flag;
381
382         /* add a 'more' menu entry */
383         uiBut *but = uiDefIconTextBut(block, UI_BTYPE_BUT, 0, ICON_PLUS, "More", 0, 0, UI_UNIT_X * 3, UI_UNIT_Y, NULL,
384                                       0.0f, 0.0f, 0.0f, 0.0f, "Show more items of this menu");
385         UI_but_funcN_set(but, ui_pie_menu_level_invoke, remaining, &lvl);
386 }
387
388 /** \} */ /* Pie Menu Levels */