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