...
 
Commits (28)
# This file specifies people who "own" specific
# pieces of code in SMTK. It is used to suggest
# reviewers for merge requests based on the set
# of files changed in the MR.
# By default, Bob owns everything.
# This is overridden below.
* bob.obara@kitware.com
# Subsystems have specific owners
conda/ tj.corona@kitware.com john.tourtellott@kitware.com
doc/tutorials/ david.thompson@kitware.com
doc/userguide/ david.thompson@kitware.com
smtk/attribute/ bob.obara@kitware.com
smtk/common/ david.thompson@kitware.com
smtk/extension/opencv/ jacob.becker@kitware.com
smtk/extension/paraview/ david.thompson@kitware.com
smtk/extension/qt/ bob.obara@kitware.com
smtk/extension/vtk/ david.thompson@kitware.com haocheng.liu@kitware.com tj.corona@kitware.com
smtk/io/ bob.obara@kitware.com tj.corona@kitware.com david.thompson@kitware.com
smtk/mesh/ tj.corona@kitware.com
smtk/model/ david.thompson@kitware.com
smtk/operation/ tj.corona@kitware.com
smtk/plugin/ tj.corona@kitware.com
smtk/project/ john.tourtellott@kitware.com
smtk/pybind11/ tj.corona@kitware.com
smtk/resource/ tj.corona@kitware.com david.thompson@kitware.com bob.obara@kitware.com
smtk/session/discrete/ bob.obara@kitware.com
smtk/session/multiscale/ tj.corona@kitware.com
smtk/session/oscillator/ david.thompson@kitware.com
smtk/session/polygon/ david.thompson@kitware.com
smtk/session/vtk/ david.thompson@kitware.com
smtk/simulation/ john.tourtellott@kitware.com
smtk/smtk/ tj.corona@kitware.com david.thompson@kitware.com
smtk/view/ david.thompson@kitware.com
utilities/ ben.boeckel@kitware.com
......@@ -8,7 +8,7 @@
// PURPOSE. See the above copyright notice for more information.
//=========================================================================
#include "smtk/plugin/InitializeDefaultPlugins.h"
#include "Initialize@PLUGIN_LIBRARY_TARGET@.h"
#include "vtkPVPlugin.h"
#include "vtkPVPluginLoader.h"
......@@ -40,15 +40,15 @@ namespace extension
{
namespace paraview
{
void initializeDefaultPlugins()
void initialize@PLUGIN_LIBRARY_TARGET@()
{
vtkPVPluginLoader::RegisterLoadPluginCallback(loadPlugins);
vtkPVPluginTracker::SetStaticPluginSearchFunction(searchPlugins);
}
void loadDefaultPlugins()
void load@PLUGIN_LIBRARY_TARGET@()
{
@SMTK_PLUGIN_IMPORT@
@SMTK_PLUGIN_IMPORT@
}
}
}
......
......@@ -7,10 +7,10 @@
// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
// PURPOSE. See the above copyright notice for more information.
//=========================================================================
#ifndef __smtk_InitializeDefaultPlugins_h
#define __smtk_InitializeDefaultPlugins_h
#ifndef __smtk_@PLUGIN_LIBRARY_TARGET@_InitializePlugins_h
#define __smtk_@PLUGIN_LIBRARY_TARGET@_InitializePlugins_h
#include "smtk/plugin/Exports.h"
#include "@PLUGIN_LIBRARY_TARGET@Export.h"
namespace smtk
{
......@@ -18,8 +18,8 @@ namespace extension
{
namespace paraview
{
SMTKDEFAULTPLUGINS_EXPORT void initializeDefaultPlugins();
SMTKDEFAULTPLUGINS_EXPORT void loadDefaultPlugins();
@SMTK_PLUGIN_LIBRARY_EXPORT@ void initialize@PLUGIN_LIBRARY_TARGET@();
@SMTK_PLUGIN_LIBRARY_EXPORT@ void load@PLUGIN_LIBRARY_TARGET@();
}
}
}
......
......@@ -6,22 +6,72 @@
#
#=========================================================================
# SMTK plugins are extensions of ParaView plugins that allow for the automatic
# registration of components to SMTK managers. They are created using the
# function "add_smtk_plugin", which requires the developer to explicitly list
# a registration class "known as a Registrar" and a list of SMTK manager types
# to which the plugin registers. SMTK plugins can be introduced to a
# ParaView-based application in several ways. The consuming project can
#
# 1) list the plugins in a configuration file that is subsequently read at
# runtime, deferring the inclusion of plugins to the application's runtime. This
# approach requires plugins to reside in certain locations that the application
# is expected to look, but facilitates the presentation of a plugin to the user
# without automatically loading the plugin. For this approach, a consuming
# project can call "generate_smtk_plugin_config_file" to convert the list of
# smtk plugin targets (which can be a part of the project or imported from
# another project) described by the global property "SMTK_PLUGINS" into a
# configuration file. The consuming project can also
#
# 2) directly link plugins into the application. This approach pushes the
# requirement of locating plugins to be a build-time dependency, which can be
# advantageous for packaging. Plugins that are directly linked to an application
# cannot be disabled, however (i.e. the target property ENABLED_BY_DEFAULT is
# ignored, as it is true for all plugins). To use this approach, a consuming
# project can call "generate_smtk_plugin_library" to to use the list of smtk
# plugin targets (which can be a part of the project or imported from another
# project) described by the global property "SMTK_PLUGINS" to generate a library
# against which the application can link to directly incorporate the associated
# plugins.
define_property(GLOBAL PROPERTY SMTK_PLUGINS
BRIEF_DOCS "Global property for aggregating smtk plugin targets"
FULL_DOCS "Global property for aggregating smtk plugin targets")
define_property(TARGET PROPERTY ENABLED_BY_DEFAULT
BRIEF_DOCS "Option to enable plugin by default"
FULL_DOCS "Option to enable plugin by default")
# Enable an SMTK plugin by default
#
# enable_smtk_plugin_by_default(target choice)
function(enable_smtk_plugin_by_default target choice)
# Append a property to the plugin target that conveys whether or not the
# plugin should be loaded by default
set_property(TARGET ${target} PROPERTY ENABLED_BY_DEFAULT ${choice})
# Mark the property for export (so consuming libraries can access it)
set_property(TARGET ${target} APPEND PROPERTY EXPORT_PROPERTIES ENABLED_BY_DEFAULT)
endfunction(enable_smtk_plugin_by_default)
# create a plugin
# ENABLED_BY_DEFAULT is an option to indicate whether or not the plugin should
# be loaded by default. This only applies when plugins are loaded via a
# plugin list at runtime (as opposed to directly linking to plugins).
# REGISTRAR is used to register the plugin
# REGISTRAR_HEADER is the include file for the registrar (if unset, a file name
# is inferred from the REGISTRAR value)
# is inferred from the REGISTRAR value)
# MANAGERS is a list of managers to which the plugin can register
# LIBRARIES is a list of libraries against which the plugin must link
# LIBRARIES_PRIVATE is a list of libraries against which the plugin must
# privately link
# privately link
#
# All other arguments are forwarded to add_paraview_plugin()
#
# add_smtk_plugin(Name Version
# [ENABLED_BY_DEFAULT]
# [REGISTRAR registrar]
# [REGISTRAR_HEADER headerfile]
# [MANAGERS list of managers used]
......@@ -29,7 +79,7 @@ define_property(GLOBAL PROPERTY SMTK_PLUGINS
# [LIBRARIES list of required private libraries]
# )
function(add_smtk_plugin SMTK_PLUGIN_NAME SMTK_PLUGIN_VERSION)
set(options)
set(options ENABLED_BY_DEFAULT)
set(oneValueArgs REGISTRAR REGISTRAR_HEADER)
set(multiValueArgs MANAGERS LIBRARIES LIBRARIES_PRIVATE)
cmake_parse_arguments(SMTK_PLUGIN
......@@ -65,6 +115,8 @@ function(add_smtk_plugin SMTK_PLUGIN_NAME SMTK_PLUGIN_VERSION)
${SMTK_PLUGIN_UNPARSED_ARGUMENTS}
)
enable_smtk_plugin_by_default(${SMTK_PLUGIN_NAME} ${SMTK_PLUGIN_ENABLED_BY_DEFAULT})
target_link_libraries(${SMTK_PLUGIN_NAME}
LINK_PUBLIC
${SMTK_PLUGIN_LIBRARIES}
......@@ -76,3 +128,121 @@ function(add_smtk_plugin SMTK_PLUGIN_NAME SMTK_PLUGIN_VERSION)
)
endfunction(add_smtk_plugin)
# create a plugin config file including all of the plugins generated by this
# project.
# PLUGIN_CONFIG_FILE_NAME is the name of the file to create
# RELATIVE_DIRECTORY is a directory relative to which plugin locations should
# be described.
function(generate_smtk_plugin_config_file PLUGIN_CONFIG_FILE_NAME)
set(options)
set(oneValueArgs RELATIVE_DIRECTORY)
set(multiValueArgs)
cmake_parse_arguments(SMTK_PLUGIN_CONFIG
"${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}
)
set(plugins_file ${PLUGIN_CONFIG_FILE_NAME})
set(SMTK_PLUGINS_FILE_CONTENTS)
foreach (plugin IN LISTS SMTK_PLUGINS)
get_property(${plugin}_location TARGET ${plugin} PROPERTY LOCATION)
if (SMTK_PLUGIN_CONFIG_RELATIVE_DIRECTORY)
file(RELATIVE_PATH ${plugin}_location
${SMTK_PLUGIN_CONFIG_RELATIVE_DIRECTORY} ${${plugin}_location})
endif ()
get_property(${plugin}_enabled_by_default TARGET ${plugin} PROPERTY ENABLED_BY_DEFAULT)
set(${plugin}_enabled_by_default_val 0)
if (${plugin}_enabled_by_default)
set(${plugin}_enabled_by_default_val 1)
endif ()
string(APPEND SMTK_PLUGINS_FILE_CONTENTS
" <"
"Plugin name=\"${plugin}\" "
"filename=\"${${plugin}_location}\" "
"auto_load=\"${${plugin}_enabled_by_default_val}\" "
"/>\n")
endforeach ()
configure_file(${smtk_cmake_dir}/plugins.xml.in ${plugins_file} @ONLY)
endfunction(generate_smtk_plugin_config_file)
# create a library that directly links smtk plugins into a consuming
# application. The function creates a library target ${PLUGIN_LIBRARY_TARGET}
# and two header files (defined at parent scope in the list
# ${${PLUGIN_LIBRARY_TARGET}_HEADERS}). All targets contained in the
# ${SMTK_PLUGINS} list will be linked into the target library, and these plugins
# can be loaded by a consuming application by including the generated header
# file Initialize${PLUGIN_LIBRARY_TARGET}.h and by calling the generated methods
# smtk::extension::paraview::initialize${PLUGIN_LIBRARY_TARGET}() and
# smtk::extension::paraview::load${PLUGIN_LIBRARY_TARGET}().
function(generate_smtk_plugin_library PLUGIN_LIBRARY_TARGET)
include(${PARAVIEW_USE_FILE})
include(ParaViewPlugins)
# We need to add the current value of VTK_MODULES_DIR to the module path
# so that when the plugins are built all the modules can be found. Otherwise,
# modules that aren't loaded as direct dependencies of CMB modules will
# not be found.
list(APPEND CMAKE_MODULE_PATH "${VTK_MODULES_DIR}")
# Construct fields to populate the generated source files for the plugin
# library.
foreach (name IN LISTS SMTK_PLUGINS)
set(SMTK_PLUGIN_IMPORT_INIT "${SMTK_PLUGIN_IMPORT_INIT}PV_PLUGIN_IMPORT_INIT(${name});\n")
set(SMTK_PLUGIN_IMPORT "${SMTK_PLUGIN_IMPORT}PV_PLUGIN_IMPORT(${name});\n")
set(SMTK_PLUGIN_QUERY "${SMTK_PLUGIN_QUERY}queryPlugin(${name});\n")
endforeach()
# Generate a unique export symbol for the plugin library.
string(TOUPPER ${PLUGIN_LIBRARY_TARGET} SMTK_PLUGIN_LIBRARY_EXPORT)
string(APPEND SMTK_PLUGIN_LIBRARY_EXPORT "_EXPORT")
# Generate the header file that declares the two methods defined in the plugin
# library.
configure_file(${smtk_cmake_dir}/InitializePlugins.h.in
${CMAKE_CURRENT_BINARY_DIR}/Initialize${PLUGIN_LIBRARY_TARGET}.h @ONLY)
# Generate the source file that implements the abovementioned methods.
configure_file(${smtk_cmake_dir}/InitializePlugins.cxx.in
${CMAKE_CURRENT_BINARY_DIR}/Initialize${PLUGIN_LIBRARY_TARGET}.cxx @ONLY)
# Include the components from ParaView necessary for directly linking plugins
# into an application.
vtk_module_dep_includes(vtkPVClientServerCoreCore)
include_directories(${vtkPVClientServerCoreCore_INCLUDE_DIRS}
${vtkPVClientServerCoreCore_DEPENDS_INCLUDE_DIRS}
${CMAKE_CURRENT_BINARY_DIR})
# Construct the library.
add_library(${PLUGIN_LIBRARY_TARGET}
${CMAKE_CURRENT_BINARY_DIR}/Initialize${PLUGIN_LIBRARY_TARGET}.cxx)
# During the build phase, include the binary directory that contains the
# generated header file.
target_include_directories(${PLUGIN_LIBRARY_TARGET}
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>
)
# Link against all of the smtk plugins.
target_link_libraries(${PLUGIN_LIBRARY_TARGET}
LINK_PRIVATE ${SMTK_PLUGINS} vtkPVClientServerCoreCore)
# Generate an export header using the symbol defined in
# ${SMTK_PLUGIN_LIBRARY_EXPORT}.
include(GenerateExportHeader)
generate_export_header(${PLUGIN_LIBRARY_TARGET}
EXPORT_MACRO_NAME ${SMTK_PLUGIN_LIBRARY_EXPORT}
EXPORT_FILE_NAME ${PLUGIN_LIBRARY_TARGET}Export.h)
# Construct a list of generated headers for the plugin library that is
# accessible at parent scope. That way, consuming applications can install
# these header files where appropriate.
set(${PLUGIN_LIBRARY_TARGET}_HEADERS
${CMAKE_CURRENT_BINARY_DIR}/Initialize${PLUGIN_LIBRARY_TARGET}.h
${CMAKE_CURRENT_BINARY_DIR}/${PLUGIN_LIBRARY_TARGET}Export.h
PARENT_SCOPE)
endfunction(generate_smtk_plugin_library)
<?xml version="1.0"?>
<Plugins>
@SMTK_PLUGINS_FILE_CONTENTS@
</Plugins>
......@@ -21,7 +21,7 @@ if(IS_DIRECTORY "${smtk_module_dir}")
endif()
unset(smtk_module_dir)
set(smtk_cmake_dir ${CMAKE_CURRENT_LIST_DIR})
set(smtk_cmake_dir @SMTK_MODULE_DIR@)
#When building documentation internally we re-import ourselves, which
#doesnt work and generates CMake warnings. So we will only look for SMTK
......@@ -29,13 +29,19 @@ set(smtk_cmake_dir ${CMAKE_CURRENT_LIST_DIR})
if(TARGET smtkCore)
else()
find_package(Boost 1.64.0
find_package(Boost @SMTK_MINIMUM_BOOST_VERSION@
COMPONENTS @required_boost_components@ REQUIRED)
find_package(nlohmann_json REQUIRED)
find_package(MOAB REQUIRED)
set(SMTK_ENABLE_QT_SUPPORT @SMTK_ENABLE_QT_SUPPORT@)
if(SMTK_ENABLE_QT_SUPPORT)
find_package(Qt5 REQUIRED COMPONENTS Core OpenGL Widgets)
endif()
set(SMTK_ENABLE_PARAVIEW_SUPPORT @SMTK_ENABLE_PARAVIEW_SUPPORT@)
if(SMTK_ENABLE_PARAVIEW_SUPPORT)
find_package(Qt5 REQUIRED COMPONENTS OpenGL)
find_package(ParaView)
endif()
......@@ -62,6 +68,9 @@ else()
set(SMTK_PYTHON_EXECUTABLE "@smtk_python_executable@")
set(SMTK_PYTHON_LIBRARY "@smtk_python_library@")
set(SMTK_PYTHON_INCLUDE_DIR "@smtk_python_include_dir@")
# SMTK_PYTHON_MDOULEDIR is the location of the smtk python module relative
# to the location of this file.
set(SMTK_PYTHON_MODULEDIR "@smtk_python_moduledir@")
if (SMTK_PYTHON_EXECUTABLE AND NOT PYTHON_EXECUTABLE)
set(PYTHON_EXECUTABLE ${SMTK_PYTHON_EXECUTABLE})
......
......@@ -287,7 +287,8 @@ else()
list(APPEND required_boost_components regex)
endif()
find_package(Boost 1.64.0
set(SMTK_MINIMUM_BOOST_VERSION 1.64.0)
find_package(Boost ${SMTK_MINIMUM_BOOST_VERSION}
COMPONENTS ${required_boost_components} REQUIRED)
if(WIN32 AND MSVC)
......@@ -648,6 +649,9 @@ install(
FILES
${PROJECT_SOURCE_DIR}/CMake/SMTKMacros.cmake
${PROJECT_SOURCE_DIR}/CMake/EncodeStringFunctions.cmake
${PROJECT_SOURCE_DIR}/CMake/InitializePlugins.cxx.in
${PROJECT_SOURCE_DIR}/CMake/InitializePlugins.h.in
${PROJECT_SOURCE_DIR}/CMake/plugins.xml.in
${PROJECT_SOURCE_DIR}/CMake/SMTKOperationXML.cmake
${PROJECT_SOURCE_DIR}/CMake/SMTKPluginMacros.cmake
${PROJECT_SOURCE_DIR}/CMake/serverSource.cxx.in
......@@ -740,6 +744,7 @@ if (SMTK_ENABLE_PYTHON_WRAPPING)
set(smtk_python_executable ${PYTHON_EXECUTABLE})
set(smtk_python_library ${PYTHON_LIBRARIES})
set(smtk_python_include_dir ${PYTHON_INCLUDE_DIRS})
set(smtk_python_moduledir)
endif ()
set(SMTK_VTK_MODULE_DIR "${CMAKE_BINARY_DIR}/lib/cmake/${PROJECT_NAME}/${PROJECT_VERSION}")
......@@ -774,6 +779,9 @@ if (SMTK_RELOCATABLE_INSTALL)
set(smtk_python_executable)
set(smtk_python_library)
set(smtk_python_include_dir)
file(RELATIVE_PATH smtk_python_moduledir
${CMAKE_INSTALL_PREFIX}/${SMTK_INSTALL_CONFIG_DIR}
${CMAKE_INSTALL_PREFIX}/${SMTK_PYTHON_MODULEDIR})
endif ()
endif()
......
#New View Type - Associations
This view has the same syntax as an Attribute View but only allows the user to change the association information of the attribute resulting in taking up less screen Real Estate
#Changes to Attribute View
* added a new XML attribute "HideAssociations". If set to true the view will not display the association editing widget save screen Real Estate
#Changes to Attribute Association Related API
* attribute::Attribute
* Removed functionality to maintain model::resource's attribute association back-store (no longer needed)
* Added a protected method forceDisassociate that will bypass disassociation checks. This is used by the attribute::Resource when disassociating all attributes from an object.
* Added association checks to the associate method.
* attribute::Resource
* Added hasAttributes method to check to see if an object has attributes associated to it
* Added disassociateAllAttributes method to remove all attribute associations from an object
* model::Entity
* Removed functionality to maintain model::resource's attribute association back-store (no longer needed)
* model::EntityRef
* Removed functionality to maintain model::resource's attribute association back-store (no longer needed) and replaced it with link-based functionality
* Added hasAttributes(smtk::attribute::ConstResourcePtr attRes) const
* Added disassociation methods that don't take in the reverse bool parameter. The original API which does take in the reverse parameter is marked for depreciation (via comment) and calls the new API
#Track attribute resource id, associations in xml I/O
In order for an attribute resource to be reliably written/read to/from
XML, its ID is now stored in its generated .sbi file. Additionally, an
attribute's associations are stored in XML with enough information to
recreate their underlying links.
#General Changes to smtk::model
* Added Pybind11 Registrar methods for smtk::model
## Add logic in the PV layer to force observers to fire on main thread
Qt requires that all methods that affect the GUI be performed on the application's main thread. Many of the registered Observer functions for both operations and resources are designed to affect the GUI. Rather than connect GUI-modifying logic to a signal triggered by an observer, we mutate the behavior of the operation and resource Observer calling logic to ensure that all Observer functors are called on the main thread, regardless of which thread performs the observation.
To support this pattern, SMTK's Observer pattern has been generalized to a single description (smtk::common::Observers) that adopts a run-time configurable type of polymorphism where consuming code can change the class's behavior, allowing consuming code to redefine the context in which the Observer functors are executed.
Add cmake logic to generate a plugin config file
ParaView-derived applications currently ingest plugins in one of two
ways: the plugins are either linked directly into the application, or
they are loaded at runtime. For the latter case, plugin discovery is
performed by reading xml files that describe the plugin's name,
location and whether or not it should be automatically loaded.
When SMTK (and SMTK-derived) plugins are intended to be loaded at
runtime, it is convenient to have the plugin config file generated
using the CMake target properties from the plugins themselves. This
change introduces the exported plugin target property
`ENABLED_BY_DEFAULT` and adds the CMake function
`generate_smtk_plugin_config_file` for ParaView-derived applications
that consume SMTK plugins to generate a plugins file.
When SMTK (and SMTK-derived) plugins are intended to be directly
linked into an application , it is convenient to have a plugin library
generated using the CMake target properties from the plugins
themselves. This change adds the CMake function
`generate_smtk_plugin_library` for ParaView-derived applications
that consume SMTK plugins to generate a plugin library.
......@@ -45,6 +45,7 @@ simulation domain.
extension/index.rst
workflow/index.rst
bindings/index.rst
plugin/index.rst
administration.rst
contributing.rst
......
.. _smtk-plugin-sys:
-----------------------
SMTK's Plugin System
-----------------------
SMTK's functionality can be extended by consuming projects via the
generation of *plugins*.
.. toctree::
:maxdepth: 3
plugins.rst
Plugins
========
SMTK plugins are extensions of ParaView plugins that allow for the automatic
registration of components to SMTK managers. They are created using the
CMake function "add_smtk_plugin", which requires the developer to explicitly list
a registration class "known as a Registrar" and a list of SMTK manager types
to which the plugin registers. SMTK plugins can be introduced to a
ParaView-based application in several ways. The consuming project can
1) list the plugins in a configuration file that is subsequently read at
runtime, deferring the inclusion of plugins to the application's runtime. This
approach requires plugins to reside in certain locations that the application
is expected to look, but facilitates the presentation of a plugin to the user
without automatically loading the plugin. For this approach, a consuming
project can call "generate_smtk_plugin_config_file" to convert the list of
smtk plugin targets (which can be a part of the project or imported from
another project) described by the global property "SMTK_PLUGINS" into a
configuration file. The consuming project can also
2) directly link plugins into the application. This approach pushes the
requirement of locating plugins to be a build-time dependency, which can be
advantageous for packaging. Plugins that are directly linked to an application
cannot be disabled, however (i.e. the target property ENABLED_BY_DEFAULT is
ignored, as it is true for all plugins). To use this approach, a consuming
project can call "generate_smtk_plugin_library" to to use the list of smtk
plugin targets (which can be a part of the project or imported from another
project) described by the global property "SMTK_PLUGINS" to generate a library
against which the application can link to directly incorporate the associated
plugins.
......@@ -333,15 +333,6 @@ bool Attribute::removeAllAssociations(bool partialRemovalOk)
return false;
}
}
// If we are here then there are no prerequisite issues to deal with
for (auto oit = m_associatedObjects->begin(); oit != m_associatedObjects->end(); ++oit)
{
auto modelEnt = dynamic_pointer_cast<smtk::model::Entity>(*oit);
if (modelEnt)
{
modelEnt->modelResource()->disassociateAttribute(nullptr, this->id(), modelEnt->id(), false);
}
}
m_associatedObjects->reset();
return true;
}
......@@ -405,7 +396,7 @@ bool Attribute::isObjectAssociated(const smtk::resource::PersistentObjectPtr& co
*/
bool Attribute::isEntityAssociated(const smtk::common::UUID& entity) const
{
return m_associatedObjects ? m_associatedObjects->has(entity) : false;
return this->isObjectAssociated(entity);
}
/**\brief Is the model entity of the \a entityref associated with this attribute?
......@@ -457,26 +448,29 @@ smtk::common::UUIDs Attribute::associatedModelEntityIds() const
bool Attribute::associate(smtk::resource::PersistentObjectPtr obj)
{
bool res = this->isObjectAssociated(obj);
if (res)
if (this->isObjectAssociated(obj))
{
return res;
return true;
}
// Lets see if we have any conflicts
res = m_associatedObjects ? m_associatedObjects->appendObjectValue(obj) : false;
if (!res)
if (m_definition->checkForConflicts(obj) != nullptr)
{
return res;
return false;
}
// What about missing prerequisites?
if (m_definition->checkForPrerequisites(obj) != nullptr)
{
return false;
}
auto modelEnt = std::dynamic_pointer_cast<smtk::model::Entity>(obj);
if (modelEnt)
// Did it pass the association rules?
if ((m_associatedObjects != nullptr) && m_associatedObjects->appendObjectValue(obj))
{
res = modelEnt->modelResource()->associateAttribute(nullptr, this->id(), modelEnt->id());
return true;
}
return res;
// Failed the association rules
return false;
}
/**\brief Associate a new-style model ID (a UUID) with this attribute.
......@@ -530,14 +524,6 @@ bool Attribute::associateEntity(const smtk::model::EntityRef& entityRef)
res =
(m_associatedObjects) ? m_associatedObjects->appendObjectValue(entityRef.component()) : false;
if (!res)
return res;
smtk::model::ResourcePtr modelMgr = entityRef.resource();
if (modelMgr)
{
res = modelMgr->associateAttribute(nullptr, this->id(), entityRef.entity());
}
return res;
}
......@@ -659,8 +645,7 @@ bool Attribute::disassociate(smtk::resource::PersistentObjectPtr obj, bool rever
return this->disassociate(obj, foo, reverse);
}
bool Attribute::disassociate(
smtk::resource::PersistentObjectPtr obj, AttributePtr& probAtt, bool reverse)
bool Attribute::disassociate(smtk::resource::PersistentObjectPtr obj, AttributePtr& probAtt, bool)
{
if (!this->canBeDisassociated(obj, probAtt))
{
......@@ -673,18 +658,17 @@ bool Attribute::disassociate(
return true;
}
m_associatedObjects->removeValue(idx);
if (reverse)
{
auto modelEnt = std::dynamic_pointer_cast<smtk::model::Entity>(obj);
if (modelEnt)
{
modelEnt->modelResource()->disassociateAttribute(
this->attributeResource(), this->id(), modelEnt->id(), false);
}
}
return true;
}
void Attribute::forceDisassociate(smtk::resource::PersistentObjectPtr obj)
{
std::ptrdiff_t idx = m_associatedObjects->find(obj);
if (idx >= 0)
{
m_associatedObjects->removeValue(idx);
}
}
/**\brief Return the item with the given \a inName, searching in the given \a style.
*
* The search style dictates whether children of conditional items are included
......
......@@ -50,8 +50,14 @@ class SMTKCORE_EXPORT Attribute : public resource::Component
public:
smtkTypeMacro(smtk::attribute::Attribute);
struct SMTKCORE_EXPORT WeakAttributePtrCompare
struct SMTKCORE_EXPORT CompareByName
{
bool operator()(
const smtk::attribute::AttributePtr& lhs, const smtk::attribute::AttributePtr& rhs) const
{
return lhs->name() < rhs->name();
}
bool operator()(const smtk::attribute::WeakAttributePtr& lhs,
const smtk::attribute::WeakAttributePtr& rhs) const
{
......@@ -283,6 +289,10 @@ protected:
Attribute(const std::string& myName, smtk::attribute::DefinitionPtr myDefinition);
void removeAllItems();
/// Used to disassociate an attribute from an object without checking constraints.
/// Typical use is either when all attributes are being disassocaited from the same
/// object or if the attribute is being deleted.
void forceDisassociate(smtk::resource::PersistentObjectPtr);
void addItem(smtk::attribute::ItemPtr iPtr) { m_items.push_back(iPtr); }
void setName(const std::string& newname) { m_name = newname; }
......
......@@ -203,6 +203,7 @@ smtk_public_headers(smtkCore ${attributeHeaders})
if (SMTK_ENABLE_PARAVIEW_SUPPORT)
add_smtk_plugin(
smtkAttributePlugin "1.0"
ENABLED_BY_DEFAULT
REGISTRAR smtk::attribute::Registrar
MANAGERS smtk::operation::Manager smtk::resource::Manager
LIBRARIES smtkCore)
......
......@@ -286,9 +286,13 @@ smtk::attribute::ItemPtr GroupItem::find(std::size_t element, const std::string&
const GroupItemDefinition* def =
static_cast<const GroupItemDefinition*>(this->definition().get());
int i = def->findItemPosition(inName);
if (i < 0)
{
return smtk::attribute::ItemPtr();
}
assert(m_items.size() > element);
assert(m_items[element].size() > static_cast<std::size_t>(i));
return (i < 0) ? smtk::attribute::ItemPtr() : m_items[element][static_cast<std::size_t>(i)];
return m_items[element][static_cast<std::size_t>(i)];
}
smtk::attribute::ConstItemPtr GroupItem::find(std::size_t element, const std::string& inName) const
......
......@@ -470,7 +470,15 @@ smtk::resource::PersistentObjectPtr ReferenceItem::objectValue(const ReferenceIt
// We can resolve the linked object.
return linkedObject;
}
// If we cannot resolve the linked object, let's check to see if the object
// is held by the same resource as this ReferenceItem. There's no need for
// resource management in this event.
else if (!key.first.isNull() &&
this->attribute()->resource()->links().data().at(key.first).resolve(
this->attribute()->resource()))
{
return this->attribute()->links().linkedObject(key);
}
return PersistentObjectPtr();
}
......@@ -501,6 +509,7 @@ bool ReferenceItem::resolve()
}
}
}
return allResolved;
}
}
......
......@@ -971,3 +971,40 @@ std::set<AttributePtr> Resource::attributes(
}
return result;
}
bool Resource::hasAttributes(const smtk::resource::ConstPersistentObjectPtr& object) const
{
// See if the object has any attributes - Note that the
// linkedFrom method takes in a const shared pointer to a non-const resource even though the method
// does not change the resource's state. The reason for this is due to issues of auto casting shared pointers to
// non-const objects to shared pointers to const objects.
auto objs = object->links().linkedFrom(
const_cast<Resource*>(this)->shared_from_this(), Resource::AssociationRole);
for (auto obj : objs)
{
auto entry = std::dynamic_pointer_cast<Attribute>(obj);
if (entry)
{ //If we find even one attribute report yes
return true;
}
}
return false;
}
void Resource::disassociateAllAttributes(const smtk::resource::PersistentObjectPtr& object)
{
// Get the attributes associated with this object - Note that the
// linkedFrom method takes in a const shared pointer to a non-const resource even though the method
// does not change the resource's state. The reason for this is due to issues of auto casting shared pointers to
// non-const objects to shared pointers to const objects.
auto objs = object->links().linkedFrom(
const_cast<Resource*>(this)->shared_from_this(), Resource::AssociationRole);
for (auto obj : objs)
{
auto entry = std::dynamic_pointer_cast<Attribute>(obj);
if (entry)
{ // Never insert a failed dynamic cast (null pointer)
entry->forceDisassociate(object);
}
}
}
......@@ -187,7 +187,12 @@ public:
// Return the attributes that are associated on a PersistentObject
std::set<AttributePtr> attributes(const smtk::resource::ConstPersistentObjectPtr& object) const;
bool hasAttributes() { return m_attributes.size() > 0; }
// true if the PersistentObject has attributes associated with it
bool hasAttributes(const smtk::resource::ConstPersistentObjectPtr& object) const;
bool hasAttributes() const { return m_attributes.size() > 0; }
void disassociateAllAttributes(const smtk::resource::PersistentObjectPtr& object);
// When a definition's items has changed use this method to update derived def
// item offsets which is used by the find item method
......@@ -224,8 +229,7 @@ protected:
smtk::attribute::ItemDefinition::CopyInfo& info);
std::map<std::string, smtk::attribute::DefinitionPtr> m_definitions;
std::map<std::string,
std::set<smtk::attribute::AttributePtr, Attribute::WeakAttributePtrCompare> >
std::map<std::string, std::set<smtk::attribute::AttributePtr, Attribute::CompareByName> >
m_attributeClusters;
std::map<std::string, smtk::attribute::AttributePtr> m_attributes;
std::map<smtk::common::UUID, smtk::attribute::AttributePtr> m_attributeIdMap;
......
......@@ -72,7 +72,8 @@ PySharedPtrClass< smtk::attribute::Resource, smtk::resource::Resource > pybind11
.def("findTopLevelViews", &smtk::attribute::Resource::findTopLevelViews)
.def("findView", &smtk::attribute::Resource::findView, py::arg("title"))
.def("findViewByType", &smtk::attribute::Resource::findViewByType, py::arg("vtype"))
.def("hasAttributes", &smtk::attribute::Resource::hasAttributes)
.def("hasAttributes", (bool (smtk::attribute::Resource::*) () const) &smtk::attribute::Resource::hasAttributes)
.def("hasAttributes", (bool (smtk::attribute::Resource::*) (const smtk::resource::ConstPersistentObjectPtr&) const) &smtk::attribute::Resource::hasAttributes, py::arg("object"))
.def("numberOfAdvanceLevels", &smtk::attribute::Resource::numberOfAdvanceLevels)
.def("numberOfAnalyses", &smtk::attribute::Resource::numberOfAnalyses)
.def("numberOfCategories", &smtk::attribute::Resource::numberOfCategories)
......
......@@ -33,16 +33,15 @@ int unitAttributeAssociation(int, char* [])
// ----
// I. First see how things work when Resource is not yet set.
attribute::ResourcePtr resptr = attribute::Resource::create();
attribute::Resource& res(*resptr.get());
smtkTest(
res.associations().empty() == true, "Resource should not have model storage by default.");
resptr->associations().empty() == true, "Resource should not have model storage by default.");
DefinitionPtr def = res.createDefinition("testDef");
DefinitionPtr def = resptr->createDefinition("testDef");
auto arule = def->createLocalAssociationRule();
def->setLocalAssociationMask(smtk::model::VERTEX);
arule->setIsExtensible(true);
arule->setMaxNumberOfValues(2);
AttributePtr att = res.createAttribute("testAtt", "testDef");
AttributePtr att = resptr->createAttribute("testAtt", "testDef");
UUID fakeEntityId = UUID::random();
smtkTest(!att->associateEntity(fakeEntityId),
......@@ -56,9 +55,9 @@ int unitAttributeAssociation(int, char* [])
// II. Now see how things work when the attribute resource has
// a valid model modelMgr pointer.
model::Resource::Ptr modelMgr = model::Resource::create();
res.associate(modelMgr);
smtkTest(
*res.associations().begin() == modelMgr, "Could not set attribute resource's model-resource.");
resptr->associate(modelMgr);
smtkTest(*resptr->associations().begin() == modelMgr,
"Could not set attribute resource's model-resource.");
smtk::model::Vertex v0 = modelMgr->addVertex();
smtk::model::Vertex v1 = modelMgr->addVertex();
......@@ -67,10 +66,11 @@ int unitAttributeAssociation(int, char* [])
"Could not associate a vertex to an attribute.");
att->disassociateEntity(v0);
smtkTest(!v0.hasAttributes(), "Disassociating an attribute did not notify the entity.");
smtkTest(!v0.hasAttributes(resptr), "Disassociating an attribute did not notify the entity.");
att->disassociateEntity(v1.entity());
smtkTest(!v1.hasAttributes(), "Disassociating a non-existent attribute appears to associate it.");
smtkTest(
!v1.hasAttributes(resptr), "Disassociating a non-existent attribute appears to associate it.");
v1.associateAttribute(att->attributeResource(), att->id());
att->removeAllAssociations();
......
......@@ -24,19 +24,36 @@ if __name__ == '__main__':
status = 0
# Create smtk model with 1 group item
mmgr = smtk.model.Resource.create()
modelResource = smtk.model.Resource.create()
mask = smtk.model.FACE | smtk.model.GROUP_ENTITY
group_item = mmgr.addGroup(mask, 'TopFaceBCS')
group_item = modelResource.addGroup(mask, 'TopFaceBCS')
# Create attribute resource with 1 def
resource = smtk.attribute.Resource.create()
defn = resource.createDefinition('testdef')
resource.associate(mmgr)
attributeResource = smtk.attribute.Resource.create()
defn = attributeResource.createDefinition('testdef')
attributeResource.associate(modelResource)
defn.setLocalAssociationMask(int(mask))
defn.associationRule().setIsExtensible(True)
# Create a resource manager
resMan = smtk.resource.Manager.create()
smtk.model.Registrar.registerTo(resMan)
smtk.attribute.Registrar.registerTo(resMan)
if resMan.add(modelResource):
print("Added model resource to manager")
else:
print("Failed to add model resource to manager")
status = -1
if resMan.add(attributeResource):
print("Added attribute resource to manager")
else:
print("Failed to add attribute resource to manager")
status = -1
# Create attribute and associate to group item
att = resource.createAttribute('testatt', defn)
att = attributeResource.createAttribute('testatt', defn)
a = att.associateEntity(group_item.entity())
if a:
......@@ -56,7 +73,7 @@ if __name__ == '__main__':
status = -1
# Check to see if the model entity knows about the attribute
if group_item.hasAttributes():
if group_item.hasAttributes(attributeResource):
print("Model Entity does have attributes")
if group_item.hasAttribute(att.id()):
print("Model Entity has this attribute associated")
......@@ -64,13 +81,13 @@ if __name__ == '__main__':
print(
"Model Entity does not have this attribute associated with it")
status = -1
uuids = group_item.attributeIds()
atts = group_item.attributes(attributeResource)
print(
"the number of attributes associated with the ent is ", len(uuids))
"the number of attributes associated with the ent is ", len(atts))
# There should be only 1 attribute on it
if len(uuids) == 1:
if len(atts) == 1:
print("Model Entity return correct attribute list size")
if att.id() in uuids:
if att in atts:
print(
"Model Entity did return the attribute via attributes() call")
else:
......
......@@ -28,6 +28,7 @@ set(commonHeaders
json/jsonLinks.h
json/jsonUUID.h
Links.h
Observers.h
Paths.h
RangeDetector.h
Registry.h
......
//=========================================================================
// 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_common_Observers_h
#define __smtk_common_Observers_h
#include <functional>
#include <map>
#include <type_traits>
#include <utility>
namespace smtk
{
namespace common
{
/// An Observer is a functor that is called when certain actions are performed.
/// This pattern allows for the injection of algorithms in response to a group
/// of actions (e.g. Resource added/removed, Operation about to run/has run).
/// Observers added to the Observers instance can also be initialized (allowing
/// for a retroactive response to items currently under observation).
///
/// In addition to adding and removing Observer functors to an Observers
/// instance, the execution logic of Observers can be overridden at runtime by
/// inserting an override functor into the Observers instance. This allows for
/// a run-time configurable type of polymorphism where consuming code can change
/// the behavior of the Observers class, allowing consuming code to redefine the
/// context in which Observer functors are executed. By default, the Observers
/// call operator iterates over and calls each Observer functor.
///
/// Variants exist for Observer functors with no return value, where the
/// Observer functors are simply called in sequence, and for Observer functions
/// with a binary return value, where the observer results are aggregated via a
/// bitwise OR operator.
template <typename Observer>
class Observers
{
public:
/// A key by which an Observer can be accessed within the Observers instance.
typedef int Key;
/// A functor to optionally initialize Observers as they are inserted into the
/// Observers instance.
typedef std::function<void(Observer&)> Initializer;
Observers()
: m_initializer()
{
}
Observers(Initializer&& initializer)
: m_initializer(initializer)
{
}
/// The call operator calls each of its Observer functors in sequence if there
/// is no override functor defined. Otherwise, it calls the override functor.
template <class... Types>
auto operator()(Types&&... args) -> decltype(std::declval<Observer>()(args...))
{
return m_override ? m_override.operator()(std::forward<Types>(args)...)
: callObserversDirectly(std::forward<Types>(args)...);
}
/// For Observer functors that return an integral value, call all Observer
/// functors and aggregate their output using a bitwise OR operator.
template <class... Types>
auto callObserversDirectly(Types&&... args) ->
typename std::enable_if<std::is_integral<decltype(std::declval<Observer>()(args...))>::value,
decltype(std::declval<Observer>()(args...))>::type
{
decltype(std::declval<Observer>()(args...)) result = 0;
// This careful loop allows an observer to erase itself.
typename std::map<Key, Observer>::iterator entry = m_observers.begin();
typename std::map<Key, Observer>::iterator next;
for (next = entry; entry != m_observers.end(); entry = next)
{
++next;
result |= entry->second(std::forward<Types>(args)...);
}
return result;
}
/// For Observer functors that do not return an integral value, simply call
/// all Observer functors.
template <class... Types>
auto callObserversDirectly(Types&&... args) ->
typename std::enable_if<!std::is_integral<decltype(std::declval<Observer>()(args...))>::value,
decltype(std::declval<Observer>()(args...))>::type
{
// This careful loop allows an observer to erase itself.
typename std::map<Key, Observer>::iterator entry = m_observers.begin();
typename std::map<Key, Observer>::iterator next;
for (next = entry; entry != m_observers.end(); entry = next)
{
++next;
entry->second(std::forward<Types>(args)...);
}
}
/// Ask to receive notification (and possibly a chance to respond to) events.
/// If the Observers instance has an initializer and initialization is
/// requested, the Observer functor is initialized (this is commonly a means
/// to run the Observer functor retroactively on things already under
/// observation). The return value is a handle that can be used to unregister
/// the observer.
Key insert(Observer fn, bool initialize = true)
{
Key handle = m_observers.empty() ? 0 : m_observers.rbegin()->first + 1;
if (initialize && m_initializer)
{
m_initializer(fn);
}
return m_observers.insert(std::make_pair(handle, fn)).second ? handle : -1;
}
/// Indicate that an observer should no longer be called. Returns the number
/// of remaining observers.
std::size_t erase(Key handle) { return m_observers.erase(handle); }
/// Return the observer for the given key if one exists or nullptr otherwise.
Observer find(Key handle) const
{
auto entry = m_observers.find(handle);
return entry == m_observers.end() ? nullptr : entry->second;
}
/// Return the number of Observer functors in this instance.
std::size_t size() const { return m_observers.size(); }
/// Replace the default implementation (calling each Observer functor in
/// sequence) with a new behavior.
void overrideWith(Observer fn) { m_override = fn; }
/// Remove the overriding behavior, restoring the default behavior (calling
/// each Observer functor when Observers is called).
void removeOverride() { m_override = Observer(); }
const Initializer& initializer() const { return m_initializer; }
void setInitializer(Initializer fn) { m_initializer = fn; }
protected:
// A map of observers. The observers are held in a map so that they can be
// referenced (and therefore removed) at a later time using the observer's
// associated key.
std::map<Key, Observer> m_observers;
// A functor to override the default behavior of the Observers' call method.
Observer m_override;
// A functor to override the default initialize method.
Initializer m_initializer;
};
}
}
#endif // __smtk_common_Observers_h
......@@ -24,6 +24,7 @@ set(PQComponentsSrcs
pqPluginSMTKViewBehavior.cxx
pqSMTKAttributePanel.cxx
pqSMTKBehavior.cxx
pqSMTKCallObserversOnMainThreadBehavior.cxx
pqSMTKCloseResourceBehavior.cxx
pqSMTKColorByToolBar.cxx
pqSMTKColorByWidget.cxx
......@@ -72,6 +73,7 @@ set(PQComponentsHeaders
pqSMTKBehavior.h
pqSMTKColorByToolBar.h
pqSMTKColorByWidget.h
pqSMTKCallObserversOnMainThreadBehavior.h
pqSMTKCloseResourceBehavior.h
pqSMTKDisplayAttributeOnLoadBehavior.h
pqSMTKRenderResourceBehavior.h
......@@ -339,6 +341,7 @@ add_paraview_plugin(
${PLUGIN_DOCK_IFACES_SRCS}
)
set_property(GLOBAL APPEND PROPERTY SMTK_PLUGINS "smtkPQComponentsPlugin")
enable_smtk_plugin_by_default(smtkPQComponentsPlugin TRUE)
target_link_libraries(smtkPQComponentsPlugin
LINK_PUBLIC
......
......@@ -13,6 +13,7 @@
#include "smtk/extension/paraview/appcomponents/pqPluginSMTKViewBehavior.h"
#include "smtk/extension/paraview/appcomponents/pqSMTKBehavior.h"
#include "smtk/extension/paraview/appcomponents/pqSMTKCallObserversOnMainThreadBehavior.h"
#include "smtk/extension/paraview/appcomponents/pqSMTKCloseResourceBehavior.h"
#include "smtk/extension/paraview/appcomponents/pqSMTKDisplayAttributeOnLoadBehavior.h"
#include "smtk/extension/paraview/appcomponents/pqSMTKExportSimulationBehavior.h"
......@@ -45,6 +46,7 @@ void pqSMTKAppComponentsAutoStart::startup()
auto renderResourceBehavior = pqSMTKRenderResourceBehavior::instance(this);
auto closeResourceBehavior = pqSMTKCloseResourceBehavior::instance(this);
auto saveOnCloseResourceBehavior = pqSMTKSaveOnCloseResourceBehavior::instance(this);
auto callObserversOnMainThread = pqSMTKCallObserversOnMainThreadBehavior::instance(this);
auto rsrcImportOpMgr = pqSMTKImportOperationBehavior::instance(this);
auto rsrcExportSimMgr = pqSMTKExportSimulationBehavior::instance(this);
auto pipelineSync = pqSMTKPipelineSelectionBehavior::instance(this);
......@@ -66,6 +68,7 @@ void pqSMTKAppComponentsAutoStart::startup()
pqCore->registerManager("smtk resource", rsrcMgr);
pqCore->registerManager("smtk render resource", renderResourceBehavior);
pqCore->registerManager("smtk save on close resource", saveOnCloseResourceBehavior);
pqCore->registerManager("call observers on main thread", callObserversOnMainThread);
pqCore->registerManager("smtk close resource", closeResourceBehavior);
pqCore->registerManager("smtk import operation", rsrcImportOpMgr);
pqCore->registerManager("smtk export simulation", rsrcExportSimMgr);
......@@ -87,6 +90,7 @@ void pqSMTKAppComponentsAutoStart::shutdown()
pqCore->unRegisterManager("smtk resource");
pqCore->unRegisterManager("smtk render resource");
pqCore->unRegisterManager("smtk save on close resource");
pqCore->unRegisterManager("call observers on main thread");
pqCore->unRegisterManager("smtk close resource");
pqCore->unRegisterManager("smtk import operation");
pqCore->unRegisterManager("smtk export simulation");
......
//=========================================================================
// 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/pqSMTKCallObserversOnMainThreadBehavior.h"
#include "pqServer.h"
#include "smtk/attribute/Attribute.h"
#include "smtk/attribute/Resource.h"
#include "smtk/extension/paraview/appcomponents/pqSMTKBehavior.h"
#include "smtk/extension/paraview/appcomponents/pqSMTKWrapper.h"
static pqSMTKCallObserversOnMainThreadBehavior* g_instance = nullptr;
pqSMTKCallObserversOnMainThreadBehavior::pqSMTKCallObserversOnMainThreadBehavior(QObject* parent)
: Superclass(parent)
{
// Whenever a new server is connected, mutate the calling logic of the
// operation and resource Observers to force observation to occur on the main
// thread.
QObject::connect(pqSMTKBehavior::instance(),
(void (pqSMTKBehavior::*)(pqSMTKWrapper*, pqServer*)) & pqSMTKBehavior::addedManagerOnServer,
this, &pqSMTKCallObserversOnMainThreadBehavior::forceObserversToBeCalledOnMainThread);
}
pqSMTKCallObserversOnMainThreadBehavior* pqSMTKCallObserversOnMainThreadBehavior::instance(
QObject* parent)
{
if (!g_instance)
{
g_instance = new pqSMTKCallObserversOnMainThreadBehavior(parent);
}
if (g_instance->parent() == nullptr && parent)
{
g_instance->setParent(parent);
}
return g_instance;
}
pqSMTKCallObserversOnMainThreadBehavior::~pqSMTKCallObserversOnMainThreadBehavior()
{
if (g_instance == this)
{
g_instance = nullptr;
}
QObject::disconnect(this);
}
void pqSMTKCallObserversOnMainThreadBehavior::forceObserversToBeCalledOnMainThread(
pqSMTKWrapper* wrapper, pqServer* server)
{
(void)server;
if (!wrapper)
{
return;
}
// Override the resource Observers' call method to emit a private signal
// instead of calling its Observer functors directly.
wrapper->smtkResourceManager()->observers().overrideWith(
[this](smtk::resource::Resource::Ptr rsrc, smtk::resource::EventType event) -> int {
m_activeResources[rsrc->id()] = rsrc;
emit resourceEvent(
QString::fromStdString(rsrc->id().toString()), static_cast<int>(event), QPrivateSignal());
return 0;
});
// Connect to the above signal on the main thread and call the Observer
// functors.
std::weak_ptr<smtk::resource::Manager> resourceManager = wrapper->smtkResourceManager();
QObject::connect(this,
(void (pqSMTKCallObserversOnMainThreadBehavior::*)(QString, int, QPrivateSignal)) &
pqSMTKCallObserversOnMainThreadBehavior::resourceEvent,
this, [this, resourceManager](QString resourceId, int event) {
auto id = smtk::common::UUID(resourceId.toStdString());
auto rsrc = m_activeResources[id];
if (auto resource = rsrc.lock())
{
if (auto manager = resourceManager.lock())
{
manager->observers().callObserversDirectly(
resource, static_cast<smtk::resource::EventType>(event));
}
}
m_activeResources.erase(id);
});
// Override the operation Observers' call method to emit a private signal
// instead of calling its Observer functors directly.
wrapper->smtkOperationManager()->observers().overrideWith(
[this](smtk::operation::Operation::Ptr oper, smtk::operation::EventType event,
smtk::operation::Operation::Result result) -> int {
auto id = smtk::common::UUID::random();
m_activeOperations[id] = oper;
emit operationEvent(QString::fromStdString(id.toString()), static_cast<int>(event),
result ? QString::fromStdString(result->name()) : QString(), QPrivateSignal());
return 0;
});
// Connect to the above signal on the main thread and call the Observer
// functors.
QObject::connect(this,
(void (pqSMTKCallObserversOnMainThreadBehavior::*)(QString, int, QString, QPrivateSignal)) &
pqSMTKCallObserversOnMainThreadBehavior::operationEvent,
this, [this](QString operationId, int event, QString resultName) {
auto id = smtk::common::UUID(operationId.toStdString());
auto op = m_activeOperations[id];
if (auto operation = op.lock())
{
smtk::attribute::AttributePtr att;
if (!resultName.isNull())
{
att = operation->specification()->findAttribute(resultName.toStdString());
}
operation->manager()->observers().callObserversDirectly(
operation, static_cast<smtk::operation::EventType>(event), att);
}
m_activeOperations.erase(id);
});
// Override the selection Observers' call method to emit a private signal
// instead of calling its Observer functors directly.
wrapper->smtkSelection()->observers().overrideWith(
[this](const std::string& str, smtk::view::Selection::Ptr selection) -> void {
auto id = smtk::common::UUID::random();
m_activeSelection[id] = selection;
emit selectionEvent(
QString::fromStdString(id.toString()), QString::fromStdString(str), QPrivateSignal());
});
// Connect to the above signal on the main thread and call the Observer
// functors.
QObject::connect(this,
(void (pqSMTKCallObserversOnMainThreadBehavior::*)(QString, QString, QPrivateSignal)) &
pqSMTKCallObserversOnMainThreadBehavior::selectionEvent,
this, [this](QString selectionId, QString qstr) {
auto id = smtk::common::UUID(selectionId.toStdString());
auto sel = m_activeSelection[id];
if (auto selection = sel.lock())
{
selection->observers().callObserversDirectly(qstr.toStdString(), selection);
}
m_activeSelection.erase(id);
});
}
//=========================================================================
// 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_pqSMTKCallObserversOnMainThreadBehavior_h
#define smtk_extension_paraview_appcomponents_pqSMTKCallObserversOnMainThreadBehavior_h
#include "smtk/extension/paraview/appcomponents/Exports.h"
#include "smtk/common/UUID.h"
#include "smtk/operation/Manager.h"
#include "smtk/resource/Manager.h"
#include "smtk/view/Selection.h"
#include <QObject>
class pqServer;
class pqSMTKWrapper;
/** \brief Add logic to new servers to ensure that operation and resource
* observers are called on the main thread.
*
* Qt requires that all methods that affect the GUI be performed on the
* application's main thread. Many of the registered Observer functions for
* both operations and resources are designed to affect the GUI. Rather than
* connect GUI-modifying logic to a signal triggered by an observer, we mutate
* the behavior of the operation and resource Observer calling logic to ensure
* that all Observer functors are called on the main thread, regardless of
* which thread performs the observation.
*
* The logic that allows us to ensure that observations are called on the main
* thread uses Qt's signals and slots to relay the observation request between
* threads. Because SMTK's Operation and Resource classes are not wrapped for
* Qt's signal/slot relay, we instead hold a map of calling Operations and
* Resources and pass an associated unique id between threads.
*/
class SMTKPQCOMPONENTSEXT_EXPORT pqSMTKCallObserversOnMainThreadBehavior : public QObject
{
Q_OBJECT
using Superclass = QObject;
public:
static pqSMTKCallObserversOnMainThreadBehavior* instance(QObject* parent = nullptr);
~pqSMTKCallObserversOnMainThreadBehavior() override;
signals:
/**\brief Signal that a resource \a rsrc has been added or removed from the
* manager.
*/
void resourceEvent(QString resourceId, int event, QPrivateSignal);