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
main.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#include "tracer.h"
4
5#include <QtCore/QDir>
6#include <QtCore/QFileInfo>
7#include <QtCore/QLibraryInfo>
8#include <QtCore/QLocale>
9#include <QtCore/QStringList>
10#include <QtCore/QTranslator>
11#include <QtCore/QUrl>
12
13#include <QtWidgets/QApplication>
14#include <QtGui/QDesktopServices>
15
16#include <QtHelp/QHelpEngine>
17#include <QtHelp/QHelpSearchEngine>
18
19#include <QtSql/QSqlDatabase>
20
21#if defined(BROWSER_QTWEBKIT)
22#include <QtGui/QFont>
23#include <QWebSettings>
24#endif
25
26#include "../shared/collectionconfiguration.h"
28#include "mainwindow.h"
29#include "cmdlineparser.h"
30
31#include <memory>
32
33// #define TRACING_REQUESTED
34
35QT_USE_NAMESPACE
36
37using namespace Qt::StringLiterals;
38
39namespace {
40
41void
42updateLastPagesOnUnregister(QHelpEngineCore& helpEngine, const QString& nsName)
43{
45 int lastPage = CollectionConfiguration::lastTabPage(helpEngine);
46 QStringList currentPages = CollectionConfiguration::lastShownPages(helpEngine);
47 if (!currentPages.isEmpty()) {
48 QStringList zoomList = CollectionConfiguration::lastZoomFactors(helpEngine);
49 while (zoomList.size() < currentPages.size())
50 zoomList.append(CollectionConfiguration::DefaultZoomFactor);
51
52 for (int i = currentPages.size(); --i >= 0;) {
53 if (QUrl(currentPages.at(i)).host() == nsName) {
54 zoomList.removeAt(i);
55 currentPages.removeAt(i);
56 lastPage = (lastPage == (i + 1)) ? 1 : lastPage;
57 }
58 }
59
60 CollectionConfiguration::setLastShownPages(helpEngine, currentPages);
62 CollectionConfiguration::setLastZoomFactors(helpEngine, zoomList);
63 }
64}
65
66void stripNonexistingDocs(QHelpEngineCore& collection)
67{
69 const QStringList &namespaces = collection.registeredDocumentations();
70 for (const QString &ns : namespaces) {
71 QFileInfo fi(collection.documentationFileName(ns));
72 if (!fi.exists() || !fi.isFile())
73 collection.unregisterDocumentation(ns);
74 }
75}
76
77QString indexFilesFolder(const QString &collectionFile)
78{
80 QString indexFilesFolder = ".fulltextsearch"_L1;
81 if (!collectionFile.isEmpty()) {
82 QFileInfo fi(collectionFile);
83 const QString fileName = fi.fileName();
84 // Keep in sync with QHelpSearchEngineCorePrivate::indexFilesFolder().
85 const QString baseName = fileName.endsWith(".qhc"_L1) ? fileName.chopped(4) : QString();
86 indexFilesFolder = u'.' + (baseName.isEmpty() ? fileName + ".fulltextsearch"_L1 : baseName);
87 }
88 return indexFilesFolder;
89}
90
91/*
92 * Returns the expected absolute file path of the cached collection file
93 * correspondinging to the given collection's file.
94 * It may or may not exist yet.
95 */
96QString constructCachedCollectionFilePath(const QHelpEngineCore &collection)
97{
99 const QString &filePath = collection.collectionFile();
100 const QString &fileName = QFileInfo(filePath).fileName();
101 const QString &cacheDir = CollectionConfiguration::cacheDir(collection);
102 const QString &dir = !cacheDir.isEmpty()
103 && CollectionConfiguration::cacheDirIsRelativeToCollection(collection)
104 ? QFileInfo(filePath).dir().absolutePath()
105 + QDir::separator() + cacheDir
106 : MainWindow::collectionFileDirectory(false, cacheDir);
107 return dir + QDir::separator() + fileName;
108}
109
110bool synchronizeDocs(QHelpEngineCore &collection,
111 QHelpEngineCore &cachedCollection,
112 CmdLineParser &cmd)
113{
115 const QDateTime &lastCollectionRegisterTime =
116 CollectionConfiguration::lastRegisterTime(collection);
117 if (!lastCollectionRegisterTime.isValid() || lastCollectionRegisterTime
118 < CollectionConfiguration::lastRegisterTime(cachedCollection))
119 return true;
120
121 const QStringList &docs = collection.registeredDocumentations();
122 const QStringList &cachedDocs = cachedCollection.registeredDocumentations();
123
124 /*
125 * Ensure that the cached collection contains all docs that
126 * the collection contains.
127 */
128 for (const QString &doc : docs) {
129 if (!cachedDocs.contains(doc)) {
130 const QString &docFile = collection.documentationFileName(doc);
131 if (!cachedCollection.registerDocumentation(docFile)) {
132 cmd.showMessage(QCoreApplication::translate("Assistant",
133 "Error registering documentation file '%1': %2").
134 arg(docFile, cachedCollection.error()), true);
135 return false;
136 }
137 }
138 }
139
141
142 return true;
143}
144
145bool removeSearchIndex(const QString &collectionFile)
146{
148 const QString path =
149 QFileInfo(collectionFile).path() + u'/' + indexFilesFolder(collectionFile);
150
151 QDir dir(path);
152 if (!dir.exists())
153 return false;
154
155 // Remove only the database created by QHelpSearchIndexWriter.
156 dir.remove("fts"_L1);
157 return true;
158}
159
160QCoreApplication* createApplication(int &argc, char *argv[])
161{
163#ifndef Q_OS_WIN
164 // Look for arguments that imply command-line mode.
165 const char * cmdModeArgs[] = {
166 "-help", "-register", "-unregister", "-remove-search-index",
167 "-rebuild-search-index"
168 };
169 for (int i = 1; i < argc; ++i) {
170 for (size_t j = 0; j < sizeof cmdModeArgs/sizeof *cmdModeArgs; ++j) {
171 if (strcmp(argv[i], cmdModeArgs[j]) == 0)
172 return new QCoreApplication(argc, argv);
173 }
174 }
175#endif
176 QApplication *app = new QApplication(argc, argv);
177 app->connect(app, &QGuiApplication::lastWindowClosed, app,
178 &QCoreApplication::quit);
179 return app;
180}
181
182bool registerDocumentation(QHelpEngineCore &collection, CmdLineParser &cmd,
183 bool printSuccess)
184{
186 if (!collection.registerDocumentation(cmd.helpFile())) {
187 cmd.showMessage(QCoreApplication::translate("Assistant",
188 "Could not register documentation file\n%1\n\nReason:\n%2")
189 .arg(cmd.helpFile(), collection.error()), true);
190 return false;
191 }
192 if (printSuccess)
193 cmd.showMessage(QCoreApplication::translate("Assistant",
194 "Documentation successfully registered."),
195 false);
197 return true;
198}
199
200bool unregisterDocumentation(QHelpEngineCore &collection,
201 const QString &namespaceName, CmdLineParser &cmd, bool printSuccess)
202{
204 if (!collection.unregisterDocumentation(namespaceName)) {
205 cmd.showMessage(QCoreApplication::translate("Assistant",
206 "Could not unregister documentation"
207 " file\n%1\n\nReason:\n%2").
208 arg(cmd.helpFile(), collection.error()), true);
209 return false;
210 }
211 updateLastPagesOnUnregister(collection, namespaceName);
212 if (printSuccess)
213 cmd.showMessage(QCoreApplication::translate("Assistant",
214 "Documentation successfully unregistered."),
215 false);
216 return true;
217}
218
219void setupTranslation(const QString &fileName, const QString &dir)
220{
221 QTranslator *translator = new QTranslator(QCoreApplication::instance());
222 if (translator->load(QLocale(), fileName, "_"_L1, dir))
223 QCoreApplication::installTranslator(translator);
224}
225
226void setupTranslations()
227{
229 const QStringList resourceDirs
230 = QLibraryInfo::paths(QLibraryInfo::TranslationsPath);
231 for (const QString &resourceDir : resourceDirs) {
232 setupTranslation("assistant"_L1, resourceDir);
233 setupTranslation("qt"_L1, resourceDir);
234 setupTranslation("qt_help"_L1, resourceDir);
235 }
236}
237
238} // Anonymous namespace.
239
245
247{
248 /*
249 * Create the collection objects that we need. We always have the
250 * cached collection file. Depending on whether the user specified
251 * one, we also may have an input collection file.
252 */
253 const QString collectionFile = cmd->collectionFile();
254 const bool collectionFileGiven = !collectionFile.isEmpty();
255 std::unique_ptr<QHelpEngineCore> collection;
256 if (collectionFileGiven) {
257 collection.reset(new QHelpEngineCore(collectionFile));
258 if (!collection->setupData()) {
259 cmd->showMessage(QCoreApplication::translate("Assistant",
260 "Error reading collection file '%1': %2.")
261 .arg(collectionFile, collection->error()), true);
262 return ExitFailure;
263 }
264 }
265 const QString &cachedCollectionFile = collectionFileGiven
266 ? constructCachedCollectionFilePath(*collection)
267 : MainWindow::defaultHelpCollectionFileName();
268 if (collectionFileGiven && !QFileInfo(cachedCollectionFile).exists()
269 && !collection->copyCollectionFile(cachedCollectionFile)) {
270 cmd->showMessage(QCoreApplication::translate("Assistant",
271 "Error creating collection file '%1': %2.")
272 .arg(cachedCollectionFile, collection->error()), true);
273 return ExitFailure;
274 }
275 QHelpEngineCore cachedCollection(cachedCollectionFile);
276 // The cached collection is written to below (and by the help engine wrapper),
277 // and it needs to be created on the first run.
278 cachedCollection.setReadOnly(false);
279 if (!cachedCollection.setupData()) {
280 cmd->showMessage(QCoreApplication::translate("Assistant",
281 "Error reading collection file '%1': %2.")
282 .arg(cachedCollectionFile, cachedCollection.error()), true);
283 return ExitFailure;
284 }
285
286 stripNonexistingDocs(cachedCollection);
287 if (collectionFileGiven) {
288 if (CollectionConfiguration::isNewer(*collection, cachedCollection))
290 cachedCollection);
291 if (!synchronizeDocs(*collection, cachedCollection, *cmd))
292 return ExitFailure;
293 }
294
296 const QStringList &cachedDocs =
297 cachedCollection.registeredDocumentations();
298 const QString &namespaceName =
299 QHelpEngineCore::namespaceName(cmd->helpFile());
301 if (collectionFileGiven
302 && !registerDocumentation(*collection, *cmd, true))
303 return ExitFailure;
304 if (!cachedDocs.contains(namespaceName)
305 && !registerDocumentation(cachedCollection, *cmd, !collectionFileGiven))
306 return ExitFailure;
307 return ExitSuccess;
308 }
310 if (collectionFileGiven
311 && !unregisterDocumentation(*collection, namespaceName, *cmd, true))
312 return ExitFailure;
313 if (cachedDocs.contains(namespaceName)
314 && !unregisterDocumentation(cachedCollection, namespaceName,
315 *cmd, !collectionFileGiven))
316 return ExitFailure;
317 return ExitSuccess;
318 }
319 }
320
321 if (cmd->removeSearchIndex()) {
322 return removeSearchIndex(cachedCollectionFile)
323 ? ExitSuccess : ExitFailure;
324 }
325
326 if (!QSqlDatabase::isDriverAvailable("QSQLITE"_L1)) {
327 cmd->showMessage(QCoreApplication::translate("Assistant",
328 "Cannot load sqlite database driver!"),
329 true);
330 return ExitFailure;
331 }
332
333 if (!cmd->currentFilter().isEmpty()) {
334 if (collectionFileGiven)
335 collection->setCurrentFilter(cmd->currentFilter());
336 cachedCollection.setCurrentFilter(cmd->currentFilter());
337 }
338
339 if (collectionFileGiven)
340 cmd->setCollectionFile(cachedCollectionFile);
341
342 return NoExit;
343}
344
345int main(int argc, char *argv[])
346{
348 std::unique_ptr<QCoreApplication> a(createApplication(argc, argv));
349#if QT_CONFIG(library)
350 a->addLibraryPath(a->applicationDirPath() + "/plugins"_L1);
351#endif
352 setupTranslations();
353
354#if defined(BROWSER_QTWEBKIT)
355 if (qobject_cast<QApplication *>(a.data())) {
356 QFont f;
357 f.setStyleHint(QFont::SansSerif);
358 QWebSettings::globalSettings()->setFontFamily(QWebSettings::StandardFont, f.defaultFamily());
359 }
360#endif // BROWSER_QTWEBKIT
361
362 // Parse arguments.
363 CmdLineParser cmd(a->arguments());
364 CmdLineParser::Result res = cmd.parse();
365 if (res == CmdLineParser::Help)
366 return 0;
367 else if (res == CmdLineParser::Error)
368 return -1;
369
370 const ExitStatus status = preliminarySetup(&cmd);
371 switch (status) {
372 case ExitFailure: return EXIT_FAILURE;
373 case ExitSuccess: return EXIT_SUCCESS;
374 default: break;
375 }
376
377 MainWindow *w = new MainWindow(&cmd);
378 w->show();
379
380 /*
381 * We need to be careful here: The main window has to be deleted before
382 * the help engine wrapper, which has to be deleted before the
383 * QApplication.
384 */
385 const int retval = a->exec();
386 delete w;
388 return retval;
389}
RegisterState registerRequest() const
bool removeSearchIndex() const
static bool isNewer(const QHelpEngineCore &newer, const QHelpEngineCore &older)
static void setLastTabPage(QHelpEngineCore &helpEngine, int lastPage)
static void copyConfiguration(const QHelpEngineCore &source, QHelpEngineCore &target)
static void updateLastRegisterTime(QHelpEngineCore &helpEngine)
static int lastTabPage(const QHelpEngineCore &helpEngine)
static void removeInstance()
static ExitStatus preliminarySetup(CmdLineParser *cmd)
Definition main.cpp:246
ExitStatus
Definition main.cpp:240
@ ExitSuccess
Definition main.cpp:241
@ ExitFailure
Definition main.cpp:242
@ NoExit
Definition main.cpp:243
int main(int argc, char *argv[])
[ctor_close]
#define TRACE_OBJ
Definition tracer.h:34