1 /*
2
3 Copyright 2018 Marshall Banana
4 Copyright 2012-2013, 2018 Adam Reichold
5 Copyright 2014 Dorian Scholz
6 Copyright 2012 Michał Trybus
7 Copyright 2013 Chris Young
8
9 This file is part of qpdfview.
10
11 qpdfview is free software: you can redistribute it and/or modify
12 it under the terms of the GNU General Public License as published by
13 the Free Software Foundation, either version 2 of the License, or
14 (at your option) any later version.
15
16 qpdfview is distributed in the hope that it will be useful,
17 but WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 GNU General Public License for more details.
20
21 You should have received a copy of the GNU General Public License
22 along with qpdfview. If not, see <http://www.gnu.org/licenses/>.
23
24 */
25
26 #include <iostream>
27
28 #include <QApplication>
29 #include <QDebug>
30 #include <QDir>
31 #include <QInputDialog>
32 #include <QLibraryInfo>
33 #include <QMessageBox>
34 #include <QScopedPointer>
35 #include <QTranslator>
36
37 #ifdef WITH_DBUS
38
39 #include <QDBusInterface>
40 #include <QDBusReply>
41
42 #endif // WITH_DBUS
43
44 #ifdef WITH_SYNCTEX
45
46 #include <synctex_parser.h>
47
48 #ifndef HAS_SYNCTEX_2
49
50 typedef synctex_scanner_t synctex_scanner_p;
51 typedef synctex_node_t synctex_node_p;
52
53 #define synctex_scanner_next_result(scanner) synctex_next_result(scanner)
54 #define synctex_display_query(scanner, file, line, column, page) synctex_display_query(scanner, file, line, column)
55
56 #endif // HAS_SYNCTEX_2
57
58 #endif // WITH_SYNCTEX
59
60 #include "documentview.h"
61 #include "database.h"
62 #include "mainwindow.h"
63
64 #ifdef WITH_SIGNALS
65
66 #include "signalhandler.h"
67
68 #endif // WITH_SIGNALS
69
70 #ifdef __amigaos4__
71
72 #include <proto/dos.h>
73 #include <workbench/startup.h>
74
75 const char* __attribute__((used)) stack_cookie = "\0$STACK:500000\0";
76
77 #endif // __amigaos4__
78
79 namespace
80 {
81
82 using namespace qpdfview;
83
84 struct File
85 {
86 QString filePath;
87 int page;
88
89 QString sourceName;
90 int sourceLine;
91 int sourceColumn;
92 QRectF enclosingBox;
93
File__anonb326af6f0111::File94 File() : filePath(), page(-1), sourceName(), sourceLine(-1), sourceColumn(-1), enclosingBox() {}
95
96 };
97
98 enum ExitStatus
99 {
100 ExitOk = 0,
101 ExitUnknownArgument = 1,
102 ExitIllegalArgument = 2,
103 ExitInconsistentArguments = 3,
104 ExitDBusError = 4
105 };
106
107 bool unique = false;
108 bool quiet = false;
109
110 QString instanceName;
111 QString searchText;
112
113 QList< File > files;
114
115 MainWindow* mainWindow = 0;
116
loadTranslator(QTranslator * const translator,const QString & fileName,const QString & path)117 bool loadTranslator(QTranslator* const translator, const QString& fileName, const QString& path)
118 {
119 #if QT_VERSION >= QT_VERSION_CHECK(4,8,0)
120
121 const bool ok = translator->load(QLocale::system(), fileName, "_", path);
122
123 #else
124
125 const bool ok = translator->load(fileName + "_" + QLocale::system().name(), path);
126
127 #endif // QT_VERSION
128
129 if(ok)
130 {
131 qApp->installTranslator(translator);
132 }
133
134 return ok;
135 }
136
loadTranslators()137 void loadTranslators()
138 {
139 QTranslator* toolkitTranslator = new QTranslator(qApp);
140 loadTranslator(toolkitTranslator, "qt", QLibraryInfo::location(QLibraryInfo::TranslationsPath));
141
142 QTranslator* applicationTranslator = new QTranslator(qApp);
143 if(loadTranslator(applicationTranslator, "qpdfview", QDir(QApplication::applicationDirPath()).filePath("data"))) {}
144 else if(loadTranslator(applicationTranslator, "qpdfview", DATA_INSTALL_PATH)) {}
145 else if(loadTranslator(applicationTranslator, "qpdfview", ":/")) {}
146 }
147
parseCommandLineArguments()148 void parseCommandLineArguments()
149 {
150 bool instanceNameIsNext = false;
151 bool searchTextIsNext = false;
152 bool noMoreOptions = false;
153
154 QRegExp fileAndPageRegExp("(.+)#(\\d+)");
155 QRegExp fileAndSourceRegExp("(.+)#src:(.+):(\\d+):(\\d+)");
156 QRegExp instanceNameRegExp("[A-Za-z_]+[A-Za-z0-9_]*");
157
158 QStringList arguments = QApplication::arguments();
159
160 if(!arguments.isEmpty())
161 {
162 arguments.removeFirst();
163 }
164
165 foreach(const QString& argument, arguments)
166 {
167 if(instanceNameIsNext)
168 {
169 if(argument.isEmpty())
170 {
171 qCritical() << QObject::tr("An empty instance name is not allowed.");
172 exit(ExitIllegalArgument);
173 }
174
175 instanceNameIsNext = false;
176 instanceName = argument;
177 }
178 else if(searchTextIsNext)
179 {
180 if(argument.isEmpty())
181 {
182 qCritical() << QObject::tr("An empty search text is not allowed.");
183 exit(ExitIllegalArgument);
184 }
185
186 searchTextIsNext = false;
187 searchText = argument;
188 }
189 else if(!noMoreOptions && argument.startsWith("--"))
190 {
191 if(argument == QLatin1String("--unique"))
192 {
193 unique = true;
194 }
195 else if(argument == QLatin1String("--quiet"))
196 {
197 quiet = true;
198 }
199 else if(argument == QLatin1String("--instance"))
200 {
201 instanceNameIsNext = true;
202 }
203 else if(argument == QLatin1String("--search"))
204 {
205 searchTextIsNext = true;
206 }
207 else if(argument == QLatin1String("--choose-instance"))
208 {
209 bool ok = false;
210 const QString chosenInstanceName = QInputDialog::getItem(0, MainWindow::tr("Choose instance"), MainWindow::tr("Instance:"), Database::instance()->knownInstanceNames(), 0, true, &ok);
211
212 if(ok)
213 {
214 instanceName = chosenInstanceName;
215 }
216 }
217 else if(argument == QLatin1String("--help"))
218 {
219 std::cout << "Usage: qpdfview [options] [--] [file[#page]] [file[#src:name:line:column]] ..." << std::endl
220 << std::endl
221 << "Available options:" << std::endl
222 << " --help Show this information" << std::endl
223 << " --quiet Suppress warning messages when opening files" << std::endl
224 << " --search text Search for text in the current tab" << std::endl
225 << " --unique Open files as tabs in unique window" << std::endl
226 << " --unique --instance name Open files as tabs in named instance" << std::endl
227 << " --unique --choose-instance Open files as tabs after choosing an instance name" << std::endl
228 << std::endl
229 << "Please report bugs at \"https://launchpad.net/qpdfview\"." << std::endl;
230
231 exit(ExitOk);
232 }
233 else if(argument == QLatin1String("--"))
234 {
235 noMoreOptions = true;
236 }
237 else
238 {
239 qCritical() << QObject::tr("Unknown command-line option '%1'.").arg(argument);
240 exit(ExitUnknownArgument);
241 }
242 }
243 else
244 {
245 File file;
246
247 if(fileAndPageRegExp.exactMatch(argument))
248 {
249 file.filePath = fileAndPageRegExp.cap(1);
250 file.page = fileAndPageRegExp.cap(2).toInt();
251 }
252 else if(fileAndSourceRegExp.exactMatch(argument))
253 {
254 file.filePath = fileAndSourceRegExp.cap(1);
255 file.sourceName = fileAndSourceRegExp.cap(2);
256 file.sourceLine = fileAndSourceRegExp.cap(3).toInt();
257 file.sourceColumn = fileAndSourceRegExp.cap(4).toInt();
258 }
259 else
260 {
261 file.filePath = argument;
262 }
263
264 files.append(file);
265 }
266 }
267
268 if(instanceNameIsNext)
269 {
270 qCritical() << QObject::tr("Using '--instance' requires an instance name.");
271 exit(ExitInconsistentArguments);
272 }
273
274 if(!unique && !instanceName.isEmpty())
275 {
276 qCritical() << QObject::tr("Using '--instance' is not allowed without using '--unique'.");
277 exit(ExitInconsistentArguments);
278 }
279
280 if(!instanceName.isEmpty() && !instanceNameRegExp.exactMatch(instanceName))
281 {
282 qCritical() << QObject::tr("An instance name must only contain the characters \"[A-Z][a-z][0-9]_\" and must not begin with a digit.");
283 exit(ExitIllegalArgument);
284 }
285
286 if(searchTextIsNext)
287 {
288 qCritical() << QObject::tr("Using '--search' requires a search text.");
289 exit(ExitInconsistentArguments);
290 }
291 }
292
parseWorkbenchExtendedSelection(int argc,char ** argv)293 void parseWorkbenchExtendedSelection(int argc, char** argv)
294 {
295 #ifdef __amigaos4__
296
297 if(argc == 0)
298 {
299 const int pathLength = 1024;
300 const QScopedArrayPointer< char > filePath(new char[pathLength]);
301
302 const struct WBStartup* wbStartup = reinterpret_cast< struct WBStartup* >(argv);
303
304 for(int index = 1; index < wbStartup->sm_NumArgs; ++index)
305 {
306 const struct WBArg* wbArg = wbStartup->sm_ArgList + index;
307
308 if((wbArg->wa_Lock) && (*wbArg->wa_Name))
309 {
310 IDOS->DevNameFromLock(wbArg->wa_Lock, filePath.data(), pathLength, DN_FULLPATH);
311 IDOS->AddPart(filePath.data(), wbArg->wa_Name, pathLength);
312
313 File file;
314 file.filePath = filePath.data();
315
316 files.append(file);
317 }
318 }
319 }
320
321 #else
322
323 Q_UNUSED(argc);
324 Q_UNUSED(argv);
325
326 #endif // __amigaos4__
327 }
328
resolveSourceReferences()329 void resolveSourceReferences()
330 {
331 #ifdef WITH_SYNCTEX
332
333 for(int index = 0; index < files.count(); ++index)
334 {
335 File& file = files[index];
336
337 if(!file.sourceName.isNull())
338 {
339 if(synctex_scanner_p scanner = synctex_scanner_new_with_output_file(file.filePath.toLocal8Bit(), 0, 1))
340 {
341 if(synctex_display_query(scanner, file.sourceName.toLocal8Bit(), file.sourceLine, file.sourceColumn, -1) > 0)
342 {
343 for(synctex_node_p node = synctex_scanner_next_result(scanner); node != 0; node = synctex_scanner_next_result(scanner))
344 {
345 int page = synctex_node_page(node);
346 QRectF enclosingBox(synctex_node_box_visible_h(node), synctex_node_box_visible_v(node), synctex_node_box_visible_width(node), synctex_node_box_visible_height(node));
347
348 if(file.page != page)
349 {
350 file.page = page;
351 file.enclosingBox = enclosingBox;
352 }
353 else
354 {
355 file.enclosingBox = file.enclosingBox.united(enclosingBox);
356 }
357 }
358 }
359
360 synctex_scanner_free(scanner);
361 }
362 else
363 {
364 qWarning() << DocumentView::tr("SyncTeX data for '%1' could not be found.").arg(file.filePath);
365 }
366 }
367 }
368
369 #endif // WITH_SYNCTEX
370 }
371
activateUniqueInstance()372 void activateUniqueInstance()
373 {
374 qApp->setObjectName(instanceName);
375
376 #ifdef WITH_DBUS
377
378 if(unique)
379 {
380 QScopedPointer< QDBusInterface > interface(MainWindowAdaptor::createInterface());
381
382 if(interface->isValid())
383 {
384 interface->call("raiseAndActivate");
385
386 foreach(const File& file, files)
387 {
388 QDBusReply< bool > reply = interface->call("jumpToPageOrOpenInNewTab", QFileInfo(file.filePath).absoluteFilePath(), file.page, true, file.enclosingBox, quiet);
389
390 if(!reply.isValid())
391 {
392 qCritical() << QDBusConnection::sessionBus().lastError().message();
393
394 exit(ExitDBusError);
395 }
396 }
397
398 if(!files.isEmpty())
399 {
400 interface->call("saveDatabase");
401 }
402
403 if(!searchText.isEmpty())
404 {
405 interface->call("startSearch", searchText);
406 }
407
408 exit(ExitOk);
409 }
410 else
411 {
412 mainWindow = new MainWindow();
413
414 if(MainWindowAdaptor::createAdaptor(mainWindow) == 0)
415 {
416 qCritical() << QDBusConnection::sessionBus().lastError().message();
417
418 delete mainWindow;
419 exit(ExitDBusError);
420 }
421 }
422 }
423 else
424 {
425 mainWindow = new MainWindow();
426 }
427
428 #else
429
430 mainWindow = new MainWindow();
431
432 #endif // WITH_DBUS
433 }
434
prepareSignalHandler()435 void prepareSignalHandler()
436 {
437 #ifdef WITH_SIGNALS
438
439 if(SignalHandler::prepareSignals())
440 {
441 SignalHandler* signalHandler = new SignalHandler(mainWindow);
442
443 QObject::connect(signalHandler, SIGNAL(sigIntReceived()), mainWindow, SLOT(close()));
444 QObject::connect(signalHandler, SIGNAL(sigTermReceived()), mainWindow, SLOT(close()));
445 }
446 else
447 {
448 qWarning() << QObject::tr("Could not prepare signal handler.");
449 }
450
451 #endif // WITH_SIGNALS
452 }
453
454 } // anonymous
455
main(int argc,char ** argv)456 int main(int argc, char** argv)
457 {
458 qRegisterMetaType< QList< QRectF > >("QList<QRectF>");
459 qRegisterMetaType< Rotation >("Rotation");
460 qRegisterMetaType< RenderParam >("RenderParam");
461
462 parseWorkbenchExtendedSelection(argc, argv);
463
464 QApplication application(argc, argv);
465
466 QApplication::setOrganizationDomain("local.qpdfview");
467 QApplication::setOrganizationName("qpdfview");
468 QApplication::setApplicationName("qpdfview");
469
470 QApplication::setApplicationVersion(APPLICATION_VERSION);
471
472 QApplication::setWindowIcon(QIcon(":icons/qpdfview"));
473
474 #if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
475
476 QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
477
478 #endif // QT_VERSION
479
480 loadTranslators();
481
482 parseCommandLineArguments();
483
484 resolveSourceReferences();
485
486 activateUniqueInstance();
487
488 prepareSignalHandler();
489
490 mainWindow->show();
491 mainWindow->setAttribute(Qt::WA_DeleteOnClose);
492
493 foreach(const File& file, files)
494 {
495 mainWindow->jumpToPageOrOpenInNewTab(file.filePath, file.page, true, file.enclosingBox, quiet);
496 }
497
498 if(!files.isEmpty())
499 {
500 mainWindow->saveDatabase();
501 }
502
503 if(!searchText.isEmpty())
504 {
505 mainWindow->startSearch(searchText);
506 }
507
508 return application.exec();
509 }
510