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