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