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:GPL-EXCEPT$
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 General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU
19** General Public License version 3 as published by the Free Software
20** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21** included in the packaging of this file. Please review the following
22** information to ensure the GNU General Public License requirements will
23** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24**
25** $QT_END_LICENSE$
26**
27****************************************************************************/
28
29#include "helpviewer.h"
30
31#include "globalactions.h"
32#include "helpenginewrapper.h"
33#include "helpviewer_p.h"
34#include "openpagesmanager.h"
35#include "tracer.h"
36
37#include <QtCore/QStringBuilder>
38
39#include <QtGui/QContextMenuEvent>
40#include <QtWidgets/QMenu>
41#include <QtWidgets/QScrollBar>
42#if QT_CONFIG(clipboard)
43#include <QtGui/QClipboard>
44#endif
45#include <QtWidgets/QApplication>
46
47QT_BEGIN_NAMESPACE
48
49HelpViewer::HelpViewer(qreal zoom, QWidget *parent)
50 : QTextBrowser(parent)
51 , d(new HelpViewerPrivate(zoom))
52{
53 TRACE_OBJ
54 QPalette p = palette();
55 p.setColor(acg: QPalette::Inactive, acr: QPalette::Highlight,
56 acolor: p.color(cg: QPalette::Active, cr: QPalette::Highlight));
57 p.setColor(acg: QPalette::Inactive, acr: QPalette::HighlightedText,
58 acolor: p.color(cg: QPalette::Active, cr: QPalette::HighlightedText));
59 setPalette(p);
60
61 installEventFilter(filterObj: this);
62 document()->setDocumentMargin(8);
63
64 QFont font = viewerFont();
65 font.setPointSize(int(font.pointSize() + zoom));
66 setViewerFont(font);
67
68 connect(sender: this, signal: &QTextBrowser::sourceChanged, receiver: this, slot: &HelpViewer::titleChanged);
69 connect(sender: this, signal: &HelpViewer::loadFinished, receiver: this, slot: &HelpViewer::setLoadFinished);
70}
71
72QFont HelpViewer::viewerFont() const
73{
74 TRACE_OBJ
75 if (HelpEngineWrapper::instance().usesBrowserFont())
76 return HelpEngineWrapper::instance().browserFont();
77 return qApp->font();
78}
79
80void HelpViewer::setViewerFont(const QFont &newFont)
81{
82 TRACE_OBJ
83 if (font() != newFont) {
84 d->forceFont = true;
85 setFont(newFont);
86 d->forceFont = false;
87 }
88}
89
90void HelpViewer::scaleUp()
91{
92 TRACE_OBJ
93 if (d->zoomCount < 10) {
94 d->zoomCount++;
95 d->forceFont = true;
96 zoomIn();
97 d->forceFont = false;
98 }
99}
100
101void HelpViewer::scaleDown()
102{
103 TRACE_OBJ
104 if (d->zoomCount > -5) {
105 d->zoomCount--;
106 d->forceFont = true;
107 zoomOut();
108 d->forceFont = false;
109 }
110}
111
112void HelpViewer::resetScale()
113{
114 TRACE_OBJ
115 if (d->zoomCount != 0) {
116 d->forceFont = true;
117 zoomOut(range: d->zoomCount);
118 d->forceFont = false;
119 }
120 d->zoomCount = 0;
121}
122
123qreal HelpViewer::scale() const
124{
125 TRACE_OBJ
126 return d->zoomCount;
127}
128
129QString HelpViewer::title() const
130{
131 TRACE_OBJ
132 return documentTitle();
133}
134
135void HelpViewer::setTitle(const QString &title)
136{
137 TRACE_OBJ
138 setDocumentTitle(title);
139}
140
141QUrl HelpViewer::source() const
142{
143 TRACE_OBJ
144 return QTextBrowser::source();
145}
146
147void HelpViewer::setSource(const QUrl &url)
148{
149 TRACE_OBJ
150 if (launchWithExternalApp(url))
151 return;
152
153 emit loadStarted();
154 bool helpOrAbout = (url.toString() == QLatin1String("help"));
155 const QUrl resolvedUrl = (helpOrAbout ? LocalHelpFile : HelpEngineWrapper::instance().findFile(url));
156
157 QTextBrowser::setSource(resolvedUrl);
158
159 if (!resolvedUrl.isValid()) {
160 helpOrAbout = (url.toString() == QLatin1String("about:blank"));
161 setHtml(helpOrAbout ? AboutBlank : PageNotFoundMessage.arg(a: url.toString()));
162 }
163 emit loadFinished(finished: true);
164}
165
166QString HelpViewer::selectedText() const
167{
168 TRACE_OBJ
169 return textCursor().selectedText();
170}
171
172bool HelpViewer::isForwardAvailable() const
173{
174 TRACE_OBJ
175 return QTextBrowser::isForwardAvailable();
176}
177
178bool HelpViewer::isBackwardAvailable() const
179{
180 TRACE_OBJ
181 return QTextBrowser::isBackwardAvailable();
182}
183
184bool HelpViewer::findText(const QString &text, FindFlags flags, bool incremental,
185 bool fromSearch)
186{
187 TRACE_OBJ
188 QTextDocument *doc = document();
189 QTextCursor cursor = textCursor();
190 if (!doc || cursor.isNull())
191 return false;
192
193 const int position = cursor.selectionStart();
194 if (incremental)
195 cursor.setPosition(pos: position);
196
197 QTextDocument::FindFlags textDocFlags;
198 if (flags & HelpViewer::FindBackward)
199 textDocFlags |= QTextDocument::FindBackward;
200 if (flags & HelpViewer::FindCaseSensitively)
201 textDocFlags |= QTextDocument::FindCaseSensitively;
202
203 QTextCursor found = doc->find(subString: text, cursor, options: textDocFlags);
204 if (found.isNull()) {
205 if ((flags & HelpViewer::FindBackward) == 0)
206 cursor.movePosition(op: QTextCursor::Start);
207 else
208 cursor.movePosition(op: QTextCursor::End);
209 found = doc->find(subString: text, cursor, options: textDocFlags);
210 }
211
212 if (fromSearch) {
213 cursor.beginEditBlock();
214 viewport()->setUpdatesEnabled(false);
215
216 QTextCharFormat marker;
217 marker.setForeground(Qt::red);
218 cursor.movePosition(op: QTextCursor::Start);
219 setTextCursor(cursor);
220
221 while (find(exp: text)) {
222 QTextCursor hit = textCursor();
223 hit.mergeCharFormat(modifier: marker);
224 }
225
226 viewport()->setUpdatesEnabled(true);
227 cursor.endEditBlock();
228 }
229
230 bool cursorIsNull = found.isNull();
231 if (cursorIsNull) {
232 found = textCursor();
233 found.setPosition(pos: position);
234 }
235 setTextCursor(found);
236 return !cursorIsNull;
237}
238
239// -- public slots
240
241#if QT_CONFIG(clipboard)
242void HelpViewer::copy()
243{
244 TRACE_OBJ
245 QTextBrowser::copy();
246}
247#endif
248
249void HelpViewer::forward()
250{
251 TRACE_OBJ
252 QTextBrowser::forward();
253}
254
255void HelpViewer::backward()
256{
257 TRACE_OBJ
258 QTextBrowser::backward();
259}
260
261// -- protected
262
263void HelpViewer::keyPressEvent(QKeyEvent *e)
264{
265 TRACE_OBJ
266 if ((e->key() == Qt::Key_Home && e->modifiers() != Qt::NoModifier)
267 || (e->key() == Qt::Key_End && e->modifiers() != Qt::NoModifier)) {
268 QKeyEvent* event = new QKeyEvent(e->type(), e->key(), Qt::NoModifier,
269 e->text(), e->isAutoRepeat(), e->count());
270 e = event;
271 }
272 QTextBrowser::keyPressEvent(ev: e);
273}
274
275
276void HelpViewer::wheelEvent(QWheelEvent *e)
277{
278 TRACE_OBJ
279 if (e->modifiers() == Qt::ControlModifier) {
280 e->accept();
281 e->angleDelta().y() > 0 ? scaleUp() : scaleDown();
282 } else {
283 QTextBrowser::wheelEvent(e);
284 }
285}
286
287void HelpViewer::mousePressEvent(QMouseEvent *e)
288{
289 TRACE_OBJ
290#ifdef Q_OS_LINUX
291 if (handleForwardBackwardMouseButtons(e))
292 return;
293#endif
294
295 QTextBrowser::mousePressEvent(ev: e);
296}
297
298void HelpViewer::mouseReleaseEvent(QMouseEvent *e)
299{
300 TRACE_OBJ
301#ifndef Q_OS_LINUX
302 if (handleForwardBackwardMouseButtons(e))
303 return;
304#endif
305
306 bool controlPressed = e->modifiers() & Qt::ControlModifier;
307 if ((controlPressed && d->hasAnchorAt(browser: this, pos: e->pos())) ||
308 (e->button() == Qt::MiddleButton && d->hasAnchorAt(browser: this, pos: e->pos()))) {
309 d->openLinkInNewPage();
310 return;
311 }
312
313 QTextBrowser::mouseReleaseEvent(ev: e);
314}
315
316
317void HelpViewer::resizeEvent(QResizeEvent *e)
318{
319 const int topTextPosition = cursorForPosition(pos: {width() / 2, 0}).position();
320 QTextBrowser::resizeEvent(e);
321 scrollToTextPosition(position: topTextPosition);
322}
323
324// -- private slots
325
326void HelpViewer::actionChanged()
327{
328 // stub
329 TRACE_OBJ
330}
331
332// -- private
333
334bool HelpViewer::eventFilter(QObject *obj, QEvent *event)
335{
336 TRACE_OBJ
337 if (event->type() == QEvent::FontChange && !d->forceFont)
338 return true;
339 return QTextBrowser::eventFilter(obj, event);
340}
341
342void HelpViewer::contextMenuEvent(QContextMenuEvent *event)
343{
344 TRACE_OBJ
345
346 QMenu menu(QString(), nullptr);
347 QUrl link;
348#if QT_CONFIG(clipboard)
349 QAction *copyAnchorAction = nullptr;
350#endif
351 if (d->hasAnchorAt(browser: this, pos: event->pos())) {
352 link = anchorAt(pos: event->pos());
353 if (link.isRelative())
354 link = source().resolved(relative: link);
355 menu.addAction(text: tr(s: "Open Link"), object: d, slot: &HelpViewerPrivate::openLink);
356 menu.addAction(text: tr(s: "Open Link in New Tab\tCtrl+LMB"), object: d, slot: &HelpViewerPrivate::openLinkInNewPage);
357
358#if QT_CONFIG(clipboard)
359 if (!link.isEmpty() && link.isValid())
360 copyAnchorAction = menu.addAction(text: tr(s: "Copy &Link Location"));
361#endif
362 } else if (!selectedText().isEmpty()) {
363#if QT_CONFIG(clipboard)
364 menu.addAction(text: tr(s: "Copy"), object: this, slot: &HelpViewer::copy);
365#endif
366 } else {
367 menu.addAction(text: tr(s: "Reload"), object: this, slot: &HelpViewer::reload);
368 }
369
370#if QT_CONFIG(clipboard)
371 if (copyAnchorAction == menu.exec(pos: event->globalPos()))
372 QApplication::clipboard()->setText(link.toString());
373#endif
374}
375
376QVariant HelpViewer::loadResource(int type, const QUrl &name)
377{
378 TRACE_OBJ
379 QByteArray ba;
380 if (type < 4) {
381 const QUrl url = HelpEngineWrapper::instance().findFile(url: name);
382 ba = HelpEngineWrapper::instance().fileData(url);
383 if (url.toString().endsWith(s: QLatin1String(".svg"), cs: Qt::CaseInsensitive)) {
384 QImage image;
385 image.loadFromData(data: ba, aformat: "svg");
386 if (!image.isNull())
387 return image;
388 }
389 }
390 return ba;
391}
392
393
394void HelpViewer::scrollToTextPosition(int position)
395{
396 QTextCursor tc(document());
397 tc.setPosition(pos: position);
398 const int dy = cursorRect(cursor: tc).top();
399 if (verticalScrollBar()) {
400 verticalScrollBar()->setValue(
401 std::min(a: verticalScrollBar()->value() + dy, b: verticalScrollBar()->maximum()));
402 }
403}
404
405QT_END_NAMESPACE
406

source code of qttools/src/assistant/assistant/helpviewer_qtb.cpp