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