1 #include "controllers/keyboard/keyboardeventfilter.h"
2
3 #include <QEvent>
4 #include <QKeyEvent>
5 #include <QList>
6 #include <QtDebug>
7
8 #include "control/controlobject.h"
9 #include "moc_keyboardeventfilter.cpp"
10 #include "util/cmdlineargs.h"
11
KeyboardEventFilter(ConfigObject<ConfigValueKbd> * pKbdConfigObject,QObject * parent,const char * name)12 KeyboardEventFilter::KeyboardEventFilter(ConfigObject<ConfigValueKbd>* pKbdConfigObject,
13 QObject* parent,
14 const char* name)
15 : QObject(parent),
16 m_pKbdConfigObject(nullptr) {
17 setObjectName(name);
18 setKeyboardConfig(pKbdConfigObject);
19 }
20
~KeyboardEventFilter()21 KeyboardEventFilter::~KeyboardEventFilter() {
22 }
23
eventFilter(QObject *,QEvent * e)24 bool KeyboardEventFilter::eventFilter(QObject*, QEvent* e) {
25 if (e->type() == QEvent::FocusOut) {
26 // If we lose focus, we need to clear out the active key list
27 // because we might not get Key Release events.
28 m_qActiveKeyList.clear();
29 } else if (e->type() == QEvent::KeyPress) {
30 QKeyEvent* ke = (QKeyEvent *)e;
31
32 #ifdef __APPLE__
33 // On Mac OSX the nativeScanCode is empty (const 1) http://doc.qt.nokia.com/4.7/qkeyevent.html#nativeScanCode
34 // We may loose the release event if a the shift key is pressed later
35 // and there is character shift like "1" -> "!"
36 int keyId = ke->key();
37 #else
38 int keyId = ke->nativeScanCode();
39 #endif
40 //qDebug() << "KeyPress event =" << ke->key() << "KeyId =" << keyId;
41
42 // Run through list of active keys to see if the pressed key is already active
43 // Just for returning true if we are consuming this key event
44
45 foreach (const KeyDownInformation& keyDownInfo, m_qActiveKeyList) {
46 if (keyDownInfo.keyId == keyId) {
47 return true;
48 }
49 }
50
51 QKeySequence ks = getKeySeq(ke);
52 if (!ks.isEmpty()) {
53 ConfigValueKbd ksv(ks);
54 // Check if a shortcut is defined
55 bool result = false;
56 // using const_iterator here is faster than QMultiHash::values()
57 for (auto it = m_keySequenceToControlHash.constFind(ksv);
58 it != m_keySequenceToControlHash.constEnd() && it.key() == ksv; ++it) {
59 const ConfigKey& configKey = it.value();
60 if (configKey.group != "[KeyboardShortcuts]") {
61 ControlObject* control = ControlObject::getControl(configKey);
62 if (control) {
63 //qDebug() << configKey << "MIDI_NOTE_ON" << 1;
64 // Add key to active key list
65 m_qActiveKeyList.append(KeyDownInformation(
66 keyId, ke->modifiers(), control));
67 // Since setting the value might cause us to go down
68 // a route that would eventually clear the active
69 // key list, do that last.
70 control->setValueFromMidi(MIDI_NOTE_ON, 1);
71 result = true;
72 } else {
73 qDebug() << "Warning: Keyboard key is configured for nonexistent control:"
74 << configKey.group << configKey.item;
75 }
76 }
77 }
78 return result;
79 }
80 } else if (e->type()==QEvent::KeyRelease) {
81 QKeyEvent* ke = (QKeyEvent*)e;
82
83 #ifdef __APPLE__
84 // On Mac OSX the nativeScanCode is empty
85 int keyId = ke->key();
86 #else
87 int keyId = ke->nativeScanCode();
88 #endif
89 bool autoRepeat = ke->isAutoRepeat();
90
91 //qDebug() << "KeyRelease event =" << ke->key() << "AutoRepeat =" << autoRepeat << "KeyId =" << keyId;
92
93 int clearModifiers = 0;
94 #ifdef __APPLE__
95 // OS X apparently doesn't deliver KeyRelease events when you are
96 // holding Ctrl. So release all key-presses that were triggered with
97 // Ctrl.
98 if (ke->key() == Qt::Key_Control) {
99 clearModifiers = Qt::ControlModifier;
100 }
101 #endif
102
103 bool matched = false;
104 // Run through list of active keys to see if the released key is active
105 for (int i = m_qActiveKeyList.size() - 1; i >= 0; i--) {
106 const KeyDownInformation& keyDownInfo = m_qActiveKeyList[i];
107 ControlObject* pControl = keyDownInfo.pControl;
108 if (keyDownInfo.keyId == keyId ||
109 (clearModifiers > 0 && keyDownInfo.modifiers == clearModifiers)) {
110 if (!autoRepeat) {
111 //qDebug() << pControl->getKey() << "MIDI_NOTE_OFF" << 0;
112 pControl->setValueFromMidi(MIDI_NOTE_OFF, 0);
113 m_qActiveKeyList.removeAt(i);
114 }
115 // Due to the modifier clearing workaround we might match multiple keys for
116 // release.
117 matched = true;
118 }
119 }
120 return matched;
121 } else if (e->type() == QEvent::KeyboardLayoutChange) {
122 // This event is not fired on ubunty natty, why?
123 // TODO(XXX): find a way to support KeyboardLayoutChange Bug #997811
124 //qDebug() << "QEvent::KeyboardLayoutChange";
125 }
126 return false;
127 }
128
getKeySeq(QKeyEvent * e)129 QKeySequence KeyboardEventFilter::getKeySeq(QKeyEvent* e) {
130 QString modseq;
131 QKeySequence k;
132
133 // TODO(XXX) check if we may simply return QKeySequence(e->modifiers()+e->key())
134
135 if (e->modifiers() & Qt::ShiftModifier) {
136 modseq += "Shift+";
137 }
138
139 if (e->modifiers() & Qt::ControlModifier) {
140 modseq += "Ctrl+";
141 }
142
143 if (e->modifiers() & Qt::AltModifier) {
144 modseq += "Alt+";
145 }
146
147 if (e->modifiers() & Qt::MetaModifier) {
148 modseq += "Meta+";
149 }
150
151 if (e->key() >= 0x01000020 && e->key() <= 0x01000023) {
152 // Do not act on Modifier only
153 // avoid returning "khmer vowel sign ie (U+17C0)"
154 return k;
155 }
156
157 QString keyseq = QKeySequence(e->key()).toString();
158 k = QKeySequence(modseq + keyseq);
159
160 if (CmdlineArgs::Instance().getDeveloper()) {
161 qDebug() << "keyboard press: " << k.toString();
162 }
163 return k;
164 }
165
setKeyboardConfig(ConfigObject<ConfigValueKbd> * pKbdConfigObject)166 void KeyboardEventFilter::setKeyboardConfig(ConfigObject<ConfigValueKbd>* pKbdConfigObject) {
167 // Keyboard configs are a surjection from ConfigKey to key sequence. We
168 // invert the mapping to create an injection from key sequence to
169 // ConfigKey. This allows a key sequence to trigger multiple controls in
170 // Mixxx.
171 m_keySequenceToControlHash = pKbdConfigObject->transpose();
172 m_pKbdConfigObject = pKbdConfigObject;
173 }
174
getKeyboardConfig()175 ConfigObject<ConfigValueKbd>* KeyboardEventFilter::getKeyboardConfig() {
176 return m_pKbdConfigObject;
177 }
178