diff --git a/doc/users/explain/event_handling.rst b/doc/users/explain/event_handling.rst index 239c32a194bb..909e9f4b9ff8 100644 --- a/doc/users/explain/event_handling.rst +++ b/doc/users/explain/event_handling.rst @@ -82,29 +82,28 @@ Event name Class Description you may encounter inconsistencies between the different user interface toolkits that Matplotlib works with. This is due to inconsistencies/limitations of the user interface toolkit. The following table shows some basic examples of - what you may expect to receive as key(s) from the different user interface toolkits, - where a comma separates different keys: - - ============== ============================= ============================== ============================== ============================== ============================== - Key(s) Pressed WxPython Qt WebAgg Gtk Tkinter - ============== ============================= ============================== ============================== ============================== ============================== - Shift+2 shift, shift+2 shift, " shift, " shift, " shift, " - Shift+F1 shift, shift+f1 shift, shift+f1 shift, shift+f1 shift, shift+f1 shift, shift+f1 - Shift shift shift shift shift shift - Control control control control control control - Alt alt alt alt alt alt - AltGr Nothing Nothing alt iso_level3_shift iso_level3_shift - CapsLock caps_lock caps_lock caps_lock caps_lock caps_lock - A a a A A A - a a a a a a - Shift+a shift, A shift, A shift, A shift, A shift, A - Shift+A shift, A shift, A shift, a shift, a shift, a - Ctrl+Shift+Alt control, ctrl+shift, ctrl+alt control, ctrl+shift, ctrl+meta control, ctrl+shift, ctrl+meta control, ctrl+shift, ctrl+meta control, ctrl+shift, ctrl+meta - Ctrl+Shift+a control, ctrl+shift, ctrl+A control, ctrl+shift, ctrl+A control, ctrl+shift, ctrl+A control, ctrl+shift, ctrl+A control, ctrl+shift, ctrl+a - Ctrl+Shift+A control, ctrl+shift, ctrl+A control, ctrl+shift, ctrl+A control, ctrl+shift, ctrl+a control, ctrl+shift, ctrl+a control, ctrl+shift, ctrl+a - F1 f1 f1 f1 f1 f1 - Ctrl+F1 control, ctrl+f1 control, ctrl+f1 control, ctrl+f1 control, ctrl+f1 control, ctrl+f1 - ============== ============================= ============================== ============================== ============================== ============================== + what you may expect to receive as key(s) (using a QWERTY keyboard layout) + from the different user interface toolkits, where a comma separates different keys: + + ================ ============================= ============================== ============================== ============================== ============================== =================================== + Key(s) Pressed WxPython Qt WebAgg Gtk Tkinter macosx + ================ ============================= ============================== ============================== ============================== ============================== =================================== + Shift+2 shift, shift+2 shift, @ shift, @ shift, @ shift, @ shift, @ + Shift+F1 shift, shift+f1 shift, shift+f1 shift, shift+f1 shift, shift+f1 shift, shift+f1 shift, shift+f1 + Shift shift shift shift shift shift shift + Control control control control control control control + Alt alt alt alt alt alt alt + AltGr Nothing Nothing alt iso_level3_shift iso_level3_shift + CapsLock caps_lock caps_lock caps_lock caps_lock caps_lock caps_lock + CapsLock+a caps_lock, a caps_lock, a caps_lock, A caps_lock, A caps_lock, A caps_lock, a + a a a a a a a + Shift+a shift, A shift, A shift, A shift, A shift, A shift, A + CapsLock+Shift+a caps_lock, shift, A caps_lock, shift, A caps_lock, shift, a caps_lock, shift, a caps_lock, shift, a caps_lock, shift, A + Ctrl+Shift+Alt control, ctrl+shift, ctrl+alt control, ctrl+shift, ctrl+meta control, ctrl+shift, ctrl+meta control, ctrl+shift, ctrl+meta control, ctrl+shift, ctrl+meta control, ctrl+shift, ctrl+alt+shift + Ctrl+Shift+a control, ctrl+shift, ctrl+A control, ctrl+shift, ctrl+A control, ctrl+shift, ctrl+A control, ctrl+shift, ctrl+A control, ctrl+shift, ctrl+a control, ctrl+shift, ctrl+A + F1 f1 f1 f1 f1 f1 f1 + Ctrl+F1 control, ctrl+f1 control, ctrl+f1 control, ctrl+f1 control, ctrl+f1 control, ctrl+f1 control, Nothing + ================ ============================= ============================== ============================== ============================== ============================== =================================== Matplotlib attaches some keypress callbacks by default for interactivity; they are documented in the :ref:`key-event-handling` section. diff --git a/src/_macosx.m b/src/_macosx.m index 9bae57ea0447..5088cb865605 100755 --- a/src/_macosx.m +++ b/src/_macosx.m @@ -62,6 +62,20 @@ Needed to know when to stop the NSApp */ static long FigureWindowCount = 0; +/* Keep track of modifier key states for flagsChanged + to keep track of press vs release */ +static bool lastCommand = false; +static bool lastControl = false; +static bool lastShift = false; +static bool lastOption = false; +static bool lastCapsLock = false; +/* Keep track of whether this specific key modifier was pressed or not */ +static bool keyChangeCommand = false; +static bool keyChangeControl = false; +static bool keyChangeShift = false; +static bool keyChangeOption = false; +static bool keyChangeCapsLock = false; + /* -------------------------- Helper function ---------------------------- */ static void @@ -247,7 +261,7 @@ - (void)keyDown:(NSEvent*)event; - (void)keyUp:(NSEvent*)event; - (void)scrollWheel:(NSEvent *)event; - (BOOL)acceptsFirstResponder; -//- (void)flagsChanged:(NSEvent*)event; +- (void)flagsChanged:(NSEvent*)event; @end /* ---------------------------- Python classes ---------------------------- */ @@ -1623,26 +1637,45 @@ - (const char*)convertKeyEvent:(NSEvent*)event ]; NSMutableString* returnkey = [NSMutableString string]; - if ([event modifierFlags] & NSEventModifierFlagControl) { - [returnkey appendString:@"ctrl+" ]; - } - if ([event modifierFlags] & NSEventModifierFlagOption) { + if (keyChangeControl) { + // When control is the key that was pressed, return the full word + [returnkey appendString:@"control+"]; + } else if (([event modifierFlags] & NSEventModifierFlagControl)) { + // If control is already pressed, return the shortened version + [returnkey appendString:@"ctrl+"]; + } + if (([event modifierFlags] & NSEventModifierFlagOption) || keyChangeOption) { [returnkey appendString:@"alt+" ]; } - if ([event modifierFlags] & NSEventModifierFlagCommand) { + if (([event modifierFlags] & NSEventModifierFlagCommand) || keyChangeCommand) { [returnkey appendString:@"cmd+" ]; } + // Don't print caps_lock unless it was the key that got pressed + if (keyChangeCapsLock) { + [returnkey appendString:@"caps_lock+" ]; + } - unichar uc = [[event charactersIgnoringModifiers] characterAtIndex:0]; - NSString* specialchar = [specialkeymappings objectForKey:[NSNumber numberWithUnsignedLong:uc]]; - if (specialchar) { - if ([event modifierFlags] & NSEventModifierFlagShift) { - [returnkey appendString:@"shift+" ]; + // flagsChanged event can't handle charactersIgnoringModifiers + // because it was a modifier key that was pressed/released + if (event.type != NSEventTypeFlagsChanged) { + unichar uc = [[event charactersIgnoringModifiers] characterAtIndex:0]; + NSString *specialchar = [specialkeymappings objectForKey:[NSNumber numberWithUnsignedLong:uc]]; + if (specialchar) { + if (([event modifierFlags] & NSEventModifierFlagShift) || keyChangeShift) { + [returnkey appendString:@"shift+"]; + } + [returnkey appendString:specialchar]; + } else { + [returnkey appendString:[event charactersIgnoringModifiers]]; + } + } else { + if (([event modifierFlags] & NSEventModifierFlagShift) || keyChangeShift) { + [returnkey appendString:@"shift+"]; } - [returnkey appendString:specialchar]; + // Since it was a modifier event trim the final character of the string + // because we added in "+" earlier + [returnkey setString: [returnkey substringToIndex:[returnkey length] - 1]]; } - else - [returnkey appendString:[event charactersIgnoringModifiers]]; return [returnkey UTF8String]; } @@ -1711,29 +1744,60 @@ - (BOOL)acceptsFirstResponder return YES; } -/* This is all wrong. Address of pointer is being passed instead of pointer, keynames don't - match up with what the front-end and does the front-end even handle modifier keys by themselves? +// flagsChanged gets called whenever a modifier key is pressed OR released +// so we need to handle both cases here +- (void)flagsChanged:(NSEvent *)event +{ + bool isPress = false; // true if key is pressed, false if key was released + + // Each if clause tests the two cases for each of the keys we can handle + // 1. If the modifier flag "command key" is pressed and it was not previously + // 2. If the modifier flag "command key" is not pressed and it was previously + // !! converts the result of the bitwise & operator to a logical boolean, + // which allows us to then bitwise xor (^) the result with a boolean (lastCommand). + if (!!([event modifierFlags] & NSEventModifierFlagCommand) ^ lastCommand) { + // Command pressed/released + lastCommand = !lastCommand; + keyChangeCommand = true; + isPress = lastCommand; + } else if (!!([event modifierFlags] & NSEventModifierFlagControl) ^ lastControl) { + // Control pressed/released + lastControl = !lastControl; + keyChangeControl = true; + isPress = lastControl; + } else if (!!([event modifierFlags] & NSEventModifierFlagShift) ^ lastShift) { + // Shift pressed/released + lastShift = !lastShift; + keyChangeShift = true; + isPress = lastShift; + } else if (!!([event modifierFlags] & NSEventModifierFlagOption) ^ lastOption) { + // Option pressed/released + lastOption = !lastOption; + keyChangeOption = true; + isPress = lastOption; + } else if (!!([event modifierFlags] & NSEventModifierFlagCapsLock) ^ lastCapsLock) { + // Capslock pressed/released + lastCapsLock = !lastCapsLock; + keyChangeCapsLock = true; + isPress = lastCapsLock; + } else { + // flag we don't handle + return; + } -- (void)flagsChanged:(NSEvent*)event -{ - const char *s = NULL; - if (([event modifierFlags] & NSControlKeyMask) == NSControlKeyMask) - s = "control"; - else if (([event modifierFlags] & NSShiftKeyMask) == NSShiftKeyMask) - s = "shift"; - else if (([event modifierFlags] & NSAlternateKeyMask) == NSAlternateKeyMask) - s = "alt"; - else return; - PyGILState_STATE gstate = PyGILState_Ensure(); - PyObject* result = PyObject_CallMethod(canvas, "key_press_event", "s", &s); - if (result) - Py_DECREF(result); - else - PyErr_Print(); + if (isPress) { + [self keyDown:event]; + } else { + [self keyUp:event]; + } - PyGILState_Release(gstate); + // Reset the state for the key changes after handling the event + keyChangeCommand = false; + keyChangeControl = false; + keyChangeShift = false; + keyChangeOption = false; + keyChangeCapsLock = false; } - */ @end static PyObject*