1 /************************************************************************
2  **
3  **  @file   mapplication.cpp
4  **  @author Roman Telezhynskyi <dismine(at)gmail.com>
5  **  @date   8 7, 2015
6  **
7  **  @brief
8  **  @copyright
9  **  This source code is part of the Valentina project, a pattern making
10  **  program, whose allow create and modeling patterns of clothing.
11  **  Copyright (C) 2015 Valentina project
12  **  <https://gitlab.com/smart-pattern/valentina> All Rights Reserved.
13  **
14  **  Valentina is free software: you can redistribute it and/or modify
15  **  it under the terms of the GNU General Public License as published by
16  **  the Free Software Foundation, either version 3 of the License, or
17  **  (at your option) any later version.
18  **
19  **  Valentina is distributed in the hope that it will be useful,
20  **  but WITHOUT ANY WARRANTY; without even the implied warranty of
21  **  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22  **  GNU General Public License for more details.
23  **
24  **  You should have received a copy of the GNU General Public License
25  **  along with Valentina.  If not, see <http://www.gnu.org/licenses/>.
26  **
27  *************************************************************************/
28 
29 #include "mapplication.h"
30 #include "version.h"
31 #include "tmainwindow.h"
32 #include "../ifc/exception/vexceptionobjecterror.h"
33 #include "../ifc/exception/vexceptionbadid.h"
34 #include "../ifc/exception/vexceptionconversionerror.h"
35 #include "../ifc/exception/vexceptionemptyparameter.h"
36 #include "../ifc/exception/vexceptionwrongid.h"
37 #include "../vmisc/vsysexits.h"
38 #include "../vmisc/diagnostic.h"
39 #include "../vmisc/qt_dispatch/qt_dispatch.h"
40 #include "../qmuparser/qmuparsererror.h"
41 #include "../vpatterndb/variables/vmeasurement.h"
42 
43 #include <QDir>
44 #include <QFileOpenEvent>
45 #include <QLocalSocket>
46 #include <QResource>
47 #include <QTranslator>
48 #include <QPointer>
49 #include <QLocalServer>
50 #include <QMessageBox>
51 #include <iostream>
52 #include <QGridLayout>
53 #include <QSpacerItem>
54 #include <QThread>
55 
56 #if defined(APPIMAGE) && defined(Q_OS_LINUX)
57 #   include "../vmisc/appimage.h"
58 #endif // defined(APPIMAGE) && defined(Q_OS_LINUX)
59 
60 QT_WARNING_PUSH
61 QT_WARNING_DISABLE_CLANG("-Wmissing-prototypes")
62 QT_WARNING_DISABLE_INTEL(1418)
63 
64 Q_LOGGING_CATEGORY(mApp, "m.application")
65 
66 QT_WARNING_POP
67 
68 #include <QCommandLineParser>
69 
70 //---------------------------------------------------------------------------------------------------------------------
noisyFailureMsgHandler(QtMsgType type,const QMessageLogContext & context,const QString & msg)71 inline void noisyFailureMsgHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg)
72 {
73     // only the GUI thread should display message boxes.  If you are
74     // writing a multithreaded application and the error happens on
75     // a non-GUI thread, you'll have to queue the message to the GUI
76     QCoreApplication *instance = QCoreApplication::instance();
77     const bool isGuiThread = instance && (QThread::currentThread() == instance->thread());
78 
79     if (not isGuiThread)
80     {
81         auto Handler = [](QtMsgType type, const QMessageLogContext &context, const QString &msg)
82         {
83             noisyFailureMsgHandler(type, context, msg);
84         };
85 
86         q_dispatch_async_main(Handler, type, context, msg);
87         return;
88     }
89 
90     // Why on earth didn't Qt want to make failed signal/slot connections qWarning?
91     if ((type == QtDebugMsg) && msg.contains(QStringLiteral("::connect")))
92     {
93         type = QtWarningMsg;
94     }
95 
96 #if defined(V_NO_ASSERT)
97     // I have decided to hide this annoing message for release builds.
98     if ((type == QtWarningMsg) && msg.contains(QStringLiteral("QSslSocket: cannot resolve")))
99     {
100         type = QtDebugMsg;
101     }
102 
103     if ((type == QtWarningMsg) && msg.contains(QStringLiteral("setGeometry: Unable to set geometry")))
104     {
105         type = QtDebugMsg;
106     }
107 #endif //defined(V_NO_ASSERT)
108 
109 #if defined(Q_OS_MAC)
110 #   if QT_VERSION >= QT_VERSION_CHECK(5, 4, 0) && QT_VERSION < QT_VERSION_CHECK(5, 7, 0)
111         // Try hide very annoying, Qt related, warnings in Mac OS X
112         // QNSView mouseDragged: Internal mouse button tracking invalid (missing Qt::LeftButton)
113         // https://bugreports.qt.io/browse/QTBUG-42846
114         if ((type == QtWarningMsg) && msg.contains(QStringLiteral("QNSView")))
115         {
116             type = QtDebugMsg;
117         }
118 #   endif
119 
120 #   if QT_VERSION < QT_VERSION_CHECK(5, 9, 0)
121     // Hide Qt bug 'Assertion when reading an icns file'
122     // https://bugreports.qt.io/browse/QTBUG-45537
123     // Remove after Qt fix will be released
124     if ((type == QtWarningMsg) && msg.contains(QStringLiteral("QICNSHandler::read()")))
125     {
126         type = QtDebugMsg;
127     }
128 #   endif
129 
130     // Hide anything that starts with QMacCGContext
131     if ((type == QtWarningMsg) && msg.contains(QStringLiteral("QMacCGContext::")))
132     {
133         type = QtDebugMsg;
134     }
135 
136     // See issue #568
137     if (msg.contains(QStringLiteral("Error receiving trust for a CA certificate")))
138     {
139         type = QtDebugMsg;
140     }
141 #endif
142 
143     // this is another one that doesn't make sense as just a debug message.  pretty serious
144     // sign of a problem
145     // http://www.developer.nokia.com/Community/Wiki/QPainter::begin:Paint_device_returned_engine_%3D%3D_0_(Known_Issue)
146     if ((type == QtDebugMsg) && msg.contains(QStringLiteral("QPainter::begin"))
147         && msg.contains(QStringLiteral("Paint device returned engine")))
148     {
149         type = QtWarningMsg;
150     }
151 
152     // This qWarning about "Cowardly refusing to send clipboard message to hung application..."
153     // is something that can easily happen if you are debugging and the application is paused.
154     // As it is so common, not worth popping up a dialog.
155     if ((type == QtWarningMsg) && msg.contains(QStringLiteral("QClipboard::event"))
156             && msg.contains(QStringLiteral("Cowardly refusing")))
157     {
158         type = QtDebugMsg;
159     }
160 
161     QString logMsg = msg;
162     const bool isWarningMessage = VAbstractApplication::VApp()->IsWarningMessage(msg);
163     if (isWarningMessage)
164     {
165         logMsg = logMsg.remove(VAbstractApplication::warningMessageSignature);
166     }
167 
168     switch (type)
169     {
170         case QtDebugMsg:
171             vStdOut() << QApplication::translate("mNoisyHandler", "DEBUG:") << logMsg << "\n";
172             return;
173         case QtWarningMsg:
174             vStdErr() << QApplication::translate("mNoisyHandler", "WARNING:") << logMsg << "\n";
175             break;
176         case QtCriticalMsg:
177             vStdErr() << QApplication::translate("mNoisyHandler", "CRITICAL:") << logMsg << "\n";
178             break;
179         case QtFatalMsg:
180             vStdErr() << QApplication::translate("mNoisyHandler", "FATAL:") << logMsg << "\n";
181             break;
182         #if QT_VERSION >= QT_VERSION_CHECK(5, 5, 0)
183         case QtInfoMsg:
184             vStdOut() << QApplication::translate("mNoisyHandler", "INFO:") << logMsg << "\n";
185             break;
186         #endif
187         default:
188             break;
189     }
190 
191     vStdOut().flush();
192     vStdErr().flush();
193 
194     if (isGuiThread)
195     {
196         //fixme: trying to make sure there are no save/load dialogs are opened, because error message during them will
197         //lead to crash
198         const bool topWinAllowsPop = (QApplication::activeModalWidget() == nullptr) ||
199                 !QApplication::activeModalWidget()->inherits("QFileDialog");
200         QMessageBox messageBox;
201         switch (type)
202         {
203             case QtWarningMsg:
204                 messageBox.setWindowTitle(QApplication::translate("mNoisyHandler", "Warning"));
205                 messageBox.setIcon(QMessageBox::Warning);
206                 break;
207             case QtCriticalMsg:
208                 messageBox.setWindowTitle(QApplication::translate("mNoisyHandler", "Critical error"));
209                 messageBox.setIcon(QMessageBox::Critical);
210                 break;
211             case QtFatalMsg:
212                 messageBox.setWindowTitle(QApplication::translate("mNoisyHandler", "Fatal error"));
213                 messageBox.setIcon(QMessageBox::Critical);
214                 break;
215             #if QT_VERSION >= QT_VERSION_CHECK(5, 5, 0)
216             case QtInfoMsg:
217                 messageBox.setWindowTitle(QApplication::translate("mNoisyHandler", "Information"));
218                 messageBox.setIcon(QMessageBox::Information);
219                 break;
220             #endif
221             case QtDebugMsg:
222                 Q_UNREACHABLE(); //-V501
223                 break;
224             default:
225                 break;
226         }
227 
228         if (type == QtWarningMsg || type == QtCriticalMsg || type == QtFatalMsg)
229         {
230             if (not MApplication::VApp()->IsTestMode())
231             {
232                 if (topWinAllowsPop)
233                 {
234                     messageBox.setText(VAbstractApplication::ClearMessage(logMsg));
235                     messageBox.setStandardButtons(QMessageBox::Ok);
236                     messageBox.setWindowModality(Qt::ApplicationModal);
237                     messageBox.setModal(true);
238                 #ifndef QT_NO_CURSOR
239                     QGuiApplication::setOverrideCursor(Qt::ArrowCursor);
240                 #endif
241                     messageBox.exec();
242                 #ifndef QT_NO_CURSOR
243                     QGuiApplication::restoreOverrideCursor();
244                 #endif
245                 }
246             }
247         }
248 
249         if (QtFatalMsg == type)
250         {
251             abort();
252         }
253     }
254     else
255     {
256         if (type != QtDebugMsg)
257         {
258             abort(); // be NOISY unless overridden!
259         }
260     }
261 }
262 
263 //---------------------------------------------------------------------------------------------------------------------
MApplication(int & argc,char ** argv)264 MApplication::MApplication(int &argc, char **argv)
265     :VAbstractApplication(argc, argv),
266       mainWindows(),
267       localServer(nullptr),
268       trVars(nullptr),
269       dataBase(QPointer<DialogMDataBase>()),
270       testMode(false)
271 {
272     setApplicationDisplayName(VER_PRODUCTNAME_STR);
273     setApplicationName(VER_INTERNALNAME_STR);
274     setOrganizationName(VER_COMPANYNAME_STR);
275     setOrganizationDomain(VER_COMPANYDOMAIN_STR);
276     // Setting the Application version
277     setApplicationVersion(APP_VERSION_STR);
278     // We have been running Tape in two different cases.
279     // The first inside own bundle where info.plist is works fine, but the second,
280     // when we run inside Valentina's bundle, require direct setting the icon.
281     setWindowIcon(QIcon(":/tapeicon/64x64/logo.png"));
282 }
283 
284 //---------------------------------------------------------------------------------------------------------------------
~MApplication()285 MApplication::~MApplication()
286 {
287     qDeleteAll(mainWindows);
288 
289     delete trVars;
290     if (not dataBase.isNull())
291     {
292         delete dataBase;
293     }
294 }
295 
296 //---------------------------------------------------------------------------------------------------------------------
297 /**
298  * @brief notify Reimplemented from QApplication::notify().
299  * @param receiver receiver.
300  * @param event event.
301  * @return value that is returned from the receiver's event handler.
302  */
303 // reimplemented from QApplication so we can throw exceptions in slots
notify(QObject * receiver,QEvent * event)304 bool MApplication::notify(QObject *receiver, QEvent *event)
305 {
306     try
307     {
308         return QApplication::notify(receiver, event);
309     }
310     catch (const VExceptionObjectError &e)
311     {
312         qCCritical(mApp, "%s\n\n%s\n\n%s", qUtf8Printable(tr("Error parsing file. Program will be terminated.")), //-V807
313                    qUtf8Printable(e.ErrorMessage()), qUtf8Printable(e.DetailedInformation()));
314         exit(V_EX_DATAERR);
315     }
316     catch (const VExceptionBadId &e)
317     {
318         qCCritical(mApp, "%s\n\n%s\n\n%s", qUtf8Printable(tr("Error bad id. Program will be terminated.")),
319                    qUtf8Printable(e.ErrorMessage()), qUtf8Printable(e.DetailedInformation()));
320         exit(V_EX_DATAERR);
321     }
322     catch (const VExceptionConversionError &e)
323     {
324         qCCritical(mApp, "%s\n\n%s\n\n%s", qUtf8Printable(tr("Error can't convert value. Program will be terminated.")),
325                    qUtf8Printable(e.ErrorMessage()), qUtf8Printable(e.DetailedInformation()));
326         exit(V_EX_DATAERR);
327     }
328     catch (const VExceptionEmptyParameter &e)
329     {
330         qCCritical(mApp, "%s\n\n%s\n\n%s", qUtf8Printable(tr("Error empty parameter. Program will be terminated.")),
331                    qUtf8Printable(e.ErrorMessage()), qUtf8Printable(e.DetailedInformation()));
332         exit(V_EX_DATAERR);
333     }
334     catch (const VExceptionWrongId &e)
335     {
336         qCCritical(mApp, "%s\n\n%s\n\n%s", qUtf8Printable(tr("Error wrong id. Program will be terminated.")),
337                    qUtf8Printable(e.ErrorMessage()), qUtf8Printable(e.DetailedInformation()));
338         exit(V_EX_DATAERR);
339     }
340     catch (const VExceptionToolWasDeleted &e)
341     {
342         qCCritical(mApp, "%s\n\n%s\n\n%s",
343                    qUtf8Printable("Unhadled deleting tool. Continue use object after deleting!"),
344                    qUtf8Printable(e.ErrorMessage()), qUtf8Printable(e.DetailedInformation()));
345         exit(V_EX_DATAERR);
346     }
347     catch (const VException &e)
348     {
349         qCCritical(mApp, "%s\n\n%s\n\n%s", qUtf8Printable(tr("Something's wrong!!")),
350                    qUtf8Printable(e.ErrorMessage()), qUtf8Printable(e.DetailedInformation()));
351         return true;
352     }
353     catch (const qmu::QmuParserWarning &e)
354     {
355         qCCritical(mApp, "%s", qUtf8Printable(tr("Formula warning: %1. Program will be terminated.").arg(e.GetMsg())));
356         exit(V_EX_DATAERR);
357     }
358     // These last two cases special. I found that we can't show here modal dialog with error message.
359     // Somehow program doesn't waite untile an error dialog will be closed. But if ignore this program will hang.
360     catch (const qmu::QmuParserError &e)
361     {
362         qCCritical(mApp, "%s", qUtf8Printable(tr("Parser error: %1. Program will be terminated.").arg(e.GetMsg())));
363         exit(V_EX_DATAERR);
364     }
365     catch (std::exception &e)
366     {
367         qCCritical(mApp, "%s", qUtf8Printable(tr("Exception thrown: %1. Program will be terminated.").arg(e.what())));
368         exit(V_EX_SOFTWARE);
369     }
370     return false;
371 }
372 
373 //---------------------------------------------------------------------------------------------------------------------
IsTestMode() const374 bool MApplication::IsTestMode() const
375 {
376     return testMode;
377 }
378 
379 //---------------------------------------------------------------------------------------------------------------------
380 /**
381  * @brief IsAppInGUIMode little hack that allow to have access to application state from VAbstractApplication class.
382  */
IsAppInGUIMode() const383 bool MApplication::IsAppInGUIMode() const
384 {
385     return IsTestMode();
386 }
387 
388 //---------------------------------------------------------------------------------------------------------------------
MainWindow()389 TMainWindow *MApplication::MainWindow()
390 {
391     Clean();
392     if (mainWindows.isEmpty())
393     {
394         NewMainWindow();
395     }
396     return mainWindows[0];
397 }
398 
399 //---------------------------------------------------------------------------------------------------------------------
MainWindows()400 QList<TMainWindow *> MApplication::MainWindows()
401 {
402     Clean();
403     QList<TMainWindow*> list;
404     for (auto &w : mainWindows)
405     {
406         list.append(w);
407     }
408     return list;
409 }
410 
411 //---------------------------------------------------------------------------------------------------------------------
InitOptions()412 void MApplication::InitOptions()
413 {
414     qInstallMessageHandler(noisyFailureMsgHandler);
415 
416     OpenSettings();
417 
418     qCDebug(mApp, "Version: %s", qUtf8Printable(APP_VERSION_STR));
419     qCDebug(mApp, "Build revision: %s", BUILD_REVISION);
420     qCDebug(mApp, "%s", qUtf8Printable(buildCompatibilityString()));
421     qCDebug(mApp, "Built on %s at %s", __DATE__, __TIME__);
422     qCDebug(mApp, "Command-line arguments: %s", qUtf8Printable(arguments().join(", ")));
423     qCDebug(mApp, "Process ID: %s", qUtf8Printable(QString().setNum(applicationPid())));
424 
425     LoadTranslation(QLocale().name());// By default the console version uses system locale
426 
427     static const char * GENERIC_ICON_TO_CHECK = "document-open";
428     if (QIcon::hasThemeIcon(GENERIC_ICON_TO_CHECK) == false)
429     {
430        //If there is no default working icon theme then we should
431        //use an icon theme that we provide via a .qrc file
432        //This case happens under Windows and Mac OS X
433        //This does not happen under GNOME or KDE
434        QIcon::setThemeName("win.icon.theme");
435     }
436     ActivateDarkMode();
437     QResource::registerResource(diagramsPath());
438 }
439 // Dark mode
440 
ActivateDarkMode()441 void MApplication::ActivateDarkMode()
442 {
443     VTapeSettings *settings = MApplication::VApp()->TapeSettings();
444      if (settings->GetDarkMode())
445      {
446          QFile f(":qdarkstyle/style.qss");
447          if (!f.exists())
448          {
449              qDebug()<<"Unable to set stylesheet, file not found\n";
450          }
451          else
452          {
453              f.open(QFile::ReadOnly | QFile::Text);
454              QTextStream ts(&f);
455              qApp->setStyleSheet(ts.readAll());
456          }
457 
458      }
459 }
460 
461 //---------------------------------------------------------------------------------------------------------------------
InitTrVars()462 void MApplication::InitTrVars()
463 {
464     if (trVars != nullptr)
465     {
466         trVars->Retranslate();
467     }
468     else
469     {
470         trVars = new VTranslateVars();
471     }
472 }
473 
474 //---------------------------------------------------------------------------------------------------------------------
event(QEvent * e)475 bool MApplication::event(QEvent *e)
476 {
477     switch(e->type())
478     {
479         // In Mac OS X the QFileOpenEvent event is generated when user perform "Open With" from Finder (this event is
480         // Mac specific).
481         case QEvent::FileOpen:
482         {
483             QFileOpenEvent *fileOpenEvent = static_cast<QFileOpenEvent *>(e);
484             const QString macFileOpen = fileOpenEvent->file();
485             if(not macFileOpen.isEmpty())
486             {
487                 TMainWindow *mw = MainWindow();
488                 if (mw)
489                 {
490                     mw->LoadFile(macFileOpen);  // open file in existing window
491                 }
492                 return true;
493             }
494             break;
495         }
496 #if defined(Q_OS_MAC)
497         case QEvent::ApplicationActivate:
498         {
499             Clean();
500             TMainWindow *mw = MainWindow();
501             if (mw && not mw->isMinimized())
502             {
503                 mw->show();
504             }
505             return true;
506         }
507 #endif //defined(Q_OS_MAC)
508         default:
509             return VAbstractApplication::event(e);
510     }
511     return VAbstractApplication::event(e);
512 }
513 
514 //---------------------------------------------------------------------------------------------------------------------
AboutToQuit()515 void MApplication::AboutToQuit()
516 {
517     // If try to use the method QApplication::exit program can't sync settings and show warning about QApplication
518     // instance. Solution is to call sync() before quit.
519     // Connect this slot with VApplication::aboutToQuit.
520     Settings()->sync();
521 }
522 
523 //---------------------------------------------------------------------------------------------------------------------
OpenSettings()524 void MApplication::OpenSettings()
525 {
526     settings = new VTapeSettings(QSettings::IniFormat, QSettings::UserScope, QCoreApplication::organizationName(),
527                                  QCoreApplication::applicationName(), this);
528 }
529 
530 //---------------------------------------------------------------------------------------------------------------------
TapeSettings()531 VTapeSettings *MApplication::TapeSettings()
532 {
533     SCASSERT(settings != nullptr)
534     return qobject_cast<VTapeSettings *>(settings);
535 }
536 
537 //---------------------------------------------------------------------------------------------------------------------
diagramsPath() const538 QString MApplication::diagramsPath() const
539 {
540     const QString dPath = QStringLiteral("/diagrams.rcc");
541 #ifdef Q_OS_WIN
542     return QCoreApplication::applicationDirPath() + dPath;
543 #elif defined(Q_OS_MAC)
544     QFileInfo fileBundle(QCoreApplication::applicationDirPath() + QStringLiteral("/../Resources") + dPath);
545     if (fileBundle.exists())
546     {
547         return fileBundle.absoluteFilePath();
548     }
549     else
550     {
551         QFileInfo file(QCoreApplication::applicationDirPath() + dPath);
552         if (file.exists())
553         {
554             return file.absoluteFilePath();
555         }
556         else
557         {
558             return PKGDATADIR + dPath;
559         }
560     }
561 #else // Unix
562     QFileInfo file(QCoreApplication::applicationDirPath() + dPath);
563     if (file.exists())
564     {
565         return file.absoluteFilePath();
566     }
567     else
568     {
569 #if defined(APPIMAGE) && defined(Q_OS_LINUX)
570         /* Fix path to diagrams when run inside AppImage. */
571         return AppImageRoot() + PKGDATADIR + dPath;
572 #else
573         return PKGDATADIR + dPath;
574 #endif // defined(APPIMAGE) && defined(Q_OS_LINUX)
575     }
576 #endif
577 }
578 
579 //---------------------------------------------------------------------------------------------------------------------
ShowDataBase()580 void MApplication::ShowDataBase()
581 {
582     if (dataBase.isNull())
583     {
584         dataBase = new DialogMDataBase();
585         dataBase->setAttribute(Qt::WA_DeleteOnClose, true);
586         dataBase->setModal(false);
587         dataBase->show();
588     }
589     else
590     {
591         dataBase->activateWindow();
592     }
593 }
594 
595 //---------------------------------------------------------------------------------------------------------------------
RetranslateGroups()596 void MApplication::RetranslateGroups()
597 {
598     if (not dataBase.isNull())
599     {
600         dataBase->RetranslateGroups();
601     }
602 }
603 
604 //---------------------------------------------------------------------------------------------------------------------
RetranslateTables()605 void MApplication::RetranslateTables()
606 {
607     const QList<TMainWindow*> list = MainWindows();
608     for (auto w : list)
609     {
610         w->RetranslateTable();
611     }
612 }
613 
614 //---------------------------------------------------------------------------------------------------------------------
ParseCommandLine(const SocketConnection & connection,const QStringList & arguments)615 void MApplication::ParseCommandLine(const SocketConnection &connection, const QStringList &arguments)
616 {
617     QCommandLineParser parser;
618     parser.setApplicationDescription(tr("Valentina's measurements editor."));
619     parser.addHelpOption();
620     parser.addVersionOption();
621     parser.addPositionalArgument("filename", tr("The measurement file."));
622 
623     const QString LONG_OPTION_DIMENSION_A = QStringLiteral("dimensionA");
624     const QString SINGLE_OPTION_DIMENSION_A = QChar('a');
625 
626     const QString LONG_OPTION_DIMENSION_B = QStringLiteral("dimensionB");
627     const QString SINGLE_OPTION_DIMENSION_B = QChar('b');
628 
629     const QString LONG_OPTION_DIMENSION_C = QStringLiteral("dimensionC");
630     const QString SINGLE_OPTION_DIMENSION_C = QChar('c');
631 
632     const QString LONG_OPTION_UNITS = QStringLiteral("units");
633     const QString SINGLE_OPTION_UNITS = QChar('u');
634 
635     const QString LONG_OPTION_TEST = QStringLiteral("test");
636 
637     parser.addOptions(
638     {
639         {{SINGLE_OPTION_DIMENSION_A, LONG_OPTION_DIMENSION_A}, tr("Set base for dimension A in the table units."),
640              tr("The dimension A base")},
641 
642         {{SINGLE_OPTION_DIMENSION_B, LONG_OPTION_DIMENSION_B}, tr("Set base for dimension B in the table units."),
643              tr("The dimension B base")},
644 
645         {{SINGLE_OPTION_DIMENSION_C, LONG_OPTION_DIMENSION_C}, tr("Set base for dimension C in the table units."),
646              tr("The dimension C base")},
647 
648         {{SINGLE_OPTION_UNITS, LONG_OPTION_UNITS}, tr("Set pattern file units: cm, mm, inch."),
649              tr("The pattern units")},
650 
651         {LONG_OPTION_TEST,
652              tr("Use for unit testing. Run the program and open a file without showing the main window.")},
653 
654         {LONG_OPTION_NO_HDPI_SCALING,
655              tr("Disable high dpi scaling. Call this option if has problem with scaling (by default scaling enabled). "
656                 "Alternatively you can use the %1 environment variable.").arg("QT_AUTO_SCREEN_SCALE_FACTOR=0")},
657     });
658 
659     parser.process(arguments);
660 
661     testMode = parser.isSet(LONG_OPTION_TEST);
662 
663     if (not testMode && connection == SocketConnection::Client)
664     {
665         const QString serverName = QCoreApplication::applicationName();
666         QLocalSocket socket;
667         socket.connectToServer(serverName);
668         if (socket.waitForConnected(1000))
669         {
670             qCDebug(mApp, "Connected to the server '%s'", qUtf8Printable(serverName));
671             QTextStream stream(&socket);
672             stream << QCoreApplication::arguments().join(";;");
673             stream.flush();
674             socket.waitForBytesWritten();
675             qApp->exit(V_EX_OK);
676             return;
677         }
678 
679         qCDebug(mApp, "Can't establish connection to the server '%s'", qUtf8Printable(serverName));
680 
681         localServer = new QLocalServer(this);
682         connect(localServer, &QLocalServer::newConnection, this, &MApplication::NewLocalSocketConnection);
683         if (not localServer->listen(serverName))
684         {
685             qCDebug(mApp, "Can't begin to listen for incoming connections on name '%s'",
686                     qUtf8Printable(serverName));
687             if (localServer->serverError() == QAbstractSocket::AddressInUseError)
688             {
689                 QLocalServer::removeServer(serverName);
690                 if (not localServer->listen(serverName))
691                 {
692                     qCWarning(mApp, "%s",
693                      qUtf8Printable(tr("Can't begin to listen for incoming connections on name '%1'").arg(serverName)));
694                 }
695             }
696         }
697 
698         LoadTranslation(TapeSettings()->GetLocale());
699     }
700 
701     const QStringList args = parser.positionalArguments();
702     if (args.count() > 0)
703     {
704         if (testMode && args.count() > 1)
705         {
706             qCCritical(mApp, "%s\n", qPrintable(tr("Test mode doesn't support openning several files.")));
707             parser.showHelp(V_EX_USAGE);
708         }
709 
710         bool flagDimensionA = false;
711         bool flagDimensionB = false;
712         bool flagDimensionC = false;
713         bool flagUnits = false;
714 
715         int dimensionAValue = 0;
716         int dimensionBValue = 0;
717         int dimensionCValue = 0;
718         Unit unit = Unit::Cm;
719 
720         if (parser.isSet(LONG_OPTION_DIMENSION_A))
721         {
722             const QString value = parser.value(LONG_OPTION_DIMENSION_A);
723 
724             bool ok = false;
725             dimensionAValue = value.toInt(&ok);
726             if(ok && dimensionAValue > 0)
727             {
728                 flagDimensionA = true;
729             }
730             else
731             {
732                 qCCritical(mApp, "%s\n", qPrintable(tr("Invalid dimension A base value.")));
733                 parser.showHelp(V_EX_USAGE);
734             }
735         }
736 
737         if (parser.isSet(LONG_OPTION_DIMENSION_B))
738         {
739             const QString value = parser.value(LONG_OPTION_DIMENSION_B);
740 
741             bool ok = false;
742             dimensionBValue = value.toInt(&ok);
743             if(ok && dimensionBValue > 0)
744             {
745                 flagDimensionB = true;
746             }
747             else
748             {
749                 qCCritical(mApp, "%s\n", qPrintable(tr("Invalid dimension B base value.")));
750                 parser.showHelp(V_EX_USAGE);
751             }
752         }
753 
754         if (parser.isSet(LONG_OPTION_DIMENSION_C))
755         {
756             const QString value = parser.value(LONG_OPTION_DIMENSION_C);
757 
758             bool ok = false;
759             dimensionCValue = value.toInt(&ok);
760             if(ok && dimensionCValue > 0)
761             {
762                 flagDimensionC = true;
763             }
764             else
765             {
766                 qCCritical(mApp, "%s\n", qPrintable(tr("Invalid dimension C base value.")));
767                 parser.showHelp(V_EX_USAGE);
768             }
769         }
770 
771         {
772             const QString unitValue = parser.value(LONG_OPTION_UNITS);
773             if (not unitValue.isEmpty())
774             {
775                 if (QStringList{unitMM, unitCM, unitINCH}.contains(unitValue))
776                 {
777                     flagUnits = true;
778                     unit = StrToUnits(unitValue);
779                 }
780                 else
781                 {
782                     qCCritical(mApp, "%s\n", qPrintable(tr("Invalid base size argument. Must be cm, mm or inch.")));
783                     parser.showHelp(V_EX_USAGE);
784                 }
785             }
786         }
787 
788         for (auto &arg : args)
789         {
790             NewMainWindow();
791             if (not MainWindow()->LoadFile(arg))
792             {
793                 if (testMode)
794                 {
795                     return; // process only one input file
796                 }
797                 delete MainWindow();
798                 continue;
799             }
800 
801             if (flagDimensionA)
802             {
803                 if (not MainWindow()->SetDimensionABase(dimensionAValue))
804                 {
805                     parser.showHelp(V_EX_USAGE);
806                 }
807             }
808 
809             if (flagDimensionB)
810             {
811                 if (not MainWindow()->SetDimensionBBase(dimensionBValue))
812                 {
813                     parser.showHelp(V_EX_USAGE);
814                 }
815             }
816 
817             if (flagDimensionC)
818             {
819                 if (not MainWindow()->SetDimensionCBase(dimensionCValue))
820                 {
821                     parser.showHelp(V_EX_USAGE);
822                 }
823             }
824 
825             if (flagUnits)
826             {
827                 MainWindow()->SetPUnit(unit);
828             }
829         }
830     }
831     else
832     {
833         if (not testMode)
834         {
835             NewMainWindow();
836         }
837         else
838         {
839             qCCritical(mApp, "%s\n", qPrintable(tr("Please, provide one input file.")));
840             parser.showHelp(V_EX_USAGE);
841         }
842     }
843 
844     if (testMode)
845     {
846         qApp->exit(V_EX_OK); // close program after processing in console mode
847     }
848 }
849 
850 //---------------------------------------------------------------------------------------------------------------------
VApp()851 auto MApplication::VApp() -> MApplication *
852 {
853     return qobject_cast<MApplication*>(QCoreApplication::instance());
854 }
855 
856 //---------------------------------------------------------------------------------------------------------------------
NewMainWindow()857 TMainWindow *MApplication::NewMainWindow()
858 {
859     TMainWindow *tape = new TMainWindow();
860     mainWindows.prepend(tape);
861     if (not MApplication::VApp()->IsTestMode())
862     {
863         tape->show();
864     }
865     return tape;
866 }
867 
868 //---------------------------------------------------------------------------------------------------------------------
ProcessCMD()869 void MApplication::ProcessCMD()
870 {
871     ParseCommandLine(SocketConnection::Client, arguments());
872 }
873 
874 //---------------------------------------------------------------------------------------------------------------------
NewLocalSocketConnection()875 void MApplication::NewLocalSocketConnection()
876 {
877     QLocalSocket *socket = localServer->nextPendingConnection();
878     if (not socket)
879     {
880         return;
881     }
882     socket->waitForReadyRead(1000);
883     QTextStream stream(socket);
884     const QString arg = stream.readAll();
885     if (not arg.isEmpty())
886     {
887         ParseCommandLine(SocketConnection::Server, arg.split(";;"));
888     }
889     delete socket;
890     MainWindow()->raise();
891     MainWindow()->activateWindow();
892 }
893 
894 //---------------------------------------------------------------------------------------------------------------------
Clean()895 void MApplication::Clean()
896 {
897     // cleanup any deleted main windows first
898     for (int i = mainWindows.count() - 1; i >= 0; --i)
899     {
900         if (mainWindows.at(i).isNull())
901         {
902             mainWindows.removeAt(i);
903         }
904     }
905 }
906