Coverage Report

Created: 2026-09-28 10:59

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
129k
    nId(nFuncId),
192
129k
    pInternalController(nullptr),
193
129k
    pController(nullptr),
194
129k
    pLastItem( nullptr ),
195
129k
    eLastState( SfxItemState::UNKNOWN ),
196
129k
    bItemVisible( true )
197
129k
{
198
129k
    bCtrlDirty = true;
199
129k
    bSlotDirty = true;
200
129k
    bItemDirty = true;
201
129k
}
202
203
204
// The Destructor checks by assertion, even if controllers are registered.
205
206
SfxStateCache::~SfxStateCache()
207
129k
{
208
129k
    DBG_ASSERT( pController == nullptr && pInternalController == nullptr, "there are still Controllers registered" );
209
210
129k
    if ( !IsInvalidItem(pLastItem) && !IsDisabledItem(pLastItem) )
211
129k
    {
212
        // tdf#162666 only delete if it *was* cloned
213
129k
        delete pLastItem;
214
129k
    }
215
216
129k
    if ( mxDispatch.is() )
217
0
        mxDispatch->Release();
218
129k
}
219
220
221
// invalidates the cache (next request will force update)
222
void SfxStateCache::Invalidate( bool bWithMsg )
223
1.62k
{
224
1.62k
    bCtrlDirty = true;
225
1.62k
    if ( bWithMsg )
226
540
    {
227
540
        bSlotDirty = true;
228
540
        aSlotServ.SetSlot( nullptr );
229
540
        if ( mxDispatch.is() )
230
0
            mxDispatch->Release();
231
540
        mxDispatch.clear();
232
540
    }
233
1.62k
}
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
1.47k
{
240
241
1.47k
    if ( bSlotDirty )
242
540
    {
243
        // get the SlotServer; we need it for internal controllers anyway, but also in most cases
244
540
        rDispat.FindServer_( nId, aSlotServ );
245
246
540
        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
540
        if ( xProv.is() )
250
540
        {
251
540
            const SfxSlot* pSlot = aSlotServ.GetSlot();
252
540
            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
540
            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
540
            css::util::URL aURL;
265
540
            OUString aCmd = u".uno:"_ustr;
266
540
            aURL.Protocol = aCmd;
267
540
            aURL.Path = pSlot->GetUnoName();
268
540
            aCmd += aURL.Path;
269
540
            aURL.Complete = aCmd;
270
540
            aURL.Main = aCmd;
271
272
            // try to get a dispatch object for this command
273
540
            css::uno::Reference< css::frame::XDispatch >  xDisp = xProv->queryDispatch( aURL, OUString(), 0 );
274
540
            if ( xDisp.is() )
275
540
            {
276
                // test the dispatch object if it is just a wrapper for a SfxDispatcher
277
540
                if (auto pDisp = dynamic_cast<SfxOfficeDispatch*>(xDisp.get()))
278
540
                {
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
540
                    SfxDispatcher *pDispatcher = pDisp->GetDispatcher_Impl();
283
540
                    if ( pDispatcher == &rDispat || pDispatcher == SfxGetpApp()->GetAppDispatcher_Impl() )
284
540
                    {
285
                        // so we can use it directly
286
540
                        bSlotDirty = false;
287
540
                        bCtrlDirty = true;
288
540
                        return aSlotServ.GetSlot()? &aSlotServ: nullptr;
289
540
                    }
290
540
                }
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
540
        }
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
936
    return aSlotServ.GetSlot()? &aSlotServ: nullptr;
316
1.47k
}
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
864
{
336
864
    SetState_Impl( eState, pState, bMaybeDirty );
337
864
}
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
864
{
406
    // If a hard update occurs between enter- and leave-registrations is a
407
    // can also intermediate Cached exist without controller.
408
864
    if ( !pController && !pInternalController )
409
0
        return;
410
411
864
    DBG_ASSERT( bMaybeDirty || !bSlotDirty, "setting state of dirty message" );
412
864
    DBG_ASSERT( SfxControllerItem::GetItemState(pState) == eState, "invalid SfxItemState" );
413
414
    // does the controller have to be notified at all?
415
864
    bool bNotify = bItemDirty;
416
864
    if ( !bItemDirty )
417
432
    {
418
432
        bNotify = !SfxPoolItem::areSame(pLastItem, pState) || (eState != eLastState);
419
432
    }
420
421
864
    if ( bNotify )
422
576
    {
423
        // Update Controller
424
576
        if ( !mxDispatch.is() && pController )
425
576
        {
426
576
            for ( SfxControllerItem *pCtrl = pController;
427
1.40k
                pCtrl;
428
828
                pCtrl = pCtrl->GetItemLink() )
429
828
                pCtrl->StateChangedAtToolBoxControl( nId, eState, pState );
430
576
        }
431
432
576
        if ( pInternalController )
433
576
            static_cast<SfxDispatchController_Impl *>(pInternalController)->StateChanged( nId, eState, pState, &aSlotServ );
434
435
        // Remember new value
436
576
        if ( !IsInvalidItem(pLastItem) && !IsDisabledItem(pLastItem) )
437
576
        {
438
576
            delete pLastItem;
439
576
            pLastItem = nullptr;
440
576
        }
441
442
        // tdf#164745 clone when it exists and it's really an item
443
576
        if ( pState && !IsInvalidItem(pState) && !IsDisabledItem(pState) )
444
396
            pLastItem = pState->Clone();
445
180
        else
446
180
            pLastItem = nullptr;
447
448
576
        eLastState = eState;
449
576
        bItemDirty = false;
450
576
    }
451
452
864
    bCtrlDirty = false;
453
864
}
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
144
{
487
144
    if ( mxDispatch.is() )
488
0
        return mxDispatch->xDisp;
489
144
    return css::uno::Reference< css::frame::XDispatch > ();
490
144
}
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: */