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