1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qhelpsearchresultwidget.h"
5
6#include <QtCore/QList>
7#include <QtCore/QString>
8#include <QtCore/QPointer>
9#include <QtCore/QStringList>
10#include <QtCore/QTextStream>
11
12#include <QtWidgets/QLabel>
13#include <QtWidgets/QLayout>
14#include <QtGui/QMouseEvent>
15#include <QtWidgets/QHeaderView>
16#include <QtWidgets/QSpacerItem>
17#include <QtWidgets/QToolButton>
18#include <QtWidgets/QTreeWidget>
19#include <QtWidgets/QTextBrowser>
20#include <QtWidgets/QTreeWidgetItem>
21
22QT_BEGIN_NAMESPACE
23
24class QResultWidget : public QTextBrowser
25{
26 Q_OBJECT
27 Q_PROPERTY(QColor linkColor READ linkColor WRITE setLinkColor)
28
29public:
30 QResultWidget(QWidget *parent = nullptr)
31 : QTextBrowser(parent)
32 {
33 connect(sender: this, signal: &QTextBrowser::anchorClicked,
34 context: this, slot: &QResultWidget::requestShowLink);
35 setContextMenuPolicy(Qt::NoContextMenu);
36 setLinkColor(palette().color(cr: QPalette::Link));
37 }
38
39 QColor linkColor() const { return m_linkColor; }
40 void setLinkColor(const QColor &color)
41 {
42 m_linkColor = color;
43 const QString sheet = QString::fromLatin1(ba: "a { text-decoration: underline; color: %1 }").arg(a: m_linkColor.name());
44 document()->setDefaultStyleSheet(sheet);
45 }
46
47 void showResultPage(const QList<QHelpSearchResult> &results, bool isIndexing)
48 {
49 QString htmlFile;
50 QTextStream str(&htmlFile);
51 str << "<html><head><title>" << tr(s: "Search Results") << "</title></head><body>";
52
53 const int count = results.size();
54 if (count != 0) {
55 if (isIndexing) {
56 str << "<div style=\"text-align:left;"
57 " font-weight:bold; color:red\">" << tr(s: "Note:")
58 << "&nbsp;<span style=\"font-weight:normal; color:black\">"
59 << tr(s: "The search results may not be complete since the "
60 "documentation is still being indexed.")
61 << "</span></div></div><br>";
62 }
63
64 for (const QHelpSearchResult &result : results) {
65 str << "<div style=\"text-align:left\"><a href=\""
66 << result.url().toString() << "\">"
67 << result.title() << "</a></div>"
68 "<div style =\"margin:5px\">" << result.snippet() << "</div>";
69 }
70 } else {
71 str << "<div align=\"center\"><br><br><h2>"
72 << tr(s: "Your search did not match any documents.")
73 << "</h2><div>";
74 if (isIndexing) {
75 str << "<div align=\"center\"><h3>"
76 << tr(s: "(The reason for this might be that the documentation "
77 "is still being indexed.)") << "</h3><div>";
78 }
79 }
80
81 str << "</body></html>";
82
83 setHtml(htmlFile);
84 }
85
86signals:
87 void requestShowLink(const QUrl &url);
88
89private slots:
90 void doSetSource(const QUrl & /*name*/, QTextDocument::ResourceType /*type*/) override {}
91
92private:
93 QColor m_linkColor;
94};
95
96
97class QHelpSearchResultWidgetPrivate : public QObject
98{
99 Q_OBJECT
100
101private slots:
102 void showFirstResultPage()
103 {
104 if (!searchEngine.isNull())
105 resultFirstToShow = 0;
106 updateHitRange();
107 }
108
109 void showLastResultPage()
110 {
111 if (!searchEngine.isNull())
112 resultFirstToShow = (searchEngine->searchResultCount() - 1) / ResultsRange * ResultsRange;
113 updateHitRange();
114 }
115
116 void showPreviousResultPage()
117 {
118 if (!searchEngine.isNull()) {
119 resultFirstToShow -= ResultsRange;
120 if (resultFirstToShow < 0)
121 resultFirstToShow = 0;
122 }
123 updateHitRange();
124 }
125
126 void showNextResultPage()
127 {
128 if (!searchEngine.isNull()
129 && resultFirstToShow + ResultsRange < searchEngine->searchResultCount()) {
130 resultFirstToShow += ResultsRange;
131 }
132 updateHitRange();
133 }
134
135 void indexingStarted()
136 {
137 isIndexing = true;
138 }
139
140 void indexingFinished()
141 {
142 isIndexing = false;
143 }
144
145private:
146 QHelpSearchResultWidgetPrivate(QHelpSearchEngine *engine)
147 : QObject()
148 , searchEngine(engine)
149 {
150 connect(sender: searchEngine.data(), signal: &QHelpSearchEngine::indexingStarted,
151 context: this, slot: &QHelpSearchResultWidgetPrivate::indexingStarted);
152 connect(sender: searchEngine.data(), signal: &QHelpSearchEngine::indexingFinished,
153 context: this, slot: &QHelpSearchResultWidgetPrivate::indexingFinished);
154 }
155
156 ~QHelpSearchResultWidgetPrivate()
157 {
158 delete searchEngine;
159 }
160
161 QToolButton* setupToolButton(const QString &iconPath)
162 {
163 QToolButton *button = new QToolButton();
164 button->setEnabled(false);
165 button->setAutoRaise(true);
166 button->setIcon(QIcon(iconPath));
167 button->setIconSize(QSize(12, 12));
168 button->setMaximumSize(QSize(16, 16));
169
170 return button;
171 }
172
173 void updateHitRange()
174 {
175 int last = 0;
176 int first = 0;
177 int count = 0;
178
179 if (!searchEngine.isNull()) {
180 count = searchEngine->searchResultCount();
181 if (count > 0) {
182 last = qMin(a: resultFirstToShow + ResultsRange, b: count);
183 first = resultFirstToShow + 1;
184 }
185 resultTextBrowser->showResultPage(results: searchEngine->searchResults(start: resultFirstToShow,
186 end: last), isIndexing);
187 }
188
189 hitsLabel->setText(QHelpSearchResultWidget::tr(s: "%1 - %2 of %n Hits", c: nullptr, n: count).arg(a: first).arg(a: last));
190 firstResultPage->setEnabled(resultFirstToShow);
191 previousResultPage->setEnabled(resultFirstToShow);
192 lastResultPage->setEnabled(count - last);
193 nextResultPage->setEnabled(count - last);
194 }
195
196private:
197 friend class QHelpSearchResultWidget;
198
199 QPointer<QHelpSearchEngine> searchEngine;
200
201 QResultWidget *resultTextBrowser = nullptr;
202
203 static const int ResultsRange = 20;
204
205 QToolButton *firstResultPage = nullptr;
206 QToolButton *previousResultPage = nullptr;
207 QToolButton *nextResultPage = nullptr;
208 QToolButton *lastResultPage = nullptr;
209 QLabel *hitsLabel = nullptr;
210 int resultFirstToShow = 0;
211 bool isIndexing = false;
212};
213
214/*!
215 \class QHelpSearchResultWidget
216 \since 4.4
217 \inmodule QtHelp
218 \brief The QHelpSearchResultWidget class provides a text browser to display
219 search results.
220*/
221
222/*!
223 \fn void QHelpSearchResultWidget::requestShowLink(const QUrl &link)
224
225 This signal is emitted when a item is activated and its associated
226 \a link should be shown.
227*/
228
229QHelpSearchResultWidget::QHelpSearchResultWidget(QHelpSearchEngine *engine)
230 : QWidget(0)
231 , d(new QHelpSearchResultWidgetPrivate(engine))
232{
233 QVBoxLayout *vLayout = new QVBoxLayout(this);
234 vLayout->setContentsMargins(QMargins());
235 vLayout->setSpacing(0);
236
237 QHBoxLayout *hBoxLayout = new QHBoxLayout();
238#ifndef Q_OS_MAC
239 hBoxLayout->setContentsMargins(QMargins());
240 hBoxLayout->setSpacing(0);
241#endif
242 hBoxLayout->addWidget(d->firstResultPage = d->setupToolButton(
243 QString::fromUtf8(utf8: ":/qt-project.org/assistant/images/3leftarrow.png")));
244
245 hBoxLayout->addWidget(d->previousResultPage = d->setupToolButton(
246 QString::fromUtf8(utf8: ":/qt-project.org/assistant/images/1leftarrow.png")));
247
248 d->hitsLabel = new QLabel(tr(s: "0 - 0 of 0 Hits"), this);
249 hBoxLayout->addWidget(d->hitsLabel);
250 d->hitsLabel->setAlignment(Qt::AlignCenter);
251 d->hitsLabel->setMinimumSize(QSize(150, d->hitsLabel->height()));
252
253 hBoxLayout->addWidget(d->nextResultPage = d->setupToolButton(
254 QString::fromUtf8(utf8: ":/qt-project.org/assistant/images/1rightarrow.png")));
255
256 hBoxLayout->addWidget(d->lastResultPage = d->setupToolButton(
257 QString::fromUtf8(utf8: ":/qt-project.org/assistant/images/3rightarrow.png")));
258
259 QSpacerItem *spacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum);
260 hBoxLayout->addItem(spacer);
261
262 vLayout->addLayout(layout: hBoxLayout);
263
264 d->resultTextBrowser = new QResultWidget(this);
265 vLayout->addWidget(d->resultTextBrowser);
266
267 connect(sender: d->resultTextBrowser, signal: &QResultWidget::requestShowLink,
268 context: this, slot: &QHelpSearchResultWidget::requestShowLink);
269
270 connect(sender: d->nextResultPage, signal: &QAbstractButton::clicked,
271 context: d, slot: &QHelpSearchResultWidgetPrivate::showNextResultPage);
272 connect(sender: d->lastResultPage, signal: &QAbstractButton::clicked,
273 context: d, slot: &QHelpSearchResultWidgetPrivate::showLastResultPage);
274 connect(sender: d->firstResultPage, signal: &QAbstractButton::clicked,
275 context: d, slot: &QHelpSearchResultWidgetPrivate::showFirstResultPage);
276 connect(sender: d->previousResultPage, signal: &QAbstractButton::clicked,
277 context: d, slot: &QHelpSearchResultWidgetPrivate::showPreviousResultPage);
278
279 connect(sender: engine, signal: &QHelpSearchEngine::searchingFinished,
280 context: d, slot: &QHelpSearchResultWidgetPrivate::showFirstResultPage);
281}
282
283/*! \reimp
284*/
285void QHelpSearchResultWidget::changeEvent(QEvent *event)
286{
287 if (event->type() == QEvent::LanguageChange)
288 d->updateHitRange();
289}
290
291/*!
292 Destroys the search result widget.
293*/
294QHelpSearchResultWidget::~QHelpSearchResultWidget()
295{
296 delete d;
297}
298
299/*!
300 Returns a reference of the URL that the item at \a point owns, or an
301 empty URL if no item exists at that point.
302*/
303QUrl QHelpSearchResultWidget::linkAt(const QPoint &point)
304{
305 if (d->resultTextBrowser)
306 return d->resultTextBrowser->anchorAt(pos: point);
307 return QUrl();
308}
309
310QT_END_NAMESPACE
311
312#include "qhelpsearchresultwidget.moc"
313

source code of qttools/src/assistant/help/qhelpsearchresultwidget.cpp