CustomizeKeyboardDialog.cpp 11 KB

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