summaryrefslogtreecommitdiffstats
path: root/vbahelper/source/vbahelper/vbaeventshelperbase.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'vbahelper/source/vbahelper/vbaeventshelperbase.cxx')
-rw-r--r--vbahelper/source/vbahelper/vbaeventshelperbase.cxx357
1 files changed, 357 insertions, 0 deletions
diff --git a/vbahelper/source/vbahelper/vbaeventshelperbase.cxx b/vbahelper/source/vbahelper/vbaeventshelperbase.cxx
new file mode 100644
index 000000000..83b047ea8
--- /dev/null
+++ b/vbahelper/source/vbahelper/vbaeventshelperbase.cxx
@@ -0,0 +1,357 @@
+/* -*- 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 <vbahelper/vbaeventshelperbase.hxx>
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/document/XEventBroadcaster.hpp>
+#include <com/sun/star/frame/XModel.hpp>
+#include <com/sun/star/script/ModuleType.hpp>
+#include <com/sun/star/script/vba/XVBAModuleInfo.hpp>
+#include <com/sun/star/script/XLibraryContainer.hpp>
+#include <com/sun/star/util/VetoException.hpp>
+#include <com/sun/star/util/XChangesNotifier.hpp>
+#include <cppuhelper/supportsservice.hxx>
+#include <filter/msfilter/msvbahelper.hxx>
+#include <unotools/eventcfg.hxx>
+#include <vbahelper/vbahelper.hxx>
+#include <sal/log.hxx>
+
+using namespace ::com::sun::star;
+using namespace ::ooo::vba;
+
+
+VbaEventsHelperBase::VbaEventsHelperBase( const uno::Sequence< uno::Any >& rArgs ) :
+ mpShell( nullptr ),
+ mbDisposed( true )
+{
+ try
+ {
+ mxModel = getXSomethingFromArgs< frame::XModel >( rArgs, 0, false );
+ mpShell = getSfxObjShell( mxModel );
+ }
+ catch( uno::Exception& )
+ {
+ }
+ mbDisposed = mpShell == nullptr;
+ startListening();
+}
+
+VbaEventsHelperBase::~VbaEventsHelperBase()
+{
+ SAL_WARN_IF( !mbDisposed, "vbahelper", "VbaEventsHelperBase::~VbaEventsHelperBase - missing disposing notification" );
+}
+
+sal_Bool SAL_CALL VbaEventsHelperBase::processVbaEvent( sal_Int32 nEventId, const uno::Sequence< uno::Any >& rArgs )
+{
+ /* Derived classes may add new event identifiers to be processed while
+ processing the original event. All unprocessed events are collected in
+ a queue. First element in the queue is the next event to be processed. */
+ EventQueue aEventQueue;
+ aEventQueue.emplace_back( nEventId, rArgs );
+
+ /* bCancel will contain the current Cancel value. It is possible that
+ multiple events will try to modify the Cancel value. Every event
+ handler receives the Cancel value of the previous event handler. */
+ bool bCancel = false;
+
+ /* bExecuted will change to true if at least one event handler has been
+ found and executed. */
+ bool bExecuted = false;
+
+ /* Loop as long as there are more events to be processed. Derived classes
+ may add new events to be processed in the virtual implPrepareEvent()
+ function. */
+ while( !aEventQueue.empty() )
+ {
+ /* Check that all class members are available, and that we are not
+ disposed (this may have happened at any time during execution of
+ the last event handler). */
+ if( mbDisposed || !mxModel.is() || !mpShell )
+ throw uno::RuntimeException();
+
+ // get info for next event
+ const EventHandlerInfo& rInfo = getEventHandlerInfo( aEventQueue.front().mnEventId );
+ uno::Sequence< uno::Any > aEventArgs = aEventQueue.front().maArgs;
+ aEventQueue.pop_front();
+ SAL_INFO("vbahelper", "VbaEventsHelperBase::processVbaEvent( \"" << rInfo.maMacroName << "\" )");
+
+ /* Let derived classes prepare the event, they may add new events for
+ next iteration. If false is returned, the event handler must not be
+ called. */
+ if( implPrepareEvent( aEventQueue, rInfo, aEventArgs ) )
+ {
+ // search the event handler macro in the document
+ OUString aMacroPath = getEventHandlerPath( rInfo, aEventArgs );
+ if( !aMacroPath.isEmpty() )
+ {
+ // build the argument list
+ uno::Sequence< uno::Any > aVbaArgs = implBuildArgumentList( rInfo, aEventArgs );
+ // insert current cancel value
+ if( rInfo.mnCancelIndex >= 0 )
+ {
+ if( rInfo.mnCancelIndex >= aVbaArgs.getLength() )
+ throw lang::IllegalArgumentException();
+ aVbaArgs[ rInfo.mnCancelIndex ] <<= bCancel;
+ }
+ // execute the event handler
+ uno::Any aRet, aCaller;
+ executeMacro( mpShell, aMacroPath, aVbaArgs, aRet, aCaller );
+ // extract new cancel value (may be boolean or any integer type)
+ if( rInfo.mnCancelIndex >= 0 )
+ {
+ checkArgument( aVbaArgs, rInfo.mnCancelIndex );
+ bCancel = extractBoolFromAny( aVbaArgs[ rInfo.mnCancelIndex ] );
+ }
+ // event handler has been found
+ bExecuted = true;
+ }
+ }
+ // post processing (also, if event handler does not exist, or disabled, or on error
+ implPostProcessEvent( aEventQueue, rInfo, bCancel );
+ }
+
+ // if event handlers want to cancel the event, do so regardless of any errors
+ if( bCancel )
+ throw util::VetoException();
+
+ // return true, if at least one event handler has been found
+ return bExecuted;
+}
+
+void SAL_CALL VbaEventsHelperBase::notifyEvent( const document::EventObject& rEvent )
+{
+ SAL_INFO("vbahelper", "VbaEventsHelperBase::notifyEvent( \"" << rEvent.EventName << "\" )");
+ if( rEvent.EventName == GlobalEventConfig::GetEventName( GlobalEventId::CLOSEDOC ) )
+ stopListening();
+}
+
+void SAL_CALL VbaEventsHelperBase::changesOccurred( const util::ChangesEvent& rEvent )
+{
+ // make sure the VBA library exists
+ try
+ {
+ ensureVBALibrary();
+ }
+ catch( uno::Exception& )
+ {
+ return;
+ }
+
+ // check that the sender of the event is the VBA library
+ uno::Reference< script::vba::XVBAModuleInfo > xSender( rEvent.Base, uno::UNO_QUERY );
+ if( mxModuleInfos.get() != xSender.get() )
+ return;
+
+ // process all changed modules
+ for( const util::ElementChange& rChange : rEvent.Changes )
+ {
+ OUString aModuleName;
+ if( (rChange.Accessor >>= aModuleName) && !aModuleName.isEmpty() ) try
+ {
+ // invalidate event handler path map depending on module type
+ if( getModuleType( aModuleName ) == script::ModuleType::NORMAL )
+ // paths to global event handlers are stored with empty key (will be searched in all normal code modules)
+ maEventPaths.erase( OUString() );
+ else
+ // paths to class/form/document event handlers are keyed by module name
+ maEventPaths.erase( aModuleName );
+ }
+ catch( uno::Exception& )
+ {
+ }
+ }
+}
+
+void SAL_CALL VbaEventsHelperBase::disposing( const lang::EventObject& rEvent )
+{
+ uno::Reference< frame::XModel > xSender( rEvent.Source, uno::UNO_QUERY );
+ if( xSender.is() )
+ stopListening();
+}
+
+sal_Bool VbaEventsHelperBase::supportsService(OUString const & ServiceName)
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+void VbaEventsHelperBase::processVbaEventNoThrow( sal_Int32 nEventId, const uno::Sequence< uno::Any >& rArgs )
+{
+ try
+ {
+ processVbaEvent( nEventId, rArgs );
+ }
+ catch( uno::Exception& )
+ {
+ }
+}
+
+// protected ------------------------------------------------------------------
+
+void VbaEventsHelperBase::registerEventHandler( sal_Int32 nEventId, sal_Int32 nModuleType,
+ const char* pcMacroName, sal_Int32 nCancelIndex, const uno::Any& rUserData )
+{
+ EventHandlerInfo& rInfo = maEventInfos[ nEventId ];
+ rInfo.mnEventId = nEventId;
+ rInfo.mnModuleType = nModuleType;
+ rInfo.maMacroName = OUString::createFromAscii( pcMacroName );
+ rInfo.mnCancelIndex = nCancelIndex;
+ rInfo.maUserData = rUserData;
+}
+
+// private --------------------------------------------------------------------
+
+void VbaEventsHelperBase::startListening()
+{
+ if( mbDisposed )
+ return;
+
+ uno::Reference< document::XEventBroadcaster > xEventBroadcaster( mxModel, uno::UNO_QUERY );
+ if( xEventBroadcaster.is() )
+ try { xEventBroadcaster->addEventListener( this ); } catch( uno::Exception& ) {}
+}
+
+void VbaEventsHelperBase::stopListening()
+{
+ if( mbDisposed )
+ return;
+
+ uno::Reference< document::XEventBroadcaster > xEventBroadcaster( mxModel, uno::UNO_QUERY );
+ if( xEventBroadcaster.is() )
+ try { xEventBroadcaster->removeEventListener( this ); } catch( uno::Exception& ) {}
+
+ mxModel.clear();
+ mpShell = nullptr;
+ maEventInfos.clear();
+ mbDisposed = true;
+}
+
+sal_Bool SAL_CALL VbaEventsHelperBase::hasVbaEventHandler( sal_Int32 nEventId, const uno::Sequence< uno::Any >& rArgs )
+{
+ EventHandlerInfoMap::const_iterator aIt = maEventInfos.find( nEventId );
+ if( aIt == maEventInfos.end() )
+ return false; // throwing a lot of exceptions is slow.
+ else // getEventHandlerPath() searches for the macro in the document
+ return !getEventHandlerPath( aIt->second, rArgs ).isEmpty();
+}
+
+const VbaEventsHelperBase::EventHandlerInfo& VbaEventsHelperBase::getEventHandlerInfo(
+ sal_Int32 nEventId ) const
+{
+ EventHandlerInfoMap::const_iterator aIt = maEventInfos.find( nEventId );
+ if( aIt == maEventInfos.end() )
+ throw lang::IllegalArgumentException();
+ return aIt->second;
+}
+
+OUString VbaEventsHelperBase::getEventHandlerPath( const EventHandlerInfo& rInfo,
+ const uno::Sequence< uno::Any >& rArgs )
+{
+ OUString aModuleName;
+ switch( rInfo.mnModuleType )
+ {
+ // global event handlers may exist in any standard code module
+ case script::ModuleType::NORMAL:
+ break;
+
+ // document event: get name of the code module associated to the event sender
+ case script::ModuleType::DOCUMENT:
+ aModuleName = implGetDocumentModuleName( rInfo, rArgs );
+ if( aModuleName.isEmpty() )
+ throw lang::IllegalArgumentException();
+ break;
+
+ default:
+ throw uno::RuntimeException(); // unsupported module type
+ }
+
+ /* Performance improvement: Check the list of existing event handlers
+ instead of searching in Basic source code every time. */
+ EventHandlerPathMap::iterator aIt = maEventPaths.find( aModuleName );
+ ModulePathMap& rPathMap = (aIt == maEventPaths.end()) ? updateModulePathMap( aModuleName ) : aIt->second;
+ return rPathMap[ rInfo.mnEventId ];
+}
+
+void VbaEventsHelperBase::ensureVBALibrary()
+{
+ if( !mxModuleInfos.is() ) try
+ {
+ maLibraryName = getDefaultProjectName( mpShell );
+ if( maLibraryName.isEmpty() )
+ throw uno::RuntimeException();
+ uno::Reference< beans::XPropertySet > xModelProps( mxModel, uno::UNO_QUERY_THROW );
+ uno::Reference< container::XNameAccess > xBasicLibs( xModelProps->getPropertyValue(
+ "BasicLibraries" ), uno::UNO_QUERY_THROW );
+
+ if(!xBasicLibs->hasByName(maLibraryName) )
+ {
+ uno::Reference< script::XLibraryContainer > xLibContainer(
+ xModelProps->getPropertyValue("BasicLibraries"), uno::UNO_QUERY_THROW);
+ xLibContainer->createLibrary(maLibraryName);
+ }
+
+ mxModuleInfos.set( xBasicLibs->getByName( maLibraryName ), uno::UNO_QUERY_THROW );
+ // listen to changes in the VBA source code
+ uno::Reference< util::XChangesNotifier > xChangesNotifier( mxModuleInfos, uno::UNO_QUERY_THROW );
+ xChangesNotifier->addChangesListener( this );
+ }
+ catch( uno::Exception& )
+ {
+ // error accessing the Basic library, so this object is useless
+ stopListening();
+ throw uno::RuntimeException();
+ }
+}
+
+sal_Int32 VbaEventsHelperBase::getModuleType( const OUString& rModuleName )
+{
+ // make sure the VBA library exists
+ ensureVBALibrary();
+
+ // no module specified: global event handler in standard code modules
+ if( rModuleName.isEmpty() )
+ return script::ModuleType::NORMAL;
+
+ // get module type from module info
+ try
+ {
+ return mxModuleInfos->getModuleInfo( rModuleName ).ModuleType;
+ }
+ catch( uno::Exception& )
+ {
+ }
+ throw uno::RuntimeException();
+}
+
+VbaEventsHelperBase::ModulePathMap& VbaEventsHelperBase::updateModulePathMap( const OUString& rModuleName )
+{
+ // get type of the specified module (throws on error)
+ sal_Int32 nModuleType = getModuleType( rModuleName );
+ // search for all event handlers
+ ModulePathMap& rPathMap = maEventPaths[ rModuleName ];
+ for( const auto& rEventInfo : maEventInfos )
+ {
+ const EventHandlerInfo& rInfo = rEventInfo.second;
+ if( rInfo.mnModuleType == nModuleType )
+ rPathMap[ rInfo.mnEventId ] = resolveVBAMacro( mpShell, maLibraryName, rModuleName, rInfo.maMacroName );
+ }
+ return rPathMap;
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */