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