Kaydet (Commit) 2d46ef6d authored tarafından Matúš Kukan's avatar Matúš Kukan

fwk: Constructor feature for single-instance framework::Desktop.

Change-Id: Id582e672c0b41a35d25361f7dfe970a767880b2a
üst ce02519c
......@@ -20,16 +20,11 @@
#ifndef INCLUDED_FRAMEWORK_INC_SERVICES_DESKTOP_HXX
#define INCLUDED_FRAMEWORK_INC_SERVICES_DESKTOP_HXX
#include <sal/config.h>
#include <classes/framecontainer.hxx>
#include <threadhelp/threadhelpbase.hxx>
#include <helper/oframes.hxx>
#include <macros/generic.hxx>
#include <macros/xinterface.hxx>
#include <macros/xtypeprovider.hxx>
#include <macros/xserviceinfo.hxx>
#include <com/sun/star/frame/XUntitledNumbers.hpp>
#include <com/sun/star/frame/XController.hpp>
#include <com/sun/star/frame/XDesktop2.hpp>
#include <com/sun/star/frame/XTerminateListener.hpp>
......@@ -38,7 +33,7 @@
#include <com/sun/star/frame/XModel.hpp>
#include <com/sun/star/frame/XFramesSupplier.hpp>
#include <com/sun/star/frame/XFrames.hpp>
#include <com/sun/star/lang/XServiceName.hpp>
#include <com/sun/star/lang/XServiceInfo.hpp>
#include <com/sun/star/frame/XDispatchProvider.hpp>
#include <com/sun/star/frame/XDispatchProviderInterception.hpp>
#include <com/sun/star/frame/XComponentLoader.hpp>
......@@ -53,11 +48,10 @@
#include <com/sun/star/frame/XDispatchRecorderSupplier.hpp>
#include <com/sun/star/uno/XComponentContext.hpp>
#include <unotools/cmdoptions.hxx>
#include <cppuhelper/propshlp.hxx>
#include <cppuhelper/implbase6.hxx>
#include <comphelper/numberedcollection.hxx>
#include <cppuhelper/compbase6.hxx>
#include <cppuhelper/propshlp.hxx>
#include <unotools/cmdoptions.hxx>
namespace framework{
......@@ -91,15 +85,10 @@ enum ELoadState
XEventListener
XInteractionHandler
@base ThreadHelpBase
TransactionBase
OBroadcastHelper
OPropertySetHelper
@devstatus ready to use
@threadsafe yes
*//*-*************************************************************************************************************/
typedef ::cppu::WeakImplHelper6<
typedef cppu::WeakComponentImplHelper6<
css::lang::XServiceInfo ,
css::frame::XDesktop2 ,
css::frame::XTasksSupplier ,
......@@ -107,14 +96,11 @@ typedef ::cppu::WeakImplHelper6<
css::task::XInteractionHandler ,
css::frame::XUntitledNumbers > Desktop_BASE;
class Desktop : // base classes
// Order is necessary for right initialization!
private ThreadHelpBase ,
private TransactionBase ,
public ::cppu::OBroadcastHelper ,
public ::cppu::OPropertySetHelper ,
// interfaces
public Desktop_BASE
class Desktop : private osl::Mutex,
private ThreadHelpBase,
private TransactionBase,
public Desktop_BASE,
public cppu::OPropertySetHelper
{
// internal used types, const etcpp.
private:
......@@ -130,8 +116,17 @@ class Desktop : // base classes
Desktop( const css::uno::Reference< css::uno::XComponentContext >& xContext );
virtual ~Desktop( );
void constructorInit();
// XServiceInfo
DECLARE_XSERVICEINFO
virtual OUString SAL_CALL getImplementationName()
throw (css::uno::RuntimeException);
virtual sal_Bool SAL_CALL supportsService(OUString const & ServiceName)
throw (css::uno::RuntimeException);
virtual css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames()
throw (css::uno::RuntimeException);
// XInterface
virtual void SAL_CALL acquire() throw ()
......@@ -274,8 +269,7 @@ class Desktop : // base classes
virtual void SAL_CALL removeFrameActionListener ( const css::uno::Reference< css::frame::XFrameActionListener >& xListener ) throw( css::uno::RuntimeException );
// XComponent
using cppu::OPropertySetHelper::disposing;
virtual void SAL_CALL dispose ( ) throw( css::uno::RuntimeException );
virtual void SAL_CALL disposing() throw( css::uno::RuntimeException );
virtual void SAL_CALL addEventListener ( const css::uno::Reference< css::lang::XEventListener >& xListener ) throw( css::uno::RuntimeException );
virtual void SAL_CALL removeEventListener ( const css::uno::Reference< css::lang::XEventListener >& xListener ) throw( css::uno::RuntimeException );
......@@ -407,7 +401,6 @@ class Desktop : // base classes
//-------------------------------------------------------------------------------------------------------------
private:
static sal_Bool implcp_ctor ( const css::uno::Reference< css::uno::XComponentContext >& xFactory );
static sal_Bool implcp_addEventListener ( const css::uno::Reference< css::lang::XEventListener >& xListener );
static sal_Bool implcp_removeEventListener ( const css::uno::Reference< css::lang::XEventListener >& xListener );
......
......@@ -33,10 +33,8 @@
IFFACTORIE( Service2 )
)
=================================================================================================================*/
#include <services/desktop.hxx>
COMPONENTGETFACTORY ( fwk,
IFFACTORY( ::framework::Desktop )
)
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
......@@ -17,12 +17,13 @@
* the License at http://www.apache.org/licenses/LICENSE-2.0 .
*/
#include <loadenv/loadenv.hxx>
#include <services/desktop.hxx>
#include <loadenv/loadenv.hxx>
#include <loadenv/targethelper.hxx>
#include <services/desktop.hxx>
#include <helper/ocomponentaccess.hxx>
#include <helper/oframes.hxx>
#include <dispatch/dispatchprovider.hxx>
#include <dispatch/interceptionhelper.hxx>
......@@ -30,7 +31,6 @@
#include <threadhelp/transactionguard.hxx>
#include <threadhelp/writeguard.hxx>
#include <threadhelp/readguard.hxx>
#include <services.h>
#include <general.h>
#include <properties.h>
......@@ -61,80 +61,77 @@
#include <com/sun/star/document/UpdateDocMode.hpp>
#include <com/sun/star/frame/XTerminateListener2.hpp>
#include <cppuhelper/queryinterface.hxx>
#include <cppuhelper/typeprovider.hxx>
#include <cppuhelper/factory.hxx>
#include <cppuhelper/proptypehlp.hxx>
#include <rtl/ustrbuf.hxx>
#include <comphelper/sequence.hxx>
#include <cppuhelper/supportsservice.hxx>
#include <vcl/svapp.hxx>
#include <tools/errinf.hxx>
#include <comphelper/extract.hxx>
#include <fwkdllapi.h>
namespace framework{
//*****************************************************************************************************************
// XInterface, XTypeProvider, XServiceInfo
//*****************************************************************************************************************
OUString SAL_CALL Desktop::getImplementationName()
throw (css::uno::RuntimeException)
{
return OUString("com.sun.star.comp.framework.Desktop");
}
DEFINE_XSERVICEINFO_ONEINSTANCESERVICE_2( Desktop,
::cppu::OWeakObject,
"com.sun.star.frame.Desktop",
IMPLEMENTATIONNAME_DESKTOP
)
DEFINE_INIT_SERVICE ( Desktop,
{
/*Attention
I think we don't need any mutex or lock here ... because we are called by our own static method impl_createInstance()
to create a new instance of this class by our own supported service factory.
see macro DEFINE_XSERVICEINFO_MULTISERVICE and "impl_initService()" for further information!
*/
//-------------------------------------------------------------------------------------------------------------
// Initialize a new XFrames-helper-object to handle XIndexAccess and XElementAccess.
// We hold member as reference ... not as pointer too!
// Attention: We share our frame container with this helper. Container is threadsafe himself ... So I think we can do that.
// But look on dispose() for right order of deinitialization.
OFrames* pFramesHelper = new OFrames( this, &m_aChildTaskContainer );
m_xFramesHelper = css::uno::Reference< css::frame::XFrames >( static_cast< ::cppu::OWeakObject* >(pFramesHelper), css::uno::UNO_QUERY );
//-------------------------------------------------------------------------------------------------------------
// Initialize a new dispatchhelper-object to handle dispatches.
// We use these helper as slave for our interceptor helper ... not directly!
// But he is event listener on THIS instance!
DispatchProvider* pDispatchHelper = new DispatchProvider( m_xContext, this );
css::uno::Reference< css::frame::XDispatchProvider > xDispatchProvider( static_cast< ::cppu::OWeakObject* >(pDispatchHelper), css::uno::UNO_QUERY );
//-------------------------------------------------------------------------------------------------------------
// Initialize a new interception helper object to handle dispatches and implement an interceptor mechanism.
// Set created dispatch provider as slowest slave of it.
// Hold interception helper by reference only - not by pointer!
// So it's easiear to destroy it.
InterceptionHelper* pInterceptionHelper = new InterceptionHelper( this, xDispatchProvider );
m_xDispatchHelper = css::uno::Reference< css::frame::XDispatchProvider >( static_cast< ::cppu::OWeakObject* >(pInterceptionHelper), css::uno::UNO_QUERY );
OUStringBuffer sUntitledPrefix (256);
sUntitledPrefix.append (FWK_RESSTR(STR_UNTITLED_DOCUMENT));
sUntitledPrefix.appendAscii (" ");
::comphelper::NumberedCollection* pNumbers = new ::comphelper::NumberedCollection ();
m_xTitleNumberGenerator = css::uno::Reference< css::frame::XUntitledNumbers >(static_cast< ::cppu::OWeakObject* >(pNumbers), css::uno::UNO_QUERY_THROW);
pNumbers->setOwner ( static_cast< ::cppu::OWeakObject* >(this) );
pNumbers->setUntitledPrefix ( sUntitledPrefix.makeStringAndClear () );
// Safe impossible cases
// We can't work without this helper!
SAL_WARN_IF( !m_xFramesHelper.is(), "fwk", "Desktop::Desktop(): Frames helper is not valid. XFrames, XIndexAccess and XElementAcces are not supported!");
SAL_WARN_IF( !m_xDispatchHelper.is(), "fwk", "Desktop::Desktop(): Dispatch helper is not valid. XDispatch will not work correctly!" );
// Enable object for real working!
// Otherwise all calls will be rejected ...
m_aTransactionManager.setWorkingMode( E_WORK );
}
)
sal_Bool SAL_CALL Desktop::supportsService(OUString const & ServiceName)
throw (css::uno::RuntimeException)
{
return cppu::supportsService(this, ServiceName);
}
css::uno::Sequence<OUString> SAL_CALL Desktop::getSupportedServiceNames()
throw (css::uno::RuntimeException)
{
css::uno::Sequence< OUString > aSeq(1);
aSeq[0] = OUString("com.sun.star.frame.Desktop");
return aSeq;
}
void Desktop::constructorInit()
{
// Initialize a new XFrames-helper-object to handle XIndexAccess and XElementAccess.
// We hold member as reference ... not as pointer too!
// Attention: We share our frame container with this helper. Container is threadsafe himself ... So I think we can do that.
// But look on dispose() for right order of deinitialization.
OFrames* pFramesHelper = new OFrames( this, &m_aChildTaskContainer );
m_xFramesHelper = css::uno::Reference< css::frame::XFrames >( static_cast< ::cppu::OWeakObject* >(pFramesHelper), css::uno::UNO_QUERY );
//-------------------------------------------------------------------------------------------------------------
// Initialize a new dispatchhelper-object to handle dispatches.
// We use these helper as slave for our interceptor helper ... not directly!
// But he is event listener on THIS instance!
DispatchProvider* pDispatchHelper = new DispatchProvider( m_xContext, this );
css::uno::Reference< css::frame::XDispatchProvider > xDispatchProvider( static_cast< ::cppu::OWeakObject* >(pDispatchHelper), css::uno::UNO_QUERY );
//-------------------------------------------------------------------------------------------------------------
// Initialize a new interception helper object to handle dispatches and implement an interceptor mechanism.
// Set created dispatch provider as slowest slave of it.
// Hold interception helper by reference only - not by pointer!
// So it's easiear to destroy it.
InterceptionHelper* pInterceptionHelper = new InterceptionHelper( this, xDispatchProvider );
m_xDispatchHelper = css::uno::Reference< css::frame::XDispatchProvider >( static_cast< ::cppu::OWeakObject* >(pInterceptionHelper), css::uno::UNO_QUERY );
OUStringBuffer sUntitledPrefix (256);
sUntitledPrefix.append (FWK_RESSTR(STR_UNTITLED_DOCUMENT));
sUntitledPrefix.appendAscii (" ");
::comphelper::NumberedCollection* pNumbers = new ::comphelper::NumberedCollection ();
m_xTitleNumberGenerator = css::uno::Reference< css::frame::XUntitledNumbers >(static_cast< ::cppu::OWeakObject* >(pNumbers), css::uno::UNO_QUERY_THROW);
pNumbers->setOwner ( static_cast< ::cppu::OWeakObject* >(this) );
pNumbers->setUntitledPrefix ( sUntitledPrefix.makeStringAndClear () );
// Safe impossible cases
// We can't work without this helper!
SAL_WARN_IF( !m_xFramesHelper.is(), "fwk", "Desktop::Desktop(): Frames helper is not valid. XFrames, XIndexAccess and XElementAcces are not supported!");
SAL_WARN_IF( !m_xDispatchHelper.is(), "fwk", "Desktop::Desktop(): Dispatch helper is not valid. XDispatch will not work correctly!" );
// Enable object for real working!
// Otherwise all calls will be rejected ...
m_aTransactionManager.setWorkingMode( E_WORK );
}
/*-************************************************************************************************************//**
@short standard constructor to create instance by factory
......@@ -158,14 +155,10 @@ DEFINE_INIT_SERVICE ( Desktop,
@onerror We throw an ASSERT in debug version or do nothing in relaese version.
*//*-*************************************************************************************************************/
Desktop::Desktop( const css::uno::Reference< css::uno::XComponentContext >& xContext )
// Init baseclasses first
// Attention: Don't change order of initialization!
// ThreadHelpBase is a struct with a lock as member. We can't use a lock as direct member!
// We must guarantee right initialization and a valid value of this to initialize other baseclasses!
: ThreadHelpBase ( &Application::GetSolarMutex() )
, TransactionBase ( )
, ::cppu::OBroadcastHelperVar< ::cppu::OMultiTypeInterfaceContainerHelper, ::cppu::OMultiTypeInterfaceContainerHelper::keyType > ( m_aLock.getShareableOslMutex() )
, ::cppu::OPropertySetHelper ( *(static_cast< ::cppu::OBroadcastHelper* >(this)) )
, Desktop_BASE ( *static_cast<osl::Mutex *>(this) )
, cppu::OPropertySetHelper( cppu::WeakComponentImplHelperBase::rBHelper )
// Init member
, m_bIsTerminated ( sal_False ) // see dispose() for further information!
, m_xContext ( xContext )
......@@ -187,9 +180,6 @@ Desktop::Desktop( const css::uno::Reference< css::uno::XComponentContext >& xCon
, m_xSfxTerminator ( )
, m_xTitleNumberGenerator ( )
{
// Safe impossible cases
// We don't accept all incoming parameter.
SAL_WARN_IF( implcp_ctor( xContext ), "fwk", "Desktop::Desktop(): Invalid parameter detected!" );
}
/*-************************************************************************************************************//**
......@@ -401,22 +391,22 @@ void SAL_CALL Desktop::addTerminateListener( const css::uno::Reference< css::fra
// SYCNHRONIZED ->
WriteGuard aWriteLock( m_aLock );
if( sImplementationName.equals(IMPLEMENTATIONNAME_SFXTERMINATOR) )
if( sImplementationName == "com.sun.star.comp.sfx2.SfxTerminateListener" )
{
m_xSfxTerminator = xListener;
return;
}
if( sImplementationName.equals(IMPLEMENTATIONNAME_PIPETERMINATOR) )
if( sImplementationName == "com.sun.star.comp.OfficeIPCThreadController" )
{
m_xPipeTerminator = xListener;
return;
}
if( sImplementationName.equals(IMPLEMENTATIONNAME_QUICKLAUNCHER) )
if( sImplementationName == "com.sun.star.comp.desktop.QuickstartWrapper" )
{
m_xQuickLauncher = xListener;
return;
}
if( sImplementationName.equals(IMPLEMENTATIONNAME_SWTHREADMANAGER) )
if( sImplementationName == "com.sun.star.util.comp.FinalThreadManager" )
{
m_xSWThreadManager = xListener;
return;
......@@ -444,25 +434,25 @@ void SAL_CALL Desktop::removeTerminateListener( const css::uno::Reference< css::
// SYCNHRONIZED ->
WriteGuard aWriteLock( m_aLock );
if( sImplementationName.equals(IMPLEMENTATIONNAME_SFXTERMINATOR) )
if( sImplementationName == "com.sun.star.comp.sfx2.SfxTerminateListener" )
{
m_xSfxTerminator.clear();
return;
}
if( sImplementationName.equals(IMPLEMENTATIONNAME_PIPETERMINATOR) )
if( sImplementationName == "com.sun.star.comp.OfficeIPCThreadController" )
{
m_xPipeTerminator.clear();
return;
}
if( sImplementationName.equals(IMPLEMENTATIONNAME_QUICKLAUNCHER) )
if( sImplementationName == "com.sun.star.comp.desktop.QuickstartWrapper" )
{
m_xQuickLauncher.clear();
return;
}
if( sImplementationName.equals(IMPLEMENTATIONNAME_SWTHREADMANAGER) )
if( sImplementationName == "com.sun.star.util.comp.FinalThreadManager" )
{
m_xSWThreadManager.clear();
return;
......@@ -607,13 +597,10 @@ css::uno::Reference< css::lang::XComponent > SAL_CALL Desktop::loadComponentFrom
TransactionGuard aTransaction( m_aTransactionManager, E_HARDEXCEPTIONS );
SAL_INFO( "fwk.desktop", "framework (as96863) ::Desktop::loadComponentFromURL" );
ReadGuard aReadLock(m_aLock);
css::uno::Reference< css::frame::XComponentLoader > xThis(static_cast< css::frame::XComponentLoader* >(this), css::uno::UNO_QUERY);
css::uno::Reference< css::uno::XComponentContext > xContext = m_xContext;
aReadLock.unlock();
SAL_INFO( "fwk.desktop", "PERFORMANCE - Desktop::loadComponentFromURL()" );
return LoadEnv::loadComponentFromURL(xThis, xContext, sURL, sTargetFrameName, nSearchFlags, lArguments);
return LoadEnv::loadComponentFromURL(xThis, m_xContext, sURL, sTargetFrameName, nSearchFlags, lArguments);
}
/*-************************************************************************************************************//**
......@@ -1001,13 +988,6 @@ css::uno::Reference< css::frame::XFrame > SAL_CALL Desktop::findFrame( const OUS
// force using of "if() else if() ..."
//-----------------------------------------------------------------------------------------------------
// get threadsafe some necessary member which are necessary for following functionality
/* SAFE { */
ReadGuard aReadLock( m_aLock );
css::uno::Reference< css::uno::XComponentContext> xContext = m_xContext;
aReadLock.unlock();
/* } SAFE */
//-----------------------------------------------------------------------------------------------------
// I.I) "_blank"
// create a new task as child of this desktop instance
......@@ -1015,7 +995,7 @@ css::uno::Reference< css::frame::XFrame > SAL_CALL Desktop::findFrame( const OUS
//-----------------------------------------------------------------------------------------------------
if ( sTargetFrameName==SPECIALTARGET_BLANK )
{
TaskCreator aCreator( xContext );
TaskCreator aCreator( m_xContext );
xTarget = aCreator.createTask(sTargetFrameName,sal_False);
}
......@@ -1051,13 +1031,6 @@ css::uno::Reference< css::frame::XFrame > SAL_CALL Desktop::findFrame( const OUS
// But note: Such flags are not valid for the desktop - especialy SIBLINGS or PARENT.
//-------------------------------------------------------------------------------------------------
// get threadsafe some necessary member which are necessary for following functionality
/* SAFE { */
aReadLock.lock();
OUString sOwnName = m_sName;
aReadLock.unlock();
/* } SAFE */
//-------------------------------------------------------------------------------------------------
// II.I) SELF
// Check for right name. If it's the searched one return ourself - otherwise
......@@ -1065,7 +1038,7 @@ css::uno::Reference< css::frame::XFrame > SAL_CALL Desktop::findFrame( const OUS
//-------------------------------------------------------------------------------------------------
if (
(nSearchFlags & css::frame::FrameSearchFlag::SELF) &&
(sOwnName == sTargetFrameName )
(m_sName == sTargetFrameName)
)
{
xTarget = this;
......@@ -1113,7 +1086,7 @@ css::uno::Reference< css::frame::XFrame > SAL_CALL Desktop::findFrame( const OUS
(nSearchFlags & css::frame::FrameSearchFlag::CREATE)
)
{
TaskCreator aCreator( xContext );
TaskCreator aCreator( m_xContext );
xTarget = aCreator.createTask(sTargetFrameName,sal_False);
}
}
......@@ -1121,8 +1094,7 @@ css::uno::Reference< css::frame::XFrame > SAL_CALL Desktop::findFrame( const OUS
return xTarget;
}
//=============================================================================
void SAL_CALL Desktop::dispose()
void SAL_CALL Desktop::disposing()
throw( css::uno::RuntimeException )
{
// Safe impossible cases
......@@ -1652,7 +1624,8 @@ css::uno::Reference< css::beans::XPropertySetInfo > SAL_CALL Desktop::getPropert
{
// Create structure of propertysetinfo for baseclass "OPropertySetHelper".
// (Use method "getInfoHelper()".)
static css::uno::Reference< css::beans::XPropertySetInfo > xInfo( createPropertySetInfo( getInfoHelper() ) );
static css::uno::Reference< css::beans::XPropertySetInfo > xInfo(
cppu::OPropertySetHelper::createPropertySetInfo( getInfoHelper() ) );
pInfo = &xInfo;
}
}
......@@ -1931,13 +1904,6 @@ void Desktop::impl_sendNotifyTerminationEvent()
return (nNonClosedFrames < 1);
}
//*****************************************************************************************************************
// We work with valid servicemanager only.
sal_Bool Desktop::implcp_ctor( const css::uno::Reference< css::uno::XComponentContext >& xContext )
{
return !xContext.is();
}
// We work with valid listener only.
sal_Bool Desktop::implcp_addEventListener( const css::uno::Reference< css::lang::XEventListener >& xListener )
{
......@@ -1952,4 +1918,34 @@ sal_Bool Desktop::implcp_removeEventListener( const css::uno::Reference< css::la
} // namespace framework
namespace {
struct Instance {
explicit Instance(
css::uno::Reference<css::uno::XComponentContext> const & context):
instance(static_cast<cppu::OWeakObject *>(new framework::Desktop(context)))
{
static_cast<framework::Desktop *>(static_cast<cppu::OWeakObject *>
(instance.get()))->constructorInit();
}
css::uno::Reference<css::uno::XInterface> instance;
};
struct Singleton:
public rtl::StaticWithArg<
Instance, css::uno::Reference<css::uno::XComponentContext>, Singleton>
{};
}
extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface * SAL_CALL
com_sun_star_comp_framework_Desktop_get_implementation(
css::uno::XComponentContext *context,
css::uno::Sequence<css::uno::Any> const &)
{
return cppu::acquire(static_cast<cppu::OWeakObject *>(
Singleton::get(context).instance.get()));
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
......@@ -36,7 +36,8 @@
constructor="com_sun_star_comp_framework_ControlMenuController_get_implementation">
<service name="com.sun.star.frame.PopupMenuController"/>
</implementation>
<implementation name="com.sun.star.comp.framework.Desktop">
<implementation name="com.sun.star.comp.framework.Desktop"
constructor="com_sun_star_comp_framework_Desktop_get_implementation">
<service name="com.sun.star.frame.Desktop"/>
<singleton name="com.sun.star.frame.theDesktop"/>
</implementation>
......
......@@ -48,6 +48,7 @@ core_factory_list = [
core_constructor_list = [
# framework/util/fwk.component
"com_sun_star_comp_framework_AutoRecovery_get_implementation",
"com_sun_star_comp_framework_Desktop_get_implementation",
"com_sun_star_comp_framework_Frame_get_implementation",
"com_sun_star_comp_framework_JobExecutor_get_implementation",
"com_sun_star_comp_framework_LayoutManager_get_implementation",
......
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