diff --git a/data/model/3d/genesis/casting-mesh1.gen b/data/model/3d/genesis/casting-mesh1.gen new file mode 100644 index 0000000000000000000000000000000000000000..9d3c1de6f66207b61eac8efad63eea9c99924bd7 --- /dev/null +++ b/data/model/3d/genesis/casting-mesh1.gen @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0282fa38d5ccf64d570b34e906930393ce495b0ba4af263dcdd87771dbe32b0b +size 3642015 diff --git a/doc/release/notes/common-observer-priority.rst b/doc/release/notes/common-observer-priority.rst new file mode 100644 index 0000000000000000000000000000000000000000..c142f4e37de86b403d23f6643a84a8a6cf2374a7 --- /dev/null +++ b/doc/release/notes/common-observer-priority.rst @@ -0,0 +1,15 @@ +Default observer priority +------------------------- + +The :smtk:`smtk::common::Observers` template now uses a default priority +of 0 instead of ``std::numeric_limits::lowest()`` (which is a negative number). +Any code that inserts observers using the signature that does not take an explicit +priority may now invoke that observer earlier than in previous releases of SMTK. +If you wish to maintain the old behavior, you must now explicitly pass a priority. + +The :smtk:`smtk::common::Observers` template now provides methods +named ``defaultPriority()`` and ``lowestPriority()`` for your convenience. + +This change was made to facilitate observers provided by SMTK that need to ensure +they are the last invoked after an operation since they release objects from managers +and may invalidate the operation result object. diff --git a/doc/release/notes/operation-toolbox.rst b/doc/release/notes/operation-toolbox.rst new file mode 100644 index 0000000000000000000000000000000000000000..fc945304a4b8817dd8818a5d92f709f8abdb566f --- /dev/null +++ b/doc/release/notes/operation-toolbox.rst @@ -0,0 +1,7 @@ +Operation Toolbox +----------------- + +The qtOperationPalette widget now accepts an "AlwaysFilter" configuration +parameter; when absent or false, a checkbox (labeled "All") appears in +the controls and can be used to display every registered operation without +any decoration. diff --git a/doc/release/notes/paraview-close-resource.rst b/doc/release/notes/paraview-close-resource.rst new file mode 100644 index 0000000000000000000000000000000000000000..c7a78daaecfeaa0055b2bb632d8b5c1f19947925 --- /dev/null +++ b/doc/release/notes/paraview-close-resource.rst @@ -0,0 +1,31 @@ +ParaView extensions +------------------- + +Closing resources now behaves differently +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Previously SMTK's "File→Close Resource" menu item would close the +single resource whose ParaView pipeline source object was active. +This was problematic for several reasons: ++ Due to recent changes, not all SMTK resources have pipeline sources + (particularly, those with no renderable geometry). ++ The user interface does not always make it clear which pipeline + source is active (because modelbuilder hides the pipeline browser + panel by default). ++ It was not possible to close multiple resources at once. + +This has been changed so that ++ A set of resources is extracted from the SMTK selection (using the + "selected" value label); all of these resources will be closed. ++ Because the SMTK selection is used, resources with no renderable + geometry can be closed. ++ Closing a project now properly removes it from the project manager. ++ If resources are owned by a project, they will not be closed unless + their owning project was also selected to be closed. ++ Users can choose whether to discard modified resources once (at + the beginning of the process); if the user elects to save resources + but then cancels during saving a resource, no further resources + will be closed. ++ The :smtk:`pqSMTKSaveResourceBehavior` has been refactored to + provide additional API that does not require ParaView pipelines + as inputs; the original API remains. diff --git a/doc/release/notes/project-manager.rst b/doc/release/notes/project-manager.rst new file mode 100644 index 0000000000000000000000000000000000000000..a091345acc01cf5ab522c61196b1c4ce00e43464 --- /dev/null +++ b/doc/release/notes/project-manager.rst @@ -0,0 +1,22 @@ +Project subsystem +----------------- + +Project manager changes +~~~~~~~~~~~~~~~~~~~~~~~ + +The project manager no longer automatically manages projects as they are created. + +Instead, the project manager observes operations which create projects and manage +any new projects upon completion. This matches the pattern set by the resource +manager and avoids observers being fired during operations when the project may +not be in a valid state. + +If your code explicitly calls ``smtk::project::Manager::create(typeName)`` outside +of an operation, you now need to explicitly ``add()`` the project to the manager. +If you call ``create(typeName)`` inside an operation, you must be sure to add the +project to your operation's Result (in a ReferenceItem) so it can be added. +If you call ``smtk::project::Manager::remove(project)`` inside an operation, you +should not do so any longer. Instead, you should add the project to the +operation-result's ``resourcesToExpunge`` ReferenceItem. The base Operation class +will remove the project from its manager after the operation's observers have been +invoked to properly order Operation and Resource observers before Project observers. diff --git a/doc/tutorials/create_a_project/create_a_project.cxx b/doc/tutorials/create_a_project/create_a_project.cxx index 0429c9e7cd4b6e7a1edbd7dad69237c55a347d01..f9a68596f7109d41a7631a6bd81f9198cbb9f686 100644 --- a/doc/tutorials/create_a_project/create_a_project.cxx +++ b/doc/tutorials/create_a_project/create_a_project.cxx @@ -48,6 +48,9 @@ int main(int /*argc*/, char* /*argv*/[]) // Create project with type "basic", a generic type that can be loaded into modelbuilder by default. projManager->registerProject("basic"); smtk::project::ProjectPtr project = projManager->create("basic"); + // If you create the project directly (rather than through + // an operation), you must add it to the manager explicitly: + projManager->add(project); // -- 2 -- // ++ 3 ++ diff --git a/doc/userguide/extension/paraview/panels.rst b/doc/userguide/extension/paraview/panels.rst index e3ed67a22d8a1faec2817b686f73f2bb6e2c6449..91c15bae7f4c1d32f897123c507c1a83fc9ad19f 100644 --- a/doc/userguide/extension/paraview/panels.rst +++ b/doc/userguide/extension/paraview/panels.rst @@ -67,10 +67,15 @@ run the operation immediately. Long-clicking a push-button will emit a signal that the parameter-editor panel below accepts to allow further user configuration before running. -You can activate the search bar for operations (i.e., switch the keyboard focus -to the search bar) at any time by pressing ``Ctrl+Space`` (or ``Cmd+Space`` on macos). -While the search bar has focus, pressing the ``Return`` key will emit a signal to -edit the parameters of the first (top, left-most) push button in the grid. +If the toolbox is configured to allow searching, you can activate the search bar +for operations (i.e., switch the keyboard focus to the search bar) at any time by +pressing ``Ctrl+Space`` (or ``Cmd+Space`` on macos). While the search bar has focus, +pressing the ``Return`` key will emit a signal to edit the parameters of the first +(top, left-most) push button in the grid. + +If the toolbox is configured to allow it, all operations (not just those available +to the currently-selection objects) will be displayed without decoration and may +be filtered by searching. Operation parameter-editor panel ================================ diff --git a/smtk/common/Observers.h b/smtk/common/Observers.h index d48bcabf28b61e8e57c1c0cb665096dfc6aae110..c2ef17403cdc934911b0a3712b5ccf58370e7a63 100644 --- a/smtk/common/Observers.h +++ b/smtk/common/Observers.h @@ -62,7 +62,7 @@ namespace common /// goes out of scope, the Observer functor is removed from the Observers /// instance) by default. To decouple the Key's lifetime from that of the /// Observer functor, use the Key's release() method. -template +template class Observers { friend class Key; @@ -72,6 +72,13 @@ public: /// called. Larger is higher priority. typedef int Priority; + /// The default priority for observers inserted without an explicit priority. + static constexpr Priority Default = DefaultPriority; + static constexpr Priority defaultPriority() { return Default; } + + /// A convenience that returns the lowest priority representable for observers. + static constexpr Priority lowestPriority() { return std::numeric_limits::lowest(); } + private: /// A key by which an Observer can be accessed within the Observers instance. struct InternalKey : std::pair @@ -388,7 +395,7 @@ public: Key insert(Observer fn, std::string description = "") { - return insert(fn, std::numeric_limits::lowest(), true, description); + return insert(fn, DefaultPriority, true, description); } /// Indicate that an observer should no longer be called. Returns the number diff --git a/smtk/extension/paraview/appcomponents/pqSMTKCloseResourceBehavior.cxx b/smtk/extension/paraview/appcomponents/pqSMTKCloseResourceBehavior.cxx index fef0c68500e2f2faa9bb5e3dee4c5be05655a16b..3347fa1449a91f1ce7be3d3d94342fa63b10393a 100644 --- a/smtk/extension/paraview/appcomponents/pqSMTKCloseResourceBehavior.cxx +++ b/smtk/extension/paraview/appcomponents/pqSMTKCloseResourceBehavior.cxx @@ -101,86 +101,170 @@ void pqCloseResourceReaction::updateEnableState() void pqCloseResourceReaction::closeResource() { - pqActiveObjects& activeObjects = pqActiveObjects::instance(); - pqSMTKResource* smtkResource = dynamic_cast(activeObjects.activeSource()); + std::set resources; + std::set dirty; - // Access the active resource - smtk::resource::ResourcePtr resource = smtkResource->getResource(); + auto* behavior = pqSMTKBehavior::instance(); + auto* wrapper = behavior->builtinOrActiveWrapper(); + if (wrapper) + { + auto selection = wrapper->smtkSelection(); + if (selection) + { + auto selectedResources = + selection->currentSelectionByValueAs>( + "selected", /* exact match */ false); + for (const auto& resource : selectedResources) + { + if (resource) + { + resources.insert(resource); + if (!resource->clean()) + { + dirty.insert(resource); + } + } + } + } + } + if (resources.empty()) + { + pqActiveObjects& activeObjects = pqActiveObjects::instance(); + pqSMTKResource* smtkResource = dynamic_cast(activeObjects.activeSource()); + + // Access the active resource + auto resource = smtkResource->getResource(); + resources.insert(resource); + if (!resource->clean()) + { + dirty.insert(resource); + } + } + if (resources.empty()) + { + // TODO: Flash some application widget to acknowledge user pressed key + // but warn the action was irrelevant. + return; + } int ret = QMessageBox::Discard; - if (resource && !resource->clean()) + if (!dirty.empty()) { - ret = pqSMTKSaveOnCloseResourceBehavior::showDialogWithPrefs(1, true); + ret = pqSMTKSaveOnCloseResourceBehavior::showDialogWithPrefs(dirty.size(), true); if (ret == QMessageBox::Save) { - activeObjects.setActiveSource(smtkResource); - pqSaveResourceReaction::State state = pqSaveResourceReaction::saveResource(); - if (state == pqSaveResourceReaction::State::Aborted) + auto managers = wrapper ? wrapper->smtkManagersPtr() : nullptr; + for (const auto& resource : dirty) { - // If user pref is DontShowAndSave, an Aborted save dialog must mean - // Discard, otherwise there's no way to discard a modified resource. - auto* settings = vtkSMTKSettings::GetInstance(); - int showSave = settings->GetShowSaveResourceOnClose(); - ret = - showSave == vtkSMTKSettings::DontShowAndSave ? QMessageBox::Discard : QMessageBox::Cancel; + auto pvResource = behavior->getPVResource(resource); + pqSaveResourceReaction::State state; + if (pvResource) + { + state = pqSaveResourceReaction::saveResource(pvResource); + } + else + { + // Handle resources (e.g., without any renderable geometry) + // that do not have a ParaView pipeline source. + state = pqSaveResourceReaction::saveResource(resource, managers); + } + if (state == pqSaveResourceReaction::State::Aborted) + { + // If user pref is DontShowAndSave, an Aborted save dialog must mean + // Discard, otherwise there's no way to discard a modified resource. + auto* settings = vtkSMTKSettings::GetInstance(); + int showSave = settings->GetShowSaveResourceOnClose(); + ret = showSave == vtkSMTKSettings::DontShowAndSave ? QMessageBox::Discard + : QMessageBox::Cancel; + if (ret == QMessageBox::Discard) + { + // The user wants to ignore modified resources... mark it clean so we can continue. + resource->setClean(true); + ret = QMessageBox::Save; + } + else + { + // The user canceled. Don't attempt to save more resources. + return; + } + } + else if (state != pqSaveResourceReaction::State::Succeeded) + { + // Failure. Do not close any resources that have not been properly saved. + return; + } } } - if (ret == QMessageBox::Discard) + else if (ret == QMessageBox::Discard) { - // Mark the resource as clean, even though it hasn't been saved. This way, + // Mark each resource as clean, even though it hasn't been saved. This way, // other listeners will not prompt the user to save the resource. - resource->setClean(true); + for (const auto& resource : dirty) + { + resource->setClean(true); + } } + dirty.clear(); } if (ret != QMessageBox::Cancel) { - // Remove it from its manager. Use an operation to avoid observer conflicts. - pqServer* server = pqActiveObjects::instance().activeServer(); - pqSMTKWrapper* wrapper = pqSMTKBehavior::instance()->resourceManagerForServer(server); - if (server && wrapper) + int numClosed = 0; + for (const auto& resource : resources) { - smtk::operation::RemoveResource::Ptr removeOp = - wrapper->smtkOperationManager()->create(); + // Remove each resource from its manager. Use an operation to avoid observer conflicts. + auto pvResource = behavior->getPVResource(resource); + pqServer* server = pvResource ? pvResource->getServer() : nullptr; + pqSMTKWrapper* wrapper = behavior->resourceManagerForServer(server); + if (wrapper) + { + smtk::operation::RemoveResource::Ptr removeOp = + wrapper->smtkOperationManager()->create(); - removeOp->parameters()->associate(resource); + removeOp->parameters()->associate(resource); - smtk::operation::Operation::Result removeOpResult = removeOp->operate(); - if ( - removeOpResult->findInt("outcome")->value() != - static_cast(smtk::operation::Operation::Outcome::SUCCEEDED)) + smtk::operation::Operation::Result removeOpResult = removeOp->operate(); + if ( + removeOpResult->findInt("outcome")->value() != + static_cast(smtk::operation::Operation::Outcome::SUCCEEDED)) + { + smtkErrorMacro( + smtk::io::Logger::instance(), + "RemoveResource operation failed while closing \"" << resource->name() << "\"."); + // resource is still managed (belongs to a project), avoid debug exception below. + ret = QMessageBox::Cancel; + break; // Jump out of loop to avoid closing more resources. + } + ++numClosed; + } + // Remove the resource from the active selection { - smtkErrorMacro( - smtk::io::Logger::instance(), "RemoveResource operation failed while closing a resource"); - // resource is still managed (belongs to a project), avoid debug exception below. - ret = QMessageBox::Cancel; + smtk::view::Selection::SelectionMap& selections = + const_cast( + smtk::view::Selection::instance()->currentSelection()); + selections.erase(resource); + } + // For debugging, print the use count of the resource to indicate + // how many shared pointers are referencing it. This will generally + // be 1 just before the resource is released, but since this now + // happens on a different thread it may be 2 at the time this + // message is printed. Larger numbers indicate problems with + // code holding on to the resource beyond its expected life. + if (resource && resource.use_count() != 1) + { + smtkInfoMacro( + smtk::io::Logger::instance(), + "Unexpected use count (" << resource.use_count() << ") " + << "for resource " << resource << " (" << resource->name() + << ", " << resource->typeName() << ", " << resource->location() + << ") being released."); } } - // Remove it from the active selection - { - smtk::view::Selection::SelectionMap& selections = - const_cast( - smtk::view::Selection::instance()->currentSelection()); - selections.erase(resource); - } - } - - // For debugging, print the use count of the resource to indicate - // how many shared pointers are referencing it. This will generally - // be 1 just before the resource is released, but since this now - // happens on a different thread it may be 2 at the time this - // message is printed. Larger numbers indicate problems with - // code holding on to the resource beyond its expected life. - if (ret != QMessageBox::Cancel && resource && resource.use_count() != 1) - { smtkInfoMacro( smtk::io::Logger::instance(), - "Unexpected use count (" << resource.use_count() << ") " - << "for resource " << resource << " (" << resource->name() << ", " - << resource->typeName() << ", " << resource->location() - << ") being released."); + "Closed " << numClosed << "/" << resources.size() << " resources."); } } diff --git a/smtk/extension/paraview/appcomponents/pqSMTKOperationParameterPanel.cxx b/smtk/extension/paraview/appcomponents/pqSMTKOperationParameterPanel.cxx index 71a86b0e06fb88eaa762cd705061eecc755a3042..407cea550268a5828091fb413ef6cfa5024f10e9 100644 --- a/smtk/extension/paraview/appcomponents/pqSMTKOperationParameterPanel.cxx +++ b/smtk/extension/paraview/appcomponents/pqSMTKOperationParameterPanel.cxx @@ -350,14 +350,17 @@ void pqSMTKOperationParameterPanel::editOperationParameters( // The current tab now has the operation of interest; // populate its associations with the current selection. auto associations = opTab->m_operation->parameters()->associations(); - if (associations->isOptional()) - { - associations->setIsEnabled(!selected.empty()); - } - if (!selected.empty()) + if (associations) { - associations->setNumberOfValues(selected.size()); - associations->setValues(selected.begin(), selected.end()); + if (associations->isOptional()) + { + associations->setIsEnabled(!selected.empty()); + } + if (!selected.empty()) + { + associations->setNumberOfValues(selected.size()); + associations->setValues(selected.begin(), selected.end()); + } } smtk::view::ConfigurationPtr view = opTab->m_uiMgr->findOrCreateOperationView(); diff --git a/smtk/extension/paraview/appcomponents/pqSMTKSaveResourceBehavior.cxx b/smtk/extension/paraview/appcomponents/pqSMTKSaveResourceBehavior.cxx index 2b7e104743e76fd469da5351e3fb1260305076ca..b21a4911d565c708a3c4a18ed3e052fac893b52a 100644 --- a/smtk/extension/paraview/appcomponents/pqSMTKSaveResourceBehavior.cxx +++ b/smtk/extension/paraview/appcomponents/pqSMTKSaveResourceBehavior.cxx @@ -99,37 +99,51 @@ pqSaveResourceReaction::State pqSaveResourceReaction::saveResource(pqSMTKResourc } auto* wrapper = pqSMTKBehavior::instance()->resourceManagerForServer(activeResource->getServer()); - if (smtk::resource::Manager::Ptr manager = resource->manager()) + return pqSaveResourceReaction::saveResource( + resource, wrapper ? wrapper->smtkManagersPtr() : nullptr); +} + +//----------------------------------------------------------------------------- +pqSaveResourceReaction::State pqSaveResourceReaction::saveResource( + const std::shared_ptr& resource, + const std::shared_ptr& managers) +{ + if (!resource) + { + return pqSaveResourceReaction::State::Failed; + } + + if (resource->location().empty()) + { + return pqSaveResourceAsReaction::saveResourceAs(resource, managers); + } + + if (auto manager = resource->manager()) { - std::shared_ptr managers = - wrapper ? wrapper->smtkManagersPtr() : nullptr; - // The resource manager returns true on success return manager->write(resource, managers) ? pqSaveResourceReaction::State::Succeeded : pqSaveResourceReaction::State::Failed; } - else + else if (managers) { // the standard way of saving doesn't work, because the deleted // pipeline object doesn't have a resource manager. // Instead, directly call WriteResource: - pqServer* server = activeObjects.activeServer(); - pqSMTKWrapper* wrapper = pqSMTKBehavior::instance()->resourceManagerForServer(server); - if (!server || !wrapper) + if (auto operationManager = managers->get()) { - return pqSaveResourceReaction::State::Failed; + if (auto writeOp = operationManager->create()) + { + std::string filename = resource->location(); + writeOp->parameters()->associate(resource); + writeOp->parameters()->findFile("filename")->setIsEnabled(true); + writeOp->parameters()->findFile("filename")->setValue(filename); + + smtk::operation::Operation::Result writeOpResult = writeOp->operate(); + return writeOpResult->findInt("outcome")->value() == + static_cast(smtk::operation::Operation::Outcome::SUCCEEDED) + ? pqSaveResourceReaction::State::Succeeded + : pqSaveResourceReaction::State::Failed; + } } - smtk::operation::WriteResource::Ptr writeOp = - wrapper->smtkOperationManager()->create(); - - writeOp->parameters()->associate(resource); - writeOp->parameters()->findFile("filename")->setIsEnabled(true); - writeOp->parameters()->findFile("filename")->setValue(filename); - - smtk::operation::Operation::Result writeOpResult = writeOp->operate(); - return writeOpResult->findInt("outcome")->value() == - static_cast(smtk::operation::Operation::Outcome::SUCCEEDED) - ? pqSaveResourceReaction::State::Succeeded - : pqSaveResourceReaction::State::Failed; } return pqSaveResourceReaction::State::Failed; @@ -161,7 +175,6 @@ void pqSaveResourceAsReaction::updateEnableState() //----------------------------------------------------------------------------- pqSaveResourceReaction::State pqSaveResourceAsReaction::saveResourceAs(pqSMTKResource* smtkResource) { - pqServer* server = pqActiveObjects::instance().activeServer(); pqActiveObjects& activeObjects = pqActiveObjects::instance(); pqSMTKResource* activeResource = smtkResource != nullptr ? smtkResource @@ -171,6 +184,36 @@ pqSaveResourceReaction::State pqSaveResourceAsReaction::saveResourceAs(pqSMTKRes return pqSaveResourceReaction::State::Failed; } smtk::resource::ResourcePtr resource = activeResource->getResource(); + auto* wrapper = pqSMTKBehavior::instance()->resourceManagerForServer(activeResource->getServer()); + return pqSaveResourceAsReaction::saveResourceAs( + resource, wrapper ? wrapper->smtkManagersPtr() : nullptr); +} + +pqSaveResourceReaction::State pqSaveResourceAsReaction::saveResourceAs( + const std::shared_ptr& resource, + const std::shared_ptr& managers) +{ + pqServer* server = pqActiveObjects::instance().activeServer(); + if (!resource) + { + return pqSaveResourceReaction::State::Failed; + } + + auto resourceManager = resource->manager(); + if (!resourceManager && managers) + { + resourceManager = managers->get(); + } + auto operationManager = managers ? managers->get() : nullptr; + if (!resourceManager && !operationManager) + { + smtkErrorMacro( + smtk::io::Logger::instance(), + "No resource or operation manager to save resource " + "\"" + << resource->name() << "\" " << resource << "."); + return pqSaveResourceReaction::State::Failed; + } QString filters("Simulation Modeling Toolkit Resource files (*.smtk)"); QString title(tr("Save File: ")); @@ -211,40 +254,20 @@ pqSaveResourceReaction::State pqSaveResourceAsReaction::saveResourceAs(pqSMTKRes return pqSaveResourceReaction::State::Aborted; } - auto* wrapper = - pqSMTKBehavior::instance()->resourceManagerForServer(activeResource->getServer()); - if (smtk::resource::Manager::Ptr manager = resource->manager()) + // At this point, we know we are saving; it is OK to set the new filename. + if (!resource->setLocation(filename)) { - std::shared_ptr managers = - wrapper ? wrapper->smtkManagersPtr() : nullptr; - return manager->write(resource, filename, managers) ? pqSaveResourceReaction::State::Succeeded - : pqSaveResourceReaction::State::Failed; - } - else - { - // the standard way of saving doesn't work, because the deleted - // pipeline object doesn't have a resource manager. - // Instead, directly call WriteResource: - pqServer* server = activeObjects.activeServer(); - pqSMTKWrapper* wrapper = pqSMTKBehavior::instance()->resourceManagerForServer(server); - if (!server || !wrapper) - { - return pqSaveResourceReaction::State::Failed; - } - smtk::operation::WriteResource::Ptr writeOp = - wrapper->smtkOperationManager()->create(); - - writeOp->parameters()->associate(resource); - writeOp->parameters()->findFile("filename")->setIsEnabled(true); - writeOp->parameters()->findFile("filename")->setValue(filename); - - smtk::operation::Operation::Result writeOpResult = writeOp->operate(); - return writeOpResult->findInt("outcome")->value() == - static_cast(smtk::operation::Operation::Outcome::SUCCEEDED) - ? pqSaveResourceReaction::State::Succeeded - : pqSaveResourceReaction::State::Failed; + QWidget* mainWidget = pqCoreUtilities::mainWidget(); + QString text; + QTextStream qs(&text); + qs << "Could not set resource's location to \"" << filename.c_str() << "\"."; + QMessageBox::critical(mainWidget, "Unable to modify resource", text); + return pqSaveResourceReaction::State::Aborted; } + + return pqSaveResourceReaction::saveResource(resource, managers); } + return pqSaveResourceReaction::State::Aborted; } diff --git a/smtk/extension/paraview/appcomponents/pqSMTKSaveResourceBehavior.h b/smtk/extension/paraview/appcomponents/pqSMTKSaveResourceBehavior.h index 76009e243d5f63b0a2a0a96b394fa33c4d6a6b17..0f1eb85a5f3904a3ab084e87a465af180abddf16 100644 --- a/smtk/extension/paraview/appcomponents/pqSMTKSaveResourceBehavior.h +++ b/smtk/extension/paraview/appcomponents/pqSMTKSaveResourceBehavior.h @@ -44,7 +44,12 @@ public: Aborted }; + /// Save the resource to disk (or if null, save the active object's resource). static State saveResource(pqSMTKResource* smtkResource = nullptr); + /// Save the resource to disk (called by the variant above). + static State saveResource( + const std::shared_ptr& resource, + const std::shared_ptr& managers); public Q_SLOTS: /** @@ -75,7 +80,12 @@ public: */ pqSaveResourceAsReaction(QAction* parent); + /// Save the resource to disk under a different name (or if null, the active object's resource). static pqSaveResourceReaction::State saveResourceAs(pqSMTKResource* smtkResource = nullptr); + /// Save the resource to disk under a different name (called by the variant above). + static pqSaveResourceReaction::State saveResourceAs( + const std::shared_ptr& resource, + const std::shared_ptr& managers); public Q_SLOTS: /** diff --git a/smtk/extension/paraview/project/pqSMTKDisplayProjectOnLoadBehavior.cxx b/smtk/extension/paraview/project/pqSMTKDisplayProjectOnLoadBehavior.cxx index ed58797b55627ed43700f9365e27935e886e6920..ba0b01b83d3c1c24e34abf0c8ea2243d9f55ffc9 100644 --- a/smtk/extension/paraview/project/pqSMTKDisplayProjectOnLoadBehavior.cxx +++ b/smtk/extension/paraview/project/pqSMTKDisplayProjectOnLoadBehavior.cxx @@ -152,18 +152,14 @@ void pqSMTKDisplayProjectOnLoadBehavior::handleProjectEvent( 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) + 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())) { - panel->taskPanel()->displayTaskManager(taskManager); - if (auto* parent = dynamic_cast(panel->parent())) - { - parent->raise(); // Make sure the widget is visible and raised. - } + parent->raise(); // Make sure the widget is visible and raised. } - }); + } } diff --git a/smtk/extension/qt/qtOperationPalette.cxx b/smtk/extension/qt/qtOperationPalette.cxx index 51dfd4a76b611f641bc2858531fb2f2a1185435a..ae6f3e31d4c99e1d0e5b8a683ffd59e961db1e85 100644 --- a/smtk/extension/qt/qtOperationPalette.cxx +++ b/smtk/extension/qt/qtOperationPalette.cxx @@ -29,6 +29,7 @@ #include "smtk/view/Configuration.h" #include +#include #include #include #include @@ -140,6 +141,36 @@ void qtOperationPalette::editTopOperation() } } +void qtOperationPalette::toggleFiltering(int filterState) +{ + if (!m_decorator) + { + // Remember the decorator if it is present so we can reset it + // when the checkbox is toggled again. + m_decorator = m_model->decorator(); + } + switch (filterState) + { + case Qt::Checked: + m_subset->setFilterRegularExpression(""); + m_model->setDecorator(nullptr); + break; + default: + case Qt::Unchecked: + { + std::string subsetFilterRegex("[4-9]"); + const auto& config = this->configuration(); + if (config) + { + config->details().attribute("SubsetAssociability", subsetFilterRegex); + } + m_subset->setFilterRegularExpression(QString::fromStdString(subsetFilterRegex)); + m_model->setDecorator(m_decorator); + } + break; + } +} + void qtOperationPalette::buildUI() { this->createWidget(); @@ -184,18 +215,44 @@ void qtOperationPalette::createWidget() m_layout = new QVBoxLayout; m_layout->setObjectName("Layout"); this->Widget->setLayout(m_layout); + auto* controlsLayout = new QHBoxLayout; + controlsLayout->setObjectName("Controls"); + bool haveControls = false; const auto& conf = m_viewInfo.configuration()->details(); if (conf.attributeAsBool("SearchBar")) { m_search = new QLineEdit(); m_search->setObjectName("Search"); m_search->setPlaceholderText("Search"); - m_layout->addWidget(m_search); + m_search->setToolTip("Search for an operation by its name."); + controlsLayout->addWidget(m_search); + haveControls = true; QObject::connect( m_search, &QLineEdit::textChanged, m_filter, &QSortFilterProxyModel::setFilterWildcard); QObject::connect( m_search, &QLineEdit::returnPressed, this, &qtOperationPalette::editTopOperation); } + if (!conf.attributeAsBool("AlwaysLimit")) + { + auto* alwaysLimit = new QCheckBox(); + alwaysLimit->setObjectName("AlwaysLimit"); + alwaysLimit->setText("All"); + alwaysLimit->setToolTip("Click to show all available operations."); + alwaysLimit->setCheckState(Qt::Unchecked); + controlsLayout->addWidget(alwaysLimit); + haveControls = true; + m_decorator = m_model->decorator(); + QObject::connect( + alwaysLimit, &QCheckBox::stateChanged, this, &qtOperationPalette::toggleFiltering); + } + if (haveControls) + { + m_layout->addLayout(controlsLayout); + } + else + { + delete controlsLayout; + } m_layout->addWidget(m_list); } diff --git a/smtk/extension/qt/qtOperationPalette.h b/smtk/extension/qt/qtOperationPalette.h index 22d7596ce484ea0fb7ccf2757132640671b88b55..4311a1438bbdb36976eb1ecc972030e00fc8f138 100644 --- a/smtk/extension/qt/qtOperationPalette.h +++ b/smtk/extension/qt/qtOperationPalette.h @@ -28,6 +28,10 @@ class qtOperationTypeView; namespace smtk { +namespace view +{ +class OperationDecorator; +} namespace extension { @@ -49,6 +53,10 @@ namespace extension * to the current selection and then, within groups of the same relevance, * sorted by the label text. * + * If the "AlwaysLimit" configuration option is false (or absent), then + * a checkbox labeled "All" will be shown. The checkbox allows users to + * display all of the operations without any decorations. + * * Each operation has a corresponding qtOperationAction that * creates QPushButton instances for display in a dynamic grid. * The push buttons cause the action to emit either an "acceptDefaults" @@ -72,6 +80,7 @@ namespace extension * * @@ -144,6 +153,7 @@ public Q_SLOTS: void onShowCategory() override {} virtual void editTopOperation(); + virtual void toggleFiltering(int filterState); protected: ///\brief Creates the UI related to the view and properly assigns it @@ -169,6 +179,11 @@ protected: QPointer m_search; /// The list-view of operations. QPointer m_list; + /// The default operation decorator owned by m_model. + /// + /// Toggling the "All" checkbox to unlimit operations unsets or restores + /// the decorator on m_model. + std::shared_ptr m_decorator; }; } // namespace extension diff --git a/smtk/extension/qt/qtSMTKCallObserversOnMainThreadBehavior.cxx b/smtk/extension/qt/qtSMTKCallObserversOnMainThreadBehavior.cxx index c859918042a69b552cd1c0bcd83461b5d1e33988..0671e60ab66196e22017244b9c2fb9b8e3e25e50 100644 --- a/smtk/extension/qt/qtSMTKCallObserversOnMainThreadBehavior.cxx +++ b/smtk/extension/qt/qtSMTKCallObserversOnMainThreadBehavior.cxx @@ -134,6 +134,7 @@ void qtSMTKCallObserversOnMainThreadBehavior::forceObserversToBeCalledOnMainThre m_activeOperationMutex.unlock(); }); } + // Override the selection Observers' call method to emit a private signal // instead of calling its Observer functors directly. auto selection = mgrs->get(); @@ -164,4 +165,194 @@ void qtSMTKCallObserversOnMainThreadBehavior::forceObserversToBeCalledOnMainThre m_activeSelection.erase(id); }); } + + auto projectManager = mgrs->get(); + if (projectManager) + { + m_projectManager = projectManager; + + projectManager->observers().overrideWith( + [this](const smtk::project::Project& project, smtk::project::EventType event) { + m_activeProjects[project.id()] = + const_cast(project).shared_from_this(); + Q_EMIT projectInstanceEvent(project.id(), event, QPrivateSignal()); + return 0; + }); + + // Connect to the above signals on the main thread and call the Observer functors. + QObject::connect( + this, + &qtSMTKCallObserversOnMainThreadBehavior::taskInstanceEvent, + this, + &qtSMTKCallObserversOnMainThreadBehavior::processTaskInstanceEvent, + Qt::QueuedConnection); + QObject::connect( + this, + &qtSMTKCallObserversOnMainThreadBehavior::adaptorInstanceEvent, + this, + &qtSMTKCallObserversOnMainThreadBehavior::processAdaptorInstanceEvent, + Qt::QueuedConnection); + QObject::connect( + this, + &qtSMTKCallObserversOnMainThreadBehavior::taskWorkflowEvent, + this, + &qtSMTKCallObserversOnMainThreadBehavior::processTaskWorkflowEvent, + Qt::QueuedConnection); + + QObject::connect( + this, + &qtSMTKCallObserversOnMainThreadBehavior::projectInstanceEvent, + this, + &qtSMTKCallObserversOnMainThreadBehavior::processProjectInstanceEvent, + Qt::QueuedConnection); + } +} + +void qtSMTKCallObserversOnMainThreadBehavior::processProjectInstanceEvent( + smtk::common::UUID projectId, + smtk::project::EventType event, + QPrivateSignal) +{ + auto it = m_activeProjects.find(projectId); + if (it == m_activeProjects.end()) + { + return; + } + auto projectManager = m_projectManager.lock(); + if (!projectManager) + { + return; + } + + const auto& project = it->second; + + // Override the task-manager's observers + if (event == smtk::project::EventType::ADDED) + { + auto projectId = project->id(); // const_cast(&project); + // Force all observers to be invoked on thread 0 (the GUI thread). + // Override task observers: + project->taskManager().taskInstances().observers().overrideWith( + [projectId, + this](smtk::common::InstanceEvent event, const std::shared_ptr& task) { + m_activeTasks[task->id()] = task; + Q_EMIT taskInstanceEvent(projectId, event, task->id(), QPrivateSignal()); + }); + // Override adaptor observers: + project->taskManager().adaptorInstances().observers().overrideWith( + [projectId, this]( + smtk::common::InstanceEvent event, const std::shared_ptr& adaptor) { + m_activeAdaptors[std::make_pair(adaptor->from()->id(), adaptor->to()->id())] = adaptor; + Q_EMIT adaptorInstanceEvent( + projectId, event, adaptor->from()->id(), adaptor->to()->id(), QPrivateSignal()); + }); + // Override task observers: + project->taskManager().taskInstances().workflowObservers().overrideWith( + [projectId, this]( + const std::set& headTasks, + smtk::task::WorkflowEvent event, + smtk::task::Task* subject) { + std::set key; + std::set> value; + for (const auto& headTask : headTasks) + { + key.insert(headTask->id()); + value.insert(headTask->shared_from_this()); + } + key.insert(subject->id()); + value.insert(subject->shared_from_this()); + m_activeWorkflows[key] = value; + Q_EMIT taskWorkflowEvent(projectId, key, event, subject->id(), QPrivateSignal()); + }); + } + + // Call other project observers + projectManager->observers().callObserversDirectly(*project, event); + + m_activeProjects.erase(it); +} + +void qtSMTKCallObserversOnMainThreadBehavior::processTaskInstanceEvent( + smtk::common::UUID projectId, + smtk::common::InstanceEvent event, + smtk::string::Token taskId, + QPrivateSignal) +{ + auto it = m_activeTasks.find(taskId); + if (it == m_activeTasks.end()) + { + return; + } + auto projectManager = m_projectManager.lock(); + auto project = projectManager ? projectManager->get(projectId) : nullptr; + if (!project) + { + return; + } + + project->taskManager().taskInstances().observers().callObserversDirectly(event, it->second); + + m_activeTasks.erase(it); +} + +void qtSMTKCallObserversOnMainThreadBehavior::processAdaptorInstanceEvent( + smtk::common::UUID projectId, + smtk::common::InstanceEvent event, + smtk::string::Token fromTaskId, + smtk::string::Token toTaskId, + QPrivateSignal) +{ + auto it = m_activeAdaptors.find(std::make_pair(fromTaskId, toTaskId)); + if (it == m_activeAdaptors.end()) + { + return; + } + auto projectManager = m_projectManager.lock(); + auto project = projectManager ? projectManager->get(projectId) : nullptr; + if (!project) + { + return; + } + + project->taskManager().adaptorInstances().observers().callObserversDirectly(event, it->second); + + m_activeAdaptors.erase(it); +} + +void qtSMTKCallObserversOnMainThreadBehavior::processTaskWorkflowEvent( + smtk::common::UUID projectId, + const std::set& headIds, + smtk::task::WorkflowEvent event, + smtk::string::Token subjectId, + QPrivateSignal) +{ + auto it = m_activeWorkflows.find(headIds); + if (it == m_activeWorkflows.end()) + { + return; + } + auto projectManager = m_projectManager.lock(); + auto project = projectManager ? projectManager->get(projectId) : nullptr; + if (!project) + { + return; + } + + std::set heads; + smtk::task::Task* subject = nullptr; + for (const auto& head : it->second) + { + if (!subject && head->id() == subjectId) + { + subject = head.get(); + } + else + { + heads.insert(head.get()); + } + } + project->taskManager().taskInstances().workflowObservers().callObserversDirectly( + heads, event, subject); + + m_activeWorkflows.erase(it); } diff --git a/smtk/extension/qt/qtSMTKCallObserversOnMainThreadBehavior.h b/smtk/extension/qt/qtSMTKCallObserversOnMainThreadBehavior.h index d0898b23440765e2de8b2b93eb70935e0027785a..3e32f435c09e01277f680856aa16c607a738d2c9 100644 --- a/smtk/extension/qt/qtSMTKCallObserversOnMainThreadBehavior.h +++ b/smtk/extension/qt/qtSMTKCallObserversOnMainThreadBehavior.h @@ -15,12 +15,15 @@ #include "smtk/common/Managers.h" #include "smtk/common/UUID.h" #include "smtk/operation/Manager.h" +#include "smtk/project/Manager.h" #include "smtk/resource/Manager.h" +#include "smtk/string/Token.h" #include "smtk/view/Selection.h" #include #include +#include /** \brief Add logic to managers to ensure that operation and resource * observers are called on the main thread. @@ -71,14 +74,89 @@ Q_SIGNALS: */ void selectionEvent(QString selectionId, QString str, QPrivateSignal); + /// Override for project observations. + void + projectInstanceEvent(smtk::common::UUID project, smtk::project::EventType event, QPrivateSignal); + + /**\brief Signal that a task has been managed/unmanaged. + */ + void taskInstanceEvent( + smtk::common::UUID project, + smtk::common::InstanceEvent event, + smtk::string::Token taskId, + QPrivateSignal); + + /**\brief Signal that an adaptor has been managed/unmanaged. + */ + void adaptorInstanceEvent( + smtk::common::UUID project, + smtk::common::InstanceEvent event, + smtk::string::Token fromTaskId, + smtk::string::Token toTaskId, + QPrivateSignal); + + /**\brief Signal that a worflow (task-chain) has been edited. + */ + void taskWorkflowEvent( + smtk::common::UUID project, + const std::set& headKeys, + smtk::task::WorkflowEvent event, + smtk::string::Token subjectId, + QPrivateSignal); + protected: qtSMTKCallObserversOnMainThreadBehavior(QObject* parent = nullptr); +protected Q_SLOTS: + /**\brief Slot run on GUI thread when a project has been managed/unmanaged. + */ + void processProjectInstanceEvent( + smtk::common::UUID project, + smtk::project::EventType event, + QPrivateSignal); + + /**\brief Slot run on GUI thread when a task has been managed/unmanaged. + */ + void processTaskInstanceEvent( + smtk::common::UUID project, + smtk::common::InstanceEvent event, + smtk::string::Token taskId, + QPrivateSignal); + + /**\brief Slot run on GUI thread when an adaptor has been managed/unmanaged. + */ + void processAdaptorInstanceEvent( + smtk::common::UUID project, + smtk::common::InstanceEvent event, + smtk::string::Token fromTaskId, + smtk::string::Token toTaskId, + QPrivateSignal); + + /**\brief Slot run on GUI thread when a worflow (task-chain) has been edited. + */ + void processTaskWorkflowEvent( + smtk::common::UUID project, + const std::set& headIds, + smtk::task::WorkflowEvent event, + smtk::string::Token subjectId, + QPrivateSignal); + private: std::map> m_activeResources; std::map> m_activeOperations; std::map> m_activeSelection; + /// Watch for projects to be added/removed + std::weak_ptr m_projectManager; + smtk::project::Observers::Key m_projectObserver; + std::map> m_activeProjects; + std::unordered_map> m_activeTasks; + std:: + map, std::shared_ptr> + m_activeAdaptors; + std::map, std::set>> + m_activeWorkflows; + // A mutex to make access to m_activeOperations thread-safe std::mutex m_activeOperationMutex; diff --git a/smtk/model/operators/DivideInstance.sbt b/smtk/model/operators/DivideInstance.sbt index 1683779f09a2b189517c45599c2841d5d13a310d..d5bf3b335d866c0fe04853b5cfa9b228b52bde63 100644 --- a/smtk/model/operators/DivideInstance.sbt +++ b/smtk/model/operators/DivideInstance.sbt @@ -10,7 +10,7 @@ Name="instance" NumberOfRequiredValues="1" Extensible="true" - HoldReferences="true"> + HoldReference="true"> diff --git a/smtk/model/operators/MergeInstances.sbt b/smtk/model/operators/MergeInstances.sbt index 90b48f68dea994957887350109fc580352f86953..4ef5de2343f1b24320b7608f8ee6055feb0c3ad9 100644 --- a/smtk/model/operators/MergeInstances.sbt +++ b/smtk/model/operators/MergeInstances.sbt @@ -10,7 +10,7 @@ Name="instance" NumberOfRequiredValues="2" Extensible="true" - HoldReferences="true"> + HoldReference="true"> diff --git a/smtk/operation/Manager.cxx b/smtk/operation/Manager.cxx index 50f8b23062ab98007bd0ff446577cb7f537aa132..734aa0b0620b10db99beb29c584fd4065cc40a79 100644 --- a/smtk/operation/Manager.cxx +++ b/smtk/operation/Manager.cxx @@ -237,6 +237,8 @@ bool Manager::registerResourceManager(smtk::resource::ManagerPtr& resourceManage } return 0; }, + smtk::operation::Observers::lowestPriority(), + /* initialize */ false, "Add created resources to the resource manager"); return m_resourceObserver.assigned(); diff --git a/smtk/operation/Operation.cxx b/smtk/operation/Operation.cxx index 9fe4db60201e238556c9dca9f9defd2d37c8a2ab..c5df659f8ce620cab36afbc8808c75d1cf6973b2 100644 --- a/smtk/operation/Operation.cxx +++ b/smtk/operation/Operation.cxx @@ -450,6 +450,7 @@ bool removeResource( return removed; } } // namespace + bool Operation::unmanageResources(Operation::Result& result) { auto item = result->findResource("resourcesToExpunge"); diff --git a/smtk/operation/SpecificationOps.cxx b/smtk/operation/SpecificationOps.cxx index b49d905afe9ef1ffb34c059392e3623b8d34c728..dc1988d75d522fe5c67a554217bc394975170d77 100644 --- a/smtk/operation/SpecificationOps.cxx +++ b/smtk/operation/SpecificationOps.cxx @@ -161,7 +161,7 @@ void resourcesFromItem( for (std::size_t i = 0; i < item->numberOfValues(); i++) { // no need to look at items that cannot be resolved - if (item->value(i) == nullptr) + if (!item->value(i)) { continue; } diff --git a/smtk/operation/operators/CoordinateTransform.sbt b/smtk/operation/operators/CoordinateTransform.sbt index 279ab54300aa32443ce9f74772989e7f2e3747df..914dd7abd0b7768185ce5c3957a95c9e2668a486 100644 --- a/smtk/operation/operators/CoordinateTransform.sbt +++ b/smtk/operation/operators/CoordinateTransform.sbt @@ -35,7 +35,7 @@ + NumberOfRequiredValues="0" Extensible="true" MaxNumberOfValues="1" HoldReference="true" LockType="Read"> The object owning the CoordinateFrame property matching this frame (if any). diff --git a/smtk/operation/operators/RemoveResource.h b/smtk/operation/operators/RemoveResource.h index 37e917283796a10964e40fc094f488a483289a01..0d068664a5a6dff543b524b2d102d180ac5dc9d7 100644 --- a/smtk/operation/operators/RemoveResource.h +++ b/smtk/operation/operators/RemoveResource.h @@ -39,6 +39,10 @@ protected: Result operateInternal() override; + /// Do not report success as the base class prints specific console + /// messages after operateInternal() completes. + void generateSummary(Result&) override {} + const char* xmlDescription() const override; }; } // namespace operation diff --git a/smtk/operation/pybind11/PybindManager.h b/smtk/operation/pybind11/PybindManager.h index 0eb5552a171f0568a2860555832597495371c0db..2e8bf60c63b35b436829a5c318c5ee3620879aac 100644 --- a/smtk/operation/pybind11/PybindManager.h +++ b/smtk/operation/pybind11/PybindManager.h @@ -51,6 +51,8 @@ inline PySharedPtrClass< smtk::operation::Manager > pybind11_init_smtk_operation return smtk::operation::ImportPythonOperation::importOperation(manager, moduleName, opName); }) .def("unregisterOperation", (bool (smtk::operation::Manager::*)(const std::string&)) &smtk::operation::Manager::unregisterOperation, py::arg("typeName")) + .def("managers", &smtk::operation::Manager::managers) + .def("setManagers", &smtk::operation::Manager::setManagers, py::arg("managers")) ; return instance; } diff --git a/smtk/operation/testing/cxx/TestHints.sbt b/smtk/operation/testing/cxx/TestHints.sbt index 3ad92b4142ab63a7effd86cc4906c72b0764f2e7..8a2c9fb12a720ae36d04eb050c8fad8c02435dea 100644 --- a/smtk/operation/testing/cxx/TestHints.sbt +++ b/smtk/operation/testing/cxx/TestHints.sbt @@ -13,7 +13,7 @@ - + diff --git a/smtk/operation/testing/cxx/TestRemoveResourceProject.cxx b/smtk/operation/testing/cxx/TestRemoveResourceProject.cxx index 6b6edb827bc373b905e11db92e2f32842de07749..4fb1bab8b86023f93ec8f6201866674c01ab4364 100644 --- a/smtk/operation/testing/cxx/TestRemoveResourceProject.cxx +++ b/smtk/operation/testing/cxx/TestRemoveResourceProject.cxx @@ -100,8 +100,8 @@ int TestRemoveResourceProject(int /*unused*/, char** const /*unused*/) auto vtkRegistry = smtk::plugin::addToManagers(resourceManager, operationManager); #endif - auto projectOpRegistry = - smtk::plugin::addToManagers(resourceManager, projectManager); + auto projectOpRegistry = smtk::plugin::addToManagers( + resourceManager, operationManager, projectManager); // Register a new project type projectManager->registerProject("foo"); @@ -109,7 +109,8 @@ int TestRemoveResourceProject(int /*unused*/, char** const /*unused*/) // Create a project and write it to disk. std::size_t numberOfResources = 0; smtk::resource::ResourcePtr resource; - smtk::project::Project::Ptr project = projectManager->create("foo"); + smtk::project::Project::Ptr project = projectManager->create("foo", managers); + projectManager->add(project->index(), project); smtkTest(!!project, "Failed to create a project"); { diff --git a/smtk/project/Manager.cxx b/smtk/project/Manager.cxx index 18552e3c0e2aadf8a7a26099efdff53ed0cc9053..ad9785edab2d5de52ad67b8bec1397c52ce7f1ba 100644 --- a/smtk/project/Manager.cxx +++ b/smtk/project/Manager.cxx @@ -9,6 +9,8 @@ //========================================================================= #include "smtk/project/Manager.h" +#include "smtk/attribute/ResourceItem.h" + #include "smtk/operation/groups/ReaderGroup.h" #include "smtk/operation/groups/WriterGroup.h" @@ -95,6 +97,67 @@ Manager::Manager( operationMetadataObserver, "Append the assignment of the project manager to the create functor " "for operations that inherit from smtk::project::Operation"); + + m_operationManagerObserver = operationManager->observers().insert( + [this]( + const smtk::operation::Operation&, + smtk::operation::EventType event, + smtk::operation::Operation::Result result) -> int { + constexpr int doNotCancel = 0; + auto rsrcMgr = m_resourceManager.lock(); + if (!rsrcMgr) + { + m_operationManagerObserver.release(); + return doNotCancel; + } + + if (event != smtk::operation::EventType::DID_OPERATE) + { + return doNotCancel; + } + + // Gather all resource items + std::vector resourceItems; + std::function filter = + [](smtk::attribute::ResourceItemPtr /*unused*/) { return true; }; + result->filterItems(resourceItems, filter); + + // For each resource item found... + for (auto& resourceItem : resourceItems) + { + bool removing = (resourceItem->name() == "resourcesToExpunge"); + // ...for each resource in a resource item... + for (std::size_t i = 0; i < resourceItem->numberOfValues(); i++) + { + // (no need to look at resources that cannot be resolved) + if (!resourceItem->isValid(i) || !resourceItem->value(i)) + { + continue; + } + + if (auto project = resourceItem->valueAs(i)) + { + if (removing) + { + // … remove the project from the manager. + // this->remove(project); + } + else + { + // … add the project to the manager. + this->add(project->index(), project); + // this->add(metadata->index(), project); + // this->add(index, project); + } + } + } + } + + return doNotCancel; + }, + smtk::operation::Observers::lowestPriority() + 1, + /* initialize */ false, + "Add/remove projects that operations report to/from a project::Manager."); } bool Manager::registerProject( @@ -106,10 +169,11 @@ bool Manager::registerProject( return registerProject(Metadata( name, std::hash{}(name), - [name]( + [this, name]( const smtk::common::UUID& id, const std::shared_ptr&) -> ProjectPtr { ProjectPtr project = Project::create(name); project->setId(id); + // this->add(project->index(), project); return project; }, resources, @@ -275,7 +339,6 @@ 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); @@ -302,7 +365,6 @@ smtk::project::Project::Ptr Manager::create( { project->taskManager().setManagers(mm); } - this->add(index, project); } return project; diff --git a/smtk/project/Manager.h b/smtk/project/Manager.h index 4deb558f7e501688e9bcba6073f8f799accebfcf..b3002160ad7074993b5929b54384fbced28b199e 100644 --- a/smtk/project/Manager.h +++ b/smtk/project/Manager.h @@ -17,6 +17,7 @@ #include "smtk/common/TypeName.h" #include "smtk/operation/Manager.h" +#include "smtk/operation/Observer.h" #include "smtk/project/Container.h" #include "smtk/project/Metadata.h" @@ -246,6 +247,10 @@ private: std::weak_ptr m_operationManager; smtk::operation::MetadataObservers::Key m_operationMetadataObserverKey; + + /// An observer for m_operationManager that adds and removes projects + /// to/from this manager as directed by Operation::Result items. + smtk::operation::Observers::Key m_operationManagerObserver; }; namespace detail diff --git a/smtk/project/Project.cxx b/smtk/project/Project.cxx index 5251a3de944384fb5558d7219ed1af0a2a815e40..0cbcda48f55d462df1e3cb2027131dacf72543a2 100644 --- a/smtk/project/Project.cxx +++ b/smtk/project/Project.cxx @@ -30,6 +30,15 @@ Project::Project(const std::string& typeName) smtk::plugin::Manager::instance()->registerPluginsTo(m_taskManager); } +std::shared_ptr Project::create(const std::string& typeName) +{ + // No operation manager; try this although it will cause trouble because + // normally the "Create" operation's result-observer will add it to the + // project manager. + auto project = smtk::shared_ptr(new smtk::project::Project(typeName)); + return project; +} + bool Project::clean() const { // Check my flag first diff --git a/smtk/project/Project.h b/smtk/project/Project.h index 7430c5423922dd0517dd576f1bd24bceace99d5e..b7041a788eba76079093d1ed023362e9e6a78b89 100644 --- a/smtk/project/Project.h +++ b/smtk/project/Project.h @@ -52,10 +52,7 @@ public: static constexpr const char* const type_name = "smtk::project::Project"; std::string typeName() const override { return (m_typeName.empty() ? type_name : m_typeName); } - static std::shared_ptr create(const std::string& typeName = "") - { - return smtk::shared_ptr(new smtk::project::Project(typeName)); - } + static std::shared_ptr create(const std::string& typeName = ""); /// A hash value uniquely representing the project type. typedef std::size_t Index; diff --git a/smtk/project/Tags.h b/smtk/project/Tags.h index e1033cf827c2a9bd654ebf8842e3bcd6d673e698..c13002ba77d620ce95458591b6a585db93bab174 100644 --- a/smtk/project/Tags.h +++ b/smtk/project/Tags.h @@ -11,25 +11,27 @@ #ifndef smtk_project_Tags_h #define smtk_project_Tags_h +#include "smtk/CoreExports.h" + namespace smtk { namespace project { /// Tags used to access Project data from multiindex arrays. -struct IdTag +struct SMTKCORE_EXPORT IdTag { }; -struct IndexTag +struct SMTKCORE_EXPORT IndexTag { }; -struct LocationTag +struct SMTKCORE_EXPORT LocationTag { }; -struct NameTag +struct SMTKCORE_EXPORT NameTag { }; -struct RoleTag +struct SMTKCORE_EXPORT RoleTag { }; } // namespace project diff --git a/smtk/project/operators/Create.cxx b/smtk/project/operators/Create.cxx index ade1cbe8fd887775fd89342b571dbc343063d96d..40100b9aa658a0959754eebc75309b779c23fab5 100644 --- a/smtk/project/operators/Create.cxx +++ b/smtk/project/operators/Create.cxx @@ -21,10 +21,14 @@ #include "smtk/attribute/StringItem.h" #include "smtk/attribute/StringItemDefinition.h" -#include "smtk/io/Logger.h" +#include "smtk/resource/Manager.h" + +#include "smtk/operation/Manager.h" #include "smtk/project/Manager.h" +#include "smtk/io/Logger.h" + #include "smtk/project/operators/Create_xml.h" #include @@ -75,6 +79,9 @@ Create::Result Create::operateInternal() return this->createResult(smtk::operation::Operation::Outcome::FAILED); } + project->resources().setManager(this->managers()->get()); + project->operations().setManager(this->managers()->get()); + auto result = this->createResult(smtk::operation::Operation::Outcome::SUCCEEDED); { smtk::attribute::ResourceItem::Ptr created = result->findResource("resource"); diff --git a/smtk/project/operators/Read.cxx b/smtk/project/operators/Read.cxx index 580d6480dd9064904b8d57c847fbb6396bde3308..9f3209a3803498a366c76ea71872ad47d3873375 100644 --- a/smtk/project/operators/Read.cxx +++ b/smtk/project/operators/Read.cxx @@ -24,8 +24,10 @@ #include "smtk/io/Logger.h" #include "smtk/operation/Hints.h" +#include "smtk/operation/Manager.h" #include "smtk/operation/operators/ReadResource.h" +#include "smtk/resource/Manager.h" #include "smtk/resource/json/Helper.h" #include "smtk/project/Manager.h" @@ -105,6 +107,8 @@ Read::Result Read::operateInternal() project->setId(projectId); project->setLocation(filename); + project->resources().setManager(this->managers()->get()); + project->operations().setManager(this->managers()->get()); // manually reset the "location" string so that smtk::project::from_json() can properly translate // resource paths to being relative (rather than absolute) diff --git a/smtk/project/testing/cxx/TestDefineOp.cxx b/smtk/project/testing/cxx/TestDefineOp.cxx index 21a2a3559f3855788b88425aa8caa895f0ae509f..da78ce0f261423e073c8a0b1dd2eb9cefd784e27 100644 --- a/smtk/project/testing/cxx/TestDefineOp.cxx +++ b/smtk/project/testing/cxx/TestDefineOp.cxx @@ -123,6 +123,8 @@ int TestDefineOp(int /*unused*/, char** const /*unused*/) { // Create an instance of smtk::project::Project auto project = projectManager->create("MyProject"); + projectManager->add( + project->index(), project); // Manual creation of project requires manual addition to manager. smtkTest(projectManager->projects().size() == 1, "Project not added to manaager"); // Create a resource diff --git a/smtk/project/testing/cxx/TestProject.cxx b/smtk/project/testing/cxx/TestProject.cxx index d416d56c13a99cd9357f72eb1f2e21d2a93cd85f..470730a4e828c76a480da4dea68cf110683321ca 100644 --- a/smtk/project/testing/cxx/TestProject.cxx +++ b/smtk/project/testing/cxx/TestProject.cxx @@ -99,6 +99,7 @@ int TestProject(int /*unused*/, char** const /*unused*/) { // Create an instance of smtk::project::Project auto project = projectManager->create("MyProject"); + projectManager->add(project->index(), project); smtkTest(projectManager->projects().size() == 1, "Project not added to manaager"); // Create a resource diff --git a/smtk/project/testing/cxx/TestProjectLifeCycle.cxx b/smtk/project/testing/cxx/TestProjectLifeCycle.cxx index eba0f8d3f8d46a611446ee9a183bf72d9f82cdbf..3dc0b6ca2228f9b3a8f1d6ca57f670eac58c2984 100644 --- a/smtk/project/testing/cxx/TestProjectLifeCycle.cxx +++ b/smtk/project/testing/cxx/TestProjectLifeCycle.cxx @@ -10,6 +10,7 @@ #include "smtk/attribute/Attribute.h" #include "smtk/attribute/IntItem.h" +#include "smtk/attribute/ReferenceItemDefinition.h" #include "smtk/attribute/Registrar.h" #include "smtk/attribute/Resource.h" #include "smtk/attribute/ResourceItem.h" @@ -23,13 +24,13 @@ #include "smtk/project/Project.h" #include "smtk/project/Registrar.h" #include "smtk/project/operators/Create.h" +#include "smtk/resource/Manager.h" #include "smtk/common/testing/cxx/helpers.h" #include #define ATT_ROLE_NAME "attributes" -#define OP_NAME "create-project-op" #define PROJECT_TYPE "foo" // This test verifies that projects can be instantiated outside of the @@ -62,12 +63,18 @@ public: Result operateInternal() override { auto project = m_projManager->create(PROJECT_TYPE); + if (this->managers()) + { + project->resources().setManager(this->managers()->get()); + project->operations().setManager(this->managers()->get()); + } auto attRes = m_projManager->resourceManager()->create(); project->resources().add(attRes, ATT_ROLE_NAME); auto result = this->createResult(Outcome::SUCCEEDED); - result->findResource("resource")->setValue(project); + auto res = result->findResource("resource"); + res->setValue(project); return result; } @@ -76,43 +83,43 @@ public: smtk::project::Manager::Ptr m_projManager; }; -const char CreateProjectOpXML[] = - "" - "" - " " - " " - " " - " " - " 0" - " " - " " - " " - " " - " " - " " - " " - " " - " " - " " - " " - " " - " " - " " - " 0" - " " - " " - " " - " " - " " - " " - " " - " " - " " - " " - " " - " " - " " - ""; +const char CreateProjectOpXML[] = R"xml( + + + + + + + 0 + + + + + + + + + + + + + + + 0 + + + + + + + + + + + + + +)xml"; const char* CreateProjectOp::xmlDescription() const { @@ -124,10 +131,14 @@ const char* CreateProjectOp::xmlDescription() const int TestProjectLifeCycle(int /*unused*/, char** const /*unused*/) { // Create managers + smtk::common::Managers::Ptr managers = smtk::common::Managers::create(); smtk::resource::ManagerPtr resManager = smtk::resource::Manager::create(); smtk::operation::ManagerPtr opManager = smtk::operation::Manager::create(); auto attributeRegistry = smtk::plugin::addToManagers(resManager, opManager); + managers->insert_or_assign(resManager); + managers->insert_or_assign(opManager); + opManager->setManagers(managers); #if 0 // This line changes behavior such that projects ARE stored in resource manager @@ -139,6 +150,7 @@ int TestProjectLifeCycle(int /*unused*/, char** const /*unused*/) smtk::project::ManagerPtr projManager = smtk::project::Manager::create(resManager, opManager); auto projectRegistry = smtk::plugin::addToManagers(projManager); projManager->registerProject(PROJECT_TYPE); + managers->insert_or_assign(projManager); smtkTest(resManager->empty(), "resource manager size is " << resManager->size()); @@ -148,16 +160,16 @@ int TestProjectLifeCycle(int /*unused*/, char** const /*unused*/) std::cout << "Creating project" << std::endl; auto createOp = opManager->create(); - smtkTest(createOp != nullptr, "create operation not created") createOp->m_projManager = - projManager; // back door + smtkTest(createOp != nullptr, "create operation not created"); + createOp->m_projManager = projManager; // back door auto result = createOp->operate(); int outcome = result->findInt("outcome")->value(); smtkTest(outcome == OP_SUCCEEDED, "create operation failed"); - auto res = result->findResource("resource")->value(); - project = std::dynamic_pointer_cast(res); - smtkTest(res != nullptr, "project not created"); + auto res = result->findResource("resource"); + project = std::dynamic_pointer_cast(res->value(0)); + smtkTest(res->value() != nullptr, "project not created"); } { diff --git a/smtk/project/testing/cxx/TestProjectPortability.cxx b/smtk/project/testing/cxx/TestProjectPortability.cxx index 32d383d86972c6b6cbcd1a3f116efc009e10fe5f..3d2f1b92b05456c3d5f667c43af73ca3b95ec669 100644 --- a/smtk/project/testing/cxx/TestProjectPortability.cxx +++ b/smtk/project/testing/cxx/TestProjectPortability.cxx @@ -150,9 +150,15 @@ int TestProjectPortability(int /*unused*/, char** const /*unused*/) // Create an operation manager smtk::operation::Manager::Ptr operationManager = smtk::operation::Manager::create(); + // Create common::Managers "application state". + auto managers = smtk::common::Managers::create(); + managers->insert_or_assign(resourceManager); + managers->insert_or_assign(operationManager); + // Register the resource manager to the operation manager (newly created // resources will be automatically registered to the resource manager). operationManager->registerResourceManager(resourceManager); + operationManager->setManagers(managers); // Create a project manager smtk::project::ManagerPtr projectManager = @@ -180,6 +186,8 @@ int TestProjectPortability(int /*unused*/, char** const /*unused*/) std::cerr << "Failed to create a project\n"; return 1; } + projectManager->add( + project->index(), project); // We didn't run a Create operation so we must add manually. { // Create an import operator diff --git a/smtk/project/testing/cxx/TestProjectReadWrite.cxx b/smtk/project/testing/cxx/TestProjectReadWrite.cxx index f5665a456e48d19502ab6e0176c3df4487f69fb3..cb49e86d3f9520fdf0e3639d54dc19ea398832b1 100644 --- a/smtk/project/testing/cxx/TestProjectReadWrite.cxx +++ b/smtk/project/testing/cxx/TestProjectReadWrite.cxx @@ -73,6 +73,11 @@ int TestProjectReadWrite(int /*unused*/, char** const /*unused*/) // Create an operation manager smtk::operation::Manager::Ptr operationManager = smtk::operation::Manager::create(); + // Create common::Managers "application state". + auto managers = smtk::common::Managers::create(); + managers->insert_or_assign(resourceManager); + managers->insert_or_assign(operationManager); + auto attributeRegistry = smtk::plugin::addToManagers(resourceManager, operationManager); auto meshRegistry = @@ -83,6 +88,7 @@ int TestProjectReadWrite(int /*unused*/, char** const /*unused*/) // Register the resource manager to the operation manager (newly created // resources will be automatically registered to the resource manager). operationManager->registerResourceManager(resourceManager); + operationManager->setManagers(managers); // Create a project manager smtk::project::ManagerPtr projectManager = @@ -105,6 +111,8 @@ int TestProjectReadWrite(int /*unused*/, char** const /*unused*/) std::cerr << "Failed to create a project\n"; return 1; } + projectManager->add( + project->index(), project); // We didn't run a Create operation so we must add manually. { // Create an import operator diff --git a/smtk/project/testing/cxx/TestProjectReadWrite2.cxx b/smtk/project/testing/cxx/TestProjectReadWrite2.cxx index f947dabd40779f9505e0eb6f76a6f2c5adf5ae39..392885e6b8774866a9bf07da278dd926de3cd6c0 100644 --- a/smtk/project/testing/cxx/TestProjectReadWrite2.cxx +++ b/smtk/project/testing/cxx/TestProjectReadWrite2.cxx @@ -84,9 +84,15 @@ int TestProjectReadWrite2(int /*unused*/, char** const /*unused*/) // Create an operation manager smtk::operation::Manager::Ptr operationManager = smtk::operation::Manager::create(); + // Create common::Managers "application state". + auto managers = smtk::common::Managers::create(); + managers->insert_or_assign(resourceManager); + managers->insert_or_assign(operationManager); + // Register the resource manager to the operation manager (newly created // resources will be automatically registered to the resource manager). operationManager->registerResourceManager(resourceManager); + operationManager->setManagers(managers); // Create a project manager smtk::project::ManagerPtr projectManager = @@ -114,6 +120,8 @@ int TestProjectReadWrite2(int /*unused*/, char** const /*unused*/) std::cerr << "Failed to create a project\n"; return 1; } + projectManager->add( + project->index(), project); // We didn't run a Create operation so we must add manually. { // Create an import operator diff --git a/smtk/project/testing/cxx/TestProjectReadWriteEmpty.cxx b/smtk/project/testing/cxx/TestProjectReadWriteEmpty.cxx index 69574907e73399c92b8e93df6daaa45dff88da17..7e4e7c31bf4e25c44ede16ba0867d5e48a8e84e2 100644 --- a/smtk/project/testing/cxx/TestProjectReadWriteEmpty.cxx +++ b/smtk/project/testing/cxx/TestProjectReadWriteEmpty.cxx @@ -53,11 +53,17 @@ int TestProjectReadWriteEmpty(int /*unused*/, char** const /*unused*/) { // Create smtk managers smtk::resource::Manager::Ptr resourceManager = smtk::resource::Manager::create(); - smtk::operation::Manager::Ptr operationManager = smtk::operation::Manager::create(); + + // Create common::Managers "application state". + auto managers = smtk::common::Managers::create(); + managers->insert_or_assign(resourceManager); + managers->insert_or_assign(operationManager); + auto operationRegistry = smtk::plugin::addToManagers(operationManager); operationManager->registerResourceManager(resourceManager); + operationManager->setManagers(managers); smtk::project::ManagerPtr projectManager = smtk::project::Manager::create(resourceManager, operationManager); @@ -71,6 +77,7 @@ int TestProjectReadWriteEmpty(int /*unused*/, char** const /*unused*/) // Create empty project and write it to disk. { smtk::project::Project::Ptr project = projectManager->create("foo"); + projectManager->add(project); // No Create operation means we must manage it manually. smtk::operation::WriteResource::Ptr writeOp = operationManager->create(); diff --git a/smtk/project/testing/cxx/TestProjectResources.cxx b/smtk/project/testing/cxx/TestProjectResources.cxx index 1d9e270d3cf05726f17b31e6a6dbab48d65e7b30..f266c00e36c494e6666583e8980cd41d766c7823 100644 --- a/smtk/project/testing/cxx/TestProjectResources.cxx +++ b/smtk/project/testing/cxx/TestProjectResources.cxx @@ -82,6 +82,7 @@ int TestProjectResources(int /*unused*/, char** const /*unused*/) { // Create an instance of smtk::project::Project auto project = projectManager->create(); + projectManager->add(project); // We didn't run a Create operation; we must add manually. smtkTest(projectManager->projects().size() == 1, "Project not added to manager"); // Create a resource diff --git a/smtk/project/testing/python/TestManagersAccess.cxx b/smtk/project/testing/python/TestManagersAccess.cxx index dedefb853238e6e75ca48dbfbe5576e4bfdac313..c7a29d9b7244b3fab024b4efa393d7bf4705a372 100644 --- a/smtk/project/testing/python/TestManagersAccess.cxx +++ b/smtk/project/testing/python/TestManagersAccess.cxx @@ -54,6 +54,7 @@ int main(int argc, char* argv[]) // Register a new project type and create one instance projectManager->registerProject("xyzzy"); smtk::project::Project::Ptr project = projectManager->create("xyzzy"); + projectManager->add(project); smtkTest(project != nullptr, "failed to create project."); smtkTest(project->setName("plugh"), "failed to set project name."); diff --git a/smtk/resource/GarbageCollector.h b/smtk/resource/GarbageCollector.h index 9828e2311f663ea794095de4a4620190df349e81..f03fa26cb1185b3f504d99e17caab57dd66f3eaa 100644 --- a/smtk/resource/GarbageCollector.h +++ b/smtk/resource/GarbageCollector.h @@ -32,7 +32,7 @@ namespace resource * managing them). * * Note that your deletion operation must (a) accept associations, (b) prevent - * its associations from holding references (i.e., "HoldReferences"="false"), + * its associations from holding references (i.e., "HoldReference"="false"), * and (c) require at least 1 associated persistent object. * * This class works by adding an observer to the registered operation manager diff --git a/smtk/string/Token.cxx b/smtk/string/Token.cxx index 8cce203b5ce802e041423e7b24d0dd35ef3393ac..130ec1ddac232cf45ce5e158de66f3de7027b0dd 100644 --- a/smtk/string/Token.cxx +++ b/smtk/string/Token.cxx @@ -55,7 +55,7 @@ bool Token::hasData() const bool Token::valid() const { - return m_id == smtk::string::Manager::Invalid; + return m_id != smtk::string::Manager::Invalid; } bool Token::operator==(const Token& other) const diff --git a/smtk/string/Token.h b/smtk/string/Token.h index 43ba0540dac6ac94b36453d3a6a828e054bb8b3d..905953f4b26db68b1e41f3640a58921fe91b60ae 100644 --- a/smtk/string/Token.h +++ b/smtk/string/Token.h @@ -41,6 +41,9 @@ public: { } + /// An invalid hash, used with the constructor above to make an invalid token. + static constexpr Hash Invalid = Manager::Invalid; + /// Return the token's ID (usually its hash but possibly not in the case of collisions). Hash id() const { return m_id; } /// Return the string corresponding to the token. diff --git a/smtk/string/testing/cxx/TestToken.cxx b/smtk/string/testing/cxx/TestToken.cxx index 82f044d7618aaa393c18692b74d75ac97ab16c4a..6f459c4ab523fd9de5b3df4702ab29e5a8894350 100644 --- a/smtk/string/testing/cxx/TestToken.cxx +++ b/smtk/string/testing/cxx/TestToken.cxx @@ -136,5 +136,10 @@ int TestToken(int, char*[]) ++expectedCandy; } + smtk::string::Token naughty(smtk::string::Token::Invalid); + smtk::string::Token uninitialized; + test(!naughty.valid(), "Improper validity check."); + test(!uninitialized.valid(), "Uninitialized tokens should be invalid."); + return 0; }