Coverage Report

Created: 2026-02-14 09:37

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/sfx2/source/sidebar/TabBar.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
20
#include <sfx2/sidebar/TabBar.hxx>
21
#include <sidebar/DeckDescriptor.hxx>
22
#include <sfx2/sidebar/Theme.hxx>
23
#include <sidebar/Tools.hxx>
24
#include <sfx2/sidebar/FocusManager.hxx>
25
#include <sfx2/sidebar/SidebarController.hxx>
26
27
#include <comphelper/lok.hxx>
28
#include <comphelper/processfactory.hxx>
29
#include <o3tl/safeint.hxx>
30
#include <utility>
31
#include <vcl/commandevent.hxx>
32
#include <vcl/commandinfoprovider.hxx>
33
#include <vcl/event.hxx>
34
#include <vcl/svapp.hxx>
35
#include <vcl/weld/Menu.hxx>
36
#include <svtools/acceleratorexecute.hxx>
37
#include <osl/diagnose.h>
38
39
#include "uiobject.hxx"
40
41
using namespace css;
42
using namespace css::uno;
43
44
static int gDefaultWidth;
45
46
namespace sfx2::sidebar {
47
48
TabBar::TabBar(vcl::Window* pParentWindow,
49
               const Reference<frame::XFrame>& rxFrame,
50
               std::function<void (const OUString&)> aDeckActivationFunctor,
51
               PopupMenuSignalConnectFunction aPopupMenuSignalConnectFunction,
52
               SidebarController& rParentSidebarController
53
              )
54
0
    : InterimItemWindow(pParentWindow, u"sfx/ui/tabbar.ui"_ustr, u"TabBar"_ustr)
55
0
    , mxFrame(rxFrame)
56
0
    , mxAuxBuilder(Application::CreateBuilder(m_xContainer.get(), u"sfx/ui/tabbarcontents.ui"_ustr))
57
0
    , mxTempToplevel(mxAuxBuilder->weld_box(u"toplevel"_ustr))
58
0
    , mxContents(mxAuxBuilder->weld_widget(u"TabBarContents"_ustr))
59
0
    , mxMeasureBox(mxAuxBuilder->weld_widget(u"measure"_ustr))
60
0
    , maDeckActivationFunctor(std::move(aDeckActivationFunctor))
61
0
    , mrParentSidebarController(rParentSidebarController)
62
0
{
63
0
    set_id(u"TabBar"_ustr); // for uitest
64
65
0
    InitControlBase(mxMenuButton.get());
66
67
0
    mxTempToplevel->move(mxContents.get(), m_xContainer.get());
68
69
    // For Gtk4 defer menu_button until after the contents have been
70
    // transferred to its final home (where the old parent is a GtkWindow to
71
    // support loading the accelerators in the menu for Gtk3)
72
0
    mxMenuButton = mxAuxBuilder->weld_menu_button(u"menubutton"_ustr);
73
0
    mxMainMenu = mxAuxBuilder->weld_menu(u"mainmenu"_ustr);
74
0
    mxSubMenu = mxAuxBuilder->weld_menu(u"submenu"_ustr);
75
0
    aPopupMenuSignalConnectFunction(*mxMainMenu, *mxSubMenu);
76
77
0
    UpdateMenus();
78
79
0
    gDefaultWidth = m_xContainer->get_preferred_size().Width();
80
81
    // we have this widget just so we can measure best width for static TabBar::GetDefaultWidth
82
0
    mxMeasureBox->hide();
83
84
0
    SetBackground(Wallpaper(Theme::GetColor(Theme::Color_TabBarBackground)));
85
86
#if OSL_DEBUG_LEVEL >= 2
87
    SetText(OUString("TabBar"));
88
#endif
89
0
}
Unexecuted instantiation: sfx2::sidebar::TabBar::TabBar(vcl::Window*, com::sun::star::uno::Reference<com::sun::star::frame::XFrame> const&, std::__1::function<void (rtl::OUString const&)>, std::__1::function<void (weld::Menu&, weld::Menu&)>, sfx2::sidebar::SidebarController&)
Unexecuted instantiation: sfx2::sidebar::TabBar::TabBar(vcl::Window*, com::sun::star::uno::Reference<com::sun::star::frame::XFrame> const&, std::__1::function<void (rtl::OUString const&)>, std::__1::function<void (weld::Menu&, weld::Menu&)>, sfx2::sidebar::SidebarController&)
90
91
TabBar::~TabBar()
92
0
{
93
0
    disposeOnce();
94
0
}
95
96
void TabBar::dispose()
97
0
{
98
0
    maItems.clear();
99
0
    mxMeasureBox.reset();
100
0
    mxSubMenu.reset();
101
0
    mxMainMenu.reset();
102
0
    mxMenuButton.reset();
103
0
    m_xContainer->move(mxContents.get(), mxTempToplevel.get());
104
0
    mxContents.reset();
105
0
    mxTempToplevel.reset();
106
0
    mxAuxBuilder.reset();
107
0
    InterimItemWindow::dispose();
108
0
}
109
110
sal_Int32 TabBar::GetDefaultWidth()
111
0
{
112
0
    if (!gDefaultWidth)
113
0
    {
114
0
        std::unique_ptr<weld::Builder> xBuilder(Application::CreateBuilder(nullptr, u"sfx/ui/tabbarcontents.ui"_ustr));
115
0
        std::unique_ptr<weld::Widget> xContainer(xBuilder->weld_widget(u"TabBarContents"_ustr));
116
0
        gDefaultWidth = xContainer->get_preferred_size().Width();
117
0
    }
118
0
    return gDefaultWidth;
119
0
}
120
121
void TabBar::SetDecks(const ResourceManager::DeckContextDescriptorContainer& rDecks)
122
0
{
123
    // invisible with LOK, so keep empty to avoid invalidations
124
0
    if (comphelper::LibreOfficeKit::isActive())
125
0
        return;
126
127
    // Remove the current buttons.
128
0
    maItems.clear();
129
0
    for (auto const& deck : rDecks)
130
0
    {
131
0
        std::shared_ptr<DeckDescriptor> xDescriptor = mrParentSidebarController.GetResourceManager()->GetDeckDescriptor(deck.msId);
132
0
        if (xDescriptor == nullptr)
133
0
        {
134
0
            OSL_ASSERT(xDescriptor!=nullptr);
135
0
            continue;
136
0
        }
137
138
0
        maItems.emplace_back(std::make_unique<Item>(*this));
139
0
        auto& xItem(maItems.back());
140
0
        xItem->msDeckId = xDescriptor->msId;
141
0
        CreateTabItem(*xItem->mxButton, *xDescriptor);
142
0
        xItem->mxButton->connect_clicked(LINK(xItem.get(), TabBar::Item, HandleClick));
143
0
        xItem->maDeckActivationFunctor = maDeckActivationFunctor;
144
0
        xItem->mbIsHidden = !xDescriptor->mbIsEnabled;
145
146
0
        xItem->mxButton->set_visible(deck.mbIsEnabled);
147
0
    }
148
149
0
    UpdateButtonIcons();
150
0
    UpdateMenus();
151
0
}
152
153
void TabBar::UpdateButtonIcons()
154
0
{
155
0
    for (auto const& item : maItems)
156
0
    {
157
0
        std::shared_ptr<DeckDescriptor> xDeckDescriptor = mrParentSidebarController.GetResourceManager()->GetDeckDescriptor(item->msDeckId);
158
0
        if (!xDeckDescriptor)
159
0
            continue;
160
0
        item->mxButton->set_item_image(u"toggle"_ustr, GetItemImage(*xDeckDescriptor));
161
0
    }
162
0
}
163
164
void TabBar::HighlightDeck(std::u16string_view rsDeckId)
165
0
{
166
0
    for (auto const& item : maItems)
167
0
        item->mxButton->set_item_active(u"toggle"_ustr, item->msDeckId == rsDeckId);
168
0
    UpdateMenus();
169
0
}
170
171
void TabBar::RemoveDeckHighlight()
172
0
{
173
0
    for (auto const& item : maItems)
174
0
        item->mxButton->set_item_active(u"toggle"_ustr, false);
175
0
    UpdateMenus();
176
0
}
177
178
void TabBar::DataChanged(const DataChangedEvent& rDataChangedEvent)
179
0
{
180
0
    SetBackground(Theme::GetColor(Theme::Color_TabBarBackground));
181
0
    UpdateButtonIcons();
182
0
    UpdateMenus();
183
184
0
    InterimItemWindow::DataChanged(rDataChangedEvent);
185
0
}
186
187
bool TabBar::EventNotify(NotifyEvent& rEvent)
188
0
{
189
0
    NotifyEventType nType = rEvent.GetType();
190
0
    if(NotifyEventType::KEYINPUT == nType)
191
0
    {
192
0
        return InterimItemWindow::EventNotify(rEvent);
193
0
    }
194
0
    else if(NotifyEventType::COMMAND == nType)
195
0
    {
196
0
        const CommandEvent& rCommandEvent = *rEvent.GetCommandEvent();
197
0
        if(rCommandEvent.GetCommand() == CommandEventId::Wheel)
198
0
        {
199
0
            const CommandWheelData* pData = rCommandEvent.GetWheelData();
200
0
            if(!pData->GetModifier() && (pData->GetMode() == CommandWheelMode::SCROLL))
201
0
            {
202
0
                auto pItem = std::find_if(maItems.begin(), maItems.end(),
203
0
                    [] (const auto& item) { return item->mxButton->get_item_active("toggle"); });
204
0
                if(pItem == maItems.end())
205
0
                    return true;
206
0
                if(pData->GetNotchDelta()<0)
207
0
                {
208
0
                    if(pItem+1 == maItems.end())
209
0
                        return true;
210
0
                    ++pItem;
211
0
                }
212
0
                else
213
0
                {
214
0
                    if(pItem == maItems.begin())
215
0
                        return true;
216
0
                    --pItem;
217
0
                }
218
0
                try
219
0
                {
220
0
                    (*pItem)->maDeckActivationFunctor((*pItem)->msDeckId);
221
0
                    GrabFocusToDocument();
222
0
                }
223
0
                catch(const css::uno::Exception&) {};
224
0
                return true;
225
0
            }
226
0
        }
227
0
    }
228
0
    return false;
229
0
}
230
231
void TabBar::CreateTabItem(weld::Toolbar& rItem, const DeckDescriptor& rDeckDescriptor)
232
0
{
233
0
    rItem.set_accessible_description(rDeckDescriptor.msHelpText);
234
0
    const OUString sCommand = ".uno:SidebarDeck." + rDeckDescriptor.msId;
235
0
    OUString sShortcut = vcl::CommandInfoProvider::GetCommandShortcut(sCommand, mxFrame);
236
0
    if (!sShortcut.isEmpty())
237
0
        sShortcut = u" (" + sShortcut + u")";
238
0
    rItem.set_item_accessible_name(u"toggle"_ustr, rDeckDescriptor.msTitle);
239
0
    rItem.set_item_tooltip_text(u"toggle"_ustr, rDeckDescriptor.msHelpText + sShortcut);
240
0
}
241
242
css::uno::Reference<css::graphic::XGraphic> TabBar::GetItemImage(const DeckDescriptor& rDeckDescriptor) const
243
0
{
244
0
    return Tools::GetImage(
245
0
        rDeckDescriptor.msIconURL,
246
0
        rDeckDescriptor.msHighContrastIconURL,
247
0
        mxFrame);
248
0
}
249
250
TabBar::Item::Item(TabBar& rTabBar)
251
0
    : mrTabBar(rTabBar)
252
0
    , mxBuilder(Application::CreateBuilder(rTabBar.GetContainer(), u"sfx/ui/tabbutton.ui"_ustr))
253
0
    , mxButton(mxBuilder->weld_toolbar(u"button"_ustr))
254
0
    , mbIsHidden(false)
255
0
{
256
0
}
257
258
TabBar::Item::~Item()
259
0
{
260
0
    mrTabBar.GetContainer()->move(mxButton.get(), nullptr);
261
0
}
262
263
IMPL_LINK_NOARG(TabBar::Item, HandleClick, const OUString&, void)
264
0
{
265
    // tdf#143146 copy the functor and arg before calling
266
    // GrabFocusToDocument which may destroy this object
267
0
    DeckActivationFunctor aDeckActivationFunctor = maDeckActivationFunctor;
268
0
    auto sDeckId = msDeckId;
269
270
0
    mrTabBar.GrabFocusToDocument();
271
0
    try
272
0
    {
273
0
        aDeckActivationFunctor(sDeckId);
274
0
    }
275
0
    catch(const css::uno::Exception&)
276
0
    {} // workaround for #i123198#
277
0
}
278
279
OUString const & TabBar::GetDeckIdForIndex (const sal_Int32 nIndex) const
280
0
{
281
0
    if (nIndex<0 || o3tl::make_unsigned(nIndex)>=maItems.size())
282
0
        throw RuntimeException();
283
0
    return maItems[nIndex]->msDeckId;
284
0
}
285
286
void TabBar::ToggleHideFlag (const sal_Int32 nIndex)
287
0
{
288
0
    if (nIndex<0 || o3tl::make_unsigned(nIndex) >= maItems.size())
289
0
        throw RuntimeException();
290
291
0
    maItems[nIndex]->mbIsHidden = ! maItems[nIndex]->mbIsHidden;
292
293
0
    std::shared_ptr<DeckDescriptor> xDeckDescriptor = mrParentSidebarController.GetResourceManager()->GetDeckDescriptor(maItems[nIndex]->msDeckId);
294
0
    if (xDeckDescriptor)
295
0
    {
296
0
        xDeckDescriptor->mbIsEnabled = ! maItems[nIndex]->mbIsHidden;
297
298
0
        Context aContext;
299
0
        aContext.msApplication = mrParentSidebarController.GetCurrentContext().msApplication;
300
        // leave aContext.msContext on default 'any' ... this func is used only for decks
301
        // and we don't have context-sensitive decks anyway
302
303
0
        xDeckDescriptor->maContextList.ToggleVisibilityForContext(
304
0
            aContext, xDeckDescriptor->mbIsEnabled );
305
0
    }
306
0
    UpdateMenus();
307
0
}
308
309
void TabBar::UpdateFocusManager(FocusManager& rFocusManager)
310
0
{
311
0
    std::vector<weld::Widget*> aButtons;
312
0
    aButtons.reserve(maItems.size()+1);
313
0
    aButtons.push_back(mxMenuButton.get());
314
0
    for (auto const& item : maItems)
315
0
    {
316
0
        aButtons.push_back(item->mxButton.get());
317
0
    }
318
0
    rFocusManager.SetButtons(aButtons);
319
0
}
320
321
void TabBar::UpdateMenus()
322
0
{
323
0
    if (Application::GetToolkitName() == u"gtk4"_ustr)
324
0
    {
325
0
        SAL_WARN("sfx", "Skipping update of sidebar menus to avoid crash due to gtk4 menu brokenness.");
326
0
        return;
327
0
    }
328
329
0
    for (int i = mxMainMenu->n_children() - 1; i >= 0; --i)
330
0
    {
331
0
        OUString sIdent = mxMainMenu->get_id(i);
332
0
        if (sIdent.startsWith("select"))
333
0
            mxMainMenu->remove(sIdent);
334
0
    }
335
0
    for (int i = mxSubMenu->n_children() - 1; i >= 0; --i)
336
0
    {
337
0
        OUString sIdent = mxSubMenu->get_id(i);
338
0
        if (sIdent.indexOf("customize") != -1)
339
0
            mxSubMenu->remove(sIdent);
340
0
    }
341
342
    // Add one entry for every tool panel element to individually make
343
    // them visible or hide them.
344
0
    sal_Int32 nIndex (0);
345
0
    for (auto const& rItem : maItems)
346
0
    {
347
0
        std::shared_ptr<DeckDescriptor> xDeckDescriptor
348
0
            = mrParentSidebarController.GetResourceManager()->GetDeckDescriptor(rItem->msDeckId);
349
350
0
        if (!xDeckDescriptor)
351
0
            continue;
352
353
0
        const OUString sDisplayName = xDeckDescriptor->msTitle;
354
0
        OUString sIdent("select" + OUString::number(nIndex));
355
0
        const bool bCurrentDeck = rItem->mxButton->get_item_active(u"toggle"_ustr);
356
0
        const bool bActive = !rItem->mbIsHidden;
357
0
        const bool bEnabled = rItem->mxButton->get_visible();
358
0
        mxMainMenu->insert(nIndex, sIdent, sDisplayName, nullptr, nullptr, nullptr, TRISTATE_FALSE);
359
0
        mxMainMenu->set_active(sIdent, bCurrentDeck);
360
0
        mxMainMenu->set_sensitive(sIdent, bEnabled && bActive);
361
362
0
        if (!comphelper::LibreOfficeKit::isActive())
363
0
        {
364
0
            if (bCurrentDeck)
365
0
            {
366
                // Don't allow the currently visible deck to be disabled.
367
0
                OUString sSubIdent("nocustomize" + OUString::number(nIndex));
368
0
                mxSubMenu->insert(nIndex, sSubIdent, sDisplayName, nullptr, nullptr, nullptr,
369
0
                                  TRISTATE_FALSE);
370
0
                mxSubMenu->set_active(sSubIdent, true);
371
0
            }
372
0
            else
373
0
            {
374
0
                OUString sSubIdent("customize" + OUString::number(nIndex));
375
0
                mxSubMenu->insert(nIndex, sSubIdent, sDisplayName, nullptr, nullptr, nullptr,
376
0
                                  TRISTATE_TRUE);
377
0
                mxSubMenu->set_active(sSubIdent, bEnabled && bActive);
378
0
            }
379
0
        }
380
381
0
        ++nIndex;
382
0
    }
383
384
0
    bool bHideLock = true;
385
0
    bool bHideUnLock = true;
386
    // LOK doesn't support docked/undocked; Sidebar is floating but rendered docked in browser.
387
0
    if (!comphelper::LibreOfficeKit::isActive())
388
0
    {
389
        // Add entry for docking or un-docking the tool panel.
390
0
        if (!mrParentSidebarController.IsDocked())
391
0
            bHideLock = false;
392
0
        else
393
0
            bHideUnLock = false;
394
0
    }
395
0
    mxMainMenu->set_visible(u"locktaskpanel"_ustr, !bHideLock);
396
0
    mxMainMenu->set_visible(u"unlocktaskpanel"_ustr, !bHideUnLock);
397
398
    // No Restore or Customize options for LoKit.
399
0
    mxMainMenu->set_visible(u"customization"_ustr, !comphelper::LibreOfficeKit::isActive());
400
0
}
401
402
void TabBar::EnableMenuButton(const bool bEnable)
403
0
{
404
0
    mxMenuButton->set_sensitive(bEnable);
405
0
}
406
407
FactoryFunction TabBar::GetUITestFactory() const
408
0
{
409
0
    return TabBarUIObject::create;
410
0
}
411
412
} // end of namespace sfx2::sidebar
413
414
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */