/************************************************************************ ** ** @file vapplication.cpp ** @author Roman Telezhynskyi ** @date November 15, 2013 ** ** @brief ** @copyright ** This source code is part of the Valentine project, a pattern making ** program, whose allow create and modeling patterns of clothing. ** Copyright (C) 2013 Valentina project ** All Rights Reserved. ** ** Valentina is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 3 of the License, or ** (at your option) any later version. ** ** Valentina is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with Valentina. If not, see . ** *************************************************************************/ #include "vapplication.h" #include "../libs/ifc/exception/vexceptionobjecterror.h" #include "../libs/ifc/exception/vexceptionbadid.h" #include "../libs/ifc/exception/vexceptionconversionerror.h" #include "../libs/ifc/exception/vexceptionemptyparameter.h" #include "../libs/ifc/exception/vexceptionwrongid.h" #include "vmaingraphicsview.h" #include "../container/calculator.h" #include "../version.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace qmu; Q_LOGGING_CATEGORY(vApp, "v.application") //--------------------------------------------------------------------------------------------------------------------- inline void noisyFailureMsgHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg) { // Why on earth didn't Qt want to make failed signal/slot connections qWarning? if ((type == QtDebugMsg) && msg.contains("::connect")) { type = QtWarningMsg; } // this is another one that doesn't make sense as just a debug message. pretty serious // sign of a problem // http://www.developer.nokia.com/Community/Wiki/QPainter::begin:Paint_device_returned_engine_%3D%3D_0_(Known_Issue) if ((type == QtDebugMsg) && msg.contains("QPainter::begin") && msg.contains("Paint device returned engine")) { type = QtWarningMsg; } // This qWarning about "Cowardly refusing to send clipboard message to hung application..." // is something that can easily happen if you are debugging and the application is paused. // As it is so common, not worth popping up a dialog. if ((type == QtWarningMsg) && QString(msg).contains("QClipboard::event") && QString(msg).contains("Cowardly refusing")) { type = QtDebugMsg; } // only the GUI thread should display message boxes. If you are // writing a multithreaded application and the error happens on // a non-GUI thread, you'll have to queue the message to the GUI QCoreApplication *instance = QCoreApplication::instance(); const bool isGuiThread = instance && (QThread::currentThread() == instance->thread()); if (isGuiThread) { QString debugdate = "[" + QDateTime::currentDateTime().toString("yyyy.MM.dd hh:mm:ss"); QMessageBox messageBox; switch (type) { case QtDebugMsg: debugdate += QString(":DEBUG:%1(%2)] %3: %4: %5").arg(context.file).arg(context.line) .arg(context.function).arg(context.category).arg(msg); break; case QtWarningMsg: debugdate += QString(":WARNING:%1(%2)] %3: %4: %5").arg(context.file).arg(context.line) .arg(context.function).arg(context.category).arg(msg); messageBox.setIcon(QMessageBox::Warning); break; case QtCriticalMsg: debugdate += QString(":CRITICAL:%1(%2)] %3: %4: %5").arg(context.file).arg(context.line) .arg(context.function).arg(context.category).arg(msg); messageBox.setIcon(QMessageBox::Critical); break; case QtFatalMsg: debugdate += QString(":FATAL:%1(%2)] %3: %4: %5").arg(context.file).arg(context.line) .arg(context.function).arg(context.category).arg(msg); messageBox.setIcon(QMessageBox::Critical); break; default: break; } (*qApp->LogFile()) << debugdate << endl; if (type == QtWarningMsg || type == QtCriticalMsg || type == QtFatalMsg) { messageBox.setInformativeText(msg); messageBox.setStandardButtons(QMessageBox::Ok); messageBox.setWindowModality(Qt::ApplicationModal); messageBox.setModal(true); messageBox.exec(); } if (QtFatalMsg == type) { abort(); } } else { if (type != QtDebugMsg) { abort(); // be NOISY unless overridden! } } } //--------------------------------------------------------------------------------------------------------------------- const qreal VApplication::PrintDPI = 96.0; #if defined(Q_OS_WIN) && defined(Q_CC_GNU) const QString VApplication::GistFileName = QStringLiteral("gist.json"); #endif // defined(Q_OS_WIN) && defined(Q_CC_GNU) #define DefWidth 1.2//mm //--------------------------------------------------------------------------------------------------------------------- /** * @brief VApplication constructor. * @param argc number arguments. * @param argv command line. */ VApplication::VApplication(int &argc, char **argv) : QApplication(argc, argv), _patternUnit(Unit::Cm), _patternType(MeasurementsType::Individual), _widthMainLine(DefWidth), _widthHairLine(DefWidth/3.0), measurements(QMap()), guiTexts(QMap()), descriptions(QMap()), variables(QMap()), functions(QMap()), postfixOperators(QMap()), stDescriptions(QMap()), undoStack(nullptr), sceneView(nullptr), currentScene(nullptr), autoSaveTimer(nullptr), mainWindow(nullptr), openingPattern(false), settings(nullptr), doc(nullptr), log(nullptr), out(nullptr), logLock(nullptr) { undoStack = new QUndoStack(this); InitLineWidth(); InitMeasurements(); InitVariables(); InitFunctions(); InitPostfixOperators(); InitSTDescriptions(); } //--------------------------------------------------------------------------------------------------------------------- VApplication::~VApplication() { qCDebug(vApp)<<"Application closing."; qInstallMessageHandler(0); // Resore the message handler delete out; if (log != nullptr) { log->close(); delete log; delete logLock; } } //--------------------------------------------------------------------------------------------------------------------- /** * @brief NewValentina start Valentina in new process, send path to pattern file in argument. * @param fileName path to pattern file. */ void VApplication::NewValentina(const QString &fileName) { qCDebug(vApp)<<"Open new detached process."; if (fileName.isEmpty()) { qCDebug(vApp)<<"New process without arguments. program ="<applicationFilePath(); // Path can contain spaces. if (QProcess::startDetached("\""+qApp->applicationFilePath()+"\"")) { qCDebug(vApp)<<"The process was started successfully."; } else { qCWarning(vApp)<<"Could not run process. The operation timed out or an error occurred."; } } else { const QString run = QString("\"%1\" \"%2\"").arg(qApp->applicationFilePath()).arg(fileName); qCDebug(vApp)<<"New process with arguments. program ="<= 0.5) { oldFactor = Newfactor; } } //--------------------------------------------------------------------------------------------------------------------- /** * @brief notify Reimplemented from QApplication::notify(). * @param receiver receiver. * @param event event. * @return value that is returned from the receiver's event handler. */ // reimplemented from QApplication so we can throw exceptions in slots bool VApplication::notify(QObject *receiver, QEvent *event) { try { return QApplication::notify(receiver, event); } catch (const VExceptionObjectError &e) { e.CriticalMessageBox(tr("Error parsing file. Program will be terminated."), mainWindow); abort(); } catch (const VExceptionBadId &e) { e.CriticalMessageBox(tr("Error bad id. Program will be terminated."), mainWindow); abort(); } catch (const VExceptionConversionError &e) { e.CriticalMessageBox(tr("Error can't convert value. Program will be terminated."), mainWindow); abort(); } catch (const VExceptionEmptyParameter &e) { e.CriticalMessageBox(tr("Error empty parameter. Program will be terminated."), mainWindow); abort(); } catch (const VExceptionWrongId &e) { e.CriticalMessageBox(tr("Error wrong id. Program will be terminated."), mainWindow); abort(); } catch (const VException &e) { e.CriticalMessageBox(tr("Something's wrong!!"), mainWindow); return true; } catch (std::exception& e) { qCritical() << "Exception thrown:" << e.what(); } return false; } //--------------------------------------------------------------------------------------------------------------------- double VApplication::toPixel(double val, const Unit &unit) const { switch (unit) { case Unit::Mm: return (val / 25.4) * PrintDPI; case Unit::Cm: return ((val * 10.0) / 25.4) * PrintDPI; case Unit::Inch: return val * PrintDPI; case Unit::Px: return val; default: break; } return 0; } //--------------------------------------------------------------------------------------------------------------------- double VApplication::toPixel(double val) const { return toPixel(val, _patternUnit); } //--------------------------------------------------------------------------------------------------------------------- double VApplication::fromPixel(double pix, const Unit &unit) const { switch (unit) { case Unit::Mm: return (pix / PrintDPI) * 25.4; case Unit::Cm: return ((pix / PrintDPI) * 25.4) / 10.0; case Unit::Inch: return pix / PrintDPI; case Unit::Px: return pix; default: break; } return 0; } //--------------------------------------------------------------------------------------------------------------------- double VApplication::fromPixel(double pix) const { return fromPixel(pix, _patternUnit); } //--------------------------------------------------------------------------------------------------------------------- bool VApplication::TryLock(QLockFile *lock) { if (lock == nullptr) { return false; } if (lock->tryLock()) { return true; } else { if (lock->error() == QLockFile::LockFailedError) { // This happens if a stale lock file exists and another process uses that PID. // Try removing the stale file, which will fail if a real process is holding a // file-level lock. A false error is more problematic than not locking properly // on corner-case systems. if (lock->removeStaleLockFile() == false || lock->tryLock() == false) { return false; } else { return true; } } else { return false; } return false; } } //--------------------------------------------------------------------------------------------------------------------- QString VApplication::translationsPath() const { const QString trPath = QStringLiteral("/translations"); #ifdef Q_OS_WIN return QApplication::applicationDirPath() + trPath; #else #ifdef QT_DEBUG return QApplication::applicationDirPath() + trPath; #else QDir dir(QApplication::applicationDirPath() + trPath); if (dir.exists()) { return dir.absolutePath(); } else { return QStringLiteral("/usr/share/valentina/translations"); } #endif #endif } //--------------------------------------------------------------------------------------------------------------------- void VApplication::InitLineWidth() { switch (_patternUnit) { case Unit::Mm: _widthMainLine = DefWidth; break; case Unit::Cm: _widthMainLine = DefWidth/10.0; break; case Unit::Inch: _widthMainLine = DefWidth/25.4; break; default: _widthMainLine = DefWidth; break; } _widthHairLine = _widthMainLine/3.0; } //--------------------------------------------------------------------------------------------------------------------- void VApplication::InitMeasurement(const QString &name, const QmuTranslation &m, const QmuTranslation &g, const QmuTranslation &d) { measurements.insert(name, m); guiTexts.insert(name, g); descriptions.insert(name, d); } //--------------------------------------------------------------------------------------------------------------------- QString VApplication::LogDirPath() const { #if defined(Q_OS_WIN) || defined(Q_OS_OSX) const QString logDirPath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QString(), QStandardPaths::LocateDirectory) + "Valentina"; #else const QString logDirPath = QStandardPaths::locate(QStandardPaths::ConfigLocation, QString(), QStandardPaths::LocateDirectory) + organizationName(); #endif return logDirPath; } //--------------------------------------------------------------------------------------------------------------------- QString VApplication::LogPath() const { return QString("%1/valentina-pid%2.log").arg(LogDirPath()).arg(qApp->applicationPid()); } //--------------------------------------------------------------------------------------------------------------------- void VApplication::CreateLogDir() const { QDir logDir(LogDirPath()); if (logDir.exists() == false) { logDir.mkpath("."); // Create directory for log if need } } //--------------------------------------------------------------------------------------------------------------------- void VApplication::BeginLogging() { log = new QFile(LogPath()); if (log->open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) { out = new QTextStream(log); qInstallMessageHandler(noisyFailureMsgHandler); logLock = new QLockFile(LogPath()+".lock"); logLock->setStaleLockTime(0); if (TryLock(logLock)) { qCDebug(vApp) << "Log file"<error(); } } else { delete log; log = nullptr; qCDebug(vApp) << "Error opening log file '" << LogPath() << "'. All debug output redirected to console."; } } //--------------------------------------------------------------------------------------------------------------------- void VApplication::ClearOldLogs() const { QStringList filters{"*.log"}; QDir logsDir(LogDirPath()); logsDir.setNameFilters(filters); logsDir.setCurrent(LogDirPath()); const QStringList allFiles = logsDir.entryList(QDir::NoDotAndDotDot | QDir::Files); if (allFiles.isEmpty() == false) { qCDebug(vApp) << "Clearing old logs"; for (int i = 0; i < allFiles.size(); ++i) { QFileInfo info(allFiles.at(i)); QLockFile *lock = new QLockFile(info.absoluteFilePath() + ".lock"); if (TryLock(lock)) { qCDebug(vApp) << "Locked file"<0")); functions.insert(rint_F, QmuTranslation::translate("Functions", "rint", "round to nearest integer")); functions.insert(abs_F, QmuTranslation::translate("Functions", "abs", "absolute value")); functions.insert(min_F, QmuTranslation::translate("Functions", "min", "min of all arguments")); functions.insert(max_F, QmuTranslation::translate("Functions", "max", "max of all arguments")); functions.insert(sum_F, QmuTranslation::translate("Functions", "sum", "sum of all arguments")); functions.insert(avg_F, QmuTranslation::translate("Functions", "avg", "mean value of all arguments")); } //--------------------------------------------------------------------------------------------------------------------- void VApplication::InitPostfixOperators() { postfixOperators.insert(cm_Oprt, QmuTranslation::translate("PostfixOperators", "cm", "centimeter")); postfixOperators.insert(mm_Oprt, QmuTranslation::translate("PostfixOperators", "mm", "millimeter")); postfixOperators.insert(in_Oprt, QmuTranslation::translate("PostfixOperators", "in", "inch")); } //--------------------------------------------------------------------------------------------------------------------- void VApplication::InitSTDescriptions() { stDescriptions.insert("0", QmuTranslation::translate("STDescriptions", "Standard figures of men 1st group, chest 100 cm", "Standard table description")); } //--------------------------------------------------------------------------------------------------------------------- /** * @brief VApplication::MeasurementsFromUser translate measurement to internal look. * @param newFormula [in|out] expression to translate * @param position token position * @param token token to translate * @param bias hold change of length between translated and origin token string * @return true if was found measurement with same name. */ bool VApplication::MeasurementsFromUser(QString &newFormula, int position, const QString &token, int &bias) const { QMap::const_iterator i = measurements.constBegin(); while (i != measurements.constEnd()) { if (token == i.value().translate()) { newFormula.replace(position, token.length(), i.key()); bias = token.length() - i.key().length(); return true; } ++i; } return false; } //--------------------------------------------------------------------------------------------------------------------- /** * @brief VApplication::VariablesFromUser translate variable to internal look. * @param newFormula [in|out] expression to translate * @param position token position * @param token token to translate * @param bias hold change of length between translated and origin token string * @return true if was found variable with same name. */ bool VApplication::VariablesFromUser(QString &newFormula, int position, const QString &token, int &bias) const { QMap::const_iterator i = variables.constBegin(); while (i != variables.constEnd()) { if (token.indexOf( i.value().translate() ) == 0) { newFormula.replace(position, i.value().translate().length(), i.key()); QString newToken = token; newToken.replace(0, i.value().translate().length(), i.key()); bias = token.length() - newToken.length(); return true; } ++i; } return false; } //--------------------------------------------------------------------------------------------------------------------- /** * @brief VApplication::PostfixOperatorsFromUser translate postfix operator to internal look. * @param newFormula [in|out] expression to translate * @param position token position * @param token token to translate * @param bias hold change of length between translated and origin token string * @return true if was found postfix operator with same name. */ bool VApplication::PostfixOperatorsFromUser(QString &newFormula, int position, const QString &token, int &bias) const { QMap::const_iterator i = postfixOperators.constBegin(); while (i != postfixOperators.constEnd()) { if (token == i.value().translate()) { newFormula.replace(position, token.length(), i.key()); bias = token.length() - i.key().length(); return true; } ++i; } return false; } //--------------------------------------------------------------------------------------------------------------------- /** * @brief VApplication::FunctionsFromUser translate function name to internal look. * @param newFormula [in|out] expression to translate * @param position token position * @param token token to translate * @param bias hold change of length between translated and origin token string * @return true if was found function with same name. */ bool VApplication::FunctionsFromUser(QString &newFormula, int position, const QString &token, int &bias) const { QMap::const_iterator i = functions.constBegin(); while (i != functions.constEnd()) { if (token == i.value().translate()) { newFormula.replace(position, token.length(), i.key()); bias = token.length() - i.key().length(); return true; } ++i; } return false; } //--------------------------------------------------------------------------------------------------------------------- /** * @brief VApplication::VariablesToUser translate variable name to user. * @param newFormula [in|out] expression to translate * @param position token position * @param token token to translate * @param bias hold change of length between translated and origin token string * @return true if was found variable with same name. */ bool VApplication::VariablesToUser(QString &newFormula, int position, const QString &token, int &bias) const { QMap::const_iterator i = variables.constBegin(); while (i != variables.constEnd()) { if (token.indexOf( i.key() ) == 0) { newFormula.replace(position, i.key().length(), i.value().translate()); QString newToken = token; newToken.replace(0, i.key().length(), i.value().translate()); bias = token.length() - newToken.length(); return true; } ++i; } return false; } //--------------------------------------------------------------------------------------------------------------------- /** * @brief VApplication::CorrectionsPositions correct position tokens in expression after token translation. * * Because translated string can have different length compare to original need make correction after each translation. * If bias = 0 correction will not happens. * * @param position position currecnt token in expression * @param bias difference between original token length and translated * @param tokens all tokens * @param numbers all numbers */ void VApplication::CorrectionsPositions(int position, int bias, QMap &tokens, QMap &numbers) { if (bias == 0) { return;// Nothing to correct; } BiasTokens(position, bias, tokens); BiasTokens(position, bias, numbers); } //--------------------------------------------------------------------------------------------------------------------- /** * @brief VApplication::BiasTokens change position for each token that have position more then "position". * @param position token position * @param bias difference between original token length and translated * @param tokens all tokens */ void VApplication::BiasTokens(int position, int bias, QMap &tokens) const { QMap newTokens; QMap::const_iterator i = tokens.constBegin(); while (i != tokens.constEnd()) { if (i.key()<= position) { // Tokens before position "position" did not change his positions. newTokens.insert(i.key(), i.value()); } else { newTokens.insert(i.key()-bias, i.value()); } ++i; } tokens = newTokens; } //--------------------------------------------------------------------------------------------------------------------- void VApplication::setPatternUnit(const Unit &patternUnit) { _patternUnit = patternUnit; InitLineWidth(); } //--------------------------------------------------------------------------------------------------------------------- QString VApplication::VarToUser(const QString &var) const { if (measurements.contains(var)) { return measurements.value(var).translate(); } if (functions.contains(var)) { return functions.value(var).translate(); } if (postfixOperators.contains(var)) { return postfixOperators.value(var).translate(); } QString newVar = var; int bias = 0; if (VariablesToUser(newVar, 0, var, bias)) { return newVar; } return newVar; } //--------------------------------------------------------------------------------------------------------------------- QString VApplication::VarFromUser(const QString &var) const { QString newVar = var; int bias = 0; if (MeasurementsFromUser(newVar, 0, var, bias)) { return newVar; } if (VariablesFromUser(newVar, 0, var, bias)) { return newVar; } if (PostfixOperatorsFromUser(newVar, 0, var, bias)) { return newVar; } if (FunctionsFromUser(newVar, 0, var, bias)) { return newVar; } return newVar; } //--------------------------------------------------------------------------------------------------------------------- QString VApplication::GuiText(const QString &measurement) const { return guiTexts.value(measurement).translate(); } //--------------------------------------------------------------------------------------------------------------------- QString VApplication::Description(const QString &measurement) const { return descriptions.value(measurement).translate(); } //--------------------------------------------------------------------------------------------------------------------- QString VApplication::PostfixOperator(const QString &name) const { return postfixOperators.value(name).translate(); } //--------------------------------------------------------------------------------------------------------------------- /** * @brief VApplication::FormulaFromUser replace all known tokens in formula to internal look. Also change decimal * separator in numbers. * @param formula expression that need translate * @return translated expression */ QString VApplication::FormulaFromUser(const QString &formula) { QString newFormula = formula;// Local copy for making changes Calculator *cal = new Calculator(formula);// Eval formula QMap tokens = cal->GetTokens();// Tokens (variables, measurements) QMap numbers = cal->GetNumbers();// All numbers in expression for changing decimal separator delete cal; QList tKeys = tokens.keys();// Take all tokens positions QList tValues = tokens.values(); for (int i = 0; i < tKeys.size(); ++i) { int bias = 0; if (MeasurementsFromUser(newFormula, tKeys.at(i), tValues.at(i), bias)) { if (bias != 0) {// Translated token has different length than original. Position next tokens need to be corrected. CorrectionsPositions(tKeys.at(i), bias, tokens, numbers); tKeys = tokens.keys(); tValues = tokens.values(); } continue; } if (VariablesFromUser(newFormula, tKeys.at(i), tValues.at(i), bias)) { if (bias != 0) {// Translated token has different length than original. Position next tokens need to be corrected. CorrectionsPositions(tKeys.at(i), bias, tokens, numbers); tKeys = tokens.keys(); tValues = tokens.values(); } continue; } if (PostfixOperatorsFromUser(newFormula, tKeys.at(i), tValues.at(i), bias)) { if (bias != 0) {// Translated token has different length than original. Position next tokens need to be corrected. CorrectionsPositions(tKeys.at(i), bias, tokens, numbers); tKeys = tokens.keys(); tValues = tokens.values(); } continue; } if (FunctionsFromUser(newFormula, tKeys.at(i), tValues.at(i), bias)) { if (bias != 0) {// Translated token has different length than original. Position next tokens need to be corrected. CorrectionsPositions(tKeys.at(i), bias, tokens, numbers); tKeys = tokens.keys(); tValues = tokens.values(); } continue; } } QLocale loc = QLocale::system(); // User locale if (loc != QLocale(QLocale::C) && getSettings()->GetOsSeparator()) {// User want use Os separator QList nKeys = numbers.keys();// Positions for all numbers in expression QList nValues = numbers.values(); for (int i = 0; i < nKeys.size(); ++i) { loc = QLocale::system();// From system locale bool ok = false; const qreal d = loc.toDouble(nValues.at(i), &ok); if (ok == false) { qDebug()<<"Can't convert to double token"< tokens; QMap numbers; try { Calculator *cal = new Calculator(formula, false);// Eval formula tokens = cal->GetTokens();// Tokens (variables, measurements) numbers = cal->GetNumbers();// All numbers in expression for changing decimal separator delete cal; } catch (qmu::QmuParserError &e) { qDebug() << "\nMath parser error:\n" << "--------------------------------------\n" << "Message: " << e.GetMsg() << "\n" << "Expression: " << e.GetExpr() << "\n" << "--------------------------------------"; return newFormula; } QList tKeys = tokens.keys(); QList tValues = tokens.values(); for (int i = 0; i < tKeys.size(); ++i) { if (measurements.contains(tValues.at(i))) { newFormula.replace(tKeys.at(i), tValues.at(i).length(), measurements.value(tValues.at(i)).translate()); int bias = tValues.at(i).length() - measurements.value(tValues.at(i)).translate().length(); if (bias != 0) {// Translated token has different length than original. Position next tokens need to be corrected. CorrectionsPositions(tKeys.at(i), bias, tokens, numbers); tKeys = tokens.keys(); tValues = tokens.values(); } continue; } if (functions.contains(tValues.at(i))) { newFormula.replace(tKeys.at(i), tValues.at(i).length(), functions.value(tValues.at(i)).translate()); int bias = tValues.at(i).length() - functions.value(tValues.at(i)).translate().length(); if (bias != 0) {// Translated token has different length than original. Position next tokens need to be corrected. CorrectionsPositions(tKeys.at(i), bias, tokens, numbers); tKeys = tokens.keys(); tValues = tokens.values(); } continue; } if (postfixOperators.contains(tValues.at(i))) { newFormula.replace(tKeys.at(i), tValues.at(i).length(), postfixOperators.value(tValues.at(i)).translate()); int bias = tValues.at(i).length() - postfixOperators.value(tValues.at(i)).translate().length(); if (bias != 0) {// Translated token has different length than original. Position next tokens need to be corrected. CorrectionsPositions(tKeys.at(i), bias, tokens, numbers); tKeys = tokens.keys(); tValues = tokens.values(); } continue; } int bias = 0; if (VariablesToUser(newFormula, tKeys.at(i), tValues.at(i), bias)) { if (bias != 0) {// Translated token has different length than original. Position next tokens need to be corrected. CorrectionsPositions(tKeys.at(i), bias, tokens, numbers); tKeys = tokens.keys(); tValues = tokens.values(); } continue; } } QLocale loc = QLocale::system();// User locale if (loc != QLocale::C && getSettings()->GetOsSeparator()) {// User want use Os separator QList nKeys = numbers.keys();// Positions for all numbers in expression QList nValues = numbers.values(); for (int i = 0; i < nKeys.size(); ++i) { loc = QLocale(QLocale::C);// From pattern locale bool ok = false; const qreal d = loc.toDouble(nValues.at(i), &ok); if (ok == false) { qDebug()<<"Can't convert to double token"<pixmap(); } QPixmap newPixmap(pixmapPath); QImage oldImage = oldPixmap.toImage(); QImage newImage = newPixmap.toImage(); if (oldImage != newImage ) { QApplication::setOverrideCursor(QCursor(newPixmap, hotX, hotY)); } #endif } //--------------------------------------------------------------------------------------------------------------------- void VApplication::restoreOverrideCursor(const QString &pixmapPath) { #ifndef QT_NO_CURSOR QPixmap oldPixmap; if (QCursor *oldCursor = QGuiApplication::overrideCursor()) { oldPixmap = oldCursor->pixmap(); } QPixmap newPixmap(pixmapPath); QImage oldImage = oldPixmap.toImage(); QImage newImage = newPixmap.toImage(); if (oldImage == newImage ) { QApplication::restoreOverrideCursor(); } #endif } //--------------------------------------------------------------------------------------------------------------------- QStringList VApplication::LabelLanguages() { QStringList list = QStringList() << "de" // German << "en" // English << "fr" // French << "ru" // Russian << "uk" // Ukrainian << "hr" // Croatian << "sr" // Serbian << "bs"; // Bosnian return list; } //--------------------------------------------------------------------------------------------------------------------- QString VApplication::STDescription(const QString &id) const { if (stDescriptions.contains(id)) { return stDescriptions.value(id).translate(); } else { qDebug()<<"Unknown id number. Got"<applicationDirPath()); QDir reports(reportsDir); if (reports.exists()) { QStringList filters{"*.log", "*.RPT"}; QDir logsDir(reportsDir); logsDir.setNameFilters(filters); logsDir.setCurrent(reportsDir); const QStringList allFiles = logsDir.entryList(QDir::NoDotAndDotDot | QDir::Files); if (allFiles.isEmpty() == false) { const QDateTime now = QDateTime::currentDateTime(); for (int i = 0; i < allFiles.size(); ++i) { QFileInfo info(allFiles.at(i)); if (info.created().daysTo(now) > 30) { QFile(allFiles.at(i)).remove(); } } } } } //--------------------------------------------------------------------------------------------------------------------- void VApplication::GatherLogs() const { QTextStream *out = nullptr; QFile *log = new QFile(QString("%1/valentina.log").arg(LogDirPath())); if (log->open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) { out = new QTextStream(log); QStringList filters{"*.log"}; QDir logsDir(LogDirPath()); logsDir.setNameFilters(filters); logsDir.setCurrent(LogDirPath()); const QStringList allFiles = logsDir.entryList(QDir::NoDotAndDotDot | QDir::Files); if (allFiles.isEmpty() == false) { for (int i = 0; i < allFiles.size(); ++i) { QFileInfo info(allFiles.at(i)); if (info.fileName() == "valentina.log") { continue; } QLockFile *logLock = new QLockFile(info.absoluteFilePath()+".lock"); logLock->setStaleLockTime(0); if (TryLock(logLock)) { *out <<"--------------------------" << endl; QFile logFile(info.absoluteFilePath()); if (logFile.open(QIODevice::ReadOnly | QIODevice::Text)) { QTextStream in(&logFile); while (!in.atEnd()) { *out << in.readLine() << endl; } logFile.close(); } else { *out << "Log file error:" + logFile.errorString() << endl; } } else { *out << "Could not lock" << info.absoluteFilePath() << "."; } delete logLock; } } else { *out << "Could not find logs."; } log->close(); } delete out; delete log; } //--------------------------------------------------------------------------------------------------------------------- // Catch exception and create report. Use if program build with Mingw compiler. // See more about catcher https://github.com/jrfonseca/drmingw/blob/master/README.md void VApplication::DrMingw() { QFile drmingw("exchndl.dll"); if(drmingw.exists()) {// If don't want create reports just delete exchndl.dll from installer LoadLibrary(L"exchndl.dll"); } } //--------------------------------------------------------------------------------------------------------------------- void VApplication::CollectReports() const { // Seek file "binary_name.RPT" const QString reportName = QString("%1/%2.RPT").arg(applicationDirPath()) .arg(QFileInfo(arguments().at(0)).baseName()); QFile reportFile(reportName); if (reportFile.exists()) { // Hooray we have found crash if (settings == nullptr) { return;// Settings was not opened. } if (settings->GetSendReportState()) { // Try send report // Remove gist.json file before close app. connect(this, &VApplication::aboutToQuit, this, &VApplication::CleanGist, Qt::UniqueConnection); SendReport(reportName); } else { // Just collect report to /reports directory CollectReport(reportName); } } } //--------------------------------------------------------------------------------------------------------------------- void VApplication::CollectReport(const QString &reportName) const { const QString reportsDir = QString("%1/reports").arg(qApp->applicationDirPath()); QDir reports(reportsDir); if (reports.exists() == false) { reports.mkpath("."); // Create directory for reports if need } const QDateTime now = QDateTime::currentDateTime(); const QString timestamp = now.toString(QLatin1String("yyyy.MM.dd-hh_mm_ss")); QString filename = QString("%1/reports/crash-%2.RPT").arg(qApp->applicationDirPath()).arg(timestamp); QFile reportFile(reportName); reportFile.copy(filename); // Collect new crash reportFile.remove(); // Clear after yourself filename = QString("%1/reports/log-%2.log").arg(qApp->applicationDirPath()).arg(timestamp); GatherLogs(); QFile logFile(QString("%1/valentina.log").arg(LogDirPath())); logFile.copy(filename); // Collect log } //--------------------------------------------------------------------------------------------------------------------- void VApplication::CleanGist() const { QFile gistFile(GistFileName); if (gistFile.exists()) { gistFile.remove(); } } //--------------------------------------------------------------------------------------------------------------------- void VApplication::SendReport(const QString &reportName) const { QString content; QFile reportFile(reportName); if (reportFile.open(QIODevice::ReadOnly | QIODevice::Text)) { content = ReadFileForSending(reportFile); reportFile.close(); } else { content = "RPT file error:" + reportFile.errorString() + "\r\n"; } // Additional information content.append(QString("-------------------------------")+"\r\n"); content.append(QString("Version:%1").arg(APP_VERSION)+"\r\n"); content.append(QString("Build revision:%1").arg(BUILD_REVISION)+"\r\n"); content.append(QString("Based on Qt %2 (32 bit)").arg(QT_VERSION_STR)+"\r\n"); content.append(QString("Built on %3 at %4").arg(__DATE__).arg(__TIME__)+"\r\n"); content.append(QString("Web site:http://www.valentina-project.org/ ")+"\r\n"); content.append("\r\n"); // Creating json with report // Example: //{ // "description":"Crash report", // "public":"true", // "files":{ // "valentina.RPT":{ // "content":"Report text here" // } // } //} // Useful to know when crash was created const QDateTime now = QDateTime::currentDateTime(); const QString timestamp = now.toString(QLatin1String("yyyy/MM/dd hh:mm:ss:zzz")); const QString report = QString("Crash report was created %2").arg(timestamp); QJsonObject reportObject; reportObject.insert(QStringLiteral("description"), QJsonValue(report)); reportObject.insert(QStringLiteral("public"), QJsonValue(QString("true"))); content.append(QString("\r\n-------------------------------\r\n")); content.append(QString("Log:")+"\r\n"); GatherLogs(); QFile logFile(QString("%1/valentina.log").arg(LogDirPath())); if (logFile.open(QIODevice::ReadOnly | QIODevice::Text)) { const QString logContent = ReadFileForSending(logFile); if (logContent.isEmpty()) { content.append("Log file is empty."); } else { content.append(logContent); } logFile.close(); } else { content.append("\r\n Log file error:" + logFile.errorString() + "\r\n"); } const QString contentSection = QStringLiteral("content"); QJsonObject contentObject; contentObject.insert(contentSection, QJsonValue(content)); const QString filesSection = QStringLiteral("files"); QJsonObject fileObject; fileObject.insert(QFileInfo(reportName).fileName(), QJsonValue(contentObject)); reportObject.insert(filesSection, QJsonValue(fileObject)); QFile gistFile(GistFileName); if (!gistFile.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) { qDebug("Couldn't open gist file."); return; } // Save data to file QJsonDocument saveRep(reportObject); gistFile.write(saveRep.toJson()); gistFile.close(); const QString curl = QString("%1/curl.exe").arg(qApp->applicationDirPath()); QFile curlFile(curl); if (curlFile.exists()) {// Trying send report // Change token if need const QStringList token = QStringList()<<"3c"<<"6e"<<"91"<<"19"<<"96"<<"92"<<"dc"<<"50"<<"67"<<"8a"<<"2a"<<"89" <<"a3"<<"55"<<"9e"<<"c7"<<"9d"<<"f8"<<"66"<<"a5"; const QString arg = QString("curl.exe -k -H \"Authorization: bearer ")+token.join("")+ QString("\" -H \"Accept: application/json\" -H \"Content-type: application/json\" -X POST " "--data @gist.json https://api.github.com/gists"); QProcess::startDetached(arg); reportFile.remove();// Clear after yourself } else {// We can not send than just collect CollectReport(reportName); } curlFile.close(); } //--------------------------------------------------------------------------------------------------------------------- QString VApplication::ReadFileForSending(QFile &file) const { QString content; QTextStream in(&file); while (!in.atEnd()) { content.append(in.readLine()+"\r\n");// Windows end of line } return content; } #endif //defined(Q_OS_WIN) && defined(Q_CC_GNU)