Coverage Report

Created: 2026-04-09 11:41

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