Seamly2D
Code documentation
vapplication.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  * *
3  * Copyright (C) 2017 Seamly, LLC *
4  * *
5  * https://github.com/fashionfreedom/seamly2d *
6  * *
7  ***************************************************************************
8  **
9  ** Seamly2D is free software: you can redistribute it and/or modify
10  ** it under the terms of the GNU General Public License as published by
11  ** the Free Software Foundation, either version 3 of the License, or
12  ** (at your option) any later version.
13  **
14  ** Seamly2D is distributed in the hope that it will be useful,
15  ** but WITHOUT ANY WARRANTY; without even the implied warranty of
16  ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17  ** GNU General Public License for more details.
18  **
19  ** You should have received a copy of the GNU General Public License
20  ** along with Seamly2D. If not, see <http://www.gnu.org/licenses/>.
21  **
22  **************************************************************************
23 
24  ************************************************************************
25  **
26  ** @file vapplication.cpp
27  ** @author Roman Telezhynskyi <dismine(at)gmail.com>
28  ** @date November 15, 2013
29  **
30  ** @brief
31  ** @copyright
32  ** This source code is part of the Valentine project, a pattern making
33  ** program, whose allow create and modeling patterns of clothing.
34  ** Copyright (C) 2013-2015 Seamly2D project
35  ** <https://github.com/fashionfreedom/seamly2d> All Rights Reserved.
36  **
37  ** Seamly2D is free software: you can redistribute it and/or modify
38  ** it under the terms of the GNU General Public License as published by
39  ** the Free Software Foundation, either version 3 of the License, or
40  ** (at your option) any later version.
41  **
42  ** Seamly2D is distributed in the hope that it will be useful,
43  ** but WITHOUT ANY WARRANTY; without even the implied warranty of
44  ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
45  ** GNU General Public License for more details.
46  **
47  ** You should have received a copy of the GNU General Public License
48  ** along with Seamly2D. If not, see <http://www.gnu.org/licenses/>.
49  **
50  *************************************************************************/
51 
52 #include "vapplication.h"
53 #include "../ifc/exception/vexceptionobjecterror.h"
54 #include "../ifc/exception/vexceptionbadid.h"
55 #include "../ifc/exception/vexceptionconversionerror.h"
56 #include "../ifc/exception/vexceptionemptyparameter.h"
57 #include "../ifc/exception/vexceptionwrongid.h"
58 #include "../vwidgets/vmaingraphicsview.h"
59 #include "../version.h"
60 #include "../vmisc/logging.h"
61 #include "../vmisc/vmath.h"
62 #include "../qmuparser/qmuparsererror.h"
63 #include "../mainwindow.h"
64 
65 #include <Qt>
66 #include <QtDebug>
67 #include <QDir>
68 #include <QProcess>
69 #include <QTemporaryFile>
70 #include <QUndoStack>
71 #include <QTemporaryFile>
72 #include <QFile>
73 #include <QStandardPaths>
74 #include <QMessageBox>
75 #include <QThread>
76 #include <QDateTime>
77 #include <QtXmlPatterns>
78 #include <QIcon>
79 
80 QT_WARNING_PUSH
81 QT_WARNING_DISABLE_CLANG("-Wmissing-prototypes")
82 QT_WARNING_DISABLE_INTEL(1418)
83 
84 Q_LOGGING_CATEGORY(vApp, "v.application")
85 
87 
88 Q_DECL_CONSTEXPR auto DAYS_TO_KEEP_LOGS = 3;
89 
90 //---------------------------------------------------------------------------------------------------------------------
91 inline void noisyFailureMsgHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg)
92 {
93  // Why on earth didn't Qt want to make failed signal/slot connections qWarning?
94  if ((type == QtDebugMsg) && msg.contains(QStringLiteral("::connect")))
95  {
96  type = QtWarningMsg;
97  }
98 
99 #if defined(V_NO_ASSERT)
100  // I have decided to hide this annoing message for release builds.
101  if ((type == QtWarningMsg) && msg.contains(QStringLiteral("QSslSocket: cannot resolve")))
102  {
103  type = QtDebugMsg;
104  }
105 
106  if ((type == QtWarningMsg) && msg.contains(QStringLiteral("setGeometry: Unable to set geometry")))
107  {
108  type = QtDebugMsg;
109  }
110 #endif //defined(V_NO_ASSERT)
111 
112 #if defined(Q_OS_MAC)
113  // Hide Qt bug 'Assertion when reading an icns file'
114  // https://bugreports.qt.io/browse/QTBUG-45537
115  // Remove after Qt fix will be released
116  if ((type == QtWarningMsg) && msg.contains(QStringLiteral("QICNSHandler::read()")))
117  {
118  type = QtDebugMsg;
119  }
120 
121  // See issue #568
122  if (msg.contains(QStringLiteral("Error receiving trust for a CA certificate")))
123  {
124  type = QtDebugMsg;
125  }
126 #endif
127 
128  // this is another one that doesn't make sense as just a debug message. pretty serious
129  // sign of a problem
130  // http://www.developer.nokia.com/Community/Wiki/QPainter::begin:Paint_device_returned_engine_%3D%3D_0_(Known_Issue)
131  if ((type == QtDebugMsg) && msg.contains(QStringLiteral("QPainter::begin"))
132  && msg.contains(QStringLiteral("Paint device returned engine")))
133  {
134  type = QtWarningMsg;
135  }
136 
137  // This qWarning about "Cowardly refusing to send clipboard message to hung application..."
138  // is something that can easily happen if you are debugging and the application is paused.
139  // As it is so common, not worth popping up a dialog.
140  if ((type == QtWarningMsg) && msg.contains(QStringLiteral("QClipboard::event"))
141  && msg.contains(QStringLiteral("Cowardly refusing")))
142  {
143  type = QtDebugMsg;
144  }
145 
146  // only the GUI thread should display message boxes. If you are
147  // writing a multithreaded application and the error happens on
148  // a non-GUI thread, you'll have to queue the message to the GUI
149  QCoreApplication *instance = QCoreApplication::instance();
150  const bool isGuiThread = instance && (QThread::currentThread() == instance->thread());
151 
152  {
153  QString debugdate = "[" + QDateTime::currentDateTime().toString(QStringLiteral("yyyy.MM.dd hh:mm:ss"));
154 
155  switch (type)
156  {
157  case QtDebugMsg:
158  debugdate += QString(":DEBUG:%1(%2)] %3: %4: %5").arg(context.file).arg(context.line)
159  .arg(context.function).arg(context.category).arg(msg);
160  vStdOut() << QApplication::translate("vNoisyHandler", "DEBUG:") << msg << "\n";
161  break;
162  case QtWarningMsg:
163  debugdate += QString(":WARNING:%1(%2)] %3: %4: %5").arg(context.file).arg(context.line)
164  .arg(context.function).arg(context.category).arg(msg);
165  vStdErr() << QApplication::translate("vNoisyHandler", "WARNING:") << msg << "\n";
166  break;
167  case QtCriticalMsg:
168  debugdate += QString(":CRITICAL:%1(%2)] %3: %4: %5").arg(context.file).arg(context.line)
169  .arg(context.function).arg(context.category).arg(msg);
170  vStdErr() << QApplication::translate("vNoisyHandler", "CRITICAL:") << msg << "\n";
171  break;
172  case QtFatalMsg:
173  debugdate += QString(":FATAL:%1(%2)] %3: %4: %5").arg(context.file).arg(context.line)
174  .arg(context.function).arg(context.category).arg(msg);
175  vStdErr() << QApplication::translate("vNoisyHandler", "FATAL:") << msg << "\n";
176  break;
177  #if QT_VERSION > QT_VERSION_CHECK(5, 4, 2)
178  case QtInfoMsg:
179  debugdate += QString(":INFO:%1(%2)] %3: %4: %5").arg(context.file).arg(context.line)
180  .arg(context.function).arg(context.category).arg(msg);
181  vStdOut() << QApplication::translate("vNoisyHandler", "INFO:") << msg << "\n";
182  break;
183  #endif
184  default:
185  break;
186  }
187 
188  (*qApp->LogFile()) << debugdate << Qt::endl;
189  }
190 
191  if (isGuiThread)
192  {
193  //fixme: trying to make sure there are no save/load dialogs are opened, because error message during them will
194  //lead to crash
195  const bool topWinAllowsPop = (QApplication::activeModalWidget() == nullptr) ||
196  !QApplication::activeModalWidget()->inherits("QFileDialog");
197 
198  QMessageBox messageBox;
199 
200  switch (type)
201  {
202  case QtWarningMsg:
203  messageBox.setWindowTitle(QApplication::translate("vNoisyHandler", "Warning"));
204  messageBox.setIcon(QMessageBox::Warning);
205  break;
206  case QtCriticalMsg:
207  messageBox.setWindowTitle(QApplication::translate("vNoisyHandler", "Critical Error"));
208  messageBox.setIcon(QMessageBox::Critical);
209  break;
210  case QtFatalMsg:
211  messageBox.setWindowTitle(QApplication::translate("vNoisyHandler", "Fatal Error"));
212  messageBox.setIcon(QMessageBox::Critical);
213  break;
214  #if QT_VERSION > QT_VERSION_CHECK(5, 4, 2)
215  case QtInfoMsg:
216  messageBox.setWindowTitle(QApplication::translate("vNoisyHandler", "Information"));
217  messageBox.setIcon(QMessageBox::Information);
218  break;
219  #endif
220  case QtDebugMsg:
221  default:
222  break;
223  }
224 
225  if (type == QtWarningMsg || type == QtCriticalMsg || type == QtFatalMsg)
226  {
228  {
229  if (topWinAllowsPop)
230  {
231  messageBox.setText(msg);
232  messageBox.setStandardButtons(QMessageBox::Ok);
233  messageBox.setWindowModality(Qt::ApplicationModal);
234  messageBox.setModal(true);
235  #ifndef QT_NO_CURSOR
236  QGuiApplication::setOverrideCursor(Qt::ArrowCursor);
237  #endif
238  messageBox.setWindowFlags(messageBox.windowFlags() & ~Qt::WindowContextHelpButtonHint);
239  messageBox.exec();
240  #ifndef QT_NO_CURSOR
241  QGuiApplication::restoreOverrideCursor();
242  #endif
243  }
244  }
245  }
246 
247  if (QtFatalMsg == type)
248  {
249  abort();
250  }
251  }
252  else
253  {
254  if( QtDebugMsg != type && QtWarningMsg != type )
255  {
256  abort(); // be NOISY unless overridden!
257  }
258  }
259 }
260 
261 //---------------------------------------------------------------------------------------------------------------------
262 
263 #if defined(Q_OS_WIN) && defined(Q_CC_GNU)
264 const QString VApplication::GistFileName = QStringLiteral("gist.json");
265 #endif // defined(Q_OS_WIN) && defined(Q_CC_GNU)
266 
267 #define DefWidth 1.2//mm
268 
269 //---------------------------------------------------------------------------------------------------------------------
270 /**
271  * @brief VApplication constructor.
272  * @param argc number arguments.
273  * @param argv command line.
274  */
275 VApplication::VApplication(int &argc, char **argv)
276  : VAbstractApplication(argc, argv)
277  , trVars(nullptr)
278  , autoSaveTimer(nullptr)
279  , lockLog()
280  , out(nullptr)
281 {
282  //setApplicationDisplayName(VER_PRODUCTNAME_STR);
283  setApplicationName(VER_INTERNALNAME_STR);
284  setOrganizationName(VER_COMPANYNAME_STR);
285  setOrganizationDomain(VER_COMPANYDOMAIN_STR);
286  // Setting the Application version
287  setApplicationVersion(APP_VERSION_STR);
288 
289  OpenSettings();
290 
291  // making sure will create new instance...just in case we will ever do 2 objects of VApplication
293  loadTranslations(QLocale().name());// By default the console version uses system locale
294  VCommandLine::Get(*this);
295  undoStack = new QUndoStack(this);
296 }
297 
298 //---------------------------------------------------------------------------------------------------------------------
300 {
301  qCDebug(vApp, "Application closing.");
302  qInstallMessageHandler(nullptr); // Restore the message handler
303  delete trVars;
305 }
306 
307 //---------------------------------------------------------------------------------------------------------------------
308 /**
309  * @brief NewSeamly2D start Seamly2D in new process, send path to pattern file in argument.
310  * @param fileName path to pattern file.
311  */
312 void VApplication::NewSeamly2D(const QString &fileName)
313 {
314  qCDebug(vApp, "Open new detached process.");
315  if (fileName.isEmpty())
316  {
317  qCDebug(vApp, "New process without arguments. program = %s",
318  qUtf8Printable(QCoreApplication::applicationFilePath()));
319  // Path can contain spaces.
320  if (QProcess::startDetached(QCoreApplication::applicationFilePath(), QStringList()))
321  {
322  qCDebug(vApp, "The process was started successfully.");
323  }
324  else
325  {
326  qCWarning(vApp, "Could not run process. The operation timed out or an error occurred.");
327  }
328  }
329  else
330  {
331  const QString run = QString("\"%1\" \"%2\"").arg(QCoreApplication::applicationFilePath()).arg(fileName);
332  qCDebug(vApp, "New process with arguments. program = %s", qUtf8Printable(run));
333 
334  if (QProcess::startDetached(QCoreApplication::applicationFilePath(), QStringList{fileName}))
335  {
336  qCDebug(vApp, "The process was started successfully.");
337  }
338  else
339  {
340  qCWarning(vApp, "Could not run process. The operation timed out or an error occurred.");
341  }
342  }
343 }
344 
345 //---------------------------------------------------------------------------------------------------------------------
346 /**
347  * @brief notify Reimplemented from QApplication::notify().
348  * @param receiver receiver.
349  * @param event event.
350  * @return value that is returned from the receiver's event handler.
351  */
352 // reimplemented from QApplication so we can throw exceptions in slots
353 bool VApplication::notify(QObject *receiver, QEvent *event)
354 {
355  try
356  {
357  return QApplication::notify(receiver, event);
358  }
359  catch (const VExceptionObjectError &e)
360  {
361  qCCritical(vApp, "%s\n\n%s\n\n%s", qUtf8Printable(tr("Error parsing file. Program will be terminated.")), //-V807
362  qUtf8Printable(e.ErrorMessage()), qUtf8Printable(e.DetailedInformation()));
363  exit(V_EX_DATAERR);
364  }
365  catch (const VExceptionBadId &e)
366  {
367  qCCritical(vApp, "%s\n\n%s\n\n%s", qUtf8Printable(tr("Error bad id. Program will be terminated.")),
368  qUtf8Printable(e.ErrorMessage()), qUtf8Printable(e.DetailedInformation()));
369  exit(V_EX_DATAERR);
370  }
371  catch (const VExceptionConversionError &e)
372  {
373  qCCritical(vApp, "%s\n\n%s\n\n%s", qUtf8Printable(tr("Error can't convert value. Program will be terminated.")),
374  qUtf8Printable(e.ErrorMessage()), qUtf8Printable(e.DetailedInformation()));
375  exit(V_EX_DATAERR);
376  }
377  catch (const VExceptionEmptyParameter &e)
378  {
379  qCCritical(vApp, "%s\n\n%s\n\n%s", qUtf8Printable(tr("Error empty parameter. Program will be terminated.")),
380  qUtf8Printable(e.ErrorMessage()), qUtf8Printable(e.DetailedInformation()));
381  exit(V_EX_DATAERR);
382  }
383  catch (const VExceptionWrongId &e)
384  {
385  qCCritical(vApp, "%s\n\n%s\n\n%s", qUtf8Printable(tr("Error wrong id. Program will be terminated.")),
386  qUtf8Printable(e.ErrorMessage()), qUtf8Printable(e.DetailedInformation()));
387  exit(V_EX_DATAERR);
388  }
389  catch (const VExceptionToolWasDeleted &e)
390  {
391  qCCritical(vApp, "%s\n\n%s\n\n%s",
392  qUtf8Printable("Unhadled deleting tool. Continue use object after deleting"),
393  qUtf8Printable(e.ErrorMessage()), qUtf8Printable(e.DetailedInformation()));
394  exit(V_EX_DATAERR);
395  }
396  catch (const VException &e)
397  {
398  qCCritical(vApp, "%s\n\n%s\n\n%s", qUtf8Printable(tr("Something's wrong!!")),
399  qUtf8Printable(e.ErrorMessage()), qUtf8Printable(e.DetailedInformation()));
400  return true;
401  }
402  // These last two cases are special. I found that we can't show here a modal dialog with an error message.
403  // Somehow program doesn't wait until an error dialog will be closed. But if ignore the exception the program will
404  // hang.
405  catch (const qmu::QmuParserError &e)
406  {
407  qCCritical(vApp, "%s", qUtf8Printable(tr("Parser error: %1. Program will be terminated.").arg(e.GetMsg())));
408  exit(V_EX_DATAERR);
409  }
410  catch (std::exception& e)
411  {
412  qCCritical(vApp, "%s", qUtf8Printable(tr("Exception thrown: %1. Program will be terminated.").arg(e.what())));
413  exit(V_EX_SOFTWARE);
414  }
415  return false;
416 }
417 
418 //---------------------------------------------------------------------------------------------------------------------
420 {
421  const QString seamlyme = QStringLiteral("seamlyme");
422 #ifdef Q_OS_WIN
423  QFileInfo seamlymeFile(QCoreApplication::applicationDirPath() + "/" + seamlyme + ".exe");
424  if (seamlymeFile.exists())
425  {
426  return seamlymeFile.absoluteFilePath();
427  }
428  else
429  {
430  return QCoreApplication::applicationDirPath() + "/../../seamlyme/bin/" + seamlyme + ".exe";
431  }
432 #elif defined(Q_OS_MAC)
433  QFileInfo seamlymeFile(QCoreApplication::applicationDirPath() + "/" + seamlyme);
434  if (seamlymeFile.exists())
435  {
436  return seamlymeFile.absoluteFilePath();
437  }
438  else
439  {
440  QFileInfo file(QCoreApplication::applicationDirPath() + "/../../seamlyme/bin/" + seamlyme);
441  if (file.exists())
442  {
443  return file.absoluteFilePath();
444  }
445  else
446  {
447  return seamlyme;
448  }
449  }
450 #else // Unix
451  QFileInfo file(QCoreApplication::applicationDirPath() + "/../../seamlyme/bin/" + seamlyme);
452  if (file.exists())
453  {
454  return file.absoluteFilePath();
455  }
456  else
457  {
458  QFileInfo seamlymeFile(QCoreApplication::applicationDirPath() + "/" + seamlyme);
459  if (seamlymeFile.exists())
460  {
461  return seamlymeFile.absoluteFilePath();
462  }
463  else
464  {
465  return seamlyme;
466  }
467  }
468 #endif
469 }
470 
471 //---------------------------------------------------------------------------------------------------------------------
473 {
474 #if defined(Q_OS_WIN) || defined(Q_OS_OSX)
475  const QString logDirPath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QString(),
476  QStandardPaths::LocateDirectory) + "Seamly2D";
477 #else
478  const QString logDirPath = QStandardPaths::locate(QStandardPaths::ConfigLocation, QString(),
479  QStandardPaths::LocateDirectory)
480  + QCoreApplication::organizationName();
481 #endif
482  return logDirPath;
483 }
484 
485 //---------------------------------------------------------------------------------------------------------------------
486 QString VApplication::LogPath() const
487 {
488  return QString("%1/seamly2d-pid%2.log").arg(LogDirPath()).arg(applicationPid());
489 }
490 
491 //---------------------------------------------------------------------------------------------------------------------
493 {
494  QDir logDir(LogDirPath());
495  if (logDir.exists() == false)
496  {
497  return logDir.mkpath("."); // Create directory for log if need
498  }
499  return true;
500 }
501 
502 //---------------------------------------------------------------------------------------------------------------------
504 {
505  VlpCreateLock(lockLog, LogPath(), [this](){return new QFile(LogPath());});
506 
507  if (lockLog->IsLocked())
508  {
509  if (lockLog->GetProtected()->open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text))
510  {
511  out.reset(new QTextStream(lockLog->GetProtected().get()));
512  qInstallMessageHandler(noisyFailureMsgHandler);
513  qCDebug(vApp, "Log file %s was locked.", qUtf8Printable(LogPath()));
514  }
515  else
516  {
517  qCDebug(vApp, "Error opening log file \'%s\'. All debug output redirected to console.",
518  qUtf8Printable(LogPath()));
519  }
520  }
521  else
522  {
523  qCDebug(vApp, "Failed to lock %s", qUtf8Printable(LogPath()));
524  }
525 }
526 
527 //---------------------------------------------------------------------------------------------------------------------
529 {
530  QDir logsDir(LogDirPath());
531  logsDir.setNameFilters(QStringList("*.log"));
532  logsDir.setCurrent(LogDirPath());
533 
534  const QStringList allFiles = logsDir.entryList(QDir::NoDotAndDotDot | QDir::Files);
535  if (allFiles.isEmpty() == false)
536  {
537  qCDebug(vApp, "Clearing old logs");
538  for (int i = 0, sz = allFiles.size(); i < sz; ++i)
539  {
540  auto fn = allFiles.at(i);
541  QFileInfo info(fn);
542  if (info.birthTime().daysTo(QDateTime::currentDateTime()) >= DAYS_TO_KEEP_LOGS)
543  {
544  VLockGuard<QFile> tmp(info.absoluteFilePath(), [&fn](){return new QFile(fn);});
545  if (tmp.GetProtected() != nullptr)
546  {
547  if (tmp.GetProtected()->remove())
548  {
549  qCDebug(vApp, "Deleted %s", qUtf8Printable(info.absoluteFilePath()));
550  }
551  else
552  {
553  qCDebug(vApp, "Could not delete %s", qUtf8Printable(info.absoluteFilePath()));
554  }
555  }
556  else
557  {
558  qCDebug(vApp, "Failed to lock %s", qUtf8Printable(info.absoluteFilePath()));
559  }
560  }
561  }
562  }
563  else
564  {
565  qCDebug(vApp, "There are no old logs.");
566  }
567 }
568 
569 //---------------------------------------------------------------------------------------------------------------------
571 {
572 #if defined(Q_OS_WIN) && defined(Q_CC_GNU)
573  // Catch and send report
574  VApplication::DrMingw();
575  this->CollectReports();
576 #endif
577 
578  // Run creation log after sending crash report
579  StartLogging();
580 
581  qDebug()<<"Version:"<<APP_VERSION_STR;
582  qDebug()<<"Build revision:"<<BUILD_REVISION;
583  qDebug()<<buildCompatibilityString();
584  qDebug()<<"Built on"<<__DATE__<<"at"<<__TIME__;
585  qDebug()<<"Command-line arguments:"<<arguments();
586  qDebug()<<"Process ID:"<<applicationPid();
587 
588  if (VApplication::IsGUIMode())// By default console version uses system locale
589  {
590  loadTranslations(Seamly2DSettings()->GetLocale());
591  }
592 
593  static const char * GENERIC_ICON_TO_CHECK = "document-open";
594  if (QIcon::hasThemeIcon(GENERIC_ICON_TO_CHECK) == false)
595  {
596  //If there is no default working icon theme then we should
597  //use an icon theme that we provide via a .qrc file
598  //This case happens under Windows and Mac OS X
599  //This does not happen under GNOME or KDE
600  QIcon::setThemeName("win.icon.theme");
601  }
602 
603  OpenSettings();
605  QDir().mkpath(settings->GetDefPathLayout());
606  QDir().mkpath(settings->GetDefPathPattern());
607  QDir().mkpath(settings->GetDefPathIndividualMeasurements());
608  QDir().mkpath(settings->GetDefPathMultisizeMeasurements());
609  QDir().mkpath(settings->GetDefPathTemplate());
610  QDir().mkpath(settings->GetDefPathLabelTemplate());
611 }
612 
613 //---------------------------------------------------------------------------------------------------------------------
615 {
616  QStringList list = QStringList() << "de" // German
617  << "en" // English
618  << "fr" // French
619  << "ru" // Russian
620  << "uk" // Ukrainian
621  << "hr" // Croatian
622  << "sr" // Serbian
623  << "bs"; // Bosnian
624  return list;
625 }
626 
627 //---------------------------------------------------------------------------------------------------------------------
629 {
630  if (CreateLogDir())
631  {
632  BeginLogging();
633  ClearOldLogs();
634 #if defined(Q_OS_WIN) && defined(Q_CC_GNU)
635  ClearOldReports();
636 #endif // defined(Q_OS_WIN) && defined(Q_CC_GNU)
637  }
638 }
639 
640 //---------------------------------------------------------------------------------------------------------------------
641 QTextStream *VApplication::LogFile()
642 {
643  return out.get();
644 }
645 
646 //---------------------------------------------------------------------------------------------------------------------
648 {
649  return trVars;
650 }
651 
652 //---------------------------------------------------------------------------------------------------------------------
654 {
655  if (trVars == nullptr)
656  {
657  trVars = new VTranslateVars();
658  }
659 }
660 
661 //---------------------------------------------------------------------------------------------------------------------
662 bool VApplication::event(QEvent *e)
663 {
664  switch(e->type())
665  {
666  // In Mac OS X the QFileOpenEvent event is generated when user perform "Open With" from Finder (this event is
667  // Mac specific).
668  case QEvent::FileOpen:
669  {
670  QFileOpenEvent *fileOpenEvent = static_cast<QFileOpenEvent *>(e);
671  const QString macFileOpen = fileOpenEvent->file();
672  if(not macFileOpen.isEmpty())
673  {
674  MainWindow *window = qobject_cast<MainWindow*>(mainWindow);
675  if (window)
676  {
677  window->LoadPattern(macFileOpen); // open file in existing window
678  }
679  return true;
680  }
681  break;
682  }
683 #if defined(Q_OS_MAC)
684  case QEvent::ApplicationActivate:
685  {
686  if (mainWindow && not mainWindow->isMinimized())
687  {
688  mainWindow->show();
689  }
690  return true;
691  }
692 #endif //defined(Q_OS_MAC)
693  default:
694  return VAbstractApplication::event(e);
695  }
696  return VAbstractApplication::event(e);
697 }
698 
699 //---------------------------------------------------------------------------------------------------------------------
700 /**
701  * @brief OpenSettings get acsses to application settings.
702  *
703  * Because we can create object in constructor we open file separately.
704  */
706 {
707  settings = new VSettings(QSettings::IniFormat, QSettings::UserScope, QCoreApplication::organizationName(),
708  QCoreApplication::applicationName(), this);
709 }
710 
711 //---------------------------------------------------------------------------------------------------------------------
713 {
714  SCASSERT(settings != nullptr)
715  return qobject_cast<VSettings *>(settings);
716 }
717 
718 //---------------------------------------------------------------------------------------------------------------------
720 {
721  return (VCommandLine::instance != nullptr) && VCommandLine::instance->IsGuiEnabled();
722 }
723 
724 //---------------------------------------------------------------------------------------------------------------------
725 /**
726  * @brief IsAppInGUIMode little hack that allow to have access to application state from VAbstractApplication class.
727  */
729 {
730  return IsGUIMode();
731 }
732 
733 //---------------------------------------------------------------------------------------------------------------------
735 {
736  return VCommandLine::instance;
737 }
738 //---------------------------------------------------------------------------------------------------------------------
739 
740 #if defined(Q_OS_WIN) && defined(Q_CC_GNU)
741 //---------------------------------------------------------------------------------------------------------------------
742 void VApplication::ClearOldReports() const
743 {
744  const QString reportsDir = QString("%1/reports").arg(qApp->applicationDirPath());
745  QDir reports(reportsDir);
746  if (reports.exists())
747  {
748  QStringList filters{"*.log", "*.RPT"};
749  QDir logsDir(reportsDir);
750  logsDir.setNameFilters(filters);
751  logsDir.setCurrent(reportsDir);
752 
753  const QStringList allFiles = logsDir.entryList(QDir::NoDotAndDotDot | QDir::Files);
754  if (allFiles.isEmpty() == false)
755  {
756  const QDateTime now = QDateTime::currentDateTime();
757  for (int i = 0; i < allFiles.size(); ++i)
758  {
759  QFileInfo info(allFiles.at(i));
760  if (info.birthTime().daysTo(now) > 30)
761  {
762  QFile(allFiles.at(i)).remove();
763  }
764  }
765  }
766  }
767 }
768 
769 //---------------------------------------------------------------------------------------------------------------------
770 void VApplication::GatherLogs() const
771 {
772  QTextStream *out = nullptr;
773  QFile *log = new QFile(QString("%1/seamly2d.log").arg(LogDirPath()));
774  if (log->open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text))
775  {
776  out = new QTextStream(log);
777 
778  QStringList filters{"*.log"};
779  QDir logsDir(LogDirPath());
780  logsDir.setNameFilters(filters);
781  logsDir.setCurrent(LogDirPath());
782 
783  const QStringList allFiles = logsDir.entryList(QDir::NoDotAndDotDot | QDir::Files);
784  if (allFiles.isEmpty() == false)
785  {
786  for (int i = 0, sz = allFiles.size(); i < sz; ++i)
787  {
788  auto fn = allFiles.at(i);
789  QFileInfo info(fn);
790  if (info.fileName() == "seamly2d.log")
791  {
792  continue;
793  }
794 
795  VLockGuard<QFile> tmp(info.absoluteFilePath(), [&fn](){return new QFile(fn);});
796 
797  if (tmp.IsLocked())
798  {
799  *out <<"--------------------------" << Qt::endl;
800  if (tmp.GetProtected()->open(QIODevice::ReadOnly | QIODevice::Text))
801  {
802  QTextStream in(tmp.GetProtected().get());
803  while (!in.atEnd())
804  {
805  *out << in.readLine() << Qt::endl;
806  }
807  tmp.GetProtected()->close();
808  }
809  else
810  {
811  *out << "Log file error:" + tmp.GetProtected()->errorString() << Qt::endl;
812  }
813  }
814  else
815  {
816  qCDebug(vApp, "Failed to lock %s", qUtf8Printable(info.absoluteFilePath()));
817  }
818  }
819  }
820  else
821  {
822  *out << "Could not find logs.";
823  }
824  log->close();
825  }
826  delete out;
827  delete log;
828 }
829 
830 //---------------------------------------------------------------------------------------------------------------------
831 // Catch exception and create report. Use if program build with Mingw compiler.
832 // See more about catcher https://github.com/jrfonseca/drmingw/blob/master/README.md
833 void VApplication::DrMingw()
834 {
835  QFile drmingw("exchndl.dll");
836  if (drmingw.exists())
837  {// If don't want create reports just delete exchndl.dll from installer
838  LoadLibrary(L"exchndl.dll");
839  }
840 }
841 
842 //---------------------------------------------------------------------------------------------------------------------
843 void VApplication::CollectReports() const
844 {
845  // Seek file "binary_name.RPT"
846  const QString reportName = QString("%1/%2.RPT").arg(applicationDirPath())
847  .arg(QFileInfo(arguments().at(0)).baseName());
848  QFile reportFile(reportName);
849  if (reportFile.exists())
850  { // Hooray we have found crash
851  if (settings == nullptr)
852  {
853  return;// Settings was not opened.
854  }
855 
857  { // Try send report
858  // Remove gist.json file before close app.
859  connect(this, &VApplication::aboutToQuit, this, &VApplication::CleanGist, Qt::UniqueConnection);
860  SendReport(reportName);
861  }
862  else
863  { // Just collect report to /reports directory
864  CollectReport(reportName);
865  }
866  }
867 }
868 
869 //---------------------------------------------------------------------------------------------------------------------
870 void VApplication::CollectReport(const QString &reportName) const
871 {
872  const QString reportsDir = QString("%1/reports").arg(qApp->applicationDirPath());
873  QDir reports(reportsDir);
874  if (reports.exists() == false)
875  {
876  reports.mkpath("."); // Create directory for reports if need
877  }
878 
879  const QDateTime now = QDateTime::currentDateTime();
880  const QString timestamp = now.toString(QLatin1String("yyyy.MM.dd-hh_mm_ss"));
881  QString filename = QString("%1/reports/crash-%2.RPT").arg(qApp->applicationDirPath()).arg(timestamp);
882 
883  QFile reportFile(reportName);
884  reportFile.copy(filename); // Collect new crash
885  reportFile.remove(); // Clear after yourself
886 
887  filename = QString("%1/reports/log-%2.log").arg(qApp->applicationDirPath()).arg(timestamp);
888  GatherLogs();
889  QFile logFile(QString("%1/seamly2d.log").arg(LogDirPath()));
890  logFile.copy(filename); // Collect log
891 }
892 
893 //---------------------------------------------------------------------------------------------------------------------
894 void VApplication::CleanGist() const
895 {
896  QFile gistFile(GistFileName);
897  if (gistFile.exists())
898  {
899  gistFile.remove();
900  }
901 }
902 
903 //---------------------------------------------------------------------------------------------------------------------
904 void VApplication::SendReport(const QString &reportName) const
905 {
906  QString content;
907  QFile reportFile(reportName);
908  if (reportFile.open(QIODevice::ReadOnly | QIODevice::Text))
909  {
910  content = ReadFileForSending(reportFile);
911  reportFile.close();
912  }
913  else
914  {
915  content = "RPT file error:" + reportFile.errorString() + "\r\n";
916  }
917 
918  // Additional information
919  content.append(QString("-------------------------------")+"\r\n");
920  content.append(QString("Version:%1").arg(APP_VERSION)+"\r\n");
921  content.append(QString("Build revision:%1").arg(BUILD_REVISION)+"\r\n");
922  content.append(QString("Based on Qt %1 (32 bit)").arg(QT_VERSION_STR)+"\r\n");
923  content.append(QString("Built on %1 at %2").arg(__DATE__).arg(__TIME__)+"\r\n");
924  content.append(QString("Web site:http://seamly.net/ ")+"\r\n");
925  content.append("\r\n");
926 
927  // Creating json with report
928  // Example:
929  //{
930  // "description":"Crash report",
931  // "public":"true",
932  // "files":{
933  // "seamly2d.RPT":{
934  // "content":"Report text here"
935  // }
936  // }
937  //}
938 
939  // Useful to know when crash was created
940  const QDateTime now = QDateTime::currentDateTime();
941  const QString timestamp = now.toString(QLatin1String("yyyy/MM/dd hh:mm:ss:zzz"));
942  const QString report = QString("Crash report was created %2").arg(timestamp);
943 
944  QJsonObject reportObject;
945  reportObject.insert(QStringLiteral("description"), QJsonValue(report));
946  reportObject.insert(QStringLiteral("public"), QJsonValue(QString("true")));
947 
948  content.append(QString("\r\n-------------------------------\r\n"));
949  content.append(QString("Log:")+"\r\n");
950 
951  GatherLogs();
952  QFile logFile(QString("%1/seamly2d.log").arg(LogDirPath()));
953  if (logFile.open(QIODevice::ReadOnly | QIODevice::Text))
954  {
955  const QString logContent = ReadFileForSending(logFile);
956  if (logContent.isEmpty())
957  {
958  content.append("Log file is empty.");
959  }
960  else
961  {
962  content.append(logContent);
963  }
964  logFile.close();
965  }
966  else
967  {
968  content.append("\r\n Log file error:" + logFile.errorString() + "\r\n");
969  }
970 
971  const QString contentSection = QStringLiteral("content");
972  QJsonObject contentObject;
973  contentObject.insert(contentSection, QJsonValue(content));
974 
975  const QString filesSection = QStringLiteral("files");
976  QJsonObject fileObject;
977  fileObject.insert(QFileInfo(reportName).fileName(), QJsonValue(contentObject));
978  reportObject.insert(filesSection, QJsonValue(fileObject));
979 
980  QFile gistFile(GistFileName);
981  if (!gistFile.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text))
982  {
983  qDebug("Couldn't open gist file.");
984  return;
985  }
986 
987  // Save data to file
988  QJsonDocument saveRep(reportObject);
989  gistFile.write(saveRep.toJson());
990  gistFile.close();
991 
992  const QString curl = QString("%1/curl.exe").arg(qApp->applicationDirPath());
993  QFile curlFile(curl);
994  if (curlFile.exists())
995  {// Trying send report
996  // Change token if need
997  const QStringList token = QStringList()<<"eb"<<"78"<<"63"<<"4e"<<"de"<<"77"<<"f7"<<"e6"<<"01"<<"4a"<<"c3"<<"60"
998  <<"96"<<"b0"<<"2d"<<"54"<<"fb"<<"8e"<<"af"<<"ec";
999 
1000  const QString arg = QString("curl.exe -k -H \"Authorization: bearer ")+token.join("")+
1001  QString("\" -H \"Accept: application/json\" -H \"Content-type: application/json\" -X POST "
1002  "--data @gist.json https://api.github.com/gists");
1003  QProcess proc;
1004  QStringList args;
1005  proc.start(arg, args);
1006  proc.waitForFinished(10000); // 10 sec
1007  reportFile.remove();// Clear after yourself
1008  }
1009  else
1010  {// We can not send than just collect
1011  CollectReport(reportName);
1012  }
1013  curlFile.close();
1014 }
1015 
1016 //---------------------------------------------------------------------------------------------------------------------
1017 QString VApplication::ReadFileForSending(QFile &file) const
1018 {
1019  QString content;
1020  QTextStream in(&file);
1021  while (!in.atEnd())
1022  {
1023  content.append(in.readLine()+"\r\n");// Windows end of line
1024  }
1025  return content;
1026 }
1027 #endif //defined(Q_OS_WIN) && defined(Q_CC_GNU)
The MainWindow class main windows.
Definition: mainwindow.h:87
bool LoadPattern(const QString &fileName, const QString &customMeasureFile=QString())
LoadPattern open pattern file.
QWidget * mainWindow
mainWindow pointer to main window. Usefull if need create modal dialog. Without pointer to main windo...
VCommonSettings * settings
settings pointer to settings. Help hide constructor creation settings. Make make code more readable.
void loadTranslations(const QString &locale)
QString SeamlyMeFilePath() const
virtual void OpenSettings() Q_DECL_OVERRIDE
OpenSettings get acsses to application settings.
static bool IsGUIMode()
std::shared_ptr< QTextStream > out
Definition: vapplication.h:121
QString LogDirPath() const
VSettings * Seamly2DSettings()
void InitOptions()
static void NewSeamly2D(const QString &fileName=QString())
NewSeamly2D start Seamly2D in new process, send path to pattern file in argument.
virtual bool notify(QObject *receiver, QEvent *event) Q_DECL_OVERRIDE
notify Reimplemented from QApplication::notify().
virtual ~VApplication() Q_DECL_OVERRIDE
virtual bool IsAppInGUIMode() const Q_DECL_OVERRIDE
IsAppInGUIMode little hack that allow to have access to application state from VAbstractApplication c...
std::shared_ptr< VLockGuard< QFile > > lockLog
Definition: vapplication.h:120
virtual bool event(QEvent *e) Q_DECL_OVERRIDE
void ClearOldLogs() const
void BeginLogging()
VApplication(int &argc, char **argv)
VApplication constructor.
QTextStream * LogFile()
void StartLogging()
QString LogPath() const
bool CreateLogDir() const
virtual void InitTrVars() Q_DECL_OVERRIDE
const VCommandLinePtr CommandLine() const
static QStringList LabelLanguages()
virtual const VTranslateVars * TrVars() Q_DECL_OVERRIDE
VTranslateVars * trVars
Definition: vapplication.h:117
static void Reset()
Definition: vcmdexport.cpp:509
static VCommandLinePtr instance
Definition: vcmdexport.h:116
static VCommandLinePtr Get(const QCoreApplication &app)
Definition: vcmdexport.cpp:487
bool GetSendReportState() const
static QString GetDefPathMultisizeMeasurements()
static QString GetDefPathTemplate()
static QString GetDefPathIndividualMeasurements()
static QString GetDefPathLabelTemplate()
The VExceptionBadId class for exception bad id.
The VExceptionConversionError class for exception of conversion error.
The VExceptionEmptyParameter class for exception empty parameter.
The VExceptionObjectError class for exception object error.
virtual QString ErrorMessage() const Q_DECL_OVERRIDE
ErrorMessage return main error message.
virtual QString DetailedInformation() const Q_DECL_OVERRIDE
DetailedInformation return detailed information about error.
The VExceptionWrongId class for exception wrong id.
The VException class parent for all exception. Could be use for abstract exception.
Definition: vexception.h:66
virtual const char * what() const Q_DECL_OVERRIDE
Definition: vexception.cpp:161
const std::shared_ptr< Guarded > & GetProtected() const
Definition: vlockguard.h:141
Error class of the parser.
#define SCASSERT(cond)
Definition: def.h:317
QString buildCompatibilityString()
#define VER_COMPANYDOMAIN_STR
#define APP_VERSION
#define VER_COMPANYNAME_STR
const QString APP_VERSION_STR
#define VER_INTERNALNAME_STR
Definition: version.h:57
QT_WARNING_PUSH QT_WARNING_POP Q_DECL_CONSTEXPR auto DAYS_TO_KEEP_LOGS
void noisyFailureMsgHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg)
#define qApp
Definition: vapplication.h:67
#define translate(context, source)
Definition: vcmdexport.cpp:41
std::shared_ptr< VCommandLine > VCommandLinePtr
Definition: vcmdexport.h:41
QT_WARNING_PUSH void VlpCreateLock(std::shared_ptr< VLockGuard< Guarded >> &r, const QString &lockName, int stale=0, int timeout=0)
Definition: vlockguard.h:213
static const auto V_EX_DATAERR
Definition: vsysexits.h:68
QTextStream & vStdOut()
Definition: vsysexits.h:114
static const auto V_EX_SOFTWARE
Definition: vsysexits.h:79
QTextStream & vStdErr()
Definition: vsysexits.h:106