From 73a16d244156ad39085c592969df359f1cae55ea Mon Sep 17 00:00:00 2001 From: Robert O'Bara Date: Mon, 30 Jan 2023 13:46:21 -0500 Subject: [PATCH 01/15] Doing Post SMTK 23.01.0 Release Step --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 95cba5cffe..911dbc7221 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -23.01.0 +23.01.100 -- GitLab From cded76f5e072b9f14784021856eef99e741dcd21 Mon Sep 17 00:00:00 2001 From: David Thompson Date: Mon, 30 Jan 2023 17:34:33 -0500 Subject: [PATCH 02/15] Add the markup resource to the proper release notes. --- doc/release/smtk-22.11.rst | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/doc/release/smtk-22.11.rst b/doc/release/smtk-22.11.rst index c5495d3deb..b0f7e61a34 100644 --- a/doc/release/smtk-22.11.rst +++ b/doc/release/smtk-22.11.rst @@ -263,6 +263,20 @@ Improving the support of Value Items whose Expression are in a different Resourc * Assigning a ValueItem to another now properly deals with this case * Copying a ValueItem Definition will also now properly support this use case. +SMTK Markup Resource +==================== + +SMTK now provides a resource based on the :smtk:`smtk::graph::Resource` +for performing annotation tasks. +It is not fully functional yet, but can import image and unstructured +data, create analytic shapes, group geometric objects together, mark +objects as instances in an ontology, and more. +The objective of this resource is to allow more flexible conceptual models +compared to the :smtk:`smtk::model::Resource`. +We are working to extend the ontology editing capability to allow users to +edit arbitrary relationship arcs between objects in the resource. +Please see :ref:`smtk-markup-sys` for more details. + SMTK Graph Resource Changes =========================== -- GitLab From 9e4eeaf0ff04d829aa949edd96c3a6e822e5bdc0 Mon Sep 17 00:00:00 2001 From: Aron Helser Date: Tue, 31 Jan 2023 10:37:26 -0500 Subject: [PATCH 03/15] Allow qtOperationDialog to be shown non-modal. This enables repeated running of an operation by clicking Apply multiple times. Cancel closes the dialog. --- .../notes/operation_dialog_allow_nonmodal.rst | 6 ++++++ smtk/extension/qt/qtOperationDialog.cxx | 13 +++++++++++-- smtk/extension/qt/qtOperationDialog.h | 4 ++-- 3 files changed, 19 insertions(+), 4 deletions(-) create mode 100644 doc/release/notes/operation_dialog_allow_nonmodal.rst diff --git a/doc/release/notes/operation_dialog_allow_nonmodal.rst b/doc/release/notes/operation_dialog_allow_nonmodal.rst new file mode 100644 index 0000000000..4e76c3e185 --- /dev/null +++ b/doc/release/notes/operation_dialog_allow_nonmodal.rst @@ -0,0 +1,6 @@ +Allow qtOperationDialog to be non-modal +--------------------------------------- + +If the qtOperationDialog is shown non-modal, using `show()` instead of `exec()`, +the Apply button doesn't close the dialog, but instead will remain active and +allow the operation to be run again. diff --git a/smtk/extension/qt/qtOperationDialog.cxx b/smtk/extension/qt/qtOperationDialog.cxx index c7b62b8fb4..63e53bc3ba 100644 --- a/smtk/extension/qt/qtOperationDialog.cxx +++ b/smtk/extension/qt/qtOperationDialog.cxx @@ -189,7 +189,8 @@ void qtOperationDialog::buildUI( m_internals->m_applyButton = buttonBox->button(QDialogButtonBox::Apply); m_internals->m_cancelButton = buttonBox->button(QDialogButtonBox::Cancel); // don't set a default button, so "Enter" won't dismiss the dialog. But - // make Apply come first it tab order, so tabbing to Apply then "Enter" works. + // make Apply come first it tab order, so tabbing to Apply then "Enter" works, + // if the dialog is modal. QWidget::setTabOrder(m_internals->m_applyButton, m_internals->m_cancelButton); QObject::connect( @@ -223,7 +224,15 @@ qtOperationDialog::~qtOperationDialog() void qtOperationDialog::onOperationExecuted(const smtk::operation::Operation::Result& result) { Q_EMIT this->operationExecuted(result); - this->accept(); // closes the dialog + if (this->isModal()) + { + this->accept(); // closes the dialog + } + else + { + // Let the user re-execute the operation + m_internals->m_smtkView->onModifiedParameters(); + } } void qtOperationDialog::showEvent(QShowEvent* event) diff --git a/smtk/extension/qt/qtOperationDialog.h b/smtk/extension/qt/qtOperationDialog.h index 44b734f88b..120ad1fb01 100644 --- a/smtk/extension/qt/qtOperationDialog.h +++ b/smtk/extension/qt/qtOperationDialog.h @@ -23,14 +23,14 @@ class QShowEvent; class QWidget; class qtOperationDialogInternals; -/**\brief Provides a model dialog for launching SMTK operations. +/**\brief Provides a dialog for launching SMTK operations. * * The intended use is for modelbuilder plugins that use menu or similar actions to invoke * SMTK operations. (For example, export operations are typically run from a modal dialog.) * The dialog is created as a QTabWidget with 2 tabs. The first tab embeds a qtOperationView * for the operation, and the second tab displays the operation's "info" content. The * dialog replaces and hides the "Apply", "Info" and "Cancel" buttons and consumes their Qt - * connections. + * connections. The dialog can be shown non-modal, to allow repeated running of an operation. */ namespace smtk -- GitLab From 56c4971f1130a27997f231cf44f7f4249146a3d5 Mon Sep 17 00:00:00 2001 From: Robert O'Bara Date: Tue, 31 Jan 2023 17:04:29 -0500 Subject: [PATCH 04/15] ENH: Improved Encode's CMakeLists File --- utilities/encode/CMakeLists.txt | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/utilities/encode/CMakeLists.txt b/utilities/encode/CMakeLists.txt index 6cf98a748c..3c2785e33f 100644 --- a/utilities/encode/CMakeLists.txt +++ b/utilities/encode/CMakeLists.txt @@ -17,6 +17,9 @@ smtk_install_target(smtk_encode_file) # so this program can be run by the build to generate header files. if (WIN32) get_target_property(_bfsLibLoc Boost::filesystem IMPORTED_IMPLIB_RELEASE) + if(NOT EXISTS ${_bfsLibLoc}) + get_target_property(_bfsLibLoc Boost::filesystem IMPORTED_IMPLIB_DEBUG) + endif() if ("${_bfsLibLoc}" STREQUAL "") # If we cannot copy the boost filesystem library into the directory where # smtk_encode_file will appear, then smtk_encode_file will fail to run. @@ -24,14 +27,36 @@ if (WIN32) # now to make debugging easier. message(FATAL_ERROR "Unable to locate Boost::filesystem; smtk_encode_file will not work.") endif() - string(REGEX REPLACE "lib/" "bin/" _bfsDllLoc1 "${_bfsLibLoc}") - string(REGEX REPLACE "lib" "dll" _bfsDllLoc "${_bfsDllLoc1}") + # We must make these directories or configure_file(…) below will create them # as regular files on otherwise-empty builds. + + # Find the corresponding dll + get_filename_component(_binName ${_bfsLibLoc} NAME_WLE) + get_filename_component(_libPath ${_bfsLibLoc} DIRECTORY) + + # NOTE: Depending on how Boost was configured dlls may appear in the lib folder or the bin folder. Search both. + find_file(_bfsDllLoc + NAMES + ${_binName}.dll + PATHS + ${_libPath}/.. + PATH_SUFFIXES + lib + bin + NO_DEFAULT_PATH + REQUIRED + ) file(MAKE_DIRECTORY "${LIBRARY_OUTPUT_PATH}") file(MAKE_DIRECTORY "${EXECUTABLE_OUTPUT_PATH}") - configure_file("${_bfsLibLoc}" "${LIBRARY_OUTPUT_PATH}" COPYONLY USE_SOURCE_PERMISSIONS) + + # If GENERATOR_IS_MULTI_CONFIG is true then the dll will need to be placed in the respective folder for the build type. + get_property(isMultiConfig GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) + if (isMultiConfig) + configure_file("${_bfsDllLoc}" "${EXECUTABLE_OUTPUT_PATH}/${CMAKE_BUILD_TYPE}" COPYONLY USE_SOURCE_PERMISSIONS) + else() configure_file("${_bfsDllLoc}" "${EXECUTABLE_OUTPUT_PATH}" COPYONLY USE_SOURCE_PERMISSIONS) + endif() endif() if (SMTK_ENABLE_TESTING) -- GitLab From 6a3117f7c62a7e97b41d7fa494b600dad3eb7695 Mon Sep 17 00:00:00 2001 From: David Thompson Date: Sun, 19 Feb 2023 00:16:08 -0500 Subject: [PATCH 05/15] Make each `smtk::project::Project` own a `task::Manager`. Before, the `task::Manager` was presumed to be application state. But because we only want to show tasks for a single active document (i.e., project) at a time, move it into project. This also helps decide where the task state should live. Note that this commit removes constructors from subclasses of `smtk::task::Task` since they need to call a deprecated parent constructor. This commit also removes signatures from `smtk::task::Instances` since that also causes deprecation warnings (and potentially further failures since the task-manager is required). This also provides `Instances::createFromName()` signatures that do not require a task manager (by recording the task manager which owns Instances at construction time). --- doc/release/notes/operation-hints.rst | 11 + doc/release/notes/string-token-api.rst | 12 + doc/release/notes/task-manager.rst | 26 ++ smtk/attribute/ValueItemTemplate.h | 2 +- smtk/common/Deprecation.h | 6 + .../pqSMTKOperationHintsBehavior.cxx | 25 ++ smtk/operation/Hints.h | 120 +++++++ smtk/operation/Hints.xml | 27 ++ smtk/project/Project.cxx | 10 + smtk/project/Project.h | 9 + smtk/project/Registrar.cxx | 7 + smtk/project/json/jsonProject.cxx | 32 +- smtk/project/operators/Create.sbt | 2 +- smtk/project/operators/Read.cxx | 32 +- smtk/project/operators/Read.sbt | 1 + smtk/project/operators/Write.cxx | 13 +- smtk/project/pybind11/PybindProject.h | 2 + smtk/string/Manager.cxx | 6 + smtk/string/Manager.h | 2 + smtk/string/Token.cxx | 10 + smtk/string/Token.h | 4 + smtk/string/json/jsonToken.cxx | 18 +- smtk/task/Active.cxx | 93 ++++-- smtk/task/Active.h | 18 +- smtk/task/ConfigureOperation.cxx | 296 ------------------ smtk/task/ConfigureOperation.h | 114 ------- smtk/task/FillOutAttributes.cxx | 6 +- smtk/task/FillOutAttributes.h | 2 + smtk/task/GatherResources.cxx | 6 +- smtk/task/GatherResources.h | 2 + smtk/task/Group.cxx | 46 +-- smtk/task/Group.h | 6 +- smtk/task/Instances.cxx | 45 ++- smtk/task/Instances.h | 56 +++- smtk/task/Manager.cxx | 3 +- smtk/task/Manager.h | 6 +- smtk/task/Registrar.cxx | 25 -- smtk/task/Registrar.h | 6 - smtk/task/Task.cxx | 80 ++++- smtk/task/Task.h | 32 +- smtk/task/json/Configurator.h | 10 +- smtk/task/json/Configurator.txx | 20 +- smtk/task/json/Helper.cxx | 64 +++- smtk/task/json/Helper.h | 39 ++- smtk/task/json/jsonAdaptor.cxx | 4 +- smtk/task/json/jsonFillOutAttributes.h | 5 + smtk/task/json/jsonGatherResources.h | 5 + smtk/task/json/jsonGroup.h | 4 + smtk/task/json/jsonManager.cxx | 150 ++++----- smtk/task/json/jsonManager.h | 43 +-- smtk/task/json/jsonResourceAndRole.h | 4 + smtk/task/json/jsonTask.cxx | 25 +- smtk/task/plugin/CMakeLists.txt | 3 +- smtk/task/pybind11/PybindInstances.h | 18 +- smtk/task/pybind11/PybindRegistrar.h | 4 - smtk/task/pybind11/PybindTask.h | 1 - smtk/task/testing/cxx/TestActiveTask.cxx | 9 +- smtk/task/testing/cxx/TestTaskBasics.cxx | 17 +- smtk/task/testing/cxx/TestTaskGroup.cxx | 83 +++-- smtk/task/testing/cxx/TestTaskJSON.cxx | 58 +++- smtk/task/testing/python/testTaskManager.py | 25 +- 61 files changed, 1028 insertions(+), 782 deletions(-) create mode 100644 doc/release/notes/operation-hints.rst create mode 100644 doc/release/notes/string-token-api.rst create mode 100644 doc/release/notes/task-manager.rst delete mode 100644 smtk/task/ConfigureOperation.cxx delete mode 100644 smtk/task/ConfigureOperation.h diff --git a/doc/release/notes/operation-hints.rst b/doc/release/notes/operation-hints.rst new file mode 100644 index 0000000000..aee9d49bbf --- /dev/null +++ b/doc/release/notes/operation-hints.rst @@ -0,0 +1,11 @@ +Operation hint for switching the active task +-------------------------------------------- + +Any operation can now request the active task be switched +by providing a hint on its result attribute. +Use the ``smtk::operation::addActivateTaskHint()`` function +to add the hint before your operation completes. +Then, the :smtk:`pqSMTKOperationHintsBehavior` object will +observe when the operation has completed and process the +hint and attempt to switch to the matching task. +See ``smtk::project::Read`` for an example. diff --git a/doc/release/notes/string-token-api.rst b/doc/release/notes/string-token-api.rst new file mode 100644 index 0000000000..7256bcbd09 --- /dev/null +++ b/doc/release/notes/string-token-api.rst @@ -0,0 +1,12 @@ +String token API +---------------- + +String tokens now have additional API: ++ :smtk:`Token::hasValue() ` + returns true the string manager contains a string for the token's integer hash. + Note that tokens constructed at compiled-time via the string-literal operator + will not insert the source string into the manager. ++ :smtk:`Token::valid() ` + returns true if the token has a valid value. + The string manager reserves a special value (``smtk::string::Manager::Invalid``) + to indicate an uninitialized or unavailable hash. diff --git a/doc/release/notes/task-manager.rst b/doc/release/notes/task-manager.rst new file mode 100644 index 0000000000..75bbdea7c6 --- /dev/null +++ b/doc/release/notes/task-manager.rst @@ -0,0 +1,26 @@ +Task subsystem changes +---------------------- + +The task manager is no longer considered part of an application's state. +Instead of expecting an application's :smtk:`Managers ` instance to +hold a single task manager, each :smtk:`Project ` owns its own +task manager. + +As part of this change, the project read and write operations now include a serialization of +the project's task manager. This means that the task system JSON state is now properly +serialized. + +Another part of this change removes the :smtk:`smtk::task::json::jsonManager` structure +with its ``serialize()`` and ``deserialize()`` methods. You should replace calls to +these methods with calls to ``to_json()`` and ``from_json()``, respectively. +Furthermore, you are responsible for pushing an instance of the +:smtk:`task helper ` before these calls and popping the instance +afterward. +See the task tests for examples of this. + +Finally, because :smtk:`smtk::common::Managers` no longer contains an application-wide +instance of a :smtk:`smtk::task::Manager`, the signature for :smtk:`Task ` +constructors is changed to additionally accept a parent task manager. +The old signatures will generate compile- and run-time warnings. +The constructors still accept a :smtk:`smtk::common::Managers` since tasks may wish +to monitor the application to determine their state. diff --git a/smtk/attribute/ValueItemTemplate.h b/smtk/attribute/ValueItemTemplate.h index 0643097a37..83b4e6f8ac 100644 --- a/smtk/attribute/ValueItemTemplate.h +++ b/smtk/attribute/ValueItemTemplate.h @@ -57,7 +57,7 @@ public: bool setValues(I vbegin, I vend) { bool ok = false; - std::size_t num = vend - vbegin; + std::size_t num = std::distance(vbegin, vend); if (this->setNumberOfValues(num)) { ok = true; diff --git a/smtk/common/Deprecation.h b/smtk/common/Deprecation.h index 96139579be..b93f606805 100644 --- a/smtk/common/Deprecation.h +++ b/smtk/common/Deprecation.h @@ -56,6 +56,12 @@ #define SMTK_DEPRECATION_REASON(version_major, version_minor, reason) \ "SMTK Deprecated in " #version_major "." #version_minor ": " reason +#if SMTK_DEPRECATION_LEVEL >= SMTK_VERSION_CHECK(23, 02) +#define SMTK_DEPRECATED_IN_23_02(reason) SMTK_DEPRECATION(SMTK_DEPRECATION_REASON(23, 02, reason)) +#else +#define SMTK_DEPRECATED_IN_23_02(reason) +#endif + #if SMTK_DEPRECATION_LEVEL >= SMTK_VERSION_CHECK(22, 11) #define SMTK_DEPRECATED_IN_22_11(reason) SMTK_DEPRECATION(SMTK_DEPRECATION_REASON(22, 11, reason)) #else diff --git a/smtk/extension/paraview/appcomponents/pqSMTKOperationHintsBehavior.cxx b/smtk/extension/paraview/appcomponents/pqSMTKOperationHintsBehavior.cxx index 446bd1b142..5bd3918216 100644 --- a/smtk/extension/paraview/appcomponents/pqSMTKOperationHintsBehavior.cxx +++ b/smtk/extension/paraview/appcomponents/pqSMTKOperationHintsBehavior.cxx @@ -234,6 +234,31 @@ int pqSMTKOperationHintsBehavior::processHints( } // III. Process render view hints. + // TODO. + + // IV. Process task hints. + smtk::operation::visitTaskHintsOfType( + result, + "activate task hint", + [&]( + const std::set& projects, + const std::set& taskIds) { + if (projects.empty() || !*projects.begin()) + { + return; + } + if (taskIds.empty()) + { + return; + } + auto project = *projects.begin(); + auto task = project->taskManager().taskInstances().findById(*taskIds.begin()); + if (task) + { + project->taskManager().active().switchTo(task.get()); + } + }); + return 0; } diff --git a/smtk/operation/Hints.h b/smtk/operation/Hints.h index daa848b69d..7857b7da3e 100644 --- a/smtk/operation/Hints.h +++ b/smtk/operation/Hints.h @@ -24,8 +24,14 @@ #include "smtk/view/SelectionAction.h" +#include "smtk/project/Project.h" + +#include "smtk/task/Task.h" + #include "smtk/common/Visit.h" +#include + namespace smtk { namespace operation @@ -186,6 +192,120 @@ SMTK_ALWAYS_EXPORT inline smtk::common::Visited visitFocusHintsOfType( return didVisit ? smtk::common::Visited::All : smtk::common::Visited::Empty; } +template +SMTK_ALWAYS_EXPORT inline smtk::attribute::Attribute::Ptr addHintWithTasks( + smtk::operation::Operation::Result result, + const smtk::project::Project::Ptr& project, + const Container& taskIds, + const std::string& hintType) +{ + if (!result) + { + return nullptr; + } + auto specification = result->attributeResource(); + auto hint = specification->createAttribute(hintType); + if (!hint) + { + smtkErrorMacro( + smtk::io::Logger::instance(), "Could not create hint of type \"" << hintType << "\"."); + return nullptr; + } + hint->associations()->setValue(project); + auto tasksItem = hint->findString("tasks"); + if (!tasksItem) + { + smtkErrorMacro( + smtk::io::Logger::instance(), + "Hint of type \"" << hintType << "\" does not allow associations."); + return nullptr; + } + bool ok = true; + ok &= tasksItem->setValues(taskIds.begin(), taskIds.end()); + ok &= result->findReference("hints")->appendValue(hint); + if (!ok) + { + specification->removeAttribute(hint); + smtkErrorMacro( + smtk::io::Logger::instance(), "Hint of type \"" << hintType << "\" does not allow objects."); + hint = nullptr; + } + return hint; +} + +/// Add a hint to the \a result indicating the given \a task should become active. +SMTK_ALWAYS_EXPORT inline smtk::attribute::Attribute::Ptr addActivateTaskHint( + smtk::operation::Operation::Result result, + const smtk::project::Project::Ptr& project, + smtk::task::Task* task) +{ + std::set taskIds; + // Serialize the string token by converting the integer hash into a string; + // do not assume the string manager can provide string content for the token + // as we do not want the string to be relied upon. + std::ostringstream taskId; + taskId << task->id().id(); + // Add the string to the set of task IDs. + taskIds.insert(taskId.str()); + auto hint = addHintWithTasks(result, project, taskIds, "activate task hint"); + return hint; +} + +template +SMTK_ALWAYS_EXPORT inline smtk::common::Visited visitTaskHintsOfType( + smtk::operation::Operation::Result result, + const std::string& hintType, + Functor functor) +{ + auto hintsItem = result->findReference("hints"); + if (!hintsItem) + { + return smtk::common::Visited::Empty; + } + std::size_t numberOfHints = hintsItem->numberOfValues(); + smtk::common::VisitorFunctor ff(functor); + bool didVisit = false; + for (std::size_t ii = 0; ii < numberOfHints; ++ii) + { + auto hint = hintsItem->valueAs(ii); + if (hint && hint->type() == hintType) + { + // Convert the associated objects into a set of project pointers. + std::set projects; + std::size_t na = hint->associations()->numberOfValues(); + for (std::size_t ii = 0; ii < na; ++ii) + { + if (auto project = hint->associations()->valueAs(ii)) + { + projects.insert(project); + } + } + // Convert the strings into string-tokens (task IDs) and then + // invoke the functor on the set. + auto tasksItem = hint->findString("tasks"); + if (!tasksItem) + { + continue; + } + + std::set taskIds; + for (const auto& value : *tasksItem) + { + smtk::string::Hash taskId; + std::istringstream valueStream(value); + valueStream >> taskId; + taskIds.insert(smtk::string::Token(taskId)); + } + if (ff(projects, taskIds) == smtk::common::Visit::Halt) + { + return smtk::common::Visited::Some; + } + didVisit = true; + } + } + return didVisit ? smtk::common::Visited::All : smtk::common::Visited::Empty; +} + } // namespace operation } // namespace smtk diff --git a/smtk/operation/Hints.xml b/smtk/operation/Hints.xml index 064160947f..b58115c86d 100644 --- a/smtk/operation/Hints.xml +++ b/smtk/operation/Hints.xml @@ -64,3 +64,30 @@ + + + + Hints that identify a change to the task system. + + + The project(s) whose tasks are involved. + + + + + + + + + + The task(s) that are the subject of the hint. + + + + + + + + Make the named task active (if possible). + + diff --git a/smtk/project/Project.cxx b/smtk/project/Project.cxx index 62b4a513af..5251a3de94 100644 --- a/smtk/project/Project.cxx +++ b/smtk/project/Project.cxx @@ -11,6 +11,10 @@ #include "smtk/resource/Manager.h" +#include "smtk/plugin/Manager.h" +#include "smtk/plugin/Registry.h" +#include "smtk/task/Registrar.h" + namespace smtk { namespace project @@ -19,7 +23,11 @@ Project::Project(const std::string& typeName) : m_resources(this, smtk::resource::Resource::m_manager) , m_operations(std::weak_ptr()) , m_typeName(typeName) + , m_taskManager(new smtk::task::Manager) { + // Ensure task types are registered to this instance of the task manager. + // s_registry = smtk::plugin::addToManagers(m_taskManager); + smtk::plugin::Manager::instance()->registerPluginsTo(m_taskManager); } bool Project::clean() const @@ -40,6 +48,8 @@ bool Project::clean() const } } + // TODO: Remove tasks? Reset their status? + return true; // everything in clean state } } // namespace project diff --git a/smtk/project/Project.h b/smtk/project/Project.h index dc9d2eccfb..7430c54239 100644 --- a/smtk/project/Project.h +++ b/smtk/project/Project.h @@ -16,11 +16,15 @@ #include "smtk/resource/DerivedFrom.h" +#include "smtk/task/Manager.h" + #include "smtk/project/OperationFactory.h" #include "smtk/project/ResourceContainer.h" #include +#include // for shared_ptr + namespace smtk { namespace project @@ -93,6 +97,10 @@ public: const smtk::project::Manager* manager() const { return m_manager; } + /// Return this project's task manager. + const smtk::task::Manager& taskManager() const { return *m_taskManager; } + smtk::task::Manager& taskManager() { return *m_taskManager; } + bool clean() const override; protected: @@ -104,6 +112,7 @@ private: std::string m_typeName; std::string m_version; smtk::project::Manager* m_manager{ nullptr }; + std::shared_ptr m_taskManager; }; } // namespace project } // namespace smtk diff --git a/smtk/project/Registrar.cxx b/smtk/project/Registrar.cxx index 761925db0e..fd42eb6dea 100644 --- a/smtk/project/Registrar.cxx +++ b/smtk/project/Registrar.cxx @@ -13,6 +13,7 @@ #include "smtk/attribute/Resource.h" +#include "smtk/operation/groups/CreatorGroup.h" #include "smtk/operation/groups/ReaderGroup.h" #include "smtk/operation/groups/WriterGroup.h" @@ -83,6 +84,9 @@ void Registrar::registerTo(const smtk::project::Manager::Ptr& projectManager) { projectManager->registerOperations(); + smtk::operation::CreatorGroup(projectManager->operationManager()) + .registerOperation(); + smtk::operation::ReaderGroup(projectManager->operationManager()) .registerOperation(); @@ -94,6 +98,9 @@ void Registrar::unregisterFrom(const smtk::project::Manager::Ptr& projectManager { projectManager->unregisterOperations(); + smtk::operation::CreatorGroup(projectManager->operationManager()) + .unregisterOperation(); + smtk::operation::ReaderGroup(projectManager->operationManager()) .unregisterOperation(); diff --git a/smtk/project/json/jsonProject.cxx b/smtk/project/json/jsonProject.cxx index f4f965e67d..dd0e8f69fa 100644 --- a/smtk/project/json/jsonProject.cxx +++ b/smtk/project/json/jsonProject.cxx @@ -9,27 +9,34 @@ //========================================================================= #include "smtk/project/json/jsonProject.h" +#include "smtk/resource/json/Helper.h" #include "smtk/resource/json/jsonResource.h" #include "smtk/project/json/jsonOperationFactory.h" #include "smtk/project/json/jsonResourceContainer.h" +#include "smtk/task/json/Helper.h" +#include "smtk/task/json/jsonFillOutAttributes.h" +#include "smtk/task/json/jsonGatherResources.h" +#include "smtk/task/json/jsonManager.h" +#include "smtk/task/json/jsonTask.h" // Define how projects are serialized. namespace smtk { namespace project { -void to_json(json& j, const ProjectPtr& project) +void to_json(json& jj, const ProjectPtr& project) { - smtk::resource::to_json(j, std::static_pointer_cast(project)); + smtk::resource::to_json(jj, std::static_pointer_cast(project)); - to_json(j["resources"], project->resources(), project); - to_json(j["operations"], project->operations()); + to_json(jj["resources"], project->resources(), project); + to_json(jj["operations"], project->operations()); - j["conceptual_version"] = project->version(); + jj["task_manager"] = project->taskManager(); + jj["conceptual_version"] = project->version(); } -void from_json(const json& j, ProjectPtr& project) +void from_json(const json& jj, ProjectPtr& project) { if (!project) { @@ -37,12 +44,17 @@ void from_json(const json& j, ProjectPtr& project) } smtk::resource::ResourcePtr tmp = std::static_pointer_cast(project); - smtk::resource::from_json(j, tmp); + smtk::resource::from_json(jj, tmp); - from_json(j["resources"], project->resources(), project); - from_json(j["operations"], project->operations()); + from_json(jj["resources"], project->resources(), project); + from_json(jj["operations"], project->operations()); + auto it = jj.find("task_manager"); + if (it != jj.end()) + { + from_json(*it, project->taskManager()); + } - project->setVersion(j["conceptual_version"]); + project->setVersion(jj["conceptual_version"]); } } // namespace project } // namespace smtk diff --git a/smtk/project/operators/Create.sbt b/smtk/project/operators/Create.sbt index 9f05d95552..f112a29710 100644 --- a/smtk/project/operators/Create.sbt +++ b/smtk/project/operators/Create.sbt @@ -3,7 +3,7 @@ - + Create a project instance for a specified type. diff --git a/smtk/project/operators/Read.cxx b/smtk/project/operators/Read.cxx index 42a6ecb2a7..5f64958ab4 100644 --- a/smtk/project/operators/Read.cxx +++ b/smtk/project/operators/Read.cxx @@ -23,12 +23,17 @@ #include "smtk/io/Logger.h" +#include "smtk/operation/Hints.h" #include "smtk/operation/operators/ReadResource.h" -#include "smtk/project/Manager.h" +#include "smtk/resource/json/Helper.h" +#include "smtk/project/Manager.h" #include "smtk/project/json/jsonProject.h" +#include "smtk/task/json/Helper.h" +#include "smtk/task/json/jsonManager.h" + #include "smtk/project/operators/Read_xml.h" SMTK_THIRDPARTY_PRE_INCLUDE @@ -105,13 +110,36 @@ Read::Result Read::operateInternal() // resource paths to being relative (rather than absolute) j["location"] = filename; - // Transcribe project data into the project + // Transcribe project data into the project. + // + // Note that `from_json()` and `to_json()` only accept 2 arguments (a JSON object and a reference + // to some object), but sometimes external data based on the context is required. Thus, when + // serializing/deserializing, we push and pop helpers that provide context. + auto& resourceHelper = smtk::resource::json::Helper::pushInstance(project); + resourceHelper.setManagers(this->managers()); + auto& taskHelper = + smtk::task::json::Helper::pushInstance(project->taskManager(), this->managers()); + // Do not invoke observers when reading individual tasks: + project->taskManager().taskInstances().pauseWorkflowNotifications(true); + + // Deserialize the project and see if it has an active task. smtk::project::from_json(j, project); + smtk::task::Task* taskToActivate = taskHelper.activeSerializedTask(); + + // Unpause task observers and pop helpers since we are done reading. + project->taskManager().taskInstances().pauseWorkflowNotifications(false); + smtk::task::json::Helper::popInstance(); + smtk::resource::json::Helper::popInstance(); Result result = this->createResult(smtk::operation::Operation::Outcome::SUCCEEDED); { smtk::attribute::ResourceItem::Ptr created = result->findResource("resource"); created->setValue(project); + // Hint to the application to make a deserialized task active upon completion: + if (taskToActivate) + { + smtk::operation::addActivateTaskHint(result, project, taskToActivate); + } } return result; diff --git a/smtk/project/operators/Read.sbt b/smtk/project/operators/Read.sbt index eb183e20b8..4872878f4b 100644 --- a/smtk/project/operators/Read.sbt +++ b/smtk/project/operators/Read.sbt @@ -21,6 +21,7 @@ + diff --git a/smtk/project/operators/Write.cxx b/smtk/project/operators/Write.cxx index aee88da21f..15110a6023 100644 --- a/smtk/project/operators/Write.cxx +++ b/smtk/project/operators/Write.cxx @@ -28,6 +28,8 @@ #include "smtk/project/Manager.h" #include "smtk/project/json/jsonProject.h" +#include "smtk/resource/json/Helper.h" +#include "smtk/task/json/Helper.h" #include "smtk/project/operators/Write_xml.h" @@ -46,7 +48,6 @@ namespace project Write::Result Write::operateInternal() { - std::cout << "smtk::project::Write::operateInternal()" << std::endl; // Access the project to write. smtk::attribute::ReferenceItem::Ptr projectItem = this->parameters()->associations(); smtk::project::ProjectPtr project = projectItem->valueAs(); @@ -118,6 +119,12 @@ Write::Result Write::operateInternal() } } + // Push helpers onto the stack so `to_json()` methods can reference + // state external to the objects they are serializing. + auto& resourceHelper = smtk::resource::json::Helper::pushInstance(project); + resourceHelper.setManagers(this->managers()); + smtk::task::json::Helper::pushInstance(project->taskManager(), this->managers()); + // We now write the project's smtk file. { nlohmann::json j = project; @@ -133,6 +140,10 @@ Write::Result Write::operateInternal() file.close(); } + // Pop helpers above from the stack. + smtk::resource::json::Helper::popInstance(); + smtk::task::json::Helper::popInstance(); + // Reset the project's clean flag project->setClean(true); diff --git a/smtk/project/pybind11/PybindProject.h b/smtk/project/pybind11/PybindProject.h index 54d4107683..a556531567 100644 --- a/smtk/project/pybind11/PybindProject.h +++ b/smtk/project/pybind11/PybindProject.h @@ -23,6 +23,7 @@ #include "smtk/project/OperationFactory.h" #include "smtk/project/ResourceContainer.h" #include "smtk/resource/Component.h" +#include "smtk/task/Manager.h" namespace py = pybind11; @@ -52,6 +53,7 @@ inline PySharedPtrClass pybind11_init_smtk_project_Proje .def("shared_from_this", (std::shared_ptr (smtk::project::Project::*)()) &smtk::project::Project::shared_from_this) .def("typeName", &smtk::project::Project::typeName) .def("version", &smtk::project::Project::version) + .def("taskManager", [](const smtk::project::Project::Ptr& project) { return &project->taskManager(); }) .def("visit", &smtk::project::Project::visit, py::arg("arg0")); return instance; } diff --git a/smtk/string/Manager.cxx b/smtk/string/Manager.cxx index e356de97aa..81b77ac7b5 100644 --- a/smtk/string/Manager.cxx +++ b/smtk/string/Manager.cxx @@ -69,6 +69,12 @@ std::size_t Manager::unmanage(Hash h) return num; } +bool Manager::hasValue(Hash h) const +{ + auto it = m_data.find(h); + return (it != m_data.end()); +} + const std::string& Manager::value(Hash h) const { static const std::string empty; diff --git a/smtk/string/Manager.h b/smtk/string/Manager.h index 5708a140c6..e1e9d8d935 100644 --- a/smtk/string/Manager.h +++ b/smtk/string/Manager.h @@ -70,6 +70,8 @@ public: /// Remove a hash from the manager. This also removes it from any string sets. std::size_t unmanage(Hash h); + /// Return true if a string exists for the Hash (false otherwise). + bool hasValue(Hash h) const; /// Look up a string from its hashed value. const std::string& value(Hash h) const; /// Look up a hash from a string value (without inserting it). diff --git a/smtk/string/Token.cxx b/smtk/string/Token.cxx index 472bf8b655..8cce203b5c 100644 --- a/smtk/string/Token.cxx +++ b/smtk/string/Token.cxx @@ -48,6 +48,16 @@ const std::string& Token::data() const return Token::manager().value(m_id); } +bool Token::hasData() const +{ + return Token::manager().hasValue(m_id); +} + +bool Token::valid() const +{ + return m_id == smtk::string::Manager::Invalid; +} + bool Token::operator==(const Token& other) const { return m_id == other.m_id; diff --git a/smtk/string/Token.h b/smtk/string/Token.h index 717b0dd61c..43ba0540da 100644 --- a/smtk/string/Token.h +++ b/smtk/string/Token.h @@ -45,6 +45,10 @@ public: Hash id() const { return m_id; } /// Return the string corresponding to the token. const std::string& data() const; + /// Return true if a string corresponding to the token exists. + bool hasData() const; + /// Return true if the token's ID has been set (false if not). + bool valid() const; /// Fast equality comparison (compares hashes, not strings). bool operator==(const Token& other) const; diff --git a/smtk/string/json/jsonToken.cxx b/smtk/string/json/jsonToken.cxx index ee9260ceab..7b807b15e5 100644 --- a/smtk/string/json/jsonToken.cxx +++ b/smtk/string/json/jsonToken.cxx @@ -16,12 +16,26 @@ namespace string void to_json(json& j, const Token& t) { - j = t.id(); + if (t.hasData()) + { + j = t.data(); + } + else + { + j = t.id(); + } } void from_json(const json& j, Token& t) { - t = Token::fromHash(j.get()); + if (j.is_string()) + { + t = Token(j.get()); + } + else + { + t = Token::fromHash(j.get()); + } } } // namespace string diff --git a/smtk/task/Active.cxx b/smtk/task/Active.cxx index 49aabfdc8d..659ae2eeed 100644 --- a/smtk/task/Active.cxx +++ b/smtk/task/Active.cxx @@ -8,6 +8,9 @@ // PURPOSE. See the above copyright notice for more information. //========================================================================= #include "smtk/task/Active.h" +#include "smtk/task/Instances.h" +#include "smtk/task/Manager.h" +#include "smtk/task/Task.h" namespace smtk { @@ -16,41 +19,61 @@ namespace task constexpr const char* const Active::type_name; -Active::Active(smtk::task::Instances* instances) - : m_instances(instances) - , m_observers(/* initializer */ - [this](Observer& observer) { - auto active = m_active.lock(); - // Ony initialization will ever call an observer with the same value for both parameters. - observer(active.get(), active.get()); - }) +struct Active::Internal { - if (m_instances) + Internal(Active* self, smtk::task::Instances* instances) + : m_self(self) + , m_instances(instances) + , m_observers(/* initializer */ + [this](Observer& observer) { + auto active = m_active.lock(); + // Ony initialization will ever call an observer with the same value for both parameters. + observer(active.get(), active.get()); + }) { - m_instancesObserver = m_instances->observers().insert( - [this](smtk::common::InstanceEvent event, const std::shared_ptr& task) { - if (event == smtk::common::InstanceEvent::Unmanaged && task && task == m_active.lock()) - { - m_observers(task.get(), nullptr); - m_active.reset(); - } - }, - /* priority */ 0, - /* initialize */ false, - "Observe task deletion for task::Active"); + if (m_instances) + { + m_instancesObserver = m_instances->observers().insert( + [this](smtk::common::InstanceEvent event, const std::shared_ptr& task) { + if (event == smtk::common::InstanceEvent::Unmanaged && task && task == m_active.lock()) + { + m_observers(task.get(), nullptr); + m_active.reset(); + } + }, + /* priority */ 0, + /* initialize */ false, + "Observe task deletion for task::Active"); + } } + + smtk::task::Active* m_self; + smtk::task::Instances* m_instances; + smtk::task::Instances::Observers::Key m_instancesObserver; + std::weak_ptr m_active; + smtk::task::Task::Observers::Key m_activeObserver; + Observers m_observers; +}; + +Active::Active(smtk::task::Instances* instances) + : m_p(new Internal(this, instances)) +{ } -Active::~Active() = default; +Active::~Active() +{ + delete m_p; + m_p = nullptr; +} smtk::task::Task* Active::task() const { - return m_active.lock().get(); + return m_p->m_active.lock().get(); } bool Active::switchTo(smtk::task::Task* task) { - auto current = m_active.lock(); + auto current = m_p->m_active.lock(); if (current.get() == task) { return false; @@ -59,9 +82,9 @@ bool Active::switchTo(smtk::task::Task* task) { if (current) { - m_observers(current.get(), nullptr); - m_active.reset(); - m_activeObserver.release(); + m_p->m_observers(current.get(), nullptr); + m_p->m_active.reset(); + m_p->m_activeObserver.release(); return true; } return false; @@ -71,14 +94,14 @@ bool Active::switchTo(smtk::task::Task* task) return false; } auto sharedTask = task->shared_from_this(); - if (m_instances && !m_instances->contains(sharedTask)) + if (m_p->m_instances && !m_p->m_instances->contains(sharedTask)) { // Only managed tasks can be active if we have instances tracked. return false; } - m_observers(current.get(), task); - m_active = sharedTask; - m_activeObserver = task->observers().insert([this](Task&, State, State next) { + m_p->m_observers(current.get(), task); + m_p->m_active = sharedTask; + m_p->m_activeObserver = task->observers().insert([this](Task&, State, State next) { if (next == State::Unavailable) { this->switchTo(nullptr); @@ -87,5 +110,15 @@ bool Active::switchTo(smtk::task::Task* task) return true; } +Active::Observers& Active::observers() +{ + return m_p->m_observers; +} + +const Active::Observers& Active::observers() const +{ + return m_p->m_observers; +} + } // namespace task } // namespace smtk diff --git a/smtk/task/Active.h b/smtk/task/Active.h index 7e2f5cdf1e..d962f6f395 100644 --- a/smtk/task/Active.h +++ b/smtk/task/Active.h @@ -11,7 +11,7 @@ #ifndef smtk_task_Active_h #define smtk_task_Active_h -#include "smtk/task/Instances.h" +#include "smtk/CoreExports.h" #include "smtk/task/Task.h" namespace smtk @@ -19,6 +19,9 @@ namespace smtk namespace task { +class Instances; +class Task; + /// This object provides applications a way to change and observe the active task. /// /// If passed an smtk::task::Manager::Instances object at construction, @@ -60,15 +63,14 @@ public: bool switchTo(smtk::task::Task*); /// Return the set of active-task observers (so you can insert yourself). - Observers& observers() { return m_observers; } - const Observers& observers() const { return m_observers; } + Observers& observers(); + const Observers& observers() const; private: - smtk::task::Instances* m_instances; - smtk::task::Instances::Observers::Key m_instancesObserver; - std::weak_ptr m_active; - smtk::task::Task::Observers::Key m_activeObserver; - Observers m_observers; + // We declare a subclass to hold some internal state to avoid a + // cyclic header dependency (Instances requires Managers requires Active). + struct Internal; + Internal* m_p; }; } // namespace task } // namespace smtk diff --git a/smtk/task/ConfigureOperation.cxx b/smtk/task/ConfigureOperation.cxx deleted file mode 100644 index b90c188dde..0000000000 --- a/smtk/task/ConfigureOperation.cxx +++ /dev/null @@ -1,296 +0,0 @@ -//========================================================================= -// Copyright (c) Kitware, Inc. -// All rights reserved. -// See LICENSE.txt for details. -// -// This software is distributed WITHOUT ANY WARRANTY; without even -// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR -// PURPOSE. See the above copyright notice for more information. -//========================================================================= -#include "smtk/task/FillOutAttributes.h" - -#include "smtk/project/ResourceContainer.h" - -#include "smtk/task/json/Helper.h" -#include "smtk/task/json/jsonFillOutAttributes.h" - -#include "smtk/operation/Manager.h" -#include "smtk/operation/SpecificationOps.h" - -#include "smtk/attribute/Attribute.h" -#include "smtk/attribute/Resource.h" - -#include "smtk/resource/Manager.h" - -#include - -namespace smtk -{ -namespace task -{ - -FillOutAttributes::FillOutAttributes() = default; - -FillOutAttributes::FillOutAttributes( - const Configuration& config, - const smtk::common::Managers::Ptr& managers) - : Task(config, managers) - , m_managers(managers) -{ - this->configure(config); -} - -FillOutAttributes::FillOutAttributes( - const Configuration& config, - const PassedDependencies& dependencies, - const smtk::common::Managers::Ptr& managers) - : Task(config, dependencies, managers) - , m_managers(managers) -{ - this->configure(config); -} - -void FillOutAttributes::configure(const Configuration& config) -{ - // The predicate from_json method needs the resource manager: - auto& helper = json::Helper::instance(); - helper.setManagers(m_managers); - - if (config.contains("attribute-sets")) - { - config.at("attribute-sets").get_to(m_attributeSets); - } - if (m_managers) - { - if (auto operationManager = m_managers->get()) - { - m_observer = operationManager->observers().insert( - [this]( - const smtk::operation::Operation& op, - smtk::operation::EventType event, - smtk::operation::Operation::Result result) { return this->update(op, event, result); }, - /* priority */ 0, - /* initialize */ true, - "FillOutAttributes monitors operations for updates."); - } - } - if (!m_attributeSets.empty()) - { - this->initializeResources(); - this->internalStateChanged(this->computeInternalState()); - } -} - -smtk::common::Visit FillOutAttributes::visitAttributeSets(AttributeSetVisitor visitor) -{ - if (!visitor) - { - return smtk::common::Visit::Halt; - } - for (auto& entry : m_attributeSets) - { - if (visitor(entry) == smtk::common::Visit::Halt) - { - return smtk::common::Visit::Halt; - } - } - return smtk::common::Visit::Continue; -} - -bool FillOutAttributes::initializeResources() -{ - bool foundResource = false; - if (m_attributeSets.empty()) - { - return foundResource; - } - if (auto resourceManager = m_managers->get()) - { - auto resources = resourceManager->find(); - for (const auto& resource : resources) - { - const std::string& role = smtk::project::detail::role(resource); - for (auto& attributeSet : m_attributeSets) - { - if ( - attributeSet.m_role.empty() || attributeSet.m_role == "*" || attributeSet.m_role == role) - { - if (attributeSet.m_autoconfigure) - { - foundResource = true; - auto it = attributeSet.m_resources.insert({ resource->id(), { {}, {} } }).first; - this->updateResourceEntry(*resource, attributeSet, it->second); - } - } - } - } - } - return foundResource; -} - -bool FillOutAttributes::updateResourceEntry( - smtk::attribute::Resource& resource, - const AttributeSet& predicate, - ResourceAttributes& entry) -{ - bool changesMade = false; - // I. Remove invalid entries for attributes that are valid or deleted. - std::set expunged; - std::set validated; - std::set invalidated; - for (const auto& invalidId : entry.m_invalid) - { - auto att = resource.findAttribute(invalidId); - if (att) - { - if (att->isValid()) // TODO: accept predicate override for categories? - { - validated.insert(invalidId); - } - } - else - { - expunged.insert(invalidId); - } - } - // II. Check valid attributes to see if they have been invalidated or expunged. - for (const auto& validId : entry.m_valid) - { - auto att = resource.findAttribute(validId); - if (att) - { - if (!att->isValid()) // TODO: accept predicate override for categories? - { - invalidated.insert(validId); - } - } - else - { - expunged.insert(validId); - } - } - // If the set of invalid attributes was changed, we need to re-run computeInternalState(). - changesMade |= !expunged.empty() || !validated.empty() || !invalidated.empty(); - for (const auto& id : validated) - { - entry.m_invalid.erase(id); - entry.m_valid.insert(id); - } - for (const auto& id : expunged) - { - entry.m_invalid.erase(id); - } - for (const auto& id : invalidated) - { - entry.m_invalid.insert(id); - entry.m_valid.erase(id); - } - // II. Check for newly-created attributes - std::vector attributes; - for (const auto& definition : predicate.m_definitions) - { - resource.findAttributes(definition, attributes); - for (const auto& attribute : attributes) - { - auto uid = attribute->id(); - if ( - (entry.m_invalid.find(uid) == entry.m_invalid.end()) && - (entry.m_valid.find(uid) == entry.m_valid.end())) - { - // We've found a new attribute. Classify it. - changesMade = true; - if (attribute->isValid()) // TODO: accept predicate override for categories? - { - entry.m_valid.insert(uid); - } - else - { - entry.m_invalid.insert(uid); - } - } - } - } - return changesMade; -} - -int FillOutAttributes::update( - const smtk::operation::Operation& op, - smtk::operation::EventType event, - smtk::operation::Operation::Result result) -{ - (void)op; - bool predicatesUpdated = false; - switch (event) - { - case smtk::operation::EventType::DID_OPERATE: - { - auto mentionedResources = smtk::operation::extractResources(result); - - for (const auto& weakResource : mentionedResources) - { - auto resource = std::dynamic_pointer_cast(weakResource.lock()); - if (resource) - { - const std::string& role = smtk::project::detail::role(resource); - // Do we care about this resource? - for (auto& predicate : m_attributeSets) - { - auto it = predicate.m_resources.find(resource->id()); - bool doUpdate = false; - if (it != predicate.m_resources.end()) - { - doUpdate = true; - } - else if ( - predicate.m_role == role || predicate.m_role == "*" || predicate.m_role.empty()) - { - if (predicate.m_autoconfigure) - { - it = predicate.m_resources.insert({ resource->id(), { {}, {} } }).first; - doUpdate = true; - } - } - if (doUpdate) - { - predicatesUpdated |= this->updateResourceEntry(*resource, predicate, it->second); - } - } - } - } - } - break; - case smtk::operation::EventType::WILL_OPERATE: - break; - } - if (predicatesUpdated) - { - this->internalStateChanged(this->computeInternalState()); - } - return 0; -} - -State FillOutAttributes::computeInternalState() const -{ - State s = State::Completable; - bool empty = true; - for (const auto& predicate : m_attributeSets) - { - for (const auto& resourceEntry : predicate.m_resources) - { - empty &= resourceEntry.second.m_valid.empty() && resourceEntry.second.m_invalid.empty(); - if (!resourceEntry.second.m_invalid.empty()) - { - s = State::Incomplete; - return s; - } - } - } - if (empty) - { - s = State::Irrelevant; - } - return s; -} - -} // namespace task -} // namespace smtk diff --git a/smtk/task/ConfigureOperation.h b/smtk/task/ConfigureOperation.h deleted file mode 100644 index 4363b9556f..0000000000 --- a/smtk/task/ConfigureOperation.h +++ /dev/null @@ -1,114 +0,0 @@ -//========================================================================= -// Copyright (c) Kitware, Inc. -// All rights reserved. -// See LICENSE.txt for details. -// -// This software is distributed WITHOUT ANY WARRANTY; without even -// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR -// PURPOSE. See the above copyright notice for more information. -//========================================================================= -#ifndef smtk_task_FillOutAttributes_h -#define smtk_task_FillOutAttributes_h - -#include "smtk/operation/Manager.h" -#include "smtk/operation/Observer.h" -#include "smtk/operation/Operation.h" -#include "smtk/resource/Resource.h" -#include "smtk/task/Task.h" - -#include "smtk/common/Visit.h" - -namespace smtk -{ -namespace task -{ -// Forward declaration -namespace adaptor -{ -class ResourceAndRole; -} - -/**\brief Creating a Task that requires an operation to be executed - * NOTE - this is just a place holder - as you can see this file and - * the corresponding Cxx file are copies of FillOutAttribute. - */ -class SMTKCORE_EXPORT FillOutAttributes : public Task -{ -public: - smtkTypeMacro(smtk::task::FillOutAttributes); - smtkSuperclassMacro(smtk::task::Task); - smtkCreateMacro(smtk::task::Task); - - /// Per-resource sets of validated attributes - /// - /// We need to track attributes so incremental updates - /// can decide whether to change state. - struct ResourceAttributes - { - /// Attributes matching a definition that are validated. - std::set m_valid; - /// Attributes matching a definition that need attention. - std::set m_invalid; - }; - /// A predicate used to collect resources that fit a given role. - struct AttributeSet - { - /// The required role. If empty, any role is allowed. - std::string m_role; - /// The definitions in matching resources whose attributes should be valid. - std::set m_definitions; - /// Should all resources with a matching role be added? - /// - /// If false (default), then resources must be explicitly configured by UUID - /// or configured by a task adaptor. - /// If true, then all resources with a matching role will have attributes - /// matching m_definitions checked. - bool m_autoconfigure = false; - /// The set of resources being managed that are selected by the validator. - std::map m_resources; - }; - /// Signatures of functors that visit resources-by-role predicates. - using AttributeSetVisitor = std::function; - - FillOutAttributes(); - FillOutAttributes( - const Configuration& config, - const smtk::common::Managers::Ptr& managers = nullptr); - FillOutAttributes( - const Configuration& config, - const PassedDependencies& dependencies, - const smtk::common::Managers::Ptr& managers = nullptr); - - ~FillOutAttributes() override = default; - - void configure(const Configuration& config); - - smtk::common::Visit visitAttributeSets(AttributeSetVisitor visitor); - -protected: - friend class adaptor::ResourceAndRole; - - /// Initialize with a list of resources from manager in m_managers. - bool initializeResources(); - /// Update a single resource in a predicate - bool updateResourceEntry( - smtk::attribute::Resource& resource, - const AttributeSet& predicate, - ResourceAttributes& entry); - /// Respond to operations that may change task state. - int update( - const smtk::operation::Operation& op, - smtk::operation::EventType event, - smtk::operation::Operation::Result result); - - /// Check m_resourcesByRole to see if all requirements are met. - State computeInternalState() const; - - smtk::common::Managers::Ptr m_managers; - smtk::operation::Observers::Key m_observer; - std::vector m_attributeSets; -}; -} // namespace task -} // namespace smtk - -#endif // smtk_task_FillOutAttributes_h diff --git a/smtk/task/FillOutAttributes.cxx b/smtk/task/FillOutAttributes.cxx index 12cb7311d0..65ad40f008 100644 --- a/smtk/task/FillOutAttributes.cxx +++ b/smtk/task/FillOutAttributes.cxx @@ -37,8 +37,9 @@ FillOutAttributes::FillOutAttributes() = default; FillOutAttributes::FillOutAttributes( const Configuration& config, + Manager& taskManager, const smtk::common::Managers::Ptr& managers) - : Task(config, managers) + : Task(config, taskManager, managers) , m_managers(managers) { this->configure(config); @@ -47,8 +48,9 @@ FillOutAttributes::FillOutAttributes( FillOutAttributes::FillOutAttributes( const Configuration& config, const PassedDependencies& dependencies, + Manager& taskManager, const smtk::common::Managers::Ptr& managers) - : Task(config, dependencies, managers) + : Task(config, dependencies, taskManager, managers) , m_managers(managers) { this->configure(config); diff --git a/smtk/task/FillOutAttributes.h b/smtk/task/FillOutAttributes.h index 7ba21d8bab..03a297ab29 100644 --- a/smtk/task/FillOutAttributes.h +++ b/smtk/task/FillOutAttributes.h @@ -81,10 +81,12 @@ public: FillOutAttributes(); FillOutAttributes( const Configuration& config, + Manager& taskManager, const smtk::common::Managers::Ptr& managers = nullptr); FillOutAttributes( const Configuration& config, const PassedDependencies& dependencies, + Manager& taskManager, const smtk::common::Managers::Ptr& managers = nullptr); ~FillOutAttributes() override = default; diff --git a/smtk/task/GatherResources.cxx b/smtk/task/GatherResources.cxx index 7c765547fd..5d04c64ea5 100644 --- a/smtk/task/GatherResources.cxx +++ b/smtk/task/GatherResources.cxx @@ -33,8 +33,9 @@ GatherResources::GatherResources() = default; GatherResources::GatherResources( const Configuration& config, + Manager& taskManager, const smtk::common::Managers::Ptr& managers) - : Task(config, managers) + : Task(config, taskManager, managers) , m_managers(managers) { this->configure(config); @@ -43,8 +44,9 @@ GatherResources::GatherResources( GatherResources::GatherResources( const Configuration& config, const PassedDependencies& dependencies, + Manager& taskManager, const smtk::common::Managers::Ptr& managers) - : Task(config, dependencies, managers) + : Task(config, dependencies, taskManager, managers) , m_managers(managers) { this->configure(config); diff --git a/smtk/task/GatherResources.h b/smtk/task/GatherResources.h index cd5a80d2be..28b7f428d1 100644 --- a/smtk/task/GatherResources.h +++ b/smtk/task/GatherResources.h @@ -64,10 +64,12 @@ public: GatherResources(); GatherResources( const Configuration& config, + Manager& taskManager, const smtk::common::Managers::Ptr& managers = nullptr); GatherResources( const Configuration& config, const PassedDependencies& dependencies, + Manager& taskManager, const smtk::common::Managers::Ptr& managers = nullptr); ~GatherResources() override = default; diff --git a/smtk/task/Group.cxx b/smtk/task/Group.cxx index d998926c26..c5d6f4085b 100644 --- a/smtk/task/Group.cxx +++ b/smtk/task/Group.cxx @@ -32,8 +32,11 @@ namespace task Group::Group() = default; -Group::Group(const Configuration& config, const smtk::common::Managers::Ptr& managers) - : Task(config, managers) +Group::Group( + const Configuration& config, + Manager& taskManager, + const smtk::common::Managers::Ptr& managers) + : Task(config, taskManager, managers) , m_managers(managers) { this->configure(config); @@ -42,8 +45,9 @@ Group::Group(const Configuration& config, const smtk::common::Managers::Ptr& man Group::Group( const Configuration& config, const PassedDependencies& dependencies, + Manager& taskManager, const smtk::common::Managers::Ptr& managers) - : Task(config, dependencies, managers) + : Task(config, dependencies, taskManager, managers) , m_managers(managers) { this->configure(config); @@ -57,30 +61,32 @@ void Group::configure(const Configuration& config) } // Push a new helper onto the stack (so task/adaptor numbers // refer to children, not exernal objects): + std::vector tasks; + smtk::task::json::Helper::instance().currentTasks(tasks); auto& helper = smtk::task::json::Helper::pushInstance(this); // Instantiate children and configure them if (config.contains("children")) { - std::vector> tasks; - if (!smtk::task::json::jsonManager::deserialize( - tasks, m_adaptors, helper.managers(), config.at("children"))) + smtk::task::from_json(config.at("children"), helper.taskManager()); + tasks.clear(); + helper.currentTasks(tasks); + std::vector adaptors; + helper.currentAdaptors(adaptors); + for (const auto& adaptor : adaptors) { - smtkErrorMacro(smtk::io::Logger::instance(), "Could not deserialize child tasks."); + m_adaptors.push_back(adaptor->shared_from_this()); } - for (const auto& weakChild : tasks) + for (auto* child : tasks) { - if (auto child = weakChild.lock()) - { - m_children.emplace(std::make_pair( - child, - child->observers().insert( - [this](Task& child, State prev, State next) { - this->childStateChanged(child, prev, next); - }, - /* priority */ 0, - /* initialize */ true, - "Group child observer."))); - } + m_children.emplace(std::make_pair( + child->shared_from_this(), + child->observers().insert( + [this](Task& child, State prev, State next) { + this->childStateChanged(child, prev, next); + }, + /* priority */ 0, + /* initialize */ true, + "Group child observer."))); } } else diff --git a/smtk/task/Group.h b/smtk/task/Group.h index 62849bb3cc..4cd7881c70 100644 --- a/smtk/task/Group.h +++ b/smtk/task/Group.h @@ -48,10 +48,14 @@ public: smtkCreateMacro(smtk::task::Task); Group(); - Group(const Configuration& config, const smtk::common::Managers::Ptr& managers = nullptr); + Group( + const Configuration& config, + Manager& taskManager, + const smtk::common::Managers::Ptr& managers = nullptr); Group( const Configuration& config, const PassedDependencies& dependencies, + Manager& taskManager, const smtk::common::Managers::Ptr& managers = nullptr); ~Group() override = default; diff --git a/smtk/task/Instances.cxx b/smtk/task/Instances.cxx index 1bb36033e4..95deb559cb 100644 --- a/smtk/task/Instances.cxx +++ b/smtk/task/Instances.cxx @@ -8,14 +8,16 @@ // PURPOSE. See the above copyright notice for more information. //========================================================================= #include "smtk/task/Instances.h" +#include "smtk/task/Manager.h" namespace smtk { namespace task { -Instances::Instances() - : m_workflowObservers([this](WorkflowObserver& observer) { +Instances::Instances(Manager& taskManager) + : m_taskManager(taskManager) + , m_workflowObservers([this](WorkflowObserver& observer) { // Initialize an observer with all the extant workflow head-tasks. // Basically, iterate over all tasks computing the set of all workflow heads // and signal them with "WorkflowEvent::Resume". @@ -70,9 +72,31 @@ bool Instances::workflowEvent(const std::set& workflows, WorkflowEvent ev return true; } -std::set Instances::findByTitle(const std::string& title) const +Task::Ptr Instances::createFromName(const std::string& taskType) { + return this->Superclass::createFromName(taskType); +} + +Task::Ptr Instances::createFromName( + const std::string& taskType, + Task::Configuration& configuration, + std::shared_ptr managers) +{ + return this->Superclass::createFromName(taskType, configuration, m_taskManager, managers); +} +Task::Ptr Instances::createFromName( + const std::string& taskType, + smtk::task::Task::Configuration& configuration, + smtk::task::Task::PassedDependencies& dependencies, + std::shared_ptr managers) +{ + return this->Superclass::createFromName( + taskType, configuration, dependencies, m_taskManager, managers); +} + +std::set Instances::findByTitle(const std::string& title) const +{ std::set foundTasks; this->visit([&foundTasks, title](const std::shared_ptr& task) { if (task->title() == title) @@ -83,5 +107,20 @@ std::set Instances::findByTitle(const std::string& title) }); return foundTasks; } + +smtk::task::Task::Ptr Instances::findById(smtk::string::Token taskId) const +{ + smtk::task::Task::Ptr foundTask; + this->visit([&foundTask, taskId](const std::shared_ptr& task) { + if (task->id() == taskId) + { + foundTask = task; + return smtk::common::Visit::Halt; + } + return smtk::common::Visit::Continue; + }); + return foundTask; +} + } // namespace task } // namespace smtk diff --git a/smtk/task/Instances.h b/smtk/task/Instances.h index da46340d0c..a0fefbee64 100644 --- a/smtk/task/Instances.h +++ b/smtk/task/Instances.h @@ -15,12 +15,15 @@ #include "smtk/common/Managers.h" #include "smtk/common/TypeName.h" +#include "smtk/string/Token.h" + #include "smtk/task/Task.h" namespace smtk { namespace task { +class Manager; /// An enum for events in the lifecycle of a workflow (tree of tasks). enum class WorkflowEvent @@ -34,22 +37,30 @@ enum class WorkflowEvent //!< This event is also used to initialize observers added after tasks have been created. }; +using TaskInstancesBase = smtk::common::Instances< + smtk::task::Task, + void, + std::tuple< + smtk::task::Task::Configuration&, + smtk::task::Manager&, + std::shared_ptr>, + std::tuple< + smtk::task::Task::Configuration&, + smtk::task::Task::PassedDependencies, + smtk::task::Manager&, + std::shared_ptr>>; + /// Track smtk::task::Task objects with smtk::common::Instances. /// /// This class adds an API for observing vectors of tasks that /// form workflows as instances are managed/unmanaged and /// dependencies are added/removed. -class SMTKCORE_EXPORT Instances - : public smtk::common::Instances< - smtk::task::Task, - void, - std::tuple>, - std::tuple< - smtk::task::Task::Configuration&, - smtk::task::Task::PassedDependencies, - std::shared_ptr>> +class SMTKCORE_EXPORT Instances : public TaskInstancesBase { public: + smtkTypeMacroBase(smtk::task::Instances); + smtkSuperclassMacro(smtk::task::TaskInstancesBase); + /// The signature for observing workflow construction/destruction/modification. /// /// The first parameter is the head task of the workflow (i.e., the one on which @@ -62,7 +73,10 @@ public: /// Observers that are invoked when the workflow structure changes. using WorkflowObservers = smtk::common::Observers; - Instances(); + Instances(Manager& taskManager); + Instances(const Instances&) = delete; + void operator=(const Instances&) = delete; + virtual ~Instances() = default; /// Return the set of workflow-event observers (so you can add yourself to it). const WorkflowObservers& workflowObservers() const { return m_workflowObservers; } @@ -81,10 +95,30 @@ public: /// is true). bool workflowEvent(const std::set& workflows, WorkflowEvent event, Task* subject); - // Returns the tasks with the given title + ///@{ + /// Create a task given its class name and optionally more configuration data. + /// + /// These override the base factory methods by supplying the task manager. + Task::Ptr createFromName(const std::string& taskType); + Task::Ptr createFromName( + const std::string& taskType, + Task::Configuration& configuration, + std::shared_ptr managers); + Task::Ptr createFromName( + const std::string& taskType, + smtk::task::Task::Configuration& configuration, + smtk::task::Task::PassedDependencies& dependencies, + std::shared_ptr managers); + ///@} + + /// Returns the tasks with the given title std::set findByTitle(const std::string& title) const; + /// Returns the task with the given (presumably unique) ID. + smtk::task::Task::Ptr findById(smtk::string::Token taskId) const; + protected: + Manager& m_taskManager; WorkflowObservers m_workflowObservers; bool m_workflowNotificationsPaused = false; bool m_needNotification = false; diff --git a/smtk/task/Manager.cxx b/smtk/task/Manager.cxx index 09203992e7..4161258b79 100644 --- a/smtk/task/Manager.cxx +++ b/smtk/task/Manager.cxx @@ -18,7 +18,8 @@ namespace task constexpr const char* const Manager::type_name; Manager::Manager() - : m_active(&m_taskInstances) + : m_taskInstances(*this) + , m_active(&m_taskInstances) { } diff --git a/smtk/task/Manager.h b/smtk/task/Manager.h index 153b87c205..2e1eef868d 100644 --- a/smtk/task/Manager.h +++ b/smtk/task/Manager.h @@ -45,7 +45,10 @@ public: smtkTypeMacroBase(smtk::task::Manager); smtkCreateMacro(smtk::task::Manager); + Manager(); virtual ~Manager(); + Manager(const Manager&) = delete; + void operator=(const Manager&) = delete; /// Managed instances of Task objects (and a registry of Task classes). using TaskInstances = smtk::task::Instances; @@ -78,9 +81,6 @@ private: AdaptorInstances m_adaptorInstances; Active m_active; std::weak_ptr m_managers; - -protected: - Manager(); }; } // namespace task } // namespace smtk diff --git a/smtk/task/Registrar.cxx b/smtk/task/Registrar.cxx index c5d35b34f2..e8e0f92934 100644 --- a/smtk/task/Registrar.cxx +++ b/smtk/task/Registrar.cxx @@ -41,31 +41,6 @@ using TaskJSON = std:: using AdaptorList = std::tuple; using AdaptorJSON = std::tuple; -void Registrar::registerTo(const smtk::common::Managers::Ptr& managers) -{ - if (managers->insert(smtk::task::Manager::create())) - { - managers->get()->setManagers(managers); - - smtk::plugin::Manager::instance()->registerPluginsTo(managers->get()); - } -} - -void Registrar::unregisterFrom(const smtk::common::Managers::Ptr& managers) -{ - managers->erase(); -} - -void Registrar::registerTo(const smtk::resource::Manager::Ptr& resourceManager) -{ - (void)resourceManager; -} - -void Registrar::unregisterFrom(const smtk::resource::Manager::Ptr& resourceManager) -{ - (void)resourceManager; -} - void Registrar::registerTo(const smtk::task::Manager::Ptr& taskManager) { auto& taskInstances = taskManager->taskInstances(); diff --git a/smtk/task/Registrar.h b/smtk/task/Registrar.h index 31b18dcffd..288ed23739 100644 --- a/smtk/task/Registrar.h +++ b/smtk/task/Registrar.h @@ -26,12 +26,6 @@ class SMTKCORE_EXPORT Registrar public: using Dependencies = std::tuple; - static void registerTo(const smtk::common::Managers::Ptr&); - static void unregisterFrom(const smtk::common::Managers::Ptr&); - - static void registerTo(const smtk::resource::Manager::Ptr&); - static void unregisterFrom(const smtk::resource::Manager::Ptr&); - static void registerTo(const smtk::task::Manager::Ptr&); static void unregisterFrom(const smtk::task::Manager::Ptr&); }; diff --git a/smtk/task/Task.cxx b/smtk/task/Task.cxx index b2add98365..61fdee76b8 100644 --- a/smtk/task/Task.cxx +++ b/smtk/task/Task.cxx @@ -10,8 +10,11 @@ #include "smtk/task/Task.h" #include "smtk/task/Manager.h" +#include "smtk/string/json/jsonToken.h" #include "smtk/task/json/Helper.h" +#include "smtk/io/Logger.h" + #include namespace smtk @@ -55,10 +58,22 @@ std::set workflowsOfTask(Task& task) constexpr const char* const Task::type_name; -Task::Task() = default; +Task::Task() +{ + this->setId(); +} Task::Task(const Configuration& config, const std::shared_ptr& managers) { + static bool once = false; + if (!once) + { + once = true; + smtkWarningMacro( + smtk::io::Logger::instance(), + "Note to developers; call a constructor that takes a task::Manager&."); + } + this->setId(); if (managers && managers->contains()) { m_manager = managers->get(); @@ -66,11 +81,31 @@ Task::Task(const Configuration& config, const std::shared_ptrconfigure(config); } +Task::Task( + const Configuration& config, + Manager& taskManager, + const std::shared_ptr& managers) +{ + this->setId(); + (void)managers; + m_manager = taskManager.shared_from_this(); + this->configure(config); +} + Task::Task( const Configuration& config, const PassedDependencies& dependencies, const std::shared_ptr& managers) { + static bool once = false; + if (!once) + { + once = true; + smtkWarningMacro( + smtk::io::Logger::instance(), + "Note to developers; call a constructor that takes a task::Manager&."); + } + this->setId(); if (managers->contains()) { m_manager = managers->get(); @@ -87,6 +122,27 @@ Task::Task( } } +Task::Task( + const Configuration& config, + const PassedDependencies& dependencies, + Manager& taskManager, + const std::shared_ptr& managers) +{ + this->setId(); + (void)managers; + m_manager = taskManager.shared_from_this(); + this->configure(config); + for (const auto& dependency : dependencies) + { + m_dependencies.insert(std::make_pair( + (const std::weak_ptr)(dependency), + dependency->observers().insert([this](Task& dependency, State prev, State next) { + bool didChange = this->updateState(dependency, prev, next); + (void)didChange; + }))); + } +} + void Task::configure(const Configuration& config) { if (!config.is_object()) @@ -99,7 +155,13 @@ void Task::configure(const Configuration& config) } if (config.contains("style")) { - m_style = config.at("style").get>(); + try + { + m_style = config.at("style").get>(); + } + catch (std::exception&) + { + } } if (config.contains("completed")) { @@ -117,16 +179,16 @@ void Task::setTitle(const std::string& title) m_title = title; } -bool Task::addStyle(const std::string& styleClass) +bool Task::addStyle(const smtk::string::Token& styleClass) { - if (styleClass.empty()) + if (styleClass.id() == smtk::string::Manager::Invalid) { return false; } return m_style.insert(styleClass).second; } -bool Task::removeStyle(const std::string& styleClass) +bool Task::removeStyle(const smtk::string::Token& styleClass) { return m_style.erase(styleClass) > 0; } @@ -395,5 +457,13 @@ bool Task::internalStateChanged(State next) return false; } +void Task::setId() +{ + // For now, create a string with the address of the task and hash it. + std::ostringstream uid; + uid << std::hex << this; + m_id = uid.str(); +} + } // namespace task } // namespace smtk diff --git a/smtk/task/Task.h b/smtk/task/Task.h index 65973a3071..e2e8f92685 100644 --- a/smtk/task/Task.h +++ b/smtk/task/Task.h @@ -13,6 +13,7 @@ #include "smtk/CoreExports.h" #include "smtk/SharedFromThis.h" #include "smtk/SystemConfig.h" +#include "smtk/common/Deprecation.h" #include "smtk/common/Managers.h" #include "smtk/common/Observers.h" #include "smtk/common/Visit.h" @@ -94,16 +95,34 @@ public: }; Task(); + SMTK_DEPRECATED_IN_23_02("Use variants that accept a task::Manager&.") Task( const Configuration& config, const std::shared_ptr& managers = nullptr); + Task( + const Configuration& config, + Manager& taskManager, + const std::shared_ptr& managers = nullptr); + SMTK_DEPRECATED_IN_23_02("Use variants that accept a task::Manager&.") + Task( + const Configuration& config, + const PassedDependencies& dependencies, + const std::shared_ptr& managers = nullptr); Task( const Configuration& config, const PassedDependencies& dependencies, + Manager& taskManager, const std::shared_ptr& managers = nullptr); virtual ~Task() = default; + /// Return a unique, ephemeral identifier for this task. + /// + /// The identifier is ephemeral in the sense that closing and reloading the + /// document that owns the task manager will not preserve the ID. + /// Thus, you must not serialize the ID. + smtk::string::Token id() const { return m_id; } + /// A method called by all constructors passed Configuration information. /// /// In general, this method should set member variables directly @@ -125,9 +144,9 @@ public: /// A style class specifies how applications should present the task /// (e.g., what type of view to provide the user, what rendering mode /// to use, what objects to list or exclude). - const std::set& style() const { return m_style; } - bool addStyle(const std::string& styleClass); - bool removeStyle(const std::string& styleClass); + const std::set& style() const { return m_style; } + bool addStyle(const smtk::string::Token& styleClass); + bool removeStyle(const smtk::string::Token& styleClass); bool clearStyle(); /// Populate a type-container with view-related data for configuration. @@ -275,7 +294,7 @@ protected: /// A task name to present to the user. std::string m_title; /// The set of style classes for this task. - std::set m_style; + std::set m_style; /// Whether the user has marked the task completed or not. bool m_completed = false; /// A set of dependent tasks and the keys used to observe their @@ -295,8 +314,13 @@ protected: Task* m_parent = nullptr; /// If this task is being managed, this will refer to its manager. std::weak_ptr m_manager; + /// The unique identifier for this task. + smtk::string::Token m_id; private: + /// Set the task's unique ID. + void setId(); + /// The internal state of the task as provided by subclasses. /// This is private so subclasses cannot alter it directly; /// instead they should invoke `internalStateChanged()` so that diff --git a/smtk/task/json/Configurator.h b/smtk/task/json/Configurator.h index bd90e735c3..d5e478f125 100644 --- a/smtk/task/json/Configurator.h +++ b/smtk/task/json/Configurator.h @@ -40,7 +40,7 @@ typedef std::mutex& (*TypeMutexFunction)(); /// This is needed in order to serialized dependencies among tasks which /// are stored as pointers that could, in theory, form a cycle. template -class SMTKCORE_EXPORT Configurator +class SMTK_ALWAYS_EXPORT Configurator { public: /// Methods that can produce a configuration for a task have this signature. @@ -133,12 +133,8 @@ public: /// Return the pointer to an object given its swizzled ID (or null). ObjectType* unswizzle(SwizzleId objectId) const; - /// Return a serialization of task-references that is consistent within - /// the scope of serializing a set of tasks. - // json swizzleDependencies(const ObjectType::PassedDependencies& deps); - - /// Return a deserialization of de-swizzled task-references. - // ObjectType::PassedDependencies unswizzleDependencies(const json& ids) const; + /// Populate the vector with tasks whose swizzle ID is \a start or above. + void currentObjects(std::vector& objects, SwizzleId start = 2); protected: Helper* m_helper; diff --git a/smtk/task/json/Configurator.txx b/smtk/task/json/Configurator.txx index 1d9ec1e671..69c0778a68 100644 --- a/smtk/task/json/Configurator.txx +++ b/smtk/task/json/Configurator.txx @@ -207,12 +207,20 @@ ObjectType* Configurator::unswizzle(SwizzleId objectId) const return it->second; } -/// Return a serialization of task-references that is consistent within -/// the scope of serializing a set of tasks. -// json swizzleDependencies(const ObjectType::PassedDependencies& deps); - -/// Return a deserialization of de-swizzled task-references. -// ObjectType::PassedDependencies unswizzleDependencies(const json& ids) const; +template +void Configurator::currentObjects( + std::vector& objects, + SwizzleId start) +{ + objects.clear(); + for (const auto& entry : m_swizzleBck) + { + if (entry.first >= start) + { + objects.push_back(entry.second); + } + } +} template typename Configurator::HelperTypeMap Configurator::s_types; diff --git a/smtk/task/json/Helper.cxx b/smtk/task/json/Helper.cxx index a2d9295471..37666fbd92 100644 --- a/smtk/task/json/Helper.cxx +++ b/smtk/task/json/Helper.cxx @@ -34,6 +34,13 @@ Helper::Helper() { } +Helper::Helper(Manager* taskManager) + : m_taskManager(taskManager) + , m_tasks(this) + , m_adaptors(this) +{ +} + Helper::~Helper() = default; Helper& Helper::instance() @@ -45,17 +52,35 @@ Helper& Helper::instance() return *(g_instanceStack.back()); } +Helper& Helper::pushInstance( + smtk::task::Manager& taskManager, + const smtk::common::Managers::Ptr& otherManagers) +{ + g_instanceStack.emplace_back(new Helper(&taskManager)); + g_instanceStack.back()->setManagers(otherManagers); + return *(g_instanceStack.back()); +} + Helper& Helper::pushInstance(smtk::task::Task* parent) { std::shared_ptr managers; + smtk::task::Manager* taskManager = nullptr; if (!g_instanceStack.empty()) { managers = g_instanceStack.back()->managers(); + taskManager = &g_instanceStack.back()->taskManager(); + g_instanceStack.emplace_back(new Helper(taskManager)); + } + else + { + g_instanceStack.emplace_back(new Helper()); } - g_instanceStack.emplace_back(new Helper); g_instanceStack.back()->setManagers(managers); - g_instanceStack.back()->tasks().swizzleId(parent); - g_instanceStack.back()->m_topLevel = false; + if (parent) + { + g_instanceStack.back()->tasks().swizzleId(parent); + g_instanceStack.back()->m_topLevel = false; + } return *(g_instanceStack.back()); } @@ -98,6 +123,16 @@ void Helper::clear() m_adaptors.clear(); } +void Helper::currentTasks(std::vector& tasks) +{ + m_tasks.currentObjects(tasks, this->topLevel() ? 1 : 2); +} + +void Helper::currentAdaptors(std::vector& adaptors) +{ + m_adaptors.currentObjects(adaptors, 1); +} + Helper::json Helper::swizzleDependencies(const Task::PassedDependencies& deps) { auto ids = Helper::json::array({}); @@ -152,6 +187,29 @@ std::pair Helper::getAdaptorTasks() return std::make_pair(from, to); } +void Helper::setActiveSerializedTask(Task* task) +{ + bool once = false; + for (auto& otherHelper : g_instanceStack) + { + if (&otherHelper->taskManager() == m_taskManager) + { + if (otherHelper->m_activeSerializedTask && !once) + { + once = true; + smtkWarningMacro( + smtk::io::Logger::instance(), "Multiple active tasks deserialized. Last one wins."); + } + otherHelper->m_activeSerializedTask = task; + } + } +} + +Task* Helper::activeSerializedTask() const +{ + return m_activeSerializedTask; +} + } // namespace json } // namespace task } // namespace smtk diff --git a/smtk/task/json/Helper.h b/smtk/task/json/Helper.h index dd8e45e7b8..019e65bd65 100644 --- a/smtk/task/json/Helper.h +++ b/smtk/task/json/Helper.h @@ -53,11 +53,21 @@ public: /// available. static Helper& instance(); - /// Push a new helper instance on the local thread's stack. + /// Push a new top-level helper instance on the local thread's stack. /// /// The returned \a Helper will have: /// + The same managers as the previous (if any) helper. /// + The \a parent task is assigned the ID 0. + static Helper& pushInstance( + smtk::task::Manager& taskManager, + const smtk::common::Managers::Ptr& otherManagers); + + /// Push a new (non-top-level) helper instance on the local thread's stack. + /// + /// The returned \a Helper will have: + /// + The same taskManager as the previous helper. + /// + The same managers as the previous (if any) helper. + /// + The \a parent task is assigned the ID 0. static Helper& pushInstance(smtk::task::Task* parent); /// Pop a helper instance off the local thread's stack. @@ -68,6 +78,8 @@ public: /// The outermost helper will return 1 (assuming you have called instance() first). static std::size_t nestingDepth(); + Manager& taskManager() { return *m_taskManager; } + /// Return an object for registering task classes and serialization helpers. Configurator& tasks(); @@ -82,13 +94,6 @@ public: void setManagers(const smtk::common::Managers::Ptr& managers); smtk::common::Managers::Ptr managers(); - /// Reset only negative task IDs. - /// - /// Negative task IDs are used to when deserializing a Group task's - /// children. Because only a single Group is deserialized at a time - /// (per thread), there are no namespace collisions. - void clearGroupTaskIds(); - /// Reset the helper's state. /// /// This should be called before beginning serialization or deserialization. @@ -97,7 +102,13 @@ public: void clear(); // --- Below here are methods specific to tasks and/or adaptors that - // --- don't fit in the Configurator class. + // --- don't fit in the Configurator class or use Configurator-specific API. + + /// Populate \a tasks with the set of current tasks. + void currentTasks(std::vector& tasks); + + /// Populate \a adaptors with the set of current adaptors. + void currentAdaptors(std::vector& adaptors); /// Return a serialization of task-references that is consistent within /// the scope of serializing a set of tasks. @@ -119,17 +130,25 @@ public: /// Get task-pointers based on the IDs set earlier. std::pair getAdaptorTasks(); + /// Set the current helper's active task and all parents which shared the + /// same task manager. You may pass (or expect) a null task. + void setActiveSerializedTask(Task* task); + Task* activeSerializedTask() const; + protected: Helper(); + Helper(Manager*); + Manager* m_taskManager{ nullptr }; Configurator m_tasks; Configurator m_adaptors; + Task* m_activeSerializedTask{ nullptr }; smtk::common::Managers::Ptr m_managers; SwizzleId m_adaptorFromId = ~static_cast(0); SwizzleId m_adaptorToId = ~static_cast(0); /// m_topLevel indicates whether pushInstance() (false) or instance() (true) /// was used to create this helper. If m_topLevel is false, the parent task /// is assigned swizzle ID 1. - bool m_topLevel = true; + bool m_topLevel{ true }; }; } // namespace json diff --git a/smtk/task/json/jsonAdaptor.cxx b/smtk/task/json/jsonAdaptor.cxx index 84fdc2a161..342e251a1a 100644 --- a/smtk/task/json/jsonAdaptor.cxx +++ b/smtk/task/json/jsonAdaptor.cxx @@ -52,10 +52,10 @@ void from_json(const nlohmann::json& j, smtk::task::Adaptor::Ptr& adaptor) { auto& helper = json::Helper::instance(); auto managers = helper.managers(); - auto taskManager = managers->get>(); + auto& taskManager = helper.taskManager(); auto adaptorType = j.at("type").get(); auto taskPair = helper.getAdaptorTasks(); - adaptor = taskManager->adaptorInstances().createFromName( + adaptor = taskManager.adaptorInstances().createFromName( adaptorType, const_cast(j), taskPair.first, taskPair.second); } catch (std::exception& e) diff --git a/smtk/task/json/jsonFillOutAttributes.h b/smtk/task/json/jsonFillOutAttributes.h index 5d40ad1125..e046f25e04 100644 --- a/smtk/task/json/jsonFillOutAttributes.h +++ b/smtk/task/json/jsonFillOutAttributes.h @@ -22,6 +22,11 @@ namespace smtk namespace task { +// NB: FillOutAttributes is serialized/deserialized by methods in smtk/task/json/jsonTask.h +// that use the Configurator to swizzle/unswizzle pointers held by tasks. +// This file just adds to_json/from_json methods for member variables of FillOutAttributes +// and declares a functor to fetch a FillOutAttributes from the Configurator. + void SMTKCORE_EXPORT from_json(const nlohmann::json& j, FillOutAttributes::AttributeSet& attributeSet); void SMTKCORE_EXPORT diff --git a/smtk/task/json/jsonGatherResources.h b/smtk/task/json/jsonGatherResources.h index d83cfd96be..11bfa69332 100644 --- a/smtk/task/json/jsonGatherResources.h +++ b/smtk/task/json/jsonGatherResources.h @@ -20,6 +20,11 @@ namespace smtk namespace task { +// NB: GatherResources is serialized/deserialized by methods in smtk/task/json/jsonTask.h +// that use the Configurator to swizzle/unswizzle pointers held by tasks. +// This file just adds to_json/from_json methods for member variables of GatherResources +// and declares a functor to fetch a GatherResources from the Configurator. + void from_json(const nlohmann::json& j, GatherResources::ResourceSet& resourceSet); void to_json(nlohmann::json& j, const GatherResources::ResourceSet& resourceSet); diff --git a/smtk/task/json/jsonGroup.h b/smtk/task/json/jsonGroup.h index 94a85a31d5..1d3c957c96 100644 --- a/smtk/task/json/jsonGroup.h +++ b/smtk/task/json/jsonGroup.h @@ -24,6 +24,10 @@ namespace json class Helper; +// NB: Group is serialized/deserialized by methods in smtk/task/json/jsonTask.h +// that use the Configurator to swizzle/unswizzle pointers held by tasks. +// This file just declares a functor to fetch a Group from the Configurator. + struct SMTKCORE_EXPORT jsonGroup { Task::Configuration operator()(const Task* task, Helper& helper) const; diff --git a/smtk/task/json/jsonManager.cxx b/smtk/task/json/jsonManager.cxx index be84312c72..7e0c8a976c 100644 --- a/smtk/task/json/jsonManager.cxx +++ b/smtk/task/json/jsonManager.cxx @@ -26,109 +26,36 @@ namespace smtk { namespace task { -namespace json -{ -bool jsonManager::serialize( - const std::shared_ptr& managers, - nlohmann::json& json) +void from_json(const nlohmann::json& jj, Manager& taskManager) { - auto taskManager = managers->get(); - if (!taskManager) + try { - // Should we succeed silently instead of failing verbosely? - smtkErrorMacro( - smtk::io::Logger::instance(), - "Could not find a task manager to serialize. " - "Add a task manager to the managers instance."); - return false; - } - - // Serialize tasks - nlohmann::json::array_t taskList; - taskManager->taskInstances().visit([&taskList](const smtk::task::Task::Ptr& task) { - auto& helper = Helper::instance(); - if ( - !task->parent() || - (smtk::task::json::Helper::nestingDepth() > 1 && - helper.tasks().unswizzle(1) == task->parent())) - { - // Only serialize top-level tasks. (Tasks with children are responsible - // for serializing their children). - nlohmann::json jsonTask = task; - taskList.push_back(jsonTask); - } - return smtk::common::Visit::Continue; - }); - json["tasks"] = taskList; - - // Serialize adaptors - nlohmann::json::array_t adaptorList; - taskManager->adaptorInstances().visit([&adaptorList](const smtk::task::Adaptor::Ptr& adaptor) { - if ( - adaptor && adaptor->from() && !adaptor->from()->parent() && adaptor->to() && - !adaptor->to()->parent()) + auto& helper = json::Helper::instance(); + if (&taskManager != &helper.taskManager()) { - // Only serialize top-level adaptors. (Tasks with child adaptors are - // responsible for serializing them.) - nlohmann::json jsonAdaptor = adaptor; - adaptorList.push_back(jsonAdaptor); + throw std::logic_error("Helper must be configured to deserialize this task manager."); } - return smtk::common::Visit::Continue; - }); - json["adaptors"] = adaptorList; - return true; -} - -bool jsonManager::deserialize( - const std::shared_ptr& managers, - const nlohmann::json& json) -{ - std::vector> tasks; - std::vector> adaptors; - return jsonManager::deserialize(tasks, adaptors, managers, json); -} - -bool jsonManager::deserialize( - std::vector>& tasks, - std::vector>& adaptors, - const std::shared_ptr& managers, - const nlohmann::json& json) -{ - auto taskManager = managers->get(); - if (!taskManager) - { - // Should we succeed silently instead of failing verbosely? - smtkErrorMacro(smtk::io::Logger::instance(), "Could not find a destination task manager."); - return false; - } - - tasks.clear(); - adaptors.clear(); - try - { - auto& helper = Helper::instance(); if (smtk::task::json::Helper::nestingDepth() == 1) { // Do not clear the parent task when deserializing nested tasks. helper.clear(); } - helper.setManagers(managers); - std::map taskMap; - std::map adaptorMap; - for (const auto& jsonTask : json.at("tasks")) + // helper.setManagers(managers); + std::map taskMap; + std::map adaptorMap; + for (const auto& jsonTask : jj.at("tasks")) { - auto taskId = jsonTask.at("id").get(); + auto taskId = jsonTask.at("id").get(); Task::Ptr task = jsonTask; taskMap[taskId] = task; helper.tasks().swizzleId(task.get()); - tasks.push_back(task); } // Do a second pass to deserialize dependencies. - for (const auto& jsonTask : json.at("tasks")) + for (const auto& jsonTask : jj.at("tasks")) { if (jsonTask.contains("dependencies")) { - auto taskId = jsonTask.at("id").get(); + auto taskId = jsonTask.at("id").get(); auto task = taskMap[taskId]; auto taskDeps = helper.unswizzleDependencies(jsonTask.at("dependencies")); task->addDependencies(taskDeps); @@ -137,23 +64,22 @@ bool jsonManager::deserialize( // Now configure dependent tasks with adaptors if specified. // Note that tasks have already been deserialized, so the // helper's map from task-id to task-pointer is complete. - if (json.contains("adaptors")) + if (jj.contains("adaptors")) { - for (const auto& jsonAdaptor : json.at("adaptors")) + for (const auto& jsonAdaptor : jj.at("adaptors")) { // Skip things that are not adaptors. if (jsonAdaptor.is_object() && jsonAdaptor.contains("id")) { try { - auto adaptorId = jsonAdaptor.at("id").get(); - auto taskFromId = jsonAdaptor.at("from").get(); - auto taskToId = jsonAdaptor.at("to").get(); + auto adaptorId = jsonAdaptor.at("id").get(); + auto taskFromId = jsonAdaptor.at("from").get(); + auto taskToId = jsonAdaptor.at("to").get(); helper.setAdaptorTaskIds(taskFromId, taskToId); Adaptor::Ptr adaptor = jsonAdaptor; adaptorMap[adaptorId] = adaptor; helper.adaptors().swizzleId(adaptor.get()); - adaptors.push_back(adaptor); } catch (std::exception&) { @@ -165,16 +91,50 @@ bool jsonManager::deserialize( } } - helper.clear(); + // helper.clear(); } catch (std::exception& e) { smtkErrorMacro(smtk::io::Logger::instance(), "Could not deserialize: " << e.what() << "."); - return false; } - return true; } -} // namespace json +void to_json(nlohmann::json& jj, const Manager& manager) +{ + // Serialize tasks + nlohmann::json::array_t taskList; + manager.taskInstances().visit([&taskList](const smtk::task::Task::Ptr& task) { + auto& helper = json::Helper::instance(); + if ( + !task->parent() || + (smtk::task::json::Helper::nestingDepth() > 1 && + helper.tasks().unswizzle(1) == task->parent())) + { + // Only serialize top-level tasks. (Tasks with children are responsible + // for serializing their children). + nlohmann::json jsonTask = task; + taskList.push_back(jsonTask); + } + return smtk::common::Visit::Continue; + }); + jj["tasks"] = taskList; + + // Serialize adaptors + nlohmann::json::array_t adaptorList; + manager.adaptorInstances().visit([&adaptorList](const smtk::task::Adaptor::Ptr& adaptor) { + if ( + adaptor && adaptor->from() && !adaptor->from()->parent() && adaptor->to() && + !adaptor->to()->parent()) + { + // Only serialize top-level adaptors. (Tasks with child adaptors are + // responsible for serializing them.) + nlohmann::json jsonAdaptor = adaptor; + adaptorList.push_back(jsonAdaptor); + } + return smtk::common::Visit::Continue; + }); + jj["adaptors"] = adaptorList; +} + } // namespace task } // namespace smtk diff --git a/smtk/task/json/jsonManager.h b/smtk/task/json/jsonManager.h index 59c0b7b214..28898acd4a 100644 --- a/smtk/task/json/jsonManager.h +++ b/smtk/task/json/jsonManager.h @@ -20,42 +20,17 @@ namespace smtk { namespace task { -namespace json -{ -/// Tools for saving and restoring the state of a task manager. -class SMTKCORE_EXPORT jsonManager -{ -public: - /// Serialize the task manager. - /// - /// Obviously, \a managers must hold a task manager before you - /// call this method. Depending on the task instances it holds, - /// other managers may be required. - static bool serialize( - const std::shared_ptr& managers, - nlohmann::json& json); - /// Deserialize the task manager. - /// - /// Obviously, \a managers must hold or be able to create a - /// task manager. Depending on the task instances being deserialized, - /// this method may access and modify other managers held by the - /// \a managers instance. - /// - /// The second variant accepts a container holding weak pointers - /// to each top-level task deserialized (i.e., child tasks are - /// not included). - static bool deserialize( - const std::shared_ptr& managers, - const nlohmann::json& json); - static bool deserialize( - std::vector>& tasks, - std::vector>& adaptors, - const std::shared_ptr& managers, - const nlohmann::json& json); -}; +///@{ +/// Serialize/deserialize a task manager. +/// +/// Note that the caller **must** push a smtk::task::json::Helper onto +/// the stack before calling these methods and the helper must have its +/// smtk::common::Managers set in order for these methods to work. +void SMTKCORE_EXPORT from_json(const nlohmann::json& j, Manager& taskManager); +void SMTKCORE_EXPORT to_json(nlohmann::json& j, const Manager& taskManager); +///@} -} // namespace json } // namespace task } // namespace smtk diff --git a/smtk/task/json/jsonResourceAndRole.h b/smtk/task/json/jsonResourceAndRole.h index ac309595f4..edeb9e1eb9 100644 --- a/smtk/task/json/jsonResourceAndRole.h +++ b/smtk/task/json/jsonResourceAndRole.h @@ -26,6 +26,10 @@ namespace json class Helper; +// NB: ResourceAndRole is serialized/deserialized by methods in smtk/task/json/jsonAdaptor.h +// that use the Configurator to swizzle/unswizzle pointers held by tasks. +// This file just declares a functor to fetch a ResourceAndRole from the Configurator. + struct SMTKCORE_EXPORT jsonResourceAndRole { Adaptor::Configuration operator()(const Adaptor* task, Helper& helper) const; diff --git a/smtk/task/json/jsonTask.cxx b/smtk/task/json/jsonTask.cxx index 5117f1174a..e29b9c708b 100644 --- a/smtk/task/json/jsonTask.cxx +++ b/smtk/task/json/jsonTask.cxx @@ -12,6 +12,9 @@ #include "smtk/task/Manager.h" +#include "smtk/string/json/jsonManager.h" +#include "smtk/string/json/jsonToken.h" + #include "smtk/io/Logger.h" namespace smtk @@ -45,26 +48,34 @@ Task::Configuration jsonTask::operator()(const Task* task, Helper& helper) const } // namespace json -void to_json(nlohmann::json& j, const smtk::task::Task::Ptr& task) +void to_json(nlohmann::json& jj, const smtk::task::Task::Ptr& task) { if (!task) { return; } auto& helper = json::Helper::instance(); - j = helper.tasks().configuration(task.get()); + jj = helper.tasks().configuration(task.get()); + if (helper.taskManager().active().task() == task.get()) + { + jj["active"] = true; + } } -void from_json(const nlohmann::json& j, smtk::task::Task::Ptr& task) +void from_json(const nlohmann::json& jj, smtk::task::Task::Ptr& task) { try { auto& helper = json::Helper::instance(); auto managers = helper.managers(); - auto taskManager = managers->get>(); - auto taskType = j.at("type").get(); - task = taskManager->taskInstances().createFromName( - taskType, const_cast(j), managers); + auto& taskManager = helper.taskManager(); + auto taskType = jj.at("type").get(); + task = taskManager.taskInstances().createFromName( + taskType, const_cast(jj), managers); + if (jj.contains("active") && jj.at("active").get()) + { + helper.setActiveSerializedTask(task.get()); + } } catch (std::exception& e) { diff --git a/smtk/task/plugin/CMakeLists.txt b/smtk/task/plugin/CMakeLists.txt index 2579d1a698..94c82e789e 100644 --- a/smtk/task/plugin/CMakeLists.txt +++ b/smtk/task/plugin/CMakeLists.txt @@ -1,7 +1,6 @@ smtk_add_plugin(smtkTaskPlugin REGISTRAR smtk::task::Registrar - MANAGERS smtk::common::Managers - smtk::task::Manager + MANAGERS smtk::task::Manager PARAVIEW_PLUGIN_ARGS VERSION 1.0 ) diff --git a/smtk/task/pybind11/PybindInstances.h b/smtk/task/pybind11/PybindInstances.h index f62c52d424..fb18ab50c8 100644 --- a/smtk/task/pybind11/PybindInstances.h +++ b/smtk/task/pybind11/PybindInstances.h @@ -26,21 +26,29 @@ inline PySharedPtrClass< smtk::task::Instances > pybind11_init_smtk_task_Instanc PySharedPtrClass< smtk::task::Instances > instance(m, "Instances"); instance .def("createFromName", - [](smtk::task::Instances& tasks, const std::string& name, const std::string& config) + [](smtk::task::Instances& taskInstances, const std::string& name, const std::string& config) { std::shared_ptr managers; smtk::task::Task::Configuration jConfig = nlohmann::json::parse(config); - std::shared_ptr task = tasks.createFromName(name, jConfig, managers); + std::shared_ptr task = taskInstances.createFromName(name, jConfig, managers); + return task; + } + ) + .def("createFromName", + [](smtk::task::Instances& taskInstances, const std::string& name, const std::string& config, std::shared_ptr managers) + { + smtk::task::Task::Configuration jConfig = nlohmann::json::parse(config); + std::shared_ptr task = taskInstances.createFromName(name, jConfig, managers); return task; } ) .def("clear", &smtk::task::Instances::clear) .def("instances", - [](smtk::task::Instances& tasks) + [](smtk::task::Instances& taskInstances) { std::vector> taskList; - taskList.reserve(tasks.size()); - tasks.visit( + taskList.reserve(taskInstances.size()); + taskInstances.visit( [&taskList](const std::shared_ptr& task) { if (task) diff --git a/smtk/task/pybind11/PybindRegistrar.h b/smtk/task/pybind11/PybindRegistrar.h index 8dd47f188d..12c7cbcd68 100644 --- a/smtk/task/pybind11/PybindRegistrar.h +++ b/smtk/task/pybind11/PybindRegistrar.h @@ -24,11 +24,7 @@ inline py::class_< smtk::task::Registrar > pybind11_init_smtk_task_Registrar(py: .def(py::init<>()) .def(py::init<::smtk::task::Registrar const &>()) .def("deepcopy", (smtk::task::Registrar & (smtk::task::Registrar::*)(::smtk::task::Registrar const &)) &smtk::task::Registrar::operator=) - .def_static("registerTo", (void (*)(::smtk::common::Managers::Ptr const &)) &smtk::task::Registrar::registerTo, py::arg("manager")) - .def_static("registerTo", (void (*)(::smtk::resource::Manager::Ptr const &)) &smtk::task::Registrar::registerTo, py::arg("manager")) .def_static("registerTo", (void (*)(::smtk::task::Manager::Ptr const &)) &smtk::task::Registrar::registerTo, py::arg("manager")) - .def_static("unregisterFrom", (void (*)(::smtk::common::Managers::Ptr const &)) &smtk::task::Registrar::unregisterFrom, py::arg("manager")) - .def_static("unregisterFrom", (void (*)(::smtk::resource::Manager::Ptr const &)) &smtk::task::Registrar::unregisterFrom, py::arg("manager")) .def_static("unregisterFrom", (void (*)(::smtk::task::Manager::Ptr const &)) &smtk::task::Registrar::unregisterFrom, py::arg("manager")) ; return instance; diff --git a/smtk/task/pybind11/PybindTask.h b/smtk/task/pybind11/PybindTask.h index 8641bbfa6e..0f070dcbdd 100644 --- a/smtk/task/pybind11/PybindTask.h +++ b/smtk/task/pybind11/PybindTask.h @@ -25,7 +25,6 @@ inline PySharedPtrClass< smtk::task::Task > pybind11_init_smtk_task_Task(py::mod instance .def("typeName", &smtk::task::Task::typeName) .def_static("create", (std::shared_ptr (*)()) &smtk::task::Task::create) - .def_static("create", (std::shared_ptr (*)(::std::shared_ptr &)) &smtk::task::Task::create, py::arg("ref")) .def("configure", [](smtk::task::Task& task, const std::string& jsonConfig) { auto config = nlohmann::json::parse(jsonConfig); diff --git a/smtk/task/testing/cxx/TestActiveTask.cxx b/smtk/task/testing/cxx/TestActiveTask.cxx index 359867ad9b..7a6c802cfb 100644 --- a/smtk/task/testing/cxx/TestActiveTask.cxx +++ b/smtk/task/testing/cxx/TestActiveTask.cxx @@ -45,7 +45,7 @@ int TestActiveTask(int, char*[]) auto resourceManager = managers->get(); auto operationManager = managers->get(); - auto taskManager = managers->get(); + auto taskManager = smtk::task::Manager::create(); auto attributeResourceRegistry = smtk::plugin::addToManagers(resourceManager); @@ -72,7 +72,7 @@ int TestActiveTask(int, char*[]) { std::shared_ptr t1 = taskManager->taskInstances().create( - Task::Configuration{ { "title", "Task 1" } }, managers); + Task::Configuration{ { "title", "Task 1" } }, *taskManager, managers); std::cout << "Attempting to set active task:\n"; taskManager->active().switchTo(t1.get()); test(count == 2, "Expected to switch active task."); @@ -95,7 +95,7 @@ int TestActiveTask(int, char*[]) // Now add a task and switch to it. std::shared_ptr t2 = taskManager->taskInstances().create( - Task::Configuration{ { "title", "Task 2" } }, managers); + Task::Configuration{ { "title", "Task 2" } }, *taskManager, managers); success = t1->addDependency(t2); std::cout << "Switching to task 2:\n"; taskManager->active().switchTo(t2.get()); @@ -112,7 +112,8 @@ int TestActiveTask(int, char*[]) { { { "role", "model geometry" }, { "type", "smtk::model::Resource" }, { "max", 2 } }, { { "role", "simulation attribute" }, { "type", "smtk::attribute::Resource" } } } } }; - auto t4 = taskManager->taskInstances().create(c4, managers); + auto t4 = + taskManager->taskInstances().create(c4, *taskManager, managers); t1->addDependency(t4); std::cout << "Ensuring switches to unavailable tasks fail.\n"; bool didSwitch = taskManager->active().switchTo(t1.get()); diff --git a/smtk/task/testing/cxx/TestTaskBasics.cxx b/smtk/task/testing/cxx/TestTaskBasics.cxx index 6aded37e69..5fd2779d6e 100644 --- a/smtk/task/testing/cxx/TestTaskBasics.cxx +++ b/smtk/task/testing/cxx/TestTaskBasics.cxx @@ -39,16 +39,18 @@ public: UnavailableTask() = default; UnavailableTask( const Configuration& config, + Manager& taskManager, const smtk::common::Managers::Ptr& managers = nullptr) - : Task(config, managers) + : Task(config, taskManager, managers) { this->internalStateChanged(State::Unavailable); } UnavailableTask( const Configuration& config, const Task::PassedDependencies& deps, + Manager& taskManager, const smtk::common::Managers::Ptr& managers = nullptr) - : Task(config, deps, managers) + : Task(config, deps, taskManager, managers) { this->internalStateChanged(State::Unavailable); } @@ -76,7 +78,7 @@ int TestTaskBasics(int, char*[]) auto resourceManager = managers->get(); auto operationManager = managers->get(); - auto taskManager = managers->get(); + auto taskManager = smtk::task::Manager::create(); auto attributeResourceRegistry = smtk::plugin::addToManagers(resourceManager); @@ -100,7 +102,7 @@ int TestTaskBasics(int, char*[]) { std::shared_ptr t1 = taskManager->taskInstances().create( - Task::Configuration{ { "title", "Task 1" } }, managers); + Task::Configuration{ { "title", "Task 1" } }, *taskManager, managers); test(!!t1, "Expecting to create a non-null task."); test( t1->state() == State::Completable, "Expected task without dependencies to be completable."); @@ -142,7 +144,7 @@ int TestTaskBasics(int, char*[]) taskManager->taskInstances().contains(), "Expected UnavailableTask to be registered."); std::shared_ptr t2 = taskManager->taskInstances().create( - Task::Configuration{ { "title", "Task 2" } }, managers); + Task::Configuration{ { "title", "Task 2" } }, *taskManager, managers); called = 0; success = t1->addDependency(t2); test(success, "Expected to add dependency to task."); @@ -156,7 +158,7 @@ int TestTaskBasics(int, char*[]) // Test construction with dependencies Task::Configuration c3{ { "title", "Task 3" }, { "completed", true } }; auto t3 = taskManager->taskInstances().create( - c3, std::set>{ { t1, t2 } }, managers); + c3, std::set>{ { t1, t2 } }, *taskManager, managers); // Test visitors. called = 0; @@ -205,7 +207,8 @@ int TestTaskBasics(int, char*[]) { { { "role", "model geometry" }, { "type", "smtk::model::Resource" }, { "max", 2 } }, { { "role", "simulation attribute" }, { "type", "smtk::attribute::Resource" } } } } }; - auto t4 = taskManager->taskInstances().create(c4, managers); + auto t4 = + taskManager->taskInstances().create(c4, *taskManager, managers); test(!!t4, "Could not create GatherResources."); test(t4->state() == State::Incomplete, "Task with no resources should be incomplete."); auto hokey = t4->observers().insert(callback); diff --git a/smtk/task/testing/cxx/TestTaskGroup.cxx b/smtk/task/testing/cxx/TestTaskGroup.cxx index 6f4b8d9802..113b58d0cf 100644 --- a/smtk/task/testing/cxx/TestTaskGroup.cxx +++ b/smtk/task/testing/cxx/TestTaskGroup.cxx @@ -35,6 +35,7 @@ #include "smtk/task/Registrar.h" #include "smtk/task/Task.h" +#include "smtk/task/json/Helper.h" #include "smtk/task/json/jsonManager.h" #include "smtk/task/json/jsonTask.h" @@ -201,7 +202,7 @@ int TestTaskGroup(int, char*[]) auto resourceManager = managers->get(); auto operationManager = managers->get(); - auto taskManager = managers->get(); + auto taskManager = smtk::task::Manager::create(); auto attributeResourceRegistry = smtk::plugin::addToManagers(resourceManager); @@ -234,7 +235,17 @@ int TestTaskGroup(int, char*[]) auto config = nlohmann::json::parse(configString); std::cout << config.dump(2) << "\n"; - bool ok = smtk::task::json::jsonManager::deserialize(managers, config); + bool ok = true; + try + { + smtk::task::json::Helper::pushInstance(*taskManager, managers); + smtk::task::from_json(config, *taskManager); + smtk::task::json::Helper::popInstance(); + } + catch (std::exception&) + { + ok = false; + } test(ok, "Failed to parse configuration."); test(taskManager->taskInstances().size() == 4, "Expected to deserialize 4 tasks."); @@ -242,30 +253,16 @@ int TestTaskGroup(int, char*[]) std::vector taskNames = { "Load a model and attribute", "Prepare simulation", "Mark up model", "Set simulation parameters" }; - smtk::task::State taskStates[][4] = { { smtk::task::State::Completable, - smtk::task::State::Unavailable, - smtk::task::State::Unavailable, - smtk::task::State::Unavailable }, - { smtk::task::State::Completed, - smtk::task::State::Incomplete, - smtk::task::State::Completable, - smtk::task::State::Incomplete }, - { smtk::task::State::Completed, - smtk::task::State::Incomplete, - smtk::task::State::Incomplete, - smtk::task::State::Incomplete }, - { smtk::task::State::Completed, - smtk::task::State::Incomplete, - smtk::task::State::Completable, - smtk::task::State::Incomplete }, - { smtk::task::State::Completed, - smtk::task::State::Completable, - smtk::task::State::Completable, - smtk::task::State::Completable }, - { smtk::task::State::Completed, - smtk::task::State::Irrelevant, - smtk::task::State::Irrelevant, - smtk::task::State::Irrelevant } }; + // clang-format off + smtk::task::State taskStates[][4] = { + { State::Completable, State::Unavailable, State::Unavailable, State::Unavailable }, + { State::Completed, State::Incomplete, State::Completable, State::Incomplete }, + { State::Completed, State::Incomplete, State::Incomplete, State::Incomplete }, + { State::Completed, State::Incomplete, State::Completable, State::Incomplete }, + { State::Completed, State::Completable, State::Completable, State::Completable }, + { State::Completed, State::Irrelevant, State::Irrelevant, State::Irrelevant } + }; + // clang-format on bool hasErrors = false; taskManager->taskInstances().visit( [&theTasks, &taskNames, &hasErrors](const smtk::task::Task::Ptr& task) { @@ -387,7 +384,16 @@ int TestTaskGroup(int, char*[]) { // Round trip it. nlohmann::json config2; - ok = smtk::task::json::jsonManager::serialize(managers, config2); + try + { + smtk::task::json::Helper::pushInstance(*taskManager, managers); + smtk::task::to_json(config2, *taskManager); + smtk::task::json::Helper::popInstance(); + } + catch (std::exception&) + { + ok = false; + } test(ok, "Failed to serialize task manager."); configString2 = config2.dump(2); std::cout << configString2 << "\n"; @@ -400,13 +406,30 @@ int TestTaskGroup(int, char*[]) managers2->insert(operationManager); auto taskManager2 = smtk::task::Manager::create(); smtk::task::Registrar::registerTo(taskManager2); - managers2->insert(taskManager2); auto config3 = nlohmann::json::parse(configString2); - ok = smtk::task::json::jsonManager::deserialize(managers2, config3); + try + { + smtk::task::json::Helper::pushInstance(*taskManager2, managers2); + smtk::task::from_json(config3, *taskManager2); + smtk::task::json::Helper::popInstance(); + } + catch (std::exception&) + { + ok = false; + } test(ok, "Failed to parse second configuration."); test(taskManager2->taskInstances().size() == 4, "Expected to deserialize 4 tasks."); nlohmann::json config4; - ok = smtk::task::json::jsonManager::serialize(managers2, config4); + try + { + smtk::task::json::Helper::pushInstance(*taskManager2, managers2); + smtk::task::to_json(config4, *taskManager2); + smtk::task::json::Helper::popInstance(); + } + catch (std::exception&) + { + ok = false; + } test(ok, "Failed to serialize second manager."); std::string configString3 = config4.dump(2); std::cout << "----\n" << configString3 << "\n"; diff --git a/smtk/task/testing/cxx/TestTaskJSON.cxx b/smtk/task/testing/cxx/TestTaskJSON.cxx index 5cb9169573..28b227862a 100644 --- a/smtk/task/testing/cxx/TestTaskJSON.cxx +++ b/smtk/task/testing/cxx/TestTaskJSON.cxx @@ -32,6 +32,9 @@ #include "smtk/task/Task.h" // #include "smtk/task/GatherResources.h" +#include "smtk/resource/json/Helper.h" + +#include "smtk/task/json/Helper.h" #include "smtk/task/json/jsonManager.h" #include "smtk/task/json/jsonTask.h" @@ -72,7 +75,7 @@ int TestTaskJSON(int, char*[]) auto resourceManager = managers->get(); auto operationManager = managers->get(); - auto taskManager = managers->get(); + auto taskManager = smtk::task::Manager::create(); auto attributeResourceRegistry = smtk::plugin::addToManagers(resourceManager); @@ -167,7 +170,24 @@ int TestTaskJSON(int, char*[]) auto config = nlohmann::json::parse(configString); std::cout << config.dump(2) << "\n"; taskManager->taskInstances().pauseWorkflowNotifications(true); - bool ok = smtk::task::json::jsonManager::deserialize(managers, config); + + bool ok = true; + auto& resourceHelper = smtk::resource::json::Helper::instance(); + resourceHelper.setManagers(managers); + try + { + auto& taskHelper = + smtk::task::json::Helper::pushInstance(*taskManager, resourceHelper.managers()); + taskHelper.setManagers(resourceHelper.managers()); + from_json(config, *taskManager); + smtk::task::json::Helper::popInstance(); + } + catch (std::exception&) + { + ok = false; + } + + // bool ok = smtk::task::json::jsonManager::deserialize(managers, config); taskManager->taskInstances().pauseWorkflowNotifications(false); test(ok, "Failed to parse configuration."); test(taskManager->taskInstances().size() == 2, "Expected to deserialize 2 tasks."); @@ -227,7 +247,16 @@ int TestTaskJSON(int, char*[]) { // Round trip it. nlohmann::json config2; - ok = smtk::task::json::jsonManager::serialize(managers, config2); + try + { + smtk::task::json::Helper::pushInstance(*taskManager, resourceHelper.managers()); + config2 = *taskManager; + smtk::task::json::Helper::popInstance(); + } + catch (std::exception&) + { + ok = false; + } test(ok, "Failed to serialize task manager."); configString2 = config2.dump(2); std::cout << configString2 << "\n"; @@ -240,9 +269,17 @@ int TestTaskJSON(int, char*[]) managers2->insert(operationManager); auto taskManager2 = smtk::task::Manager::create(); smtk::task::Registrar::registerTo(taskManager2); - managers2->insert(taskManager2); auto config3 = nlohmann::json::parse(configString2); - ok = smtk::task::json::jsonManager::deserialize(managers2, config3); + try + { + smtk::task::json::Helper::pushInstance(*taskManager2, resourceHelper.managers()); + from_json(config3, *taskManager2); + smtk::task::json::Helper::popInstance(); + } + catch (std::exception&) + { + ok = false; + } test(ok, "Failed to parse second configuration."); test(taskManager2->taskInstances().size() == 2, "Expected to deserialize 2 tasks."); @@ -266,7 +303,16 @@ int TestTaskJSON(int, char*[]) gatherResources2->style().size() == 3, "Expected 3 style class-names for gatherResources2."); nlohmann::json config4; - ok = smtk::task::json::jsonManager::serialize(managers2, config4); + try + { + smtk::task::json::Helper::pushInstance(*taskManager2, resourceHelper.managers()); + config4 = *taskManager2; + smtk::task::json::Helper::popInstance(); + } + catch (std::exception&) + { + ok = false; + } test(ok, "Failed to serialize second manager."); std::string configString3 = config4.dump(2); std::cout << "----\n" << configString3 << "\n"; diff --git a/smtk/task/testing/python/testTaskManager.py b/smtk/task/testing/python/testTaskManager.py index 82b0cf5955..f2d291b4c9 100644 --- a/smtk/task/testing/python/testTaskManager.py +++ b/smtk/task/testing/python/testTaskManager.py @@ -25,11 +25,13 @@ class TestTaskManager(smtk.testing.TestCase): import smtk.task import json + print('Test task-manager creation.') # Verify we can create a task manager mgr = smtk.task.Manager.create() - assert(mgr) - assert(mgr.typeName() == 'smtk::task::Manager') + assert (mgr) + assert (mgr.typeName() == 'smtk::task::Manager') + print('Test task-manager registration.') # Register core task types to manager smtk.task.Registrar.registerTo(mgr) @@ -44,44 +46,45 @@ class TestTaskManager(smtk.testing.TestCase): } ] } + print('Test task creation.') foa = mgr.taskInstances().createFromName( 'smtk::task::FillOutAttributes', json.dumps(cfg)) - assert(foa != None) + assert (foa != None) print('{:1} (an instance of {:2}), {:3}'.format( foa.title(), foa.typeName(), str(foa.state()))) - assert(foa.title() == cfg['title']) - assert(foa.typeName() == 'smtk::task::FillOutAttributes') - assert(foa.state() == smtk.task.State.Completable) + assert (foa.title() == cfg['title']) + assert (foa.typeName() == 'smtk::task::FillOutAttributes') + assert (foa.state() == smtk.task.State.Completable) foa.markCompleted(True) - assert(foa.state() == smtk.task.State.Completed) + assert (foa.state() == smtk.task.State.Completed) # Verify the instance we created is managed print('All managed task instances:') tasks = mgr.taskInstances() print('\n'.join([' {:1} (an instance of {:2}'.format( x.title(), x.typeName()) for x in tasks.instances()])) - assert(len(tasks.instances()) == 1) + assert (len(tasks.instances()) == 1) # Test getting/setting the manager's active task. # Initially there should be no active task. activeTask = mgr.active().task() print('Active task is {:1}'.format( activeTask.title() if activeTask else '(null)')) - assert(activeTask == None) + assert (activeTask == None) # We should be able to switch the active task. mgr.active().switchTo(foa) activeTask = mgr.active().task() print('Active task is {:1}'.format( activeTask.title() if activeTask else '(null)')) - assert(activeTask == foa) + assert (activeTask == foa) # We should be able to reset the active task to its initial state. mgr.active().switchTo(None) activeTask = mgr.active().task() print('Active task is {:1}'.format( activeTask.title() if activeTask else '(null)')) - assert(activeTask == None) + assert (activeTask == None) if __name__ == '__main__': -- GitLab From 260eafd2de7c1058535aeff4b8b1402db3eb75b2 Mon Sep 17 00:00:00 2001 From: Aron Helser Date: Tue, 28 Feb 2023 12:32:01 -0500 Subject: [PATCH 06/15] Add optional "Apply & Close" button to qtOperationDialog. For client plugins, gives a quick way to run the operation and close the dialog when shown non-modal. Doc comment cleanup. --- smtk/extension/qt/qtOperationDialog.cxx | 39 +++++++++++++++++++----- smtk/extension/qt/qtOperationDialog.h | 40 +++++++++++++------------ 2 files changed, 52 insertions(+), 27 deletions(-) diff --git a/smtk/extension/qt/qtOperationDialog.cxx b/smtk/extension/qt/qtOperationDialog.cxx index 63e53bc3ba..c71b119616 100644 --- a/smtk/extension/qt/qtOperationDialog.cxx +++ b/smtk/extension/qt/qtOperationDialog.cxx @@ -83,12 +83,15 @@ class qtOperationDialogInternals public: QPushButton* m_applyButton = nullptr; QPushButton* m_cancelButton = nullptr; + QPushButton* m_applyCloseButton = nullptr; QTabWidget* m_tabWidget = nullptr; QSharedPointer m_uiManager; smtk::extension::qtOperationView* m_smtkView = nullptr; smtk::operation::OperationPtr m_operation; + bool m_closeAfterOperate = false; + qtOperationDialogInternals() = default; ~qtOperationDialogInternals() = default; }; @@ -96,22 +99,24 @@ public: qtOperationDialog::qtOperationDialog( smtk::operation::OperationPtr op, QSharedPointer uiManager, - QWidget* parentWidget) + QWidget* parentWidget, + bool showApplyAndClose) : QDialog(parentWidget) { - this->buildUI(op, uiManager); + this->buildUI(op, uiManager, false, showApplyAndClose); } qtOperationDialog::qtOperationDialog( smtk::operation::OperationPtr op, smtk::resource::ManagerPtr resManager, smtk::view::ManagerPtr viewManager, - QWidget* parentWidget) + QWidget* parentWidget, + bool showApplyAndClose) : QDialog(parentWidget) { auto uiManager = QSharedPointer( new smtk::extension::qtUIManager(op, resManager, viewManager)); - this->buildUI(op, uiManager); + this->buildUI(op, uiManager, false, showApplyAndClose); } qtOperationDialog::qtOperationDialog( @@ -124,13 +129,14 @@ qtOperationDialog::qtOperationDialog( { auto uiManager = QSharedPointer( new smtk::extension::qtUIManager(op, resManager, viewManager)); - this->buildUI(op, uiManager, scrollable); + this->buildUI(op, uiManager, scrollable, false); } void qtOperationDialog::buildUI( smtk::operation::OperationPtr op, QSharedPointer uiManager, - bool scrollable) + bool scrollable, + bool showApplyAndClose) { this->setObjectName("ExportDialog"); m_internals = new qtOperationDialogInternals(); @@ -188,6 +194,12 @@ void qtOperationDialog::buildUI( m_internals->m_applyButton = buttonBox->button(QDialogButtonBox::Apply); m_internals->m_cancelButton = buttonBox->button(QDialogButtonBox::Cancel); + if (showApplyAndClose) + { + m_internals->m_applyCloseButton = buttonBox->addButton(QDialogButtonBox::Yes); + m_internals->m_applyCloseButton->setText("Apply && Close"); + } + // don't set a default button, so "Enter" won't dismiss the dialog. But // make Apply come first it tab order, so tabbing to Apply then "Enter" works, // if the dialog is modal. @@ -199,6 +211,15 @@ void qtOperationDialog::buildUI( this, &qtOperationDialog::onOperationExecuted); QObject::connect(m_internals->m_cancelButton, &QPushButton::clicked, this, &QDialog::reject); + if (m_internals->m_applyCloseButton) + { + QObject::connect(m_internals->m_applyCloseButton, &QPushButton::clicked, this, [this]() { + // flag that we want the dialog to close after the operation finishes + this->m_internals->m_closeAfterOperate = true; + // perform operation + this->m_internals->m_smtkView->onOperate(); + }); + } m_internals->m_smtkView->setButtons(m_internals->m_applyButton, nullptr, nullptr); bool isValid = m_internals->m_operation->parameters()->isValid(); @@ -224,9 +245,11 @@ qtOperationDialog::~qtOperationDialog() void qtOperationDialog::onOperationExecuted(const smtk::operation::Operation::Result& result) { Q_EMIT this->operationExecuted(result); - if (this->isModal()) + if (this->isModal() || this->m_internals->m_closeAfterOperate) { - this->accept(); // closes the dialog + this->m_internals->m_closeAfterOperate = false; + // closes the dialog, and deletes if setAttribute(Qt::WA_DeleteOnClose) as called. + this->done(QDialog::Accepted); } else { diff --git a/smtk/extension/qt/qtOperationDialog.h b/smtk/extension/qt/qtOperationDialog.h index 120ad1fb01..99bef29ccb 100644 --- a/smtk/extension/qt/qtOperationDialog.h +++ b/smtk/extension/qt/qtOperationDialog.h @@ -23,6 +23,13 @@ class QShowEvent; class QWidget; class qtOperationDialogInternals; +namespace smtk +{ +namespace extension +{ + +class qtUIManager; + /**\brief Provides a dialog for launching SMTK operations. * * The intended use is for modelbuilder plugins that use menu or similar actions to invoke @@ -31,16 +38,8 @@ class qtOperationDialogInternals; * for the operation, and the second tab displays the operation's "info" content. The * dialog replaces and hides the "Apply", "Info" and "Cancel" buttons and consumes their Qt * connections. The dialog can be shown non-modal, to allow repeated running of an operation. + * In non-modal operation, an optional "Apply & Close" button can be shown. */ - -namespace smtk -{ -namespace extension -{ - -class qtUIManager; - -// Modal dialog for smtk operation view. class SMTKQTEXT_EXPORT qtOperationDialog : public QDialog { Q_OBJECT @@ -49,19 +48,21 @@ public: qtOperationDialog( smtk::operation::OperationPtr operation, QSharedPointer uiManager, - QWidget* parentWidget = nullptr); + QWidget* parentWidget = nullptr, + bool showApplyAndClose = false); qtOperationDialog( smtk::operation::OperationPtr operation, smtk::resource::ManagerPtr resourceManager, smtk::view::ManagerPtr viewManager, - QWidget* parentWidget = nullptr); - - // Use this constructor to display the operation view in a - // vertically-scrolling area. You would generally only need - // this option if the operation parameters are lengthy and - // take up a significant amount of vertical display space. - // When setting the scrollable option, you would generally - // also want to call this class' setMinimumHeight() method. + QWidget* parentWidget = nullptr, + bool showApplyAndClose = false); + + /// Use this constructor to display the operation view in a + /// vertically-scrolling area. You would generally only need + /// this option if the operation parameters are lengthy and + /// take up a significant amount of vertical display space. + /// When setting the scrollable option, you would generally + /// also want to call this class' setMinimumHeight() method. qtOperationDialog( smtk::operation::OperationPtr operation, smtk::resource::ManagerPtr resourceManager, @@ -82,7 +83,8 @@ protected: void buildUI( smtk::operation::OperationPtr op, QSharedPointer uiMManager, - bool scrollable = false); + bool scrollable = false, + bool showApplyAndClose = false); // Override showEvent() in order to fix Qt sizing issue void showEvent(QShowEvent* event) override; -- GitLab From ec5e46ee9f20ac300e6f405d38422f568ea4b581 Mon Sep 17 00:00:00 2001 From: Robert O'Bara Date: Fri, 24 Mar 2023 15:15:10 -0400 Subject: [PATCH 07/15] ENH: Added ability for Instanced Views to find Attributes by ID You can now specify the ID of the Attribute by adding a View Component Attributed called ID whose value is the Attribute's UUID (as a string). The previous methods of finding the Attribute by Name or using Name and Type to create the Attribute will still work; however, the View will modify its configuration by adding the ID attribute set to the Attribute that was found by Name (or created using Name and Type). This way the View would still be appropriate even if the Attributes' Names where changed afterwords. --- doc/release/notes/InstancedViewChanges.rst | 10 +++ smtk/extension/qt/qtInstancedView.cxx | 78 ++++++++++++++++------ 2 files changed, 67 insertions(+), 21 deletions(-) create mode 100644 doc/release/notes/InstancedViewChanges.rst diff --git a/doc/release/notes/InstancedViewChanges.rst b/doc/release/notes/InstancedViewChanges.rst new file mode 100644 index 0000000000..5f878f4cc0 --- /dev/null +++ b/doc/release/notes/InstancedViewChanges.rst @@ -0,0 +1,10 @@ +Adding ID Support to Instanced Views +------------------------------------ + +You can now refer to an Attribute in an Instanced View by its **ID**. This will allow the View to refer to +the Attribute even if its Name is changed later on. + +The View will add the **ID** View Configuration information if it doesn't already exist and will use it +in the future to find the Attribute. + +If the **ID** is not specified, the view will continue require the **Name** configuration view attribute to be specified (along with the **Type** configuration view attribute if the named Attribute does not currently exists). diff --git a/smtk/extension/qt/qtInstancedView.cxx b/smtk/extension/qt/qtInstancedView.cxx index a313331109..f9b1c7bd47 100644 --- a/smtk/extension/qt/qtInstancedView.cxx +++ b/smtk/extension/qt/qtInstancedView.cxx @@ -142,7 +142,7 @@ void qtInstancedView::updateUI() } smtk::attribute::ResourcePtr resource = this->attributeResource(); - std::string attName, defName; + std::string attName, defName, attIDStr; smtk::attribute::AttributePtr att; smtk::attribute::DefinitionPtr attDef; Q_FOREACH (qtAttribute* qatt, this->Internals->AttInstances) @@ -154,7 +154,7 @@ void qtInstancedView::updateUI() std::vector atts; std::vector comps; int longLabelWidth = 0; - // Lets find the InstancedAttributes Infomation + // Lets find the InstancedAttributes Information int index = view->details().findChild("InstancedAttributes"); if (index < 0) { @@ -166,40 +166,76 @@ void qtInstancedView::updateUI() std::size_t i, n = comp.numberOfChildren(); for (i = 0; i < n; i++) { - smtk::view::Configuration::Component attComp = comp.child(i); + smtk::view::Configuration::Component& attComp = comp.child(i); if (attComp.name() != "Att") { continue; } - if (!attComp.attribute("Name", attName)) + // Lets see if we are searching by ID + if (attComp.attribute("ID", attIDStr)) { - return; // No name set + smtk::common::UUID attID(attIDStr); + att = resource->findAttribute(attID); + if (att == nullptr) + { + qWarning( + "WARNING: View \"%s\" could not find Attribute by ID: \"%s\".", + view->name().c_str(), + attIDStr.c_str()); + continue; + } + attDef = att->definition(); } - - // See if the attribute exists and if not then create it - att = resource->findAttribute(attName); - if (!att) + else { - if (!attComp.attribute("Type", defName)) + if (!attComp.attribute("Name", attName)) { - // No attribute definition name - continue; + qWarning( + "WARNING: View \"%s\" could not find Attribute. Neither ID or Name was specified.", + view->name().c_str()); + continue; // No name set } - attDef = resource->findDefinition(defName); - if (!attDef) + + // See if the attribute exists and if not then create it + att = resource->findAttribute(attName); + if (att) { - continue; + attDef = att->definition(); } else { - att = resource->createAttribute(attName, attDef); - this->attributeCreated(att); + if (!attComp.attribute("Type", defName)) + { + // No attribute definition name + qWarning( + "WARNING: View \"%s\" could not find Attribute by Name: \"%s\" and Type was not " + "specified.", + view->name().c_str(), + attName.c_str()); + continue; + } + attDef = resource->findDefinition(defName); + if (!attDef) + { + qWarning( + "WARNING: View \"%s\" could not find Attribute by Name: \"%s\" and not find Type: " + "\"%s\".", + view->name().c_str(), + attName.c_str(), + defName.c_str()); + continue; + } + else + { + att = resource->createAttribute(attName, attDef); + this->attributeCreated(att); + } } - } - else - { - attDef = att->definition(); + // Lets add the Attribute's ID to the configuration so we can look it up by ID. + // This will allow the View to reference the attribute even if its name is changed later on. + attIDStr = att->id().toString(); + attComp.setAttribute("ID", attIDStr); } atts.push_back(att); -- GitLab From 11beaa4bca87a5a97ede41c967d3130a3bfa3b92 Mon Sep 17 00:00:00 2001 From: Robert O'Bara Date: Fri, 24 Mar 2023 17:08:23 -0400 Subject: [PATCH 08/15] BUG: Fix Crash related to lambda expression A lambda was being used to deal with Tab ordering and was passing the this pointer. In some race conditions, the object was being deleted when the lambda was being executed. To fix this we needed to use a QPointer instead. --- smtk/extension/qt/qtGroupItem.cxx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/smtk/extension/qt/qtGroupItem.cxx b/smtk/extension/qt/qtGroupItem.cxx index a02a5c1cf2..7dd516aa76 100644 --- a/smtk/extension/qt/qtGroupItem.cxx +++ b/smtk/extension/qt/qtGroupItem.cxx @@ -230,7 +230,13 @@ void qtGroupItem::createWidget() } // Update tab ordering on next event loop - QTimer::singleShot(0, [this]() { this->updateTabOrder(m_internals->m_precedingEditor); }); + QPointer guardedObject(this); + QTimer::singleShot(0, [guardedObject]() { + if (guardedObject) + { + guardedObject->updateTabOrder(guardedObject->m_internals->m_precedingEditor); + } + }); } void qtGroupItem::setEnabledState(int state) -- GitLab From 759efe2cdc202df577075539d7a0b9d5aae2a135 Mon Sep 17 00:00:00 2001 From: Robert O'Bara Date: Mon, 27 Mar 2023 17:26:03 -0400 Subject: [PATCH 09/15] BUG: Adding Missing Export These templates should have been marked SMTK_ALWAYS_EXPORT. An issue showed up on an ARM Mac where a ValueItem failed to be cast into the appropriate ValueItemTemplate instance. --- smtk/attribute/ValueItemDefinitionTemplate.h | 4 +++- smtk/attribute/ValueItemTemplate.h | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/smtk/attribute/ValueItemDefinitionTemplate.h b/smtk/attribute/ValueItemDefinitionTemplate.h index 4b2a683eb7..07eaddb278 100644 --- a/smtk/attribute/ValueItemDefinitionTemplate.h +++ b/smtk/attribute/ValueItemDefinitionTemplate.h @@ -15,6 +15,8 @@ #define smtk_attribute_ValueItemDefinitionTemplate_h #include "smtk/attribute/ValueItemDefinition.h" +#include "smtk/common/CompilerInformation.h" + #include #include @@ -23,7 +25,7 @@ namespace smtk namespace attribute { template -class ValueItemDefinitionTemplate : public smtk::attribute::ValueItemDefinition +class SMTK_ALWAYS_EXPORT ValueItemDefinitionTemplate : public smtk::attribute::ValueItemDefinition { public: typedef DataT DataType; diff --git a/smtk/attribute/ValueItemTemplate.h b/smtk/attribute/ValueItemTemplate.h index 83b4e6f8ac..aff6f5dbaf 100644 --- a/smtk/attribute/ValueItemTemplate.h +++ b/smtk/attribute/ValueItemTemplate.h @@ -30,7 +30,7 @@ namespace smtk namespace attribute { template -class ValueItemTemplate : public ValueItem +class SMTK_ALWAYS_EXPORT ValueItemTemplate : public ValueItem { //template friend class ValueItemDefinitionTemplate; public: -- GitLab From 378f9fecf3f7638c40c572c09115ce2090fa9cf9 Mon Sep 17 00:00:00 2001 From: David Thompson Date: Wed, 29 Mar 2023 14:08:28 -0400 Subject: [PATCH 10/15] Catch boost exception thrown for inaccessible directories. --- doc/release/notes/paths-exception.rst | 7 +++ smtk/common/Paths.cxx | 27 +++++++-- smtk/common/testing/cxx/CMakeLists.txt | 1 + smtk/common/testing/cxx/unitPaths.cxx | 78 ++++++++++++++++++++++++++ 4 files changed, 109 insertions(+), 4 deletions(-) create mode 100644 doc/release/notes/paths-exception.rst diff --git a/doc/release/notes/paths-exception.rst b/doc/release/notes/paths-exception.rst new file mode 100644 index 0000000000..800df564a0 --- /dev/null +++ b/doc/release/notes/paths-exception.rst @@ -0,0 +1,7 @@ +Common directory utility +------------------------ + +Previously, the :smtk:`smtk::common::Paths::directory` method would +throw a boost filesystem exception if a user called it without +permission to the path passed to it. This has been fixed by capturing +the exception internally. diff --git a/smtk/common/Paths.cxx b/smtk/common/Paths.cxx index c749ec2353..30fe00b307 100644 --- a/smtk/common/Paths.cxx +++ b/smtk/common/Paths.cxx @@ -37,6 +37,7 @@ #endif SMTK_THIRDPARTY_PRE_INCLUDE +#define BOOST_FILESYSTEM_VERSION 3 #include "boost/filesystem.hpp" #include "boost/system/error_code.hpp" #include @@ -121,7 +122,18 @@ bool Paths::createDirectory(const std::string& path) /// Is the given path a file? bool Paths::fileExists(const std::string& path) { - return boost::filesystem::exists(path.c_str()); + bool ok = false; + try + { + ok = boost::filesystem::exists(path.c_str()); + } + catch (boost::filesystem::filesystem_error&) + { + // Do nothing. If we get here, it is because we do not + // have permission to read a containing directory; assume + // the file does not exist (to the best of our knowledge). + } + return ok; } /// Is the path relative (i.e., not absolute)? @@ -194,10 +206,17 @@ std::string Paths::canonical(const std::string& path, const std::string& base) std::string Paths::directory(const std::string& path) { auto fullpath = boost::filesystem::path(path); - if (boost::filesystem::exists(fullpath)) + try + { + if (boost::filesystem::exists(fullpath)) + { + return boost::filesystem::is_directory(fullpath) ? fullpath.string() + : fullpath.parent_path().string(); + } + } + catch (boost::filesystem::filesystem_error&) { - return boost::filesystem::is_directory(fullpath) ? fullpath.string() - : fullpath.parent_path().string(); + // Do nothing. } return fullpath.parent_path().string(); } diff --git a/smtk/common/testing/cxx/CMakeLists.txt b/smtk/common/testing/cxx/CMakeLists.txt index 205d84cbc3..5d0c2a6e32 100644 --- a/smtk/common/testing/cxx/CMakeLists.txt +++ b/smtk/common/testing/cxx/CMakeLists.txt @@ -16,6 +16,7 @@ foreach (test ${commonTests}) NAME ${test} COMMAND $) endforeach() +target_link_libraries(unitPaths ${Boost_LIBRARIES}) if (SMTK_ENABLE_PYTHON_WRAPPING) add_executable(QuerySMTKPythonForModule QuerySMTKPythonForModule.cxx) diff --git a/smtk/common/testing/cxx/unitPaths.cxx b/smtk/common/testing/cxx/unitPaths.cxx index 221b9fdca8..578a6ee2c1 100644 --- a/smtk/common/testing/cxx/unitPaths.cxx +++ b/smtk/common/testing/cxx/unitPaths.cxx @@ -15,6 +15,13 @@ #include "smtk/io/Logger.h" +SMTK_THIRDPARTY_PRE_INCLUDE +#define BOOST_FILESYSTEM_VERSION 3 +#include "boost/filesystem.hpp" +#include "boost/system/error_code.hpp" +#include +SMTK_THIRDPARTY_POST_INCLUDE + #include // for remove() #include #include @@ -23,6 +30,72 @@ using namespace smtk::common; +namespace +{ + +bool directoryDoesNotThrow(bool allowAccess) +{ + std::cout << "Test parent directory computation when access " << (allowAccess ? "is" : "is not") + << " allowed.\n"; + bool ok = true; + using boost::filesystem::create_directories; + using boost::filesystem::permissions; + using boost::filesystem::perms; + using boost::filesystem::remove_all; + + auto unlikely = smtk::common::Paths::uniquePath(); + auto unlikelyChild = unlikely + "/" + smtk::common::Paths::uniquePath(); + std::cout << " Unlikely dirs: \"" << unlikelyChild << "\n"; + + if (smtk::common::Paths::canonical(smtk::common::Paths::currentDirectory()).empty()) + { + // We don't have a temp directory; skip the rest. + return ok; + } + + // Create a directory and subdirectory, then remove permissions from the parent. + create_directories(unlikelyChild); + if (!allowAccess) + { + permissions(unlikelyChild, perms::remove_perms | perms::all_all); + permissions(unlikely, perms::remove_perms | perms::all_all); + } + try + { + auto parent = smtk::common::Paths::directory(unlikelyChild); + std::cout << " Parent of " << unlikelyChild << " is " << parent << "\n"; + + // When directory access is allowed, since unlikelyChild is a directory, + // it returns the input directory as the directory portion of the path + // rather than its parent. In the case where allowAccess is false, we + // should expect the parent directory to be returned (since we cannot + // inspect unlikelyChild to determine its path). However, some platforms + // (fedora33/36, windows) behave differently than others (macos) so we do + // not force a validity check when access is disallowed. + if (allowAccess) + { + smtkTest(parent == unlikelyChild, "Expected parent directories to match."); + ok &= (parent == unlikelyChild); + } + } + catch (...) + { + std::cerr << "ERROR: Caught exception trying to identify parent directory.\n"; + ok = false; + } + if (!allowAccess) + { + // Add permissions back and delete the directories whether we failed or not. + permissions(unlikely, perms::add_perms | perms::all_all); + permissions(unlikelyChild, perms::add_perms | perms::all_all); + } + remove_all(unlikely); + + return ok; +} + +} // namespace + int main(int argc, char* argv[]) { smtk::io::Logger::instance().setFlushToStdout(true); @@ -169,5 +242,10 @@ int main(int argc, char* argv[]) remove(unlikely.c_str()); + smtkTest( + directoryDoesNotThrow(false), "Expected Paths::directory() to succeed without exception."); + smtkTest( + directoryDoesNotThrow(true), "Expected Paths::directory() to succeed without exception."); + return 0; } -- GitLab From 1081009cb3a145d63fb53db4f2ab9fc6b997059e Mon Sep 17 00:00:00 2001 From: Robert O'Bara Date: Fri, 31 Mar 2023 14:02:11 -0400 Subject: [PATCH 11/15] BUG: Fixing Instance View Issue When the ID support was added, the including making a smtk::view::Configuration::Component variable a component so that it could be modified by adding the newly created Attribute's ID. Unfortunately the same variable is later assigned to a style which inadvertently changed the configuration making it invalid. This change undoes the reference change and directly modified the configuration with the ID information. --- smtk/extension/qt/qtInstancedView.cxx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/smtk/extension/qt/qtInstancedView.cxx b/smtk/extension/qt/qtInstancedView.cxx index f9b1c7bd47..4065721f4e 100644 --- a/smtk/extension/qt/qtInstancedView.cxx +++ b/smtk/extension/qt/qtInstancedView.cxx @@ -166,7 +166,7 @@ void qtInstancedView::updateUI() std::size_t i, n = comp.numberOfChildren(); for (i = 0; i < n; i++) { - smtk::view::Configuration::Component& attComp = comp.child(i); + smtk::view::Configuration::Component attComp = comp.child(i); if (attComp.name() != "Att") { continue; @@ -235,7 +235,7 @@ void qtInstancedView::updateUI() // Lets add the Attribute's ID to the configuration so we can look it up by ID. // This will allow the View to reference the attribute even if its name is changed later on. attIDStr = att->id().toString(); - attComp.setAttribute("ID", attIDStr); + comp.child(i).setAttribute("ID", attIDStr); } atts.push_back(att); -- GitLab From 9cffb6bb01e7916e01256f33ddd07d52d385bc46 Mon Sep 17 00:00:00 2001 From: David Thompson Date: Wed, 22 Feb 2023 17:46:46 -0500 Subject: [PATCH 12/15] Add a node-based task-editor panel. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Show and raise the task panel when loading a project; tasks are illustrated as nodes in a graph whose edges are dependencies and task-adaptors. This also: + Adds task styles to the `smtk::task::Manager` and makes the attribute-editor panel respond to active task changes that provide an attribute view style. + Renames project plugins to make room for a "base" plugin. Now the two paraview-dependent plugins are named + smtkPQCoreProjectPlugin – registers GUI functionality but does not create GUI elements. + smtkPQGuiProjectPlugin – add GUI elements to application. We also now register only the project manager and other core classes (that have no Qt/ParaView dependencies) in a new "core" project plugin. + Fixes an issue with `GatherResources` warnings. Warnings were being emitted even when attribute IDs were discovered because finding an ID never reset the warning flag. + Fixes a `FillOutAttributes` initialization issue; unless a task was set to autoconfigure, it would never report being properly configured (even if its serialized configuration included resource ids as needed). + Ensures application state is available when reading resources. We pass the `smtk::common::Managers` instance from the `ReadResource` operation to the internal `Read` operation it creates so that they can access application state. This is required for projects whose task managers need access to at least a `resource::Manager` in order to find external components specified by UUID in their configuration. + Fixes a task deserialization issue. When tasks are written, there is no guarantee they will be ordered by monotonically increasing ID. Take this into account when restoring saved state through a new method that allows callers to specify (rather than be assigned) a swizzle ID. Co-authored-by: John Tourtellott Co-authored-by: Aron Helser Co-authored-by: David Thompson --- CMake/FindGraphviz.cmake | 80 +++ CMake/Options.h.in | 4 + CMakeLists.txt | 5 + data/baseline/smtk/mesh/MeshSelection_5.png | 3 + data/baseline/smtk/mesh/OpenExodusFile_1.png | 3 + doc/release/notes/task-panel.rst | 17 + .../paraview/appcomponents/CMakeLists.txt | 2 + .../appcomponents/plugin-gui/CMakeLists.txt | 9 +- .../DefaultConfiguration.cxx | 14 +- .../pqSMTKAppComponentsAutoStart.cxx | 1 - .../appcomponents/pqSMTKAttributePanel.cxx | 143 +++++ .../appcomponents/pqSMTKAttributePanel.h | 15 + .../pqSMTKOperationHintsBehavior.cxx | 25 +- .../paraview/appcomponents/pqSMTKTaskDock.h | 26 + .../appcomponents/pqSMTKTaskPanel.cxx | 166 ++++++ .../paraview/appcomponents/pqSMTKTaskPanel.h | 60 ++ .../mesh/testing/xml/MeshSelection.xml | 1 + .../extension/paraview/project/CMakeLists.txt | 1 + .../project/plugin-core/CMakeLists.txt | 5 +- .../project/plugin-core/paraview.plugin | 2 +- .../project/plugin-gui/CMakeLists.txt | 8 +- .../project/plugin-gui/paraview.plugin | 4 +- .../pqSMTKDisplayProjectOnLoadBehavior.cxx | 169 ++++++ .../pqSMTKDisplayProjectOnLoadBehavior.h | 73 +++ .../project/pqSMTKProjectAutoStart.cxx | 4 + smtk/extension/qt/CMakeLists.txt | 38 +- smtk/extension/qt/qtViewRegistrar.cxx | 8 +- .../extension/qt/task/PanelConfiguration.json | 15 + .../qt/task/PanelConfiguration.spec.json | 78 +++ smtk/extension/qt/task/TaskEditorState.cxx | 39 ++ smtk/extension/qt/task/TaskEditorState.h | 43 ++ smtk/extension/qt/task/TaskNode.ui | 185 ++++++ smtk/extension/qt/task/qtTaskArc.cxx | 311 ++++++++++ smtk/extension/qt/task/qtTaskArc.h | 103 ++++ smtk/extension/qt/task/qtTaskEditor.cxx | 549 ++++++++++++++++++ smtk/extension/qt/task/qtTaskEditor.h | 77 +++ smtk/extension/qt/task/qtTaskNode.cxx | 357 ++++++++++++ smtk/extension/qt/task/qtTaskNode.h | 113 ++++ smtk/extension/qt/task/qtTaskScene.cxx | 215 +++++++ smtk/extension/qt/task/qtTaskScene.h | 76 +++ smtk/extension/qt/task/qtTaskView.cxx | 72 +++ smtk/extension/qt/task/qtTaskView.h | 61 ++ .../qt/task/qtTaskViewConfiguration.cxx | 167 ++++++ .../qt/task/qtTaskViewConfiguration.h | 99 ++++ smtk/operation/operators/ReadResource.cxx | 3 + smtk/project/CMakeLists.txt | 5 + smtk/project/Manager.cxx | 8 + smtk/project/operators/Read.cxx | 2 +- smtk/project/plugin/CMakeLists.txt | 9 + smtk/project/plugin/paraview.plugin | 4 + .../mesh/testing/xml/OpenExodusFile.xml | 1 + smtk/task/CMakeLists.txt | 4 + smtk/task/FillOutAttributes.cxx | 49 +- smtk/task/Manager.cxx | 9 + smtk/task/Manager.h | 14 + smtk/task/State.h | 10 +- smtk/task/Task.h | 3 + smtk/task/UIState.cxx | 79 +++ smtk/task/UIState.h | 67 +++ smtk/task/UIStateGenerator.cxx | 20 + smtk/task/UIStateGenerator.h | 43 ++ smtk/task/json/Configurator.h | 5 + smtk/task/json/Configurator.txx | 33 ++ smtk/task/json/Helper.cxx | 12 + smtk/task/json/Helper.h | 3 + smtk/task/json/jsonGatherResources.cxx | 6 + smtk/task/json/jsonManager.cxx | 35 +- smtk/task/pybind11/PybindState.h | 2 +- smtk/task/testing/cxx/CMakeLists.txt | 1 + smtk/task/testing/cxx/TestTaskUIState.cxx | 83 +++ 70 files changed, 3909 insertions(+), 47 deletions(-) create mode 100644 CMake/FindGraphviz.cmake create mode 100644 data/baseline/smtk/mesh/MeshSelection_5.png create mode 100644 data/baseline/smtk/mesh/OpenExodusFile_1.png create mode 100644 doc/release/notes/task-panel.rst create mode 100644 smtk/extension/paraview/appcomponents/pqSMTKTaskDock.h create mode 100644 smtk/extension/paraview/appcomponents/pqSMTKTaskPanel.cxx create mode 100644 smtk/extension/paraview/appcomponents/pqSMTKTaskPanel.h create mode 100644 smtk/extension/paraview/project/pqSMTKDisplayProjectOnLoadBehavior.cxx create mode 100644 smtk/extension/paraview/project/pqSMTKDisplayProjectOnLoadBehavior.h create mode 100644 smtk/extension/qt/task/PanelConfiguration.json create mode 100644 smtk/extension/qt/task/PanelConfiguration.spec.json create mode 100644 smtk/extension/qt/task/TaskEditorState.cxx create mode 100644 smtk/extension/qt/task/TaskEditorState.h create mode 100644 smtk/extension/qt/task/TaskNode.ui create mode 100644 smtk/extension/qt/task/qtTaskArc.cxx create mode 100644 smtk/extension/qt/task/qtTaskArc.h create mode 100644 smtk/extension/qt/task/qtTaskEditor.cxx create mode 100644 smtk/extension/qt/task/qtTaskEditor.h create mode 100644 smtk/extension/qt/task/qtTaskNode.cxx create mode 100644 smtk/extension/qt/task/qtTaskNode.h create mode 100644 smtk/extension/qt/task/qtTaskScene.cxx create mode 100644 smtk/extension/qt/task/qtTaskScene.h create mode 100644 smtk/extension/qt/task/qtTaskView.cxx create mode 100644 smtk/extension/qt/task/qtTaskView.h create mode 100644 smtk/extension/qt/task/qtTaskViewConfiguration.cxx create mode 100644 smtk/extension/qt/task/qtTaskViewConfiguration.h create mode 100644 smtk/project/plugin/CMakeLists.txt create mode 100644 smtk/project/plugin/paraview.plugin create mode 100644 smtk/task/UIState.cxx create mode 100644 smtk/task/UIState.h create mode 100644 smtk/task/UIStateGenerator.cxx create mode 100644 smtk/task/UIStateGenerator.h create mode 100644 smtk/task/testing/cxx/TestTaskUIState.cxx diff --git a/CMake/FindGraphviz.cmake b/CMake/FindGraphviz.cmake new file mode 100644 index 0000000000..a5ce98f106 --- /dev/null +++ b/CMake/FindGraphviz.cmake @@ -0,0 +1,80 @@ +find_path(Graphviz_INCLUDE_DIR + NAMES + cgraph.h + PATH_SUFFIXES + graphviz + ) +mark_as_advanced(Graphviz_INCLUDE_DIR) + +find_library(Graphviz_CDT_LIBRARY + NAMES + cdt + ) +mark_as_advanced(Graphviz_CDT_LIBRARY) + +find_library(Graphviz_GVC_LIBRARY + NAMES + gvc + ) +mark_as_advanced(Graphviz_GVC_LIBRARY) + +find_library(Graphviz_CGRAPH_LIBRARY + NAMES + cgraph + ) +mark_as_advanced(Graphviz_CGRAPH_LIBRARY) + +find_library(Graphviz_PATHPLAN_LIBRARY + NAMES + pathplan + ) +mark_as_advanced(Graphviz_PATHPLAN_LIBRARY) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(Graphviz + REQUIRED_VARS + Graphviz_INCLUDE_DIR + Graphviz_CDT_LIBRARY + Graphviz_GVC_LIBRARY + Graphviz_CGRAPH_LIBRARY + Graphviz_PATHPLAN_LIBRARY +) + +if(Graphviz_FOUND) + set(Graphviz_INCLUDE_DIRS "${Graphviz_INCLUDE_DIR}") + set(Graphviz_LIBRARIES + "${Graphviz_CDT_LIBRARY}" + "${Graphviz_GVC_LIBRARY}" + "${Graphviz_CGRAPH_LIBRARY}" + "${Graphviz_PATHPLAN_LIBRARY}") + + if (NOT TARGET Graphviz::cdt) + add_library(Graphviz::cdt UNKNOWN IMPORTED) + set_target_properties(Graphviz::cdt PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${Graphviz_INCLUDE_DIR}" + IMPORTED_LOCATION "${Graphviz_CDT_LIBRARY}") + endif () + + if (NOT TARGET Graphviz::pathplan) + add_library(Graphviz::pathplan UNKNOWN IMPORTED) + set_target_properties(Graphviz::pathplan PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${Graphviz_INCLUDE_DIR}" + IMPORTED_LOCATION "${Graphviz_PATHPLAN_LIBRARY}") + endif () + + if (NOT TARGET Graphviz::cgraph) + add_library(Graphviz::cgraph UNKNOWN IMPORTED) + set_target_properties(Graphviz::cgraph PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${Graphviz_INCLUDE_DIR}" + IMPORTED_LOCATION "${Graphviz_CGRAPH_LIBRARY}" + INTERFACE_LINK_LIBRARIES "Graphviz::cdt") + endif () + + if (NOT TARGET Graphviz::gvc) + add_library(Graphviz::gvc UNKNOWN IMPORTED) + set_target_properties(Graphviz::gvc PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${Graphviz_INCLUDE_DIR}" + IMPORTED_LOCATION "${Graphviz_GVC_LIBRARY}" + INTERFACE_LINK_LIBRARIES "Graphviz::cdt;Graphviz::cgraph;Graphviz::pathplan") + endif () +endif() diff --git a/CMake/Options.h.in b/CMake/Options.h.in index c73dd2b41e..ef36d2f339 100644 --- a/CMake/Options.h.in +++ b/CMake/Options.h.in @@ -18,6 +18,9 @@ // Was SMTK built with VTK? If true, the smtkVTKExt library will exist. #cmakedefine SMTK_ENABLE_VTK_SUPPORT +// Was SMTK build with Graphviz support? It true, the task editor will lay out tasks. +#cmakedefine01 SMTK_ENABLE_GRAPHVIZ_SUPPORT + // Was SMTK built with GDAL support? This affects functionality in smtkVTKExt. #cmakedefine01 SMTK_ENABLE_GDAL_SUPPORT @@ -28,6 +31,7 @@ // Was SMTK built with Remus? If true, smtkRemoteSession library will exist. #cmakedefine SMTK_ENABLE_REMUS_SUPPORT + #define SMTK_INSTALL_PREFIX "@CMAKE_INSTALL_PREFIX@" #endif // smtk_Options_h diff --git a/CMakeLists.txt b/CMakeLists.txt index 14598f01b0..912726868c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -141,6 +141,11 @@ option(SMTK_BUILD_PRECOMPILED_HEADERS "Precompile headers to improve build times" OFF) mark_as_advanced(SMTK_BUILD_PRECOMPILED_HEADERS) +option(SMTK_ENABLE_GRAPHVIZ_SUPPORT "Use graphviz for task layout." OFF) +if (SMTK_ENABLE_GRAPHVIZ_SUPPORT) + find_package(Graphviz REQUIRED) +endif() + option(SMTK_ENABLE_GDAL_SUPPORT "Include I/O for GDAL. This requires ParaView/VTK built with GDAL." OFF) diff --git a/data/baseline/smtk/mesh/MeshSelection_5.png b/data/baseline/smtk/mesh/MeshSelection_5.png new file mode 100644 index 0000000000..f5815c72d9 --- /dev/null +++ b/data/baseline/smtk/mesh/MeshSelection_5.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:23464f395ecdcc07d8cfefcda26499fc743f37dbc42c11a71f631e6142ddb4ee +size 3712 diff --git a/data/baseline/smtk/mesh/OpenExodusFile_1.png b/data/baseline/smtk/mesh/OpenExodusFile_1.png new file mode 100644 index 0000000000..2b91567cb1 --- /dev/null +++ b/data/baseline/smtk/mesh/OpenExodusFile_1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:87c670d4b59e56e647a6344a3285b2be8c32054d680aa81d13aab902ef7b5a64 +size 2634 diff --git a/doc/release/notes/task-panel.rst b/doc/release/notes/task-panel.rst new file mode 100644 index 0000000000..d61dd96a6e --- /dev/null +++ b/doc/release/notes/task-panel.rst @@ -0,0 +1,17 @@ +User interface for task-based workflows +--------------------------------------- + +SMTK now provides a ParaView plugin which adds a :smtk:`task panel ` +similar to ParaView's node editor. Each task is shown as a node in a graph and +may be made active, marked complete (or incomplete), and manually placed by the user. +Each task may have a collection of style keywords associated with it and the +task manager holds settings for these keywords that affect the application state +when matching tasks are made active. + +In particular, SMTK's attribute-editor panel can be directed to display any view +held by attribute resource when a related :smtk:`smtk::task::FillOutAttributes` task +becomes active. + +This functionality is a preview and still under development; you should expect +changes to user-interface classes that may break backwards compatibility while +this development takes place. diff --git a/smtk/extension/paraview/appcomponents/CMakeLists.txt b/smtk/extension/paraview/appcomponents/CMakeLists.txt index 2eb8b23a31..34ab0be8e4 100644 --- a/smtk/extension/paraview/appcomponents/CMakeLists.txt +++ b/smtk/extension/paraview/appcomponents/CMakeLists.txt @@ -33,6 +33,7 @@ set(classes pqSMTKSaveResourceBehavior pqSMTKSelectionFilterBehavior pqSMTKSubtractUI + pqSMTKTaskPanel vtkSMTKEncodeSelection # For testing @@ -47,6 +48,7 @@ set(headers pqSMTKOperationParameterDock.h pqSMTKOperationToolboxDock.h pqSMTKResourceDock.h + pqSMTKTaskDock.h ) set(CMAKE_AUTOMOC 1) diff --git a/smtk/extension/paraview/appcomponents/plugin-gui/CMakeLists.txt b/smtk/extension/paraview/appcomponents/plugin-gui/CMakeLists.txt index 78cbfd75a1..d952daac2b 100644 --- a/smtk/extension/paraview/appcomponents/plugin-gui/CMakeLists.txt +++ b/smtk/extension/paraview/appcomponents/plugin-gui/CMakeLists.txt @@ -25,14 +25,20 @@ paraview_plugin_add_dock_window( DOCK_AREA Left INTERFACES resource_dock_interfaces SOURCES resource_dock_sources) +paraview_plugin_add_dock_window( + CLASS_NAME pqSMTKTaskDock + DOCK_AREA Left + INTERFACES task_dock_interfaces + SOURCES task_dock_sources) set(interfaces - ${auto_start_interfaces} + ${auto_start_interfaces} ${action_group_interfaces} ${toolbar_interfaces} ${proxy_interfaces} ${attribute_dock_interfaces} ${resource_dock_interfaces} + ${task_dock_interfaces} ) list(APPEND sources ${auto_start_sources} @@ -41,6 +47,7 @@ list(APPEND sources ${proxy_sources} ${attribute_dock_sources} ${resource_dock_sources} + ${task_dock_sources} ) smtk_add_plugin(smtkPQGuiComponentsPlugin diff --git a/smtk/extension/paraview/appcomponents/plugin-panel-defaults/DefaultConfiguration.cxx b/smtk/extension/paraview/appcomponents/plugin-panel-defaults/DefaultConfiguration.cxx index 9b5e18312b..c0c0b0ea3d 100644 --- a/smtk/extension/paraview/appcomponents/plugin-panel-defaults/DefaultConfiguration.cxx +++ b/smtk/extension/paraview/appcomponents/plugin-panel-defaults/DefaultConfiguration.cxx @@ -12,6 +12,8 @@ #include "smtk/extension/paraview/appcomponents/pqSMTKOperationToolboxPanel.h" #include "smtk/extension/paraview/appcomponents/pqSMTKResourceBrowser.h" #include "smtk/extension/paraview/appcomponents/pqSMTKResourcePanel.h" +#include "smtk/extension/paraview/appcomponents/pqSMTKTaskPanel.h" +#include "smtk/extension/qt/task/qtTaskEditor.h" #include "smtk/view/Configuration.h" #include "smtk/view/Information.h" #include "smtk/view/json/jsonView.h" @@ -44,11 +46,21 @@ smtk::view::Information DefaultConfiguration::panelConfiguration(const QWidget* else if (const auto* browser = dynamic_cast(panel)) { (void)browser; - // Use the default JSON configuration for the resource-browser panel. + // We provide a default configuration, but you can manipulate the + // panel or construct your own configuration as needed in your application. auto jsonConfig = nlohmann::json::parse(pqSMTKResourceBrowser::getJSONConfiguration())[0]; std::shared_ptr viewConfig = jsonConfig; result.insert_or_assign(viewConfig); } + else if (const auto* tasks = dynamic_cast(panel)) + { + // We provide a default configuration, but you can manipulate the + // panel or construct your own configuration as needed in your application. + (void)tasks; + std::shared_ptr viewConfig = + smtk::extension::qtTaskEditor::defaultConfiguration(); + result.insert_or_assign(viewConfig); + } else { smtkWarningMacro( diff --git a/smtk/extension/paraview/appcomponents/pqSMTKAppComponentsAutoStart.cxx b/smtk/extension/paraview/appcomponents/pqSMTKAppComponentsAutoStart.cxx index d4b8435036..c5e1b6da04 100644 --- a/smtk/extension/paraview/appcomponents/pqSMTKAppComponentsAutoStart.cxx +++ b/smtk/extension/paraview/appcomponents/pqSMTKAppComponentsAutoStart.cxx @@ -177,7 +177,6 @@ void pqSMTKAppComponentsAutoStart::shutdown() pqCore->unRegisterManager("call observers on main thread"); pqCore->unRegisterManager("smtk register importers"); pqCore->unRegisterManager("smtk pipeline selection sync"); - pqCore->unRegisterManager("smtk display attribute on load"); } } diff --git a/smtk/extension/paraview/appcomponents/pqSMTKAttributePanel.cxx b/smtk/extension/paraview/appcomponents/pqSMTKAttributePanel.cxx index 830a4b58ac..2520f8661c 100644 --- a/smtk/extension/paraview/appcomponents/pqSMTKAttributePanel.cxx +++ b/smtk/extension/paraview/appcomponents/pqSMTKAttributePanel.cxx @@ -22,6 +22,9 @@ #include "smtk/io/Logger.h" +#include "smtk/project/Manager.h" +#include "smtk/project/Project.h" + #include "smtk/resource/Manager.h" #include "smtk/resource/Observer.h" #include "smtk/resource/Properties.h" @@ -44,6 +47,7 @@ #include "vtkVector.h" #include +#include #include pqSMTKAttributePanel::pqSMTKAttributePanel(QWidget* parent) @@ -59,6 +63,20 @@ pqSMTKAttributePanel::pqSMTKAttributePanel(QWidget* parent) SIGNAL(postProcessingModeChanged(bool)), this, SLOT(displayActivePipelineSource(bool))); + QObject::connect( + behavior, + SIGNAL(addedManagerOnServer(pqSMTKWrapper*, pqServer*)), + this, + SLOT(observeProjectsOnServer(pqSMTKWrapper*, pqServer*))); + QObject::connect( + behavior, + SIGNAL(removingManagerFromServer(pqSMTKWrapper*, pqServer*)), + this, + SLOT(unobserveProjectsOnServer(pqSMTKWrapper*, pqServer*))); + behavior->visitResourceManagersOnServers([this](pqSMTKWrapper* wrapper, pqServer* server) { + this->observeProjectsOnServer(wrapper, server); + return false; // terminate early + }); auto* pqCore = pqApplicationCore::instance(); if (pqCore) { @@ -387,3 +405,128 @@ void pqSMTKAttributePanel::updateTitle(const smtk::view::ConfigurationPtr& view) this->setWindowTitle(panelName.c_str()); Q_EMIT titleChanged(panelName.c_str()); } + +void pqSMTKAttributePanel::observeProjectsOnServer(pqSMTKWrapper* mgr, pqServer* server) +{ + (void)server; + if (!mgr) + { + return; + } + auto projectManager = mgr->smtkProjectManager(); + if (!projectManager) + { + return; + } + + QPointer self(this); + auto observerKey = projectManager->observers().insert( + [self](const smtk::project::Project& project, smtk::project::EventType event) { + if (self) + { + self->handleProjectEvent(project, event); + } + }, + 0, // assign a neutral priority + true, // immediatelyNotify + "pqSMTKAttributePanel: Display new attribute project in panel."); + m_projectManagerObservers[projectManager] = std::move(observerKey); +} + +void pqSMTKAttributePanel::unobserveProjectsOnServer(pqSMTKWrapper* mgr, pqServer* server) +{ + (void)server; + if (!mgr) + { + return; + } + auto projectManager = mgr->smtkProjectManager(); + if (!projectManager) + { + return; + } + + auto entry = m_projectManagerObservers.find(projectManager); + if (entry != m_projectManagerObservers.end()) + { + projectManager->observers().erase(entry->second); + m_projectManagerObservers.erase(entry); + } +} + +void pqSMTKAttributePanel::handleProjectEvent( + const smtk::project::Project& project, + smtk::project::EventType event) +{ + auto* taskManager = const_cast(&project.taskManager()); + QPointer self(this); + switch (event) + { + case smtk::project::EventType::ADDED: + // observe the active task + // Use QTimer to wait until the event queue is emptied before trying this; + // that gives operations time to complete. Blech. + QTimer::singleShot(0, [this, taskManager, self]() { + if (!self) + { + return; + } + auto& activeTracker = taskManager->active(); + m_activeObserverKey = activeTracker.observers().insert( + [this, self](smtk::task::Task* oldTask, smtk::task::Task* newTask) { + if (!self) + { + return; + } + (void)oldTask; + if (newTask) + { + auto styles = newTask->style(); + for (const auto& style : styles) + { + auto styleConfig = newTask->manager()->getStyle(style); + // Does this style have a tag for us? + if (styleConfig.contains("attribute-panel")) + { + auto panelConfig = styleConfig.at("attribute-panel"); + if (panelConfig.contains("attribute-editor")) + { + auto viewName = panelConfig.at("attribute-editor").get(); + // std::cout << "Got view name " << viewName << std::endl; + if (auto rsrc = m_rsrc.lock()) + { + auto attrRsrc = std::dynamic_pointer_cast(rsrc); + smtk::view::ConfigurationPtr viewConfig = + attrRsrc ? attrRsrc->findView(viewName) : nullptr; + if (viewConfig) + { + self->resetPanel(attrRsrc->manager()); + // replace the contents with UI for this view. + self->displayResource(attrRsrc, viewConfig); + } + } + } + } + } + } + }, + "AttributePanel active task tracking"); + }); + break; + case smtk::project::EventType::REMOVED: + // stop observing active task + QTimer::singleShot(0, [this, taskManager, self]() { + if (!self) + { + return; + } + auto& activeTracker = taskManager->active(); + activeTracker.observers().erase(m_activeObserverKey); + }); + break; + case smtk::project::EventType::MODIFIED: + default: + // Do nothing. + break; + } +} diff --git a/smtk/extension/paraview/appcomponents/pqSMTKAttributePanel.h b/smtk/extension/paraview/appcomponents/pqSMTKAttributePanel.h index 5cba1f7bef..946628ec44 100644 --- a/smtk/extension/paraview/appcomponents/pqSMTKAttributePanel.h +++ b/smtk/extension/paraview/appcomponents/pqSMTKAttributePanel.h @@ -14,6 +14,7 @@ #include "smtk/extension/qt/qtUIManager.h" +#include "smtk/project/Observer.h" // for EventType #include "smtk/resource/Observer.h" #include "smtk/PublicPointerDefs.h" @@ -26,6 +27,7 @@ class pqServer; class pqPipelineSource; +class pqSMTKWrapper; /**\brief A panel that displays a single SMTK resource for editing by the user. * @@ -123,6 +125,16 @@ protected Q_SLOTS: */ virtual void displayActivePipelineSource(bool doDisplay); + /**\brief Track projects, react to the active task. + * + * These methods are used to add observers to each project loaded on each server + * so that changes to the active task of any can affect the attribute displayed + * in this panel. + */ + virtual void observeProjectsOnServer(pqSMTKWrapper* mgr, pqServer* server); + virtual void unobserveProjectsOnServer(pqSMTKWrapper* mgr, pqServer* server); + virtual void handleProjectEvent(const smtk::project::Project&, smtk::project::EventType); + protected: virtual bool displayResourceInternal( const smtk::attribute::ResourcePtr& rsrc, @@ -136,6 +148,9 @@ protected: smtk::operation::ManagerPtr m_opManager; smtk::resource::Observers::Key m_observer; pqPropertyLinks m_propertyLinks; + + std::map m_projectManagerObservers; + smtk::task::Active::Observers::Key m_activeObserverKey; }; #endif // smtk_extension_paraview_appcomponents_pqSMTKAttributePanel_h diff --git a/smtk/extension/paraview/appcomponents/pqSMTKOperationHintsBehavior.cxx b/smtk/extension/paraview/appcomponents/pqSMTKOperationHintsBehavior.cxx index 5bd3918216..6bce2883b1 100644 --- a/smtk/extension/paraview/appcomponents/pqSMTKOperationHintsBehavior.cxx +++ b/smtk/extension/paraview/appcomponents/pqSMTKOperationHintsBehavior.cxx @@ -12,10 +12,12 @@ #include "smtk/extension/paraview/appcomponents/pqSMTKBehavior.h" #include "smtk/extension/paraview/appcomponents/pqSMTKResourceDock.h" #include "smtk/extension/paraview/appcomponents/pqSMTKResourcePanel.h" +#include "smtk/extension/paraview/appcomponents/pqSMTKTaskPanel.h" #include "smtk/extension/paraview/appcomponents/pqSMTKWrapper.h" #include "smtk/extension/qt/qtDescriptivePhraseModel.h" #include "smtk/extension/qt/qtResourceBrowser.h" +#include "smtk/extension/qt/task/qtTaskEditor.h" #include "smtk/view/PhraseModel.h" #include "smtk/view/SelectionObserver.h" @@ -28,6 +30,8 @@ #include "smtk/attribute/ReferenceItem.h" +#include "pqApplicationCore.h" + #include #include @@ -257,6 +261,12 @@ int pqSMTKOperationHintsBehavior::processHints( { project->taskManager().active().switchTo(task.get()); } + if ( + auto* taskPanel = + dynamic_cast(pqApplicationCore::instance()->manager("smtk task panel"))) + { + taskPanel->taskPanel()->displayProject(project); + } }); return 0; @@ -424,25 +434,16 @@ void pqSMTKOperationHintsBehavior::unobserveWrapper(pqSMTKWrapper* wrapper, pqSe { (void)wrapper; auto oit = m_p->m_opObservers.find(server); + // The observers may not alwats exist (e.g., during tests whose first recorded + // action is to disconnect the pre-existing built-in server). If they do exist, + // remove them. if (oit != m_p->m_opObservers.end()) { m_p->m_opObservers.erase(oit); } - else - { - smtkWarningMacro( - smtk::io::Logger::instance(), - "No operation observer existed for server " << server << " being disconnected."); - } auto sit = m_p->m_selnObservers.find(server); if (sit != m_p->m_selnObservers.end()) { m_p->m_selnObservers.erase(sit); } - else - { - smtkWarningMacro( - smtk::io::Logger::instance(), - "No selection observer existed for server " << server << " being disconnected."); - } } diff --git a/smtk/extension/paraview/appcomponents/pqSMTKTaskDock.h b/smtk/extension/paraview/appcomponents/pqSMTKTaskDock.h new file mode 100644 index 0000000000..c2906dca92 --- /dev/null +++ b/smtk/extension/paraview/appcomponents/pqSMTKTaskDock.h @@ -0,0 +1,26 @@ +//========================================================================= +// Copyright (c) Kitware, Inc. +// All rights reserved. +// See LICENSE.txt for details. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notice for more information. +//========================================================================= +#ifndef smtk_extension_paraview_appcomponents_pqSMTKTaskDock_h +#define smtk_extension_paraview_appcomponents_pqSMTKTaskDock_h + +#include "smtk/extension/paraview/appcomponents/pqSMTKDock.h" +#include "smtk/extension/paraview/appcomponents/pqSMTKTaskPanel.h" + +class SMTKPQCOMPONENTSEXT_EXPORT pqSMTKTaskDock : public pqSMTKDock +{ + Q_OBJECT +public: + pqSMTKTaskDock(QWidget* parent = nullptr) + : pqSMTKDock("pqSMTKTaskDock", parent) + { + } + ~pqSMTKTaskDock() override = default; +}; +#endif // smtk_extension_paraview_appcomponents_pqSMTKTaskDock_h diff --git a/smtk/extension/paraview/appcomponents/pqSMTKTaskPanel.cxx b/smtk/extension/paraview/appcomponents/pqSMTKTaskPanel.cxx new file mode 100644 index 0000000000..c1737c452c --- /dev/null +++ b/smtk/extension/paraview/appcomponents/pqSMTKTaskPanel.cxx @@ -0,0 +1,166 @@ +//========================================================================= +// Copyright (c) Kitware, Inc. +// All rights reserved. +// See LICENSE.txt for details. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notice for more information. +//========================================================================= +#include "smtk/extension/paraview/appcomponents/pqSMTKTaskPanel.h" + +#include "smtk/extension/qt/task/qtTaskEditor.h" + +#include "smtk/extension/paraview/appcomponents/ApplicationConfiguration.h" +#include "smtk/extension/paraview/appcomponents/pqSMTKBehavior.h" +#include "smtk/extension/paraview/appcomponents/pqSMTKWrapper.h" + +#include "smtk/extension/qt/qtDescriptivePhraseModel.h" + +#include "smtk/io/Logger.h" + +#include "smtk/view/json/jsonView.h" + +#include "pqApplicationCore.h" + +#include + +pqSMTKTaskPanel::pqSMTKTaskPanel(QWidget* parent) + : Superclass(parent) +{ + this->setObjectName("pqSMTKTaskPanel"); + auto* pqCore = pqApplicationCore::instance(); + if (pqCore) + { + pqCore->registerManager("smtk task panel", this); + } + + // Either we get the application's configuration or we use a default + // until the application's configuration plugin is loaded. + bool immediatelyConfigured = false; + smtk::paraview::ApplicationConfiguration::notify( + [this, &immediatelyConfigured](smtk::paraview::ApplicationConfiguration& configurator) { + auto viewInfo = configurator.panelConfiguration(this); + // Extract just the view configuration. + auto viewConfig = viewInfo.get(); + if (viewConfig) + { + this->setView(viewConfig); + immediatelyConfigured = true; + } + }); + if (!immediatelyConfigured) + { + // Parse a json representation of our default config, and use it + // since the application can't immediately configure us. + smtk::view::ConfigurationPtr config = smtk::extension::qtTaskEditor::defaultConfiguration(); + this->setView(config); + } + + auto* smtkBehavior = pqSMTKBehavior::instance(); + // Now listen for future connections. + QObject::connect( + smtkBehavior, + SIGNAL(addedManagerOnServer(pqSMTKWrapper*, pqServer*)), + this, + SLOT(resourceManagerAdded(pqSMTKWrapper*, pqServer*))); + QObject::connect( + smtkBehavior, + SIGNAL(removingManagerFromServer(pqSMTKWrapper*, pqServer*)), + this, + SLOT(resourceManagerRemoved(pqSMTKWrapper*, pqServer*))); +} + +pqSMTKTaskPanel::~pqSMTKTaskPanel() +{ + auto* pqCore = pqApplicationCore::instance(); + if (pqCore) + { + pqCore->unRegisterManager("smtk task panel"); + } + delete m_viewUIMgr; + // m_viewUIMgr deletes m_taskPanel + // deletion of m_taskPanel->widget() is handled when parent widget is deleted. +} + +void pqSMTKTaskPanel::setView(const smtk::view::ConfigurationPtr& view) +{ + m_view = view; + + auto* smtkBehavior = pqSMTKBehavior::instance(); + + smtkBehavior->visitResourceManagersOnServers([this](pqSMTKWrapper* r, pqServer* s) { + this->resourceManagerAdded(r, s); + return false; + }); +} + +void pqSMTKTaskPanel::resourceManagerAdded(pqSMTKWrapper* wrapper, pqServer* server) +{ + if (!wrapper || !server) + { + return; + } + Q_EMIT this->titleChanged("Tasks"); + + smtk::resource::ManagerPtr rsrcMgr = wrapper->smtkResourceManager(); + smtk::view::ManagerPtr viewMgr = wrapper->smtkViewManager(); + if (!rsrcMgr || !viewMgr) + { + return; + } + if (m_viewUIMgr) + { + delete m_viewUIMgr; + m_viewUIMgr = nullptr; + // m_viewUIMgr deletes m_taskPanel, which deletes the container, which deletes child QWidgets. + m_taskPanel = nullptr; + } + + m_viewUIMgr = new smtk::extension::qtUIManager(rsrcMgr, viewMgr); + m_viewUIMgr->setOperationManager(wrapper->smtkOperationManager()); + m_viewUIMgr->setSelection(wrapper->smtkSelection()); + // m_viewUIMgr->setSelectionBit(1); // ToDo: should be set ? + + smtk::view::Information resinfo; + resinfo.insert(m_view); + resinfo.insert(this); + resinfo.insert(m_viewUIMgr); + + // the top-level "Type" in m_view should be qtTaskEditor or compatible. + auto* baseView = m_viewUIMgr->setSMTKView(resinfo); + m_taskPanel = dynamic_cast(baseView); + if (baseView && !m_taskPanel) + { + smtkErrorMacro(smtk::io::Logger::instance(), "Unsupported task panel type."); + return; + } + else if (!m_taskPanel) + { + return; + } + m_taskPanel->widget()->setObjectName("qtTaskEditor"); + std::string title; + m_view->details().attribute("Title", title); + if (title.empty()) + { + title = "Resources"; + } + this->setWindowTitle(title.c_str()); + Q_EMIT titleChanged(title.c_str()); + if (!m_layout) + { + m_layout = new QVBoxLayout; + m_layout->setObjectName("Layout"); + this->setLayout(m_layout); + } + m_layout->addWidget(m_taskPanel->widget()); +} + +void pqSMTKTaskPanel::resourceManagerRemoved(pqSMTKWrapper* mgr, pqServer* server) +{ + if (!mgr || !server) + { + return; + } +} diff --git a/smtk/extension/paraview/appcomponents/pqSMTKTaskPanel.h b/smtk/extension/paraview/appcomponents/pqSMTKTaskPanel.h new file mode 100644 index 0000000000..af86e515e7 --- /dev/null +++ b/smtk/extension/paraview/appcomponents/pqSMTKTaskPanel.h @@ -0,0 +1,60 @@ +//========================================================================= +// Copyright (c) Kitware, Inc. +// All rights reserved. +// See LICENSE.txt for details. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notice for more information. +//========================================================================= +#ifndef smtk_extension_paraview_appcomponents_pqSMTKTaskPanel_h +#define smtk_extension_paraview_appcomponents_pqSMTKTaskPanel_h + +#include "smtk/extension/paraview/appcomponents/smtkPQComponentsExtModule.h" + +#include "smtk/extension/qt/qtUIManager.h" +#include "smtk/extension/qt/task/qtTaskEditor.h" + +#include +#include + +#include "smtk/extension/paraview/appcomponents/pqQtKeywordWrapping.h" + +class pqSMTKWrapper; +class pqServer; +class QVBoxLayout; + +/**\brief A panel that displays SMTK tasks available to the user. + * + */ +class SMTKPQCOMPONENTSEXT_EXPORT pqSMTKTaskPanel : public QWidget +{ + Q_OBJECT + typedef QWidget Superclass; + +public: + pqSMTKTaskPanel(QWidget* parent = nullptr); + ~pqSMTKTaskPanel() override; + + /// Let the panel display a custom view config, from json or xml. + void setView(const smtk::view::ConfigurationPtr& view); + + /// Access the underlying resource browser. + smtk::extension::qtTaskEditor* taskPanel() const { return m_taskPanel; } + +Q_SIGNALS: + void titleChanged(QString title); + +protected Q_SLOTS: + virtual void resourceManagerAdded(pqSMTKWrapper* mgr, pqServer* server); + virtual void resourceManagerRemoved(pqSMTKWrapper* mgr, pqServer* server); + +protected: + smtk::extension::qtTaskEditor* m_taskPanel{ nullptr }; + smtk::view::ConfigurationPtr m_view; + smtk::extension::qtUIManager* m_viewUIMgr{ nullptr }; + /// The central widget's layout. + QPointer m_layout; +}; + +#endif // smtk_extension_paraview_appcomponents_pqSMTKTaskPanel_h diff --git a/smtk/extension/paraview/mesh/testing/xml/MeshSelection.xml b/smtk/extension/paraview/mesh/testing/xml/MeshSelection.xml index 751da2d8f7..e46d5bed19 100644 --- a/smtk/extension/paraview/mesh/testing/xml/MeshSelection.xml +++ b/smtk/extension/paraview/mesh/testing/xml/MeshSelection.xml @@ -8,6 +8,7 @@ + diff --git a/smtk/extension/paraview/project/CMakeLists.txt b/smtk/extension/paraview/project/CMakeLists.txt index 4a5d61c1ad..bba2c37d19 100644 --- a/smtk/extension/paraview/project/CMakeLists.txt +++ b/smtk/extension/paraview/project/CMakeLists.txt @@ -1,4 +1,5 @@ set(classes + pqSMTKDisplayProjectOnLoadBehavior pqSMTKProjectAutoStart pqSMTKProjectBrowser pqSMTKProjectMenu diff --git a/smtk/extension/paraview/project/plugin-core/CMakeLists.txt b/smtk/extension/paraview/project/plugin-core/CMakeLists.txt index 5dce9bb808..41bd735d1a 100644 --- a/smtk/extension/paraview/project/plugin-core/CMakeLists.txt +++ b/smtk/extension/paraview/project/plugin-core/CMakeLists.txt @@ -2,7 +2,7 @@ set(sources) set(interfaces) -smtk_add_plugin(smtkProjectPlugin +smtk_add_plugin(smtkPQCoreProjectPlugin REGISTRAR smtk::extension::paraview::project::Registrar MANAGERS smtk::common::Managers @@ -12,11 +12,12 @@ smtk_add_plugin(smtkProjectPlugin smtk::view::Manager PARAVIEW_PLUGIN_ARGS VERSION "1.0" + REQUIRED_PLUGINS smtkProjectPlugin UI_INTERFACES ${interfaces} SOURCES ${sources} ) -target_link_libraries(smtkProjectPlugin +target_link_libraries(smtkPQCoreProjectPlugin PRIVATE ParaView::pqApplicationComponents smtkCore diff --git a/smtk/extension/paraview/project/plugin-core/paraview.plugin b/smtk/extension/paraview/project/plugin-core/paraview.plugin index 8cc954cd18..32594ebec0 100644 --- a/smtk/extension/paraview/project/plugin-core/paraview.plugin +++ b/smtk/extension/paraview/project/plugin-core/paraview.plugin @@ -1,4 +1,4 @@ NAME - smtkProjectPlugin + smtkPQCoreProjectPlugin DESCRIPTION SMTK project core plugin for ParaView diff --git a/smtk/extension/paraview/project/plugin-gui/CMakeLists.txt b/smtk/extension/paraview/project/plugin-gui/CMakeLists.txt index ad3126aef1..47b8c4e4ee 100644 --- a/smtk/extension/paraview/project/plugin-gui/CMakeLists.txt +++ b/smtk/extension/paraview/project/plugin-gui/CMakeLists.txt @@ -20,14 +20,14 @@ set(interfaces ${dock_interfaces} ) -paraview_add_plugin(smtkPQProjectPlugin +paraview_add_plugin(smtkPQGuiProjectPlugin VERSION "1.0" - REQUIRED_PLUGINS smtkProjectPlugin + REQUIRED_PLUGINS smtkPQCoreProjectPlugin UI_INTERFACES ${interfaces} SOURCES ${sources} ) -target_link_libraries(smtkPQProjectPlugin +target_link_libraries(smtkPQGuiProjectPlugin PRIVATE ParaView::pqApplicationComponents smtkCore @@ -35,4 +35,4 @@ target_link_libraries(smtkPQProjectPlugin smtkPQProjectExt smtkPVServerExt ) -target_compile_definitions(smtkPQProjectPlugin PRIVATE QT_NO_KEYWORDS) +target_compile_definitions(smtkPQGuiProjectPlugin PRIVATE QT_NO_KEYWORDS) diff --git a/smtk/extension/paraview/project/plugin-gui/paraview.plugin b/smtk/extension/paraview/project/plugin-gui/paraview.plugin index 7750c5575f..c7ba40ea0f 100644 --- a/smtk/extension/paraview/project/plugin-gui/paraview.plugin +++ b/smtk/extension/paraview/project/plugin-gui/paraview.plugin @@ -1,4 +1,4 @@ NAME - smtkPQProjectPlugin + smtkPQGuiProjectPlugin DESCRIPTION - SMTK project gui plugin for ParaView + SMTK project GUI plugin for ParaView diff --git a/smtk/extension/paraview/project/pqSMTKDisplayProjectOnLoadBehavior.cxx b/smtk/extension/paraview/project/pqSMTKDisplayProjectOnLoadBehavior.cxx new file mode 100644 index 0000000000..ed58797b55 --- /dev/null +++ b/smtk/extension/paraview/project/pqSMTKDisplayProjectOnLoadBehavior.cxx @@ -0,0 +1,169 @@ +//========================================================================= +// Copyright (c) Kitware, Inc. +// All rights reserved. +// See LICENSE.txt for details. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notice for more information. +//========================================================================= +#include "smtk/extension/paraview/project/pqSMTKDisplayProjectOnLoadBehavior.h" + +// SMTK +#include "smtk/extension/paraview/appcomponents/pqSMTKBehavior.h" +#include "smtk/extension/paraview/appcomponents/pqSMTKTaskPanel.h" +#include "smtk/extension/paraview/appcomponents/pqSMTKWrapper.h" + +#include "smtk/extension/paraview/server/vtkSMSMTKWrapperProxy.h" + +#include "smtk/view/Selection.h" + +#include "smtk/project/Manager.h" +#include "smtk/project/Project.h" + +#include "smtk/io/Logger.h" + +// Client side +#include "pqApplicationCore.h" +#include "pqCoreUtilities.h" +#include "pqLiveInsituManager.h" +#include "pqPipelineSource.h" +#include "pqSelectionManager.h" +#include "pqServerManagerModel.h" + +// Qt +#include +#include +#include + +using namespace smtk; + +static pqSMTKDisplayProjectOnLoadBehavior* g_displayOnLoad = nullptr; + +pqSMTKDisplayProjectOnLoadBehavior::pqSMTKDisplayProjectOnLoadBehavior(QObject* parent) + : Superclass(parent) +{ + if (!g_displayOnLoad) + { + g_displayOnLoad = this; + } + + // Track server connects/disconnects + auto* projectBehavior = pqSMTKBehavior::instance(); + QObject::connect( + projectBehavior, + SIGNAL(addedManagerOnServer(vtkSMSMTKWrapperProxy*, pqServer*)), + this, + SLOT(observeProjectsOnServer(vtkSMSMTKWrapperProxy*, pqServer*))); + QObject::connect( + projectBehavior, + SIGNAL(removingManagerFromServer(vtkSMSMTKWrapperProxy*, pqServer*)), + this, + SLOT(unobserveProjectsOnServer(vtkSMSMTKWrapperProxy*, pqServer*))); +} + +pqSMTKDisplayProjectOnLoadBehavior::~pqSMTKDisplayProjectOnLoadBehavior() +{ + if (g_displayOnLoad == this) + { + g_displayOnLoad = nullptr; + } +} + +pqSMTKDisplayProjectOnLoadBehavior* pqSMTKDisplayProjectOnLoadBehavior::instance(QObject* parent) +{ + if (!g_displayOnLoad) + { + g_displayOnLoad = new pqSMTKDisplayProjectOnLoadBehavior(parent); + } + + return g_displayOnLoad; +} + +void pqSMTKDisplayProjectOnLoadBehavior::observeProjectsOnServer( + vtkSMSMTKWrapperProxy* mgr, + pqServer* server) +{ + (void)server; + if (!mgr) + { + vtkGenericWarningMacro("No wrapper."); + return; + } + auto projectManager = mgr->GetProjectManager(); + if (!projectManager) + { + vtkGenericWarningMacro("No project manager to observe."); + return; + } + + auto observerKey = projectManager->observers().insert( + [this](const smtk::project::Project& project, smtk::project::EventType event) { + this->handleProjectEvent(project, event); + }, + 0, // assign a neutral priority + true, // immediatelyNotify + "pqSMTKDisplayProjectOnLoadBehavior: Display new attribute project in panel."); + m_projectManagerObservers[projectManager] = std::move(observerKey); +} + +void pqSMTKDisplayProjectOnLoadBehavior::unobserveProjectsOnServer( + vtkSMSMTKWrapperProxy* mgr, + pqServer* server) +{ + (void)server; + if (!mgr) + { + return; + } + auto projectManager = mgr->GetProjectManager(); + if (!projectManager) + { + return; + } + + auto entry = m_projectManagerObservers.find(projectManager); + if (entry != m_projectManagerObservers.end()) + { + projectManager->observers().erase(entry->second); + m_projectManagerObservers.erase(entry); + } +} + +void pqSMTKDisplayProjectOnLoadBehavior::handleProjectEvent( + const smtk::project::Project& project, + smtk::project::EventType event) +{ + auto* taskManager = const_cast(&project.taskManager()); + switch (event) + { + case smtk::project::EventType::ADDED: + this->focusTaskPanel(taskManager); + break; + case smtk::project::EventType::REMOVED: + this->focusTaskPanel(nullptr); + break; + case smtk::project::EventType::MODIFIED: + default: + // Do nothing. + break; + } +} + +void pqSMTKDisplayProjectOnLoadBehavior::focusTaskPanel(smtk::task::Manager* taskManager) +{ + // Use QTimer to wait until the event queue is emptied before trying this; + // that gives operations time to complete. Blech. + QTimer::singleShot(0, [this, taskManager]() { + auto* core = pqApplicationCore::instance(); + auto* panel = dynamic_cast(core->manager("smtk task panel")); + if (panel) + { + panel->taskPanel()->displayTaskManager(taskManager); + if (auto* parent = dynamic_cast(panel->parent())) + { + parent->raise(); // Make sure the widget is visible and raised. + } + } + }); +} diff --git a/smtk/extension/paraview/project/pqSMTKDisplayProjectOnLoadBehavior.h b/smtk/extension/paraview/project/pqSMTKDisplayProjectOnLoadBehavior.h new file mode 100644 index 0000000000..3b9f1ea264 --- /dev/null +++ b/smtk/extension/paraview/project/pqSMTKDisplayProjectOnLoadBehavior.h @@ -0,0 +1,73 @@ +//========================================================================= +// Copyright (c) Kitware, Inc. +// All rights reserved. +// See LICENSE.txt for details. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notice for more information. +//========================================================================= +#ifndef smtk_extension_paraview_project_pqSMTKDisplayProjectOnLoadBehavior_h +#define smtk_extension_paraview_project_pqSMTKDisplayProjectOnLoadBehavior_h + +#include "smtk/extension/paraview/project/smtkPQProjectExtModule.h" + +#include "smtk/PublicPointerDefs.h" + +#include "smtk/project/Observer.h" // for EventType + +#include "smtk/extension/paraview/appcomponents/pqQtKeywordWrapping.h" + +#include + +#include + +class vtkSMSMTKWrapperProxy; + +class pqServer; + +namespace smtk +{ +namespace task +{ +class Manager; +} +} // namespace smtk + +/**\brief Make the SMTK task panel display a project when one is loaded. + * + * This is accomplished by adding an observer to the application's project manager. + * When a project is loaded, the task panel is made visible and raised to the top + * (if in a tabbed dock). + */ +class SMTKPQPROJECTEXT_EXPORT pqSMTKDisplayProjectOnLoadBehavior : public QObject +{ + Q_OBJECT + using Superclass = QObject; + +public: + pqSMTKDisplayProjectOnLoadBehavior(QObject* parent = nullptr); + ~pqSMTKDisplayProjectOnLoadBehavior() override; + + /// This behavior is a singleton. + static pqSMTKDisplayProjectOnLoadBehavior* instance(QObject* parent = nullptr); + +protected Q_SLOTS: + //@{ + /// Observe server connections/disconnections so we can monitor each server's + /// projects for updates and, on project load/creation, force the task panel + /// to the top. + virtual void observeProjectsOnServer(vtkSMSMTKWrapperProxy* mgr, pqServer* server); + virtual void unobserveProjectsOnServer(vtkSMSMTKWrapperProxy* mgr, pqServer* server); + virtual void handleProjectEvent(const smtk::project::Project&, smtk::project::EventType); + virtual void focusTaskPanel(smtk::task::Manager* taskManager); + //@} + +protected: + std::map m_projectManagerObservers; + +private: + Q_DISABLE_COPY(pqSMTKDisplayProjectOnLoadBehavior); +}; + +#endif diff --git a/smtk/extension/paraview/project/pqSMTKProjectAutoStart.cxx b/smtk/extension/paraview/project/pqSMTKProjectAutoStart.cxx index a5747d2a7e..dc46dfc299 100644 --- a/smtk/extension/paraview/project/pqSMTKProjectAutoStart.cxx +++ b/smtk/extension/paraview/project/pqSMTKProjectAutoStart.cxx @@ -11,6 +11,7 @@ #include "smtk/extension/paraview/appcomponents/pqSMTKResourceDock.h" #include "smtk/extension/paraview/appcomponents/pqSMTKResourcePanel.h" +#include "smtk/extension/paraview/project/pqSMTKDisplayProjectOnLoadBehavior.h" #include "smtk/extension/paraview/project/pqSMTKProjectMenu.h" #include "smtk/project/Project.h" #include "smtk/view/ResourcePhraseModel.h" @@ -67,10 +68,12 @@ pqSMTKProjectAutoStart::~pqSMTKProjectAutoStart() = default; void pqSMTKProjectAutoStart::startup() { auto* projectMenuMgr = pqSMTKProjectMenu::instance(this); + auto* displayProjectOnLoad = pqSMTKDisplayProjectOnLoadBehavior::instance(this); auto* pqCore = pqApplicationCore::instance(); if (pqCore) { + pqCore->registerManager("smtk display project on load", displayProjectOnLoad); pqCore->registerManager("smtk project menu", projectMenuMgr); } @@ -84,6 +87,7 @@ void pqSMTKProjectAutoStart::shutdown() auto* pqCore = pqApplicationCore::instance(); if (pqCore) { + pqCore->unRegisterManager("smtk display attribute on load"); pqCore->unRegisterManager("smtk project menu"); } } diff --git a/smtk/extension/qt/CMakeLists.txt b/smtk/extension/qt/CMakeLists.txt index 60ce53b4b5..5aa10d5f8a 100644 --- a/smtk/extension/qt/CMakeLists.txt +++ b/smtk/extension/qt/CMakeLists.txt @@ -68,6 +68,14 @@ set(QAttrLibSrcs SVGIconEngine.cxx TypeAndColorBadge.cxx VisibilityBadge.cxx + + task/qtTaskArc.cxx + task/qtTaskEditor.cxx + task/qtTaskNode.cxx + task/qtTaskScene.cxx + task/qtTaskView.cxx + task/qtTaskViewConfiguration.cxx + task/TaskEditorState.cxx ) set(QAttrLibUIs @@ -78,6 +86,7 @@ set(QAttrLibUIs qtNewAttributeWidget.ui qtTimeZoneSelectWidget.ui qtViewInfoDialog.ui + task/TaskNode.ui ) @@ -147,6 +156,14 @@ set(QAttrLibMocHeaders MembershipBadge.h SVGIconEngine.h VisibilityBadge.h + + task/qtTaskArc.h + task/qtTaskEditor.h + task/qtTaskNode.h + task/qtTaskScene.h + task/qtTaskView.h + task/qtTaskViewConfiguration.h + task/TaskEditorState.h ) set(QAttrLibHeaders @@ -164,11 +181,18 @@ set(QAttrLibHeaders qtViewRegistrar.h ) -# put contents of this file in a string in a header, ending _json.h +# Put the contents of this file in a string in a header, ending in `_json.h`. smtk_encode_file("${CMAKE_CURRENT_SOURCE_DIR}/ResourcePanelConfiguration.json" TYPE "_json" HEADER_OUTPUT rpJsonHeader) +# Put the contents of this file in a *function* in a header, ending in `_cpp.h`. +smtk_encode_file("${CMAKE_CURRENT_SOURCE_DIR}/task/PanelConfiguration.json" + NAME "taskPanelConfiguration" + TYPE "_cpp" + HEADER_OUTPUT tpXmlHeader +) + #install the headers smtk_public_headers(smtkQtExt ${QAttrLibHeaders}) @@ -184,6 +208,17 @@ add_library(smtkQtExt qtReferenceItemIcons.qrc qtAttributeIcons.qrc ${rpJsonHeader} + ${tpXmlHeader} +) + +if (SMTK_ENABLE_GRAPHVIZ_SUPPORT) + list(APPEND _extra_private_libraries + Graphviz::cgraph + Graphviz::gvc + ) +endif() +target_compile_definitions(smtkQtExt PRIVATE + "SMTK_ENABLE_GRAPHVIZ_SUPPORT=$" ) if (NOT SMTK_ENABLE_OPERATION_THREADS) @@ -203,6 +238,7 @@ target_link_libraries(smtkQtExt Qt5::Widgets LINK_PRIVATE Threads::Threads + ${_extra_private_libraries} ) # add_dependencies(smtkQtExt ${rpJsonTarget}) diff --git a/smtk/extension/qt/qtViewRegistrar.cxx b/smtk/extension/qt/qtViewRegistrar.cxx index 50bf665b9b..49ba14bd4e 100644 --- a/smtk/extension/qt/qtViewRegistrar.cxx +++ b/smtk/extension/qt/qtViewRegistrar.cxx @@ -25,6 +25,7 @@ #include "smtk/extension/qt/qtResourceBrowser.h" #include "smtk/extension/qt/qtSelectorView.h" #include "smtk/extension/qt/qtSimpleExpressionView.h" +#include "smtk/extension/qt/task/qtTaskEditor.h" #include @@ -34,7 +35,7 @@ namespace extension { namespace { -typedef std::tuple< +using ViewWidgetList = std::tuple< qtAnalysisView, qtAssociationView, qtAttributeView, @@ -46,8 +47,8 @@ typedef std::tuple< qtOperationPalette, qtResourceBrowser, qtSelectorView, - qtSimpleExpressionView> - ViewWidgetList; + qtSimpleExpressionView, + qtTaskEditor>; using BadgeList = std::tuple; } // namespace @@ -70,6 +71,7 @@ void qtViewRegistrar::registerTo(const smtk::view::Manager::Ptr& manager) manager->viewWidgetFactory().addAlias("ModelEntity"); manager->viewWidgetFactory().addAlias("ComponentAttribute"); manager->viewWidgetFactory().addAlias("ResourceBrowser"); + manager->viewWidgetFactory().addAlias("TaskEditor"); manager->badgeFactory().registerTypes(); } diff --git a/smtk/extension/qt/task/PanelConfiguration.json b/smtk/extension/qt/task/PanelConfiguration.json new file mode 100644 index 0000000000..29e0bc0f94 --- /dev/null +++ b/smtk/extension/qt/task/PanelConfiguration.json @@ -0,0 +1,15 @@ +[ + { + "Name": "TaskView", + "Type": "TaskEditor", + "Component": { + "Name": "Details", + "Attributes": { + "Title": "Tasks", + "TopLevel": true, + "Widget": "TaskGraph", + "SearchBar": true + } + } + } +] diff --git a/smtk/extension/qt/task/PanelConfiguration.spec.json b/smtk/extension/qt/task/PanelConfiguration.spec.json new file mode 100644 index 0000000000..69eda5f0b4 --- /dev/null +++ b/smtk/extension/qt/task/PanelConfiguration.spec.json @@ -0,0 +1,78 @@ +[ + "// The object below is the full set of panel configuration options accepted.", + "// Several of the colors should not be specified by workflow developers (such as", + "// BacgroundFill) since they will override user-selected themes (e.g. dark mode).", + { + "Name": "TaskView", + "Type": "TaskEditor", + "Component": { + "Name": "Details", + "Attributes": { + "Title": "Tasks", + "TopLevel": true, + "Widget": "TaskGraph", + "SearchBar": true + }, + "Children": [ + { + "Name": "Style", + "Attributes": { + "NodeInfo": "compact", + "DependencyArcs": true, + "AdaptorArcs": true + }, + "Children": [ + { + "Name": "NodeLayout", + "Attributes": { + "Width": 300.0, + "Radius": 4.0, + "HeadlineHeight": 13.0, + "HeadlinePadding": 4.0, + "BorderThickness": 4.0, + "FontSize": 13, + "Layer": 10 + } + }, + { + "Name": "ArcLayout", + "Attributes": { + "Width": 4.0, + "Outline": 1.0, + "Layer": 5, + "ArrowStemLength": 16.0, + "ArrowHeadLength": 12.0, + "ArrowTipAspectRatio": 2.0 + } + }, + { + "Name": "ArcPalette", + "Attributes": { + "Dependency": "#BF5B17", + "Adaptor": "#386CB0" + } + }, + { + "Name": "StatusPalette", + "Attributes": { + "Unavailable": "#ff9898", + "Incomplete": "#ffeca3", + "Completable": "#e0f1ba", + "Complete": "#7ed637", + "Irrelevant": "#e7e7e7" + } + }, + { + "Name": "ViewPalette", + "Attributes": { + "BackgroundGrid": "#cccccc", + "BackgroundFill": "#ffffff", + "ActiveTask": "#aaaaff" + } + } + ] + } + ] + } + } +] diff --git a/smtk/extension/qt/task/TaskEditorState.cxx b/smtk/extension/qt/task/TaskEditorState.cxx new file mode 100644 index 0000000000..beb61931bb --- /dev/null +++ b/smtk/extension/qt/task/TaskEditorState.cxx @@ -0,0 +1,39 @@ +//========================================================================= +// Copyright (c) Kitware, Inc. +// All rights reserved. +// See LICENSE.txt for details. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notice for more information. +//========================================================================= +#include "smtk/extension/qt/task/TaskEditorState.h" + +#include "smtk/extension/qt/task/qtTaskEditor.h" +#include "smtk/task/Task.h" + +#include "nlohmann/json.hpp" + +namespace smtk +{ +namespace extension +{ + +TaskEditorState::TaskEditorState(qtTaskEditor* taskEditor) + : m_editor(taskEditor) +{ +} + +nlohmann::json TaskEditorState::globalState() const +{ + return nlohmann::json(); +} + +nlohmann::json TaskEditorState::taskState(const std::shared_ptr& task) const +{ + (void)task; + return m_editor->uiStateForTask(task.get()); +} + +} // namespace extension +} // namespace smtk diff --git a/smtk/extension/qt/task/TaskEditorState.h b/smtk/extension/qt/task/TaskEditorState.h new file mode 100644 index 0000000000..ed270a0694 --- /dev/null +++ b/smtk/extension/qt/task/TaskEditorState.h @@ -0,0 +1,43 @@ +//========================================================================= +// Copyright (c) Kitware, Inc. +// All rights reserved. +// See LICENSE.txt for details. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notice for more information. +//========================================================================= +#ifndef smtk_extension_TaskEditorState_h +#define smtk_extension_TaskEditorState_h + +#include "smtk/extension/qt/Exports.h" + +#include "smtk/task/UIStateGenerator.h" + +namespace smtk +{ +namespace extension +{ + +class qtTaskEditor; + +/** \brief A widget that holds a Qt scene graph. */ +class SMTKQTEXT_EXPORT TaskEditorState : public smtk::task::UIStateGenerator +{ +public: + TaskEditorState(qtTaskEditor* taskEditor); + ~TaskEditorState() = default; + + /// Provide any global state for the task-editor that should be saved across sessions. + nlohmann::json globalState() const override; + /// Provide UI state for the given task so it can be saved across sessions. + nlohmann::json taskState(const std::shared_ptr& task) const override; + +protected: + qtTaskEditor* m_editor{ nullptr }; +}; + +} // namespace extension +} // namespace smtk + +#endif diff --git a/smtk/extension/qt/task/TaskNode.ui b/smtk/extension/qt/task/TaskNode.ui new file mode 100644 index 0000000000..280858b135 --- /dev/null +++ b/smtk/extension/qt/task/TaskNode.ui @@ -0,0 +1,185 @@ + + + TaskNode + + + + 0 + 0 + 171 + 80 + + + + Form + + + + + + + + + + + + + 192 + 191 + 188 + + + + + + + 119 + 118 + 123 + + + + + + + + + 192 + 191 + 188 + + + + + + + 119 + 118 + 123 + + + + + + + + + 146 + 149 + 149 + + + + + + + 146 + 149 + 149 + + + + + + + + + 13 + 75 + true + + + + ClosedHandCursor + + + + + + + + + + + 0 + 0 + + + + + + + + + 119 + 118 + 123 + + + + + + + + + 119 + 118 + 123 + + + + + + + + + 119 + 118 + 123 + + + + + + + + + 13 + 75 + true + + + + Task title + + + QToolButton::MenuButtonPopup + + + Qt::ToolButtonTextBesideIcon + + + + + + + + + + 0 + 0 + + + + Controls + + + true + + + + + + + + diff --git a/smtk/extension/qt/task/qtTaskArc.cxx b/smtk/extension/qt/task/qtTaskArc.cxx new file mode 100644 index 0000000000..532f791a79 --- /dev/null +++ b/smtk/extension/qt/task/qtTaskArc.cxx @@ -0,0 +1,311 @@ +//========================================================================= +// Copyright (c) Kitware, Inc. +// All rights reserved. +// See LICENSE.txt for details. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notice for more information. +//========================================================================= +#include "smtk/extension/qt/task/qtTaskArc.h" + +#include "smtk/extension/qt/qtBaseView.h" +#include "smtk/extension/qt/task/qtTaskNode.h" +#include "smtk/extension/qt/task/qtTaskScene.h" +#include "smtk/extension/qt/task/qtTaskViewConfiguration.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +class QAbstractItemModel; +class QItemSelection; +class QTreeView; + +namespace smtk +{ +namespace extension +{ + +qtTaskArc::qtTaskArc( + qtTaskScene* scene, + qtTaskNode* predecessor, + qtTaskNode* successor, + ArcType arcType, + QGraphicsItem* parent) + : Superclass(parent) + , m_scene(scene) + , m_predecessor(predecessor) + , m_successor(successor) + , m_arcType(arcType) +{ + const auto& cfg(*m_scene->configuration()); + QObject::connect( + m_predecessor, &qtTaskNode::nodeMovedImmediate, this, &qtTaskArc::updateArcPoints); + QObject::connect(m_predecessor, &qtTaskNode::nodeResized, this, &qtTaskArc::updateArcPoints); + QObject::connect(m_successor, &qtTaskNode::nodeMovedImmediate, this, &qtTaskArc::updateArcPoints); + QObject::connect(m_successor, &qtTaskNode::nodeResized, this, &qtTaskArc::updateArcPoints); + this->setAcceptedMouseButtons(Qt::NoButton); + + this->updateArcPoints(); + + m_scene->addItem(this); + + // === Task-specific constructor === + this->setZValue(cfg.arcLayer() + 1); // Draw dependencies on top of adaptors +} + +qtTaskArc::qtTaskArc( + qtTaskScene* scene, + qtTaskNode* predecessor, + qtTaskNode* successor, + smtk::task::Adaptor* adaptor, + QGraphicsItem* parent) + : Superclass(parent) + , m_scene(scene) + , m_predecessor(predecessor) + , m_successor(successor) + , m_adaptor(adaptor) + , m_arcType(ArcType::Adaptor) +{ + const auto& cfg(*m_scene->configuration()); + QObject::connect(m_predecessor, &qtTaskNode::nodeMoved, this, &qtTaskArc::updateArcPoints); + QObject::connect(m_predecessor, &qtTaskNode::nodeResized, this, &qtTaskArc::updateArcPoints); + QObject::connect(m_successor, &qtTaskNode::nodeMoved, this, &qtTaskArc::updateArcPoints); + QObject::connect(m_successor, &qtTaskNode::nodeResized, this, &qtTaskArc::updateArcPoints); + this->setAcceptedMouseButtons(Qt::NoButton); + + this->updateArcPoints(); + + m_scene->addItem(this); + + // === Task-specific constructor === + this->setZValue(cfg.arcLayer()); +} + +qtTaskArc::~qtTaskArc() +{ + m_scene->removeItem(this); +} + +QRectF qtTaskArc::boundingRect() const +{ + const auto& cfg(*m_scene->configuration()); + const qreal margin = cfg.arcWidth() + cfg.arcOutline(); + + QRectF pb = this->path().boundingRect(); + return pb.adjusted(-margin, -margin, margin, margin); +} + +qtTaskArc::ArcType qtTaskArc::arcTypeEnum(const std::string& enumerant, bool* match) +{ + if (match) + { + *match = true; + } + std::string arcTypeName(enumerant); + std::transform(arcTypeName.begin(), arcTypeName.end(), arcTypeName.begin(), [](unsigned char c) { + return std::tolower(c); + }); + if (arcTypeName.substr(0, 6) == "smtk::") + { + arcTypeName = arcTypeName.substr(6); + } + if (arcTypeName.substr(0, 11) == "extension::") + { + arcTypeName = arcTypeName.substr(11); + } + if (arcTypeName.substr(0, 11) == "qttaskarc::") + { + arcTypeName = arcTypeName.substr(11); + } + if (arcTypeName.substr(0, 9) == "arctype::") + { + arcTypeName = arcTypeName.substr(9); + } + if (arcTypeName == "dependency") + { + return ArcType::Dependency; + } + if (arcTypeName == "adaptor") + { + return ArcType::Adaptor; + } + if (match) + { + *match = (arcTypeName == "dependency"); + } + return ArcType::Dependency; +} + +std::string qtTaskArc::arcTypeName(ArcType enumerant) +{ + switch (enumerant) + { + case ArcType::Dependency: + return "dependency"; + case ArcType::Adaptor: + return "adaptor"; + } + return "unknown"; +} + +int qtTaskArc::updateArcPoints() +{ + const auto& cfg(*m_scene->configuration()); + this->prepareGeometryChange(); + m_computedPath.clear(); + m_arrowPath.clear(); + + auto predRect = m_predecessor->boundingRect(); + predRect = m_predecessor->mapRectToScene(predRect); + auto succRect = m_successor->boundingRect(); + succRect = m_successor->mapRectToScene(succRect); + + // If the nodes overlap, there is no arc to draw. + if (predRect.intersects(succRect)) + { + this->setPath(m_computedPath); + return 1; + } + + // Determine a line connecting the node centers. + auto pc = predRect.center(); + auto sc = succRect.center(); + auto dl = sc - pc; // delta. Line is L(t) = pc + dl * t, t ∈ [0,1]. + auto dp = predRect.bottomRight() - pc; // Always positive along both axes. + + // Find point, pi, on boundary of predRect and on line bet. pc and sc + qreal tx = dp.x() / std::abs(dl.x()); + qreal ty = dp.y() / std::abs(dl.y()); + QPointF pi; + pi = pc + (tx < ty ? tx : ty) * dl; + // ni is the "normal" to the node at the intersection point pi. + QPointF ni( + tx < ty ? (std::signbit(dl.x()) ? -1. : +1.) : 0., + tx < ty ? 0. : (std::signbit(dl.y()) ? -1. : +1)); + QPointF nearestCorner( + dl.x() < 0. ? (dl.y() < 0. ? predRect.topLeft() : predRect.bottomLeft()) + : (dl.y() < 0. ? predRect.topRight() : predRect.bottomRight())); + QPointF cornerVector(nearestCorner - pi); + // Make the normal transition smoothly near corners: + if ( + (tx >= ty && std::abs(cornerVector.x()) < cfg.arrowStemLength()) || + (tx < ty && std::abs(cornerVector.y()) < cfg.arrowStemLength())) + { + ni = pi - + (nearestCorner + QPointF(std::signbit(dl.x()) ? +16 : -16, std::signbit(dl.y()) ? +16 : -16)); + qreal invMag = 1.0 / std::sqrt(QPointF::dotProduct(ni, ni)); + ni = invMag * ni; + + // Adjust the intersection point to deal with the rounded rectangle corner: + if ( + (tx >= ty && std::abs(cornerVector.x()) < cfg.nodeRadius()) || + (tx < ty && std::abs(cornerVector.y()) < cfg.nodeRadius())) + { + QPointF centerOfCurvature = + nearestCorner + QPointF(std::signbit(dl.x()) ? +4 : -4, std::signbit(dl.y()) ? +4 : -4); + QPointF tmp = pi - centerOfCurvature; + qreal tmpMag = std::sqrt(QPointF::dotProduct(tmp, tmp)); + pi = centerOfCurvature + (4.0 / tmpMag) * tmp; + } + } + + // Now, telescope a couple points out along the normal. + QPointF pi1 = pi + ni * cfg.arrowStemLength(); + QPointF pi2 = pi + ni * cfg.arrowStemLength() * 2; + + // Repeat the above, but with the successor node (so dl is reversed). + dl = -1.0 * dl; + auto ds = succRect.bottomRight() - sc; // Always positive along both axes. + + tx = ds.x() / std::abs(dl.x()); + ty = ds.y() / std::abs(dl.y()); + QPointF si; + si = sc + (tx < ty ? tx : ty) * dl; + // ni is the "normal" to the node at the intersection point si. + ni = QPointF( + tx < ty ? (std::signbit(dl.x()) ? -1. : +1.) : 0., + tx < ty ? 0. : (std::signbit(dl.y()) ? -1. : +1)); + nearestCorner = QPointF( + dl.x() < 0. ? (dl.y() < 0. ? succRect.topLeft() : succRect.bottomLeft()) + : (dl.y() < 0. ? succRect.topRight() : succRect.bottomRight())); + cornerVector = QPointF(nearestCorner - si); + // Make the normal transition smoothly near corners: + if ( + (tx >= ty && std::abs(cornerVector.x()) < cfg.arrowStemLength()) || + (tx < ty && std::abs(cornerVector.y()) < cfg.arrowStemLength())) + { + ni = si - + (nearestCorner + QPointF(std::signbit(dl.x()) ? +16 : -16, std::signbit(dl.y()) ? +16 : -16)); + qreal invMag = 1.0 / std::sqrt(QPointF::dotProduct(ni, ni)); + ni = invMag * ni; + + // Adjust the intersection point to deal with the rounded rectangle corner: + if ( + (tx >= ty && std::abs(cornerVector.x()) < cfg.nodeRadius()) || + (tx < ty && std::abs(cornerVector.y()) < cfg.nodeRadius())) + { + QPointF centerOfCurvature = + nearestCorner + QPointF(std::signbit(dl.x()) ? +4 : -4, std::signbit(dl.y()) ? +4 : -4); + QPointF tmp = si - centerOfCurvature; + qreal tmpMag = std::sqrt(QPointF::dotProduct(tmp, tmp)); + si = centerOfCurvature + (4.0 / tmpMag) * tmp; + } + } + + // Now, telescope a couple points out along the normal. + QPointF si1 = si + ni * cfg.arrowStemLength(); + QPointF si2 = si + ni * cfg.arrowStemLength() * 2; + + // Midpoint between pi and si + QPointF psm = 0.5 * (pi1 + si1); + + // Arrowhead points that replace si as the path's destination + // 12 = 0.75 * ARC_ARROW_STEM = ARC_ARROW_HEAD + // 6 = 0.50 * ARC_ARROW_HEAD + QPointF a1 = si + 12. * ni; + QPointF ti{ ni.y(), -ni.x() }; + QPointF a2 = a1 + 6. * ti; + QPointF a3 = a1 - 6. * ti; + QPointF a4 = 0.75 * a1 + 0.25 * si; + // Finally, we can declare our path: + // (pi pi1) [pi2 psm si2] (si1 a4) + // The points in parentheses are connected with a straight line. + // The points in square brackets use a rational quadratic curve. + m_computedPath.moveTo(pi); + m_computedPath.lineTo(pi1); + m_computedPath.quadTo(pi2, psm); + m_computedPath.quadTo(si2, si1); + m_computedPath.lineTo(a4); + this->setPath(m_computedPath); + + m_arrowPath.moveTo(si); + m_arrowPath.lineTo(a2); + m_arrowPath.quadTo(a4, a3); + m_arrowPath.lineTo(si); + return 1; +} + +void qtTaskArc::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) +{ + (void)option; + (void)widget; + const auto& cfg(*m_scene->configuration()); + + QPen pen; + pen.setWidth(cfg.arcWidth()); + pen.setBrush(cfg.colorForArc(m_arcType)); + + // painter->setPen(pen); + painter->strokePath(this->path(), pen); + painter->fillPath(m_arrowPath, pen.brush()); +} + +} // namespace extension +} // namespace smtk diff --git a/smtk/extension/qt/task/qtTaskArc.h b/smtk/extension/qt/task/qtTaskArc.h new file mode 100644 index 0000000000..1fcdb9d062 --- /dev/null +++ b/smtk/extension/qt/task/qtTaskArc.h @@ -0,0 +1,103 @@ +//========================================================================= +// Copyright (c) Kitware, Inc. +// All rights reserved. +// See LICENSE.txt for details. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notice for more information. +//========================================================================= +#ifndef smtk_extension_qtTaskArc_h +#define smtk_extension_qtTaskArc_h + +#include "smtk/extension/qt/Exports.h" + +#include "smtk/common/TypeContainer.h" + +#include "smtk/PublicPointerDefs.h" + +#include +#include +#include + +namespace smtk +{ +namespace task +{ +class Adaptor; +} + +namespace extension +{ + +class qtTaskEditor; +class qtTaskScene; +class qtTaskNode; + +/**\brief A widget that holds a Qt scene graph. + * + */ +class SMTKQTEXT_EXPORT qtTaskArc + : public QObject + , public QGraphicsPathItem +{ + Q_OBJECT + +public: + using Superclass = QGraphicsPathItem; + + /// Arcs between nodes indicate a required ordering of tasks. + enum class ArcType : int + { + Dependency, //!< Tasks are administratively forced to occur in order. + Adaptor //!< Tasks are technically forced into order by information flow. + }; + + qtTaskArc( + qtTaskScene* scene, + qtTaskNode* predecessor, + qtTaskNode* successor, + ArcType type = ArcType::Dependency, + QGraphicsItem* parent = nullptr); + qtTaskArc( + qtTaskScene* scene, + qtTaskNode* predecessor, + qtTaskNode* successor, + smtk::task::Adaptor* adaptor, + QGraphicsItem* parent = nullptr); + ~qtTaskArc() override; + + qtTaskScene* scene() const { return m_scene; } + ArcType arcType() const { return m_arcType; } + qtTaskNode* predecessor() const { return m_predecessor; } + qtTaskNode* successor() const { return m_successor; } + smtk::task::Adaptor* adaptor() const { return m_adaptor; } + + static ArcType arcTypeEnum(const std::string& enumerant, bool* match = nullptr); + static std::string arcTypeName(ArcType enumerant); + +public Q_SLOTS: // NOLINT(readability-redundant-access-specifiers) + + /// Recompute points specifying the shape of the arc based on the current + /// endpoint-node positions and geometry. + int updateArcPoints(); + +protected: + /// Get the bounding box of the node, which includes the border width and the label. + QRectF boundingRect() const override; + /// Draw the arc into the scene. + void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) override; + + qtTaskScene* m_scene{ nullptr }; + qtTaskNode* m_predecessor{ nullptr }; + qtTaskNode* m_successor{ nullptr }; + smtk::task::Adaptor* m_adaptor{ nullptr }; // Only set when ArcType == Adaptor. + ArcType m_arcType{ ArcType::Dependency }; + QPainterPath m_computedPath; + QPainterPath m_arrowPath; +}; + +} // namespace extension +} // namespace smtk + +#endif // smtk_extension_qtTaskArc_h diff --git a/smtk/extension/qt/task/qtTaskEditor.cxx b/smtk/extension/qt/task/qtTaskEditor.cxx new file mode 100644 index 0000000000..18b27756ce --- /dev/null +++ b/smtk/extension/qt/task/qtTaskEditor.cxx @@ -0,0 +1,549 @@ +//========================================================================= +// Copyright (c) Kitware, Inc. +// All rights reserved. +// See LICENSE.txt for details. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notice for more information. +//========================================================================= +#include "smtk/extension/qt/task/qtTaskEditor.h" + +#include "smtk/common/Managers.h" + +#include "smtk/extension/qt/task/PanelConfiguration_cpp.h" +#include "smtk/extension/qt/task/TaskEditorState.h" +#include "smtk/extension/qt/task/qtTaskArc.h" +#include "smtk/extension/qt/task/qtTaskNode.h" +#include "smtk/extension/qt/task/qtTaskScene.h" +#include "smtk/extension/qt/task/qtTaskView.h" +#include "smtk/extension/qt/task/qtTaskViewConfiguration.h" + +#include "smtk/project/Manager.h" +#include "smtk/project/Project.h" + +#include "smtk/task/Active.h" +#include "smtk/task/Instances.h" +#include "smtk/task/Manager.h" + +#include "smtk/view/Configuration.h" +#include "smtk/view/json/jsonView.h" + +#include "smtk/io/Logger.h" + +#include "nlohmann/json.hpp" + +#include +#include +#include +#include +#include +#include + +// Uncomment to get debug printouts from workflow events. +// #define SMTK_DBG_WORKFLOWS 1 + +namespace smtk +{ +namespace extension +{ + +class qtTaskEditor::Internal +{ +public: + using Task = smtk::task::Task; + + Internal(qtTaskEditor* self, const smtk::view::Information& info) + { + (void)info; + m_self = self; + m_scene = new qtTaskScene(m_self); + m_widget = new qtTaskView(m_scene, m_self); + m_uiState = std::make_shared(m_self); + auto* layout = new QVBoxLayout; + layout->setObjectName("taskEditor"); + m_self->Widget = m_widget; + m_self->Widget->setLayout(layout); + if (info.configuration()) + { + m_scene->setConfiguration(new qtTaskViewConfiguration(*info.configuration())); + } + } + + void displayTaskManager(smtk::task::Manager* taskManager) + { + if (m_taskManager == taskManager) + { + return; + } + this->removeObservers(); + this->clear(); + m_taskManager = taskManager; + this->installObservers(); + + auto generator = std::dynamic_pointer_cast(m_uiState); + m_taskManager->uiState().setGenerator(m_self->typeName(), generator); + + QTimer::singleShot(0, [this]() { + bool modified = this->computeNodeLayout(); + m_widget->ensureVisible(m_scene->sceneRect()); + + // After layout complete, connect nodeMoved signal + for (const auto& entry : m_taskIndex) + { + qtTaskNode* taskNode = entry.second; + QObject::connect( + taskNode, &qtTaskNode::nodeMoved, m_self, &qtTaskEditor::onNodeGeometryChanged); + } + + if (modified) + { + m_self->onNodeGeometryChanged(); + } + }); + } + + // Returns true if UI configuration was modified + bool computeNodeLayout() + { + if (m_taskIndex.empty()) + { + return false; + } + + // Check if taskManager has UI config objects first + bool configured = false; + auto* firstTask = m_taskIndex.begin()->first; + auto* taskManager = firstTask->manager(); + auto& uiState = taskManager->uiState(); + smtk::string::Token classToken = m_self->typeName(); + for (const auto& entry : m_taskIndex) + { + nlohmann::json uiConfig = uiState.getData(classToken, entry.first); + if (uiConfig.contains("position")) + { + auto jPosition = uiConfig["position"]; + double x = jPosition[0].get(); + double y = jPosition[1].get(); + + auto* node = entry.second; + node->setPos(x, y); + + configured = true; + } // if + } // for + + if (configured) + { + return false; // not modified + } + + // If geometry not configured, call the scenes method to layout nodes + std::unordered_set nodes; + std::unordered_set arcs; + for (const auto& entry : m_taskIndex) + { + nodes.insert(entry.second); + } + for (const auto& entry : m_arcIndex) + { + for (const auto& arc : entry.second) + { + arcs.insert(arc); + } + } + return (m_scene->computeLayout(nodes, arcs) == 1); + } + + void removeObservers() + { + // Reset task-manager observers + m_adaptorObserverKey.release(); + m_instanceObserverKey.release(); + m_workflowObserverKey.release(); + m_activeObserverKey.release(); + } + + void installObservers() + { + if (!m_taskManager) + { + return; + } + + QPointer parent(m_self); + m_instanceObserverKey = m_taskManager->taskInstances().observers().insert( + [this, + parent](smtk::common::InstanceEvent event, const std::shared_ptr& task) { + if (!parent) + { + return; + } + switch (event) + { + case smtk::common::InstanceEvent::Managed: + { + // std::cout << "Add task instance " << task << " " << task->title() << "\n"; + auto* tnode = new qtTaskNode(m_scene, task.get()); + m_taskIndex[task.get()] = tnode; + } + break; + case smtk::common::InstanceEvent::Unmanaged: + { + // std::cout << "Remove task instance " << task << " " << task->title() << "\n"; + auto it = m_taskIndex.find(task.get()); + if (it != m_taskIndex.end()) + { + this->removeArcsAttachedTo(it->second); + delete it->second; + m_taskIndex.erase(it); + } + } + break; + } + }, + "qtTaskEditor watching task instances."); + // Observe task-manager's taskInstances() and active() objects. + m_workflowObserverKey = m_taskManager->taskInstances().workflowObservers().insert( + [this, parent]( + const std::set& workflow, + smtk::task::WorkflowEvent workflowEvent, + smtk::task::Task* task) { + (void)workflow; + (void)task; + if (!parent) + { + return; + } + switch (workflowEvent) + { + case smtk::task::WorkflowEvent::Created: +#ifdef SMTK_DBG_WORKFLOWS + std::cout << "Workflow created, task " << task << ":\n"; +#endif + break; + case smtk::task::WorkflowEvent::TaskAdded: +#ifdef SMTK_DBG_WORKFLOWS + std::cout << "Add task " << task << " with " << workflow.size() << " tasks in flow:\n"; +#endif + // new qtTaskNode(m_scene, task); + break; + case smtk::task::WorkflowEvent::TaskRemoved: +#ifdef SMTK_DBG_WORKFLOWS + std::cout << "Task " << task << " removed:\n"; +#endif + break; + case smtk::task::WorkflowEvent::Destroyed: +#ifdef SMTK_DBG_WORKFLOWS + std::cout << "Workflow destroyed, task " << task << "\n"; +#endif + break; + case smtk::task::WorkflowEvent::Resuming: + this->resetArcs(qtTaskArc::ArcType::Dependency); +#ifdef SMTK_DBG_WORKFLOWS + std::cout << "Resuming w " << workflow.size() << " tasks in flow:\n"; +#endif + break; + } +#ifdef SMTK_DBG_WORKFLOWS + for (const auto& wtask : workflow) + { + std::cout << " " << wtask << " " << wtask->title() << "\n"; + } +#endif + }, + "qtTaskEditor watching task workflows."); + + m_adaptorObserverKey = m_taskManager->adaptorInstances().observers().insert( + [this, parent]( + smtk::common::InstanceEvent event, const std::shared_ptr& adaptor) { + if (!parent) + { + return; + } + auto fromIt = m_taskIndex.find(adaptor->from()); + auto toIt = m_taskIndex.find(adaptor->to()); + if (fromIt == m_taskIndex.end() || toIt == m_taskIndex.end()) + { + smtkWarningMacro( + smtk::io::Logger::instance(), + "An adaptor's arc could not be " + << (event == smtk::common::InstanceEvent::Managed ? "added" : "removed") + << " because the nodes don't exist yet."); + return; + } + switch (event) + { + case smtk::common::InstanceEvent::Managed: + m_arcIndex[fromIt->second].insert( + new qtTaskArc(m_scene, fromIt->second, toIt->second, adaptor.get())); + break; + case smtk::common::InstanceEvent::Unmanaged: + { + qtTaskArc* match = nullptr; + auto it = m_arcIndex.find(fromIt->second); + if (it != m_arcIndex.end()) + { + for (const auto& arcItem : it->second) + { + if (arcItem->adaptor() == adaptor.get()) + { + match = arcItem; + m_arcIndex.erase(it); + break; + } + } + } + if (!match) + { + smtkWarningMacro( + smtk::io::Logger::instance(), + "An adaptor's arc could not be removed because the arc don't exist or is " + "improperly indexed!"); + return; + } + delete match; + } + break; + } + }, + "qtTaskEditor watching task adaptors."); + + m_activeObserverKey = m_taskManager->active().observers().insert( + [this, parent](smtk::task::Task* prev, smtk::task::Task* next) { + if (!parent) + { + return; + } + // std::cout << "Switch active task from " << prev << " to " << next << "\n"; + auto prevNode = m_taskIndex.find(prev); + auto nextNode = m_taskIndex.find(next); + if (prevNode != m_taskIndex.end()) + { + prevNode->second->setOutlineStyle(qtTaskNode::OutlineStyle::Normal); + } + if (nextNode != m_taskIndex.end()) + { + nextNode->second->setOutlineStyle(qtTaskNode::OutlineStyle::Active); + } + }, + "qtTaskEditor watching active task."); + } + + void removeArcsAttachedTo(qtTaskNode* node) + { + // Examine node->task()->dependencies() for upstream arcs + auto deps = node->task()->dependencies(); + // std::cout << " Task " << node->task() << " has " << deps.size() << " dependencies.\n"; + for (const auto& predecessor : deps) + { + auto predIt = m_taskIndex.find(predecessor.get()); + if (predIt == m_taskIndex.end()) + { + continue; + } + auto arcIt = m_arcIndex.find(predIt->second); + if (arcIt != m_arcIndex.end()) + { + std::unordered_set toErase; + for (const auto& arcItem : arcIt->second) + { + if (arcItem->successor() == node) + { + toErase.insert(arcItem); + delete arcItem; + } + } + for (const auto& entry : toErase) + { + arcIt->second.erase(entry); + } + } + } + // Examine m_arcIndex[node] for downstream arcs + auto arcIt = m_arcIndex.find(node); + if (arcIt != m_arcIndex.end()) + { + for (const auto& arcItem : arcIt->second) + { + delete arcItem; + } + m_arcIndex.erase(node); + } + } + + void resetArcs(qtTaskArc::ArcType arcsToReset) + { + // Erase all arcs and repopulate. + for (auto& entry : m_arcIndex) + { + std::unordered_set toErase; + for (const auto& arc : entry.second) + { + if (arc->arcType() == arcsToReset) + { + toErase.insert(arc); + delete arc; + } + } + for (const auto& arcToErase : toErase) + { + entry.second.erase(arcToErase); + } + } + + m_taskManager->taskInstances().visit([this](const std::shared_ptr& successor) { + auto succIt = m_taskIndex.find(successor.get()); + if (succIt == m_taskIndex.end()) + { + // std::cout << " Skip tasks " << successor.get() << "\n"; + return smtk::common::Visit::Continue; + } + auto deps = successor->dependencies(); + // std::cout << " Task " << successor << " has " << deps.size() << " dependencies.\n"; + for (const auto& predecessor : deps) + { + // std::cout << " Task " << predecessor << " is a dependency.\n"; + auto predIt = m_taskIndex.find(predecessor.get()); + if (predIt == m_taskIndex.end()) + { + // std::cout << " Skip task " << predecessor.get() << "\n"; + continue; + } + m_arcIndex[predIt->second].insert(new qtTaskArc(m_scene, predIt->second, succIt->second)); + } + return smtk::common::Visit::Continue; + }); + } + + void clear() + { + for (const auto& entry : m_taskIndex) + { + delete entry.second; + } + // TODO: Delete arcs, too. + } + + qtTaskEditor* m_self; + smtk::task::Manager* m_taskManager{ nullptr }; + qtTaskScene* m_scene{ nullptr }; + qtTaskView* m_widget{ nullptr }; + std::shared_ptr m_uiState; + smtk::task::adaptor::Instances::Observers::Key m_adaptorObserverKey; + smtk::task::Instances::WorkflowObservers::Key m_workflowObserverKey; + smtk::task::Instances::Observers::Key m_instanceObserverKey; + smtk::task::Active::Observers::Key m_activeObserverKey; + std::unordered_map m_taskIndex; + std::unordered_map> + m_arcIndex; // Arcs grouped by their predecessor task. +}; + +qtTaskEditor::qtTaskEditor(const smtk::view::Information& info) + : qtBaseView(info) + , m_p(new Internal(this, info)) +{ +} + +qtTaskEditor::~qtTaskEditor() = default; + +qtBaseView* qtTaskEditor::createViewWidget(const smtk::view::Information& info) +{ + qtTaskEditor* editor = new qtTaskEditor(info); + // editor->buildUI(); + return editor; +} + +void qtTaskEditor::displayProject(const std::shared_ptr& project) +{ + if (project) + { + this->displayTaskManager(&project->taskManager()); + // TODO: Observe project's manager and clear this view if a different project is loaded. + } + else + { + // TODO: Unobserve any project manager + this->displayTaskManager(nullptr); + } +} + +void qtTaskEditor::displayTaskManager(smtk::task::Manager* taskManager) +{ + m_p->displayTaskManager(taskManager); +} + +qtTaskScene* qtTaskEditor::taskScene() const +{ + return m_p->m_scene; +} + +qtTaskView* qtTaskEditor::taskWidget() const +{ + return m_p->m_widget; +} + +std::shared_ptr qtTaskEditor::defaultConfiguration() +{ + std::shared_ptr result; + auto jsonConfig = nlohmann::json::parse(taskPanelConfiguration())[0]; + result = jsonConfig; + return result; +} + +nlohmann::json qtTaskEditor::uiStateForTask(const smtk::task::Task* task) const +{ + auto iter = m_p->m_taskIndex.find(const_cast(task)); + if (iter == m_p->m_taskIndex.end()) + { + return nlohmann::json(); + } + + qtTaskNode* taskNode = iter->second; + QPointF pos = taskNode->scenePos(); + nlohmann::json jUI = nlohmann::json::object(); + jUI["position"] = { pos.x(), pos.y() }; + return jUI; +} + +void qtTaskEditor::onNodeGeometryChanged() +{ + auto managers = m_p->m_taskManager->managers(); + if (managers == nullptr) + { + return; + } + + auto* taskNode = dynamic_cast(this->sender()); + if (taskNode != nullptr) + { + // Check if modified + smtk::string::Token classToken = this->typeName(); + nlohmann::json origData = m_p->m_taskManager->uiState().getData(classToken, taskNode->task()); + nlohmann::json newData = this->uiStateForTask(taskNode->task()); + if (origData == newData) + { + return; + } + } + + // Set project's modified flag + auto projectManager = managers->get(); + if (projectManager == nullptr) + { + return; + } + + auto projects = projectManager->projects(); + if (projects.size() != 1) + { + return; + } + + auto project = *projects.begin(); + auto mutableProject = dynamic_pointer_cast(project); + mutableProject->setClean(false); +} + +} // namespace extension +} // namespace smtk diff --git a/smtk/extension/qt/task/qtTaskEditor.h b/smtk/extension/qt/task/qtTaskEditor.h new file mode 100644 index 0000000000..64ddac3aa6 --- /dev/null +++ b/smtk/extension/qt/task/qtTaskEditor.h @@ -0,0 +1,77 @@ +//========================================================================= +// Copyright (c) Kitware, Inc. +// All rights reserved. +// See LICENSE.txt for details. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notice for more information. +//========================================================================= +#ifndef smtk_extension_qtTaskEditor_h +#define smtk_extension_qtTaskEditor_h + +#include "smtk/extension/qt/Exports.h" +#include "smtk/extension/qt/qtBaseView.h" + +#include "smtk/project/Project.h" +#include "smtk/task/Manager.h" +#include "smtk/view/Configuration.h" + +#include "smtk/common/TypeContainer.h" + +#include "smtk/PublicPointerDefs.h" + +#include "nlohmann/json.hpp" + +#include + +class QAbstractItemModel; +class QItemSelection; +class QTreeView; + +namespace smtk +{ +namespace extension +{ + +class qtTaskScene; +class qtTaskView; + +/**\brief A widget that displays SMTK tasks available to users as a graph. + * + */ +class SMTKQTEXT_EXPORT qtTaskEditor : public qtBaseView +{ + Q_OBJECT + typedef smtk::extension::qtBaseView Superclass; + +public: + smtkTypenameMacro(qtTaskEditor); + + static qtBaseView* createViewWidget(const smtk::view::Information& info); + qtTaskEditor(const smtk::view::Information& info); + ~qtTaskEditor() override; + + qtTaskScene* taskScene() const; + qtTaskView* taskWidget() const; + + static std::shared_ptr defaultConfiguration(); + nlohmann::json uiStateForTask(const smtk::task::Task* task) const; + +public Q_SLOTS: + /// Display the \a project's tasks in this widget. + virtual void displayProject(const std::shared_ptr& project); + /// Display a \a taskManager's tasks in this widget (which need not belong to a project). + virtual void displayTaskManager(smtk::task::Manager* taskManager); + +protected Q_SLOTS: + void onNodeGeometryChanged(); + +protected: + class Internal; + Internal* m_p; +}; + +} // namespace extension +} // namespace smtk +#endif // smtk_extension_qtTaskEditor_h diff --git a/smtk/extension/qt/task/qtTaskNode.cxx b/smtk/extension/qt/task/qtTaskNode.cxx new file mode 100644 index 0000000000..56b20a31e6 --- /dev/null +++ b/smtk/extension/qt/task/qtTaskNode.cxx @@ -0,0 +1,357 @@ +//========================================================================= +// Copyright (c) Kitware, Inc. +// All rights reserved. +// See LICENSE.txt for details. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notice for more information. +//========================================================================= +#include "smtk/extension/qt/task/qtTaskNode.h" + +#include "smtk/extension/qt/qtBaseView.h" +#include "smtk/extension/qt/task/qtTaskScene.h" +#include "smtk/extension/qt/task/qtTaskViewConfiguration.h" + +#include "smtk/task/Active.h" +#include "smtk/task/Manager.h" +#include "smtk/task/Task.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "task/ui_TaskNode.h" + +class QAbstractItemModel; +class QItemSelection; +class QTreeView; + +namespace +{ + +template +/** + * Intercept all events from a particular QObject and process them using the + * given @c functor. This is usually used with the QObjects::installEventFilter() + * function. + */ +class Interceptor final : public QObject +{ +public: + /** + * Create an Interceptor that process all events of @c parent using @c functor. + */ + Interceptor(QObject* parent, F fn) + : QObject(parent) + , functor(fn) + { + } + ~Interceptor() override = default; + +protected: + /** + * Filters events if this object has been installed as an event filter for the watched object. + */ + bool eventFilter(QObject* object, QEvent* event) override { return this->functor(object, event); } + + F functor; +}; + +/** + * Create a new Interceptor instance. + */ +template +Interceptor* createInterceptor(QObject* parent, F functor) +{ + return new Interceptor(parent, functor); +}; + +} // namespace + +namespace smtk +{ +namespace extension +{ + +class TaskNodeWidget + : public QWidget + , public Ui::TaskNode +{ +public: + TaskNodeWidget(qtTaskNode* node, QWidget* parent = nullptr) + : QWidget(parent) + , m_node(node) + { + this->setupUi(this); + m_nodeMenu = new QMenu(m_headlineButton); + m_activateTask = new QAction("Work on this"); + m_expandTask = new QAction("Show controls"); + m_markCompleted = new QAction("Mark completed"); + m_nodeMenu->addAction(m_activateTask); + m_nodeMenu->addAction(m_expandTask); + m_nodeMenu->addAction(m_markCompleted); + m_markCompleted->setEnabled(false); + m_headlineButton->setMenu(m_nodeMenu); + QObject::connect(m_activateTask, &QAction::triggered, this, &TaskNodeWidget::activateTask); + QObject::connect(m_markCompleted, &QAction::triggered, this, &TaskNodeWidget::markCompleted); + QObject::connect(m_expandTask, &QAction::triggered, this, &TaskNodeWidget::toggleControls); + m_taskObserver = m_node->task()->observers().insert( + [this](smtk::task::Task&, smtk::task::State prev, smtk::task::State next) { + // Sometimes the application invokes this observer after the GUI + // has been shut down. Calling setEnabled on widgets generates a + // log message and attempting to construct a QPixmap throws exceptions; + // so, check that qApp exists before going further. + if (qApp) + { + this->updateTaskState(prev, next); + } + }, + "TaskNodeWidget observer"); + this->updateTaskState(m_node->m_task->state(), m_node->m_task->state()); + } + + void updateTaskState(smtk::task::State prev, smtk::task::State next) + { + (void)prev; + switch (next) + { + case smtk::task::State::Irrelevant: + m_headlineButton->setEnabled(false); + break; + case smtk::task::State::Unavailable: + m_headlineButton->setEnabled(false); + m_activateTask->setEnabled(false); + break; + case smtk::task::State::Incomplete: + m_headlineButton->setEnabled(true); + m_activateTask->setEnabled(true); + m_markCompleted->setEnabled(false); + break; + case smtk::task::State::Completable: + m_headlineButton->setEnabled(true); + m_activateTask->setEnabled(true); + m_markCompleted->setEnabled(true); + break; + case smtk::task::State::Completed: + m_headlineButton->setEnabled(true); + m_activateTask->setEnabled(false); + m_markCompleted->setEnabled(true); + break; + } + m_headlineButton->setToolTip(QString::fromStdString("Status: " + smtk::task::stateName(next))); + m_headlineButton->setIcon(this->renderStatusIcon(next, m_headlineButton->height() / 2)); + } + + void activateTask() + { + auto* taskManager = m_node->m_task->manager(); + if (taskManager) + { + taskManager->active().switchTo(m_node->m_task); + // TODO: Provide feedback if no action taken (e.g., flash red) + } + } + + void markCompleted() + { + m_node->m_task->markCompleted(m_node->m_task->state() == smtk::task::State::Completable); + // TODO: Provide feedback if no action taken (e.g., flash red) + } + + QIcon renderStatusIcon(smtk::task::State state, int radius) + { + if (radius < 10) + { + radius = 10; + } + QPixmap pix(radius, radius); + pix.fill(QColor(0, 0, 0, 0)); + + auto& cfg = *m_node->m_scene->configuration(); + QPainter painter(&pix); + painter.setRenderHint(QPainter::Antialiasing, true); + painter.setBrush(QBrush(cfg.colorForState(state))); + painter.drawEllipse(1, 1, radius - 2, radius - 2); + painter.end(); + return QIcon(pix); + } + + void toggleControls() + { + bool shouldShow = !m_controls->isVisible(); + m_controls->setVisible(shouldShow); + m_expandTask->setText(shouldShow ? "Hide controls" : "Show controls"); + } + + QMenu* m_nodeMenu; + QAction* m_activateTask; + QAction* m_expandTask; + QAction* m_markCompleted; + qtTaskNode* m_node; + smtk::task::Task::Observers::Key m_taskObserver; +}; + +qtTaskNode::qtTaskNode(qtTaskScene* scene, smtk::task::Task* task, QGraphicsItem* parent) + : Superclass(parent) + , m_scene(scene) + , m_task(task) + , m_container(new TaskNodeWidget(this)) +{ + qtTaskViewConfiguration& cfg(*m_scene->configuration()); + // === Base constructor === + this->setFlag(GraphicsItemFlag::ItemIsMovable); + this->setFlag(GraphicsItemFlag::ItemSendsGeometryChanges); + this->setCacheMode(CacheMode::DeviceCoordinateCache); + this->setCursor(Qt::ArrowCursor); + this->setObjectName(QString("node") + QString::fromStdString(m_task->title())); + + // Create a container to hold node contents + { + m_container->setObjectName("nodeContainer"); + m_container->setMinimumWidth(cfg.nodeWidth()); + // m_container->setMaximumWidth(cfg.nodeWidth()); + + // install resize event filter + m_container->installEventFilter( + createInterceptor(m_container, [this](QObject* /*object*/, QEvent* event) { + if (event->type() == QEvent::LayoutRequest) + { + this->updateSize(); + } + return false; + })); + + auto* graphicsProxyWidget = new QGraphicsProxyWidget(this); + graphicsProxyWidget->setObjectName("graphicsProxyWidget"); + graphicsProxyWidget->setWidget(m_container); + graphicsProxyWidget->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed)); + graphicsProxyWidget->setPos(QPointF(0, 0)); + + m_container->m_headlineButton->setText(QString::fromStdString(m_task->title())); + m_container->m_controls->hide(); + + // Configure timer to rate-limit nodeMoved signal + m_moveSignalTimer = new QTimer(this); + m_moveSignalTimer->setSingleShot(true); + m_moveSignalTimer->setInterval(100); + QObject::connect(m_moveSignalTimer, &QTimer::timeout, this, &qtTaskNode::nodeMoved); + } + + this->updateSize(); + m_scene->addItem(this); + + // === Task-specific constructor === + this->setZValue(cfg.nodeLayer()); +} + +qtTaskNode::~qtTaskNode() +{ + m_scene->removeItem(this); +} + +void qtTaskNode::setContentStyle(ContentStyle cs) +{ + m_contentStyle = cs; + switch (cs) + { + case ContentStyle::Minimal: + m_container->hide(); + break; + case ContentStyle::Summary: + case ContentStyle::Details: + m_container->show(); + break; + default: + break; + } +} + +void qtTaskNode::setOutlineStyle(OutlineStyle os) +{ + qtTaskViewConfiguration& cfg(*m_scene->configuration()); + m_outlineStyle = os; + this->setZValue(cfg.nodeLayer() - (os == OutlineStyle::Normal ? 0 : 1)); + this->update(this->boundingRect()); +} + +QRectF qtTaskNode::boundingRect() const +{ + qtTaskViewConfiguration& cfg(*m_scene->configuration()); + const auto& border = cfg.nodeBorderThickness(); + const double height = m_container->height(); + // was = m_headlineHeight + (m_container->isVisible() ? m_container->height() : 0.0); + return QRectF(0, 0, m_container->width(), height).adjusted(-border, -border, border, border); +} + +QVariant qtTaskNode::itemChange(GraphicsItemChange change, const QVariant& value) +{ + if (change == GraphicsItemChange::ItemPositionHasChanged) + { + m_moveSignalTimer->start(); + Q_EMIT this->nodeMovedImmediate(); + } + + return QGraphicsItem::itemChange(change, value); +} + +void qtTaskNode::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) +{ + (void)option; + (void)widget; + qtTaskViewConfiguration& cfg(*m_scene->configuration()); + QPainterPath path; + // Make sure the whole node is redrawn to avoid artifacts: + const double borderOffset = 0.5 * cfg.nodeBorderThickness(); + const QRectF br = + this->boundingRect().adjusted(borderOffset, borderOffset, -borderOffset, -borderOffset); + path.addRoundedRect(br, cfg.nodeBorderThickness(), cfg.nodeBorderThickness()); + + const QColor baseColor = QApplication::palette().window().color(); + const QColor highlightColor = QApplication::palette().highlight().color(); + const QColor contrastColor = QColor::fromHslF( + baseColor.hueF(), + baseColor.saturationF(), + baseColor.lightnessF() > 0.5 ? baseColor.lightnessF() - 0.5 : baseColor.lightnessF() + 0.5); + // const QColor greenBaseColor = QColor::fromHslF(0.361, 0.666, baseColor.lightnessF() * 0.4 + 0.2); + + QPen pen; + pen.setWidth(cfg.nodeBorderThickness()); + switch (m_outlineStyle) + { + case OutlineStyle::Normal: + pen.setBrush(contrastColor); + break; + case OutlineStyle::Active: + pen.setBrush(highlightColor); + break; + default: + break; + } + + painter->setPen(pen); + painter->fillPath(path, baseColor); + painter->drawPath(path); +} + +int qtTaskNode::updateSize() +{ + this->prepareGeometryChange(); + + m_container->resize(m_container->layout()->sizeHint()); + Q_EMIT this->nodeResized(); + + return 1; +} + +} // namespace extension +} // namespace smtk diff --git a/smtk/extension/qt/task/qtTaskNode.h b/smtk/extension/qt/task/qtTaskNode.h new file mode 100644 index 0000000000..1651a9d4ea --- /dev/null +++ b/smtk/extension/qt/task/qtTaskNode.h @@ -0,0 +1,113 @@ +//========================================================================= +// Copyright (c) Kitware, Inc. +// All rights reserved. +// See LICENSE.txt for details. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notice for more information. +//========================================================================= +#ifndef smtk_extension_qtTaskNode_h +#define smtk_extension_qtTaskNode_h + +#include "smtk/extension/qt/Exports.h" +#include "smtk/extension/qt/qtBaseView.h" + +#include "smtk/common/TypeContainer.h" + +#include "smtk/PublicPointerDefs.h" + +#include +#include + +class QAbstractItemModel; +class QGraphicsTextItem; +class QItemSelection; +class QTimer; +class QTreeView; + +namespace smtk +{ +namespace task +{ +class Task; +} +namespace extension +{ + +class qtTaskEditor; +class qtTaskScene; +class TaskNodeWidget; + +/**\brief A widget that represents a task as a node in a scene. + * + */ +class SMTKQTEXT_EXPORT qtTaskNode + : public QObject + , public QGraphicsItem +{ + Q_OBJECT + Q_INTERFACES(QGraphicsItem) + +public: + using Superclass = QGraphicsItem; + + /// Determine how the node is presented to users. + enum class ContentStyle : int + { + Minimal, //!< Only the node's title-bar is shown. + Summary, //!< The node's title bar and a "mini" viewer should be shown. + Details //!< The node's full state and any contained view should be shown. + }; + + /// Determine how the border of the node's visual representation should be rendered. + enum class OutlineStyle : int + { + Normal, //!< Render an unobtrusive, subdued border around the node. + Active //!< Render a highlighted border around the node. + }; + + qtTaskNode(qtTaskScene* scene, smtk::task::Task* task, QGraphicsItem* parent = nullptr); + ~qtTaskNode() override; + + /// Return the task this node represents. + smtk::task::Task* task() const { return m_task; } + + /// Set/get how much data the node should render inside its boundary. + void setContentStyle(ContentStyle cs); + ContentStyle contentStyle() const { return m_contentStyle; } + + /// Set/get how the node's boundary should be rendered. + void setOutlineStyle(OutlineStyle cs); + OutlineStyle outlineStyle() const { return m_outlineStyle; } + + /// Get the bounding box of the node, which includes the border width and the label. + QRectF boundingRect() const override; + +Q_SIGNALS: + void nodeResized(); + void nodeMovedImmediate(); + void nodeMoved(); // a rate-limited version of nodeMovedImmediate + +protected: + friend class TaskNodeWidget; + QVariant itemChange(GraphicsItemChange change, const QVariant& value) override; + void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) override; + + /// Update the node bounds to fit its content. + int updateSize(); + + qtTaskScene* m_scene{ nullptr }; + smtk::task::Task* m_task{ nullptr }; + TaskNodeWidget* m_container{ nullptr }; + ContentStyle m_contentStyle{ ContentStyle::Minimal }; + OutlineStyle m_outlineStyle{ OutlineStyle::Normal }; + + // Use timer to limit frequency of nodeMoved() signals to 10 hz. + QTimer* m_moveSignalTimer{ nullptr }; +}; + +} // namespace extension +} // namespace smtk + +#endif // smtk_extension_qtTaskNode_h diff --git a/smtk/extension/qt/task/qtTaskScene.cxx b/smtk/extension/qt/task/qtTaskScene.cxx new file mode 100644 index 0000000000..5fe7d28ff3 --- /dev/null +++ b/smtk/extension/qt/task/qtTaskScene.cxx @@ -0,0 +1,215 @@ +//========================================================================= +// Copyright (c) Kitware, Inc. +// All rights reserved. +// See LICENSE.txt for details. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notice for more information. +//========================================================================= +#include "smtk/extension/qt/task/qtTaskScene.h" +#include "smtk/extension/qt/task/qtTaskArc.h" +#include "smtk/extension/qt/task/qtTaskEditor.h" +#include "smtk/extension/qt/task/qtTaskNode.h" + +#include "smtk/Options.h" +#include "smtk/io/Logger.h" + +#include +#include + +#if SMTK_ENABLE_GRAPHVIZ_SUPPORT +// Older Graphviz releases (before 2.40.0) require `HAVE_CONFIG_H` to define +// `POINTS_PER_INCH`. +#define HAVE_CONFIG_H +#include +#include +#include +#undef HAVE_CONFIG_H +#endif // SMTK_ENABLE_GRAPHVIZ_SUPPORT + +#include + +namespace smtk +{ +namespace extension +{ + +qtTaskScene::qtTaskScene(qtTaskEditor* parent) + : Superclass(parent->widget()) +{ +} + +qtTaskScene::~qtTaskScene() = default; + +bool qtTaskScene::computeLayout( + const std::unordered_set& nodes, + const std::unordered_set& arcs) +{ +#if SMTK_ENABLE_GRAPHVIZ_SUPPORT + // compute dot string + qreal maxHeight = 0.0; + qreal maxY = 0; + std::string dotString; + { + std::stringstream nodeString; + std::stringstream edgeString; + + for (const auto& node : nodes) + { + // Ignore hidden nodes + if (!node->isVisible() || !node->task()) + { + continue; + } + + const QRectF& b = node->boundingRect(); + qreal width = b.width() / POINTS_PER_INCH; // convert from points to inches + qreal height = b.height() / POINTS_PER_INCH; + if (maxHeight < height) + { + maxHeight = height; + } + + // Construct the string declaring a node. + // See https://www.graphviz.org/pdf/libguide.pdf for more detail + nodeString << "n" << node << "[" + << "shape=record," + << "width=" << width << "," + << "height=" << height << "" + << "];\n"; + } + + // Construct the string representing all arcs in the graph + // See https://www.graphviz.org/pdf/libguide.pdf for more detail + for (const auto& arc : arcs) + { + edgeString << "n" << arc->predecessor() << " -> " + << "n" << arc->successor() << ";\n"; + } + + // describe the overall look of the graph. For example : rankdir=LR -> Left To Right layout + // See https://www.graphviz.org/pdf/libguide.pdf for more detail + dotString += "digraph g {\nrankdir=TB;splines = line;graph[pad=\"0\", ranksep=\"0.6\", " + "nodesep=\"0.6\"];\n" + + nodeString.str() + edgeString.str() + "\n}"; + } + + std::vector coords(2 * nodes.size(), 0.0); + // compute layout + { + Agraph_t* G = agmemread(dotString.data()); + GVC_t* gvc = gvContext(); + if (!G || !gvc || gvLayout(gvc, G, "dot")) + { + smtkErrorMacro( + smtk::io::Logger::instance(), "[NodeEditorPlugin] Cannot intialize Graphviz context."); + return 0; + } + + // read layout + int i = -2; + for (const auto& node : nodes) + { + if (!node->isVisible() || !node->task()) + { + continue; + } + + i += 2; + + std::ostringstream nodeName; + nodeName << "n" << node; + + Agnode_t* n = agnode(G, const_cast(nodeName.str().c_str()), 0); + if (n != nullptr) + { + const auto& coord = ND_coord(n); + const auto& w = ND_width(n); + const auto& h = ND_height(n); + + auto& x = coords[i]; + auto& y = coords[i + 1]; + x = (coord.x - w * POINTS_PER_INCH / 2.0); // convert w/h in inches to points + y = (-coord.y - h * POINTS_PER_INCH / 2.0); + + maxY = std::max(maxY, y); + } + } + + // free memory + int status = gvFreeLayout(gvc, G); + status += agclose(G); + status += gvFreeContext(gvc); + if (status) + { + smtkWarningMacro( + smtk::io::Logger::instance(), "[NodeEditorPlugin] Error when freeing Graphviz resources."); + } + } + + // set positions + { + int i = -2; + for (const auto& node : nodes) + { + if (!node->isVisible() || !node->task()) + { + continue; + } + i += 2; + + node->setPos(qtTaskScene::snapToGrid(coords[i], coords[i + 1])); + } + } + + return 1; +#else // NodeEditor_ENABLE_GRAPHVIZ + (void)nodes; + (void)arcs; + return false; +#endif // NodeEditor_ENABLE_GRAPHVIZ +} + +QPointF qtTaskScene::snapToGrid(const qreal& x, const qreal& y, const qreal& resolution) +{ + // const auto gridSize = pqNodeEditorUtils::CONSTS::GRID_SIZE * resolution; + const auto gridSize = 25 * resolution; + return QPointF(x - std::fmod(x, gridSize), y - std::fmod(y, gridSize)); +} + +void qtTaskScene::drawBackground(QPainter* painter, const QRectF& rect) +{ + // painter->setPen(pqNodeEditorUtils::CONSTS::COLOR_GRID); + painter->setPen(QApplication::palette().mid().color()); + + // get rectangle bounds + const qreal recL = rect.left(); + const qreal recR = rect.right(); + const qreal recT = rect.top(); + const qreal recB = rect.bottom(); + + // determine whether to use low or high resoltion grid + const qreal gridResolution = (recB - recT) > 2000 ? 4 : 1; + // const qreal gridSize = gridResolution * pqNodeEditorUtils::CONSTS::GRID_SIZE; + const qreal gridSize = gridResolution * 25; + + // find top left corner of active rectangle and snap to grid + // const QPointF& snappedTopLeft = pqNodeEditorScene::snapToGrid(recL, recT, gridResolution); + const QPointF& snappedTopLeft = qtTaskScene::snapToGrid(recL, recT, gridResolution); + + // iterate over the x range of the rectangle to draw vertical lines + for (qreal x = snappedTopLeft.x(); x < recR; x += gridSize) + { + painter->drawLine(x, recT, x, recB); + } + + // iterate over the y range of the rectangle to draw horizontal lines + for (qreal y = snappedTopLeft.y(); y < recB; y += gridSize) + { + painter->drawLine(recL, y, recR, y); + } +} + +} // namespace extension +} // namespace smtk diff --git a/smtk/extension/qt/task/qtTaskScene.h b/smtk/extension/qt/task/qtTaskScene.h new file mode 100644 index 0000000000..4748615ca8 --- /dev/null +++ b/smtk/extension/qt/task/qtTaskScene.h @@ -0,0 +1,76 @@ +//========================================================================= +// Copyright (c) Kitware, Inc. +// All rights reserved. +// See LICENSE.txt for details. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notice for more information. +//========================================================================= +#ifndef smtk_extension_qtTaskScene_h +#define smtk_extension_qtTaskScene_h + +#include "smtk/extension/qt/Exports.h" +#include "smtk/extension/qt/qtBaseView.h" + +#include "smtk/string/Token.h" + +#include "smtk/common/TypeContainer.h" + +#include "smtk/PublicPointerDefs.h" + +#include + +class QAbstractItemModel; +class QItemSelection; +class QTreeView; + +namespace smtk +{ +namespace extension +{ + +class qtTaskArc; +class qtTaskEditor; +class qtTaskNode; +class qtTaskViewConfiguration; + +/**\brief A QGraphicsScene that holds workflow-related QGraphicsItems. + * + */ +class SMTKQTEXT_EXPORT qtTaskScene : public QGraphicsScene +{ + Q_OBJECT + +public: + using Superclass = QGraphicsScene; + + qtTaskScene(qtTaskEditor*); + ~qtTaskScene() override; + + /// Set/get the view configuration object passed to us from the parent widget. + void setConfiguration(qtTaskViewConfiguration* config) { m_config = config; } + qtTaskViewConfiguration* configuration() const { return m_config; } + +public Q_SLOTS: + /// Compute a layout of the \a nodes and \a arcs passed into this method. + /// + /// This uses graphviz to perform the layout and returns true on success. + bool computeLayout( + const std::unordered_set& nodes, + const std::unordered_set& arcs); + +protected: + /// Snaps the given \a x and \a y coordinate to the next available top left grid point. + /// Optionally, the grid can be scaled with the \a resolution parameter. + static QPointF snapToGrid(const qreal& x, const qreal& y, const qreal& resolution = 1.0); + + /// Draw a cross-hatched grid for the background. + void drawBackground(QPainter* painter, const QRectF& rect) override; + + qtTaskViewConfiguration* m_config{ nullptr }; +}; + +} // namespace extension +} // namespace smtk +#endif // smtk_extension_qtTaskScene_h diff --git a/smtk/extension/qt/task/qtTaskView.cxx b/smtk/extension/qt/task/qtTaskView.cxx new file mode 100644 index 0000000000..c9c392b336 --- /dev/null +++ b/smtk/extension/qt/task/qtTaskView.cxx @@ -0,0 +1,72 @@ +//========================================================================= +// Copyright (c) Kitware, Inc. +// All rights reserved. +// See LICENSE.txt for details. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notice for more information. +//========================================================================= +#include "smtk/extension/qt/task/qtTaskView.h" + +#include "smtk/extension/qt/task/qtTaskEditor.h" +#include "smtk/extension/qt/task/qtTaskScene.h" + +#include +#include +#include + +namespace smtk +{ +namespace extension +{ + +class qtTaskView::Internal +{ +}; + +qtTaskView::qtTaskView(qtTaskScene* scene, qtTaskEditor* widget) + : Superclass(scene, widget->widget()) + , m_p(new Internal) +{ + this->setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform); + this->setViewportUpdateMode(QGraphicsView::FullViewportUpdate); + + this->setDragMode(QGraphicsView::ScrollHandDrag); + constexpr QRectF MAX_SCENE_SIZE{ -1e4, -1e4, 3e4, 3e4 }; + this->setSceneRect(MAX_SCENE_SIZE); +} + +qtTaskView::~qtTaskView() +{ + delete m_p; + m_p = nullptr; +} + +void qtTaskView::wheelEvent(QWheelEvent* event) +{ + constexpr double ZOOM_INCREMENT_RATIO = 0.0125; + + const ViewportAnchor anchor = this->transformationAnchor(); + this->setTransformationAnchor(QGraphicsView::AnchorUnderMouse); + const int angle = event->angleDelta().y(); + static ulong lastTimestamp = 0; + double dt = + (event->timestamp() > lastTimestamp && lastTimestamp != 0 ? event->timestamp() - lastTimestamp + : 50); + lastTimestamp = event->timestamp(); + double rate = std::abs(angle / dt); + const double factor = 1.0 + rate * ((angle > 0) ? ZOOM_INCREMENT_RATIO : -ZOOM_INCREMENT_RATIO); + + this->scale(factor, factor); + this->setTransformationAnchor(anchor); +} + +void qtTaskView::keyReleaseEvent(QKeyEvent* event) +{ + (void)event; + // do nothing yet. +} + +} // namespace extension +} // namespace smtk diff --git a/smtk/extension/qt/task/qtTaskView.h b/smtk/extension/qt/task/qtTaskView.h new file mode 100644 index 0000000000..d307c92503 --- /dev/null +++ b/smtk/extension/qt/task/qtTaskView.h @@ -0,0 +1,61 @@ +//========================================================================= +// Copyright (c) Kitware, Inc. +// All rights reserved. +// See LICENSE.txt for details. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notice for more information. +//========================================================================= +#ifndef smtk_extension_qtTaskView_h +#define smtk_extension_qtTaskView_h + +#include "smtk/extension/qt/Exports.h" +#include "smtk/extension/qt/qtBaseView.h" + +#include "smtk/common/TypeContainer.h" + +#include "smtk/PublicPointerDefs.h" + +#include + +class QAbstractItemModel; +class QItemSelection; +class QTreeView; + +namespace smtk +{ +namespace extension +{ + +class qtTaskEditor; +class qtTaskScene; + +/**\brief A widget that holds a Qt scene graph. + * + */ +class SMTKQTEXT_EXPORT qtTaskView : public QGraphicsView +{ + Q_OBJECT + +public: + using Superclass = QGraphicsView; + + qtTaskView(qtTaskScene* scene, qtTaskEditor* widget = nullptr); + ~qtTaskView() override; + + // qtTaskScene* taskScene() const; + // qtTaskEditor* taskEditor() const; + +protected: + void wheelEvent(QWheelEvent* event) override; + void keyReleaseEvent(QKeyEvent* event) override; + + class Internal; + Internal* m_p; +}; + +} // namespace extension +} // namespace smtk + +#endif // smtk_extension_qtTaskView_h diff --git a/smtk/extension/qt/task/qtTaskViewConfiguration.cxx b/smtk/extension/qt/task/qtTaskViewConfiguration.cxx new file mode 100644 index 0000000000..5c4cba99b8 --- /dev/null +++ b/smtk/extension/qt/task/qtTaskViewConfiguration.cxx @@ -0,0 +1,167 @@ +//========================================================================= +// Copyright (c) Kitware, Inc. +// All rights reserved. +// See LICENSE.txt for details. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notice for more information. +//========================================================================= +#include "smtk/extension/qt/task/qtTaskViewConfiguration.h" + +#include "smtk/io/Logger.h" +#include "smtk/string/Token.h" +#include "smtk/task/State.h" +#include "smtk/view/Configuration.h" + +#include +#include +#include + +namespace smtk +{ +namespace extension +{ + +qtTaskViewConfiguration::qtTaskViewConfiguration(const smtk::view::Configuration& viewConfig) +{ + using namespace smtk::string::literals; + // Create default colors from QApplication's palette. + auto palette = qApp->palette(static_cast(nullptr)); + m_backgroundFillColor = palette.base().color(); + m_backgroundGridColor = palette.midlight().color(); + m_activeTaskColor = palette.highlight().color(); + m_colorForState = { + QColor("#e7e7e7"), // irrelevant + QColor("#ff9898"), // unavailable + QColor("#ffeca3"), // incomplete + QColor("#e0f1ba"), // completable + QColor("#7ed637") // completed + }; + m_colorForArc = { + QColor("#BF5B17"), // dependency + QColor("#386CB0"), // adaptor + }; + + // Look for overrides from the workflow designer. + int styleIdx = viewConfig.details().findChild("Style"); + if (styleIdx < 0) + { + return; + } + const auto& styleComp = viewConfig.details().child(styleIdx); + + int statusColorIdx = styleComp.findChild("StatusPalette"); + if (statusColorIdx >= 0) + { + using smtk::task::State; + const auto& statusColor = styleComp.child(statusColorIdx); + for (const auto& entry : statusColor.attributes()) + { + bool validName; + State state = smtk::task::stateEnum(entry.first, &validName); + // Skip invalid attributes: + if (!validName) + { + continue; + } + m_colorForState[static_cast(state)] = QColor(QString::fromStdString(entry.second)); + } + } + + int arcColorIdx = styleComp.findChild("ArcPalette"); + if (arcColorIdx >= 0) + { + const auto& arcColor = styleComp.child(arcColorIdx); + for (const auto& entry : arcColor.attributes()) + { + bool validName; + qtTaskArc::ArcType arcType = qtTaskArc::arcTypeEnum(entry.first, &validName); + // Skip invalid attributes: + if (!validName) + { + continue; + } + m_colorForArc[static_cast(arcType)] = QColor(QString::fromStdString(entry.second)); + } + } + + int viewPaletteIdx = styleComp.findChild("ViewPalette"); + if (viewPaletteIdx >= 0) + { + const auto& viewPalette = styleComp.child(viewPaletteIdx); + for (const auto& entry : viewPalette.attributes()) + { + smtk::string::Token attName(entry.first); + auto val = QString::fromStdString(entry.second); + switch (attName.id()) + { + // clang-format off + case "ActiveTask"_hash: m_activeTaskColor = QColor(val); break; + case "BackgroundGrid"_hash: m_backgroundGridColor = QColor(val); break; + case "BackgroundFill"_hash: m_backgroundFillColor = QColor(val); break; + default: + smtkWarningMacro(smtk::io::Logger::instance(), + "Unrecognized attribute \"" << entry.first << "\" in ViewPalette."); + break; + // clang-format on + } + } + } + + int nodeLayoutIdx = styleComp.findChild("NodeLayout"); + if (nodeLayoutIdx >= 0) + { + const auto& nodeLayout = styleComp.child(nodeLayoutIdx); + for (const auto& entry : nodeLayout.attributes()) + { + smtk::string::Token attName(entry.first); + switch (attName.id()) + { + // clang-format off + case "Width"_hash: m_nodeWidth = QString::fromStdString(entry.second).toDouble(); break; + case "Radius"_hash: m_nodeRadius = QString::fromStdString(entry.second).toDouble(); break; + case "HeadlineHeight"_hash: m_nodeHeadlineHeight = QString::fromStdString(entry.second).toDouble(); break; + case "HeadlinePadding"_hash: m_nodeHeadlinePadding = QString::fromStdString(entry.second).toDouble(); break; + case "BorderThickness"_hash: m_nodeBorderThickness = QString::fromStdString(entry.second).toDouble(); break; + case "FontSize"_hash: m_nodeFontSize = QString::fromStdString(entry.second).toInt(); break; + case "Layer"_hash: m_nodeLayer = QString::fromStdString(entry.second).toInt(); break; + default: + smtkWarningMacro(smtk::io::Logger::instance(), + "Unrecognized attribute \"" << entry.first << "\" in NodeLayout."); + break; + // clang-format on + } + } + } + + int arcLayoutIdx = styleComp.findChild("ArcLayout"); + if (arcLayoutIdx >= 0) + { + const auto& arcLayout = styleComp.child(arcLayoutIdx); + for (const auto& entry : arcLayout.attributes()) + { + smtk::string::Token attName(entry.first); + switch (attName.id()) + { + // clang-format off + case "Width"_hash: m_arcWidth = QString::fromStdString(entry.second).toDouble(); break; + case "Outline"_hash: m_arcOutline = QString::fromStdString(entry.second).toDouble(); break; + case "ArrowStemLength"_hash: m_arrowStemLength = QString::fromStdString(entry.second).toDouble(); break; + case "ArrowHeadLength"_hash: m_arrowHeadLength = QString::fromStdString(entry.second).toDouble(); break; + case "ArrowTipAspectRatio"_hash: m_arrowTipAspectRatio = QString::fromStdString(entry.second).toDouble(); break; + case "Layer"_hash: m_arcLayer = QString::fromStdString(entry.second).toInt(); break; + default: + smtkWarningMacro(smtk::io::Logger::instance(), + "Unrecognized attribute \"" << entry.first << "\" in ArcLayout."); + break; + // clang-format on + } + } + } + + // TODO: Look for overrides from the user (via QSettings). +} + +} // namespace extension +} // namespace smtk diff --git a/smtk/extension/qt/task/qtTaskViewConfiguration.h b/smtk/extension/qt/task/qtTaskViewConfiguration.h new file mode 100644 index 0000000000..d8cc5622e1 --- /dev/null +++ b/smtk/extension/qt/task/qtTaskViewConfiguration.h @@ -0,0 +1,99 @@ +//========================================================================= +// Copyright (c) Kitware, Inc. +// All rights reserved. +// See LICENSE.txt for details. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notice for more information. +//========================================================================= +#ifndef smtk_extension_qtTaskViewConfiguration_h +#define smtk_extension_qtTaskViewConfiguration_h + +#include "smtk/extension/qt/task/qtTaskArc.h" +#include "smtk/task/State.h" + +#include + +#include + +namespace smtk +{ +namespace view +{ +class Configuration; +} +namespace extension +{ + +/**\brief An object to hold view configuration settings. + * + * This class extracts settings used to render the task view from a + * `view::Configuration` instance and makes them quickly accessible + * to the Qt classes that render tasks. + */ +class SMTKQTEXT_EXPORT qtTaskViewConfiguration +{ +public: + qtTaskViewConfiguration(const smtk::view::Configuration& viewConfig); + + QColor backgroundFillColor() const { return m_backgroundFillColor; } + QColor backgroundGridColor() const { return m_backgroundGridColor; } + + QColor activeTaskColor() const { return m_activeTaskColor; } + + QColor colorForArc(qtTaskArc::ArcType arcType) const + { + return m_colorForArc[static_cast(arcType)]; + } + QColor colorForState(smtk::task::State state) const + { + return m_colorForState[static_cast(state)]; + } + + qreal nodeWidth() const { return m_nodeWidth; } + qreal nodeRadius() const { return m_nodeRadius; } + qreal nodeHeadlineHeight() const { return m_nodeHeadlineHeight; } + qreal nodeHeadlinePadding() const { return m_nodeHeadlinePadding; } + qreal nodeBorderThickness() const { return m_nodeBorderThickness; } + int nodeFontSize() const { return m_nodeFontSize; } + int nodeLayer() const { return m_nodeLayer; } + + qreal arcWidth() const { return m_arcWidth; } + qreal arcOutline() const { return m_arcOutline; } + int arcLayer() const { return m_arcLayer; } + + qreal arrowStemLength() const { return m_arrowStemLength; } + qreal arrowHeadLength() const { return m_arrowHeadLength; } + qreal arrowTipAspectRatio() const { return m_arrowTipAspectRatio; } + +protected: + QColor m_backgroundFillColor; + QColor m_backgroundGridColor; + + QColor m_activeTaskColor; + + std::array(qtTaskArc::ArcType::Adaptor) + 1> m_colorForArc; + std::array(smtk::task::State::Completed) + 1> m_colorForState; + + qreal m_nodeWidth{ 300. }; + qreal m_nodeRadius{ 4. }; + qreal m_nodeHeadlineHeight{ 13 }; + qreal m_nodeHeadlinePadding{ 4. }; + qreal m_nodeBorderThickness{ 4. }; + int m_nodeFontSize{ 13 }; + int m_nodeLayer{ 10 }; + + qreal m_arcWidth{ 4. }; + qreal m_arcOutline{ 1. }; + int m_arcLayer{ 5 }; + + qreal m_arrowStemLength{ 16. }; // The length of the path guaranteed to be a straight line. + qreal m_arrowHeadLength{ 12. }; // The length of the arrow head along the linear stem. + qreal m_arrowTipAspectRatio{ 2. }; // The width of the arrow head as a fraction of head length. +}; + +} // namespace extension +} // namespace smtk + +#endif // smtk_extension_qtTaskViewConfiguration_h diff --git a/smtk/operation/operators/ReadResource.cxx b/smtk/operation/operators/ReadResource.cxx index 5c68bc12e1..1a8b69ef1e 100644 --- a/smtk/operation/operators/ReadResource.cxx +++ b/smtk/operation/operators/ReadResource.cxx @@ -151,6 +151,9 @@ ReadResource::Result ReadResource::operateInternal() return this->createResult(smtk::operation::Operation::Outcome::FAILED); } + // Pass our application state in to the read operation: + readOperation->setManagers(this->managers()); + // Set the local reader's filename field. smtk::attribute::FileItem::Ptr readerFileItem = readOperation->parameters()->findFile( readerGroup.fileItemNameForOperation(readOperation->index())); diff --git a/smtk/project/CMakeLists.txt b/smtk/project/CMakeLists.txt index f03cc53e97..ceb0c8c413 100644 --- a/smtk/project/CMakeLists.txt +++ b/smtk/project/CMakeLists.txt @@ -78,6 +78,11 @@ set(projectDependencies ${_projectDependencies} PARENT_SCOPE) # Install the headers smtk_public_headers(smtkCore ${projectHeaders}) +if (SMTK_ENABLE_PARAVIEW_SUPPORT) + set_property(GLOBAL APPEND + PROPERTY _smtk_plugin_files "${CMAKE_CURRENT_SOURCE_DIR}/plugin/paraview.plugin") +endif() + if (SMTK_ENABLE_PYTHON_WRAPPING) list(APPEND projectSrcs RegisterPythonProject.cxx) diff --git a/smtk/project/Manager.cxx b/smtk/project/Manager.cxx index 80b988ac36..b460614eb7 100644 --- a/smtk/project/Manager.cxx +++ b/smtk/project/Manager.cxx @@ -276,6 +276,10 @@ smtk::project::Project::Ptr Manager::create( // Create the project with the appropriate UUID project = metadata->create(id, m); this->add(metadata->index(), project); + if (project) + { + project->taskManager().setManagers(m); + } } return project; @@ -294,6 +298,10 @@ smtk::project::Project::Ptr Manager::create( { // Create the project with the appropriate UUID project = metadata->create(id, mm); + if (project) + { + project->taskManager().setManagers(mm); + } this->add(index, project); } diff --git a/smtk/project/operators/Read.cxx b/smtk/project/operators/Read.cxx index 5f64958ab4..580d6480dd 100644 --- a/smtk/project/operators/Read.cxx +++ b/smtk/project/operators/Read.cxx @@ -96,7 +96,7 @@ Read::Result Read::operateInternal() // Create a new project for the import boost::filesystem::path projectFilePath(filename); - auto project = this->projectManager()->create(j.at("type").get()); + auto project = this->projectManager()->create(j.at("type").get(), this->managers()); if (project == nullptr) { smtkErrorMacro(log(), "project of type " << j.at("type") << " was not created."); diff --git a/smtk/project/plugin/CMakeLists.txt b/smtk/project/plugin/CMakeLists.txt new file mode 100644 index 0000000000..b517c8ee2c --- /dev/null +++ b/smtk/project/plugin/CMakeLists.txt @@ -0,0 +1,9 @@ +smtk_add_plugin(smtkProjectPlugin + REGISTRAR smtk::project::Registrar + MANAGERS smtk::project::Manager + smtk::common::Managers + smtk::operation::Manager + smtk::resource::Manager + smtk::view::Manager + PARAVIEW_PLUGIN_ARGS + VERSION 1.0) diff --git a/smtk/project/plugin/paraview.plugin b/smtk/project/plugin/paraview.plugin new file mode 100644 index 0000000000..ce41a9a4d6 --- /dev/null +++ b/smtk/project/plugin/paraview.plugin @@ -0,0 +1,4 @@ +NAME + smtkProjectPlugin +DESCRIPTION + SMTK project support for ParaView diff --git a/smtk/session/mesh/testing/xml/OpenExodusFile.xml b/smtk/session/mesh/testing/xml/OpenExodusFile.xml index 0ae9b1d2e2..56648560a3 100644 --- a/smtk/session/mesh/testing/xml/OpenExodusFile.xml +++ b/smtk/session/mesh/testing/xml/OpenExodusFile.xml @@ -7,4 +7,5 @@ + diff --git a/smtk/task/CMakeLists.txt b/smtk/task/CMakeLists.txt index ddb7438ddc..261abe6a60 100644 --- a/smtk/task/CMakeLists.txt +++ b/smtk/task/CMakeLists.txt @@ -17,6 +17,8 @@ set(taskSrcs Instances.cxx Manager.cxx Registrar.cxx + UIState.cxx + UIStateGenerator.cxx adaptor/ResourceAndRole.cxx json/Configurator.cxx json/Helper.cxx @@ -31,6 +33,8 @@ set(taskHeaders Manager.h Registrar.h State.h + UIState.h + UIStateGenerator.h adaptor/Instances.h adaptor/ResourceAndRole.h json/Configurator.h diff --git a/smtk/task/FillOutAttributes.cxx b/smtk/task/FillOutAttributes.cxx index 65ad40f008..68dcf64230 100644 --- a/smtk/task/FillOutAttributes.cxx +++ b/smtk/task/FillOutAttributes.cxx @@ -182,27 +182,55 @@ smtk::common::Visit FillOutAttributes::visitAttributeSets(AttributeSetVisitor vi bool FillOutAttributes::initializeResources() { + // By default, assume we are unconfigured: bool foundResource = false; if (m_attributeSets.empty()) { return foundResource; } + if (auto resourceManager = m_managers->get()) { - auto resources = resourceManager->find(); - for (const auto& resource : resources) + // Iterate attribute sets to see if any are configured with valid + // attribute resource and/or attribute UUIDs. If so and if we can + // find the matching resource in the manager, then we can return true. + bool anyAutoconfigure = false; + for (const auto& attributeSet : m_attributeSets) { - const std::string& role = smtk::project::detail::role(resource); - for (auto& attributeSet : m_attributeSets) + if (attributeSet.m_autoconfigure) { - if ( - attributeSet.m_role.empty() || attributeSet.m_role == "*" || attributeSet.m_role == role) + anyAutoconfigure = true; + } + for (const auto& resourceEntry : attributeSet.m_resources) + { + if (auto rsrc = resourceManager->get(resourceEntry.first)) { - if (attributeSet.m_autoconfigure) + foundResource = true; + } + } + } + + // If any attribute sets were marked "autoconfigure" (meaning we should + // identify any attribute resources with the proper role), then iterate + // resources in the resource manager and configure as required. + if (anyAutoconfigure) + { + auto resources = resourceManager->find(); + for (const auto& resource : resources) + { + const std::string& role = smtk::project::detail::role(resource); + for (auto& attributeSet : m_attributeSets) + { + if ( + attributeSet.m_role.empty() || attributeSet.m_role == "*" || + attributeSet.m_role == role) { - foundResource = true; - auto it = attributeSet.m_resources.insert({ resource->id(), { {}, {} } }).first; - this->updateResourceEntry(*resource, attributeSet, it->second); + if (attributeSet.m_autoconfigure) + { + foundResource = true; + auto it = attributeSet.m_resources.insert({ resource->id(), { {}, {} } }).first; + this->updateResourceEntry(*resource, attributeSet, it->second); + } } } } @@ -422,7 +450,6 @@ bool FillOutAttributes::hasRelevantInfomation( } State FillOutAttributes::computeInternalState() const { - std::cerr << "Computing new state\n"; auto resourceManager = m_managers->get(); if (!resourceManager) { diff --git a/smtk/task/Manager.cxx b/smtk/task/Manager.cxx index 4161258b79..43d5147bf3 100644 --- a/smtk/task/Manager.cxx +++ b/smtk/task/Manager.cxx @@ -25,5 +25,14 @@ Manager::Manager() Manager::~Manager() = default; +nlohmann::json Manager::getStyle(const smtk::string::Token& styleClass) const +{ + if (this->m_styles.contains(styleClass.data())) + { + return this->m_styles.at(styleClass.data()); + } + return nlohmann::json(); +} + } // namespace task } // namespace smtk diff --git a/smtk/task/Manager.h b/smtk/task/Manager.h index 2e1eef868d..0d8a39d6dd 100644 --- a/smtk/task/Manager.h +++ b/smtk/task/Manager.h @@ -17,13 +17,17 @@ #include "smtk/common/Managers.h" #include "smtk/common/TypeName.h" +#include "smtk/string/Token.h" #include "smtk/task/Active.h" #include "smtk/task/Adaptor.h" #include "smtk/task/Instances.h" #include "smtk/task/Task.h" +#include "smtk/task/UIState.h" #include "smtk/task/adaptor/Instances.h" +#include "nlohmann/json.hpp" + #include #include #include @@ -76,11 +80,21 @@ public: smtk::common::Managers::Ptr managers() const { return m_managers.lock(); } void setManagers(const smtk::common::Managers::Ptr& managers) { m_managers = managers; } + /// Given a style key, return a style config. + nlohmann::json getStyle(const smtk::string::Token& styleClass) const; + nlohmann::json getStyles() const { return m_styles; }; + void setStyles(const nlohmann::json& styles) { m_styles = styles; } + + /// Store geometry changes from UI components + UIState& uiState() { return m_uiState; } + private: TaskInstances m_taskInstances; AdaptorInstances m_adaptorInstances; Active m_active; std::weak_ptr m_managers; + nlohmann::json m_styles; + UIState m_uiState; }; } // namespace task } // namespace smtk diff --git a/smtk/task/State.h b/smtk/task/State.h index 59e13566ad..9b8a8a6a73 100644 --- a/smtk/task/State.h +++ b/smtk/task/State.h @@ -45,8 +45,12 @@ inline std::string stateName(const State& s) } /// A type-conversion operation to cast strings to enumerants. -inline State stateEnum(const std::string& s) +inline State stateEnum(const std::string& s, bool* valid = nullptr) { + if (valid) + { + *valid = true; + } std::string stateName(s); std::transform(stateName.begin(), stateName.end(), stateName.begin(), [](unsigned char c) { return std::tolower(c); @@ -71,6 +75,10 @@ inline State stateEnum(const std::string& s) { return State::Completed; } + if (valid) + { + *valid = (stateName == "irrelevant"); + } return State::Irrelevant; } diff --git a/smtk/task/Task.h b/smtk/task/Task.h index e2e8f92685..1a9b81daf9 100644 --- a/smtk/task/Task.h +++ b/smtk/task/Task.h @@ -266,6 +266,9 @@ public: /// inherent for the task itself. State internalState() const { return m_internalState; } + /// Return the tasks's manager (or null if unmanaged). + Manager* manager() const { return m_manager.lock().get(); } + protected: friend SMTKCORE_EXPORT void workflowsOfTask(Task*, std::set&, std::set&); diff --git a/smtk/task/UIState.cxx b/smtk/task/UIState.cxx new file mode 100644 index 0000000000..8948324262 --- /dev/null +++ b/smtk/task/UIState.cxx @@ -0,0 +1,79 @@ +//========================================================================= +// Copyright (c) Kitware, Inc. +// All rights reserved. +// See LICENSE.txt for details. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notice for more information. +//========================================================================= +#include "smtk/task/UIState.h" + +#include "smtk/io/Logger.h" +#include "smtk/task/Task.h" + +namespace smtk +{ +namespace task +{ + +void UIState::setData(std::shared_ptr task, const nlohmann::json& j) +{ + for (auto& entry : j.items()) + { + smtk::string::Token classToken = entry.key(); + m_data[classToken][task->id()] = entry.value(); + } +} + +nlohmann::json UIState::getData(smtk::string::Token classToken, smtk::task::Task* task) const +{ + auto classIter = m_data.find(classToken); + if (classIter == m_data.end()) + { + return nlohmann::json(); + } + + auto uiIter = classIter->second.find(task->id()); + if (uiIter == classIter->second.end()) + { + return nlohmann::json::object(); + } + + return uiIter->second; +} + +void UIState::updateJson(std::shared_ptr task, nlohmann::json& j) const +{ + auto jUI = nlohmann::json::object(); + if (j.contains("ui")) + { + jUI = j["ui"]; + } + + for (const auto& entry : m_generators) + { + jUI[entry.first] = entry.second->taskState(task); + } + + j["ui"] = jUI; +} + +void UIState::dump(std::ostream& os) +{ + os << '\n'; + auto classIter = m_data.begin(); + for (; classIter != m_data.end(); ++classIter) + { + os << classIter->first.data() << '\n'; + auto configIter = classIter->second.begin(); + for (; configIter != classIter->second.end(); ++configIter) + { + os << " " << configIter->first.data() << ", " << configIter->second << '\n'; + } + } + os << std::endl; +} + +} // namespace task +} // namespace smtk diff --git a/smtk/task/UIState.h b/smtk/task/UIState.h new file mode 100644 index 0000000000..5e8caab912 --- /dev/null +++ b/smtk/task/UIState.h @@ -0,0 +1,67 @@ +//========================================================================= +// Copyright (c) Kitware, Inc. +// All rights reserved. +// See LICENSE.txt for details. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notice for more information. +//========================================================================= +#ifndef smtk_task_UIState_h +#define smtk_task_UIState_h + +#include "smtk/CoreExports.h" + +#include "smtk/string/Token.h" +#include "smtk/task/UIStateGenerator.h" + +#include "nlohmann/json.hpp" + +#include +#include +#include + +namespace smtk +{ +namespace task +{ +class Task; + +/**\brief Stores UI state data for task UI classes. */ +class SMTKCORE_EXPORT UIState +{ +public: + UIState() = default; + ~UIState() = default; + + /** \brief Stores "ui" object for specified task. */ + void setData(std::shared_ptr task, const nlohmann::json& j); + + /** \brief Returns "ui" data for given class and task. */ + nlohmann::json getData(smtk::string::Token classToken, smtk::task::Task* task) const; + + /** \brief Stores generator for given class name. */ + void setGenerator(const std::string& className, std::shared_ptr generator) + { + m_generators[className] = generator; + } + + /** \brief Updates "ui" object to include data from all generators. */ + void updateJson(std::shared_ptr task, nlohmann::json& j) const; + + /** \brief Writes contents of "ui" objects stored for each task and class */ + void dump(std::ostream& os); + +protected: + /** \brief Nested map of <, > for deserialized UI state data. */ + std::unordered_map> + m_data; + + /** \brief Map of for serializing UI state data. */ + std::unordered_map> m_generators; +}; + +} // namespace task +} // namespace smtk + +#endif diff --git a/smtk/task/UIStateGenerator.cxx b/smtk/task/UIStateGenerator.cxx new file mode 100644 index 0000000000..237c738755 --- /dev/null +++ b/smtk/task/UIStateGenerator.cxx @@ -0,0 +1,20 @@ +//========================================================================= +// Copyright (c) Kitware, Inc. +// All rights reserved. +// See LICENSE.txt for details. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notice for more information. +//========================================================================= +#include "smtk/task/UIStateGenerator.h" + +namespace smtk +{ +namespace task +{ + +UIStateGenerator::UIStateGenerator() = default; + +} // namespace task +} // namespace smtk diff --git a/smtk/task/UIStateGenerator.h b/smtk/task/UIStateGenerator.h new file mode 100644 index 0000000000..d6262e5c3b --- /dev/null +++ b/smtk/task/UIStateGenerator.h @@ -0,0 +1,43 @@ +//========================================================================= +// Copyright (c) Kitware, Inc. +// All rights reserved. +// See LICENSE.txt for details. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notice for more information. +//========================================================================= +#ifndef smtk_task_UIStateGenerator_h +#define smtk_task_UIStateGenerator_h + +#include "smtk/CoreExports.h" + +#include "smtk/task/Task.h" + +#include "nlohmann/json.hpp" + +namespace smtk +{ +namespace task +{ + +/**\brief Pure virtual class for read/write UI state data. */ + +class SMTKCORE_EXPORT UIStateGenerator +{ +public: + UIStateGenerator(); + ~UIStateGenerator() = default; + + /** \brief Returns state not tied to a particular task (e.g. background color). */ + virtual nlohmann::json globalState() const = 0; + /** \brief Returns state attached to a particular \a task. */ + virtual nlohmann::json taskState(const std::shared_ptr& task) const = 0; + +protected: +}; + +} // namespace task +} // namespace smtk + +#endif diff --git a/smtk/task/json/Configurator.h b/smtk/task/json/Configurator.h index d5e478f125..06c0f77db0 100644 --- a/smtk/task/json/Configurator.h +++ b/smtk/task/json/Configurator.h @@ -130,6 +130,11 @@ public: /// This will allocate a new ID if none exists. SwizzleId swizzleId(const ObjectType* object); + /// When deserializing an object, we have the swizzle ID assigned previously. + /// Accept the given ID; if it already exists, then return false and print + /// a warning. Otherwise, assign the given ID and return true. + bool setSwizzleId(const ObjectType* object, SwizzleId swizzle); + /// Return the pointer to an object given its swizzled ID (or null). ObjectType* unswizzle(SwizzleId objectId) const; diff --git a/smtk/task/json/Configurator.txx b/smtk/task/json/Configurator.txx index 69c0778a68..95bd0e8a20 100644 --- a/smtk/task/json/Configurator.txx +++ b/smtk/task/json/Configurator.txx @@ -13,6 +13,8 @@ #include "smtk/task/json/Configurator.h" #include "smtk/task/json/Helper.h" +#include "smtk/io/Logger.h" + namespace smtk { namespace task @@ -195,6 +197,37 @@ typename Configurator::SwizzleId Configurator::s return id; } +/// Assign a previously-provided swizzle ID to the object. +/// This will warn and return false if the ID already exists. +template +bool Configurator::setSwizzleId( + const ObjectType* object, + typename Configurator::SwizzleId swizzle) +{ + if (!object) + { + return false; + } + auto bit = m_swizzleBck.find(swizzle); + if (bit != m_swizzleBck.end()) + { + smtkWarningMacro( + smtk::io::Logger::instance(), + "Deserialized swizzle ID " << swizzle << " is already assigned to" + << "\"" << bit->second->title() << "\" " << bit->second + << ". Skipping."); + return false; + } + auto* ncobject = const_cast(object); // Need a non-const ObjectType in some cases. + m_swizzleFwd[ncobject] = swizzle; + m_swizzleBck[swizzle] = ncobject; + if (swizzle >= m_nextSwizzle) + { + m_nextSwizzle = swizzle + 1; + } + return true; +} + /// Return the pointer to an object given its swizzled ID (or null). template ObjectType* Configurator::unswizzle(SwizzleId objectId) const diff --git a/smtk/task/json/Helper.cxx b/smtk/task/json/Helper.cxx index 37666fbd92..8584b7a5bd 100644 --- a/smtk/task/json/Helper.cxx +++ b/smtk/task/json/Helper.cxx @@ -11,6 +11,7 @@ #include "smtk/task/json/Configurator.txx" #include "smtk/io/Logger.h" +#include "smtk/task/Manager.h" #include #include @@ -210,6 +211,17 @@ Task* Helper::activeSerializedTask() const return m_activeSerializedTask; } +void Helper::updateUIState(std::shared_ptr task, nlohmann::json& j) +{ + if (m_taskManager == nullptr) + { + return; + } + + auto& uiState = m_taskManager->uiState(); + uiState.updateJson(task, j); +} + } // namespace json } // namespace task } // namespace smtk diff --git a/smtk/task/json/Helper.h b/smtk/task/json/Helper.h index 019e65bd65..581cb808cd 100644 --- a/smtk/task/json/Helper.h +++ b/smtk/task/json/Helper.h @@ -135,6 +135,9 @@ public: void setActiveSerializedTask(Task* task); Task* activeSerializedTask() const; + // Insert UI states (if any) for given task + void updateUIState(std::shared_ptr task, nlohmann::json& j); + protected: Helper(); Helper(Manager*); diff --git a/smtk/task/json/jsonGatherResources.cxx b/smtk/task/json/jsonGatherResources.cxx index f6af8f7296..e134d0ae49 100644 --- a/smtk/task/json/jsonGatherResources.cxx +++ b/smtk/task/json/jsonGatherResources.cxx @@ -74,9 +74,11 @@ void from_json(const nlohmann::json& j, GatherResources::ResourceSet& resourceSe if (resource) { resourceSet.m_resources.insert(resource); + warnOnIds = false; } else { + warnOnIds = true; smtkWarningMacro( smtk::io::Logger::instance(), "Resource \"" << jsonId << "\" not found."); } @@ -87,6 +89,10 @@ void from_json(const nlohmann::json& j, GatherResources::ResourceSet& resourceSe warnOnIds = true; } } + else + { + warnOnIds = true; + } if (warnOnIds) { smtkWarningMacro( diff --git a/smtk/task/json/jsonManager.cxx b/smtk/task/json/jsonManager.cxx index 7e0c8a976c..ead0972785 100644 --- a/smtk/task/json/jsonManager.cxx +++ b/smtk/task/json/jsonManager.cxx @@ -48,19 +48,35 @@ void from_json(const nlohmann::json& jj, Manager& taskManager) auto taskId = jsonTask.at("id").get(); Task::Ptr task = jsonTask; taskMap[taskId] = task; - helper.tasks().swizzleId(task.get()); + helper.tasks().setSwizzleId(task.get(), taskId); } - // Do a second pass to deserialize dependencies. + // Do a second pass to deserialize dependencies and UI config. for (const auto& jsonTask : jj.at("tasks")) { + auto taskId = jsonTask.at("id").get(); + auto task = taskMap[taskId]; if (jsonTask.contains("dependencies")) { - auto taskId = jsonTask.at("id").get(); - auto task = taskMap[taskId]; auto taskDeps = helper.unswizzleDependencies(jsonTask.at("dependencies")); + // Make sure this is not its own dependencies + auto finder = taskDeps.find(task); + if (finder != taskDeps.end()) + { + smtkWarningMacro( + smtk::io::Logger::instance(), task->title() << " trying to set deps to itself"); + taskDeps.erase(task); + } + task->addDependencies(taskDeps); } + + // Get UI object + if (jsonTask.contains("ui")) + { + taskManager.uiState().setData(task, jsonTask["ui"]); + } } + // Now configure dependent tasks with adaptors if specified. // Note that tasks have already been deserialized, so the // helper's map from task-id to task-pointer is complete. @@ -90,6 +106,10 @@ void from_json(const nlohmann::json& jj, Manager& taskManager) } } } + if (jj.contains("styles")) + { + taskManager.setStyles(jj.at("styles")); + } // helper.clear(); } @@ -113,7 +133,11 @@ void to_json(nlohmann::json& jj, const Manager& manager) // Only serialize top-level tasks. (Tasks with children are responsible // for serializing their children). nlohmann::json jsonTask = task; - taskList.push_back(jsonTask); + if (!jsonTask.is_null()) + { + helper.updateUIState(task, jsonTask); + taskList.push_back(jsonTask); + } } return smtk::common::Visit::Continue; }); @@ -134,6 +158,7 @@ void to_json(nlohmann::json& jj, const Manager& manager) return smtk::common::Visit::Continue; }); jj["adaptors"] = adaptorList; + jj["styles"] = manager.getStyles(); } } // namespace task diff --git a/smtk/task/pybind11/PybindState.h b/smtk/task/pybind11/PybindState.h index 61a2bf020a..51ee7e46ae 100644 --- a/smtk/task/pybind11/PybindState.h +++ b/smtk/task/pybind11/PybindState.h @@ -34,7 +34,7 @@ inline void pybind11_init_smtk_task_stateName(py::module &m) inline void pybind11_init_smtk_task_stateEnum(py::module &m) { - m.def("stateEnum", &smtk::task::stateEnum, "", py::arg("s")); + m.def("stateEnum", &smtk::task::stateEnum, "", py::arg("s"), py::arg("matched")); } #endif diff --git a/smtk/task/testing/cxx/CMakeLists.txt b/smtk/task/testing/cxx/CMakeLists.txt index 34fe555948..176e26dbc9 100644 --- a/smtk/task/testing/cxx/CMakeLists.txt +++ b/smtk/task/testing/cxx/CMakeLists.txt @@ -3,6 +3,7 @@ set(unit_tests TestTaskBasics.cxx TestTaskGroup.cxx TestTaskJSON.cxx + TestTaskUIState.cxx ) find_package(Threads REQUIRED) diff --git a/smtk/task/testing/cxx/TestTaskUIState.cxx b/smtk/task/testing/cxx/TestTaskUIState.cxx new file mode 100644 index 0000000000..e115a5792e --- /dev/null +++ b/smtk/task/testing/cxx/TestTaskUIState.cxx @@ -0,0 +1,83 @@ +//========================================================================= +// Copyright (c) Kitware, Inc. +// All rights reserved. +// See LICENSE.txt for details. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notice for more information. +//========================================================================= + +#include "smtk/common/Managers.h" +#include "smtk/plugin/Registry.h" +#include "smtk/resource/json/Helper.h" +#include "smtk/task/Manager.h" +#include "smtk/task/Registrar.h" +#include "smtk/task/Task.h" +#include "smtk/task/UIState.h" +#include "smtk/task/UIStateGenerator.h" +#include "smtk/task/json/Helper.h" +#include "smtk/task/json/jsonManager.h" +#include "smtk/task/json/jsonTask.h" + +#include "smtk/common/testing/cxx/helpers.h" + +#include "nlohmann/json.hpp" + +#include + +namespace +{ +class TaskUIStateGenerator : public smtk::task::UIStateGenerator +{ +public: + TaskUIStateGenerator() = default; + virtual ~TaskUIStateGenerator() = default; + + nlohmann::json globalState() const override { return nlohmann::json(); } + nlohmann::json taskState(const std::shared_ptr& task) const override + { + (void)task; + nlohmann::json state = R"({ "test": "passed" })"_json; + return state; + } +}; +} // namespace + +int TestTaskUIState(int, char*[]) +{ + // Create managers + auto managers = smtk::common::Managers::create(); + auto taskRegistry = smtk::plugin::addToManagers(managers); + + auto taskManager = smtk::task::Manager::create(); + auto taskTaskRegistry = smtk::plugin::addToManagers(taskManager); + + // Add UI state generator + std::shared_ptr gen(new TaskUIStateGenerator); + auto baseGen = std::dynamic_pointer_cast(gen); + taskManager->uiState().setGenerator("TaskUIStateGenerator", baseGen); + + // Create task + std::shared_ptr t1 = taskManager->taskInstances().create( + smtk::task::Task::Configuration{ { "title", "Task 1" } }, *taskManager, managers); + smtkTest(t1 != nullptr, "failed to create task."); + + // Generate json object + auto& resourceHelper = smtk::resource::json::Helper::instance(); + resourceHelper.setManagers(managers); + auto& taskHelper = + smtk::task::json::Helper::pushInstance(*taskManager, resourceHelper.managers()); + taskHelper.setManagers(resourceHelper.managers()); + nlohmann::json j = *taskManager; + smtk::task::json::Helper::popInstance(); + std::cout << j << std::endl; + + // Verify that ui content was generated + auto jtest = j["tasks"][0]["ui"]["TaskUIStateGenerator"]["test"]; + smtkTest(jtest.get() == "passed", "did not find \"passed\" field."); + + // Future: come up with test reading json object to initialize (second) task manager. + + return 0; +} -- GitLab From e7e0bb2f09e687ffe183bea053d54e81c9feca61 Mon Sep 17 00:00:00 2001 From: Robert O'Bara Date: Mon, 3 Apr 2023 15:38:02 -0400 Subject: [PATCH 13/15] Compile release notes for 23.04.0 --- ReadMe.md | 2 +- doc/release/index.rst | 1 + doc/release/notes/InstancedViewChanges.rst | 10 -- doc/release/notes/operation-hints.rst | 11 -- .../notes/operation_dialog_allow_nonmodal.rst | 6 - doc/release/notes/paths-exception.rst | 7 -- doc/release/notes/string-token-api.rst | 12 -- doc/release/notes/task-manager.rst | 26 ---- doc/release/notes/task-panel.rst | 17 --- doc/release/smtk-23.04.rst | 116 ++++++++++++++++++ 10 files changed, 118 insertions(+), 90 deletions(-) delete mode 100644 doc/release/notes/InstancedViewChanges.rst delete mode 100644 doc/release/notes/operation-hints.rst delete mode 100644 doc/release/notes/operation_dialog_allow_nonmodal.rst delete mode 100644 doc/release/notes/paths-exception.rst delete mode 100644 doc/release/notes/string-token-api.rst delete mode 100644 doc/release/notes/task-manager.rst delete mode 100644 doc/release/notes/task-panel.rst create mode 100644 doc/release/smtk-23.04.rst diff --git a/ReadMe.md b/ReadMe.md index a400648efb..1fb44d91d5 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -62,7 +62,7 @@ See [CONTRIBUTING.md][] for instructions to contribute. Latest Release Notes ==================== -Can be found [here](doc/release/smtk-23.01.rst). +Can be found [here](doc/release/smtk-23.04.rst). License ======= diff --git a/doc/release/index.rst b/doc/release/index.rst index 83be961bc9..69fe7b11aa 100644 --- a/doc/release/index.rst +++ b/doc/release/index.rst @@ -5,6 +5,7 @@ Release notes .. toctree:: :maxdepth: 2 + smtk-23.04.rst smtk-23.01.rst smtk-22.11.rst smtk-22.08.rst diff --git a/doc/release/notes/InstancedViewChanges.rst b/doc/release/notes/InstancedViewChanges.rst deleted file mode 100644 index 5f878f4cc0..0000000000 --- a/doc/release/notes/InstancedViewChanges.rst +++ /dev/null @@ -1,10 +0,0 @@ -Adding ID Support to Instanced Views ------------------------------------- - -You can now refer to an Attribute in an Instanced View by its **ID**. This will allow the View to refer to -the Attribute even if its Name is changed later on. - -The View will add the **ID** View Configuration information if it doesn't already exist and will use it -in the future to find the Attribute. - -If the **ID** is not specified, the view will continue require the **Name** configuration view attribute to be specified (along with the **Type** configuration view attribute if the named Attribute does not currently exists). diff --git a/doc/release/notes/operation-hints.rst b/doc/release/notes/operation-hints.rst deleted file mode 100644 index aee9d49bbf..0000000000 --- a/doc/release/notes/operation-hints.rst +++ /dev/null @@ -1,11 +0,0 @@ -Operation hint for switching the active task --------------------------------------------- - -Any operation can now request the active task be switched -by providing a hint on its result attribute. -Use the ``smtk::operation::addActivateTaskHint()`` function -to add the hint before your operation completes. -Then, the :smtk:`pqSMTKOperationHintsBehavior` object will -observe when the operation has completed and process the -hint and attempt to switch to the matching task. -See ``smtk::project::Read`` for an example. diff --git a/doc/release/notes/operation_dialog_allow_nonmodal.rst b/doc/release/notes/operation_dialog_allow_nonmodal.rst deleted file mode 100644 index 4e76c3e185..0000000000 --- a/doc/release/notes/operation_dialog_allow_nonmodal.rst +++ /dev/null @@ -1,6 +0,0 @@ -Allow qtOperationDialog to be non-modal ---------------------------------------- - -If the qtOperationDialog is shown non-modal, using `show()` instead of `exec()`, -the Apply button doesn't close the dialog, but instead will remain active and -allow the operation to be run again. diff --git a/doc/release/notes/paths-exception.rst b/doc/release/notes/paths-exception.rst deleted file mode 100644 index 800df564a0..0000000000 --- a/doc/release/notes/paths-exception.rst +++ /dev/null @@ -1,7 +0,0 @@ -Common directory utility ------------------------- - -Previously, the :smtk:`smtk::common::Paths::directory` method would -throw a boost filesystem exception if a user called it without -permission to the path passed to it. This has been fixed by capturing -the exception internally. diff --git a/doc/release/notes/string-token-api.rst b/doc/release/notes/string-token-api.rst deleted file mode 100644 index 7256bcbd09..0000000000 --- a/doc/release/notes/string-token-api.rst +++ /dev/null @@ -1,12 +0,0 @@ -String token API ----------------- - -String tokens now have additional API: -+ :smtk:`Token::hasValue() ` - returns true the string manager contains a string for the token's integer hash. - Note that tokens constructed at compiled-time via the string-literal operator - will not insert the source string into the manager. -+ :smtk:`Token::valid() ` - returns true if the token has a valid value. - The string manager reserves a special value (``smtk::string::Manager::Invalid``) - to indicate an uninitialized or unavailable hash. diff --git a/doc/release/notes/task-manager.rst b/doc/release/notes/task-manager.rst deleted file mode 100644 index 75bbdea7c6..0000000000 --- a/doc/release/notes/task-manager.rst +++ /dev/null @@ -1,26 +0,0 @@ -Task subsystem changes ----------------------- - -The task manager is no longer considered part of an application's state. -Instead of expecting an application's :smtk:`Managers ` instance to -hold a single task manager, each :smtk:`Project ` owns its own -task manager. - -As part of this change, the project read and write operations now include a serialization of -the project's task manager. This means that the task system JSON state is now properly -serialized. - -Another part of this change removes the :smtk:`smtk::task::json::jsonManager` structure -with its ``serialize()`` and ``deserialize()`` methods. You should replace calls to -these methods with calls to ``to_json()`` and ``from_json()``, respectively. -Furthermore, you are responsible for pushing an instance of the -:smtk:`task helper ` before these calls and popping the instance -afterward. -See the task tests for examples of this. - -Finally, because :smtk:`smtk::common::Managers` no longer contains an application-wide -instance of a :smtk:`smtk::task::Manager`, the signature for :smtk:`Task ` -constructors is changed to additionally accept a parent task manager. -The old signatures will generate compile- and run-time warnings. -The constructors still accept a :smtk:`smtk::common::Managers` since tasks may wish -to monitor the application to determine their state. diff --git a/doc/release/notes/task-panel.rst b/doc/release/notes/task-panel.rst deleted file mode 100644 index d61dd96a6e..0000000000 --- a/doc/release/notes/task-panel.rst +++ /dev/null @@ -1,17 +0,0 @@ -User interface for task-based workflows ---------------------------------------- - -SMTK now provides a ParaView plugin which adds a :smtk:`task panel ` -similar to ParaView's node editor. Each task is shown as a node in a graph and -may be made active, marked complete (or incomplete), and manually placed by the user. -Each task may have a collection of style keywords associated with it and the -task manager holds settings for these keywords that affect the application state -when matching tasks are made active. - -In particular, SMTK's attribute-editor panel can be directed to display any view -held by attribute resource when a related :smtk:`smtk::task::FillOutAttributes` task -becomes active. - -This functionality is a preview and still under development; you should expect -changes to user-interface classes that may break backwards compatibility while -this development takes place. diff --git a/doc/release/smtk-23.04.rst b/doc/release/smtk-23.04.rst new file mode 100644 index 0000000000..04035d3da1 --- /dev/null +++ b/doc/release/smtk-23.04.rst @@ -0,0 +1,116 @@ +.. _release-notes-23.04: + +========================= +SMTK 23.04 Release Notes +========================= + +See also :ref:`release-notes-23.01` for previous changes. + + +SMTK Common Related Changes +===================================== + +Common Directory Utility +------------------------ + +Previously, the :smtk:`smtk::common::Paths::directory` method would +throw a boost filesystem exception if a user called it without +permission to the path passed to it. This has been fixed by capturing +the exception internally. + +Expanding String token API +-------------------------- + +String tokens now have additional API: + ++ :smtk:`Token::hasValue() ` + returns true the string manager contains a string for the token's integer hash. + Note that tokens constructed at compiled-time via the string-literal operator + will not insert the source string into the manager. ++ :smtk:`Token::valid() ` + returns true if the token has a valid value. + The string manager reserves a special value (``smtk::string::Manager::Invalid``) + to indicate an uninitialized or unavailable hash. + + +SMTK UI Related Changes +======================= + +Adding ID Support to Instanced Views +------------------------------------ + +You can now refer to an Attribute in an Instanced View by its **ID**. This will allow the View to refer to +the Attribute even if its Name is changed later on. + +The View will add the **ID** View Configuration information if it doesn't already exist and will use it +in the future to find the Attribute. + +If the **ID** is not specified, the view will continue require the **Name** configuration view attribute to be specified (along with the **Type** configuration view attribute if the named Attribute does not currently exists). + +Allow qtOperationDialog to be non-modal +--------------------------------------- + +The qtOperationDialog can now be shown non-modal, using `show()` instead of `exec()`. When used as a non-model dialog, +the Apply button doesn't close the dialog, but instead will remain active and +allow the operation to be run again. + + +Changes to SMTK's Task Subsystem +================================ + +User Interface for Task-based Workflows (Preview) +------------------------------------------------- + +SMTK now provides a ParaView plugin which adds a :smtk:`task panel ` +similar to ParaView's node editor. Each task is shown as a node in a graph and +may be made active, marked complete (or incomplete), and manually placed by the user. +Each task may have a collection of style keywords associated with it and the +task manager holds settings for these keywords that affect the application state +when matching tasks are made active. + +In particular, SMTK's attribute-editor panel can be directed to display any view +held by attribute resource when a related :smtk:`smtk::task::FillOutAttributes` task +becomes active. + +This functionality is a preview and still under development; you should expect +changes to user-interface classes that may break backwards compatibility while +this development takes place. + +Operation Hint for Switching the Active Task +-------------------------------------------- + +Any operation can now request the active task be switched +by providing a hint on its result attribute. +Use the ``smtk::operation::addActivateTaskHint()`` function +to add the hint before your operation completes. +Then, the :smtk:`pqSMTKOperationHintsBehavior` object will +observe when the operation has completed and process the +hint and attempt to switch to the matching task. +See ``smtk::project::Read`` for an example. + +Task Management Changes +------------------------ + +The task manager is no longer considered part of an application's state. +Instead of expecting an application's :smtk:`Managers ` instance to +hold a single task manager, each :smtk:`Project ` owns its own +task manager. + +As part of this change, the project read and write operations now include a serialization of +the project's task manager. This means that the task system JSON state is now properly +serialized. + +Another part of this change removes the :smtk:`smtk::task::json::jsonManager` structure +with its ``serialize()`` and ``deserialize()`` methods. You should replace calls to +these methods with calls to ``to_json()`` and ``from_json()``, respectively. +Furthermore, you are responsible for pushing an instance of the +:smtk:`task helper ` before these calls and popping the instance +afterward. +See the task tests for examples of this. + +Finally, because :smtk:`smtk::common::Managers` no longer contains an application-wide +instance of a :smtk:`smtk::task::Manager`, the signature for :smtk:`Task ` +constructors is changed to additionally accept a parent task manager. +The old signatures will generate compile- and run-time warnings. +The constructors still accept a :smtk:`smtk::common::Managers` since tasks may wish +to monitor the application to determine their state. -- GitLab From 95553c54953cc294905f3ecf74a4456ff1c5de2e Mon Sep 17 00:00:00 2001 From: Robert O'Bara Date: Mon, 3 Apr 2023 15:46:26 -0400 Subject: [PATCH 14/15] Update version number to 23.04.0 --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 911dbc7221..92f023e6ed 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -23.01.100 +23.04.0 -- GitLab From 315563cc783aad8fc390cb909292debd0e54ee78 Mon Sep 17 00:00:00 2001 From: Robert O'Bara Date: Mon, 3 Apr 2023 15:47:24 -0400 Subject: [PATCH 15/15] Update cdash-groups.json to track the release group --- .gitlab/ci/cdash-groups.json | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/.gitlab/ci/cdash-groups.json b/.gitlab/ci/cdash-groups.json index 250ce75f62..a5ec087acd 100644 --- a/.gitlab/ci/cdash-groups.json +++ b/.gitlab/ci/cdash-groups.json @@ -1,62 +1,62 @@ { - "latest-master": [ + "latest-release": [ { - "group": "master", + "group": "release", "site": "gitlab-ci", "buildname": "fedora33_asan" }, { - "group": "master", + "group": "release", "site": "gitlab-ci", "buildname": "fedora33_coverage" }, { - "group": "master", + "group": "release", "site": "gitlab-ci", "buildname": "fedora33_nodata" }, { - "group": "master", + "group": "release", "site": "gitlab-ci", "buildname": "fedora33_paraview" }, { - "group": "master", + "group": "release", "site": "gitlab-ci", "buildname": "fedora33_paraview59_compat" }, { - "group": "master", + "group": "release", "site": "gitlab-ci", "buildname": "fedora33_plain" }, { - "group": "master", + "group": "release", "site": "gitlab-ci", "buildname": "fedora33_tidy" }, { - "group": "master", + "group": "release", "site": "gitlab-ci", "buildname": "fedora33_ubsan" }, { - "group": "master", + "group": "release", "site": "gitlab-ci", "buildname": "fedora33_vtk_python3" }, { - "group": "master", + "group": "release", "site": "gitlab-ci", "buildname": "macos_arm64" }, { - "group": "master", + "group": "release", "site": "gitlab-ci", "buildname": "macos_x86_64" }, { - "group": "master", + "group": "release", "site": "gitlab-ci", "buildname": "windows_vs2022_ninja" } -- GitLab