1// Copyright (C) 2016 The Qt Company Ltd.
2// Copyright (C) 2013 Ivan Komissarov.
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
4
5#include "qkeysequenceedit.h"
6#include "qkeysequenceedit_p.h"
7
8#include "qboxlayout.h"
9#include "qlineedit.h"
10#include <private/qkeymapper_p.h>
11
12QT_BEGIN_NAMESPACE
13
14static_assert(QKeySequencePrivate::MaxKeyCount == 4); // assumed by the code around here
15
16void QKeySequenceEditPrivate::init()
17{
18 Q_Q(QKeySequenceEdit);
19
20 lineEdit = new QLineEdit(q);
21 lineEdit->setObjectName(QStringLiteral("qt_keysequenceedit_lineedit"));
22 lineEdit->setClearButtonEnabled(false);
23 q->connect(sender: lineEdit, signal: &QLineEdit::textChanged, slot: [q](const QString& text) {
24 // Clear the shortcut if the user clicked on the clear icon
25 if (text.isEmpty())
26 q->clear();
27 });
28
29 keyNum = 0;
30 prevKey = -1;
31 releaseTimer = 0;
32 finishingKeyCombinations = {Qt::Key_Tab, Qt::Key_Backtab};
33
34 QVBoxLayout *layout = new QVBoxLayout(q);
35 layout->setContentsMargins(left: 0, top: 0, right: 0, bottom: 0);
36 layout->addWidget(lineEdit);
37
38 std::fill_n(key, QKeySequencePrivate::MaxKeyCount, QKeyCombination::fromCombined(combined: 0));
39
40 lineEdit->setFocusProxy(q);
41 lineEdit->installEventFilter(filterObj: q);
42 resetState();
43
44 q->setSizePolicy(hor: QSizePolicy::Expanding, ver: QSizePolicy::Fixed);
45 q->setFocusPolicy(Qt::StrongFocus);
46 q->setAttribute(Qt::WA_MacShowFocusRect, on: true);
47 q->setAttribute(Qt::WA_InputMethodEnabled, on: false);
48}
49
50int QKeySequenceEditPrivate::translateModifiers(Qt::KeyboardModifiers state, const QString &text)
51{
52 Q_UNUSED(text);
53 int result = 0;
54 if (state & Qt::ControlModifier)
55 result |= Qt::CTRL;
56 if (state & Qt::MetaModifier)
57 result |= Qt::META;
58 if (state & Qt::AltModifier)
59 result |= Qt::ALT;
60 return result;
61}
62
63void QKeySequenceEditPrivate::resetState()
64{
65 Q_Q(QKeySequenceEdit);
66
67 if (releaseTimer) {
68 q->killTimer(id: releaseTimer);
69 releaseTimer = 0;
70 }
71 prevKey = -1;
72 lineEdit->setText(keySequence.toString(format: QKeySequence::NativeText));
73 lineEdit->setPlaceholderText(QKeySequenceEdit::tr(s: "Press shortcut"));
74}
75
76void QKeySequenceEditPrivate::finishEditing()
77{
78 Q_Q(QKeySequenceEdit);
79
80 resetState();
81 emit q->keySequenceChanged(keySequence);
82 emit q->editingFinished();
83}
84
85/*!
86 \class QKeySequenceEdit
87 \brief The QKeySequenceEdit widget allows to input a QKeySequence.
88
89 \inmodule QtWidgets
90
91 \since 5.2
92
93 This widget lets the user choose a QKeySequence, which is usually used as
94 a shortcut. The recording is initiated when the widget receives the focus
95 and ends one second after the user releases the last key.
96
97 \sa QKeySequenceEdit::keySequence
98*/
99
100/*!
101 Constructs a QKeySequenceEdit widget with the given \a parent.
102*/
103QKeySequenceEdit::QKeySequenceEdit(QWidget *parent)
104 : QKeySequenceEdit(*new QKeySequenceEditPrivate, parent, { })
105{
106}
107
108/*!
109 Constructs a QKeySequenceEdit widget with the given \a keySequence and \a parent.
110*/
111QKeySequenceEdit::QKeySequenceEdit(const QKeySequence &keySequence, QWidget *parent)
112 : QKeySequenceEdit(parent)
113{
114 setKeySequence(keySequence);
115}
116
117/*!
118 \internal
119*/
120QKeySequenceEdit::QKeySequenceEdit(QKeySequenceEditPrivate &dd, QWidget *parent, Qt::WindowFlags f) :
121 QWidget(dd, parent, f)
122{
123 Q_D(QKeySequenceEdit);
124 d->init();
125}
126
127/*!
128 Destroys the QKeySequenceEdit object.
129*/
130QKeySequenceEdit::~QKeySequenceEdit()
131{
132}
133
134/*!
135 \property QKeySequenceEdit::keySequence
136
137 \brief This property contains the currently chosen key sequence.
138
139 The shortcut can be changed by the user or via setter function.
140
141 \note If the QKeySequence is longer than the maximumSequenceLength
142 property, the key sequence is truncated.
143*/
144QKeySequence QKeySequenceEdit::keySequence() const
145{
146 Q_D(const QKeySequenceEdit);
147
148 return d->keySequence;
149}
150
151/*!
152 \property QKeySequenceEdit::clearButtonEnabled
153 \brief Whether the key sequence edit displays a clear button when it is not
154 empty.
155
156 If enabled, the key sequence edit displays a trailing \e clear button when
157 it contains some text, otherwise the line edit does not show a clear button
158 (the default).
159
160 \since 6.4
161*/
162void QKeySequenceEdit::setClearButtonEnabled(bool enable)
163{
164 Q_D(QKeySequenceEdit);
165
166 d->lineEdit->setClearButtonEnabled(enable);
167}
168
169bool QKeySequenceEdit::isClearButtonEnabled() const
170{
171 Q_D(const QKeySequenceEdit);
172
173 return d->lineEdit->isClearButtonEnabled();
174}
175
176/*!
177 \property QKeySequenceEdit::maximumSequenceLength
178 \brief The maximum sequence length.
179
180 The maximum number of key sequences a user can enter. The value needs to
181 be between 1 and 4, with 4 being the default.
182
183 \since 6.5
184*/
185qsizetype QKeySequenceEdit::maximumSequenceLength() const
186{
187 Q_D(const QKeySequenceEdit);
188 return d->maximumSequenceLength;
189}
190
191void QKeySequenceEdit::setMaximumSequenceLength(qsizetype count)
192{
193 Q_D(QKeySequenceEdit);
194
195 if (count < 1 || count > QKeySequencePrivate::MaxKeyCount) {
196 qWarning(msg: "QKeySequenceEdit: maximumSequenceLength %lld is out of range (1..%d)",
197 qlonglong(count), QKeySequencePrivate::MaxKeyCount);
198 return;
199 }
200 d->maximumSequenceLength = int(count);
201 if (d->keyNum > count) {
202 for (qsizetype i = d->keyNum; i < count; ++i)
203 d->key[i] = QKeyCombination::fromCombined(combined: 0);
204 d->keyNum = count;
205 d->rebuildKeySequence();
206 }
207}
208
209/*!
210 \property QKeySequenceEdit::finishingKeyCombinations
211 \brief The list of key combinations that finish editing the key sequences.
212
213 Any combination in the list will finish the editing of key sequences.
214 All other key combinations can be recorded as part of a key sequence. By
215 default, Qt::Key_Tab and Qt::Key_Backtab will finish recording the key
216 sequence.
217
218 \since 6.5
219*/
220void QKeySequenceEdit::setFinishingKeyCombinations(const QList<QKeyCombination> &finishingKeyCombinations)
221{
222 Q_D(QKeySequenceEdit);
223
224 d->finishingKeyCombinations = finishingKeyCombinations;
225}
226
227QList<QKeyCombination> QKeySequenceEdit::finishingKeyCombinations() const
228{
229 Q_D(const QKeySequenceEdit);
230
231 return d->finishingKeyCombinations;
232}
233
234void QKeySequenceEdit::setKeySequence(const QKeySequence &keySequence)
235{
236 Q_D(QKeySequenceEdit);
237
238 d->resetState();
239
240 if (d->keySequence == keySequence)
241 return;
242
243 const auto desiredCount = keySequence.count();
244 if (desiredCount > d->maximumSequenceLength) {
245 qWarning(msg: "QKeySequenceEdit: setting a key sequence of length %d "
246 "when maximumSequenceLength is %d, truncating.",
247 desiredCount, d->maximumSequenceLength);
248 }
249
250 d->keyNum = std::min(a: desiredCount, b: d->maximumSequenceLength);
251 for (int i = 0; i < d->keyNum; ++i)
252 d->key[i] = keySequence[i];
253 for (int i = d->keyNum; i < QKeySequencePrivate::MaxKeyCount; ++i)
254 d->key[i] = QKeyCombination::fromCombined(combined: 0);
255
256 d->rebuildKeySequence();
257
258 d->lineEdit->setText(d->keySequence.toString(format: QKeySequence::NativeText));
259
260 emit keySequenceChanged(keySequence: d->keySequence);
261}
262
263/*!
264 \fn void QKeySequenceEdit::editingFinished()
265
266 This signal is emitted when the user finishes entering the shortcut.
267
268 \note there is a one second delay before releasing the last key and
269 emitting this signal.
270*/
271
272/*!
273 \brief Clears the current key sequence.
274*/
275void QKeySequenceEdit::clear()
276{
277 setKeySequence(QKeySequence());
278}
279
280/*!
281 \reimp
282*/
283bool QKeySequenceEdit::event(QEvent *e)
284{
285 Q_D(const QKeySequenceEdit);
286
287 switch (e->type()) {
288 case QEvent::Shortcut:
289 return true;
290 case QEvent::ShortcutOverride:
291 e->accept();
292 return true;
293 case QEvent::KeyPress: {
294 QKeyEvent *ke = static_cast<QKeyEvent *>(e);
295 if (!d->finishingKeyCombinations.contains(t: ke->keyCombination())) {
296 keyPressEvent(ke);
297 return true;
298 }
299 }
300 break;
301 default:
302 break;
303 }
304
305 return QWidget::event(event: e);
306}
307
308/*!
309 \reimp
310*/
311void QKeySequenceEdit::keyPressEvent(QKeyEvent *e)
312{
313 Q_D(QKeySequenceEdit);
314
315 if (d->finishingKeyCombinations.contains(t: e->keyCombination())) {
316 d->finishEditing();
317 return;
318 }
319
320 int nextKey = e->key();
321
322 if (d->prevKey == -1) {
323 clear();
324 d->prevKey = nextKey;
325 }
326
327 d->lineEdit->setPlaceholderText(QString());
328 if (nextKey == Qt::Key_Control
329 || nextKey == Qt::Key_Shift
330 || nextKey == Qt::Key_Meta
331 || nextKey == Qt::Key_Alt
332 || nextKey == Qt::Key_unknown) {
333 return;
334 }
335
336 QString selectedText = d->lineEdit->selectedText();
337 if (!selectedText.isEmpty() && selectedText == d->lineEdit->text()) {
338 clear();
339 if (nextKey == Qt::Key_Backspace)
340 return;
341 }
342
343 if (d->keyNum >= d->maximumSequenceLength)
344 return;
345
346 if (e->modifiers() & Qt::ShiftModifier) {
347 QList<int> possibleKeys = QKeyMapper::possibleKeys(e);
348 int pkTotal = possibleKeys.size();
349 if (!pkTotal)
350 return;
351 bool found = false;
352 for (int i = 0; i < possibleKeys.size(); ++i) {
353 if (possibleKeys.at(i) - nextKey == int(e->modifiers())
354 || (possibleKeys.at(i) == nextKey && e->modifiers() == Qt::ShiftModifier)) {
355 nextKey = possibleKeys.at(i);
356 found = true;
357 break;
358 }
359 }
360 // Use as fallback
361 if (!found)
362 nextKey = possibleKeys.first();
363 } else {
364 nextKey |= d->translateModifiers(state: e->modifiers(), text: e->text());
365 }
366
367
368 d->key[d->keyNum] = QKeyCombination::fromCombined(combined: nextKey);
369 d->keyNum++;
370
371 d->rebuildKeySequence();
372 QString text = d->keySequence.toString(format: QKeySequence::NativeText);
373 if (d->keyNum < d->maximumSequenceLength) {
374 //: This text is an "unfinished" shortcut, expands like "Ctrl+A, ..."
375 text = tr(s: "%1, ...").arg(a: text);
376 }
377 d->lineEdit->setText(text);
378 e->accept();
379}
380
381/*!
382 \reimp
383*/
384void QKeySequenceEdit::keyReleaseEvent(QKeyEvent *e)
385{
386 Q_D(QKeySequenceEdit);
387
388 if (d->prevKey == e->key()) {
389 if (d->keyNum < d->maximumSequenceLength)
390 d->releaseTimer = startTimer(interval: 1000);
391 else
392 d->finishEditing();
393 }
394 e->accept();
395}
396
397/*!
398 \reimp
399*/
400void QKeySequenceEdit::timerEvent(QTimerEvent *e)
401{
402 Q_D(QKeySequenceEdit);
403 if (e->timerId() == d->releaseTimer) {
404 d->finishEditing();
405 return;
406 }
407
408 QWidget::timerEvent(event: e);
409}
410
411/*!
412 \reimp
413*/
414void QKeySequenceEdit::focusOutEvent(QFocusEvent *e)
415{
416 Q_D(QKeySequenceEdit);
417 if (e->reason() != Qt::PopupFocusReason)
418 d->finishEditing();
419 QWidget::focusOutEvent(event: e);
420}
421
422QT_END_NAMESPACE
423
424#include "moc_qkeysequenceedit.cpp"
425

source code of qtbase/src/widgets/widgets/qkeysequenceedit.cpp