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_colorband.h"
65 #include "BKE_blender_undo.h"
66 #include "BKE_brush.h"
67 #include "BKE_colortools.h"
68 #include "BKE_context.h"
69 #include "BKE_idprop.h"
70 #include "BKE_report.h"
71 #include "BKE_screen.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_FLASH_DELAY 0.020
134 #define MENU_SCROLL_INTERVAL 0.1
135 #define PIE_MENU_INTERVAL 0.01
136 #define BUTTON_AUTO_OPEN_THRESH 0.3
137 #define BUTTON_MOUSE_TOWARDS_THRESH 1.0
138 /* pixels to move the cursor to get out of keyboard navigation */
139 #define BUTTON_KEYNAV_PX_LIMIT 8
141 #define MENU_TOWARDS_MARGIN 20 /* margin in pixels */
142 #define MENU_TOWARDS_WIGGLE_ROOM 64 /* tolerance in pixels */
143 /* drag-lock distance threshold in pixels */
144 #define BUTTON_DRAGLOCK_THRESH 3
146 typedef enum uiButtonActivateType {
147 BUTTON_ACTIVATE_OVER,
149 BUTTON_ACTIVATE_APPLY,
150 BUTTON_ACTIVATE_TEXT_EDITING,
152 } uiButtonActivateType;
154 typedef enum uiHandleButtonState {
156 BUTTON_STATE_HIGHLIGHT,
157 BUTTON_STATE_WAIT_FLASH,
158 BUTTON_STATE_WAIT_RELEASE,
159 BUTTON_STATE_WAIT_KEY_EVENT,
160 BUTTON_STATE_NUM_EDITING,
161 BUTTON_STATE_TEXT_EDITING,
162 BUTTON_STATE_TEXT_SELECTING,
163 BUTTON_STATE_MENU_OPEN,
164 BUTTON_STATE_WAIT_DRAG,
166 } uiHandleButtonState;
171 /* Unfortunately theres no good way handle more generally:
172 * (propagate single clicks on layer buttons to other objects) */
173 #define USE_ALLSELECT_LAYER_HACK
175 typedef struct uiSelectContextElem {
182 } uiSelectContextElem;
184 typedef struct uiSelectContextStore {
185 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);
204 #define IS_ALLSELECT_EVENT(event) ((event)->alt != 0)
206 /** just show a tinted color so users know its activated */
207 #define UI_BUT_IS_SELECT_CONTEXT UI_BUT_NODE_ACTIVE
209 #endif /* USE_ALLSELECT */
212 #ifdef USE_DRAG_MULTINUM
215 * how far to drag before we check for gesture direction (in pixels),
216 * note: half the height of a button is about right... */
217 #define DRAG_MULTINUM_THRESHOLD_DRAG_X (UI_UNIT_Y / 4)
220 * how far to drag horizontally before we stop checking which buttons the gesture spans (in pixels),
221 * locking down the buttons so we can drag freely without worrying about vertical movement. */
222 #define DRAG_MULTINUM_THRESHOLD_DRAG_Y (UI_UNIT_Y / 4)
225 * how strict to be when detecting a vertical gesture, [0.5 == sloppy], [0.9 == strict], (unsigned dot-product)
226 * note: we should be quite strict here, since doing a vertical gesture by accident should be avoided,
227 * however with some care a user should be able to do a vertical movement without *missing*. */
228 #define DRAG_MULTINUM_THRESHOLD_VERTICAL (0.75f)
231 /* a simple version of uiHandleButtonData when accessing multiple buttons */
232 typedef struct uiButMultiState {
237 uiSelectContextStore select_others;
241 typedef struct uiHandleButtonMulti {
243 BUTTON_MULTI_INIT_UNSET = 0, /* gesture direction unknown, wait until mouse has moved enough... */
244 BUTTON_MULTI_INIT_SETUP, /* vertical gesture detected, flag buttons interactively (UI_BUT_DRAG_MULTI) */
245 BUTTON_MULTI_INIT_ENABLE, /* flag buttons finished, apply horizontal motion to active and flagged */
246 BUTTON_MULTI_INIT_DISABLE, /* vertical gesture _not_ detected, take no further action */
249 bool has_mbuts; /* any buttons flagged UI_BUT_DRAG_MULTI */
251 uiButStore *bs_mbuts;
253 bool is_proportional;
255 /* before activating, we need to check gesture direction
256 * accumulate signed cursor movement here so we can tell if this is a vertical motion or not. */
259 /* values copied direct from event->x,y
260 * used to detect buttons between the current and initial mouse position */
263 /* store x location once BUTTON_MULTI_INIT_SETUP is set,
264 * moving outside this sets BUTTON_MULTI_INIT_ENABLE */
267 } uiHandleButtonMulti;
269 #endif /* USE_DRAG_MULTINUM */
271 typedef struct uiHandleButtonData {
279 uiHandleButtonState state;
281 /* booleans (could be made into flags) */
282 bool cancel, escapecancel;
283 bool applied, applied_interactive;
287 /* use 'ui_textedit_string_set' to assign new strings */
290 double value, origvalue, startvalue;
291 float vec[3], origvec[3];
293 int togdual, togonly;
298 unsigned int tooltip_force : 1;
302 wmTimer *autoopentimer;
304 /* auto open (hold) */
305 wmTimer *hold_action_timer;
307 /* text selection/editing */
308 /* size of 'str' (including terminator) */
310 /* Button text selection:
311 * extension direction, selextend, inside ui_do_but_TEX */
318 /* allow to realloc str/editstr and use 'maxlen' to track alloc size (maxlen + 1) */
321 /* number editing / dragging */
322 /* coords are Window/uiBlock relative (depends on the button) */
323 int draglastx, draglasty;
324 int dragstartx, dragstarty;
327 bool dragchange, draglock;
329 float dragf, dragfstart;
332 #ifdef USE_CONT_MOUSE_CORRECT
333 /* when ungrabbing buttons which are #ui_but_is_cursor_warp(), we may want to position them
334 * FLT_MAX signifies do-nothing, use #ui_block_to_window_fl() to get this into a usable space */
335 float ungrab_mval[2];
338 /* menu open (watch UI_screen_free_active_but) */
339 uiPopupBlockHandle *menu;
342 /* search box (watch UI_screen_free_active_but) */
344 #ifdef USE_KEYNAV_LIMIT
345 struct uiKeyNavLock searchbox_keynav_state;
348 #ifdef USE_DRAG_MULTINUM
349 /* Multi-buttons will be updated in unison with the active button. */
350 uiHandleButtonMulti multi_data;
354 uiSelectContextStore select_others;
358 uiButtonActivateType posttype;
360 } uiHandleButtonData;
362 typedef struct uiAfterFunc {
363 struct uiAfterFunc *next, *prev;
365 uiButHandleFunc func;
369 uiButHandleNFunc funcN;
372 uiButHandleRenameFunc rename_func;
376 uiBlockHandleFunc handle_func;
377 void *handle_func_arg;
380 uiMenuHandleFunc butm_func;
384 wmOperator *popup_op;
385 wmOperatorType *optype;
390 PropertyRNA *rnaprop;
392 bContextStore *context;
394 char undostr[BKE_UNDO_STR_MAX];
399 static bool ui_but_is_interactive(const uiBut *but, const bool labeledit);
400 static bool ui_but_contains_pt(uiBut *but, float mx, float my);
401 static bool ui_but_contains_point_px(ARegion *ar, uiBut *but, int x, int y);
402 static uiBut *ui_but_find_mouse_over_ex(ARegion *ar, const int x, const int y, const bool labeledit);
403 static void button_activate_init(bContext *C, ARegion *ar, uiBut *but, uiButtonActivateType type);
404 static void button_activate_state(bContext *C, uiBut *but, uiHandleButtonState state);
405 static void button_activate_exit(
406 bContext *C, uiBut *but, uiHandleButtonData *data,
407 const bool mousemove, const bool onfree);
408 static int ui_handler_region_menu(bContext *C, const wmEvent *event, void *userdata);
409 static void ui_handle_button_activate(bContext *C, ARegion *ar, uiBut *but, uiButtonActivateType type);
411 #ifdef USE_DRAG_MULTINUM
412 static void ui_multibut_restore(bContext *C, uiHandleButtonData *data, uiBlock *block);
413 static uiButMultiState *ui_multibut_lookup(uiHandleButtonData *data, const uiBut *but);
416 /* buttons clipboard */
417 static ColorBand but_copypaste_coba = {0};
418 static CurveMapping but_copypaste_curve = {0};
419 static bool but_copypaste_curve_alive = false;
421 /* ******************** menu navigation helpers ************** */
428 static enum eSnapType ui_event_to_snap(const wmEvent *event)
430 return (event->ctrl) ? (event->shift) ? SNAP_ON_SMALL : SNAP_ON : SNAP_OFF;
433 static bool ui_event_is_snap(const wmEvent *event)
435 return (ELEM(event->type, LEFTCTRLKEY, RIGHTCTRLKEY) ||
436 ELEM(event->type, LEFTSHIFTKEY, RIGHTSHIFTKEY));
439 static void ui_color_snap_hue(const enum eSnapType snap, float *r_hue)
441 const float snap_increment = (snap == SNAP_ON_SMALL) ? 24 : 12;
442 BLI_assert(snap != SNAP_OFF);
443 *r_hue = roundf((*r_hue) * snap_increment) / snap_increment;
446 /* assumes event type is MOUSEPAN */
447 void ui_pan_to_scroll(const wmEvent *event, int *type, int *val)
449 static int lastdy = 0;
450 int dy = event->prevy - event->y;
452 /* This event should be originally from event->type,
453 * converting wrong event into wheel is bad, see [#33803] */
454 BLI_assert(*type == MOUSEPAN);
456 /* sign differs, reset */
457 if ((dy > 0 && lastdy < 0) || (dy < 0 && lastdy > 0)) {
463 if (ABS(lastdy) > (int)UI_UNIT_Y) {
464 if (U.uiflag2 & USER_TRACKPAD_NATURAL)
470 *type = WHEELUPMOUSE;
472 *type = WHEELDOWNMOUSE;
479 bool ui_but_is_editable(const uiBut *but)
481 return !ELEM(but->type,
482 UI_BTYPE_LABEL, UI_BTYPE_SEPR, UI_BTYPE_SEPR_LINE,
483 UI_BTYPE_ROUNDBOX, UI_BTYPE_LISTBOX, UI_BTYPE_PROGRESS_BAR);
486 bool ui_but_is_editable_as_text(const uiBut *but)
488 return ELEM(but->type,
489 UI_BTYPE_TEXT, UI_BTYPE_NUM, UI_BTYPE_NUM_SLIDER,
490 UI_BTYPE_SEARCH_MENU);
494 static uiBut *ui_but_prev(uiBut *but)
498 if (ui_but_is_editable(but)) return but;
503 static uiBut *ui_but_next(uiBut *but)
507 if (ui_but_is_editable(but)) return but;
512 static uiBut *ui_but_first(uiBlock *block)
516 but = block->buttons.first;
518 if (ui_but_is_editable(but)) return but;
524 static uiBut *ui_but_last(uiBlock *block)
528 but = block->buttons.last;
530 if (ui_but_is_editable(but)) return but;
536 static bool ui_but_is_cursor_warp(uiBut *but)
538 if (U.uiflag & USER_CONTINUOUS_MOUSE) {
540 UI_BTYPE_NUM, UI_BTYPE_NUM_SLIDER, UI_BTYPE_HSVCIRCLE,
541 UI_BTYPE_TRACK_PREVIEW, UI_BTYPE_HSVCUBE, UI_BTYPE_CURVE))
551 * Ignore mouse movements within some horizontal pixel threshold before starting to drag
553 static bool ui_but_dragedit_update_mval(uiHandleButtonData *data, int mx)
555 if (mx == data->draglastx)
558 if (data->draglock) {
559 if (ABS(mx - data->dragstartx) <= BUTTON_DRAGLOCK_THRESH) {
562 #ifdef USE_DRAG_MULTINUM
563 if (ELEM(data->multi_data.init, BUTTON_MULTI_INIT_UNSET, BUTTON_MULTI_INIT_SETUP)) {
567 data->draglock = false;
568 data->dragstartx = mx; /* ignore mouse movement within drag-lock */
574 static float ui_mouse_scale_warp_factor(const bool shift)
576 return shift ? 0.05f : 1.0f;
579 static void ui_mouse_scale_warp(
580 uiHandleButtonData *data, const float mx, const float my,
581 float *r_mx, float *r_my, const bool shift)
583 const float fac = ui_mouse_scale_warp_factor(shift);
585 /* slow down the mouse, this is fairly picky */
586 *r_mx = (data->dragstartx * (1.0f - fac) + mx * fac);
587 *r_my = (data->dragstarty * (1.0f - fac) + my * fac);
590 /* file selectors are exempt from utf-8 checks */
591 bool ui_but_is_utf8(const uiBut *but)
594 const int subtype = RNA_property_subtype(but->rnaprop);
595 return !(ELEM(subtype, PROP_FILEPATH, PROP_DIRPATH, PROP_FILENAME, PROP_BYTESTRING));
598 return !(but->flag & UI_BUT_NO_UTF8);
602 /* ********************** button apply/revert ************************/
604 static ListBase UIAfterFuncs = {NULL, NULL};
606 static uiAfterFunc *ui_afterfunc_new(void)
610 after = MEM_callocN(sizeof(uiAfterFunc), "uiAfterFunc");
612 BLI_addtail(&UIAfterFuncs, after);
618 * For executing operators after the button is pressed.
619 * (some non operator buttons need to trigger operators), see: [#37795]
621 * \note Can only call while handling buttons.
623 PointerRNA *ui_handle_afterfunc_add_operator(wmOperatorType *ot, int opcontext, bool create_props)
625 PointerRNA *ptr = NULL;
626 uiAfterFunc *after = ui_afterfunc_new();
629 after->opcontext = opcontext;
632 ptr = MEM_callocN(sizeof(PointerRNA), __func__);
633 WM_operator_properties_create_ptr(ptr, ot);
640 static void popup_check(bContext *C, wmOperator *op)
642 if (op && op->type->check && op->type->check(C, op)) {
643 /* check for popup and re-layout buttons */
644 ARegion *ar_menu = CTX_wm_menu(C);
646 ED_region_tag_refresh_ui(ar_menu);
651 * Check if a #uiAfterFunc is needed for this button.
653 static bool ui_afterfunc_check(const uiBlock *block, const uiBut *but)
655 return (but->func || but->funcN || but->rename_func || but->optype || but->rnaprop || block->handle_func ||
656 (but->type == UI_BTYPE_BUT_MENU && block->butm_func) ||
657 (block->handle && block->handle->popup_op));
660 static void ui_apply_but_func(bContext *C, uiBut *but)
663 uiBlock *block = but->block;
665 /* these functions are postponed and only executed after all other
666 * handling is done, i.e. menus are closed, in order to avoid conflicts
667 * with these functions removing the buttons we are working with */
669 if (ui_afterfunc_check(block, but)) {
670 after = ui_afterfunc_new();
672 if (but->func && ELEM(but, but->func_arg1, but->func_arg2)) {
673 /* exception, this will crash due to removed button otherwise */
674 but->func(C, but->func_arg1, but->func_arg2);
677 after->func = but->func;
679 after->func_arg1 = but->func_arg1;
680 after->func_arg2 = but->func_arg2;
682 after->funcN = but->funcN;
683 after->func_argN = (but->func_argN) ? MEM_dupallocN(but->func_argN) : NULL;
685 after->rename_func = but->rename_func;
686 after->rename_arg1 = but->rename_arg1;
687 after->rename_orig = but->rename_orig; /* needs free! */
689 after->handle_func = block->handle_func;
690 after->handle_func_arg = block->handle_func_arg;
691 after->retval = but->retval;
693 if (but->type == UI_BTYPE_BUT_MENU) {
694 after->butm_func = block->butm_func;
695 after->butm_func_arg = block->butm_func_arg;
700 after->popup_op = block->handle->popup_op;
702 after->optype = but->optype;
703 after->opcontext = but->opcontext;
704 after->opptr = but->opptr;
706 after->rnapoin = but->rnapoin;
707 after->rnaprop = but->rnaprop;
710 after->context = CTX_store_copy(but->context);
718 /* typically call ui_apply_but_undo(), ui_apply_but_autokey() */
719 static void ui_apply_but_undo(uiBut *but)
723 if (but->flag & UI_BUT_UNDO) {
724 const char *str = NULL;
726 /* define which string to use for undo */
727 if (ELEM(but->type, UI_BTYPE_LINK, UI_BTYPE_INLINK)) str = "Add button link";
728 else if (but->type == UI_BTYPE_MENU) str = but->drawstr;
729 else if (but->drawstr[0]) str = but->drawstr;
732 /* fallback, else we don't get an undo! */
733 if (str == NULL || str[0] == '\0') {
734 str = "Unknown Action";
737 /* delayed, after all other funcs run, popups are closed, etc */
738 after = ui_afterfunc_new();
739 BLI_strncpy(after->undostr, str, sizeof(after->undostr));
743 static void ui_apply_but_autokey(bContext *C, uiBut *but)
745 Scene *scene = CTX_data_scene(C);
748 ui_but_anim_autokey(C, but, scene, scene->r.cfra);
750 /* make a little report about what we've done! */
754 if (RNA_property_subtype(but->rnaprop) == PROP_PASSWORD) {
758 buf = WM_prop_pystring_assign(C, &but->rnapoin, but->rnaprop, but->rnaindex);
760 BKE_report(CTX_wm_reports(C), RPT_PROPERTY, buf);
763 WM_event_add_notifier(C, NC_SPACE | ND_SPACE_INFO_REPORT, NULL);
768 static void ui_apply_but_funcs_after(bContext *C)
770 uiAfterFunc *afterf, after;
774 /* copy to avoid recursive calls */
775 funcs = UIAfterFuncs;
776 BLI_listbase_clear(&UIAfterFuncs);
778 for (afterf = funcs.first; afterf; afterf = after.next) {
779 after = *afterf; /* copy to avoid memleak on exit() */
780 BLI_freelinkN(&funcs, afterf);
783 CTX_store_set(C, after.context);
786 popup_check(C, after.popup_op);
789 /* free in advance to avoid leak on exit */
790 opptr = *after.opptr;
791 MEM_freeN(after.opptr);
795 WM_operator_name_call_ptr(C, after.optype, after.opcontext, (after.opptr) ? &opptr : NULL);
798 WM_operator_properties_free(&opptr);
800 if (after.rnapoin.data)
801 RNA_property_update(C, &after.rnapoin, after.rnaprop);
804 CTX_store_set(C, NULL);
805 CTX_store_free(after.context);
809 after.func(C, after.func_arg1, after.func_arg2);
811 after.funcN(C, after.func_argN, after.func_arg2);
813 MEM_freeN(after.func_argN);
815 if (after.handle_func)
816 after.handle_func(C, after.handle_func_arg, after.retval);
818 after.butm_func(C, after.butm_func_arg, after.a2);
820 if (after.rename_func)
821 after.rename_func(C, after.rename_arg1, after.rename_orig);
822 if (after.rename_orig)
823 MEM_freeN(after.rename_orig);
825 if (after.undostr[0])
826 ED_undo_push(C, after.undostr);
830 static void ui_apply_but_BUT(bContext *C, uiBut *but, uiHandleButtonData *data)
832 ui_apply_but_func(C, but);
834 data->retval = but->retval;
835 data->applied = true;
838 static void ui_apply_but_BUTM(bContext *C, uiBut *but, uiHandleButtonData *data)
840 ui_but_value_set(but, but->hardmin);
841 ui_apply_but_func(C, but);
843 data->retval = but->retval;
844 data->applied = true;
847 static void ui_apply_but_BLOCK(bContext *C, uiBut *but, uiHandleButtonData *data)
849 if (but->type == UI_BTYPE_MENU)
850 ui_but_value_set(but, data->value);
852 ui_but_update_edited(but);
853 ui_apply_but_func(C, but);
854 data->retval = but->retval;
855 data->applied = true;
858 static void ui_apply_but_TOG(bContext *C, uiBut *but, uiHandleButtonData *data)
863 value = ui_but_value_get(but);
867 w = UI_BITBUT_TEST(lvalue, but->bitnr);
868 if (w) lvalue = UI_BITBUT_CLR(lvalue, but->bitnr);
869 else lvalue = UI_BITBUT_SET(lvalue, but->bitnr);
871 ui_but_value_set(but, (double)lvalue);
872 if (but->type == UI_BTYPE_ICON_TOGGLE || but->type == UI_BTYPE_ICON_TOGGLE_N) {
873 ui_but_update_edited(but);
878 if (value == 0.0) push = 1;
881 if (ELEM(but->type, UI_BTYPE_TOGGLE_N, UI_BTYPE_ICON_TOGGLE_N, UI_BTYPE_CHECKBOX_N)) push = !push;
882 ui_but_value_set(but, (double)push);
883 if (but->type == UI_BTYPE_ICON_TOGGLE || but->type == UI_BTYPE_ICON_TOGGLE_N) {
884 ui_but_update_edited(but);
888 ui_apply_but_func(C, but);
890 data->retval = but->retval;
891 data->applied = true;
894 static void ui_apply_but_ROW(bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data)
898 ui_but_value_set(but, but->hardmax);
900 ui_apply_but_func(C, but);
902 /* states of other row buttons */
903 for (bt = block->buttons.first; bt; bt = bt->next) {
904 if (bt != but && bt->poin == but->poin && ELEM(bt->type, UI_BTYPE_ROW, UI_BTYPE_LISTROW)) {
905 ui_but_update_edited(bt);
909 data->retval = but->retval;
910 data->applied = true;
913 static void ui_apply_but_TEX(bContext *C, uiBut *but, uiHandleButtonData *data)
918 ui_but_string_set(C, but, data->str);
919 ui_but_update_edited(but);
921 /* give butfunc a copy of the original text too.
922 * feature used for bone renaming, channels, etc.
923 * afterfunc frees rename_orig */
924 if (data->origstr && (but->flag & UI_BUT_TEXTEDIT_UPDATE)) {
925 /* In this case, we need to keep origstr available, to restore real org string in case we cancel after
926 * having typed something already. */
927 but->rename_orig = BLI_strdup(data->origstr);
929 /* only if there are afterfuncs, otherwise 'renam_orig' isn't freed */
930 else if (ui_afterfunc_check(but->block, but)) {
931 but->rename_orig = data->origstr;
932 data->origstr = NULL;
934 ui_apply_but_func(C, but);
936 data->retval = but->retval;
937 data->applied = true;
940 static void ui_apply_but_NUM(bContext *C, uiBut *but, uiHandleButtonData *data)
943 if (ui_but_string_set(C, but, data->str)) {
944 data->value = ui_but_value_get(but);
952 ui_but_value_set(but, data->value);
955 ui_but_update_edited(but);
956 ui_apply_but_func(C, but);
958 data->retval = but->retval;
959 data->applied = true;
962 static void ui_apply_but_VEC(bContext *C, uiBut *but, uiHandleButtonData *data)
964 ui_but_v3_set(but, data->vec);
965 ui_but_update_edited(but);
966 ui_apply_but_func(C, but);
968 data->retval = but->retval;
969 data->applied = true;
972 static void ui_apply_but_COLORBAND(bContext *C, uiBut *but, uiHandleButtonData *data)
974 ui_apply_but_func(C, but);
975 data->retval = but->retval;
976 data->applied = true;
979 static void ui_apply_but_CURVE(bContext *C, uiBut *but, uiHandleButtonData *data)
981 ui_apply_but_func(C, but);
982 data->retval = but->retval;
983 data->applied = true;
986 /* ****************** drag drop code *********************** */
989 #ifdef USE_DRAG_MULTINUM
991 /* small multi-but api */
992 static void ui_multibut_add(uiHandleButtonData *data, uiBut *but)
994 uiButMultiState *mbut_state;
996 BLI_assert(but->flag & UI_BUT_DRAG_MULTI);
997 BLI_assert(data->multi_data.has_mbuts);
1000 mbut_state = MEM_callocN(sizeof(*mbut_state), __func__);
1001 mbut_state->but = but;
1002 mbut_state->origvalue = ui_but_value_get(but);
1004 BLI_linklist_prepend(&data->multi_data.mbuts, mbut_state);
1006 UI_butstore_register(data->multi_data.bs_mbuts, &mbut_state->but);
1009 static uiButMultiState *ui_multibut_lookup(uiHandleButtonData *data, const uiBut *but)
1013 for (l = data->multi_data.mbuts; l; l = l->next) {
1014 uiButMultiState *mbut_state;
1016 mbut_state = l->link;
1018 if (mbut_state->but == but) {
1026 static void ui_multibut_restore(bContext *C, uiHandleButtonData *data, uiBlock *block)
1030 for (but = block->buttons.first; but; but = but->next) {
1031 if (but->flag & UI_BUT_DRAG_MULTI) {
1032 uiButMultiState *mbut_state = ui_multibut_lookup(data, but);
1034 ui_but_value_set(but, mbut_state->origvalue);
1036 #ifdef USE_ALLSELECT
1037 if (mbut_state->select_others.elems_len > 0) {
1038 ui_selectcontext_apply(
1039 C, but, &mbut_state->select_others,
1040 mbut_state->origvalue, mbut_state->origvalue);
1050 static void ui_multibut_free(uiHandleButtonData *data, uiBlock *block)
1052 #ifdef USE_ALLSELECT
1053 if (data->multi_data.mbuts) {
1054 LinkNode *list = data->multi_data.mbuts;
1056 LinkNode *next = list->next;
1057 uiButMultiState *mbut_state = list->link;
1059 if (mbut_state->select_others.elems) {
1060 MEM_freeN(mbut_state->select_others.elems);
1063 MEM_freeN(list->link);
1069 BLI_linklist_freeN(data->multi_data.mbuts);
1072 data->multi_data.mbuts = NULL;
1074 if (data->multi_data.bs_mbuts) {
1075 UI_butstore_free(block, data->multi_data.bs_mbuts);
1076 data->multi_data.bs_mbuts = NULL;
1080 static bool ui_multibut_states_tag(
1082 uiHandleButtonData *data, const wmEvent *event)
1086 bool changed = false;
1088 seg[0][0] = data->multi_data.drag_start[0];
1089 seg[0][1] = data->multi_data.drag_start[1];
1091 seg[1][0] = event->x;
1092 seg[1][1] = event->y;
1094 BLI_assert(data->multi_data.init == BUTTON_MULTI_INIT_SETUP);
1096 ui_window_to_block_fl(data->region, but_active->block, &seg[0][0], &seg[0][1]);
1097 ui_window_to_block_fl(data->region, but_active->block, &seg[1][0], &seg[1][1]);
1099 data->multi_data.has_mbuts = false;
1101 /* follow ui_but_find_mouse_over_ex logic */
1102 for (but = but_active->block->buttons.first; but; but = but->next) {
1103 bool drag_prev = false;
1104 bool drag_curr = false;
1106 /* re-set each time */
1107 if (but->flag & UI_BUT_DRAG_MULTI) {
1108 but->flag &= ~UI_BUT_DRAG_MULTI;
1112 if (ui_but_is_interactive(but, false)) {
1115 if (but_active != but) {
1116 if (ui_but_is_compatible(but_active, but)) {
1118 BLI_assert(but->active == NULL);
1120 /* finally check for overlap */
1121 if (BLI_rctf_isect_segment(&but->rect, seg[0], seg[1])) {
1123 but->flag |= UI_BUT_DRAG_MULTI;
1124 data->multi_data.has_mbuts = true;
1131 changed |= (drag_prev != drag_curr);
1137 static void ui_multibut_states_create(uiBut *but_active, uiHandleButtonData *data)
1141 BLI_assert(data->multi_data.init == BUTTON_MULTI_INIT_SETUP);
1142 BLI_assert(data->multi_data.has_mbuts);
1144 data->multi_data.bs_mbuts = UI_butstore_create(but_active->block);
1146 for (but = but_active->block->buttons.first; but; but = but->next) {
1147 if (but->flag & UI_BUT_DRAG_MULTI) {
1148 ui_multibut_add(data, but);
1152 /* edit buttons proportionally to eachother
1153 * note: if we mix buttons which are proportional and others which are not,
1154 * this may work a bit strangely */
1155 if (but_active->rnaprop) {
1156 if ((data->origvalue != 0.0) && (RNA_property_flag(but_active->rnaprop) & PROP_PROPORTIONAL)) {
1157 data->multi_data.is_proportional = true;
1162 static void ui_multibut_states_apply(bContext *C, uiHandleButtonData *data, uiBlock *block)
1164 ARegion *ar = data->region;
1165 const double value_delta = data->value - data->origvalue;
1166 const double value_scale = data->multi_data.is_proportional ? (data->value / data->origvalue) : 0.0;
1169 BLI_assert(data->multi_data.init == BUTTON_MULTI_INIT_ENABLE);
1171 for (but = block->buttons.first; but; but = but->next) {
1172 if (but->flag & UI_BUT_DRAG_MULTI) {
1173 /* mbut_states for delta */
1174 uiButMultiState *mbut_state = ui_multibut_lookup(data, but);
1179 ui_but_execute_begin(C, ar, but, &active_back);
1181 #ifdef USE_ALLSELECT
1182 if (data->select_others.is_enabled) {
1184 if (mbut_state->select_others.elems_len == 0) {
1185 ui_selectcontext_begin(C, but, &mbut_state->select_others);
1187 if (mbut_state->select_others.elems_len == 0) {
1188 mbut_state->select_others.elems_len = -1;
1192 /* needed so we apply the right deltas */
1193 but->active->origvalue = mbut_state->origvalue;
1194 but->active->select_others = mbut_state->select_others;
1195 but->active->select_others.do_free = false;
1198 BLI_assert(active_back == NULL);
1199 /* no need to check 'data->state' here */
1201 /* entering text (set all) */
1202 but->active->value = data->value;
1203 ui_but_string_set(C, but, data->str);
1206 /* dragging (use delta) */
1207 if (data->multi_data.is_proportional) {
1208 but->active->value = mbut_state->origvalue * value_scale;
1211 but->active->value = mbut_state->origvalue + value_delta;
1214 /* clamp based on soft limits, see: T40154 */
1215 CLAMP(but->active->value, (double)but->softmin, (double)but->softmax);
1217 ui_but_execute_end(C, ar, but, active_back);
1220 /* highly unlikely */
1221 printf("%s: cant find button\n", __func__);
1229 #endif /* USE_DRAG_MULTINUM */
1232 #ifdef USE_DRAG_TOGGLE
1234 typedef struct uiDragToggleHandle {
1238 float but_cent_start[2];
1239 eButType but_type_start;
1244 } uiDragToggleHandle;
1246 static bool ui_drag_toggle_set_xy_xy(
1247 bContext *C, ARegion *ar, const bool is_set, const eButType but_type_start,
1248 const int xy_src[2], const int xy_dst[2])
1250 /* popups such as layers won't re-evaluate on redraw */
1251 const bool do_check = (ar->regiontype == RGN_TYPE_TEMPORARY);
1252 bool changed = false;
1255 for (block = ar->uiblocks.first; block; block = block->next) {
1258 float xy_a_block[2] = {UNPACK2(xy_src)};
1259 float xy_b_block[2] = {UNPACK2(xy_dst)};
1261 ui_window_to_block_fl(ar, block, &xy_a_block[0], &xy_a_block[1]);
1262 ui_window_to_block_fl(ar, block, &xy_b_block[0], &xy_b_block[1]);
1264 for (but = block->buttons.first; but; but = but->next) {
1265 /* Note: ctrl is always true here because (at least for now) we always want to consider text control
1266 * in this case, even when not embossed. */
1267 if (ui_but_is_interactive(but, true)) {
1268 if (BLI_rctf_isect_segment(&but->rect, xy_a_block, xy_b_block)) {
1270 /* execute the button */
1271 if (ui_but_is_bool(but) && but->type == but_type_start) {
1272 /* is it pressed? */
1273 bool is_set_but = ui_but_is_pushed(but);
1274 BLI_assert(ui_but_is_bool(but) == true);
1275 if (is_set_but != is_set) {
1276 UI_but_execute(C, but);
1278 ui_but_update_edited(but);
1290 /* apply now, not on release (or if handlers are canceled for whatever reason) */
1291 ui_apply_but_funcs_after(C);
1297 static void ui_drag_toggle_set(bContext *C, uiDragToggleHandle *drag_info, const int xy_input[2])
1299 ARegion *ar = CTX_wm_region(C);
1300 bool do_draw = false;
1304 * Initialize Locking:
1306 * Check if we need to initialize the lock axis by finding if the first
1307 * button we mouse over is X or Y aligned, then lock the mouse to that axis after.
1309 if (drag_info->is_init == false) {
1310 /* first store the buttons original coords */
1311 uiBut *but = ui_but_find_mouse_over_ex(ar, xy_input[0], xy_input[1], true);
1314 if (but->flag & UI_BUT_DRAG_LOCK) {
1315 const float but_cent_new[2] = {BLI_rctf_cent_x(&but->rect),
1316 BLI_rctf_cent_y(&but->rect)};
1318 /* check if this is a different button, chances are high the button wont move about :) */
1319 if (len_manhattan_v2v2(drag_info->but_cent_start, but_cent_new) > 1.0f) {
1320 if (fabsf(drag_info->but_cent_start[0] - but_cent_new[0]) <
1321 fabsf(drag_info->but_cent_start[1] - but_cent_new[1]))
1323 drag_info->xy_lock[0] = true;
1326 drag_info->xy_lock[1] = true;
1328 drag_info->is_init = true;
1332 drag_info->is_init = true;
1336 /* done with axis locking */
1339 xy[0] = (drag_info->xy_lock[0] == false) ? xy_input[0] : drag_info->xy_last[0];
1340 xy[1] = (drag_info->xy_lock[1] == false) ? xy_input[1] : drag_info->xy_last[1];
1343 /* touch all buttons between last mouse coord and this one */
1344 do_draw = ui_drag_toggle_set_xy_xy(C, ar, drag_info->is_set, drag_info->but_type_start, drag_info->xy_last, xy);
1347 ED_region_tag_redraw(ar);
1350 copy_v2_v2_int(drag_info->xy_last, xy);
1353 static void ui_handler_region_drag_toggle_remove(bContext *UNUSED(C), void *userdata)
1355 uiDragToggleHandle *drag_info = userdata;
1356 MEM_freeN(drag_info);
1359 static int ui_handler_region_drag_toggle(bContext *C, const wmEvent *event, void *userdata)
1361 uiDragToggleHandle *drag_info = userdata;
1364 switch (event->type) {
1367 if (event->val != KM_PRESS) {
1374 ui_drag_toggle_set(C, drag_info, &event->x);
1380 wmWindow *win = CTX_wm_window(C);
1381 ARegion *ar = CTX_wm_region(C);
1382 uiBut *but = ui_but_find_mouse_over_ex(ar, drag_info->xy_init[0], drag_info->xy_init[1], true);
1385 ui_apply_but_undo(but);
1388 WM_event_remove_ui_handler(&win->modalhandlers,
1389 ui_handler_region_drag_toggle,
1390 ui_handler_region_drag_toggle_remove,
1392 ui_handler_region_drag_toggle_remove(C, drag_info);
1394 WM_event_add_mousemove(C);
1395 return WM_UI_HANDLER_BREAK;
1398 return WM_UI_HANDLER_CONTINUE;
1402 static bool ui_but_is_drag_toggle(const uiBut *but)
1404 return ((ui_but_is_bool(but) == true) &&
1405 /* menu check is importnt so the button dragged over isn't removed instantly */
1406 (ui_block_is_menu(but->block) == false));
1409 #endif /* USE_DRAG_TOGGLE */
1412 #ifdef USE_ALLSELECT
1414 static bool ui_selectcontext_begin(
1415 bContext *C, uiBut *but, uiSelectContextStore *selctx_data)
1417 PointerRNA ptr, lptr, idptr;
1418 PropertyRNA *prop, *lprop;
1419 bool success = false;
1423 ListBase lb = {NULL};
1426 prop = but->rnaprop;
1427 index = but->rnaindex;
1429 /* for now don't support whole colors */
1433 /* if there is a valid property that is editable... */
1434 if (ptr.data && prop) {
1435 CollectionPointerLink *link;
1436 bool use_path_from_id;
1439 /* some facts we want to know */
1440 const bool is_array = RNA_property_array_check(prop);
1441 const int rna_type = RNA_property_type(prop);
1443 if (UI_context_copy_to_selected_list(C, &ptr, prop, &lb, &use_path_from_id, &path) &&
1444 !BLI_listbase_is_empty(&lb))
1446 selctx_data->elems_len = BLI_listbase_count(&lb);
1447 selctx_data->elems = MEM_mallocN(sizeof(uiSelectContextElem) * selctx_data->elems_len, __func__);
1449 for (i = 0, link = lb.first; i < selctx_data->elems_len; i++, link = link->next) {
1450 uiSelectContextElem *other = &selctx_data->elems[i];
1451 /* TODO,. de-duplicate copy_to_selected_button */
1452 if (link->ptr.data != ptr.data) {
1453 if (use_path_from_id) {
1454 /* Path relative to ID. */
1456 RNA_id_pointer_create(link->ptr.id.data, &idptr);
1457 RNA_path_resolve_property(&idptr, path, &lptr, &lprop);
1460 /* Path relative to elements from list. */
1462 RNA_path_resolve_property(&link->ptr, path, &lptr, &lprop);
1469 /* lptr might not be the same as link->ptr! */
1470 if ((lptr.data != ptr.data) &&
1472 RNA_property_editable(&lptr, lprop))
1476 if (rna_type == PROP_FLOAT) {
1477 other->val_f = RNA_property_float_get_index(&lptr, lprop, index);
1479 else if (rna_type == PROP_INT) {
1480 other->val_i = RNA_property_int_get_index(&lptr, lprop, index);
1482 /* ignored for now */
1484 else if (rna_type == PROP_BOOLEAN) {
1485 other->val_b = RNA_property_boolean_get_index(&lptr, lprop, index);
1490 if (rna_type == PROP_FLOAT) {
1491 other->val_f = RNA_property_float_get(&lptr, lprop);
1493 else if (rna_type == PROP_INT) {
1494 other->val_i = RNA_property_int_get(&lptr, lprop);
1496 /* ignored for now */
1498 else if (rna_type == PROP_BOOLEAN) {
1499 other->val_b = RNA_property_boolean_get(&lptr, lprop);
1501 else if (rna_type == PROP_ENUM) {
1502 other->val_i = RNA_property_enum_get(&lptr, lprop);
1511 selctx_data->elems_len -= 1;
1515 success = (selctx_data->elems_len != 0);
1519 if (selctx_data->elems_len == 0) {
1520 MEM_SAFE_FREE(selctx_data->elems);
1523 MEM_SAFE_FREE(path);
1526 /* caller can clear */
1527 selctx_data->do_free = true;
1530 but->flag |= UI_BUT_IS_SELECT_CONTEXT;
1536 static void ui_selectcontext_end(
1537 uiBut *but, uiSelectContextStore *selctx_data)
1539 if (selctx_data->do_free) {
1540 if (selctx_data->elems) {
1541 MEM_freeN(selctx_data->elems);
1545 but->flag &= ~UI_BUT_IS_SELECT_CONTEXT;
1548 static void ui_selectcontext_apply(
1549 bContext *C, uiBut *but, uiSelectContextStore *selctx_data,
1550 const double value, const double value_orig)
1552 if (selctx_data->elems) {
1553 PropertyRNA *prop = but->rnaprop;
1554 PropertyRNA *lprop = but->rnaprop;
1555 int index = but->rnaindex;
1557 const bool use_delta = (selctx_data->is_copy == false);
1565 const bool is_array = RNA_property_array_check(prop);
1566 const int rna_type = RNA_property_type(prop);
1568 if (rna_type == PROP_FLOAT) {
1569 delta.f = use_delta ? (value - value_orig) : value;
1570 RNA_property_float_range(&but->rnapoin, prop, &min.f, &max.f);
1572 else if (rna_type == PROP_INT) {
1573 delta.i = use_delta ? ((int)value - (int)value_orig) : (int)value;
1574 RNA_property_int_range(&but->rnapoin, prop, &min.i, &max.i);
1576 else if (rna_type == PROP_ENUM) {
1577 delta.i = RNA_property_enum_get(&but->rnapoin, prop); /* not a delta infact */
1579 else if (rna_type == PROP_BOOLEAN) {
1581 delta.b = RNA_property_boolean_get_index(&but->rnapoin, prop, index); /* not a delta infact */
1584 delta.b = RNA_property_boolean_get(&but->rnapoin, prop); /* not a delta infact */
1588 #ifdef USE_ALLSELECT_LAYER_HACK
1589 /* make up for not having 'handle_layer_buttons' */
1591 PropertySubType subtype = RNA_property_subtype(prop);
1593 if ((rna_type == PROP_BOOLEAN) &&
1594 ELEM(subtype, PROP_LAYER, PROP_LAYER_MEMBER) &&
1596 /* could check for 'handle_layer_buttons' */
1599 wmWindow *win = CTX_wm_window(C);
1600 if (!win->eventstate->shift) {
1601 const int len = RNA_property_array_length(&but->rnapoin, prop);
1602 int *tmparray = MEM_callocN(sizeof(int) * len, __func__);
1604 tmparray[index] = true;
1606 for (i = 0; i < selctx_data->elems_len; i++) {
1607 uiSelectContextElem *other = &selctx_data->elems[i];
1608 PointerRNA lptr = other->ptr;
1609 RNA_property_boolean_set_array(&lptr, lprop, tmparray);
1610 RNA_property_update(C, &lptr, lprop);
1613 MEM_freeN(tmparray);
1621 for (i = 0; i < selctx_data->elems_len; i++) {
1622 uiSelectContextElem *other = &selctx_data->elems[i];
1623 PointerRNA lptr = other->ptr;
1625 if (rna_type == PROP_FLOAT) {
1626 float other_value = use_delta ? (other->val_f + delta.f) : delta.f;
1627 CLAMP(other_value, min.f, max.f);
1629 RNA_property_float_set_index(&lptr, lprop, index, other_value);
1632 RNA_property_float_set(&lptr, lprop, other_value);
1635 else if (rna_type == PROP_INT) {
1636 int other_value = use_delta ? (other->val_i + delta.i) : delta.i;
1637 CLAMP(other_value, min.i, max.i);
1639 RNA_property_int_set_index(&lptr, lprop, index, other_value);
1642 RNA_property_int_set(&lptr, lprop, other_value);
1645 else if (rna_type == PROP_BOOLEAN) {
1646 const bool other_value = delta.b;
1648 RNA_property_boolean_set_index(&lptr, lprop, index, other_value);
1651 RNA_property_boolean_set(&lptr, lprop, delta.b);
1654 else if (rna_type == PROP_ENUM) {
1655 const int other_value = delta.i;
1656 BLI_assert(!is_array);
1657 RNA_property_enum_set(&lptr, lprop, other_value);
1660 RNA_property_update(C, &lptr, prop);
1665 #endif /* USE_ALLSELECT */
1668 static bool ui_but_contains_point_px_icon(uiBut *but, ARegion *ar, const wmEvent *event)
1671 int x = event->x, y = event->y;
1673 ui_window_to_block(ar, but->block, &x, &y);
1675 BLI_rcti_rctf_copy(&rect, &but->rect);
1677 if (but->imb || but->type == UI_BTYPE_COLOR) {
1678 /* use button size itself */
1680 else if (but->drawflag & UI_BUT_ICON_LEFT) {
1681 rect.xmax = rect.xmin + (BLI_rcti_size_y(&rect));
1684 int delta = BLI_rcti_size_x(&rect) - BLI_rcti_size_y(&rect);
1685 rect.xmin += delta / 2;
1686 rect.xmax -= delta / 2;
1689 return BLI_rcti_isect_pt(&rect, x, y);
1692 static bool ui_but_drag_init(
1693 bContext *C, uiBut *but,
1694 uiHandleButtonData *data, const wmEvent *event)
1696 /* prevent other WM gestures to start while we try to drag */
1697 WM_gestures_remove(C);
1699 if (ABS(data->dragstartx - event->x) + ABS(data->dragstarty - event->y) > U.dragthreshold) {
1701 button_activate_state(C, but, BUTTON_STATE_EXIT);
1702 data->cancel = true;
1703 #ifdef USE_DRAG_TOGGLE
1704 if (ui_but_is_bool(but)) {
1705 uiDragToggleHandle *drag_info = MEM_callocN(sizeof(*drag_info), __func__);
1708 /* call here because regular mouse-up event wont run,
1709 * typically 'button_activate_exit()' handles this */
1710 ui_apply_but_autokey(C, but);
1712 drag_info->is_set = ui_but_is_pushed(but);
1713 drag_info->but_cent_start[0] = BLI_rctf_cent_x(&but->rect);
1714 drag_info->but_cent_start[1] = BLI_rctf_cent_y(&but->rect);
1715 drag_info->but_type_start = but->type;
1716 copy_v2_v2_int(drag_info->xy_init, &event->x);
1717 copy_v2_v2_int(drag_info->xy_last, &event->x);
1719 /* needed for toggle drag on popups */
1720 ar_prev = CTX_wm_region(C);
1721 CTX_wm_region_set(C, data->region);
1723 WM_event_add_ui_handler(
1724 C, &data->window->modalhandlers,
1725 ui_handler_region_drag_toggle,
1726 ui_handler_region_drag_toggle_remove,
1727 drag_info, WM_HANDLER_BLOCKING);
1729 CTX_wm_region_set(C, ar_prev);
1733 if (but->type == UI_BTYPE_COLOR) {
1735 uiDragColorHandle *drag_info = MEM_callocN(sizeof(*drag_info), __func__);
1737 /* TODO support more button pointer types */
1738 if (but->rnaprop && RNA_property_subtype(but->rnaprop) == PROP_COLOR_GAMMA) {
1739 RNA_property_float_get_array(&but->rnapoin, but->rnaprop, drag_info->color);
1740 drag_info->gamma_corrected = true;
1743 else if (but->rnaprop && RNA_property_subtype(but->rnaprop) == PROP_COLOR) {
1744 RNA_property_float_get_array(&but->rnapoin, but->rnaprop, drag_info->color);
1745 drag_info->gamma_corrected = false;
1748 else if (but->pointype == UI_BUT_POIN_FLOAT) {
1749 copy_v3_v3(drag_info->color, (float *)but->poin);
1752 else if (but->pointype == UI_BUT_POIN_CHAR) {
1753 rgb_uchar_to_float(drag_info->color, (unsigned char *)but->poin);
1758 WM_event_start_drag(C, ICON_COLOR, WM_DRAG_COLOR, drag_info, 0.0, WM_DRAG_FREE_DATA);
1761 MEM_freeN(drag_info);
1766 wmDrag *drag = WM_event_start_drag(
1767 C, but->icon, but->dragtype, but->dragpoin,
1768 ui_but_value_get(but), WM_DRAG_NOP);
1771 WM_event_drag_image(
1772 drag, but->imb, but->imb_scale,
1773 BLI_rctf_size_x(&but->rect),
1774 BLI_rctf_size_y(&but->rect));
1783 /* ********************** linklines *********************** */
1785 static void ui_linkline_remove_active(uiBlock *block)
1789 uiLinkLine *line, *nline;
1792 for (but = block->buttons.first; but; but = but->next) {
1793 if (but->type == UI_BTYPE_LINK && but->link) {
1794 for (line = but->link->lines.first; line; line = nline) {
1797 if (line->flag & UI_SELECT) {
1798 BLI_remlink(&but->link->lines, line);
1800 link = line->from->link;
1802 /* are there more pointers allowed? */
1805 if (*(link->totlink) == 1) {
1806 *(link->totlink) = 0;
1807 MEM_freeN(*(link->ppoin));
1808 *(link->ppoin) = NULL;
1812 for (a = 0; a < (*(link->totlink)); a++) {
1814 if ((*(link->ppoin))[a] != line->to->poin) {
1815 (*(link->ppoin))[b] = (*(link->ppoin))[a];
1819 (*(link->totlink))--;
1823 *(link->poin) = NULL;
1834 static uiLinkLine *ui_but_find_link(uiBut *from, uiBut *to)
1841 for (line = link->lines.first; line; line = line->next) {
1842 if (line->from == from && line->to == to) {
1850 /* XXX BAD BAD HACK, fixme later **************** */
1851 /* Try to add an AND Controller between the sensor and the actuator logic bricks and to connect them all */
1852 static void ui_but_smart_controller_add(bContext *C, uiBut *from, uiBut *to)
1856 bActuator *act_to, *act_iter;
1858 bController ***sens_from_links;
1861 uiLink *link = from->link;
1863 PointerRNA props_ptr, object_ptr;
1866 sens_from_links = (bController ***)(link->ppoin);
1869 act_to = (bActuator *)(to->poin);
1871 /* (1) get the object */
1872 CTX_DATA_BEGIN (C, Object *, ob_iter, selected_editable_objects)
1874 for (sens_iter = ob_iter->sensors.first; sens_iter; sens_iter = sens_iter->next) {
1875 if (&(sens_iter->links) == sens_from_links) {
1885 /* (2) check if the sensor and the actuator are from the same object */
1886 for (act_iter = ob->actuators.first; act_iter; act_iter = (bActuator *)act_iter->next) {
1887 if (act_iter == act_to)
1891 /* only works if the sensor and the actuator are from the same object */
1892 if (!act_iter) return;
1894 /* in case the linked controller is not the active one */
1895 RNA_pointer_create((ID *)ob, &RNA_Object, ob, &object_ptr);
1897 WM_operator_properties_create(&props_ptr, "LOGIC_OT_controller_add");
1898 RNA_string_set(&props_ptr, "object", ob->id.name + 2);
1900 /* (3) add a new controller */
1901 if (WM_operator_name_call(C, "LOGIC_OT_controller_add", WM_OP_EXEC_DEFAULT, &props_ptr) & OPERATOR_FINISHED) {
1902 cont = (bController *)ob->controllers.last;
1903 /* Quick fix to make sure we always have an AND controller.
1904 * It might be nicer to make sure the operator gives us the right one though... */
1905 cont->type = CONT_LOGIC_AND;
1907 /* (4) link the sensor->controller->actuator */
1908 tmp_but = MEM_callocN(sizeof(uiBut), "uiBut");
1910 tmp_but, (void **)&cont, (void ***)&(cont->links),
1911 &cont->totlinks, from->link->tocode, (int)to->hardmin);
1912 tmp_but->hardmin = from->link->tocode;
1913 tmp_but->poin = (char *)cont;
1915 tmp_but->type = UI_BTYPE_INLINK;
1916 ui_but_link_add(C, from, tmp_but);
1918 tmp_but->type = UI_BTYPE_LINK;
1919 ui_but_link_add(C, tmp_but, to);
1921 /* (5) garbage collection */
1922 MEM_freeN(tmp_but->link);
1925 WM_operator_properties_free(&props_ptr);
1928 static void ui_but_link_add(bContext *C, uiBut *from, uiBut *to)
1930 /* in 'from' we have to add a link to 'to' */
1936 if ((line = ui_but_find_link(from, to))) {
1937 line->flag |= UI_SELECT;
1938 ui_linkline_remove_active(from->block);
1942 if (from->type == UI_BTYPE_INLINK && to->type == UI_BTYPE_INLINK) {
1945 else if (from->type == UI_BTYPE_LINK && to->type == UI_BTYPE_INLINK) {
1946 if (from->link->tocode != (int)to->hardmin) {
1947 ui_but_smart_controller_add(C, from, to);
1951 else if (from->type == UI_BTYPE_INLINK && to->type == UI_BTYPE_LINK) {
1952 if (to->link->tocode == (int)from->hardmin) {
1959 /* are there more pointers allowed? */
1961 oldppoin = *(link->ppoin);
1963 (*(link->totlink))++;
1964 *(link->ppoin) = MEM_callocN(*(link->totlink) * sizeof(void *), "new link");
1966 for (a = 0; a < (*(link->totlink)) - 1; a++) {
1967 (*(link->ppoin))[a] = oldppoin[a];
1969 (*(link->ppoin))[a] = to->poin;
1971 if (oldppoin) MEM_freeN(oldppoin);
1974 *(link->poin) = to->poin;
1980 static void ui_apply_but_LINK(bContext *C, uiBut *but, uiHandleButtonData *data)
1982 ARegion *ar = CTX_wm_region(C);
1985 for (bt = but->block->buttons.first; bt; bt = bt->next) {
1986 if (ui_but_contains_point_px(ar, bt, but->linkto[0] + ar->winrct.xmin, but->linkto[1] + ar->winrct.ymin) )
1989 if (bt && bt != but) {
1990 if (!ELEM(bt->type, UI_BTYPE_LINK, UI_BTYPE_INLINK) || !ELEM(but->type, UI_BTYPE_LINK, UI_BTYPE_INLINK))
1993 if (but->type == UI_BTYPE_LINK) ui_but_link_add(C, but, bt);
1994 else ui_but_link_add(C, bt, but);
1996 ui_apply_but_func(C, but);
1997 data->retval = but->retval;
1999 data->applied = true;
2002 static void ui_apply_but_IMAGE(bContext *C, uiBut *but, uiHandleButtonData *data)
2004 ui_apply_but_func(C, but);
2005 data->retval = but->retval;
2006 data->applied = true;
2009 static void ui_apply_but_HISTOGRAM(bContext *C, uiBut *but, uiHandleButtonData *data)
2011 ui_apply_but_func(C, but);
2012 data->retval = but->retval;
2013 data->applied = true;
2016 static void ui_apply_but_WAVEFORM(bContext *C, uiBut *but, uiHandleButtonData *data)
2018 ui_apply_but_func(C, but);
2019 data->retval = but->retval;
2020 data->applied = true;
2023 static void ui_apply_but_TRACKPREVIEW(bContext *C, uiBut *but, uiHandleButtonData *data)
2025 ui_apply_but_func(C, but);
2026 data->retval = but->retval;
2027 data->applied = true;
2031 static void ui_apply_but(bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const bool interactive)
2036 ColorBand *editcoba;
2037 CurveMapping *editcumap;
2041 /* if we cancel and have not applied yet, there is nothing to do,
2042 * otherwise we have to restore the original value again */
2047 if (data->str) MEM_freeN(data->str);
2048 data->str = data->origstr;
2049 data->origstr = NULL;
2050 data->value = data->origvalue;
2051 copy_v3_v3(data->vec, data->origvec);
2052 /* postpone clearing origdata */
2055 /* we avoid applying interactive edits a second time
2056 * at the end with the appliedinteractive flag */
2058 data->applied_interactive = true;
2060 else if (data->applied_interactive) {
2064 #ifdef USE_ALLSELECT
2065 # ifdef USE_DRAG_MULTINUM
2066 if (but->flag & UI_BUT_DRAG_MULTI) {
2071 if (data->select_others.elems_len == 0) {
2072 wmWindow *win = CTX_wm_window(C);
2073 /* may have been enabled before activating */
2074 if (data->select_others.is_enabled || IS_ALLSELECT_EVENT(win->eventstate)) {
2075 ui_selectcontext_begin(C, but, &data->select_others);
2076 data->select_others.is_enabled = true;
2079 if (data->select_others.elems_len == 0) {
2080 /* dont check again */
2081 data->select_others.elems_len = -1;
2086 /* ensures we are writing actual values */
2087 editstr = but->editstr;
2088 editval = but->editval;
2089 editvec = but->editvec;
2090 editcoba = but->editcoba;
2091 editcumap = but->editcumap;
2092 but->editstr = NULL;
2093 but->editval = NULL;
2094 but->editvec = NULL;
2095 but->editcoba = NULL;
2096 but->editcumap = NULL;
2098 /* handle different types */
2099 switch (but->type) {
2101 ui_apply_but_BUT(C, but, data);
2104 case UI_BTYPE_SEARCH_MENU:
2105 ui_apply_but_TEX(C, but, data);
2107 case UI_BTYPE_BUT_TOGGLE:
2108 case UI_BTYPE_TOGGLE:
2109 case UI_BTYPE_TOGGLE_N:
2110 case UI_BTYPE_ICON_TOGGLE:
2111 case UI_BTYPE_ICON_TOGGLE_N:
2112 case UI_BTYPE_CHECKBOX:
2113 case UI_BTYPE_CHECKBOX_N:
2114 ui_apply_but_TOG(C, but, data);
2117 case UI_BTYPE_LISTROW:
2118 ui_apply_but_ROW(C, block, but, data);
2120 case UI_BTYPE_SCROLL:
2123 case UI_BTYPE_NUM_SLIDER:
2124 ui_apply_but_NUM(C, but, data);
2127 case UI_BTYPE_BLOCK:
2128 case UI_BTYPE_PULLDOWN:
2129 ui_apply_but_BLOCK(C, but, data);
2131 case UI_BTYPE_COLOR:
2133 ui_apply_but_VEC(C, but, data);
2135 ui_apply_but_BLOCK(C, but, data);
2137 case UI_BTYPE_BUT_MENU:
2138 ui_apply_but_BUTM(C, but, data);
2140 case UI_BTYPE_UNITVEC:
2141 case UI_BTYPE_HSVCUBE:
2142 case UI_BTYPE_HSVCIRCLE:
2143 ui_apply_but_VEC(C, but, data);
2145 case UI_BTYPE_COLORBAND:
2146 ui_apply_but_COLORBAND(C, but, data);
2148 case UI_BTYPE_CURVE:
2149 ui_apply_but_CURVE(C, but, data);
2151 case UI_BTYPE_KEY_EVENT:
2152 case UI_BTYPE_HOTKEY_EVENT:
2153 ui_apply_but_BUT(C, but, data);
2156 case UI_BTYPE_INLINK:
2157 ui_apply_but_LINK(C, but, data);
2159 case UI_BTYPE_IMAGE:
2160 ui_apply_but_IMAGE(C, but, data);
2162 case UI_BTYPE_HISTOGRAM:
2163 ui_apply_but_HISTOGRAM(C, but, data);
2165 case UI_BTYPE_WAVEFORM:
2166 ui_apply_but_WAVEFORM(C, but, data);
2168 case UI_BTYPE_TRACK_PREVIEW:
2169 ui_apply_but_TRACKPREVIEW(C, but, data);
2175 #ifdef USE_DRAG_MULTINUM
2176 if (data->multi_data.has_mbuts) {
2177 if (data->multi_data.init == BUTTON_MULTI_INIT_ENABLE) {
2179 ui_multibut_restore(C, data, block);
2182 ui_multibut_states_apply(C, data, block);
2188 #ifdef USE_ALLSELECT
2189 ui_selectcontext_apply(C, but, &data->select_others, data->value, data->origvalue);
2193 data->origvalue = 0.0;
2194 zero_v3(data->origvec);
2197 but->editstr = editstr;
2198 but->editval = editval;
2199 but->editvec = editvec;
2200 but->editcoba = editcoba;
2201 but->editcumap = editcumap;
2204 /* ******************* drop event ******************** */
2206 /* only call if event type is EVT_DROP */
2207 static void ui_but_drop(bContext *C, const wmEvent *event, uiBut *but, uiHandleButtonData *data)
2210 ListBase *drags = event->customdata; /* drop event type has listbase customdata by default */
2212 for (wmd = drags->first; wmd; wmd = wmd->next) {
2213 if (wmd->type == WM_DRAG_ID) {
2214 /* align these types with UI_but_active_drop_name */
2215 if (ELEM(but->type, UI_BTYPE_TEXT, UI_BTYPE_SEARCH_MENU)) {
2216 ID *id = (ID *)wmd->poin;
2218 button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING);
2220 ui_textedit_string_set(but, data, id->name + 2);
2222 if (ELEM(but->type, UI_BTYPE_SEARCH_MENU)) {
2223 but->changed = true;
2224 ui_searchbox_update(C, data->searchbox, but, true);
2227 button_activate_state(C, but, BUTTON_STATE_EXIT);
2234 /* ******************* copy and paste ******************** */
2236 /* c = copy, v = paste */
2237 static void ui_but_copy_paste(bContext *C, uiBut *but, uiHandleButtonData *data, const char mode, const bool copy_array)
2239 int buf_paste_len = 0;
2240 const char *buf_paste = "";
2241 bool buf_paste_alloc = false;
2242 bool show_report = false; /* use to display errors parsing paste input */
2244 BLI_assert((but->flag & UI_BUT_DISABLED) == 0); /* caller should check */
2247 /* disallow copying from any passwords */
2248 if (but->rnaprop && (RNA_property_subtype(but->rnaprop) == PROP_PASSWORD)) {
2254 /* extract first line from clipboard in case of multi-line copies */
2255 const char *buf_paste_test;
2257 buf_paste_test = WM_clipboard_text_get_firstline(false, &buf_paste_len);
2258 if (buf_paste_test) {
2259 buf_paste = buf_paste_test;
2260 buf_paste_alloc = true;
2264 /* No return from here down */
2268 if (ELEM(but->type, UI_BTYPE_NUM, UI_BTYPE_NUM_SLIDER)) {
2270 if (but->poin == NULL && but->rnapoin.data == NULL) {
2273 else if (copy_array && but->rnapoin.data && but->rnaprop &&
2274 ELEM(RNA_property_subtype(but->rnaprop), PROP_COLOR, PROP_TRANSLATION, PROP_DIRECTION,
2275 PROP_VELOCITY, PROP_ACCELERATION, PROP_MATRIX, PROP_EULER, PROP_QUATERNION, PROP_AXISANGLE,
2276 PROP_XYZ, PROP_XYZ_LENGTH, PROP_COLOR_GAMMA, PROP_COORDS))
2279 int array_length = RNA_property_array_length(&but->rnapoin, but->rnaprop);
2282 char buf_copy[UI_MAX_DRAW_STR];
2284 if (array_length == 4) {
2285 values[3] = RNA_property_float_get_index(&but->rnapoin, but->rnaprop, 3);
2290 ui_but_v3_get(but, values);
2292 BLI_snprintf(buf_copy, sizeof(buf_copy), "[%f, %f, %f, %f]", values[0], values[1], values[2], values[3]);
2293 WM_clipboard_text_set(buf_copy, 0);
2296 if (sscanf(buf_paste, "[%f, %f, %f, %f]", &values[0], &values[1], &values[2], &values[3]) >= array_length) {
2297 button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
2299 ui_but_v3_set(but, values);
2300 if (but->rnaprop && array_length == 4) {
2301 RNA_property_float_set_index(&but->rnapoin, but->rnaprop, 3, values[3]);
2303 data->value = values[but->rnaindex];
2305 button_activate_state(C, but, BUTTON_STATE_EXIT);
2308 WM_report(RPT_ERROR, "Paste expected 4 numbers, formatted: '[n, n, n, n]'");
2313 else if (mode == 'c') {
2314 /* Get many decimal places, then strip trailing zeros.
2315 * note: too high values start to give strange results */
2316 char buf_copy[UI_MAX_DRAW_STR];
2317 ui_but_string_get_ex(but, buf_copy, sizeof(buf_copy), UI_PRECISION_FLOAT_MAX, false, NULL);
2318 BLI_str_rstrip_float_zero(buf_copy, '\0');
2320 WM_clipboard_text_set(buf_copy, 0);
2325 if (ui_but_string_set_eval_num(C, but, buf_paste, &val)) {
2326 button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
2328 ui_but_string_set(C, but, buf_paste);
2329 button_activate_state(C, but, BUTTON_STATE_EXIT);
2332 /* evaluating will report errors */
2339 else if (but->type == UI_BTYPE_UNITVEC) {
2342 if (but->poin == NULL && but->rnapoin.data == NULL) {
2345 else if (mode == 'c') {
2346 char buf_copy[UI_MAX_DRAW_STR];
2347 ui_but_v3_get(but, xyz);
2348 BLI_snprintf(buf_copy, sizeof(buf_copy), "[%f, %f, %f]", xyz[0], xyz[1], xyz[2]);
2349 WM_clipboard_text_set(buf_copy, 0);
2352 if (sscanf(buf_paste, "[%f, %f, %f]", &xyz[0], &xyz[1], &xyz[2]) == 3) {
2353 if (normalize_v3(xyz) == 0.0f) {
2354 /* better set Z up then have a zero vector */
2357 button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
2358 ui_but_v3_set(but, xyz);
2359 button_activate_state(C, but, BUTTON_STATE_EXIT);
2362 WM_report(RPT_ERROR, "Paste expected 3 numbers, formatted: '[n, n, n]'");
2370 else if (but->type == UI_BTYPE_COLOR) {
2373 if (but->poin == NULL && but->rnapoin.data == NULL) {
2376 else if (mode == 'c') {
2377 char buf_copy[UI_MAX_DRAW_STR];
2379 if (but->rnaprop && RNA_property_array_length(&but->rnapoin, but->rnaprop) == 4)
2380 rgba[3] = RNA_property_float_get_index(&but->rnapoin, but->rnaprop, 3);
2384 ui_but_v3_get(but, rgba);
2385 /* convert to linear color to do compatible copy between gamma and non-gamma */
2386 if (but->rnaprop && RNA_property_subtype(but->rnaprop) == PROP_COLOR_GAMMA)
2387 srgb_to_linearrgb_v3_v3(rgba, rgba);
2389 BLI_snprintf(buf_copy, sizeof(buf_copy), "[%f, %f, %f, %f]", rgba[0], rgba[1], rgba[2], rgba[3]);
2390 WM_clipboard_text_set(buf_copy, 0);
2394 if (sscanf(buf_paste, "[%f, %f, %f, %f]", &rgba[0], &rgba[1], &rgba[2], &rgba[3]) == 4) {
2395 /* assume linear colors in buffer */
2396 if (but->rnaprop && RNA_property_subtype(but->rnaprop) == PROP_COLOR_GAMMA)
2397 linearrgb_to_srgb_v3_v3(rgba, rgba);
2399 button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
2400 ui_but_v3_set(but, rgba);
2401 if (but->rnaprop && RNA_property_array_length(&but->rnapoin, but->rnaprop) == 4)
2402 RNA_property_float_set_index(&but->rnapoin, but->rnaprop, 3, rgba[3]);
2404 button_activate_state(C, but, BUTTON_STATE_EXIT);
2407 WM_report(RPT_ERROR, "Paste expected 4 numbers, formatted: '[n, n, n, n]'");
2413 /* text/string and ID data */
2414 else if (ELEM(but->type, UI_BTYPE_TEXT, UI_BTYPE_SEARCH_MENU)) {
2415 uiHandleButtonData *active_data = but->active;
2417 if (but->poin == NULL && but->rnapoin.data == NULL) {
2420 else if (mode == 'c') {
2421 button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING);
2422 WM_clipboard_text_set(active_data->str, 0);
2423 active_data->cancel = true;
2424 button_activate_state(C, but, BUTTON_STATE_EXIT);
2427 button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING);
2429 ui_textedit_string_set(but, active_data, buf_paste);
2431 if (but->type == UI_BTYPE_SEARCH_MENU) {
2432 /* else uiSearchboxData.active member is not updated [#26856] */
2433 but->changed = true;
2434 ui_searchbox_update(C, data->searchbox, but, true);
2436 button_activate_state(C, but, BUTTON_STATE_EXIT);
2439 /* colorband (not supported by system clipboard) */
2440 else if (but->type == UI_BTYPE_COLORBAND) {
2442 if (but->poin != NULL) {
2443 memcpy(&but_copypaste_coba, but->poin, sizeof(ColorBand));
2447 if (but_copypaste_coba.tot != 0) {
2449 but->poin = MEM_callocN(sizeof(ColorBand), "colorband");
2451 button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
2452 memcpy(data->coba, &but_copypaste_coba, sizeof(ColorBand));
2453 button_activate_state(C, but, BUTTON_STATE_EXIT);
2457 else if (but->type == UI_BTYPE_CURVE) {
2459 if (but->poin != NULL) {
2460 but_copypaste_curve_alive = true;
2461 curvemapping_free_data(&but_copypaste_curve);
2462 curvemapping_copy_data(&but_copypaste_curve, (CurveMapping *) but->poin);
2466 if (but_copypaste_curve_alive) {
2468 but->poin = MEM_callocN(sizeof(CurveMapping), "curvemapping");
2470 button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
2471 curvemapping_free_data((CurveMapping *) but->poin);
2472 curvemapping_copy_data((CurveMapping *) but->poin, &but_copypaste_curve);
2473 button_activate_state(C, but, BUTTON_STATE_EXIT);
2477 /* operator button (any type) */
2478 else if (but->optype) {
2482 opptr = UI_but_operator_ptr_get(but); /* allocated when needed, the button owns it */
2484 str = WM_operator_pystring_ex(C, NULL, false, true, but->optype, opptr);
2486 WM_clipboard_text_set(str, 0);
2491 /* menu (any type) */
2492 else if (ELEM(but->type, UI_BTYPE_MENU, UI_BTYPE_PULLDOWN)) {
2493 MenuType *mt = UI_but_menutype_get(but);
2495 char str[32 + sizeof(mt->idname)];
2496 BLI_snprintf(str, sizeof(str), "bpy.ops.wm.call_menu(name=\"%s\")", mt->idname);
2497 WM_clipboard_text_set(str, 0);
2501 if (buf_paste_alloc) {
2502 MEM_freeN((void *)buf_paste);
2506 WM_report_banner_show();
2514 * Functions to convert password strings that should not be displayed
2515 * to asterisk representation (e.g. 'mysecretpasswd' -> '*************')
2517 * It converts every UTF-8 character to an asterisk, and also remaps
2518 * the cursor position and selection start/end.
2520 * \note: remaping is used, because password could contain UTF-8 characters.
2524 static int ui_text_position_from_hidden(uiBut *but, int pos)
2526 const char *strpos, *butstr;
2529 butstr = (but->editstr) ? but->editstr : but->drawstr;
2531 for (i = 0, strpos = butstr; i < pos; i++)
2532 strpos = BLI_str_find_next_char_utf8(strpos, NULL);
2534 return (strpos - butstr);
2537 static int ui_text_position_to_hidden(uiBut *but, int pos)
2539 const char *butstr = (but->editstr) ? but->editstr : but->drawstr;
2540 return BLI_strnlen_utf8(butstr, pos);
2543 void ui_but_text_password_hide(char password_str[UI_MAX_PASSWORD_STR], uiBut *but, const bool restore)
2547 if (!(but->rnaprop && RNA_property_subtype(but->rnaprop) == PROP_PASSWORD))
2550 butstr = (but->editstr) ? but->editstr : but->drawstr;
2553 /* restore original string */
2554 BLI_strncpy(butstr, password_str, UI_MAX_PASSWORD_STR);
2556 /* remap cursor positions */
2557 if (but->pos >= 0) {
2558 but->pos = ui_text_position_from_hidden(but, but->pos);
2559 but->selsta = ui_text_position_from_hidden(but, but->selsta);
2560 but->selend = ui_text_position_from_hidden(but, but->selend);
2564 /* convert text to hidden text using asterisks (e.g. pass -> ****) */
2565 const size_t len = BLI_strlen_utf8(butstr);
2567 /* remap cursor positions */
2568 if (but->pos >= 0) {
2569 but->pos = ui_text_position_to_hidden(but, but->pos);
2570 but->selsta = ui_text_position_to_hidden(but, but->selsta);
2571 but->selend = ui_text_position_to_hidden(but, but->selend);
2574 /* save original string */
2575 BLI_strncpy(password_str, butstr, UI_MAX_PASSWORD_STR);
2576 memset(butstr, '*', len);
2581 static void ui_but_text_clear(bContext *C, uiBut *but, uiHandleButtonData *data)
2583 /* most likely NULL, but let's check, and give it temp zero string */
2585 data->str = MEM_callocN(1, "temp str");
2589 ui_apply_but_TEX(C, but, data);
2590 button_activate_state(C, but, BUTTON_STATE_EXIT);
2594 /* ************* in-button text selection/editing ************* */
2596 static void ui_textedit_string_ensure_max_length(uiBut *but, uiHandleButtonData *data, int maxlen)
2598 BLI_assert(data->is_str_dynamic);
2599 BLI_assert(data->str == but->editstr);
2601 if (maxlen > data->maxlen) {
2602 data->str = but->editstr = MEM_reallocN(data->str, sizeof(char) * maxlen);
2603 data->maxlen = maxlen;
2607 static void ui_textedit_string_set(uiBut *but, uiHandleButtonData *data, const char *str)
2609 if (data->is_str_dynamic) {
2610 ui_textedit_string_ensure_max_length(but, data, strlen(str) + 1);
2613 if (ui_but_is_utf8(but)) {
2614 BLI_strncpy_utf8(data->str, str, data->maxlen);
2617 BLI_strncpy(data->str, str, data->maxlen);
2622 static bool ui_textedit_delete_selection(uiBut *but, uiHandleButtonData *data)
2624 char *str = data->str;
2625 const int len = strlen(str);
2626 bool changed = false;
2627 if (but->selsta != but->selend && len) {
2628 memmove(str + but->selsta, str + but->selend, (len - but->selend) + 1);
2632 but->pos = but->selend = but->selsta;
2637 * \param x Screen space cursor location - #wmEvent.x
2639 * \note ``but->block->aspect`` is used here, so drawing button style is getting scaled too.
2641 static void ui_textedit_set_cursor_pos(uiBut *but, uiHandleButtonData *data, const float x)
2643 uiStyle *style = UI_style_get(); // XXX pass on as arg
2644 uiFontStyle *fstyle = &style->widget;
2645 const float aspect = but->block->aspect;
2646 const short fstyle_points_prev = fstyle->points;
2648 float startx = but->rect.xmin;
2649 float starty_dummy = 0.0f;
2650 char password_str[UI_MAX_PASSWORD_STR];
2651 /* treat 'str_last' as null terminator for str, no need to modify in-place */
2652 const char *str = but->editstr, *str_last;
2654 ui_block_to_window_fl(data->region, but->block, &startx, &starty_dummy);
2656 ui_fontscale(&fstyle->points, aspect);
2658 UI_fontstyle_set(fstyle);
2660 if (fstyle->kerning == 1) /* for BLF_width */
2661 BLF_enable(fstyle->uifont_id, BLF_KERNING_DEFAULT);
2663 ui_but_text_password_hide(password_str, but, false);
2665 if (ELEM(but->type, UI_BTYPE_TEXT, UI_BTYPE_SEARCH_MENU)) {
2666 if (but->flag & UI_HAS_ICON) {
2667 startx += UI_DPI_ICON_SIZE / aspect;
2670 /* but this extra .05 makes clicks inbetween characters feel nicer */
2671 startx += ((UI_TEXT_MARGIN_X + 0.05f) * U.widget_unit) / aspect;
2673 /* mouse dragged outside the widget to the left */
2677 str_last = &str[but->ofs];
2680 if (BLI_str_cursor_step_prev_utf8(str, but->ofs, &i)) {
2681 /* 0.25 == scale factor for less sensitivity */
2682 if (BLF_width(fstyle->uifont_id, str + i, (str_last - str) - i) > (startx - x) * 0.25f) {
2687 break; /* unlikely but possible */
2691 but->pos = but->ofs;
2693 /* mouse inside the widget, mouse coords mapped in widget space */
2694 else { /* (x >= startx) */
2697 /* keep track of previous distance from the cursor to the char */
2698 float cdist, cdist_prev = 0.0f;
2701 str_last = &str[strlen(str)];
2703 but->pos = pos_prev = ((str_last - str) - but->ofs);
2706 cdist = startx + BLF_width(fstyle->uifont_id, str + but->ofs, (str_last - str) - but->ofs);
2708 /* check if position is found */
2710 /* check is previous location was in fact closer */
2711 if ((x - cdist) > (cdist_prev - x)) {
2712 but->pos = pos_prev;
2717 pos_prev = but->pos;
2718 /* done with tricky distance checks */
2721 if (but->pos <= 0) break;
2722 if (BLI_str_cursor_step_prev_utf8(str, but->ofs, &pos_i)) {
2724 str_last = &str[but->pos + but->ofs];
2727 break; /* unlikely but possible */
2730 but->pos += but->ofs;
2731 if (but->pos < 0) but->pos = 0;
2734 if (fstyle->kerning == 1)
2735 BLF_disable(fstyle->uifont_id, BLF_KERNING_DEFAULT);
2737 ui_but_text_password_hide(password_str, but, true);
2739 fstyle->points = fstyle_points_prev;
2742 static void ui_textedit_set_cursor_select(uiBut *but, uiHandleButtonData *data, const float x)
2744 if (x > data->selstartx) data->selextend = EXTEND_RIGHT;
2745 else if (x < data->selstartx) data->selextend = EXTEND_LEFT;
2747 ui_textedit_set_cursor_pos(but, data, x);
2749 if (data->selextend == EXTEND_RIGHT) but->selend = but->pos;
2750 else if (data->selextend == EXTEND_LEFT) but->selsta = but->pos;
2756 * This is used for both utf8 and ascii
2758 * For unicode buttons, \a buf is treated as unicode.
2760 static bool ui_textedit_insert_buf(
2761 uiBut *but, uiHandleButtonData *data,
2762 const char *buf, int buf_len)
2764 int len = strlen(data->str);
2765 int len_new = len - (but->selend - but->selsta) + 1;
2766 bool changed = false;
2768 if (data->is_str_dynamic) {
2769 ui_textedit_string_ensure_max_length(but, data, len_new + buf_len);
2772 if (len_new <= data->maxlen) {
2773 char *str = data->str;
2774 size_t step = buf_len;
2776 /* type over the current selection */
2777 if ((but->selend - but->selsta) > 0) {
2778 changed = ui_textedit_delete_selection(but, data);
2782 if ((len + step >= data->maxlen) && (data->maxlen - (len + 1) > 0)) {
2783 if (ui_but_is_utf8(but)) {
2784 /* shorten 'step' to a utf8 algined size that fits */
2785 BLI_strnlen_utf8_ex(buf, data->maxlen - (len + 1), &step);
2788 step = data->maxlen - (len + 1);
2792 if (step && (len + step < data->maxlen)) {
2793 memmove(&str[but->pos + step], &str[but->pos], (len + 1) - but->pos);
2794 memcpy(&str[but->pos], buf, step * sizeof(char));
2803 static bool ui_textedit_insert_ascii(uiBut *but, uiHandleButtonData *data, char ascii)
2805 char buf[2] = {ascii, '\0'};
2807 if (ui_but_is_utf8(but) && (BLI_str_utf8_size(buf) == -1)) {
2808 printf("%s: entering invalid ascii char into an ascii key (%d)\n",
2809 __func__, (int)(unsigned char)ascii);
2814 /* in some cases we want to allow invalid utf8 chars */
2815 return ui_textedit_insert_buf(but, data, buf, 1);
2818 static void ui_textedit_move(
2819 uiBut *but, uiHandleButtonData *data, eStrCursorJumpDirection direction,
2820 const bool select, eStrCursorJumpType jump)
2822 const char *str = data->str;
2823 const int len = strlen(str);
2824 const int pos_prev = but->pos;
2825 const bool has_sel = (but->selend - but->selsta) > 0;
2829 /* special case, quit selection and set cursor */
2830 if (has_sel && !select) {
2831 if (jump == STRCUR_JUMP_ALL) {
2832 but->selsta = but->selend = but->pos = direction ? len : 0;
2836 but->selsta = but->pos = but->selend;
2839 but->pos = but->selend = but->selsta;
2842 data->selextend = EXTEND_NONE;
2845 int pos_i = but->pos;
2846 BLI_str_cursor_step_utf8(str, len, &pos_i, direction, jump, true);
2850 /* existing selection */
2853 if (data->selextend == EXTEND_NONE) {
2854 data->selextend = EXTEND_RIGHT;
2858 if (data->selextend == EXTEND_RIGHT) {
2859 but->selend = but->pos;
2862 but->selsta = but->pos;
2866 if (data->selextend == EXTEND_LEFT) {
2867 but->selsta = but->pos;
2870 but->selend = but->pos;
2874 if (but->selend < but->selsta) {
2875 SWAP(short, but->selsta, but->selend);
2876 data->selextend = (data->selextend == EXTEND_RIGHT) ? EXTEND_LEFT : EXTEND_RIGHT;
2879 } /* new selection */
2882 data->selextend = EXTEND_RIGHT;
2883 but->selend = but->pos;
2884 but->selsta = pos_prev;
2887 data->selextend = EXTEND_LEFT;
2888 but->selend = pos_prev;
2889 but->selsta = but->pos;
2896 static bool ui_textedit_delete(uiBut *but, uiHandleButtonData *data, int direction, eStrCursorJumpType jump)
2898 char *str = data->str;
2899 const int len = strlen(str);
2901 bool changed = false;
2903 if (jump == STRCUR_JUMP_ALL) {
2904 if (len) changed = true;
2908 else if (direction) { /* delete */
2909 if ((but->selend - but->selsta) > 0) {
2910 changed = ui_textedit_delete_selection(but, data);
2912 else if (but->pos >= 0 && but->pos < len) {
2915 BLI_str_cursor_step_utf8(str, len, &pos, direction, jump, true);
2916 step = pos - but->pos;
2917 memmove(&str[but->pos], &str[but->pos + step], (len + 1) - (but->pos + step));
2921 else { /* backspace */
2923 if ((but->selend - but->selsta) > 0) {
2924 changed = ui_textedit_delete_selection(but, data);
2926 else if (but->pos > 0) {
2930 BLI_str_cursor_step_utf8(str, len, &pos, direction, jump, true);
2931 step = but->pos - pos;
2932 memmove(&str[but->pos - step], &str[but->pos], (len + 1) - but->pos);
2942 static int ui_textedit_autocomplete(bContext *C, uiBut *but, uiHandleButtonData *data)
2949 if (data->searchbox)
2950 changed = ui_searchbox_autocomplete(C, data->searchbox, but, data->str);
2952 changed = but->autocomplete_func(C, str, but->autofunc_arg);
2954 but->pos = strlen(str);
2955 but->selsta = but->selend = but->pos;
2960 /* mode for ui_textedit_copypaste() */
2962 UI_TEXTEDIT_PASTE = 1,
2967 static bool ui_textedit_copypaste(uiBut *but, uiHandleButtonData *data, const int mode)
2970 bool changed = false;
2974 if (mode == UI_TEXTEDIT_PASTE) {
2975 /* extract the first line from the clipboard */
2976 pbuf = WM_clipboard_text_get_firstline(false, &buf_len);
2979 if (ui_but_is_utf8(but)) {
2980 buf_len -= BLI_utf8_invalid_strip(pbuf, (size_t)buf_len);
2983 ui_textedit_insert_buf(but, data, pbuf, buf_len);
2991 else if (ELEM(mode, UI_TEXTEDIT_COPY, UI_TEXTEDIT_CUT)) {
2992 /* copy the contents to the copypaste buffer */
2993 int sellen = but->selend - but->selsta;
2994 char *buf = MEM_mallocN(sizeof(char) * (sellen + 1), "ui_textedit_copypaste");
2996 BLI_strncpy(buf, data->str + but->selsta, sellen + 1);
2997 WM_clipboard_text_set(buf, 0);
3000 /* for cut only, delete the selection afterwards */
3001 if (mode == UI_TEXTEDIT_CUT) {
3002 if ((but->selend - but->selsta) > 0) {
3003 changed = ui_textedit_delete_selection(but, data);
3011 #ifdef WITH_INPUT_IME
3012 /* enable ime, and set up uibut ime data */
3013 static void ui_textedit_ime_begin(wmWindow *win, uiBut *UNUSED(but))
3015 /* XXX Is this really needed? */
3018 BLI_assert(win->ime_data == NULL);
3020 /* enable IME and position to cursor, it's a trick */
3021 x = win->eventstate->x;
3022 /* flip y and move down a bit, prevent the IME panel cover the edit button */
3023 y = win->eventstate->y - 12;
3025 wm_window_IME_begin(win, x, y, 0, 0, true);
3028 /* disable ime, and clear uibut ime data */
3029 static void ui_textedit_ime_end(wmWindow *win, uiBut *UNUSED(but))
3031 wm_window_IME_end(win);
3034 void ui_but_ime_reposition(uiBut *but, int x, int y, bool complete)
3036 BLI_assert(but->active);
3038 ui_region_to_window(but->active->region, &x, &y);
3039 wm_window_IME_begin(but->active->window, x, y - 4, 0, 0, complete);
3042 wmIMEData *ui_but_ime_data_get(uiBut *but)
3044 if (but->active && but->active->window) {
3045 return but->active->window->ime_data;
3051 #endif /* WITH_INPUT_IME */
3053 static void ui_textedit_begin(bContext