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 <ViewTabBar.hxx>
21 
22 #include <ViewShellBase.hxx>
23 #include <framework/FrameworkHelper.hxx>
24 #include <framework/Pane.hxx>
25 #include <DrawController.hxx>
26 
27 #include <Client.hxx>
28 #include <vcl/svapp.hxx>
29 #include <vcl/tabpage.hxx>
30 #include <vcl/settings.hxx>
31 
32 #include <sfx2/viewfrm.hxx>
33 #include <com/sun/star/drawing/framework/ResourceId.hpp>
34 #include <com/sun/star/drawing/framework/XControllerManager.hpp>
35 #include <com/sun/star/lang/DisposedException.hpp>
36 #include <com/sun/star/drawing/framework/XView.hpp>
37 #include <comphelper/processfactory.hxx>
38 #include <comphelper/servicehelper.hxx>
39 #include <tools/diagnose_ex.h>
40 
41 using namespace ::com::sun::star;
42 using namespace ::com::sun::star::uno;
43 using namespace ::com::sun::star::drawing::framework;
44 using ::sd::framework::FrameworkHelper;
45 
46 namespace sd {
47 
48 namespace {
IsEqual(const TabBarButton & rButton1,const TabBarButton & rButton2)49 bool IsEqual (const TabBarButton& rButton1, const TabBarButton& rButton2)
50 {
51     return ((rButton1.ResourceId.is()
52                 && rButton2.ResourceId.is()
53                 && rButton1.ResourceId->compareTo(rButton2.ResourceId) == 0)
54         || rButton1.ButtonLabel == rButton2.ButtonLabel);
55 }
56 
57 class TabBarControl : public ::TabControl
58 {
59 public:
60     TabBarControl (vcl::Window* pParentWindow, const ::rtl::Reference<ViewTabBar>& rpViewTabBar);
61     virtual void Paint (vcl::RenderContext& rRenderContext, const ::tools::Rectangle& rRect) override;
62     virtual void ActivatePage() override;
63 private:
64     ::rtl::Reference<ViewTabBar> mpViewTabBar;
65 };
66 
67 } // end of anonymous namespace
68 
ViewTabBar(const Reference<XResourceId> & rxViewTabBarId,const Reference<frame::XController> & rxController)69 ViewTabBar::ViewTabBar (
70     const Reference<XResourceId>& rxViewTabBarId,
71     const Reference<frame::XController>& rxController)
72     : ViewTabBarInterfaceBase(maMutex),
73       mpTabControl(VclPtr<TabBarControl>::Create(GetAnchorWindow(rxViewTabBarId,rxController), this)),
74       mxController(rxController),
75       maTabBarButtons(),
76       mpTabPage(nullptr),
77       mxViewTabBarId(rxViewTabBarId),
78       mpViewShellBase(nullptr)
79 {
80     // Set one new tab page for all tab entries.  We need it only to
81     // determine the height of the tab bar.
82     mpTabPage.reset(VclPtr<TabPage>::Create(mpTabControl.get()));
83     mpTabPage->Hide();
84 
85     // add some space before the tabitems
86     mpTabControl->SetItemsOffset(Point(5, 3));
87 
88     // Tunnel through the controller and use the ViewShellBase to obtain the
89     // view frame.
90     try
91     {
92         Reference<lang::XUnoTunnel> xTunnel (mxController, UNO_QUERY_THROW);
93         DrawController* pController = reinterpret_cast<DrawController*>(
94             xTunnel->getSomething(DrawController::getUnoTunnelId()));
95         mpViewShellBase = pController->GetViewShellBase();
96     }
97     catch (const RuntimeException&)
98     {
99     }
100 
101     // Register as listener at XConfigurationController.
102     Reference<XControllerManager> xControllerManager (mxController, UNO_QUERY);
103     if (xControllerManager.is())
104     {
105         mxConfigurationController = xControllerManager->getConfigurationController();
106         if (mxConfigurationController.is())
107         {
108             mxConfigurationController->addConfigurationChangeListener(
109                 this,
110                     FrameworkHelper::msResourceActivationEvent,
111                 Any());
112         }
113     }
114 
115     mpTabControl->Show();
116 
117     if (mpViewShellBase != nullptr
118         && rxViewTabBarId->isBoundToURL(
119             FrameworkHelper::msCenterPaneURL, AnchorBindingMode_DIRECT))
120     {
121         mpViewShellBase->SetViewTabBar(this);
122     }
123 }
124 
~ViewTabBar()125 ViewTabBar::~ViewTabBar()
126 {
127 }
128 
disposing()129 void ViewTabBar::disposing()
130 {
131     if (mpViewShellBase != nullptr
132         && mxViewTabBarId->isBoundToURL(
133             FrameworkHelper::msCenterPaneURL, AnchorBindingMode_DIRECT))
134     {
135         mpViewShellBase->SetViewTabBar(nullptr);
136     }
137 
138     if (mxConfigurationController.is())
139     {
140         // Unregister listener from XConfigurationController.
141         try
142         {
143             mxConfigurationController->removeConfigurationChangeListener(this);
144         }
145         catch (const lang::DisposedException&)
146         {
147             // Receiving a disposed exception is the normal case.  Is there
148             // a way to avoid it?
149         }
150         mxConfigurationController = nullptr;
151     }
152 
153     {
154         const SolarMutexGuard aSolarGuard;
155         // Set all references to the one tab page to NULL and delete the page.
156         for (sal_uInt16 nIndex=0; nIndex<mpTabControl->GetPageCount(); ++nIndex)
157             mpTabControl->SetTabPage(nIndex, nullptr);
158         mpTabPage.disposeAndClear();
159         mpTabControl.disposeAndClear();
160     }
161 
162     mxController = nullptr;
163 }
164 
GetAnchorWindow(const Reference<XResourceId> & rxViewTabBarId,const Reference<frame::XController> & rxController)165 vcl::Window* ViewTabBar::GetAnchorWindow(
166     const Reference<XResourceId>& rxViewTabBarId,
167     const Reference<frame::XController>& rxController)
168 {
169     vcl::Window* pWindow = nullptr;
170     ViewShellBase* pBase = nullptr;
171 
172     // Tunnel through the controller and use the ViewShellBase to obtain the
173     // view frame.
174     try
175     {
176         Reference<lang::XUnoTunnel> xTunnel (rxController, UNO_QUERY_THROW);
177         DrawController* pController = reinterpret_cast<DrawController*>(
178             xTunnel->getSomething(DrawController::getUnoTunnelId()));
179         pBase = pController->GetViewShellBase();
180     }
181     catch (const RuntimeException&)
182     {
183     }
184 
185     // The ViewTabBar supports at the moment only the center pane.
186     if (rxViewTabBarId.is()
187         && rxViewTabBarId->isBoundToURL(
188             FrameworkHelper::msCenterPaneURL, AnchorBindingMode_DIRECT))
189     {
190         if (pBase != nullptr && pBase->GetViewFrame() != nullptr)
191             pWindow = &pBase->GetViewFrame()->GetWindow();
192     }
193 
194     // The rest is (at the moment) just for the emergency case.
195     if (pWindow == nullptr)
196     {
197         Reference<XPane> xPane;
198         try
199         {
200             Reference<XControllerManager> xControllerManager (rxController, UNO_QUERY_THROW);
201             Reference<XConfigurationController> xCC (
202                 xControllerManager->getConfigurationController());
203             if (xCC.is())
204                 xPane.set(xCC->getResource(rxViewTabBarId->getAnchor()), UNO_QUERY);
205         }
206         catch (const RuntimeException&)
207         {
208         }
209 
210         // Tunnel through the XWindow to the VCL side.
211         try
212         {
213             Reference<lang::XUnoTunnel> xTunnel (xPane, UNO_QUERY_THROW);
214             framework::Pane* pPane = reinterpret_cast<framework::Pane*>(
215                 xTunnel->getSomething(framework::Pane::getUnoTunnelId()));
216             if (pPane != nullptr)
217                 pWindow = pPane->GetWindow()->GetParent();
218         }
219         catch (const RuntimeException&)
220         {
221         }
222     }
223 
224     return pWindow;
225 }
226 
227 //----- XConfigurationChangeListener ------------------------------------------
228 
notifyConfigurationChange(const ConfigurationChangeEvent & rEvent)229 void SAL_CALL  ViewTabBar::notifyConfigurationChange (
230     const ConfigurationChangeEvent& rEvent)
231 {
232     if (rEvent.Type == FrameworkHelper::msResourceActivationEvent
233         && rEvent.ResourceId->getResourceURL().match(FrameworkHelper::msViewURLPrefix)
234         && rEvent.ResourceId->isBoundTo(mxViewTabBarId->getAnchor(), AnchorBindingMode_DIRECT))
235     {
236         UpdateActiveButton();
237     }
238 }
239 
240 //----- XEventListener --------------------------------------------------------
241 
disposing(const lang::EventObject & rEvent)242 void SAL_CALL ViewTabBar::disposing(
243     const lang::EventObject& rEvent)
244 {
245     if (rEvent.Source == mxConfigurationController)
246     {
247         mxConfigurationController = nullptr;
248         mxController = nullptr;
249     }
250 }
251 
252 //----- XTabBar ---------------------------------------------------------------
253 
addTabBarButtonAfter(const TabBarButton & rButton,const TabBarButton & rAnchor)254 void SAL_CALL ViewTabBar::addTabBarButtonAfter (
255     const TabBarButton& rButton,
256     const TabBarButton& rAnchor)
257 {
258     const SolarMutexGuard aSolarGuard;
259     AddTabBarButton(rButton, rAnchor);
260 }
261 
appendTabBarButton(const TabBarButton & rButton)262 void SAL_CALL ViewTabBar::appendTabBarButton (const TabBarButton& rButton)
263 {
264     const SolarMutexGuard aSolarGuard;
265     AddTabBarButton(rButton);
266 }
267 
removeTabBarButton(const TabBarButton & rButton)268 void SAL_CALL ViewTabBar::removeTabBarButton (const TabBarButton& rButton)
269 {
270     const SolarMutexGuard aSolarGuard;
271     RemoveTabBarButton(rButton);
272 }
273 
hasTabBarButton(const TabBarButton & rButton)274 sal_Bool SAL_CALL ViewTabBar::hasTabBarButton (const TabBarButton& rButton)
275 {
276     const SolarMutexGuard aSolarGuard;
277     return HasTabBarButton(rButton);
278 }
279 
getTabBarButtons()280 Sequence<TabBarButton> SAL_CALL ViewTabBar::getTabBarButtons()
281 {
282     const SolarMutexGuard aSolarGuard;
283     return GetTabBarButtons();
284 }
285 
286 //----- XResource -------------------------------------------------------------
287 
getResourceId()288 Reference<XResourceId> SAL_CALL ViewTabBar::getResourceId()
289 {
290     return mxViewTabBarId;
291 }
292 
isAnchorOnly()293 sal_Bool SAL_CALL ViewTabBar::isAnchorOnly()
294 {
295     return false;
296 }
297 
298 //----- XUnoTunnel ------------------------------------------------------------
299 
300 namespace
301 {
302     class theViewTabBarUnoTunnelId : public rtl::Static< UnoTunnelIdInit, theViewTabBarUnoTunnelId > {};
303 }
304 
getUnoTunnelId()305 const Sequence<sal_Int8>& ViewTabBar::getUnoTunnelId()
306 {
307     return theViewTabBarUnoTunnelId::get().getSeq();
308 }
309 
getSomething(const Sequence<sal_Int8> & rId)310 sal_Int64 SAL_CALL ViewTabBar::getSomething (const Sequence<sal_Int8>& rId)
311 {
312     sal_Int64 nResult = 0;
313 
314     if (isUnoTunnelId<ViewTabBar>(rId))
315     {
316         nResult = reinterpret_cast<sal_Int64>(this);
317     }
318 
319     return nResult;
320 }
321 
ActivatePage()322 bool ViewTabBar::ActivatePage()
323 {
324     try
325     {
326         Reference<XControllerManager> xControllerManager (mxController,UNO_QUERY_THROW);
327         Reference<XConfigurationController> xConfigurationController (
328             xControllerManager->getConfigurationController());
329         if ( ! xConfigurationController.is())
330             throw RuntimeException();
331         Reference<XView> xView;
332         try
333         {
334             xView.set(xConfigurationController->getResource(
335                           ResourceId::create(
336                               ::comphelper::getProcessComponentContext(),
337                               FrameworkHelper::msCenterPaneURL)),
338                       UNO_QUERY);
339         }
340         catch (const DeploymentException&)
341         {
342         }
343 
344         Client* pIPClient = nullptr;
345         if (mpViewShellBase != nullptr)
346             pIPClient = dynamic_cast<Client*>(mpViewShellBase->GetIPClient());
347         if (pIPClient==nullptr || ! pIPClient->IsObjectInPlaceActive())
348         {
349             sal_uInt16 nIndex (mpTabControl->GetCurPageId() - 1);
350             if (nIndex < maTabBarButtons.size())
351             {
352                 xConfigurationController->requestResourceActivation(
353                     maTabBarButtons[nIndex].ResourceId,
354                     ResourceActivationMode_REPLACE);
355             }
356 
357             return true;
358         }
359         else
360         {
361             // When we run into this else branch then we have an active OLE
362             // object.  We ignore the request to switch views.  Additionally
363             // we put the active tab back to the one for the current view.
364             UpdateActiveButton();
365         }
366     }
367     catch (const RuntimeException&)
368     {
369         DBG_UNHANDLED_EXCEPTION("sd.view");
370     }
371 
372     return false;
373 }
374 
GetHeight() const375 int ViewTabBar::GetHeight() const
376 {
377     int nHeight (0);
378 
379     if (!maTabBarButtons.empty())
380     {
381         TabPage* pActivePage (mpTabControl->GetTabPage(
382             mpTabControl->GetCurPageId()));
383         if (pActivePage!=nullptr && mpTabControl->IsReallyVisible())
384             nHeight = pActivePage->GetPosPixel().Y();
385 
386         if (nHeight <= 0)
387             // Using a default when the real height can not be determined.
388             // To get correct height this method should be called when the
389             // control is visible.
390             nHeight = 21;
391     }
392 
393     return nHeight;
394 }
395 
AddTabBarButton(const css::drawing::framework::TabBarButton & rButton,const css::drawing::framework::TabBarButton & rAnchor)396 void ViewTabBar::AddTabBarButton (
397     const css::drawing::framework::TabBarButton& rButton,
398     const css::drawing::framework::TabBarButton& rAnchor)
399 {
400     TabBarButtonList::size_type nIndex;
401 
402     if ( ! rAnchor.ResourceId.is()
403         || (rAnchor.ResourceId->getResourceURL().isEmpty()
404             && rAnchor.ButtonLabel.isEmpty()))
405     {
406         nIndex = 0;
407     }
408     else
409     {
410         for (nIndex=0; nIndex<maTabBarButtons.size(); ++nIndex)
411         {
412             if (IsEqual(maTabBarButtons[nIndex], rAnchor))
413             {
414                 ++nIndex;
415                 break;
416             }
417         }
418     }
419 
420     AddTabBarButton(rButton,nIndex);
421 }
422 
AddTabBarButton(const css::drawing::framework::TabBarButton & rButton)423 void ViewTabBar::AddTabBarButton (
424     const css::drawing::framework::TabBarButton& rButton)
425 {
426     AddTabBarButton(rButton, maTabBarButtons.size());
427 }
428 
AddTabBarButton(const css::drawing::framework::TabBarButton & rButton,sal_Int32 nPosition)429 void ViewTabBar::AddTabBarButton (
430     const css::drawing::framework::TabBarButton& rButton,
431     sal_Int32 nPosition)
432 {
433     if (nPosition>=0
434         && nPosition<=mpTabControl->GetPageCount())
435     {
436         sal_uInt16 nIndex (static_cast<sal_uInt16>(nPosition));
437 
438         // Insert the button into our local array.
439         maTabBarButtons.insert(maTabBarButtons.begin()+nIndex, rButton);
440         UpdateTabBarButtons();
441         UpdateActiveButton();
442     }
443 }
444 
RemoveTabBarButton(const css::drawing::framework::TabBarButton & rButton)445 void ViewTabBar::RemoveTabBarButton (
446     const css::drawing::framework::TabBarButton& rButton)
447 {
448     for (TabBarButtonList::size_type nIndex=0; nIndex<maTabBarButtons.size(); ++nIndex)
449     {
450         if (IsEqual(maTabBarButtons[nIndex], rButton))
451         {
452             maTabBarButtons.erase(maTabBarButtons.begin()+nIndex);
453             UpdateTabBarButtons();
454             UpdateActiveButton();
455             break;
456         }
457     }
458 }
459 
HasTabBarButton(const css::drawing::framework::TabBarButton & rButton)460 bool ViewTabBar::HasTabBarButton (
461     const css::drawing::framework::TabBarButton& rButton)
462 {
463     bool bResult (false);
464 
465     for (const css::drawing::framework::TabBarButton & r : maTabBarButtons)
466     {
467         if (IsEqual(r, rButton))
468         {
469             bResult = true;
470             break;
471         }
472     }
473 
474     return bResult;
475 }
476 
477 css::uno::Sequence<css::drawing::framework::TabBarButton>
GetTabBarButtons()478     ViewTabBar::GetTabBarButtons()
479 {
480     sal_uInt32 nCount (maTabBarButtons.size());
481     css::uno::Sequence<css::drawing::framework::TabBarButton>
482           aList (nCount);
483 
484     for (sal_uInt32 nIndex=0; nIndex<nCount; ++nIndex)
485         aList[nIndex] = maTabBarButtons[nIndex];
486 
487     return aList;
488 }
489 
UpdateActiveButton()490 void ViewTabBar::UpdateActiveButton()
491 {
492     Reference<XView> xView;
493     if (mpViewShellBase != nullptr)
494         xView = FrameworkHelper::Instance(*mpViewShellBase)->GetView(
495             mxViewTabBarId->getAnchor());
496     if (!xView.is())
497         return;
498 
499     Reference<XResourceId> xViewId (xView->getResourceId());
500     for (size_t nIndex=0; nIndex<maTabBarButtons.size(); ++nIndex)
501     {
502         if (maTabBarButtons[nIndex].ResourceId->compareTo(xViewId) == 0)
503         {
504             mpTabControl->SetCurPageId(nIndex+1);
505             mpTabControl->::TabControl::ActivatePage();
506             break;
507         }
508     }
509 }
510 
UpdateTabBarButtons()511 void ViewTabBar::UpdateTabBarButtons()
512 {
513     sal_uInt16 nPageCount (mpTabControl->GetPageCount());
514     sal_uInt16 nIndex = 1;
515     for (const auto& rTab : maTabBarButtons)
516     {
517         // Create a new tab when there are not enough.
518         if (nPageCount < nIndex)
519             mpTabControl->InsertPage(nIndex, rTab.ButtonLabel);
520 
521         // Update the tab.
522         mpTabControl->SetPageText(nIndex, rTab.ButtonLabel);
523         mpTabControl->SetHelpText(nIndex, rTab.HelpText);
524         mpTabControl->SetTabPage(nIndex, mpTabPage.get());
525 
526         ++nIndex;
527     }
528 
529     // Delete tabs that are no longer used.
530     for (; nIndex<=nPageCount; ++nIndex)
531         mpTabControl->RemovePage(nIndex);
532 
533     mpTabPage->Hide();
534 }
535 
536 //===== TabBarControl =========================================================
537 
TabBarControl(vcl::Window * pParentWindow,const::rtl::Reference<ViewTabBar> & rpViewTabBar)538 TabBarControl::TabBarControl (
539     vcl::Window* pParentWindow,
540     const ::rtl::Reference<ViewTabBar>& rpViewTabBar)
541     : ::TabControl(pParentWindow),
542       mpViewTabBar(rpViewTabBar)
543 {
544 }
545 
Paint(vcl::RenderContext & rRenderContext,const::tools::Rectangle & rRect)546 void TabBarControl::Paint (vcl::RenderContext& rRenderContext, const ::tools::Rectangle& rRect)
547 {
548     Color aOriginalFillColor(rRenderContext.GetFillColor());
549     Color aOriginalLineColor(rRenderContext.GetLineColor());
550 
551     // Because the actual window background is transparent--to avoid
552     // flickering due to multiple background paintings by this and by child
553     // windows--we have to paint the background for this control explicitly:
554     // the actual control is not painted over its whole bounding box.
555     rRenderContext.SetFillColor(rRenderContext.GetSettings().GetStyleSettings().GetDialogColor());
556     rRenderContext.SetLineColor();
557     rRenderContext.DrawRect(rRect);
558 
559     ::TabControl::Paint(rRenderContext, rRect);
560 
561     rRenderContext.SetFillColor(aOriginalFillColor);
562     rRenderContext.SetLineColor(aOriginalLineColor);
563 }
564 
ActivatePage()565 void TabBarControl::ActivatePage()
566 {
567     if (mpViewTabBar->ActivatePage())
568     {
569         // Call the parent so that the correct tab is highlighted.
570         this->::TabControl::ActivatePage();
571     }
572 }
573 
574 } // end of namespace sd
575 
576 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
577