From dee6a2db6060532a4174057c29acff2664edbfb5 Mon Sep 17 00:00:00 2001 From: IonutMuthi Date: Tue, 26 Nov 2024 02:57:21 -0500 Subject: [PATCH] generic: what's new carousel popup Signed-off-by: IonutMuthi --- CMakeLists.txt | 7 + cmake/Modules/ScopyWhatsNew.cmake | 45 ++++ core/include/core/scopymainwindow.h | 1 + core/include/core/whatsnewoverlay.h | 55 ++++ core/src/scopymainwindow.cpp | 18 +- core/src/whatsnewoverlay.cpp | 252 ++++++++++++++++++ .../qss/properties/button/whatsNewButton.qss | 27 ++ 7 files changed, 404 insertions(+), 1 deletion(-) create mode 100644 cmake/Modules/ScopyWhatsNew.cmake create mode 100644 core/include/core/whatsnewoverlay.h create mode 100644 core/src/whatsnewoverlay.cpp create mode 100644 gui/style/qss/properties/button/whatsNewButton.qss diff --git a/CMakeLists.txt b/CMakeLists.txt index d8e6a51af5..c9d4ccc2c1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -71,6 +71,13 @@ file(GLOB UI_LIST *.ui) set(PROJECT_SOURCES ${SRC_LIST} ${HEADER_LIST} ${UI_LIST}) +include(ScopyWhatsNew) +set(FOLDER ${CMAKE_SOURCE_DIR}/resources/whatsnew/) +set(RESOURCE_FILE ${CMAKE_SOURCE_DIR}/resources/whatsnew/whatsnew.qrc) +generate_whats_new("${RESOURCE_FILE}" "${FOLDER}") +file(GLOB WHATS_NEW_RESOURCES resources/whatsnew/whatsnew.qrc) +qt_add_resources(SCOPY_RESOURCES ${WHATS_NEW_RESOURCES}) + include(ScopyAbout) configure_about(./resources/about) file(GLOB SCOPY_RESOURCE_FILES gui/res/resources.qrc resources/aboutpage.qrc) diff --git a/cmake/Modules/ScopyWhatsNew.cmake b/cmake/Modules/ScopyWhatsNew.cmake new file mode 100644 index 0000000000..4655369e81 --- /dev/null +++ b/cmake/Modules/ScopyWhatsNew.cmake @@ -0,0 +1,45 @@ +# +# Copyright (c) 2024 Analog Devices Inc. +# +# This file is part of Scopy +# (see https://www.github.com/analogdevicesinc/scopy). +# +# This program 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. +# +# This program 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 this program. If not, see . +# + +if(DEFINED __INCLUDED_SCOPY_WHATS_NEW_CMAKE) + return() +endif() +set(__INCLUDED_SCOPY_WHATS_NEW_CMAKE TRUE) + +function(generate_whats_new RESOURCE_FILE FOLDER) + # Specify the folder containing resources Create the list of HTML files recursively + + file(GLOB_RECURSE HTML_FILES "${FOLDER}*.html") + + # Start writing to the resource file + file(WRITE "${RESOURCE_FILE}" "\n \n") + + # Loop through each HTML file and add it to the resource file + foreach(HTML_FILE ${HTML_FILES}) + # Convert the file path to a resource path + file(RELATIVE_PATH relative ${FOLDER} ${HTML_FILE}) + + # Write the file to the resource file + file(APPEND "${RESOURCE_FILE}" " ${relative}\n") + endforeach() + + # End the resource file + file(APPEND "${RESOURCE_FILE}" " \n\n") +endfunction() diff --git a/core/include/core/scopymainwindow.h b/core/include/core/scopymainwindow.h index 5e4263e017..5ba6b34546 100644 --- a/core/include/core/scopymainwindow.h +++ b/core/include/core/scopymainwindow.h @@ -108,6 +108,7 @@ public Q_SLOTS: void handleScanner(); void enableScanner(); void deviceAutoconnect(); + void showWhatsNew(); protected: void closeEvent(QCloseEvent *event) override; diff --git a/core/include/core/whatsnewoverlay.h b/core/include/core/whatsnewoverlay.h new file mode 100644 index 0000000000..0332cfb50b --- /dev/null +++ b/core/include/core/whatsnewoverlay.h @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2024 Analog Devices Inc. + * + * This file is part of Scopy + * (see https://www.github.com/analogdevicesinc/scopy). + * + * This program 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. + * + * This program 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 this program. If not, see . + * + */ + +#ifndef WHATSNEWOVERLAY_H +#define WHATSNEWOVERLAY_H + +#include +#include +#include +#include "scopy-core_export.h" +#include +#include +#include + +namespace scopy { +class SCOPY_CORE_EXPORT WhatsNewOverlay : public QWidget +{ + Q_OBJECT +public: + explicit WhatsNewOverlay(QWidget *parent = nullptr); + ~WhatsNewOverlay(); + + void showOverlay(); + void enableTintedOverlay(bool enable = true); + +private: + PopupWidget *m_popupWidget; + QString getHtmlPageContent(QString fileName); + void initCarousel(); + void generateVersionPage(QString filePath); + + QStackedWidget *m_carouselWidget; + QComboBox *m_versionCb; + gui::TintedOverlay *m_tintedOverlay; +}; +} // namespace scopy +#endif // WHATSNEWOVERLAY_H diff --git a/core/src/scopymainwindow.cpp b/core/src/scopymainwindow.cpp index f3c6b3f016..ffc4fe563c 100644 --- a/core/src/scopymainwindow.cpp +++ b/core/src/scopymainwindow.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include "logging_categories.h" #include "qmessagebox.h" @@ -254,6 +255,13 @@ void ScopyMainWindow::deviceAutoconnect() } } +void ScopyMainWindow::showWhatsNew() +{ + WhatsNewOverlay *whatsNew = new WhatsNewOverlay(this); + whatsNew->move(this->rect().center() - whatsNew->rect().center()); + QMetaObject::invokeMethod(whatsNew, &WhatsNewOverlay::showOverlay, Qt::QueuedConnection); +} + void ScopyMainWindow::save() { QString selectedFilter; @@ -351,6 +359,7 @@ void ScopyMainWindow::setupPreferences() if(p->get("general_show_status_bar").toBool()) { StatusBarManager::GetInstance()->setEnabled(true); } + if(p->get("general_first_run").toBool()) { license = new LicenseOverlay(this); auto versionCheckInfo = new VersionCheckMessage(this); @@ -358,9 +367,15 @@ void ScopyMainWindow::setupPreferences() StatusBarManager::pushWidget(versionCheckInfo, "Should Scopy check for online versions?"); QMetaObject::invokeMethod(license, &LicenseOverlay::showOverlay, Qt::QueuedConnection); + + connect(license->getContinueBtn(), &QPushButton::clicked, this, &ScopyMainWindow::showWhatsNew, + Qt::QueuedConnection); } -} + if(p->get("general_show_whats_new").toBool() && !p->get("general_first_run").toBool()) { + showWhatsNew(); + } +} void ScopyMainWindow::initPreferences() { QElapsedTimer timer; @@ -374,6 +389,7 @@ void ScopyMainWindow::initPreferences() p->init("general_save_session", true); p->init("general_save_attached", true); p->init("general_doubleclick_attach", true); + p->init("general_show_whats_new", true); #if defined(__linux__) && (defined(__arm__) || defined(__aarch64__)) p->init("general_use_opengl", false); #else diff --git a/core/src/whatsnewoverlay.cpp b/core/src/whatsnewoverlay.cpp new file mode 100644 index 0000000000..c3aad3e528 --- /dev/null +++ b/core/src/whatsnewoverlay.cpp @@ -0,0 +1,252 @@ +/* + * Copyright (c) 2024 Analog Devices Inc. + * + * This file is part of Scopy + * (see https://www.github.com/analogdevicesinc/scopy). + * + * This program 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. + * + * This program 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 this program. If not, see . + * + */ + +#include "whatsnewoverlay.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace scopy; + +WhatsNewOverlay::WhatsNewOverlay(QWidget *parent) + : m_tintedOverlay(nullptr) + , QWidget{parent} +{ + // main layout vertical + this->resize(400, 600); + QVBoxLayout *mainLayout = new QVBoxLayout(); + this->setLayout(mainLayout); + + m_carouselWidget = new QStackedWidget(this); + + // bottom of main layout has controll widget + + QWidget *overlayControllWidget = new QWidget(this); + QVBoxLayout *overlayControllWidgetLayout = new QVBoxLayout(overlayControllWidget); + + QWidget *optionsoverlayControllWidget = new QWidget(overlayControllWidget); + overlayControllWidgetLayout->addWidget(optionsoverlayControllWidget); + QHBoxLayout *optionsControllLayout = new QHBoxLayout(optionsoverlayControllWidget); + optionsoverlayControllWidget->setLayout(optionsControllLayout); + + QCheckBox *showAgain = new QCheckBox("Show this again", optionsoverlayControllWidget); + showAgain->setChecked(true); + optionsControllLayout->addWidget(showAgain); + + connect(showAgain, &QCheckBox::toggled, this, [=](bool en) { + Preferences *p = Preferences::GetInstance(); + p->set("general_show_whats_new", en); + }); + + // version picker + m_versionCb = new QComboBox(optionsoverlayControllWidget); + optionsControllLayout->addWidget(m_versionCb); + + connect(m_versionCb, qOverload(&QComboBox::currentIndexChanged), this, + [=](int idx) { m_carouselWidget->setCurrentIndex(idx); }); + + QPushButton *okButton = new QPushButton("Ok", optionsoverlayControllWidget); + connect(okButton, &QPushButton::clicked, this, [=]() { this->deleteLater(); }); + Style::setStyle(okButton, style::properties::button::basicButton, true, true); + optionsControllLayout->addWidget(okButton); + + mainLayout->addWidget(m_carouselWidget); + mainLayout->addWidget(overlayControllWidget); + Style::setBackgroundColor(this, json::theme::background_primary); + + initCarousel(); + enableTintedOverlay(true); +} + +WhatsNewOverlay::~WhatsNewOverlay() +{ + // TODO + if(m_tintedOverlay != nullptr) { + m_tintedOverlay->deleteLater(); + } +} + +void WhatsNewOverlay::showOverlay() +{ + raise(); + show(); +} + +void WhatsNewOverlay::enableTintedOverlay(bool enable) +{ + if(enable) { + delete m_tintedOverlay; + + m_tintedOverlay = new gui::TintedOverlay(parentWidget()); + m_tintedOverlay->show(); + raise(); + show(); + } else { + delete m_tintedOverlay; + m_tintedOverlay = nullptr; + } +} + +void WhatsNewOverlay::initCarousel() +{ + QDir dir(":/whatsnew"); + + // Get a list of all entries in the directory + QFileInfoList entries = dir.entryInfoList(QDir::AllDirs | QDir::NoDotAndDotDot, QDir::Name); + + // Iterate through the list and print the folder names + + for(int i = entries.count() - 1; i >= 0; i--) { + QFileInfo fileInfo = entries.at(i); + if(fileInfo.isDir()) { + // add option to combo + m_versionCb->addItem(fileInfo.fileName()); + // create page and button for page + generateVersionPage(fileInfo.absoluteFilePath()); + } + } +} + +void WhatsNewOverlay::generateVersionPage(QString filePath) +{ + QWidget *carouselContent = new QWidget(m_carouselWidget); + QVBoxLayout *carouselContentLayout = new QVBoxLayout(carouselContent); + carouselContentLayout->setMargin(2); + carouselContentLayout->setSpacing(0); + + QStackedWidget *versionCarouselWithControlls = new QStackedWidget(m_carouselWidget); + carouselContentLayout->addWidget(versionCarouselWithControlls, 9); + + QDir versionDir(filePath); + QFileInfoList fileList = versionDir.entryInfoList(QDir::Files | QDir::NoDotAndDotDot); + + for(const QFileInfo &page : fileList) { + + QWidget *scrollWidget = new QWidget(); + scrollWidget->setLayout(new QVBoxLayout()); + QScrollArea *scrollArea = new QScrollArea(); + scrollArea->setWidgetResizable(true); + scrollArea->setWidget(scrollWidget); + + QTextBrowser *htmlPage = new QTextBrowser(scrollWidget); + QString filePath(page.absoluteFilePath()); + htmlPage->setHtml(getHtmlPageContent(filePath)); + htmlPage->setProperty("openExternalLinks", true); + + scrollWidget->layout()->addWidget(htmlPage); + versionCarouselWithControlls->addWidget(scrollArea); + Style::setStyle(scrollWidget, style::properties::widget::border_interactive), true, true; + } + + int numberOfPages = fileList.count(); + + if(numberOfPages > 1) { + // carousel control contains buttons for changeing info page + QWidget *carouselControllWidget = new QWidget(carouselContent); + + QButtonGroup *m_carouselButtons = new QButtonGroup(carouselControllWidget); + m_carouselButtons->setExclusive(true); + + QHBoxLayout *carouselControllLayout = new QHBoxLayout(carouselControllWidget); + carouselControllWidget->setLayout(carouselControllLayout); + carouselControllLayout->setContentsMargins(0, 0, 0, 4); + + QPushButton *previous = new QPushButton(carouselControllWidget); + + StyleHelper::BlueIconButton(previous, + Style::getPixmap(":/gui/icons/handle_left_arrow.svg", + Style::getColor(json::theme::content_inverse)), + "previousButton"); + + carouselControllLayout->addWidget(previous); + carouselControllLayout->addSpacerItem( + new QSpacerItem(1, 1, QSizePolicy::Expanding, QSizePolicy::Expanding)); + + for(int i = 0; i < numberOfPages; i++) { + + QPushButton *htmlPageButton = new QPushButton("", carouselControllWidget); + htmlPageButton->setCheckable(true); + + if(i == 0) { + htmlPageButton->setChecked(true); + } + + connect(htmlPageButton, &QPushButton::toggled, this, [=](bool toggled) { + if(toggled) { + versionCarouselWithControlls->setCurrentIndex(i); + } + }); + + carouselControllLayout->addWidget(htmlPageButton, 0, Qt::AlignBottom); + // carouselControllLayout->setAlignment(htmlPageButton, Qt::AlignBottom); + m_carouselButtons->addButton(htmlPageButton, i); + Style::setStyle(htmlPageButton, style::properties::button::whatsNewButton, true, true); + htmlPageButton->setFixedHeight(Style::getAttribute(json::global::radius_2).toInt()); + } + + QPushButton *next = new QPushButton(carouselControllWidget); + StyleHelper::BlueIconButton(next, + Style::getPixmap(":/gui/icons/handle_right_arrow.svg", + Style::getColor(json::theme::content_inverse)), + "nextButton"); + + carouselControllLayout->addSpacerItem( + new QSpacerItem(1, 1, QSizePolicy::Expanding, QSizePolicy::Expanding)); + carouselControllLayout->addWidget(next); + + connect(previous, &QPushButton::clicked, this, [=]() { + int currentIndex = m_carouselButtons->checkedId(); + int previousIndex = (currentIndex - 1 + numberOfPages) % numberOfPages; + m_carouselButtons->button(previousIndex)->setChecked(true); + }); + + connect(next, &QPushButton::clicked, this, [=]() { + int currentIndex = m_carouselButtons->checkedId(); + int nextIndex = (currentIndex + 1) % numberOfPages; + m_carouselButtons->button(nextIndex)->setChecked(true); + }); + + carouselContentLayout->addWidget(carouselControllWidget, 1); + } + + m_carouselWidget->addWidget(carouselContent); +} + +QString WhatsNewOverlay::getHtmlPageContent(QString fileName) +{ + QFile file(fileName); + file.open(QIODevice::ReadOnly); + QString text = QString(file.readAll()); + file.close(); + + return text; +} + +#include "moc_whatsnewoverlay.cpp" + +#include diff --git a/gui/style/qss/properties/button/whatsNewButton.qss b/gui/style/qss/properties/button/whatsNewButton.qss new file mode 100644 index 0000000000..0afd9de96b --- /dev/null +++ b/gui/style/qss/properties/button/whatsNewButton.qss @@ -0,0 +1,27 @@ +QPushButton[&&property&&=true] { + height: &radius_1&; + width: &radius_1&; + border-radius: &radius_1&; + background-color: &content_silent&; + color: &content_inverse&; + font-weight: 700; + /* qproperty-iconSize: &unit_4&px; */ +} +QPushButton[&&property&&=true]:hover { + background-color: &content_default&; + color: &content_inverse&; +} + +QPushButton[&&property&&=true]:pressed { + background-color: &content_silent&; + color: &content_inverse&; +} + +QPushButton[&&property&&=true]:disabled { + background-color: &interactive_subtle_disabled&; +} + +QPushButton[&&property&&=true]:checked { + background-color: &content_default&; + color: &content_inverse&; +}