CustomizeKeyboardDialog.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include "EditorDefs.h"
  9. #include "CustomizeKeyboardDialog.h"
  10. // Qt
  11. #include <QMenu>
  12. #include <QMenuBar>
  13. #include <QMessageBox>
  14. // AzQtComponents
  15. #include <AzQtComponents/Components/WindowDecorationWrapper.h>
  16. AZ_PUSH_DISABLE_DLL_EXPORT_MEMBER_WARNING
  17. #include "ui_CustomizeKeyboardDialog.h"
  18. AZ_POP_DISABLE_DLL_EXPORT_MEMBER_WARNING
  19. using namespace AzQtComponents;
  20. namespace
  21. {
  22. enum CustomRole
  23. {
  24. ActionRole = Qt::UserRole,
  25. KeySequenceRole
  26. };
  27. }
  28. class NestedQAction
  29. {
  30. public:
  31. NestedQAction()
  32. : m_action(nullptr)
  33. {
  34. }
  35. NestedQAction(const QString& path, QAction* action)
  36. : m_path(path)
  37. , m_action(action)
  38. {
  39. }
  40. QString Path() const { return m_path; }
  41. QAction* Action() const { return m_action; }
  42. private:
  43. QString m_path;
  44. QAction* m_action;
  45. };
  46. QVector<NestedQAction> GetAllActionsForMenu(const QMenu* menu, const QString& path)
  47. {
  48. QList<QAction*> menuActions = menu->actions();
  49. QVector<NestedQAction> actions;
  50. actions.reserve(menuActions.size());
  51. foreach(QAction * action, menuActions)
  52. {
  53. if (action->menu() != nullptr)
  54. {
  55. QString newPath = path + action->text() + QStringLiteral(" | ");
  56. newPath = RemoveAcceleratorAmpersands(newPath);
  57. QVector<NestedQAction> subMenuActions = GetAllActionsForMenu(action->menu(), newPath);
  58. actions.reserve(actions.size() + subMenuActions.size());
  59. actions += subMenuActions;
  60. }
  61. else if (!action->isSeparator())
  62. {
  63. actions.push_back(NestedQAction(path + RemoveAcceleratorAmpersands(action->text()), action));
  64. }
  65. }
  66. return actions;
  67. }
  68. class MenuActionsModel
  69. : public QAbstractListModel
  70. {
  71. public:
  72. MenuActionsModel(QObject* parent = nullptr)
  73. : QAbstractListModel(parent)
  74. {
  75. }
  76. ~MenuActionsModel() override {}
  77. int rowCount([[maybe_unused]] const QModelIndex& parent = QModelIndex()) const override
  78. {
  79. return m_actions.size();
  80. }
  81. QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override
  82. {
  83. if (index.row() < 0 || index.row() >= m_actions.size())
  84. {
  85. return {};
  86. }
  87. switch (role)
  88. {
  89. case Qt::DisplayRole:
  90. {
  91. return m_actions[index.row()].Path();
  92. }
  93. case ActionRole:
  94. return QVariant::fromValue(m_actions[index.row()].Action());
  95. }
  96. return {};
  97. }
  98. void Reset(const QVector<NestedQAction>& actions)
  99. {
  100. beginResetModel();
  101. m_actions = actions;
  102. endResetModel();
  103. }
  104. private:
  105. QVector<NestedQAction> m_actions;
  106. };
  107. class ActionShortcutsModel
  108. : public QAbstractListModel
  109. {
  110. public:
  111. ActionShortcutsModel(QObject* parent = nullptr)
  112. : QAbstractListModel(parent)
  113. , m_action(nullptr)
  114. {
  115. }
  116. ~ActionShortcutsModel() override {}
  117. int rowCount([[maybe_unused]] const QModelIndex& parent = QModelIndex()) const override
  118. {
  119. if (m_action)
  120. {
  121. return m_action->shortcuts().size();
  122. }
  123. else
  124. {
  125. return 0;
  126. }
  127. }
  128. QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override
  129. {
  130. auto shortcuts = m_action->shortcuts();
  131. if (index.row() < 0 || index.row() >= shortcuts.size())
  132. {
  133. return {};
  134. }
  135. switch (role)
  136. {
  137. case Qt::DisplayRole:
  138. return shortcuts.at(index.row()).toString();
  139. case KeySequenceRole:
  140. return QVariant::fromValue(shortcuts.at(index.row()));
  141. }
  142. return {};
  143. }
  144. void RemoveAll()
  145. {
  146. auto shortcuts = m_action->shortcuts();
  147. beginRemoveRows({}, 0, shortcuts.size() - 1);
  148. shortcuts.clear();
  149. m_action->setShortcuts(shortcuts);
  150. endRemoveRows();
  151. }
  152. void Remove(const QKeySequence& sequence)
  153. {
  154. auto shortcuts = m_action->shortcuts();
  155. int index = shortcuts.indexOf(sequence);
  156. if (index >= 0)
  157. {
  158. beginRemoveRows({}, index, index);
  159. shortcuts.removeAll(sequence);
  160. m_action->setShortcuts(shortcuts);
  161. endRemoveRows();
  162. }
  163. }
  164. QModelIndex Add(const QKeySequence& sequence)
  165. {
  166. auto shortcuts = m_action->shortcuts();
  167. int position = shortcuts.indexOf(sequence);
  168. if (-1 == position)
  169. {
  170. position = shortcuts.size();
  171. beginInsertRows({}, position, position);
  172. shortcuts.append(sequence);
  173. m_action->setShortcuts(shortcuts);
  174. endInsertRows();
  175. }
  176. return index(position);
  177. }
  178. bool Contains(const QKeySequence& sequence) const
  179. {
  180. return m_action->shortcuts().contains(sequence);
  181. }
  182. void Reset(QAction& action)
  183. {
  184. beginResetModel();
  185. m_action = &action;
  186. endResetModel();
  187. }
  188. private:
  189. QAction* m_action;
  190. };
  191. CustomizeKeyboardDialog::CustomizeKeyboardDialog(KeyboardCustomizationSettings& settings, QWidget* parent /* = nullptr */)
  192. : QDialog(new WindowDecorationWrapper(WindowDecorationWrapper::OptionAutoAttach | WindowDecorationWrapper::OptionAutoTitleBarButtons, parent))
  193. , m_ui(new Ui::CustomizeKeyboardDialog)
  194. , m_settings(settings)
  195. , m_settingsSnapshot(m_settings.CreateSnapshot())
  196. {
  197. m_ui->setupUi(this);
  198. m_menuActionsModel = new MenuActionsModel(this);
  199. m_actionShortcutsModel = new ActionShortcutsModel(this);
  200. m_ui->commandsView->setModel(m_menuActionsModel);
  201. m_ui->shortcutsView->setModel(m_actionShortcutsModel);
  202. QStringList categories = BuildModels(parent);
  203. connect(m_ui->categories, &QComboBox::currentTextChanged, this, &CustomizeKeyboardDialog::CategoryChanged);
  204. connect(m_ui->commandsView->selectionModel(), &QItemSelectionModel::currentChanged, this, &CustomizeKeyboardDialog::CommandSelectionChanged);
  205. connect(m_ui->shortcutsView->selectionModel(), &QItemSelectionModel::currentChanged, this, &CustomizeKeyboardDialog::ShortcutsViewSelectionChanged);
  206. connect(m_actionShortcutsModel, &QAbstractItemModel::rowsRemoved, this, &CustomizeKeyboardDialog::ShortcutsViewDataChanged);
  207. connect(m_actionShortcutsModel, &QAbstractItemModel::rowsInserted, this, &CustomizeKeyboardDialog::ShortcutsViewDataChanged);
  208. connect(m_ui->keySequenceEdit, &QKeySequenceEdit::editingFinished, this, &CustomizeKeyboardDialog::KeySequenceEditingFinished);
  209. connect(m_ui->assignButton, &QPushButton::clicked, this, &CustomizeKeyboardDialog::AssignButtonClicked);
  210. connect(m_ui->removeButton, &QPushButton::clicked, this, &CustomizeKeyboardDialog::ShortcutRemoved);
  211. connect(m_ui->clearButton, &QPushButton::clicked, m_actionShortcutsModel, &ActionShortcutsModel::RemoveAll);
  212. connect(m_ui->buttonBox, &QDialogButtonBox::clicked, this, &CustomizeKeyboardDialog::DialogButtonClicked);
  213. connect(this, &QDialog::rejected, this, [&]() { m_settings.Load(m_settingsSnapshot); });
  214. m_ui->categories->addItems(categories);
  215. }
  216. CustomizeKeyboardDialog::~CustomizeKeyboardDialog()
  217. {
  218. }
  219. QStringList CustomizeKeyboardDialog::BuildModels(QWidget* parent)
  220. {
  221. QMenuBar* menuBar = parent->findChild<QMenuBar*>();
  222. QList<QAction*> menuBarActions = menuBar->actions();
  223. QStringList categories;
  224. foreach(QAction * menuAction, menuBarActions)
  225. {
  226. QString category = RemoveAcceleratorAmpersands(menuAction->text());
  227. categories.append(category);
  228. QMenu* menu = menuAction->menu();
  229. m_menuActions[category] = GetAllActionsForMenu(menu, QString());
  230. }
  231. return categories;
  232. }
  233. void CustomizeKeyboardDialog::CategoryChanged(const QString& category)
  234. {
  235. m_menuActionsModel->Reset(m_menuActions[category]);
  236. // Must reset the shortcut sequence text box back to disabled state
  237. // if the category is changed
  238. m_ui->keySequenceEdit->setEnabled(false);
  239. m_ui->commandsView->scrollToTop();
  240. }
  241. void CustomizeKeyboardDialog::CommandSelectionChanged(const QModelIndex& current, [[maybe_unused]] const QModelIndex& previous)
  242. {
  243. QAction* action = current.data(ActionRole).value<QAction*>();
  244. m_actionShortcutsModel->Reset(*action);
  245. m_ui->removeButton->setEnabled(false);
  246. m_ui->clearButton->setEnabled(m_actionShortcutsModel->rowCount() > 0);
  247. m_ui->keySequenceEdit->setEnabled(true);
  248. const auto& description = action->statusTip().size() > 0 ? action->statusTip() : action->toolTip();
  249. m_ui->descriptionLabel->setText(description);
  250. m_ui->keySequenceEdit->clear();
  251. }
  252. void CustomizeKeyboardDialog::ShortcutsViewSelectionChanged(const QModelIndex& current, [[maybe_unused]] const QModelIndex& previous)
  253. {
  254. m_ui->removeButton->setEnabled(current.isValid());
  255. }
  256. void CustomizeKeyboardDialog::ShortcutsViewDataChanged()
  257. {
  258. m_ui->clearButton->setEnabled(m_actionShortcutsModel->rowCount() > 0);
  259. }
  260. void CustomizeKeyboardDialog::ShortcutRemoved()
  261. {
  262. auto index = m_ui->shortcutsView->selectionModel()->selectedIndexes().first();
  263. m_actionShortcutsModel->Remove(index.data(KeySequenceRole).value<QKeySequence>());
  264. }
  265. void CustomizeKeyboardDialog::KeySequenceEditingFinished()
  266. {
  267. auto keySequence = m_ui->keySequenceEdit->keySequence();
  268. m_ui->assignButton->setEnabled(!keySequence.isEmpty() && !m_actionShortcutsModel->Contains(keySequence));
  269. }
  270. void CustomizeKeyboardDialog::AssignButtonClicked()
  271. {
  272. auto sequence = m_ui->keySequenceEdit->keySequence();
  273. m_ui->keySequenceEdit->clear();
  274. auto currentAction = m_settings.FindActionForShortcut(sequence);
  275. if (currentAction)
  276. {
  277. auto result = QMessageBox::warning(
  278. this,
  279. tr("Shortcut already in use"),
  280. tr("%1 is currently assigned to '%2'.\n\nAssign and replace?")
  281. .arg(sequence.toString()).arg(RemoveAcceleratorAmpersands(currentAction->text())),
  282. QMessageBox::Yes | QMessageBox::No,
  283. QMessageBox::No);
  284. if (result == QMessageBox::No)
  285. {
  286. m_ui->keySequenceEdit->setFocus();
  287. return;
  288. }
  289. //remove this sequence from the current shortcut
  290. auto shortcuts = currentAction->shortcuts();
  291. shortcuts.removeAll(sequence);
  292. currentAction->setShortcuts(shortcuts);
  293. }
  294. QModelIndex index = m_actionShortcutsModel->Add(sequence);
  295. m_ui->shortcutsView->selectionModel()->setCurrentIndex(index, QItemSelectionModel::Clear | QItemSelectionModel::SelectCurrent);
  296. m_ui->assignButton->setEnabled(false);
  297. m_ui->removeButton->setFocus();
  298. }
  299. void CustomizeKeyboardDialog::DialogButtonClicked(const QAbstractButton* button)
  300. {
  301. if (button == m_ui->buttonBox->button(QDialogButtonBox::RestoreDefaults))
  302. {
  303. auto result = QMessageBox::question(
  304. this,
  305. tr("Restore Default Keyboard Shortcuts"),
  306. tr("Are you sure you wish to restore all keyboard shortcuts to factory defaults?"));
  307. if (result == QMessageBox::Yes)
  308. {
  309. m_settings.LoadDefaults();
  310. }
  311. }
  312. else if (button == m_ui->buttonBox->button(QDialogButtonBox::Close))
  313. {
  314. m_settings.Save();
  315. accept();
  316. }
  317. else if (button == m_ui->buttonBox->button(QDialogButtonBox::Cancel))
  318. {
  319. m_settings.Load(m_settingsSnapshot);
  320. reject();
  321. }
  322. }
  323. #include <moc_CustomizeKeyboardDialog.cpp>