510 lines
16 KiB
C++
510 lines
16 KiB
C++
/* -*- 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 .
|
|
*/
|
|
|
|
|
|
#ifdef __sun
|
|
#include <ctime>
|
|
#endif
|
|
|
|
#include <framework/dispatchhelper.hxx>
|
|
#include <com/sun/star/frame/DispatchResultState.hpp>
|
|
#include <com/sun/star/frame/XFrame.hpp>
|
|
#include <com/sun/star/beans/PropertyValue.hpp>
|
|
#include <comphelper/servicehelper.hxx>
|
|
#include <svl/eitem.hxx>
|
|
#include <svl/intitem.hxx>
|
|
#include <svl/stritem.hxx>
|
|
#include <svl/visitem.hxx>
|
|
#include <svl/voiditem.hxx>
|
|
|
|
#include <sfx2/app.hxx>
|
|
#include <statcach.hxx>
|
|
#include <sfx2/msg.hxx>
|
|
#include <sfx2/ctrlitem.hxx>
|
|
#include <sfx2/dispatch.hxx>
|
|
#include <sfx2/sfxuno.hxx>
|
|
#include <unoctitm.hxx>
|
|
#include <sfx2/msgpool.hxx>
|
|
#include <sfx2/viewfrm.hxx>
|
|
#include <utility>
|
|
#include <comphelper/diagnose_ex.hxx>
|
|
|
|
using namespace ::com::sun::star;
|
|
using namespace ::com::sun::star::uno;
|
|
using namespace ::com::sun::star::util;
|
|
|
|
BindDispatch_Impl::BindDispatch_Impl( css::uno::Reference< css::frame::XDispatch > _xDisp, css::util::URL _aURL, SfxStateCache *pStateCache, const SfxSlot* pS )
|
|
: xDisp(std::move( _xDisp ))
|
|
, aURL(std::move( _aURL ))
|
|
, pCache( pStateCache )
|
|
, pSlot( pS )
|
|
{
|
|
DBG_ASSERT( pCache && pSlot, "Invalid BindDispatch!");
|
|
aStatus.IsEnabled = true;
|
|
}
|
|
|
|
void SAL_CALL BindDispatch_Impl::disposing( const css::lang::EventObject& )
|
|
{
|
|
if ( xDisp.is() )
|
|
{
|
|
xDisp->removeStatusListener( static_cast<css::frame::XStatusListener*>(this), aURL );
|
|
xDisp.clear();
|
|
}
|
|
}
|
|
|
|
void SAL_CALL BindDispatch_Impl::statusChanged( const css::frame::FeatureStateEvent& rEvent )
|
|
{
|
|
aStatus = rEvent;
|
|
if ( !pCache )
|
|
return;
|
|
|
|
css::uno::Reference< css::frame::XStatusListener > xKeepAlive(this);
|
|
if ( aStatus.Requery )
|
|
pCache->Invalidate( true );
|
|
else
|
|
{
|
|
std::unique_ptr<SfxPoolItem> pItem;
|
|
sal_uInt16 nId = pCache->GetId();
|
|
SfxItemState eState = SfxItemState::DISABLED;
|
|
const SfxPoolItem* pArg(nullptr);
|
|
if ( !aStatus.IsEnabled )
|
|
{
|
|
// default
|
|
}
|
|
else if (aStatus.State.hasValue())
|
|
{
|
|
eState = SfxItemState::DEFAULT;
|
|
css::uno::Any aAny = aStatus.State;
|
|
|
|
const css::uno::Type& aType = aAny.getValueType();
|
|
if ( aType == cppu::UnoType< bool >::get() )
|
|
{
|
|
bool bTemp = false;
|
|
aAny >>= bTemp ;
|
|
pItem.reset( new SfxBoolItem( nId, bTemp ) );
|
|
}
|
|
else if ( aType == ::cppu::UnoType< ::cppu::UnoUnsignedShortType >::get() )
|
|
{
|
|
sal_uInt16 nTemp = 0;
|
|
aAny >>= nTemp ;
|
|
pItem.reset( new SfxUInt16Item( nId, nTemp ) );
|
|
}
|
|
else if ( aType == cppu::UnoType<sal_uInt32>::get() )
|
|
{
|
|
sal_uInt32 nTemp = 0;
|
|
aAny >>= nTemp ;
|
|
pItem.reset( new SfxUInt32Item( nId, nTemp ) );
|
|
}
|
|
else if ( aType == cppu::UnoType<OUString>::get() )
|
|
{
|
|
OUString sTemp ;
|
|
aAny >>= sTemp ;
|
|
pItem.reset( new SfxStringItem( nId, sTemp ) );
|
|
}
|
|
else
|
|
{
|
|
if ( pSlot )
|
|
pItem = pSlot->GetType()->CreateItem();
|
|
if ( pItem )
|
|
{
|
|
pItem->SetWhich( nId );
|
|
pItem->PutValue( aAny, 0 );
|
|
}
|
|
else
|
|
// tdf#162666 nId should not be zero. This will now create
|
|
// a SAL_INFO in SfxVoidItem::SfxVoidItem
|
|
pItem.reset( new SfxVoidItem( nId ) );
|
|
}
|
|
pArg = pItem.get();
|
|
}
|
|
else
|
|
{
|
|
// DONTCARE status
|
|
pArg = DISABLED_POOL_ITEM;
|
|
eState = SfxItemState::UNKNOWN;
|
|
}
|
|
|
|
for ( SfxControllerItem *pCtrl = pCache->GetItemLink();
|
|
pCtrl;
|
|
pCtrl = pCtrl->GetItemLink() )
|
|
pCtrl->StateChangedAtToolBoxControl( nId, eState, pArg );
|
|
}
|
|
}
|
|
|
|
void BindDispatch_Impl::Release()
|
|
{
|
|
if ( xDisp.is() )
|
|
{
|
|
try
|
|
{
|
|
xDisp->removeStatusListener(static_cast<css::frame::XStatusListener*>(this), aURL);
|
|
}
|
|
catch (const lang::DisposedException&)
|
|
{
|
|
TOOLS_WARN_EXCEPTION("sfx", "BindDispatch_Impl::Release: xDisp is disposed: ");
|
|
}
|
|
xDisp.clear();
|
|
}
|
|
pCache = nullptr;
|
|
}
|
|
|
|
|
|
sal_Int16 BindDispatch_Impl::Dispatch( const css::uno::Sequence < css::beans::PropertyValue >& aProps, bool bForceSynchron )
|
|
{
|
|
sal_Int16 eRet = css::frame::DispatchResultState::DONTKNOW;
|
|
|
|
if ( xDisp.is() && aStatus.IsEnabled )
|
|
{
|
|
::rtl::Reference< ::framework::DispatchHelper > xHelper( new ::framework::DispatchHelper(nullptr));
|
|
css::uno::Any aResult = xHelper->executeDispatch(xDisp, aURL, bForceSynchron, aProps);
|
|
|
|
css::frame::DispatchResultEvent aEvent;
|
|
aResult >>= aEvent;
|
|
|
|
eRet = aEvent.State;
|
|
}
|
|
|
|
return eRet;
|
|
}
|
|
|
|
|
|
// This constructor for an invalid cache that is updated in the first request.
|
|
|
|
SfxStateCache::SfxStateCache( sal_uInt16 nFuncId ):
|
|
nId(nFuncId),
|
|
pInternalController(nullptr),
|
|
pController(nullptr),
|
|
pLastItem( nullptr ),
|
|
eLastState( SfxItemState::UNKNOWN ),
|
|
bItemVisible( true )
|
|
{
|
|
bCtrlDirty = true;
|
|
bSlotDirty = true;
|
|
bItemDirty = true;
|
|
}
|
|
|
|
|
|
// The Destructor checks by assertion, even if controllers are registered.
|
|
|
|
SfxStateCache::~SfxStateCache()
|
|
{
|
|
DBG_ASSERT( pController == nullptr && pInternalController == nullptr, "there are still Controllers registered" );
|
|
|
|
if ( !IsInvalidItem(pLastItem) && !IsDisabledItem(pLastItem) )
|
|
{
|
|
// tdf#162666 only delete if it *was* cloned
|
|
delete pLastItem;
|
|
}
|
|
|
|
if ( mxDispatch.is() )
|
|
mxDispatch->Release();
|
|
}
|
|
|
|
|
|
// invalidates the cache (next request will force update)
|
|
void SfxStateCache::Invalidate( bool bWithMsg )
|
|
{
|
|
bCtrlDirty = true;
|
|
if ( bWithMsg )
|
|
{
|
|
bSlotDirty = true;
|
|
aSlotServ.SetSlot( nullptr );
|
|
if ( mxDispatch.is() )
|
|
mxDispatch->Release();
|
|
mxDispatch.clear();
|
|
}
|
|
}
|
|
|
|
|
|
// gets the corresponding function from the dispatcher or the cache
|
|
|
|
const SfxSlotServer* SfxStateCache::GetSlotServer( SfxDispatcher &rDispat , const css::uno::Reference< css::frame::XDispatchProvider > & xProv )
|
|
{
|
|
|
|
if ( bSlotDirty )
|
|
{
|
|
// get the SlotServer; we need it for internal controllers anyway, but also in most cases
|
|
rDispat.FindServer_( nId, aSlotServ );
|
|
|
|
DBG_ASSERT( !mxDispatch.is(), "Old Dispatch not removed!" );
|
|
|
|
// we don't need to check the dispatch provider if we only have an internal controller
|
|
if ( xProv.is() )
|
|
{
|
|
const SfxSlot* pSlot = aSlotServ.GetSlot();
|
|
if ( !pSlot )
|
|
// get the slot - even if it is disabled on the dispatcher
|
|
pSlot = SfxSlotPool::GetSlotPool( rDispat.GetFrame() ).GetSlot( nId );
|
|
|
|
if ( !pSlot || pSlot->aUnoName.isEmpty() )
|
|
{
|
|
bSlotDirty = false;
|
|
bCtrlDirty = true;
|
|
return aSlotServ.GetSlot()? &aSlotServ: nullptr;
|
|
}
|
|
|
|
// create the dispatch URL from the slot data
|
|
css::util::URL aURL;
|
|
OUString aCmd = u".uno:"_ustr;
|
|
aURL.Protocol = aCmd;
|
|
aURL.Path = pSlot->GetUnoName();
|
|
aCmd += aURL.Path;
|
|
aURL.Complete = aCmd;
|
|
aURL.Main = aCmd;
|
|
|
|
// try to get a dispatch object for this command
|
|
css::uno::Reference< css::frame::XDispatch > xDisp = xProv->queryDispatch( aURL, OUString(), 0 );
|
|
if ( xDisp.is() )
|
|
{
|
|
// test the dispatch object if it is just a wrapper for a SfxDispatcher
|
|
if (auto pDisp = dynamic_cast<SfxOfficeDispatch*>(xDisp.get()))
|
|
{
|
|
// The intercepting object is an SFX component
|
|
// If this dispatch object does not use the wanted dispatcher or the AppDispatcher, it's treated like any other UNO component
|
|
// (intercepting by internal dispatches)
|
|
SfxDispatcher *pDispatcher = pDisp->GetDispatcher_Impl();
|
|
if ( pDispatcher == &rDispat || pDispatcher == SfxGetpApp()->GetAppDispatcher_Impl() )
|
|
{
|
|
// so we can use it directly
|
|
bSlotDirty = false;
|
|
bCtrlDirty = true;
|
|
return aSlotServ.GetSlot()? &aSlotServ: nullptr;
|
|
}
|
|
}
|
|
|
|
// so the dispatch object isn't a SfxDispatcher wrapper or it is one, but it uses another dispatcher, but not rDispat
|
|
mxDispatch = new BindDispatch_Impl( xDisp, aURL, this, pSlot );
|
|
|
|
// flags must be set before adding StatusListener because the dispatch object will set the state
|
|
bSlotDirty = false;
|
|
bCtrlDirty = true;
|
|
xDisp->addStatusListener( mxDispatch, aURL );
|
|
}
|
|
else if ( rDispat.GetFrame() )
|
|
{
|
|
css::uno::Reference < css::frame::XDispatchProvider > xFrameProv(
|
|
rDispat.GetFrame()->GetFrame().GetFrameInterface(), css::uno::UNO_QUERY );
|
|
if ( xFrameProv != xProv )
|
|
return GetSlotServer( rDispat, xFrameProv );
|
|
}
|
|
}
|
|
|
|
bSlotDirty = false;
|
|
bCtrlDirty = true;
|
|
}
|
|
|
|
// we *always* return a SlotServer (if there is one); but in case of an external dispatch we might not use it
|
|
// for the "real" (non internal) controllers
|
|
return aSlotServ.GetSlot()? &aSlotServ: nullptr;
|
|
}
|
|
|
|
|
|
// Set Status in all Controllers
|
|
|
|
void SfxStateCache::SetState
|
|
(
|
|
SfxItemState eState, // <SfxItemState> from 'pState'
|
|
const SfxPoolItem* pState, // Slot Status, 0 or -1
|
|
bool bMaybeDirty
|
|
)
|
|
|
|
/* [Description]
|
|
|
|
This method distributes the status of all of this SID bound
|
|
<SfxControllerItem>s. If the value is the same as before, and if neither
|
|
controller was registered nor invalidated inbetween, then no value is
|
|
passed. This way the flickering is for example avoided in ListBoxes.
|
|
*/
|
|
{
|
|
SetState_Impl( eState, pState, bMaybeDirty );
|
|
}
|
|
|
|
void SfxStateCache::GetState
|
|
(
|
|
boost::property_tree::ptree& rState
|
|
)
|
|
{
|
|
if ( !mxDispatch.is() && pController )
|
|
{
|
|
for ( SfxControllerItem *pCtrl = pController;
|
|
pCtrl;
|
|
pCtrl = pCtrl->GetItemLink() )
|
|
pCtrl->GetControlState( nId, rState );
|
|
}
|
|
}
|
|
|
|
void SfxStateCache::SetVisibleState( bool bShow )
|
|
{
|
|
if ( bShow == bItemVisible )
|
|
return;
|
|
|
|
SfxItemState eState( SfxItemState::DEFAULT );
|
|
const SfxPoolItem* pState( nullptr );
|
|
bool bDeleteItem( false );
|
|
|
|
bItemVisible = bShow;
|
|
if ( bShow )
|
|
{
|
|
// tdf#164745 also need to do this when disabled item
|
|
if ( nullptr == pLastItem || IsInvalidItem(pLastItem) || IsDisabledItem(pLastItem) )
|
|
{
|
|
pState = new SfxVoidItem( nId );
|
|
bDeleteItem = true;
|
|
}
|
|
else
|
|
pState = pLastItem;
|
|
|
|
eState = eLastState;
|
|
}
|
|
else
|
|
{
|
|
pState = new SfxVisibilityItem( nId, false );
|
|
bDeleteItem = true;
|
|
}
|
|
|
|
// Update Controller
|
|
if ( !mxDispatch.is() && pController )
|
|
{
|
|
for ( SfxControllerItem *pCtrl = pController;
|
|
pCtrl;
|
|
pCtrl = pCtrl->GetItemLink() )
|
|
pCtrl->StateChangedAtToolBoxControl( nId, eState, pState );
|
|
}
|
|
|
|
if ( pInternalController )
|
|
pInternalController->StateChangedAtToolBoxControl( nId, eState, pState );
|
|
|
|
if ( bDeleteItem )
|
|
delete pState;
|
|
}
|
|
|
|
|
|
void SfxStateCache::SetState_Impl
|
|
(
|
|
SfxItemState eState, // <SfxItemState> from 'pState'
|
|
const SfxPoolItem* pState, // Slot Status, 0 or -1
|
|
bool bMaybeDirty
|
|
)
|
|
{
|
|
// If a hard update occurs between enter- and leave-registrations is a
|
|
// can also intermediate Cached exist without controller.
|
|
if ( !pController && !pInternalController )
|
|
return;
|
|
|
|
DBG_ASSERT( bMaybeDirty || !bSlotDirty, "setting state of dirty message" );
|
|
DBG_ASSERT( SfxControllerItem::GetItemState(pState) == eState, "invalid SfxItemState" );
|
|
|
|
// does the controller have to be notified at all?
|
|
bool bNotify = bItemDirty;
|
|
if ( !bItemDirty )
|
|
{
|
|
bNotify = !SfxPoolItem::areSame(pLastItem, pState) || (eState != eLastState);
|
|
}
|
|
|
|
if ( bNotify )
|
|
{
|
|
// Update Controller
|
|
if ( !mxDispatch.is() && pController )
|
|
{
|
|
for ( SfxControllerItem *pCtrl = pController;
|
|
pCtrl;
|
|
pCtrl = pCtrl->GetItemLink() )
|
|
pCtrl->StateChangedAtToolBoxControl( nId, eState, pState );
|
|
}
|
|
|
|
if ( pInternalController )
|
|
static_cast<SfxDispatchController_Impl *>(pInternalController)->StateChanged( nId, eState, pState, &aSlotServ );
|
|
|
|
// Remember new value
|
|
if ( !IsInvalidItem(pLastItem) && !IsDisabledItem(pLastItem) )
|
|
{
|
|
delete pLastItem;
|
|
pLastItem = nullptr;
|
|
}
|
|
|
|
// tdf#164745 clone when it exists and it's really an item
|
|
if ( pState && !IsInvalidItem(pState) && !IsDisabledItem(pState) )
|
|
pLastItem = pState->Clone();
|
|
else
|
|
pLastItem = nullptr;
|
|
|
|
eLastState = eState;
|
|
bItemDirty = false;
|
|
}
|
|
|
|
bCtrlDirty = false;
|
|
}
|
|
|
|
|
|
// Set old status again in all the controllers
|
|
|
|
void SfxStateCache::SetCachedState( bool bAlways )
|
|
{
|
|
DBG_ASSERT(pController==nullptr||pController->GetId()==nId, "Cache with wrong ControllerItem" );
|
|
|
|
// Only update if cached item exists and also able to process.
|
|
// (If the State is sent, it must be ensured that a SlotServer is present,
|
|
// see SfxControllerItem:: GetCoreMetric())
|
|
if ( !(bAlways || ( !bItemDirty && !bSlotDirty )) )
|
|
return;
|
|
|
|
// Update Controller
|
|
if ( !mxDispatch.is() && pController )
|
|
{
|
|
for ( SfxControllerItem *pCtrl = pController;
|
|
pCtrl;
|
|
pCtrl = pCtrl->GetItemLink() )
|
|
pCtrl->StateChangedAtToolBoxControl( nId, eLastState, pLastItem );
|
|
}
|
|
|
|
if ( pInternalController )
|
|
static_cast<SfxDispatchController_Impl *>(pInternalController)->StateChanged( nId, eLastState, pLastItem, &aSlotServ );
|
|
|
|
// Controller is now ok
|
|
bCtrlDirty = true;
|
|
}
|
|
|
|
|
|
css::uno::Reference< css::frame::XDispatch > SfxStateCache::GetDispatch() const
|
|
{
|
|
if ( mxDispatch.is() )
|
|
return mxDispatch->xDisp;
|
|
return css::uno::Reference< css::frame::XDispatch > ();
|
|
}
|
|
|
|
sal_Int16 SfxStateCache::Dispatch( const SfxItemSet* pSet, bool bForceSynchron )
|
|
{
|
|
// protect pDispatch against destruction in the call
|
|
rtl::Reference<BindDispatch_Impl> xKeepAlive( mxDispatch );
|
|
sal_Int16 eRet = css::frame::DispatchResultState::DONTKNOW;
|
|
|
|
if ( mxDispatch.is() )
|
|
{
|
|
uno::Sequence < beans::PropertyValue > aArgs;
|
|
if (pSet)
|
|
TransformItems( nId, *pSet, aArgs );
|
|
|
|
eRet = mxDispatch->Dispatch( aArgs, bForceSynchron );
|
|
}
|
|
|
|
return eRet;
|
|
}
|
|
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|