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() << " <file>] "
386 << "[" << DASH_EXPORT_ONLY.toLatin1().data() << "] "
387 << "[" << DASH_EXTRACT_IMAGE_ONLY.toLatin1().data() << " <extension>] "
388 << "[" << DASH_FILE_CMD_SCRIPT.toLatin1().data() << " <file> "
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 () << " <style>] "
394 << "[" << DASH_STYLES.toLatin1().data () << "] "
395 << "[<load_file1>] [<load_file2>] ..." << 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 ("<load file> ").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