Coverage Report

Created: 2026-04-09 11:41

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/sd/source/ui/view/ViewShellManager.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
#include <ViewShellManager.hxx>
21
#include <ViewShell.hxx>
22
#include <ViewShellBase.hxx>
23
#include <Window.hxx>
24
#include <DrawDocShell.hxx>
25
26
#include <sal/log.hxx>
27
#include <sfx2/dispatch.hxx>
28
#include <sfx2/viewfrm.hxx>
29
#include <svx/svxids.hrc>
30
#include <svx/fmshell.hxx>
31
#include <vcl/vclevent.hxx>
32
#include <osl/diagnose.h>
33
34
#include <iterator>
35
#include <list>
36
#include <unordered_map>
37
38
namespace sd {
39
40
namespace {
41
42
/** The ShellDescriptor class is used to shells together with their ids and
43
    the factory that was used to create the shell.
44
45
    The shell pointer may be NULL.  In that case the shell is created on
46
    demand by a factory.
47
48
    The factory pointer may be NULL.  In that case the shell pointer is
49
    given to the ViewShellManager.
50
51
    Shell pointer and factory pointer can but should not be NULL at the same
52
    time.
53
*/
54
class ShellDescriptor {
55
public:
56
    SfxShell* mpShell;
57
    ShellId mnId;
58
    ViewShellManager::SharedShellFactory mpFactory;
59
    bool mbIsListenerAddedToWindow;
60
61
    ShellDescriptor ();
62
    explicit ShellDescriptor (ShellId nId);
63
    vcl::Window* GetWindow() const;
64
};
65
66
/** This functor can be used to search for a shell in an STL container when the
67
    shell pointer is given.
68
*/
69
class IsShell
70
{
71
public:
72
0
    explicit IsShell (const SfxShell* pShell) : mpShell(pShell) {}
73
    bool operator() (const ShellDescriptor& rDescriptor)
74
0
    { return rDescriptor.mpShell == mpShell; }
75
private:
76
    const SfxShell* mpShell;
77
};
78
79
/** This functor can be used to search for a shell in an STL container when the
80
    id of the shell is given.
81
*/
82
class IsId
83
{
84
public:
85
0
    explicit IsId (ShellId nId) : mnId(nId) {}
86
    bool operator() (const ShellDescriptor& rDescriptor)
87
0
    { return rDescriptor.mnId == mnId; }
88
private:
89
    ShellId mnId;
90
};
91
92
} // end of anonymous namespace
93
94
class ViewShellManager::Implementation
95
{
96
public:
97
    Implementation (
98
        ViewShellBase& rBase);
99
    ~Implementation() COVERITY_NOEXCEPT_FALSE;
100
101
    void AddShellFactory (
102
        const SfxShell* pViewShell,
103
        const SharedShellFactory& rpFactory);
104
    void RemoveShellFactory (
105
        const SfxShell* pViewShell,
106
        const SharedShellFactory& rpFactory);
107
    void ActivateViewShell (
108
        ViewShell* pViewShell);
109
    void DeactivateViewShell (const ViewShell& rShell);
110
    void ActivateLowPriorityShell (SfxShell& rShell);
111
    void DeactivateShell (const SfxShell& rShell);
112
    void ActivateShell (const ShellDescriptor& rDescriptor);
113
    void ActivateLowPriorityShell (const ShellDescriptor& rDescriptor);
114
    void SetFormShell (const ViewShell* pViewShell, FmFormShell* pFormShell, bool bAbove);
115
    void ActivateSubShell (const SfxShell& rParentShell, ShellId nId);
116
    void DeactivateSubShell (const SfxShell& rParentShell, ShellId nId);
117
    void RemoveOverridingMainShell();
118
    void SetOverridingShell(const std::shared_ptr<ViewShell>& pViewShell);
119
    std::shared_ptr<ViewShell> GetOverridingShell();
120
    void MoveToTop (const SfxShell& rParentShell);
121
    SfxShell* GetShell (ShellId nId) const;
122
    SfxShell* GetTopShell() const;
123
    SfxShell* GetTopViewShell() const;
124
    void Shutdown();
125
    void InvalidateAllSubShells (const SfxShell* pParentShell);
126
127
    /** Remove all shells from the SFX stack above and including the given
128
        shell.
129
    */
130
    void TakeShellsFromStack (const SfxShell& rShell);
131
132
    class UpdateLock
133
    {
134
    public:
135
0
        explicit UpdateLock (Implementation& rImpl) : mrImpl(rImpl) {mrImpl.LockUpdate();}
136
0
        ~UpdateLock() COVERITY_NOEXCEPT_FALSE {mrImpl.UnlockUpdate();}
137
    private:
138
        Implementation& mrImpl;
139
    };
140
141
    /** Prevent updates of the shell stack.  While the sub shell manager is
142
        locked it will update its internal data structures but not alter the
143
        shell stack.  Use this method when there are several modifications
144
        to the shell stack to prevent multiple rebuilds of the shell stack
145
        and resulting broadcasts.
146
    */
147
    void LockUpdate();
148
149
    /** Allow updates of the shell stack.  This method has to be called the
150
        same number of times as LockUpdate() to really allow a rebuild of
151
        the shell stack.
152
    */
153
    void UnlockUpdate();
154
155
private:
156
    ViewShellBase& mrBase;
157
    mutable ::osl::Mutex maMutex;
158
159
0
    class ShellHash { public: size_t operator()(const SfxShell* p) const { return reinterpret_cast<size_t>(p);} };
160
    typedef std::unordered_multimap<const SfxShell*,SharedShellFactory,ShellHash>
161
        FactoryList;
162
    FactoryList maShellFactories;
163
164
    /** List of the active view shells.  In order to create gather all shells
165
        to put on the shell stack each view shell in this list is asked for
166
        its sub-shells (typically toolbars).
167
    */
168
    typedef std::list<ShellDescriptor> ActiveShellList;
169
    ActiveShellList maActiveViewShells;
170
171
    typedef std::list<ShellDescriptor> SubShellSubList;
172
    typedef std::unordered_map<const SfxShell*,SubShellSubList,ShellHash> SubShellList;
173
    SubShellList maActiveSubShells;
174
175
    /** In this member we remember what shells we have pushed on the shell
176
        stack.
177
    */
178
    typedef ::std::vector<SfxShell*> ShellStack;
179
180
    int mnUpdateLockCount;
181
182
    /** The UpdateShellStack() method can be called recursively.  This flag
183
        is used to communicate between different levels of invocation: if
184
        the stack has been updated in an inner call the outer call can (has
185
        to) stop and return immediately.
186
    */
187
    bool mbShellStackIsUpToDate;
188
189
    SfxShell* mpFormShell;
190
    const ViewShell* mpFormShellParent;
191
    bool mbFormShellAboveParent;
192
193
    SfxShell* mpTopShell;
194
    SfxShell* mpTopViewShell;
195
196
    std::weak_ptr<ViewShell> mpOverridingShell;
197
198
    void UpdateShellStack();
199
200
    void CreateShells();
201
202
    /** This method rebuilds the stack of shells that are stacked upon the
203
        view shell base.
204
    */
205
    void CreateTargetStack (ShellStack& rStack) const;
206
207
    DECL_LINK(WindowEventHandler, VclWindowEvent&, void);
208
209
#if OSL_DEBUG_LEVEL >= 2
210
    void DumpShellStack (const ShellStack& rStack);
211
    void DumpSfxShellStack();
212
#endif
213
214
    /** To be called before a shell is taken from the SFX shell stack.  This
215
        method deactivates an active text editing to avoid problems with
216
        undo managers.
217
        Afterwards the Deactivate() of the shell is called.
218
    */
219
    static void Deactivate (SfxShell* pShell);
220
221
    ShellDescriptor CreateSubShell (
222
        SfxShell const * pShell,
223
        ShellId nShellId);
224
    void DestroyViewShell (ShellDescriptor& rDescriptor);
225
    static void DestroySubShell (const ShellDescriptor& rDescriptor);
226
};
227
228
//===== ViewShellManager ======================================================
229
230
ViewShellManager::ViewShellManager (ViewShellBase& rBase)
231
0
    : mpImpl(new Implementation(rBase)),
232
0
      mbValid(true)
233
0
{
234
0
}
235
236
ViewShellManager::~ViewShellManager()
237
0
{
238
0
}
239
240
void ViewShellManager::AddSubShellFactory (
241
    ViewShell const * pViewShell,
242
    const SharedShellFactory& rpFactory)
243
0
{
244
0
    if (mbValid)
245
0
        mpImpl->AddShellFactory(pViewShell, rpFactory);
246
0
}
247
248
void ViewShellManager::RemoveSubShellFactory (
249
    ViewShell const * pViewShell,
250
    const SharedShellFactory& rpFactory)
251
0
{
252
0
    if (mbValid)
253
0
        mpImpl->RemoveShellFactory(pViewShell, rpFactory);
254
0
}
255
256
void ViewShellManager::ActivateViewShell (ViewShell* pViewShell)
257
0
{
258
0
    if (mbValid)
259
0
        return mpImpl->ActivateViewShell(pViewShell);
260
0
}
261
262
void ViewShellManager::DeactivateViewShell (const ViewShell* pShell)
263
0
{
264
0
    if (mbValid && pShell!=nullptr)
265
0
        mpImpl->DeactivateViewShell(*pShell);
266
0
}
267
268
269
void ViewShellManager::RemoveOverridingMainShell()
270
0
{
271
0
    if(mbValid)
272
0
        mpImpl->RemoveOverridingMainShell();
273
0
}
274
275
void ViewShellManager::SetOverridingMainShell(const std::shared_ptr<ViewShell>& pViewShell)
276
0
{
277
0
    if(mbValid)
278
0
        mpImpl->SetOverridingShell(pViewShell);
279
0
}
280
281
std::shared_ptr<ViewShell> ViewShellManager::GetOverridingMainShell()
282
0
{
283
0
    if(mbValid)
284
0
        return mpImpl->GetOverridingShell();
285
0
    return {};
286
0
}
287
288
void ViewShellManager::SetFormShell (
289
    const ViewShell* pParentShell,
290
    FmFormShell* pFormShell,
291
    bool bAbove)
292
0
{
293
0
    if (mbValid)
294
0
        mpImpl->SetFormShell(pParentShell,pFormShell,bAbove);
295
0
}
296
297
void ViewShellManager::ActivateSubShell (const ViewShell& rViewShell, ShellId nId)
298
0
{
299
0
    if (mbValid)
300
0
        mpImpl->ActivateSubShell(rViewShell,nId);
301
0
}
302
303
void ViewShellManager::DeactivateSubShell (const ViewShell& rViewShell, ShellId nId)
304
0
{
305
0
    if (mbValid)
306
0
        mpImpl->DeactivateSubShell(rViewShell,nId);
307
0
}
308
309
void ViewShellManager::InvalidateAllSubShells (ViewShell const * pViewShell)
310
0
{
311
0
    if (mbValid)
312
0
        mpImpl->InvalidateAllSubShells(pViewShell);
313
0
}
314
315
void ViewShellManager::ActivateLowPriorityShell (SfxShell* pShell)
316
0
{
317
0
    if (mbValid && pShell!=nullptr)
318
0
        mpImpl->ActivateLowPriorityShell(*pShell);
319
0
}
320
321
void ViewShellManager::DeactivateShell (const SfxShell* pShell)
322
0
{
323
0
    if (mbValid && pShell!=nullptr)
324
0
        mpImpl->DeactivateShell(*pShell);
325
0
}
326
327
void ViewShellManager::MoveToTop (const ViewShell& rParentShell)
328
0
{
329
0
    if (mbValid)
330
0
        mpImpl->MoveToTop(rParentShell);
331
0
}
332
333
SfxShell* ViewShellManager::GetShell (ShellId nId) const
334
0
{
335
0
    if (mbValid)
336
0
        return mpImpl->GetShell(nId);
337
0
    else
338
0
        return nullptr;
339
0
}
340
341
SfxShell* ViewShellManager::GetTopShell() const
342
0
{
343
0
    if (mbValid)
344
0
        return mpImpl->GetTopShell();
345
0
    else
346
0
        return nullptr;
347
0
}
348
349
SfxShell* ViewShellManager::GetTopViewShell() const
350
0
{
351
0
    if (mbValid)
352
0
        return mpImpl->GetTopViewShell();
353
0
    else
354
0
        return nullptr;
355
0
}
356
357
void ViewShellManager::Shutdown()
358
0
{
359
0
    if (mbValid)
360
0
    {
361
0
        mpImpl->Shutdown();
362
0
        mbValid = false;
363
0
    }
364
0
}
365
366
void ViewShellManager::LockUpdate()
367
0
{
368
0
    mpImpl->LockUpdate();
369
0
}
370
371
void ViewShellManager::UnlockUpdate()
372
0
{
373
0
    mpImpl->UnlockUpdate();
374
0
}
375
376
//===== ViewShellManager::Implementation ======================================
377
378
ViewShellManager::Implementation::Implementation (
379
    ViewShellBase& rBase)
380
0
    : mrBase(rBase),
381
0
      mnUpdateLockCount(0),
382
0
      mbShellStackIsUpToDate(true),
383
0
      mpFormShell(nullptr),
384
0
      mpFormShellParent(nullptr),
385
0
      mbFormShellAboveParent(true),
386
0
      mpTopShell(nullptr),
387
0
      mpTopViewShell(nullptr)
388
0
{}
389
390
ViewShellManager::Implementation::~Implementation() COVERITY_NOEXCEPT_FALSE
391
0
{
392
0
    Shutdown();
393
0
}
394
395
void ViewShellManager::Implementation::AddShellFactory (
396
    const SfxShell* pViewShell,
397
    const SharedShellFactory& rpFactory)
398
0
{
399
0
    bool bAlreadyAdded (false);
400
401
    // Check that the given factory has not already been added.
402
0
    ::std::pair<FactoryList::iterator,FactoryList::iterator> aRange(
403
0
        maShellFactories.equal_range(pViewShell));
404
0
    for (FactoryList::const_iterator iFactory=aRange.first; iFactory!=aRange.second; ++iFactory)
405
0
        if (iFactory->second == rpFactory)
406
0
        {
407
0
            bAlreadyAdded = true;
408
0
            break;
409
0
        }
410
411
    // Add the factory if it is not already present.
412
0
    if ( ! bAlreadyAdded)
413
0
        maShellFactories.emplace(pViewShell, rpFactory);
414
0
}
415
416
void ViewShellManager::Implementation::RemoveShellFactory (
417
    const SfxShell* pViewShell,
418
    const SharedShellFactory& rpFactory)
419
0
{
420
0
    ::std::pair<FactoryList::iterator,FactoryList::iterator> aRange(
421
0
        maShellFactories.equal_range(pViewShell));
422
0
    for (FactoryList::iterator iFactory=aRange.first; iFactory!=aRange.second; ++iFactory)
423
0
        if (iFactory->second == rpFactory)
424
0
        {
425
0
            maShellFactories.erase(iFactory);
426
0
            break;
427
0
        }
428
0
}
429
430
void ViewShellManager::Implementation::ActivateViewShell (ViewShell* pViewShell)
431
0
{
432
0
    ::osl::MutexGuard aGuard (maMutex);
433
434
0
    ShellDescriptor aResult;
435
0
    aResult.mpShell = pViewShell;
436
437
    // Register as window listener so that the shells of the current
438
    // window can be moved to the top of the shell stack.
439
0
    if (aResult.mpShell != nullptr)
440
0
    {
441
0
        vcl::Window* pWindow = aResult.GetWindow();
442
0
        if (pWindow != nullptr)
443
0
        {
444
0
            pWindow->AddEventListener(
445
0
                LINK(this, ViewShellManager::Implementation, WindowEventHandler));
446
0
            aResult.mbIsListenerAddedToWindow = true;
447
0
        }
448
0
        else
449
0
        {
450
0
            SAL_WARN("sd.view",
451
0
                "ViewShellManager::ActivateViewShell: "
452
0
                "new view shell has no active window");
453
0
        }
454
0
    }
455
456
0
    ActivateShell(aResult);
457
0
}
458
459
void ViewShellManager::Implementation::DeactivateViewShell (const ViewShell& rShell)
460
0
{
461
0
    ::osl::MutexGuard aGuard (maMutex);
462
463
0
    ActiveShellList::iterator iShell (::std::find_if (
464
0
        maActiveViewShells.begin(),
465
0
        maActiveViewShells.end(),
466
0
        IsShell(&rShell)));
467
0
    if (iShell == maActiveViewShells.end())
468
0
        return;
469
470
    // iShell points to a ShellDescriptor with mpShell pointing to rShell
471
472
0
    UpdateLock aLocker (*this);
473
474
0
    ShellDescriptor aDescriptor(*iShell);
475
0
    mrBase.GetDocShell()->Disconnect(&rShell);
476
0
    maActiveViewShells.erase(iShell);
477
0
    TakeShellsFromStack(rShell);
478
479
    // Deactivate sub shells.
480
0
    SubShellList::iterator iList (maActiveSubShells.find(&rShell));
481
0
    if (iList != maActiveSubShells.end())
482
0
    {
483
0
        SubShellSubList& rList (iList->second);
484
0
        while ( ! rList.empty())
485
0
            DeactivateSubShell(rShell, rList.front().mnId);
486
0
    }
487
488
0
    DestroyViewShell(aDescriptor);
489
0
}
490
491
void ViewShellManager::Implementation::ActivateLowPriorityShell (SfxShell& rShell)
492
0
{
493
0
    ::osl::MutexGuard aGuard (maMutex);
494
495
    // Create a new shell or recycle on in the cache.
496
0
    ShellDescriptor aDescriptor;
497
0
    aDescriptor.mpShell = &rShell;
498
499
0
    ActivateLowPriorityShell(aDescriptor);
500
0
}
501
502
void ViewShellManager::Implementation::ActivateShell (const ShellDescriptor& rDescriptor)
503
0
{
504
    // Put shell on top of the active view shells.
505
0
    if (rDescriptor.mpShell != nullptr)
506
0
    {
507
0
        maActiveViewShells.insert( maActiveViewShells.begin(), rDescriptor);
508
0
    }
509
0
}
510
511
void ViewShellManager::Implementation::ActivateLowPriorityShell (const ShellDescriptor& rDescriptor)
512
0
{
513
    // Put shell on bottom of the active view shells.
514
0
    if (rDescriptor.mpShell != nullptr)
515
0
    {
516
0
        maActiveViewShells.push_back( rDescriptor );
517
0
    }
518
0
}
519
520
void ViewShellManager::Implementation::DeactivateShell (const SfxShell& rShell)
521
0
{
522
0
    ::osl::MutexGuard aGuard (maMutex);
523
524
0
    ActiveShellList::iterator iShell (::std::find_if (
525
0
        maActiveViewShells.begin(),
526
0
        maActiveViewShells.end(),
527
0
        IsShell(&rShell)));
528
0
    if (iShell == maActiveViewShells.end())
529
0
        return;
530
531
    // iShell points to a ShellDescriptor with mpShell pointing to rShell
532
533
0
    UpdateLock aLocker (*this);
534
535
0
    ShellDescriptor aDescriptor(*iShell);
536
0
    mrBase.GetDocShell()->Disconnect(dynamic_cast<const ViewShell*>(&rShell));
537
0
    maActiveViewShells.erase(iShell);
538
0
    TakeShellsFromStack(rShell);
539
540
    // Deactivate sub shells.
541
0
    SubShellList::iterator iList (maActiveSubShells.find(&rShell));
542
0
    if (iList != maActiveSubShells.end())
543
0
    {
544
0
        SubShellSubList& rList (iList->second);
545
0
        while ( ! rList.empty())
546
0
            DeactivateSubShell(rShell, rList.front().mnId);
547
0
    }
548
549
0
    DestroyViewShell(aDescriptor);
550
0
}
551
552
void ViewShellManager::Implementation::ActivateSubShell (
553
    const SfxShell& rParentShell,
554
    ShellId nId)
555
0
{
556
0
    ::osl::MutexGuard aGuard (maMutex);
557
558
    // Check that the given view shell is active.
559
0
    if (std::none_of (maActiveViewShells.begin(), maActiveViewShells.end(), IsShell(&rParentShell)))
560
0
        return;
561
562
    // Create the sub shell list if it does not yet exist.
563
0
    SubShellList::iterator iList (maActiveSubShells.find(&rParentShell));
564
0
    if (iList == maActiveSubShells.end())
565
0
        iList = maActiveSubShells.emplace(&rParentShell,SubShellSubList()).first;
566
567
    // Do not activate an object bar that is already active.  Requesting
568
    // this is not exactly an error but may be an indication of one.
569
0
    SubShellSubList& rList (iList->second);
570
0
    if (std::any_of(rList.begin(),rList.end(), IsId(nId)))
571
0
        return;
572
573
    // Add just the id of the sub shell. The actual shell is created
574
    // later in CreateShells().
575
0
    UpdateLock aLock (*this);
576
0
    rList.emplace_back(nId);
577
0
}
578
579
void ViewShellManager::Implementation::DeactivateSubShell (
580
    const SfxShell& rParentShell,
581
    ShellId nId)
582
0
{
583
0
    ::osl::MutexGuard aGuard (maMutex);
584
585
    // Check that the given view shell is active.
586
0
    SubShellList::iterator iList (maActiveSubShells.find(&rParentShell));
587
0
    if (iList == maActiveSubShells.end())
588
0
        return;
589
590
    // Look up the sub shell.
591
0
    SubShellSubList& rList (iList->second);
592
0
    SubShellSubList::iterator iShell (
593
0
        ::std::find_if(rList.begin(),rList.end(), IsId(nId)));
594
0
    if (iShell == rList.end())
595
0
        return;
596
0
    SfxShell* pShell = iShell->mpShell;
597
0
    if (pShell == nullptr)
598
0
        return;
599
600
0
    UpdateLock aLock (*this);
601
602
0
    ShellDescriptor aDescriptor(*iShell);
603
    // Remove the sub shell from both the internal structure as well as the
604
    // SFX shell stack above and including the sub shell.
605
0
    rList.erase(iShell);
606
0
    TakeShellsFromStack(*pShell);
607
608
0
    DestroySubShell(aDescriptor);
609
0
}
610
611
std::shared_ptr<ViewShell> ViewShellManager::Implementation::GetOverridingShell()
612
0
{
613
0
    return mpOverridingShell.lock();
614
0
}
615
616
void ViewShellManager::Implementation::RemoveOverridingMainShell()
617
0
{
618
0
    mpOverridingShell.reset();
619
0
}
620
621
void ViewShellManager::Implementation::SetOverridingShell(const std::shared_ptr<ViewShell>& pViewShell)
622
0
{
623
0
    mpOverridingShell = pViewShell;
624
0
}
625
626
void ViewShellManager::Implementation::MoveToTop (const SfxShell& rShell)
627
0
{
628
0
    ::osl::MutexGuard aGuard (maMutex);
629
630
    // Check that we have access to a dispatcher.  If not, then we are
631
    // (probably) called while the view shell is still being created or
632
    // initialized.  Without dispatcher we can not rebuild the shell stack
633
    // to move the requested shell to the top.  So return right away instead
634
    // of making a mess without being able to clean up afterwards.
635
0
    if (mrBase.GetDispatcher() == nullptr)
636
0
        return;
637
638
0
    ActiveShellList::iterator iShell (::std::find_if (
639
0
        maActiveViewShells.begin(),
640
0
        maActiveViewShells.end(),
641
0
        IsShell(&rShell)));
642
0
    bool bMove = true;
643
0
    if (iShell != maActiveViewShells.end())
644
0
    {
645
        // Is the shell already at the top of the stack?  We have to keep
646
        // the case in mind that mbKeepMainViewShellOnTop is true.  Shells
647
        // that are not the main view shell are placed on the second-to-top
648
        // position in this case.
649
0
        if (iShell == maActiveViewShells.begin())
650
0
        {
651
            // The shell is at the top position and is either a) the main
652
            // view shell or b) another shell but the main view shell is not
653
            // kept at the top position.  We do not have to move the shell.
654
0
            bMove = false;
655
0
        }
656
0
    }
657
0
    else
658
0
    {
659
        // The shell is not on the stack.  Therefore it can not be moved.
660
        // We could insert it but we don't.  Use ActivateViewShell() for
661
        // that.
662
0
        bMove = false;
663
0
    }
664
665
    // When the shell is not at the right position it is removed from the
666
    // internal list of shells and inserted at the correct position.
667
0
    if (bMove)
668
0
    {
669
0
        UpdateLock aLock (*this);
670
671
0
        ShellDescriptor aDescriptor(*iShell);
672
673
0
        TakeShellsFromStack(rShell);
674
0
        maActiveViewShells.erase(iShell);
675
676
0
        maActiveViewShells.insert(maActiveViewShells.begin(), aDescriptor);
677
0
    }
678
0
}
679
680
SfxShell* ViewShellManager::Implementation::GetShell (ShellId nId) const
681
0
{
682
0
    ::osl::MutexGuard aGuard (maMutex);
683
684
0
    SfxShell* pShell = nullptr;
685
686
    // First search the active view shells.
687
0
    ActiveShellList::const_iterator iShell (
688
0
        ::std::find_if (
689
0
        maActiveViewShells.begin(),
690
0
        maActiveViewShells.end(),
691
0
        IsId(nId)));
692
0
    if (iShell != maActiveViewShells.end())
693
0
        pShell = iShell->mpShell;
694
0
    else
695
0
    {
696
        // Now search the active sub shells of every active view shell.
697
0
        for (auto const& activeSubShell : maActiveSubShells)
698
0
        {
699
0
            const SubShellSubList& rList (activeSubShell.second);
700
0
            SubShellSubList::const_iterator iSubShell(
701
0
                ::std::find_if(rList.begin(),rList.end(), IsId(nId)));
702
0
            if (iSubShell != rList.end())
703
0
            {
704
0
                pShell = iSubShell->mpShell;
705
0
                break;
706
0
            }
707
0
        }
708
0
    }
709
710
0
    return pShell;
711
0
}
712
713
SfxShell* ViewShellManager::Implementation::GetTopShell() const
714
0
{
715
0
    OSL_ASSERT(mpTopShell == mrBase.GetSubShell(0));
716
0
    return mpTopShell;
717
0
}
718
719
SfxShell* ViewShellManager::Implementation::GetTopViewShell() const
720
0
{
721
0
    return mpTopViewShell;
722
0
}
723
724
void ViewShellManager::Implementation::LockUpdate()
725
0
{
726
0
    mnUpdateLockCount++;
727
0
}
728
729
void ViewShellManager::Implementation::UnlockUpdate()
730
0
{
731
0
    ::osl::MutexGuard aGuard (maMutex);
732
733
0
    mnUpdateLockCount--;
734
0
    if (mnUpdateLockCount < 0)
735
0
    {
736
        // This should not happen.
737
0
        OSL_ASSERT (mnUpdateLockCount>=0);
738
0
        mnUpdateLockCount = 0;
739
0
    }
740
0
    if (mnUpdateLockCount == 0)
741
0
        UpdateShellStack();
742
0
}
743
744
/** Update the SFX shell stack (the portion that is visible to us) so that
745
    it matches the internal shell stack.  This is done in six steps:
746
    1. Create the missing view shells and sub shells.
747
    2. Set up the internal shell stack.
748
    3. Get the SFX shell stack.
749
    4. Find the lowest shell in which the two stacks differ.
750
    5. Remove all shells above and including that shell from the SFX stack.
751
    6. Push all shells of the internal stack on the SFX shell stack that are
752
    not already present on the later.
753
*/
754
void ViewShellManager::Implementation::UpdateShellStack()
755
0
{
756
0
    ::osl::MutexGuard aGuard (maMutex);
757
758
    // Remember the undo manager from the top-most shell on the stack.
759
0
    SfxShell* pTopMostShell = mrBase.GetSubShell(0);
760
0
    SfxUndoManager* pUndoManager = (pTopMostShell!=nullptr)
761
0
        ? pTopMostShell->GetUndoManager()
762
0
        : nullptr;
763
764
    // 1. Create the missing shells.
765
0
    CreateShells();
766
767
0
    SfxShell* pPreviousTopViewShell = mpTopViewShell;
768
    // Update the pointer to the top-most active view shell.
769
0
    mpTopViewShell = (maActiveViewShells.empty() || mbFormShellAboveParent)
770
0
        ? nullptr : maActiveViewShells.begin()->mpShell;
771
772
0
    bool bTopViewShellChanged = mpTopViewShell != pPreviousTopViewShell;
773
774
    // 2. Create the internal target stack.
775
0
    ShellStack aTargetStack;
776
0
    CreateTargetStack(aTargetStack);
777
778
    // 3. Get SFX shell stack.
779
0
    ShellStack aSfxShellStack;
780
0
    sal_uInt16 nIndex (0);
781
0
    while (mrBase.GetSubShell(nIndex)!=nullptr)
782
0
        ++nIndex;
783
0
    aSfxShellStack.reserve(nIndex);
784
0
    while (nIndex > 0)
785
0
    {
786
0
        --nIndex;
787
0
        aSfxShellStack.push_back(mrBase.GetSubShell(nIndex));
788
0
    }
789
790
#if OSL_DEBUG_LEVEL >= 2
791
    SAL_INFO("sd.view", __func__ << ": Current SFX Stack");
792
    DumpShellStack(aSfxShellStack);
793
    SAL_INFO("sd.view", __func__ << ": Target Stack");
794
    DumpShellStack(aTargetStack);
795
#endif
796
797
    // 4. Find the lowest shell in which the two stacks differ.
798
0
    auto mismatchIters = std::mismatch(aSfxShellStack.begin(), aSfxShellStack.end(),
799
0
        aTargetStack.begin(), aTargetStack.end());
800
0
    ShellStack::iterator iSfxShell (mismatchIters.first);
801
0
    ShellStack::iterator iTargetShell (mismatchIters.second);
802
803
    // 5. Remove all shells above and including the differing shell from the
804
    // SFX stack starting with the shell on top of the stack.
805
0
    for (std::reverse_iterator<ShellStack::const_iterator> i(aSfxShellStack.end()), iLast(iSfxShell);
806
0
            i != iLast; ++i)
807
0
    {
808
0
        SfxShell* const pShell = *i;
809
0
        SAL_INFO("sd.view", __func__ << ": removing shell " << pShell << " from stack");
810
0
        mrBase.RemoveSubShell(pShell);
811
0
    }
812
0
    aSfxShellStack.erase(iSfxShell, aSfxShellStack.end());
813
814
    // 6. Push shells from the given stack onto the SFX stack.
815
0
    mbShellStackIsUpToDate = false;
816
0
    while (iTargetShell != aTargetStack.end())
817
0
    {
818
0
        SAL_INFO("sd.view", __func__ << ": pushing shell " << *iTargetShell << " on stack");
819
0
        mrBase.AddSubShell(**iTargetShell);
820
0
        ++iTargetShell;
821
822
        // The pushing of the shell on to the shell stack may have lead to
823
        // another invocation of this method.  In this case we have to abort
824
        // pushing shells on the stack and return immediately.
825
0
        if (mbShellStackIsUpToDate)
826
0
            break;
827
0
    }
828
0
    if (mrBase.GetDispatcher() != nullptr)
829
0
        mrBase.GetDispatcher()->Flush();
830
831
    // Update the pointer to the top-most shell and set its undo manager
832
    // to the one of the previous top-most shell.
833
0
    mpTopShell = mrBase.GetSubShell(0);
834
0
    if (mpTopShell!=nullptr && pUndoManager!=nullptr && mpTopShell->GetUndoManager()==nullptr)
835
0
        mpTopShell->SetUndoManager(pUndoManager);
836
837
    // Only broadcast context for activation on the top-most ViewShell
838
0
    if (mpTopViewShell && bTopViewShellChanged)
839
0
        mpTopViewShell->BroadcastContextForActivation(true);
840
841
    // Finally tell an invocation of this method on a higher level that it can (has
842
    // to) abort and return immediately.
843
0
    mbShellStackIsUpToDate = true;
844
845
#if OSL_DEBUG_LEVEL >= 2
846
    SAL_INFO("sd.view", __func__ << ": New current stack");
847
    DumpSfxShellStack();
848
#endif
849
0
}
850
851
void ViewShellManager::Implementation::TakeShellsFromStack (const SfxShell& rShell)
852
0
{
853
0
    ::osl::MutexGuard aGuard (maMutex);
854
855
    // Remember the undo manager from the top-most shell on the stack.
856
0
    SfxShell* pTopMostShell = mrBase.GetSubShell(0);
857
0
    SfxUndoManager* pUndoManager = (pTopMostShell!=nullptr)
858
0
        ? pTopMostShell->GetUndoManager()
859
0
        : nullptr;
860
861
#if OSL_DEBUG_LEVEL >= 2
862
    SAL_INFO("sd.view", __func__ << "TakeShellsFromStack( " << pShell << ")");
863
    DumpSfxShellStack();
864
#endif
865
866
    // 0.Make sure that the given shell is on the stack.  This is a
867
    // preparation for the following assertion.
868
0
    for (sal_uInt16 nIndex=0; true; nIndex++)
869
0
    {
870
0
        SfxShell* pShellOnStack = mrBase.GetSubShell(nIndex);
871
0
        if (pShellOnStack == nullptr)
872
0
        {
873
            // the shell is not on the stack.
874
0
            return;
875
0
        }
876
0
        else if (pShellOnStack == &rShell)
877
0
            break;
878
0
    }
879
880
    // 1. Deactivate our shells on the stack before they are removed so
881
    // that during the Deactivation() calls the stack is still intact.
882
0
    for (sal_uInt16 nIndex=0; true; nIndex++)
883
0
    {
884
0
        SfxShell* pShellOnStack = mrBase.GetSubShell(nIndex);
885
0
        Deactivate(pShellOnStack);
886
0
        if (pShellOnStack == &rShell)
887
0
            break;
888
0
    }
889
890
    // 2. Remove the shells from the stack.
891
0
    while (true)
892
0
    {
893
0
        SfxShell* pShellOnStack = mrBase.GetSubShell(0);
894
0
        SAL_INFO("sd.view", __func__ << "removing shell " << pShellOnStack << " from stack");
895
0
        mrBase.RemoveSubShell(pShellOnStack);
896
0
        if (pShellOnStack == &rShell)
897
0
            break;
898
0
    }
899
900
    // 3. Update the stack.
901
0
    if (mrBase.GetDispatcher() != nullptr)
902
0
        mrBase.GetDispatcher()->Flush();
903
904
    // Update the pointer to the top-most shell and set its undo manager
905
    // to the one of the previous top-most shell.
906
0
    mpTopShell = mrBase.GetSubShell(0);
907
0
    if (mpTopShell!=nullptr && pUndoManager!=nullptr && mpTopShell->GetUndoManager()==nullptr)
908
0
        mpTopShell->SetUndoManager(pUndoManager);
909
910
#if OSL_DEBUG_LEVEL >= 2
911
    SAL_INFO("sd.view", __func__ << "Sfx shell stack is:");
912
    DumpSfxShellStack();
913
#endif
914
0
}
915
916
void ViewShellManager::Implementation::CreateShells()
917
0
{
918
0
    ::osl::MutexGuard aGuard (maMutex);
919
920
    // Iterate over all view shells.
921
0
    ActiveShellList::reverse_iterator iShell;
922
0
    for (iShell=maActiveViewShells.rbegin(); iShell!=maActiveViewShells.rend(); ++iShell)
923
0
    {
924
        // Get the list of associated sub shells.
925
0
        SubShellList::iterator iList (maActiveSubShells.find(iShell->mpShell));
926
0
        if (iList != maActiveSubShells.end())
927
0
        {
928
0
            SubShellSubList& rList (iList->second);
929
930
            // Iterate over all sub shells of the current view shell.
931
0
            for (auto & subShell : rList)
932
0
            {
933
0
                if (subShell.mpShell == nullptr)
934
0
                {
935
0
                    subShell = CreateSubShell(iShell->mpShell,subShell.mnId);
936
0
                }
937
0
            }
938
0
        }
939
0
    }
940
0
}
941
942
void ViewShellManager::Implementation::CreateTargetStack (ShellStack& rStack) const
943
0
{
944
    // Create a local stack of the shells that are to push on the shell
945
    // stack.  We can thus safely create the required shells while still
946
    // having a valid shell stack.
947
0
    for (ActiveShellList::const_reverse_iterator iViewShell (maActiveViewShells.rbegin());
948
0
         iViewShell != maActiveViewShells.rend();
949
0
         ++iViewShell)
950
0
    {
951
        // Possibly place the form shell below the current view shell.
952
0
        if ( ! mbFormShellAboveParent
953
0
            && mpFormShell!=nullptr
954
0
            && iViewShell->mpShell==mpFormShellParent)
955
0
        {
956
0
            rStack.push_back(mpFormShell);
957
0
        }
958
959
        // Put the view shell itself on the local stack.
960
0
        rStack.push_back (iViewShell->mpShell);
961
962
        // Possibly place the form shell above the current view shell.
963
0
        if (mbFormShellAboveParent
964
0
            && mpFormShell!=nullptr
965
0
            && iViewShell->mpShell==mpFormShellParent)
966
0
        {
967
0
            rStack.push_back(mpFormShell);
968
0
        }
969
970
        // Add all other sub shells.
971
0
        SubShellList::const_iterator iList (maActiveSubShells.find(iViewShell->mpShell));
972
0
        if (iList != maActiveSubShells.end())
973
0
        {
974
0
            const SubShellSubList& rList (iList->second);
975
0
            SubShellSubList::const_reverse_iterator iSubShell;
976
0
            for (iSubShell=rList.rbegin(); iSubShell!=rList.rend(); ++iSubShell)
977
0
                if (iSubShell->mpShell != mpFormShell)
978
0
                    rStack.push_back(iSubShell->mpShell);
979
0
        }
980
0
    }
981
0
}
982
983
IMPL_LINK(ViewShellManager::Implementation, WindowEventHandler, VclWindowEvent&, rEvent, void)
984
0
{
985
0
        vcl::Window* pEventWindow = rEvent.GetWindow();
986
987
0
        switch (rEvent.GetId())
988
0
        {
989
0
            case VclEventId::WindowGetFocus:
990
0
            {
991
0
                for (auto const& activeShell : maActiveViewShells)
992
0
                {
993
0
                    if (pEventWindow == activeShell.GetWindow())
994
0
                    {
995
0
                        MoveToTop(*activeShell.mpShell);
996
0
                        break;
997
0
                    }
998
0
                }
999
0
            }
1000
0
            break;
1001
1002
0
            case VclEventId::WindowLoseFocus:
1003
0
                break;
1004
1005
0
            case VclEventId::ObjectDying:
1006
                // Remember that we do not have to remove the window
1007
                // listener for this window.
1008
0
                for (auto & activeViewShell : maActiveViewShells)
1009
0
                {
1010
0
                    if (activeViewShell.GetWindow() == pEventWindow)
1011
0
                    {
1012
0
                        activeViewShell.mbIsListenerAddedToWindow = false;
1013
0
                        break;
1014
0
                    }
1015
0
                }
1016
0
                break;
1017
1018
0
            default: break;
1019
0
        }
1020
0
}
1021
1022
ShellDescriptor ViewShellManager::Implementation::CreateSubShell (
1023
    SfxShell const * pParentShell,
1024
    ShellId nShellId)
1025
0
{
1026
0
    ::osl::MutexGuard aGuard (maMutex);
1027
0
    ShellDescriptor aResult;
1028
1029
    // Look up the factories for the parent shell.
1030
0
    ::std::pair<FactoryList::iterator,FactoryList::iterator> aRange(
1031
0
        maShellFactories.equal_range(pParentShell));
1032
1033
    // Try all factories to create the shell.
1034
0
    for (FactoryList::const_iterator iFactory=aRange.first; iFactory!=aRange.second; ++iFactory)
1035
0
    {
1036
0
        SharedShellFactory pFactory = iFactory->second;
1037
0
        if (pFactory != nullptr)
1038
0
            aResult.mpShell = pFactory->CreateShell(nShellId);
1039
1040
        // Exit the loop when the shell has been successfully created.
1041
0
        if (aResult.mpShell != nullptr)
1042
0
        {
1043
0
            aResult.mpFactory = std::move(pFactory);
1044
0
            aResult.mnId = nShellId;
1045
0
            break;
1046
0
        }
1047
0
    }
1048
1049
0
    return aResult;
1050
0
}
1051
1052
void ViewShellManager::Implementation::DestroyViewShell (
1053
    ShellDescriptor& rDescriptor)
1054
0
{
1055
0
    OSL_ASSERT(rDescriptor.mpShell != nullptr);
1056
1057
0
    if (rDescriptor.mbIsListenerAddedToWindow)
1058
0
    {
1059
0
        rDescriptor.mbIsListenerAddedToWindow = false;
1060
0
        vcl::Window* pWindow = rDescriptor.GetWindow();
1061
0
        if (pWindow != nullptr)
1062
0
        {
1063
0
            pWindow->RemoveEventListener(
1064
0
                LINK(this, ViewShellManager::Implementation, WindowEventHandler));
1065
0
        }
1066
0
    }
1067
1068
    // Destroy the sub shell factories.
1069
0
    ::std::pair<FactoryList::iterator,FactoryList::iterator> aRange(
1070
0
        maShellFactories.equal_range(rDescriptor.mpShell));
1071
0
    if (aRange.first != maShellFactories.end())
1072
0
        maShellFactories.erase(aRange.first, aRange.second);
1073
1074
    // Release the shell.
1075
0
    if (rDescriptor.mpFactory)
1076
0
        rDescriptor.mpFactory->ReleaseShell(rDescriptor.mpShell);
1077
0
}
1078
1079
void ViewShellManager::Implementation::DestroySubShell (
1080
    const ShellDescriptor& rDescriptor)
1081
0
{
1082
0
    OSL_ASSERT(rDescriptor.mpFactory);
1083
0
    rDescriptor.mpFactory->ReleaseShell(rDescriptor.mpShell);
1084
0
}
1085
1086
void ViewShellManager::Implementation::InvalidateAllSubShells (const SfxShell* pParentShell)
1087
0
{
1088
0
    ::osl::MutexGuard aGuard (maMutex);
1089
1090
0
    SubShellList::iterator iList (maActiveSubShells.find(pParentShell));
1091
0
    if (iList != maActiveSubShells.end())
1092
0
    {
1093
0
        SubShellSubList& rList (iList->second);
1094
0
        for (auto const& shell : rList)
1095
0
            if (shell.mpShell != nullptr)
1096
0
                shell.mpShell->Invalidate();
1097
0
    }
1098
0
}
1099
1100
void ViewShellManager::Implementation::Shutdown()
1101
0
{
1102
0
    ::osl::MutexGuard aGuard (maMutex);
1103
1104
    // Take stacked shells from stack.
1105
0
    if ( ! maActiveViewShells.empty())
1106
0
    {
1107
0
        UpdateLock aLock (*this);
1108
1109
0
        while ( ! maActiveViewShells.empty())
1110
0
        {
1111
0
            SfxShell* pShell = maActiveViewShells.front().mpShell;
1112
0
            if (pShell != nullptr)
1113
0
            {
1114
0
                ViewShell* pViewShell = dynamic_cast<ViewShell*>(pShell);
1115
0
                if (pViewShell != nullptr)
1116
0
                    DeactivateViewShell(*pViewShell);
1117
0
                else
1118
0
                    DeactivateShell(*pShell);
1119
0
            }
1120
0
            else
1121
0
            {
1122
0
                SAL_WARN("sd.view",
1123
0
                    "ViewShellManager::Implementation::Shutdown(): empty active shell descriptor");
1124
0
                maActiveViewShells.pop_front();
1125
0
            }
1126
0
        }
1127
0
    }
1128
0
    mrBase.RemoveSubShell ();
1129
1130
0
    maShellFactories.clear();
1131
0
    mpOverridingShell.reset();
1132
0
}
1133
1134
#if OSL_DEBUG_LEVEL >= 2
1135
void ViewShellManager::Implementation::DumpShellStack (const ShellStack& rStack)
1136
{
1137
    ShellStack::const_reverse_iterator iEntry;
1138
    for (iEntry=rStack.rbegin(); iEntry!=rStack.rend(); ++iEntry)
1139
        if (*iEntry != NULL)
1140
            SAL_INFO("sd.view", __func__ << ":    " <<
1141
                *iEntry << " : " <<
1142
                (*iEntry)->GetName());
1143
        else
1144
            SAL_INFO("sd.view", __func__ << "     null");
1145
}
1146
1147
void ViewShellManager::Implementation::DumpSfxShellStack()
1148
{
1149
    ShellStack aSfxShellStack;
1150
    sal_uInt16 nIndex (0);
1151
    while (mrBase.GetSubShell(nIndex)!=NULL)
1152
        ++nIndex;
1153
    aSfxShellStack.reserve(nIndex);
1154
    while (nIndex-- > 0)
1155
        aSfxShellStack.push_back(mrBase.GetSubShell(nIndex));
1156
    DumpShellStack(aSfxShellStack);
1157
}
1158
#endif
1159
1160
void ViewShellManager::Implementation::Deactivate (SfxShell* pShell)
1161
0
{
1162
0
    OSL_ASSERT(pShell!=nullptr);
1163
1164
    // We have to end a text edit for view shells that are to be taken from
1165
    // the shell stack.
1166
0
    ViewShell* pViewShell = dynamic_cast<ViewShell*>(pShell);
1167
0
    if (pViewShell != nullptr)
1168
0
    {
1169
0
        sd::View* pView = pViewShell->GetView();
1170
0
        if (pView!=nullptr && pView->IsTextEdit())
1171
0
        {
1172
0
            pView->SdrEndTextEdit();
1173
0
            pView->UnmarkAll();
1174
1175
            // dispatch synchronously, otherwise it might execute while another
1176
            // ViewShell is active!
1177
0
            pViewShell->GetViewFrame()->GetDispatcher()->Execute(
1178
0
                SID_OBJECT_SELECT,
1179
0
                SfxCallMode::SYNCHRON);
1180
0
        }
1181
0
    }
1182
1183
    // Now we can deactivate the shell.
1184
0
    pShell->Deactivate(true);
1185
0
}
1186
1187
void ViewShellManager::Implementation::SetFormShell (
1188
    const ViewShell* pFormShellParent,
1189
    FmFormShell* pFormShell,
1190
    bool bFormShellAboveParent)
1191
0
{
1192
0
    ::osl::MutexGuard aGuard (maMutex);
1193
1194
0
    mpFormShellParent = pFormShellParent;
1195
0
    mpFormShell = pFormShell;
1196
0
    mbFormShellAboveParent = bFormShellAboveParent;
1197
0
}
1198
1199
namespace {
1200
1201
ShellDescriptor::ShellDescriptor()
1202
0
    : mpShell(nullptr),
1203
0
      mnId(ToolbarId::None),
1204
0
      mbIsListenerAddedToWindow(false)
1205
0
{
1206
0
}
1207
1208
ShellDescriptor::ShellDescriptor (
1209
    ShellId nId)
1210
0
    : mpShell(nullptr),
1211
0
      mnId(nId),
1212
0
      mbIsListenerAddedToWindow(false)
1213
0
{
1214
0
}
1215
1216
vcl::Window* ShellDescriptor::GetWindow() const
1217
0
{
1218
0
    ViewShell* pViewShell = dynamic_cast<ViewShell*>(mpShell);
1219
0
    if (pViewShell != nullptr)
1220
0
        return pViewShell->GetActiveWindow();
1221
0
    else
1222
0
        return nullptr;
1223
0
}
1224
1225
} // end of anonymous namespace
1226
1227
} // end of namespace sd
1228
1229
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */