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 #include <sfx2/sidebar/SidebarController.hxx> 20 #include <sfx2/sidebar/Deck.hxx> 21 #include <sidebar/DeckDescriptor.hxx> 22 #include <sidebar/DeckTitleBar.hxx> 23 #include <sfx2/sidebar/Panel.hxx> 24 #include <sidebar/PanelDescriptor.hxx> 25 #include <sidebar/PanelTitleBar.hxx> 26 #include <sfx2/sidebar/TabBar.hxx> 27 #include <sfx2/sidebar/Theme.hxx> 28 #include <sfx2/sidebar/SidebarChildWindow.hxx> 29 #include <sidebar/Tools.hxx> 30 #include <sfx2/sidebar/SidebarDockingWindow.hxx> 31 #include <com/sun/star/ui/XSidebarProvider.hpp> 32 #include <com/sun/star/frame/XController2.hpp> 33 #include <sfx2/sidebar/Context.hxx> 34 #include <sfx2/viewsh.hxx> 35 36 37 #include <framework/ContextChangeEventMultiplexerTunnel.hxx> 38 #include <vcl/floatwin.hxx> 39 #include <vcl/fixed.hxx> 40 #include <vcl/uitest/logger.hxx> 41 #include <vcl/uitest/eventdescription.hxx> 42 #include <vcl/svapp.hxx> 43 #include <splitwin.hxx> 44 #include <tools/diagnose_ex.h> 45 #include <tools/link.hxx> 46 #include <toolkit/helper/vclunohelper.hxx> 47 #include <comphelper/processfactory.hxx> 48 #include <comphelper/namedvaluecollection.hxx> 49 #include <comphelper/lok.hxx> 50 #include <sal/log.hxx> 51 #include <officecfg/Office/UI/Sidebar.hxx> 52 #include <LibreOfficeKit/LibreOfficeKitEnums.h> 53 #include <boost/property_tree/ptree.hpp> 54 #include <boost/property_tree/json_parser.hpp> 55 56 #include <com/sun/star/awt/XWindowPeer.hpp> 57 #include <com/sun/star/frame/XDispatch.hpp> 58 #include <com/sun/star/ui/ContextChangeEventMultiplexer.hpp> 59 #include <com/sun/star/ui/ContextChangeEventObject.hpp> 60 #include <com/sun/star/ui/theUIElementFactoryManager.hpp> 61 #include <com/sun/star/util/URL.hpp> 62 #include <com/sun/star/rendering/XSpriteCanvas.hpp> 63 64 65 using namespace css; 66 using namespace css::uno; 67 68 namespace 69 { 70 constexpr OUStringLiteral gsReadOnlyCommandName = u".uno:EditDoc"; 71 const sal_Int32 gnWidthCloseThreshold (70); 72 const sal_Int32 gnWidthOpenThreshold (40); 73 74 std::string UnoNameFromDeckId(std::u16string_view rsDeckId, bool isImpress = false) 75 { 76 if (rsDeckId == u"SdCustomAnimationDeck") 77 return ".uno:CustomAnimation"; 78 79 if (rsDeckId == u"PropertyDeck") 80 return isImpress ? ".uno:ModifyPage" : ".uno:Sidebar"; 81 82 if (rsDeckId == u"SdLayoutsDeck") 83 return ".uno:ModifyPage"; 84 85 if (rsDeckId == u"SdSlideTransitionDeck") 86 return ".uno:SlideChangeWindow"; 87 88 if (rsDeckId == u"SdAllMasterPagesDeck") 89 return ".uno:MasterSlidesPanel"; 90 91 if (rsDeckId == u"SdMasterPagesDeck") 92 return ".uno:MasterSlidesPanel"; 93 94 if (rsDeckId == u"GalleryDeck") 95 return ".uno:Gallery"; 96 97 return ""; 98 } 99 } 100 101 namespace sfx2::sidebar { 102 103 namespace { 104 105 /** When in doubt, show this deck. 106 */ 107 constexpr OUStringLiteral gsDefaultDeckId(u"PropertyDeck"); 108 } 109 110 SidebarController::SidebarController ( 111 SidebarDockingWindow* pParentWindow, 112 const SfxViewFrame* pViewFrame) 113 : SidebarControllerInterfaceBase(m_aMutex), 114 mpCurrentDeck(), 115 mpParentWindow(pParentWindow), 116 mpViewFrame(pViewFrame), 117 mxFrame(pViewFrame->GetFrame().GetFrameInterface()), 118 mpTabBar(VclPtr<TabBar>::Create( 119 mpParentWindow, 120 mxFrame, 121 [this](const OUString& rsDeckId) { return this->OpenThenToggleDeck(rsDeckId); }, 122 [this](weld::Menu& rMainMenu, weld::Menu& rSubMenu, 123 const ::std::vector<TabBar::DeckMenuData>& rMenuData) { return this->ShowPopupMenu(rMainMenu, rSubMenu, rMenuData); }, 124 this)), 125 maCurrentContext(OUString(), OUString()), 126 maRequestedContext(), 127 mnRequestedForceFlags(SwitchFlag_NoForce), 128 mnMaximumSidebarWidth(officecfg::Office::UI::Sidebar::General::MaximumWidth::get()), 129 msCurrentDeckId(gsDefaultDeckId), 130 maPropertyChangeForwarder([this](){ return this->BroadcastPropertyChange(); }), 131 maContextChangeUpdate([this](){ return this->UpdateConfigurations(); }), 132 mbIsDeckRequestedOpen(), 133 mbIsDeckOpen(), 134 mbFloatingDeckClosed(!pParentWindow->IsFloatingMode()), 135 mnSavedSidebarWidth(pParentWindow->GetSizePixel().Width()), 136 maFocusManager([this](const Panel& rPanel){ return this->ShowPanel(rPanel); }), 137 mxReadOnlyModeDispatch(), 138 mbIsDocumentReadOnly(false), 139 mpSplitWindow(nullptr), 140 mnWidthOnSplitterButtonDown(0), 141 mpResourceManager() 142 { 143 // Decks and panel collections for this sidebar 144 mpResourceManager = std::make_unique<ResourceManager>(); 145 } 146 147 rtl::Reference<SidebarController> SidebarController::create(SidebarDockingWindow* pParentWindow, 148 const SfxViewFrame* pViewFrame) 149 { 150 rtl::Reference<SidebarController> instance(new SidebarController(pParentWindow, pViewFrame)); 151 152 const css::uno::Reference<css::frame::XFrame>& rxFrame = pViewFrame->GetFrame().GetFrameInterface(); 153 registerSidebarForFrame(instance.get(), rxFrame->getController()); 154 rxFrame->addFrameActionListener(instance.get()); 155 // Listen for window events. 156 instance->mpParentWindow->AddEventListener(LINK(instance.get(), SidebarController, WindowEventHandler)); 157 158 // Listen for theme property changes. 159 Theme::GetPropertySet()->addPropertyChangeListener( 160 "", 161 static_cast<css::beans::XPropertyChangeListener*>(instance.get())); 162 163 // Get the dispatch object as preparation to listen for changes of 164 // the read-only state. 165 const util::URL aURL (Tools::GetURL(gsReadOnlyCommandName)); 166 instance->mxReadOnlyModeDispatch = Tools::GetDispatch(rxFrame, aURL); 167 if (instance->mxReadOnlyModeDispatch.is()) 168 instance->mxReadOnlyModeDispatch->addStatusListener(instance.get(), aURL); 169 170 //first UpdateConfigurations call will SwitchToDeck 171 172 return instance; 173 } 174 175 SidebarController::~SidebarController() 176 { 177 } 178 179 SidebarController* SidebarController::GetSidebarControllerForFrame ( 180 const css::uno::Reference<css::frame::XFrame>& rxFrame) 181 { 182 uno::Reference<frame::XController> const xController(rxFrame->getController()); 183 if (!xController.is()) // this may happen during dispose of Draw controller but perhaps it's a bug 184 { 185 SAL_WARN("sfx.sidebar", "GetSidebarControllerForFrame: frame has no XController"); 186 return nullptr; 187 } 188 uno::Reference<ui::XContextChangeEventListener> const xListener( 189 framework::GetFirstListenerWith(xController, 190 [] (uno::Reference<uno::XInterface> const& xRef) 191 { return nullptr != dynamic_cast<SidebarController*>(xRef.get()); } 192 )); 193 194 return dynamic_cast<SidebarController*>(xListener.get()); 195 } 196 197 void SidebarController::registerSidebarForFrame(SidebarController* pController, const css::uno::Reference<css::frame::XController>& xController) 198 { 199 // Listen for context change events. 200 css::uno::Reference<css::ui::XContextChangeEventMultiplexer> xMultiplexer ( 201 css::ui::ContextChangeEventMultiplexer::get( 202 ::comphelper::getProcessComponentContext())); 203 xMultiplexer->addContextChangeEventListener( 204 static_cast<css::ui::XContextChangeEventListener*>(pController), 205 xController); 206 } 207 208 void SidebarController::unregisterSidebarForFrame(SidebarController* pController, const css::uno::Reference<css::frame::XController>& xController) 209 { 210 pController->saveDeckState(); 211 pController->disposeDecks(); 212 213 css::uno::Reference<css::ui::XContextChangeEventMultiplexer> xMultiplexer ( 214 css::ui::ContextChangeEventMultiplexer::get( 215 ::comphelper::getProcessComponentContext())); 216 xMultiplexer->removeContextChangeEventListener( 217 static_cast<css::ui::XContextChangeEventListener*>(pController), 218 xController); 219 } 220 221 void SidebarController::disposeDecks() 222 { 223 SolarMutexGuard aSolarMutexGuard; 224 225 if (comphelper::LibreOfficeKit::isActive()) 226 { 227 if (const SfxViewShell* pViewShell = mpViewFrame->GetViewShell()) 228 { 229 const std::string hide = UnoNameFromDeckId(msCurrentDeckId, vcl::EnumContext::Application::Impress == vcl::EnumContext::GetApplicationEnum(GetCurrentContext().msApplication)); 230 if (!hide.empty()) 231 pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_STATE_CHANGED, 232 (hide + "=false").c_str()); 233 } 234 235 mpParentWindow->ReleaseLOKNotifier(); 236 } 237 238 mpCurrentDeck.clear(); 239 maFocusManager.Clear(); 240 mpResourceManager->disposeDecks(); 241 } 242 243 void SAL_CALL SidebarController::disposing() 244 { 245 SolarMutexGuard aSolarMutexGuard; 246 247 mpCloseIndicator.disposeAndClear(); 248 249 maFocusManager.Clear(); 250 mpTabBar.disposeAndClear(); 251 252 saveDeckState(); 253 254 // clear decks 255 ResourceManager::DeckContextDescriptorContainer aDecks; 256 257 mpResourceManager->GetMatchingDecks ( 258 aDecks, 259 GetCurrentContext(), 260 IsDocumentReadOnly(), 261 mxFrame->getController()); 262 263 for (const auto& rDeck : aDecks) 264 { 265 std::shared_ptr<DeckDescriptor> deckDesc = mpResourceManager->GetDeckDescriptor(rDeck.msId); 266 267 VclPtr<Deck> aDeck = deckDesc->mpDeck; 268 if (aDeck) 269 aDeck.disposeAndClear(); 270 } 271 272 uno::Reference<css::frame::XController> xController = mxFrame->getController(); 273 if (!xController.is()) 274 xController = mxCurrentController; 275 276 mxFrame->removeFrameActionListener(this); 277 unregisterSidebarForFrame(this, xController); 278 279 if (mxReadOnlyModeDispatch.is()) 280 mxReadOnlyModeDispatch->removeStatusListener(this, Tools::GetURL(gsReadOnlyCommandName)); 281 if (mpSplitWindow != nullptr) 282 { 283 mpSplitWindow->RemoveEventListener(LINK(this, SidebarController, WindowEventHandler)); 284 mpSplitWindow = nullptr; 285 } 286 287 if (mpParentWindow != nullptr) 288 { 289 mpParentWindow->RemoveEventListener(LINK(this, SidebarController, WindowEventHandler)); 290 mpParentWindow = nullptr; 291 } 292 293 Theme::GetPropertySet()->removePropertyChangeListener( 294 "", 295 static_cast<css::beans::XPropertyChangeListener*>(this)); 296 297 maContextChangeUpdate.CancelRequest(); 298 } 299 300 void SAL_CALL SidebarController::notifyContextChangeEvent (const css::ui::ContextChangeEventObject& rEvent) 301 { 302 SolarMutexGuard aSolarMutexGuard; 303 304 // Update to the requested new context asynchronously to avoid 305 // subtle errors caused by SFX2 which in rare cases can not 306 // properly handle a synchronous update. 307 308 maRequestedContext = Context( 309 rEvent.ApplicationName, 310 rEvent.ContextName); 311 312 if (maRequestedContext != maCurrentContext) 313 { 314 mxCurrentController.set(rEvent.Source, css::uno::UNO_QUERY); 315 maContextChangeUpdate.RequestCall(); // async call, not a prob 316 // calling with held 317 // solarmutex 318 // TODO: this call is redundant but mandatory for unit test to update context on document loading 319 UpdateConfigurations(); 320 } 321 } 322 323 void SAL_CALL SidebarController::disposing (const css::lang::EventObject& ) 324 { 325 SolarMutexGuard aSolarMutexGuard; 326 327 dispose(); 328 } 329 330 void SAL_CALL SidebarController::propertyChange (const css::beans::PropertyChangeEvent& ) 331 { 332 SolarMutexGuard aSolarMutexGuard; 333 334 maPropertyChangeForwarder.RequestCall(); // async call, not a prob 335 // to call with held 336 // solarmutex 337 } 338 339 void SAL_CALL SidebarController::statusChanged (const css::frame::FeatureStateEvent& rEvent) 340 { 341 SolarMutexGuard aSolarMutexGuard; 342 343 bool bIsReadWrite (true); 344 if (rEvent.IsEnabled) 345 rEvent.State >>= bIsReadWrite; 346 347 if (mbIsDocumentReadOnly != !bIsReadWrite) 348 { 349 mbIsDocumentReadOnly = !bIsReadWrite; 350 351 // Force the current deck to update its panel list. 352 if ( ! mbIsDocumentReadOnly) 353 SwitchToDefaultDeck(); 354 355 mnRequestedForceFlags |= SwitchFlag_ForceSwitch; 356 maContextChangeUpdate.RequestCall(); // async call, ok to call 357 // with held solarmutex 358 } 359 } 360 361 void SAL_CALL SidebarController::requestLayout() 362 { 363 SolarMutexGuard aSolarMutexGuard; 364 365 sal_Int32 nMinimalWidth = 0; 366 if (mpCurrentDeck && !mpCurrentDeck->isDisposed()) 367 { 368 mpCurrentDeck->RequestLayout(); 369 nMinimalWidth = mpCurrentDeck->GetMinimalWidth(); 370 } 371 RestrictWidth(nMinimalWidth); 372 } 373 374 void SidebarController::BroadcastPropertyChange() 375 { 376 mpParentWindow->Invalidate(InvalidateFlags::Children); 377 } 378 379 void SidebarController::NotifyResize() 380 { 381 if (!mpTabBar) 382 { 383 OSL_ASSERT(mpTabBar!=nullptr); 384 return; 385 } 386 387 vcl::Window* pParentWindow = mpTabBar->GetParent(); 388 const sal_Int32 nTabBarDefaultWidth = TabBar::GetDefaultWidth() * mpTabBar->GetDPIScaleFactor(); 389 390 const sal_Int32 nWidth (pParentWindow->GetSizePixel().Width()); 391 const sal_Int32 nHeight (pParentWindow->GetSizePixel().Height()); 392 393 mbIsDeckOpen = (nWidth > nTabBarDefaultWidth); 394 395 if (mnSavedSidebarWidth <= 0) 396 mnSavedSidebarWidth = nWidth; 397 398 bool bIsDeckVisible; 399 const bool bIsOpening (nWidth > mnWidthOnSplitterButtonDown); 400 if (bIsOpening) 401 bIsDeckVisible = nWidth >= nTabBarDefaultWidth + gnWidthOpenThreshold; 402 else 403 bIsDeckVisible = nWidth >= nTabBarDefaultWidth + gnWidthCloseThreshold; 404 mbIsDeckRequestedOpen = bIsDeckVisible; 405 UpdateCloseIndicator(!bIsDeckVisible); 406 407 if (mpCurrentDeck && !mpCurrentDeck->isDisposed()) 408 { 409 SfxSplitWindow* pSplitWindow = GetSplitWindow(); 410 WindowAlign eAlign = pSplitWindow ? pSplitWindow->GetAlign() : WindowAlign::Right; 411 tools::Long nDeckX, nTabX; 412 if (eAlign == WindowAlign::Left) // attach the Sidebar towards the left-side of screen 413 { 414 nDeckX = nTabBarDefaultWidth; 415 nTabX = 0; 416 } 417 else // attach the Sidebar towards the right-side of screen 418 { 419 nDeckX = 0; 420 nTabX = nWidth - nTabBarDefaultWidth; 421 } 422 423 // Place the deck first. 424 if (bIsDeckVisible) 425 { 426 if (comphelper::LibreOfficeKit::isActive()) 427 { 428 // We want to let the layouter use up as much of the 429 // height as necessary to make sure no scrollbar is 430 // visible. This only works when there are no greedy 431 // panes that fill up all available area. So we only 432 // use this for the PropertyDeck, which has no such 433 // panes, while most other do. This is fine, since 434 // it's the PropertyDeck that really has many panes 435 // that can collapse or expand. For others, limit 436 // the height to something sensible. 437 // tdf#130348: Add special case for ChartDeck, too. 438 const sal_Int32 nExtHeight = (msCurrentDeckId == "PropertyDeck" ? 2000 : 439 (msCurrentDeckId == "ChartDeck" ? 1200 : 600)); 440 // No TabBar in LOK (use nWidth in full). 441 mpCurrentDeck->setPosSizePixel(nDeckX, 0, nWidth, nExtHeight); 442 } 443 else 444 mpCurrentDeck->setPosSizePixel(nDeckX, 0, nWidth - nTabBarDefaultWidth, nHeight); 445 mpCurrentDeck->Show(); 446 mpCurrentDeck->RequestLayout(); 447 } 448 else 449 mpCurrentDeck->Hide(); 450 451 // Now place the tab bar. 452 mpTabBar->setPosSizePixel(nTabX, 0, nTabBarDefaultWidth, nHeight); 453 if (!comphelper::LibreOfficeKit::isActive()) 454 mpTabBar->Show(); // Don't show TabBar in LOK. 455 } 456 457 // Determine if the closer of the deck can be shown. 458 sal_Int32 nMinimalWidth = 0; 459 if (mpCurrentDeck && !mpCurrentDeck->isDisposed()) 460 { 461 VclPtr<DeckTitleBar> pTitleBar = mpCurrentDeck->GetTitleBar(); 462 if (pTitleBar && pTitleBar->IsVisible()) 463 pTitleBar->SetCloserVisible(CanModifyChildWindowWidth()); 464 nMinimalWidth = mpCurrentDeck->GetMinimalWidth(); 465 } 466 467 RestrictWidth(nMinimalWidth); 468 469 mpParentWindow->NotifyResize(); 470 } 471 472 void SidebarController::ProcessNewWidth (const sal_Int32 nNewWidth) 473 { 474 if ( ! mbIsDeckRequestedOpen) 475 return; 476 477 if (*mbIsDeckRequestedOpen) 478 { 479 // Deck became large enough to be shown. Show it. 480 mnSavedSidebarWidth = nNewWidth; 481 if (!*mbIsDeckOpen) 482 RequestOpenDeck(); 483 } 484 else 485 { 486 // Deck became too small. Close it completely. 487 // If window is wider than the tab bar then mark the deck as being visible, even when it is not. 488 // This is to trigger an adjustment of the width to the width of the tab bar. 489 mbIsDeckOpen = true; 490 RequestCloseDeck(); 491 492 if (mnWidthOnSplitterButtonDown > TabBar::GetDefaultWidth() * mpTabBar->GetDPIScaleFactor()) 493 mnSavedSidebarWidth = mnWidthOnSplitterButtonDown; 494 } 495 } 496 497 void SidebarController::SyncUpdate() 498 { 499 maPropertyChangeForwarder.Sync(); 500 maContextChangeUpdate.Sync(); 501 } 502 503 void SidebarController::UpdateConfigurations() 504 { 505 if (maCurrentContext == maRequestedContext 506 && mnRequestedForceFlags == SwitchFlag_NoForce) 507 return; 508 509 if ((maCurrentContext.msApplication != "none") && 510 !maCurrentContext.msApplication.isEmpty()) 511 { 512 mpResourceManager->SaveDecksSettings(maCurrentContext); 513 mpResourceManager->SetLastActiveDeck(maCurrentContext, msCurrentDeckId); 514 } 515 516 // get last active deck for this application on first update 517 if (!maRequestedContext.msApplication.isEmpty() && 518 (maCurrentContext.msApplication != maRequestedContext.msApplication)) 519 { 520 OUString sLastActiveDeck = mpResourceManager->GetLastActiveDeck( maRequestedContext ); 521 if (!sLastActiveDeck.isEmpty()) 522 msCurrentDeckId = sLastActiveDeck; 523 } 524 525 maCurrentContext = maRequestedContext; 526 527 mpResourceManager->InitDeckContext(GetCurrentContext()); 528 529 // Find the set of decks that could be displayed for the new context. 530 ResourceManager::DeckContextDescriptorContainer aDecks; 531 532 css::uno::Reference<css::frame::XController> xController = mxCurrentController.is() ? mxCurrentController : mxFrame->getController(); 533 534 mpResourceManager->GetMatchingDecks ( 535 aDecks, 536 maCurrentContext, 537 mbIsDocumentReadOnly, 538 xController); 539 540 maFocusManager.Clear(); 541 542 // Notify the tab bar about the updated set of decks. 543 mpTabBar->SetDecks(aDecks); 544 545 // Find the new deck. By default that is the same as the old 546 // one. If that is not set or not enabled, then choose the 547 // first enabled deck (which is PropertyDeck). 548 OUString sNewDeckId; 549 for (const auto& rDeck : aDecks) 550 { 551 if (rDeck.mbIsEnabled) 552 { 553 if (rDeck.msId == msCurrentDeckId) 554 { 555 sNewDeckId = msCurrentDeckId; 556 break; 557 } 558 else if (sNewDeckId.getLength() == 0) 559 sNewDeckId = rDeck.msId; 560 } 561 } 562 563 if (sNewDeckId.getLength() == 0) 564 { 565 // We did not find a valid deck. 566 RequestCloseDeck(); 567 return; 568 } 569 570 // Tell the tab bar to highlight the button associated 571 // with the deck. 572 mpTabBar->HighlightDeck(sNewDeckId); 573 574 std::shared_ptr<DeckDescriptor> xDescriptor = mpResourceManager->GetDeckDescriptor(sNewDeckId); 575 576 if (xDescriptor) 577 { 578 SwitchToDeck(*xDescriptor, maCurrentContext); 579 } 580 } 581 582 namespace { 583 584 void collectUIInformation(const OUString& rDeckId) 585 { 586 EventDescription aDescription; 587 aDescription.aAction = "SIDEBAR"; 588 aDescription.aParent = "MainWindow"; 589 aDescription.aParameters = {{"PANEL", rDeckId}}; 590 aDescription.aKeyWord = "CurrentApp"; 591 592 UITestLogger::getInstance().logEvent(aDescription); 593 } 594 595 } 596 597 void SidebarController::OpenThenToggleDeck ( 598 const OUString& rsDeckId) 599 { 600 SfxSplitWindow* pSplitWindow = GetSplitWindow(); 601 if ( pSplitWindow && !pSplitWindow->IsFadeIn() ) 602 // tdf#83546 Collapsed sidebar should expand first 603 pSplitWindow->FadeIn(); 604 else if ( IsDeckVisible( rsDeckId ) ) 605 { 606 if( !WasFloatingDeckClosed() ) 607 { 608 // tdf#88241 Summoning an undocked sidebar a second time should close sidebar 609 mpParentWindow->Close(); 610 return; 611 } 612 else 613 { 614 // tdf#67627 Clicking a second time on a Deck icon will close the Deck 615 RequestCloseDeck(); 616 return; 617 } 618 } 619 RequestOpenDeck(); 620 // before SwitchToDeck which may cause the rsDeckId string to be released 621 collectUIInformation(rsDeckId); 622 SwitchToDeck(rsDeckId); 623 624 // Make sure the sidebar is wide enough to fit the requested content 625 if (mpCurrentDeck && mpTabBar) 626 { 627 sal_Int32 nRequestedWidth = (mpCurrentDeck->GetMinimalWidth() + TabBar::GetDefaultWidth()) 628 * mpTabBar->GetDPIScaleFactor(); 629 if (mnSavedSidebarWidth < nRequestedWidth) 630 SetChildWindowWidth(nRequestedWidth); 631 } 632 } 633 634 void SidebarController::OpenThenSwitchToDeck ( 635 std::u16string_view rsDeckId) 636 { 637 RequestOpenDeck(); 638 SwitchToDeck(rsDeckId); 639 640 } 641 642 void SidebarController::SwitchToDefaultDeck() 643 { 644 SwitchToDeck(gsDefaultDeckId); 645 } 646 647 void SidebarController::SwitchToDeck ( 648 std::u16string_view rsDeckId) 649 { 650 if ( msCurrentDeckId != rsDeckId 651 || ! mbIsDeckOpen 652 || mnRequestedForceFlags!=SwitchFlag_NoForce) 653 { 654 std::shared_ptr<DeckDescriptor> xDeckDescriptor = mpResourceManager->GetDeckDescriptor(rsDeckId); 655 656 if (xDeckDescriptor) 657 SwitchToDeck(*xDeckDescriptor, maCurrentContext); 658 } 659 } 660 661 void SidebarController::CreateDeck(std::u16string_view rDeckId) { 662 CreateDeck(rDeckId, maCurrentContext); 663 } 664 665 void SidebarController::CreateDeck(std::u16string_view rDeckId, const Context& rContext, bool bForceCreate) 666 { 667 std::shared_ptr<DeckDescriptor> xDeckDescriptor = mpResourceManager->GetDeckDescriptor(rDeckId); 668 669 if (!xDeckDescriptor) 670 return; 671 672 VclPtr<Deck> aDeck = xDeckDescriptor->mpDeck; 673 if (!aDeck || bForceCreate) 674 { 675 if (aDeck) 676 aDeck.disposeAndClear(); 677 678 aDeck = VclPtr<Deck>::Create( 679 *xDeckDescriptor, 680 mpParentWindow, 681 [this]() { return this->RequestCloseDeck(); }); 682 } 683 xDeckDescriptor->mpDeck = aDeck; 684 CreatePanels(rDeckId, rContext); 685 } 686 687 void SidebarController::CreatePanels(std::u16string_view rDeckId, const Context& rContext) 688 { 689 std::shared_ptr<DeckDescriptor> xDeckDescriptor = mpResourceManager->GetDeckDescriptor(rDeckId); 690 691 // init panels bounded to that deck, do not wait them being displayed as may be accessed through API 692 693 VclPtr<Deck> pDeck = xDeckDescriptor->mpDeck; 694 695 ResourceManager::PanelContextDescriptorContainer aPanelContextDescriptors; 696 697 css::uno::Reference<css::frame::XController> xController = mxCurrentController.is() ? mxCurrentController : mxFrame->getController(); 698 699 mpResourceManager->GetMatchingPanels( 700 aPanelContextDescriptors, 701 rContext, 702 rDeckId, 703 xController); 704 705 // Update the panel list. 706 const sal_Int32 nNewPanelCount (aPanelContextDescriptors.size()); 707 SharedPanelContainer aNewPanels; 708 sal_Int32 nWriteIndex (0); 709 710 aNewPanels.resize(nNewPanelCount); 711 712 for (sal_Int32 nReadIndex=0; nReadIndex<nNewPanelCount; ++nReadIndex) 713 { 714 const ResourceManager::PanelContextDescriptor& rPanelContexDescriptor ( 715 aPanelContextDescriptors[nReadIndex]); 716 717 // Determine if the panel can be displayed. 718 const bool bIsPanelVisible (!mbIsDocumentReadOnly || rPanelContexDescriptor.mbShowForReadOnlyDocuments); 719 if ( ! bIsPanelVisible) 720 continue; 721 722 Panel *const pPanel(pDeck->GetPanel(rPanelContexDescriptor.msId)); 723 if (pPanel != nullptr) 724 { 725 pPanel->SetLurkMode(false); 726 aNewPanels[nWriteIndex] = pPanel; 727 pPanel->SetExpanded( rPanelContexDescriptor.mbIsInitiallyVisible ); 728 ++nWriteIndex; 729 } 730 else 731 { 732 VclPtr<Panel> aPanel = CreatePanel( 733 rPanelContexDescriptor.msId, 734 pDeck->GetPanelParentWindow(), 735 rPanelContexDescriptor.mbIsInitiallyVisible, 736 rContext, 737 pDeck); 738 if (aPanel ) 739 { 740 aNewPanels[nWriteIndex] = aPanel; 741 742 // Depending on the context we have to change the command 743 // for the "more options" dialog. 744 VclPtr<PanelTitleBar> pTitleBar = aNewPanels[nWriteIndex]->GetTitleBar(); 745 if (pTitleBar) 746 { 747 pTitleBar->SetMoreOptionsCommand( 748 rPanelContexDescriptor.msMenuCommand, 749 mxFrame, xController); 750 } 751 ++nWriteIndex; 752 } 753 } 754 } 755 756 // mpCurrentPanels - may miss stuff (?) 757 aNewPanels.resize(nWriteIndex); 758 pDeck->ResetPanels(aNewPanels); 759 } 760 761 void SidebarController::SwitchToDeck ( 762 const DeckDescriptor& rDeckDescriptor, 763 const Context& rContext) 764 { 765 if (comphelper::LibreOfficeKit::isActive()) 766 { 767 if (const SfxViewShell* pViewShell = mpViewFrame->GetViewShell()) 768 { 769 if (msCurrentDeckId != rDeckDescriptor.msId) 770 { 771 const std::string hide = UnoNameFromDeckId(msCurrentDeckId, vcl::EnumContext::Application::Impress == vcl::EnumContext::GetApplicationEnum(GetCurrentContext().msApplication)); 772 if (!hide.empty()) 773 pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_STATE_CHANGED, 774 (hide + "=false").c_str()); 775 } 776 777 const std::string show = UnoNameFromDeckId(rDeckDescriptor.msId, vcl::EnumContext::Application::Impress == vcl::EnumContext::GetApplicationEnum(GetCurrentContext().msApplication)); 778 if (!show.empty()) 779 pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_STATE_CHANGED, 780 (show + "=true").c_str()); 781 } 782 } 783 784 maFocusManager.Clear(); 785 786 const bool bForceNewDeck ((mnRequestedForceFlags&SwitchFlag_ForceNewDeck)!=0); 787 const bool bForceNewPanels ((mnRequestedForceFlags&SwitchFlag_ForceNewPanels)!=0); 788 mnRequestedForceFlags = SwitchFlag_NoForce; 789 790 if ( msCurrentDeckId != rDeckDescriptor.msId 791 || bForceNewDeck) 792 { 793 if (mpCurrentDeck) 794 mpCurrentDeck->Hide(); 795 796 msCurrentDeckId = rDeckDescriptor.msId; 797 } 798 799 // invisible with LOK, so avoid invalidations 800 if (!comphelper::LibreOfficeKit::isActive()) 801 mpTabBar->Invalidate(); 802 803 mpTabBar->HighlightDeck(msCurrentDeckId); 804 805 // Determine the panels to display in the deck. 806 ResourceManager::PanelContextDescriptorContainer aPanelContextDescriptors; 807 808 css::uno::Reference<css::frame::XController> xController = mxCurrentController.is() ? mxCurrentController : mxFrame->getController(); 809 810 mpResourceManager->GetMatchingPanels( 811 aPanelContextDescriptors, 812 rContext, 813 rDeckDescriptor.msId, 814 xController); 815 816 if (aPanelContextDescriptors.empty()) 817 { 818 // There are no panels to be displayed in the current context. 819 if (vcl::EnumContext::GetContextEnum(rContext.msContext) != vcl::EnumContext::Context::Empty) 820 { 821 // Switch to the "empty" context and try again. 822 SwitchToDeck( 823 rDeckDescriptor, 824 Context( 825 rContext.msApplication, 826 vcl::EnumContext::GetContextName(vcl::EnumContext::Context::Empty))); 827 return; 828 } 829 else 830 { 831 // This is already the "empty" context. Looks like we have 832 // to live with an empty deck. 833 } 834 } 835 836 // Provide a configuration and Deck object. 837 838 CreateDeck(rDeckDescriptor.msId, rContext, bForceNewDeck); 839 840 if (bForceNewPanels && !bForceNewDeck) // already forced if bForceNewDeck 841 CreatePanels(rDeckDescriptor.msId, rContext); 842 843 if (mpCurrentDeck && mpCurrentDeck != rDeckDescriptor.mpDeck) 844 mpCurrentDeck->Hide(); 845 mpCurrentDeck.reset(rDeckDescriptor.mpDeck); 846 847 if ( ! mpCurrentDeck) 848 return; 849 850 #ifdef DEBUG 851 // Show the context name in the deck title bar. 852 VclPtr<DeckTitleBar> pDebugTitleBar = mpCurrentDeck->GetTitleBar(); 853 if (pDebugTitleBar) 854 pDebugTitleBar->SetTitle(rDeckDescriptor.msTitle + " (" + maCurrentContext.msContext + ")"); 855 #endif 856 857 SfxSplitWindow* pSplitWindow = GetSplitWindow(); 858 sal_Int32 nTabBarDefaultWidth = TabBar::GetDefaultWidth() * mpTabBar->GetDPIScaleFactor(); 859 WindowAlign eAlign = pSplitWindow ? pSplitWindow->GetAlign() : WindowAlign::Right; 860 tools::Long nDeckX; 861 if (eAlign == WindowAlign::Left) // attach the Sidebar towards the left-side of screen 862 { 863 nDeckX = nTabBarDefaultWidth; 864 } 865 else // attach the Sidebar towards the right-side of screen 866 { 867 nDeckX = 0; 868 } 869 870 // Activate the deck and the new set of panels. 871 mpCurrentDeck->setPosSizePixel( 872 nDeckX, 873 0, 874 mpParentWindow->GetSizePixel().Width() - nTabBarDefaultWidth, 875 mpParentWindow->GetSizePixel().Height()); 876 877 mpCurrentDeck->Show(); 878 879 mpParentWindow->SetText(rDeckDescriptor.msTitle); 880 881 NotifyResize(); 882 883 // Tell the focus manager about the new panels and tab bar 884 // buttons. 885 maFocusManager.SetDeckTitle(mpCurrentDeck->GetTitleBar()); 886 maFocusManager.SetPanels(mpCurrentDeck->GetPanels()); 887 888 mpTabBar->UpdateFocusManager(maFocusManager); 889 UpdateTitleBarIcons(); 890 } 891 892 void SidebarController::notifyDeckTitle(std::u16string_view targetDeckId) 893 { 894 if (msCurrentDeckId == targetDeckId) 895 { 896 maFocusManager.SetDeckTitle(mpCurrentDeck->GetTitleBar()); 897 mpTabBar->UpdateFocusManager(maFocusManager); 898 UpdateTitleBarIcons(); 899 } 900 } 901 902 VclPtr<Panel> SidebarController::CreatePanel ( 903 std::u16string_view rsPanelId, 904 vcl::Window* pParentWindow, 905 const bool bIsInitiallyExpanded, 906 const Context& rContext, 907 const VclPtr<Deck>& pDeck) 908 { 909 std::shared_ptr<PanelDescriptor> xPanelDescriptor = mpResourceManager->GetPanelDescriptor(rsPanelId); 910 911 if (!xPanelDescriptor) 912 return nullptr; 913 914 // Create the panel which is the parent window of the UIElement. 915 VclPtr<Panel> pPanel = VclPtr<Panel>::Create( 916 *xPanelDescriptor, 917 pParentWindow, 918 bIsInitiallyExpanded, 919 [pDeck]() { return pDeck->RequestLayout(); }, 920 [this]() { return this->GetCurrentContext(); }, 921 mxFrame); 922 923 // Create the XUIElement. 924 Reference<ui::XUIElement> xUIElement (CreateUIElement( 925 pPanel->GetComponentInterface(), 926 xPanelDescriptor->msImplementationURL, 927 xPanelDescriptor->mbWantsCanvas, 928 rContext)); 929 if (xUIElement.is()) 930 { 931 // Initialize the panel and add it to the active deck. 932 pPanel->SetUIElement(xUIElement); 933 } 934 else 935 { 936 pPanel.disposeAndClear(); 937 } 938 939 return pPanel; 940 } 941 942 Reference<ui::XUIElement> SidebarController::CreateUIElement ( 943 const Reference<awt::XWindowPeer>& rxWindow, 944 const OUString& rsImplementationURL, 945 const bool bWantsCanvas, 946 const Context& rContext) 947 { 948 try 949 { 950 const Reference<XComponentContext> xComponentContext (::comphelper::getProcessComponentContext() ); 951 const Reference<ui::XUIElementFactory> xUIElementFactory = 952 ui::theUIElementFactoryManager::get( xComponentContext ); 953 954 // Create the XUIElement. 955 ::comphelper::NamedValueCollection aCreationArguments; 956 aCreationArguments.put("Frame", makeAny(mxFrame)); 957 aCreationArguments.put("ParentWindow", makeAny(rxWindow)); 958 SidebarDockingWindow* pSfxDockingWindow = mpParentWindow.get(); 959 if (pSfxDockingWindow != nullptr) 960 aCreationArguments.put("SfxBindings", makeAny(reinterpret_cast<sal_uInt64>(&pSfxDockingWindow->GetBindings()))); 961 aCreationArguments.put("Theme", Theme::GetPropertySet()); 962 aCreationArguments.put("Sidebar", makeAny(Reference<ui::XSidebar>(static_cast<ui::XSidebar*>(this)))); 963 if (bWantsCanvas) 964 { 965 Reference<rendering::XSpriteCanvas> xCanvas (VCLUnoHelper::GetWindow(rxWindow)->GetSpriteCanvas()); 966 aCreationArguments.put("Canvas", makeAny(xCanvas)); 967 } 968 969 if (mxCurrentController.is()) 970 { 971 OUString aModule = Tools::GetModuleName(mxCurrentController); 972 if (!aModule.isEmpty()) 973 { 974 aCreationArguments.put("Module", makeAny(aModule)); 975 } 976 aCreationArguments.put("Controller", makeAny(mxCurrentController)); 977 } 978 979 aCreationArguments.put("ApplicationName", makeAny(rContext.msApplication)); 980 aCreationArguments.put("ContextName", makeAny(rContext.msContext)); 981 982 Reference<ui::XUIElement> xUIElement( 983 xUIElementFactory->createUIElement( 984 rsImplementationURL, 985 aCreationArguments.getPropertyValues()), 986 UNO_SET_THROW); 987 988 return xUIElement; 989 } 990 catch(const Exception&) 991 { 992 TOOLS_WARN_EXCEPTION("sfx.sidebar", "Cannot create panel " << rsImplementationURL); 993 return nullptr; 994 } 995 } 996 997 IMPL_LINK(SidebarController, WindowEventHandler, VclWindowEvent&, rEvent, void) 998 { 999 if (rEvent.GetWindow() == mpParentWindow) 1000 { 1001 switch (rEvent.GetId()) 1002 { 1003 case VclEventId::WindowShow: 1004 case VclEventId::WindowResize: 1005 NotifyResize(); 1006 break; 1007 1008 case VclEventId::WindowDataChanged: 1009 // Force an update of deck and tab bar to reflect 1010 // changes in theme (high contrast mode). 1011 Theme::HandleDataChange(); 1012 UpdateTitleBarIcons(); 1013 mpParentWindow->Invalidate(); 1014 mnRequestedForceFlags |= SwitchFlag_ForceNewDeck | SwitchFlag_ForceNewPanels; 1015 maContextChangeUpdate.RequestCall(); 1016 break; 1017 1018 case VclEventId::ObjectDying: 1019 dispose(); 1020 break; 1021 1022 case VclEventId::WindowPaint: 1023 SAL_INFO("sfx.sidebar", "Paint"); 1024 break; 1025 1026 default: 1027 break; 1028 } 1029 } 1030 else if (rEvent.GetWindow()==mpSplitWindow && mpSplitWindow!=nullptr) 1031 { 1032 switch (rEvent.GetId()) 1033 { 1034 case VclEventId::WindowMouseButtonDown: 1035 mnWidthOnSplitterButtonDown = mpParentWindow->GetSizePixel().Width(); 1036 break; 1037 1038 case VclEventId::WindowMouseButtonUp: 1039 { 1040 ProcessNewWidth(mpParentWindow->GetSizePixel().Width()); 1041 mnWidthOnSplitterButtonDown = 0; 1042 break; 1043 } 1044 1045 case VclEventId::ObjectDying: 1046 dispose(); 1047 break; 1048 1049 default: break; 1050 } 1051 } 1052 } 1053 1054 void SidebarController::ShowPopupMenu( 1055 weld::Menu& rMainMenu, weld::Menu& rSubMenu, 1056 const ::std::vector<TabBar::DeckMenuData>& rMenuData) const 1057 { 1058 PopulatePopupMenus(rMainMenu, rSubMenu, rMenuData); 1059 rMainMenu.connect_activate(LINK(const_cast<SidebarController*>(this), SidebarController, OnMenuItemSelected)); 1060 rSubMenu.connect_activate(LINK(const_cast<SidebarController*>(this), SidebarController, OnSubMenuItemSelected)); 1061 } 1062 1063 void SidebarController::PopulatePopupMenus(weld::Menu& rMenu, weld::Menu& rCustomizationMenu, 1064 const std::vector<TabBar::DeckMenuData>& rMenuData) const 1065 { 1066 // Add one entry for every tool panel element to individually make 1067 // them visible or hide them. 1068 sal_Int32 nIndex (0); 1069 for (const auto& rItem : rMenuData) 1070 { 1071 OString sIdent("select" + OString::number(nIndex)); 1072 rMenu.insert(nIndex, OUString::fromUtf8(sIdent), rItem.msDisplayName, nullptr, nullptr, TRISTATE_FALSE); 1073 rMenu.set_active(sIdent, rItem.mbIsCurrentDeck); 1074 rMenu.set_sensitive(sIdent, rItem.mbIsEnabled && rItem.mbIsActive); 1075 1076 if (!comphelper::LibreOfficeKit::isActive()) 1077 { 1078 if (rItem.mbIsCurrentDeck) 1079 { 1080 // Don't allow the currently visible deck to be disabled. 1081 OString sSubIdent("nocustomize" + OString::number(nIndex)); 1082 rCustomizationMenu.insert(nIndex, OUString::fromUtf8(sSubIdent), rItem.msDisplayName, nullptr, nullptr, TRISTATE_FALSE); 1083 rCustomizationMenu.set_active(sSubIdent, true); 1084 } 1085 else 1086 { 1087 OString sSubIdent("customize" + OString::number(nIndex)); 1088 rCustomizationMenu.insert(nIndex, OUString::fromUtf8(sSubIdent), rItem.msDisplayName, nullptr, nullptr, TRISTATE_TRUE); 1089 rCustomizationMenu.set_active(sSubIdent, rItem.mbIsEnabled && rItem.mbIsActive); 1090 } 1091 } 1092 1093 ++nIndex; 1094 } 1095 1096 bool bHideLock = true; 1097 bool bHideUnLock = true; 1098 // LOK doesn't support docked/undocked; Sidebar is floating but rendered docked in browser. 1099 if (!comphelper::LibreOfficeKit::isActive()) 1100 { 1101 // Add entry for docking or un-docking the tool panel. 1102 if (mpParentWindow->IsFloatingMode()) 1103 bHideLock = false; 1104 else 1105 bHideUnLock = false; 1106 } 1107 rMenu.set_visible("locktaskpanel", !bHideLock); 1108 rMenu.set_visible("unlocktaskpanel", !bHideUnLock); 1109 1110 // No Restore or Customize options for LoKit. 1111 rMenu.set_visible("customization", !comphelper::LibreOfficeKit::isActive()); 1112 } 1113 1114 IMPL_LINK(SidebarController, OnMenuItemSelected, const OString&, rCurItemId, void) 1115 { 1116 if (rCurItemId == "unlocktaskpanel") 1117 { 1118 mpParentWindow->SetFloatingMode(true); 1119 if (mpParentWindow->IsFloatingMode()) 1120 mpParentWindow->ToTop(ToTopFlags::GrabFocusOnly); 1121 } 1122 else if (rCurItemId == "locktaskpanel") 1123 { 1124 mpParentWindow->SetFloatingMode(false); 1125 } 1126 else if (rCurItemId == "hidesidebar") 1127 { 1128 if (!comphelper::LibreOfficeKit::isActive()) 1129 { 1130 const util::URL aURL(Tools::GetURL(".uno:Sidebar")); 1131 Reference<frame::XDispatch> xDispatch(Tools::GetDispatch(mxFrame, aURL)); 1132 if (xDispatch.is()) 1133 xDispatch->dispatch(aURL, Sequence<beans::PropertyValue>()); 1134 } 1135 else 1136 { 1137 // In LOK we don't really destroy the sidebar when "closing"; 1138 // we simply hide it. This is because recreating it is problematic 1139 // See notes in SidebarDockingWindow::NotifyResize(). 1140 RequestCloseDeck(); 1141 } 1142 } 1143 else 1144 { 1145 try 1146 { 1147 OString sNumber; 1148 if (rCurItemId.startsWith("select", &sNumber)) 1149 { 1150 RequestOpenDeck(); 1151 SwitchToDeck(mpTabBar->GetDeckIdForIndex(sNumber.toInt32())); 1152 } 1153 mpParentWindow->GrabFocusToDocument(); 1154 } 1155 catch (RuntimeException&) 1156 { 1157 } 1158 } 1159 } 1160 1161 IMPL_LINK(SidebarController, OnSubMenuItemSelected, const OString&, rCurItemId, void) 1162 { 1163 if (rCurItemId == "restoredefault") 1164 mpTabBar->RestoreHideFlags(); 1165 else 1166 { 1167 try 1168 { 1169 OString sNumber; 1170 if (rCurItemId.startsWith("customize", &sNumber)) 1171 { 1172 mpTabBar->ToggleHideFlag(sNumber.toInt32()); 1173 1174 // Find the set of decks that could be displayed for the new context. 1175 ResourceManager::DeckContextDescriptorContainer aDecks; 1176 mpResourceManager->GetMatchingDecks ( 1177 aDecks, 1178 GetCurrentContext(), 1179 IsDocumentReadOnly(), 1180 mxFrame->getController()); 1181 // Notify the tab bar about the updated set of decks. 1182 maFocusManager.Clear(); 1183 mpTabBar->SetDecks(aDecks); 1184 mpTabBar->HighlightDeck(mpCurrentDeck->GetId()); 1185 mpTabBar->UpdateFocusManager(maFocusManager); 1186 } 1187 mpParentWindow->GrabFocusToDocument(); 1188 } 1189 catch (RuntimeException&) 1190 { 1191 } 1192 } 1193 } 1194 1195 1196 void SidebarController::RequestCloseDeck() 1197 { 1198 if (comphelper::LibreOfficeKit::isActive() && mpCurrentDeck) 1199 { 1200 const vcl::ILibreOfficeKitNotifier* pNotifier = mpCurrentDeck->GetLOKNotifier(); 1201 auto pMobileNotifier = SfxViewShell::Current(); 1202 const SfxViewShell* pViewShell = SfxViewShell::Current(); 1203 if (pMobileNotifier && pViewShell && pViewShell->isLOKMobilePhone()) 1204 { 1205 // Mobile phone. 1206 std::stringstream aStream; 1207 boost::property_tree::ptree aTree; 1208 aTree.put("id", mpParentWindow->get_id()); // TODO could be missing - sort out 1209 aTree.put("type", "dockingwindow"); 1210 aTree.put("text", mpParentWindow->GetText()); 1211 aTree.put("enabled", false); 1212 boost::property_tree::write_json(aStream, aTree); 1213 const std::string message = aStream.str(); 1214 pMobileNotifier->libreOfficeKitViewCallback(LOK_CALLBACK_JSDIALOG, message.c_str()); 1215 } 1216 else if (pNotifier) 1217 pNotifier->notifyWindow(mpCurrentDeck->GetLOKWindowId(), "close"); 1218 } 1219 1220 mbIsDeckRequestedOpen = false; 1221 UpdateDeckOpenState(); 1222 1223 if (!mpCurrentDeck) 1224 mpTabBar->RemoveDeckHighlight(); 1225 } 1226 1227 void SidebarController::RequestOpenDeck() 1228 { 1229 SfxSplitWindow* pSplitWindow = GetSplitWindow(); 1230 if ( pSplitWindow && !pSplitWindow->IsFadeIn() ) 1231 // tdf#83546 Collapsed sidebar should expand first 1232 pSplitWindow->FadeIn(); 1233 1234 mbIsDeckRequestedOpen = true; 1235 UpdateDeckOpenState(); 1236 } 1237 1238 bool SidebarController::IsDeckOpen(const sal_Int32 nIndex) 1239 { 1240 if (nIndex >= 0) 1241 { 1242 OUString asDeckId(mpTabBar->GetDeckIdForIndex(nIndex)); 1243 return IsDeckVisible(asDeckId); 1244 } 1245 return mbIsDeckOpen && *mbIsDeckOpen; 1246 } 1247 1248 bool SidebarController::IsDeckVisible(std::u16string_view rsDeckId) 1249 { 1250 return mbIsDeckOpen && *mbIsDeckOpen && msCurrentDeckId == rsDeckId; 1251 } 1252 1253 void SidebarController::UpdateDeckOpenState() 1254 { 1255 if ( ! mbIsDeckRequestedOpen) 1256 // No state requested. 1257 return; 1258 1259 const sal_Int32 nTabBarDefaultWidth = TabBar::GetDefaultWidth() * mpTabBar->GetDPIScaleFactor(); 1260 1261 // Update (change) the open state when it either has not yet been initialized 1262 // or when its value differs from the requested state. 1263 if ( mbIsDeckOpen && *mbIsDeckOpen == *mbIsDeckRequestedOpen ) 1264 return; 1265 1266 if (*mbIsDeckRequestedOpen) 1267 { 1268 if (!mpParentWindow->IsFloatingMode()) 1269 { 1270 if (mnSavedSidebarWidth <= nTabBarDefaultWidth) 1271 SetChildWindowWidth(SidebarChildWindow::GetDefaultWidth(mpParentWindow)); 1272 else 1273 SetChildWindowWidth(mnSavedSidebarWidth); 1274 } 1275 else 1276 { 1277 // Show the Deck by resizing back to the original size (before hiding). 1278 Size aNewSize(mpParentWindow->GetFloatingWindow()->GetSizePixel()); 1279 Point aNewPos(mpParentWindow->GetFloatingWindow()->GetPosPixel()); 1280 1281 aNewPos.setX(aNewPos.X() - mnSavedSidebarWidth + nTabBarDefaultWidth); 1282 aNewSize.setWidth(mnSavedSidebarWidth); 1283 1284 mpParentWindow->GetFloatingWindow()->SetPosSizePixel(aNewPos, aNewSize); 1285 1286 if (comphelper::LibreOfficeKit::isActive()) 1287 { 1288 // Sidebar wide enough to render the menu; enable it. 1289 mpTabBar->EnableMenuButton(true); 1290 1291 if (const SfxViewShell* pViewShell = mpViewFrame->GetViewShell()) 1292 { 1293 const std::string uno = UnoNameFromDeckId(msCurrentDeckId, vcl::EnumContext::Application::Impress == vcl::EnumContext::GetApplicationEnum(GetCurrentContext().msApplication)); 1294 if (!uno.empty()) 1295 pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_STATE_CHANGED, 1296 (uno + "=true").c_str()); 1297 } 1298 } 1299 } 1300 } 1301 else 1302 { 1303 if ( ! mpParentWindow->IsFloatingMode()) 1304 mnSavedSidebarWidth = SetChildWindowWidth(nTabBarDefaultWidth); 1305 else 1306 { 1307 // Hide the Deck by resizing to the width of the TabBar. 1308 Size aNewSize(mpParentWindow->GetFloatingWindow()->GetSizePixel()); 1309 Point aNewPos(mpParentWindow->GetFloatingWindow()->GetPosPixel()); 1310 mnSavedSidebarWidth = aNewSize.Width(); // Save the current width to restore. 1311 1312 aNewPos.setX(aNewPos.X() + mnSavedSidebarWidth - nTabBarDefaultWidth); 1313 if (comphelper::LibreOfficeKit::isActive()) 1314 { 1315 // Hide by collapsing, otherwise with 0x0 the client might expect 1316 // to get valid dimensions on rendering and not collapse the sidebar. 1317 aNewSize.setWidth(1); 1318 } 1319 else 1320 aNewSize.setWidth(nTabBarDefaultWidth); 1321 1322 mpParentWindow->GetFloatingWindow()->SetPosSizePixel(aNewPos, aNewSize); 1323 1324 if (comphelper::LibreOfficeKit::isActive()) 1325 { 1326 // Sidebar too narrow to render the menu; disable it. 1327 mpTabBar->EnableMenuButton(false); 1328 1329 if (const SfxViewShell* pViewShell = mpViewFrame->GetViewShell()) 1330 { 1331 const std::string uno = UnoNameFromDeckId(msCurrentDeckId, vcl::EnumContext::Application::Impress == vcl::EnumContext::GetApplicationEnum(GetCurrentContext().msApplication)); 1332 if (!uno.empty()) 1333 pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_STATE_CHANGED, 1334 (uno + "=false").c_str()); 1335 } 1336 } 1337 } 1338 1339 if (mnWidthOnSplitterButtonDown > nTabBarDefaultWidth) 1340 mnSavedSidebarWidth = mnWidthOnSplitterButtonDown; 1341 mpParentWindow->SetStyle(mpParentWindow->GetStyle() & ~WB_SIZEABLE); 1342 } 1343 1344 mbIsDeckOpen = *mbIsDeckRequestedOpen; 1345 if (*mbIsDeckOpen && mpCurrentDeck) 1346 mpCurrentDeck->Show(*mbIsDeckOpen); 1347 NotifyResize(); 1348 } 1349 1350 bool SidebarController::CanModifyChildWindowWidth() 1351 { 1352 SfxSplitWindow* pSplitWindow = GetSplitWindow(); 1353 if (pSplitWindow == nullptr) 1354 return false; 1355 1356 sal_uInt16 nRow (0xffff); 1357 sal_uInt16 nColumn (0xffff); 1358 if (pSplitWindow->GetWindowPos(mpParentWindow, nColumn, nRow)) 1359 { 1360 sal_uInt16 nRowCount (pSplitWindow->GetWindowCount(nColumn)); 1361 return nRowCount==1; 1362 } 1363 else 1364 return false; 1365 } 1366 1367 sal_Int32 SidebarController::SetChildWindowWidth (const sal_Int32 nNewWidth) 1368 { 1369 SfxSplitWindow* pSplitWindow = GetSplitWindow(); 1370 if (pSplitWindow == nullptr) 1371 return 0; 1372 1373 sal_uInt16 nRow (0xffff); 1374 sal_uInt16 nColumn (0xffff); 1375 pSplitWindow->GetWindowPos(mpParentWindow, nColumn, nRow); 1376 const tools::Long nColumnWidth (pSplitWindow->GetLineSize(nColumn)); 1377 1378 vcl::Window* pWindow = mpParentWindow; 1379 const Size aWindowSize (pWindow->GetSizePixel()); 1380 1381 pSplitWindow->MoveWindow( 1382 mpParentWindow, 1383 Size(nNewWidth, aWindowSize.Height()), 1384 nColumn, 1385 nRow, 1386 false); 1387 static_cast<SplitWindow*>(pSplitWindow)->Split(); 1388 1389 return static_cast<sal_Int32>(nColumnWidth); 1390 } 1391 1392 void SidebarController::RestrictWidth (sal_Int32 nWidth) 1393 { 1394 SfxSplitWindow* pSplitWindow = GetSplitWindow(); 1395 if (pSplitWindow != nullptr) 1396 { 1397 const sal_uInt16 nId (pSplitWindow->GetItemId(mpParentWindow.get())); 1398 const sal_uInt16 nSetId (pSplitWindow->GetSet(nId)); 1399 const sal_Int32 nRequestedWidth 1400 = (TabBar::GetDefaultWidth() + nWidth) * mpTabBar->GetDPIScaleFactor(); 1401 1402 pSplitWindow->SetItemSizeRange( 1403 nSetId, 1404 Range(nRequestedWidth, 1405 getMaximumWidth() * mpTabBar->GetDPIScaleFactor())); 1406 } 1407 } 1408 1409 SfxSplitWindow* SidebarController::GetSplitWindow() 1410 { 1411 if (mpParentWindow != nullptr) 1412 { 1413 SfxSplitWindow* pSplitWindow = dynamic_cast<SfxSplitWindow*>(mpParentWindow->GetParent()); 1414 if (pSplitWindow != mpSplitWindow) 1415 { 1416 if (mpSplitWindow != nullptr) 1417 mpSplitWindow->RemoveEventListener(LINK(this, SidebarController, WindowEventHandler)); 1418 1419 mpSplitWindow = pSplitWindow; 1420 1421 if (mpSplitWindow != nullptr) 1422 mpSplitWindow->AddEventListener(LINK(this, SidebarController, WindowEventHandler)); 1423 } 1424 return mpSplitWindow; 1425 } 1426 else 1427 return nullptr; 1428 } 1429 1430 void SidebarController::UpdateCloseIndicator (const bool bCloseAfterDrag) 1431 { 1432 if (mpParentWindow == nullptr) 1433 return; 1434 1435 if (bCloseAfterDrag) 1436 { 1437 // Make sure that the indicator exists. 1438 if ( ! mpCloseIndicator) 1439 { 1440 mpCloseIndicator.reset(VclPtr<FixedImage>::Create(mpParentWindow)); 1441 FixedImage* pFixedImage = static_cast<FixedImage*>(mpCloseIndicator.get()); 1442 const Image aImage (Theme::GetImage(Theme::Image_CloseIndicator)); 1443 pFixedImage->SetImage(aImage); 1444 pFixedImage->SetSizePixel(aImage.GetSizePixel()); 1445 pFixedImage->SetBackground(Theme::GetColor(Theme::Color_DeckBackground)); 1446 } 1447 1448 // Place and show the indicator. 1449 const Size aWindowSize (mpParentWindow->GetSizePixel()); 1450 const Size aImageSize (mpCloseIndicator->GetSizePixel()); 1451 mpCloseIndicator->SetPosPixel( 1452 Point( 1453 aWindowSize.Width() - TabBar::GetDefaultWidth() * mpTabBar->GetDPIScaleFactor() - aImageSize.Width(), 1454 (aWindowSize.Height() - aImageSize.Height())/2)); 1455 mpCloseIndicator->Show(); 1456 } 1457 else 1458 { 1459 // Hide but don't delete the indicator. 1460 if (mpCloseIndicator) 1461 mpCloseIndicator->Hide(); 1462 } 1463 } 1464 1465 void SidebarController::UpdateTitleBarIcons() 1466 { 1467 if ( ! mpCurrentDeck) 1468 return; 1469 1470 const bool bIsHighContrastModeActive (Theme::IsHighContrastMode()); 1471 1472 const ResourceManager& rResourceManager = *mpResourceManager; 1473 1474 // Update the deck icon. 1475 std::shared_ptr<DeckDescriptor> xDeckDescriptor = rResourceManager.GetDeckDescriptor(mpCurrentDeck->GetId()); 1476 if (xDeckDescriptor && mpCurrentDeck->GetTitleBar()) 1477 { 1478 const OUString sIconURL( 1479 bIsHighContrastModeActive 1480 ? xDeckDescriptor->msHighContrastTitleBarIconURL 1481 : xDeckDescriptor->msTitleBarIconURL); 1482 mpCurrentDeck->GetTitleBar()->SetIcon(Tools::GetImage(sIconURL, mxFrame)); 1483 } 1484 1485 // Update the panel icons. 1486 const SharedPanelContainer& rPanels (mpCurrentDeck->GetPanels()); 1487 for (const auto& rxPanel : rPanels) 1488 { 1489 if ( ! rxPanel) 1490 continue; 1491 if (!rxPanel->GetTitleBar()) 1492 continue; 1493 std::shared_ptr<PanelDescriptor> xPanelDescriptor = rResourceManager.GetPanelDescriptor(rxPanel->GetId()); 1494 if (!xPanelDescriptor) 1495 continue; 1496 const OUString sIconURL ( 1497 bIsHighContrastModeActive 1498 ? xPanelDescriptor->msHighContrastTitleBarIconURL 1499 : xPanelDescriptor->msTitleBarIconURL); 1500 rxPanel->GetTitleBar()->SetIcon(Tools::GetImage(sIconURL, mxFrame)); 1501 } 1502 } 1503 1504 void SidebarController::ShowPanel (const Panel& rPanel) 1505 { 1506 if (mpCurrentDeck) 1507 { 1508 if (!IsDeckOpen()) 1509 RequestOpenDeck(); 1510 mpCurrentDeck->ShowPanel(rPanel); 1511 } 1512 } 1513 1514 ResourceManager::DeckContextDescriptorContainer SidebarController::GetMatchingDecks() 1515 { 1516 ResourceManager::DeckContextDescriptorContainer aDecks; 1517 mpResourceManager->GetMatchingDecks (aDecks, 1518 GetCurrentContext(), 1519 IsDocumentReadOnly(), 1520 mxFrame->getController()); 1521 return aDecks; 1522 } 1523 1524 ResourceManager::PanelContextDescriptorContainer SidebarController::GetMatchingPanels(std::u16string_view rDeckId) 1525 { 1526 ResourceManager::PanelContextDescriptorContainer aPanels; 1527 1528 mpResourceManager->GetMatchingPanels(aPanels, 1529 GetCurrentContext(), 1530 rDeckId, 1531 mxFrame->getController()); 1532 return aPanels; 1533 } 1534 1535 void SidebarController::updateModel(const css::uno::Reference<css::frame::XModel>& xModel) 1536 { 1537 mpResourceManager->UpdateModel(xModel); 1538 } 1539 1540 void SidebarController::FadeOut() 1541 { 1542 if (mpSplitWindow) 1543 mpSplitWindow->FadeOut(); 1544 } 1545 1546 void SidebarController::FadeIn() 1547 { 1548 if (mpSplitWindow) 1549 mpSplitWindow->FadeIn(); 1550 } 1551 1552 tools::Rectangle SidebarController::GetDeckDragArea() const 1553 { 1554 tools::Rectangle aRect; 1555 if (mpCurrentDeck) 1556 { 1557 VclPtr<DeckTitleBar> pTitleBar(mpCurrentDeck->GetTitleBar()); 1558 1559 if (pTitleBar) 1560 { 1561 aRect = pTitleBar->GetDragArea(); 1562 } 1563 } 1564 return aRect; 1565 } 1566 1567 void SidebarController::frameAction(const css::frame::FrameActionEvent& rEvent) 1568 { 1569 if (rEvent.Frame == mxFrame) 1570 { 1571 if (rEvent.Action == css::frame::FrameAction_COMPONENT_DETACHING) 1572 unregisterSidebarForFrame(this, mxFrame->getController()); 1573 else if (rEvent.Action == css::frame::FrameAction_COMPONENT_REATTACHED) 1574 registerSidebarForFrame(this, mxFrame->getController()); 1575 } 1576 } 1577 1578 void SidebarController::saveDeckState() 1579 { 1580 // Impress shutdown : context (frame) is disposed before sidebar disposing 1581 // calc writer : context (frame) is disposed after sidebar disposing 1582 // so need to test if GetCurrentContext is still valid regarding msApplication 1583 if (GetCurrentContext().msApplication != "none") 1584 { 1585 mpResourceManager->SaveDecksSettings(GetCurrentContext()); 1586 mpResourceManager->SaveLastActiveDeck(GetCurrentContext(), msCurrentDeckId); 1587 } 1588 } 1589 1590 bool SidebarController::hasChartContextCurrently() const 1591 { 1592 return GetCurrentContext().msApplication == "com.sun.star.chart2.ChartDocument"; 1593 } 1594 1595 sfx2::sidebar::SidebarController* SidebarController::GetSidebarControllerForView(SfxViewShell* pViewShell) 1596 { 1597 if (!pViewShell) 1598 return nullptr; 1599 1600 Reference<css::frame::XController2> xController(pViewShell->GetController(), UNO_QUERY); 1601 if (!xController.is()) 1602 return nullptr; 1603 1604 // Make sure there is a model behind the controller, otherwise getSidebar() can crash. 1605 if (!xController->getModel().is()) 1606 return nullptr; 1607 1608 Reference<css::ui::XSidebarProvider> xSidebarProvider = xController->getSidebar(); 1609 if (!xSidebarProvider.is()) 1610 return nullptr; 1611 1612 Reference<css::ui::XSidebar> xSidebar = xSidebarProvider->getSidebar(); 1613 if (!xSidebar.is()) 1614 return nullptr; 1615 1616 return dynamic_cast<sfx2::sidebar::SidebarController*>(xSidebar.get()); 1617 } 1618 1619 } // end of namespace sfx2::sidebar 1620 1621 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ 1622
