xref: /core/sfx2/source/sidebar/TabBar.cxx (revision aef615ac)
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 <sfx2/sidebar/TabItem.hxx>
22 #include <sfx2/sidebar/ControlFactory.hxx>
23 #include <sfx2/sidebar/DeckDescriptor.hxx>
24 #include <sfx2/sidebar/Paint.hxx>
25 #include <sfx2/sidebar/Theme.hxx>
26 #include <sfx2/sidebar/Tools.hxx>
27 #include <sfx2/sidebar/FocusManager.hxx>
28 #include <sfx2/sidebar/SidebarController.hxx>
29 #include <sfx2/strings.hrc>
30 
31 #include <sfx2/sfxresid.hxx>
32 
33 #include <comphelper/processfactory.hxx>
34 #include <vcl/commandevent.hxx>
35 #include <vcl/event.hxx>
36 #include <vcl/gradient.hxx>
37 #include <vcl/image.hxx>
38 #include <vcl/svapp.hxx>
39 #include <vcl/wrkwin.hxx>
40 #include <tools/svborder.hxx>
41 #include <svtools/acceleratorexecute.hxx>
42 
43 #include <com/sun/star/graphic/XGraphicProvider.hpp>
44 
45 #include <sfx2/app.hxx>
46 
47 using namespace css;
48 using namespace css::uno;
49 
50 namespace sfx2 { namespace sidebar {
51 
52 TabBar::TabBar(vcl::Window* pParentWindow,
53                const Reference<frame::XFrame>& rxFrame,
54                const std::function<void (const OUString&)>& rDeckActivationFunctor,
55                const PopupMenuProvider& rPopupMenuProvider,
56                SidebarController* rParentSidebarController
57               )
58     : Window(pParentWindow, WB_DIALOGCONTROL),
59       mxFrame(rxFrame),
60       mpMenuButton(ControlFactory::CreateMenuButton(this)),
61       maItems(),
62       maDeckActivationFunctor(rDeckActivationFunctor),
63       maPopupMenuProvider(rPopupMenuProvider),
64       pParentSidebarController(rParentSidebarController)
65 {
66 
67     SetBackground(Theme::GetPaint(Theme::Paint_TabBarBackground).GetWallpaper());
68 
69     mpMenuButton->SetModeImage(Theme::GetImage(Theme::Image_TabBarMenu));
70     mpMenuButton->SetClickHdl(LINK(this, TabBar, OnToolboxClicked));
71     mpMenuButton->SetQuickHelpText(SfxResId(SFX_STR_SIDEBAR_SETTINGS));
72     Layout();
73 
74 #ifdef DEBUG
75     SetText(OUString("TabBar"));
76 #endif
77 }
78 
79 TabBar::~TabBar()
80 {
81     disposeOnce();
82 }
83 
84 void TabBar::dispose()
85 {
86     for (auto & item : maItems)
87         item.mpButton.disposeAndClear();
88     maItems.clear();
89     mpMenuButton.disposeAndClear();
90     vcl::Window::dispose();
91 }
92 
93 void TabBar::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rUpdateArea)
94 {
95     Window::Paint(rRenderContext, rUpdateArea);
96 
97     const sal_Int32 nHorizontalPadding(Theme::GetInteger(Theme::Int_TabMenuSeparatorPadding));
98     rRenderContext.SetLineColor(Theme::GetColor(Theme::Color_TabMenuSeparator));
99     rRenderContext.DrawLine(Point(nHorizontalPadding, mnMenuSeparatorY),
100                             Point(GetSizePixel().Width() - nHorizontalPadding, mnMenuSeparatorY));
101 }
102 
103 sal_Int32 TabBar::GetDefaultWidth()
104 {
105     return Theme::GetInteger(Theme::Int_TabItemWidth)
106         + Theme::GetInteger(Theme::Int_TabBarLeftPadding)
107         + Theme::GetInteger(Theme::Int_TabBarRightPadding);
108 }
109 
110 void TabBar::SetDecks(const ResourceManager::DeckContextDescriptorContainer& rDecks)
111 {
112     // Remove the current buttons.
113     {
114         for (auto & item : maItems)
115         {
116             item.mpButton.disposeAndClear();
117         }
118         maItems.clear();
119     }
120     maItems.resize(rDecks.size());
121     sal_Int32 nIndex (0);
122     for (auto const& deck : rDecks)
123     {
124         std::shared_ptr<DeckDescriptor> xDescriptor = pParentSidebarController->GetResourceManager()->GetDeckDescriptor(deck.msId);
125         if (xDescriptor == nullptr)
126         {
127             OSL_ASSERT(xDescriptor!=nullptr);
128             continue;
129         }
130 
131         Item& rItem (maItems[nIndex++]);
132         rItem.msDeckId = xDescriptor->msId;
133         rItem.mpButton.disposeAndClear();
134         rItem.mpButton = CreateTabItem(*xDescriptor);
135         rItem.mpButton->SetClickHdl(LINK(&rItem, TabBar::Item, HandleClick));
136         rItem.maDeckActivationFunctor = maDeckActivationFunctor;
137         rItem.mbIsHidden = ! xDescriptor->mbIsEnabled;
138         rItem.mbIsHiddenByDefault = rItem.mbIsHidden; // the default is the state while creating
139 
140         rItem.mpButton->Enable(deck.mbIsEnabled);
141     }
142 
143     UpdateButtonIcons();
144     Layout();
145 }
146 
147 void TabBar::UpdateButtonIcons()
148 {
149     Image aImage = Theme::GetImage(Theme::Image_TabBarMenu);
150     mpMenuButton->SetModeImage(aImage);
151 
152     for (auto const& item : maItems)
153     {
154         std::shared_ptr<DeckDescriptor> xDeckDescriptor = pParentSidebarController->GetResourceManager()->GetDeckDescriptor(item.msDeckId);
155 
156         if (xDeckDescriptor)
157         {
158             aImage = GetItemImage(*xDeckDescriptor);
159             item.mpButton->SetModeImage(aImage);
160         }
161     }
162 
163     Invalidate();
164 }
165 
166 void TabBar::Layout()
167 {
168     const SvBorder aPadding (
169         Theme::GetInteger(Theme::Int_TabBarLeftPadding),
170         Theme::GetInteger(Theme::Int_TabBarTopPadding),
171         Theme::GetInteger(Theme::Int_TabBarRightPadding),
172         Theme::GetInteger(Theme::Int_TabBarBottomPadding));
173     sal_Int32 nX (aPadding.Top());
174     sal_Int32 nY (aPadding.Left());
175     const Size aTabItemSize (
176         Theme::GetInteger(Theme::Int_TabItemWidth) * GetDPIScaleFactor(),
177         Theme::GetInteger(Theme::Int_TabItemHeight) * GetDPIScaleFactor());
178 
179     // Place the menu button and the separator.
180     if (mpMenuButton != nullptr)
181     {
182         mpMenuButton->SetPosSizePixel(
183             Point(nX,nY),
184             aTabItemSize);
185         mpMenuButton->Show();
186         nY += mpMenuButton->GetSizePixel().Height() + 1 + Theme::GetInteger(Theme::Int_TabMenuPadding);
187         mnMenuSeparatorY = nY - Theme::GetInteger(Theme::Int_TabMenuPadding)/2 - 1;
188     }
189 
190     // Place the deck selection buttons.
191     for (auto const& item : maItems)
192     {
193         Button& rButton (*item.mpButton);
194         rButton.Show( ! item.mbIsHidden);
195 
196         if (item.mbIsHidden)
197             continue;
198 
199         // Place and size the icon.
200         rButton.SetPosSizePixel(
201             Point(nX,nY),
202             aTabItemSize);
203         rButton.Show();
204 
205         nY += rButton.GetSizePixel().Height() + 1 + aPadding.Bottom();
206     }
207     Invalidate();
208 }
209 
210 void TabBar::HighlightDeck (const OUString& rsDeckId)
211 {
212     for (auto const& item : maItems)
213     {
214         if (item.msDeckId == rsDeckId)
215             item.mpButton->Check();
216         else
217             item.mpButton->Check(false);
218     }
219 }
220 
221 void TabBar::RemoveDeckHighlight ()
222 {
223     for (auto const& item : maItems)
224     {
225         item.mpButton->Check(false);
226     }
227 }
228 
229 void TabBar::DataChanged (const DataChangedEvent& rDataChangedEvent)
230 {
231     SetBackground(Theme::GetPaint(Theme::Paint_TabBarBackground).GetWallpaper());
232     UpdateButtonIcons();
233 
234     Window::DataChanged(rDataChangedEvent);
235 }
236 
237 bool TabBar::EventNotify(NotifyEvent& rEvent)
238 {
239     MouseNotifyEvent nType = rEvent.GetType();
240     if(MouseNotifyEvent::KEYINPUT == nType)
241     {
242         const vcl::KeyCode& rKeyCode = rEvent.GetKeyEvent()->GetKeyCode();
243         if (!mpAccel)
244         {
245             mpAccel = svt::AcceleratorExecute::createAcceleratorHelper();
246             mpAccel->init(comphelper::getProcessComponentContext(), mxFrame);
247         }
248         const OUString aCommand(mpAccel->findCommand(svt::AcceleratorExecute::st_VCLKey2AWTKey(rKeyCode)));
249         if (".uno:Sidebar" == aCommand)
250             return vcl::Window::EventNotify(rEvent);
251         return true;
252     }
253     else if(MouseNotifyEvent::COMMAND == nType)
254     {
255         const CommandEvent& rCommandEvent = *rEvent.GetCommandEvent();
256         if(rCommandEvent.GetCommand() == CommandEventId::Wheel)
257         {
258             const CommandWheelData* pData = rCommandEvent.GetWheelData();
259             if(!pData->GetModifier() && (pData->GetMode() == CommandWheelMode::SCROLL))
260             {
261                 auto pItem = std::find_if(maItems.begin(), maItems.end(),
262                     [] (Item const& rItem) { return rItem.mpButton->IsChecked(); });
263                 if(pItem == maItems.end())
264                     return true;
265                 if(pData->GetNotchDelta()<0)
266                 {
267                     if(pItem+1 == maItems.end())
268                         return true;
269                     ++pItem;
270                 }
271                 else
272                 {
273                     if(pItem == maItems.begin())
274                         return true;
275                     --pItem;
276                 }
277                 try
278                 {
279                     pItem->maDeckActivationFunctor(pItem->msDeckId);
280                 }
281                 catch(const css::uno::Exception&) {};
282                 return true;
283             }
284         }
285     }
286     return false;
287 }
288 
289 VclPtr<RadioButton> TabBar::CreateTabItem(const DeckDescriptor& rDeckDescriptor)
290 {
291     VclPtr<RadioButton> pItem = ControlFactory::CreateTabItem(this);
292     pItem->SetAccessibleName(rDeckDescriptor.msTitle);
293     pItem->SetAccessibleDescription(rDeckDescriptor.msHelpText);
294     pItem->SetHelpText(rDeckDescriptor.msHelpText);
295     pItem->SetQuickHelpText(rDeckDescriptor.msHelpText);
296     return pItem;
297 }
298 
299 Image TabBar::GetItemImage(const DeckDescriptor& rDeckDescriptor) const
300 {
301     return Tools::GetImage(
302         rDeckDescriptor.msIconURL,
303         rDeckDescriptor.msHighContrastIconURL,
304         mxFrame);
305 }
306 
307 IMPL_LINK_NOARG(TabBar::Item, HandleClick, Button*, void)
308 {
309     vcl::Window* pFocusWin = Application::GetFocusWindow();
310     pFocusWin->GrabFocusToDocument();
311     try
312     {
313         maDeckActivationFunctor(msDeckId);
314     }
315     catch(const css::uno::Exception&)
316     {} // workaround for #i123198#
317 }
318 
319 OUString const & TabBar::GetDeckIdForIndex (const sal_Int32 nIndex) const
320 {
321     if (nIndex<0 || static_cast<size_t>(nIndex)>=maItems.size())
322         throw RuntimeException();
323     return maItems[nIndex].msDeckId;
324 }
325 
326 void TabBar::ToggleHideFlag (const sal_Int32 nIndex)
327 {
328     if (nIndex<0 || static_cast<size_t>(nIndex) >= maItems.size())
329         throw RuntimeException();
330 
331     maItems[nIndex].mbIsHidden = ! maItems[nIndex].mbIsHidden;
332 
333     std::shared_ptr<DeckDescriptor> xDeckDescriptor = pParentSidebarController->GetResourceManager()->GetDeckDescriptor(maItems[nIndex].msDeckId);
334     if (xDeckDescriptor)
335     {
336         xDeckDescriptor->mbIsEnabled = ! maItems[nIndex].mbIsHidden;
337 
338         Context aContext;
339         aContext.msApplication = pParentSidebarController->GetCurrentContext().msApplication;
340         // leave aContext.msContext on default 'any' ... this func is used only for decks
341         // and we don't have context-sensitive decks anyway
342 
343         xDeckDescriptor->maContextList.ToggleVisibilityForContext(
344             aContext, xDeckDescriptor->mbIsEnabled );
345     }
346 
347     Layout();
348 }
349 
350 void TabBar::RestoreHideFlags()
351 {
352     bool bNeedsLayout(false);
353     for (auto & item : maItems)
354     {
355         if (item.mbIsHidden != item.mbIsHiddenByDefault)
356         {
357             item.mbIsHidden = item.mbIsHiddenByDefault;
358             bNeedsLayout = true;
359 
360             std::shared_ptr<DeckDescriptor> xDeckDescriptor = pParentSidebarController->GetResourceManager()->GetDeckDescriptor(item.msDeckId);
361             if (xDeckDescriptor)
362                 xDeckDescriptor->mbIsEnabled = ! item.mbIsHidden;
363 
364         }
365     }
366     if (bNeedsLayout)
367         Layout();
368 }
369 
370 void TabBar::UpdateFocusManager(FocusManager& rFocusManager)
371 {
372     std::vector<Button*> aButtons;
373     aButtons.reserve(maItems.size()+1);
374 
375     aButtons.push_back(mpMenuButton.get());
376     for (auto const& item : maItems)
377     {
378         aButtons.push_back(item.mpButton.get());
379     }
380     rFocusManager.SetButtons(aButtons);
381 }
382 
383 IMPL_LINK_NOARG(TabBar, OnToolboxClicked, Button*, void)
384 {
385     if (!mpMenuButton)
386         return;
387 
388     std::vector<DeckMenuData> aMenuData;
389 
390     for (auto const& item : maItems)
391     {
392         std::shared_ptr<DeckDescriptor> xDeckDescriptor = pParentSidebarController->GetResourceManager()->GetDeckDescriptor(item.msDeckId);
393 
394         if (xDeckDescriptor)
395         {
396             DeckMenuData aData;
397             aData.msDisplayName = xDeckDescriptor->msTitle;
398             aData.mbIsCurrentDeck = item.mpButton->IsChecked();
399             aData.mbIsActive = !item.mbIsHidden;
400             aData.mbIsEnabled = item.mpButton->IsEnabled();
401 
402             aMenuData.push_back(aData);
403         }
404     }
405 
406     maPopupMenuProvider(
407         tools::Rectangle(
408             mpMenuButton->GetPosPixel(),
409             mpMenuButton->GetSizePixel()),
410         aMenuData);
411     mpMenuButton->Check(false);
412 }
413 
414 void TabBar::EnableMenuButton(const bool bEnable)
415 {
416     mpMenuButton->Enable(bEnable);
417 }
418 
419 } } // end of namespace sfx2::sidebar
420 
421 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
422