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 #include <sfx2/sidebar/SidebarController.hxx>
20 #include <sfx2/sidebar/Deck.hxx>
21 #include <sidebar/DeckDescriptor.hxx>
22 #include <sidebar/DeckTitleBar.hxx>
23 #include <sfx2/sidebar/Panel.hxx>
24 #include <sidebar/PanelDescriptor.hxx>
25 #include <sidebar/PanelTitleBar.hxx>
26 #include <sfx2/sidebar/TabBar.hxx>
27 #include <sfx2/sidebar/Theme.hxx>
28 #include <sfx2/sidebar/SidebarChildWindow.hxx>
29 #include <sidebar/Tools.hxx>
30 #include <sfx2/sidebar/SidebarDockingWindow.hxx>
31 #include <com/sun/star/ui/XSidebarProvider.hpp>
32 #include <com/sun/star/frame/XController2.hpp>
33 #include <sfx2/sidebar/Context.hxx>
34 #include <sfx2/viewsh.hxx>
35 
36 
37 #include <framework/ContextChangeEventMultiplexerTunnel.hxx>
38 #include <vcl/floatwin.hxx>
39 #include <vcl/fixed.hxx>
40 #include <vcl/uitest/logger.hxx>
41 #include <vcl/uitest/eventdescription.hxx>
42 #include <vcl/svapp.hxx>
43 #include <splitwin.hxx>
44 #include <tools/diagnose_ex.h>
45 #include <tools/link.hxx>
46 #include <toolkit/helper/vclunohelper.hxx>
47 #include <comphelper/processfactory.hxx>
48 #include <comphelper/namedvaluecollection.hxx>
49 #include <comphelper/lok.hxx>
50 #include <sal/log.hxx>
51 #include <officecfg/Office/UI/Sidebar.hxx>
52 #include <LibreOfficeKit/LibreOfficeKitEnums.h>
53 #include <boost/property_tree/ptree.hpp>
54 #include <boost/property_tree/json_parser.hpp>
55 
56 #include <com/sun/star/awt/XWindowPeer.hpp>
57 #include <com/sun/star/frame/XDispatch.hpp>
58 #include <com/sun/star/ui/ContextChangeEventMultiplexer.hpp>
59 #include <com/sun/star/ui/ContextChangeEventObject.hpp>
60 #include <com/sun/star/ui/theUIElementFactoryManager.hpp>
61 #include <com/sun/star/util/URL.hpp>
62 #include <com/sun/star/rendering/XSpriteCanvas.hpp>
63 
64 
65 using namespace css;
66 using namespace css::uno;
67 
68 namespace
69 {
70     constexpr OUStringLiteral gsReadOnlyCommandName = u".uno:EditDoc";
71     const sal_Int32 gnWidthCloseThreshold (70);
72     const sal_Int32 gnWidthOpenThreshold (40);
73 
74     std::string UnoNameFromDeckId(std::u16string_view rsDeckId, bool isImpress = false)
75     {
76         if (rsDeckId == u"SdCustomAnimationDeck")
77             return ".uno:CustomAnimation";
78 
79         if (rsDeckId == u"PropertyDeck")
80             return isImpress ? ".uno:ModifyPage" : ".uno:Sidebar";
81 
82         if (rsDeckId == u"SdLayoutsDeck")
83             return ".uno:ModifyPage";
84 
85         if (rsDeckId == u"SdSlideTransitionDeck")
86             return ".uno:SlideChangeWindow";
87 
88         if (rsDeckId == u"SdAllMasterPagesDeck")
89             return ".uno:MasterSlidesPanel";
90 
91         if (rsDeckId == u"SdMasterPagesDeck")
92             return ".uno:MasterSlidesPanel";
93 
94         if (rsDeckId == u"GalleryDeck")
95             return ".uno:Gallery";
96 
97         return "";
98     }
99 }
100 
101 namespace sfx2::sidebar {
102 
103 namespace {
104 
105     /** When in doubt, show this deck.
106     */
107     constexpr OUStringLiteral gsDefaultDeckId(u"PropertyDeck");
108 }
109 
110 SidebarController::SidebarController (
111     SidebarDockingWindow* pParentWindow,
112     const SfxViewFrame* pViewFrame)
113     : SidebarControllerInterfaceBase(m_aMutex),
114       mpCurrentDeck(),
115       mpParentWindow(pParentWindow),
116       mpViewFrame(pViewFrame),
117       mxFrame(pViewFrame->GetFrame().GetFrameInterface()),
118       mpTabBar(VclPtr<TabBar>::Create(
119               mpParentWindow,
120               mxFrame,
121               [this](const OUString& rsDeckId) { return this->OpenThenToggleDeck(rsDeckId); },
122               [this](weld::Menu& rMainMenu, weld::Menu& rSubMenu,
123                      const ::std::vector<TabBar::DeckMenuData>& rMenuData) { return this->ShowPopupMenu(rMainMenu, rSubMenu, rMenuData); },
124               this)),
125       maCurrentContext(OUString(), OUString()),
126       maRequestedContext(),
127       mnRequestedForceFlags(SwitchFlag_NoForce),
128       mnMaximumSidebarWidth(officecfg::Office::UI::Sidebar::General::MaximumWidth::get()),
129       msCurrentDeckId(gsDefaultDeckId),
130       maPropertyChangeForwarder([this](){ return this->BroadcastPropertyChange(); }),
131       maContextChangeUpdate([this](){ return this->UpdateConfigurations(); }),
132       mbIsDeckRequestedOpen(),
133       mbIsDeckOpen(),
134       mbFloatingDeckClosed(!pParentWindow->IsFloatingMode()),
135       mnSavedSidebarWidth(pParentWindow->GetSizePixel().Width()),
136       maFocusManager([this](const Panel& rPanel){ return this->ShowPanel(rPanel); }),
137       mxReadOnlyModeDispatch(),
138       mbIsDocumentReadOnly(false),
139       mpSplitWindow(nullptr),
140       mnWidthOnSplitterButtonDown(0),
141       mpResourceManager()
142 {
143     // Decks and panel collections for this sidebar
144     mpResourceManager = std::make_unique<ResourceManager>();
145 }
146 
147 rtl::Reference<SidebarController> SidebarController::create(SidebarDockingWindow* pParentWindow,
148                                                             const SfxViewFrame* pViewFrame)
149 {
150     rtl::Reference<SidebarController> instance(new SidebarController(pParentWindow, pViewFrame));
151 
152     const css::uno::Reference<css::frame::XFrame>& rxFrame = pViewFrame->GetFrame().GetFrameInterface();
153     registerSidebarForFrame(instance.get(), rxFrame->getController());
154     rxFrame->addFrameActionListener(instance.get());
155     // Listen for window events.
156     instance->mpParentWindow->AddEventListener(LINK(instance.get(), SidebarController, WindowEventHandler));
157 
158     // Listen for theme property changes.
159     Theme::GetPropertySet()->addPropertyChangeListener(
160         "",
161         static_cast<css::beans::XPropertyChangeListener*>(instance.get()));
162 
163     // Get the dispatch object as preparation to listen for changes of
164     // the read-only state.
165     const util::URL aURL (Tools::GetURL(gsReadOnlyCommandName));
166     instance->mxReadOnlyModeDispatch = Tools::GetDispatch(rxFrame, aURL);
167     if (instance->mxReadOnlyModeDispatch.is())
168         instance->mxReadOnlyModeDispatch->addStatusListener(instance.get(), aURL);
169 
170     //first UpdateConfigurations call will SwitchToDeck
171 
172     return instance;
173 }
174 
175 SidebarController::~SidebarController()
176 {
177 }
178 
179 SidebarController* SidebarController::GetSidebarControllerForFrame (
180     const css::uno::Reference<css::frame::XFrame>& rxFrame)
181 {
182     uno::Reference<frame::XController> const xController(rxFrame->getController());
183     if (!xController.is()) // this may happen during dispose of Draw controller but perhaps it's a bug
184     {
185         SAL_WARN("sfx.sidebar", "GetSidebarControllerForFrame: frame has no XController");
186         return nullptr;
187     }
188     uno::Reference<ui::XContextChangeEventListener> const xListener(
189         framework::GetFirstListenerWith(xController,
190             [] (uno::Reference<uno::XInterface> const& xRef)
191             { return nullptr != dynamic_cast<SidebarController*>(xRef.get()); }
192         ));
193 
194     return dynamic_cast<SidebarController*>(xListener.get());
195 }
196 
197 void SidebarController::registerSidebarForFrame(SidebarController* pController, const css::uno::Reference<css::frame::XController>& xController)
198 {
199     // Listen for context change events.
200     css::uno::Reference<css::ui::XContextChangeEventMultiplexer> xMultiplexer (
201         css::ui::ContextChangeEventMultiplexer::get(
202             ::comphelper::getProcessComponentContext()));
203     xMultiplexer->addContextChangeEventListener(
204         static_cast<css::ui::XContextChangeEventListener*>(pController),
205         xController);
206 }
207 
208 void SidebarController::unregisterSidebarForFrame(SidebarController* pController, const css::uno::Reference<css::frame::XController>& xController)
209 {
210     pController->saveDeckState();
211     pController->disposeDecks();
212 
213     css::uno::Reference<css::ui::XContextChangeEventMultiplexer> xMultiplexer (
214         css::ui::ContextChangeEventMultiplexer::get(
215             ::comphelper::getProcessComponentContext()));
216     xMultiplexer->removeContextChangeEventListener(
217         static_cast<css::ui::XContextChangeEventListener*>(pController),
218         xController);
219 }
220 
221 void SidebarController::disposeDecks()
222 {
223     SolarMutexGuard aSolarMutexGuard;
224 
225     if (comphelper::LibreOfficeKit::isActive())
226     {
227         if (const SfxViewShell* pViewShell = mpViewFrame->GetViewShell())
228         {
229             const std::string hide = UnoNameFromDeckId(msCurrentDeckId, vcl::EnumContext::Application::Impress == vcl::EnumContext::GetApplicationEnum(GetCurrentContext().msApplication));
230             if (!hide.empty())
231                 pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_STATE_CHANGED,
232                                                        (hide + "=false").c_str());
233         }
234 
235         mpParentWindow->ReleaseLOKNotifier();
236     }
237 
238     mpCurrentDeck.clear();
239     maFocusManager.Clear();
240     mpResourceManager->disposeDecks();
241 }
242 
243 void SAL_CALL SidebarController::disposing()
244 {
245     SolarMutexGuard aSolarMutexGuard;
246 
247     mpCloseIndicator.disposeAndClear();
248 
249     maFocusManager.Clear();
250     mpTabBar.disposeAndClear();
251 
252     saveDeckState();
253 
254     // clear decks
255     ResourceManager::DeckContextDescriptorContainer aDecks;
256 
257     mpResourceManager->GetMatchingDecks (
258             aDecks,
259             GetCurrentContext(),
260             IsDocumentReadOnly(),
261             mxFrame->getController());
262 
263     for (const auto& rDeck : aDecks)
264     {
265         std::shared_ptr<DeckDescriptor> deckDesc = mpResourceManager->GetDeckDescriptor(rDeck.msId);
266 
267         VclPtr<Deck> aDeck = deckDesc->mpDeck;
268         if (aDeck)
269             aDeck.disposeAndClear();
270     }
271 
272     uno::Reference<css::frame::XController> xController = mxFrame->getController();
273     if (!xController.is())
274         xController = mxCurrentController;
275 
276     mxFrame->removeFrameActionListener(this);
277     unregisterSidebarForFrame(this, xController);
278 
279     if (mxReadOnlyModeDispatch.is())
280         mxReadOnlyModeDispatch->removeStatusListener(this, Tools::GetURL(gsReadOnlyCommandName));
281     if (mpSplitWindow != nullptr)
282     {
283         mpSplitWindow->RemoveEventListener(LINK(this, SidebarController, WindowEventHandler));
284         mpSplitWindow = nullptr;
285     }
286 
287     if (mpParentWindow != nullptr)
288     {
289         mpParentWindow->RemoveEventListener(LINK(this, SidebarController, WindowEventHandler));
290         mpParentWindow = nullptr;
291     }
292 
293     Theme::GetPropertySet()->removePropertyChangeListener(
294         "",
295         static_cast<css::beans::XPropertyChangeListener*>(this));
296 
297     maContextChangeUpdate.CancelRequest();
298 }
299 
300 void SAL_CALL SidebarController::notifyContextChangeEvent (const css::ui::ContextChangeEventObject& rEvent)
301 {
302     SolarMutexGuard aSolarMutexGuard;
303 
304     // Update to the requested new context asynchronously to avoid
305     // subtle errors caused by SFX2 which in rare cases can not
306     // properly handle a synchronous update.
307 
308     maRequestedContext = Context(
309         rEvent.ApplicationName,
310         rEvent.ContextName);
311 
312     if (maRequestedContext != maCurrentContext)
313     {
314         mxCurrentController.set(rEvent.Source, css::uno::UNO_QUERY);
315         maContextChangeUpdate.RequestCall(); // async call, not a prob
316                                              // calling with held
317                                              // solarmutex
318         // TODO: this call is redundant but mandatory for unit test to update context on document loading
319         UpdateConfigurations();
320     }
321 }
322 
323 void SAL_CALL SidebarController::disposing (const css::lang::EventObject& )
324 {
325     SolarMutexGuard aSolarMutexGuard;
326 
327     dispose();
328 }
329 
330 void SAL_CALL SidebarController::propertyChange (const css::beans::PropertyChangeEvent& )
331 {
332     SolarMutexGuard aSolarMutexGuard;
333 
334     maPropertyChangeForwarder.RequestCall(); // async call, not a prob
335                                              // to call with held
336                                              // solarmutex
337 }
338 
339 void SAL_CALL SidebarController::statusChanged (const css::frame::FeatureStateEvent& rEvent)
340 {
341     SolarMutexGuard aSolarMutexGuard;
342 
343     bool bIsReadWrite (true);
344     if (rEvent.IsEnabled)
345         rEvent.State >>= bIsReadWrite;
346 
347     if (mbIsDocumentReadOnly != !bIsReadWrite)
348     {
349         mbIsDocumentReadOnly = !bIsReadWrite;
350 
351         // Force the current deck to update its panel list.
352         if ( ! mbIsDocumentReadOnly)
353             SwitchToDefaultDeck();
354 
355         mnRequestedForceFlags |= SwitchFlag_ForceSwitch;
356         maContextChangeUpdate.RequestCall(); // async call, ok to call
357                                              // with held solarmutex
358     }
359 }
360 
361 void SAL_CALL SidebarController::requestLayout()
362 {
363     SolarMutexGuard aSolarMutexGuard;
364 
365     sal_Int32 nMinimalWidth = 0;
366     if (mpCurrentDeck && !mpCurrentDeck->isDisposed())
367     {
368         mpCurrentDeck->RequestLayout();
369         nMinimalWidth = mpCurrentDeck->GetMinimalWidth();
370     }
371     RestrictWidth(nMinimalWidth);
372 }
373 
374 void SidebarController::BroadcastPropertyChange()
375 {
376     mpParentWindow->Invalidate(InvalidateFlags::Children);
377 }
378 
379 void SidebarController::NotifyResize()
380 {
381     if (!mpTabBar)
382     {
383         OSL_ASSERT(mpTabBar!=nullptr);
384         return;
385     }
386 
387     vcl::Window* pParentWindow = mpTabBar->GetParent();
388     const sal_Int32 nTabBarDefaultWidth = TabBar::GetDefaultWidth() * mpTabBar->GetDPIScaleFactor();
389 
390     const sal_Int32 nWidth (pParentWindow->GetSizePixel().Width());
391     const sal_Int32 nHeight (pParentWindow->GetSizePixel().Height());
392 
393     mbIsDeckOpen = (nWidth > nTabBarDefaultWidth);
394 
395     if (mnSavedSidebarWidth <= 0)
396         mnSavedSidebarWidth = nWidth;
397 
398     bool bIsDeckVisible;
399     const bool bIsOpening (nWidth > mnWidthOnSplitterButtonDown);
400     if (bIsOpening)
401         bIsDeckVisible = nWidth >= nTabBarDefaultWidth + gnWidthOpenThreshold;
402     else
403         bIsDeckVisible = nWidth >= nTabBarDefaultWidth + gnWidthCloseThreshold;
404     mbIsDeckRequestedOpen = bIsDeckVisible;
405     UpdateCloseIndicator(!bIsDeckVisible);
406 
407     if (mpCurrentDeck && !mpCurrentDeck->isDisposed())
408     {
409         SfxSplitWindow* pSplitWindow = GetSplitWindow();
410         WindowAlign eAlign = pSplitWindow ? pSplitWindow->GetAlign() : WindowAlign::Right;
411         tools::Long nDeckX, nTabX;
412         if (eAlign == WindowAlign::Left)     // attach the Sidebar towards the left-side of screen
413         {
414             nDeckX = nTabBarDefaultWidth;
415             nTabX = 0;
416         }
417         else   // attach the Sidebar towards the right-side of screen
418         {
419             nDeckX = 0;
420             nTabX = nWidth - nTabBarDefaultWidth;
421         }
422 
423         // Place the deck first.
424         if (bIsDeckVisible)
425         {
426             if (comphelper::LibreOfficeKit::isActive())
427             {
428                 // We want to let the layouter use up as much of the
429                 // height as necessary to make sure no scrollbar is
430                 // visible. This only works when there are no greedy
431                 // panes that fill up all available area. So we only
432                 // use this for the PropertyDeck, which has no such
433                 // panes, while most other do. This is fine, since
434                 // it's the PropertyDeck that really has many panes
435                 // that can collapse or expand. For others, limit
436                 // the height to something sensible.
437                 // tdf#130348: Add special case for ChartDeck, too.
438                 const sal_Int32 nExtHeight = (msCurrentDeckId == "PropertyDeck" ? 2000 :
439                                               (msCurrentDeckId == "ChartDeck" ? 1200 : 600));
440                 // No TabBar in LOK (use nWidth in full).
441                 mpCurrentDeck->setPosSizePixel(nDeckX, 0, nWidth, nExtHeight);
442             }
443             else
444                 mpCurrentDeck->setPosSizePixel(nDeckX, 0, nWidth - nTabBarDefaultWidth, nHeight);
445             mpCurrentDeck->Show();
446             mpCurrentDeck->RequestLayout();
447         }
448         else
449             mpCurrentDeck->Hide();
450 
451         // Now place the tab bar.
452         mpTabBar->setPosSizePixel(nTabX, 0, nTabBarDefaultWidth, nHeight);
453         if (!comphelper::LibreOfficeKit::isActive())
454             mpTabBar->Show(); // Don't show TabBar in LOK.
455     }
456 
457     // Determine if the closer of the deck can be shown.
458     sal_Int32 nMinimalWidth = 0;
459     if (mpCurrentDeck && !mpCurrentDeck->isDisposed())
460     {
461         VclPtr<DeckTitleBar> pTitleBar = mpCurrentDeck->GetTitleBar();
462         if (pTitleBar && pTitleBar->IsVisible())
463             pTitleBar->SetCloserVisible(CanModifyChildWindowWidth());
464         nMinimalWidth = mpCurrentDeck->GetMinimalWidth();
465     }
466 
467     RestrictWidth(nMinimalWidth);
468 
469     mpParentWindow->NotifyResize();
470 }
471 
472 void SidebarController::ProcessNewWidth (const sal_Int32 nNewWidth)
473 {
474     if ( ! mbIsDeckRequestedOpen)
475         return;
476 
477     if (*mbIsDeckRequestedOpen)
478     {
479         // Deck became large enough to be shown.  Show it.
480         mnSavedSidebarWidth = nNewWidth;
481         if (!*mbIsDeckOpen)
482             RequestOpenDeck();
483     }
484     else
485     {
486         // Deck became too small.  Close it completely.
487         // If window is wider than the tab bar then mark the deck as being visible, even when it is not.
488         // This is to trigger an adjustment of the width to the width of the tab bar.
489         mbIsDeckOpen = true;
490         RequestCloseDeck();
491 
492         if (mnWidthOnSplitterButtonDown > TabBar::GetDefaultWidth() * mpTabBar->GetDPIScaleFactor())
493             mnSavedSidebarWidth = mnWidthOnSplitterButtonDown;
494     }
495 }
496 
497 void SidebarController::SyncUpdate()
498 {
499     maPropertyChangeForwarder.Sync();
500     maContextChangeUpdate.Sync();
501 }
502 
503 void SidebarController::UpdateConfigurations()
504 {
505     if (maCurrentContext == maRequestedContext
506         && mnRequestedForceFlags == SwitchFlag_NoForce)
507         return;
508 
509     if ((maCurrentContext.msApplication != "none") &&
510             !maCurrentContext.msApplication.isEmpty())
511     {
512         mpResourceManager->SaveDecksSettings(maCurrentContext);
513         mpResourceManager->SetLastActiveDeck(maCurrentContext, msCurrentDeckId);
514     }
515 
516     // get last active deck for this application on first update
517     if (!maRequestedContext.msApplication.isEmpty() &&
518             (maCurrentContext.msApplication != maRequestedContext.msApplication))
519     {
520         OUString sLastActiveDeck = mpResourceManager->GetLastActiveDeck( maRequestedContext );
521         if (!sLastActiveDeck.isEmpty())
522             msCurrentDeckId = sLastActiveDeck;
523     }
524 
525     maCurrentContext = maRequestedContext;
526 
527     mpResourceManager->InitDeckContext(GetCurrentContext());
528 
529     // Find the set of decks that could be displayed for the new context.
530     ResourceManager::DeckContextDescriptorContainer aDecks;
531 
532     css::uno::Reference<css::frame::XController> xController = mxCurrentController.is() ? mxCurrentController : mxFrame->getController();
533 
534     mpResourceManager->GetMatchingDecks (
535         aDecks,
536         maCurrentContext,
537         mbIsDocumentReadOnly,
538         xController);
539 
540     maFocusManager.Clear();
541 
542     // Notify the tab bar about the updated set of decks.
543     mpTabBar->SetDecks(aDecks);
544 
545     // Find the new deck.  By default that is the same as the old
546     // one.  If that is not set or not enabled, then choose the
547     // first enabled deck (which is PropertyDeck).
548     OUString sNewDeckId;
549     for (const auto& rDeck : aDecks)
550     {
551         if (rDeck.mbIsEnabled)
552         {
553             if (rDeck.msId == msCurrentDeckId)
554             {
555                 sNewDeckId = msCurrentDeckId;
556                 break;
557             }
558             else if (sNewDeckId.getLength() == 0)
559                 sNewDeckId = rDeck.msId;
560         }
561     }
562 
563     if (sNewDeckId.getLength() == 0)
564     {
565         // We did not find a valid deck.
566         RequestCloseDeck();
567         return;
568     }
569 
570     // Tell the tab bar to highlight the button associated
571     // with the deck.
572     mpTabBar->HighlightDeck(sNewDeckId);
573 
574     std::shared_ptr<DeckDescriptor> xDescriptor = mpResourceManager->GetDeckDescriptor(sNewDeckId);
575 
576     if (xDescriptor)
577     {
578         SwitchToDeck(*xDescriptor, maCurrentContext);
579     }
580 }
581 
582 namespace {
583 
584 void collectUIInformation(const OUString& rDeckId)
585 {
586     EventDescription aDescription;
587     aDescription.aAction = "SIDEBAR";
588     aDescription.aParent = "MainWindow";
589     aDescription.aParameters = {{"PANEL", rDeckId}};
590     aDescription.aKeyWord = "CurrentApp";
591 
592     UITestLogger::getInstance().logEvent(aDescription);
593 }
594 
595 }
596 
597 void SidebarController::OpenThenToggleDeck (
598     const OUString& rsDeckId)
599 {
600     SfxSplitWindow* pSplitWindow = GetSplitWindow();
601     if ( pSplitWindow && !pSplitWindow->IsFadeIn() )
602         // tdf#83546 Collapsed sidebar should expand first
603         pSplitWindow->FadeIn();
604     else if ( IsDeckVisible( rsDeckId ) )
605     {
606         if( !WasFloatingDeckClosed() )
607         {
608             // tdf#88241 Summoning an undocked sidebar a second time should close sidebar
609             mpParentWindow->Close();
610             return;
611         }
612         else
613         {
614             // tdf#67627 Clicking a second time on a Deck icon will close the Deck
615             RequestCloseDeck();
616             return;
617         }
618     }
619     RequestOpenDeck();
620     // before SwitchToDeck which may cause the rsDeckId string to be released
621     collectUIInformation(rsDeckId);
622     SwitchToDeck(rsDeckId);
623 
624     // Make sure the sidebar is wide enough to fit the requested content
625     if (mpCurrentDeck && mpTabBar)
626     {
627         sal_Int32 nRequestedWidth = (mpCurrentDeck->GetMinimalWidth() + TabBar::GetDefaultWidth())
628                                     * mpTabBar->GetDPIScaleFactor();
629         if (mnSavedSidebarWidth < nRequestedWidth)
630             SetChildWindowWidth(nRequestedWidth);
631     }
632 }
633 
634 void SidebarController::OpenThenSwitchToDeck (
635     std::u16string_view rsDeckId)
636 {
637     RequestOpenDeck();
638     SwitchToDeck(rsDeckId);
639 
640 }
641 
642 void SidebarController::SwitchToDefaultDeck()
643 {
644     SwitchToDeck(gsDefaultDeckId);
645 }
646 
647 void SidebarController::SwitchToDeck (
648     std::u16string_view rsDeckId)
649 {
650     if (  msCurrentDeckId != rsDeckId
651         || ! mbIsDeckOpen
652         || mnRequestedForceFlags!=SwitchFlag_NoForce)
653     {
654         std::shared_ptr<DeckDescriptor> xDeckDescriptor = mpResourceManager->GetDeckDescriptor(rsDeckId);
655 
656         if (xDeckDescriptor)
657             SwitchToDeck(*xDeckDescriptor, maCurrentContext);
658     }
659 }
660 
661 void SidebarController::CreateDeck(std::u16string_view rDeckId) {
662     CreateDeck(rDeckId, maCurrentContext);
663 }
664 
665 void SidebarController::CreateDeck(std::u16string_view rDeckId, const Context& rContext, bool bForceCreate)
666 {
667     std::shared_ptr<DeckDescriptor> xDeckDescriptor = mpResourceManager->GetDeckDescriptor(rDeckId);
668 
669     if (!xDeckDescriptor)
670         return;
671 
672     VclPtr<Deck> aDeck = xDeckDescriptor->mpDeck;
673     if (!aDeck || bForceCreate)
674     {
675         if (aDeck)
676             aDeck.disposeAndClear();
677 
678         aDeck = VclPtr<Deck>::Create(
679                         *xDeckDescriptor,
680                         mpParentWindow,
681                         [this]() { return this->RequestCloseDeck(); });
682     }
683     xDeckDescriptor->mpDeck = aDeck;
684     CreatePanels(rDeckId, rContext);
685 }
686 
687 void SidebarController::CreatePanels(std::u16string_view rDeckId, const Context& rContext)
688 {
689     std::shared_ptr<DeckDescriptor> xDeckDescriptor = mpResourceManager->GetDeckDescriptor(rDeckId);
690 
691     // init panels bounded to that deck, do not wait them being displayed as may be accessed through API
692 
693     VclPtr<Deck> pDeck = xDeckDescriptor->mpDeck;
694 
695     ResourceManager::PanelContextDescriptorContainer aPanelContextDescriptors;
696 
697     css::uno::Reference<css::frame::XController> xController = mxCurrentController.is() ? mxCurrentController : mxFrame->getController();
698 
699     mpResourceManager->GetMatchingPanels(
700                                         aPanelContextDescriptors,
701                                         rContext,
702                                         rDeckId,
703                                         xController);
704 
705     // Update the panel list.
706     const sal_Int32 nNewPanelCount (aPanelContextDescriptors.size());
707     SharedPanelContainer aNewPanels;
708     sal_Int32 nWriteIndex (0);
709 
710     aNewPanels.resize(nNewPanelCount);
711 
712     for (sal_Int32 nReadIndex=0; nReadIndex<nNewPanelCount; ++nReadIndex)
713     {
714         const ResourceManager::PanelContextDescriptor& rPanelContexDescriptor (
715             aPanelContextDescriptors[nReadIndex]);
716 
717         // Determine if the panel can be displayed.
718         const bool bIsPanelVisible (!mbIsDocumentReadOnly || rPanelContexDescriptor.mbShowForReadOnlyDocuments);
719         if ( ! bIsPanelVisible)
720             continue;
721 
722         Panel *const pPanel(pDeck->GetPanel(rPanelContexDescriptor.msId));
723         if (pPanel != nullptr)
724         {
725             pPanel->SetLurkMode(false);
726             aNewPanels[nWriteIndex] = pPanel;
727             pPanel->SetExpanded( rPanelContexDescriptor.mbIsInitiallyVisible );
728             ++nWriteIndex;
729         }
730         else
731         {
732                 VclPtr<Panel>  aPanel = CreatePanel(
733                                             rPanelContexDescriptor.msId,
734                                             pDeck->GetPanelParentWindow(),
735                                             rPanelContexDescriptor.mbIsInitiallyVisible,
736                                             rContext,
737                                             pDeck);
738                 if (aPanel )
739                 {
740                     aNewPanels[nWriteIndex] = aPanel;
741 
742                     // Depending on the context we have to change the command
743                     // for the "more options" dialog.
744                     VclPtr<PanelTitleBar> pTitleBar = aNewPanels[nWriteIndex]->GetTitleBar();
745                     if (pTitleBar)
746                     {
747                         pTitleBar->SetMoreOptionsCommand(
748                             rPanelContexDescriptor.msMenuCommand,
749                             mxFrame, xController);
750                     }
751                     ++nWriteIndex;
752                 }
753         }
754     }
755 
756     // mpCurrentPanels - may miss stuff (?)
757     aNewPanels.resize(nWriteIndex);
758     pDeck->ResetPanels(aNewPanels);
759 }
760 
761 void SidebarController::SwitchToDeck (
762     const DeckDescriptor& rDeckDescriptor,
763     const Context& rContext)
764 {
765     if (comphelper::LibreOfficeKit::isActive())
766     {
767         if (const SfxViewShell* pViewShell = mpViewFrame->GetViewShell())
768         {
769             if (msCurrentDeckId != rDeckDescriptor.msId)
770             {
771                 const std::string hide = UnoNameFromDeckId(msCurrentDeckId, vcl::EnumContext::Application::Impress == vcl::EnumContext::GetApplicationEnum(GetCurrentContext().msApplication));
772                 if (!hide.empty())
773                     pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_STATE_CHANGED,
774                                                            (hide + "=false").c_str());
775             }
776 
777             const std::string show = UnoNameFromDeckId(rDeckDescriptor.msId, vcl::EnumContext::Application::Impress == vcl::EnumContext::GetApplicationEnum(GetCurrentContext().msApplication));
778             if (!show.empty())
779                 pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_STATE_CHANGED,
780                                                        (show + "=true").c_str());
781         }
782     }
783 
784     maFocusManager.Clear();
785 
786     const bool bForceNewDeck ((mnRequestedForceFlags&SwitchFlag_ForceNewDeck)!=0);
787     const bool bForceNewPanels ((mnRequestedForceFlags&SwitchFlag_ForceNewPanels)!=0);
788     mnRequestedForceFlags = SwitchFlag_NoForce;
789 
790     if (   msCurrentDeckId != rDeckDescriptor.msId
791         || bForceNewDeck)
792     {
793         if (mpCurrentDeck)
794             mpCurrentDeck->Hide();
795 
796         msCurrentDeckId = rDeckDescriptor.msId;
797     }
798 
799     // invisible with LOK, so avoid invalidations
800     if (!comphelper::LibreOfficeKit::isActive())
801         mpTabBar->Invalidate();
802 
803     mpTabBar->HighlightDeck(msCurrentDeckId);
804 
805     // Determine the panels to display in the deck.
806     ResourceManager::PanelContextDescriptorContainer aPanelContextDescriptors;
807 
808     css::uno::Reference<css::frame::XController> xController = mxCurrentController.is() ? mxCurrentController : mxFrame->getController();
809 
810     mpResourceManager->GetMatchingPanels(
811         aPanelContextDescriptors,
812         rContext,
813         rDeckDescriptor.msId,
814         xController);
815 
816     if (aPanelContextDescriptors.empty())
817     {
818         // There are no panels to be displayed in the current context.
819         if (vcl::EnumContext::GetContextEnum(rContext.msContext) != vcl::EnumContext::Context::Empty)
820         {
821             // Switch to the "empty" context and try again.
822             SwitchToDeck(
823                 rDeckDescriptor,
824                 Context(
825                     rContext.msApplication,
826                     vcl::EnumContext::GetContextName(vcl::EnumContext::Context::Empty)));
827             return;
828         }
829         else
830         {
831             // This is already the "empty" context. Looks like we have
832             // to live with an empty deck.
833         }
834     }
835 
836     // Provide a configuration and Deck object.
837 
838     CreateDeck(rDeckDescriptor.msId, rContext, bForceNewDeck);
839 
840     if (bForceNewPanels && !bForceNewDeck) // already forced if bForceNewDeck
841         CreatePanels(rDeckDescriptor.msId, rContext);
842 
843     if (mpCurrentDeck && mpCurrentDeck != rDeckDescriptor.mpDeck)
844         mpCurrentDeck->Hide();
845     mpCurrentDeck.reset(rDeckDescriptor.mpDeck);
846 
847     if ( ! mpCurrentDeck)
848         return;
849 
850 #ifdef DEBUG
851     // Show the context name in the deck title bar.
852     VclPtr<DeckTitleBar> pDebugTitleBar = mpCurrentDeck->GetTitleBar();
853     if (pDebugTitleBar)
854         pDebugTitleBar->SetTitle(rDeckDescriptor.msTitle + " (" + maCurrentContext.msContext + ")");
855 #endif
856 
857     SfxSplitWindow* pSplitWindow = GetSplitWindow();
858     sal_Int32 nTabBarDefaultWidth = TabBar::GetDefaultWidth() * mpTabBar->GetDPIScaleFactor();
859     WindowAlign eAlign = pSplitWindow ? pSplitWindow->GetAlign() : WindowAlign::Right;
860     tools::Long nDeckX;
861     if (eAlign == WindowAlign::Left)     // attach the Sidebar towards the left-side of screen
862     {
863         nDeckX = nTabBarDefaultWidth;
864     }
865     else   // attach the Sidebar towards the right-side of screen
866     {
867         nDeckX = 0;
868     }
869 
870     // Activate the deck and the new set of panels.
871     mpCurrentDeck->setPosSizePixel(
872         nDeckX,
873         0,
874         mpParentWindow->GetSizePixel().Width() - nTabBarDefaultWidth,
875         mpParentWindow->GetSizePixel().Height());
876 
877     mpCurrentDeck->Show();
878 
879     mpParentWindow->SetText(rDeckDescriptor.msTitle);
880 
881     NotifyResize();
882 
883     // Tell the focus manager about the new panels and tab bar
884     // buttons.
885     maFocusManager.SetDeckTitle(mpCurrentDeck->GetTitleBar());
886     maFocusManager.SetPanels(mpCurrentDeck->GetPanels());
887 
888     mpTabBar->UpdateFocusManager(maFocusManager);
889     UpdateTitleBarIcons();
890 }
891 
892 void SidebarController::notifyDeckTitle(std::u16string_view targetDeckId)
893 {
894     if (msCurrentDeckId == targetDeckId)
895     {
896         maFocusManager.SetDeckTitle(mpCurrentDeck->GetTitleBar());
897         mpTabBar->UpdateFocusManager(maFocusManager);
898         UpdateTitleBarIcons();
899     }
900 }
901 
902 VclPtr<Panel> SidebarController::CreatePanel (
903     std::u16string_view rsPanelId,
904     vcl::Window* pParentWindow,
905     const bool bIsInitiallyExpanded,
906     const Context& rContext,
907     const VclPtr<Deck>& pDeck)
908 {
909     std::shared_ptr<PanelDescriptor> xPanelDescriptor = mpResourceManager->GetPanelDescriptor(rsPanelId);
910 
911     if (!xPanelDescriptor)
912         return nullptr;
913 
914     // Create the panel which is the parent window of the UIElement.
915     VclPtr<Panel> pPanel = VclPtr<Panel>::Create(
916         *xPanelDescriptor,
917         pParentWindow,
918         bIsInitiallyExpanded,
919         [pDeck]() { return pDeck->RequestLayout(); },
920         [this]() { return this->GetCurrentContext(); },
921         mxFrame);
922 
923     // Create the XUIElement.
924     Reference<ui::XUIElement> xUIElement (CreateUIElement(
925             pPanel->GetComponentInterface(),
926             xPanelDescriptor->msImplementationURL,
927             xPanelDescriptor->mbWantsCanvas,
928             rContext));
929     if (xUIElement.is())
930     {
931         // Initialize the panel and add it to the active deck.
932         pPanel->SetUIElement(xUIElement);
933     }
934     else
935     {
936         pPanel.disposeAndClear();
937     }
938 
939     return pPanel;
940 }
941 
942 Reference<ui::XUIElement> SidebarController::CreateUIElement (
943     const Reference<awt::XWindowPeer>& rxWindow,
944     const OUString& rsImplementationURL,
945     const bool bWantsCanvas,
946     const Context& rContext)
947 {
948     try
949     {
950         const Reference<XComponentContext> xComponentContext (::comphelper::getProcessComponentContext() );
951         const Reference<ui::XUIElementFactory> xUIElementFactory =
952                ui::theUIElementFactoryManager::get( xComponentContext );
953 
954        // Create the XUIElement.
955         ::comphelper::NamedValueCollection aCreationArguments;
956         aCreationArguments.put("Frame", makeAny(mxFrame));
957         aCreationArguments.put("ParentWindow", makeAny(rxWindow));
958         SidebarDockingWindow* pSfxDockingWindow = mpParentWindow.get();
959         if (pSfxDockingWindow != nullptr)
960             aCreationArguments.put("SfxBindings", makeAny(reinterpret_cast<sal_uInt64>(&pSfxDockingWindow->GetBindings())));
961         aCreationArguments.put("Theme", Theme::GetPropertySet());
962         aCreationArguments.put("Sidebar", makeAny(Reference<ui::XSidebar>(static_cast<ui::XSidebar*>(this))));
963         if (bWantsCanvas)
964         {
965             Reference<rendering::XSpriteCanvas> xCanvas (VCLUnoHelper::GetWindow(rxWindow)->GetSpriteCanvas());
966             aCreationArguments.put("Canvas", makeAny(xCanvas));
967         }
968 
969         if (mxCurrentController.is())
970         {
971             OUString aModule = Tools::GetModuleName(mxCurrentController);
972             if (!aModule.isEmpty())
973             {
974                 aCreationArguments.put("Module", makeAny(aModule));
975             }
976             aCreationArguments.put("Controller", makeAny(mxCurrentController));
977         }
978 
979         aCreationArguments.put("ApplicationName", makeAny(rContext.msApplication));
980         aCreationArguments.put("ContextName", makeAny(rContext.msContext));
981 
982         Reference<ui::XUIElement> xUIElement(
983             xUIElementFactory->createUIElement(
984                 rsImplementationURL,
985                 aCreationArguments.getPropertyValues()),
986             UNO_SET_THROW);
987 
988         return xUIElement;
989     }
990     catch(const Exception&)
991     {
992         TOOLS_WARN_EXCEPTION("sfx.sidebar", "Cannot create panel " << rsImplementationURL);
993         return nullptr;
994     }
995 }
996 
997 IMPL_LINK(SidebarController, WindowEventHandler, VclWindowEvent&, rEvent, void)
998 {
999     if (rEvent.GetWindow() == mpParentWindow)
1000     {
1001         switch (rEvent.GetId())
1002         {
1003             case VclEventId::WindowShow:
1004             case VclEventId::WindowResize:
1005                 NotifyResize();
1006                 break;
1007 
1008             case VclEventId::WindowDataChanged:
1009                 // Force an update of deck and tab bar to reflect
1010                 // changes in theme (high contrast mode).
1011                 Theme::HandleDataChange();
1012                 UpdateTitleBarIcons();
1013                 mpParentWindow->Invalidate();
1014                 mnRequestedForceFlags |= SwitchFlag_ForceNewDeck | SwitchFlag_ForceNewPanels;
1015                 maContextChangeUpdate.RequestCall();
1016                 break;
1017 
1018             case VclEventId::ObjectDying:
1019                 dispose();
1020                 break;
1021 
1022             case VclEventId::WindowPaint:
1023                 SAL_INFO("sfx.sidebar", "Paint");
1024                 break;
1025 
1026             default:
1027                 break;
1028         }
1029     }
1030     else if (rEvent.GetWindow()==mpSplitWindow && mpSplitWindow!=nullptr)
1031     {
1032         switch (rEvent.GetId())
1033         {
1034             case VclEventId::WindowMouseButtonDown:
1035                 mnWidthOnSplitterButtonDown = mpParentWindow->GetSizePixel().Width();
1036                 break;
1037 
1038             case VclEventId::WindowMouseButtonUp:
1039             {
1040                 ProcessNewWidth(mpParentWindow->GetSizePixel().Width());
1041                 mnWidthOnSplitterButtonDown = 0;
1042                 break;
1043             }
1044 
1045             case VclEventId::ObjectDying:
1046                 dispose();
1047                 break;
1048 
1049             default: break;
1050          }
1051     }
1052 }
1053 
1054 void SidebarController::ShowPopupMenu(
1055     weld::Menu& rMainMenu, weld::Menu& rSubMenu,
1056     const ::std::vector<TabBar::DeckMenuData>& rMenuData) const
1057 {
1058     PopulatePopupMenus(rMainMenu, rSubMenu, rMenuData);
1059     rMainMenu.connect_activate(LINK(const_cast<SidebarController*>(this), SidebarController, OnMenuItemSelected));
1060     rSubMenu.connect_activate(LINK(const_cast<SidebarController*>(this), SidebarController, OnSubMenuItemSelected));
1061 }
1062 
1063 void SidebarController::PopulatePopupMenus(weld::Menu& rMenu, weld::Menu& rCustomizationMenu,
1064                                            const std::vector<TabBar::DeckMenuData>& rMenuData) const
1065 {
1066     // Add one entry for every tool panel element to individually make
1067     // them visible or hide them.
1068     sal_Int32 nIndex (0);
1069     for (const auto& rItem : rMenuData)
1070     {
1071         OString sIdent("select" + OString::number(nIndex));
1072         rMenu.insert(nIndex, OUString::fromUtf8(sIdent), rItem.msDisplayName, nullptr, nullptr, TRISTATE_FALSE);
1073         rMenu.set_active(sIdent, rItem.mbIsCurrentDeck);
1074         rMenu.set_sensitive(sIdent, rItem.mbIsEnabled && rItem.mbIsActive);
1075 
1076         if (!comphelper::LibreOfficeKit::isActive())
1077         {
1078             if (rItem.mbIsCurrentDeck)
1079             {
1080                 // Don't allow the currently visible deck to be disabled.
1081                 OString sSubIdent("nocustomize" + OString::number(nIndex));
1082                 rCustomizationMenu.insert(nIndex, OUString::fromUtf8(sSubIdent), rItem.msDisplayName, nullptr, nullptr, TRISTATE_FALSE);
1083                 rCustomizationMenu.set_active(sSubIdent, true);
1084             }
1085             else
1086             {
1087                 OString sSubIdent("customize" + OString::number(nIndex));
1088                 rCustomizationMenu.insert(nIndex, OUString::fromUtf8(sSubIdent), rItem.msDisplayName, nullptr, nullptr, TRISTATE_TRUE);
1089                 rCustomizationMenu.set_active(sSubIdent, rItem.mbIsEnabled && rItem.mbIsActive);
1090             }
1091         }
1092 
1093         ++nIndex;
1094     }
1095 
1096     bool bHideLock = true;
1097     bool bHideUnLock = true;
1098     // LOK doesn't support docked/undocked; Sidebar is floating but rendered docked in browser.
1099     if (!comphelper::LibreOfficeKit::isActive())
1100     {
1101         // Add entry for docking or un-docking the tool panel.
1102         if (mpParentWindow->IsFloatingMode())
1103             bHideLock = false;
1104         else
1105             bHideUnLock = false;
1106     }
1107     rMenu.set_visible("locktaskpanel", !bHideLock);
1108     rMenu.set_visible("unlocktaskpanel", !bHideUnLock);
1109 
1110     // No Restore or Customize options for LoKit.
1111     rMenu.set_visible("customization", !comphelper::LibreOfficeKit::isActive());
1112 }
1113 
1114 IMPL_LINK(SidebarController, OnMenuItemSelected, const OString&, rCurItemId, void)
1115 {
1116     if (rCurItemId == "unlocktaskpanel")
1117     {
1118         mpParentWindow->SetFloatingMode(true);
1119         if (mpParentWindow->IsFloatingMode())
1120             mpParentWindow->ToTop(ToTopFlags::GrabFocusOnly);
1121     }
1122     else if (rCurItemId == "locktaskpanel")
1123     {
1124         mpParentWindow->SetFloatingMode(false);
1125     }
1126     else if (rCurItemId == "hidesidebar")
1127     {
1128         if (!comphelper::LibreOfficeKit::isActive())
1129         {
1130             const util::URL aURL(Tools::GetURL(".uno:Sidebar"));
1131             Reference<frame::XDispatch> xDispatch(Tools::GetDispatch(mxFrame, aURL));
1132             if (xDispatch.is())
1133                 xDispatch->dispatch(aURL, Sequence<beans::PropertyValue>());
1134         }
1135         else
1136         {
1137             // In LOK we don't really destroy the sidebar when "closing";
1138             // we simply hide it. This is because recreating it is problematic
1139             // See notes in SidebarDockingWindow::NotifyResize().
1140             RequestCloseDeck();
1141         }
1142     }
1143     else
1144     {
1145         try
1146         {
1147             OString sNumber;
1148             if (rCurItemId.startsWith("select", &sNumber))
1149             {
1150                 RequestOpenDeck();
1151                 SwitchToDeck(mpTabBar->GetDeckIdForIndex(sNumber.toInt32()));
1152             }
1153             mpParentWindow->GrabFocusToDocument();
1154         }
1155         catch (RuntimeException&)
1156         {
1157         }
1158     }
1159 }
1160 
1161 IMPL_LINK(SidebarController, OnSubMenuItemSelected, const OString&, rCurItemId, void)
1162 {
1163     if (rCurItemId == "restoredefault")
1164         mpTabBar->RestoreHideFlags();
1165     else
1166     {
1167         try
1168         {
1169             OString sNumber;
1170             if (rCurItemId.startsWith("customize", &sNumber))
1171             {
1172                 mpTabBar->ToggleHideFlag(sNumber.toInt32());
1173 
1174                 // Find the set of decks that could be displayed for the new context.
1175                 ResourceManager::DeckContextDescriptorContainer aDecks;
1176                 mpResourceManager->GetMatchingDecks (
1177                                             aDecks,
1178                                             GetCurrentContext(),
1179                                             IsDocumentReadOnly(),
1180                                             mxFrame->getController());
1181                 // Notify the tab bar about the updated set of decks.
1182                 maFocusManager.Clear();
1183                 mpTabBar->SetDecks(aDecks);
1184                 mpTabBar->HighlightDeck(mpCurrentDeck->GetId());
1185                 mpTabBar->UpdateFocusManager(maFocusManager);
1186             }
1187             mpParentWindow->GrabFocusToDocument();
1188         }
1189         catch (RuntimeException&)
1190         {
1191         }
1192     }
1193 }
1194 
1195 
1196 void SidebarController::RequestCloseDeck()
1197 {
1198     if (comphelper::LibreOfficeKit::isActive() && mpCurrentDeck)
1199     {
1200         const vcl::ILibreOfficeKitNotifier* pNotifier = mpCurrentDeck->GetLOKNotifier();
1201         auto pMobileNotifier = SfxViewShell::Current();
1202         const SfxViewShell* pViewShell = SfxViewShell::Current();
1203         if (pMobileNotifier && pViewShell && pViewShell->isLOKMobilePhone())
1204         {
1205             // Mobile phone.
1206             std::stringstream aStream;
1207             boost::property_tree::ptree aTree;
1208             aTree.put("id", mpParentWindow->get_id()); // TODO could be missing - sort out
1209             aTree.put("type", "dockingwindow");
1210             aTree.put("text", mpParentWindow->GetText());
1211             aTree.put("enabled", false);
1212             boost::property_tree::write_json(aStream, aTree);
1213             const std::string message = aStream.str();
1214             pMobileNotifier->libreOfficeKitViewCallback(LOK_CALLBACK_JSDIALOG, message.c_str());
1215         }
1216         else if (pNotifier)
1217             pNotifier->notifyWindow(mpCurrentDeck->GetLOKWindowId(), "close");
1218     }
1219 
1220     mbIsDeckRequestedOpen = false;
1221     UpdateDeckOpenState();
1222 
1223     if (!mpCurrentDeck)
1224         mpTabBar->RemoveDeckHighlight();
1225 }
1226 
1227 void SidebarController::RequestOpenDeck()
1228 {
1229     SfxSplitWindow* pSplitWindow = GetSplitWindow();
1230     if ( pSplitWindow && !pSplitWindow->IsFadeIn() )
1231         // tdf#83546 Collapsed sidebar should expand first
1232         pSplitWindow->FadeIn();
1233 
1234     mbIsDeckRequestedOpen = true;
1235     UpdateDeckOpenState();
1236 }
1237 
1238 bool SidebarController::IsDeckOpen(const sal_Int32 nIndex)
1239 {
1240     if (nIndex >= 0)
1241     {
1242         OUString asDeckId(mpTabBar->GetDeckIdForIndex(nIndex));
1243         return IsDeckVisible(asDeckId);
1244     }
1245     return mbIsDeckOpen && *mbIsDeckOpen;
1246 }
1247 
1248 bool SidebarController::IsDeckVisible(std::u16string_view rsDeckId)
1249 {
1250     return mbIsDeckOpen && *mbIsDeckOpen && msCurrentDeckId == rsDeckId;
1251 }
1252 
1253 void SidebarController::UpdateDeckOpenState()
1254 {
1255     if ( ! mbIsDeckRequestedOpen)
1256         // No state requested.
1257         return;
1258 
1259     const sal_Int32 nTabBarDefaultWidth = TabBar::GetDefaultWidth() * mpTabBar->GetDPIScaleFactor();
1260 
1261     // Update (change) the open state when it either has not yet been initialized
1262     // or when its value differs from the requested state.
1263     if ( mbIsDeckOpen && *mbIsDeckOpen == *mbIsDeckRequestedOpen )
1264         return;
1265 
1266     if (*mbIsDeckRequestedOpen)
1267     {
1268         if (!mpParentWindow->IsFloatingMode())
1269         {
1270             if (mnSavedSidebarWidth <= nTabBarDefaultWidth)
1271                 SetChildWindowWidth(SidebarChildWindow::GetDefaultWidth(mpParentWindow));
1272             else
1273                 SetChildWindowWidth(mnSavedSidebarWidth);
1274         }
1275         else
1276         {
1277             // Show the Deck by resizing back to the original size (before hiding).
1278             Size aNewSize(mpParentWindow->GetFloatingWindow()->GetSizePixel());
1279             Point aNewPos(mpParentWindow->GetFloatingWindow()->GetPosPixel());
1280 
1281             aNewPos.setX(aNewPos.X() - mnSavedSidebarWidth + nTabBarDefaultWidth);
1282             aNewSize.setWidth(mnSavedSidebarWidth);
1283 
1284             mpParentWindow->GetFloatingWindow()->SetPosSizePixel(aNewPos, aNewSize);
1285 
1286             if (comphelper::LibreOfficeKit::isActive())
1287             {
1288                 // Sidebar wide enough to render the menu; enable it.
1289                 mpTabBar->EnableMenuButton(true);
1290 
1291                 if (const SfxViewShell* pViewShell = mpViewFrame->GetViewShell())
1292                 {
1293                     const std::string uno = UnoNameFromDeckId(msCurrentDeckId, vcl::EnumContext::Application::Impress == vcl::EnumContext::GetApplicationEnum(GetCurrentContext().msApplication));
1294                     if (!uno.empty())
1295                         pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_STATE_CHANGED,
1296                                                                 (uno + "=true").c_str());
1297                 }
1298             }
1299         }
1300     }
1301     else
1302     {
1303         if ( ! mpParentWindow->IsFloatingMode())
1304             mnSavedSidebarWidth = SetChildWindowWidth(nTabBarDefaultWidth);
1305         else
1306         {
1307             // Hide the Deck by resizing to the width of the TabBar.
1308             Size aNewSize(mpParentWindow->GetFloatingWindow()->GetSizePixel());
1309             Point aNewPos(mpParentWindow->GetFloatingWindow()->GetPosPixel());
1310             mnSavedSidebarWidth = aNewSize.Width(); // Save the current width to restore.
1311 
1312             aNewPos.setX(aNewPos.X() + mnSavedSidebarWidth - nTabBarDefaultWidth);
1313             if (comphelper::LibreOfficeKit::isActive())
1314             {
1315                 // Hide by collapsing, otherwise with 0x0 the client might expect
1316                 // to get valid dimensions on rendering and not collapse the sidebar.
1317                 aNewSize.setWidth(1);
1318             }
1319             else
1320                 aNewSize.setWidth(nTabBarDefaultWidth);
1321 
1322             mpParentWindow->GetFloatingWindow()->SetPosSizePixel(aNewPos, aNewSize);
1323 
1324             if (comphelper::LibreOfficeKit::isActive())
1325             {
1326                 // Sidebar too narrow to render the menu; disable it.
1327                 mpTabBar->EnableMenuButton(false);
1328 
1329                 if (const SfxViewShell* pViewShell = mpViewFrame->GetViewShell())
1330                 {
1331                     const std::string uno = UnoNameFromDeckId(msCurrentDeckId, vcl::EnumContext::Application::Impress == vcl::EnumContext::GetApplicationEnum(GetCurrentContext().msApplication));
1332                     if (!uno.empty())
1333                         pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_STATE_CHANGED,
1334                                                                 (uno + "=false").c_str());
1335                 }
1336             }
1337         }
1338 
1339         if (mnWidthOnSplitterButtonDown > nTabBarDefaultWidth)
1340             mnSavedSidebarWidth = mnWidthOnSplitterButtonDown;
1341         mpParentWindow->SetStyle(mpParentWindow->GetStyle() & ~WB_SIZEABLE);
1342     }
1343 
1344     mbIsDeckOpen = *mbIsDeckRequestedOpen;
1345     if (*mbIsDeckOpen && mpCurrentDeck)
1346         mpCurrentDeck->Show(*mbIsDeckOpen);
1347     NotifyResize();
1348 }
1349 
1350 bool SidebarController::CanModifyChildWindowWidth()
1351 {
1352     SfxSplitWindow* pSplitWindow = GetSplitWindow();
1353     if (pSplitWindow == nullptr)
1354         return false;
1355 
1356     sal_uInt16 nRow (0xffff);
1357     sal_uInt16 nColumn (0xffff);
1358     if (pSplitWindow->GetWindowPos(mpParentWindow, nColumn, nRow))
1359     {
1360         sal_uInt16 nRowCount (pSplitWindow->GetWindowCount(nColumn));
1361         return nRowCount==1;
1362     }
1363     else
1364         return false;
1365 }
1366 
1367 sal_Int32 SidebarController::SetChildWindowWidth (const sal_Int32 nNewWidth)
1368 {
1369     SfxSplitWindow* pSplitWindow = GetSplitWindow();
1370     if (pSplitWindow == nullptr)
1371         return 0;
1372 
1373     sal_uInt16 nRow (0xffff);
1374     sal_uInt16 nColumn (0xffff);
1375     pSplitWindow->GetWindowPos(mpParentWindow, nColumn, nRow);
1376     const tools::Long nColumnWidth (pSplitWindow->GetLineSize(nColumn));
1377 
1378     vcl::Window* pWindow = mpParentWindow;
1379     const Size aWindowSize (pWindow->GetSizePixel());
1380 
1381     pSplitWindow->MoveWindow(
1382         mpParentWindow,
1383         Size(nNewWidth, aWindowSize.Height()),
1384         nColumn,
1385         nRow,
1386         false);
1387     static_cast<SplitWindow*>(pSplitWindow)->Split();
1388 
1389     return static_cast<sal_Int32>(nColumnWidth);
1390 }
1391 
1392 void SidebarController::RestrictWidth (sal_Int32 nWidth)
1393 {
1394     SfxSplitWindow* pSplitWindow = GetSplitWindow();
1395     if (pSplitWindow != nullptr)
1396     {
1397         const sal_uInt16 nId (pSplitWindow->GetItemId(mpParentWindow.get()));
1398         const sal_uInt16 nSetId (pSplitWindow->GetSet(nId));
1399         const sal_Int32 nRequestedWidth
1400             = (TabBar::GetDefaultWidth() + nWidth) * mpTabBar->GetDPIScaleFactor();
1401 
1402         pSplitWindow->SetItemSizeRange(
1403             nSetId,
1404             Range(nRequestedWidth,
1405                   getMaximumWidth() * mpTabBar->GetDPIScaleFactor()));
1406     }
1407 }
1408 
1409 SfxSplitWindow* SidebarController::GetSplitWindow()
1410 {
1411     if (mpParentWindow != nullptr)
1412     {
1413         SfxSplitWindow* pSplitWindow = dynamic_cast<SfxSplitWindow*>(mpParentWindow->GetParent());
1414         if (pSplitWindow != mpSplitWindow)
1415         {
1416             if (mpSplitWindow != nullptr)
1417                 mpSplitWindow->RemoveEventListener(LINK(this, SidebarController, WindowEventHandler));
1418 
1419             mpSplitWindow = pSplitWindow;
1420 
1421             if (mpSplitWindow != nullptr)
1422                 mpSplitWindow->AddEventListener(LINK(this, SidebarController, WindowEventHandler));
1423         }
1424         return mpSplitWindow;
1425     }
1426     else
1427         return nullptr;
1428 }
1429 
1430 void SidebarController::UpdateCloseIndicator (const bool bCloseAfterDrag)
1431 {
1432     if (mpParentWindow == nullptr)
1433         return;
1434 
1435     if (bCloseAfterDrag)
1436     {
1437         // Make sure that the indicator exists.
1438         if ( ! mpCloseIndicator)
1439         {
1440             mpCloseIndicator.reset(VclPtr<FixedImage>::Create(mpParentWindow));
1441             FixedImage* pFixedImage = static_cast<FixedImage*>(mpCloseIndicator.get());
1442             const Image aImage (Theme::GetImage(Theme::Image_CloseIndicator));
1443             pFixedImage->SetImage(aImage);
1444             pFixedImage->SetSizePixel(aImage.GetSizePixel());
1445             pFixedImage->SetBackground(Theme::GetColor(Theme::Color_DeckBackground));
1446         }
1447 
1448         // Place and show the indicator.
1449         const Size aWindowSize (mpParentWindow->GetSizePixel());
1450         const Size aImageSize (mpCloseIndicator->GetSizePixel());
1451         mpCloseIndicator->SetPosPixel(
1452             Point(
1453                 aWindowSize.Width() - TabBar::GetDefaultWidth() * mpTabBar->GetDPIScaleFactor() - aImageSize.Width(),
1454                 (aWindowSize.Height() - aImageSize.Height())/2));
1455         mpCloseIndicator->Show();
1456     }
1457     else
1458     {
1459         // Hide but don't delete the indicator.
1460         if (mpCloseIndicator)
1461             mpCloseIndicator->Hide();
1462     }
1463 }
1464 
1465 void SidebarController::UpdateTitleBarIcons()
1466 {
1467     if ( ! mpCurrentDeck)
1468         return;
1469 
1470     const bool bIsHighContrastModeActive (Theme::IsHighContrastMode());
1471 
1472     const ResourceManager& rResourceManager = *mpResourceManager;
1473 
1474     // Update the deck icon.
1475     std::shared_ptr<DeckDescriptor> xDeckDescriptor = rResourceManager.GetDeckDescriptor(mpCurrentDeck->GetId());
1476     if (xDeckDescriptor && mpCurrentDeck->GetTitleBar())
1477     {
1478         const OUString sIconURL(
1479             bIsHighContrastModeActive
1480                 ? xDeckDescriptor->msHighContrastTitleBarIconURL
1481                 : xDeckDescriptor->msTitleBarIconURL);
1482         mpCurrentDeck->GetTitleBar()->SetIcon(Tools::GetImage(sIconURL, mxFrame));
1483     }
1484 
1485     // Update the panel icons.
1486     const SharedPanelContainer& rPanels (mpCurrentDeck->GetPanels());
1487     for (const auto& rxPanel : rPanels)
1488     {
1489         if ( ! rxPanel)
1490             continue;
1491         if (!rxPanel->GetTitleBar())
1492             continue;
1493         std::shared_ptr<PanelDescriptor> xPanelDescriptor = rResourceManager.GetPanelDescriptor(rxPanel->GetId());
1494         if (!xPanelDescriptor)
1495             continue;
1496         const OUString sIconURL (
1497             bIsHighContrastModeActive
1498                ? xPanelDescriptor->msHighContrastTitleBarIconURL
1499                : xPanelDescriptor->msTitleBarIconURL);
1500         rxPanel->GetTitleBar()->SetIcon(Tools::GetImage(sIconURL, mxFrame));
1501     }
1502 }
1503 
1504 void SidebarController::ShowPanel (const Panel& rPanel)
1505 {
1506     if (mpCurrentDeck)
1507     {
1508         if (!IsDeckOpen())
1509             RequestOpenDeck();
1510         mpCurrentDeck->ShowPanel(rPanel);
1511     }
1512 }
1513 
1514 ResourceManager::DeckContextDescriptorContainer SidebarController::GetMatchingDecks()
1515 {
1516     ResourceManager::DeckContextDescriptorContainer aDecks;
1517     mpResourceManager->GetMatchingDecks (aDecks,
1518                                         GetCurrentContext(),
1519                                         IsDocumentReadOnly(),
1520                                         mxFrame->getController());
1521     return aDecks;
1522 }
1523 
1524 ResourceManager::PanelContextDescriptorContainer SidebarController::GetMatchingPanels(std::u16string_view rDeckId)
1525 {
1526     ResourceManager::PanelContextDescriptorContainer aPanels;
1527 
1528     mpResourceManager->GetMatchingPanels(aPanels,
1529                                         GetCurrentContext(),
1530                                         rDeckId,
1531                                         mxFrame->getController());
1532     return aPanels;
1533 }
1534 
1535 void SidebarController::updateModel(const css::uno::Reference<css::frame::XModel>& xModel)
1536 {
1537     mpResourceManager->UpdateModel(xModel);
1538 }
1539 
1540 void SidebarController::FadeOut()
1541 {
1542     if (mpSplitWindow)
1543         mpSplitWindow->FadeOut();
1544 }
1545 
1546 void SidebarController::FadeIn()
1547 {
1548     if (mpSplitWindow)
1549         mpSplitWindow->FadeIn();
1550 }
1551 
1552 tools::Rectangle SidebarController::GetDeckDragArea() const
1553 {
1554     tools::Rectangle aRect;
1555     if (mpCurrentDeck)
1556     {
1557         VclPtr<DeckTitleBar> pTitleBar(mpCurrentDeck->GetTitleBar());
1558 
1559         if (pTitleBar)
1560         {
1561             aRect = pTitleBar->GetDragArea();
1562         }
1563     }
1564     return aRect;
1565 }
1566 
1567 void SidebarController::frameAction(const css::frame::FrameActionEvent& rEvent)
1568 {
1569     if (rEvent.Frame == mxFrame)
1570     {
1571         if (rEvent.Action == css::frame::FrameAction_COMPONENT_DETACHING)
1572             unregisterSidebarForFrame(this, mxFrame->getController());
1573         else if (rEvent.Action == css::frame::FrameAction_COMPONENT_REATTACHED)
1574             registerSidebarForFrame(this, mxFrame->getController());
1575     }
1576 }
1577 
1578 void SidebarController::saveDeckState()
1579 {
1580     // Impress shutdown : context (frame) is disposed before sidebar disposing
1581     // calc writer : context (frame) is disposed after sidebar disposing
1582     // so need to test if GetCurrentContext is still valid regarding msApplication
1583     if (GetCurrentContext().msApplication != "none")
1584     {
1585         mpResourceManager->SaveDecksSettings(GetCurrentContext());
1586         mpResourceManager->SaveLastActiveDeck(GetCurrentContext(), msCurrentDeckId);
1587     }
1588 }
1589 
1590 bool SidebarController::hasChartContextCurrently() const
1591 {
1592     return GetCurrentContext().msApplication == "com.sun.star.chart2.ChartDocument";
1593 }
1594 
1595 sfx2::sidebar::SidebarController* SidebarController::GetSidebarControllerForView(SfxViewShell* pViewShell)
1596 {
1597     if (!pViewShell)
1598         return nullptr;
1599 
1600     Reference<css::frame::XController2> xController(pViewShell->GetController(), UNO_QUERY);
1601     if (!xController.is())
1602         return nullptr;
1603 
1604     // Make sure there is a model behind the controller, otherwise getSidebar() can crash.
1605     if (!xController->getModel().is())
1606         return nullptr;
1607 
1608     Reference<css::ui::XSidebarProvider> xSidebarProvider = xController->getSidebar();
1609     if (!xSidebarProvider.is())
1610         return nullptr;
1611 
1612     Reference<css::ui::XSidebar> xSidebar = xSidebarProvider->getSidebar();
1613     if (!xSidebar.is())
1614         return nullptr;
1615 
1616     return dynamic_cast<sfx2::sidebar::SidebarController*>(xSidebar.get());
1617 }
1618 
1619 } // end of namespace sfx2::sidebar
1620 
1621 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
1622