Skip to content

Commit 4569a70

Browse files
authored
Merge pull request #21512 from greglucas/macosx-keypress
MNT: Add modifier key press handling to macosx backend
2 parents d536acf + 7819b0e commit 4569a70

File tree

2 files changed

+120
-57
lines changed

2 files changed

+120
-57
lines changed

doc/users/explain/event_handling.rst

Lines changed: 22 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -82,29 +82,28 @@ Event name Class Description
8282
you may encounter inconsistencies between the different user interface
8383
toolkits that Matplotlib works with. This is due to inconsistencies/limitations
8484
of the user interface toolkit. The following table shows some basic examples of
85-
what you may expect to receive as key(s) from the different user interface toolkits,
86-
where a comma separates different keys:
87-
88-
============== ============================= ============================== ============================== ============================== ==============================
89-
Key(s) Pressed WxPython Qt WebAgg Gtk Tkinter
90-
============== ============================= ============================== ============================== ============================== ==============================
91-
Shift+2 shift, shift+2 shift, " shift, " shift, " shift, "
92-
Shift+F1 shift, shift+f1 shift, shift+f1 shift, shift+f1 shift, shift+f1 shift, shift+f1
93-
Shift shift shift shift shift shift
94-
Control control control control control control
95-
Alt alt alt alt alt alt
96-
AltGr Nothing Nothing alt iso_level3_shift iso_level3_shift
97-
CapsLock caps_lock caps_lock caps_lock caps_lock caps_lock
98-
A a a A A A
99-
a a a a a a
100-
Shift+a shift, A shift, A shift, A shift, A shift, A
101-
Shift+A shift, A shift, A shift, a shift, a shift, a
102-
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
103-
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
104-
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
105-
F1 f1 f1 f1 f1 f1
106-
Ctrl+F1 control, ctrl+f1 control, ctrl+f1 control, ctrl+f1 control, ctrl+f1 control, ctrl+f1
107-
============== ============================= ============================== ============================== ============================== ==============================
85+
what you may expect to receive as key(s) (using a QWERTY keyboard layout)
86+
from the different user interface toolkits, where a comma separates different keys:
87+
88+
================ ============================= ============================== ============================== ============================== ============================== ===================================
89+
Key(s) Pressed WxPython Qt WebAgg Gtk Tkinter macosx
90+
================ ============================= ============================== ============================== ============================== ============================== ===================================
91+
Shift+2 shift, shift+2 shift, @ shift, @ shift, @ shift, @ shift, @
92+
Shift+F1 shift, shift+f1 shift, shift+f1 shift, shift+f1 shift, shift+f1 shift, shift+f1 shift, shift+f1
93+
Shift shift shift shift shift shift shift
94+
Control control control control control control control
95+
Alt alt alt alt alt alt alt
96+
AltGr Nothing Nothing alt iso_level3_shift iso_level3_shift
97+
CapsLock caps_lock caps_lock caps_lock caps_lock caps_lock caps_lock
98+
CapsLock+a caps_lock, a caps_lock, a caps_lock, A caps_lock, A caps_lock, A caps_lock, a
99+
a a a a a a a
100+
Shift+a shift, A shift, A shift, A shift, A shift, A shift, A
101+
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
102+
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
103+
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
104+
F1 f1 f1 f1 f1 f1 f1
105+
Ctrl+F1 control, ctrl+f1 control, ctrl+f1 control, ctrl+f1 control, ctrl+f1 control, ctrl+f1 control, Nothing
106+
================ ============================= ============================== ============================== ============================== ============================== ===================================
108107

109108
Matplotlib attaches some keypress callbacks by default for interactivity; they
110109
are documented in the :ref:`key-event-handling` section.

src/_macosx.m

Lines changed: 98 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,20 @@
6262
Needed to know when to stop the NSApp */
6363
static long FigureWindowCount = 0;
6464

