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"
67 #include "ED_screen.h"
69 #include "ED_keyframing.h"
71 #include "UI_interface.h"
75 #include "interface_intern.h"
77 #include "RNA_access.h"
82 /* place the mouse at the scaled down location when un-grabbing */
83 #define USE_CONT_MOUSE_CORRECT
86 static void ui_add_smart_controller(bContext *C, uiBut *from, uiBut *to);
87 static void ui_add_link(bContext *C, uiBut *from, uiBut *to);
88 static int ui_do_but_EXIT(bContext *C, uiBut *but, struct uiHandleButtonData *data, const wmEvent *event);
90 /***************** structs and defines ****************/
92 #define BUTTON_TOOLTIP_DELAY 0.500
93 #define BUTTON_FLASH_DELAY 0.020
94 #define MENU_SCROLL_INTERVAL 0.1
95 #define BUTTON_AUTO_OPEN_THRESH 0.3
96 #define BUTTON_MOUSE_TOWARDS_THRESH 1.0
98 typedef enum uiButtonActivateType {
101 BUTTON_ACTIVATE_APPLY,
102 BUTTON_ACTIVATE_TEXT_EDITING,
104 } uiButtonActivateType;
106 typedef enum uiHandleButtonState {
108 BUTTON_STATE_HIGHLIGHT,
109 BUTTON_STATE_WAIT_FLASH,
110 BUTTON_STATE_WAIT_RELEASE,
111 BUTTON_STATE_WAIT_KEY_EVENT,
112 BUTTON_STATE_NUM_EDITING,
113 BUTTON_STATE_TEXT_EDITING,
114 BUTTON_STATE_TEXT_SELECTING,
115 BUTTON_STATE_MENU_OPEN,
116 BUTTON_STATE_WAIT_DRAG,
118 } uiHandleButtonState;
120 typedef struct uiHandleButtonData {
128 uiHandleButtonState state;
130 /* booleans (could be made into flags) */
131 char cancel, escapecancel;
132 char applied, appliedinteractive;
137 double value, origvalue, startvalue;
138 float vec[3], origvec[3];
139 int togdual, togonly;
144 wmTimer *tooltiptimer;
148 wmTimer *autoopentimer;
150 /* text selection/editing */
151 int maxlen, selextend, selstartx;
153 /* number editing / dragging */
154 int draglastx, draglasty;
155 int dragstartx, dragstarty;
156 int dragchange, draglock, dragsel;
157 float dragf, dragfstart;
160 #ifdef USE_CONT_MOUSE_CORRECT
161 /* when ungrabbing buttons which are #ui_is_a_warp_but(), we may want to position them
162 * FLT_MAX signifies do-nothing, use #ui_block_to_window_fl() to get this into a usable space */
163 float ungrab_mval[2];
166 /* menu open (watch uiFreeActiveButtons) */
167 uiPopupBlockHandle *menu;
170 /* search box (watch uiFreeActiveButtons) */
174 uiButtonActivateType posttype;
176 } uiHandleButtonData;
178 typedef struct uiAfterFunc {
179 struct uiAfterFunc *next, *prev;
181 uiButHandleFunc func;
186 uiButHandleNFunc funcN;
189 uiButHandleRenameFunc rename_func;
193 uiBlockHandleFunc handle_func;
194 void *handle_func_arg;
197 uiMenuHandleFunc butm_func;
201 wmOperatorType *optype;
206 PropertyRNA *rnaprop;
208 bContextStore *context;
210 char undostr[BKE_UNDO_STR_MAX];
215 static int ui_but_contains_pt(uiBut *but, int mx, int my);
216 static int ui_mouse_inside_button(ARegion *ar, uiBut *but, int x, int y);
217 static void button_activate_state(bContext *C, uiBut *but, uiHandleButtonState state);
218 static int ui_handler_region_menu(bContext *C, const wmEvent *event, void *userdata);
219 static void ui_handle_button_activate(bContext *C, ARegion *ar, uiBut *but, uiButtonActivateType type);
220 static void button_timers_tooltip_remove(bContext *C, uiBut *but);
222 /* ******************** menu navigation helpers ************** */
224 /* assumes event type is MOUSEPAN */
225 void ui_pan_to_scroll(const wmEvent *event, int *type, int *val)
227 static int lastdy = 0;
228 int dy = event->prevy - event->y;
230 /* This event should be originally from event->type,
231 * converting wrong event into wheel is bad, see [#33803] */
232 BLI_assert(*type == MOUSEPAN);
234 /* sign differs, reset */
235 if ((dy > 0 && lastdy < 0) || (dy < 0 && lastdy > 0))
240 if (ABS(lastdy) > (int)UI_UNIT_Y) {
241 int dy = event->prevy - event->y;
243 if (U.uiflag2 & USER_TRACKPAD_NATURAL)
249 *type = WHEELUPMOUSE;
251 *type = WHEELDOWNMOUSE;
258 static int ui_but_editable(uiBut *but)
260 return ELEM5(but->type, LABEL, SEPR, ROUNDBOX, LISTBOX, PROGRESSBAR);
263 static uiBut *ui_but_prev(uiBut *but)
267 if (!ui_but_editable(but)) return but;
272 static uiBut *ui_but_next(uiBut *but)
276 if (!ui_but_editable(but)) return but;
281 static uiBut *ui_but_first(uiBlock *block)
285 but = block->buttons.first;
287 if (!ui_but_editable(but)) return but;
293 static uiBut *ui_but_last(uiBlock *block)
297 but = block->buttons.last;
299 if (!ui_but_editable(but)) return but;
305 static bool ui_is_a_warp_but(uiBut *but)
307 if (U.uiflag & USER_CONTINUOUS_MOUSE) {
308 if (ELEM7(but->type, NUM, NUMSLI, NUMABS, HSVCIRCLE, TRACKPREVIEW, HSVCUBE, BUT_CURVE)) {
316 static float ui_mouse_scale_warp_factor(const short shift)
318 return shift ? 0.05f : 1.0f;
321 static void ui_mouse_scale_warp(uiHandleButtonData *data,
322 const float mx, const float my,
323 float *r_mx, float *r_my,
326 const float fac = ui_mouse_scale_warp_factor(shift);
328 /* slow down the mouse, this is fairly picky */
329 *r_mx = (data->dragstartx * (1.0f - fac) + mx * fac);
330 *r_my = (data->dragstarty * (1.0f - fac) + my * fac);
333 /* file selectors are exempt from utf-8 checks */
334 int ui_is_but_utf8(uiBut *but)
337 const int subtype = RNA_property_subtype(but->rnaprop);
338 return !(ELEM4(subtype, PROP_FILEPATH, PROP_DIRPATH, PROP_FILENAME, PROP_BYTESTRING));
341 return !(but->flag & UI_BUT_NO_UTF8);
345 /* ********************** button apply/revert ************************/
347 static ListBase UIAfterFuncs = {NULL, NULL};
349 static void ui_apply_but_func(bContext *C, uiBut *but)
352 uiBlock *block = but->block;
354 /* these functions are postponed and only executed after all other
355 * handling is done, i.e. menus are closed, in order to avoid conflicts
356 * with these functions removing the buttons we are working with */
358 if (but->func || but->funcN || block->handle_func || but->rename_func ||
359 (but->type == BUTM && block->butm_func) || but->optype || but->rnaprop)
361 after = MEM_callocN(sizeof(uiAfterFunc), "uiAfterFunc");
363 if (but->func && ELEM(but, but->func_arg1, but->func_arg2)) {
364 /* exception, this will crash due to removed button otherwise */
365 but->func(C, but->func_arg1, but->func_arg2);
368 after->func = but->func;
370 after->func_arg1 = but->func_arg1;
371 after->func_arg2 = but->func_arg2;
372 after->func_arg3 = but->func_arg3;
374 after->funcN = but->funcN;
375 after->func_argN = MEM_dupallocN(but->func_argN);
377 after->rename_func = but->rename_func;
378 after->rename_arg1 = but->rename_arg1;
379 after->rename_orig = but->rename_orig; /* needs free! */
381 after->handle_func = block->handle_func;
382 after->handle_func_arg = block->handle_func_arg;
383 after->retval = but->retval;
385 if (but->type == BUTM) {
386 after->butm_func = block->butm_func;
387 after->butm_func_arg = block->butm_func_arg;
391 after->optype = but->optype;
392 after->opcontext = but->opcontext;
393 after->opptr = but->opptr;
395 after->rnapoin = but->rnapoin;
396 after->rnaprop = but->rnaprop;
399 after->context = CTX_store_copy(but->context);
405 BLI_addtail(&UIAfterFuncs, after);
409 static void ui_apply_autokey_undo(bContext *C, uiBut *but)
411 Scene *scene = CTX_data_scene(C);
414 if (but->flag & UI_BUT_UNDO) {
415 const char *str = NULL;
417 /* define which string to use for undo */
418 if (ELEM(but->type, LINK, INLINK)) str = "Add button link";
419 else if (ELEM(but->type, MENU, ICONTEXTROW)) str = but->drawstr;
420 else if (but->drawstr[0]) str = but->drawstr;
423 /* fallback, else we don't get an undo! */
424 if (str == NULL || str[0] == '\0') {
425 str = "Unknown Action";
428 /* delayed, after all other funcs run, popups are closed, etc */
429 after = MEM_callocN(sizeof(uiAfterFunc), "uiAfterFunc");
430 BLI_strncpy(after->undostr, str, sizeof(after->undostr));
431 BLI_addtail(&UIAfterFuncs, after);
435 ui_but_anim_autokey(C, but, scene, scene->r.cfra);
437 /* make a little report about what we've done! */
439 char *buf = WM_prop_pystring_assign(C, &but->rnapoin, but->rnaprop, but->rnaindex);
441 BKE_report(CTX_wm_reports(C), RPT_PROPERTY, buf);
444 WM_event_add_notifier(C, NC_SPACE | ND_SPACE_INFO_REPORT, NULL);
449 static void ui_apply_but_funcs_after(bContext *C)
451 uiAfterFunc *afterf, after;
455 /* copy to avoid recursive calls */
456 funcs = UIAfterFuncs;
457 UIAfterFuncs.first = UIAfterFuncs.last = NULL;
459 for (afterf = funcs.first; afterf; afterf = after.next) {
460 after = *afterf; /* copy to avoid memleak on exit() */
461 BLI_freelinkN(&funcs, afterf);
464 CTX_store_set(C, after.context);
467 /* free in advance to avoid leak on exit */
468 opptr = *after.opptr,
469 MEM_freeN(after.opptr);
473 WM_operator_name_call(C, after.optype->idname, after.opcontext, (after.opptr) ? &opptr : NULL);
476 WM_operator_properties_free(&opptr);
478 if (after.rnapoin.data)
479 RNA_property_update(C, &after.rnapoin, after.rnaprop);
482 CTX_store_set(C, NULL);
483 CTX_store_free(after.context);
487 after.func(C, after.func_arg1, after.func_arg2);
489 after.funcN(C, after.func_argN, after.func_arg2);
491 MEM_freeN(after.func_argN);
493 if (after.handle_func)
494 after.handle_func(C, after.handle_func_arg, after.retval);
496 after.butm_func(C, after.butm_func_arg, after.a2);
498 if (after.rename_func)
499 after.rename_func(C, after.rename_arg1, after.rename_orig);
500 if (after.rename_orig)
501 MEM_freeN(after.rename_orig);
503 if (after.undostr[0])
504 ED_undo_push(C, after.undostr);
508 static void ui_apply_but_BUT(bContext *C, uiBut *but, uiHandleButtonData *data)
510 ui_apply_but_func(C, but);
512 data->retval = but->retval;
513 data->applied = TRUE;
516 static void ui_apply_but_BUTM(bContext *C, uiBut *but, uiHandleButtonData *data)
518 ui_set_but_val(but, but->hardmin);
519 ui_apply_but_func(C, but);
521 data->retval = but->retval;
522 data->applied = TRUE;
525 static void ui_apply_but_BLOCK(bContext *C, uiBut *but, uiHandleButtonData *data)
527 if (ELEM3(but->type, MENU, ICONROW, ICONTEXTROW))
528 ui_set_but_val(but, data->value);
531 ui_apply_but_func(C, but);
532 data->retval = but->retval;
533 data->applied = TRUE;
536 static void ui_apply_but_TOG(bContext *C, uiBut *but, uiHandleButtonData *data)
542 if (but->type == BUT_TOGDUAL && data->togdual) {
543 if (but->pointype == UI_BUT_POIN_SHORT) {
546 else if (but->pointype == UI_BUT_POIN_INT) {
551 value = ui_get_but_val(but);
555 w = UI_BITBUT_TEST(lvalue, but->bitnr);
556 if (w) lvalue = UI_BITBUT_CLR(lvalue, but->bitnr);
557 else lvalue = UI_BITBUT_SET(lvalue, but->bitnr);
559 if (but->type == TOGR) {
560 if (!data->togonly) {
561 lvalue = 1 << (but->bitnr);
563 ui_set_but_val(but, (double)lvalue);
566 if (lvalue == 0) lvalue = 1 << (but->bitnr);
570 ui_set_but_val(but, (double)lvalue);
571 if (but->type == ICONTOG || but->type == ICONTOGN) ui_check_but(but);
575 if (value == 0.0) push = 1;
578 if (ELEM3(but->type, TOGN, ICONTOGN, OPTIONN)) push = !push;
579 ui_set_but_val(but, (double)push);
580 if (but->type == ICONTOG || but->type == ICONTOGN) ui_check_but(but);
583 /* end local hack... */
584 if (but->type == BUT_TOGDUAL && data->togdual) {
585 if (but->pointype == UI_BUT_POIN_SHORT) {
588 else if (but->pointype == UI_BUT_POIN_INT) {
593 ui_apply_but_func(C, but);
595 data->retval = but->retval;
596 data->applied = TRUE;
599 static void ui_apply_but_ROW(bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data)
603 ui_set_but_val(but, but->hardmax);
605 /* states of other row buttons */
606 for (bt = block->buttons.first; bt; bt = bt->next)
607 if (bt != but && bt->poin == but->poin && ELEM(bt->type, ROW, LISTROW))
610 ui_apply_but_func(C, but);
612 data->retval = but->retval;
613 data->applied = TRUE;
616 static void ui_apply_but_TEX(bContext *C, uiBut *but, uiHandleButtonData *data)
621 ui_set_but_string(C, but, data->str);
624 /* give butfunc the original text too */
625 /* feature used for bone renaming, channels, etc */
626 /* afterfunc frees origstr */
627 but->rename_orig = data->origstr;
628 data->origstr = NULL;
629 ui_apply_but_func(C, but);
631 data->retval = but->retval;
632 data->applied = TRUE;
635 static void ui_apply_but_NUM(bContext *C, uiBut *but, uiHandleButtonData *data)
638 if (ui_set_but_string(C, but, data->str)) {
639 data->value = ui_get_but_val(but);
647 ui_set_but_val(but, data->value);
650 ui_apply_but_func(C, but);
652 data->retval = but->retval;
653 data->applied = TRUE;
656 static void ui_apply_but_TOG3(bContext *C, uiBut *but, uiHandleButtonData *data)
658 if (but->pointype == UI_BUT_POIN_SHORT) {
659 short *sp = (short *)but->poin;
661 if (UI_BITBUT_TEST(sp[1], but->bitnr)) {
662 sp[1] = UI_BITBUT_CLR(sp[1], but->bitnr);
663 sp[0] = UI_BITBUT_CLR(sp[0], but->bitnr);
665 else if (UI_BITBUT_TEST(sp[0], but->bitnr)) {
666 sp[1] = UI_BITBUT_SET(sp[1], but->bitnr);
669 sp[0] = UI_BITBUT_SET(sp[0], but->bitnr);
673 if (UI_BITBUT_TEST(*(but->poin + 2), but->bitnr)) {
674 *(but->poin + 2) = UI_BITBUT_CLR(*(but->poin + 2), but->bitnr);
675 *(but->poin) = UI_BITBUT_CLR(*(but->poin), but->bitnr);
677 else if (UI_BITBUT_TEST(*(but->poin), but->bitnr)) {
678 *(but->poin + 2) = UI_BITBUT_SET(*(but->poin + 2), but->bitnr);
681 *(but->poin) = UI_BITBUT_SET(*(but->poin), but->bitnr);
686 ui_apply_but_func(C, but);
687 data->retval = but->retval;
688 data->applied = TRUE;
691 static void ui_apply_but_VEC(bContext *C, uiBut *but, uiHandleButtonData *data)
693 ui_set_but_vectorf(but, data->vec);
695 ui_apply_but_func(C, but);
697 data->retval = but->retval;
698 data->applied = TRUE;
701 static void ui_apply_but_COLORBAND(bContext *C, uiBut *but, uiHandleButtonData *data)
703 ui_apply_but_func(C, but);
704 data->retval = but->retval;
705 data->applied = TRUE;
708 static void ui_apply_but_CURVE(bContext *C, uiBut *but, uiHandleButtonData *data)
710 ui_apply_but_func(C, but);
711 data->retval = but->retval;
712 data->applied = TRUE;
715 static void ui_apply_but_IDPOIN(bContext *C, uiBut *but, uiHandleButtonData *data)
717 ui_set_but_string(C, but, data->str);
719 ui_apply_but_func(C, but);
720 data->retval = but->retval;
721 data->applied = TRUE;
724 #ifdef WITH_INTERNATIONAL
725 static void ui_apply_but_CHARTAB(bContext *C, uiBut *but, uiHandleButtonData *data)
727 ui_apply_but_func(C, but);
728 data->retval = but->retval;
729 data->applied = TRUE;
733 /* ****************** drag drop code *********************** */
735 static int ui_but_mouse_inside_icon(uiBut *but, ARegion *ar, const wmEvent *event)
738 int x = event->x, y = event->y;
740 ui_window_to_block(ar, but->block, &x, &y);
742 BLI_rcti_rctf_copy(&rect, &but->rect);
745 /* use button size itself */
747 else if (but->flag & UI_ICON_LEFT) {
748 rect.xmax = rect.xmin + (BLI_rcti_size_y(&rect));
751 int delta = BLI_rcti_size_x(&rect) - BLI_rcti_size_y(&rect);
752 rect.xmin += delta / 2;
753 rect.xmax -= delta / 2;
756 return BLI_rcti_isect_pt(&rect, x, y);
759 static int ui_but_start_drag(bContext *C, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
761 /* prevent other WM gestures to start while we try to drag */
762 WM_gestures_remove(C);
764 if (ABS(data->dragstartx - event->x) + ABS(data->dragstarty - event->y) > U.dragthreshold) {
766 button_activate_state(C, but, BUTTON_STATE_EXIT);
769 if (ui_is_but_bool(but)) {
770 const bool is_set = (ui_get_but_val(but) != 0.0);
772 WM_operator_properties_create(&ptr, "UI_OT_drag_toggle");
773 RNA_boolean_set(&ptr, "state", !is_set);
774 RNA_int_set(&ptr, "last_x", data->dragstartx);
775 RNA_int_set(&ptr, "last_y", data->dragstarty);
776 WM_operator_name_call(C, "UI_OT_drag_toggle", WM_OP_INVOKE_DEFAULT, &ptr);
777 WM_operator_properties_free(&ptr);
782 drag = WM_event_start_drag(C, but->icon, but->dragtype, but->dragpoin, ui_get_but_val(but));
784 WM_event_drag_image(drag, but->imb, but->imb_scale, BLI_rctf_size_x(&but->rect), BLI_rctf_size_y(&but->rect));
792 /* ********************** linklines *********************** */
794 static void ui_delete_active_linkline(uiBlock *block)
798 uiLinkLine *line, *nline;
801 for (but = block->buttons.first; but; but = but->next) {
802 if (but->type == LINK && but->link) {
803 for (line = but->link->lines.first; line; line = nline) {
806 if (line->flag & UI_SELECT) {
807 BLI_remlink(&but->link->lines, line);
809 link = line->from->link;
811 /* are there more pointers allowed? */
814 if (*(link->totlink) == 1) {
815 *(link->totlink) = 0;
816 MEM_freeN(*(link->ppoin));
817 *(link->ppoin) = NULL;
821 for (a = 0; a < (*(link->totlink)); a++) {
823 if ((*(link->ppoin))[a] != line->to->poin) {
824 (*(link->ppoin))[b] = (*(link->ppoin))[a];
828 (*(link->totlink))--;
832 *(link->poin) = NULL;
843 static uiLinkLine *ui_is_a_link(uiBut *from, uiBut *to)
850 for (line = link->lines.first; line; line = line->next) {
851 if (line->from == from && line->to == to) {
859 /* XXX BAD BAD HACK, fixme later **************** */
860 /* Try to add an AND Controller between the sensor and the actuator logic bricks and to connect them all */
861 static void ui_add_smart_controller(bContext *C, uiBut *from, uiBut *to)
865 bActuator *act_to, *act_iter;
867 bController ***sens_from_links;
870 uiLink *link = from->link;
872 PointerRNA props_ptr, object_ptr;
875 sens_from_links = (bController ***)(link->ppoin);
878 act_to = (bActuator *)(to->poin);
880 /* (1) get the object */
881 CTX_DATA_BEGIN (C, Object *, ob_iter, selected_editable_objects)
883 for (sens_iter = ob_iter->sensors.first; sens_iter; sens_iter = sens_iter->next) {
884 if (&(sens_iter->links) == sens_from_links) {
894 /* (2) check if the sensor and the actuator are from the same object */
895 for (act_iter = ob->actuators.first; act_iter; act_iter = (bActuator *)act_iter->next) {
896 if (act_iter == act_to)
900 /* only works if the sensor and the actuator are from the same object */
901 if (!act_iter) return;
903 /* in case the linked controller is not the active one */
904 RNA_pointer_create((ID *)ob, &RNA_Object, ob, &object_ptr);
906 WM_operator_properties_create(&props_ptr, "LOGIC_OT_controller_add");
907 RNA_string_set(&props_ptr, "object", ob->id.name + 2);
909 /* (3) add a new controller */
910 if (WM_operator_name_call(C, "LOGIC_OT_controller_add", WM_OP_EXEC_DEFAULT, &props_ptr) & OPERATOR_FINISHED) {
911 cont = (bController *)ob->controllers.last;
912 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... */
914 /* (4) link the sensor->controller->actuator */
915 tmp_but = MEM_callocN(sizeof(uiBut), "uiBut");
916 uiSetButLink(tmp_but, (void **)&cont, (void ***)&(cont->links), &(cont->totlinks), from->link->tocode, (int)to->hardmin);
917 tmp_but->hardmin = from->link->tocode;
918 tmp_but->poin = (char *)cont;
920 tmp_but->type = INLINK;
921 ui_add_link(C, from, tmp_but);
923 tmp_but->type = LINK;
924 ui_add_link(C, tmp_but, to);
926 /* (5) garbage collection */
927 MEM_freeN(tmp_but->link);
930 WM_operator_properties_free(&props_ptr);
933 static void ui_add_link(bContext *C, uiBut *from, uiBut *to)
935 /* in 'from' we have to add a link to 'to' */
941 if ((line = ui_is_a_link(from, to))) {
942 line->flag |= UI_SELECT;
943 ui_delete_active_linkline(from->block);
947 if (from->type == INLINK && to->type == INLINK) {
950 else if (from->type == LINK && to->type == INLINK) {
951 if (from->link->tocode != (int)to->hardmin) {
952 ui_add_smart_controller(C, from, to);
956 else if (from->type == INLINK && to->type == LINK) {
957 if (to->link->tocode == (int)from->hardmin) {
964 /* are there more pointers allowed? */
966 oldppoin = *(link->ppoin);
968 (*(link->totlink))++;
969 *(link->ppoin) = MEM_callocN(*(link->totlink) * sizeof(void *), "new link");
971 for (a = 0; a < (*(link->totlink)) - 1; a++) {
972 (*(link->ppoin))[a] = oldppoin[a];
974 (*(link->ppoin))[a] = to->poin;
976 if (oldppoin) MEM_freeN(oldppoin);
979 *(link->poin) = to->poin;
985 static void ui_apply_but_LINK(bContext *C, uiBut *but, uiHandleButtonData *data)
987 ARegion *ar = CTX_wm_region(C);
990 for (bt = but->block->buttons.first; bt; bt = bt->next) {
991 if (ui_mouse_inside_button(ar, bt, but->linkto[0] + ar->winrct.xmin, but->linkto[1] + ar->winrct.ymin) )
994 if (bt && bt != but) {
995 if (!ELEM(bt->type, LINK, INLINK) || !ELEM(but->type, LINK, INLINK))
998 if (but->type == LINK) ui_add_link(C, but, bt);
999 else ui_add_link(C, bt, but);
1001 ui_apply_but_func(C, but);
1002 data->retval = but->retval;
1004 data->applied = TRUE;
1007 static void ui_apply_but_IMAGE(bContext *C, uiBut *but, uiHandleButtonData *data)
1009 ui_apply_but_func(C, but);
1010 data->retval = but->retval;
1011 data->applied = TRUE;
1014 static void ui_apply_but_HISTOGRAM(bContext *C, uiBut *but, uiHandleButtonData *data)
1016 ui_apply_but_func(C, but);
1017 data->retval = but->retval;
1018 data->applied = TRUE;
1021 static void ui_apply_but_WAVEFORM(bContext *C, uiBut *but, uiHandleButtonData *data)
1023 ui_apply_but_func(C, but);
1024 data->retval = but->retval;
1025 data->applied = TRUE;
1028 static void ui_apply_but_TRACKPREVIEW(bContext *C, uiBut *but, uiHandleButtonData *data)
1030 ui_apply_but_func(C, but);
1031 data->retval = but->retval;
1032 data->applied = TRUE;
1036 static void ui_apply_button(bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, int interactive)
1041 ColorBand *editcoba;
1042 CurveMapping *editcumap;
1046 /* if we cancel and have not applied yet, there is nothing to do,
1047 * otherwise we have to restore the original value again */
1052 if (data->str) MEM_freeN(data->str);
1053 data->str = data->origstr;
1054 data->origstr = NULL;
1055 data->value = data->origvalue;
1056 data->origvalue = 0.0;
1057 copy_v3_v3(data->vec, data->origvec);
1058 data->origvec[0] = data->origvec[1] = data->origvec[2] = 0.0f;
1061 /* we avoid applying interactive edits a second time
1062 * at the end with the appliedinteractive flag */
1064 data->appliedinteractive = TRUE;
1066 else if (data->appliedinteractive) {
1071 /* ensures we are writing actual values */
1072 editstr = but->editstr;
1073 editval = but->editval;
1074 editvec = but->editvec;
1075 editcoba = but->editcoba;
1076 editcumap = but->editcumap;
1077 but->editstr = NULL;
1078 but->editval = NULL;
1079 but->editvec = NULL;
1080 but->editcoba = NULL;
1081 but->editcumap = NULL;
1083 /* handle different types */
1084 switch (but->type) {
1086 ui_apply_but_BUT(C, but, data);
1089 case SEARCH_MENU_UNLINK:
1091 ui_apply_but_TEX(C, but, data);
1102 ui_apply_but_TOG(C, but, data);
1106 ui_apply_but_ROW(C, block, but, data);
1113 ui_apply_but_NUM(C, but, data);
1118 ui_apply_but_TOG3(C, but, data);
1125 ui_apply_but_BLOCK(C, but, data);
1129 ui_apply_but_VEC(C, but, data);
1131 ui_apply_but_BLOCK(C, but, data);
1134 ui_apply_but_BUTM(C, but, data);
1139 ui_apply_but_VEC(C, but, data);
1142 ui_apply_but_COLORBAND(C, but, data);
1145 ui_apply_but_CURVE(C, but, data);
1148 ui_apply_but_IDPOIN(C, but, data);
1150 #ifdef WITH_INTERNATIONAL
1152 ui_apply_but_CHARTAB(C, but, data);
1157 ui_apply_but_BUT(C, but, data);
1161 ui_apply_but_LINK(C, but, data);
1164 ui_apply_but_IMAGE(C, but, data);
1167 ui_apply_but_HISTOGRAM(C, but, data);
1170 ui_apply_but_WAVEFORM(C, but, data);
1173 ui_apply_but_TRACKPREVIEW(C, but, data);
1179 but->editstr = editstr;
1180 but->editval = editval;
1181 but->editvec = editvec;
1182 but->editcoba = editcoba;
1183 but->editcumap = editcumap;
1186 /* ******************* drop event ******************** */
1188 /* only call if event type is EVT_DROP */
1189 static void ui_but_drop(bContext *C, const wmEvent *event, uiBut *but, uiHandleButtonData *data)
1192 ListBase *drags = event->customdata; /* drop event type has listbase customdata by default */
1194 for (wmd = drags->first; wmd; wmd = wmd->next) {
1195 if (wmd->type == WM_DRAG_ID) {
1196 /* align these types with UI_but_active_drop_name */
1197 if (ELEM4(but->type, TEX, IDPOIN, SEARCH_MENU, SEARCH_MENU_UNLINK)) {
1198 ID *id = (ID *)wmd->poin;
1200 if (but->poin == NULL && but->rnapoin.data == NULL) {}
1201 button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING);
1202 BLI_strncpy(data->str, id->name + 2, data->maxlen);
1203 button_activate_state(C, but, BUTTON_STATE_EXIT);
1210 /* ******************* copy and paste ******************** */
1212 /* c = copy, v = paste */
1213 static void ui_but_copy_paste(bContext *C, uiBut *but, uiHandleButtonData *data, char mode)
1215 static ColorBand but_copypaste_coba = {0};
1216 char buf[UI_MAX_DRAW_STR + 1] = {0};
1218 if (mode == 'v' && but->lock == TRUE) {
1223 /* extract first line from clipboard in case of multi-line copies */
1224 char *p, *pbuf = WM_clipboard_text_get(0);
1228 while (*p && *p != '\r' && *p != '\n' && i < UI_MAX_DRAW_STR) {
1238 if (ELEM4(but->type, NUM, NUMABS, NUMSLI, HSVSLI)) {
1240 if (but->poin == NULL && but->rnapoin.data == NULL) {
1243 else if (mode == 'c') {
1244 ui_get_but_string(but, buf, sizeof(buf));
1245 WM_clipboard_text_set(buf, 0);
1250 if (ui_set_but_string_eval_num(C, but, buf, &val)) {
1251 button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
1253 ui_set_but_string(C, but, buf);
1254 button_activate_state(C, but, BUTTON_STATE_EXIT);
1260 else if (but->type == COLOR) {
1263 if (but->poin == NULL && but->rnapoin.data == NULL) {
1266 else if (mode == 'c') {
1267 if (RNA_property_array_length(&but->rnapoin, but->rnaprop) == 4)
1268 rgba[3] = RNA_property_float_get_index(&but->rnapoin, but->rnaprop, 3);
1272 ui_get_but_vectorf(but, rgba);
1273 /* convert to linear color to do compatible copy between gamma and non-gamma */
1274 if (but->rnaprop && RNA_property_subtype(but->rnaprop) == PROP_COLOR_GAMMA)
1275 srgb_to_linearrgb_v3_v3(rgba, rgba);
1277 BLI_snprintf(buf, sizeof(buf), "[%f, %f, %f, %f]", rgba[0], rgba[1], rgba[2], rgba[3]);
1278 WM_clipboard_text_set(buf, 0);
1282 if (sscanf(buf, "[%f, %f, %f, %f]", &rgba[0], &rgba[1], &rgba[2], &rgba[3]) == 4) {
1283 /* assume linear colors in buffer */
1284 if (but->rnaprop && RNA_property_subtype(but->rnaprop) == PROP_COLOR_GAMMA)
1285 linearrgb_to_srgb_v3_v3(rgba, rgba);
1287 button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
1288 ui_set_but_vectorf(but, rgba);
1289 if (RNA_property_array_length(&but->rnapoin, but->rnaprop) == 4)
1290 RNA_property_float_set_index(&but->rnapoin, but->rnaprop, 3, rgba[3]);
1292 button_activate_state(C, but, BUTTON_STATE_EXIT);
1297 /* text/string and ID data */
1298 else if (ELEM4(but->type, TEX, IDPOIN, SEARCH_MENU, SEARCH_MENU_UNLINK)) {
1299 uiHandleButtonData *active_data = but->active;
1301 if (but->poin == NULL && but->rnapoin.data == NULL) {
1304 else if (mode == 'c') {
1305 button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING);
1306 BLI_strncpy(buf, active_data->str, UI_MAX_DRAW_STR);
1307 WM_clipboard_text_set(active_data->str, 0);
1308 active_data->cancel = TRUE;
1309 button_activate_state(C, but, BUTTON_STATE_EXIT);
1312 button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING);
1314 if (ui_is_but_utf8(but)) BLI_strncpy_utf8(active_data->str, buf, active_data->maxlen);
1315 else BLI_strncpy(active_data->str, buf, active_data->maxlen);
1317 if (ELEM(but->type, SEARCH_MENU, SEARCH_MENU_UNLINK)) {
1318 /* else uiSearchboxData.active member is not updated [#26856] */
1319 ui_searchbox_update(C, data->searchbox, but, 1);
1321 button_activate_state(C, but, BUTTON_STATE_EXIT);
1324 /* colorband (not supported by system clipboard) */
1325 else if (but->type == BUT_COLORBAND) {
1327 if (but->poin == NULL)
1330 memcpy(&but_copypaste_coba, but->poin, sizeof(ColorBand));
1333 if (but_copypaste_coba.tot == 0)
1337 but->poin = MEM_callocN(sizeof(ColorBand), "colorband");
1339 button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
1340 memcpy(data->coba, &but_copypaste_coba, sizeof(ColorBand));
1341 button_activate_state(C, but, BUTTON_STATE_EXIT);
1344 /* operator button (any type) */
1345 else if (but->optype) {
1349 opptr = uiButGetOperatorPtrRNA(but); /* allocated when needed, the button owns it */
1351 str = WM_operator_pystring(C, but->optype, opptr, 0);
1353 WM_clipboard_text_set(str, 0);
1360 /* ************************ password text ******************************
1362 * Functions to convert password strings that should not be displayed
1363 * to asterisk representation (e.g. mysecretpasswd -> *************)
1365 * It converts every UTF-8 character to an asterisk, and also remaps
1366 * the cursor position and selection start/end.
1368 * Note: remaping is used, because password could contain UTF-8 characters.
1372 static int ui_text_position_from_hidden(uiBut *but, int pos)
1377 for (i = 0, strpos = but->drawstr; i < pos; i++)
1378 strpos = BLI_str_find_next_char_utf8(strpos, NULL);
1380 return (strpos - but->drawstr);
1383 static int ui_text_position_to_hidden(uiBut *but, int pos)
1385 return BLI_strnlen_utf8(but->drawstr, pos);
1388 void ui_button_text_password_hide(char password_str[UI_MAX_DRAW_STR], uiBut *but, int restore)
1390 if (!(but->rnaprop && RNA_property_subtype(but->rnaprop) == PROP_PASSWORD))
1394 /* restore original string */
1395 BLI_strncpy(but->drawstr, password_str, UI_MAX_DRAW_STR);
1397 /* remap cursor positions */
1398 if (but->pos >= 0) {
1399 but->pos = ui_text_position_from_hidden(but, but->pos);
1400 but->selsta = ui_text_position_from_hidden(but, but->selsta);
1401 but->selend = ui_text_position_from_hidden(but, but->selend);
1405 /* convert text to hidden test using asterisks (e.g. pass -> ****) */
1406 int i, len = BLI_strlen_utf8(but->drawstr);
1408 /* remap cursor positions */
1409 if (but->pos >= 0) {
1410 but->pos = ui_text_position_to_hidden(but, but->pos);
1411 but->selsta = ui_text_position_to_hidden(but, but->selsta);
1412 but->selend = ui_text_position_to_hidden(but, but->selend);
1415 /* save original string */
1416 BLI_strncpy(password_str, but->drawstr, UI_MAX_DRAW_STR);
1418 for (i = 0; i < len; i++)
1419 but->drawstr[i] = '*';
1420 but->drawstr[i] = '\0';
1425 /* ************* in-button text selection/editing ************* */
1428 static int ui_textedit_delete_selection(uiBut *but, uiHandleButtonData *data)
1430 char *str = data->str;
1431 int len = strlen(str);
1433 if (but->selsta != but->selend && len) {
1434 memmove(str + but->selsta, str + but->selend, (len - but->selend) + 1);
1438 but->pos = but->selend = but->selsta;
1442 /* note, but->block->aspect is used here, when drawing button style is getting scaled too */
1443 static void ui_textedit_set_cursor_pos(uiBut *but, uiHandleButtonData *data, const float x)
1445 uiStyle *style = UI_GetStyle(); // XXX pass on as arg
1446 uiFontStyle *fstyle = &style->widget;
1447 float startx = but->rect.xmin;
1448 char *origstr, password_str[UI_MAX_DRAW_STR];
1450 uiStyleFontSet(fstyle);
1452 if (fstyle->kerning == 1) /* for BLF_width */
1453 BLF_enable(fstyle->uifont_id, BLF_KERNING_DEFAULT);
1455 ui_button_text_password_hide(password_str, but, FALSE);
1457 origstr = MEM_mallocN(sizeof(char) * data->maxlen, "ui_textedit origstr");
1459 BLI_strncpy(origstr, but->drawstr, data->maxlen);
1461 /* XXX solve generic, see: #widget_draw_text_icon */
1462 if (but->type == NUM || but->type == NUMSLI) {
1463 startx += (int)(0.5f * (BLI_rctf_size_y(&but->rect)));
1465 else if (ELEM3(but->type, TEX, SEARCH_MENU, SEARCH_MENU_UNLINK)) {
1466 if (but->flag & UI_HAS_ICON) {
1467 startx += UI_DPI_ICON_SIZE;
1469 /* but this extra .05 makes clicks inbetween characters feel nicer */
1470 startx += ((UI_TEXT_MARGIN_X + 0.05f) * U.widget_unit);
1473 /* mouse dragged outside the widget to the left */
1477 origstr[but->ofs] = '\0';
1480 if (BLI_str_cursor_step_prev_utf8(origstr, but->ofs, &i)) {
1481 /* 0.25 == scale factor for less sensitivity */
1482 if (BLF_width(fstyle->uifont_id, origstr + i) > (startx - x) * 0.25f) {
1487 break; /* unlikely but possible */
1491 but->pos = but->ofs;
1493 /* mouse inside the widget, mouse coords mapped in widget space */
1494 else { /* (x >= startx) */
1497 /* keep track of previous distance from the cursor to the char */
1498 float cdist, cdist_prev = 0.0f;
1501 but->pos = pos_prev = strlen(origstr) - but->ofs;
1504 cdist = startx + BLF_width(fstyle->uifont_id, origstr + but->ofs);
1506 /* check if position is found */
1508 /* check is previous location was in fact closer */
1509 if (((float)x - cdist) > (cdist_prev - (float)x)) {
1510 but->pos = pos_prev;
1515 pos_prev = but->pos;
1516 /* done with tricky distance checks */
1519 if (but->pos <= 0) break;
1520 if (BLI_str_cursor_step_prev_utf8(origstr, but->ofs, &pos_i)) {
1522 origstr[but->pos + but->ofs] = 0;
1525 break; /* unlikely but possible */
1528 but->pos += but->ofs;
1529 if (but->pos < 0) but->pos = 0;
1532 if (fstyle->kerning == 1)
1533 BLF_disable(fstyle->uifont_id, BLF_KERNING_DEFAULT);
1535 ui_button_text_password_hide(password_str, but, TRUE);
1540 static void ui_textedit_set_cursor_select(uiBut *but, uiHandleButtonData *data, const float x)
1542 if (x > data->selstartx) data->selextend = EXTEND_RIGHT;
1543 else if (x < data->selstartx) data->selextend = EXTEND_LEFT;
1545 ui_textedit_set_cursor_pos(but, data, x);
1547 if (data->selextend == EXTEND_RIGHT) but->selend = but->pos;
1548 else if (data->selextend == EXTEND_LEFT) but->selsta = but->pos;
1553 /* this is used for both utf8 and ascii, its meant to be used for single keys,
1554 * notice the buffer is either copied or not, so its not suitable for pasting in
1556 static int ui_textedit_type_buf(uiBut *but, uiHandleButtonData *data,
1557 const char *utf8_buf, int utf8_buf_len)
1560 int len, changed = 0;
1565 if (len - (but->selend - but->selsta) + 1 <= data->maxlen) {
1566 int step = utf8_buf_len;
1568 /* type over the current selection */
1569 if ((but->selend - but->selsta) > 0) {
1570 changed = ui_textedit_delete_selection(but, data);
1574 if (len + step < data->maxlen) {
1575 memmove(&str[but->pos + step], &str[but->pos], (len + 1) - but->pos);
1576 memcpy(&str[but->pos], utf8_buf, step * sizeof(char));
1585 static int ui_textedit_type_ascii(uiBut *but, uiHandleButtonData *data, char ascii)
1587 char buf[2] = {ascii, '\0'};
1589 if (ui_is_but_utf8(but) && (BLI_str_utf8_size(buf) == -1)) {
1590 printf("%s: entering invalid ascii char into an ascii key (%d)\n",
1591 __func__, (int)(unsigned char)ascii);
1596 /* in some cases we want to allow invalid utf8 chars */
1597 return ui_textedit_type_buf(but, data, buf, 1);
1600 static void ui_textedit_move(uiBut *but, uiHandleButtonData *data, strCursorJumpDirection direction,
1601 int select, strCursorJumpType jump)
1603 const char *str = data->str;
1604 const int len = strlen(str);
1605 const int pos_prev = but->pos;
1606 const int has_sel = (but->selend - but->selsta) > 0;
1610 /* special case, quit selection and set cursor */
1611 if (has_sel && !select) {
1612 if (jump == STRCUR_JUMP_ALL) {
1613 but->selsta = but->selend = but->pos = direction ? len : 0;
1617 but->selsta = but->pos = but->selend;
1620 but->pos = but->selend = but->selsta;
1623 data->selextend = 0;
1626 int pos_i = but->pos;
1627 BLI_str_cursor_step_utf8(str, len, &pos_i, direction, jump, true);
1631 /* existing selection */
1634 if (data->selextend == 0) {
1635 data->selextend = EXTEND_RIGHT;
1639 if (data->selextend == EXTEND_RIGHT) {
1640 but->selend = but->pos;
1643 but->selsta = but->pos;
1647 if (data->selextend == EXTEND_LEFT) {
1648 but->selsta = but->pos;
1651 but->selend = but->pos;
1655 if (but->selend < but->selsta) {
1656 SWAP(short, but->selsta, but->selend);
1657 data->selextend = (data->selextend == EXTEND_RIGHT) ? EXTEND_LEFT : EXTEND_RIGHT;
1660 } /* new selection */
1663 data->selextend = EXTEND_RIGHT;
1664 but->selend = but->pos;
1665 but->selsta = pos_prev;
1668 data->selextend = EXTEND_LEFT;
1669 but->selend = pos_prev;
1670 but->selsta = but->pos;
1677 static int ui_textedit_delete(uiBut *but, uiHandleButtonData *data, int direction, strCursorJumpType jump)
1679 char *str = data->str;
1680 const int len = strlen(str);
1684 if (jump == STRCUR_JUMP_ALL) {
1685 if (len) changed = 1;
1689 else if (direction) { /* delete */
1690 if ((but->selend - but->selsta) > 0) {
1691 changed = ui_textedit_delete_selection(but, data);
1693 else if (but->pos >= 0 && but->pos < len) {
1696 BLI_str_cursor_step_utf8(str, len, &pos, direction, jump, true);
1697 step = pos - but->pos;
1698 memmove(&str[but->pos], &str[but->pos + step], (len + 1) - but->pos);
1702 else { /* backspace */
1704 if ((but->selend - but->selsta) > 0) {
1705 changed = ui_textedit_delete_selection(but, data);
1707 else if (but->pos > 0) {
1711 BLI_str_cursor_step_utf8(str, len, &pos, direction, jump, true);
1712 step = but->pos - pos;
1713 memmove(&str[but->pos - step], &str[but->pos], (len + 1) - but->pos);
1723 static int ui_textedit_autocomplete(bContext *C, uiBut *but, uiHandleButtonData *data)
1730 if (data->searchbox)
1731 ui_searchbox_autocomplete(C, data->searchbox, but, data->str);
1733 but->autocomplete_func(C, str, but->autofunc_arg);
1735 but->pos = strlen(str);
1736 but->selsta = but->selend = but->pos;
1741 static int ui_textedit_copypaste(uiBut *but, uiHandleButtonData *data, int paste, int copy, int cut)
1743 char buf[UI_MAX_DRAW_STR] = {0};
1744 char *str, *p, *pbuf;
1746 int str_len, buf_len;
1749 str_len = strlen(str);
1753 /* TODO, ensure UTF8 ui_is_but_utf8() - campbell */
1754 /* extract the first line from the clipboard */
1755 p = pbuf = WM_clipboard_text_get(0);
1760 while (*p && *p != '\r' && *p != '\n' && buf_len < UI_MAX_DRAW_STR - 1) {
1761 buf[buf_len++] = *p;
1766 /* paste over the current selection */
1767 if ((but->selend - but->selsta) > 0) {
1768 ui_textedit_delete_selection(but, data);
1769 str_len = strlen(str);
1772 for (y = 0; y < buf_len; y++) {
1773 /* add contents of buffer */
1774 if (str_len + 1 < data->maxlen) {
1775 for (x = data->maxlen; x > but->pos; x--)
1776 str[x] = str[x - 1];
1777 str[but->pos] = buf[y];
1780 str[str_len] = '\0';
1792 else if (copy || cut) {
1793 /* copy the contents to the copypaste buffer */
1794 for (x = but->selsta; x <= but->selend; x++) {
1795 if (x == but->selend)
1798 buf[(x - but->selsta)] = str[x];
1801 WM_clipboard_text_set(buf, 0);
1803 /* for cut only, delete the selection afterwards */
1805 if ((but->selend - but->selsta) > 0)
1806 changed = ui_textedit_delete_selection(but, data);
1812 static void ui_textedit_begin(bContext *C, uiBut *but, uiHandleButtonData *data)
1817 MEM_freeN(data->str);
1821 /* retrieve string */
1822 data->maxlen = ui_get_but_string_max_length(but);
1823 data->str = MEM_callocN(sizeof(char) * data->maxlen + 1, "textedit str");
1824 ui_get_but_string(but, data->str, data->maxlen);
1826 if (ELEM3(but->type, NUM, NUMABS, NUMSLI)) {
1827 ui_convert_to_unit_alt_name(but, data->str, data->maxlen);
1830 /* won't change from now on */
1831 len = strlen(data->str);
1833 data->origstr = BLI_strdupn(data->str, len);
1834 data->selextend = 0;
1835 data->selstartx = 0;
1837 /* set cursor pos to the end of the text */
1838 but->editstr = data->str;
1843 /* optional searchbox */
1844 if (ELEM(but->type, SEARCH_MENU, SEARCH_MENU_UNLINK)) {
1845 data->searchbox = ui_searchbox_create(C, data->region, but);
1846 ui_searchbox_update(C, data->searchbox, but, 1); /* 1 = reset */
1851 WM_cursor_modal(CTX_wm_window(C), BC_TEXTEDITCURSOR);
1854 static void ui_textedit_end(bContext *C, uiBut *but, uiHandleButtonData *data)
1857 if (ui_is_but_utf8(but)) {
1858 int strip = BLI_utf8_invalid_strip(but->editstr, strlen(but->editstr));
1859 /* not a file?, strip non utf-8 chars */
1861 /* wont happen often so isn't that annoying to keep it here for a while */
1862 printf("%s: invalid utf8 - stripped chars %d\n", __func__, strip);
1866 if (data->searchbox) {
1867 if (data->cancel == 0)
1868 ui_searchbox_apply(but, data->searchbox);
1870 ui_searchbox_free(C, data->searchbox);
1871 data->searchbox = NULL;
1874 but->editstr = NULL;
1878 WM_cursor_restore(CTX_wm_window(C));
1881 static void ui_textedit_next_but(uiBlock *block, uiBut *actbut, uiHandleButtonData *data)
1885 /* label and roundbox can overlap real buttons (backdrops...) */
1886 if (ELEM4(actbut->type, LABEL, SEPR, ROUNDBOX, LISTBOX))
1889 for (but = actbut->next; but; but = but->next) {
1890 if (ELEM8(but->type, TEX, NUM, NUMABS, NUMSLI, HSVSLI, IDPOIN, SEARCH_MENU, SEARCH_MENU_UNLINK)) {
1891 if (!(but->flag & UI_BUT_DISABLED)) {
1892 data->postbut = but;
1893 data->posttype = BUTTON_ACTIVATE_TEXT_EDITING;
1898 for (but = block->buttons.first; but != actbut; but = but->next) {
1899 if (ELEM8(but->type, TEX, NUM, NUMABS, NUMSLI, HSVSLI, IDPOIN, SEARCH_MENU, SEARCH_MENU_UNLINK)) {
1900 if (!(but->flag & UI_BUT_DISABLED)) {
1901 data->postbut = but;
1902 data->posttype = BUTTON_ACTIVATE_TEXT_EDITING;
1909 static void ui_textedit_prev_but(uiBlock *block, uiBut *actbut, uiHandleButtonData *data)
1913 /* label and roundbox can overlap real buttons (backdrops...) */
1914 if (ELEM4(actbut->type, LABEL, SEPR, ROUNDBOX, LISTBOX))
1917 for (but = actbut->prev; but; but = but->prev) {
1918 if (ELEM8(but->type, TEX, NUM, NUMABS, NUMSLI, HSVSLI, IDPOIN, SEARCH_MENU, SEARCH_MENU_UNLINK)) {
1919 if (!(but->flag & UI_BUT_DISABLED)) {
1920 data->postbut = but;
1921 data->posttype = BUTTON_ACTIVATE_TEXT_EDITING;
1926 for (but = block->buttons.last; but != actbut; but = but->prev) {
1927 if (ELEM8(but->type, TEX, NUM, NUMABS, NUMSLI, HSVSLI, IDPOIN, SEARCH_MENU, SEARCH_MENU_UNLINK)) {
1928 if (!(but->flag & UI_BUT_DISABLED)) {
1929 data->postbut = but;
1930 data->posttype = BUTTON_ACTIVATE_TEXT_EDITING;
1938 static void ui_do_but_textedit(bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
1940 int mx, my, changed = 0, inbox = 0, update = 0, retval = WM_UI_HANDLER_CONTINUE;
1942 switch (event->type) {
1944 case WHEELDOWNMOUSE:
1947 if (data->searchbox)
1948 ui_searchbox_event(C, data->searchbox, but, event);
1953 data->cancel = TRUE;
1954 data->escapecancel = TRUE;
1955 button_activate_state(C, but, BUTTON_STATE_EXIT);
1956 retval = WM_UI_HANDLER_BREAK;
1960 /* exit on LMB only on RELEASE for searchbox, to mimic other popups, and allow multiple menu levels */
1961 if (data->searchbox)
1962 inbox = ui_searchbox_inside(data->searchbox, event->x, event->y);
1964 if (event->val == KM_PRESS) {
1967 ui_window_to_block(data->region, block, &mx, &my);
1969 if (ui_but_contains_pt(but, mx, my)) {
1970 ui_textedit_set_cursor_pos(but, data, mx);
1971 but->selsta = but->selend = but->pos;
1972 data->selstartx = mx;
1974 button_activate_state(C, but, BUTTON_STATE_TEXT_SELECTING);
1975 retval = WM_UI_HANDLER_BREAK;
1977 else if (inbox == 0) {
1978 /* if searchbox, click outside will cancel */
1979 if (data->searchbox)
1980 data->cancel = data->escapecancel = TRUE;
1981 button_activate_state(C, but, BUTTON_STATE_EXIT);
1982 retval = WM_UI_HANDLER_BREAK;
1986 button_activate_state(C, but, BUTTON_STATE_EXIT);
1987 retval = WM_UI_HANDLER_BREAK;
1993 if (event->val == KM_PRESS) {
1994 switch (event->type) {
1998 if (event->ctrl || event->oskey) {
1999 if (event->type == VKEY)
2000 changed = ui_textedit_copypaste(but, data, 1, 0, 0);
2001 else if (event->type == CKEY)
2002 changed = ui_textedit_copypaste(but, data, 0, 1, 0);
2003 else if (event->type == XKEY)
2004 changed = ui_textedit_copypaste(but, data, 0, 0, 1);
2006 retval = WM_UI_HANDLER_BREAK;
2010 ui_textedit_move(but, data, STRCUR_DIR_NEXT,
2011 event->shift, event->ctrl ? STRCUR_JUMP_DELIM : STRCUR_JUMP_NONE);
2012 retval = WM_UI_HANDLER_BREAK;
2015 ui_textedit_move(but, data, STRCUR_DIR_PREV,
2016 event->shift, event->ctrl ? STRCUR_JUMP_DELIM : STRCUR_JUMP_NONE);
2017 retval = WM_UI_HANDLER_BREAK;
2020 if (data->searchbox) {
2021 ui_searchbox_event(C, data->searchbox, but, event);
2024 /* pass on purposedly */
2026 ui_textedit_move(but, data, STRCUR_DIR_NEXT,
2027 event->shift, STRCUR_JUMP_ALL);
2028 retval = WM_UI_HANDLER_BREAK;
2031 if (data->searchbox) {
2032 ui_searchbox_event(C, data->searchbox, but, event);
2035 /* pass on purposedly */
2037 ui_textedit_move(but, data, STRCUR_DIR_PREV,
2038 event->shift, STRCUR_JUMP_ALL);
2039 retval = WM_UI_HANDLER_BREAK;
2043 button_activate_state(C, but, BUTTON_STATE_EXIT);
2044 retval = WM_UI_HANDLER_BREAK;
2047 changed = ui_textedit_delete(but, data, 1,
2048 event->ctrl ? STRCUR_JUMP_DELIM : STRCUR_JUMP_NONE);
2049 retval = WM_UI_HANDLER_BREAK;
2053 changed = ui_textedit_delete(but, data, 0,
2054 event->shift ? STRCUR_JUMP_ALL : (event->ctrl ? STRCUR_JUMP_DELIM : STRCUR_JUMP_NONE));
2055 retval = WM_UI_HANDLER_BREAK;
2059 /* there is a key conflict here, we can't tab with autocomplete */
2060 if (but->autocomplete_func || data->searchbox) {
2061 changed = ui_textedit_autocomplete(C, but, data);
2062 update = 1; /* do live update for tab key */
2064 /* the hotkey here is not well defined, was G.qual so we check all */
2065 else if (event->shift || event->ctrl || event->alt || event->oskey) {
2066 ui_textedit_prev_but(block, but, data);
2067 button_activate_state(C, but, BUTTON_STATE_EXIT);
2070 ui_textedit_next_but(block, but, data);
2071 button_activate_state(C, but, BUTTON_STATE_EXIT);
2073 retval = WM_UI_HANDLER_BREAK;
2077 if ((event->ascii || event->utf8_buf[0]) && (retval == WM_UI_HANDLER_CONTINUE)) {
2078 char ascii = event->ascii;
2079 const char *utf8_buf = event->utf8_buf;
2081 /* exception that's useful for number buttons, some keyboard
2082 * numpads have a comma instead of a period */
2083 if (ELEM3(but->type, NUM, NUMABS, NUMSLI)) { /* could use data->min*/
2084 if (event->type == PADPERIOD && ascii == ',') {
2086 utf8_buf = NULL; /* force ascii fallback */
2090 if (utf8_buf && utf8_buf[0]) {
2091 int utf8_buf_len = BLI_str_utf8_size(utf8_buf);
2092 /* keep this printf until utf8 is well tested */
2093 if (utf8_buf_len != 1) {
2094 printf("%s: utf8 char '%.*s'\n", __func__, utf8_buf_len, utf8_buf);
2097 // strcpy(utf8_buf, "12345");
2098 changed = ui_textedit_type_buf(but, data, event->utf8_buf, utf8_buf_len);
2101 changed = ui_textedit_type_ascii(but, data, ascii);
2104 retval = WM_UI_HANDLER_BREAK;
2107 /* textbutton with magnifier icon: do live update for search button */
2108 if (but->icon == ICON_VIEWZOOM)
2113 /* only update when typing for TAB key */
2114 if (update && data->interactive) {
2115 ui_apply_button(C, block, but, data, 1);
2120 but->changed = TRUE;
2122 if (data->searchbox)
2123 ui_searchbox_update(C, data->searchbox, but, 1); /* 1 = reset */
2126 if (changed || (retval == WM_UI_HANDLER_BREAK))
2127 ED_region_tag_redraw(data->region);
2130 static void ui_do_but_textedit_select(bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
2132 int mx, my, retval = WM_UI_HANDLER_CONTINUE;
2134 switch (event->type) {
2139 ui_window_to_block(data->region, block, &mx, &my);
2141 ui_textedit_set_cursor_select(but, data, mx);
2142 retval = WM_UI_HANDLER_BREAK;
2146 if (event->val == KM_RELEASE)
2147 button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING);
2148 retval = WM_UI_HANDLER_BREAK;
2152 if (retval == WM_UI_HANDLER_BREAK) {
2154 ED_region_tag_redraw(data->region);
2158 /* ************* number editing for various types ************* */
2160 static void ui_numedit_begin(uiBut *but, uiHandleButtonData *data)
2162 if (but->type == BUT_CURVE) {
2163 but->editcumap = (CurveMapping *)but->poin;
2165 else if (but->type == BUT_COLORBAND) {
2166 data->coba = (ColorBand *)but->poin;
2167 but->editcoba = data->coba;
2169 else if (ELEM3(but->type, BUT_NORMAL, HSVCUBE, HSVCIRCLE)) {
2170 ui_get_but_vectorf(but, data->origvec);
2171 copy_v3_v3(data->vec, data->origvec);
2172 but->editvec = data->vec;
2175 float softrange, softmin, softmax;
2177 data->startvalue = ui_get_but_val(but);
2178 data->origvalue = data->startvalue;
2179 data->value = data->origvalue;
2180 but->editval = &data->value;
2182 softmin = but->softmin;
2183 softmax = but->softmax;
2184 softrange = softmax - softmin;
2186 data->dragfstart = (softrange == 0.0f) ? 0.0f : ((float)data->value - softmin) / softrange;
2187 data->dragf = data->dragfstart;
2190 data->dragchange = 0;
2194 static void ui_numedit_end(uiBut *but, uiHandleButtonData *data)
2196 but->editval = NULL;
2197 but->editvec = NULL;
2198 but->editcoba = NULL;
2199 but->editcumap = NULL;
2201 data->dragstartx = 0;
2202 data->draglastx = 0;
2203 data->dragchange = 0;
2204 data->dragcbd = NULL;
2208 static void ui_numedit_apply(bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data)
2210 if (data->interactive) {
2211 ui_apply_button(C, block, but, data, 1);
2217 ED_region_tag_redraw(data->region);
2220 /* ****************** menu opening for various types **************** */
2222 static void ui_blockopen_begin(bContext *C, uiBut *but, uiHandleButtonData *data)
2224 uiBlockCreateFunc func = NULL;
2225 uiBlockHandleCreateFunc handlefunc = NULL;
2226 uiMenuCreateFunc menufunc = NULL;
2227 char *menustr = NULL;
2230 switch (but->type) {
2233 if (but->menu_create_func) {
2234 menufunc = but->menu_create_func;
2238 func = but->block_create_func;
2239 arg = but->poin ? but->poin : but->func_argN;
2243 if (but->menu_create_func) {
2244 menufunc = but->menu_create_func;
2248 data->origvalue = ui_get_but_val(but);
2249 data->value = data->origvalue;
2250 but->editval = &data->value;
2256 menufunc = ui_block_func_ICONROW;
2260 menufunc = ui_block_func_ICONTEXTROW;
2264 ui_get_but_vectorf(but, data->origvec);
2265 copy_v3_v3(data->vec, data->origvec);
2266 but->editvec = data->vec;
2268 handlefunc = ui_block_func_COLOR;
2272 /* quiet warnings for unhandled types */
2277 if (func || handlefunc) {
2278 data->menu = ui_popup_block_create(C, data->region, but, func, handlefunc, arg);
2279 if (but->block->handle)
2280 data->menu->popup = but->block->handle->popup;
2282 else if (menufunc || menustr) {
2283 data->menu = ui_popup_menu_create(C, data->region, but, menufunc, arg, menustr);
2284 if (but->block->handle)
2285 data->menu->popup = but->block->handle->popup;
2288 /* this makes adjacent blocks auto open from now on */
2289 //if (but->block->auto_open == 0) but->block->auto_open = 1;
2292 static void ui_blockopen_end(bContext *C, uiBut *but, uiHandleButtonData *data)
2295 but->editval = NULL;
2296 but->editvec = NULL;
2298 but->block->auto_open_last = PIL_check_seconds_timer();
2302 ui_popup_block_free(C, data->menu);
2307 int ui_button_open_menu_direction(uiBut *but)
2309 uiHandleButtonData *data = but->active;
2311 if (data && data->menu)
2312 return data->menu->direction;
2317 /* ***************** events for different button types *************** */
2319 static int ui_do_but_BUT(bContext *C, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
2321 if (data->state == BUTTON_STATE_HIGHLIGHT) {
2322 if (event->type == LEFTMOUSE && event->val == KM_PRESS) {
2323 button_activate_state(C, but, BUTTON_STATE_WAIT_RELEASE);
2324 return WM_UI_HANDLER_BREAK;
2326 else if (event->type == LEFTMOUSE && but->block->handle) {
2327 button_activate_state(C, but, BUTTON_STATE_EXIT);
2328 return WM_UI_HANDLER_BREAK;
2330 else if (ELEM(event->type, PADENTER, RETKEY) && event->val == KM_PRESS) {
2331 button_activate_state(C, but, BUTTON_STATE_WAIT_FLASH);
2332 return WM_UI_HANDLER_BREAK;
2335 else if (data->state == BUTTON_STATE_WAIT_RELEASE) {
2336 if (event->type == LEFTMOUSE && event->val != KM_PRESS) {
2337 if (!(but->flag & UI_SELECT))
2338 data->cancel = TRUE;
2339 button_activate_state(C, but, BUTTON_STATE_EXIT);
2340 return WM_UI_HANDLER_BREAK;
2344 return WM_UI_HANDLER_CONTINUE;
2347 static int ui_do_but_HOTKEYEVT(bContext *C, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
2349 if (data->state == BUTTON_STATE_HIGHLIGHT) {
2350 if (ELEM3(event->type, LEFTMOUSE, PADENTER, RETKEY) && event->val == KM_PRESS) {
2351 but->drawstr[0] = 0;
2352 but->modifier_key = 0;
2353 button_activate_state(C, but, BUTTON_STATE_WAIT_KEY_EVENT);
2354 return WM_UI_HANDLER_BREAK;
2357 else if (data->state == BUTTON_STATE_WAIT_KEY_EVENT) {
2359 if (event->type == MOUSEMOVE)
2360 return WM_UI_HANDLER_CONTINUE;
2362 if (event->type == LEFTMOUSE && event->val == KM_PRESS) {
2363 /* only cancel if click outside the button */
2364 if (ui_mouse_inside_button(but->active->region, but, event->x, event->y) == 0) {
2365 /* data->cancel doesnt work, this button opens immediate */
2366 if (but->flag & UI_BUT_IMMEDIATE)
2367 ui_set_but_val(but, 0);
2369 data->cancel = TRUE;
2370 button_activate_state(C, but, BUTTON_STATE_EXIT);
2371 return WM_UI_HANDLER_BREAK;
2376 but->modifier_key = 0;
2377 if (event->shift) but->modifier_key |= KM_SHIFT;
2378 if (event->alt) but->modifier_key |= KM_ALT;
2379 if (event->ctrl) but->modifier_key |= KM_CTRL;
2380 if (event->oskey) but->modifier_key |= KM_OSKEY;
2383 ED_region_tag_redraw(data->region);
2385 if (event->val == KM_PRESS) {
2386 if (ISHOTKEY(event->type)) {
2388 if (WM_key_event_string(event->type)[0])
2389 ui_set_but_val(but, event->type);
2391 data->cancel = TRUE;
2393 button_activate_state(C, but, BUTTON_STATE_EXIT);
2394 return WM_UI_HANDLER_BREAK;
2396 else if (event->type == ESCKEY) {
2397 data->cancel = TRUE;
2398 data->escapecancel = TRUE;
2399 button_activate_state(C, but, BUTTON_STATE_EXIT);
2405 return WM_UI_HANDLER_CONTINUE;
2408 static int ui_do_but_KEYEVT(bContext *C, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
2410 if (data->state == BUTTON_STATE_HIGHLIGHT) {
2411 if (ELEM3(event->type, LEFTMOUSE, PADENTER, RETKEY) && event->val == KM_PRESS) {
2412 button_activate_state(C, but, BUTTON_STATE_WAIT_KEY_EVENT);
2413 return WM_UI_HANDLER_BREAK;
2416 else if (data->state == BUTTON_STATE_WAIT_KEY_EVENT) {
2417 if (event->type == MOUSEMOVE)
2418 return WM_UI_HANDLER_CONTINUE;
2420 if (event->val == KM_PRESS) {
2421 if (WM_key_event_string(event->type)[0])
2422 ui_set_but_val(but, event->type);
2424 data->cancel = TRUE;
2426 button_activate_state(C, but, BUTTON_STATE_EXIT);
2430 return WM_UI_HANDLER_CONTINUE;
2433 static int ui_do_but_TEX(bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
2435 if (data->state == BUTTON_STATE_HIGHLIGHT) {
2436 if (ELEM(event->type, LEFTMOUSE, EVT_BUT_OPEN) && event->val == KM_PRESS) {
2437 if (but->dt == UI_EMBOSSN && !event->ctrl) {
2441 button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING);
2442 return WM_UI_HANDLER_BREAK;
2446 else if (data->state == BUTTON_STATE_TEXT_EDITING) {
2447 ui_do_but_textedit(C, block, but, data, event);
2448 return WM_UI_HANDLER_BREAK;
2450 else if (data->state == BUTTON_STATE_TEXT_SELECTING) {
2451 ui_do_but_textedit_select(C, block, but, data, event);
2452 return WM_UI_HANDLER_BREAK;
2455 return WM_UI_HANDLER_CONTINUE;
2458 static int ui_do_but_SEARCH_UNLINK(bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
2460 /* unlink icon is on right */
2461 if (ELEM(event->type, LEFTMOUSE, EVT_BUT_OPEN) && event->val == KM_PRESS) {
2462 ARegion *ar = CTX_wm_region(C);
2464 int x = event->x, y = event->y;
2466 ui_window_to_block(ar, but->block, &x, &y);
2468 BLI_rcti_rctf_copy(&rect, &but->rect);
2470 rect.xmin = rect.xmax - (BLI_rcti_size_y(&rect));
2471 if ( BLI_rcti_isect_pt(&rect, x, y) ) {
2472 /* most likely NULL, but let's check, and give it temp zero string */
2473 if (data->str == NULL)
2474 data->str = MEM_callocN(16, "temp str");
2477 ui_apply_but_TEX(C, but, data);
2478 button_activate_state(C, but, BUTTON_STATE_EXIT);
2480 return WM_UI_HANDLER_BREAK;
2483 return ui_do_but_TEX(C, block, but, data, event);
2486 static int ui_do_but_TOG(bContext *C, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
2488 if (data->state == BUTTON_STATE_HIGHLIGHT) {
2489 if (event->type == LEFTMOUSE && event->val == KM_PRESS && ui_is_but_bool(but)) {
2490 button_activate_state(C, but, BUTTON_STATE_WAIT_DRAG);
2491 data->dragstartx = event->x;
2492 data->dragstarty = event->y;
2493 return WM_UI_HANDLER_CONTINUE;
2496 if (ELEM3(event->type, LEFTMOUSE, PADENTER, RETKEY) && event->val == KM_PRESS) {
2497 data->togdual = event->ctrl;
2498 data->togonly = !event->shift;
2499 button_activate_state(C, but, BUTTON_STATE_EXIT);
2500 return WM_UI_HANDLER_CONTINUE;
2503 else if (data->state == BUTTON_STATE_WAIT_DRAG) {
2504 /* note: the 'BUTTON_STATE_WAIT_DRAG' part of 'ui_do_but_EXIT' could be refactored into its own function */
2505 return ui_do_but_EXIT(C, but, data, event);
2507 return WM_UI_HANDLER_CONTINUE;
2510 static int ui_do_but_EXIT(bContext *C, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
2513 if (data->state == BUTTON_STATE_HIGHLIGHT) {
2515 /* first handle click on icondrag type button */
2516 if (event->type == LEFTMOUSE && but->dragpoin) {
2517 if (ui_but_mouse_inside_icon(but, data->region, event)) {
2519 /* tell the button to wait and keep checking further events to
2520 * see if it should start dragging */
2521 button_activate_state(C, but, BUTTON_STATE_WAIT_DRAG);
2522 data->dragstartx = event->x;
2523 data->dragstarty = event->y;
2524 return WM_UI_HANDLER_CONTINUE;
2527 if (event->type == LEFTMOUSE && ui_is_but_bool(but)) {
2528 button_activate_state(C, but, BUTTON_STATE_WAIT_DRAG);
2529 data->dragstartx = event->x;
2530 data->dragstarty = event->y;
2531 return WM_UI_HANDLER_CONTINUE;
2534 if (ELEM3(event->type, LEFTMOUSE, PADENTER, RETKEY) && event->val == KM_PRESS) {
2535 int ret = WM_UI_HANDLER_BREAK;
2536 /* XXX (a bit ugly) Special case handling for filebrowser drag button */
2537 if (but->dragpoin && but->imb && ui_but_mouse_inside_icon(but, data->region, event)) {
2538 ret = WM_UI_HANDLER_CONTINUE;
2540 button_activate_state(C, but, BUTTON_STATE_EXIT);
2544 else if (data->state == BUTTON_STATE_WAIT_DRAG) {
2546 /* this function also ends state */
2547 if (ui_but_start_drag(C, but, data, event)) {
2548 return WM_UI_HANDLER_BREAK;
2551 /* If the mouse has been pressed and released, getting to
2552 * this point without triggering a drag, then clear the
2553 * drag state for this button and continue to pass on the event */
2554 if (event->type == LEFTMOUSE && event->val == KM_RELEASE) {
2555 button_activate_state(C, but, BUTTON_STATE_EXIT);
2556 return WM_UI_HANDLER_CONTINUE;
2559 /* while waiting for a drag to be triggered, always block
2560 * other events from getting handled */
2561 return WM_UI_HANDLER_BREAK;
2564 return WM_UI_HANDLER_CONTINUE;
2567 /* var names match ui_numedit_but_NUM */
2568 static float ui_numedit_apply_snapf(uiBut *but, float tempf, float softmin, float softmax, float softrange, int snap)
2570 if (tempf == softmin || tempf == softmax || snap == 0) {
2576 if (ui_is_but_unit(but)) {
2577 UnitSettings *unit = but->block->unit;
2578 int unit_type = RNA_SUBTYPE_UNIT_VALUE(uiButGetUnitType(but));
2580 if (bUnit_IsValid(unit->system, unit_type)) {
2581 fac = (float)bUnit_BaseScalar(unit->system, unit_type);
2582 if (ELEM3(unit_type, B_UNIT_LENGTH, B_UNIT_AREA, B_UNIT_VOLUME)) {
2583 fac /= unit->scale_length;
2589 /* snap in unit-space */
2591 /* softmin /= fac; */ /* UNUSED */
2592 /* softmax /= fac; */ /* UNUSED */
2597 if (softrange < 2.10f) tempf = 0.1f * floorf(10.0f * tempf);
2598 else if (softrange < 21.0f) tempf = floorf(tempf);
2599 else tempf = 10.0f * floorf(tempf / 10.0f);
2601 else if (snap == 2) {
2602 if (softrange < 2.10f) tempf = 0.01f * floorf(100.0f * tempf);
2603 else if (softrange < 21.0f) tempf = 0.1f * floorf(10.0f * tempf);
2604 else tempf = floor(tempf);
2614 static float ui_numedit_apply_snap(int temp, float softmin, float softmax, int snap)
2616 if (temp == softmin || temp == softmax)
2623 temp = 10 * (temp / 10);
2626 temp = 100 * (temp / 100);
2633 static int ui_numedit_but_NUM(uiBut *but, uiHandleButtonData *data, float fac, int snap, int mx)
2635 float deler, tempf, softmin, softmax, softrange;
2636 int lvalue, temp, changed = 0;
2637 const bool is_float = ui_is_but_float(but);
2639 if (mx == data->draglastx)
2642 /* drag-lock - prevent unwanted scroll adjustments */
2643 /* change value (now 3) to adjust threshold in pixels */
2644 if (data->draglock) {
2645 if (abs(mx - data->dragstartx) <= 3)
2649 data->dragstartx = mx; /* ignore mouse movement within drag-lock */
2652 softmin = but->softmin;
2653 softmax = but->softmax;
2654 softrange = softmax - softmin;
2656 if (ui_is_a_warp_but(but)) {
2657 /* Mouse location isn't screen clamped to the screen so use a linear mapping
2658 * 2px == 1-int, or 1px == 1-ClickStep */
2660 fac *= 0.01f * but->a1;
2661 tempf = (float)data->startvalue + ((float)(mx - data->dragstartx) * fac);
2662 tempf = ui_numedit_apply_snapf(but, tempf, softmin, softmax, softrange, snap);
2664 #if 1 /* fake moving the click start, nicer for dragging back after passing the limit */
2665 if (tempf < softmin) {
2666 data->dragstartx -= (softmin - tempf) / fac;
2669 else if (tempf > softmax) {
2670 data->dragstartx += (tempf - softmax) / fac;
2674 CLAMP(tempf, softmin, softmax);
2677 if (tempf != (float)data->value) {
2678 data->dragchange = 1;
2679 data->value = tempf;
2684 if (softrange > 256) fac = 1.0; /* 1px == 1 */
2685 else if (softrange > 32) fac = 1.0 / 2.0; /* 2px == 1 */
2686 else fac = 1.0 / 16.0; /* 16px == 1? */
2688 temp = data->startvalue + (((double)mx - data->dragstartx) * (double)fac);
2689 temp = ui_numedit_apply_snap(temp, softmin, softmax, snap);
2691 #if 1 /* fake moving the click start, nicer for dragging back after passing the limit */
2692 if (temp < softmin) {
2693 data->dragstartx -= (softmin - temp) / fac;
2696 else if (temp > softmax) {
2697 data->dragstartx += (temp - softmax) / fac;
2701 CLAMP(temp, softmin, softmax);
2704 if (temp != data->value) {
2705 data->dragchange = 1;
2711 data->draglastx = mx;
2714 /* Use a non-linear mapping of the mouse drag especially for large floats (normal behavior) */
2717 /* prevent large ranges from getting too out of control */
2718 if (softrange > 600) deler = powf(softrange, 0.75f);
2719 else if (softrange < 25) deler = 50.0;
2720 else if (softrange < 100) deler = 100.0;
2724 if ((is_float == true) && (softrange > 11)) {
2725 /* non linear change in mouse input- good for high precicsion */
2726 data->dragf += (((float)(mx - data->draglastx)) / deler) * (fabsf(mx - data->dragstartx) / 500.0f);
2728 else if ((is_float == false) && (softrange > 129)) { /* only scale large int buttons */
2729 /* non linear change in mouse input- good for high precicsionm ints need less fine tuning */
2730 data->dragf += (((float)(mx - data->draglastx)) / deler) * (fabsf(mx - data->dragstartx) / 250.0f);
2734 data->dragf += ((float)(mx - data->draglastx)) / deler;
2737 CLAMP(data->dragf, 0.0f, 1.0f);
2738 data->draglastx = mx;
2739 tempf = (softmin + data->dragf * softrange);
2743 temp = floorf(tempf + 0.5f);
2745 temp = ui_numedit_apply_snap(temp, softmin, softmax, snap);
2747 CLAMP(temp, softmin, softmax);
2748 lvalue = (int)data->value;
2750 if (temp != lvalue) {
2751 data->dragchange = 1;
2752 data->value = (double)temp;
2758 tempf = ui_numedit_apply_snapf(but, tempf, softmin, softmax, softrange, snap);
2760 CLAMP(tempf, softmin, softmax);
2762 if (tempf != (float)data->value) {
2763 data->dragchange = 1;
2764 data->value = tempf;
2774 static int ui_do_but_NUM(bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
2776 int mx, my; /* mouse location scaled to fit the UI */
2777 int screen_mx, screen_my; /* mouse location kept at screen pixel coords */
2779 int retval = WM_UI_HANDLER_CONTINUE;
2781 mx = screen_mx = event->x;
2782 my = screen_my = event->y;
2784 ui_window_to_block(data->region, block, &mx, &my);
2786 if (data->state == BUTTON_STATE_HIGHLIGHT) {
2787 int type = event->type, val = event->val;
2789 if (type == MOUSEPAN) {
2790 ui_pan_to_scroll(event, &type, &val);
2793 /* XXX hardcoded keymap check.... */
2794 if (type == MOUSEPAN && event->alt)
2795 retval = WM_UI_HANDLER_BREAK; /* allow accumulating values, otherwise scrolling gets preference */
2796 else if (type == WHEELDOWNMOUSE && event->alt) {
2797 mx = but->rect.xmin;
2800 else if (type == WHEELUPMOUSE && event->alt) {
2801 mx = but->rect.xmax;
2804 else if (event->val == KM_PRESS) {
2805 if (ELEM3(event->type, LEFTMOUSE, PADENTER, RETKEY) && event->ctrl) {
2806 button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING);
2807 retval = WM_UI_HANDLER_BREAK;
2809 else if (event->type == LEFTMOUSE) {
2810 data->dragstartx = data->draglastx = ui_is_a_warp_but(but) ? screen_mx : mx;
2811 button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
2812 retval = WM_UI_HANDLER_BREAK;
2814 else if (ELEM(event->type, PADENTER, RETKEY) && event->val == KM_PRESS) {
2817 else if (event->type == MINUSKEY && event->val == KM_PRESS) {
2818 button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
2819 data->value = -data->value;
2820 button_activate_state(C, but, BUTTON_STATE_EXIT);
2821 retval = WM_UI_HANDLER_BREAK;
2826 else if (data->state == BUTTON_STATE_NUM_EDITING) {
2827 if (event->type == ESCKEY) {
2828 data->cancel = TRUE;
2829 data->escapecancel = TRUE;
2830 button_activate_state(C, but, BUTTON_STATE_EXIT);
2832 else if (event->type == LEFTMOUSE && event->val != KM_PRESS) {
2833 if (data->dragchange)
2834 button_activate_state(C, but, BUTTON_STATE_EXIT);
2838 else if (event->type == MOUSEMOVE) {
2843 if (event->shift) fac /= 10.0f;
2844 if (event->alt) fac /= 20.0f;
2846 snap = (event->ctrl) ? (event->shift) ? 2 : 1 : 0;
2848 if (ui_numedit_but_NUM(but, data, fac, snap, (ui_is_a_warp_but(but) ? screen_mx : mx)))
2849 ui_numedit_apply(C, block, but, data);
2851 retval = WM_UI_HANDLER_BREAK;
2853 else if (data->state == BUTTON_STATE_TEXT_EDITING) {
2854 ui_do_but_textedit(C, block, but, data, event);
2855 retval = WM_UI_HANDLER_BREAK;
2857 else if (data->state == BUTTON_STATE_TEXT_SELECTING) {
2858 ui_do_but_textedit_select(C, block, but, data, event);
2859 retval = WM_UI_HANDLER_BREAK;
2863 /* we can click on the side arrows to increment/decrement,
2864 * or click inside to edit the value directly */
2865 float tempf, softmin, softmax;
2868 softmin = but->softmin;
2869 softmax = but->softmax;
2871 if (!ui_is_but_float(but)) {
2872 if (mx < (but->rect.xmin + BLI_rctf_size_x(&but->rect) / 3 - 3)) {
2873 button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
2875 temp = (int)data->value - 1;
2876 if (temp >= softmin && temp <= softmax)
2877 data->value = (double)temp;
2879 data->cancel = TRUE;
2881 button_activate_state(C, but, BUTTON_STATE_EXIT);
2883 else if (mx > (but->rect.xmin + (2 * BLI_rctf_size_x(&but->rect) / 3) + 3)) {
2884 button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
2886 temp = (int)data->value + 1;
2887 if (temp >= softmin && temp <= softmax)
2888 data->value = (double)temp;
2890 data->cancel = TRUE;
2892 button_activate_state(C, but, BUTTON_STATE_EXIT);
2895 button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING);
2899 if (mx < (but->rect.xmin + BLI_rctf_size_x(&but->rect) / 3 - 3)) {
2900 button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
2902 tempf = (float)data->value - 0.01f * but->a1;
2903 if (tempf < softmin) tempf = softmin;
2904 data->value = tempf;
2906 button_activate_state(C, but, BUTTON_STATE_EXIT);
2908 else if (mx > but->rect.xmin + (2 * (BLI_rctf_size_x(&but->rect) / 3) + 3)) {
2909 button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
2911 tempf = (float)data->value + 0.01f * but->a1;
2912 if (tempf > softmax) tempf = softmax;
2913 data->value = tempf;
2915 button_activate_state(C, but, BUTTON_STATE_EXIT);
2918 button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING);
2922 retval = WM_UI_HANDLER_BREAK;
2928 static bool ui_numedit_but_SLI(uiBut *but, uiHandleButtonData *data,
2929 const bool is_horizontal, const bool shift, const bool ctrl, int mx)
2931 float deler, f, tempf, softmin, softmax, softrange;
2933 bool changed = false;
2935 /* note, 'offs' is really from the widget drawing rounded corners see 'widget_numslider' */
2938 softmin = but->softmin;
2939 softmax = but->softmax;
2940 softrange = softmax - softmin;
2942 /* yes, 'mx' as both x/y is intentional */
2943 ui_mouse_scale_warp(data, mx, mx, &mx_fl, &my_fl, shift);
2945 if (but->type == NUMSLI) {
2946 offs = (BLI_rctf_size_y(&but->rect) / 2.0f) * but->aspect;
2947 deler = BLI_rctf_size_x(&but->rect) - offs;
2949 else if (but->type == HSVSLI) {
2950 offs = (BLI_rctf_size_y(&but->rect) / 2.0f) * but->aspect;
2951 deler = (BLI_rctf_size_x(&but->rect) / 2.0f) - offs;
2953 else if (but->type == SCROLL) {
2954 const float size = (is_horizontal) ? BLI_rctf_size_x(&but->rect) : -BLI_rctf_size_y(&but->rect);
2955 deler = size * (but->softmax - but->softmin) / (but->softmax - but->softmin + but->a1);
2959 offs = (BLI_rctf_size_y(&but->rect) / 2.0f) * but->aspect;
2960 deler = (BLI_rctf_size_x(&but->rect) - offs);
2963 f = (mx_fl - data->dragstartx) / deler + data->dragfstart;
2964 CLAMP(f, 0.0f, 1.0f);
2967 /* deal with mouse correction */
2968 #ifdef USE_CONT_MOUSE_CORRECT
2969 if (ui_is_a_warp_but(but)) {
2970 /* OK but can go outside bounds */
2971 if (is_horizontal) {
2972 data->ungrab_mval[0] = (but->rect.xmin + offs / but->aspect) + (f * deler);
2973 data->ungrab_mval[1] = BLI_rctf_cent_y(&but->rect);
2976 data->ungrab_mval[1] = (but->rect.ymin + offs / but->aspect) + (f * deler);
2977 data->ungrab_mval[0] = BLI_rctf_cent_x(&but->rect);
2979 BLI_rctf_clamp_pt_v(&but->rect, data->ungrab_mval);