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