diff --git a/ManiVault/cmake/CMakeMvSourcesApplication.cmake b/ManiVault/cmake/CMakeMvSourcesApplication.cmake index 7ca3167bc..4d9db74ff 100644 --- a/ManiVault/cmake/CMakeMvSourcesApplication.cmake +++ b/ManiVault/cmake/CMakeMvSourcesApplication.cmake @@ -301,7 +301,6 @@ if(MV_USE_ERROR_LOGGING) list(APPEND PRIVATE_APPLICATION_HEADERS src/private/SentryErrorLogger.h src/private/CrashReportDialog.h - src/private/ErrorLoggingConsentDialog.h ) endif() @@ -320,7 +319,6 @@ if(MV_USE_ERROR_LOGGING) list(APPEND PRIVATE_APPLICATION_SOURCES src/private/SentryErrorLogger.cpp src/private/CrashReportDialog.cpp - src/private/ErrorLoggingConsentDialog.cpp ) endif() @@ -329,6 +327,19 @@ set(PRIVATE_APPLICATION_FILES ${PRIVATE_APPLICATION_SOURCES} ) +set(PRIVATE_APP_FEATURES_HEADERS + src/private/AppFeaturesDialog.h +) + +set(PRIVATE_APP_FEATURES_SOURCES + src/private/AppFeaturesDialog.cpp +) + +set(PRIVATE_APP_FEATURES_FILES + ${PRIVATE_APP_FEATURES_HEADERS} + ${PRIVATE_APP_FEATURES_SOURCES} +) + set(PRIVATE_STARTUP_PROJECT_HEADERS src/private/StartupProjectsModel.h src/private/StartupProjectsFilterModel.h @@ -448,6 +459,7 @@ set(PRIVATE_HEADERS ${PRIVATE_MISCELLANEOUS_HEADERS} ${PRIVATE_ACTIONS_HEADERS} ${PRIVATE_STARTUP_PROJECT_HEADERS} + ${PRIVATE_APP_FEATURES_HEADERS} ) set(PRIVATE_SOURCES @@ -460,6 +472,7 @@ set(PRIVATE_SOURCES ${PRIVATE_MISCELLANEOUS_SOURCES} ${PRIVATE_ACTIONS_SOURCES} ${PRIVATE_STARTUP_PROJECT_SOURCES} + ${PRIVATE_APP_FEATURES_SOURCES} ${PRIVATE_HEADERS} ) @@ -469,6 +482,7 @@ list(REMOVE_DUPLICATES PRIVATE_SOURCES) source_group(Core FILES ${PRIVATE_CORE_FILES}) source_group(Actions FILES ${PRIVATE_ACTION_FILES}) source_group(Application FILES ${PRIVATE_APPLICATION_FILES} ${MAIN_SOURCES}) +source_group(AppFeatures FILES ${PRIVATE_APP_FEATURES_FILES}) source_group(StartupProject FILES ${PRIVATE_STARTUP_PROJECT_FILES}) source_group(Managers\\Layout FILES ${PRIVATE_LAYOUT_MANAGER_FILES}) source_group(Managers\\Plugin FILES ${PRIVATE_PLUGIN_MANAGER_FILES}) diff --git a/ManiVault/cmake/CMakeMvSourcesPublic.cmake b/ManiVault/cmake/CMakeMvSourcesPublic.cmake index d06dd48ef..1c9909cb2 100644 --- a/ManiVault/cmake/CMakeMvSourcesPublic.cmake +++ b/ManiVault/cmake/CMakeMvSourcesPublic.cmake @@ -315,6 +315,27 @@ set(PUBLIC_TASK_ACTIONS_FILES ${PUBLIC_TASK_ACTIONS_SOURCES} ) +set(PUBLIC_APP_FEATURE_ACTIONS_HEADERS + src/actions/AppFeatureAction.h + src/actions/ErrorLoggingAppFeatureAction.h + src/actions/TutorialsAppFeatureAction.h + src/actions/ProjectsAppFeatureAction.h + src/actions/VideosAppFeatureAction.h +) + +set(PUBLIC_APP_FEATURE_ACTIONS_SOURCES + src/actions/AppFeatureAction.cpp + src/actions/ErrorLoggingAppFeatureAction.cpp + src/actions/TutorialsAppFeatureAction.cpp + src/actions/ProjectsAppFeatureAction.cpp + src/actions/VideosAppFeatureAction.cpp +) + +set(PUBLIC_APP_FEATURE_ACTIONS_FILES + ${PUBLIC_APP_FEATURE_ACTIONS_HEADERS} + ${PUBLIC_APP_FEATURE_ACTIONS_SOURCES} +) + set(PUBLIC_ACTIONS_INTERNAL_HEADERS src/actions/WidgetAction.h src/actions/WidgetActionWidget.h @@ -1021,7 +1042,7 @@ set(PUBLIC_GLOBAL_SETTINGS_HEADERS src/TasksSettingsAction.h src/AppearanceSettingsAction.h src/TemporaryDirectoriesSettingsAction.h - src/ErrorLoggingSettingsAction.h + src/AppFeaturesSettingsAction.h src/PluginGlobalSettingsGroupAction.h ) @@ -1032,7 +1053,7 @@ set(PUBLIC_GLOBAL_SETTINGS_SOURCES src/TasksSettingsAction.cpp src/AppearanceSettingsAction.cpp src/TemporaryDirectoriesSettingsAction.cpp - src/ErrorLoggingSettingsAction.cpp + src/AppFeaturesSettingsAction.cpp src/PluginGlobalSettingsGroupAction.cpp ) @@ -1106,6 +1127,7 @@ set(PUBLIC_HEADERS ${PUBLIC_TOOLBAR_ACTIONS_HEADERS} ${PUBLIC_MISCELLANEOUS_ACTIONS_HEADERS} ${PUBLIC_TASK_ACTIONS_HEADERS} + ${PUBLIC_APP_FEATURE_ACTIONS_HEADERS} ${PUBLIC_ACTIONS_INTERNAL_HEADERS} ${PUBLIC_WIDGETS_HEADERS} ${PUBLIC_WIDGETS_INTERNAL_HEADERS} @@ -1155,6 +1177,7 @@ set(PUBLIC_SOURCES ${PUBLIC_TOOLBAR_ACTIONS_SOURCES} ${PUBLIC_MISCELLANEOUS_ACTIONS_SOURCES} ${PUBLIC_TASK_ACTIONS_SOURCES} + ${PUBLIC_APP_FEATURE_ACTIONS_SOURCES} ${PUBLIC_ACTIONS_INTERNAL_SOURCES} ${PUBLIC_WIDGETS_SOURCES} ${PUBLIC_WIDGETS_INTERNAL_SOURCES} @@ -1218,6 +1241,7 @@ source_group(Actions\\Toolbar FILES ${PUBLIC_TOOLBAR_ACTIONS_FILES}) source_group(Actions\\Miscellaneous FILES ${PUBLIC_MISCELLANEOUS_ACTIONS_FILES}) source_group(Actions\\Task FILES ${PUBLIC_TASK_ACTIONS_FILES}) source_group(Actions\\Internal FILES ${PUBLIC_ACTIONS_INTERNAL_FILES}) +source_group(Actions\\AppFeature FILES ${PUBLIC_APP_FEATURE_ACTIONS_FILES}) source_group(Widgets FILES ${PUBLIC_WIDGETS_FILES}) source_group(Widgets\\Internal FILES ${PUBLIC_WIDGETS_INTERNAL_FILES}) source_group(Renderers FILES ${PUBLIC_RENDERERS_FILES}) diff --git a/ManiVault/res/ResourcesCore.qrc b/ManiVault/res/ResourcesCore.qrc index 622b674ab..1e6b4e454 100644 --- a/ManiVault/res/ResourcesCore.qrc +++ b/ManiVault/res/ResourcesCore.qrc @@ -85,7 +85,10 @@ html/markdown.html - html/ErrorLoggingConsent.html + html/AppFeatureErrorLogging.html + html/AppFeatureProjects.html + html/AppFeatureTutorials.html + html/AppFeatureVideos.html json/tutorials.schema.json diff --git a/ManiVault/res/html/AppFeatureErrorLogging.html b/ManiVault/res/html/AppFeatureErrorLogging.html new file mode 100644 index 000000000..6fd1ef9ac --- /dev/null +++ b/ManiVault/res/html/AppFeatureErrorLogging.html @@ -0,0 +1,19 @@ + + + +

Automated error reporting

+

ManiVault Studio can automatically report bugs and crashes using Sentry. This might help us fix issues faster and improve the application for everyone.

+

What we collect:

+ +

What we don’t collect:

+ +

You can opt out of automated error reporting at any time in the application settings.

+ + diff --git a/ManiVault/res/html/AppFeatureProjects.html b/ManiVault/res/html/AppFeatureProjects.html new file mode 100644 index 000000000..52e0ec1fb --- /dev/null +++ b/ManiVault/res/html/AppFeatureProjects.html @@ -0,0 +1,20 @@ + + + +

Projects

+

ManiVault Studio can optionally download dynamic projects content. When you enable this feature, the application connects to servers to:

+ +

You will find this feature at the start page, and posibly during startup of ManiVault Studio.

+

Projects listings and projects might originate from:

+ +

Please note that we do not track which projects you download.

+

If you prefer not to allow automatic downloads of projects, you can disable this feature at any time in the application settings.

+ + + diff --git a/ManiVault/res/html/AppFeatureTutorials.html b/ManiVault/res/html/AppFeatureTutorials.html new file mode 100644 index 000000000..d29c8c288 --- /dev/null +++ b/ManiVault/res/html/AppFeatureTutorials.html @@ -0,0 +1,20 @@ + + + +

Learning center tutorials

+

As part of the learning center, ManiVault Studio can optionally download dynamic tutorial content. When you enable this feature, the application connects to servers to:

+ +

You will find this feature at the start page of ManiVault Studio and in the help menu.

+

Tutorials listings and projects might originate from:

+ +

+

Please note that we do not track which tutorials you download.

+

If you prefer not to allow automatic downloads of start page content, you can disable this feature at any time in the application settings.

+ + diff --git a/ManiVault/res/html/AppFeatureVideos.html b/ManiVault/res/html/AppFeatureVideos.html new file mode 100644 index 000000000..61282b71b --- /dev/null +++ b/ManiVault/res/html/AppFeatureVideos.html @@ -0,0 +1,14 @@ + + + +

Learning center videos

+

As part of the learning center, ManiVault Studio can optionally download dynamic video content. When you enable this feature, the application connects to servers to fetch available videos listings (the videos are watched by opening a browser window). Videos listings might originate from: +

+

+

Please note that we do not track which videos you view.

+

If you prefer not to allow automatic downloads of videos content, you can disable this feature at any time in the application settings.

+ + diff --git a/ManiVault/res/html/ErrorLoggingConsent.html b/ManiVault/res/html/ErrorLoggingConsent.html deleted file mode 100644 index 2556e2aa4..000000000 --- a/ManiVault/res/html/ErrorLoggingConsent.html +++ /dev/null @@ -1,25 +0,0 @@ - - - -

Automated error reporting

-

ManiVault Studio can automatically report bugs and crashes using Sentry. This might helps us fix issues faster and improve the application for everyone.

-

What we collect: -

-

What we don’t collect:

- - -

Sentry has a privacy policy that outlines how they collect, use, and protect personal information. You can view the full policy here.

- -

You can opt out at any time in the application settings.

- -

We also support self-hosted Sentry servers. Simply modify the error logging DSN in the application settings to point to your own self-hosted Sentry server to mitigate possible privacy concerns.

- - - \ No newline at end of file diff --git a/ManiVault/src/AbstractErrorManager.h b/ManiVault/src/AbstractErrorManager.h index 2411c1195..eb302d13d 100644 --- a/ManiVault/src/AbstractErrorManager.h +++ b/ManiVault/src/AbstractErrorManager.h @@ -84,7 +84,6 @@ class CORE_EXPORT AbstractErrorManager : public AbstractManager AbstractErrorLogger* _errorLogger; /** Pointer to the error logger */ friend class AbstractErrorLogger; - friend class gui::ErrorLoggingSettingsAction; }; } diff --git a/ManiVault/src/AbstractSettingsManager.h b/ManiVault/src/AbstractSettingsManager.h index 406fecdf9..8383515a0 100644 --- a/ManiVault/src/AbstractSettingsManager.h +++ b/ManiVault/src/AbstractSettingsManager.h @@ -8,12 +8,12 @@ #include "actions/TriggerAction.h" +#include "AppFeaturesSettingsAction.h" #include "ParametersSettingsAction.h" #include "MiscellaneousSettingsAction.h" #include "TasksSettingsAction.h" #include "AppearanceSettingsAction.h" #include "TemporaryDirectoriesSettingsAction.h" -#include "ErrorLoggingSettingsAction.h" #include "PluginGlobalSettingsGroupAction.h" namespace mv { @@ -56,12 +56,20 @@ class CORE_EXPORT AbstractSettingsManager : public AbstractManager public: // Global settings actions + //virtual gui::AppFeaturesSettingsAction& getAppFeaturesSettingsAction() = 0; virtual gui::ParametersSettingsAction& getParametersSettings() = 0; virtual gui::MiscellaneousSettingsAction& getMiscellaneousSettings() = 0; virtual gui::TasksSettingsAction& getTasksSettingsAction() = 0; virtual gui::AppearanceSettingsAction& getAppearanceSettingsAction() = 0; virtual gui::TemporaryDirectoriesSettingsAction& getTemporaryDirectoriesSettingsAction() = 0; - virtual gui::ErrorLoggingSettingsAction& getErrorLoggingSettingsAction() = 0; + + virtual const gui::AppFeaturesSettingsAction& getAppFeaturesSettingsAction() const = 0; + virtual const gui::ParametersSettingsAction& getParametersSettings() const = 0; + virtual const gui::MiscellaneousSettingsAction& getMiscellaneousSettings() const = 0; + virtual const gui::TasksSettingsAction& getTasksSettingsAction() const = 0; + virtual const gui::AppearanceSettingsAction& getAppearanceSettingsAction() const = 0; + virtual const gui::TemporaryDirectoriesSettingsAction& getTemporaryDirectoriesSettingsAction() const = 0; + /** * Get plugin global settings for plugin \p kind diff --git a/ManiVault/src/AppFeaturesSettingsAction.cpp b/ManiVault/src/AppFeaturesSettingsAction.cpp new file mode 100644 index 000000000..fe2f08491 --- /dev/null +++ b/ManiVault/src/AppFeaturesSettingsAction.cpp @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// A corresponding LICENSE file is located in the root directory of this source tree +// Copyright (C) 2023 BioVault (Biomedical Visual Analytics Unit LUMC - TU Delft) + +#include "AppFeaturesSettingsAction.h" + +#include "actions/VerticalGroupAction.h" + +namespace mv::gui +{ + +AppFeaturesSettingsAction::AppFeaturesSettingsAction(QObject* parent) : + GlobalSettingsGroupAction(parent, "App Features", false), + _errorLoggingAppFeatureAction(this), + _projectsAppFeatureAction(this), + _tutorialsAppFeatureAction(this), + _videosAppFeatureAction(this) +{ + +#ifdef ERROR_LOGGING + addAction(&_errorLoggingAppFeatureAction); +#endif + + auto downloadableContentAppFeaturesAction = new VerticalGroupAction(this, "Downloadable Content"); + + downloadableContentAppFeaturesAction->setShowLabels(false); + downloadableContentAppFeaturesAction->setDefaultWidgetFlag(GroupAction::WidgetFlag::NoMargins); + + addAction(&_projectsAppFeatureAction); + addAction(&_tutorialsAppFeatureAction); + addAction(&_videosAppFeatureAction); +} + +/* +const gui::TriggerAction& AppFeaturesSettingsAction::getLoggingAskConsentDialogAction() const +{ + return mv::errors().getLoggingAskConsentDialogAction(); +} + +const gui::ToggleAction& AppFeaturesSettingsAction::getUserHasOptedAction() const +{ + return mv::errors().getLoggingUserHasOptedAction(); +} + +const gui::ToggleAction& AppFeaturesSettingsAction::getEnabledAction() const +{ + return mv::errors().getLoggingEnabledAction(); +} + +const gui::StringAction& AppFeaturesSettingsAction::getDsnAction() const +{ + return mv::errors().getLoggingDsnAction(); +} + +const gui::ToggleAction& AppFeaturesSettingsAction::getShowCrashReportDialogAction() const +{ + return mv::errors().getLoggingShowCrashReportDialogAction(); +} +*/ + +} diff --git a/ManiVault/src/AppFeaturesSettingsAction.h b/ManiVault/src/AppFeaturesSettingsAction.h new file mode 100644 index 000000000..5c2d50240 --- /dev/null +++ b/ManiVault/src/AppFeaturesSettingsAction.h @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// A corresponding LICENSE file is located in the root directory of this source tree +// Copyright (C) 2023 BioVault (Biomedical Visual Analytics Unit LUMC - TU Delft) + +#pragma once + +#include "GlobalSettingsGroupAction.h" + +#include "actions/ErrorLoggingAppFeatureAction.h" +#include "actions/ProjectsAppFeatureAction.h" +#include "actions/TutorialsAppFeatureAction.h" +#include "actions/VideosAppFeatureAction.h" + +namespace mv::gui +{ + +/** + * App features settings action class + * + * Groups all app features settings for the application + * + * @author Thomas Kroes + */ +class CORE_EXPORT AppFeaturesSettingsAction final : public GlobalSettingsGroupAction +{ +public: + + /** + * Construct the error logging settings action with a parent object + * @param parent Pointer to parent object + */ + AppFeaturesSettingsAction(QObject* parent); + +public: // Action getters + + const ErrorLoggingAppFeatureAction& getErrorLoggingAppFeatureAction() const { return _errorLoggingAppFeatureAction; } + const ProjectsAppFeatureAction& getProjectsAppFeatureAction() const { return _projectsAppFeatureAction; } + const TutorialsAppFeatureAction& getTutorialsAppFeatureAction() const { return _tutorialsAppFeatureAction; } + const VideosAppFeatureAction& getVideosAppFeatureAction() const { return _videosAppFeatureAction; } + +private: + ErrorLoggingAppFeatureAction _errorLoggingAppFeatureAction; /** App feature action for configuring error logging */ + ProjectsAppFeatureAction _projectsAppFeatureAction; /** App feature action for configuring projects dynamic content */ + TutorialsAppFeatureAction _tutorialsAppFeatureAction; /** App feature action for configuring tutorials dynamic content */ + VideosAppFeatureAction _videosAppFeatureAction; /** App feature action for configuring videos dynamic content */ +}; + +} diff --git a/ManiVault/src/CoreInterface.h b/ManiVault/src/CoreInterface.h index 40334f457..e93189ebf 100644 --- a/ManiVault/src/CoreInterface.h +++ b/ManiVault/src/CoreInterface.h @@ -247,6 +247,14 @@ CORE_EXPORT inline AbstractSettingsManager& settings() { return core()->getSettingsManager(); } +/** +* Convenience function to obtain access to the settings manager in the core +* @return Reference to abstract settings manager +*/ +CORE_EXPORT inline const AbstractSettingsManager& constSettings() { + return core()->getSettingsManager(); +} + /** * Convenience function to obtain access to the help manager in the core * @return Reference to abstract help manager diff --git a/ManiVault/src/ErrorLoggingSettingsAction.cpp b/ManiVault/src/ErrorLoggingSettingsAction.cpp deleted file mode 100644 index 0eb0c1cbe..000000000 --- a/ManiVault/src/ErrorLoggingSettingsAction.cpp +++ /dev/null @@ -1,42 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-or-later -// A corresponding LICENSE file is located in the root directory of this source tree -// Copyright (C) 2023 BioVault (Biomedical Visual Analytics Unit LUMC - TU Delft) - -#include "ErrorLoggingSettingsAction.h" - -#include "CoreInterface.h" - -namespace mv::gui -{ - -ErrorLoggingSettingsAction::ErrorLoggingSettingsAction(QObject* parent) : - GlobalSettingsGroupAction(parent, "Error logging", false) -{ -} - -const gui::TriggerAction& ErrorLoggingSettingsAction::getLoggingAskConsentDialogAction() const -{ - return mv::errors().getLoggingAskConsentDialogAction(); -} - -const gui::ToggleAction& ErrorLoggingSettingsAction::getUserHasOptedAction() const -{ - return mv::errors().getLoggingUserHasOptedAction(); -} - -const gui::ToggleAction& ErrorLoggingSettingsAction::getEnabledAction() const -{ - return mv::errors().getLoggingEnabledAction(); -} - -const gui::StringAction& ErrorLoggingSettingsAction::getDsnAction() const -{ - return mv::errors().getLoggingDsnAction(); -} - -const gui::ToggleAction& ErrorLoggingSettingsAction::getShowCrashReportDialogAction() const -{ - return mv::errors().getLoggingShowCrashReportDialogAction(); -} - -} diff --git a/ManiVault/src/ErrorLoggingSettingsAction.h b/ManiVault/src/ErrorLoggingSettingsAction.h deleted file mode 100644 index 222786153..000000000 --- a/ManiVault/src/ErrorLoggingSettingsAction.h +++ /dev/null @@ -1,42 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-or-later -// A corresponding LICENSE file is located in the root directory of this source tree -// Copyright (C) 2023 BioVault (Biomedical Visual Analytics Unit LUMC - TU Delft) - -#pragma once - -#include "GlobalSettingsGroupAction.h" - -#include "actions/ToggleAction.h" -#include "actions/StringAction.h" -#include "actions/TriggerAction.h" - -namespace mv::gui -{ - -/** - * Error logging settings action class - * - * Groups all error logging settings for the application - * - * @author Thomas Kroes - */ -class CORE_EXPORT ErrorLoggingSettingsAction final : public GlobalSettingsGroupAction -{ -public: - - /** - * Construct the error logging settings action with a parent object - * @param parent Pointer to parent object - */ - ErrorLoggingSettingsAction(QObject* parent); - -public: // Action getters - - const gui::TriggerAction& getLoggingAskConsentDialogAction() const; /** Get action for asking the user for consent to log errors */ - const gui::ToggleAction& getUserHasOptedAction() const; /** Get action for user has opted */ - const gui::ToggleAction& getEnabledAction() const; /** Get action for logging enabled */ - const gui::StringAction& getDsnAction() const; /** Get action for logging data source name (DSN) */ - const gui::ToggleAction& getShowCrashReportDialogAction() const; /** Get action for showing a crash report dialog when the application fails */ -}; - -} diff --git a/ManiVault/src/PluginFactory.cpp b/ManiVault/src/PluginFactory.cpp index 38b60dad2..5478d591b 100644 --- a/ManiVault/src/PluginFactory.cpp +++ b/ManiVault/src/PluginFactory.cpp @@ -28,13 +28,17 @@ PluginFactory::PluginFactory(Type type, const QString& title) : _allowPluginCreationFromStandardGui(true), _pluginMetadata(*this), _tutorialsDsnsAction(this, "Tutorials DSNs"), - _projectsDsnsAction(this, "Projects DSNs") + _projectsDsnsAction(this, "Projects DSNs"), + _videosDsnsAction(this, "Videos DSNs") { _tutorialsDsnsAction.setIconByName("globe"); _tutorialsDsnsAction.setToolTip(QString("Data Source Names for %1 tutorials").arg(title)); _projectsDsnsAction.setIconByName("globe"); _projectsDsnsAction.setToolTip(QString("Data Source Names for %1 projects").arg(title)); + + _videosDsnsAction.setIconByName("globe"); + _videosDsnsAction.setToolTip(QString("Data Source Names for %1 videos").arg(title)); } QString PluginFactory::getKind() const diff --git a/ManiVault/src/PluginFactory.h b/ManiVault/src/PluginFactory.h index 07c1069b6..92570d18a 100644 --- a/ManiVault/src/PluginFactory.h +++ b/ManiVault/src/PluginFactory.h @@ -338,6 +338,7 @@ class CORE_EXPORT PluginFactory : public gui::WidgetAction gui::StringsAction& getTutorialsDsnsAction() { return _tutorialsDsnsAction; } gui::StringsAction& getProjectsDsnsAction() { return _projectsDsnsAction; } + gui::StringsAction& getVideosDsnsAction() { return _videosDsnsAction; } signals: @@ -420,7 +421,8 @@ class CORE_EXPORT PluginFactory : public gui::WidgetAction PluginMetadata _pluginMetadata; /** Plugin metadata */ QIcon _categoryIcon; /** Category icon */ gui::StringsAction _tutorialsDsnsAction; /** Action for editing the tutorials Data Source Names */ - gui::StringsAction _projectsDsnsAction; /** Action for editing the project Data Source Names */ + gui::StringsAction _projectsDsnsAction; /** Action for editing the projects Data Source Names */ + gui::StringsAction _videosDsnsAction; /** Action for editing the videos Data Source Names */ }; } diff --git a/ManiVault/src/actions/AppFeatureAction.cpp b/ManiVault/src/actions/AppFeatureAction.cpp new file mode 100644 index 000000000..760fc3525 --- /dev/null +++ b/ManiVault/src/actions/AppFeatureAction.cpp @@ -0,0 +1,213 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// A corresponding LICENSE file is located in the root directory of this source tree +// Copyright (C) 2023 BioVault (Biomedical Visual Analytics Unit LUMC - TU Delft) + +#include "AppFeatureAction.h" + +using namespace mv::util; + +namespace mv::gui { + +AppFeatureAction::AppFeatureAction(QObject* parent, const QString& title) : + HorizontalGroupAction(parent, title), + _enabledAction(this, title, false), + _summaryAction(this, "Summary"), + _userHasOptedAction(this, "User has opted", false), + _descriptionAction(this, "Description"), + _settingsAction(this, QString("%1 Settings").arg(title)) +{ + setShowLabels(false); + + auto settingsPrefix = QString("AppFeatures/%1/").arg(title); + + settingsPrefix.replace(" ", ""); + + setSettingsPrefix(settingsPrefix); + + _enabledAction.setSettingsPrefix(QString("%1/Enabled").arg(WidgetAction::getSettingsPrefix())); + _userHasOptedAction.setSettingsPrefix(QString("%1/UserHasOpted").arg(WidgetAction::getSettingsPrefix())); + + _enabledAction.setDefaultWidgetFlags(ToggleAction::WidgetFlag::ToggleImage); + + _summaryAction.setEnabled(false); + _summaryAction.setDefaultWidgetFlags(StringAction::WidgetFlag::LineEdit); + _summaryAction.setToolTip(QString("%1 summary").arg(title)); + _summaryAction.setStretch(1); + + _descriptionAction.setIconByName("circle-info"); + _descriptionAction.setToolTip(QString("%1 settings").arg(title)); + _descriptionAction.setConfigurationFlag(ConfigurationFlag::ForceCollapsedInGroup); + _descriptionAction.setDefaultWidgetFlags(StringAction::WidgetFlag::TextBrowser); + _descriptionAction.setPopupSizeHint(QSize(550, 350)); + + _settingsAction.setVisible(false); + _settingsAction.setIconByName("gear"); + _settingsAction.setToolTip(QString("%1 settings").arg(title)); + _settingsAction.setConfigurationFlag(ConfigurationFlag::ForceCollapsedInGroup); + _settingsAction.setPopupSizeHint(QSize(400, 10)); + + HorizontalGroupAction::addAction(&_enabledAction); + HorizontalGroupAction::addAction(&_summaryAction); + HorizontalGroupAction::addAction(&_descriptionAction); + HorizontalGroupAction::addAction(&_settingsAction); + + const auto updateActionsReadOnly = [this]() -> void { + _settingsAction.setEnabled(_enabledAction.isChecked()); + _descriptionAction.setEnabled(_enabledAction.isChecked()); + }; + + updateActionsReadOnly(); + + connect(&_enabledAction, &ToggleAction::toggled, this, [this, updateActionsReadOnly](bool toggled) -> void { + + if (toggled) { + ConsentDialog consentDialog(this); + + if (consentDialog.exec() == QDialog::Accepted) { + _userHasOptedAction.setChecked(true); + _enabledAction.setChecked(true); + } + else { + _enabledAction.setChecked(false); + } + _settingsAction.setEnabled(_enabledAction.isChecked()); + _descriptionAction.setEnabled(_enabledAction.isChecked()); + } + + updateActionsReadOnly(); + //mv::help().addNotification("App Feature", QString("%1 app feature has been %2").arg(text(), toggled ? "enabled" : "disabled"), StyledIcon(toggled ? "toggle-on" : "toggle-off")); + }); + + if (hasSetting(PrefixType::UserHasOpted)) { + if (getUserHasOptedFromSettings()) { + _enabledAction.setChecked(getEnabledFromSettings()); + } + } +} + +void AppFeatureAction::addAction(WidgetAction* action, std::int32_t widgetFlags /*= -1*/, WidgetConfigurationFunction widgetConfigurationFunction /*= WidgetConfigurationFunction()*/, bool load) +{ + _settingsAction.addAction(action, widgetFlags, widgetConfigurationFunction, load); + _settingsAction.setVisible(true); +} + +bool AppFeatureAction::getEnabled() const +{ + return _enabledAction.isChecked(); +} + +QString AppFeatureAction::getResourceLocation() const +{ + return _resourceLocation; +} + +QString AppFeatureAction::getSettingsPrefix(const PrefixType& prefixType) const +{ + switch (prefixType) { + case PrefixType::Enabled: + return QString("AppFeatures/%1/Enabled").arg(text()); + + case PrefixType::UserHasOpted: + return QString("AppFeatures/%1/UserHasOpted").arg(text()); + } + + return {}; +} + +bool AppFeatureAction::hasSetting(const PrefixType& prefixType) const +{ + return Application::current()->hasSetting(getSettingsPrefix(prefixType)); +} + +void AppFeatureAction::loadDescriptionFromResource(const QString& resourceLocation) +{ + _resourceLocation = resourceLocation; + + QFile appFeatureHtmlFile(resourceLocation); + + if (appFeatureHtmlFile.open(QIODevice::ReadOnly)) { + _descriptionAction.setString(appFeatureHtmlFile.readAll()); + appFeatureHtmlFile.close(); + } + else { + qWarning() << QString("No description available for %1: %2").arg(text(), appFeatureHtmlFile.errorString()); + } +} + +bool AppFeatureAction::getEnabledFromSettings() const +{ + if (hasSetting(PrefixType::Enabled)) + return Application::current()->getSetting(getSettingsPrefix(PrefixType::Enabled)).toBool(); + + return false; +} + +bool AppFeatureAction::getUserHasOptedFromSettings() const +{ + if (hasSetting(PrefixType::UserHasOpted)) + return Application::current()->getSetting(getSettingsPrefix(PrefixType::UserHasOpted)).toBool(); + + return false; +} + +AppFeatureAction::ConsentDialog::ConsentDialog(AppFeatureAction* appFeatureAction, QWidget* parent /*= nullptr*/) : + QDialog(parent), + _acceptPushButton("Accept"), + _cancelPushButton("Cancel"), + _decideLaterPushButton("Decide Later") +{ + setWindowModality(Qt::ApplicationModal); + setWindowFlag(Qt::Dialog); + setWindowFlag(Qt::WindowTitleHint); + setWindowFlag(Qt::WindowStaysOnTopHint); + setWindowTitle(QString("Turn on %1 App Feature").arg(appFeatureAction->text())); + setWindowIcon(StyledIcon("toggle-on")); + move(QApplication::activeWindow()->geometry().center() - rect().center()); + + _notificationLabel.setWordWrap(true); + _notificationLabel.setOpenExternalLinks(true); + + QFile errorLoggingConsentHtmlFile(appFeatureAction->getResourceLocation()); + + if (errorLoggingConsentHtmlFile.open(QIODevice::ReadOnly)) { + _notificationLabel.setText(errorLoggingConsentHtmlFile.readAll()); + errorLoggingConsentHtmlFile.close(); + } + + _layout.addWidget(&_notificationLabel); + _layout.addStretch(1); + + _buttonsLayout.setContentsMargins(9, 30, 9, 9); + + _buttonsLayout.addStretch(1); + _buttonsLayout.addWidget(&_acceptPushButton); + _buttonsLayout.addWidget(&_cancelPushButton); + + const auto& constErrorManager = mv::errors(); + + if (!constErrorManager.getLoggingUserHasOptedAction().isChecked()) + _buttonsLayout.addWidget(&_decideLaterPushButton); + + _layout.addLayout(&_buttonsLayout); + + setLayout(&_layout); + + connect(&_acceptPushButton, &QPushButton::clicked, this, [this, &constErrorManager]() -> void { + accept(); + }); + + connect(&_cancelPushButton, &QPushButton::clicked, this, [this, &constErrorManager]() -> void { + reject(); + }); + + connect(&_decideLaterPushButton, &QPushButton::clicked, this, [this]() -> void { + reject(); + }); +} + +QSize AppFeatureAction::ConsentDialog::sizeHint() const +{ + return { 500, 300 }; +} + +} diff --git a/ManiVault/src/actions/AppFeatureAction.h b/ManiVault/src/actions/AppFeatureAction.h new file mode 100644 index 000000000..00ab7dd32 --- /dev/null +++ b/ManiVault/src/actions/AppFeatureAction.h @@ -0,0 +1,163 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// A corresponding LICENSE file is located in the root directory of this source tree +// Copyright (C) 2023 BioVault (Biomedical Visual Analytics Unit LUMC - TU Delft) + +#pragma once + +#include "HorizontalGroupAction.h" +#include "ToggleAction.h" +#include "VerticalGroupAction.h" + +namespace mv::gui { + +/** + * App feature action class + * + * Base vertical group action class for grouping app feature settings + * + * Note: This action is developed for internal use only + * + * @author Thomas Kroes + */ +class CORE_EXPORT AppFeatureAction : public HorizontalGroupAction +{ + Q_OBJECT + +public: + + /** Enum for the prefix type */ + enum class PrefixType + { + Enabled, /** Prefix for the enabled type */ + UserHasOpted, /** Prefix for the user has opted type */ + }; + +protected: + + /** Dialog for giving consent for a specific app feature */ + class CORE_EXPORT ConsentDialog final : public QDialog + { + protected: + + /** + * Construct a dialog with \p parent + * @param appFeatureAction Pointer to owning app feature action + * @param parent Pointer to parent widget + */ + ConsentDialog(AppFeatureAction* appFeatureAction, QWidget* parent = nullptr); + + /** Get preferred size */ + QSize sizeHint() const override; + + /** Get minimum size hint*/ + QSize minimumSizeHint() const override { + return sizeHint(); + } + + private: + QVBoxLayout _layout; /** Main layout */ + QLabel _notificationLabel; /** Notification label */ + QHBoxLayout _buttonsLayout; /** Bottom buttons layout */ + QPushButton _acceptPushButton; /** Opt in of automated error reporting when clicked */ + QPushButton _cancelPushButton; /** Abort consent process */ + QPushButton _decideLaterPushButton; /** Exit the dialog and show it at next startup */ + + friend class AppFeatureAction; + }; + +public: + + /** + * Construct with pointer to \p parent object and \p title + * @param parent Pointer to parent object + * @param title Title of the action + */ + Q_INVOKABLE AppFeatureAction(QObject* parent, const QString& title); + + /** + * Add \p action to the group + * @param action Pointer to action to add + * @param widgetFlags Action widget flags (default flags if -1) + * @param widgetConfigurationFunction When set, overrides the standard widget configuration function in the widget action + * @param load Currently not used + */ + void addAction(WidgetAction* action, std::int32_t widgetFlags = -1, WidgetConfigurationFunction widgetConfigurationFunction = WidgetConfigurationFunction(), bool load = true) override; + + /** + * Get whether the app feature is enabled or not + * @return Boolean determining whether the app feature is enabled or not + */ + bool getEnabled() const; + + /** + * Get the HTML resource file location + * @return Location of the app feature description HTML resource file + */ + QString getResourceLocation() const; + +protected: + + /** + * Get the settings prefix for the given \p prefixType + * @param prefixType Prefix type to get the settings prefix for + * @return Settings prefix + */ + QString getSettingsPrefix(const PrefixType& prefixType) const; + + /** + * Get whether a setting is available + * @param prefixType Prefix type to check + * @return Pointer to the settings action + */ + bool hasSetting(const PrefixType& prefixType) const; + + /** + * Load the description from \p resourceName + * @param resourceLocation Location of the resource to load + */ + void loadDescriptionFromResource(const QString& resourceLocation); + +private: // Settings + + /** + * Get whether the feature is on or off from settings + * @return Boolean determining whether the feature is on or off (off if the setting is not found) + */ + bool getEnabledFromSettings() const; + + /** + * Get whether the user has opted for the feature or not from settings + * @return Boolean determining whether the user has opted for the feature or not (off if the setting is not found) + */ + bool getUserHasOptedFromSettings() const; + +public: // Action getters + + const ToggleAction& getEnabledAction() const { return _enabledAction; } + const StringAction& getSummaryAction() const { return _summaryAction; } + const ToggleAction& getUserHasOptedAction() const { return _userHasOptedAction; } + const StringAction& getDescriptionAction() const { return _descriptionAction; } + +protected: // Action getters + + ToggleAction& getEnabledAction() { return _enabledAction; } + StringAction& getSummaryAction() { return _summaryAction; } + ToggleAction& getUserHasOptedAction() { return _userHasOptedAction; } + StringAction& getDescriptionAction() { return _descriptionAction; } + +private: + ToggleAction _enabledAction; /** Determines whether the app feature is enabled or not */ + StringAction _summaryAction; /** Short one-liner that describes the app feature */ + ToggleAction _userHasOptedAction; /** Determines if the user has given permission to use the app feature */ + StringAction _descriptionAction; /** App feature description */ + VerticalGroupAction _settingsAction; /** App feature settings */ + QString _resourceLocation; /** Location of the resource to load */ + + friend class AppFeaturesSettingsAction; // Allow access to private members +}; + +} + +Q_DECLARE_METATYPE(mv::gui::AppFeatureAction) + +inline const auto appFeatureActionMetaTypeId = qRegisterMetaType("mv::gui::AppFeatureAction"); diff --git a/ManiVault/src/actions/ErrorLoggingAppFeatureAction.cpp b/ManiVault/src/actions/ErrorLoggingAppFeatureAction.cpp new file mode 100644 index 000000000..9c2262063 --- /dev/null +++ b/ManiVault/src/actions/ErrorLoggingAppFeatureAction.cpp @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// A corresponding LICENSE file is located in the root directory of this source tree +// Copyright (C) 2023 BioVault (Biomedical Visual Analytics Unit LUMC - TU Delft) + +#include "ErrorLoggingAppFeatureAction.h" + +namespace mv::gui { + +ErrorLoggingAppFeatureAction::ErrorLoggingAppFeatureAction(QObject* parent, const QString& title /*= "Error Logging"*/) : + AppFeatureAction(parent, title), + _loggingDsnAction(this, "Sentry DSN", "https://211289c773dcc267b1bb536b6c3a23f7@lkebsentry.nl/2"), + _loggingShowCrashReportDialogAction(this, "Show crash report dialog", true) +{ + loadDescriptionFromResource(":/HTML/AppFeatureErrorLogging"); + + getSummaryAction().setString("Send anonymous crash reports to improve the application"); + + _loggingDsnAction.setSettingsPrefix(QString("%1/DSN").arg(WidgetAction::getSettingsPrefix())); + _loggingDsnAction.setToolTip("The Sentry error logging data source name"); + _loggingDsnAction.getValidator().setRegularExpression(QRegularExpression(R"(^https?://[a-f0-9]{32}@[a-z0-9\.-]+(:\d+)?/[\d]+$)")); + + _loggingShowCrashReportDialogAction.setSettingsPrefix(QString("%1/ShowCrashReportDialog").arg(WidgetAction::getSettingsPrefix())); + _loggingShowCrashReportDialogAction.setToolTip("Show the crash report dialog prior to sending an error report"); + + addAction(&_loggingDsnAction); + addAction(&_loggingShowCrashReportDialogAction); + + const auto allowErrorReportingChanged = [this]() -> void { + _loggingShowCrashReportDialogAction.setEnabled(getEnabledAction().isChecked()); + _loggingDsnAction.setEnabled(getEnabledAction().isChecked()); + }; + + allowErrorReportingChanged(); + + connect(&getEnabledAction(), &ToggleAction::toggled, this, allowErrorReportingChanged); +} + +} diff --git a/ManiVault/src/actions/ErrorLoggingAppFeatureAction.h b/ManiVault/src/actions/ErrorLoggingAppFeatureAction.h new file mode 100644 index 000000000..872419003 --- /dev/null +++ b/ManiVault/src/actions/ErrorLoggingAppFeatureAction.h @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// A corresponding LICENSE file is located in the root directory of this source tree +// Copyright (C) 2023 BioVault (Biomedical Visual Analytics Unit LUMC - TU Delft) + +#pragma once + +#include "AppFeatureAction.h" + +#include "actions/StringAction.h" +#include "actions/ToggleAction.h" + +namespace mv::gui { + +/** + * Error logging app feature action class + * + * App feature action class for grouping error logging settings + * + * Note: This action is developed for internal use only + * + * @author Thomas Kroes + */ +class CORE_EXPORT ErrorLoggingAppFeatureAction : public AppFeatureAction +{ + Q_OBJECT + +public: + + /** + * Construct with pointer to \p parent object and \p title + * @param parent Pointer to parent object + * @param title Title of the action + */ + Q_INVOKABLE ErrorLoggingAppFeatureAction(QObject* parent, const QString& title = "Error Logging"); + +public: // Action getters + + gui::StringAction& getLoggingDsnAction() { return _loggingDsnAction; } + gui::ToggleAction& getLoggingShowCrashReportDialogAction() { return _loggingShowCrashReportDialogAction; } + +private: + StringAction _loggingDsnAction; /** Error logging data source name action */ + ToggleAction _loggingShowCrashReportDialogAction; /** Toggle crash dialog on/off */ +}; + +} + +Q_DECLARE_METATYPE(mv::gui::ErrorLoggingAppFeatureAction) + +inline const auto errorLoggingAppFeatureActionMetaTypeId = qRegisterMetaType("mv::gui::ErrorLoggingAppFeatureAction"); diff --git a/ManiVault/src/actions/GroupAction.cpp b/ManiVault/src/actions/GroupAction.cpp index 85d7c3526..2de9bc15b 100644 --- a/ManiVault/src/actions/GroupAction.cpp +++ b/ManiVault/src/actions/GroupAction.cpp @@ -115,8 +115,7 @@ void GroupAction::setShowLabels(bool showLabels) emit showLabelsChanged(_showLabels); } -void GroupAction::addAction(WidgetAction* action, std::int32_t widgetFlags /*= -1*/, WidgetConfigurationFunction widgetConfigurationFunction /*= WidgetConfigurationFunction()*/ - , bool load) +void GroupAction::addAction(WidgetAction* action, std::int32_t widgetFlags /*= -1*/, WidgetConfigurationFunction widgetConfigurationFunction /*= WidgetConfigurationFunction()*/, bool load) { Q_ASSERT(action != nullptr); diff --git a/ManiVault/src/actions/GroupAction.h b/ManiVault/src/actions/GroupAction.h index 3af439f01..6f5f23318 100644 --- a/ManiVault/src/actions/GroupAction.h +++ b/ManiVault/src/actions/GroupAction.h @@ -172,7 +172,7 @@ class CORE_EXPORT GroupAction : public WidgetAction * @param action Pointer to action to add * @param widgetFlags Action widget flags (default flags if -1) * @param widgetConfigurationFunction When set, overrides the standard widget configuration function in the widget action - * @param load + * @param load Currently not used */ virtual void addAction(WidgetAction* action, std::int32_t widgetFlags = -1, WidgetConfigurationFunction widgetConfigurationFunction = WidgetConfigurationFunction(), bool load = true); @@ -180,25 +180,25 @@ class CORE_EXPORT GroupAction : public WidgetAction * Remove \p action from the group * @param action Pointer to action to add */ - virtual void removeAction(WidgetAction* action) final; + void removeAction(WidgetAction* action); /** Remove all actions */ - virtual void removeAllActions() final; + void removeAllActions(); /** Remove all actions (alias for GroupAction::removeAllActions()) */ - virtual void clear() final; + void clear(); /** * Get actions * @return Vector of pointers to actions */ - virtual WidgetActions getActions() final; + WidgetActions getActions(); /** * Get const actions * @return Vector of const pointers to actions */ - virtual ConstWidgetActions getConstActions() final; + ConstWidgetActions getConstActions(); /** * Get widget flags map (maps widget action pointer to widget creation flags) @@ -247,7 +247,7 @@ class CORE_EXPORT GroupAction : public WidgetAction private: /** Sort added actions based on their sort index */ - virtual void sortActions() final; + void sortActions(); public: // Serialization diff --git a/ManiVault/src/actions/ProjectsAppFeatureAction.cpp b/ManiVault/src/actions/ProjectsAppFeatureAction.cpp new file mode 100644 index 000000000..7b34b32f1 --- /dev/null +++ b/ManiVault/src/actions/ProjectsAppFeatureAction.cpp @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// A corresponding LICENSE file is located in the root directory of this source tree +// Copyright (C) 2023 BioVault (Biomedical Visual Analytics Unit LUMC - TU Delft) + +#include "ProjectsAppFeatureAction.h" + +namespace mv::gui { + +ProjectsAppFeatureAction::ProjectsAppFeatureAction(QObject* parent, const QString& title /*= "Projects"*/) : + AppFeatureAction(parent, title) +{ + loadDescriptionFromResource(":/HTML/AppFeatureProjects"); + + getSummaryAction().setString("Allow ManiVault Studio to download projects"); +} + +} diff --git a/ManiVault/src/actions/ProjectsAppFeatureAction.h b/ManiVault/src/actions/ProjectsAppFeatureAction.h new file mode 100644 index 000000000..bbdcd9e9b --- /dev/null +++ b/ManiVault/src/actions/ProjectsAppFeatureAction.h @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// A corresponding LICENSE file is located in the root directory of this source tree +// Copyright (C) 2023 BioVault (Biomedical Visual Analytics Unit LUMC - TU Delft) + +#pragma once + +#include "AppFeatureAction.h" + +#include "actions/ToggleAction.h" + +namespace mv::gui { + +/** + * Projects app feature action class + * + * App feature action class for grouping projects dynamic content settings + * + * Note: This action is developed for internal use only + * + * @author Thomas Kroes + */ +class CORE_EXPORT ProjectsAppFeatureAction : public AppFeatureAction +{ + Q_OBJECT + +public: + + /** + * Construct with pointer to \p parent object and \p title + * @param parent Pointer to parent object + * @param title Title of the action + */ + Q_INVOKABLE ProjectsAppFeatureAction(QObject* parent, const QString& title = "Projects"); + +//TODO +//public: // Action getters +// +// const ToggleAction& getTutorialsAction() const { return _tutorialsAction; } +// const ToggleAction& getProjectsAction() const { return _projectsAction; } +// +//private: +// ToggleAction _tutorialsAction; /** Toggle tutorials dynamic content */ +// ToggleAction _projectsAction; /** Toggle projects dynamic content */ +}; + +} + +Q_DECLARE_METATYPE(mv::gui::ProjectsAppFeatureAction) + +inline const auto projectsAppFeatureActionMetaTypeId = qRegisterMetaType("mv::gui::ProjectsAppFeatureAction"); diff --git a/ManiVault/src/actions/StringAction.cpp b/ManiVault/src/actions/StringAction.cpp index 94d738bc5..969b3486b 100644 --- a/ManiVault/src/actions/StringAction.cpp +++ b/ManiVault/src/actions/StringAction.cpp @@ -211,6 +211,9 @@ QWidget* StringAction::getWidget(QWidget* parent, const std::int32_t& widgetFlag if (widgetFlags & WidgetFlag::TextEdit) layout->addWidget(new StringAction::TextEditWidget(parent, this)); + if (widgetFlags & WidgetFlag::TextBrowser) + layout->addWidget(new StringAction::TextBrowserWidget(parent, this)); + widget->setLayout(layout); return widget; @@ -506,4 +509,48 @@ StringAction::TextEditWidget::TextEditWidget(QWidget* parent, StringAction* stri updatePlaceHolderText(); } +StringAction::TextBrowserWidget::TextBrowserWidget(QWidget* parent, StringAction* stringAction) : + QTextEdit(parent) +{ + setObjectName("TextBrowser"); + setAcceptDrops(true); + + const auto updateToolTip = [this, stringAction]() -> void { + setToolTip(stringAction->getString()); + }; + + updateToolTip(); + + connect(stringAction, &QAction::changed, this, updateToolTip); + + const auto updateTextBrowser = [this, stringAction]() { + QSignalBlocker blocker(this); + + const auto cacheCursorPosition = textCursor().position(); + + setHtml(stringAction->getString()); + + auto updateCursor = textCursor(); + + updateCursor.setPosition(cacheCursorPosition); + + setTextCursor(updateCursor); + }; + + const auto updatePlaceHolderText = [this, stringAction]() -> void { + setPlaceholderText(stringAction->getPlaceholderString()); + }; + + connect(stringAction, &StringAction::stringChanged, this, updateTextBrowser); + + connect(stringAction, &StringAction::placeholderStringChanged, this, updatePlaceHolderText); + + connect(this, &QTextEdit::textChanged, this, [this, stringAction]() { + stringAction->setString(toPlainText()); + }); + + updateTextBrowser(); + updatePlaceHolderText(); +} + } diff --git a/ManiVault/src/actions/StringAction.h b/ManiVault/src/actions/StringAction.h index 8a2db779a..a93afc71b 100644 --- a/ManiVault/src/actions/StringAction.h +++ b/ManiVault/src/actions/StringAction.h @@ -38,6 +38,7 @@ class CORE_EXPORT StringAction : public WidgetAction Label = 0x00001, /** Widget includes a label */ LineEdit = 0x00002, /** Widget includes a line edit */ TextEdit = 0x00004, /** Widget includes a text edit */ + TextBrowser = 0x00008, /** Widget includes a text browser */ Default = LineEdit, }; @@ -177,6 +178,21 @@ class CORE_EXPORT StringAction : public WidgetAction friend class StringAction; }; + /** Text browser widget class for multi-line (HTML) strings */ + class CORE_EXPORT TextBrowserWidget : public QTextEdit + { + protected: + + /** + * Constructor + * @param parent Pointer to parent widget + * @param stringAction Pointer to string action + */ + TextBrowserWidget(QWidget* parent, StringAction* stringAction); + + friend class StringAction; + }; + protected: /** diff --git a/ManiVault/src/actions/ToggleAction.cpp b/ManiVault/src/actions/ToggleAction.cpp index 0c43988aa..9c71676c2 100644 --- a/ManiVault/src/actions/ToggleAction.cpp +++ b/ManiVault/src/actions/ToggleAction.cpp @@ -241,7 +241,7 @@ ToggleAction::ToggleImageLabelWidget::ToggleImageLabelWidget(QWidget* parent, To _toggleAction(toggleAction) { setAcceptDrops(true); - //setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); auto layout = new QHBoxLayout(); diff --git a/ManiVault/src/actions/TutorialsAppFeatureAction.cpp b/ManiVault/src/actions/TutorialsAppFeatureAction.cpp new file mode 100644 index 000000000..8671cfabf --- /dev/null +++ b/ManiVault/src/actions/TutorialsAppFeatureAction.cpp @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// A corresponding LICENSE file is located in the root directory of this source tree +// Copyright (C) 2023 BioVault (Biomedical Visual Analytics Unit LUMC - TU Delft) + +#include "TutorialsAppFeatureAction.h" + +namespace mv::gui { + +TutorialsAppFeatureAction::TutorialsAppFeatureAction(QObject* parent, const QString& title /*= "Tutorials"*/) : + AppFeatureAction(parent, title) +{ + loadDescriptionFromResource(":/HTML/AppFeatureTutorials"); + + getSummaryAction().setString("Allow ManiVault Studio to download learning center tutorial content"); +} + +} diff --git a/ManiVault/src/actions/TutorialsAppFeatureAction.h b/ManiVault/src/actions/TutorialsAppFeatureAction.h new file mode 100644 index 000000000..5fc79c87b --- /dev/null +++ b/ManiVault/src/actions/TutorialsAppFeatureAction.h @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// A corresponding LICENSE file is located in the root directory of this source tree +// Copyright (C) 2023 BioVault (Biomedical Visual Analytics Unit LUMC - TU Delft) + +#pragma once + +#include "AppFeatureAction.h" + +#include "actions/ToggleAction.h" + +namespace mv::gui { + +/** + * Tutorials app feature action class + * + * App feature action class for grouping tutorials dynamic content settings + * + * Note: This action is developed for internal use only + * + * @author Thomas Kroes + */ +class CORE_EXPORT TutorialsAppFeatureAction : public AppFeatureAction +{ + Q_OBJECT + +public: + + /** + * Construct with pointer to \p parent object and \p title + * @param parent Pointer to parent object + * @param title Title of the action + */ + Q_INVOKABLE TutorialsAppFeatureAction(QObject* parent, const QString& title = "Tutorials"); + +// TODO +//public: // Action getters +// +// const ToggleAction& getTutorialsAction() const { return _tutorialsAction; } +// const ToggleAction& getProjectsAction() const { return _projectsAction; } +// +//private: +// ToggleAction _tutorialsAction; /** Toggle tutorials dynamic content */ +// ToggleAction _projectsAction; /** Toggle projects dynamic content */ +}; + +} + +Q_DECLARE_METATYPE(mv::gui::TutorialsAppFeatureAction) + +inline const auto tutorialsAppFeatureActionMetaTypeId = qRegisterMetaType("mv::gui::TutorialsAppFeatureAction"); diff --git a/ManiVault/src/actions/VideosAppFeatureAction.cpp b/ManiVault/src/actions/VideosAppFeatureAction.cpp new file mode 100644 index 000000000..87f4e1869 --- /dev/null +++ b/ManiVault/src/actions/VideosAppFeatureAction.cpp @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// A corresponding LICENSE file is located in the root directory of this source tree +// Copyright (C) 2023 BioVault (Biomedical Visual Analytics Unit LUMC - TU Delft) + +#include "VideosAppFeatureAction.h" + +namespace mv::gui { + +VideosAppFeatureAction::VideosAppFeatureAction(QObject* parent, const QString& title /*= "Videos"*/) : + AppFeatureAction(parent, title) +{ + loadDescriptionFromResource(":/HTML/AppFeatureVideos"); + + getSummaryAction().setString("Allow ManiVault Studio to download data for learning center videos."); +} + +} diff --git a/ManiVault/src/actions/VideosAppFeatureAction.h b/ManiVault/src/actions/VideosAppFeatureAction.h new file mode 100644 index 000000000..0684eb314 --- /dev/null +++ b/ManiVault/src/actions/VideosAppFeatureAction.h @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// A corresponding LICENSE file is located in the root directory of this source tree +// Copyright (C) 2023 BioVault (Biomedical Visual Analytics Unit LUMC - TU Delft) + +#pragma once + +#include "AppFeatureAction.h" + +#include "actions/ToggleAction.h" + +namespace mv::gui { + +/** + * Videos app feature action class + * + * App feature action class for grouping videos dynamic content settings + * + * Note: This action is developed for internal use only + * + * @author Thomas Kroes + */ +class CORE_EXPORT VideosAppFeatureAction : public AppFeatureAction +{ + Q_OBJECT + +public: + + /** + * Construct with pointer to \p parent object and \p title + * @param parent Pointer to parent object + * @param title Title of the action + */ + Q_INVOKABLE VideosAppFeatureAction(QObject* parent, const QString& title = "Videos"); + +// TODO +//public: // Action getters +// +// const ToggleAction& getTutorialsAction() const { return _tutorialsAction; } +// const ToggleAction& getProjectsAction() const { return _projectsAction; } +// +//private: +// ToggleAction _tutorialsAction; /** Toggle tutorials dynamic content */ +// ToggleAction _projectsAction; /** Toggle projects dynamic content */ +}; + +} + +Q_DECLARE_METATYPE(mv::gui::VideosAppFeatureAction) + +inline const auto videosAppFeatureActionMetaTypeId = qRegisterMetaType("mv::gui::VideosAppFeatureAction"); diff --git a/ManiVault/src/models/LearningCenterTutorialsModel.cpp b/ManiVault/src/models/LearningCenterTutorialsModel.cpp index d2e874622..3302a736a 100644 --- a/ManiVault/src/models/LearningCenterTutorialsModel.cpp +++ b/ManiVault/src/models/LearningCenterTutorialsModel.cpp @@ -101,6 +101,8 @@ LearningCenterTutorialsModel::LearningCenterTutorialsModel(QObject* parent /*= n for (auto pluginFactory : mv::plugins().getPluginFactoriesByTypes()) { connect(&pluginFactory->getTutorialsDsnsAction(), &StringsAction::stringsChanged, this, &LearningCenterTutorialsModel::synchronizeWithDsns); } + + connect(&mv::settings().getAppFeaturesSettingsAction().getTutorialsAppFeatureAction(), &TutorialsAppFeatureAction::enabledChanged, this, &LearningCenterTutorialsModel::synchronizeWithDsns); } QVariant LearningCenterTutorialsModel::headerData(int section, Qt::Orientation orientation, int role /*= Qt::DisplayRole*/) const @@ -178,6 +180,9 @@ void LearningCenterTutorialsModel::updateTags() void LearningCenterTutorialsModel::synchronizeWithDsns() { + if (!mv::settings().getAppFeaturesSettingsAction().getTutorialsAppFeatureAction().getEnabledAction().isChecked()) + return; + auto uniqueDsns = _dsnsAction.getStrings(); for (auto pluginFactory : mv::plugins().getPluginFactoriesByTypes()) { diff --git a/ManiVault/src/models/LearningCenterVideosModel.cpp b/ManiVault/src/models/LearningCenterVideosModel.cpp index 9e7500e50..9efdacdf7 100644 --- a/ManiVault/src/models/LearningCenterVideosModel.cpp +++ b/ManiVault/src/models/LearningCenterVideosModel.cpp @@ -4,11 +4,14 @@ #include "LearningCenterVideosModel.h" +#include + #ifdef _DEBUG //#define LEARNING_CENTER_VIDEOS_MODEL_VERBOSE #endif using namespace mv::util; +using namespace mv::gui; namespace mv { @@ -24,9 +27,57 @@ QMap(Column::Count)); + + _dsnsAction.setIconByName("globe"); + _dsnsAction.setToolTip("Videos Data Source Names (DSN)"); + _dsnsAction.setConfigurationFlag(WidgetAction::ConfigurationFlag::ForceCollapsedInGroup); + _dsnsAction.setDefaultWidgetFlags(StringsAction::WidgetFlag::ListView); + _dsnsAction.setPopupSizeHint(QSize(550, 100)); + + connect(&_dsnsAction, &StringsAction::stringsChanged, this, [this]() -> void { + setRowCount(0); + + _future = QtConcurrent::mapped( + _dsnsAction.getStrings(), + [this](const QString& dsn) { + return downloadVideosFromDsn(dsn); + }); + + connect(&_watcher, &QFutureWatcher::finished, [&]() { + for (int dsnIndex = 0; dsnIndex < _dsnsAction.getStrings().size(); ++dsnIndex) { + QJsonParseError jsonParseError; + + const auto jsonDocument = QJsonDocument::fromJson(_future.resultAt(dsnIndex), &jsonParseError); + + if (jsonParseError.error != QJsonParseError::NoError || !jsonDocument.isObject()) { + qWarning() << "Invalid JSON from DSN at index" << dsnIndex << ":" << jsonParseError.errorString(); + continue; + } + + const auto videos = jsonDocument.object()["videos"].toArray(); + + for (const auto video : videos) { + auto videoMap = video.toVariant().toMap(); + + addVideo(new LearningCenterVideo(LearningCenterVideo::Type::YouTube, videoMap["title"].toString(), videoMap["tags"].toStringList(), videoMap["date"].toString().chopped(15), videoMap["summary"].toString(), videoMap["youtube-id"].toString())); + } + } + + emit populatedFromDsns(); + }); + + _watcher.setFuture(_future); + }); + + for (auto pluginFactory : mv::plugins().getPluginFactoriesByTypes()) { + connect(&pluginFactory->getVideosDsnsAction(), &StringsAction::stringsChanged, this, &LearningCenterVideosModel::synchronizeWithDsns); + } + + connect(&mv::settings().getAppFeaturesSettingsAction().getVideosAppFeatureAction(), &VideosAppFeatureAction::enabledChanged, this, &LearningCenterVideosModel::synchronizeWithDsns); } QVariant LearningCenterVideosModel::headerData(int section, Qt::Orientation orientation, int role /*= Qt::DisplayRole*/) const @@ -70,6 +121,53 @@ QSet LearningCenterVideosModel::getTagsSet() const return _tags; } +void LearningCenterVideosModel::synchronizeWithDsns() +{ + if (!mv::settings().getAppFeaturesSettingsAction().getVideosAppFeatureAction().getEnabledAction().isChecked()) + return; + + auto uniqueDsns = _dsnsAction.getStrings(); + + for (auto pluginFactory : mv::plugins().getPluginFactoriesByTypes()) { + uniqueDsns << pluginFactory->getVideosDsnsAction().getStrings(); + } + + uniqueDsns.removeDuplicates(); + + _dsnsAction.setStrings(uniqueDsns); +} + +QByteArray LearningCenterVideosModel::downloadVideosFromDsn(const QString& dsn) +{ + QEventLoop loop; + + QByteArray downloadedData; + + FileDownloader fileDownloader; + + connect(&fileDownloader, &FileDownloader::downloaded, [&]() -> void { + try + { + downloadedData = fileDownloader.downloadedData(); + loop.quit(); + } + catch (std::exception& e) + { + exceptionMessageBox("Unable to download videos JSON from DSN", e); + } + catch (...) + { + exceptionMessageBox("Unable to download videos JSON from DSN"); + } + }); + + fileDownloader.download(dsn); + + loop.exec(); + + return downloadedData; +} + void LearningCenterVideosModel::addVideo(const LearningCenterVideo* video) { Q_ASSERT(video); diff --git a/ManiVault/src/models/LearningCenterVideosModel.h b/ManiVault/src/models/LearningCenterVideosModel.h index e02b0cc81..7580cdbfa 100644 --- a/ManiVault/src/models/LearningCenterVideosModel.h +++ b/ManiVault/src/models/LearningCenterVideosModel.h @@ -409,6 +409,24 @@ class CORE_EXPORT LearningCenterVideosModel final : public QStandardItemModel /** Builds a set of all video tags and emits LearningCenterVideosModel::tagsChanged(...) */ void updateTags(); + /** Synchronize the model with the data source names */ + void synchronizeWithDsns(); + +private: + + /** + * Download videos from \p dsn + * @param dsn Videos Data Source Name (DSN) + * @return Downloaded data + */ + static QByteArray downloadVideosFromDsn(const QString& dsn); + +public: // Action getters + + gui::StringsAction& getDsnsAction() { return _dsnsAction; } + + const gui::StringsAction& getDsnsAction() const { return _dsnsAction; } + signals: /** @@ -417,9 +435,15 @@ class CORE_EXPORT LearningCenterVideosModel final : public QStandardItemModel */ void tagsChanged(const QSet& tags); + /** Signals that the model was populated from one or more source DSNs */ + void populatedFromDsns(); + private: - util::LearningCenterVideos _videos; /** Model videos */ - QSet _tags; /** All tags */ + util::LearningCenterVideos _videos; /** Model videos */ + QSet _tags; /** All tags */ + gui::StringsAction _dsnsAction; /** Data source names action */ + QFuture _future; /** Future for downloading projects */ + QFutureWatcher _watcher; /** Future watcher for downloading projects */ }; } diff --git a/ManiVault/src/private/ErrorLoggingConsentDialog.cpp b/ManiVault/src/private/AppFeaturesDialog.cpp similarity index 91% rename from ManiVault/src/private/ErrorLoggingConsentDialog.cpp rename to ManiVault/src/private/AppFeaturesDialog.cpp index 5ef8b2d27..3c94bdc9b 100644 --- a/ManiVault/src/private/ErrorLoggingConsentDialog.cpp +++ b/ManiVault/src/private/AppFeaturesDialog.cpp @@ -2,22 +2,20 @@ // A corresponding LICENSE file is located in the root directory of this source tree // Copyright (C) 2023 BioVault (Biomedical Visual Analytics Unit LUMC - TU Delft) -#include "ErrorLoggingConsentDialog.h" +#include "AppFeaturesDialog.h" #include "AbstractErrorLogger.h" -#include - #include #ifdef _DEBUG - #define ERROR_LOGGING_CONSENT_DIALOG_VERBOSE + #define APP_FEATURES_DIALOG_VERBOSE #endif using namespace mv; using namespace mv::gui; using namespace mv::util; -ErrorLoggingConsentDialog::ErrorLoggingConsentDialog(QWidget* parent): +AppFeaturesDialog::AppFeaturesDialog(QWidget* parent): QDialog(parent), _acceptPushButton("Accept"), _optOutPushButton( "Opt Out"), @@ -34,7 +32,7 @@ ErrorLoggingConsentDialog::ErrorLoggingConsentDialog(QWidget* parent): _notificationLabel.setWordWrap(true); _notificationLabel.setOpenExternalLinks(true); - QFile errorLoggingConsentHtmlFile(":/HTML/ErrorLoggingConsent"); + QFile errorLoggingConsentHtmlFile(":/HTML/AppFeatures.html"); if (errorLoggingConsentHtmlFile.open(QIODevice::ReadOnly)) { _notificationLabel.setText(errorLoggingConsentHtmlFile.readAll()); diff --git a/ManiVault/src/private/ErrorLoggingConsentDialog.h b/ManiVault/src/private/AppFeaturesDialog.h similarity index 73% rename from ManiVault/src/private/ErrorLoggingConsentDialog.h rename to ManiVault/src/private/AppFeaturesDialog.h index cad26c6a0..f0f14c296 100644 --- a/ManiVault/src/private/ErrorLoggingConsentDialog.h +++ b/ManiVault/src/private/AppFeaturesDialog.h @@ -4,8 +4,6 @@ #pragma once -#include - #include #include #include @@ -13,13 +11,16 @@ #include /** - * Error logging consent dialog class + * App features dialog class * - * Establish whether the user wants to opt in or out of (anonymous) error logging. + * Establish which app features the user wants. + * - Automated error reporting + * - A curated list of tutorials (downloaded from the ManiVault Studio website) + * - A curated list of downloadable projects (downloaded from the ManiVault Studio website) * * @author Thomas Kroes */ -class ErrorLoggingConsentDialog : public QDialog +class AppFeaturesDialog : public QDialog { Q_OBJECT @@ -29,7 +30,7 @@ class ErrorLoggingConsentDialog : public QDialog * Construct with pointer to \p parent widget * @param parent Pointer to parent widget (maybe nullptr) */ - explicit ErrorLoggingConsentDialog(QWidget* parent = nullptr); + explicit AppFeaturesDialog(QWidget* parent = nullptr); private: QVBoxLayout _layout; /** Main layout */ diff --git a/ManiVault/src/private/CrashReportDialog.cpp b/ManiVault/src/private/CrashReportDialog.cpp index 4c0bb35bf..7aa4e7e9f 100644 --- a/ManiVault/src/private/CrashReportDialog.cpp +++ b/ManiVault/src/private/CrashReportDialog.cpp @@ -71,7 +71,8 @@ CrashReportDialog::CrashReportDialog(QWidget* parent): _layout.addWidget(&_contactLineEdit); - _buttonsLayout.addWidget(const_cast(mv::settings().getErrorLoggingSettingsAction().getShowCrashReportDialogAction()).createWidget(this)); + // TODO + // _buttonsLayout.addWidget(const_cast(mv::settings().getErrorLoggingSettingsAction().getShowCrashReportDialogAction()).createWidget(this)); _buttonsLayout.addStretch(1); _buttonsLayout.addWidget(&_sendButton); _buttonsLayout.addWidget(&_cancelButton); diff --git a/ManiVault/src/private/ErrorManager.cpp b/ManiVault/src/private/ErrorManager.cpp index e6ce94590..cea93f7e7 100644 --- a/ManiVault/src/private/ErrorManager.cpp +++ b/ManiVault/src/private/ErrorManager.cpp @@ -3,7 +3,7 @@ // Copyright (C) 2023 BioVault (Biomedical Visual Analytics Unit LUMC - TU Delft) #include "ErrorManager.h" -#include "ErrorLoggingConsentDialog.h" +#include "AppFeaturesDialog.h" using namespace mv::gui; using namespace mv::util; @@ -75,12 +75,14 @@ void ErrorManager::initialize() beginInitialization(); { - auto& errorLoggingSettingsAction = mv::settings().getErrorLoggingSettingsAction(); + // TODO + //auto& errorLoggingSettingsAction = mv::settings().getErrorLoggingSettingsAction(); - errorLoggingSettingsAction.addAction(&getLoggingAskConsentDialogAction()); - errorLoggingSettingsAction.addAction(&getLoggingEnabledAction()); - errorLoggingSettingsAction.addAction(&getLoggingDsnAction()); - errorLoggingSettingsAction.addAction(&getLoggingShowCrashReportDialogAction()); + // TODO + //errorLoggingSettingsAction.addAction(&getLoggingAskConsentDialogAction()); + //errorLoggingSettingsAction.addAction(&getLoggingEnabledAction()); + //errorLoggingSettingsAction.addAction(&getLoggingDsnAction()); + //errorLoggingSettingsAction.addAction(&getLoggingShowCrashReportDialogAction()); #ifdef ERROR_LOGGING setErrorLogger(new SentryErrorLogger(this)); @@ -112,10 +114,11 @@ void ErrorManager::reset() void ErrorManager::showErrorLoggingConsentDialog() { -#ifdef ERROR_LOGGING - ErrorLoggingConsentDialog errorLoggingConsentDialog; - errorLoggingConsentDialog.exec(); -#endif +// TODO +//#ifdef ERROR_LOGGING +// ErrorLoggingConsentDialog errorLoggingConsentDialog; +// errorLoggingConsentDialog.exec(); +//#endif } } diff --git a/ManiVault/src/private/HelpManager.cpp b/ManiVault/src/private/HelpManager.cpp index 5eccaf537..7a2cbb4a9 100644 --- a/ManiVault/src/private/HelpManager.cpp +++ b/ManiVault/src/private/HelpManager.cpp @@ -44,8 +44,7 @@ HelpManager::HelpManager(QObject* parent) : _toWebsiteAction(this, "Website"), _toWikiAction(this, "Wiki"), _toRepositoryAction(this, "Repository"), - _toLearningCenterAction(this, "Go to learning center"), - _fileDownloader(FileDownloader::StorageMode::File, Task::GuiScope::Background) + _toLearningCenterAction(this, "Go to learning center") { _showLearningCenterPageAction.setIconByName("chalkboard-user"); _showLearningCenterPageAction.setToolTip("Go to the learning center"); @@ -153,11 +152,12 @@ void HelpManager::initialize() beginInitialization(); { - _fileDownloader.download(QUrl("https://www.manivault.studio/api/learning-center.json")); - _tutorialsModel.getDsnsAction().addString("https://www.manivault.studio/api/learning-center.json"); _tutorialsModel.synchronizeWithDsns(); + _videosModel.getDsnsAction().addString("https://www.manivault.studio/api/learning-center.json"); + _videosModel.synchronizeWithDsns(); + _tasksFilterModel.setSourceModel(&_tasksModel); _tasksFilterModel.getTaskScopeFilterAction().setSelectedOptions({ "Foreground" }); _tasksFilterModel.getTaskStatusFilterAction().setSelectedOptions({ "Running Indeterminate", "Running", "Finished", "Aborting" }); @@ -178,6 +178,7 @@ void HelpManager::initialize() } }); } + endInitialization(); } @@ -248,6 +249,17 @@ QMenu* HelpManager::getVideosMenu() const videosMenu->addAction(videoAction); } + const auto& videosAppFeatureEnabledAction = mv::settings().getAppFeaturesSettingsAction().getTutorialsAppFeatureAction().getEnabledAction(); + + const auto toggleVisibility = [videosMenu, &videosAppFeatureEnabledAction]() -> void { + videosMenu->setEnabled(videosAppFeatureEnabledAction.isChecked()); + + for (auto action : videosMenu->actions()) + action->setVisible(videosAppFeatureEnabledAction.isChecked()); + }; + + connect(&videosAppFeatureEnabledAction, &ToggleAction::toggled, videosMenu, toggleVisibility); + return videosMenu; } @@ -327,6 +339,17 @@ QMenu* HelpManager::getTutorialsMenu() const tutorialsMenu->setEnabled(!tutorials.empty()); + const auto& tutorialsAppFeatureEnabledAction = mv::settings().getAppFeaturesSettingsAction().getTutorialsAppFeatureAction().getEnabledAction(); + + const auto toggleVisibility = [tutorialsMenu, &tutorialsAppFeatureEnabledAction]() -> void { + tutorialsMenu->setEnabled(tutorialsAppFeatureEnabledAction.isChecked()); + + for (auto action : tutorialsMenu->actions()) + action->setVisible(tutorialsAppFeatureEnabledAction.isChecked()); + }; + + connect(&tutorialsAppFeatureEnabledAction, &ToggleAction::toggled, tutorialsMenu, toggleVisibility); + return tutorialsMenu; } diff --git a/ManiVault/src/private/HelpMenu.cpp b/ManiVault/src/private/HelpMenu.cpp index 4af5048a5..d1c166ba0 100644 --- a/ManiVault/src/private/HelpMenu.cpp +++ b/ManiVault/src/private/HelpMenu.cpp @@ -76,8 +76,15 @@ void HelpMenu::populate() addSeparator(); - addMenu(mv::help().getVideosMenu()); - addMenu(mv::help().getTutorialsMenu()); + const auto& appFeatureEnabledAction = mv::settings().getAppFeaturesSettingsAction(); + const auto& videosAppFeatureEnabledAction = appFeatureEnabledAction.getVideosAppFeatureAction().getEnabledAction(); + const auto& tutorialsAppFeatureEnabledAction = appFeatureEnabledAction.getTutorialsAppFeatureAction().getEnabledAction(); + + if (videosAppFeatureEnabledAction.isChecked()) + addMenu(mv::help().getVideosMenu()); + + if (tutorialsAppFeatureEnabledAction.isChecked()) + addMenu(mv::help().getTutorialsMenu()); if(!isEmpty()) addSeparator(); diff --git a/ManiVault/src/private/LearningPageContentWidget.cpp b/ManiVault/src/private/LearningPageContentWidget.cpp index a59077f79..cb2150273 100644 --- a/ManiVault/src/private/LearningPageContentWidget.cpp +++ b/ManiVault/src/private/LearningPageContentWidget.cpp @@ -12,10 +12,10 @@ using namespace mv::gui; LearningPageContentWidget::LearningPageContentWidget(QWidget* parent /*= nullptr*/) : PageContentWidget(Qt::Vertical, parent), - _showVideosAction(this, "Show videos", true), - _showTutorialsAction(this, "Show tutorials (coming soon)", true), - _showExamplesAction(this, "Show examples (coming soon)", false), - _showPluginResourcesAction(this, "Show plugin resources", false), + _toggleVideosSectionAction(this, "Show videos", true), + _toggleTutorialsSectionAction(this, "Show tutorials", true), + _toggleExamplesSectionAction(this, "Show examples (coming soon)", false), + _togglePluginResourcesSectionAction(this, "Show plugin resources", false), _settingsAction(this, "Page settings"), _toStartPageAction(this, "To start page"), _toolbarAction(this, "Toolbar settings"), @@ -37,26 +37,33 @@ LearningPageContentWidget::LearningPageContentWidget(QWidget* parent /*= nullptr rowsLayout.addWidget(&_examplesWidget); rowsLayout.addWidget(&_pluginResourcesWidget); - _showVideosAction.setSettingsPrefix("LearningPage/ShowVideos"); - _showTutorialsAction.setSettingsPrefix("LearningPage/ShowTutorials"); - _showExamplesAction.setSettingsPrefix("LearningPage/ShowExamples"); - _showPluginResourcesAction.setSettingsPrefix("LearningPage/ShowPluginResources"); + _toggleVideosSectionAction.setSettingsPrefix("LearningPage/ShowVideos"); + _toggleTutorialsSectionAction.setSettingsPrefix("LearningPage/ShowTutorials"); + _toggleExamplesSectionAction.setSettingsPrefix("LearningPage/ShowExamples"); + _togglePluginResourcesSectionAction.setSettingsPrefix("LearningPage/ShowPluginResources"); - _showExamplesAction.setEnabled(false); + _toggleExamplesSectionAction.setEnabled(false); - const auto toggleSections = [this]() -> void { - _videosWidget.setVisible(_showVideosAction.isChecked()); - _tutorialsWidget.setVisible(_showTutorialsAction.isChecked()); - _examplesWidget.setVisible(_showExamplesAction.isChecked()); - _pluginResourcesWidget.setVisible(_showPluginResourcesAction.isChecked()); + const auto& appFeaturesSettingsAction = mv::constSettings().getAppFeaturesSettingsAction(); + const auto& videosAppFeatureEnabledAction = appFeaturesSettingsAction.getVideosAppFeatureAction().getEnabledAction(); + const auto& tutorialsAppFeatureEnabledAction = appFeaturesSettingsAction.getTutorialsAppFeatureAction().getEnabledAction(); + + const auto toggleSections = [this, &videosAppFeatureEnabledAction, &tutorialsAppFeatureEnabledAction]() -> void { + _videosWidget.setVisible(videosAppFeatureEnabledAction.isChecked() && _toggleVideosSectionAction.isChecked()); + _tutorialsWidget.setVisible(tutorialsAppFeatureEnabledAction.isChecked() && _toggleTutorialsSectionAction.isChecked()); + _examplesWidget.setVisible(_toggleExamplesSectionAction.isChecked()); + _pluginResourcesWidget.setVisible(_togglePluginResourcesSectionAction.isChecked()); }; toggleSections(); - connect(&_showVideosAction, &ToggleAction::toggled, this, toggleSections); - connect(&_showTutorialsAction, &ToggleAction::toggled, this, toggleSections); - connect(&_showExamplesAction, &ToggleAction::toggled, this, toggleSections); - connect(&_showPluginResourcesAction, &ToggleAction::toggled, this, toggleSections); + connect(&tutorialsAppFeatureEnabledAction, &ToggleAction::toggled, this, toggleSections); + connect(&videosAppFeatureEnabledAction, &ToggleAction::toggled, this, toggleSections); + + connect(&_toggleVideosSectionAction, &ToggleAction::toggled, this, toggleSections); + connect(&_toggleTutorialsSectionAction, &ToggleAction::toggled, this, toggleSections); + connect(&_toggleExamplesSectionAction, &ToggleAction::toggled, this, toggleSections); + connect(&_togglePluginResourcesSectionAction, &ToggleAction::toggled, this, toggleSections); _settingsAction.setToolTip("Adjust page settings"); _settingsAction.setIconByName("gear"); @@ -66,11 +73,12 @@ LearningPageContentWidget::LearningPageContentWidget(QWidget* parent /*= nullptr _toStartPageAction.setDefaultWidgetFlags(TriggerAction::Icon); _settingsAction.setConfigurationFlag(WidgetAction::ConfigurationFlag::ForceCollapsedInGroup); + _settingsAction.setPopupSizeHint(QSize(200, 200)); - _settingsAction.addAction(&_showVideosAction); - _settingsAction.addAction(&_showTutorialsAction); - _settingsAction.addAction(&_showExamplesAction); - _settingsAction.addAction(&_showPluginResourcesAction); + _settingsAction.addAction(&_toggleVideosSectionAction); + _settingsAction.addAction(&_toggleTutorialsSectionAction); + _settingsAction.addAction(&_toggleExamplesSectionAction); + _settingsAction.addAction(&_togglePluginResourcesSectionAction); _toolbarAction.setShowLabels(false); _toolbarAction.setWidgetConfigurationFunction([](WidgetAction* action, QWidget* widget) -> void { @@ -92,5 +100,25 @@ LearningPageContentWidget::LearningPageContentWidget(QWidget* parent /*= nullptr mv::help().getShowLearningCenterPageAction().setChecked(false); mv::projects().getShowStartPageAction().setChecked(true); }); + + const auto updateToggleSectionActionsVisibility = [this, &videosAppFeatureEnabledAction, &tutorialsAppFeatureEnabledAction]() -> void { + _toggleVideosSectionAction.setVisible(videosAppFeatureEnabledAction.isChecked()); + _toggleTutorialsSectionAction.setVisible(tutorialsAppFeatureEnabledAction.isChecked()); + }; + + updateToggleSectionActionsVisibility(); + + connect(&videosAppFeatureEnabledAction, &ToggleAction::toggled, this, updateToggleSectionActionsVisibility); + connect(&tutorialsAppFeatureEnabledAction, &ToggleAction::toggled, this, updateToggleSectionActionsVisibility); + + connect(&videosAppFeatureEnabledAction, &ToggleAction::toggled, this, [this](bool toggled) -> void { + if (toggled) + _toggleVideosSectionAction.setChecked(true); + }); + + connect(&tutorialsAppFeatureEnabledAction, &ToggleAction::toggled, this, [this](bool toggled) -> void { + if (toggled) + _toggleTutorialsSectionAction.setChecked(true); + }); } diff --git a/ManiVault/src/private/LearningPageContentWidget.h b/ManiVault/src/private/LearningPageContentWidget.h index 2af75b3c9..980bc36fd 100644 --- a/ManiVault/src/private/LearningPageContentWidget.h +++ b/ManiVault/src/private/LearningPageContentWidget.h @@ -29,17 +29,17 @@ class LearningPageContentWidget final : public PageContentWidget LearningPageContentWidget(QWidget* parent = nullptr); private: - mv::gui::ToggleAction _showVideosAction; /** Action to toggle videos section */ - mv::gui::ToggleAction _showTutorialsAction; /** Action to toggle tutorials section */ - mv::gui::ToggleAction _showExamplesAction; /** Action to toggle examples section */ - mv::gui::ToggleAction _showPluginResourcesAction; /** Action to toggle plugin resources section */ - mv::gui::GroupAction _settingsAction; /** Page settings action */ - mv::gui::TriggerAction _toStartPageAction; /** Trigger action for showing the start page */ - mv::gui::HorizontalGroupAction _toolbarAction; /** Bottom toolbar action */ - LearningPageVideosWidget _videosWidget; /** Learning page video section content widget */ - PageTutorialsWidget _tutorialsWidget; /** Learning page tutorials section content widget */ - LearningPageExamplesWidget _examplesWidget; /** Learning page examples section content widget */ - LearningPagePluginResourcesWidget _pluginResourcesWidget; /** Learning page plugin section resources content widget */ + mv::gui::ToggleAction _toggleVideosSectionAction; /** Action to toggle the videos section */ + mv::gui::ToggleAction _toggleTutorialsSectionAction; /** Action to toggle the tutorials section */ + mv::gui::ToggleAction _toggleExamplesSectionAction; /** Action to toggle the examples section */ + mv::gui::ToggleAction _togglePluginResourcesSectionAction; /** Action to toggle the plugin resources section */ + mv::gui::GroupAction _settingsAction; /** Page settings action */ + mv::gui::TriggerAction _toStartPageAction; /** Trigger action for showing the start page */ + mv::gui::HorizontalGroupAction _toolbarAction; /** Bottom toolbar action */ + LearningPageVideosWidget _videosWidget; /** Learning page video section content widget */ + PageTutorialsWidget _tutorialsWidget; /** Learning page tutorials section content widget */ + LearningPageExamplesWidget _examplesWidget; /** Learning page examples section content widget */ + LearningPagePluginResourcesWidget _pluginResourcesWidget; /** Learning page plugin section resources content widget */ friend class LearningPageWidget; }; \ No newline at end of file diff --git a/ManiVault/src/private/PageContentWidget.cpp b/ManiVault/src/private/PageContentWidget.cpp index 7cb186fdc..fe3a5fcb8 100644 --- a/ManiVault/src/private/PageContentWidget.cpp +++ b/ManiVault/src/private/PageContentWidget.cpp @@ -27,7 +27,7 @@ PageContentWidget::PageContentWidget(const Qt::Orientation& orientation, QWidget break; case Qt::Vertical: - _mainLayout.addLayout(&_rowsLayout); + _mainLayout.addLayout(&_rowsLayout, 1); break; } diff --git a/ManiVault/src/private/SettingsManager.cpp b/ManiVault/src/private/SettingsManager.cpp index 2b92b2e7d..c8b72edfd 100644 --- a/ManiVault/src/private/SettingsManager.cpp +++ b/ManiVault/src/private/SettingsManager.cpp @@ -27,11 +27,11 @@ namespace mv SettingsManager::SettingsManager(QObject* parent) : AbstractSettingsManager(parent), _editSettingsAction(this, "Settings..."), + _appFeaturesSettingsAction(this), _parametersSettingsAction(this), _miscellaneousSettingsAction(this), _tasksSettingsAction(this), _temporaryDirectoriesSettingsAction(this), - _errorLoggingSettingsAction(this), _appearanceSettingsAction(this) { _editSettingsAction.setShortcutContext(Qt::WidgetWithChildrenShortcut); diff --git a/ManiVault/src/private/SettingsManager.h b/ManiVault/src/private/SettingsManager.h index 1b29af5d4..8ca2ed3e8 100644 --- a/ManiVault/src/private/SettingsManager.h +++ b/ManiVault/src/private/SettingsManager.h @@ -39,12 +39,19 @@ class SettingsManager final : public AbstractSettingsManager public: // Global settings actions - gui::ParametersSettingsAction& getParametersSettings() override { return _parametersSettingsAction; }; - gui::MiscellaneousSettingsAction& getMiscellaneousSettings() override { return _miscellaneousSettingsAction; }; - gui::TasksSettingsAction& getTasksSettingsAction() override { return _tasksSettingsAction; }; - gui::AppearanceSettingsAction& getAppearanceSettingsAction() override { return _appearanceSettingsAction; }; - gui::TemporaryDirectoriesSettingsAction& getTemporaryDirectoriesSettingsAction() override { return _temporaryDirectoriesSettingsAction; }; - gui::ErrorLoggingSettingsAction& getErrorLoggingSettingsAction() override { return _errorLoggingSettingsAction; }; + //gui::AppFeaturesSettingsAction& getAppFeaturesSettingsAction() override { return _appFeaturesSettingsAction; } + gui::ParametersSettingsAction& getParametersSettings() override { return _parametersSettingsAction; } + gui::MiscellaneousSettingsAction& getMiscellaneousSettings() override { return _miscellaneousSettingsAction; } + gui::TasksSettingsAction& getTasksSettingsAction() override { return _tasksSettingsAction; } + gui::AppearanceSettingsAction& getAppearanceSettingsAction() override { return _appearanceSettingsAction; } + gui::TemporaryDirectoriesSettingsAction& getTemporaryDirectoriesSettingsAction() override { return _temporaryDirectoriesSettingsAction; } + + const gui::AppFeaturesSettingsAction& getAppFeaturesSettingsAction() const override { return _appFeaturesSettingsAction; } + const gui::ParametersSettingsAction& getParametersSettings() const override { return _parametersSettingsAction; } + const gui::MiscellaneousSettingsAction& getMiscellaneousSettings() const override { return _miscellaneousSettingsAction; } + const gui::TasksSettingsAction& getTasksSettingsAction() const override { return _tasksSettingsAction; } + const gui::AppearanceSettingsAction& getAppearanceSettingsAction() const override { return _appearanceSettingsAction; } + const gui::TemporaryDirectoriesSettingsAction& getTemporaryDirectoriesSettingsAction() const override { return _temporaryDirectoriesSettingsAction; } /** * Get plugin global settings for plugin \p kind @@ -61,13 +68,13 @@ class SettingsManager final : public AbstractSettingsManager gui::PluginGlobalSettingsGroupAction* getPluginGlobalSettingsGroupAction(const plugin::Plugin* plugin) override; private: + gui::AppFeaturesSettingsAction _appFeaturesSettingsAction; /** App features global settings */ gui::TriggerAction _editSettingsAction; /** Action for triggering the settings dialog */ gui::ParametersSettingsAction _parametersSettingsAction; /** Parameters global settings */ gui::MiscellaneousSettingsAction _miscellaneousSettingsAction; /** Miscellaneous global settings */ gui::TasksSettingsAction _tasksSettingsAction; /** Tasks global settings */ gui::AppearanceSettingsAction _appearanceSettingsAction; /** Appearance global settings */ gui::TemporaryDirectoriesSettingsAction _temporaryDirectoriesSettingsAction; /** Temporary files global settings */ - gui::ErrorLoggingSettingsAction _errorLoggingSettingsAction; /** Error logging settings */ }; } diff --git a/ManiVault/src/private/SettingsManagerDialog.cpp b/ManiVault/src/private/SettingsManagerDialog.cpp index dd29b694d..d54e6b5c4 100644 --- a/ManiVault/src/private/SettingsManagerDialog.cpp +++ b/ManiVault/src/private/SettingsManagerDialog.cpp @@ -33,10 +33,7 @@ SettingsManagerDialog::SettingsManagerDialog(QWidget* parent /*= nullptr*/) : layout->addWidget(_groupsAction.createWidget(this)); -#ifdef ERROR_LOGGING - _groupsAction.addGroupAction(&mv::settings().getErrorLoggingSettingsAction()); -#endif - + _groupsAction.addGroupAction(&const_cast(mv::settings().getAppFeaturesSettingsAction())); _groupsAction.addGroupAction(&mv::settings().getAppearanceSettingsAction()); _groupsAction.addGroupAction(&mv::settings().getParametersSettings()); _groupsAction.addGroupAction(&mv::settings().getMiscellaneousSettings()); diff --git a/ManiVault/src/private/StartPageContentWidget.cpp b/ManiVault/src/private/StartPageContentWidget.cpp index 75762badb..8dbb52608 100644 --- a/ManiVault/src/private/StartPageContentWidget.cpp +++ b/ManiVault/src/private/StartPageContentWidget.cpp @@ -19,12 +19,12 @@ StartPageContentWidget::StartPageContentWidget(QWidget* parent /*= nullptr*/) : PageContentWidget(Qt::Horizontal, parent), Serializable("StartPageContentWidget"), _compactViewAction(this, "Compact"), - _toggleOpenCreateProjectAction(this, "Open & Create", true), - _toggleProjectDatabaseAction(this, "Project Database", true), - _toggleRecentProjectsAction(this, "Recent Projects", true), - _toggleProjectFromDataAction(this, "Project From Data", true), - _toggleProjectFromWorkspaceAction(this, "Project From Workspace"), - _toggleTutorialsAction(this, "Tutorials", true), + _toggleOpenCreateProjectSectionAction(this, "Open & Create", true), + _toggleProjectDatabaseSectionAction(this, "Project Database", true), + _toggleRecentProjectsSectionAction(this, "Recent Projects", true), + _toggleProjectFromDataSectionAction(this, "Project From Data", true), + _toggleProjectFromWorkspaceSectionAction(this, "Project From Workspace"), + _toggleTutorialsSectionAction(this, "Tutorials", true), _settingsAction(this, "Settings"), _toLearningCenterAction(this, "Learning center"), _toolbarAction(this, "Toolbar settings"), @@ -33,12 +33,12 @@ StartPageContentWidget::StartPageContentWidget(QWidget* parent /*= nullptr*/) : { if (!QFileInfo("StartPage.json").exists()) { _compactViewAction.setSettingsPrefix("StartPage/ToggleCompactView"); - _toggleOpenCreateProjectAction.setSettingsPrefix("StartPage/ToggleOpenCreateProject"); - _toggleProjectDatabaseAction.setSettingsPrefix("StartPage/ToggleProjectsRepository"); - _toggleRecentProjectsAction.setSettingsPrefix("StartPage/ToggleRecentProjects"); - _toggleProjectFromWorkspaceAction.setSettingsPrefix("StartPage/ToggleProjectFromWorkspace"); - _toggleProjectFromDataAction.setSettingsPrefix("StartPage/ToggleProjectFromData"); - _toggleTutorialsAction.setSettingsPrefix("StartPage/ToggleTutorials"); + _toggleOpenCreateProjectSectionAction.setSettingsPrefix("StartPage/ToggleOpenCreateProject"); + _toggleProjectDatabaseSectionAction.setSettingsPrefix("StartPage/ToggleProjectsRepository"); + _toggleRecentProjectsSectionAction.setSettingsPrefix("StartPage/ToggleRecentProjects"); + _toggleProjectFromWorkspaceSectionAction.setSettingsPrefix("StartPage/ToggleProjectFromWorkspace"); + _toggleProjectFromDataSectionAction.setSettingsPrefix("StartPage/ToggleProjectFromData"); + _toggleTutorialsSectionAction.setSettingsPrefix("StartPage/ToggleTutorials"); _getStartedWidget.getTutorialsWidget().getTutorialsFilterModel().getTagsFilterAction().setSelectedOptions({ "GettingStarted" }); } else { @@ -46,7 +46,7 @@ StartPageContentWidget::StartPageContentWidget(QWidget* parent /*= nullptr*/) : _toLearningCenterAction.setVisible(false); } - _toggleProjectFromWorkspaceAction.setEnabled(false); + _toggleProjectFromWorkspaceSectionAction.setEnabled(false); _settingsAction.setText("Toggle Views"); _settingsAction.setToolTip("Adjust page settings"); @@ -58,17 +58,15 @@ StartPageContentWidget::StartPageContentWidget(QWidget* parent /*= nullptr*/) : _settingsAction.setConfigurationFlag(WidgetAction::ConfigurationFlag::ForceCollapsedInGroup); - _settingsAction.addAction(&_toggleOpenCreateProjectAction); - + _settingsAction.addAction(&_toggleOpenCreateProjectSectionAction); _settingsAction.addAction(&_toggleProjectDatabaseAction); - - _settingsAction.addAction(&_toggleRecentProjectsAction); - _settingsAction.addAction(&_toggleProjectFromDataAction); + _settingsAction.addAction(&_toggleRecentProjectsSectionAction); + _settingsAction.addAction(&_toggleProjectFromDataSectionAction); // Disable until the project from workspace action is implemented properly - //_settingsAction.addAction(&_toggleProjectFromWorkspaceAction); + //_settingsAction.addAction(&_toggleProjectFromWorkspaceSectionAction); - _settingsAction.addAction(&_toggleTutorialsAction); + _settingsAction.addAction(&_toggleTutorialsSectionAction); _settingsAction.addAction(&_compactViewAction); getColumnsLayout().addWidget(&_openProjectWidget); @@ -92,6 +90,21 @@ StartPageContentWidget::StartPageContentWidget(QWidget* parent /*= nullptr*/) : }); connect(&_compactViewAction, &ToggleAction::toggled, this, &StartPageContentWidget::updateActions); + + const auto& tutorialsAppFeatureEnabledAction = mv::constSettings().getAppFeaturesSettingsAction().getTutorialsAppFeatureAction().getEnabledAction(); + + const auto updateTutorialsToggleVisibility = [this, &tutorialsAppFeatureEnabledAction]() -> void { + _toggleTutorialsSectionAction.setVisible(tutorialsAppFeatureEnabledAction.isChecked()); + }; + + updateTutorialsToggleVisibility(); + + connect(&tutorialsAppFeatureEnabledAction, &ToggleAction::toggled, this, updateTutorialsToggleVisibility); + + connect(&tutorialsAppFeatureEnabledAction, &ToggleAction::toggled, this, [this](bool toggled) -> void { + if (toggled) + _toggleTutorialsSectionAction.setChecked(true); + }); } void StartPageContentWidget::updateActions() @@ -107,12 +120,12 @@ void StartPageContentWidget::fromVariantMap(const QVariantMap& variantMap) Serializable::fromVariantMap(variantMap); _compactViewAction.fromParentVariantMap(variantMap); - _toggleOpenCreateProjectAction.fromParentVariantMap(variantMap); - _toggleProjectDatabaseAction.fromParentVariantMap(variantMap); - _toggleRecentProjectsAction.fromParentVariantMap(variantMap); - _toggleProjectFromDataAction.fromParentVariantMap(variantMap); - _toggleProjectFromWorkspaceAction.fromParentVariantMap(variantMap); - _toggleTutorialsAction.fromParentVariantMap(variantMap); + _toggleOpenCreateProjectSectionAction.fromParentVariantMap(variantMap); + _toggleProjectDatabaseSectionAction.fromParentVariantMap(variantMap); + _toggleRecentProjectsSectionAction.fromParentVariantMap(variantMap); + _toggleProjectFromDataSectionAction.fromParentVariantMap(variantMap); + _toggleProjectFromWorkspaceSectionAction.fromParentVariantMap(variantMap); + _toggleTutorialsSectionAction.fromParentVariantMap(variantMap); _openProjectWidget.fromParentVariantMap(variantMap); _getStartedWidget.fromParentVariantMap(variantMap); } @@ -122,12 +135,12 @@ QVariantMap StartPageContentWidget::toVariantMap() const auto variantMap = Serializable::toVariantMap(); _compactViewAction.insertIntoVariantMap(variantMap); - _toggleOpenCreateProjectAction.insertIntoVariantMap(variantMap); - _toggleProjectDatabaseAction.insertIntoVariantMap(variantMap); - _toggleRecentProjectsAction.insertIntoVariantMap(variantMap); - _toggleProjectFromDataAction.insertIntoVariantMap(variantMap); - _toggleProjectFromWorkspaceAction.insertIntoVariantMap(variantMap); - _toggleTutorialsAction.insertIntoVariantMap(variantMap); + _toggleOpenCreateProjectSectionAction.insertIntoVariantMap(variantMap); + _toggleProjectDatabaseSectionAction.insertIntoVariantMap(variantMap); + _toggleRecentProjectsSectionAction.insertIntoVariantMap(variantMap); + _toggleProjectFromDataSectionAction.insertIntoVariantMap(variantMap); + _toggleProjectFromWorkspaceSectionAction.insertIntoVariantMap(variantMap); + _toggleTutorialsSectionAction.insertIntoVariantMap(variantMap); _openProjectWidget.insertIntoVariantMap(variantMap); _getStartedWidget.insertIntoVariantMap(variantMap); diff --git a/ManiVault/src/private/StartPageContentWidget.h b/ManiVault/src/private/StartPageContentWidget.h index 3bb6989fb..b57566493 100644 --- a/ManiVault/src/private/StartPageContentWidget.h +++ b/ManiVault/src/private/StartPageContentWidget.h @@ -51,12 +51,12 @@ class StartPageContentWidget final : public PageContentWidget, public mv::util:: public: // Action getters const mv::gui::ToggleAction& getCompactViewAction() const { return _compactViewAction; } - const mv::gui::ToggleAction& getToggleOpenCreateProjectAction() const { return _toggleOpenCreateProjectAction; } - const mv::gui::ToggleAction& getToggleProjectDatabaseAction() const { return _toggleProjectDatabaseAction; } - const mv::gui::ToggleAction& getToggleRecentProjectsAction() const { return _toggleRecentProjectsAction; } - const mv::gui::ToggleAction& getToggleProjectFromDataAction() const { return _toggleProjectFromDataAction; } - const mv::gui::ToggleAction& getToggleProjectFromWorkspaceAction() const { return _toggleProjectFromWorkspaceAction; } - const mv::gui::ToggleAction& getToggleTutorialsAction() const { return _toggleTutorialsAction; } + const mv::gui::ToggleAction& getToggleOpenCreateProjectAction() const { return _toggleOpenCreateProjectSectionAction; } + const mv::gui::ToggleAction& getToggleProjectDatabaseAction() const { return _toggleProjectDatabaseSectionAction; } + const mv::gui::ToggleAction& getToggleRecentProjectsAction() const { return _toggleRecentProjectsSectionAction; } + const mv::gui::ToggleAction& getToggleProjectFromDataAction() const { return _toggleProjectFromDataSectionAction; } + const mv::gui::ToggleAction& getToggleProjectFromWorkspaceAction() const { return _toggleProjectFromWorkspaceSectionAction; } + const mv::gui::ToggleAction& getToggleTutorialsAction() const { return _toggleTutorialsSectionAction; } const mv::gui::GroupAction& getSettingsAction() const { return _settingsAction; } const mv::gui::TriggerAction& getToLearningCenterAction() const { return _toLearningCenterAction; } @@ -65,12 +65,12 @@ class StartPageContentWidget final : public PageContentWidget, public mv::util:: private: mv::gui::ToggleAction _compactViewAction; /** Toggle compact view on/off */ - mv::gui::ToggleAction _toggleOpenCreateProjectAction; /** Toggle open and create project section */ - mv::gui::ToggleAction _toggleProjectDatabaseAction; /** Toggle project database section */ - mv::gui::ToggleAction _toggleRecentProjectsAction; /** Toggle recent projects section */ - mv::gui::ToggleAction _toggleProjectFromDataAction; /** Toggle project from data section */ - mv::gui::ToggleAction _toggleProjectFromWorkspaceAction; /** Toggle project from workspace section */ - mv::gui::ToggleAction _toggleTutorialsAction; /** Toggle tutorials section */ + mv::gui::ToggleAction _toggleOpenCreateProjectSectionAction; /** Toggle open and create project section */ + mv::gui::ToggleAction _toggleProjectDatabaseSectionAction; /** Toggle project database section */ + mv::gui::ToggleAction _toggleRecentProjectsSectionAction; /** Toggle recent projects section */ + mv::gui::ToggleAction _toggleProjectFromDataSectionAction; /** Toggle project from data section */ + mv::gui::ToggleAction _toggleProjectFromWorkspaceSectionAction; /** Toggle project from workspace section */ + mv::gui::ToggleAction _toggleTutorialsSectionAction; /** Toggle tutorials section */ mv::gui::GroupAction _settingsAction; /** Settings action */ mv::gui::TriggerAction _toLearningCenterAction; /** Trigger action for showing the learning center */ mv::gui::HorizontalGroupAction _toolbarAction; /** Bottom toolbar action */ diff --git a/ManiVault/src/private/StartPageGetStartedWidget.cpp b/ManiVault/src/private/StartPageGetStartedWidget.cpp index 9a51d0168..df0b827d8 100644 --- a/ManiVault/src/private/StartPageGetStartedWidget.cpp +++ b/ManiVault/src/private/StartPageGetStartedWidget.cpp @@ -67,17 +67,21 @@ StartPageGetStartedWidget::StartPageGetStartedWidget(StartPageContentWidget* sta _recentWorkspacesAction.initialize("Manager/Workspace/Recent", "Workspace", "Ctrl+Alt"); _recentProjectsAction.initialize("Manager/Project/Recent", "Project", "Ctrl"); - const auto toggleViews = [this]() -> void { + const auto& tutorialsAppFeatureEnabledAction = mv::settings().getAppFeaturesSettingsAction().getTutorialsAppFeatureAction().getEnabledAction(); + + const auto toggleViews = [this, &tutorialsAppFeatureEnabledAction]() -> void { _createProjectFromWorkspaceWidget.setVisible(_startPageContentWidget->getToggleProjectFromWorkspaceAction().isChecked()); _createProjectFromDatasetWidget.setVisible(_startPageContentWidget->getToggleProjectFromDataAction().isChecked()); - _tutorialsWidget.setVisible(_startPageContentWidget->getToggleTutorialsAction().isChecked()); + _tutorialsWidget.setVisible(tutorialsAppFeatureEnabledAction.isChecked() && _startPageContentWidget->getToggleTutorialsAction().isChecked()); }; + toggleViews(); + connect(&_startPageContentWidget->getToggleProjectFromWorkspaceAction(), &ToggleAction::toggled, this, toggleViews); connect(&_startPageContentWidget->getToggleProjectFromDataAction(), &ToggleAction::toggled, this, toggleViews); connect(&_startPageContentWidget->getToggleTutorialsAction(), &ToggleAction::toggled, this, toggleViews); - toggleViews(); + connect(&tutorialsAppFeatureEnabledAction, &ToggleAction::toggled, this, toggleViews); } void StartPageGetStartedWidget::updateActions()