1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the Qt Assistant of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39
40 #include "qhelpsearchquerywidget.h"
41
42 #include <QtCore/QAbstractListModel>
43 #include <QtCore/QObject>
44 #include <QtCore/QStringList>
45 #include <QtCore/QtGlobal>
46
47 #include <QtWidgets/QCompleter>
48 #include <QtWidgets/QLabel>
49 #include <QtWidgets/QLayout>
50 #include <QtWidgets/QLineEdit>
51 #include <QtGui/QFocusEvent>
52 #include <QtWidgets/QPushButton>
53 #include <QtWidgets/QToolButton>
54
55 QT_BEGIN_NAMESPACE
56
57 class QHelpSearchQueryWidgetPrivate : public QObject
58 {
59 Q_OBJECT
60
61 private:
62 struct QueryHistory {
QueryHistoryQHelpSearchQueryWidgetPrivate::QueryHistory63 explicit QueryHistory() : curQuery(-1) {}
64 QStringList queries;
65 int curQuery;
66 };
67
68 class CompleterModel : public QAbstractListModel
69 {
70 public:
CompleterModel(QObject * parent)71 explicit CompleterModel(QObject *parent)
72 : QAbstractListModel(parent) {}
73
rowCount(const QModelIndex & parent=QModelIndex ()) const74 int rowCount(const QModelIndex &parent = QModelIndex()) const override
75 {
76 return parent.isValid() ? 0 : termList.size();
77 }
78
data(const QModelIndex & index,int role=Qt::DisplayRole) const79 QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override
80 {
81 if (!index.isValid() || index.row() >= termList.count()||
82 (role != Qt::EditRole && role != Qt::DisplayRole))
83 return QVariant();
84 return termList.at(index.row());
85 }
86
addTerm(const QString & term)87 void addTerm(const QString &term)
88 {
89 if (!termList.contains(term)) {
90 beginResetModel();
91 termList.append(term);
92 endResetModel();
93 }
94 }
95
96 private:
97 QStringList termList;
98 };
99
QHelpSearchQueryWidgetPrivate()100 QHelpSearchQueryWidgetPrivate()
101 : QObject()
102 , m_searchCompleter(new CompleterModel(this), this)
103 {
104 }
105
~QHelpSearchQueryWidgetPrivate()106 ~QHelpSearchQueryWidgetPrivate() override
107 {
108 // nothing todo
109 }
110
retranslate()111 void retranslate()
112 {
113 m_searchLabel->setText(QHelpSearchQueryWidget::tr("Search for:"));
114 m_searchButton->setText(QHelpSearchQueryWidget::tr("Search"));
115 #if QT_CONFIG(tooltip)
116 m_prevQueryButton->setToolTip(QHelpSearchQueryWidget::tr("Previous search"));
117 m_nextQueryButton->setToolTip(QHelpSearchQueryWidget::tr("Next search"));
118 #endif
119 }
120
saveQuery(const QString & query)121 void saveQuery(const QString &query)
122 {
123 // We only add the query to the list if it is different from the last one.
124 if (!m_queries.queries.isEmpty() && m_queries.queries.last() == query)
125 return;
126
127 m_queries.queries.append(query);
128 static_cast<CompleterModel *>(m_searchCompleter.model())->addTerm(query);
129 }
130
nextOrPrevQuery(int maxOrMinIndex,int addend,QToolButton * thisButton,QToolButton * otherButton)131 void nextOrPrevQuery(int maxOrMinIndex, int addend, QToolButton *thisButton,
132 QToolButton *otherButton)
133 {
134 m_lineEdit->clear();
135
136 // Otherwise, the respective button would be disabled.
137 Q_ASSERT(m_queries.curQuery != maxOrMinIndex);
138
139 m_queries.curQuery = qBound(0, m_queries.curQuery + addend, m_queries.queries.count() - 1);
140 const QString &query = m_queries.queries.at(m_queries.curQuery);
141 m_lineEdit->setText(query);
142
143 if (m_queries.curQuery == maxOrMinIndex)
144 thisButton->setEnabled(false);
145 otherButton->setEnabled(true);
146 }
147
enableOrDisableToolButtons()148 void enableOrDisableToolButtons()
149 {
150 m_prevQueryButton->setEnabled(m_queries.curQuery > 0);
151 m_nextQueryButton->setEnabled(m_queries.curQuery
152 < m_queries.queries.size() - 1);
153 }
154
155 private slots:
eventFilter(QObject * ob,QEvent * event)156 bool eventFilter(QObject *ob, QEvent *event) override
157 {
158 if (event->type() == QEvent::KeyPress) {
159 QKeyEvent *const keyEvent = static_cast<QKeyEvent *>(event);
160 if (keyEvent->key() == Qt::Key_Down) {
161 if (m_queries.curQuery + 1 < m_queries.queries.size())
162 nextQuery();
163 return true;
164 }
165 if (keyEvent->key() == Qt::Key_Up) {
166 if (m_queries.curQuery > 0)
167 prevQuery();
168 return true;
169 }
170
171 }
172 return QObject::eventFilter(ob, event);
173 }
174
searchRequested()175 void searchRequested()
176 {
177 saveQuery(m_lineEdit->text());
178 m_queries.curQuery = m_queries.queries.size() - 1;
179 if (m_queries.curQuery > 0)
180 m_prevQueryButton->setEnabled(true);
181 m_nextQueryButton->setEnabled(false);
182 }
183
nextQuery()184 void nextQuery()
185 {
186 nextOrPrevQuery(m_queries.queries.size() - 1, 1, m_nextQueryButton,
187 m_prevQueryButton);
188 }
189
prevQuery()190 void prevQuery()
191 {
192 nextOrPrevQuery(0, -1, m_prevQueryButton, m_nextQueryButton);
193 }
194
195 private:
196 friend class QHelpSearchQueryWidget;
197
198 QLabel *m_searchLabel = nullptr;
199 QPushButton *m_searchButton = nullptr;
200 QLineEdit *m_lineEdit = nullptr;
201 QToolButton *m_nextQueryButton = nullptr;
202 QToolButton *m_prevQueryButton = nullptr;
203 QueryHistory m_queries;
204 QCompleter m_searchCompleter;
205 bool m_compactMode = false;
206 };
207
208 /*!
209 \class QHelpSearchQueryWidget
210 \since 4.4
211 \inmodule QtHelp
212 \brief The QHelpSearchQueryWidget class provides a simple line edit or
213 an advanced widget to enable the user to input a search term in a
214 standardized input mask.
215 */
216
217 /*!
218 \fn void QHelpSearchQueryWidget::search()
219
220 This signal is emitted when a the user has the search button invoked.
221 After receiving the signal you can ask the QHelpSearchQueryWidget for the
222 search input that you may pass to the QHelpSearchEngine::search() function.
223 */
224
225 /*!
226 Constructs a new search query widget with the given \a parent.
227 */
QHelpSearchQueryWidget(QWidget * parent)228 QHelpSearchQueryWidget::QHelpSearchQueryWidget(QWidget *parent)
229 : QWidget(parent)
230 {
231 d = new QHelpSearchQueryWidgetPrivate();
232
233 QVBoxLayout *vLayout = new QVBoxLayout(this);
234 vLayout->setContentsMargins(QMargins());
235
236 QHBoxLayout* hBoxLayout = new QHBoxLayout();
237 d->m_searchLabel = new QLabel(this);
238 d->m_lineEdit = new QLineEdit(this);
239 d->m_lineEdit->setClearButtonEnabled(true);
240 d->m_lineEdit->setCompleter(&d->m_searchCompleter);
241 d->m_lineEdit->installEventFilter(d);
242 d->m_prevQueryButton = new QToolButton(this);
243 d->m_prevQueryButton->setArrowType(Qt::LeftArrow);
244 d->m_prevQueryButton->setEnabled(false);
245 d->m_nextQueryButton = new QToolButton(this);
246 d->m_nextQueryButton->setArrowType(Qt::RightArrow);
247 d->m_nextQueryButton->setEnabled(false);
248 d->m_searchButton = new QPushButton(this);
249 hBoxLayout->addWidget(d->m_searchLabel);
250 hBoxLayout->addWidget(d->m_lineEdit);
251 hBoxLayout->addWidget(d->m_prevQueryButton);
252 hBoxLayout->addWidget(d->m_nextQueryButton);
253 hBoxLayout->addWidget(d->m_searchButton);
254
255 vLayout->addLayout(hBoxLayout);
256
257 connect(d->m_prevQueryButton, &QAbstractButton::clicked,
258 d, &QHelpSearchQueryWidgetPrivate::prevQuery);
259 connect(d->m_nextQueryButton, &QAbstractButton::clicked,
260 d, &QHelpSearchQueryWidgetPrivate::nextQuery);
261 connect(d->m_searchButton, &QAbstractButton::clicked,
262 this, &QHelpSearchQueryWidget::search);
263 connect(d->m_lineEdit, &QLineEdit::returnPressed,
264 this, &QHelpSearchQueryWidget::search);
265
266 d->retranslate();
267 connect(this, &QHelpSearchQueryWidget::search,
268 d, &QHelpSearchQueryWidgetPrivate::searchRequested);
269 setCompactMode(true);
270 }
271
272 /*!
273 Destroys the search query widget.
274 */
~QHelpSearchQueryWidget()275 QHelpSearchQueryWidget::~QHelpSearchQueryWidget()
276 {
277 delete d;
278 }
279
280 /*!
281 Expands the search query widget so that the extended search fields are shown.
282 */
expandExtendedSearch()283 void QHelpSearchQueryWidget::expandExtendedSearch()
284 {
285 // TODO: no extended search anymore, deprecate it?
286 }
287
288 /*!
289 Collapses the search query widget so that only the default search field is
290 shown.
291 */
collapseExtendedSearch()292 void QHelpSearchQueryWidget::collapseExtendedSearch()
293 {
294 // TODO: no extended search anymore, deprecate it?
295 }
296
297 /*!
298 \obsolete
299
300 Use searchInput() instead.
301 */
query() const302 QList<QHelpSearchQuery> QHelpSearchQueryWidget::query() const
303 {
304 return QList<QHelpSearchQuery>() << QHelpSearchQuery(QHelpSearchQuery::DEFAULT,
305 searchInput().split(QChar::Space, Qt::SkipEmptyParts));
306 }
307
308 /*!
309 \obsolete
310
311 Use setSearchInput() instead.
312 */
setQuery(const QList<QHelpSearchQuery> & queryList)313 void QHelpSearchQueryWidget::setQuery(const QList<QHelpSearchQuery> &queryList)
314 {
315 if (queryList.isEmpty())
316 return;
317
318 setSearchInput(queryList.first().wordList.join(QChar::Space));
319 }
320
321 /*!
322 \since 5.9
323
324 Returns a search phrase to use in combination with the
325 QHelpSearchEngine::search(const QString &searchInput) function.
326 */
searchInput() const327 QString QHelpSearchQueryWidget::searchInput() const
328 {
329 if (d->m_queries.queries.isEmpty())
330 return QString();
331 return d->m_queries.queries.last();
332 }
333
334 /*!
335 \since 5.9
336
337 Sets the QHelpSearchQueryWidget input field to the value specified by
338 \a searchInput.
339
340 \note The QHelpSearchEngine::search(const QString &searchInput) function has
341 to be called to perform the actual search.
342 */
setSearchInput(const QString & searchInput)343 void QHelpSearchQueryWidget::setSearchInput(const QString &searchInput)
344 {
345 d->m_lineEdit->clear();
346
347 d->m_lineEdit->setText(searchInput);
348
349 d->searchRequested();
350 }
351
isCompactMode() const352 bool QHelpSearchQueryWidget::isCompactMode() const
353 {
354 return d->m_compactMode;
355 }
356
setCompactMode(bool on)357 void QHelpSearchQueryWidget::setCompactMode(bool on)
358 {
359 if (d->m_compactMode != on) {
360 d->m_compactMode = on;
361 d->m_prevQueryButton->setVisible(!on);
362 d->m_nextQueryButton->setVisible(!on);
363 d->m_searchLabel->setVisible(!on);
364 }
365 }
366
367 /*!
368 \reimp
369 */
focusInEvent(QFocusEvent * focusEvent)370 void QHelpSearchQueryWidget::focusInEvent(QFocusEvent *focusEvent)
371 {
372 if (focusEvent->reason() != Qt::MouseFocusReason) {
373 d->m_lineEdit->selectAll();
374 d->m_lineEdit->setFocus();
375 }
376 }
377
378 /*!
379 \reimp
380 */
changeEvent(QEvent * event)381 void QHelpSearchQueryWidget::changeEvent(QEvent *event)
382 {
383 if (event->type() == QEvent::LanguageChange)
384 d->retranslate();
385 else
386 QWidget::changeEvent(event);
387 }
388
389 QT_END_NAMESPACE
390
391 #include "qhelpsearchquerywidget.moc"
392