src/platform/qt/InputModel.cpp (view raw)
1/* Copyright (c) 2013-2015 Jeffrey Pfau
2 *
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6#include "InputModel.h"
7
8#include "ConfigController.h"
9#include "GamepadButtonEvent.h"
10#include "InputProfile.h"
11
12#include <QAction>
13#include <QKeyEvent>
14#include <QMenu>
15
16using namespace QGBA;
17
18InputModel::InputModel(QObject* parent)
19 : QAbstractItemModel(parent)
20 , m_rootMenu(nullptr)
21{
22}
23
24void InputModel::setConfigController(ConfigController* controller) {
25 m_config = controller;
26}
27
28QVariant InputModel::data(const QModelIndex& index, int role) const {
29 if (role != Qt::DisplayRole || !index.isValid()) {
30 return QVariant();
31 }
32 int row = index.row();
33 const InputItem* item = static_cast<const InputItem*>(index.internalPointer());
34 switch (index.column()) {
35 case 0:
36 return item->visibleName();
37 case 1:
38 return QKeySequence(item->shortcut()).toString(QKeySequence::NativeText);
39 case 2:
40 if (item->button() >= 0) {
41 return item->button();
42 }
43 if (item->axis() >= 0) {
44 char d = '\0';
45 if (item->direction() == GamepadAxisEvent::POSITIVE) {
46 d = '+';
47 }
48 if (item->direction() == GamepadAxisEvent::NEGATIVE) {
49 d = '-';
50 }
51 return QString("%1%2").arg(d).arg(item->axis());
52 }
53 break;
54 }
55 return QVariant();
56}
57
58QVariant InputModel::headerData(int section, Qt::Orientation orientation, int role) const {
59 if (role != Qt::DisplayRole) {
60 return QAbstractItemModel::headerData(section, orientation, role);
61 }
62 if (orientation == Qt::Horizontal) {
63 switch (section) {
64 case 0:
65 return tr("Action");
66 case 1:
67 return tr("Keyboard");
68 case 2:
69 return tr("Gamepad");
70 }
71 }
72 return section;
73}
74
75QModelIndex InputModel::index(int row, int column, const QModelIndex& parent) const {
76 const InputItem* pmenu = &m_rootMenu;
77 if (parent.isValid()) {
78 pmenu = static_cast<InputItem*>(parent.internalPointer());
79 }
80 return createIndex(row, column, const_cast<InputItem*>(&pmenu->items()[row]));
81}
82
83QModelIndex InputModel::parent(const QModelIndex& index) const {
84 if (!index.isValid() || !index.internalPointer()) {
85 return QModelIndex();
86 }
87 InputItem* item = static_cast<InputItem*>(index.internalPointer());
88 return this->index(item->parent());
89}
90
91QModelIndex InputModel::index(InputItem* item) const {
92 if (!item || !item->parent()) {
93 return QModelIndex();
94 }
95 return createIndex(item->parent()->items().indexOf(*item), 0, item);
96}
97
98int InputModel::columnCount(const QModelIndex& index) const {
99 return 3;
100}
101
102int InputModel::rowCount(const QModelIndex& index) const {
103 if (!index.isValid()) {
104 return m_rootMenu.items().count();
105 }
106 const InputItem* item = static_cast<const InputItem*>(index.internalPointer());
107 return item->items().count();
108}
109
110InputItem* InputModel::add(QMenu* menu, std::function<void (InputItem*)> callback) {
111 InputItem* smenu = m_menuMap[menu];
112 if (!smenu) {
113 return nullptr;
114 }
115 QModelIndex parent = index(smenu);
116 beginInsertRows(parent, smenu->items().count(), smenu->items().count());
117 callback(smenu);
118 endInsertRows();
119 InputItem* item = &smenu->items().last();
120 emit dataChanged(createIndex(smenu->items().count() - 1, 0, item),
121 createIndex(smenu->items().count() - 1, 2, item));
122 return item;
123}
124
125void InputModel::addAction(QMenu* menu, QAction* action, const QString& name) {
126 InputItem* item = add(menu, [&](InputItem* smenu) {
127 smenu->addAction(action, name);
128 });
129 if (!item) {
130 return;
131 }
132 if (m_config) {
133 loadShortcuts(item);
134 }
135}
136
137void InputModel::addFunctions(QMenu* menu, std::function<void()> press, std::function<void()> release,
138 int shortcut, const QString& visibleName, const QString& name) {
139 InputItem* item = add(menu, [&](InputItem* smenu) {
140 smenu->addFunctions(qMakePair(press, release), shortcut, visibleName, name);
141 });
142 if (!item) {
143 return;
144 }
145
146 bool loadedShortcut = false;
147 if (m_config) {
148 loadedShortcut = loadShortcuts(item);
149 }
150 if (!loadedShortcut && !m_heldKeys.contains(shortcut)) {
151 m_heldKeys[shortcut] = item;
152 }
153}
154
155void InputModel::addFunctions(QMenu* menu, std::function<void()> press, std::function<void()> release,
156 const QKeySequence& shortcut, const QString& visibleName, const QString& name) {
157 addFunctions(menu, press, release, shortcut[0], visibleName, name);
158}
159
160void InputModel::addKey(QMenu* menu, mPlatform platform, int key, int shortcut, const QString& visibleName, const QString& name) {
161 InputItem* item = add(menu, [&](InputItem* smenu) {
162 smenu->addKey(platform, key, shortcut, visibleName, name);
163 });
164 if (!item) {
165 return;
166 }
167 bool loadedShortcut = false;
168 if (m_config) {
169 loadedShortcut = loadShortcuts(item);
170 }
171 if (!loadedShortcut && !m_keys.contains(qMakePair(platform, shortcut))) {
172 m_keys[qMakePair(platform, shortcut)] = item;
173 }
174}
175
176QModelIndex InputModel::addMenu(QMenu* menu, QMenu* parentMenu) {
177 InputItem* smenu = m_menuMap[parentMenu];
178 if (!smenu) {
179 smenu = &m_rootMenu;
180 }
181 QModelIndex parent = index(smenu);
182 beginInsertRows(parent, smenu->items().count(), smenu->items().count());
183 smenu->addSubmenu(menu);
184 endInsertRows();
185 InputItem* item = &smenu->items().last();
186 emit dataChanged(createIndex(smenu->items().count() - 1, 0, item),
187 createIndex(smenu->items().count() - 1, 2, item));
188 m_menuMap[menu] = item;
189 return index(item);
190}
191
192InputItem* InputModel::itemAt(const QModelIndex& index) {
193 if (!index.isValid()) {
194 return nullptr;
195 }
196 if (index.internalPointer()) {
197 return static_cast<InputItem*>(index.internalPointer());
198 }
199 if (!index.parent().isValid()) {
200 return nullptr;
201 }
202 InputItem* pmenu = static_cast<InputItem*>(index.parent().internalPointer());
203 return &pmenu->items()[index.row()];
204}
205
206const InputItem* InputModel::itemAt(const QModelIndex& index) const {
207 if (!index.isValid()) {
208 return nullptr;
209 }
210 if (index.internalPointer()) {
211 return static_cast<InputItem*>(index.internalPointer());
212 }
213 if (!index.parent().isValid()) {
214 return nullptr;
215 }
216 InputItem* pmenu = static_cast<InputItem*>(index.parent().internalPointer());
217 return &pmenu->items()[index.row()];
218}
219
220int InputModel::shortcutAt(const QModelIndex& index) const {
221 const InputItem* item = itemAt(index);
222 if (!item) {
223 return 0;
224 }
225 return item->shortcut();
226}
227
228int InputModel::keyAt(const QModelIndex& index) const {
229 const InputItem* item = itemAt(index);
230 if (!item) {
231 return -1;
232 }
233 return item->key();
234}
235
236bool InputModel::isMenuAt(const QModelIndex& index) const {
237 const InputItem* item = itemAt(index);
238 if (!item) {
239 return false;
240 }
241 return item->menu();
242}
243
244void InputModel::updateKey(const QModelIndex& index, int keySequence) {
245 if (!index.isValid()) {
246 return;
247 }
248 const QModelIndex& parent = index.parent();
249 if (!parent.isValid()) {
250 return;
251 }
252 InputItem* item = itemAt(index);
253 updateKey(item, keySequence);
254 if (m_config) {
255 m_config->setQtOption(item->name(), QKeySequence(keySequence).toString(), KEY_SECTION);
256 }
257}
258
259void InputModel::updateKey(InputItem* item, int keySequence) {
260 int oldShortcut = item->shortcut();
261 if (item->functions().first) {
262 if (oldShortcut > 0) {
263 m_heldKeys.take(oldShortcut);
264 }
265 if (keySequence > 0) {
266 m_heldKeys[keySequence] = item;
267 }
268 }
269
270 if (item->key() >= 0) {
271 if (oldShortcut > 0) {
272 m_keys.take(qMakePair(item->platform(), oldShortcut));
273 }
274 if (keySequence > 0) {
275 m_keys[qMakePair(item->platform(), keySequence)] = item;
276 }
277 }
278
279 item->setShortcut(keySequence);
280
281 emit dataChanged(createIndex(index(item).row(), 0, item),
282 createIndex(index(item).row(), 2, item));
283
284 emit keyRebound(index(item), keySequence);
285}
286
287void InputModel::updateButton(const QModelIndex& index, int button) {
288 if (!index.isValid()) {
289 return;
290 }
291 const QModelIndex& parent = index.parent();
292 if (!parent.isValid()) {
293 return;
294 }
295 InputItem* item = itemAt(index);
296 int oldButton = item->button();
297 if (oldButton >= 0) {
298 m_buttons.take(oldButton);
299 }
300 updateAxis(index, -1, GamepadAxisEvent::NEUTRAL);
301 item->setButton(button);
302 if (button >= 0) {
303 m_buttons[button] = item;
304 }
305 if (m_config) {
306 m_config->setQtOption(item->name(), button, BUTTON_SECTION);
307 if (!m_profileName.isNull()) {
308 m_config->setQtOption(item->name(), button, BUTTON_PROFILE_SECTION + m_profileName);
309 }
310 }
311 emit dataChanged(createIndex(index.row(), 0, index.internalPointer()),
312 createIndex(index.row(), 2, index.internalPointer()));
313
314 emit buttonRebound(index, button);
315}
316
317void InputModel::updateAxis(const QModelIndex& index, int axis, GamepadAxisEvent::Direction direction) {
318 if (!index.isValid()) {
319 return;
320 }
321 const QModelIndex& parent = index.parent();
322 if (!parent.isValid()) {
323 return;
324 }
325 InputItem* item = itemAt(index);
326 int oldAxis = item->axis();
327 GamepadAxisEvent::Direction oldDirection = item->direction();
328 if (oldAxis >= 0) {
329 m_axes.take(qMakePair(oldAxis, oldDirection));
330 }
331 if (axis >= 0 && direction != GamepadAxisEvent::NEUTRAL) {
332 updateButton(index, -1);
333 m_axes[qMakePair(axis, direction)] = item;
334 }
335 item->setAxis(axis, direction);
336 if (m_config) {
337 char d = '\0';
338 if (direction == GamepadAxisEvent::POSITIVE) {
339 d = '+';
340 }
341 if (direction == GamepadAxisEvent::NEGATIVE) {
342 d = '-';
343 }
344 m_config->setQtOption(item->name(), QString("%1%2").arg(d).arg(axis), AXIS_SECTION);
345 if (!m_profileName.isNull()) {
346 m_config->setQtOption(item->name(), QString("%1%2").arg(d).arg(axis), AXIS_PROFILE_SECTION + m_profileName);
347 }
348 }
349 emit dataChanged(createIndex(index.row(), 0, index.internalPointer()),
350 createIndex(index.row(), 2, index.internalPointer()));
351
352 emit axisRebound(index, axis, direction);
353}
354
355void InputModel::clearKey(const QModelIndex& index) {
356 updateKey(index, 0);
357}
358
359void InputModel::clearButton(const QModelIndex& index) {
360 updateButton(index, -1);
361}
362
363bool InputModel::triggerKey(int keySequence, bool down, mPlatform platform) {
364 auto key = m_keys.find(qMakePair(platform, keySequence));
365 if (key != m_keys.end()) {
366 m_keyCallback(key.value()->parent()->menu(), key.value()->key(), down);
367 return true;
368 }
369 auto heldKey = m_heldKeys.find(keySequence);
370 if (heldKey != m_heldKeys.end()) {
371 auto pair = heldKey.value()->functions();
372 if (down) {
373 if (pair.first) {
374 pair.first();
375 }
376 } else {
377 if (pair.second) {
378 pair.second();
379 }
380 }
381 return true;
382 }
383 return false;
384}
385
386bool InputModel::triggerButton(int button, bool down) {
387 auto item = m_buttons.find(button);
388 if (item == m_buttons.end()) {
389 return false;
390 }
391 if (down) {
392 QAction* action = item.value()->action();
393 if (action && action->isEnabled()) {
394 action->trigger();
395 }
396 auto pair = item.value()->functions();
397 if (pair.first) {
398 pair.first();
399 }
400 } else {
401 auto pair = item.value()->functions();
402 if (pair.second) {
403 pair.second();
404 }
405 }
406 return true;
407}
408
409bool InputModel::triggerAxis(int axis, GamepadAxisEvent::Direction direction, bool isNew) {
410 auto item = m_axes.find(qMakePair(axis, direction));
411 if (item == m_axes.end()) {
412 return false;
413 }
414 if (isNew) {
415 QAction* action = item.value()->action();
416 if (action && action->isEnabled()) {
417 action->trigger();
418 }
419 }
420 auto pair = item.value()->functions();
421 if (isNew) {
422 if (pair.first) {
423 pair.first();
424 }
425 } else {
426 if (pair.second) {
427 pair.second();
428 }
429 }
430 return true;
431}
432
433bool InputModel::loadShortcuts(InputItem* item) {
434 if (item->name().isNull()) {
435 return false;
436 }
437 loadGamepadShortcuts(item);
438 QVariant shortcut = m_config->getQtOption(item->name(), KEY_SECTION);
439 if (!shortcut.isNull()) {
440 if (shortcut.toString().endsWith("+")) {
441 updateKey(item, toModifierShortcut(shortcut.toString()));
442 } else {
443 updateKey(item, QKeySequence(shortcut.toString())[0]);
444 }
445 return true;
446 }
447 return false;
448}
449
450void InputModel::loadGamepadShortcuts(InputItem* item) {
451 if (item->name().isNull()) {
452 return;
453 }
454 QVariant button = m_config->getQtOption(item->name(), !m_profileName.isNull() ? BUTTON_PROFILE_SECTION + m_profileName : BUTTON_SECTION);
455 int oldButton = item->button();
456 if (oldButton >= 0) {
457 m_buttons.take(oldButton);
458 item->setButton(-1);
459 }
460 if (button.isNull() && m_profile) {
461 int buttonInt;
462 if (m_profile->lookupShortcutButton(item->name(), &buttonInt)) {
463 button = buttonInt;
464 }
465 }
466 if (!button.isNull()) {
467 item->setButton(button.toInt());
468 m_buttons[button.toInt()] = item;
469 }
470
471 QVariant axis = m_config->getQtOption(item->name(), !m_profileName.isNull() ? AXIS_PROFILE_SECTION + m_profileName : AXIS_SECTION);
472 int oldAxis = item->axis();
473 GamepadAxisEvent::Direction oldDirection = item->direction();
474 if (oldAxis >= 0) {
475 m_axes.take(qMakePair(oldAxis, oldDirection));
476 item->setAxis(-1, GamepadAxisEvent::NEUTRAL);
477 }
478 if (axis.isNull() && m_profile) {
479 int axisInt;
480 GamepadAxisEvent::Direction direction;
481 if (m_profile->lookupShortcutAxis(item->name(), &axisInt, &direction)) {
482 axis = QLatin1String(direction == GamepadAxisEvent::Direction::NEGATIVE ? "-" : "+") + QString::number(axisInt);
483 }
484 }
485 if (!axis.isNull()) {
486 QString axisDesc = axis.toString();
487 if (axisDesc.size() >= 2) {
488 GamepadAxisEvent::Direction direction = GamepadAxisEvent::NEUTRAL;
489 if (axisDesc[0] == '-') {
490 direction = GamepadAxisEvent::NEGATIVE;
491 }
492 if (axisDesc[0] == '+') {
493 direction = GamepadAxisEvent::POSITIVE;
494 }
495 bool ok;
496 int axis = axisDesc.mid(1).toInt(&ok);
497 if (ok) {
498 item->setAxis(axis, direction);
499 m_axes[qMakePair(axis, direction)] = item;
500 }
501 }
502 }
503}
504
505void InputModel::loadProfile(mPlatform platform, const QString& profile) {
506 m_profileName = profile;
507 m_profile = InputProfile::findProfile(platform, profile);
508 onSubitems(&m_rootMenu, [this](InputItem* item) {
509 loadGamepadShortcuts(item);
510 });
511}
512
513void InputModel::onSubitems(InputItem* item, std::function<void(InputItem*)> func) {
514 for (InputItem& subitem : item->items()) {
515 func(&subitem);
516 onSubitems(&subitem, func);
517 }
518}
519
520int InputModel::toModifierShortcut(const QString& shortcut) {
521 // Qt doesn't seem to work with raw modifier shortcuts!
522 QStringList modifiers = shortcut.split('+');
523 int value = 0;
524 for (const auto& mod : modifiers) {
525 if (mod == QLatin1String("Shift")) {
526 value |= Qt::ShiftModifier;
527 continue;
528 }
529 if (mod == QLatin1String("Ctrl")) {
530 value |= Qt::ControlModifier;
531 continue;
532 }
533 if (mod == QLatin1String("Alt")) {
534 value |= Qt::AltModifier;
535 continue;
536 }
537 if (mod == QLatin1String("Meta")) {
538 value |= Qt::MetaModifier;
539 continue;
540 }
541 }
542 return value;
543}
544
545bool InputModel::isModifierKey(int key) {
546 switch (key) {
547 case Qt::Key_Shift:
548 case Qt::Key_Control:
549 case Qt::Key_Alt:
550 case Qt::Key_Meta:
551 return true;
552 default:
553 return false;
554 }
555}
556
557int InputModel::toModifierKey(int key) {
558 int modifiers = key & (Qt::ShiftModifier | Qt::ControlModifier | Qt::AltModifier | Qt::MetaModifier);
559 key ^= modifiers;
560 switch (key) {
561 case Qt::Key_Shift:
562 modifiers |= Qt::ShiftModifier;
563 break;
564 case Qt::Key_Control:
565 modifiers |= Qt::ControlModifier;
566 break;
567 case Qt::Key_Alt:
568 modifiers |= Qt::AltModifier;
569 break;
570 case Qt::Key_Meta:
571 modifiers |= Qt::MetaModifier;
572 break;
573 default:
574 break;
575 }
576 return modifiers;
577}