Coverage Report

Created: 2026-06-30 11:14

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