65+
/* Keep track of modifier key states for flagsChanged
66+
to keep track of press vs release */
67+
static bool lastCommand = false;
68+
static bool lastControl = false;
69+
static bool lastShift = false;
70+
static bool lastOption = false;
71+
static bool lastCapsLock = false;
72+
/* Keep track of whether this specific key modifier was pressed or not */
73+
static bool keyChangeCommand = false;
74+
static bool keyChangeControl = false;
75+
static bool keyChangeShift = false;
76+
static bool keyChangeOption = false;
77+
static bool keyChangeCapsLock = false;
78+
6579
/* -------------------------- Helper function ---------------------------- */
6680

6781
static void
@@ -247,7 +261,7 @@ - (void)keyDown:(NSEvent*)event;
247261
- (void)keyUp:(NSEvent*)event;
248262
- (void)scrollWheel:(NSEvent *)event;
249263
- (BOOL)acceptsFirstResponder;
250-
//- (void)flagsChanged:(NSEvent*)event;
264+
- (void)flagsChanged:(NSEvent*)event;
251265
@end
252266

253267
/* ---------------------------- Python classes ---------------------------- */
@@ -1623,26 +1637,45 @@ - (const char*)convertKeyEvent:(NSEvent*)event
16231637
];
16241638

16251639
NSMutableString* returnkey = [NSMutableString string];
1626-
if ([event modifierFlags] & NSEventModifierFlagControl) {
1627-
[returnkey appendString:@"ctrl+" ];
1628-
}
1629-
if ([event modifierFlags] & NSEventModifierFlagOption) {
1640+
if (keyChangeControl) {
1641+
// When control is the key that was pressed, return the full word
1642+
[returnkey appendString:@"control+"];
1643+
} else if (([event modifierFlags] & NSEventModifierFlagControl)) {
1644+
// If control is already pressed, return the shortened version
1645+
[returnkey appendString:@"ctrl+"];
1646+
}
1647+
if (([event modifierFlags] & NSEventModifierFlagOption) || keyChangeOption) {
16301648
[returnkey appendString:@"alt+" ];
16311649
}
1632-
if ([event modifierFlags] & NSEventModifierFlagCommand) {
1650+
if (([event modifierFlags] & NSEventModifierFlagCommand) || keyChangeCommand) {
16331651
[returnkey appendString:@"cmd+" ];
16341652
}
1653+
// Don't print caps_lock unless it was the key that got pressed
1654+
if (keyChangeCapsLock) {
1655+
[returnkey appendString:@"caps_lock+" ];
1656+
}
16351657

1636-
unichar uc = [[event charactersIgnoringModifiers] characterAtIndex:0];
1637-
NSString* specialchar = [specialkeymappings objectForKey:[NSNumber numberWithUnsignedLong:uc]];
1638-
if (specialchar) {
1639-
if ([event modifierFlags] & NSEventModifierFlagShift) {
1640-
[returnkey appendString:@"shift+" ];
1658+
// flagsChanged event can't handle charactersIgnoringModifiers
1659+
// because it was a modifier key that was pressed/released
1660+
if (event.type != NSEventTypeFlagsChanged) {
1661+
unichar uc = [[event charactersIgnoringModifiers] characterAtIndex:0];
1662+
NSString *specialchar = [specialkeymappings objectForKey:[NSNumber numberWithUnsignedLong:uc]];
1663+
if (specialchar) {
1664+
if (([event modifierFlags] & NSEventModifierFlagShift) || keyChangeShift) {
1665+
[returnkey appendString:@"shift+"];
1666+
}
1667+
[returnkey appendString:specialchar];
1668+
} else {
1669+
[returnkey appendString:[event charactersIgnoringModifiers]];
1670+
}
1671+
} else {
1672+
if (([event modifierFlags] & NSEventModifierFlagShift) || keyChangeShift) {
1673+
[returnkey appendString:@"shift+"];
16411674
}
1642-
[returnkey appendString:specialchar];
1675+
// Since it was a modifier event trim the final character of the string
1676+
// because we added in "+" earlier
1677+
[returnkey setString: [returnkey substringToIndex:[returnkey length] - 1]];
16431678
}
1644-
else
1645-
[returnkey appendString:[event charactersIgnoringModifiers]];
16461679

16471680
return [returnkey UTF8String];
16481681
}
@@ -1711,29 +1744,60 @@ - (BOOL)acceptsFirstResponder
17111744
return YES;
17121745
}
17131746

1714-
/* This is all wrong. Address of pointer is being passed instead of pointer, keynames don't
1715-
match up with what the front-end and does the front-end even handle modifier keys by themselves?
1747+
// flagsChanged gets called whenever a modifier key is pressed OR released
1748+
// so we need to handle both cases here
1749+
- (void)flagsChanged:(NSEvent *)event
1750+
{
1751+
bool isPress = false; // true if key is pressed, false if key was released
1752+
1753+
// Each if clause tests the two cases for each of the keys we can handle
1754+
// 1. If the modifier flag "command key" is pressed and it was not previously
1755+
// 2. If the modifier flag "command key" is not pressed and it was previously
1756+
// !! converts the result of the bitwise & operator to a logical boolean,
1757+
// which allows us to then bitwise xor (^) the result with a boolean (lastCommand).
1758+
if (!!([event modifierFlags] & NSEventModifierFlagCommand) ^ lastCommand) {
1759+
// Command pressed/released
1760+
lastCommand = !lastCommand;
1761+
keyChangeCommand = true;
1762+
isPress = lastCommand;
1763+
} else if (!!([event modifierFlags] & NSEventModifierFlagControl) ^ lastControl) {
1764+
// Control pressed/released
1765+
lastControl = !lastControl;
1766+
keyChangeControl = true;
1767+
isPress = lastControl;
1768+
} else if (!!([event modifierFlags] & NSEventModifierFlagShift) ^ lastShift) {
1769+
// Shift pressed/released
1770+
lastShift = !lastShift;
1771+
keyChangeShift = true;
1772+
isPress = lastShift;
1773+
} else if (!!([event modifierFlags] & NSEventModifierFlagOption) ^ lastOption) {
1774+
// Option pressed/released
1775+
lastOption = !lastOption;
1776+
keyChangeOption = true;
1777+
isPress = lastOption;
1778+
} else if (!!([event modifierFlags] & NSEventModifierFlagCapsLock) ^ lastCapsLock) {
1779+
// Capslock pressed/released
1780+
lastCapsLock = !lastCapsLock;
1781+
keyChangeCapsLock = true;
1782+
isPress = lastCapsLock;
1783+
} else {
1784+
// flag we don't handle
1785+
return;
1786+
}
17161787

1717-
- (void)flagsChanged:(NSEvent*)event
1718-
{
1719-
const char *s = NULL;
1720-
if (([event modifierFlags] & NSControlKeyMask) == NSControlKeyMask)
1721-
s = "control";
1722-
else if (([event modifierFlags] & NSShiftKeyMask) == NSShiftKeyMask)
1723-
s = "shift";
1724-
else if (([event modifierFlags] & NSAlternateKeyMask) == NSAlternateKeyMask)
1725-
s = "alt";
1726-
else return;
1727-
PyGILState_STATE gstate = PyGILState_Ensure();
1728-
PyObject* result = PyObject_CallMethod(canvas, "key_press_event", "s", &s);
1729-
if (result)
1730-
Py_DECREF(result);
1731-
else
1732-
PyErr_Print();
1788+
if (isPress) {
1789+
[self keyDown:event];
1790+
} else {
1791+
[self keyUp:event];
1792+
}
17331793

1734-
PyGILState_Release(gstate);
1794+
// Reset the state for the key changes after handling the event
1795+
keyChangeCommand = false;
1796+
keyChangeControl = false;
1797+
keyChangeShift = false;
1798+
keyChangeOption = false;
1799+
keyChangeCapsLock = false;
17351800
}
1736-
*/
17371801
@end
17381802

17391803
static PyObject*

0 commit comments

Comments
 (0)