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 
20 
21 #ifdef __sun
22 #include <ctime>
23 #endif
24 
25 #include <string>
26 #include <com/sun/star/util/XURLTransformer.hpp>
27 #include <framework/dispatchhelper.hxx>
28 #include <com/sun/star/frame/DispatchResultState.hpp>
29 #include <com/sun/star/beans/PropertyValue.hpp>
30 #include <cppuhelper/weak.hxx>
31 #include <svl/eitem.hxx>
32 #include <svl/intitem.hxx>
33 #include <svl/stritem.hxx>
34 #include <svl/visitem.hxx>
35 
36 #include <sfx2/app.hxx>
37 #include <statcach.hxx>
38 #include <sfx2/msg.hxx>
39 #include <sfx2/ctrlitem.hxx>
40 #include <sfx2/dispatch.hxx>
41 #include <sfxtypes.hxx>
42 #include <sfx2/sfxuno.hxx>
43 #include <sfx2/unoctitm.hxx>
44 #include <sfx2/msgpool.hxx>
45 #include <sfx2/viewfrm.hxx>
46 
47 using namespace ::com::sun::star;
48 using namespace ::com::sun::star::uno;
49 using namespace ::com::sun::star::util;
50 
BindDispatch_Impl(const css::uno::Reference<css::frame::XDispatch> & rDisp,const css::util::URL & rURL,SfxStateCache * pStateCache,const SfxSlot * pS)51 BindDispatch_Impl::BindDispatch_Impl( const css::uno::Reference< css::frame::XDispatch > & rDisp, const css::util::URL& rURL, SfxStateCache *pStateCache, const SfxSlot* pS )
52     : xDisp( rDisp )
53     , aURL( rURL )
54     , pCache( pStateCache )
55     , pSlot( pS )
56 {
57     DBG_ASSERT( pCache && pSlot, "Invalid BindDispatch!");
58     aStatus.IsEnabled = true;
59 }
60 
disposing(const css::lang::EventObject &)61 void SAL_CALL BindDispatch_Impl::disposing( const css::lang::EventObject& )
62 {
63     if ( xDisp.is() )
64     {
65         xDisp->removeStatusListener( static_cast<css::frame::XStatusListener*>(this), aURL );
66         xDisp.clear();
67     }
68 }
69 
statusChanged(const css::frame::FeatureStateEvent & rEvent)70 void SAL_CALL  BindDispatch_Impl::statusChanged( const css::frame::FeatureStateEvent& rEvent )
71 {
72     aStatus = rEvent;
73     if ( !pCache )
74         return;
75 
76     css::uno::Reference< css::frame::XStatusListener >  xRef( static_cast<cppu::OWeakObject*>(this), css::uno::UNO_QUERY );
77     if ( aStatus.Requery )
78         pCache->Invalidate( true );
79     else
80     {
81         std::unique_ptr<SfxPoolItem> pItem;
82         sal_uInt16 nId = pCache->GetId();
83         SfxItemState eState = SfxItemState::DISABLED;
84         if ( !aStatus.IsEnabled )
85         {
86             // default
87         }
88         else if (aStatus.State.hasValue())
89         {
90             eState = SfxItemState::DEFAULT;
91             css::uno::Any aAny = aStatus.State;
92 
93             const css::uno::Type& aType = aAny.getValueType();
94             if ( aType == cppu::UnoType< bool >::get() )
95             {
96                 bool bTemp = false;
97                 aAny >>= bTemp ;
98                 pItem.reset( new SfxBoolItem( nId, bTemp ) );
99             }
100             else if ( aType == ::cppu::UnoType< ::cppu::UnoUnsignedShortType >::get() )
101             {
102                 sal_uInt16 nTemp = 0;
103                 aAny >>= nTemp ;
104                 pItem.reset( new SfxUInt16Item( nId, nTemp ) );
105             }
106             else if ( aType == cppu::UnoType<sal_uInt32>::get() )
107             {
108                 sal_uInt32 nTemp = 0;
109                 aAny >>= nTemp ;
110                 pItem.reset( new SfxUInt32Item( nId, nTemp ) );
111             }
112             else if ( aType == cppu::UnoType<OUString>::get() )
113             {
114                 OUString sTemp ;
115                 aAny >>= sTemp ;
116                 pItem.reset( new SfxStringItem( nId, sTemp ) );
117             }
118             else
119             {
120                 if ( pSlot )
121                     pItem = pSlot->GetType()->CreateItem();
122                 if ( pItem )
123                 {
124                     pItem->SetWhich( nId );
125                     pItem->PutValue( aAny, 0 );
126                 }
127                 else
128                     pItem.reset( new SfxVoidItem( nId ) );
129             }
130         }
131         else
132         {
133             // DONTCARE status
134             pItem.reset( new SfxVoidItem(0) );
135             eState = SfxItemState::UNKNOWN;
136         }
137 
138         for ( SfxControllerItem *pCtrl = pCache->GetItemLink();
139             pCtrl;
140             pCtrl = pCtrl->GetItemLink() )
141             pCtrl->StateChanged( nId, eState, pItem.get() );
142     }
143 }
144 
Release()145 void BindDispatch_Impl::Release()
146 {
147     if ( xDisp.is() )
148     {
149         xDisp->removeStatusListener( static_cast<css::frame::XStatusListener*>(this), aURL );
150         xDisp.clear();
151     }
152     pCache = nullptr;
153 }
154 
155 
Dispatch(const css::uno::Sequence<css::beans::PropertyValue> & aProps,bool bForceSynchron)156 sal_Int16 BindDispatch_Impl::Dispatch( const css::uno::Sequence < css::beans::PropertyValue >& aProps, bool bForceSynchron )
157 {
158     sal_Int16 eRet = css::frame::DispatchResultState::DONTKNOW;
159 
160     if ( xDisp.is() && aStatus.IsEnabled )
161     {
162         ::rtl::Reference< ::framework::DispatchHelper > xHelper( new ::framework::DispatchHelper(nullptr));
163         css::uno::Any aResult = xHelper->executeDispatch(xDisp, aURL, bForceSynchron, aProps);
164 
165         css::frame::DispatchResultEvent aEvent;
166         aResult >>= aEvent;
167 
168         eRet = aEvent.State;
169     }
170 
171     return eRet;
172 }
173 
174 
175 // This constructor for an invalid cache that is updated in the first request.
176 
SfxStateCache(sal_uInt16 nFuncId)177 SfxStateCache::SfxStateCache( sal_uInt16 nFuncId ):
178     nId(nFuncId),
179     pInternalController(nullptr),
180     pController(nullptr),
181     pLastItem( nullptr ),
182     eLastState( SfxItemState::UNKNOWN ),
183     bItemVisible( true )
184 {
185     bCtrlDirty = true;
186     bSlotDirty = true;
187     bItemDirty = true;
188 }
189 
190 
191 // The Destructor checks by assertion, even if controllers are registered.
192 
~SfxStateCache()193 SfxStateCache::~SfxStateCache()
194 {
195     DBG_ASSERT( pController == nullptr && pInternalController == nullptr, "there are still Controllers registered" );
196     if ( !IsInvalidItem(pLastItem) )
197         delete pLastItem;
198     if ( mxDispatch.is() )
199         mxDispatch->Release();
200 }
201 
202 
203 // invalidates the cache (next request will force update)
Invalidate(bool bWithMsg)204 void SfxStateCache::Invalidate( bool bWithMsg )
205 {
206     bCtrlDirty = true;
207     if ( bWithMsg )
208     {
209         bSlotDirty = true;
210         aSlotServ.SetSlot( nullptr );
211         if ( mxDispatch.is() )
212             mxDispatch->Release();
213         mxDispatch.clear();
214     }
215 }
216 
217 
218 // gets the corresponding function from the dispatcher or the cache
219 
GetSlotServer(SfxDispatcher & rDispat,const css::uno::Reference<css::frame::XDispatchProvider> & xProv)220 const SfxSlotServer* SfxStateCache::GetSlotServer( SfxDispatcher &rDispat , const css::uno::Reference< css::frame::XDispatchProvider > & xProv )
221 {
222 
223     if ( bSlotDirty )
224     {
225         // get the SlotServer; we need it for internal controllers anyway, but also in most cases
226         rDispat.FindServer_( nId, aSlotServ );
227 
228         DBG_ASSERT( !mxDispatch.is(), "Old Dispatch not removed!" );
229 
230         // we don't need to check the dispatch provider if we only have an internal controller
231         if ( xProv.is() )
232         {
233             const SfxSlot* pSlot = aSlotServ.GetSlot();
234             if ( !pSlot )
235                 // get the slot - even if it is disabled on the dispatcher
236                 pSlot = SfxSlotPool::GetSlotPool( rDispat.GetFrame() ).GetSlot( nId );
237 
238             if ( !pSlot || !pSlot->pUnoName )
239             {
240                 bSlotDirty = false;
241                 bCtrlDirty = true;
242                 return aSlotServ.GetSlot()? &aSlotServ: nullptr;
243             }
244 
245             // create the dispatch URL from the slot data
246             css::util::URL aURL;
247             OUString aCmd = ".uno:";
248             aURL.Protocol = aCmd;
249             aURL.Path = OUString::createFromAscii( pSlot->GetUnoName() );
250             aCmd += aURL.Path;
251             aURL.Complete = aCmd;
252             aURL.Main = aCmd;
253 
254             // try to get a dispatch object for this command
255             css::uno::Reference< css::frame::XDispatch >  xDisp = xProv->queryDispatch( aURL, OUString(), 0 );
256             if ( xDisp.is() )
257             {
258                 // test the dispatch object if it is just a wrapper for a SfxDispatcher
259                 css::uno::Reference< css::lang::XUnoTunnel > xTunnel( xDisp, css::uno::UNO_QUERY );
260                 SfxOfficeDispatch* pDisp = nullptr;
261                 if ( xTunnel.is() )
262                 {
263                     sal_Int64 nImplementation = xTunnel->getSomething(SfxOfficeDispatch::impl_getStaticIdentifier());
264                     pDisp = reinterpret_cast< SfxOfficeDispatch* >(sal::static_int_cast< sal_IntPtr >( nImplementation ));
265                 }
266 
267                 if ( pDisp )
268                 {
269                     // The intercepting object is an SFX component
270                     // If this dispatch object does not use the wanted dispatcher or the AppDispatcher, it's treated like any other UNO component
271                     // (intercepting by internal dispatches)
272                     SfxDispatcher *pDispatcher = pDisp->GetDispatcher_Impl();
273                     if ( pDispatcher == &rDispat || pDispatcher == SfxGetpApp()->GetAppDispatcher_Impl() )
274                     {
275                         // so we can use it directly
276                         bSlotDirty = false;
277                         bCtrlDirty = true;
278                         return aSlotServ.GetSlot()? &aSlotServ: nullptr;
279                     }
280                 }
281 
282                 // so the dispatch object isn't a SfxDispatcher wrapper or it is one, but it uses another dispatcher, but not rDispat
283                 mxDispatch = new BindDispatch_Impl( xDisp, aURL, this, pSlot );
284 
285                 // flags must be set before adding StatusListener because the dispatch object will set the state
286                 bSlotDirty = false;
287                 bCtrlDirty = true;
288                 xDisp->addStatusListener( mxDispatch.get(), aURL );
289             }
290             else if ( rDispat.GetFrame() )
291             {
292                 css::uno::Reference < css::frame::XDispatchProvider > xFrameProv(
293                         rDispat.GetFrame()->GetFrame().GetFrameInterface(), css::uno::UNO_QUERY );
294                 if ( xFrameProv != xProv )
295                     return GetSlotServer( rDispat, xFrameProv );
296             }
297         }
298 
299         bSlotDirty = false;
300         bCtrlDirty = true;
301     }
302 
303     // we *always* return a SlotServer (if there is one); but in case of an external dispatch we might not use it
304     // for the "real" (non internal) controllers
305     return aSlotServ.GetSlot()? &aSlotServ: nullptr;
306 }
307 
308 
309 // Set Status in all Controllers
310 
SetState(SfxItemState eState,const SfxPoolItem * pState,bool bMaybeDirty)311 void SfxStateCache::SetState
312 (
313     SfxItemState        eState,  // <SfxItemState> from 'pState'
314     const SfxPoolItem*  pState,  // Slot Status, 0 or -1
315     bool bMaybeDirty
316 )
317 
318 /*  [Description]
319 
320     This method distributes the status of all of this SID bound
321     <SfxControllerItem>s. If the value is the same as before, and if neither
322     controller was registered nor invalidated inbetween, then no value is
323     passed. This way the flickering is for example avoided in ListBoxes.
324 */
325 {
326     SetState_Impl( eState, pState, bMaybeDirty );
327 }
328 
329 
SetVisibleState(bool bShow)330 void SfxStateCache::SetVisibleState( bool bShow )
331 {
332     if ( bShow == bItemVisible )
333         return;
334 
335     SfxItemState eState( SfxItemState::DEFAULT );
336     const SfxPoolItem*  pState( nullptr );
337     bool bDeleteItem( false );
338 
339     bItemVisible = bShow;
340     if ( bShow )
341     {
342         if ( IsInvalidItem(pLastItem) || ( pLastItem == nullptr ))
343         {
344             pState = new SfxVoidItem( nId );
345             bDeleteItem = true;
346         }
347         else
348             pState = pLastItem;
349 
350         eState = eLastState;
351     }
352     else
353     {
354         pState = new SfxVisibilityItem( nId, false );
355         bDeleteItem = true;
356     }
357 
358     // Update Controller
359     if ( !mxDispatch.is() && pController )
360     {
361         for ( SfxControllerItem *pCtrl = pController;
362                 pCtrl;
363                 pCtrl = pCtrl->GetItemLink() )
364             pCtrl->StateChanged( nId, eState, pState );
365     }
366 
367     if ( pInternalController )
368         pInternalController->StateChanged( nId, eState, pState );
369 
370     if ( bDeleteItem )
371         delete pState;
372 }
373 
374 
SetState_Impl(SfxItemState eState,const SfxPoolItem * pState,bool bMaybeDirty)375 void SfxStateCache::SetState_Impl
376 (
377     SfxItemState        eState,  // <SfxItemState> from 'pState'
378     const SfxPoolItem*  pState,  // Slot Status, 0 or -1
379     bool bMaybeDirty
380 )
381 {
382     // If a hard update occurs between enter- and leave-registrations is a
383     // can also intermediate Cached exist without controller.
384     if ( !pController && !pInternalController )
385         return;
386 
387     DBG_ASSERT( bMaybeDirty || !bSlotDirty, "setting state of dirty message" );
388     DBG_ASSERT( SfxControllerItem::GetItemState(pState) == eState, "invalid SfxItemState" );
389 
390     // does the controller have to be notified at all?
391     bool bNotify = bItemDirty;
392     if ( !bItemDirty )
393     {
394         bool bBothAvailable = pLastItem && pState &&
395                     !IsInvalidItem(pState) && !IsInvalidItem(pLastItem);
396         DBG_ASSERT( !bBothAvailable || pState != pLastItem, "setting state with own item" );
397         if ( bBothAvailable )
398             bNotify = typeid(*pState) != typeid(*pLastItem) ||
399                       *pState != *pLastItem;
400         else
401             bNotify = ( pState != pLastItem ) || ( eState != eLastState );
402     }
403 
404     if ( bNotify )
405     {
406         // Update Controller
407         if ( !mxDispatch.is() && pController )
408         {
409             for ( SfxControllerItem *pCtrl = pController;
410                 pCtrl;
411                 pCtrl = pCtrl->GetItemLink() )
412                 pCtrl->StateChanged( nId, eState, pState );
413         }
414 
415         if ( pInternalController )
416             static_cast<SfxDispatchController_Impl *>(pInternalController)->StateChanged( nId, eState, pState, &aSlotServ );
417 
418         // Remember new value
419         if ( !IsInvalidItem(pLastItem) )
420             DELETEZ(pLastItem);
421         if ( pState && !IsInvalidItem(pState) )
422             pLastItem = pState->Clone();
423         else
424             pLastItem = nullptr;
425         eLastState = eState;
426         bItemDirty = false;
427     }
428 
429     bCtrlDirty = false;
430 }
431 
432 
433 // Set old status again in all the controllers
434 
SetCachedState(bool bAlways)435 void SfxStateCache::SetCachedState( bool bAlways )
436 {
437     DBG_ASSERT(pController==nullptr||pController->GetId()==nId, "Cache with wrong ControllerItem" );
438 
439     // Only update if cached item exists and also able to process.
440     // (If the State is sent, it must be ensured that a SlotServer is present,
441     // see SfxControllerItem:: GetCoreMetric())
442     if ( !(bAlways || ( !bItemDirty && !bSlotDirty )) )
443         return;
444 
445     // Update Controller
446     if ( !mxDispatch.is() && pController )
447     {
448         for ( SfxControllerItem *pCtrl = pController;
449             pCtrl;
450             pCtrl = pCtrl->GetItemLink() )
451             pCtrl->StateChanged( nId, eLastState, pLastItem );
452     }
453 
454     if ( pInternalController )
455         static_cast<SfxDispatchController_Impl *>(pInternalController)->StateChanged( nId, eLastState, pLastItem, &aSlotServ );
456 
457     // Controller is now ok
458     bCtrlDirty = true;
459 }
460 
461 
GetDispatch() const462 css::uno::Reference< css::frame::XDispatch >  SfxStateCache::GetDispatch() const
463 {
464     if ( mxDispatch.is() )
465         return mxDispatch->xDisp;
466     return css::uno::Reference< css::frame::XDispatch > ();
467 }
468 
Dispatch(const SfxItemSet * pSet,bool bForceSynchron)469 sal_Int16 SfxStateCache::Dispatch( const SfxItemSet* pSet, bool bForceSynchron )
470 {
471     // protect pDispatch against destruction in the call
472     rtl::Reference<BindDispatch_Impl> xKeepAlive( mxDispatch );
473     sal_Int16 eRet = css::frame::DispatchResultState::DONTKNOW;
474 
475     if ( mxDispatch.is() )
476     {
477         uno::Sequence < beans::PropertyValue > aArgs;
478         if (pSet)
479             TransformItems( nId, *pSet, aArgs );
480 
481         eRet = mxDispatch->Dispatch( aArgs, bForceSynchron );
482     }
483 
484     return eRet;
485 }
486 
487 
488 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
489