1 /******************************************************************************************************
2  * (C) 2014 markummitchell@github.com. This file is part of Engauge Digitizer, which is released      *
3  * under GNU General Public License version 2 (GPLv2) or (at your option) any later version. See file *
4  * LICENSE or go to gnu.org/licenses for details. Distribution requires prior written permission.     *
5  ******************************************************************************************************/
6 
7 #include "ColorFilterMode.h"
8 #include "FittingCurveCoefficients.h"
9 #include "ImportImageExtensions.h"
10 #include "Logger.h"
11 #include "MainWindow.h"
12 #include <QApplication>
13 #include <QCoreApplication>
14 #include <QDebug>
15 #include <QDir>
16 #include <QFileInfo>
17 #include <QMessageBox>
18 #include <QObject>
19 #include <QProcessEnvironment>
20 #include <QStyleFactory>
21 #include "TranslatorContainer.h"
22 #include "ZoomFactor.h"
23 
24 using namespace std;
25 
26 const QString CMD_DEBUG ("debug");
27 const QString CMD_DROP_REGRESSION ("dropregression");
28 const QString CMD_ERROR_REPORT ("errorreport");
29 const QString CMD_EXPORT_ONLY ("exportonly");
30 const QString CMD_EXTRACT_IMAGE_ONLY ("extractimageonly");
31 const QString CMD_FILE_CMD_SCRIPT ("filecmdscript");
32 const QString CMD_GNUPLOT ("gnuplot");
33 const QString CMD_HELP ("help");
34 const QString CMD_REGRESSION ("regression");
35 const QString CMD_RESET ("reset");
36 const QString CMD_STYLE ("style"); // Qt handles this
37 const QString CMD_STYLES ("styles"); // Not to be confused with -style option that qt handles
38 const QString CMD_UPGRADE ("upgrade");
39 const QString DASH ("-");
40 const QString DASH_DEBUG ("-" + CMD_DEBUG);
41 const QString DASH_DROP_REGRESSION ("-" + CMD_DROP_REGRESSION);
42 const QString DASH_ERROR_REPORT ("-" + CMD_ERROR_REPORT);
43 const QString DASH_EXTRACT_IMAGE_ONLY ("-" + CMD_EXTRACT_IMAGE_ONLY);
44 const QString DASH_EXPORT_ONLY ("-" + CMD_EXPORT_ONLY);
45 const QString DASH_FILE_CMD_SCRIPT ("-" + CMD_FILE_CMD_SCRIPT);
46 const QString DASH_GNUPLOT ("-" + CMD_GNUPLOT);
47 const QString DASH_HELP ("-" + CMD_HELP);
48 const QString DASH_REGRESSION ("-" + CMD_REGRESSION);
49 const QString DASH_RESET ("-" + CMD_RESET);
50 const QString DASH_STYLE ("-" + CMD_STYLE);
51 const QString DASH_STYLES ("-" + CMD_STYLES);
52 const QString DASH_UPGRADE ("-" + CMD_UPGRADE);
53 const QString ENGAUGE_LOG_FILE (".engauge.log");
54 
55 // Prototypes
56 bool checkFileExists (const QString &file);
57 QString engaugeLogFilename ();
58 bool engaugeLogFilenameAttempt (const QString &path,
59                                 QString &pathAndFile);
60 void parseCmdLine (int argc,
61                    char **argv,
62                    bool &isDebug,
63                    bool &isDropRegression,
64                    bool &isReset,
65                    QString &errorReportFile,
66                    QString &fileCmdScriptFile,
67                    bool &isErrorReportRegressionTest,
68                    bool &isGnuplot,
69                    bool &isExportOnly,
70                    bool &isExtractImageOnly,
71                    QString &extractImageOnlyExtension,
72                    bool &isUpgrade,
73                    QStringList &loadStartupFiles,
74                    QStringList &commandLineWithoutLoadStartupFiles);
75 void sanityCheckLoadStartupFiles (bool isRepeatingFlag,
76                                   const QString &dashForRepeatingFlag,
77                                   const QString &errorReportFile,
78                                   const QString &fileCmdScriptFile,
79                                   const QStringList &loadStartupFiles);
80 void sanityCheckValue (bool requiredCondition,
81                        const QString &arg,
82                        const QString &msgUnadorned);
83 void showMessageAndQuit (const QString &msg);
84 void showStylesAndQuit ();
85 void showUsageAndQuit ();
86 void upgradeFiles (const QStringList &loadStartupFiles);
87 
88 // Functions
checkFileExists(const QString & file)89 bool checkFileExists (const QString &file)
90 {
91   QFileInfo check (file);
92   return check.exists() && check.isFile();
93 }
94 
engaugeLogFilename()95 QString engaugeLogFilename()
96 {
97   QString pathAndFile; // Return empty value in OSX which is unused
98 
99 #if !defined(OSX_RELEASE) && !defined(WIN_RELEASE) && !defined(APPIMAGE_RELEASE)
100   QProcessEnvironment env;
101 
102   // Make multiple attempts until a directory is found where the log file can be written
103   if (!engaugeLogFilenameAttempt (QCoreApplication::applicationDirPath(), pathAndFile)) {
104     if (!engaugeLogFilenameAttempt (env.value ("HOME"), pathAndFile)) {
105       if (!engaugeLogFilenameAttempt (env.value ("TEMP"), pathAndFile)) {
106         pathAndFile = ENGAUGE_LOG_FILE; // Current directory will have to do
107       }
108     }
109   }
110 #endif
111 
112   return pathAndFile;
113 }
114 
engaugeLogFilenameAttempt(const QString & path,QString & pathAndFile)115 bool engaugeLogFilenameAttempt (const QString &path,
116                                 QString &pathAndFile)
117 {
118   bool success = false;
119 
120   // Test if file can be opened. Checking permissions on directory is unreliable in Windows/OSX
121   pathAndFile = QString ("%1%2%3")
122                          .arg (path)
123                          .arg (QDir::separator())
124                          .arg (ENGAUGE_LOG_FILE);
125   QFile file (pathAndFile);
126   if (file.open(QIODevice::WriteOnly | QIODevice::Text)) {
127     // Success
128     file.close();
129     file.remove(); // Cleanup
130     success = true;
131   }
132 
133   return success;
134 }
135 
main(int argc,char * argv[])136 int main(int argc, char *argv[])
137 {
138   qRegisterMetaType<ColorFilterMode> ("ColorFilterMode");
139   qRegisterMetaType<FittingCurveCoefficients> ("FilterCurveCoefficients");
140   qRegisterMetaType<ZoomFactor> ("ZoomFactor");
141 
142   QApplication app(argc, argv);
143 
144   // Translations
145   TranslatorContainer translatorContainer (app); // Must exist until execution terminates
146 
147   // Command line
148   bool isDebug, isDropRegression, isReset, isGnuplot, isErrorReportRegressionTest, isExportOnly, isExtractImageOnly, isUpgrade;
149   QString errorReportFile, extractImageOnlyExtension, fileCmdScriptFile;
150   QStringList loadStartupFiles, commandLineWithoutLoadStartupFiles;
151   parseCmdLine (argc,
152                 argv,
153                 isDebug,
154                 isDropRegression,
155                 isReset,
156                 errorReportFile,
157                 fileCmdScriptFile,
158                 isErrorReportRegressionTest,
159                 isGnuplot,
160                 isExportOnly,
161                 isExtractImageOnly,
162                 extractImageOnlyExtension,
163                 isUpgrade,
164                 loadStartupFiles,
165                 commandLineWithoutLoadStartupFiles);
166 
167   // Logging
168   initializeLogging ("engauge",
169                      engaugeLogFilename(),
170                      isDebug);
171   LOG4CPP_INFO_S ((*mainCat)) << "main args=" << QApplication::arguments().join (" ").toLatin1().data();
172 
173   // Upgrade or run normally
174   int rtn = 0;
175   if (isUpgrade) {
176     upgradeFiles (loadStartupFiles);
177   } else {
178     // Create and show main window
179     MainWindow w (errorReportFile,
180                   fileCmdScriptFile,
181                   isDropRegression,
182                   isErrorReportRegressionTest,
183                   isGnuplot,
184                   isReset,
185                   isExportOnly,
186                   isExtractImageOnly,
187                   extractImageOnlyExtension,
188                   loadStartupFiles,
189                   commandLineWithoutLoadStartupFiles);
190     w.show();
191 
192     // Event loop
193     rtn = app.exec();
194   }
195 
196   return rtn;
197 }
198 
parseCmdLine(int argc,char ** argv,bool & isDebug,bool & isDropRegression,bool & isReset,QString & errorReportFile,QString & fileCmdScriptFile,bool & isErrorReportRegressionTest,bool & isGnuplot,bool & isExportOnly,bool & isExtractImageOnly,QString & extractImageOnlyExtension,bool & isUpgrade,QStringList & loadStartupFiles,QStringList & commandLineWithoutLoadStartupFiles)199 void parseCmdLine (int argc,
200                    char **argv,
201                    bool &isDebug,
202                    bool &isDropRegression,
203                    bool &isReset,
204                    QString &errorReportFile,
205                    QString &fileCmdScriptFile,
206                    bool &isErrorReportRegressionTest,
207                    bool &isGnuplot,
208                    bool &isExportOnly,
209                    bool &isExtractImageOnly,
210                    QString &extractImageOnlyExtension,
211                    bool &isUpgrade,
212                    QStringList &loadStartupFiles,
213                    QStringList &commandLineWithoutLoadStartupFiles)
214 {
215   bool showUsage = false;
216 
217   ImportImageExtensions importImageExtensions;
218 
219   // State
220   bool nextIsErrorReportFile = false;
221   bool nextIsExtractImageOnly = false;
222   bool nextIsFileCmdScript = false;
223 
224   // Defaults
225   isDebug = false;
226   isDropRegression = false;
227   isReset = false;
228   errorReportFile = "";
229   fileCmdScriptFile = "";
230   isErrorReportRegressionTest = false;
231   isGnuplot = false;
232   isExportOnly = false;
233   isExtractImageOnly = false;
234   extractImageOnlyExtension = "";
235   isUpgrade = false;
236 
237   for (int i = 1; i < argc; i++) {
238 
239     bool isLoadStartupFile = false;
240 
241     if (nextIsErrorReportFile) {
242       sanityCheckValue (checkFileExists (argv [i]),
243                         argv [i],
244                         QObject::tr ("is not a valid file name"));
245       errorReportFile = argv [i];
246       nextIsErrorReportFile = false;
247     } else if (nextIsExtractImageOnly) {
248       sanityCheckValue (importImageExtensions.offers (argv [i]),
249                         argv [i],
250                         QObject::tr ("is not a valid image file extension"));
251       extractImageOnlyExtension = argv [i];
252       nextIsExtractImageOnly = false;
253     } else if (nextIsFileCmdScript) {
254       sanityCheckValue (checkFileExists (argv [i]),
255                         argv [i],
256                         QObject::tr ("is not a valid file name"));
257       fileCmdScriptFile = argv [i];
258       nextIsFileCmdScript = false;
259     } else if (strcmp (argv [i], DASH_DEBUG.toLatin1().data()) == 0) {
260       isDebug = true;
261     } else if (strcmp (argv [i], DASH_DROP_REGRESSION.toLatin1().data()) == 0) {
262       isDropRegression = true;
263     } else if (strcmp (argv [i], DASH_ERROR_REPORT.toLatin1().data()) == 0) {
264       nextIsErrorReportFile = true;
265     } else if (strcmp (argv [i], DASH_EXPORT_ONLY.toLatin1().data()) == 0) {
266       isExportOnly = true;
267     } else if (strcmp (argv [i], DASH_EXTRACT_IMAGE_ONLY.toLatin1().data()) == 0) {
268       isExtractImageOnly = true;
269       nextIsExtractImageOnly = true;
270     } else if (strcmp (argv [i], DASH_FILE_CMD_SCRIPT.toLatin1().data()) == 0) {
271       nextIsFileCmdScript = true;
272     } else if (strcmp (argv [i], DASH_GNUPLOT.toLatin1().data()) == 0) {
273       isGnuplot = true;
274     } else if (strcmp (argv [i], DASH_HELP.toLatin1().data()) == 0) {
275       showUsage = true; // User requested help
276     } else if (strcmp (argv [i], DASH_REGRESSION.toLatin1().data()) == 0) {
277       isErrorReportRegressionTest = true;
278     } else if (strcmp (argv [i], DASH_RESET.toLatin1().data()) == 0) {
279       isReset = true;
280     } else if (strcmp (argv [i], DASH_STYLE.toLatin1().data()) == 0) {
281       // This branch never executes because Qt strips out '-style <style>' and processes it.
282       // This comment is here just to document that special handling
283     } else if (strcmp (argv [i], DASH_STYLES.toLatin1().data()) == 0) {
284       showStylesAndQuit ();
285     } else if (strcmp (argv [i], DASH_UPGRADE.toLatin1().data()) == 0) {
286       isUpgrade = true;
287     } else if (strncmp (argv [i], DASH.toLatin1().data(), 1) == 0) {
288       showUsage = true; // User entered an unrecognized token
289     } else {
290       // MainWindow will change current directory (which is often some obscure application directory),
291       // so relative paths must be changed in advance to absolute so the files can still be found
292       QString fileName = argv [i];
293       QFileInfo fInfo (fileName);
294       if (fInfo.isRelative() && !fileName.startsWith ("http")) {
295         fileName = fInfo.absoluteFilePath();
296       }
297 
298       isLoadStartupFile = true;
299       loadStartupFiles << fileName; // Save file name
300     }
301 
302     // keep command line arguments
303     if (!isLoadStartupFile) {
304       commandLineWithoutLoadStartupFiles << argv [i];
305     }
306   }
307 
308   // Sanity checks
309   sanityCheckLoadStartupFiles (isExportOnly,
310                                DASH_EXPORT_ONLY,
311                                errorReportFile,
312                                fileCmdScriptFile,
313                                loadStartupFiles);
314   sanityCheckLoadStartupFiles (isExtractImageOnly,
315                                DASH_EXTRACT_IMAGE_ONLY,
316                                errorReportFile,
317                                fileCmdScriptFile,
318                                loadStartupFiles);
319 
320   // Usage
321   if (showUsage || nextIsErrorReportFile || nextIsExtractImageOnly || nextIsFileCmdScript) {
322 
323     showUsageAndQuit ();
324 
325   }
326 }
327 
sanityCheckLoadStartupFiles(bool isRepeatingFlag,const QString & dashForRepeatingFlag,const QString & errorReportFile,const QString & fileCmdScriptFile,const QStringList & loadStartupFiles)328 void sanityCheckLoadStartupFiles (bool isRepeatingFlag,
329                                   const QString &dashForRepeatingFlag,
330                                   const QString &errorReportFile,
331                                   const QString &fileCmdScriptFile,
332                                   const QStringList &loadStartupFiles)
333 {
334   if (isRepeatingFlag && (!errorReportFile.isEmpty() ||
335                           !fileCmdScriptFile.isEmpty() ||
336                           loadStartupFiles.size() == 0)) {
337 
338     // Condition that at only load files are specified, and there is at least one of them, is not satisfied so
339     // show more specific error message than showUsageAndQuit, and then quit
340     QString msg;
341     QTextStream str (&msg);
342     str << dashForRepeatingFlag.toLatin1().data() << " " << QObject::tr ("is used only with one or more load files");
343     showMessageAndQuit (msg);
344   }
345 }
346 
sanityCheckValue(bool requiredCondition,const QString & arg,const QString & msgUnadorned)347 void sanityCheckValue (bool requiredCondition,
348                        const QString &arg,
349                        const QString &msgUnadorned)
350 {
351   if (!requiredCondition) {
352 
353     // Required condition is not satisfied. Show a more specific error message than showUsageAndQuit and then quit
354     QString msg = QString ("%1 %2")
355         .arg (arg)
356         .arg (msgUnadorned);
357     showMessageAndQuit (msg);
358   }
359 }
360 
showMessageAndQuit(const QString & msg)361 void showMessageAndQuit (const QString &msg)
362 {
363   // Show message in QMessageBox instead of cout or cerr since console output is disabled in Microsoft Windows
364   QMessageBox::critical (nullptr,
365                          QObject::tr ("Engauge Digitizer"),
366                          msg);
367   exit (0);
368 }
369 
showStylesAndQuit()370 void showStylesAndQuit ()
371 {
372   QString msg;
373   QTextStream str (&msg);
374   str << QObject::tr ("Available styles") << ": " << QStyleFactory::keys ().join (", ");
375   showMessageAndQuit (msg);
376 }
377 
showUsageAndQuit()378 void showUsageAndQuit ()
379 {
380   QString msg;
381   QTextStream str (&msg);
382   str << "<html>Usage: engauge "
383       << "[" << DASH_DEBUG.toLatin1().data() << "] "
384       << "[" << DASH_DROP_REGRESSION.toLatin1().data() << "] "
385       << "[" << DASH_ERROR_REPORT.toLatin1().data() << " &lt;file&gt;] "
386       << "[" << DASH_EXPORT_ONLY.toLatin1().data() << "] "
387       << "[" << DASH_EXTRACT_IMAGE_ONLY.toLatin1().data() << " &lt;extension&gt;] "
388       << "[" << DASH_FILE_CMD_SCRIPT.toLatin1().data() << " &lt;file&gt; "
389       << "[" << DASH_GNUPLOT.toLatin1().data() << "] "
390       << "[" << DASH_HELP.toLatin1().data() << "] "
391       << "[" << DASH_REGRESSION.toLatin1().data() << "] "
392       << "[" << DASH_RESET.toLatin1().data () << "] "
393       << "[" << DASH_STYLE.toLatin1().data () << " &lt;style&gt;] "
394       << "[" << DASH_STYLES.toLatin1().data () << "] "
395       << "[&lt;load_file1&gt;] [&lt;load_file2&gt;] ..." << endl
396       << "<table>"
397       << "<tr>"
398       << "<td>" << DASH_DEBUG.toLatin1().data() << "</td>"
399       << "<td>"
400       << QObject::tr ("Enables extra debug information. Used for debugging").toLatin1().data()
401       << "</td>"
402       << "</tr>"
403       << "<tr>"
404       << "<td>" << DASH_DROP_REGRESSION.toLatin1().data() << "</td>"
405       << "<td>"
406       << QObject::tr ("Indicates files opened at startup are for testing drag and drop. Used for regression testing").toLatin1().data()
407       << "</td>"
408       << "</tr>"
409       << "<tr>"
410       << "<td>" << DASH_ERROR_REPORT.toLatin1().data() << "</td>"
411       << "<td>"
412       << QObject::tr ("Specifies an error report file as input. Used for debugging and regression testing").toLatin1().data()
413       << "</td>"
414       << "</tr>"
415       << "<tr>"
416       << "<td>" << DASH_EXPORT_ONLY.toLatin1().data() << "</td>"
417       << "<td>"
418       << QObject::tr ("Export each loaded startup file, which must have all axis points defined, then stop").toLatin1().data()
419       << "</td>"
420       << "</tr>"
421       << "<tr>"
422       << "<td>" << DASH_EXTRACT_IMAGE_ONLY.toLatin1().data() << "</td>"
423       << "<td>"
424       << QObject::tr ("Extract image in each loaded startup file to a file with the specified extension, then stop").toLatin1().data()
425       << "</td>"
426       << "</tr>"
427       << "<tr>"
428       << "<td>" << DASH_FILE_CMD_SCRIPT.toLatin1().data() << "</td>"
429       << "<td>"
430       << QObject::tr ("Specifies a file command script file as input. Used for debugging and testing").toLatin1().data()
431       << "</td>"
432       << "</tr>"
433       << "<tr>"
434       << "<td>" << DASH_GNUPLOT.toLatin1().data() << "</td>"
435       << "<td>"
436       << QObject::tr ("Output diagnostic gnuplot input files. Used for debugging").toLatin1().data()
437       << "</td>"
438       << "</tr>"
439       << "<tr>"
440       << "<td>" << DASH_HELP.toLatin1().data() << "</td>"
441       << "<td>"
442       << QObject::tr ("Show this help information").toLatin1().data()
443       << "</td>"
444       << "</tr>"
445       << "<tr>"
446       << "<td>" << DASH_REGRESSION.toLatin1().data() << "</td>"
447       << "<td>"
448       << QObject::tr ("Executes the error report file or file command script. Used for regression testing").toLatin1().data()
449       << "</td>"
450       << "</tr>"
451       << "<tr>"
452       << "<td>" << DASH_RESET.toLatin1().data() << "</td>"
453       << "<td>"
454       << QObject::tr ("Removes all stored settings, including window positions. Used when windows start up offscreen").toLatin1().data()
455       << "</td>"
456       << "</tr>"
457       << "<tr>"
458       << "<td>" << DASH_STYLE.toLatin1().data() << "</td>"
459       << "<td>"
460       << QString ("%1 %2")
461          .arg (QObject::tr ("Set the window style to one of the styles listed by the command line option"))
462          .arg (DASH_STYLES).toLatin1().data()
463       << "</td>"
464       << "</tr>"
465       << "<tr>"
466       << "<td>" << DASH_STYLES.toLatin1().data() << "</td>"
467       << "<td>"
468       << QString ("%1 %2")
469          .arg (QObject::tr ("Show a list of available styles that can be used with the command line option"))
470          .arg (DASH_STYLE).toLatin1().data()
471       << "</td>"
472       << "</tr>"
473       << "<tr>"
474       << "<td>" << DASH_UPGRADE.toLatin1().data() << "</td>"
475       << "<td>"
476       << QObject::tr ("Upgrade files opened at startup to the most recent version").toLatin1().data()
477       << "</td>"
478       << "</tr>"
479       << "<tr>"
480       << "<td>" << QString ("&lt;load file&gt; ").toLatin1().data() << "</td>"
481       << "<td>"
482       << QObject::tr ("File(s) to be imported or opened at startup").toLatin1().data()
483       << "</td>"
484       << "</tr>"
485       << "</table></html>";
486 
487   showMessageAndQuit (msg);
488 }
489 
upgradeFiles(const QStringList & loadStartupFiles)490 void upgradeFiles (const QStringList &loadStartupFiles)
491 {
492   QString FILE_SUFFIX (".dig");
493   QString UPGRADE_TOKEN ("_upgrade");
494 
495   QString msg;
496 
497   QStringList::const_iterator itr;
498   for (itr = loadStartupFiles.begin(); itr != loadStartupFiles.end(); itr++) {
499 
500     QString filenameOld = *itr;
501     QString filenameNew;
502 
503     // First try to insert upgrade token before file prefix if it is recognized
504     if (filenameOld.endsWith (FILE_SUFFIX,
505                               Qt::CaseInsensitive)) {
506       QString withoutSuffix = filenameOld.left (filenameOld.size () - FILE_SUFFIX.size ());
507       filenameNew = QString ("%1%2%3")
508           .arg (withoutSuffix)
509           .arg (UPGRADE_TOKEN)
510           .arg (FILE_SUFFIX);
511     } else {
512 
513       // Otherwise append upgrade token
514       filenameNew = QString ("%1%2")
515           .arg (filenameOld)
516           .arg (UPGRADE_TOKEN);
517     }
518 
519     // Get old file
520     Document document (filenameOld);
521 
522     // Make new file
523     QFile file (filenameNew);
524     if (!file.open (QFile::WriteOnly)) {
525 
526       msg += QString ("%1 %2")
527         .arg (QObject::tr ("Could not write to"))
528         .arg (filenameNew);
529 
530     } else {
531 
532       QXmlStreamWriter writer (&file);
533       writer.setAutoFormatting (true);
534       writer.writeStartDocument();
535       writer.writeDTD ("<!DOCTYPE engauge>");
536       document.saveXml (writer);
537       writer.writeEndDocument ();
538 
539       msg += QString ("%1 %2 %3 %4")
540         .arg (QObject::tr ("Upgraded"))
541         .arg (filenameOld)
542         .arg (QObject::tr ("to"))
543         .arg (filenameNew);
544     }
545   }
546 
547   // Do not show a message using QMessageBox since upgrade mode may be called hundreds
548   // of times successively by python scripts. Logging is used instead
549   LOG4CPP_INFO_S ((*mainCat)) << "Upgrade results: " << msg.toLatin1().data ();
550 
551   exit (0);
552 }
553