From 02b508ed4df96a57dc32f557df1608eda0756b19 Mon Sep 17 00:00:00 2001 From: Maxim Monastirsky Date: Wed, 1 May 2019 12:25:54 +0300 Subject: [PATCH] tdf#125040 POC Change-Id: I353c4d2eb451a1690d99ce4f2e3bdeeea1bf7595 --- .../inc/uielement/addonstoolbarmanager.hxx | 1 - framework/inc/uielement/toolbarmanager.hxx | 6 +- framework/inc/uielement/toolbarmerger.hxx | 1 + framework/inc/uielement/toolbarwrapper.hxx | 17 ++ framework/source/uielement/toolbarmanager.cxx | 98 +++++----- framework/source/uielement/toolbarmerger.cxx | 5 +- framework/source/uielement/toolbarwrapper.cxx | 175 +++++++++++++++++- sfx2/source/appl/workwin.cxx | 3 +- sw/uiconfig/swriter/toolbar/singlemode.xml | 58 +----- 9 files changed, 253 insertions(+), 111 deletions(-) diff --git a/framework/inc/uielement/addonstoolbarmanager.hxx b/framework/inc/uielement/addonstoolbarmanager.hxx index 8842ad608c79..19f923ac3f15 100644 --- a/framework/inc/uielement/addonstoolbarmanager.hxx +++ b/framework/inc/uielement/addonstoolbarmanager.hxx @@ -49,7 +49,6 @@ class AddonsToolBarManager final : public ToolBarManager void SAL_CALL dispose() override; virtual void RefreshImages() override; - using ToolBarManager::FillToolbar; void FillToolbar( const css::uno::Sequence< css::uno::Sequence< css::beans::PropertyValue > >& rAddonToolbar ); private: diff --git a/framework/inc/uielement/toolbarmanager.hxx b/framework/inc/uielement/toolbarmanager.hxx index 7219e8770899..a40c9ebfb8b6 100644 --- a/framework/inc/uielement/toolbarmanager.hxx +++ b/framework/inc/uielement/toolbarmanager.hxx @@ -91,7 +91,9 @@ class ToolBarManager : public ToolbarManager_Base void CheckAndUpdateImages(); virtual void RefreshImages(); - void FillToolbar( const css::uno::Reference< css::container::XIndexAccess >& rToolBarData ); + void FillToolbar( const css::uno::Reference< css::container::XIndexAccess >& rToolBarData, + const css::uno::Reference< css::container::XIndexAccess >& rSecondaryData, + const OUString& rSecondaryResourceName ); void FillOverflowToolbar( ToolBox const * pParent ); void notifyRegisteredControllers( const OUString& aUIElementName, const OUString& aCommand ); void Destroy(); @@ -145,6 +147,8 @@ class ToolBarManager : public ToolbarManager_Base void HandleClick(void ( SAL_CALL css::frame::XToolbarController::*_pClick )( )); void setToolBarImage(const Image& _aImage,const CommandToInfoMap::const_iterator& _pIter); void impl_elementChanged(bool _bRemove,const css::ui::ConfigurationEvent& Event ); + void impl_fillToolbarFromContainer( const css::uno::Reference< css::container::XIndexAccess >& rItemContainer, + const OUString& rResourceName, sal_uInt16& nId, sal_uInt16& nAddonId ); protected: typedef std::unordered_map< sal_uInt16, css::uno::Reference< css::frame::XStatusListener > > ToolBarControllerMap; diff --git a/framework/inc/uielement/toolbarmerger.hxx b/framework/inc/uielement/toolbarmerger.hxx index 24017793bede..44e2085b5e0a 100644 --- a/framework/inc/uielement/toolbarmerger.hxx +++ b/framework/inc/uielement/toolbarmerger.hxx @@ -83,6 +83,7 @@ class ToolBarMerger sal_uInt16& rWidth ); static ReferenceToolbarPathInfo FindReferencePoint( ToolBox* pToolbar, + ToolBox::ImplToolItems::size_type nFirstItem, const OUString& rReferencePoint ); static bool ProcessMergeOperation( ToolBox* pToolbar, diff --git a/framework/inc/uielement/toolbarwrapper.hxx b/framework/inc/uielement/toolbarwrapper.hxx index 0e8da868a5a4..c5939923142d 100644 --- a/framework/inc/uielement/toolbarwrapper.hxx +++ b/framework/inc/uielement/toolbarwrapper.hxx @@ -24,6 +24,8 @@ #include #include +#include +#include #include #include @@ -32,6 +34,7 @@ namespace framework class ToolBarManager; class ToolBarWrapper : public css::ui::XUIFunctionListener, + public css::ui::XContextChangeEventListener, public UIConfigElementWrapperBase { public: @@ -61,6 +64,14 @@ class ToolBarWrapper : public css::ui::XUIFunctionListener, // XUIFunctionListener virtual void SAL_CALL functionExecute( const OUString& aUIElementName, const OUString& aCommand ) override; + // XContextChangeEventListener + virtual void SAL_CALL notifyContextChangeEvent( const css::ui::ContextChangeEventObject& aEvent ) override; + + // XUIConfigurationListener + virtual void SAL_CALL elementInserted( const css::ui::ConfigurationEvent& aEvent ) override; + virtual void SAL_CALL elementRemoved( const css::ui::ConfigurationEvent& aEvent ) override; + virtual void SAL_CALL elementReplaced( const css::ui::ConfigurationEvent& aEvent ) override; + // XEventListener using cppu::OPropertySetHelper::disposing; virtual void SAL_CALL disposing( const css::lang::EventObject& aEvent ) override; @@ -70,8 +81,14 @@ class ToolBarWrapper : public css::ui::XUIFunctionListener, protected: virtual void SAL_CALL setFastPropertyValue_NoBroadcast( sal_Int32 nHandle, const css::uno::Any& aValue ) override; virtual void impl_fillNewData() override; + void impl_updateSecondaryData(); private: + OUString m_aLastContext; + OUString m_aSecondaryToolbar; + css::uno::Reference< css::ui::XContextChangeEventMultiplexer > m_xMultiplexer; + css::uno::Reference< css::ui::XUIConfigurationManager > m_xConfigManager, m_xModuleConfigManager; + css::uno::Reference< css::container::XIndexAccess > m_xSecondaryConfigData; css::uno::Reference< css::lang::XComponent > m_xToolBarManager; css::uno::Reference< css::uno::XComponentContext > m_xContext; }; diff --git a/framework/source/uielement/toolbarmanager.cxx b/framework/source/uielement/toolbarmanager.cxx index 280958311ae4..e7047d469d4b 100644 --- a/framework/source/uielement/toolbarmanager.cxx +++ b/framework/source/uielement/toolbarmanager.cxx @@ -959,7 +959,9 @@ void ToolBarManager::InitImageManager() } } -void ToolBarManager::FillToolbar( const Reference< XIndexAccess >& rItemContainer ) +void ToolBarManager::FillToolbar( const Reference< XIndexAccess >& rItemContainer, + const Reference< XIndexAccess >& rSecondaryData, + const OUString& rSecondaryResourceName ) { OString aTbxName = OUStringToOString( m_aResourceName, RTL_TEXTENCODING_ASCII_US ); SAL_INFO( "fwk.uielement", "framework (cd100003) ::ToolBarManager::FillToolbar " << aTbxName ); @@ -979,6 +981,54 @@ void ToolBarManager::FillToolbar( const Reference< XIndexAccess >& rItemContaine m_aCommandMap.clear(); sal_uInt16 nId( 1 ); + sal_uInt16 nAddonId( 1000 ); + impl_fillToolbarFromContainer( rItemContainer, m_aResourceName, nId, nAddonId ); + if ( rSecondaryData.is() ) + { + m_pToolBar->InsertSeparator(); + impl_fillToolbarFromContainer( rSecondaryData, rSecondaryResourceName, nId, nAddonId ); + } + + // Request images for all toolbar items. Must be done before CreateControllers as + // some controllers need access to the image. + RequestImages(); + + // Create controllers after we set the images. There are controllers which needs + // an image at the toolbar at creation time! + CreateControllers(); + + // Notify controllers that they are now correctly initialized and can start listening + // toolbars that will open in popup mode will be updated immediately to avoid flickering + if( m_pToolBar->WillUsePopupMode() ) + UpdateControllers(); + else if ( m_pToolBar->IsReallyVisible() ) + { + m_aAsyncUpdateControllersTimer.Start(); + } + + // Try to retrieve UIName from the container property set and set it as the title + // if it is not empty. + Reference< XPropertySet > xPropSet( rItemContainer, UNO_QUERY ); + if ( xPropSet.is() ) + { + try + { + OUString aUIName; + xPropSet->getPropertyValue("UIName") >>= aUIName; + if ( !aUIName.isEmpty() ) + m_pToolBar->SetText( aUIName ); + } + catch (const Exception&) + { + } + } +} + +void ToolBarManager::impl_fillToolbarFromContainer( const Reference< XIndexAccess >& rItemContainer, + const OUString& rResourceName, sal_uInt16& nId , sal_uInt16& nAddonId) +{ + const sal_uInt16 nFirstItem( m_pToolBar->GetItemCount() ); + CommandInfo aCmdInfo; for ( sal_Int32 n = 0; n < rItemContainer->getCount(); n++ ) { @@ -1080,12 +1130,10 @@ void ToolBarManager::FillToolbar( const Reference< XIndexAccess >& rItemContaine // Support add-on toolbar merging here. Working directly on the toolbar object is much // simpler and faster. - const sal_uInt16 TOOLBAR_ITEM_STARTID = 1000; - MergeToolbarInstructionContainer aMergeInstructionContainer; // Retrieve the toolbar name from the resource name - OUString aToolbarName( m_aResourceName ); + OUString aToolbarName( rResourceName ); sal_Int32 nIndex = aToolbarName.lastIndexOf( '/' ); if (( nIndex > 0 ) && ( nIndex < aToolbarName.getLength() )) aToolbarName = aToolbarName.copy( nIndex+1 ); @@ -1094,14 +1142,14 @@ void ToolBarManager::FillToolbar( const Reference< XIndexAccess >& rItemContaine if ( !aMergeInstructionContainer.empty() ) { - sal_uInt16 nItemId( TOOLBAR_ITEM_STARTID ); const sal_uInt32 nCount = aMergeInstructionContainer.size(); for ( sal_uInt32 i=0; i < nCount; i++ ) { MergeToolbarInstruction& rInstruction = aMergeInstructionContainer[i]; if ( ToolBarMerger::IsCorrectContext( rInstruction.aMergeContext, m_aModuleIdentifier )) { - ReferenceToolbarPathInfo aRefPoint = ToolBarMerger::FindReferencePoint( m_pToolBar, rInstruction.aMergePoint ); + ReferenceToolbarPathInfo aRefPoint = ToolBarMerger::FindReferencePoint( m_pToolBar, nFirstItem, + rInstruction.aMergePoint ); // convert the sequence< sequence< propertyvalue > > structure to // something we can better handle. A vector with item data @@ -1112,7 +1160,7 @@ void ToolBarManager::FillToolbar( const Reference< XIndexAccess >& rItemContaine { ToolBarMerger::ProcessMergeOperation( m_pToolBar, aRefPoint.nPos, - nItemId, + nAddonId, m_aCommandMap, m_aModuleIdentifier, rInstruction.aMergeCommand, @@ -1122,7 +1170,7 @@ void ToolBarManager::FillToolbar( const Reference< XIndexAccess >& rItemContaine else { ToolBarMerger::ProcessMergeFallback( m_pToolBar, - nItemId, + nAddonId, m_aCommandMap, m_aModuleIdentifier, rInstruction.aMergeCommand, @@ -1132,40 +1180,6 @@ void ToolBarManager::FillToolbar( const Reference< XIndexAccess >& rItemContaine } } } - - // Request images for all toolbar items. Must be done before CreateControllers as - // some controllers need access to the image. - RequestImages(); - - // Create controllers after we set the images. There are controllers which needs - // an image at the toolbar at creation time! - CreateControllers(); - - // Notify controllers that they are now correctly initialized and can start listening - // toolbars that will open in popup mode will be updated immediately to avoid flickering - if( m_pToolBar->WillUsePopupMode() ) - UpdateControllers(); - else if ( m_pToolBar->IsReallyVisible() ) - { - m_aAsyncUpdateControllersTimer.Start(); - } - - // Try to retrieve UIName from the container property set and set it as the title - // if it is not empty. - Reference< XPropertySet > xPropSet( rItemContainer, UNO_QUERY ); - if ( xPropSet.is() ) - { - try - { - OUString aUIName; - xPropSet->getPropertyValue("UIName") >>= aUIName; - if ( !aUIName.isEmpty() ) - m_pToolBar->SetText( aUIName ); - } - catch (const Exception&) - { - } - } } void ToolBarManager::FillOverflowToolbar( ToolBox const * pParent ) diff --git a/framework/source/uielement/toolbarmerger.cxx b/framework/source/uielement/toolbarmerger.cxx index e66a31e77d85..35b42806ea8d 100644 --- a/framework/source/uielement/toolbarmerger.cxx +++ b/framework/source/uielement/toolbarmerger.cxx @@ -223,8 +223,7 @@ void ToolBarMerger::ConvertSequenceToValues( Provides information about the search result, the position of the reference point and the toolbar used. */ -ReferenceToolbarPathInfo ToolBarMerger::FindReferencePoint( - ToolBox* pToolbar, +ReferenceToolbarPathInfo ToolBarMerger::FindReferencePoint(ToolBox* pToolbar, ToolBox::ImplToolItems::size_type nFirstItem, const OUString& rReferencePoint ) { ReferenceToolbarPathInfo aResult; @@ -233,7 +232,7 @@ ReferenceToolbarPathInfo ToolBarMerger::FindReferencePoint( const ToolBox::ImplToolItems::size_type nSize( pToolbar->GetItemCount() ); - for ( ToolBox::ImplToolItems::size_type i = 0; i < nSize; i++ ) + for ( ToolBox::ImplToolItems::size_type i = nFirstItem; i < nSize; i++ ) { const sal_uInt16 nItemId = pToolbar->GetItemId( i ); if ( nItemId > 0 ) diff --git a/framework/source/uielement/toolbarwrapper.cxx b/framework/source/uielement/toolbarwrapper.cxx index 951fb3bacec7..c45288b2fed6 100644 --- a/framework/source/uielement/toolbarwrapper.cxx +++ b/framework/source/uielement/toolbarwrapper.cxx @@ -29,7 +29,10 @@ #include #include #include +#include #include +#include +#include #include #include @@ -96,6 +99,23 @@ void SAL_CALL ToolBarWrapper::dispose() return; } + Reference< XFrame > xFrame( m_xWeakFrame ); + if ( m_xMultiplexer.is() ) + m_xMultiplexer->removeContextChangeEventListener( this, xFrame->getController() ); + m_xMultiplexer.clear(); + + css::uno::Reference< css::ui::XUIConfiguration > xConfig( m_xConfigManager, css::uno::UNO_QUERY ); + if ( xConfig.is() ) + xConfig->removeConfigurationListener( this ); + + css::uno::Reference< css::ui::XUIConfiguration > xModuleConfig( m_xModuleConfigManager, css::uno::UNO_QUERY ); + if ( xModuleConfig.is() ) + xModuleConfig->removeConfigurationListener( this ); + + m_xConfigManager.clear(); + m_xModuleConfigManager.clear(); + m_xSecondaryConfigData.clear(); + css::lang::EventObject aEvent( xThis ); m_aListenerContainer.disposeAndClear( aEvent ); @@ -122,6 +142,58 @@ void SAL_CALL ToolBarWrapper::initialize( const Sequence< Any >& aArguments ) { UIConfigElementWrapperBase::initialize( aArguments ); + Reference< XFrame > xFrame( m_xWeakFrame ); + + if (m_aResourceURL == "private:resource/toolbar/singlemode") + { + try + { + css::uno::Reference< css::frame::XController > xController( xFrame->getController() ); + css::uno::Reference< css::frame::XModel > xModel( xController->getModel() ); + css::uno::Reference< css::ui::XUIConfigurationManagerSupplier > xSupplier( xModel, css::uno::UNO_QUERY_THROW ); + m_xConfigManager.set( xSupplier->getUIConfigurationManager() ); + css::uno::Reference< css::ui::XUIConfiguration > xConfig( m_xConfigManager, css::uno::UNO_QUERY_THROW ); + xConfig->addConfigurationListener( this ); + } + catch( const css::uno::RuntimeException& ) + {} + + OUString aModuleName; + try + { + css::uno::Reference< css::frame::XModuleManager > xModuleManager( css::frame::ModuleManager::create( m_xContext ) ); + aModuleName = xModuleManager->identify( xFrame ); + } + catch( const css::uno::Exception& ) + {} + + try + { + css::uno::Reference< css::ui::XModuleUIConfigurationManagerSupplier > xModuleCfgMgrSupplier( + css::ui::theModuleUIConfigurationManagerSupplier::get( m_xContext ) ); + m_xModuleConfigManager.set( xModuleCfgMgrSupplier->getUIConfigurationManager( aModuleName ) ); + css::uno::Reference< css::ui::XUIConfiguration > xConfig( m_xModuleConfigManager, css::uno::UNO_QUERY_THROW ); + xConfig->addConfigurationListener( this ); + } + catch ( const css::container::NoSuchElementException& ) + { + SAL_WARN( "fwk.uielement", "Invalid module identifier: " << aModuleName ); + } + catch( const css::uno::RuntimeException& ) + {} + + m_xMultiplexer.set( ui::ContextChangeEventMultiplexer::get( m_xContext ) ); + try + { + m_xMultiplexer->addContextChangeEventListener( this, xFrame->getController() ); + } + catch( const css::uno::Exception& ) + { + } + + impl_updateSecondaryData(); + } + bool bPopupMode( false ); Reference< XWindow > xParentWindow; for ( sal_Int32 i = 0; i < aArguments.getLength(); i++ ) @@ -136,7 +208,6 @@ void SAL_CALL ToolBarWrapper::initialize( const Sequence< Any >& aArguments ) } } - Reference< XFrame > xFrame( m_xWeakFrame ); if ( xFrame.is() && m_xConfigSource.is() ) { // Create VCL based toolbar which will be filled with settings data @@ -165,7 +236,7 @@ void SAL_CALL ToolBarWrapper::initialize( const Sequence< Any >& aArguments ) if ( m_xConfigData.is() && pToolBar && pToolBarManager ) { // Fill toolbar with container contents - pToolBarManager->FillToolbar( m_xConfigData ); + pToolBarManager->FillToolbar( m_xConfigData, m_xSecondaryConfigData, m_aSecondaryToolbar ); pToolBar->SetOutStyle( SvtMiscOptions().GetToolboxStyle() ); pToolBar->EnableCustomize(); ::Size aActSize( pToolBar->GetSizePixel() ); @@ -194,9 +265,14 @@ void SAL_CALL ToolBarWrapper::initialize( const Sequence< Any >& aArguments ) } // XEventListener -void SAL_CALL ToolBarWrapper::disposing( const css::lang::EventObject& ) +void SAL_CALL ToolBarWrapper::disposing( const css::lang::EventObject& aEvent ) { - // nothing todo + if ( aEvent.Source == m_xMultiplexer ) + m_xMultiplexer.clear(); + else if ( aEvent.Source == m_xConfigManager ) + m_xConfigManager.clear(); + else if ( aEvent.Source == m_xModuleConfigManager ) + m_xModuleConfigManager.clear(); } // XUpdatable @@ -230,7 +306,7 @@ void SAL_CALL ToolBarWrapper::updateSettings() m_xConfigData = m_xConfigSource->getSettings( m_aResourceURL, false ); if ( m_xConfigData.is() ) - pToolBarManager->FillToolbar( m_xConfigData ); + pToolBarManager->FillToolbar( m_xConfigData, m_xSecondaryConfigData, m_aSecondaryToolbar ); } catch ( const NoSuchElementException& ) { @@ -248,7 +324,47 @@ void ToolBarWrapper::impl_fillNewData() // Transient toolbar => Fill toolbar with new data ToolBarManager* pToolBarManager = static_cast< ToolBarManager *>( m_xToolBarManager.get() ); if ( pToolBarManager ) - pToolBarManager->FillToolbar( m_xConfigData ); + pToolBarManager->FillToolbar( m_xConfigData, nullptr, OUString() ); +} + +void ToolBarWrapper::impl_updateSecondaryData() +{ + m_xSecondaryConfigData.clear(); + + if ( m_xConfigManager.is() ) + { + try + { + m_xSecondaryConfigData.set( m_xConfigManager->getSettings( m_aSecondaryToolbar, false ) ); + } + catch ( const css::container::NoSuchElementException& ) + { + // Not an error - element may exist only in the module. + } + catch ( const css::lang::IllegalArgumentException& ) + { + SAL_WARN( "fwk.uielement", "The given URL is not valid: " << m_aSecondaryToolbar ); + return; + } + } + + if ( !m_xSecondaryConfigData.is() && m_xModuleConfigManager.is() ) + { + try + { + m_xSecondaryConfigData.set( m_xModuleConfigManager->getSettings( m_aSecondaryToolbar, false ) ); + } + catch ( const css::container::NoSuchElementException& ) + { + SAL_WARN( "fwk.uielement", "Can not find settings for " << m_aSecondaryToolbar ); + return; + } + catch ( const css::lang::IllegalArgumentException& ) + { + SAL_WARN( "fwk.uielement", "The given URL is not valid: " << m_aSecondaryToolbar ); + return; + } + } } // XUIElement interface @@ -284,6 +400,53 @@ void SAL_CALL ToolBarWrapper::functionExecute( } } +//XContextChangeEventListener +void SAL_CALL ToolBarWrapper::notifyContextChangeEvent( const css::ui::ContextChangeEventObject& aEvent ) +{ + if (aEvent.ContextName == m_aLastContext) + return; + + // FIXME + if (aEvent.ContextName == "Text") + m_aSecondaryToolbar = "private:resource/toolbar/textobjectbar"; + else if (aEvent.ContextName == "Table") + m_aSecondaryToolbar = "private:resource/toolbar/tableobjectbar"; + else if (aEvent.ContextName == "Graphic") + m_aSecondaryToolbar = "private:resource/toolbar/graphicobjectbar"; + else if (aEvent.ContextName == "OLE") + m_aSecondaryToolbar = "private:resource/toolbar/oleobjectbar"; + else if (aEvent.ContextName == "Draw") + m_aSecondaryToolbar = "private:resource/toolbar/drawingobjectbar"; + else + { + m_aSecondaryToolbar.clear(); + return; + } + + m_aLastContext = aEvent.ContextName; + impl_updateSecondaryData(); + updateSettings(); +} + +//XUIConfigurationListener +void SAL_CALL ToolBarWrapper::elementInserted( const css::ui::ConfigurationEvent& aEvent ) +{ + if ( aEvent.ResourceURL == m_aSecondaryToolbar ) + { + impl_updateSecondaryData(); + updateSettings(); + } +} + +void SAL_CALL ToolBarWrapper::elementRemoved( const css::ui::ConfigurationEvent& aEvent ) +{ + elementInserted( aEvent ); +} +void SAL_CALL ToolBarWrapper::elementReplaced( const css::ui::ConfigurationEvent& aEvent ) +{ + elementInserted( aEvent ); +} + void SAL_CALL ToolBarWrapper::setFastPropertyValue_NoBroadcast( sal_Int32 nHandle, const css::uno::Any& aValue ) { SolarMutexResettableGuard aLock; diff --git a/sfx2/source/appl/workwin.cxx b/sfx2/source/appl/workwin.cxx index 12276fe01bdb..e197d885122f 100644 --- a/sfx2/source/appl/workwin.cxx +++ b/sfx2/source/appl/workwin.cxx @@ -1204,7 +1204,8 @@ void SfxWorkWindow::UpdateObjectBars_Impl2() // Is a ToolBox required in this context ? bool bModesMatching = (nUpdateMode != SfxVisibilityFlags::Invisible) && ((nTbxMode & nUpdateMode) == nUpdateMode); - if ( bDestroy || sfx2::SfxNotebookBar::IsActive()) + if ( bDestroy || sfx2::SfxNotebookBar::IsActive() || + xLayoutManager->isElementVisible("private:resource/toolbar/singlemode") ) { OUString aTbxId = g_aTbxTypeName + GetResourceURLFromToolbarId(eId); xLayoutManager->destroyElement( aTbxId ); diff --git a/sw/uiconfig/swriter/toolbar/singlemode.xml b/sw/uiconfig/swriter/toolbar/singlemode.xml index 78a69958f812..bf8224e445e9 100644 --- a/sw/uiconfig/swriter/toolbar/singlemode.xml +++ b/sw/uiconfig/swriter/toolbar/singlemode.xml @@ -18,62 +18,6 @@ * the License at http://www.apache.org/licenses/LICENSE-2.0 . --> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + -- 2.20.1