/* Copyright (c) 2013-2015 Jeffrey Pfau * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "ShortcutController.h" #include "ConfigController.h" #include "GamepadButtonEvent.h" #include "InputProfile.h" #include #include #include #include using namespace QGBA; ShortcutController::ShortcutController(QObject* parent) : QObject(parent) { } void ShortcutController::setConfigController(ConfigController* controller) { m_config = controller; } void ShortcutController::setActionMapper(ActionMapper* actions) { m_actions = actions; connect(actions, &ActionMapper::actionAdded, this, &ShortcutController::generateItem); connect(actions, &ActionMapper::menuCleared, this, &ShortcutController::menuCleared); rebuildItems(); } void ShortcutController::updateKey(const QString& name, int keySequence) { auto item = m_items[name]; if (!item) { return; } updateKey(item, keySequence); if (m_config) { m_config->setQtOption(item->name(), QKeySequence(keySequence).toString(), KEY_SECTION); } } void ShortcutController::updateKey(std::shared_ptr item, int keySequence) { int oldShortcut = item->shortcut(); if (oldShortcut != keySequence && m_actions->isHeld(item->name())) { if (oldShortcut > 0) { if (item->action() && item->action()->booleanAction()) { item->action()->booleanAction()(false); } m_heldKeys.take(oldShortcut); } if (keySequence > 0) { m_heldKeys[keySequence] = item; } } item->setShortcut(keySequence); } void ShortcutController::updateButton(const QString& name, int button) { auto item = m_items[name]; if (!item) { return; } int oldButton = item->button(); if (oldButton >= 0) { m_buttons.take(oldButton); } item->setButton(button); if (button >= 0) { clearAxis(name); m_buttons[button] = item; } if (m_config) { m_config->setQtOption(name, button, BUTTON_SECTION); if (!m_profileName.isNull()) { m_config->setQtOption(name, button, BUTTON_PROFILE_SECTION + m_profileName); } } } void ShortcutController::updateAxis(const QString& name, int axis, GamepadAxisEvent::Direction direction) { auto item = m_items[name]; if (!item) { return; } int oldAxis = item->axis(); GamepadAxisEvent::Direction oldDirection = item->direction(); if (oldAxis >= 0) { m_axes.take(std::make_pair(oldAxis, oldDirection)); } if (axis >= 0 && direction != GamepadAxisEvent::NEUTRAL) { clearButton(name); m_axes[std::make_pair(axis, direction)] = item; } item->setAxis(axis, direction); if (m_config) { char d = '\0'; if (direction == GamepadAxisEvent::POSITIVE) { d = '+'; } if (direction == GamepadAxisEvent::NEGATIVE) { d = '-'; } m_config->setQtOption(name, QString("%1%2").arg(d).arg(axis), AXIS_SECTION); if (!m_profileName.isNull()) { m_config->setQtOption(name, QString("%1%2").arg(d).arg(axis), AXIS_PROFILE_SECTION + m_profileName); } } } void ShortcutController::clearKey(const QString& name) { updateKey(name, 0); } void ShortcutController::clearButton(const QString& name) { updateButton(name, -1); } void ShortcutController::clearAxis(const QString& name) { updateAxis(name, -1, GamepadAxisEvent::NEUTRAL); } void ShortcutController::rebuildItems() { m_items.clear(); m_buttons.clear(); m_axes.clear(); m_heldKeys.clear(); onSubitems({}, std::bind(&ShortcutController::generateItem, this, std::placeholders::_1)); } bool ShortcutController::eventFilter(QObject*, QEvent* event) { if (event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease) { QKeyEvent* keyEvent = static_cast(event); if (keyEvent->isAutoRepeat()) { return false; } int key = keyEvent->key(); if (!isModifierKey(key)) { key |= (keyEvent->modifiers() & ~Qt::KeypadModifier); } else { key = toModifierKey(key | (keyEvent->modifiers() & ~Qt::KeypadModifier)); } auto item = m_heldKeys.find(key); if (item != m_heldKeys.end()) { Action::BooleanFunction fn = item.value()->action()->booleanAction(); fn(event->type() == QEvent::KeyPress); event->accept(); return true; } } if (event->type() == GamepadButtonEvent::Down()) { auto item = m_buttons.find(static_cast(event)->value()); if (item == m_buttons.end()) { return false; } Action* action = item.value()->action(); if (action) { if (m_actions->isHeld(action->name())) { action->trigger(true); } else { action->trigger(!action->isActive()); } } event->accept(); return true; } if (event->type() == GamepadButtonEvent::Up()) { auto item = m_buttons.find(static_cast(event)->value()); if (item == m_buttons.end()) { return false; } Action* action = item.value()->action(); if (action && m_actions->isHeld(action->name())) { action->trigger(false); } event->accept(); return true; } if (event->type() == GamepadAxisEvent::Type()) { GamepadAxisEvent* gae = static_cast(event); auto item = m_axes.find(std::make_pair(gae->axis(), gae->direction())); if (item == m_axes.end()) { return false; } Action* action = item.value()->action(); if (action) { if (gae->isNew()) { if (m_actions->isHeld(action->name())) { action->trigger(true); } else { action->trigger(!action->isActive()); } } else if (m_actions->isHeld(action->name())) { action->trigger(false); } } event->accept(); return true; } return false; } void ShortcutController::generateItem(const QString& itemName) { if (itemName.isNull() || itemName[0] == '.') { return; } Action* action = m_actions->getAction(itemName); if (action) { std::shared_ptr item = std::make_shared(action); m_items[itemName] = item; loadShortcuts(item); } emit shortcutAdded(itemName); } bool ShortcutController::loadShortcuts(std::shared_ptr item) { if (item->name().isNull()) { return false; } loadGamepadShortcuts(item); QVariant shortcut = m_config->getQtOption(item->name(), KEY_SECTION); if (!shortcut.isNull()) { if (shortcut.toString().endsWith("+")) { updateKey(item, toModifierShortcut(shortcut.toString())); } else { updateKey(item, QKeySequence(shortcut.toString())[0]); } return true; } else { QKeySequence defaultShortcut = m_actions->defaultShortcut(item->name()); if (!defaultShortcut.isEmpty()) { updateKey(item, defaultShortcut[0]); return true; } } return false; } void ShortcutController::loadGamepadShortcuts(std::shared_ptr item) { if (item->name().isNull()) { return; } QVariant button = m_config->getQtOption(item->name(), !m_profileName.isNull() ? BUTTON_PROFILE_SECTION + m_profileName : BUTTON_SECTION); int oldButton = item->button(); if (oldButton >= 0) { m_buttons.take(oldButton); item->setButton(-1); } if (button.isNull() && m_profile) { int buttonInt; if (m_profile->lookupShortcutButton(item->name(), &buttonInt)) { button = buttonInt; } } if (!button.isNull()) { item->setButton(button.toInt()); m_buttons[button.toInt()] = item; } QVariant axis = m_config->getQtOption(item->name(), !m_profileName.isNull() ? AXIS_PROFILE_SECTION + m_profileName : AXIS_SECTION); int oldAxis = item->axis(); GamepadAxisEvent::Direction oldDirection = item->direction(); if (oldAxis >= 0) { m_axes.take(std::make_pair(oldAxis, oldDirection)); item->setAxis(-1, GamepadAxisEvent::NEUTRAL); } if (axis.isNull() && m_profile) { int axisInt; GamepadAxisEvent::Direction direction; if (m_profile->lookupShortcutAxis(item->name(), &axisInt, &direction)) { axis = QLatin1String(direction == GamepadAxisEvent::Direction::NEGATIVE ? "-" : "+") + QString::number(axisInt); } } if (!axis.isNull()) { QString axisDesc = axis.toString(); if (axisDesc.size() >= 2) { GamepadAxisEvent::Direction direction = GamepadAxisEvent::NEUTRAL; if (axisDesc[0] == '-') { direction = GamepadAxisEvent::NEGATIVE; } if (axisDesc[0] == '+') { direction = GamepadAxisEvent::POSITIVE; } bool ok; int axis = axisDesc.mid(1).toInt(&ok); if (ok) { item->setAxis(axis, direction); m_axes[std::make_pair(axis, direction)] = item; } } } } void ShortcutController::loadProfile(const QString& profile) { m_profileName = profile; m_profile = InputProfile::findProfile(profile); onSubitems({}, [this](std::shared_ptr item) { loadGamepadShortcuts(item); }); } void ShortcutController::onSubitems(const QString& menu, std::function)> func) { for (const QString& subitem : m_actions->menuItems(menu)) { auto item = m_items[subitem]; if (item) { func(item); } if (subitem.size() && subitem[0] == '.') { onSubitems(subitem.mid(1), func); } } } void ShortcutController::onSubitems(const QString& menu, std::function func) { for (const QString& subitem : m_actions->menuItems(menu)) { func(subitem); if (subitem.size() && subitem[0] == '.') { onSubitems(subitem.mid(1), func); } } } int ShortcutController::toModifierShortcut(const QString& shortcut) { // Qt doesn't seem to work with raw modifier shortcuts! QStringList modifiers = shortcut.split('+'); int value = 0; for (const auto& mod : modifiers) { if (mod == QLatin1String("Shift")) { value |= Qt::ShiftModifier; continue; } if (mod == QLatin1String("Ctrl")) { value |= Qt::ControlModifier; continue; } if (mod == QLatin1String("Alt")) { value |= Qt::AltModifier; continue; } if (mod == QLatin1String("Meta")) { value |= Qt::MetaModifier; continue; } } return value; } bool ShortcutController::isModifierKey(int key) { switch (key) { case Qt::Key_Shift: case Qt::Key_Control: case Qt::Key_Alt: case Qt::Key_Meta: return true; default: return false; } } int ShortcutController::toModifierKey(int key) { int modifiers = key & (Qt::ShiftModifier | Qt::ControlModifier | Qt::AltModifier | Qt::MetaModifier); key ^= modifiers; switch (key) { case Qt::Key_Shift: modifiers |= Qt::ShiftModifier; break; case Qt::Key_Control: modifiers |= Qt::ControlModifier; break; case Qt::Key_Alt: modifiers |= Qt::AltModifier; break; case Qt::Key_Meta: modifiers |= Qt::MetaModifier; break; default: break; } return modifiers; } const Shortcut* ShortcutController::shortcut(const QString& action) const { return m_items[action].get(); } QString ShortcutController::name(int index, const QString& parent) const { QStringList menu = m_actions->menuItems(parent.isNull() || parent[0] != '.' ? parent : parent.mid(1)); menu.removeAll({}); if (index >= menu.size()) { return {}; } return menu[index]; } QString ShortcutController::parent(const QString& action) const { return QString(".%1").arg(m_actions->menuFor(action)); } QString ShortcutController::visibleName(const QString& action) const { if (action.isNull()) { return {}; } QString name; if (action[0] == '.') { name = m_actions->menuName(action.mid(1)); } else { name = m_actions->getAction(action)->visibleName(); } return name.replace(QRegularExpression("&(.)"), "\\1"); } int ShortcutController::indexIn(const QString& action) const { QString name = m_actions->menuFor(action); QStringList menu = m_actions->menuItems(name); menu.removeAll({}); return menu.indexOf(action); } int ShortcutController::count(const QString& name) const { QStringList menu; if (name.isNull()) { menu = m_actions->menuItems(); } else if (name[0] != '.') { return 0; } else { menu = m_actions->menuItems(name.mid(1)); } menu.removeAll({}); return menu.count(); } Shortcut::Shortcut(Action* action) : m_action(action) { } void Shortcut::setShortcut(int shortcut) { if (m_shortcut == shortcut) { return; } m_shortcut = shortcut; emit shortcutChanged(shortcut); } void Shortcut::setButton(int button) { if (m_button == button) { return; } m_button = button; emit buttonChanged(button); } void Shortcut::setAxis(int axis, GamepadAxisEvent::Direction direction) { if (m_axis == axis && m_direction == direction) { return; } m_axis = axis; m_direction = direction; emit axisChanged(axis, direction); }