diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 054ee9a6..9d10684a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -85,21 +85,10 @@ add_executable(dlt-viewer fileexplorertab.ui fileexplorertab.h fileexplorertab.cpp - applicationdialog.ui - contextdialog.ui - ecudialog.ui - exporterdialog.ui - fileexplorertab.ui - filterdialog.ui - injectiondialog.ui - jumptodialog.ui - mainwindow.ui - multiplecontextdialog.ui - plugindialog.ui - searchform.ui - settingsdialog.ui - searchdialog.ui + applicationdialog.ui contextdialog.ui ecudialog.ui exporterdialog.ui fileexplorertab.ui filterdialog.ui injectiondialog.ui jumptodialog.ui mainwindow.ui multiplecontextdialog.ui plugindialog.ui searchdialog.ui searchform.ui settingsdialog.ui + updatechecker.h + updatechecker.cpp ) target_link_libraries(dlt-viewer diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 347cad6f..de165ad3 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -77,7 +77,7 @@ #include "qdltctrlmsg.h" #include #include "ecutree.h" - +#include "updatechecker.h" MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), @@ -87,6 +87,7 @@ MainWindow::MainWindow(QWidget *parent) : pulseButtonColor(255, 40, 40), isSearchOngoing(false) { + dltIndexer = NULL; settings = QDltSettingsManager::getInstance(); ui->setupUi(this); @@ -111,7 +112,6 @@ MainWindow::MainWindow(QWidget *parent) : initFileHandling(); - /* Commands plugin after loading log file */ qDebug() << "### Plugin commands after loading log file"; if(!QDltOptManager::getInstance()->getPostPluginCommands().isEmpty()) @@ -196,6 +196,7 @@ MainWindow::MainWindow(QWidget *parent) : qDebug() << "Start minimzed as defined in the settings"; this->setWindowState(Qt::WindowMinimized); } + } MainWindow::~MainWindow() @@ -280,6 +281,10 @@ void MainWindow::initState() settingsDlg->assertSettingsVersion(); settingsDlg->readSettings(); + /* Update Checker call for timer to check if there is any new update*/ + updChecker = new UpdateChecker(this); + updChecker->startAutoCheck(); + if (QDltSettingsManager::UI_Colour::UI_Dark == QDltSettingsManager::getInstance()->uiColour) { qApp->setStyle(QStyleFactory::create("Fusion")); @@ -530,6 +535,7 @@ void MainWindow::initView() addFilter->setShortcut((Qt::SHIFT | Qt::CTRL) | Qt::Key_A); connect(addFilter, SIGNAL(triggered()), this, SLOT(on_action_menuFilter_Add_triggered())); addAction(addFilter); + } void MainWindow::initSignalConnections() @@ -843,6 +849,7 @@ void MainWindow::initFileHandling() // normally load log file mutithreaded reloadLogFile(); } + } @@ -5389,7 +5396,7 @@ void MainWindow::on_actionShortcuts_List_triggered(){ const QString shortcutCollapseAllECU = "Ctrl+"; const QString shortcutCopyPayload = "Ctrl + P"; const QString shortcutInfo = "F1"; - const QString shortcutQuit = "Ctrl +- Q"; + const QString shortcutQuit = "Ctrl + Q"; // Store shortcuts dynamically using a list of pairs QList> shortcutsList = { @@ -5444,6 +5451,11 @@ void MainWindow::on_actionShortcuts_List_triggered(){ delete shortcutDialog; } + +void MainWindow::on_actionCheck_For_Latest_Updates_triggered(){ + updChecker->linkToUrl(); +} + void MainWindow::on_pluginWidget_itemSelectionChanged() { QList list = project.plugin->selectedItems(); diff --git a/src/mainwindow.h b/src/mainwindow.h index 858d227c..75d58333 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -49,6 +49,7 @@ #include "searchtablemodel.h" #include "ui_mainwindow.h" #include "searchform.h" +#include "updatechecker.h" /** * @brief Namespace to contain the toolbar positions. @@ -162,6 +163,9 @@ class MainWindow : public QMainWindow SettingsDialog *settingsDlg; QDltSettingsManager *settings; + /* Update Checker class for automatic pop up for new updates*/ + UpdateChecker *updChecker; + /* injections */ QString injectionAplicationId; QString injectionContextId; @@ -460,6 +464,7 @@ private slots: void on_action_menuHelp_Info_triggered(); void on_action_menuHelp_Command_Line_triggered(); void on_actionShortcuts_List_triggered(); + void on_actionCheck_For_Latest_Updates_triggered(); // Config methods void on_action_menuConfig_Context_Delete_triggered(); diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 99ded826..6c08b844 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -11,7 +11,7 @@ - Qt::NoFocus + Qt::FocusPolicy::NoFocus MainWindow @@ -21,7 +21,7 @@ :/icons/png/org.genivi.DLTViewer.png:/icons/png/org.genivi.DLTViewer.png - QTabWidget::Rounded + QTabWidget::TabShape::Rounded @@ -45,10 +45,10 @@ - Qt::CustomContextMenu + Qt::ContextMenuPolicy::CustomContextMenu - QAbstractScrollArea::AdjustToContentsOnFirstShow + QAbstractScrollArea::SizeAdjustPolicy::AdjustToContentsOnFirstShow false @@ -60,13 +60,13 @@ true - QAbstractItemView::ExtendedSelection + QAbstractItemView::SelectionMode::ExtendedSelection - QAbstractItemView::SelectRows + QAbstractItemView::SelectionBehavior::SelectRows - QAbstractItemView::ScrollPerPixel + QAbstractItemView::ScrollMode::ScrollPerPixel false @@ -93,7 +93,7 @@ 0 0 1001 - 23 + 26 @@ -212,6 +212,7 @@ + @@ -311,10 +312,10 @@ - Qt::NoFocus + Qt::FocusPolicy::NoFocus - QDockWidget::DockWidgetClosable|QDockWidget::DockWidgetFloatable|QDockWidget::DockWidgetMovable + QDockWidget::DockWidgetFeature::DockWidgetClosable|QDockWidget::DockWidgetFeature::DockWidgetFloatable|QDockWidget::DockWidgetFeature::DockWidgetMovable Project @@ -324,7 +325,7 @@ - Qt::NoFocus + Qt::FocusPolicy::NoFocus @@ -342,10 +343,10 @@ - QFrame::NoFrame + QFrame::Shape::NoFrame - QFrame::Plain + QFrame::Shadow::Plain @@ -439,26 +440,26 @@ true - Qt::TabFocus + Qt::FocusPolicy::TabFocus - QTabWidget::South + QTabWidget::TabPosition::South - QTabWidget::Rounded + QTabWidget::TabShape::Rounded 0 - Qt::ElideNone + Qt::TextElideMode::ElideNone false - Qt::NoFocus + Qt::FocusPolicy::NoFocus Explore @@ -480,7 +481,7 @@ - Qt::NoFocus + Qt::FocusPolicy::NoFocus Config @@ -501,19 +502,19 @@ - Qt::TabFocus + Qt::FocusPolicy::TabFocus - Qt::CustomContextMenu + Qt::ContextMenuPolicy::CustomContextMenu true - QAbstractItemView::ExtendedSelection + QAbstractItemView::SelectionMode::ExtendedSelection - QAbstractItemView::SelectRows + QAbstractItemView::SelectionBehavior::SelectRows true @@ -574,7 +575,7 @@ - Qt::CustomContextMenu + Qt::ContextMenuPolicy::CustomContextMenu true @@ -690,7 +691,7 @@ - Qt::CustomContextMenu + Qt::ContextMenuPolicy::CustomContextMenu true @@ -699,13 +700,13 @@ true - QAbstractItemView::InternalMove + QAbstractItemView::DragDropMode::InternalMove - Qt::MoveAction + Qt::DropAction::MoveAction - QAbstractItemView::ExtendedSelection + QAbstractItemView::SelectionMode::ExtendedSelection false @@ -782,7 +783,7 @@ - Qt::CustomContextMenu + Qt::ContextMenuPolicy::CustomContextMenu false @@ -1606,6 +1607,11 @@ Shortcuts List + + + Check For Latest Updates + + diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index 151a6e0d..bd400a45 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -43,6 +43,8 @@ #define DAYLIGHT daylight #endif +// Define default update check interval in months +static const int DEFAULT_UPDATE_CHECK_MONTHS = 3; SettingsDialog::SettingsDialog(QDltFile *_qFile, QWidget *parent): @@ -120,6 +122,38 @@ SettingsDialog::SettingsDialog(QDltFile *_qFile, QWidget *parent): QDltSettingsManager *settings = QDltSettingsManager::getInstance(); settings->fmaxFileSizeMB = 0.0; settings->appendDateTime = 0; + + loadUpdateSettings(); + + // Enable/disable spinbox based on selected radio button + connect(ui->defaultRadioButton, &QRadioButton::toggled, this, [=](bool checked){ + if (checked) { + ui->intervalspinBox->setEnabled(false); + } + }); + + connect(ui->customRadioButton, &QRadioButton::toggled, this, [=](bool checked){ + if (checked) { + ui->intervalspinBox->setEnabled(true); + ui->intervalUnit->setEnabled(true); + } + }); + + connect(ui->defaultRadioButton, &QRadioButton::clicked, this, [=](){ + emit intervalModeChanged(false, 2); // Default mode → 2 minutes + }); + + connect(ui->customRadioButton, &QRadioButton::clicked, this, [=](){ + emit intervalModeChanged(true, ui->intervalspinBox->value()); + }); + + connect(ui->intervalspinBox, qOverload(&QSpinBox::valueChanged), + this, [=](int val){ + if (ui->customRadioButton->isChecked()) { + emit intervalModeChanged(true, val); + } + }); + } SettingsDialog::SettingsDialog(QWidget *parent) : @@ -134,6 +168,31 @@ SettingsDialog::~SettingsDialog() delete ui; } +void SettingsDialog::loadUpdateSettings() +{ + QSettings settings("MyCompany", "DLTViewer"); + + bool isCustom = settings.value("updateCheck/useCustom", false).toBool(); + int interval = settings.value("updateCheck/customMonths", DEFAULT_UPDATE_CHECK_MONTHS).toInt(); + + if (isCustom) + ui->customRadioButton->setChecked(true); + else + ui->defaultRadioButton->setChecked(true); + + ui->intervalspinBox->setEnabled(isCustom); + ui->intervalspinBox->setValue(interval); + +} + +void SettingsDialog::saveUpdateSettings() +{ + QSettings settings("MyCompany", "DLTViewer"); + + settings.setValue("updateCheck/useCustom", ui->customRadioButton->isChecked()); + settings.setValue("updateCheck/customMonths", ui->intervalspinBox->value()); +} + void SettingsDialog::changeEvent(QEvent *e) { QDialog::changeEvent(e); @@ -489,6 +548,7 @@ void SettingsDialog::readDlg() QMessageBox::Ok); msgBox.exec(); } + saveUpdateSettings(); } void SettingsDialog::writeSettings(QMainWindow *mainwindow) diff --git a/src/settingsdialog.h b/src/settingsdialog.h index 5486d71a..44bf4766 100644 --- a/src/settingsdialog.h +++ b/src/settingsdialog.h @@ -28,6 +28,9 @@ #define AUTOCONNECT_DEFAULT_TIME 1000 // in ms +// Define default update check interval in months +static const int DEFAULT_UPDATE_CHECK_MONTHS = 3; + namespace Ui { class SettingsDialog; } @@ -57,9 +60,13 @@ class SettingsDialog : public QDialog { QStringList getRecentFilters(); QString getWorkingDirectory(); + void loadUpdateSettings(); + void saveUpdateSettings(); + Q_SIGNALS: void FilterPathChanged(); void PluginsAutoloadChanged(); + void intervalModeChanged(bool isCustom, int minutes); protected: void changeEvent(QEvent *e); @@ -87,6 +94,7 @@ private slots: void on_checkBoxPluginsAutoload_stateChanged(int arg1); void on_pushButtonMarkerColor_clicked(); void on_pushButtonSelectFont_clicked(); + }; #endif // SETTINGSDIALOG_H diff --git a/src/settingsdialog.ui b/src/settingsdialog.ui index 6661aeb0..1ec8f201 100644 --- a/src/settingsdialog.ui +++ b/src/settingsdialog.ui @@ -19,19 +19,9 @@ - - - Qt::Orientation::Horizontal - - - QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok - - - - - 0 + 2 @@ -1005,9 +995,9 @@ 10 - 400 + 390 571 - 241 + 231 @@ -1185,6 +1175,87 @@ + + + + 10 + 630 + 561 + 80 + + + + DLT Viewer Latest Update + + + + + 30 + 30 + 121 + 24 + + + + Default + + + + + + 200 + 30 + 121 + 24 + + + + Custom + + + + + + 290 + 30 + 51 + 26 + + + + 1 + + + + + + 340 + 30 + 63 + 20 + + + + month + + + + + + + 0 + 720 + 591 + 29 + + + + Qt::Orientation::Horizontal + + + QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok + + @@ -1234,7 +1305,6 @@ - tabWidget radioButtonUseTemp lineEditSystemTemp radioButtonSelectTemp diff --git a/src/updatechecker.cpp b/src/updatechecker.cpp new file mode 100644 index 00000000..18f54c19 --- /dev/null +++ b/src/updatechecker.cpp @@ -0,0 +1,216 @@ + #include +#include +#include + +#include +#include +#include +#include +#include +#include // To send HTTP GET requests +#include // To create a request +#include // To handle the network response +#include +#include +#include +#include + +UpdateChecker::UpdateChecker(QObject *parent) + : QObject(parent), + updateTimer(nullptr), + manager(new QNetworkAccessManager(this)) +{ +} + +void UpdateChecker::linkToUrl(){ + qDebug() << "Update check called"; + + QString currentVersion = PACKAGE_VERSION; + QNetworkRequest request(QUrl("https://api.github.com/repos/COVESA/dlt-viewer/releases/latest")); + QNetworkReply *reply = manager->get(request); + + connect(reply, &QNetworkReply::finished, this, [=]() { + if (reply->error() != QNetworkReply::NoError) { + qDebug() << "Failed to fetch version info:" << reply->errorString(); + reply->deleteLater(); + return; + } + + QString response = reply->readAll(); + reply->deleteLater(); + + QRegularExpression re(R"((\d+\.\d+(\.\d+)?))"); + QRegularExpressionMatch match = re.match(response); + QString latestVersion = match.hasMatch() ? match.captured(1) : ""; + + qDebug() << "Current Version:" << currentVersion; + qDebug() << "Latest Version:" << latestVersion; + + // Compare versions + if (isNewerVersion(latestVersion, currentVersion)) { + showUpdatePopup(currentVersion, latestVersion); + } else { + showNoUpdatePopup(); + } + + reply->deleteLater(); + }); + +} + +QDateTime UpdateChecker::getLastCheckTime() +{ + QSettings s("MyCompany", "DLTViewer"); + return s.value("updateCheck/lastCheck").toDateTime(); +} + +void UpdateChecker::updateLastCheckTime() +{ + + QDateTime now = QDateTime::currentDateTime(); + qDebug() << "[UpdateTime] Saving last check time:" << now.toString(); + + QSettings s("MyCompany", "DLTViewer"); + s.setValue("updateCheck/lastCheck", QDateTime::currentDateTime()); +} + +// interval: default = 3 months +bool UpdateChecker::isIntervalPassed() +{ + QSettings s("MyCompany", "DLTViewer"); + + bool useCustom = s.value("updateCheck/useCustom", false).toBool(); + int customMonths = s.value("updateCheck/customMonths", DEFAULT_UPDATE_CHECK_MONTHS).toInt(); + int months = useCustom ? customMonths : DEFAULT_UPDATE_CHECK_MONTHS; + + QDateTime last = getLastCheckTime(); + + qDebug() << "[IntervalCheck] Custom?" << useCustom + << "Months =" << months; + + if (!last.isValid()){ + + qDebug() << "[IntervalCheck] No last check found → FIRST RUN → PASS"; + return true; + } + + bool passed = last.addMonths(months) <= QDateTime::currentDateTime(); + qDebug() << "[IntervalCheck] Interval Passed =" << passed; + + return passed; +} + +// Start auto-check timer +void UpdateChecker::startAutoCheck() +{ + QSettings s("MyCompany", "DLTViewer"); + + bool useCustom = s.value("updateCheck/useCustom", false).toBool(); + int minutes = s.value("updateCheck/customMinutes", 2).toInt(); + + if (!useCustom) + minutes = 120; + + qDebug() << "[AutoCheck] Starting auto update timer every" << minutes << "minutes"; + + if (!updateTimer) { + updateTimer = new QTimer(this); + connect(updateTimer, &QTimer::timeout, this, &UpdateChecker::checkForUpdates); + } + + updateTimer->start(minutes * 60 * 1000); +} + +void UpdateChecker::checkForUpdates() +{ + if (!isIntervalPassed()) { + qDebug() << "Interval not passed. Skipping update check."; + return; + } + + QString currentVersion = PACKAGE_VERSION; + QNetworkRequest request(QUrl("https://api.github.com/repos/COVESA/dlt-viewer/releases/latest")); + + // Add GitHub-required header + request.setRawHeader("User-Agent", "QtApp"); + + QNetworkReply *reply = manager->get(request); + + connect(reply, &QNetworkReply::finished, this, [=]() { + + qDebug() << "[Network] Reply received"; + + updateLastCheckTime(); + + if (reply->error() != QNetworkReply::NoError) { + qDebug() << "Failed to fetch version info:" << reply->errorString(); + reply->deleteLater(); + return; + } + + QString jsonResponse = reply->readAll(); + qDebug() << "[Network] JSON Received =" << jsonResponse.left(200) << "..."; + reply->deleteLater(); + + // Parse JSON + QJsonDocument doc = QJsonDocument::fromJson(jsonResponse.toUtf8()); + QJsonObject obj = doc.object(); + + QString tag = obj["tag_name"].toString(); // Example: "v3.35.0" + QString latestVersion = tag.startsWith('v') ? tag.mid(1) : tag; + + QString releaseUrl = obj["html_url"].toString(); + + qDebug() << "Current Version:" << currentVersion; + qDebug() << "Latest Version:" << latestVersion; + qDebug() << "Release URL:" << releaseUrl; + + if (isNewerVersion(latestVersion, currentVersion)) { + showUpdatePopup(currentVersion, latestVersion); // You can open releaseUrl here + } else { + qDebug() << "No Latest Version available for DLT Viewer"; + // showNoUpdatePopup(); + } + }); +} + +// Version comparison +bool UpdateChecker::isNewerVersion(const QString &latest, const QString ¤t) +{ + QStringList l = latest.split("."); + QStringList c = current.split("."); + + for (int i = 0; i < qMax(l.size(), c.size()); ++i) { + int latestVersion = (i < l.size()) ? l[i].toInt() : 0; + int currentVersion = (i < c.size()) ? c[i].toInt() : 0; + + if (latestVersion > currentVersion) return true; + if (latestVersion < currentVersion) return false; + } + return false; +} + +// Popups +void UpdateChecker::showUpdatePopup(const QString ¤t, const QString &latest) +{ + QMessageBox msg; + msg.setWindowTitle("Update Available"); + msg.setText(QString("Current Version: %1\nAvailable Version: %2") + .arg(current, latest)); + + QPushButton *updateBtn = msg.addButton("Update Now", QMessageBox::AcceptRole); + msg.addButton("Ignore", QMessageBox::RejectRole); + + msg.exec(); + + if (msg.clickedButton() == updateBtn) { + QDesktopServices::openUrl(QUrl("https://github.com/COVESA/dlt-viewer/releases/latest")); + } +} + +void UpdateChecker::showNoUpdatePopup() +{ + QMessageBox::information(nullptr, + "No Updates", + "No recent updates available."); +} diff --git a/src/updatechecker.h b/src/updatechecker.h new file mode 100644 index 00000000..1c4de50f --- /dev/null +++ b/src/updatechecker.h @@ -0,0 +1,35 @@ +#ifndef UPDATECHECKER_H +#define UPDATECHECKER_H + +#include +#include +#include + +class UpdateChecker : public QObject +{ + Q_OBJECT + + public: + explicit UpdateChecker(QObject *parent = nullptr); + + void startAutoCheck(); + void checkForUpdates(); + void linkToUrl(); + + private: + void showUpdatePopup(const QString ¤t, const QString &latest); + void showNoUpdatePopup(); + bool isNewerVersion(const QString &latest, const QString ¤t); + + private: + QTimer *updateTimer; + QNetworkAccessManager *manager; + + // used for 3-month interval logic + void updateLastCheckTime(); + QDateTime getLastCheckTime(); + bool isIntervalPassed(); + +}; + +#endif // UPDATECHECKER_H