BibleTime
btshortcutseditor.cpp
Go to the documentation of this file.
1 /*********
2 *
3 * In the name of the Father, and of the Son, and of the Holy Spirit.
4 *
5 * This file is part of BibleTime's source code, https://bibletime.info/
6 *
7 * Copyright 1999-2021 by the BibleTime developers.
8 * The BibleTime source code is licensed under the GNU General Public License
9 * version 2.0.
10 *
11 **********/
12 
13 #include "btshortcutseditor.h"
14 
15 #include <QAbstractItemView>
16 #include <QAction>
17 #include <QDialog>
18 #include <QGroupBox>
19 #include <QHBoxLayout>
20 #include <QHeaderView>
21 #include <QKeySequence>
22 #include <QLabel>
23 #include <QList>
24 #include <QPushButton>
25 #include <QRadioButton>
26 #include <QSizePolicy>
27 #include <QSpacerItem>
28 #include <Qt>
29 #include <QtGlobal>
30 #include <QTableWidget>
31 #include <QTableWidgetItem>
32 #include <QVBoxLayout>
33 #include <utility>
34 #include "../displaywindow/btactioncollection.h"
35 #include "../../util/btconnect.h"
36 #include "btshortcutsdialog.h"
37 
38 
39 namespace {
40 
41 inline QString removeMnemonics(QString input) {
42  auto i = input.indexOf(QLatin1Char('&'));
43  if (i < 0)
44  return input;
45 
46  auto out = input.mid(0, i);
47  decltype(i) nextFragmentStart;
48  do {
49  nextFragmentStart = i + 1;
50  i = input.indexOf(QLatin1Char('&'), nextFragmentStart + 1);
51  out.append(input.mid(nextFragmentStart, i - nextFragmentStart));
52  } while (i >= 0);
53  return out;
54 }
55 
56 /** Widget for the first column of the BtShortcutsEditor holding extra
57  information about the action. */
59 
60 public: // methods:
61 
63  QKeySequence defaultKeys)
64  : m_action(action)
65  , m_defaultKeys(std::move(defaultKeys))
66  {
67  setText(removeMnemonics(action->text()));
68  setIcon(action->icon());
69  setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
70 
71  QList<QKeySequence> list = m_action->shortcuts();
72  if (list.count() > 0)
73  m_newFirstHotkey = list.at(0);
74  if (list.count() > 1)
75  m_newSecondHotkey = list.at(1);
76  }
77 
78  void commitChanges() {
79  if (!m_action)
80  return;
82  if (!m_newFirstHotkey.isEmpty())
83  list << m_newFirstHotkey;
84  if (!m_newSecondHotkey.isEmpty())
85  list << m_newSecondHotkey;
86 
87  m_action->setShortcuts(list);
88  }
89 
90  QKeySequence getDefaultKeys() const { return m_defaultKeys; }
91 
92  template <typename ... Args>
93  void setFirstHotkey(Args && ... args)
94  { m_newFirstHotkey = QKeySequence(std::forward<Args>(args)...); }
95 
96  template <typename ... Args>
97  void setSecondHotkey(Args && ... args)
98  { m_newSecondHotkey = QKeySequence(std::forward<Args>(args)...); }
99 
100  void deleteHotkeys() noexcept {
101  m_newFirstHotkey = QKeySequence();
102  m_newSecondHotkey = QKeySequence();
103  }
104 
105 private: // fields:
106 
108  QKeySequence m_newFirstHotkey;
109  QKeySequence m_newSecondHotkey;
110  QKeySequence m_defaultKeys;
111 
112 };
113 
114 // Get the shortcut editor item from the zeroth column of the table:
115 inline auto getShortcutsEditor(QTableWidget const & tableWidget, int const row)
116 { return dynamic_cast<BtShortcutsEditorItem *>(tableWidget.item(row, 0)); }
117 
118 } // anonymous namespace
119 
120 // ******************* BtShortcutsEditor *******************************************************
121 
123  : QWidget(parent), m_dlg(new BtShortcutsDialog(this)), m_table(nullptr), m_shortcutChooser(nullptr), m_noneButton(nullptr), m_defaultButton(nullptr),
124  m_customButton(nullptr), m_defaultLabelValue(nullptr), m_currentRow(-1) {
125 
126  QVBoxLayout * const vBox = new QVBoxLayout(this);
127  setLayout(vBox);
128 
129  // Create the action and shortcuts table:
130  m_table = new QTableWidget{this};
131  m_table->setColumnCount(3);
132  m_table->setAlternatingRowColors(true);
133  m_table->setSelectionBehavior(QAbstractItemView::SelectRows);
134  m_table->setSelectionMode(QAbstractItemView::SingleSelection);
135  m_table->setHorizontalHeaderLabels({tr("Action"),
136  tr("Shortcut"),
137  tr("Alternate")});
138  m_table->horizontalHeader()->setSectionResizeMode(QHeaderView::Interactive);
139  m_table->horizontalHeader()->resizeSection(0, 180);
140  m_table->horizontalHeader()->resizeSection(1, 100);
141  m_table->horizontalHeader()->setStretchLastSection(true);
142  m_table->verticalHeader()->setVisible(false);
143  m_table->setShowGrid(false);
144  BT_CONNECT(m_table, &QTableWidget::itemSelectionChanged,
146  vBox->addWidget(m_table);
147 
148  // Create the area below the table where the shortcuts are edited:
150  new QGroupBox(tr("Shortcut for selected action name"), this);
151  m_shortcutChooser->setFlat(false);
152  {
153  QVBoxLayout * const vLayout = new QVBoxLayout(m_shortcutChooser);
154  {
155  QHBoxLayout * const hLayout = new QHBoxLayout();
156  vLayout->addLayout(hLayout);
157 
158  m_noneButton = new QRadioButton(tr("None"), m_shortcutChooser);
159  hLayout->addWidget(m_noneButton);
160  BT_CONNECT(m_noneButton, &QRadioButton::clicked,
162 
164  new QRadioButton(tr("Default"), m_shortcutChooser);
165  hLayout->addWidget(m_defaultButton);
166  BT_CONNECT(
167  m_defaultButton, &QRadioButton::clicked,
169 
170  m_customButton = new QRadioButton(tr("Custom"), m_shortcutChooser);
171  hLayout->addWidget(m_customButton);
172  BT_CONNECT(m_customButton, &QRadioButton::clicked,
174 
175  m_customPushButton = new QPushButton(m_shortcutChooser);
176  m_customPushButton->setMinimumWidth(140);
177  BT_CONNECT(m_customPushButton, &QPushButton::clicked,
179  hLayout->addWidget(m_customPushButton);
180 
181  hLayout->addItem(new QSpacerItem(1,
182  1,
183  QSizePolicy::Expanding,
184  QSizePolicy::Minimum));
185  }
186 
187  QHBoxLayout * const hLayout = new QHBoxLayout();
188  vLayout->addLayout(hLayout);
189 
190  hLayout->addWidget(new QLabel(tr("Default key:"), m_shortcutChooser));
191 
193  hLayout->addWidget(m_defaultLabelValue);
194 
195  hLayout->addItem(new QSpacerItem(1,
196  1,
197  QSizePolicy::Expanding,
198  QSizePolicy::Minimum));
199  }
200  vBox->addWidget(m_shortcutChooser);
201 
202  for (auto * const action : collection->actions()) {
203  int const count = m_table->rowCount();
204  m_table->insertRow(count);
205 
206  {
207  auto * const item =
208  new BtShortcutsEditorItem(
209  action,
211  try {
212  /// \todo Remove this & hack and use Qt properties instead:
213  m_table->setItem(count, 0, item);
214  } catch (...) {
215  delete item;
216  throw;
217  }
218  }
219 
220  QList<QKeySequence> keys = action->shortcuts();
221 
222  {
223  QTableWidgetItem * const item = new QTableWidgetItem;
224  try {
225  item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
226  item->setToolTip(tr("Select to change key"));
227  if (keys.count() > 0)
228  item->setText(keys[0].toString());
229  m_table->setItem(count, 1, item);
230  } catch (...) {
231  delete item;
232  throw;
233  }
234  }
235 
236  {
237  QTableWidgetItem * const item = new QTableWidgetItem;
238  try {
239  item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
240  item->setToolTip(tr("Select to change key"));
241  if (keys.count() > 1)
242  item->setText(keys[1].toString());
243  m_table->setItem(count, 2, item);
244  } catch (...) {
245  delete item;
246  throw;
247  }
248  }
249  }
250  m_table->sortItems(0);
251  m_table->selectRow(0);
254  [this](QKeySequence const & keys) {
255  Q_EMIT keyChangeRequest(
256  m_table->item(m_currentRow, 0)->text(),
257  keys);
258  });
259 }
260 
261 // saves shortcut keys into the QAction
263  int const rows = m_table->rowCount();
264  for (int row = 0; row < rows; row++)
265  if (auto * const btItem = getShortcutsEditor(*m_table, row))
266  btItem->commitChanges();
267 }
268 
269 // called when a different action name row is selected
271  m_currentRow = m_table->currentRow();
272  auto & item = *getShortcutsEditor(*m_table, m_currentRow);
273  auto const defaultKeys = item.getDefaultKeys();
274 
275  m_defaultLabelValue->setText(defaultKeys.toString());
276 
277  QTableWidgetItem* item1 = m_table->item(m_currentRow, 1);
278  QString shortcut = item1->text();
279 
280  QTableWidgetItem* item2 = m_table->item(m_currentRow, 2);
281  QString alternate = item2->text();
282 
283  QString bothKeys = shortcut;
284  if (!alternate.isEmpty())
285  bothKeys = bothKeys + QStringLiteral("; ") + alternate;
286  m_customPushButton->setText(bothKeys);
287 
288  if ( bothKeys == defaultKeys.toString())
289  m_defaultButton->setChecked(true);
290  else if (bothKeys.isEmpty())
291  m_noneButton->setChecked(true);
292  else
293  m_customButton->setChecked(true);
294 }
295 
296 // called when the none radio button is clicked
298  Q_UNUSED(checked) /// \todo Is this correct?
299 
300  if (m_currentRow < 0)
301  return;
302  auto & item = *getShortcutsEditor(*m_table, m_currentRow);
303  m_customPushButton->setText(QString());
304  item.deleteHotkeys();
305  item.setFirstHotkey();
306  m_table->item(m_currentRow, 1)->setText(QString());
307  m_table->item(m_currentRow, 2)->setText(QString());
308 }
309 
310 // called when the default radio button is clicked
312  Q_UNUSED(checked) /// \todo Is this correct?
313 
314  if (m_currentRow < 0)
315  return;
316  auto & item = *getShortcutsEditor(*m_table, m_currentRow);
317  auto const defaultKeys = item.getDefaultKeys();
318  item.deleteHotkeys();
319  item.setFirstHotkey(defaultKeys);
320  m_customPushButton->setText(defaultKeys.toString());
321  m_table->item(m_currentRow, 1)->setText(defaultKeys.toString());
322  m_table->item(m_currentRow, 2)->setText(QString());
323 }
324 
325 // called when the custom radio button is clicked
327  Q_UNUSED(checked) /// \todo Is this correct?
328 
329  if (m_currentRow < 0)
330  return;
331 
332  QString priKeys = m_table->item(m_currentRow, 1)->text();
333  QString altKeys = m_table->item(m_currentRow, 2)->text();
334  m_dlg->setSecondKeys(altKeys);
335  m_dlg->setFirstKeys(priKeys);
336  int code = m_dlg->exec();
337  if (code == QDialog::Accepted) {
338  QString newPriKeys = m_dlg->getFirstKeys();
339  QString newAltKeys = m_dlg->getSecondKeys();
340  if (newPriKeys == newAltKeys)
341  newAltKeys = QString();
342  auto & item = *getShortcutsEditor(*m_table, m_currentRow);
343  item.setFirstHotkey(newPriKeys);
344  item.setSecondHotkey(newAltKeys);
345  m_table->item(m_currentRow, 1)->setText(newPriKeys);
346  m_table->item(m_currentRow, 2)->setText(newAltKeys);
347  }
348 }
349 
350 // used by application to complete the keyChangeRequest signal
351 // stores "keys" into the custom shortcuts dialog field
352 void BtShortcutsEditor::changeShortcutInDialog(QKeySequence const & keys)
353 { m_dlg->changeSelectedShortcut(keys); }
354 
355 // clears any shortcut keys in the table matching the specified keys
356 void BtShortcutsEditor::clearConflictWithKeys(QKeySequence const & keys) {
357  auto const keyString(keys.toString());
358  for (int row = 0; row < m_table->rowCount(); row++) {
359  auto & item = *getShortcutsEditor(*m_table, row);
360  if (m_table->item(row, 1)->text() == keyString) {
361  m_table->item(row, 1)->setText(QString());
362  item.setFirstHotkey();
363  }
364  if (m_table->item(row, 2)->text() == keyString) {
365  m_table->item(row, 2)->setText(QString());
366  item.setSecondHotkey();
367  }
368  }
369 }
370 
371 // finds any shortcut keys in the table matching the specified keys - returns the Action Name for it.
372 QString
373 BtShortcutsEditor::findConflictWithKeys(QKeySequence const & keys) const {
374  auto const keyString(keys.toString());
375  for (int i = 0; i < m_table->rowCount(); i++)
376  if ((m_table->item(i, 1)->text() == keyString)
377  || (m_table->item(i, 2)->text() == keyString))
378  return m_table->item(i, 0)->text();
379  return {};
380 }
#define BT_CONNECT(...)
Definition: btconnect.h:20
QMap< QString, QAction * > const & actions() const noexcept
static QKeySequence getDefaultShortcut(QAction &action)
void changeSelectedShortcut(QKeySequence const &keys)
void keyChangeRequest(QKeySequence const &keys)
void setFirstKeys(const QString &keys)
void setSecondKeys(const QString &keys)
BtShortcutsEditor(BtActionCollection *collection, QWidget *parent)
void customButtonClicked(bool checked)
void clearConflictWithKeys(QKeySequence const &keys)
void keyChangeRequest(QString const &actionName, QKeySequence const &keys)
void defaultButtonClicked(bool checked)
QRadioButton * m_noneButton
QTableWidget * m_table
QString findConflictWithKeys(QKeySequence const &keys) const
QRadioButton * m_customButton
void changeShortcutInDialog(QKeySequence const &keys)
QPushButton * m_customPushButton
QRadioButton * m_defaultButton
QGroupBox * m_shortcutChooser
QLabel * m_defaultLabelValue
void noneButtonClicked(bool checked)
BtShortcutsDialog * m_dlg
BtShortcutsEditorItem(QAction *action, QKeySequence defaultKeys)
auto getShortcutsEditor(QTableWidget const &tableWidget, int const row)