diff options
author | Julian Eisel <eiseljulian@gmail.com> | 2015-04-03 17:21:22 +0300 |
---|---|---|
committer | Julian Eisel <eiseljulian@gmail.com> | 2015-04-03 17:21:22 +0300 |
commit | 53a3850a8a05249942a0c4a16060e9491456af02 (patch) | |
tree | 39d46eeeba6e5570fadf0778942f2c6f56591526 /source/blender/windowmanager | |
parent | b444887054297d83c560e860219a5c598d390935 (diff) |
Sticky Keys backend
Design task: T42339
Differential Revision: D840
Initial implementation proposal: T41867
Short description:
With this we can distinguish between holding and tabbing a key. Useful
is this if we want to assign to operators to a single shortcut. If two
operators are assigned to one shortcut, we call this a sticky key.
More info is accessible through the design task and the diff.
A few people that were involved with this:
* Sean Olson for stressing me with this burden ;) - It is his enthusiasm
that pushed me forward to get this done
* Campbell and Antony for the code and design review
* Ton for the design review
* All the other people that gave feedback on the patch and helped to
make this possible
A big "Thank You" for you all!
Diffstat (limited to 'source/blender/windowmanager')
-rw-r--r-- | source/blender/windowmanager/WM_types.h | 17 | ||||
-rw-r--r-- | source/blender/windowmanager/intern/wm_event_system.c | 134 | ||||
-rw-r--r-- | source/blender/windowmanager/intern/wm_keymap.c | 4 | ||||
-rw-r--r-- | source/blender/windowmanager/intern/wm_operators.c | 2 | ||||
-rw-r--r-- | source/blender/windowmanager/intern/wm_window.c | 76 |
5 files changed, 150 insertions, 83 deletions
diff --git a/source/blender/windowmanager/WM_types.h b/source/blender/windowmanager/WM_types.h index e716cd6b779..366d3218176 100644 --- a/source/blender/windowmanager/WM_types.h +++ b/source/blender/windowmanager/WM_types.h @@ -180,9 +180,11 @@ enum { #define KM_NOTHING 0 #define KM_PRESS 1 #define KM_RELEASE 2 -#define KM_CLICK 3 -#define KM_DBL_CLICK 4 +/* clicktype */ +#define KM_CLICK 3 /* clicked key (click_time <= U.click_timeout) */ +#define KM_DBL_CLICK 4 /* double click - keep at 4 to avoid breakage with older key configs */ +#define KM_HOLD 5 /* held key (click_time > U.click_timeout) */ /* ************** UI Handler ***************** */ @@ -427,7 +429,9 @@ typedef struct wmEvent { short type; /* event code itself (short, is also in keymap) */ short val; /* press, release, scrollvalue */ + short click_type; /* click, hold or double click */ int x, y; /* mouse pointer position, screen coord */ + double click_time; /* the time since keypress started */ int mval[2]; /* region mouse position, name convention pre 2.5 :) */ char utf8_buf[6]; /* from, ghost if utf8 is enabled for the platform, * BLI_str_utf8_size() must _always_ be valid, check @@ -435,20 +439,19 @@ typedef struct wmEvent { char ascii; /* from ghost, fallback if utf8 isn't set */ char pad; - /* previous state, used for double click and the 'click' */ + bool is_key_pressed; /* is a (non-modifier) key is pressed? (keyboard, mouse, NDOF, ...) */ + + /* previous state, used for clicktype */ short prevtype; short prevval; int prevx, prevy; - double prevclicktime; + double prevclick_time; int prevclickx, prevclicky; /* modifier states */ short shift, ctrl, alt, oskey; /* oskey is apple or windowskey, value denotes order of pressed */ short keymodifier; /* rawkey modifier */ - /* set in case a KM_PRESS went by unhandled */ - short check_click; - /* keymap item, set by handler (weak?) */ const char *keymap_idname; diff --git a/source/blender/windowmanager/intern/wm_event_system.c b/source/blender/windowmanager/intern/wm_event_system.c index e8004297011..ce9a729a7a9 100644 --- a/source/blender/windowmanager/intern/wm_event_system.c +++ b/source/blender/windowmanager/intern/wm_event_system.c @@ -404,7 +404,7 @@ static int wm_handler_ui_call(bContext *C, wmEventHandler *handler, wmEvent *eve * to make the DBL_CLICK conversion work, we just don't send this to UI, except mouse clicks */ if (((handler->flag & WM_HANDLER_ACCEPT_DBL_CLICK) == 0) && (event->type != LEFTMOUSE) && - (event->val == KM_DBL_CLICK)) + (event->click_type == KM_DBL_CLICK)) { return WM_HANDLER_CONTINUE; } @@ -1442,6 +1442,7 @@ int WM_userdef_event_map(int kmitype) } +/* XXX rename to something more descriptive like wm_event_is_keymapitem_matching and use bool */ static int wm_eventmatch(wmEvent *winevent, wmKeyMapItem *kmi) { int kmitype = WM_userdef_event_map(kmi->type); @@ -1458,10 +1459,13 @@ static int wm_eventmatch(wmEvent *winevent, wmKeyMapItem *kmi) if (kmitype != KM_ANY) if (winevent->type != kmitype) return 0; - + + /* KM_ANY excludes click_type events - filter them out */ + if (kmi->val == KM_ANY && winevent->click_type) return 0; + if (kmi->val != KM_ANY) - if (winevent->val != kmi->val) return 0; - + if (!ELEM(kmi->val, winevent->val, winevent->click_type)) return 0; + /* modifiers also check bits, so it allows modifier order */ if (kmi->shift != KM_ANY) if (winevent->shift != kmi->shift && !(winevent->shift & kmi->shift)) return 0; @@ -1508,8 +1512,9 @@ static void wm_event_modalkeymap(const bContext *C, wmOperator *op, wmEvent *eve /* modal keymap checking returns handled events fine, but all hardcoded modal * handling typically swallows all events (OPERATOR_RUNNING_MODAL). * This bypass just disables support for double clicks in hardcoded modal handlers */ - if (event->val == KM_DBL_CLICK) { + if (event->click_type == KM_DBL_CLICK) { event->val = KM_PRESS; + event->click_type = 0; *dbl_click_disabled = true; } } @@ -1541,9 +1546,9 @@ static void wm_event_modalmap_end(wmEvent *event, bool dbl_click_disabled) event->val = event->prevval; event->prevval = 0; } - else if (dbl_click_disabled) - event->val = KM_DBL_CLICK; - + else if (dbl_click_disabled) { + event->click_type = KM_DBL_CLICK; + } } /* Warning: this function removes a modal handler, when finished */ @@ -2021,47 +2026,21 @@ static int wm_handlers_do(bContext *C, wmEvent *event, ListBase *handlers) /* test for CLICK events */ if (wm_action_not_handled(action)) { wmWindow *win = CTX_wm_window(C); - - /* eventstate stores if previous event was a KM_PRESS, in case that - * wasn't handled, the KM_RELEASE will become a KM_CLICK */ - - if (win && event->val == KM_PRESS) { - win->eventstate->check_click = true; - } - - if (win && win->eventstate->prevtype == event->type) { - - if ((event->val == KM_RELEASE) && - (win->eventstate->prevval == KM_PRESS) && - (win->eventstate->check_click == true)) - { - event->val = KM_CLICK; - - if (G.debug & (G_DEBUG_HANDLERS)) { - printf("%s: handling CLICK\n", __func__); - } - - action |= wm_handlers_do_intern(C, event, handlers); - event->val = KM_RELEASE; - } - else if (event->val == KM_DBL_CLICK) { + /* XXX check if those double click hacks can be removed/improved since click_type was introduced */ + if (win && win->eventstate->prevtype == event->type) { + if (event->click_type == KM_DBL_CLICK) { event->val = KM_PRESS; + event->click_type = 0; action |= wm_handlers_do_intern(C, event, handlers); /* revert value if not handled */ if (wm_action_not_handled(action)) { - event->val = KM_DBL_CLICK; + event->click_type = KM_DBL_CLICK; } } } } - else { - wmWindow *win = CTX_wm_window(C); - - if (win) - win->eventstate->check_click = 0; - } } return action; @@ -3005,22 +2984,50 @@ static wmWindow *wm_event_cursor_other_windows(wmWindowManager *wm, wmWindow *wi return NULL; } -static bool wm_event_is_double_click(wmEvent *event, wmEvent *event_state) +/** + * Clicktype test + * + * We have 3 different click_types: #KM_CLICK, #KM_HOLD# and #KM_DBL_CLICK. + * + * Time is used to determine, what to send. It works as follows: + * - #KM_RELEASE && time since first #KM_PRESS < U.click_timeout --> send #KM_CLICK + * - #KM_PRESS && time since first #KM_PRESS > U.click_timeout --> send #KM_HOLD + * - #KM_PRESS after a #KM_RELEASE && time since previous #KM_PRESS < U.dbl_click_time --> send #KM_DBL_CLICK + * + * \note: only #KM_DBL_CLICK is handled here, rest in #wm_window_event_clicktype_init (wm_window.c) + */ +static void wm_event_clicktype_init(wmWindow *win, wmEvent *event, wmEvent *event_state) { - if ((event->type == event_state->prevtype) && - (event_state->prevval == KM_RELEASE) && - (event->val == KM_PRESS)) + short click_type = 0; + + if ((event->val == KM_PRESS) && + (event_state->prevval != KM_PRESS || event->prevtype != win->eventstate->prevtype)) + { + event_state->prevclick_time = event->click_time; + event_state->prevclickx = event->x; + event_state->prevclicky = event->y; + } + + /* double click */ + if (event->type == event_state->prevtype && + event_state->prevval == KM_RELEASE && + event->val == KM_PRESS) { if ((ISMOUSE(event->type) == false) || ((ABS(event->x - event_state->prevclickx)) <= 2 && (ABS(event->y - event_state->prevclicky)) <= 2)) { - if ((PIL_check_seconds_timer() - event_state->prevclicktime) * 1000 < U.dbl_click_time) { - return true; + if ((PIL_check_seconds_timer() - event_state->prevclick_time) * 1000 < U.dbl_click_time) { + click_type = KM_DBL_CLICK; + if (G.debug & (G_DEBUG_HANDLERS | G_DEBUG_EVENTS)) { + printf("%s Send double click event\n", __func__); + } } } } - return false; + if (click_type != event->click_type) { + event_state->click_type = event->click_type = click_type; + } } static void wm_event_add_mousemove(wmWindow *win, const wmEvent *event) @@ -3152,6 +3159,9 @@ void wm_event_add_ghostevent(wmWindowManager *wm, wmWindow *win, int type, int U evt->val = event.val; evt->type = event.type; + /* click_type */ + wm_event_clicktype_init(win, &event, evt); + if (win->active == 0) { int cx, cy; @@ -3162,18 +3172,6 @@ void wm_event_add_ghostevent(wmWindowManager *wm, wmWindow *win, int type, int U event.y = evt->y = cy; } - /* double click test */ - if (wm_event_is_double_click(&event, evt)) { - if (G.debug & (G_DEBUG_HANDLERS | G_DEBUG_EVENTS)) - printf("%s Send double click\n", __func__); - event.val = KM_DBL_CLICK; - } - if (event.val == KM_PRESS) { - evt->prevclicktime = PIL_check_seconds_timer(); - evt->prevclickx = event.x; - evt->prevclicky = event.y; - } - /* add to other window if event is there (not to both!) */ owin = wm_event_cursor_other_windows(wm, win, &event); if (owin) { @@ -3212,7 +3210,10 @@ void wm_event_add_ghostevent(wmWindowManager *wm, wmWindow *win, int type, int U /* copy to event state */ evt->val = event.val; evt->type = event.type; - + + /* clicktype */ + wm_event_clicktype_init(win, &event, evt); + /* exclude arrow keys, esc, etc from text input */ if (type == GHOST_kEventKeyUp) { event.ascii = '\0'; @@ -3278,14 +3279,6 @@ void wm_event_add_ghostevent(wmWindowManager *wm, wmWindow *win, int type, int U event.keymodifier = evt->keymodifier = 0; break; } - - /* double click test */ - /* if previous event was same type, and previous was release, and now it presses... */ - if (wm_event_is_double_click(&event, evt)) { - if (G.debug & (G_DEBUG_HANDLERS | G_DEBUG_EVENTS)) - printf("%s Send double click\n", __func__); - evt->val = event.val = KM_DBL_CLICK; - } /* this case happens on holding a key pressed, it should not generate * press events events with the same key as modifier */ @@ -3306,13 +3299,6 @@ void wm_event_add_ghostevent(wmWindowManager *wm, wmWindow *win, int type, int U G.is_break = true; } - /* double click test - only for press */ - if (event.val == KM_PRESS) { - evt->prevclicktime = PIL_check_seconds_timer(); - evt->prevclickx = event.x; - evt->prevclicky = event.y; - } - wm_event_add(win, &event); break; diff --git a/source/blender/windowmanager/intern/wm_keymap.c b/source/blender/windowmanager/intern/wm_keymap.c index 82e46c1b333..0a89ca113c4 100644 --- a/source/blender/windowmanager/intern/wm_keymap.c +++ b/source/blender/windowmanager/intern/wm_keymap.c @@ -1192,11 +1192,15 @@ int WM_keymap_item_compare(wmKeyMapItem *k1, wmKeyMapItem *k2) return 0; if (k1->val != KM_ANY && k2->val != KM_ANY) { + +#if 0 /* thanks to clicktype those shouldn't be needed anymore */ /* take click, press, release conflict into account */ if (k1->val == KM_CLICK && ELEM(k2->val, KM_PRESS, KM_RELEASE, KM_CLICK) == 0) return 0; if (k2->val == KM_CLICK && ELEM(k1->val, KM_PRESS, KM_RELEASE, KM_CLICK) == 0) return 0; +#endif + if (k1->val != k2->val) return 0; } diff --git a/source/blender/windowmanager/intern/wm_operators.c b/source/blender/windowmanager/intern/wm_operators.c index c62e82b8e43..c2cd96165f3 100644 --- a/source/blender/windowmanager/intern/wm_operators.c +++ b/source/blender/windowmanager/intern/wm_operators.c @@ -5108,7 +5108,7 @@ void wm_window_keymap(wmKeyConfig *keyconf) WM_keymap_verify_item(keymap, "WM_OT_debug_menu", DKEY, KM_PRESS, KM_ALT | KM_CTRL, 0); /* menus that can be accessed anywhere in blender */ - WM_keymap_verify_item(keymap, "WM_OT_search_menu", SPACEKEY, KM_PRESS, 0, 0); + WM_keymap_verify_item(keymap, "WM_OT_search_menu", SPACEKEY, KM_CLICK, 0, 0); WM_keymap_add_menu(keymap, "USERPREF_MT_ndof_settings", NDOF_BUTTON_MENU, KM_PRESS, 0, 0); /* Space switching */ diff --git a/source/blender/windowmanager/intern/wm_window.c b/source/blender/windowmanager/intern/wm_window.c index 4f7e5ab75b3..98766f35006 100644 --- a/source/blender/windowmanager/intern/wm_window.c +++ b/source/blender/windowmanager/intern/wm_window.c @@ -1097,6 +1097,76 @@ static int ghost_event_proc(GHOST_EventHandle evt, GHOST_TUserDataPtr C_void_ptr return 1; } +/** + * #KM_DBL_CLICK is set in wm_event_clicktype_init (wm_event_system.c) + * Normally, this should be there too, but for #KM_CLICK/#KM_HOLD, we need a + * time precision of a few milliseconds, which we can't get from there + */ +static void wm_window_event_clicktype_init(const bContext *C) +{ + wmWindowManager *wm = CTX_wm_manager(C); + + if (wm->winactive) { + wmWindow *win = wm->winactive; + wmEvent *event = win->eventstate; + short click_type = event->click_type; + + BLI_assert(event != NULL); + + if ((event->type == EVENT_NONE) || + ((event->val == KM_NOTHING) && (event->is_key_pressed == false))) + { + /* nothing needs to be done here */ + return; + } + + /* we always want click_type of last clicked button (to enable + * use with modifier keys) - unnecessary for mouse though */ + if (!ISMOUSE(event->type) && + event->val == KM_PRESS && + event->type != event->keymodifier) + { + event->is_key_pressed = false; + } + else if (event->val == KM_PRESS && !event->is_key_pressed) { + event->is_key_pressed = true; + event->click_time = PIL_check_seconds_timer(); + } + else if (event->val == KM_RELEASE && event->is_key_pressed) { + event->is_key_pressed = false; + } + else if (event->is_key_pressed == false) { + return; + } + + /* the actual test */ + if ((PIL_check_seconds_timer() - event->click_time) * 1000 <= U.click_timeout) { + /* for any reason some X11 systems send two release events triggering two KM_CLICK + * events - making the rules more strict by checking for prevval resolves this */ + if (event->val == KM_RELEASE && event->prevval != KM_RELEASE) { + click_type = KM_CLICK; + if (G.debug & (G_DEBUG_HANDLERS | G_DEBUG_EVENTS)) { + printf("%s Send click event\n", __func__); + } + } + } + else if (event->is_key_pressed) { + click_type = KM_HOLD; + if (G.debug & (G_DEBUG_HANDLERS | G_DEBUG_EVENTS)) { + printf("%s Send hold event\n", __func__); + } + + /* the event we send in this case is a "dummy" event - don't send value */ + event->val = KM_NOTHING; + } + + /* send event with new click_type */ + if (event->click_type != click_type) { + event->click_type = click_type; + wm_event_add(win, event); + } + } +} /* This timer system only gives maximum 1 timer event per redraw cycle, * to prevent queues to get overloaded. @@ -1156,7 +1226,11 @@ void wm_window_process_events(const bContext *C) if (hasevent) GHOST_DispatchEvents(g_system); - + + /* not nice to have this here, but it's the only place + * that can call it with the needed time precision */ + wm_window_event_clicktype_init(C); + hasevent |= wm_window_timer(C); /* no event, we sleep 5 milliseconds */ |