1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
20 #include <config_features.h>
21 #include <config_folders.h>
23 #include <tools/debug.hxx>
24 #include <svl/eitem.hxx>
25 #include <svl/stritem.hxx>
26 #include <svl/intitem.hxx>
27 #include <svl/itemset.hxx>
28 #include <svl/visitem.hxx>
29 #include <svtools/javacontext.hxx>
30 #include <svtools/javainteractionhandler.hxx>
31 #include <svl/itempool.hxx>
32 #include <tools/urlobj.hxx>
33 #include <toolkit/helper/vclunohelper.hxx>
34 #include <com/sun/star/awt/FontDescriptor.hpp>
35 #include <com/sun/star/util/URLTransformer.hpp>
36 #include <com/sun/star/util/XURLTransformer.hpp>
37 #include <com/sun/star/frame/Desktop.hpp>
38 #include <com/sun/star/frame/XFrame.hpp>
39 #include <com/sun/star/frame/status/FontHeight.hpp>
40 #include <com/sun/star/frame/status/ItemStatus.hpp>
41 #include <com/sun/star/frame/status/ItemState.hpp>
42 #include <com/sun/star/frame/status/Template.hpp>
43 #include <com/sun/star/frame/DispatchResultState.hpp>
44 #include <com/sun/star/frame/ModuleManager.hpp>
45 #include <com/sun/star/frame/status/Visibility.hpp>
46 #include <comphelper/processfactory.hxx>
47 #include <comphelper/sequence.hxx>
48 #include <officecfg/Office/Common.hxx>
49 #include <uno/current_context.hxx>
50 #include <vcl/svapp.hxx>
51 #include <vcl/uitest/logger.hxx>
53 #include <sfx2/app.hxx>
54 #include <sfx2/unoctitm.hxx>
55 #include <sfx2/viewfrm.hxx>
56 #include <sfx2/frame.hxx>
57 #include <sfx2/ctrlitem.hxx>
58 #include <sfx2/sfxuno.hxx>
59 #include <sfx2/bindings.hxx>
60 #include <sfx2/dispatch.hxx>
61 #include <sfx2/sfxsids.hrc>
62 #include <sfx2/request.hxx>
63 #include <statcach.hxx>
64 #include <sfx2/msgpool.hxx>
65 #include <sfx2/objsh.hxx>
66 #include <sfx2/viewsh.hxx>
67 #include <osl/file.hxx>
68 #include <rtl/ustring.hxx>
69 #include <unotools/pathoptions.hxx>
76 #include <o3tl/make_unique.hxx>
78 #include <sal/log.hxx>
79 #include <LibreOfficeKit/LibreOfficeKitEnums.h>
80 #include <comphelper/lok.hxx>
82 using namespace ::com::sun::star
;
83 using namespace ::com::sun::star::uno
;
84 using namespace ::com::sun::star::util
;
99 const char* const URLTypeNames
[URLType_COUNT
] =
111 static void InterceptLOKStateChangeEvent( const SfxViewFrame
* pViewFrame
, const css::frame::FeatureStateEvent
& aEvent
, const SfxPoolItem
* pState
);
113 void SfxStatusDispatcher::ReleaseAll()
115 css::lang::EventObject aObject
;
116 aObject
.Source
= static_cast<cppu::OWeakObject
*>(this);
117 aListeners
.disposeAndClear( aObject
);
120 void SAL_CALL
SfxStatusDispatcher::dispatch( const css::util::URL
&, const css::uno::Sequence
< css::beans::PropertyValue
>& )
124 void SAL_CALL
SfxStatusDispatcher::dispatchWithNotification(
125 const css::util::URL
&,
126 const css::uno::Sequence
< css::beans::PropertyValue
>&,
127 const css::uno::Reference
< css::frame::XDispatchResultListener
>& )
131 SfxStatusDispatcher::SfxStatusDispatcher()
132 : aListeners( aMutex
)
136 void SAL_CALL
SfxStatusDispatcher::addStatusListener(const css::uno::Reference
< css::frame::XStatusListener
> & aListener
, const css::util::URL
& aURL
)
138 aListeners
.addInterface( aURL
.Complete
, aListener
);
139 if ( aURL
.Complete
== ".uno:LifeTime" )
141 css::frame::FeatureStateEvent aEvent
;
142 aEvent
.FeatureURL
= aURL
;
143 aEvent
.Source
= static_cast<css::frame::XDispatch
*>(this);
144 aEvent
.IsEnabled
= true;
145 aEvent
.Requery
= false;
146 aListener
->statusChanged( aEvent
);
150 void SAL_CALL
SfxStatusDispatcher::removeStatusListener( const css::uno::Reference
< css::frame::XStatusListener
> & aListener
, const css::util::URL
& aURL
)
152 aListeners
.removeInterface( aURL
.Complete
, aListener
);
157 sal_Int64 SAL_CALL
SfxOfficeDispatch::getSomething( const css::uno::Sequence
< sal_Int8
>& aIdentifier
)
159 if ( aIdentifier
== impl_getStaticIdentifier() )
160 return sal::static_int_cast
< sal_Int64
>( reinterpret_cast< sal_IntPtr
>( this ));
165 SfxOfficeDispatch::SfxOfficeDispatch( SfxBindings
& rBindings
, SfxDispatcher
* pDispat
, const SfxSlot
* pSlot
, const css::util::URL
& rURL
)
166 : pImpl( new SfxDispatchController_Impl( this, &rBindings
, pDispat
, pSlot
, rURL
))
168 // pImpl is an adapter that shows a css::frame::XDispatch-Interface to the outside and uses a SfxControllerItem to monitor a state
172 SfxOfficeDispatch::SfxOfficeDispatch( SfxDispatcher
* pDispat
, const SfxSlot
* pSlot
, const css::util::URL
& rURL
)
173 : pImpl( new SfxDispatchController_Impl( this, nullptr, pDispat
, pSlot
, rURL
))
175 // pImpl is an adapter that shows a css::frame::XDispatch-Interface to the outside and uses a SfxControllerItem to monitor a state
178 SfxOfficeDispatch::~SfxOfficeDispatch()
182 // when dispatch object is released, destroy its connection to this object and destroy it
183 pImpl
->UnBindController();
187 const css::uno::Sequence
< sal_Int8
>& SfxOfficeDispatch::impl_getStaticIdentifier()
189 // {38 57 CA 80 09 36 11 d4 83 FE 00 50 04 52 6B 21}
190 static const sal_uInt8 pGUID
[16] = { 0x38, 0x57, 0xCA, 0x80, 0x09, 0x36, 0x11, 0xd4, 0x83, 0xFE, 0x00, 0x50, 0x04, 0x52, 0x6B, 0x21 };
191 static css::uno::Sequence
< sal_Int8
> seqID(reinterpret_cast<const sal_Int8
*>(pGUID
), 16) ;
195 #if HAVE_FEATURE_JAVA
196 // The JavaContext contains an interaction handler which is used when
197 // the creation of a Java Virtual Machine fails. There shall only be one
198 // user notification (message box) even if the same error (interaction)
199 // reoccurs. The effect is, that if a user selects a menu entry than they
200 // may get only one notification that a JRE is not selected.
201 // This function checks if a JavaContext is already available (typically
202 // created by Desktop::Main() in app.cxx), and creates new one if not.
204 std::unique_ptr
< css::uno::ContextLayer
> EnsureJavaContext()
206 css::uno::Reference
< css::uno::XCurrentContext
> xContext(css::uno::getCurrentContext());
209 css::uno::Reference
< css::task::XInteractionHandler
> xHandler
;
210 xContext
->getValueByName(JAVA_INTERACTION_HANDLER_NAME
) >>= xHandler
;
212 return nullptr; // No need to add new layer: JavaContext already present
214 return o3tl::make_unique
< css::uno::ContextLayer
>(new svt::JavaContext(xContext
));
219 void SAL_CALL
SfxOfficeDispatch::dispatch( const css::util::URL
& aURL
, const css::uno::Sequence
< css::beans::PropertyValue
>& aArgs
)
221 // ControllerItem is the Impl class
224 #if HAVE_FEATURE_JAVA
225 std::unique_ptr
< css::uno::ContextLayer
> layer(EnsureJavaContext());
227 pImpl
->dispatch( aURL
, aArgs
, css::uno::Reference
< css::frame::XDispatchResultListener
>() );
231 void SAL_CALL
SfxOfficeDispatch::dispatchWithNotification( const css::util::URL
& aURL
,
232 const css::uno::Sequence
< css::beans::PropertyValue
>& aArgs
,
233 const css::uno::Reference
< css::frame::XDispatchResultListener
>& rListener
)
235 // ControllerItem is the Impl class
238 #if HAVE_FEATURE_JAVA
239 std::unique_ptr
< css::uno::ContextLayer
> layer(EnsureJavaContext());
241 pImpl
->dispatch( aURL
, aArgs
, rListener
);
245 void SAL_CALL
SfxOfficeDispatch::addStatusListener(const css::uno::Reference
< css::frame::XStatusListener
> & aListener
, const css::util::URL
& aURL
)
247 GetListeners().addInterface( aURL
.Complete
, aListener
);
250 // ControllerItem is the Impl class
251 pImpl
->addStatusListener( aListener
, aURL
);
255 SfxDispatcher
* SfxOfficeDispatch::GetDispatcher_Impl()
257 return pImpl
->GetDispatcher();
260 void SfxOfficeDispatch::SetFrame(const css::uno::Reference
< css::frame::XFrame
>& xFrame
)
263 pImpl
->SetFrame( xFrame
);
266 void SfxOfficeDispatch::SetMasterUnoCommand( bool bSet
)
269 pImpl
->setMasterSlaveCommand( bSet
);
272 // Determine if URL contains a master/slave command which must be handled a little bit different
273 bool SfxOfficeDispatch::IsMasterUnoCommand( const css::util::URL
& aURL
)
275 return aURL
.Protocol
== ".uno:" && ( aURL
.Path
.indexOf( '.' ) > 0 );
278 OUString
SfxOfficeDispatch::GetMasterUnoCommand( const css::util::URL
& aURL
)
280 OUString aMasterCommand
;
281 if ( IsMasterUnoCommand( aURL
))
283 sal_Int32 nIndex
= aURL
.Path
.indexOf( '.' );
285 aMasterCommand
= aURL
.Path
.copy( 0, nIndex
);
288 return aMasterCommand
;
291 SfxDispatchController_Impl::SfxDispatchController_Impl(
292 SfxOfficeDispatch
* pDisp
,
294 SfxDispatcher
* pDispat
,
295 const SfxSlot
* pSlot
,
296 const css::util::URL
& rURL
)
297 : aDispatchURL( rURL
)
298 , pDispatcher( pDispat
)
300 , pLastState( nullptr )
301 , nSlot( pSlot
->GetSlotId() )
303 , bMasterSlave( false )
305 , pUnoName( pSlot
->pUnoName
)
307 if ( aDispatchURL
.Protocol
== "slot:" && pUnoName
)
309 OStringBuffer
aTmp(".uno:");
310 aTmp
.append(pUnoName
);
311 aDispatchURL
.Complete
= OStringToOUString(aTmp
.makeStringAndClear(), RTL_TEXTENCODING_ASCII_US
);
312 Reference
< XURLTransformer
> xTrans( URLTransformer::create( ::comphelper::getProcessComponentContext() ) );
313 xTrans
->parseStrict( aDispatchURL
);
319 // Bind immediately to enable the cache to recycle dispatches when asked for the same command
320 // a command in "slot" or in ".uno" notation must be treated as identical commands!
321 pBindings
->ENTERREGISTRATIONS();
322 BindInternal_Impl( nSlot
, pBindings
);
323 pBindings
->LEAVEREGISTRATIONS();
327 SfxDispatchController_Impl::~SfxDispatchController_Impl()
329 if ( pLastState
&& !IsInvalidItem( pLastState
) )
335 pDispatch
->pImpl
= nullptr;
337 // force all listeners to release the dispatch object
338 css::lang::EventObject aObject
;
339 aObject
.Source
= static_cast<cppu::OWeakObject
*>(pDispatch
);
340 pDispatch
->GetListeners().disposeAndClear( aObject
);
344 void SfxDispatchController_Impl::SetFrame(const css::uno::Reference
< css::frame::XFrame
>& _xFrame
)
349 void SfxDispatchController_Impl::setMasterSlaveCommand( bool bSet
)
354 void SfxDispatchController_Impl::UnBindController()
359 GetBindings().ENTERREGISTRATIONS();
360 SfxControllerItem::UnBind();
361 GetBindings().LEAVEREGISTRATIONS();
365 void SfxDispatchController_Impl::addParametersToArgs( const css::util::URL
& aURL
, css::uno::Sequence
< css::beans::PropertyValue
>& rArgs
)
367 // Extract the parameter from the URL and put them into the property value sequence
368 sal_Int32 nQueryIndex
= aURL
.Complete
.indexOf( '?' );
369 if ( nQueryIndex
> 0 )
371 OUString
aParamString( aURL
.Complete
.copy( nQueryIndex
+1 ));
372 sal_Int32 nIndex
= 0;
375 OUString aToken
= aParamString
.getToken( 0, '&', nIndex
);
377 sal_Int32 nParmIndex
= 0;
379 OUString aParamName
= aToken
.getToken( 0, '=', nParmIndex
);
380 OUString aValue
= (nParmIndex
!=-1) ? aToken
.getToken( 0, '=', nParmIndex
) : OUString();
382 if ( !aParamName
.isEmpty() )
386 aParamName
= aToken
.getToken( 0, ':', nParmIndex
);
387 aParamType
= (nParmIndex
!=-1) ? aToken
.getToken( 0, ':', nParmIndex
) : OUString();
390 sal_Int32 nLen
= rArgs
.getLength();
391 rArgs
.realloc( nLen
+1 );
392 rArgs
[nLen
].Name
= aParamName
;
394 if ( aParamType
.isEmpty() )
397 rArgs
[nLen
].Value
<<= aValue
.toInt32();
399 else if ( aParamType
.equalsAsciiL( URLTypeNames
[URLType_BOOL
], 4 ))
402 rArgs
[nLen
].Value
<<= aValue
.toBoolean();
404 else if ( aParamType
.equalsAsciiL( URLTypeNames
[URLType_BYTE
], 4 ))
407 rArgs
[nLen
].Value
<<= sal_Int8( aValue
.toInt32() );
409 else if ( aParamType
.equalsAsciiL( URLTypeNames
[URLType_LONG
], 4 ))
412 rArgs
[nLen
].Value
<<= aValue
.toInt32();
414 else if ( aParamType
.equalsAsciiL( URLTypeNames
[URLType_SHORT
], 5 ))
417 rArgs
[nLen
].Value
<<= sal_Int16( aValue
.toInt32() );
419 else if ( aParamType
.equalsAsciiL( URLTypeNames
[URLType_HYPER
], 5 ))
422 rArgs
[nLen
].Value
<<= aValue
.toInt64();
424 else if ( aParamType
.equalsAsciiL( URLTypeNames
[URLType_FLOAT
], 5 ))
427 rArgs
[nLen
].Value
<<= aValue
.toFloat();
429 else if ( aParamType
.equalsAsciiL( URLTypeNames
[URLType_STRING
], 6 ))
432 rArgs
[nLen
].Value
<<= INetURLObject::decode( aValue
, INetURLObject::DecodeMechanism::WithCharset
);
434 else if ( aParamType
.equalsAsciiL( URLTypeNames
[URLType_DOUBLE
], 6))
437 rArgs
[nLen
].Value
<<= aValue
.toDouble();
440 while ( nIndex
>= 0 );
444 MapUnit
SfxDispatchController_Impl::GetCoreMetric( SfxItemPool
const & rPool
, sal_uInt16 nSlotId
)
446 sal_uInt16 nWhich
= rPool
.GetWhich( nSlotId
);
447 return rPool
.GetMetric( nWhich
);
450 OUString
SfxDispatchController_Impl::getSlaveCommand( const css::util::URL
& rURL
)
452 OUString aSlaveCommand
;
453 sal_Int32 nIndex
= rURL
.Path
.indexOf( '.' );
454 if (( nIndex
> 0 ) && ( nIndex
< rURL
.Path
.getLength() ))
455 aSlaveCommand
= rURL
.Path
.copy( nIndex
+1 );
456 return aSlaveCommand
;
461 /// Class that collects the usage information - how many times what .uno: command was used.
464 typedef std::map
<OUString
, int> UsageMap
;
466 /// Are we collecting the info? We cache the value because the call to save can happen very late.
469 /// Command vs. how many times it was used
472 /// config path, get it long before atexit time
473 OUString msConfigPath
;
476 UsageInfo() : mbIsCollecting(false)
485 /// Increment command's use.
486 void increment(const OUString
&rCommand
);
488 /// Save the usage data for the next session.
491 /// Modify the flag whether we are collecting.
492 void setCollecting(bool bIsCollecting
)
494 mbIsCollecting
= bIsCollecting
;
497 msConfigPath
= SvtPathOptions().GetConfigPath();
498 msConfigPath
+= "usage/";
503 void UsageInfo::increment(const OUString
&rCommand
)
505 UsageMap::iterator it
= maUsage
.find(rCommand
);
507 if (it
!= maUsage
.end())
510 maUsage
[rCommand
] = 1;
513 void UsageInfo::save()
518 osl::Directory::createPath(msConfigPath
);
520 //get system time information.
521 TimeValue systemTime
;
523 oslDateTime localDateTime
;
524 osl_getSystemTime( &systemTime
);
525 osl_getLocalTimeFromSystemTime( &systemTime
, &localTime
);
526 osl_getDateTimeFromTimeValue( &localTime
, &localDateTime
);
529 sprintf(time
,"%4i-%02i-%02iT%02i_%02i_%02i", localDateTime
.Year
, localDateTime
.Month
, localDateTime
.Day
, localDateTime
.Hours
, localDateTime
.Minutes
, localDateTime
.Seconds
);
531 //filename type: usage-YYYY-MM-DDTHH_MM_SS.csv
532 OUString filename
= "usage-" + OUString::createFromAscii(time
) + ".csv";
533 OUString path
= msConfigPath
+ filename
;
535 osl::File
file(path
);
537 if( file
.open(osl_File_OpenFlag_Read
| osl_File_OpenFlag_Write
| osl_File_OpenFlag_Create
) == osl::File::E_None
)
539 OString aUsageInfoMsg
= "Document Type;Command;Count";
541 for (UsageMap::const_iterator it
= maUsage
.begin(); it
!= maUsage
.end(); ++it
)
542 aUsageInfoMsg
+= "\n" + it
->first
.toUtf8() + ";" + OString::number(it
->second
);
544 sal_uInt64 written
= 0;
545 file
.write(aUsageInfoMsg
.pData
->buffer
, aUsageInfoMsg
.getLength(), written
);
550 class theUsageInfo
: public rtl::Static
<UsageInfo
, theUsageInfo
> {};
552 /// Extracts information about the command + args, and stores that.
553 void collectUsageInformation(const util::URL
& rURL
, const uno::Sequence
<beans::PropertyValue
>& rArgs
)
555 bool bCollecting
= getenv("LO_COLLECT_USAGE") || officecfg::Office::Common::Misc::CollectUsageInformation::get();
556 theUsageInfo::get().setCollecting(bCollecting
);
560 OUStringBuffer aBuffer
;
562 // app identification [uh, several UNO calls :-(]
563 uno::Reference
<uno::XComponentContext
> xContext
= ::comphelper::getProcessComponentContext();
564 uno::Reference
<frame::XModuleManager2
> xModuleManager(frame::ModuleManager::create(xContext
));
565 uno::Reference
<frame::XDesktop2
> xDesktop
= frame::Desktop::create(xContext
);
566 uno::Reference
<frame::XFrame
> xFrame
= xDesktop
->getCurrentFrame();
568 OUString
aModule(xModuleManager
->identify(xFrame
));
569 sal_Int32 nLastDot
= aModule
.lastIndexOf('.');
571 aModule
= aModule
.copy(nLastDot
+ 1);
573 aBuffer
.append(aModule
);
577 aBuffer
.append(rURL
.Protocol
);
578 aBuffer
.append(rURL
.Path
);
579 sal_Int32 nCount
= rArgs
.getLength();
581 // parameters - only their names, not the values (could be sensitive!)
585 for (sal_Int32 n
= 0; n
< nCount
; n
++)
587 const css::beans::PropertyValue
& rProp
= rArgs
[n
];
590 aBuffer
.append(rProp
.Name
);
595 OUString
aCommand(aBuffer
.makeStringAndClear());
598 theUsageInfo::get().increment(aCommand
);
601 void collectUIInformation(const util::URL
& rURL
)
603 static const char* pFile
= std::getenv("LO_COLLECT_UIINFO");
607 UITestLogger::getInstance().logCommand(rURL
.Complete
);
612 void SfxDispatchController_Impl::dispatch( const css::util::URL
& aURL
,
613 const css::uno::Sequence
< css::beans::PropertyValue
>& aArgs
,
614 const css::uno::Reference
< css::frame::XDispatchResultListener
>& rListener
)
616 collectUsageInformation(aURL
, aArgs
);
617 collectUIInformation(aURL
);
619 SolarMutexGuard aGuard
;
623 (aURL
.Protocol
== ".uno:" && aURL
.Path
== aDispatchURL
.Path
) ||
624 (aURL
.Protocol
== "slot:" && aURL
.Path
.toInt32() == GetId())
628 if ( !pDispatcher
&& pBindings
)
629 pDispatcher
= GetBindings().GetDispatcher_Impl();
631 css::uno::Sequence
< css::beans::PropertyValue
> lNewArgs
;
632 sal_Int32 nCount
= aArgs
.getLength();
634 // Support for URL based arguments
635 INetURLObject
aURLObj( aURL
.Complete
);
636 if ( aURLObj
.HasParam() )
637 addParametersToArgs( aURL
, lNewArgs
);
639 // Try to find call mode and frame name inside given arguments...
640 SfxCallMode nCall
= SfxCallMode::RECORD
;
641 sal_Int32 nMarkArg
= -1;
643 VclPtr
<vcl::Window
> xDialogParent
;
645 // Filter arguments which shouldn't be part of the sequence property value
646 sal_uInt16
nModifier(0);
647 std::vector
< css::beans::PropertyValue
> aAddArgs
;
648 for( sal_Int32 n
=0; n
<nCount
; n
++ )
650 const css::beans::PropertyValue
& rProp
= aArgs
[n
];
651 if( rProp
.Name
== "SynchronMode" )
654 if( rProp
.Value
>>= bTemp
)
655 nCall
= bTemp
? SfxCallMode::SYNCHRON
: SfxCallMode::ASYNCHRON
;
657 else if( rProp
.Name
== "DialogParent" )
659 Reference
<css::awt::XWindow
> xWindow
;
660 if (rProp
.Value
>>= xWindow
)
661 xDialogParent
= VCLUnoHelper::GetWindow(xWindow
);
663 else if( rProp
.Name
== "Bookmark" )
666 aAddArgs
.push_back( aArgs
[n
] );
668 else if( rProp
.Name
== "KeyModifier" )
669 rProp
.Value
>>= nModifier
;
671 aAddArgs
.push_back( aArgs
[n
] );
674 // Add needed arguments to sequence property value
675 sal_uInt32 nAddArgs
= aAddArgs
.size();
678 sal_uInt32
nIndex( lNewArgs
.getLength() );
680 lNewArgs
.realloc( lNewArgs
.getLength()+aAddArgs
.size() );
681 for ( sal_uInt32 i
= 0; i
< nAddArgs
; i
++ )
682 lNewArgs
[nIndex
++] = aAddArgs
[i
];
685 // Overwrite possible detected synchron argument, if real listener exists (currently no other way)
686 if ( rListener
.is() )
687 nCall
= SfxCallMode::SYNCHRON
;
689 if( GetId() == SID_JUMPTOMARK
&& nMarkArg
== - 1 )
691 // we offer dispatches for SID_JUMPTOMARK if the URL points to a bookmark inside the document
692 // so we must retrieve this as an argument from the parsed URL
693 lNewArgs
.realloc( lNewArgs
.getLength()+1 );
694 nMarkArg
= lNewArgs
.getLength()-1;
695 lNewArgs
[nMarkArg
].Name
= "Bookmark";
696 lNewArgs
[nMarkArg
].Value
<<= aURL
.Mark
;
699 css::uno::Reference
< css::frame::XFrame
> xFrameRef(xFrame
.get(), css::uno::UNO_QUERY
);
700 if (! xFrameRef
.is() && pDispatcher
)
702 SfxViewFrame
* pViewFrame
= pDispatcher
->GetFrame();
704 xFrameRef
= pViewFrame
->GetFrame().GetFrameInterface();
707 bool bSuccess
= false;
708 const SfxPoolItem
* pItem
= nullptr;
709 MapUnit
eMapUnit( MapUnit::Map100thMM
);
711 // Extra scope so that aInternalSet is destroyed before
712 // rListener->dispatchFinished potentially calls
713 // framework::Desktop::terminate -> SfxApplication::Deinitialize ->
717 SfxAllItemSet
aInternalSet( SfxGetpApp()->GetPool() );
718 if (xFrameRef
.is()) // an empty set is no problem ... but an empty frame reference can be a problem !
719 aInternalSet
.Put( SfxUnoFrameItem( SID_FILLFRAME
, xFrameRef
) );
721 SfxShell
* pShell( nullptr );
722 // #i102619# Retrieve metric from shell before execution - the shell could be destroyed after execution
723 if ( pDispatcher
->GetBindings() )
725 if ( !pDispatcher
->IsLocked() )
727 const SfxSlot
*pSlot
= nullptr;
728 if ( pDispatcher
->GetShellAndSlot_Impl( GetId(), &pShell
, &pSlot
, false,
729 SfxCallMode::MODAL
==(nCall
&SfxCallMode::MODAL
), false ) )
733 // Extract slave command and add argument to the args list. Master slot MUST
734 // have a argument that has the same name as the master slot and type is SfxStringItem.
735 sal_Int32 nIndex
= lNewArgs
.getLength();
736 lNewArgs
.realloc( nIndex
+1 );
737 lNewArgs
[nIndex
].Name
= OUString::createFromAscii( pSlot
->pUnoName
);
738 lNewArgs
[nIndex
].Value
<<= SfxDispatchController_Impl::getSlaveCommand( aDispatchURL
);
741 eMapUnit
= GetCoreMetric( pShell
->GetPool(), GetId() );
742 std::unique_ptr
<SfxAllItemSet
> xSet(new SfxAllItemSet(pShell
->GetPool()));
743 TransformParameters(GetId(), lNewArgs
, *xSet
, pSlot
);
746 // execute with arguments - call directly
747 pItem
= pDispatcher
->Execute(GetId(), nCall
, xSet
.get(), &aInternalSet
, nModifier
, xDialogParent
);
748 if ( pItem
!= nullptr )
750 if (const SfxBoolItem
* pBoolItem
= dynamic_cast<const SfxBoolItem
*>(pItem
))
751 bSuccess
= pBoolItem
->GetValue();
752 else if ( !pItem
->IsVoidItem() )
753 bSuccess
= true; // all other types are true
755 // else bSuccess = false look to line 664 it is false
759 // Be sure to delete this before we send a dispatch
760 // request, which will destroy the current shell.
763 // execute using bindings, enables support for toggle/enum etc.
764 SfxRequest
aReq( GetId(), nCall
, pShell
->GetPool() );
765 aReq
.SetModifier( nModifier
);
766 aReq
.SetInternalArgs_Impl(aInternalSet
);
767 pDispatcher
->GetBindings()->Execute_Impl( aReq
, pSlot
, pShell
);
768 pItem
= aReq
.GetReturnValue();
769 bSuccess
= aReq
.IsDone() || pItem
!= nullptr;
773 SAL_INFO("sfx.control", "MacroPlayer: Unknown slot dispatched!");
778 eMapUnit
= GetCoreMetric( SfxGetpApp()->GetPool(), GetId() );
780 SfxAllItemSet
aSet( SfxGetpApp()->GetPool() );
781 TransformParameters( GetId(), lNewArgs
, aSet
);
784 pItem
= pDispatcher
->Execute(GetId(), nCall
, &aSet
, &aInternalSet
, nModifier
, xDialogParent
);
786 // SfxRequests take empty sets as argument sets, GetArgs() returning non-zero!
787 pItem
= pDispatcher
->Execute(GetId(), nCall
, nullptr, &aInternalSet
, nModifier
, xDialogParent
);
789 // no bindings, no invalidate ( usually done in SfxDispatcher::Call_Impl()! )
790 if (SfxApplication
* pApp
= SfxApplication::Get())
792 SfxDispatcher
* pAppDispat
= pApp
->GetAppDispatcher_Impl();
795 const SfxPoolItem
* pState
=nullptr;
796 SfxItemState eState
= pDispatcher
->QueryState( GetId(), pState
);
797 StateChanged( GetId(), eState
, pState
);
801 bSuccess
= (pItem
!= nullptr);
805 if ( rListener
.is() )
807 css::frame::DispatchResultEvent aEvent
;
809 aEvent
.State
= css::frame::DispatchResultState::SUCCESS
;
811 aEvent
.State
= css::frame::DispatchResultState::FAILURE
;
813 aEvent
.Source
= static_cast<css::frame::XDispatch
*>(pDispatch
);
814 if ( bSuccess
&& pItem
&& !pItem
->IsVoidItem() )
816 sal_uInt16
nSubId( 0 );
817 if ( eMapUnit
== MapUnit::MapTwip
)
818 nSubId
|= CONVERT_TWIPS
;
819 pItem
->QueryValue( aEvent
.Result
, (sal_uInt8
)nSubId
);
822 rListener
->dispatchFinished( aEvent
);
827 SfxDispatcher
* SfxDispatchController_Impl::GetDispatcher()
829 if ( !pDispatcher
&& pBindings
)
830 pDispatcher
= GetBindings().GetDispatcher_Impl();
834 void SfxDispatchController_Impl::addStatusListener(const css::uno::Reference
< css::frame::XStatusListener
> & aListener
, const css::util::URL
& aURL
)
836 SolarMutexGuard aGuard
;
840 // Use alternative QueryState call to have a valid UNO representation of the state.
841 css::uno::Any aState
;
842 if ( !pDispatcher
&& pBindings
)
843 pDispatcher
= GetBindings().GetDispatcher_Impl();
844 SfxItemState eState
= pDispatcher
? pDispatcher
->QueryState( GetId(), aState
) : SfxItemState::DONTCARE
;
846 if ( eState
== SfxItemState::DONTCARE
)
848 // Use special uno struct to transport don't care state
849 css::frame::status::ItemStatus aItemStatus
;
850 aItemStatus
.State
= css::frame::status::ItemState::DONT_CARE
;
851 aState
<<= aItemStatus
;
854 css::frame::FeatureStateEvent aEvent
;
855 aEvent
.FeatureURL
= aURL
;
856 aEvent
.Source
= static_cast<css::frame::XDispatch
*>(pDispatch
);
857 aEvent
.Requery
= false;
860 aEvent
.IsEnabled
= eState
!= SfxItemState::DISABLED
;
861 aEvent
.State
= aState
;
865 css::frame::status::Visibility aVisibilityStatus
;
866 aVisibilityStatus
.bVisible
= false;
868 // MBA: we might decide to *not* disable "invisible" slots, but this would be
869 // a change that needs to adjust at least the testtool
870 aEvent
.IsEnabled
= false;
871 aEvent
.State
<<= aVisibilityStatus
;
874 aListener
->statusChanged( aEvent
);
877 void SfxDispatchController_Impl::sendStatusChanged(const OUString
& rURL
, const css::frame::FeatureStateEvent
& rEvent
)
879 ::cppu::OInterfaceContainerHelper
* pContnr
= pDispatch
->GetListeners().getContainer(rURL
);
882 ::cppu::OInterfaceIteratorHelper
aIt(*pContnr
);
883 while (aIt
.hasMoreElements())
887 static_cast<css::frame::XStatusListener
*>(aIt
.next())->statusChanged(rEvent
);
889 catch (const css::uno::RuntimeException
&)
896 void SfxDispatchController_Impl::StateChanged( sal_uInt16 nSID
, SfxItemState eState
, const SfxPoolItem
* pState
, SfxSlotServer
const * pSlotServ
)
901 // Bindings instance notifies controller about a state change, listeners must be notified also
902 // Don't cache visibility state changes as they are volatile. We need our real state to send it
903 // to our controllers after visibility is set to true.
905 if ( pState
&& !IsInvalidItem( pState
) )
907 if ( dynamic_cast< const SfxVisibilityItem
*>( pState
) == nullptr )
909 if (pLastState
&& !IsInvalidItem(pLastState
))
911 bNotify
= typeid(*pState
) != typeid(*pLastState
) || *pState
!= *pLastState
;
914 pLastState
= !IsInvalidItem(pState
) ? pState
->Clone() : pState
;
918 bVisible
= static_cast<const SfxVisibilityItem
*>(pState
)->GetValue();
922 if ( pLastState
&& !IsInvalidItem( pLastState
) )
929 css::uno::Any aState
;
930 if ( ( eState
>= SfxItemState::DEFAULT
) && pState
&& !IsInvalidItem( pState
) && !pState
->IsVoidItem() )
932 // Retrieve metric from pool to have correct sub ID when calling QueryValue
933 sal_uInt16
nSubId( 0 );
934 MapUnit
eMapUnit( MapUnit::Map100thMM
);
936 // retrieve the core metric
937 // it's enough to check the objectshell, the only shell that does not use the pool of the document
938 // is SfxViewFrame, but it hasn't any metric parameters
939 // TODO/LATER: what about the FormShell? Does it use any metric data?! Perhaps it should use the Pool of the document!
940 if ( pSlotServ
&& pDispatcher
)
942 SfxShell
* pShell
= pDispatcher
->GetShell( pSlotServ
->GetShellLevel() );
943 DBG_ASSERT( pShell
, "Can't get core metric without shell!" );
945 eMapUnit
= GetCoreMetric( pShell
->GetPool(), nSID
);
948 if ( eMapUnit
== MapUnit::MapTwip
)
949 nSubId
|= CONVERT_TWIPS
;
951 pState
->QueryValue( aState
, (sal_uInt8
)nSubId
);
953 else if ( eState
== SfxItemState::DONTCARE
)
955 // Use special uno struct to transport don't care state
956 css::frame::status::ItemStatus aItemStatus
;
957 aItemStatus
.State
= css::frame::status::ItemState::DONT_CARE
;
958 aState
<<= aItemStatus
;
961 css::frame::FeatureStateEvent aEvent
;
962 aEvent
.FeatureURL
= aDispatchURL
;
963 aEvent
.Source
= static_cast<css::frame::XDispatch
*>(pDispatch
);
964 aEvent
.IsEnabled
= eState
!= SfxItemState::DISABLED
;
965 aEvent
.Requery
= false;
966 aEvent
.State
= aState
;
968 if (pDispatcher
&& pDispatcher
->GetFrame())
970 InterceptLOKStateChangeEvent(pDispatcher
->GetFrame(), aEvent
, pState
);
973 Sequence
< OUString
> seqNames
= pDispatch
->GetListeners().getContainedTypes();
974 sal_Int32 nLength
= seqNames
.getLength();
975 for (sal_Int32 i
= 0; i
< nLength
; ++i
)
977 if (seqNames
[i
] == aDispatchURL
.Main
|| seqNames
[i
] == aDispatchURL
.Complete
)
978 sendStatusChanged(seqNames
[i
], aEvent
);
983 void SfxDispatchController_Impl::StateChanged( sal_uInt16 nSID
, SfxItemState eState
, const SfxPoolItem
* pState
)
985 StateChanged( nSID
, eState
, pState
, nullptr );
988 static void InterceptLOKStateChangeEvent(const SfxViewFrame
* pViewFrame
, const css::frame::FeatureStateEvent
& aEvent
, const SfxPoolItem
* pState
)
990 if (!comphelper::LibreOfficeKit::isActive())
993 OUStringBuffer aBuffer
;
994 aBuffer
.append(aEvent
.FeatureURL
.Complete
);
995 aBuffer
.append(u
'=');
997 if (aEvent
.FeatureURL
.Path
== "Bold" ||
998 aEvent
.FeatureURL
.Path
== "CenterPara" ||
999 aEvent
.FeatureURL
.Path
== "CharBackgroundExt" ||
1000 aEvent
.FeatureURL
.Path
== "ControlCodes" ||
1001 aEvent
.FeatureURL
.Path
== "DefaultBullet" ||
1002 aEvent
.FeatureURL
.Path
== "DefaultNumbering" ||
1003 aEvent
.FeatureURL
.Path
== "Italic" ||
1004 aEvent
.FeatureURL
.Path
== "JustifyPara" ||
1005 aEvent
.FeatureURL
.Path
== "LeftPara" ||
1006 aEvent
.FeatureURL
.Path
== "OutlineFont" ||
1007 aEvent
.FeatureURL
.Path
== "RightPara" ||
1008 aEvent
.FeatureURL
.Path
== "Shadowed" ||
1009 aEvent
.FeatureURL
.Path
== "SpellOnline" ||
1010 aEvent
.FeatureURL
.Path
== "SubScript" ||
1011 aEvent
.FeatureURL
.Path
== "SuperScript" ||
1012 aEvent
.FeatureURL
.Path
== "Strikeout" ||
1013 aEvent
.FeatureURL
.Path
== "Underline" ||
1014 aEvent
.FeatureURL
.Path
== "ModifiedStatus" ||
1015 aEvent
.FeatureURL
.Path
== "TrackChanges" ||
1016 aEvent
.FeatureURL
.Path
== "ShowTrackedChanges" ||
1017 aEvent
.FeatureURL
.Path
== "NextTrackedChange" ||
1018 aEvent
.FeatureURL
.Path
== "PreviousTrackedChange" ||
1019 aEvent
.FeatureURL
.Path
== "AlignLeft" ||
1020 aEvent
.FeatureURL
.Path
== "AlignHorizontalCenter" ||
1021 aEvent
.FeatureURL
.Path
== "AlignRight" ||
1022 aEvent
.FeatureURL
.Path
== "DocumentRepair" ||
1023 aEvent
.FeatureURL
.Path
== "InsertPageHeader" ||
1024 aEvent
.FeatureURL
.Path
== "InsertPageFooter")
1027 aEvent
.State
>>= bTemp
;
1028 aBuffer
.append(bTemp
);
1030 else if (aEvent
.FeatureURL
.Path
== "CharFontName")
1032 css::awt::FontDescriptor aFontDesc
;
1033 aEvent
.State
>>= aFontDesc
;
1034 aBuffer
.append(aFontDesc
.Name
);
1036 else if (aEvent
.FeatureURL
.Path
== "FontHeight")
1038 css::frame::status::FontHeight aFontHeight
;
1039 aEvent
.State
>>= aFontHeight
;
1040 aBuffer
.append(aFontHeight
.Height
);
1042 else if (aEvent
.FeatureURL
.Path
== "StyleApply")
1044 css::frame::status::Template aTemplate
;
1045 aEvent
.State
>>= aTemplate
;
1046 aBuffer
.append(aTemplate
.StyleName
);
1048 else if (aEvent
.FeatureURL
.Path
== "BackColor" ||
1049 aEvent
.FeatureURL
.Path
== "BackgroundColor" ||
1050 aEvent
.FeatureURL
.Path
== "CharBackColor" ||
1051 aEvent
.FeatureURL
.Path
== "Color" ||
1052 aEvent
.FeatureURL
.Path
== "FontColor")
1054 sal_Int32 nColor
= -1;
1055 aEvent
.State
>>= nColor
;
1056 aBuffer
.append(nColor
);
1058 else if (aEvent
.FeatureURL
.Path
== "Undo" ||
1059 aEvent
.FeatureURL
.Path
== "Redo")
1061 const SfxUInt32Item
* pUndoConflict
= dynamic_cast< const SfxUInt32Item
* >( pState
);
1062 if ( pUndoConflict
&& pUndoConflict
->GetValue() > 0 )
1064 aBuffer
.append(OUString("disabled"));
1068 aBuffer
.append(aEvent
.IsEnabled
? OUString("enabled") : OUString("disabled"));
1071 else if (aEvent
.FeatureURL
.Path
== "Cut" ||
1072 aEvent
.FeatureURL
.Path
== "Copy" ||
1073 aEvent
.FeatureURL
.Path
== "Paste" ||
1074 aEvent
.FeatureURL
.Path
== "SelectAll" ||
1075 aEvent
.FeatureURL
.Path
== "InsertAnnotation" ||
1076 aEvent
.FeatureURL
.Path
== "DeleteAnnotation" ||
1077 aEvent
.FeatureURL
.Path
== "InsertRowsBefore" ||
1078 aEvent
.FeatureURL
.Path
== "InsertRowsAfter" ||
1079 aEvent
.FeatureURL
.Path
== "InsertColumnsBefore" ||
1080 aEvent
.FeatureURL
.Path
== "InsertColumnsAfter" ||
1081 aEvent
.FeatureURL
.Path
== "DeleteRows" ||
1082 aEvent
.FeatureURL
.Path
== "DeleteColumns" ||
1083 aEvent
.FeatureURL
.Path
== "DeleteTable" ||
1084 aEvent
.FeatureURL
.Path
== "SelectTable" ||
1085 aEvent
.FeatureURL
.Path
== "EntireRow" ||
1086 aEvent
.FeatureURL
.Path
== "EntireColumn" ||
1087 aEvent
.FeatureURL
.Path
== "EntireCell" ||
1088 aEvent
.FeatureURL
.Path
== "SortAscending" ||
1089 aEvent
.FeatureURL
.Path
== "SortDescending" ||
1090 aEvent
.FeatureURL
.Path
== "AcceptAllTrackedChanges" ||
1091 aEvent
.FeatureURL
.Path
== "RejectAllTrackedChanges" ||
1092 aEvent
.FeatureURL
.Path
== "TableDialog" ||
1093 aEvent
.FeatureURL
.Path
== "FormatCellDialog" ||
1094 aEvent
.FeatureURL
.Path
== "FontDialog" ||
1095 aEvent
.FeatureURL
.Path
== "ParagraphDialog" ||
1096 aEvent
.FeatureURL
.Path
== "OutlineBullet" ||
1097 aEvent
.FeatureURL
.Path
== "InsertIndexesEntry")
1100 aBuffer
.append(aEvent
.IsEnabled
? OUString("enabled") : OUString("disabled"));
1102 else if (aEvent
.FeatureURL
.Path
== "InsertPage" ||
1103 aEvent
.FeatureURL
.Path
== "DeletePage" ||
1104 aEvent
.FeatureURL
.Path
== "DuplicatePage")
1106 aBuffer
.append(OUString::boolean(aEvent
.IsEnabled
));
1108 else if (aEvent
.FeatureURL
.Path
== "AssignLayout" ||
1109 aEvent
.FeatureURL
.Path
== "StatusSelectionMode" ||
1110 aEvent
.FeatureURL
.Path
== "Signature" ||
1111 aEvent
.FeatureURL
.Path
== "SelectionMode" ||
1112 aEvent
.FeatureURL
.Path
== "StatusBarFunc")
1116 if (aEvent
.IsEnabled
&& (aEvent
.State
>>= aInt32
))
1118 aBuffer
.append(OUString::number(aInt32
));
1121 else if (aEvent
.FeatureURL
.Path
== "StatusDocPos" ||
1122 aEvent
.FeatureURL
.Path
== "RowColSelCount" ||
1123 aEvent
.FeatureURL
.Path
== "StatusPageStyle" ||
1124 aEvent
.FeatureURL
.Path
== "StateTableCell" ||
1125 aEvent
.FeatureURL
.Path
== "StatePageNumber" ||
1126 aEvent
.FeatureURL
.Path
== "StateWordCount" ||
1127 aEvent
.FeatureURL
.Path
== "PageStyleName" ||
1128 aEvent
.FeatureURL
.Path
== "PageStatus" ||
1129 aEvent
.FeatureURL
.Path
== "LayoutStatus" ||
1130 aEvent
.FeatureURL
.Path
== "Context")
1134 if (aEvent
.IsEnabled
&& (aEvent
.State
>>= aString
))
1136 aBuffer
.append(aString
);
1139 else if (aEvent
.FeatureURL
.Path
== "InsertMode" ||
1140 aEvent
.FeatureURL
.Path
== "WrapText" ||
1141 aEvent
.FeatureURL
.Path
== "NumberFormatCurrency" ||
1142 aEvent
.FeatureURL
.Path
== "NumberFormatPercent" ||
1143 aEvent
.FeatureURL
.Path
== "NumberFormatDate")
1147 if (aEvent
.IsEnabled
&& (aEvent
.State
>>= aBool
))
1149 aBuffer
.append(OUString::boolean(aBool
));
1152 else if (aEvent
.FeatureURL
.Path
== "ToggleMergeCells")
1156 if (aEvent
.IsEnabled
&& (aEvent
.State
>>= aBool
))
1158 aBuffer
.append(OUString::boolean(aBool
));
1162 aBuffer
.append(OUString("disabled"));
1165 else if (aEvent
.FeatureURL
.Path
== "Position")
1167 css::awt::Point aPoint
;
1169 if (aEvent
.IsEnabled
&& (aEvent
.State
>>= aPoint
))
1171 aBuffer
.append(OUString::number(aPoint
.X
) + " / " + OUString::number(aPoint
.Y
));
1174 else if (aEvent
.FeatureURL
.Path
== "Size")
1176 css::awt::Size aSize
;
1178 if (aEvent
.IsEnabled
&& (aEvent
.State
>>= aSize
))
1180 aBuffer
.append(OUString::number(aSize
.Width
) + " x " + OUString::number(aSize
.Height
));
1183 else if (aEvent
.FeatureURL
.Path
== "LanguageStatus")
1186 css::uno::Sequence
< OUString
> aSeq
;
1188 if (aEvent
.IsEnabled
)
1190 if (aEvent
.State
>>= sValue
)
1192 aBuffer
.append(sValue
);
1194 else if (aEvent
.State
>>= aSeq
)
1196 aBuffer
.append(aSeq
[0]);
1205 OUString payload
= aBuffer
.makeStringAndClear();
1206 if (const SfxViewShell
* pViewShell
= pViewFrame
->GetViewShell())
1207 pViewShell
->libreOfficeKitViewCallback(LOK_CALLBACK_STATE_CHANGED
, payload
.toUtf8().getStr());
1210 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */