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 
24 #include <ctype.h>
25 #include <float.h>
26 #include <limits.h>
27 #include <math.h>
28 #include <stdlib.h>
29 #include <string.h>
30 
31 #include "MEM_guardedalloc.h"
32 
33 #include "DNA_brush_types.h"
34 #include "DNA_curveprofile_types.h"
35 #include "DNA_scene_types.h"
36 #include "DNA_screen_types.h"
37 
38 #include "BLI_linklist.h"
39 #include "BLI_listbase.h"
40 #include "BLI_math.h"
41 #include "BLI_rect.h"
42 #include "BLI_string.h"
43 #include "BLI_string_cursor_utf8.h"
44 #include "BLI_string_utf8.h"
45 #include "BLI_utildefines.h"
46 
47 #include "PIL_time.h"
48 
49 #include "BKE_animsys.h"
50 #include "BKE_blender_undo.h"
51 #include "BKE_brush.h"
52 #include "BKE_colorband.h"
53 #include "BKE_colortools.h"
54 #include "BKE_context.h"
55 #include "BKE_curveprofile.h"
56 #include "BKE_movieclip.h"
57 #include "BKE_paint.h"
58 #include "BKE_report.h"
59 #include "BKE_screen.h"
60 #include "BKE_tracking.h"
61 #include "BKE_unit.h"
62 
63 #include "IMB_colormanagement.h"
64 
65 #include "ED_screen.h"
66 #include "ED_undo.h"
67 
68 #include "UI_interface.h"
69 #include "UI_view2d.h"
70 
71 #include "BLF_api.h"
72 
73 #include "interface_intern.h"
74 
75 #include "RNA_access.h"
76 
77 #include "WM_api.h"
78 #include "WM_types.h"
79 #include "wm_event_system.h"
80 
81 #ifdef WITH_INPUT_IME
82 #  include "BLT_lang.h"
83 #  include "BLT_translation.h"
84 #  include "wm_window.h"
85 #endif
86 
87 /* -------------------------------------------------------------------- */
88 /** \name Feature Defines
89  *
90  * These defines allow developers to locally toggle functionality which
91  * may be useful for testing (especially conflicts in dragging).
92  * Ideally the code would be refactored to support this functionality in a less fragile way.
93  * Until then keep these defines.
94  * \{ */
95 
96 /** Place the mouse at the scaled down location when un-grabbing. */
97 #define USE_CONT_MOUSE_CORRECT
98 /** Support dragging toggle buttons. */
99 #define USE_DRAG_TOGGLE
100 
101 /** Support dragging multiple number buttons at once. */
102 #define USE_DRAG_MULTINUM
103 
104 /** Allow dragging/editing all other selected items at once. */
105 #define USE_ALLSELECT
106 
107 /**
108  * Check to avoid very small mouse-moves from jumping away from keyboard navigation,
109  * while larger mouse motion will override keyboard input, see: T34936.
110  */
111 #define USE_KEYNAV_LIMIT
112 
113 /** Support dragging popups by their header. */
114 #define USE_DRAG_POPUP
115 
116 /** \} */
117 
118 /* -------------------------------------------------------------------- */
119 /** \name Local Defines
120  * \{ */
121 
122 /**
123  * The buffer side used for password strings, where the password is stored internally,
124  * but not displayed.
125  */
126 #define UI_MAX_PASSWORD_STR 128
127 
128 /**
129  * When #USER_CONTINUOUS_MOUSE is disabled or tablet input is used,
130  * Use this as a maximum soft range for mapping cursor motion to the value.
131  * Otherwise min/max of #FLT_MAX, #INT_MAX cause small adjustments to jump to large numbers.
132  *
133  * This is needed for values such as location & dimensions which don't have a meaningful min/max,
134  * Instead of mapping cursor motion to the min/max, map the motion to the click-step.
135  *
136  * This value is multiplied by the click step to calculate a range to clamp the soft-range by.
137  * See: T68130
138  */
139 #define UI_DRAG_MAP_SOFT_RANGE_PIXEL_MAX 1000
140 
141 /** \} */
142 
143 /* -------------------------------------------------------------------- */
144 /** \name Local Prototypes
145  * \{ */
146 
147 static int ui_do_but_EXIT(bContext *C,
148                           uiBut *but,
149                           struct uiHandleButtonData *data,
150                           const wmEvent *event);
151 static bool ui_but_find_select_in_enum__cmp(const uiBut *but_a, const uiBut *but_b);
152 static void ui_textedit_string_set(uiBut *but, struct uiHandleButtonData *data, const char *str);
153 static void button_tooltip_timer_reset(bContext *C, uiBut *but);
154 
155 #ifdef USE_KEYNAV_LIMIT
156 static void ui_mouse_motion_keynav_init(struct uiKeyNavLock *keynav, const wmEvent *event);
157 static bool ui_mouse_motion_keynav_test(struct uiKeyNavLock *keynav, const wmEvent *event);
158 #endif
159 
160 /** \} */
161 
162 /* -------------------------------------------------------------------- */
163 /** \name Structs & Defines
164  * \{ */
165 
166 #define BUTTON_FLASH_DELAY 0.020
167 #define MENU_SCROLL_INTERVAL 0.1
168 #define PIE_MENU_INTERVAL 0.01
169 #define BUTTON_AUTO_OPEN_THRESH 0.2
170 #define BUTTON_MOUSE_TOWARDS_THRESH 1.0
171 /* pixels to move the cursor to get out of keyboard navigation */
172 #define BUTTON_KEYNAV_PX_LIMIT 8
173 
174 #define MENU_TOWARDS_MARGIN 20 /* margin in pixels */
175 #define MENU_TOWARDS_WIGGLE_ROOM 64 /* tolerance in pixels */
176 /* drag-lock distance threshold in pixels */
177 #define BUTTON_DRAGLOCK_THRESH 3
178 
179 typedef enum uiButtonActivateType {
180   BUTTON_ACTIVATE_OVER,
181   BUTTON_ACTIVATE,
182   BUTTON_ACTIVATE_APPLY,
183   BUTTON_ACTIVATE_TEXT_EDITING,
184   BUTTON_ACTIVATE_OPEN,
185 } uiButtonActivateType;
186 
187 typedef enum uiHandleButtonState {
188   BUTTON_STATE_INIT,
189   BUTTON_STATE_HIGHLIGHT,
190   BUTTON_STATE_WAIT_FLASH,
191   BUTTON_STATE_WAIT_RELEASE,
192   BUTTON_STATE_WAIT_KEY_EVENT,
193   BUTTON_STATE_NUM_EDITING,
194   BUTTON_STATE_TEXT_EDITING,
195   BUTTON_STATE_TEXT_SELECTING,
196   BUTTON_STATE_MENU_OPEN,
197   BUTTON_STATE_WAIT_DRAG,
198   BUTTON_STATE_EXIT,
199 } uiHandleButtonState;
200 
201 typedef enum uiMenuScrollType {
202   MENU_SCROLL_UP,
203   MENU_SCROLL_DOWN,
204   MENU_SCROLL_TOP,
205   MENU_SCROLL_BOTTOM,
206 } uiMenuScrollType;
207 
208 #ifdef USE_ALLSELECT
209 
210 /* Unfortunately there's no good way handle more generally:
211  * (propagate single clicks on layer buttons to other objects) */
212 #  define USE_ALLSELECT_LAYER_HACK
213 
214 typedef struct uiSelectContextElem {
215   PointerRNA ptr;
216   union {
217     bool val_b;
218     int val_i;
219     float val_f;
220   };
221 } uiSelectContextElem;
222 
223 typedef struct uiSelectContextStore {
224   uiSelectContextElem *elems;
225   int elems_len;
226   bool do_free;
227   bool is_enabled;
228   /* When set, simply copy values (don't apply difference).
229    * Rules are:
230    * - dragging numbers uses delta.
231    * - typing in values will assign to all. */
232   bool is_copy;
233 } uiSelectContextStore;
234 
235 static bool ui_selectcontext_begin(bContext *C,
236                                    uiBut *but,
237                                    struct uiSelectContextStore *selctx_data);
238 static void ui_selectcontext_end(uiBut *but, uiSelectContextStore *selctx_data);
239 static void ui_selectcontext_apply(bContext *C,
240                                    uiBut *but,
241                                    struct uiSelectContextStore *selctx_data,
242                                    const double value,
243                                    const double value_orig);
244 
245 #  define IS_ALLSELECT_EVENT(event) ((event)->alt != 0)
246 
247 /** just show a tinted color so users know its activated */
248 #  define UI_BUT_IS_SELECT_CONTEXT UI_BUT_NODE_ACTIVE
249 
250 #endif /* USE_ALLSELECT */
251 
252 #ifdef USE_DRAG_MULTINUM
253 
254 /**
255  * how far to drag before we check for gesture direction (in pixels),
256  * note: half the height of a button is about right... */
257 #  define DRAG_MULTINUM_THRESHOLD_DRAG_X (UI_UNIT_Y / 4)
258 
259 /**
260  * How far to drag horizontally
261  * before we stop checking which buttons the gesture spans (in pixels),
262  * locking down the buttons so we can drag freely without worrying about vertical movement.
263  */
264 #  define DRAG_MULTINUM_THRESHOLD_DRAG_Y (UI_UNIT_Y / 4)
265 
266 /**
267  * How strict to be when detecting a vertical gesture:
268  * [0.5 == sloppy], [0.9 == strict], (unsigned dot-product).
269  *
270  * \note We should be quite strict here,
271  * since doing a vertical gesture by accident should be avoided,
272  * however with some care a user should be able to do a vertical movement without _missing_.
273  */
274 #  define DRAG_MULTINUM_THRESHOLD_VERTICAL (0.75f)
275 
276 /* a simple version of uiHandleButtonData when accessing multiple buttons */
277 typedef struct uiButMultiState {
278   double origvalue;
279   uiBut *but;
280 
281 #  ifdef USE_ALLSELECT
282   uiSelectContextStore select_others;
283 #  endif
284 } uiButMultiState;
285 
286 typedef struct uiHandleButtonMulti {
287   enum {
288     /** gesture direction unknown, wait until mouse has moved enough... */
289     BUTTON_MULTI_INIT_UNSET = 0,
290     /** vertical gesture detected, flag buttons interactively (UI_BUT_DRAG_MULTI) */
291     BUTTON_MULTI_INIT_SETUP,
292     /** flag buttons finished, apply horizontal motion to active and flagged */
293     BUTTON_MULTI_INIT_ENABLE,
294     /** vertical gesture _not_ detected, take no further action */
295     BUTTON_MULTI_INIT_DISABLE,
296   } init;
297 
298   bool has_mbuts; /* any buttons flagged UI_BUT_DRAG_MULTI */
299   LinkNode *mbuts;
300   uiButStore *bs_mbuts;
301 
302   bool is_proportional;
303 
304   /* In some cases we directly apply the changes to multiple buttons,
305    * so we don't want to do it twice. */
306   bool skip;
307 
308   /* before activating, we need to check gesture direction accumulate signed cursor movement
309    * here so we can tell if this is a vertical motion or not. */
310   float drag_dir[2];
311 
312   /* values copied direct from event->x,y
313    * used to detect buttons between the current and initial mouse position */
314   int drag_start[2];
315 
316   /* store x location once BUTTON_MULTI_INIT_SETUP is set,
317    * moving outside this sets BUTTON_MULTI_INIT_ENABLE */
318   int drag_lock_x;
319 
320 } uiHandleButtonMulti;
321 
322 #endif /* USE_DRAG_MULTINUM */
323 
324 typedef struct uiHandleButtonData {
325   wmWindowManager *wm;
326   wmWindow *window;
327   ScrArea *area;
328   ARegion *region;
329 
330   bool interactive;
331 
332   /* overall state */
333   uiHandleButtonState state;
334   int retval;
335   /* booleans (could be made into flags) */
336   bool cancel, escapecancel;
337   bool applied, applied_interactive;
338   bool changed_cursor;
339   wmTimer *flashtimer;
340 
341   /* edited value */
342   /* use 'ui_textedit_string_set' to assign new strings */
343   char *str;
344   char *origstr;
345   double value, origvalue, startvalue;
346   float vec[3], origvec[3];
347 #if 0 /* UNUSED */
348   int togdual, togonly;
349 #endif
350   ColorBand *coba;
351 
352   /* tooltip */
353   uint tooltip_force : 1;
354 
355   /* auto open */
356   bool used_mouse;
357   wmTimer *autoopentimer;
358 
359   /* auto open (hold) */
360   wmTimer *hold_action_timer;
361 
362   /* text selection/editing */
363   /* size of 'str' (including terminator) */
364   int maxlen;
365   /* Button text selection:
366    * extension direction, selextend, inside ui_do_but_TEX */
367   int sel_pos_init;
368   /* allow to realloc str/editstr and use 'maxlen' to track alloc size (maxlen + 1) */
369   bool is_str_dynamic;
370 
371   /* number editing / dragging */
372   /* coords are Window/uiBlock relative (depends on the button) */
373   int draglastx, draglasty;
374   int dragstartx, dragstarty;
375   int draglastvalue;
376   int dragstartvalue;
377   bool dragchange, draglock;
378   int dragsel;
379   float dragf, dragfstart;
380   CBData *dragcbd;
381 
382   /** Soft min/max with #UI_DRAG_MAP_SOFT_RANGE_PIXEL_MAX applied. */
383   float drag_map_soft_min;
384   float drag_map_soft_max;
385 
386 #ifdef USE_CONT_MOUSE_CORRECT
387   /* when ungrabbing buttons which are #ui_but_is_cursor_warp(),
388    * we may want to position them.
389    * FLT_MAX signifies do-nothing, use #ui_block_to_window_fl()
390    * to get this into a usable space. */
391   float ungrab_mval[2];
392 #endif
393 
394   /* menu open (watch UI_screen_free_active_but) */
395   uiPopupBlockHandle *menu;
396   int menuretval;
397 
398   /* search box (watch UI_screen_free_active_but) */
399   ARegion *searchbox;
400 #ifdef USE_KEYNAV_LIMIT
401   struct uiKeyNavLock searchbox_keynav_state;
402 #endif
403 
404 #ifdef USE_DRAG_MULTINUM
405   /* Multi-buttons will be updated in unison with the active button. */
406   uiHandleButtonMulti multi_data;
407 #endif
408 
409 #ifdef USE_ALLSELECT
410   uiSelectContextStore select_others;
411 #endif
412 
413   /* Text field undo. */
414   struct uiUndoStack_Text *undo_stack_text;
415 
416   /* post activate */
417   uiButtonActivateType posttype;
418   uiBut *postbut;
419 } uiHandleButtonData;
420 
421 typedef struct uiAfterFunc {
422   struct uiAfterFunc *next, *prev;
423 
424   uiButHandleFunc func;
425   void *func_arg1;
426   void *func_arg2;
427 
428   uiButHandleNFunc funcN;
429   void *func_argN;
430 
431   uiButHandleRenameFunc rename_func;
432   void *rename_arg1;
433   void *rename_orig;
434 
435   uiBlockHandleFunc handle_func;
436   void *handle_func_arg;
437   int retval;
438 
439   uiMenuHandleFunc butm_func;
440   void *butm_func_arg;
441   int a2;
442 
443   wmOperator *popup_op;
444   wmOperatorType *optype;
445   int opcontext;
446   PointerRNA *opptr;
447 
448   PointerRNA rnapoin;
449   PropertyRNA *rnaprop;
450 
451   void *search_arg;
452   uiButSearchArgFreeFn search_arg_free_fn;
453 
454   bContextStore *context;
455 
456   char undostr[BKE_UNDO_STR_MAX];
457 } uiAfterFunc;
458 
459 static void button_activate_init(bContext *C,
460                                  ARegion *region,
461                                  uiBut *but,
462                                  uiButtonActivateType type);
463 static void button_activate_state(bContext *C, uiBut *but, uiHandleButtonState state);
464 static void button_activate_exit(
465     bContext *C, uiBut *but, uiHandleButtonData *data, const bool mousemove, const bool onfree);
466 static int ui_handler_region_menu(bContext *C, const wmEvent *event, void *userdata);
467 static void ui_handle_button_activate(bContext *C,
468                                       ARegion *region,
469                                       uiBut *but,
470                                       uiButtonActivateType type);
471 static bool ui_do_but_extra_operator_icon(bContext *C,
472                                           uiBut *but,
473                                           uiHandleButtonData *data,
474                                           const wmEvent *event);
475 static void ui_do_but_extra_operator_icons_mousemove(uiBut *but,
476                                                      uiHandleButtonData *data,
477                                                      const wmEvent *event);
478 
479 #ifdef USE_DRAG_MULTINUM
480 static void ui_multibut_restore(bContext *C, uiHandleButtonData *data, uiBlock *block);
481 static uiButMultiState *ui_multibut_lookup(uiHandleButtonData *data, const uiBut *but);
482 #endif
483 
484 /* buttons clipboard */
485 static ColorBand but_copypaste_coba = {0};
486 static CurveMapping but_copypaste_curve = {0};
487 static bool but_copypaste_curve_alive = false;
488 static CurveProfile but_copypaste_profile = {0};
489 static bool but_copypaste_profile_alive = false;
490 
491 /** \} */
492 
493 /* -------------------------------------------------------------------- */
494 /** \name UI Queries
495  * \{ */
496 
ui_but_is_editing(const uiBut * but)497 bool ui_but_is_editing(const uiBut *but)
498 {
499   uiHandleButtonData *data = but->active;
500   return (data && ELEM(data->state, BUTTON_STATE_TEXT_EDITING, BUTTON_STATE_NUM_EDITING));
501 }
502 
503 /* assumes event type is MOUSEPAN */
ui_pan_to_scroll(const wmEvent * event,int * type,int * val)504 void ui_pan_to_scroll(const wmEvent *event, int *type, int *val)
505 {
506   static int lastdy = 0;
507   int dy = event->prevy - event->y;
508 
509   /* This event should be originally from event->type,
510    * converting wrong event into wheel is bad, see T33803. */
511   BLI_assert(*type == MOUSEPAN);
512 
513   /* sign differs, reset */
514   if ((dy > 0 && lastdy < 0) || (dy < 0 && lastdy > 0)) {
515     lastdy = dy;
516   }
517   else {
518     lastdy += dy;
519 
520     if (abs(lastdy) > (int)UI_UNIT_Y) {
521       if (U.uiflag2 & USER_TRACKPAD_NATURAL) {
522         dy = -dy;
523       }
524 
525       *val = KM_PRESS;
526 
527       if (dy > 0) {
528         *type = WHEELUPMOUSE;
529       }
530       else {
531         *type = WHEELDOWNMOUSE;
532       }
533 
534       lastdy = 0;
535     }
536   }
537 }
538 
ui_but_find_select_in_enum__cmp(const uiBut * but_a,const uiBut * but_b)539 static bool ui_but_find_select_in_enum__cmp(const uiBut *but_a, const uiBut *but_b)
540 {
541   return ((but_a->type == but_b->type) && (but_a->alignnr == but_b->alignnr) &&
542           (but_a->poin == but_b->poin) && (but_a->rnapoin.type == but_b->rnapoin.type) &&
543           (but_a->rnaprop == but_b->rnaprop));
544 }
545 
546 /**
547  * Finds the pressed button in an aligned row (typically an expanded enum).
548  *
549  * \param direction: Use when there may be multiple buttons pressed.
550  */
ui_but_find_select_in_enum(uiBut * but,int direction)551 uiBut *ui_but_find_select_in_enum(uiBut *but, int direction)
552 {
553   uiBut *but_iter = but;
554   uiBut *but_found = NULL;
555   BLI_assert(ELEM(direction, -1, 1));
556 
557   while ((but_iter->prev) && ui_but_find_select_in_enum__cmp(but_iter->prev, but)) {
558     but_iter = but_iter->prev;
559   }
560 
561   while (but_iter && ui_but_find_select_in_enum__cmp(but_iter, but)) {
562     if (but_iter->flag & UI_SELECT) {
563       but_found = but_iter;
564       if (direction == 1) {
565         break;
566       }
567     }
568     but_iter = but_iter->next;
569   }
570 
571   return but_found;
572 }
573 
ui_mouse_scale_warp_factor(const bool shift)574 static float ui_mouse_scale_warp_factor(const bool shift)
575 {
576   return shift ? 0.05f : 1.0f;
577 }
578 
ui_mouse_scale_warp(uiHandleButtonData * data,const float mx,const float my,float * r_mx,float * r_my,const bool shift)579 static void ui_mouse_scale_warp(uiHandleButtonData *data,
580                                 const float mx,
581                                 const float my,
582                                 float *r_mx,
583                                 float *r_my,
584                                 const bool shift)
585 {
586   const float fac = ui_mouse_scale_warp_factor(shift);
587 
588   /* slow down the mouse, this is fairly picky */
589   *r_mx = (data->dragstartx * (1.0f - fac) + mx * fac);
590   *r_my = (data->dragstarty * (1.0f - fac) + my * fac);
591 }
592 
593 /** \} */
594 
595 /* -------------------------------------------------------------------- */
596 /** \name UI Utilities
597  * \{ */
598 
599 /**
600  * Ignore mouse movements within some horizontal pixel threshold before starting to drag
601  */
ui_but_dragedit_update_mval(uiHandleButtonData * data,int mx)602 static bool ui_but_dragedit_update_mval(uiHandleButtonData *data, int mx)
603 {
604   if (mx == data->draglastx) {
605     return false;
606   }
607 
608   if (data->draglock) {
609     if (abs(mx - data->dragstartx) <= BUTTON_DRAGLOCK_THRESH) {
610       return false;
611     }
612 #ifdef USE_DRAG_MULTINUM
613     if (ELEM(data->multi_data.init, BUTTON_MULTI_INIT_UNSET, BUTTON_MULTI_INIT_SETUP)) {
614       return false;
615     }
616 #endif
617     data->draglock = false;
618     data->dragstartx = mx; /* ignore mouse movement within drag-lock */
619   }
620 
621   return true;
622 }
623 
ui_rna_is_userdef(PointerRNA * ptr,PropertyRNA * prop)624 static bool ui_rna_is_userdef(PointerRNA *ptr, PropertyRNA *prop)
625 {
626   /* Not very elegant, but ensures preference changes force re-save. */
627   bool tag = false;
628   if (prop && !(RNA_property_flag(prop) & PROP_NO_DEG_UPDATE)) {
629     StructRNA *base = RNA_struct_base(ptr->type);
630     if (base == NULL) {
631       base = ptr->type;
632     }
633     if (ELEM(base, &RNA_AddonPreferences, &RNA_KeyConfigPreferences, &RNA_KeyMapItem)) {
634       tag = true;
635     }
636   }
637   return tag;
638 }
639 
UI_but_is_userdef(const uiBut * but)640 bool UI_but_is_userdef(const uiBut *but)
641 {
642   /* This is read-only, RNA API isn't using const when it could. */
643   return ui_rna_is_userdef((PointerRNA *)&but->rnapoin, but->rnaprop);
644 }
645 
ui_rna_update_preferences_dirty(PointerRNA * ptr,PropertyRNA * prop)646 static void ui_rna_update_preferences_dirty(PointerRNA *ptr, PropertyRNA *prop)
647 {
648   if (ui_rna_is_userdef(ptr, prop)) {
649     U.runtime.is_dirty = true;
650     WM_main_add_notifier(NC_WINDOW, NULL);
651   }
652 }
653 
ui_but_update_preferences_dirty(uiBut * but)654 static void ui_but_update_preferences_dirty(uiBut *but)
655 {
656   ui_rna_update_preferences_dirty(&but->rnapoin, but->rnaprop);
657 }
658 
ui_afterfunc_update_preferences_dirty(uiAfterFunc * after)659 static void ui_afterfunc_update_preferences_dirty(uiAfterFunc *after)
660 {
661   ui_rna_update_preferences_dirty(&after->rnapoin, after->rnaprop);
662 }
663 
664 /** \} */
665 
666 /* -------------------------------------------------------------------- */
667 /** \name Button Snap Values
668  *
669  * \{ */
670 
671 enum eSnapType {
672   SNAP_OFF = 0,
673   SNAP_ON,
674   SNAP_ON_SMALL,
675 };
676 
ui_event_to_snap(const wmEvent * event)677 static enum eSnapType ui_event_to_snap(const wmEvent *event)
678 {
679   return (event->ctrl) ? (event->shift) ? SNAP_ON_SMALL : SNAP_ON : SNAP_OFF;
680 }
681 
ui_event_is_snap(const wmEvent * event)682 static bool ui_event_is_snap(const wmEvent *event)
683 {
684   return (ELEM(event->type, EVT_LEFTCTRLKEY, EVT_RIGHTCTRLKEY) ||
685           ELEM(event->type, EVT_LEFTSHIFTKEY, EVT_RIGHTSHIFTKEY));
686 }
687 
ui_color_snap_hue(const enum eSnapType snap,float * r_hue)688 static void ui_color_snap_hue(const enum eSnapType snap, float *r_hue)
689 {
690   const float snap_increment = (snap == SNAP_ON_SMALL) ? 24 : 12;
691   BLI_assert(snap != SNAP_OFF);
692   *r_hue = roundf((*r_hue) * snap_increment) / snap_increment;
693 }
694 
695 /** \} */
696 
697 /* -------------------------------------------------------------------- */
698 /** \name Button Apply/Revert
699  * \{ */
700 
701 static ListBase UIAfterFuncs = {NULL, NULL};
702 
ui_afterfunc_new(void)703 static uiAfterFunc *ui_afterfunc_new(void)
704 {
705   uiAfterFunc *after = MEM_callocN(sizeof(uiAfterFunc), "uiAfterFunc");
706 
707   BLI_addtail(&UIAfterFuncs, after);
708 
709   return after;
710 }
711 
712 /**
713  * For executing operators after the button is pressed.
714  * (some non operator buttons need to trigger operators), see: T37795.
715  *
716  * \note Can only call while handling buttons.
717  */
ui_handle_afterfunc_add_operator(wmOperatorType * ot,int opcontext,bool create_props)718 PointerRNA *ui_handle_afterfunc_add_operator(wmOperatorType *ot, int opcontext, bool create_props)
719 {
720   PointerRNA *ptr = NULL;
721   uiAfterFunc *after = ui_afterfunc_new();
722 
723   after->optype = ot;
724   after->opcontext = opcontext;
725 
726   if (create_props) {
727     ptr = MEM_callocN(sizeof(PointerRNA), __func__);
728     WM_operator_properties_create_ptr(ptr, ot);
729     after->opptr = ptr;
730   }
731 
732   return ptr;
733 }
734 
popup_check(bContext * C,wmOperator * op)735 static void popup_check(bContext *C, wmOperator *op)
736 {
737   if (op && op->type->check) {
738     op->type->check(C, op);
739   }
740 }
741 
742 /**
743  * Check if a #uiAfterFunc is needed for this button.
744  */
ui_afterfunc_check(const uiBlock * block,const uiBut * but)745 static bool ui_afterfunc_check(const uiBlock *block, const uiBut *but)
746 {
747   return (but->func || but->funcN || but->rename_func || but->optype || but->rnaprop ||
748           block->handle_func || (but->type == UI_BTYPE_BUT_MENU && block->butm_func) ||
749           (block->handle && block->handle->popup_op));
750 }
751 
ui_apply_but_func(bContext * C,uiBut * but)752 static void ui_apply_but_func(bContext *C, uiBut *but)
753 {
754   uiBlock *block = but->block;
755 
756   /* these functions are postponed and only executed after all other
757    * handling is done, i.e. menus are closed, in order to avoid conflicts
758    * with these functions removing the buttons we are working with */
759 
760   if (ui_afterfunc_check(block, but)) {
761     uiAfterFunc *after = ui_afterfunc_new();
762 
763     if (but->func && ELEM(but, but->func_arg1, but->func_arg2)) {
764       /* exception, this will crash due to removed button otherwise */
765       but->func(C, but->func_arg1, but->func_arg2);
766     }
767     else {
768       after->func = but->func;
769     }
770 
771     after->func_arg1 = but->func_arg1;
772     after->func_arg2 = but->func_arg2;
773 
774     after->funcN = but->funcN;
775     after->func_argN = (but->func_argN) ? MEM_dupallocN(but->func_argN) : NULL;
776 
777     after->rename_func = but->rename_func;
778     after->rename_arg1 = but->rename_arg1;
779     after->rename_orig = but->rename_orig; /* needs free! */
780 
781     after->handle_func = block->handle_func;
782     after->handle_func_arg = block->handle_func_arg;
783     after->retval = but->retval;
784 
785     if (but->type == UI_BTYPE_BUT_MENU) {
786       after->butm_func = block->butm_func;
787       after->butm_func_arg = block->butm_func_arg;
788       after->a2 = but->a2;
789     }
790 
791     if (block->handle) {
792       after->popup_op = block->handle->popup_op;
793     }
794 
795     after->optype = but->optype;
796     after->opcontext = but->opcontext;
797     after->opptr = but->opptr;
798 
799     after->rnapoin = but->rnapoin;
800     after->rnaprop = but->rnaprop;
801 
802     if (but->type == UI_BTYPE_SEARCH_MENU) {
803       uiButSearch *search_but = (uiButSearch *)but;
804       after->search_arg_free_fn = search_but->arg_free_fn;
805       after->search_arg = search_but->arg;
806       search_but->arg_free_fn = NULL;
807       search_but->arg = NULL;
808     }
809 
810     if (but->context) {
811       after->context = CTX_store_copy(but->context);
812     }
813 
814     but->optype = NULL;
815     but->opcontext = 0;
816     but->opptr = NULL;
817   }
818 }
819 
820 /* typically call ui_apply_but_undo(), ui_apply_but_autokey() */
ui_apply_but_undo(uiBut * but)821 static void ui_apply_but_undo(uiBut *but)
822 {
823   if (but->flag & UI_BUT_UNDO) {
824     const char *str = NULL;
825     size_t str_len_clip = SIZE_MAX - 1;
826     bool skip_undo = false;
827 
828     /* define which string to use for undo */
829     if (but->type == UI_BTYPE_MENU) {
830       str = but->drawstr;
831       str_len_clip = ui_but_drawstr_len_without_sep_char(but);
832     }
833     else if (but->drawstr[0]) {
834       str = but->drawstr;
835       str_len_clip = ui_but_drawstr_len_without_sep_char(but);
836     }
837     else {
838       str = but->tip;
839       str_len_clip = ui_but_tip_len_only_first_line(but);
840     }
841 
842     /* fallback, else we don't get an undo! */
843     if (str == NULL || str[0] == '\0' || str_len_clip == 0) {
844       str = "Unknown Action";
845     }
846 
847     /* Optionally override undo when undo system doesn't support storing properties. */
848     if (but->rnapoin.owner_id) {
849       /* Exception for renaming ID data, we always need undo pushes in this case,
850        * because undo systems track data by their ID, see: T67002. */
851       extern PropertyRNA rna_ID_name;
852       /* Exception for active shape-key, since changing this in edit-mode updates
853        * the shape key from object mode data. */
854       extern PropertyRNA rna_Object_active_shape_key_index;
855       if (ELEM(but->rnaprop, &rna_ID_name, &rna_Object_active_shape_key_index)) {
856         /* pass */
857       }
858       else {
859         ID *id = but->rnapoin.owner_id;
860         if (!ED_undo_is_legacy_compatible_for_property(but->block->evil_C, id)) {
861           skip_undo = true;
862         }
863       }
864     }
865 
866     if (skip_undo == false) {
867       /* XXX: disable all undo pushes from UI changes from sculpt mode as they cause memfile undo
868        * steps to be written which cause lag: T71434. */
869       if (BKE_paintmode_get_active_from_context(but->block->evil_C) == PAINT_MODE_SCULPT) {
870         skip_undo = true;
871       }
872     }
873 
874     if (skip_undo) {
875       str = "";
876     }
877 
878     /* delayed, after all other funcs run, popups are closed, etc */
879     uiAfterFunc *after = ui_afterfunc_new();
880     BLI_strncpy(after->undostr, str, min_zz(str_len_clip + 1, sizeof(after->undostr)));
881   }
882 }
883 
ui_apply_but_autokey(bContext * C,uiBut * but)884 static void ui_apply_but_autokey(bContext *C, uiBut *but)
885 {
886   Scene *scene = CTX_data_scene(C);
887 
888   /* try autokey */
889   ui_but_anim_autokey(C, but, scene, scene->r.cfra);
890 
891   /* make a little report about what we've done! */
892   if (but->rnaprop) {
893     char *buf;
894 
895     if (RNA_property_subtype(but->rnaprop) == PROP_PASSWORD) {
896       return;
897     }
898 
899     buf = WM_prop_pystring_assign(C, &but->rnapoin, but->rnaprop, but->rnaindex);
900     if (buf) {
901       BKE_report(CTX_wm_reports(C), RPT_PROPERTY, buf);
902       MEM_freeN(buf);
903 
904       WM_event_add_notifier(C, NC_SPACE | ND_SPACE_INFO_REPORT, NULL);
905     }
906   }
907 }
908 
ui_apply_but_funcs_after(bContext * C)909 static void ui_apply_but_funcs_after(bContext *C)
910 {
911   /* copy to avoid recursive calls */
912   ListBase funcs = UIAfterFuncs;
913   BLI_listbase_clear(&UIAfterFuncs);
914 
915   LISTBASE_FOREACH_MUTABLE (uiAfterFunc *, afterf, &funcs) {
916     uiAfterFunc after = *afterf; /* copy to avoid memleak on exit() */
917     BLI_freelinkN(&funcs, afterf);
918 
919     if (after.context) {
920       CTX_store_set(C, after.context);
921     }
922 
923     if (after.popup_op) {
924       popup_check(C, after.popup_op);
925     }
926 
927     PointerRNA opptr;
928     if (after.opptr) {
929       /* free in advance to avoid leak on exit */
930       opptr = *after.opptr;
931       MEM_freeN(after.opptr);
932     }
933 
934     if (after.optype) {
935       WM_operator_name_call_ptr(C, after.optype, after.opcontext, (after.opptr) ? &opptr : NULL);
936     }
937 
938     if (after.opptr) {
939       WM_operator_properties_free(&opptr);
940     }
941 
942     if (after.rnapoin.data) {
943       RNA_property_update(C, &after.rnapoin, after.rnaprop);
944     }
945 
946     if (after.context) {
947       CTX_store_set(C, NULL);
948       CTX_store_free(after.context);
949     }
950 
951     if (after.func) {
952       after.func(C, after.func_arg1, after.func_arg2);
953     }
954     if (after.funcN) {
955       after.funcN(C, after.func_argN, after.func_arg2);
956     }
957     if (after.func_argN) {
958       MEM_freeN(after.func_argN);
959     }
960 
961     if (after.handle_func) {
962       after.handle_func(C, after.handle_func_arg, after.retval);
963     }
964     if (after.butm_func) {
965       after.butm_func(C, after.butm_func_arg, after.a2);
966     }
967 
968     if (after.rename_func) {
969       after.rename_func(C, after.rename_arg1, after.rename_orig);
970     }
971     if (after.rename_orig) {
972       MEM_freeN(after.rename_orig);
973     }
974 
975     if (after.search_arg_free_fn) {
976       after.search_arg_free_fn(after.search_arg);
977     }
978 
979     ui_afterfunc_update_preferences_dirty(&after);
980 
981     if (after.undostr[0]) {
982       ED_undo_push(C, after.undostr);
983     }
984   }
985 }
986 
ui_apply_but_BUT(bContext * C,uiBut * but,uiHandleButtonData * data)987 static void ui_apply_but_BUT(bContext *C, uiBut *but, uiHandleButtonData *data)
988 {
989   ui_apply_but_func(C, but);
990 
991   data->retval = but->retval;
992   data->applied = true;
993 }
994 
ui_apply_but_BUTM(bContext * C,uiBut * but,uiHandleButtonData * data)995 static void ui_apply_but_BUTM(bContext *C, uiBut *but, uiHandleButtonData *data)
996 {
997   ui_but_value_set(but, but->hardmin);
998   ui_apply_but_func(C, but);
999 
1000   data->retval = but->retval;
1001   data->applied = true;
1002 }
1003 
ui_apply_but_BLOCK(bContext * C,uiBut * but,uiHandleButtonData * data)1004 static void ui_apply_but_BLOCK(bContext *C, uiBut *but, uiHandleButtonData *data)
1005 {
1006   if (but->type == UI_BTYPE_MENU) {
1007     ui_but_value_set(but, data->value);
1008   }
1009 
1010   ui_but_update_edited(but);
1011   ui_apply_but_func(C, but);
1012   data->retval = but->retval;
1013   data->applied = true;
1014 }
1015 
ui_apply_but_TOG(bContext * C,uiBut * but,uiHandleButtonData * data)1016 static void ui_apply_but_TOG(bContext *C, uiBut *but, uiHandleButtonData *data)
1017 {
1018   const double value = ui_but_value_get(but);
1019   int value_toggle;
1020   if (but->bit) {
1021     value_toggle = UI_BITBUT_VALUE_TOGGLED((int)value, but->bitnr);
1022   }
1023   else {
1024     value_toggle = (value == 0.0);
1025     if (ELEM(but->type, UI_BTYPE_TOGGLE_N, UI_BTYPE_ICON_TOGGLE_N, UI_BTYPE_CHECKBOX_N)) {
1026       value_toggle = !value_toggle;
1027     }
1028   }
1029 
1030   ui_but_value_set(but, (double)value_toggle);
1031   if (but->type == UI_BTYPE_ICON_TOGGLE || but->type == UI_BTYPE_ICON_TOGGLE_N) {
1032     ui_but_update_edited(but);
1033   }
1034 
1035   ui_apply_but_func(C, but);
1036 
1037   data->retval = but->retval;
1038   data->applied = true;
1039 }
1040 
ui_apply_but_ROW(bContext * C,uiBlock * block,uiBut * but,uiHandleButtonData * data)1041 static void ui_apply_but_ROW(bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data)
1042 {
1043   ui_but_value_set(but, but->hardmax);
1044 
1045   ui_apply_but_func(C, but);
1046 
1047   /* states of other row buttons */
1048   LISTBASE_FOREACH (uiBut *, bt, &block->buttons) {
1049     if (bt != but && bt->poin == but->poin && ELEM(bt->type, UI_BTYPE_ROW, UI_BTYPE_LISTROW)) {
1050       ui_but_update_edited(bt);
1051     }
1052   }
1053 
1054   data->retval = but->retval;
1055   data->applied = true;
1056 }
1057 
ui_apply_but_TEX(bContext * C,uiBut * but,uiHandleButtonData * data)1058 static void ui_apply_but_TEX(bContext *C, uiBut *but, uiHandleButtonData *data)
1059 {
1060   if (!data->str) {
1061     return;
1062   }
1063 
1064   ui_but_string_set(C, but, data->str);
1065   ui_but_update_edited(but);
1066 
1067   /* give butfunc a copy of the original text too.
1068    * feature used for bone renaming, channels, etc.
1069    * afterfunc frees rename_orig */
1070   if (data->origstr && (but->flag & UI_BUT_TEXTEDIT_UPDATE)) {
1071     /* In this case, we need to keep origstr available,
1072      * to restore real org string in case we cancel after having typed something already. */
1073     but->rename_orig = BLI_strdup(data->origstr);
1074   }
1075   /* only if there are afterfuncs, otherwise 'renam_orig' isn't freed */
1076   else if (ui_afterfunc_check(but->block, but)) {
1077     but->rename_orig = data->origstr;
1078     data->origstr = NULL;
1079   }
1080 
1081   void *orig_arg2 = but->func_arg2;
1082 
1083   /* If arg2 isn't in use already, pass the active search item through it. */
1084   if ((but->func_arg2 == NULL) && (but->type == UI_BTYPE_SEARCH_MENU)) {
1085     uiButSearch *search_but = (uiButSearch *)but;
1086     but->func_arg2 = search_but->item_active;
1087   }
1088 
1089   ui_apply_but_func(C, but);
1090 
1091   but->func_arg2 = orig_arg2;
1092 
1093   data->retval = but->retval;
1094   data->applied = true;
1095 }
1096 
ui_apply_but_TAB(bContext * C,uiBut * but,uiHandleButtonData * data)1097 static void ui_apply_but_TAB(bContext *C, uiBut *but, uiHandleButtonData *data)
1098 {
1099   if (data->str) {
1100     ui_but_string_set(C, but, data->str);
1101     ui_but_update_edited(but);
1102   }
1103   else {
1104     ui_but_value_set(but, but->hardmax);
1105     ui_apply_but_func(C, but);
1106   }
1107 
1108   data->retval = but->retval;
1109   data->applied = true;
1110 }
1111 
ui_apply_but_NUM(bContext * C,uiBut * but,uiHandleButtonData * data)1112 static void ui_apply_but_NUM(bContext *C, uiBut *but, uiHandleButtonData *data)
1113 {
1114   if (data->str) {
1115     if (ui_but_string_set(C, but, data->str)) {
1116       data->value = ui_but_value_get(but);
1117     }
1118     else {
1119       data->cancel = true;
1120       return;
1121     }
1122   }
1123   else {
1124     ui_but_value_set(but, data->value);
1125   }
1126 
1127   ui_but_update_edited(but);
1128   ui_apply_but_func(C, but);
1129 
1130   data->retval = but->retval;
1131   data->applied = true;
1132 }
1133 
ui_apply_but_VEC(bContext * C,uiBut * but,uiHandleButtonData * data)1134 static void ui_apply_but_VEC(bContext *C, uiBut *but, uiHandleButtonData *data)
1135 {
1136   ui_but_v3_set(but, data->vec);
1137   ui_but_update_edited(but);
1138   ui_apply_but_func(C, but);
1139 
1140   data->retval = but->retval;
1141   data->applied = true;
1142 }
1143 
ui_apply_but_COLORBAND(bContext * C,uiBut * but,uiHandleButtonData * data)1144 static void ui_apply_but_COLORBAND(bContext *C, uiBut *but, uiHandleButtonData *data)
1145 {
1146   ui_apply_but_func(C, but);
1147   data->retval = but->retval;
1148   data->applied = true;
1149 }
1150 
ui_apply_but_CURVE(bContext * C,uiBut * but,uiHandleButtonData * data)1151 static void ui_apply_but_CURVE(bContext *C, uiBut *but, uiHandleButtonData *data)
1152 {
1153   ui_apply_but_func(C, but);
1154   data->retval = but->retval;
1155   data->applied = true;
1156 }
1157 
ui_apply_but_CURVEPROFILE(bContext * C,uiBut * but,uiHandleButtonData * data)1158 static void ui_apply_but_CURVEPROFILE(bContext *C, uiBut *but, uiHandleButtonData *data)
1159 {
1160   ui_apply_but_func(C, but);
1161   data->retval = but->retval;
1162   data->applied = true;
1163 }
1164 
1165 /** \} */
1166 
1167 /* -------------------------------------------------------------------- */
1168 /** \name Button Drag Multi-Number
1169  * \{ */
1170 
1171 #ifdef USE_DRAG_MULTINUM
1172 
1173 /* small multi-but api */
ui_multibut_add(uiHandleButtonData * data,uiBut * but)1174 static void ui_multibut_add(uiHandleButtonData *data, uiBut *but)
1175 {
1176   BLI_assert(but->flag & UI_BUT_DRAG_MULTI);
1177   BLI_assert(data->multi_data.has_mbuts);
1178 
1179   uiButMultiState *mbut_state = MEM_callocN(sizeof(*mbut_state), __func__);
1180   mbut_state->but = but;
1181   mbut_state->origvalue = ui_but_value_get(but);
1182 #  ifdef USE_ALLSELECT
1183   mbut_state->select_others.is_copy = data->select_others.is_copy;
1184 #  endif
1185 
1186   BLI_linklist_prepend(&data->multi_data.mbuts, mbut_state);
1187 
1188   UI_butstore_register(data->multi_data.bs_mbuts, &mbut_state->but);
1189 }
1190 
ui_multibut_lookup(uiHandleButtonData * data,const uiBut * but)1191 static uiButMultiState *ui_multibut_lookup(uiHandleButtonData *data, const uiBut *but)
1192 {
1193   for (LinkNode *l = data->multi_data.mbuts; l; l = l->next) {
1194     uiButMultiState *mbut_state = l->link;
1195 
1196     if (mbut_state->but == but) {
1197       return mbut_state;
1198     }
1199   }
1200 
1201   return NULL;
1202 }
1203 
ui_multibut_restore(bContext * C,uiHandleButtonData * data,uiBlock * block)1204 static void ui_multibut_restore(bContext *C, uiHandleButtonData *data, uiBlock *block)
1205 {
1206   LISTBASE_FOREACH (uiBut *, but, &block->buttons) {
1207     if (but->flag & UI_BUT_DRAG_MULTI) {
1208       uiButMultiState *mbut_state = ui_multibut_lookup(data, but);
1209       if (mbut_state) {
1210         ui_but_value_set(but, mbut_state->origvalue);
1211 
1212 #  ifdef USE_ALLSELECT
1213         if (mbut_state->select_others.elems_len > 0) {
1214           ui_selectcontext_apply(
1215               C, but, &mbut_state->select_others, mbut_state->origvalue, mbut_state->origvalue);
1216         }
1217 #  else
1218         UNUSED_VARS(C);
1219 #  endif
1220       }
1221     }
1222   }
1223 }
1224 
ui_multibut_free(uiHandleButtonData * data,uiBlock * block)1225 static void ui_multibut_free(uiHandleButtonData *data, uiBlock *block)
1226 {
1227 #  ifdef USE_ALLSELECT
1228   if (data->multi_data.mbuts) {
1229     LinkNode *list = data->multi_data.mbuts;
1230     while (list) {
1231       LinkNode *next = list->next;
1232       uiButMultiState *mbut_state = list->link;
1233 
1234       if (mbut_state->select_others.elems) {
1235         MEM_freeN(mbut_state->select_others.elems);
1236       }
1237 
1238       MEM_freeN(list->link);
1239       MEM_freeN(list);
1240       list = next;
1241     }
1242   }
1243 #  else
1244   BLI_linklist_freeN(data->multi_data.mbuts);
1245 #  endif
1246 
1247   data->multi_data.mbuts = NULL;
1248 
1249   if (data->multi_data.bs_mbuts) {
1250     UI_butstore_free(block, data->multi_data.bs_mbuts);
1251     data->multi_data.bs_mbuts = NULL;
1252   }
1253 }
1254 
ui_multibut_states_tag(uiBut * but_active,uiHandleButtonData * data,const wmEvent * event)1255 static bool ui_multibut_states_tag(uiBut *but_active,
1256                                    uiHandleButtonData *data,
1257                                    const wmEvent *event)
1258 {
1259   float seg[2][2];
1260   bool changed = false;
1261 
1262   seg[0][0] = data->multi_data.drag_start[0];
1263   seg[0][1] = data->multi_data.drag_start[1];
1264 
1265   seg[1][0] = event->x;
1266   seg[1][1] = event->y;
1267 
1268   BLI_assert(data->multi_data.init == BUTTON_MULTI_INIT_SETUP);
1269 
1270   ui_window_to_block_fl(data->region, but_active->block, &seg[0][0], &seg[0][1]);
1271   ui_window_to_block_fl(data->region, but_active->block, &seg[1][0], &seg[1][1]);
1272 
1273   data->multi_data.has_mbuts = false;
1274 
1275   /* follow ui_but_find_mouse_over_ex logic */
1276   LISTBASE_FOREACH (uiBut *, but, &but_active->block->buttons) {
1277     bool drag_prev = false;
1278     bool drag_curr = false;
1279 
1280     /* re-set each time */
1281     if (but->flag & UI_BUT_DRAG_MULTI) {
1282       but->flag &= ~UI_BUT_DRAG_MULTI;
1283       drag_prev = true;
1284     }
1285 
1286     if (ui_but_is_interactive(but, false)) {
1287 
1288       /* drag checks */
1289       if (but_active != but) {
1290         if (ui_but_is_compatible(but_active, but)) {
1291 
1292           BLI_assert(but->active == NULL);
1293 
1294           /* finally check for overlap */
1295           if (BLI_rctf_isect_segment(&but->rect, seg[0], seg[1])) {
1296 
1297             but->flag |= UI_BUT_DRAG_MULTI;
1298             data->multi_data.has_mbuts = true;
1299             drag_curr = true;
1300           }
1301         }
1302       }
1303     }
1304 
1305     changed |= (drag_prev != drag_curr);
1306   }
1307 
1308   return changed;
1309 }
1310 
ui_multibut_states_create(uiBut * but_active,uiHandleButtonData * data)1311 static void ui_multibut_states_create(uiBut *but_active, uiHandleButtonData *data)
1312 {
1313   BLI_assert(data->multi_data.init == BUTTON_MULTI_INIT_SETUP);
1314   BLI_assert(data->multi_data.has_mbuts);
1315 
1316   data->multi_data.bs_mbuts = UI_butstore_create(but_active->block);
1317 
1318   LISTBASE_FOREACH (uiBut *, but, &but_active->block->buttons) {
1319     if (but->flag & UI_BUT_DRAG_MULTI) {
1320       ui_multibut_add(data, but);
1321     }
1322   }
1323 
1324   /* edit buttons proportionally to eachother
1325    * note: if we mix buttons which are proportional and others which are not,
1326    * this may work a bit strangely */
1327   if ((but_active->rnaprop && (RNA_property_flag(but_active->rnaprop) & PROP_PROPORTIONAL)) ||
1328       ELEM(but_active->unit_type, RNA_SUBTYPE_UNIT_VALUE(PROP_UNIT_LENGTH))) {
1329     if (data->origvalue != 0.0) {
1330       data->multi_data.is_proportional = true;
1331     }
1332   }
1333 }
1334 
ui_multibut_states_apply(bContext * C,uiHandleButtonData * data,uiBlock * block)1335 static void ui_multibut_states_apply(bContext *C, uiHandleButtonData *data, uiBlock *block)
1336 {
1337   ARegion *region = data->region;
1338   const double value_delta = data->value - data->origvalue;
1339   const double value_scale = data->multi_data.is_proportional ? (data->value / data->origvalue) :
1340                                                                 0.0;
1341 
1342   BLI_assert(data->multi_data.init == BUTTON_MULTI_INIT_ENABLE);
1343   BLI_assert(data->multi_data.skip == false);
1344 
1345   LISTBASE_FOREACH (uiBut *, but, &block->buttons) {
1346     if (!(but->flag & UI_BUT_DRAG_MULTI)) {
1347       continue;
1348     }
1349 
1350     uiButMultiState *mbut_state = ui_multibut_lookup(data, but);
1351 
1352     if (mbut_state == NULL) {
1353       /* Highly unlikely. */
1354       printf("%s: Can't find button\n", __func__);
1355     }
1356 
1357     void *active_back;
1358     ui_but_execute_begin(C, region, but, &active_back);
1359 
1360 #  ifdef USE_ALLSELECT
1361     if (data->select_others.is_enabled) {
1362       /* init once! */
1363       if (mbut_state->select_others.elems_len == 0) {
1364         ui_selectcontext_begin(C, but, &mbut_state->select_others);
1365       }
1366       if (mbut_state->select_others.elems_len == 0) {
1367         mbut_state->select_others.elems_len = -1;
1368       }
1369     }
1370 
1371     /* Needed so we apply the right deltas. */
1372     but->active->origvalue = mbut_state->origvalue;
1373     but->active->select_others = mbut_state->select_others;
1374     but->active->select_others.do_free = false;
1375 #  endif
1376 
1377     BLI_assert(active_back == NULL);
1378     /* No need to check 'data->state' here. */
1379     if (data->str) {
1380       /* Entering text (set all). */
1381       but->active->value = data->value;
1382       ui_but_string_set(C, but, data->str);
1383     }
1384     else {
1385       /* Dragging (use delta). */
1386       if (data->multi_data.is_proportional) {
1387         but->active->value = mbut_state->origvalue * value_scale;
1388       }
1389       else {
1390         but->active->value = mbut_state->origvalue + value_delta;
1391       }
1392 
1393       /* Clamp based on soft limits, see T40154. */
1394       CLAMP(but->active->value, (double)but->softmin, (double)but->softmax);
1395     }
1396 
1397     ui_but_execute_end(C, region, but, active_back);
1398   }
1399 }
1400 
1401 #endif /* USE_DRAG_MULTINUM */
1402 
1403 /** \} */
1404 
1405 /* -------------------------------------------------------------------- */
1406 /** \name Button Drag Toggle
1407  * \{ */
1408 
1409 #ifdef USE_DRAG_TOGGLE
1410 
1411 /* Helpers that wrap boolean functions, to support different kinds of buttons. */
1412 
ui_drag_toggle_but_is_supported(const uiBut * but)1413 static bool ui_drag_toggle_but_is_supported(const uiBut *but)
1414 {
1415   if (but->flag & UI_BUT_DISABLED) {
1416     return false;
1417   }
1418   if (ui_but_is_bool(but)) {
1419     return true;
1420   }
1421   if (UI_but_is_decorator(but)) {
1422     return ELEM(but->icon,
1423                 ICON_DECORATE,
1424                 ICON_DECORATE_KEYFRAME,
1425                 ICON_DECORATE_ANIMATE,
1426                 ICON_DECORATE_OVERRIDE);
1427   }
1428   return false;
1429 }
1430 
1431 /* Button pushed state to compare if other buttons match. Can be more
1432  * then just true or false for toggle buttons with more than 2 states. */
ui_drag_toggle_but_pushed_state(bContext * C,uiBut * but)1433 static int ui_drag_toggle_but_pushed_state(bContext *C, uiBut *but)
1434 {
1435   if (but->rnapoin.data == NULL && but->poin == NULL && but->icon) {
1436     if (but->pushed_state_func) {
1437       return but->pushed_state_func(C, but->pushed_state_arg);
1438     }
1439     /* Assume icon identifies a unique state, for buttons that
1440      * work through functions callbacks and don't have an boolean
1441      * value that indicates the state. */
1442     return but->icon + but->iconadd;
1443   }
1444   if (ui_but_is_bool(but)) {
1445     return ui_but_is_pushed(but);
1446   }
1447   return 0;
1448 }
1449 
1450 typedef struct uiDragToggleHandle {
1451   /* init */
1452   int pushed_state;
1453   float but_cent_start[2];
1454 
1455   bool is_xy_lock_init;
1456   bool xy_lock[2];
1457 
1458   int xy_init[2];
1459   int xy_last[2];
1460 } uiDragToggleHandle;
1461 
ui_drag_toggle_set_xy_xy(bContext * C,ARegion * region,const int pushed_state,const int xy_src[2],const int xy_dst[2])1462 static bool ui_drag_toggle_set_xy_xy(
1463     bContext *C, ARegion *region, const int pushed_state, const int xy_src[2], const int xy_dst[2])
1464 {
1465   /* popups such as layers won't re-evaluate on redraw */
1466   const bool do_check = (region->regiontype == RGN_TYPE_TEMPORARY);
1467   bool changed = false;
1468 
1469   LISTBASE_FOREACH (uiBlock *, block, &region->uiblocks) {
1470     float xy_a_block[2] = {UNPACK2(xy_src)};
1471     float xy_b_block[2] = {UNPACK2(xy_dst)};
1472 
1473     ui_window_to_block_fl(region, block, &xy_a_block[0], &xy_a_block[1]);
1474     ui_window_to_block_fl(region, block, &xy_b_block[0], &xy_b_block[1]);
1475 
1476     LISTBASE_FOREACH (uiBut *, but, &block->buttons) {
1477       /* Note: ctrl is always true here because (at least for now)
1478        * we always want to consider text control in this case, even when not embossed. */
1479       if (ui_but_is_interactive(but, true)) {
1480         if (BLI_rctf_isect_segment(&but->rect, xy_a_block, xy_b_block)) {
1481 
1482           /* execute the button */
1483           if (ui_drag_toggle_but_is_supported(but)) {
1484             /* is it pressed? */
1485             const int pushed_state_but = ui_drag_toggle_but_pushed_state(C, but);
1486             if (pushed_state_but != pushed_state) {
1487               UI_but_execute(C, region, but);
1488               if (do_check) {
1489                 ui_but_update_edited(but);
1490               }
1491               if (U.runtime.is_dirty == false) {
1492                 ui_but_update_preferences_dirty(but);
1493               }
1494               changed = true;
1495             }
1496           }
1497           /* done */
1498         }
1499       }
1500     }
1501   }
1502   if (changed) {
1503     /* apply now, not on release (or if handlers are canceled for whatever reason) */
1504     ui_apply_but_funcs_after(C);
1505   }
1506 
1507   return changed;
1508 }
1509 
ui_drag_toggle_set(bContext * C,uiDragToggleHandle * drag_info,const int xy_input[2])1510 static void ui_drag_toggle_set(bContext *C, uiDragToggleHandle *drag_info, const int xy_input[2])
1511 {
1512   ARegion *region = CTX_wm_region(C);
1513   bool do_draw = false;
1514 
1515   /**
1516    * Initialize Locking:
1517    *
1518    * Check if we need to initialize the lock axis by finding if the first
1519    * button we mouse over is X or Y aligned, then lock the mouse to that axis after.
1520    */
1521   if (drag_info->is_xy_lock_init == false) {
1522     /* first store the buttons original coords */
1523     uiBut *but = ui_but_find_mouse_over_ex(region, xy_input[0], xy_input[1], true);
1524 
1525     if (but) {
1526       if (but->flag & UI_BUT_DRAG_LOCK) {
1527         const float but_cent_new[2] = {
1528             BLI_rctf_cent_x(&but->rect),
1529             BLI_rctf_cent_y(&but->rect),
1530         };
1531 
1532         /* check if this is a different button,
1533          * chances are high the button wont move about :) */
1534         if (len_manhattan_v2v2(drag_info->but_cent_start, but_cent_new) > 1.0f) {
1535           if (fabsf(drag_info->but_cent_start[0] - but_cent_new[0]) <
1536               fabsf(drag_info->but_cent_start[1] - but_cent_new[1])) {
1537             drag_info->xy_lock[0] = true;
1538           }
1539           else {
1540             drag_info->xy_lock[1] = true;
1541           }
1542           drag_info->is_xy_lock_init = true;
1543         }
1544       }
1545       else {
1546         drag_info->is_xy_lock_init = true;
1547       }
1548     }
1549   }
1550   /* done with axis locking */
1551 
1552   int xy[2];
1553   xy[0] = (drag_info->xy_lock[0] == false) ? xy_input[0] : drag_info->xy_last[0];
1554   xy[1] = (drag_info->xy_lock[1] == false) ? xy_input[1] : drag_info->xy_last[1];
1555 
1556   /* touch all buttons between last mouse coord and this one */
1557   do_draw = ui_drag_toggle_set_xy_xy(C, region, drag_info->pushed_state, drag_info->xy_last, xy);
1558 
1559   if (do_draw) {
1560     ED_region_tag_redraw(region);
1561   }
1562 
1563   copy_v2_v2_int(drag_info->xy_last, xy);
1564 }
1565 
ui_handler_region_drag_toggle_remove(bContext * UNUSED (C),void * userdata)1566 static void ui_handler_region_drag_toggle_remove(bContext *UNUSED(C), void *userdata)
1567 {
1568   uiDragToggleHandle *drag_info = userdata;
1569   MEM_freeN(drag_info);
1570 }
1571 
ui_handler_region_drag_toggle(bContext * C,const wmEvent * event,void * userdata)1572 static int ui_handler_region_drag_toggle(bContext *C, const wmEvent *event, void *userdata)
1573 {
1574   uiDragToggleHandle *drag_info = userdata;
1575   bool done = false;
1576 
1577   switch (event->type) {
1578     case LEFTMOUSE: {
1579       if (event->val == KM_RELEASE) {
1580         done = true;
1581       }
1582       break;
1583     }
1584     case MOUSEMOVE: {
1585       ui_drag_toggle_set(C, drag_info, &event->x);
1586       break;
1587     }
1588   }
1589 
1590   if (done) {
1591     wmWindow *win = CTX_wm_window(C);
1592     ARegion *region = CTX_wm_region(C);
1593     uiBut *but = ui_but_find_mouse_over_ex(
1594         region, drag_info->xy_init[0], drag_info->xy_init[1], true);
1595 
1596     if (but) {
1597       ui_apply_but_undo(but);
1598     }
1599 
1600     WM_event_remove_ui_handler(&win->modalhandlers,
1601                                ui_handler_region_drag_toggle,
1602                                ui_handler_region_drag_toggle_remove,
1603                                drag_info,
1604                                false);
1605     ui_handler_region_drag_toggle_remove(C, drag_info);
1606 
1607     WM_event_add_mousemove(win);
1608     return WM_UI_HANDLER_BREAK;
1609   }
1610   return WM_UI_HANDLER_CONTINUE;
1611 }
1612 
ui_but_is_drag_toggle(const uiBut * but)1613 static bool ui_but_is_drag_toggle(const uiBut *but)
1614 {
1615   return ((ui_drag_toggle_but_is_supported(but) == true) &&
1616           /* menu check is importnt so the button dragged over isn't removed instantly */
1617           (ui_block_is_menu(but->block) == false));
1618 }
1619 
1620 #endif /* USE_DRAG_TOGGLE */
1621 
1622 #ifdef USE_ALLSELECT
1623 
ui_selectcontext_begin(bContext * C,uiBut * but,uiSelectContextStore * selctx_data)1624 static bool ui_selectcontext_begin(bContext *C, uiBut *but, uiSelectContextStore *selctx_data)
1625 {
1626   PointerRNA lptr, idptr;
1627   PropertyRNA *lprop;
1628   bool success = false;
1629 
1630   char *path = NULL;
1631   ListBase lb = {NULL};
1632 
1633   PointerRNA ptr = but->rnapoin;
1634   PropertyRNA *prop = but->rnaprop;
1635   const int index = but->rnaindex;
1636 
1637   /* for now don't support whole colors */
1638   if (index == -1) {
1639     return false;
1640   }
1641 
1642   /* if there is a valid property that is editable... */
1643   if (ptr.data && prop) {
1644     bool use_path_from_id;
1645 
1646     /* some facts we want to know */
1647     const bool is_array = RNA_property_array_check(prop);
1648     const int rna_type = RNA_property_type(prop);
1649 
1650     if (UI_context_copy_to_selected_list(C, &ptr, prop, &lb, &use_path_from_id, &path) &&
1651         !BLI_listbase_is_empty(&lb)) {
1652       selctx_data->elems_len = BLI_listbase_count(&lb);
1653       selctx_data->elems = MEM_mallocN(sizeof(uiSelectContextElem) * selctx_data->elems_len,
1654                                        __func__);
1655       int i;
1656       LISTBASE_FOREACH_INDEX (CollectionPointerLink *, link, &lb, i) {
1657         if (i >= selctx_data->elems_len) {
1658           break;
1659         }
1660         uiSelectContextElem *other = &selctx_data->elems[i];
1661         /* TODO,. de-duplicate copy_to_selected_button */
1662         if (link->ptr.data != ptr.data) {
1663           if (use_path_from_id) {
1664             /* Path relative to ID. */
1665             lprop = NULL;
1666             RNA_id_pointer_create(link->ptr.owner_id, &idptr);
1667             RNA_path_resolve_property(&idptr, path, &lptr, &lprop);
1668           }
1669           else if (path) {
1670             /* Path relative to elements from list. */
1671             lprop = NULL;
1672             RNA_path_resolve_property(&link->ptr, path, &lptr, &lprop);
1673           }
1674           else {
1675             lptr = link->ptr;
1676             lprop = prop;
1677           }
1678 
1679           /* lptr might not be the same as link->ptr! */
1680           if ((lptr.data != ptr.data) && (lprop == prop) && RNA_property_editable(&lptr, lprop)) {
1681             other->ptr = lptr;
1682             if (is_array) {
1683               if (rna_type == PROP_FLOAT) {
1684                 other->val_f = RNA_property_float_get_index(&lptr, lprop, index);
1685               }
1686               else if (rna_type == PROP_INT) {
1687                 other->val_i = RNA_property_int_get_index(&lptr, lprop, index);
1688               }
1689               /* ignored for now */
1690 #  if 0
1691               else if (rna_type == PROP_BOOLEAN) {
1692                 other->val_b = RNA_property_boolean_get_index(&lptr, lprop, index);
1693               }
1694 #  endif
1695             }
1696             else {
1697               if (rna_type == PROP_FLOAT) {
1698                 other->val_f = RNA_property_float_get(&lptr, lprop);
1699               }
1700               else if (rna_type == PROP_INT) {
1701                 other->val_i = RNA_property_int_get(&lptr, lprop);
1702               }
1703               /* ignored for now */
1704 #  if 0
1705               else if (rna_type == PROP_BOOLEAN) {
1706                 other->val_b = RNA_property_boolean_get(&lptr, lprop);
1707               }
1708               else if (rna_type == PROP_ENUM) {
1709                 other->val_i = RNA_property_enum_get(&lptr, lprop);
1710               }
1711 #  endif
1712             }
1713 
1714             continue;
1715           }
1716         }
1717 
1718         selctx_data->elems_len -= 1;
1719         i -= 1;
1720       }
1721 
1722       success = (selctx_data->elems_len != 0);
1723     }
1724   }
1725 
1726   if (selctx_data->elems_len == 0) {
1727     MEM_SAFE_FREE(selctx_data->elems);
1728   }
1729 
1730   MEM_SAFE_FREE(path);
1731   BLI_freelistN(&lb);
1732 
1733   /* caller can clear */
1734   selctx_data->do_free = true;
1735 
1736   if (success) {
1737     but->flag |= UI_BUT_IS_SELECT_CONTEXT;
1738   }
1739 
1740   return success;
1741 }
1742 
ui_selectcontext_end(uiBut * but,uiSelectContextStore * selctx_data)1743 static void ui_selectcontext_end(uiBut *but, uiSelectContextStore *selctx_data)
1744 {
1745   if (selctx_data->do_free) {
1746     if (selctx_data->elems) {
1747       MEM_freeN(selctx_data->elems);
1748     }
1749   }
1750 
1751   but->flag &= ~UI_BUT_IS_SELECT_CONTEXT;
1752 }
1753 
ui_selectcontext_apply(bContext * C,uiBut * but,uiSelectContextStore * selctx_data,const double value,const double value_orig)1754 static void ui_selectcontext_apply(bContext *C,
1755                                    uiBut *but,
1756                                    uiSelectContextStore *selctx_data,
1757                                    const double value,
1758                                    const double value_orig)
1759 {
1760   if (selctx_data->elems) {
1761     PropertyRNA *prop = but->rnaprop;
1762     PropertyRNA *lprop = but->rnaprop;
1763     const int index = but->rnaindex;
1764     const bool use_delta = (selctx_data->is_copy == false);
1765 
1766     union {
1767       bool b;
1768       int i;
1769       float f;
1770       PointerRNA p;
1771     } delta, min, max;
1772 
1773     const bool is_array = RNA_property_array_check(prop);
1774     const int rna_type = RNA_property_type(prop);
1775 
1776     if (rna_type == PROP_FLOAT) {
1777       delta.f = use_delta ? (value - value_orig) : value;
1778       RNA_property_float_range(&but->rnapoin, prop, &min.f, &max.f);
1779     }
1780     else if (rna_type == PROP_INT) {
1781       delta.i = use_delta ? ((int)value - (int)value_orig) : (int)value;
1782       RNA_property_int_range(&but->rnapoin, prop, &min.i, &max.i);
1783     }
1784     else if (rna_type == PROP_ENUM) {
1785       /* Not a delta in fact. */
1786       delta.i = RNA_property_enum_get(&but->rnapoin, prop);
1787     }
1788     else if (rna_type == PROP_BOOLEAN) {
1789       if (is_array) {
1790         /* Not a delta in fact. */
1791         delta.b = RNA_property_boolean_get_index(&but->rnapoin, prop, index);
1792       }
1793       else {
1794         /* Not a delta in fact. */
1795         delta.b = RNA_property_boolean_get(&but->rnapoin, prop);
1796       }
1797     }
1798     else if (rna_type == PROP_POINTER) {
1799       /* Not a delta in fact. */
1800       delta.p = RNA_property_pointer_get(&but->rnapoin, prop);
1801     }
1802 
1803 #  ifdef USE_ALLSELECT_LAYER_HACK
1804     /* make up for not having 'handle_layer_buttons' */
1805     {
1806       const PropertySubType subtype = RNA_property_subtype(prop);
1807 
1808       if ((rna_type == PROP_BOOLEAN) && ELEM(subtype, PROP_LAYER, PROP_LAYER_MEMBER) && is_array &&
1809           /* could check for 'handle_layer_buttons' */
1810           but->func) {
1811         wmWindow *win = CTX_wm_window(C);
1812         if (!win->eventstate->shift) {
1813           const int len = RNA_property_array_length(&but->rnapoin, prop);
1814           bool *tmparray = MEM_callocN(sizeof(bool) * len, __func__);
1815 
1816           tmparray[index] = true;
1817 
1818           for (int i = 0; i < selctx_data->elems_len; i++) {
1819             uiSelectContextElem *other = &selctx_data->elems[i];
1820             PointerRNA lptr = other->ptr;
1821             RNA_property_boolean_set_array(&lptr, lprop, tmparray);
1822             RNA_property_update(C, &lptr, lprop);
1823           }
1824 
1825           MEM_freeN(tmparray);
1826 
1827           return;
1828         }
1829       }
1830     }
1831 #  endif
1832 
1833     for (int i = 0; i < selctx_data->elems_len; i++) {
1834       uiSelectContextElem *other = &selctx_data->elems[i];
1835       PointerRNA lptr = other->ptr;
1836 
1837       if (rna_type == PROP_FLOAT) {
1838         float other_value = use_delta ? (other->val_f + delta.f) : delta.f;
1839         CLAMP(other_value, min.f, max.f);
1840         if (is_array) {
1841           RNA_property_float_set_index(&lptr, lprop, index, other_value);
1842         }
1843         else {
1844           RNA_property_float_set(&lptr, lprop, other_value);
1845         }
1846       }
1847       else if (rna_type == PROP_INT) {
1848         int other_value = use_delta ? (other->val_i + delta.i) : delta.i;
1849         CLAMP(other_value, min.i, max.i);
1850         if (is_array) {
1851           RNA_property_int_set_index(&lptr, lprop, index, other_value);
1852         }
1853         else {
1854           RNA_property_int_set(&lptr, lprop, other_value);
1855         }
1856       }
1857       else if (rna_type == PROP_BOOLEAN) {
1858         const bool other_value = delta.b;
1859         if (is_array) {
1860           RNA_property_boolean_set_index(&lptr, lprop, index, other_value);
1861         }
1862         else {
1863           RNA_property_boolean_set(&lptr, lprop, delta.b);
1864         }
1865       }
1866       else if (rna_type == PROP_ENUM) {
1867         const int other_value = delta.i;
1868         BLI_assert(!is_array);
1869         RNA_property_enum_set(&lptr, lprop, other_value);
1870       }
1871       else if (rna_type == PROP_POINTER) {
1872         const PointerRNA other_value = delta.p;
1873         RNA_property_pointer_set(&lptr, lprop, other_value, NULL);
1874       }
1875 
1876       RNA_property_update(C, &lptr, prop);
1877     }
1878   }
1879 }
1880 
1881 #endif /* USE_ALLSELECT */
1882 
1883 /** \} */
1884 
1885 /* -------------------------------------------------------------------- */
1886 /** \name Button Drag
1887  * \{ */
1888 
ui_but_drag_init(bContext * C,uiBut * but,uiHandleButtonData * data,const wmEvent * event)1889 static bool ui_but_drag_init(bContext *C,
1890                              uiBut *but,
1891                              uiHandleButtonData *data,
1892                              const wmEvent *event)
1893 {
1894   /* prevent other WM gestures to start while we try to drag */
1895   WM_gestures_remove(CTX_wm_window(C));
1896 
1897   /* Clamp the maximum to half the UI unit size so a high user preference
1898    * doesn't require the user to drag more than half the default button height. */
1899   const int drag_threshold = min_ii(
1900       WM_event_drag_threshold(event),
1901       (int)((UI_UNIT_Y / 2) * ui_block_to_window_scale(data->region, but->block)));
1902 
1903   if (abs(data->dragstartx - event->x) + abs(data->dragstarty - event->y) > drag_threshold) {
1904     button_activate_state(C, but, BUTTON_STATE_EXIT);
1905     data->cancel = true;
1906 #ifdef USE_DRAG_TOGGLE
1907     if (ui_drag_toggle_but_is_supported(but)) {
1908       uiDragToggleHandle *drag_info = MEM_callocN(sizeof(*drag_info), __func__);
1909       ARegion *region_prev;
1910 
1911       /* call here because regular mouse-up event wont run,
1912        * typically 'button_activate_exit()' handles this */
1913       ui_apply_but_autokey(C, but);
1914 
1915       drag_info->pushed_state = ui_drag_toggle_but_pushed_state(C, but);
1916       drag_info->but_cent_start[0] = BLI_rctf_cent_x(&but->rect);
1917       drag_info->but_cent_start[1] = BLI_rctf_cent_y(&but->rect);
1918       copy_v2_v2_int(drag_info->xy_init, &event->x);
1919       copy_v2_v2_int(drag_info->xy_last, &event->x);
1920 
1921       /* needed for toggle drag on popups */
1922       region_prev = CTX_wm_region(C);
1923       CTX_wm_region_set(C, data->region);
1924 
1925       WM_event_add_ui_handler(C,
1926                               &data->window->modalhandlers,
1927                               ui_handler_region_drag_toggle,
1928                               ui_handler_region_drag_toggle_remove,
1929                               drag_info,
1930                               WM_HANDLER_BLOCKING);
1931 
1932       CTX_wm_region_set(C, region_prev);
1933 
1934       /* Initialize alignment for single row/column regions,
1935        * otherwise we use the relative position of the first other button dragged over. */
1936       if (ELEM(data->region->regiontype,
1937                RGN_TYPE_NAV_BAR,
1938                RGN_TYPE_HEADER,
1939                RGN_TYPE_TOOL_HEADER,
1940                RGN_TYPE_FOOTER)) {
1941         const int region_alignment = RGN_ALIGN_ENUM_FROM_MASK(data->region->alignment);
1942         int lock_axis = -1;
1943 
1944         if (ELEM(region_alignment, RGN_ALIGN_LEFT, RGN_ALIGN_RIGHT)) {
1945           lock_axis = 0;
1946         }
1947         else if (ELEM(region_alignment, RGN_ALIGN_TOP, RGN_ALIGN_BOTTOM)) {
1948           lock_axis = 1;
1949         }
1950         if (lock_axis != -1) {
1951           drag_info->xy_lock[lock_axis] = true;
1952           drag_info->is_xy_lock_init = true;
1953         }
1954       }
1955     }
1956     else
1957 #endif
1958         if (but->type == UI_BTYPE_COLOR) {
1959       bool valid = false;
1960       uiDragColorHandle *drag_info = MEM_callocN(sizeof(*drag_info), __func__);
1961 
1962       /* TODO support more button pointer types */
1963       if (but->rnaprop && RNA_property_subtype(but->rnaprop) == PROP_COLOR_GAMMA) {
1964         ui_but_v3_get(but, drag_info->color);
1965         drag_info->gamma_corrected = true;
1966         valid = true;
1967       }
1968       else if (but->rnaprop && RNA_property_subtype(but->rnaprop) == PROP_COLOR) {
1969         ui_but_v3_get(but, drag_info->color);
1970         drag_info->gamma_corrected = false;
1971         valid = true;
1972       }
1973       else if (ELEM(but->pointype, UI_BUT_POIN_FLOAT, UI_BUT_POIN_CHAR)) {
1974         ui_but_v3_get(but, drag_info->color);
1975         copy_v3_v3(drag_info->color, (float *)but->poin);
1976         valid = true;
1977       }
1978 
1979       if (valid) {
1980         WM_event_start_drag(C, ICON_COLOR, WM_DRAG_COLOR, drag_info, 0.0, WM_DRAG_FREE_DATA);
1981       }
1982       else {
1983         MEM_freeN(drag_info);
1984         return false;
1985       }
1986     }
1987     else {
1988       wmDrag *drag = WM_event_start_drag(
1989           C, but->icon, but->dragtype, but->dragpoin, ui_but_value_get(but), WM_DRAG_NOP);
1990 
1991       if (but->imb) {
1992         WM_event_drag_image(drag,
1993                             but->imb,
1994                             but->imb_scale,
1995                             BLI_rctf_size_x(&but->rect),
1996                             BLI_rctf_size_y(&but->rect));
1997       }
1998     }
1999     return true;
2000   }
2001 
2002   return false;
2003 }
2004 
2005 /** \} */
2006 
2007 /* -------------------------------------------------------------------- */
2008 /** \name Button Apply
2009  * \{ */
2010 
ui_apply_but_IMAGE(bContext * C,uiBut * but,uiHandleButtonData * data)2011 static void ui_apply_but_IMAGE(bContext *C, uiBut *but, uiHandleButtonData *data)
2012 {
2013   ui_apply_but_func(C, but);
2014   data->retval = but->retval;
2015   data->applied = true;
2016 }
2017 
ui_apply_but_HISTOGRAM(bContext * C,uiBut * but,uiHandleButtonData * data)2018 static void ui_apply_but_HISTOGRAM(bContext *C, uiBut *but, uiHandleButtonData *data)
2019 {
2020   ui_apply_but_func(C, but);
2021   data->retval = but->retval;
2022   data->applied = true;
2023 }
2024 
ui_apply_but_WAVEFORM(bContext * C,uiBut * but,uiHandleButtonData * data)2025 static void ui_apply_but_WAVEFORM(bContext *C, uiBut *but, uiHandleButtonData *data)
2026 {
2027   ui_apply_but_func(C, but);
2028   data->retval = but->retval;
2029   data->applied = true;
2030 }
2031 
ui_apply_but_TRACKPREVIEW(bContext * C,uiBut * but,uiHandleButtonData * data)2032 static void ui_apply_but_TRACKPREVIEW(bContext *C, uiBut *but, uiHandleButtonData *data)
2033 {
2034   ui_apply_but_func(C, but);
2035   data->retval = but->retval;
2036   data->applied = true;
2037 }
2038 
ui_apply_but(bContext * C,uiBlock * block,uiBut * but,uiHandleButtonData * data,const bool interactive)2039 static void ui_apply_but(
2040     bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const bool interactive)
2041 {
2042   const eButType but_type = but->type; /* Store as const to quiet maybe uninitialized warning. */
2043 
2044   data->retval = 0;
2045 
2046   /* if we cancel and have not applied yet, there is nothing to do,
2047    * otherwise we have to restore the original value again */
2048   if (data->cancel) {
2049     if (!data->applied) {
2050       return;
2051     }
2052 
2053     if (data->str) {
2054       MEM_freeN(data->str);
2055     }
2056     data->str = data->origstr;
2057     data->origstr = NULL;
2058     data->value = data->origvalue;
2059     copy_v3_v3(data->vec, data->origvec);
2060     /* postpone clearing origdata */
2061   }
2062   else {
2063     /* We avoid applying interactive edits a second time
2064      * at the end with the #uiHandleButtonData.applied_interactive flag. */
2065     if (interactive) {
2066       data->applied_interactive = true;
2067     }
2068     else if (data->applied_interactive) {
2069       return;
2070     }
2071 
2072 #ifdef USE_ALLSELECT
2073 #  ifdef USE_DRAG_MULTINUM
2074     if (but->flag & UI_BUT_DRAG_MULTI) {
2075       /* pass */
2076     }
2077     else
2078 #  endif
2079         if (data->select_others.elems_len == 0) {
2080       wmWindow *win = CTX_wm_window(C);
2081       /* may have been enabled before activating */
2082       if (data->select_others.is_enabled || IS_ALLSELECT_EVENT(win->eventstate)) {
2083         ui_selectcontext_begin(C, but, &data->select_others);
2084         data->select_others.is_enabled = true;
2085       }
2086     }
2087     if (data->select_others.elems_len == 0) {
2088       /* dont check again */
2089       data->select_others.elems_len = -1;
2090     }
2091 #endif
2092   }
2093 
2094   /* ensures we are writing actual values */
2095   char *editstr = but->editstr;
2096   double *editval = but->editval;
2097   float *editvec = but->editvec;
2098   ColorBand *editcoba;
2099   CurveMapping *editcumap;
2100   CurveProfile *editprofile;
2101   if (but_type == UI_BTYPE_COLORBAND) {
2102     uiButColorBand *but_coba = (uiButColorBand *)but;
2103     editcoba = but_coba->edit_coba;
2104   }
2105   else if (but_type == UI_BTYPE_CURVE) {
2106     uiButCurveMapping *but_cumap = (uiButCurveMapping *)but;
2107     editcumap = but_cumap->edit_cumap;
2108   }
2109   else if (but_type == UI_BTYPE_CURVEPROFILE) {
2110     uiButCurveProfile *but_profile = (uiButCurveProfile *)but;
2111     editprofile = but_profile->edit_profile;
2112   }
2113   but->editstr = NULL;
2114   but->editval = NULL;
2115   but->editvec = NULL;
2116   if (but_type == UI_BTYPE_COLORBAND) {
2117     uiButColorBand *but_coba = (uiButColorBand *)but;
2118     but_coba->edit_coba = NULL;
2119   }
2120   else if (but_type == UI_BTYPE_CURVE) {
2121     uiButCurveMapping *but_cumap = (uiButCurveMapping *)but;
2122     but_cumap->edit_cumap = NULL;
2123   }
2124   else if (but_type == UI_BTYPE_CURVEPROFILE) {
2125     uiButCurveProfile *but_profile = (uiButCurveProfile *)but;
2126     but_profile->edit_profile = NULL;
2127   }
2128 
2129   /* handle different types */
2130   switch (but_type) {
2131     case UI_BTYPE_BUT:
2132     case UI_BTYPE_DECORATOR:
2133       ui_apply_but_BUT(C, but, data);
2134       break;
2135     case UI_BTYPE_TEXT:
2136     case UI_BTYPE_SEARCH_MENU:
2137       ui_apply_but_TEX(C, but, data);
2138       break;
2139     case UI_BTYPE_BUT_TOGGLE:
2140     case UI_BTYPE_TOGGLE:
2141     case UI_BTYPE_TOGGLE_N:
2142     case UI_BTYPE_ICON_TOGGLE:
2143     case UI_BTYPE_ICON_TOGGLE_N:
2144     case UI_BTYPE_CHECKBOX:
2145     case UI_BTYPE_CHECKBOX_N:
2146       ui_apply_but_TOG(C, but, data);
2147       break;
2148     case UI_BTYPE_ROW:
2149     case UI_BTYPE_LISTROW:
2150       ui_apply_but_ROW(C, block, but, data);
2151       break;
2152     case UI_BTYPE_TAB:
2153       ui_apply_but_TAB(C, but, data);
2154       break;
2155     case UI_BTYPE_SCROLL:
2156     case UI_BTYPE_GRIP:
2157     case UI_BTYPE_NUM:
2158     case UI_BTYPE_NUM_SLIDER:
2159       ui_apply_but_NUM(C, but, data);
2160       break;
2161     case UI_BTYPE_MENU:
2162     case UI_BTYPE_BLOCK:
2163     case UI_BTYPE_PULLDOWN:
2164       ui_apply_but_BLOCK(C, but, data);
2165       break;
2166     case UI_BTYPE_COLOR:
2167       if (data->cancel) {
2168         ui_apply_but_VEC(C, but, data);
2169       }
2170       else {
2171         ui_apply_but_BLOCK(C, but, data);
2172       }
2173       break;
2174     case UI_BTYPE_BUT_MENU:
2175       ui_apply_but_BUTM(C, but, data);
2176       break;
2177     case UI_BTYPE_UNITVEC:
2178     case UI_BTYPE_HSVCUBE:
2179     case UI_BTYPE_HSVCIRCLE:
2180       ui_apply_but_VEC(C, but, data);
2181       break;
2182     case UI_BTYPE_COLORBAND:
2183       ui_apply_but_COLORBAND(C, but, data);
2184       break;
2185     case UI_BTYPE_CURVE:
2186       ui_apply_but_CURVE(C, but, data);
2187       break;
2188     case UI_BTYPE_CURVEPROFILE:
2189       ui_apply_but_CURVEPROFILE(C, but, data);
2190       break;
2191     case UI_BTYPE_KEY_EVENT:
2192     case UI_BTYPE_HOTKEY_EVENT:
2193       ui_apply_but_BUT(C, but, data);
2194       break;
2195     case UI_BTYPE_IMAGE:
2196       ui_apply_but_IMAGE(C, but, data);
2197       break;
2198     case UI_BTYPE_HISTOGRAM:
2199       ui_apply_but_HISTOGRAM(C, but, data);
2200       break;
2201     case UI_BTYPE_WAVEFORM:
2202       ui_apply_but_WAVEFORM(C, but, data);
2203       break;
2204     case UI_BTYPE_TRACK_PREVIEW:
2205       ui_apply_but_TRACKPREVIEW(C, but, data);
2206       break;
2207     default:
2208       break;
2209   }
2210 
2211 #ifdef USE_DRAG_MULTINUM
2212   if (data->multi_data.has_mbuts) {
2213     if ((data->multi_data.init == BUTTON_MULTI_INIT_ENABLE) && (data->multi_data.skip == false)) {
2214       if (data->cancel) {
2215         ui_multibut_restore(C, data, block);
2216       }
2217       else {
2218         ui_multibut_states_apply(C, data, block);
2219       }
2220     }
2221   }
2222 #endif
2223 
2224 #ifdef USE_ALLSELECT
2225   ui_selectcontext_apply(C, but, &data->select_others, data->value, data->origvalue);
2226 #endif
2227 
2228   if (data->cancel) {
2229     data->origvalue = 0.0;
2230     zero_v3(data->origvec);
2231   }
2232 
2233   but->editstr = editstr;
2234   but->editval = editval;
2235   but->editvec = editvec;
2236   if (but_type == UI_BTYPE_COLORBAND) {
2237     uiButColorBand *but_coba = (uiButColorBand *)but;
2238     but_coba->edit_coba = editcoba;
2239   }
2240   else if (but_type == UI_BTYPE_CURVE) {
2241     uiButCurveMapping *but_cumap = (uiButCurveMapping *)but;
2242     but_cumap->edit_cumap = editcumap;
2243   }
2244   else if (but_type == UI_BTYPE_CURVEPROFILE) {
2245     uiButCurveProfile *but_profile = (uiButCurveProfile *)but;
2246     but_profile->edit_profile = editprofile;
2247   }
2248 }
2249 
2250 /** \} */
2251 
2252 /* -------------------------------------------------------------------- */
2253 /** \name Button Drop Event
2254  * \{ */
2255 
2256 /* only call if event type is EVT_DROP */
ui_but_drop(bContext * C,const wmEvent * event,uiBut * but,uiHandleButtonData * data)2257 static void ui_but_drop(bContext *C, const wmEvent *event, uiBut *but, uiHandleButtonData *data)
2258 {
2259   ListBase *drags = event->customdata; /* drop event type has listbase customdata by default */
2260 
2261   LISTBASE_FOREACH (wmDrag *, wmd, drags) {
2262     if (wmd->type == WM_DRAG_ID) {
2263       /* align these types with UI_but_active_drop_name */
2264       if (ELEM(but->type, UI_BTYPE_TEXT, UI_BTYPE_SEARCH_MENU)) {
2265         ID *id = WM_drag_ID(wmd, 0);
2266 
2267         button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING);
2268 
2269         ui_textedit_string_set(but, data, id->name + 2);
2270 
2271         if (ELEM(but->type, UI_BTYPE_SEARCH_MENU)) {
2272           but->changed = true;
2273           ui_searchbox_update(C, data->searchbox, but, true);
2274         }
2275 
2276         button_activate_state(C, but, BUTTON_STATE_EXIT);
2277       }
2278     }
2279   }
2280 }
2281 
2282 /** \} */
2283 
2284 /* -------------------------------------------------------------------- */
2285 /** \name Button Copy & Paste
2286  * \{ */
2287 
ui_but_get_pasted_text_from_clipboard(char ** buf_paste,int * buf_len)2288 static void ui_but_get_pasted_text_from_clipboard(char **buf_paste, int *buf_len)
2289 {
2290   /* get only first line even if the clipboard contains multiple lines */
2291   int length;
2292   char *text = WM_clipboard_text_get_firstline(false, &length);
2293 
2294   if (text) {
2295     *buf_paste = text;
2296     *buf_len = length;
2297   }
2298   else {
2299     *buf_paste = MEM_callocN(sizeof(char), __func__);
2300     *buf_len = 0;
2301   }
2302 }
2303 
get_but_property_array_length(uiBut * but)2304 static int get_but_property_array_length(uiBut *but)
2305 {
2306   return RNA_property_array_length(&but->rnapoin, but->rnaprop);
2307 }
2308 
ui_but_set_float_array(bContext * C,uiBut * but,uiHandleButtonData * data,float * values,int array_length)2309 static void ui_but_set_float_array(
2310     bContext *C, uiBut *but, uiHandleButtonData *data, float *values, int array_length)
2311 {
2312   button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
2313 
2314   for (int i = 0; i < array_length; i++) {
2315     RNA_property_float_set_index(&but->rnapoin, but->rnaprop, i, values[i]);
2316   }
2317   if (data) {
2318     if (but->type == UI_BTYPE_UNITVEC) {
2319       BLI_assert(array_length == 3);
2320       copy_v3_v3(data->vec, values);
2321     }
2322     else {
2323       data->value = values[but->rnaindex];
2324     }
2325   }
2326 
2327   button_activate_state(C, but, BUTTON_STATE_EXIT);
2328 }
2329 
float_array_to_string(float * values,int array_length,char * output,int output_len_max)2330 static void float_array_to_string(float *values,
2331                                   int array_length,
2332                                   char *output,
2333                                   int output_len_max)
2334 {
2335   /* to avoid buffer overflow attacks; numbers are quite arbitrary */
2336   BLI_assert(output_len_max > 15);
2337   output_len_max -= 10;
2338 
2339   int current_index = 0;
2340   output[current_index] = '[';
2341   current_index++;
2342 
2343   for (int i = 0; i < array_length; i++) {
2344     int length = BLI_snprintf(
2345         output + current_index, output_len_max - current_index, "%f", values[i]);
2346     current_index += length;
2347 
2348     if (i < array_length - 1) {
2349       if (current_index < output_len_max) {
2350         output[current_index + 0] = ',';
2351         output[current_index + 1] = ' ';
2352         current_index += 2;
2353       }
2354     }
2355   }
2356 
2357   output[current_index + 0] = ']';
2358   output[current_index + 1] = '\0';
2359 }
2360 
ui_but_copy_numeric_array(uiBut * but,char * output,int output_len_max)2361 static void ui_but_copy_numeric_array(uiBut *but, char *output, int output_len_max)
2362 {
2363   const int array_length = get_but_property_array_length(but);
2364   float *values = alloca(array_length * sizeof(float));
2365   RNA_property_float_get_array(&but->rnapoin, but->rnaprop, values);
2366   float_array_to_string(values, array_length, output, output_len_max);
2367 }
2368 
parse_float_array(char * text,float * values,int expected_length)2369 static bool parse_float_array(char *text, float *values, int expected_length)
2370 {
2371   /* can parse max 4 floats for now */
2372   BLI_assert(0 <= expected_length && expected_length <= 4);
2373 
2374   float v[5];
2375   const int actual_length = sscanf(
2376       text, "[%f, %f, %f, %f, %f]", &v[0], &v[1], &v[2], &v[3], &v[4]);
2377 
2378   if (actual_length == expected_length) {
2379     memcpy(values, v, sizeof(float) * expected_length);
2380     return true;
2381   }
2382   return false;
2383 }
2384 
ui_but_paste_numeric_array(bContext * C,uiBut * but,uiHandleButtonData * data,char * buf_paste)2385 static void ui_but_paste_numeric_array(bContext *C,
2386                                        uiBut *but,
2387                                        uiHandleButtonData *data,
2388                                        char *buf_paste)
2389 {
2390   const int array_length = get_but_property_array_length(but);
2391   if (array_length > 4) {
2392     /* not supported for now */
2393     return;
2394   }
2395 
2396   float *values = alloca(sizeof(float) * array_length);
2397 
2398   if (parse_float_array(buf_paste, values, array_length)) {
2399     ui_but_set_float_array(C, but, data, values, array_length);
2400   }
2401   else {
2402     WM_report(RPT_ERROR, "Expected an array of numbers: [n, n, ...]");
2403   }
2404 }
2405 
ui_but_copy_numeric_value(uiBut * but,char * output,int output_len_max)2406 static void ui_but_copy_numeric_value(uiBut *but, char *output, int output_len_max)
2407 {
2408   /* Get many decimal places, then strip trailing zeros.
2409    * note: too high values start to give strange results */
2410   ui_but_string_get_ex(but, output, output_len_max, UI_PRECISION_FLOAT_MAX, false, NULL);
2411   BLI_str_rstrip_float_zero(output, '\0');
2412 }
2413 
ui_but_paste_numeric_value(bContext * C,uiBut * but,uiHandleButtonData * data,char * buf_paste)2414 static void ui_but_paste_numeric_value(bContext *C,
2415                                        uiBut *but,
2416                                        uiHandleButtonData *data,
2417                                        char *buf_paste)
2418 {
2419   double value;
2420   if (ui_but_string_eval_number(C, but, buf_paste, &value)) {
2421     button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
2422     data->value = value;
2423     ui_but_string_set(C, but, buf_paste);
2424     button_activate_state(C, but, BUTTON_STATE_EXIT);
2425   }
2426   else {
2427     WM_report(RPT_ERROR, "Expected a number");
2428   }
2429 }
2430 
ui_but_paste_normalized_vector(bContext * C,uiBut * but,uiHandleButtonData * data,char * buf_paste)2431 static void ui_but_paste_normalized_vector(bContext *C,
2432                                            uiBut *but,
2433                                            uiHandleButtonData *data,
2434                                            char *buf_paste)
2435 {
2436   float xyz[3];
2437   if (parse_float_array(buf_paste, xyz, 3)) {
2438     if (normalize_v3(xyz) == 0.0f) {
2439       /* better set Z up then have a zero vector */
2440       xyz[2] = 1.0;
2441     }
2442     ui_but_set_float_array(C, but, data, xyz, 3);
2443   }
2444   else {
2445     WM_report(RPT_ERROR, "Paste expected 3 numbers, formatted: '[n, n, n]'");
2446   }
2447 }
2448 
ui_but_copy_color(uiBut * but,char * output,int output_len_max)2449 static void ui_but_copy_color(uiBut *but, char *output, int output_len_max)
2450 {
2451   float rgba[4];
2452 
2453   if (but->rnaprop && get_but_property_array_length(but) == 4) {
2454     rgba[3] = RNA_property_float_get_index(&but->rnapoin, but->rnaprop, 3);
2455   }
2456   else {
2457     rgba[3] = 1.0f;
2458   }
2459 
2460   ui_but_v3_get(but, rgba);
2461 
2462   /* convert to linear color to do compatible copy between gamma and non-gamma */
2463   if (but->rnaprop && RNA_property_subtype(but->rnaprop) == PROP_COLOR_GAMMA) {
2464     srgb_to_linearrgb_v3_v3(rgba, rgba);
2465   }
2466 
2467   float_array_to_string(rgba, 4, output, output_len_max);
2468 }
2469 
ui_but_paste_color(bContext * C,uiBut * but,char * buf_paste)2470 static void ui_but_paste_color(bContext *C, uiBut *but, char *buf_paste)
2471 {
2472   float rgba[4];
2473   if (parse_float_array(buf_paste, rgba, 4)) {
2474     if (but->rnaprop) {
2475       /* Assume linear colors in buffer. */
2476       if (RNA_property_subtype(but->rnaprop) == PROP_COLOR_GAMMA) {
2477         linearrgb_to_srgb_v3_v3(rgba, rgba);
2478       }
2479 
2480       /* Some color properties are RGB, not RGBA. */
2481       const int array_len = get_but_property_array_length(but);
2482       BLI_assert(ELEM(array_len, 3, 4));
2483       ui_but_set_float_array(C, but, NULL, rgba, array_len);
2484     }
2485   }
2486   else {
2487     WM_report(RPT_ERROR, "Paste expected 4 numbers, formatted: '[n, n, n, n]'");
2488   }
2489 }
2490 
ui_but_copy_text(uiBut * but,char * output,int output_len_max)2491 static void ui_but_copy_text(uiBut *but, char *output, int output_len_max)
2492 {
2493   ui_but_string_get(but, output, output_len_max);
2494 }
2495 
ui_but_paste_text(bContext * C,uiBut * but,uiHandleButtonData * data,char * buf_paste)2496 static void ui_but_paste_text(bContext *C, uiBut *but, uiHandleButtonData *data, char *buf_paste)
2497 {
2498   button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING);
2499   ui_textedit_string_set(but, but->active, buf_paste);
2500 
2501   if (but->type == UI_BTYPE_SEARCH_MENU) {
2502     but->changed = true;
2503     ui_searchbox_update(C, data->searchbox, but, true);
2504   }
2505 
2506   button_activate_state(C, but, BUTTON_STATE_EXIT);
2507 }
2508 
ui_but_copy_colorband(uiBut * but)2509 static void ui_but_copy_colorband(uiBut *but)
2510 {
2511   if (but->poin != NULL) {
2512     memcpy(&but_copypaste_coba, but->poin, sizeof(ColorBand));
2513   }
2514 }
2515 
ui_but_paste_colorband(bContext * C,uiBut * but,uiHandleButtonData * data)2516 static void ui_but_paste_colorband(bContext *C, uiBut *but, uiHandleButtonData *data)
2517 {
2518   if (but_copypaste_coba.tot != 0) {
2519     if (!but->poin) {
2520       but->poin = MEM_callocN(sizeof(ColorBand), "colorband");
2521     }
2522 
2523     button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
2524     memcpy(data->coba, &but_copypaste_coba, sizeof(ColorBand));
2525     button_activate_state(C, but, BUTTON_STATE_EXIT);
2526   }
2527 }
2528 
ui_but_copy_curvemapping(uiBut * but)2529 static void ui_but_copy_curvemapping(uiBut *but)
2530 {
2531   if (but->poin != NULL) {
2532     but_copypaste_curve_alive = true;
2533     BKE_curvemapping_free_data(&but_copypaste_curve);
2534     BKE_curvemapping_copy_data(&but_copypaste_curve, (CurveMapping *)but->poin);
2535   }
2536 }
2537 
ui_but_paste_curvemapping(bContext * C,uiBut * but)2538 static void ui_but_paste_curvemapping(bContext *C, uiBut *but)
2539 {
2540   if (but_copypaste_curve_alive) {
2541     if (!but->poin) {
2542       but->poin = MEM_callocN(sizeof(CurveMapping), "curvemapping");
2543     }
2544 
2545     button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
2546     BKE_curvemapping_free_data((CurveMapping *)but->poin);
2547     BKE_curvemapping_copy_data((CurveMapping *)but->poin, &but_copypaste_curve);
2548     button_activate_state(C, but, BUTTON_STATE_EXIT);
2549   }
2550 }
2551 
ui_but_copy_CurveProfile(uiBut * but)2552 static void ui_but_copy_CurveProfile(uiBut *but)
2553 {
2554   if (but->poin != NULL) {
2555     but_copypaste_profile_alive = true;
2556     BKE_curveprofile_free_data(&but_copypaste_profile);
2557     BKE_curveprofile_copy_data(&but_copypaste_profile, (CurveProfile *)but->poin);
2558   }
2559 }
2560 
ui_but_paste_CurveProfile(bContext * C,uiBut * but)2561 static void ui_but_paste_CurveProfile(bContext *C, uiBut *but)
2562 {
2563   if (but_copypaste_profile_alive) {
2564     if (!but->poin) {
2565       but->poin = MEM_callocN(sizeof(CurveProfile), "CurveProfile");
2566     }
2567 
2568     button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
2569     BKE_curveprofile_free_data((CurveProfile *)but->poin);
2570     BKE_curveprofile_copy_data((CurveProfile *)but->poin, &but_copypaste_profile);
2571     button_activate_state(C, but, BUTTON_STATE_EXIT);
2572   }
2573 }
2574 
ui_but_copy_operator(bContext * C,uiBut * but,char * output,int output_len_max)2575 static void ui_but_copy_operator(bContext *C, uiBut *but, char *output, int output_len_max)
2576 {
2577   PointerRNA *opptr = UI_but_operator_ptr_get(but);
2578 
2579   char *str;
2580   str = WM_operator_pystring_ex(C, NULL, false, true, but->optype, opptr);
2581   BLI_strncpy(output, str, output_len_max);
2582   MEM_freeN(str);
2583 }
2584 
ui_but_copy_menu(uiBut * but,char * output,int output_len_max)2585 static bool ui_but_copy_menu(uiBut *but, char *output, int output_len_max)
2586 {
2587   MenuType *mt = UI_but_menutype_get(but);
2588   if (mt) {
2589     BLI_snprintf(output, output_len_max, "bpy.ops.wm.call_menu(name=\"%s\")", mt->idname);
2590     return true;
2591   }
2592   return false;
2593 }
2594 
ui_but_copy_popover(uiBut * but,char * output,int output_len_max)2595 static bool ui_but_copy_popover(uiBut *but, char *output, int output_len_max)
2596 {
2597   PanelType *pt = UI_but_paneltype_get(but);
2598   if (pt) {
2599     BLI_snprintf(output, output_len_max, "bpy.ops.wm.call_panel(name=\"%s\")", pt->idname);
2600     return true;
2601   }
2602   return false;
2603 }
2604 
ui_but_copy(bContext * C,uiBut * but,const bool copy_array)2605 static void ui_but_copy(bContext *C, uiBut *but, const bool copy_array)
2606 {
2607   if (ui_but_contains_password(but)) {
2608     return;
2609   }
2610 
2611   /* Arbitrary large value (allow for paths: 'PATH_MAX') */
2612   char buf[4096] = {0};
2613   const int buf_max_len = sizeof(buf);
2614 
2615   /* Left false for copying internal data (color-band for eg). */
2616   bool is_buf_set = false;
2617 
2618   const bool has_required_data = !(but->poin == NULL && but->rnapoin.data == NULL);
2619 
2620   switch (but->type) {
2621     case UI_BTYPE_NUM:
2622     case UI_BTYPE_NUM_SLIDER:
2623       if (!has_required_data) {
2624         break;
2625       }
2626       if (copy_array && ui_but_has_array_value(but)) {
2627         ui_but_copy_numeric_array(but, buf, buf_max_len);
2628       }
2629       else {
2630         ui_but_copy_numeric_value(but, buf, buf_max_len);
2631       }
2632       is_buf_set = true;
2633       break;
2634 
2635     case UI_BTYPE_UNITVEC:
2636       if (!has_required_data) {
2637         break;
2638       }
2639       ui_but_copy_numeric_array(but, buf, buf_max_len);
2640       is_buf_set = true;
2641       break;
2642 
2643     case UI_BTYPE_COLOR:
2644       if (!has_required_data) {
2645         break;
2646       }
2647       ui_but_copy_color(but, buf, buf_max_len);
2648       is_buf_set = true;
2649       break;
2650 
2651     case UI_BTYPE_TEXT:
2652     case UI_BTYPE_SEARCH_MENU:
2653       if (!has_required_data) {
2654         break;
2655       }
2656       ui_but_copy_text(but, buf, buf_max_len);
2657       is_buf_set = true;
2658       break;
2659 
2660     case UI_BTYPE_COLORBAND:
2661       ui_but_copy_colorband(but);
2662       break;
2663 
2664     case UI_BTYPE_CURVE:
2665       ui_but_copy_curvemapping(but);
2666       break;
2667 
2668     case UI_BTYPE_CURVEPROFILE:
2669       ui_but_copy_CurveProfile(but);
2670       break;
2671 
2672     case UI_BTYPE_BUT:
2673       if (!but->optype) {
2674         break;
2675       }
2676       ui_but_copy_operator(C, but, buf, buf_max_len);
2677       is_buf_set = true;
2678       break;
2679 
2680     case UI_BTYPE_MENU:
2681     case UI_BTYPE_PULLDOWN:
2682       if (ui_but_copy_menu(but, buf, buf_max_len)) {
2683         is_buf_set = true;
2684       }
2685       break;
2686     case UI_BTYPE_POPOVER:
2687       if (ui_but_copy_popover(but, buf, buf_max_len)) {
2688         is_buf_set = true;
2689       }
2690       break;
2691 
2692     default:
2693       break;
2694   }
2695 
2696   if (is_buf_set) {
2697     WM_clipboard_text_set(buf, 0);
2698   }
2699 }
2700 
ui_but_paste(bContext * C,uiBut * but,uiHandleButtonData * data,const bool paste_array)2701 static void ui_but_paste(bContext *C, uiBut *but, uiHandleButtonData *data, const bool paste_array)
2702 {
2703   BLI_assert((but->flag & UI_BUT_DISABLED) == 0); /* caller should check */
2704 
2705   int buf_paste_len = 0;
2706   char *buf_paste;
2707   ui_but_get_pasted_text_from_clipboard(&buf_paste, &buf_paste_len);
2708 
2709   const bool has_required_data = !(but->poin == NULL && but->rnapoin.data == NULL);
2710 
2711   switch (but->type) {
2712     case UI_BTYPE_NUM:
2713     case UI_BTYPE_NUM_SLIDER:
2714       if (!has_required_data) {
2715         break;
2716       }
2717       if (paste_array && ui_but_has_array_value(but)) {
2718         ui_but_paste_numeric_array(C, but, data, buf_paste);
2719       }
2720       else {
2721         ui_but_paste_numeric_value(C, but, data, buf_paste);
2722       }
2723       break;
2724 
2725     case UI_BTYPE_UNITVEC:
2726       if (!has_required_data) {
2727         break;
2728       }
2729       ui_but_paste_normalized_vector(C, but, data, buf_paste);
2730       break;
2731 
2732     case UI_BTYPE_COLOR:
2733       if (!has_required_data) {
2734         break;
2735       }
2736       ui_but_paste_color(C, but, buf_paste);
2737       break;
2738 
2739     case UI_BTYPE_TEXT:
2740     case UI_BTYPE_SEARCH_MENU:
2741       if (!has_required_data) {
2742         break;
2743       }
2744       ui_but_paste_text(C, but, data, buf_paste);
2745       break;
2746 
2747     case UI_BTYPE_COLORBAND:
2748       ui_but_paste_colorband(C, but, data);
2749       break;
2750 
2751     case UI_BTYPE_CURVE:
2752       ui_but_paste_curvemapping(C, but);
2753       break;
2754 
2755     case UI_BTYPE_CURVEPROFILE:
2756       ui_but_paste_CurveProfile(C, but);
2757       break;
2758 
2759     default:
2760       break;
2761   }
2762 
2763   MEM_freeN((void *)buf_paste);
2764 }
2765 
ui_but_clipboard_free(void)2766 void ui_but_clipboard_free(void)
2767 {
2768   BKE_curvemapping_free_data(&but_copypaste_curve);
2769   BKE_curveprofile_free_data(&but_copypaste_profile);
2770 }
2771 
2772 /** \} */
2773 
2774 /* -------------------------------------------------------------------- */
2775 /** \name Button Text Password
2776  *
2777  * Functions to convert password strings that should not be displayed
2778  * to asterisk representation (e.g. 'mysecretpasswd' -> '*************')
2779  *
2780  * It converts every UTF-8 character to an asterisk, and also remaps
2781  * the cursor position and selection start/end.
2782  *
2783  * \note remapping is used, because password could contain UTF-8 characters.
2784  *
2785  * \{ */
2786 
ui_text_position_from_hidden(uiBut * but,int pos)2787 static int ui_text_position_from_hidden(uiBut *but, int pos)
2788 {
2789   const char *butstr = (but->editstr) ? but->editstr : but->drawstr;
2790   const char *strpos = butstr;
2791   for (int i = 0; i < pos; i++) {
2792     strpos = BLI_str_find_next_char_utf8(strpos, NULL);
2793   }
2794 
2795   return (strpos - butstr);
2796 }
2797 
ui_text_position_to_hidden(uiBut * but,int pos)2798 static int ui_text_position_to_hidden(uiBut *but, int pos)
2799 {
2800   const char *butstr = (but->editstr) ? but->editstr : but->drawstr;
2801   return BLI_strnlen_utf8(butstr, pos);
2802 }
2803 
ui_but_text_password_hide(char password_str[UI_MAX_PASSWORD_STR],uiBut * but,const bool restore)2804 void ui_but_text_password_hide(char password_str[UI_MAX_PASSWORD_STR],
2805                                uiBut *but,
2806                                const bool restore)
2807 {
2808   if (!(but->rnaprop && RNA_property_subtype(but->rnaprop) == PROP_PASSWORD)) {
2809     return;
2810   }
2811 
2812   char *butstr = (but->editstr) ? but->editstr : but->drawstr;
2813 
2814   if (restore) {
2815     /* restore original string */
2816     BLI_strncpy(butstr, password_str, UI_MAX_PASSWORD_STR);
2817 
2818     /* remap cursor positions */
2819     if (but->pos >= 0) {
2820       but->pos = ui_text_position_from_hidden(but, but->pos);
2821       but->selsta = ui_text_position_from_hidden(but, but->selsta);
2822       but->selend = ui_text_position_from_hidden(but, but->selend);
2823     }
2824   }
2825   else {
2826     /* convert text to hidden text using asterisks (e.g. pass -> ****) */
2827     const size_t len = BLI_strlen_utf8(butstr);
2828 
2829     /* remap cursor positions */
2830     if (but->pos >= 0) {
2831       but->pos = ui_text_position_to_hidden(but, but->pos);
2832       but->selsta = ui_text_position_to_hidden(but, but->selsta);
2833       but->selend = ui_text_position_to_hidden(but, but->selend);
2834     }
2835 
2836     /* save original string */
2837     BLI_strncpy(password_str, butstr, UI_MAX_PASSWORD_STR);
2838     memset(butstr, '*', len);
2839     butstr[len] = '\0';
2840   }
2841 }
2842 
2843 /** \} */
2844 
2845 /* -------------------------------------------------------------------- */
2846 /** \name Button Text Selection/Editing
2847  * \{ */
2848 
ui_but_active_string_clear_and_exit(bContext * C,uiBut * but)2849 void ui_but_active_string_clear_and_exit(bContext *C, uiBut *but)
2850 {
2851   if (!but->active) {
2852     return;
2853   }
2854 
2855   /* most likely NULL, but let's check, and give it temp zero string */
2856   if (!but->active->str) {
2857     but->active->str = MEM_callocN(1, "temp str");
2858   }
2859   but->active->str[0] = 0;
2860 
2861   ui_apply_but_TEX(C, but, but->active);
2862   button_activate_state(C, but, BUTTON_STATE_EXIT);
2863 }
2864 
ui_textedit_string_ensure_max_length(uiBut * but,uiHandleButtonData * data,int maxlen)2865 static void ui_textedit_string_ensure_max_length(uiBut *but, uiHandleButtonData *data, int maxlen)
2866 {
2867   BLI_assert(data->is_str_dynamic);
2868   BLI_assert(data->str == but->editstr);
2869 
2870   if (maxlen > data->maxlen) {
2871     data->str = but->editstr = MEM_reallocN(data->str, sizeof(char) * maxlen);
2872     data->maxlen = maxlen;
2873   }
2874 }
2875 
ui_textedit_string_set(uiBut * but,uiHandleButtonData * data,const char * str)2876 static void ui_textedit_string_set(uiBut *but, uiHandleButtonData *data, const char *str)
2877 {
2878   if (data->is_str_dynamic) {
2879     ui_textedit_string_ensure_max_length(but, data, strlen(str) + 1);
2880   }
2881 
2882   if (UI_but_is_utf8(but)) {
2883     BLI_strncpy_utf8(data->str, str, data->maxlen);
2884   }
2885   else {
2886     BLI_strncpy(data->str, str, data->maxlen);
2887   }
2888 }
2889 
ui_textedit_delete_selection(uiBut * but,uiHandleButtonData * data)2890 static bool ui_textedit_delete_selection(uiBut *but, uiHandleButtonData *data)
2891 {
2892   char *str = data->str;
2893   const int len = strlen(str);
2894   bool changed = false;
2895   if (but->selsta != but->selend && len) {
2896     memmove(str + but->selsta, str + but->selend, (len - but->selend) + 1);
2897     changed = true;
2898   }
2899 
2900   but->pos = but->selend = but->selsta;
2901   return changed;
2902 }
2903 
ui_textedit_set_cursor_pos_foreach_glyph(const char * UNUSED (str),const size_t str_step_ofs,const rcti * glyph_step_bounds,const int UNUSED (glyph_advance_x),const rctf * glyph_bounds,const int UNUSED (glyph_bearing[2]),void * user_data)2904 static bool ui_textedit_set_cursor_pos_foreach_glyph(const char *UNUSED(str),
2905                                                      const size_t str_step_ofs,
2906                                                      const rcti *glyph_step_bounds,
2907                                                      const int UNUSED(glyph_advance_x),
2908                                                      const rctf *glyph_bounds,
2909                                                      const int UNUSED(glyph_bearing[2]),
2910                                                      void *user_data)
2911 {
2912   int *cursor_data = user_data;
2913   const float center = glyph_step_bounds->xmin + (BLI_rctf_size_x(glyph_bounds) / 2.0f);
2914   if (cursor_data[0] < center) {
2915     cursor_data[1] = str_step_ofs;
2916     return false;
2917   }
2918   return true;
2919 }
2920 
2921 /**
2922  * \param x: Screen space cursor location - #wmEvent.x
2923  *
2924  * \note ``but->block->aspect`` is used here, so drawing button style is getting scaled too.
2925  */
ui_textedit_set_cursor_pos(uiBut * but,uiHandleButtonData * data,const float x)2926 static void ui_textedit_set_cursor_pos(uiBut *but, uiHandleButtonData *data, const float x)
2927 {
2928   /* XXX pass on as arg. */
2929   uiFontStyle fstyle = UI_style_get()->widget;
2930   const float aspect = but->block->aspect;
2931 
2932   float startx = but->rect.xmin;
2933   float starty_dummy = 0.0f;
2934   char password_str[UI_MAX_PASSWORD_STR];
2935   /* treat 'str_last' as null terminator for str, no need to modify in-place */
2936   const char *str = but->editstr, *str_last;
2937 
2938   ui_block_to_window_fl(data->region, but->block, &startx, &starty_dummy);
2939 
2940   ui_fontscale(&fstyle.points, aspect);
2941 
2942   UI_fontstyle_set(&fstyle);
2943 
2944   if (fstyle.kerning == 1) {
2945     /* for BLF_width */
2946     BLF_enable(fstyle.uifont_id, BLF_KERNING_DEFAULT);
2947   }
2948 
2949   ui_but_text_password_hide(password_str, but, false);
2950 
2951   if (ELEM(but->type, UI_BTYPE_TEXT, UI_BTYPE_SEARCH_MENU)) {
2952     if (but->flag & UI_HAS_ICON) {
2953       startx += UI_DPI_ICON_SIZE / aspect;
2954     }
2955   }
2956   startx += (UI_TEXT_MARGIN_X * U.widget_unit) / aspect;
2957 
2958   /* mouse dragged outside the widget to the left */
2959   if (x < startx) {
2960     int i = but->ofs;
2961 
2962     str_last = &str[but->ofs];
2963 
2964     while (i > 0) {
2965       if (BLI_str_cursor_step_prev_utf8(str, but->ofs, &i)) {
2966         /* 0.25 == scale factor for less sensitivity */
2967         if (BLF_width(fstyle.uifont_id, str + i, (str_last - str) - i) > (startx - x) * 0.25f) {
2968           break;
2969         }
2970       }
2971       else {
2972         break; /* unlikely but possible */
2973       }
2974     }
2975     but->ofs = i;
2976     but->pos = but->ofs;
2977   }
2978   /* mouse inside the widget, mouse coords mapped in widget space */
2979   else {
2980     str_last = &str[but->ofs];
2981     const int str_last_len = strlen(str_last);
2982     const int x_pos = (int)(x - startx);
2983     int glyph_data[2] = {
2984         x_pos, /* horizontal position to test. */
2985         -1,    /* Write the character offset here. */
2986     };
2987     BLF_boundbox_foreach_glyph(fstyle.uifont_id,
2988                                str + but->ofs,
2989                                INT_MAX,
2990                                ui_textedit_set_cursor_pos_foreach_glyph,
2991                                glyph_data);
2992     /* If value untouched then we are to the right. */
2993     if (glyph_data[1] == -1) {
2994       glyph_data[1] = str_last_len;
2995     }
2996     but->pos = glyph_data[1] + but->ofs;
2997   }
2998 
2999   if (fstyle.kerning == 1) {
3000     BLF_disable(fstyle.uifont_id, BLF_KERNING_DEFAULT);
3001   }
3002 
3003   ui_but_text_password_hide(password_str, but, true);
3004 }
3005 
ui_textedit_set_cursor_select(uiBut * but,uiHandleButtonData * data,const float x)3006 static void ui_textedit_set_cursor_select(uiBut *but, uiHandleButtonData *data, const float x)
3007 {
3008   ui_textedit_set_cursor_pos(but, data, x);
3009 
3010   but->selsta = but->pos;
3011   but->selend = data->sel_pos_init;
3012   if (but->selend < but->selsta) {
3013     SWAP(short, but->selsta, but->selend);
3014   }
3015 
3016   ui_but_update(but);
3017 }
3018 
3019 /**
3020  * This is used for both utf8 and ascii
3021  *
3022  * For unicode buttons, \a buf is treated as unicode.
3023  */
ui_textedit_insert_buf(uiBut * but,uiHandleButtonData * data,const char * buf,int buf_len)3024 static bool ui_textedit_insert_buf(uiBut *but,
3025                                    uiHandleButtonData *data,
3026                                    const char *buf,
3027                                    int buf_len)
3028 {
3029   int len = strlen(data->str);
3030   const int len_new = len - (but->selend - but->selsta) + 1;
3031   bool changed = false;
3032 
3033   if (data->is_str_dynamic) {
3034     ui_textedit_string_ensure_max_length(but, data, len_new + buf_len);
3035   }
3036 
3037   if (len_new <= data->maxlen) {
3038     char *str = data->str;
3039     size_t step = buf_len;
3040 
3041     /* type over the current selection */
3042     if ((but->selend - but->selsta) > 0) {
3043       changed = ui_textedit_delete_selection(but, data);
3044       len = strlen(str);
3045     }
3046 
3047     if ((len + step >= data->maxlen) && (data->maxlen - (len + 1) > 0)) {
3048       if (UI_but_is_utf8(but)) {
3049         /* shorten 'step' to a utf8 aligned size that fits  */
3050         BLI_strnlen_utf8_ex(buf, data->maxlen - (len + 1), &step);
3051       }
3052       else {
3053         step = data->maxlen - (len + 1);
3054       }
3055     }
3056 
3057     if (step && (len + step < data->maxlen)) {
3058       memmove(&str[but->pos + step], &str[but->pos], (len + 1) - but->pos);
3059       memcpy(&str[but->pos], buf, step * sizeof(char));
3060       but->pos += step;
3061       changed = true;
3062     }
3063   }
3064 
3065   return changed;
3066 }
3067 
ui_textedit_insert_ascii(uiBut * but,uiHandleButtonData * data,char ascii)3068 static bool ui_textedit_insert_ascii(uiBut *but, uiHandleButtonData *data, char ascii)
3069 {
3070   const char buf[2] = {ascii, '\0'};
3071 
3072   if (UI_but_is_utf8(but) && (BLI_str_utf8_size(buf) == -1)) {
3073     printf(
3074         "%s: entering invalid ascii char into an ascii key (%d)\n", __func__, (int)(uchar)ascii);
3075 
3076     return false;
3077   }
3078 
3079   /* in some cases we want to allow invalid utf8 chars */
3080   return ui_textedit_insert_buf(but, data, buf, 1);
3081 }
3082 
ui_textedit_move(uiBut * but,uiHandleButtonData * data,eStrCursorJumpDirection direction,const bool select,eStrCursorJumpType jump)3083 static void ui_textedit_move(uiBut *but,
3084                              uiHandleButtonData *data,
3085                              eStrCursorJumpDirection direction,
3086                              const bool select,
3087                              eStrCursorJumpType jump)
3088 {
3089   const char *str = data->str;
3090   const int len = strlen(str);
3091   const int pos_prev = but->pos;
3092   const bool has_sel = (but->selend - but->selsta) > 0;
3093 
3094   ui_but_update(but);
3095 
3096   /* special case, quit selection and set cursor */
3097   if (has_sel && !select) {
3098     if (jump == STRCUR_JUMP_ALL) {
3099       but->selsta = but->selend = but->pos = direction ? len : 0;
3100     }
3101     else {
3102       if (direction) {
3103         but->selsta = but->pos = but->selend;
3104       }
3105       else {
3106         but->pos = but->selend = but->selsta;
3107       }
3108     }
3109     data->sel_pos_init = but->pos;
3110   }
3111   else {
3112     int pos_i = but->pos;
3113     BLI_str_cursor_step_utf8(str, len, &pos_i, direction, jump, true);
3114     but->pos = pos_i;
3115 
3116     if (select) {
3117       if (has_sel == false) {
3118         data->sel_pos_init = pos_prev;
3119       }
3120       but->selsta = but->pos;
3121       but->selend = data->sel_pos_init;
3122     }
3123     if (but->selend < but->selsta) {
3124       SWAP(short, but->selsta, but->selend);
3125     }
3126   }
3127 }
3128 
ui_textedit_delete(uiBut * but,uiHandleButtonData * data,int direction,eStrCursorJumpType jump)3129 static bool ui_textedit_delete(uiBut *but,
3130                                uiHandleButtonData *data,
3131                                int direction,
3132                                eStrCursorJumpType jump)
3133 {
3134   char *str = data->str;
3135   const int len = strlen(str);
3136 
3137   bool changed = false;
3138 
3139   if (jump == STRCUR_JUMP_ALL) {
3140     if (len) {
3141       changed = true;
3142     }
3143     str[0] = '\0';
3144     but->pos = 0;
3145   }
3146   else if (direction) { /* delete */
3147     if ((but->selend - but->selsta) > 0) {
3148       changed = ui_textedit_delete_selection(but, data);
3149     }
3150     else if (but->pos >= 0 && but->pos < len) {
3151       int pos = but->pos;
3152       int step;
3153       BLI_str_cursor_step_utf8(str, len, &pos, direction, jump, true);
3154       step = pos - but->pos;
3155       memmove(&str[but->pos], &str[but->pos + step], (len + 1) - (but->pos + step));
3156       changed = true;
3157     }
3158   }
3159   else { /* backspace */
3160     if (len != 0) {
3161       if ((but->selend - but->selsta) > 0) {
3162         changed = ui_textedit_delete_selection(but, data);
3163       }
3164       else if (but->pos > 0) {
3165         int pos = but->pos;
3166         int step;
3167 
3168         BLI_str_cursor_step_utf8(str, len, &pos, direction, jump, true);
3169         step = but->pos - pos;
3170         memmove(&str[but->pos - step], &str[but->pos], (len + 1) - but->pos);
3171         but->pos -= step;
3172         changed = true;
3173       }
3174     }
3175   }
3176 
3177   return changed;
3178 }
3179 
ui_textedit_autocomplete(bContext * C,uiBut * but,uiHandleButtonData * data)3180 static int ui_textedit_autocomplete(bContext *C, uiBut *but, uiHandleButtonData *data)
3181 {
3182   char *str = data->str;
3183 
3184   int changed;
3185   if (data->searchbox) {
3186     changed = ui_searchbox_autocomplete(C, data->searchbox, but, data->str);
3187   }
3188   else {
3189     changed = but->autocomplete_func(C, str, but->autofunc_arg);
3190   }
3191 
3192   but->pos = strlen(str);
3193   but->selsta = but->selend = but->pos;
3194 
3195   return changed;
3196 }
3197 
3198 /* mode for ui_textedit_copypaste() */
3199 enum {
3200   UI_TEXTEDIT_PASTE = 1,
3201   UI_TEXTEDIT_COPY,
3202   UI_TEXTEDIT_CUT,
3203 };
3204 
ui_textedit_copypaste(uiBut * but,uiHandleButtonData * data,const int mode)3205 static bool ui_textedit_copypaste(uiBut *but, uiHandleButtonData *data, const int mode)
3206 {
3207   bool changed = false;
3208 
3209   /* paste */
3210   if (mode == UI_TEXTEDIT_PASTE) {
3211     /* extract the first line from the clipboard */
3212     int buf_len;
3213     char *pbuf = WM_clipboard_text_get_firstline(false, &buf_len);
3214 
3215     if (pbuf) {
3216       if (UI_but_is_utf8(but)) {
3217         buf_len -= BLI_utf8_invalid_strip(pbuf, (size_t)buf_len);
3218       }
3219 
3220       ui_textedit_insert_buf(but, data, pbuf, buf_len);
3221 
3222       changed = true;
3223 
3224       MEM_freeN(pbuf);
3225     }
3226   }
3227   /* cut & copy */
3228   else if (ELEM(mode, UI_TEXTEDIT_COPY, UI_TEXTEDIT_CUT)) {
3229     /* copy the contents to the copypaste buffer */
3230     const int sellen = but->selend - but->selsta;
3231     char *buf = MEM_mallocN(sizeof(char) * (sellen + 1), "ui_textedit_copypaste");
3232 
3233     BLI_strncpy(buf, data->str + but->selsta, sellen + 1);
3234     WM_clipboard_text_set(buf, 0);
3235     MEM_freeN(buf);
3236 
3237     /* for cut only, delete the selection afterwards */
3238     if (mode == UI_TEXTEDIT_CUT) {
3239       if ((but->selend - but->selsta) > 0) {
3240         changed = ui_textedit_delete_selection(but, data);
3241       }
3242     }
3243   }
3244 
3245   return changed;
3246 }
3247 
3248 #ifdef WITH_INPUT_IME
3249 /* enable ime, and set up uibut ime data */
ui_textedit_ime_begin(wmWindow * win,uiBut * UNUSED (but))3250 static void ui_textedit_ime_begin(wmWindow *win, uiBut *UNUSED(but))
3251 {
3252   /* XXX Is this really needed? */
3253   int x, y;
3254 
3255   BLI_assert(win->ime_data == NULL);
3256 
3257   /* enable IME and position to cursor, it's a trick */
3258   x = win->eventstate->x;
3259   /* flip y and move down a bit, prevent the IME panel cover the edit button */
3260   y = win->eventstate->y - 12;
3261 
3262   wm_window_IME_begin(win, x, y, 0, 0, true);
3263 }
3264 
3265 /* disable ime, and clear uibut ime data */
ui_textedit_ime_end(wmWindow * win,uiBut * UNUSED (but))3266 static void ui_textedit_ime_end(wmWindow *win, uiBut *UNUSED(but))
3267 {
3268   wm_window_IME_end(win);
3269 }
3270 
ui_but_ime_reposition(uiBut * but,int x,int y,bool complete)3271 void ui_but_ime_reposition(uiBut *but, int x, int y, bool complete)
3272 {
3273   BLI_assert(but->active);
3274 
3275   ui_region_to_window(but->active->region, &x, &y);
3276   wm_window_IME_begin(but->active->window, x, y - 4, 0, 0, complete);
3277 }
3278 
ui_but_ime_data_get(uiBut * but)3279 wmIMEData *ui_but_ime_data_get(uiBut *but)
3280 {
3281   if (but->active && but->active->window) {
3282     return but->active->window->ime_data;
3283   }
3284   else {
3285     return NULL;
3286   }
3287 }
3288 #endif /* WITH_INPUT_IME */
3289 
ui_textedit_begin(bContext * C,uiBut * but,uiHandleButtonData * data)3290 static void ui_textedit_begin(bContext *C, uiBut *but, uiHandleButtonData *data)
3291 {
3292   wmWindow *win = data->window;
3293   const bool is_num_but = ELEM(but->type, UI_BTYPE_NUM, UI_BTYPE_NUM_SLIDER);
3294   bool no_zero_strip = false;
3295 
3296   if (data->str) {
3297     MEM_freeN(data->str);
3298     data->str = NULL;
3299   }
3300 
3301 #ifdef USE_DRAG_MULTINUM
3302   /* this can happen from multi-drag */
3303   if (data->applied_interactive) {
3304     /* remove any small changes so canceling edit doesn't restore invalid value: T40538 */
3305     data->cancel = true;
3306     ui_apply_but(C, but->block, but, data, true);
3307     data->cancel = false;
3308 
3309     data->applied_interactive = false;
3310   }
3311 #endif
3312 
3313 #ifdef USE_ALLSELECT
3314   if (is_num_but) {
3315     if (IS_ALLSELECT_EVENT(win->eventstate)) {
3316       data->select_others.is_enabled = true;
3317       data->select_others.is_copy = true;
3318     }
3319   }
3320 #endif
3321 
3322   /* retrieve string */
3323   data->maxlen = ui_but_string_get_max_length(but);
3324   if (data->maxlen != 0) {
3325     data->str = MEM_callocN(sizeof(char) * data->maxlen, "textedit str");
3326     /* We do not want to truncate precision to default here, it's nice to show value,
3327      * not to edit it - way too much precision is lost then. */
3328     ui_but_string_get_ex(
3329         but, data->str, data->maxlen, UI_PRECISION_FLOAT_MAX, true, &no_zero_strip);
3330   }
3331   else {
3332     data->is_str_dynamic = true;
3333     data->str = ui_but_string_get_dynamic(but, &data->maxlen);
3334   }
3335 
3336   if (ui_but_is_float(but) && !ui_but_is_unit(but) && !ui_but_anim_expression_get(but, NULL, 0) &&
3337       !no_zero_strip) {
3338     BLI_str_rstrip_float_zero(data->str, '\0');
3339   }
3340 
3341   if (is_num_but) {
3342     BLI_assert(data->is_str_dynamic == false);
3343     ui_but_convert_to_unit_alt_name(but, data->str, data->maxlen);
3344   }
3345 
3346   /* won't change from now on */
3347   const int len = strlen(data->str);
3348 
3349   data->origstr = BLI_strdupn(data->str, len);
3350   data->sel_pos_init = 0;
3351 
3352   /* set cursor pos to the end of the text */
3353   but->editstr = data->str;
3354   but->pos = len;
3355   but->selsta = 0;
3356   but->selend = len;
3357 
3358   /* Initialize undo history tracking. */
3359   data->undo_stack_text = ui_textedit_undo_stack_create();
3360   ui_textedit_undo_push(data->undo_stack_text, but->editstr, but->pos);
3361 
3362   /* optional searchbox */
3363   if (but->type == UI_BTYPE_SEARCH_MENU) {
3364     uiButSearch *search_but = (uiButSearch *)but;
3365 
3366     data->searchbox = search_but->popup_create_fn(C, data->region, search_but);
3367     ui_searchbox_update(C, data->searchbox, but, true); /* true = reset */
3368   }
3369 
3370   /* reset alert flag (avoid confusion, will refresh on exit) */
3371   but->flag &= ~UI_BUT_REDALERT;
3372 
3373   ui_but_update(but);
3374 
3375   WM_cursor_modal_set(win, WM_CURSOR_TEXT_EDIT);
3376 
3377 #ifdef WITH_INPUT_IME
3378   if (is_num_but == false && BLT_lang_is_ime_supported()) {
3379     ui_textedit_ime_begin(win, but);
3380   }
3381 #endif
3382 }
3383 
ui_textedit_end(bContext * C,uiBut * but,uiHandleButtonData * data)3384 static void ui_textedit_end(bContext *C, uiBut *but, uiHandleButtonData *data)
3385 {
3386   wmWindow *win = data->window;
3387 
3388   if (but) {
3389     if (UI_but_is_utf8(but)) {
3390       const int strip = BLI_utf8_invalid_strip(but->editstr, strlen(but->editstr));
3391       /* not a file?, strip non utf-8 chars */
3392       if (strip) {
3393         /* wont happen often so isn't that annoying to keep it here for a while */
3394         printf("%s: invalid utf8 - stripped chars %d\n", __func__, strip);
3395       }
3396     }
3397 
3398     if (data->searchbox) {
3399       if (data->cancel == false) {
3400         if ((ui_searchbox_apply(but, data->searchbox) == false) &&
3401             (ui_searchbox_find_index(data->searchbox, but->editstr) == -1)) {
3402           data->cancel = true;
3403 
3404           /* ensure menu (popup) too is closed! */
3405           data->escapecancel = true;
3406 
3407           WM_reportf(RPT_ERROR, "Failed to find '%s'", but->editstr);
3408           WM_report_banner_show();
3409         }
3410       }
3411 
3412       ui_searchbox_free(C, data->searchbox);
3413       data->searchbox = NULL;
3414     }
3415 
3416     but->editstr = NULL;
3417     but->pos = -1;
3418   }
3419 
3420   WM_cursor_modal_restore(win);
3421 
3422   /* Free text undo history text blocks. */
3423   ui_textedit_undo_stack_destroy(data->undo_stack_text);
3424   data->undo_stack_text = NULL;
3425 
3426 #ifdef WITH_INPUT_IME
3427   if (win->ime_data) {
3428     ui_textedit_ime_end(win, but);
3429   }
3430 #endif
3431 }
3432 
ui_textedit_next_but(uiBlock * block,uiBut * actbut,uiHandleButtonData * data)3433 static void ui_textedit_next_but(uiBlock *block, uiBut *actbut, uiHandleButtonData *data)
3434 {
3435   /* label and roundbox can overlap real buttons (backdrops...) */
3436   if (ELEM(actbut->type,
3437            UI_BTYPE_LABEL,
3438            UI_BTYPE_SEPR,
3439            UI_BTYPE_SEPR_LINE,
3440            UI_BTYPE_ROUNDBOX,
3441            UI_BTYPE_LISTBOX)) {
3442     return;
3443   }
3444 
3445   for (uiBut *but = actbut->next; but; but = but->next) {
3446     if (ui_but_is_editable_as_text(but)) {
3447       if (!(but->flag & UI_BUT_DISABLED)) {
3448         data->postbut = but;
3449         data->posttype = BUTTON_ACTIVATE_TEXT_EDITING;
3450         return;
3451       }
3452     }
3453   }
3454   for (uiBut *but = block->buttons.first; but != actbut; but = but->next) {
3455     if (ui_but_is_editable_as_text(but)) {
3456       if (!(but->flag & UI_BUT_DISABLED)) {
3457         data->postbut = but;
3458         data->posttype = BUTTON_ACTIVATE_TEXT_EDITING;
3459         return;
3460       }
3461     }
3462   }
3463 }
3464 
ui_textedit_prev_but(uiBlock * block,uiBut * actbut,uiHandleButtonData * data)3465 static void ui_textedit_prev_but(uiBlock *block, uiBut *actbut, uiHandleButtonData *data)
3466 {
3467   /* label and roundbox can overlap real buttons (backdrops...) */
3468   if (ELEM(actbut->type,
3469            UI_BTYPE_LABEL,
3470            UI_BTYPE_SEPR,
3471            UI_BTYPE_SEPR_LINE,
3472            UI_BTYPE_ROUNDBOX,
3473            UI_BTYPE_LISTBOX)) {
3474     return;
3475   }
3476 
3477   for (uiBut *but = actbut->prev; but; but = but->prev) {
3478     if (ui_but_is_editable_as_text(but)) {
3479       if (!(but->flag & UI_BUT_DISABLED)) {
3480         data->postbut = but;
3481         data->posttype = BUTTON_ACTIVATE_TEXT_EDITING;
3482         return;
3483       }
3484     }
3485   }
3486   for (uiBut *but = block->buttons.last; but != actbut; but = but->prev) {
3487     if (ui_but_is_editable_as_text(but)) {
3488       if (!(but->flag & UI_BUT_DISABLED)) {
3489         data->postbut = but;
3490         data->posttype = BUTTON_ACTIVATE_TEXT_EDITING;
3491         return;
3492       }
3493     }
3494   }
3495 }
3496 
ui_do_but_textedit(bContext * C,uiBlock * block,uiBut * but,uiHandleButtonData * data,const wmEvent * event)3497 static void ui_do_but_textedit(
3498     bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
3499 {
3500   int retval = WM_UI_HANDLER_CONTINUE;
3501   bool changed = false, inbox = false, update = false, skip_undo_push = false;
3502 
3503 #ifdef WITH_INPUT_IME
3504   wmWindow *win = CTX_wm_window(C);
3505   wmIMEData *ime_data = win->ime_data;
3506   const bool is_ime_composing = ime_data && ime_data->is_ime_composing;
3507 #else
3508   const bool is_ime_composing = false;
3509 #endif
3510 
3511   switch (event->type) {
3512     case MOUSEMOVE:
3513     case MOUSEPAN:
3514       if (data->searchbox) {
3515 #ifdef USE_KEYNAV_LIMIT
3516         if ((event->type == MOUSEMOVE) &&
3517             ui_mouse_motion_keynav_test(&data->searchbox_keynav_state, event)) {
3518           /* pass */
3519         }
3520         else {
3521           ui_searchbox_event(C, data->searchbox, but, data->region, event);
3522         }
3523 #else
3524         ui_searchbox_event(C, data->searchbox, but, data->region, event);
3525 #endif
3526       }
3527       ui_do_but_extra_operator_icons_mousemove(but, data, event);
3528 
3529       break;
3530     case RIGHTMOUSE:
3531     case EVT_ESCKEY:
3532       if (event->val == KM_PRESS) {
3533         /* Support search context menu. */
3534         if (event->type == RIGHTMOUSE) {
3535           if (data->searchbox) {
3536             if (ui_searchbox_event(C, data->searchbox, but, data->region, event)) {
3537               /* Only break if the event was handled. */
3538               break;
3539             }
3540           }
3541         }
3542 
3543 #ifdef WITH_INPUT_IME
3544         /* skips button handling since it is not wanted */
3545         if (is_ime_composing) {
3546           break;
3547         }
3548 #endif
3549         data->cancel = true;
3550         data->escapecancel = true;
3551         button_activate_state(C, but, BUTTON_STATE_EXIT);
3552         retval = WM_UI_HANDLER_BREAK;
3553       }
3554       break;
3555     case LEFTMOUSE: {
3556       /* Allow clicks on extra icons while editing. */
3557       if (ui_do_but_extra_operator_icon(C, but, data, event)) {
3558         break;
3559       }
3560 
3561       const bool had_selection = but->selsta != but->selend;
3562 
3563       /* exit on LMB only on RELEASE for searchbox, to mimic other popups,
3564        * and allow multiple menu levels */
3565       if (data->searchbox) {
3566         inbox = ui_searchbox_inside(data->searchbox, event->x, event->y);
3567       }
3568 
3569       /* for double click: we do a press again for when you first click on button
3570        * (selects all text, no cursor pos) */
3571       if (event->val == KM_PRESS || event->val == KM_DBL_CLICK) {
3572         float mx = event->x;
3573         float my = event->y;
3574         ui_window_to_block_fl(data->region, block, &mx, &my);
3575 
3576         if (ui_but_contains_pt(but, mx, my)) {
3577           ui_textedit_set_cursor_pos(but, data, event->x);
3578           but->selsta = but->selend = but->pos;
3579           data->sel_pos_init = but->pos;
3580 
3581           button_activate_state(C, but, BUTTON_STATE_TEXT_SELECTING);
3582           retval = WM_UI_HANDLER_BREAK;
3583         }
3584         else if (inbox == false) {
3585           /* if searchbox, click outside will cancel */
3586           if (data->searchbox) {
3587             data->cancel = data->escapecancel = true;
3588           }
3589           button_activate_state(C, but, BUTTON_STATE_EXIT);
3590           retval = WM_UI_HANDLER_BREAK;
3591         }
3592       }
3593 
3594       /* only select a word in button if there was no selection before */
3595       if (event->val == KM_DBL_CLICK && had_selection == false) {
3596         ui_textedit_move(but, data, STRCUR_DIR_PREV, false, STRCUR_JUMP_DELIM);
3597         ui_textedit_move(but, data, STRCUR_DIR_NEXT, true, STRCUR_JUMP_DELIM);
3598         retval = WM_UI_HANDLER_BREAK;
3599         changed = true;
3600       }
3601       else if (inbox) {
3602         /* if we allow activation on key press,
3603          * it gives problems launching operators T35713. */
3604         if (event->val == KM_RELEASE) {
3605           button_activate_state(C, but, BUTTON_STATE_EXIT);
3606           retval = WM_UI_HANDLER_BREAK;
3607         }
3608       }
3609       break;
3610     }
3611   }
3612 
3613   if (event->val == KM_PRESS && !is_ime_composing) {
3614     switch (event->type) {
3615       case EVT_VKEY:
3616       case EVT_XKEY:
3617       case EVT_CKEY:
3618         if (IS_EVENT_MOD(event, ctrl, oskey)) {
3619           if (event->type == EVT_VKEY) {
3620             changed = ui_textedit_copypaste(but, data, UI_TEXTEDIT_PASTE);
3621           }
3622           else if (event->type == EVT_CKEY) {
3623             changed = ui_textedit_copypaste(but, data, UI_TEXTEDIT_COPY);
3624           }
3625           else if (event->type == EVT_XKEY) {
3626             changed = ui_textedit_copypaste(but, data, UI_TEXTEDIT_CUT);
3627           }
3628 
3629           retval = WM_UI_HANDLER_BREAK;
3630         }
3631         break;
3632       case EVT_RIGHTARROWKEY:
3633         ui_textedit_move(but,
3634                          data,
3635                          STRCUR_DIR_NEXT,
3636                          event->shift != 0,
3637                          event->ctrl ? STRCUR_JUMP_DELIM : STRCUR_JUMP_NONE);
3638         retval = WM_UI_HANDLER_BREAK;
3639         break;
3640       case EVT_LEFTARROWKEY:
3641         ui_textedit_move(but,
3642                          data,
3643                          STRCUR_DIR_PREV,
3644                          event->shift != 0,
3645                          event->ctrl ? STRCUR_JUMP_DELIM : STRCUR_JUMP_NONE);
3646         retval = WM_UI_HANDLER_BREAK;
3647         break;
3648       case WHEELDOWNMOUSE:
3649       case EVT_DOWNARROWKEY:
3650         if (data->searchbox) {
3651 #ifdef USE_KEYNAV_LIMIT
3652           ui_mouse_motion_keynav_init(&data->searchbox_keynav_state, event);
3653 #endif
3654           ui_searchbox_event(C, data->searchbox, but, data->region, event);
3655           break;
3656         }
3657         if (event->type == WHEELDOWNMOUSE) {
3658           break;
3659         }
3660         ATTR_FALLTHROUGH;
3661       case EVT_ENDKEY:
3662         ui_textedit_move(but, data, STRCUR_DIR_NEXT, event->shift != 0, STRCUR_JUMP_ALL);
3663         retval = WM_UI_HANDLER_BREAK;
3664         break;
3665       case WHEELUPMOUSE:
3666       case EVT_UPARROWKEY:
3667         if (data->searchbox) {
3668 #ifdef USE_KEYNAV_LIMIT
3669           ui_mouse_motion_keynav_init(&data->searchbox_keynav_state, event);
3670 #endif
3671           ui_searchbox_event(C, data->searchbox, but, data->region, event);
3672           break;
3673         }
3674         if (event->type == WHEELUPMOUSE) {
3675           break;
3676         }
3677         ATTR_FALLTHROUGH;
3678       case EVT_HOMEKEY:
3679         ui_textedit_move(but, data, STRCUR_DIR_PREV, event->shift != 0, STRCUR_JUMP_ALL);
3680         retval = WM_UI_HANDLER_BREAK;
3681         break;
3682       case EVT_PADENTER:
3683       case EVT_RETKEY:
3684         button_activate_state(C, but, BUTTON_STATE_EXIT);
3685         retval = WM_UI_HANDLER_BREAK;
3686         break;
3687       case EVT_DELKEY:
3688         changed = ui_textedit_delete(
3689             but, data, 1, event->ctrl ? STRCUR_JUMP_DELIM : STRCUR_JUMP_NONE);
3690         retval = WM_UI_HANDLER_BREAK;
3691         break;
3692 
3693       case EVT_BACKSPACEKEY:
3694         changed = ui_textedit_delete(
3695             but, data, 0, event->ctrl ? STRCUR_JUMP_DELIM : STRCUR_JUMP_NONE);
3696         retval = WM_UI_HANDLER_BREAK;
3697         break;
3698 
3699       case EVT_AKEY:
3700 
3701         /* Ctrl + A: Select all */
3702 #if defined(__APPLE__)
3703         /* OSX uses cmd-a systemwide, so add it */
3704         if ((event->oskey && !IS_EVENT_MOD(event, shift, alt, ctrl)) ||
3705             (event->ctrl && !IS_EVENT_MOD(event, shift, alt, oskey)))
3706 #else
3707         if (event->ctrl && !IS_EVENT_MOD(event, shift, alt, oskey))
3708 #endif
3709         {
3710           ui_textedit_move(but, data, STRCUR_DIR_PREV, false, STRCUR_JUMP_ALL);
3711           ui_textedit_move(but, data, STRCUR_DIR_NEXT, true, STRCUR_JUMP_ALL);
3712           retval = WM_UI_HANDLER_BREAK;
3713         }
3714         break;
3715 
3716       case EVT_TABKEY:
3717         /* there is a key conflict here, we can't tab with autocomplete */
3718         if (but->autocomplete_func || data->searchbox) {
3719           const int autocomplete = ui_textedit_autocomplete(C, but, data);
3720           changed = autocomplete != AUTOCOMPLETE_NO_MATCH;
3721 
3722           if (autocomplete == AUTOCOMPLETE_FULL_MATCH) {
3723             button_activate_state(C, but, BUTTON_STATE_EXIT);
3724           }
3725         }
3726         /* the hotkey here is not well defined, was G.qual so we check all */
3727         else if (IS_EVENT_MOD(event, shift, ctrl, alt, oskey)) {
3728           ui_textedit_prev_but(block, but, data);
3729           button_activate_state(C, but, BUTTON_STATE_EXIT);
3730         }
3731         else {
3732           ui_textedit_next_but(block, but, data);
3733           button_activate_state(C, but, BUTTON_STATE_EXIT);
3734         }
3735         retval = WM_UI_HANDLER_BREAK;
3736         break;
3737       case EVT_ZKEY: {
3738         /* Ctrl-Z or Ctrl-Shift-Z: Undo/Redo (allowing for OS-Key on Apple). */
3739 
3740         const bool is_redo = (event->shift != 0);
3741         if (
3742 #if defined(__APPLE__)
3743             (event->oskey && !IS_EVENT_MOD(event, alt, ctrl)) ||
3744 #endif
3745             (event->ctrl && !IS_EVENT_MOD(event, alt, oskey))) {
3746           int undo_pos;
3747           const char *undo_str = ui_textedit_undo(
3748               data->undo_stack_text, is_redo ? 1 : -1, &undo_pos);
3749           if (undo_str != NULL) {
3750             ui_textedit_string_set(but, data, undo_str);
3751 
3752             /* Set the cursor & clear selection. */
3753             but->pos = undo_pos;
3754             but->selsta = but->pos;
3755             but->selend = but->pos;
3756             changed = true;
3757           }
3758           retval = WM_UI_HANDLER_BREAK;
3759           skip_undo_push = true;
3760         }
3761         break;
3762       }
3763     }
3764 
3765     if ((event->ascii || event->utf8_buf[0]) && (retval == WM_UI_HANDLER_CONTINUE)
3766 #ifdef WITH_INPUT_IME
3767         && !is_ime_composing && (!WM_event_is_ime_switch(event) || !BLT_lang_is_ime_supported())
3768 #endif
3769     ) {
3770       char ascii = event->ascii;
3771       const char *utf8_buf = event->utf8_buf;
3772 
3773       /* exception that's useful for number buttons, some keyboard
3774        * numpads have a comma instead of a period */
3775       if (ELEM(but->type, UI_BTYPE_NUM, UI_BTYPE_NUM_SLIDER)) { /* could use data->min*/
3776         if (event->type == EVT_PADPERIOD && ascii == ',') {
3777           ascii = '.';
3778           utf8_buf = NULL; /* force ascii fallback */
3779         }
3780       }
3781 
3782       if (utf8_buf && utf8_buf[0]) {
3783         const int utf8_buf_len = BLI_str_utf8_size(utf8_buf);
3784         BLI_assert(utf8_buf_len != -1);
3785         changed = ui_textedit_insert_buf(but, data, event->utf8_buf, utf8_buf_len);
3786       }
3787       else {
3788         changed = ui_textedit_insert_ascii(but, data, ascii);
3789       }
3790 
3791       retval = WM_UI_HANDLER_BREAK;
3792     }
3793     /* textbutton with this flag: do live update (e.g. for search buttons) */
3794     if (but->flag & UI_BUT_TEXTEDIT_UPDATE) {
3795       update = true;
3796     }
3797   }
3798 
3799 #ifdef WITH_INPUT_IME
3800   if (event->type == WM_IME_COMPOSITE_START || event->type == WM_IME_COMPOSITE_EVENT) {
3801     changed = true;
3802 
3803     if (event->type == WM_IME_COMPOSITE_START && but->selend > but->selsta) {
3804       ui_textedit_delete_selection(but, data);
3805     }
3806     if (event->type == WM_IME_COMPOSITE_EVENT && ime_data->result_len) {
3807       ui_textedit_insert_buf(but, data, ime_data->str_result, ime_data->result_len);
3808     }
3809   }
3810   else if (event->type == WM_IME_COMPOSITE_END) {
3811     changed = true;
3812   }
3813 #endif
3814 
3815   if (changed) {
3816     /* The undo stack may be NULL if an event exits editing. */
3817     if ((skip_undo_push == false) && (data->undo_stack_text != NULL)) {
3818       ui_textedit_undo_push(data->undo_stack_text, data->str, but->pos);
3819     }
3820 
3821     /* only do live update when but flag request it (UI_BUT_TEXTEDIT_UPDATE). */
3822     if (update && data->interactive) {
3823       ui_apply_but(C, block, but, data, true);
3824     }
3825     else {
3826       ui_but_update_edited(but);
3827     }
3828     but->changed = true;
3829 
3830     if (data->searchbox) {
3831       ui_searchbox_update(C, data->searchbox, but, true); /* true = reset */
3832     }
3833   }
3834 
3835   if (changed || (retval == WM_UI_HANDLER_BREAK)) {
3836     ED_region_tag_redraw(data->region);
3837   }
3838 }
3839 
ui_do_but_textedit_select(bContext * C,uiBlock * block,uiBut * but,uiHandleButtonData * data,const wmEvent * event)3840 static void ui_do_but_textedit_select(
3841     bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
3842 {
3843   int retval = WM_UI_HANDLER_CONTINUE;
3844 
3845   switch (event->type) {
3846     case MOUSEMOVE: {
3847       int mx = event->x;
3848       int my = event->y;
3849       ui_window_to_block(data->region, block, &mx, &my);
3850 
3851       ui_textedit_set_cursor_select(but, data, event->x);
3852       retval = WM_UI_HANDLER_BREAK;
3853       break;
3854     }
3855     case LEFTMOUSE:
3856       if (event->val == KM_RELEASE) {
3857         button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING);
3858       }
3859       retval = WM_UI_HANDLER_BREAK;
3860       break;
3861   }
3862 
3863   if (retval == WM_UI_HANDLER_BREAK) {
3864     ui_but_update(but);
3865     ED_region_tag_redraw(data->region);
3866   }
3867 }
3868 
3869 /** \} */
3870 
3871 /* -------------------------------------------------------------------- */
3872 /** \name Button Number Editing (various types)
3873  * \{ */
3874 
ui_numedit_begin(uiBut * but,uiHandleButtonData * data)3875 static void ui_numedit_begin(uiBut *but, uiHandleButtonData *data)
3876 {
3877   if (but->type == UI_BTYPE_CURVE) {
3878     uiButCurveMapping *but_cumap = (uiButCurveMapping *)but;
3879     but_cumap->edit_cumap = (CurveMapping *)but->poin;
3880   }
3881   else if (but->type == UI_BTYPE_CURVEPROFILE) {
3882     uiButCurveProfile *but_profile = (uiButCurveProfile *)but;
3883     but_profile->edit_profile = (CurveProfile *)but->poin;
3884   }
3885   else if (but->type == UI_BTYPE_COLORBAND) {
3886     uiButColorBand *but_coba = (uiButColorBand *)but;
3887     data->coba = (ColorBand *)but->poin;
3888     but_coba->edit_coba = data->coba;
3889   }
3890   else if (ELEM(but->type,
3891                 UI_BTYPE_UNITVEC,
3892                 UI_BTYPE_HSVCUBE,
3893                 UI_BTYPE_HSVCIRCLE,
3894                 UI_BTYPE_COLOR)) {
3895     ui_but_v3_get(but, data->origvec);
3896     copy_v3_v3(data->vec, data->origvec);
3897     but->editvec = data->vec;
3898   }
3899   else {
3900     float softrange, softmin, softmax;
3901 
3902     data->startvalue = ui_but_value_get(but);
3903     data->origvalue = data->startvalue;
3904     data->value = data->origvalue;
3905     but->editval = &data->value;
3906 
3907     softmin = but->softmin;
3908     softmax = but->softmax;
3909     softrange = softmax - softmin;
3910 
3911     if ((but->type == UI_BTYPE_NUM) && (ui_but_is_cursor_warp(but) == false)) {
3912       uiButNumber *number_but = (uiButNumber *)but;
3913       /* Use a minimum so we have a predictable range,
3914        * otherwise some float buttons get a large range. */
3915       const float value_step_float_min = 0.1f;
3916       const bool is_float = ui_but_is_float(but);
3917       const double value_step = is_float ?
3918                                     (double)(number_but->step_size * UI_PRECISION_FLOAT_SCALE) :
3919                                     (int)number_but->step_size;
3920       const float drag_map_softrange_max = UI_DRAG_MAP_SOFT_RANGE_PIXEL_MAX * UI_DPI_FAC;
3921       const float softrange_max = min_ff(
3922           softrange,
3923           2 * (is_float ? min_ff(value_step, value_step_float_min) *
3924                               (drag_map_softrange_max / value_step_float_min) :
3925                           drag_map_softrange_max));
3926 
3927       if (softrange > softrange_max) {
3928         /* Center around the value, keeping in the real soft min/max range. */
3929         softmin = data->origvalue - (softrange_max / 2);
3930         softmax = data->origvalue + (softrange_max / 2);
3931         if (!isfinite(softmin)) {
3932           softmin = (data->origvalue > 0.0f ? FLT_MAX : -FLT_MAX);
3933         }
3934         if (!isfinite(softmax)) {
3935           softmax = (data->origvalue > 0.0f ? FLT_MAX : -FLT_MAX);
3936         }
3937 
3938         if (softmin < but->softmin) {
3939           softmin = but->softmin;
3940           softmax = softmin + softrange_max;
3941         }
3942         else if (softmax > but->softmax) {
3943           softmax = but->softmax;
3944           softmin = softmax - softrange_max;
3945         }
3946 
3947         /* Can happen at extreme values. */
3948         if (UNLIKELY(softmin == softmax)) {
3949           if (data->origvalue > 0.0) {
3950             softmin = nextafterf(softmin, -FLT_MAX);
3951           }
3952           else {
3953             softmax = nextafterf(softmax, FLT_MAX);
3954           }
3955         }
3956 
3957         softrange = softmax - softmin;
3958       }
3959     }
3960 
3961     data->dragfstart = (softrange == 0.0f) ? 0.0f : ((float)data->value - softmin) / softrange;
3962     data->dragf = data->dragfstart;
3963 
3964     data->drag_map_soft_min = softmin;
3965     data->drag_map_soft_max = softmax;
3966   }
3967 
3968   data->dragchange = false;
3969   data->draglock = true;
3970 }
3971 
ui_numedit_end(uiBut * but,uiHandleButtonData * data)3972 static void ui_numedit_end(uiBut *but, uiHandleButtonData *data)
3973 {
3974   but->editval = NULL;
3975   but->editvec = NULL;
3976   if (but->type == UI_BTYPE_COLORBAND) {
3977     uiButColorBand *but_coba = (uiButColorBand *)but;
3978     but_coba->edit_coba = NULL;
3979   }
3980   else if (but->type == UI_BTYPE_CURVE) {
3981     uiButCurveMapping *but_cumap = (uiButCurveMapping *)but;
3982     but_cumap->edit_cumap = NULL;
3983   }
3984   else if (but->type == UI_BTYPE_CURVEPROFILE) {
3985     uiButCurveProfile *but_profile = (uiButCurveProfile *)but;
3986     but_profile->edit_profile = NULL;
3987   }
3988   data->dragstartx = 0;
3989   data->draglastx = 0;
3990   data->dragchange = false;
3991   data->dragcbd = NULL;
3992   data->dragsel = 0;
3993 }
3994 
ui_numedit_apply(bContext * C,uiBlock * block,uiBut * but,uiHandleButtonData * data)3995 static void ui_numedit_apply(bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data)
3996 {
3997   if (data->interactive) {
3998     ui_apply_but(C, block, but, data, true);
3999   }
4000   else {
4001     ui_but_update(but);
4002   }
4003 
4004   ED_region_tag_redraw(data->region);
4005 }
4006 
ui_but_extra_operator_icon_apply(bContext * C,uiBut * but,uiButExtraOpIcon * op_icon)4007 static void ui_but_extra_operator_icon_apply(bContext *C, uiBut *but, uiButExtraOpIcon *op_icon)
4008 {
4009   if (but->active->interactive) {
4010     ui_apply_but(C, but->block, but, but->active, true);
4011   }
4012   button_activate_state(C, but, BUTTON_STATE_EXIT);
4013   WM_operator_name_call_ptr(C,
4014                             op_icon->optype_params->optype,
4015                             op_icon->optype_params->opcontext,
4016                             op_icon->optype_params->opptr);
4017 
4018   /* Force recreation of extra operator icons (pseudo update). */
4019   ui_but_extra_operator_icons_free(but);
4020 
4021   WM_event_add_mousemove(CTX_wm_window(C));
4022 }
4023 
4024 /** \} */
4025 
4026 /* -------------------------------------------------------------------- */
4027 /** \name Menu/Popup Begin/End (various popup types)
4028  * \{ */
4029 
ui_block_open_begin(bContext * C,uiBut * but,uiHandleButtonData * data)4030 static void ui_block_open_begin(bContext *C, uiBut *but, uiHandleButtonData *data)
4031 {
4032   uiBlockCreateFunc func = NULL;
4033   uiBlockHandleCreateFunc handlefunc = NULL;
4034   uiMenuCreateFunc menufunc = NULL;
4035   uiMenuCreateFunc popoverfunc = NULL;
4036   void *arg = NULL;
4037 
4038   switch (but->type) {
4039     case UI_BTYPE_BLOCK:
4040     case UI_BTYPE_PULLDOWN:
4041       if (but->menu_create_func) {
4042         menufunc = but->menu_create_func;
4043         arg = but->poin;
4044       }
4045       else {
4046         func = but->block_create_func;
4047         arg = but->poin ? but->poin : but->func_argN;
4048       }
4049       break;
4050     case UI_BTYPE_MENU:
4051     case UI_BTYPE_POPOVER:
4052       BLI_assert(but->menu_create_func);
4053       if ((but->type == UI_BTYPE_POPOVER) || ui_but_menu_draw_as_popover(but)) {
4054         popoverfunc = but->menu_create_func;
4055       }
4056       else {
4057         menufunc = but->menu_create_func;
4058       }
4059       arg = but->poin;
4060       break;
4061     case UI_BTYPE_COLOR:
4062       ui_but_v3_get(but, data->origvec);
4063       copy_v3_v3(data->vec, data->origvec);
4064       but->editvec = data->vec;
4065 
4066       if (ui_but_menu_draw_as_popover(but)) {
4067         popoverfunc = but->menu_create_func;
4068       }
4069       else {
4070         handlefunc = ui_block_func_COLOR;
4071       }
4072       arg = but;
4073       break;
4074 
4075       /* quiet warnings for unhandled types */
4076     default:
4077       break;
4078   }
4079 
4080   if (func || handlefunc) {
4081     data->menu = ui_popup_block_create(C, data->region, but, func, handlefunc, arg, NULL);
4082     if (but->block->handle) {
4083       data->menu->popup = but->block->handle->popup;
4084     }
4085   }
4086   else if (menufunc) {
4087     data->menu = ui_popup_menu_create(C, data->region, but, menufunc, arg);
4088     if (but->block->handle) {
4089       data->menu->popup = but->block->handle->popup;
4090     }
4091   }
4092   else if (popoverfunc) {
4093     data->menu = ui_popover_panel_create(C, data->region, but, popoverfunc, arg);
4094     if (but->block->handle) {
4095       data->menu->popup = but->block->handle->popup;
4096     }
4097   }
4098 
4099 #ifdef USE_ALLSELECT
4100   {
4101     if (IS_ALLSELECT_EVENT(data->window->eventstate)) {
4102       data->select_others.is_enabled = true;
4103     }
4104   }
4105 #endif
4106 
4107   /* this makes adjacent blocks auto open from now on */
4108   // if (but->block->auto_open == 0) {
4109   //  but->block->auto_open = 1;
4110   //}
4111 }
4112 
ui_block_open_end(bContext * C,uiBut * but,uiHandleButtonData * data)4113 static void ui_block_open_end(bContext *C, uiBut *but, uiHandleButtonData *data)
4114 {
4115   if (but) {
4116     but->editval = NULL;
4117     but->editvec = NULL;
4118 
4119     but->block->auto_open_last = PIL_check_seconds_timer();
4120   }
4121 
4122   if (data->menu) {
4123     ui_popup_block_free(C, data->menu);
4124     data->menu = NULL;
4125   }
4126 }
4127 
ui_but_menu_direction(uiBut * but)4128 int ui_but_menu_direction(uiBut *but)
4129 {
4130   uiHandleButtonData *data = but->active;
4131 
4132   if (data && data->menu) {
4133     return data->menu->direction;
4134   }
4135 
4136   return 0;
4137 }
4138 
4139 /**
4140  * Hack for #uiList #UI_BTYPE_LISTROW buttons to "give" events to overlaying #UI_BTYPE_TEXT
4141  * buttons (Ctrl-Click rename feature & co).
4142  */
ui_but_list_row_text_activate(bContext * C,uiBut * but,uiHandleButtonData * data,const wmEvent * event,uiButtonActivateType activate_type)4143 static uiBut *ui_but_list_row_text_activate(bContext *C,
4144                                             uiBut *but,
4145                                             uiHandleButtonData *data,
4146                                             const wmEvent *event,
4147                                             uiButtonActivateType activate_type)
4148 {
4149   ARegion *region = CTX_wm_region(C);
4150   uiBut *labelbut = ui_but_find_mouse_over_ex(region, event->x, event->y, true);
4151 
4152   if (labelbut && labelbut->type == UI_BTYPE_TEXT && !(labelbut->flag & UI_BUT_DISABLED)) {
4153     /* exit listrow */
4154     data->cancel = true;
4155     button_activate_exit(C, but, data, false, false);
4156 
4157     /* Activate the text button. */
4158     button_activate_init(C, region, labelbut, activate_type);
4159 
4160     return labelbut;
4161   }
4162   return NULL;
4163 }
4164 
4165 /** \} */
4166 
4167 /* -------------------------------------------------------------------- */
4168 /** \name Events for Various Button Types
4169  * \{ */
4170 
ui_but_extra_operator_icon_mouse_over_get(uiBut * but,uiHandleButtonData * data,const wmEvent * event)4171 static uiButExtraOpIcon *ui_but_extra_operator_icon_mouse_over_get(uiBut *but,
4172                                                                    uiHandleButtonData *data,
4173                                                                    const wmEvent *event)
4174 {
4175   float xmax = but->rect.xmax;
4176   const float icon_size = 0.8f * BLI_rctf_size_y(&but->rect); /* ICON_SIZE_FROM_BUTRECT */
4177   int x = event->x, y = event->y;
4178 
4179   ui_window_to_block(data->region, but->block, &x, &y);
4180   if (!BLI_rctf_isect_pt(&but->rect, x, y)) {
4181     return NULL;
4182   }
4183 
4184   /* Inverse order, from right to left. */
4185   LISTBASE_FOREACH_BACKWARD (uiButExtraOpIcon *, op_icon, &but->extra_op_icons) {
4186     if ((x > (xmax - icon_size)) && x < xmax) {
4187       return op_icon;
4188     }
4189     xmax -= icon_size;
4190   }
4191 
4192   return NULL;
4193 }
4194 
ui_do_but_extra_operator_icon(bContext * C,uiBut * but,uiHandleButtonData * data,const wmEvent * event)4195 static bool ui_do_but_extra_operator_icon(bContext *C,
4196                                           uiBut *but,
4197                                           uiHandleButtonData *data,
4198                                           const wmEvent *event)
4199 {
4200   uiButExtraOpIcon *op_icon = ui_but_extra_operator_icon_mouse_over_get(but, data, event);
4201 
4202   if (!op_icon) {
4203     return false;
4204   }
4205 
4206   /* Only act on release, avoids some glitches. */
4207   if (event->val != KM_RELEASE) {
4208     /* Still swallow events on the icon. */
4209     return true;
4210   }
4211 
4212   ED_region_tag_redraw(data->region);
4213   button_tooltip_timer_reset(C, but);
4214 
4215   ui_but_extra_operator_icon_apply(C, but, op_icon);
4216   /* Note: 'but', 'data' may now be freed, don't access. */
4217 
4218   return true;
4219 }
4220 
ui_do_but_extra_operator_icons_mousemove(uiBut * but,uiHandleButtonData * data,const wmEvent * event)4221 static void ui_do_but_extra_operator_icons_mousemove(uiBut *but,
4222                                                      uiHandleButtonData *data,
4223                                                      const wmEvent *event)
4224 {
4225   uiButExtraOpIcon *old_highlighted = NULL;
4226 
4227   /* Unset highlighting of all first. */
4228   LISTBASE_FOREACH (uiButExtraOpIcon *, op_icon, &but->extra_op_icons) {
4229     if (op_icon->highlighted) {
4230       old_highlighted = op_icon;
4231     }
4232     op_icon->highlighted = false;
4233   }
4234 
4235   uiButExtraOpIcon *hovered = ui_but_extra_operator_icon_mouse_over_get(but, data, event);
4236 
4237   if (hovered) {
4238     hovered->highlighted = true;
4239   }
4240 
4241   if (old_highlighted != hovered) {
4242     ED_region_tag_redraw_no_rebuild(data->region);
4243   }
4244 }
4245 
4246 #ifdef USE_DRAG_TOGGLE
4247 /* Shared by any button that supports drag-toggle. */
ui_do_but_ANY_drag_toggle(bContext * C,uiBut * but,uiHandleButtonData * data,const wmEvent * event,int * r_retval)4248 static bool ui_do_but_ANY_drag_toggle(
4249     bContext *C, uiBut *but, uiHandleButtonData *data, const wmEvent *event, int *r_retval)
4250 {
4251   if (data->state == BUTTON_STATE_HIGHLIGHT) {
4252     if (event->type == LEFTMOUSE && event->val == KM_PRESS && ui_but_is_drag_toggle(but)) {
4253 #  if 0 /* UNUSED */
4254       data->togdual = event->ctrl;
4255       data->togonly = !event->shift;
4256 #  endif
4257       ui_apply_but(C, but->block, but, data, true);
4258       button_activate_state(C, but, BUTTON_STATE_WAIT_DRAG);
4259       data->dragstartx = event->x;
4260       data->dragstarty = event->y;
4261       *r_retval = WM_UI_HANDLER_BREAK;
4262       return true;
4263     }
4264   }
4265   else if (data->state == BUTTON_STATE_WAIT_DRAG) {
4266     /* note: the 'BUTTON_STATE_WAIT_DRAG' part of 'ui_do_but_EXIT' could be refactored into
4267      * its own function */
4268     data->applied = false;
4269     *r_retval = ui_do_but_EXIT(C, but, data, event);
4270     return true;
4271   }
4272   return false;
4273 }
4274 #endif /* USE_DRAG_TOGGLE */
4275 
ui_do_but_BUT(bContext * C,uiBut * but,uiHandleButtonData * data,const wmEvent * event)4276 static int ui_do_but_BUT(bContext *C, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
4277 {
4278 #ifdef USE_DRAG_TOGGLE
4279   {
4280     int retval;
4281     if (ui_do_but_ANY_drag_toggle(C, but, data, event, &retval)) {
4282       return retval;
4283     }
4284   }
4285 #endif
4286 
4287   if (data->state == BUTTON_STATE_HIGHLIGHT) {
4288     if (event->type == LEFTMOUSE && event->val == KM_PRESS) {
4289       button_activate_state(C, but, BUTTON_STATE_WAIT_RELEASE);
4290       return WM_UI_HANDLER_BREAK;
4291     }
4292     if (event->type == LEFTMOUSE && event->val == KM_RELEASE && but->block->handle) {
4293       /* regular buttons will be 'UI_SELECT', menu items 'UI_ACTIVE' */
4294       if (!(but->flag & (UI_SELECT | UI_ACTIVE))) {
4295         data->cancel = true;
4296       }
4297       button_activate_state(C, but, BUTTON_STATE_EXIT);
4298       return WM_UI_HANDLER_BREAK;
4299     }
4300     if (ELEM(event->type, EVT_PADENTER, EVT_RETKEY) && event->val == KM_PRESS) {
4301       button_activate_state(C, but, BUTTON_STATE_WAIT_FLASH);
4302       return WM_UI_HANDLER_BREAK;
4303     }
4304   }
4305   else if (data->state == BUTTON_STATE_WAIT_RELEASE) {
4306     if (event->type == LEFTMOUSE && event->val == KM_RELEASE) {
4307       if (!(but->flag & UI_SELECT)) {
4308         data->cancel = true;
4309       }
4310       button_activate_state(C, but, BUTTON_STATE_EXIT);
4311       return WM_UI_HANDLER_BREAK;
4312     }
4313   }
4314 
4315   return WM_UI_HANDLER_CONTINUE;
4316 }
4317 
ui_do_but_HOTKEYEVT(bContext * C,uiBut * but,uiHandleButtonData * data,const wmEvent * event)4318 static int ui_do_but_HOTKEYEVT(bContext *C,
4319                                uiBut *but,
4320                                uiHandleButtonData *data,
4321                                const wmEvent *event)
4322 {
4323   if (data->state == BUTTON_STATE_HIGHLIGHT) {
4324     if (ELEM(event->type, LEFTMOUSE, EVT_PADENTER, EVT_RETKEY) && event->val == KM_PRESS) {
4325       but->drawstr[0] = 0;
4326       but->modifier_key = 0;
4327       button_activate_state(C, but, BUTTON_STATE_WAIT_KEY_EVENT);
4328       return WM_UI_HANDLER_BREAK;
4329     }
4330   }
4331   else if (data->state == BUTTON_STATE_WAIT_KEY_EVENT) {
4332     if (ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE)) {
4333       return WM_UI_HANDLER_CONTINUE;
4334     }
4335     if (event->type == EVT_UNKNOWNKEY) {
4336       WM_report(RPT_WARNING, "Unsupported key: Unknown");
4337       return WM_UI_HANDLER_CONTINUE;
4338     }
4339     if (event->type == EVT_CAPSLOCKKEY) {
4340       WM_report(RPT_WARNING, "Unsupported key: CapsLock");
4341       return WM_UI_HANDLER_CONTINUE;
4342     }
4343 
4344     if (event->type == LEFTMOUSE && event->val == KM_PRESS) {
4345       /* only cancel if click outside the button */
4346       if (ui_but_contains_point_px(but, but->active->region, event->x, event->y) == false) {
4347         /* data->cancel doesn't work, this button opens immediate */
4348         if (but->flag & UI_BUT_IMMEDIATE) {
4349           ui_but_value_set(but, 0);
4350         }
4351         else {
4352           data->cancel = true;
4353         }
4354         button_activate_state(C, but, BUTTON_STATE_EXIT);
4355         return WM_UI_HANDLER_BREAK;
4356       }
4357     }
4358 
4359     /* always set */
4360     but->modifier_key = 0;
4361     if (event->shift) {
4362       but->modifier_key |= KM_SHIFT;
4363     }
4364     if (event->alt) {
4365       but->modifier_key |= KM_ALT;
4366     }
4367     if (event->ctrl) {
4368       but->modifier_key |= KM_CTRL;
4369     }
4370     if (event->oskey) {
4371       but->modifier_key |= KM_OSKEY;
4372     }
4373 
4374     ui_but_update(but);
4375     ED_region_tag_redraw(data->region);
4376 
4377     if (event->val == KM_PRESS) {
4378       if (ISHOTKEY(event->type) && (event->type != EVT_ESCKEY)) {
4379         if (WM_key_event_string(event->type, false)[0]) {
4380           ui_but_value_set(but, event->type);
4381         }
4382         else {
4383           data->cancel = true;
4384         }
4385 
4386         button_activate_state(C, but, BUTTON_STATE_EXIT);
4387         return WM_UI_HANDLER_BREAK;
4388       }
4389       if (event->type == EVT_ESCKEY) {
4390         if (event->val == KM_PRESS) {
4391           data->cancel = true;
4392           data->escapecancel = true;
4393           button_activate_state(C, but, BUTTON_STATE_EXIT);
4394         }
4395       }
4396     }
4397   }
4398 
4399   return WM_UI_HANDLER_CONTINUE;
4400 }
4401 
ui_do_but_KEYEVT(bContext * C,uiBut * but,uiHandleButtonData * data,const wmEvent * event)4402 static int ui_do_but_KEYEVT(bContext *C,
4403                             uiBut *but,
4404                             uiHandleButtonData *data,
4405                             const wmEvent *event)
4406 {
4407   if (data->state == BUTTON_STATE_HIGHLIGHT) {
4408     if (ELEM(event->type, LEFTMOUSE, EVT_PADENTER, EVT_RETKEY) && event->val == KM_PRESS) {
4409       button_activate_state(C, but, BUTTON_STATE_WAIT_KEY_EVENT);
4410       return WM_UI_HANDLER_BREAK;
4411     }
4412   }
4413   else if (data->state == BUTTON_STATE_WAIT_KEY_EVENT) {
4414     if (ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE)) {
4415       return WM_UI_HANDLER_CONTINUE;
4416     }
4417 
4418     if (event->val == KM_PRESS) {
4419       if (WM_key_event_string(event->type, false)[0]) {
4420         ui_but_value_set(but, event->type);
4421       }
4422       else {
4423         data->cancel = true;
4424       }
4425 
4426       button_activate_state(C, but, BUTTON_STATE_EXIT);
4427     }
4428   }
4429 
4430   return WM_UI_HANDLER_CONTINUE;
4431 }
4432 
ui_do_but_TAB(bContext * C,uiBlock * block,uiBut * but,uiHandleButtonData * data,const wmEvent * event)4433 static int ui_do_but_TAB(
4434     bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
4435 {
4436   const bool is_property = (but->rnaprop != NULL);
4437 
4438 #ifdef USE_DRAG_TOGGLE
4439   if (is_property) {
4440     int retval;
4441     if (ui_do_but_ANY_drag_toggle(C, but, data, event, &retval)) {
4442       return retval;
4443     }
4444   }
4445 #endif
4446 
4447   if (data->state == BUTTON_STATE_HIGHLIGHT) {
4448     const int rna_type = but->rnaprop ? RNA_property_type(but->rnaprop) : 0;
4449 
4450     if (is_property && ELEM(rna_type, PROP_POINTER, PROP_STRING) && (but->custom_data != NULL) &&
4451         (event->type == LEFTMOUSE) && ((event->val == KM_DBL_CLICK) || event->ctrl)) {
4452       button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING);
4453       return WM_UI_HANDLER_BREAK;
4454     }
4455     if (ELEM(event->type, LEFTMOUSE, EVT_PADENTER, EVT_RETKEY)) {
4456       const int event_val = (is_property) ? KM_PRESS : KM_CLICK;
4457       if (event->val == event_val) {
4458         button_activate_state(C, but, BUTTON_STATE_EXIT);
4459         return WM_UI_HANDLER_BREAK;
4460       }
4461     }
4462   }
4463   else if (data->state == BUTTON_STATE_TEXT_EDITING) {
4464     ui_do_but_textedit(C, block, but, data, event);
4465     return WM_UI_HANDLER_BREAK;
4466   }
4467   else if (data->state == BUTTON_STATE_TEXT_SELECTING) {
4468     ui_do_but_textedit_select(C, block, but, data, event);
4469     return WM_UI_HANDLER_BREAK;
4470   }
4471 
4472   return WM_UI_HANDLER_CONTINUE;
4473 }
4474 
ui_do_but_TEX(bContext * C,uiBlock * block,uiBut * but,uiHandleButtonData * data,const wmEvent * event)4475 static int ui_do_but_TEX(
4476     bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
4477 {
4478   if (data->state == BUTTON_STATE_HIGHLIGHT) {
4479     if (ELEM(event->type, LEFTMOUSE, EVT_BUT_OPEN, EVT_PADENTER, EVT_RETKEY) &&
4480         event->val == KM_PRESS) {
4481       if (ELEM(event->type, EVT_PADENTER, EVT_RETKEY) && (!UI_but_is_utf8(but))) {
4482         /* pass - allow filesel, enter to execute */
4483       }
4484       else if (but->emboss == UI_EMBOSS_NONE && !event->ctrl) {
4485         /* pass */
4486       }
4487       else {
4488         if (!ui_but_extra_operator_icon_mouse_over_get(but, data, event)) {
4489           button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING);
4490         }
4491         return WM_UI_HANDLER_BREAK;
4492       }
4493     }
4494   }
4495   else if (data->state == BUTTON_STATE_TEXT_EDITING) {
4496     ui_do_but_textedit(C, block, but, data, event);
4497     return WM_UI_HANDLER_BREAK;
4498   }
4499   else if (data->state == BUTTON_STATE_TEXT_SELECTING) {
4500     ui_do_but_textedit_select(C, block, but, data, event);
4501     return WM_UI_HANDLER_BREAK;
4502   }
4503 
4504   return WM_UI_HANDLER_CONTINUE;
4505 }
4506 
ui_do_but_SEARCH_UNLINK(bContext * C,uiBlock * block,uiBut * but,uiHandleButtonData * data,const wmEvent * event)4507 static int ui_do_but_SEARCH_UNLINK(
4508     bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
4509 {
4510   /* unlink icon is on right */
4511   if (ELEM(event->type, LEFTMOUSE, EVT_BUT_OPEN, EVT_PADENTER, EVT_RETKEY)) {
4512     /* doing this on KM_PRESS calls eyedropper after clicking unlink icon */
4513     if ((event->val == KM_RELEASE) && ui_do_but_extra_operator_icon(C, but, data, event)) {
4514       return WM_UI_HANDLER_BREAK;
4515     }
4516   }
4517   return ui_do_but_TEX(C, block, but, data, event);
4518 }
4519 
ui_do_but_TOG(bContext * C,uiBut * but,uiHandleButtonData * data,const wmEvent * event)4520 static int ui_do_but_TOG(bContext *C, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
4521 {
4522 #ifdef USE_DRAG_TOGGLE
4523   {
4524     int retval;
4525     if (ui_do_but_ANY_drag_toggle(C, but, data, event, &retval)) {
4526       return retval;
4527     }
4528   }
4529 #endif
4530 
4531   if (data->state == BUTTON_STATE_HIGHLIGHT) {
4532     bool do_activate = false;
4533     if (ELEM(event->type, EVT_PADENTER, EVT_RETKEY)) {
4534       if (event->val == KM_PRESS) {
4535         do_activate = true;
4536       }
4537     }
4538     else if (event->type == LEFTMOUSE) {
4539       if (ui_block_is_menu(but->block)) {
4540         /* Behave like other menu items. */
4541         do_activate = (event->val == KM_RELEASE);
4542       }
4543       else {
4544         /* Also use double-clicks to prevent fast clicks to leak to other handlers (T76481). */
4545         do_activate = ELEM(event->val, KM_PRESS, KM_DBL_CLICK);
4546       }
4547     }
4548 
4549     if (do_activate) {
4550 #if 0 /* UNUSED */
4551       data->togdual = event->ctrl;
4552       data->togonly = !event->shift;
4553 #endif
4554       button_activate_state(C, but, BUTTON_STATE_EXIT);
4555       return WM_UI_HANDLER_BREAK;
4556     }
4557     if (ELEM(event->type, WHEELDOWNMOUSE, WHEELUPMOUSE) && event->ctrl) {
4558       /* Support alt+wheel on expanded enum rows */
4559       if (but->type == UI_BTYPE_ROW) {
4560         const int direction = (event->type == WHEELDOWNMOUSE) ? -1 : 1;
4561         uiBut *but_select = ui_but_find_select_in_enum(but, direction);
4562         if (but_select) {
4563           uiBut *but_other = (direction == -1) ? but_select->next : but_select->prev;
4564           if (but_other && ui_but_find_select_in_enum__cmp(but, but_other)) {
4565             ARegion *region = data->region;
4566 
4567             data->cancel = true;
4568             button_activate_exit(C, but, data, false, false);
4569 
4570             /* Activate the text button. */
4571             button_activate_init(C, region, but_other, BUTTON_ACTIVATE_OVER);
4572             data = but_other->active;
4573             if (data) {
4574               ui_apply_but(C, but->block, but_other, but_other->active, true);
4575               button_activate_exit(C, but_other, data, false, false);
4576 
4577               /* restore active button */
4578               button_activate_init(C, region, but, BUTTON_ACTIVATE_OVER);
4579             }
4580             else {
4581               /* shouldn't happen */
4582               BLI_assert(0);
4583             }
4584           }
4585         }
4586         return WM_UI_HANDLER_BREAK;
4587       }
4588     }
4589   }
4590   return WM_UI_HANDLER_CONTINUE;
4591 }
4592 
ui_do_but_EXIT(bContext * C,uiBut * but,uiHandleButtonData * data,const wmEvent * event)4593 static int ui_do_but_EXIT(bContext *C, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
4594 {
4595   if (data->state == BUTTON_STATE_HIGHLIGHT) {
4596 
4597     /* first handle click on icondrag type button */
4598     if ((event->type == LEFTMOUSE) && (event->val == KM_PRESS) && but->dragpoin) {
4599       if (ui_but_contains_point_px_icon(but, data->region, event)) {
4600 
4601         /* tell the button to wait and keep checking further events to
4602          * see if it should start dragging */
4603         button_activate_state(C, but, BUTTON_STATE_WAIT_DRAG);
4604         data->dragstartx = event->x;
4605         data->dragstarty = event->y;
4606         return WM_UI_HANDLER_CONTINUE;
4607       }
4608     }
4609 #ifdef USE_DRAG_TOGGLE
4610     if ((event->type == LEFTMOUSE) && (event->val == KM_PRESS) && ui_but_is_drag_toggle(but)) {
4611       button_activate_state(C, but, BUTTON_STATE_WAIT_DRAG);
4612       data->dragstartx = event->x;
4613       data->dragstarty = event->y;
4614       return WM_UI_HANDLER_CONTINUE;
4615     }
4616 #endif
4617 
4618     if (ELEM(event->type, LEFTMOUSE, EVT_PADENTER, EVT_RETKEY) && event->val == KM_PRESS) {
4619       int ret = WM_UI_HANDLER_BREAK;
4620       /* XXX (a bit ugly) Special case handling for filebrowser drag button */
4621       if (but->dragpoin && but->imb && ui_but_contains_point_px_icon(but, data->region, event)) {
4622         ret = WM_UI_HANDLER_CONTINUE;
4623       }
4624       button_activate_state(C, but, BUTTON_STATE_EXIT);
4625       return ret;
4626     }
4627   }
4628   else if (data->state == BUTTON_STATE_WAIT_DRAG) {
4629 
4630     /* this function also ends state */
4631     if (ui_but_drag_init(C, but, data, event)) {
4632       return WM_UI_HANDLER_BREAK;
4633     }
4634 
4635     /* If the mouse has been pressed and released, getting to
4636      * this point without triggering a drag, then clear the
4637      * drag state for this button and continue to pass on the event */
4638     if (event->type == LEFTMOUSE && event->val == KM_RELEASE) {
4639       button_activate_state(C, but, BUTTON_STATE_EXIT);
4640       return WM_UI_HANDLER_CONTINUE;
4641     }
4642 
4643     /* while waiting for a drag to be triggered, always block
4644      * other events from getting handled */
4645     return WM_UI_HANDLER_BREAK;
4646   }
4647 
4648   return WM_UI_HANDLER_CONTINUE;
4649 }
4650 
4651 /* var names match ui_numedit_but_NUM */
ui_numedit_apply_snapf(uiBut * but,float tempf,float softmin,float softmax,const enum eSnapType snap)4652 static float ui_numedit_apply_snapf(
4653     uiBut *but, float tempf, float softmin, float softmax, const enum eSnapType snap)
4654 {
4655   if (tempf == softmin || tempf == softmax || snap == SNAP_OFF) {
4656     /* pass */
4657   }
4658   else {
4659     float softrange = softmax - softmin;
4660     float fac = 1.0f;
4661 
4662     if (ui_but_is_unit(but)) {
4663       UnitSettings *unit = but->block->unit;
4664       const int unit_type = RNA_SUBTYPE_UNIT_VALUE(UI_but_unit_type_get(but));
4665 
4666       if (BKE_unit_is_valid(unit->system, unit_type)) {
4667         fac = (float)BKE_unit_base_scalar(unit->system, unit_type);
4668         if (ELEM(unit_type, B_UNIT_LENGTH, B_UNIT_AREA, B_UNIT_VOLUME)) {
4669           fac /= unit->scale_length;
4670         }
4671       }
4672     }
4673 
4674     if (fac != 1.0f) {
4675       /* snap in unit-space */
4676       tempf /= fac;
4677       /* softmin /= fac; */ /* UNUSED */
4678       /* softmax /= fac; */ /* UNUSED */
4679       softrange /= fac;
4680     }
4681 
4682     /* workaround, too high snapping values */
4683     /* snapping by 10's for float buttons is quite annoying (location, scale...),
4684      * but allow for rotations */
4685     if (softrange >= 21.0f) {
4686       UnitSettings *unit = but->block->unit;
4687       const int unit_type = UI_but_unit_type_get(but);
4688       if ((unit_type == PROP_UNIT_ROTATION) && (unit->system_rotation != USER_UNIT_ROT_RADIANS)) {
4689         /* pass (degrees)*/
4690       }
4691       else {
4692         softrange = 20.0f;
4693       }
4694     }
4695 
4696     if (snap == SNAP_ON) {
4697       if (softrange < 2.10f) {
4698         tempf = roundf(tempf * 10.0f) * 0.1f;
4699       }
4700       else if (softrange < 21.0f) {
4701         tempf = roundf(tempf);
4702       }
4703       else {
4704         tempf = roundf(tempf * 0.1f) * 10.0f;
4705       }
4706     }
4707     else if (snap == SNAP_ON_SMALL) {
4708       if (softrange < 2.10f) {
4709         tempf = roundf(tempf * 100.0f) * 0.01f;
4710       }
4711       else if (softrange < 21.0f) {
4712         tempf = roundf(tempf * 10.0f) * 0.1f;
4713       }
4714       else {
4715         tempf = roundf(tempf);
4716       }
4717     }
4718     else {
4719       BLI_assert(0);
4720     }
4721 
4722     if (fac != 1.0f) {
4723       tempf *= fac;
4724     }
4725   }
4726 
4727   return tempf;
4728 }
4729 
ui_numedit_apply_snap(int temp,float softmin,float softmax,const enum eSnapType snap)4730 static float ui_numedit_apply_snap(int temp,
4731                                    float softmin,
4732                                    float softmax,
4733                                    const enum eSnapType snap)
4734 {
4735   if (temp == softmin || temp == softmax) {
4736     return temp;
4737   }
4738 
4739   switch (snap) {
4740     case SNAP_OFF:
4741       break;
4742     case SNAP_ON:
4743       temp = 10 * (temp / 10);
4744       break;
4745     case SNAP_ON_SMALL:
4746       temp = 100 * (temp / 100);
4747       break;
4748   }
4749 
4750   return temp;
4751 }
4752 
ui_numedit_but_NUM(uiButNumber * number_but,uiHandleButtonData * data,int mx,const bool is_motion,const enum eSnapType snap,float fac)4753 static bool ui_numedit_but_NUM(uiButNumber *number_but,
4754                                uiHandleButtonData *data,
4755                                int mx,
4756                                const bool is_motion,
4757                                const enum eSnapType snap,
4758                                float fac)
4759 {
4760   uiBut *but = &number_but->but;
4761   float deler, tempf;
4762   int lvalue, temp;
4763   bool changed = false;
4764   const bool is_float = ui_but_is_float(but);
4765 
4766   /* prevent unwanted drag adjustments, test motion so modifier keys refresh. */
4767   if ((is_motion || data->draglock) && (ui_but_dragedit_update_mval(data, mx) == false)) {
4768     return changed;
4769   }
4770 
4771   if (ui_but_is_cursor_warp(but)) {
4772     const float softmin = but->softmin;
4773     const float softmax = but->softmax;
4774     const float softrange = softmax - softmin;
4775 
4776     /* Mouse location isn't screen clamped to the screen so use a linear mapping
4777      * 2px == 1-int, or 1px == 1-ClickStep */
4778     if (is_float) {
4779       fac *= 0.01f * number_but->step_size;
4780       tempf = (float)data->startvalue + ((float)(mx - data->dragstartx) * fac);
4781       tempf = ui_numedit_apply_snapf(but, tempf, softmin, softmax, snap);
4782 
4783 #if 1 /* fake moving the click start, nicer for dragging back after passing the limit */
4784       if (tempf < softmin) {
4785         data->dragstartx -= (softmin - tempf) / fac;
4786         tempf = softmin;
4787       }
4788       else if (tempf > softmax) {
4789         data->dragstartx += (tempf - softmax) / fac;
4790         tempf = softmax;
4791       }
4792 #else
4793       CLAMP(tempf, softmin, softmax);
4794 #endif
4795 
4796       if (tempf != (float)data->value) {
4797         data->dragchange = true;
4798         data->value = tempf;
4799         changed = true;
4800       }
4801     }
4802     else {
4803       if (softrange > 256) {
4804         fac = 1.0;
4805       } /* 1px == 1 */
4806       else if (softrange > 32) {
4807         fac = 1.0 / 2.0;
4808       } /* 2px == 1 */
4809       else {
4810         fac = 1.0 / 16.0;
4811       } /* 16px == 1? */
4812 
4813       temp = data->startvalue + (((double)mx - data->dragstartx) * (double)fac);
4814       temp = ui_numedit_apply_snap(temp, softmin, softmax, snap);
4815 
4816 #if 1 /* fake moving the click start, nicer for dragging back after passing the limit */
4817       if (temp < softmin) {
4818         data->dragstartx -= (softmin - temp) / fac;
4819         temp = softmin;
4820       }
4821       else if (temp > softmax) {
4822         data->dragstartx += (temp - softmax) / fac;
4823         temp = softmax;
4824       }
4825 #else
4826       CLAMP(temp, softmin, softmax);
4827 #endif
4828 
4829       if (temp != data->value) {
4830         data->dragchange = true;
4831         data->value = temp;
4832         changed = true;
4833       }
4834     }
4835 
4836     data->draglastx = mx;
4837   }
4838   else {
4839     /* Use 'but->softmin', 'but->softmax' when clamping values. */
4840     const float softmin = data->drag_map_soft_min;
4841     const float softmax = data->drag_map_soft_max;
4842     const float softrange = softmax - softmin;
4843 
4844     float non_linear_range_limit;
4845     float non_linear_pixel_map;
4846     float non_linear_scale;
4847 
4848     /* Use a non-linear mapping of the mouse drag especially for large floats
4849      * (normal behavior) */
4850     deler = 500;
4851     if (is_float) {
4852       /* not needed for smaller float buttons */
4853       non_linear_range_limit = 11.0f;
4854       non_linear_pixel_map = 500.0f;
4855     }
4856     else {
4857       /* only scale large int buttons */
4858       non_linear_range_limit = 129.0f;
4859       /* larger for ints, we dont need to fine tune them */
4860       non_linear_pixel_map = 250.0f;
4861 
4862       /* prevent large ranges from getting too out of control */
4863       if (softrange > 600) {
4864         deler = powf(softrange, 0.75f);
4865       }
4866       else if (softrange < 25) {
4867         deler = 50.0;
4868       }
4869       else if (softrange < 100) {
4870         deler = 100.0;
4871       }
4872     }
4873     deler /= fac;
4874 
4875     if (softrange > non_linear_range_limit) {
4876       non_linear_scale = (float)abs(mx - data->dragstartx) / non_linear_pixel_map;
4877     }
4878     else {
4879       non_linear_scale = 1.0f;
4880     }
4881 
4882     if (is_float == false) {
4883       /* at minimum, moving cursor 2 pixels should change an int button. */
4884       CLAMP_MIN(non_linear_scale, 0.5f * UI_DPI_FAC);
4885     }
4886 
4887     data->dragf += (((float)(mx - data->draglastx)) / deler) * non_linear_scale;
4888 
4889     if (but->softmin == softmin) {
4890       CLAMP_MIN(data->dragf, 0.0f);
4891     }
4892     if (but->softmax == softmax) {
4893       CLAMP_MAX(data->dragf, 1.0f);
4894     }
4895 
4896     data->draglastx = mx;
4897     tempf = (softmin + data->dragf * softrange);
4898 
4899     if (!is_float) {
4900       temp = round_fl_to_int(tempf);
4901 
4902       temp = ui_numedit_apply_snap(temp, but->softmin, but->softmax, snap);
4903 
4904       CLAMP(temp, but->softmin, but->softmax);
4905       lvalue = (int)data->value;
4906 
4907       if (temp != lvalue) {
4908         data->dragchange = true;
4909         data->value = (double)temp;
4910         changed = true;
4911       }
4912     }
4913     else {
4914       temp = 0;
4915       tempf = ui_numedit_apply_snapf(but, tempf, but->softmin, but->softmax, snap);
4916 
4917       CLAMP(tempf, but->softmin, but->softmax);
4918 
4919       if (tempf != (float)data->value) {
4920         data->dragchange = true;
4921         data->value = tempf;
4922         changed = true;
4923       }
4924     }
4925   }
4926 
4927   return changed;
4928 }
4929 
ui_numedit_set_active(uiBut * but)4930 static void ui_numedit_set_active(uiBut *but)
4931 {
4932   const int oldflag = but->drawflag;
4933   but->drawflag &= ~(UI_BUT_ACTIVE_LEFT | UI_BUT_ACTIVE_RIGHT);
4934 
4935   uiHandleButtonData *data = but->active;
4936   if (!data) {
4937     return;
4938   }
4939 
4940   /* Ignore once we start dragging. */
4941   if (data->dragchange == false) {
4942     const float handle_width = min_ff(BLI_rctf_size_x(&but->rect) / 3,
4943                                       BLI_rctf_size_y(&but->rect) * 0.7f);
4944     /* we can click on the side arrows to increment/decrement,
4945      * or click inside to edit the value directly */
4946     int mx = data->window->eventstate->x;
4947     int my = data->window->eventstate->y;
4948     ui_window_to_block(data->region, but->block, &mx, &my);
4949 
4950     if (mx < (but->rect.xmin + handle_width)) {
4951       but->drawflag |= UI_BUT_ACTIVE_LEFT;
4952     }
4953     else if (mx > (but->rect.xmax - handle_width)) {
4954       but->drawflag |= UI_BUT_ACTIVE_RIGHT;
4955     }
4956   }
4957 
4958   /* Don't change the cursor once pressed. */
4959   if ((but->flag & UI_SELECT) == 0) {
4960     if ((but->drawflag & UI_BUT_ACTIVE_LEFT) || (but->drawflag & UI_BUT_ACTIVE_RIGHT)) {
4961       if (data->changed_cursor) {
4962         WM_cursor_modal_restore(data->window);
4963         data->changed_cursor = false;
4964       }
4965     }
4966     else {
4967       if (data->changed_cursor == false) {
4968         WM_cursor_modal_set(data->window, WM_CURSOR_X_MOVE);
4969         data->changed_cursor = true;
4970       }
4971     }
4972   }
4973 
4974   if (but->drawflag != oldflag) {
4975     ED_region_tag_redraw(data->region);
4976   }
4977 }
4978 
ui_do_but_NUM(bContext * C,uiBlock * block,uiBut * but,uiHandleButtonData * data,const wmEvent * event)4979 static int ui_do_but_NUM(
4980     bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
4981 {
4982   uiButNumber *number_but = (uiButNumber *)but;
4983   int click = 0;
4984   int retval = WM_UI_HANDLER_CONTINUE;
4985 
4986   /* mouse location scaled to fit the UI */
4987   int mx = event->x;
4988   int my = event->y;
4989   /* mouse location kept at screen pixel coords */
4990   const int screen_mx = event->x;
4991 
4992   BLI_assert(but->type == UI_BTYPE_NUM);
4993 
4994   ui_window_to_block(data->region, block, &mx, &my);
4995   ui_numedit_set_active(but);
4996 
4997   if (data->state == BUTTON_STATE_HIGHLIGHT) {
4998     int type = event->type, val = event->val;
4999 
5000     if (type == MOUSEPAN) {
5001       ui_pan_to_scroll(event, &type, &val);
5002     }
5003 
5004     /* XXX hardcoded keymap check.... */
5005     if (type == MOUSEPAN && event->ctrl) {
5006       /* allow accumulating values, otherwise scrolling gets preference */
5007       retval = WM_UI_HANDLER_BREAK;
5008     }
5009     else if (type == WHEELDOWNMOUSE && event->ctrl) {
5010       mx = but->rect.xmin;
5011       but->drawflag &= ~UI_BUT_ACTIVE_RIGHT;
5012       but->drawflag |= UI_BUT_ACTIVE_LEFT;
5013       click = 1;
5014     }
5015     else if (type == WHEELUPMOUSE && event->ctrl) {
5016       mx = but->rect.xmax;
5017       but->drawflag &= ~UI_BUT_ACTIVE_LEFT;
5018       but->drawflag |= UI_BUT_ACTIVE_RIGHT;
5019       click = 1;
5020     }
5021     else if (event->val == KM_PRESS) {
5022       if (ELEM(event->type, LEFTMOUSE, EVT_PADENTER, EVT_RETKEY) && event->ctrl) {
5023         button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING);
5024         retval = WM_UI_HANDLER_BREAK;
5025       }
5026       else if (event->type == LEFTMOUSE) {
5027         data->dragstartx = data->draglastx = ui_but_is_cursor_warp(but) ? screen_mx : mx;
5028         button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
5029         retval = WM_UI_HANDLER_BREAK;
5030       }
5031       else if (ELEM(event->type, EVT_PADENTER, EVT_RETKEY) && event->val == KM_PRESS) {
5032         click = 1;
5033       }
5034       else if (event->type == EVT_MINUSKEY && event->val == KM_PRESS) {
5035         button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
5036         data->value = -data->value;
5037         button_activate_state(C, but, BUTTON_STATE_EXIT);
5038         retval = WM_UI_HANDLER_BREAK;
5039       }
5040 
5041 #ifdef USE_DRAG_MULTINUM
5042       copy_v2_v2_int(data->multi_data.drag_start, &event->x);
5043 #endif
5044     }
5045   }
5046   else if (data->state == BUTTON_STATE_NUM_EDITING) {
5047     if (event->type == EVT_ESCKEY || event->type == RIGHTMOUSE) {
5048       if (event->val == KM_PRESS) {
5049         data->cancel = true;
5050         data->escapecancel = true;
5051         button_activate_state(C, but, BUTTON_STATE_EXIT);
5052       }
5053     }
5054     else if (event->type == LEFTMOUSE && event->val == KM_RELEASE) {
5055       if (data->dragchange) {
5056 #ifdef USE_DRAG_MULTINUM
5057         /* if we started multibutton but didn't drag, then edit */
5058         if (data->multi_data.init == BUTTON_MULTI_INIT_SETUP) {
5059           click = 1;
5060         }
5061         else
5062 #endif
5063         {
5064           button_activate_state(C, but, BUTTON_STATE_EXIT);
5065         }
5066       }
5067       else {
5068         click = 1;
5069       }
5070     }
5071     else if ((event->type == MOUSEMOVE) || ui_event_is_snap(event)) {
5072       const bool is_motion = (event->type == MOUSEMOVE);
5073       const enum eSnapType snap = ui_event_to_snap(event);
5074       float fac;
5075 
5076 #ifdef USE_DRAG_MULTINUM
5077       data->multi_data.drag_dir[0] += abs(data->draglastx - mx);
5078       data->multi_data.drag_dir[1] += abs(data->draglasty - my);
5079 #endif
5080 
5081       fac = 1.0f;
5082       if (event->shift) {
5083         fac /= 10.0f;
5084       }
5085 
5086       if (ui_numedit_but_NUM(number_but,
5087                              data,
5088                              (ui_but_is_cursor_warp(but) ? screen_mx : mx),
5089                              is_motion,
5090                              snap,
5091                              fac)) {
5092         ui_numedit_apply(C, block, but, data);
5093       }
5094 #ifdef USE_DRAG_MULTINUM
5095       else if (data->multi_data.has_mbuts) {
5096         if (data->multi_data.init == BUTTON_MULTI_INIT_ENABLE) {
5097           ui_multibut_states_apply(C, data, block);
5098         }
5099       }
5100 #endif
5101     }
5102     retval = WM_UI_HANDLER_BREAK;
5103   }
5104   else if (data->state == BUTTON_STATE_TEXT_EDITING) {
5105     ui_do_but_textedit(C, block, but, data, event);
5106     retval = WM_UI_HANDLER_BREAK;
5107   }
5108   else if (data->state == BUTTON_STATE_TEXT_SELECTING) {
5109     ui_do_but_textedit_select(C, block, but, data, event);
5110     retval = WM_UI_HANDLER_BREAK;
5111   }
5112 
5113   if (click) {
5114     /* we can click on the side arrows to increment/decrement,
5115      * or click inside to edit the value directly */
5116 
5117     if (!ui_but_is_float(but)) {
5118       /* Integer Value. */
5119       if (but->drawflag & (UI_BUT_ACTIVE_LEFT | UI_BUT_ACTIVE_RIGHT)) {
5120         button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
5121 
5122         const int value_step = (int)number_but->step_size;
5123         BLI_assert(value_step > 0);
5124         const int softmin = round_fl_to_int_clamp(but->softmin);
5125         const int softmax = round_fl_to_int_clamp(but->softmax);
5126         const double value_test = (but->drawflag & UI_BUT_ACTIVE_LEFT) ?
5127                                       (double)max_ii(softmin, (int)data->value - value_step) :
5128                                       (double)min_ii(softmax, (int)data->value + value_step);
5129         if (value_test != data->value) {
5130           data->value = (double)value_test;
5131         }
5132         else {
5133           data->cancel = true;
5134         }
5135         button_activate_state(C, but, BUTTON_STATE_EXIT);
5136       }
5137       else {
5138         button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING);
5139       }
5140     }
5141     else {
5142       /* Float Value. */
5143       if (but->drawflag & (UI_BUT_ACTIVE_LEFT | UI_BUT_ACTIVE_RIGHT)) {
5144         button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
5145 
5146         const double value_step = (double)number_but->step_size * UI_PRECISION_FLOAT_SCALE;
5147         BLI_assert(value_step > 0.0f);
5148         const double value_test = (but->drawflag & UI_BUT_ACTIVE_LEFT) ?
5149                                       (double)max_ff(but->softmin,
5150                                                      (float)(data->value - value_step)) :
5151                                       (double)min_ff(but->softmax,
5152                                                      (float)(data->value + value_step));
5153         if (value_test != data->value) {
5154           data->value = value_test;
5155         }
5156         else {
5157           data->cancel = true;
5158         }
5159         button_activate_state(C, but, BUTTON_STATE_EXIT);
5160       }
5161       else {
5162         button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING);
5163       }
5164     }
5165 
5166     retval = WM_UI_HANDLER_BREAK;
5167   }
5168 
5169   data->draglastx = mx;
5170   data->draglasty = my;
5171 
5172   return retval;
5173 }
5174 
ui_numedit_but_SLI(uiBut * but,uiHandleButtonData * data,int mx,const bool is_horizontal,const bool is_motion,const bool snap,const bool shift)5175 static bool ui_numedit_but_SLI(uiBut *but,
5176                                uiHandleButtonData *data,
5177                                int mx,
5178                                const bool is_horizontal,
5179                                const bool is_motion,
5180                                const bool snap,
5181                                const bool shift)
5182 {
5183   float cursor_x_range, f, tempf, softmin, softmax, softrange;
5184   int temp, lvalue;
5185   bool changed = false;
5186   float mx_fl, my_fl;
5187 
5188   /* prevent unwanted drag adjustments, test motion so modifier keys refresh. */
5189   if ((but->type != UI_BTYPE_SCROLL) && (is_motion || data->draglock) &&
5190       (ui_but_dragedit_update_mval(data, mx) == false)) {
5191     return changed;
5192   }
5193 
5194   softmin = but->softmin;
5195   softmax = but->softmax;
5196   softrange = softmax - softmin;
5197 
5198   /* yes, 'mx' as both x/y is intentional */
5199   ui_mouse_scale_warp(data, mx, mx, &mx_fl, &my_fl, shift);
5200 
5201   if (but->type == UI_BTYPE_NUM_SLIDER) {
5202     cursor_x_range = BLI_rctf_size_x(&but->rect);
5203   }
5204   else if (but->type == UI_BTYPE_SCROLL) {
5205     const float size = (is_horizontal) ? BLI_rctf_size_x(&but->rect) :
5206                                          -BLI_rctf_size_y(&but->rect);
5207     cursor_x_range = size * (but->softmax - but->softmin) /
5208                      (but->softmax - but->softmin + but->a1);
5209   }
5210   else {
5211     const float offs = (BLI_rctf_size_y(&but->rect) / 2.0f);
5212     cursor_x_range = (BLI_rctf_size_x(&but->rect) - offs);
5213   }
5214 
5215   f = (mx_fl - data->dragstartx) / cursor_x_range + data->dragfstart;
5216   CLAMP(f, 0.0f, 1.0f);
5217 
5218   /* deal with mouse correction */
5219 #ifdef USE_CONT_MOUSE_CORRECT
5220   if (ui_but_is_cursor_warp(but)) {
5221     /* OK but can go outside bounds */
5222     if (is_horizontal) {
5223       data->ungrab_mval[0] = but->rect.xmin + (f * cursor_x_range);
5224       data->ungrab_mval[1] = BLI_rctf_cent_y(&but->rect);
5225     }
5226     else {
5227       data->ungrab_mval[1] = but->rect.ymin + (f * cursor_x_range);
5228       data->ungrab_mval[0] = BLI_rctf_cent_x(&but->rect);
5229     }
5230     BLI_rctf_clamp_pt_v(&but->rect, data->ungrab_mval);
5231   }
5232 #endif
5233   /* done correcting mouse */
5234 
5235   tempf = softmin + f * softrange;
5236   temp = round_fl_to_int(tempf);
5237 
5238   if (snap) {
5239     if (tempf == softmin || tempf == softmax) {
5240       /* pass */
5241     }
5242     else if (ui_but_is_float(but)) {
5243 
5244       if (shift) {
5245         if (tempf == softmin || tempf == softmax) {
5246         }
5247         else if (softrange < 2.10f) {
5248           tempf = roundf(tempf * 100.0f) * 0.01f;
5249         }
5250         else if (softrange < 21.0f) {
5251           tempf = roundf(tempf * 10.0f) * 0.1f;
5252         }
5253         else {
5254           tempf = roundf(tempf);
5255         }
5256       }
5257       else {
5258         if (softrange < 2.10f) {
5259           tempf = roundf(tempf * 10.0f) * 0.1f;
5260         }
5261         else if (softrange < 21.0f) {
5262           tempf = roundf(tempf);
5263         }
5264         else {
5265           tempf = roundf(tempf * 0.1f) * 10.0f;
5266         }
5267       }
5268     }
5269     else {
5270       temp = 10 * (temp / 10);
5271       tempf = temp;
5272     }
5273   }
5274 
5275   if (!ui_but_is_float(but)) {
5276     lvalue = round(data->value);
5277 
5278     CLAMP(temp, softmin, softmax);
5279 
5280     if (temp != lvalue) {
5281       data->value = temp;
5282       data->dragchange = true;
5283       changed = true;
5284     }
5285   }
5286   else {
5287     CLAMP(tempf, softmin, softmax);
5288 
5289     if (tempf != (float)data->value) {
5290       data->value = tempf;
5291       data->dragchange = true;
5292       changed = true;
5293     }
5294   }
5295 
5296   return changed;
5297 }
5298 
ui_do_but_SLI(bContext * C,uiBlock * block,uiBut * but,uiHandleButtonData * data,const wmEvent * event)5299 static int ui_do_but_SLI(
5300     bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
5301 {
5302   int click = 0;
5303   int retval = WM_UI_HANDLER_CONTINUE;
5304 
5305   int mx = event->x;
5306   int my = event->y;
5307   ui_window_to_block(data->region, block, &mx, &my);
5308 
5309   if (data->state == BUTTON_STATE_HIGHLIGHT) {
5310     int type = event->type, val = event->val;
5311 
5312     if (type == MOUSEPAN) {
5313       ui_pan_to_scroll(event, &type, &val);
5314     }
5315 
5316     /* XXX hardcoded keymap check.... */
5317     if (type == MOUSEPAN && event->ctrl) {
5318       /* allow accumulating values, otherwise scrolling gets preference */
5319       retval = WM_UI_HANDLER_BREAK;
5320     }
5321     else if (type == WHEELDOWNMOUSE && event->ctrl) {
5322       mx = but->rect.xmin;
5323       click = 2;
5324     }
5325     else if (type == WHEELUPMOUSE && event->ctrl) {
5326       mx = but->rect.xmax;
5327       click = 2;
5328     }
5329     else if (event->val == KM_PRESS) {
5330       if (ELEM(event->type, LEFTMOUSE, EVT_PADENTER, EVT_RETKEY) && event->ctrl) {
5331         button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING);
5332         retval = WM_UI_HANDLER_BREAK;
5333       }
5334 #ifndef USE_ALLSELECT
5335       /* alt-click on sides to get "arrows" like in UI_BTYPE_NUM buttons,
5336        * and match wheel usage above */
5337       else if (event->type == LEFTMOUSE && event->alt) {
5338         int halfpos = BLI_rctf_cent_x(&but->rect);
5339         click = 2;
5340         if (mx < halfpos) {
5341           mx = but->rect.xmin;
5342         }
5343         else {
5344           mx = but->rect.xmax;
5345         }
5346       }
5347 #endif
5348       else if (event->type == LEFTMOUSE) {
5349         data->dragstartx = mx;
5350         data->draglastx = mx;
5351         button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
5352         retval = WM_UI_HANDLER_BREAK;
5353       }
5354       else if (ELEM(event->type, EVT_PADENTER, EVT_RETKEY) && event->val == KM_PRESS) {
5355         click = 1;
5356       }
5357       else if (event->type == EVT_MINUSKEY && event->val == KM_PRESS) {
5358         button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
5359         data->value = -data->value;
5360         button_activate_state(C, but, BUTTON_STATE_EXIT);
5361         retval = WM_UI_HANDLER_BREAK;
5362       }
5363     }
5364 #ifdef USE_DRAG_MULTINUM
5365     copy_v2_v2_int(data->multi_data.drag_start, &event->x);
5366 #endif
5367   }
5368   else if (data->state == BUTTON_STATE_NUM_EDITING) {
5369     if (event->type == EVT_ESCKEY || event->type == RIGHTMOUSE) {
5370       if (event->val == KM_PRESS) {
5371         data->cancel = true;
5372         data->escapecancel = true;
5373         button_activate_state(C, but, BUTTON_STATE_EXIT);
5374       }
5375     }
5376     else if (event->type == LEFTMOUSE && event->val == KM_RELEASE) {
5377       if (data->dragchange) {
5378 #ifdef USE_DRAG_MULTINUM
5379         /* if we started multibutton but didn't drag, then edit */
5380         if (data->multi_data.init == BUTTON_MULTI_INIT_SETUP) {
5381           click = 1;
5382         }
5383         else
5384 #endif
5385         {
5386           button_activate_state(C, but, BUTTON_STATE_EXIT);
5387         }
5388       }
5389       else {
5390 #ifdef USE_CONT_MOUSE_CORRECT
5391         /* reset! */
5392         copy_v2_fl(data->ungrab_mval, FLT_MAX);
5393 #endif
5394         click = 1;
5395       }
5396     }
5397     else if ((event->type == MOUSEMOVE) || ui_event_is_snap(event)) {
5398       const bool is_motion = (event->type == MOUSEMOVE);
5399 #ifdef USE_DRAG_MULTINUM
5400       data->multi_data.drag_dir[0] += abs(data->draglastx - mx);
5401       data->multi_data.drag_dir[1] += abs(data->draglasty - my);
5402 #endif
5403       if (ui_numedit_but_SLI(
5404               but, data, mx, true, is_motion, event->ctrl != 0, event->shift != 0)) {
5405         ui_numedit_apply(C, block, but, data);
5406       }
5407 
5408 #ifdef USE_DRAG_MULTINUM
5409       else if (data->multi_data.has_mbuts) {
5410         if (data->multi_data.init == BUTTON_MULTI_INIT_ENABLE) {
5411           ui_multibut_states_apply(C, data, block);
5412         }
5413       }
5414 #endif
5415     }
5416     retval = WM_UI_HANDLER_BREAK;
5417   }
5418   else if (data->state == BUTTON_STATE_TEXT_EDITING) {
5419     ui_do_but_textedit(C, block, but, data, event);
5420     retval = WM_UI_HANDLER_BREAK;
5421   }
5422   else if (data->state == BUTTON_STATE_TEXT_SELECTING) {
5423     ui_do_but_textedit_select(C, block, but, data, event);
5424     retval = WM_UI_HANDLER_BREAK;
5425   }
5426 
5427   if (click) {
5428     if (click == 2) {
5429       /* nudge slider to the left or right */
5430       float f, tempf, softmin, softmax, softrange;
5431       int temp;
5432 
5433       button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
5434 
5435       softmin = but->softmin;
5436       softmax = but->softmax;
5437       softrange = softmax - softmin;
5438 
5439       tempf = data->value;
5440       temp = (int)data->value;
5441 
5442 #if 0
5443       if (but->type == SLI) {
5444         /* same as below */
5445         f = (float)(mx - but->rect.xmin) / (BLI_rctf_size_x(&but->rect));
5446       }
5447       else
5448 #endif
5449       {
5450         f = (float)(mx - but->rect.xmin) / (BLI_rctf_size_x(&but->rect));
5451       }
5452 
5453       f = softmin + f * softrange;
5454 
5455       if (!ui_but_is_float(but)) {
5456         if (f < temp) {
5457           temp--;
5458         }
5459         else {
5460           temp++;
5461         }
5462 
5463         if (temp >= softmin && temp <= softmax) {
5464           data->value = temp;
5465         }
5466         else {
5467           data->cancel = true;
5468         }
5469       }
5470       else {
5471         if (f < tempf) {
5472           tempf -= 0.01f;
5473         }
5474         else {
5475           tempf += 0.01f;
5476         }
5477 
5478         if (tempf >= softmin && tempf <= softmax) {
5479           data->value = tempf;
5480         }
5481         else {
5482           data->cancel = true;
5483         }
5484       }
5485 
5486       button_activate_state(C, but, BUTTON_STATE_EXIT);
5487       retval = WM_UI_HANDLER_BREAK;
5488     }
5489     else {
5490       /* edit the value directly */
5491       button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING);
5492       retval = WM_UI_HANDLER_BREAK;
5493     }
5494   }
5495 
5496   data->draglastx = mx;
5497   data->draglasty = my;
5498 
5499   return retval;
5500 }
5501 
ui_do_but_SCROLL(bContext * C,uiBlock * block,uiBut * but,uiHandleButtonData * data,const wmEvent * event)5502 static int ui_do_but_SCROLL(
5503     bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
5504 {
5505   int retval = WM_UI_HANDLER_CONTINUE;
5506   const bool horizontal = (BLI_rctf_size_x(&but->rect) > BLI_rctf_size_y(&but->rect));
5507 
5508   int mx = event->x;
5509   int my = event->y;
5510   ui_window_to_block(data->region, block, &mx, &my);
5511 
5512   if (data->state == BUTTON_STATE_HIGHLIGHT) {
5513     if (event->val == KM_PRESS) {
5514       if (event->type == LEFTMOUSE) {
5515         if (horizontal) {
5516           data->dragstartx = mx;
5517           data->draglastx = mx;
5518         }
5519         else {
5520           data->dragstartx = my;
5521           data->draglastx = my;
5522         }
5523         button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
5524         retval = WM_UI_HANDLER_BREAK;
5525       }
5526     }
5527   }
5528   else if (data->state == BUTTON_STATE_NUM_EDITING) {
5529     if (event->type == EVT_ESCKEY) {
5530       if (event->val == KM_PRESS) {
5531         data->cancel = true;
5532         data->escapecancel = true;
5533         button_activate_state(C, but, BUTTON_STATE_EXIT);
5534       }
5535     }
5536     else if (event->type == LEFTMOUSE && event->val == KM_RELEASE) {
5537       button_activate_state(C, but, BUTTON_STATE_EXIT);
5538     }
5539     else if (event->type == MOUSEMOVE) {
5540       const bool is_motion = (event->type == MOUSEMOVE);
5541       if (ui_numedit_but_SLI(
5542               but, data, (horizontal) ? mx : my, horizontal, is_motion, false, false)) {
5543         ui_numedit_apply(C, block, but, data);
5544       }
5545     }
5546 
5547     retval = WM_UI_HANDLER_BREAK;
5548   }
5549 
5550   return retval;
5551 }
5552 
ui_do_but_GRIP(bContext * C,uiBlock * block,uiBut * but,uiHandleButtonData * data,const wmEvent * event)5553 static int ui_do_but_GRIP(
5554     bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
5555 {
5556   int retval = WM_UI_HANDLER_CONTINUE;
5557   const bool horizontal = (BLI_rctf_size_x(&but->rect) < BLI_rctf_size_y(&but->rect));
5558 
5559   /* Note: Having to store org point in window space and recompute it to block "space" each time
5560    *       is not ideal, but this is a way to hack around behavior of ui_window_to_block(), which
5561    *       returns different results when the block is inside a panel or not...
5562    *       See T37739.
5563    */
5564 
5565   int mx = event->x;
5566   int my = event->y;
5567   ui_window_to_block(data->region, block, &mx, &my);
5568 
5569   if (data->state == BUTTON_STATE_HIGHLIGHT) {
5570     if (event->val == KM_PRESS) {
5571       if (event->type == LEFTMOUSE) {
5572         data->dragstartx = event->x;
5573         data->dragstarty = event->y;
5574         button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
5575         retval = WM_UI_HANDLER_BREAK;
5576       }
5577     }
5578   }
5579   else if (data->state == BUTTON_STATE_NUM_EDITING) {
5580     if (event->type == EVT_ESCKEY) {
5581       if (event->val == KM_PRESS) {
5582         data->cancel = true;
5583         data->escapecancel = true;
5584         button_activate_state(C, but, BUTTON_STATE_EXIT);
5585       }
5586     }
5587     else if (event->type == LEFTMOUSE && event->val == KM_RELEASE) {
5588       button_activate_state(C, but, BUTTON_STATE_EXIT);
5589     }
5590     else if (event->type == MOUSEMOVE) {
5591       int dragstartx = data->dragstartx;
5592       int dragstarty = data->dragstarty;
5593       ui_window_to_block(data->region, block, &dragstartx, &dragstarty);
5594       data->value = data->origvalue + (horizontal ? mx - dragstartx : dragstarty - my);
5595       ui_numedit_apply(C, block, but, data);
5596     }
5597 
5598     retval = WM_UI_HANDLER_BREAK;
5599   }
5600 
5601   return retval;
5602 }
5603 
ui_do_but_LISTROW(bContext * C,uiBut * but,uiHandleButtonData * data,const wmEvent * event)5604 static int ui_do_but_LISTROW(bContext *C,
5605                              uiBut *but,
5606                              uiHandleButtonData *data,
5607                              const wmEvent *event)
5608 {
5609   if (data->state == BUTTON_STATE_HIGHLIGHT) {
5610     /* hack to pass on ctrl+click and double click to overlapping text
5611      * editing field for editing list item names
5612      */
5613     if ((ELEM(event->type, LEFTMOUSE, EVT_PADENTER, EVT_RETKEY) && event->val == KM_PRESS &&
5614          event->ctrl) ||
5615         (event->type == LEFTMOUSE && event->val == KM_DBL_CLICK)) {
5616       uiBut *labelbut = ui_but_list_row_text_activate(
5617           C, but, data, event, BUTTON_ACTIVATE_TEXT_EDITING);
5618       if (labelbut) {
5619         /* Nothing else to do. */
5620         return WM_UI_HANDLER_BREAK;
5621       }
5622     }
5623   }
5624 
5625   return ui_do_but_EXIT(C, but, data, event);
5626 }
5627 
ui_do_but_BLOCK(bContext * C,uiBut * but,uiHandleButtonData * data,const wmEvent * event)5628 static int ui_do_but_BLOCK(bContext *C, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
5629 {
5630   if (data->state == BUTTON_STATE_HIGHLIGHT) {
5631 
5632     /* first handle click on icondrag type button */
5633     if (event->type == LEFTMOUSE && but->dragpoin && event->val == KM_PRESS) {
5634       if (ui_but_contains_point_px_icon(but, data->region, event)) {
5635         button_activate_state(C, but, BUTTON_STATE_WAIT_DRAG);
5636         data->dragstartx = event->x;
5637         data->dragstarty = event->y;
5638         return WM_UI_HANDLER_BREAK;
5639       }
5640     }
5641 #ifdef USE_DRAG_TOGGLE
5642     if (event->type == LEFTMOUSE && event->val == KM_PRESS && (ui_but_is_drag_toggle(but))) {
5643       button_activate_state(C, but, BUTTON_STATE_WAIT_DRAG);
5644       data->dragstartx = event->x;
5645       data->dragstarty = event->y;
5646       return WM_UI_HANDLER_BREAK;
5647     }
5648 #endif
5649     /* regular open menu */
5650     if (ELEM(event->type, LEFTMOUSE, EVT_PADENTER, EVT_RETKEY) && event->val == KM_PRESS) {
5651       button_activate_state(C, but, BUTTON_STATE_MENU_OPEN);
5652       return WM_UI_HANDLER_BREAK;
5653     }
5654     if (ui_but_supports_cycling(but)) {
5655       if (ELEM(event->type, WHEELDOWNMOUSE, WHEELUPMOUSE) && event->ctrl) {
5656         const int direction = (event->type == WHEELDOWNMOUSE) ? 1 : -1;
5657 
5658         data->value = ui_but_menu_step(but, direction);
5659 
5660         button_activate_state(C, but, BUTTON_STATE_EXIT);
5661         ui_apply_but(C, but->block, but, data, true);
5662 
5663         /* Button's state need to be changed to EXIT so moving mouse away from this mouse
5664          * wouldn't lead to cancel changes made to this button, but changing state to EXIT also
5665          * makes no button active for a while which leads to triggering operator when doing fast
5666          * scrolling mouse wheel. using post activate stuff from button allows to make button be
5667          * active again after checking for all all that mouse leave and cancel stuff, so quick
5668          * scroll wouldn't be an issue anymore. Same goes for scrolling wheel in another
5669          * direction below (sergey).
5670          */
5671         data->postbut = but;
5672         data->posttype = BUTTON_ACTIVATE_OVER;
5673 
5674         /* without this, a new interface that draws as result of the menu change
5675          * won't register that the mouse is over it, eg:
5676          * Alt+MouseWheel over the render slots, without this,
5677          * the slot menu fails to switch a second time.
5678          *
5679          * The active state of the button could be maintained some other way
5680          * and remove this mousemove event.
5681          */
5682         WM_event_add_mousemove(data->window);
5683 
5684         return WM_UI_HANDLER_BREAK;
5685       }
5686     }
5687   }
5688   else if (data->state == BUTTON_STATE_WAIT_DRAG) {
5689 
5690     /* this function also ends state */
5691     if (ui_but_drag_init(C, but, data, event)) {
5692       return WM_UI_HANDLER_BREAK;
5693     }
5694 
5695     /* outside icon quit, not needed if drag activated */
5696     if (0 == ui_but_contains_point_px_icon(but, data->region, event)) {
5697       button_activate_state(C, but, BUTTON_STATE_EXIT);
5698       data->cancel = true;
5699       return WM_UI_HANDLER_BREAK;
5700     }
5701 
5702     if (event->type == LEFTMOUSE && event->val == KM_RELEASE) {
5703       button_activate_state(C, but, BUTTON_STATE_MENU_OPEN);
5704       return WM_UI_HANDLER_BREAK;
5705     }
5706   }
5707 
5708   return WM_UI_HANDLER_CONTINUE;
5709 }
5710 
ui_numedit_but_UNITVEC(uiBut * but,uiHandleButtonData * data,int mx,int my,const enum eSnapType snap)5711 static bool ui_numedit_but_UNITVEC(
5712     uiBut *but, uiHandleButtonData *data, int mx, int my, const enum eSnapType snap)
5713 {
5714   float mrad;
5715   bool changed = true;
5716 
5717   /* button is presumed square */
5718   /* if mouse moves outside of sphere, it does negative normal */
5719 
5720   /* note that both data->vec and data->origvec should be normalized
5721    * else we'll get a harmless but annoying jump when first clicking */
5722 
5723   float *fp = data->origvec;
5724   const float rad = BLI_rctf_size_x(&but->rect);
5725   const float radsq = rad * rad;
5726 
5727   int mdx, mdy;
5728   if (fp[2] > 0.0f) {
5729     mdx = (rad * fp[0]);
5730     mdy = (rad * fp[1]);
5731   }
5732   else if (fp[2] > -1.0f) {
5733     mrad = rad / sqrtf(fp[0] * fp[0] + fp[1] * fp[1]);
5734 
5735     mdx = 2.0f * mrad * fp[0] - (rad * fp[0]);
5736     mdy = 2.0f * mrad * fp[1] - (rad * fp[1]);
5737   }
5738   else {
5739     mdx = mdy = 0;
5740   }
5741 
5742   float dx = (float)(mx + mdx - data->dragstartx);
5743   float dy = (float)(my + mdy - data->dragstarty);
5744 
5745   fp = data->vec;
5746   mrad = dx * dx + dy * dy;
5747   if (mrad < radsq) { /* inner circle */
5748     fp[0] = dx;
5749     fp[1] = dy;
5750     fp[2] = sqrtf(radsq - dx * dx - dy * dy);
5751   }
5752   else { /* outer circle */
5753 
5754     mrad = rad / sqrtf(mrad); /* veclen */
5755 
5756     dx *= (2.0f * mrad - 1.0f);
5757     dy *= (2.0f * mrad - 1.0f);
5758 
5759     mrad = dx * dx + dy * dy;
5760     if (mrad < radsq) {
5761       fp[0] = dx;
5762       fp[1] = dy;
5763       fp[2] = -sqrtf(radsq - dx * dx - dy * dy);
5764     }
5765   }
5766   normalize_v3(fp);
5767 
5768   if (snap != SNAP_OFF) {
5769     const int snap_steps = (snap == SNAP_ON) ? 4 : 12; /* 45 or 15 degree increments */
5770     const float snap_steps_angle = M_PI / snap_steps;
5771     float angle, angle_snap;
5772 
5773     /* round each axis of 'fp' to the next increment
5774      * do this in "angle" space - this gives increments of same size */
5775     for (int i = 0; i < 3; i++) {
5776       angle = asinf(fp[i]);
5777       angle_snap = roundf((angle / snap_steps_angle)) * snap_steps_angle;
5778       fp[i] = sinf(angle_snap);
5779     }
5780     normalize_v3(fp);
5781     changed = !compare_v3v3(fp, data->origvec, FLT_EPSILON);
5782   }
5783 
5784   data->draglastx = mx;
5785   data->draglasty = my;
5786 
5787   return changed;
5788 }
5789 
ui_palette_set_active(uiButColor * color_but)5790 static void ui_palette_set_active(uiButColor *color_but)
5791 {
5792   if (color_but->is_pallete_color) {
5793     Palette *palette = (Palette *)color_but->but.rnapoin.owner_id;
5794     PaletteColor *color = color_but->but.rnapoin.data;
5795     palette->active_color = BLI_findindex(&palette->colors, color);
5796   }
5797 }
5798 
ui_do_but_COLOR(bContext * C,uiBut * but,uiHandleButtonData * data,const wmEvent * event)5799 static int ui_do_but_COLOR(bContext *C, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
5800 {
5801   BLI_assert(but->type == UI_BTYPE_COLOR);
5802   uiButColor *color_but = (uiButColor *)but;
5803 
5804   if (data->state == BUTTON_STATE_HIGHLIGHT) {
5805     /* first handle click on icondrag type button */
5806     if (event->type == LEFTMOUSE && but->dragpoin && event->val == KM_PRESS) {
5807       ui_palette_set_active(color_but);
5808       if (ui_but_contains_point_px_icon(but, data->region, event)) {
5809         button_activate_state(C, but, BUTTON_STATE_WAIT_DRAG);
5810         data->dragstartx = event->x;
5811         data->dragstarty = event->y;
5812         return WM_UI_HANDLER_BREAK;
5813       }
5814     }
5815 #ifdef USE_DRAG_TOGGLE
5816     if (event->type == LEFTMOUSE && event->val == KM_PRESS) {
5817       ui_palette_set_active(color_but);
5818       button_activate_state(C, but, BUTTON_STATE_WAIT_DRAG);
5819       data->dragstartx = event->x;
5820       data->dragstarty = event->y;
5821       return WM_UI_HANDLER_BREAK;
5822     }
5823 #endif
5824     /* regular open menu */
5825     if (ELEM(event->type, LEFTMOUSE, EVT_PADENTER, EVT_RETKEY) && event->val == KM_PRESS) {
5826       ui_palette_set_active(color_but);
5827       button_activate_state(C, but, BUTTON_STATE_MENU_OPEN);
5828       return WM_UI_HANDLER_BREAK;
5829     }
5830     if (ELEM(event->type, MOUSEPAN, WHEELDOWNMOUSE, WHEELUPMOUSE) && event->ctrl) {
5831       ColorPicker *cpicker = but->custom_data;
5832       float hsv_static[3] = {0.0f};
5833       float *hsv = cpicker ? cpicker->color_data : hsv_static;
5834       float col[3];
5835 
5836       ui_but_v3_get(but, col);
5837       rgb_to_hsv_compat_v(col, hsv);
5838 
5839       if (event->type == WHEELDOWNMOUSE) {
5840         hsv[2] = clamp_f(hsv[2] - 0.05f, 0.0f, 1.0f);
5841       }
5842       else if (event->type == WHEELUPMOUSE) {
5843         hsv[2] = clamp_f(hsv[2] + 0.05f, 0.0f, 1.0f);
5844       }
5845       else {
5846         const float fac = 0.005 * (event->y - event->prevy);
5847         hsv[2] = clamp_f(hsv[2] + fac, 0.0f, 1.0f);
5848       }
5849 
5850       hsv_to_rgb_v(hsv, data->vec);
5851       ui_but_v3_set(but, data->vec);
5852 
5853       button_activate_state(C, but, BUTTON_STATE_EXIT);
5854       ui_apply_but(C, but->block, but, data, true);
5855       return WM_UI_HANDLER_BREAK;
5856     }
5857     if (color_but->is_pallete_color && (event->type == EVT_DELKEY) && (event->val == KM_PRESS)) {
5858       Palette *palette = (Palette *)but->rnapoin.owner_id;
5859       PaletteColor *color = but->rnapoin.data;
5860 
5861       BKE_palette_color_remove(palette, color);
5862 
5863       button_activate_state(C, but, BUTTON_STATE_EXIT);
5864 
5865       /* this is risky. it works OK for now,
5866        * but if it gives trouble we should delay execution */
5867       but->rnapoin = PointerRNA_NULL;
5868       but->rnaprop = NULL;
5869 
5870       return WM_UI_HANDLER_BREAK;
5871     }
5872   }
5873   else if (data->state == BUTTON_STATE_WAIT_DRAG) {
5874 
5875     /* this function also ends state */
5876     if (ui_but_drag_init(C, but, data, event)) {
5877       return WM_UI_HANDLER_BREAK;
5878     }
5879 
5880     /* outside icon quit, not needed if drag activated */
5881     if (0 == ui_but_contains_point_px_icon(but, data->region, event)) {
5882       button_activate_state(C, but, BUTTON_STATE_EXIT);
5883       data->cancel = true;
5884       return WM_UI_HANDLER_BREAK;
5885     }
5886 
5887     if (event->type == LEFTMOUSE && event->val == KM_RELEASE) {
5888       if (color_but->is_pallete_color) {
5889         if (!event->ctrl) {
5890           float color[3];
5891           Paint *paint = BKE_paint_get_active_from_context(C);
5892           Brush *brush = BKE_paint_brush(paint);
5893 
5894           if (brush->flag & BRUSH_USE_GRADIENT) {
5895             float *target = &brush->gradient->data[brush->gradient->cur].r;
5896 
5897             if (but->rnaprop && RNA_property_subtype(but->rnaprop) == PROP_COLOR_GAMMA) {
5898               RNA_property_float_get_array(&but->rnapoin, but->rnaprop, target);
5899               IMB_colormanagement_srgb_to_scene_linear_v3(target);
5900             }
5901             else if (but->rnaprop && RNA_property_subtype(but->rnaprop) == PROP_COLOR) {
5902               RNA_property_float_get_array(&but->rnapoin, but->rnaprop, target);
5903             }
5904           }
5905           else {
5906             Scene *scene = CTX_data_scene(C);
5907             bool updated = false;
5908 
5909             if (but->rnaprop && RNA_property_subtype(but->rnaprop) == PROP_COLOR_GAMMA) {
5910               RNA_property_float_get_array(&but->rnapoin, but->rnaprop, color);
5911               BKE_brush_color_set(scene, brush, color);
5912               updated = true;
5913             }
5914             else if (but->rnaprop && RNA_property_subtype(but->rnaprop) == PROP_COLOR) {
5915               RNA_property_float_get_array(&but->rnapoin, but->rnaprop, color);
5916               IMB_colormanagement_scene_linear_to_srgb_v3(color);
5917               BKE_brush_color_set(scene, brush, color);
5918               updated = true;
5919             }
5920 
5921             if (updated) {
5922               PointerRNA brush_ptr;
5923               PropertyRNA *brush_color_prop;
5924 
5925               RNA_id_pointer_create(&brush->id, &brush_ptr);
5926               brush_color_prop = RNA_struct_find_property(&brush_ptr, "color");
5927               RNA_property_update(C, &brush_ptr, brush_color_prop);
5928             }
5929           }
5930 
5931           button_activate_state(C, but, BUTTON_STATE_EXIT);
5932         }
5933         else {
5934           button_activate_state(C, but, BUTTON_STATE_MENU_OPEN);
5935         }
5936       }
5937       else {
5938         button_activate_state(C, but, BUTTON_STATE_MENU_OPEN);
5939       }
5940       return WM_UI_HANDLER_BREAK;
5941     }
5942   }
5943 
5944   return WM_UI_HANDLER_CONTINUE;
5945 }
5946 
ui_do_but_UNITVEC(bContext * C,uiBlock * block,uiBut * but,uiHandleButtonData * data,const wmEvent * event)5947 static int ui_do_but_UNITVEC(
5948     bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
5949 {
5950   int mx = event->x;
5951   int my = event->y;
5952   ui_window_to_block(data->region, block, &mx, &my);
5953 
5954   if (data->state == BUTTON_STATE_HIGHLIGHT) {
5955     if (event->type == LEFTMOUSE && event->val == KM_PRESS) {
5956       const enum eSnapType snap = ui_event_to_snap(event);
5957       data->dragstartx = mx;
5958       data->dragstarty = my;
5959       data->draglastx = mx;
5960       data->draglasty = my;
5961       button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
5962 
5963       /* also do drag the first time */
5964       if (ui_numedit_but_UNITVEC(but, data, mx, my, snap)) {
5965         ui_numedit_apply(C, block, but, data);
5966       }
5967 
5968       return WM_UI_HANDLER_BREAK;
5969     }
5970   }
5971   else if (data->state == BUTTON_STATE_NUM_EDITING) {
5972     if ((event->type == MOUSEMOVE) || ui_event_is_snap(event)) {
5973       if (mx != data->draglastx || my != data->draglasty || event->type != MOUSEMOVE) {
5974         const enum eSnapType snap = ui_event_to_snap(event);
5975         if (ui_numedit_but_UNITVEC(but, data, mx, my, snap)) {
5976           ui_numedit_apply(C, block, but, data);
5977         }
5978       }
5979     }
5980     else if (event->type == EVT_ESCKEY || event->type == RIGHTMOUSE) {
5981       if (event->val == KM_PRESS) {
5982         data->cancel = true;
5983         data->escapecancel = true;
5984         button_activate_state(C, but, BUTTON_STATE_EXIT);
5985       }
5986     }
5987     else if (event->type == LEFTMOUSE && event->val == KM_RELEASE) {
5988       button_activate_state(C, but, BUTTON_STATE_EXIT);
5989     }
5990 
5991     return WM_UI_HANDLER_BREAK;
5992   }
5993 
5994   return WM_UI_HANDLER_CONTINUE;
5995 }
5996 
5997 /* scales a vector so no axis exceeds max
5998  * (could become BLI_math func) */
clamp_axis_max_v3(float v[3],const float max)5999 static void clamp_axis_max_v3(float v[3], const float max)
6000 {
6001   const float v_max = max_fff(v[0], v[1], v[2]);
6002   if (v_max > max) {
6003     mul_v3_fl(v, max / v_max);
6004     if (v[0] > max) {
6005       v[0] = max;
6006     }
6007     if (v[1] > max) {
6008       v[1] = max;
6009     }
6010     if (v[2] > max) {
6011       v[2] = max;
6012     }
6013   }
6014 }
6015 
ui_rgb_to_color_picker_HSVCUBE_compat_v(const uiButHSVCube * hsv_but,const float rgb[3],float hsv[3])6016 static void ui_rgb_to_color_picker_HSVCUBE_compat_v(const uiButHSVCube *hsv_but,
6017                                                     const float rgb[3],
6018                                                     float hsv[3])
6019 {
6020   if (hsv_but->gradient_type == UI_GRAD_L_ALT) {
6021     rgb_to_hsl_compat_v(rgb, hsv);
6022   }
6023   else {
6024     rgb_to_hsv_compat_v(rgb, hsv);
6025   }
6026 }
6027 
ui_rgb_to_color_picker_HSVCUBE_v(const uiButHSVCube * hsv_but,const float rgb[3],float hsv[3])6028 static void ui_rgb_to_color_picker_HSVCUBE_v(const uiButHSVCube *hsv_but,
6029                                              const float rgb[3],
6030                                              float hsv[3])
6031 {
6032   if (hsv_but->gradient_type == UI_GRAD_L_ALT) {
6033     rgb_to_hsl_v(rgb, hsv);
6034   }
6035   else {
6036     rgb_to_hsv_v(rgb, hsv);
6037   }
6038 }
6039 
ui_color_picker_to_rgb_HSVCUBE_v(const uiButHSVCube * hsv_but,const float hsv[3],float rgb[3])6040 static void ui_color_picker_to_rgb_HSVCUBE_v(const uiButHSVCube *hsv_but,
6041                                              const float hsv[3],
6042                                              float rgb[3])
6043 {
6044   if (hsv_but->gradient_type == UI_GRAD_L_ALT) {
6045     hsl_to_rgb_v(hsv, rgb);
6046   }
6047   else {
6048     hsv_to_rgb_v(hsv, rgb);
6049   }
6050 }
6051 
ui_numedit_but_HSVCUBE(uiBut * but,uiHandleButtonData * data,int mx,int my,const enum eSnapType snap,const bool shift)6052 static bool ui_numedit_but_HSVCUBE(uiBut *but,
6053                                    uiHandleButtonData *data,
6054                                    int mx,
6055                                    int my,
6056                                    const enum eSnapType snap,
6057                                    const bool shift)
6058 {
6059   const uiButHSVCube *hsv_but = (uiButHSVCube *)but;
6060   ColorPicker *cpicker = but->custom_data;
6061   float *hsv = cpicker->color_data;
6062   float rgb[3];
6063   float x, y;
6064   float mx_fl, my_fl;
6065   const bool changed = true;
6066 
6067   ui_mouse_scale_warp(data, mx, my, &mx_fl, &my_fl, shift);
6068 
6069 #ifdef USE_CONT_MOUSE_CORRECT
6070   if (ui_but_is_cursor_warp(but)) {
6071     /* OK but can go outside bounds */
6072     data->ungrab_mval[0] = mx_fl;
6073     data->ungrab_mval[1] = my_fl;
6074     BLI_rctf_clamp_pt_v(&but->rect, data->ungrab_mval);
6075   }
6076 #endif
6077 
6078   ui_but_v3_get(but, rgb);
6079   ui_scene_linear_to_color_picker_space(but, rgb);
6080 
6081   ui_rgb_to_color_picker_HSVCUBE_compat_v(hsv_but, rgb, hsv);
6082 
6083   /* only apply the delta motion, not absolute */
6084   if (shift) {
6085     rcti rect_i;
6086     float xpos, ypos, hsvo[3];
6087 
6088     BLI_rcti_rctf_copy(&rect_i, &but->rect);
6089 
6090     /* calculate original hsv again */
6091     copy_v3_v3(rgb, data->origvec);
6092     ui_scene_linear_to_color_picker_space(but, rgb);
6093 
6094     copy_v3_v3(hsvo, hsv);
6095 
6096     ui_rgb_to_color_picker_HSVCUBE_compat_v(hsv_but, rgb, hsvo);
6097 
6098     /* and original position */
6099     ui_hsvcube_pos_from_vals(hsv_but, &rect_i, hsvo, &xpos, &ypos);
6100 
6101     mx_fl = xpos - (data->dragstartx - mx_fl);
6102     my_fl = ypos - (data->dragstarty - my_fl);
6103   }
6104 
6105   /* relative position within box */
6106   x = ((float)mx_fl - but->rect.xmin) / BLI_rctf_size_x(&but->rect);
6107   y = ((float)my_fl - but->rect.ymin) / BLI_rctf_size_y(&but->rect);
6108   CLAMP(x, 0.0f, 1.0f);
6109   CLAMP(y, 0.0f, 1.0f);
6110 
6111   switch (hsv_but->gradient_type) {
6112     case UI_GRAD_SV:
6113       hsv[1] = x;
6114       hsv[2] = y;
6115       break;
6116     case UI_GRAD_HV:
6117       hsv[0] = x;
6118       hsv[2] = y;
6119       break;
6120     case UI_GRAD_HS:
6121       hsv[0] = x;
6122       hsv[1] = y;
6123       break;
6124     case UI_GRAD_H:
6125       hsv[0] = x;
6126       break;
6127     case UI_GRAD_S:
6128       hsv[1] = x;
6129       break;
6130     case UI_GRAD_V:
6131       hsv[2] = x;
6132       break;
6133     case UI_GRAD_L_ALT:
6134       hsv[2] = y;
6135       break;
6136     case UI_GRAD_V_ALT: {
6137       /* vertical 'value' strip */
6138       const float min = but->softmin, max = but->softmax;
6139       /* exception only for value strip - use the range set in but->min/max */
6140       hsv[2] = y * (max - min) + min;
6141       break;
6142     }
6143     default:
6144       BLI_assert(0);
6145       break;
6146   }
6147 
6148   if (snap != SNAP_OFF) {
6149     if (ELEM(hsv_but->gradient_type, UI_GRAD_HV, UI_GRAD_HS, UI_GRAD_H)) {
6150       ui_color_snap_hue(snap, &hsv[0]);
6151     }
6152   }
6153 
6154   ui_color_picker_to_rgb_HSVCUBE_v(hsv_but, hsv, rgb);
6155   ui_color_picker_to_scene_linear_space(but, rgb);
6156 
6157   /* clamp because with color conversion we can exceed range T34295. */
6158   if (hsv_but->gradient_type == UI_GRAD_V_ALT) {
6159     clamp_axis_max_v3(rgb, but->softmax);
6160   }
6161 
6162   copy_v3_v3(data->vec, rgb);
6163 
6164   data->draglastx = mx;
6165   data->draglasty = my;
6166 
6167   return changed;
6168 }
6169 
6170 #ifdef WITH_INPUT_NDOF
ui_ndofedit_but_HSVCUBE(uiButHSVCube * hsv_but,uiHandleButtonData * data,const wmNDOFMotionData * ndof,const enum eSnapType snap,const bool shift)6171 static void ui_ndofedit_but_HSVCUBE(uiButHSVCube *hsv_but,
6172                                     uiHandleButtonData *data,
6173                                     const wmNDOFMotionData *ndof,
6174                                     const enum eSnapType snap,
6175                                     const bool shift)
6176 {
6177   ColorPicker *cpicker = hsv_but->but.custom_data;
6178   float *hsv = cpicker->color_data;
6179   const float hsv_v_max = max_ff(hsv[2], hsv_but->but.softmax);
6180   float rgb[3];
6181   const float sensitivity = (shift ? 0.15f : 0.3f) * ndof->dt;
6182 
6183   ui_but_v3_get(&hsv_but->but, rgb);
6184   ui_scene_linear_to_color_picker_space(&hsv_but->but, rgb);
6185   ui_rgb_to_color_picker_HSVCUBE_compat_v(hsv_but, rgb, hsv);
6186 
6187   switch (hsv_but->gradient_type) {
6188     case UI_GRAD_SV:
6189       hsv[1] += ndof->rvec[2] * sensitivity;
6190       hsv[2] += ndof->rvec[0] * sensitivity;
6191       break;
6192     case UI_GRAD_HV:
6193       hsv[0] += ndof->rvec[2] * sensitivity;
6194       hsv[2] += ndof->rvec[0] * sensitivity;
6195       break;
6196     case UI_GRAD_HS:
6197       hsv[0] += ndof->rvec[2] * sensitivity;
6198       hsv[1] += ndof->rvec[0] * sensitivity;
6199       break;
6200     case UI_GRAD_H:
6201       hsv[0] += ndof->rvec[2] * sensitivity;
6202       break;
6203     case UI_GRAD_S:
6204       hsv[1] += ndof->rvec[2] * sensitivity;
6205       break;
6206     case UI_GRAD_V:
6207       hsv[2] += ndof->rvec[2] * sensitivity;
6208       break;
6209     case UI_GRAD_V_ALT:
6210     case UI_GRAD_L_ALT:
6211       /* vertical 'value' strip */
6212 
6213       /* exception only for value strip - use the range set in but->min/max */
6214       hsv[2] += ndof->rvec[0] * sensitivity;
6215 
6216       CLAMP(hsv[2], hsv_but->but.softmin, hsv_but->but.softmax);
6217       break;
6218     default:
6219       BLI_assert(!"invalid hsv type");
6220       break;
6221   }
6222 
6223   if (snap != SNAP_OFF) {
6224     if (ELEM(hsv_but->gradient_type, UI_GRAD_HV, UI_GRAD_HS, UI_GRAD_H)) {
6225       ui_color_snap_hue(snap, &hsv[0]);
6226     }
6227   }
6228 
6229   /* ndof specific: the changes above aren't clamping */
6230   hsv_clamp_v(hsv, hsv_v_max);
6231 
6232   ui_color_picker_to_rgb_HSVCUBE_v(hsv_but, hsv, rgb);
6233   ui_color_picker_to_scene_linear_space(&hsv_but->but, rgb);
6234 
6235   copy_v3_v3(data->vec, rgb);
6236   ui_but_v3_set(&hsv_but->but, data->vec);
6237 }
6238 #endif /* WITH_INPUT_NDOF */
6239 
ui_do_but_HSVCUBE(bContext * C,uiBlock * block,uiBut * but,uiHandleButtonData * data,const wmEvent * event)6240 static int ui_do_but_HSVCUBE(
6241     bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
6242 {
6243   uiButHSVCube *hsv_but = (uiButHSVCube *)but;
6244   int mx = event->x;
6245   int my = event->y;
6246   ui_window_to_block(data->region, block, &mx, &my);
6247 
6248   if (data->state == BUTTON_STATE_HIGHLIGHT) {
6249     if (event->type == LEFTMOUSE && event->val == KM_PRESS) {
6250       const enum eSnapType snap = ui_event_to_snap(event);
6251 
6252       data->dragstartx = mx;
6253       data->dragstarty = my;
6254       data->draglastx = mx;
6255       data->draglasty = my;
6256       button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
6257 
6258       /* also do drag the first time */
6259       if (ui_numedit_but_HSVCUBE(but, data, mx, my, snap, event->shift != 0)) {
6260         ui_numedit_apply(C, block, but, data);
6261       }
6262 
6263       return WM_UI_HANDLER_BREAK;
6264     }
6265 #ifdef WITH_INPUT_NDOF
6266     if (event->type == NDOF_MOTION) {
6267       const wmNDOFMotionData *ndof = event->customdata;
6268       const enum eSnapType snap = ui_event_to_snap(event);
6269 
6270       ui_ndofedit_but_HSVCUBE(hsv_but, data, ndof, snap, event->shift != 0);
6271 
6272       button_activate_state(C, but, BUTTON_STATE_EXIT);
6273       ui_apply_but(C, but->block, but, data, true);
6274 
6275       return WM_UI_HANDLER_BREAK;
6276     }
6277 #endif /* WITH_INPUT_NDOF */
6278     /* XXX hardcoded keymap check.... */
6279     if (event->type == EVT_BACKSPACEKEY && event->val == KM_PRESS) {
6280       if (ELEM(hsv_but->gradient_type, UI_GRAD_V_ALT, UI_GRAD_L_ALT)) {
6281         int len;
6282 
6283         /* reset only value */
6284 
6285         len = RNA_property_array_length(&but->rnapoin, but->rnaprop);
6286         if (ELEM(len, 3, 4)) {
6287           float rgb[3], def_hsv[3];
6288           float def[4];
6289           ColorPicker *cpicker = but->custom_data;
6290           float *hsv = cpicker->color_data;
6291 
6292           RNA_property_float_get_default_array(&but->rnapoin, but->rnaprop, def);
6293           ui_rgb_to_color_picker_HSVCUBE_v(hsv_but, def, def_hsv);
6294 
6295           ui_but_v3_get(but, rgb);
6296           ui_rgb_to_color_picker_HSVCUBE_compat_v(hsv_but, rgb, hsv);
6297 
6298           def_hsv[0] = hsv[0];
6299           def_hsv[1] = hsv[1];
6300 
6301           ui_color_picker_to_rgb_HSVCUBE_v(hsv_but, def_hsv, rgb);
6302           ui_but_v3_set(but, rgb);
6303 
6304           RNA_property_update(C, &but->rnapoin, but->rnaprop);
6305           return WM_UI_HANDLER_BREAK;
6306         }
6307       }
6308     }
6309   }
6310   else if (data->state == BUTTON_STATE_NUM_EDITING) {
6311     if (event->type == EVT_ESCKEY || event->type == RIGHTMOUSE) {
6312       if (event->val == KM_PRESS) {
6313         data->cancel = true;
6314         data->escapecancel = true;
6315         button_activate_state(C, but, BUTTON_STATE_EXIT);
6316       }
6317     }
6318     else if ((event->type == MOUSEMOVE) || ui_event_is_snap(event)) {
6319       if (mx != data->draglastx || my != data->draglasty || event->type != MOUSEMOVE) {
6320         const enum eSnapType snap = ui_event_to_snap(event);
6321 
6322         if (ui_numedit_but_HSVCUBE(but, data, mx, my, snap, event->shift != 0)) {
6323           ui_numedit_apply(C, block, but, data);
6324         }
6325       }
6326     }
6327     else if (event->type == LEFTMOUSE && event->val == KM_RELEASE) {
6328       button_activate_state(C, but, BUTTON_STATE_EXIT);
6329     }
6330 
6331     return WM_UI_HANDLER_BREAK;
6332   }
6333 
6334   return WM_UI_HANDLER_CONTINUE;
6335 }
6336 
ui_numedit_but_HSVCIRCLE(uiBut * but,uiHandleButtonData * data,float mx,float my,const enum eSnapType snap,const bool shift)6337 static bool ui_numedit_but_HSVCIRCLE(uiBut *but,
6338                                      uiHandleButtonData *data,
6339                                      float mx,
6340                                      float my,
6341                                      const enum eSnapType snap,
6342                                      const bool shift)
6343 {
6344   const bool changed = true;
6345   ColorPicker *cpicker = but->custom_data;
6346   float *hsv = cpicker->color_data;
6347 
6348   float mx_fl, my_fl;
6349   ui_mouse_scale_warp(data, mx, my, &mx_fl, &my_fl, shift);
6350 
6351 #ifdef USE_CONT_MOUSE_CORRECT
6352   if (ui_but_is_cursor_warp(but)) {
6353     /* OK but can go outside bounds */
6354     data->ungrab_mval[0] = mx_fl;
6355     data->ungrab_mval[1] = my_fl;
6356     { /* clamp */
6357       const float radius = min_ff(BLI_rctf_size_x(&but->rect), BLI_rctf_size_y(&but->rect)) / 2.0f;
6358       const float cent[2] = {BLI_rctf_cent_x(&but->rect), BLI_rctf_cent_y(&but->rect)};
6359       const float len = len_v2v2(cent, data->ungrab_mval);
6360       if (len > radius) {
6361         dist_ensure_v2_v2fl(data->ungrab_mval, cent, radius);
6362       }
6363     }
6364   }
6365 #endif
6366 
6367   rcti rect;
6368   BLI_rcti_rctf_copy(&rect, &but->rect);
6369 
6370   float rgb[3];
6371   ui_but_v3_get(but, rgb);
6372   ui_scene_linear_to_color_picker_space(but, rgb);
6373   ui_rgb_to_color_picker_compat_v(rgb, hsv);
6374 
6375   /* exception, when using color wheel in 'locked' value state:
6376    * allow choosing a hue for black values, by giving a tiny increment */
6377   if (cpicker->use_color_lock) {
6378     if (U.color_picker_type == USER_CP_CIRCLE_HSV) { /* lock */
6379       if (hsv[2] == 0.f) {
6380         hsv[2] = 0.0001f;
6381       }
6382     }
6383     else {
6384       if (hsv[2] == 0.0f) {
6385         hsv[2] = 0.0001f;
6386       }
6387       if (hsv[2] >= 0.9999f) {
6388         hsv[2] = 0.9999f;
6389       }
6390     }
6391   }
6392 
6393   /* only apply the delta motion, not absolute */
6394   if (shift) {
6395     float xpos, ypos, hsvo[3], rgbo[3];
6396 
6397     /* calculate original hsv again */
6398     copy_v3_v3(hsvo, hsv);
6399     copy_v3_v3(rgbo, data->origvec);
6400     ui_scene_linear_to_color_picker_space(but, rgbo);
6401     ui_rgb_to_color_picker_compat_v(rgbo, hsvo);
6402 
6403     /* and original position */
6404     ui_hsvcircle_pos_from_vals(cpicker, &rect, hsvo, &xpos, &ypos);
6405 
6406     mx_fl = xpos - (data->dragstartx - mx_fl);
6407     my_fl = ypos - (data->dragstarty - my_fl);
6408   }
6409 
6410   ui_hsvcircle_vals_from_pos(&rect, mx_fl, my_fl, hsv, hsv + 1);
6411 
6412   if ((cpicker->use_color_cubic) && (U.color_picker_type == USER_CP_CIRCLE_HSV)) {
6413     hsv[1] = 1.0f - sqrt3f(1.0f - hsv[1]);
6414   }
6415 
6416   if (snap != SNAP_OFF) {
6417     ui_color_snap_hue(snap, &hsv[0]);
6418   }
6419 
6420   ui_color_picker_to_rgb_v(hsv, rgb);
6421 
6422   if ((cpicker->use_luminosity_lock)) {
6423     if (!is_zero_v3(rgb)) {
6424       normalize_v3_length(rgb, cpicker->luminosity_lock_value);
6425     }
6426   }
6427 
6428   ui_color_picker_to_scene_linear_space(but, rgb);
6429   ui_but_v3_set(but, rgb);
6430 
6431   data->draglastx = mx;
6432   data->draglasty = my;
6433 
6434   return changed;
6435 }
6436 
6437 #ifdef WITH_INPUT_NDOF
ui_ndofedit_but_HSVCIRCLE(uiBut * but,uiHandleButtonData * data,const wmNDOFMotionData * ndof,const enum eSnapType snap,const bool shift)6438 static void ui_ndofedit_but_HSVCIRCLE(uiBut *but,
6439                                       uiHandleButtonData *data,
6440                                       const wmNDOFMotionData *ndof,
6441                                       const enum eSnapType snap,
6442                                       const bool shift)
6443 {
6444   ColorPicker *cpicker = but->custom_data;
6445   float *hsv = cpicker->color_data;
6446   float rgb[3];
6447   float phi, r /*, sqr */ /* UNUSED */, v[2];
6448   const float sensitivity = (shift ? 0.06f : 0.3f) * ndof->dt;
6449 
6450   ui_but_v3_get(but, rgb);
6451   ui_scene_linear_to_color_picker_space(but, rgb);
6452   ui_rgb_to_color_picker_compat_v(rgb, hsv);
6453 
6454   /* Convert current color on hue/sat disc to circular coordinates phi, r */
6455   phi = fmodf(hsv[0] + 0.25f, 1.0f) * -2.0f * (float)M_PI;
6456   r = hsv[1];
6457   /* sqr = r > 0.0f ? sqrtf(r) : 1; */ /* UNUSED */
6458 
6459   /* Convert to 2d vectors */
6460   v[0] = r * cosf(phi);
6461   v[1] = r * sinf(phi);
6462 
6463   /* Use ndof device y and x rotation to move the vector in 2d space */
6464   v[0] += ndof->rvec[2] * sensitivity;
6465   v[1] += ndof->rvec[0] * sensitivity;
6466 
6467   /* convert back to polar coords on circle */
6468   phi = atan2f(v[0], v[1]) / (2.0f * (float)M_PI) + 0.5f;
6469 
6470   /* use ndof Y rotation to additionally rotate hue */
6471   phi += ndof->rvec[1] * sensitivity * 0.5f;
6472   r = len_v2(v);
6473 
6474   /* convert back to hsv values, in range [0,1] */
6475   hsv[0] = phi;
6476   hsv[1] = r;
6477 
6478   /* exception, when using color wheel in 'locked' value state:
6479    * allow choosing a hue for black values, by giving a tiny increment */
6480   if (cpicker->use_color_lock) {
6481     if (U.color_picker_type == USER_CP_CIRCLE_HSV) { /* lock */
6482       if (hsv[2] == 0.f) {
6483         hsv[2] = 0.0001f;
6484       }
6485     }
6486     else {
6487       if (hsv[2] == 0.f) {
6488         hsv[2] = 0.0001f;
6489       }
6490       if (hsv[2] == 1.f) {
6491         hsv[2] = 0.9999f;
6492       }
6493     }
6494   }
6495 
6496   if (snap != SNAP_OFF) {
6497     ui_color_snap_hue(snap, &hsv[0]);
6498   }
6499 
6500   hsv_clamp_v(hsv, FLT_MAX);
6501 
6502   ui_color_picker_to_rgb_v(hsv, data->vec);
6503 
6504   if (cpicker->use_luminosity_lock) {
6505     if (!is_zero_v3(data->vec)) {
6506       normalize_v3_length(data->vec, cpicker->luminosity_lock_value);
6507     }
6508   }
6509 
6510   ui_color_picker_to_scene_linear_space(but, data->vec);
6511   ui_but_v3_set(but, data->vec);
6512 }
6513 #endif /* WITH_INPUT_NDOF */
6514 
ui_do_but_HSVCIRCLE(bContext * C,uiBlock * block,uiBut * but,uiHandleButtonData * data,const wmEvent * event)6515 static int ui_do_but_HSVCIRCLE(
6516     bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
6517 {
6518   ColorPicker *cpicker = but->custom_data;
6519   float *hsv = cpicker->color_data;
6520   int mx = event->x;
6521   int my = event->y;
6522   ui_window_to_block(data->region, block, &mx, &my);
6523 
6524   if (data->state == BUTTON_STATE_HIGHLIGHT) {
6525     if (event->type == LEFTMOUSE && event->val == KM_PRESS) {
6526       const enum eSnapType snap = ui_event_to_snap(event);
6527       data->dragstartx = mx;
6528       data->dragstarty = my;
6529       data->draglastx = mx;
6530       data->draglasty = my;
6531       button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
6532 
6533       /* also do drag the first time */
6534       if (ui_numedit_but_HSVCIRCLE(but, data, mx, my, snap, event->shift != 0)) {
6535         ui_numedit_apply(C, block, but, data);
6536       }
6537 
6538       return WM_UI_HANDLER_BREAK;
6539     }
6540 #ifdef WITH_INPUT_NDOF
6541     if (event->type == NDOF_MOTION) {
6542       const enum eSnapType snap = ui_event_to_snap(event);
6543       const wmNDOFMotionData *ndof = event->customdata;
6544 
6545       ui_ndofedit_but_HSVCIRCLE(but, data, ndof, snap, event->shift != 0);
6546 
6547       button_activate_state(C, but, BUTTON_STATE_EXIT);
6548       ui_apply_but(C, but->block, but, data, true);
6549 
6550       return WM_UI_HANDLER_BREAK;
6551     }
6552 #endif /* WITH_INPUT_NDOF */
6553     /* XXX hardcoded keymap check.... */
6554     if (event->type == EVT_BACKSPACEKEY && event->val == KM_PRESS) {
6555       int len;
6556 
6557       /* reset only saturation */
6558 
6559       len = RNA_property_array_length(&but->rnapoin, but->rnaprop);
6560       if (len >= 3) {
6561         float rgb[3], def_hsv[3];
6562         float *def;
6563         def = MEM_callocN(sizeof(float) * len, "reset_defaults - float");
6564 
6565         RNA_property_float_get_default_array(&but->rnapoin, but->rnaprop, def);
6566         ui_color_picker_to_rgb_v(def, def_hsv);
6567 
6568         ui_but_v3_get(but, rgb);
6569         ui_rgb_to_color_picker_compat_v(rgb, hsv);
6570 
6571         def_hsv[0] = hsv[0];
6572         def_hsv[2] = hsv[2];
6573 
6574         hsv_to_rgb_v(def_hsv, rgb);
6575         ui_but_v3_set(but, rgb);
6576 
6577         RNA_property_update(C, &but->rnapoin, but->rnaprop);
6578 
6579         MEM_freeN(def);
6580       }
6581       return WM_UI_HANDLER_BREAK;
6582     }
6583   }
6584   else if (data->state == BUTTON_STATE_NUM_EDITING) {
6585     if (event->type == EVT_ESCKEY || event->type == RIGHTMOUSE) {
6586       if (event->val == KM_PRESS) {
6587         data->cancel = true;
6588         data->escapecancel = true;
6589         button_activate_state(C, but, BUTTON_STATE_EXIT);
6590       }
6591     }
6592     /* XXX hardcoded keymap check.... */
6593     else if (event->type == WHEELDOWNMOUSE) {
6594       hsv[2] = clamp_f(hsv[2] - 0.05f, 0.0f, 1.0f);
6595       ui_but_hsv_set(but); /* converts to rgb */
6596       ui_numedit_apply(C, block, but, data);
6597     }
6598     else if (event->type == WHEELUPMOUSE) {
6599       hsv[2] = clamp_f(hsv[2] + 0.05f, 0.0f, 1.0f);
6600       ui_but_hsv_set(but); /* converts to rgb */
6601       ui_numedit_apply(C, block, but, data);
6602     }
6603     else if ((event->type == MOUSEMOVE) || ui_event_is_snap(event)) {
6604       if (mx != data->draglastx || my != data->draglasty || event->type != MOUSEMOVE) {
6605         const enum eSnapType snap = ui_event_to_snap(event);
6606 
6607         if (ui_numedit_but_HSVCIRCLE(but, data, mx, my, snap, event->shift != 0)) {
6608           ui_numedit_apply(C, block, but, data);
6609         }
6610       }
6611     }
6612     else if (event->type == LEFTMOUSE && event->val == KM_RELEASE) {
6613       button_activate_state(C, but, BUTTON_STATE_EXIT);
6614     }
6615     return WM_UI_HANDLER_BREAK;
6616   }
6617 
6618   return WM_UI_HANDLER_CONTINUE;
6619 }
6620 
ui_numedit_but_COLORBAND(uiBut * but,uiHandleButtonData * data,int mx)6621 static bool ui_numedit_but_COLORBAND(uiBut *but, uiHandleButtonData *data, int mx)
6622 {
6623   bool changed = false;
6624 
6625   if (data->draglastx == mx) {
6626     return changed;
6627   }
6628 
6629   if (data->coba->tot == 0) {
6630     return changed;
6631   }
6632 
6633   const float dx = ((float)(mx - data->draglastx)) / BLI_rctf_size_x(&but->rect);
6634   data->dragcbd->pos += dx;
6635   CLAMP(data->dragcbd->pos, 0.0f, 1.0f);
6636 
6637   BKE_colorband_update_sort(data->coba);
6638   data->dragcbd = data->coba->data + data->coba->cur; /* because qsort */
6639 
6640   data->draglastx = mx;
6641   changed = true;
6642 
6643   return changed;
6644 }
6645 
ui_do_but_COLORBAND(bContext * C,uiBlock * block,uiBut * but,uiHandleButtonData * data,const wmEvent * event)6646 static int ui_do_but_COLORBAND(
6647     bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
6648 {
6649   int mx = event->x;
6650   int my = event->y;
6651   ui_window_to_block(data->region, block, &mx, &my);
6652 
6653   if (data->state == BUTTON_STATE_HIGHLIGHT) {
6654     if (event->type == LEFTMOUSE && event->val == KM_PRESS) {
6655       ColorBand *coba = (ColorBand *)but->poin;
6656 
6657       if (event->ctrl) {
6658         /* insert new key on mouse location */
6659         const float pos = ((float)(mx - but->rect.xmin)) / BLI_rctf_size_x(&but->rect);
6660         BKE_colorband_element_add(coba, pos);
6661         button_activate_state(C, but, BUTTON_STATE_EXIT);
6662       }
6663       else {
6664         CBData *cbd;
6665         /* ignore zoom-level for mindist */
6666         int mindist = (50 * UI_DPI_FAC) * block->aspect;
6667         int xco;
6668         data->dragstartx = mx;
6669         data->dragstarty = my;
6670         data->draglastx = mx;
6671         data->draglasty = my;
6672 
6673         /* activate new key when mouse is close */
6674         int a;
6675         for (a = 0, cbd = coba->data; a < coba->tot; a++, cbd++) {
6676           xco = but->rect.xmin + (cbd->pos * BLI_rctf_size_x(&but->rect));
6677           xco = abs(xco - mx);
6678           if (a == coba->cur) {
6679             /* Selected one disadvantage. */
6680             xco += 5;
6681           }
6682           if (xco < mindist) {
6683             coba->cur = a;
6684             mindist = xco;
6685           }
6686         }
6687 
6688         data->dragcbd = coba->data + coba->cur;
6689         data->dragfstart = data->dragcbd->pos;
6690         button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
6691       }
6692 
6693       return WM_UI_HANDLER_BREAK;
6694     }
6695   }
6696   else if (data->state == BUTTON_STATE_NUM_EDITING) {
6697     if (event->type == MOUSEMOVE) {
6698       if (mx != data->draglastx || my != data->draglasty) {
6699         if (ui_numedit_but_COLORBAND(but, data, mx)) {
6700           ui_numedit_apply(C, block, but, data);
6701         }
6702       }
6703     }
6704     else if (event->type == LEFTMOUSE && event->val == KM_RELEASE) {
6705       button_activate_state(C, but, BUTTON_STATE_EXIT);
6706     }
6707     else if (ELEM(event->type, EVT_ESCKEY, RIGHTMOUSE)) {
6708       if (event->val == KM_PRESS) {
6709         data->dragcbd->pos = data->dragfstart;
6710         BKE_colorband_update_sort(data->coba);
6711         data->cancel = true;
6712         data->escapecancel = true;
6713         button_activate_state(C, but, BUTTON_STATE_EXIT);
6714       }
6715     }
6716     return WM_UI_HANDLER_BREAK;
6717   }
6718 
6719   return WM_UI_HANDLER_CONTINUE;
6720 }
6721 
ui_numedit_but_CURVE(uiBlock * block,uiBut * but,uiHandleButtonData * data,int evtx,int evty,bool snap,const bool shift)6722 static bool ui_numedit_but_CURVE(uiBlock *block,
6723                                  uiBut *but,
6724                                  uiHandleButtonData *data,
6725                                  int evtx,
6726                                  int evty,
6727                                  bool snap,
6728                                  const bool shift)
6729 {
6730   CurveMapping *cumap = (CurveMapping *)but->poin;
6731   CurveMap *cuma = cumap->cm + cumap->cur;
6732   CurveMapPoint *cmp = cuma->curve;
6733   bool changed = false;
6734 
6735   /* evtx evty and drag coords are absolute mousecoords,
6736    * prevents errors when editing when layout changes */
6737   int mx = evtx;
6738   int my = evty;
6739   ui_window_to_block(data->region, block, &mx, &my);
6740   int dragx = data->draglastx;
6741   int dragy = data->draglasty;
6742   ui_window_to_block(data->region, block, &dragx, &dragy);
6743 
6744   const float zoomx = BLI_rctf_size_x(&but->rect) / BLI_rctf_size_x(&cumap->curr);
6745   const float zoomy = BLI_rctf_size_y(&but->rect) / BLI_rctf_size_y(&cumap->curr);
6746 
6747   if (snap) {
6748     float d[2];
6749 
6750     d[0] = mx - data->dragstartx;
6751     d[1] = my - data->dragstarty;
6752 
6753     if (len_squared_v2(d) < (3.0f * 3.0f)) {
6754       snap = false;
6755     }
6756   }
6757 
6758   float fx = (mx - dragx) / zoomx;
6759   float fy = (my - dragy) / zoomy;
6760 
6761   if (data->dragsel != -1) {
6762     CurveMapPoint *cmp_last = NULL;
6763     const float mval_factor = ui_mouse_scale_warp_factor(shift);
6764     bool moved_point = false; /* for ctrl grid, can't use orig coords because of sorting */
6765 
6766     fx *= mval_factor;
6767     fy *= mval_factor;
6768 
6769     for (int a = 0; a < cuma->totpoint; a++) {
6770       if (cmp[a].flag & CUMA_SELECT) {
6771         const float origx = cmp[a].x, origy = cmp[a].y;
6772         cmp[a].x += fx;
6773         cmp[a].y += fy;
6774         if (snap) {
6775           cmp[a].x = 0.125f * roundf(8.0f * cmp[a].x);
6776           cmp[a].y = 0.125f * roundf(8.0f * cmp[a].y);
6777         }
6778         if (cmp[a].x != origx || cmp[a].y != origy) {
6779           moved_point = true;
6780         }
6781 
6782         cmp_last = &cmp[a];
6783       }
6784     }
6785 
6786     BKE_curvemapping_changed(cumap, false);
6787 
6788     if (moved_point) {
6789       data->draglastx = evtx;
6790       data->draglasty = evty;
6791       changed = true;
6792 
6793 #ifdef USE_CONT_MOUSE_CORRECT
6794       /* note: using 'cmp_last' is weak since there may be multiple points selected,
6795        * but in practice this isnt really an issue */
6796       if (ui_but_is_cursor_warp(but)) {
6797         /* OK but can go outside bounds */
6798         data->ungrab_mval[0] = but->rect.xmin + ((cmp_last->x - cumap->curr.xmin) * zoomx);
6799         data->ungrab_mval[1] = but->rect.ymin + ((cmp_last->y - cumap->curr.ymin) * zoomy);
6800         BLI_rctf_clamp_pt_v(&but->rect, data->ungrab_mval);
6801       }
6802 #endif
6803     }
6804 
6805     data->dragchange = true; /* mark for selection */
6806   }
6807   else {
6808     /* clamp for clip */
6809     if (cumap->flag & CUMA_DO_CLIP) {
6810       if (cumap->curr.xmin - fx < cumap->clipr.xmin) {
6811         fx = cumap->curr.xmin - cumap->clipr.xmin;
6812       }
6813       else if (cumap->curr.xmax - fx > cumap->clipr.xmax) {
6814         fx = cumap->curr.xmax - cumap->clipr.xmax;
6815       }
6816       if (cumap->curr.ymin - fy < cumap->clipr.ymin) {
6817         fy = cumap->curr.ymin - cumap->clipr.ymin;
6818       }
6819       else if (cumap->curr.ymax - fy > cumap->clipr.ymax) {
6820         fy = cumap->curr.ymax - cumap->clipr.ymax;
6821       }
6822     }
6823 
6824     cumap->curr.xmin -= fx;
6825     cumap->curr.ymin -= fy;
6826     cumap->curr.xmax -= fx;
6827     cumap->curr.ymax -= fy;
6828 
6829     data->draglastx = evtx;
6830     data->draglasty = evty;
6831 
6832     changed = true;
6833   }
6834 
6835   return changed;
6836 }
6837 
ui_do_but_CURVE(bContext * C,uiBlock * block,uiBut * but,uiHandleButtonData * data,const wmEvent * event)6838 static int ui_do_but_CURVE(
6839     bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
6840 {
6841   bool changed = false;
6842   Scene *scene = CTX_data_scene(C);
6843   ViewLayer *view_layer = CTX_data_view_layer(C);
6844 
6845   int mx = event->x;
6846   int my = event->y;
6847   ui_window_to_block(data->region, block, &mx, &my);
6848 
6849   if (data->state == BUTTON_STATE_HIGHLIGHT) {
6850     if (event->type == LEFTMOUSE && event->val == KM_PRESS) {
6851       CurveMapping *cumap = (CurveMapping *)but->poin;
6852       CurveMap *cuma = cumap->cm + cumap->cur;
6853       const float m_xy[2] = {mx, my};
6854       float dist_min_sq = square_f(U.dpi_fac * 14.0f); /* 14 pixels radius */
6855       int sel = -1;
6856 
6857       if (event->ctrl) {
6858         float f_xy[2];
6859         BLI_rctf_transform_pt_v(&cumap->curr, &but->rect, f_xy, m_xy);
6860 
6861         BKE_curvemap_insert(cuma, f_xy[0], f_xy[1]);
6862         BKE_curvemapping_changed(cumap, false);
6863         changed = true;
6864       }
6865 
6866       /* check for selecting of a point */
6867       CurveMapPoint *cmp = cuma->curve; /* ctrl adds point, new malloc */
6868       for (int a = 0; a < cuma->totpoint; a++) {
6869         float f_xy[2];
6870         BLI_rctf_transform_pt_v(&but->rect, &cumap->curr, f_xy, &cmp[a].x);
6871         const float dist_sq = len_squared_v2v2(m_xy, f_xy);
6872         if (dist_sq < dist_min_sq) {
6873           sel = a;
6874           dist_min_sq = dist_sq;
6875         }
6876       }
6877 
6878       if (sel == -1) {
6879         float f_xy[2], f_xy_prev[2];
6880 
6881         /* if the click didn't select anything, check if it's clicked on the
6882          * curve itself, and if so, add a point */
6883         cmp = cuma->table;
6884 
6885         BLI_rctf_transform_pt_v(&but->rect, &cumap->curr, f_xy, &cmp[0].x);
6886 
6887         /* with 160px height 8px should translate to the old 0.05 coefficient at no zoom */
6888         dist_min_sq = square_f(U.dpi_fac * 8.0f);
6889 
6890         /* loop through the curve segment table and find what's near the mouse. */
6891         for (int i = 1; i <= CM_TABLE; i++) {
6892           copy_v2_v2(f_xy_prev, f_xy);
6893           BLI_rctf_transform_pt_v(&but->rect, &cumap->curr, f_xy, &cmp[i].x);
6894 
6895           if (dist_squared_to_line_segment_v2(m_xy, f_xy_prev, f_xy) < dist_min_sq) {
6896             BLI_rctf_transform_pt_v(&cumap->curr, &but->rect, f_xy, m_xy);
6897 
6898             BKE_curvemap_insert(cuma, f_xy[0], f_xy[1]);
6899             BKE_curvemapping_changed(cumap, false);
6900 
6901             changed = true;
6902 
6903             /* reset cmp back to the curve points again,
6904              * rather than drawing segments */
6905             cmp = cuma->curve;
6906 
6907             /* find newly added point and make it 'sel' */
6908             for (int a = 0; a < cuma->totpoint; a++) {
6909               if (cmp[a].x == f_xy[0]) {
6910                 sel = a;
6911               }
6912             }
6913             break;
6914           }
6915         }
6916       }
6917 
6918       if (sel != -1) {
6919         /* ok, we move a point */
6920         /* deselect all if this one is deselect. except if we hold shift */
6921         if (!event->shift) {
6922           for (int a = 0; a < cuma->totpoint; a++) {
6923             cmp[a].flag &= ~CUMA_SELECT;
6924           }
6925           cmp[sel].flag |= CUMA_SELECT;
6926         }
6927         else {
6928           cmp[sel].flag ^= CUMA_SELECT;
6929         }
6930       }
6931       else {
6932         /* move the view */
6933         data->cancel = true;
6934       }
6935 
6936       data->dragsel = sel;
6937 
6938       data->dragstartx = event->x;
6939       data->dragstarty = event->y;
6940       data->draglastx = event->x;
6941       data->draglasty = event->y;
6942 
6943       button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
6944       return WM_UI_HANDLER_BREAK;
6945     }
6946   }
6947   else if (data->state == BUTTON_STATE_NUM_EDITING) {
6948     if (event->type == MOUSEMOVE) {
6949       if (event->x != data->draglastx || event->y != data->draglasty) {
6950 
6951         if (ui_numedit_but_CURVE(
6952                 block, but, data, event->x, event->y, event->ctrl != 0, event->shift != 0)) {
6953           ui_numedit_apply(C, block, but, data);
6954         }
6955       }
6956     }
6957     else if (event->type == LEFTMOUSE && event->val == KM_RELEASE) {
6958       if (data->dragsel != -1) {
6959         CurveMapping *cumap = (CurveMapping *)but->poin;
6960         CurveMap *cuma = cumap->cm + cumap->cur;
6961         CurveMapPoint *cmp = cuma->curve;
6962 
6963         if (data->dragchange == false) {
6964           /* deselect all, select one */
6965           if (!event->shift) {
6966             for (int a = 0; a < cuma->totpoint; a++) {
6967               cmp[a].flag &= ~CUMA_SELECT;
6968             }
6969             cmp[data->dragsel].flag |= CUMA_SELECT;
6970           }
6971         }
6972         else {
6973           BKE_curvemapping_changed(cumap, true); /* remove doubles */
6974           BKE_paint_invalidate_cursor_overlay(scene, view_layer, cumap);
6975         }
6976       }
6977 
6978       button_activate_state(C, but, BUTTON_STATE_EXIT);
6979     }
6980 
6981     return WM_UI_HANDLER_BREAK;
6982   }
6983 
6984   /* UNUSED but keep for now */
6985   (void)changed;
6986 
6987   return WM_UI_HANDLER_CONTINUE;
6988 }
6989 
6990 /* Same as ui_numedit_but_CURVE with some smaller changes. */
ui_numedit_but_CURVEPROFILE(uiBlock * block,uiBut * but,uiHandleButtonData * data,int evtx,int evty,bool snap,const bool shift)6991 static bool ui_numedit_but_CURVEPROFILE(uiBlock *block,
6992                                         uiBut *but,
6993                                         uiHandleButtonData *data,
6994                                         int evtx,
6995                                         int evty,
6996                                         bool snap,
6997                                         const bool shift)
6998 {
6999   CurveProfile *profile = (CurveProfile *)but->poin;
7000   CurveProfilePoint *pts = profile->path;
7001   bool changed = false;
7002 
7003   /* evtx evty and drag coords are absolute mousecoords,
7004    * prevents errors when editing when layout changes */
7005   int mx = evtx;
7006   int my = evty;
7007   ui_window_to_block(data->region, block, &mx, &my);
7008   int dragx = data->draglastx;
7009   int dragy = data->draglasty;
7010   ui_window_to_block(data->region, block, &dragx, &dragy);
7011 
7012   const float zoomx = BLI_rctf_size_x(&but->rect) / BLI_rctf_size_x(&profile->view_rect);
7013   const float zoomy = BLI_rctf_size_y(&but->rect) / BLI_rctf_size_y(&profile->view_rect);
7014 
7015   if (snap) {
7016     float d[2] = {mx - data->dragstartx, data->dragstarty};
7017 
7018     if (len_squared_v2(d) < (9.0f * U.dpi_fac)) {
7019       snap = false;
7020     }
7021   }
7022 
7023   float fx = (mx - dragx) / zoomx;
7024   float fy = (my - dragy) / zoomy;
7025 
7026   if (data->dragsel != -1) {
7027     float last_x, last_y;
7028     const float mval_factor = ui_mouse_scale_warp_factor(shift);
7029     bool moved_point = false; /* for ctrl grid, can't use orig coords because of sorting */
7030 
7031     fx *= mval_factor;
7032     fy *= mval_factor;
7033 
7034     /* Move all selected points. */
7035     const float delta[2] = {fx, fy};
7036     for (int a = 0; a < profile->path_len; a++) {
7037       /* Don't move the last and first control points. */
7038       if (pts[a].flag & PROF_SELECT) {
7039         moved_point |= BKE_curveprofile_move_point(profile, &pts[a], snap, delta);
7040         last_x = pts[a].x;
7041         last_y = pts[a].y;
7042       }
7043       else {
7044         /* Move handles when they're selected but the control point isn't. */
7045         if (ELEM(pts[a].h2, HD_FREE, HD_ALIGN) && pts[a].flag == PROF_H1_SELECT) {
7046           moved_point |= BKE_curveprofile_move_handle(&pts[a], true, snap, delta);
7047           last_x = pts[a].h1_loc[0];
7048           last_y = pts[a].h1_loc[1];
7049         }
7050         if (ELEM(pts[a].h2, HD_FREE, HD_ALIGN) && pts[a].flag == PROF_H2_SELECT) {
7051           moved_point |= BKE_curveprofile_move_handle(&pts[a], false, snap, delta);
7052           last_x = pts[a].h2_loc[0];
7053           last_y = pts[a].h2_loc[1];
7054         }
7055       }
7056     }
7057 
7058     BKE_curveprofile_update(profile, PROF_UPDATE_NONE);
7059 
7060     if (moved_point) {
7061       data->draglastx = evtx;
7062       data->draglasty = evty;
7063       changed = true;
7064 #ifdef USE_CONT_MOUSE_CORRECT
7065       /* note: using 'cmp_last' is weak since there may be multiple points selected,
7066        * but in practice this isnt really an issue */
7067       if (ui_but_is_cursor_warp(but)) {
7068         /* OK but can go outside bounds */
7069         data->ungrab_mval[0] = but->rect.xmin + ((last_x - profile->view_rect.xmin) * zoomx);
7070         data->ungrab_mval[1] = but->rect.ymin + ((last_y - profile->view_rect.ymin) * zoomy);
7071         BLI_rctf_clamp_pt_v(&but->rect, data->ungrab_mval);
7072       }
7073 #endif
7074     }
7075     data->dragchange = true; /* mark for selection */
7076   }
7077   else {
7078     /* Clamp the view rect when clipping is on. */
7079     if (profile->flag & PROF_USE_CLIP) {
7080       if (profile->view_rect.xmin - fx < profile->clip_rect.xmin) {
7081         fx = profile->view_rect.xmin - profile->clip_rect.xmin;
7082       }
7083       else if (profile->view_rect.xmax - fx > profile->clip_rect.xmax) {
7084         fx = profile->view_rect.xmax - profile->clip_rect.xmax;
7085       }
7086       if (profile->view_rect.ymin - fy < profile->clip_rect.ymin) {
7087         fy = profile->view_rect.ymin - profile->clip_rect.ymin;
7088       }
7089       else if (profile->view_rect.ymax - fy > profile->clip_rect.ymax) {
7090         fy = profile->view_rect.ymax - profile->clip_rect.ymax;
7091       }
7092     }
7093 
7094     profile->view_rect.xmin -= fx;
7095     profile->view_rect.ymin -= fy;
7096     profile->view_rect.xmax -= fx;
7097     profile->view_rect.ymax -= fy;
7098 
7099     data->draglastx = evtx;
7100     data->draglasty = evty;
7101 
7102     changed = true;
7103   }
7104 
7105   return changed;
7106 }
7107 
7108 /**
7109  * Helper for #ui_do_but_CURVEPROFILE. Used to tell whether to select a control point's handles.
7110  */
point_draw_handles(CurveProfilePoint * point)7111 static bool point_draw_handles(CurveProfilePoint *point)
7112 {
7113   return (point->flag & PROF_SELECT &&
7114           (ELEM(point->h1, HD_FREE, HD_ALIGN) || ELEM(point->h2, HD_FREE, HD_ALIGN))) ||
7115          ELEM(point->flag, PROF_H1_SELECT, PROF_H2_SELECT);
7116 }
7117 
7118 /**
7119  * Interaction for curve profile widget.
7120  * \note Uses hardcoded keys rather than the keymap.
7121  */
ui_do_but_CURVEPROFILE(bContext * C,uiBlock * block,uiBut * but,uiHandleButtonData * data,const wmEvent * event)7122 static int ui_do_but_CURVEPROFILE(
7123     bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
7124 {
7125   CurveProfile *profile = (CurveProfile *)but->poin;
7126   int mx = event->x;
7127   int my = event->y;
7128 
7129   ui_window_to_block(data->region, block, &mx, &my);
7130 
7131   /* Move selected control points. */
7132   if (event->type == EVT_GKEY && event->val == KM_RELEASE) {
7133     data->dragstartx = mx;
7134     data->dragstarty = my;
7135     data->draglastx = mx;
7136     data->draglasty = my;
7137     button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
7138     return WM_UI_HANDLER_BREAK;
7139   }
7140 
7141   /* Delete selected control points. */
7142   if (event->type == EVT_XKEY && event->val == KM_RELEASE) {
7143     BKE_curveprofile_remove_by_flag(profile, PROF_SELECT);
7144     BKE_curveprofile_update(profile, PROF_UPDATE_NONE);
7145     button_activate_state(C, but, BUTTON_STATE_EXIT);
7146     return WM_UI_HANDLER_BREAK;
7147   }
7148 
7149   /* Selecting, adding, and starting point movements. */
7150   if (data->state == BUTTON_STATE_HIGHLIGHT) {
7151     if (event->type == LEFTMOUSE && event->val == KM_PRESS) {
7152       const float m_xy[2] = {mx, my};
7153 
7154       if (event->ctrl) {
7155         float f_xy[2];
7156         BLI_rctf_transform_pt_v(&profile->view_rect, &but->rect, f_xy, m_xy);
7157 
7158         BKE_curveprofile_insert(profile, f_xy[0], f_xy[1]);
7159         BKE_curveprofile_update(profile, PROF_UPDATE_CLIP);
7160       }
7161 
7162       /* Check for selecting of a point by finding closest point in radius. */
7163       CurveProfilePoint *pts = profile->path;
7164       float dist_min_sq = square_f(U.dpi_fac * 14.0f); /* 14 pixels radius for selecting points. */
7165       int i_selected = -1;
7166       short selection_type = 0; /* For handle selection. */
7167       for (int i = 0; i < profile->path_len; i++) {
7168         float f_xy[2];
7169         BLI_rctf_transform_pt_v(&but->rect, &profile->view_rect, f_xy, &pts[i].x);
7170         float dist_sq = len_squared_v2v2(m_xy, f_xy);
7171         if (dist_sq < dist_min_sq) {
7172           i_selected = i;
7173           selection_type = PROF_SELECT;
7174           dist_min_sq = dist_sq;
7175         }
7176 
7177         /* Also select handles if the point is selected and it has the right handle type. */
7178         if (point_draw_handles(&pts[i])) {
7179           if (ELEM(profile->path[i].h1, HD_FREE, HD_ALIGN)) {
7180             BLI_rctf_transform_pt_v(&but->rect, &profile->view_rect, f_xy, pts[i].h1_loc);
7181             dist_sq = len_squared_v2v2(m_xy, f_xy);
7182             if (dist_sq < dist_min_sq) {
7183               i_selected = i;
7184               selection_type = PROF_H1_SELECT;
7185               dist_min_sq = dist_sq;
7186             }
7187           }
7188           if (ELEM(profile->path[i].h2, HD_FREE, HD_ALIGN)) {
7189             BLI_rctf_transform_pt_v(&but->rect, &profile->view_rect, f_xy, pts[i].h2_loc);
7190             dist_sq = len_squared_v2v2(m_xy, f_xy);
7191             if (dist_sq < dist_min_sq) {
7192               i_selected = i;
7193               selection_type = PROF_H2_SELECT;
7194               dist_min_sq = dist_sq;
7195             }
7196           }
7197         }
7198       }
7199 
7200       /* Add a point if the click was close to the path but not a control point or handle. */
7201       if (i_selected == -1) {
7202         float f_xy[2], f_xy_prev[2];
7203         CurveProfilePoint *table = profile->table;
7204         BLI_rctf_transform_pt_v(&but->rect, &profile->view_rect, f_xy, &table[0].x);
7205 
7206         dist_min_sq = square_f(U.dpi_fac * 8.0f); /* 8 pixel radius from each table point. */
7207 
7208         /* Loop through the path's high resolution table and find what's near the click. */
7209         for (int i = 1; i <= PROF_TABLE_LEN(profile->path_len); i++) {
7210           copy_v2_v2(f_xy_prev, f_xy);
7211           BLI_rctf_transform_pt_v(&but->rect, &profile->view_rect, f_xy, &table[i].x);
7212 
7213           if (dist_squared_to_line_segment_v2(m_xy, f_xy_prev, f_xy) < dist_min_sq) {
7214             BLI_rctf_transform_pt_v(&profile->view_rect, &but->rect, f_xy, m_xy);
7215 
7216             CurveProfilePoint *new_pt = BKE_curveprofile_insert(profile, f_xy[0], f_xy[1]);
7217             BKE_curveprofile_update(profile, PROF_UPDATE_CLIP);
7218 
7219             /* Get the index of the newly added point. */
7220             i_selected = (int)(new_pt - profile->path);
7221             BLI_assert(i_selected >= 0 && i_selected <= profile->path_len);
7222             selection_type = PROF_SELECT;
7223             break;
7224           }
7225         }
7226       }
7227 
7228       /* Change the flag for the point(s) if one was selected or added. */
7229       if (i_selected != -1) {
7230         /* Deselect all if this one is deselected, except if we hold shift. */
7231         if (event->shift) {
7232           pts[i_selected].flag ^= selection_type;
7233         }
7234         else {
7235           for (int i = 0; i < profile->path_len; i++) {
7236             // pts[i].flag &= ~(PROF_SELECT | PROF_H1_SELECT | PROF_H2_SELECT);
7237             profile->path[i].flag &= ~(PROF_SELECT | PROF_H1_SELECT | PROF_H2_SELECT);
7238           }
7239           profile->path[i_selected].flag |= selection_type;
7240         }
7241       }
7242       else {
7243         /* Move the view. */
7244         data->cancel = true;
7245       }
7246 
7247       data->dragsel = i_selected;
7248 
7249       data->dragstartx = mx;
7250       data->dragstarty = my;
7251       data->draglastx = mx;
7252       data->draglasty = my;
7253 
7254       button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
7255       return WM_UI_HANDLER_BREAK;
7256     }
7257   }
7258   else if (data->state == BUTTON_STATE_NUM_EDITING) { /* Do control point movement. */
7259     if (event->type == MOUSEMOVE) {
7260       if (mx != data->draglastx || my != data->draglasty) {
7261         if (ui_numedit_but_CURVEPROFILE(
7262                 block, but, data, mx, my, event->ctrl != 0, event->shift != 0)) {
7263           ui_numedit_apply(C, block, but, data);
7264         }
7265       }
7266     }
7267     else if (event->type == LEFTMOUSE && event->val == KM_RELEASE) {
7268       /* Finish move. */
7269       if (data->dragsel != -1) {
7270 
7271         if (data->dragchange == false) {
7272           /* Deselect all, select one. */
7273         }
7274         else {
7275           /* Remove doubles, clip after move. */
7276           BKE_curveprofile_update(profile, PROF_UPDATE_REMOVE_DOUBLES | PROF_UPDATE_CLIP);
7277         }
7278       }
7279       button_activate_state(C, but, BUTTON_STATE_EXIT);
7280     }
7281     return WM_UI_HANDLER_BREAK;
7282   }
7283 
7284   return WM_UI_HANDLER_CONTINUE;
7285 }
7286 
ui_numedit_but_HISTOGRAM(uiBut * but,uiHandleButtonData * data,int mx,int my)7287 static bool ui_numedit_but_HISTOGRAM(uiBut *but, uiHandleButtonData *data, int mx, int my)
7288 {
7289   Histogram *hist = (Histogram *)but->poin;
7290   const bool changed = true;
7291   const float dy = my - data->draglasty;
7292 
7293   /* scale histogram values (dy / 10 for better control) */
7294   const float yfac = min_ff(pow2f(hist->ymax), 1.0f) * 0.5f;
7295   hist->ymax += (dy * 0.1f) * yfac;
7296 
7297   /* 0.1 allows us to see HDR colors up to 10 */
7298   CLAMP(hist->ymax, 0.1f, 100.f);
7299 
7300   data->draglastx = mx;
7301   data->draglasty = my;
7302 
7303   return changed;
7304 }
7305 
ui_do_but_HISTOGRAM(bContext * C,uiBlock * block,uiBut * but,uiHandleButtonData * data,const wmEvent * event)7306 static int ui_do_but_HISTOGRAM(
7307     bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
7308 {
7309   int mx = event->x;
7310   int my = event->y;
7311   ui_window_to_block(data->region, block, &mx, &my);
7312 
7313   if (data->state == BUTTON_STATE_HIGHLIGHT) {
7314     if (event->type == LEFTMOUSE && event->val == KM_PRESS) {
7315       data->dragstartx = mx;
7316       data->dragstarty = my;
7317       data->draglastx = mx;
7318       data->draglasty = my;
7319       button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
7320 
7321       /* also do drag the first time */
7322       if (ui_numedit_but_HISTOGRAM(but, data, mx, my)) {
7323         ui_numedit_apply(C, block, but, data);
7324       }
7325 
7326       return WM_UI_HANDLER_BREAK;
7327     }
7328     /* XXX hardcoded keymap check.... */
7329     if (event->type == EVT_BACKSPACEKEY && event->val == KM_PRESS) {
7330       Histogram *hist = (Histogram *)but->poin;
7331       hist->ymax = 1.f;
7332 
7333       button_activate_state(C, but, BUTTON_STATE_EXIT);
7334       return WM_UI_HANDLER_BREAK;
7335     }
7336   }
7337   else if (data->state == BUTTON_STATE_NUM_EDITING) {
7338     if (event->type == EVT_ESCKEY) {
7339       if (event->val == KM_PRESS) {
7340         data->cancel = true;
7341         data->escapecancel = true;
7342         button_activate_state(C, but, BUTTON_STATE_EXIT);
7343       }
7344     }
7345     else if (event->type == MOUSEMOVE) {
7346       if (mx != data->draglastx || my != data->draglasty) {
7347         if (ui_numedit_but_HISTOGRAM(but, data, mx, my)) {
7348           ui_numedit_apply(C, block, but, data);
7349         }
7350       }
7351     }
7352     else if (event->type == LEFTMOUSE && event->val == KM_RELEASE) {
7353       button_activate_state(C, but, BUTTON_STATE_EXIT);
7354     }
7355     return WM_UI_HANDLER_BREAK;
7356   }
7357 
7358   return WM_UI_HANDLER_CONTINUE;
7359 }
7360 
ui_numedit_but_WAVEFORM(uiBut * but,uiHandleButtonData * data,int mx,int my)7361 static bool ui_numedit_but_WAVEFORM(uiBut *but, uiHandleButtonData *data, int mx, int my)
7362 {
7363   Scopes *scopes = (Scopes *)but->poin;
7364   const bool changed = true;
7365 
7366   const float dy = my - data->draglasty;
7367 
7368   /* scale waveform values */
7369   scopes->wavefrm_yfac += dy / 200.0f;
7370 
7371   CLAMP(scopes->wavefrm_yfac, 0.5f, 2.0f);
7372 
7373   data->draglastx = mx;
7374   data->draglasty = my;
7375 
7376   return changed;
7377 }
7378 
ui_do_but_WAVEFORM(bContext * C,uiBlock * block,uiBut * but,uiHandleButtonData * data,const wmEvent * event)7379 static int ui_do_but_WAVEFORM(
7380     bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
7381 {
7382   int mx = event->x;
7383   int my = event->y;
7384   ui_window_to_block(data->region, block, &mx, &my);
7385 
7386   if (data->state == BUTTON_STATE_HIGHLIGHT) {
7387     if (event->type == LEFTMOUSE && event->val == KM_PRESS) {
7388       data->dragstartx = mx;
7389       data->dragstarty = my;
7390       data->draglastx = mx;
7391       data->draglasty = my;
7392       button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
7393 
7394       /* also do drag the first time */
7395       if (ui_numedit_but_WAVEFORM(but, data, mx, my)) {
7396         ui_numedit_apply(C, block, but, data);
7397       }
7398 
7399       return WM_UI_HANDLER_BREAK;
7400     }
7401     /* XXX hardcoded keymap check.... */
7402     if (event->type == EVT_BACKSPACEKEY && event->val == KM_PRESS) {
7403       Scopes *scopes = (Scopes *)but->poin;
7404       scopes->wavefrm_yfac = 1.f;
7405 
7406       button_activate_state(C, but, BUTTON_STATE_EXIT);
7407       return WM_UI_HANDLER_BREAK;
7408     }
7409   }
7410   else if (data->state == BUTTON_STATE_NUM_EDITING) {
7411     if (event->type == EVT_ESCKEY) {
7412       if (event->val == KM_PRESS) {
7413         data->cancel = true;
7414         data->escapecancel = true;
7415         button_activate_state(C, but, BUTTON_STATE_EXIT);
7416       }
7417     }
7418     else if (event->type == MOUSEMOVE) {
7419       if (mx != data->draglastx || my != data->draglasty) {
7420         if (ui_numedit_but_WAVEFORM(but, data, mx, my)) {
7421           ui_numedit_apply(C, block, but, data);
7422         }
7423       }
7424     }
7425     else if (event->type == LEFTMOUSE && event->val == KM_RELEASE) {
7426       button_activate_state(C, but, BUTTON_STATE_EXIT);
7427     }
7428     return WM_UI_HANDLER_BREAK;
7429   }
7430 
7431   return WM_UI_HANDLER_CONTINUE;
7432 }
7433 
ui_numedit_but_TRACKPREVIEW(bContext * C,uiBut * but,uiHandleButtonData * data,int mx,int my,const bool shift)7434 static bool ui_numedit_but_TRACKPREVIEW(
7435     bContext *C, uiBut *but, uiHandleButtonData *data, int mx, int my, const bool shift)
7436 {
7437   MovieClipScopes *scopes = (MovieClipScopes *)but->poin;
7438   const bool changed = true;
7439 
7440   float dx = mx - data->draglastx;
7441   float dy = my - data->draglasty;
7442 
7443   if (shift) {
7444     dx /= 5.0f;
7445     dy /= 5.0f;
7446   }
7447 
7448   if (!scopes->track_locked) {
7449     const MovieClip *clip = CTX_data_edit_movieclip(C);
7450     const int clip_framenr = BKE_movieclip_remap_scene_to_clip_frame(clip, scopes->framenr);
7451     if (scopes->marker->framenr != clip_framenr) {
7452       scopes->marker = BKE_tracking_marker_ensure(scopes->track, clip_framenr);
7453     }
7454 
7455     scopes->marker->flag &= ~(MARKER_DISABLED | MARKER_TRACKED);
7456     scopes->marker->pos[0] += -dx * scopes->slide_scale[0] / BLI_rctf_size_x(&but->block->rect);
7457     scopes->marker->pos[1] += -dy * scopes->slide_scale[1] / BLI_rctf_size_y(&but->block->rect);
7458 
7459     WM_event_add_notifier(C, NC_MOVIECLIP | NA_EDITED, NULL);
7460   }
7461 
7462   scopes->ok = 0;
7463 
7464   data->draglastx = mx;
7465   data->draglasty = my;
7466 
7467   return changed;
7468 }
7469 
ui_do_but_TRACKPREVIEW(bContext * C,uiBlock * block,uiBut * but,uiHandleButtonData * data,const wmEvent * event)7470 static int ui_do_but_TRACKPREVIEW(
7471     bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
7472 {
7473   int mx = event->x;
7474   int my = event->y;
7475   ui_window_to_block(data->region, block, &mx, &my);
7476 
7477   if (data->state == BUTTON_STATE_HIGHLIGHT) {
7478     if (event->type == LEFTMOUSE && event->val == KM_PRESS) {
7479       data->dragstartx = mx;
7480       data->dragstarty = my;
7481       data->draglastx = mx;
7482       data->draglasty = my;
7483       button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
7484 
7485       /* also do drag the first time */
7486       if (ui_numedit_but_TRACKPREVIEW(C, but, data, mx, my, event->shift != 0)) {
7487         ui_numedit_apply(C, block, but, data);
7488       }
7489 
7490       return WM_UI_HANDLER_BREAK;
7491     }
7492   }
7493   else if (data->state == BUTTON_STATE_NUM_EDITING) {
7494     if (event->type == EVT_ESCKEY) {
7495       if (event->val == KM_PRESS) {
7496         data->cancel = true;
7497         data->escapecancel = true;
7498         button_activate_state(C, but, BUTTON_STATE_EXIT);
7499       }
7500     }
7501     else if (event->type == MOUSEMOVE) {
7502       if (mx != data->draglastx || my != data->draglasty) {
7503         if (ui_numedit_but_TRACKPREVIEW(C, but, data, mx, my, event->shift != 0)) {
7504           ui_numedit_apply(C, block, but, data);
7505         }
7506       }
7507     }
7508     else if (event->type == LEFTMOUSE && event->val == KM_RELEASE) {
7509       button_activate_state(C, but, BUTTON_STATE_EXIT);
7510     }
7511     return WM_UI_HANDLER_BREAK;
7512   }
7513 
7514   return WM_UI_HANDLER_CONTINUE;
7515 }
7516 
ui_do_button(bContext * C,uiBlock * block,uiBut * but,const wmEvent * event)7517 static int ui_do_button(bContext *C, uiBlock *block, uiBut *but, const wmEvent *event)
7518 {
7519   uiHandleButtonData *data = but->active;
7520   int retval = WM_UI_HANDLER_CONTINUE;
7521 
7522   const bool is_disabled = but->flag & UI_BUT_DISABLED;
7523 
7524   /* if but->pointype is set, but->poin should be too */
7525   BLI_assert(!but->pointype || but->poin);
7526 
7527   /* Only hard-coded stuff here, button interactions with configurable
7528    * keymaps are handled using operators (see #ED_keymap_ui). */
7529 
7530   if ((data->state == BUTTON_STATE_HIGHLIGHT) || (event->type == EVT_DROP)) {
7531 
7532     /* handle copy and paste */
7533     bool is_press_ctrl_but_no_shift = event->val == KM_PRESS && IS_EVENT_MOD(event, ctrl, oskey) &&
7534                                       !event->shift;
7535     const bool do_copy = event->type == EVT_CKEY && is_press_ctrl_but_no_shift;
7536     const bool do_paste = event->type == EVT_VKEY && is_press_ctrl_but_no_shift;
7537 
7538     /* Specific handling for listrows, we try to find their overlapping tex button. */
7539     if ((do_copy || do_paste) && but->type == UI_BTYPE_LISTROW) {
7540       uiBut *labelbut = ui_but_list_row_text_activate(C, but, data, event, BUTTON_ACTIVATE_OVER);
7541       if (labelbut) {
7542         but = labelbut;
7543         data = but->active;
7544       }
7545     }
7546 
7547     /* do copy first, because it is the only allowed operator when disabled */
7548     if (do_copy) {
7549       ui_but_copy(C, but, event->alt);
7550       return WM_UI_HANDLER_BREAK;
7551     }
7552 
7553     /* handle menu */
7554     if ((event->type == RIGHTMOUSE) && !IS_EVENT_MOD(event, shift, ctrl, alt, oskey) &&
7555         (event->val == KM_PRESS)) {
7556       /* RMB has two options now */
7557       if (ui_popup_context_menu_for_button(C, but)) {
7558         return WM_UI_HANDLER_BREAK;
7559       }
7560     }
7561 
7562     if (is_disabled) {
7563       return WM_UI_HANDLER_CONTINUE;
7564     }
7565 
7566     if (do_paste) {
7567       ui_but_paste(C, but, data, event->alt);
7568       return WM_UI_HANDLER_BREAK;
7569     }
7570 
7571     /* handle drop */
7572     if (event->type == EVT_DROP) {
7573       ui_but_drop(C, event, but, data);
7574     }
7575 
7576     if ((data->state == BUTTON_STATE_HIGHLIGHT) &&
7577         ELEM(event->type, LEFTMOUSE, EVT_BUT_OPEN, EVT_PADENTER, EVT_RETKEY) &&
7578         (event->val == KM_RELEASE) &&
7579         /* Only returns true if the event was handled. */
7580         ui_do_but_extra_operator_icon(C, but, data, event)) {
7581       return WM_UI_HANDLER_BREAK;
7582     }
7583   }
7584 
7585   if (but->flag & UI_BUT_DISABLED) {
7586     return WM_UI_HANDLER_BREAK;
7587   }
7588 
7589   switch (but->type) {
7590     case UI_BTYPE_BUT:
7591     case UI_BTYPE_DECORATOR:
7592       retval = ui_do_but_BUT(C, but, data, event);
7593       break;
7594     case UI_BTYPE_KEY_EVENT:
7595       retval = ui_do_but_KEYEVT(C, but, data, event);
7596       break;
7597     case UI_BTYPE_HOTKEY_EVENT:
7598       retval = ui_do_but_HOTKEYEVT(C, but, data, event);
7599       break;
7600     case UI_BTYPE_TAB:
7601       retval = ui_do_but_TAB(C, block, but, data, event);
7602       break;
7603     case UI_BTYPE_BUT_TOGGLE:
7604     case UI_BTYPE_TOGGLE:
7605     case UI_BTYPE_ICON_TOGGLE:
7606     case UI_BTYPE_ICON_TOGGLE_N:
7607     case UI_BTYPE_TOGGLE_N:
7608     case UI_BTYPE_CHECKBOX:
7609     case UI_BTYPE_CHECKBOX_N:
7610     case UI_BTYPE_ROW:
7611       retval = ui_do_but_TOG(C, but, data, event);
7612       break;
7613     case UI_BTYPE_SCROLL:
7614       retval = ui_do_but_SCROLL(C, block, but, data, event);
7615       break;
7616     case UI_BTYPE_GRIP:
7617       retval = ui_do_but_GRIP(C, block, but, data, event);
7618       break;
7619     case UI_BTYPE_NUM:
7620       retval = ui_do_but_NUM(C, block, but, data, event);
7621       break;
7622     case UI_BTYPE_NUM_SLIDER:
7623       retval = ui_do_but_SLI(C, block, but, data, event);
7624       break;
7625     case UI_BTYPE_LISTBOX:
7626       /* Nothing to do! */
7627       break;
7628     case UI_BTYPE_LISTROW:
7629       retval = ui_do_but_LISTROW(C, but, data, event);
7630       break;
7631     case UI_BTYPE_ROUNDBOX:
7632     case UI_BTYPE_LABEL:
7633     case UI_BTYPE_IMAGE:
7634     case UI_BTYPE_PROGRESS_BAR:
7635     case UI_BTYPE_NODE_SOCKET:
7636       retval = ui_do_but_EXIT(C, but, data, event);
7637       break;
7638     case UI_BTYPE_HISTOGRAM:
7639       retval = ui_do_but_HISTOGRAM(C, block, but, data, event);
7640       break;
7641     case UI_BTYPE_WAVEFORM:
7642       retval = ui_do_but_WAVEFORM(C, block, but, data, event);
7643       break;
7644     case UI_BTYPE_VECTORSCOPE:
7645       /* Nothing to do! */
7646       break;
7647     case UI_BTYPE_TEXT:
7648     case UI_BTYPE_SEARCH_MENU:
7649       if ((but->type == UI_BTYPE_SEARCH_MENU) && (but->flag & UI_BUT_VALUE_CLEAR)) {
7650         retval = ui_do_but_SEARCH_UNLINK(C, block, but, data, event);
7651         if (retval & WM_UI_HANDLER_BREAK) {
7652           break;
7653         }
7654       }
7655       retval = ui_do_but_TEX(C, block, but, data, event);
7656       break;
7657     case UI_BTYPE_MENU:
7658     case UI_BTYPE_POPOVER:
7659     case UI_BTYPE_BLOCK:
7660     case UI_BTYPE_PULLDOWN:
7661       retval = ui_do_but_BLOCK(C, but, data, event);
7662       break;
7663     case UI_BTYPE_BUT_MENU:
7664       retval = ui_do_but_BUT(C, but, data, event);
7665       break;
7666     case UI_BTYPE_COLOR:
7667       retval = ui_do_but_COLOR(C, but, data, event);
7668       break;
7669     case UI_BTYPE_UNITVEC:
7670       retval = ui_do_but_UNITVEC(C, block, but, data, event);
7671       break;
7672     case UI_BTYPE_COLORBAND:
7673       retval = ui_do_but_COLORBAND(C, block, but, data, event);
7674       break;
7675     case UI_BTYPE_CURVE:
7676       retval = ui_do_but_CURVE(C, block, but, data, event);
7677       break;
7678     case UI_BTYPE_CURVEPROFILE:
7679       retval = ui_do_but_CURVEPROFILE(C, block, but, data, event);
7680       break;
7681     case UI_BTYPE_HSVCUBE:
7682       retval = ui_do_but_HSVCUBE(C, block, but, data, event);
7683       break;
7684     case UI_BTYPE_HSVCIRCLE:
7685       retval = ui_do_but_HSVCIRCLE(C, block, but, data, event);
7686       break;
7687     case UI_BTYPE_TRACK_PREVIEW:
7688       retval = ui_do_but_TRACKPREVIEW(C, block, but, data, event);
7689       break;
7690 
7691       /* quiet warnings for unhandled types */
7692     case UI_BTYPE_SEPR:
7693     case UI_BTYPE_SEPR_LINE:
7694     case UI_BTYPE_SEPR_SPACER:
7695     case UI_BTYPE_EXTRA:
7696       break;
7697   }
7698 
7699 #ifdef USE_DRAG_MULTINUM
7700   if (data) {
7701     if (ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE) ||
7702         /* if we started dragging, progress on any event */
7703         (data->multi_data.init == BUTTON_MULTI_INIT_SETUP)) {
7704       if (ELEM(but->type, UI_BTYPE_NUM, UI_BTYPE_NUM_SLIDER) &&
7705           ELEM(data->state, BUTTON_STATE_TEXT_EDITING, BUTTON_STATE_NUM_EDITING)) {
7706         /* initialize! */
7707         if (data->multi_data.init == BUTTON_MULTI_INIT_UNSET) {
7708           /* --> (BUTTON_MULTI_INIT_SETUP | BUTTON_MULTI_INIT_DISABLE) */
7709 
7710           const float margin_y = DRAG_MULTINUM_THRESHOLD_DRAG_Y / sqrtf(block->aspect);
7711 
7712           /* check if we have a vertical gesture */
7713           if (len_squared_v2(data->multi_data.drag_dir) > (margin_y * margin_y)) {
7714             const float dir_nor_y[2] = {0.0, 1.0f};
7715             float dir_nor_drag[2];
7716 
7717             normalize_v2_v2(dir_nor_drag, data->multi_data.drag_dir);
7718 
7719             if (fabsf(dot_v2v2(dir_nor_drag, dir_nor_y)) > DRAG_MULTINUM_THRESHOLD_VERTICAL) {
7720               data->multi_data.init = BUTTON_MULTI_INIT_SETUP;
7721               data->multi_data.drag_lock_x = event->x;
7722             }
7723             else {
7724               data->multi_data.init = BUTTON_MULTI_INIT_DISABLE;
7725             }
7726           }
7727         }
7728         else if (data->multi_data.init == BUTTON_MULTI_INIT_SETUP) {
7729           /* --> (BUTTON_MULTI_INIT_ENABLE) */
7730           const float margin_x = DRAG_MULTINUM_THRESHOLD_DRAG_X / sqrtf(block->aspect);
7731           /* check if we're dont setting buttons */
7732           if ((data->str &&
7733                ELEM(data->state, BUTTON_STATE_TEXT_EDITING, BUTTON_STATE_NUM_EDITING)) ||
7734               ((abs(data->multi_data.drag_lock_x - event->x) > margin_x) &&
7735                /* just to be sure, check we're dragging more hoz then virt */
7736                abs(event->prevx - event->x) > abs(event->prevy - event->y))) {
7737             if (data->multi_data.has_mbuts) {
7738               ui_multibut_states_create(but, data);
7739               data->multi_data.init = BUTTON_MULTI_INIT_ENABLE;
7740             }
7741             else {
7742               data->multi_data.init = BUTTON_MULTI_INIT_DISABLE;
7743             }
7744           }
7745         }
7746 
7747         if (data->multi_data.init == BUTTON_MULTI_INIT_SETUP) {
7748           if (ui_multibut_states_tag(but, data, event)) {
7749             ED_region_tag_redraw(data->region);
7750           }
7751         }
7752       }
7753     }
7754   }
7755 #endif /* USE_DRAG_MULTINUM */
7756 
7757   return retval;
7758 }
7759 
7760 /** \} */
7761 
7762 /* -------------------------------------------------------------------- */
7763 /** \name Button Tool Tip
7764  * \{ */
7765 
ui_blocks_set_tooltips(ARegion * region,const bool enable)7766 static void ui_blocks_set_tooltips(ARegion *region, const bool enable)
7767 {
7768   if (!region) {
7769     return;
7770   }
7771 
7772   /* we disabled buttons when when they were already shown, and
7773    * re-enable them on mouse move */
7774   LISTBASE_FOREACH (uiBlock *, block, &region->uiblocks) {
7775     block->tooltipdisabled = !enable;
7776   }
7777 }
7778 
7779 /**
7780  * Recreate tooltip (use to update dynamic tips)
7781  */
UI_but_tooltip_refresh(bContext * C,uiBut * but)7782 void UI_but_tooltip_refresh(bContext *C, uiBut *but)
7783 {
7784   uiHandleButtonData *data = but->active;
7785   if (data) {
7786     bScreen *screen = WM_window_get_active_screen(data->window);
7787     if (screen->tool_tip && screen->tool_tip->region) {
7788       WM_tooltip_refresh(C, data->window);
7789     }
7790   }
7791 }
7792 
7793 /**
7794  * Removes tool-tip timer from active but
7795  * (meaning tool-tip is disabled until it's re-enabled again).
7796  */
UI_but_tooltip_timer_remove(bContext * C,uiBut * but)7797 void UI_but_tooltip_timer_remove(bContext *C, uiBut *but)
7798 {
7799   uiHandleButtonData *data = but->active;
7800   if (data) {
7801     if (data->autoopentimer) {
7802       WM_event_remove_timer(data->wm, data->window, data->autoopentimer);
7803       data->autoopentimer = NULL;
7804     }
7805 
7806     if (data->window) {
7807       WM_tooltip_clear(C, data->window);
7808     }
7809   }
7810 }
7811 
ui_but_tooltip_init(bContext * C,ARegion * region,int * pass,double * r_pass_delay,bool * r_exit_on_event)7812 static ARegion *ui_but_tooltip_init(
7813     bContext *C, ARegion *region, int *pass, double *r_pass_delay, bool *r_exit_on_event)
7814 {
7815   bool is_label = false;
7816   if (*pass == 1) {
7817     is_label = true;
7818     (*pass)--;
7819     (*r_pass_delay) = UI_TOOLTIP_DELAY - UI_TOOLTIP_DELAY_LABEL;
7820   }
7821 
7822   uiBut *but = UI_region_active_but_get(region);
7823   *r_exit_on_event = false;
7824   if (but) {
7825     return UI_tooltip_create_from_button(C, region, but, is_label);
7826   }
7827   return NULL;
7828 }
7829 
button_tooltip_timer_reset(bContext * C,uiBut * but)7830 static void button_tooltip_timer_reset(bContext *C, uiBut *but)
7831 {
7832   wmWindowManager *wm = CTX_wm_manager(C);
7833   uiHandleButtonData *data = but->active;
7834 
7835   WM_tooltip_timer_clear(C, data->window);
7836 
7837   if ((U.flag & USER_TOOLTIPS) || (data->tooltip_force)) {
7838     if (!but->block->tooltipdisabled) {
7839       if (!wm->drags.first) {
7840         const bool is_label = UI_but_has_tooltip_label(but);
7841         const double delay = is_label ? UI_TOOLTIP_DELAY_LABEL : UI_TOOLTIP_DELAY;
7842         WM_tooltip_timer_init_ex(
7843             C, data->window, data->area, data->region, ui_but_tooltip_init, delay);
7844         if (is_label) {
7845           bScreen *screen = WM_window_get_active_screen(data->window);
7846           if (screen->tool_tip) {
7847             screen->tool_tip->pass = 1;
7848           }
7849         }
7850       }
7851     }
7852   }
7853 }
7854 
7855 /** \} */
7856 
7857 /* -------------------------------------------------------------------- */
7858 /** \name Button State Handling
7859  * \{ */
7860 
button_modal_state(uiHandleButtonState state)7861 static bool button_modal_state(uiHandleButtonState state)
7862 {
7863   return ELEM(state,
7864               BUTTON_STATE_WAIT_RELEASE,
7865               BUTTON_STATE_WAIT_KEY_EVENT,
7866               BUTTON_STATE_NUM_EDITING,
7867               BUTTON_STATE_TEXT_EDITING,
7868               BUTTON_STATE_TEXT_SELECTING,
7869               BUTTON_STATE_MENU_OPEN);
7870 }
7871 
button_activate_state(bContext * C,uiBut * but,uiHandleButtonState state)7872 static void button_activate_state(bContext *C, uiBut *but, uiHandleButtonState state)
7873 {
7874   uiHandleButtonData *data = but->active;
7875   if (data->state == state) {
7876     return;
7877   }
7878 
7879   /* highlight has timers for tooltips and auto open */
7880   if (state == BUTTON_STATE_HIGHLIGHT) {
7881     but->flag &= ~UI_SELECT;
7882 
7883     button_tooltip_timer_reset(C, but);
7884 
7885     /* automatic open pulldown block timer */
7886     if (ELEM(but->type, UI_BTYPE_BLOCK, UI_BTYPE_PULLDOWN, UI_BTYPE_POPOVER) ||
7887         /* Menu button types may draw as popovers, check for this case
7888          * ignoring other kinds of menus (mainly enums). (see T66538). */
7889         ((but->type == UI_BTYPE_MENU) &&
7890          (UI_but_paneltype_get(but) || ui_but_menu_draw_as_popover(but)))) {
7891       if (data->used_mouse && !data->autoopentimer) {
7892         int time;
7893 
7894         if (but->block->auto_open == true) { /* test for toolbox */
7895           time = 1;
7896         }
7897         else if ((but->block->flag & UI_BLOCK_LOOP && but->type != UI_BTYPE_BLOCK) ||
7898                  (but->block->auto_open == true)) {
7899           time = 5 * U.menuthreshold2;
7900         }
7901         else if (U.uiflag & USER_MENUOPENAUTO) {
7902           time = 5 * U.menuthreshold1;
7903         }
7904         else {
7905           time = -1; /* do nothing */
7906         }
7907 
7908         if (time >= 0) {
7909           data->autoopentimer = WM_event_add_timer(
7910               data->wm, data->window, TIMER, 0.02 * (double)time);
7911         }
7912       }
7913     }
7914   }
7915   else {
7916     but->flag |= UI_SELECT;
7917     UI_but_tooltip_timer_remove(C, but);
7918   }
7919 
7920   /* text editing */
7921   if (state == BUTTON_STATE_TEXT_EDITING && data->state != BUTTON_STATE_TEXT_SELECTING) {
7922     ui_textedit_begin(C, but, data);
7923   }
7924   else if (data->state == BUTTON_STATE_TEXT_EDITING && state != BUTTON_STATE_TEXT_SELECTING) {
7925     ui_textedit_end(C, but, data);
7926   }
7927   else if (data->state == BUTTON_STATE_TEXT_SELECTING && state != BUTTON_STATE_TEXT_EDITING) {
7928     ui_textedit_end(C, but, data);
7929   }
7930 
7931   /* number editing */
7932   if (state == BUTTON_STATE_NUM_EDITING) {
7933     if (ui_but_is_cursor_warp(but)) {
7934       WM_cursor_grab_enable(CTX_wm_window(C), WM_CURSOR_WRAP_XY, true, NULL);
7935     }
7936     ui_numedit_begin(but, data);
7937   }
7938   else if (data->state == BUTTON_STATE_NUM_EDITING) {
7939     ui_numedit_end(but, data);
7940 
7941     if (but->flag & UI_BUT_DRIVEN) {
7942       /* Only warn when editing stepping/dragging the value.
7943        * No warnings should show for editing driver expressions though!
7944        */
7945       if (state != BUTTON_STATE_TEXT_EDITING) {
7946         WM_report(RPT_INFO,
7947                   "Can't edit driven number value, see graph editor for the driver setup.");
7948       }
7949     }
7950 
7951     if (ui_but_is_cursor_warp(but)) {
7952 
7953 #ifdef USE_CONT_MOUSE_CORRECT
7954       /* stereo3d has issues with changing cursor location so rather avoid */
7955       if (data->ungrab_mval[0] != FLT_MAX && !WM_stereo3d_enabled(data->window, false)) {
7956         int mouse_ungrab_xy[2];
7957         ui_block_to_window_fl(
7958             data->region, but->block, &data->ungrab_mval[0], &data->ungrab_mval[1]);
7959         mouse_ungrab_xy[0] = data->ungrab_mval[0];
7960         mouse_ungrab_xy[1] = data->ungrab_mval[1];
7961 
7962         WM_cursor_grab_disable(data->window, mouse_ungrab_xy);
7963       }
7964       else {
7965         WM_cursor_grab_disable(data->window, NULL);
7966       }
7967 #else
7968       WM_cursor_grab_disable(data->window, NULL);
7969 #endif
7970     }
7971   }
7972   /* menu open */
7973   if (state == BUTTON_STATE_MENU_OPEN) {
7974     ui_block_open_begin(C, but, data);
7975   }
7976   else if (data->state == BUTTON_STATE_MENU_OPEN) {
7977     ui_block_open_end(C, but, data);
7978   }
7979 
7980   /* add a short delay before exiting, to ensure there is some feedback */
7981   if (state == BUTTON_STATE_WAIT_FLASH) {
7982     data->flashtimer = WM_event_add_timer(data->wm, data->window, TIMER, BUTTON_FLASH_DELAY);
7983   }
7984   else if (data->flashtimer) {
7985     WM_event_remove_timer(data->wm, data->window, data->flashtimer);
7986     data->flashtimer = NULL;
7987   }
7988 
7989   /* add hold timer if it's used */
7990   if (state == BUTTON_STATE_WAIT_RELEASE && (but->hold_func != NULL)) {
7991     data->hold_action_timer = WM_event_add_timer(
7992         data->wm, data->window, TIMER, BUTTON_AUTO_OPEN_THRESH);
7993   }
7994   else if (data->hold_action_timer) {
7995     WM_event_remove_timer(data->wm, data->window, data->hold_action_timer);
7996     data->hold_action_timer = NULL;
7997   }
7998 
7999   /* add a blocking ui handler at the window handler for blocking, modal states
8000    * but not for popups, because we already have a window level handler*/
8001   if (!(but->block->handle && but->block->handle->popup)) {
8002     if (button_modal_state(state)) {
8003       if (!button_modal_state(data->state)) {
8004         WM_event_add_ui_handler(
8005             C, &data->window->modalhandlers, ui_handler_region_menu, NULL, data, 0);
8006       }
8007     }
8008     else {
8009       if (button_modal_state(data->state)) {
8010         /* true = postpone free */
8011         WM_event_remove_ui_handler(
8012             &data->window->modalhandlers, ui_handler_region_menu, NULL, data, true);
8013       }
8014     }
8015   }
8016 
8017   /* wait for mousemove to enable drag */
8018   if (state == BUTTON_STATE_WAIT_DRAG) {
8019     but->flag &= ~UI_SELECT;
8020   }
8021 
8022   data->state = state;
8023 
8024   if (state != BUTTON_STATE_EXIT) {
8025     /* When objects for eg. are removed, running ui_but_update() can access
8026      * the removed data - so disable update on exit. Also in case of
8027      * highlight when not in a popup menu, we remove because data used in
8028      * button below popup might have been removed by action of popup. Needs
8029      * a more reliable solution... */
8030     if (state != BUTTON_STATE_HIGHLIGHT || (but->block->flag & UI_BLOCK_LOOP)) {
8031       ui_but_update(but);
8032     }
8033   }
8034 
8035   /* redraw */
8036   ED_region_tag_redraw_no_rebuild(data->region);
8037 }
8038 
button_activate_init(bContext * C,ARegion * region,uiBut * but,uiButtonActivateType type)8039 static void button_activate_init(bContext *C,
8040                                  ARegion *region,
8041                                  uiBut *but,
8042                                  uiButtonActivateType type)
8043 {
8044   /* Only ever one active button! */
8045   BLI_assert(ui_region_find_active_but(region) == NULL);
8046 
8047   /* setup struct */
8048   uiHandleButtonData *data = MEM_callocN(sizeof(uiHandleButtonData), "uiHandleButtonData");
8049   data->wm = CTX_wm_manager(C);
8050   data->window = CTX_wm_window(C);
8051   data->area = CTX_wm_area(C);
8052   BLI_assert(region != NULL);
8053   data->region = region;
8054 
8055 #ifdef USE_CONT_MOUSE_CORRECT
8056   copy_v2_fl(data->ungrab_mval, FLT_MAX);
8057 #endif
8058 
8059   if (ELEM(but->type, UI_BTYPE_CURVE, UI_BTYPE_CURVEPROFILE, UI_BTYPE_SEARCH_MENU)) {
8060     /* XXX curve is temp */
8061   }
8062   else {
8063     if ((but->flag & UI_BUT_UPDATE_DELAY) == 0) {
8064       data->interactive = true;
8065     }
8066   }
8067 
8068   data->state = BUTTON_STATE_INIT;
8069 
8070   /* activate button */
8071   but->flag |= UI_ACTIVE;
8072 
8073   but->active = data;
8074 
8075   /* we disable auto_open in the block after a threshold, because we still
8076    * want to allow auto opening adjacent menus even if no button is activated
8077    * in between going over to the other button, but only for a short while */
8078   if (type == BUTTON_ACTIVATE_OVER && but->block->auto_open == true) {
8079     if (but->block->auto_open_last + BUTTON_AUTO_OPEN_THRESH < PIL_check_seconds_timer()) {
8080       but->block->auto_open = false;
8081     }
8082   }
8083 
8084   if (type == BUTTON_ACTIVATE_OVER) {
8085     data->used_mouse = true;
8086   }
8087   button_activate_state(C, but, BUTTON_STATE_HIGHLIGHT);
8088 
8089   /* activate right away */
8090   if (but->flag & UI_BUT_IMMEDIATE) {
8091     if (but->type == UI_BTYPE_HOTKEY_EVENT) {
8092       button_activate_state(C, but, BUTTON_STATE_WAIT_KEY_EVENT);
8093     }
8094     /* .. more to be added here */
8095   }
8096 
8097   if (type == BUTTON_ACTIVATE_OPEN) {
8098     button_activate_state(C, but, BUTTON_STATE_MENU_OPEN);
8099 
8100     /* activate first button in submenu */
8101     if (data->menu && data->menu->region) {
8102       ARegion *subar = data->menu->region;
8103       uiBlock *subblock = subar->uiblocks.first;
8104       uiBut *subbut;
8105 
8106       if (subblock) {
8107         subbut = ui_but_first(subblock);
8108 
8109         if (subbut) {
8110           ui_handle_button_activate(C, subar, subbut, BUTTON_ACTIVATE);
8111         }
8112       }
8113     }
8114   }
8115   else if (type == BUTTON_ACTIVATE_TEXT_EDITING) {
8116     button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING);
8117   }
8118   else if (type == BUTTON_ACTIVATE_APPLY) {
8119     button_activate_state(C, but, BUTTON_STATE_WAIT_FLASH);
8120   }
8121 
8122   if (but->type == UI_BTYPE_GRIP) {
8123     const bool horizontal = (BLI_rctf_size_x(&but->rect) < BLI_rctf_size_y(&but->rect));
8124     WM_cursor_modal_set(data->window, horizontal ? WM_CURSOR_X_MOVE : WM_CURSOR_Y_MOVE);
8125   }
8126   else if (but->type == UI_BTYPE_NUM) {
8127     ui_numedit_set_active(but);
8128   }
8129 
8130   if (UI_but_has_tooltip_label(but)) {
8131     /* Show a label for this button. */
8132     bScreen *screen = WM_window_get_active_screen(data->window);
8133     if ((PIL_check_seconds_timer() - WM_tooltip_time_closed()) < 0.1) {
8134       WM_tooltip_immediate_init(C, CTX_wm_window(C), data->area, region, ui_but_tooltip_init);
8135       if (screen->tool_tip) {
8136         screen->tool_tip->pass = 1;
8137       }
8138     }
8139   }
8140 }
8141 
button_activate_exit(bContext * C,uiBut * but,uiHandleButtonData * data,const bool mousemove,const bool onfree)8142 static void button_activate_exit(
8143     bContext *C, uiBut *but, uiHandleButtonData *data, const bool mousemove, const bool onfree)
8144 {
8145   wmWindow *win = data->window;
8146   uiBlock *block = but->block;
8147 
8148   if (but->type == UI_BTYPE_GRIP) {
8149     WM_cursor_modal_restore(win);
8150   }
8151 
8152   /* ensure we are in the exit state */
8153   if (data->state != BUTTON_STATE_EXIT) {
8154     button_activate_state(C, but, BUTTON_STATE_EXIT);
8155   }
8156 
8157   /* apply the button action or value */
8158   if (!onfree) {
8159     ui_apply_but(C, block, but, data, false);
8160   }
8161 
8162 #ifdef USE_DRAG_MULTINUM
8163   if (data->multi_data.has_mbuts) {
8164     LISTBASE_FOREACH (uiBut *, bt, &block->buttons) {
8165       if (bt->flag & UI_BUT_DRAG_MULTI) {
8166         bt->flag &= ~UI_BUT_DRAG_MULTI;
8167 
8168         if (!data->cancel) {
8169           ui_apply_but_autokey(C, bt);
8170         }
8171       }
8172     }
8173 
8174     ui_multibut_free(data, block);
8175   }
8176 #endif
8177 
8178   /* if this button is in a menu, this will set the button return
8179    * value to the button value and the menu return value to ok, the
8180    * menu return value will be picked up and the menu will close */
8181   if (block->handle && !(block->flag & UI_BLOCK_KEEP_OPEN)) {
8182     if (!data->cancel || data->escapecancel) {
8183       uiPopupBlockHandle *menu;
8184 
8185       menu = block->handle;
8186       menu->butretval = data->retval;
8187       menu->menuretval = (data->cancel) ? UI_RETURN_CANCEL : UI_RETURN_OK;
8188     }
8189   }
8190 
8191   if (!onfree && !data->cancel) {
8192     /* autokey & undo push */
8193     ui_apply_but_undo(but);
8194     ui_apply_but_autokey(C, but);
8195 
8196 #ifdef USE_ALLSELECT
8197     {
8198       /* only RNA from this button is used */
8199       uiBut but_temp = *but;
8200       uiSelectContextStore *selctx_data = &data->select_others;
8201       for (int i = 0; i < selctx_data->elems_len; i++) {
8202         uiSelectContextElem *other = &selctx_data->elems[i];
8203         but_temp.rnapoin = other->ptr;
8204         ui_apply_but_autokey(C, &but_temp);
8205       }
8206     }
8207 #endif
8208 
8209     /* popup menu memory */
8210     if (block->flag & UI_BLOCK_POPUP_MEMORY) {
8211       ui_popup_menu_memory_set(block, but);
8212     }
8213 
8214     if (U.runtime.is_dirty == false) {
8215       ui_but_update_preferences_dirty(but);
8216     }
8217   }
8218 
8219   /* disable tooltips until mousemove + last active flag */
8220   LISTBASE_FOREACH (uiBlock *, block_iter, &data->region->uiblocks) {
8221     LISTBASE_FOREACH (uiBut *, bt, &block_iter->buttons) {
8222       bt->flag &= ~UI_BUT_LAST_ACTIVE;
8223     }
8224 
8225     block_iter->tooltipdisabled = 1;
8226   }
8227 
8228   ui_blocks_set_tooltips(data->region, false);
8229 
8230   /* clean up */
8231   if (data->str) {
8232     MEM_freeN(data->str);
8233   }
8234   if (data->origstr) {
8235     MEM_freeN(data->origstr);
8236   }
8237 
8238 #ifdef USE_ALLSELECT
8239   ui_selectcontext_end(but, &data->select_others);
8240 #endif
8241 
8242   if (data->changed_cursor) {
8243     WM_cursor_modal_restore(data->window);
8244   }
8245 
8246   /* redraw and refresh (for popups) */
8247   ED_region_tag_redraw_no_rebuild(data->region);
8248   ED_region_tag_refresh_ui(data->region);
8249 
8250   /* clean up button */
8251   if (but->active) {
8252     MEM_freeN(but->active);
8253     but->active = NULL;
8254   }
8255 
8256   but->flag &= ~(UI_ACTIVE | UI_SELECT);
8257   but->flag |= UI_BUT_LAST_ACTIVE;
8258   if (!onfree) {
8259     ui_but_update(but);
8260   }
8261 
8262   /* adds empty mousemove in queue for re-init handler, in case mouse is
8263    * still over a button. We cannot just check for this ourselves because
8264    * at this point the mouse may be over a button in another region */
8265   if (mousemove) {
8266     WM_event_add_mousemove(CTX_wm_window(C));
8267   }
8268 }
8269 
ui_but_active_free(const bContext * C,uiBut * but)8270 void ui_but_active_free(const bContext *C, uiBut *but)
8271 {
8272   /* this gets called when the button somehow disappears while it is still
8273    * active, this is bad for user interaction, but we need to handle this
8274    * case cleanly anyway in case it happens */
8275   if (but->active) {
8276     uiHandleButtonData *data = but->active;
8277     data->cancel = true;
8278     button_activate_exit((bContext *)C, but, data, false, true);
8279   }
8280 }
8281 
8282 /* returns the active button with an optional checking function */
ui_context_button_active(ARegion * region,bool (* but_check_cb)(uiBut *))8283 static uiBut *ui_context_button_active(ARegion *region, bool (*but_check_cb)(uiBut *))
8284 {
8285   uiBut *but_found = NULL;
8286 
8287   while (region) {
8288     uiBut *activebut = NULL;
8289 
8290     /* find active button */
8291     LISTBASE_FOREACH (uiBlock *, block, &region->uiblocks) {
8292       LISTBASE_FOREACH (uiBut *, but, &block->buttons) {
8293         if (but->active) {
8294           activebut = but;
8295         }
8296         else if (!activebut && (but->flag & UI_BUT_LAST_ACTIVE)) {
8297           activebut = but;
8298         }
8299       }
8300     }
8301 
8302     if (activebut && (but_check_cb == NULL || but_check_cb(activebut))) {
8303       uiHandleButtonData *data = activebut->active;
8304 
8305       but_found = activebut;
8306 
8307       /* recurse into opened menu, like colorpicker case */
8308       if (data && data->menu && (region != data->menu->region)) {
8309         region = data->menu->region;
8310       }
8311       else {
8312         return but_found;
8313       }
8314     }
8315     else {
8316       /* no active button */
8317       return but_found;
8318     }
8319   }
8320 
8321   return but_found;
8322 }
8323 
ui_context_rna_button_active_test(uiBut * but)8324 static bool ui_context_rna_button_active_test(uiBut *but)
8325 {
8326   return (but->rnapoin.data != NULL);
8327 }
ui_context_rna_button_active(const bContext * C)8328 static uiBut *ui_context_rna_button_active(const bContext *C)
8329 {
8330   return ui_context_button_active(CTX_wm_region(C), ui_context_rna_button_active_test);
8331 }
8332 
UI_context_active_but_get(const bContext * C)8333 uiBut *UI_context_active_but_get(const bContext *C)
8334 {
8335   return ui_context_button_active(CTX_wm_region(C), NULL);
8336 }
8337 
8338 /*
8339  * Version of #UI_context_active_get() that uses the result of #CTX_wm_menu()
8340  * if set. Does not traverse into parent menus, which may be wanted in some
8341  * cases.
8342  */
UI_context_active_but_get_respect_menu(const bContext * C)8343 uiBut *UI_context_active_but_get_respect_menu(const bContext *C)
8344 {
8345   ARegion *ar_menu = CTX_wm_menu(C);
8346   return ui_context_button_active(ar_menu ? ar_menu : CTX_wm_region(C), NULL);
8347 }
8348 
UI_region_active_but_get(ARegion * region)8349 uiBut *UI_region_active_but_get(ARegion *region)
8350 {
8351   return ui_context_button_active(region, NULL);
8352 }
8353 
UI_region_but_find_rect_over(const ARegion * region,const rcti * rect_px)8354 uiBut *UI_region_but_find_rect_over(const ARegion *region, const rcti *rect_px)
8355 {
8356   return ui_but_find_rect_over(region, rect_px);
8357 }
8358 
UI_region_block_find_mouse_over(const struct ARegion * region,const int xy[2],bool only_clip)8359 uiBlock *UI_region_block_find_mouse_over(const struct ARegion *region,
8360                                          const int xy[2],
8361                                          bool only_clip)
8362 {
8363   return ui_block_find_mouse_over_ex(region, xy[0], xy[1], only_clip);
8364 }
8365 
8366 /**
8367  * Version of #UI_context_active_but_get that also returns RNA property info.
8368  * Helper function for insert keyframe, reset to default, etc operators.
8369  *
8370  * \return active button, NULL if none found or if it doesn't contain valid RNA data.
8371  */
UI_context_active_but_prop_get(const bContext * C,struct PointerRNA * r_ptr,struct PropertyRNA ** r_prop,int * r_index)8372 uiBut *UI_context_active_but_prop_get(const bContext *C,
8373                                       struct PointerRNA *r_ptr,
8374                                       struct PropertyRNA **r_prop,
8375                                       int *r_index)
8376 {
8377   uiBut *activebut = ui_context_rna_button_active(C);
8378 
8379   if (activebut && activebut->rnapoin.data) {
8380     *r_ptr = activebut->rnapoin;
8381     *r_prop = activebut->rnaprop;
8382     *r_index = activebut->rnaindex;
8383   }
8384   else {
8385     memset(r_ptr, 0, sizeof(*r_ptr));
8386     *r_prop = NULL;
8387     *r_index = 0;
8388   }
8389 
8390   return activebut;
8391 }
8392 
UI_context_active_but_prop_handle(bContext * C)8393 void UI_context_active_but_prop_handle(bContext *C)
8394 {
8395   uiBut *activebut = ui_context_rna_button_active(C);
8396   if (activebut) {
8397     /* TODO, look into a better way to handle the button change
8398      * currently this is mainly so reset defaults works for the
8399      * operator redo panel - campbell */
8400     uiBlock *block = activebut->block;
8401     if (block->handle_func) {
8402       block->handle_func(C, block->handle_func_arg, activebut->retval);
8403     }
8404   }
8405 }
8406 
UI_context_active_but_clear(bContext * C,wmWindow * win,ARegion * region)8407 void UI_context_active_but_clear(bContext *C, wmWindow *win, ARegion *region)
8408 {
8409   wm_event_handler_ui_cancel_ex(C, win, region, false);
8410 }
8411 
UI_context_active_operator_get(const struct bContext * C)8412 wmOperator *UI_context_active_operator_get(const struct bContext *C)
8413 {
8414   ARegion *region_ctx = CTX_wm_region(C);
8415 
8416   /* background mode */
8417   if (region_ctx == NULL) {
8418     return NULL;
8419   }
8420 
8421   /* scan active regions ui */
8422   LISTBASE_FOREACH (uiBlock *, block, &region_ctx->uiblocks) {
8423     if (block->ui_operator) {
8424       return block->ui_operator;
8425     }
8426   }
8427 
8428   /* scan popups */
8429   {
8430     bScreen *screen = CTX_wm_screen(C);
8431 
8432     LISTBASE_FOREACH (ARegion *, region, &screen->regionbase) {
8433       if (region == region_ctx) {
8434         continue;
8435       }
8436       LISTBASE_FOREACH (uiBlock *, block, &region->uiblocks) {
8437         if (block->ui_operator) {
8438           return block->ui_operator;
8439         }
8440       }
8441     }
8442   }
8443 
8444   return NULL;
8445 }
8446 
8447 /* helper function for insert keyframe, reset to default, etc operators */
UI_context_update_anim_flag(const bContext * C)8448 void UI_context_update_anim_flag(const bContext *C)
8449 {
8450   Scene *scene = CTX_data_scene(C);
8451   ARegion *region = CTX_wm_region(C);
8452   struct Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
8453   const AnimationEvalContext anim_eval_context = BKE_animsys_eval_context_construct(
8454       depsgraph, (scene) ? scene->r.cfra : 0.0f);
8455 
8456   while (region) {
8457     /* find active button */
8458     uiBut *activebut = NULL;
8459 
8460     LISTBASE_FOREACH (uiBlock *, block, &region->uiblocks) {
8461       LISTBASE_FOREACH (uiBut *, but, &block->buttons) {
8462         ui_but_anim_flag(but, &anim_eval_context);
8463         ui_but_override_flag(CTX_data_main(C), but);
8464         if (UI_but_is_decorator(but)) {
8465           ui_but_anim_decorate_update_from_flag((uiButDecorator *)but);
8466         }
8467 
8468         ED_region_tag_redraw(region);
8469 
8470         if (but->active) {
8471           activebut = but;
8472         }
8473         else if (!activebut && (but->flag & UI_BUT_LAST_ACTIVE)) {
8474           activebut = but;
8475         }
8476       }
8477     }
8478 
8479     if (activebut) {
8480       /* always recurse into opened menu, so all buttons update (like colorpicker) */
8481       uiHandleButtonData *data = activebut->active;
8482       if (data && data->menu) {
8483         region = data->menu->region;
8484       }
8485       else {
8486         return;
8487       }
8488     }
8489     else {
8490       /* no active button */
8491       return;
8492     }
8493   }
8494 }
8495 
8496 /** \} */
8497 
8498 /* -------------------------------------------------------------------- */
8499 /** \name Button Activation Handling
8500  * \{ */
8501 
ui_but_find_open_event(ARegion * region,const wmEvent * event)8502 static uiBut *ui_but_find_open_event(ARegion *region, const wmEvent *event)
8503 {
8504   LISTBASE_FOREACH (uiBlock *, block, &region->uiblocks) {
8505     LISTBASE_FOREACH (uiBut *, but, &block->buttons) {
8506       if (but == event->customdata) {
8507         return but;
8508       }
8509     }
8510   }
8511   return NULL;
8512 }
8513 
ui_handle_button_over(bContext * C,const wmEvent * event,ARegion * region)8514 static int ui_handle_button_over(bContext *C, const wmEvent *event, ARegion *region)
8515 {
8516   if (event->type == MOUSEMOVE) {
8517     uiBut *but = ui_but_find_mouse_over(region, event);
8518     if (but) {
8519       button_activate_init(C, region, but, BUTTON_ACTIVATE_OVER);
8520 
8521       if (event->alt && but->active) {
8522         /* display tooltips if holding alt on mouseover when tooltips are off in prefs */
8523         but->active->tooltip_force = true;
8524       }
8525     }
8526   }
8527   else if (event->type == EVT_BUT_OPEN) {
8528     uiBut *but = ui_but_find_open_event(region, event);
8529     if (but) {
8530       button_activate_init(C, region, but, BUTTON_ACTIVATE_OVER);
8531       ui_do_button(C, but->block, but, event);
8532     }
8533   }
8534 
8535   return WM_UI_HANDLER_CONTINUE;
8536 }
8537 
8538 /**
8539  * Exported to interface.c: #UI_but_active_only()
8540  * \note The region is only for the button.
8541  * The context needs to be set by the caller.
8542  */
ui_but_activate_event(bContext * C,ARegion * region,uiBut * but)8543 void ui_but_activate_event(bContext *C, ARegion *region, uiBut *but)
8544 {
8545   wmWindow *win = CTX_wm_window(C);
8546 
8547   button_activate_init(C, region, but, BUTTON_ACTIVATE_OVER);
8548 
8549   wmEvent event;
8550   wm_event_init_from_window(win, &event);
8551   event.type = EVT_BUT_OPEN;
8552   event.val = KM_PRESS;
8553   event.is_repeat = false;
8554   event.customdata = but;
8555   event.customdatafree = false;
8556 
8557   ui_do_button(C, but->block, but, &event);
8558 }
8559 
8560 /**
8561  * Simulate moving the mouse over a button (or navigating to it with arrow keys).
8562  *
8563  * exported so menus can start with a highlighted button,
8564  * even if the mouse isnt over it
8565  */
ui_but_activate_over(bContext * C,ARegion * region,uiBut * but)8566 void ui_but_activate_over(bContext *C, ARegion *region, uiBut *but)
8567 {
8568   button_activate_init(C, region, but, BUTTON_ACTIVATE_OVER);
8569 }
8570 
ui_but_execute_begin(struct bContext * UNUSED (C),struct ARegion * region,uiBut * but,void ** active_back)8571 void ui_but_execute_begin(struct bContext *UNUSED(C),
8572                           struct ARegion *region,
8573                           uiBut *but,
8574                           void **active_back)
8575 {
8576   BLI_assert(region != NULL);
8577   BLI_assert(BLI_findindex(&region->uiblocks, but->block) != -1);
8578   /* note: ideally we would not have to change 'but->active' however
8579    * some functions we call don't use data (as they should be doing) */
8580   uiHandleButtonData *data;
8581   *active_back = but->active;
8582   data = MEM_callocN(sizeof(uiHandleButtonData), "uiHandleButtonData_Fake");
8583   but->active = data;
8584   BLI_assert(region != NULL);
8585   data->region = region;
8586 }
8587 
ui_but_execute_end(struct bContext * C,struct ARegion * UNUSED (region),uiBut * but,void * active_back)8588 void ui_but_execute_end(struct bContext *C,
8589                         struct ARegion *UNUSED(region),
8590                         uiBut *but,
8591                         void *active_back)
8592 {
8593   ui_apply_but(C, but->block, but, but->active, true);
8594 
8595   if ((but->flag & UI_BUT_DRAG_MULTI) == 0) {
8596     ui_apply_but_autokey(C, but);
8597   }
8598   /* use onfree event so undo is handled by caller and apply is already done above */
8599   button_activate_exit((bContext *)C, but, but->active, false, true);
8600   but->active = active_back;
8601 }
8602 
ui_handle_button_activate(bContext * C,ARegion * region,uiBut * but,uiButtonActivateType type)8603 static void ui_handle_button_activate(bContext *C,
8604                                       ARegion *region,
8605                                       uiBut *but,
8606                                       uiButtonActivateType type)
8607 {
8608   uiBut *oldbut = ui_region_find_active_but(region);
8609   if (oldbut) {
8610     uiHandleButtonData *data = oldbut->active;
8611     data->cancel = true;
8612     button_activate_exit(C, oldbut, data, false, false);
8613   }
8614 
8615   button_activate_init(C, region, but, type);
8616 }
8617 
8618 /**
8619  * Use for key accelerator or default key to activate the button even if its not active.
8620  */
ui_handle_button_activate_by_type(bContext * C,ARegion * region,uiBut * but)8621 static bool ui_handle_button_activate_by_type(bContext *C, ARegion *region, uiBut *but)
8622 {
8623   if (but->type == UI_BTYPE_BUT_MENU) {
8624     /* mainly for operator buttons */
8625     ui_handle_button_activate(C, region, but, BUTTON_ACTIVATE_APPLY);
8626   }
8627   else if (ELEM(but->type, UI_BTYPE_BLOCK, UI_BTYPE_PULLDOWN)) {
8628     /* open sub-menus (like right arrow key) */
8629     ui_handle_button_activate(C, region, but, BUTTON_ACTIVATE_OPEN);
8630   }
8631   else if (but->type == UI_BTYPE_MENU) {
8632     /* activate menu items */
8633     ui_handle_button_activate(C, region, but, BUTTON_ACTIVATE);
8634   }
8635   else {
8636 #ifdef DEBUG
8637     printf("%s: error, unhandled type: %u\n", __func__, but->type);
8638 #endif
8639     return false;
8640   }
8641   return true;
8642 }
8643 
8644 /** \} */
8645 
8646 /* -------------------------------------------------------------------- */
8647 /** \name Handle Events for Activated Buttons
8648  * \{ */
8649 
ui_button_value_default(uiBut * but,double * r_value)8650 static bool ui_button_value_default(uiBut *but, double *r_value)
8651 {
8652   if (but->rnaprop != NULL && ui_but_is_rna_valid(but)) {
8653     const int type = RNA_property_type(but->rnaprop);
8654     if (ELEM(type, PROP_FLOAT, PROP_INT)) {
8655       double default_value;
8656       switch (type) {
8657         case PROP_INT:
8658           if (RNA_property_array_check(but->rnaprop)) {
8659             default_value = (double)RNA_property_int_get_default_index(
8660                 &but->rnapoin, but->rnaprop, but->rnaindex);
8661           }
8662           else {
8663             default_value = (double)RNA_property_int_get_default(&but->rnapoin, but->rnaprop);
8664           }
8665           break;
8666         case PROP_FLOAT:
8667           if (RNA_property_array_check(but->rnaprop)) {
8668             default_value = (double)RNA_property_float_get_default_index(
8669                 &but->rnapoin, but->rnaprop, but->rnaindex);
8670           }
8671           else {
8672             default_value = (double)RNA_property_float_get_default(&but->rnapoin, but->rnaprop);
8673           }
8674           break;
8675       }
8676       *r_value = default_value;
8677       return true;
8678     }
8679   }
8680   return false;
8681 }
8682 
ui_handle_button_event(bContext * C,const wmEvent * event,uiBut * but)8683 static int ui_handle_button_event(bContext *C, const wmEvent *event, uiBut *but)
8684 {
8685   uiHandleButtonData *data = but->active;
8686   const uiHandleButtonState state_orig = data->state;
8687 
8688   uiBlock *block = but->block;
8689   ARegion *region = data->region;
8690 
8691   int retval = WM_UI_HANDLER_CONTINUE;
8692 
8693   if (data->state == BUTTON_STATE_HIGHLIGHT) {
8694     switch (event->type) {
8695       case WINDEACTIVATE:
8696       case EVT_BUT_CANCEL:
8697         data->cancel = true;
8698         button_activate_state(C, but, BUTTON_STATE_EXIT);
8699         break;
8700 #ifdef USE_UI_POPOVER_ONCE
8701       case LEFTMOUSE: {
8702         if (event->val == KM_RELEASE) {
8703           if (block->flag & UI_BLOCK_POPOVER_ONCE) {
8704             if (!(but->flag & UI_BUT_DISABLED)) {
8705               if (ui_but_is_popover_once_compat(but)) {
8706                 data->cancel = false;
8707                 button_activate_state(C, but, BUTTON_STATE_EXIT);
8708                 retval = WM_UI_HANDLER_BREAK;
8709                 /* Cancel because this `but` handles all events and we don't want
8710                  * the parent button's update function to do anything.
8711                  *
8712                  * Causes issues with buttons defined by #uiItemFullR_with_popover. */
8713                 block->handle->menuretval = UI_RETURN_CANCEL;
8714               }
8715               else if (ui_but_is_editable_as_text(but)) {
8716                 ui_handle_button_activate(C, region, but, BUTTON_ACTIVATE_TEXT_EDITING);
8717                 retval = WM_UI_HANDLER_BREAK;
8718               }
8719             }
8720           }
8721         }
8722         break;
8723       }
8724 #endif
8725       case MOUSEMOVE: {
8726         uiBut *but_other = ui_but_find_mouse_over(region, event);
8727         bool exit = false;
8728 
8729         /* always deactivate button for pie menus,
8730          * else moving to blank space will leave activated */
8731         if ((!ui_block_is_menu(block) || ui_block_is_pie_menu(block)) &&
8732             !ui_but_contains_point_px(but, region, event->x, event->y)) {
8733           exit = true;
8734         }
8735         else if (but_other && ui_but_is_editable(but_other) && (but_other != but)) {
8736           exit = true;
8737         }
8738 
8739         if (exit) {
8740           data->cancel = true;
8741           button_activate_state(C, but, BUTTON_STATE_EXIT);
8742         }
8743         else if (event->x != event->prevx || event->y != event->prevy) {
8744           /* re-enable tooltip on mouse move */
8745           ui_blocks_set_tooltips(region, true);
8746           button_tooltip_timer_reset(C, but);
8747         }
8748 
8749         /* Update extra icons states. */
8750         ui_do_but_extra_operator_icons_mousemove(but, data, event);
8751 
8752         break;
8753       }
8754       case TIMER: {
8755         /* Handle menu auto open timer. */
8756         if (event->customdata == data->autoopentimer) {
8757           WM_event_remove_timer(data->wm, data->window, data->autoopentimer);
8758           data->autoopentimer = NULL;
8759 
8760           if (ui_but_contains_point_px(but, region, event->x, event->y) || but->active) {
8761             button_activate_state(C, but, BUTTON_STATE_MENU_OPEN);
8762           }
8763         }
8764 
8765         break;
8766       }
8767       /* XXX hardcoded keymap check... but anyway,
8768        * while view changes, tooltips should be removed */
8769       case WHEELUPMOUSE:
8770       case WHEELDOWNMOUSE:
8771       case MIDDLEMOUSE:
8772       case MOUSEPAN:
8773         UI_but_tooltip_timer_remove(C, but);
8774         ATTR_FALLTHROUGH;
8775       default:
8776         break;
8777     }
8778 
8779     /* handle button type specific events */
8780     retval = ui_do_button(C, block, but, event);
8781   }
8782   else if (data->state == BUTTON_STATE_WAIT_RELEASE) {
8783     switch (event->type) {
8784       case WINDEACTIVATE:
8785         data->cancel = true;
8786         button_activate_state(C, but, BUTTON_STATE_EXIT);
8787         break;
8788 
8789       case TIMER: {
8790         if (event->customdata == data->hold_action_timer) {
8791           if (true) {
8792             data->cancel = true;
8793             button_activate_state(C, but, BUTTON_STATE_EXIT);
8794           }
8795           else {
8796             /* Do this so we can still mouse-up, closing the menu and running the button.
8797              * This is nice to support but there are times when the button gets left pressed.
8798              * Keep disabled for now. */
8799             WM_event_remove_timer(data->wm, data->window, data->hold_action_timer);
8800             data->hold_action_timer = NULL;
8801           }
8802           retval = WM_UI_HANDLER_CONTINUE;
8803           but->hold_func(C, data->region, but);
8804         }
8805         break;
8806       }
8807       case MOUSEMOVE: {
8808         /* deselect the button when moving the mouse away */
8809         /* also de-activate for buttons that only show highlights */
8810         if (ui_but_contains_point_px(but, region, event->x, event->y)) {
8811 
8812           /* Drag on a hold button (used in the toolbar) now opens it immediately. */
8813           if (data->hold_action_timer) {
8814             if (but->flag & UI_SELECT) {
8815               if (len_manhattan_v2v2_int(&event->x, &event->prevx) <=
8816                   WM_EVENT_CURSOR_MOTION_THRESHOLD) {
8817                 /* pass */
8818               }
8819               else {
8820                 WM_event_remove_timer(data->wm, data->window, data->hold_action_timer);
8821                 data->hold_action_timer = WM_event_add_timer(data->wm, data->window, TIMER, 0.0f);
8822               }
8823             }
8824           }
8825 
8826           if (!(but->flag & UI_SELECT)) {
8827             but->flag |= (UI_SELECT | UI_ACTIVE);
8828             data->cancel = false;
8829             ED_region_tag_redraw_no_rebuild(data->region);
8830           }
8831         }
8832         else {
8833           if (but->flag & UI_SELECT) {
8834             but->flag &= ~(UI_SELECT | UI_ACTIVE);
8835             data->cancel = true;
8836             ED_region_tag_redraw_no_rebuild(data->region);
8837           }
8838         }
8839         break;
8840       }
8841       default:
8842         /* otherwise catch mouse release event */
8843         ui_do_button(C, block, but, event);
8844         break;
8845     }
8846 
8847     retval = WM_UI_HANDLER_BREAK;
8848   }
8849   else if (data->state == BUTTON_STATE_WAIT_FLASH) {
8850     switch (event->type) {
8851       case TIMER: {
8852         if (event->customdata == data->flashtimer) {
8853           button_activate_state(C, but, BUTTON_STATE_EXIT);
8854         }
8855         break;
8856       }
8857     }
8858 
8859     retval = WM_UI_HANDLER_CONTINUE;
8860   }
8861   else if (data->state == BUTTON_STATE_MENU_OPEN) {
8862     /* check for exit because of mouse-over another button */
8863     switch (event->type) {
8864       case MOUSEMOVE: {
8865         uiBut *bt;
8866 
8867         if (data->menu && data->menu->region) {
8868           if (ui_region_contains_point_px(data->menu->region, event->x, event->y)) {
8869             break;
8870           }
8871         }
8872 
8873         bt = ui_but_find_mouse_over(region, event);
8874 
8875         if (bt && bt->active != data) {
8876           if (but->type != UI_BTYPE_COLOR) { /* exception */
8877             data->cancel = true;
8878           }
8879           button_activate_state(C, but, BUTTON_STATE_EXIT);
8880         }
8881         break;
8882       }
8883       case RIGHTMOUSE: {
8884         if (event->val == KM_PRESS) {
8885           uiBut *bt = ui_but_find_mouse_over(region, event);
8886           if (bt && bt->active == data) {
8887             button_activate_state(C, bt, BUTTON_STATE_HIGHLIGHT);
8888           }
8889         }
8890         break;
8891       }
8892     }
8893 
8894     ui_do_button(C, block, but, event);
8895     retval = WM_UI_HANDLER_CONTINUE;
8896   }
8897   else {
8898     retval = ui_do_button(C, block, but, event);
8899     // retval = WM_UI_HANDLER_BREAK; XXX why ?
8900   }
8901 
8902   /* may have been re-allocated above (eyedropper for eg) */
8903   data = but->active;
8904   if (data && data->state == BUTTON_STATE_EXIT) {
8905     uiBut *post_but = data->postbut;
8906     const uiButtonActivateType post_type = data->posttype;
8907 
8908     /* Reset the button value when empty text is typed. */
8909     if ((data->cancel == false) && (data->str != NULL) && (data->str[0] == '\0') &&
8910         (but->rnaprop && ELEM(RNA_property_type(but->rnaprop), PROP_FLOAT, PROP_INT))) {
8911       MEM_SAFE_FREE(data->str);
8912       ui_button_value_default(but, &data->value);
8913 
8914 #ifdef USE_DRAG_MULTINUM
8915       if (data->multi_data.mbuts) {
8916         for (LinkNode *l = data->multi_data.mbuts; l; l = l->next) {
8917           uiButMultiState *state = l->link;
8918           uiBut *but_iter = state->but;
8919           double default_value;
8920 
8921           if (ui_button_value_default(but_iter, &default_value)) {
8922             ui_but_value_set(but_iter, default_value);
8923           }
8924         }
8925       }
8926       data->multi_data.skip = true;
8927 #endif
8928     }
8929 
8930     button_activate_exit(C, but, data, (post_but == NULL), false);
8931 
8932     /* for jumping to the next button with tab while text editing */
8933     if (post_but) {
8934       /* The post_but still has previous ranges (without the changes in active button considered),
8935        * needs refreshing the ranges. */
8936       ui_but_range_set_soft(post_but);
8937       ui_but_range_set_hard(post_but);
8938 
8939       button_activate_init(C, region, post_but, post_type);
8940     }
8941     else if (!((event->type == EVT_BUT_CANCEL) && (event->val == 1))) {
8942       /* XXX issue is because WM_event_add_mousemove(wm) is a bad hack and not reliable,
8943        * if that gets coded better this bypass can go away too.
8944        *
8945        * This is needed to make sure if a button was active,
8946        * it stays active while the mouse is over it.
8947        * This avoids adding mousemoves, see: T33466. */
8948       if (ELEM(state_orig, BUTTON_STATE_INIT, BUTTON_STATE_HIGHLIGHT, BUTTON_STATE_WAIT_DRAG)) {
8949         if (ui_but_find_mouse_over(region, event) == but) {
8950           button_activate_init(C, region, but, BUTTON_ACTIVATE_OVER);
8951         }
8952       }
8953     }
8954   }
8955 
8956   return retval;
8957 }
8958 
ui_handle_list_event(bContext * C,const wmEvent * event,ARegion * region,uiBut * listbox)8959 static int ui_handle_list_event(bContext *C, const wmEvent *event, ARegion *region, uiBut *listbox)
8960 {
8961   int retval = WM_UI_HANDLER_CONTINUE;
8962   int type = event->type, val = event->val;
8963   bool redraw = false;
8964 
8965   uiList *ui_list = listbox->custom_data;
8966   if (!ui_list || !ui_list->dyn_data) {
8967     return retval;
8968   }
8969   uiListDyn *dyn_data = ui_list->dyn_data;
8970 
8971   int mx = event->x;
8972   int my = event->y;
8973   ui_window_to_block(region, listbox->block, &mx, &my);
8974 
8975   /* Convert pan to scroll-wheel. */
8976   if (type == MOUSEPAN) {
8977     ui_pan_to_scroll(event, &type, &val);
8978 
8979     /* If type still is mouse-pan, we call it handled, since delta-y accumulate. */
8980     /* also see wm_event_system.c do_wheel_ui hack */
8981     if (type == MOUSEPAN) {
8982       retval = WM_UI_HANDLER_BREAK;
8983     }
8984   }
8985 
8986   if (val == KM_PRESS) {
8987     if ((ELEM(type, EVT_UPARROWKEY, EVT_DOWNARROWKEY) &&
8988          !IS_EVENT_MOD(event, shift, ctrl, alt, oskey)) ||
8989         ((ELEM(type, WHEELUPMOUSE, WHEELDOWNMOUSE) && event->ctrl &&
8990           !IS_EVENT_MOD(event, shift, alt, oskey)))) {
8991       const int value_orig = RNA_property_int_get(&listbox->rnapoin, listbox->rnaprop);
8992       int value, min, max, inc;
8993 
8994       /* activate up/down the list */
8995       value = value_orig;
8996       if ((ui_list->filter_sort_flag & UILST_FLT_SORT_REVERSE) != 0) {
8997         inc = ELEM(type, EVT_UPARROWKEY, WHEELUPMOUSE) ? 1 : -1;
8998       }
8999       else {
9000         inc = ELEM(type, EVT_UPARROWKEY, WHEELUPMOUSE) ? -1 : 1;
9001       }
9002 
9003       if (dyn_data->items_filter_neworder || dyn_data->items_filter_flags) {
9004         /* If we have a display order different from
9005          * collection order, we have some work! */
9006         int *org_order = MEM_mallocN(dyn_data->items_shown * sizeof(int), __func__);
9007         const int *new_order = dyn_data->items_filter_neworder;
9008         int org_idx = -1, len = dyn_data->items_len;
9009         int current_idx = -1;
9010         const int filter_exclude = ui_list->filter_flag & UILST_FLT_EXCLUDE;
9011 
9012         for (int i = 0; i < len; i++) {
9013           if (!dyn_data->items_filter_flags ||
9014               ((dyn_data->items_filter_flags[i] & UILST_FLT_ITEM) ^ filter_exclude)) {
9015             org_order[new_order ? new_order[++org_idx] : ++org_idx] = i;
9016             if (i == value) {
9017               current_idx = new_order ? new_order[org_idx] : org_idx;
9018             }
9019           }
9020           else if (i == value && org_idx >= 0) {
9021             current_idx = -(new_order ? new_order[org_idx] : org_idx) - 1;
9022           }
9023         }
9024         /* Now, org_order maps displayed indices to real indices,
9025          * and current_idx either contains the displayed index of active value (positive),
9026          *                 or its more-nearest one (negated).
9027          */
9028         if (current_idx < 0) {
9029           current_idx = (current_idx * -1) + (inc < 0 ? inc : inc - 1);
9030         }
9031         else {
9032           current_idx += inc;
9033         }
9034         CLAMP(current_idx, 0, dyn_data->items_shown - 1);
9035         value = org_order[current_idx];
9036         MEM_freeN(org_order);
9037       }
9038       else {
9039         value += inc;
9040       }
9041 
9042       CLAMP(value, 0, dyn_data->items_len - 1);
9043 
9044       RNA_property_int_range(&listbox->rnapoin, listbox->rnaprop, &min, &max);
9045       CLAMP(value, min, max);
9046 
9047       if (value != value_orig) {
9048         RNA_property_int_set(&listbox->rnapoin, listbox->rnaprop, value);
9049         RNA_property_update(C, &listbox->rnapoin, listbox->rnaprop);
9050 
9051         ui_apply_but_undo(listbox);
9052 
9053         ui_list->flag |= UILST_SCROLL_TO_ACTIVE_ITEM;
9054         redraw = true;
9055       }
9056       retval = WM_UI_HANDLER_BREAK;
9057     }
9058     else if (ELEM(type, WHEELUPMOUSE, WHEELDOWNMOUSE) && event->shift) {
9059       /* We now have proper grip, but keep this anyway! */
9060       if (ui_list->list_grip < (dyn_data->visual_height_min - UI_LIST_AUTO_SIZE_THRESHOLD)) {
9061         ui_list->list_grip = dyn_data->visual_height;
9062       }
9063       ui_list->list_grip += (type == WHEELUPMOUSE) ? -1 : 1;
9064 
9065       ui_list->flag |= UILST_SCROLL_TO_ACTIVE_ITEM;
9066 
9067       redraw = true;
9068       retval = WM_UI_HANDLER_BREAK;
9069     }
9070     else if (ELEM(type, WHEELUPMOUSE, WHEELDOWNMOUSE)) {
9071       if (dyn_data->height > dyn_data->visual_height) {
9072         /* list template will clamp */
9073         ui_list->list_scroll += (type == WHEELUPMOUSE) ? -1 : 1;
9074 
9075         redraw = true;
9076         retval = WM_UI_HANDLER_BREAK;
9077       }
9078     }
9079   }
9080 
9081   if (redraw) {
9082     ED_region_tag_redraw(region);
9083     ED_region_tag_refresh_ui(region);
9084   }
9085 
9086   return retval;
9087 }
9088 
ui_handle_button_return_submenu(bContext * C,const wmEvent * event,uiBut * but)9089 static void ui_handle_button_return_submenu(bContext *C, const wmEvent *event, uiBut *but)
9090 {
9091   uiHandleButtonData *data = but->active;
9092   uiPopupBlockHandle *menu = data->menu;
9093 
9094   /* copy over return values from the closing menu */
9095   if ((menu->menuretval & UI_RETURN_OK) || (menu->menuretval & UI_RETURN_UPDATE)) {
9096     if (but->type == UI_BTYPE_COLOR) {
9097       copy_v3_v3(data->vec, menu->retvec);
9098     }
9099     else if (but->type == UI_BTYPE_MENU) {
9100       data->value = menu->retvalue;
9101     }
9102   }
9103 
9104   if (menu->menuretval & UI_RETURN_UPDATE) {
9105     if (data->interactive) {
9106       ui_apply_but(C, but->block, but, data, true);
9107     }
9108     else {
9109       ui_but_update(but);
9110     }
9111 
9112     menu->menuretval = 0;
9113   }
9114 
9115   /* now change button state or exit, which will close the submenu */
9116   if ((menu->menuretval & UI_RETURN_OK) || (menu->menuretval & UI_RETURN_CANCEL)) {
9117     if (menu->menuretval != UI_RETURN_OK) {
9118       data->cancel = true;
9119     }
9120 
9121     button_activate_exit(C, but, data, true, false);
9122   }
9123   else if (menu->menuretval & UI_RETURN_OUT) {
9124     if (event->type == MOUSEMOVE &&
9125         ui_but_contains_point_px(but, data->region, event->x, event->y)) {
9126       button_activate_state(C, but, BUTTON_STATE_HIGHLIGHT);
9127     }
9128     else {
9129       if (ISKEYBOARD(event->type)) {
9130         /* keyboard menu hierarchy navigation, going back to previous level */
9131         but->active->used_mouse = false;
9132         button_activate_state(C, but, BUTTON_STATE_HIGHLIGHT);
9133       }
9134       else {
9135         data->cancel = true;
9136         button_activate_exit(C, but, data, true, false);
9137       }
9138     }
9139   }
9140 }
9141 
9142 /** \} */
9143 
9144 /* -------------------------------------------------------------------- */
9145 /** \name Menu Towards (mouse motion logic)
9146  * \{ */
9147 
9148 /**
9149  * Function used to prevent losing the open menu when using nested pull-downs,
9150  * when moving mouse towards the pull-down menu over other buttons that could
9151  * steal the highlight from the current button, only checks:
9152  *
9153  * - while mouse moves in triangular area defined old mouse position and
9154  *   left/right side of new menu.
9155  * - only for 1 second.
9156  */
9157 
ui_mouse_motion_towards_init_ex(uiPopupBlockHandle * menu,const int xy[2],const bool force)9158 static void ui_mouse_motion_towards_init_ex(uiPopupBlockHandle *menu,
9159                                             const int xy[2],
9160                                             const bool force)
9161 {
9162   BLI_assert(((uiBlock *)menu->region->uiblocks.first)->flag &
9163              (UI_BLOCK_MOVEMOUSE_QUIT | UI_BLOCK_POPOVER));
9164 
9165   if (!menu->dotowards || force) {
9166     menu->dotowards = true;
9167     menu->towards_xy[0] = xy[0];
9168     menu->towards_xy[1] = xy[1];
9169 
9170     if (force) {
9171       menu->towardstime = DBL_MAX; /* unlimited time */
9172     }
9173     else {
9174       menu->towardstime = PIL_check_seconds_timer();
9175     }
9176   }
9177 }
9178 
ui_mouse_motion_towards_init(uiPopupBlockHandle * menu,const int xy[2])9179 static void ui_mouse_motion_towards_init(uiPopupBlockHandle *menu, const int xy[2])
9180 {
9181   ui_mouse_motion_towards_init_ex(menu, xy, false);
9182 }
9183 
ui_mouse_motion_towards_reinit(uiPopupBlockHandle * menu,const int xy[2])9184 static void ui_mouse_motion_towards_reinit(uiPopupBlockHandle *menu, const int xy[2])
9185 {
9186   ui_mouse_motion_towards_init_ex(menu, xy, true);
9187 }
9188 
ui_mouse_motion_towards_check(uiBlock * block,uiPopupBlockHandle * menu,const int xy[2],const bool use_wiggle_room)9189 static bool ui_mouse_motion_towards_check(uiBlock *block,
9190                                           uiPopupBlockHandle *menu,
9191                                           const int xy[2],
9192                                           const bool use_wiggle_room)
9193 {
9194   BLI_assert(block->flag & (UI_BLOCK_MOVEMOUSE_QUIT | UI_BLOCK_POPOVER));
9195 
9196   /* annoying fix for T36269, this is a bit odd but in fact works quite well
9197    * don't mouse-out of a menu if another menu has been created after it.
9198    * if this causes problems we could remove it and check on a different fix - campbell */
9199   if (menu->region->next) {
9200     /* am I the last menu (test) */
9201     ARegion *region = menu->region->next;
9202     do {
9203       uiBlock *block_iter = region->uiblocks.first;
9204       if (block_iter && ui_block_is_menu(block_iter)) {
9205         return true;
9206       }
9207     } while ((region = region->next));
9208   }
9209   /* annoying fix end! */
9210 
9211   if (!menu->dotowards) {
9212     return false;
9213   }
9214 
9215   float oldp[2] = {menu->towards_xy[0], menu->towards_xy[1]};
9216   const float newp[2] = {xy[0], xy[1]};
9217   if (len_squared_v2v2(oldp, newp) < (4.0f * 4.0f)) {
9218     return menu->dotowards;
9219   }
9220 
9221   /* verify that we are moving towards one of the edges of the
9222    * menu block, in other words, in the triangle formed by the
9223    * initial mouse location and two edge points. */
9224   rctf rect_px;
9225   ui_block_to_window_rctf(menu->region, block, &rect_px, &block->rect);
9226 
9227   const float margin = MENU_TOWARDS_MARGIN;
9228 
9229   const float p1[2] = {rect_px.xmin - margin, rect_px.ymin - margin};
9230   const float p2[2] = {rect_px.xmax + margin, rect_px.ymin - margin};
9231   const float p3[2] = {rect_px.xmax + margin, rect_px.ymax + margin};
9232   const float p4[2] = {rect_px.xmin - margin, rect_px.ymax + margin};
9233 
9234   /* allow for some wiggle room, if the user moves a few pixels away,
9235    * don't immediately quit (only for top level menus) */
9236   if (use_wiggle_room) {
9237     const float cent[2] = {BLI_rctf_cent_x(&rect_px), BLI_rctf_cent_y(&rect_px)};
9238     float delta[2];
9239 
9240     sub_v2_v2v2(delta, oldp, cent);
9241     normalize_v2_length(delta, MENU_TOWARDS_WIGGLE_ROOM);
9242     add_v2_v2(oldp, delta);
9243   }
9244 
9245   bool closer = (isect_point_tri_v2(newp, oldp, p1, p2) ||
9246                  isect_point_tri_v2(newp, oldp, p2, p3) ||
9247                  isect_point_tri_v2(newp, oldp, p3, p4) || isect_point_tri_v2(newp, oldp, p4, p1));
9248 
9249   if (!closer) {
9250     menu->dotowards = false;
9251   }
9252 
9253   /* 1 second timer */
9254   if (PIL_check_seconds_timer() - menu->towardstime > BUTTON_MOUSE_TOWARDS_THRESH) {
9255     menu->dotowards = false;
9256   }
9257 
9258   return menu->dotowards;
9259 }
9260 
9261 #ifdef USE_KEYNAV_LIMIT
ui_mouse_motion_keynav_init(struct uiKeyNavLock * keynav,const wmEvent * event)9262 static void ui_mouse_motion_keynav_init(struct uiKeyNavLock *keynav, const wmEvent *event)
9263 {
9264   keynav->is_keynav = true;
9265   copy_v2_v2_int(keynav->event_xy, &event->x);
9266 }
9267 /**
9268  * Return true if keyinput isn't blocking mouse-motion,
9269  * or if the mouse-motion is enough to disable keyinput.
9270  */
ui_mouse_motion_keynav_test(struct uiKeyNavLock * keynav,const wmEvent * event)9271 static bool ui_mouse_motion_keynav_test(struct uiKeyNavLock *keynav, const wmEvent *event)
9272 {
9273   if (keynav->is_keynav &&
9274       (len_manhattan_v2v2_int(keynav->event_xy, &event->x) > BUTTON_KEYNAV_PX_LIMIT)) {
9275     keynav->is_keynav = false;
9276   }
9277 
9278   return keynav->is_keynav;
9279 }
9280 #endif /* USE_KEYNAV_LIMIT */
9281 
9282 /** \} */
9283 
9284 /* -------------------------------------------------------------------- */
9285 /** \name Menu Scroll
9286  * \{ */
9287 
ui_menu_scroll_test(uiBlock * block,int my)9288 static char ui_menu_scroll_test(uiBlock *block, int my)
9289 {
9290   if (block->flag & (UI_BLOCK_CLIPTOP | UI_BLOCK_CLIPBOTTOM)) {
9291     if (block->flag & UI_BLOCK_CLIPTOP) {
9292       if (my > block->rect.ymax - UI_MENU_SCROLL_MOUSE) {
9293         return 't';
9294       }
9295     }
9296     if (block->flag & UI_BLOCK_CLIPBOTTOM) {
9297       if (my < block->rect.ymin + UI_MENU_SCROLL_MOUSE) {
9298         return 'b';
9299       }
9300     }
9301   }
9302   return 0;
9303 }
9304 
ui_menu_scroll_apply_offset_y(ARegion * region,uiBlock * block,float dy)9305 static void ui_menu_scroll_apply_offset_y(ARegion *region, uiBlock *block, float dy)
9306 {
9307   BLI_assert(dy != 0.0f);
9308 
9309   if (ui_block_is_menu(block)) {
9310     if (dy < 0.0f) {
9311       /* Stop at top item, extra 0.5 UI_UNIT_Y makes it snap nicer. */
9312       float ymax = -FLT_MAX;
9313       LISTBASE_FOREACH (uiBut *, bt, &block->buttons) {
9314         ymax = max_ff(ymax, bt->rect.ymax);
9315       }
9316       if (ymax + dy - UI_UNIT_Y * 0.5f < block->rect.ymax - UI_MENU_SCROLL_PAD) {
9317         dy = block->rect.ymax - ymax - UI_MENU_SCROLL_PAD;
9318       }
9319     }
9320     else {
9321       /* Stop at bottom item, extra 0.5 UI_UNIT_Y makes it snap nicer. */
9322       float ymin = FLT_MAX;
9323       LISTBASE_FOREACH (uiBut *, bt, &block->buttons) {
9324         ymin = min_ff(ymin, bt->rect.ymin);
9325       }
9326       if (ymin + dy + UI_UNIT_Y * 0.5f > block->rect.ymin + UI_MENU_SCROLL_PAD) {
9327         dy = block->rect.ymin - ymin + UI_MENU_SCROLL_PAD;
9328       }
9329     }
9330   }
9331 
9332   /* remember scroll offset for refreshes */
9333   block->handle->scrolloffset += dy;
9334 
9335   /* apply scroll offset */
9336   LISTBASE_FOREACH (uiBut *, bt, &block->buttons) {
9337     bt->rect.ymin += dy;
9338     bt->rect.ymax += dy;
9339   }
9340 
9341   /* set flags again */
9342   ui_popup_block_scrolltest(block);
9343 
9344   ED_region_tag_redraw(region);
9345 }
9346 
9347 /** Scroll to activated button. */
ui_menu_scroll_to_but(ARegion * region,uiBlock * block,uiBut * but_target)9348 static bool ui_menu_scroll_to_but(ARegion *region, uiBlock *block, uiBut *but_target)
9349 {
9350   float dy = 0.0;
9351   if (block->flag & UI_BLOCK_CLIPTOP) {
9352     if (but_target->rect.ymax > block->rect.ymax - UI_MENU_SCROLL_ARROW) {
9353       dy = block->rect.ymax - but_target->rect.ymax - UI_MENU_SCROLL_ARROW;
9354     }
9355   }
9356   if (block->flag & UI_BLOCK_CLIPBOTTOM) {
9357     if (but_target->rect.ymin < block->rect.ymin + UI_MENU_SCROLL_ARROW) {
9358       dy = block->rect.ymin - but_target->rect.ymin + UI_MENU_SCROLL_ARROW;
9359     }
9360   }
9361   if (dy != 0.0f) {
9362     ui_menu_scroll_apply_offset_y(region, block, dy);
9363     return true;
9364   }
9365   return false;
9366 }
9367 
9368 /** Scroll to y location (in block space, see #ui_window_to_block). */
ui_menu_scroll_to_y(ARegion * region,uiBlock * block,int y)9369 static bool ui_menu_scroll_to_y(ARegion *region, uiBlock *block, int y)
9370 {
9371   const char test = ui_menu_scroll_test(block, y);
9372   float dy = 0.0f;
9373   if (test == 't') {
9374     dy = -UI_UNIT_Y; /* scroll to the top */
9375   }
9376   else if (test == 'b') {
9377     dy = UI_UNIT_Y; /* scroll to the bottom */
9378   }
9379   if (dy != 0.0f) {
9380     ui_menu_scroll_apply_offset_y(region, block, dy);
9381     return true;
9382   }
9383   return false;
9384 }
9385 
ui_menu_scroll_step(ARegion * region,uiBlock * block,const int scroll_dir)9386 static bool ui_menu_scroll_step(ARegion *region, uiBlock *block, const int scroll_dir)
9387 {
9388   int my;
9389   if (scroll_dir == 1) {
9390     if ((block->flag & UI_BLOCK_CLIPTOP) == 0) {
9391       return false;
9392     }
9393     my = block->rect.ymax + UI_UNIT_Y;
9394   }
9395   else if (scroll_dir == -1) {
9396     if ((block->flag & UI_BLOCK_CLIPBOTTOM) == 0) {
9397       return false;
9398     }
9399     my = block->rect.ymin - UI_UNIT_Y;
9400   }
9401   else {
9402     BLI_assert(0);
9403     return false;
9404   }
9405 
9406   return ui_menu_scroll_to_y(region, block, my);
9407 }
9408 
9409 /** \} */
9410 
9411 /* -------------------------------------------------------------------- */
9412 /** \name Menu Event Handling
9413  * \{ */
9414 
ui_region_auto_open_clear(ARegion * region)9415 static void ui_region_auto_open_clear(ARegion *region)
9416 {
9417   LISTBASE_FOREACH (uiBlock *, block, &region->uiblocks) {
9418     block->auto_open = false;
9419   }
9420 }
9421 
9422 /**
9423  * Special function to handle nested menus.
9424  * let the parent menu get the event.
9425  *
9426  * This allows a menu to be open,
9427  * but send key events to the parent if there's no active buttons.
9428  *
9429  * Without this keyboard navigation from menu's wont work.
9430  */
ui_menu_pass_event_to_parent_if_nonactive(uiPopupBlockHandle * menu,const uiBut * but,const int level,const int retval)9431 static bool ui_menu_pass_event_to_parent_if_nonactive(uiPopupBlockHandle *menu,
9432                                                       const uiBut *but,
9433                                                       const int level,
9434                                                       const int retval)
9435 {
9436   if ((level != 0) && (but == NULL)) {
9437     menu->menuretval = UI_RETURN_OUT | UI_RETURN_OUT_PARENT;
9438     (void)retval; /* so release builds with strict flags are happy as well */
9439     BLI_assert(retval == WM_UI_HANDLER_CONTINUE);
9440     return true;
9441   }
9442   return false;
9443 }
9444 
ui_handle_menu_button(bContext * C,const wmEvent * event,uiPopupBlockHandle * menu)9445 static int ui_handle_menu_button(bContext *C, const wmEvent *event, uiPopupBlockHandle *menu)
9446 {
9447   ARegion *region = menu->region;
9448   uiBut *but = ui_region_find_active_but(region);
9449 
9450   if (but) {
9451     /* Its possible there is an active menu item NOT under the mouse,
9452      * in this case ignore mouse clicks outside the button (but Enter etc is accepted) */
9453     if (event->val == KM_RELEASE) {
9454       /* pass, needed so we can exit active menu-items when click-dragging out of them */
9455     }
9456     else if (but->type == UI_BTYPE_SEARCH_MENU) {
9457       /* Pass, needed so search popup can have RMB context menu.
9458        * This may be useful for other interactions which happen in the search popup
9459        * without being directly over the search button. */
9460     }
9461     else if (!ui_block_is_menu(but->block) || ui_block_is_pie_menu(but->block)) {
9462       /* pass, skip for dialogs */
9463     }
9464     else if (!ui_region_contains_point_px(but->active->region, event->x, event->y)) {
9465       /* pass, needed to click-exit outside of non-flaoting menus */
9466       ui_region_auto_open_clear(but->active->region);
9467     }
9468     else if ((!ELEM(event->type, MOUSEMOVE, WHEELUPMOUSE, WHEELDOWNMOUSE, MOUSEPAN)) &&
9469              ISMOUSE(event->type)) {
9470       if (!ui_but_contains_point_px(but, but->active->region, event->x, event->y)) {
9471         but = NULL;
9472       }
9473     }
9474   }
9475 
9476   int retval;
9477   if (but) {
9478     ScrArea *ctx_area = CTX_wm_area(C);
9479     ARegion *ctx_region = CTX_wm_region(C);
9480 
9481     if (menu->ctx_area) {
9482       CTX_wm_area_set(C, menu->ctx_area);
9483     }
9484     if (menu->ctx_region) {
9485       CTX_wm_region_set(C, menu->ctx_region);
9486     }
9487 
9488     retval = ui_handle_button_event(C, event, but);
9489 
9490     if (menu->ctx_area) {
9491       CTX_wm_area_set(C, ctx_area);
9492     }
9493     if (menu->ctx_region) {
9494       CTX_wm_region_set(C, ctx_region);
9495     }
9496   }
9497   else {
9498     retval = ui_handle_button_over(C, event, region);
9499   }
9500 
9501   return retval;
9502 }
9503 
ui_block_calc_pie_segment(uiBlock * block,const float event_xy[2])9504 float ui_block_calc_pie_segment(uiBlock *block, const float event_xy[2])
9505 {
9506   float seg1[2];
9507 
9508   if (block->pie_data.flags & UI_PIE_INITIAL_DIRECTION) {
9509     copy_v2_v2(seg1, block->pie_data.pie_center_init);
9510   }
9511   else {
9512     copy_v2_v2(seg1, block->pie_data.pie_center_spawned);
9513   }
9514 
9515   float seg2[2];
9516   sub_v2_v2v2(seg2, event_xy, seg1);
9517 
9518   const float len = normalize_v2_v2(block->pie_data.pie_dir, seg2);
9519 
9520   if (len < U.pie_menu_threshold * U.dpi_fac) {
9521     block->pie_data.flags |= UI_PIE_INVALID_DIR;
9522   }
9523   else {
9524     block->pie_data.flags &= ~UI_PIE_INVALID_DIR;
9525   }
9526 
9527   return len;
9528 }
9529 
ui_handle_menu_event(bContext * C,const wmEvent * event,uiPopupBlockHandle * menu,int level,const bool is_parent_inside,const bool is_parent_menu,const bool is_floating)9530 static int ui_handle_menu_event(bContext *C,
9531                                 const wmEvent *event,
9532                                 uiPopupBlockHandle *menu,
9533                                 int level,
9534                                 const bool is_parent_inside,
9535                                 const bool is_parent_menu,
9536                                 const bool is_floating)
9537 {
9538   uiBut *but;
9539   ARegion *region = menu->region;
9540   uiBlock *block = region->uiblocks.first;
9541 
9542   int retval = WM_UI_HANDLER_CONTINUE;
9543 
9544   int mx = event->x;
9545   int my = event->y;
9546   ui_window_to_block(region, block, &mx, &my);
9547 
9548   /* check if mouse is inside block */
9549   const bool inside = BLI_rctf_isect_pt(&block->rect, mx, my);
9550   /* check for title dragging */
9551   const bool inside_title = inside && ((my + (UI_UNIT_Y * 1.5f)) > block->rect.ymax);
9552 
9553   /* if there's an active modal button, don't check events or outside, except for search menu */
9554   but = ui_region_find_active_but(region);
9555 
9556 #ifdef USE_DRAG_POPUP
9557   if (menu->is_grab) {
9558     if (event->type == LEFTMOUSE) {
9559       menu->is_grab = false;
9560       retval = WM_UI_HANDLER_BREAK;
9561     }
9562     else {
9563       if (event->type == MOUSEMOVE) {
9564         int mdiff[2];
9565 
9566         sub_v2_v2v2_int(mdiff, &event->x, menu->grab_xy_prev);
9567         copy_v2_v2_int(menu->grab_xy_prev, &event->x);
9568 
9569         add_v2_v2v2_int(menu->popup_create_vars.event_xy, menu->popup_create_vars.event_xy, mdiff);
9570 
9571         ui_popup_translate(region, mdiff);
9572       }
9573 
9574       return retval;
9575     }
9576   }
9577 #endif
9578 
9579   if (but && button_modal_state(but->active->state)) {
9580     if (block->flag & (UI_BLOCK_MOVEMOUSE_QUIT | UI_BLOCK_POPOVER)) {
9581       /* if a button is activated modal, always reset the start mouse
9582        * position of the towards mechanism to avoid losing focus,
9583        * and don't handle events */
9584       ui_mouse_motion_towards_reinit(menu, &event->x);
9585     }
9586   }
9587   else if (event->type == TIMER) {
9588     if (event->customdata == menu->scrolltimer) {
9589       ui_menu_scroll_to_y(region, block, my);
9590     }
9591   }
9592   else {
9593     /* for ui_mouse_motion_towards_block */
9594     if (event->type == MOUSEMOVE) {
9595       if (block->flag & (UI_BLOCK_MOVEMOUSE_QUIT | UI_BLOCK_POPOVER)) {
9596         ui_mouse_motion_towards_init(menu, &event->x);
9597       }
9598 
9599       /* add menu scroll timer, if needed */
9600       if (ui_menu_scroll_test(block, my)) {
9601         if (menu->scrolltimer == NULL) {
9602           menu->scrolltimer = WM_event_add_timer(
9603               CTX_wm_manager(C), CTX_wm_window(C), TIMER, MENU_SCROLL_INTERVAL);
9604         }
9605       }
9606     }
9607 
9608     /* first block own event func */
9609     if (block->block_event_func && block->block_event_func(C, block, event)) {
9610       /* pass */
9611     } /* events not for active search menu button */
9612     else {
9613       int act = 0;
9614 
9615       switch (event->type) {
9616 
9617         /* Closing sub-levels of pull-downs.
9618          *
9619          * The actual event is handled by the button under the cursor.
9620          * This is done so we can right click on menu items even when they have sub-menus open.
9621          */
9622         case RIGHTMOUSE:
9623           if (inside == false) {
9624             if (event->val == KM_PRESS && (block->flag & UI_BLOCK_LOOP)) {
9625               if (block->saferct.first) {
9626                 /* Currently right clicking on a top level pull-down (typically in the header)
9627                  * just closes the menu and doesn't support immediately handling the RMB event.
9628                  *
9629                  * To support we would need UI_RETURN_OUT_PARENT to be handled by
9630                  * top-level buttons, not just menus. Note that this isn't very important
9631                  * since it's easy to manually close these menus by clicking on them. */
9632                 menu->menuretval = (level > 0 && is_parent_inside) ? UI_RETURN_OUT_PARENT :
9633                                                                      UI_RETURN_OUT;
9634               }
9635             }
9636             retval = WM_UI_HANDLER_BREAK;
9637           }
9638           break;
9639 
9640         /* Closing sub-levels of pull-downs. */
9641         case EVT_LEFTARROWKEY:
9642           if (event->val == KM_PRESS && (block->flag & UI_BLOCK_LOOP)) {
9643             if (block->saferct.first) {
9644               menu->menuretval = UI_RETURN_OUT;
9645             }
9646           }
9647 
9648           retval = WM_UI_HANDLER_BREAK;
9649           break;
9650 
9651         /* Opening sub-levels of pull-downs. */
9652         case EVT_RIGHTARROWKEY:
9653           if (event->val == KM_PRESS && (block->flag & UI_BLOCK_LOOP)) {
9654 
9655             if (ui_menu_pass_event_to_parent_if_nonactive(menu, but, level, retval)) {
9656               break;
9657             }
9658 
9659             but = ui_region_find_active_but(region);
9660 
9661             if (!but) {
9662               /* no item active, we make first active */
9663               if (block->direction & UI_DIR_UP) {
9664                 but = ui_but_last(block);
9665               }
9666               else {
9667                 but = ui_but_first(block);
9668               }
9669             }
9670 
9671             if (but && ELEM(but->type, UI_BTYPE_BLOCK, UI_BTYPE_PULLDOWN)) {
9672               ui_handle_button_activate(C, region, but, BUTTON_ACTIVATE_OPEN);
9673             }
9674           }
9675 
9676           retval = WM_UI_HANDLER_BREAK;
9677           break;
9678 
9679         case WHEELUPMOUSE:
9680         case WHEELDOWNMOUSE:
9681         case MOUSEPAN: {
9682           if (IS_EVENT_MOD(event, shift, ctrl, alt, oskey)) {
9683             /* pass */
9684           }
9685           else if (!ui_block_is_menu(block)) {
9686             int type = event->type;
9687             int val = event->val;
9688 
9689             /* Convert pan to scroll-wheel. */
9690             if (type == MOUSEPAN) {
9691               ui_pan_to_scroll(event, &type, &val);
9692             }
9693 
9694             if (type != MOUSEPAN) {
9695               const int scroll_dir = (type == WHEELUPMOUSE) ? 1 : -1;
9696               if (ui_menu_scroll_step(region, block, scroll_dir)) {
9697                 if (but) {
9698                   but->active->cancel = true;
9699                   button_activate_exit(C, but, but->active, false, false);
9700                 }
9701                 WM_event_add_mousemove(CTX_wm_window(C));
9702               }
9703             }
9704             break;
9705           }
9706           ATTR_FALLTHROUGH;
9707         }
9708         case EVT_UPARROWKEY:
9709         case EVT_DOWNARROWKEY:
9710         case EVT_PAGEUPKEY:
9711         case EVT_PAGEDOWNKEY:
9712         case EVT_HOMEKEY:
9713         case EVT_ENDKEY:
9714           /* Arrow-keys: only handle for block_loop blocks. */
9715           if (IS_EVENT_MOD(event, shift, ctrl, alt, oskey)) {
9716             /* pass */
9717           }
9718           else if (inside || (block->flag & UI_BLOCK_LOOP)) {
9719             int type = event->type;
9720             int val = event->val;
9721 
9722             /* Convert pan to scroll-wheel. */
9723             if (type == MOUSEPAN) {
9724               ui_pan_to_scroll(event, &type, &val);
9725             }
9726 
9727             if (val == KM_PRESS) {
9728               /* Determine scroll operation. */
9729               uiMenuScrollType scrolltype;
9730               const bool ui_block_flipped = (block->flag & UI_BLOCK_IS_FLIP) != 0;
9731 
9732               if (ELEM(type, EVT_PAGEUPKEY, EVT_HOMEKEY)) {
9733                 scrolltype = ui_block_flipped ? MENU_SCROLL_TOP : MENU_SCROLL_BOTTOM;
9734               }
9735               else if (ELEM(type, EVT_PAGEDOWNKEY, EVT_ENDKEY)) {
9736                 scrolltype = ui_block_flipped ? MENU_SCROLL_BOTTOM : MENU_SCROLL_TOP;
9737               }
9738               else if (ELEM(type, EVT_UPARROWKEY, WHEELUPMOUSE)) {
9739                 scrolltype = ui_block_flipped ? MENU_SCROLL_UP : MENU_SCROLL_DOWN;
9740               }
9741               else {
9742                 scrolltype = ui_block_flipped ? MENU_SCROLL_DOWN : MENU_SCROLL_UP;
9743               }
9744 
9745               if (ui_menu_pass_event_to_parent_if_nonactive(menu, but, level, retval)) {
9746                 break;
9747               }
9748 
9749 #ifdef USE_KEYNAV_LIMIT
9750               ui_mouse_motion_keynav_init(&menu->keynav_state, event);
9751 #endif
9752 
9753               but = ui_region_find_active_but(region);
9754               if (but) {
9755                 /* Apply scroll operation. */
9756                 if (scrolltype == MENU_SCROLL_DOWN) {
9757                   but = ui_but_next(but);
9758                 }
9759                 else if (scrolltype == MENU_SCROLL_UP) {
9760                   but = ui_but_prev(but);
9761                 }
9762                 else if (scrolltype == MENU_SCROLL_TOP) {
9763                   but = ui_but_first(block);
9764                 }
9765                 else if (scrolltype == MENU_SCROLL_BOTTOM) {
9766                   but = ui_but_last(block);
9767                 }
9768               }
9769 
9770               if (!but) {
9771                 /* wrap button or no active button*/
9772                 uiBut *but_wrap = NULL;
9773                 if (ELEM(scrolltype, MENU_SCROLL_UP, MENU_SCROLL_BOTTOM)) {
9774                   but_wrap = ui_but_last(block);
9775                 }
9776                 else if (ELEM(scrolltype, MENU_SCROLL_DOWN, MENU_SCROLL_TOP)) {
9777                   but_wrap = ui_but_first(block);
9778                 }
9779                 if (but_wrap) {
9780                   but = but_wrap;
9781                 }
9782               }
9783 
9784               if (but) {
9785                 ui_handle_button_activate(C, region, but, BUTTON_ACTIVATE);
9786                 ui_menu_scroll_to_but(region, block, but);
9787               }
9788             }
9789 
9790             retval = WM_UI_HANDLER_BREAK;
9791           }
9792 
9793           break;
9794 
9795         case EVT_ONEKEY:
9796         case EVT_PAD1:
9797           act = 1;
9798           ATTR_FALLTHROUGH;
9799         case EVT_TWOKEY:
9800         case EVT_PAD2:
9801           if (act == 0) {
9802             act = 2;
9803           }
9804           ATTR_FALLTHROUGH;
9805         case EVT_THREEKEY:
9806         case EVT_PAD3:
9807           if (act == 0) {
9808             act = 3;
9809           }
9810           ATTR_FALLTHROUGH;
9811         case EVT_FOURKEY:
9812         case EVT_PAD4:
9813           if (act == 0) {
9814             act = 4;
9815           }
9816           ATTR_FALLTHROUGH;
9817         case EVT_FIVEKEY:
9818         case EVT_PAD5:
9819           if (act == 0) {
9820             act = 5;
9821           }
9822           ATTR_FALLTHROUGH;
9823         case EVT_SIXKEY:
9824         case EVT_PAD6:
9825           if (act == 0) {
9826             act = 6;
9827           }
9828           ATTR_FALLTHROUGH;
9829         case EVT_SEVENKEY:
9830         case EVT_PAD7:
9831           if (act == 0) {
9832             act = 7;
9833           }
9834           ATTR_FALLTHROUGH;
9835         case EVT_EIGHTKEY:
9836         case EVT_PAD8:
9837           if (act == 0) {
9838             act = 8;
9839           }
9840           ATTR_FALLTHROUGH;
9841         case EVT_NINEKEY:
9842         case EVT_PAD9:
9843           if (act == 0) {
9844             act = 9;
9845           }
9846           ATTR_FALLTHROUGH;
9847         case EVT_ZEROKEY:
9848         case EVT_PAD0:
9849           if (act == 0) {
9850             act = 10;
9851           }
9852 
9853           if ((block->flag & UI_BLOCK_NUMSELECT) && event->val == KM_PRESS) {
9854             int count;
9855 
9856             if (ui_menu_pass_event_to_parent_if_nonactive(menu, but, level, retval)) {
9857               break;
9858             }
9859 
9860             if (event->alt) {
9861               act += 10;
9862             }
9863 
9864             count = 0;
9865             for (but = block->buttons.first; but; but = but->next) {
9866               bool doit = false;
9867 
9868               if (!ELEM(but->type,
9869                         UI_BTYPE_LABEL,
9870                         UI_BTYPE_SEPR,
9871                         UI_BTYPE_SEPR_LINE,
9872                         UI_BTYPE_IMAGE)) {
9873                 count++;
9874               }
9875 
9876               /* exception for rna layer buts */
9877               if (but->rnapoin.data && but->rnaprop &&
9878                   ELEM(RNA_property_subtype(but->rnaprop), PROP_LAYER, PROP_LAYER_MEMBER)) {
9879                 if (but->rnaindex == act - 1) {
9880                   doit = true;
9881                 }
9882               }
9883               else if (ELEM(but->type,
9884                             UI_BTYPE_BUT,
9885                             UI_BTYPE_BUT_MENU,
9886                             UI_BTYPE_MENU,
9887                             UI_BTYPE_BLOCK,
9888                             UI_BTYPE_PULLDOWN) &&
9889                        count == act) {
9890                 doit = true;
9891               }
9892 
9893               if (!(but->flag & UI_BUT_DISABLED) && doit) {
9894                 /* activate buttons but open menu's */
9895                 uiButtonActivateType activate;
9896                 if (but->type == UI_BTYPE_PULLDOWN) {
9897                   activate = BUTTON_ACTIVATE_OPEN;
9898                 }
9899                 else {
9900                   activate = BUTTON_ACTIVATE_APPLY;
9901                 }
9902 
9903                 ui_handle_button_activate(C, region, but, activate);
9904                 break;
9905               }
9906             }
9907 
9908             retval = WM_UI_HANDLER_BREAK;
9909           }
9910           break;
9911 
9912         /* Handle keystrokes on menu items */
9913         case EVT_AKEY:
9914         case EVT_BKEY:
9915         case EVT_CKEY:
9916         case EVT_DKEY:
9917         case EVT_EKEY:
9918         case EVT_FKEY:
9919         case EVT_GKEY:
9920         case EVT_HKEY:
9921         case EVT_IKEY:
9922         case EVT_JKEY:
9923         case EVT_KKEY:
9924         case EVT_LKEY:
9925         case EVT_MKEY:
9926         case EVT_NKEY:
9927         case EVT_OKEY:
9928         case EVT_PKEY:
9929         case EVT_QKEY:
9930         case EVT_RKEY:
9931         case EVT_SKEY:
9932         case EVT_TKEY:
9933         case EVT_UKEY:
9934         case EVT_VKEY:
9935         case EVT_WKEY:
9936         case EVT_XKEY:
9937         case EVT_YKEY:
9938         case EVT_ZKEY: {
9939           if ((event->val == KM_PRESS || event->val == KM_DBL_CLICK) &&
9940               !IS_EVENT_MOD(event, shift, ctrl, oskey)) {
9941             if (ui_menu_pass_event_to_parent_if_nonactive(menu, but, level, retval)) {
9942               break;
9943             }
9944 
9945             for (but = block->buttons.first; but; but = but->next) {
9946               if (!(but->flag & UI_BUT_DISABLED) && but->menu_key == event->type) {
9947                 if (but->type == UI_BTYPE_BUT) {
9948                   UI_but_execute(C, region, but);
9949                 }
9950                 else {
9951                   ui_handle_button_activate_by_type(C, region, but);
9952                 }
9953                 break;
9954               }
9955             }
9956 
9957             retval = WM_UI_HANDLER_BREAK;
9958           }
9959           break;
9960         }
9961       }
9962     }
9963 
9964     /* here we check return conditions for menus */
9965     if (block->flag & UI_BLOCK_LOOP) {
9966       /* If we click outside the block, verify if we clicked on the
9967        * button that opened us, otherwise we need to close,
9968        *
9969        * note that there is an exception for root level menus and
9970        * popups which you can click again to close.
9971        *
9972        * Events handled above may have already set the return value,
9973        * don't overwrite them, see: T61015.
9974        */
9975       if ((inside == false) && (menu->menuretval == 0)) {
9976         uiSafetyRct *saferct = block->saferct.first;
9977 
9978         if (ELEM(event->type, LEFTMOUSE, MIDDLEMOUSE, RIGHTMOUSE)) {
9979           if (ELEM(event->val, KM_PRESS, KM_DBL_CLICK)) {
9980             if ((is_parent_menu == false) && (U.uiflag & USER_MENUOPENAUTO) == 0) {
9981               /* for root menus, allow clicking to close */
9982               if (block->flag & UI_BLOCK_OUT_1) {
9983                 menu->menuretval = UI_RETURN_OK;
9984               }
9985               else {
9986                 menu->menuretval = UI_RETURN_OUT;
9987               }
9988             }
9989             else if (saferct && !BLI_rctf_isect_pt(&saferct->parent, event->x, event->y)) {
9990               if (block->flag & UI_BLOCK_OUT_1) {
9991                 menu->menuretval = UI_RETURN_OK;
9992               }
9993               else {
9994                 menu->menuretval = UI_RETURN_OUT;
9995               }
9996             }
9997           }
9998           else if (ELEM(event->val, KM_RELEASE, KM_CLICK)) {
9999             /* For buttons that use a hold function,
10000              * exit when mouse-up outside the menu. */
10001             if (block->flag & UI_BLOCK_POPUP_HOLD) {
10002               /* Note, we could check the cursor is over the parent button. */
10003               menu->menuretval = UI_RETURN_CANCEL;
10004               retval = WM_UI_HANDLER_CONTINUE;
10005             }
10006           }
10007         }
10008       }
10009 
10010       if (menu->menuretval) {
10011         /* pass */
10012       }
10013 #ifdef USE_KEYNAV_LIMIT
10014       else if ((event->type == MOUSEMOVE) &&
10015                ui_mouse_motion_keynav_test(&menu->keynav_state, event)) {
10016         /* Don't handle the mouse-move if we're using key-navigation. */
10017         retval = WM_UI_HANDLER_BREAK;
10018       }
10019 #endif
10020       else if (event->type == EVT_ESCKEY && event->val == KM_PRESS) {
10021         /* Escape cancels this and all preceding menus. */
10022         menu->menuretval = UI_RETURN_CANCEL;
10023       }
10024       else if (ELEM(event->type, EVT_RETKEY, EVT_PADENTER) && event->val == KM_PRESS) {
10025         uiBut *but_default = ui_region_find_first_but_test_flag(
10026             region, UI_BUT_ACTIVE_DEFAULT, UI_HIDDEN);
10027         if ((but_default != NULL) && (but_default->active == NULL)) {
10028           if (but_default->type == UI_BTYPE_BUT) {
10029             UI_but_execute(C, region, but_default);
10030           }
10031           else {
10032             ui_handle_button_activate_by_type(C, region, but_default);
10033           }
10034         }
10035         else {
10036           uiBut *but_active = ui_region_find_active_but(region);
10037 
10038           /* enter will always close this block, we let the event
10039            * get handled by the button if it is activated, otherwise we cancel */
10040           if (but_active == NULL) {
10041             menu->menuretval = UI_RETURN_CANCEL | UI_RETURN_POPUP_OK;
10042           }
10043         }
10044       }
10045 #ifdef USE_DRAG_POPUP
10046       else if ((event->type == LEFTMOUSE) && (event->val == KM_PRESS) &&
10047                (inside && is_floating && inside_title)) {
10048         if (!but || !ui_but_contains_point_px(but, region, event->x, event->y)) {
10049           if (but) {
10050             UI_but_tooltip_timer_remove(C, but);
10051           }
10052 
10053           menu->is_grab = true;
10054           copy_v2_v2_int(menu->grab_xy_prev, &event->x);
10055           retval = WM_UI_HANDLER_BREAK;
10056         }
10057       }
10058 #endif
10059       else {
10060 
10061         /* check mouse moving outside of the menu */
10062         if (inside == false && (block->flag & (UI_BLOCK_MOVEMOUSE_QUIT | UI_BLOCK_POPOVER))) {
10063           uiSafetyRct *saferct;
10064 
10065           ui_mouse_motion_towards_check(block, menu, &event->x, is_parent_inside == false);
10066 
10067           /* check for all parent rects, enables arrowkeys to be used */
10068           for (saferct = block->saferct.first; saferct; saferct = saferct->next) {
10069             /* for mouse move we only check our own rect, for other
10070              * events we check all preceding block rects too to make
10071              * arrow keys navigation work */
10072             if (event->type != MOUSEMOVE || saferct == block->saferct.first) {
10073               if (BLI_rctf_isect_pt(&saferct->parent, (float)event->x, (float)event->y)) {
10074                 break;
10075               }
10076               if (BLI_rctf_isect_pt(&saferct->safety, (float)event->x, (float)event->y)) {
10077                 break;
10078               }
10079             }
10080           }
10081 
10082           /* strict check, and include the parent rect */
10083           if (!menu->dotowards && !saferct) {
10084             if (block->flag & UI_BLOCK_OUT_1) {
10085               menu->menuretval = UI_RETURN_OK;
10086             }
10087             else {
10088               menu->menuretval = UI_RETURN_OUT;
10089             }
10090           }
10091           else if (menu->dotowards && event->type == MOUSEMOVE) {
10092             retval = WM_UI_HANDLER_BREAK;
10093           }
10094         }
10095       }
10096 
10097       /* end switch */
10098     }
10099   }
10100 
10101   /* if we are didn't handle the event yet, lets pass it on to
10102    * buttons inside this region. disabled inside check .. not sure
10103    * anymore why it was there? but it meant enter didn't work
10104    * for example when mouse was not over submenu */
10105   if ((event->type == TIMER) ||
10106       (/*inside &&*/ (!menu->menuretval || (menu->menuretval & UI_RETURN_UPDATE)) &&
10107        retval == WM_UI_HANDLER_CONTINUE)) {
10108     retval = ui_handle_menu_button(C, event, menu);
10109   }
10110 
10111 #ifdef USE_UI_POPOVER_ONCE
10112   if (block->flag & UI_BLOCK_POPOVER_ONCE) {
10113     if ((event->type == LEFTMOUSE) && (event->val == KM_RELEASE)) {
10114       UI_popover_once_clear(menu->popup_create_vars.arg);
10115       block->flag &= ~UI_BLOCK_POPOVER_ONCE;
10116     }
10117   }
10118 #endif
10119 
10120   /* Don't handle double click events, rehandle as regular press/release. */
10121   if (retval == WM_UI_HANDLER_CONTINUE && event->val == KM_DBL_CLICK) {
10122     return retval;
10123   }
10124 
10125   /* if we set a menu return value, ensure we continue passing this on to
10126    * lower menus and buttons, so always set continue then, and if we are
10127    * inside the region otherwise, ensure we swallow the event */
10128   if (menu->menuretval) {
10129     return WM_UI_HANDLER_CONTINUE;
10130   }
10131   if (inside) {
10132     return WM_UI_HANDLER_BREAK;
10133   }
10134   return retval;
10135 }
10136 
ui_handle_menu_return_submenu(bContext * C,const wmEvent * event,uiPopupBlockHandle * menu)10137 static int ui_handle_menu_return_submenu(bContext *C,
10138                                          const wmEvent *event,
10139                                          uiPopupBlockHandle *menu)
10140 {
10141   ARegion *region = menu->region;
10142   uiBlock *block = region->uiblocks.first;
10143 
10144   uiBut *but = ui_region_find_active_but(region);
10145 
10146   BLI_assert(but);
10147 
10148   uiHandleButtonData *data = but->active;
10149   uiPopupBlockHandle *submenu = data->menu;
10150 
10151   if (submenu->menuretval) {
10152     bool update;
10153 
10154     /* first decide if we want to close our own menu cascading, if
10155      * so pass on the sub menu return value to our own menu handle */
10156     if ((submenu->menuretval & UI_RETURN_OK) || (submenu->menuretval & UI_RETURN_CANCEL)) {
10157       if (!(block->flag & UI_BLOCK_KEEP_OPEN)) {
10158         menu->menuretval = submenu->menuretval;
10159         menu->butretval = data->retval;
10160       }
10161     }
10162 
10163     update = (submenu->menuretval & UI_RETURN_UPDATE) != 0;
10164 
10165     /* now let activated button in this menu exit, which
10166      * will actually close the submenu too */
10167     ui_handle_button_return_submenu(C, event, but);
10168 
10169     if (update) {
10170       submenu->menuretval = 0;
10171     }
10172   }
10173 
10174   if (block->flag & (UI_BLOCK_MOVEMOUSE_QUIT | UI_BLOCK_POPOVER)) {
10175     /* for cases where close does not cascade, allow the user to
10176      * move the mouse back towards the menu without closing */
10177     ui_mouse_motion_towards_reinit(menu, &event->x);
10178   }
10179 
10180   if (menu->menuretval) {
10181     return WM_UI_HANDLER_CONTINUE;
10182   }
10183   return WM_UI_HANDLER_BREAK;
10184 }
10185 
ui_but_pie_menu_supported_apply(uiBut * but)10186 static bool ui_but_pie_menu_supported_apply(uiBut *but)
10187 {
10188   return (!ELEM(but->type, UI_BTYPE_NUM_SLIDER, UI_BTYPE_NUM));
10189 }
10190 
ui_but_pie_menu_apply(bContext * C,uiPopupBlockHandle * menu,uiBut * but,bool force_close)10191 static int ui_but_pie_menu_apply(bContext *C,
10192                                  uiPopupBlockHandle *menu,
10193                                  uiBut *but,
10194                                  bool force_close)
10195 {
10196   const int retval = WM_UI_HANDLER_BREAK;
10197 
10198   if (but && ui_but_pie_menu_supported_apply(but)) {
10199     if (but->type == UI_BTYPE_MENU) {
10200       /* forcing the pie menu to close will not handle menus */
10201       if (!force_close) {
10202         uiBut *active_but = ui_region_find_active_but(menu->region);
10203 
10204         if (active_but) {
10205           button_activate_exit(C, active_but, active_but->active, false, false);
10206         }
10207 
10208         button_activate_init(C, menu->region, but, BUTTON_ACTIVATE_OPEN);
10209         return retval;
10210       }
10211       menu->menuretval = UI_RETURN_CANCEL;
10212     }
10213     else {
10214       button_activate_exit((bContext *)C, but, but->active, false, false);
10215 
10216       menu->menuretval = UI_RETURN_OK;
10217     }
10218   }
10219   else {
10220     menu->menuretval = UI_RETURN_CANCEL;
10221 
10222     ED_region_tag_redraw(menu->region);
10223   }
10224 
10225   return retval;
10226 }
10227 
ui_block_pie_dir_activate(uiBlock * block,const wmEvent * event,RadialDirection dir)10228 static uiBut *ui_block_pie_dir_activate(uiBlock *block, const wmEvent *event, RadialDirection dir)
10229 {
10230   if ((block->flag & UI_BLOCK_NUMSELECT) && event->val == KM_PRESS) {
10231     LISTBASE_FOREACH (uiBut *, but, &block->buttons) {
10232       if (but->pie_dir == dir && !ELEM(but->type, UI_BTYPE_SEPR, UI_BTYPE_SEPR_LINE)) {
10233         return but;
10234       }
10235     }
10236   }
10237 
10238   return NULL;
10239 }
10240 
ui_but_pie_button_activate(bContext * C,uiBut * but,uiPopupBlockHandle * menu)10241 static int ui_but_pie_button_activate(bContext *C, uiBut *but, uiPopupBlockHandle *menu)
10242 {
10243   if (but == NULL) {
10244     return WM_UI_HANDLER_BREAK;
10245   }
10246 
10247   uiBut *active_but = ui_region_find_active_but(menu->region);
10248 
10249   if (active_but) {
10250     button_activate_exit(C, active_but, active_but->active, false, false);
10251   }
10252 
10253   button_activate_init(C, menu->region, but, BUTTON_ACTIVATE_OVER);
10254   return ui_but_pie_menu_apply(C, menu, but, false);
10255 }
10256 
ui_pie_handler(bContext * C,const wmEvent * event,uiPopupBlockHandle * menu)10257 static int ui_pie_handler(bContext *C, const wmEvent *event, uiPopupBlockHandle *menu)
10258 {
10259   /* we block all events, this is modal interaction,
10260    * except for drop events which is described below */
10261   int retval = WM_UI_HANDLER_BREAK;
10262 
10263   if (event->type == EVT_DROP) {
10264     /* may want to leave this here for later if we support pie ovens */
10265 
10266     retval = WM_UI_HANDLER_CONTINUE;
10267   }
10268 
10269   ARegion *region = menu->region;
10270   uiBlock *block = region->uiblocks.first;
10271 
10272   const bool is_click_style = (block->pie_data.flags & UI_PIE_CLICK_STYLE);
10273 
10274   /* if there's an active modal button, don't check events or outside, except for search menu */
10275   uiBut *but_active = ui_region_find_active_but(region);
10276 
10277   if (menu->scrolltimer == NULL) {
10278     menu->scrolltimer = WM_event_add_timer(
10279         CTX_wm_manager(C), CTX_wm_window(C), TIMER, PIE_MENU_INTERVAL);
10280     menu->scrolltimer->duration = 0.0;
10281   }
10282 
10283   const double duration = menu->scrolltimer->duration;
10284 
10285   float event_xy[2] = {event->x, event->y};
10286 
10287   ui_window_to_block_fl(region, block, &event_xy[0], &event_xy[1]);
10288 
10289   /* Distance from initial point. */
10290   const float dist = ui_block_calc_pie_segment(block, event_xy);
10291 
10292   if (but_active && button_modal_state(but_active->active->state)) {
10293     retval = ui_handle_menu_button(C, event, menu);
10294   }
10295   else {
10296     if (event->type == TIMER) {
10297       if (event->customdata == menu->scrolltimer) {
10298         /* deactivate initial direction after a while */
10299         if (duration > 0.01 * U.pie_initial_timeout) {
10300           block->pie_data.flags &= ~UI_PIE_INITIAL_DIRECTION;
10301         }
10302 
10303         /* handle animation */
10304         if (!(block->pie_data.flags & UI_PIE_ANIMATION_FINISHED)) {
10305           const double final_time = 0.01 * U.pie_animation_timeout;
10306           float fac = duration / final_time;
10307           const float pie_radius = U.pie_menu_radius * UI_DPI_FAC;
10308 
10309           if (fac > 1.0f) {
10310             fac = 1.0f;
10311             block->pie_data.flags |= UI_PIE_ANIMATION_FINISHED;
10312           }
10313 
10314           LISTBASE_FOREACH (uiBut *, but, &block->buttons) {
10315             if (but->pie_dir != UI_RADIAL_NONE) {
10316               float vec[2];
10317               float center[2];
10318 
10319               ui_but_pie_dir(but->pie_dir, vec);
10320 
10321               center[0] = (vec[0] > 0.01f) ? 0.5f : ((vec[0] < -0.01f) ? -0.5f : 0.0f);
10322               center[1] = (vec[1] > 0.99f) ? 0.5f : ((vec[1] < -0.99f) ? -0.5f : 0.0f);
10323 
10324               center[0] *= BLI_rctf_size_x(&but->rect);
10325               center[1] *= BLI_rctf_size_y(&but->rect);
10326 
10327               mul_v2_fl(vec, pie_radius);
10328               add_v2_v2(vec, center);
10329               mul_v2_fl(vec, fac);
10330               add_v2_v2(vec, block->pie_data.pie_center_spawned);
10331 
10332               BLI_rctf_recenter(&but->rect, vec[0], vec[1]);
10333             }
10334           }
10335           block->pie_data.alphafac = fac;
10336 
10337           ED_region_tag_redraw(region);
10338         }
10339       }
10340 
10341       /* check pie velociy here if gesture has ended */
10342       if (block->pie_data.flags & UI_PIE_GESTURE_END_WAIT) {
10343         float len_sq = 10;
10344 
10345         /* use a time threshold to ensure we leave time to the mouse to move */
10346         if (duration - block->pie_data.duration_gesture > 0.02) {
10347           len_sq = len_squared_v2v2(event_xy, block->pie_data.last_pos);
10348           copy_v2_v2(block->pie_data.last_pos, event_xy);
10349           block->pie_data.duration_gesture = duration;
10350         }
10351 
10352         if (len_sq < 1.0f) {
10353           uiBut *but = ui_region_find_active_but(menu->region);
10354 
10355           if (but) {
10356             return ui_but_pie_menu_apply(C, menu, but, true);
10357           }
10358         }
10359       }
10360     }
10361 
10362     if (event->type == block->pie_data.event && !is_click_style) {
10363       if (event->val != KM_RELEASE) {
10364         ui_handle_menu_button(C, event, menu);
10365 
10366         if (len_squared_v2v2(event_xy, block->pie_data.pie_center_init) > PIE_CLICK_THRESHOLD_SQ) {
10367           block->pie_data.flags |= UI_PIE_DRAG_STYLE;
10368         }
10369         /* why redraw here? It's simple, we are getting many double click events here.
10370          * Those operate like mouse move events almost */
10371         ED_region_tag_redraw(region);
10372       }
10373       else {
10374         if ((duration < 0.01 * U.pie_tap_timeout) &&
10375             !(block->pie_data.flags & UI_PIE_DRAG_STYLE)) {
10376           block->pie_data.flags |= UI_PIE_CLICK_STYLE;
10377         }
10378         else {
10379           uiBut *but = ui_region_find_active_but(menu->region);
10380 
10381           if (but && (U.pie_menu_confirm > 0) &&
10382               (dist >= U.dpi_fac * (U.pie_menu_threshold + U.pie_menu_confirm))) {
10383             return ui_but_pie_menu_apply(C, menu, but, true);
10384           }
10385 
10386           retval = ui_but_pie_menu_apply(C, menu, but, true);
10387         }
10388       }
10389     }
10390     else {
10391       /* direction from numpad */
10392       RadialDirection num_dir = UI_RADIAL_NONE;
10393 
10394       switch (event->type) {
10395         case MOUSEMOVE:
10396           if (!is_click_style) {
10397             const float len_sq = len_squared_v2v2(event_xy, block->pie_data.pie_center_init);
10398 
10399             /* here we use the initial position explicitly */
10400             if (len_sq > PIE_CLICK_THRESHOLD_SQ) {
10401               block->pie_data.flags |= UI_PIE_DRAG_STYLE;
10402             }
10403 
10404             /* here instead, we use the offset location to account for the initial
10405              * direction timeout */
10406             if ((U.pie_menu_confirm > 0) &&
10407                 (dist >= U.dpi_fac * (U.pie_menu_threshold + U.pie_menu_confirm))) {
10408               block->pie_data.flags |= UI_PIE_GESTURE_END_WAIT;
10409               copy_v2_v2(block->pie_data.last_pos, event_xy);
10410               block->pie_data.duration_gesture = duration;
10411             }
10412           }
10413 
10414           ui_handle_menu_button(C, event, menu);
10415 
10416           /* mouse move should always refresh the area for pie menus */
10417           ED_region_tag_redraw(region);
10418           break;
10419 
10420         case LEFTMOUSE:
10421           if (is_click_style) {
10422             if (block->pie_data.flags & UI_PIE_INVALID_DIR) {
10423               menu->menuretval = UI_RETURN_CANCEL;
10424             }
10425             else {
10426               retval = ui_handle_menu_button(C, event, menu);
10427             }
10428           }
10429           break;
10430 
10431         case EVT_ESCKEY:
10432         case RIGHTMOUSE:
10433           menu->menuretval = UI_RETURN_CANCEL;
10434           break;
10435 
10436         case EVT_AKEY:
10437         case EVT_BKEY:
10438         case EVT_CKEY:
10439         case EVT_DKEY:
10440         case EVT_EKEY:
10441         case EVT_FKEY:
10442         case EVT_GKEY:
10443         case EVT_HKEY:
10444         case EVT_IKEY:
10445         case EVT_JKEY:
10446         case EVT_KKEY:
10447         case EVT_LKEY:
10448         case EVT_MKEY:
10449         case EVT_NKEY:
10450         case EVT_OKEY:
10451         case EVT_PKEY:
10452         case EVT_QKEY:
10453         case EVT_RKEY:
10454         case EVT_SKEY:
10455         case EVT_TKEY:
10456         case EVT_UKEY:
10457         case EVT_VKEY:
10458         case EVT_WKEY:
10459         case EVT_XKEY:
10460         case EVT_YKEY:
10461         case EVT_ZKEY: {
10462           if ((event->val == KM_PRESS || event->val == KM_DBL_CLICK) &&
10463               !IS_EVENT_MOD(event, shift, ctrl, oskey)) {
10464             LISTBASE_FOREACH (uiBut *, but, &block->buttons) {
10465               if (but->menu_key == event->type) {
10466                 ui_but_pie_button_activate(C, but, menu);
10467               }
10468             }
10469           }
10470           break;
10471         }
10472 
10473 #define CASE_NUM_TO_DIR(n, d) \
10474   case (EVT_ZEROKEY + n): \
10475   case (EVT_PAD0 + n): { \
10476     if (num_dir == UI_RADIAL_NONE) { \
10477       num_dir = d; \
10478     } \
10479   } \
10480     (void)0
10481 
10482           CASE_NUM_TO_DIR(1, UI_RADIAL_SW);
10483           ATTR_FALLTHROUGH;
10484           CASE_NUM_TO_DIR(2, UI_RADIAL_S);
10485           ATTR_FALLTHROUGH;
10486           CASE_NUM_TO_DIR(3, UI_RADIAL_SE);
10487           ATTR_FALLTHROUGH;
10488           CASE_NUM_TO_DIR(4, UI_RADIAL_W);
10489           ATTR_FALLTHROUGH;
10490           CASE_NUM_TO_DIR(6, UI_RADIAL_E);
10491           ATTR_FALLTHROUGH;
10492           CASE_NUM_TO_DIR(7, UI_RADIAL_NW);
10493           ATTR_FALLTHROUGH;
10494           CASE_NUM_TO_DIR(8, UI_RADIAL_N);
10495           ATTR_FALLTHROUGH;
10496           CASE_NUM_TO_DIR(9, UI_RADIAL_NE);
10497           {
10498             uiBut *but = ui_block_pie_dir_activate(block, event, num_dir);
10499             retval = ui_but_pie_button_activate(C, but, menu);
10500             break;
10501           }
10502 #undef CASE_NUM_TO_DIR
10503         default:
10504           retval = ui_handle_menu_button(C, event, menu);
10505           break;
10506       }
10507     }
10508   }
10509 
10510   return retval;
10511 }
10512 
ui_handle_menus_recursive(bContext * C,const wmEvent * event,uiPopupBlockHandle * menu,int level,const bool is_parent_inside,const bool is_parent_menu,const bool is_floating)10513 static int ui_handle_menus_recursive(bContext *C,
10514                                      const wmEvent *event,
10515                                      uiPopupBlockHandle *menu,
10516                                      int level,
10517                                      const bool is_parent_inside,
10518                                      const bool is_parent_menu,
10519                                      const bool is_floating)
10520 {
10521   int retval = WM_UI_HANDLER_CONTINUE;
10522   bool do_towards_reinit = false;
10523 
10524   /* check if we have a submenu, and handle events for it first */
10525   uiBut *but = ui_region_find_active_but(menu->region);
10526   uiHandleButtonData *data = (but) ? but->active : NULL;
10527   uiPopupBlockHandle *submenu = (data) ? data->menu : NULL;
10528 
10529   if (submenu) {
10530     uiBlock *block = menu->region->uiblocks.first;
10531     const bool is_menu = ui_block_is_menu(block);
10532     bool inside = false;
10533     /* root pie menus accept the key that spawned
10534      * them as double click to improve responsiveness */
10535     const bool do_recursion = (!(block->flag & UI_BLOCK_RADIAL) ||
10536                                event->type != block->pie_data.event);
10537 
10538     if (do_recursion) {
10539       if (is_parent_inside == false) {
10540         int mx = event->x;
10541         int my = event->y;
10542         ui_window_to_block(menu->region, block, &mx, &my);
10543         inside = BLI_rctf_isect_pt(&block->rect, mx, my);
10544       }
10545 
10546       retval = ui_handle_menus_recursive(
10547           C, event, submenu, level + 1, is_parent_inside || inside, is_menu, false);
10548     }
10549   }
10550 
10551   /* now handle events for our own menu */
10552   if (retval == WM_UI_HANDLER_CONTINUE || event->type == TIMER) {
10553     const bool do_but_search = (but && (but->type == UI_BTYPE_SEARCH_MENU));
10554     if (submenu && submenu->menuretval) {
10555       const bool do_ret_out_parent = (submenu->menuretval & UI_RETURN_OUT_PARENT) != 0;
10556       retval = ui_handle_menu_return_submenu(C, event, menu);
10557       submenu = NULL; /* hint not to use this, it may be freed by call above */
10558       (void)submenu;
10559       /* we may want to quit the submenu and handle the even in this menu,
10560        * if its important to use it, check 'data->menu' first */
10561       if (((retval == WM_UI_HANDLER_BREAK) && do_ret_out_parent) == false) {
10562         /* skip applying the event */
10563         return retval;
10564       }
10565     }
10566 
10567     if (do_but_search) {
10568       uiBlock *block = menu->region->uiblocks.first;
10569 
10570       retval = ui_handle_menu_button(C, event, menu);
10571 
10572       if (block->flag & (UI_BLOCK_MOVEMOUSE_QUIT | UI_BLOCK_POPOVER)) {
10573         /* when there is a active search button and we close it,
10574          * we need to reinit the mouse coords T35346. */
10575         if (ui_region_find_active_but(menu->region) != but) {
10576           do_towards_reinit = true;
10577         }
10578       }
10579     }
10580     else {
10581       uiBlock *block = menu->region->uiblocks.first;
10582       uiBut *listbox = ui_list_find_mouse_over(menu->region, event);
10583 
10584       if (block->flag & UI_BLOCK_RADIAL) {
10585         retval = ui_pie_handler(C, event, menu);
10586       }
10587       else if (event->type == LEFTMOUSE || event->val != KM_DBL_CLICK) {
10588         bool handled = false;
10589 
10590         if (listbox) {
10591           const int retval_test = ui_handle_list_event(C, event, menu->region, listbox);
10592           if (retval_test != WM_UI_HANDLER_CONTINUE) {
10593             retval = retval_test;
10594             handled = true;
10595           }
10596         }
10597 
10598         if (handled == false) {
10599           retval = ui_handle_menu_event(
10600               C, event, menu, level, is_parent_inside, is_parent_menu, is_floating);
10601         }
10602       }
10603     }
10604   }
10605 
10606   if (do_towards_reinit) {
10607     ui_mouse_motion_towards_reinit(menu, &event->x);
10608   }
10609 
10610   return retval;
10611 }
10612 
10613 /**
10614  * Allow setting menu return value from externals.
10615  * E.g. WM might need to do this for exiting files correctly.
10616  */
UI_popup_menu_retval_set(const uiBlock * block,const int retval,const bool enable)10617 void UI_popup_menu_retval_set(const uiBlock *block, const int retval, const bool enable)
10618 {
10619   uiPopupBlockHandle *menu = block->handle;
10620   if (menu) {
10621     menu->menuretval = enable ? (menu->menuretval | retval) : (menu->menuretval & retval);
10622   }
10623 }
10624 
10625 /** \} */
10626 
10627 /* -------------------------------------------------------------------- */
10628 /** \name UI Event Handlers
10629  * \{ */
10630 
ui_region_handler(bContext * C,const wmEvent * event,void * UNUSED (userdata))10631 static int ui_region_handler(bContext *C, const wmEvent *event, void *UNUSED(userdata))
10632 {
10633   /* here we handle buttons at the region level, non-modal */
10634   ARegion *region = CTX_wm_region(C);
10635   int retval = WM_UI_HANDLER_CONTINUE;
10636 
10637   if (region == NULL || BLI_listbase_is_empty(&region->uiblocks)) {
10638     return retval;
10639   }
10640 
10641   /* either handle events for already activated button or try to activate */
10642   uiBut *but = ui_region_find_active_but(region);
10643   uiBut *listbox = ui_list_find_mouse_over(region, event);
10644 
10645   retval = ui_handler_panel_region(C, event, region, listbox ? listbox : but);
10646 
10647   if (retval == WM_UI_HANDLER_CONTINUE && listbox) {
10648     retval = ui_handle_list_event(C, event, region, listbox);
10649 
10650     /* interactions with the listbox should disable tips */
10651     if (retval == WM_UI_HANDLER_BREAK) {
10652       if (but) {
10653         UI_but_tooltip_timer_remove(C, but);
10654       }
10655     }
10656   }
10657 
10658   if (retval == WM_UI_HANDLER_CONTINUE) {
10659     if (but) {
10660       retval = ui_handle_button_event(C, event, but);
10661     }
10662     else {
10663       retval = ui_handle_button_over(C, event, region);
10664     }
10665   }
10666 
10667   /* re-enable tooltips */
10668   if (event->type == MOUSEMOVE && (event->x != event->prevx || event->y != event->prevy)) {
10669     ui_blocks_set_tooltips(region, true);
10670   }
10671 
10672   /* delayed apply callbacks */
10673   ui_apply_but_funcs_after(C);
10674 
10675   return retval;
10676 }
10677 
ui_region_handler_remove(bContext * C,void * UNUSED (userdata))10678 static void ui_region_handler_remove(bContext *C, void *UNUSED(userdata))
10679 {
10680   ARegion *region = CTX_wm_region(C);
10681   if (region == NULL) {
10682     return;
10683   }
10684 
10685   UI_blocklist_free(C, &region->uiblocks);
10686 
10687   bScreen *screen = CTX_wm_screen(C);
10688   if (screen == NULL) {
10689     return;
10690   }
10691 
10692   /* delayed apply callbacks, but not for screen level regions, those
10693    * we rather do at the very end after closing them all, which will
10694    * be done in ui_region_handler/window */
10695   if (BLI_findindex(&screen->regionbase, region) == -1) {
10696     ui_apply_but_funcs_after(C);
10697   }
10698 }
10699 
10700 /* handle buttons at the window level, modal, for example while
10701  * number sliding, text editing, or when a menu block is open */
ui_handler_region_menu(bContext * C,const wmEvent * event,void * UNUSED (userdata))10702 static int ui_handler_region_menu(bContext *C, const wmEvent *event, void *UNUSED(userdata))
10703 {
10704   ARegion *menu_region = CTX_wm_menu(C);
10705   ARegion *region = menu_region ? menu_region : CTX_wm_region(C);
10706   int retval = WM_UI_HANDLER_CONTINUE;
10707 
10708   uiBut *but = ui_region_find_active_but(region);
10709 
10710   if (but) {
10711     bScreen *screen = CTX_wm_screen(C);
10712     uiBut *but_other;
10713 
10714     /* handle activated button events */
10715     uiHandleButtonData *data = but->active;
10716 
10717     if ((data->state == BUTTON_STATE_MENU_OPEN) &&
10718         /* Make sure this popup isn't dragging a button.
10719          * can happen with popovers (see T67882). */
10720         (ui_region_find_active_but(data->menu->region) == NULL) &&
10721         /* make sure mouse isn't inside another menu (see T43247) */
10722         (ui_screen_region_find_mouse_over(screen, event) == NULL) &&
10723         (ELEM(but->type, UI_BTYPE_PULLDOWN, UI_BTYPE_POPOVER, UI_BTYPE_MENU)) &&
10724         (but_other = ui_but_find_mouse_over(region, event)) && (but != but_other) &&
10725         (ELEM(but_other->type, UI_BTYPE_PULLDOWN, UI_BTYPE_POPOVER, UI_BTYPE_MENU)) &&
10726         /* Hover-opening menu's doesn't work well for buttons over one another
10727          * along the same axis the menu is opening on (see T71719). */
10728         (((data->menu->direction & (UI_DIR_LEFT | UI_DIR_RIGHT)) &&
10729           BLI_rctf_isect_rect_x(&but->rect, &but_other->rect, NULL)) ||
10730          ((data->menu->direction & (UI_DIR_DOWN | UI_DIR_UP)) &&
10731           BLI_rctf_isect_rect_y(&but->rect, &but_other->rect, NULL)))) {
10732       /* if mouse moves to a different root-level menu button,
10733        * open it to replace the current menu */
10734       if ((but_other->flag & UI_BUT_DISABLED) == 0) {
10735         ui_handle_button_activate(C, region, but_other, BUTTON_ACTIVATE_OVER);
10736         button_activate_state(C, but_other, BUTTON_STATE_MENU_OPEN);
10737         retval = WM_UI_HANDLER_BREAK;
10738       }
10739     }
10740     else if (data->state == BUTTON_STATE_MENU_OPEN) {
10741       /* handle events for menus and their buttons recursively,
10742        * this will handle events from the top to the bottom menu */
10743       if (data->menu) {
10744         retval = ui_handle_menus_recursive(C, event, data->menu, 0, false, false, false);
10745       }
10746 
10747       /* handle events for the activated button */
10748       if ((data->menu && (retval == WM_UI_HANDLER_CONTINUE)) || (event->type == TIMER)) {
10749         if (data->menu && data->menu->menuretval) {
10750           ui_handle_button_return_submenu(C, event, but);
10751           retval = WM_UI_HANDLER_BREAK;
10752         }
10753         else {
10754           retval = ui_handle_button_event(C, event, but);
10755         }
10756       }
10757     }
10758     else {
10759       /* handle events for the activated button */
10760       retval = ui_handle_button_event(C, event, but);
10761     }
10762   }
10763 
10764   /* re-enable tooltips */
10765   if (event->type == MOUSEMOVE && (event->x != event->prevx || event->y != event->prevy)) {
10766     ui_blocks_set_tooltips(region, true);
10767   }
10768 
10769   if (but && but->active && but->active->menu) {
10770     /* Set correct context menu-region. The handling button above breaks if we set the region
10771      * first, so only set it for executing the after-funcs. */
10772     CTX_wm_menu_set(C, but->active->menu->region);
10773   }
10774 
10775   /* delayed apply callbacks */
10776   ui_apply_but_funcs_after(C);
10777 
10778   /* Reset to previous context region. */
10779   CTX_wm_menu_set(C, menu_region);
10780 
10781   /* Don't handle double-click events,
10782    * these will be converted into regular clicks which we handle. */
10783   if (retval == WM_UI_HANDLER_CONTINUE) {
10784     if (event->val == KM_DBL_CLICK) {
10785       return WM_UI_HANDLER_CONTINUE;
10786     }
10787   }
10788 
10789   /* we block all events, this is modal interaction */
10790   return WM_UI_HANDLER_BREAK;
10791 }
10792 
10793 /* two types of popups, one with operator + enum, other with regular callbacks */
ui_popup_handler(bContext * C,const wmEvent * event,void * userdata)10794 static int ui_popup_handler(bContext *C, const wmEvent *event, void *userdata)
10795 {
10796   uiPopupBlockHandle *menu = userdata;
10797   /* we block all events, this is modal interaction,
10798    * except for drop events which is described below */
10799   int retval = WM_UI_HANDLER_BREAK;
10800   bool reset_pie = false;
10801 
10802   ARegion *menu_region = CTX_wm_menu(C);
10803   CTX_wm_menu_set(C, menu->region);
10804 
10805   if (event->type == EVT_DROP || event->val == KM_DBL_CLICK) {
10806     /* EVT_DROP:
10807      *   If we're handling drop event we'll want it to be handled by popup callee as well,
10808      *   so it'll be possible to perform such operations as opening .blend files by dropping
10809      *   them into blender, even if there's opened popup like splash screen (sergey).
10810      * KM_DBL_CLICK:
10811      *   Continue in case of double click so wm_handlers_do calls handler again with KM_PRESS
10812      *   event. This is needed to ensure correct button handling for fast clicking (T47532).
10813      */
10814 
10815     retval = WM_UI_HANDLER_CONTINUE;
10816   }
10817 
10818   ui_handle_menus_recursive(C, event, menu, 0, false, false, true);
10819 
10820   /* free if done, does not free handle itself */
10821   if (menu->menuretval) {
10822     wmWindow *win = CTX_wm_window(C);
10823     /* copy values, we have to free first (closes region) */
10824     const uiPopupBlockHandle temp = *menu;
10825     uiBlock *block = menu->region->uiblocks.first;
10826 
10827     /* set last pie event to allow chained pie spawning */
10828     if (block->flag & UI_BLOCK_RADIAL) {
10829       win->last_pie_event = block->pie_data.event;
10830       reset_pie = true;
10831     }
10832 
10833     ui_popup_block_free(C, menu);
10834     UI_popup_handlers_remove(&win->modalhandlers, menu);
10835     CTX_wm_menu_set(C, NULL);
10836 
10837 #ifdef USE_DRAG_TOGGLE
10838     {
10839       WM_event_free_ui_handler_all(C,
10840                                    &win->modalhandlers,
10841                                    ui_handler_region_drag_toggle,
10842                                    ui_handler_region_drag_toggle_remove);
10843     }
10844 #endif
10845 
10846     if ((temp.menuretval & UI_RETURN_OK) || (temp.menuretval & UI_RETURN_POPUP_OK)) {
10847       if (temp.popup_func) {
10848         temp.popup_func(C, temp.popup_arg, temp.retvalue);
10849       }
10850     }
10851     else if (temp.cancel_func) {
10852       temp.cancel_func(C, temp.popup_arg);
10853     }
10854 
10855     WM_event_add_mousemove(win);
10856   }
10857   else {
10858     /* re-enable tooltips */
10859     if (event->type == MOUSEMOVE && (event->x != event->prevx || event->y != event->prevy)) {
10860       ui_blocks_set_tooltips(menu->region, true);
10861     }
10862   }
10863 
10864   /* delayed apply callbacks */
10865   ui_apply_but_funcs_after(C);
10866 
10867   if (reset_pie) {
10868     /* reaqcuire window in case pie invalidates it somehow */
10869     wmWindow *win = CTX_wm_window(C);
10870 
10871     if (win) {
10872       win->last_pie_event = EVENT_NONE;
10873     }
10874   }
10875 
10876   CTX_wm_region_set(C, menu_region);
10877 
10878   return retval;
10879 }
10880 
ui_popup_handler_remove(bContext * C,void * userdata)10881 static void ui_popup_handler_remove(bContext *C, void *userdata)
10882 {
10883   uiPopupBlockHandle *menu = userdata;
10884 
10885   /* More correct would be to expect UI_RETURN_CANCEL here, but not wanting to
10886    * cancel when removing handlers because of file exit is a rare exception.
10887    * So instead of setting cancel flag for all menus before removing handlers,
10888    * just explicitly flag menu with UI_RETURN_OK to avoid canceling it. */
10889   if ((menu->menuretval & UI_RETURN_OK) == 0 && menu->cancel_func) {
10890     menu->cancel_func(C, menu->popup_arg);
10891   }
10892 
10893   /* free menu block if window is closed for some reason */
10894   ui_popup_block_free(C, menu);
10895 
10896   /* delayed apply callbacks */
10897   ui_apply_but_funcs_after(C);
10898 }
10899 
UI_region_handlers_add(ListBase * handlers)10900 void UI_region_handlers_add(ListBase *handlers)
10901 {
10902   WM_event_remove_ui_handler(handlers, ui_region_handler, ui_region_handler_remove, NULL, false);
10903   WM_event_add_ui_handler(NULL, handlers, ui_region_handler, ui_region_handler_remove, NULL, 0);
10904 }
10905 
UI_popup_handlers_add(bContext * C,ListBase * handlers,uiPopupBlockHandle * popup,const char flag)10906 void UI_popup_handlers_add(bContext *C,
10907                            ListBase *handlers,
10908                            uiPopupBlockHandle *popup,
10909                            const char flag)
10910 {
10911   WM_event_add_ui_handler(C, handlers, ui_popup_handler, ui_popup_handler_remove, popup, flag);
10912 }
10913 
UI_popup_handlers_remove(ListBase * handlers,uiPopupBlockHandle * popup)10914 void UI_popup_handlers_remove(ListBase *handlers, uiPopupBlockHandle *popup)
10915 {
10916   LISTBASE_FOREACH (wmEventHandler *, handler_base, handlers) {
10917     if (handler_base->type == WM_HANDLER_TYPE_UI) {
10918       wmEventHandler_UI *handler = (wmEventHandler_UI *)handler_base;
10919 
10920       if (handler->handle_fn == ui_popup_handler &&
10921           handler->remove_fn == ui_popup_handler_remove && handler->user_data == popup) {
10922         /* tag refresh parent popup */
10923         wmEventHandler_UI *handler_next = (wmEventHandler_UI *)handler->head.next;
10924         if (handler_next && handler_next->head.type == WM_HANDLER_TYPE_UI &&
10925             handler_next->handle_fn == ui_popup_handler &&
10926             handler_next->remove_fn == ui_popup_handler_remove) {
10927           uiPopupBlockHandle *parent_popup = handler_next->user_data;
10928           ED_region_tag_refresh_ui(parent_popup->region);
10929         }
10930         break;
10931       }
10932     }
10933   }
10934 
10935   WM_event_remove_ui_handler(handlers, ui_popup_handler, ui_popup_handler_remove, popup, false);
10936 }
10937 
UI_popup_handlers_remove_all(bContext * C,ListBase * handlers)10938 void UI_popup_handlers_remove_all(bContext *C, ListBase *handlers)
10939 {
10940   WM_event_free_ui_handler_all(C, handlers, ui_popup_handler, ui_popup_handler_remove);
10941 }
10942 
UI_textbutton_activate_rna(const bContext * C,ARegion * region,const void * rna_poin_data,const char * rna_prop_id)10943 bool UI_textbutton_activate_rna(const bContext *C,
10944                                 ARegion *region,
10945                                 const void *rna_poin_data,
10946                                 const char *rna_prop_id)
10947 {
10948   uiBlock *block_text = NULL;
10949   uiBut *but_text = NULL;
10950 
10951   LISTBASE_FOREACH (uiBlock *, block, &region->uiblocks) {
10952     LISTBASE_FOREACH (uiBut *, but, &block->buttons) {
10953       if (but->type == UI_BTYPE_TEXT) {
10954         if (but->rnaprop && but->rnapoin.data == rna_poin_data) {
10955           if (STREQ(RNA_property_identifier(but->rnaprop), rna_prop_id)) {
10956             block_text = block;
10957             but_text = but;
10958             break;
10959           }
10960         }
10961       }
10962     }
10963     if (but_text) {
10964       break;
10965     }
10966   }
10967 
10968   if (but_text) {
10969     UI_but_active_only(C, region, block_text, but_text);
10970     return true;
10971   }
10972   return false;
10973 }
10974 
UI_textbutton_activate_but(const bContext * C,uiBut * actbut)10975 bool UI_textbutton_activate_but(const bContext *C, uiBut *actbut)
10976 {
10977   ARegion *region = CTX_wm_region(C);
10978   uiBlock *block_text = NULL;
10979   uiBut *but_text = NULL;
10980 
10981   LISTBASE_FOREACH (uiBlock *, block, &region->uiblocks) {
10982     LISTBASE_FOREACH (uiBut *, but, &block->buttons) {
10983       if (but == actbut && but->type == UI_BTYPE_TEXT) {
10984         block_text = block;
10985         but_text = but;
10986         break;
10987       }
10988     }
10989 
10990     if (but_text) {
10991       break;
10992     }
10993   }
10994 
10995   if (but_text) {
10996     UI_but_active_only(C, region, block_text, but_text);
10997     return true;
10998   }
10999   return false;
11000 }
11001 
11002 /** \} */
11003 
11004 /* -------------------------------------------------------------------- */
11005 /** \name Public Utilities
11006  * \{ */
11007 
11008 /* is called by notifier */
UI_screen_free_active_but(const bContext * C,bScreen * screen)11009 void UI_screen_free_active_but(const bContext *C, bScreen *screen)
11010 {
11011   wmWindow *win = CTX_wm_window(C);
11012 
11013   ED_screen_areas_iter (win, screen, area) {
11014     LISTBASE_FOREACH (ARegion *, region, &area->regionbase) {
11015       uiBut *but = ui_region_find_active_but(region);
11016       if (but) {
11017         uiHandleButtonData *data = but->active;
11018 
11019         if (data->menu == NULL && data->searchbox == NULL) {
11020           if (data->state == BUTTON_STATE_HIGHLIGHT) {
11021             ui_but_active_free(C, but);
11022           }
11023         }
11024       }
11025     }
11026   }
11027 }
11028 
11029 /* returns true if highlighted button allows drop of names */
11030 /* called in region context */
UI_but_active_drop_name(bContext * C)11031 bool UI_but_active_drop_name(bContext *C)
11032 {
11033   ARegion *region = CTX_wm_region(C);
11034   uiBut *but = ui_region_find_active_but(region);
11035 
11036   if (but) {
11037     if (ELEM(but->type, UI_BTYPE_TEXT, UI_BTYPE_SEARCH_MENU)) {
11038       return true;
11039     }
11040   }
11041 
11042   return false;
11043 }
11044 
UI_but_active_drop_color(bContext * C)11045 bool UI_but_active_drop_color(bContext *C)
11046 {
11047   ARegion *region = CTX_wm_region(C);
11048 
11049   if (region) {
11050     uiBut *but = ui_region_find_active_but(region);
11051 
11052     if (but && but->type == UI_BTYPE_COLOR) {
11053       return true;
11054     }
11055   }
11056 
11057   return false;
11058 }
11059 
11060 /** \} */
11061