Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
centralwidget.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3
5
6#include "findwidget.h"
8#include "helpviewer.h"
10#include "tracer.h"
11
12#include <QtCore/QRegularExpression>
13#include <QtCore/QTimer>
14
15#include <QtGui/QKeyEvent>
16#include <QtWidgets/QMenu>
17#ifndef QT_NO_PRINTER
18#include <QtPrintSupport/QPageSetupDialog>
19#include <QtPrintSupport/QPrintDialog>
20#include <QtPrintSupport/QPrintPreviewDialog>
21#include <QtPrintSupport/QPrinter>
22#endif
23#include <QtWidgets/QStackedWidget>
24#include <QtWidgets/QTextBrowser>
25#include <QtWidgets/QVBoxLayout>
26
27#include <QtHelp/QHelpSearchEngine>
28
30
31using namespace Qt::StringLiterals;
32
33namespace {
34 CentralWidget *staticCentralWidget = nullptr;
35}
36
37// -- TabBar
38
39TabBar::TabBar(QWidget *parent)
40 : QTabBar(parent)
41{
43#ifdef Q_OS_MAC
44 setDocumentMode(true);
45#endif
46 setMovable(true);
47 setShape(QTabBar::RoundedNorth);
48 setContextMenuPolicy(Qt::CustomContextMenu);
49 setSizePolicy(QSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred,
50 QSizePolicy::TabWidget));
51 connect(this, &QTabBar::currentChanged,
52 this, &TabBar::slotCurrentChanged);
53 connect(this, &QTabBar::tabCloseRequested,
54 this, &TabBar::slotTabCloseRequested);
55 connect(this, &QWidget::customContextMenuRequested,
56 this, &TabBar::slotCustomContextMenuRequested);
57}
58
60{
62}
63
64int TabBar::addNewTab(const QString &title)
65{
67 const int index = addTab(title);
68 setTabsClosable(count() > 1);
69 return index;
70}
71
73{
75 for (int i = 0; i < count(); ++i) {
76 HelpViewer *data = tabData(i).value<HelpViewer*>();
77 if (data == viewer) {
78 setCurrentIndex(i);
79 break;
80 }
81 }
82}
83
85{
87 for (int i = 0; i < count(); ++i) {
88 HelpViewer *data = tabData(i).value<HelpViewer*>();
89 if (data == viewer) {
90 removeTab(i);
91 break;
92 }
93 }
94 setTabsClosable(count() > 1);
95}
96
97void TabBar::titleChanged()
98{
100 for (int i = 0; i < count(); ++i) {
101 HelpViewer *data = tabData(i).value<HelpViewer*>();
102 QString title = data->title();
103 title.replace(u'&', "&&"_L1);
104 setTabText(i, title.isEmpty() ? tr("(Untitled)") : title);
105 }
106}
107
108void TabBar::slotCurrentChanged(int index)
109{
111 emit currentTabChanged(tabData(index).value<HelpViewer*>());
112}
113
114void TabBar::slotTabCloseRequested(int index)
115{
117 OpenPagesManager::instance()->closePage(tabData(index).value<HelpViewer*>());
118}
119
120void TabBar::slotCustomContextMenuRequested(const QPoint &pos)
121{
123 const int tab = tabAt(pos);
124 if (tab < 0)
125 return;
126
127 QMenu menu(QString(), this);
128 menu.addAction(tr("New &Tab"), OpenPagesManager::instance(),
130
131 const bool enableAction = count() > 1;
132 QAction *closePage = menu.addAction(tr("&Close Tab"));
133 closePage->setEnabled(enableAction);
134
135 QAction *closePages = menu.addAction(tr("Close Other Tabs"));
136 closePages->setEnabled(enableAction);
137
138 menu.addSeparator();
139
140 HelpViewer *viewer = tabData(tab).value<HelpViewer*>();
141 QAction *newBookmark = menu.addAction(tr("Add Bookmark for this Page..."));
142 const QString &url = viewer->source().toString();
143 if (url.isEmpty() || url == "about:blank"_L1)
144 newBookmark->setEnabled(false);
145
146 QAction *pickedAction = menu.exec(mapToGlobal(pos));
147 if (pickedAction == closePage)
148 slotTabCloseRequested(tab);
149 else if (pickedAction == closePages) {
150 for (int i = count() - 1; i >= 0; --i) {
151 if (i != tab)
152 slotTabCloseRequested(i);
153 }
154 } else if (pickedAction == newBookmark)
155 emit addBookmark(viewer->title(), url);
156}
157
158// -- CentralWidget
159
161 : QWidget(parent)
162#ifndef QT_NO_PRINTER
163 , m_printer(nullptr)
164#endif
165 , m_findWidget(new FindWidget(this))
167 , m_tabBar(new TabBar(this))
168{
170 staticCentralWidget = this;
171 QVBoxLayout *vboxLayout = new QVBoxLayout(this);
172
173 vboxLayout->setContentsMargins(QMargins());
174 vboxLayout->setSpacing(0);
175 vboxLayout->addWidget(m_tabBar);
176 m_tabBar->setVisible(HelpEngineWrapper::instance().showTabs());
177 vboxLayout->addWidget(m_stackedWidget);
178 vboxLayout->addWidget(m_findWidget);
179 m_findWidget->hide();
180
181 connect(m_findWidget, &FindWidget::findNext, this, &CentralWidget::findNext);
182 connect(m_findWidget, &FindWidget::findPrevious, this, &CentralWidget::findPrevious);
183 connect(m_findWidget, &FindWidget::find, this, &CentralWidget::find);
184 connect(m_findWidget, &FindWidget::escapePressed, this, &CentralWidget::activateTab);
185 connect(m_tabBar, &TabBar::addBookmark, this, &CentralWidget::addBookmark);
186}
187
189{
191 QStringList zoomFactors;
192 QStringList currentPages;
193 for (int i = 0; i < m_stackedWidget->count(); ++i) {
194 const HelpViewer * const viewer = viewerAt(i);
195 const QUrl &source = viewer->source();
196 if (source.isValid()) {
197 currentPages << source.toString();
198 zoomFactors << QString::number(viewer->scale());
199 }
200 }
201
203 helpEngine.setLastShownPages(currentPages);
204 helpEngine.setLastZoomFactors(zoomFactors);
205 helpEngine.setLastTabPage(m_stackedWidget->currentIndex());
206
207#ifndef QT_NO_PRINTER
208 delete m_printer;
209#endif
210}
211
213{
215 return staticCentralWidget;
216}
217
219{
221 return currentHelpViewer()->source();
222}
223
225{
227 return currentHelpViewer()->title();
228}
229
231{
233 return !currentHelpViewer()->selectedText().isEmpty();
234}
235
241
247
249{
251 return static_cast<HelpViewer*>(m_stackedWidget->widget(index));
252}
253
255{
257 return static_cast<HelpViewer *>(m_stackedWidget->currentWidget());
258}
259
260void CentralWidget::addPage(HelpViewer *page, bool fromSearch)
261{
263 page->installEventFilter(this);
264 page->setFocus(Qt::OtherFocusReason);
265 connectSignals(page);
266 const int index = m_stackedWidget->addWidget(page);
267 m_tabBar->setTabData(m_tabBar->addNewTab(page->title()),
268 QVariant::fromValue(viewerAt(index)));
269 connect(page, &HelpViewer::titleChanged, m_tabBar, &TabBar::titleChanged);
270
271 if (fromSearch) {
272 connect(currentHelpViewer(), &HelpViewer::loadFinished,
273 this, &CentralWidget::highlightSearchTerms);
274 }
275}
276
277void CentralWidget::removePage(int index)
278{
280 const bool currentChanged = index == currentIndex();
281 m_tabBar->removeTabAt(viewerAt(index));
282 m_stackedWidget->removeWidget(m_stackedWidget->widget(index));
283 if (currentChanged)
284 emit currentViewerChanged();
285}
286
288{
290 return m_stackedWidget->currentIndex();
291}
292
293void CentralWidget::setCurrentPage(HelpViewer *page)
294{
296 m_tabBar->setCurrent(page);
297 m_stackedWidget->setCurrentWidget(page);
298 emit currentViewerChanged();
299}
300
302{
304 connect(m_tabBar, &TabBar::currentTabChanged, OpenPagesManager::instance(),
305 QOverload<HelpViewer *>::of(&OpenPagesManager::setCurrentPage));
306}
307
308// -- public slots
309
310#if QT_CONFIG(clipboard)
311void CentralWidget::copy()
312{
313 TRACE_OBJ
314 currentHelpViewer()->copy();
315}
316#endif
317
318void CentralWidget::home()
319{
321 currentHelpViewer()->home();
322}
323
329
335
341
347
349{
351 m_stackedWidget->setCurrentIndex((m_stackedWidget->currentIndex() + 1)
352 % m_stackedWidget->count());
353}
354
360
362{
364 m_stackedWidget->setCurrentIndex((m_stackedWidget->currentIndex() - 1)
365 % m_stackedWidget->count());
366}
367
369{
371#if !defined(QT_NO_PRINTER) && !defined(QT_NO_PRINTDIALOG)
372 initPrinter();
373 QPrintDialog dlg(m_printer, this);
374
375 if (!currentHelpViewer()->selectedText().isEmpty())
376 dlg.setOption(QAbstractPrintDialog::PrintSelection);
377 dlg.setOption(QAbstractPrintDialog::PrintPageRange);
378 dlg.setOption(QAbstractPrintDialog::PrintCollateCopies);
379 dlg.setWindowTitle(tr("Print Document"));
380 if (dlg.exec() == QDialog::Accepted)
381 currentHelpViewer()->print(m_printer);
382#endif
383}
384
386{
388#if !defined(QT_NO_PRINTER) && !defined(QT_NO_PRINTDIALOG)
389 initPrinter();
390 QPageSetupDialog dlg(m_printer);
391 dlg.exec();
392#endif
393}
394
396{
398#if !defined(QT_NO_PRINTER) && !defined(QT_NO_PRINTDIALOG)
399 initPrinter();
400 QPrintPreviewDialog preview(m_printer, this);
401 preview.resize(m_printer->width(), m_printer->height());
402 connect(&preview, &QPrintPreviewDialog::paintRequested,
403 this, &CentralWidget::printPreviewToPrinter);
404 preview.exec();
405#endif
406}
407
408void CentralWidget::setSource(const QUrl &url)
409{
412 viewer->setSource(url);
413 viewer->setFocus(Qt::OtherFocusReason);
414}
415
416void CentralWidget::setSourceFromSearch(const QUrl &url)
417{
419 connect(currentHelpViewer(), &HelpViewer::loadFinished,
420 this, &CentralWidget::highlightSearchTerms);
421 currentHelpViewer()->setSource(url);
422 currentHelpViewer()->setFocus(Qt::OtherFocusReason);
423}
424
426{
428 find(m_findWidget->text(), true, false);
429}
430
432{
434 find(m_findWidget->text(), false, false);
435}
436
437void CentralWidget::find(const QString &ttf, bool forward, bool incremental)
438{
440 bool found = false;
441 if (HelpViewer *viewer = currentHelpViewer()) {
442 HelpViewer::FindFlags flags;
443 if (!forward)
444 flags |= HelpViewer::FindBackward;
445 if (m_findWidget->caseSensitive())
446 flags |= HelpViewer::FindCaseSensitively;
447 found = viewer->findText(ttf, flags, incremental, false);
448 }
449
450 if (!found && ttf.isEmpty())
451 found = true; // the line edit is empty, no need to mark it red...
452
453 if (!m_findWidget->isVisible())
454 m_findWidget->show();
455 m_findWidget->setPalette(found);
456}
457
459{
461 currentHelpViewer()->setFocus();
462}
463
465{
467 m_findWidget->show();
468}
469
471{
473 const int count = m_stackedWidget->count();
474 const QFont &font = viewerAt(count - 1)->viewerFont();
475 for (int i = 0; i < count; ++i)
476 viewerAt(i)->setViewerFont(font);
477}
478
483
484// -- protected
485
486void CentralWidget::keyPressEvent(QKeyEvent *e)
487{
489 const QString &text = e->text();
490 if (text.startsWith(u'/')) {
491 if (!m_findWidget->isVisible()) {
492 m_findWidget->showAndClear();
493 } else {
494 m_findWidget->show();
495 }
496 } else {
497 QWidget::keyPressEvent(e);
498 }
499}
500
501void CentralWidget::focusInEvent(QFocusEvent * /* event */)
502{
504 // If we have a current help viewer then this is the 'focus proxy',
505 // otherwise it's the central widget. This is needed, so an embedding
506 // program can just set the focus to the central widget and it does
507 // The Right Thing(TM)
508 QWidget *receiver = m_stackedWidget;
509 if (HelpViewer *viewer = currentHelpViewer())
510 receiver = viewer;
511 QTimer::singleShot(1, receiver,
512 QOverload<>::of(&QWidget::setFocus));
513}
514
515// -- private slots
516
517void CentralWidget::highlightSearchTerms()
518{
520 QHelpSearchEngine *searchEngine =
522 const QString searchInput = searchEngine->searchInput();
523 const bool wholePhrase = searchInput.startsWith(u'"') &&
524 searchInput.endsWith(u'"');
525 const QStringList &words = wholePhrase ? QStringList(searchInput.mid(1, searchInput.size() - 2)) :
526 searchInput.split(QRegularExpression("\\W+"), Qt::SkipEmptyParts);
528 for (const QString &word : words)
529 viewer->findText(word, {}, false, true);
530 disconnect(viewer, &HelpViewer::loadFinished,
531 this, &CentralWidget::highlightSearchTerms);
532}
533
534void CentralWidget::printPreviewToPrinter(QPrinter *p)
535{
537#ifndef QT_NO_PRINTER
538 currentHelpViewer()->print(p);
539#endif
540}
541
542void CentralWidget::handleSourceChanged(const QUrl &url)
543{
545 if (sender() == currentHelpViewer())
546 emit sourceChanged(url);
547}
548
549void CentralWidget::slotHighlighted(const QUrl &link)
550{
552 QUrl resolvedLink = m_resolvedLinks.value(link);
553 if (!link.isEmpty() && resolvedLink.isEmpty()) {
554 resolvedLink = HelpEngineWrapper::instance().findFile(link);
555 m_resolvedLinks.insert(link, resolvedLink);
556 }
557 emit highlighted(resolvedLink);
558}
559
560// -- private
561
562void CentralWidget::initPrinter()
563{
565#ifndef QT_NO_PRINTER
566 if (!m_printer)
567 m_printer = new QPrinter(QPrinter::ScreenResolution);
568#endif
569}
570
571void CentralWidget::connectSignals(HelpViewer *page)
572{
574#if defined(BROWSER_QTWEBKIT)
575 connect(page, &HelpViewer::printRequested,
576 this, &CentralWidget::print);
577#endif
578#if QT_CONFIG(clipboard)
579 connect(page, &HelpViewer::copyAvailable,
580 this, &CentralWidget::copyAvailable);
581#endif
582 connect(page, &HelpViewer::forwardAvailable,
584 connect(page, &HelpViewer::backwardAvailable,
586 connect(page, &HelpViewer::sourceChanged,
587 this, &CentralWidget::handleSourceChanged);
588 connect(page, QOverload<const QUrl &>::of(&HelpViewer::highlighted),
589 this, &CentralWidget::slotHighlighted);
590}
591
592bool CentralWidget::eventFilter(QObject *object, QEvent *e)
593{
595 if (e->type() != QEvent::KeyPress)
596 return QWidget::eventFilter(object, e);
597
599 QKeyEvent *keyEvent = static_cast<QKeyEvent*> (e);
600 if (viewer == object && keyEvent->key() == Qt::Key_Backspace) {
601 if (viewer->isBackwardAvailable()) {
602#if defined(BROWSER_QTWEBKIT)
603 // this helps in case there is an html <input> field
604 if (!viewer->hasFocus())
605#endif // BROWSER_QTWEBKIT
606 viewer->backward();
607 }
608 }
609 return QWidget::eventFilter(object, e);
610}
611
612QT_END_NAMESPACE
QString currentTitle() const
void backwardAvailable(bool available)
bool isBackwardAvailable() const
HelpViewer * viewerAt(int index) const
int currentIndex() const
static CentralWidget * instance()
void forwardAvailable(bool available)
bool hasSelection() const
CentralWidget(QWidget *parent=nullptr)
bool eventFilter(QObject *object, QEvent *e) override
Filters events if this object has been installed as an event filter for the watched object.
QUrl currentSource() const
HelpViewer * currentHelpViewer() const
void focusInEvent(QFocusEvent *event) override
This event handler can be reimplemented in a subclass to receive keyboard focus events (focus receive...
void updateUserInterface()
~CentralWidget() override
bool isForwardAvailable() const
void escapePressed()
void findPrevious()
void showAndClear()
void setPalette(bool found)
void show()
QHelpSearchEngine * searchEngine() const
static HelpEngineWrapper & instance()
void setLastTabPage(int lastPage)
void scaleUp()
void forwardAvailable(bool enabled)
void resetScale()
void loadFinished()
void forward()
void scaleDown()
bool isForwardAvailable() const
void backward()
void backwardAvailable(bool enabled)
bool isBackwardAvailable() const
HelpViewer * createBlankPage()
static OpenPagesManager * instance()
void setCurrent(HelpViewer *viewer)
void removeTabAt(HelpViewer *viewer)
Combined button and popup list for selecting options.
#define TRACE_OBJ
Definition tracer.h:34