9129bbddf69f8144ff47d54b5e0b111fb4d030cf
[blender.git] / source / blender / editors / interface / interface_query.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
17 /** \file
18  * \ingroup edinterface
19  *
20  * Utilities to inspect the interface, extract information.
21  */
22
23 #include "BLI_utildefines.h"
24 #include "BLI_math.h"
25 #include "BLI_rect.h"
26
27 #include "DNA_screen_types.h"
28
29 #include "UI_interface.h"
30 #include "UI_view2d.h"
31
32 #include "RNA_access.h"
33
34 #include "interface_intern.h"
35
36 #include "WM_api.h"
37 #include "WM_types.h"
38
39 /* -------------------------------------------------------------------- */
40 /** \name Button (#uiBut) State
41  * \{ */
42
43 bool ui_but_is_editable(const uiBut *but)
44 {
45   return !ELEM(but->type,
46                UI_BTYPE_LABEL,
47                UI_BTYPE_SEPR,
48                UI_BTYPE_SEPR_LINE,
49                UI_BTYPE_ROUNDBOX,
50                UI_BTYPE_LISTBOX,
51                UI_BTYPE_PROGRESS_BAR);
52 }
53
54 bool ui_but_is_editable_as_text(const uiBut *but)
55 {
56   return ELEM(but->type, UI_BTYPE_TEXT, UI_BTYPE_NUM, UI_BTYPE_NUM_SLIDER, UI_BTYPE_SEARCH_MENU);
57 }
58
59 bool ui_but_is_toggle(const uiBut *but)
60 {
61   return ELEM(but->type,
62               UI_BTYPE_BUT_TOGGLE,
63               UI_BTYPE_TOGGLE,
64               UI_BTYPE_ICON_TOGGLE,
65               UI_BTYPE_ICON_TOGGLE_N,
66               UI_BTYPE_TOGGLE_N,
67               UI_BTYPE_CHECKBOX,
68               UI_BTYPE_CHECKBOX_N,
69               UI_BTYPE_ROW);
70 }
71
72 /**
73  * Can we mouse over the button or is it hidden/disabled/layout.
74  * \note ctrl is kind of a hack currently,
75  * so that non-embossed UI_BTYPE_TEXT button behaves as a label when ctrl is not pressed.
76  */
77 bool ui_but_is_interactive(const uiBut *but, const bool labeledit)
78 {
79   /* note, UI_BTYPE_LABEL is included for highlights, this allows drags */
80   if ((but->type == UI_BTYPE_LABEL) && but->dragpoin == NULL) {
81     return false;
82   }
83   if (ELEM(but->type, UI_BTYPE_ROUNDBOX, UI_BTYPE_SEPR, UI_BTYPE_SEPR_LINE, UI_BTYPE_LISTBOX)) {
84     return false;
85   }
86   if (but->flag & UI_HIDDEN) {
87     return false;
88   }
89   if (but->flag & UI_SCROLLED) {
90     return false;
91   }
92   if ((but->type == UI_BTYPE_TEXT) && (but->dt == UI_EMBOSS_NONE) && !labeledit) {
93     return false;
94   }
95   if ((but->type == UI_BTYPE_LISTROW) && labeledit) {
96     return false;
97   }
98
99   return true;
100 }
101
102 /* file selectors are exempt from utf-8 checks */
103 bool ui_but_is_utf8(const uiBut *but)
104 {
105   if (but->rnaprop) {
106     const int subtype = RNA_property_subtype(but->rnaprop);
107     return !(ELEM(subtype, PROP_FILEPATH, PROP_DIRPATH, PROP_FILENAME, PROP_BYTESTRING));
108   }
109   else {
110     return !(but->flag & UI_BUT_NO_UTF8);
111   }
112 }
113
114 #ifdef USE_UI_POPOVER_ONCE
115 bool ui_but_is_popover_once_compat(const uiBut *but)
116 {
117   return ((but->type == UI_BTYPE_BUT) || ui_but_is_toggle(but));
118 }
119 #endif
120
121 bool ui_but_has_array_value(const uiBut *but)
122 {
123   return (but->rnapoin.data && but->rnaprop &&
124           ELEM(RNA_property_subtype(but->rnaprop),
125                PROP_COLOR,
126                PROP_TRANSLATION,
127                PROP_DIRECTION,
128                PROP_VELOCITY,
129                PROP_ACCELERATION,
130                PROP_MATRIX,
131                PROP_EULER,
132                PROP_QUATERNION,
133                PROP_AXISANGLE,
134                PROP_XYZ,
135                PROP_XYZ_LENGTH,
136                PROP_COLOR_GAMMA,
137                PROP_COORDS));
138 }
139
140 bool UI_but_is_tool(const uiBut *but)
141 {
142   /* very evil! */
143   if (but->optype != NULL) {
144     static wmOperatorType *ot = NULL;
145     if (ot == NULL) {
146       ot = WM_operatortype_find("WM_OT_tool_set_by_id", false);
147     }
148     if (but->optype == ot) {
149       return true;
150     }
151   }
152   return false;
153 }
154
155 bool UI_but_has_tooltip_label(const uiBut *but)
156 {
157   if ((but->drawstr[0] == '\0') && !ui_block_is_popover(but->block)) {
158     return UI_but_is_tool(but);
159   }
160   return false;
161 }
162
163 /** \} */
164
165 /* -------------------------------------------------------------------- */
166 /** \name Button (#uiBut) Spatial
167  * \{ */
168
169 void ui_but_pie_dir(RadialDirection dir, float vec[2])
170 {
171   float angle;
172
173   BLI_assert(dir != UI_RADIAL_NONE);
174
175   angle = DEG2RADF((float)ui_radial_dir_to_angle[dir]);
176   vec[0] = cosf(angle);
177   vec[1] = sinf(angle);
178 }
179
180 static bool ui_but_isect_pie_seg(const uiBlock *block, const uiBut *but)
181 {
182   const float angle_range = (block->pie_data.flags & UI_PIE_DEGREES_RANGE_LARGE) ? M_PI_4 :
183                                                                                    M_PI_4 / 2.0;
184   float vec[2];
185
186   if (block->pie_data.flags & UI_PIE_INVALID_DIR) {
187     return false;
188   }
189
190   ui_but_pie_dir(but->pie_dir, vec);
191
192   if (saacos(dot_v2v2(vec, block->pie_data.pie_dir)) < angle_range) {
193     return true;
194   }
195
196   return false;
197 }
198
199 bool ui_but_contains_pt(const uiBut *but, float mx, float my)
200 {
201   return BLI_rctf_isect_pt(&but->rect, mx, my);
202 }
203
204 bool ui_but_contains_point_px(const uiBut *but, const ARegion *ar, int x, int y)
205 {
206   uiBlock *block = but->block;
207   float mx, my;
208   if (!ui_region_contains_point_px(ar, x, y)) {
209     return false;
210   }
211
212   mx = x;
213   my = y;
214
215   ui_window_to_block_fl(ar, block, &mx, &my);
216
217   if (but->pie_dir != UI_RADIAL_NONE) {
218     if (!ui_but_isect_pie_seg(block, but)) {
219       return false;
220     }
221   }
222   else if (!ui_but_contains_pt(but, mx, my)) {
223     return false;
224   }
225
226   return true;
227 }
228
229 bool ui_but_contains_point_px_icon(const uiBut *but, ARegion *ar, const wmEvent *event)
230 {
231   rcti rect;
232   int x = event->x, y = event->y;
233
234   ui_window_to_block(ar, but->block, &x, &y);
235
236   BLI_rcti_rctf_copy(&rect, &but->rect);
237
238   if (but->imb || but->type == UI_BTYPE_COLOR) {
239     /* use button size itself */
240   }
241   else if (but->drawflag & UI_BUT_ICON_LEFT) {
242     rect.xmax = rect.xmin + (BLI_rcti_size_y(&rect));
243   }
244   else {
245     int delta = BLI_rcti_size_x(&rect) - BLI_rcti_size_y(&rect);
246     rect.xmin += delta / 2;
247     rect.xmax -= delta / 2;
248   }
249
250   return BLI_rcti_isect_pt(&rect, x, y);
251 }
252
253 /* x and y are only used in case event is NULL... */
254 uiBut *ui_but_find_mouse_over_ex(ARegion *ar, const int x, const int y, const bool labeledit)
255 {
256   uiBlock *block;
257   uiBut *but, *butover = NULL;
258   float mx, my;
259
260   //  if (!win->active) {
261   //      return NULL;
262   //  }
263   if (!ui_region_contains_point_px(ar, x, y)) {
264     return NULL;
265   }
266
267   for (block = ar->uiblocks.first; block; block = block->next) {
268     mx = x;
269     my = y;
270     ui_window_to_block_fl(ar, block, &mx, &my);
271
272     for (but = block->buttons.last; but; but = but->prev) {
273       if (ui_but_is_interactive(but, labeledit)) {
274         if (but->pie_dir != UI_RADIAL_NONE) {
275           if (ui_but_isect_pie_seg(block, but)) {
276             butover = but;
277             break;
278           }
279         }
280         else if (ui_but_contains_pt(but, mx, my)) {
281           butover = but;
282           break;
283         }
284       }
285     }
286
287     /* CLIP_EVENTS prevents the event from reaching other blocks */
288     if (block->flag & UI_BLOCK_CLIP_EVENTS) {
289       /* check if mouse is inside block */
290       if (BLI_rctf_isect_pt(&block->rect, mx, my)) {
291         break;
292       }
293     }
294   }
295
296   return butover;
297 }
298
299 uiBut *ui_but_find_mouse_over(ARegion *ar, const wmEvent *event)
300 {
301   return ui_but_find_mouse_over_ex(ar, event->x, event->y, event->ctrl != 0);
302 }
303
304 uiBut *ui_list_find_mouse_over_ex(ARegion *ar, int x, int y)
305 {
306   uiBlock *block;
307   uiBut *but;
308   float mx, my;
309
310   if (!ui_region_contains_point_px(ar, x, y)) {
311     return NULL;
312   }
313
314   for (block = ar->uiblocks.first; block; block = block->next) {
315     mx = x;
316     my = y;
317     ui_window_to_block_fl(ar, block, &mx, &my);
318
319     for (but = block->buttons.last; but; but = but->prev) {
320       if (but->type == UI_BTYPE_LISTBOX && ui_but_contains_pt(but, mx, my)) {
321         return but;
322       }
323     }
324   }
325
326   return NULL;
327 }
328
329 uiBut *ui_list_find_mouse_over(ARegion *ar, const wmEvent *event)
330 {
331   return ui_list_find_mouse_over_ex(ar, event->x, event->y);
332 }
333
334 /** \} */
335
336 /* -------------------------------------------------------------------- */
337 /** \name Button (#uiBut) Relations
338  * \{ */
339
340 uiBut *ui_but_prev(uiBut *but)
341 {
342   while (but->prev) {
343     but = but->prev;
344     if (ui_but_is_editable(but)) {
345       return but;
346     }
347   }
348   return NULL;
349 }
350
351 uiBut *ui_but_next(uiBut *but)
352 {
353   while (but->next) {
354     but = but->next;
355     if (ui_but_is_editable(but)) {
356       return but;
357     }
358   }
359   return NULL;
360 }
361
362 uiBut *ui_but_first(uiBlock *block)
363 {
364   uiBut *but;
365
366   but = block->buttons.first;
367   while (but) {
368     if (ui_but_is_editable(but)) {
369       return but;
370     }
371     but = but->next;
372   }
373   return NULL;
374 }
375
376 uiBut *ui_but_last(uiBlock *block)
377 {
378   uiBut *but;
379
380   but = block->buttons.last;
381   while (but) {
382     if (ui_but_is_editable(but)) {
383       return but;
384     }
385     but = but->prev;
386   }
387   return NULL;
388 }
389
390 bool ui_but_is_cursor_warp(const uiBut *but)
391 {
392   if (U.uiflag & USER_CONTINUOUS_MOUSE) {
393     if (ELEM(but->type,
394              UI_BTYPE_NUM,
395              UI_BTYPE_NUM_SLIDER,
396              UI_BTYPE_HSVCIRCLE,
397              UI_BTYPE_TRACK_PREVIEW,
398              UI_BTYPE_HSVCUBE,
399              UI_BTYPE_CURVE)) {
400       return true;
401     }
402   }
403
404   return false;
405 }
406
407 bool ui_but_contains_password(const uiBut *but)
408 {
409   return but->rnaprop && (RNA_property_subtype(but->rnaprop) == PROP_PASSWORD);
410 }
411
412 /** \} */
413
414 /* -------------------------------------------------------------------- */
415 /** \name Block (#uiBlock) State
416  * \{ */
417
418 bool ui_block_is_menu(const uiBlock *block)
419 {
420   return (((block->flag & UI_BLOCK_LOOP) != 0) &&
421           /* non-menu popups use keep-open, so check this is off */
422           ((block->flag & UI_BLOCK_KEEP_OPEN) == 0));
423 }
424
425 bool ui_block_is_popover(const uiBlock *block)
426 {
427   return (block->flag & UI_BLOCK_POPOVER) != 0;
428 }
429
430 bool ui_block_is_pie_menu(const uiBlock *block)
431 {
432   return ((block->flag & UI_BLOCK_RADIAL) != 0);
433 }
434
435 bool ui_block_is_popup_any(const uiBlock *block)
436 {
437   return (ui_block_is_menu(block) || ui_block_is_popover(block) || ui_block_is_pie_menu(block));
438 }
439
440 bool UI_block_is_empty(const uiBlock *block)
441 {
442   for (const uiBut *but = block->buttons.first; but; but = but->next) {
443     if (!ELEM(but->type, UI_BTYPE_SEPR, UI_BTYPE_SEPR_LINE)) {
444       return false;
445     }
446   }
447   return true;
448 }
449
450 /** \} */
451
452 /* -------------------------------------------------------------------- */
453 /** \name Region (#ARegion) State
454  * \{ */
455
456 uiBut *ui_region_find_active_but(ARegion *ar)
457 {
458   uiBlock *block;
459   uiBut *but;
460
461   for (block = ar->uiblocks.first; block; block = block->next) {
462     for (but = block->buttons.first; but; but = but->next) {
463       if (but->active) {
464         return but;
465       }
466     }
467   }
468
469   return NULL;
470 }
471
472 uiBut *ui_region_find_first_but_test_flag(ARegion *ar, int flag_include, int flag_exclude)
473 {
474   for (uiBlock *block = ar->uiblocks.first; block; block = block->next) {
475     for (uiBut *but = block->buttons.first; but; but = but->next) {
476       if (((but->flag & flag_include) == flag_include) && ((but->flag & flag_exclude) == 0)) {
477         return but;
478       }
479     }
480   }
481
482   return NULL;
483 }
484
485 /** \} */
486
487 /* -------------------------------------------------------------------- */
488 /** \name Region (#ARegion) State
489  * \{ */
490
491 bool ui_region_contains_point_px(const ARegion *ar, int x, int y)
492 {
493   rcti winrct;
494
495   /* scale down area rect to exclude shadow */
496   ui_region_winrct_get_no_margin(ar, &winrct);
497
498   /* check if the mouse is in the region */
499   if (!BLI_rcti_isect_pt(&winrct, x, y)) {
500     return false;
501   }
502
503   /* also, check that with view2d, that the mouse is not over the scrollbars
504    * NOTE: care is needed here, since the mask rect may include the scrollbars
505    * even when they are not visible, so we need to make a copy of the mask to
506    * use to check
507    */
508   if (ar->v2d.mask.xmin != ar->v2d.mask.xmax) {
509     const View2D *v2d = &ar->v2d;
510     int mx, my;
511
512     /* convert window coordinates to region coordinates */
513     mx = x;
514     my = y;
515     ui_window_to_region(ar, &mx, &my);
516
517     /* check if in the rect */
518     if (!BLI_rcti_isect_pt(&v2d->mask, mx, my) ||
519         UI_view2d_mouse_in_scrollers(ar, &ar->v2d, x, y)) {
520       return false;
521     }
522   }
523
524   return true;
525 }
526
527 /** \} */