Commit bae5ba09 authored by T.J. Corona's avatar T.J. Corona Committed by Kitware Robot

Merge topic 'enable-plugins-immediately-on-load'

f864b523 RegisterImportersBehavior: add new importers to paraview's File->Open
9398822e NewResourceBehavior: update File->New() when operations are changed
d80f5749 PluginSupport: fix active manager registration
c4c5e643 Resource: add observer for metadata actions (add/remove)
85ddb3c1 Operation: add observers for group actions (add/remove from group)
5ad110ec Registrars: unregister operations from groups
Acked-by: Kitware Robot's avatarKitware Robot <kwrobot@kitware.com>
Acked-by: David Thompson's avatarDavid Thompson <david.thompson@kitware.com>
Merge-request: !1491
parents df83a36d f864b523
Pipeline #131738 running with stage
......@@ -44,6 +44,10 @@ void Registrar::registerTo(const smtk::operation::Manager::Ptr& operationManager
void Registrar::unregisterFrom(const smtk::operation::Manager::Ptr& operationManager)
{
smtk::operation::ReaderGroup(operationManager).unregisterOperation<smtk::attribute::Read>();
smtk::operation::WriterGroup(operationManager).unregisterOperation<smtk::attribute::Write>();
operationManager->unregisterOperations<OperationList>();
}
......
......@@ -168,6 +168,21 @@ pqSMTKNewResourceBehavior::pqSMTKNewResourceBehavior(QObject* parent)
pqActiveObjects* activeObjects = &pqActiveObjects::instance();
QObject::connect(
activeObjects, SIGNAL(serverChanged(pqServer*)), this, SLOT(updateNewMenu()));
pqServer* server = pqActiveObjects::instance().activeServer();
pqSMTKWrapper* wrapper = pqSMTKBehavior::instance()->resourceManagerForServer(server);
if (wrapper != nullptr)
{
wrapper->smtkOperationManager()->groupObservers().insert(
[this](const smtk::operation::Operation::Index&, const std::string& groupName, bool) {
if (groupName == smtk::operation::CreatorGroup::type_name)
{
this->updateNewMenu();
}
});
// Access the creator group.
auto creatorGroup = smtk::operation::CreatorGroup(wrapper->smtkOperationManager());
}
}
});
}
......
......@@ -21,6 +21,7 @@
#include "pqActiveObjects.h"
#include "pqApplicationCore.h"
#include "pqServerManagerModel.h"
#include "vtkPVXMLParser.h"
#include "vtkSMProxyDefinitionManager.h"
#include "vtkSMSessionProxyManager.h"
......@@ -83,14 +84,23 @@ void extensionsAndDescriptionsFromFileFilters(const std::string& fileFilters,
}
}
std::string proxyName(
const std::string& resource, const std::string&, const std::string& description)
{
std::size_t hash = std::hash<std::string>{}(resource + description);
std::stringstream s;
s << "SMTKModelImporter_" << hash;
return s.str();
}
std::string xmlForSMTKImporter(
const std::string& resource, const std::string& extensions, const std::string& description)
{
std::size_t hash = std::hash<std::string>{}(resource + description);
std::stringstream s;
s << "<ServerManagerConfiguration>\n";
s << " <ProxyGroup name=\"sources\">\n";
s << " <SourceProxy name=\"SMTKModelImporter_" << hash << " \" class=\"vtkSMTKSource\" ";
s << " <SourceProxy name=\"" << proxyName(resource, extensions, description)
<< "\" class=\"vtkSMTKSource\" ";
s << "label=\"SMTK importer for " << description << " into " << resource << "\">\n";
s << " <Documentation>\n";
s << " short_help=\"Import a " << description << " as an SMTK " << resource << ".\"\n";
......@@ -138,11 +148,36 @@ void registerSMTKImporter(
extensionsAndDescriptionsFromFileFilters(fileFilters, extensionsAndDescriptions);
for (auto& token : extensionsAndDescriptions)
{
server->proxyManager()->GetProxyDefinitionManager()->LoadConfigurationXMLFromString(
xmlForSMTKImporter(resource, token.first, token.second).c_str());
vtkNew<vtkPVXMLParser> parser;
if (parser->Parse(xmlForSMTKImporter(resource, token.first, token.second).c_str()) != 0)
{
server->proxyManager()->GetProxyDefinitionManager()->LoadConfigurationXML(
parser->GetRootElement());
}
}
}
}
// TODO: Once ParaView has the ability to unregister configurations for
// readers, the logic below can be used to ensure that removed operations are
// safely removed from ParaView's File->Open() interface.
#if 0
void unregisterSMTKImporter(
pqServer* server, const std::string& resource, const std::string& fileFilters)
{
auto app = pqApplicationCore::instance();
if (app)
{
std::vector<std::pair<std::string, std::string> > extensionsAndDescriptions;
extensionsAndDescriptionsFromFileFilters(fileFilters, extensionsAndDescriptions);
for (auto& token : extensionsAndDescriptions)
{
// TODO: ParaView's vtkSIProxyDefinitionManager needs the ability to
// remove a configuration.
}
}
}
#endif
}
static pqSMTKRegisterImportersBehavior* g_instance = nullptr;
......@@ -181,14 +216,14 @@ pqSMTKRegisterImportersBehavior::~pqSMTKRegisterImportersBehavior()
}
void pqSMTKRegisterImportersBehavior::constructModelImporters(
pqSMTKWrapper* rsrcMgr, pqServer* server)
pqSMTKWrapper* wrapper, pqServer* server)
{
if (!rsrcMgr)
if (!wrapper)
{
return;
}
auto importerGroup = smtk::operation::ImporterGroup(rsrcMgr->smtkOperationManager());
auto importerGroup = smtk::operation::ImporterGroup(wrapper->smtkOperationManager());
for (auto& resourceName : importerGroup.supportedResources())
{
for (auto index : importerGroup.operationsForResource(resourceName))
......@@ -197,4 +232,52 @@ void pqSMTKRegisterImportersBehavior::constructModelImporters(
registerSMTKImporter(server, resourceName, fileItemDef->getFileFilters());
}
}
std::weak_ptr<smtk::operation::Manager> opManager = wrapper->smtkOperationManager();
wrapper->smtkOperationManager()->groupObservers().insert([opManager, server](
const smtk::operation::Operation::Index& index, const std::string& groupName, bool adding) {
if (!adding)
{
return;
}
if (auto operationManager = opManager.lock())
{
if (groupName == smtk::operation::ImporterGroup::type_name)
{
auto importerGroup = smtk::operation::ImporterGroup(operationManager);
assert(importerGroup.has(index));
auto fileItemDef = importerGroup.fileItemDefinitionForOperation(index);
registerSMTKImporter(
server, importerGroup.resourceForOperation(index), fileItemDef->getFileFilters());
}
}
});
// TODO: Once ParaView has the ability to unregister configurations for
// readers, the logic below can be used to ensure that removed operations are
// safely removed from ParaView's File->Open() interface.
#if 0
wrapper->smtkOperationManager()->groupObservers().insert(
[opManager, server](const smtk::operation::Operation::Index& index,
const std::string& groupName, bool adding)
{
if (adding)
{
return;
}
if (auto operationManager = opManager.lock())
{
if (groupName == smtk::operation::ImporterGroup::type_name)
{
auto importerGroup = smtk::operation::ImporterGroup(operationManager);
assert(importerGroup.has(index));
auto fileItemDef = importerGroup.fileItemDefinitionForOperation(index);
unregisterSMTKImporter(server, importerGroup.resourceForOperation(index),
fileItemDef->getFileFilters());
}
}
});
#endif
}
......@@ -71,8 +71,7 @@ private:
{
bool operator()(const RegisterFunction& lhs, const RegisterFunction& rhs) const
{
return lhs.target<bool (*)(const std::weak_ptr<PluginClientBase>&)>() <
rhs.target<bool (*)(const std::weak_ptr<PluginClientBase>&)>();
return &lhs < &rhs;
}
};
......
......@@ -57,7 +57,7 @@ void PluginManager::setRegistryStatus(const std::shared_ptr<Manager>& manager, b
// ...then we also construct a functor for registering this manager to
// future plugins. It accepts as input the plugin client and returns true is
// the manager has not yet expired.
std::weak_ptr<Manager> weakMgr;
std::weak_ptr<Manager> weakMgr = manager;
auto registerToFuturePlugins = [=](const std::weak_ptr<PluginClientBase>& pluginClient) {
if (auto manager = weakMgr.lock())
{
......
......@@ -61,6 +61,14 @@ void Registrar::registerTo(const smtk::operation::Manager::Ptr& operationManager
void Registrar::unregisterFrom(const smtk::operation::Manager::Ptr& operationManager)
{
smtk::operation::ImporterGroup(operationManager).unregisterOperation<smtk::mesh::Import>();
smtk::operation::ExporterGroup(operationManager).unregisterOperation<smtk::mesh::Export>();
smtk::operation::ReaderGroup(operationManager).unregisterOperation<smtk::mesh::Read>();
smtk::operation::WriterGroup(operationManager).unregisterOperation<smtk::mesh::Write>();
operationManager->unregisterOperations<OperationList>();
}
......
......@@ -26,6 +26,7 @@ set(operationSrcs
set(operationHeaders
Launcher.h
Group.h
GroupObserver.h
Manager.h
Metadata.h
MetadataContainer.h
......
......@@ -64,7 +64,16 @@ bool Group::registerOperation(const std::string& typeName, std::set<std::string>
Operation::Specification spec = getSpecification<std::string>(typeName, m_manager);
// Add the group name to the operator specification's list of groups.
return spec ? addTag(spec, name(), values) : false;
bool added = spec ? addTag(spec, name(), values) : false;
if (added)
{
auto manager = m_manager.lock();
auto metadata = manager->metadata().get<NameTag>().find(typeName);
manager->groupObservers()(metadata->index(), m_name, true);
}
return added;
}
bool Group::registerOperation(const Operation::Index& index, std::set<std::string> values)
......@@ -73,7 +82,15 @@ bool Group::registerOperation(const Operation::Index& index, std::set<std::strin
Operation::Specification spec = getSpecification<Operation::Index>(index, m_manager);
// Add the group name to the operator specification's list of groups.
return spec ? addTag(spec, name(), values) : false;
bool added = spec ? addTag(spec, name(), values) : false;
if (added)
{
auto manager = m_manager.lock();
manager->groupObservers()(index, m_name, true);
}
return added;
}
bool Group::unregisterOperation(const std::string& typeName)
......@@ -82,7 +99,16 @@ bool Group::unregisterOperation(const std::string& typeName)
Operation::Specification spec = getSpecification<std::string>(typeName, m_manager);
// Remove the group name from the operator specification's list of groups.
return spec ? removeTag(spec, name()) : false;
bool removed = spec ? removeTag(spec, name()) : false;
if (removed)
{
auto manager = m_manager.lock();
auto metadata = manager->metadata().get<NameTag>().find(typeName);
manager->groupObservers()(metadata->index(), m_name, false);
}
return removed;
}
bool Group::unregisterOperation(const Operation::Index& index)
......@@ -91,7 +117,15 @@ bool Group::unregisterOperation(const Operation::Index& index)
Operation::Specification spec = getSpecification<Operation::Index>(index, m_manager);
// Remove the group name from the operator specification's list of groups.
return spec ? removeTag(spec, name()) : false;
bool removed = spec ? removeTag(spec, name()) : false;
if (removed)
{
auto manager = m_manager.lock();
manager->groupObservers()(index, m_name, false);
}
return removed;
}
Operation::Specification Group::specification(const std::string& typeName) const
......
......@@ -13,6 +13,7 @@
#include "smtk/CoreExports.h"
#include "smtk/operation/GroupObserver.h"
#include "smtk/operation/Operation.h"
#include <set>
......@@ -48,6 +49,9 @@ class Manager;
class SMTKCORE_EXPORT Group
{
public:
typedef GroupObserver Observer;
typedef GroupObservers Observers;
Group(const std::string& name, std::shared_ptr<smtk::operation::Manager> manager)
: m_manager(manager)
, m_name(name)
......
//=========================================================================
// 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_operation_GroupObserver_h
#define __smtk_operation_GroupObserver_h
#include "smtk/CoreExports.h"
#include "smtk/common/Observers.h"
#include "smtk/operation/Operation.h"
namespace smtk
{
namespace operation
{
class Group;
typedef std::function<void(const Operation::Index&, const std::string&, bool)> GroupObserver;
typedef smtk::common::Observers<GroupObserver> GroupObservers;
}
}
#endif // __smtk_operation_GroupObserver_h
......@@ -113,6 +113,10 @@ public:
Observers& observers() { return m_observers; }
const Observers& observers() const { return m_observers; }
/// Return the group observers associated with this manager.
Group::Observers& groupObservers() { return m_groupObservers; }
const Group::Observers& groupObservers() const { return m_groupObservers; }
/// Return the metadata observers associated with this manager.
Metadata::Observers& metadataObservers() { return m_metadataObservers; }
const Metadata::Observers& metadataObservers() const { return m_metadataObservers; }
......@@ -190,6 +194,9 @@ private:
/// A container for all operation observers.
Observers m_observers;
/// A container for all operation group observers.
Group::Observers m_groupObservers;
/// A container for all operation metadata observers.
Metadata::Observers m_metadataObservers;
......
......@@ -42,6 +42,7 @@ public:
using Group::operationNames;
using Group::operationName;
using Group::operationLabel;
using Group::unregisterOperation;
static constexpr const char* const type_name = "creator";
......
......@@ -40,6 +40,7 @@ public:
using Group::has;
using Group::operations;
using Group::operationNames;
using Group::unregisterOperation;
ResourceIOGroup(const std::string& name, std::shared_ptr<smtk::operation::Manager> manager)
: Group(name, manager)
......@@ -142,8 +143,8 @@ bool ResourceIOGroup::registerOperation(
return false;
}
}
return (Group::registerOperation(typeName, { smtk::common::typeName<ResourceType>() }) &&
m_fileItemName.registerOperation(typeName, { fileItemName }));
return (m_fileItemName.registerOperation(typeName, { fileItemName }) &&
Group::registerOperation(typeName, { smtk::common::typeName<ResourceType>() }));
}
template <typename ResourceType>
......@@ -179,8 +180,8 @@ bool ResourceIOGroup::registerOperation(
return false;
}
}
return (Group::registerOperation(index, { smtk::common::typeName<ResourceType>() }) &&
m_fileItemName.registerOperation(index, { fileItemName }));
return (m_fileItemName.registerOperation(index, { fileItemName }) &&
Group::registerOperation(index, { smtk::common::typeName<ResourceType>() }));
}
template <typename ResourceType, typename OperationType>
......@@ -216,10 +217,10 @@ bool ResourceIOGroup::registerOperation(const std::string& fileItemName)
return false;
}
}
return (Group::registerOperation(std::type_index(typeid(OperationType)).hash_code(),
{ smtk::common::typeName<ResourceType>() }) &&
m_fileItemName.registerOperation(
std::type_index(typeid(OperationType)).hash_code(), { fileItemName }));
return (m_fileItemName.registerOperation(
std::type_index(typeid(OperationType)).hash_code(), { fileItemName }) &&
Group::registerOperation(std::type_index(typeid(OperationType)).hash_code(),
{ smtk::common::typeName<ResourceType>() }));
}
template <typename OperationType>
......
......@@ -24,6 +24,7 @@ set(resourceHeaders
Manager.h
Metadata.h
MetadataContainer.h
MetadataObserver.h
Observer.h
PersistentObject.h
PropertyType.h
......
......@@ -52,6 +52,7 @@ bool Manager::unregisterResource(const std::string& typeName)
if (metadata != m_metadata.get<NameTag>().end())
{
m_metadata.get<NameTag>().erase(metadata);
m_metadataObservers(*metadata, false);
return true;
}
......@@ -65,6 +66,7 @@ bool Manager::unregisterResource(const Resource::Index& index)
if (metadata != m_metadata.get<IndexTag>().end())
{
m_metadata.get<IndexTag>().erase(metadata);
m_metadataObservers(*metadata, false);
return true;
}
......@@ -148,9 +150,12 @@ bool Manager::registerResource(Metadata&& metadata)
auto alreadyRegisteredMetadata = m_metadata.get<IndexTag>().find(metadata.index());
if (alreadyRegisteredMetadata == m_metadata.get<IndexTag>().end())
{
auto size = m_metadata.get<IndexTag>().size();
m_metadata.get<IndexTag>().insert(metadata);
return m_metadata.get<IndexTag>().size() > size;
auto inserted = m_metadata.get<IndexTag>().insert(metadata);
if (inserted.second)
{
m_metadataObservers(*inserted.first, true);
return true;
}
}
return false;
......
......@@ -179,6 +179,10 @@ public:
Observers& observers() { return m_observers; }
const Observers& observers() const { return m_observers; }
/// Return the metadata observers associated with this manager.
Metadata::Observers& metadataObservers() { return m_metadataObservers; }
const Metadata::Observers& metadataObservers() const { return m_metadataObservers; }
private:
Manager();
......@@ -195,6 +199,9 @@ private:
/// A container for all resource observers.
Observers m_observers;
/// A container for all resource metadata observers.
Metadata::Observers m_metadataObservers;
/// A map connecting legacy resource names to legacy readers.
std::map<std::string, std::function<ResourcePtr(const std::string&)> > m_legacyReaders;
};
......
......@@ -14,6 +14,7 @@
#include "smtk/CoreExports.h"
#include "smtk/PublicPointerDefs.h"
#include "smtk/common/UUID.h"
#include "smtk/resource/MetadataObserver.h"
#include "smtk/resource/Resource.h"
#include <functional>
......@@ -38,6 +39,8 @@ class Metadata
public:
typedef std::function<void(const Metadata&)> Visitor;
typedef MetadataObserver Observer;
typedef MetadataObservers Observers;
Metadata(const std::string& typeName, Resource::Index index,
std::set<Resource::Index> parentIndices,
......
//=========================================================================
// 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_resource_MetadataObserver_h
#define __smtk_resource_MetadataObserver_h
#include "smtk/CoreExports.h"
#include "smtk/common/Observers.h"
namespace smtk
{
namespace resource
{
class Metadata;
typedef std::function<void(const Metadata&, bool)> MetadataObserver;
typedef smtk::common::Observers<MetadataObserver> MetadataObservers;
}
}
#ifndef smtkCore_EXPORTS
extern
#endif
template class SMTKCORE_EXPORT std::function<void(const smtk::resource::Metadata&)>;
#endif // __smtk_resource_MetadataObserver_h
......@@ -84,6 +84,18 @@ void Registrar::unregisterFrom(const smtk::resource::Manager::Ptr& resourceManag
void Registrar::unregisterFrom(const smtk::operation::Manager::Ptr& operationManager)
{
smtk::operation::ImporterGroup(operationManager)
.unregisterOperation<smtk::session::discrete::ImportOperation>();
smtk::operation::ImporterGroup(operationManager)
.unregisterOperation<smtk::session::discrete::ReadOperation>();
smtk::operation::ReaderGroup(operationManager)
.unregisterOperation<smtk::session::discrete::ReadResource>();
smtk::operation::ReaderGroup(operationManager).unregisterOperation<LegacyReadResource>();
smtk::operation::WriterGroup(operationManager)
.unregisterOperation<smtk::session::discrete::WriteResource>();
operationManager->unregisterOperations<OperationList>();
}
}
......
......@@ -69,6 +69,16 @@ void Registrar::unregisterFrom(const smtk::resource::Manager::Ptr& resourceManag
void Registrar::unregisterFrom(const smtk::operation::Manager::Ptr& operationManager)
{
smtk::operation::CreatorGroup(operationManager)
.unregisterOperation<smtk::session::mesh::CreateUniformGrid>();
smtk::operation::ImporterGroup(operationManager)
.unregisterOperation<smtk::session::mesh::Import>();
smtk::operation::ReaderGroup(operationManager).unregisterOperation<smtk::session::mesh::Read>();
smtk::operation::WriterGroup(operationManager).unregisterOperation<smtk::session::mesh::Write>();
operationManager->unregisterOperations<OperationList>();
}
}
......
......@@ -72,6 +72,16 @@ void Registrar::unregisterFrom(const smtk::resource::Manager::Ptr& resourceManag
void Registrar::unregisterFrom(const smtk::operation::Manager::Ptr& operationManager)
{
smtk::operation::CreatorGroup(operationManager)
.unregisterOperation("smtk.session.multiscale.import_from_deform.import_from_deform");
smtk::operation::ImporterGroup(operationManager)
.unregisterOperation<smtk::session::mesh::Import>();
smtk::operation::ReaderGroup(operationManager).unregisterOperation<smtk::session::mesh::Read>();
smtk::operation::WriterGroup(operationManager).unregisterOperation<smtk::session::mesh::Write>();
operationManager->unregisterOperations<OperationList>();
operationManager->unregisterOperation(
"smtk.session.multiscale.import_from_deform.import_from_deform");
......
......@@ -79,6 +79,15 @@ void Registrar::unregisterFrom(const smtk::resource::Manager::Ptr& resourceManag
void Registrar::unregisterFrom(const smtk::operation::Manager::Ptr& operationManager)
{
smtk::operation::CreatorGroup(operationManager)
.unregisterOperation<smtk::session::oscillator::EditDomain>();
smtk::operation::ReaderGroup(operationManager)
.unregisterOperation<smtk::session::oscillator::Read>();
smtk::operation::WriterGroup(operationManager)
.unregisterOperation<smtk::session::oscillator::Write>();
operationManager->unregisterOperations<OperationList>();
}
}
......
......@@ -93,6 +93,22 @@ void Registrar::unregisterFrom(const smtk::resource::Manager::Ptr& resourceManag
void Registrar::unregisterFrom(const smtk::operation::Manager::Ptr& operationManager)
{
smtk::operation::CreatorGroup(operationManager)
.unregisterOperation<smtk::session::polygon::CreateModel>();
#ifdef VTK_SUPPORT
smtk::operation::ImporterGroup(operationManager)
.unregisterOperation<smtk::session::polygon::Import>();
#endif
smtk::operation::ReaderGroup(operationManager)
.unregisterOperation<smtk::session::polygon::Read>();
smtk::operation::ReaderGroup(operationManager)
.unregisterOperation<smtk::session::polygon::LegacyRead>();
smtk::operation::WriterGroup(operationManager)
.unregisterOperation<smtk::session::polygon::Write>();
operationManager->unregisterOperations<OperationList>();
}
}
......
......@@ -88,6 +88,24 @@ void Registrar::unregisterFrom(const smtk::resource::Manager::Ptr& resourceManag
void Registrar::unregisterFrom(const smtk::operation::Manager::Ptr& operationManager)
{
#ifdef ENABLE_PYARC_BINDINGS
smtk::operation::ExporterGroup(operationManager)
.unregisterOperation("smtk.session.rgg.export_to_pyarc.export_to_pyarc");
#endif
smtk::operation::CreatorGroup(operationManager)
.unregisterOperation<smtk::session::rgg::CreateModel>();
// smtk::operation::ImporterGroup(operationManager)
// .unregisterOperation<smtk::session::rgg::ReadRXFFile>();
// smtk::operation::ReaderGroup(operationManager)
// .unregisterOperation<smtk::session::rgg::Read>();
// smtk::operation::WriterGroup(operationManager)
// .unregisterOperation<smtk::session::rgg::Write>();
operationManager->unregisterOperations<OperationList>();
}
}
......
......@@ -68,6 +68,19 @@ void Registrar::unregisterFrom(const smtk::resource::Manager::Ptr& resourceManag
void Registrar::unregisterFrom(const smtk::operation::Manager::Ptr& operationManager)
{
smtk::operation::ReaderGroup(operationManager).unregisterOperation<smtk::session::vtk::Read>();
smtk::operation::ReaderGroup(operationManager)
.unregisterOperation<smtk::session::vtk::LegacyRead>();
smtk::operation::WriterGroup(operationManager).unregisterOperation<smtk::session::vtk::Write>();
smtk::operation::ExporterGroup(operationManager)
.unregisterOperation<smtk::session::vtk::Export>();
smtk::operation::ImporterGroup(operationManager)
.unregisterOperation<smtk::session::vtk::Import>();
operationManager->unregisterOperations<OperationList>();
}
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment