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