2 * ***** BEGIN GPL LICENSE BLOCK *****
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; either version 2
7 * of the License, or (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software Foundation,
16 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * The Original Code is Copyright (C) 2008 Blender Foundation.
19 * All rights reserved.
21 * Contributor(s): Blender Foundation
23 * ***** END GPL LICENSE BLOCK *****
26 /** \file blender/editors/interface/interface_handlers.c
27 * \ingroup edinterface
39 #include "MEM_guardedalloc.h"
41 #include "DNA_brush_types.h"
42 #include "DNA_sensor_types.h"
43 #include "DNA_controller_types.h"
44 #include "DNA_actuator_types.h"
46 #include "DNA_object_types.h"
47 #include "DNA_scene_types.h"
48 #include "DNA_screen_types.h"
51 #include "BLI_listbase.h"
52 #include "BLI_linklist.h"
53 #include "BLI_string.h"
54 #include "BLI_string_utf8.h"
56 #include "BLI_utildefines.h"
57 #include "BLI_string_cursor_utf8.h"
59 #include "BLF_translation.h"
63 #include "BKE_blender.h"
64 #include "BKE_brush.h"
65 #include "BKE_colortools.h"
66 #include "BKE_context.h"
67 #include "BKE_idprop.h"
68 #include "BKE_report.h"
69 #include "BKE_screen.h"
70 #include "BKE_texture.h"
71 #include "BKE_tracking.h"
73 #include "BKE_paint.h"
75 #include "ED_screen.h"
77 #include "ED_keyframing.h"
79 #include "UI_interface.h"
83 #include "interface_intern.h"
85 #include "RNA_access.h"
90 /* place the mouse at the scaled down location when un-grabbing */
91 #define USE_CONT_MOUSE_CORRECT
92 /* support dragging toggle buttons */
93 #define USE_DRAG_TOGGLE
95 /* support dragging multiple number buttons at once */
96 #define USE_DRAG_MULTINUM
98 /* so we can avoid very small mouse-moves from jumping away from keyboard navigation [#34936] */
99 #define USE_KEYNAV_LIMIT
101 /* drag popups by their header */
102 #define USE_DRAG_POPUP
104 #define UI_MAX_PASSWORD_STR 128
107 static void ui_add_smart_controller(bContext *C, uiBut *from, uiBut *to);
108 static void ui_add_link(bContext *C, uiBut *from, uiBut *to);
109 static int ui_do_but_EXIT(bContext *C, uiBut *but, struct uiHandleButtonData *data, const wmEvent *event);
111 #ifdef USE_KEYNAV_LIMIT
112 static void ui_mouse_motion_keynav_init(struct uiKeyNavLock *keynav, const wmEvent *event);
113 static bool ui_mouse_motion_keynav_test(struct uiKeyNavLock *keynav, const wmEvent *event);
116 /***************** structs and defines ****************/
118 #define BUTTON_TOOLTIP_DELAY 0.500
119 #define BUTTON_FLASH_DELAY 0.020
120 #define MENU_SCROLL_INTERVAL 0.1
121 #define PIE_MENU_INTERVAL 0.01
122 #define BUTTON_AUTO_OPEN_THRESH 0.3
123 #define BUTTON_MOUSE_TOWARDS_THRESH 1.0
124 /* pixels to move the cursor to get out of keyboard navigation */
125 #define BUTTON_KEYNAV_PX_LIMIT 8
127 #define MENU_TOWARDS_MARGIN 20 /* margin in pixels */
128 #define MENU_TOWARDS_WIGGLE_ROOM 64 /* tolerance in pixels */
130 typedef enum uiButtonActivateType {
131 BUTTON_ACTIVATE_OVER,
133 BUTTON_ACTIVATE_APPLY,
134 BUTTON_ACTIVATE_TEXT_EDITING,
136 } uiButtonActivateType;
138 typedef enum uiHandleButtonState {
140 BUTTON_STATE_HIGHLIGHT,
141 BUTTON_STATE_WAIT_FLASH,
142 BUTTON_STATE_WAIT_RELEASE,
143 BUTTON_STATE_WAIT_KEY_EVENT,
144 BUTTON_STATE_NUM_EDITING,
145 BUTTON_STATE_TEXT_EDITING,
146 BUTTON_STATE_TEXT_SELECTING,
147 BUTTON_STATE_MENU_OPEN,
148 BUTTON_STATE_WAIT_DRAG,
150 } uiHandleButtonState;
153 #ifdef USE_DRAG_MULTINUM
155 /* how far to drag before we check for gesture direction (in pixels),
156 * note: half the height of a button is about right... */
157 #define DRAG_MULTINUM_THRESHOLD_DRAG_X (UI_UNIT_Y / 4)
159 /* how far to drag horizontally before we stop checking which buttons the gesture spans (in pixels),
160 * locking down the buttons so we can drag freely without worrying about vertical movement. */
161 #define DRAG_MULTINUM_THRESHOLD_DRAG_Y (UI_UNIT_Y / 4)
163 /* how strict to be when detecting a vertical gesture, [0.5 == sloppy], [0.9 == strict], (unsigned dot-product)
164 * note: we should be quite strict here, since doing a vertical gesture by accident should be avoided,
165 * however with some care a user should be able to do a vertical movement without *missing*. */
166 #define DRAG_MULTINUM_THRESHOLD_VERTICAL (0.75f)
169 /* a simple version of uiHandleButtonData when accessing multiple buttons */
170 typedef struct uiButMultiState {
175 typedef struct uiHandleButtonMulti {
177 BUTTON_MULTI_INIT_UNSET = 0, /* gesture direction unknown, wait until mouse has moved enough... */
178 BUTTON_MULTI_INIT_SETUP, /* vertical gesture detected, flag buttons interactively (UI_BUT_DRAG_MULTI) */
179 BUTTON_MULTI_INIT_ENABLE, /* flag buttons finished, apply horizontal motion to active and flagged */
180 BUTTON_MULTI_INIT_DISABLE, /* vertical gesture _not_ detected, take no further action */
183 bool has_mbuts; /* any buttons flagged UI_BUT_DRAG_MULTI */
185 uiButStore *bs_mbuts;
187 bool is_proportional;
189 /* before activating, we need to check gesture direction
190 * accumulate signed cursor movement here so we can tell if this is a vertical motion or not. */
193 /* values copied direct from event->x,y
194 * used to detect buttons between the current and initial mouse position */
197 /* store x location once BUTTON_MULTI_INIT_SETUP is set,
198 * moving outside this sets BUTTON_MULTI_INIT_ENABLE */
201 } uiHandleButtonMulti;
203 #endif /* USE_DRAG_MULTINUM */
207 typedef struct uiHandleButtonData {
215 uiHandleButtonState state;
217 /* booleans (could be made into flags) */
218 bool cancel, escapecancel;
219 bool applied, applied_interactive;
224 double value, origvalue, startvalue;
225 float vec[3], origvec[3];
227 int togdual, togonly;
233 wmTimer *tooltiptimer;
237 wmTimer *autoopentimer;
239 /* text selection/editing */
240 int maxlen, selextend;
243 /* number editing / dragging */
244 /* coords are Window/uiBlock relative (depends on the button) */
245 int draglastx, draglasty;
246 int dragstartx, dragstarty;
249 bool dragchange, draglock;
251 float dragf, dragfstart;
254 #ifdef USE_CONT_MOUSE_CORRECT
255 /* when ungrabbing buttons which are #ui_is_a_warp_but(), we may want to position them
256 * FLT_MAX signifies do-nothing, use #ui_block_to_window_fl() to get this into a usable space */
257 float ungrab_mval[2];
260 /* menu open (watch uiFreeActiveButtons) */
261 uiPopupBlockHandle *menu;
264 /* search box (watch uiFreeActiveButtons) */
266 #ifdef USE_KEYNAV_LIMIT
267 struct uiKeyNavLock searchbox_keynav_state;
270 #ifdef USE_DRAG_MULTINUM
271 /* Multi-buttons will be updated in unison with the active button. */
272 uiHandleButtonMulti multi_data;
276 uiButtonActivateType posttype;
278 } uiHandleButtonData;
280 typedef struct uiAfterFunc {
281 struct uiAfterFunc *next, *prev;
283 uiButHandleFunc func;
287 uiButHandleNFunc funcN;
290 uiButHandleRenameFunc rename_func;
294 uiBlockHandleFunc handle_func;
295 void *handle_func_arg;
298 uiMenuHandleFunc butm_func;
302 wmOperatorType *optype;
307 PropertyRNA *rnaprop;
309 bContextStore *context;
311 char undostr[BKE_UNDO_STR_MAX];
316 static bool ui_is_but_interactive(const uiBut *but, const bool labeledit);
317 static bool ui_but_contains_pt(uiBut *but, float mx, float my);
318 static bool ui_mouse_inside_button(ARegion *ar, uiBut *but, int x, int y);
319 static uiBut *ui_but_find_mouse_over_ex(ARegion *ar, const int x, const int y, const bool labeledit);
320 static uiBut *ui_but_find_mouse_over(ARegion *ar, const wmEvent *event);
321 static void button_activate_init(bContext *C, ARegion *ar, uiBut *but, uiButtonActivateType type);
322 static void button_activate_state(bContext *C, uiBut *but, uiHandleButtonState state);
323 static void button_activate_exit(bContext *C, uiBut *but, uiHandleButtonData *data,
324 const bool mousemove, const bool onfree);
325 static int ui_handler_region_menu(bContext *C, const wmEvent *event, void *userdata);
326 static void ui_handle_button_activate(bContext *C, ARegion *ar, uiBut *but, uiButtonActivateType type);
327 static void button_timers_tooltip_remove(bContext *C, uiBut *but);
329 #ifdef USE_DRAG_MULTINUM
330 static void ui_multibut_restore(uiHandleButtonData *data, uiBlock *block);
331 static uiButMultiState *ui_multibut_lookup(uiHandleButtonData *data, const uiBut *but);
334 /* buttons clipboard */
335 static ColorBand but_copypaste_coba = {0};
336 static CurveMapping but_copypaste_curve = {0};
337 static bool but_copypaste_curve_alive = false;
339 /* ******************** menu navigation helpers ************** */
346 static enum eSnapType ui_event_to_snap(const wmEvent *event)
348 return (event->ctrl) ? (event->shift) ? SNAP_ON_SMALL : SNAP_ON : SNAP_OFF;
351 static void ui_color_snap_hue(const enum eSnapType snap, float *r_hue)
353 const float snap_increment = (snap == SNAP_ON_SMALL) ? 24 : 12;
354 BLI_assert(snap != SNAP_OFF);
355 *r_hue = floorf(0.5f + ((*r_hue) * snap_increment)) / snap_increment;
358 /* assumes event type is MOUSEPAN */
359 void ui_pan_to_scroll(const wmEvent *event, int *type, int *val)
361 static int lastdy = 0;
362 int dy = event->prevy - event->y;
364 /* This event should be originally from event->type,
365 * converting wrong event into wheel is bad, see [#33803] */
366 BLI_assert(*type == MOUSEPAN);
368 /* sign differs, reset */
369 if ((dy > 0 && lastdy < 0) || (dy < 0 && lastdy > 0)) {
375 if (ABS(lastdy) > (int)UI_UNIT_Y) {
376 if (U.uiflag2 & USER_TRACKPAD_NATURAL)
382 *type = WHEELUPMOUSE;
384 *type = WHEELDOWNMOUSE;
391 bool ui_but_is_editable(const uiBut *but)
393 return !ELEM(but->type, LABEL, SEPR, SEPRLINE, ROUNDBOX, LISTBOX, PROGRESSBAR);
396 static uiBut *ui_but_prev(uiBut *but)
400 if (ui_but_is_editable(but)) return but;
405 static uiBut *ui_but_next(uiBut *but)
409 if (ui_but_is_editable(but)) return but;
414 static uiBut *ui_but_first(uiBlock *block)
418 but = block->buttons.first;
420 if (ui_but_is_editable(but)) return but;
426 static uiBut *ui_but_last(uiBlock *block)
430 but = block->buttons.last;
432 if (ui_but_is_editable(but)) return but;
438 static bool ui_is_a_warp_but(uiBut *but)
440 if (U.uiflag & USER_CONTINUOUS_MOUSE) {
441 if (ELEM(but->type, NUM, NUMSLI, HSVCIRCLE, TRACKPREVIEW, HSVCUBE, BUT_CURVE)) {
449 static float ui_mouse_scale_warp_factor(const bool shift)
451 return shift ? 0.05f : 1.0f;
454 static void ui_mouse_scale_warp(uiHandleButtonData *data,
455 const float mx, const float my,
456 float *r_mx, float *r_my,
459 const float fac = ui_mouse_scale_warp_factor(shift);
461 /* slow down the mouse, this is fairly picky */
462 *r_mx = (data->dragstartx * (1.0f - fac) + mx * fac);
463 *r_my = (data->dragstarty * (1.0f - fac) + my * fac);
466 /* file selectors are exempt from utf-8 checks */
467 bool ui_is_but_utf8(const uiBut *but)
470 const int subtype = RNA_property_subtype(but->rnaprop);
471 return !(ELEM(subtype, PROP_FILEPATH, PROP_DIRPATH, PROP_FILENAME, PROP_BYTESTRING));
474 return !(but->flag & UI_BUT_NO_UTF8);
478 /* ********************** button apply/revert ************************/
480 static ListBase UIAfterFuncs = {NULL, NULL};
482 static uiAfterFunc *ui_afterfunc_new(void)
486 after = MEM_callocN(sizeof(uiAfterFunc), "uiAfterFunc");
488 BLI_addtail(&UIAfterFuncs, after);
494 * For executing operators after the button is pressed.
495 * (some non operator buttons need to trigger operators), see: [#37795]
497 * \note Can only call while handling buttons.
499 PointerRNA *ui_handle_afterfunc_add_operator(wmOperatorType *ot, int opcontext, bool create_props)
501 PointerRNA *ptr = NULL;
502 uiAfterFunc *after = ui_afterfunc_new();
505 after->opcontext = opcontext;
508 ptr = MEM_callocN(sizeof(PointerRNA), __func__);
509 WM_operator_properties_create_ptr(ptr, ot);
516 static void ui_apply_but_func(bContext *C, uiBut *but)
519 uiBlock *block = but->block;
521 /* these functions are postponed and only executed after all other
522 * handling is done, i.e. menus are closed, in order to avoid conflicts
523 * with these functions removing the buttons we are working with */
525 if (but->func || but->funcN || block->handle_func || but->rename_func ||
526 (but->type == BUTM && block->butm_func) || but->optype || but->rnaprop)
528 after = ui_afterfunc_new();
530 if (but->func && ELEM(but, but->func_arg1, but->func_arg2)) {
531 /* exception, this will crash due to removed button otherwise */
532 but->func(C, but->func_arg1, but->func_arg2);
535 after->func = but->func;
537 after->func_arg1 = but->func_arg1;
538 after->func_arg2 = but->func_arg2;
540 after->funcN = but->funcN;
541 after->func_argN = (but->func_argN) ? MEM_dupallocN(but->func_argN) : NULL;
543 after->rename_func = but->rename_func;
544 after->rename_arg1 = but->rename_arg1;
545 after->rename_orig = but->rename_orig; /* needs free! */
547 after->handle_func = block->handle_func;
548 after->handle_func_arg = block->handle_func_arg;
549 after->retval = but->retval;
551 if (but->type == BUTM) {
552 after->butm_func = block->butm_func;
553 after->butm_func_arg = block->butm_func_arg;
557 after->optype = but->optype;
558 after->opcontext = but->opcontext;
559 after->opptr = but->opptr;
561 after->rnapoin = but->rnapoin;
562 after->rnaprop = but->rnaprop;
565 after->context = CTX_store_copy(but->context);
573 /* typically call ui_apply_undo(), ui_apply_autokey() */
574 static void ui_apply_undo(uiBut *but)
578 if (but->flag & UI_BUT_UNDO) {
579 const char *str = NULL;
581 /* define which string to use for undo */
582 if (ELEM(but->type, LINK, INLINK)) str = "Add button link";
583 else if (but->type == MENU) str = but->drawstr;
584 else if (but->drawstr[0]) str = but->drawstr;
587 /* fallback, else we don't get an undo! */
588 if (str == NULL || str[0] == '\0') {
589 str = "Unknown Action";
592 /* delayed, after all other funcs run, popups are closed, etc */
593 after = ui_afterfunc_new();
594 BLI_strncpy(after->undostr, str, sizeof(after->undostr));
598 static void ui_apply_autokey(bContext *C, uiBut *but)
600 Scene *scene = CTX_data_scene(C);
603 ui_but_anim_autokey(C, but, scene, scene->r.cfra);
605 /* make a little report about what we've done! */
609 if (RNA_property_subtype(but->rnaprop) == PROP_PASSWORD) {
613 buf = WM_prop_pystring_assign(C, &but->rnapoin, but->rnaprop, but->rnaindex);
615 BKE_report(CTX_wm_reports(C), RPT_PROPERTY, buf);
618 WM_event_add_notifier(C, NC_SPACE | ND_SPACE_INFO_REPORT, NULL);
623 static void ui_apply_but_funcs_after(bContext *C)
625 uiAfterFunc *afterf, after;
629 /* copy to avoid recursive calls */
630 funcs = UIAfterFuncs;
631 BLI_listbase_clear(&UIAfterFuncs);
633 for (afterf = funcs.first; afterf; afterf = after.next) {
634 after = *afterf; /* copy to avoid memleak on exit() */
635 BLI_freelinkN(&funcs, afterf);
638 CTX_store_set(C, after.context);
641 /* free in advance to avoid leak on exit */
642 opptr = *after.opptr,
643 MEM_freeN(after.opptr);
647 WM_operator_name_call_ptr(C, after.optype, after.opcontext, (after.opptr) ? &opptr : NULL);
650 WM_operator_properties_free(&opptr);
652 if (after.rnapoin.data)
653 RNA_property_update(C, &after.rnapoin, after.rnaprop);
656 CTX_store_set(C, NULL);
657 CTX_store_free(after.context);
661 after.func(C, after.func_arg1, after.func_arg2);
663 after.funcN(C, after.func_argN, after.func_arg2);
665 MEM_freeN(after.func_argN);
667 if (after.handle_func)
668 after.handle_func(C, after.handle_func_arg, after.retval);
670 after.butm_func(C, after.butm_func_arg, after.a2);
672 if (after.rename_func)
673 after.rename_func(C, after.rename_arg1, after.rename_orig);
674 if (after.rename_orig)
675 MEM_freeN(after.rename_orig);
677 if (after.undostr[0])
678 ED_undo_push(C, after.undostr);
682 static void ui_apply_but_BUT(bContext *C, uiBut *but, uiHandleButtonData *data)
684 ui_apply_but_func(C, but);
686 data->retval = but->retval;
687 data->applied = true;
690 static void ui_apply_but_BUTM(bContext *C, uiBut *but, uiHandleButtonData *data)
692 ui_set_but_val(but, but->hardmin);
693 ui_apply_but_func(C, but);
695 data->retval = but->retval;
696 data->applied = true;
699 static void ui_apply_but_BLOCK(bContext *C, uiBut *but, uiHandleButtonData *data)
701 if (but->type == MENU)
702 ui_set_but_val(but, data->value);
705 ui_apply_but_func(C, but);
706 data->retval = but->retval;
707 data->applied = true;
710 static void ui_apply_but_TOG(bContext *C, uiBut *but, uiHandleButtonData *data)
715 value = ui_get_but_val(but);
719 w = UI_BITBUT_TEST(lvalue, but->bitnr);
720 if (w) lvalue = UI_BITBUT_CLR(lvalue, but->bitnr);
721 else lvalue = UI_BITBUT_SET(lvalue, but->bitnr);
723 ui_set_but_val(but, (double)lvalue);
724 if (but->type == ICONTOG || but->type == ICONTOGN) ui_check_but(but);
728 if (value == 0.0) push = 1;
731 if (ELEM(but->type, TOGN, ICONTOGN, OPTIONN)) push = !push;
732 ui_set_but_val(but, (double)push);
733 if (but->type == ICONTOG || but->type == ICONTOGN) ui_check_but(but);
736 ui_apply_but_func(C, but);
738 data->retval = but->retval;
739 data->applied = true;
742 static void ui_apply_but_ROW(bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data)
746 ui_set_but_val(but, but->hardmax);
748 ui_apply_but_func(C, but);
750 /* states of other row buttons */
751 for (bt = block->buttons.first; bt; bt = bt->next)
752 if (bt != but && bt->poin == but->poin && ELEM(bt->type, ROW, LISTROW))
755 data->retval = but->retval;
756 data->applied = true;
759 static void ui_apply_but_TEX(bContext *C, uiBut *but, uiHandleButtonData *data)
764 ui_set_but_string(C, but, data->str);
767 /* give butfunc the original text too */
768 /* feature used for bone renaming, channels, etc */
769 /* afterfunc frees origstr */
770 but->rename_orig = data->origstr;
771 data->origstr = NULL;
772 ui_apply_but_func(C, but);
774 data->retval = but->retval;
775 data->applied = true;
778 static void ui_apply_but_NUM(bContext *C, uiBut *but, uiHandleButtonData *data)
781 if (ui_set_but_string(C, but, data->str)) {
782 data->value = ui_get_but_val(but);
790 ui_set_but_val(but, data->value);
793 ui_apply_but_func(C, but);
795 data->retval = but->retval;
796 data->applied = true;
799 static void ui_apply_but_VEC(bContext *C, uiBut *but, uiHandleButtonData *data)
801 ui_set_but_vectorf(but, data->vec);
803 ui_apply_but_func(C, but);
805 data->retval = but->retval;
806 data->applied = true;
809 static void ui_apply_but_COLORBAND(bContext *C, uiBut *but, uiHandleButtonData *data)
811 ui_apply_but_func(C, but);
812 data->retval = but->retval;
813 data->applied = true;
816 static void ui_apply_but_CURVE(bContext *C, uiBut *but, uiHandleButtonData *data)
818 ui_apply_but_func(C, but);
819 data->retval = but->retval;
820 data->applied = true;
823 /* ****************** drag drop code *********************** */
826 #ifdef USE_DRAG_MULTINUM
828 /* small multi-but api */
829 static void ui_multibut_add(uiHandleButtonData *data, uiBut *but)
831 uiButMultiState *mbut_state;
833 BLI_assert(but->flag & UI_BUT_DRAG_MULTI);
834 BLI_assert(data->multi_data.has_mbuts);
837 mbut_state = MEM_callocN(sizeof(*mbut_state), __func__);
838 mbut_state->but = but;
839 mbut_state->origvalue = ui_get_but_val(but);
841 BLI_linklist_prepend(&data->multi_data.mbuts, mbut_state);
843 UI_butstore_register(data->multi_data.bs_mbuts, &mbut_state->but);
846 static uiButMultiState *ui_multibut_lookup(uiHandleButtonData *data, const uiBut *but)
850 for (l = data->multi_data.mbuts; l; l = l->next) {
851 uiButMultiState *mbut_state;
853 mbut_state = l->link;
855 if (mbut_state->but == but) {
863 static void ui_multibut_restore(uiHandleButtonData *data, uiBlock *block)
867 for (but = block->buttons.first; but; but = but->next) {
868 if (but->flag & UI_BUT_DRAG_MULTI) {
869 uiButMultiState *mbut_state = ui_multibut_lookup(data, but);
871 ui_set_but_val(but, mbut_state->origvalue);
877 static void ui_multibut_free(uiHandleButtonData *data, uiBlock *block)
879 BLI_linklist_freeN(data->multi_data.mbuts);
880 data->multi_data.mbuts = NULL;
882 if (data->multi_data.bs_mbuts) {
883 UI_butstore_free(block, data->multi_data.bs_mbuts);
884 data->multi_data.bs_mbuts = NULL;
888 static bool ui_multibut_states_tag(uiBut *but_active, uiHandleButtonData *data, const wmEvent *event)
892 bool changed = false;
894 seg[0][0] = data->multi_data.drag_start[0];
895 seg[0][1] = data->multi_data.drag_start[1];
897 seg[1][0] = event->x;
898 seg[1][1] = event->y;
900 BLI_assert(data->multi_data.init == BUTTON_MULTI_INIT_SETUP);
902 ui_window_to_block_fl(data->region, but_active->block, &seg[0][0], &seg[0][1]);
903 ui_window_to_block_fl(data->region, but_active->block, &seg[1][0], &seg[1][1]);
905 data->multi_data.has_mbuts = false;
907 /* follow ui_but_find_mouse_over_ex logic */
908 for (but = but_active->block->buttons.first; but; but = but->next) {
909 bool drag_prev = false;
910 bool drag_curr = false;
912 /* re-set each time */
913 if (but->flag & UI_BUT_DRAG_MULTI) {
914 but->flag &= ~UI_BUT_DRAG_MULTI;
918 if (ui_is_but_interactive(but, false)) {
921 if (but_active != but) {
922 if (ui_is_but_compatible(but_active, but)) {
924 BLI_assert(but->active == NULL);
926 /* finally check for overlap */
927 if (BLI_rctf_isect_segment(&but->rect, seg[0], seg[1])) {
929 but->flag |= UI_BUT_DRAG_MULTI;
930 data->multi_data.has_mbuts = true;
937 changed |= (drag_prev != drag_curr);
943 static void ui_multibut_states_create(uiBut *but_active, uiHandleButtonData *data)
947 BLI_assert(data->multi_data.init == BUTTON_MULTI_INIT_SETUP);
949 data->multi_data.bs_mbuts = UI_butstore_create(but_active->block);
951 for (but = but_active->block->buttons.first; but; but = but->next) {
952 if (but->flag & UI_BUT_DRAG_MULTI) {
953 ui_multibut_add(data, but);
957 /* edit buttons proportionally to eachother
958 * note: if we mix buttons which are proportional and others which are not,
959 * this may work a bit strangely */
960 if (but_active->rnaprop) {
961 if ((data->origvalue != 0.0) && (RNA_property_flag(but_active->rnaprop) & PROP_PROPORTIONAL)) {
962 data->multi_data.is_proportional = true;
967 static void ui_multibut_states_apply(bContext *C, uiHandleButtonData *data, uiBlock *block)
969 ARegion *ar = data->region;
970 const double value_delta = data->value - data->origvalue;
971 const double value_scale = data->multi_data.is_proportional ? (data->value / data->origvalue) : 0.0;
974 BLI_assert(data->multi_data.init == BUTTON_MULTI_INIT_ENABLE);
976 for (but = block->buttons.first; but; but = but->next) {
977 if (but->flag & UI_BUT_DRAG_MULTI) {
978 /* mbut_states for delta */
979 uiButMultiState *mbut_state = ui_multibut_lookup(data, but);
984 ui_button_execute_begin(C, ar, but, &active_back);
985 BLI_assert(active_back == NULL);
986 /* no need to check 'data->state' here */
988 /* entering text (set all) */
989 but->active->value = data->value;
990 ui_set_but_string(C, but, data->str);
993 /* dragging (use delta) */
994 if (data->multi_data.is_proportional) {
995 but->active->value = mbut_state->origvalue * value_scale;
998 but->active->value = mbut_state->origvalue + value_delta;
1001 /* clamp based on soft limits, see: T40154 */
1002 CLAMP(but->active->value, (double)but->softmin, (double)but->softmax);
1004 ui_button_execute_end(C, ar, but, active_back);
1007 /* highly unlikely */
1008 printf("%s: cant find button\n", __func__);
1016 #endif /* USE_DRAG_MULTINUM */
1019 #ifdef USE_DRAG_TOGGLE
1021 typedef struct uiDragToggleHandle {
1025 float but_cent_start[2];
1026 eButType but_type_start;
1031 } uiDragToggleHandle;
1033 static bool ui_drag_toggle_set_xy_xy(bContext *C, ARegion *ar, const bool is_set, const eButType but_type_start,
1034 const int xy_src[2], const int xy_dst[2])
1036 /* popups such as layers won't re-evaluate on redraw */
1037 const bool do_check = (ar->regiontype == RGN_TYPE_TEMPORARY);
1038 bool changed = false;
1041 for (block = ar->uiblocks.first; block; block = block->next) {
1044 float xy_a_block[2] = {UNPACK2(xy_src)};
1045 float xy_b_block[2] = {UNPACK2(xy_dst)};
1047 ui_window_to_block_fl(ar, block, &xy_a_block[0], &xy_a_block[1]);
1048 ui_window_to_block_fl(ar, block, &xy_b_block[0], &xy_b_block[1]);
1050 for (but = block->buttons.first; but; but = but->next) {
1051 /* Note: ctrl is always true here because (at least for now) we always want to consider text control
1052 * in this case, even when not embossed. */
1053 if (ui_is_but_interactive(but, true)) {
1054 if (BLI_rctf_isect_segment(&but->rect, xy_a_block, xy_b_block)) {
1056 /* execute the button */
1057 if (ui_is_but_bool(but) && but->type == but_type_start) {
1058 /* is it pressed? */
1059 bool is_set_but = ui_is_but_push(but);
1060 BLI_assert(ui_is_but_bool(but) == true);
1061 if (is_set_but != is_set) {
1062 uiButExecute(C, but);
1079 static void ui_drag_toggle_set(bContext *C, uiDragToggleHandle *drag_info, const int xy_input[2])
1081 ARegion *ar = CTX_wm_region(C);
1082 bool do_draw = false;
1086 * Initialize Locking:
1088 * Check if we need to initialize the lock axis by finding if the first
1089 * button we mouse over is X or Y aligned, then lock the mouse to that axis after.
1091 if (drag_info->is_init == false) {
1092 /* first store the buttons original coords */
1093 uiBut *but = ui_but_find_mouse_over_ex(ar, xy_input[0], xy_input[1], true);
1096 if (but->flag & UI_BUT_DRAG_LOCK) {
1097 const float but_cent_new[2] = {BLI_rctf_cent_x(&but->rect),
1098 BLI_rctf_cent_y(&but->rect)};
1100 /* check if this is a different button, chances are high the button wont move about :) */
1101 if (len_manhattan_v2v2(drag_info->but_cent_start, but_cent_new) > 1.0f) {
1102 if (fabsf(drag_info->but_cent_start[0] - but_cent_new[0]) <
1103 fabsf(drag_info->but_cent_start[1] - but_cent_new[1]))
1105 drag_info->xy_lock[0] = true;
1108 drag_info->xy_lock[1] = true;
1110 drag_info->is_init = true;
1114 drag_info->is_init = true;
1118 /* done with axis locking */
1121 xy[0] = (drag_info->xy_lock[0] == false) ? xy_input[0] : drag_info->xy_last[0];
1122 xy[1] = (drag_info->xy_lock[1] == false) ? xy_input[1] : drag_info->xy_last[1];
1125 /* touch all buttons between last mouse coord and this one */
1126 do_draw = ui_drag_toggle_set_xy_xy(C, ar, drag_info->is_set, drag_info->but_type_start, drag_info->xy_last, xy);
1129 ED_region_tag_redraw(ar);
1132 copy_v2_v2_int(drag_info->xy_last, xy);
1135 static void ui_handler_region_drag_toggle_remove(bContext *UNUSED(C), void *userdata)
1137 uiDragToggleHandle *drag_info = userdata;
1138 MEM_freeN(drag_info);
1141 static int ui_handler_region_drag_toggle(bContext *C, const wmEvent *event, void *userdata)
1143 uiDragToggleHandle *drag_info = userdata;
1146 switch (event->type) {
1149 if (event->val != KM_PRESS) {
1156 ui_drag_toggle_set(C, drag_info, &event->x);
1162 wmWindow *win = CTX_wm_window(C);
1163 ARegion *ar = CTX_wm_region(C);
1164 uiBut *but = ui_but_find_mouse_over_ex(ar, drag_info->xy_init[0], drag_info->xy_init[1], true);
1170 WM_event_remove_ui_handler(&win->modalhandlers,
1171 ui_handler_region_drag_toggle,
1172 ui_handler_region_drag_toggle_remove,
1174 ui_handler_region_drag_toggle_remove(C, drag_info);
1176 WM_event_add_mousemove(C);
1177 return WM_UI_HANDLER_BREAK;
1180 return WM_UI_HANDLER_CONTINUE;
1184 static bool ui_is_but_drag_toggle(const uiBut *but)
1186 return ((ui_is_but_bool(but) == true) &&
1187 /* menu check is importnt so the button dragged over isn't removed instantly */
1188 (ui_block_is_menu(but->block) == false));
1191 #endif /* USE_DRAG_TOGGLE */
1194 static bool ui_but_mouse_inside_icon(uiBut *but, ARegion *ar, const wmEvent *event)
1197 int x = event->x, y = event->y;
1199 ui_window_to_block(ar, but->block, &x, &y);
1201 BLI_rcti_rctf_copy(&rect, &but->rect);
1203 if (but->imb || but->type == COLOR) {
1204 /* use button size itself */
1206 else if (but->drawflag & UI_BUT_ICON_LEFT) {
1207 rect.xmax = rect.xmin + (BLI_rcti_size_y(&rect));
1210 int delta = BLI_rcti_size_x(&rect) - BLI_rcti_size_y(&rect);
1211 rect.xmin += delta / 2;
1212 rect.xmax -= delta / 2;
1215 return BLI_rcti_isect_pt(&rect, x, y);
1218 static bool ui_but_start_drag(bContext *C, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
1220 /* prevent other WM gestures to start while we try to drag */
1221 WM_gestures_remove(C);
1223 if (ABS(data->dragstartx - event->x) + ABS(data->dragstarty - event->y) > U.dragthreshold) {
1225 button_activate_state(C, but, BUTTON_STATE_EXIT);
1226 data->cancel = true;
1227 #ifdef USE_DRAG_TOGGLE
1228 if (ui_is_but_bool(but)) {
1229 uiDragToggleHandle *drag_info = MEM_callocN(sizeof(*drag_info), __func__);
1232 /* call here because regular mouse-up event wont run,
1233 * typically 'button_activate_exit()' handles this */
1234 ui_apply_autokey(C, but);
1236 drag_info->is_set = ui_is_but_push(but);
1237 drag_info->but_cent_start[0] = BLI_rctf_cent_x(&but->rect);
1238 drag_info->but_cent_start[1] = BLI_rctf_cent_y(&but->rect);
1239 drag_info->but_type_start = but->type;
1240 copy_v2_v2_int(drag_info->xy_init, &event->x);
1241 copy_v2_v2_int(drag_info->xy_last, &event->x);
1243 /* needed for toggle drag on popups */
1244 ar_prev = CTX_wm_region(C);
1245 CTX_wm_region_set(C, data->region);
1247 WM_event_add_ui_handler(C, &data->window->modalhandlers,
1248 ui_handler_region_drag_toggle,
1249 ui_handler_region_drag_toggle_remove,
1252 CTX_wm_region_set(C, ar_prev);
1256 if (but->type == COLOR) {
1258 uiDragColorHandle *drag_info = MEM_callocN(sizeof(*drag_info), __func__);
1260 /* TODO support more button pointer types */
1261 if (but->rnaprop && RNA_property_subtype(but->rnaprop) == PROP_COLOR_GAMMA) {
1262 RNA_property_float_get_array(&but->rnapoin, but->rnaprop, drag_info->color);
1263 drag_info->gamma_corrected = true;
1266 else if (but->rnaprop && RNA_property_subtype(but->rnaprop) == PROP_COLOR) {
1267 RNA_property_float_get_array(&but->rnapoin, but->rnaprop, drag_info->color);
1268 drag_info->gamma_corrected = false;
1271 else if (but->pointype == UI_BUT_POIN_FLOAT) {
1272 copy_v3_v3(drag_info->color, (float *)but->poin);
1275 else if (but->pointype == UI_BUT_POIN_CHAR) {
1276 rgb_uchar_to_float(drag_info->color, (unsigned char *)but->poin);
1281 WM_event_start_drag(C, ICON_COLOR, WM_DRAG_COLOR, drag_info, 0.0, WM_DRAG_FREE_DATA);
1284 MEM_freeN(drag_info);
1291 drag = WM_event_start_drag(C, but->icon, but->dragtype, but->dragpoin, ui_get_but_val(but), WM_DRAG_NOP);
1293 WM_event_drag_image(drag, but->imb, but->imb_scale, BLI_rctf_size_x(&but->rect), BLI_rctf_size_y(&but->rect));
1301 /* ********************** linklines *********************** */
1303 static void ui_delete_active_linkline(uiBlock *block)
1307 uiLinkLine *line, *nline;
1310 for (but = block->buttons.first; but; but = but->next) {
1311 if (but->type == LINK && but->link) {
1312 for (line = but->link->lines.first; line; line = nline) {
1315 if (line->flag & UI_SELECT) {
1316 BLI_remlink(&but->link->lines, line);
1318 link = line->from->link;
1320 /* are there more pointers allowed? */
1323 if (*(link->totlink) == 1) {
1324 *(link->totlink) = 0;
1325 MEM_freeN(*(link->ppoin));
1326 *(link->ppoin) = NULL;
1330 for (a = 0; a < (*(link->totlink)); a++) {
1332 if ((*(link->ppoin))[a] != line->to->poin) {
1333 (*(link->ppoin))[b] = (*(link->ppoin))[a];
1337 (*(link->totlink))--;
1341 *(link->poin) = NULL;
1352 static uiLinkLine *ui_is_a_link(uiBut *from, uiBut *to)
1359 for (line = link->lines.first; line; line = line->next) {
1360 if (line->from == from && line->to == to) {
1368 /* XXX BAD BAD HACK, fixme later **************** */
1369 /* Try to add an AND Controller between the sensor and the actuator logic bricks and to connect them all */
1370 static void ui_add_smart_controller(bContext *C, uiBut *from, uiBut *to)
1374 bActuator *act_to, *act_iter;
1376 bController ***sens_from_links;
1379 uiLink *link = from->link;
1381 PointerRNA props_ptr, object_ptr;
1384 sens_from_links = (bController ***)(link->ppoin);
1387 act_to = (bActuator *)(to->poin);
1389 /* (1) get the object */
1390 CTX_DATA_BEGIN (C, Object *, ob_iter, selected_editable_objects)
1392 for (sens_iter = ob_iter->sensors.first; sens_iter; sens_iter = sens_iter->next) {
1393 if (&(sens_iter->links) == sens_from_links) {
1403 /* (2) check if the sensor and the actuator are from the same object */
1404 for (act_iter = ob->actuators.first; act_iter; act_iter = (bActuator *)act_iter->next) {
1405 if (act_iter == act_to)
1409 /* only works if the sensor and the actuator are from the same object */
1410 if (!act_iter) return;
1412 /* in case the linked controller is not the active one */
1413 RNA_pointer_create((ID *)ob, &RNA_Object, ob, &object_ptr);
1415 WM_operator_properties_create(&props_ptr, "LOGIC_OT_controller_add");
1416 RNA_string_set(&props_ptr, "object", ob->id.name + 2);
1418 /* (3) add a new controller */
1419 if (WM_operator_name_call(C, "LOGIC_OT_controller_add", WM_OP_EXEC_DEFAULT, &props_ptr) & OPERATOR_FINISHED) {
1420 cont = (bController *)ob->controllers.last;
1421 cont->type = CONT_LOGIC_AND; /* Quick fix to make sure we always have an AND controller. It might be nicer to make sure the operator gives us the right one though... */
1423 /* (4) link the sensor->controller->actuator */
1424 tmp_but = MEM_callocN(sizeof(uiBut), "uiBut");
1425 uiSetButLink(tmp_but, (void **)&cont, (void ***)&(cont->links), &(cont->totlinks), from->link->tocode, (int)to->hardmin);
1426 tmp_but->hardmin = from->link->tocode;
1427 tmp_but->poin = (char *)cont;
1429 tmp_but->type = INLINK;
1430 ui_add_link(C, from, tmp_but);
1432 tmp_but->type = LINK;
1433 ui_add_link(C, tmp_but, to);
1435 /* (5) garbage collection */
1436 MEM_freeN(tmp_but->link);
1439 WM_operator_properties_free(&props_ptr);
1442 static void ui_add_link(bContext *C, uiBut *from, uiBut *to)
1444 /* in 'from' we have to add a link to 'to' */
1450 if ((line = ui_is_a_link(from, to))) {
1451 line->flag |= UI_SELECT;
1452 ui_delete_active_linkline(from->block);
1456 if (from->type == INLINK && to->type == INLINK) {
1459 else if (from->type == LINK && to->type == INLINK) {
1460 if (from->link->tocode != (int)to->hardmin) {
1461 ui_add_smart_controller(C, from, to);
1465 else if (from->type == INLINK && to->type == LINK) {
1466 if (to->link->tocode == (int)from->hardmin) {
1473 /* are there more pointers allowed? */
1475 oldppoin = *(link->ppoin);
1477 (*(link->totlink))++;
1478 *(link->ppoin) = MEM_callocN(*(link->totlink) * sizeof(void *), "new link");
1480 for (a = 0; a < (*(link->totlink)) - 1; a++) {
1481 (*(link->ppoin))[a] = oldppoin[a];
1483 (*(link->ppoin))[a] = to->poin;
1485 if (oldppoin) MEM_freeN(oldppoin);
1488 *(link->poin) = to->poin;
1494 static void ui_apply_but_LINK(bContext *C, uiBut *but, uiHandleButtonData *data)
1496 ARegion *ar = CTX_wm_region(C);
1499 for (bt = but->block->buttons.first; bt; bt = bt->next) {
1500 if (ui_mouse_inside_button(ar, bt, but->linkto[0] + ar->winrct.xmin, but->linkto[1] + ar->winrct.ymin) )
1503 if (bt && bt != but) {
1504 if (!ELEM(bt->type, LINK, INLINK) || !ELEM(but->type, LINK, INLINK))
1507 if (but->type == LINK) ui_add_link(C, but, bt);
1508 else ui_add_link(C, bt, but);
1510 ui_apply_but_func(C, but);
1511 data->retval = but->retval;
1513 data->applied = true;
1516 static void ui_apply_but_IMAGE(bContext *C, uiBut *but, uiHandleButtonData *data)
1518 ui_apply_but_func(C, but);
1519 data->retval = but->retval;
1520 data->applied = true;
1523 static void ui_apply_but_HISTOGRAM(bContext *C, uiBut *but, uiHandleButtonData *data)
1525 ui_apply_but_func(C, but);
1526 data->retval = but->retval;
1527 data->applied = true;
1530 static void ui_apply_but_WAVEFORM(bContext *C, uiBut *but, uiHandleButtonData *data)
1532 ui_apply_but_func(C, but);
1533 data->retval = but->retval;
1534 data->applied = true;
1537 static void ui_apply_but_TRACKPREVIEW(bContext *C, uiBut *but, uiHandleButtonData *data)
1539 ui_apply_but_func(C, but);
1540 data->retval = but->retval;
1541 data->applied = true;
1545 static void ui_apply_button(bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const bool interactive)
1550 ColorBand *editcoba;
1551 CurveMapping *editcumap;
1555 /* if we cancel and have not applied yet, there is nothing to do,
1556 * otherwise we have to restore the original value again */
1561 if (data->str) MEM_freeN(data->str);
1562 data->str = data->origstr;
1563 data->origstr = NULL;
1564 data->value = data->origvalue;
1565 data->origvalue = 0.0;
1566 copy_v3_v3(data->vec, data->origvec);
1567 data->origvec[0] = data->origvec[1] = data->origvec[2] = 0.0f;
1570 /* we avoid applying interactive edits a second time
1571 * at the end with the appliedinteractive flag */
1573 data->applied_interactive = true;
1575 else if (data->applied_interactive) {
1580 /* ensures we are writing actual values */
1581 editstr = but->editstr;
1582 editval = but->editval;
1583 editvec = but->editvec;
1584 editcoba = but->editcoba;
1585 editcumap = but->editcumap;
1586 but->editstr = NULL;
1587 but->editval = NULL;
1588 but->editvec = NULL;
1589 but->editcoba = NULL;
1590 but->editcumap = NULL;
1592 /* handle different types */
1593 switch (but->type) {
1595 ui_apply_but_BUT(C, but, data);
1598 case SEARCH_MENU_UNLINK:
1600 ui_apply_but_TEX(C, but, data);
1609 ui_apply_but_TOG(C, but, data);
1613 ui_apply_but_ROW(C, block, but, data);
1619 ui_apply_but_NUM(C, but, data);
1624 ui_apply_but_BLOCK(C, but, data);
1628 ui_apply_but_VEC(C, but, data);
1630 ui_apply_but_BLOCK(C, but, data);
1633 ui_apply_but_BUTM(C, but, data);
1638 ui_apply_but_VEC(C, but, data);
1641 ui_apply_but_COLORBAND(C, but, data);
1644 ui_apply_but_CURVE(C, but, data);
1648 ui_apply_but_BUT(C, but, data);
1652 ui_apply_but_LINK(C, but, data);
1655 ui_apply_but_IMAGE(C, but, data);
1658 ui_apply_but_HISTOGRAM(C, but, data);
1661 ui_apply_but_WAVEFORM(C, but, data);
1664 ui_apply_but_TRACKPREVIEW(C, but, data);
1670 #ifdef USE_DRAG_MULTINUM
1671 if (data->multi_data.has_mbuts) {
1672 if (data->multi_data.init == BUTTON_MULTI_INIT_ENABLE) {
1674 ui_multibut_restore(data, block);
1677 ui_multibut_states_apply(C, data, block);
1683 but->editstr = editstr;
1684 but->editval = editval;
1685 but->editvec = editvec;
1686 but->editcoba = editcoba;
1687 but->editcumap = editcumap;
1690 /* ******************* drop event ******************** */
1692 /* only call if event type is EVT_DROP */
1693 static void ui_but_drop(bContext *C, const wmEvent *event, uiBut *but, uiHandleButtonData *data)
1696 ListBase *drags = event->customdata; /* drop event type has listbase customdata by default */
1698 for (wmd = drags->first; wmd; wmd = wmd->next) {
1699 if (wmd->type == WM_DRAG_ID) {
1700 /* align these types with UI_but_active_drop_name */
1701 if (ELEM(but->type, TEX, SEARCH_MENU, SEARCH_MENU_UNLINK)) {
1702 ID *id = (ID *)wmd->poin;
1704 button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING);
1705 BLI_strncpy(data->str, id->name + 2, data->maxlen);
1707 if (ELEM(but->type, SEARCH_MENU, SEARCH_MENU_UNLINK)) {
1708 but->changed = true;
1709 ui_searchbox_update(C, data->searchbox, but, true);
1712 button_activate_state(C, but, BUTTON_STATE_EXIT);
1719 /* ******************* copy and paste ******************** */
1721 /* c = copy, v = paste */
1722 static void ui_but_copy_paste(bContext *C, uiBut *but, uiHandleButtonData *data, char mode)
1724 int buf_paste_len = 0;
1725 const char *buf_paste = "";
1726 bool buf_paste_alloc = false;
1728 if (mode == 'v' && but->lock == true) {
1733 /* disallow copying from any passwords */
1734 if (but->rnaprop && (RNA_property_subtype(but->rnaprop) == PROP_PASSWORD)) {
1740 /* extract first line from clipboard in case of multi-line copies */
1741 const char *buf_paste_test;
1743 buf_paste_test = WM_clipboard_text_get_firstline(false, &buf_paste_len);
1744 if (buf_paste_test) {
1745 buf_paste = buf_paste_test;
1746 buf_paste_alloc = true;
1750 /* No return from here down */
1754 if (ELEM(but->type, NUM, NUMSLI)) {
1756 if (but->poin == NULL && but->rnapoin.data == NULL) {
1759 else if (mode == 'c') {
1760 /* Get many decimal places, then strip trailing zeros.
1761 * note: too high values start to give strange results (6 or so is ok) */
1762 char buf_copy[UI_MAX_DRAW_STR];
1763 ui_get_but_string_ex(but, buf_copy, sizeof(buf_copy), 6);
1764 BLI_str_rstrip_float_zero(buf_copy, '\0');
1766 WM_clipboard_text_set(buf_copy, 0);
1771 if (ui_set_but_string_eval_num(C, but, buf_paste, &val)) {
1772 button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
1774 ui_set_but_string(C, but, buf_paste);
1775 button_activate_state(C, but, BUTTON_STATE_EXIT);
1781 else if (but->type == BUT_NORMAL) {
1784 if (but->poin == NULL && but->rnapoin.data == NULL) {
1787 else if (mode == 'c') {
1788 char buf_copy[UI_MAX_DRAW_STR];
1789 ui_get_but_vectorf(but, xyz);
1790 BLI_snprintf(buf_copy, sizeof(buf_copy), "[%f, %f, %f]", xyz[0], xyz[1], xyz[2]);
1791 WM_clipboard_text_set(buf_copy, 0);
1794 if (sscanf(buf_paste, "[%f, %f, %f]", &xyz[0], &xyz[1], &xyz[2]) == 3) {
1795 if (normalize_v3(xyz) == 0.0f) {
1796 /* better set Z up then have a zero vector */
1799 button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
1800 ui_set_but_vectorf(but, xyz);
1801 button_activate_state(C, but, BUTTON_STATE_EXIT);
1808 else if (but->type == COLOR) {
1811 if (but->poin == NULL && but->rnapoin.data == NULL) {
1814 else if (mode == 'c') {
1815 char buf_copy[UI_MAX_DRAW_STR];
1817 if (but->rnaprop && RNA_property_array_length(&but->rnapoin, but->rnaprop) == 4)
1818 rgba[3] = RNA_property_float_get_index(&but->rnapoin, but->rnaprop, 3);
1822 ui_get_but_vectorf(but, rgba);
1823 /* convert to linear color to do compatible copy between gamma and non-gamma */
1824 if (but->rnaprop && RNA_property_subtype(but->rnaprop) == PROP_COLOR_GAMMA)
1825 srgb_to_linearrgb_v3_v3(rgba, rgba);
1827 BLI_snprintf(buf_copy, sizeof(buf_copy), "[%f, %f, %f, %f]", rgba[0], rgba[1], rgba[2], rgba[3]);
1828 WM_clipboard_text_set(buf_copy, 0);
1832 if (sscanf(buf_paste, "[%f, %f, %f, %f]", &rgba[0], &rgba[1], &rgba[2], &rgba[3]) == 4) {
1833 /* assume linear colors in buffer */
1834 if (but->rnaprop && RNA_property_subtype(but->rnaprop) == PROP_COLOR_GAMMA)
1835 linearrgb_to_srgb_v3_v3(rgba, rgba);
1837 button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
1838 ui_set_but_vectorf(but, rgba);
1839 if (but->rnaprop && RNA_property_array_length(&but->rnapoin, but->rnaprop) == 4)
1840 RNA_property_float_set_index(&but->rnapoin, but->rnaprop, 3, rgba[3]);
1842 button_activate_state(C, but, BUTTON_STATE_EXIT);
1847 /* text/string and ID data */
1848 else if (ELEM(but->type, TEX, SEARCH_MENU, SEARCH_MENU_UNLINK)) {
1849 uiHandleButtonData *active_data = but->active;
1851 if (but->poin == NULL && but->rnapoin.data == NULL) {
1854 else if (mode == 'c') {
1855 button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING);
1856 WM_clipboard_text_set(active_data->str, 0);
1857 active_data->cancel = true;
1858 button_activate_state(C, but, BUTTON_STATE_EXIT);
1861 button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING);
1863 if (ui_is_but_utf8(but))
1864 BLI_strncpy_utf8(active_data->str, buf_paste, active_data->maxlen);
1866 BLI_strncpy(active_data->str, buf_paste, active_data->maxlen);
1868 if (ELEM(but->type, SEARCH_MENU, SEARCH_MENU_UNLINK)) {
1869 /* else uiSearchboxData.active member is not updated [#26856] */
1870 but->changed = true;
1871 ui_searchbox_update(C, data->searchbox, but, true);
1873 button_activate_state(C, but, BUTTON_STATE_EXIT);
1876 /* colorband (not supported by system clipboard) */
1877 else if (but->type == BUT_COLORBAND) {
1879 if (but->poin != NULL) {
1880 memcpy(&but_copypaste_coba, but->poin, sizeof(ColorBand));
1884 if (but_copypaste_coba.tot != 0) {
1886 but->poin = MEM_callocN(sizeof(ColorBand), "colorband");
1888 button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
1889 memcpy(data->coba, &but_copypaste_coba, sizeof(ColorBand));
1890 button_activate_state(C, but, BUTTON_STATE_EXIT);
1894 else if (but->type == BUT_CURVE) {
1896 if (but->poin != NULL) {
1897 but_copypaste_curve_alive = true;
1898 curvemapping_free_data(&but_copypaste_curve);
1899 curvemapping_copy_data(&but_copypaste_curve, (CurveMapping *) but->poin);
1903 if (but_copypaste_curve_alive) {
1905 but->poin = MEM_callocN(sizeof(CurveMapping), "curvemapping");
1907 button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
1908 curvemapping_free_data((CurveMapping *) but->poin);
1909 curvemapping_copy_data((CurveMapping *) but->poin, &but_copypaste_curve);
1910 button_activate_state(C, but, BUTTON_STATE_EXIT);
1914 /* operator button (any type) */
1915 else if (but->optype) {
1919 opptr = uiButGetOperatorPtrRNA(but); /* allocated when needed, the button owns it */
1921 str = WM_operator_pystring_ex(C, NULL, false, true, but->optype, opptr);
1923 WM_clipboard_text_set(str, 0);
1928 /* menu (any type) */
1929 else if (ELEM(but->type, MENU, PULLDOWN)) {
1930 MenuType *mt = uiButGetMenuType(but);
1932 char str[32 + sizeof(mt->idname)];
1933 BLI_snprintf(str, sizeof(str), "bpy.ops.wm.call_menu(name=\"%s\")", mt->idname);
1934 WM_clipboard_text_set(str, 0);
1938 if (buf_paste_alloc) {
1939 MEM_freeN((void *)buf_paste);
1943 /* ************************ password text ******************************
1945 * Functions to convert password strings that should not be displayed
1946 * to asterisk representation (e.g. mysecretpasswd -> *************)
1948 * It converts every UTF-8 character to an asterisk, and also remaps
1949 * the cursor position and selection start/end.
1951 * Note: remaping is used, because password could contain UTF-8 characters.
1955 static int ui_text_position_from_hidden(uiBut *but, int pos)
1957 const char *strpos, *butstr;
1960 butstr = (but->editstr) ? but->editstr : but->drawstr;
1962 for (i = 0, strpos = butstr; i < pos; i++)
1963 strpos = BLI_str_find_next_char_utf8(strpos, NULL);
1965 return (strpos - butstr);
1968 static int ui_text_position_to_hidden(uiBut *but, int pos)
1970 const char *butstr = (but->editstr) ? but->editstr : but->drawstr;
1971 return BLI_strnlen_utf8(butstr, pos);
1974 void ui_button_text_password_hide(char password_str[UI_MAX_PASSWORD_STR], uiBut *but, const bool restore)
1978 if (!(but->rnaprop && RNA_property_subtype(but->rnaprop) == PROP_PASSWORD))
1981 butstr = (but->editstr) ? but->editstr : but->drawstr;
1984 /* restore original string */
1985 BLI_strncpy(butstr, password_str, UI_MAX_PASSWORD_STR);
1987 /* remap cursor positions */
1988 if (but->pos >= 0) {
1989 but->pos = ui_text_position_from_hidden(but, but->pos);
1990 but->selsta = ui_text_position_from_hidden(but, but->selsta);
1991 but->selend = ui_text_position_from_hidden(but, but->selend);
1995 /* convert text to hidden text using asterisks (e.g. pass -> ****) */
1996 const size_t len = BLI_strlen_utf8(butstr);
1998 /* remap cursor positions */
1999 if (but->pos >= 0) {
2000 but->pos = ui_text_position_to_hidden(but, but->pos);
2001 but->selsta = ui_text_position_to_hidden(but, but->selsta);
2002 but->selend = ui_text_position_to_hidden(but, but->selend);
2005 /* save original string */
2006 BLI_strncpy(password_str, butstr, UI_MAX_PASSWORD_STR);
2007 memset(butstr, '*', len);
2013 /* ************* in-button text selection/editing ************* */
2016 static bool ui_textedit_delete_selection(uiBut *but, uiHandleButtonData *data)
2018 char *str = data->str;
2019 const int len = strlen(str);
2020 bool changed = false;
2021 if (but->selsta != but->selend && len) {
2022 memmove(str + but->selsta, str + but->selend, (len - but->selend) + 1);
2026 but->pos = but->selend = but->selsta;
2031 * \param x Screen space cursor location - #wmEvent.x
2033 * \note ``but->block->aspect`` is used here, so drawing button style is getting scaled too.
2035 static void ui_textedit_set_cursor_pos(uiBut *but, uiHandleButtonData *data, const float x)
2037 uiStyle *style = UI_GetStyle(); // XXX pass on as arg
2038 uiFontStyle *fstyle = &style->widget;
2039 const float aspect = but->block->aspect;
2040 const short fstyle_points_prev = fstyle->points;
2042 float startx = but->rect.xmin;
2043 float starty_dummy = 0.0f;
2044 char *origstr, password_str[UI_MAX_PASSWORD_STR];
2046 ui_block_to_window_fl(data->region, but->block, &startx, &starty_dummy);
2048 ui_fontscale(&fstyle->points, aspect);
2050 uiStyleFontSet(fstyle);
2052 if (fstyle->kerning == 1) /* for BLF_width */
2053 BLF_enable(fstyle->uifont_id, BLF_KERNING_DEFAULT);
2055 ui_button_text_password_hide(password_str, but, false);
2057 origstr = MEM_mallocN(sizeof(char) * data->maxlen, "ui_textedit origstr");
2059 BLI_strncpy(origstr, but->editstr, data->maxlen);
2061 if (ELEM(but->type, TEX, SEARCH_MENU, SEARCH_MENU_UNLINK)) {
2062 if (but->flag & UI_HAS_ICON) {
2063 startx += UI_DPI_ICON_SIZE / aspect;
2066 /* but this extra .05 makes clicks inbetween characters feel nicer */
2067 startx += ((UI_TEXT_MARGIN_X + 0.05f) * U.widget_unit) / aspect;
2069 /* mouse dragged outside the widget to the left */
2073 origstr[but->ofs] = '\0';
2076 if (BLI_str_cursor_step_prev_utf8(origstr, but->ofs, &i)) {
2077 /* 0.25 == scale factor for less sensitivity */
2078 if (BLF_width(fstyle->uifont_id, origstr + i, BLF_DRAW_STR_DUMMY_MAX) > (startx - x) * 0.25f) {
2083 break; /* unlikely but possible */
2087 but->pos = but->ofs;
2089 /* mouse inside the widget, mouse coords mapped in widget space */
2090 else { /* (x >= startx) */
2093 /* keep track of previous distance from the cursor to the char */
2094 float cdist, cdist_prev = 0.0f;
2097 but->pos = pos_prev = strlen(origstr) - but->ofs;
2100 cdist = startx + BLF_width(fstyle->uifont_id, origstr + but->ofs, BLF_DRAW_STR_DUMMY_MAX);
2102 /* check if position is found */
2104 /* check is previous location was in fact closer */
2105 if ((x - cdist) > (cdist_prev - x)) {
2106 but->pos = pos_prev;
2111 pos_prev = but->pos;
2112 /* done with tricky distance checks */
2115 if (but->pos <= 0) break;
2116 if (BLI_str_cursor_step_prev_utf8(origstr, but->ofs, &pos_i)) {
2118 origstr[but->pos + but->ofs] = 0;
2121 break; /* unlikely but possible */
2124 but->pos += but->ofs;
2125 if (but->pos < 0) but->pos = 0;
2128 if (fstyle->kerning == 1)
2129 BLF_disable(fstyle->uifont_id, BLF_KERNING_DEFAULT);
2131 ui_button_text_password_hide(password_str, but, true);
2135 fstyle->points = fstyle_points_prev;
2138 static void ui_textedit_set_cursor_select(uiBut *but, uiHandleButtonData *data, const float x)
2140 if (x > data->selstartx) data->selextend = EXTEND_RIGHT;
2141 else if (x < data->selstartx) data->selextend = EXTEND_LEFT;
2143 ui_textedit_set_cursor_pos(but, data, x);
2145 if (data->selextend == EXTEND_RIGHT) but->selend = but->pos;
2146 else if (data->selextend == EXTEND_LEFT) but->selsta = but->pos;
2151 /* this is used for both utf8 and ascii, its meant to be used for single keys,
2152 * notice the buffer is either copied or not, so its not suitable for pasting in
2154 static bool ui_textedit_type_buf(uiBut *but, uiHandleButtonData *data,
2155 const char *utf8_buf, int utf8_buf_len)
2159 bool changed = false;
2164 if (len - (but->selend - but->selsta) + 1 <= data->maxlen) {
2165 int step = utf8_buf_len;
2167 /* type over the current selection */
2168 if ((but->selend - but->selsta) > 0) {
2169 changed = ui_textedit_delete_selection(but, data);
2173 if (len + step < data->maxlen) {
2174 memmove(&str[but->pos + step], &str[but->pos], (len + 1) - but->pos);
2175 memcpy(&str[but->pos], utf8_buf, step * sizeof(char));
2184 static bool ui_textedit_type_ascii(uiBut *but, uiHandleButtonData *data, char ascii)
2186 char buf[2] = {ascii, '\0'};
2188 if (ui_is_but_utf8(but) && (BLI_str_utf8_size(buf) == -1)) {
2189 printf("%s: entering invalid ascii char into an ascii key (%d)\n",
2190 __func__, (int)(unsigned char)ascii);
2195 /* in some cases we want to allow invalid utf8 chars */
2196 return ui_textedit_type_buf(but, data, buf, 1);
2199 static void ui_textedit_move(uiBut *but, uiHandleButtonData *data, strCursorJumpDirection direction,
2200 const bool select, strCursorJumpType jump)
2202 const char *str = data->str;
2203 const int len = strlen(str);
2204 const int pos_prev = but->pos;
2205 const bool has_sel = (but->selend - but->selsta) > 0;
2209 /* special case, quit selection and set cursor */
2210 if (has_sel && !select) {
2211 if (jump == STRCUR_JUMP_ALL) {
2212 but->selsta = but->selend = but->pos = direction ? len : 0;
2216 but->selsta = but->pos = but->selend;
2219 but->pos = but->selend = but->selsta;
2222 data->selextend = 0;
2225 int pos_i = but->pos;
2226 BLI_str_cursor_step_utf8(str, len, &pos_i, direction, jump, true);
2230 /* existing selection */
2233 if (data->selextend == 0) {
2234 data->selextend = EXTEND_RIGHT;
2238 if (data->selextend == EXTEND_RIGHT) {
2239 but->selend = but->pos;
2242 but->selsta = but->pos;
2246 if (data->selextend == EXTEND_LEFT) {
2247 but->selsta = but->pos;
2250 but->selend = but->pos;
2254 if (but->selend < but->selsta) {
2255 SWAP(short, but->selsta, but->selend);
2256 data->selextend = (data->selextend == EXTEND_RIGHT) ? EXTEND_LEFT : EXTEND_RIGHT;
2259 } /* new selection */
2262 data->selextend = EXTEND_RIGHT;
2263 but->selend = but->pos;
2264 but->selsta = pos_prev;
2267 data->selextend = EXTEND_LEFT;
2268 but->selend = pos_prev;
2269 but->selsta = but->pos;
2276 static bool ui_textedit_delete(uiBut *but, uiHandleButtonData *data, int direction, strCursorJumpType jump)
2278 char *str = data->str;
2279 const int len = strlen(str);
2281 bool changed = false;
2283 if (jump == STRCUR_JUMP_ALL) {
2284 if (len) changed = true;
2288 else if (direction) { /* delete */
2289 if ((but->selend - but->selsta) > 0) {
2290 changed = ui_textedit_delete_selection(but, data);
2292 else if (but->pos >= 0 && but->pos < len) {
2295 BLI_str_cursor_step_utf8(str, len, &pos, direction, jump, true);
2296 step = pos - but->pos;
2297 memmove(&str[but->pos], &str[but->pos + step], (len + 1) - (but->pos + step));
2301 else { /* backspace */
2303 if ((but->selend - but->selsta) > 0) {
2304 changed = ui_textedit_delete_selection(but, data);
2306 else if (but->pos > 0) {
2310 BLI_str_cursor_step_utf8(str, len, &pos, direction, jump, true);
2311 step = but->pos - pos;
2312 memmove(&str[but->pos - step], &str[but->pos], (len + 1) - but->pos);
2322 static int ui_textedit_autocomplete(bContext *C, uiBut *but, uiHandleButtonData *data)
2329 if (data->searchbox)
2330 changed = ui_searchbox_autocomplete(C, data->searchbox, but, data->str);
2332 changed = but->autocomplete_func(C, str, but->autofunc_arg);
2334 but->pos = strlen(str);
2335 but->selsta = but->selend = but->pos;
2340 /* mode for ui_textedit_copypaste() */
2342 UI_TEXTEDIT_PASTE = 1,
2347 static bool ui_textedit_copypaste(uiBut *but, uiHandleButtonData *data, const int mode)
2351 bool changed = false;
2352 int str_len, buf_len;
2355 str_len = strlen(str);
2358 if (mode == UI_TEXTEDIT_PASTE) {
2359 /* TODO, ensure UTF8 ui_is_but_utf8() - campbell */
2360 /* extract the first line from the clipboard */
2361 pbuf = WM_clipboard_text_get_firstline(false, &buf_len);
2364 char buf[UI_MAX_DRAW_STR] = {0};
2367 buf_len = BLI_strncpy_rlen(buf, pbuf, sizeof(buf));
2369 /* paste over the current selection */
2370 if ((but->selend - but->selsta) > 0) {
2371 ui_textedit_delete_selection(but, data);
2372 str_len = strlen(str);
2375 for (y = 0; y < buf_len; y++) {
2376 /* add contents of buffer */
2377 if (str_len + 1 < data->maxlen) {
2378 for (x = data->maxlen; x > but->pos; x--)
2379 str[x] = str[x - 1];
2380 str[but->pos] = buf[y];
2383 str[str_len] = '\0';
2395 else if (ELEM(mode, UI_TEXTEDIT_COPY, UI_TEXTEDIT_CUT)) {
2396 /* copy the contents to the copypaste buffer */
2397 int sellen = but->selend - but->selsta;
2398 char *buf = MEM_mallocN(sizeof(char) * (sellen + 1), "ui_textedit_copypaste");
2400 BLI_strncpy(buf, str + but->selsta, sellen + 1);
2401 WM_clipboard_text_set(buf, 0);
2404 /* for cut only, delete the selection afterwards */
2405 if (mode == UI_TEXTEDIT_CUT) {
2406 if ((but->selend - but->selsta) > 0) {
2407 changed = ui_textedit_delete_selection(but, data);
2415 static void ui_textedit_begin(bContext *C, uiBut *but, uiHandleButtonData *data)
2420 MEM_freeN(data->str);
2424 #ifdef USE_DRAG_MULTINUM
2425 /* this can happen from multi-drag */
2426 if (data->applied_interactive) {
2427 /* remove any small changes so canceling edit doesn't restore invalid value: T40538 */
2428 data->cancel = true;
2429 ui_apply_button(C, but->block, but, data, true);
2430 data->cancel = false;
2432 data->applied_interactive = false;
2436 /* retrieve string */
2437 data->maxlen = ui_get_but_string_max_length(but);
2438 data->str = MEM_callocN(sizeof(char) * data->maxlen + 1, "textedit str");
2439 ui_get_but_string(but, data->str, data->maxlen);
2441 if (ui_is_but_float(but) && !ui_is_but_unit(but)) {
2442 BLI_str_rstrip_float_zero(data->str, '\0');
2445 if (ELEM(but->type, NUM, NUMSLI)) {
2446 ui_convert_to_unit_alt_name(but, data->str, data->maxlen);
2449 /* won't change from now on */
2450 len = strlen(data->str);
2452 data->origstr = BLI_strdupn(data->str, len);
2453 data->selextend = 0;
2454 data->selstartx = 0.0f;
2456 /* set cursor pos to the end of the text */
2457 but->editstr = data->str;
2462 /* optional searchbox */
2463 if (ELEM(but->type, SEARCH_MENU, SEARCH_MENU_UNLINK)) {
2464 data->searchbox = ui_searchbox_create(C, data->region, but);
2465 ui_searchbox_update(C, data->searchbox, but, true); /* true = reset */
2468 /* reset alert flag (avoid confusion, will refresh on exit) */
2469 but->flag &= ~UI_BUT_REDALERT;
2473 WM_cursor_modal_set(CTX_wm_window(C), BC_TEXTEDITCURSOR);
2476 static void ui_textedit_end(bContext *C, uiBut *but, uiHandleButtonData *data)
2479 if (ui_is_but_utf8(but)) {
2480 int strip = BLI_utf8_invalid_strip(but->editstr, strlen(but->editstr));
2481 /* not a file?, strip non utf-8 chars */
2483 /* wont happen often so isn't that annoying to keep it here for a while */
2484 printf("%s: invalid utf8 - stripped chars %d\n", __func__, strip);
2488 if (data->searchbox) {
2489 if (data->cancel == false) {
2490 if ((ui_searchbox_apply(but, data->searchbox) == false) &&
2491 (ui_searchbox_find_index(data->searchbox, but->editstr) == -1))
2493 data->cancel = true;
2495 /* ensure menu (popup) too is closed! */
2496 data->escapecancel = true;
2500 ui_searchbox_free(C, data->searchbox);
2501 data->searchbox = NULL;
2504 but->editstr = NULL;
2508 WM_cursor_modal_restore(CTX_wm_window(C));
2511 static void ui_textedit_next_but(uiBlock *block, uiBut *actbut, uiHandleButtonData *data)
2515 /* label and roundbox can overlap real buttons (backdrops...) */
2516 if (ELEM(actbut->type, LABEL, SEPR, SEPRLINE, ROUNDBOX, LISTBOX))
2519 for (but = actbut->next; but; but = but->next) {
2520 if (ELEM(but->type, TEX, NUM, NUMSLI, SEARCH_MENU, SEARCH_MENU_UNLINK)) {
2521 if (!(but->flag & UI_BUT_DISABLED)) {
2522 data->postbut = but;
2523 data->posttype = BUTTON_ACTIVATE_TEXT_EDITING;
2528 for (but = block->buttons.first; but != actbut; but = but->next) {
2529 if (ELEM(but->type, TEX, NUM, NUMSLI, SEARCH_MENU, SEARCH_MENU_UNLINK)) {
2530 if (!(but->flag & UI_BUT_DISABLED)) {
2531 data->postbut = but;
2532 data->posttype = BUTTON_ACTIVATE_TEXT_EDITING;
2539 static void ui_textedit_prev_but(uiBlock *block, uiBut *actbut, uiHandleButtonData *data)
2543 /* label and roundbox can overlap real buttons (backdrops...) */
2544 if (ELEM(actbut->type, LABEL, SEPR, SEPRLINE, ROUNDBOX, LISTBOX))
2547 for (but = actbut->prev; but; but = but->prev) {
2548 if (ELEM(but->type, TEX, NUM, NUMSLI, SEARCH_MENU, SEARCH_MENU_UNLINK)) {
2549 if (!(but->flag & UI_BUT_DISABLED)) {
2550 data->postbut = but;
2551 data->posttype = BUTTON_ACTIVATE_TEXT_EDITING;
2556 for (but = block->buttons.last; but != actbut; but = but->prev) {
2557 if (ELEM(but->type, TEX, NUM, NUMSLI, SEARCH_MENU, SEARCH_MENU_UNLINK)) {
2558 if (!(but->flag & UI_BUT_DISABLED)) {
2559 data->postbut = but;
2560 data->posttype = BUTTON_ACTIVATE_TEXT_EDITING;
2568 static void ui_do_but_textedit(bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
2570 int retval = WM_UI_HANDLER_CONTINUE;
2571 bool changed = false, inbox = false, update = false;
2573 switch (event->type) {
2576 if (data->searchbox) {
2577 #ifdef USE_KEYNAV_LIMIT
2578 if ((event->type == MOUSEMOVE) && ui_mouse_motion_keynav_test(&data->searchbox_keynav_state, event)) {
2582 ui_searchbox_event(C, data->searchbox, but, event);
2585 ui_searchbox_event(C, data->searchbox, but, event);
2592 if (event->val == KM_PRESS) {
2593 data->cancel = true;
2594 data->escapecancel = true;
2595 button_activate_state(C, but, BUTTON_STATE_EXIT);
2596 retval = WM_UI_HANDLER_BREAK;
2601 bool had_selection = but->selsta != but->selend;
2603 /* exit on LMB only on RELEASE for searchbox, to mimic other popups, and allow multiple menu levels */
2604 if (data->searchbox)
2605 inbox = ui_searchbox_inside(data->searchbox, event->x, event->y);
2607 /* for double click: we do a press again for when you first click on button (selects all text, no cursor pos) */
2608 if (event->val == KM_PRESS || event->val == KM_DBL_CLICK) {
2613 ui_window_to_block_fl(data->region, block, &mx, &my);
2615 if (ui_but_contains_pt(but, mx, my)) {
2616 ui_textedit_set_cursor_pos(but, data, event->x);
2617 but->selsta = but->selend = but->pos;
2618 data->selstartx = event->x;
2620 button_activate_state(C, but, BUTTON_STATE_TEXT_SELECTING);
2621 retval = WM_UI_HANDLER_BREAK;
2623 else if (inbox == false) {
2624 /* if searchbox, click outside will cancel */
2625 if (data->searchbox)
2626 data->cancel = data->escapecancel = true;
2627 button_activate_state(C, but, BUTTON_STATE_EXIT);
2628 retval = WM_UI_HANDLER_BREAK;
2632 /* only select a word in button if there was no selection before */
2633 if (event->val == KM_DBL_CLICK && had_selection == false) {
2634 ui_textedit_move(but, data, STRCUR_DIR_PREV, false, STRCUR_JUMP_DELIM);
2635 ui_textedit_move(but, data, STRCUR_DIR_NEXT, true, STRCUR_JUMP_DELIM);
2636 retval = WM_UI_HANDLER_BREAK;
2640 /* if we allow activation on key press, it gives problems launching operators [#35713] */
2641 if (event->val == KM_RELEASE) {
2642 button_activate_state(C, but, BUTTON_STATE_EXIT);
2643 retval = WM_UI_HANDLER_BREAK;
2650 if (event->val == KM_PRESS) {
2651 switch (event->type) {
2655 if (event->ctrl || event->oskey) {
2656 if (event->type == VKEY)
2657 changed = ui_textedit_copypaste(but, data, UI_TEXTEDIT_PASTE);
2658 else if (event->type == CKEY)
2659 changed = ui_textedit_copypaste(but, data, UI_TEXTEDIT_COPY);
2660 else if (event->type == XKEY)
2661 changed = ui_textedit_copypaste(but, data, UI_TEXTEDIT_CUT);
2663 retval = WM_UI_HANDLER_BREAK;
2667 ui_textedit_move(but, data, STRCUR_DIR_NEXT,
2668 event->shift != 0, event->ctrl ? STRCUR_JUMP_DELIM : STRCUR_JUMP_NONE);
2669 retval = WM_UI_HANDLER_BREAK;
2672 ui_textedit_move(but, data, STRCUR_DIR_PREV,
2673 event->shift != 0, event->ctrl ? STRCUR_JUMP_DELIM : STRCUR_JUMP_NONE);
2674 retval = WM_UI_HANDLER_BREAK;
2676 case WHEELDOWNMOUSE:
2678 if (data->searchbox) {
2679 #ifdef USE_KEYNAV_LIMIT
2680 ui_mouse_motion_keynav_init(&data->searchbox_keynav_state, event);
2682 ui_searchbox_event(C, data->searchbox, but, event);
2687 ui_textedit_move(but, data, STRCUR_DIR_NEXT,
2688 event->shift != 0, STRCUR_JUMP_ALL);
2689 retval = WM_UI_HANDLER_BREAK;
2693 if (data->searchbox) {
2694 #ifdef USE_KEYNAV_LIMIT
2695 ui_mouse_motion_keynav_init(&data->searchbox_keynav_state, event);
2697 ui_searchbox_event(C, data->searchbox, but, event);
2702 ui_textedit_move(but, data, STRCUR_DIR_PREV,
2703 event->shift != 0, STRCUR_JUMP_ALL);
2704 retval = WM_UI_HANDLER_BREAK;
2708 button_activate_state(C, but, BUTTON_STATE_EXIT);
2709 retval = WM_UI_HANDLER_BREAK;
2712 changed = ui_textedit_delete(but, data, 1,
2713 event->ctrl ? STRCUR_JUMP_DELIM : STRCUR_JUMP_NONE);
2714 retval = WM_UI_HANDLER_BREAK;
2718 changed = ui_textedit_delete(but, data, 0,
2719 event->ctrl ? STRCUR_JUMP_DELIM : STRCUR_JUMP_NONE);
2720 retval = WM_UI_HANDLER_BREAK;
2725 /* Ctrl + A: Select all */
2726 #if defined(__APPLE__)
2727 /* OSX uses cmd-a systemwide, so add it */
2728 if ((event->oskey && !(event->alt || event->shift || event->ctrl)) ||
2729 (event->ctrl && !(event->alt || event->shift || event->oskey)))
2731 if (event->ctrl && !(event->alt || event->shift || event->oskey))
2734 ui_textedit_move(but, data, STRCUR_DIR_PREV,
2735 false, STRCUR_JUMP_ALL);
2736 ui_textedit_move(but, data, STRCUR_DIR_NEXT,
2737 true, STRCUR_JUMP_ALL);
2738 retval = WM_UI_HANDLER_BREAK;
2743 /* there is a key conflict here, we can't tab with autocomplete */
2744 if (but->autocomplete_func || data->searchbox) {
2745 int autocomplete = ui_textedit_autocomplete(C, but, data);
2746 changed = autocomplete != AUTOCOMPLETE_NO_MATCH;
2748 if (autocomplete == AUTOCOMPLETE_FULL_MATCH)
2749 button_activate_state(C, but, BUTTON_STATE_EXIT);
2751 update = true; /* do live update for tab key */
2753 /* the hotkey here is not well defined, was G.qual so we check all */
2754 else if (event->shift || event->ctrl || event->alt || event->oskey) {
2755 ui_textedit_prev_but(block, but, data);
2756 button_activate_state(C, but, BUTTON_STATE_EXIT);
2759 ui_textedit_next_but(block, but, data);
2760 button_activate_state(C, but, BUTTON_STATE_EXIT);
2762 retval = WM_UI_HANDLER_BREAK;
2766 if ((event->ascii || event->utf8_buf[0]) && (retval == WM_UI_HANDLER_CONTINUE)) {
2767 char ascii = event->ascii;
2768 const char *utf8_buf = event->utf8_buf;
2770 /* exception that's useful for number buttons, some keyboard
2771 * numpads have a comma instead of a period */
2772 if (ELEM(but->type, NUM, NUMSLI)) { /* could use data->min*/
2773 if (event->type == PADPERIOD && ascii == ',') {
2775 utf8_buf = NULL; /* force ascii fallback */
2779 if (utf8_buf && utf8_buf[0]) {
2780 int utf8_buf_len = BLI_str_utf8_size(utf8_buf);
2781 /* keep this printf until utf8 is well tested */
2782 if (utf8_buf_len != 1) {
2783 printf("%s: utf8 char '%.*s'\n", __func__, utf8_buf_len, utf8_buf);
2786 // strcpy(utf8_buf, "12345");
2787 changed = ui_textedit_type_buf(but, data, event->utf8_buf, utf8_buf_len);
2790 changed = ui_textedit_type_ascii(but, data, ascii);
2793 retval = WM_UI_HANDLER_BREAK;
2796 /* textbutton with magnifier icon: do live update for search button */
2797 if (but->icon == ICON_VIEWZOOM)
2802 /* only update when typing for TAB key */
2803 if (update && data->interactive) {
2804 ui_apply_button(C, block, but, data, true);
2809 but->changed = true;
2811 if (data->searchbox)
2812 ui_searchbox_update(C, data->searchbox, but, true); /* true = reset */
2815 if (changed || (retval == WM_UI_HANDLER_BREAK))
2816 ED_region_tag_redraw(data->region);
2819 static void ui_do_but_textedit_select(bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
2821 int mx, my, retval = WM_UI_HANDLER_CONTINUE;
2823 switch (event->type) {
2828 ui_window_to_block(data->region, block, &mx, &my);
2830 ui_textedit_set_cursor_select(but, data, event->x);
2831 retval = WM_UI_HANDLER_BREAK;
2835 if (event->val == KM_RELEASE)
2836 button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING);
2837 retval = WM_UI_HANDLER_BREAK;
2841 if (retval == WM_UI_HANDLER_BREAK) {
2843 ED_region_tag_redraw(data->region);
2847 /* ************* number editing for various types ************* */
2849 static void ui_numedit_begin(uiBut *but, uiHandleButtonData *data)
2851 if (but->type == BUT_CURVE) {
2852 but->editcumap = (CurveMapping *)but->poin;
2854 else if (but->type == BUT_COLORBAND) {
2855 data->coba = (ColorBand *)but->poin;
2856 but->editcoba = data->coba;
2858 else if (ELEM(but->type, BUT_NORMAL, HSVCUBE, HSVCIRCLE, COLOR)) {
2859 ui_get_but_vectorf(but, data->origvec);
2860 copy_v3_v3(data->vec, data->origvec);
2861 but->editvec = data->vec;
2864 float softrange, softmin, softmax;
2866 data->startvalue = ui_get_but_val(but);
2867 data->origvalue = data->startvalue;
2868 data->value = data->origvalue;
2869 but->editval = &data->value;
2871 softmin = but->softmin;
2872 softmax = but->softmax;
2873 softrange = softmax - softmin;
2875 data->dragfstart = (softrange == 0.0f) ? 0.0f : ((float)data->value - softmin) / softrange;
2876 data->dragf = data->dragfstart;
2879 data->dragchange = false;
2880 data->draglock = true;
2883 static void ui_numedit_end(uiBut *but, uiHandleButtonData *data)
2885 but->editval = NULL;
2886 but->editvec = NULL;
2887 but->editcoba = NULL;
2888 but->editcumap = NULL;
2890 data->dragstartx = 0;
2891 data->draglastx = 0;
2892 data->dragchange = false;
2893 data->dragcbd = NULL;
2897 static void ui_numedit_apply(bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data)
2899 if (data->interactive) {
2900 ui_apply_button(C, block, but, data, true);
2906 ED_region_tag_redraw(data->region);
2909 /* ****************** menu opening for various types **************** */
2911 static void ui_blockopen_begin(bContext *C, uiBut *but, uiHandleButtonData *data)
2913 uiBlockCreateFunc func = NULL;
2914 uiBlockHandleCreateFunc handlefunc = NULL;
2915 uiMenuCreateFunc menufunc = NULL;
2918 switch (but->type) {
2921 if (but->menu_create_func) {
2922 menufunc = but->menu_create_func;
2926 func = but->block_create_func;
2927 arg = but->poin ? but->poin : but->func_argN;
2931 BLI_assert(but->menu_create_func);
2932 menufunc = but->menu_create_func;
2936 ui_get_but_vectorf(but, data->origvec);
2937 copy_v3_v3(data->vec, data->origvec);
2938 but->editvec = data->vec;
2940 handlefunc = ui_block_func_COLOR;
2944 /* quiet warnings for unhandled types */
2949 if (func || handlefunc) {
2950 data->menu = ui_popup_block_create(C, data->region, but, func, handlefunc, arg);
2951 if (but->block->handle)
2952 data->menu->popup = but->block->handle->popup;
2954 else if (menufunc) {
2955 data->menu = ui_popup_menu_create(C, data->region, but, menufunc, arg);
2956 if (but->block->handle)