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 <comphelper/componentfactory.hxx>
41 #include <comphelper/componentcontext.hxx>
42 #include <comphelper/namedvaluecollection.hxx>
43 
44 #include <com/sun/star/ui/ContextChangeEventMultiplexer.hpp>
45 #include <com/sun/star/ui/ContextChangeEventObject.hpp>
46 
47 #include <boost/bind.hpp>
48 #include <boost/foreach.hpp>
49 
50 
51 using namespace css;
52 using namespace cssu;
53 
54 
55 #define A2S(pString) (::rtl::OUString(RTL_CONSTASCII_USTRINGPARAM(pString)))
56 
57 namespace sfx2 { namespace sidebar {
58 
59 namespace {
60     enum MenuId
61     {
62         MID_UNLOCK_TASK_PANEL = 1,
63         MID_LOCK_TASK_PANEL,
64         MID_CUSTOMIZATION,
65         MID_RESTORE_DEFAULT,
66         MID_FIRST_PANEL,
67         MID_FIRST_HIDE = 1000
68     };
69 }
70 
71 
72 SidebarController::SidebarController (
73     DockingWindow* pParentWindow,
74     const cssu::Reference<css::frame::XFrame>& rxFrame)
75     : SidebarControllerInterfaceBase(m_aMutex),
76       mpCurrentConfiguration(),
77       mpParentWindow(pParentWindow),
78       mpTabBar(new TabBar(
79               mpParentWindow,
80               rxFrame,
81               ::boost::bind(&SidebarController::SwitchToDeck, this, _1),
82               ::boost::bind(&SidebarController::ShowPopupMenu, this, _1))),
83       mxFrame(rxFrame)
84 {
85     if (pParentWindow == NULL)
86     {
87         OSL_ASSERT(pParentWindow!=NULL);
88             return;
89     }
90 
91     UpdateConfigurations(Context(A2S("default"), A2S("default")));
92 
93     // Listen for context change events.
94     cssu::Reference<css::ui::XContextChangeEventMultiplexer> xMultiplexer (
95         css::ui::ContextChangeEventMultiplexer::get(
96             ::comphelper::getProcessComponentContext()));
97     if (xMultiplexer.is())
98         xMultiplexer->addContextChangeEventListener(
99             static_cast<css::ui::XContextChangeEventListener*>(this),
100             NULL);
101 
102     // Listen for window events.
103     mpParentWindow->AddEventListener(LINK(this, SidebarController, WindowEventHandler));
104 
105     // Listen for theme property changes.
106     Theme::GetPropertySet()->addPropertyChangeListener(
107         A2S(""),
108         static_cast<css::beans::XPropertyChangeListener*>(this));
109 }
110 
111 
112 
113 
114 SidebarController::~SidebarController (void)
115 {
116 }
117 
118 
119 
120 
121 void SAL_CALL SidebarController::disposing (void)
122 {
123     cssu::Reference<css::ui::XContextChangeEventMultiplexer> xMultiplexer (
124         css::ui::ContextChangeEventMultiplexer::get(
125             ::comphelper::getProcessComponentContext()));
126     if (xMultiplexer.is())
127         xMultiplexer->removeAllContextChangeEventListeners(
128             static_cast<css::ui::XContextChangeEventListener*>(this));
129 
130     if (mpParentWindow != NULL)
131     {
132         mpParentWindow->RemoveEventListener(LINK(this, SidebarController, WindowEventHandler));
133         mpParentWindow = NULL;
134     }
135 
136     if (mpCurrentConfiguration)
137     {
138         mpCurrentConfiguration->Disable();
139         mpCurrentConfiguration.reset();
140     }
141 
142     Theme::GetPropertySet()->removePropertyChangeListener(
143         A2S(""),
144         static_cast<css::beans::XPropertyChangeListener*>(this));
145 }
146 
147 
148 
149 
150 void SAL_CALL SidebarController::notifyContextChangeEvent (const css::ui::ContextChangeEventObject& rEvent)
151     throw(cssu::RuntimeException)
152 {
153     UpdateConfigurations(Context(rEvent.ApplicationName, rEvent.ContextName));
154 }
155 
156 
157 
158 
159 void SAL_CALL SidebarController::disposing (const css::lang::EventObject& rEventObject)
160     throw(cssu::RuntimeException)
161 {
162     if (mpCurrentConfiguration)
163     {
164         mpCurrentConfiguration->Disable();
165         mpCurrentConfiguration.reset();
166     }
167     if (mpTabBar != NULL)
168     {
169         mpTabBar->Hide();
170         delete mpTabBar;
171         mpTabBar = NULL;
172     }
173 }
174 
175 
176 
177 
178 void SAL_CALL SidebarController::propertyChange (const css::beans::PropertyChangeEvent& rEvent)
179     throw(cssu::RuntimeException)
180 {
181     DataChangedEvent aEvent (DATACHANGED_USER);
182     mpParentWindow->NotifyAllChilds(aEvent);
183     mpParentWindow->Invalidate(INVALIDATE_CHILDREN);
184 }
185 
186 
187 
188 
189 void SidebarController::NotifyResize (void)
190 {
191     if (mpCurrentConfiguration != NULL)
192     {
193         if (mpCurrentConfiguration->mpDeck==NULL || mpTabBar==NULL)
194         {
195             OSL_ASSERT(mpCurrentConfiguration->mpDeck!=NULL && mpTabBar!=NULL);
196         }
197         else
198         {
199             Window* pParentWindow = mpCurrentConfiguration->mpDeck->GetParent();
200             if (pParentWindow==NULL || pParentWindow!=mpTabBar->GetParent())
201             {
202                 OSL_ASSERT(mpCurrentConfiguration->mpDeck->GetParent() != NULL);
203                 OSL_ASSERT(mpCurrentConfiguration->mpDeck->GetParent() == mpTabBar->GetParent());
204             }
205 
206             const sal_Int32 nWidth (pParentWindow->GetSizePixel().Width());
207             const sal_Int32 nHeight (pParentWindow->GetSizePixel().Height());
208             mpCurrentConfiguration->mpDeck->SetPosSizePixel(0,0, nWidth-TabBar::GetDefaultWidth(), nHeight);
209             mpCurrentConfiguration->mpDeck->Show();
210             mpTabBar->SetPosSizePixel(nWidth-TabBar::GetDefaultWidth(),0,TabBar::GetDefaultWidth(),nHeight);
211             mpTabBar->Show();
212 
213             mpCurrentConfiguration->mpDeck->RequestLayout();
214         }
215     }
216 }
217 
218 
219 
220 
221 void SidebarController::UpdateConfigurations (const Context& rContext)
222 {
223     maCurrentContext = rContext;
224 
225     ResourceManager::DeckContainer aDeckDescriptors;
226     ResourceManager::Instance().GetMatchingDecks (
227         aDeckDescriptors,
228         rContext,
229         mxFrame);
230     mpTabBar->SetDecks(aDeckDescriptors);
231 
232     const DeckDescriptor* pDeckDescriptor (ResourceManager::Instance().GetBestMatchingDeck(rContext, mxFrame));
233     if (pDeckDescriptor != NULL)
234         SwitchToDeck(*pDeckDescriptor, rContext);
235 }
236 
237 
238 
239 
240 void SidebarController::SwitchToDeck (
241     const DeckDescriptor& rDeckDescriptor)
242 {
243     SwitchToDeck(rDeckDescriptor, maCurrentContext);
244 }
245 
246 
247 
248 
249 void SidebarController::SwitchToDeck (
250     const DeckDescriptor& rDeckDescriptor,
251     const Context& rContext)
252 {
253     // Determine the panels to display in the deck.
254     ResourceManager::PanelContainer aPanelDescriptors;
255     ResourceManager::Instance().GetMatchingPanels(
256         aPanelDescriptors,
257         rContext,
258         rDeckDescriptor.msId,
259         mxFrame);
260 
261     // Setup a configuration for the requested deck
262     // and create the deck.
263     ::boost::shared_ptr<DeckConfiguration> pConfiguration (new DeckConfiguration);
264     pConfiguration->mpDeck = new Deck(rDeckDescriptor, mpParentWindow);
265 
266     // Create the panels.
267     for (ResourceManager::PanelContainer::const_iterator
268              iPanel(aPanelDescriptors.begin()),
269              iEnd(aPanelDescriptors.end());
270          iPanel!=iEnd;
271          ++iPanel)
272     {
273         // Create the panel which is the parent window of the UIElement.
274         Panel* pPanel = new Panel(
275             *iPanel,
276             pConfiguration->mpDeck,
277             ::boost::bind(&Deck::RequestLayout,pConfiguration->mpDeck));
278 
279         // Create the XUIElement.
280         Reference<ui::XUIElement> xUIElement (CreateUIElement(
281                 pPanel->GetComponentInterface(),
282                 iPanel->msImplementationURL,
283                 pPanel
284                 ));
285         if (xUIElement.is())
286         {
287             // Initialize the panel and add it to the active deck.
288             pPanel->SetUIElement(xUIElement);
289             pConfiguration->maPanels.push_back(pPanel);
290         }
291         else
292         {
293             delete pPanel;
294         }
295     }
296 
297     // Activate the new configuration.
298     MakeConfigurationCurrent(pConfiguration);
299 
300     // Tell the tab bar to highlight the button associated with the
301     // deck.
302     mpTabBar->HighlightDeck(rDeckDescriptor.msId);
303 }
304 
305 
306 
307 
308 Reference<ui::XUIElement> SidebarController::CreateUIElement (
309     const Reference<awt::XWindowPeer>& rxWindow,
310     const ::rtl::OUString& rsImplementationURL,
311     Panel* pPanel) const
312 {
313     try
314     {
315         const ::comphelper::ComponentContext aComponentContext (::comphelper::getProcessServiceFactory());
316         const Reference<ui::XUIElementFactory> xUIElementFactory (
317             aComponentContext.createComponent("com.sun.star.ui.UIElementFactoryManager"),
318             UNO_QUERY_THROW);
319 
320 
321         // Create the XUIElement.
322         ::comphelper::NamedValueCollection aCreationArguments;
323         aCreationArguments.put("Frame", makeAny(mxFrame));
324         aCreationArguments.put("ParentWindow", makeAny(rxWindow));
325         SfxDockingWindow* pSfxDockingWindow = dynamic_cast<SfxDockingWindow*>(mpParentWindow);
326         if (pSfxDockingWindow != NULL)
327             aCreationArguments.put("SfxBindings", makeAny(sal_uInt64(&pSfxDockingWindow->GetBindings())));
328         Reference<ui::XUIElement> xUIElement(
329             xUIElementFactory->createUIElement(
330                 rsImplementationURL,
331                 aCreationArguments.getPropertyValues()),
332             UNO_QUERY_THROW);
333 
334         // Provide the new ui element with the XSidebarPanel object
335         // that gives access to a canvas, screen coordinates of the
336         // panel or the theme properties.
337         if (xUIElement.is())
338         {
339             Reference<lang::XInitialization> xInitialization(xUIElement->getRealInterface(), UNO_QUERY);
340             if (xInitialization.is())
341             {
342                 Sequence<Any> aArguments (1);
343                 Reference<ui::XSidebarPanel> xPanel (SidebarPanel::Create(pPanel));
344                 aArguments[0] = Any(xPanel);
345                 xInitialization->initialize(aArguments);
346             }
347         }
348 
349         return xUIElement;
350     }
351     catch(Exception& rException)
352     {
353         OSL_TRACE("caught exception: %s",
354             OUStringToOString(rException.Message, RTL_TEXTENCODING_ASCII_US).getStr());
355         // For some reason we can not create the actual panel.
356         // Probably because its factory was not properly registered.
357         // TODO: provide feedback to developer to better pinpoint the
358         // source of the error.
359 
360         return NULL;
361     }
362 }
363 
364 
365 
366 
367 void SidebarController::MakeConfigurationCurrent (const ::boost::shared_ptr<DeckConfiguration>& rpConfiguration)
368 {
369     if ( ! rpConfiguration || rpConfiguration->mpDeck == NULL)
370         return;
371 
372     // Deactivate the current deck and panels.
373     if (mpCurrentConfiguration && mpCurrentConfiguration->mpDeck!=NULL)
374         mpCurrentConfiguration->Disable();
375 
376     mpCurrentConfiguration = rpConfiguration;
377 
378     mpCurrentConfiguration->mpDeck->SetPosSizePixel(
379         0,
380         0,
381         mpParentWindow->GetSizePixel().Width()-TabBar::GetDefaultWidth(),
382         mpParentWindow->GetSizePixel().Height());
383     mpCurrentConfiguration->mpDeck->Show();
384     mpCurrentConfiguration->Activate();
385 }
386 
387 
388 
389 
390 IMPL_LINK(SidebarController, WindowEventHandler, VclWindowEvent*, pEvent)
391 {
392     if (pEvent != NULL)
393     {
394         ::Window* pWindow = pEvent->GetWindow();
395         switch (pEvent->GetId())
396         {
397             case VCLEVENT_WINDOW_GETFOCUS:
398             case VCLEVENT_WINDOW_LOSEFOCUS:
399                 break;
400 
401             case VCLEVENT_WINDOW_SHOW:
402             case VCLEVENT_WINDOW_RESIZE:
403                 NotifyResize();
404                 break;
405 
406             case VCLEVENT_WINDOW_DATACHANGED:
407                 // Force an update of deck and tab bar to reflect
408                 // changes in theme (high contrast mode).
409                 Theme::HandleDataChange();
410                 mpParentWindow->Invalidate();
411                 break;
412 
413             case SFX_HINT_DYING:
414                 dispose();
415                 break;
416 
417             default:
418                 break;
419         }
420     }
421 
422     return sal_True;
423 }
424 
425 
426 
427 
428 void SidebarController::ShowPopupMenu (const Rectangle& rButtonBox) const
429 {
430     ::boost::shared_ptr<PopupMenu> pMenu = CreatePopupMenu();
431     pMenu->SetSelectHdl(LINK(this, SidebarController, OnMenuItemSelected));
432 
433     // pass toolbox button rect so the menu can stay open on button up
434     Rectangle aBox (rButtonBox);
435     aBox.Move(mpTabBar->GetPosPixel().X(), 0);
436     pMenu->Execute(mpParentWindow, aBox, POPUPMENU_EXECUTE_DOWN);
437 }
438 
439 
440 
441 
442 ::boost::shared_ptr<PopupMenu> SidebarController::CreatePopupMenu (void) const
443 {
444     ::boost::shared_ptr<PopupMenu> pMenu (new PopupMenu());
445     FloatingWindow* pMenuWindow = dynamic_cast<FloatingWindow*>(pMenu->GetWindow());
446     if (pMenuWindow != NULL)
447     {
448         pMenuWindow->SetPopupModeFlags(pMenuWindow->GetPopupModeFlags() | FLOATWIN_POPUPMODE_NOMOUSEUPCLOSE);
449     }
450 
451     SidebarResource aLocalResource;
452 
453     // Add one entry for every tool panel element to individually make
454     // them visible or hide them.
455     if (mpTabBar != NULL)
456     {
457         mpTabBar->AddPopupMenuEntries(*pMenu, MID_FIRST_PANEL);
458         pMenu->InsertSeparator();
459     }
460 
461     // Add entry for docking or un-docking the tool panel.
462     if (mpParentWindow->IsFloatingMode())
463         pMenu->InsertItem(MID_LOCK_TASK_PANEL, String(SfxResId(STR_SFX_DOCK)));
464     else
465         pMenu->InsertItem(MID_UNLOCK_TASK_PANEL, String(SfxResId(STR_SFX_UNDOCK)));
466 
467     // Add sub menu for customization (hiding of deck tabs.)
468     PopupMenu* pCustomizationMenu = new PopupMenu();
469     mpTabBar->AddCustomizationMenuEntries(*pCustomizationMenu, MID_FIRST_HIDE);
470     pCustomizationMenu->InsertSeparator();
471     pCustomizationMenu->InsertItem(MID_RESTORE_DEFAULT, String(SfxResId(STRING_RESTORE)));
472 
473     pMenu->InsertItem(MID_CUSTOMIZATION, String(SfxResId(STRING_CUSTOMIZATION)));
474     pMenu->SetPopupMenu(MID_CUSTOMIZATION, pCustomizationMenu);
475 
476     pMenu->RemoveDisabledEntries(sal_False, sal_False);
477 
478     return pMenu;
479 }
480 
481 
482 
483 
484 IMPL_LINK(SidebarController, OnMenuItemSelected, Menu*, pMenu)
485 {
486     if (pMenu == NULL)
487     {
488         OSL_ENSURE(pMenu!=NULL, "TaskPaneController_Impl::OnMenuItemSelected: illegal menu!");
489         return 0;
490     }
491 
492     pMenu->Deactivate();
493     const sal_Int32 nIndex (pMenu->GetCurItemId());
494     switch (nIndex)
495     {
496         case MID_UNLOCK_TASK_PANEL:
497             mpParentWindow->SetFloatingMode(sal_True);
498             break;
499 
500         case MID_LOCK_TASK_PANEL:
501             mpParentWindow->SetFloatingMode(sal_False);
502             break;
503 
504         case MID_RESTORE_DEFAULT:
505             mpTabBar->RestoreHideFlags();
506             break;
507 
508         default:
509         {
510             try
511             {
512                 if (nIndex >= MID_FIRST_PANEL && nIndex<MID_FIRST_HIDE)
513                     SwitchToDeck(mpTabBar->GetDeckDescriptorForIndex(nIndex - MID_FIRST_PANEL));
514                 else if (nIndex >=MID_FIRST_HIDE)
515                     mpTabBar->ToggleHideFlag(nIndex-MID_FIRST_HIDE);
516             }
517             catch (RuntimeException&)
518             {
519             }
520         }
521         break;
522     }
523 
524     return 1;
525 }
526 
527 
528 
529 
530 
531 } } // end of namespace sfx2::sidebar
532