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_sensor_types.h"
42 #include "DNA_controller_types.h"
43 #include "DNA_actuator_types.h"
45 #include "DNA_object_types.h"
46 #include "DNA_scene_types.h"
47 #include "DNA_screen_types.h"
50 #include "BLI_blenlib.h"
51 #include "BLI_utildefines.h"
52 #include "BLI_string_cursor_utf8.h"
54 #include "BLF_translation.h"
58 #include "BKE_blender.h"
59 #include "BKE_colortools.h"
60 #include "BKE_context.h"
61 #include "BKE_idprop.h"
62 #include "BKE_report.h"
63 #include "BKE_texture.h"
64 #include "BKE_tracking.h"
66 #include "BKE_paint.h"
68 #include "ED_screen.h"
70 #include "ED_keyframing.h"
72 #include "UI_interface.h"
76 #include "interface_intern.h"
78 #include "RNA_access.h"
83 /* place the mouse at the scaled down location when un-grabbing */
84 #define USE_CONT_MOUSE_CORRECT
85 /* support dragging toggle buttons */
86 #define USE_DRAG_TOGGLE
88 /* so we can avoid very small mouse-moves from jumping away from keyboard navigation [#34936] */
89 #define USE_KEYNAV_LIMIT
92 static void ui_add_smart_controller(bContext *C, uiBut *from, uiBut *to);
93 static void ui_add_link(bContext *C, uiBut *from, uiBut *to);
94 static int ui_do_but_EXIT(bContext *C, uiBut *but, struct uiHandleButtonData *data, const wmEvent *event);
96 #ifdef USE_KEYNAV_LIMIT
97 static void ui_mouse_motion_keynav_init(struct uiKeyNavLock *keynav, const wmEvent *event);
98 static bool ui_mouse_motion_keynav_test(struct uiKeyNavLock *keynav, const wmEvent *event);
101 /***************** structs and defines ****************/
103 #define BUTTON_TOOLTIP_DELAY 0.500
104 #define BUTTON_FLASH_DELAY 0.020
105 #define MENU_SCROLL_INTERVAL 0.1
106 #define BUTTON_AUTO_OPEN_THRESH 0.3
107 #define BUTTON_MOUSE_TOWARDS_THRESH 1.0
108 /* pixels to move the cursor to get out of keyboard navigation */
109 #define BUTTON_KEYNAV_PX_LIMIT 4
111 #define MENU_TOWARDS_MARGIN 20 /* margin in pixels */
112 #define MENU_TOWARDS_WIGGLE_ROOM 64 /* tolerance in pixels */
114 typedef enum uiButtonActivateType {
115 BUTTON_ACTIVATE_OVER,
117 BUTTON_ACTIVATE_APPLY,
118 BUTTON_ACTIVATE_TEXT_EDITING,
120 } uiButtonActivateType;
122 typedef enum uiHandleButtonState {
124 BUTTON_STATE_HIGHLIGHT,
125 BUTTON_STATE_WAIT_FLASH,
126 BUTTON_STATE_WAIT_RELEASE,
127 BUTTON_STATE_WAIT_KEY_EVENT,
128 BUTTON_STATE_NUM_EDITING,
129 BUTTON_STATE_TEXT_EDITING,
130 BUTTON_STATE_TEXT_SELECTING,
131 BUTTON_STATE_MENU_OPEN,
132 BUTTON_STATE_WAIT_DRAG,
134 } uiHandleButtonState;
136 typedef struct uiHandleButtonData {
144 uiHandleButtonState state;
146 /* booleans (could be made into flags) */
147 bool cancel, escapecancel;
148 bool applied, applied_interactive;
153 double value, origvalue, startvalue;
154 float vec[3], origvec[3];
156 int togdual, togonly;
162 wmTimer *tooltiptimer;
166 wmTimer *autoopentimer;
168 /* text selection/editing */
169 int maxlen, selextend, selstartx;
171 /* number editing / dragging */
172 /* coords are Window/uiBlock relative (depends on the button) */
173 int draglastx, draglasty;
174 int dragstartx, dragstarty;
176 bool dragchange, draglock;
178 float dragf, dragfstart;
181 #ifdef USE_CONT_MOUSE_CORRECT
182 /* when ungrabbing buttons which are #ui_is_a_warp_but(), we may want to position them
183 * FLT_MAX signifies do-nothing, use #ui_block_to_window_fl() to get this into a usable space */
184 float ungrab_mval[2];
187 /* menu open (watch uiFreeActiveButtons) */
188 uiPopupBlockHandle *menu;
191 /* search box (watch uiFreeActiveButtons) */
193 #ifdef USE_KEYNAV_LIMIT
194 struct uiKeyNavLock searchbox_keynav_state;
198 uiButtonActivateType posttype;
200 } uiHandleButtonData;
202 typedef struct uiAfterFunc {
203 struct uiAfterFunc *next, *prev;
205 uiButHandleFunc func;
209 uiButHandleNFunc funcN;
212 uiButHandleRenameFunc rename_func;
216 uiBlockHandleFunc handle_func;
217 void *handle_func_arg;
220 uiMenuHandleFunc butm_func;
224 wmOperatorType *optype;
229 PropertyRNA *rnaprop;
231 bContextStore *context;
233 char undostr[BKE_UNDO_STR_MAX];
238 static bool ui_is_but_interactive(uiBut *but, const bool ctrl);
239 static bool ui_but_contains_pt(uiBut *but, int mx, int my);
240 static bool ui_mouse_inside_button(ARegion *ar, uiBut *but, int x, int y);
241 static uiBut *ui_but_find_mouse_over(ARegion *ar, const wmEvent *event, int x, int y);
242 static void button_activate_state(bContext *C, uiBut *but, uiHandleButtonState state);
243 static int ui_handler_region_menu(bContext *C, const wmEvent *event, void *userdata);
244 static void ui_handle_button_activate(bContext *C, ARegion *ar, uiBut *but, uiButtonActivateType type);
245 static void button_timers_tooltip_remove(bContext *C, uiBut *but);
247 /* buttons clipboard */
248 static ColorBand but_copypaste_coba = {0};
249 static CurveMapping but_copypaste_curve = {0};
250 static bool but_copypaste_curve_alive = false;
252 /* ******************** menu navigation helpers ************** */
259 static enum eSnapType ui_event_to_snap(const wmEvent *event)
261 return (event->ctrl) ? (event->shift) ? SNAP_ON_SMALL : SNAP_ON : SNAP_OFF;
264 static void ui_color_snap_hue(const enum eSnapType snap, float *r_hue)
266 const float snap_increment = (snap == SNAP_ON_SMALL) ? 24 : 12;
267 BLI_assert(snap != SNAP_OFF);
268 *r_hue = floorf(0.5f + ((*r_hue) * snap_increment)) / snap_increment;
271 /* assumes event type is MOUSEPAN */
272 void ui_pan_to_scroll(const wmEvent *event, int *type, int *val)
274 static int lastdy = 0;
275 int dy = event->prevy - event->y;
277 /* This event should be originally from event->type,
278 * converting wrong event into wheel is bad, see [#33803] */
279 BLI_assert(*type == MOUSEPAN);
281 /* sign differs, reset */
282 if ((dy > 0 && lastdy < 0) || (dy < 0 && lastdy > 0)) {
288 if (ABS(lastdy) > (int)UI_UNIT_Y) {
289 if (U.uiflag2 & USER_TRACKPAD_NATURAL)
295 *type = WHEELUPMOUSE;
297 *type = WHEELDOWNMOUSE;
304 static bool ui_but_editable(uiBut *but)
306 return ELEM5(but->type, LABEL, SEPR, ROUNDBOX, LISTBOX, PROGRESSBAR);
309 static uiBut *ui_but_prev(uiBut *but)
313 if (!ui_but_editable(but)) return but;
318 static uiBut *ui_but_next(uiBut *but)
322 if (!ui_but_editable(but)) return but;
327 static uiBut *ui_but_first(uiBlock *block)
331 but = block->buttons.first;
333 if (!ui_but_editable(but)) return but;
339 static uiBut *ui_but_last(uiBlock *block)
343 but = block->buttons.last;
345 if (!ui_but_editable(but)) return but;
351 static bool ui_is_a_warp_but(uiBut *but)
353 if (U.uiflag & USER_CONTINUOUS_MOUSE) {
354 if (ELEM6(but->type, NUM, NUMSLI, HSVCIRCLE, TRACKPREVIEW, HSVCUBE, BUT_CURVE)) {
362 static float ui_mouse_scale_warp_factor(const bool shift)
364 return shift ? 0.05f : 1.0f;
367 static void ui_mouse_scale_warp(uiHandleButtonData *data,
368 const float mx, const float my,
369 float *r_mx, float *r_my,
372 const float fac = ui_mouse_scale_warp_factor(shift);
374 /* slow down the mouse, this is fairly picky */
375 *r_mx = (data->dragstartx * (1.0f - fac) + mx * fac);
376 *r_my = (data->dragstarty * (1.0f - fac) + my * fac);
379 /* file selectors are exempt from utf-8 checks */
380 bool ui_is_but_utf8(uiBut *but)
383 const int subtype = RNA_property_subtype(but->rnaprop);
384 return !(ELEM4(subtype, PROP_FILEPATH, PROP_DIRPATH, PROP_FILENAME, PROP_BYTESTRING));
387 return !(but->flag & UI_BUT_NO_UTF8);
391 /* ********************** button apply/revert ************************/
393 static ListBase UIAfterFuncs = {NULL, NULL};
395 static void ui_apply_but_func(bContext *C, uiBut *but)
398 uiBlock *block = but->block;
400 /* these functions are postponed and only executed after all other
401 * handling is done, i.e. menus are closed, in order to avoid conflicts
402 * with these functions removing the buttons we are working with */
404 if (but->func || but->funcN || block->handle_func || but->rename_func ||
405 (but->type == BUTM && block->butm_func) || but->optype || but->rnaprop)
407 after = MEM_callocN(sizeof(uiAfterFunc), "uiAfterFunc");
409 if (but->func && ELEM(but, but->func_arg1, but->func_arg2)) {
410 /* exception, this will crash due to removed button otherwise */
411 but->func(C, but->func_arg1, but->func_arg2);
414 after->func = but->func;
416 after->func_arg1 = but->func_arg1;
417 after->func_arg2 = but->func_arg2;
419 after->funcN = but->funcN;
420 after->func_argN = MEM_dupallocN(but->func_argN);
422 after->rename_func = but->rename_func;
423 after->rename_arg1 = but->rename_arg1;
424 after->rename_orig = but->rename_orig; /* needs free! */
426 after->handle_func = block->handle_func;
427 after->handle_func_arg = block->handle_func_arg;
428 after->retval = but->retval;
430 if (but->type == BUTM) {
431 after->butm_func = block->butm_func;
432 after->butm_func_arg = block->butm_func_arg;
436 after->optype = but->optype;
437 after->opcontext = but->opcontext;
438 after->opptr = but->opptr;
440 after->rnapoin = but->rnapoin;
441 after->rnaprop = but->rnaprop;
444 after->context = CTX_store_copy(but->context);
450 BLI_addtail(&UIAfterFuncs, after);
454 /* typically call ui_apply_undo(), ui_apply_autokey() */
455 static void ui_apply_undo(uiBut *but)
459 if (but->flag & UI_BUT_UNDO) {
460 const char *str = NULL;
462 /* define which string to use for undo */
463 if (ELEM(but->type, LINK, INLINK)) str = "Add button link";
464 else if (but->type == MENU) str = but->drawstr;
465 else if (but->drawstr[0]) str = but->drawstr;
468 /* fallback, else we don't get an undo! */
469 if (str == NULL || str[0] == '\0') {
470 str = "Unknown Action";
473 /* delayed, after all other funcs run, popups are closed, etc */
474 after = MEM_callocN(sizeof(uiAfterFunc), "uiAfterFunc");
475 BLI_strncpy(after->undostr, str, sizeof(after->undostr));
476 BLI_addtail(&UIAfterFuncs, after);
480 static void ui_apply_autokey(bContext *C, uiBut *but)
482 Scene *scene = CTX_data_scene(C);
485 ui_but_anim_autokey(C, but, scene, scene->r.cfra);
487 /* make a little report about what we've done! */
489 char *buf = WM_prop_pystring_assign(C, &but->rnapoin, but->rnaprop, but->rnaindex);
491 BKE_report(CTX_wm_reports(C), RPT_PROPERTY, buf);
494 WM_event_add_notifier(C, NC_SPACE | ND_SPACE_INFO_REPORT, NULL);
499 static void ui_apply_but_funcs_after(bContext *C)
501 uiAfterFunc *afterf, after;
505 /* copy to avoid recursive calls */
506 funcs = UIAfterFuncs;
507 UIAfterFuncs.first = UIAfterFuncs.last = NULL;
509 for (afterf = funcs.first; afterf; afterf = after.next) {
510 after = *afterf; /* copy to avoid memleak on exit() */
511 BLI_freelinkN(&funcs, afterf);
514 CTX_store_set(C, after.context);
517 /* free in advance to avoid leak on exit */
518 opptr = *after.opptr,
519 MEM_freeN(after.opptr);
523 WM_operator_name_call(C, after.optype->idname, after.opcontext, (after.opptr) ? &opptr : NULL);
526 WM_operator_properties_free(&opptr);
528 if (after.rnapoin.data)
529 RNA_property_update(C, &after.rnapoin, after.rnaprop);
532 CTX_store_set(C, NULL);
533 CTX_store_free(after.context);
537 after.func(C, after.func_arg1, after.func_arg2);
539 after.funcN(C, after.func_argN, after.func_arg2);
541 MEM_freeN(after.func_argN);
543 if (after.handle_func)
544 after.handle_func(C, after.handle_func_arg, after.retval);
546 after.butm_func(C, after.butm_func_arg, after.a2);
548 if (after.rename_func)
549 after.rename_func(C, after.rename_arg1, after.rename_orig);
550 if (after.rename_orig)
551 MEM_freeN(after.rename_orig);
553 if (after.undostr[0])
554 ED_undo_push(C, after.undostr);
558 static void ui_apply_but_BUT(bContext *C, uiBut *but, uiHandleButtonData *data)
560 ui_apply_but_func(C, but);
562 data->retval = but->retval;
563 data->applied = true;
566 static void ui_apply_but_BUTM(bContext *C, uiBut *but, uiHandleButtonData *data)
568 ui_set_but_val(but, but->hardmin);
569 ui_apply_but_func(C, but);
571 data->retval = but->retval;
572 data->applied = true;
575 static void ui_apply_but_BLOCK(bContext *C, uiBut *but, uiHandleButtonData *data)
577 if (but->type == MENU)
578 ui_set_but_val(but, data->value);
581 ui_apply_but_func(C, but);
582 data->retval = but->retval;
583 data->applied = true;
586 static void ui_apply_but_TOG(bContext *C, uiBut *but, uiHandleButtonData *data)
591 value = ui_get_but_val(but);
595 w = UI_BITBUT_TEST(lvalue, but->bitnr);
596 if (w) lvalue = UI_BITBUT_CLR(lvalue, but->bitnr);
597 else lvalue = UI_BITBUT_SET(lvalue, but->bitnr);
599 ui_set_but_val(but, (double)lvalue);
600 if (but->type == ICONTOG || but->type == ICONTOGN) ui_check_but(but);
604 if (value == 0.0) push = 1;
607 if (ELEM3(but->type, TOGN, ICONTOGN, OPTIONN)) push = !push;
608 ui_set_but_val(but, (double)push);
609 if (but->type == ICONTOG || but->type == ICONTOGN) ui_check_but(but);
612 ui_apply_but_func(C, but);
614 data->retval = but->retval;
615 data->applied = true;
618 static void ui_apply_but_ROW(bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data)
622 ui_set_but_val(but, but->hardmax);
624 /* states of other row buttons */
625 for (bt = block->buttons.first; bt; bt = bt->next)
626 if (bt != but && bt->poin == but->poin && ELEM(bt->type, ROW, LISTROW))
629 ui_apply_but_func(C, but);
631 data->retval = but->retval;
632 data->applied = true;
635 static void ui_apply_but_TEX(bContext *C, uiBut *but, uiHandleButtonData *data)
640 ui_set_but_string(C, but, data->str);
643 /* give butfunc the original text too */
644 /* feature used for bone renaming, channels, etc */
645 /* afterfunc frees origstr */
646 but->rename_orig = data->origstr;
647 data->origstr = NULL;
648 ui_apply_but_func(C, but);
650 data->retval = but->retval;
651 data->applied = true;
654 static void ui_apply_but_NUM(bContext *C, uiBut *but, uiHandleButtonData *data)
657 if (ui_set_but_string(C, but, data->str)) {
658 data->value = ui_get_but_val(but);
666 ui_set_but_val(but, data->value);
669 ui_apply_but_func(C, but);
671 data->retval = but->retval;
672 data->applied = true;
675 static void ui_apply_but_VEC(bContext *C, uiBut *but, uiHandleButtonData *data)
677 ui_set_but_vectorf(but, data->vec);
679 ui_apply_but_func(C, but);
681 data->retval = but->retval;
682 data->applied = true;
685 static void ui_apply_but_COLORBAND(bContext *C, uiBut *but, uiHandleButtonData *data)
687 ui_apply_but_func(C, but);
688 data->retval = but->retval;
689 data->applied = true;
692 static void ui_apply_but_CURVE(bContext *C, uiBut *but, uiHandleButtonData *data)
694 ui_apply_but_func(C, but);
695 data->retval = but->retval;
696 data->applied = true;
699 /* ****************** drag drop code *********************** */
701 #ifdef USE_DRAG_TOGGLE
703 typedef struct uiDragToggleHandle {
707 float but_cent_start[2];
708 eButType but_type_start;
713 } uiDragToggleHandle;
715 static bool ui_drag_toggle_set_xy_xy(bContext *C, ARegion *ar, const bool is_set, const eButType but_type_start,
716 const int xy_src[2], const int xy_dst[2])
718 /* popups such as layers won't re-evaluate on redraw */
719 const bool do_check = (ar->regiontype == RGN_TYPE_TEMPORARY);
723 for (block = ar->uiblocks.first; block; block = block->next) {
726 float xy_a_block[2] = {UNPACK2(xy_src)};
727 float xy_b_block[2] = {UNPACK2(xy_dst)};
729 ui_window_to_block_fl(ar, block, &xy_a_block[0], &xy_a_block[1]);
730 ui_window_to_block_fl(ar, block, &xy_b_block[0], &xy_b_block[1]);
732 for (but = block->buttons.first; but; but = but->next) {
733 /* Note: ctrl is always true here because (at least for now) we always want to consider text control
734 * in this case, even when not embossed. */
735 if (ui_is_but_interactive(but, true)) {
736 if (BLI_rctf_isect_segment(&but->rect, xy_a_block, xy_b_block)) {
738 /* execute the button */
739 if (ui_is_but_bool(but) && but->type == but_type_start) {
741 bool is_set_but = ui_is_but_push(but);
742 BLI_assert(ui_is_but_bool(but) == true);
743 if (is_set_but != is_set) {
744 uiButExecute(C, but);
761 static void ui_drag_toggle_set(bContext *C, uiDragToggleHandle *drag_info, const wmEvent *event)
763 ARegion *ar = CTX_wm_region(C);
764 bool do_draw = false;
768 * Initialize Locking:
770 * Check if we need to initialize the lock axis by finding if the first
771 * button we mouse over is X or Y aligned, then lock the mouse to that axis after.
773 if (drag_info->is_init == false) {
774 /* first store the buttons original coords */
775 uiBut *but = ui_but_find_mouse_over(ar, event, 0, 0);
778 if (but->flag & UI_BUT_DRAG_LOCK) {
779 const float but_cent_new[2] = {BLI_rctf_cent_x(&but->rect),
780 BLI_rctf_cent_y(&but->rect)};
782 /* check if this is a different button, chances are high the button wont move about :) */
783 if (len_manhattan_v2v2(drag_info->but_cent_start, but_cent_new) > 1.0f) {
784 if (fabsf(drag_info->but_cent_start[0] - but_cent_new[0]) <
785 fabsf(drag_info->but_cent_start[1] - but_cent_new[1]))
787 drag_info->xy_lock[0] = true;
790 drag_info->xy_lock[1] = true;
792 drag_info->is_init = true;
796 drag_info->is_init = true;
800 /* done with axis locking */
803 xy[0] = (drag_info->xy_lock[0] == false) ? event->x : drag_info->xy_last[0];
804 xy[1] = (drag_info->xy_lock[1] == false) ? event->y : drag_info->xy_last[1];
807 /* touch all buttons between last mouse coord and this one */
808 do_draw = ui_drag_toggle_set_xy_xy(C, ar, drag_info->is_set, drag_info->but_type_start, drag_info->xy_last, xy);
811 ED_region_tag_redraw(ar);
814 copy_v2_v2_int(drag_info->xy_last, xy);
817 static void ui_handler_region_drag_toggle_remove(bContext *UNUSED(C), void *userdata)
819 uiDragToggleHandle *drag_info = userdata;
820 MEM_freeN(drag_info);
823 static int ui_handler_region_drag_toggle(bContext *C, const wmEvent *event, void *userdata)
825 uiDragToggleHandle *drag_info = userdata;
828 switch (event->type) {
831 if (event->val != KM_PRESS) {
838 ui_drag_toggle_set(C, drag_info, event);
844 wmWindow *win = CTX_wm_window(C);
845 ARegion *ar = CTX_wm_region(C);
846 uiBut *but = ui_but_find_mouse_over(ar, NULL, drag_info->xy_init[0], drag_info->xy_init[1]);
852 WM_event_remove_ui_handler(&win->modalhandlers,
853 ui_handler_region_drag_toggle,
854 ui_handler_region_drag_toggle_remove,
856 ui_handler_region_drag_toggle_remove(C, drag_info);
858 WM_event_add_mousemove(C);
859 return WM_UI_HANDLER_BREAK;
862 return WM_UI_HANDLER_CONTINUE;
866 static bool ui_is_but_drag_toggle(uiBut *but)
868 return ((ui_is_but_bool(but) == true) &&
869 /* menu check is importnt so the button dragged over isn't removed instantly */
870 (ui_block_is_menu(but->block) == false));
873 #endif /* USE_DRAG_TOGGLE */
876 static bool ui_but_mouse_inside_icon(uiBut *but, ARegion *ar, const wmEvent *event)
879 int x = event->x, y = event->y;
881 ui_window_to_block(ar, but->block, &x, &y);
883 BLI_rcti_rctf_copy(&rect, &but->rect);
886 /* use button size itself */
888 else if (but->drawflag & UI_BUT_ICON_LEFT) {
889 rect.xmax = rect.xmin + (BLI_rcti_size_y(&rect));
892 int delta = BLI_rcti_size_x(&rect) - BLI_rcti_size_y(&rect);
893 rect.xmin += delta / 2;
894 rect.xmax -= delta / 2;
897 return BLI_rcti_isect_pt(&rect, x, y);
900 static bool ui_but_start_drag(bContext *C, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
902 /* prevent other WM gestures to start while we try to drag */
903 WM_gestures_remove(C);
905 if (ABS(data->dragstartx - event->x) + ABS(data->dragstarty - event->y) > U.dragthreshold) {
907 button_activate_state(C, but, BUTTON_STATE_EXIT);
909 #ifdef USE_DRAG_TOGGLE
910 if (ui_is_but_bool(but)) {
911 uiDragToggleHandle *drag_info = MEM_callocN(sizeof(*drag_info), __func__);
914 /* call here because regular mouse-up event wont run,
915 * typically 'button_activate_exit()' handles this */
916 ui_apply_autokey(C, but);
918 drag_info->is_set = ui_is_but_push(but);
919 drag_info->but_cent_start[0] = BLI_rctf_cent_x(&but->rect);
920 drag_info->but_cent_start[1] = BLI_rctf_cent_y(&but->rect);
921 drag_info->but_type_start = but->type;
922 copy_v2_v2_int(drag_info->xy_init, &event->x);
923 copy_v2_v2_int(drag_info->xy_last, &event->x);
925 /* needed for toggle drag on popups */
926 ar_prev = CTX_wm_region(C);
927 CTX_wm_region_set(C, data->region);
929 WM_event_add_ui_handler(C, &data->window->modalhandlers,
930 ui_handler_region_drag_toggle,
931 ui_handler_region_drag_toggle_remove,
934 CTX_wm_region_set(C, ar_prev);
941 drag = WM_event_start_drag(C, but->icon, but->dragtype, but->dragpoin, ui_get_but_val(but));
943 WM_event_drag_image(drag, but->imb, but->imb_scale, BLI_rctf_size_x(&but->rect), BLI_rctf_size_y(&but->rect));
951 /* ********************** linklines *********************** */
953 static void ui_delete_active_linkline(uiBlock *block)
957 uiLinkLine *line, *nline;
960 for (but = block->buttons.first; but; but = but->next) {
961 if (but->type == LINK && but->link) {
962 for (line = but->link->lines.first; line; line = nline) {
965 if (line->flag & UI_SELECT) {
966 BLI_remlink(&but->link->lines, line);
968 link = line->from->link;
970 /* are there more pointers allowed? */
973 if (*(link->totlink) == 1) {
974 *(link->totlink) = 0;
975 MEM_freeN(*(link->ppoin));
976 *(link->ppoin) = NULL;
980 for (a = 0; a < (*(link->totlink)); a++) {
982 if ((*(link->ppoin))[a] != line->to->poin) {
983 (*(link->ppoin))[b] = (*(link->ppoin))[a];
987 (*(link->totlink))--;
991 *(link->poin) = NULL;
1002 static uiLinkLine *ui_is_a_link(uiBut *from, uiBut *to)
1009 for (line = link->lines.first; line; line = line->next) {
1010 if (line->from == from && line->to == to) {
1018 /* XXX BAD BAD HACK, fixme later **************** */
1019 /* Try to add an AND Controller between the sensor and the actuator logic bricks and to connect them all */
1020 static void ui_add_smart_controller(bContext *C, uiBut *from, uiBut *to)
1024 bActuator *act_to, *act_iter;
1026 bController ***sens_from_links;
1029 uiLink *link = from->link;
1031 PointerRNA props_ptr, object_ptr;
1034 sens_from_links = (bController ***)(link->ppoin);
1037 act_to = (bActuator *)(to->poin);
1039 /* (1) get the object */
1040 CTX_DATA_BEGIN (C, Object *, ob_iter, selected_editable_objects)
1042 for (sens_iter = ob_iter->sensors.first; sens_iter; sens_iter = sens_iter->next) {
1043 if (&(sens_iter->links) == sens_from_links) {
1053 /* (2) check if the sensor and the actuator are from the same object */
1054 for (act_iter = ob->actuators.first; act_iter; act_iter = (bActuator *)act_iter->next) {
1055 if (act_iter == act_to)
1059 /* only works if the sensor and the actuator are from the same object */
1060 if (!act_iter) return;
1062 /* in case the linked controller is not the active one */
1063 RNA_pointer_create((ID *)ob, &RNA_Object, ob, &object_ptr);
1065 WM_operator_properties_create(&props_ptr, "LOGIC_OT_controller_add");
1066 RNA_string_set(&props_ptr, "object", ob->id.name + 2);
1068 /* (3) add a new controller */
1069 if (WM_operator_name_call(C, "LOGIC_OT_controller_add", WM_OP_EXEC_DEFAULT, &props_ptr) & OPERATOR_FINISHED) {
1070 cont = (bController *)ob->controllers.last;
1071 cont->type = CONT_LOGIC_AND; /* Quick fix to make sure we always have an AND controller. It might be nicer to make sure the operator gives us the right one though... */
1073 /* (4) link the sensor->controller->actuator */
1074 tmp_but = MEM_callocN(sizeof(uiBut), "uiBut");
1075 uiSetButLink(tmp_but, (void **)&cont, (void ***)&(cont->links), &(cont->totlinks), from->link->tocode, (int)to->hardmin);
1076 tmp_but->hardmin = from->link->tocode;
1077 tmp_but->poin = (char *)cont;
1079 tmp_but->type = INLINK;
1080 ui_add_link(C, from, tmp_but);
1082 tmp_but->type = LINK;
1083 ui_add_link(C, tmp_but, to);
1085 /* (5) garbage collection */
1086 MEM_freeN(tmp_but->link);
1089 WM_operator_properties_free(&props_ptr);
1092 static void ui_add_link(bContext *C, uiBut *from, uiBut *to)
1094 /* in 'from' we have to add a link to 'to' */
1100 if ((line = ui_is_a_link(from, to))) {
1101 line->flag |= UI_SELECT;
1102 ui_delete_active_linkline(from->block);
1106 if (from->type == INLINK && to->type == INLINK) {
1109 else if (from->type == LINK && to->type == INLINK) {
1110 if (from->link->tocode != (int)to->hardmin) {
1111 ui_add_smart_controller(C, from, to);
1115 else if (from->type == INLINK && to->type == LINK) {
1116 if (to->link->tocode == (int)from->hardmin) {
1123 /* are there more pointers allowed? */
1125 oldppoin = *(link->ppoin);
1127 (*(link->totlink))++;
1128 *(link->ppoin) = MEM_callocN(*(link->totlink) * sizeof(void *), "new link");
1130 for (a = 0; a < (*(link->totlink)) - 1; a++) {
1131 (*(link->ppoin))[a] = oldppoin[a];
1133 (*(link->ppoin))[a] = to->poin;
1135 if (oldppoin) MEM_freeN(oldppoin);
1138 *(link->poin) = to->poin;
1144 static void ui_apply_but_LINK(bContext *C, uiBut *but, uiHandleButtonData *data)
1146 ARegion *ar = CTX_wm_region(C);
1149 for (bt = but->block->buttons.first; bt; bt = bt->next) {
1150 if (ui_mouse_inside_button(ar, bt, but->linkto[0] + ar->winrct.xmin, but->linkto[1] + ar->winrct.ymin) )
1153 if (bt && bt != but) {
1154 if (!ELEM(bt->type, LINK, INLINK) || !ELEM(but->type, LINK, INLINK))
1157 if (but->type == LINK) ui_add_link(C, but, bt);
1158 else ui_add_link(C, bt, but);
1160 ui_apply_but_func(C, but);
1161 data->retval = but->retval;
1163 data->applied = true;
1166 static void ui_apply_but_IMAGE(bContext *C, uiBut *but, uiHandleButtonData *data)
1168 ui_apply_but_func(C, but);
1169 data->retval = but->retval;
1170 data->applied = true;
1173 static void ui_apply_but_HISTOGRAM(bContext *C, uiBut *but, uiHandleButtonData *data)
1175 ui_apply_but_func(C, but);
1176 data->retval = but->retval;
1177 data->applied = true;
1180 static void ui_apply_but_WAVEFORM(bContext *C, uiBut *but, uiHandleButtonData *data)
1182 ui_apply_but_func(C, but);
1183 data->retval = but->retval;
1184 data->applied = true;
1187 static void ui_apply_but_TRACKPREVIEW(bContext *C, uiBut *but, uiHandleButtonData *data)
1189 ui_apply_but_func(C, but);
1190 data->retval = but->retval;
1191 data->applied = true;
1195 static void ui_apply_button(bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const bool interactive)
1200 ColorBand *editcoba;
1201 CurveMapping *editcumap;
1205 /* if we cancel and have not applied yet, there is nothing to do,
1206 * otherwise we have to restore the original value again */
1211 if (data->str) MEM_freeN(data->str);
1212 data->str = data->origstr;
1213 data->origstr = NULL;
1214 data->value = data->origvalue;
1215 data->origvalue = 0.0;
1216 copy_v3_v3(data->vec, data->origvec);
1217 data->origvec[0] = data->origvec[1] = data->origvec[2] = 0.0f;
1220 /* we avoid applying interactive edits a second time
1221 * at the end with the appliedinteractive flag */
1223 data->applied_interactive = true;
1225 else if (data->applied_interactive) {
1230 /* ensures we are writing actual values */
1231 editstr = but->editstr;
1232 editval = but->editval;
1233 editvec = but->editvec;
1234 editcoba = but->editcoba;
1235 editcumap = but->editcumap;
1236 but->editstr = NULL;
1237 but->editval = NULL;
1238 but->editvec = NULL;
1239 but->editcoba = NULL;
1240 but->editcumap = NULL;
1242 /* handle different types */
1243 switch (but->type) {
1245 ui_apply_but_BUT(C, but, data);
1248 case SEARCH_MENU_UNLINK:
1250 ui_apply_but_TEX(C, but, data);
1259 ui_apply_but_TOG(C, but, data);
1263 ui_apply_but_ROW(C, block, but, data);
1268 ui_apply_but_NUM(C, but, data);
1273 ui_apply_but_BLOCK(C, but, data);
1277 ui_apply_but_VEC(C, but, data);
1279 ui_apply_but_BLOCK(C, but, data);
1282 ui_apply_but_BUTM(C, but, data);
1287 ui_apply_but_VEC(C, but, data);
1290 ui_apply_but_COLORBAND(C, but, data);
1293 ui_apply_but_CURVE(C, but, data);
1297 ui_apply_but_BUT(C, but, data);
1301 ui_apply_but_LINK(C, but, data);
1304 ui_apply_but_IMAGE(C, but, data);
1307 ui_apply_but_HISTOGRAM(C, but, data);
1310 ui_apply_but_WAVEFORM(C, but, data);
1313 ui_apply_but_TRACKPREVIEW(C, but, data);
1319 but->editstr = editstr;
1320 but->editval = editval;
1321 but->editvec = editvec;
1322 but->editcoba = editcoba;
1323 but->editcumap = editcumap;
1326 /* ******************* drop event ******************** */
1328 /* only call if event type is EVT_DROP */
1329 static void ui_but_drop(bContext *C, const wmEvent *event, uiBut *but, uiHandleButtonData *data)
1332 ListBase *drags = event->customdata; /* drop event type has listbase customdata by default */
1334 for (wmd = drags->first; wmd; wmd = wmd->next) {
1335 if (wmd->type == WM_DRAG_ID) {
1336 /* align these types with UI_but_active_drop_name */
1337 if (ELEM3(but->type, TEX, SEARCH_MENU, SEARCH_MENU_UNLINK)) {
1338 ID *id = (ID *)wmd->poin;
1340 button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING);
1341 BLI_strncpy(data->str, id->name + 2, data->maxlen);
1343 if (ELEM(but->type, SEARCH_MENU, SEARCH_MENU_UNLINK)) {
1344 but->changed = true;
1345 ui_searchbox_update(C, data->searchbox, but, true);
1348 button_activate_state(C, but, BUTTON_STATE_EXIT);
1355 /* ******************* copy and paste ******************** */
1357 /* c = copy, v = paste */
1358 static void ui_but_copy_paste(bContext *C, uiBut *but, uiHandleButtonData *data, char mode)
1360 char buf[UI_MAX_DRAW_STR + 1] = {0};
1362 if (mode == 'v' && but->lock == TRUE) {
1367 /* extract first line from clipboard in case of multi-line copies */
1368 char *p, *pbuf = WM_clipboard_text_get(0);
1372 while (*p && *p != '\r' && *p != '\n' && i < UI_MAX_DRAW_STR) {
1382 if (ELEM(but->type, NUM, NUMSLI)) {
1384 if (but->poin == NULL && but->rnapoin.data == NULL) {
1387 else if (mode == 'c') {
1388 /* Get many decimal places, then strip trailing zeros.
1389 * note: too high values start to give strange results (6 or so is ok) */
1390 ui_get_but_string_ex(but, buf, sizeof(buf), 6);
1391 BLI_str_rstrip_float_zero(buf, '\0');
1393 WM_clipboard_text_set(buf, 0);
1398 if (ui_set_but_string_eval_num(C, but, buf, &val)) {
1399 button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
1401 ui_set_but_string(C, but, buf);
1402 button_activate_state(C, but, BUTTON_STATE_EXIT);
1408 else if (but->type == BUT_NORMAL) {
1411 if (but->poin == NULL && but->rnapoin.data == NULL) {
1414 else if (mode == 'c') {
1415 ui_get_but_vectorf(but, xyz);
1416 BLI_snprintf(buf, sizeof(buf), "[%f, %f, %f]", xyz[0], xyz[1], xyz[2]);
1417 WM_clipboard_text_set(buf, 0);
1420 if (sscanf(buf, "[%f, %f, %f]", &xyz[0], &xyz[1], &xyz[2]) == 3) {
1421 if (normalize_v3(xyz) == 0.0f) {
1422 /* better set Z up then have a zero vector */
1425 button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
1426 ui_set_but_vectorf(but, xyz);
1427 button_activate_state(C, but, BUTTON_STATE_EXIT);
1434 else if (but->type == COLOR) {
1437 if (but->poin == NULL && but->rnapoin.data == NULL) {
1440 else if (mode == 'c') {
1441 if (RNA_property_array_length(&but->rnapoin, but->rnaprop) == 4)
1442 rgba[3] = RNA_property_float_get_index(&but->rnapoin, but->rnaprop, 3);
1446 ui_get_but_vectorf(but, rgba);
1447 /* convert to linear color to do compatible copy between gamma and non-gamma */
1448 if (but->rnaprop && RNA_property_subtype(but->rnaprop) == PROP_COLOR_GAMMA)
1449 srgb_to_linearrgb_v3_v3(rgba, rgba);
1451 BLI_snprintf(buf, sizeof(buf), "[%f, %f, %f, %f]", rgba[0], rgba[1], rgba[2], rgba[3]);
1452 WM_clipboard_text_set(buf, 0);
1456 if (sscanf(buf, "[%f, %f, %f, %f]", &rgba[0], &rgba[1], &rgba[2], &rgba[3]) == 4) {
1457 /* assume linear colors in buffer */
1458 if (but->rnaprop && RNA_property_subtype(but->rnaprop) == PROP_COLOR_GAMMA)
1459 linearrgb_to_srgb_v3_v3(rgba, rgba);
1461 button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
1462 ui_set_but_vectorf(but, rgba);
1463 if (RNA_property_array_length(&but->rnapoin, but->rnaprop) == 4)
1464 RNA_property_float_set_index(&but->rnapoin, but->rnaprop, 3, rgba[3]);
1466 button_activate_state(C, but, BUTTON_STATE_EXIT);
1471 /* text/string and ID data */
1472 else if (ELEM3(but->type, TEX, SEARCH_MENU, SEARCH_MENU_UNLINK)) {
1473 uiHandleButtonData *active_data = but->active;
1475 if (but->poin == NULL && but->rnapoin.data == NULL) {
1478 else if (mode == 'c') {
1479 button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING);
1480 BLI_strncpy(buf, active_data->str, UI_MAX_DRAW_STR);
1481 WM_clipboard_text_set(active_data->str, 0);
1482 active_data->cancel = true;
1483 button_activate_state(C, but, BUTTON_STATE_EXIT);
1486 button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING);
1488 if (ui_is_but_utf8(but)) BLI_strncpy_utf8(active_data->str, buf, active_data->maxlen);
1489 else BLI_strncpy(active_data->str, buf, active_data->maxlen);
1491 if (ELEM(but->type, SEARCH_MENU, SEARCH_MENU_UNLINK)) {
1492 /* else uiSearchboxData.active member is not updated [#26856] */
1493 but->changed = true;
1494 ui_searchbox_update(C, data->searchbox, but, true);
1496 button_activate_state(C, but, BUTTON_STATE_EXIT);
1499 /* colorband (not supported by system clipboard) */
1500 else if (but->type == BUT_COLORBAND) {
1502 if (but->poin == NULL)
1505 memcpy(&but_copypaste_coba, but->poin, sizeof(ColorBand));
1508 if (but_copypaste_coba.tot == 0)
1512 but->poin = MEM_callocN(sizeof(ColorBand), "colorband");
1514 button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
1515 memcpy(data->coba, &but_copypaste_coba, sizeof(ColorBand));
1516 button_activate_state(C, but, BUTTON_STATE_EXIT);
1519 else if (but->type == BUT_CURVE) {
1521 if (but->poin == NULL)
1524 but_copypaste_curve_alive = true;
1525 curvemapping_free_data(&but_copypaste_curve);
1526 curvemapping_copy_data(&but_copypaste_curve, (CurveMapping *) but->poin);
1529 if (!but_copypaste_curve_alive)
1533 but->poin = MEM_callocN(sizeof(CurveMapping), "curvemapping");
1535 button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
1536 curvemapping_free_data((CurveMapping *) but->poin);
1537 curvemapping_copy_data((CurveMapping *) but->poin, &but_copypaste_curve);
1538 button_activate_state(C, but, BUTTON_STATE_EXIT);
1541 /* operator button (any type) */
1542 else if (but->optype) {
1546 opptr = uiButGetOperatorPtrRNA(but); /* allocated when needed, the button owns it */
1548 str = WM_operator_pystring_ex(C, NULL, false, but->optype, opptr);
1550 WM_clipboard_text_set(str, 0);
1557 /* ************************ password text ******************************
1559 * Functions to convert password strings that should not be displayed
1560 * to asterisk representation (e.g. mysecretpasswd -> *************)
1562 * It converts every UTF-8 character to an asterisk, and also remaps
1563 * the cursor position and selection start/end.
1565 * Note: remaping is used, because password could contain UTF-8 characters.
1569 static int ui_text_position_from_hidden(uiBut *but, int pos)
1574 for (i = 0, strpos = but->drawstr; i < pos; i++)
1575 strpos = BLI_str_find_next_char_utf8(strpos, NULL);
1577 return (strpos - but->drawstr);
1580 static int ui_text_position_to_hidden(uiBut *but, int pos)
1582 return BLI_strnlen_utf8(but->drawstr, pos);
1585 void ui_button_text_password_hide(char password_str[UI_MAX_DRAW_STR], uiBut *but, int restore)
1587 if (!(but->rnaprop && RNA_property_subtype(but->rnaprop) == PROP_PASSWORD))
1591 /* restore original string */
1592 BLI_strncpy(but->drawstr, password_str, UI_MAX_DRAW_STR);
1594 /* remap cursor positions */
1595 if (but->pos >= 0) {
1596 but->pos = ui_text_position_from_hidden(but, but->pos);
1597 but->selsta = ui_text_position_from_hidden(but, but->selsta);
1598 but->selend = ui_text_position_from_hidden(but, but->selend);
1602 /* convert text to hidden test using asterisks (e.g. pass -> ****) */
1603 int i, len = BLI_strlen_utf8(but->drawstr);
1605 /* remap cursor positions */
1606 if (but->pos >= 0) {
1607 but->pos = ui_text_position_to_hidden(but, but->pos);
1608 but->selsta = ui_text_position_to_hidden(but, but->selsta);
1609 but->selend = ui_text_position_to_hidden(but, but->selend);
1612 /* save original string */
1613 BLI_strncpy(password_str, but->drawstr, UI_MAX_DRAW_STR);
1615 for (i = 0; i < len; i++)
1616 but->drawstr[i] = '*';
1617 but->drawstr[i] = '\0';
1622 /* ************* in-button text selection/editing ************* */
1625 static bool ui_textedit_delete_selection(uiBut *but, uiHandleButtonData *data)
1627 char *str = data->str;
1628 const int len = strlen(str);
1629 bool change = false;
1630 if (but->selsta != but->selend && len) {
1631 memmove(str + but->selsta, str + but->selend, (len - but->selend) + 1);
1635 but->pos = but->selend = but->selsta;
1639 /* note, but->block->aspect is used here, when drawing button style is getting scaled too */
1640 static void ui_textedit_set_cursor_pos(uiBut *but, uiHandleButtonData *data, const float x)
1642 uiStyle *style = UI_GetStyle(); // XXX pass on as arg
1643 uiFontStyle *fstyle = &style->widget;
1644 float startx = but->rect.xmin;
1645 char *origstr, password_str[UI_MAX_DRAW_STR];
1647 uiStyleFontSet(fstyle);
1649 if (fstyle->kerning == 1) /* for BLF_width */
1650 BLF_enable(fstyle->uifont_id, BLF_KERNING_DEFAULT);
1652 ui_button_text_password_hide(password_str, but, FALSE);
1654 origstr = MEM_mallocN(sizeof(char) * data->maxlen, "ui_textedit origstr");
1656 BLI_strncpy(origstr, but->drawstr, data->maxlen);
1658 /* XXX solve generic, see: #widget_draw_text_icon */
1659 if (but->type == NUM || but->type == NUMSLI) {
1660 startx += (int)(0.5f * (BLI_rctf_size_y(&but->rect)));
1662 else if (ELEM3(but->type, TEX, SEARCH_MENU, SEARCH_MENU_UNLINK)) {
1663 if (but->flag & UI_HAS_ICON) {
1664 startx += UI_DPI_ICON_SIZE;
1666 /* but this extra .05 makes clicks inbetween characters feel nicer */
1667 startx += ((UI_TEXT_MARGIN_X + 0.05f) * U.widget_unit);
1670 /* mouse dragged outside the widget to the left */
1674 origstr[but->ofs] = '\0';
1677 if (BLI_str_cursor_step_prev_utf8(origstr, but->ofs, &i)) {
1678 /* 0.25 == scale factor for less sensitivity */
1679 if (BLF_width(fstyle->uifont_id, origstr + i) > (startx - x) * 0.25f) {
1684 break; /* unlikely but possible */
1688 but->pos = but->ofs;
1690 /* mouse inside the widget, mouse coords mapped in widget space */
1691 else { /* (x >= startx) */
1694 /* keep track of previous distance from the cursor to the char */
1695 float cdist, cdist_prev = 0.0f;
1698 but->pos = pos_prev = strlen(origstr) - but->ofs;
1701 cdist = startx + BLF_width(fstyle->uifont_id, origstr + but->ofs);
1703 /* check if position is found */
1705 /* check is previous location was in fact closer */
1706 if (((float)x - cdist) > (cdist_prev - (float)x)) {
1707 but->pos = pos_prev;
1712 pos_prev = but->pos;
1713 /* done with tricky distance checks */
1716 if (but->pos <= 0) break;
1717 if (BLI_str_cursor_step_prev_utf8(origstr, but->ofs, &pos_i)) {
1719 origstr[but->pos + but->ofs] = 0;
1722 break; /* unlikely but possible */
1725 but->pos += but->ofs;
1726 if (but->pos < 0) but->pos = 0;
1729 if (fstyle->kerning == 1)
1730 BLF_disable(fstyle->uifont_id, BLF_KERNING_DEFAULT);
1732 ui_button_text_password_hide(password_str, but, TRUE);
1737 static void ui_textedit_set_cursor_select(uiBut *but, uiHandleButtonData *data, const float x)
1739 if (x > data->selstartx) data->selextend = EXTEND_RIGHT;
1740 else if (x < data->selstartx) data->selextend = EXTEND_LEFT;
1742 ui_textedit_set_cursor_pos(but, data, x);
1744 if (data->selextend == EXTEND_RIGHT) but->selend = but->pos;
1745 else if (data->selextend == EXTEND_LEFT) but->selsta = but->pos;
1750 /* this is used for both utf8 and ascii, its meant to be used for single keys,
1751 * notice the buffer is either copied or not, so its not suitable for pasting in
1753 static bool ui_textedit_type_buf(uiBut *but, uiHandleButtonData *data,
1754 const char *utf8_buf, int utf8_buf_len)
1758 bool changed = false;
1763 if (len - (but->selend - but->selsta) + 1 <= data->maxlen) {
1764 int step = utf8_buf_len;
1766 /* type over the current selection */
1767 if ((but->selend - but->selsta) > 0) {
1768 changed = ui_textedit_delete_selection(but, data);
1772 if (len + step < data->maxlen) {
1773 memmove(&str[but->pos + step], &str[but->pos], (len + 1) - but->pos);
1774 memcpy(&str[but->pos], utf8_buf, step * sizeof(char));
1783 static bool ui_textedit_type_ascii(uiBut *but, uiHandleButtonData *data, char ascii)
1785 char buf[2] = {ascii, '\0'};
1787 if (ui_is_but_utf8(but) && (BLI_str_utf8_size(buf) == -1)) {
1788 printf("%s: entering invalid ascii char into an ascii key (%d)\n",
1789 __func__, (int)(unsigned char)ascii);
1794 /* in some cases we want to allow invalid utf8 chars */
1795 return ui_textedit_type_buf(but, data, buf, 1);
1798 static void ui_textedit_move(uiBut *but, uiHandleButtonData *data, strCursorJumpDirection direction,
1799 const bool select, strCursorJumpType jump)
1801 const char *str = data->str;
1802 const int len = strlen(str);
1803 const int pos_prev = but->pos;
1804 const int has_sel = (but->selend - but->selsta) > 0;
1808 /* special case, quit selection and set cursor */
1809 if (has_sel && !select) {
1810 if (jump == STRCUR_JUMP_ALL) {
1811 but->selsta = but->selend = but->pos = direction ? len : 0;
1815 but->selsta = but->pos = but->selend;
1818 but->pos = but->selend = but->selsta;
1821 data->selextend = 0;
1824 int pos_i = but->pos;
1825 BLI_str_cursor_step_utf8(str, len, &pos_i, direction, jump, true);
1829 /* existing selection */
1832 if (data->selextend == 0) {
1833 data->selextend = EXTEND_RIGHT;
1837 if (data->selextend == EXTEND_RIGHT) {
1838 but->selend = but->pos;
1841 but->selsta = but->pos;
1845 if (data->selextend == EXTEND_LEFT) {
1846 but->selsta = but->pos;
1849 but->selend = but->pos;
1853 if (but->selend < but->selsta) {
1854 SWAP(short, but->selsta, but->selend);
1855 data->selextend = (data->selextend == EXTEND_RIGHT) ? EXTEND_LEFT : EXTEND_RIGHT;
1858 } /* new selection */
1861 data->selextend = EXTEND_RIGHT;
1862 but->selend = but->pos;
1863 but->selsta = pos_prev;
1866 data->selextend = EXTEND_LEFT;
1867 but->selend = pos_prev;
1868 but->selsta = but->pos;
1875 static bool ui_textedit_delete(uiBut *but, uiHandleButtonData *data, int direction, strCursorJumpType jump)
1877 char *str = data->str;
1878 const int len = strlen(str);
1880 bool changed = false;
1882 if (jump == STRCUR_JUMP_ALL) {
1883 if (len) changed = true;
1887 else if (direction) { /* delete */
1888 if ((but->selend - but->selsta) > 0) {
1889 changed = ui_textedit_delete_selection(but, data);
1891 else if (but->pos >= 0 && but->pos < len) {
1894 BLI_str_cursor_step_utf8(str, len, &pos, direction, jump, true);
1895 step = pos - but->pos;
1896 memmove(&str[but->pos], &str[but->pos + step], (len + 1) - but->pos);
1900 else { /* backspace */
1902 if ((but->selend - but->selsta) > 0) {
1903 changed = ui_textedit_delete_selection(but, data);
1905 else if (but->pos > 0) {
1909 BLI_str_cursor_step_utf8(str, len, &pos, direction, jump, true);
1910 step = but->pos - pos;
1911 memmove(&str[but->pos - step], &str[but->pos], (len + 1) - but->pos);
1921 static bool ui_textedit_autocomplete(bContext *C, uiBut *but, uiHandleButtonData *data)
1928 if (data->searchbox)
1929 change = ui_searchbox_autocomplete(C, data->searchbox, but, data->str);
1931 change = but->autocomplete_func(C, str, but->autofunc_arg);
1933 but->pos = strlen(str);
1934 but->selsta = but->selend = but->pos;
1939 /* mode for ui_textedit_copypaste() */
1941 UI_TEXTEDIT_PASTE = 1,
1946 static bool ui_textedit_copypaste(uiBut *but, uiHandleButtonData *data, const int mode)
1948 char *str, *p, *pbuf;
1950 bool changed = false;
1951 int str_len, buf_len;
1954 str_len = strlen(str);
1957 if (mode == UI_TEXTEDIT_PASTE) {
1958 /* TODO, ensure UTF8 ui_is_but_utf8() - campbell */
1959 /* extract the first line from the clipboard */
1960 p = pbuf = WM_clipboard_text_get(0);
1963 char buf[UI_MAX_DRAW_STR] = {0};
1966 while (*p && *p != '\r' && *p != '\n' && buf_len < UI_MAX_DRAW_STR - 1) {
1967 buf[buf_len++] = *p;
1972 /* paste over the current selection */
1973 if ((but->selend - but->selsta) > 0) {
1974 ui_textedit_delete_selection(but, data);
1975 str_len = strlen(str);
1978 for (y = 0; y < buf_len; y++) {
1979 /* add contents of buffer */
1980 if (str_len + 1 < data->maxlen) {
1981 for (x = data->maxlen; x > but->pos; x--)
1982 str[x] = str[x - 1];
1983 str[but->pos] = buf[y];
1986 str[str_len] = '\0';
1998 else if (ELEM(mode, UI_TEXTEDIT_COPY, UI_TEXTEDIT_CUT)) {
1999 /* copy the contents to the copypaste buffer */
2000 int sellen = but->selend - but->selsta;
2001 char *buf = MEM_mallocN(sizeof(char) * (sellen + 1), "ui_textedit_copypaste");
2003 BLI_strncpy(buf, str + but->selsta, sellen + 1);
2004 WM_clipboard_text_set(buf, 0);
2007 /* for cut only, delete the selection afterwards */
2008 if (mode == UI_TEXTEDIT_CUT) {
2009 if ((but->selend - but->selsta) > 0) {
2010 changed = ui_textedit_delete_selection(but, data);
2018 static void ui_textedit_begin(bContext *C, uiBut *but, uiHandleButtonData *data)
2023 MEM_freeN(data->str);
2027 /* retrieve string */
2028 data->maxlen = ui_get_but_string_max_length(but);
2029 data->str = MEM_callocN(sizeof(char) * data->maxlen + 1, "textedit str");
2030 ui_get_but_string(but, data->str, data->maxlen);
2032 if (ui_is_but_float(but) && !ui_is_but_unit(but)) {
2033 BLI_str_rstrip_float_zero(data->str, '\0');
2036 if (ELEM(but->type, NUM, NUMSLI)) {
2037 ui_convert_to_unit_alt_name(but, data->str, data->maxlen);
2040 /* won't change from now on */
2041 len = strlen(data->str);
2043 data->origstr = BLI_strdupn(data->str, len);
2044 data->selextend = 0;
2045 data->selstartx = 0;
2047 /* set cursor pos to the end of the text */
2048 but->editstr = data->str;
2053 /* optional searchbox */
2054 if (ELEM(but->type, SEARCH_MENU, SEARCH_MENU_UNLINK)) {
2055 data->searchbox = ui_searchbox_create(C, data->region, but);
2056 ui_searchbox_update(C, data->searchbox, but, true); /* true = reset */
2059 /* reset alert flag (avoid confusion, will refresh on exit) */
2060 but->flag &= ~UI_BUT_REDALERT;
2064 WM_cursor_modal_set(CTX_wm_window(C), BC_TEXTEDITCURSOR);
2067 static void ui_textedit_end(bContext *C, uiBut *but, uiHandleButtonData *data)
2070 if (ui_is_but_utf8(but)) {
2071 int strip = BLI_utf8_invalid_strip(but->editstr, strlen(but->editstr));
2072 /* not a file?, strip non utf-8 chars */
2074 /* wont happen often so isn't that annoying to keep it here for a while */
2075 printf("%s: invalid utf8 - stripped chars %d\n", __func__, strip);
2079 if (data->searchbox) {
2080 if (data->cancel == false) {
2081 if ((ui_searchbox_apply(but, data->searchbox) == false) &&
2082 (ui_searchbox_find_index(data->searchbox, but->editstr) == -1))
2084 data->cancel = true;
2088 ui_searchbox_free(C, data->searchbox);
2089 data->searchbox = NULL;
2092 but->editstr = NULL;
2096 WM_cursor_modal_restore(CTX_wm_window(C));
2099 static void ui_textedit_next_but(uiBlock *block, uiBut *actbut, uiHandleButtonData *data)
2103 /* label and roundbox can overlap real buttons (backdrops...) */
2104 if (ELEM4(actbut->type, LABEL, SEPR, ROUNDBOX, LISTBOX))
2107 for (but = actbut->next; but; but = but->next) {
2108 if (ELEM5(but->type, TEX, NUM, NUMSLI, SEARCH_MENU, SEARCH_MENU_UNLINK)) {
2109 if (!(but->flag & UI_BUT_DISABLED)) {
2110 data->postbut = but;
2111 data->posttype = BUTTON_ACTIVATE_TEXT_EDITING;
2116 for (but = block->buttons.first; but != actbut; but = but->next) {
2117 if (ELEM5(but->type, TEX, NUM, NUMSLI, SEARCH_MENU, SEARCH_MENU_UNLINK)) {
2118 if (!(but->flag & UI_BUT_DISABLED)) {
2119 data->postbut = but;
2120 data->posttype = BUTTON_ACTIVATE_TEXT_EDITING;
2127 static void ui_textedit_prev_but(uiBlock *block, uiBut *actbut, uiHandleButtonData *data)
2131 /* label and roundbox can overlap real buttons (backdrops...) */
2132 if (ELEM4(actbut->type, LABEL, SEPR, ROUNDBOX, LISTBOX))
2135 for (but = actbut->prev; but; but = but->prev) {
2136 if (ELEM5(but->type, TEX, NUM, NUMSLI, SEARCH_MENU, SEARCH_MENU_UNLINK)) {
2137 if (!(but->flag & UI_BUT_DISABLED)) {
2138 data->postbut = but;
2139 data->posttype = BUTTON_ACTIVATE_TEXT_EDITING;
2144 for (but = block->buttons.last; but != actbut; but = but->prev) {
2145 if (ELEM5(but->type, TEX, NUM, NUMSLI, SEARCH_MENU, SEARCH_MENU_UNLINK)) {
2146 if (!(but->flag & UI_BUT_DISABLED)) {
2147 data->postbut = but;
2148 data->posttype = BUTTON_ACTIVATE_TEXT_EDITING;
2156 static void ui_do_but_textedit(bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
2158 int mx, my, retval = WM_UI_HANDLER_CONTINUE;
2159 bool changed = false, inbox = false, update = false;
2161 switch (event->type) {
2164 if (data->searchbox) {
2165 #ifdef USE_KEYNAV_LIMIT
2166 if ((event->type == MOUSEMOVE) && ui_mouse_motion_keynav_test(&data->searchbox_keynav_state, event)) {
2170 ui_searchbox_event(C, data->searchbox, but, event);
2173 ui_searchbox_event(C, data->searchbox, but, event);
2180 if (event->val == KM_PRESS) {
2181 data->cancel = true;
2182 data->escapecancel = true;
2183 button_activate_state(C, but, BUTTON_STATE_EXIT);
2184 retval = WM_UI_HANDLER_BREAK;
2189 bool had_selection = but->selsta != but->selend;
2191 /* exit on LMB only on RELEASE for searchbox, to mimic other popups, and allow multiple menu levels */
2192 if (data->searchbox)
2193 inbox = ui_searchbox_inside(data->searchbox, event->x, event->y);
2195 /* for double click: we do a press again for when you first click on button (selects all text, no cursor pos) */
2196 if (event->val == KM_PRESS || event->val == KM_DBL_CLICK) {
2199 ui_window_to_block(data->region, block, &mx, &my);
2201 if (ui_but_contains_pt(but, mx, my)) {
2202 ui_textedit_set_cursor_pos(but, data, mx);
2203 but->selsta = but->selend = but->pos;
2204 data->selstartx = mx;
2206 button_activate_state(C, but, BUTTON_STATE_TEXT_SELECTING);
2207 retval = WM_UI_HANDLER_BREAK;
2209 else if (inbox == false) {
2210 /* if searchbox, click outside will cancel */
2211 if (data->searchbox)
2212 data->cancel = data->escapecancel = true;
2213 button_activate_state(C, but, BUTTON_STATE_EXIT);
2214 retval = WM_UI_HANDLER_BREAK;
2218 /* only select a word in button if there was no selection before */
2219 if (event->val == KM_DBL_CLICK && had_selection == false) {
2220 ui_textedit_move(but, data, STRCUR_DIR_PREV, false, STRCUR_JUMP_DELIM);
2221 ui_textedit_move(but, data, STRCUR_DIR_NEXT, true, STRCUR_JUMP_DELIM);
2222 retval = WM_UI_HANDLER_BREAK;
2226 /* if we allow activation on key press, it gives problems launching operators [#35713] */
2227 if (event->val == KM_RELEASE) {
2228 button_activate_state(C, but, BUTTON_STATE_EXIT);
2229 retval = WM_UI_HANDLER_BREAK;
2236 if (event->val == KM_PRESS) {
2237 switch (event->type) {
2241 if (event->ctrl || event->oskey) {
2242 if (event->type == VKEY)
2243 changed = ui_textedit_copypaste(but, data, UI_TEXTEDIT_PASTE);
2244 else if (event->type == CKEY)
2245 changed = ui_textedit_copypaste(but, data, UI_TEXTEDIT_COPY);
2246 else if (event->type == XKEY)
2247 changed = ui_textedit_copypaste(but, data, UI_TEXTEDIT_CUT);
2249 retval = WM_UI_HANDLER_BREAK;
2253 ui_textedit_move(but, data, STRCUR_DIR_NEXT,
2254 event->shift != 0, event->ctrl ? STRCUR_JUMP_DELIM : STRCUR_JUMP_NONE);
2255 retval = WM_UI_HANDLER_BREAK;
2258 ui_textedit_move(but, data, STRCUR_DIR_PREV,
2259 event->shift != 0, event->ctrl ? STRCUR_JUMP_DELIM : STRCUR_JUMP_NONE);
2260 retval = WM_UI_HANDLER_BREAK;
2262 case WHEELDOWNMOUSE:
2264 if (data->searchbox) {
2265 #ifdef USE_KEYNAV_LIMIT
2266 ui_mouse_motion_keynav_init(&data->searchbox_keynav_state, event);
2268 ui_searchbox_event(C, data->searchbox, but, event);
2273 ui_textedit_move(but, data, STRCUR_DIR_NEXT,
2274 event->shift != 0, STRCUR_JUMP_ALL);
2275 retval = WM_UI_HANDLER_BREAK;
2279 if (data->searchbox) {
2280 #ifdef USE_KEYNAV_LIMIT
2281 ui_mouse_motion_keynav_init(&data->searchbox_keynav_state, event);
2283 ui_searchbox_event(C, data->searchbox, but, event);
2288 ui_textedit_move(but, data, STRCUR_DIR_PREV,
2289 event->shift != 0, STRCUR_JUMP_ALL);
2290 retval = WM_UI_HANDLER_BREAK;
2294 button_activate_state(C, but, BUTTON_STATE_EXIT);
2295 retval = WM_UI_HANDLER_BREAK;
2298 changed = ui_textedit_delete(but, data, 1,
2299 event->ctrl ? STRCUR_JUMP_DELIM : STRCUR_JUMP_NONE);
2300 retval = WM_UI_HANDLER_BREAK;
2304 changed = ui_textedit_delete(but, data, 0,
2305 event->ctrl ? STRCUR_JUMP_DELIM : STRCUR_JUMP_NONE);
2306 retval = WM_UI_HANDLER_BREAK;
2311 /* Ctrl + A: Select all */
2312 #if defined(__APPLE__)
2313 /* OSX uses cmd-a systemwide, so add it */
2314 if ((event->oskey && !(event->alt || event->shift || event->ctrl)) ||
2315 (event->ctrl && !(event->alt || event->shift || event->oskey)))
2317 if (event->ctrl && !(event->alt || event->shift || event->oskey))
2320 ui_textedit_move(but, data, STRCUR_DIR_PREV,
2321 false, STRCUR_JUMP_ALL);
2322 ui_textedit_move(but, data, STRCUR_DIR_NEXT,
2323 true, STRCUR_JUMP_ALL);
2324 retval = WM_UI_HANDLER_BREAK;
2329 /* there is a key conflict here, we can't tab with autocomplete */
2330 if (but->autocomplete_func || data->searchbox) {
2331 changed = ui_textedit_autocomplete(C, but, data);
2332 update = true; /* do live update for tab key */
2334 /* the hotkey here is not well defined, was G.qual so we check all */
2335 else if (event->shift || event->ctrl || event->alt || event->oskey) {
2336 ui_textedit_prev_but(block, but, data);
2337 button_activate_state(C, but, BUTTON_STATE_EXIT);
2340 ui_textedit_next_but(block, but, data);
2341 button_activate_state(C, but, BUTTON_STATE_EXIT);
2343 retval = WM_UI_HANDLER_BREAK;
2347 if ((event->ascii || event->utf8_buf[0]) && (retval == WM_UI_HANDLER_CONTINUE)) {
2348 char ascii = event->ascii;
2349 const char *utf8_buf = event->utf8_buf;
2351 /* exception that's useful for number buttons, some keyboard
2352 * numpads have a comma instead of a period */
2353 if (ELEM(but->type, NUM, NUMSLI)) { /* could use data->min*/
2354 if (event->type == PADPERIOD && ascii == ',') {
2356 utf8_buf = NULL; /* force ascii fallback */
2360 if (utf8_buf && utf8_buf[0]) {
2361 int utf8_buf_len = BLI_str_utf8_size(utf8_buf);
2362 /* keep this printf until utf8 is well tested */
2363 if (utf8_buf_len != 1) {
2364 printf("%s: utf8 char '%.*s'\n", __func__, utf8_buf_len, utf8_buf);
2367 // strcpy(utf8_buf, "12345");
2368 changed = ui_textedit_type_buf(but, data, event->utf8_buf, utf8_buf_len);
2371 changed = ui_textedit_type_ascii(but, data, ascii);
2374 retval = WM_UI_HANDLER_BREAK;
2377 /* textbutton with magnifier icon: do live update for search button */
2378 if (but->icon == ICON_VIEWZOOM)
2383 /* only update when typing for TAB key */
2384 if (update && data->interactive) {
2385 ui_apply_button(C, block, but, data, true);
2390 but->changed = true;
2392 if (data->searchbox)
2393 ui_searchbox_update(C, data->searchbox, but, true); /* true = reset */
2396 if (changed || (retval == WM_UI_HANDLER_BREAK))
2397 ED_region_tag_redraw(data->region);
2400 static void ui_do_but_textedit_select(bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
2402 int mx, my, retval = WM_UI_HANDLER_CONTINUE;
2404 switch (event->type) {
2409 ui_window_to_block(data->region, block, &mx, &my);
2411 ui_textedit_set_cursor_select(but, data, mx);
2412 retval = WM_UI_HANDLER_BREAK;
2416 if (event->val == KM_RELEASE)
2417 button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING);
2418 retval = WM_UI_HANDLER_BREAK;
2422 if (retval == WM_UI_HANDLER_BREAK) {
2424 ED_region_tag_redraw(data->region);
2428 /* ************* number editing for various types ************* */
2430 static void ui_numedit_begin(uiBut *but, uiHandleButtonData *data)
2432 if (but->type == BUT_CURVE) {
2433 but->editcumap = (CurveMapping *)but->poin;
2435 else if (but->type == BUT_COLORBAND) {
2436 data->coba = (ColorBand *)but->poin;
2437 but->editcoba = data->coba;
2439 else if (ELEM4(but->type, BUT_NORMAL, HSVCUBE, HSVCIRCLE, COLOR)) {
2440 ui_get_but_vectorf(but, data->origvec);
2441 copy_v3_v3(data->vec, data->origvec);
2442 but->editvec = data->vec;
2445 float softrange, softmin, softmax;
2447 data->startvalue = ui_get_but_val(but);
2448 data->origvalue = data->startvalue;
2449 data->value = data->origvalue;
2450 but->editval = &data->value;
2452 softmin = but->softmin;
2453 softmax = but->softmax;
2454 softrange = softmax - softmin;
2456 data->dragfstart = (softrange == 0.0f) ? 0.0f : ((float)data->value - softmin) / softrange;
2457 data->dragf = data->dragfstart;
2460 data->dragchange = false;
2461 data->draglock = true;
2464 static void ui_numedit_end(uiBut *but, uiHandleButtonData *data)
2466 but->editval = NULL;
2467 but->editvec = NULL;
2468 but->editcoba = NULL;
2469 but->editcumap = NULL;
2471 data->dragstartx = 0;
2472 data->draglastx = 0;
2473 data->dragchange = false;
2474 data->dragcbd = NULL;
2478 static void ui_numedit_apply(bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data)
2480 if (data->interactive) {
2481 ui_apply_button(C, block, but, data, true);
2487 ED_region_tag_redraw(data->region);
2490 /* ****************** menu opening for various types **************** */
2492 static void ui_blockopen_begin(bContext *C, uiBut *but, uiHandleButtonData *data)
2494 uiBlockCreateFunc func = NULL;
2495 uiBlockHandleCreateFunc handlefunc = NULL;
2496 uiMenuCreateFunc menufunc = NULL;
2497 char *menustr = NULL;
2500 switch (but->type) {
2503 if (but->menu_create_func) {
2504 menufunc = but->menu_create_func;
2508 func = but->block_create_func;
2509 arg = but->poin ? but->poin : but->func_argN;
2513 if (but->menu_create_func) {
2514 menufunc = but->menu_create_func;
2518 data->origvalue = ui_get_but_val(but);
2519 data->value = data->origvalue;
2520 but->editval = &data->value;
2526 ui_get_but_vectorf(but, data->origvec);
2527 copy_v3_v3(data->vec, data->origvec);
2528 but->editvec = data->vec;
2530 handlefunc = ui_block_func_COLOR;
2534 /* quiet warnings for unhandled types */
2539 if (func || handlefunc) {
2540 data->menu = ui_popup_block_create(C, data->region, but, func, handlefunc, arg);
2541 if (but->block->handle)
2542 data->menu->popup = but->block->handle->popup;
2544 else if (menufunc || menustr) {
2545 data->menu = ui_popup_menu_create(C, data->region, but, menufunc, arg, menustr);
2546 if (but->block->handle)
2547 data->menu->popup = but->block->handle->popup;
2550 /* this makes adjacent blocks auto open from now on */
2551 //if (but->block->auto_open == 0) but->block->auto_open = 1;
2554 static void ui_blockopen_end(bContext *C, uiBut *but, uiHandleButtonData *data)
2557 but->editval = NULL;
2558 but->editvec = NULL;
2560 but->block->auto_open_last = PIL_check_seconds_timer();
2564 ui_popup_block_free(C, data->menu);
2569 int ui_button_open_menu_direction(uiBut *but)
2571 uiHandleButtonData *data = but->active;
2573 if (data && data->menu)
2574 return data->menu->direction;
2579 /* ***************** events for different button types *************** */
2581 static int ui_do_but_BUT(bContext *C, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
2583 if (data->state == BUTTON_STATE_HIGHLIGHT) {
2584 if (event->type == LEFTMOUSE && event->val == KM_PRESS) {
2585 button_activate_state(C, but, BUTTON_STATE_WAIT_RELEASE);
2586 return WM_UI_HANDLER_BREAK;
2588 else if (event->type == LEFTMOUSE && but->block->handle) {
2589 /* regular buttons will be 'UI_SELECT', menu items 'UI_ACTIVE' */
2590 if (!(but->flag & (UI_SELECT | UI_ACTIVE)))
2591 data->cancel = true;
2592 button_activate_state(C, but, BUTTON_STATE_EXIT);
2593 return WM_UI_HANDLER_BREAK;
2595 else if (ELEM(event->type, PADENTER, RETKEY) && event->val == KM_PRESS) {
2596 button_activate_state(C, but, BUTTON_STATE_WAIT_FLASH);
2597 return WM_UI_HANDLER_BREAK;
2600 else if (data->state == BUTTON_STATE_WAIT_RELEASE) {
2601 if (event->type == LEFTMOUSE && event->val != KM_PRESS) {
2602 if (!(but->flag & UI_SELECT))
2603 data->cancel = true;
2604 button_activate_state(C, but, BUTTON_STATE_EXIT);
2605 return WM_UI_HANDLER_BREAK;
2609 return WM_UI_HANDLER_CONTINUE;
2612 static int ui_do_but_HOTKEYEVT(bContext *C, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
2614 if (data->state == BUTTON_STATE_HIGHLIGHT) {
2615 if (ELEM3(event->type, LEFTMOUSE, PADENTER, RETKEY) && event->val == KM_PRESS) {
2616 but->drawstr[0] = 0;
2617 but->modifier_key = 0;
2618 button_activate_state(C, but, BUTTON_STATE_WAIT_KEY_EVENT);
2619 return WM_UI_HANDLER_BREAK;
2622 else if (data->state == BUTTON_STATE_WAIT_KEY_EVENT) {
2624 if (event->type == MOUSEMOVE)
2625 return WM_UI_HANDLER_CONTINUE;
2627 if (event->type == LEFTMOUSE && event->val == KM_PRESS) {
2628 /* only cancel if click outside the button */
2629 if (ui_mouse_inside_button(but->active->region, but, event->x, event->y) == 0) {
2630 /* data->cancel doesnt work, this button opens immediate */
2631 if (but->flag & UI_BUT_IMMEDIATE)
2632 ui_set_but_val(but, 0);
2634 data->cancel = true;
2635 button_activate_state(C, but, BUTTON_STATE_EXIT);
2636 return WM_UI_HANDLER_BREAK;
2641 but->modifier_key = 0;
2642 if (event->shift) but->modifier_key |= KM_SHIFT;
2643 if (event->alt) but->modifier_key |= KM_ALT;
2644 if (event->ctrl) but->modifier_key |= KM_CTRL;
2645 if (event->oskey) but->modifier_key |= KM_OSKEY;
2648 ED_region_tag_redraw(data->region);
2650 if (event->val == KM_PRESS) {
2651 if (ISHOTKEY(event->type)) {
2653 if (WM_key_event_string(event->type)[0])
2654 ui_set_but_val(but, event->type);
2656 data->cancel = true;
2658 button_activate_state(C, but, BUTTON_STATE_EXIT);
2659 return WM_UI_HANDLER_BREAK;
2661 else if (event->type == ESCKEY) {
2662 if (event->val == KM_PRESS) {
2663 data->cancel = true;
2664 data->escapecancel = true;
2665 button_activate_state(C, but, BUTTON_STATE_EXIT);
2672 return WM_UI_HANDLER_CONTINUE;
2675 static int ui_do_but_KEYEVT(bContext *C, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
2677 if (data->state == BUTTON_STATE_HIGHLIGHT) {
2678 if (ELEM3(event->type, LEFTMOUSE, PADENTER, RETKEY) && event->val == KM_PRESS) {
2679 button_activate_state(C, but, BUTTON_STATE_WAIT_KEY_EVENT);
2680 return WM_UI_HANDLER_BREAK;
2683 else if (data->state == BUTTON_STATE_WAIT_KEY_EVENT) {
2684 if (event->type == MOUSEMOVE)
2685 return WM_UI_HANDLER_CONTINUE;
2687 if (event->val == KM_PRESS) {
2688 if (WM_key_event_string(event->type)[0])
2689 ui_set_but_val(but, event->type);
2691 data->cancel = true;
2693 button_activate_state(C, but, BUTTON_STATE_EXIT);
2697 return WM_UI_HANDLER_CONTINUE;
2700 static int ui_do_but_TEX(bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
2702 if (data->state == BUTTON_STATE_HIGHLIGHT) {
2703 if (ELEM4(event->type, LEFTMOUSE, EVT_BUT_OPEN, PADENTER, RETKEY) && event->val == KM_PRESS) {
2704 if (ELEM(event->type, PADENTER, RETKEY) && (!ui_is_but_utf8(but))) {
2705 /* pass - allow filesel, enter to execute */
2707 else if (but->dt == UI_EMBOSSN && !event->ctrl) {
2711 button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING);
2712 return WM_UI_HANDLER_BREAK;
2716 else if (data->state == BUTTON_STATE_TEXT_EDITING) {
2717 ui_do_but_textedit(C, block, but, data, event);
2718 return WM_UI_HANDLER_BREAK;
2720 else if (data->state == BUTTON_STATE_TEXT_SELECTING) {
2721 ui_do_but_textedit_select(C, block, but, data, event);
2722 return WM_UI_HANDLER_BREAK;
2725 return WM_UI_HANDLER_CONTINUE;
2728 static int ui_do_but_SEARCH_UNLINK(bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
2730 /* unlink icon is on right */
2731 if (ELEM4(event->type, LEFTMOUSE, EVT_BUT_OPEN, PADENTER, RETKEY) && event->val == KM_PRESS &&
2732 ui_is_but_search_unlink_visible(but))
2734 ARegion *ar = data->region;
2736 int x = event->x, y = event->y;
2738 ui_window_to_block(ar, but->block, &x, &y);
2740 BLI_rcti_rctf_copy(&rect, &but->rect);
2742 rect.xmin = rect.xmax - (BLI_rcti_size_y(&rect));
2743 if (BLI_rcti_isect_pt(&rect, x, y)) {
2744 /* most likely NULL, but let's check, and give it temp zero string */
2745 if (data->str == NULL)
2746 data->str = MEM_callocN(1, "temp str");
2749 ui_apply_but_TEX(C, but, data);
2750 button_activate_state(C, but, BUTTON_STATE_EXIT);
2752 return WM_UI_HANDLER_BREAK;
2755 return ui_do_but_TEX(C, block, but, data, event);
2758 static int ui_do_but_TOG(bContext *C, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
2760 #ifdef USE_DRAG_TOGGLE
2761 if (data->state == BUTTON_STATE_HIGHLIGHT) {
2762 if (event->type == LEFTMOUSE && event->val == KM_PRESS && ui_is_but_drag_toggle(but)) {
2764 data->togdual = event->ctrl;
2765 data->togonly = !event->shift;
2767 ui_apply_button(C, but->block, but, data, true);
2768 button_activate_state(C, but, BUTTON_STATE_WAIT_DRAG);
2769 data->dragstartx = event->x;
2770 data->dragstarty = event->y;
2771 return WM_UI_HANDLER_BREAK;
2774 else if (data->state == BUTTON_STATE_WAIT_DRAG) {
2775 /* note: the 'BUTTON_STATE_WAIT_DRAG' part of 'ui_do_but_EXIT' could be refactored into its own function */
2776 data->applied = false;
2777 return ui_do_but_EXIT(C, but, data, event);
2780 if (data->state == BUTTON_STATE_HIGHLIGHT) {
2781 if (ELEM3(event->type, LEFTMOUSE, PADENTER, RETKEY) && event->val == KM_PRESS) {
2783 data->togdual = event->ctrl;
2784 data->togonly = !event->shift;
2786 button_activate_state(C, but, BUTTON_STATE_EXIT);
2787 return WM_UI_HANDLER_BREAK;
2790 return WM_UI_HANDLER_CONTINUE;
2793 static int ui_do_but_EXIT(bContext *C, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
2796 if (data->state == BUTTON_STATE_HIGHLIGHT) {
2798 /* first handle click on icondrag type button */
2799 if (event->type == LEFTMOUSE && but->dragpoin) {
2800 if (ui_but_mouse_inside_icon(but, data->region, event)) {
2802 /* tell the button to wait and keep checking further events to
2803 * see if it should start dragging */
2804 button_activate_state(C, but, BUTTON_STATE_WAIT_DRAG);
2805 data->dragstartx = event->x;
2806 data->dragstarty = event->y;
2807 return WM_UI_HANDLER_CONTINUE;
2810 #ifdef USE_DRAG_TOGGLE
2811 if (event->type == LEFTMOUSE && ui_is_but_drag_toggle(but)) {
2812 button_activate_state(C, but, BUTTON_STATE_WAIT_DRAG);
2813 data->dragstartx = event->x;
2814 data->dragstarty = event->y;
2815 return WM_UI_HANDLER_CONTINUE;
2819 if (ELEM3(event->type, LEFTMOUSE, PADENTER, RETKEY) && event->val == KM_PRESS) {
2820 int ret = WM_UI_HANDLER_BREAK;
2821 /* XXX (a bit ugly) Special case handling for filebrowser drag button */
2822 if (but->dragpoin && but->imb && ui_but_mouse_inside_icon(but, data->region, event)) {
2823 ret = WM_UI_HANDLER_CONTINUE;
2825 button_activate_state(C, but, BUTTON_STATE_EXIT);
2829 else if (data->state == BUTTON_STATE_WAIT_DRAG) {
2831 /* this function also ends state */
2832 if (ui_but_start_drag(C, but, data, event)) {
2833 return WM_UI_HANDLER_BREAK;
2836 /* If the mouse has been pressed and released, getting to
2837 * this point without triggering a drag, then clear the
2838 * drag state for this button and continue to pass on the event */
2839 if (event->type == LEFTMOUSE && event->val == KM_RELEASE) {
2840 button_activate_state(C, but, BUTTON_STATE_EXIT);
2841 return WM_UI_HANDLER_CONTINUE;
2844 /* while waiting for a drag to be triggered, always block
2845 * other events from getting handled */
2846 return WM_UI_HANDLER_BREAK;
2849 return WM_UI_HANDLER_CONTINUE;
2852 /* var names match ui_numedit_but_NUM */
2853 static float ui_numedit_apply_snapf(uiBut *but, float tempf, float softmin, float softmax, float softrange,
2854 const enum eSnapType snap)
2856 if (tempf == softmin || tempf == softmax || snap == SNAP_OFF) {
2862 if (ui_is_but_unit(but)) {
2863 UnitSettings *unit = but->block->unit;
2864 int unit_type = RNA_SUBTYPE_UNIT_VALUE(uiButGetUnitType(but));
2866 if (bUnit_IsValid(unit->system, unit_type)) {
2867 fac = (float)bUnit_BaseScalar(unit->system, unit_type);
2868 if (ELEM3(unit_type, B_UNIT_LENGTH, B_UNIT_AREA, B_UNIT_VOLUME)) {
2869 fac /= unit->scale_length;
2875 /* snap in unit-space */
2877 /* softmin /= fac; */ /* UNUSED */
2878 /* softmax /= fac; */ /* UNUSED */
2882 if (snap == SNAP_ON) {
2883 if (softrange < 2.10f) tempf = 0.1f * floorf(10.0f * tempf);
2884 else if (softrange < 21.0f) tempf = floorf(tempf);
2885 else tempf = 10.0f * floorf(tempf / 10.0f);
2887 else if (snap == SNAP_ON_SMALL) {
2888 if (softrange < 2.10f) tempf = 0.01f * floorf(100.0f * tempf);
2889 else if (softrange < 21.0f) tempf = 0.1f * floorf(10.0f * tempf);
2890 else tempf = floor(tempf);
2903 static float ui_numedit_apply_snap(int temp, float softmin, float softmax,
2904 const enum eSnapType snap)
2906 if (temp == softmin || temp == softmax)
2913 temp = 10 * (temp / 10);
2916 temp = 100 * (temp / 100);
2923 static bool ui_numedit_but_NUM(uiBut *but, uiHandleButtonData *data,