summaryrefslogtreecommitdiffstats
path: root/sc/source/ui/vba/vbaeventshelper.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'sc/source/ui/vba/vbaeventshelper.cxx')
-rw-r--r--sc/source/ui/vba/vbaeventshelper.cxx898
1 files changed, 898 insertions, 0 deletions
diff --git a/sc/source/ui/vba/vbaeventshelper.cxx b/sc/source/ui/vba/vbaeventshelper.cxx
new file mode 100644
index 000000000..bd00fdcac
--- /dev/null
+++ b/sc/source/ui/vba/vbaeventshelper.cxx
@@ -0,0 +1,898 @@
+/* -*- 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 "vbaeventshelper.hxx"
+#include "excelvbahelper.hxx"
+
+#include <com/sun/star/awt/XTopWindow.hpp>
+#include <com/sun/star/awt/XTopWindowListener.hpp>
+#include <com/sun/star/awt/XWindowListener.hpp>
+#include <com/sun/star/frame/XBorderResizeListener.hpp>
+#include <com/sun/star/frame/XControllerBorder.hpp>
+#include <com/sun/star/script/ModuleType.hpp>
+#include <com/sun/star/script/vba/VBAEventId.hpp>
+#include <com/sun/star/sheet/XCellRangeAddressable.hpp>
+#include <com/sun/star/sheet/XSheetCellRangeContainer.hpp>
+#include <com/sun/star/table/XCellRange.hpp>
+#include <com/sun/star/util/XChangesListener.hpp>
+#include <com/sun/star/util/XChangesNotifier.hpp>
+
+#include <cppuhelper/implbase.hxx>
+#include <toolkit/helper/vclunohelper.hxx>
+#include <unotools/eventcfg.hxx>
+#include <vcl/event.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/window.hxx>
+#include <vbahelper/vbaaccesshelper.hxx>
+
+#include <docsh.hxx>
+#include <document.hxx>
+#include <cellsuno.hxx>
+#include <convuno.hxx>
+#include "vbaapplication.hxx"
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::script::vba::VBAEventId;
+using namespace ::ooo::vba;
+
+namespace {
+
+/** Extracts a sheet index from the specified element of the passed sequence.
+ The element may be an integer, a Calc range or ranges object, or a VBA Range object.
+
+ @throws lang::IllegalArgumentException
+ @throws uno::RuntimeException
+*/
+SCTAB lclGetTabFromArgs( const uno::Sequence< uno::Any >& rArgs, sal_Int32 nIndex )
+{
+ VbaEventsHelperBase::checkArgument( rArgs, nIndex );
+
+ // first try to extract a sheet index
+ sal_Int32 nTab = -1;
+ if( rArgs[ nIndex ] >>= nTab )
+ {
+ if( (nTab < 0) || (nTab > MAXTAB) )
+ throw lang::IllegalArgumentException();
+ return static_cast< SCTAB >( nTab );
+ }
+
+ // try VBA Range object
+ uno::Reference< excel::XRange > xVbaRange = getXSomethingFromArgs< excel::XRange >( rArgs, nIndex );
+ if( xVbaRange.is() )
+ {
+ uno::Reference< XHelperInterface > xVbaHelper( xVbaRange, uno::UNO_QUERY_THROW );
+ // TODO: in the future, the parent may be an excel::XChart (chart sheet) -> will there be a common base interface?
+ uno::Reference< excel::XWorksheet > xVbaSheet( xVbaHelper->getParent(), uno::UNO_QUERY_THROW );
+ // VBA sheet index is 1-based
+ return static_cast< SCTAB >( xVbaSheet->getIndex() - 1 );
+ }
+
+ // try single UNO range object
+ uno::Reference< sheet::XCellRangeAddressable > xCellRangeAddressable = getXSomethingFromArgs< sheet::XCellRangeAddressable >( rArgs, nIndex );
+ if( xCellRangeAddressable.is() )
+ return xCellRangeAddressable->getRangeAddress().Sheet;
+
+ // at last, try UNO range list
+ uno::Reference< sheet::XSheetCellRangeContainer > xRanges = getXSomethingFromArgs< sheet::XSheetCellRangeContainer >( rArgs, nIndex );
+ if( xRanges.is() )
+ {
+ uno::Sequence< table::CellRangeAddress > aRangeAddresses = xRanges->getRangeAddresses();
+ if( aRangeAddresses.hasElements() )
+ return aRangeAddresses[ 0 ].Sheet;
+ }
+
+ throw lang::IllegalArgumentException();
+}
+
+/** Returns the AWT container window of the passed controller. */
+uno::Reference< awt::XWindow > lclGetWindowForController( const uno::Reference< frame::XController >& rxController )
+{
+ if( rxController.is() ) try
+ {
+ uno::Reference< frame::XFrame > xFrame( rxController->getFrame(), uno::UNO_SET_THROW );
+ return xFrame->getContainerWindow();
+ }
+ catch( uno::Exception& )
+ {
+ }
+ return nullptr;
+}
+
+} // namespace
+
+// This class is to process Workbook window related event
+class ScVbaEventListener : public ::cppu::WeakImplHelper< awt::XTopWindowListener,
+ awt::XWindowListener,
+ frame::XBorderResizeListener,
+ util::XChangesListener >
+{
+public:
+ ScVbaEventListener( ScVbaEventsHelper& rVbaEvents, const uno::Reference< frame::XModel >& rxModel, ScDocShell* pDocShell );
+
+ /** Starts listening to the passed document controller. */
+ void startControllerListening( const uno::Reference< frame::XController >& rxController );
+ /** Stops listening to the passed document controller. */
+ void stopControllerListening( const uno::Reference< frame::XController >& rxController );
+
+ // XTopWindowListener
+ virtual void SAL_CALL windowOpened( const lang::EventObject& rEvent ) override;
+ virtual void SAL_CALL windowClosing( const lang::EventObject& rEvent ) override;
+ virtual void SAL_CALL windowClosed( const lang::EventObject& rEvent ) override;
+ virtual void SAL_CALL windowMinimized( const lang::EventObject& rEvent ) override;
+ virtual void SAL_CALL windowNormalized( const lang::EventObject& rEvent ) override;
+ virtual void SAL_CALL windowActivated( const lang::EventObject& rEvent ) override;
+ virtual void SAL_CALL windowDeactivated( const lang::EventObject& rEvent ) override;
+
+ // XWindowListener
+ virtual void SAL_CALL windowResized( const awt::WindowEvent& rEvent ) override;
+ virtual void SAL_CALL windowMoved( const awt::WindowEvent& rEvent ) override;
+ virtual void SAL_CALL windowShown( const lang::EventObject& rEvent ) override;
+ virtual void SAL_CALL windowHidden( const lang::EventObject& rEvent ) override;
+
+ // XBorderResizeListener
+ virtual void SAL_CALL borderWidthsChanged( const uno::Reference< uno::XInterface >& rSource, const frame::BorderWidths& aNewSize ) override;
+
+ // XChangesListener
+ virtual void SAL_CALL changesOccurred( const util::ChangesEvent& rEvent ) override;
+
+ // XEventListener
+ virtual void SAL_CALL disposing( const lang::EventObject& rEvent ) override;
+
+private:
+ /** Starts listening to the document model. */
+ void startModelListening();
+ /** Stops listening to the document model. */
+ void stopModelListening();
+
+ /** Returns the controller for the passed VCL window. */
+ uno::Reference< frame::XController > getControllerForWindow( vcl::Window* pWindow ) const;
+
+ /** Calls the Workbook_Window[Activate|Deactivate] event handler. */
+ void processWindowActivateEvent( vcl::Window* pWindow, bool bActivate );
+ /** Posts a Workbook_WindowResize user event. */
+ void postWindowResizeEvent( vcl::Window* pWindow );
+ /** Callback link for Application::PostUserEvent(). */
+ DECL_LINK( processWindowResizeEvent, void*, void );
+
+private:
+ typedef ::std::map< VclPtr<vcl::Window>, uno::Reference< frame::XController > > WindowControllerMap;
+
+ ::osl::Mutex maMutex;
+ ScVbaEventsHelper& mrVbaEvents;
+ uno::Reference< frame::XModel > mxModel;
+ ScDocShell* mpDocShell;
+ WindowControllerMap maControllers; /// Maps VCL top windows to their controllers.
+ std::multiset< VclPtr<vcl::Window> > m_PostedWindows; /// Keeps processWindowResizeEvent windows from being deleted between postWindowResizeEvent and processWindowResizeEvent
+ VclPtr<vcl::Window> mpActiveWindow; /// Currently activated window, to prevent multiple (de)activation.
+ bool mbWindowResized; /// True = window resize system event processed.
+ bool mbBorderChanged; /// True = borders changed system event processed.
+ bool mbDisposed;
+};
+
+ScVbaEventListener::ScVbaEventListener( ScVbaEventsHelper& rVbaEvents, const uno::Reference< frame::XModel >& rxModel, ScDocShell* pDocShell ) :
+ mrVbaEvents( rVbaEvents ),
+ mxModel( rxModel ),
+ mpDocShell( pDocShell ),
+ mpActiveWindow( nullptr ),
+ mbWindowResized( false ),
+ mbBorderChanged( false ),
+ mbDisposed( !rxModel.is() )
+{
+ if( !mxModel.is() )
+ return;
+
+ startModelListening();
+ try
+ {
+ uno::Reference< frame::XController > xController( mxModel->getCurrentController(), uno::UNO_SET_THROW );
+ startControllerListening( xController );
+ }
+ catch( uno::Exception& )
+ {
+ }
+}
+
+void ScVbaEventListener::startControllerListening( const uno::Reference< frame::XController >& rxController )
+{
+ ::osl::MutexGuard aGuard( maMutex );
+
+ uno::Reference< awt::XWindow > xWindow = lclGetWindowForController( rxController );
+ if( xWindow.is() )
+ try { xWindow->addWindowListener( this ); } catch( uno::Exception& ) {}
+
+ uno::Reference< awt::XTopWindow > xTopWindow( xWindow, uno::UNO_QUERY );
+ if( xTopWindow.is() )
+ try { xTopWindow->addTopWindowListener( this ); } catch( uno::Exception& ) {}
+
+ uno::Reference< frame::XControllerBorder > xControllerBorder( rxController, uno::UNO_QUERY );
+ if( xControllerBorder.is() )
+ try { xControllerBorder->addBorderResizeListener( this ); } catch( uno::Exception& ) {}
+
+ if( VclPtr<vcl::Window> pWindow = VCLUnoHelper::GetWindow( xWindow ) )
+ {
+ maControllers[ pWindow ] = rxController;
+ }
+}
+
+void ScVbaEventListener::stopControllerListening( const uno::Reference< frame::XController >& rxController )
+{
+ ::osl::MutexGuard aGuard( maMutex );
+
+ uno::Reference< awt::XWindow > xWindow = lclGetWindowForController( rxController );
+ if( xWindow.is() )
+ try { xWindow->removeWindowListener( this ); } catch( uno::Exception& ) {}
+
+ uno::Reference< awt::XTopWindow > xTopWindow( xWindow, uno::UNO_QUERY );
+ if( xTopWindow.is() )
+ try { xTopWindow->removeTopWindowListener( this ); } catch( uno::Exception& ) {}
+
+ uno::Reference< frame::XControllerBorder > xControllerBorder( rxController, uno::UNO_QUERY );
+ if( xControllerBorder.is() )
+ try { xControllerBorder->removeBorderResizeListener( this ); } catch( uno::Exception& ) {}
+
+ if( VclPtr<vcl::Window> pWindow = VCLUnoHelper::GetWindow( xWindow ) )
+ {
+ maControllers.erase( pWindow );
+ if( pWindow == mpActiveWindow )
+ mpActiveWindow = nullptr;
+ }
+}
+
+void SAL_CALL ScVbaEventListener::windowOpened( const lang::EventObject& /*rEvent*/ )
+{
+}
+
+void SAL_CALL ScVbaEventListener::windowClosing( const lang::EventObject& /*rEvent*/ )
+{
+}
+
+void SAL_CALL ScVbaEventListener::windowClosed( const lang::EventObject& /*rEvent*/ )
+{
+}
+
+void SAL_CALL ScVbaEventListener::windowMinimized( const lang::EventObject& /*rEvent*/ )
+{
+}
+
+void SAL_CALL ScVbaEventListener::windowNormalized( const lang::EventObject& /*rEvent*/ )
+{
+}
+
+void SAL_CALL ScVbaEventListener::windowActivated( const lang::EventObject& rEvent )
+{
+ ::osl::MutexGuard aGuard( maMutex );
+
+ if( mbDisposed )
+ return;
+
+ uno::Reference< awt::XWindow > xWindow( rEvent.Source, uno::UNO_QUERY );
+ VclPtr<vcl::Window> pWindow = VCLUnoHelper::GetWindow( xWindow );
+ // do not fire activation event multiple time for the same window
+ if( pWindow && (pWindow != mpActiveWindow) )
+ {
+ // if another window is active, fire deactivation event first
+ if( mpActiveWindow )
+ processWindowActivateEvent( mpActiveWindow, false );
+ // fire activation event for the new window
+ processWindowActivateEvent( pWindow, true );
+ mpActiveWindow = pWindow;
+ }
+}
+
+void SAL_CALL ScVbaEventListener::windowDeactivated( const lang::EventObject& rEvent )
+{
+ ::osl::MutexGuard aGuard( maMutex );
+
+ if( !mbDisposed )
+ {
+ uno::Reference< awt::XWindow > xWindow( rEvent.Source, uno::UNO_QUERY );
+ VclPtr<vcl::Window> pWindow = VCLUnoHelper::GetWindow( xWindow );
+ // do not fire the deactivation event, if the window is not active (prevent multiple deactivation)
+ if( pWindow && (pWindow == mpActiveWindow) )
+ processWindowActivateEvent( pWindow, false );
+ // forget pointer to the active window
+ mpActiveWindow = nullptr;
+ }
+}
+
+void SAL_CALL ScVbaEventListener::windowResized( const awt::WindowEvent& rEvent )
+{
+ ::osl::MutexGuard aGuard( maMutex );
+
+ mbWindowResized = true;
+ if( !mbDisposed && mbBorderChanged )
+ {
+ uno::Reference< awt::XWindow > xWindow( rEvent.Source, uno::UNO_QUERY );
+ postWindowResizeEvent( VCLUnoHelper::GetWindow( xWindow ) );
+ }
+}
+
+void SAL_CALL ScVbaEventListener::windowMoved( const awt::WindowEvent& /*rEvent*/ )
+{
+}
+
+void SAL_CALL ScVbaEventListener::windowShown( const lang::EventObject& /*rEvent*/ )
+{
+}
+
+void SAL_CALL ScVbaEventListener::windowHidden( const lang::EventObject& /*rEvent*/ )
+{
+}
+
+void SAL_CALL ScVbaEventListener::borderWidthsChanged( const uno::Reference< uno::XInterface >& rSource, const frame::BorderWidths& /*aNewSize*/ )
+{
+ ::osl::MutexGuard aGuard( maMutex );
+
+ mbBorderChanged = true;
+ if( !mbDisposed && mbWindowResized )
+ {
+ uno::Reference< frame::XController > xController( rSource, uno::UNO_QUERY );
+ uno::Reference< awt::XWindow > xWindow = lclGetWindowForController( xController );
+ postWindowResizeEvent( VCLUnoHelper::GetWindow( xWindow ) );
+ }
+}
+
+void SAL_CALL ScVbaEventListener::changesOccurred( const util::ChangesEvent& rEvent )
+{
+ ::osl::MutexGuard aGuard( maMutex );
+
+ sal_Int32 nCount = rEvent.Changes.getLength();
+ if( mbDisposed || !mpDocShell || (nCount == 0) )
+ return;
+
+ util::ElementChange aChange = rEvent.Changes[ 0 ];
+ OUString sOperation;
+ aChange.Accessor >>= sOperation;
+ if( !sOperation.equalsIgnoreAsciiCase("cell-change") )
+ return;
+
+ if( nCount == 1 )
+ {
+ uno::Reference< table::XCellRange > xRangeObj;
+ aChange.ReplacedElement >>= xRangeObj;
+ if( xRangeObj.is() )
+ {
+ uno::Sequence< uno::Any > aArgs{ uno::Any(xRangeObj) };
+ mrVbaEvents.processVbaEventNoThrow( WORKSHEET_CHANGE, aArgs );
+ }
+ return;
+ }
+
+ ScRangeList aRangeList;
+ for( const util::ElementChange& rChange : rEvent.Changes )
+ {
+ rChange.Accessor >>= sOperation;
+ uno::Reference< table::XCellRange > xRangeObj;
+ rChange.ReplacedElement >>= xRangeObj;
+ if( xRangeObj.is() && sOperation.equalsIgnoreAsciiCase("cell-change") )
+ {
+ uno::Reference< sheet::XCellRangeAddressable > xCellRangeAddressable( xRangeObj, uno::UNO_QUERY );
+ if( xCellRangeAddressable.is() )
+ {
+ ScRange aRange;
+ ScUnoConversion::FillScRange( aRange, xCellRangeAddressable->getRangeAddress() );
+ aRangeList.push_back( aRange );
+ }
+ }
+ }
+
+ if (!aRangeList.empty())
+ {
+ uno::Reference< sheet::XSheetCellRangeContainer > xRanges( new ScCellRangesObj( mpDocShell, aRangeList ) );
+ uno::Sequence< uno::Any > aArgs{ uno::Any(xRanges) };
+ mrVbaEvents.processVbaEventNoThrow( WORKSHEET_CHANGE, aArgs );
+ }
+}
+
+void SAL_CALL ScVbaEventListener::disposing( const lang::EventObject& rEvent )
+{
+ ::osl::MutexGuard aGuard( maMutex );
+
+ uno::Reference< frame::XModel > xModel( rEvent.Source, uno::UNO_QUERY );
+ if( xModel.is() )
+ {
+ OSL_ENSURE( xModel.get() == mxModel.get(), "ScVbaEventListener::disposing - disposing from unknown model" );
+ stopModelListening();
+ mbDisposed = true;
+ return;
+ }
+
+ uno::Reference< frame::XController > xController( rEvent.Source, uno::UNO_QUERY );
+ if( xController.is() )
+ {
+ stopControllerListening( xController );
+ return;
+ }
+}
+
+// private --------------------------------------------------------------------
+
+void ScVbaEventListener::startModelListening()
+{
+ try
+ {
+ uno::Reference< util::XChangesNotifier > xChangesNotifier( mxModel, uno::UNO_QUERY_THROW );
+ xChangesNotifier->addChangesListener( this );
+ }
+ catch( uno::Exception& )
+ {
+ }
+}
+
+void ScVbaEventListener::stopModelListening()
+{
+ try
+ {
+ uno::Reference< util::XChangesNotifier > xChangesNotifier( mxModel, uno::UNO_QUERY_THROW );
+ xChangesNotifier->removeChangesListener( this );
+ }
+ catch( uno::Exception& )
+ {
+ }
+}
+
+uno::Reference< frame::XController > ScVbaEventListener::getControllerForWindow( vcl::Window* pWindow ) const
+{
+ WindowControllerMap::const_iterator aIt = maControllers.find( pWindow );
+ return (aIt == maControllers.end()) ? uno::Reference< frame::XController >() : aIt->second;
+}
+
+void ScVbaEventListener::processWindowActivateEvent( vcl::Window* pWindow, bool bActivate )
+{
+ uno::Reference< frame::XController > xController = getControllerForWindow( pWindow );
+ if( xController.is() )
+ {
+ uno::Sequence< uno::Any > aArgs{ uno::Any(xController) };
+ mrVbaEvents.processVbaEventNoThrow( bActivate ? WORKBOOK_WINDOWACTIVATE : WORKBOOK_WINDOWDEACTIVATE, aArgs );
+ }
+}
+
+void ScVbaEventListener::postWindowResizeEvent( vcl::Window* pWindow )
+{
+ // check that the passed window is still alive (it must be registered in maControllers)
+ if( pWindow && (maControllers.count( pWindow ) > 0) )
+ {
+ mbWindowResized = mbBorderChanged = false;
+ acquire(); // ensure we don't get deleted before the timer fires
+ m_PostedWindows.insert(pWindow);
+ Application::PostUserEvent( LINK( this, ScVbaEventListener, processWindowResizeEvent ), pWindow );
+ }
+}
+
+IMPL_LINK( ScVbaEventListener, processWindowResizeEvent, void*, p, void )
+{
+ vcl::Window* pWindow = static_cast<vcl::Window*>(p);
+ ::osl::MutexGuard aGuard( maMutex );
+
+ /* Check that the passed window is still alive (it must be registered in
+ maControllers). While closing a document, postWindowResizeEvent() may
+ be called on the last window which posts a user event via
+ Application::PostUserEvent to call this event handler. VCL will trigger
+ the handler some time later. Sometimes, the window gets deleted before.
+ This is handled via the disposing() function which removes the window
+ pointer from the member maControllers. Thus, checking whether
+ maControllers contains pWindow ensures that the window is still alive. */
+ if( !mbDisposed && pWindow && !pWindow->isDisposed() && (maControllers.count(pWindow) > 0) )
+ {
+ // do not fire event unless all mouse buttons have been released
+ vcl::Window::PointerState aPointerState = pWindow->GetPointerState();
+ if( (aPointerState.mnState & (MOUSE_LEFT | MOUSE_MIDDLE | MOUSE_RIGHT)) == 0 )
+ {
+ uno::Reference< frame::XController > xController = getControllerForWindow( pWindow );
+ if( xController.is() )
+ {
+ uno::Sequence< uno::Any > aArgs{ uno::Any(xController) };
+ // #163419# do not throw exceptions into application core
+ mrVbaEvents.processVbaEventNoThrow( WORKBOOK_WINDOWRESIZE, aArgs );
+ }
+ }
+ }
+ {
+ // note: there may be multiple processWindowResizeEvent outstanding
+ // for pWindow, so it may have been added to m_PostedWindows multiple
+ // times - so this must delete exactly one of these elements!
+ auto const iter(m_PostedWindows.find(pWindow));
+ assert(iter != m_PostedWindows.end());
+ m_PostedWindows.erase(iter);
+ }
+ release();
+}
+
+ScVbaEventsHelper::ScVbaEventsHelper( const uno::Sequence< uno::Any >& rArgs ) :
+ VbaEventsHelperBase( rArgs ),
+ mbOpened( false )
+{
+ mpDocShell = dynamic_cast< ScDocShell* >( mpShell ); // mpShell from base class
+ mpDoc = mpDocShell ? &mpDocShell->GetDocument() : nullptr;
+
+ if( !mxModel.is() || !mpDocShell || !mpDoc )
+ return;
+
+ // global
+ auto registerAutoEvent = [this](sal_Int32 nID, const char* sName)
+ { registerEventHandler(nID, script::ModuleType::NORMAL, OString(OString::Concat("Auto_") + sName).getStr(), -1, uno::Any(false)); };
+ registerAutoEvent(AUTO_OPEN, "Open");
+ registerAutoEvent(AUTO_CLOSE, "Close");
+
+ // Workbook
+ auto registerWorkbookEvent = [this](sal_Int32 nID, const char* sName, sal_Int32 nCancelIndex)
+ { registerEventHandler(nID, script::ModuleType::DOCUMENT, OString(OString::Concat("Workbook_") + sName).getStr(), nCancelIndex, uno::Any(false)); };
+ registerWorkbookEvent( WORKBOOK_ACTIVATE, "Activate", -1 );
+ registerWorkbookEvent( WORKBOOK_DEACTIVATE, "Deactivate", -1 );
+ registerWorkbookEvent( WORKBOOK_OPEN, "Open", -1 );
+ registerWorkbookEvent( WORKBOOK_BEFORECLOSE, "BeforeClose", 0 );
+ registerWorkbookEvent( WORKBOOK_BEFOREPRINT, "BeforePrint", 0 );
+ registerWorkbookEvent( WORKBOOK_BEFORESAVE, "BeforeSave", 1 );
+ registerWorkbookEvent( WORKBOOK_AFTERSAVE, "AfterSave", -1 );
+ registerWorkbookEvent( WORKBOOK_NEWSHEET, "NewSheet", -1 );
+ registerWorkbookEvent( WORKBOOK_WINDOWACTIVATE, "WindowActivate", -1 );
+ registerWorkbookEvent( WORKBOOK_WINDOWDEACTIVATE, "WindowDeactivate", -1 );
+ registerWorkbookEvent( WORKBOOK_WINDOWRESIZE, "WindowResize", -1 );
+
+ // Worksheet events. All events have a corresponding workbook event.
+ auto registerWorksheetEvent = [this](sal_Int32 nID, const char* sName, sal_Int32 nCancelIndex)
+ {
+ registerEventHandler(nID, script::ModuleType::DOCUMENT, OString(OString::Concat("Worksheet_") + sName).getStr(),
+ nCancelIndex, uno::Any(true));
+ registerEventHandler(USERDEFINED_START + nID, script::ModuleType::DOCUMENT,
+ OString(OString::Concat("Workbook_Worksheet") + sName).getStr(),
+ ((nCancelIndex >= 0) ? (nCancelIndex + 1) : -1), uno::Any(false));
+ };
+ registerWorksheetEvent( WORKSHEET_ACTIVATE, "Activate", -1 );
+ registerWorksheetEvent( WORKSHEET_DEACTIVATE, "Deactivate", -1 );
+ registerWorksheetEvent( WORKSHEET_BEFOREDOUBLECLICK, "BeforeDoubleClick", 1 );
+ registerWorksheetEvent( WORKSHEET_BEFORERIGHTCLICK, "BeforeRightClick", 1 );
+ registerWorksheetEvent( WORKSHEET_CALCULATE, "Calculate", -1 );
+ registerWorksheetEvent( WORKSHEET_CHANGE, "Change", -1 );
+ registerWorksheetEvent( WORKSHEET_SELECTIONCHANGE, "SelectionChange", -1 );
+ registerWorksheetEvent( WORKSHEET_FOLLOWHYPERLINK, "FollowHyperlink", -1 );
+}
+
+ScVbaEventsHelper::~ScVbaEventsHelper()
+{
+}
+
+void SAL_CALL ScVbaEventsHelper::notifyEvent( const css::document::EventObject& rEvent )
+{
+ static const uno::Sequence< uno::Any > saEmptyArgs;
+ if( (rEvent.EventName == GlobalEventConfig::GetEventName( GlobalEventId::OPENDOC )) ||
+ (rEvent.EventName == GlobalEventConfig::GetEventName( GlobalEventId::CREATEDOC )) ) // CREATEDOC triggered e.g. during VBA Workbooks.Add
+ {
+ processVbaEventNoThrow( WORKBOOK_OPEN, saEmptyArgs );
+ }
+ else if( rEvent.EventName == GlobalEventConfig::GetEventName( GlobalEventId::ACTIVATEDOC ) )
+ {
+ processVbaEventNoThrow( WORKBOOK_ACTIVATE, saEmptyArgs );
+ }
+ else if( rEvent.EventName == GlobalEventConfig::GetEventName( GlobalEventId::DEACTIVATEDOC ) )
+ {
+ processVbaEventNoThrow( WORKBOOK_DEACTIVATE, saEmptyArgs );
+ }
+ else if( (rEvent.EventName == GlobalEventConfig::GetEventName( GlobalEventId::SAVEDOCDONE )) ||
+ (rEvent.EventName == GlobalEventConfig::GetEventName( GlobalEventId::SAVEASDOCDONE )) ||
+ (rEvent.EventName == GlobalEventConfig::GetEventName( GlobalEventId::SAVETODOCDONE )) )
+ {
+ uno::Sequence< uno::Any > aArgs{ uno::Any(true) };
+ processVbaEventNoThrow( WORKBOOK_AFTERSAVE, aArgs );
+ }
+ else if( (rEvent.EventName == GlobalEventConfig::GetEventName( GlobalEventId::SAVEDOCFAILED )) ||
+ (rEvent.EventName == GlobalEventConfig::GetEventName( GlobalEventId::SAVEASDOCFAILED )) ||
+ (rEvent.EventName == GlobalEventConfig::GetEventName( GlobalEventId::SAVETODOCFAILED )) )
+ {
+ uno::Sequence< uno::Any > aArgs{ uno::Any(false) };
+ processVbaEventNoThrow( WORKBOOK_AFTERSAVE, aArgs );
+ }
+ else if( rEvent.EventName == GlobalEventConfig::GetEventName( GlobalEventId::CLOSEDOC ) )
+ {
+ /* Trigger the WORKBOOK_WINDOWDEACTIVATE and WORKBOOK_DEACTIVATE
+ events and stop listening to the model (done in base class). */
+ uno::Reference< frame::XController > xController( mxModel->getCurrentController() );
+ if( xController.is() )
+ {
+ uno::Sequence< uno::Any > aArgs{ uno::Any(xController) };
+ processVbaEventNoThrow( WORKBOOK_WINDOWDEACTIVATE, aArgs );
+ }
+ processVbaEventNoThrow( WORKBOOK_DEACTIVATE, saEmptyArgs );
+ }
+ else if( rEvent.EventName == GlobalEventConfig::GetEventName( GlobalEventId::VIEWCREATED ) )
+ {
+ uno::Reference< frame::XController > xController( mxModel->getCurrentController() );
+ if( mxListener && xController.is() )
+ mxListener->startControllerListening( xController );
+ }
+ VbaEventsHelperBase::notifyEvent( rEvent );
+}
+
+OUString ScVbaEventsHelper::getImplementationName()
+{
+ return "ScVbaEventsHelper";
+}
+
+css::uno::Sequence<OUString> ScVbaEventsHelper::getSupportedServiceNames()
+{
+ return css::uno::Sequence<OUString>{
+ "com.sun.star.script.vba.VBASpreadsheetEventProcessor"};
+}
+
+// protected ------------------------------------------------------------------
+
+bool ScVbaEventsHelper::implPrepareEvent( EventQueue& rEventQueue,
+ const EventHandlerInfo& rInfo, const uno::Sequence< uno::Any >& rArgs )
+{
+ // document and document shell are needed during event processing
+ if( !mpShell || !mpDoc )
+ throw uno::RuntimeException();
+
+ /* For document events: check if events are enabled via the
+ Application.EnableEvents symbol (this is an Excel-only attribute).
+ Check this again for every event, as the event handler may change the
+ state of the EnableEvents symbol. Global events such as AUTO_OPEN and
+ AUTO_CLOSE are always enabled. */
+ bool bExecuteEvent = (rInfo.mnModuleType != script::ModuleType::DOCUMENT) || ScVbaApplication::getDocumentEventsEnabled();
+
+ // framework and Calc fire a few events before 'OnLoad', ignore them
+ if( bExecuteEvent )
+ bExecuteEvent = (rInfo.mnEventId == WORKBOOK_OPEN) ? !mbOpened : mbOpened;
+
+ // special handling for some events
+ if( bExecuteEvent ) switch( rInfo.mnEventId )
+ {
+ case WORKBOOK_OPEN:
+ {
+ // execute delayed Activate event too (see above)
+ rEventQueue.emplace_back(WORKBOOK_ACTIVATE );
+ uno::Sequence< uno::Any > aArgs{ uno::Any(mxModel->getCurrentController()) };
+ rEventQueue.emplace_back( WORKBOOK_WINDOWACTIVATE, aArgs );
+ rEventQueue.emplace_back(AUTO_OPEN );
+ // remember initial selection
+ maOldSelection <<= mxModel->getCurrentSelection();
+ }
+ break;
+ case WORKSHEET_SELECTIONCHANGE:
+ // if selection is not changed, then do not fire the event
+ bExecuteEvent = isSelectionChanged( rArgs, 0 );
+ break;
+ }
+
+ if( bExecuteEvent )
+ {
+ // add workbook event associated to a sheet event
+ bool bSheetEvent = false;
+ if( (rInfo.maUserData >>= bSheetEvent) && bSheetEvent )
+ rEventQueue.emplace_back( rInfo.mnEventId + USERDEFINED_START, rArgs );
+ }
+
+ return bExecuteEvent;
+}
+
+uno::Sequence< uno::Any > ScVbaEventsHelper::implBuildArgumentList( const EventHandlerInfo& rInfo,
+ const uno::Sequence< uno::Any >& rArgs )
+{
+ // fill arguments for workbook events associated to sheet events according to sheet events, sheet will be added below
+ bool bSheetEventAsBookEvent = rInfo.mnEventId > USERDEFINED_START;
+ sal_Int32 nEventId = bSheetEventAsBookEvent ? (rInfo.mnEventId - USERDEFINED_START) : rInfo.mnEventId;
+
+ uno::Sequence< uno::Any > aVbaArgs;
+ switch( nEventId )
+ {
+ // *** Workbook ***
+
+ // no arguments
+ case WORKBOOK_ACTIVATE:
+ case WORKBOOK_DEACTIVATE:
+ case WORKBOOK_OPEN:
+ break;
+ // 1 arg: cancel
+ case WORKBOOK_BEFORECLOSE:
+ case WORKBOOK_BEFOREPRINT:
+ aVbaArgs.realloc( 1 );
+ // current cancel state will be inserted by caller
+ break;
+ // 2 args: saveAs, cancel
+ case WORKBOOK_BEFORESAVE:
+ checkArgumentType< bool >( rArgs, 0 );
+ aVbaArgs = { rArgs[ 0 ], {} };
+ // current cancel state will be inserted by caller
+ break;
+ // 1 arg: success
+ case WORKBOOK_AFTERSAVE:
+ checkArgumentType< bool >( rArgs, 0 );
+ aVbaArgs = { rArgs[ 0 ] };
+ break;
+ // 1 arg: window
+ case WORKBOOK_WINDOWACTIVATE:
+ case WORKBOOK_WINDOWDEACTIVATE:
+ case WORKBOOK_WINDOWRESIZE:
+ aVbaArgs = { createWindow( rArgs, 0 ) };
+ break;
+ // 1 arg: worksheet
+ case WORKBOOK_NEWSHEET:
+ aVbaArgs = { createWorksheet( rArgs, 0 ) };
+ break;
+
+ // *** Worksheet ***
+
+ // no arguments
+ case WORKSHEET_ACTIVATE:
+ case WORKSHEET_CALCULATE:
+ case WORKSHEET_DEACTIVATE:
+ break;
+ // 1 arg: range
+ case WORKSHEET_CHANGE:
+ case WORKSHEET_SELECTIONCHANGE:
+ aVbaArgs = { createRange( rArgs, 0 ) };
+ break;
+ // 2 args: range, cancel
+ case WORKSHEET_BEFOREDOUBLECLICK:
+ case WORKSHEET_BEFORERIGHTCLICK:
+ aVbaArgs = { createRange( rArgs, 0 ), {} };
+ // current cancel state will be inserted by caller
+ break;
+ // 1 arg: hyperlink
+ case WORKSHEET_FOLLOWHYPERLINK:
+ aVbaArgs = { createHyperlink( rArgs, 0 ) };
+ break;
+ }
+
+ /* For workbook events associated to sheet events, the workbook event gets
+ the same arguments but with a Worksheet object in front of them. */
+ if( bSheetEventAsBookEvent )
+ {
+ sal_Int32 nLength = aVbaArgs.getLength();
+ uno::Sequence< uno::Any > aVbaArgs2( nLength + 1 );
+ auto pVbaArgs2 = aVbaArgs2.getArray();
+ *pVbaArgs2 = createWorksheet( rArgs, 0 );
+ std::copy_n(std::cbegin(aVbaArgs), nLength, std::next(pVbaArgs2));
+ aVbaArgs = aVbaArgs2;
+ }
+
+ return aVbaArgs;
+}
+
+void ScVbaEventsHelper::implPostProcessEvent( EventQueue& rEventQueue,
+ const EventHandlerInfo& rInfo, bool bCancel )
+{
+ switch( rInfo.mnEventId )
+ {
+ case WORKBOOK_OPEN:
+ mbOpened = true;
+ // register the listeners
+ if( !mxListener.is() )
+ mxListener = new ScVbaEventListener( *this, mxModel, mpDocShell );
+ break;
+ case WORKBOOK_BEFORECLOSE:
+ /* Execute Auto_Close only if not cancelled by event handler, but
+ before UI asks user whether to cancel closing the document. */
+ if( !bCancel )
+ rEventQueue.emplace_back(AUTO_CLOSE );
+ break;
+ }
+}
+
+OUString ScVbaEventsHelper::implGetDocumentModuleName( const EventHandlerInfo& rInfo,
+ const uno::Sequence< uno::Any >& rArgs ) const
+{
+ bool bSheetEvent = false;
+ rInfo.maUserData >>= bSheetEvent;
+ SCTAB nTab = bSheetEvent ? lclGetTabFromArgs( rArgs, 0 ) : -1;
+ if( bSheetEvent && (nTab < 0) )
+ throw lang::IllegalArgumentException();
+
+ OUString aCodeName;
+ if( bSheetEvent )
+ mpDoc->GetCodeName( nTab, aCodeName );
+ else
+ aCodeName = mpDoc->GetCodeName();
+ return aCodeName;
+}
+
+// private --------------------------------------------------------------------
+
+namespace {
+
+/** Compares the passed range lists representing sheet selections. Ignores
+ selections that refer to different sheets (returns false in this case). */
+bool lclSelectionChanged( const ScRangeList& rLeft, const ScRangeList& rRight )
+{
+ // one of the range lists empty? -> return false, if both lists empty
+ bool bLeftEmpty = rLeft.empty();
+ bool bRightEmpty = rRight.empty();
+ if( bLeftEmpty || bRightEmpty )
+ return !(bLeftEmpty && bRightEmpty);
+
+ // check sheet indexes of the range lists (assuming that all ranges in a list are on the same sheet)
+ if (rLeft[0].aStart.Tab() != rRight[0].aStart.Tab())
+ return false;
+
+ // compare all ranges
+ return rLeft != rRight;
+}
+
+} // namespace
+
+bool ScVbaEventsHelper::isSelectionChanged( const uno::Sequence< uno::Any >& rArgs, sal_Int32 nIndex )
+{
+ uno::Reference< uno::XInterface > xOldSelection( maOldSelection, uno::UNO_QUERY );
+ uno::Reference< uno::XInterface > xNewSelection = getXSomethingFromArgs< uno::XInterface >( rArgs, nIndex, false );
+ ScCellRangesBase* pOldCellRanges = comphelper::getFromUnoTunnel<ScCellRangesBase>( xOldSelection );
+ ScCellRangesBase* pNewCellRanges = comphelper::getFromUnoTunnel<ScCellRangesBase>( xNewSelection );
+ bool bChanged = !pOldCellRanges || !pNewCellRanges || lclSelectionChanged( pOldCellRanges->GetRangeList(), pNewCellRanges->GetRangeList() );
+ maOldSelection <<= xNewSelection;
+ return bChanged;
+}
+
+uno::Any ScVbaEventsHelper::createWorksheet( const uno::Sequence< uno::Any >& rArgs, sal_Int32 nIndex ) const
+{
+ // extract sheet index, will throw, if parameter is invalid
+ SCTAB nTab = lclGetTabFromArgs( rArgs, nIndex );
+ return uno::Any( excel::getUnoSheetModuleObj( mxModel, nTab ) );
+}
+
+uno::Any ScVbaEventsHelper::createRange( const uno::Sequence< uno::Any >& rArgs, sal_Int32 nIndex ) const
+{
+ // it is possible to pass an existing VBA Range object
+ uno::Reference< excel::XRange > xVbaRange = getXSomethingFromArgs< excel::XRange >( rArgs, nIndex );
+ if( !xVbaRange.is() )
+ {
+ uno::Reference< sheet::XSheetCellRangeContainer > xRanges = getXSomethingFromArgs< sheet::XSheetCellRangeContainer >( rArgs, nIndex );
+ uno::Reference< table::XCellRange > xRange = getXSomethingFromArgs< table::XCellRange >( rArgs, nIndex );
+ if ( !xRanges.is() && !xRange.is() )
+ throw lang::IllegalArgumentException();
+
+ uno::Sequence< uno::Any > aArgs;
+ if ( xRanges.is() )
+ {
+ aArgs = { uno::Any(excel::getUnoSheetModuleObj( xRanges )), uno::Any(xRanges) };
+ }
+ else
+ {
+ aArgs = { uno::Any(excel::getUnoSheetModuleObj( xRange )), uno::Any(xRange) };
+ }
+ xVbaRange.set( createVBAUnoAPIServiceWithArgs( mpShell, "ooo.vba.excel.Range", aArgs ), uno::UNO_QUERY_THROW );
+ }
+ return uno::Any( xVbaRange );
+}
+
+uno::Any ScVbaEventsHelper::createHyperlink( const uno::Sequence< uno::Any >& rArgs, sal_Int32 nIndex ) const
+{
+ uno::Reference< table::XCell > xCell = getXSomethingFromArgs< table::XCell >( rArgs, nIndex, false );
+ uno::Sequence< uno::Any > aArgs{ uno::Any(excel::getUnoSheetModuleObj( xCell )),
+ uno::Any(xCell) };
+ uno::Reference< uno::XInterface > xHyperlink( createVBAUnoAPIServiceWithArgs( mpShell, "ooo.vba.excel.Hyperlink", aArgs ), uno::UNO_SET_THROW );
+ return uno::Any( xHyperlink );
+}
+
+uno::Any ScVbaEventsHelper::createWindow( const uno::Sequence< uno::Any >& rArgs, sal_Int32 nIndex ) const
+{
+ uno::Sequence< uno::Any > aArgs{ uno::Any(getVBADocument( mxModel )),
+ uno::Any(mxModel),
+ uno::Any(getXSomethingFromArgs< frame::XController >( rArgs, nIndex, false )) };
+ uno::Reference< uno::XInterface > xWindow( createVBAUnoAPIServiceWithArgs( mpShell, "ooo.vba.excel.Window", aArgs ), uno::UNO_SET_THROW );
+ return uno::Any( xWindow );
+}
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface *
+ScVbaEventsHelper_get_implementation(
+ css::uno::XComponentContext * /*context*/,
+ css::uno::Sequence<css::uno::Any> const &arguments)
+{
+ return cppu::acquire(new ScVbaEventsHelper(arguments));
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */