summaryrefslogtreecommitdiffstats
path: root/framework/source/fwe/helper
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:06:44 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:06:44 +0000
commited5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch)
tree7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /framework/source/fwe/helper
parentInitial commit. (diff)
downloadlibreoffice-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.cxx376
-rw-r--r--framework/source/fwe/helper/configimporter.cxx74
-rw-r--r--framework/source/fwe/helper/documentundoguard.cxx223
-rw-r--r--framework/source/fwe/helper/propertysetcontainer.cxx158
-rw-r--r--framework/source/fwe/helper/titlehelper.cxx685
-rw-r--r--framework/source/fwe/helper/undomanagerhelper.cxx1056
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: */