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

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

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