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_path_util.h"
54 #include "BLI_string.h"
55 #include "BLI_string_utf8.h"
56 #include "BLI_string_cursor_utf8.h"
58 #include "BLI_utildefines.h"
60 #include "BLT_translation.h"
64 #include "BKE_blender_undo.h"
65 #include "BKE_brush.h"
66 #include "BKE_colortools.h"
67 #include "BKE_context.h"
68 #include "BKE_idprop.h"
69 #include "BKE_report.h"
70 #include "BKE_screen.h"
71 #include "BKE_texture.h"
72 #include "BKE_tracking.h"
74 #include "BKE_paint.h"
76 #include "ED_screen.h"
78 #include "ED_keyframing.h"
80 #include "UI_interface.h"
84 #include "interface_intern.h"
86 #include "RNA_access.h"
90 #include "wm_event_system.h"
93 # include "wm_window.h"
94 # include "BLT_lang.h"
97 /* place the mouse at the scaled down location when un-grabbing */
98 #define USE_CONT_MOUSE_CORRECT
99 /* support dragging toggle buttons */
100 #define USE_DRAG_TOGGLE
102 /* support dragging multiple number buttons at once */
103 #define USE_DRAG_MULTINUM
105 /* allow dragging/editing all other selected items at once */
106 #define USE_ALLSELECT
108 /* so we can avoid very small mouse-moves from jumping away from keyboard navigation [#34936] */
109 #define USE_KEYNAV_LIMIT
111 /* drag popups by their header */
112 #define USE_DRAG_POPUP
114 #define UI_MAX_PASSWORD_STR 128
116 /* This hack is needed because we don't have a good way to re-reference keymap items once added: T42944 */
117 #define USE_KEYMAP_ADD_HACK
120 static void ui_but_smart_controller_add(bContext *C, uiBut *from, uiBut *to);
121 static void ui_but_link_add(bContext *C, uiBut *from, uiBut *to);
122 static int ui_do_but_EXIT(bContext *C, uiBut *but, struct uiHandleButtonData *data, const wmEvent *event);
123 static bool ui_but_find_select_in_enum__cmp(const uiBut *but_a, const uiBut *but_b);
124 static void ui_textedit_string_set(uiBut *but, struct uiHandleButtonData *data, const char *str);
126 #ifdef USE_KEYNAV_LIMIT
127 static void ui_mouse_motion_keynav_init(struct uiKeyNavLock *keynav, const wmEvent *event);
128 static bool ui_mouse_motion_keynav_test(struct uiKeyNavLock *keynav, const wmEvent *event);
131 /***************** structs and defines ****************/
133 #define BUTTON_TOOLTIP_DELAY 0.500
134 #define BUTTON_FLASH_DELAY 0.020
135 #define MENU_SCROLL_INTERVAL 0.1
136 #define PIE_MENU_INTERVAL 0.01
137 #define BUTTON_AUTO_OPEN_THRESH 0.3
138 #define BUTTON_MOUSE_TOWARDS_THRESH 1.0
139 /* pixels to move the cursor to get out of keyboard navigation */
140 #define BUTTON_KEYNAV_PX_LIMIT 8
142 #define MENU_TOWARDS_MARGIN 20 /* margin in pixels */
143 #define MENU_TOWARDS_WIGGLE_ROOM 64 /* tolerance in pixels */
144 /* drag-lock distance threshold in pixels */
145 #define BUTTON_DRAGLOCK_THRESH 3
147 typedef enum uiButtonActivateType {
148 BUTTON_ACTIVATE_OVER,
150 BUTTON_ACTIVATE_APPLY,
151 BUTTON_ACTIVATE_TEXT_EDITING,
153 } uiButtonActivateType;
155 typedef enum uiHandleButtonState {
157 BUTTON_STATE_HIGHLIGHT,
158 BUTTON_STATE_WAIT_FLASH,
159 BUTTON_STATE_WAIT_RELEASE,
160 BUTTON_STATE_WAIT_KEY_EVENT,
161 BUTTON_STATE_NUM_EDITING,
162 BUTTON_STATE_TEXT_EDITING,
163 BUTTON_STATE_TEXT_SELECTING,
164 BUTTON_STATE_MENU_OPEN,
165 BUTTON_STATE_WAIT_DRAG,
167 } uiHandleButtonState;
172 /* Unfortunately theres no good way handle more generally:
173 * (propagate single clicks on layer buttons to other objects) */
174 #define USE_ALLSELECT_LAYER_HACK
176 typedef struct uiSelectContextElem {
183 } uiSelectContextElem;
185 typedef struct uiSelectContextStore {
186 uiSelectContextElem *elems;
189 /* When set, simply copy values (don't apply difference).
191 * - dragging numbers uses delta.
192 * - typing in values will assign to all. */
194 } uiSelectContextStore;
196 static bool ui_selectcontext_begin(
197 bContext *C, uiBut *but, struct uiSelectContextStore *selctx_data);
198 static void ui_selectcontext_end(
199 uiBut *but, uiSelectContextStore *selctx_data);
200 static void ui_selectcontext_apply(
201 bContext *C, uiBut *but, struct uiSelectContextStore *selctx_data,
202 const double value, const double value_orig);
205 #define IS_ALLSELECT_EVENT(event) ((event)->alt != 0)
208 /** just show a tinted color so users know its activated */
209 #define UI_BUT_IS_SELECT_CONTEXT UI_BUT_NODE_ACTIVE
211 #endif /* USE_ALLSELECT */
214 #ifdef USE_DRAG_MULTINUM
217 * how far to drag before we check for gesture direction (in pixels),
218 * note: half the height of a button is about right... */
219 #define DRAG_MULTINUM_THRESHOLD_DRAG_X (UI_UNIT_Y / 4)
222 * how far to drag horizontally before we stop checking which buttons the gesture spans (in pixels),
223 * locking down the buttons so we can drag freely without worrying about vertical movement. */
224 #define DRAG_MULTINUM_THRESHOLD_DRAG_Y (UI_UNIT_Y / 4)
227 * how strict to be when detecting a vertical gesture, [0.5 == sloppy], [0.9 == strict], (unsigned dot-product)
228 * note: we should be quite strict here, since doing a vertical gesture by accident should be avoided,
229 * however with some care a user should be able to do a vertical movement without *missing*. */
230 #define DRAG_MULTINUM_THRESHOLD_VERTICAL (0.75f)
233 /* a simple version of uiHandleButtonData when accessing multiple buttons */
234 typedef struct uiButMultiState {
239 uiSelectContextStore select_others;
243 typedef struct uiHandleButtonMulti {
245 BUTTON_MULTI_INIT_UNSET = 0, /* gesture direction unknown, wait until mouse has moved enough... */
246 BUTTON_MULTI_INIT_SETUP, /* vertical gesture detected, flag buttons interactively (UI_BUT_DRAG_MULTI) */
247 BUTTON_MULTI_INIT_ENABLE, /* flag buttons finished, apply horizontal motion to active and flagged */
248 BUTTON_MULTI_INIT_DISABLE, /* vertical gesture _not_ detected, take no further action */
251 bool has_mbuts; /* any buttons flagged UI_BUT_DRAG_MULTI */
253 uiButStore *bs_mbuts;
255 bool is_proportional;
257 /* before activating, we need to check gesture direction
258 * accumulate signed cursor movement here so we can tell if this is a vertical motion or not. */
261 /* values copied direct from event->x,y
262 * used to detect buttons between the current and initial mouse position */
265 /* store x location once BUTTON_MULTI_INIT_SETUP is set,
266 * moving outside this sets BUTTON_MULTI_INIT_ENABLE */
269 } uiHandleButtonMulti;
271 #endif /* USE_DRAG_MULTINUM */
273 typedef struct uiHandleButtonData {
281 uiHandleButtonState state;
283 /* booleans (could be made into flags) */
284 bool cancel, escapecancel;
285 bool applied, applied_interactive;
289 /* use 'ui_textedit_string_set' to assign new strings */
292 double value, origvalue, startvalue;
293 float vec[3], origvec[3];
295 int togdual, togonly;
301 wmTimer *tooltiptimer;
302 unsigned int tooltip_force : 1;
306 wmTimer *autoopentimer;
308 /* text selection/editing */
309 /* size of 'str' (including terminator) */
311 /* Button text selection:
312 * extension direction, selextend, inside ui_do_but_TEX */
319 /* allow to realloc str/editstr and use 'maxlen' to track alloc size (maxlen + 1) */
322 /* number editing / dragging */
323 /* coords are Window/uiBlock relative (depends on the button) */
324 int draglastx, draglasty;
325 int dragstartx, dragstarty;
328 bool dragchange, draglock;
330 float dragf, dragfstart;
333 #ifdef USE_CONT_MOUSE_CORRECT
334 /* when ungrabbing buttons which are #ui_but_is_cursor_warp(), we may want to position them
335 * FLT_MAX signifies do-nothing, use #ui_block_to_window_fl() to get this into a usable space */
336 float ungrab_mval[2];
339 /* menu open (watch UI_screen_free_active_but) */
340 uiPopupBlockHandle *menu;
343 /* search box (watch UI_screen_free_active_but) */
345 #ifdef USE_KEYNAV_LIMIT
346 struct uiKeyNavLock searchbox_keynav_state;
349 #ifdef USE_DRAG_MULTINUM
350 /* Multi-buttons will be updated in unison with the active button. */
351 uiHandleButtonMulti multi_data;
355 uiSelectContextStore select_others;
359 uiButtonActivateType posttype;
361 } uiHandleButtonData;
363 typedef struct uiAfterFunc {
364 struct uiAfterFunc *next, *prev;
366 uiButHandleFunc func;
370 uiButHandleNFunc funcN;
373 uiButHandleRenameFunc rename_func;
377 uiBlockHandleFunc handle_func;
378 void *handle_func_arg;
381 uiMenuHandleFunc butm_func;
385 wmOperator *popup_op;
386 wmOperatorType *optype;
391 PropertyRNA *rnaprop;
393 bContextStore *context;
395 char undostr[BKE_UNDO_STR_MAX];
400 static bool ui_but_is_interactive(const uiBut *but, const bool labeledit);
401 static bool ui_but_contains_pt(uiBut *but, float mx, float my);
402 static bool ui_but_contains_point_px(ARegion *ar, uiBut *but, int x, int y);
403 static uiBut *ui_but_find_mouse_over_ex(ARegion *ar, const int x, const int y, const bool labeledit);
404 static void button_activate_init(bContext *C, ARegion *ar, uiBut *but, uiButtonActivateType type);
405 static void button_activate_state(bContext *C, uiBut *but, uiHandleButtonState state);
406 static void button_activate_exit(
407 bContext *C, uiBut *but, uiHandleButtonData *data,
408 const bool mousemove, const bool onfree);
409 static int ui_handler_region_menu(bContext *C, const wmEvent *event, void *userdata);
410 static void ui_handle_button_activate(bContext *C, ARegion *ar, uiBut *but, uiButtonActivateType type);
412 #ifdef USE_DRAG_MULTINUM
413 static void ui_multibut_restore(bContext *C, uiHandleButtonData *data, uiBlock *block);
414 static uiButMultiState *ui_multibut_lookup(uiHandleButtonData *data, const uiBut *but);
417 /* buttons clipboard */
418 static ColorBand but_copypaste_coba = {0};
419 static CurveMapping but_copypaste_curve = {0};
420 static bool but_copypaste_curve_alive = false;
422 /* ******************** menu navigation helpers ************** */
429 static enum eSnapType ui_event_to_snap(const wmEvent *event)
431 return (event->ctrl) ? (event->shift) ? SNAP_ON_SMALL : SNAP_ON : SNAP_OFF;
434 static bool ui_event_is_snap(const wmEvent *event)
436 return (ELEM(event->type, LEFTCTRLKEY, RIGHTCTRLKEY) ||
437 ELEM(event->type, LEFTSHIFTKEY, RIGHTSHIFTKEY));
440 static void ui_color_snap_hue(const enum eSnapType snap, float *r_hue)
442 const float snap_increment = (snap == SNAP_ON_SMALL) ? 24 : 12;
443 BLI_assert(snap != SNAP_OFF);
444 *r_hue = roundf((*r_hue) * snap_increment) / snap_increment;
447 /* assumes event type is MOUSEPAN */
448 void ui_pan_to_scroll(const wmEvent *event, int *type, int *val)
450 static int lastdy = 0;
451 int dy = event->prevy - event->y;
453 /* This event should be originally from event->type,
454 * converting wrong event into wheel is bad, see [#33803] */
455 BLI_assert(*type == MOUSEPAN);
457 /* sign differs, reset */
458 if ((dy > 0 && lastdy < 0) || (dy < 0 && lastdy > 0)) {
464 if (ABS(lastdy) > (int)UI_UNIT_Y) {
465 if (U.uiflag2 & USER_TRACKPAD_NATURAL)
471 *type = WHEELUPMOUSE;
473 *type = WHEELDOWNMOUSE;
480 bool ui_but_is_editable(const uiBut *but)
482 return !ELEM(but->type,
483 UI_BTYPE_LABEL, UI_BTYPE_SEPR, UI_BTYPE_SEPR_LINE,
484 UI_BTYPE_ROUNDBOX, UI_BTYPE_LISTBOX, UI_BTYPE_PROGRESS_BAR);
487 bool ui_but_is_editable_as_text(const uiBut *but)
489 return ELEM(but->type,
490 UI_BTYPE_TEXT, UI_BTYPE_NUM, UI_BTYPE_NUM_SLIDER,
491 UI_BTYPE_SEARCH_MENU);
495 static uiBut *ui_but_prev(uiBut *but)
499 if (ui_but_is_editable(but)) return but;
504 static uiBut *ui_but_next(uiBut *but)
508 if (ui_but_is_editable(but)) return but;
513 static uiBut *ui_but_first(uiBlock *block)
517 but = block->buttons.first;
519 if (ui_but_is_editable(but)) return but;
525 static uiBut *ui_but_last(uiBlock *block)
529 but = block->buttons.last;
531 if (ui_but_is_editable(but)) return but;
537 static bool ui_but_is_cursor_warp(uiBut *but)
539 if (U.uiflag & USER_CONTINUOUS_MOUSE) {
541 UI_BTYPE_NUM, UI_BTYPE_NUM_SLIDER, UI_BTYPE_HSVCIRCLE,
542 UI_BTYPE_TRACK_PREVIEW, UI_BTYPE_HSVCUBE, UI_BTYPE_CURVE))
552 * Ignore mouse movements within some horizontal pixel threshold before starting to drag
554 static bool ui_but_dragedit_update_mval(uiHandleButtonData *data, int mx)
556 if (mx == data->draglastx)
559 if (data->draglock) {
560 if (ABS(mx - data->dragstartx) <= BUTTON_DRAGLOCK_THRESH) {
563 #ifdef USE_DRAG_MULTINUM
564 if (ELEM(data->multi_data.init, BUTTON_MULTI_INIT_UNSET, BUTTON_MULTI_INIT_SETUP)) {
568 data->draglock = false;
569 data->dragstartx = mx; /* ignore mouse movement within drag-lock */
575 static float ui_mouse_scale_warp_factor(const bool shift)
577 return shift ? 0.05f : 1.0f;
580 static void ui_mouse_scale_warp(
581 uiHandleButtonData *data, const float mx, const float my,
582 float *r_mx, float *r_my, const bool shift)
584 const float fac = ui_mouse_scale_warp_factor(shift);
586 /* slow down the mouse, this is fairly picky */
587 *r_mx = (data->dragstartx * (1.0f - fac) + mx * fac);
588 *r_my = (data->dragstarty * (1.0f - fac) + my * fac);
591 /* file selectors are exempt from utf-8 checks */
592 bool ui_but_is_utf8(const uiBut *but)
595 const int subtype = RNA_property_subtype(but->rnaprop);
596 return !(ELEM(subtype, PROP_FILEPATH, PROP_DIRPATH, PROP_FILENAME, PROP_BYTESTRING));
599 return !(but->flag & UI_BUT_NO_UTF8);
603 /* ********************** button apply/revert ************************/
605 static ListBase UIAfterFuncs = {NULL, NULL};
607 static uiAfterFunc *ui_afterfunc_new(void)
611 after = MEM_callocN(sizeof(uiAfterFunc), "uiAfterFunc");
613 BLI_addtail(&UIAfterFuncs, after);
619 * For executing operators after the button is pressed.
620 * (some non operator buttons need to trigger operators), see: [#37795]
622 * \note Can only call while handling buttons.
624 PointerRNA *ui_handle_afterfunc_add_operator(wmOperatorType *ot, int opcontext, bool create_props)
626 PointerRNA *ptr = NULL;
627 uiAfterFunc *after = ui_afterfunc_new();
630 after->opcontext = opcontext;
633 ptr = MEM_callocN(sizeof(PointerRNA), __func__);
634 WM_operator_properties_create_ptr(ptr, ot);
641 static void popup_check(bContext *C, wmOperator *op)
643 if (op && op->type->check && op->type->check(C, op)) {
644 /* check for popup and re-layout buttons */
645 ARegion *ar_menu = CTX_wm_menu(C);
647 ED_region_tag_refresh_ui(ar_menu);
652 * Check if a #uiAfterFunc is needed for this button.
654 static bool ui_afterfunc_check(const uiBlock *block, const uiBut *but)
656 return (but->func || but->funcN || but->rename_func || but->optype || but->rnaprop || block->handle_func ||
657 (but->type == UI_BTYPE_BUT_MENU && block->butm_func) ||
658 (block->handle && block->handle->popup_op));
661 static void ui_apply_but_func(bContext *C, uiBut *but)
664 uiBlock *block = but->block;
666 /* these functions are postponed and only executed after all other
667 * handling is done, i.e. menus are closed, in order to avoid conflicts
668 * with these functions removing the buttons we are working with */
670 if (ui_afterfunc_check(block, but)) {
671 after = ui_afterfunc_new();
673 if (but->func && ELEM(but, but->func_arg1, but->func_arg2)) {
674 /* exception, this will crash due to removed button otherwise */
675 but->func(C, but->func_arg1, but->func_arg2);
678 after->func = but->func;
680 after->func_arg1 = but->func_arg1;
681 after->func_arg2 = but->func_arg2;
683 after->funcN = but->funcN;
684 after->func_argN = (but->func_argN) ? MEM_dupallocN(but->func_argN) : NULL;
686 after->rename_func = but->rename_func;
687 after->rename_arg1 = but->rename_arg1;
688 after->rename_orig = but->rename_orig; /* needs free! */
690 after->handle_func = block->handle_func;
691 after->handle_func_arg = block->handle_func_arg;
692 after->retval = but->retval;
694 if (but->type == UI_BTYPE_BUT_MENU) {
695 after->butm_func = block->butm_func;
696 after->butm_func_arg = block->butm_func_arg;
701 after->popup_op = block->handle->popup_op;
703 after->optype = but->optype;
704 after->opcontext = but->opcontext;
705 after->opptr = but->opptr;
707 after->rnapoin = but->rnapoin;
708 after->rnaprop = but->rnaprop;
711 after->context = CTX_store_copy(but->context);
719 /* typically call ui_apply_but_undo(), ui_apply_but_autokey() */
720 static void ui_apply_but_undo(uiBut *but)
724 if (but->flag & UI_BUT_UNDO) {
725 const char *str = NULL;
727 /* define which string to use for undo */
728 if (ELEM(but->type, UI_BTYPE_LINK, UI_BTYPE_INLINK)) str = "Add button link";
729 else if (but->type == UI_BTYPE_MENU) str = but->drawstr;
730 else if (but->drawstr[0]) str = but->drawstr;
733 /* fallback, else we don't get an undo! */
734 if (str == NULL || str[0] == '\0') {
735 str = "Unknown Action";
738 /* delayed, after all other funcs run, popups are closed, etc */
739 after = ui_afterfunc_new();
740 BLI_strncpy(after->undostr, str, sizeof(after->undostr));
744 static void ui_apply_but_autokey(bContext *C, uiBut *but)
746 Scene *scene = CTX_data_scene(C);
749 ui_but_anim_autokey(C, but, scene, scene->r.cfra);
751 /* make a little report about what we've done! */
755 if (RNA_property_subtype(but->rnaprop) == PROP_PASSWORD) {
759 buf = WM_prop_pystring_assign(C, &but->rnapoin, but->rnaprop, but->rnaindex);
761 BKE_report(CTX_wm_reports(C), RPT_PROPERTY, buf);
764 WM_event_add_notifier(C, NC_SPACE | ND_SPACE_INFO_REPORT, NULL);
769 static void ui_apply_but_funcs_after(bContext *C)
771 uiAfterFunc *afterf, after;
775 /* copy to avoid recursive calls */
776 funcs = UIAfterFuncs;
777 BLI_listbase_clear(&UIAfterFuncs);
779 for (afterf = funcs.first; afterf; afterf = after.next) {
780 after = *afterf; /* copy to avoid memleak on exit() */
781 BLI_freelinkN(&funcs, afterf);
784 CTX_store_set(C, after.context);
787 popup_check(C, after.popup_op);
790 /* free in advance to avoid leak on exit */
791 opptr = *after.opptr;
792 MEM_freeN(after.opptr);
796 WM_operator_name_call_ptr(C, after.optype, after.opcontext, (after.opptr) ? &opptr : NULL);
799 WM_operator_properties_free(&opptr);
801 if (after.rnapoin.data)
802 RNA_property_update(C, &after.rnapoin, after.rnaprop);
805 CTX_store_set(C, NULL);
806 CTX_store_free(after.context);
810 after.func(C, after.func_arg1, after.func_arg2);
812 after.funcN(C, after.func_argN, after.func_arg2);
814 MEM_freeN(after.func_argN);
816 if (after.handle_func)
817 after.handle_func(C, after.handle_func_arg, after.retval);
819 after.butm_func(C, after.butm_func_arg, after.a2);
821 if (after.rename_func)
822 after.rename_func(C, after.rename_arg1, after.rename_orig);
823 if (after.rename_orig)
824 MEM_freeN(after.rename_orig);
826 if (after.undostr[0])
827 ED_undo_push(C, after.undostr);
831 static void ui_apply_but_BUT(bContext *C, uiBut *but, uiHandleButtonData *data)
833 ui_apply_but_func(C, but);
835 data->retval = but->retval;
836 data->applied = true;
839 static void ui_apply_but_BUTM(bContext *C, uiBut *but, uiHandleButtonData *data)
841 ui_but_value_set(but, but->hardmin);
842 ui_apply_but_func(C, but);
844 data->retval = but->retval;
845 data->applied = true;
848 static void ui_apply_but_BLOCK(bContext *C, uiBut *but, uiHandleButtonData *data)
850 if (but->type == UI_BTYPE_MENU)
851 ui_but_value_set(but, data->value);
853 ui_but_update_edited(but);
854 ui_apply_but_func(C, but);
855 data->retval = but->retval;
856 data->applied = true;
859 static void ui_apply_but_TOG(bContext *C, uiBut *but, uiHandleButtonData *data)
864 value = ui_but_value_get(but);
868 w = UI_BITBUT_TEST(lvalue, but->bitnr);
869 if (w) lvalue = UI_BITBUT_CLR(lvalue, but->bitnr);
870 else lvalue = UI_BITBUT_SET(lvalue, but->bitnr);
872 ui_but_value_set(but, (double)lvalue);
873 if (but->type == UI_BTYPE_ICON_TOGGLE || but->type == UI_BTYPE_ICON_TOGGLE_N) {
874 ui_but_update_edited(but);
879 if (value == 0.0) push = 1;
882 if (ELEM(but->type, UI_BTYPE_TOGGLE_N, UI_BTYPE_ICON_TOGGLE_N, UI_BTYPE_CHECKBOX_N)) push = !push;
883 ui_but_value_set(but, (double)push);
884 if (but->type == UI_BTYPE_ICON_TOGGLE || but->type == UI_BTYPE_ICON_TOGGLE_N) {
885 ui_but_update_edited(but);
889 ui_apply_but_func(C, but);
891 data->retval = but->retval;
892 data->applied = true;
895 static void ui_apply_but_ROW(bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data)
899 ui_but_value_set(but, but->hardmax);
901 ui_apply_but_func(C, but);
903 /* states of other row buttons */
904 for (bt = block->buttons.first; bt; bt = bt->next) {
905 if (bt != but && bt->poin == but->poin && ELEM(bt->type, UI_BTYPE_ROW, UI_BTYPE_LISTROW)) {
906 ui_but_update_edited(bt);
910 data->retval = but->retval;
911 data->applied = true;
914 static void ui_apply_but_TEX(bContext *C, uiBut *but, uiHandleButtonData *data)
919 ui_but_string_set(C, but, data->str);
920 ui_but_update_edited(but);
922 /* give butfunc a copy of the original text too.
923 * feature used for bone renaming, channels, etc.
924 * afterfunc frees rename_orig */
925 if (data->origstr && (but->flag & UI_BUT_TEXTEDIT_UPDATE)) {
926 /* In this case, we need to keep origstr available, to restore real org string in case we cancel after
927 * having typed something already. */
928 but->rename_orig = BLI_strdup(data->origstr);
930 /* only if there are afterfuncs, otherwise 'renam_orig' isn't freed */
931 else if (ui_afterfunc_check(but->block, but)) {
932 but->rename_orig = data->origstr;
933 data->origstr = NULL;
935 ui_apply_but_func(C, but);
937 data->retval = but->retval;
938 data->applied = true;
941 static void ui_apply_but_NUM(bContext *C, uiBut *but, uiHandleButtonData *data)
944 if (ui_but_string_set(C, but, data->str)) {
945 data->value = ui_but_value_get(but);
953 ui_but_value_set(but, data->value);
956 ui_but_update_edited(but);
957 ui_apply_but_func(C, but);
959 data->retval = but->retval;
960 data->applied = true;
963 static void ui_apply_but_VEC(bContext *C, uiBut *but, uiHandleButtonData *data)
965 ui_but_v3_set(but, data->vec);
966 ui_but_update_edited(but);
967 ui_apply_but_func(C, but);
969 data->retval = but->retval;
970 data->applied = true;
973 static void ui_apply_but_COLORBAND(bContext *C, uiBut *but, uiHandleButtonData *data)
975 ui_apply_but_func(C, but);
976 data->retval = but->retval;
977 data->applied = true;
980 static void ui_apply_but_CURVE(bContext *C, uiBut *but, uiHandleButtonData *data)
982 ui_apply_but_func(C, but);
983 data->retval = but->retval;
984 data->applied = true;
987 /* ****************** drag drop code *********************** */
990 #ifdef USE_DRAG_MULTINUM
992 /* small multi-but api */
993 static void ui_multibut_add(uiHandleButtonData *data, uiBut *but)
995 uiButMultiState *mbut_state;
997 BLI_assert(but->flag & UI_BUT_DRAG_MULTI);
998 BLI_assert(data->multi_data.has_mbuts);
1001 mbut_state = MEM_callocN(sizeof(*mbut_state), __func__);
1002 mbut_state->but = but;
1003 mbut_state->origvalue = ui_but_value_get(but);
1005 BLI_linklist_prepend(&data->multi_data.mbuts, mbut_state);
1007 UI_butstore_register(data->multi_data.bs_mbuts, &mbut_state->but);
1010 static uiButMultiState *ui_multibut_lookup(uiHandleButtonData *data, const uiBut *but)
1014 for (l = data->multi_data.mbuts; l; l = l->next) {
1015 uiButMultiState *mbut_state;
1017 mbut_state = l->link;
1019 if (mbut_state->but == but) {
1027 static void ui_multibut_restore(bContext *C, uiHandleButtonData *data, uiBlock *block)
1031 for (but = block->buttons.first; but; but = but->next) {
1032 if (but->flag & UI_BUT_DRAG_MULTI) {
1033 uiButMultiState *mbut_state = ui_multibut_lookup(data, but);
1035 ui_but_value_set(but, mbut_state->origvalue);
1037 #ifdef USE_ALLSELECT
1038 if (mbut_state->select_others.elems_len > 0) {
1039 ui_selectcontext_apply(
1040 C, but, &mbut_state->select_others,
1041 mbut_state->origvalue, mbut_state->origvalue);
1051 static void ui_multibut_free(uiHandleButtonData *data, uiBlock *block)
1053 #ifdef USE_ALLSELECT
1054 if (data->multi_data.mbuts) {
1055 LinkNode *list = data->multi_data.mbuts;
1057 LinkNode *next = list->next;
1058 uiButMultiState *mbut_state = list->link;
1060 if (mbut_state->select_others.elems) {
1061 MEM_freeN(mbut_state->select_others.elems);
1064 MEM_freeN(list->link);
1070 BLI_linklist_freeN(data->multi_data.mbuts);
1073 data->multi_data.mbuts = NULL;
1075 if (data->multi_data.bs_mbuts) {
1076 UI_butstore_free(block, data->multi_data.bs_mbuts);
1077 data->multi_data.bs_mbuts = NULL;
1081 static bool ui_multibut_states_tag(
1083 uiHandleButtonData *data, const wmEvent *event)
1087 bool changed = false;
1089 seg[0][0] = data->multi_data.drag_start[0];
1090 seg[0][1] = data->multi_data.drag_start[1];
1092 seg[1][0] = event->x;
1093 seg[1][1] = event->y;
1095 BLI_assert(data->multi_data.init == BUTTON_MULTI_INIT_SETUP);
1097 ui_window_to_block_fl(data->region, but_active->block, &seg[0][0], &seg[0][1]);
1098 ui_window_to_block_fl(data->region, but_active->block, &seg[1][0], &seg[1][1]);
1100 data->multi_data.has_mbuts = false;
1102 /* follow ui_but_find_mouse_over_ex logic */
1103 for (but = but_active->block->buttons.first; but; but = but->next) {
1104 bool drag_prev = false;
1105 bool drag_curr = false;
1107 /* re-set each time */
1108 if (but->flag & UI_BUT_DRAG_MULTI) {
1109 but->flag &= ~UI_BUT_DRAG_MULTI;
1113 if (ui_but_is_interactive(but, false)) {
1116 if (but_active != but) {
1117 if (ui_but_is_compatible(but_active, but)) {
1119 BLI_assert(but->active == NULL);
1121 /* finally check for overlap */
1122 if (BLI_rctf_isect_segment(&but->rect, seg[0], seg[1])) {
1124 but->flag |= UI_BUT_DRAG_MULTI;
1125 data->multi_data.has_mbuts = true;
1132 changed |= (drag_prev != drag_curr);
1138 static void ui_multibut_states_create(uiBut *but_active, uiHandleButtonData *data)
1142 BLI_assert(data->multi_data.init == BUTTON_MULTI_INIT_SETUP);
1143 BLI_assert(data->multi_data.has_mbuts);
1145 data->multi_data.bs_mbuts = UI_butstore_create(but_active->block);
1147 for (but = but_active->block->buttons.first; but; but = but->next) {
1148 if (but->flag & UI_BUT_DRAG_MULTI) {
1149 ui_multibut_add(data, but);
1153 /* edit buttons proportionally to eachother
1154 * note: if we mix buttons which are proportional and others which are not,
1155 * this may work a bit strangely */
1156 if (but_active->rnaprop) {
1157 if ((data->origvalue != 0.0) && (RNA_property_flag(but_active->rnaprop) & PROP_PROPORTIONAL)) {
1158 data->multi_data.is_proportional = true;
1163 static void ui_multibut_states_apply(bContext *C, uiHandleButtonData *data, uiBlock *block)
1165 ARegion *ar = data->region;
1166 const double value_delta = data->value - data->origvalue;
1167 const double value_scale = data->multi_data.is_proportional ? (data->value / data->origvalue) : 0.0;
1170 BLI_assert(data->multi_data.init == BUTTON_MULTI_INIT_ENABLE);
1172 for (but = block->buttons.first; but; but = but->next) {
1173 if (but->flag & UI_BUT_DRAG_MULTI) {
1174 /* mbut_states for delta */
1175 uiButMultiState *mbut_state = ui_multibut_lookup(data, but);
1180 ui_but_execute_begin(C, ar, but, &active_back);
1182 #ifdef USE_ALLSELECT
1183 if (mbut_state->select_others.elems_len == 0) {
1184 ui_selectcontext_begin(C, but, &mbut_state->select_others);
1186 if (mbut_state->select_others.elems_len == 0) {
1187 mbut_state->select_others.elems_len = -1;
1190 /* needed so we apply the right deltas */
1191 but->active->origvalue = mbut_state->origvalue;
1192 but->active->select_others = mbut_state->select_others;
1193 but->active->select_others.do_free = false;
1196 BLI_assert(active_back == NULL);
1197 /* no need to check 'data->state' here */
1199 /* entering text (set all) */
1200 but->active->value = data->value;
1201 ui_but_string_set(C, but, data->str);
1204 /* dragging (use delta) */
1205 if (data->multi_data.is_proportional) {
1206 but->active->value = mbut_state->origvalue * value_scale;
1209 but->active->value = mbut_state->origvalue + value_delta;
1212 /* clamp based on soft limits, see: T40154 */
1213 CLAMP(but->active->value, (double)but->softmin, (double)but->softmax);
1215 ui_but_execute_end(C, ar, but, active_back);
1218 /* highly unlikely */
1219 printf("%s: cant find button\n", __func__);
1227 #endif /* USE_DRAG_MULTINUM */
1230 #ifdef USE_DRAG_TOGGLE
1232 typedef struct uiDragToggleHandle {
1236 float but_cent_start[2];
1237 eButType but_type_start;
1242 } uiDragToggleHandle;
1244 static bool ui_drag_toggle_set_xy_xy(
1245 bContext *C, ARegion *ar, const bool is_set, const eButType but_type_start,
1246 const int xy_src[2], const int xy_dst[2])
1248 /* popups such as layers won't re-evaluate on redraw */
1249 const bool do_check = (ar->regiontype == RGN_TYPE_TEMPORARY);
1250 bool changed = false;
1253 for (block = ar->uiblocks.first; block; block = block->next) {
1256 float xy_a_block[2] = {UNPACK2(xy_src)};
1257 float xy_b_block[2] = {UNPACK2(xy_dst)};
1259 ui_window_to_block_fl(ar, block, &xy_a_block[0], &xy_a_block[1]);
1260 ui_window_to_block_fl(ar, block, &xy_b_block[0], &xy_b_block[1]);
1262 for (but = block->buttons.first; but; but = but->next) {
1263 /* Note: ctrl is always true here because (at least for now) we always want to consider text control
1264 * in this case, even when not embossed. */
1265 if (ui_but_is_interactive(but, true)) {
1266 if (BLI_rctf_isect_segment(&but->rect, xy_a_block, xy_b_block)) {
1268 /* execute the button */
1269 if (ui_but_is_bool(but) && but->type == but_type_start) {
1270 /* is it pressed? */
1271 bool is_set_but = ui_but_is_pushed(but);
1272 BLI_assert(ui_but_is_bool(but) == true);
1273 if (is_set_but != is_set) {
1274 UI_but_execute(C, but);
1276 ui_but_update_edited(but);
1288 /* apply now, not on release (or if handlers are cancelled for whatever reason) */
1289 ui_apply_but_funcs_after(C);
1295 static void ui_drag_toggle_set(bContext *C, uiDragToggleHandle *drag_info, const int xy_input[2])
1297 ARegion *ar = CTX_wm_region(C);
1298 bool do_draw = false;
1302 * Initialize Locking:
1304 * Check if we need to initialize the lock axis by finding if the first
1305 * button we mouse over is X or Y aligned, then lock the mouse to that axis after.
1307 if (drag_info->is_init == false) {
1308 /* first store the buttons original coords */
1309 uiBut *but = ui_but_find_mouse_over_ex(ar, xy_input[0], xy_input[1], true);
1312 if (but->flag & UI_BUT_DRAG_LOCK) {
1313 const float but_cent_new[2] = {BLI_rctf_cent_x(&but->rect),
1314 BLI_rctf_cent_y(&but->rect)};
1316 /* check if this is a different button, chances are high the button wont move about :) */
1317 if (len_manhattan_v2v2(drag_info->but_cent_start, but_cent_new) > 1.0f) {
1318 if (fabsf(drag_info->but_cent_start[0] - but_cent_new[0]) <
1319 fabsf(drag_info->but_cent_start[1] - but_cent_new[1]))
1321 drag_info->xy_lock[0] = true;
1324 drag_info->xy_lock[1] = true;
1326 drag_info->is_init = true;
1330 drag_info->is_init = true;
1334 /* done with axis locking */
1337 xy[0] = (drag_info->xy_lock[0] == false) ? xy_input[0] : drag_info->xy_last[0];
1338 xy[1] = (drag_info->xy_lock[1] == false) ? xy_input[1] : drag_info->xy_last[1];
1341 /* touch all buttons between last mouse coord and this one */
1342 do_draw = ui_drag_toggle_set_xy_xy(C, ar, drag_info->is_set, drag_info->but_type_start, drag_info->xy_last, xy);
1345 ED_region_tag_redraw(ar);
1348 copy_v2_v2_int(drag_info->xy_last, xy);
1351 static void ui_handler_region_drag_toggle_remove(bContext *UNUSED(C), void *userdata)
1353 uiDragToggleHandle *drag_info = userdata;
1354 MEM_freeN(drag_info);
1357 static int ui_handler_region_drag_toggle(bContext *C, const wmEvent *event, void *userdata)
1359 uiDragToggleHandle *drag_info = userdata;
1362 switch (event->type) {
1365 if (event->val != KM_PRESS) {
1372 ui_drag_toggle_set(C, drag_info, &event->x);
1378 wmWindow *win = CTX_wm_window(C);
1379 ARegion *ar = CTX_wm_region(C);
1380 uiBut *but = ui_but_find_mouse_over_ex(ar, drag_info->xy_init[0], drag_info->xy_init[1], true);
1383 ui_apply_but_undo(but);
1386 WM_event_remove_ui_handler(&win->modalhandlers,
1387 ui_handler_region_drag_toggle,
1388 ui_handler_region_drag_toggle_remove,
1390 ui_handler_region_drag_toggle_remove(C, drag_info);
1392 WM_event_add_mousemove(C);
1393 return WM_UI_HANDLER_BREAK;
1396 return WM_UI_HANDLER_CONTINUE;
1400 static bool ui_but_is_drag_toggle(const uiBut *but)
1402 return ((ui_but_is_bool(but) == true) &&
1403 /* menu check is importnt so the button dragged over isn't removed instantly */
1404 (ui_block_is_menu(but->block) == false));
1407 #endif /* USE_DRAG_TOGGLE */
1410 #ifdef USE_ALLSELECT
1412 static bool ui_selectcontext_begin(
1413 bContext *C, uiBut *but, uiSelectContextStore *selctx_data)
1415 PointerRNA ptr, lptr, idptr;
1416 PropertyRNA *prop, *lprop;
1417 bool success = false;
1421 ListBase lb = {NULL};
1424 prop = but->rnaprop;
1425 index = but->rnaindex;
1427 /* for now don't support whole colors */
1431 /* if there is a valid property that is editable... */
1432 if (ptr.data && prop) {
1433 CollectionPointerLink *link;
1434 bool use_path_from_id;
1437 /* some facts we want to know */
1438 const bool is_array = RNA_property_array_check(prop);
1439 const int rna_type = RNA_property_type(prop);
1441 if (!UI_context_copy_to_selected_list(C, &ptr, prop, &lb, &use_path_from_id, &path)) {
1445 selctx_data->elems_len = BLI_listbase_count(&lb);
1446 if (selctx_data->elems_len == 0) {
1450 selctx_data->elems = MEM_mallocN(sizeof(uiSelectContextElem) * selctx_data->elems_len, __func__);
1452 for (i = 0, link = lb.first; i < selctx_data->elems_len; i++, link = link->next) {
1453 uiSelectContextElem *other = &selctx_data->elems[i];
1454 /* TODO,. de-duplicate copy_to_selected_button */
1455 if (link->ptr.data != ptr.data) {
1456 if (use_path_from_id) {
1457 /* Path relative to ID. */
1459 RNA_id_pointer_create(link->ptr.id.data, &idptr);
1460 RNA_path_resolve_property(&idptr, path, &lptr, &lprop);
1463 /* Path relative to elements from list. */
1465 RNA_path_resolve_property(&link->ptr, path, &lptr, &lprop);
1472 /* lptr might not be the same as link->ptr! */
1473 if ((lptr.data != ptr.data) &&
1475 RNA_property_editable(&lptr, lprop))
1479 if (rna_type == PROP_FLOAT) {
1480 other->val_f = RNA_property_float_get_index(&lptr, lprop, index);
1482 else if (rna_type == PROP_INT) {
1483 other->val_i = RNA_property_int_get_index(&lptr, lprop, index);
1485 /* ignored for now */
1487 else if (rna_type == PROP_BOOLEAN) {
1488 other->val_b = RNA_property_boolean_get_index(&lptr, lprop, index);
1493 if (rna_type == PROP_FLOAT) {
1494 other->val_f = RNA_property_float_get(&lptr, lprop);
1496 else if (rna_type == PROP_INT) {
1497 other->val_i = RNA_property_int_get(&lptr, lprop);
1499 /* ignored for now */
1501 else if (rna_type == PROP_BOOLEAN) {
1502 other->val_b = RNA_property_boolean_get(&lptr, lprop);
1504 else if (rna_type == PROP_ENUM) {
1505 other->val_i = RNA_property_enum_get(&lptr, lprop);
1514 selctx_data->elems_len -= 1;
1519 success = (selctx_data->elems_len != 0);
1522 if (selctx_data->elems_len == 0) {
1523 MEM_SAFE_FREE(selctx_data->elems);
1526 MEM_SAFE_FREE(path);
1529 /* caller can clear */
1530 selctx_data->do_free = true;
1533 but->flag |= UI_BUT_IS_SELECT_CONTEXT;
1539 static void ui_selectcontext_end(
1540 uiBut *but, uiSelectContextStore *selctx_data)
1542 if (selctx_data->do_free) {
1543 if (selctx_data->elems) {
1544 MEM_freeN(selctx_data->elems);
1548 but->flag &= ~UI_BUT_IS_SELECT_CONTEXT;
1551 static void ui_selectcontext_apply(
1552 bContext *C, uiBut *but, uiSelectContextStore *selctx_data,
1553 const double value, const double value_orig)
1555 if (selctx_data->elems) {
1556 PropertyRNA *prop = but->rnaprop;
1557 PropertyRNA *lprop = but->rnaprop;
1558 int index = but->rnaindex;
1560 const bool use_delta = (selctx_data->is_copy == false);
1568 const bool is_array = RNA_property_array_check(prop);
1569 const int rna_type = RNA_property_type(prop);
1571 if (rna_type == PROP_FLOAT) {
1572 delta.f = use_delta ? (value - value_orig) : value;
1573 RNA_property_float_range(&but->rnapoin, prop, &min.f, &max.f);
1575 else if (rna_type == PROP_INT) {
1576 delta.i = use_delta ? ((int)value - (int)value_orig) : (int)value;
1577 RNA_property_int_range(&but->rnapoin, prop, &min.i, &max.i);
1579 else if (rna_type == PROP_ENUM) {
1580 delta.i = RNA_property_enum_get(&but->rnapoin, prop); /* not a delta infact */
1582 else if (rna_type == PROP_BOOLEAN) {
1584 delta.b = RNA_property_boolean_get_index(&but->rnapoin, prop, index); /* not a delta infact */
1587 delta.b = RNA_property_boolean_get(&but->rnapoin, prop); /* not a delta infact */
1591 #ifdef USE_ALLSELECT_LAYER_HACK
1592 /* make up for not having 'handle_layer_buttons' */
1594 PropertySubType subtype = RNA_property_subtype(prop);
1596 if ((rna_type == PROP_BOOLEAN) &&
1597 ELEM(subtype, PROP_LAYER, PROP_LAYER_MEMBER) &&
1599 /* could check for 'handle_layer_buttons' */
1602 wmWindow *win = CTX_wm_window(C);
1603 if (!win->eventstate->shift) {
1604 const int len = RNA_property_array_length(&but->rnapoin, prop);
1605 int *tmparray = MEM_callocN(sizeof(int) * len, __func__);
1607 tmparray[index] = true;
1609 for (i = 0; i < selctx_data->elems_len; i++) {
1610 uiSelectContextElem *other = &selctx_data->elems[i];
1611 PointerRNA lptr = other->ptr;
1612 RNA_property_boolean_set_array(&lptr, lprop, tmparray);
1613 RNA_property_update(C, &lptr, lprop);
1616 MEM_freeN(tmparray);
1624 for (i = 0; i < selctx_data->elems_len; i++) {
1625 uiSelectContextElem *other = &selctx_data->elems[i];
1626 PointerRNA lptr = other->ptr;
1628 if (rna_type == PROP_FLOAT) {
1629 float other_value = use_delta ? (other->val_f + delta.f) : delta.f;
1630 CLAMP(other_value, min.f, max.f);
1632 RNA_property_float_set_index(&lptr, lprop, index, other_value);
1635 RNA_property_float_set(&lptr, lprop, other_value);
1638 else if (rna_type == PROP_INT) {
1639 int other_value = use_delta ? (other->val_i + delta.i) : delta.i;
1640 CLAMP(other_value, min.i, max.i);
1642 RNA_property_int_set_index(&lptr, lprop, index, other_value);
1645 RNA_property_int_set(&lptr, lprop, other_value);
1648 else if (rna_type == PROP_BOOLEAN) {
1649 const bool other_value = delta.b;
1651 RNA_property_boolean_set_index(&lptr, lprop, index, other_value);
1654 RNA_property_boolean_set(&lptr, lprop, delta.b);
1657 else if (rna_type == PROP_ENUM) {
1658 const int other_value = delta.i;
1659 BLI_assert(!is_array);
1660 RNA_property_enum_set(&lptr, lprop, other_value);
1663 RNA_property_update(C, &lptr, prop);
1668 #endif /* USE_ALLSELECT */
1671 static bool ui_but_contains_point_px_icon(uiBut *but, ARegion *ar, const wmEvent *event)
1674 int x = event->x, y = event->y;
1676 ui_window_to_block(ar, but->block, &x, &y);
1678 BLI_rcti_rctf_copy(&rect, &but->rect);
1680 if (but->imb || but->type == UI_BTYPE_COLOR) {
1681 /* use button size itself */
1683 else if (but->drawflag & UI_BUT_ICON_LEFT) {
1684 rect.xmax = rect.xmin + (BLI_rcti_size_y(&rect));
1687 int delta = BLI_rcti_size_x(&rect) - BLI_rcti_size_y(&rect);
1688 rect.xmin += delta / 2;
1689 rect.xmax -= delta / 2;
1692 return BLI_rcti_isect_pt(&rect, x, y);
1695 static bool ui_but_drag_init(
1696 bContext *C, uiBut *but,
1697 uiHandleButtonData *data, const wmEvent *event)
1699 /* prevent other WM gestures to start while we try to drag */
1700 WM_gestures_remove(C);
1702 if (ABS(data->dragstartx - event->x) + ABS(data->dragstarty - event->y) > U.dragthreshold) {
1704 button_activate_state(C, but, BUTTON_STATE_EXIT);
1705 data->cancel = true;
1706 #ifdef USE_DRAG_TOGGLE
1707 if (ui_but_is_bool(but)) {
1708 uiDragToggleHandle *drag_info = MEM_callocN(sizeof(*drag_info), __func__);
1711 /* call here because regular mouse-up event wont run,
1712 * typically 'button_activate_exit()' handles this */
1713 ui_apply_but_autokey(C, but);
1715 drag_info->is_set = ui_but_is_pushed(but);
1716 drag_info->but_cent_start[0] = BLI_rctf_cent_x(&but->rect);
1717 drag_info->but_cent_start[1] = BLI_rctf_cent_y(&but->rect);
1718 drag_info->but_type_start = but->type;
1719 copy_v2_v2_int(drag_info->xy_init, &event->x);
1720 copy_v2_v2_int(drag_info->xy_last, &event->x);
1722 /* needed for toggle drag on popups */
1723 ar_prev = CTX_wm_region(C);
1724 CTX_wm_region_set(C, data->region);
1726 WM_event_add_ui_handler(
1727 C, &data->window->modalhandlers,
1728 ui_handler_region_drag_toggle,
1729 ui_handler_region_drag_toggle_remove,
1730 drag_info, WM_HANDLER_BLOCKING);
1732 CTX_wm_region_set(C, ar_prev);
1736 if (but->type == UI_BTYPE_COLOR) {
1738 uiDragColorHandle *drag_info = MEM_callocN(sizeof(*drag_info), __func__);
1740 /* TODO support more button pointer types */
1741 if (but->rnaprop && RNA_property_subtype(but->rnaprop) == PROP_COLOR_GAMMA) {
1742 RNA_property_float_get_array(&but->rnapoin, but->rnaprop, drag_info->color);
1743 drag_info->gamma_corrected = true;
1746 else if (but->rnaprop && RNA_property_subtype(but->rnaprop) == PROP_COLOR) {
1747 RNA_property_float_get_array(&but->rnapoin, but->rnaprop, drag_info->color);
1748 drag_info->gamma_corrected = false;
1751 else if (but->pointype == UI_BUT_POIN_FLOAT) {
1752 copy_v3_v3(drag_info->color, (float *)but->poin);
1755 else if (but->pointype == UI_BUT_POIN_CHAR) {
1756 rgb_uchar_to_float(drag_info->color, (unsigned char *)but->poin);
1761 WM_event_start_drag(C, ICON_COLOR, WM_DRAG_COLOR, drag_info, 0.0, WM_DRAG_FREE_DATA);
1764 MEM_freeN(drag_info);
1769 wmDrag *drag = WM_event_start_drag(
1770 C, but->icon, but->dragtype, but->dragpoin,
1771 ui_but_value_get(but), WM_DRAG_NOP);
1774 WM_event_drag_image(
1775 drag, but->imb, but->imb_scale,
1776 BLI_rctf_size_x(&but->rect),
1777 BLI_rctf_size_y(&but->rect));
1786 /* ********************** linklines *********************** */
1788 static void ui_linkline_remove_active(uiBlock *block)
1792 uiLinkLine *line, *nline;
1795 for (but = block->buttons.first; but; but = but->next) {
1796 if (but->type == UI_BTYPE_LINK && but->link) {
1797 for (line = but->link->lines.first; line; line = nline) {
1800 if (line->flag & UI_SELECT) {
1801 BLI_remlink(&but->link->lines, line);
1803 link = line->from->link;
1805 /* are there more pointers allowed? */
1808 if (*(link->totlink) == 1) {
1809 *(link->totlink) = 0;
1810 MEM_freeN(*(link->ppoin));
1811 *(link->ppoin) = NULL;
1815 for (a = 0; a < (*(link->totlink)); a++) {
1817 if ((*(link->ppoin))[a] != line->to->poin) {
1818 (*(link->ppoin))[b] = (*(link->ppoin))[a];
1822 (*(link->totlink))--;
1826 *(link->poin) = NULL;
1837 static uiLinkLine *ui_but_find_link(uiBut *from, uiBut *to)
1844 for (line = link->lines.first; line; line = line->next) {
1845 if (line->from == from && line->to == to) {
1853 /* XXX BAD BAD HACK, fixme later **************** */
1854 /* Try to add an AND Controller between the sensor and the actuator logic bricks and to connect them all */
1855 static void ui_but_smart_controller_add(bContext *C, uiBut *from, uiBut *to)
1859 bActuator *act_to, *act_iter;
1861 bController ***sens_from_links;
1864 uiLink *link = from->link;
1866 PointerRNA props_ptr, object_ptr;
1869 sens_from_links = (bController ***)(link->ppoin);
1872 act_to = (bActuator *)(to->poin);
1874 /* (1) get the object */
1875 CTX_DATA_BEGIN (C, Object *, ob_iter, selected_editable_objects)
1877 for (sens_iter = ob_iter->sensors.first; sens_iter; sens_iter = sens_iter->next) {
1878 if (&(sens_iter->links) == sens_from_links) {
1888 /* (2) check if the sensor and the actuator are from the same object */
1889 for (act_iter = ob->actuators.first; act_iter; act_iter = (bActuator *)act_iter->next) {
1890 if (act_iter == act_to)
1894 /* only works if the sensor and the actuator are from the same object */
1895 if (!act_iter) return;
1897 /* in case the linked controller is not the active one */
1898 RNA_pointer_create((ID *)ob, &RNA_Object, ob, &object_ptr);
1900 WM_operator_properties_create(&props_ptr, "LOGIC_OT_controller_add");
1901 RNA_string_set(&props_ptr, "object", ob->id.name + 2);
1903 /* (3) add a new controller */
1904 if (WM_operator_name_call(C, "LOGIC_OT_controller_add", WM_OP_EXEC_DEFAULT, &props_ptr) & OPERATOR_FINISHED) {
1905 cont = (bController *)ob->controllers.last;
1906 /* Quick fix to make sure we always have an AND controller.
1907 * It might be nicer to make sure the operator gives us the right one though... */
1908 cont->type = CONT_LOGIC_AND;
1910 /* (4) link the sensor->controller->actuator */
1911 tmp_but = MEM_callocN(sizeof(uiBut), "uiBut");
1913 tmp_but, (void **)&cont, (void ***)&(cont->links),
1914 &cont->totlinks, from->link->tocode, (int)to->hardmin);
1915 tmp_but->hardmin = from->link->tocode;
1916 tmp_but->poin = (char *)cont;
1918 tmp_but->type = UI_BTYPE_INLINK;
1919 ui_but_link_add(C, from, tmp_but);
1921 tmp_but->type = UI_BTYPE_LINK;
1922 ui_but_link_add(C, tmp_but, to);
1924 /* (5) garbage collection */
1925 MEM_freeN(tmp_but->link);
1928 WM_operator_properties_free(&props_ptr);
1931 static void ui_but_link_add(bContext *C, uiBut *from, uiBut *to)
1933 /* in 'from' we have to add a link to 'to' */
1939 if ((line = ui_but_find_link(from, to))) {
1940 line->flag |= UI_SELECT;
1941 ui_linkline_remove_active(from->block);
1945 if (from->type == UI_BTYPE_INLINK && to->type == UI_BTYPE_INLINK) {
1948 else if (from->type == UI_BTYPE_LINK && to->type == UI_BTYPE_INLINK) {
1949 if (from->link->tocode != (int)to->hardmin) {
1950 ui_but_smart_controller_add(C, from, to);
1954 else if (from->type == UI_BTYPE_INLINK && to->type == UI_BTYPE_LINK) {
1955 if (to->link->tocode == (int)from->hardmin) {
1962 /* are there more pointers allowed? */
1964 oldppoin = *(link->ppoin);
1966 (*(link->totlink))++;
1967 *(link->ppoin) = MEM_callocN(*(link->totlink) * sizeof(void *), "new link");
1969 for (a = 0; a < (*(link->totlink)) - 1; a++) {
1970 (*(link->ppoin))[a] = oldppoin[a];
1972 (*(link->ppoin))[a] = to->poin;
1974 if (oldppoin) MEM_freeN(oldppoin);
1977 *(link->poin) = to->poin;
1983 static void ui_apply_but_LINK(bContext *C, uiBut *but, uiHandleButtonData *data)
1985 ARegion *ar = CTX_wm_region(C);
1988 for (bt = but->block->buttons.first; bt; bt = bt->next) {
1989 if (ui_but_contains_point_px(ar, bt, but->linkto[0] + ar->winrct.xmin, but->linkto[1] + ar->winrct.ymin) )
1992 if (bt && bt != but) {
1993 if (!ELEM(bt->type, UI_BTYPE_LINK, UI_BTYPE_INLINK) || !ELEM(but->type, UI_BTYPE_LINK, UI_BTYPE_INLINK))
1996 if (but->type == UI_BTYPE_LINK) ui_but_link_add(C, but, bt);
1997 else ui_but_link_add(C, bt, but);
1999 ui_apply_but_func(C, but);
2000 data->retval = but->retval;
2002 data->applied = true;
2005 static void ui_apply_but_IMAGE(bContext *C, uiBut *but, uiHandleButtonData *data)
2007 ui_apply_but_func(C, but);
2008 data->retval = but->retval;
2009 data->applied = true;
2012 static void ui_apply_but_HISTOGRAM(bContext *C, uiBut *but, uiHandleButtonData *data)
2014 ui_apply_but_func(C, but);
2015 data->retval = but->retval;
2016 data->applied = true;
2019 static void ui_apply_but_WAVEFORM(bContext *C, uiBut *but, uiHandleButtonData *data)
2021 ui_apply_but_func(C, but);
2022 data->retval = but->retval;
2023 data->applied = true;
2026 static void ui_apply_but_TRACKPREVIEW(bContext *C, uiBut *but, uiHandleButtonData *data)
2028 ui_apply_but_func(C, but);
2029 data->retval = but->retval;
2030 data->applied = true;
2034 static void ui_apply_but(bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const bool interactive)
2039 ColorBand *editcoba;
2040 CurveMapping *editcumap;
2044 /* if we cancel and have not applied yet, there is nothing to do,
2045 * otherwise we have to restore the original value again */
2050 if (data->str) MEM_freeN(data->str);
2051 data->str = data->origstr;
2052 data->origstr = NULL;
2053 data->value = data->origvalue;
2054 copy_v3_v3(data->vec, data->origvec);
2055 /* postpone clearing origdata */
2058 /* we avoid applying interactive edits a second time
2059 * at the end with the appliedinteractive flag */
2061 data->applied_interactive = true;
2063 else if (data->applied_interactive) {
2067 #ifdef USE_ALLSELECT
2068 # ifdef USE_DRAG_MULTINUM
2069 if (but->flag & UI_BUT_DRAG_MULTI) {
2074 if (data->select_others.elems_len == 0) {
2075 ui_selectcontext_begin(C, but, &data->select_others);
2077 if (data->select_others.elems_len == 0) {
2078 /* dont check again */
2079 data->select_others.elems_len = -1;
2084 /* ensures we are writing actual values */
2085 editstr = but->editstr;
2086 editval = but->editval;
2087 editvec = but->editvec;
2088 editcoba = but->editcoba;
2089 editcumap = but->editcumap;
2090 but->editstr = NULL;
2091 but->editval = NULL;
2092 but->editvec = NULL;
2093 but->editcoba = NULL;
2094 but->editcumap = NULL;
2096 /* handle different types */
2097 switch (but->type) {
2099 ui_apply_but_BUT(C, but, data);
2102 case UI_BTYPE_SEARCH_MENU:
2103 ui_apply_but_TEX(C, but, data);
2105 case UI_BTYPE_BUT_TOGGLE:
2106 case UI_BTYPE_TOGGLE:
2107 case UI_BTYPE_TOGGLE_N:
2108 case UI_BTYPE_ICON_TOGGLE:
2109 case UI_BTYPE_ICON_TOGGLE_N:
2110 case UI_BTYPE_CHECKBOX:
2111 case UI_BTYPE_CHECKBOX_N:
2112 ui_apply_but_TOG(C, but, data);
2115 case UI_BTYPE_LISTROW:
2117 ui_apply_but_ROW(C, block, but, data);
2119 case UI_BTYPE_SCROLL:
2122 case UI_BTYPE_NUM_SLIDER:
2123 ui_apply_but_NUM(C, but, data);
2126 case UI_BTYPE_BLOCK:
2127 case UI_BTYPE_PULLDOWN:
2128 ui_apply_but_BLOCK(C, but, data);
2130 case UI_BTYPE_COLOR:
2132 ui_apply_but_VEC(C, but, data);
2134 ui_apply_but_BLOCK(C, but, data);
2136 case UI_BTYPE_BUT_MENU:
2137 ui_apply_but_BUTM(C, but, data);
2139 case UI_BTYPE_UNITVEC:
2140 case UI_BTYPE_HSVCUBE:
2141 case UI_BTYPE_HSVCIRCLE:
2142 ui_apply_but_VEC(C, but, data);
2144 case UI_BTYPE_COLORBAND:
2145 ui_apply_but_COLORBAND(C, but, data);
2147 case UI_BTYPE_CURVE:
2148 ui_apply_but_CURVE(C, but, data);
2150 case UI_BTYPE_KEY_EVENT:
2151 case UI_BTYPE_HOTKEY_EVENT:
2152 ui_apply_but_BUT(C, but, data);
2155 case UI_BTYPE_INLINK:
2156 ui_apply_but_LINK(C, but, data);
2158 case UI_BTYPE_IMAGE:
2159 ui_apply_but_IMAGE(C, but, data);
2161 case UI_BTYPE_HISTOGRAM:
2162 ui_apply_but_HISTOGRAM(C, but, data);
2164 case UI_BTYPE_WAVEFORM:
2165 ui_apply_but_WAVEFORM(C, but, data);
2167 case UI_BTYPE_TRACK_PREVIEW:
2168 ui_apply_but_TRACKPREVIEW(C, but, data);
2174 #ifdef USE_DRAG_MULTINUM
2175 if (data->multi_data.has_mbuts) {
2176 if (data->multi_data.init == BUTTON_MULTI_INIT_ENABLE) {
2178 ui_multibut_restore(C, data, block);
2181 ui_multibut_states_apply(C, data, block);
2187 #ifdef USE_ALLSELECT
2188 ui_selectcontext_apply(C, but, &data->select_others, data->value, data->origvalue);
2192 data->origvalue = 0.0;
2193 zero_v3(data->origvec);
2196 but->editstr = editstr;
2197 but->editval = editval;
2198 but->editvec = editvec;
2199 but->editcoba = editcoba;
2200 but->editcumap = editcumap;
2203 /* ******************* drop event ******************** */
2205 /* only call if event type is EVT_DROP */
2206 static void ui_but_drop(bContext *C, const wmEvent *event, uiBut *but, uiHandleButtonData *data)
2209 ListBase *drags = event->customdata; /* drop event type has listbase customdata by default */
2211 for (wmd = drags->first; wmd; wmd = wmd->next) {
2212 if (wmd->type == WM_DRAG_ID) {
2213 /* align these types with UI_but_active_drop_name */
2214 if (ELEM(but->type, UI_BTYPE_TEXT, UI_BTYPE_SEARCH_MENU)) {
2215 ID *id = (ID *)wmd->poin;
2217 button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING);
2219 ui_textedit_string_set(but, data, id->name + 2);
2221 if (ELEM(but->type, UI_BTYPE_SEARCH_MENU)) {
2222 but->changed = true;
2223 ui_searchbox_update(C, data->searchbox, but, true);
2226 button_activate_state(C, but, BUTTON_STATE_EXIT);
2233 /* ******************* copy and paste ******************** */
2235 /* c = copy, v = paste */
2236 static void ui_but_copy_paste(bContext *C, uiBut *but, uiHandleButtonData *data, const char mode, const bool copy_array)
2238 int buf_paste_len = 0;
2239 const char *buf_paste = "";
2240 bool buf_paste_alloc = false;
2241 bool show_report = false; /* use to display errors parsing paste input */
2243 BLI_assert((but->flag & UI_BUT_DISABLED) == 0); /* caller should check */
2246 /* disallow copying from any passwords */
2247 if (but->rnaprop && (RNA_property_subtype(but->rnaprop) == PROP_PASSWORD)) {
2253 /* extract first line from clipboard in case of multi-line copies */
2254 const char *buf_paste_test;
2256 buf_paste_test = WM_clipboard_text_get_firstline(false, &buf_paste_len);
2257 if (buf_paste_test) {
2258 buf_paste = buf_paste_test;
2259 buf_paste_alloc = true;
2263 /* No return from here down */
2267 if (ELEM(but->type, UI_BTYPE_NUM, UI_BTYPE_NUM_SLIDER)) {
2269 if (but->poin == NULL && but->rnapoin.data == NULL) {
2272 else if (copy_array && but->rnapoin.data && but->rnaprop &&
2273 ELEM(RNA_property_subtype(but->rnaprop), PROP_COLOR, PROP_TRANSLATION, PROP_DIRECTION,
2274 PROP_VELOCITY, PROP_ACCELERATION, PROP_MATRIX, PROP_EULER, PROP_QUATERNION, PROP_AXISANGLE,
2275 PROP_XYZ, PROP_XYZ_LENGTH, PROP_COLOR_GAMMA, PROP_COORDS))
2278 int array_length = RNA_property_array_length(&but->rnapoin, but->rnaprop);
2281 char buf_copy[UI_MAX_DRAW_STR];
2283 if (array_length == 4) {
2284 values[3] = RNA_property_float_get_index(&but->rnapoin, but->rnaprop, 3);
2289 ui_but_v3_get(but, values);
2291 BLI_snprintf(buf_copy, sizeof(buf_copy), "[%f, %f, %f, %f]", values[0], values[1], values[2], values[3]);
2292 WM_clipboard_text_set(buf_copy, 0);
2295 if (sscanf(buf_paste, "[%f, %f, %f, %f]", &values[0], &values[1], &values[2], &values[3]) >= array_length) {
2296 button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
2298 ui_but_v3_set(but, values);
2299 if (but->rnaprop && array_length == 4) {
2300 RNA_property_float_set_index(&but->rnapoin, but->rnaprop, 3, values[3]);
2302 data->value = values[but->rnaindex];
2304 button_activate_state(C, but, BUTTON_STATE_EXIT);
2307 WM_report(RPT_ERROR, "Paste expected 4 numbers, formatted: '[n, n, n, n]'");
2312 else if (mode == 'c') {
2313 /* Get many decimal places, then strip trailing zeros.
2314 * note: too high values start to give strange results */
2315 char buf_copy[UI_MAX_DRAW_STR];
2316 ui_but_string_get_ex(but, buf_copy, sizeof(buf_copy), UI_PRECISION_FLOAT_MAX);
2317 BLI_str_rstrip_float_zero(buf_copy, '\0');
2319 WM_clipboard_text_set(buf_copy, 0);
2324 if (ui_but_string_set_eval_num(C, but, buf_paste, &val)) {
2325 button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
2327 ui_but_string_set(C, but, buf_paste);
2328 button_activate_state(C, but, BUTTON_STATE_EXIT);
2331 /* evaluating will report errors */
2338 else if (but->type == UI_BTYPE_UNITVEC) {
2341 if (but->poin == NULL && but->rnapoin.data == NULL) {
2344 else if (mode == 'c') {
2345 char buf_copy[UI_MAX_DRAW_STR];
2346 ui_but_v3_get(but, xyz);
2347 BLI_snprintf(buf_copy, sizeof(buf_copy), "[%f, %f, %f]", xyz[0], xyz[1], xyz[2]);
2348 WM_clipboard_text_set(buf_copy, 0);
2351 if (sscanf(buf_paste, "[%f, %f, %f]", &xyz[0], &xyz[1], &xyz[2]) == 3) {
2352 if (normalize_v3(xyz) == 0.0f) {
2353 /* better set Z up then have a zero vector */
2356 button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
2357 ui_but_v3_set(but, xyz);
2358 button_activate_state(C, but, BUTTON_STATE_EXIT);
2361 WM_report(RPT_ERROR, "Paste expected 3 numbers, formatted: '[n, n, n]'");
2369 else if (but->type == UI_BTYPE_COLOR) {
2372 if (but->poin == NULL && but->rnapoin.data == NULL) {
2375 else if (mode == 'c') {
2376 char buf_copy[UI_MAX_DRAW_STR];
2378 if (but->rnaprop && RNA_property_array_length(&but->rnapoin, but->rnaprop) == 4)
2379 rgba[3] = RNA_property_float_get_index(&but->rnapoin, but->rnaprop, 3);
2383 ui_but_v3_get(but, rgba);
2384 /* convert to linear color to do compatible copy between gamma and non-gamma */
2385 if (but->rnaprop && RNA_property_subtype(but->rnaprop) == PROP_COLOR_GAMMA)
2386 srgb_to_linearrgb_v3_v3(rgba, rgba);
2388 BLI_snprintf(buf_copy, sizeof(buf_copy), "[%f, %f, %f, %f]", rgba[0], rgba[1], rgba[2], rgba[3]);
2389 WM_clipboard_text_set(buf_copy, 0);
2393 if (sscanf(buf_paste, "[%f, %f, %f, %f]", &rgba[0], &rgba[1], &rgba[2], &rgba[3]) == 4) {
2394 /* assume linear colors in buffer */
2395 if (but->rnaprop && RNA_property_subtype(but->rnaprop) == PROP_COLOR_GAMMA)
2396 linearrgb_to_srgb_v3_v3(rgba, rgba);
2398 button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
2399 ui_but_v3_set(but, rgba);
2400 if (but->rnaprop && RNA_property_array_length(&but->rnapoin, but->rnaprop) == 4)
2401 RNA_property_float_set_index(&but->rnapoin, but->rnaprop, 3, rgba[3]);
2403 button_activate_state(C, but, BUTTON_STATE_EXIT);
2406 WM_report(RPT_ERROR, "Paste expected 4 numbers, formatted: '[n, n, n, n]'");
2412 /* text/string and ID data */
2413 else if (ELEM(but->type, UI_BTYPE_TEXT, UI_BTYPE_SEARCH_MENU)) {
2414 uiHandleButtonData *active_data = but->active;
2416 if (but->poin == NULL && but->rnapoin.data == NULL) {
2419 else if (mode == 'c') {
2420 button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING);
2421 WM_clipboard_text_set(active_data->str, 0);
2422 active_data->cancel = true;
2423 button_activate_state(C, but, BUTTON_STATE_EXIT);
2426 button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING);
2428 ui_textedit_string_set(but, active_data, buf_paste);
2430 if (but->type == UI_BTYPE_SEARCH_MENU) {
2431 /* else uiSearchboxData.active member is not updated [#26856] */
2432 but->changed = true;
2433 ui_searchbox_update(C, data->searchbox, but, true);
2435 button_activate_state(C, but, BUTTON_STATE_EXIT);
2438 /* colorband (not supported by system clipboard) */
2439 else if (but->type == UI_BTYPE_COLORBAND) {
2441 if (but->poin != NULL) {
2442 memcpy(&but_copypaste_coba, but->poin, sizeof(ColorBand));
2446 if (but_copypaste_coba.tot != 0) {
2448 but->poin = MEM_callocN(sizeof(ColorBand), "colorband");
2450 button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
2451 memcpy(data->coba, &but_copypaste_coba, sizeof(ColorBand));
2452 button_activate_state(C, but, BUTTON_STATE_EXIT);
2456 else if (but->type == UI_BTYPE_CURVE) {
2458 if (but->poin != NULL) {
2459 but_copypaste_curve_alive = true;
2460 curvemapping_free_data(&but_copypaste_curve);
2461 curvemapping_copy_data(&but_copypaste_curve, (CurveMapping *) but->poin);
2465 if (but_copypaste_curve_alive) {
2467 but->poin = MEM_callocN(sizeof(CurveMapping), "curvemapping");
2469 button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
2470 curvemapping_free_data((CurveMapping *) but->poin);
2471 curvemapping_copy_data((CurveMapping *) but->poin, &but_copypaste_curve);
2472 button_activate_state(C, but, BUTTON_STATE_EXIT);
2476 /* operator button (any type) */
2477 else if (but->optype) {
2481 opptr = UI_but_operator_ptr_get(but); /* allocated when needed, the button owns it */
2483 str = WM_operator_pystring_ex(C, NULL, false, true, but->optype, opptr);
2485 WM_clipboard_text_set(str, 0);
2490 /* menu (any type) */
2491 else if (ELEM(but->type, UI_BTYPE_MENU, UI_BTYPE_PULLDOWN)) {
2492 MenuType *mt = UI_but_menutype_get(but);
2494 char str[32 + sizeof(mt->idname)];
2495 BLI_snprintf(str, sizeof(str), "bpy.ops.wm.call_menu(name=\"%s\")", mt->idname);
2496 WM_clipboard_text_set(str, 0);
2500 if (buf_paste_alloc) {
2501 MEM_freeN((void *)buf_paste);
2505 WM_report_banner_show();
2513 * Functions to convert password strings that should not be displayed
2514 * to asterisk representation (e.g. 'mysecretpasswd' -> '*************')
2516 * It converts every UTF-8 character to an asterisk, and also remaps
2517 * the cursor position and selection start/end.
2519 * \note: remaping is used, because password could contain UTF-8 characters.
2523 static int ui_text_position_from_hidden(uiBut *but, int pos)
2525 const char *strpos, *butstr;
2528 butstr = (but->editstr) ? but->editstr : but->drawstr;
2530 for (i = 0, strpos = butstr; i < pos; i++)
2531 strpos = BLI_str_find_next_char_utf8(strpos, NULL);
2533 return (strpos - butstr);
2536 static int ui_text_position_to_hidden(uiBut *but, int pos)
2538 const char *butstr = (but->editstr) ? but->editstr : but->drawstr;
2539 return BLI_strnlen_utf8(butstr, pos);
2542 void ui_but_text_password_hide(char password_str[UI_MAX_PASSWORD_STR], uiBut *but, const bool restore)
2546 if (!(but->rnaprop && RNA_property_subtype(but->rnaprop) == PROP_PASSWORD))
2549 butstr = (but->editstr) ? but->editstr : but->drawstr;
2552 /* restore original string */
2553 BLI_strncpy(butstr, password_str, UI_MAX_PASSWORD_STR);
2555 /* remap cursor positions */
2556 if (but->pos >= 0) {
2557 but->pos = ui_text_position_from_hidden(but, but->pos);
2558 but->selsta = ui_text_position_from_hidden(but, but->selsta);
2559 but->selend = ui_text_position_from_hidden(but, but->selend);
2563 /* convert text to hidden text using asterisks (e.g. pass -> ****) */
2564 const size_t len = BLI_strlen_utf8(butstr);
2566 /* remap cursor positions */
2567 if (but->pos >= 0) {
2568 but->pos = ui_text_position_to_hidden(but, but->pos);
2569 but->selsta = ui_text_position_to_hidden(but, but->selsta);
2570 but->selend = ui_text_position_to_hidden(but, but->selend);
2573 /* save original string */
2574 BLI_strncpy(password_str, butstr, UI_MAX_PASSWORD_STR);
2575 memset(butstr, '*', len);
2580 static void ui_but_text_clear(bContext *C, uiBut *but, uiHandleButtonData *data)
2582 /* most likely NULL, but let's check, and give it temp zero string */
2584 data->str = MEM_callocN(1, "temp str");
2588 ui_apply_but_TEX(C, but, data);
2589 button_activate_state(C, but, BUTTON_STATE_EXIT);
2593 /* ************* in-button text selection/editing ************* */
2595 static void ui_textedit_string_ensure_max_length(uiBut *but, uiHandleButtonData *data, int maxlen)
2597 BLI_assert(data->is_str_dynamic);
2598 BLI_assert(data->str == but->editstr);
2600 if (maxlen > data->maxlen) {
2601 data->str = but->editstr = MEM_reallocN(data->str, sizeof(char) * maxlen);
2602 data->maxlen = maxlen;
2606 static void ui_textedit_string_set(uiBut *but, uiHandleButtonData *data, const char *str)
2608 if (data->is_str_dynamic) {
2609 ui_textedit_string_ensure_max_length(but, data, strlen(str) + 1);
2612 if (ui_but_is_utf8(but)) {
2613 BLI_strncpy_utf8(data->str, str, data->maxlen);
2616 BLI_strncpy(data->str, str, data->maxlen);
2621 static bool ui_textedit_delete_selection(uiBut *but, uiHandleButtonData *data)
2623 char *str = data->str;
2624 const int len = strlen(str);
2625 bool changed = false;
2626 if (but->selsta != but->selend && len) {
2627 memmove(str + but->selsta, str + but->selend, (len - but->selend) + 1);
2631 but->pos = but->selend = but->selsta;
2636 * \param x Screen space cursor location - #wmEvent.x
2638 * \note ``but->block->aspect`` is used here, so drawing button style is getting scaled too.
2640 static void ui_textedit_set_cursor_pos(uiBut *but, uiHandleButtonData *data, const float x)
2642 uiStyle *style = UI_style_get(); // XXX pass on as arg
2643 uiFontStyle *fstyle = &style->widget;
2644 const float aspect = but->block->aspect;
2645 const short fstyle_points_prev = fstyle->points;
2647 float startx = but->rect.xmin;
2648 float starty_dummy = 0.0f;
2649 char password_str[UI_MAX_PASSWORD_STR];
2650 /* treat 'str_last' as null terminator for str, no need to modify in-place */
2651 const char *str = but->editstr, *str_last;
2653 ui_block_to_window_fl(data->region, but->block, &startx, &starty_dummy);
2655 ui_fontscale(&fstyle->points, aspect);
2657 UI_fontstyle_set(fstyle);
2659 if (fstyle->kerning == 1) /* for BLF_width */
2660 BLF_enable(fstyle->uifont_id, BLF_KERNING_DEFAULT);
2662 ui_but_text_password_hide(password_str, but, false);
2664 if (ELEM(but->type, UI_BTYPE_TEXT, UI_BTYPE_SEARCH_MENU)) {
2665 if (but->flag & UI_HAS_ICON) {
2666 startx += UI_DPI_ICON_SIZE / aspect;
2669 /* but this extra .05 makes clicks inbetween characters feel nicer */
2670 startx += ((UI_TEXT_MARGIN_X + 0.05f) * U.widget_unit) / aspect;
2672 /* mouse dragged outside the widget to the left */
2676 str_last = &str[but->ofs];
2679 if (BLI_str_cursor_step_prev_utf8(str, but->ofs, &i)) {
2680 /* 0.25 == scale factor for less sensitivity */
2681 if (BLF_width(fstyle->uifont_id, str + i, (str_last - str) - i) > (startx - x) * 0.25f) {
2686 break; /* unlikely but possible */
2690 but->pos = but->ofs;
2692 /* mouse inside the widget, mouse coords mapped in widget space */
2693 else { /* (x >= startx) */
2696 /* keep track of previous distance from the cursor to the char */
2697 float cdist, cdist_prev = 0.0f;
2700 str_last = &str[strlen(str)];
2702 but->pos = pos_prev = ((str_last - str) - but->ofs);
2705 cdist = startx + BLF_width(fstyle->uifont_id, str + but->ofs, (str_last - str) - but->ofs);
2707 /* check if position is found */
2709 /* check is previous location was in fact closer */
2710 if ((x - cdist) > (cdist_prev - x)) {
2711 but->pos = pos_prev;
2716 pos_prev = but->pos;
2717 /* done with tricky distance checks */
2720 if (but->pos <= 0) break;
2721 if (BLI_str_cursor_step_prev_utf8(str, but->ofs, &pos_i)) {
2723 str_last = &str[but->pos + but->ofs];
2726 break; /* unlikely but possible */
2729 but->pos += but->ofs;
2730 if (but->pos < 0) but->pos = 0;
2733 if (fstyle->kerning == 1)
2734 BLF_disable(fstyle->uifont_id, BLF_KERNING_DEFAULT);
2736 ui_but_text_password_hide(password_str, but, true);
2738 fstyle->points = fstyle_points_prev;
2741 static void ui_textedit_set_cursor_select(uiBut *but, uiHandleButtonData *data, const float x)
2743 if (x > data->selstartx) data->selextend = EXTEND_RIGHT;
2744 else if (x < data->selstartx) data->selextend = EXTEND_LEFT;
2746 ui_textedit_set_cursor_pos(but, data, x);
2748 if (data->selextend == EXTEND_RIGHT) but->selend = but->pos;
2749 else if (data->selextend == EXTEND_LEFT) but->selsta = but->pos;
2755 * This is used for both utf8 and ascii
2757 * For unicode buttons, \a buf is treated as unicode.
2759 static bool ui_textedit_insert_buf(
2760 uiBut *but, uiHandleButtonData *data,
2761 const char *buf, int buf_len)
2763 int len = strlen(data->str);
2764 int len_new = len - (but->selend - but->selsta) + 1;
2765 bool changed = false;
2767 if (data->is_str_dynamic) {
2768 ui_textedit_string_ensure_max_length(but, data, len_new + buf_len);
2771 if (len_new <= data->maxlen) {
2772 char *str = data->str;
2773 size_t step = buf_len;
2775 /* type over the current selection */
2776 if ((but->selend - but->selsta) > 0) {
2777 changed = ui_textedit_delete_selection(but, data);
2781 if ((len + step >= data->maxlen) && (data->maxlen - (len + 1) > 0)) {
2782 if (ui_but_is_utf8(but)) {
2783 /* shorten 'step' to a utf8 algined size that fits */
2784 BLI_strnlen_utf8_ex(buf, data->maxlen - (len + 1), &step);
2787 step = data->maxlen - (len + 1);
2791 if (step && (len + step < data->maxlen)) {
2792 memmove(&str[but->pos + step], &str[but->pos], (len + 1) - but->pos);
2793 memcpy(&str[but->pos], buf, step * sizeof(char));
2802 static bool ui_textedit_insert_ascii(uiBut *but, uiHandleButtonData *data, char ascii)
2804 char buf[2] = {ascii, '\0'};
2806 if (ui_but_is_utf8(but) && (BLI_str_utf8_size(buf) == -1)) {
2807 printf("%s: entering invalid ascii char into an ascii key (%d)\n",
2808 __func__, (int)(unsigned char)ascii);
2813 /* in some cases we want to allow invalid utf8 chars */
2814 return ui_textedit_insert_buf(but, data, buf, 1);
2817 static void ui_textedit_move(
2818 uiBut *but, uiHandleButtonData *data, strCursorJumpDirection direction,
2819 const bool select, strCursorJumpType jump)
2821 const char *str = data->str;
2822 const int len = strlen(str);
2823 const int pos_prev = but->pos;
2824 const bool has_sel = (but->selend - but->selsta) > 0;
2828 /* special case, quit selection and set cursor */
2829 if (has_sel && !select) {
2830 if (jump == STRCUR_JUMP_ALL) {
2831 but->selsta = but->selend = but->pos = direction ? len : 0;
2835 but->selsta = but->pos = but->selend;
2838 but->pos = but->selend = but->selsta;
2841 data->selextend = EXTEND_NONE;
2844 int pos_i = but->pos;
2845 BLI_str_cursor_step_utf8(str, len, &pos_i, direction, jump, true);
2849 /* existing selection */
2852 if (data->selextend == EXTEND_NONE) {
2853 data->selextend = EXTEND_RIGHT;
2857 if (data->selextend == EXTEND_RIGHT) {
2858 but->selend = but->pos;
2861 but->selsta = but->pos;
2865 if (data->selextend == EXTEND_LEFT) {
2866 but->selsta = but->pos;
2869 but->selend = but->pos;
2873 if (but->selend < but->selsta) {
2874 SWAP(short, but->selsta, but->selend);
2875 data->selextend = (data->selextend == EXTEND_RIGHT) ? EXTEND_LEFT : EXTEND_RIGHT;
2878 } /* new selection */
2881 data->selextend = EXTEND_RIGHT;
2882 but->selend = but->pos;
2883 but->selsta = pos_prev;
2886 data->selextend = EXTEND_LEFT;
2887 but->selend = pos_prev;
2888 but->selsta = but->pos;
2895 static bool ui_textedit_delete(uiBut *but, uiHandleButtonData *data, int direction, strCursorJumpType jump)
2897 char *str = data->str;
2898 const int len = strlen(str);
2900 bool changed = false;
2902 if (jump == STRCUR_JUMP_ALL) {
2903 if (len) changed = true;
2907 else if (direction) { /* delete */
2908 if ((but->selend - but->selsta) > 0) {
2909 changed = ui_textedit_delete_selection(but, data);
2911 else if (but->pos >= 0 && but->pos < len) {
2914 BLI_str_cursor_step_utf8(str, len, &pos, direction, jump, true);
2915 step = pos - but->pos;
2916 memmove(&str[but->pos], &str[but->pos + step], (len + 1) - (but->pos + step));
2920 else { /* backspace */
2922 if ((but->selend - but->selsta) > 0) {
2923 changed = ui_textedit_delete_selection(but, data);
2925 else if (but->pos > 0) {
2929 BLI_str_cursor_step_utf8(str, len, &pos, direction, jump, true);
2930 step = but->pos - pos;
2931 memmove(&str[but->pos - step], &str[but->pos], (len + 1) - but->pos);
2941 static int ui_textedit_autocomplete(bContext *C, uiBut *but, uiHandleButtonData *data)
2948 if (data->searchbox)
2949 changed = ui_searchbox_autocomplete(C, data->searchbox, but, data->str);
2951 changed = but->autocomplete_func(C, str, but->autofunc_arg);
2953 but->pos = strlen(str);
2954 but->selsta = but->selend = but->pos;
2959 /* mode for ui_textedit_copypaste() */
2961 UI_TEXTEDIT_PASTE = 1,
2966 static bool ui_textedit_copypaste(uiBut *but, uiHandleButtonData *data, const int mode)
2969 bool changed = false;
2973 if (mode == UI_TEXTEDIT_PASTE) {
2974 /* extract the first line from the clipboard */
2975 pbuf = WM_clipboard_text_get_firstline(false, &buf_len);
2978 if (ui_but_is_utf8(but)) {
2979 buf_len -= BLI_utf8_invalid_strip(pbuf, (size_t)buf_len);
2982 ui_textedit_insert_buf(but, data, pbuf, buf_len);
2990 else if (ELEM(mode, UI_TEXTEDIT_COPY, UI_TEXTEDIT_CUT)) {
2991 /* copy the contents to the copypaste buffer */
2992 int sellen = but->selend - but->selsta;
2993 char *buf = MEM_mallocN(sizeof(char) * (sellen + 1), "ui_textedit_copypaste");
2995 BLI_strncpy(buf, data->str + but->selsta, sellen + 1);
2996 WM_clipboard_text_set(buf, 0);
2999 /* for cut only, delete the selection afterwards */
3000 if (mode == UI_TEXTEDIT_CUT) {
3001 if ((but->selend - but->selsta) > 0) {
3002 changed = ui_textedit_delete_selection(but, data);
3010 #ifdef WITH_INPUT_IME
3011 /* enable ime, and set up uibut ime data */
3012 static void ui_textedit_ime_begin(wmWindow *win, uiBut *UNUSED(but))
3014 /* XXX Is this really needed? */
3017 BLI_assert(win->ime_data == NULL);
3019 /* enable IME and position to cursor, it's a trick */
3020 x = win->eventstate->x;
3021 /* flip y and move down a bit, prevent the IME panel cover the edit button */
3022 y = win->eventstate->y - 12;
3024 wm_window_IME_begin(win, x, y, 0, 0, true);
3027 /* disable ime, and clear uibut ime data */
3028 static void ui_textedit_ime_end(wmWindow *win, uiBut *UNUSED(but))
3030 wm_window_IME_end(win);
3033 void ui_but_ime_reposition(uiBut *but, int x, int y, bool complete)
3035 BLI_assert(but->active);
3037 ui_region_to_window(but->active->region, &x, &y);
3038 wm_window_IME_begin(but->active->window, x, y - 4, 0, 0, complete);
3041 wmIMEData *ui_but_ime_data_get(uiBut *but)
3043 if (but->active && but->active->window) {
3044 return but->active->window->ime_data;
3050 #endif /* WITH_INPUT_IME */
3052 static void ui_textedit_begin(bContext *C, uiBut *but, uiHandleButtonData *data)
3054 wmWindow *win = CTX_wm_window(C);
3056 const bool is_num_but = ELEM(but->type, UI_BTYPE_NUM, UI_BTYPE_NUM_SLIDER);
3059 MEM_freeN(data->str);
3063 #ifdef USE_DRAG_MULTINUM
3064 /* this can happen from multi-drag */
3065 if (data->applied_interactive) {
3066 /* remove any small changes so canceling edit doesn't restore invalid value: T40538 */