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 "centralwidget.h"
30
31#include "findwidget.h"
32#include "helpenginewrapper.h"
33#include "helpviewer.h"
34#include "openpagesmanager.h"
35#include "tracer.h"
36
37#include <QtCore/QRegExp>
38#include <QtCore/QTimer>
39
40#include <QtGui/QKeyEvent>
41#include <QtWidgets/QMenu>
42#ifndef QT_NO_PRINTER
43#include <QtPrintSupport/QPageSetupDialog>
44#include <QtPrintSupport/QPrintDialog>
45#include <QtPrintSupport/QPrintPreviewDialog>
46#include <QtPrintSupport/QPrinter>
47#endif
48#include <QtWidgets/QStackedWidget>
49#include <QtWidgets/QTextBrowser>
50#include <QtWidgets/QVBoxLayout>
51
52#include <QtHelp/QHelpSearchEngine>
53
54QT_BEGIN_NAMESPACE
55
56namespace {
57 CentralWidget *staticCentralWidget = nullptr;
58}
59
60// -- TabBar
61
62TabBar::TabBar(QWidget *parent)
63 : QTabBar(parent)
64{
65 TRACE_OBJ
66#ifdef Q_OS_MAC
67 setDocumentMode(true);
68#endif
69 setMovable(true);
70 setShape(QTabBar::RoundedNorth);
71 setContextMenuPolicy(Qt::CustomContextMenu);
72 setSizePolicy(QSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred,
73 QSizePolicy::TabWidget));
74 connect(sender: this, signal: &QTabBar::currentChanged,
75 receiver: this, slot: &TabBar::slotCurrentChanged);
76 connect(sender: this, signal: &QTabBar::tabCloseRequested,
77 receiver: this, slot: &TabBar::slotTabCloseRequested);
78 connect(sender: this, signal: &QWidget::customContextMenuRequested,
79 receiver: this, slot: &TabBar::slotCustomContextMenuRequested);
80}
81
82TabBar::~TabBar()
83{
84 TRACE_OBJ
85}
86
87int TabBar::addNewTab(const QString &title)
88{
89 TRACE_OBJ
90 const int index = addTab(text: title);
91 setTabsClosable(count() > 1);
92 return index;
93}
94
95void TabBar::setCurrent(HelpViewer *viewer)
96{
97 TRACE_OBJ
98 for (int i = 0; i < count(); ++i) {
99 HelpViewer *data = tabData(index: i).value<HelpViewer*>();
100 if (data == viewer) {
101 setCurrentIndex(i);
102 break;
103 }
104 }
105}
106
107void TabBar::removeTabAt(HelpViewer *viewer)
108{
109 TRACE_OBJ
110 for (int i = 0; i < count(); ++i) {
111 HelpViewer *data = tabData(index: i).value<HelpViewer*>();
112 if (data == viewer) {
113 removeTab(index: i);
114 break;
115 }
116 }
117 setTabsClosable(count() > 1);
118}
119
120void TabBar::titleChanged()
121{
122 TRACE_OBJ
123 for (int i = 0; i < count(); ++i) {
124 HelpViewer *data = tabData(index: i).value<HelpViewer*>();
125 QString title = data->title();
126 title.replace(c: QLatin1Char('&'), after: QLatin1String("&&"));
127 setTabText(index: i, text: title.isEmpty() ? tr(s: "(Untitled)") : title);
128 }
129}
130
131void TabBar::slotCurrentChanged(int index)
132{
133 TRACE_OBJ
134 emit currentTabChanged(viewer: tabData(index).value<HelpViewer*>());
135}
136
137void TabBar::slotTabCloseRequested(int index)
138{
139 TRACE_OBJ
140 OpenPagesManager::instance()->closePage(page: tabData(index).value<HelpViewer*>());
141}
142
143void TabBar::slotCustomContextMenuRequested(const QPoint &pos)
144{
145 TRACE_OBJ
146 const int tab = tabAt(pos);
147 if (tab < 0)
148 return;
149
150 QMenu menu(QString(), this);
151 menu.addAction(text: tr(s: "New &Tab"), object: OpenPagesManager::instance(),
152 slot: &OpenPagesManager::createBlankPage);
153
154 const bool enableAction = count() > 1;
155 QAction *closePage = menu.addAction(text: tr(s: "&Close Tab"));
156 closePage->setEnabled(enableAction);
157
158 QAction *closePages = menu.addAction(text: tr(s: "Close Other Tabs"));
159 closePages->setEnabled(enableAction);
160
161 menu.addSeparator();
162
163 HelpViewer *viewer = tabData(index: tab).value<HelpViewer*>();
164 QAction *newBookmark = menu.addAction(text: tr(s: "Add Bookmark for this Page..."));
165 const QString &url = viewer->source().toString();
166 if (url.isEmpty() || url == QLatin1String("about:blank"))
167 newBookmark->setEnabled(false);
168
169 QAction *pickedAction = menu.exec(pos: mapToGlobal(pos));
170 if (pickedAction == closePage)
171 slotTabCloseRequested(index: tab);
172 else if (pickedAction == closePages) {
173 for (int i = count() - 1; i >= 0; --i) {
174 if (i != tab)
175 slotTabCloseRequested(index: i);
176 }
177 } else if (pickedAction == newBookmark)
178 emit addBookmark(title: viewer->title(), url);
179}
180
181// -- CentralWidget
182
183CentralWidget::CentralWidget(QWidget *parent)
184 : QWidget(parent)
185#ifndef QT_NO_PRINTER
186 , m_printer(nullptr)
187#endif
188 , m_findWidget(new FindWidget(this))
189 , m_stackedWidget(new QStackedWidget(this))
190 , m_tabBar(new TabBar(this))
191{
192 TRACE_OBJ
193 staticCentralWidget = this;
194 QVBoxLayout *vboxLayout = new QVBoxLayout(this);
195
196 vboxLayout->setContentsMargins(QMargins());
197 vboxLayout->setSpacing(0);
198 vboxLayout->addWidget(m_tabBar);
199 m_tabBar->setVisible(HelpEngineWrapper::instance().showTabs());
200 vboxLayout->addWidget(m_stackedWidget);
201 vboxLayout->addWidget(m_findWidget);
202 m_findWidget->hide();
203
204 connect(sender: m_findWidget, signal: &FindWidget::findNext, receiver: this, slot: &CentralWidget::findNext);
205 connect(sender: m_findWidget, signal: &FindWidget::findPrevious, receiver: this, slot: &CentralWidget::findPrevious);
206 connect(sender: m_findWidget, signal: &FindWidget::find, receiver: this, slot: &CentralWidget::find);
207 connect(sender: m_findWidget, signal: &FindWidget::escapePressed, receiver: this, slot: &CentralWidget::activateTab);
208 connect(sender: m_tabBar, signal: &TabBar::addBookmark, receiver: this, slot: &CentralWidget::addBookmark);
209}
210
211CentralWidget::~CentralWidget()
212{
213 TRACE_OBJ
214 QStringList zoomFactors;
215 QStringList currentPages;
216 for (int i = 0; i < m_stackedWidget->count(); ++i) {
217 const HelpViewer * const viewer = viewerAt(index: i);
218 const QUrl &source = viewer->source();
219 if (source.isValid()) {
220 currentPages << source.toString();
221 zoomFactors << QString::number(viewer->scale());
222 }
223 }
224
225 HelpEngineWrapper &helpEngine = HelpEngineWrapper::instance();
226 helpEngine.setLastShownPages(currentPages);
227 helpEngine.setLastZoomFactors(zoomFactors);
228 helpEngine.setLastTabPage(m_stackedWidget->currentIndex());
229
230#ifndef QT_NO_PRINTER
231 delete m_printer;
232#endif
233}
234
235CentralWidget *CentralWidget::instance()
236{
237 TRACE_OBJ
238 return staticCentralWidget;
239}
240
241QUrl CentralWidget::currentSource() const
242{
243 TRACE_OBJ
244 return currentHelpViewer()->source();
245}
246
247QString CentralWidget::currentTitle() const
248{
249 TRACE_OBJ
250 return currentHelpViewer()->title();
251}
252
253bool CentralWidget::hasSelection() const
254{
255 TRACE_OBJ
256 return !currentHelpViewer()->selectedText().isEmpty();
257}
258
259bool CentralWidget::isForwardAvailable() const
260{
261 TRACE_OBJ
262 return currentHelpViewer()->isForwardAvailable();
263}
264
265bool CentralWidget::isBackwardAvailable() const
266{
267 TRACE_OBJ
268 return currentHelpViewer()->isBackwardAvailable();
269}
270
271HelpViewer* CentralWidget::viewerAt(int index) const
272{
273 TRACE_OBJ
274 return static_cast<HelpViewer*>(m_stackedWidget->widget(index));
275}
276
277HelpViewer* CentralWidget::currentHelpViewer() const
278{
279 TRACE_OBJ
280 return static_cast<HelpViewer *>(m_stackedWidget->currentWidget());
281}
282
283void CentralWidget::addPage(HelpViewer *page, bool fromSearch)
284{
285 TRACE_OBJ
286 page->installEventFilter(filterObj: this);
287 page->setFocus(Qt::OtherFocusReason);
288 connectSignals(page);
289 const int index = m_stackedWidget->addWidget(w: page);
290 m_tabBar->setTabData(index: m_tabBar->addNewTab(title: page->title()),
291 data: QVariant::fromValue(value: viewerAt(index)));
292 connect(sender: page, signal: &HelpViewer::titleChanged, receiver: m_tabBar, slot: &TabBar::titleChanged);
293
294 if (fromSearch) {
295 connect(sender: currentHelpViewer(), signal: &HelpViewer::loadFinished,
296 receiver: this, slot: &CentralWidget::highlightSearchTerms);
297 }
298}
299
300void CentralWidget::removePage(int index)
301{
302 TRACE_OBJ
303 const bool currentChanged = index == currentIndex();
304 m_tabBar->removeTabAt(viewer: viewerAt(index));
305 m_stackedWidget->removeWidget(w: m_stackedWidget->widget(index));
306 if (currentChanged)
307 emit currentViewerChanged();
308}
309
310int CentralWidget::currentIndex() const
311{
312 TRACE_OBJ
313 return m_stackedWidget->currentIndex();
314}
315
316void CentralWidget::setCurrentPage(HelpViewer *page)
317{
318 TRACE_OBJ
319 m_tabBar->setCurrent(page);
320 m_stackedWidget->setCurrentWidget(page);
321 emit currentViewerChanged();
322}
323
324void CentralWidget::connectTabBar()
325{
326 TRACE_OBJ
327 connect(sender: m_tabBar, signal: &TabBar::currentTabChanged, receiver: OpenPagesManager::instance(),
328 slot: QOverload<HelpViewer *>::of(ptr: &OpenPagesManager::setCurrentPage));
329}
330
331// -- public slots
332
333#if QT_CONFIG(clipboard)
334void CentralWidget::copy()
335{
336 TRACE_OBJ
337 currentHelpViewer()->copy();
338}
339#endif
340
341void CentralWidget::home()
342{
343 TRACE_OBJ
344 currentHelpViewer()->home();
345}
346
347void CentralWidget::zoomIn()
348{
349 TRACE_OBJ
350 currentHelpViewer()->scaleUp();
351}
352
353void CentralWidget::zoomOut()
354{
355 TRACE_OBJ
356 currentHelpViewer()->scaleDown();
357}
358
359void CentralWidget::resetZoom()
360{
361 TRACE_OBJ
362 currentHelpViewer()->resetScale();
363}
364
365void CentralWidget::forward()
366{
367 TRACE_OBJ
368 currentHelpViewer()->forward();
369}
370
371void CentralWidget::nextPage()
372{
373 TRACE_OBJ
374 m_stackedWidget->setCurrentIndex((m_stackedWidget->currentIndex() + 1)
375 % m_stackedWidget->count());
376}
377
378void CentralWidget::backward()
379{
380 TRACE_OBJ
381 currentHelpViewer()->backward();
382}
383
384void CentralWidget::previousPage()
385{
386 TRACE_OBJ
387 m_stackedWidget->setCurrentIndex((m_stackedWidget->currentIndex() - 1)
388 % m_stackedWidget->count());
389}
390
391void CentralWidget::print()
392{
393 TRACE_OBJ
394#if !defined(QT_NO_PRINTER) && !defined(QT_NO_PRINTDIALOG)
395 initPrinter();
396 QPrintDialog dlg(m_printer, this);
397
398 if (!currentHelpViewer()->selectedText().isEmpty())
399 dlg.addEnabledOption(option: QAbstractPrintDialog::PrintSelection);
400 dlg.addEnabledOption(option: QAbstractPrintDialog::PrintPageRange);
401 dlg.addEnabledOption(option: QAbstractPrintDialog::PrintCollateCopies);
402 dlg.setWindowTitle(tr(s: "Print Document"));
403 if (dlg.exec() == QDialog::Accepted)
404 currentHelpViewer()->print(printer: m_printer);
405#endif
406}
407
408void CentralWidget::pageSetup()
409{
410 TRACE_OBJ
411#if !defined(QT_NO_PRINTER) && !defined(QT_NO_PRINTDIALOG)
412 initPrinter();
413 QPageSetupDialog dlg(m_printer);
414 dlg.exec();
415#endif
416}
417
418void CentralWidget::printPreview()
419{
420 TRACE_OBJ
421#if !defined(QT_NO_PRINTER) && !defined(QT_NO_PRINTDIALOG)
422 initPrinter();
423 QPrintPreviewDialog preview(m_printer, this);
424 connect(sender: &preview, signal: &QPrintPreviewDialog::paintRequested,
425 receiver: this, slot: &CentralWidget::printPreviewToPrinter);
426 preview.exec();
427#endif
428}
429
430void CentralWidget::setSource(const QUrl &url)
431{
432 TRACE_OBJ
433 HelpViewer *viewer = currentHelpViewer();
434 viewer->setSource(url);
435 viewer->setFocus(Qt::OtherFocusReason);
436}
437
438void CentralWidget::setSourceFromSearch(const QUrl &url)
439{
440 TRACE_OBJ
441 connect(sender: currentHelpViewer(), signal: &HelpViewer::loadFinished,
442 receiver: this, slot: &CentralWidget::highlightSearchTerms);
443 currentHelpViewer()->setSource(url);
444 currentHelpViewer()->setFocus(Qt::OtherFocusReason);
445}
446
447void CentralWidget::findNext()
448{
449 TRACE_OBJ
450 find(text: m_findWidget->text(), forward: true, incremental: false);
451}
452
453void CentralWidget::findPrevious()
454{
455 TRACE_OBJ
456 find(text: m_findWidget->text(), forward: false, incremental: false);
457}
458
459void CentralWidget::find(const QString &ttf, bool forward, bool incremental)
460{
461 TRACE_OBJ
462 bool found = false;
463 if (HelpViewer *viewer = currentHelpViewer()) {
464 HelpViewer::FindFlags flags;
465 if (!forward)
466 flags |= HelpViewer::FindBackward;
467 if (m_findWidget->caseSensitive())
468 flags |= HelpViewer::FindCaseSensitively;
469 found = viewer->findText(text: ttf, flags, incremental, fromSearch: false);
470 }
471
472 if (!found && ttf.isEmpty())
473 found = true; // the line edit is empty, no need to mark it red...
474
475 if (!m_findWidget->isVisible())
476 m_findWidget->show();
477 m_findWidget->setPalette(found);
478}
479
480void CentralWidget::activateTab()
481{
482 TRACE_OBJ
483 currentHelpViewer()->setFocus();
484}
485
486void CentralWidget::showTextSearch()
487{
488 TRACE_OBJ
489 m_findWidget->show();
490}
491
492void CentralWidget::updateBrowserFont()
493{
494 TRACE_OBJ
495 const int count = m_stackedWidget->count();
496 const QFont &font = viewerAt(index: count - 1)->viewerFont();
497 for (int i = 0; i < count; ++i)
498 viewerAt(index: i)->setViewerFont(font);
499}
500
501void CentralWidget::updateUserInterface()
502{
503 m_tabBar->setVisible(HelpEngineWrapper::instance().showTabs());
504}
505
506// -- protected
507
508void CentralWidget::keyPressEvent(QKeyEvent *e)
509{
510 TRACE_OBJ
511 const QString &text = e->text();
512 if (text.startsWith(c: QLatin1Char('/'))) {
513 if (!m_findWidget->isVisible()) {
514 m_findWidget->showAndClear();
515 } else {
516 m_findWidget->show();
517 }
518 } else {
519 QWidget::keyPressEvent(event: e);
520 }
521}
522
523void CentralWidget::focusInEvent(QFocusEvent * /* event */)
524{
525 TRACE_OBJ
526 // If we have a current help viewer then this is the 'focus proxy',
527 // otherwise it's the central widget. This is needed, so an embedding
528 // program can just set the focus to the central widget and it does
529 // The Right Thing(TM)
530 QWidget *receiver = m_stackedWidget;
531 if (HelpViewer *viewer = currentHelpViewer())
532 receiver = viewer;
533 QTimer::singleShot(interval: 1, receiver,
534 slot: QOverload<>::of(ptr: &QWidget::setFocus));
535}
536
537// -- private slots
538
539void CentralWidget::highlightSearchTerms()
540{
541 TRACE_OBJ
542 QHelpSearchEngine *searchEngine =
543 HelpEngineWrapper::instance().searchEngine();
544 const QString searchInput = searchEngine->searchInput();
545 const bool wholePhrase = searchInput.startsWith(c: QLatin1Char('"')) &&
546 searchInput.endsWith(c: QLatin1Char('"'));
547 const QStringList &words = wholePhrase ? QStringList(searchInput.mid(position: 1, n: searchInput.length() - 2)) :
548 searchInput.split(sep: QRegExp("\\W+"), behavior: Qt::SkipEmptyParts);
549 HelpViewer *viewer = currentHelpViewer();
550 for (const QString &word : words)
551 viewer->findText(text: word, flags: {}, incremental: false, fromSearch: true);
552 disconnect(sender: viewer, signal: &HelpViewer::loadFinished,
553 receiver: this, slot: &CentralWidget::highlightSearchTerms);
554}
555
556void CentralWidget::printPreviewToPrinter(QPrinter *p)
557{
558 TRACE_OBJ
559#ifndef QT_NO_PRINTER
560 currentHelpViewer()->print(printer: p);
561#endif
562}
563
564void CentralWidget::handleSourceChanged(const QUrl &url)
565{
566 TRACE_OBJ
567 if (sender() == currentHelpViewer())
568 emit sourceChanged(url);
569}
570
571void CentralWidget::slotHighlighted(const QUrl &link)
572{
573 TRACE_OBJ
574 QUrl resolvedLink = m_resolvedLinks.value(akey: link);
575 if (!link.isEmpty() && resolvedLink.isEmpty()) {
576 resolvedLink = HelpEngineWrapper::instance().findFile(url: link);
577 m_resolvedLinks.insert(akey: link, avalue: resolvedLink);
578 }
579 emit highlighted(link: resolvedLink);
580}
581
582// -- private
583
584void CentralWidget::initPrinter()
585{
586 TRACE_OBJ
587#ifndef QT_NO_PRINTER
588 if (!m_printer)
589 m_printer = new QPrinter(QPrinter::HighResolution);
590#endif
591}
592
593void CentralWidget::connectSignals(HelpViewer *page)
594{
595 TRACE_OBJ
596#if defined(BROWSER_QTWEBKIT)
597 connect(page, &HelpViewer::printRequested,
598 this, &CentralWidget::print);
599#endif
600#if QT_CONFIG(clipboard)
601 connect(sender: page, signal: &HelpViewer::copyAvailable,
602 receiver: this, slot: &CentralWidget::copyAvailable);
603#endif
604 connect(sender: page, signal: &HelpViewer::forwardAvailable,
605 receiver: this, slot: &CentralWidget::forwardAvailable);
606 connect(sender: page, signal: &HelpViewer::backwardAvailable,
607 receiver: this, slot: &CentralWidget::backwardAvailable);
608 connect(sender: page, signal: &HelpViewer::sourceChanged,
609 receiver: this, slot: &CentralWidget::handleSourceChanged);
610 connect(sender: page, signal: QOverload<const QUrl &>::of(ptr: &HelpViewer::highlighted),
611 receiver: this, slot: &CentralWidget::slotHighlighted);
612}
613
614bool CentralWidget::eventFilter(QObject *object, QEvent *e)
615{
616 TRACE_OBJ
617 if (e->type() != QEvent::KeyPress)
618 return QWidget::eventFilter(watched: object, event: e);
619
620 HelpViewer *viewer = currentHelpViewer();
621 QKeyEvent *keyEvent = static_cast<QKeyEvent*> (e);
622 if (viewer == object && keyEvent->key() == Qt::Key_Backspace) {
623 if (viewer->isBackwardAvailable()) {
624#if defined(BROWSER_QTWEBKIT)
625 // this helps in case there is an html <input> field
626 if (!viewer->hasFocus())
627#endif // BROWSER_QTWEBKIT
628 viewer->backward();
629 }
630 }
631 return QWidget::eventFilter(watched: object, event: e);
632}
633
634QT_END_NAMESPACE
635

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