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..7325750 --- /dev/null +++ b/ExampleActions/CMakeLists.txt @@ -0,0 +1,119 @@ +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 + src/ActionsWidget.h + src/ActionsWidget.cpp + src/ActionsFilterModel.h + src/ActionsFilterModel.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/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..127c208 --- /dev/null +++ b/ExampleActions/src/ActionsWidget.cpp @@ -0,0 +1,198 @@ +#include "ActionsWidget.h" + +#include +#include + +ActionsWidget::ActionsWidget(QWidget* parent): + QWidget(parent), + _model(this), + _proxy(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); + _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.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"); + + _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); + + _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)); + + if (!text.isEmpty()) + _tree.expandAll(); + else + _tree.collapseAll(); + }); + + 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, 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 actionId = QUuid::createUuid().toString(QUuid::WithoutBraces); + + 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::requestUpdateWidget() +{ + if (_ignoreRequestUpdateWidget) + return; + + clearDetails(); + + const auto current = _tree.selectionModel()->currentIndex(); + + 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; + + 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 (!action) + return; + + int defaultWidgetFlags = 0; + + for (const auto& selectedWidgetFlag : selectedWidgetFlags) + defaultWidgetFlags |= widgetFlagsMap.value(selectedWidgetFlag).toInt(); + + if (!widgetFlagsMap.isEmpty()) + action->setDefaultWidgetFlags(defaultWidgetFlags); + + _detailsGroupAction.addAction(action); + _detailsGroupAction.addStretch(); + _detailsGroupAction.setLabelSizingType(GroupAction::LabelSizingType::Fixed); + _detailsGroupAction.setLabelWidthFixed(250); +} + +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 (QLayoutItem* child = _detailsLayout.takeAt(0)) + { + if (QWidget* w = child->widget()) + w->deleteLater(); + delete child; + } + + _detailsGroupAction.clear(); + + _detailsLayout.addWidget(_detailsGroupAction.createWidget(this)); +} diff --git a/ExampleActions/src/ActionsWidget.h b/ExampleActions/src/ActionsWidget.h new file mode 100644 index 0000000..d157f7e --- /dev/null +++ b/ExampleActions/src/ActionsWidget.h @@ -0,0 +1,97 @@ +#pragma once + +#include "ActionsFilterModel.h" + +#include "actions/HorizontalGroupAction.h" +#include "actions/OptionsAction.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 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; + +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 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, 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. + */ + void requestUpdateWidget(); + +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 */ + 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 new file mode 100644 index 0000000..0fcc4b1 --- /dev/null +++ b/ExampleActions/src/ExampleActionsPlugin.cpp @@ -0,0 +1,202 @@ +#include "ExampleActionsPlugin.h" + +#include "../Common/common.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +Q_PLUGIN_METADATA(IID "studio.manivault.ExampleActionsPlugin")using namespace mv; + +ExampleActionsPlugin::ExampleActionsPlugin(const PluginFactory* factory) : + ViewPlugin(factory) +{ +} + +void ExampleActionsPlugin::init() +{ + auto layout = new QVBoxLayout(); + + layout->setContentsMargins(0, 0, 0, 0); + layout->addWidget(&_actionsWidget); + + getWidget().setLayout(layout); + + _actionsWidget.addAction("Trigger", "Trigger", [this](QWidget* parent) -> WidgetAction* { + return new TriggerAction(this, "Example trigger action"); + }, { + { "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", "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" }); + + _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" }); + + _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"); + }); + + _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() +{ + 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..228aed7 --- /dev/null +++ b/ExampleActions/src/ExampleActionsPlugin.h @@ -0,0 +1,67 @@ +#pragma once + +#include + +#include "ActionsWidget.h" + +/** 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: + ActionsWidget _actionsWidget; /** Actions widget */ +}; + +/** + * 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; +};