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