1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Copyright (C) 2013 Ivan Komissarov.
5 ** Contact: https://www.qt.io/licensing/
6 **
7 ** This file is part of the QtWidgets module of the Qt Toolkit.
8 **
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** Commercial License Usage
11 ** Licensees holding valid commercial Qt licenses may use this file in
12 ** accordance with the commercial license agreement provided with the
13 ** Software or, alternatively, in accordance with the terms contained in
14 ** a written agreement between you and The Qt Company. For licensing terms
15 ** and conditions see https://www.qt.io/terms-conditions. For further
16 ** information use the contact form at https://www.qt.io/contact-us.
17 **
18 ** GNU Lesser General Public License Usage
19 ** Alternatively, this file may be used under the terms of the GNU Lesser
20 ** General Public License version 3 as published by the Free Software
21 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
22 ** packaging of this file. Please review the following information to
23 ** ensure the GNU Lesser General Public License version 3 requirements
24 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
25 **
26 ** GNU General Public License Usage
27 ** Alternatively, this file may be used under the terms of the GNU
28 ** General Public License version 2.0 or (at your option) the GNU General
29 ** Public license version 3 or any later version approved by the KDE Free
30 ** Qt Foundation. The licenses are as published by the Free Software
31 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
32 ** included in the packaging of this file. Please review the following
33 ** information to ensure the GNU General Public License requirements will
34 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
35 ** https://www.gnu.org/licenses/gpl-3.0.html.
36 **
37 ** $QT_END_LICENSE$
38 **
39 ****************************************************************************/
40
41 #include "qkeysequenceedit.h"
42 #include "qkeysequenceedit_p.h"
43
44 #include "qboxlayout.h"
45 #include "qlineedit.h"
46 #include <private/qkeymapper_p.h>
47
48 QT_BEGIN_NAMESPACE
49
50 Q_STATIC_ASSERT(QKeySequencePrivate::MaxKeyCount == 4); // assumed by the code around here
51
init()52 void QKeySequenceEditPrivate::init()
53 {
54 Q_Q(QKeySequenceEdit);
55
56 lineEdit = new QLineEdit(q);
57 lineEdit->setObjectName(QStringLiteral("qt_keysequenceedit_lineedit"));
58 keyNum = 0;
59 prevKey = -1;
60 releaseTimer = 0;
61
62 QVBoxLayout *layout = new QVBoxLayout(q);
63 layout->setContentsMargins(0, 0, 0, 0);
64 layout->addWidget(lineEdit);
65
66 key[0] = key[1] = key[2] = key[3] = 0;
67
68 lineEdit->setFocusProxy(q);
69 lineEdit->installEventFilter(q);
70 resetState();
71
72 q->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
73 q->setFocusPolicy(Qt::StrongFocus);
74 q->setAttribute(Qt::WA_MacShowFocusRect, true);
75 q->setAttribute(Qt::WA_InputMethodEnabled, false);
76
77 // TODO: add clear button
78 }
79
translateModifiers(Qt::KeyboardModifiers state,const QString & text)80 int QKeySequenceEditPrivate::translateModifiers(Qt::KeyboardModifiers state, const QString &text)
81 {
82 Q_UNUSED(text);
83 int result = 0;
84 if (state & Qt::ControlModifier)
85 result |= Qt::CTRL;
86 if (state & Qt::MetaModifier)
87 result |= Qt::META;
88 if (state & Qt::AltModifier)
89 result |= Qt::ALT;
90 return result;
91 }
92
resetState()93 void QKeySequenceEditPrivate::resetState()
94 {
95 Q_Q(QKeySequenceEdit);
96
97 if (releaseTimer) {
98 q->killTimer(releaseTimer);
99 releaseTimer = 0;
100 }
101 prevKey = -1;
102 lineEdit->setText(keySequence.toString(QKeySequence::NativeText));
103 lineEdit->setPlaceholderText(QKeySequenceEdit::tr("Press shortcut"));
104 }
105
finishEditing()106 void QKeySequenceEditPrivate::finishEditing()
107 {
108 Q_Q(QKeySequenceEdit);
109
110 resetState();
111 emit q->keySequenceChanged(keySequence);
112 emit q->editingFinished();
113 }
114
115 /*!
116 \class QKeySequenceEdit
117 \brief The QKeySequenceEdit widget allows to input a QKeySequence.
118
119 \inmodule QtWidgets
120
121 \since 5.2
122
123 This widget lets the user choose a QKeySequence, which is usually used as
124 a shortcut. The recording is initiated when the widget receives the focus
125 and ends one second after the user releases the last key.
126
127 \sa QKeySequenceEdit::keySequence
128 */
129
130 /*!
131 Constructs a QKeySequenceEdit widget with the given \a parent.
132 */
QKeySequenceEdit(QWidget * parent)133 QKeySequenceEdit::QKeySequenceEdit(QWidget *parent)
134 : QKeySequenceEdit(*new QKeySequenceEditPrivate, parent, { })
135 {
136 }
137
138 /*!
139 Constructs a QKeySequenceEdit widget with the given \a keySequence and \a parent.
140 */
QKeySequenceEdit(const QKeySequence & keySequence,QWidget * parent)141 QKeySequenceEdit::QKeySequenceEdit(const QKeySequence &keySequence, QWidget *parent)
142 : QKeySequenceEdit(parent)
143 {
144 setKeySequence(keySequence);
145 }
146
147 /*!
148 \internal
149 */
QKeySequenceEdit(QKeySequenceEditPrivate & dd,QWidget * parent,Qt::WindowFlags f)150 QKeySequenceEdit::QKeySequenceEdit(QKeySequenceEditPrivate &dd, QWidget *parent, Qt::WindowFlags f) :
151 QWidget(dd, parent, f)
152 {
153 Q_D(QKeySequenceEdit);
154 d->init();
155 }
156
157 /*!
158 Destroys the QKeySequenceEdit object.
159 */
~QKeySequenceEdit()160 QKeySequenceEdit::~QKeySequenceEdit()
161 {
162 }
163
164 /*!
165 \property QKeySequenceEdit::keySequence
166
167 \brief This property contains the currently chosen key sequence.
168
169 The shortcut can be changed by the user or via setter function.
170 */
keySequence() const171 QKeySequence QKeySequenceEdit::keySequence() const
172 {
173 Q_D(const QKeySequenceEdit);
174
175 return d->keySequence;
176 }
177
setKeySequence(const QKeySequence & keySequence)178 void QKeySequenceEdit::setKeySequence(const QKeySequence &keySequence)
179 {
180 Q_D(QKeySequenceEdit);
181
182 d->resetState();
183
184 if (d->keySequence == keySequence)
185 return;
186
187 d->keySequence = keySequence;
188
189 d->key[0] = d->key[1] = d->key[2] = d->key[3] = 0;
190 d->keyNum = keySequence.count();
191 for (int i = 0; i < d->keyNum; ++i)
192 d->key[i] = keySequence[i];
193
194 d->lineEdit->setText(keySequence.toString(QKeySequence::NativeText));
195
196 emit keySequenceChanged(keySequence);
197 }
198
199 /*!
200 \fn void QKeySequenceEdit::editingFinished()
201
202 This signal is emitted when the user finishes entering the shortcut.
203
204 \note there is a one second delay before releasing the last key and
205 emitting this signal.
206 */
207
208 /*!
209 \brief Clears the current key sequence.
210 */
clear()211 void QKeySequenceEdit::clear()
212 {
213 setKeySequence(QKeySequence());
214 }
215
216 /*!
217 \reimp
218 */
event(QEvent * e)219 bool QKeySequenceEdit::event(QEvent *e)
220 {
221 switch (e->type()) {
222 case QEvent::Shortcut:
223 return true;
224 case QEvent::ShortcutOverride:
225 e->accept();
226 return true;
227 default :
228 break;
229 }
230
231 return QWidget::event(e);
232 }
233
234 /*!
235 \reimp
236 */
keyPressEvent(QKeyEvent * e)237 void QKeySequenceEdit::keyPressEvent(QKeyEvent *e)
238 {
239 Q_D(QKeySequenceEdit);
240
241 int nextKey = e->key();
242
243 if (d->prevKey == -1) {
244 clear();
245 d->prevKey = nextKey;
246 }
247
248 d->lineEdit->setPlaceholderText(QString());
249 if (nextKey == Qt::Key_Control
250 || nextKey == Qt::Key_Shift
251 || nextKey == Qt::Key_Meta
252 || nextKey == Qt::Key_Alt
253 || nextKey == Qt::Key_unknown) {
254 return;
255 }
256
257 QString selectedText = d->lineEdit->selectedText();
258 if (!selectedText.isEmpty() && selectedText == d->lineEdit->text()) {
259 clear();
260 if (nextKey == Qt::Key_Backspace)
261 return;
262 }
263
264 if (d->keyNum >= QKeySequencePrivate::MaxKeyCount)
265 return;
266
267 if (e->modifiers() & Qt::ShiftModifier) {
268 QList<int> possibleKeys = QKeyMapper::possibleKeys(e);
269 int pkTotal = possibleKeys.count();
270 if (!pkTotal)
271 return;
272 bool found = false;
273 for (int i = 0; i < possibleKeys.size(); ++i) {
274 if (possibleKeys.at(i) - nextKey == int(e->modifiers())
275 || (possibleKeys.at(i) == nextKey && e->modifiers() == Qt::ShiftModifier)) {
276 nextKey = possibleKeys.at(i);
277 found = true;
278 break;
279 }
280 }
281 // Use as fallback
282 if (!found)
283 nextKey = possibleKeys.first();
284 } else {
285 nextKey |= d->translateModifiers(e->modifiers(), e->text());
286 }
287
288
289 d->key[d->keyNum] = nextKey;
290 d->keyNum++;
291
292 QKeySequence key(d->key[0], d->key[1], d->key[2], d->key[3]);
293 d->keySequence = key;
294 QString text = key.toString(QKeySequence::NativeText);
295 if (d->keyNum < QKeySequencePrivate::MaxKeyCount) {
296 //: This text is an "unfinished" shortcut, expands like "Ctrl+A, ..."
297 text = tr("%1, ...").arg(text);
298 }
299 d->lineEdit->setText(text);
300 e->accept();
301 }
302
303 /*!
304 \reimp
305 */
keyReleaseEvent(QKeyEvent * e)306 void QKeySequenceEdit::keyReleaseEvent(QKeyEvent *e)
307 {
308 Q_D(QKeySequenceEdit);
309
310 if (d->prevKey == e->key()) {
311 if (d->keyNum < QKeySequencePrivate::MaxKeyCount)
312 d->releaseTimer = startTimer(1000);
313 else
314 d->finishEditing();
315 }
316 e->accept();
317 }
318
319 /*!
320 \reimp
321 */
timerEvent(QTimerEvent * e)322 void QKeySequenceEdit::timerEvent(QTimerEvent *e)
323 {
324 Q_D(QKeySequenceEdit);
325 if (e->timerId() == d->releaseTimer) {
326 d->finishEditing();
327 return;
328 }
329
330 QWidget::timerEvent(e);
331 }
332
333 QT_END_NAMESPACE
334
335 #include "moc_qkeysequenceedit.cpp"
336