1 /**************************************************************
2  *
3  * Licensed to the Apache Software Foundation (ASF) under one
4  * or more contributor license agreements.  See the NOTICE file
5  * distributed with this work for additional information
6  * regarding copyright ownership.  The ASF licenses this file
7  * to you under the Apache License, Version 2.0 (the
8  * "License"); you may not use this file except in compliance
9  * with the License.  You may obtain a copy of the License at
10  *
11  *   http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing,
14  * software distributed under the License is distributed on an
15  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16  * KIND, either express or implied.  See the License for the
17  * specific language governing permissions and limitations
18  * under the License.
19  *
20  *************************************************************/
21 
22 #include "precompiled_sfx2.hxx"
23 
24 #include "SidebarController.hxx"
25 #include "Deck.hxx"
26 #include "DeckConfiguration.hxx"
27 #include "Panel.hxx"
28 #include "SidebarPanel.hxx"
29 #include "SidebarResource.hxx"
30 #include "TitleBar.hxx"
31 #include "TabBar.hxx"
32 #include "sfx2/sidebar/Theme.hxx"
33 
34 #include "sfxresid.hxx"
35 #include "sfx2/sfxsids.hrc"
36 #include "sfx2/dockwin.hxx"
37 #include "sfxlocal.hrc"
38 #include <vcl/floatwin.hxx>
39 #include <vcl/dockwin.hxx>
40 #include <svl/smplhint.hxx>
41 #include <tools/link.hxx>
42 #include <comphelper/componentfactory.hxx>
43 #include <comphelper/processfactory.hxx>
44 #include <comphelper/componentcontext.hxx>
45 #include <comphelper/namedvaluecollection.hxx>
46 
47 #include <com/sun/star/ui/ContextChangeEventMultiplexer.hpp>
48 #include <com/sun/star/ui/ContextChangeEventObject.hpp>
49 #include <com/sun/star/ui/XUIElementFactory.hpp>
50 #include <com/sun/star/lang/XInitialization.hpp>
51 
52 #include <boost/bind.hpp>
53 #include <boost/foreach.hpp>
54 
55 
56 using namespace css;
57 using namespace cssu;
58 using ::rtl::OUString;
59 
60 #define A2S(pString) (::rtl::OUString(RTL_CONSTASCII_USTRINGPARAM(pString)))
61 
62 namespace sfx2 { namespace sidebar {
63 
64 namespace {
65     enum MenuId
66     {
67         MID_UNLOCK_TASK_PANEL = 1,
68         MID_LOCK_TASK_PANEL,
69         MID_CUSTOMIZATION,
70         MID_RESTORE_DEFAULT,
71         MID_FIRST_PANEL,
72         MID_FIRST_HIDE = 1000
73     };
74 }
75 
76 
77 SidebarController::SidebarController (
78     DockingWindow* pParentWindow,
79     const cssu::Reference<css::frame::XFrame>& rxFrame)
80     : SidebarControllerInterfaceBase(m_aMutex),
81       mpCurrentConfiguration(),
82       mpParentWindow(pParentWindow),
83       mpTabBar(new TabBar(
84               mpParentWindow,
85               rxFrame,
86               ::boost::bind(&SidebarController::SwitchToDeck, this, _1),
87               ::boost::bind(&SidebarController::ShowPopupMenu, this, _1,_2,_3))),
88       mxFrame(rxFrame),
89       maCurrentContext(
90           EnumContext::Application_Unknown,
91           EnumContext::Context_Unknown),
92       msCurrentDeckId(A2S("PropertyDeck")),
93       maPropertyChangeForwarder(::boost::bind(&SidebarController::BroadcastPropertyChange, this))
94 {
95     if (pParentWindow == NULL)
96     {
97         OSL_ASSERT(pParentWindow!=NULL);
98             return;
99     }
100 
101     // Listen for context change events.
102     cssu::Reference<css::ui::XContextChangeEventMultiplexer> xMultiplexer (
103         css::ui::ContextChangeEventMultiplexer::get(
104             ::comphelper::getProcessComponentContext()));
105     if (xMultiplexer.is())
106         xMultiplexer->addContextChangeEventListener(
107             static_cast<css::ui::XContextChangeEventListener*>(this),
108             mxFrame->getController());
109 
110     // Listen for window events.
111     mpParentWindow->AddEventListener(LINK(this, SidebarController, WindowEventHandler));
112 
113     // Listen for theme property changes.
114     Theme::GetPropertySet()->addPropertyChangeListener(
115         A2S(""),
116         static_cast<css::beans::XPropertyChangeListener*>(this));
117 }
118 
119 
120 
121 
122 SidebarController::~SidebarController (void)
123 {
124 }
125 
126 
127 
128 
129 void SAL_CALL SidebarController::disposing (void)
130 {
131     cssu::Reference<css::ui::XContextChangeEventMultiplexer> xMultiplexer (
132         css::ui::ContextChangeEventMultiplexer::get(
133             ::comphelper::getProcessComponentContext()));
134     if (xMultiplexer.is())
135         xMultiplexer->removeAllContextChangeEventListeners(
136             static_cast<css::ui::XContextChangeEventListener*>(this));
137 
138     if (mpParentWindow != NULL)
139     {
140         mpParentWindow->RemoveEventListener(LINK(this, SidebarController, WindowEventHandler));
141         mpParentWindow = NULL;
142     }
143 
144     if (mpCurrentConfiguration)
145     {
146         mpCurrentConfiguration->Dispose();
147         mpCurrentConfiguration.reset();
148     }
149 
150     Theme::GetPropertySet()->removePropertyChangeListener(
151         A2S(""),
152         static_cast<css::beans::XPropertyChangeListener*>(this));
153 }
154 
155 
156 
157 
158 void SAL_CALL SidebarController::notifyContextChangeEvent (const css::ui::ContextChangeEventObject& rEvent)
159     throw(cssu::RuntimeException)
160 {
161     UpdateConfigurations(
162         EnumContext(
163             rEvent.ApplicationName,
164             rEvent.ContextName));
165 }
166 
167 
168 
169 
170 void SAL_CALL SidebarController::disposing (const css::lang::EventObject& rEventObject)
171     throw(cssu::RuntimeException)
172 {
173     (void)rEventObject;
174 
175     if (mpCurrentConfiguration)
176     {
177         mpCurrentConfiguration->Dispose();
178         mpCurrentConfiguration.reset();
179     }
180     if (mpTabBar != NULL)
181     {
182         mpTabBar->Hide();
183         delete mpTabBar;
184         mpTabBar = NULL;
185     }
186 }
187 
188 
189 
190 
191 void SAL_CALL SidebarController::propertyChange (const css::beans::PropertyChangeEvent& rEvent)
192     throw(cssu::RuntimeException)
193 {
194     (void)rEvent;
195 
196     maPropertyChangeForwarder.RequestCall();
197 }
198 
199 
200 
201 
202 void SidebarController::BroadcastPropertyChange (void)
203 {
204     DataChangedEvent aEvent (DATACHANGED_USER);
205     mpParentWindow->NotifyAllChilds(aEvent);
206     mpParentWindow->Invalidate(INVALIDATE_CHILDREN);
207 }
208 
209 
210 
211 
212 void SidebarController::NotifyResize (void)
213 {
214     if (mpTabBar == NULL)
215     {
216         OSL_ASSERT(mpTabBar!=NULL);
217         return;
218     }
219 
220     Window* pParentWindow = mpTabBar->GetParent();
221 
222     const sal_Int32 nWidth (pParentWindow->GetSizePixel().Width());
223     const sal_Int32 nHeight (pParentWindow->GetSizePixel().Height());
224 
225     if (mpCurrentConfiguration != NULL)
226     {
227         if (mpCurrentConfiguration->mpDeck==NULL)
228         {
229             OSL_ASSERT(mpCurrentConfiguration->mpDeck!=NULL);
230         }
231         else
232         {
233             mpCurrentConfiguration->mpDeck->SetPosSizePixel(0,0, nWidth-TabBar::GetDefaultWidth(), nHeight);
234             mpCurrentConfiguration->mpDeck->Show();
235             mpCurrentConfiguration->mpDeck->RequestLayout();
236         }
237     }
238 
239     mpTabBar->SetPosSizePixel(nWidth-TabBar::GetDefaultWidth(),0,TabBar::GetDefaultWidth(),nHeight);
240     mpTabBar->Show();
241 }
242 
243 
244 
245 
246 void SidebarController::UpdateConfigurations (const EnumContext& rContext)
247 {
248     if (maCurrentContext != rContext)
249     {
250         maCurrentContext = rContext;
251 
252         // Notify the tab bar about the updated set of decks.
253         ResourceManager::IdContainer aDeckIds;
254         ResourceManager::Instance().GetMatchingDecks (
255             aDeckIds,
256             rContext,
257             mxFrame);
258         mpTabBar->SetDecks(aDeckIds);
259 
260         // Check if the current deck is among the matching decks.
261         bool bCurrentDeckMatches (false);
262         for (ResourceManager::IdContainer::const_iterator
263                  iDeck(aDeckIds.begin()),
264                  iEnd(aDeckIds.end());
265              iDeck!=iEnd;
266              ++iDeck)
267         {
268             if (iDeck->equals(msCurrentDeckId))
269             {
270                 bCurrentDeckMatches = true;
271                 break;
272             }
273         }
274 
275         DeckDescriptor const* pDeckDescriptor = NULL;
276         if ( ! bCurrentDeckMatches)
277         {
278             pDeckDescriptor = ResourceManager::Instance().GetBestMatchingDeck(rContext, mxFrame);
279             msCurrentDeckId = pDeckDescriptor->msId;
280         }
281         else
282             pDeckDescriptor = ResourceManager::Instance().GetDeckDescriptor(msCurrentDeckId);
283         if (pDeckDescriptor != NULL)
284         {
285             msCurrentDeckId = pDeckDescriptor->msId;
286             SwitchToDeck(*pDeckDescriptor, rContext);
287         }
288     }
289 }
290 
291 
292 
293 
294 void SidebarController::SwitchToDeck (
295     const ::rtl::OUString& rsDeckId)
296 {
297     if ( ! msCurrentDeckId.equals(rsDeckId))
298     {
299         const DeckDescriptor* pDeckDescriptor = ResourceManager::Instance().GetDeckDescriptor(rsDeckId);
300         if (pDeckDescriptor != NULL)
301             SwitchToDeck(*pDeckDescriptor, maCurrentContext);
302     }
303 }
304 
305 
306 
307 
308 void SidebarController::SwitchToDeck (
309     const DeckDescriptor& rDeckDescriptor,
310     const EnumContext& rContext)
311 {
312     if ( ! msCurrentDeckId.equals(rDeckDescriptor.msId))
313     {
314         // When the deck changes then destroy the deck and all panels
315         // and create everything new.
316         if (mpCurrentConfiguration)
317         {
318             mpCurrentConfiguration->Dispose();
319             mpCurrentConfiguration.reset();
320         }
321 
322         msCurrentDeckId = rDeckDescriptor.msId;
323     }
324 
325     // Determine the panels to display in the deck.
326     ResourceManager::IdContainer aPanelIds;
327     ResourceManager::Instance().GetMatchingPanels(
328         aPanelIds,
329         rContext,
330         rDeckDescriptor.msId,
331         mxFrame);
332 
333     // Provide a configuration and Deck object.
334     if ( ! mpCurrentConfiguration)
335     {
336         mpCurrentConfiguration.reset(new DeckConfiguration);
337         mpCurrentConfiguration->mpDeck = new Deck(rDeckDescriptor, mpParentWindow);
338     }
339 
340     // Update the panel list.
341     const sal_Int32 nNewPanelCount (aPanelIds.size());
342     ::std::vector<Panel*> aNewPanels;
343     ::std::vector<Panel*> aCurrentPanels;
344     if (mpCurrentConfiguration)
345         aCurrentPanels.swap(mpCurrentConfiguration->maPanels);
346     aNewPanels.resize(nNewPanelCount);
347     for (sal_Int32 nIndex=0; nIndex<nNewPanelCount; ++nIndex)
348     {
349         const OUString& rsPanelId (aPanelIds[nIndex]);
350 
351         // Find the corresponding panel among the currently active
352         // panels.
353         ::std::vector<Panel*>::iterator iPanel (::std::find_if(
354                 aCurrentPanels.begin(),
355                 aCurrentPanels.end(),
356                 ::boost::bind(&Panel::HasIdPredicate, _1, ::boost::cref(rsPanelId))));
357         if (iPanel != aCurrentPanels.end())
358         {
359             // Panel already exists in current configuration.  Move it
360             // to new configuration.
361             aNewPanels[nIndex] = *iPanel;
362             aCurrentPanels[::std::distance(aCurrentPanels.begin(), iPanel)] = NULL;
363         }
364         else
365         {
366             // Panel does not yet exist.  Create it.
367             aNewPanels[nIndex] = CreatePanel(rsPanelId, mpCurrentConfiguration->mpDeck);
368         }
369     }
370 
371     // Destroy all panels that are not used in the new configuration.
372     for (::std::vector<Panel*>::const_iterator iPanel(aCurrentPanels.begin()),iEnd(aCurrentPanels.end());
373          iPanel!=iEnd;
374          ++iPanel)
375     {
376         if (*iPanel != NULL)
377             (*iPanel)->Dispose();
378     }
379 
380     // Activate the deck and the new set of panels.
381     mpCurrentConfiguration->maPanels.swap(aNewPanels);
382     mpCurrentConfiguration->mpDeck->SetPosSizePixel(
383         0,
384         0,
385         mpParentWindow->GetSizePixel().Width()-TabBar::GetDefaultWidth(),
386         mpParentWindow->GetSizePixel().Height());
387     mpCurrentConfiguration->mpDeck->SetPanels(mpCurrentConfiguration->maPanels);
388     mpCurrentConfiguration->mpDeck->Show();
389 
390     // Tell the tab bar to highlight the button associated with the
391     // deck.
392     mpTabBar->HighlightDeck(rDeckDescriptor.msId);
393 }
394 
395 
396 
397 
398 Panel* SidebarController::CreatePanel (
399     const OUString& rsPanelId,
400     Deck* pDeck) const
401 {
402     const PanelDescriptor* pPanelDescriptor = ResourceManager::Instance().GetPanelDescriptor(rsPanelId);
403     if (pPanelDescriptor == NULL)
404         return NULL;
405 
406     // Create the panel which is the parent window of the UIElement.
407     Panel* pPanel = new Panel(
408         *pPanelDescriptor,
409         pDeck,
410         ::boost::bind(&Deck::RequestLayout,pDeck));
411 
412     // Create the XUIElement.
413     Reference<ui::XUIElement> xUIElement (CreateUIElement(
414             pPanel->GetComponentInterface(),
415             pPanelDescriptor->msImplementationURL,
416             pPanel));
417     if (xUIElement.is())
418     {
419         // Initialize the panel and add it to the active deck.
420         pPanel->SetUIElement(xUIElement);
421     }
422     else
423     {
424         delete pPanel;
425         pPanel = NULL;
426     }
427 
428     return pPanel;
429 }
430 
431 
432 
433 
434 Reference<ui::XUIElement> SidebarController::CreateUIElement (
435     const Reference<awt::XWindowPeer>& rxWindow,
436     const ::rtl::OUString& rsImplementationURL,
437     Panel* pPanel) const
438 {
439     try
440     {
441         const ::comphelper::ComponentContext aComponentContext (::comphelper::getProcessServiceFactory());
442         const Reference<ui::XUIElementFactory> xUIElementFactory (
443             aComponentContext.createComponent("com.sun.star.ui.UIElementFactoryManager"),
444             UNO_QUERY_THROW);
445 
446 
447         // Create the XUIElement.
448         ::comphelper::NamedValueCollection aCreationArguments;
449         aCreationArguments.put("Frame", makeAny(mxFrame));
450         aCreationArguments.put("ParentWindow", makeAny(rxWindow));
451         SfxDockingWindow* pSfxDockingWindow = dynamic_cast<SfxDockingWindow*>(mpParentWindow);
452         if (pSfxDockingWindow != NULL)
453             aCreationArguments.put("SfxBindings", makeAny(sal_uInt64(&pSfxDockingWindow->GetBindings())));
454         const Sequence<beans::PropertyValue> aProperties (aCreationArguments.getPropertyValues());
455         Reference<ui::XUIElement> xUIElement(
456             xUIElementFactory->createUIElement(
457                 rsImplementationURL,
458                 aProperties),
459             UNO_QUERY_THROW);
460 
461         // Provide the new ui element with the XSidebarPanel object
462         // that gives access to a canvas, screen coordinates of the
463         // panel or the theme properties.
464         if (xUIElement.is())
465         {
466             Reference<lang::XInitialization> xInitialization(xUIElement->getRealInterface(), UNO_QUERY);
467             if (xInitialization.is())
468             {
469                 Sequence<Any> aArguments (1);
470                 Reference<ui::XSidebarPanel> xPanel (SidebarPanel::Create(pPanel));
471                 aArguments[0] = Any(xPanel);
472                 xInitialization->initialize(aArguments);
473             }
474         }
475 
476         return xUIElement;
477     }
478     catch(Exception& rException)
479     {
480         OSL_TRACE("caught exception: %s",
481             OUStringToOString(rException.Message, RTL_TEXTENCODING_ASCII_US).getStr());
482         // For some reason we can not create the actual panel.
483         // Probably because its factory was not properly registered.
484         // TODO: provide feedback to developer to better pinpoint the
485         // source of the error.
486 
487         return NULL;
488     }
489 }
490 
491 
492 
493 
494 IMPL_LINK(SidebarController, WindowEventHandler, VclWindowEvent*, pEvent)
495 {
496     if (pEvent != NULL)
497     {
498         switch (pEvent->GetId())
499         {
500             case VCLEVENT_WINDOW_GETFOCUS:
501             case VCLEVENT_WINDOW_LOSEFOCUS:
502                 break;
503 
504             case VCLEVENT_WINDOW_SHOW:
505             case VCLEVENT_WINDOW_RESIZE:
506                 NotifyResize();
507                 break;
508 
509             case VCLEVENT_WINDOW_DATACHANGED:
510                 // Force an update of deck and tab bar to reflect
511                 // changes in theme (high contrast mode).
512                 Theme::HandleDataChange();
513                 mpParentWindow->Invalidate();
514                 break;
515 
516             case SFX_HINT_DYING:
517                 dispose();
518                 break;
519 
520             default:
521                 break;
522         }
523     }
524 
525     return sal_True;
526 }
527 
528 
529 
530 
531 void SidebarController::ShowPopupMenu (
532     const Rectangle& rButtonBox,
533     const ::std::vector<TabBar::DeckMenuData>& rDeckSelectionData,
534     const ::std::vector<TabBar::DeckMenuData>& rDeckShowData) const
535 {
536     ::boost::shared_ptr<PopupMenu> pMenu = CreatePopupMenu(rDeckSelectionData, rDeckShowData);
537     pMenu->SetSelectHdl(LINK(this, SidebarController, OnMenuItemSelected));
538 
539     // pass toolbox button rect so the menu can stay open on button up
540     Rectangle aBox (rButtonBox);
541     aBox.Move(mpTabBar->GetPosPixel().X(), 0);
542     pMenu->Execute(mpParentWindow, aBox, POPUPMENU_EXECUTE_DOWN);
543 }
544 
545 
546 
547 
548 ::boost::shared_ptr<PopupMenu> SidebarController::CreatePopupMenu (
549     const ::std::vector<TabBar::DeckMenuData>& rDeckSelectionData,
550     const ::std::vector<TabBar::DeckMenuData>& rDeckShowData) const
551 {
552     ::boost::shared_ptr<PopupMenu> pMenu (new PopupMenu());
553     FloatingWindow* pMenuWindow = dynamic_cast<FloatingWindow*>(pMenu->GetWindow());
554     if (pMenuWindow != NULL)
555     {
556         pMenuWindow->SetPopupModeFlags(pMenuWindow->GetPopupModeFlags() | FLOATWIN_POPUPMODE_NOMOUSEUPCLOSE);
557     }
558 
559     SidebarResource aLocalResource;
560 
561     // Add one entry for every tool panel element to individually make
562     // them visible or hide them.
563     {
564         sal_Int32 nIndex (MID_FIRST_PANEL);
565         for(::std::vector<TabBar::DeckMenuData>::const_iterator
566                 iItem(rDeckSelectionData.begin()),
567                 iEnd(rDeckSelectionData.end());
568             iItem!=iEnd;
569             ++iItem)
570         {
571             pMenu->InsertItem(nIndex, iItem->get<0>(), MIB_RADIOCHECK);
572             pMenu->CheckItem(nIndex, iItem->get<2>());
573             ++nIndex;
574         }
575     }
576 
577     pMenu->InsertSeparator();
578 
579     // Add entry for docking or un-docking the tool panel.
580     if (mpParentWindow->IsFloatingMode())
581         pMenu->InsertItem(MID_LOCK_TASK_PANEL, String(SfxResId(STR_SFX_DOCK)));
582     else
583         pMenu->InsertItem(MID_UNLOCK_TASK_PANEL, String(SfxResId(STR_SFX_UNDOCK)));
584 
585     // Add sub menu for customization (hiding of deck tabs.)
586     PopupMenu* pCustomizationMenu = new PopupMenu();
587     {
588         sal_Int32 nIndex (MID_FIRST_HIDE);
589         for(::std::vector<TabBar::DeckMenuData>::const_iterator
590                 iItem(rDeckShowData.begin()),
591                 iEnd(rDeckShowData.end());
592             iItem!=iEnd;
593             ++iItem)
594         {
595             pCustomizationMenu->InsertItem(nIndex, iItem->get<0>(), MIB_CHECKABLE);
596             pCustomizationMenu->CheckItem(nIndex, iItem->get<2>());
597             ++nIndex;
598         }
599     }
600 
601     pCustomizationMenu->InsertSeparator();
602     pCustomizationMenu->InsertItem(MID_RESTORE_DEFAULT, String(SfxResId(STRING_RESTORE)));
603 
604     pMenu->InsertItem(MID_CUSTOMIZATION, String(SfxResId(STRING_CUSTOMIZATION)));
605     pMenu->SetPopupMenu(MID_CUSTOMIZATION, pCustomizationMenu);
606 
607     pMenu->RemoveDisabledEntries(sal_False, sal_False);
608 
609     return pMenu;
610 }
611 
612 
613 
614 
615 IMPL_LINK(SidebarController, OnMenuItemSelected, Menu*, pMenu)
616 {
617     if (pMenu == NULL)
618     {
619         OSL_ENSURE(pMenu!=NULL, "TaskPaneController_Impl::OnMenuItemSelected: illegal menu!");
620         return 0;
621     }
622 
623     pMenu->Deactivate();
624     const sal_Int32 nIndex (pMenu->GetCurItemId());
625     switch (nIndex)
626     {
627         case MID_UNLOCK_TASK_PANEL:
628             mpParentWindow->SetFloatingMode(sal_True);
629             break;
630 
631         case MID_LOCK_TASK_PANEL:
632             mpParentWindow->SetFloatingMode(sal_False);
633             break;
634 
635         case MID_RESTORE_DEFAULT:
636             mpTabBar->RestoreHideFlags();
637             break;
638 
639         default:
640         {
641             try
642             {
643                 if (nIndex >= MID_FIRST_PANEL && nIndex<MID_FIRST_HIDE)
644                     SwitchToDeck(mpTabBar->GetDeckIdForIndex(nIndex - MID_FIRST_PANEL));
645                 else if (nIndex >=MID_FIRST_HIDE)
646                     mpTabBar->ToggleHideFlag(nIndex-MID_FIRST_HIDE);
647             }
648             catch (RuntimeException&)
649             {
650             }
651         }
652         break;
653     }
654 
655     return 1;
656 }
657 
658 
659 
660 
661 
662 } } // end of namespace sfx2::sidebar
663