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