1 #include "shortcuteventhandler.hpp"
2 
3 #include <algorithm>
4 #include <cassert>
5 
6 #include <QEvent>
7 #include <QKeyEvent>
8 #include <QMouseEvent>
9 #include <QWidget>
10 
11 #include "shortcut.hpp"
12 
13 namespace CSMPrefs
14 {
ShortcutEventHandler(QObject * parent)15     ShortcutEventHandler::ShortcutEventHandler(QObject* parent)
16         : QObject(parent)
17     {
18     }
19 
addShortcut(Shortcut * shortcut)20     void ShortcutEventHandler::addShortcut(Shortcut* shortcut)
21     {
22         // Enforced by shortcut class
23         QWidget* widget = static_cast<QWidget*>(shortcut->parent());
24 
25         // Check if widget setup is needed
26         ShortcutMap::iterator shortcutListIt = mWidgetShortcuts.find(widget);
27         if (shortcutListIt == mWidgetShortcuts.end())
28         {
29             // Create list
30             shortcutListIt = mWidgetShortcuts.insert(std::make_pair(widget, ShortcutList())).first;
31 
32             // Check if widget has a parent with shortcuts, unfortunately it is not typically set yet
33             updateParent(widget);
34 
35             // Intercept widget events
36             widget->installEventFilter(this);
37             connect(widget, SIGNAL(destroyed()), this, SLOT(widgetDestroyed()));
38         }
39 
40         // Add to list
41         shortcutListIt->second.push_back(shortcut);
42     }
43 
removeShortcut(Shortcut * shortcut)44     void ShortcutEventHandler::removeShortcut(Shortcut* shortcut)
45     {
46         // Enforced by shortcut class
47         QWidget* widget = static_cast<QWidget*>(shortcut->parent());
48 
49         ShortcutMap::iterator shortcutListIt = mWidgetShortcuts.find(widget);
50         if (shortcutListIt != mWidgetShortcuts.end())
51         {
52             shortcutListIt->second.erase(std::remove(shortcutListIt->second.begin(), shortcutListIt->second.end(), shortcut), shortcutListIt->second.end());
53         }
54     }
55 
eventFilter(QObject * watched,QEvent * event)56     bool ShortcutEventHandler::eventFilter(QObject* watched, QEvent* event)
57     {
58         // Process event
59         if (event->type() == QEvent::KeyPress)
60         {
61             QWidget* widget = static_cast<QWidget*>(watched);
62             QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
63             unsigned int mod = (unsigned int) keyEvent->modifiers();
64             unsigned int key = (unsigned int) keyEvent->key();
65 
66             if (!keyEvent->isAutoRepeat())
67                 return activate(widget, mod, key);
68         }
69         else if (event->type() == QEvent::KeyRelease)
70         {
71             QWidget* widget = static_cast<QWidget*>(watched);
72             QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
73             unsigned int mod = (unsigned int) keyEvent->modifiers();
74             unsigned int key = (unsigned int) keyEvent->key();
75 
76             if (!keyEvent->isAutoRepeat())
77                 return deactivate(widget, mod, key);
78         }
79         else if (event->type() == QEvent::MouseButtonPress)
80         {
81             QWidget* widget = static_cast<QWidget*>(watched);
82             QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(event);
83             unsigned int mod = (unsigned int) mouseEvent->modifiers();
84             unsigned int button = (unsigned int) mouseEvent->button();
85 
86             return activate(widget, mod, button);
87         }
88         else if (event->type() == QEvent::MouseButtonRelease)
89         {
90             QWidget* widget = static_cast<QWidget*>(watched);
91             QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(event);
92             unsigned int mod = (unsigned int) mouseEvent->modifiers();
93             unsigned int button = (unsigned int) mouseEvent->button();
94 
95             return deactivate(widget, mod, button);
96         }
97         else if (event->type() == QEvent::FocusOut)
98         {
99             QWidget* widget = static_cast<QWidget*>(watched);
100             ShortcutMap::iterator shortcutListIt = mWidgetShortcuts.find(widget);
101 
102             // Deactivate in case events are missed
103             for (ShortcutList::iterator it = shortcutListIt->second.begin(); it != shortcutListIt->second.end(); ++it)
104             {
105                 Shortcut* shortcut = *it;
106 
107                 shortcut->setPosition(0);
108                 shortcut->setModifierStatus(false);
109 
110                 if (shortcut->getActivationStatus() == Shortcut::AS_Regular)
111                 {
112                     shortcut->setActivationStatus(Shortcut::AS_Inactive);
113                     shortcut->signalActivated(false);
114                 }
115                 else if (shortcut->getActivationStatus() == Shortcut::AS_Secondary)
116                 {
117                     shortcut->setActivationStatus(Shortcut::AS_Inactive);
118                     shortcut->signalSecondary(false);
119                 }
120             }
121         }
122         else if (event->type() == QEvent::FocusIn)
123         {
124             QWidget* widget = static_cast<QWidget*>(watched);
125             updateParent(widget);
126         }
127 
128         return false;
129     }
130 
updateParent(QWidget * widget)131     void ShortcutEventHandler::updateParent(QWidget* widget)
132     {
133         QWidget* parent = widget->parentWidget();
134         while (parent)
135         {
136             ShortcutMap::iterator parentIt = mWidgetShortcuts.find(parent);
137             if (parentIt != mWidgetShortcuts.end())
138             {
139                 mChildParentRelations.insert(std::make_pair(widget, parent));
140                 updateParent(parent);
141                 break;
142             }
143 
144             // Check next
145             parent = parent->parentWidget();
146         }
147     }
148 
activate(QWidget * widget,unsigned int mod,unsigned int button)149     bool ShortcutEventHandler::activate(QWidget* widget, unsigned int mod, unsigned int button)
150     {
151         std::vector<std::pair<MatchResult, Shortcut*> > potentials;
152         bool used = false;
153 
154         while (widget)
155         {
156             ShortcutMap::iterator shortcutListIt = mWidgetShortcuts.find(widget);
157             assert(shortcutListIt != mWidgetShortcuts.end());
158 
159             // Find potential activations
160             for (ShortcutList::iterator it = shortcutListIt->second.begin(); it != shortcutListIt->second.end(); ++it)
161             {
162                 Shortcut* shortcut = *it;
163 
164                 if (!shortcut->isEnabled())
165                     continue;
166 
167                 if (checkModifier(mod, button, shortcut, true))
168                     used = true;
169 
170                 if (shortcut->getActivationStatus() != Shortcut::AS_Inactive)
171                     continue;
172 
173                 int pos = shortcut->getPosition();
174                 int lastPos = shortcut->getLastPosition();
175                 MatchResult result = match(mod, button, shortcut->getSequence()[pos]);
176 
177                 if (result == Matches_WithMod || result == Matches_NoMod)
178                 {
179                     if (pos < lastPos && (result == Matches_WithMod || pos > 0))
180                     {
181                         shortcut->setPosition(pos+1);
182                     }
183                     else if (pos == lastPos)
184                     {
185                         potentials.emplace_back(result, shortcut);
186                     }
187                 }
188             }
189 
190             // Move on to parent
191             WidgetMap::iterator widgetIt = mChildParentRelations.find(widget);
192             widget = (widgetIt != mChildParentRelations.end()) ? widgetIt->second : 0;
193         }
194 
195         // Only activate the best match; in exact conflicts, this will favor the first shortcut added.
196         if (!potentials.empty())
197         {
198             std::stable_sort(potentials.begin(), potentials.end(), ShortcutEventHandler::sort);
199             Shortcut* shortcut = potentials.front().second;
200 
201             if (shortcut->getModifierStatus() && shortcut->getSecondaryMode() == Shortcut::SM_Replace)
202             {
203                 shortcut->setActivationStatus(Shortcut::AS_Secondary);
204                 shortcut->signalSecondary(true);
205                 shortcut->signalSecondary();
206             }
207             else
208             {
209                 shortcut->setActivationStatus(Shortcut::AS_Regular);
210                 shortcut->signalActivated(true);
211                 shortcut->signalActivated();
212             }
213 
214             used = true;
215         }
216 
217         return used;
218     }
219 
deactivate(QWidget * widget,unsigned int mod,unsigned int button)220     bool ShortcutEventHandler::deactivate(QWidget* widget, unsigned int mod, unsigned int button)
221     {
222         const int KeyMask = 0x01FFFFFF;
223 
224         bool used = false;
225 
226         while (widget)
227         {
228             ShortcutMap::iterator shortcutListIt = mWidgetShortcuts.find(widget);
229             assert(shortcutListIt != mWidgetShortcuts.end());
230 
231             for (ShortcutList::iterator it = shortcutListIt->second.begin(); it != shortcutListIt->second.end(); ++it)
232             {
233                 Shortcut* shortcut = *it;
234 
235                 if (checkModifier(mod, button, shortcut, false))
236                     used = true;
237 
238                 int pos = shortcut->getPosition();
239                 MatchResult result = match(0, button, shortcut->getSequence()[pos] & KeyMask);
240 
241                 if (result != Matches_Not)
242                 {
243                     shortcut->setPosition(0);
244 
245                     if (shortcut->getActivationStatus() == Shortcut::AS_Regular)
246                     {
247                         shortcut->setActivationStatus(Shortcut::AS_Inactive);
248                         shortcut->signalActivated(false);
249                         used = true;
250                     }
251                     else if (shortcut->getActivationStatus() == Shortcut::AS_Secondary)
252                     {
253                         shortcut->setActivationStatus(Shortcut::AS_Inactive);
254                         shortcut->signalSecondary(false);
255                         used = true;
256                     }
257                 }
258             }
259 
260             // Move on to parent
261             WidgetMap::iterator widgetIt = mChildParentRelations.find(widget);
262             widget = (widgetIt != mChildParentRelations.end()) ? widgetIt->second : 0;
263         }
264 
265         return used;
266     }
267 
checkModifier(unsigned int mod,unsigned int button,Shortcut * shortcut,bool activate)268     bool ShortcutEventHandler::checkModifier(unsigned int mod, unsigned int button, Shortcut* shortcut, bool activate)
269     {
270         if (!shortcut->isEnabled() || !shortcut->getModifier() || shortcut->getSecondaryMode() == Shortcut::SM_Ignore ||
271             shortcut->getModifierStatus() == activate)
272             return false;
273 
274         MatchResult result = match(mod, button, shortcut->getModifier());
275         bool used = false;
276 
277         if (result != Matches_Not)
278         {
279             shortcut->setModifierStatus(activate);
280 
281             if (shortcut->getSecondaryMode() == Shortcut::SM_Detach)
282             {
283                 if (activate)
284                 {
285                     shortcut->signalSecondary(true);
286                     shortcut->signalSecondary();
287                 }
288                 else
289                 {
290                     shortcut->signalSecondary(false);
291                 }
292             }
293             else if (!activate && shortcut->getActivationStatus() == Shortcut::AS_Secondary)
294             {
295                 shortcut->setActivationStatus(Shortcut::AS_Inactive);
296                 shortcut->setPosition(0);
297                 shortcut->signalSecondary(false);
298                 used = true;
299             }
300         }
301 
302         return used;
303     }
304 
match(unsigned int mod,unsigned int button,unsigned int value)305     ShortcutEventHandler::MatchResult ShortcutEventHandler::match(unsigned int mod, unsigned int button,
306         unsigned int value)
307     {
308         if ((mod | button) == value)
309         {
310             return Matches_WithMod;
311         }
312         else if (button == value)
313         {
314             return Matches_NoMod;
315         }
316         else
317         {
318             return Matches_Not;
319         }
320     }
321 
sort(const std::pair<MatchResult,Shortcut * > & left,const std::pair<MatchResult,Shortcut * > & right)322     bool ShortcutEventHandler::sort(const std::pair<MatchResult, Shortcut*>& left,
323         const std::pair<MatchResult, Shortcut*>& right)
324     {
325         if (left.first == Matches_WithMod && right.first == Matches_NoMod)
326             return true;
327         else
328             return left.second->getPosition() > right.second->getPosition();
329     }
330 
widgetDestroyed()331     void ShortcutEventHandler::widgetDestroyed()
332     {
333         QWidget* widget = static_cast<QWidget*>(sender());
334 
335         mWidgetShortcuts.erase(widget);
336         mChildParentRelations.erase(widget);
337     }
338 }
339