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-2025 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
39namespace {
40
41inline 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
60public: // 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
79 if (!m_action)
80 return;
81 QList<QKeySequence> list;
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
105private: // 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:
115inline 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);
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
352void BtShortcutsEditor::changeShortcutInDialog(QKeySequence const & keys)
354
355// clears any shortcut keys in the table matching the specified keys
356void 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.
372QString
373BtShortcutsEditor::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
void noneButtonClicked(bool checked)
BtShortcutsDialog * m_dlg
BtShortcutsEditorItem(QAction *action, QKeySequence defaultKeys)
auto getShortcutsEditor(QTableWidget const &tableWidget, int const row)