diff --git a/share/translations/translations.pro b/share/translations/translations.pro
index 0109a1d4e..6a3894b94 100644
--- a/share/translations/translations.pro
+++ b/share/translations/translations.pro
@@ -22,7 +22,8 @@ DEPENDPATH += \
../../src/libs/vtools \
../../src/libs/vformat \
../../src/libs/fervor \
- ../../src/libs/vwidgets
+ ../../src/libs/vwidgets \
+ ../../src/libs/vganalytics
include(../../src/app/valentina/valentina.pri)
include(../../src/app/tape/tape.pri)
@@ -39,6 +40,7 @@ include(../../src/libs/vtools/vtools.pri)
include(../../src/libs/vformat/vformat.pri)
include(../../src/libs/fervor/fervor.pri)
include(../../src/libs/vwidgets/vwidgets.pri)
+include(../../src/libs/vganalytics/vganalytics.pri)
# Add here path to new translation file with name "valentina_*_*.ts" if you want to add new language.
# Same paths in variable INSTALL_TRANSLATIONS (translations.pri).
diff --git a/src/app/puzzle/dialogs/configpages/puzzlepreferencesconfigurationpage.cpp b/src/app/puzzle/dialogs/configpages/puzzlepreferencesconfigurationpage.cpp
index 526cf228e..1b49dd157 100644
--- a/src/app/puzzle/dialogs/configpages/puzzlepreferencesconfigurationpage.cpp
+++ b/src/app/puzzle/dialogs/configpages/puzzlepreferencesconfigurationpage.cpp
@@ -33,6 +33,8 @@
#include "../vmisc/backport/qoverload.h"
#endif // QT_VERSION < QT_VERSION_CHECK(5, 7, 0)
+#include "../vganalytics/vganalytics.h"
+
//---------------------------------------------------------------------------------------------------------------------
PuzzlePreferencesConfigurationPage::PuzzlePreferencesConfigurationPage(QWidget *parent)
: QWidget(parent),
@@ -89,6 +91,9 @@ PuzzlePreferencesConfigurationPage::PuzzlePreferencesConfigurationPage(QWidget *
ui->doubleSpinBoxAcceleration->setMinimum(VCommonSettings::scrollingAccelerationMin);
ui->doubleSpinBoxAcceleration->setMaximum(VCommonSettings::scrollingAccelerationMax);
ui->doubleSpinBoxAcceleration->setValue(settings->GetScrollingAcceleration());
+
+ // Tab Privacy
+ ui->checkBoxSendUsageStatistics->setChecked(settings->IsCollectStatistic());
}
//---------------------------------------------------------------------------------------------------------------------
@@ -166,6 +171,10 @@ auto PuzzlePreferencesConfigurationPage::Apply() -> QStringList
settings->SetWheelMouseScale(ui->doubleSpinBoxWheel->value());
settings->SetScrollingAcceleration(ui->doubleSpinBoxAcceleration->value());
+ // Tab Privacy
+ settings->SetCollectStatistic(ui->checkBoxSendUsageStatistics->isChecked());
+ VGAnalytics::Instance()->Enable(ui->checkBoxSendUsageStatistics->isChecked());
+
return preferences;
}
diff --git a/src/app/puzzle/dialogs/configpages/puzzlepreferencesconfigurationpage.ui b/src/app/puzzle/dialogs/configpages/puzzlepreferencesconfigurationpage.ui
index 1aced40f9..f90d997a1 100644
--- a/src/app/puzzle/dialogs/configpages/puzzlepreferencesconfigurationpage.ui
+++ b/src/app/puzzle/dialogs/configpages/puzzlepreferencesconfigurationpage.ui
@@ -6,8 +6,8 @@
0
0
- 545
- 696
+ 539
+ 702
@@ -34,8 +34,8 @@
0
0
- 503
- 627
+ 497
+ 633
@@ -424,6 +424,43 @@ This option will take an affect after restart.
+
+
+ Privacy
+
+
+ -
+
+
+ Send usage statistics
+
+
+
+ -
+
+
+ Please help to improve Valentina's quality by automatically sending usage statistics. Sent data contains <span style=" font-weight:700;">no potentially sensitive information</span> like user names, email addresses, file contents or file paths.
+
+
+ true
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 540
+
+
+
+
+
+
diff --git a/src/app/puzzle/puzzle.pro b/src/app/puzzle/puzzle.pro
index ef824ee9c..653876c8e 100644
--- a/src/app/puzzle/puzzle.pro
+++ b/src/app/puzzle/puzzle.pro
@@ -282,6 +282,15 @@ DEPENDPATH += $$PWD/../../libs/vdxf
win32:!win32-g++: PRE_TARGETDEPS += $$OUT_PWD/../../libs/vdxf/$${DESTDIR}/vdxf.lib
else:unix|win32-g++: PRE_TARGETDEPS += $$OUT_PWD/../../libs/vdxf/$${DESTDIR}/libvdxf.a
+# VGAnalytics static library
+unix|win32: LIBS += -L$$OUT_PWD/../../libs/vganalytics/$${DESTDIR}/ -lvganalytics
+
+INCLUDEPATH += $$PWD/../../libs/vganalytics
+DEPENDPATH += $$PWD/../../libs/vganalytics
+
+win32:!win32-g++: PRE_TARGETDEPS += $$OUT_PWD/../../libs/vganalytics/$${DESTDIR}/vganalytics.lib
+else:unix|win32-g++: PRE_TARGETDEPS += $$OUT_PWD/../../libs/vganalytics/$${DESTDIR}/libvganalytics.a
+
# QMuParser library
win32:CONFIG(release, debug|release): LIBS += -L$${OUT_PWD}/../../libs/qmuparser/$${DESTDIR} -lqmuparser2
else:win32:CONFIG(debug, debug|release): LIBS += -L$${OUT_PWD}/../../libs/qmuparser/$${DESTDIR} -lqmuparser2
diff --git a/src/app/puzzle/puzzle.qbs b/src/app/puzzle/puzzle.qbs
index ece097a72..b07a5c92e 100644
--- a/src/app/puzzle/puzzle.qbs
+++ b/src/app/puzzle/puzzle.qbs
@@ -12,6 +12,7 @@ VToolApp {
Depends { name: "VWidgetsLib" }
Depends { name: "FervorLib" }
Depends { name: "multibundle"; }
+ Depends { name: "VGAnalyticsLib" }
name: "Puzzle"
buildconfig.appTarget: qbs.targetOS.contains("macos") ? "Puzzle" : "puzzle"
diff --git a/src/app/puzzle/vpapplication.cpp b/src/app/puzzle/vpapplication.cpp
index d55b94c85..81d40cbb7 100644
--- a/src/app/puzzle/vpapplication.cpp
+++ b/src/app/puzzle/vpapplication.cpp
@@ -32,6 +32,8 @@
#include "../ifc/exception/vexceptionemptyparameter.h"
#include "../ifc/exception/vexceptionobjecterror.h"
#include "../ifc/exception/vexceptionwrongid.h"
+#include "../vganalytics/def.h"
+#include "../vganalytics/vganalytics.h"
#include "../vmisc/vsysexits.h"
#include "version.h"
#include "vpmainwindow.h"
@@ -277,6 +279,17 @@ VPApplication::VPApplication(int &argc, char **argv)
//---------------------------------------------------------------------------------------------------------------------
VPApplication::~VPApplication()
{
+ if (settings->IsCollectStatistic())
+ {
+ auto *statistic = VGAnalytics::Instance();
+
+ QString clientID = settings->GetClientID();
+ if (!clientID.isEmpty())
+ {
+ statistic->SendAppCloseEvent(m_uptimeTimer.elapsed());
+ }
+ }
+
qDeleteAll(m_mainWindows);
}
@@ -430,6 +443,19 @@ void VPApplication::InitOptions()
QIcon::setThemeName(QStringLiteral("win.icon.theme"));
}
ActivateDarkMode();
+
+ auto *statistic = VGAnalytics::Instance();
+ QString clientID = settings->GetClientID();
+ if (clientID.isEmpty())
+ {
+ clientID = QUuid::createUuid().toString();
+ settings->SetClientID(clientID);
+ }
+ statistic->SetClientID(clientID);
+ statistic->SetGUILanguage(settings->GetLocale());
+ statistic->SetMeasurementId(GA_MEASUREMENT_ID);
+ statistic->SetApiSecret(GA_API_SECRET);
+ statistic->Enable(settings->IsCollectStatistic());
}
//---------------------------------------------------------------------------------------------------------------------
diff --git a/src/app/puzzle/vpmainwindow.cpp b/src/app/puzzle/vpmainwindow.cpp
index acf099064..c65f7297c 100644
--- a/src/app/puzzle/vpmainwindow.cpp
+++ b/src/app/puzzle/vpmainwindow.cpp
@@ -47,6 +47,7 @@
#include "../vlayout/vlayoutexporter.h"
#include "../vlayout/vprintlayout.h"
#include "../vlayout/vrawlayout.h"
+#include "../vmisc/dialogs/dialogaskcollectstatistic.h"
#include "../vmisc/dialogs/dialogselectlanguage.h"
#include "../vmisc/lambdaconstants.h"
#include "../vmisc/projectversion.h"
@@ -67,6 +68,7 @@
#if QT_VERSION < QT_VERSION_CHECK(5, 7, 0)
#include "../vmisc/backport/qoverload.h"
#endif // QT_VERSION < QT_VERSION_CHECK(5, 7, 0)
+#include "../vganalytics/vganalytics.h"
#include "layout/vppiece.h"
#include "vptilefactory.h"
@@ -389,7 +391,7 @@ VPMainWindow::VPMainWindow(const VPCommandLinePtr &cmd, QWidget *parent)
if (m_cmd->IsGuiEnabled())
{
- QTimer::singleShot(V_SECONDS(1), this, &VPMainWindow::SetDefaultGUILanguage);
+ QTimer::singleShot(V_SECONDS(1), this, &VPMainWindow::AskDefaultSettings);
}
}
@@ -4536,7 +4538,7 @@ void VPMainWindow::RemoveWatermark()
}
//---------------------------------------------------------------------------------------------------------------------
-void VPMainWindow::SetDefaultGUILanguage()
+void VPMainWindow::AskDefaultSettings()
{
if (m_cmd->IsGuiEnabled())
{
@@ -4554,6 +4556,38 @@ void VPMainWindow::SetDefaultGUILanguage()
VAbstractApplication::VApp()->LoadTranslation(locale);
}
}
+
+ if (settings->IsAskCollectStatistic())
+ {
+ DialogAskCollectStatistic dialog(this);
+ if (dialog.exec() == QDialog::Accepted)
+ {
+ settings->SetCollectStatistic(dialog.CollectStatistic());
+ }
+
+ settings->SetAskCollectStatistic(false);
+ }
+
+ if (settings->IsCollectStatistic())
+ {
+ auto *statistic = VGAnalytics::Instance();
+ statistic->SetGUILanguage(settings->GetLocale());
+
+ QString clientID = settings->GetClientID();
+ bool freshID = false;
+ if (clientID.isEmpty())
+ {
+ clientID = QUuid::createUuid().toString();
+ settings->SetClientID(clientID);
+ statistic->SetClientID(clientID);
+ freshID = true;
+ }
+
+ statistic->Enable(true);
+
+ const qint64 uptime = VPApplication::VApp()->AppUptime();
+ freshID ? statistic->SendAppFreshInstallEvent(uptime) : statistic->SendAppStartEvent(uptime);
+ }
}
}
diff --git a/src/app/puzzle/vpmainwindow.h b/src/app/puzzle/vpmainwindow.h
index e00c71d57..87dcde859 100644
--- a/src/app/puzzle/vpmainwindow.h
+++ b/src/app/puzzle/vpmainwindow.h
@@ -286,7 +286,7 @@ private slots:
void AboutToShowDockMenu();
#endif //defined(Q_OS_MAC)
- void SetDefaultGUILanguage();
+ void AskDefaultSettings();
void HorizontalScaleChanged(double value);
void VerticalScaleChanged(double value);
diff --git a/src/app/tape/dialogs/configpages/tapepreferencesconfigurationpage.cpp b/src/app/tape/dialogs/configpages/tapepreferencesconfigurationpage.cpp
index a4140594d..428470ade 100644
--- a/src/app/tape/dialogs/configpages/tapepreferencesconfigurationpage.cpp
+++ b/src/app/tape/dialogs/configpages/tapepreferencesconfigurationpage.cpp
@@ -35,6 +35,7 @@
#include "../vmisc/backport/qoverload.h"
#endif // QT_VERSION < QT_VERSION_CHECK(5, 7, 0)
#include "../qmuparser/qmudef.h"
+#include "../vganalytics/vganalytics.h"
//---------------------------------------------------------------------------------------------------------------------
TapePreferencesConfigurationPage::TapePreferencesConfigurationPage(QWidget *parent)
@@ -53,13 +54,13 @@ TapePreferencesConfigurationPage::TapePreferencesConfigurationPage(QWidget *pare
VTapeSettings *settings = MApplication::VApp()->TapeSettings();
//-------------------- Decimal separator setup
- ui->osOptionCheck->setChecked(MApplication::VApp()->TapeSettings()->GetOsSeparator());
+ ui->osOptionCheck->setChecked(settings->GetOsSeparator());
// Theme
- ui->darkModeCheck->setChecked(MApplication::VApp()->TapeSettings()->GetDarkMode());
+ ui->darkModeCheck->setChecked(settings->GetDarkMode());
// Native dialogs
- ui->checkBoxDontUseNativeDialog->setChecked(MApplication::VApp()->TapeSettings()->IsDontUseNativeDialog());
+ ui->checkBoxDontUseNativeDialog->setChecked(settings->IsDontUseNativeDialog());
//---------------------- Pattern making system
ui->systemBookValueLabel->setFixedHeight(4 * QFontMetrics(ui->systemBookValueLabel->font()).lineSpacing());
@@ -77,7 +78,7 @@ TapePreferencesConfigurationPage::TapePreferencesConfigurationPage(QWidget *pare
});
// set default pattern making system
- int index = ui->systemCombo->findData(MApplication::VApp()->TapeSettings()->GetPMSystemCode());
+ int index = ui->systemCombo->findData(settings->GetPMSystemCode());
if (index != -1)
{
ui->systemCombo->setCurrentIndex(index);
@@ -88,11 +89,13 @@ TapePreferencesConfigurationPage::TapePreferencesConfigurationPage(QWidget *pare
[]() { MApplication::VApp()->TapeSettings()->SetConfirmFormatRewriting(true); });
//----------------------- Toolbar
- ui->toolBarStyleCheck->setChecked(MApplication::VApp()->TapeSettings()->GetToolBarStyle());
+ ui->toolBarStyleCheck->setChecked(settings->GetToolBarStyle());
//----------------------- Update
- ui->checkBoxAutomaticallyCheckUpdates->setChecked(
- MApplication::VApp()->TapeSettings()->IsAutomaticallyCheckUpdates());
+ ui->checkBoxAutomaticallyCheckUpdates->setChecked(settings->IsAutomaticallyCheckUpdates());
+
+ // Tab Privacy
+ ui->checkBoxSendUsageStatistics->setChecked(settings->IsCollectStatistic());
}
//---------------------------------------------------------------------------------------------------------------------
@@ -144,6 +147,10 @@ auto TapePreferencesConfigurationPage::Apply() -> QStringList
settings->SetAutomaticallyCheckUpdates(ui->checkBoxAutomaticallyCheckUpdates->isChecked());
}
+ // Tab Privacy
+ settings->SetCollectStatistic(ui->checkBoxSendUsageStatistics->isChecked());
+ VGAnalytics::Instance()->Enable(ui->checkBoxSendUsageStatistics->isChecked());
+
return preferences;
}
diff --git a/src/app/tape/dialogs/configpages/tapepreferencesconfigurationpage.ui b/src/app/tape/dialogs/configpages/tapepreferencesconfigurationpage.ui
index 8747455c0..272d598d6 100644
--- a/src/app/tape/dialogs/configpages/tapepreferencesconfigurationpage.ui
+++ b/src/app/tape/dialogs/configpages/tapepreferencesconfigurationpage.ui
@@ -6,209 +6,260 @@
0
0
- 544
- 636
+ 564
+ 686
Configuration
-
+
-
-
-
- true
+
+
+ 0
-
-
-
- 0
- 0
- 524
- 616
-
-
-
+
+
+ General
+
+
-
-
-
- Language
+
+
+ true
-
-
- QFormLayout::ExpandingFieldsGrow
+
+
+
+ 0
+ 0
+ 522
+ 617
+
-
-
-
-
- GUI language:
-
-
-
- -
-
-
- -
-
-
- Decimal separator parts:
-
-
-
- -
-
-
- < With OS options >
-
-
- true
-
-
-
-
+
+ -
+
+
+ Language
+
+
+
+ QFormLayout::ExpandingFieldsGrow
+
+
-
+
+
+ GUI language:
+
+
+
+ -
+
+
+ -
+
+
+ Decimal separator parts:
+
+
+
+ -
+
+
+ < With OS options >
+
+
+ true
+
+
+
+
+
+
+ -
+
+
+ Pattern making system
+
+
+
+ QFormLayout::ExpandingFieldsGrow
+
+
-
+
+
+ Pattern making system:
+
+
+
+ -
+
+
+ -
+
+
+ Author:
+
+
+
+ -
+
+
+ author
+
+
+
+ -
+
+
+ Book:
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ true
+
+
+
+
+
+
+ -
+
+
+ Measurements editing
+
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ Reset warnings
+
+
+
+
+
+
+ -
+
+
+ Toolbar
+
+
+
-
+
+
+ The text appears under the icon (recommended for beginners).
+
+
+ true
+
+
+
+
+
+
+ -
+
+
+ User Interface
+
+
+
-
+
+
+ Activate dark mode
+
+
+
+ -
+
+
+ Don't use the native file dialog
+
+
+
+
+
+
+ -
+
+
+ Update
+
+
+
-
+
+
+ Automatically check for updates each time the application starts
+
+
+ Automatically check for updates
+
+
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Privacy
+
+
+ -
+
+
+ Send usage statistics
+
-
-
-
- Pattern making system
+
+
+ Please help to improve Valentina's quality by automatically sending usage statistics. Sent data contains <span style=" font-weight:700;">no potentially sensitive information</span> like user names, email addresses, file contents or file paths.
+
+
+ true
-
-
- QFormLayout::ExpandingFieldsGrow
-
-
-
-
-
- Pattern making system:
-
-
-
- -
-
-
- -
-
-
- Author:
-
-
-
- -
-
-
- author
-
-
-
- -
-
-
- Book:
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- true
-
-
-
-
-
-
-
- Measurements editing
+
+
+ Qt::Vertical
-
-
-
-
-
-
- 0
- 0
-
-
-
- Reset warnings
-
-
-
-
-
-
- -
-
-
- Toolbar
+
+
+ 20
+ 530
+
-
-
-
-
-
- The text appears under the icon (recommended for beginners).
-
-
- true
-
-
-
-
-
-
- -
-
-
- User Interface
-
-
-
-
-
-
- Activate dark mode
-
-
-
- -
-
-
- Don't use the native file dialog
-
-
-
-
-
-
- -
-
-
- Update
-
-
-
-
-
-
- Automatically check for updates each time the application starts
-
-
- Automatically check for updates
-
-
- true
-
-
-
-
-
+
diff --git a/src/app/tape/mapplication.cpp b/src/app/tape/mapplication.cpp
index 2d879f391..8d0e1f0c5 100644
--- a/src/app/tape/mapplication.cpp
+++ b/src/app/tape/mapplication.cpp
@@ -32,6 +32,8 @@
#include "../ifc/exception/vexceptionemptyparameter.h"
#include "../ifc/exception/vexceptionobjecterror.h"
#include "../ifc/exception/vexceptionwrongid.h"
+#include "../vganalytics/def.h"
+#include "../vganalytics/vganalytics.h"
#include "../vmisc/projectversion.h"
#include "../vmisc/vsysexits.h"
#include "tmainwindow.h"
@@ -77,6 +79,7 @@ Q_LOGGING_CATEGORY(mApp, "m.application") // NOLINT
QT_WARNING_POP
#include
+#include
namespace
{
@@ -318,6 +321,17 @@ MApplication::MApplication(int &argc, char **argv)
//---------------------------------------------------------------------------------------------------------------------
MApplication::~MApplication()
{
+ if (settings->IsCollectStatistic())
+ {
+ auto *statistic = VGAnalytics::Instance();
+
+ QString clientID = settings->GetClientID();
+ if (!clientID.isEmpty())
+ {
+ statistic->SendAppCloseEvent(m_uptimeTimer.elapsed());
+ }
+ }
+
qDeleteAll(m_mainWindows);
delete m_trVars;
@@ -473,6 +487,19 @@ void MApplication::InitOptions()
}
ActivateDarkMode();
QResource::registerResource(diagramsPath());
+
+ auto *statistic = VGAnalytics::Instance();
+ QString clientID = settings->GetClientID();
+ if (clientID.isEmpty())
+ {
+ clientID = QUuid::createUuid().toString();
+ settings->SetClientID(clientID);
+ }
+ statistic->SetClientID(clientID);
+ statistic->SetGUILanguage(settings->GetLocale());
+ statistic->SetMeasurementId(GA_MEASUREMENT_ID);
+ statistic->SetApiSecret(GA_API_SECRET);
+ statistic->Enable(settings->IsCollectStatistic());
}
//---------------------------------------------------------------------------------------------------------------------
diff --git a/src/app/tape/tape.pro b/src/app/tape/tape.pro
index 0bc474357..5670b90fc 100644
--- a/src/app/tape/tape.pro
+++ b/src/app/tape/tape.pro
@@ -408,6 +408,15 @@ DEPENDPATH += $$PWD/../../libs/vmisc
win32:!win32-g++: PRE_TARGETDEPS += $$OUT_PWD/../../libs/vmisc/$${DESTDIR}/vmisc.lib
else:unix|win32-g++: PRE_TARGETDEPS += $$OUT_PWD/../../libs/vmisc/$${DESTDIR}/libvmisc.a
+# VGAnalytics static library
+unix|win32: LIBS += -L$$OUT_PWD/../../libs/vganalytics/$${DESTDIR}/ -lvganalytics
+
+INCLUDEPATH += $$PWD/../../libs/vganalytics
+DEPENDPATH += $$PWD/../../libs/vganalytics
+
+win32:!win32-g++: PRE_TARGETDEPS += $$OUT_PWD/../../libs/vganalytics/$${DESTDIR}/vganalytics.lib
+else:unix|win32-g++: PRE_TARGETDEPS += $$OUT_PWD/../../libs/vganalytics/$${DESTDIR}/libvganalytics.a
+
# QMuParser library
win32:CONFIG(release, debug|release): LIBS += -L$${OUT_PWD}/../../libs/qmuparser/$${DESTDIR} -lqmuparser2
else:win32:CONFIG(debug, debug|release): LIBS += -L$${OUT_PWD}/../../libs/qmuparser/$${DESTDIR} -lqmuparser2
diff --git a/src/app/tape/tape.qbs b/src/app/tape/tape.qbs
index b57540157..eb5403b01 100644
--- a/src/app/tape/tape.qbs
+++ b/src/app/tape/tape.qbs
@@ -14,6 +14,7 @@ VToolApp {
Depends { name: "VToolsLib"; }
Depends { name: "ebr" }
Depends { name: "multibundle"; }
+ Depends { name: "VGAnalyticsLib" }
Depends { name: "conan.XercesC"; condition: buildconfig.useConanPackages }
name: "Tape"
diff --git a/src/app/tape/tmainwindow.cpp b/src/app/tape/tmainwindow.cpp
index 3b8cb0c51..82fa749ac 100644
--- a/src/app/tape/tmainwindow.cpp
+++ b/src/app/tape/tmainwindow.cpp
@@ -32,6 +32,7 @@
#include "../ifc/xml/vvitconverter.h"
#include "../ifc/xml/vvstconverter.h"
#include "../vmisc/compatibility.h"
+#include "../vmisc/dialogs/dialogaskcollectstatistic.h"
#include "../vmisc/dialogs/dialogexporttocsv.h"
#include "../vmisc/qxtcsvmodel.h"
#include "../vmisc/vsysexits.h"
@@ -56,6 +57,7 @@
#include "../vmisc/backport/qoverload.h"
#endif // QT_VERSION < QT_VERSION_CHECK(5, 7, 0)
#include "../qmuparser/qmudef.h"
+#include "../vganalytics/vganalytics.h"
#include "../vmisc/dialogs/dialogselectlanguage.h"
#include "../vtools/dialogs/support/dialogeditwrongformula.h"
#include "mapplication.h" // Should be last because of definning qApp
@@ -304,7 +306,7 @@ TMainWindow::TMainWindow(QWidget *parent)
if (MApplication::VApp()->IsAppInGUIMode())
{
- QTimer::singleShot(V_SECONDS(1), this, &TMainWindow::SetDefaultGUILanguage);
+ QTimer::singleShot(V_SECONDS(1), this, &TMainWindow::AskDefaultSettings);
}
}
@@ -2534,7 +2536,7 @@ void TMainWindow::DimensionCustomNames()
}
//---------------------------------------------------------------------------------------------------------------------
-void TMainWindow::SetDefaultGUILanguage()
+void TMainWindow::AskDefaultSettings()
{
if (MApplication::VApp()->IsAppInGUIMode())
{
@@ -2552,6 +2554,38 @@ void TMainWindow::SetDefaultGUILanguage()
VAbstractApplication::VApp()->LoadTranslation(locale);
}
}
+
+ if (settings->IsAskCollectStatistic())
+ {
+ DialogAskCollectStatistic dialog(this);
+ if (dialog.exec() == QDialog::Accepted)
+ {
+ settings->SetCollectStatistic(dialog.CollectStatistic());
+ }
+
+ settings->SetAskCollectStatistic(false);
+ }
+
+ if (settings->IsCollectStatistic())
+ {
+ auto *statistic = VGAnalytics::Instance();
+ statistic->SetGUILanguage(settings->GetLocale());
+
+ QString clientID = settings->GetClientID();
+ bool freshID = false;
+ if (clientID.isEmpty())
+ {
+ clientID = QUuid::createUuid().toString();
+ settings->SetClientID(clientID);
+ statistic->SetClientID(clientID);
+ freshID = true;
+ }
+
+ statistic->Enable(true);
+
+ const qint64 uptime = MApplication::VApp()->AppUptime();
+ freshID ? statistic->SendAppFreshInstallEvent(uptime) : statistic->SendAppStartEvent(uptime);
+ }
}
}
diff --git a/src/app/tape/tmainwindow.h b/src/app/tape/tmainwindow.h
index b14b6e952..d0b930362 100644
--- a/src/app/tape/tmainwindow.h
+++ b/src/app/tape/tmainwindow.h
@@ -146,7 +146,7 @@ private slots:
void EditDimensionLabels();
void DimensionCustomNames();
- void SetDefaultGUILanguage();
+ void AskDefaultSettings();
private:
// cppcheck-suppress unknownMacro
diff --git a/src/app/valentina/core/vapplication.cpp b/src/app/valentina/core/vapplication.cpp
index 6e07238cb..0d47d0574 100644
--- a/src/app/valentina/core/vapplication.cpp
+++ b/src/app/valentina/core/vapplication.cpp
@@ -36,10 +36,11 @@
#include "../mainwindow.h"
#include "../qmuparser/qmuparsererror.h"
#include "../version.h"
+#include "../vganalytics/def.h"
+#include "../vganalytics/vganalytics.h"
#include "../vmisc/qt_dispatch/qt_dispatch.h"
#include "../vmisc/vsysexits.h"
#include "../vmisc/vvalentinasettings.h"
-#include
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
#include "../vmisc/backport/text.h"
@@ -383,6 +384,18 @@ VApplication::VApplication(int &argc, char **argv)
VApplication::~VApplication()
{
qCDebug(vApp, "Application closing.");
+
+ if (settings->IsCollectStatistic())
+ {
+ auto *statistic = VGAnalytics::Instance();
+
+ QString clientID = settings->GetClientID();
+ if (!clientID.isEmpty())
+ {
+ statistic->SendAppCloseEvent(m_uptimeTimer.elapsed());
+ }
+ }
+
qInstallMessageHandler(nullptr); // Resore the message handler
delete m_trVars;
VCommandLine::Reset();
@@ -695,6 +708,19 @@ void VApplication::InitOptions()
QIcon::setThemeName(QStringLiteral("win.icon.theme"));
}
ActivateDarkMode();
+
+ auto *statistic = VGAnalytics::Instance();
+ QString clientID = settings->GetClientID();
+ if (clientID.isEmpty())
+ {
+ clientID = QUuid::createUuid().toString();
+ settings->SetClientID(clientID);
+ }
+ statistic->SetClientID(clientID);
+ statistic->SetGUILanguage(settings->GetLocale());
+ statistic->SetMeasurementId(GA_MEASUREMENT_ID);
+ statistic->SetApiSecret(GA_API_SECRET);
+ statistic->Enable(settings->IsCollectStatistic());
}
//---------------------------------------------------------------------------------------------------------------------
diff --git a/src/app/valentina/dialogs/configpages/preferencesconfigurationpage.cpp b/src/app/valentina/dialogs/configpages/preferencesconfigurationpage.cpp
index 38940cf09..2114efd39 100644
--- a/src/app/valentina/dialogs/configpages/preferencesconfigurationpage.cpp
+++ b/src/app/valentina/dialogs/configpages/preferencesconfigurationpage.cpp
@@ -31,12 +31,12 @@
#include "../vmisc/literals.h"
#include "../vmisc/vvalentinasettings.h"
#include "../vpatterndb/pmsystems.h"
-#include "def.h"
#include "ui_preferencesconfigurationpage.h"
#if QT_VERSION < QT_VERSION_CHECK(5, 7, 0)
#include "../vmisc/backport/qoverload.h"
#endif // QT_VERSION < QT_VERSION_CHECK(5, 7, 0)
#include "../qmuparser/qmudef.h"
+#include "../vganalytics/vganalytics.h"
#include
#include
@@ -162,6 +162,9 @@ PreferencesConfigurationPage::PreferencesConfigurationPage(QWidget *parent)
ui->doubleSpinBoxAcceleration->setMinimum(VCommonSettings::scrollingAccelerationMin);
ui->doubleSpinBoxAcceleration->setMaximum(VCommonSettings::scrollingAccelerationMax);
ui->doubleSpinBoxAcceleration->setValue(settings->GetScrollingAcceleration());
+
+ // Tab Privacy
+ ui->checkBoxSendUsageStatistics->setChecked(settings->IsCollectStatistic());
}
//---------------------------------------------------------------------------------------------------------------------
@@ -254,6 +257,10 @@ auto PreferencesConfigurationPage::Apply() -> QStringList
settings->SetWheelMouseScale(ui->doubleSpinBoxWheel->value());
settings->SetScrollingAcceleration(ui->doubleSpinBoxAcceleration->value());
+ // Tab Privacy
+ settings->SetCollectStatistic(ui->checkBoxSendUsageStatistics->isChecked());
+ VGAnalytics::Instance()->Enable(ui->checkBoxSendUsageStatistics->isChecked());
+
return preferences;
}
diff --git a/src/app/valentina/dialogs/configpages/preferencesconfigurationpage.ui b/src/app/valentina/dialogs/configpages/preferencesconfigurationpage.ui
index 0ae06b7f4..c2b063fcc 100644
--- a/src/app/valentina/dialogs/configpages/preferencesconfigurationpage.ui
+++ b/src/app/valentina/dialogs/configpages/preferencesconfigurationpage.ui
@@ -527,6 +527,43 @@
+
+
+ Privacy
+
+
+ -
+
+
+ Send usage statistics
+
+
+
+ -
+
+
+ Please help to improve Valentina's quality by automatically sending usage statistics. Sent data contains <span style=" font-weight:700;">no potentially sensitive information</span> like user names, email addresses, file contents or file paths.
+
+
+ true
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 716
+
+
+
+
+
+
diff --git a/src/app/valentina/mainwindow.cpp b/src/app/valentina/mainwindow.cpp
index 627e05426..f2b419c04 100644
--- a/src/app/valentina/mainwindow.cpp
+++ b/src/app/valentina/mainwindow.cpp
@@ -38,9 +38,11 @@
#include "../ifc/xml/vvstconverter.h"
#include "../vformat/vmeasurements.h"
#include "../vformat/vpatternrecipe.h"
+#include "../vganalytics/vganalytics.h"
#include "../vlayout/dialogs/watermarkwindow.h"
#include "../vmisc/customevents.h"
#include "../vmisc/def.h"
+#include "../vmisc/dialogs/dialogaskcollectstatistic.h"
#include "../vmisc/qxtcsvmodel.h"
#include "../vmisc/vmodifierkey.h"
#include "../vmisc/vsysexits.h"
@@ -505,7 +507,7 @@ MainWindow::MainWindow(QWidget *parent)
if (VApplication::IsGUIMode())
{
- QTimer::singleShot(V_SECONDS(1), this, &MainWindow::SetDefaultGUILanguage);
+ QTimer::singleShot(V_SECONDS(1), this, &MainWindow::AskDefaultSettings);
}
ui->actionExportFontCorrections->setEnabled(settings->GetSingleStrokeOutlineFont());
@@ -4548,7 +4550,7 @@ void MainWindow::ClearPatternMessages()
}
//---------------------------------------------------------------------------------------------------------------------
-void MainWindow::SetDefaultGUILanguage()
+void MainWindow::AskDefaultSettings()
{
if (VApplication::IsGUIMode())
{
@@ -4565,6 +4567,38 @@ void MainWindow::SetDefaultGUILanguage()
VAbstractApplication::VApp()->LoadTranslation(locale);
}
}
+
+ if (settings->IsAskCollectStatistic())
+ {
+ DialogAskCollectStatistic dialog(this);
+ if (dialog.exec() == QDialog::Accepted)
+ {
+ settings->SetCollectStatistic(dialog.CollectStatistic());
+ }
+
+ settings->SetAskCollectStatistic(false);
+ }
+
+ if (settings->IsCollectStatistic())
+ {
+ auto *statistic = VGAnalytics::Instance();
+ statistic->SetGUILanguage(settings->GetLocale());
+
+ QString clientID = settings->GetClientID();
+ bool freshID = false;
+ if (clientID.isEmpty())
+ {
+ clientID = QUuid::createUuid().toString();
+ settings->SetClientID(clientID);
+ statistic->SetClientID(clientID);
+ freshID = true;
+ }
+
+ statistic->Enable(true);
+
+ const qint64 uptime = VAbstractValApplication::VApp()->AppUptime();
+ freshID ? statistic->SendAppFreshInstallEvent(uptime) : statistic->SendAppStartEvent(uptime);
+ }
}
}
diff --git a/src/app/valentina/mainwindow.h b/src/app/valentina/mainwindow.h
index de3fc6ad7..00fc3024b 100644
--- a/src/app/valentina/mainwindow.h
+++ b/src/app/valentina/mainwindow.h
@@ -237,7 +237,7 @@ private slots:
void ShowProgress();
void ClearPatternMessages();
- void SetDefaultGUILanguage();
+ void AskDefaultSettings();
void AddBackgroundImageItem(const QUuid &id);
void DeleteBackgroundImageItem(const QUuid &id);
diff --git a/src/app/valentina/valentina.pro b/src/app/valentina/valentina.pro
index 4fb4b8805..9a435e6ac 100644
--- a/src/app/valentina/valentina.pro
+++ b/src/app/valentina/valentina.pro
@@ -698,6 +698,15 @@ DEPENDPATH += $$PWD/../../libs/vdxf
win32:!win32-g++: PRE_TARGETDEPS += $$OUT_PWD/../../libs/vdxf/$${DESTDIR}/vdxf.lib
else:unix|win32-g++: PRE_TARGETDEPS += $$OUT_PWD/../../libs/vdxf/$${DESTDIR}/libvdxf.a
+# VGAnalytics static library
+unix|win32: LIBS += -L$$OUT_PWD/../../libs/vganalytics/$${DESTDIR}/ -lvganalytics
+
+INCLUDEPATH += $$PWD/../../libs/vganalytics
+DEPENDPATH += $$PWD/../../libs/vganalytics
+
+win32:!win32-g++: PRE_TARGETDEPS += $$OUT_PWD/../../libs/vganalytics/$${DESTDIR}/vganalytics.lib
+else:unix|win32-g++: PRE_TARGETDEPS += $$OUT_PWD/../../libs/vganalytics/$${DESTDIR}/libvganalytics.a
+
# QMuParser library
win32:CONFIG(release, debug|release): LIBS += -L$${OUT_PWD}/../../libs/qmuparser/$${DESTDIR} -lqmuparser2
else:win32:CONFIG(debug, debug|release): LIBS += -L$${OUT_PWD}/../../libs/qmuparser/$${DESTDIR} -lqmuparser2
diff --git a/src/app/valentina/valentina.qbs b/src/app/valentina/valentina.qbs
index 1c5ab6a2b..5c000376c 100644
--- a/src/app/valentina/valentina.qbs
+++ b/src/app/valentina/valentina.qbs
@@ -14,6 +14,7 @@ VToolApp {
Depends { name: "VToolsLib"; }
Depends { name: "VFormatLib"; }
Depends { name: "VMiscLib"; }
+ Depends { name: "VGAnalyticsLib" }
Depends {
name: "Qt.xmlpatterns"
diff --git a/src/libs/libs.pri b/src/libs/libs.pri
index 322920d57..46a291726 100644
--- a/src/libs/libs.pri
+++ b/src/libs/libs.pri
@@ -33,3 +33,6 @@ INCLUDEPATH += $${PWD}/vpropertyexplorer
#VTools static library
INCLUDEPATH += $${PWD}/vtest
+
+#VGAnalytics static library
+INCLUDEPATH += $${PWD}/vganalytics
diff --git a/src/libs/libs.pro b/src/libs/libs.pro
index 84cbd2f09..aad3bde72 100644
--- a/src/libs/libs.pro
+++ b/src/libs/libs.pro
@@ -13,4 +13,5 @@ SUBDIRS = \
vtools \
vformat \
fervor \
- vtest
+ vtest \
+ vganalytics
diff --git a/src/libs/libs.qbs b/src/libs/libs.qbs
index c9d277dff..a15fba06a 100644
--- a/src/libs/libs.qbs
+++ b/src/libs/libs.qbs
@@ -14,5 +14,6 @@ Project {
"vtest/vtest.qbs",
"vtools/vtools.qbs",
"vwidgets/vwidgets.qbs",
+ "vganalytics/vganalytics.qbs",
]
}
diff --git a/src/libs/vganalytics/def.h b/src/libs/vganalytics/def.h
new file mode 100644
index 000000000..7f8aba7aa
--- /dev/null
+++ b/src/libs/vganalytics/def.h
@@ -0,0 +1,36 @@
+/************************************************************************
+ **
+ ** @file def.h
+ ** @author Roman Telezhynskyi
+ ** @date 26 6, 2023
+ **
+ ** @brief
+ ** @copyright
+ ** This source code is part of the Valentina project, a pattern making
+ ** program, whose allow create and modeling patterns of clothing.
+ ** Copyright (C) 2023 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 .
+ **
+ *************************************************************************/
+#ifndef GADEF_H
+#define GADEF_H
+
+#include
+
+const QString GA_MEASUREMENT_ID = QStringLiteral("G-KH8CCG06VJ"); // NOLINT(clazy-non-pod-global-static)
+const QString GA_API_SECRET = QStringLiteral("vnXM9BlCQXC9nvaXid_lAA"); // NOLINT(clazy-non-pod-global-static)
+
+#endif // GADEF_H
diff --git a/src/libs/vganalytics/vganalytics.cpp b/src/libs/vganalytics/vganalytics.cpp
new file mode 100644
index 000000000..64ea6b2d7
--- /dev/null
+++ b/src/libs/vganalytics/vganalytics.cpp
@@ -0,0 +1,306 @@
+/************************************************************************
+ **
+ ** @file vganalytics.cpp
+ ** @author Roman Telezhynskyi
+ ** @date 26 6, 2023
+ **
+ ** @brief
+ ** @copyright
+ ** This source code is part of the Valentina project, a pattern making
+ ** program, whose allow create and modeling patterns of clothing.
+ ** Copyright (C) 2023 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 "vganalytics.h"
+#include "qguiapplication.h"
+#include "qstringliteral.h"
+#include "vganalyticsworker.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+//---------------------------------------------------------------------------------------------------------------------
+VGAnalytics::VGAnalytics(QObject *parent)
+ : QObject{parent},
+ d(new VGAnalyticsWorker(this))
+{
+}
+
+//---------------------------------------------------------------------------------------------------------------------
+VGAnalytics::~VGAnalytics()
+{
+ delete d;
+}
+
+//---------------------------------------------------------------------------------------------------------------------
+auto VGAnalytics::Instance() -> VGAnalytics *
+{
+ static VGAnalytics *instance = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
+ if (instance == nullptr)
+ {
+ instance = new VGAnalytics();
+ }
+
+ return instance;
+}
+
+//---------------------------------------------------------------------------------------------------------------------
+void VGAnalytics::SetLogLevel(enum VGAnalytics::LogLevel logLevel)
+{
+ d->m_logLevel = logLevel;
+}
+
+//---------------------------------------------------------------------------------------------------------------------
+auto VGAnalytics::LogLevel() const -> enum VGAnalytics::LogLevel { return d->m_logLevel; }
+
+//---------------------------------------------------------------------------------------------------------------------
+void VGAnalytics::SetRepoRevision(const QString &rev)
+{
+ d->m_repoRevision = rev;
+}
+
+//---------------------------------------------------------------------------------------------------------------------
+auto VGAnalytics::RepoRevision() const -> QString
+{
+ return d->m_repoRevision;
+}
+
+//---------------------------------------------------------------------------------------------------------------------
+void VGAnalytics::SetGUILanguage(const QString &language)
+{
+ d->m_guiLanguage = language.toLower().replace(QChar('_'), QChar('-'));
+}
+
+//---------------------------------------------------------------------------------------------------------------------
+auto VGAnalytics::GUILanguage() const -> QString
+{
+ return d->m_guiLanguage;
+}
+
+//---------------------------------------------------------------------------------------------------------------------
+void VGAnalytics::SetSendInterval(int milliseconds)
+{
+ d->m_timer.setInterval(milliseconds);
+}
+
+//---------------------------------------------------------------------------------------------------------------------
+auto VGAnalytics::SendInterval() const -> int
+{
+ return (d->m_timer.interval());
+}
+
+//---------------------------------------------------------------------------------------------------------------------
+auto VGAnalytics::IsEnabled() -> bool
+{
+ return d->m_isEnabled;
+}
+
+//---------------------------------------------------------------------------------------------------------------------
+void VGAnalytics::SetMeasurementId(const QString &measurementId)
+{
+ d->m_measurementId = measurementId;
+}
+
+//---------------------------------------------------------------------------------------------------------------------
+void VGAnalytics::SetApiSecret(const QString &apiSecret)
+{
+ d->m_apiSecret = apiSecret;
+}
+
+//---------------------------------------------------------------------------------------------------------------------
+void VGAnalytics::SetClientID(const QString &clientID)
+{
+ d->m_clientID = clientID;
+}
+
+//---------------------------------------------------------------------------------------------------------------------
+void VGAnalytics::Enable(bool state)
+{
+ d->Enable(state);
+}
+
+//---------------------------------------------------------------------------------------------------------------------
+void VGAnalytics::SetNetworkAccessManager(QNetworkAccessManager *networkAccessManager)
+{
+ if (d->networkManager != networkAccessManager)
+ {
+ // Delete the old network manager if it was our child
+ if (d->networkManager && d->networkManager->parent() == this)
+ {
+ d->networkManager->deleteLater();
+ }
+
+ d->networkManager = networkAccessManager;
+ }
+}
+
+//---------------------------------------------------------------------------------------------------------------------
+auto VGAnalytics::NetworkAccessManager() const -> QNetworkAccessManager *
+{
+ return d->networkManager;
+}
+
+//---------------------------------------------------------------------------------------------------------------------
+void VGAnalytics::SendAppFreshInstallEvent(qint64 engagementTimeMsec)
+{
+ SendEvent(QStringLiteral("vapp_fresh_install"), InitAppStartEventParams(engagementTimeMsec));
+}
+
+//---------------------------------------------------------------------------------------------------------------------
+void VGAnalytics::SendAppStartEvent(qint64 engagementTimeMsec)
+{
+ SendEvent(QStringLiteral("vapp_start"), InitAppStartEventParams(engagementTimeMsec));
+}
+
+//---------------------------------------------------------------------------------------------------------------------
+void VGAnalytics::SendAppCloseEvent(qint64 engagementTimeMsec)
+{
+ QHash params{
+ // {QStringLiteral("category"), ""},
+ // In order for user activity to display in standard reports like Realtime, engagement_time_msec and session_id
+ // must be supplied as part of the params for an event.
+ // https://developers.google.com/analytics/devguides/collection/protocol/ga4/sending-events?client_type=gtag#optional_parameters_for_reports
+ {QStringLiteral("engagement_time_msec"), engagementTimeMsec},
+ };
+
+ SendEvent(QStringLiteral("vapp_close"), params);
+ QNetworkReply *reply = d->PostMessage();
+ if (reply)
+ {
+ QTimer timer;
+ const int timeoutSeconds = 3; // Wait for 3 seconds
+ timer.setSingleShot(true);
+ timer.start(timeoutSeconds * 1000);
+
+ QEventLoop loop;
+ connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit);
+ QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
+ loop.exec(); // wait for finished
+ }
+}
+
+//---------------------------------------------------------------------------------------------------------------------
+/**
+ * A query for a POST message will be created to report this event. The
+ * created query will be stored in a message queue.
+ */
+void VGAnalytics::SendEvent(const QString &eventName, const QHash ¶ms)
+{
+ /*
+ // event body example
+ {
+ "client_id": "XXXXXXXXXX.YYYYYYYYYY",
+ "events": [{
+ "name": "refund",
+ "params": {
+ "currency": "USD",
+ "value": "9.99",
+ "transaction_id": "ABC-123"
+ }
+ }]
+ }
+ */
+
+ QJsonObject root;
+ root[QStringLiteral("client_id")] = d->m_clientID;
+
+ QJsonObject event;
+ event[QStringLiteral("name")] = eventName;
+
+ QJsonObject eventParams;
+
+ auto i = params.constBegin();
+ while (i != params.constEnd())
+ {
+ eventParams[i.key()] = i.value();
+ ++i;
+ }
+
+ // In order for user activity to display in standard reports like Realtime, engagement_time_msec and session_id must
+ // be supplied as part of the params for an event.
+ // https://developers.google.com/analytics/devguides/collection/protocol/ga4/sending-events?client_type=gtag#optional_parameters_for_reports
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-static-cast-downcast)
+ eventParams[QStringLiteral("session_id")] = qApp->sessionId();
+ event[QStringLiteral("params")] = eventParams;
+
+ QJsonArray eventArray;
+ eventArray.append(event);
+ root[QStringLiteral("events")] = eventArray;
+
+ d->EnqueQueryWithCurrentTime(root);
+}
+
+//---------------------------------------------------------------------------------------------------------------------
+auto VGAnalytics::InitAppStartEventParams(qint64 engagementTimeMsec) const -> QHash
+{
+
+ QHash params{
+ // {QStringLiteral("category"), ""},
+ {QStringLiteral("qt_version"), QT_VERSION_STR},
+ {QStringLiteral("app_version"), d->m_appVersion},
+ {QStringLiteral("word_size"), QStringLiteral("%1 bit").arg(QSysInfo::WordSize)},
+ {QStringLiteral("cpu_architecture"), QSysInfo::currentCpuArchitecture()},
+ {QStringLiteral("revision"), d->m_repoRevision},
+ {QStringLiteral("os_version"), QSysInfo::prettyProductName()},
+ {QStringLiteral("screen_size"), d->m_screenResolution},
+ {QStringLiteral("screen_scale_factor"), d->m_screenScaleFactor},
+ // In order for user activity to display in standard reports like Realtime, engagement_time_msec and session_id
+ // must be supplied as part of the params for an event.
+ // https://developers.google.com/analytics/devguides/collection/protocol/ga4/sending-events?client_type=gtag#optional_parameters_for_reports
+ {QStringLiteral("engagement_time_msec"), engagementTimeMsec},
+ {QStringLiteral("gui_language"), d->m_guiLanguage},
+ };
+ return params;
+}
+
+//---------------------------------------------------------------------------------------------------------------------
+/**
+ * Qut stream to persist class GAnalytics.
+ */
+auto operator<<(QDataStream &outStream, const VGAnalytics &analytics) -> QDataStream &
+{
+ outStream << analytics.d->PersistMessageQueue();
+
+ return outStream;
+}
+
+//---------------------------------------------------------------------------------------------------------------------
+/**
+ * In stream to read GAnalytics from file.
+ */
+auto operator>>(QDataStream &inStream, VGAnalytics &analytics) -> QDataStream &
+{
+ QList dataList;
+ inStream >> dataList;
+ analytics.d->ReadMessagesFromFile(dataList);
+
+ return inStream;
+}
diff --git a/src/libs/vganalytics/vganalytics.h b/src/libs/vganalytics/vganalytics.h
new file mode 100644
index 000000000..802e265ee
--- /dev/null
+++ b/src/libs/vganalytics/vganalytics.h
@@ -0,0 +1,99 @@
+/************************************************************************
+ **
+ ** @file vganalytics.h
+ ** @author Roman Telezhynskyi
+ ** @date 26 6, 2023
+ **
+ ** @brief
+ ** @copyright
+ ** This source code is part of the Valentina project, a pattern making
+ ** program, whose allow create and modeling patterns of clothing.
+ ** Copyright (C) 2023 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 .
+ **
+ *************************************************************************/
+#ifndef VGANALYTICS_H
+#define VGANALYTICS_H
+
+#include
+
+class QNetworkAccessManager;
+class VGAnalyticsWorker;
+
+// https://developers.google.com/analytics/devguides/collection/protocol/ga4/verify-implementation?client_type=gtag
+class VGAnalytics : public QObject
+{
+ Q_OBJECT // NOLINT
+
+public:
+ ~VGAnalytics() override;
+
+ static auto Instance() -> VGAnalytics *;
+
+ enum LogLevel
+ {
+ Debug,
+ Info,
+ Error
+ };
+
+ Q_ENUM(LogLevel) // NOLINT
+
+ void SetMeasurementId(const QString &measurementId);
+ void SetApiSecret(const QString &apiSecret);
+ void SetClientID(const QString &clientID);
+
+ void SetLogLevel(LogLevel logLevel);
+ auto LogLevel() const -> LogLevel;
+
+ void SetRepoRevision(const QString &rev);
+ auto RepoRevision() const -> QString;
+
+ void SetGUILanguage(const QString &language);
+ auto GUILanguage() const -> QString;
+
+ void SetSendInterval(int milliseconds);
+ auto SendInterval() const -> int;
+
+ void Enable(bool state = true);
+ auto IsEnabled() -> bool;
+
+ /// Get or set the network access manager. If none is set, the class creates its own on the first request
+ void SetNetworkAccessManager(QNetworkAccessManager *networkAccessManager);
+ auto NetworkAccessManager() const -> QNetworkAccessManager *;
+
+public slots:
+ void SendAppFreshInstallEvent(qint64 engagementTimeMsec);
+ void SendAppStartEvent(qint64 engagementTimeMsec);
+ void SendAppCloseEvent(qint64 engagementTimeMsec);
+
+private:
+ Q_DISABLE_COPY_MOVE(VGAnalytics) // NOLINT
+
+ explicit VGAnalytics(QObject *parent = nullptr);
+
+private:
+ VGAnalyticsWorker *d;
+
+ void SendEvent(const QString &eventName, const QHash ¶ms);
+
+ auto InitAppStartEventParams(qint64 engagementTimeMsec) const -> QHash;
+
+ friend auto operator<<(QDataStream &outStream, const VGAnalytics &analytics) -> QDataStream &;
+ friend auto operator>>(QDataStream &inStream, VGAnalytics &analytics) -> QDataStream &;
+};
+
+#endif // VGANALYTICS_H
diff --git a/src/libs/vganalytics/vganalytics.pri b/src/libs/vganalytics/vganalytics.pri
new file mode 100644
index 000000000..49aa55477
--- /dev/null
+++ b/src/libs/vganalytics/vganalytics.pri
@@ -0,0 +1,8 @@
+HEADERS += \
+ $$PWD/def.h \
+ $$PWD/vganalytics.h \
+ $$PWD/vganalyticsworker.h
+
+SOURCES += \
+ $$PWD/vganalytics.cpp \
+ $$PWD/vganalyticsworker.cpp
diff --git a/src/libs/vganalytics/vganalytics.pro b/src/libs/vganalytics/vganalytics.pro
new file mode 100644
index 000000000..74c4934cd
--- /dev/null
+++ b/src/libs/vganalytics/vganalytics.pro
@@ -0,0 +1,80 @@
+# File with common stuff for whole project
+include(../../../common.pri)
+
+QT += network gui
+
+# Name of library
+TARGET = vganalytics
+
+# We want to create a library
+TEMPLATE = lib
+
+CONFIG += staticlib # Making static library
+
+# Since Q5.12 available support for C++17
+equals(QT_MAJOR_VERSION, 5):greaterThan(QT_MINOR_VERSION, 11) {
+ CONFIG += c++17
+} else {
+ CONFIG += c++14
+}
+
+# Use out-of-source builds (shadow builds)
+CONFIG -= debug_and_release debug_and_release_target
+
+# The following define makes your compiler emit warnings if you use
+# any feature of Qt which has been marked as deprecated (the exact warnings
+# depend on your compiler). Please consult the documentation of the
+# deprecated API in order to know how to port your code away from it.
+DEFINES += QT_DEPRECATED_WARNINGS
+
+# You can also make your code fail to compile if you use deprecated APIs.
+# In order to do so, uncomment the following line.
+# You can also select to disable deprecated APIs only up to a certain version of Qt.
+#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
+
+# Since Qt 5.4.0 the source code location is recorded only in debug builds.
+# We need this information also in release builds. For this need define QT_MESSAGELOGCONTEXT.
+DEFINES += QT_MESSAGELOGCONTEXT
+
+include(vganalytics.pri)
+
+# This is static library so no need in "make install"
+
+# directory for executable file
+DESTDIR = bin
+
+# files created moc
+MOC_DIR = moc
+
+# objecs files
+OBJECTS_DIR = obj
+
+# Set using ccache. Function enable_ccache() defined in common.pri.
+$$enable_ccache()
+
+include(warnings.pri)
+
+CONFIG(debug, debug|release){
+ # Debug mode
+}else{
+ # Release mode
+ !*msvc*:CONFIG += silent
+ DEFINES += V_NO_ASSERT
+ !unix:*g++*{
+ QMAKE_CXXFLAGS += -fno-omit-frame-pointer # Need for exchndl.dll
+ }
+
+ noDebugSymbols{ # For enable run qmake with CONFIG+=noDebugSymbols
+ # do nothing
+ } else {
+ !macx:!*msvc*{
+ # Turn on debug symbols in release mode on Unix systems.
+ # On Mac OS X temporarily disabled. TODO: find way how to strip binary file.
+ QMAKE_CXXFLAGS_RELEASE += -g -gdwarf-3
+ QMAKE_CFLAGS_RELEASE += -g -gdwarf-3
+ QMAKE_LFLAGS_RELEASE =
+ }
+ }
+}
+
+include (../libs.pri)
diff --git a/src/libs/vganalytics/vganalytics.qbs b/src/libs/vganalytics/vganalytics.qbs
new file mode 100644
index 000000000..c96e919d5
--- /dev/null
+++ b/src/libs/vganalytics/vganalytics.qbs
@@ -0,0 +1,16 @@
+VLib {
+ name: "VGAnalyticsLib"
+ files: [
+ "def.h",
+ "vganalytics.cpp",
+ "vganalytics.h",
+ "vganalyticsworker.cpp",
+ "vganalyticsworker.h",
+ ]
+ Depends { name: "Qt"; submodules: ["core", "network", "gui"] }
+
+ Export {
+ Depends { name: "cpp" }
+ cpp.includePaths: [exportingProduct.sourceDirectory]
+ }
+}
diff --git a/src/libs/vganalytics/vganalyticsworker.cpp b/src/libs/vganalytics/vganalyticsworker.cpp
new file mode 100644
index 000000000..6e95c22a4
--- /dev/null
+++ b/src/libs/vganalytics/vganalyticsworker.cpp
@@ -0,0 +1,281 @@
+/************************************************************************
+ **
+ ** @file vganalyticsworker.cpp
+ ** @author Roman Telezhynskyi
+ ** @date 26 6, 2023
+ **
+ ** @brief
+ ** @copyright
+ ** This source code is part of the Valentina project, a pattern making
+ ** program, whose allow create and modeling patterns of clothing.
+ ** Copyright (C) 2023 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 "vganalyticsworker.h"
+#include "vganalytics.h"
+
+#include
+#include
+#include
+
+#include
+#include
+
+#include
+#include
+#include
+
+const QLatin1String VGAnalyticsWorker::dateTimeFormat("yyyy,MM,dd-hh:mm::ss:zzz");
+
+namespace
+{
+constexpr int fourHours = 4 * 60 * 60 * 1000;
+}
+
+//---------------------------------------------------------------------------------------------------------------------
+VGAnalyticsWorker::VGAnalyticsWorker(QObject *parent)
+ : QObject(parent),
+ m_logLevel(VGAnalytics::Error)
+{
+ m_appName = QCoreApplication::applicationName();
+ m_appVersion = QCoreApplication::applicationVersion();
+ m_request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json; charset=utf-8");
+ m_request.setHeader(QNetworkRequest::UserAgentHeader, GetUserAgent());
+
+ m_guiLanguage = QLocale::system().name().toLower().replace(QChar('_'), QChar('-'));
+
+ QScreen *screen = QGuiApplication::primaryScreen();
+ QSize size = screen->size();
+
+ m_screenResolution = QStringLiteral("%1x%2").arg(size.width()).arg(size.height());
+ m_screenScaleFactor = screen->logicalDotsPerInchX() / 96.0;
+
+ m_timer.setInterval(m_timerInterval);
+ connect(&m_timer, &QTimer::timeout, this, &VGAnalyticsWorker::PostMessage);
+}
+
+//---------------------------------------------------------------------------------------------------------------------
+void VGAnalyticsWorker::Enable(bool state)
+{
+ // state change to the same is not valid.
+ if (m_isEnabled == state)
+ {
+ return;
+ }
+
+ m_isEnabled = state;
+ if (m_isEnabled)
+ {
+ // enable -> start doing things :)
+ m_timer.start();
+ }
+ else
+ {
+ // disable -> stop the timer
+ m_timer.stop();
+ }
+}
+
+//---------------------------------------------------------------------------------------------------------------------
+void VGAnalyticsWorker::LogMessage(enum VGAnalytics::LogLevel level, const QString &message) const
+{
+ if (m_logLevel > level)
+ {
+ return;
+ }
+
+ qDebug() << "[Analytics]" << message;
+}
+
+//---------------------------------------------------------------------------------------------------------------------
+/**
+ * Try to gain information about the system where this application
+ * is running. It needs to get the name and version of the operating
+ * system, the language and screen resolution.
+ * All this information will be send in POST messages.
+ * @return agent A QString with all the information formatted for a POST message.
+ */
+auto VGAnalyticsWorker::GetUserAgent() const -> QString
+{
+ return QStringLiteral("%1/%2").arg(m_appName, m_appVersion);
+}
+
+//---------------------------------------------------------------------------------------------------------------------
+/**
+ * The message queue contains a list of QueryBuffer object.
+ * QueryBuffer holds a QUrlQuery object and a QDateTime object.
+ * These both object are freed from the buffer object and
+ * inserted as QString objects in a QList.
+ * @return dataList The list with concartinated queue data.
+ */
+auto VGAnalyticsWorker::PersistMessageQueue() -> QList
+{
+ QList dataList;
+ dataList.reserve(m_messageQueue.size() * 2);
+
+ for (const auto &buffer : m_messageQueue)
+ {
+ dataList << QJsonDocument(buffer.postQuery).toJson();
+ dataList << buffer.time.toString(dateTimeFormat);
+ }
+ return dataList;
+}
+
+//---------------------------------------------------------------------------------------------------------------------
+/**
+ * Reads persistent messages from a file.
+ * Gets all message data as a QList.
+ * Two lines in the list build a QueryBuffer object.
+ */
+void VGAnalyticsWorker::ReadMessagesFromFile(const QList &dataList)
+{
+ QListIterator iter(dataList);
+ while (iter.hasNext())
+ {
+ QString queryString = iter.next();
+ QString dateString = iter.next();
+ QDateTime dateTime = QDateTime::fromString(dateString, dateTimeFormat);
+ QueryBuffer buffer;
+
+ QJsonDocument jsonDocument = QJsonDocument::fromJson(queryString.toUtf8());
+ if (jsonDocument.isNull())
+ {
+ qDebug() << "===> please check the string " << queryString.toUtf8();
+ }
+ QJsonObject jsonObject = jsonDocument.object();
+
+ buffer.postQuery = jsonObject;
+ buffer.time = dateTime;
+ m_messageQueue.enqueue(buffer);
+ }
+}
+
+//---------------------------------------------------------------------------------------------------------------------
+/**
+ * Takes a QUrlQuery object and wrapp it together with
+ * a QTime object into a QueryBuffer struct. These struct
+ * will be stored in the message queue.
+ */
+void VGAnalyticsWorker::EnqueQueryWithCurrentTime(const QJsonObject &query)
+{
+ QueryBuffer buffer;
+ buffer.postQuery = query;
+ buffer.time = QDateTime::currentDateTime();
+
+ m_messageQueue.enqueue(buffer);
+}
+
+//---------------------------------------------------------------------------------------------------------------------
+/**
+ * This function is called by a timer interval.
+ * The function tries to send a messages from the queue.
+ * If message was successfully send then this function
+ * will be called back to send next message.
+ * If message queue contains more than one message then
+ * the connection will kept open.
+ * The message POST is asyncroniously when the server
+ * answered a signal will be emitted.
+ */
+auto VGAnalyticsWorker::PostMessage() -> QNetworkReply *
+{
+ if (m_messageQueue.isEmpty())
+ {
+ // queue empty -> try sending later
+ m_timer.start();
+ return nullptr;
+ }
+
+ // queue has messages -> stop timer and start sending
+ m_timer.stop();
+
+ QString connection = QStringLiteral("close");
+ if (m_messageQueue.count() > 1)
+ {
+ connection = QLatin1String("keep-alive");
+ }
+
+ QueryBuffer buffer = m_messageQueue.head();
+ QDateTime sendTime = QDateTime::currentDateTime();
+ qint64 timeDiff = buffer.time.msecsTo(sendTime);
+
+ if (timeDiff > fourHours)
+ {
+ // too old.
+ m_messageQueue.dequeue();
+ return PostMessage();
+ }
+
+ QByteArray requestJson = QJsonDocument(buffer.postQuery).toJson(QJsonDocument::Compact);
+ m_request.setRawHeader("Connection", connection.toUtf8());
+ m_request.setHeader(QNetworkRequest::ContentLengthHeader, requestJson.length());
+
+ if (m_measurementId.isEmpty())
+ {
+ LogMessage(VGAnalytics::Error, QStringLiteral("google analytics measurement id was not set!"));
+ }
+ if (m_apiSecret.isEmpty())
+ {
+ LogMessage(VGAnalytics::Error, QStringLiteral("google analytics api seceret was not set!"));
+ }
+
+ QString requestUrl = QStringLiteral("https://www.google-analytics.com/mp/collect?measurement_id=%1&api_secret=%2");
+ requestUrl = requestUrl.arg(m_measurementId, m_apiSecret);
+ m_request.setUrl(QUrl(requestUrl));
+
+ LogMessage(VGAnalytics::Debug, "GA Query string = " + requestJson);
+
+ // Create a new network access manager if we don't have one yet
+ if (networkManager == nullptr)
+ {
+ networkManager = new QNetworkAccessManager(this);
+ }
+
+ QNetworkReply *reply = networkManager->post(m_request, requestJson);
+ connect(reply, &QNetworkReply::finished, this, &VGAnalyticsWorker::PostMessageFinished);
+ return reply;
+}
+
+//---------------------------------------------------------------------------------------------------------------------
+/**
+ * NetworkAccsessManager has finished to POST a message.
+ * If POST message was successfully send then the message
+ * query should be removed from queue.
+ * SIGNAL "postMessage" will be emitted to send next message
+ * if there is any.
+ * If message couldn't be send then next try is when the
+ * timer emits its signal.
+ */
+void VGAnalyticsWorker::PostMessageFinished()
+{
+ auto *reply = qobject_cast(sender());
+
+ int httpStausCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
+ if (httpStausCode < 200 || httpStausCode > 299)
+ {
+ LogMessage(VGAnalytics::Error, QStringLiteral("Error posting message: %1").arg(reply->errorString()));
+
+ // An error ocurred. Try sending later.
+ m_timer.start();
+ return;
+ }
+
+ LogMessage(VGAnalytics::Debug, QStringLiteral("Message sent"));
+
+ m_messageQueue.dequeue();
+ PostMessage();
+ reply->deleteLater();
+}
diff --git a/src/libs/vganalytics/vganalyticsworker.h b/src/libs/vganalytics/vganalyticsworker.h
new file mode 100644
index 000000000..4708984e4
--- /dev/null
+++ b/src/libs/vganalytics/vganalyticsworker.h
@@ -0,0 +1,97 @@
+/************************************************************************
+ **
+ ** @file vganalyticsworker.h
+ ** @author Roman Telezhynskyi
+ ** @date 26 6, 2023
+ **
+ ** @brief
+ ** @copyright
+ ** This source code is part of the Valentina project, a pattern making
+ ** program, whose allow create and modeling patterns of clothing.
+ ** Copyright (C) 2023 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 .
+ **
+ *************************************************************************/
+#ifndef VGANALYTICSWORKER_H
+#define VGANALYTICSWORKER_H
+
+#include "vganalytics.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+class QNetworkReply;
+
+struct QueryBuffer
+{
+ QJsonObject postQuery{};
+ QDateTime time{};
+};
+
+class VGAnalyticsWorker : public QObject
+{
+ Q_OBJECT // NOLINT
+
+public:
+ explicit VGAnalyticsWorker(QObject *parent = nullptr);
+
+ QNetworkAccessManager *networkManager{nullptr}; // NOLINT(misc-non-private-member-variables-in-classes)
+
+ QQueue m_messageQueue{}; // NOLINT(misc-non-private-member-variables-in-classes)
+ QTimer m_timer{}; // NOLINT(misc-non-private-member-variables-in-classes)
+ QNetworkRequest m_request{}; // NOLINT(misc-non-private-member-variables-in-classes)
+ enum VGAnalytics::LogLevel m_logLevel{VGAnalytics::Debug}; // NOLINT(misc-non-private-member-variables-in-classes)
+
+ QString m_measurementId{}; // NOLINT(misc-non-private-member-variables-in-classes)
+ QString m_apiSecret{}; // NOLINT(misc-non-private-member-variables-in-classes)
+ QString m_clientID{}; // NOLINT(misc-non-private-member-variables-in-classes)
+ QString m_userID{}; // NOLINT(misc-non-private-member-variables-in-classes)
+ QString m_appName{}; // NOLINT(misc-non-private-member-variables-in-classes)
+ QString m_appVersion{}; // NOLINT(misc-non-private-member-variables-in-classes)
+ QString m_guiLanguage{}; // NOLINT(misc-non-private-member-variables-in-classes)
+ QString m_screenResolution{}; // NOLINT(misc-non-private-member-variables-in-classes)
+ QString m_repoRevision{}; // NOLINT(misc-non-private-member-variables-in-classes)
+
+ bool m_isEnabled{false}; // NOLINT(misc-non-private-member-variables-in-classes)
+ int m_timerInterval{30000}; // NOLINT(misc-non-private-member-variables-in-classes)
+ qreal m_screenScaleFactor{}; // NOLINT(misc-non-private-member-variables-in-classes)
+
+ const static QLatin1String dateTimeFormat;
+
+ void LogMessage(enum VGAnalytics::LogLevel level, const QString &message) const;
+
+ auto GetUserAgent() const -> QString;
+ auto PersistMessageQueue() -> QList;
+ void ReadMessagesFromFile(const QList &dataList);
+
+ void EnqueQueryWithCurrentTime(const QJsonObject &query);
+ void Enable(bool state);
+
+public slots:
+ QNetworkReply *PostMessage(); // NOLINT(modernize-use-trailing-return-type)
+ void PostMessageFinished();
+
+private:
+ Q_DISABLE_COPY_MOVE(VGAnalyticsWorker)
+};
+
+#endif // VGANALYTICSWORKER_H
diff --git a/src/libs/vganalytics/warnings.pri b/src/libs/vganalytics/warnings.pri
new file mode 100644
index 000000000..24184cea5
--- /dev/null
+++ b/src/libs/vganalytics/warnings.pri
@@ -0,0 +1,82 @@
+#Turn on compilers warnings.
+unix {
+ *g++*{
+ QMAKE_CXXFLAGS += \
+ # Key -isystem disable checking errors in system headers.
+ -isystem "$${OUT_PWD}/$${MOC_DIR}" \
+ -isystem "$${OUT_PWD}/$${UI_DIR}" \
+ $$GCC_DEBUG_CXXFLAGS # See common.pri for more details.
+
+ checkWarnings{ # For enable run qmake with CONFIG+=checkWarnings
+ QMAKE_CXXFLAGS += -Werror
+ }
+
+ noAddressSanitizer{ # For enable run qmake with CONFIG+=noAddressSanitizer
+ # do nothing
+ } else {
+ CONFIG(debug, debug|release){
+ # Debug mode
+ #gcc’s 4.8.0 Address Sanitizer
+ #http://blog.qt.digia.com/blog/2013/04/17/using-gccs-4-8-0-address-sanitizer-with-qt/
+ QMAKE_CXXFLAGS += -fsanitize=address -fno-omit-frame-pointer
+ QMAKE_CFLAGS += -fsanitize=address -fno-omit-frame-pointer
+ QMAKE_LFLAGS += -fsanitize=address
+ }
+ }
+
+ gccUbsan{ # For enable run qmake with CONFIG+=gccUbsan
+ CONFIG(debug, debug|release){
+ # Debug mode
+ #gcc’s 4.9.0 Undefined Behavior Sanitizer (ubsan)
+ QMAKE_CXXFLAGS += -fsanitize=undefined
+ QMAKE_CFLAGS += -fsanitize=undefined
+ QMAKE_LFLAGS += -fsanitize=undefined
+ }
+ }
+ }
+
+ *clang*{
+ QMAKE_CXXFLAGS += \
+ # Key -isystem disable checking errors in system headers.
+ -isystem "$${OUT_PWD}/$${MOC_DIR}" \
+ -isystem "$${OUT_PWD}/$${UI_DIR}" \
+ $$CLANG_DEBUG_CXXFLAGS # See common.pri for more details.
+
+ checkWarnings{ # For enable run qmake with CONFIG+=checkWarnings
+ QMAKE_CXXFLAGS += -Werror
+ }
+
+ # -isystem key works only for headers. In some cases it's not enough. But we can't delete these warnings and
+ # want them in global list. Compromise decision delete them from local list.
+ QMAKE_CXXFLAGS -= \
+ -Wmissing-prototypes \
+ -Wundefined-reinterpret-cast
+ }
+
+ *-icc-*{
+ QMAKE_CXXFLAGS += \
+ -isystem "$${OUT_PWD}/$${MOC_DIR}" \
+ -isystem "$${OUT_PWD}/$${UI_DIR}" \
+ $$ICC_DEBUG_CXXFLAGS
+
+ checkWarnings{ # For enable run qmake with CONFIG+=checkWarnings
+ QMAKE_CXXFLAGS += -Werror
+ }
+ }
+} else { # Windows
+ *g++*{
+ QMAKE_CXXFLAGS += $$GCC_DEBUG_CXXFLAGS # See common.pri for more details.
+
+ checkWarnings{ # For enable run qmake with CONFIG+=checkWarnings
+ QMAKE_CXXFLAGS += -Werror
+ }
+ }
+
+ *msvc*{
+ QMAKE_CXXFLAGS += $$MSVC_DEBUG_CXXFLAGS # See common.pri for more details.
+
+ checkWarnings{ # For enable run qmake with CONFIG+=checkWarnings
+ QMAKE_CXXFLAGS += -WX
+ }
+ }
+}
diff --git a/src/libs/vmisc/dialogs/dialogaskcollectstatistic.cpp b/src/libs/vmisc/dialogs/dialogaskcollectstatistic.cpp
new file mode 100644
index 000000000..af16ce47a
--- /dev/null
+++ b/src/libs/vmisc/dialogs/dialogaskcollectstatistic.cpp
@@ -0,0 +1,49 @@
+/************************************************************************
+ **
+ ** @file dialogaskcollectstatistic.cpp
+ ** @author Roman Telezhynskyi
+ ** @date 26 6, 2023
+ **
+ ** @brief
+ ** @copyright
+ ** This source code is part of the Valentina project, a pattern making
+ ** program, whose allow create and modeling patterns of clothing.
+ ** Copyright (C) 2023 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 "dialogaskcollectstatistic.h"
+#include "ui_dialogaskcollectstatistic.h"
+
+//---------------------------------------------------------------------------------------------------------------------
+DialogAskCollectStatistic::DialogAskCollectStatistic(QWidget *parent)
+ : QDialog(parent),
+ ui(new Ui::DialogAskCollectStatistic)
+{
+ ui->setupUi(this);
+}
+
+//---------------------------------------------------------------------------------------------------------------------
+DialogAskCollectStatistic::~DialogAskCollectStatistic()
+{
+ delete ui;
+}
+
+//---------------------------------------------------------------------------------------------------------------------
+auto DialogAskCollectStatistic::CollectStatistic() -> bool
+{
+ return ui->checkBoxSendUsageStatistics->isChecked();
+}
diff --git a/src/libs/vmisc/dialogs/dialogaskcollectstatistic.h b/src/libs/vmisc/dialogs/dialogaskcollectstatistic.h
new file mode 100644
index 000000000..7b7520121
--- /dev/null
+++ b/src/libs/vmisc/dialogs/dialogaskcollectstatistic.h
@@ -0,0 +1,57 @@
+/************************************************************************
+ **
+ ** @file dialogaskcollectstatistic.h
+ ** @author Roman Telezhynskyi
+ ** @date 26 6, 2023
+ **
+ ** @brief
+ ** @copyright
+ ** This source code is part of the Valentina project, a pattern making
+ ** program, whose allow create and modeling patterns of clothing.
+ ** Copyright (C) 2023 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 .
+ **
+ *************************************************************************/
+#ifndef DIALOGASKCOLLECTSTATISTIC_H
+#define DIALOGASKCOLLECTSTATISTIC_H
+
+#include
+#include
+#if QT_VERSION < QT_VERSION_CHECK(5, 13, 0)
+#include "../defglobal.h"
+#endif
+
+namespace Ui
+{
+class DialogAskCollectStatistic;
+}
+
+class DialogAskCollectStatistic : public QDialog
+{
+ Q_OBJECT // NOLINT
+
+public:
+ explicit DialogAskCollectStatistic(QWidget *parent = nullptr);
+ ~DialogAskCollectStatistic() override;
+
+ auto CollectStatistic() -> bool;
+
+private:
+ Q_DISABLE_COPY_MOVE(DialogAskCollectStatistic) // NOLINT
+ Ui::DialogAskCollectStatistic *ui;
+};
+
+#endif // DIALOGASKCOLLECTSTATISTIC_H
diff --git a/src/libs/vmisc/dialogs/dialogaskcollectstatistic.ui b/src/libs/vmisc/dialogs/dialogaskcollectstatistic.ui
new file mode 100644
index 000000000..eb977b4a7
--- /dev/null
+++ b/src/libs/vmisc/dialogs/dialogaskcollectstatistic.ui
@@ -0,0 +1,103 @@
+
+
+ DialogAskCollectStatistic
+
+
+
+ 0
+ 0
+ 376
+ 183
+
+
+
+ Privacy
+
+
+
+ :/icon/64x64/icon64x64.png:/icon/64x64/icon64x64.png
+
+
+ -
+
+
+ Send usage statistics
+
+
+ true
+
+
+
+ -
+
+
+ Please help to improve Valentina's quality by automatically sending usage statistics. Sent data contains <span style=" font-weight:700;">no potentially sensitive information</span> like user names, email addresses, file contents or file paths.
+
+
+ true
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QDialogButtonBox::Cancel|QDialogButtonBox::Ok
+
+
+
+
+
+
+
+
+
+
+ buttonBox
+ accepted()
+ DialogAskCollectStatistic
+ accept()
+
+
+ 248
+ 254
+
+
+ 157
+ 274
+
+
+
+
+ buttonBox
+ rejected()
+ DialogAskCollectStatistic
+ reject()
+
+
+ 316
+ 260
+
+
+ 286
+ 274
+
+
+
+
+
diff --git a/src/libs/vmisc/vabstractapplication.cpp b/src/libs/vmisc/vabstractapplication.cpp
index 9a11f8ae4..4046ba30b 100644
--- a/src/libs/vmisc/vabstractapplication.cpp
+++ b/src/libs/vmisc/vabstractapplication.cpp
@@ -153,6 +153,8 @@ VAbstractApplication::VAbstractApplication(int &argc, char **argv)
#endif
connect(this, &QApplication::aboutToQuit, this, &VAbstractApplication::AboutToQuit);
+
+ m_uptimeTimer.start();
}
//---------------------------------------------------------------------------------------------------------------------
@@ -433,6 +435,12 @@ void VAbstractApplication::RestartSVGFontDatabaseWatcher()
}
}
+//---------------------------------------------------------------------------------------------------------------------
+auto VAbstractApplication::AppUptime() const -> qint64
+{
+ return m_uptimeTimer.elapsed();
+}
+
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
//---------------------------------------------------------------------------------------------------------------------
auto VAbstractApplication::TextCodecCache(QStringConverter::Encoding encoding) const -> VTextCodec *
diff --git a/src/libs/vmisc/vabstractapplication.h b/src/libs/vmisc/vabstractapplication.h
index c9b79c50a..cdf1e5eb7 100644
--- a/src/libs/vmisc/vabstractapplication.h
+++ b/src/libs/vmisc/vabstractapplication.h
@@ -31,6 +31,7 @@
#include
#include
+#include
#include
#include
#include
@@ -104,6 +105,8 @@ public:
auto SVGFontDatabase() -> VSvgFontDatabase *;
void RestartSVGFontDatabaseWatcher();
+ auto AppUptime() const -> qint64;
+
protected:
QUndoStack *undoStack;
@@ -120,6 +123,8 @@ protected:
QPointer appTranslator{nullptr};
QPointer pmsTranslator{nullptr};
+ QElapsedTimer m_uptimeTimer{};
+
virtual void InitTrVars() = 0;
static void CheckSystemLocale();
diff --git a/src/libs/vmisc/vcommonsettings.cpp b/src/libs/vmisc/vcommonsettings.cpp
index cff496e98..22fcf09b1 100644
--- a/src/libs/vmisc/vcommonsettings.cpp
+++ b/src/libs/vmisc/vcommonsettings.cpp
@@ -233,6 +233,10 @@ Q_GLOBAL_STATIC_WITH_ARGS(const QString, settingWatermarkEditorSize, (QLatin1Str
// NOLINTNEXTLINE
Q_GLOBAL_STATIC_WITH_ARGS(const QString, settingWatermarkCustomColors, (QLatin1String("watermarkCustomColors")))
+Q_GLOBAL_STATIC_WITH_ARGS(const QString, settingsStatistictAskCollect, (QLatin1String("askCollect"))) // NOLINT
+Q_GLOBAL_STATIC_WITH_ARGS(const QString, settingsStatistictCollect, (QLatin1String("collect"))) // NOLINT
+Q_GLOBAL_STATIC_WITH_ARGS(const QString, settingsStatistictClientID, (QLatin1String("clientID"))) // NOLINT
+
// Reading settings file is very expensive, cache curve approximation to speed up getting value
qreal curveApproximationCached = -1; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
Q_GLOBAL_STATIC(QString, localeCached) // NOLINT
@@ -1116,20 +1120,20 @@ auto VCommonSettings::GetCSVSeparator() const -> QChar
switch (separator)
{
case 0:
- return QChar('\t');
+ return QChar('\t'); // NOLINT(modernize-return-braced-init-list)
case 1:
- return QChar(';');
+ return QChar(';'); // NOLINT(modernize-return-braced-init-list)
case 2:
- return QChar(' ');
+ return QChar(' '); // NOLINT(modernize-return-braced-init-list)
default:
- return QChar(',');
+ return QChar(','); // NOLINT(modernize-return-braced-init-list)
}
}
//---------------------------------------------------------------------------------------------------------------------
auto VCommonSettings::GetDefCSVSeparator() -> QChar
{
- return QChar(',');
+ return QChar(','); // NOLINT(modernize-return-braced-init-list)
}
//---------------------------------------------------------------------------------------------------------------------
@@ -1751,6 +1755,51 @@ void VCommonSettings::SetWatermarkCustomColors(QVector colors)
settings.sync();
}
+//---------------------------------------------------------------------------------------------------------------------
+auto VCommonSettings::IsAskCollectStatistic() const -> bool
+{
+ QSettings settings(this->format(), this->scope(), this->organizationName(), *commonIniFilename);
+ return settings.value(*settingsStatistictAskCollect, 1).toBool();
+}
+
+//---------------------------------------------------------------------------------------------------------------------
+void VCommonSettings::SetAskCollectStatistic(bool value)
+{
+ QSettings settings(this->format(), this->scope(), this->organizationName(), *commonIniFilename);
+ settings.setValue(*settingsStatistictAskCollect, value);
+ settings.sync();
+}
+
+//---------------------------------------------------------------------------------------------------------------------
+auto VCommonSettings::IsCollectStatistic() const -> bool
+{
+ QSettings settings(this->format(), this->scope(), this->organizationName(), *commonIniFilename);
+ return settings.value(*settingsStatistictCollect, 1).toBool();
+}
+
+//---------------------------------------------------------------------------------------------------------------------
+void VCommonSettings::SetCollectStatistic(bool value)
+{
+ QSettings settings(this->format(), this->scope(), this->organizationName(), *commonIniFilename);
+ settings.setValue(*settingsStatistictCollect, value);
+ settings.sync();
+}
+
+//---------------------------------------------------------------------------------------------------------------------
+auto VCommonSettings::GetClientID() const -> QString
+{
+ QSettings settings(this->format(), this->scope(), this->organizationName(), *commonIniFilename);
+ return settings.value(*settingsStatistictClientID, QString()).toString();
+}
+
+//---------------------------------------------------------------------------------------------------------------------
+void VCommonSettings::SetClientID(const QString &clientID)
+{
+ QSettings settings(this->format(), this->scope(), this->organizationName(), *commonIniFilename);
+ settings.setValue(*settingsStatistictClientID, clientID);
+ settings.sync();
+}
+
//---------------------------------------------------------------------------------------------------------------------
auto VCommonSettings::PrepareStandardFiles(const QString ¤tPath, const QString &standardPath,
const QString &defPath) -> QString
diff --git a/src/libs/vmisc/vcommonsettings.h b/src/libs/vmisc/vcommonsettings.h
index d93d1f4c3..67a06322c 100644
--- a/src/libs/vmisc/vcommonsettings.h
+++ b/src/libs/vmisc/vcommonsettings.h
@@ -321,6 +321,15 @@ public:
auto GetWatermarkCustomColors() const -> QVector;
void SetWatermarkCustomColors(QVector colors);
+ auto IsAskCollectStatistic() const -> bool;
+ void SetAskCollectStatistic(bool value);
+
+ auto IsCollectStatistic() const -> bool;
+ void SetCollectStatistic(bool value);
+
+ auto GetClientID() const -> QString;
+ void SetClientID(const QString &clientID);
+
signals:
void SVGFontsPathChanged(const QString &oldPath, const QString &newPath);
diff --git a/src/libs/vmisc/vmisc.pri b/src/libs/vmisc/vmisc.pri
index 0db34ea68..1e04cacc5 100644
--- a/src/libs/vmisc/vmisc.pri
+++ b/src/libs/vmisc/vmisc.pri
@@ -3,6 +3,7 @@
SOURCES += \
$$PWD/def.cpp \
+ $$PWD/dialogs/dialogaskcollectstatistic.cpp \
$$PWD/svgfont/svgdef.cpp \
$$PWD/svgfont/vsvgfont.cpp \
$$PWD/svgfont/vsvgfontdatabase.cpp \
@@ -61,6 +62,7 @@ HEADERS += \
$$PWD/bpstd/utility.hpp \
$$PWD/bpstd/variant.hpp \
$$PWD/compatibility.h \
+ $$PWD/dialogs/dialogaskcollectstatistic.h \
$$PWD/lambdaconstants.h \
$$PWD/stable.h \
$$PWD/def.h \
@@ -122,5 +124,6 @@ contains(QT_VERSION, ^5\\.[0-2]\\.[0-2]$) { # Since Qt 5.3.0
}
FORMS += \
+ $$PWD/dialogs/dialogaskcollectstatistic.ui \
$$PWD/dialogs/dialogexporttocsv.ui \
$$PWD/dialogs/dialogselectlanguage.ui
diff --git a/src/libs/vmisc/vmisc.qbs b/src/libs/vmisc/vmisc.qbs
index 6c0487667..9f125aa3b 100644
--- a/src/libs/vmisc/vmisc.qbs
+++ b/src/libs/vmisc/vmisc.qbs
@@ -67,12 +67,15 @@ VLib {
name: "dialogs"
prefix: "dialogs/"
files: [
+ "dialogaskcollectstatistic.cpp",
+ "dialogaskcollectstatistic.h",
+ "dialogaskcollectstatistic.ui",
"dialogexporttocsv.cpp",
"dialogselectlanguage.cpp",
"dialogexporttocsv.h",
"dialogselectlanguage.h",
"dialogexporttocsv.ui",
- "dialogselectlanguage.ui"
+ "dialogselectlanguage.ui",
]
}