Coverage Report

Created: 2025-12-31 10:39

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