From 4c067c66f9d772d14224a581c1ec82b1b2332111 Mon Sep 17 00:00:00 2001 From: Thomas Kroes Date: Fri, 14 Jun 2024 11:20:00 +0200 Subject: [PATCH 1/5] Add skeleton view plugin code (copied from ExampleViewPlugin) --- ExampleActions/CMakeLists.txt | 122 ++++++++++++ ExampleActions/src/.editorconfig | 10 + ExampleActions/src/ExampleViewPlugin.cpp | 219 ++++++++++++++++++++++ ExampleActions/src/ExampleViewPlugin.h | 100 ++++++++++ ExampleActions/src/ExampleViewPlugin.json | 5 + 5 files changed, 456 insertions(+) create mode 100644 ExampleActions/CMakeLists.txt create mode 100644 ExampleActions/src/.editorconfig create mode 100644 ExampleActions/src/ExampleViewPlugin.cpp create mode 100644 ExampleActions/src/ExampleViewPlugin.h create mode 100644 ExampleActions/src/ExampleViewPlugin.json diff --git a/ExampleActions/CMakeLists.txt b/ExampleActions/CMakeLists.txt new file mode 100644 index 0000000..fd41640 --- /dev/null +++ b/ExampleActions/CMakeLists.txt @@ -0,0 +1,122 @@ +cmake_minimum_required(VERSION 3.17) + +option(MV_UNITY_BUILD "Combine target source files into batches for faster compilation" OFF) + +# ----------------------------------------------------------------------------- +# ExampleView Plugin +# ----------------------------------------------------------------------------- +PROJECT("ExampleViewPlugin") + +# ----------------------------------------------------------------------------- +# CMake Options +# ----------------------------------------------------------------------------- +set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) +set(CMAKE_INCLUDE_CURRENT_DIR ON) +set(CMAKE_AUTOMOC ON) + +if(MSVC) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /DWIN32 /EHsc /MP /permissive- /Zc:__cplusplus") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /NODEFAULTLIB:LIBCMT") + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /MDd") + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /MD") +endif(MSVC) + +# ----------------------------------------------------------------------------- +# Set install directory +# ----------------------------------------------------------------------------- +# Check if the directory to the ManiVault installation has been provided +if(NOT DEFINED MV_INSTALL_DIR) + set(MV_INSTALL_DIR "" CACHE PATH "Directory where ManiVault is installed") + message(FATAL_ERROR "Please set MV_INSTALL_DIR to the directory where ManiVault is installed") +endif() +file(TO_CMAKE_PATH ${MV_INSTALL_DIR} MV_INSTALL_DIR) + +# ----------------------------------------------------------------------------- +# Dependencies +# ----------------------------------------------------------------------------- +find_package(Qt6 COMPONENTS Widgets WebEngineWidgets REQUIRED) + +# ----------------------------------------------------------------------------- +# Source files +# ----------------------------------------------------------------------------- +# Define the plugin sources +set(PLUGIN_SOURCES + src/ExampleViewPlugin.h + src/ExampleViewPlugin.cpp + src/ExampleViewPlugin.json +) + +set(PLUGIN_MOC_HEADERS + src/ExampleViewPlugin.h +) + +source_group( Plugin FILES ${PLUGIN_SOURCES}) + +# ----------------------------------------------------------------------------- +# CMake Target +# ----------------------------------------------------------------------------- +# Create dynamic library for the plugin +add_library(${PROJECT_NAME} SHARED ${PLUGIN_SOURCES}) + +# ----------------------------------------------------------------------------- +# Target include directories +# ----------------------------------------------------------------------------- +# Include ManiVault headers, including system data plugins +target_include_directories(${PROJECT_NAME} PRIVATE "${MV_INSTALL_DIR}/$/include/") + +# ----------------------------------------------------------------------------- +# Target properties +# ----------------------------------------------------------------------------- +# Request C++17 +target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_17) + +# 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 +# The link path in this repo assumes that the ManiVault core was built locally +# in contrast to having been installed with an installer. In the latter case you'll have +# to adapt the MV_LINK_PATH and PLUGIN_LINK_PATH to your install folder +set(MV_LINK_PATH "${MV_INSTALL_DIR}/$/lib") +set(PLUGIN_LINK_PATH "${MV_INSTALL_DIR}/$/$,lib,Plugins>") +set(MV_LINK_SUFFIX $,${CMAKE_LINK_LIBRARY_SUFFIX},${CMAKE_SHARED_LIBRARY_SUFFIX}>) + +set(MV_LINK_LIBRARY "${MV_LINK_PATH}/${CMAKE_SHARED_LIBRARY_PREFIX}MV_Public${MV_LINK_SUFFIX}") +set(POINTDATA_LINK_LIBRARY "${PLUGIN_LINK_PATH}/${CMAKE_SHARED_LIBRARY_PREFIX}PointData${MV_LINK_SUFFIX}") + +target_link_libraries(${PROJECT_NAME} PRIVATE "${MV_LINK_LIBRARY}") +target_link_libraries(${PROJECT_NAME} PRIVATE "${POINTDATA_LINK_LIBRARY}") + +# ----------------------------------------------------------------------------- +# Target installation +# ----------------------------------------------------------------------------- +# Install the shared plugin libary 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 ${MV_INSTALL_DIR}/$ +) + +# ----------------------------------------------------------------------------- +# Miscellaneous +# ----------------------------------------------------------------------------- +# Automatically set the debug environment (command + working directory) for MSVC +if(MSVC) + set_property(TARGET ${PROJECT_NAME} PROPERTY VS_DEBUGGER_WORKING_DIRECTORY $,${MV_INSTALL_DIR}/debug,${MV_INSTALL_DIR}/release>) + set_property(TARGET ${PROJECT_NAME} PROPERTY VS_DEBUGGER_COMMAND $,${MV_INSTALL_DIR}/debug/ManiVault\ Studio.exe,${MV_INSTALL_DIR}/release/ManiVault\ Studio.exe>) +endif() 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/ExampleViewPlugin.cpp b/ExampleActions/src/ExampleViewPlugin.cpp new file mode 100644 index 0000000..0c5732a --- /dev/null +++ b/ExampleActions/src/ExampleViewPlugin.cpp @@ -0,0 +1,219 @@ +#include "ExampleViewPlugin.h" + +#include + +#include + +#include +#include + +Q_PLUGIN_METADATA(IID "studio.manivault.ExampleViewPlugin") + +using namespace mv; + +ExampleViewPlugin::ExampleViewPlugin(const PluginFactory* factory) : + ViewPlugin(factory), + _dropWidget(nullptr), + _points(), + _currentDatasetName(), + _currentDatasetNameLabel(new QLabel()) +{ + // This line is mandatory if drag and drop behavior is required + _currentDatasetNameLabel->setAcceptDrops(true); + + // Align text in the center + _currentDatasetNameLabel->setAlignment(Qt::AlignCenter); +} + +void ExampleViewPlugin::init() +{ + // Create layout + auto layout = new QVBoxLayout(); + + layout->setContentsMargins(0, 0, 0, 0); + + layout->addWidget(_currentDatasetNameLabel); + + // Apply the layout + getWidget().setLayout(layout); + + // Instantiate new drop widget + _dropWidget = new DropWidget(_currentDatasetNameLabel); + + // Set the drop indicator widget (the widget that indicates that the view is eligible for data dropping) + _dropWidget->setDropIndicatorWidget(new DropWidget::DropIndicatorWidget(&getWidget(), "No data loaded", "Drag an item from the data hierarchy and drop it here to visualize data...")); + + // Initialize the drop regions + _dropWidget->initialize([this](const QMimeData* mimeData) -> DropWidget::DropRegions { + // A drop widget can contain zero or more drop regions + DropWidget::DropRegions dropRegions; + + const auto datasetsMimeData = dynamic_cast(mimeData); + + if (datasetsMimeData == nullptr) + return dropRegions; + + if (datasetsMimeData->getDatasets().count() > 1) + return dropRegions; + + // Gather information to generate appropriate drop regions + const auto dataset = datasetsMimeData->getDatasets().first(); + const auto datasetGuiName = dataset->getGuiName(); + const auto datasetId = dataset->getId(); + const auto dataType = dataset->getDataType(); + const auto dataTypes = DataTypes({ PointType }); + + // Visually indicate if the dataset is of the wrong data type and thus cannot be dropped + if (!dataTypes.contains(dataType)) { + dropRegions << new DropWidget::DropRegion(this, "Incompatible data", "This type of data is not supported", "exclamation-circle", false); + } + else { + + // Get points dataset from the core + auto candidateDataset = mv::data().getDataset(datasetId); + + // Accept points datasets drag and drop + if (dataType == PointType) { + const auto description = QString("Load %1 into example view").arg(datasetGuiName); + + if (_points == candidateDataset) { + + // Dataset cannot be dropped because it is already loaded + dropRegions << new DropWidget::DropRegion(this, "Warning", "Data already loaded", "exclamation-circle", false); + } + else { + + // Dataset can be dropped + dropRegions << new DropWidget::DropRegion(this, "Points", description, "map-marker-alt", true, [this, candidateDataset]() { + _points = candidateDataset; + }); + } + } + } + + return dropRegions; + }); + + // Respond when the name of the dataset in the dataset reference changes + connect(&_points, &Dataset::guiNameChanged, this, [this]() { + + auto newDatasetName = _points->getGuiName(); + + // Update the current dataset name label + _currentDatasetNameLabel->setText(QString("Current points dataset: %1").arg(newDatasetName)); + + // Only show the drop indicator when nothing is loaded in the dataset reference + _dropWidget->setShowDropIndicator(newDatasetName.isEmpty()); + }); + + // Alternatively, classes which derive from hdsp::EventListener (all plugins do) can also respond to events + _eventListener.addSupportedEventType(static_cast(EventType::DatasetAdded)); + _eventListener.addSupportedEventType(static_cast(EventType::DatasetDataChanged)); + _eventListener.addSupportedEventType(static_cast(EventType::DatasetRemoved)); + _eventListener.addSupportedEventType(static_cast(EventType::DatasetDataSelectionChanged)); + _eventListener.registerDataEventByType(PointType, std::bind(&ExampleViewPlugin::onDataEvent, this, std::placeholders::_1)); +} + +void ExampleViewPlugin::onDataEvent(mv::DatasetEvent* dataEvent) +{ + // Get smart pointer to dataset that changed + const auto changedDataSet = dataEvent->getDataset(); + + // Get GUI name of the dataset that changed + const auto datasetGuiName = changedDataSet->getGuiName(); + + // The data event has a type so that we know what type of data event occurred (e.g. data added, changed, removed, renamed, selection changes) + switch (dataEvent->getType()) { + + // A points dataset was added + case EventType::DatasetAdded: + { + // Cast the data event to a data added event + const auto dataAddedEvent = static_cast(dataEvent); + + // Get the GUI name of the added points dataset and print to the console + qDebug() << datasetGuiName << "was added"; + + break; + } + + // Points dataset data has changed + case EventType::DatasetDataChanged: + { + // Cast the data event to a data changed event + const auto dataChangedEvent = static_cast(dataEvent); + + // Get the name of the points dataset of which the data changed and print to the console + qDebug() << datasetGuiName << "data changed"; + + break; + } + + // Points dataset data was removed + case EventType::DatasetRemoved: + { + // Cast the data event to a data removed event + const auto dataRemovedEvent = static_cast(dataEvent); + + // Get the name of the removed points dataset and print to the console + qDebug() << datasetGuiName << "was removed"; + + break; + } + + // Points dataset selection has changed + case EventType::DatasetDataSelectionChanged: + { + // Cast the data event to a data selection changed event + const auto dataSelectionChangedEvent = static_cast(dataEvent); + + // Get the selection set that changed + const auto& selectionSet = changedDataSet->getSelection(); + + // Print to the console + qDebug() << datasetGuiName << "selection has changed"; + + break; + } + + default: + break; + } +} + +ViewPlugin* ExampleViewPluginFactory::produce() +{ + return new ExampleViewPlugin(this); +} + +mv::DataTypes ExampleViewPluginFactory::supportedDataTypes() const +{ + DataTypes supportedTypes; + + // This example analysis plugin is compatible with points datasets + supportedTypes.append(PointType); + + return supportedTypes; +} + +mv::gui::PluginTriggerActions ExampleViewPluginFactory::getPluginTriggerActions(const mv::Datasets& datasets) const +{ + PluginTriggerActions pluginTriggerActions; + + const auto getPluginInstance = [this]() -> ExampleViewPlugin* { + return dynamic_cast(plugins().requestViewPlugin(getKind())); + }; + + const auto numberOfDatasets = datasets.count(); + + if (numberOfDatasets >= 1 && PluginFactory::areAllDatasetsOfTheSameType(datasets, PointType)) { + auto pluginTriggerAction = new PluginTriggerAction(const_cast(this), this, "Example", "View example data", getIcon(), [this, getPluginInstance, datasets](PluginTriggerAction& pluginTriggerAction) -> void { + for (auto dataset : datasets) + getPluginInstance(); + }); + + pluginTriggerActions << pluginTriggerAction; + } + + return pluginTriggerActions; +} diff --git a/ExampleActions/src/ExampleViewPlugin.h b/ExampleActions/src/ExampleViewPlugin.h new file mode 100644 index 0000000..84dcd33 --- /dev/null +++ b/ExampleActions/src/ExampleViewPlugin.h @@ -0,0 +1,100 @@ +#pragma once + +#include + +#include +#include + +#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 + * + * This view plugin class provides skeleton code that shows how to develop + * a view plugin in ManiVault. It shows how to use the built-in drag and drop + * behavior. + * + * To see the plugin in action, please follow the steps below: + * + * 1. Go to the visualization menu in ManiVault + * 2. Choose the Example view menu item, the view will be added to the layout + * + * @authors J. Thijssen & T. Kroes + */ +class ExampleViewPlugin : public ViewPlugin +{ + Q_OBJECT + +public: + + /** + * Constructor + * @param factory Pointer to the plugin factory + */ + ExampleViewPlugin(const PluginFactory* factory); + + /** Destructor */ + ~ExampleViewPlugin() override = default; + + /** This function is called by the core after the view plugin has been created */ + void init() override; + + /** + * Invoked when a data event occurs + * @param dataEvent Data event which occurred + */ + void onDataEvent(mv::DatasetEvent* dataEvent); + +protected: + DropWidget* _dropWidget; /** Widget for drag and drop behavior */ + mv::Dataset _points; /** Points smart pointer */ + QString _currentDatasetName; /** Name of the current dataset */ + QLabel* _currentDatasetNameLabel; /** Label that show the current dataset name */ +}; + +/** + * Example view plugin factory class + * + * Note: Factory does not need to be altered (merely responsible for generating new plugins when requested) + */ +class ExampleViewPluginFactory : public ViewPluginFactory +{ + Q_INTERFACES(mv::plugin::ViewPluginFactory mv::plugin::PluginFactory) + Q_OBJECT + Q_PLUGIN_METADATA(IID "studio.manivault.ExampleViewPlugin" + FILE "ExampleViewPlugin.json") + +public: + + /** Default constructor */ + ExampleViewPluginFactory() {} + + /** Destructor */ + ~ExampleViewPluginFactory() override {} + + /** 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; + + /** + * Get plugin trigger actions given \p datasets + * @param datasets Vector of input datasets + * @return Vector of plugin trigger actions + */ + PluginTriggerActions getPluginTriggerActions(const mv::Datasets& datasets) const override; +}; diff --git a/ExampleActions/src/ExampleViewPlugin.json b/ExampleActions/src/ExampleViewPlugin.json new file mode 100644 index 0000000..a967080 --- /dev/null +++ b/ExampleActions/src/ExampleViewPlugin.json @@ -0,0 +1,5 @@ +{ + "name" : "Example View", + "version" : "1.1", + "dependencies" : ["Points"] +} From 5eb468c375c66fa99ea7ab0db65bb5e97ec9ebc5 Mon Sep 17 00:00:00 2001 From: Thomas Kroes Date: Fri, 14 Jun 2024 11:33:00 +0200 Subject: [PATCH 2/5] Further work on skeleton code Renamed all classes etc Removed redundant code left over from copy --- CMakeLists.txt | 1 + ExampleActions/CMakeLists.txt | 10 +- ExampleActions/src/ExampleActionsPlugin.cpp | 30 +++ ExampleActions/src/ExampleActionsPlugin.h | 70 ++++++ ...wPlugin.json => ExampleActionsPlugin.json} | 2 +- ExampleActions/src/ExampleViewPlugin.cpp | 219 ------------------ ExampleActions/src/ExampleViewPlugin.h | 100 -------- 7 files changed, 107 insertions(+), 325 deletions(-) create mode 100644 ExampleActions/src/ExampleActionsPlugin.cpp create mode 100644 ExampleActions/src/ExampleActionsPlugin.h rename ExampleActions/src/{ExampleViewPlugin.json => ExampleActionsPlugin.json} (64%) delete mode 100644 ExampleActions/src/ExampleViewPlugin.cpp delete mode 100644 ExampleActions/src/ExampleViewPlugin.h diff --git a/CMakeLists.txt b/CMakeLists.txt index a54b9f1..a8d0d4e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,3 +14,4 @@ add_subdirectory(ExampleTransformation) add_subdirectory(ExampleLoader) add_subdirectory(ExampleWriter) add_subdirectory(ExampleData) +add_subdirectory(ExampleActions) diff --git a/ExampleActions/CMakeLists.txt b/ExampleActions/CMakeLists.txt index fd41640..8c90631 100644 --- a/ExampleActions/CMakeLists.txt +++ b/ExampleActions/CMakeLists.txt @@ -5,7 +5,7 @@ option(MV_UNITY_BUILD "Combine target source files into batches for faster compi # ----------------------------------------------------------------------------- # ExampleView Plugin # ----------------------------------------------------------------------------- -PROJECT("ExampleViewPlugin") +PROJECT("ExampleActionsPlugin") # ----------------------------------------------------------------------------- # CMake Options @@ -41,13 +41,13 @@ find_package(Qt6 COMPONENTS Widgets WebEngineWidgets REQUIRED) # ----------------------------------------------------------------------------- # Define the plugin sources set(PLUGIN_SOURCES - src/ExampleViewPlugin.h - src/ExampleViewPlugin.cpp - src/ExampleViewPlugin.json + src/ExampleActionsPlugin.h + src/ExampleActionsPlugin.cpp + src/ExampleActionsPlugin.json ) set(PLUGIN_MOC_HEADERS - src/ExampleViewPlugin.h + src/ExampleActionsPlugin.h ) source_group( Plugin FILES ${PLUGIN_SOURCES}) diff --git a/ExampleActions/src/ExampleActionsPlugin.cpp b/ExampleActions/src/ExampleActionsPlugin.cpp new file mode 100644 index 0000000..76ef38f --- /dev/null +++ b/ExampleActions/src/ExampleActionsPlugin.cpp @@ -0,0 +1,30 @@ +#include "ExampleActionsPlugin.h" + +#include + +#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); + + // Apply the layout + getWidget().setLayout(layout); +} + +ViewPlugin* ExampleActionsPluginFactory::produce() +{ + return new ExampleActionsPlugin(this); +} diff --git a/ExampleActions/src/ExampleActionsPlugin.h b/ExampleActions/src/ExampleActionsPlugin.h new file mode 100644 index 0000000..177ad13 --- /dev/null +++ b/ExampleActions/src/ExampleActionsPlugin.h @@ -0,0 +1,70 @@ +#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; + +/** + * Example actions view plugin class + * + * This view plugin class provides skeleton code that shows how to use GUI building + * blocks (aka actions) to build interfaces. + * + * To see the plugin in action, please follow the steps below: + * + * 1. Go to the visualization menu in ManiVault + * 2. Choose the Example actions menu item, the view will be added to the layout + * + * @author T. Kroes + */ +class ExampleActionsPlugin : public ViewPlugin +{ + Q_OBJECT + +public: + + /** + * Construct with pointer to plugin \p factory + * @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; +}; + +/** + * Example actions 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 "ExampleActionsPlugin.json") + +public: + + /** Default constructor */ + ExampleActionsPluginFactory() {} + + /** Destructor */ + ~ExampleActionsPluginFactory() override {} + + /** Creates an instance of the example view plugin */ + ViewPlugin* produce() override; +}; diff --git a/ExampleActions/src/ExampleViewPlugin.json b/ExampleActions/src/ExampleActionsPlugin.json similarity index 64% rename from ExampleActions/src/ExampleViewPlugin.json rename to ExampleActions/src/ExampleActionsPlugin.json index a967080..dffc5ec 100644 --- a/ExampleActions/src/ExampleViewPlugin.json +++ b/ExampleActions/src/ExampleActionsPlugin.json @@ -1,5 +1,5 @@ { - "name" : "Example View", + "name" : "Example Actions", "version" : "1.1", "dependencies" : ["Points"] } diff --git a/ExampleActions/src/ExampleViewPlugin.cpp b/ExampleActions/src/ExampleViewPlugin.cpp deleted file mode 100644 index 0c5732a..0000000 --- a/ExampleActions/src/ExampleViewPlugin.cpp +++ /dev/null @@ -1,219 +0,0 @@ -#include "ExampleViewPlugin.h" - -#include - -#include - -#include -#include - -Q_PLUGIN_METADATA(IID "studio.manivault.ExampleViewPlugin") - -using namespace mv; - -ExampleViewPlugin::ExampleViewPlugin(const PluginFactory* factory) : - ViewPlugin(factory), - _dropWidget(nullptr), - _points(), - _currentDatasetName(), - _currentDatasetNameLabel(new QLabel()) -{ - // This line is mandatory if drag and drop behavior is required - _currentDatasetNameLabel->setAcceptDrops(true); - - // Align text in the center - _currentDatasetNameLabel->setAlignment(Qt::AlignCenter); -} - -void ExampleViewPlugin::init() -{ - // Create layout - auto layout = new QVBoxLayout(); - - layout->setContentsMargins(0, 0, 0, 0); - - layout->addWidget(_currentDatasetNameLabel); - - // Apply the layout - getWidget().setLayout(layout); - - // Instantiate new drop widget - _dropWidget = new DropWidget(_currentDatasetNameLabel); - - // Set the drop indicator widget (the widget that indicates that the view is eligible for data dropping) - _dropWidget->setDropIndicatorWidget(new DropWidget::DropIndicatorWidget(&getWidget(), "No data loaded", "Drag an item from the data hierarchy and drop it here to visualize data...")); - - // Initialize the drop regions - _dropWidget->initialize([this](const QMimeData* mimeData) -> DropWidget::DropRegions { - // A drop widget can contain zero or more drop regions - DropWidget::DropRegions dropRegions; - - const auto datasetsMimeData = dynamic_cast(mimeData); - - if (datasetsMimeData == nullptr) - return dropRegions; - - if (datasetsMimeData->getDatasets().count() > 1) - return dropRegions; - - // Gather information to generate appropriate drop regions - const auto dataset = datasetsMimeData->getDatasets().first(); - const auto datasetGuiName = dataset->getGuiName(); - const auto datasetId = dataset->getId(); - const auto dataType = dataset->getDataType(); - const auto dataTypes = DataTypes({ PointType }); - - // Visually indicate if the dataset is of the wrong data type and thus cannot be dropped - if (!dataTypes.contains(dataType)) { - dropRegions << new DropWidget::DropRegion(this, "Incompatible data", "This type of data is not supported", "exclamation-circle", false); - } - else { - - // Get points dataset from the core - auto candidateDataset = mv::data().getDataset(datasetId); - - // Accept points datasets drag and drop - if (dataType == PointType) { - const auto description = QString("Load %1 into example view").arg(datasetGuiName); - - if (_points == candidateDataset) { - - // Dataset cannot be dropped because it is already loaded - dropRegions << new DropWidget::DropRegion(this, "Warning", "Data already loaded", "exclamation-circle", false); - } - else { - - // Dataset can be dropped - dropRegions << new DropWidget::DropRegion(this, "Points", description, "map-marker-alt", true, [this, candidateDataset]() { - _points = candidateDataset; - }); - } - } - } - - return dropRegions; - }); - - // Respond when the name of the dataset in the dataset reference changes - connect(&_points, &Dataset::guiNameChanged, this, [this]() { - - auto newDatasetName = _points->getGuiName(); - - // Update the current dataset name label - _currentDatasetNameLabel->setText(QString("Current points dataset: %1").arg(newDatasetName)); - - // Only show the drop indicator when nothing is loaded in the dataset reference - _dropWidget->setShowDropIndicator(newDatasetName.isEmpty()); - }); - - // Alternatively, classes which derive from hdsp::EventListener (all plugins do) can also respond to events - _eventListener.addSupportedEventType(static_cast(EventType::DatasetAdded)); - _eventListener.addSupportedEventType(static_cast(EventType::DatasetDataChanged)); - _eventListener.addSupportedEventType(static_cast(EventType::DatasetRemoved)); - _eventListener.addSupportedEventType(static_cast(EventType::DatasetDataSelectionChanged)); - _eventListener.registerDataEventByType(PointType, std::bind(&ExampleViewPlugin::onDataEvent, this, std::placeholders::_1)); -} - -void ExampleViewPlugin::onDataEvent(mv::DatasetEvent* dataEvent) -{ - // Get smart pointer to dataset that changed - const auto changedDataSet = dataEvent->getDataset(); - - // Get GUI name of the dataset that changed - const auto datasetGuiName = changedDataSet->getGuiName(); - - // The data event has a type so that we know what type of data event occurred (e.g. data added, changed, removed, renamed, selection changes) - switch (dataEvent->getType()) { - - // A points dataset was added - case EventType::DatasetAdded: - { - // Cast the data event to a data added event - const auto dataAddedEvent = static_cast(dataEvent); - - // Get the GUI name of the added points dataset and print to the console - qDebug() << datasetGuiName << "was added"; - - break; - } - - // Points dataset data has changed - case EventType::DatasetDataChanged: - { - // Cast the data event to a data changed event - const auto dataChangedEvent = static_cast(dataEvent); - - // Get the name of the points dataset of which the data changed and print to the console - qDebug() << datasetGuiName << "data changed"; - - break; - } - - // Points dataset data was removed - case EventType::DatasetRemoved: - { - // Cast the data event to a data removed event - const auto dataRemovedEvent = static_cast(dataEvent); - - // Get the name of the removed points dataset and print to the console - qDebug() << datasetGuiName << "was removed"; - - break; - } - - // Points dataset selection has changed - case EventType::DatasetDataSelectionChanged: - { - // Cast the data event to a data selection changed event - const auto dataSelectionChangedEvent = static_cast(dataEvent); - - // Get the selection set that changed - const auto& selectionSet = changedDataSet->getSelection(); - - // Print to the console - qDebug() << datasetGuiName << "selection has changed"; - - break; - } - - default: - break; - } -} - -ViewPlugin* ExampleViewPluginFactory::produce() -{ - return new ExampleViewPlugin(this); -} - -mv::DataTypes ExampleViewPluginFactory::supportedDataTypes() const -{ - DataTypes supportedTypes; - - // This example analysis plugin is compatible with points datasets - supportedTypes.append(PointType); - - return supportedTypes; -} - -mv::gui::PluginTriggerActions ExampleViewPluginFactory::getPluginTriggerActions(const mv::Datasets& datasets) const -{ - PluginTriggerActions pluginTriggerActions; - - const auto getPluginInstance = [this]() -> ExampleViewPlugin* { - return dynamic_cast(plugins().requestViewPlugin(getKind())); - }; - - const auto numberOfDatasets = datasets.count(); - - if (numberOfDatasets >= 1 && PluginFactory::areAllDatasetsOfTheSameType(datasets, PointType)) { - auto pluginTriggerAction = new PluginTriggerAction(const_cast(this), this, "Example", "View example data", getIcon(), [this, getPluginInstance, datasets](PluginTriggerAction& pluginTriggerAction) -> void { - for (auto dataset : datasets) - getPluginInstance(); - }); - - pluginTriggerActions << pluginTriggerAction; - } - - return pluginTriggerActions; -} diff --git a/ExampleActions/src/ExampleViewPlugin.h b/ExampleActions/src/ExampleViewPlugin.h deleted file mode 100644 index 84dcd33..0000000 --- a/ExampleActions/src/ExampleViewPlugin.h +++ /dev/null @@ -1,100 +0,0 @@ -#pragma once - -#include - -#include -#include - -#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 - * - * This view plugin class provides skeleton code that shows how to develop - * a view plugin in ManiVault. It shows how to use the built-in drag and drop - * behavior. - * - * To see the plugin in action, please follow the steps below: - * - * 1. Go to the visualization menu in ManiVault - * 2. Choose the Example view menu item, the view will be added to the layout - * - * @authors J. Thijssen & T. Kroes - */ -class ExampleViewPlugin : public ViewPlugin -{ - Q_OBJECT - -public: - - /** - * Constructor - * @param factory Pointer to the plugin factory - */ - ExampleViewPlugin(const PluginFactory* factory); - - /** Destructor */ - ~ExampleViewPlugin() override = default; - - /** This function is called by the core after the view plugin has been created */ - void init() override; - - /** - * Invoked when a data event occurs - * @param dataEvent Data event which occurred - */ - void onDataEvent(mv::DatasetEvent* dataEvent); - -protected: - DropWidget* _dropWidget; /** Widget for drag and drop behavior */ - mv::Dataset _points; /** Points smart pointer */ - QString _currentDatasetName; /** Name of the current dataset */ - QLabel* _currentDatasetNameLabel; /** Label that show the current dataset name */ -}; - -/** - * Example view plugin factory class - * - * Note: Factory does not need to be altered (merely responsible for generating new plugins when requested) - */ -class ExampleViewPluginFactory : public ViewPluginFactory -{ - Q_INTERFACES(mv::plugin::ViewPluginFactory mv::plugin::PluginFactory) - Q_OBJECT - Q_PLUGIN_METADATA(IID "studio.manivault.ExampleViewPlugin" - FILE "ExampleViewPlugin.json") - -public: - - /** Default constructor */ - ExampleViewPluginFactory() {} - - /** Destructor */ - ~ExampleViewPluginFactory() override {} - - /** 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; - - /** - * Get plugin trigger actions given \p datasets - * @param datasets Vector of input datasets - * @return Vector of plugin trigger actions - */ - PluginTriggerActions getPluginTriggerActions(const mv::Datasets& datasets) const override; -}; From 0a06d7d09f523524b734229b38306b9eff918a59 Mon Sep 17 00:00:00 2001 From: Thomas Kroes Date: Wed, 19 Jun 2024 16:59:35 +0200 Subject: [PATCH 3/5] First working version --- ExampleActions/CMakeLists.txt | 21 +++- .../src/AbstractExampleActionsModel.cpp | 36 ++++++ .../src/AbstractExampleActionsModel.h | 52 +++++++++ .../src/ExampleActionsFilterModel.cpp | 17 +++ .../src/ExampleActionsFilterModel.h | 25 ++++ ExampleActions/src/ExampleActionsPlugin.cpp | 64 ++++++++++- ExampleActions/src/ExampleActionsPlugin.h | 15 +++ .../src/ExampleActionsTreeModel.cpp | 107 ++++++++++++++++++ ExampleActions/src/ExampleActionsTreeModel.h | 65 +++++++++++ ExampleActions/src/ExampleProxyAction.cpp | 30 +++++ ExampleActions/src/ExampleProxyAction.h | 47 ++++++++ 11 files changed, 474 insertions(+), 5 deletions(-) create mode 100644 ExampleActions/src/AbstractExampleActionsModel.cpp create mode 100644 ExampleActions/src/AbstractExampleActionsModel.h create mode 100644 ExampleActions/src/ExampleActionsFilterModel.cpp create mode 100644 ExampleActions/src/ExampleActionsFilterModel.h create mode 100644 ExampleActions/src/ExampleActionsTreeModel.cpp create mode 100644 ExampleActions/src/ExampleActionsTreeModel.h create mode 100644 ExampleActions/src/ExampleProxyAction.cpp create mode 100644 ExampleActions/src/ExampleProxyAction.h diff --git a/ExampleActions/CMakeLists.txt b/ExampleActions/CMakeLists.txt index 8c90631..47816b8 100644 --- a/ExampleActions/CMakeLists.txt +++ b/ExampleActions/CMakeLists.txt @@ -46,17 +46,34 @@ set(PLUGIN_SOURCES src/ExampleActionsPlugin.json ) +set(MODEL + src/AbstractExampleActionsModel.h + src/AbstractExampleActionsModel.cpp + src/ExampleActionsTreeModel.h + src/ExampleActionsTreeModel.cpp + src/ExampleActionsFilterModel.h + src/ExampleActionsFilterModel.cpp +) + +set(ACTIONS + src/ExampleProxyAction.h + src/ExampleProxyAction.cpp +) + set(PLUGIN_MOC_HEADERS src/ExampleActionsPlugin.h + src/ExampleActionsTreeModel.h ) -source_group( Plugin FILES ${PLUGIN_SOURCES}) +source_group(Plugin FILES ${PLUGIN_SOURCES}) +source_group(Model FILES ${MODEL}) +source_group(Actions FILES ${ACTIONS}) # ----------------------------------------------------------------------------- # CMake Target # ----------------------------------------------------------------------------- # Create dynamic library for the plugin -add_library(${PROJECT_NAME} SHARED ${PLUGIN_SOURCES}) +add_library(${PROJECT_NAME} SHARED ${PLUGIN_SOURCES} ${MODEL} ${ACTIONS}) # ----------------------------------------------------------------------------- # Target include directories diff --git a/ExampleActions/src/AbstractExampleActionsModel.cpp b/ExampleActions/src/AbstractExampleActionsModel.cpp new file mode 100644 index 0000000..8f86e3f --- /dev/null +++ b/ExampleActions/src/AbstractExampleActionsModel.cpp @@ -0,0 +1,36 @@ +// 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 "AbstractExampleActionsModel.h" + +#include + +#include + +using namespace mv; +using namespace mv::util; +using namespace mv::gui; + +#ifdef _DEBUG + #define ABSTRACT_EXAMPLE_ACTIONS_MODEL_VERBOSE +#endif + +AbstractExampleActionsModel::ExampleActionItem::ExampleActionItem(const QString& title, mv::gui::WidgetAction* action) : + QStandardItem(title), + QObject(), + _action(action) +{ + setEditable(false); + setDropEnabled(false); + + Q_ASSERT(_action != nullptr); + + if (!_action) + return; +} + +mv::gui::WidgetAction* AbstractExampleActionsModel::ExampleActionItem::getAction() const +{ + return _action; +} diff --git a/ExampleActions/src/AbstractExampleActionsModel.h b/ExampleActions/src/AbstractExampleActionsModel.h new file mode 100644 index 0000000..028ae7a --- /dev/null +++ b/ExampleActions/src/AbstractExampleActionsModel.h @@ -0,0 +1,52 @@ +// 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 + +#include + +namespace mv::gui { + class WidgetAction; +} + +/** + * Abstract example actions model class + * + * @author Thomas Kroes + */ +class AbstractExampleActionsModel : public mv::StandardItemModel +{ +protected: + + /** Base standard model item class for example widget action */ + class ExampleActionItem : public QStandardItem, public QObject { + public: + + /** + * Construct with pointer to \p action + * @param title Item title + * @param action Pointer to action to display item for + */ + ExampleActionItem(const QString& title, mv::gui::WidgetAction* action); + + /** + * Get action + * return Pointer to action to display item for + */ + mv::gui::WidgetAction* getAction() const; + + private: + mv::gui::WidgetAction* _action; /** Pointer to action to display item for */ + }; + +public: + + /** No need for custom constructor */ + using mv::StandardItemModel::StandardItemModel; + + friend class ExampleActionStyledItemDelegate; + friend class ExampleActionsPlugin; +}; diff --git a/ExampleActions/src/ExampleActionsFilterModel.cpp b/ExampleActions/src/ExampleActionsFilterModel.cpp new file mode 100644 index 0000000..9f458f0 --- /dev/null +++ b/ExampleActions/src/ExampleActionsFilterModel.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 "ExampleActionsFilterModel.h" + +#include + +#ifdef _DEBUG + #define EXAMPLE_ACTIONS_FILTER_MODEL_VERBOSE +#endif + +ExampleActionsFilterModel::ExampleActionsFilterModel(QObject* parent /*= nullptr*/) : + SortFilterProxyModel(parent) +{ + setRecursiveFilteringEnabled(true); +} diff --git a/ExampleActions/src/ExampleActionsFilterModel.h b/ExampleActions/src/ExampleActionsFilterModel.h new file mode 100644 index 0000000..198f30a --- /dev/null +++ b/ExampleActions/src/ExampleActionsFilterModel.h @@ -0,0 +1,25 @@ +// 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 + +/** + * Example actions filter model class + * + * Sorting and filtering model for example actions + * + * @author Thomas Kroes + */ +class ExampleActionsFilterModel : public mv::SortFilterProxyModel +{ +public: + + /** + * Construct the filter model with \p parent + * @param parent Pointer to parent object + */ + ExampleActionsFilterModel(QObject* parent = nullptr); +}; diff --git a/ExampleActions/src/ExampleActionsPlugin.cpp b/ExampleActions/src/ExampleActionsPlugin.cpp index 76ef38f..00fa249 100644 --- a/ExampleActions/src/ExampleActionsPlugin.cpp +++ b/ExampleActions/src/ExampleActionsPlugin.cpp @@ -1,26 +1,84 @@ #include "ExampleActionsPlugin.h" +#include "ExampleProxyAction.h" #include #include +#include Q_PLUGIN_METADATA(IID "studio.manivault.ExampleActionsPlugin") using namespace mv; ExampleActionsPlugin::ExampleActionsPlugin(const PluginFactory* factory) : - ViewPlugin(factory) + ViewPlugin(factory), + _exampleActionsTreeModel(this), + _exampleActionsFilterModel(this), + _examplesTreeAction(this, "Examples tree"), + _examplesGroupsAction(this, "Examples groups") { + _examplesTreeAction.setIconByName("play"); + + _examplesTreeAction.setWidgetConfigurationFunction([this](WidgetAction* action, QWidget* widget) -> void { + auto hierarchyWidget = widget->findChild("HierarchyWidget"); + + Q_ASSERT(hierarchyWidget); + + if (!hierarchyWidget) + return; + + auto selectionModel = hierarchyWidget->getTreeView().selectionModel(); + + connect(selectionModel, &QItemSelectionModel::selectionChanged, hierarchyWidget, [this, selectionModel](const QItemSelection& selected, const QItemSelection& deselected) -> void { + _examplesGroupsAction.resetGroupActions(); + + QSet selectedModelIndexes; + + for (const auto& selectedIndex : selectionModel->selectedRows(static_cast(ExampleActionsTreeModel::Column::ClassName))) { + const auto nameIndex = selectedIndex.siblingAtColumn(static_cast(ExampleActionsTreeModel::Column::Name)); + + if (nameIndex.parent().isValid()) { + selectedModelIndexes << selectedIndex; + } + else { + for (int rowIndex = 0; rowIndex < _exampleActionsFilterModel.rowCount(nameIndex); rowIndex++) + selectedModelIndexes << _exampleActionsFilterModel.index(rowIndex, static_cast(ExampleActionsTreeModel::Column::ClassName), nameIndex); + } + } + + for (const auto& selectedModelIndex : selectedModelIndexes) { + auto actionItem = _exampleActionsTreeModel.itemFromIndex(_exampleActionsFilterModel.mapToSource(selectedModelIndex)); + auto action = dynamic_cast(actionItem)->getAction(); + auto name = QString("%1/%2").arg(selectedModelIndex.parent().data().toString(), selectedModelIndex.data().toString()); + auto verticalGroupAction = new VerticalGroupAction(this, name, true); + + verticalGroupAction->setDefaultWidgetFlag(VerticalGroupAction::WidgetFlag::NoMargins); + verticalGroupAction->setShowLabels(false); + verticalGroupAction->addAction(new ExampleProxyAction(this, "", action)); + + _examplesGroupsAction.addGroupAction(verticalGroupAction); + } + }); + }); } void ExampleActionsPlugin::init() { - // Create layout auto layout = new QVBoxLayout(); layout->setContentsMargins(0, 0, 0, 0); - // Apply the layout + _examplesTreeAction.initialize(&_exampleActionsTreeModel, &_exampleActionsFilterModel, "Example action"); + + auto splitter = new QSplitter(); + + splitter->setOrientation(Qt::Orientation::Horizontal); + + splitter->addWidget(_examplesTreeAction.createWidget(&getWidget())); + splitter->addWidget(_examplesGroupsAction.createWidget(&getWidget())); + + layout->addWidget(splitter); + getWidget().setLayout(layout); } diff --git a/ExampleActions/src/ExampleActionsPlugin.h b/ExampleActions/src/ExampleActionsPlugin.h index 177ad13..7f826c1 100644 --- a/ExampleActions/src/ExampleActionsPlugin.h +++ b/ExampleActions/src/ExampleActionsPlugin.h @@ -2,7 +2,14 @@ #include +#include +#include + +#include "ExampleActionsTreeModel.h" +#include "ExampleActionsFilterModel.h" + #include +#include /** All plugin related classes are in the ManiVault plugin namespace */ using namespace mv::plugin; @@ -43,6 +50,14 @@ class ExampleActionsPlugin : public ViewPlugin /** This function is called by the core after the view plugin has been created */ void init() override; + +protected: + ExampleActionsTreeModel _exampleActionsTreeModel; /** Standard item model which stores example actions grouped by category */ + ExampleActionsFilterModel _exampleActionsFilterModel; /** Sort/filter proxy model for ExampleActionsPlugin#_exampleActionsTreeModel */ + mv::gui::TreeAction _examplesTreeAction; /** For displaying the ExampleActionsPlugin#_exampleActionsFilterModel */ + mv::gui::GroupsAction _examplesGroupsAction; /** For displaying the ExampleActionsPlugin#_exampleActionsListModel */ + + friend class ExampleActionStyledItemDelegate; }; /** diff --git a/ExampleActions/src/ExampleActionsTreeModel.cpp b/ExampleActions/src/ExampleActionsTreeModel.cpp new file mode 100644 index 0000000..2238e6b --- /dev/null +++ b/ExampleActions/src/ExampleActionsTreeModel.cpp @@ -0,0 +1,107 @@ +// 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 "ExampleActionsTreeModel.h" + +#include + +#include + +using namespace mv; +using namespace mv::util; +using namespace mv::gui; + +#ifdef _DEBUG + #define EXAMPLE_ACTIONS_TREE_MODEL_VERBOSE +#endif + +ExampleActionsTreeModel::ExampleActionsTreeModel(QObject* parent /*= nullptr*/) : + AbstractExampleActionsModel(parent) +{ + setColumnCount(static_cast(Column::Count)); + + appendRow(CategoryRow("Color", { + { "Color action", "For picking colors", "mv::gui::ColorAction" }, + { "1D Color map action", "For configuring one-dimensional color maps", "mv::gui::ColorMap1DAction" }, + { "2D Color map action", "For configuring two-dimensional color maps", "mv::gui::ColorMap2DAction" } + })); + + appendRow(CategoryRow("File", { + { "File picker", "For picking file location", "mv::gui::FilePickerAction" }, + { "Directory picker", "For picking directories", "mv::gui::DirectoryPickerAction" } + })); + + appendRow(CategoryRow("Grouping", { + { "Horizontal group", "For laying out actions horizontally", "mv::gui::HorizontalGroupAction" }, + { "Vertical group", "For laying out actions vertically", "mv::gui::VerticalGroupAction" }, + { "Groups", "Vertical groups accordion action", "mv::gui::GroupsAction" }, + { "Horizontal toolbar", "Horizontal group action that changes the state of action based on the available width ", "mv::gui::HorizontalToolbarAction" }, + { "Stretch", "Adds stretch to a group action", "mv::gui::StretchAction" } + })); + + appendRow(CategoryRow("Miscellaneous", { + { "Dataset picker", "For picking datasets", "mv::gui::DatasetPickerAction" } + })); +} + +QVariant ExampleActionsTreeModel::headerData(int section, Qt::Orientation orientation, int role /*= Qt::DisplayRole*/) const +{ + switch (static_cast(section)) + { + case Column::Name: + return "Name"; + + case Column::Description: + return "Description"; + + case Column::ClassName: + return "Class name"; + + default: + break; + } + + return {}; +} + +ExampleActionsTreeModel::CategoryRow::CategoryRow(const QString& category, const QList& actions) : QList() +{ + auto categoryItem = new QStandardItem(category); + + for (const auto& action : actions) { + QList items; + + try { + for (const auto& string : action) { + + const auto metaType = action[2]; + const auto metaTypeId = QMetaType::type(metaType.toLatin1()); + const auto metaObject = QMetaType::metaObjectForType(metaTypeId); + + if (!metaObject) + throw std::runtime_error(QString("Meta object type '%1' is not known. Did you forget to register the action correctly with Qt meta object system? See ToggleAction.h for an example.").arg(metaType).toLatin1()); + + auto metaObjectInstance = metaObject->newInstance(Q_ARG(QObject*, nullptr), Q_ARG(QString, QString("Example%1").arg(metaType))); + auto exampleAction = dynamic_cast(metaObjectInstance); + + if (!exampleAction) + throw std::runtime_error(QString("Unable to create a new instance of type '%1'").arg(metaType).toLatin1()); + + items << new ExampleActionItem(string, exampleAction); + } + } + catch (std::exception& e) + { + exceptionMessageBox("Unable to load example action:", e); + } + catch (...) + { + exceptionMessageBox("Unable to load example action:"); + } + + categoryItem->appendRow(items); + } + + append(categoryItem); +} diff --git a/ExampleActions/src/ExampleActionsTreeModel.h b/ExampleActions/src/ExampleActionsTreeModel.h new file mode 100644 index 0000000..caae898 --- /dev/null +++ b/ExampleActions/src/ExampleActionsTreeModel.h @@ -0,0 +1,65 @@ +// 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 "AbstractExampleActionsModel.h" + +#include +#include + +/** + * Example actions tree model class + * + * Example actions grouped by category + * + * @author Thomas Kroes + */ +class ExampleActionsTreeModel : public AbstractExampleActionsModel +{ +public: + + /** Model columns */ + enum class Column { + Name, /** Item name */ + Description, /** Action/category description */ + ClassName, /** Action class name */ + + Count + }; + +protected: + + /** Convenience class for category row and child action items */ + class CategoryRow final : public QList + { + public: + + /** + * Construct with \p name, \p description and \p className + * @param category Action category + * @param actions Child actions + */ + CategoryRow(const QString& category, const QList& actions); + }; + +public: + + /** + * Construct with pointer to \p parent object + * @param parent Pointer to parent object + */ + ExampleActionsTreeModel(QObject* parent = nullptr); + + /** + * Get header data for \p section, \p orientation and display \p role + * @param section Section + * @param orientation Orientation + * @param role Data role + * @return Header + */ + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + + friend class ExampleActionStyledItemDelegate; +}; diff --git a/ExampleActions/src/ExampleProxyAction.cpp b/ExampleActions/src/ExampleProxyAction.cpp new file mode 100644 index 0000000..1d4daac --- /dev/null +++ b/ExampleActions/src/ExampleProxyAction.cpp @@ -0,0 +1,30 @@ +// 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 "ExampleProxyAction.h" + +#include + +using namespace mv::gui; + +ExampleProxyAction::ExampleProxyAction(QObject* parent, const QString& title, WidgetAction* action /*= nullptr*/) : + WidgetAction(parent, title), + _action(action) +{ +} + +QWidget* ExampleProxyAction::getWidget(QWidget* parent, const std::int32_t& widgetFlags) +{ + auto widget = new WidgetActionWidget(parent, this); + auto layout = new QHBoxLayout(); + + layout->setContentsMargins(0, 0, 0, 0); + + if (_action) + layout->addWidget(_action->createExampleWidget(widget)); + + widget->setLayout(layout); + + return widget; +} diff --git a/ExampleActions/src/ExampleProxyAction.h b/ExampleActions/src/ExampleProxyAction.h new file mode 100644 index 0000000..51bd4d1 --- /dev/null +++ b/ExampleActions/src/ExampleProxyAction.h @@ -0,0 +1,47 @@ +// 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 + +/** + * Example proxy action class + * + * Uses target action createExampleWidget(...) in stead of createWidget() + * + * @author Thomas Kroes + */ +class ExampleProxyAction : public mv::gui::WidgetAction +{ + Q_OBJECT + +protected: + + /** + * Get widget representation of the proxy action + * @param parent Pointer to parent widget + * @param widgetFlags Widget flags for the configuration of the widget (type) + */ + QWidget* getWidget(QWidget* parent, const std::int32_t& widgetFlags) override; + +public: + + /** + * Constructor + * @param parent Pointer to parent object + * @param title Title of the action + * @param action Pointer to action + */ + Q_INVOKABLE ExampleProxyAction(QObject* parent, const QString& title, mv::gui::WidgetAction* action = nullptr); + +private: + mv::gui::WidgetAction* _action; /** Pointer to target action */ + + friend class AbstractActionsManager; +}; + +Q_DECLARE_METATYPE(ExampleProxyAction) + +inline const auto exampleProxyActionMetaTypeId = qRegisterMetaType("ExampleProxyAction"); From a15919107095579479a8c6e945f199ee5af22426 Mon Sep 17 00:00:00 2001 From: Thomas Kroes Date: Thu, 20 Jun 2024 17:49:48 +0200 Subject: [PATCH 4/5] Work on ExampleActionsPlugin and ExampleProxyAction --- ExampleActions/src/ExampleActionsPlugin.cpp | 5 ++++- ExampleActions/src/ExampleProxyAction.cpp | 13 ++++++++++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/ExampleActions/src/ExampleActionsPlugin.cpp b/ExampleActions/src/ExampleActionsPlugin.cpp index 00fa249..8961112 100644 --- a/ExampleActions/src/ExampleActionsPlugin.cpp +++ b/ExampleActions/src/ExampleActionsPlugin.cpp @@ -54,7 +54,7 @@ ExampleActionsPlugin::ExampleActionsPlugin(const PluginFactory* factory) : verticalGroupAction->setDefaultWidgetFlag(VerticalGroupAction::WidgetFlag::NoMargins); verticalGroupAction->setShowLabels(false); - verticalGroupAction->addAction(new ExampleProxyAction(this, "", action)); + verticalGroupAction->addAction(new ExampleProxyAction(verticalGroupAction, "", action)); _examplesGroupsAction.addGroupAction(verticalGroupAction); } @@ -77,6 +77,9 @@ void ExampleActionsPlugin::init() splitter->addWidget(_examplesTreeAction.createWidget(&getWidget())); splitter->addWidget(_examplesGroupsAction.createWidget(&getWidget())); + splitter->setStretchFactor(0, 1); + splitter->setStretchFactor(1, 3); + layout->addWidget(splitter); getWidget().setLayout(layout); diff --git a/ExampleActions/src/ExampleProxyAction.cpp b/ExampleActions/src/ExampleProxyAction.cpp index 1d4daac..93f07b7 100644 --- a/ExampleActions/src/ExampleProxyAction.cpp +++ b/ExampleActions/src/ExampleProxyAction.cpp @@ -5,6 +5,7 @@ #include "ExampleProxyAction.h" #include +#include using namespace mv::gui; @@ -19,10 +20,16 @@ QWidget* ExampleProxyAction::getWidget(QWidget* parent, const std::int32_t& widg auto widget = new WidgetActionWidget(parent, this); auto layout = new QHBoxLayout(); - layout->setContentsMargins(0, 0, 0, 0); + //layout->setContentsMargins(0, 0, 0, 0); - if (_action) - layout->addWidget(_action->createExampleWidget(widget)); + if (_action) { + auto exampleActionWidget = _action->createExampleWidget(widget); + + if (exampleActionWidget) + layout->addWidget(exampleActionWidget); + else + layout->addWidget(new QLabel("Coming soon...")); + } widget->setLayout(layout); From 413a81dd4594dad346f1773752588889214cb508 Mon Sep 17 00:00:00 2001 From: Thomas Kroes Date: Mon, 2 Dec 2024 12:29:55 +0100 Subject: [PATCH 5/5] Work on tree view --- ExampleActions/src/ExampleActionsPlugin.cpp | 15 ++++++++++++++- ExampleActions/src/ExampleActionsTreeModel.cpp | 7 +++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/ExampleActions/src/ExampleActionsPlugin.cpp b/ExampleActions/src/ExampleActionsPlugin.cpp index 8961112..3ee34e8 100644 --- a/ExampleActions/src/ExampleActionsPlugin.cpp +++ b/ExampleActions/src/ExampleActionsPlugin.cpp @@ -5,6 +5,7 @@ #include #include +#include Q_PLUGIN_METADATA(IID "studio.manivault.ExampleActionsPlugin") @@ -27,6 +28,18 @@ ExampleActionsPlugin::ExampleActionsPlugin(const PluginFactory* factory) : if (!hierarchyWidget) return; + auto& treeView = hierarchyWidget->getTreeView(); + + //treeView.setRootIsDecorated(false); + treeView.setTextElideMode(Qt::ElideMiddle); + + treeView.setColumnHidden(static_cast(ExampleActionsTreeModel::Column::ClassName), true); + + auto treeViewHeader = treeView.header(); + + treeViewHeader->setSectionResizeMode(static_cast(ExampleActionsTreeModel::Column::Name), QHeaderView::Stretch); + treeViewHeader->setSectionResizeMode(static_cast(ExampleActionsTreeModel::Column::Description), QHeaderView::Stretch); + auto selectionModel = hierarchyWidget->getTreeView().selectionModel(); connect(selectionModel, &QItemSelectionModel::selectionChanged, hierarchyWidget, [this, selectionModel](const QItemSelection& selected, const QItemSelection& deselected) -> void { @@ -54,7 +67,7 @@ ExampleActionsPlugin::ExampleActionsPlugin(const PluginFactory* factory) : verticalGroupAction->setDefaultWidgetFlag(VerticalGroupAction::WidgetFlag::NoMargins); verticalGroupAction->setShowLabels(false); - verticalGroupAction->addAction(new ExampleProxyAction(verticalGroupAction, "", action)); + verticalGroupAction->addAction(new ExampleProxyAction(verticalGroupAction, "ExampleProxy", action)); _examplesGroupsAction.addGroupAction(verticalGroupAction); } diff --git a/ExampleActions/src/ExampleActionsTreeModel.cpp b/ExampleActions/src/ExampleActionsTreeModel.cpp index 2238e6b..adcde3b 100644 --- a/ExampleActions/src/ExampleActionsTreeModel.cpp +++ b/ExampleActions/src/ExampleActionsTreeModel.cpp @@ -43,6 +43,13 @@ ExampleActionsTreeModel::ExampleActionsTreeModel(QObject* parent /*= nullptr*/) appendRow(CategoryRow("Miscellaneous", { { "Dataset picker", "For picking datasets", "mv::gui::DatasetPickerAction" } })); + + appendRow(CategoryRow("Numerical", { + { "Integral", "For configuring an integral value", "mv::gui::IntegralAction" }, + { "IntegralRange", "For configuring an integral range", "mv::gui::IntegralRangeAction" }, + { "Decimal", "For configuring a decimal value", "mv::gui::DecimalAction" }, + { "DecimalRange", "For configuring a decimal range", "mv::gui::DecimalRangeAction" }, + })); } QVariant ExampleActionsTreeModel::headerData(int section, Qt::Orientation orientation, int role /*= Qt::DisplayRole*/) const