diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
commit | ed5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch) | |
tree | 7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /framework/source/fwe/helper | |
parent | Initial commit. (diff) | |
download | libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.tar.xz libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.zip |
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'framework/source/fwe/helper')
-rw-r--r-- | framework/source/fwe/helper/actiontriggerhelper.cxx | 376 | ||||
-rw-r--r-- | framework/source/fwe/helper/configimporter.cxx | 74 | ||||
-rw-r--r-- | framework/source/fwe/helper/documentundoguard.cxx | 223 | ||||
-rw-r--r-- | framework/source/fwe/helper/propertysetcontainer.cxx | 158 | ||||
-rw-r--r-- | framework/source/fwe/helper/titlehelper.cxx | 685 | ||||
-rw-r--r-- | framework/source/fwe/helper/undomanagerhelper.cxx | 1056 |
6 files changed, 2572 insertions, 0 deletions
diff --git a/framework/source/fwe/helper/actiontriggerhelper.cxx b/framework/source/fwe/helper/actiontriggerhelper.cxx new file mode 100644 index 000000000..88edd70be --- /dev/null +++ b/framework/source/fwe/helper/actiontriggerhelper.cxx @@ -0,0 +1,376 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <framework/actiontriggerhelper.hxx> +#include <classes/actiontriggerseparatorpropertyset.hxx> +#include <classes/rootactiontriggercontainer.hxx> +#include <framework/addonsoptions.hxx> +#include <com/sun/star/awt/XBitmap.hpp> +#include <com/sun/star/awt/XPopupMenu.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <toolkit/awt/vclxmenu.hxx> +#include <tools/stream.hxx> +#include <vcl/dibtools.hxx> +#include <vcl/graph.hxx> +#include <vcl/svapp.hxx> +#include <o3tl/string_view.hxx> + +const sal_uInt16 START_ITEMID = 1000; + +using namespace com::sun::star::awt; +using namespace com::sun::star::uno; +using namespace com::sun::star::lang; +using namespace com::sun::star::beans; +using namespace com::sun::star::container; + +namespace framework +{ + +// implementation helper ( menu => ActionTrigger ) + +static bool IsSeparator( const Reference< XPropertySet >& xPropertySet ) +{ + Reference< XServiceInfo > xServiceInfo( xPropertySet, UNO_QUERY ); + try + { + return xServiceInfo->supportsService( SERVICENAME_ACTIONTRIGGERSEPARATOR ); + } + catch (const Exception&) + { + } + + return false; +} + +static void GetMenuItemAttributes( const Reference< XPropertySet >& xActionTriggerPropertySet, + OUString& aMenuLabel, + OUString& aCommandURL, + OUString& aHelpURL, + Reference< XBitmap >& xBitmap, + Reference< XIndexContainer >& xSubContainer ) +{ + Any a; + + try + { + // mandatory properties + a = xActionTriggerPropertySet->getPropertyValue("Text"); + a >>= aMenuLabel; + a = xActionTriggerPropertySet->getPropertyValue("CommandURL"); + a >>= aCommandURL; + a = xActionTriggerPropertySet->getPropertyValue("Image"); + a >>= xBitmap; + a = xActionTriggerPropertySet->getPropertyValue("SubContainer"); + a >>= xSubContainer; + } + catch (const Exception&) + { + } + + // optional properties + try + { + a = xActionTriggerPropertySet->getPropertyValue("HelpURL"); + a >>= aHelpURL; + } + catch (const Exception&) + { + } +} + +static void InsertSubMenuItems(const Reference<XPopupMenu>& rSubMenu, sal_uInt16& nItemId, + const Reference<XIndexContainer>& xActionTriggerContainer) +{ + if ( !xActionTriggerContainer.is() ) + return; + + AddonsOptions aAddonOptions; + OUString aSlotURL( "slot:" ); + + for ( sal_Int32 i = 0; i < xActionTriggerContainer->getCount(); i++ ) + { + try + { + Reference< XPropertySet > xPropSet; + if (( xActionTriggerContainer->getByIndex( i ) >>= xPropSet ) && ( xPropSet.is() )) + { + if ( IsSeparator( xPropSet )) + { + // Separator + SolarMutexGuard aGuard; + rSubMenu->insertSeparator(i); + } + else + { + // Menu item + OUString aLabel; + OUString aCommandURL; + OUString aHelpURL; + Reference< XBitmap > xBitmap; + Reference< XIndexContainer > xSubContainer; + + sal_uInt16 nNewItemId = nItemId++; + GetMenuItemAttributes( xPropSet, aLabel, aCommandURL, aHelpURL, xBitmap, xSubContainer ); + + SolarMutexGuard aGuard; + { + // insert new menu item + sal_Int32 nIndex = aCommandURL.indexOf( aSlotURL ); + if ( nIndex >= 0 ) + { + // Special code for our menu implementation: some menu items don't have a + // command url but uses the item id as a unique identifier. These entries + // got a special url during conversion from menu=>actiontriggercontainer. + // Now we have to extract this special url and set the correct item id!!! + nNewItemId = static_cast<sal_uInt16>(o3tl::toInt32(aCommandURL.subView( nIndex+aSlotURL.getLength() ))); + rSubMenu->insertItem(nNewItemId, aLabel, 0, i); + } + else + { + rSubMenu->insertItem(nNewItemId, aLabel, 0, i); + rSubMenu->setCommand(nNewItemId, aCommandURL); + } + + // handle bitmap + if ( xBitmap.is() ) + { + bool bImageSet = false; + + Reference<css::graphic::XGraphic> xGraphic(xBitmap, UNO_QUERY); + if (xGraphic.is()) + { + // we can take the optimized route if XGraphic is supported + rSubMenu->setItemImage(nNewItemId, xGraphic, false); + bImageSet = true; + } + + if ( !bImageSet ) + { + // This is an unknown implementation of a XBitmap interface. We have to + // use a more time consuming way to build an Image! + BitmapEx aBitmap; + + Sequence< sal_Int8 > aDIBSeq; + { + aDIBSeq = xBitmap->getDIB(); + SvMemoryStream aMem( const_cast<sal_Int8 *>(aDIBSeq.getConstArray()), aDIBSeq.getLength(), StreamMode::READ ); + ReadDIBBitmapEx(aBitmap, aMem); + } + + aDIBSeq = xBitmap->getMaskDIB(); + if ( aDIBSeq.hasElements() ) + { + Bitmap aMaskBitmap; + SvMemoryStream aMem( const_cast<sal_Int8 *>(aDIBSeq.getConstArray()), aDIBSeq.getLength(), StreamMode::READ ); + ReadDIB(aMaskBitmap, aMem, true); + aBitmap = BitmapEx(aBitmap.GetBitmap(), aMaskBitmap); + } + + if (!aBitmap.IsEmpty()) + rSubMenu->setItemImage(nNewItemId, Graphic(aBitmap).GetXGraphic(), false); + } + } + else + { + // Support add-on images for context menu interceptors + BitmapEx aBitmap(aAddonOptions.GetImageFromURL(aCommandURL, false, true)); + if (!aBitmap.IsEmpty()) + rSubMenu->setItemImage(nNewItemId, Graphic(aBitmap).GetXGraphic(), false); + } + + if ( xSubContainer.is() ) + { + rtl::Reference xNewSubMenu(new VCLXPopupMenu); + + // Sub menu (recursive call CreateSubMenu ) + InsertSubMenuItems(xNewSubMenu, nItemId, xSubContainer); + rSubMenu->setPopupMenu(nNewItemId, xNewSubMenu); + } + } + } + } + } + catch (const IndexOutOfBoundsException&) + { + return; + } + catch (const WrappedTargetException&) + { + return; + } + catch (const RuntimeException&) + { + return; + } + } +} + +// implementation helper ( ActionTrigger => menu ) + +/// @throws RuntimeException +static Reference< XPropertySet > CreateActionTrigger(sal_uInt16 nItemId, + const Reference<XPopupMenu>& rMenu, + const Reference<XIndexContainer>& rActionTriggerContainer) +{ + Reference< XPropertySet > xPropSet; + + Reference< XMultiServiceFactory > xMultiServiceFactory( rActionTriggerContainer, UNO_QUERY ); + if ( xMultiServiceFactory.is() ) + { + xPropSet.set( xMultiServiceFactory->createInstance( "com.sun.star.ui.ActionTrigger" ), + UNO_QUERY ); + + Any a; + + try + { + // Retrieve the menu attributes and set them in our PropertySet + OUString aLabel = rMenu->getItemText(nItemId); + a <<= aLabel; + xPropSet->setPropertyValue("Text", a ); + + OUString aCommandURL = rMenu->getCommand(nItemId); + + if ( aCommandURL.isEmpty() ) + { + aCommandURL = "slot:" + OUString::number( nItemId ); + } + + a <<= aCommandURL; + xPropSet->setPropertyValue("CommandURL", a ); + + Reference<XBitmap> xBitmap(rMenu->getItemImage(nItemId), UNO_QUERY); + if (xBitmap.is()) + { + a <<= xBitmap; + xPropSet->setPropertyValue("Image", a ); + } + } + catch (const Exception&) + { + } + } + + return xPropSet; +} + +/// @throws RuntimeException +static Reference< XPropertySet > CreateActionTriggerSeparator( const Reference< XIndexContainer >& rActionTriggerContainer ) +{ + Reference< XMultiServiceFactory > xMultiServiceFactory( rActionTriggerContainer, UNO_QUERY ); + if ( xMultiServiceFactory.is() ) + { + return Reference< XPropertySet >( xMultiServiceFactory->createInstance( + "com.sun.star.ui.ActionTriggerSeparator" ), + UNO_QUERY ); + } + + return Reference< XPropertySet >(); +} + +/// @throws RuntimeException +static Reference< XIndexContainer > CreateActionTriggerContainer( const Reference< XIndexContainer >& rActionTriggerContainer ) +{ + Reference< XMultiServiceFactory > xMultiServiceFactory( rActionTriggerContainer, UNO_QUERY ); + if ( xMultiServiceFactory.is() ) + { + return Reference< XIndexContainer >( xMultiServiceFactory->createInstance( + "com.sun.star.ui.ActionTriggerContainer" ), + UNO_QUERY ); + } + + return Reference< XIndexContainer >(); +} + +static void FillActionTriggerContainerWithMenu(const Reference<XPopupMenu>& rMenu, + const Reference<XIndexContainer>& rActionTriggerContainer) +{ + SolarMutexGuard aGuard; + + for (sal_uInt16 nPos = 0, nCount = rMenu->getItemCount(); nPos < nCount; ++nPos) + { + sal_uInt16 nItemId = rMenu->getItemId(nPos); + css::awt::MenuItemType nType = rMenu->getItemType(nPos); + + try + { + Any a; + Reference< XPropertySet > xPropSet; + + if (nType == css::awt::MenuItemType_SEPARATOR) + { + xPropSet = CreateActionTriggerSeparator( rActionTriggerContainer ); + + a <<= xPropSet; + rActionTriggerContainer->insertByIndex( nPos, a ); + } + else + { + xPropSet = CreateActionTrigger(nItemId, rMenu, rActionTriggerContainer); + + a <<= xPropSet; + rActionTriggerContainer->insertByIndex( nPos, a ); + + css::uno::Reference<XPopupMenu> xPopupMenu = rMenu->getPopupMenu(nItemId); + if (xPopupMenu.is()) + { + // recursive call to build next sub menu + Reference< XIndexContainer > xSubContainer = CreateActionTriggerContainer( rActionTriggerContainer ); + + a <<= xSubContainer; + xPropSet->setPropertyValue("SubContainer", a ); + FillActionTriggerContainerWithMenu(xPopupMenu, xSubContainer); + } + } + } + catch (const Exception&) + { + } + } +} + +void ActionTriggerHelper::CreateMenuFromActionTriggerContainer( + const Reference<XPopupMenu>& rNewMenu, + const Reference<XIndexContainer>& rActionTriggerContainer) +{ + sal_uInt16 nItemId = START_ITEMID; + + if ( rActionTriggerContainer.is() ) + InsertSubMenuItems(rNewMenu, nItemId, rActionTriggerContainer); +} + +void ActionTriggerHelper::FillActionTriggerContainerFromMenu( + Reference< XIndexContainer > const & xActionTriggerContainer, + const css::uno::Reference<XPopupMenu>& rMenu) +{ + FillActionTriggerContainerWithMenu(rMenu, xActionTriggerContainer); +} + +Reference< XIndexContainer > ActionTriggerHelper::CreateActionTriggerContainerFromMenu( + const css::uno::Reference<XPopupMenu>& rMenu, + const OUString* pMenuIdentifier ) +{ + return new RootActionTriggerContainer(rMenu, pMenuIdentifier); +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/fwe/helper/configimporter.cxx b/framework/source/fwe/helper/configimporter.cxx new file mode 100644 index 000000000..bc92d180b --- /dev/null +++ b/framework/source/fwe/helper/configimporter.cxx @@ -0,0 +1,74 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <framework/configimporter.hxx> +#include <toolboxconfiguration.hxx> +#include <com/sun/star/embed/ElementModes.hpp> +#include <com/sun/star/ui/XUIConfigurationManager2.hpp> + +using namespace ::com::sun::star; + +namespace framework +{ + +bool UIConfigurationImporterOOo1x::ImportCustomToolbars( + const uno::Reference< ui::XUIConfigurationManager2 >& rContainerFactory, + std::vector< uno::Reference< container::XIndexContainer > >& rSeqContainer, + const uno::Reference< uno::XComponentContext >& rxContext, + const uno::Reference< embed::XStorage >& rToolbarStorage ) +{ + bool bResult ( false ); + if ( rToolbarStorage.is() && rContainerFactory.is() ) + { + try + { + for ( sal_uInt16 i = 1; i <= 4; i++ ) + { + OUString aTbxStreamName = "userdeftoolbox" + OUString::number(i) + ".xml"; + uno::Reference< io::XStream > xStream = rToolbarStorage->openStreamElement( aTbxStreamName, embed::ElementModes::READ ); + if ( xStream.is() ) + { + uno::Reference< io::XInputStream > xInputStream = xStream->getInputStream(); + if ( xInputStream.is() ) + { + uno::Reference< container::XIndexContainer > xContainer = rContainerFactory->createSettings(); + if ( ToolBoxConfiguration::LoadToolBox( rxContext, xInputStream, xContainer )) + { + rSeqContainer.push_back( xContainer ); + bResult = true; + } + } + } + } + } + catch ( const uno::RuntimeException& ) + { + throw; + } + catch ( const uno::Exception& ) + { + } + } + + return bResult; +} + +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/fwe/helper/documentundoguard.cxx b/framework/source/fwe/helper/documentundoguard.cxx new file mode 100644 index 000000000..5f7d16041 --- /dev/null +++ b/framework/source/fwe/helper/documentundoguard.cxx @@ -0,0 +1,223 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <framework/documentundoguard.hxx> + +#include <com/sun/star/document/XUndoManagerSupplier.hpp> + +#include <cppuhelper/implbase.hxx> +#include <rtl/ref.hxx> +#include <tools/diagnose_ex.h> + +namespace framework +{ + + using ::com::sun::star::uno::Reference; + using ::com::sun::star::uno::XInterface; + using ::com::sun::star::uno::UNO_QUERY; + using ::com::sun::star::uno::UNO_QUERY_THROW; + using ::com::sun::star::uno::Exception; + using ::com::sun::star::document::XUndoManagerSupplier; + using ::com::sun::star::document::XUndoManager; + using ::com::sun::star::document::XUndoManagerListener; + using ::com::sun::star::document::UndoManagerEvent; + using ::com::sun::star::lang::EventObject; + + //= UndoManagerContextListener + + typedef ::cppu::WeakImplHelper < XUndoManagerListener + > UndoManagerContextListener_Base; + + namespace { + + class UndoManagerContextListener : public UndoManagerContextListener_Base + { + public: + explicit UndoManagerContextListener( const Reference< XUndoManager >& i_undoManager ) + :m_xUndoManager( i_undoManager ) + ,m_nRelativeContextDepth( 0 ) + ,m_documentDisposed( false ) + { + osl_atomic_increment( &m_refCount ); + { + m_xUndoManager->addUndoManagerListener( this ); + } + osl_atomic_decrement( &m_refCount ); + } + + void finish() + { + OSL_ENSURE( m_nRelativeContextDepth >= 0, "UndoManagerContextListener: more contexts left than entered?" ); + + if ( m_documentDisposed ) + return; + + // work with a copy of m_nRelativeContextDepth, to be independent from possible bugs in the + // listener notifications (where it would be decremented with every leaveUndoContext) + sal_Int32 nDepth = m_nRelativeContextDepth; + while ( nDepth-- > 0 ) + { + m_xUndoManager->leaveUndoContext(); + } + m_xUndoManager->removeUndoManagerListener( this ); + } + + // XUndoManagerListener + virtual void SAL_CALL undoActionAdded( const UndoManagerEvent& i_event ) override; + virtual void SAL_CALL actionUndone( const UndoManagerEvent& i_event ) override; + virtual void SAL_CALL actionRedone( const UndoManagerEvent& i_event ) override; + virtual void SAL_CALL allActionsCleared( const EventObject& i_event ) override; + virtual void SAL_CALL redoActionsCleared( const EventObject& i_event ) override; + virtual void SAL_CALL resetAll( const EventObject& i_event ) override; + virtual void SAL_CALL enteredContext( const UndoManagerEvent& i_event ) override; + virtual void SAL_CALL enteredHiddenContext( const UndoManagerEvent& i_event ) override; + virtual void SAL_CALL leftContext( const UndoManagerEvent& i_event ) override; + virtual void SAL_CALL leftHiddenContext( const UndoManagerEvent& i_event ) override; + virtual void SAL_CALL cancelledContext( const UndoManagerEvent& i_event ) override; + + // XEventListener + virtual void SAL_CALL disposing( const EventObject& i_event ) override; + + private: + Reference< XUndoManager > const m_xUndoManager; + oslInterlockedCount m_nRelativeContextDepth; + bool m_documentDisposed; + }; + + } + + void SAL_CALL UndoManagerContextListener::undoActionAdded( const UndoManagerEvent& ) + { + // not interested in + } + + void SAL_CALL UndoManagerContextListener::actionUndone( const UndoManagerEvent& ) + { + // not interested in + } + + void SAL_CALL UndoManagerContextListener::actionRedone( const UndoManagerEvent& ) + { + // not interested in + } + + void SAL_CALL UndoManagerContextListener::allActionsCleared( const EventObject& ) + { + // not interested in + } + + void SAL_CALL UndoManagerContextListener::redoActionsCleared( const EventObject& ) + { + // not interested in + } + + void SAL_CALL UndoManagerContextListener::resetAll( const EventObject& ) + { + m_nRelativeContextDepth = 0; + } + + void SAL_CALL UndoManagerContextListener::enteredContext( const UndoManagerEvent& ) + { + osl_atomic_increment( &m_nRelativeContextDepth ); + } + + void SAL_CALL UndoManagerContextListener::enteredHiddenContext( const UndoManagerEvent& ) + { + osl_atomic_increment( &m_nRelativeContextDepth ); + } + + void SAL_CALL UndoManagerContextListener::leftContext( const UndoManagerEvent& ) + { + osl_atomic_decrement( &m_nRelativeContextDepth ); + } + + void SAL_CALL UndoManagerContextListener::leftHiddenContext( const UndoManagerEvent& ) + { + osl_atomic_decrement( &m_nRelativeContextDepth ); + } + + void SAL_CALL UndoManagerContextListener::cancelledContext( const UndoManagerEvent& ) + { + osl_atomic_decrement( &m_nRelativeContextDepth ); + } + + void SAL_CALL UndoManagerContextListener::disposing( const EventObject& ) + { + m_documentDisposed = true; + } + + //= DocumentUndoGuard_Data + + struct DocumentUndoGuard_Data + { + Reference< XUndoManager > xUndoManager; + ::rtl::Reference< UndoManagerContextListener > pContextListener; + }; + + namespace + { + + void lcl_init( DocumentUndoGuard_Data& i_data, const Reference< XInterface >& i_undoSupplierComponent ) + { + try + { + Reference< XUndoManagerSupplier > xUndoSupplier( i_undoSupplierComponent, UNO_QUERY ); + if ( xUndoSupplier.is() ) + i_data.xUndoManager.set( xUndoSupplier->getUndoManager(), css::uno::UNO_SET_THROW ); + + if ( i_data.xUndoManager.is() ) + i_data.pContextListener.set( new UndoManagerContextListener( i_data.xUndoManager ) ); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("fwk"); + } + } + + void lcl_restore( DocumentUndoGuard_Data& i_data ) + { + try + { + if ( i_data.pContextListener.is() ) + i_data.pContextListener->finish(); + i_data.pContextListener.clear(); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("fwk"); + } + } + } + + //= DocumentUndoGuard + + DocumentUndoGuard::DocumentUndoGuard( const Reference< XInterface >& i_undoSupplierComponent ) + :m_xData( new DocumentUndoGuard_Data ) + { + lcl_init( *m_xData, i_undoSupplierComponent ); + } + + DocumentUndoGuard::~DocumentUndoGuard() + { + lcl_restore( *m_xData ); + } + +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/fwe/helper/propertysetcontainer.cxx b/framework/source/fwe/helper/propertysetcontainer.cxx new file mode 100644 index 000000000..ad7d63953 --- /dev/null +++ b/framework/source/fwe/helper/propertysetcontainer.cxx @@ -0,0 +1,158 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <helper/propertysetcontainer.hxx> + +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <cppuhelper/queryinterface.hxx> +#include <vcl/svapp.hxx> + +constexpr OUStringLiteral WRONG_TYPE_EXCEPTION = u"Only XPropertSet allowed!"; + +using namespace cppu; +using namespace com::sun::star::uno; +using namespace com::sun::star::container; +using namespace com::sun::star::lang; +using namespace com::sun::star::beans; + +namespace framework +{ + +PropertySetContainer::PropertySetContainer() +{ +} + +PropertySetContainer::~PropertySetContainer() +{ +} + +// XInterface +void SAL_CALL PropertySetContainer::acquire() noexcept +{ + OWeakObject::acquire(); +} + +void SAL_CALL PropertySetContainer::release() noexcept +{ + OWeakObject::release(); +} + +Any SAL_CALL PropertySetContainer::queryInterface( const Type& rType ) +{ + Any a = ::cppu::queryInterface( + rType , + static_cast< XIndexContainer* >(this), + static_cast< XIndexReplace* >(this), + static_cast< XIndexAccess* >(this), + static_cast< XElementAccess* >(this) ); + + if( a.hasValue() ) + { + return a; + } + + return OWeakObject::queryInterface( rType ); +} + +// XIndexContainer +void SAL_CALL PropertySetContainer::insertByIndex( sal_Int32 Index, const css::uno::Any& Element ) +{ + SolarMutexGuard g; + + sal_Int32 nSize = m_aPropertySetVector.size(); + + if ( nSize < Index ) + throw IndexOutOfBoundsException( OUString(), static_cast<OWeakObject *>(this) ); + + Reference< XPropertySet > aPropertySetElement; + + if ( !(Element >>= aPropertySetElement) ) + { + throw IllegalArgumentException( + WRONG_TYPE_EXCEPTION, + static_cast<OWeakObject *>(this), 2 ); + } + + if ( nSize == Index ) + m_aPropertySetVector.push_back( aPropertySetElement ); + else + { + PropertySetVector::iterator aIter = m_aPropertySetVector.begin(); + aIter += Index; + m_aPropertySetVector.insert( aIter, aPropertySetElement ); + } +} + +void SAL_CALL PropertySetContainer::removeByIndex( sal_Int32 nIndex ) +{ + SolarMutexGuard g; + + if ( static_cast<sal_Int32>(m_aPropertySetVector.size()) <= nIndex ) + throw IndexOutOfBoundsException( OUString(), static_cast<OWeakObject *>(this) ); + + m_aPropertySetVector.erase(m_aPropertySetVector.begin() + nIndex); +} + +// XIndexReplace +void SAL_CALL PropertySetContainer::replaceByIndex( sal_Int32 Index, const css::uno::Any& Element ) +{ + if ( static_cast<sal_Int32>(m_aPropertySetVector.size()) <= Index ) + throw IndexOutOfBoundsException( OUString(), static_cast<OWeakObject *>(this) ); + + Reference< XPropertySet > aPropertySetElement; + + if ( !(Element >>= aPropertySetElement) ) + { + throw IllegalArgumentException( + WRONG_TYPE_EXCEPTION, + static_cast<OWeakObject *>(this), 2 ); + } + + m_aPropertySetVector[ Index ] = aPropertySetElement; +} + +// XIndexAccess +sal_Int32 SAL_CALL PropertySetContainer::getCount() +{ + SolarMutexGuard g; + + return m_aPropertySetVector.size(); +} + +Any SAL_CALL PropertySetContainer::getByIndex( sal_Int32 Index ) +{ + SolarMutexGuard g; + + if ( static_cast<sal_Int32>(m_aPropertySetVector.size()) <= Index ) + throw IndexOutOfBoundsException( OUString(), static_cast<OWeakObject *>(this) ); + + return Any(m_aPropertySetVector[ Index ]); +} + +// XElementAccess +sal_Bool SAL_CALL PropertySetContainer::hasElements() +{ + SolarMutexGuard g; + + return !( m_aPropertySetVector.empty() ); +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/fwe/helper/titlehelper.cxx b/framework/source/fwe/helper/titlehelper.cxx new file mode 100644 index 000000000..aa4e8f0b2 --- /dev/null +++ b/framework/source/fwe/helper/titlehelper.cxx @@ -0,0 +1,685 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <framework/titlehelper.hxx> +#include <classes/fwkresid.hxx> +#include <strings.hrc> +#include <properties.h> + +#include <com/sun/star/frame/UntitledNumbersConst.hpp> +#include <com/sun/star/frame/XStorable.hpp> +#include <com/sun/star/frame/ModuleManager.hpp> +#include <com/sun/star/frame/XUntitledNumbers.hpp> +#include <com/sun/star/frame/XModel3.hpp> +#include <com/sun/star/document/XDocumentEventBroadcaster.hpp> + +#include <unotools/configmgr.hxx> +#include <unotools/bootstrap.hxx> +#include <unotools/mediadescriptor.hxx> +#include <comphelper/sequenceashashmap.hxx> +#include <rtl/ustrbuf.hxx> +#include <osl/mutex.hxx> +#include <tools/urlobj.hxx> +#include <utility> +#include <vcl/svapp.hxx> + + +using namespace css; +using namespace css::uno; +using namespace css::frame; + +namespace framework{ + +TitleHelper::TitleHelper(css::uno::Reference< css::uno::XComponentContext > xContext, + const css::uno::Reference< css::uno::XInterface >& xOwner, + const css::uno::Reference< css::frame::XUntitledNumbers >& xNumbers) + : ::cppu::BaseMutex () + , m_xContext (std::move(xContext)) + , m_bExternalTitle (false) + , m_nLeasedNumber (css::frame::UntitledNumbersConst::INVALID_NUMBER) + , m_aListener (m_aMutex) +{ + // SYNCHRONIZED -> + { + osl::MutexGuard aLock(m_aMutex); + + m_xOwner = xOwner; + m_xUntitledNumbers = xNumbers; + } + // <- SYNCHRONIZED + + css::uno::Reference< css::frame::XModel > xModel(xOwner, css::uno::UNO_QUERY); + if (xModel.is ()) + { + impl_startListeningForModel (xModel); + return; + } + + css::uno::Reference< css::frame::XController > xController(xOwner, css::uno::UNO_QUERY); + if (xController.is ()) + { + impl_startListeningForController (xController); + return; + } + + css::uno::Reference< css::frame::XFrame > xFrame(xOwner, css::uno::UNO_QUERY); + if (xFrame.is ()) + { + impl_startListeningForFrame (xFrame); + return; + } +} + +TitleHelper::~TitleHelper() +{ +} + +OUString SAL_CALL TitleHelper::getTitle() +{ + // SYNCHRONIZED -> + osl::MutexGuard aLock(m_aMutex); + + // An external title will win always and disable all internal logic about + // creating/using a title value. + // Even an empty string will be accepted as valid title ! + if (m_bExternalTitle) + return m_sTitle; + + // Title seems to be up-to-date. Return it directly. + if (!m_sTitle.isEmpty()) + return m_sTitle; + + // Title seems to be unused till now ... do bootstraping + impl_updateTitle (true); + + return m_sTitle; + // <- SYNCHRONIZED +} + +void SAL_CALL TitleHelper::setTitle(const OUString& sTitle) +{ + // SYNCHRONIZED -> + { + osl::MutexGuard aLock(m_aMutex); + + m_bExternalTitle = true; + m_sTitle = sTitle; + } + // <- SYNCHRONIZED + + impl_sendTitleChangedEvent (); +} + +void SAL_CALL TitleHelper::addTitleChangeListener(const css::uno::Reference< css::frame::XTitleChangeListener >& xListener) +{ + // container is threadsafe by himself + m_aListener.addInterface( cppu::UnoType<css::frame::XTitleChangeListener>::get(), xListener ); +} + +void SAL_CALL TitleHelper::removeTitleChangeListener(const css::uno::Reference< css::frame::XTitleChangeListener >& xListener) +{ + // container is threadsafe by himself + m_aListener.removeInterface( cppu::UnoType<css::frame::XTitleChangeListener>::get(), xListener ); +} + +void SAL_CALL TitleHelper::titleChanged(const css::frame::TitleChangedEvent& aEvent) +{ + css::uno::Reference< css::frame::XTitle > xSubTitle; + // SYNCHRONIZED -> + { + osl::MutexGuard aLock(m_aMutex); + + xSubTitle.set(m_xSubTitle.get (), css::uno::UNO_QUERY); + } + // <- SYNCHRONIZED + + if (aEvent.Source != xSubTitle) + return; + + impl_updateTitle (); +} + +void SAL_CALL TitleHelper::documentEventOccured(const css::document::DocumentEvent& aEvent) +{ + if ( ! aEvent.EventName.equalsIgnoreAsciiCase("OnSaveAsDone") + && ! aEvent.EventName.equalsIgnoreAsciiCase("OnModeChanged") + && ! aEvent.EventName.equalsIgnoreAsciiCase("OnTitleChanged")) + return; + + css::uno::Reference< css::frame::XModel > xOwner; + // SYNCHRONIZED -> + { + osl::MutexGuard aLock(m_aMutex); + + xOwner.set(m_xOwner.get (), css::uno::UNO_QUERY); + } + // <- SYNCHRONIZED + + if (aEvent.Source != xOwner + || ((aEvent.EventName.equalsIgnoreAsciiCase("OnModeChanged") + || aEvent.EventName.equalsIgnoreAsciiCase("OnTitleChanged")) + && !xOwner.is())) + { + return; + } + + impl_updateTitle (); +} + +void SAL_CALL TitleHelper::frameAction(const css::frame::FrameActionEvent& aEvent) +{ + css::uno::Reference< css::frame::XFrame > xOwner; + // SYNCHRONIZED -> + { + osl::MutexGuard aLock(m_aMutex); + + xOwner.set(m_xOwner.get (), css::uno::UNO_QUERY); + } + // <- SYNCHRONIZED + + if (aEvent.Source != xOwner) + return; + + // we are interested on events only, which must trigger a title bar update + // because component was changed. + if ( + (aEvent.Action == css::frame::FrameAction_COMPONENT_ATTACHED ) || + (aEvent.Action == css::frame::FrameAction_COMPONENT_REATTACHED) || + (aEvent.Action == css::frame::FrameAction_COMPONENT_DETACHING ) + ) + { + impl_updateListeningForFrame (xOwner); + impl_updateTitle (); + } +} + +void SAL_CALL TitleHelper::disposing(const css::lang::EventObject& aEvent) +{ + css::uno::Reference< css::uno::XInterface > xOwner; + css::uno::Reference< css::frame::XUntitledNumbers > xNumbers; + ::sal_Int32 nLeasedNumber; + // SYNCHRONIZED -> + { + osl::MutexGuard aLock(m_aMutex); + + xOwner = m_xOwner; + xNumbers.set(m_xUntitledNumbers.get(), css::uno::UNO_QUERY); + nLeasedNumber = m_nLeasedNumber; + } + // <- SYNCHRONIZED + + if ( ! xOwner.is ()) + return; + + css::uno::Reference< css::frame::XFrame > xFrame(xOwner, css::uno::UNO_QUERY); + if (xFrame.is()) + xFrame->removeFrameActionListener(this); + + if (xOwner != aEvent.Source) + return; + + if ( + (xNumbers.is () ) && + (nLeasedNumber != css::frame::UntitledNumbersConst::INVALID_NUMBER) + ) + xNumbers->releaseNumber (nLeasedNumber); + + // SYNCHRONIZED -> + { + osl::MutexGuard aLock(m_aMutex); + + m_xOwner.clear(); + m_sTitle = OUString (); + m_nLeasedNumber = css::frame::UntitledNumbersConst::INVALID_NUMBER; + } + // <- SYNCHRONIZED +} + +void TitleHelper::impl_sendTitleChangedEvent () +{ + css::uno::Reference<css::uno::XInterface> xOwner; + // SYNCHRONIZED -> + { + osl::MutexGuard aLock(m_aMutex); + + xOwner = m_xOwner; + } + // <- SYNCHRONIZED + + css::frame::TitleChangedEvent aEvent(xOwner, m_sTitle); + + if( ! aEvent.Source.is() ) + return; + + comphelper::OInterfaceContainerHelper2* pContainer = m_aListener.getContainer( cppu::UnoType<css::frame::XTitleChangeListener>::get()); + if ( ! pContainer) + return; + + comphelper::OInterfaceIteratorHelper2 pIt( *pContainer ); + while ( pIt.hasMoreElements() ) + { + try + { + static_cast<css::frame::XTitleChangeListener*>(pIt.next())->titleChanged( aEvent ); + } + catch(const css::uno::Exception&) + { + pIt.remove(); + } + } +} + +void TitleHelper::impl_updateTitle (bool init) +{ + css::uno::Reference< css::frame::XModel3 > xModel; + css::uno::Reference< css::frame::XController > xController; + css::uno::Reference< css::frame::XFrame > xFrame; + // SYNCHRONIZED -> + { + osl::MutexGuard aLock(m_aMutex); + + xModel.set (m_xOwner.get(), css::uno::UNO_QUERY); + xController.set(m_xOwner.get(), css::uno::UNO_QUERY); + xFrame.set (m_xOwner.get(), css::uno::UNO_QUERY); + } + // <- SYNCHRONIZED + + if (xModel.is ()) + { + impl_updateTitleForModel (xModel, init); + } + else if (xController.is ()) + { + impl_updateTitleForController (xController, init); + } + else if (xFrame.is ()) + { + impl_updateTitleForFrame (xFrame, init); + } +} + +void TitleHelper::impl_updateTitleForModel (const css::uno::Reference< css::frame::XModel3 >& xModel, bool init) +{ + css::uno::Reference< css::uno::XInterface > xOwner; + css::uno::Reference< css::frame::XUntitledNumbers > xNumbers; + ::sal_Int32 nLeasedNumber; + // SYNCHRONIZED -> + { + osl::MutexGuard aLock(m_aMutex); + + // external title won't be updated internally! + // It has to be set from outside new. + if (m_bExternalTitle) + return; + + xOwner = m_xOwner; + xNumbers.set (m_xUntitledNumbers.get(), css::uno::UNO_QUERY); + nLeasedNumber = m_nLeasedNumber; + } + // <- SYNCHRONIZED + + if ( + ( ! xOwner.is ()) || + ( ! xNumbers.is ()) || + ( ! xModel.is ()) + ) + return; + + OUString sTitle; + OUString sURL; + + css::uno::Reference< css::frame::XStorable > xURLProvider(xModel , css::uno::UNO_QUERY); + if (xURLProvider.is()) + sURL = xURLProvider->getLocation (); + + utl::MediaDescriptor aDescriptor(xModel->getArgs2( { utl::MediaDescriptor::PROP_SUGGESTEDSAVEASNAME } )); + const OUString sSuggestedSaveAsName = aDescriptor.getUnpackedValueOrDefault( + utl::MediaDescriptor::PROP_SUGGESTEDSAVEASNAME, OUString()); + + if (!sURL.isEmpty()) + { + sTitle = impl_convertURL2Title(sURL); + if (nLeasedNumber != css::frame::UntitledNumbersConst::INVALID_NUMBER) + xNumbers->releaseNumber (nLeasedNumber); + nLeasedNumber = css::frame::UntitledNumbersConst::INVALID_NUMBER; + } + else if (!sSuggestedSaveAsName.isEmpty()) + { + // tdf#121537 Use suggested save as name for title if file has not yet been saved + sTitle = sSuggestedSaveAsName; + } + else + { + if (nLeasedNumber == css::frame::UntitledNumbersConst::INVALID_NUMBER) + nLeasedNumber = xNumbers->leaseNumber (xOwner); + + if (nLeasedNumber != css::frame::UntitledNumbersConst::INVALID_NUMBER) + sTitle = xNumbers->getUntitledPrefix() + OUString::number(nLeasedNumber); + else + sTitle = xNumbers->getUntitledPrefix() + "?"; + } + + bool bChanged; + // SYNCHRONIZED -> + { + osl::MutexGuard aLock(m_aMutex); + + // WORKAROUND: the notification is currently sent always, + // can be changed after shared mode is supported per UNO API + bChanged = !init; // && m_sTitle != sTitle + + m_sTitle = sTitle; + m_nLeasedNumber = nLeasedNumber; + } + // <- SYNCHRONIZED + + if (bChanged) + impl_sendTitleChangedEvent (); +} + +void TitleHelper::impl_updateTitleForController (const css::uno::Reference< css::frame::XController >& xController, bool init) +{ + css::uno::Reference< css::uno::XInterface > xOwner; + css::uno::Reference< css::frame::XUntitledNumbers > xNumbers; + ::sal_Int32 nLeasedNumber; + // SYNCHRONIZED -> + { + osl::MutexGuard aLock(m_aMutex); + + // external title won't be updated internally! + // It has to be set from outside new. + if (m_bExternalTitle) + return; + + xOwner = m_xOwner; + xNumbers.set (m_xUntitledNumbers.get(), css::uno::UNO_QUERY); + nLeasedNumber = m_nLeasedNumber; + } + // <- SYNCHRONIZED + + if ( + ( ! xOwner.is ()) || + ( ! xNumbers.is ()) || + ( ! xController.is ()) + ) + return; + + OUStringBuffer sTitle(256); + + if (nLeasedNumber == css::frame::UntitledNumbersConst::INVALID_NUMBER) + nLeasedNumber = xNumbers->leaseNumber (xOwner); + + css::uno::Reference< css::frame::XTitle > xModelTitle(xController->getModel (), css::uno::UNO_QUERY); + css::uno::Reference< css::frame::XModel > xModel = xController->getModel (); + if (!xModelTitle.is ()) + xModelTitle.set(xController, css::uno::UNO_QUERY); + if (xModelTitle.is ()) + { + sTitle.append (xModelTitle->getTitle ()); + if ( nLeasedNumber > 1 ) + { + sTitle.append(" : "); + sTitle.append(nLeasedNumber); + } + if (xModel.is ()) + { + INetURLObject aURL (xModel->getURL ()); + if (aURL.GetProtocol () != INetProtocol::File + && aURL.GetProtocol () != INetProtocol::NotValid) + { + OUString sRemoteText (FwkResId (STR_REMOTE_TITLE)); + sTitle.append (sRemoteText); + } + } + } + else + { + sTitle.append (xNumbers->getUntitledPrefix ()); + if ( nLeasedNumber > 1 ) + { + sTitle.append(nLeasedNumber ); + } + } + + bool bChanged; + // SYNCHRONIZED -> + { + osl::MutexGuard aLock(m_aMutex); + + OUString sNewTitle = sTitle.makeStringAndClear (); + bChanged = !init && m_sTitle != sNewTitle; + m_sTitle = sNewTitle; + m_nLeasedNumber = nLeasedNumber; + } + // <- SYNCHRONIZED + + if (bChanged) + impl_sendTitleChangedEvent (); +} + +void TitleHelper::impl_updateTitleForFrame (const css::uno::Reference< css::frame::XFrame >& xFrame, bool init) +{ + if ( ! xFrame.is ()) + return; + + // SYNCHRONIZED -> + { + osl::MutexGuard aLock(m_aMutex); + + // external title won't be updated internally! + // It has to be set from outside new. + if (m_bExternalTitle) + return; + } + // <- SYNCHRONIZED + + css::uno::Reference< css::uno::XInterface > xComponent = xFrame->getController (); + if ( ! xComponent.is ()) + xComponent = xFrame->getComponentWindow (); + + OUStringBuffer sTitle (256); + + impl_appendComponentTitle (sTitle, xComponent); +#ifndef MACOSX + // fdo#70376: We want the window title to contain just the + // document name (from the above "component title"). + impl_appendProductName (sTitle); + impl_appendModuleName (sTitle); + impl_appendDebugVersion (sTitle); +#endif + impl_appendSafeMode (sTitle); + + bool bChanged; + // SYNCHRONIZED -> + { + osl::MutexGuard aLock(m_aMutex); + + OUString sNewTitle = sTitle.makeStringAndClear (); + bChanged = !init && m_sTitle != sNewTitle; + m_sTitle = sNewTitle; + } + // <- SYNCHRONIZED + + if (bChanged) + impl_sendTitleChangedEvent (); +} + +void TitleHelper::impl_appendComponentTitle ( OUStringBuffer& sTitle , + const css::uno::Reference< css::uno::XInterface >& xComponent) +{ + css::uno::Reference< css::frame::XTitle > xTitle(xComponent, css::uno::UNO_QUERY); + + // Note: Title has to be used (even if it's empty) if the right interface is supported. + if (xTitle.is ()) + sTitle.append (xTitle->getTitle ()); +} + +void TitleHelper::impl_appendProductName (OUStringBuffer& sTitle) +{ + OUString name(utl::ConfigManager::getProductName()); + if (!name.isEmpty()) + { + if (!sTitle.isEmpty()) + sTitle.append(" - "); + sTitle.append(name); + } +} + +void TitleHelper::impl_appendModuleName (OUStringBuffer& sTitle) +{ + css::uno::Reference< css::uno::XInterface > xOwner; + css::uno::Reference< css::uno::XComponentContext > xContext; + // SYNCHRONIZED -> + { + osl::MutexGuard aLock(m_aMutex); + + xOwner = m_xOwner.get(); + xContext = m_xContext; + } + // <- SYNCHRONIZED + + try + { + css::uno::Reference< css::frame::XModuleManager2 > xModuleManager = + css::frame::ModuleManager::create(xContext); + + const OUString sID = xModuleManager->identify(xOwner); + ::comphelper::SequenceAsHashMap lProps = xModuleManager->getByName (sID); + const OUString sUIName = lProps.getUnpackedValueOrDefault (OFFICEFACTORY_PROPNAME_ASCII_UINAME, OUString()); + + // An UIname property is an optional value ! + // So please add it to the title in case it does really exists only. + if (!sUIName.isEmpty()) + { + sTitle.append (" " ); + sTitle.append (sUIName); + } + } + catch(const css::uno::Exception&) + {} +} + +#ifdef DBG_UTIL +void TitleHelper::impl_appendDebugVersion (OUStringBuffer& sTitle) +{ + OUString version(utl::ConfigManager::getProductVersion()); + sTitle.append(' '); + sTitle.append(version); + OUString sVersion = ::utl::Bootstrap::getBuildIdData("development"); + sTitle.append(" ["); + sTitle.append(sVersion); + sTitle.append("]"); +} +#else +void TitleHelper::impl_appendDebugVersion (OUStringBuffer&) +{ +} +#endif + +void TitleHelper::impl_appendSafeMode (OUStringBuffer& sTitle) +{ + if (Application::IsSafeModeEnabled()) + sTitle.append(FwkResId (STR_SAFEMODE_TITLE)); +} + +void TitleHelper::impl_startListeningForModel (const css::uno::Reference< css::frame::XModel >& xModel) +{ + css::uno::Reference< css::document::XDocumentEventBroadcaster > xBroadcaster(xModel, css::uno::UNO_QUERY); + if ( ! xBroadcaster.is ()) + return; + + xBroadcaster->addDocumentEventListener (static_cast< css::document::XDocumentEventListener* >(this)); +} + +void TitleHelper::impl_startListeningForController (const css::uno::Reference< css::frame::XController >& xController) +{ + xController->addEventListener (static_cast< css::lang::XEventListener* > (static_cast< css::frame::XFrameActionListener* > (this) ) ); + css::uno::Reference< css::frame::XTitle > xSubTitle(xController->getModel (), css::uno::UNO_QUERY); + impl_setSubTitle (xSubTitle); +} + +void TitleHelper::impl_startListeningForFrame (const css::uno::Reference< css::frame::XFrame >& xFrame) +{ + xFrame->addFrameActionListener(this ); + impl_updateListeningForFrame (xFrame); +} + +void TitleHelper::impl_updateListeningForFrame (const css::uno::Reference< css::frame::XFrame >& xFrame) +{ + css::uno::Reference< css::frame::XTitle > xSubTitle(xFrame->getController (), css::uno::UNO_QUERY); + impl_setSubTitle (xSubTitle); +} + +void TitleHelper::impl_setSubTitle (const css::uno::Reference< css::frame::XTitle >& xSubTitle) +{ + css::uno::Reference< css::frame::XTitle > xOldSubTitle; + // SYNCHRONIZED -> + { + osl::MutexGuard aLock(m_aMutex); + + // ignore duplicate calls. Makes outside using of this helper more easy :-) + xOldSubTitle.set(m_xSubTitle.get(), css::uno::UNO_QUERY); + if (xOldSubTitle == xSubTitle) + return; + + m_xSubTitle = xSubTitle; + } + // <- SYNCHRONIZED + + css::uno::Reference< css::frame::XTitleChangeBroadcaster > xOldBroadcaster(xOldSubTitle , css::uno::UNO_QUERY ); + css::uno::Reference< css::frame::XTitleChangeBroadcaster > xNewBroadcaster(xSubTitle , css::uno::UNO_QUERY ); + css::uno::Reference< css::frame::XTitleChangeListener > xThis(this); + + if (xOldBroadcaster.is()) + xOldBroadcaster->removeTitleChangeListener (xThis); + + if (xNewBroadcaster.is()) + xNewBroadcaster->addTitleChangeListener (xThis); +} + +OUString TitleHelper::impl_convertURL2Title(std::u16string_view sURL) +{ + INetURLObject aURL (sURL); + OUString sTitle; + + if (aURL.GetProtocol() == INetProtocol::File) + { + if (aURL.HasMark()) + aURL = INetURLObject(aURL.GetURLNoMark()); + + sTitle = aURL.getName(INetURLObject::LAST_SEGMENT, true, INetURLObject::DecodeMechanism::WithCharset); + } + else + { + if (aURL.hasExtension()) + sTitle = aURL.getName(INetURLObject::LAST_SEGMENT, true, INetURLObject::DecodeMechanism::WithCharset); + + if ( sTitle.isEmpty() ) + sTitle = aURL.GetHostPort(INetURLObject::DecodeMechanism::WithCharset); + + if ( sTitle.isEmpty() ) + sTitle = aURL.GetURLNoPass(INetURLObject::DecodeMechanism::WithCharset); + } + + return sTitle; +} + +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/fwe/helper/undomanagerhelper.cxx b/framework/source/fwe/helper/undomanagerhelper.cxx new file mode 100644 index 000000000..779fe02e6 --- /dev/null +++ b/framework/source/fwe/helper/undomanagerhelper.cxx @@ -0,0 +1,1056 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <framework/undomanagerhelper.hxx> +#include <framework/imutex.hxx> + +#include <com/sun/star/document/EmptyUndoStackException.hpp> +#include <com/sun/star/document/UndoContextNotClosedException.hpp> +#include <com/sun/star/document/UndoFailedException.hpp> +#include <com/sun/star/document/XUndoManager.hpp> +#include <com/sun/star/lang/XComponent.hpp> +#include <com/sun/star/util/InvalidStateException.hpp> +#include <com/sun/star/util/NotLockedException.hpp> +#include <com/sun/star/util/XModifyListener.hpp> + +#include <comphelper/interfacecontainer3.hxx> +#include <cppuhelper/exc_hlp.hxx> +#include <comphelper/flagguard.hxx> +#include <comphelper/asyncnotification.hxx> +#include <svl/undo.hxx> +#include <tools/diagnose_ex.h> +#include <osl/conditn.hxx> + +#include <functional> +#include <mutex> +#include <stack> +#include <queue> +#include <utility> + +namespace framework +{ + + using ::com::sun::star::uno::Reference; + using ::com::sun::star::uno::XInterface; + using ::com::sun::star::uno::UNO_QUERY; + using ::com::sun::star::uno::Exception; + using ::com::sun::star::uno::RuntimeException; + using ::com::sun::star::uno::Any; + using ::com::sun::star::uno::Sequence; + using ::com::sun::star::document::XUndoManagerListener; + using ::com::sun::star::document::UndoManagerEvent; + using ::com::sun::star::document::EmptyUndoStackException; + using ::com::sun::star::document::UndoContextNotClosedException; + using ::com::sun::star::document::UndoFailedException; + using ::com::sun::star::util::NotLockedException; + using ::com::sun::star::lang::EventObject; + using ::com::sun::star::document::XUndoAction; + using ::com::sun::star::lang::XComponent; + using ::com::sun::star::document::XUndoManager; + using ::com::sun::star::util::InvalidStateException; + using ::com::sun::star::lang::IllegalArgumentException; + using ::com::sun::star::util::XModifyListener; + + //= UndoActionWrapper + + namespace { + + class UndoActionWrapper : public SfxUndoAction + { + public: + explicit UndoActionWrapper( + Reference< XUndoAction > const& i_undoAction + ); + virtual ~UndoActionWrapper() override; + + virtual OUString GetComment() const override; + virtual void Undo() override; + virtual void Redo() override; + virtual bool CanRepeat(SfxRepeatTarget&) const override; + + private: + const Reference< XUndoAction > m_xUndoAction; + }; + + } + + UndoActionWrapper::UndoActionWrapper( Reference< XUndoAction > const& i_undoAction ) + : m_xUndoAction( i_undoAction ) + { + ENSURE_OR_THROW( m_xUndoAction.is(), "illegal undo action" ); + } + + UndoActionWrapper::~UndoActionWrapper() + { + try + { + Reference< XComponent > xComponent( m_xUndoAction, UNO_QUERY ); + if ( xComponent.is() ) + xComponent->dispose(); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("fwk"); + } + } + + OUString UndoActionWrapper::GetComment() const + { + OUString sComment; + try + { + sComment = m_xUndoAction->getTitle(); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("fwk"); + } + return sComment; + } + + void UndoActionWrapper::Undo() + { + m_xUndoAction->undo(); + } + + void UndoActionWrapper::Redo() + { + m_xUndoAction->redo(); + } + + bool UndoActionWrapper::CanRepeat(SfxRepeatTarget&) const + { + return false; + } + + //= UndoManagerRequest + + namespace { + + class UndoManagerRequest : public ::comphelper::AnyEvent + { + public: + explicit UndoManagerRequest( ::std::function<void ()> i_request ) + :m_request(std::move( i_request )) + { + m_finishCondition.reset(); + } + + void execute() + { + try + { + m_request(); + } + catch( const Exception& ) + { + m_caughtException = ::cppu::getCaughtException(); + } + m_finishCondition.set(); + } + + void wait() + { + m_finishCondition.wait(); + if ( m_caughtException.hasValue() ) + ::cppu::throwException( m_caughtException ); + } + + void cancel( const Reference< XInterface >& i_context ) + { + m_caughtException <<= RuntimeException( + "Concurrency error: an earlier operation on the stack failed.", + i_context + ); + m_finishCondition.set(); + } + + protected: + virtual ~UndoManagerRequest() override + { + } + + private: + ::std::function<void ()> m_request; + Any m_caughtException; + ::osl::Condition m_finishCondition; + }; + + } + + //= UndoManagerHelper_Impl + + class UndoManagerHelper_Impl : public SfxUndoListener + { + private: + ::osl::Mutex m_aMutex; + std::mutex m_aQueueMutex; + bool m_bAPIActionRunning; + bool m_bProcessingEvents; + sal_Int32 m_nLockCount; + ::comphelper::OInterfaceContainerHelper3<XUndoManagerListener> m_aUndoListeners; + ::comphelper::OInterfaceContainerHelper3<XModifyListener> m_aModifyListeners; + IUndoManagerImplementation& m_rUndoManagerImplementation; + ::std::stack< bool > m_aContextVisibilities; +#if OSL_DEBUG_LEVEL > 0 + bool m_bContextAPIFlagsEverPushed = {false}; + ::std::stack< bool > m_aContextAPIFlags; +#endif + ::std::queue< ::rtl::Reference< UndoManagerRequest > > + m_aEventQueue; + + public: + ::osl::Mutex& getMutex() { return m_aMutex; } + + public: + explicit UndoManagerHelper_Impl( IUndoManagerImplementation& i_undoManagerImpl ) + :m_bAPIActionRunning( false ) + ,m_bProcessingEvents( false ) + ,m_nLockCount( 0 ) + ,m_aUndoListeners( m_aMutex ) + ,m_aModifyListeners( m_aMutex ) + ,m_rUndoManagerImplementation( i_undoManagerImpl ) + { + getUndoManager().AddUndoListener( *this ); + } + + virtual ~UndoManagerHelper_Impl() + { + } + + SfxUndoManager& getUndoManager() const + { + return m_rUndoManagerImplementation.getImplUndoManager(); + } + + Reference< XUndoManager > getXUndoManager() const + { + return m_rUndoManagerImplementation.getThis(); + } + + // SfxUndoListener + virtual void actionUndone( const OUString& i_actionComment ) override; + virtual void actionRedone( const OUString& i_actionComment ) override; + virtual void undoActionAdded( const OUString& i_actionComment ) override; + virtual void cleared() override; + virtual void clearedRedo() override; + virtual void resetAll() override; + virtual void listActionEntered( const OUString& i_comment ) override; + virtual void listActionLeft( const OUString& i_comment ) override; + virtual void listActionCancelled() override; + + // public operations + void disposing(); + + void enterUndoContext( const OUString& i_title, const bool i_hidden, IMutexGuard& i_instanceLock ); + void leaveUndoContext( IMutexGuard& i_instanceLock ); + void addUndoAction( const Reference< XUndoAction >& i_action, IMutexGuard& i_instanceLock ); + void undo( IMutexGuard& i_instanceLock ); + void redo( IMutexGuard& i_instanceLock ); + void clear( IMutexGuard& i_instanceLock ); + void clearRedo( IMutexGuard& i_instanceLock ); + void reset( IMutexGuard& i_instanceLock ); + + void lock(); + void unlock(); + + void addUndoManagerListener( const Reference< XUndoManagerListener >& i_listener ) + { + m_aUndoListeners.addInterface( i_listener ); + } + + void removeUndoManagerListener( const Reference< XUndoManagerListener >& i_listener ) + { + m_aUndoListeners.removeInterface( i_listener ); + } + + void addModifyListener( const Reference< XModifyListener >& i_listener ) + { + m_aModifyListeners.addInterface( i_listener ); + } + + void removeModifyListener( const Reference< XModifyListener >& i_listener ) + { + m_aModifyListeners.removeInterface( i_listener ); + } + + UndoManagerEvent + buildEvent( OUString const& i_title ) const; + + void impl_notifyModified(); + void notify( OUString const& i_title, + void ( SAL_CALL XUndoManagerListener::*i_notificationMethod )( const UndoManagerEvent& ) + ); + void notify( void ( SAL_CALL XUndoManagerListener::*i_notificationMethod )( const EventObject& ) ); + + private: + /// adds a function to be called to the request processor's queue + void impl_processRequest(::std::function<void ()> const& i_request, IMutexGuard& i_instanceLock); + + /// impl-versions of the XUndoManager API. + void impl_enterUndoContext( const OUString& i_title, const bool i_hidden ); + void impl_leaveUndoContext(); + void impl_addUndoAction( const Reference< XUndoAction >& i_action ); + void impl_doUndoRedo( IMutexGuard& i_externalLock, const bool i_undo ); + void impl_clear(); + void impl_clearRedo(); + void impl_reset(); + }; + + void UndoManagerHelper_Impl::disposing() + { + EventObject aEvent; + aEvent.Source = getXUndoManager(); + m_aUndoListeners.disposeAndClear( aEvent ); + m_aModifyListeners.disposeAndClear( aEvent ); + + ::osl::MutexGuard aGuard( m_aMutex ); + + getUndoManager().RemoveUndoListener( *this ); + } + + UndoManagerEvent UndoManagerHelper_Impl::buildEvent( OUString const& i_title ) const + { + UndoManagerEvent aEvent; + aEvent.Source = getXUndoManager(); + aEvent.UndoActionTitle = i_title; + aEvent.UndoContextDepth = getUndoManager().GetListActionDepth(); + return aEvent; + } + + void UndoManagerHelper_Impl::impl_notifyModified() + { + const EventObject aEvent( getXUndoManager() ); + m_aModifyListeners.notifyEach( &XModifyListener::modified, aEvent ); + } + + void UndoManagerHelper_Impl::notify( OUString const& i_title, + void ( SAL_CALL XUndoManagerListener::*i_notificationMethod )( const UndoManagerEvent& ) ) + { + const UndoManagerEvent aEvent( buildEvent( i_title ) ); + + // TODO: this notification method here is used by UndoManagerHelper_Impl, to multiplex the notifications we + // receive from the SfxUndoManager. Those notifications are sent with a locked SolarMutex, which means + // we're doing the multiplexing here with a locked SM, too. Which is Bad (TM). + // Fixing this properly would require outsourcing all the notifications into an own thread - which might lead + // to problems of its own, since clients might expect synchronous notifications. + + m_aUndoListeners.notifyEach( i_notificationMethod, aEvent ); + impl_notifyModified(); + } + + void UndoManagerHelper_Impl::notify( void ( SAL_CALL XUndoManagerListener::*i_notificationMethod )( const EventObject& ) ) + { + const EventObject aEvent( getXUndoManager() ); + + // TODO: the same comment as in the other notify, regarding SM locking applies here ... + + m_aUndoListeners.notifyEach( i_notificationMethod, aEvent ); + impl_notifyModified(); + } + + void UndoManagerHelper_Impl::enterUndoContext( const OUString& i_title, const bool i_hidden, IMutexGuard& i_instanceLock ) + { + impl_processRequest( + [this, &i_title, i_hidden] () { return this->impl_enterUndoContext(i_title, i_hidden); }, + i_instanceLock + ); + } + + void UndoManagerHelper_Impl::leaveUndoContext( IMutexGuard& i_instanceLock ) + { + impl_processRequest( + [this] () { return this->impl_leaveUndoContext(); }, + i_instanceLock + ); + } + + void UndoManagerHelper_Impl::addUndoAction( const Reference< XUndoAction >& i_action, IMutexGuard& i_instanceLock ) + { + if ( !i_action.is() ) + throw IllegalArgumentException( + "illegal undo action object", + getXUndoManager(), + 1 + ); + + impl_processRequest( + [this, &i_action] () { return this->impl_addUndoAction(i_action); }, + i_instanceLock + ); + } + + void UndoManagerHelper_Impl::clear( IMutexGuard& i_instanceLock ) + { + impl_processRequest( + [this] () { return this->impl_clear(); }, + i_instanceLock + ); + } + + void UndoManagerHelper_Impl::clearRedo( IMutexGuard& i_instanceLock ) + { + impl_processRequest( + [this] () { return this->impl_clearRedo(); }, + i_instanceLock + ); + } + + void UndoManagerHelper_Impl::reset( IMutexGuard& i_instanceLock ) + { + impl_processRequest( + [this] () { return this->impl_reset(); }, + i_instanceLock + ); + } + + void UndoManagerHelper_Impl::lock() + { + // SYNCHRONIZED ---> + ::osl::MutexGuard aGuard( getMutex() ); + + if ( ++m_nLockCount == 1 ) + { + SfxUndoManager& rUndoManager = getUndoManager(); + rUndoManager.EnableUndo( false ); + } + // <--- SYNCHRONIZED + } + + void UndoManagerHelper_Impl::unlock() + { + // SYNCHRONIZED ---> + ::osl::MutexGuard aGuard( getMutex() ); + + if ( m_nLockCount == 0 ) + throw NotLockedException( "Undo manager is not locked", getXUndoManager() ); + + if ( --m_nLockCount == 0 ) + { + SfxUndoManager& rUndoManager = getUndoManager(); + rUndoManager.EnableUndo( true ); + } + // <--- SYNCHRONIZED + } + + void UndoManagerHelper_Impl::impl_processRequest(::std::function<void ()> const& i_request, IMutexGuard& i_instanceLock) + { + // create the request, and add it to our queue + ::rtl::Reference< UndoManagerRequest > pRequest( new UndoManagerRequest( i_request ) ); + { + std::unique_lock aQueueGuard( m_aQueueMutex ); + m_aEventQueue.push( pRequest ); + } + + i_instanceLock.clear(); + + if ( m_bProcessingEvents ) + { + // another thread is processing the event queue currently => it will also process the event which we just added + pRequest->wait(); + return; + } + + m_bProcessingEvents = true; + do + { + pRequest.clear(); + { + std::unique_lock aQueueGuard( m_aQueueMutex ); + if ( m_aEventQueue.empty() ) + { + // reset the flag before releasing the queue mutex, otherwise it's possible that another thread + // could add an event after we release the mutex, but before we reset the flag. If then this other + // thread checks the flag before be reset it, this thread's event would starve. + m_bProcessingEvents = false; + return; + } + pRequest = m_aEventQueue.front(); + m_aEventQueue.pop(); + } + try + { + pRequest->execute(); + pRequest->wait(); + } + catch( ... ) + { + { + // no chance to process further requests, if the current one failed + // => discard them + std::unique_lock aQueueGuard( m_aQueueMutex ); + while ( !m_aEventQueue.empty() ) + { + pRequest = m_aEventQueue.front(); + m_aEventQueue.pop(); + pRequest->cancel( getXUndoManager() ); + } + m_bProcessingEvents = false; + } + // re-throw the error + throw; + } + } + while ( true ); + } + + void UndoManagerHelper_Impl::impl_enterUndoContext( const OUString& i_title, const bool i_hidden ) + { + // SYNCHRONIZED ---> + ::osl::ClearableMutexGuard aGuard( m_aMutex ); + + SfxUndoManager& rUndoManager = getUndoManager(); + if ( !rUndoManager.IsUndoEnabled() ) + // ignore this request if the manager is locked + return; + + if ( i_hidden && ( rUndoManager.GetUndoActionCount() == 0 ) ) + throw EmptyUndoStackException( + "can't enter a hidden context without a previous Undo action", + m_rUndoManagerImplementation.getThis() + ); + + { + ::comphelper::FlagGuard aNotificationGuard( m_bAPIActionRunning ); + rUndoManager.EnterListAction( i_title, OUString(), 0, ViewShellId(-1) ); + } + + m_aContextVisibilities.push( i_hidden ); + + const UndoManagerEvent aEvent( buildEvent( i_title ) ); + aGuard.clear(); + // <--- SYNCHRONIZED + + m_aUndoListeners.notifyEach( i_hidden ? &XUndoManagerListener::enteredHiddenContext : &XUndoManagerListener::enteredContext, aEvent ); + impl_notifyModified(); + } + + void UndoManagerHelper_Impl::impl_leaveUndoContext() + { + // SYNCHRONIZED ---> + ::osl::ClearableMutexGuard aGuard( m_aMutex ); + + SfxUndoManager& rUndoManager = getUndoManager(); + if ( !rUndoManager.IsUndoEnabled() ) + // ignore this request if the manager is locked + return; + + if ( !rUndoManager.IsInListAction() ) + throw InvalidStateException( + "no active undo context", + getXUndoManager() + ); + + size_t nContextElements = 0; + + const bool isHiddenContext = m_aContextVisibilities.top(); + m_aContextVisibilities.pop(); + + const bool bHadRedoActions = ( rUndoManager.GetRedoActionCount( SfxUndoManager::TopLevel ) > 0 ); + { + ::comphelper::FlagGuard aNotificationGuard( m_bAPIActionRunning ); + if ( isHiddenContext ) + nContextElements = rUndoManager.LeaveAndMergeListAction(); + else + nContextElements = rUndoManager.LeaveListAction(); + } + const bool bHasRedoActions = ( rUndoManager.GetRedoActionCount( SfxUndoManager::TopLevel ) > 0 ); + + // prepare notification + void ( SAL_CALL XUndoManagerListener::*notificationMethod )( const UndoManagerEvent& ) = nullptr; + + UndoManagerEvent aContextEvent( buildEvent( OUString() ) ); + const EventObject aClearedEvent( getXUndoManager() ); + if ( nContextElements == 0 ) + { + notificationMethod = &XUndoManagerListener::cancelledContext; + } + else if ( isHiddenContext ) + { + notificationMethod = &XUndoManagerListener::leftHiddenContext; + } + else + { + aContextEvent.UndoActionTitle = rUndoManager.GetUndoActionComment(); + notificationMethod = &XUndoManagerListener::leftContext; + } + + aGuard.clear(); + // <--- SYNCHRONIZED + + if ( bHadRedoActions && !bHasRedoActions ) + m_aUndoListeners.notifyEach( &XUndoManagerListener::redoActionsCleared, aClearedEvent ); + m_aUndoListeners.notifyEach( notificationMethod, aContextEvent ); + impl_notifyModified(); + } + + void UndoManagerHelper_Impl::impl_doUndoRedo( IMutexGuard& i_externalLock, const bool i_undo ) + { + ::osl::Guard< ::framework::IMutex > aExternalGuard( i_externalLock.getGuardedMutex() ); + // note that this assumes that the mutex has been released in the thread which added the + // Undo/Redo request, so we can successfully acquire it + + // SYNCHRONIZED ---> + ::osl::ClearableMutexGuard aGuard( m_aMutex ); + + SfxUndoManager& rUndoManager = getUndoManager(); + if ( rUndoManager.IsInListAction() ) + throw UndoContextNotClosedException( OUString(), getXUndoManager() ); + + const size_t nElements = i_undo + ? rUndoManager.GetUndoActionCount( SfxUndoManager::TopLevel ) + : rUndoManager.GetRedoActionCount( SfxUndoManager::TopLevel ); + if ( nElements == 0 ) + throw EmptyUndoStackException("stack is empty", getXUndoManager() ); + + aGuard.clear(); + // <--- SYNCHRONIZED + + try + { + if ( i_undo ) + rUndoManager.Undo(); + else + rUndoManager.Redo(); + } + catch( const RuntimeException& ) { /* allowed to leave here */ throw; } + catch( const UndoFailedException& ) { /* allowed to leave here */ throw; } + catch( const Exception& ) + { + // not allowed to leave + const Any aError( ::cppu::getCaughtException() ); + throw UndoFailedException( OUString(), getXUndoManager(), aError ); + } + + // note that in opposite to all of the other methods, we do *not* have our mutex locked when calling + // into the SfxUndoManager implementation. This ensures that an actual XUndoAction::undo/redo is also + // called without our mutex being locked. + // As a consequence, we do not set m_bAPIActionRunning here. Instead, our actionUndone/actionRedone methods + // *always* multiplex the event to our XUndoManagerListeners, not only when m_bAPIActionRunning is FALSE (This + // again is different from all other SfxUndoListener methods). + // So, we do not need to do this notification here ourself. + } + + void UndoManagerHelper_Impl::impl_addUndoAction( const Reference< XUndoAction >& i_action ) + { + // SYNCHRONIZED ---> + ::osl::ClearableMutexGuard aGuard( m_aMutex ); + + SfxUndoManager& rUndoManager = getUndoManager(); + if ( !rUndoManager.IsUndoEnabled() ) + // ignore the request if the manager is locked + return; + + const UndoManagerEvent aEventAdd( buildEvent( i_action->getTitle() ) ); + const EventObject aEventClear( getXUndoManager() ); + + const bool bHadRedoActions = ( rUndoManager.GetRedoActionCount() > 0 ); + { + ::comphelper::FlagGuard aNotificationGuard( m_bAPIActionRunning ); + rUndoManager.AddUndoAction( std::make_unique<UndoActionWrapper>( i_action ) ); + } + const bool bHasRedoActions = ( rUndoManager.GetRedoActionCount() > 0 ); + + aGuard.clear(); + // <--- SYNCHRONIZED + + m_aUndoListeners.notifyEach( &XUndoManagerListener::undoActionAdded, aEventAdd ); + if ( bHadRedoActions && !bHasRedoActions ) + m_aUndoListeners.notifyEach( &XUndoManagerListener::redoActionsCleared, aEventClear ); + impl_notifyModified(); + } + + void UndoManagerHelper_Impl::impl_clear() + { + // SYNCHRONIZED ---> + ::osl::ClearableMutexGuard aGuard( m_aMutex ); + + SfxUndoManager& rUndoManager = getUndoManager(); + if ( rUndoManager.IsInListAction() ) + throw UndoContextNotClosedException( OUString(), getXUndoManager() ); + + { + ::comphelper::FlagGuard aNotificationGuard( m_bAPIActionRunning ); + rUndoManager.Clear(); + } + + const EventObject aEvent( getXUndoManager() ); + aGuard.clear(); + // <--- SYNCHRONIZED + + m_aUndoListeners.notifyEach( &XUndoManagerListener::allActionsCleared, aEvent ); + impl_notifyModified(); + } + + void UndoManagerHelper_Impl::impl_clearRedo() + { + // SYNCHRONIZED ---> + ::osl::ClearableMutexGuard aGuard( m_aMutex ); + + SfxUndoManager& rUndoManager = getUndoManager(); + if ( rUndoManager.IsInListAction() ) + throw UndoContextNotClosedException( OUString(), getXUndoManager() ); + + { + ::comphelper::FlagGuard aNotificationGuard( m_bAPIActionRunning ); + rUndoManager.ClearRedo(); + } + + const EventObject aEvent( getXUndoManager() ); + aGuard.clear(); + // <--- SYNCHRONIZED + + m_aUndoListeners.notifyEach( &XUndoManagerListener::redoActionsCleared, aEvent ); + impl_notifyModified(); + } + + void UndoManagerHelper_Impl::impl_reset() + { + // SYNCHRONIZED ---> + ::osl::ClearableMutexGuard aGuard( m_aMutex ); + + SfxUndoManager& rUndoManager = getUndoManager(); + { + ::comphelper::FlagGuard aNotificationGuard( m_bAPIActionRunning ); + rUndoManager.Reset(); + } + + const EventObject aEvent( getXUndoManager() ); + aGuard.clear(); + // <--- SYNCHRONIZED + + m_aUndoListeners.notifyEach( &XUndoManagerListener::resetAll, aEvent ); + impl_notifyModified(); + } + + void UndoManagerHelper_Impl::actionUndone( const OUString& i_actionComment ) + { + UndoManagerEvent aEvent; + aEvent.Source = getXUndoManager(); + aEvent.UndoActionTitle = i_actionComment; + aEvent.UndoContextDepth = 0; // Undo can happen on level 0 only + m_aUndoListeners.notifyEach( &XUndoManagerListener::actionUndone, aEvent ); + impl_notifyModified(); + } + + void UndoManagerHelper_Impl::actionRedone( const OUString& i_actionComment ) + { + UndoManagerEvent aEvent; + aEvent.Source = getXUndoManager(); + aEvent.UndoActionTitle = i_actionComment; + aEvent.UndoContextDepth = 0; // Redo can happen on level 0 only + m_aUndoListeners.notifyEach( &XUndoManagerListener::actionRedone, aEvent ); + impl_notifyModified(); + } + + void UndoManagerHelper_Impl::undoActionAdded( const OUString& i_actionComment ) + { + if ( m_bAPIActionRunning ) + return; + + notify( i_actionComment, &XUndoManagerListener::undoActionAdded ); + } + + void UndoManagerHelper_Impl::cleared() + { + if ( m_bAPIActionRunning ) + return; + + notify( &XUndoManagerListener::allActionsCleared ); + } + + void UndoManagerHelper_Impl::clearedRedo() + { + if ( m_bAPIActionRunning ) + return; + + notify( &XUndoManagerListener::redoActionsCleared ); + } + + void UndoManagerHelper_Impl::resetAll() + { + if ( m_bAPIActionRunning ) + return; + + notify( &XUndoManagerListener::resetAll ); + } + + void UndoManagerHelper_Impl::listActionEntered( const OUString& i_comment ) + { +#if OSL_DEBUG_LEVEL > 0 + m_aContextAPIFlags.push( m_bAPIActionRunning ); + m_bContextAPIFlagsEverPushed = true; +#endif + + if ( m_bAPIActionRunning ) + return; + + notify( i_comment, &XUndoManagerListener::enteredContext ); + } + + void UndoManagerHelper_Impl::listActionLeft( const OUString& i_comment ) + { +#if OSL_DEBUG_LEVEL > 0 + // It may happen that the very first event listener is added during a + // list action after listActionEntered() was already called, e.g. Calc + // formula calculation event listener during the input of the very + // first formula. Instead of checking m_aContextAPIFlags for empty, + // still assert (on calling top()) other stack mismatches but ignore + // this one case. See tdf#142980 + if (m_bContextAPIFlagsEverPushed) + { + const bool bCurrentContextIsAPIContext = m_aContextAPIFlags.top(); + m_aContextAPIFlags.pop(); + OSL_ENSURE( bCurrentContextIsAPIContext == m_bAPIActionRunning, "UndoManagerHelper_Impl::listActionLeft: API and non-API contexts interwoven!" ); + } +#endif + + if ( m_bAPIActionRunning ) + return; + + notify( i_comment, &XUndoManagerListener::leftContext ); + } + + void UndoManagerHelper_Impl::listActionCancelled() + { +#if OSL_DEBUG_LEVEL > 0 + const bool bCurrentContextIsAPIContext = m_aContextAPIFlags.top(); + m_aContextAPIFlags.pop(); + OSL_ENSURE( bCurrentContextIsAPIContext == m_bAPIActionRunning, "UndoManagerHelper_Impl::listActionCancelled: API and non-API contexts interwoven!" ); +#endif + + if ( m_bAPIActionRunning ) + return; + + notify( OUString(), &XUndoManagerListener::cancelledContext ); + } + + //= UndoManagerHelper + + UndoManagerHelper::UndoManagerHelper( IUndoManagerImplementation& i_undoManagerImpl ) + :m_xImpl( new UndoManagerHelper_Impl( i_undoManagerImpl ) ) + { + } + + UndoManagerHelper::~UndoManagerHelper() + { + } + + void UndoManagerHelper::disposing() + { + m_xImpl->disposing(); + } + + void UndoManagerHelper::enterUndoContext( const OUString& i_title, IMutexGuard& i_instanceLock ) + { + m_xImpl->enterUndoContext( i_title, false, i_instanceLock ); + } + + void UndoManagerHelper::enterHiddenUndoContext( IMutexGuard& i_instanceLock ) + { + m_xImpl->enterUndoContext( OUString(), true, i_instanceLock ); + } + + void UndoManagerHelper::leaveUndoContext( IMutexGuard& i_instanceLock ) + { + m_xImpl->leaveUndoContext( i_instanceLock ); + } + + void UndoManagerHelper_Impl::undo( IMutexGuard& i_instanceLock ) + { + impl_processRequest( + [this, &i_instanceLock] () { return this->impl_doUndoRedo(i_instanceLock, true); }, + i_instanceLock + ); + } + + void UndoManagerHelper_Impl::redo( IMutexGuard& i_instanceLock ) + { + impl_processRequest( + [this, &i_instanceLock] () { return this->impl_doUndoRedo(i_instanceLock, false); }, + i_instanceLock + ); + } + + void UndoManagerHelper::addUndoAction( const Reference< XUndoAction >& i_action, IMutexGuard& i_instanceLock ) + { + m_xImpl->addUndoAction( i_action, i_instanceLock ); + } + + void UndoManagerHelper::undo( IMutexGuard& i_instanceLock ) + { + m_xImpl->undo( i_instanceLock ); + } + + void UndoManagerHelper::redo( IMutexGuard& i_instanceLock ) + { + m_xImpl->redo( i_instanceLock ); + } + + bool UndoManagerHelper::isUndoPossible() const + { + // SYNCHRONIZED ---> + ::osl::MutexGuard aGuard( m_xImpl->getMutex() ); + SfxUndoManager& rUndoManager = m_xImpl->getUndoManager(); + if ( rUndoManager.IsInListAction() ) + return false; + return rUndoManager.GetUndoActionCount( SfxUndoManager::TopLevel ) > 0; + // <--- SYNCHRONIZED + } + + bool UndoManagerHelper::isRedoPossible() const + { + // SYNCHRONIZED ---> + ::osl::MutexGuard aGuard( m_xImpl->getMutex() ); + const SfxUndoManager& rUndoManager = m_xImpl->getUndoManager(); + if ( rUndoManager.IsInListAction() ) + return false; + return rUndoManager.GetRedoActionCount( SfxUndoManager::TopLevel ) > 0; + // <--- SYNCHRONIZED + } + + namespace + { + + OUString lcl_getCurrentActionTitle( UndoManagerHelper_Impl& i_impl, const bool i_undo ) + { + // SYNCHRONIZED ---> + ::osl::MutexGuard aGuard( i_impl.getMutex() ); + + const SfxUndoManager& rUndoManager = i_impl.getUndoManager(); + const size_t nActionCount = i_undo + ? rUndoManager.GetUndoActionCount( SfxUndoManager::TopLevel ) + : rUndoManager.GetRedoActionCount( SfxUndoManager::TopLevel ); + if ( nActionCount == 0 ) + throw EmptyUndoStackException( + i_undo ? OUString( "no action on the undo stack" ) + : OUString( "no action on the redo stack" ), + i_impl.getXUndoManager() + ); + return i_undo + ? rUndoManager.GetUndoActionComment( 0, SfxUndoManager::TopLevel ) + : rUndoManager.GetRedoActionComment( 0, SfxUndoManager::TopLevel ); + // <--- SYNCHRONIZED + } + + Sequence< OUString > lcl_getAllActionTitles( UndoManagerHelper_Impl& i_impl, const bool i_undo ) + { + // SYNCHRONIZED ---> + ::osl::MutexGuard aGuard( i_impl.getMutex() ); + + const SfxUndoManager& rUndoManager = i_impl.getUndoManager(); + const size_t nCount = i_undo + ? rUndoManager.GetUndoActionCount( SfxUndoManager::TopLevel ) + : rUndoManager.GetRedoActionCount( SfxUndoManager::TopLevel ); + + Sequence< OUString > aTitles( nCount ); + auto aTitlesRange = asNonConstRange(aTitles); + for ( size_t i=0; i<nCount; ++i ) + { + aTitlesRange[i] = i_undo + ? rUndoManager.GetUndoActionComment( i, SfxUndoManager::TopLevel ) + : rUndoManager.GetRedoActionComment( i, SfxUndoManager::TopLevel ); + } + return aTitles; + // <--- SYNCHRONIZED + } + } + + OUString UndoManagerHelper::getCurrentUndoActionTitle() const + { + return lcl_getCurrentActionTitle( *m_xImpl, true ); + } + + OUString UndoManagerHelper::getCurrentRedoActionTitle() const + { + return lcl_getCurrentActionTitle( *m_xImpl, false ); + } + + Sequence< OUString > UndoManagerHelper::getAllUndoActionTitles() const + { + return lcl_getAllActionTitles( *m_xImpl, true ); + } + + Sequence< OUString > UndoManagerHelper::getAllRedoActionTitles() const + { + return lcl_getAllActionTitles( *m_xImpl, false ); + } + + void UndoManagerHelper::clear( IMutexGuard& i_instanceLock ) + { + m_xImpl->clear( i_instanceLock ); + } + + void UndoManagerHelper::clearRedo( IMutexGuard& i_instanceLock ) + { + m_xImpl->clearRedo( i_instanceLock ); + } + + void UndoManagerHelper::reset( IMutexGuard& i_instanceLock ) + { + m_xImpl->reset( i_instanceLock ); + } + + void UndoManagerHelper::lock() + { + m_xImpl->lock(); + } + + void UndoManagerHelper::unlock() + { + m_xImpl->unlock(); + } + + bool UndoManagerHelper::isLocked() + { + // SYNCHRONIZED ---> + ::osl::MutexGuard aGuard( m_xImpl->getMutex() ); + + SfxUndoManager& rUndoManager = m_xImpl->getUndoManager(); + return !rUndoManager.IsUndoEnabled(); + // <--- SYNCHRONIZED + } + + void UndoManagerHelper::addUndoManagerListener( const Reference< XUndoManagerListener >& i_listener ) + { + if ( i_listener.is() ) + m_xImpl->addUndoManagerListener( i_listener ); + } + + void UndoManagerHelper::removeUndoManagerListener( const Reference< XUndoManagerListener >& i_listener ) + { + if ( i_listener.is() ) + m_xImpl->removeUndoManagerListener( i_listener ); + } + + void UndoManagerHelper::addModifyListener( const Reference< XModifyListener >& i_listener ) + { + if ( i_listener.is() ) + m_xImpl->addModifyListener( i_listener ); + } + + void UndoManagerHelper::removeModifyListener( const Reference< XModifyListener >& i_listener ) + { + if ( i_listener.is() ) + m_xImpl->removeModifyListener( i_listener ); + } + +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |