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