LibreOffice Module vbahelper (master) 1
vbaeventshelperbase.cxx
Go to the documentation of this file.
1/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2/*
3 * This file is part of the LibreOffice project.
4 *
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 *
9 * This file incorporates work covered by the following license notice:
10 *
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
18 */
19
21#include <com/sun/star/beans/XPropertySet.hpp>
22#include <com/sun/star/document/XEventBroadcaster.hpp>
23#include <com/sun/star/frame/XModel.hpp>
24#include <com/sun/star/script/ModuleType.hpp>
25#include <com/sun/star/script/vba/VBAEventId.hpp>
26#include <com/sun/star/script/vba/XVBAModuleInfo.hpp>
27#include <com/sun/star/script/XLibraryContainer.hpp>
28#include <com/sun/star/util/VetoException.hpp>
29#include <com/sun/star/util/XChangesNotifier.hpp>
32#include <unotools/eventcfg.hxx>
34#include <sal/log.hxx>
35
36using namespace ::com::sun::star;
37using namespace ::ooo::vba;
38
39
40VbaEventsHelperBase::VbaEventsHelperBase( const uno::Sequence< uno::Any >& rArgs ) :
41 mpShell( nullptr ),
42 mbDisposed( true )
43{
44 try
45 {
46 mxModel = getXSomethingFromArgs< frame::XModel >( rArgs, 0, false );
48 }
49 catch( uno::Exception& )
50 {
51 }
52 mbDisposed = mpShell == nullptr;
54}
55
57{
58 SAL_WARN_IF( !mbDisposed, "vbahelper", "VbaEventsHelperBase::~VbaEventsHelperBase - missing disposing notification" );
59}
60
61sal_Bool SAL_CALL VbaEventsHelperBase::processVbaEvent( sal_Int32 nEventId, const uno::Sequence< uno::Any >& rArgs )
62{
63 /* Derived classes may add new event identifiers to be processed while
64 processing the original event. All unprocessed events are collected in
65 a queue. First element in the queue is the next event to be processed. */
66 EventQueue aEventQueue;
67 aEventQueue.emplace_back( nEventId, rArgs );
68
69 /* bCancel will contain the current Cancel value. It is possible that
70 multiple events will try to modify the Cancel value. Every event
71 handler receives the Cancel value of the previous event handler. */
72 bool bCancel = false;
73
74 /* bExecuted will change to true if at least one event handler has been
75 found and executed. */
76 bool bExecuted = false;
77
78 /* Loop as long as there are more events to be processed. Derived classes
79 may add new events to be processed in the virtual implPrepareEvent()
80 function. */
81 while( !aEventQueue.empty() )
82 {
83 /* Check that all class members are available, and that we are not
84 disposed (this may have happened at any time during execution of
85 the last event handler). */
86 if( mbDisposed || !mxModel.is() || !mpShell )
87 throw uno::RuntimeException();
88
89 // get info for next event
90 const EventHandlerInfo& rInfo = getEventHandlerInfo( aEventQueue.front().mnEventId );
91 uno::Sequence< uno::Any > aEventArgs = aEventQueue.front().maArgs;
92 aEventQueue.pop_front();
93 SAL_INFO("vbahelper", "VbaEventsHelperBase::processVbaEvent( \"" << rInfo.maMacroName << "\" )");
94
95 /* Let derived classes prepare the event, they may add new events for
96 next iteration. If false is returned, the event handler must not be
97 called. */
98 if( implPrepareEvent( aEventQueue, rInfo, aEventArgs ) )
99 {
100 // search the event handler macro in the document
101 OUString aMacroPath = getEventHandlerPath( rInfo, aEventArgs );
102 if( !aMacroPath.isEmpty() )
103 {
104 // build the argument list
105 uno::Sequence< uno::Any > aVbaArgs = implBuildArgumentList( rInfo, aEventArgs );
106 // insert current cancel value
107 if( rInfo.mnCancelIndex >= 0 )
108 {
109 if( rInfo.mnCancelIndex >= aVbaArgs.getLength() )
110 throw lang::IllegalArgumentException();
111 aVbaArgs.getArray()[ rInfo.mnCancelIndex ] <<= bCancel;
112 }
113 // execute the event handler
114 uno::Any aRet, aCaller;
115 executeMacro( mpShell, aMacroPath, aVbaArgs, aRet, aCaller );
116 // extract new cancel value (may be boolean or any integer type)
117 if( rInfo.mnCancelIndex >= 0 )
118 {
119 checkArgument( aVbaArgs, rInfo.mnCancelIndex );
120 bCancel = extractBoolFromAny( aVbaArgs[ rInfo.mnCancelIndex ] );
121 }
122 // event handler has been found
123 bExecuted = true;
124 }
125 }
126 // post processing (also, if event handler does not exist, or disabled, or on error
127 implPostProcessEvent( aEventQueue, rInfo, bCancel );
128 }
129
130 // if event handlers want to cancel the event, do so regardless of any errors
131 if( bCancel )
132 throw util::VetoException();
133
134 // return true, if at least one event handler has been found
135 return bExecuted;
136}
137
138void SAL_CALL VbaEventsHelperBase::notifyEvent( const document::EventObject& rEvent )
139{
140 SAL_INFO("vbahelper", "VbaEventsHelperBase::notifyEvent( \"" << rEvent.EventName << "\" )");
141 if( rEvent.EventName == GlobalEventConfig::GetEventName( GlobalEventId::CLOSEDOC ) )
143}
144
145void SAL_CALL VbaEventsHelperBase::changesOccurred( const util::ChangesEvent& rEvent )
146{
147 // make sure the VBA library exists
148 try
149 {
151 }
152 catch( uno::Exception& )
153 {
154 return;
155 }
156
157 // check that the sender of the event is the VBA library
158 uno::Reference< script::vba::XVBAModuleInfo > xSender( rEvent.Base, uno::UNO_QUERY );
159 if( mxModuleInfos.get() != xSender.get() )
160 return;
161
162 // process all changed modules
163 for( const util::ElementChange& rChange : rEvent.Changes )
164 {
165 OUString aModuleName;
166 if( (rChange.Accessor >>= aModuleName) && !aModuleName.isEmpty() ) try
167 {
168 // invalidate event handler path map depending on module type
169 if( getModuleType( aModuleName ) == script::ModuleType::NORMAL )
170 // paths to global event handlers are stored with empty key (will be searched in all normal code modules)
171 maEventPaths.erase( OUString() );
172 else
173 // paths to class/form/document event handlers are keyed by module name
174 maEventPaths.erase( aModuleName );
175 }
176 catch( uno::Exception& )
177 {
178 }
179 }
180}
181
182void SAL_CALL VbaEventsHelperBase::disposing( const lang::EventObject& rEvent )
183{
184 uno::Reference< frame::XModel > xSender( rEvent.Source, uno::UNO_QUERY );
185 if( xSender.is() )
187}
188
190{
192}
193
194void VbaEventsHelperBase::processVbaEventNoThrow( sal_Int32 nEventId, const uno::Sequence< uno::Any >& rArgs )
195{
196 try
197 {
198 processVbaEvent( nEventId, rArgs );
199 }
200 catch( uno::Exception& )
201 {
202 }
203}
204
205// protected ------------------------------------------------------------------
206
207void VbaEventsHelperBase::registerEventHandler( sal_Int32 nEventId, sal_Int32 nModuleType,
208 const char* pcMacroName, sal_Int32 nCancelIndex, const uno::Any& rUserData )
209{
210 EventHandlerInfo& rInfo = maEventInfos[ nEventId ];
211 rInfo.mnEventId = nEventId;
212 rInfo.mnModuleType = nModuleType;
213 rInfo.maMacroName = OUString::createFromAscii( pcMacroName );
214 rInfo.mnCancelIndex = nCancelIndex;
215 rInfo.maUserData = rUserData;
216}
217
218// private --------------------------------------------------------------------
219
221{
222 if( mbDisposed )
223 return;
224
225 uno::Reference< document::XEventBroadcaster > xEventBroadcaster( mxModel, uno::UNO_QUERY );
226 if( xEventBroadcaster.is() )
227 try { xEventBroadcaster->addEventListener( this ); } catch( uno::Exception& ) {}
228}
229
231{
232 if( mbDisposed )
233 return;
234
235 uno::Reference< document::XEventBroadcaster > xEventBroadcaster( mxModel, uno::UNO_QUERY );
236 if( xEventBroadcaster.is() )
237 try { xEventBroadcaster->removeEventListener( this ); } catch( uno::Exception& ) {}
238
239 mxModel.clear();
240 mpShell = nullptr;
241 maEventInfos.clear();
242 mbDisposed = true;
243}
244
245sal_Bool SAL_CALL VbaEventsHelperBase::hasVbaEventHandler( sal_Int32 nEventId, const uno::Sequence< uno::Any >& rArgs )
246{
247 EventHandlerInfoMap::const_iterator aIt = maEventInfos.find( nEventId );
248 if( aIt == maEventInfos.end() )
249 return false; // throwing a lot of exceptions is slow.
250 else // getEventHandlerPath() searches for the macro in the document
251 return !getEventHandlerPath( aIt->second, rArgs ).isEmpty();
252}
253
255 sal_Int32 nEventId ) const
256{
257 EventHandlerInfoMap::const_iterator aIt = maEventInfos.find( nEventId );
258 if( aIt == maEventInfos.end() )
259 throw lang::IllegalArgumentException();
260 return aIt->second;
261}
262
264 const uno::Sequence< uno::Any >& rArgs )
265{
266 OUString aModuleName;
267 switch( rInfo.mnModuleType )
268 {
269 // global event handlers may exist in any standard code module
270 case script::ModuleType::NORMAL:
271 break;
272
273 // document event: get name of the code module associated to the event sender
274 case script::ModuleType::DOCUMENT:
275 aModuleName = implGetDocumentModuleName( rInfo, rArgs );
276 if( aModuleName.isEmpty() )
277 throw lang::IllegalArgumentException();
278 break;
279
280 default:
281 throw uno::RuntimeException("This module type is unsupported"); // unsupported module type
282 }
283
284 /* Performance improvement: Check the list of existing event handlers
285 instead of searching in Basic source code every time. */
286 EventHandlerPathMap::iterator aIt = maEventPaths.find( aModuleName );
287 ModulePathMap& rPathMap = (aIt == maEventPaths.end()) ? updateModulePathMap( aModuleName ) : aIt->second;
288 return rPathMap[ rInfo.mnEventId ];
289}
290
292{
293 if( mxModuleInfos.is() ) return;
294
295 try
296 {
298 if( maLibraryName.isEmpty() )
299 throw uno::RuntimeException();
300 uno::Reference< beans::XPropertySet > xModelProps( mxModel, uno::UNO_QUERY_THROW );
301 uno::Reference< container::XNameAccess > xBasicLibs( xModelProps->getPropertyValue(
302 "BasicLibraries" ), uno::UNO_QUERY_THROW );
303
304 if(!xBasicLibs->hasByName(maLibraryName) )
305 {
306 uno::Reference< script::XLibraryContainer > xLibContainer(
307 xModelProps->getPropertyValue("BasicLibraries"), uno::UNO_QUERY_THROW);
308 xLibContainer->createLibrary(maLibraryName);
309 }
310
311 mxModuleInfos.set( xBasicLibs->getByName( maLibraryName ), uno::UNO_QUERY_THROW );
312 // listen to changes in the VBA source code
313 uno::Reference< util::XChangesNotifier > xChangesNotifier( mxModuleInfos, uno::UNO_QUERY_THROW );
314 xChangesNotifier->addChangesListener( this );
315 }
316 catch( uno::Exception& )
317 {
318 // error accessing the Basic library, so this object is useless
320 throw uno::RuntimeException();
321 }
322}
323
324bool VbaEventsHelperBase::hasModule(const OUString& rModuleName)
325{
326 if (rModuleName.isEmpty())
327 return false;
328
329 bool bRet = false;
330 try
331 {
333 bRet = mxModuleInfos->hasModuleInfo(rModuleName);
334 }
335 catch (uno::Exception&)
336 {}
337
338 return bRet;
339}
340
341sal_Int32 VbaEventsHelperBase::getModuleType( const OUString& rModuleName )
342{
343 // make sure the VBA library exists
345
346 // no module specified: global event handler in standard code modules
347 if( rModuleName.isEmpty() )
348 return script::ModuleType::NORMAL;
349
350 // get module type from module info
351 try
352 {
353 return mxModuleInfos->getModuleInfo( rModuleName ).ModuleType;
354 }
355 catch( uno::Exception& )
356 {
357 }
358 throw uno::RuntimeException();
359}
360
362{
363 // get type of the specified module (throws on error)
364 sal_Int32 nModuleType = getModuleType( rModuleName );
365 // search for all event handlers
366 ModulePathMap& rPathMap = maEventPaths[ rModuleName ];
367
368 // Use WORKBOOK_OPEN as a way to get the codename for ThisWorkbook
369 OUString sThisWorkbook;
370 if (getImplementationName() == "ScVbaEventsHelper")
371 {
372 EventHandlerInfo& rThisWorksheetInfo
373 = maEventInfos[css::script::vba::VBAEventId::WORKBOOK_OPEN];
374 css::uno::Sequence<css::uno::Any> aNoArgs;
375 sThisWorkbook = implGetDocumentModuleName(rThisWorksheetInfo, aNoArgs);
376 }
377
378 // Use DOCUMENT_OPEN as a way to get the codename for ThisDocument
379 OUString sThisDocument;
380 if (getImplementationName() == "SwVbaEventsHelper")
381 {
382 EventHandlerInfo& rThisDocumentInfo
383 = maEventInfos[css::script::vba::VBAEventId::DOCUMENT_OPEN];
384 css::uno::Sequence<css::uno::Any> aNoArgs;
385 sThisDocument = implGetDocumentModuleName(rThisDocumentInfo, aNoArgs);
386 }
387
388 for( const auto& rEventInfo : maEventInfos )
389 {
390 const EventHandlerInfo& rInfo = rEventInfo.second;
391 if( rInfo.mnModuleType == nModuleType )
392 {
393 OUString sName;
394 bool bOnlyPublic = false;
395 OUString sSkipModule;
396 if (rInfo.mnEventId == css::script::vba::VBAEventId::AUTO_NEW
397 || rInfo.mnEventId == css::script::vba::VBAEventId::AUTO_OPEN
398 || rInfo.mnEventId == css::script::vba::VBAEventId::AUTO_CLOSE)
399 {
400 if (getImplementationName() == "ScVbaEventsHelper")
401 {
402 // Only in Calc, ignore Auto_* in ThisWorkbook
403 sSkipModule = sThisWorkbook;
404 }
405 else if (getImplementationName() == "SwVbaEventsHelper")
406 {
407 // Only in Word, Auto* only runs if defined as Public, not Private.
408 bOnlyPublic = true;
409 // Only in Word, auto* subroutines in ThisDocument have highest priority
410 sName = resolveVBAMacro(mpShell, maLibraryName, sThisDocument,
411 rInfo.maMacroName, bOnlyPublic, sSkipModule);
412 }
413 }
414
415 if (sName.isEmpty())
417 rInfo.maMacroName, bOnlyPublic, sSkipModule);
418
419 // Only in Word (with lowest priority), an Auto* module can execute a "Public Sub Main"
420 if (sName.isEmpty() && rModuleName.isEmpty()
421 && getImplementationName() == "SwVbaEventsHelper")
422 {
423 if (rInfo.mnEventId == css::script::vba::VBAEventId::AUTO_NEW
424 || rInfo.mnEventId == css::script::vba::VBAEventId::AUTO_OPEN
425 || rInfo.mnEventId == css::script::vba::VBAEventId::AUTO_CLOSE)
426 {
428 bOnlyPublic, sSkipModule);
429 }
430 }
431 rPathMap[rInfo.mnEventId] = sName;
432 }
433 }
434 return rPathMap;
435}
436
437
438/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
static OUString GetEventName(GlobalEventId nID)
virtual void SAL_CALL disposing(const css::lang::EventObject &rEvent) override
::std::deque< EventQueueEntry > EventQueue
void startListening()
Starts listening at the document model.
virtual void implPostProcessEvent(EventQueue &rEventQueue, const EventHandlerInfo &rInfo, bool bCancel)=0
Derived classes may do additional postprocessing.
void processVbaEventNoThrow(sal_Int32 nEventId, const css::uno::Sequence< css::uno::Any > &rArgs)
Helper to execute event handlers without throwing any exceptions.
EventHandlerInfoMap maEventInfos
EventHandlerPathMap maEventPaths
virtual void SAL_CALL notifyEvent(const css::document::EventObject &rEvent) override
OUString getEventHandlerPath(const EventHandlerInfo &rInfo, const css::uno::Sequence< css::uno::Any > &rArgs)
Searches the event handler in the document and returns its full script path.
virtual bool implPrepareEvent(EventQueue &rEventQueue, const EventHandlerInfo &rInfo, const css::uno::Sequence< css::uno::Any > &rArgs)=0
Derived classes do additional preparations and return whether the event handler has to be called.
virtual css::uno::Sequence< css::uno::Any > implBuildArgumentList(const EventHandlerInfo &rInfo, const css::uno::Sequence< css::uno::Any > &rArgs)=0
Derived classes have to return the argument list for the specified VBA event handler.
virtual sal_Bool SAL_CALL processVbaEvent(sal_Int32 nEventId, const css::uno::Sequence< css::uno::Any > &rArgs) override
ModulePathMap & updateModulePathMap(const OUString &rModuleName)
Updates the map containing paths to event handlers for a Basic module.
css::uno::Reference< css::script::vba::XVBAModuleInfo > mxModuleInfos
sal_Bool SAL_CALL supportsService(OUString const &ServiceName) override
virtual sal_Bool SAL_CALL hasVbaEventHandler(sal_Int32 nEventId, const css::uno::Sequence< css::uno::Any > &rArgs) override
sal_Int32 getModuleType(const OUString &rModuleName)
Returns the type of the Basic module with the specified name.
void registerEventHandler(sal_Int32 nEventId, sal_Int32 nModuleType, const char *pcMacroName, sal_Int32 nCancelIndex=-1, const css::uno::Any &rUserData=css::uno::Any())
Registers a supported event handler.
::std::map< sal_Int32, OUString > ModulePathMap
virtual OUString implGetDocumentModuleName(const EventHandlerInfo &rInfo, const css::uno::Sequence< css::uno::Any > &rArgs) const =0
Derived classes have to return the name of the Basic document module.
css::uno::Reference< css::frame::XModel > mxModel
VbaEventsHelperBase(const css::uno::Sequence< css::uno::Any > &rArgs)
void stopListening()
Stops listening at the document model.
static void checkArgument(const css::uno::Sequence< css::uno::Any > &rArgs, sal_Int32 nIndex)
bool hasModule(const OUString &rModuleName)
virtual void SAL_CALL changesOccurred(const css::util::ChangesEvent &rEvent) override
const EventHandlerInfo & getEventHandlerInfo(sal_Int32 nEventId) const
Returns the event handler info struct for the specified event, or throws.
void ensureVBALibrary()
On first call, accesses the Basic library containing the VBA source code.
virtual ~VbaEventsHelperBase() override
OUString sName
#define SAL_WARN_IF(condition, area, stream)
#define SAL_INFO(area, stream)
OUString getImplementationName()
bool CPPUHELPER_DLLPUBLIC supportsService(css::lang::XServiceInfo *implementation, rtl::OUString const &name)
bool extractBoolFromAny(const uno::Any &rAny)
Definition: vbahelper.cxx:444
bool executeMacro(SfxObjectShell *pShell, const OUString &sMacroName, uno::Sequence< uno::Any > &aArgs, uno::Any &aRet, const uno::Any &)
MSFILTER_DLLPUBLIC OUString getDefaultProjectName(SfxObjectShell const *pShell)
SfxObjectShell * getSfxObjShell(const uno::Reference< frame::XModel > &xModel)
Definition: vbahelper.cxx:1113
OUString resolveVBAMacro(SfxObjectShell const *pShell, const OUString &rLibName, const OUString &rModuleName, const OUString &rMacroName, bool bOnlyPublic, const OUString &sSkipModule)
unsigned char sal_Bool