swift
hotkeydialog.cpp
1 // SPDX-FileCopyrightText: Copyright (C) 2014 swift Project Community / Contributors
2 // SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-swift-pilot-client-1
3 
5 
6 #include <QFrame>
7 #include <QGroupBox>
8 #include <QItemSelection>
9 #include <QItemSelectionModel>
10 #include <QLayout>
11 #include <QLayoutItem>
12 #include <QList>
13 #include <QModelIndex>
14 #include <QModelIndexList>
15 #include <QPushButton>
16 #include <QString>
17 #include <QStringList>
18 #include <QTreeView>
19 #include <QVariant>
20 #include <QWidget>
21 #include <Qt>
22 #include <QtGlobal>
23 
24 #include "ui_hotkeydialog.h"
25 
27 #include "core/inputmanager.h"
28 #include "gui/guiapplication.h"
29 #include "gui/stylesheetutility.h"
30 #include "misc/icons.h"
31 #include "misc/identifier.h"
34 #include "misc/logmessage.h"
35 #include "misc/statusmessage.h"
36 
37 using namespace swift::misc;
38 using namespace swift::misc::input;
39 using namespace swift::core;
40 using namespace swift::gui::models;
41 
42 namespace swift::gui::components
43 {
44  CHotkeyDialog::CHotkeyDialog(const CActionHotkey &actionHotkey, const CIdentifierList &identifiers, QWidget *parent)
45  : QDialog(parent), ui(new Ui::CHotkeyDialog), m_actionHotkey(actionHotkey), m_actionModel(this)
46  {
47  setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
48 
49  ui->setupUi(this);
50  ui->qf_Advanced->hide();
51 
52  ui->pb_AdvancedMode->setIcon(CIcons::arrowMediumSouth16());
53  ui->tv_Actions->setModel(&m_actionModel);
54  selectAction();
55 
56  CHotkeyCombination combination = actionHotkey.getCombination();
57  if (!combination.isEmpty())
58  {
59  ui->pb_SelectedHotkey->setText(combination.toQString());
60  ui->pb_SelectedHotkey->setToolTip(combination.asStringWithDeviceNames());
61  }
62  else { ui->pb_SelectedHotkey->setToolTip("Press to select an new combination..."); }
63 
64  // get all remote identifiers in case there is no key for a remote machine yet
65  CIdentifierList registeredApplications;
67  {
68  registeredApplications = sGui->getIContextApplication()->getRegisteredApplications();
70  registeredApplications.push_back(appIdentifier);
71  }
72 
73  CIdentifierList machineIdentifiers(identifiers);
74  machineIdentifiers.push_back(registeredApplications); // add the registered applications
75 
76  if (actionHotkey.isValid()) { machineIdentifiers.push_back(actionHotkey.getApplicableMachine()); }
77  const CIdentifierList machineIdentifiersUnique = machineIdentifiers.getMachinesUnique();
78 
79  int index = -1;
80  for (const CIdentifier &app : machineIdentifiersUnique)
81  {
82  ui->cb_Identifier->addItem(app.getMachineName(), QVariant::fromValue(app));
83  if (m_actionHotkey.getApplicableMachine().hasSameMachineName(app))
84  {
85  index = ui->cb_Identifier->count() - 1;
86  }
87  }
88 
89  if (index < 0 && ui->cb_Identifier->count() > 0)
90  {
91  // if nothing was found
92  ui->cb_Identifier->setCurrentIndex(0);
93  }
94  else if (index != ui->cb_Identifier->currentIndex()) { ui->cb_Identifier->setCurrentIndex(index); }
95 
96  connect(ui->pb_AdvancedMode, &QPushButton::clicked, this, &CHotkeyDialog::advancedModeChanged);
97  connect(ui->pb_SelectedHotkey, &QPushButton::clicked, this, &CHotkeyDialog::captureHotkey);
98  connect(ui->pb_Accept, &QPushButton::clicked, this, &CHotkeyDialog::accept);
99  connect(ui->pb_Cancel, &QPushButton::clicked, this, &CHotkeyDialog::reject);
100  connect(ui->tv_Actions->selectionModel(), &QItemSelectionModel::selectionChanged, this,
101  &CHotkeyDialog::changeSelectedAction);
102  connect(ui->cb_Identifier, qOverload<int>(&QComboBox::currentIndexChanged), this,
103  &CHotkeyDialog::changeApplicableMachine);
104 
105  if (sGui && sGui->getInputManager())
106  {
108  &CHotkeyDialog::combinationSelectionChanged);
110  &CHotkeyDialog::combinationSelectionFinished);
111  }
112 
113  initStyleSheet();
114  }
115 
117 
118  CKeySelectionBox::CKeySelectionBox(QWidget *parent) : QComboBox(parent)
119  {
120  connect(this, qOverload<int>(&CKeySelectionBox::currentIndexChanged), this,
121  &CKeySelectionBox::updateSelectedIndex);
122  }
123 
125  {
126  m_oldIndex = index;
127  setCurrentIndex(m_oldIndex);
128  }
129 
130  void CKeySelectionBox::updateSelectedIndex(int index)
131  {
132  emit keySelectionChanged(m_oldIndex, index);
133  m_oldIndex = index;
134  }
135 
137  {
138  if (!sGui) { return; }
139  const QString s = sGui->getStyleSheetUtility().styles(
141  this->setStyleSheet(s);
142  }
143 
145  QWidget *parent)
146  {
147  CHotkeyDialog editDialog(initial, identifiers, parent);
148  editDialog.setWindowModality(Qt::WindowModal);
149  if (editDialog.exec()) { return editDialog.getSelectedActionHotkey(); }
150  return {};
151  }
152 
153  void CHotkeyDialog::advancedModeChanged()
154  {
155  // if (m_actionHotkey.getCombination().isEmpty()) return;
156  if (!ui->qf_Advanced->isVisible())
157  {
158  setupAdvancedFrame();
159  ui->qf_Advanced->show();
160  ui->pb_AdvancedMode->setIcon(CIcons::arrowMediumNorth16());
161  }
162  else
163  {
164  ui->pb_AdvancedMode->setIcon(CIcons::arrowMediumSouth16());
165  ui->qf_Advanced->hide();
166  ui->gb_Hotkey->resize(0, 0);
167  }
168  }
169 
170  void CHotkeyDialog::captureHotkey()
171  {
172  if (!sApp || sApp->isShuttingDown()) { return; }
173  ui->pb_SelectedHotkey->setText("Press any key/button...");
174  ui->pb_SelectedHotkey->setToolTip({});
176  }
177 
178  void CHotkeyDialog::combinationSelectionChanged(const CHotkeyCombination &combination)
179  {
180  ui->pb_SelectedHotkey->setText(combination.toQString());
181  ui->pb_SelectedHotkey->setToolTip(combination.asStringWithDeviceNames());
182  }
183 
184  void CHotkeyDialog::combinationSelectionFinished(const CHotkeyCombination &combination)
185  {
186  m_actionHotkey.setCombination(combination);
187  synchronize();
188  }
189 
190  void CHotkeyDialog::changeSelectedAction(const QItemSelection &selected, const QItemSelection &deselected)
191  {
192  Q_UNUSED(deselected)
193  if (selected.indexes().isEmpty()) { return; }
194  const auto index = selected.indexes().first();
195  m_actionHotkey.setAction(index.data(CActionModel::ActionRole).toString());
196  }
197 
198  CKeySelectionBox *CHotkeyDialog::addSelectionBox(const CKeyboardKeyList &allSupportedKeys,
199  const CKeyboardKey &keyboardKey)
200  {
201  int currentIndex = 0;
202  const bool select = !keyboardKey.isUnknown();
203 
204  CKeySelectionBox *ksb = new CKeySelectionBox(ui->qf_Advanced);
205  ksb->addItem(noKeyButton(), QVariant::fromValue(CKeyboardKey())); // at front
206  for (const CKeyboardKey &supportedKey : allSupportedKeys)
207  {
208  ksb->addItem(supportedKey.toQString(), QVariant::fromValue(supportedKey));
209  if (select && supportedKey == keyboardKey) { currentIndex = ksb->count() - 1; }
210  }
211 
212  ksb->setSelectedIndex(currentIndex);
213  ksb->addItem(noKeyButton(), QVariant::fromValue(CKeyboardKey())); // at back (easier to find it is there twice)
214 
215  ui->qf_Advanced->layout()->addWidget(ksb);
216  const int position = ui->qf_Advanced->layout()->count() - 1;
217  ksb->setProperty("position", position);
218  connect(ksb, &CKeySelectionBox::keySelectionChanged, this, &CHotkeyDialog::advancedKeyChanged);
219 
220  return ksb;
221  }
222 
223  CKeySelectionBox *CHotkeyDialog::addSelectionBox(const CJoystickButtonList &allAvailableButtons,
224  const CJoystickButton &joystickButton)
225  {
226  int currentIndex = -1;
227 
228  CKeySelectionBox *ksb = new CKeySelectionBox(ui->qf_Advanced);
229  ksb->addItem(noKeyButton(), QVariant::fromValue(CJoystickButton())); // at front
230  for (const CJoystickButton &availableButton : allAvailableButtons)
231  {
232  ksb->addItem(availableButton.toQString(), QVariant::fromValue(availableButton));
233  if (availableButton == joystickButton)
234  {
235  currentIndex = ksb->count() - 1;
236  ksb->setToolTip(joystickButton.getButtonAsStringWithDeviceName());
237  }
238  }
239 
240  ksb->setSelectedIndex(currentIndex);
241  ksb->addItem(noKeyButton(),
242  QVariant::fromValue(CJoystickButton())); // at back (easier to find it is there twice)
243 
244  ui->qf_Advanced->layout()->addWidget(ksb);
245  const int position = ui->qf_Advanced->layout()->count() - 1;
246  ksb->setProperty("position", position);
247  connect(ksb, &CKeySelectionBox::keySelectionChanged, this, &CHotkeyDialog::advancedKeyChanged);
248 
249  return ksb;
250  }
251 
252  void CHotkeyDialog::changeApplicableMachine(int index)
253  {
254  Q_UNUSED(index)
255  const QVariant userData = ui->cb_Identifier->currentData();
256  Q_ASSERT(userData.canConvert<CIdentifier>());
257  m_actionHotkey.setApplicableMachine(userData.value<CIdentifier>());
258  }
259 
261  {
262  if (m_actionHotkey.getApplicableMachine().getMachineName().isEmpty())
263  {
264  CLogMessage(this).validationWarning(u"Missing hotkey '%1'") << ui->gb_Machine->title();
265  return;
266  }
267 
268  if (m_actionHotkey.getCombination().isEmpty())
269  {
270  CLogMessage(this).validationWarning(u"Missing hotkey '%1'") << ui->gb_Hotkey->title();
271  return;
272  }
273 
274  if (m_actionHotkey.getAction().isEmpty())
275  {
276  CLogMessage(this).validationWarning(u"Missing hotkey '%1'") << ui->gb_Action->title();
277  return;
278  }
279 
280  QDialog::accept();
281  }
282 
283  void CHotkeyDialog::synchronize()
284  {
285  synchronizeSimpleSelection();
286  synchronizeAdvancedSelection();
287  }
288 
289  void CHotkeyDialog::synchronizeSimpleSelection()
290  {
291  CHotkeyCombination combination = m_actionHotkey.getCombination();
292  ui->pb_SelectedHotkey->setText(combination.toQString());
293  ui->pb_SelectedHotkey->setToolTip(combination.asStringWithDeviceNames());
294  }
295 
296  void CHotkeyDialog::synchronizeAdvancedSelection()
297  {
298  if (ui->qf_Advanced->isVisible()) { setupAdvancedFrame(); }
299  }
300 
301  void CHotkeyDialog::setupAdvancedFrame()
302  {
303  if (!sGui || sGui->isShuttingDown()) { return; }
304  this->clearAdvancedFrame();
305  const CKeyboardKeyList allSupportedKeys = CKeyboardKeyList::allSupportedKeys();
307 
308  const CKeyboardKeyList keyboardKeys = m_actionHotkey.getCombination().getKeyboardKeys();
309  int c = 0;
310 
311  for (const CKeyboardKey &keyboardKey : keyboardKeys)
312  {
313  this->addSelectionBox(allSupportedKeys, keyboardKey);
314  c++;
315  }
316 
317  const CJoystickButtonList joystickButtons = m_actionHotkey.getCombination().getJoystickButtons();
318  for (const CJoystickButton &joystickButton : joystickButtons)
319  {
320  this->addSelectionBox(allAvailableButtons, joystickButton);
321  c++;
322  }
323 
324  // add one box more so we can add keys/buttons
325  if (c < 2) { this->addSelectionBox(allSupportedKeys); }
326  }
327 
328  void CHotkeyDialog::clearAdvancedFrame()
329  {
330  QLayout *layout = ui->qf_Advanced->layout();
331  QLayoutItem *child;
332 
333  while ((child = layout->takeAt(0)) != nullptr)
334  {
335  if (child->widget()) child->widget()->deleteLater();
336  delete child;
337  }
338  }
339 
340  void CHotkeyDialog::advancedKeyChanged(int oldIndex, int newIndex)
341  {
342  const CKeySelectionBox *ksb = qobject_cast<CKeySelectionBox *>(sender());
343  Q_ASSERT(ksb);
344 
345  if (ksb->itemData(oldIndex).canConvert<CKeyboardKey>() && ksb->itemData(newIndex).canConvert<CKeyboardKey>())
346  {
347  CKeyboardKey oldKey = ksb->itemData(oldIndex).value<CKeyboardKey>();
348  CKeyboardKey newKey = ksb->itemData(newIndex).value<CKeyboardKey>();
349 
350  CHotkeyCombination combination = m_actionHotkey.getCombination();
351  if (newKey.isUnknown()) { combination.removeKeyboardKey(oldKey); }
352  else { combination.replaceKey(oldKey, newKey); }
353  m_actionHotkey.setCombination(combination);
354  }
355 
356  if (ksb->itemData(oldIndex).canConvert<CJoystickButton>() &&
357  ksb->itemData(newIndex).canConvert<CJoystickButton>())
358  {
359  CJoystickButton oldButton = ksb->itemData(oldIndex).value<CJoystickButton>();
360  CJoystickButton newButton = ksb->itemData(newIndex).value<CJoystickButton>();
361 
362  CHotkeyCombination combination = m_actionHotkey.getCombination();
363  if (!newButton.isValid()) { combination.removeJoystickButton(oldButton); }
364  else { combination.replaceButton(oldButton, newButton); }
365  m_actionHotkey.setCombination(combination);
366  }
367 
368  ui->pb_SelectedHotkey->setText(m_actionHotkey.getCombination().toQString());
369  ui->pb_SelectedHotkey->setToolTip(m_actionHotkey.getCombination().asStringWithDeviceNames());
370  }
371 
372  void CHotkeyDialog::selectAction()
373  {
374  if (m_actionHotkey.getAction().isEmpty()) { return; }
375  const QStringList tokens = m_actionHotkey.getAction().split("/", Qt::SkipEmptyParts);
376  QModelIndex parentIndex = QModelIndex();
377 
378  for (const QString &token : tokens)
379  {
380  const QModelIndex startIndex = m_actionModel.index(0, 0, parentIndex);
381  const QModelIndexList indexList =
382  m_actionModel.match(startIndex, Qt::DisplayRole, QVariant::fromValue(token));
383  if (indexList.isEmpty()) { return; }
384  parentIndex = indexList.first();
385  ui->tv_Actions->expand(parentIndex);
386  }
387 
388  QItemSelectionModel *selectionModel = ui->tv_Actions->selectionModel();
389  selectionModel->select(parentIndex, QItemSelectionModel::Select);
390  }
391 
392  const QString &CHotkeyDialog::noKeyButton()
393  {
394  static const QString k = "[none]";
395  return k;
396  }
397 } // namespace swift::gui::components
SWIFT_CORE_EXPORT swift::core::CApplication * sApp
Single instance of application object.
Definition: application.cpp:71
CInputManager * getInputManager() const
The input manager, if available.
Definition: application.h:302
const context::IContextApplication * getIContextApplication() const
Direct access to contexts if a CCoreFacade has been initialized.
bool isShuttingDown() const
Is application shutting down?
void combinationSelectionFinished(const swift::misc::input::CHotkeyCombination &combination)
Combination selection has finished.
void combinationSelectionChanged(const swift::misc::input::CHotkeyCombination &combination)
Selected combination has changed.
void startCapture()
Select a key combination as hotkey. This method returns immediatly. Listen for signals combinationSel...
swift::misc::input::CJoystickButtonList getAllAvailableJoystickButtons() const
Get all available joystick buttons.
virtual misc::CIdentifier getApplicationIdentifier() const =0
Identifier of application, remote side if distributed.
virtual misc::CIdentifierList getRegisteredApplications() const =0
All registered applications.
const CStyleSheetUtility & getStyleSheetUtility() const
Style sheet handling.
static const QString & fileNameStandardWidget()
File name for standard widgets.
QString styles(const QStringList &fileNames) const
Multiple styles concatenated.
static const QString & fileNameFonts()
File name fonts.qss.
void initStyleSheet()
Init style sheet.
swift::misc::input::CActionHotkey getSelectedActionHotkey() const
Get hotkey selected by user.
Definition: hotkeydialog.h:75
static swift::misc::input::CActionHotkey getActionHotkey(const swift::misc::input::CActionHotkey &initial, const swift::misc::CIdentifierList &identifiers, QWidget *parent=nullptr)
Runs the hotkey dialog and returns the result.
CKeySelectionBox(QWidget *parent=nullptr)
Constructor.
void keySelectionChanged(int oldIndex, int newIndex)
User has changed the selection.
void setSelectedIndex(int index)
Set key with index as selected.
QModelIndex index(int row, int column, const QModelIndex &parent=QModelIndex()) const
Definition: actionmodel.cpp:57
Value object encapsulating information identifying a component of a modular distributed swift process...
Definition: identifier.h:29
const QString & getMachineName() const
Machine name.
Definition: identifier.h:103
bool hasSameMachineName(const CIdentifier &other) const
Check if the other identifier has the same machine name.
Definition: identifier.cpp:158
Value object encapsulating a list of object identifiers.
CIdentifierList getMachinesUnique() const
Get a list of identifiers reduced to maximum one per machine. If there is more than one per machine,...
Class for emitting a log message.
Definition: logmessage.h:27
Derived & validationWarning(const char16_t(&format)[N])
Set the severity to warning, providing a format string, and adding the validation category.
void push_back(const T &value)
Appends an element at the end of the sequence.
Definition: sequence.h:305
Value object encapsulating a action hotkey.
Definition: actionhotkey.h:25
void setApplicableMachine(const CIdentifier &identifier)
Set applicable machine.
Definition: actionhotkey.h:64
void setCombination(const CHotkeyCombination &combination)
Set hotkey combination.
const CIdentifier & getApplicableMachine() const
Get applicable machine.
Definition: actionhotkey.h:67
void setAction(const QString &action)
Set function.
Definition: actionhotkey.h:58
const QString & getAction() const
Action.
Definition: actionhotkey.h:55
bool isValid() const
Is hotkey valid?
Definition: actionhotkey.h:85
const CHotkeyCombination & getCombination() const
Get hotkey combination.
Definition: actionhotkey.h:49
Value object representing hotkey sequence.
void removeJoystickButton(CJoystickButton button)
Remove joystick button.
CJoystickButtonList getJoystickButtons() const
Get joystick buttons.
QString asStringWithDeviceNames() const
Returns the button name with the device name prefix.
CKeyboardKeyList getKeyboardKeys() const
Get keyboard keys.
bool isEmpty() const
Is sequence empty?
void removeKeyboardKey(CKeyboardKey key)
Remove keyboard key.
Value object representing a joystick button.
QString getButtonAsStringWithDeviceName() const
Get button as String including its device name.
Value object encapsulating a list of joystick buttons.
Value object representing a keyboard key.
Definition: keyboardkey.h:25
bool isUnknown() const
Is unknown?
Definition: keyboardkey.h:57
Value object encapsulating a list of keyboard keys.
QString toQString(bool i18n=false) const
Cast as QString.
Definition: mixinstring.h:76
SWIFT_GUI_EXPORT swift::gui::CGuiApplication * sGui
Single instance of GUI application object.
Backend services of the swift project, like dealing with the network or the simulators.
Definition: actionbind.cpp:7
High level reusable GUI components.
Definition: aboutdialog.cpp:13
Models to be used with views, mainly QTableView.
Free functions in swift::misc.