From b863c4a2e251b5108310a1a62e654e005c57a14b Mon Sep 17 00:00:00 2001 From: Thomas Kroes Date: Fri, 9 Jan 2026 10:29:00 +0100 Subject: [PATCH 1/6] Implemented skeleton code for actions example plugin --- CMakeLists.txt | 3 +- ExampleActions/CMakeLists.txt | 115 ++++++++++++++++++++ ExampleActions/PluginInfo.json | 9 ++ ExampleActions/src/.editorconfig | 10 ++ ExampleActions/src/ExampleActionsPlugin.cpp | 47 ++++++++ ExampleActions/src/ExampleActionsPlugin.h | 66 +++++++++++ 6 files changed, 249 insertions(+), 1 deletion(-) create mode 100644 ExampleActions/CMakeLists.txt create mode 100644 ExampleActions/PluginInfo.json create mode 100644 ExampleActions/src/.editorconfig create mode 100644 ExampleActions/src/ExampleActionsPlugin.cpp create mode 100644 ExampleActions/src/ExampleActionsPlugin.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 3b3fb92..68edd1f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -31,8 +31,9 @@ add_subdirectory(ExampleLoader) add_subdirectory(ExampleWriter) add_subdirectory(ExampleData) add_subdirectory(ExampleDependencies) +add_subdirectory(ExampleActions) -set_target_properties(ExampleViewPlugin ExampleViewJSPlugin ExampleViewOpenGLPlugin ExampleAnalysisPlugin ExampleTransformationPlugin ExampleLoaderPlugin ExampleWriterPlugin ExampleDataPlugin ExampleDependenciesPlugin +set_target_properties(ExampleViewPlugin ExampleViewJSPlugin ExampleViewOpenGLPlugin ExampleAnalysisPlugin ExampleTransformationPlugin ExampleLoaderPlugin ExampleWriterPlugin ExampleDataPlugin ExampleDependenciesPlugin ExampleActionsPlugin PROPERTIES FOLDER ExamplePlugins ) diff --git a/ExampleActions/CMakeLists.txt b/ExampleActions/CMakeLists.txt new file mode 100644 index 0000000..e175f56 --- /dev/null +++ b/ExampleActions/CMakeLists.txt @@ -0,0 +1,115 @@ +cmake_minimum_required(VERSION 3.22) + +option(MV_UNITY_BUILD "Combine target source files into batches for faster compilation" OFF) + +# ----------------------------------------------------------------------------- +# ExampleView Plugin +# ----------------------------------------------------------------------------- +PROJECT("ExampleActionsPlugin" LANGUAGES CXX) + +# ----------------------------------------------------------------------------- +# CMake Options +# ----------------------------------------------------------------------------- +set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) +set(CMAKE_INCLUDE_CURRENT_DIR ON) +set(CMAKE_AUTOMOC ON) + +if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /DWIN32 /EHsc /MP /permissive- /Zc:__cplusplus") + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /MDd") + set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} /MD") + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /MD") +endif() + +# ----------------------------------------------------------------------------- +# Dependencies +# ----------------------------------------------------------------------------- +find_package(Qt6 COMPONENTS Widgets WebEngineWidgets REQUIRED) + +find_package(ManiVault COMPONENTS Core PointData CONFIG QUIET) + +# ----------------------------------------------------------------------------- +# Include Common directory +# ----------------------------------------------------------------------------- +add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../Common ${CMAKE_CURRENT_BINARY_DIR}/Common) + +# ----------------------------------------------------------------------------- +# Source files +# ----------------------------------------------------------------------------- +# Define the plugin sources +set(PLUGIN_SOURCES + src/ExampleActionsPlugin.h + src/ExampleActionsPlugin.cpp + PluginInfo.json +) + +set(PLUGIN_MOC_HEADERS + src/ExampleActionsPlugin.h +) + +source_group(Plugin FILES ${PLUGIN_SOURCES}) +source_group(Common FILES ${COMMON_FILES}) + +# ----------------------------------------------------------------------------- +# CMake Target +# ----------------------------------------------------------------------------- +# Create dynamic library for the plugin +add_library(${PROJECT_NAME} SHARED ${PLUGIN_SOURCES} ${COMMON_FILES}) + +# ----------------------------------------------------------------------------- +# Target include directories +# ----------------------------------------------------------------------------- +# Include ManiVault headers, including system data plugins +target_include_directories(${PROJECT_NAME} PRIVATE "${ManiVault_INCLUDE_DIR}") + +# ----------------------------------------------------------------------------- +# Target properties +# ----------------------------------------------------------------------------- +# Request C++20 +target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_20) + +# Enable unity build +if(MV_UNITY_BUILD) + set_target_properties(${PROJECT_NAME} PROPERTIES UNITY_BUILD ON) +endif() + +# ----------------------------------------------------------------------------- +# Target library linking +# ----------------------------------------------------------------------------- +# Link to Qt libraries +target_link_libraries(${PROJECT_NAME} PRIVATE Qt6::Widgets) +target_link_libraries(${PROJECT_NAME} PRIVATE Qt6::WebEngineWidgets) + +# Link to ManiVault and data plugins +target_link_libraries(${PROJECT_NAME} PRIVATE ManiVault::Core) +target_link_libraries(${PROJECT_NAME} PRIVATE ManiVault::PointData) + +# ----------------------------------------------------------------------------- +# Target installation +# ----------------------------------------------------------------------------- +# Install the shared plugin library to the "Plugins" folder in the ManiVault install directory +install(TARGETS ${PROJECT_NAME} + RUNTIME DESTINATION Plugins COMPONENT PLUGINS # Windows .dll + LIBRARY DESTINATION Plugins COMPONENT PLUGINS # Linux/Mac .so +) + +add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD + COMMAND "${CMAKE_COMMAND}" + --install ${CMAKE_CURRENT_BINARY_DIR} + --config $ + --prefix ${ManiVault_INSTALL_DIR}/$ +) + +# Append plugin version to output file +# 0 disables automatic folder placement (same as plugin type) +# since we want to place all example plugins in a separate folder +mv_handle_plugin_config(${PROJECT_NAME} 0) + +# ----------------------------------------------------------------------------- +# Miscellaneous +# ----------------------------------------------------------------------------- +# Automatically set the debug environment (command + working directory) for MSVC +if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") + set_property(TARGET ${PROJECT_NAME} PROPERTY VS_DEBUGGER_WORKING_DIRECTORY $,${ManiVault_INSTALL_DIR}/Debug,$,${ManiVault_INSTALL_DIR}/RelWithDebInfo,${ManiVault_INSTALL_DIR}/Release>>) + set_property(TARGET ${PROJECT_NAME} PROPERTY VS_DEBUGGER_COMMAND $,"${ManiVault_INSTALL_DIR}/Debug/ManiVault Studio.exe",$,"${ManiVault_INSTALL_DIR}/RelWithDebInfo/ManiVault Studio.exe","${ManiVault_INSTALL_DIR}/Release/ManiVault Studio.exe">>) +endif() diff --git a/ExampleActions/PluginInfo.json b/ExampleActions/PluginInfo.json new file mode 100644 index 0000000..e7f89d3 --- /dev/null +++ b/ExampleActions/PluginInfo.json @@ -0,0 +1,9 @@ +{ + "name" : "Example Actions", + "version" : { + "plugin" : "1.4", + "core" : ["1.3"] + }, + "type" : "View", + "dependencies" : ["Points"] +} diff --git a/ExampleActions/src/.editorconfig b/ExampleActions/src/.editorconfig new file mode 100644 index 0000000..7eddc72 --- /dev/null +++ b/ExampleActions/src/.editorconfig @@ -0,0 +1,10 @@ +# To learn more about .editorconfig see https://aka.ms/editorconfigdocs +root = true + +# All files +[*] +indent_style = space + +# Cpp files +[*.{h,cpp}] +indent_size = 4 diff --git a/ExampleActions/src/ExampleActionsPlugin.cpp b/ExampleActions/src/ExampleActionsPlugin.cpp new file mode 100644 index 0000000..3638982 --- /dev/null +++ b/ExampleActions/src/ExampleActionsPlugin.cpp @@ -0,0 +1,47 @@ +#include "ExampleActionsPlugin.h" + +#include "../Common/common.h" + +#include + +Q_PLUGIN_METADATA(IID "studio.manivault.ExampleActionsPlugin") + +using namespace mv; + +ExampleActionsPlugin::ExampleActionsPlugin(const PluginFactory* factory) : + ViewPlugin(factory) +{ +} + +void ExampleActionsPlugin::init() +{ + // Create layout + auto layout = new QVBoxLayout(); + + layout->setContentsMargins(0, 0, 0, 0); +} + +ExampleActionsPluginFactory::ExampleActionsPluginFactory() +{ + getPluginMetadata().setDescription("Example actions view plugin"); + getPluginMetadata().setSummary("This example shows how to use action GUI building blocks in ManiVault Studio."); + getPluginMetadata().setCopyrightHolder({ "BioVault (Biomedical Visual Analytics Unit LUMC - TU Delft)" }); + getPluginMetadata().setAuthors({ + { "T. Kroes", { "Lead software architect" }, { "LUMC" } } + }); + getPluginMetadata().setOrganizations({ + { "LUMC", "Leiden University Medical Center", "https://www.lumc.nl/en/" }, + { "TU Delft", "Delft university of technology", "https://www.tudelft.nl/" } + }); + getPluginMetadata().setLicenseText("This plugin is distributed under the [LGPL v3.0](https://www.gnu.org/licenses/lgpl-3.0.en.html) license."); +} + +ViewPlugin* ExampleActionsPluginFactory::produce() +{ + return new ExampleActionsPlugin(this); +} + +mv::DataTypes ExampleActionsPluginFactory::supportedDataTypes() const +{ + return {}; +} diff --git a/ExampleActions/src/ExampleActionsPlugin.h b/ExampleActions/src/ExampleActionsPlugin.h new file mode 100644 index 0000000..fc06f5f --- /dev/null +++ b/ExampleActions/src/ExampleActionsPlugin.h @@ -0,0 +1,66 @@ +#pragma once + +#include + +#include + +/** All plugin related classes are in the ManiVault plugin namespace */ +using namespace mv::plugin; + +/** Drop widget used in this plugin is located in the ManiVault gui namespace */ +using namespace mv::gui; + +/** Dataset reference used in this plugin is located in the ManiVault util namespace */ +using namespace mv::util; + +class QLabel; + +/** + * Example view plugin class + * + * @authors T. Kroes + */ +class ExampleActionsPlugin : public ViewPlugin +{ + Q_OBJECT + +public: + + /** + * Constructor + * @param factory Pointer to the plugin factory + */ + ExampleActionsPlugin(const PluginFactory* factory); + + /** Destructor */ + ~ExampleActionsPlugin() override = default; + + /** This function is called by the core after the view plugin has been created */ + void init() override; + +private: +}; + +/** + * Example actions view plugin factory class + * + * Note: Factory does not need to be altered (merely responsible for generating new plugins when requested) + */ +class ExampleActionsPluginFactory : public ViewPluginFactory +{ + Q_INTERFACES(mv::plugin::ViewPluginFactory mv::plugin::PluginFactory) + Q_OBJECT + Q_PLUGIN_METADATA(IID "studio.manivault.ExampleActionsPlugin" + FILE "PluginInfo.json") + +public: + + /** Default constructor */ + ExampleActionsPluginFactory(); + + /** Creates an instance of the example view plugin */ + ViewPlugin* produce() override; + + /** Returns the data types that are supported by the example view plugin */ + mv::DataTypes supportedDataTypes() const override; +}; From ae5222a1826a698797bc45c5cab5427dafe8b913 Mon Sep 17 00:00:00 2001 From: Thomas Kroes Date: Fri, 9 Jan 2026 11:52:30 +0100 Subject: [PATCH 2/6] Add ActionsWidget and ActionsFilterModel for action management Introduces ActionsWidget and ActionsFilterModel to provide a categorized, searchable UI for managing actions. Updates CMakeLists.txt to include new sources and integrates ActionsWidget into ExampleActionsPlugin. The new components support recursive filtering and dynamic detail views for actions. --- ExampleActions/CMakeLists.txt | 4 + ExampleActions/src/ActionsFilterModel.cpp | 53 ++++++++ ExampleActions/src/ActionsFilterModel.h | 47 +++++++ ExampleActions/src/ActionsWidget.cpp | 138 ++++++++++++++++++++ ExampleActions/src/ActionsWidget.h | 89 +++++++++++++ ExampleActions/src/ExampleActionsPlugin.cpp | 5 +- 6 files changed, 335 insertions(+), 1 deletion(-) create mode 100644 ExampleActions/src/ActionsFilterModel.cpp create mode 100644 ExampleActions/src/ActionsFilterModel.h create mode 100644 ExampleActions/src/ActionsWidget.cpp create mode 100644 ExampleActions/src/ActionsWidget.h diff --git a/ExampleActions/CMakeLists.txt b/ExampleActions/CMakeLists.txt index e175f56..7325750 100644 --- a/ExampleActions/CMakeLists.txt +++ b/ExampleActions/CMakeLists.txt @@ -40,6 +40,10 @@ add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../Common ${CMAKE_CURRENT_BINARY_DI set(PLUGIN_SOURCES src/ExampleActionsPlugin.h src/ExampleActionsPlugin.cpp + src/ActionsWidget.h + src/ActionsWidget.cpp + src/ActionsFilterModel.h + src/ActionsFilterModel.cpp PluginInfo.json ) diff --git a/ExampleActions/src/ActionsFilterModel.cpp b/ExampleActions/src/ActionsFilterModel.cpp new file mode 100644 index 0000000..0e5f1aa --- /dev/null +++ b/ExampleActions/src/ActionsFilterModel.cpp @@ -0,0 +1,53 @@ +#include "ActionsFilterModel.h" + +ActionsFilterModel::ActionsFilterModel(QObject* parent) : + QSortFilterProxyModel(parent) +{ +} + +void ActionsFilterModel::setSearchRoles(QVector roles) +{ + searchRoles = std::move(roles); +} + +bool ActionsFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const +{ + const QModelIndex idx = sourceModel()->index(sourceRow, 0, sourceParent); + + if (!idx.isValid()) + return false; + + if (rowMatches(idx)) + return true; + + const auto childCount = sourceModel()->rowCount(idx); + + for (int i = 0; i < childCount; ++i) + { + if (filterAcceptsRow(i, idx)) + return true; + } + + return false; +} + +bool ActionsFilterModel::rowMatches(const QModelIndex& idx) const +{ + const auto re = filterRegularExpression(); + if (!re.isValid() || re.pattern().isEmpty()) + return true; + + // Always include DisplayRole at minimum + const QString display = sourceModel()->data(idx, Qt::DisplayRole).toString(); + if (display.contains(re)) + return true; + + for (int r : searchRoles) { + const auto string = sourceModel()->data(idx, r).toString(); + + if (!string.isEmpty() && string.contains(re)) + return true; + } + + return false; +} diff --git a/ExampleActions/src/ActionsFilterModel.h b/ExampleActions/src/ActionsFilterModel.h new file mode 100644 index 0000000..69c4226 --- /dev/null +++ b/ExampleActions/src/ActionsFilterModel.h @@ -0,0 +1,47 @@ +#pragma once + +#include +#include + +/** + * A QSortFilterProxyModel that filters recursively through all child items. + * + * @author Thomas Kroes +*/ +class ActionsFilterModel final : public QSortFilterProxyModel +{ + Q_OBJECT +public: + + /** + * Construct with optional parent. + * @param parent The parent QObject. + */ + explicit ActionsFilterModel(QObject* parent = nullptr); + + // Optional: include tooltips and ids in matching. + void setSearchRoles(QVector roles); + +protected: + + /** + * Reimplemented to filter recursively. + * @param sourceRow The row in the source model. + * @param sourceParent The parent index in the source model. + * @return True if the row should be included in the filtered model. + */ + bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const override; + +private: + + /** + * Check if a row matches the current filter. + * @param idx The index of the row to check. + * @return True if the row matches the filter, false otherwise. + */ + bool rowMatches(const QModelIndex& idx) const; + + +private: + QVector searchRoles; /** The roles to search in. */ +}; diff --git a/ExampleActions/src/ActionsWidget.cpp b/ExampleActions/src/ActionsWidget.cpp new file mode 100644 index 0000000..c37308e --- /dev/null +++ b/ExampleActions/src/ActionsWidget.cpp @@ -0,0 +1,138 @@ +#include "ActionsWidget.h" + +ActionsWidget::ActionsWidget(QWidget* parent): + QWidget(parent), + _model(this), + _proxy(this), + _splitter(Qt::Horizontal, this) +{ + _proxy.setSourceModel(&_model); + _proxy.setFilterCaseSensitivity(Qt::CaseInsensitive); + _proxy.setSearchRoles({Qt::ToolTipRole, Roles::ActionId}); + + _mainLayout.addWidget(&_splitter); + + setLayout(&_mainLayout); + + _leftPanelWidget.setLayout(&_leftPanelLayout); + _rightPanelWidget.setLayout(&_rightPanelLayout); + + _splitter.addWidget(&_leftPanelWidget); + _splitter.addWidget(&_rightPanelWidget); + + _splitter.setStretchFactor(0, 0); + _splitter.setStretchFactor(1, 1); + + _splitter.setSizes({ 320, 680 }); + + _leftPanelLayout.addWidget(&_search); + _leftPanelLayout.addWidget(&_tree); + + _rightPanelLayout.addWidget(&_detailsArea); + + _search.setPlaceholderText("Search actions"); + + _tree.setModel(&_proxy); + _tree.setHeaderHidden(true); + _tree.setUniformRowHeights(true); + _tree.setEditTriggers(QAbstractItemView::NoEditTriggers); + _tree.setSelectionMode(QAbstractItemView::SingleSelection); + _tree.setSelectionBehavior(QAbstractItemView::SelectRows); + _tree.header()->setStretchLastSection(true); + + _detailsLayout.setContentsMargins(0, 0, 0, 0); + + _detailsArea.setWidgetResizable(true); + _detailsArea.setWidget(&_detailsHost); + + connect(&_search, &QLineEdit::textChanged, this, [this](const QString& text) { + _proxy.setFilterRegularExpression(QRegularExpression(QRegularExpression::escape(text), QRegularExpression::CaseInsensitiveOption)); + + // UX: when searching, expand all; when empty, collapse to top-level + if (!text.isEmpty()) + _tree.expandAll(); + else + _tree.collapseAll(); + }); + + connect(_tree.selectionModel(), &QItemSelectionModel::currentChanged, this, &ActionsWidget::onCurrentChanged); +} + +void ActionsWidget::addAction(const QString& category, const QString& text, const QString& actionId, Factory factory, const QIcon& icon, const QString& toolTip) +{ + const auto cat = category.trimmed().isEmpty() ? QStringLiteral("Uncategorized") : category.trimmed(); + + auto categoryItem = ensureCategory(cat); + + auto actionItem = new QStandardItem(icon, text); + + actionItem->setToolTip(toolTip); + actionItem->setData(ActionNode, Roles::NodeType); + actionItem->setData(actionId, Roles::ActionId); + + categoryItem->appendRow(actionItem); + + _factories.insert(actionId, std::move(factory)); +} + +void ActionsWidget::onCurrentChanged(const QModelIndex& current, const QModelIndex& previous) +{ + clearDetails(); + + if (!current.isValid()) + return; + + const auto sourceIndex = _proxy.mapToSource(current); + + auto item = _model.itemFromIndex(sourceIndex); + + if (!item) + return; + + const auto type = item->data(Roles::NodeType).toInt(); + + if (type != ActionNode) + return; // category selected: leave details empty (or show placeholder) + + const auto actionId = item->data(Roles::ActionId).toString(); + + auto it = _factories.find(actionId); + + if (it == _factories.end()) + return; + + auto actionWidget = it.value()(&_detailsHost); + + if (!actionWidget) + return; + + _detailsLayout.addWidget(actionWidget); + _detailsLayout.addStretch(1); +} + +QStandardItem* ActionsWidget::ensureCategory(const QString& category) +{ + if (auto it = _categories.find(category); it != _categories.end()) + return it.value(); + + auto catItem = new QStandardItem(category); + + catItem->setData(CategoryNode, Roles::NodeType); + catItem->setSelectable(true); + + _model.appendRow(catItem); + _categories.insert(category, catItem); + + return catItem; +} + +void ActionsWidget::clearDetails() +{ + while (auto child = _detailsLayout.takeAt(0)) + { + if (auto actionWidget = child->widget()) + actionWidget->deleteLater(); + + delete child; + } +} diff --git a/ExampleActions/src/ActionsWidget.h b/ExampleActions/src/ActionsWidget.h new file mode 100644 index 0000000..5b05f73 --- /dev/null +++ b/ExampleActions/src/ActionsWidget.h @@ -0,0 +1,89 @@ +#pragma once + +#include "ActionsFilterModel.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* Custom roles for action items */ +namespace Roles { + static constexpr int NodeType = Qt::UserRole + 1; // int + static constexpr int ActionId = Qt::UserRole + 2; // QString +} + +/* Node types for items in the model */ +enum NodeType { CategoryNode = 0, ActionNode = 1 }; + +/* Factory function type for creating detail widgets */ +using Factory = std::function; + +class ActionsWidget final : public QWidget +{ + Q_OBJECT +public: + + /** + * Construct with optional parent. + * @param parent The parent QWidget. + */ + explicit ActionsWidget(QWidget* parent = nullptr); + + /** + * Add an action to the widget. + * @param category The category of the action. + * @param text The display text of the action. + * @param actionId The unique identifier of the action. + * @param factory The factory function to create the detail widget. + * @param icon The optional icon for the action. + * @param toolTip The optional tooltip for the action. + */ + void addAction(const QString& category, const QString& text, const QString& actionId, Factory factory, const QIcon& icon = {}, const QString& toolTip = {}); + +private slots: + + /** + * Slot called when the current selection changes in the tree view. + * @param current The new current index. + * @param previous The previous current index (unused). + */ + void onCurrentChanged(const QModelIndex& current, const QModelIndex& previous); + +private: + + /* + * Ensure that a category item exists in the model + * @param category The category name + * @return The QStandardItem representing the category + */ + QStandardItem* ensureCategory(const QString& category); + + /** Clear the details area */ + void clearDetails(); + +private: + QStandardItemModel _model; /** The underlying model */ + ActionsFilterModel _proxy; /** The filter proxy model */ + QVBoxLayout _mainLayout; /** The main layout */ + QSplitter _splitter; /** The main splitter */ + QWidget _leftPanelWidget; /** The left panel widget */ + QVBoxLayout _leftPanelLayout; /** The left panel layout */ + QWidget _rightPanelWidget; /** The right panel widget */ + QVBoxLayout _rightPanelLayout; /** The right panel layout */ + QLineEdit _search; /** The search box */ + QTreeView _tree; /** The tree view */ + QScrollArea _detailsArea; /** The details scroll area */ + QWidget _detailsHost; /** The details host widget */ + QVBoxLayout _detailsLayout; /** The details layout */ + QHash _categories; /** The category items */ + QHash _factories; /** The action detail widget factories */ +}; diff --git a/ExampleActions/src/ExampleActionsPlugin.cpp b/ExampleActions/src/ExampleActionsPlugin.cpp index 3638982..25087c3 100644 --- a/ExampleActions/src/ExampleActionsPlugin.cpp +++ b/ExampleActions/src/ExampleActionsPlugin.cpp @@ -1,4 +1,5 @@ #include "ExampleActionsPlugin.h" +#include "ActionsWidget.h" #include "../Common/common.h" @@ -15,10 +16,12 @@ ExampleActionsPlugin::ExampleActionsPlugin(const PluginFactory* factory) : void ExampleActionsPlugin::init() { - // Create layout auto layout = new QVBoxLayout(); layout->setContentsMargins(0, 0, 0, 0); + layout->addWidget(new ActionsWidget()); + + getWidget().setLayout(layout); } ExampleActionsPluginFactory::ExampleActionsPluginFactory() From b19b7b434d35da4188cb407fb86c90a22360ecc0 Mon Sep 17 00:00:00 2001 From: Thomas Kroes Date: Fri, 9 Jan 2026 14:32:24 +0100 Subject: [PATCH 3/6] Refactor ActionsWidget to support widget flags and actions Enhanced ActionsWidget to support widget flags and selected widget flags, and refactored the API to use WidgetAction-based factories. Added support for OptionsAction and group actions for widget settings and details. Updated ExampleActionsPlugin to register actions with widget flags and initial selections, improving flexibility and extensibility of the actions system. --- ExampleActions/src/ActionsWidget.cpp | 100 ++++++++++++++++---- ExampleActions/src/ActionsWidget.h | 66 +++++++------ ExampleActions/src/ExampleActionsPlugin.cpp | 47 ++++++++- ExampleActions/src/ExampleActionsPlugin.h | 3 +- 4 files changed, 163 insertions(+), 53 deletions(-) diff --git a/ExampleActions/src/ActionsWidget.cpp b/ExampleActions/src/ActionsWidget.cpp index c37308e..7007f4a 100644 --- a/ExampleActions/src/ActionsWidget.cpp +++ b/ExampleActions/src/ActionsWidget.cpp @@ -1,10 +1,17 @@ #include "ActionsWidget.h" +#include +#include + ActionsWidget::ActionsWidget(QWidget* parent): QWidget(parent), _model(this), _proxy(this), - _splitter(Qt::Horizontal, this) + _splitter(Qt::Horizontal, this), + _widgetSettingsAction(this, "Widget settings"), + _widgetFlagsAction(this, "Widget flags"), + _widgetEnabledAction(this, "Enabled"), + _detailsGroupAction(this, "Details") { _proxy.setSourceModel(&_model); _proxy.setFilterCaseSensitivity(Qt::CaseInsensitive); @@ -25,10 +32,13 @@ ActionsWidget::ActionsWidget(QWidget* parent): _splitter.setSizes({ 320, 680 }); + _leftPanelLayout.setContentsMargins(0, 0, 0, 0); _leftPanelLayout.addWidget(&_search); _leftPanelLayout.addWidget(&_tree); + _rightPanelLayout.addWidget(_widgetSettingsAction.createWidget(this)); _rightPanelLayout.addWidget(&_detailsArea); + _rightPanelLayout.setContentsMargins(0, 0, 0, 0); _search.setPlaceholderText("Search actions"); @@ -38,47 +48,73 @@ ActionsWidget::ActionsWidget(QWidget* parent): _tree.setEditTriggers(QAbstractItemView::NoEditTriggers); _tree.setSelectionMode(QAbstractItemView::SingleSelection); _tree.setSelectionBehavior(QAbstractItemView::SelectRows); + _tree.header()->setStretchLastSection(true); - - _detailsLayout.setContentsMargins(0, 0, 0, 0); _detailsArea.setWidgetResizable(true); _detailsArea.setWidget(&_detailsHost); + _detailsHost.setLayout(&_detailsLayout); + connect(&_search, &QLineEdit::textChanged, this, [this](const QString& text) { _proxy.setFilterRegularExpression(QRegularExpression(QRegularExpression::escape(text), QRegularExpression::CaseInsensitiveOption)); - // UX: when searching, expand all; when empty, collapse to top-level if (!text.isEmpty()) _tree.expandAll(); else _tree.collapseAll(); }); - connect(_tree.selectionModel(), &QItemSelectionModel::currentChanged, this, &ActionsWidget::onCurrentChanged); + connect(_tree.selectionModel(), &QItemSelectionModel::currentChanged, this, &ActionsWidget::requestUpdateWidget); + connect(&_widgetFlagsAction, &OptionsAction::selectedOptionsChanged, this, [this](const QStringList& selectedOptions) -> void { + const auto current = _tree.selectionModel()->currentIndex(); + + if (!current.isValid()) + return; + + const auto sourceIndex = _proxy.mapToSource(current); + + _model.setData(sourceIndex, selectedOptions, Roles::SelectedWidgetFlags); + + requestUpdateWidget(); + }); + + _widgetSettingsAction.addAction(&_widgetFlagsAction); + _widgetSettingsAction.addAction(&_widgetEnabledAction); } -void ActionsWidget::addAction(const QString& category, const QString& text, const QString& actionId, Factory factory, const QIcon& icon, const QString& toolTip) +void ActionsWidget::addAction(const QString& category, const QString& text, Factory factory, const QVariantMap& widgetFlags, const QStringList& selectedWidgetFlags, const QIcon& icon, const QString& toolTip) { - const auto cat = category.trimmed().isEmpty() ? QStringLiteral("Uncategorized") : category.trimmed(); + const auto cat = category.trimmed().isEmpty() ? QStringLiteral("Uncategorized") : category.trimmed(); + const auto actionId = QUuid::createUuid().toString(QUuid::WithoutBraces); - auto categoryItem = ensureCategory(cat); - - auto actionItem = new QStandardItem(icon, text); + auto categoryItem = ensureCategory(cat); + auto actionItem = new QStandardItem(icon, text); actionItem->setToolTip(toolTip); actionItem->setData(ActionNode, Roles::NodeType); actionItem->setData(actionId, Roles::ActionId); + if (!widgetFlags.isEmpty()) + actionItem->setData(widgetFlags, Roles::WidgetFlags); + + if (!selectedWidgetFlags.isEmpty()) + actionItem->setData(selectedWidgetFlags, Roles::SelectedWidgetFlags); + categoryItem->appendRow(actionItem); _factories.insert(actionId, std::move(factory)); } -void ActionsWidget::onCurrentChanged(const QModelIndex& current, const QModelIndex& previous) +void ActionsWidget::requestUpdateWidget() { + if (_ignoreRequestUpdateWidget) + return; + clearDetails(); + const auto current = _tree.selectionModel()->currentIndex(); + if (!current.isValid()) return; @@ -101,13 +137,32 @@ void ActionsWidget::onCurrentChanged(const QModelIndex& current, const QModelInd if (it == _factories.end()) return; - auto actionWidget = it.value()(&_detailsHost); + const auto widgetFlagsMap = item->data(Roles::WidgetFlags).toMap(); + const auto selectedWidgetFlags = item->data(Roles::SelectedWidgetFlags).toStringList(); + + _widgetFlagsAction.setEnabled(!widgetFlagsMap.isEmpty()); + + _ignoreRequestUpdateWidget = true; + { + _widgetFlagsAction.initialize(widgetFlagsMap.keys(), selectedWidgetFlags); + } + _ignoreRequestUpdateWidget = false; + + auto action = it.value()(&_detailsHost); - if (!actionWidget) + if (!action) return; - _detailsLayout.addWidget(actionWidget); - _detailsLayout.addStretch(1); + int defaultWidgetFlags = 0; + + for (const auto& selectedWidgetFlag : selectedWidgetFlags) + defaultWidgetFlags |= widgetFlagsMap.value(selectedWidgetFlag).toInt(); + + if (!widgetFlagsMap.isEmpty()) + action->setDefaultWidgetFlags(defaultWidgetFlags); + + _detailsGroupAction.addAction(action); + _detailsGroupAction.addStretch(); } QStandardItem* ActionsWidget::ensureCategory(const QString& category) @@ -128,11 +183,14 @@ QStandardItem* ActionsWidget::ensureCategory(const QString& category) void ActionsWidget::clearDetails() { - while (auto child = _detailsLayout.takeAt(0)) - { - if (auto actionWidget = child->widget()) - actionWidget->deleteLater(); + while (QLayoutItem* child = _detailsLayout.takeAt(0)) + { + if (QWidget* w = child->widget()) + w->deleteLater(); + delete child; + } + + _detailsGroupAction.clear(); - delete child; - } + _detailsLayout.addWidget(_detailsGroupAction.createWidget(this)); } diff --git a/ExampleActions/src/ActionsWidget.h b/ExampleActions/src/ActionsWidget.h index 5b05f73..d157f7e 100644 --- a/ExampleActions/src/ActionsWidget.h +++ b/ExampleActions/src/ActionsWidget.h @@ -2,9 +2,11 @@ #include "ActionsFilterModel.h" +#include "actions/HorizontalGroupAction.h" +#include "actions/OptionsAction.h" + #include #include -#include #include #include #include @@ -15,17 +17,21 @@ #include +using namespace mv::gui; + /* Custom roles for action items */ namespace Roles { - static constexpr int NodeType = Qt::UserRole + 1; // int - static constexpr int ActionId = Qt::UserRole + 2; // QString + static constexpr int NodeType = Qt::UserRole + 1; // int + static constexpr int ActionId = Qt::UserRole + 2; // QString + static constexpr int WidgetFlags = Qt::UserRole + 4; // QVariantMap + static constexpr int SelectedWidgetFlags = Qt::UserRole + 5; // QVariantMap } /* Node types for items in the model */ enum NodeType { CategoryNode = 0, ActionNode = 1 }; /* Factory function type for creating detail widgets */ -using Factory = std::function; +using Factory = std::function; class ActionsWidget final : public QWidget { @@ -39,24 +45,21 @@ class ActionsWidget final : public QWidget explicit ActionsWidget(QWidget* parent = nullptr); /** - * Add an action to the widget. - * @param category The category of the action. - * @param text The display text of the action. - * @param actionId The unique identifier of the action. - * @param factory The factory function to create the detail widget. - * @param icon The optional icon for the action. + * Add an action to the widget + * @param category The category of the action + * @param text The display text of the action + * @param factory The factory function to create the detail widget + * @param icon The optional icon for the action * @param toolTip The optional tooltip for the action. */ - void addAction(const QString& category, const QString& text, const QString& actionId, Factory factory, const QIcon& icon = {}, const QString& toolTip = {}); + void addAction(const QString& category, const QString& text, Factory factory, const QVariantMap& widgetFlags = {}, const QStringList& selectedWidgetFlags = {}, const QIcon & icon = {}, const QString & toolTip = {}); private slots: /** * Slot called when the current selection changes in the tree view. - * @param current The new current index. - * @param previous The previous current index (unused). */ - void onCurrentChanged(const QModelIndex& current, const QModelIndex& previous); + void requestUpdateWidget(); private: @@ -71,19 +74,24 @@ private slots: void clearDetails(); private: - QStandardItemModel _model; /** The underlying model */ - ActionsFilterModel _proxy; /** The filter proxy model */ - QVBoxLayout _mainLayout; /** The main layout */ - QSplitter _splitter; /** The main splitter */ - QWidget _leftPanelWidget; /** The left panel widget */ - QVBoxLayout _leftPanelLayout; /** The left panel layout */ - QWidget _rightPanelWidget; /** The right panel widget */ - QVBoxLayout _rightPanelLayout; /** The right panel layout */ - QLineEdit _search; /** The search box */ - QTreeView _tree; /** The tree view */ - QScrollArea _detailsArea; /** The details scroll area */ - QWidget _detailsHost; /** The details host widget */ - QVBoxLayout _detailsLayout; /** The details layout */ - QHash _categories; /** The category items */ - QHash _factories; /** The action detail widget factories */ + QStandardItemModel _model; /** The underlying model */ + ActionsFilterModel _proxy; /** The filter proxy model */ + QVBoxLayout _mainLayout; /** The main layout */ + QSplitter _splitter; /** The main splitter */ + QWidget _leftPanelWidget; /** The left panel widget */ + QVBoxLayout _leftPanelLayout; /** The left panel layout */ + QWidget _rightPanelWidget; /** The right panel widget */ + QVBoxLayout _rightPanelLayout; /** The right panel layout */ + QLineEdit _search; /** The search box */ + QTreeView _tree; /** The tree view */ + HorizontalGroupAction _widgetSettingsAction; /** The widget settings action */ + OptionsAction _widgetFlagsAction; /** The options action for widget flags */ + ToggleAction _widgetEnabledAction; /** The toggle action for enabling/disabling the widget */ + VerticalGroupAction _detailsGroupAction; /** The details group action */ + QScrollArea _detailsArea; /** The details scroll area */ + QWidget _detailsHost; /** The details host widget */ + QVBoxLayout _detailsLayout; /** The details layout */ + QHash _categories; /** The category items */ + QHash _factories; /** The action detail widget factories */ + bool _ignoreRequestUpdateWidget = false; /** Flag to ignore update requests */ }; diff --git a/ExampleActions/src/ExampleActionsPlugin.cpp b/ExampleActions/src/ExampleActionsPlugin.cpp index 25087c3..6f96f0d 100644 --- a/ExampleActions/src/ExampleActionsPlugin.cpp +++ b/ExampleActions/src/ExampleActionsPlugin.cpp @@ -1,5 +1,4 @@ #include "ExampleActionsPlugin.h" -#include "ActionsWidget.h" #include "../Common/common.h" @@ -19,9 +18,53 @@ void ExampleActionsPlugin::init() auto layout = new QVBoxLayout(); layout->setContentsMargins(0, 0, 0, 0); - layout->addWidget(new ActionsWidget()); + layout->addWidget(&_actionsWidget); getWidget().setLayout(layout); + + _actionsWidget.addAction("Textual", "String", [this](QWidget* parent) -> WidgetAction* { + return new StringAction(this, "Example string action", "Initial string value"); + }, { + { "Label", 1 }, + { "LineEdit", 2 }, + { "TextEdit", 4 } + }, { "LineEdit" }); + + _actionsWidget.addAction("Textual", "Strings", [this](QWidget* parent) -> WidgetAction* { + return new StringsAction(this, "Example strings action", { "String 1", "String 2", "String 3" }); + }, { + { "MayEdit", 1 }, + { "ListView", 2 } + }, { "ListView" }); + + _actionsWidget.addAction("Option", "Toggle", [this](QWidget* parent) -> WidgetAction* { + return new ToggleAction(this, "Example toggle action", true); + }, { + { "Icon", 1 }, + { "Text", 2 }, + { "CheckBox", 4 }, + { "PushButton", 8 } + }, { "CheckBox" }); + + _actionsWidget.addAction("Option", "Option select", [this](QWidget* parent) -> WidgetAction* { + return new OptionAction(this, "Example option action", { "A", "B", "C" }); + }, { + { "ComboBox", 1 }, + { "LineEdit", 2 }, + { "HorizontalButtons", 4 }, + { "VerticalButtons", 8 }, + { "Clearable", 10 } + }, { "ComboBox" }); + + _actionsWidget.addAction("Option", "Options select", [this](QWidget* parent) -> WidgetAction* { + return new OptionsAction(this, "Example options action", { "A", "B", "C" }, { "A", "B" }); + }, { + { "ComboBox", 1 }, + { "ListView", 2 }, + { "Selection", 4 }, + { "File", 8 }, + { "Tags", 10 } + }, { "ComboBox" }); } ExampleActionsPluginFactory::ExampleActionsPluginFactory() diff --git a/ExampleActions/src/ExampleActionsPlugin.h b/ExampleActions/src/ExampleActionsPlugin.h index fc06f5f..228aed7 100644 --- a/ExampleActions/src/ExampleActionsPlugin.h +++ b/ExampleActions/src/ExampleActionsPlugin.h @@ -2,7 +2,7 @@ #include -#include +#include "ActionsWidget.h" /** All plugin related classes are in the ManiVault plugin namespace */ using namespace mv::plugin; @@ -39,6 +39,7 @@ class ExampleActionsPlugin : public ViewPlugin void init() override; private: + ActionsWidget _actionsWidget; /** Actions widget */ }; /** From c652b793d13e2a1fe053f832f5621b291e2ad633 Mon Sep 17 00:00:00 2001 From: Thomas Kroes Date: Fri, 9 Jan 2026 15:03:20 +0100 Subject: [PATCH 4/6] Add new action types to ExampleActionsPlugin Introduces TriggerAction, TriggersAction, ColorAction, and ColorMap1DAction to the ExampleActionsPlugin. Updates the init method to register these new actions with appropriate configuration options. --- ExampleActions/src/ExampleActionsPlugin.cpp | 39 ++++++++++++++++----- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/ExampleActions/src/ExampleActionsPlugin.cpp b/ExampleActions/src/ExampleActionsPlugin.cpp index 6f96f0d..85765f6 100644 --- a/ExampleActions/src/ExampleActionsPlugin.cpp +++ b/ExampleActions/src/ExampleActionsPlugin.cpp @@ -4,9 +4,15 @@ #include -Q_PLUGIN_METADATA(IID "studio.manivault.ExampleActionsPlugin") +#include +#include +#include +#include +#include +#include +#include -using namespace mv; +Q_PLUGIN_METADATA(IID "studio.manivault.ExampleActionsPlugin")using namespace mv; ExampleActionsPlugin::ExampleActionsPlugin(const PluginFactory* factory) : ViewPlugin(factory) @@ -22,13 +28,19 @@ void ExampleActionsPlugin::init() getWidget().setLayout(layout); - _actionsWidget.addAction("Textual", "String", [this](QWidget* parent) -> WidgetAction* { - return new StringAction(this, "Example string action", "Initial string value"); + _actionsWidget.addAction("Trigger", "Trigger", [this](QWidget* parent) -> WidgetAction* { + return new TriggerAction(this, "Example trigger action"); }, { - { "Label", 1 }, - { "LineEdit", 2 }, - { "TextEdit", 4 } - }, { "LineEdit" }); + { "Icon", 1 }, + { "Text", 2 }, + }, { "Icon", "Text" }); + + _actionsWidget.addAction("Trigger", "Triggers", [this](QWidget* parent) -> WidgetAction* { + return new TriggersAction(this, "Example Triggers action", { { "Trigger 1", "Action 1" }, { "Trigger 2", "Action 2" } }); + }, { + { "Horizontal", 1 }, + { "Vertical", 2 }, + }, { "Horizontal" }); _actionsWidget.addAction("Textual", "Strings", [this](QWidget* parent) -> WidgetAction* { return new StringsAction(this, "Example strings action", { "String 1", "String 2", "String 3" }); @@ -65,6 +77,17 @@ void ExampleActionsPlugin::init() { "File", 8 }, { "Tags", 10 } }, { "ComboBox" }); + + _actionsWidget.addAction("Color", "Color", [this](QWidget* parent) -> WidgetAction* { + return new ColorAction(this, "Example color action"); + }); + + _actionsWidget.addAction("Color", "1D color map", [this](QWidget* parent) -> WidgetAction* { + return new ColorMap1DAction(this, "Example 1D color map action"); + }, { + { "Settings", 1 }, + { "EditRange", 2 } + }, { "Settings" }); } ExampleActionsPluginFactory::ExampleActionsPluginFactory() From b2b11392b5363d73bd5c378d01bba0642176b8bd Mon Sep 17 00:00:00 2001 From: Thomas Kroes Date: Fri, 9 Jan 2026 15:13:32 +0100 Subject: [PATCH 5/6] Add numerical and disk actions to plugin Introduced new actions for numerical input (decimal, integral, ranges, rectangles, points) and disk operations (file and directory pickers) to the ExampleActionsPlugin. Updated includes and registered these actions in the plugin initialization. --- ExampleActions/src/ExampleActionsPlugin.cpp | 59 +++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/ExampleActions/src/ExampleActionsPlugin.cpp b/ExampleActions/src/ExampleActionsPlugin.cpp index 85765f6..9895690 100644 --- a/ExampleActions/src/ExampleActionsPlugin.cpp +++ b/ExampleActions/src/ExampleActionsPlugin.cpp @@ -11,6 +11,17 @@ #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include Q_PLUGIN_METADATA(IID "studio.manivault.ExampleActionsPlugin")using namespace mv; @@ -88,6 +99,54 @@ void ExampleActionsPlugin::init() { "Settings", 1 }, { "EditRange", 2 } }, { "Settings" }); + + _actionsWidget.addAction("Numerical", "Decimal", [this](QWidget* parent) -> WidgetAction* { + return new DecimalAction(this, "Example decimal action", 0.0); + }, { + { "SpinBox", 1 }, + { "Slider", 2 }, + { "LineEdit", 2 } + }, { "SpinBox", "Slider" }); + + _actionsWidget.addAction("Numerical", "Decimal point", [this](QWidget* parent) -> WidgetAction* { + return new DecimalPointAction(this, "Example decimal point action"); + }); + + _actionsWidget.addAction("Numerical", "Decimal range", [this](QWidget* parent) -> WidgetAction* { + return new DecimalRangeAction(this, "Example decimal range action", 0.0, 1.0); + }); + + _actionsWidget.addAction("Numerical", "Decimal rectangle", [this](QWidget* parent) -> WidgetAction* { + return new DecimalRectangleAction(this, "Example decimal rectangle action"); + }); + + _actionsWidget.addAction("Numerical", "Integral", [this](QWidget* parent) -> WidgetAction* { + return new IntegralAction(this, "Example integral action"); + }, { + { "SpinBox", 1 }, + { "Slider", 2 }, + { "LineEdit", 2 } + }, { "SpinBox", "Slider" }); + + _actionsWidget.addAction("Numerical", "Integral point", [this](QWidget* parent) -> WidgetAction* { + return new IntegralAction(this, "Example integral point action"); + }); + + _actionsWidget.addAction("Numerical", "Integral range", [this](QWidget* parent) -> WidgetAction* { + return new IntegralRangeAction(this, "Example integral range action", 0.0, 1.0); + }); + + _actionsWidget.addAction("Numerical", "Integral rectangle", [this](QWidget* parent) -> WidgetAction* { + return new IntegralRectangleAction(this, "Example integral rectangle action"); + }); + + _actionsWidget.addAction("Disk", "File picker", [this](QWidget* parent) -> WidgetAction* { + return new FilePickerAction(this, "Example file picker action"); + }); + + _actionsWidget.addAction("Disk", "Directory picker", [this](QWidget* parent) -> WidgetAction* { + return new DirectoryPickerAction(this, "Example directory picker action"); + }); } ExampleActionsPluginFactory::ExampleActionsPluginFactory() From 6d2afe4e2738aeb0286b9e9956242ce02748ead6 Mon Sep 17 00:00:00 2001 From: Thomas Kroes Date: Fri, 9 Jan 2026 15:22:52 +0100 Subject: [PATCH 6/6] Add new action types and group label sizing Introduced DatasetPickerAction, HorizontalGroupAction, VerticalGroupAction, and GroupsAction to ExampleActionsPlugin. Added a new string action and set label sizing to fixed width in ActionsWidget for improved UI consistency. --- ExampleActions/src/ActionsWidget.cpp | 2 ++ ExampleActions/src/ExampleActionsPlugin.cpp | 29 ++++++++++++++++++++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/ExampleActions/src/ActionsWidget.cpp b/ExampleActions/src/ActionsWidget.cpp index 7007f4a..127c208 100644 --- a/ExampleActions/src/ActionsWidget.cpp +++ b/ExampleActions/src/ActionsWidget.cpp @@ -163,6 +163,8 @@ void ActionsWidget::requestUpdateWidget() _detailsGroupAction.addAction(action); _detailsGroupAction.addStretch(); + _detailsGroupAction.setLabelSizingType(GroupAction::LabelSizingType::Fixed); + _detailsGroupAction.setLabelWidthFixed(250); } QStandardItem* ActionsWidget::ensureCategory(const QString& category) diff --git a/ExampleActions/src/ExampleActionsPlugin.cpp b/ExampleActions/src/ExampleActionsPlugin.cpp index 9895690..0fcc4b1 100644 --- a/ExampleActions/src/ExampleActionsPlugin.cpp +++ b/ExampleActions/src/ExampleActionsPlugin.cpp @@ -15,13 +15,16 @@ #include #include #include -#include #include #include #include #include #include #include +#include +#include +#include +#include Q_PLUGIN_METADATA(IID "studio.manivault.ExampleActionsPlugin")using namespace mv; @@ -53,6 +56,14 @@ void ExampleActionsPlugin::init() { "Vertical", 2 }, }, { "Horizontal" }); + _actionsWidget.addAction("Textual", "String", [this](QWidget* parent) -> WidgetAction* { + return new StringAction(this, "Example string action", "Initial string value"); + }, { + { "Label", 1 }, + { "LineEdit", 2 }, + { "TextEdit", 4 } + }, { "LineEdit" }); + _actionsWidget.addAction("Textual", "Strings", [this](QWidget* parent) -> WidgetAction* { return new StringsAction(this, "Example strings action", { "String 1", "String 2", "String 3" }); }, { @@ -147,6 +158,22 @@ void ExampleActionsPlugin::init() _actionsWidget.addAction("Disk", "Directory picker", [this](QWidget* parent) -> WidgetAction* { return new DirectoryPickerAction(this, "Example directory picker action"); }); + + _actionsWidget.addAction("Dataset", "Dataset picker", [this](QWidget* parent) -> WidgetAction* { + return new DatasetPickerAction(this, "Example dataset picker action"); + }); + + _actionsWidget.addAction("Grouping", "Horizontal", [this](QWidget* parent) -> WidgetAction* { + return new HorizontalGroupAction(this, "Example horizontal grouping action"); + }); + + _actionsWidget.addAction("Grouping", "Vertical", [this](QWidget* parent) -> WidgetAction* { + return new VerticalGroupAction(this, "Example vertical grouping action"); + }); + + _actionsWidget.addAction("Grouping", "Groups", [this](QWidget* parent) -> WidgetAction* { + return new GroupsAction(this, "Example groups action"); + }); } ExampleActionsPluginFactory::ExampleActionsPluginFactory()