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 m_keys[qMakePair(platform, key)] = item;
170}
171
172QModelIndex InputModel::addMenu(QMenu* menu, QMenu* parentMenu) {
173 InputItem* smenu = m_menuMap[parentMenu];
174 if (!smenu) {
175 smenu = &m_rootMenu;
176 }
177 QModelIndex parent = index(smenu);
178 beginInsertRows(parent, smenu->items().count(), smenu->items().count());
179 smenu->addSubmenu(menu);
180 endInsertRows();
181 InputItem* item = &smenu->items().last();
182 emit dataChanged(createIndex(smenu->items().count() - 1, 0, item),
183 createIndex(smenu->items().count() - 1, 2, item));
184 m_menuMap[menu] = item;
185 return index(item);
186}
187
188InputItem* InputModel::itemAt(const QModelIndex& index) {
189 if (!index.isValid()) {
190 return nullptr;
191 }
192 if (index.internalPointer()) {
193 return static_cast<InputItem*>(index.internalPointer());
194 }
195 if (!index.parent().isValid()) {
196 return nullptr;
197 }
198 InputItem* pmenu = static_cast<InputItem*>(index.parent().internalPointer());
199 return &pmenu->items()[index.row()];
200}
201
202const InputItem* InputModel::itemAt(const QModelIndex& index) const {
203 if (!index.isValid()) {
204 return nullptr;
205 }
206 if (index.internalPointer()) {
207 return static_cast<InputItem*>(index.internalPointer());
208 }
209 if (!index.parent().isValid()) {
210 return nullptr;
211 }
212 InputItem* pmenu = static_cast<InputItem*>(index.parent().internalPointer());
213 return &pmenu->items()[index.row()];
214}
215
216int InputModel::shortcutAt(const QModelIndex& index) const {
217 const InputItem* item = itemAt(index);
218 if (!item) {
219 return 0;
220 }
221 return item->shortcut();
222}
223
224int InputModel::keyAt(const QModelIndex& index) const {
225 const InputItem* item = itemAt(index);
226 if (!item) {
227 return -1;
228 }
229 return item->key();
230}
231
232bool InputModel::isMenuAt(const QModelIndex& index) const {
233 const InputItem* item = itemAt(index);
234 if (!item) {
235 return false;
236 }
237 return item->menu();
238}
239
240void InputModel::updateKey(const QModelIndex& index, int keySequence) {
241 if (!index.isValid()) {
242 return;
243 }
244 const QModelIndex& parent = index.parent();
245 if (!parent.isValid()) {
246 return;
247 }
248 InputItem* item = itemAt(index);
249 updateKey(item, keySequence);
250 if (m_config) {
251 m_config->setQtOption(item->name(), QKeySequence(keySequence).toString(), KEY_SECTION);
252 }
253}
254
255void InputModel::updateKey(InputItem* item, int keySequence) {
256 int oldShortcut = item->shortcut();
257 if (item->functions().first || item->key() >= 0) {
258 if (oldShortcut > 0) {
259 m_heldKeys.take(oldShortcut);
260 }
261 if (keySequence >= 0) {
262 m_keys[qMakePair(item->platform(), keySequence)] = item;
263 }
264 }
265
266 item->setShortcut(keySequence);
267
268 emit dataChanged(createIndex(index(item).row(), 0, item),
269 createIndex(index(item).row(), 2, item));
270
271 emit keyRebound(index(item), keySequence);
272}
273
274void InputModel::updateButton(const QModelIndex& index, int button) {
275 if (!index.isValid()) {
276 return;
277 }
278 const QModelIndex& parent = index.parent();
279 if (!parent.isValid()) {
280 return;
281 }
282 InputItem* item = itemAt(index);
283 int oldButton = item->button();
284 if (oldButton >= 0) {
285 m_buttons.take(oldButton);
286 }
287 updateAxis(index, -1, GamepadAxisEvent::NEUTRAL);
288 item->setButton(button);
289 if (button >= 0) {
290 m_buttons[button] = item;
291 }
292 if (m_config) {
293 m_config->setQtOption(item->name(), button, BUTTON_SECTION);
294 if (!m_profileName.isNull()) {
295 m_config->setQtOption(item->name(), button, BUTTON_PROFILE_SECTION + m_profileName);
296 }
297 }
298 emit dataChanged(createIndex(index.row(), 0, index.internalPointer()),
299 createIndex(index.row(), 2, index.internalPointer()));
300
301 emit buttonRebound(index, button);
302}
303
304void InputModel::updateAxis(const QModelIndex& index, int axis, GamepadAxisEvent::Direction direction) {
305 if (!index.isValid()) {
306 return;
307 }
308 const QModelIndex& parent = index.parent();
309 if (!parent.isValid()) {
310 return;
311 }
312 InputItem* item = itemAt(index);
313 int oldAxis = item->axis();
314 GamepadAxisEvent::Direction oldDirection = item->direction();
315 if (oldAxis >= 0) {
316 m_axes.take(qMakePair(oldAxis, oldDirection));
317 }
318 if (axis >= 0 && direction != GamepadAxisEvent::NEUTRAL) {
319 updateButton(index, -1);
320 m_axes[qMakePair(axis, direction)] = item;
321 }
322 item->setAxis(axis, direction);
323 if (m_config) {
324 char d = '\0';
325 if (direction == GamepadAxisEvent::POSITIVE) {
326 d = '+';
327 }
328 if (direction == GamepadAxisEvent::NEGATIVE) {
329 d = '-';
330 }
331 m_config->setQtOption(item->name(), QString("%1%2").arg(d).arg(axis), AXIS_SECTION);
332 if (!m_profileName.isNull()) {
333 m_config->setQtOption(item->name(), QString("%1%2").arg(d).arg(axis), AXIS_PROFILE_SECTION + m_profileName);
334 }
335 }
336 emit dataChanged(createIndex(index.row(), 0, index.internalPointer()),
337 createIndex(index.row(), 2, index.internalPointer()));
338
339 emit axisRebound(index, axis, direction);
340}
341
342void InputModel::clearKey(const QModelIndex& index) {
343 updateKey(index, 0);
344}
345
346void InputModel::clearButton(const QModelIndex& index) {
347 updateButton(index, -1);
348}
349
350bool InputModel::triggerKey(int keySequence, bool down, mPlatform platform) {
351 auto key = m_keys.find(qMakePair(platform, keySequence));
352 if (key != m_keys.end()) {
353 m_keyCallback(key.value()->parent()->menu(), key.value()->key(), down);
354 return true;
355 }
356 auto heldKey = m_heldKeys.find(keySequence);
357 if (heldKey != m_heldKeys.end()) {
358 auto pair = heldKey.value()->functions();
359 if (down) {
360 if (pair.first) {
361 pair.first();
362 }
363 } else {
364 if (pair.second) {
365 pair.second();
366 }
367 }
368 return true;
369 }
370 return false;
371}
372
373bool InputModel::triggerButton(int button, bool down) {
374 auto item = m_buttons.find(button);
375 if (item == m_buttons.end()) {
376 return false;
377 }
378 if (down) {
379 QAction* action = item.value()->action();
380 if (action && action->isEnabled()) {
381 action->trigger();
382 }
383 auto pair = item.value()->functions();
384 if (pair.first) {
385 pair.first();
386 }
387 } else {
388 auto pair = item.value()->functions();
389 if (pair.second) {
390 pair.second();
391 }
392 }
393 return true;
394}
395
396bool InputModel::triggerAxis(int axis, GamepadAxisEvent::Direction direction, bool isNew) {
397 auto item = m_axes.find(qMakePair(axis, direction));
398 if (item == m_axes.end()) {
399 return false;
400 }
401 if (isNew) {
402 QAction* action = item.value()->action();
403 if (action && action->isEnabled()) {
404 action->trigger();
405 }
406 }
407 auto pair = item.value()->functions();
408 if (isNew) {
409 if (pair.first) {
410 pair.first();
411 }
412 } else {
413 if (pair.second) {
414 pair.second();
415 }
416 }
417 return true;
418}
419
420bool InputModel::loadShortcuts(InputItem* item) {
421 if (item->name().isNull()) {
422 return false;
423 }
424 loadGamepadShortcuts(item);
425 QVariant shortcut = m_config->getQtOption(item->name(), KEY_SECTION);
426 if (!shortcut.isNull()) {
427 if (shortcut.toString().endsWith("+")) {
428 updateKey(item, toModifierShortcut(shortcut.toString()));
429 } else {
430 updateKey(item, QKeySequence(shortcut.toString())[0]);
431 }
432 return true;
433 }
434 return false;
435}
436
437void InputModel::loadGamepadShortcuts(InputItem* item) {
438 if (item->name().isNull()) {
439 return;
440 }
441 QVariant button = m_config->getQtOption(item->name(), !m_profileName.isNull() ? BUTTON_PROFILE_SECTION + m_profileName : BUTTON_SECTION);
442 int oldButton = item->button();
443 if (oldButton >= 0) {
444 m_buttons.take(oldButton);
445 item->setButton(-1);
446 }
447 if (button.isNull() && m_profile) {
448 int buttonInt;
449 if (m_profile->lookupShortcutButton(item->name(), &buttonInt)) {
450 button = buttonInt;
451 }
452 }
453 if (!button.isNull()) {
454 item->setButton(button.toInt());
455 m_buttons[button.toInt()] = item;
456 }
457
458 QVariant axis = m_config->getQtOption(item->name(), !m_profileName.isNull() ? AXIS_PROFILE_SECTION + m_profileName : AXIS_SECTION);
459 int oldAxis = item->axis();
460 GamepadAxisEvent::Direction oldDirection = item->direction();
461 if (oldAxis >= 0) {
462 m_axes.take(qMakePair(oldAxis, oldDirection));
463 item->setAxis(-1, GamepadAxisEvent::NEUTRAL);
464 }
465 if (axis.isNull() && m_profile) {
466 int axisInt;
467 GamepadAxisEvent::Direction direction;
468 if (m_profile->lookupShortcutAxis(item->name(), &axisInt, &direction)) {
469 axis = QLatin1String(direction == GamepadAxisEvent::Direction::NEGATIVE ? "-" : "+") + QString::number(axisInt);
470 }
471 }
472 if (!axis.isNull()) {
473 QString axisDesc = axis.toString();
474 if (axisDesc.size() >= 2) {
475 GamepadAxisEvent::Direction direction = GamepadAxisEvent::NEUTRAL;
476 if (axisDesc[0] == '-') {
477 direction = GamepadAxisEvent::NEGATIVE;
478 }
479 if (axisDesc[0] == '+') {
480 direction = GamepadAxisEvent::POSITIVE;
481 }
482 bool ok;
483 int axis = axisDesc.mid(1).toInt(&ok);
484 if (ok) {
485 item->setAxis(axis, direction);
486 m_axes[qMakePair(axis, direction)] = item;
487 }
488 }
489 }
490}
491
492void InputModel::loadProfile(mPlatform platform, const QString& profile) {
493 m_profileName = profile;
494 m_profile = InputProfile::findProfile(platform, profile);
495 onSubitems(&m_rootMenu, [this](InputItem* item) {
496 loadGamepadShortcuts(item);
497 });
498}
499
500void InputModel::onSubitems(InputItem* item, std::function<void(InputItem*)> func) {
501 for (InputItem& subitem : item->items()) {
502 func(&subitem);
503 onSubitems(&subitem, func);
504 }
505}
506
507int InputModel::toModifierShortcut(const QString& shortcut) {
508 // Qt doesn't seem to work with raw modifier shortcuts!
509 QStringList modifiers = shortcut.split('+');
510 int value = 0;
511 for (const auto& mod : modifiers) {
512 if (mod == QLatin1String("Shift")) {
513 value |= Qt::ShiftModifier;
514 continue;
515 }
516 if (mod == QLatin1String("Ctrl")) {
517 value |= Qt::ControlModifier;
518 continue;
519 }
520 if (mod == QLatin1String("Alt")) {
521 value |= Qt::AltModifier;
522 continue;
523 }
524 if (mod == QLatin1String("Meta")) {
525 value |= Qt::MetaModifier;
526 continue;
527 }
528 }
529 return value;
530}
531
532bool InputModel::isModifierKey(int key) {
533 switch (key) {
534 case Qt::Key_Shift:
535 case Qt::Key_Control:
536 case Qt::Key_Alt:
537 case Qt::Key_Meta:
538 return true;
539 default:
540 return false;
541 }
542}
543
544int InputModel::toModifierKey(int key) {
545 int modifiers = key & (Qt::ShiftModifier | Qt::ControlModifier | Qt::AltModifier | Qt::MetaModifier);
546 key ^= modifiers;
547 switch (key) {
548 case Qt::Key_Shift:
549 modifiers |= Qt::ShiftModifier;
550 break;
551 case Qt::Key_Control:
552 modifiers |= Qt::ControlModifier;
553 break;
554 case Qt::Key_Alt:
555 modifiers |= Qt::AltModifier;
556 break;
557 case Qt::Key_Meta:
558 modifiers |= Qt::MetaModifier;
559 break;
560 default:
561 break;
562 }
563 return modifiers;
564}