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