Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJulian Eisel <eiseljulian@gmail.com>2015-04-03 17:21:22 +0300
committerJulian Eisel <eiseljulian@gmail.com>2015-04-03 17:21:22 +0300
commit53a3850a8a05249942a0c4a16060e9491456af02 (patch)
tree39d46eeeba6e5570fadf0778942f2c6f56591526 /source/blender/windowmanager
parentb444887054297d83c560e860219a5c598d390935 (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.h17
-rw-r--r--source/blender/windowmanager/intern/wm_event_system.c134
-rw-r--r--source/blender/windowmanager/intern/wm_keymap.c4
-rw-r--r--source/blender/windowmanager/intern/wm_operators.c2
-rw-r--r--source/blender/windowmanager/intern/wm_window.c76
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 */