1// Copyright (C) 2020 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3
4#include "mainwindow.h"
5
6#include "aboutdialog.h"
7#include "bookmarkmanager.h"
8#include "centralwidget.h"
9#include "cmdlineparser.h"
10#include "contentwindow.h"
11#include "globalactions.h"
12#include "helpenginewrapper.h"
13#include "indexwindow.h"
14#include "openpagesmanager.h"
15#include "preferencesdialog.h"
16#include "qtdocinstaller.h"
17#include "remotecontrol.h"
18#include "searchwidget.h"
19#include "topicchooser.h"
20#include "tracer.h"
21
22#include <QtCore/QByteArray>
23#include <QtCore/QDateTime>
24#include <QtCore/QDebug>
25#include <QtCore/QDir>
26#include <QtCore/QPair>
27#include <QtCore/QResource>
28#include <QtCore/QStandardPaths>
29#include <QtCore/QTextStream>
30#include <QtCore/QTimer>
31#include <QtCore/QBuffer>
32#include <QtCore/QLibraryInfo>
33#include <QtCore/QFileInfo>
34#include <QtCore/QDir>
35
36#include <QtGui/QAction>
37#include <QtGui/QFontDatabase>
38#include <QtGui/QImageReader>
39#include <QtGui/QScreen>
40#include <QtGui/QShortcut>
41
42#include <QtWidgets/QComboBox>
43#include <QtWidgets/QDockWidget>
44#include <QtWidgets/QFileDialog>
45#include <QtWidgets/QLabel>
46#include <QtWidgets/QLayout>
47#include <QtWidgets/QLineEdit>
48#include <QtWidgets/QMenuBar>
49#include <QtWidgets/QMessageBox>
50#include <QtWidgets/QProgressBar>
51#include <QtWidgets/QStatusBar>
52#include <QtWidgets/QToolBar>
53#include <QtWidgets/QToolButton>
54
55#include <QtHelp/QHelpContentModel>
56#include <QtHelp/QHelpEngineCore>
57#include <QtHelp/QHelpIndexModel>
58#include <QtHelp/QHelpSearchEngine>
59#include <QtHelp/QHelpFilterEngine>
60
61#include <cstdlib>
62
63QT_BEGIN_NAMESPACE
64
65enum { warnAboutMissingModules = 0 };
66
67MainWindow::MainWindow(CmdLineParser *cmdLine, QWidget *parent)
68 : QMainWindow(parent)
69 , m_cmdLine(cmdLine)
70{
71 TRACE_OBJ
72
73 setToolButtonStyle(Qt::ToolButtonFollowStyle);
74 setDockOptions(dockOptions() | AllowNestedDocks);
75
76 QString collectionFile;
77 if (usesDefaultCollection()) {
78 MainWindow::collectionFileDirectory(createDir: true);
79 collectionFile = MainWindow::defaultHelpCollectionFileName();
80 } else {
81 collectionFile = cmdLine->collectionFile();
82 }
83 HelpEngineWrapper &helpEngineWrapper =
84 HelpEngineWrapper::instance(collectionFile);
85 BookmarkManager *bookMarkManager = BookmarkManager::instance();
86
87 if (!initHelpDB(registerInternalDoc: !cmdLine->collectionFileGiven())) {
88 qDebug(msg: "Fatal error: Help engine initialization failed. "
89 "Error message was: %s\nAssistant will now exit.",
90 qPrintable(HelpEngineWrapper::instance().error()));
91 std::exit(status: 1);
92 }
93
94 m_centralWidget = new CentralWidget(this);
95 setCentralWidget(m_centralWidget);
96
97 m_indexWindow = new IndexWindow(this);
98 QDockWidget *indexDock = new QDockWidget(tr(s: "Index"), this);
99 indexDock->setObjectName(QLatin1String("IndexWindow"));
100 indexDock->setWidget(m_indexWindow);
101 addDockWidget(area: Qt::LeftDockWidgetArea, dockwidget: indexDock);
102
103 m_contentWindow = new ContentWindow;
104 QDockWidget *contentDock = new QDockWidget(tr(s: "Contents"), this);
105 contentDock->setObjectName(QLatin1String("ContentWindow"));
106 contentDock->setWidget(m_contentWindow);
107 addDockWidget(area: Qt::LeftDockWidgetArea, dockwidget: contentDock);
108
109 m_searchWindow = new SearchWidget(helpEngineWrapper.searchEngine());
110 m_searchWindow->setFont(!helpEngineWrapper.usesBrowserFont() ? qApp->font()
111 : helpEngineWrapper.browserFont());
112 QDockWidget *searchDock = new QDockWidget(tr(s: "Search"), this);
113 searchDock->setObjectName(QLatin1String("SearchWindow"));
114 searchDock->setWidget(m_searchWindow);
115 addDockWidget(area: Qt::LeftDockWidgetArea, dockwidget: searchDock);
116
117 QDockWidget *bookmarkDock = new QDockWidget(tr(s: "Bookmarks"), this);
118 bookmarkDock->setObjectName(QLatin1String("BookmarkWindow"));
119 bookmarkDock->setWidget(m_bookmarkWidget
120 = bookMarkManager->bookmarkDockWidget());
121 addDockWidget(area: Qt::LeftDockWidgetArea, dockwidget: bookmarkDock);
122
123 QDockWidget *openPagesDock = new QDockWidget(tr(s: "Open Pages"), this);
124 openPagesDock->setObjectName(QLatin1String("Open Pages"));
125 OpenPagesManager *openPagesManager
126 = OpenPagesManager::createInstance(parent: this, defaultCollection: usesDefaultCollection(), cmdLineUrl: m_cmdLine->url());
127 openPagesDock->setWidget(openPagesManager->openPagesWidget());
128 addDockWidget(area: Qt::LeftDockWidgetArea, dockwidget: openPagesDock);
129
130 connect(sender: m_centralWidget, signal: &CentralWidget::addBookmark,
131 context: bookMarkManager, slot: &BookmarkManager::addBookmark);
132 connect(sender: bookMarkManager, signal: &BookmarkManager::escapePressed,
133 context: this, slot: &MainWindow::activateCurrentCentralWidgetTab);
134 connect(sender: bookMarkManager, signal: &BookmarkManager::setSource,
135 context: m_centralWidget, slot: &CentralWidget::setSource);
136 connect(sender: bookMarkManager, signal: &BookmarkManager::setSourceInNewTab,
137 context: openPagesManager, slot: [openPagesManager](const QUrl &url){ openPagesManager->createPage(url); });
138
139 QHelpSearchEngine *searchEngine = helpEngineWrapper.searchEngine();
140 connect(sender: searchEngine, signal: &QHelpSearchEngine::indexingStarted,
141 context: this, slot: &MainWindow::indexingStarted);
142 connect(sender: searchEngine, signal: &QHelpSearchEngine::indexingFinished,
143 context: this, slot: &MainWindow::indexingFinished);
144
145 QString defWindowTitle = tr(s: "Qt Assistant");
146 setWindowTitle(defWindowTitle);
147
148 setupActions();
149 statusBar()->show();
150 m_centralWidget->connectTabBar();
151
152 setupFilterToolbar();
153 setupAddressToolbar();
154
155 const QString windowTitle = helpEngineWrapper.windowTitle();
156 setWindowTitle(windowTitle.isEmpty() ? defWindowTitle : windowTitle);
157 QByteArray iconArray = helpEngineWrapper.applicationIcon();
158 if (iconArray.size() > 0) {
159 QBuffer buffer(&iconArray);
160 QImageReader reader(&buffer);
161 QIcon appIcon;
162 do {
163 QPixmap pix;
164 pix.convertFromImage(img: reader.read());
165 appIcon.addPixmap(pixmap: pix);
166 } while (reader.jumpToNextImage());
167 qApp->setWindowIcon(appIcon);
168 }
169#if !defined(Q_OS_MACOS) && !defined(Q_OS_WIN)
170 else {
171 QIcon appIcon(QLatin1String(":/qt-project.org/assistant/images/assistant-128.png"));
172 qApp->setWindowIcon(appIcon);
173 }
174#endif
175
176 QToolBar *toolBar = addToolBar(title: tr(s: "Bookmark Toolbar"));
177 toolBar->setObjectName(QLatin1String("Bookmark Toolbar"));
178 bookMarkManager->setBookmarksToolbar(toolBar);
179
180 toolBar->hide();
181 toolBarMenu()->addAction(action: toolBar->toggleViewAction());
182
183 QByteArray ba(helpEngineWrapper.mainWindow());
184 if (!ba.isEmpty())
185 restoreState(state: ba);
186
187 ba = helpEngineWrapper.mainWindowGeometry();
188 if (!ba.isEmpty()) {
189 restoreGeometry(geometry: ba);
190 } else {
191 tabifyDockWidget(first: contentDock, second: indexDock);
192 tabifyDockWidget(first: indexDock, second: bookmarkDock);
193 tabifyDockWidget(first: bookmarkDock, second: searchDock);
194 contentDock->raise();
195 const QRect screen = QGuiApplication::primaryScreen()->geometry();
196 adjustSize(); // make sure we won't start outside of the screen
197 resize(w: 4 * screen.width() / 5, h: 4 * screen.height() / 5);
198
199 move(screen.center() - rect().center());
200 }
201
202 if (!helpEngineWrapper.hasFontSettings()) {
203 helpEngineWrapper.setUseAppFont(false);
204 helpEngineWrapper.setUseBrowserFont(false);
205 helpEngineWrapper.setAppFont(qApp->font());
206 helpEngineWrapper.setAppWritingSystem(QFontDatabase::Latin);
207 helpEngineWrapper.setBrowserFont(qApp->font());
208 helpEngineWrapper.setBrowserWritingSystem(QFontDatabase::Latin);
209 } else {
210 updateApplicationFont();
211 }
212
213 updateAboutMenuText();
214
215 QTimer::singleShot(interval: 0, receiver: this, slot: &MainWindow::insertLastPages);
216 if (m_cmdLine->enableRemoteControl())
217 (void)new RemoteControl(this);
218
219 if (m_cmdLine->contents() == CmdLineParser::Show)
220 showContents();
221 else if (m_cmdLine->contents() == CmdLineParser::Hide)
222 hideContents();
223
224 if (m_cmdLine->index() == CmdLineParser::Show)
225 showIndex();
226 else if (m_cmdLine->index() == CmdLineParser::Hide)
227 hideIndex();
228
229 if (m_cmdLine->bookmarks() == CmdLineParser::Show)
230 showBookmarksDockWidget();
231 else if (m_cmdLine->bookmarks() == CmdLineParser::Hide)
232 hideBookmarksDockWidget();
233
234 if (m_cmdLine->search() == CmdLineParser::Show)
235 showSearch();
236 else if (m_cmdLine->search() == CmdLineParser::Hide)
237 hideSearch();
238
239 if (m_cmdLine->contents() == CmdLineParser::Activate)
240 showContents();
241 else if (m_cmdLine->index() == CmdLineParser::Activate)
242 showIndex();
243 else if (m_cmdLine->bookmarks() == CmdLineParser::Activate)
244 showBookmarksDockWidget();
245
246 if (!m_cmdLine->currentFilter().isEmpty()) {
247 const QString &curFilter = m_cmdLine->currentFilter();
248 if (helpEngineWrapper.filterEngine()->filters().contains(str: curFilter))
249 helpEngineWrapper.filterEngine()->setActiveFilter(curFilter);
250 }
251
252 if (usesDefaultCollection())
253 QTimer::singleShot(interval: 0, receiver: this, slot: &MainWindow::lookForNewQtDocumentation);
254 else
255 checkInitState();
256
257 connect(sender: &helpEngineWrapper, signal: &HelpEngineWrapper::documentationRemoved,
258 context: this, slot: &MainWindow::documentationRemoved);
259 connect(sender: &helpEngineWrapper, signal: &HelpEngineWrapper::documentationUpdated,
260 context: this, slot: &MainWindow::documentationUpdated);
261
262 setTabPosition(areas: Qt::AllDockWidgetAreas, tabPosition: QTabWidget::North);
263 GlobalActions::instance()->updateActions();
264 if (helpEngineWrapper.addressBarEnabled())
265 showNewAddress();
266}
267
268MainWindow::~MainWindow()
269{
270 TRACE_OBJ
271 delete m_qtDocInstaller;
272}
273
274bool MainWindow::usesDefaultCollection() const
275{
276 TRACE_OBJ
277 return m_cmdLine->collectionFile().isEmpty();
278}
279
280void MainWindow::closeEvent(QCloseEvent *e)
281{
282 TRACE_OBJ
283 BookmarkManager::destroy();
284 HelpEngineWrapper::instance().setMainWindow(saveState());
285 HelpEngineWrapper::instance().setMainWindowGeometry(saveGeometry());
286 QMainWindow::closeEvent(event: e);
287}
288
289bool MainWindow::initHelpDB(bool registerInternalDoc)
290{
291 TRACE_OBJ
292 HelpEngineWrapper &helpEngineWrapper = HelpEngineWrapper::instance();
293 if (!helpEngineWrapper.setupData())
294 return false;
295
296 if (!registerInternalDoc && helpEngineWrapper.defaultHomePage() == QLatin1String("help"))
297 helpEngineWrapper.setDefaultHomePage(QLatin1String("about:blank"));
298
299 return true;
300}
301
302static const char *docs[] = {
303 "assistant", "designer", "linguist", // Qt 4
304 "qmake",
305 "qt",
306 "qtqmake",
307 "activeqt",
308 "qtandroidextras",
309 "qtassistant",
310 "qtbluetooth",
311 "qtconcurrent",
312 "qtconnectivity",
313 "qtcore",
314 "qtdbus",
315 "qtdesigner",
316 "qtdoc",
317 "qtenginio",
318 "qtgraphicaleffects",
319 "qtgui",
320 "qthelp",
321 "qtimageformats",
322 "qtlinguist",
323 "qtlocation",
324 "qtmacextras",
325 "qtmultimedia",
326 "qtmultimediawidgets",
327 "qtnfc",
328 "qtnetwork",
329 "qtopengl",
330 "qtpositioning",
331 "qtprintsupport",
332 "qtqml",
333 "qtquick",
334 "qtscript",
335 "qtscripttools",
336 "qtsensors",
337 "qtsql",
338 "qtsvg",
339 "qttestlib",
340 "qtuitools",
341 "qtwebkit",
342 "qtwebkitexamples",
343 "qtwidgets",
344 "qtxml",
345 "qtxmlpatterns",
346 "qdoc",
347 "qtx11extras",
348 "qtserialport",
349 "qtquickcontrols",
350 "qtquickcontrolsstyles",
351 "qtquickdialogs",
352 "qtquicklayouts",
353 "qtwebsockets",
354 "qtwinextras"
355};
356
357static QStringList newQtDocumentation()
358{
359 QStringList result;
360 const QDir docDirectory(QLibraryInfo::path(p: QLibraryInfo::DocumentationPath));
361 const QFileInfoList entries = docDirectory.entryInfoList(nameFilters: QStringList(QStringLiteral("*.qch")),
362 filters: QDir::Files, sort: QDir::Name);
363 if (!entries.isEmpty()) {
364 result.reserve(asize: entries.size());
365 for (const QFileInfo &fi : entries)
366 result.append(t: fi.baseName());
367 return result;
368 }
369 if (warnAboutMissingModules)
370 qWarning() << "No documentation found in " << QDir::toNativeSeparators(pathName: docDirectory.absolutePath());
371 const int docCount = int(sizeof(docs) / sizeof(docs[0]));
372 result.reserve(asize: docCount);
373 for (int d = 0; d < docCount; ++d)
374 result.append(t: QLatin1String(docs[d]));
375 return result;
376}
377
378void MainWindow::lookForNewQtDocumentation()
379{
380 TRACE_OBJ
381 HelpEngineWrapper &helpEngine = HelpEngineWrapper::instance();
382
383 const QStringList &docs = newQtDocumentation();
384 const int docCount = docs.size();
385 QList<QtDocInstaller::DocInfo> qtDocInfos;
386 qtDocInfos.reserve(asize: docCount);
387 for (const QString &doc : docs) {
388 const QtDocInstaller::DocInfo docInfo(doc, helpEngine.qtDocInfo(component: doc));
389 qtDocInfos.append(t: docInfo);
390 if (warnAboutMissingModules && (docInfo.second.isEmpty() || docInfo.second.first().isEmpty()))
391 qWarning() << "No documentation found for " << doc;
392 }
393
394 m_qtDocInstaller = new QtDocInstaller(qtDocInfos);
395 connect(sender: m_qtDocInstaller, signal: &QtDocInstaller::docsInstalled,
396 context: this, slot: &MainWindow::qtDocumentationInstalled);
397 connect(sender: m_qtDocInstaller, signal: &QtDocInstaller::qchFileNotFound,
398 context: this, slot: &MainWindow::resetQtDocInfo);
399 connect(sender: m_qtDocInstaller, signal: &QtDocInstaller::registerDocumentation,
400 context: this, slot: &MainWindow::registerDocumentation);
401 if (helpEngine.qtDocInfo(component: QLatin1String("qt")).size() != 2)
402 statusBar()->showMessage(text: tr(s: "Looking for Qt Documentation..."));
403 m_qtDocInstaller->installDocs();
404}
405
406void MainWindow::qtDocumentationInstalled()
407{
408 TRACE_OBJ
409 OpenPagesManager::instance()->resetHelpPage();
410 statusBar()->clearMessage();
411 checkInitState();
412}
413
414void MainWindow::checkInitState()
415{
416 TRACE_OBJ
417 if (!m_cmdLine->enableRemoteControl()) {
418 HelpEngineWrapper::instance().initialDocSetupDone();
419 return;
420 }
421
422 HelpEngineWrapper &helpEngine = HelpEngineWrapper::instance();
423 if (helpEngine.contentModel()->isCreatingContents()
424 || helpEngine.indexModel()->isCreatingIndex()) {
425 if (!m_connectedInitSignals) {
426 connect(sender: helpEngine.contentModel(), signal: &QHelpContentModel::contentsCreated,
427 context: this, slot: &MainWindow::checkInitState);
428 connect(sender: helpEngine.indexModel(), signal: &QHelpIndexModel::indexCreated,
429 context: this, slot: &MainWindow::checkInitState);
430 m_connectedInitSignals = true;
431 }
432 } else {
433 if (m_connectedInitSignals) {
434 disconnect(sender: helpEngine.contentModel(), signal: nullptr, receiver: this, member: nullptr);
435 disconnect(sender: helpEngine.indexModel(), signal: nullptr, receiver: this, member: nullptr);
436 }
437 HelpEngineWrapper::instance().initialDocSetupDone();
438 emit initDone();
439 }
440}
441
442void MainWindow::insertLastPages()
443{
444 TRACE_OBJ
445 if (m_cmdLine->search() == CmdLineParser::Activate)
446 showSearch();
447}
448
449void MainWindow::setupActions()
450{
451 TRACE_OBJ
452 QString resourcePath = QLatin1String(":/qt-project.org/assistant/images/");
453#ifdef Q_OS_MAC
454 setUnifiedTitleAndToolBarOnMac(true);
455 resourcePath.append(QLatin1String("mac"));
456#else
457 resourcePath.append(s: QLatin1String("win"));
458#endif
459
460 QMenu *menu = menuBar()->addMenu(title: tr(s: "&File"));
461 OpenPagesManager *const openPages = OpenPagesManager::instance();
462 m_newTabAction = menu->addAction(text: tr(s: "New &Tab"),
463 args: openPages, args: &OpenPagesManager::createBlankPage);
464 m_newTabAction->setShortcut(QKeySequence::AddTab);
465 m_closeTabAction = menu->addAction(text: tr(s: "&Close Tab"),
466 args: openPages, args: &OpenPagesManager::closeCurrentPage);
467 m_closeTabAction->setShortcuts(QKeySequence::Close);
468 m_closeTabAction->setEnabled(openPages->pageCount() > 1);
469 connect(sender: openPages, signal: &OpenPagesManager::pageClosed,
470 context: this, slot: &MainWindow::handlePageCountChanged);
471 connect(sender: openPages, signal: &OpenPagesManager::pageAdded,
472 context: this, slot: &MainWindow::handlePageCountChanged);
473
474 menu->addSeparator();
475
476 m_pageSetupAction = menu->addAction(text: tr(s: "Page Set&up..."),
477 args&: m_centralWidget, args: &CentralWidget::pageSetup);
478 m_printPreviewAction = menu->addAction(text: tr(s: "Print Preview..."),
479 args&: m_centralWidget, args: &CentralWidget::printPreview);
480
481 GlobalActions *globalActions = GlobalActions::instance(parent: this);
482 menu->addAction(action: globalActions->printAction());
483 menu->addSeparator();
484
485 QIcon appExitIcon = QIcon::fromTheme(name: "application-exit");
486 QAction *tmp;
487#ifdef Q_OS_WIN
488 tmp = menu->addAction(appExitIcon, tr("E&xit"),
489 this, &QWidget::close);
490 tmp->setShortcut(QKeySequence(tr("CTRL+Q")));
491#else
492 tmp = menu->addAction(icon: appExitIcon, text: tr(s: "&Quit"),
493 args: this, args: &QWidget::close);
494 tmp->setShortcut(QKeySequence::Quit);
495#endif
496 tmp->setMenuRole(QAction::QuitRole);
497
498 menu = menuBar()->addMenu(title: tr(s: "&Edit"));
499#if QT_CONFIG(clipboard)
500 menu->addAction(action: globalActions->copyAction());
501#endif
502 menu->addAction(action: globalActions->findAction());
503
504 QAction *findNextAction = menu->addAction(text: tr(s: "Find &Next"),
505 args&: m_centralWidget, args: &CentralWidget::findNext);
506 findNextAction->setShortcuts(QKeySequence::FindNext);
507
508 QAction *findPreviousAction = menu->addAction(text: tr(s: "Find &Previous"),
509 args&: m_centralWidget, args: &CentralWidget::findPrevious);
510 findPreviousAction->setShortcuts(QKeySequence::FindPrevious);
511
512 menu->addSeparator();
513 tmp = menu->addAction(text: tr(s: "Preferences..."),
514 args: this, args: &MainWindow::showPreferences);
515 tmp->setMenuRole(QAction::PreferencesRole);
516
517 m_viewMenu = menuBar()->addMenu(title: tr(s: "&View"));
518 m_viewMenu->addAction(action: globalActions->zoomInAction());
519 m_viewMenu->addAction(action: globalActions->zoomOutAction());
520
521 m_resetZoomAction = m_viewMenu->addAction(text: tr(s: "Normal &Size"),
522 args&: m_centralWidget, args: &CentralWidget::resetZoom);
523 m_resetZoomAction->setPriority(QAction::LowPriority);
524 m_resetZoomAction->setIcon(QIcon(resourcePath + QLatin1String("/resetzoom.png")));
525 m_resetZoomAction->setShortcut(tr(s: "Ctrl+0"));
526
527 m_viewMenu->addSeparator();
528
529 m_viewMenu->addAction(text: tr(s: "Contents"), shortcut: QKeySequence(tr(s: "ALT+C")),
530 args: this, args: &MainWindow::showContents);
531 m_viewMenu->addAction(text: tr(s: "Index"), shortcut: QKeySequence(tr(s: "ALT+I")),
532 args: this, args: &MainWindow::showIndex);
533 m_viewMenu->addAction(text: tr(s: "Bookmarks"), shortcut: QKeySequence(tr(s: "ALT+O")),
534 args: this, args: &MainWindow::showBookmarksDockWidget);
535 m_viewMenu->addAction(text: tr(s: "Search"), shortcut: QKeySequence(tr(s: "ALT+S")),
536 args: this, args: &MainWindow::showSearch);
537 m_viewMenu->addAction(text: tr(s: "Open Pages"), shortcut: QKeySequence(tr(s: "ALT+P")),
538 args: this, args: &MainWindow::showOpenPages);
539
540 menu = menuBar()->addMenu(title: tr(s: "&Go"));
541 menu->addAction(action: globalActions->homeAction());
542 menu->addAction(action: globalActions->backAction());
543 menu->addAction(action: globalActions->nextAction());
544
545 m_syncAction = menu->addAction(text: tr(s: "Sync with Table of Contents"),
546 args: this, args: &MainWindow::syncContents);
547 m_syncAction->setIconText(tr(s: "Sync"));
548 m_syncAction->setIcon(QIcon(resourcePath + QLatin1String("/synctoc.png")));
549
550 menu->addSeparator();
551
552 tmp = menu->addAction(text: tr(s: "Next Page"),
553 args: openPages, args: &OpenPagesManager::nextPage);
554 tmp->setShortcuts(QList<QKeySequence>() << QKeySequence(tr(s: "Ctrl+Alt+Right"))
555 << QKeySequence(Qt::CTRL | Qt::Key_PageDown));
556
557 tmp = menu->addAction(text: tr(s: "Previous Page"),
558 args: openPages, args: &OpenPagesManager::previousPage);
559 tmp->setShortcuts(QList<QKeySequence>() << QKeySequence(tr(s: "Ctrl+Alt+Left"))
560 << QKeySequence(Qt::CTRL | Qt::Key_PageUp));
561
562 const Qt::Modifier modifier =
563#ifdef Q_OS_MAC
564 Qt::ALT;
565#else
566 Qt::CTRL;
567#endif
568
569 QShortcut *sct = new QShortcut(QKeySequence(modifier | Qt::Key_Tab), this);
570 connect(sender: sct, signal: &QShortcut::activated,
571 context: openPages, slot: &OpenPagesManager::nextPageWithSwitcher);
572 sct = new QShortcut(QKeySequence(modifier | Qt::SHIFT | Qt::Key_Tab), this);
573 connect(sender: sct, signal: &QShortcut::activated,
574 context: openPages, slot: &OpenPagesManager::previousPageWithSwitcher);
575
576 BookmarkManager::instance()->setBookmarksMenu(menuBar()->addMenu(title: tr(s: "&Bookmarks")));
577
578 menu = menuBar()->addMenu(title: tr(s: "&Help"));
579 m_aboutAction = menu->addAction(text: tr(s: "About..."),
580 args: this, args: &MainWindow::showAboutDialog);
581 m_aboutAction->setMenuRole(QAction::AboutRole);
582
583#if defined(Q_OS_UNIX) && !defined(Q_OS_MAC)
584 m_resetZoomAction->setIcon(QIcon::fromTheme(name: "zoom-original" , fallback: m_resetZoomAction->icon()));
585 m_syncAction->setIcon(QIcon::fromTheme(name: "view-refresh" , fallback: m_syncAction->icon()));
586#endif
587
588 QToolBar *navigationBar = addToolBar(title: tr(s: "Navigation Toolbar"));
589 navigationBar->setObjectName(QLatin1String("NavigationToolBar"));
590 navigationBar->addAction(action: globalActions->backAction());
591 navigationBar->addAction(action: globalActions->nextAction());
592 navigationBar->addAction(action: globalActions->homeAction());
593 navigationBar->addAction(action: m_syncAction);
594 navigationBar->addSeparator();
595#if QT_CONFIG(clipboard)
596 navigationBar->addAction(action: globalActions->copyAction());
597#endif
598 navigationBar->addAction(action: globalActions->printAction());
599 navigationBar->addAction(action: globalActions->findAction());
600 navigationBar->addSeparator();
601 navigationBar->addAction(action: globalActions->zoomInAction());
602 navigationBar->addAction(action: globalActions->zoomOutAction());
603 navigationBar->addAction(action: m_resetZoomAction);
604
605#if defined(Q_OS_MAC)
606 QMenu *windowMenu = new QMenu(tr("&Window"), this);
607 menuBar()->insertMenu(menu->menuAction(), windowMenu);
608 windowMenu->addAction(tr("Zoom"),
609 this, &QWidget::showMaximized);
610 windowMenu->addAction(tr("Minimize"), QKeySequence(tr("Ctrl+M")),
611 this, &QWidget::showMinimized);
612#endif
613
614 // content viewer connections
615#if QT_CONFIG(clipboard)
616 connect(sender: m_centralWidget, signal: &CentralWidget::copyAvailable,
617 context: globalActions, slot: &GlobalActions::setCopyAvailable);
618#endif
619 connect(sender: m_centralWidget, signal: &CentralWidget::currentViewerChanged,
620 context: globalActions, slot: &GlobalActions::updateActions);
621 connect(sender: m_centralWidget, signal: &CentralWidget::forwardAvailable,
622 context: globalActions, slot: &GlobalActions::updateActions);
623 connect(sender: m_centralWidget, signal: &CentralWidget::backwardAvailable,
624 context: globalActions, slot: &GlobalActions::updateActions);
625 connect(sender: m_centralWidget, signal: &CentralWidget::highlighted,
626 context: this, slot: [this](const QUrl &link) { statusBar()->showMessage(text: link.toString());} );
627
628 // index window
629 connect(sender: m_indexWindow, signal: &IndexWindow::linkActivated,
630 context: m_centralWidget, slot: &CentralWidget::setSource);
631 connect(sender: m_indexWindow, signal: &IndexWindow::documentsActivated,
632 context: this, slot: &MainWindow::showTopicChooser);
633 connect(sender: m_indexWindow, signal: &IndexWindow::escapePressed,
634 context: this, slot: &MainWindow::activateCurrentCentralWidgetTab);
635
636 // content window
637 connect(sender: m_contentWindow, signal: &ContentWindow::linkActivated,
638 context: m_centralWidget, slot: &CentralWidget::setSource);
639 connect(sender: m_contentWindow, signal: &ContentWindow::escapePressed,
640 context: this, slot: &MainWindow::activateCurrentCentralWidgetTab);
641
642 // search window
643 connect(sender: m_searchWindow, signal: &SearchWidget::requestShowLink,
644 context: CentralWidget::instance(), slot: &CentralWidget::setSourceFromSearch);
645 connect(sender: m_searchWindow, signal: &SearchWidget::requestShowLinkInNewTab,
646 context: OpenPagesManager::instance(), slot: &OpenPagesManager::createNewPageFromSearch);
647
648#if defined(QT_NO_PRINTER)
649 m_pageSetupAction->setVisible(false);
650 m_printPreviewAction->setVisible(false);
651 globalActions->printAction()->setVisible(false);
652#endif
653}
654
655QMenu *MainWindow::toolBarMenu()
656{
657 TRACE_OBJ
658 if (!m_toolBarMenu) {
659 m_viewMenu->addSeparator();
660 m_toolBarMenu = m_viewMenu->addMenu(title: tr(s: "Toolbars"));
661 }
662 return m_toolBarMenu;
663}
664
665void MainWindow::setupFilterToolbar()
666{
667 TRACE_OBJ
668 HelpEngineWrapper &helpEngine = HelpEngineWrapper::instance();
669 if (!helpEngine.filterFunctionalityEnabled())
670 return;
671
672 m_filterCombo = new QComboBox(this);
673 m_filterCombo->setMinimumWidth(QFontMetrics(QFont()).
674 horizontalAdvance(QLatin1String("MakeTheComboBoxWidthEnough")));
675
676 QToolBar *filterToolBar = addToolBar(title: tr(s: "Filter Toolbar"));
677 filterToolBar->setObjectName(QLatin1String("FilterToolBar"));
678 filterToolBar->addWidget(widget: new QLabel(tr(s: "Filtered by:").append(c: QLatin1Char(' ')),
679 this));
680 filterToolBar->addWidget(widget: m_filterCombo);
681
682 if (!helpEngine.filterToolbarVisible())
683 filterToolBar->hide();
684 toolBarMenu()->addAction(action: filterToolBar->toggleViewAction());
685
686 connect(sender: &helpEngine, signal: &HelpEngineWrapper::setupFinished,
687 context: this, slot: &MainWindow::setupFilterCombo, type: Qt::QueuedConnection);
688 connect(sender: m_filterCombo, signal: &QComboBox::activated,
689 context: this, slot: &MainWindow::filterDocumentation);
690 connect(sender: helpEngine.filterEngine(), signal: &QHelpFilterEngine::filterActivated,
691 context: this, slot: &MainWindow::currentFilterChanged);
692
693 setupFilterCombo();
694}
695
696void MainWindow::setupAddressToolbar()
697{
698 TRACE_OBJ
699 HelpEngineWrapper &helpEngine = HelpEngineWrapper::instance();
700 if (!helpEngine.addressBarEnabled())
701 return;
702
703 m_addressLineEdit = new QLineEdit(this);
704 QToolBar *addressToolBar = addToolBar(title: tr(s: "Address Toolbar"));
705 addressToolBar->setObjectName(QLatin1String("AddressToolBar"));
706 insertToolBarBreak(before: addressToolBar);
707
708 addressToolBar->addWidget(widget: new QLabel(tr(s: "Address:").append(c: QChar::Space),
709 this));
710 addressToolBar->addWidget(widget: m_addressLineEdit);
711
712 if (!helpEngine.addressBarVisible())
713 addressToolBar->hide();
714 toolBarMenu()->addAction(action: addressToolBar->toggleViewAction());
715
716 // address lineedit
717 connect(sender: m_addressLineEdit, signal: &QLineEdit::returnPressed,
718 context: this, slot: &MainWindow::gotoAddress);
719 connect(sender: m_centralWidget, signal: &CentralWidget::currentViewerChanged,
720 context: this, slot: QOverload<>::of(ptr: &MainWindow::showNewAddress));
721 connect(sender: m_centralWidget, signal: &CentralWidget::sourceChanged,
722 context: this, slot: QOverload<>::of(ptr: &MainWindow::showNewAddress));
723}
724
725void MainWindow::updateAboutMenuText()
726{
727 TRACE_OBJ
728 QByteArray ba = HelpEngineWrapper::instance().aboutMenuTexts();
729 if (ba.size() > 0) {
730 QString lang;
731 QString str;
732 QString trStr;
733 QString currentLang = QLocale::system().name();
734 int i = currentLang.indexOf(c: QLatin1Char('_'));
735 if (i > -1)
736 currentLang = currentLang.left(n: i);
737 QDataStream s(&ba, QIODevice::ReadOnly);
738 while (!s.atEnd()) {
739 s >> lang;
740 s >> str;
741 if (lang == QLatin1String("default") && trStr.isEmpty()) {
742 trStr = str;
743 } else if (lang == currentLang) {
744 trStr = str;
745 break;
746 }
747 }
748 if (!trStr.isEmpty())
749 m_aboutAction->setText(trStr);
750 }
751}
752
753void MainWindow::showNewAddress()
754{
755 TRACE_OBJ
756 showNewAddress(url: m_centralWidget->currentSource());
757}
758
759void MainWindow::showNewAddress(const QUrl &url)
760{
761 TRACE_OBJ
762 m_addressLineEdit->setText(url.toString());
763}
764
765void MainWindow::gotoAddress()
766{
767 TRACE_OBJ
768 m_centralWidget->setSource(m_addressLineEdit->text());
769}
770
771void MainWindow::showTopicChooser(const QList<QHelpLink> &documents,
772 const QString &keyword)
773{
774 TRACE_OBJ
775 TopicChooser tc(this, keyword, documents);
776 if (tc.exec() == QDialog::Accepted) {
777 m_centralWidget->setSource(tc.link());
778 }
779}
780
781void MainWindow::showPreferences()
782{
783 TRACE_OBJ
784 PreferencesDialog dia(this);
785 connect(sender: &dia, signal: &PreferencesDialog::updateApplicationFont,
786 context: this, slot: &MainWindow::updateApplicationFont);
787 connect(sender: &dia, signal: &PreferencesDialog::updateBrowserFont,
788 context: m_centralWidget, slot: &CentralWidget::updateBrowserFont);
789 connect(sender: &dia, signal: &PreferencesDialog::updateUserInterface,
790 context: m_centralWidget, slot: &CentralWidget::updateUserInterface);
791 dia.exec();
792}
793
794void MainWindow::syncContents()
795{
796 TRACE_OBJ
797 qApp->setOverrideCursor(QCursor(Qt::WaitCursor));
798 const QUrl url = m_centralWidget->currentSource();
799 showContents();
800 if (!m_contentWindow->syncToContent(url))
801 statusBar()->showMessage(
802 text: tr(s: "Could not find the associated content item."), timeout: 3000);
803 qApp->restoreOverrideCursor();
804}
805
806void MainWindow::showAboutDialog()
807{
808 TRACE_OBJ
809 HelpEngineWrapper &helpEngine = HelpEngineWrapper::instance();
810 QByteArray contents;
811 QByteArray ba = helpEngine.aboutTexts();
812 if (!ba.isEmpty()) {
813 QString lang;
814 QByteArray cba;
815 QString currentLang = QLocale::system().name();
816 int i = currentLang.indexOf(c: QLatin1Char('_'));
817 if (i > -1)
818 currentLang = currentLang.left(n: i);
819 QDataStream s(&ba, QIODevice::ReadOnly);
820 while (!s.atEnd()) {
821 s >> lang;
822 s >> cba;
823 if (lang == QLatin1String("default") && contents.isEmpty()) {
824 contents = cba;
825 } else if (lang == currentLang) {
826 contents = cba;
827 break;
828 }
829 }
830 }
831
832 AboutDialog aboutDia(this);
833
834 QByteArray iconArray;
835 if (!contents.isEmpty()) {
836 iconArray = helpEngine.aboutIcon();
837 QByteArray resources = helpEngine.aboutImages();
838 QPixmap pix;
839 pix.loadFromData(buf: iconArray);
840 aboutDia.setText(text: QString::fromUtf8(ba: contents), resources);
841 if (!pix.isNull())
842 aboutDia.setPixmap(pix);
843 aboutDia.setWindowTitle(aboutDia.documentTitle());
844 } else {
845 QByteArray resources;
846#if defined(BROWSER_QTWEBKIT)
847 QString browser = QStringLiteral("Qt WebKit");
848#else
849 QString browser = QStringLiteral("QTextBrowser");
850#endif
851 if (m_centralWidget->currentHelpViewer())
852 browser = QStringLiteral("QLiteHtmlWidget");
853 aboutDia.setText(text: tr(s: "<center>"
854 "<h3>%1</h3>"
855 "<p>Version %2</p>"
856 "<p>Browser: %3</p></center>"
857 "<p>Copyright (C) %4 The Qt Company Ltd.</p>")
858 .arg(args: tr(s: "Qt Assistant"), args: QLatin1String(QT_VERSION_STR), args&: browser, QStringLiteral("2023")),
859 resources);
860 QLatin1String path(":/qt-project.org/assistant/images/assistant-128.png");
861 aboutDia.setPixmap(QString(path));
862 }
863 if (aboutDia.windowTitle().isEmpty())
864 aboutDia.setWindowTitle(tr(s: "About %1").arg(a: windowTitle()));
865 aboutDia.exec();
866}
867
868void MainWindow::setContentsVisible(bool visible)
869{
870 TRACE_OBJ
871 if (visible)
872 showContents();
873 else
874 hideContents();
875}
876
877void MainWindow::showContents()
878{
879 TRACE_OBJ
880 activateDockWidget(w: m_contentWindow);
881}
882
883void MainWindow::hideContents()
884{
885 TRACE_OBJ
886 m_contentWindow->parentWidget()->hide();
887}
888
889void MainWindow::setIndexVisible(bool visible)
890{
891 TRACE_OBJ
892 if (visible)
893 showIndex();
894 else
895 hideIndex();
896}
897
898void MainWindow::showIndex()
899{
900 TRACE_OBJ
901 activateDockWidget(w: m_indexWindow);
902}
903
904void MainWindow::hideIndex()
905{
906 TRACE_OBJ
907 m_indexWindow->parentWidget()->hide();
908}
909
910void MainWindow::setBookmarksVisible(bool visible)
911{
912 TRACE_OBJ
913 if (visible)
914 showBookmarksDockWidget();
915 else
916 hideBookmarksDockWidget();
917}
918
919void MainWindow::showBookmarksDockWidget()
920{
921 TRACE_OBJ
922 activateDockWidget(w: m_bookmarkWidget);
923}
924
925void MainWindow::hideBookmarksDockWidget()
926{
927 TRACE_OBJ
928 m_bookmarkWidget->parentWidget()->hide();
929}
930
931void MainWindow::setSearchVisible(bool visible)
932{
933 TRACE_OBJ
934 if (visible)
935 showSearch();
936 else
937 hideSearch();
938}
939
940void MainWindow::showSearch()
941{
942 TRACE_OBJ
943 activateDockWidget(w: m_searchWindow);
944}
945
946void MainWindow::showOpenPages()
947{
948 TRACE_OBJ
949 activateDockWidget(w: OpenPagesManager::instance()->openPagesWidget());
950}
951
952void MainWindow::hideSearch()
953{
954 TRACE_OBJ
955 m_searchWindow->parentWidget()->hide();
956}
957
958void MainWindow::activateDockWidget(QWidget *w)
959{
960 TRACE_OBJ
961 w->parentWidget()->show();
962 w->parentWidget()->raise();
963 w->setFocus();
964}
965
966void MainWindow::setIndexString(const QString &str)
967{
968 TRACE_OBJ
969 m_indexWindow->setSearchLineEditText(str);
970}
971
972void MainWindow::activateCurrentBrowser()
973{
974 TRACE_OBJ
975 CentralWidget::instance()->activateTab();
976}
977
978void MainWindow::activateCurrentCentralWidgetTab()
979{
980 TRACE_OBJ
981 m_centralWidget->activateTab();
982}
983
984void MainWindow::updateApplicationFont()
985{
986 TRACE_OBJ
987 HelpEngineWrapper &helpEngine = HelpEngineWrapper::instance();
988 QFont font = qApp->font();
989 if (helpEngine.usesAppFont())
990 font = helpEngine.appFont();
991
992 const QWidgetList &widgets = QApplication::allWidgets();
993 for (QWidget *widget : widgets)
994 widget->setFont(font);
995}
996
997void MainWindow::setupFilterCombo()
998{
999 TRACE_OBJ
1000 HelpEngineWrapper &helpEngine = HelpEngineWrapper::instance();
1001 const QString currentFilter = helpEngine.filterEngine()->activeFilter();
1002 m_filterCombo->clear();
1003 m_filterCombo->addItem(atext: tr(s: "Unfiltered"));
1004 const QStringList allFilters = helpEngine.filterEngine()->filters();
1005 if (!allFilters.isEmpty())
1006 m_filterCombo->insertSeparator(index: 1);
1007 for (const QString &filter : allFilters)
1008 m_filterCombo->addItem(atext: filter, auserData: filter);
1009
1010 int idx = m_filterCombo->findData(data: currentFilter);
1011 if (idx < 0)
1012 idx = 0;
1013 m_filterCombo->setCurrentIndex(idx);
1014}
1015
1016void MainWindow::filterDocumentation(int filterIndex)
1017{
1018 TRACE_OBJ
1019
1020 const QString filter = m_filterCombo->itemData(index: filterIndex).toString();
1021 HelpEngineWrapper::instance().filterEngine()->setActiveFilter(filter);
1022}
1023
1024void MainWindow::expandTOC(int depth)
1025{
1026 TRACE_OBJ
1027 Q_ASSERT(depth >= -1);
1028 m_contentWindow->expandToDepth(depth);
1029}
1030
1031void MainWindow::indexingStarted()
1032{
1033 TRACE_OBJ
1034 if (!m_progressWidget) {
1035 m_progressWidget = new QWidget();
1036 QLayout* hlayout = new QHBoxLayout(m_progressWidget);
1037
1038 QSizePolicy sizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum);
1039
1040 QLabel *label = new QLabel(tr(s: "Updating search index"));
1041 label->setSizePolicy(sizePolicy);
1042 hlayout->addWidget(w: label);
1043
1044 QProgressBar *progressBar = new QProgressBar();
1045 progressBar->setRange(minimum: 0, maximum: 0);
1046 progressBar->setTextVisible(false);
1047 progressBar->setSizePolicy(sizePolicy);
1048
1049 hlayout->setSpacing(6);
1050 hlayout->setContentsMargins(QMargins());
1051 hlayout->addWidget(w: progressBar);
1052
1053 statusBar()->addPermanentWidget(widget: m_progressWidget);
1054 }
1055}
1056
1057void MainWindow::indexingFinished()
1058{
1059 TRACE_OBJ
1060 statusBar()->removeWidget(widget: m_progressWidget);
1061 delete m_progressWidget;
1062 m_progressWidget = nullptr;
1063}
1064
1065QString MainWindow::collectionFileDirectory(bool createDir, const QString &cacheDir)
1066{
1067 TRACE_OBJ
1068 QString collectionPath =
1069 QStandardPaths::writableLocation(type: QStandardPaths::GenericDataLocation);
1070 if (collectionPath.isEmpty()) {
1071 if (cacheDir.isEmpty())
1072 collectionPath = QDir::homePath() + QDir::separator()
1073 + QLatin1String(".assistant");
1074 else
1075 collectionPath = QDir::homePath() + QLatin1String("/.") + cacheDir;
1076 } else {
1077 if (cacheDir.isEmpty())
1078 collectionPath = collectionPath + QLatin1String("/QtProject/Assistant");
1079 else
1080 collectionPath = collectionPath + QDir::separator() + cacheDir;
1081 }
1082 if (createDir) {
1083 QDir dir;
1084 if (!dir.exists(name: collectionPath))
1085 dir.mkpath(dirPath: collectionPath);
1086 }
1087 return collectionPath;
1088}
1089
1090QString MainWindow::defaultHelpCollectionFileName()
1091{
1092 TRACE_OBJ
1093 // forces creation of the default collection file path
1094 return collectionFileDirectory(createDir: true) + QDir::separator() +
1095 QString(QLatin1String("qthelpcollection_%1.qhc")).
1096 arg(a: QLatin1String(QT_VERSION_STR));
1097}
1098
1099void MainWindow::currentFilterChanged(const QString &filter)
1100{
1101 TRACE_OBJ
1102 int index = m_filterCombo->findData(data: filter);
1103 if (index < 0)
1104 index = 0;
1105 m_filterCombo->setCurrentIndex(index);
1106}
1107
1108void MainWindow::documentationRemoved(const QString &namespaceName)
1109{
1110 TRACE_OBJ
1111 OpenPagesManager::instance()->closePages(nameSpace: namespaceName);
1112}
1113
1114void MainWindow::documentationUpdated(const QString &namespaceName)
1115{
1116 TRACE_OBJ
1117 OpenPagesManager::instance()->reloadPages(nameSpace: namespaceName);
1118}
1119
1120void MainWindow::resetQtDocInfo(const QString &component)
1121{
1122 TRACE_OBJ
1123 HelpEngineWrapper::instance().setQtDocInfo(component,
1124 doc: QStringList(QDateTime().toString(format: Qt::ISODate)));
1125}
1126
1127void MainWindow::registerDocumentation(const QString &component,
1128 const QString &absFileName)
1129{
1130 TRACE_OBJ
1131 QString ns = QHelpEngineCore::namespaceName(documentationFileName: absFileName);
1132 if (ns.isEmpty())
1133 return;
1134
1135 HelpEngineWrapper &helpEngine = HelpEngineWrapper::instance();
1136 if (helpEngine.registeredDocumentations().contains(str: ns))
1137 helpEngine.unregisterDocumentation(namespaceName: ns);
1138 if (!helpEngine.registerDocumentation(docFile: absFileName)) {
1139 QMessageBox::warning(parent: this, title: tr(s: "Qt Assistant"),
1140 text: tr(s: "Could not register file '%1': %2").
1141 arg(a: absFileName).arg(a: helpEngine.error()));
1142 } else {
1143 QStringList docInfo;
1144 docInfo << QFileInfo(absFileName).lastModified().toString(format: Qt::ISODate)
1145 << absFileName;
1146 helpEngine.setQtDocInfo(component, doc: docInfo);
1147 }
1148}
1149
1150void MainWindow::handlePageCountChanged()
1151{
1152 m_closeTabAction->setEnabled(OpenPagesManager::instance()->pageCount() > 1);
1153}
1154
1155QT_END_NAMESPACE
1156

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