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