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