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