summaryrefslogtreecommitdiffstats
path: root/sfx2/source/sidebar
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 05:54:39 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 05:54:39 +0000
commit267c6f2ac71f92999e969232431ba04678e7437e (patch)
tree358c9467650e1d0a1d7227a21dac2e3d08b622b2 /sfx2/source/sidebar
parentInitial commit. (diff)
downloadlibreoffice-267c6f2ac71f92999e969232431ba04678e7437e.tar.xz
libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.zip
Adding upstream version 4:24.2.0.upstream/4%24.2.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'sfx2/source/sidebar')
-rw-r--r--sfx2/source/sidebar/AsynchronousCall.cxx72
-rw-r--r--sfx2/source/sidebar/Context.cxx79
-rw-r--r--sfx2/source/sidebar/ContextChangeBroadcaster.cxx127
-rw-r--r--sfx2/source/sidebar/ContextList.cxx103
-rw-r--r--sfx2/source/sidebar/ControllerFactory.cxx251
-rw-r--r--sfx2/source/sidebar/ControllerItem.cxx71
-rw-r--r--sfx2/source/sidebar/Deck.cxx304
-rw-r--r--sfx2/source/sidebar/DeckDescriptor.cxx54
-rw-r--r--sfx2/source/sidebar/DeckLayouter.cxx549
-rw-r--r--sfx2/source/sidebar/DeckTitleBar.cxx137
-rw-r--r--sfx2/source/sidebar/FocusManager.cxx478
-rw-r--r--sfx2/source/sidebar/IContextChangeReceiver.cxx29
-rw-r--r--sfx2/source/sidebar/ILayoutableWindow.cxx29
-rw-r--r--sfx2/source/sidebar/Panel.cxx241
-rw-r--r--sfx2/source/sidebar/PanelDescriptor.cxx57
-rw-r--r--sfx2/source/sidebar/PanelLayout.cxx86
-rw-r--r--sfx2/source/sidebar/PanelTitleBar.cxx123
-rw-r--r--sfx2/source/sidebar/ResourceManager.cxx806
-rw-r--r--sfx2/source/sidebar/Sidebar.cxx128
-rw-r--r--sfx2/source/sidebar/SidebarChildWindow.cxx95
-rw-r--r--sfx2/source/sidebar/SidebarController.cxx1651
-rw-r--r--sfx2/source/sidebar/SidebarDockingWindow.cxx216
-rw-r--r--sfx2/source/sidebar/SidebarModelUpdate.cxx17
-rw-r--r--sfx2/source/sidebar/SidebarPanelBase.cxx198
-rw-r--r--sfx2/source/sidebar/SidebarToolBox.cxx343
-rw-r--r--sfx2/source/sidebar/TabBar.cxx392
-rw-r--r--sfx2/source/sidebar/Theme.cxx675
-rw-r--r--sfx2/source/sidebar/TitleBar.cxx91
-rw-r--r--sfx2/source/sidebar/Tools.cxx113
-rw-r--r--sfx2/source/sidebar/UnoDeck.cxx280
-rw-r--r--sfx2/source/sidebar/UnoDecks.cxx144
-rw-r--r--sfx2/source/sidebar/UnoPanel.cxx296
-rw-r--r--sfx2/source/sidebar/UnoPanels.cxx154
-rw-r--r--sfx2/source/sidebar/UnoSidebar.cxx96
-rw-r--r--sfx2/source/sidebar/uiobject.cxx61
-rw-r--r--sfx2/source/sidebar/uiobject.hxx32
36 files changed, 8578 insertions, 0 deletions
diff --git a/sfx2/source/sidebar/AsynchronousCall.cxx b/sfx2/source/sidebar/AsynchronousCall.cxx
new file mode 100644
index 0000000000..fdb76d63d1
--- /dev/null
+++ b/sfx2/source/sidebar/AsynchronousCall.cxx
@@ -0,0 +1,72 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sfx2/sidebar/AsynchronousCall.hxx>
+#include <utility>
+#include <vcl/svapp.hxx>
+
+namespace sfx2::sidebar {
+
+AsynchronousCall::AsynchronousCall (Action aAction)
+ : maAction(std::move(aAction)),
+ mnCallId(nullptr)
+{
+}
+
+AsynchronousCall::~AsynchronousCall()
+{
+ CancelRequest();
+}
+
+void AsynchronousCall::RequestCall()
+{
+ if (mnCallId == nullptr)
+ {
+ Link<void*,void> aLink (LINK(this, AsynchronousCall, HandleUserCall));
+ mnCallId = Application::PostUserEvent(aLink);
+ }
+}
+
+void AsynchronousCall::CancelRequest()
+{
+ if (mnCallId != nullptr)
+ {
+ Application::RemoveUserEvent(mnCallId);
+ mnCallId = nullptr;
+ }
+}
+
+void AsynchronousCall::Sync()
+{
+ if (mnCallId != nullptr) {
+ maAction();
+ CancelRequest();
+ }
+}
+
+IMPL_LINK_NOARG(AsynchronousCall, HandleUserCall, void*, void )
+{
+ mnCallId = nullptr;
+ if (maAction)
+ maAction();
+}
+
+} // end of namespace sfx2::sidebar
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/sidebar/Context.cxx b/sfx2/source/sidebar/Context.cxx
new file mode 100644
index 0000000000..17bc54ba94
--- /dev/null
+++ b/sfx2/source/sidebar/Context.cxx
@@ -0,0 +1,79 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#include <sfx2/sidebar/Context.hxx>
+#include <utility>
+
+
+constexpr OUString AnyApplicationName = u"any"_ustr;
+constexpr OUString AnyContextName = u"any"_ustr;
+
+namespace sfx2::sidebar {
+
+const sal_Int32 Context::NoMatch = 4;
+const sal_Int32 Context::ApplicationWildcardMatch = 1;
+const sal_Int32 Context::ContextWildcardMatch = 2;
+const sal_Int32 Context::OptimalMatch = 0; // Neither application nor context name is "any".
+
+Context::Context()
+ : msApplication(AnyApplicationName),
+ msContext(AnyContextName)
+{
+}
+
+Context::Context (
+ OUString sApplication,
+ OUString sContext)
+ : msApplication(std::move(sApplication)),
+ msContext(std::move(sContext))
+{
+}
+
+sal_Int32 Context::EvaluateMatch (
+ const Context& rOther) const
+{
+ const bool bApplicationNameIsAny (rOther.msApplication == AnyApplicationName);
+ if (rOther.msApplication == msApplication || bApplicationNameIsAny)
+ {
+ // Application name matches.
+ const bool bContextNameIsAny (rOther.msContext == AnyContextName);
+ if (rOther.msContext == msContext || bContextNameIsAny)
+ {
+ // Context name matches.
+ return (bApplicationNameIsAny ? ApplicationWildcardMatch : 0)
+ + (bContextNameIsAny ? ContextWildcardMatch : 0);
+ }
+ }
+ return NoMatch;
+}
+
+bool Context::operator== (const Context& rOther) const
+{
+ return msApplication == rOther.msApplication
+ && msContext == rOther.msContext;
+}
+
+bool Context::operator!= (const Context& rOther) const
+{
+ return ( msApplication != rOther.msApplication)
+ || ( msContext != rOther.msContext);
+}
+
+} // end of namespace sfx2::sidebar
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/sidebar/ContextChangeBroadcaster.cxx b/sfx2/source/sidebar/ContextChangeBroadcaster.cxx
new file mode 100644
index 0000000000..4c1cf2c16a
--- /dev/null
+++ b/sfx2/source/sidebar/ContextChangeBroadcaster.cxx
@@ -0,0 +1,127 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#include <sidebar/ContextChangeBroadcaster.hxx>
+#include <vcl/EnumContext.hxx>
+#include <com/sun/star/ui/ContextChangeEventObject.hpp>
+#include <com/sun/star/ui/ContextChangeEventMultiplexer.hpp>
+#include <com/sun/star/frame/ModuleManager.hpp>
+#include <osl/diagnose.h>
+#include <comphelper/diagnose_ex.hxx>
+#include <comphelper/lok.hxx>
+#include <comphelper/processfactory.hxx>
+#include <sfx2/lokhelper.hxx>
+#include <sfx2/viewsh.hxx>
+
+using namespace css;
+using namespace css::uno;
+
+namespace sfx2::sidebar {
+
+ContextChangeBroadcaster::ContextChangeBroadcaster()
+ : mbIsBroadcasterEnabled(true)
+{
+}
+
+ContextChangeBroadcaster::~ContextChangeBroadcaster()
+{
+}
+
+void ContextChangeBroadcaster::Initialize (const OUString& rsContextName)
+{
+ msContextName = rsContextName;
+}
+
+void ContextChangeBroadcaster::Activate (const css::uno::Reference<css::frame::XFrame>& rxFrame)
+{
+ if (msContextName.getLength() > 0)
+ BroadcastContextChange(rxFrame, GetModuleName(rxFrame), msContextName);
+}
+
+void ContextChangeBroadcaster::Deactivate (const css::uno::Reference<css::frame::XFrame>& rxFrame)
+{
+ if (msContextName.getLength() > 0 && !comphelper::LibreOfficeKit::isActive())
+ {
+ BroadcastContextChange(rxFrame, GetModuleName(rxFrame),
+ vcl::EnumContext::GetContextName(vcl::EnumContext::Context::Default));
+ }
+}
+
+bool ContextChangeBroadcaster::SetBroadcasterEnabled (const bool bIsEnabled)
+{
+ const bool bWasEnabled (mbIsBroadcasterEnabled);
+ mbIsBroadcasterEnabled = bIsEnabled;
+ return bWasEnabled;
+}
+
+void ContextChangeBroadcaster::BroadcastContextChange (
+ const css::uno::Reference<css::frame::XFrame>& rxFrame,
+ const OUString& rsModuleName,
+ const OUString& rsContextName)
+{
+ if ( ! mbIsBroadcasterEnabled)
+ return;
+
+ if (rsContextName.getLength() == 0)
+ return;
+
+ if ( ! rxFrame.is() || ! rxFrame->getController().is())
+ {
+ // Frame is (probably) being deleted. Broadcasting context
+ // changes is not necessary anymore.
+ return;
+ }
+
+ const css::ui::ContextChangeEventObject aEvent(
+ rxFrame->getController(),
+ rsModuleName,
+ rsContextName);
+
+ // notify the LOK too
+ if (comphelper::LibreOfficeKit::isActive())
+ {
+ SfxLokHelper::notifyContextChange(aEvent);
+ }
+
+ css::uno::Reference<css::ui::XContextChangeEventMultiplexer> xMultiplexer (
+ css::ui::ContextChangeEventMultiplexer::get(
+ ::comphelper::getProcessComponentContext()));
+ if (xMultiplexer.is())
+ xMultiplexer->broadcastContextChangeEvent(aEvent, rxFrame->getController());
+}
+
+OUString ContextChangeBroadcaster::GetModuleName (const css::uno::Reference<css::frame::XFrame>& rxFrame)
+{
+ if ( ! rxFrame.is() || ! rxFrame->getController().is())
+ return OUString();
+ try
+ {
+ const Reference<XComponentContext> xContext (::comphelper::getProcessComponentContext() );
+ const Reference<frame::XModuleManager> xModuleManager = frame::ModuleManager::create( xContext );
+ return xModuleManager->identify(rxFrame);
+ }
+ catch (const Exception&)
+ {
+ TOOLS_WARN_EXCEPTION("sfx.sidebar", "can not determine module name");
+ }
+ return OUString();
+}
+
+} // end of namespace ::sfx2::sidebar
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/sidebar/ContextList.cxx b/sfx2/source/sidebar/ContextList.cxx
new file mode 100644
index 0000000000..9f17164471
--- /dev/null
+++ b/sfx2/source/sidebar/ContextList.cxx
@@ -0,0 +1,103 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#include <sidebar/ContextList.hxx>
+#include <sfx2/sidebar/Context.hxx>
+
+namespace sfx2::sidebar {
+
+ContextList::ContextList()
+{
+}
+
+const ContextList::Entry* ContextList::GetMatch (const Context& rContext) const
+{
+ const ::std::vector<Entry>::const_iterator iEntry = FindBestMatch(rContext);
+ if (iEntry != maEntries.end())
+ return &*iEntry;
+ else
+ return nullptr;
+}
+
+ContextList::Entry* ContextList::GetMatch (const Context& rContext)
+{
+ const ::std::vector<Entry>::const_iterator iEntry = FindBestMatch(rContext);
+ if (iEntry != maEntries.end())
+ return const_cast<Entry*>(&*iEntry);
+ else
+ return nullptr;
+}
+
+::std::vector<ContextList::Entry>::const_iterator ContextList::FindBestMatch (const Context& rContext) const
+{
+ sal_Int32 nBestMatch (Context::NoMatch);
+ ::std::vector<Entry>::const_iterator iBestMatch (maEntries.end());
+
+ for (::std::vector<Entry>::const_iterator
+ iEntry(maEntries.begin()),
+ iEnd(maEntries.end());
+ iEntry!=iEnd;
+ ++iEntry)
+ {
+ const sal_Int32 nMatch (rContext.EvaluateMatch(iEntry->maContext));
+ if (nMatch < nBestMatch)
+ {
+ nBestMatch = nMatch;
+ iBestMatch = iEntry;
+ }
+ if (nBestMatch == Context::OptimalMatch)
+ return iEntry;
+ }
+
+ return iBestMatch;
+}
+
+void ContextList::AddContextDescription (
+ const Context& rContext,
+ const bool bIsInitiallyVisible,
+ const OUString& rsMenuCommand)
+{
+ maEntries.emplace_back();
+ maEntries.back().maContext = rContext;
+ maEntries.back().mbIsInitiallyVisible = bIsInitiallyVisible;
+ maEntries.back().msMenuCommand = rsMenuCommand;
+}
+
+void ContextList::ToggleVisibilityForContext( const Context &rContext, const bool bVisible)
+{
+ ContextList::Entry *pEntry = GetMatch( rContext );
+
+ if ( !pEntry )
+ return;
+
+ const sal_Int32 nMatch( rContext.EvaluateMatch( pEntry->maContext ) );
+
+ if ( nMatch & Context::ApplicationWildcardMatch )
+ {
+ // Create a separate context list entry for this app if 'any'
+ // is the only context that matches. Toggling the visibility
+ // for 'any' would change it for all apps, not just this one
+ AddContextDescription( rContext, bVisible, OUString() );
+ }
+ else if ( nMatch == Context::OptimalMatch || nMatch == Context::ContextWildcardMatch )
+ pEntry->mbIsInitiallyVisible = bVisible;
+}
+
+} // end of namespace sfx2::sidebar
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/sidebar/ControllerFactory.cxx b/sfx2/source/sidebar/ControllerFactory.cxx
new file mode 100644
index 0000000000..cc453c53cd
--- /dev/null
+++ b/sfx2/source/sidebar/ControllerFactory.cxx
@@ -0,0 +1,251 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sidebar/ControllerFactory.hxx>
+#include <sidebar/Tools.hxx>
+
+#include <com/sun/star/frame/XToolbarController.hpp>
+#include <com/sun/star/frame/XFrame.hpp>
+#include <com/sun/star/frame/theToolbarControllerFactory.hpp>
+#include <com/sun/star/lang/XMultiServiceFactory.hpp>
+
+#include <framework/sfxhelperfunctions.hxx>
+#include <framework/generictoolbarcontroller.hxx>
+#include <vcl/toolbox.hxx>
+#include <vcl/commandinfoprovider.hxx>
+#include <vcl/weldutils.hxx>
+#include <comphelper/processfactory.hxx>
+#include <toolkit/helper/vclunohelper.hxx>
+
+using namespace css;
+using namespace css::uno;
+
+namespace sfx2::sidebar {
+
+Reference<lang::XComponent> ControllerFactory::CreateImageController(
+ const Reference<frame::XFrame>& rxFrame,
+ const Reference<awt::XWindow>& rxParentWindow)
+{
+ rtl::Reference xController(new framework::ImageOrientationController(
+ comphelper::getProcessComponentContext(), rxFrame, rxParentWindow,
+ vcl::CommandInfoProvider::GetModuleIdentifier(rxFrame)));
+
+ xController->update();
+ return xController;
+}
+
+Reference<frame::XToolbarController> ControllerFactory::CreateToolBoxController(
+ ToolBox* pToolBox,
+ const ToolBoxItemId nItemId,
+ const OUString& rsCommandName,
+ const Reference<frame::XFrame>& rxFrame,
+ const Reference<frame::XController>& rxController,
+ const Reference<awt::XWindow>& rxParentWindow,
+ const sal_Int32 nWidth, bool bSideBar)
+{
+ Reference<frame::XToolbarController> xController (
+ CreateToolBarController(
+ VCLUnoHelper::GetInterface(pToolBox),
+ rsCommandName,
+ rxFrame, rxController,
+ nWidth, bSideBar));
+
+ bool bFactoryHasController( xController.is() );
+
+ // Create a controller for the new item.
+ if ( !bFactoryHasController )
+ {
+ xController = ::framework::CreateToolBoxController(
+ rxFrame,
+ pToolBox,
+ nItemId,
+ rsCommandName);
+ }
+ if ( ! xController.is())
+ {
+ xController = new framework::GenericToolbarController(
+ ::comphelper::getProcessComponentContext(),
+ rxFrame,
+ pToolBox,
+ nItemId,
+ rsCommandName);
+ }
+
+ // Initialize the controller with eg a service factory.
+ Reference<lang::XInitialization> xInitialization (xController, UNO_QUERY);
+ if (!bFactoryHasController && xInitialization.is())
+ {
+ beans::PropertyValue aPropValue;
+ std::vector<Any> aPropertyVector;
+
+ aPropValue.Name = "Frame";
+ aPropValue.Value <<= rxFrame;
+ aPropertyVector.push_back(Any(aPropValue));
+
+ aPropValue.Name = "ServiceManager";
+ aPropValue.Value <<= ::comphelper::getProcessServiceFactory();
+ aPropertyVector.push_back(Any(aPropValue));
+
+ aPropValue.Name = "CommandURL";
+ aPropValue.Value <<= rsCommandName;
+ aPropertyVector.push_back(Any(aPropValue));
+
+ Sequence<Any> aArgs (comphelper::containerToSequence(aPropertyVector));
+ xInitialization->initialize(aArgs);
+ }
+
+ if (xController.is())
+ {
+ if (rxParentWindow.is())
+ {
+ Reference<awt::XWindow> xItemWindow (xController->createItemWindow(rxParentWindow));
+ VclPtr<vcl::Window> pItemWindow = VCLUnoHelper::GetWindow(xItemWindow);
+ if (pItemWindow != nullptr)
+ {
+ WindowType nType = pItemWindow->GetType();
+ if (nType == WindowType::LISTBOX || nType == WindowType::MULTILISTBOX || nType == WindowType::COMBOBOX)
+ pItemWindow->SetAccessibleName(pToolBox->GetItemText(nItemId));
+ if (nWidth > 0)
+ pItemWindow->SetSizePixel(Size(nWidth, pItemWindow->GetSizePixel().Height()));
+ pToolBox->SetItemWindow(nItemId, pItemWindow);
+ }
+ }
+
+ Reference<util::XUpdatable> xUpdatable (xController, UNO_QUERY);
+ if (xUpdatable.is())
+ xUpdatable->update();
+
+ // Add tooltip.
+ if (xController.is())
+ {
+ auto aProperties = vcl::CommandInfoProvider::GetCommandProperties(rsCommandName,
+ vcl::CommandInfoProvider::GetModuleIdentifier(rxFrame));
+ const OUString sTooltip (vcl::CommandInfoProvider::GetTooltipForCommand(
+ rsCommandName, aProperties, rxFrame));
+ if (pToolBox->GetQuickHelpText(nItemId).isEmpty())
+ pToolBox->SetQuickHelpText(nItemId, sTooltip);
+ pToolBox->EnableItem(nItemId);
+ }
+ }
+
+ return xController;
+}
+
+Reference<frame::XToolbarController> ControllerFactory::CreateToolBoxController(
+ weld::Toolbar& rToolbar, weld::Builder& rBuilder,
+ const OUString& rsCommandName,
+ const Reference<frame::XFrame>& rxFrame,
+ const Reference<frame::XController>& rxController,
+ bool bSideBar)
+{
+ css::uno::Reference<css::awt::XWindow> xWidget(new weld::TransportAsXWindow(&rToolbar, &rBuilder));
+
+ Reference<frame::XToolbarController> xController(
+ CreateToolBarController(
+ xWidget,
+ rsCommandName,
+ rxFrame, rxController,
+ -1, bSideBar));
+
+ if (!xController.is())
+ {
+ xController = new framework::GenericToolbarController(
+ ::comphelper::getProcessComponentContext(),
+ rxFrame,
+ rToolbar,
+ rsCommandName);
+ }
+
+ if (xController.is())
+ {
+ xController->createItemWindow(xWidget);
+
+ Reference<util::XUpdatable> xUpdatable(xController, UNO_QUERY);
+ if (xUpdatable.is())
+ xUpdatable->update();
+ }
+
+ return xController;
+}
+
+
+Reference<frame::XToolbarController> ControllerFactory::CreateToolBarController(
+ const Reference<awt::XWindow>& rxToolbar,
+ const OUString& rsCommandName,
+ const Reference<frame::XFrame>& rxFrame,
+ const Reference<frame::XController>& rxController,
+ const sal_Int32 nWidth, bool bSideBar)
+{
+ try
+ {
+ Reference<XComponentContext> xContext = comphelper::getProcessComponentContext();
+ Reference<frame::XUIControllerFactory> xFactory = frame::theToolbarControllerFactory::get( xContext );
+ OUString sModuleName (Tools::GetModuleName(rxController));
+
+ if (xFactory.is() && xFactory->hasController(rsCommandName, sModuleName))
+ {
+ beans::PropertyValue aPropValue;
+ std::vector<Any> aPropertyVector;
+
+ aPropValue.Name = "ModuleIdentifier";
+ aPropValue.Value <<= sModuleName;
+ aPropertyVector.push_back( Any( aPropValue ));
+
+ aPropValue.Name = "Frame";
+ aPropValue.Value <<= rxFrame;
+ aPropertyVector.push_back( Any( aPropValue ));
+
+ aPropValue.Name = "ServiceManager";
+ aPropValue.Value <<= comphelper::getProcessServiceFactory();
+ aPropertyVector.push_back( Any( aPropValue ));
+
+ aPropValue.Name = "ParentWindow";
+ aPropValue.Value <<= rxToolbar;
+ aPropertyVector.push_back( Any( aPropValue ));
+
+ aPropValue.Name = "IsSidebar";
+ aPropValue.Value <<= bSideBar;
+ aPropertyVector.push_back( Any( aPropValue ));
+
+ if (nWidth > 0)
+ {
+ aPropValue.Name = "Width";
+ aPropValue.Value <<= nWidth;
+ aPropertyVector.push_back( Any( aPropValue ));
+ }
+
+ Sequence<Any> aArgs (comphelper::containerToSequence(aPropertyVector));
+ return Reference<frame::XToolbarController>(
+ xFactory->createInstanceWithArgumentsAndContext(
+ rsCommandName,
+ aArgs,
+ xContext),
+ UNO_QUERY);
+ }
+ }
+ catch (Exception&)
+ {
+ // Ignore exception.
+ }
+ return nullptr;
+}
+
+} // end of namespace sfx2::sidebar
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/sidebar/ControllerItem.cxx b/sfx2/source/sidebar/ControllerItem.cxx
new file mode 100644
index 0000000000..e02276ec0c
--- /dev/null
+++ b/sfx2/source/sidebar/ControllerItem.cxx
@@ -0,0 +1,71 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#include <memory>
+#include <sfx2/sidebar/ControllerItem.hxx>
+
+#include <sfx2/bindings.hxx>
+
+using namespace css;
+using namespace css::uno;
+
+namespace sfx2::sidebar {
+
+ControllerItem::ControllerItem (
+ const sal_uInt16 nSlotId,
+ SfxBindings &rBindings,
+ ItemUpdateReceiverInterface& rItemUpdateReceiver)
+ : SfxControllerItem(nSlotId, rBindings),
+ mrItemUpdateReceiver(rItemUpdateReceiver)
+{
+}
+
+ControllerItem::~ControllerItem()
+{
+ dispose();
+}
+
+void ControllerItem::StateChangedAtToolBoxControl (
+ sal_uInt16 nSID,
+ SfxItemState eState,
+ const SfxPoolItem* pState)
+{
+ mrItemUpdateReceiver.NotifyItemUpdate(nSID, eState, pState);
+}
+
+void ControllerItem::GetControlState (
+ sal_uInt16 nSID,
+ boost::property_tree::ptree& rState)
+{
+ mrItemUpdateReceiver.GetControlState(nSID, rState);
+}
+
+void ControllerItem::RequestUpdate()
+{
+ std::unique_ptr<SfxPoolItem> pState;
+ const SfxItemState eState (GetBindings().QueryState(GetId(), pState));
+ mrItemUpdateReceiver.NotifyItemUpdate(GetId(), eState, pState.get());
+}
+
+ControllerItem::ItemUpdateReceiverInterface::~ItemUpdateReceiverInterface()
+{
+}
+
+} // end of namespace sfx2::sidebar
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/sidebar/Deck.cxx b/sfx2/source/sidebar/Deck.cxx
new file mode 100644
index 0000000000..c45ab52e57
--- /dev/null
+++ b/sfx2/source/sidebar/Deck.cxx
@@ -0,0 +1,304 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sfx2/sidebar/Deck.hxx>
+#include <sidebar/DeckDescriptor.hxx>
+#include <sidebar/DeckLayouter.hxx>
+#include <sidebar/DeckTitleBar.hxx>
+#include <sidebar/PanelTitleBar.hxx>
+#include <sfx2/sidebar/Panel.hxx>
+#include <sfx2/sidebar/SidebarDockingWindow.hxx>
+#include <sfx2/sidebar/Theme.hxx>
+#include <sfx2/viewsh.hxx>
+
+#include <vcl/event.hxx>
+#include <comphelper/lok.hxx>
+#include <vcl/jsdialog/executor.hxx>
+#include <tools/json_writer.hxx>
+
+using namespace css;
+using namespace css::uno;
+
+namespace sfx2::sidebar {
+
+void Deck::LOKSendSidebarFullUpdate()
+{
+ if (comphelper::LibreOfficeKit::isActive())
+ {
+ sal_uInt64 nShellId = reinterpret_cast<sal_uInt64>(SfxViewShell::Current());
+ jsdialog::SendFullUpdate(OUString::number(nShellId) + "sidebar", "Panel");
+ }
+}
+
+Deck::Deck(const DeckDescriptor& rDeckDescriptor, SidebarDockingWindow* pParentWindow,
+ const std::function<void()>& rCloserAction)
+ : InterimItemWindow(pParentWindow, "sfx/ui/deck.ui", "Deck")
+ , msId(rDeckDescriptor.msId)
+ , mnMinimalWidth(0)
+ , mnScrolledWindowExtraWidth(0)
+ , mnMinimalHeight(0)
+ , maPanels()
+ , mxParentWindow(pParentWindow)
+ , mxTitleBar(new DeckTitleBar(rDeckDescriptor.msTitle, *m_xBuilder,
+ rDeckDescriptor.msHelpId, rCloserAction))
+ , mxVerticalScrollBar(m_xBuilder->weld_scrolled_window("scrolledwindow"))
+ , mxContents(m_xBuilder->weld_box("contents"))
+{
+ SetStyle(GetStyle() | WB_DIALOGCONTROL);
+
+ m_xContainer->set_background(Theme::GetColor(Theme::Color_DeckBackground));
+
+ mxVerticalScrollBar->vadjustment_set_step_increment(10);
+ mxVerticalScrollBar->vadjustment_set_page_increment(100);
+
+ // tdf#142458 Measure the preferred width of an empty ScrolledWindow
+ // to add to the width of the union of panel widths when calculating
+ // the minimal width of the deck
+ mxVerticalScrollBar->set_hpolicy(VclPolicyType::NEVER);
+ mxVerticalScrollBar->set_vpolicy(VclPolicyType::NEVER);
+ mnScrolledWindowExtraWidth = mxVerticalScrollBar->get_preferred_size().Width();
+ mxVerticalScrollBar->set_hpolicy(VclPolicyType::AUTOMATIC);
+ mxVerticalScrollBar->set_vpolicy(VclPolicyType::AUTOMATIC);
+}
+
+Deck::~Deck()
+{
+ disposeOnce();
+}
+
+void Deck::dispose()
+{
+ SharedPanelContainer aPanels;
+ aPanels.swap(maPanels);
+
+ // We have to explicitly trigger the destruction of panels.
+ // Otherwise that is done by one of our base class destructors
+ // without updating maPanels.
+ for (auto& rpPanel : aPanels)
+ rpPanel.reset();
+
+ maPanels.clear();
+ mxTitleBar.reset();
+ mxContents.reset();
+ mxVerticalScrollBar.reset();
+
+ mxParentWindow.clear();
+
+ InterimItemWindow::dispose();
+}
+
+DeckTitleBar* Deck::GetTitleBar() const
+{
+ return mxTitleBar.get();
+}
+
+tools::Rectangle Deck::GetContentArea() const
+{
+ const Size aWindowSize (GetSizePixel());
+ const int nBorderSize (Theme::GetInteger(Theme::Int_DeckBorderSize));
+ if (aWindowSize.IsEmpty())
+ return tools::Rectangle();
+
+ return tools::Rectangle(
+ Theme::GetInteger(Theme::Int_DeckLeftPadding) + nBorderSize,
+ Theme::GetInteger(Theme::Int_DeckTopPadding) + nBorderSize,
+ aWindowSize.Width() - 1 - Theme::GetInteger(Theme::Int_DeckRightPadding) - nBorderSize,
+ aWindowSize.Height() - 1 - Theme::GetInteger(Theme::Int_DeckBottomPadding) - nBorderSize);
+}
+
+void Deck::DataChanged(const DataChangedEvent&)
+{
+ for (auto& rpPanel : maPanels)
+ rpPanel->DataChanged();
+
+ RequestLayoutInternal();
+ Deck::LOKSendSidebarFullUpdate();
+}
+
+/*
+ * Get the ordering as is shown in the layout, and our type as 'deck'
+ * also elide nested panel windows.
+ */
+void Deck::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter)
+{
+ rJsonWriter.put("id", get_id().isEmpty() ? msId : get_id());
+ rJsonWriter.put("type", "deck");
+ rJsonWriter.put("text", GetText());
+ rJsonWriter.put("enabled", IsEnabled());
+ if (!IsVisible())
+ rJsonWriter.put("visible", false);
+
+ auto childrenNode = rJsonWriter.startArray("children");
+ for (const auto &it : maPanels)
+ {
+ // collapse the panel itself out
+ auto xContent = it->GetContents();
+ if (!xContent)
+ continue;
+
+ auto childNode = rJsonWriter.startStruct();
+ rJsonWriter.put("id", it->GetId());
+ rJsonWriter.put("type", "panel");
+ rJsonWriter.put("text", it->GetTitle());
+ rJsonWriter.put("enabled", true);
+ rJsonWriter.put("hidden", it->IsLurking());
+ rJsonWriter.put("expanded", it->IsExpanded());
+
+ if (it->GetTitleBar() && !it->GetTitleBar()->GetMoreOptionsCommand().isEmpty())
+ rJsonWriter.put("command", it->GetTitleBar()->GetMoreOptionsCommand());
+
+ {
+ auto children2Node = rJsonWriter.startArray("children");
+ {
+ auto child2Node = rJsonWriter.startStruct();
+ xContent->get_property_tree(rJsonWriter);
+ }
+ }
+ }
+}
+
+/**
+ * This container may contain existing panels that are
+ * being re-used, and new ones too.
+ */
+void Deck::ResetPanels(SharedPanelContainer&& rPanelContainer)
+{
+ SharedPanelContainer aHiddens;
+
+ // First hide old panels we don't need just now.
+ for (auto& rpPanel : maPanels)
+ {
+ bool bFound = false;
+ for (const auto & i : rPanelContainer)
+ bFound = bFound || (rpPanel.get() == i.get());
+ if (!bFound) // this one didn't survive.
+ {
+ rpPanel->SetLurkMode(true);
+ aHiddens.push_back(rpPanel);
+ }
+ }
+
+ bool bDifferent = maPanels.size() != rPanelContainer.size() || aHiddens.size();
+ maPanels = std::move(rPanelContainer);
+
+ // Hidden ones always at the end
+ maPanels.insert(std::end(maPanels), std::begin(aHiddens), std::end(aHiddens));
+
+ RequestLayoutInternal();
+
+ if (bDifferent)
+ Deck::LOKSendSidebarFullUpdate();
+}
+
+void Deck::RequestLayoutInternal()
+{
+ mnMinimalWidth = 0;
+ mnMinimalHeight = 0;
+
+ DeckLayouter::LayoutDeck(mxParentWindow.get(), GetContentArea(),
+ mnMinimalWidth, mnMinimalHeight, maPanels,
+ *GetTitleBar(), *mxVerticalScrollBar);
+
+ if (mnMinimalWidth)
+ {
+ // tdf#142458 at this point mnMinimalWidth contains the width required
+ // by the panels, but extra space may be needed by the scrolledwindow
+ // that will contain the panels
+ mnMinimalWidth += mnScrolledWindowExtraWidth;
+ }
+}
+
+void Deck::RequestLayout()
+{
+ RequestLayoutInternal();
+
+ if (!comphelper::LibreOfficeKit::isActive())
+ return;
+
+ bool bChangeNeeded = false;
+ Size aParentSize = mxParentWindow->GetSizePixel();
+
+ if (mnMinimalHeight > 0 && (mnMinimalHeight != aParentSize.Height() || GetSizePixel().Height() != mnMinimalHeight))
+ {
+ aParentSize.setHeight(mnMinimalHeight);
+ bChangeNeeded = true;
+ }
+ const SfxViewShell* pViewShell = SfxViewShell::Current();
+ if (mnMinimalWidth > 0 && (mnMinimalWidth != aParentSize.Width() || GetSizePixel().Width() != mnMinimalWidth)
+ && pViewShell && pViewShell->isLOKMobilePhone())
+ {
+ aParentSize.setWidth(mnMinimalWidth);
+ bChangeNeeded = true;
+ }
+
+ if (bChangeNeeded)
+ {
+ mxParentWindow->SetSizePixel(aParentSize);
+ setPosSizePixel(0, 0, aParentSize.Width(), aParentSize.Height());
+ }
+ else if (aParentSize != GetSizePixel()) //Sync parent & child sizes
+ setPosSizePixel(0, 0, aParentSize.Width(), aParentSize.Height());
+}
+
+weld::Widget* Deck::GetPanelParentWindow()
+{
+ return mxContents.get();
+}
+
+std::shared_ptr<Panel> Deck::GetPanel(std::u16string_view panelId)
+{
+ for (const auto& pPanel : maPanels)
+ {
+ if(pPanel->GetId() == panelId)
+ {
+ return pPanel;
+ }
+ }
+ return nullptr;
+
+}
+
+void Deck::ShowPanel(const Panel& rPanel)
+{
+ if (!mxVerticalScrollBar || mxVerticalScrollBar->get_vpolicy() == VclPolicyType::NEVER)
+ return;
+
+ // Get vertical extent of the panel.
+ tools::Rectangle aExtents;
+ if (!rPanel.get_extents(aExtents))
+ return;
+
+ auto nPanelTop = aExtents.Top();
+ auto nPanelBottom = aExtents.Bottom() - 1;
+
+ // Determine what the new thumb position should be like.
+ // When the whole panel does not fit then make its top visible
+ // and it off at the bottom.
+ sal_Int32 nNewThumbPos(mxVerticalScrollBar->vadjustment_get_value());
+ if (nPanelBottom >= nNewThumbPos + mxVerticalScrollBar->vadjustment_get_page_size())
+ nNewThumbPos = nPanelBottom - mxVerticalScrollBar->vadjustment_get_page_size();
+ if (nPanelTop < nNewThumbPos)
+ nNewThumbPos = nPanelTop;
+
+ mxVerticalScrollBar->vadjustment_set_value(nNewThumbPos);
+}
+
+} // end of namespace sfx2::sidebar
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/sidebar/DeckDescriptor.cxx b/sfx2/source/sidebar/DeckDescriptor.cxx
new file mode 100644
index 0000000000..29af33e33f
--- /dev/null
+++ b/sfx2/source/sidebar/DeckDescriptor.cxx
@@ -0,0 +1,54 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sidebar/DeckDescriptor.hxx>
+
+namespace sfx2::sidebar {
+
+DeckDescriptor::DeckDescriptor()
+ : mbIsEnabled(true),
+ mnOrderIndex(10000), // Default value as defined in Sidebar.xcs
+ mbExperimental(false)
+{
+}
+
+DeckDescriptor::DeckDescriptor (const DeckDescriptor& rOther)
+ : msTitle(rOther.msTitle),
+ msId(rOther.msId),
+ msIconURL(rOther.msIconURL),
+ msHighContrastIconURL(rOther.msHighContrastIconURL),
+ msTitleBarIconURL(rOther.msTitleBarIconURL),
+ msHighContrastTitleBarIconURL(rOther.msHighContrastTitleBarIconURL),
+ msHelpText(rOther.msHelpText),
+ msHelpId(rOther.msHelpId),
+ maContextList(rOther.maContextList),
+ mbIsEnabled(rOther.mbIsEnabled),
+ mnOrderIndex(rOther.mnOrderIndex),
+ mbExperimental(rOther.mbExperimental),
+ mpDeck(rOther.mpDeck)
+{
+}
+
+DeckDescriptor::~DeckDescriptor()
+{
+}
+
+} // end of namespace sfx2::sidebar
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/sidebar/DeckLayouter.cxx b/sfx2/source/sidebar/DeckLayouter.cxx
new file mode 100644
index 0000000000..824b66ac1a
--- /dev/null
+++ b/sfx2/source/sidebar/DeckLayouter.cxx
@@ -0,0 +1,549 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sidebar/DeckLayouter.hxx>
+#include <sidebar/DeckTitleBar.hxx>
+#include <sidebar/PanelTitleBar.hxx>
+#include <sfx2/sidebar/Panel.hxx>
+#include <sfx2/sidebar/Theme.hxx>
+#include <sfx2/sidebar/SidebarDockingWindow.hxx>
+#include <sfx2/sidebar/SidebarController.hxx>
+#include <sfx2/viewsh.hxx>
+#include <comphelper/lok.hxx>
+#include <osl/diagnose.h>
+
+#include <comphelper/processfactory.hxx>
+
+#include <com/sun/star/uno/Reference.hxx>
+#include <com/sun/star/frame/Desktop.hpp>
+#include <com/sun/star/frame/XDesktop2.hpp>
+#include <com/sun/star/frame/XFrame.hpp>
+#include <com/sun/star/ui/XSidebarPanel.hpp>
+
+#include <utility>
+
+using namespace css;
+using namespace css::uno;
+
+namespace sfx2::sidebar {
+
+namespace {
+ const sal_Int32 MinimalPanelHeight (25);
+
+ enum LayoutMode
+ {
+ MinimumOrLarger,
+ PreferredOrLarger,
+ Preferred
+ };
+ class LayoutItem
+ {
+ public:
+ std::shared_ptr<Panel> mpPanel;
+ css::ui::LayoutSize maLayoutSize;
+ sal_Int32 mnDistributedHeight;
+ sal_Int32 mnWeight;
+ bool mbShowTitleBar;
+
+ LayoutItem(std::shared_ptr<Panel> pPanel)
+ : mpPanel(std::move(pPanel))
+ , maLayoutSize(0, 0, 0)
+ , mnDistributedHeight(0)
+ , mnWeight(0)
+ , mbShowTitleBar(true)
+ {
+ }
+ };
+ void LayoutPanels (
+ const tools::Rectangle& rContentArea,
+ sal_Int32& rMinimalWidth,
+ sal_Int32& rMinimalHeight,
+ ::std::vector<LayoutItem>& rLayoutItems,
+ weld::ScrolledWindow& pVerticalScrollBar,
+ const bool bShowVerticalScrollBar);
+ void GetRequestedSizes (
+ ::std::vector<LayoutItem>& rLayoutItem,
+ sal_Int32& rAvailableHeight,
+ sal_Int32& rMinimalWidth,
+ const tools::Rectangle& rContentBox);
+ void DistributeHeights (
+ ::std::vector<LayoutItem>& rLayoutItems,
+ const sal_Int32 nHeightToDistribute,
+ const sal_Int32 nContainerHeight,
+ const bool bMinimumHeightIsBase);
+ sal_Int32 PlacePanels (
+ ::std::vector<LayoutItem>& rLayoutItems,
+ const LayoutMode eMode_);
+ tools::Rectangle PlaceDeckTitle (
+ const SidebarDockingWindow* pDockingWindow,
+ DeckTitleBar& rTitleBar,
+ const tools::Rectangle& rAvailableSpace);
+ tools::Rectangle PlaceVerticalScrollBar (
+ weld::ScrolledWindow& rVerticalScrollBar,
+ const tools::Rectangle& rAvailableSpace,
+ const bool bShowVerticalScrollBar);
+ void SetupVerticalScrollBar(
+ weld::ScrolledWindow& rVerticalScrollBar,
+ const sal_Int32 nContentHeight,
+ const sal_Int32 nVisibleHeight);
+}
+
+void DeckLayouter::LayoutDeck (
+ const SidebarDockingWindow* pDockingWindow,
+ const tools::Rectangle& rContentArea,
+ sal_Int32& rMinimalWidth,
+ sal_Int32& rMinimalHeight,
+ SharedPanelContainer& rPanels,
+ DeckTitleBar& rDeckTitleBar,
+ weld::ScrolledWindow& rVerticalScrollBar)
+{
+ if (rContentArea.GetWidth()<=0 || rContentArea.GetHeight()<=0)
+ return;
+ tools::Rectangle aBox(PlaceDeckTitle(pDockingWindow, rDeckTitleBar, rContentArea));
+
+ if ( rPanels.empty())
+ return;
+
+ // Prepare the layout item container.
+ ::std::vector<LayoutItem> aLayoutItems;
+ aLayoutItems.reserve(rPanels.size());
+ for (auto& rPanel : rPanels)
+ aLayoutItems.emplace_back(rPanel);
+
+ LayoutPanels(
+ aBox,
+ rMinimalWidth,
+ rMinimalHeight,
+ aLayoutItems,
+ rVerticalScrollBar,
+ false);
+}
+
+namespace {
+
+void LayoutPanels (
+ const tools::Rectangle& rContentArea,
+ sal_Int32& rMinimalWidth,
+ sal_Int32& rMinimalHeight,
+ ::std::vector<LayoutItem>& rLayoutItems,
+ weld::ScrolledWindow& rVerticalScrollBar,
+ const bool bShowVerticalScrollBar)
+{
+ tools::Rectangle aBox (PlaceVerticalScrollBar(rVerticalScrollBar, rContentArea, bShowVerticalScrollBar));
+
+ // Get the requested heights of the panels and the available
+ // height that is left when all panel titles and separators are
+ // taken into account.
+ sal_Int32 nAvailableHeight (aBox.GetHeight());
+ GetRequestedSizes(rLayoutItems, nAvailableHeight, rMinimalWidth, aBox);
+ const sal_Int32 nTotalDecorationHeight (aBox.GetHeight() - nAvailableHeight);
+
+ // Analyze the requested heights.
+ // Determine the height that is available for panel content
+ // and count the different layouts.
+ sal_Int32 nTotalPreferredHeight (0);
+ sal_Int32 nTotalMinimumHeight (0);
+
+ for (const auto& rItem : rLayoutItems)
+ {
+ nTotalMinimumHeight += rItem.maLayoutSize.Minimum;
+ nTotalPreferredHeight += rItem.maLayoutSize.Preferred;
+ }
+
+ if (nTotalMinimumHeight > nAvailableHeight && !bShowVerticalScrollBar
+ && !comphelper::LibreOfficeKit::isActive())
+ {
+ // Not enough space, even when all panels are shrunk to their
+ // minimum height.
+ // Show a vertical scrollbar.
+ LayoutPanels(
+ rContentArea,
+ rMinimalWidth,
+ rMinimalHeight,
+ rLayoutItems,
+ rVerticalScrollBar,
+ true);
+ return;
+ }
+
+ // We are now in one of three modes.
+ // - The preferred height fits into the available size:
+ // Use the preferred size, distribute the remaining height by
+ // enlarging panels.
+ // - The total minimum height fits into the available size:
+ // Use the minimum size, distribute the remaining height by
+ // enlarging panels.
+ // - The total minimum height does not fit into the available
+ // size:
+ // Use the unmodified preferred height for all panels.
+
+ LayoutMode eMode(MinimumOrLarger);
+ if (bShowVerticalScrollBar)
+ {
+ eMode = Preferred;
+
+ const sal_Int32 nContentHeight(nTotalPreferredHeight + nTotalDecorationHeight);
+ SetupVerticalScrollBar(rVerticalScrollBar, nContentHeight, aBox.GetHeight());
+ }
+ else
+ {
+ if (nTotalPreferredHeight <= nAvailableHeight)
+ eMode = PreferredOrLarger;
+ else
+ eMode = MinimumOrLarger;
+
+ const sal_Int32 nTotalHeight (eMode==MinimumOrLarger ? nTotalMinimumHeight : nTotalPreferredHeight);
+
+ DistributeHeights(
+ rLayoutItems,
+ nAvailableHeight-nTotalHeight,
+ aBox.GetHeight(),
+ eMode==MinimumOrLarger);
+ }
+
+ const sal_Int32 nUsedHeight(PlacePanels(rLayoutItems, eMode));
+ rMinimalHeight = nUsedHeight;
+}
+
+sal_Int32 PlacePanels (
+ ::std::vector<LayoutItem>& rLayoutItems,
+ const LayoutMode eMode)
+{
+ const sal_Int32 nDeckSeparatorHeight (Theme::GetInteger(Theme::Int_DeckSeparatorHeight));
+ sal_Int32 nY (0);
+
+ // Assign heights and places.
+ for(::std::vector<LayoutItem>::const_iterator iItem(rLayoutItems.begin()),
+ iEnd(rLayoutItems.end());
+ iItem!=iEnd;
+ ++iItem)
+ {
+ if (!iItem->mpPanel)
+ continue;
+
+ Panel& rPanel (*iItem->mpPanel);
+
+ rPanel.set_margin_top(nDeckSeparatorHeight);
+ rPanel.set_margin_bottom(0);
+
+ // Separator above the panel title bar.
+ if (!rPanel.IsLurking())
+ {
+ nY += nDeckSeparatorHeight;
+ }
+
+ bool bShowTitlebar = iItem->mbShowTitleBar;
+ PanelTitleBar* pTitleBar = rPanel.GetTitleBar();
+ pTitleBar->Show(bShowTitlebar);
+ rPanel.set_vexpand(!bShowTitlebar);
+ weld::Container* pContents = rPanel.GetContents();
+ pContents->set_vexpand(true);
+
+ bool bExpanded = rPanel.IsExpanded() && !rPanel.IsLurking();
+ if (bShowTitlebar || bExpanded)
+ {
+ rPanel.Show(true);
+
+ sal_Int32 nPanelHeight(0);
+ if (bExpanded)
+ {
+ // Determine the height of the panel depending on layout
+ // mode and distributed heights.
+ switch(eMode)
+ {
+ case MinimumOrLarger:
+ nPanelHeight = iItem->maLayoutSize.Minimum + iItem->mnDistributedHeight;
+ break;
+ case PreferredOrLarger:
+ nPanelHeight = iItem->maLayoutSize.Preferred + iItem->mnDistributedHeight;
+ break;
+ case Preferred:
+ nPanelHeight = iItem->maLayoutSize.Preferred;
+ break;
+ default:
+ OSL_ASSERT(false);
+ break;
+ }
+ }
+ if (bShowTitlebar)
+ nPanelHeight += pTitleBar->get_preferred_size().Height();
+
+ rPanel.SetHeightPixel(nPanelHeight);
+
+ nY += nPanelHeight;
+ }
+ else
+ {
+ rPanel.Show(false);
+ }
+
+ if (!bExpanded)
+ {
+ // Add a separator below the collapsed panel, if it is the
+ // last panel in the deck.
+ if (iItem == rLayoutItems.end()-1)
+ {
+ // Separator below the panel title bar.
+ rPanel.set_margin_bottom(nDeckSeparatorHeight);
+ nY += nDeckSeparatorHeight;
+ }
+ }
+ }
+
+ return nY;
+}
+
+void GetRequestedSizes (
+ ::std::vector<LayoutItem>& rLayoutItems,
+ sal_Int32& rAvailableHeight,
+ sal_Int32& rMinimalWidth,
+ const tools::Rectangle& rContentBox)
+{
+ rAvailableHeight = rContentBox.GetHeight();
+
+ const sal_Int32 nDeckSeparatorHeight (Theme::GetInteger(Theme::Int_DeckSeparatorHeight));
+
+ for (auto& rItem : rLayoutItems)
+ {
+ rItem.maLayoutSize = ui::LayoutSize(0,0,0);
+
+ if (rItem.mpPanel == nullptr)
+ continue;
+
+ if (rItem.mpPanel->IsLurking())
+ {
+ rItem.mbShowTitleBar = false;
+ continue;
+ }
+
+ if (rLayoutItems.size() == 1
+ && rItem.mpPanel->IsTitleBarOptional())
+ {
+ // There is only one panel and its title bar is
+ // optional => hide it.
+ rAvailableHeight -= nDeckSeparatorHeight;
+ rItem.mbShowTitleBar = false;
+ }
+ else
+ {
+ // Show the title bar and a separator above and below
+ // the title bar.
+ PanelTitleBar* pTitleBar = rItem.mpPanel->GetTitleBar();
+ const sal_Int32 nPanelTitleBarHeight = pTitleBar->get_preferred_size().Height();
+
+ rAvailableHeight -= nPanelTitleBarHeight;
+ rAvailableHeight -= nDeckSeparatorHeight;
+ }
+
+ if (rItem.mpPanel->IsExpanded() && rItem.mpPanel->GetPanelComponent().is())
+ {
+ Reference<ui::XSidebarPanel> xPanel (rItem.mpPanel->GetPanelComponent());
+
+ rItem.maLayoutSize = xPanel->getHeightForWidth(rContentBox.GetWidth());
+ if (!(0 <= rItem.maLayoutSize.Minimum && rItem.maLayoutSize.Minimum <= rItem.maLayoutSize.Preferred
+ && rItem.maLayoutSize.Preferred <= rItem.maLayoutSize.Maximum))
+ {
+ SAL_INFO("sfx.sidebar", "Please follow LayoutSize constraints: 0 ≤ "
+ "Minimum ≤ Preferred ≤ Maximum."
+ " Currently: Minimum: "
+ << rItem.maLayoutSize.Minimum
+ << " Preferred: " << rItem.maLayoutSize.Preferred
+ << " Maximum: " << rItem.maLayoutSize.Maximum);
+ }
+
+ sal_Int32 nWidth = rMinimalWidth;
+ try
+ {
+ // The demo sidebar extension "Analog Clock" fails with
+ // java.lang.AbstractMethodError here
+ nWidth = xPanel->getMinimalWidth();
+ }
+ catch (...)
+ {
+ }
+
+ uno::Reference<frame::XDesktop2> xDesktop
+ = frame::Desktop::create(comphelper::getProcessComponentContext());
+ uno::Reference<frame::XFrame> xFrame = xDesktop->getActiveFrame();
+ if (xFrame.is())
+ {
+ SidebarController* pController
+ = SidebarController::GetSidebarControllerForFrame(xFrame);
+ if (pController && pController->getMaximumWidth() < nWidth)
+ {
+ // Add 100 extra pixels to still have the sidebar resizable
+ // (See also documentation of XSidebarPanel::getMinimalWidth)
+ pController->setMaximumWidth(nWidth + 100);
+ }
+ }
+
+ if (nWidth > rMinimalWidth)
+ rMinimalWidth = nWidth;
+ }
+ else
+ rItem.maLayoutSize = ui::LayoutSize(MinimalPanelHeight, -1, 0);
+ }
+}
+
+void DistributeHeights (
+ ::std::vector<LayoutItem>& rLayoutItems,
+ const sal_Int32 nHeightToDistribute,
+ const sal_Int32 nContainerHeight,
+ const bool bMinimumHeightIsBase)
+{
+ if (nHeightToDistribute <= 0)
+ return;
+
+ sal_Int32 nRemainingHeightToDistribute (nHeightToDistribute);
+
+ // Compute the weights as difference between panel base height
+ // (either its minimum or preferred height) and the container height.
+ sal_Int32 nTotalWeight (0);
+ sal_Int32 nNoMaximumCount (0);
+
+ for (auto& rItem : rLayoutItems)
+ {
+ if (rItem.maLayoutSize.Maximum == 0)
+ continue;
+ if (rItem.maLayoutSize.Maximum < 0)
+ ++nNoMaximumCount;
+
+ const sal_Int32 nBaseHeight (
+ bMinimumHeightIsBase
+ ? rItem.maLayoutSize.Minimum
+ : rItem.maLayoutSize.Preferred);
+ if (nBaseHeight < nContainerHeight)
+ {
+ rItem.mnWeight = nContainerHeight - nBaseHeight;
+ nTotalWeight += rItem.mnWeight;
+ }
+ }
+
+ if (nTotalWeight == 0)
+ return;
+
+ // First pass of height distribution.
+ for (auto& rItem : rLayoutItems)
+ {
+ const sal_Int32 nBaseHeight (
+ bMinimumHeightIsBase
+ ? rItem.maLayoutSize.Minimum
+ : rItem.maLayoutSize.Preferred);
+ sal_Int32 nDistributedHeight (rItem.mnWeight * nHeightToDistribute / nTotalWeight);
+ if (nBaseHeight+nDistributedHeight > rItem.maLayoutSize.Maximum
+ && rItem.maLayoutSize.Maximum >= 0)
+ {
+ nDistributedHeight = ::std::max<sal_Int32>(0, rItem.maLayoutSize.Maximum - nBaseHeight);
+ }
+ rItem.mnDistributedHeight = nDistributedHeight;
+ nRemainingHeightToDistribute -= nDistributedHeight;
+ }
+
+ if (nRemainingHeightToDistribute == 0)
+ return;
+ OSL_ASSERT(nRemainingHeightToDistribute > 0);
+
+ // It is possible that not all of the height could be distributed
+ // because of Maximum heights being smaller than expected.
+ // Distribute the remaining height between the panels that have no
+ // Maximum (ie Maximum==-1).
+ if (nNoMaximumCount == 0)
+ {
+ // There are no panels with unrestricted height.
+ return;
+ }
+
+ const sal_Int32 nAdditionalHeightPerPanel(nRemainingHeightToDistribute / nNoMaximumCount);
+ // Handle rounding error.
+ sal_Int32 nAdditionalHeightForFirstPanel (nRemainingHeightToDistribute
+ - nNoMaximumCount*nAdditionalHeightPerPanel);
+
+ for (auto& rItem : rLayoutItems)
+ {
+ if (rItem.maLayoutSize.Maximum < 0)
+ {
+ rItem.mnDistributedHeight += nAdditionalHeightPerPanel + nAdditionalHeightForFirstPanel;
+ nRemainingHeightToDistribute -= nAdditionalHeightPerPanel + nAdditionalHeightForFirstPanel;
+ }
+ }
+
+ OSL_ASSERT(nRemainingHeightToDistribute==0);
+}
+
+tools::Rectangle PlaceDeckTitle(
+ const SidebarDockingWindow* pDockingWindow,
+ DeckTitleBar& rDeckTitleBar,
+ const tools::Rectangle& rAvailableSpace)
+{
+ if (pDockingWindow->IsFloatingMode())
+ {
+ // When the side bar is undocked then the outer system window displays the deck title.
+ rDeckTitleBar.Show(false);
+ return rAvailableSpace;
+ }
+ else
+ {
+ rDeckTitleBar.Show(true);
+ const sal_Int32 nDeckTitleBarHeight(rDeckTitleBar.get_preferred_size().Height());
+ return tools::Rectangle(
+ rAvailableSpace.Left(),
+ rAvailableSpace.Top() + nDeckTitleBarHeight,
+ rAvailableSpace.Right(),
+ rAvailableSpace.Bottom());
+ }
+}
+
+tools::Rectangle PlaceVerticalScrollBar (
+ weld::ScrolledWindow& rVerticalScrollBar,
+ const tools::Rectangle& rAvailableSpace,
+ const bool bShowVerticalScrollBar)
+{
+ if (bShowVerticalScrollBar)
+ {
+ const sal_Int32 nScrollBarWidth(rVerticalScrollBar.get_scroll_thickness());
+ rVerticalScrollBar.set_vpolicy(VclPolicyType::ALWAYS);
+ return tools::Rectangle(
+ rAvailableSpace.Left(),
+ rAvailableSpace.Top(),
+ rAvailableSpace.Right() - nScrollBarWidth,
+ rAvailableSpace.Bottom());
+ }
+ else
+ {
+ rVerticalScrollBar.set_vpolicy(VclPolicyType::NEVER);
+ return rAvailableSpace;
+ }
+}
+
+void SetupVerticalScrollBar(
+ weld::ScrolledWindow& rVerticalScrollBar,
+ const sal_Int32 nContentHeight,
+ const sal_Int32 nVisibleHeight)
+{
+ OSL_ASSERT(nContentHeight > nVisibleHeight);
+
+ rVerticalScrollBar.vadjustment_set_upper(nContentHeight-1);
+ rVerticalScrollBar.vadjustment_set_page_size(nVisibleHeight);
+}
+
+}
+
+} // end of namespace sfx2::sidebar
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/sidebar/DeckTitleBar.cxx b/sfx2/source/sidebar/DeckTitleBar.cxx
new file mode 100644
index 0000000000..9e12fd15c6
--- /dev/null
+++ b/sfx2/source/sidebar/DeckTitleBar.cxx
@@ -0,0 +1,137 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sidebar/DeckTitleBar.hxx>
+#include <sfx2/sidebar/Theme.hxx>
+
+#include <utility>
+#include <vcl/bitmapex.hxx>
+#include <vcl/customweld.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/ptrstyle.hxx>
+
+#ifdef DEBUG
+#include <sfx2/sidebar/Tools.hxx>
+#endif
+
+namespace sfx2::sidebar {
+
+class GripWidget : public weld::CustomWidgetController
+{
+private:
+ BitmapEx maGrip;
+public:
+ virtual void SetDrawingArea(weld::DrawingArea* pDrawingArea) override
+ {
+ weld::CustomWidgetController::SetDrawingArea(pDrawingArea);
+ StyleUpdated();
+ }
+
+ virtual void StyleUpdated() override
+ {
+ maGrip = BitmapEx("sfx2/res/grip.png");
+ Size aGripSize(maGrip.GetSizePixel());
+ set_size_request(aGripSize.Width(), aGripSize.Height());
+ weld::CustomWidgetController::StyleUpdated();
+ }
+
+ virtual void Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& /*rRect*/) override
+ {
+ rRenderContext.SetBackground(Theme::GetColor(Theme::Color_DeckTitleBarBackground));
+ rRenderContext.Erase();
+ rRenderContext.DrawBitmapEx(Point(0, 0), maGrip);
+ }
+};
+
+DeckTitleBar::DeckTitleBar (const OUString& rsTitle,
+ weld::Builder& rBuilder,
+ const OUString& rsHelpId,
+ std::function<void()> aCloserAction)
+ : TitleBar(rBuilder, Theme::Color_DeckTitleBarBackground)
+ , mxGripWidget(new GripWidget)
+ , mxGripWeld(new weld::CustomWeld(rBuilder, "grip", *mxGripWidget))
+ , mxLabel(rBuilder.weld_label("label"))
+ , msHelpId(rsHelpId)
+ , maCloserAction(std::move(aCloserAction))
+ , mbIsCloserVisible(false)
+{
+ mxLabel->set_label(rsTitle);
+ mxGripWidget->SetPointer(PointerStyle::Move);
+
+ if (maCloserAction)
+ SetCloserVisible(true);
+}
+
+DeckTitleBar::~DeckTitleBar()
+{
+}
+
+tools::Rectangle DeckTitleBar::GetDragArea() const
+{
+ int x, y, width, height;
+ if (mxGripWidget->GetDrawingArea()->get_extents_relative_to(*mxTitlebar, x, y, width, height))
+ return tools::Rectangle(Point(x, y), Size(width, height));
+ return tools::Rectangle();
+}
+
+void DeckTitleBar::SetTitle(const OUString& rsTitle)
+{
+ mxLabel->set_label(rsTitle);
+}
+
+OUString DeckTitleBar::GetTitle() const
+{
+ return mxLabel->get_label();
+}
+
+void DeckTitleBar::SetCloserVisible (const bool bIsCloserVisible)
+{
+ if (mbIsCloserVisible == bIsCloserVisible)
+ return;
+
+ mbIsCloserVisible = bIsCloserVisible;
+
+ mxToolBox->set_visible(mbIsCloserVisible);
+}
+
+void DeckTitleBar::HandleToolBoxItemClick()
+{
+ if (msToolBoxRId == "btn_help")
+ {
+ // Help toolbox button was clicked
+ DeckTitleBar::ShowHelp(msHelpId);
+ }
+ else if ((msToolBoxRId.isEmpty()) || (msToolBoxRId == "btn_close"))
+ {
+ if (maCloserAction)
+ maCloserAction();
+ }
+ // Reset the toolbox response id
+ msToolBoxRId = "";
+}
+
+void DeckTitleBar::DataChanged()
+{
+ mxToolBox->set_item_icon_name("button", "sfx2/res/closedoc.png");
+ TitleBar::DataChanged();
+}
+
+} // end of namespace sfx2::sidebar
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/sidebar/FocusManager.cxx b/sfx2/source/sidebar/FocusManager.cxx
new file mode 100644
index 0000000000..e86de2b7de
--- /dev/null
+++ b/sfx2/source/sidebar/FocusManager.cxx
@@ -0,0 +1,478 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <o3tl/safeint.hxx>
+#include <sfx2/sidebar/FocusManager.hxx>
+#include <sfx2/sidebar/Deck.hxx>
+#include <sfx2/sidebar/Panel.hxx>
+#include <sidebar/DeckTitleBar.hxx>
+#include <sidebar/PanelTitleBar.hxx>
+#include <sidebar/TitleBar.hxx>
+#include <utility>
+#include <vcl/event.hxx>
+#include <vcl/weld.hxx>
+
+namespace sfx2::sidebar {
+
+FocusManager::FocusLocation::FocusLocation (const PanelComponent eComponent, const sal_Int32 nIndex)
+ : meComponent(eComponent),
+ mnIndex(nIndex)
+{
+}
+
+FocusManager::FocusManager(std::function<void(const Panel&)> aShowPanelFunctor)
+ : mpDeckTitleBar(nullptr),
+ maShowPanelFunctor(std::move(aShowPanelFunctor))
+{
+}
+
+FocusManager::~FocusManager()
+{
+ Clear();
+}
+
+void FocusManager::GrabFocus()
+{
+ FocusDeckTitle();
+}
+
+void FocusManager::GrabFocusPanel()
+{
+ FocusPanel(0, false);
+}
+
+void FocusManager::Clear()
+{
+ SetDeck(nullptr);
+ ClearPanels();
+ ClearButtons();
+}
+
+void FocusManager::ClearPanels()
+{
+ SharedPanelContainer aPanels;
+ aPanels.swap(maPanels);
+ for (auto const& panel : aPanels)
+ {
+ if (panel->GetTitleBar())
+ {
+ UnregisterWindow(panel->GetTitleBar()->GetToolBox());
+ UnregisterWindow(panel->GetTitleBar()->GetExpander());
+ }
+
+ weld::Container* pContents = panel->GetContents();
+ UnregisterWindow(*pContents);
+ }
+}
+
+void FocusManager::ClearButtons()
+{
+ std::vector<weld::Widget*> aButtons;
+ aButtons.swap(maButtons);
+ for (auto const& button : aButtons)
+ {
+ UnregisterWindow(*button);
+ }
+}
+
+void FocusManager::SetDeck(Deck* pDeck)
+{
+ DeckTitleBar* pDeckTitleBar = pDeck ? pDeck->GetTitleBar() : nullptr;
+ if (mpDeckTitleBar != nullptr)
+ UnregisterWindow(mpDeckTitleBar->GetToolBox());
+ mxDeck = pDeck;
+ mpDeckTitleBar = pDeckTitleBar;
+ if (mpDeckTitleBar != nullptr)
+ RegisterWindow(mpDeckTitleBar->GetToolBox());
+}
+
+void FocusManager::SetPanels (const SharedPanelContainer& rPanels)
+{
+ ClearPanels();
+ for (auto const& panel : rPanels)
+ {
+ if (panel->GetTitleBar())
+ {
+ RegisterWindow(panel->GetTitleBar()->GetToolBox());
+ RegisterWindow(panel->GetTitleBar()->GetExpander());
+ }
+
+ // Register also as key event listener at the panel.
+ weld::Container* pContents = panel->GetContents();
+ RegisterWindow(*pContents);
+
+ maPanels.emplace_back(panel);
+ }
+}
+
+void FocusManager::SetButtons(const std::vector<weld::Widget*>& rButtons)
+{
+ ClearButtons();
+ for (auto const& button : rButtons)
+ {
+ RegisterWindow(*button);
+ maButtons.emplace_back(button);
+ }
+}
+
+void FocusManager::RegisterWindow(weld::Widget& rWidget)
+{
+ UnregisterWindow(rWidget); // explicitly unset key press handler so we can reconnect without warnings
+ rWidget.connect_key_press(LINK(this, FocusManager, KeyInputHdl));
+}
+
+void FocusManager::UnregisterWindow(weld::Widget& rWidget)
+{
+ rWidget.connect_key_press(Link<const KeyEvent&, bool>());
+}
+
+FocusManager::FocusLocation FocusManager::GetFocusLocation() const
+{
+ // Check the deck title.
+ if (mpDeckTitleBar && mpDeckTitleBar->GetToolBox().has_focus())
+ return FocusLocation(PC_DeckToolBox, -1);
+
+ // Search the panels.
+ for (size_t nIndex = 0; nIndex < maPanels.size(); ++nIndex)
+ {
+ PanelTitleBar* pTitleBar = maPanels[nIndex]->GetTitleBar();
+ if (!pTitleBar)
+ continue;
+ if (pTitleBar->GetExpander().has_focus())
+ return FocusLocation(PC_PanelTitle, nIndex);
+ if (pTitleBar->GetToolBox().has_focus())
+ return FocusLocation(PC_PanelToolBox, nIndex);
+ weld::Container* pContents = maPanels[nIndex]->GetContents();
+ if (pContents->has_child_focus())
+ return FocusLocation(PC_PanelContent, nIndex);
+ }
+
+ // Search the buttons.
+ for (size_t nIndex=0; nIndex < maButtons.size(); ++nIndex)
+ {
+ if (maButtons[nIndex]->has_focus())
+ return FocusLocation(PC_TabBar, nIndex);
+ }
+ return FocusLocation(PC_None, -1);
+}
+
+void FocusManager::FocusDeckTitle()
+{
+ if (mpDeckTitleBar != nullptr)
+ {
+ if (mpDeckTitleBar->GetToolBox().get_n_items() > 0)
+ {
+ weld::Toolbar& rToolBox = mpDeckTitleBar->GetToolBox();
+ rToolBox.grab_focus();
+ }
+ else
+ FocusPanel(0, false);
+ }
+ else
+ FocusPanel(0, false);
+}
+
+bool FocusManager::IsDeckTitleVisible() const
+{
+ return mpDeckTitleBar != nullptr && mpDeckTitleBar->GetVisible();
+}
+
+void FocusManager::FocusPanel (
+ const sal_Int32 nPanelIndex,
+ const bool bFallbackToDeckTitle)
+{
+ if (nPanelIndex<0 || o3tl::make_unsigned(nPanelIndex)>=maPanels.size())
+ {
+ if (bFallbackToDeckTitle)
+ FocusDeckTitle();
+ return;
+ }
+
+ Panel& rPanel (*maPanels[nPanelIndex]);
+ PanelTitleBar* pTitleBar = rPanel.GetTitleBar();
+ if (pTitleBar && pTitleBar->GetVisible())
+ {
+ rPanel.SetExpanded(true);
+ pTitleBar->GetExpander().grab_focus();
+ }
+ // Fallback to deck title should only be applicable when there is more than one panel,
+ // or else it will never be possible to enter the panel contents when there's a single panel
+ // without a titlebar and expander
+ else if (bFallbackToDeckTitle && maPanels.size() > 1)
+ {
+ // The panel title is not visible, fall back to the deck
+ // title.
+ // Make sure that the desk title is visible here to prevent a
+ // loop when both the title of panel 0 and the deck title are
+ // not present.
+ if (IsDeckTitleVisible())
+ FocusDeckTitle();
+ else
+ FocusPanelContent(nPanelIndex);
+ }
+ else
+ FocusPanelContent(nPanelIndex);
+
+ if (maShowPanelFunctor)
+ maShowPanelFunctor(rPanel);
+}
+
+void FocusManager::FocusPanelContent(const sal_Int32 nPanelIndex)
+{
+ if (!maPanels[nPanelIndex]->IsExpanded())
+ maPanels[nPanelIndex]->SetExpanded(true);
+
+ weld::Container* pContents = maPanels[nPanelIndex]->GetContents();
+ pContents->child_grab_focus();
+}
+
+void FocusManager::FocusButton (const sal_Int32 nButtonIndex)
+{
+ maButtons[nButtonIndex]->grab_focus();
+}
+
+void FocusManager::MoveFocusInsidePanel (
+ const FocusLocation& rFocusLocation,
+ const sal_Int32 nDirection)
+{
+ const bool bHasToolBoxItem (
+ maPanels[rFocusLocation.mnIndex]->GetTitleBar()->GetToolBox().get_n_items() > 0);
+ switch (rFocusLocation.meComponent)
+ {
+ case PC_PanelTitle:
+ if (nDirection > 0 && bHasToolBoxItem)
+ maPanels[rFocusLocation.mnIndex]->GetTitleBar()->GetToolBox().grab_focus();
+ else
+ FocusPanelContent(rFocusLocation.mnIndex);
+ break;
+
+ case PC_PanelToolBox:
+ if (nDirection < 0 && bHasToolBoxItem)
+ maPanels[rFocusLocation.mnIndex]->GetTitleBar()->GetExpander().grab_focus();
+ else
+ FocusPanelContent(rFocusLocation.mnIndex);
+ break;
+
+ default: break;
+ }
+}
+
+bool FocusManager::HandleKeyEvent(
+ const vcl::KeyCode& rKeyCode,
+ const FocusLocation& aLocation)
+{
+ bool bConsumed = false;
+
+ switch (rKeyCode.GetCode())
+ {
+ case KEY_ESCAPE:
+ switch (aLocation.meComponent)
+ {
+ case PC_TabBar:
+ case PC_DeckToolBox:
+ case PC_PanelTitle:
+ case PC_PanelToolBox:
+ {
+ if (mxDeck)
+ {
+ mxDeck->GrabFocusToDocument();
+ bConsumed = true;
+ }
+ break;
+ }
+ case PC_PanelContent:
+ // Return focus to tab bar sidebar settings button or panel title.
+ if (!IsDeckTitleVisible() && maPanels.size() == 1)
+ FocusButton(0);
+ else
+ FocusPanel(aLocation.mnIndex, true);
+ bConsumed = true;
+ break;
+ default:
+ break;
+ }
+ return bConsumed;
+
+ case KEY_RETURN:
+ switch (aLocation.meComponent)
+ {
+ case PC_PanelTitle:
+ // Enter the panel.
+ FocusPanelContent(aLocation.mnIndex);
+ bConsumed = true;
+ break;
+
+ default:
+ break;
+ }
+ return bConsumed;
+
+ case KEY_TAB:
+ {
+ const sal_Int32 nDirection (
+ rKeyCode.IsShift()
+ ? -1
+ : +1);
+ switch (aLocation.meComponent)
+ {
+ case PC_PanelTitle:
+ case PC_PanelToolBox:
+ if (rKeyCode.IsShift())
+ break;
+ MoveFocusInsidePanel(aLocation, nDirection);
+ bConsumed = true;
+ break;
+
+ case PC_DeckToolBox:
+ {
+ // Moves to the first deck activation button that is visible and sensitive
+ sal_Int32 nIndex(0);
+ sal_Int32 nButtons(maButtons.size());
+ if (nButtons > 1)
+ {
+ nIndex = 1;
+ // Finds the next visible button that is sensitive
+ while((!maButtons[nIndex]->get_visible() ||
+ !maButtons[nIndex]->get_sensitive()) && ++nIndex < nButtons);
+ // Wrap to the menu button when going past the last button
+ if (nIndex >= nButtons)
+ nIndex = 0;
+ }
+ FocusButton(nIndex);
+ bConsumed = true;
+ }
+ break;
+
+ case PC_TabBar:
+ if (rKeyCode.IsShift())
+ {
+ if (IsDeckTitleVisible())
+ FocusDeckTitle();
+ else
+ FocusPanel(0, true);
+ }
+ else
+ FocusPanel(0, true);
+ bConsumed = true;
+ break;
+
+ default:
+ break;
+ }
+ break;
+ }
+
+ case KEY_LEFT:
+ case KEY_UP:
+ switch (aLocation.meComponent)
+ {
+ case PC_PanelTitle:
+ case PC_PanelToolBox:
+ // Go to previous panel or the deck title.
+ if (aLocation.mnIndex > 0)
+ FocusPanel(aLocation.mnIndex-1, true);
+ else if (IsDeckTitleVisible())
+ FocusDeckTitle();
+ else
+ {
+ // Set focus to the last visible sensitive button.
+ sal_Int32 nIndex(maButtons.size()-1);
+ while((!maButtons[nIndex]->get_visible() ||
+ !maButtons[nIndex]->get_sensitive()) && --nIndex > 0);
+ FocusButton(nIndex);
+ }
+ bConsumed = true;
+ break;
+
+ case PC_TabBar:
+ {
+ if (rKeyCode.GetCode() == KEY_LEFT)
+ break;
+
+ sal_Int32 nIndex;
+ if (aLocation.mnIndex <= 0)
+ nIndex = maButtons.size() - 1;
+ else
+ nIndex = aLocation.mnIndex - 1;
+
+ // Finds the previous visible sensitive button
+ while((!maButtons[nIndex]->get_visible() ||
+ !maButtons[nIndex]->get_sensitive()) && --nIndex > 0);
+ FocusButton(nIndex);
+ bConsumed = true;
+ }
+ break;
+
+ default:
+ break;
+ }
+ break;
+
+ case KEY_RIGHT:
+ case KEY_DOWN:
+ switch(aLocation.meComponent)
+ {
+ case PC_PanelTitle:
+ case PC_PanelToolBox:
+ // Go to next panel.
+ if (aLocation.mnIndex < static_cast<sal_Int32>(maPanels.size())-1)
+ FocusPanel(aLocation.mnIndex+1, false);
+ else
+ FocusButton(0);
+ bConsumed = true;
+ break;
+
+ case PC_TabBar:
+ {
+ if (rKeyCode.GetCode() == KEY_RIGHT)
+ break;
+
+ sal_Int32 nButtons(maButtons.size());
+
+ sal_Int32 nIndex = aLocation.mnIndex + 1;
+ if (nIndex >= nButtons)
+ nIndex = 0;
+
+ // Finds the next visible sensitive button
+ while((!maButtons[nIndex]->get_visible() ||
+ !maButtons[nIndex]->get_sensitive()) && ++nIndex < nButtons);
+ // Wrap to the menu button when going past the last button
+ if (nIndex >= nButtons)
+ nIndex = 0;
+ FocusButton(nIndex);
+ bConsumed = true;
+ }
+ break;
+
+ default:
+ break;
+ }
+ break;
+ }
+ return bConsumed;
+}
+
+IMPL_LINK(FocusManager, KeyInputHdl, const KeyEvent&, rKeyEvent, bool)
+{
+ return HandleKeyEvent(rKeyEvent.GetKeyCode(), GetFocusLocation());
+}
+
+} // end of namespace sfx2::sidebar
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/sidebar/IContextChangeReceiver.cxx b/sfx2/source/sidebar/IContextChangeReceiver.cxx
new file mode 100644
index 0000000000..6fc053921b
--- /dev/null
+++ b/sfx2/source/sidebar/IContextChangeReceiver.cxx
@@ -0,0 +1,29 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#include <sfx2/sidebar/IContextChangeReceiver.hxx>
+
+namespace sfx2::sidebar {
+
+IContextChangeReceiver::~IContextChangeReceiver()
+{
+}
+
+} // end of namespace ::sfx2::sidebar
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/sidebar/ILayoutableWindow.cxx b/sfx2/source/sidebar/ILayoutableWindow.cxx
new file mode 100644
index 0000000000..6a24cadbfa
--- /dev/null
+++ b/sfx2/source/sidebar/ILayoutableWindow.cxx
@@ -0,0 +1,29 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#include <sfx2/sidebar/ILayoutableWindow.hxx>
+
+namespace sfx2::sidebar {
+
+ILayoutableWindow::~ILayoutableWindow()
+{
+}
+
+} // end of namespace ::sfx2::sidebar
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/sidebar/Panel.cxx b/sfx2/source/sidebar/Panel.cxx
new file mode 100644
index 0000000000..5998cb2ee9
--- /dev/null
+++ b/sfx2/source/sidebar/Panel.cxx
@@ -0,0 +1,241 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sfx2/sidebar/Panel.hxx>
+#include <sidebar/PanelTitleBar.hxx>
+#include <sidebar/PanelDescriptor.hxx>
+#include <sfx2/sidebar/ResourceManager.hxx>
+#include <sfx2/sidebar/SidebarController.hxx>
+#include <sfx2/sidebar/SidebarPanelBase.hxx>
+#include <sfx2/viewsh.hxx>
+#include <tools/json_writer.hxx>
+
+
+#ifdef DEBUG
+#include <sfx2/sidebar/Tools.hxx>
+#include <sfx2/sidebar/Deck.hxx>
+#endif
+
+#include <com/sun/star/ui/XToolPanel.hpp>
+#include <com/sun/star/ui/XSidebarPanel.hpp>
+#include <com/sun/star/ui/XUIElement.hpp>
+
+#include <utility>
+#include <vcl/weldutils.hxx>
+
+using namespace css;
+using namespace css::uno;
+
+namespace sfx2::sidebar {
+
+Panel::Panel(const PanelDescriptor& rPanelDescriptor,
+ weld::Widget* pParentWindow,
+ const bool bIsInitiallyExpanded,
+ Deck* pDeck,
+ std::function<Context()> aContextAccess,
+ const css::uno::Reference<css::frame::XFrame>& rxFrame)
+ : mxBuilder(Application::CreateBuilder(pParentWindow, "sfx/ui/panel.ui", false, reinterpret_cast<sal_uInt64>(SfxViewShell::Current())))
+ , msPanelId(rPanelDescriptor.msId)
+ , msTitle(rPanelDescriptor.msTitle)
+ , mbIsTitleBarOptional(rPanelDescriptor.mbIsTitleBarOptional)
+ , mbWantsAWT(rPanelDescriptor.mbWantsAWT)
+ , mbIsExpanded(bIsInitiallyExpanded)
+ , mbLurking(false)
+ , maContextAccess(std::move(aContextAccess))
+ , mxFrame(rxFrame)
+ , mpParentWindow(pParentWindow)
+ , mxDeck(pDeck)
+ , mxContainer(mxBuilder->weld_box("Panel"))
+ , mxTitleBar(new PanelTitleBar(rPanelDescriptor.msTitle, *mxBuilder, this))
+ , mxContents(mxBuilder->weld_box("contents"))
+{
+ mxContents->set_visible(mbIsExpanded);
+ mxContainer->connect_get_property_tree(LINK(this, Panel, DumpAsPropertyTreeHdl));
+}
+
+bool Panel::get_extents(tools::Rectangle &rExtents) const
+{
+ // Get vertical extent of the panel.
+ int x, y, width, height;
+ if (mxContainer->get_extents_relative_to(*mpParentWindow, x, y, width, height))
+ {
+ rExtents = tools::Rectangle(Point(x, y), Size(width, height));
+ return true;
+ }
+ return false;
+}
+
+void Panel::SetHeightPixel(int nHeight)
+{
+ mxContainer->set_size_request(-1, nHeight);
+}
+
+void Panel::set_margin_top(int nMargin)
+{
+ mxContainer->set_margin_top(nMargin);
+}
+
+void Panel::set_margin_bottom(int nMargin)
+{
+ mxContainer->set_margin_bottom(nMargin);
+}
+
+void Panel::set_vexpand(bool bExpand)
+{
+ mxContainer->set_vexpand(bExpand);
+}
+
+void Panel::SetLurkMode(bool bLurk)
+{
+ // cf. DeckLayouter
+ mbLurking = bLurk;
+}
+
+IMPL_LINK(Panel, DumpAsPropertyTreeHdl, tools::JsonWriter&, rJsonWriter, void)
+{
+ if (!IsLurking())
+ rJsonWriter.put("type", "panel");
+}
+
+Panel::~Panel()
+{
+ mxPanelComponent = nullptr;
+
+ {
+ Reference<lang::XComponent> xComponent (mxElement, UNO_QUERY);
+ mxElement = nullptr;
+ if (xComponent.is())
+ xComponent->dispose();
+ }
+
+ {
+ Reference<lang::XComponent> xComponent = GetElementWindow();
+ if (xComponent.is())
+ xComponent->dispose();
+ }
+
+ mxTitleBar.reset();
+
+ if (mxXWindow)
+ {
+ mxXWindow->dispose();
+ mxXWindow.clear();
+ }
+ mxContents.reset();
+
+ assert(!mxTitleBar);
+}
+
+PanelTitleBar* Panel::GetTitleBar() const
+{
+ return mxTitleBar.get();
+}
+
+weld::Box* Panel::GetContents() const
+{
+ return mxContents.get();
+}
+
+void Panel::Show(bool bShow)
+{
+ mxContainer->set_visible(bShow);
+}
+
+void Panel::SetUIElement (const Reference<ui::XUIElement>& rxElement)
+{
+ mxElement = rxElement;
+ if (!mxElement.is())
+ return;
+ mxPanelComponent.set(mxElement->getRealInterface(), UNO_QUERY);
+ if (mbWantsAWT)
+ return;
+ sfx2::sidebar::SidebarPanelBase* pPanelBase = dynamic_cast<sfx2::sidebar::SidebarPanelBase*>(mxElement.get());
+ assert(pPanelBase && "internal panels are all expected to inherit from SidebarPanelBase");
+ pPanelBase->SetParentPanel(this);
+}
+
+void Panel::TriggerDeckLayouting()
+{
+ mxDeck->RequestLayout();
+}
+
+weld::Window* Panel::GetFrameWeld()
+{
+ return mxDeck->GetFrameWeld();
+}
+
+void Panel::SetExpanded (const bool bIsExpanded)
+{
+ SidebarController* pSidebarController = SidebarController::GetSidebarControllerForFrame(mxFrame);
+
+ if (mbIsExpanded == bIsExpanded)
+ return;
+
+ mbIsExpanded = bIsExpanded;
+ mxTitleBar->UpdateExpandedState();
+ TriggerDeckLayouting();
+
+ if (maContextAccess && pSidebarController)
+ {
+ pSidebarController->GetResourceManager()->StorePanelExpansionState(
+ msPanelId,
+ bIsExpanded,
+ maContextAccess());
+ }
+
+ mxContents->set_visible(mbIsExpanded);
+}
+
+bool Panel::HasIdPredicate (std::u16string_view rsId) const
+{
+ return msPanelId == rsId;
+}
+
+void Panel::DataChanged()
+{
+ mxTitleBar->DataChanged();
+}
+
+Reference<awt::XWindow> Panel::GetElementWindow()
+{
+ if (mxElement.is())
+ {
+ Reference<ui::XToolPanel> xToolPanel(mxElement->getRealInterface(), UNO_QUERY);
+ if (xToolPanel.is())
+ return xToolPanel->getWindow();
+ }
+
+ return nullptr;
+}
+
+const Reference<awt::XWindow>& Panel::GetElementParentWindow()
+{
+ if (!mxXWindow)
+ {
+ if (mbWantsAWT)
+ mxXWindow = mxContents->CreateChildFrame();
+ else
+ mxXWindow = Reference<awt::XWindow>(new weld::TransportAsXWindow(mxContents.get()));
+ }
+ return mxXWindow;
+}
+
+} // end of namespace sfx2::sidebar
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/sidebar/PanelDescriptor.cxx b/sfx2/source/sidebar/PanelDescriptor.cxx
new file mode 100644
index 0000000000..6ca328de28
--- /dev/null
+++ b/sfx2/source/sidebar/PanelDescriptor.cxx
@@ -0,0 +1,57 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sidebar/PanelDescriptor.hxx>
+
+namespace sfx2::sidebar {
+
+PanelDescriptor::PanelDescriptor()
+ : mbIsTitleBarOptional(false),
+ mnOrderIndex(10000), // Default value as defined in Sidebar.xcs
+ mbShowForReadOnlyDocuments(false),
+ mbWantsCanvas(false),
+ mbWantsAWT(true),
+ mbExperimental(false)
+{
+}
+
+PanelDescriptor::PanelDescriptor (const PanelDescriptor& rOther)
+ : msTitle(rOther.msTitle),
+ mbIsTitleBarOptional(rOther.mbIsTitleBarOptional),
+ msId(rOther.msId),
+ msDeckId(rOther.msDeckId),
+ msTitleBarIconURL(rOther.msTitleBarIconURL),
+ msHighContrastTitleBarIconURL(rOther.msHighContrastTitleBarIconURL),
+ maContextList(rOther.maContextList),
+ msImplementationURL(rOther.msImplementationURL),
+ mnOrderIndex(rOther.mnOrderIndex),
+ mbShowForReadOnlyDocuments(rOther.mbShowForReadOnlyDocuments),
+ mbWantsCanvas(rOther.mbWantsCanvas),
+ mbWantsAWT(rOther.mbWantsAWT),
+ mbExperimental(rOther.mbExperimental)
+{
+}
+
+PanelDescriptor::~PanelDescriptor()
+{
+}
+
+} // end of namespace sfx2::sidebar
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/sidebar/PanelLayout.cxx b/sfx2/source/sidebar/PanelLayout.cxx
new file mode 100644
index 0000000000..afe018db8a
--- /dev/null
+++ b/sfx2/source/sidebar/PanelLayout.cxx
@@ -0,0 +1,86 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <sal/log.hxx>
+#include <sfx2/sidebar/Panel.hxx>
+#include <sfx2/sidebar/PanelLayout.hxx>
+#include <sfx2/sidebar/Theme.hxx>
+#include <sfx2/viewsh.hxx>
+#include <vcl/event.hxx>
+#include <vcl/svapp.hxx>
+
+using namespace sfx2::sidebar;
+
+PanelLayout::PanelLayout(weld::Widget* pParent, const OUString& rID, const OUString& rUIXMLDescription)
+ : m_xBuilder(Application::CreateBuilder(pParent, rUIXMLDescription, false, reinterpret_cast<sal_uInt64>(SfxViewShell::Current())))
+ , m_xContainer(m_xBuilder->weld_container(rID))
+ , m_pPanel(nullptr)
+{
+ m_xContainer->set_background(Theme::GetColor(Theme::Color_PanelBackground));
+ m_xContainer->connect_get_property_tree(LINK(this, PanelLayout, DumpAsPropertyTreeHdl));
+ ::Application::AddEventListener(LINK(this, PanelLayout, DataChangedEventListener));
+}
+
+IMPL_LINK(PanelLayout, DumpAsPropertyTreeHdl, tools::JsonWriter&, rJsonWriter, void)
+{
+ DumpAsPropertyTree(rJsonWriter);
+}
+
+void PanelLayout::DumpAsPropertyTree(tools::JsonWriter&)
+{
+}
+
+IMPL_LINK(PanelLayout, DataChangedEventListener, VclSimpleEvent&, rEvent, void)
+{
+ if (rEvent.GetId() != VclEventId::ApplicationDataChanged)
+ return;
+
+ DataChangedEvent* pData = static_cast<DataChangedEvent*>(static_cast<VclWindowEvent&>(rEvent).GetData());
+ DataChanged(*pData);
+}
+
+void PanelLayout::DataChanged(const DataChangedEvent& rEvent)
+{
+ if (rEvent.GetType() != DataChangedEventType::SETTINGS)
+ return;
+ if (rEvent.GetFlags() & AllSettingsFlags::STYLE)
+ m_xContainer->set_background(Theme::GetColor(Theme::Color_PanelBackground));
+}
+
+void PanelLayout::SetPanel(sfx2::sidebar::Panel* pPanel)
+{
+ m_pPanel = pPanel;
+}
+
+weld::Window* PanelLayout::GetFrameWeld() const
+{
+ if (!m_pPanel)
+ {
+ SAL_WARN("sfx.sidebar", "Expected a toplevel Panel to exist");
+ return nullptr;
+ }
+ return m_pPanel->GetFrameWeld();
+}
+
+PanelLayout::~PanelLayout()
+{
+ ::Application::RemoveEventListener(LINK(this, PanelLayout, DataChangedEventListener));
+
+ m_xContainer.reset();
+ m_xBuilder.reset();
+}
+
+void PanelLayout::queue_resize()
+{
+ if (!m_xContainer)
+ return;
+ m_xContainer->queue_resize();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/sidebar/PanelTitleBar.cxx b/sfx2/source/sidebar/PanelTitleBar.cxx
new file mode 100644
index 0000000000..8fa401f407
--- /dev/null
+++ b/sfx2/source/sidebar/PanelTitleBar.cxx
@@ -0,0 +1,123 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sidebar/PanelTitleBar.hxx>
+#include <sfx2/sidebar/Panel.hxx>
+#include <sfx2/sidebar/Theme.hxx>
+#include <sidebar/ControllerFactory.hxx>
+
+using namespace css;
+using namespace css::uno;
+
+namespace sfx2::sidebar {
+
+PanelTitleBar::PanelTitleBar(const OUString& rsTitle,
+ weld::Builder& rBuilder,
+ Panel* pPanel)
+ : TitleBar(rBuilder, Theme::Color_PanelTitleBarBackground),
+ mxExpander(rBuilder.weld_expander("expander")),
+ mpPanel(pPanel),
+ msIdent("button")
+{
+ mxExpander->set_label(rsTitle);
+ mxExpander->connect_expanded(LINK(this, PanelTitleBar, ExpandHdl));
+
+ // tdf#145801 lock the height to the size it needs with the "toolbar" button shown
+ // so all of the titlebars are the same height if the "toolbar" is hidden in some
+ // of them
+ mxToolBox->show();
+ mxTitlebar->set_size_request(-1, mxTitlebar->get_preferred_size().Height());
+ mxToolBox->hide();
+
+ assert(mpPanel);
+
+ UpdateExpandedState();
+}
+
+void PanelTitleBar::SetTitle(const OUString& rsTitle)
+{
+ mxExpander->set_label(rsTitle);
+}
+
+OUString PanelTitleBar::GetTitle() const
+{
+ return mxExpander->get_label();
+}
+
+void PanelTitleBar::UpdateExpandedState()
+{
+ mxExpander->set_expanded(mpPanel->IsExpanded());
+}
+
+PanelTitleBar::~PanelTitleBar()
+{
+ Reference<lang::XComponent> xComponent(mxController, UNO_QUERY);
+ if (xComponent.is())
+ xComponent->dispose();
+ mxController.clear();
+ mpPanel = nullptr;
+ mxExpander.reset();
+}
+
+void PanelTitleBar::SetMoreOptionsCommand(const OUString& rsCommandName,
+ const css::uno::Reference<css::frame::XFrame>& rxFrame,
+ const css::uno::Reference<css::frame::XController>& rxController)
+{
+ if (rsCommandName == msMoreOptionsCommand)
+ return;
+
+ if (!msMoreOptionsCommand.isEmpty())
+ mxToolBox->hide();
+
+ msMoreOptionsCommand = rsCommandName;
+
+ if (msMoreOptionsCommand.isEmpty())
+ return;
+
+ msIdent = msMoreOptionsCommand;
+ mxToolBox->set_item_ident(0, msIdent);
+
+ Reference<lang::XComponent> xComponent(mxController, UNO_QUERY);
+ if (xComponent.is())
+ xComponent->dispose();
+ mxController =
+ ControllerFactory::CreateToolBoxController(
+ *mxToolBox, mrBuilder, msMoreOptionsCommand, rxFrame, rxController, true);
+
+ mxToolBox->show();
+}
+
+void PanelTitleBar::HandleToolBoxItemClick()
+{
+ if (!mxController)
+ return;
+ mxController->click();
+ mxController->execute(0);
+}
+
+IMPL_LINK(PanelTitleBar, ExpandHdl, weld::Expander&, rExpander, void)
+{
+ if (!mpPanel)
+ return;
+ mpPanel->SetExpanded(rExpander.get_expanded());
+}
+
+} // end of namespace sfx2::sidebar
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/sidebar/ResourceManager.cxx b/sfx2/source/sidebar/ResourceManager.cxx
new file mode 100644
index 0000000000..d4e5b97228
--- /dev/null
+++ b/sfx2/source/sidebar/ResourceManager.cxx
@@ -0,0 +1,806 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sidebar/DeckDescriptor.hxx>
+#include <sidebar/PanelDescriptor.hxx>
+#include <sfx2/sidebar/ResourceManager.hxx>
+#include <sidebar/Tools.hxx>
+
+#include <officecfg/Office/Common.hxx>
+#include <officecfg/Office/UI/Sidebar.hxx>
+#include <unotools/confignode.hxx>
+#include <comphelper/lok.hxx>
+#include <comphelper/processfactory.hxx>
+#include <comphelper/namedvaluecollection.hxx>
+#include <comphelper/sequence.hxx>
+#include <comphelper/types.hxx>
+
+#include <comphelper/diagnose_ex.hxx>
+#include <sal/log.hxx>
+#include <vcl/EnumContext.hxx>
+#include <o3tl/string_view.hxx>
+
+#include <com/sun/star/frame/ModuleManager.hpp>
+#include <com/sun/star/ui/XSidebarPanel.hpp>
+#include <com/sun/star/ui/XUpdateModel.hpp>
+
+#include <map>
+
+using namespace css;
+using namespace css::uno;
+
+namespace sfx2::sidebar {
+
+namespace
+{
+
+OUString getString(utl::OConfigurationNode const & aNode, const char* pNodeName)
+{
+ return comphelper::getString(aNode.getNodeValue(pNodeName));
+}
+sal_Int32 getInt32(utl::OConfigurationNode const & aNode, const char* pNodeName)
+{
+ return comphelper::getINT32(aNode.getNodeValue(pNodeName));
+}
+bool getBool(utl::OConfigurationNode const & aNode, const char* pNodeName)
+{
+ return comphelper::getBOOL(aNode.getNodeValue(pNodeName));
+}
+
+css::uno::Sequence<OUString> BuildContextList (const ContextList& rContextList)
+{
+ const ::std::vector<ContextList::Entry>& entries = rContextList.GetEntries();
+
+ css::uno::Sequence<OUString> result(entries.size());
+ auto resultRange = asNonConstRange(result);
+ tools::Long i = 0;
+
+ for (auto const& entry : entries)
+ {
+ OUString appName = entry.maContext.msApplication;
+ OUString contextName = entry.maContext.msContext;
+ OUString menuCommand = entry.msMenuCommand;
+
+ OUString visibility;
+ if (entry.mbIsInitiallyVisible)
+ visibility = "visible";
+ else
+ visibility = "hidden";
+
+ OUString element = appName + ", " + contextName +", " + visibility;
+
+ if (!menuCommand.isEmpty())
+ element += ", "+menuCommand;
+
+ resultRange[i] = element;
+
+ ++i;
+ }
+
+ return result;
+
+}
+
+} //end anonymous namespace
+
+ResourceManager::ResourceManager()
+{
+ ReadDeckList();
+ ReadPanelList();
+ ReadLastActive();
+}
+
+ResourceManager::~ResourceManager()
+{
+}
+
+void ResourceManager::InitDeckContext(const Context& rContext)
+{
+ for (auto const& deck : maDecks)
+ {
+ const ContextList::Entry* pMatchingEntry = deck->maContextList.GetMatch(rContext);
+
+ bool bIsEnabled;
+ if (pMatchingEntry)
+ bIsEnabled = pMatchingEntry->mbIsInitiallyVisible;
+ else
+ bIsEnabled = false;
+
+ deck->mbIsEnabled = bIsEnabled;
+ }
+}
+
+std::shared_ptr<DeckDescriptor> ResourceManager::ImplGetDeckDescriptor(std::u16string_view rsDeckId) const
+{
+ for (auto const& deck : maDecks)
+ {
+ if (deck->mbExperimental && !officecfg::Office::Common::Misc::ExperimentalMode::get())
+ continue;
+ if (deck->msId == rsDeckId)
+ return deck;
+ }
+ return nullptr;
+}
+
+std::shared_ptr<DeckDescriptor> ResourceManager::GetDeckDescriptor(std::u16string_view rsDeckId) const
+{
+ return ImplGetDeckDescriptor( rsDeckId );
+}
+
+std::shared_ptr<PanelDescriptor> ResourceManager::ImplGetPanelDescriptor(std::u16string_view rsPanelId) const
+{
+ for (auto const& panel : maPanels)
+ {
+ if (panel->msId == rsPanelId)
+ return panel;
+ }
+ return nullptr;
+}
+
+std::shared_ptr<PanelDescriptor> ResourceManager::GetPanelDescriptor(std::u16string_view rsPanelId) const
+{
+ return ImplGetPanelDescriptor( rsPanelId );
+}
+
+const ResourceManager::DeckContextDescriptorContainer& ResourceManager::GetMatchingDecks (
+ DeckContextDescriptorContainer& rDecks,
+ const Context& rContext,
+ const bool bIsDocumentReadOnly,
+ const Reference<frame::XController>& rxController)
+{
+ ReadLegacyAddons(rxController);
+
+ std::multimap<sal_Int32,DeckContextDescriptor> aOrderedIds;
+ for (auto const& deck : maDecks)
+ {
+ if (deck->mbExperimental && !officecfg::Office::Common::Misc::ExperimentalMode::get())
+ continue;
+
+ const DeckDescriptor& rDeckDescriptor (*deck);
+ if (rDeckDescriptor.maContextList.GetMatch(rContext) == nullptr)
+ continue;
+
+ DeckContextDescriptor aDeckContextDescriptor;
+ aDeckContextDescriptor.msId = rDeckDescriptor.msId;
+
+ aDeckContextDescriptor.mbIsEnabled = (! bIsDocumentReadOnly || IsDeckEnabled(rDeckDescriptor.msId, rContext, rxController) )
+ && rDeckDescriptor.mbIsEnabled;
+
+
+ aOrderedIds.emplace(rDeckDescriptor.mnOrderIndex, aDeckContextDescriptor);
+ }
+
+ for (auto const& orderId : aOrderedIds)
+ {
+ rDecks.push_back(orderId.second);
+ }
+
+ return rDecks;
+}
+
+const ResourceManager::PanelContextDescriptorContainer& ResourceManager::GetMatchingPanels (
+ PanelContextDescriptorContainer& rPanelIds,
+ const Context& rContext,
+ std::u16string_view sDeckId,
+ const Reference<frame::XController>& rxController)
+{
+ ReadLegacyAddons(rxController);
+
+ std::multimap<sal_Int32, PanelContextDescriptor> aOrderedIds;
+ for (auto const& panel : maPanels)
+ {
+ const PanelDescriptor& rPanelDescriptor (*panel);
+ if (rPanelDescriptor.mbExperimental && !officecfg::Office::Common::Misc::ExperimentalMode::get())
+ continue;
+ if ( rPanelDescriptor.msDeckId != sDeckId )
+ continue;
+
+ const ContextList::Entry* pEntry = rPanelDescriptor.maContextList.GetMatch(rContext);
+ if (pEntry == nullptr)
+ continue;
+
+ PanelContextDescriptor aPanelContextDescriptor;
+ aPanelContextDescriptor.msId = rPanelDescriptor.msId;
+ aPanelContextDescriptor.msMenuCommand = pEntry->msMenuCommand;
+ aPanelContextDescriptor.mbIsInitiallyVisible = pEntry->mbIsInitiallyVisible;
+ aPanelContextDescriptor.mbShowForReadOnlyDocuments = rPanelDescriptor.mbShowForReadOnlyDocuments;
+ aOrderedIds.emplace(rPanelDescriptor.mnOrderIndex, aPanelContextDescriptor);
+ }
+
+ for (auto const& orderId : aOrderedIds)
+ {
+ rPanelIds.push_back(orderId.second);
+ }
+
+ return rPanelIds;
+}
+
+const OUString& ResourceManager::GetLastActiveDeck( const Context& rContext )
+{
+ if( maLastActiveDecks.find( rContext.msApplication ) == maLastActiveDecks.end())
+ return maLastActiveDecks["any"];
+ else
+ return maLastActiveDecks[rContext.msApplication];
+}
+
+void ResourceManager::SetLastActiveDeck( const Context& rContext, const OUString &rsDeckId )
+{
+ maLastActiveDecks[rContext.msApplication] = rsDeckId;
+}
+
+void ResourceManager::ReadDeckList()
+{
+ const utl::OConfigurationTreeRoot aDeckRootNode(
+ comphelper::getProcessComponentContext(),
+ "org.openoffice.Office.UI.Sidebar/Content/DeckList",
+ false);
+ if (!aDeckRootNode.isValid())
+ return;
+
+ const Sequence<OUString> aDeckNodeNames (aDeckRootNode.getNodeNames());
+ maDecks.clear();
+ for (const OUString& aDeckName : aDeckNodeNames)
+ {
+ if (comphelper::LibreOfficeKit::isActive())
+ {
+ // Hide these decks in LOK as they aren't fully functional.
+ if (aDeckName == "GalleryDeck" || aDeckName == "StyleListDeck")
+ continue;
+ }
+
+ const utl::OConfigurationNode aDeckNode(aDeckRootNode.openNode(aDeckName));
+ if (!aDeckNode.isValid())
+ continue;
+
+ maDecks.push_back(std::make_shared<DeckDescriptor>());
+ DeckDescriptor& rDeckDescriptor (*maDecks.back());
+
+ rDeckDescriptor.msTitle = getString(aDeckNode, "Title");
+ rDeckDescriptor.msId = getString(aDeckNode, "Id");
+ rDeckDescriptor.msIconURL = getString(aDeckNode, "IconURL");
+ rDeckDescriptor.msHighContrastIconURL = getString(aDeckNode, "HighContrastIconURL");
+ rDeckDescriptor.msTitleBarIconURL = getString(aDeckNode, "TitleBarIconURL");
+ rDeckDescriptor.msHighContrastTitleBarIconURL = getString(aDeckNode, "HighContrastTitleBarIconURL");
+ rDeckDescriptor.msHelpText = rDeckDescriptor.msTitle;
+ rDeckDescriptor.msHelpId = "SIDEBAR_" + rDeckDescriptor.msId.toAsciiUpperCase();
+ rDeckDescriptor.mnOrderIndex = getInt32(aDeckNode, "OrderIndex");
+ rDeckDescriptor.mbExperimental = getBool(aDeckNode, "IsExperimental");
+
+ rDeckDescriptor.msNodeName = aDeckName;
+
+ ReadContextList(
+ aDeckNode,
+ rDeckDescriptor.maContextList,
+ OUString());
+
+ }
+}
+
+void ResourceManager::SaveDecksSettings(const Context& rContext)
+{
+ for (auto const& deck : maDecks)
+ {
+ const ContextList::Entry* pMatchingEntry = deck->maContextList.GetMatch(rContext);
+ if (pMatchingEntry)
+ {
+ std::shared_ptr<DeckDescriptor> xDeckDesc = GetDeckDescriptor(deck->msId);
+ if (xDeckDesc)
+ SaveDeckSettings(xDeckDesc.get());
+ }
+
+ }
+}
+
+void ResourceManager::SaveDeckSettings(const DeckDescriptor* pDeckDesc)
+{
+ const utl::OConfigurationTreeRoot aDeckRootNode(
+ comphelper::getProcessComponentContext(),
+ "org.openoffice.Office.UI.Sidebar/Content/DeckList",
+ true);
+ if (!aDeckRootNode.isValid())
+ return;
+
+ // save deck settings
+
+ ::uno::Sequence< OUString > sContextList = BuildContextList(pDeckDesc->maContextList);
+
+ utl::OConfigurationNode aDeckNode (aDeckRootNode.openNode(pDeckDesc->msNodeName));
+
+ css::uno::Any aTitle(Any(pDeckDesc->msTitle));
+ css::uno::Any aOrder(Any(pDeckDesc->mnOrderIndex));
+ css::uno::Any aContextList(sContextList);
+
+ bool bChanged = false;
+ if (aTitle != aDeckNode.getNodeValue("Title"))
+ {
+ aDeckNode.setNodeValue("Title", aTitle);
+ bChanged = true;
+ }
+ if (aOrder != aDeckNode.getNodeValue("OrderIndex"))
+ {
+ aDeckNode.setNodeValue("OrderIndex", aOrder);
+ bChanged = true;
+ }
+ if (aContextList != aDeckNode.getNodeValue("ContextList"))
+ {
+ aDeckNode.setNodeValue("ContextList", aContextList);
+ bChanged = true;
+ }
+
+ if (bChanged)
+ aDeckRootNode.commit();
+
+ // save panel settings
+
+ const utl::OConfigurationTreeRoot aPanelRootNode(
+ comphelper::getProcessComponentContext(),
+ "org.openoffice.Office.UI.Sidebar/Content/PanelList",
+ true);
+
+ if (!aPanelRootNode.isValid())
+ return;
+
+ if (!pDeckDesc->mpDeck) // the deck has not been edited
+ return;
+
+ SharedPanelContainer rPanels = pDeckDesc->mpDeck->GetPanels();
+
+ bChanged = false;
+ for (auto const& panel : rPanels)
+ {
+ OUString panelId = panel->GetId();
+ std::shared_ptr<PanelDescriptor> xPanelDesc = GetPanelDescriptor(panelId);
+
+ ::uno::Sequence< OUString > sPanelContextList = BuildContextList(xPanelDesc->maContextList);
+
+ utl::OConfigurationNode aPanelNode (aPanelRootNode.openNode(xPanelDesc->msNodeName));
+
+ aTitle <<= xPanelDesc->msTitle;
+ aOrder <<= xPanelDesc->mnOrderIndex;
+ aContextList <<= sPanelContextList;
+
+ if (aTitle != aPanelNode.getNodeValue("Title"))
+ {
+ aPanelNode.setNodeValue("Title", aTitle);
+ bChanged = true;
+ }
+ if (aOrder != aPanelNode.getNodeValue("OrderIndex"))
+ {
+ aPanelNode.setNodeValue("OrderIndex", aOrder);
+ bChanged = true;
+ }
+ if (aContextList != aPanelNode.getNodeValue("ContextList"))
+ {
+ aPanelNode.setNodeValue("ContextList", aContextList);
+ bChanged = true;
+ }
+ }
+
+ if (bChanged)
+ aPanelRootNode.commit();
+}
+
+void ResourceManager::SaveLastActiveDeck(const Context& rContext, const OUString& rActiveDeck)
+{
+ maLastActiveDecks[rContext.msApplication] = rActiveDeck;
+
+ std::set<OUString> aLastActiveDecks;
+ for ( auto const & rEntry : maLastActiveDecks )
+ aLastActiveDecks.insert( rEntry.first + "," + rEntry.second);
+
+ std::shared_ptr<comphelper::ConfigurationChanges> cfgWriter( comphelper::ConfigurationChanges::create() );
+
+ officecfg::Office::UI::Sidebar::Content::LastActiveDeck::set(comphelper::containerToSequence(aLastActiveDecks), cfgWriter);
+ cfgWriter->commit();
+
+}
+
+void ResourceManager::ReadPanelList()
+{
+ const utl::OConfigurationTreeRoot aPanelRootNode(
+ comphelper::getProcessComponentContext(),
+ "org.openoffice.Office.UI.Sidebar/Content/PanelList",
+ false);
+ if (!aPanelRootNode.isValid())
+ return;
+
+ const Sequence<OUString> aPanelNodeNames (aPanelRootNode.getNodeNames());
+ maPanels.clear();
+ for (const auto& rPanelNodeName : aPanelNodeNames)
+ {
+ const utl::OConfigurationNode aPanelNode (aPanelRootNode.openNode(rPanelNodeName));
+ if (!aPanelNode.isValid())
+ continue;
+
+ if (comphelper::LibreOfficeKit::isActive())
+ {
+ // Hide these panels in LOK as they aren't fully functional.
+ OUString aPanelId = getString(aPanelNode, "Id");
+ if (aPanelId == "PageStylesPanel" || aPanelId == "PageHeaderPanel"
+ || aPanelId == "PageFooterPanel")
+ continue;
+ }
+
+ maPanels.push_back(std::make_shared<PanelDescriptor>());
+ PanelDescriptor& rPanelDescriptor(*maPanels.back());
+
+ rPanelDescriptor.msTitle = getString(aPanelNode, "Title");
+ rPanelDescriptor.mbIsTitleBarOptional = getBool(aPanelNode, "TitleBarIsOptional");
+ rPanelDescriptor.msId = getString(aPanelNode, "Id");
+ rPanelDescriptor.msDeckId = getString(aPanelNode, "DeckId");
+ rPanelDescriptor.msTitleBarIconURL = getString(aPanelNode, "TitleBarIconURL");
+ rPanelDescriptor.msHighContrastTitleBarIconURL = getString(aPanelNode, "HighContrastTitleBarIconURL");
+ rPanelDescriptor.msImplementationURL = getString(aPanelNode, "ImplementationURL");
+ rPanelDescriptor.mnOrderIndex = getInt32(aPanelNode, "OrderIndex");
+ rPanelDescriptor.mbShowForReadOnlyDocuments = getBool(aPanelNode, "ShowForReadOnlyDocument");
+ rPanelDescriptor.mbWantsCanvas = getBool(aPanelNode, "WantsCanvas");
+ rPanelDescriptor.mbWantsAWT = getBool(aPanelNode, "WantsAWT");
+ rPanelDescriptor.mbExperimental = getBool(aPanelNode, "IsExperimental");
+ const OUString sDefaultMenuCommand(getString(aPanelNode, "DefaultMenuCommand"));
+
+ rPanelDescriptor.msNodeName = rPanelNodeName;
+
+ ReadContextList(aPanelNode, rPanelDescriptor.maContextList, sDefaultMenuCommand);
+ }
+}
+
+void ResourceManager::ReadLastActive()
+{
+ const Sequence <OUString> aLastActive (officecfg::Office::UI::Sidebar::Content::LastActiveDeck::get());
+
+ for (const auto& rDeckInfo : aLastActive)
+ {
+ sal_Int32 nCharIdx = rDeckInfo.lastIndexOf(',');
+ if ( nCharIdx <= 0 || (nCharIdx == rDeckInfo.getLength() - 1) )
+ {
+ SAL_WARN("sfx.sidebar", "Expecting 2 values separated by comma");
+ continue;
+ }
+
+ const OUString sApplicationName = rDeckInfo.copy( 0, nCharIdx );
+ vcl::EnumContext::Application eApplication (vcl::EnumContext::GetApplicationEnum(sApplicationName));
+ const OUString sLastUsed = rDeckInfo.copy( nCharIdx + 1 );
+
+ // guard against garbage in place of application
+ if (eApplication != vcl::EnumContext::Application::NONE)
+ maLastActiveDecks.insert( std::make_pair(sApplicationName, sLastUsed ) );
+ }
+
+ // Set up a default for Math - will do nothing if already set
+ maLastActiveDecks.emplace(
+ vcl::EnumContext::GetApplicationName(vcl::EnumContext::Application::Formula),
+ "ElementsDeck");
+}
+
+void ResourceManager::ReadContextList (
+ const utl::OConfigurationNode& rParentNode,
+ ContextList& rContextList,
+ const OUString& rsDefaultMenuCommand)
+{
+ const Any aValue = rParentNode.getNodeValue("ContextList");
+ Sequence<OUString> aValues;
+ if (!(aValue >>= aValues))
+ return;
+
+ for (const OUString& sValue : std::as_const(aValues))
+ {
+ sal_Int32 nCharacterIndex (0);
+ const OUString sApplicationName (o3tl::trim(o3tl::getToken(sValue, 0, ',', nCharacterIndex)));
+ if (nCharacterIndex < 0)
+ {
+ if (sApplicationName.getLength() == 0)
+ {
+ // This is a valid case: in the XML file the separator
+ // was used as terminator. Using it in the last line
+ // creates an additional but empty entry.
+ break;
+ }
+ else
+ {
+ OSL_FAIL("expecting three or four values per ContextList entry, separated by comma");
+ continue;
+ }
+ }
+
+ const OUString sContextName(o3tl::trim(o3tl::getToken(sValue, 0, ',', nCharacterIndex)));
+ if (nCharacterIndex < 0)
+ {
+ OSL_FAIL("expecting three or four values per ContextList entry, separated by comma");
+ continue;
+ }
+
+ const std::u16string_view sInitialState(o3tl::trim(o3tl::getToken(sValue, 0, ',', nCharacterIndex)));
+
+ // The fourth argument is optional.
+ const OUString sMenuCommandOverride(
+ nCharacterIndex < 0
+ ? OUString()
+ : OUString(o3tl::trim(o3tl::getToken(sValue, 0, ',', nCharacterIndex))));
+
+ const OUString sMenuCommand(
+ sMenuCommandOverride.getLength() > 0
+ ? (sMenuCommandOverride == "none"
+ ? OUString()
+ : sMenuCommandOverride)
+ : rsDefaultMenuCommand);
+
+ // Setup a list of application enums. Note that the
+ // application name may result in more than one value (eg
+ // DrawImpress will result in two enums, one for Draw and one
+ // for Impress).
+ std::vector<vcl::EnumContext::Application> aApplications;
+ vcl::EnumContext::Application eApplication (vcl::EnumContext::GetApplicationEnum(sApplicationName));
+
+ if (eApplication == vcl::EnumContext::Application::NONE
+ && sApplicationName != vcl::EnumContext::GetApplicationName(vcl::EnumContext::Application::NONE))
+ {
+ // Handle some special names: abbreviations that make
+ // context descriptions more readable.
+ if (sApplicationName == "Writer")
+ aApplications.push_back(vcl::EnumContext::Application::Writer);
+ else if (sApplicationName == "Calc")
+ aApplications.push_back(vcl::EnumContext::Application::Calc);
+ else if (sApplicationName == "Draw")
+ aApplications.push_back(vcl::EnumContext::Application::Draw);
+ else if (sApplicationName == "Impress")
+ aApplications.push_back(vcl::EnumContext::Application::Impress);
+ else if (sApplicationName == "Chart")
+ aApplications.push_back(vcl::EnumContext::Application::Chart);
+ else if (sApplicationName == "Math")
+ aApplications.push_back(vcl::EnumContext::Application::Formula);
+ else if (sApplicationName == "DrawImpress")
+ {
+ // A special case among the special names: it is
+ // common to use the same context descriptions for
+ // both Draw and Impress. This special case helps to
+ // avoid duplication in the .xcu file.
+ aApplications.push_back(vcl::EnumContext::Application::Draw);
+ aApplications.push_back(vcl::EnumContext::Application::Impress);
+ }
+ else if (sApplicationName == "WriterVariants")
+ {
+ // Another special case for all Writer variants.
+ aApplications.push_back(vcl::EnumContext::Application::Writer);
+ aApplications.push_back(vcl::EnumContext::Application::WriterGlobal);
+ aApplications.push_back(vcl::EnumContext::Application::WriterWeb);
+ aApplications.push_back(vcl::EnumContext::Application::WriterXML);
+ aApplications.push_back(vcl::EnumContext::Application::WriterForm);
+ aApplications.push_back(vcl::EnumContext::Application::WriterReport);
+ }
+ else
+ {
+ SAL_WARN("sfx.sidebar", "application name " << sApplicationName << " not recognized");
+ continue;
+ }
+ }
+ else
+ {
+ // No conversion of the application name necessary.
+ aApplications.push_back(eApplication);
+ }
+
+ // Setup the actual context enum.
+ const vcl::EnumContext::Context eContext (vcl::EnumContext::GetContextEnum(sContextName));
+ if (eContext == vcl::EnumContext::Context::Unknown)
+ {
+ SAL_WARN("sfx.sidebar", "context name " << sContextName << " not recognized");
+ continue;
+ }
+
+ // Setup the flag that controls whether a deck/pane is
+ // initially visible/expanded.
+ bool bIsInitiallyVisible;
+ if (sInitialState == u"visible")
+ bIsInitiallyVisible = true;
+ else if (sInitialState == u"hidden")
+ bIsInitiallyVisible = false;
+ else
+ {
+ OSL_FAIL("unrecognized state");
+ continue;
+ }
+
+
+ // Add context descriptors.
+ for (auto const& application : aApplications)
+ {
+ if (application != vcl::EnumContext::Application::NONE)
+ {
+ rContextList.AddContextDescription(
+ Context(
+ vcl::EnumContext::GetApplicationName(application),
+ vcl::EnumContext::GetContextName(eContext)),
+ bIsInitiallyVisible,
+ sMenuCommand);
+ }
+ }
+ }
+}
+
+void ResourceManager::ReadLegacyAddons (const Reference<frame::XController>& rxController)
+{
+ // Get module name for given frame.
+ OUString sModuleName (Tools::GetModuleName(rxController));
+ if (sModuleName.getLength() == 0)
+ return;
+ if (maProcessedApplications.find(sModuleName) != maProcessedApplications.end())
+ {
+ // Addons for this application have already been read.
+ // There is nothing more to do.
+ return;
+ }
+
+ // Mark module as processed. Even when there is an error that
+ // prevents the configuration data from being read, this error
+ // will not be triggered a second time.
+ maProcessedApplications.insert(sModuleName);
+
+ // Get access to the configuration root node for the application.
+ utl::OConfigurationTreeRoot aLegacyRootNode (GetLegacyAddonRootNode(sModuleName));
+ if (!aLegacyRootNode.isValid())
+ return;
+
+ // Process child nodes.
+ std::vector<OUString> aMatchingNodeNames;
+ GetToolPanelNodeNames(aMatchingNodeNames, aLegacyRootNode);
+ const sal_Int32 nCount (aMatchingNodeNames.size());
+ for (sal_Int32 nReadIndex(0); nReadIndex<nCount; ++nReadIndex)
+ {
+ const OUString& rsNodeName (aMatchingNodeNames[nReadIndex]);
+ const utl::OConfigurationNode aChildNode (aLegacyRootNode.openNode(rsNodeName));
+ if (!aChildNode.isValid())
+ continue;
+
+ if ( rsNodeName == "private:resource/toolpanel/DrawingFramework/CustomAnimations" ||
+ rsNodeName == "private:resource/toolpanel/DrawingFramework/Layouts" ||
+ rsNodeName == "private:resource/toolpanel/DrawingFramework/MasterPages" ||
+ rsNodeName == "private:resource/toolpanel/DrawingFramework/SlideTransitions" ||
+ rsNodeName == "private:resource/toolpanel/DrawingFramework/TableDesign" )
+ continue;
+
+ maDecks.push_back(std::make_shared<DeckDescriptor>());
+ DeckDescriptor& rDeckDescriptor(*maDecks.back());
+ rDeckDescriptor.msTitle = getString(aChildNode, "UIName");
+ rDeckDescriptor.msId = rsNodeName;
+ rDeckDescriptor.msIconURL = getString(aChildNode, "ImageURL");
+ rDeckDescriptor.msHighContrastIconURL = rDeckDescriptor.msIconURL;
+ rDeckDescriptor.msTitleBarIconURL.clear();
+ rDeckDescriptor.msHighContrastTitleBarIconURL.clear();
+ rDeckDescriptor.msHelpText = rDeckDescriptor.msTitle;
+ rDeckDescriptor.mbIsEnabled = true;
+ rDeckDescriptor.mnOrderIndex = 100000 + nReadIndex;
+ rDeckDescriptor.maContextList.AddContextDescription(Context(sModuleName, "any"), true, OUString());
+
+ maPanels.push_back(std::make_shared<PanelDescriptor>());
+ PanelDescriptor& rPanelDescriptor(*maPanels.back());
+ rPanelDescriptor.msTitle = getString(aChildNode, "UIName");
+ rPanelDescriptor.mbIsTitleBarOptional = true;
+ rPanelDescriptor.msId = rsNodeName;
+ rPanelDescriptor.msDeckId = rsNodeName;
+ rPanelDescriptor.msTitleBarIconURL.clear();
+ rPanelDescriptor.msHighContrastTitleBarIconURL.clear();
+ rPanelDescriptor.msImplementationURL = rsNodeName;
+ rPanelDescriptor.mnOrderIndex = 100000 + nReadIndex;
+ rPanelDescriptor.mbShowForReadOnlyDocuments = false;
+ rPanelDescriptor.mbWantsCanvas = false;
+ rPanelDescriptor.mbWantsAWT = true;
+ rPanelDescriptor.maContextList.AddContextDescription(Context(sModuleName, "any"), true, OUString());
+ }
+}
+
+void ResourceManager::StorePanelExpansionState (
+ std::u16string_view rsPanelId,
+ const bool bExpansionState,
+ const Context& rContext)
+{
+ for (auto const& panel : maPanels)
+ {
+ if (panel->msId == rsPanelId)
+ {
+ ContextList::Entry* pEntry(panel->maContextList.GetMatch(rContext));
+ if (pEntry != nullptr)
+ pEntry->mbIsInitiallyVisible = bExpansionState;
+ }
+ }
+}
+
+utl::OConfigurationTreeRoot ResourceManager::GetLegacyAddonRootNode (const OUString& rsModuleName)
+{
+ try
+ {
+ const Reference<XComponentContext> xContext(comphelper::getProcessComponentContext());
+ const Reference<frame::XModuleManager2> xModuleAccess = frame::ModuleManager::create(xContext);
+ const comphelper::NamedValueCollection aModuleProperties(xModuleAccess->getByName(rsModuleName));
+ const OUString sWindowStateRef(aModuleProperties.getOrDefault(
+ "ooSetupFactoryWindowStateConfigRef",
+ OUString()));
+
+ OUString aPathComposer = "org.openoffice.Office.UI." + sWindowStateRef +
+ "/UIElements/States";
+
+ return utl::OConfigurationTreeRoot(xContext, aPathComposer, false);
+ }
+ catch (const Exception&)
+ {
+ DBG_UNHANDLED_EXCEPTION("sfx.sidebar");
+ }
+
+ return utl::OConfigurationTreeRoot();
+}
+
+void ResourceManager::GetToolPanelNodeNames (
+ std::vector<OUString>& rMatchingNames,
+ const utl::OConfigurationTreeRoot& aRoot)
+{
+ const Sequence<OUString> aChildNodeNames (aRoot.getNodeNames());
+ std::copy_if(aChildNodeNames.begin(), aChildNodeNames.end(), std::back_inserter(rMatchingNames),
+ [](const OUString& rChildNodeName) { return rChildNodeName.startsWith( "private:resource/toolpanel/" ); });
+}
+
+bool ResourceManager::IsDeckEnabled (
+ std::u16string_view rsDeckId,
+ const Context& rContext,
+ const Reference<frame::XController>& rxController)
+{
+
+ // Check if any panel that matches the current context can be
+ // displayed.
+ PanelContextDescriptorContainer aPanelContextDescriptors;
+
+ GetMatchingPanels(aPanelContextDescriptors, rContext, rsDeckId, rxController);
+
+ for (auto const& panelContextDescriptor : aPanelContextDescriptors)
+ {
+ if (panelContextDescriptor.mbShowForReadOnlyDocuments)
+ return true;
+ }
+ return false;
+}
+
+void ResourceManager::UpdateModel(const css::uno::Reference<css::frame::XModel>& xModel)
+{
+ for (auto const& deck : maDecks)
+ {
+ if (!deck->mpDeck)
+ continue;
+
+ const SharedPanelContainer& rContainer = deck->mpDeck->GetPanels();
+
+ for (auto const& elem : rContainer)
+ {
+ css::uno::Reference<css::ui::XUpdateModel> xPanel(elem->GetPanelComponent(), css::uno::UNO_QUERY);
+ if (xPanel.is()) // tdf#108814 interface is optional
+ {
+ xPanel->updateModel(xModel);
+ }
+ }
+ }
+}
+
+void ResourceManager::disposeDecks()
+{
+ for (auto const& deck : maDecks)
+ {
+ deck->mpDeck.disposeAndClear();
+ }
+}
+
+} // end of namespace sfx2::sidebar
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/sidebar/Sidebar.cxx b/sfx2/source/sidebar/Sidebar.cxx
new file mode 100644
index 0000000000..e56a259c5e
--- /dev/null
+++ b/sfx2/source/sidebar/Sidebar.cxx
@@ -0,0 +1,128 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sfx2/sidebar/Sidebar.hxx>
+#include <sfx2/sidebar/SidebarController.hxx>
+#include <sfx2/sidebar/ResourceManager.hxx>
+#include <sidebar/PanelDescriptor.hxx>
+#include <sidebar/Tools.hxx>
+#include <sfx2/sidebar/FocusManager.hxx>
+#include <sfx2/childwin.hxx>
+#include <sfx2/sfxsids.hrc>
+#include <com/sun/star/frame/XDispatch.hpp>
+
+using namespace css;
+
+namespace sfx2::sidebar {
+
+void Sidebar::ShowDeck(std::u16string_view rsDeckId, SfxViewFrame* pViewFrame, bool bToggle)
+{
+ if (!pViewFrame)
+ return;
+
+ SfxChildWindow* pSidebarChildWindow = pViewFrame->GetChildWindow(SID_SIDEBAR);
+ bool bInitiallyVisible = pSidebarChildWindow && pSidebarChildWindow->IsVisible();
+ if (!bInitiallyVisible)
+ pViewFrame->ShowChildWindow(SID_SIDEBAR);
+
+ SidebarController* pController =
+ SidebarController::GetSidebarControllerForFrame(pViewFrame->GetFrame().GetFrameInterface());
+ if (!pController)
+ return;
+
+ if (bToggle && bInitiallyVisible && pController->IsDeckVisible(rsDeckId))
+ {
+ // close the sidebar if it was already visible and showing this sidebar deck
+ const util::URL aURL(Tools::GetURL(".uno:Sidebar"));
+ css::uno::Reference<frame::XDispatch> xDispatch(Tools::GetDispatch(pViewFrame->GetFrame().GetFrameInterface(), aURL));
+ if (xDispatch.is())
+ xDispatch->dispatch(aURL, css::uno::Sequence<beans::PropertyValue>());
+ }
+ else
+ {
+ pController->OpenThenSwitchToDeck(rsDeckId);
+ pController->GetFocusManager().GrabFocusPanel();
+ }
+}
+
+void Sidebar::ShowPanel (
+ std::u16string_view rsPanelId,
+ const css::uno::Reference<frame::XFrame>& rxFrame, bool bFocus)
+{
+ SidebarController* pController = SidebarController::GetSidebarControllerForFrame(rxFrame);
+ if (!pController)
+ return;
+
+ std::shared_ptr<PanelDescriptor> xPanelDescriptor = pController->GetResourceManager()->GetPanelDescriptor(rsPanelId);
+
+ if (!xPanelDescriptor)
+ return;
+
+ // This should be a lot more sophisticated:
+ // - Make the deck switching asynchronous
+ // - Make sure to use a context that really shows the panel
+
+ // All that is not necessary for the current use cases so lets
+ // keep it simple for the time being.
+ pController->OpenThenSwitchToDeck(xPanelDescriptor->msDeckId);
+
+ if (bFocus)
+ pController->GetFocusManager().GrabFocusPanel();
+}
+
+void Sidebar::TogglePanel (
+ std::u16string_view rsPanelId,
+ const css::uno::Reference<frame::XFrame>& rxFrame)
+{
+ SidebarController* pController = SidebarController::GetSidebarControllerForFrame(rxFrame);
+ if (!pController)
+ return;
+
+ std::shared_ptr<PanelDescriptor> xPanelDescriptor = pController->GetResourceManager()->GetPanelDescriptor(rsPanelId);
+
+ if (!xPanelDescriptor)
+ return;
+
+ // This should be a lot more sophisticated:
+ // - Make the deck switching asynchronous
+ // - Make sure to use a context that really shows the panel
+
+ // All that is not necessary for the current use cases so lets
+ // keep it simple for the time being.
+ pController->OpenThenToggleDeck(xPanelDescriptor->msDeckId);
+}
+
+bool Sidebar::IsPanelVisible(
+ std::u16string_view rsPanelId,
+ const css::uno::Reference<frame::XFrame>& rxFrame)
+{
+ SidebarController* pController = SidebarController::GetSidebarControllerForFrame(rxFrame);
+ if (!pController)
+ return false;
+
+ std::shared_ptr<PanelDescriptor> xPanelDescriptor = pController->GetResourceManager()->GetPanelDescriptor(rsPanelId);
+ if (!xPanelDescriptor)
+ return false;
+
+ return pController->IsDeckVisible(xPanelDescriptor->msDeckId);
+}
+
+} // end of namespace sfx2::sidebar
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/sidebar/SidebarChildWindow.cxx b/sfx2/source/sidebar/SidebarChildWindow.cxx
new file mode 100644
index 0000000000..272c280488
--- /dev/null
+++ b/sfx2/source/sidebar/SidebarChildWindow.cxx
@@ -0,0 +1,95 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sfx2/sidebar/TabBar.hxx>
+#include <sfx2/bindings.hxx>
+#include <sfx2/sidebar/SidebarChildWindow.hxx>
+#include <sfx2/sidebar/SidebarDockingWindow.hxx>
+#include <sfx2/sfxsids.hrc>
+#include <helpids.h>
+#include <comphelper/lok.hxx>
+
+namespace sfx2::sidebar {
+
+SFX_IMPL_DOCKINGWINDOW_WITHID(SidebarChildWindow, SID_SIDEBAR);
+
+SidebarChildWindow::SidebarChildWindow(vcl::Window* pParentWindow, sal_uInt16 nId,
+ SfxBindings* pBindings, SfxChildWinInfo* pInfo)
+ : SfxChildWindow(pParentWindow, nId)
+{
+ auto pDockWin = VclPtr<SidebarDockingWindow>::Create(
+ pBindings, *this, pParentWindow, WB_STDDOCKWIN | WB_OWNERDRAWDECORATION | WB_CLIPCHILDREN
+ | WB_SIZEABLE | WB_3DLOOK);
+ SetWindow(pDockWin);
+ SetAlignment(SfxChildAlignment::RIGHT);
+
+ pDockWin->SetHelpId(HID_SIDEBAR_WINDOW);
+ pDockWin->SetOutputSizePixel(Size(GetDefaultWidth(pDockWin), 450));
+
+ if (pInfo && pInfo->aExtraString.isEmpty() && pInfo->aModule != "sdraw"
+ && pInfo->aModule != "simpress" && pInfo->aModule != "smath")
+ {
+ // When this is the first start (never had the sidebar open yet),
+ // default to non-expanded sidebars in Writer and Calc.
+ //
+ // HACK: unfortunately I haven't found a clean solution to do
+ // this, so do it this way:
+ //
+ if (!comphelper::LibreOfficeKit::isActive())
+ {
+ pDockWin->SetSizePixel(
+ Size(TabBar::GetDefaultWidth(),
+ pDockWin->GetSizePixel().Height()));
+ }
+ }
+
+ pDockWin->Initialize(pInfo);
+
+ if (comphelper::LibreOfficeKit::isActive())
+ {
+ // Undock sidebar in LOK to allow for resizing freely
+ // (i.e. when the client window is resized) and collapse
+ // it so the client can open it on demand.
+ pDockWin->SetFloatingSize(Size(pDockWin->GetSizePixel().Width(),
+ pDockWin->GetSizePixel().Height()));
+ pDockWin->SetFloatingMode(true);
+ }
+
+ SetHideNotDelete(true);
+
+ pDockWin->Show();
+}
+
+sal_Int32 SidebarChildWindow::GetDefaultWidth(vcl::Window const* pWindow)
+{
+ if (pWindow != nullptr)
+ {
+ // Width of the paragraph panel.
+ const static sal_Int32 nMaxPropertyPageWidth(146);
+
+ return pWindow->LogicToPixel(Point(nMaxPropertyPageWidth,1), MapMode(MapUnit::MapAppFont)).X()
+ + TabBar::GetDefaultWidth();
+ }
+ else
+ return 0;
+}
+
+} // end of namespace sfx2::sidebar
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/sidebar/SidebarController.cxx b/sfx2/source/sidebar/SidebarController.cxx
new file mode 100644
index 0000000000..3e76cb2b14
--- /dev/null
+++ b/sfx2/source/sidebar/SidebarController.cxx
@@ -0,0 +1,1651 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#include <sfx2/sidebar/SidebarController.hxx>
+#include <sfx2/sidebar/Deck.hxx>
+#include <sidebar/DeckDescriptor.hxx>
+#include <sidebar/DeckTitleBar.hxx>
+#include <sfx2/sidebar/Panel.hxx>
+#include <sidebar/PanelDescriptor.hxx>
+#include <sidebar/PanelTitleBar.hxx>
+#include <sfx2/sidebar/TabBar.hxx>
+#include <sfx2/sidebar/Theme.hxx>
+#include <sfx2/sidebar/SidebarChildWindow.hxx>
+#include <sidebar/Tools.hxx>
+#include <sfx2/sidebar/SidebarDockingWindow.hxx>
+#include <com/sun/star/ui/XSidebarProvider.hpp>
+#include <com/sun/star/frame/XController2.hpp>
+#include <sfx2/sidebar/Context.hxx>
+#include <sfx2/viewsh.hxx>
+
+
+#include <framework/ContextChangeEventMultiplexerTunnel.hxx>
+#include <vcl/EnumContext.hxx>
+#include <vcl/uitest/logger.hxx>
+#include <vcl/uitest/eventdescription.hxx>
+#include <vcl/svapp.hxx>
+#include <splitwin.hxx>
+#include <comphelper/diagnose_ex.hxx>
+#include <tools/json_writer.hxx>
+#include <tools/link.hxx>
+#include <toolkit/helper/vclunohelper.hxx>
+#include <comphelper/processfactory.hxx>
+#include <comphelper/namedvaluecollection.hxx>
+#include <comphelper/lok.hxx>
+#include <sal/log.hxx>
+#include <officecfg/Office/UI/Sidebar.hxx>
+#include <LibreOfficeKit/LibreOfficeKitEnums.h>
+
+#include <com/sun/star/awt/XWindowPeer.hpp>
+#include <com/sun/star/frame/XDispatch.hpp>
+#include <com/sun/star/ui/ContextChangeEventMultiplexer.hpp>
+#include <com/sun/star/ui/ContextChangeEventObject.hpp>
+#include <com/sun/star/ui/theUIElementFactoryManager.hpp>
+#include <com/sun/star/util/URL.hpp>
+#include <com/sun/star/rendering/XSpriteCanvas.hpp>
+
+#include <bitmaps.hlst>
+
+using namespace css;
+using namespace css::uno;
+
+namespace
+{
+ constexpr OUString gsReadOnlyCommandName = u".uno:EditDoc"_ustr;
+ const sal_Int32 gnWidthCloseThreshold (70);
+ const sal_Int32 gnWidthOpenThreshold (40);
+
+ std::string UnoNameFromDeckId(std::u16string_view rsDeckId, const sfx2::sidebar::Context& context)
+ {
+ if (rsDeckId == u"SdCustomAnimationDeck")
+ return ".uno:CustomAnimation";
+
+ if (rsDeckId == u"PropertyDeck")
+ return vcl::EnumContext::Application::Impress == vcl::EnumContext::GetApplicationEnum(context.msApplication) ? ".uno:ModifyPage" : ".uno:Sidebar";
+
+ if (rsDeckId == u"SdLayoutsDeck")
+ return ".uno:ModifyPage";
+
+ if (rsDeckId == u"SdSlideTransitionDeck")
+ return ".uno:SlideChangeWindow";
+
+ if (rsDeckId == u"SdAllMasterPagesDeck")
+ return ".uno:MasterSlidesPanel";
+
+ if (rsDeckId == u"SdMasterPagesDeck")
+ return ".uno:MasterSlidesPanel";
+
+ if (rsDeckId == u"GalleryDeck")
+ return ".uno:Gallery";
+
+ OString sUno = ".uno:SidebarDeck." + OUStringToOString(rsDeckId, RTL_TEXTENCODING_ASCII_US);
+ return std::string(sUno);
+ }
+}
+
+namespace sfx2::sidebar {
+
+namespace {
+
+ /** When in doubt, show this deck.
+ */
+ constexpr OUString gsDefaultDeckId(u"PropertyDeck"_ustr);
+}
+
+SidebarController::SidebarController (
+ SidebarDockingWindow* pParentWindow,
+ const SfxViewFrame* pViewFrame)
+ : mpParentWindow(pParentWindow),
+ mpViewFrame(pViewFrame),
+ mxFrame(pViewFrame->GetFrame().GetFrameInterface()),
+ mpTabBar(VclPtr<TabBar>::Create(
+ mpParentWindow,
+ mxFrame,
+ [this](const OUString& rsDeckId) { return this->OpenThenToggleDeck(rsDeckId); },
+ [this](weld::Menu& rMainMenu, weld::Menu& rSubMenu,
+ const ::std::vector<TabBar::DeckMenuData>& rMenuData) { return this->ShowPopupMenu(rMainMenu, rSubMenu, rMenuData); },
+ this)),
+ maCurrentContext(OUString(), OUString()),
+ maRequestedContext(OUString(), OUString()),
+ mnRequestedForceFlags(SwitchFlag_NoForce),
+ mbMinimumSidebarWidth(officecfg::Office::UI::Sidebar::General::MinimumWidth::get()),
+ msCurrentDeckId(gsDefaultDeckId),
+ maPropertyChangeForwarder([this](){ return this->BroadcastPropertyChange(); }),
+ maContextChangeUpdate([this](){ return this->UpdateConfigurations(); }),
+ mbFloatingDeckClosed(!pParentWindow->IsFloatingMode()),
+ mnSavedSidebarWidth(pParentWindow->GetSizePixel().Width()),
+ maFocusManager([this](const Panel& rPanel){ return this->ShowPanel(rPanel); }),
+ mbIsDocumentReadOnly(false),
+ mpSplitWindow(nullptr),
+ mnWidthOnSplitterButtonDown(0)
+{
+ mnMaximumSidebarWidth = officecfg::Office::UI::Sidebar::General::MaximumWidth::get() * mpTabBar->GetDPIScaleFactor();
+ // Decks and panel collections for this sidebar
+ mpResourceManager = std::make_unique<ResourceManager>();
+}
+
+rtl::Reference<SidebarController> SidebarController::create(SidebarDockingWindow* pParentWindow,
+ const SfxViewFrame* pViewFrame)
+{
+ rtl::Reference<SidebarController> instance(new SidebarController(pParentWindow, pViewFrame));
+
+ const css::uno::Reference<css::frame::XFrame>& rxFrame = pViewFrame->GetFrame().GetFrameInterface();
+ instance->registerSidebarForFrame(rxFrame->getController());
+ rxFrame->addFrameActionListener(instance);
+ // Listen for window events.
+ instance->mpParentWindow->AddEventListener(LINK(instance.get(), SidebarController, WindowEventHandler));
+
+ // Listen for theme property changes.
+ instance->mxThemePropertySet = Theme::GetPropertySet();
+ instance->mxThemePropertySet->addPropertyChangeListener(
+ "",
+ static_cast<css::beans::XPropertyChangeListener*>(instance.get()));
+
+ // Get the dispatch object as preparation to listen for changes of
+ // the read-only state.
+ const util::URL aURL (Tools::GetURL(gsReadOnlyCommandName));
+ instance->mxReadOnlyModeDispatch = Tools::GetDispatch(rxFrame, aURL);
+ if (instance->mxReadOnlyModeDispatch.is())
+ instance->mxReadOnlyModeDispatch->addStatusListener(instance, aURL);
+
+ //first UpdateConfigurations call will SwitchToDeck
+
+ return instance;
+}
+
+SidebarController::~SidebarController()
+{
+}
+
+SidebarController* SidebarController::GetSidebarControllerForFrame (
+ const css::uno::Reference<css::frame::XFrame>& rxFrame)
+{
+ uno::Reference<frame::XController> const xController(rxFrame->getController());
+ if (!xController.is()) // this may happen during dispose of Draw controller but perhaps it's a bug
+ {
+ SAL_WARN("sfx.sidebar", "GetSidebarControllerForFrame: frame has no XController");
+ return nullptr;
+ }
+ uno::Reference<ui::XContextChangeEventListener> const xListener(
+ framework::GetFirstListenerWith(
+ ::comphelper::getProcessComponentContext(),
+ xController,
+ [] (uno::Reference<uno::XInterface> const& xRef)
+ { return nullptr != dynamic_cast<SidebarController*>(xRef.get()); }
+ ));
+
+ return dynamic_cast<SidebarController*>(xListener.get());
+}
+
+void SidebarController::registerSidebarForFrame(const css::uno::Reference<css::frame::XController>& xController)
+{
+ // Listen for context change events.
+ css::uno::Reference<css::ui::XContextChangeEventMultiplexer> xMultiplexer (
+ css::ui::ContextChangeEventMultiplexer::get(
+ ::comphelper::getProcessComponentContext()));
+ xMultiplexer->addContextChangeEventListener(
+ static_cast<css::ui::XContextChangeEventListener*>(this),
+ xController);
+}
+
+void SidebarController::unregisterSidebarForFrame(const css::uno::Reference<css::frame::XController>& xController)
+{
+ saveDeckState();
+ disposeDecks();
+
+ css::uno::Reference<css::ui::XContextChangeEventMultiplexer> xMultiplexer (
+ css::ui::ContextChangeEventMultiplexer::get(
+ ::comphelper::getProcessComponentContext()));
+ xMultiplexer->removeContextChangeEventListener(
+ static_cast<css::ui::XContextChangeEventListener*>(this),
+ xController);
+}
+
+void SidebarController::disposeDecks()
+{
+ SolarMutexGuard aSolarMutexGuard;
+
+ if (comphelper::LibreOfficeKit::isActive())
+ {
+ if (const SfxViewShell* pViewShell = mpViewFrame->GetViewShell())
+ {
+ const std::string hide = UnoNameFromDeckId(msCurrentDeckId, GetCurrentContext());
+ if (!hide.empty())
+ pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_STATE_CHANGED,
+ OString(hide + "=false"));
+ }
+
+ if (mpParentWindow)
+ mpParentWindow->ReleaseLOKNotifier();
+ }
+
+ mpCurrentDeck.clear();
+ maFocusManager.Clear();
+ mpResourceManager->disposeDecks();
+}
+
+namespace
+{
+ class CloseIndicator final : public InterimItemWindow
+ {
+ public:
+ CloseIndicator(vcl::Window* pParent)
+ : InterimItemWindow(pParent, "svt/ui/fixedimagecontrol.ui", "FixedImageControl")
+ , m_xWidget(m_xBuilder->weld_image("image"))
+ {
+ InitControlBase(m_xWidget.get());
+
+ m_xWidget->set_from_icon_name(SIDEBAR_CLOSE_INDICATOR);
+
+ SetSizePixel(get_preferred_size());
+
+ SetBackground(Theme::GetColor(Theme::Color_DeckBackground));
+ }
+
+ virtual ~CloseIndicator() override
+ {
+ disposeOnce();
+ }
+
+ virtual void dispose() override
+ {
+ m_xWidget.reset();
+ InterimItemWindow::dispose();
+ }
+
+ private:
+ std::unique_ptr<weld::Image> m_xWidget;
+ };
+}
+
+void SidebarController::disposing(std::unique_lock<std::mutex>&)
+{
+ SolarMutexGuard aSolarMutexGuard;
+
+ mpCloseIndicator.disposeAndClear();
+
+ maFocusManager.Clear();
+ mpTabBar.disposeAndClear();
+
+ saveDeckState();
+
+ // clear decks
+ ResourceManager::DeckContextDescriptorContainer aDecks;
+
+ mpResourceManager->GetMatchingDecks (
+ aDecks,
+ GetCurrentContext(),
+ IsDocumentReadOnly(),
+ mxFrame->getController());
+
+ for (const auto& rDeck : aDecks)
+ {
+ std::shared_ptr<DeckDescriptor> deckDesc = mpResourceManager->GetDeckDescriptor(rDeck.msId);
+
+ VclPtr<Deck> aDeck = deckDesc->mpDeck;
+ if (aDeck)
+ aDeck.disposeAndClear();
+ }
+
+ maContextChangeUpdate.CancelRequest();
+
+ if (mxReadOnlyModeDispatch.is())
+ mxReadOnlyModeDispatch->removeStatusListener(this, Tools::GetURL(gsReadOnlyCommandName));
+
+ if (mxThemePropertySet.is())
+ mxThemePropertySet->removePropertyChangeListener(
+ "",
+ static_cast<css::beans::XPropertyChangeListener*>(this));
+
+ if (mpParentWindow != nullptr)
+ {
+ mpParentWindow->RemoveEventListener(LINK(this, SidebarController, WindowEventHandler));
+ mpParentWindow = nullptr;
+ }
+
+ if (mpSplitWindow != nullptr)
+ {
+ mpSplitWindow->RemoveEventListener(LINK(this, SidebarController, WindowEventHandler));
+ mpSplitWindow = nullptr;
+ }
+
+ mxFrame->removeFrameActionListener(this);
+
+ uno::Reference<css::frame::XController> xController = mxFrame->getController();
+ if (!xController.is())
+ xController = mxCurrentController;
+
+ unregisterSidebarForFrame(xController);
+}
+
+void SAL_CALL SidebarController::notifyContextChangeEvent (const css::ui::ContextChangeEventObject& rEvent)
+{
+ SolarMutexGuard aSolarMutexGuard;
+
+ // Update to the requested new context asynchronously to avoid
+ // subtle errors caused by SFX2 which in rare cases can not
+ // properly handle a synchronous update.
+
+ maRequestedContext = Context(
+ rEvent.ApplicationName,
+ rEvent.ContextName);
+
+ if (maRequestedContext != maCurrentContext)
+ {
+ mxCurrentController.set(rEvent.Source, css::uno::UNO_QUERY);
+ maContextChangeUpdate.RequestCall(); // async call, not a prob
+ // calling with held
+ // solarmutex
+ // TODO: this call is redundant but mandatory for unit test to update context on document loading
+ if (!comphelper::LibreOfficeKit::isActive())
+ UpdateConfigurations();
+ }
+}
+
+void SAL_CALL SidebarController::disposing (const css::lang::EventObject& )
+{
+ dispose();
+}
+
+void SAL_CALL SidebarController::propertyChange (const css::beans::PropertyChangeEvent& )
+{
+ SolarMutexGuard aSolarMutexGuard;
+
+ maPropertyChangeForwarder.RequestCall(); // async call, not a prob
+ // to call with held
+ // solarmutex
+}
+
+void SAL_CALL SidebarController::statusChanged (const css::frame::FeatureStateEvent& rEvent)
+{
+ SolarMutexGuard aSolarMutexGuard;
+
+ bool bIsReadWrite (true);
+ if (rEvent.IsEnabled)
+ rEvent.State >>= bIsReadWrite;
+
+ if (mbIsDocumentReadOnly != !bIsReadWrite)
+ {
+ mbIsDocumentReadOnly = !bIsReadWrite;
+
+ // Force the current deck to update its panel list.
+ if ( ! mbIsDocumentReadOnly)
+ SwitchToDefaultDeck();
+
+ mnRequestedForceFlags |= SwitchFlag_ForceSwitch;
+ maContextChangeUpdate.RequestCall(); // async call, ok to call
+ // with held solarmutex
+ }
+}
+
+void SAL_CALL SidebarController::requestLayout()
+{
+ SolarMutexGuard aSolarMutexGuard;
+
+ sal_Int32 nMinimalWidth = 0;
+ if (mpCurrentDeck && !mpCurrentDeck->isDisposed())
+ {
+ mpCurrentDeck->RequestLayout();
+ nMinimalWidth = mbMinimumSidebarWidth ? mpCurrentDeck->GetMinimalWidth() : 0;
+ }
+ RestrictWidth(nMinimalWidth);
+}
+
+void SidebarController::BroadcastPropertyChange()
+{
+ mpParentWindow->Invalidate(InvalidateFlags::Children);
+}
+
+void SidebarController::NotifyResize()
+{
+ if (!mpTabBar)
+ {
+ OSL_ASSERT(mpTabBar!=nullptr);
+ return;
+ }
+
+ const sal_Int32 nTabBarDefaultWidth = TabBar::GetDefaultWidth();
+
+ const sal_Int32 nWidth(mpParentWindow->GetSizePixel().Width());
+ const sal_Int32 nHeight(mpParentWindow->GetSizePixel().Height());
+
+ mbIsDeckOpen = (nWidth > nTabBarDefaultWidth);
+
+ if (mnSavedSidebarWidth <= 0)
+ mnSavedSidebarWidth = nWidth;
+
+ bool bIsDeckVisible;
+ const bool bIsOpening (nWidth > mnWidthOnSplitterButtonDown);
+ if (bIsOpening)
+ bIsDeckVisible = nWidth >= nTabBarDefaultWidth + gnWidthOpenThreshold;
+ else
+ bIsDeckVisible = nWidth >= nTabBarDefaultWidth + gnWidthCloseThreshold;
+ mbIsDeckRequestedOpen = bIsDeckVisible;
+ UpdateCloseIndicator(!bIsDeckVisible);
+
+ if (mpCurrentDeck && !mpCurrentDeck->isDisposed())
+ {
+ SfxSplitWindow* pSplitWindow = GetSplitWindow();
+ WindowAlign eAlign = pSplitWindow ? pSplitWindow->GetAlign() : WindowAlign::Right;
+ tools::Long nDeckX, nTabX;
+ if (eAlign == WindowAlign::Left) // attach the Sidebar towards the left-side of screen
+ {
+ nDeckX = nTabBarDefaultWidth;
+ nTabX = 0;
+ }
+ else // attach the Sidebar towards the right-side of screen
+ {
+ nDeckX = 0;
+ nTabX = nWidth - nTabBarDefaultWidth;
+ }
+
+ // Place the deck first.
+ if (bIsDeckVisible)
+ {
+ if (comphelper::LibreOfficeKit::isActive())
+ {
+ // We want to let the layouter use up as much of the
+ // height as necessary to make sure no scrollbar is
+ // visible. This only works when there are no greedy
+ // panes that fill up all available area. So we only
+ // use this for the PropertyDeck, which has no such
+ // panes, while most other do. This is fine, since
+ // it's the PropertyDeck that really has many panes
+ // that can collapse or expand. For others, limit
+ // the height to something sensible.
+ const sal_Int32 nExtHeight = (msCurrentDeckId == "PropertyDeck" ? 2000 : 600);
+ // No TabBar in LOK (use nWidth in full).
+ mpCurrentDeck->setPosSizePixel(nDeckX, 0, nWidth, nExtHeight);
+ }
+ else
+ mpCurrentDeck->setPosSizePixel(nDeckX, 0, nWidth - nTabBarDefaultWidth, nHeight);
+ mpCurrentDeck->Show();
+ mpCurrentDeck->RequestLayout();
+ mpTabBar->HighlightDeck(mpCurrentDeck->GetId());
+ }
+ else
+ mpCurrentDeck->Hide();
+
+ // Now place the tab bar.
+ mpTabBar->setPosSizePixel(nTabX, 0, nTabBarDefaultWidth, nHeight);
+ if (!comphelper::LibreOfficeKit::isActive())
+ mpTabBar->Show(); // Don't show TabBar in LOK.
+ }
+
+ // Determine if the closer of the deck can be shown.
+ sal_Int32 nMinimalWidth = 0;
+ if (mpCurrentDeck && !mpCurrentDeck->isDisposed())
+ {
+ DeckTitleBar* pTitleBar = mpCurrentDeck->GetTitleBar();
+ if (pTitleBar && pTitleBar->GetVisible())
+ pTitleBar->SetCloserVisible(CanModifyChildWindowWidth());
+ nMinimalWidth = mbMinimumSidebarWidth ? mpCurrentDeck->GetMinimalWidth() : 0;
+ }
+
+ RestrictWidth(nMinimalWidth);
+}
+
+void SidebarController::ProcessNewWidth (const sal_Int32 nNewWidth)
+{
+ if ( ! mbIsDeckRequestedOpen.has_value())
+ return;
+
+ if (*mbIsDeckRequestedOpen)
+ {
+ // Deck became large enough to be shown. Show it.
+ mnSavedSidebarWidth = nNewWidth;
+ // Store nNewWidth to mnWidthOnSplitterButtonDown when dragging sidebar Splitter
+ mnWidthOnSplitterButtonDown = nNewWidth;
+ if (!*mbIsDeckOpen)
+ RequestOpenDeck();
+ }
+ else
+ {
+ // Deck became too small. Close it completely.
+ // If window is wider than the tab bar then mark the deck as being visible, even when it is not.
+ // This is to trigger an adjustment of the width to the width of the tab bar.
+ mbIsDeckOpen = true;
+ RequestCloseDeck();
+
+ if (mnWidthOnSplitterButtonDown > TabBar::GetDefaultWidth())
+ mnSavedSidebarWidth = mnWidthOnSplitterButtonDown;
+ }
+}
+
+void SidebarController::SyncUpdate()
+{
+ maPropertyChangeForwarder.Sync();
+ maContextChangeUpdate.Sync();
+}
+
+void SidebarController::UpdateConfigurations()
+{
+ if (maCurrentContext == maRequestedContext
+ && mnRequestedForceFlags == SwitchFlag_NoForce)
+ return;
+
+ if ((maCurrentContext.msApplication != "none") &&
+ !maCurrentContext.msApplication.isEmpty())
+ {
+ mpResourceManager->SaveDecksSettings(maCurrentContext);
+ mpResourceManager->SetLastActiveDeck(maCurrentContext, msCurrentDeckId);
+ }
+
+ // get last active deck for this application on first update
+ if (!maRequestedContext.msApplication.isEmpty() &&
+ (maCurrentContext.msApplication != maRequestedContext.msApplication))
+ {
+ OUString sLastActiveDeck = mpResourceManager->GetLastActiveDeck( maRequestedContext );
+ if (!sLastActiveDeck.isEmpty())
+ msCurrentDeckId = sLastActiveDeck;
+ }
+
+ maCurrentContext = maRequestedContext;
+
+ mpResourceManager->InitDeckContext(GetCurrentContext());
+
+ // Find the set of decks that could be displayed for the new context.
+ ResourceManager::DeckContextDescriptorContainer aDecks;
+
+ css::uno::Reference<css::frame::XController> xController = mxCurrentController.is() ? mxCurrentController : mxFrame->getController();
+
+ mpResourceManager->GetMatchingDecks (
+ aDecks,
+ maCurrentContext,
+ mbIsDocumentReadOnly,
+ xController);
+
+ maFocusManager.Clear();
+
+ // Notify the tab bar about the updated set of decks.
+ mpTabBar->SetDecks(aDecks);
+
+ // Find the new deck. By default that is the same as the old
+ // one. If that is not set or not enabled, then choose the
+ // first enabled deck (which is PropertyDeck).
+ OUString sNewDeckId;
+ for (const auto& rDeck : aDecks)
+ {
+ if (rDeck.mbIsEnabled)
+ {
+ if (rDeck.msId == msCurrentDeckId)
+ {
+ sNewDeckId = msCurrentDeckId;
+ break;
+ }
+ else if (sNewDeckId.getLength() == 0)
+ sNewDeckId = rDeck.msId;
+ }
+ }
+
+ if (sNewDeckId.getLength() == 0)
+ {
+ // We did not find a valid deck.
+ RequestCloseDeck();
+ return;
+ }
+
+ std::shared_ptr<DeckDescriptor> xDescriptor = mpResourceManager->GetDeckDescriptor(sNewDeckId);
+
+ if (xDescriptor)
+ {
+ SwitchToDeck(*xDescriptor, maCurrentContext);
+ }
+}
+
+namespace {
+
+void collectUIInformation(const OUString& rDeckId)
+{
+ EventDescription aDescription;
+ aDescription.aAction = "SIDEBAR";
+ aDescription.aParent = "MainWindow";
+ aDescription.aParameters = {{"PANEL", rDeckId}};
+ aDescription.aKeyWord = "CurrentApp";
+
+ UITestLogger::getInstance().logEvent(aDescription);
+}
+
+}
+
+void SidebarController::OpenThenToggleDeck (
+ const OUString& rsDeckId)
+{
+ SfxSplitWindow* pSplitWindow = GetSplitWindow();
+ if ( pSplitWindow && !pSplitWindow->IsFadeIn() )
+ // tdf#83546 Collapsed sidebar should expand first
+ pSplitWindow->FadeIn();
+ else if ( IsDeckVisible( rsDeckId ) )
+ {
+ if( !WasFloatingDeckClosed() )
+ {
+ // tdf#88241 Summoning an undocked sidebar a second time should close sidebar
+ mpParentWindow->Close();
+ return;
+ }
+ else
+ {
+ // tdf#67627 Clicking a second time on a Deck icon will close the Deck
+ RequestCloseDeck();
+ return;
+ }
+ }
+ RequestOpenDeck();
+ // before SwitchToDeck which may cause the rsDeckId string to be released
+ collectUIInformation(rsDeckId);
+ SwitchToDeck(rsDeckId);
+
+ // Make sure the sidebar is wide enough to fit the requested content
+ if (mpCurrentDeck && mpTabBar)
+ {
+ sal_Int32 nRequestedWidth = mpCurrentDeck->GetMinimalWidth() + TabBar::GetDefaultWidth();
+ // if sidebar was dragged
+ if(mnWidthOnSplitterButtonDown > 0 && mnWidthOnSplitterButtonDown > nRequestedWidth){
+ SetChildWindowWidth(mnWidthOnSplitterButtonDown);
+ }else{
+ // tdf#150639 The mnWidthOnSplitterButtonDown is initialized to 0 at program start.
+ // This makes every call to take the else case until the user manually changes the
+ // width, but some decks such as Master Slides have the mnMinimalWidth too low which
+ // makes them too narrow for the content they should display to the user.
+ SetChildWindowWidth(nRequestedWidth > mnSavedSidebarWidth ? nRequestedWidth
+ : mnSavedSidebarWidth);
+ }
+ }
+}
+
+void SidebarController::OpenThenSwitchToDeck (
+ std::u16string_view rsDeckId)
+{
+ RequestOpenDeck();
+ SwitchToDeck(rsDeckId);
+
+}
+
+void SidebarController::SwitchToDefaultDeck()
+{
+ SwitchToDeck(gsDefaultDeckId);
+}
+
+void SidebarController::SwitchToDeck (
+ std::u16string_view rsDeckId)
+{
+ if ( msCurrentDeckId != rsDeckId
+ || ! mbIsDeckOpen.has_value()
+ || mnRequestedForceFlags!=SwitchFlag_NoForce)
+ {
+ std::shared_ptr<DeckDescriptor> xDeckDescriptor = mpResourceManager->GetDeckDescriptor(rsDeckId);
+
+ if (xDeckDescriptor)
+ {
+ SwitchToDeck(*xDeckDescriptor, maCurrentContext);
+ Deck::LOKSendSidebarFullUpdate();
+ }
+ }
+}
+
+void SidebarController::CreateDeck(std::u16string_view rDeckId) {
+ CreateDeck(rDeckId, maCurrentContext);
+}
+
+void SidebarController::CreateDeck(std::u16string_view rDeckId, const Context& rContext, bool bForceCreate)
+{
+ std::shared_ptr<DeckDescriptor> xDeckDescriptor = mpResourceManager->GetDeckDescriptor(rDeckId);
+
+ if (!xDeckDescriptor)
+ return;
+
+ VclPtr<Deck> aDeck = xDeckDescriptor->mpDeck;
+ if (!aDeck || bForceCreate)
+ {
+ if (aDeck)
+ aDeck.disposeAndClear();
+
+ aDeck = VclPtr<Deck>::Create(
+ *xDeckDescriptor,
+ mpParentWindow,
+ [this]() { return this->RequestCloseDeck(); });
+ }
+ xDeckDescriptor->mpDeck = aDeck;
+ CreatePanels(rDeckId, rContext);
+}
+
+void SidebarController::CreatePanels(std::u16string_view rDeckId, const Context& rContext)
+{
+ std::shared_ptr<DeckDescriptor> xDeckDescriptor = mpResourceManager->GetDeckDescriptor(rDeckId);
+
+ // init panels bounded to that deck, do not wait them being displayed as may be accessed through API
+
+ VclPtr<Deck> pDeck = xDeckDescriptor->mpDeck;
+
+ ResourceManager::PanelContextDescriptorContainer aPanelContextDescriptors;
+
+ css::uno::Reference<css::frame::XController> xController = mxCurrentController.is() ? mxCurrentController : mxFrame->getController();
+
+ mpResourceManager->GetMatchingPanels(
+ aPanelContextDescriptors,
+ rContext,
+ rDeckId,
+ xController);
+
+ // Update the panel list.
+ const sal_Int32 nNewPanelCount (aPanelContextDescriptors.size());
+ SharedPanelContainer aNewPanels;
+ sal_Int32 nWriteIndex (0);
+
+ aNewPanels.resize(nNewPanelCount);
+
+ for (sal_Int32 nReadIndex=0; nReadIndex<nNewPanelCount; ++nReadIndex)
+ {
+ const ResourceManager::PanelContextDescriptor& rPanelContexDescriptor (
+ aPanelContextDescriptors[nReadIndex]);
+
+ // Determine if the panel can be displayed.
+ const bool bIsPanelVisible (!mbIsDocumentReadOnly || rPanelContexDescriptor.mbShowForReadOnlyDocuments);
+ if ( ! bIsPanelVisible)
+ continue;
+
+ auto xOldPanel(pDeck->GetPanel(rPanelContexDescriptor.msId));
+ if (xOldPanel)
+ {
+ xOldPanel->SetLurkMode(false);
+ aNewPanels[nWriteIndex] = xOldPanel;
+ xOldPanel->SetExpanded(rPanelContexDescriptor.mbIsInitiallyVisible);
+ ++nWriteIndex;
+ }
+ else
+ {
+ auto aPanel = CreatePanel(rPanelContexDescriptor.msId,
+ pDeck->GetPanelParentWindow(),
+ rPanelContexDescriptor.mbIsInitiallyVisible,
+ rContext,
+ pDeck);
+ if (aPanel)
+ {
+ aNewPanels[nWriteIndex] = std::move(aPanel);
+
+ // Depending on the context we have to change the command
+ // for the "more options" dialog.
+ PanelTitleBar* pTitleBar = aNewPanels[nWriteIndex]->GetTitleBar();
+ if (pTitleBar)
+ {
+ pTitleBar->SetMoreOptionsCommand(
+ rPanelContexDescriptor.msMenuCommand,
+ mxFrame, xController);
+ }
+ ++nWriteIndex;
+ }
+ }
+ }
+
+ // mpCurrentPanels - may miss stuff (?)
+ aNewPanels.resize(nWriteIndex);
+ pDeck->ResetPanels(std::move(aNewPanels));
+}
+
+void SidebarController::SwitchToDeck (
+ const DeckDescriptor& rDeckDescriptor,
+ const Context& rContext)
+{
+ if (comphelper::LibreOfficeKit::isActive())
+ {
+ if (const SfxViewShell* pViewShell = mpViewFrame->GetViewShell())
+ {
+ if (msCurrentDeckId != rDeckDescriptor.msId)
+ {
+ const std::string hide = UnoNameFromDeckId(msCurrentDeckId, GetCurrentContext());
+ if (!hide.empty())
+ pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_STATE_CHANGED,
+ OString(hide + "=false"));
+ }
+
+ const std::string show = UnoNameFromDeckId(rDeckDescriptor.msId, GetCurrentContext());
+ if (!show.empty())
+ pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_STATE_CHANGED,
+ OString(show + "=true"));
+ }
+ }
+
+ maFocusManager.Clear();
+
+ const bool bForceNewDeck ((mnRequestedForceFlags&SwitchFlag_ForceNewDeck)!=0);
+ const bool bForceNewPanels ((mnRequestedForceFlags&SwitchFlag_ForceNewPanels)!=0);
+ mnRequestedForceFlags = SwitchFlag_NoForce;
+
+ if ( msCurrentDeckId != rDeckDescriptor.msId
+ || bForceNewDeck)
+ {
+ if (mpCurrentDeck)
+ mpCurrentDeck->Hide();
+
+ msCurrentDeckId = rDeckDescriptor.msId;
+ }
+
+ // Determine the panels to display in the deck.
+ ResourceManager::PanelContextDescriptorContainer aPanelContextDescriptors;
+
+ css::uno::Reference<css::frame::XController> xController = mxCurrentController.is() ? mxCurrentController : mxFrame->getController();
+
+ mpResourceManager->GetMatchingPanels(
+ aPanelContextDescriptors,
+ rContext,
+ rDeckDescriptor.msId,
+ xController);
+
+ if (aPanelContextDescriptors.empty())
+ {
+ // There are no panels to be displayed in the current context.
+ if (vcl::EnumContext::GetContextEnum(rContext.msContext) != vcl::EnumContext::Context::Empty)
+ {
+ // Switch to the "empty" context and try again.
+ SwitchToDeck(
+ rDeckDescriptor,
+ Context(
+ rContext.msApplication,
+ vcl::EnumContext::GetContextName(vcl::EnumContext::Context::Empty)));
+ return;
+ }
+ else
+ {
+ // This is already the "empty" context. Looks like we have
+ // to live with an empty deck.
+ }
+ }
+
+ // Provide a configuration and Deck object.
+
+ CreateDeck(rDeckDescriptor.msId, rContext, bForceNewDeck);
+
+ if (bForceNewPanels && !bForceNewDeck) // already forced if bForceNewDeck
+ CreatePanels(rDeckDescriptor.msId, rContext);
+
+ if (mpCurrentDeck && mpCurrentDeck != rDeckDescriptor.mpDeck)
+ mpCurrentDeck->Hide();
+ mpCurrentDeck.reset(rDeckDescriptor.mpDeck);
+
+ if ( ! mpCurrentDeck)
+ return;
+
+#ifdef DEBUG
+ // Show the context name in the deck title bar.
+ DeckTitleBar* pDebugTitleBar = mpCurrentDeck->GetTitleBar();
+ if (pDebugTitleBar)
+ pDebugTitleBar->SetTitle(rDeckDescriptor.msTitle + " (" + maCurrentContext.msContext + ")");
+#endif
+
+ SfxSplitWindow* pSplitWindow = GetSplitWindow();
+ sal_Int32 nTabBarDefaultWidth = TabBar::GetDefaultWidth();
+ WindowAlign eAlign = pSplitWindow ? pSplitWindow->GetAlign() : WindowAlign::Right;
+ tools::Long nDeckX;
+ if (eAlign == WindowAlign::Left) // attach the Sidebar towards the left-side of screen
+ {
+ nDeckX = nTabBarDefaultWidth;
+ }
+ else // attach the Sidebar towards the right-side of screen
+ {
+ nDeckX = 0;
+ }
+
+ // Activate the deck and the new set of panels.
+ mpCurrentDeck->setPosSizePixel(
+ nDeckX,
+ 0,
+ mpParentWindow->GetSizePixel().Width() - nTabBarDefaultWidth,
+ mpParentWindow->GetSizePixel().Height());
+
+ mpCurrentDeck->Show();
+
+ mpParentWindow->SetText(rDeckDescriptor.msTitle);
+
+ NotifyResize();
+
+ // Tell the focus manager about the new panels and tab bar
+ // buttons.
+ maFocusManager.SetDeck(mpCurrentDeck);
+ maFocusManager.SetPanels(mpCurrentDeck->GetPanels());
+
+ mpTabBar->UpdateFocusManager(maFocusManager);
+ UpdateTitleBarIcons();
+}
+
+void SidebarController::notifyDeckTitle(std::u16string_view targetDeckId)
+{
+ if (msCurrentDeckId == targetDeckId)
+ {
+ maFocusManager.SetDeck(mpCurrentDeck);
+ mpTabBar->UpdateFocusManager(maFocusManager);
+ UpdateTitleBarIcons();
+ }
+}
+
+std::shared_ptr<Panel> SidebarController::CreatePanel (
+ std::u16string_view rsPanelId,
+ weld::Widget* pParentWindow,
+ const bool bIsInitiallyExpanded,
+ const Context& rContext,
+ const VclPtr<Deck>& pDeck)
+{
+ std::shared_ptr<PanelDescriptor> xPanelDescriptor = mpResourceManager->GetPanelDescriptor(rsPanelId);
+
+ if (!xPanelDescriptor)
+ return nullptr;
+
+ // Create the panel which is the parent window of the UIElement.
+ auto xPanel = std::make_shared<Panel>(
+ *xPanelDescriptor,
+ pParentWindow,
+ bIsInitiallyExpanded,
+ pDeck,
+ [this]() { return this->GetCurrentContext(); },
+ mxFrame);
+
+ // Create the XUIElement.
+ Reference<ui::XUIElement> xUIElement (CreateUIElement(
+ xPanel->GetElementParentWindow(),
+ xPanelDescriptor->msImplementationURL,
+ xPanelDescriptor->mbWantsCanvas,
+ rContext));
+ if (xUIElement.is())
+ {
+ // Initialize the panel and add it to the active deck.
+ xPanel->SetUIElement(xUIElement);
+ }
+ else
+ {
+ xPanel.reset();
+ }
+
+ return xPanel;
+}
+
+Reference<ui::XUIElement> SidebarController::CreateUIElement (
+ const Reference<awt::XWindow>& rxWindow,
+ const OUString& rsImplementationURL,
+ const bool bWantsCanvas,
+ const Context& rContext)
+{
+ try
+ {
+ const Reference<XComponentContext> xComponentContext (::comphelper::getProcessComponentContext() );
+ const Reference<ui::XUIElementFactory> xUIElementFactory =
+ ui::theUIElementFactoryManager::get( xComponentContext );
+
+ // Create the XUIElement.
+ ::comphelper::NamedValueCollection aCreationArguments;
+ aCreationArguments.put("Frame", Any(mxFrame));
+ aCreationArguments.put("ParentWindow", Any(rxWindow));
+ SidebarDockingWindow* pSfxDockingWindow = mpParentWindow.get();
+ if (pSfxDockingWindow != nullptr)
+ aCreationArguments.put("SfxBindings", Any(reinterpret_cast<sal_uInt64>(&pSfxDockingWindow->GetBindings())));
+ aCreationArguments.put("Theme", Theme::GetPropertySet());
+ aCreationArguments.put("Sidebar", Any(Reference<ui::XSidebar>(static_cast<ui::XSidebar*>(this))));
+ if (bWantsCanvas)
+ {
+ Reference<rendering::XSpriteCanvas> xCanvas (VCLUnoHelper::GetWindow(rxWindow)->GetOutDev()->GetSpriteCanvas());
+ aCreationArguments.put("Canvas", Any(xCanvas));
+ }
+
+ if (mxCurrentController.is())
+ {
+ OUString aModule = Tools::GetModuleName(mxCurrentController);
+ if (!aModule.isEmpty())
+ {
+ aCreationArguments.put("Module", Any(aModule));
+ }
+ aCreationArguments.put("Controller", Any(mxCurrentController));
+ }
+
+ aCreationArguments.put("ApplicationName", Any(rContext.msApplication));
+ aCreationArguments.put("ContextName", Any(rContext.msContext));
+
+ Reference<ui::XUIElement> xUIElement(
+ xUIElementFactory->createUIElement(
+ rsImplementationURL,
+ aCreationArguments.getPropertyValues()),
+ UNO_SET_THROW);
+
+ return xUIElement;
+ }
+ catch(const Exception&)
+ {
+ TOOLS_WARN_EXCEPTION("sfx.sidebar", "Cannot create panel " << rsImplementationURL);
+ return nullptr;
+ }
+}
+
+IMPL_LINK(SidebarController, WindowEventHandler, VclWindowEvent&, rEvent, void)
+{
+ if (rEvent.GetWindow() == mpParentWindow)
+ {
+ switch (rEvent.GetId())
+ {
+ case VclEventId::WindowShow:
+ case VclEventId::WindowResize:
+ NotifyResize();
+ break;
+
+ case VclEventId::WindowDataChanged:
+ // Force an update of deck and tab bar to reflect
+ // changes in theme (high contrast mode).
+ Theme::HandleDataChange();
+ UpdateTitleBarIcons();
+ mpParentWindow->Invalidate();
+ mnRequestedForceFlags |= SwitchFlag_ForceNewDeck | SwitchFlag_ForceNewPanels;
+ maContextChangeUpdate.RequestCall();
+ break;
+
+ case VclEventId::ObjectDying:
+ dispose();
+ break;
+
+ case VclEventId::WindowPaint:
+ SAL_INFO("sfx.sidebar", "Paint");
+ break;
+
+ default:
+ break;
+ }
+ }
+ else if (rEvent.GetWindow()==mpSplitWindow && mpSplitWindow!=nullptr)
+ {
+ switch (rEvent.GetId())
+ {
+ case VclEventId::WindowMouseButtonDown:
+ mnWidthOnSplitterButtonDown = mpParentWindow->GetSizePixel().Width();
+ break;
+
+ case VclEventId::WindowMouseButtonUp:
+ {
+ ProcessNewWidth(mpParentWindow->GetSizePixel().Width());
+ break;
+ }
+
+ case VclEventId::ObjectDying:
+ dispose();
+ break;
+
+ default: break;
+ }
+ }
+}
+
+void SidebarController::ShowPopupMenu(
+ weld::Menu& rMainMenu, weld::Menu& rSubMenu,
+ const ::std::vector<TabBar::DeckMenuData>& rMenuData) const
+{
+ PopulatePopupMenus(rMainMenu, rSubMenu, rMenuData);
+ rMainMenu.connect_activate(LINK(const_cast<SidebarController*>(this), SidebarController, OnMenuItemSelected));
+ rSubMenu.connect_activate(LINK(const_cast<SidebarController*>(this), SidebarController, OnSubMenuItemSelected));
+}
+
+void SidebarController::PopulatePopupMenus(weld::Menu& rMenu, weld::Menu& rCustomizationMenu,
+ const std::vector<TabBar::DeckMenuData>& rMenuData) const
+{
+ // Add one entry for every tool panel element to individually make
+ // them visible or hide them.
+ sal_Int32 nIndex (0);
+ for (const auto& rItem : rMenuData)
+ {
+ OUString sIdent("select" + OUString::number(nIndex));
+ rMenu.insert(nIndex, sIdent, rItem.msDisplayName,
+ nullptr, nullptr, nullptr, TRISTATE_FALSE);
+ rMenu.set_active(sIdent, rItem.mbIsCurrentDeck);
+ rMenu.set_sensitive(sIdent, rItem.mbIsEnabled && rItem.mbIsActive);
+
+ if (!comphelper::LibreOfficeKit::isActive())
+ {
+ if (rItem.mbIsCurrentDeck)
+ {
+ // Don't allow the currently visible deck to be disabled.
+ OUString sSubIdent("nocustomize" + OUString::number(nIndex));
+ rCustomizationMenu.insert(nIndex, sSubIdent, rItem.msDisplayName,
+ nullptr, nullptr, nullptr, TRISTATE_FALSE);
+ rCustomizationMenu.set_active(sSubIdent, true);
+ }
+ else
+ {
+ OUString sSubIdent("customize" + OUString::number(nIndex));
+ rCustomizationMenu.insert(nIndex, sSubIdent, rItem.msDisplayName,
+ nullptr, nullptr, nullptr, TRISTATE_TRUE);
+ rCustomizationMenu.set_active(sSubIdent, rItem.mbIsEnabled && rItem.mbIsActive);
+ }
+ }
+
+ ++nIndex;
+ }
+
+ bool bHideLock = true;
+ bool bHideUnLock = true;
+ // LOK doesn't support docked/undocked; Sidebar is floating but rendered docked in browser.
+ if (!comphelper::LibreOfficeKit::isActive())
+ {
+ // Add entry for docking or un-docking the tool panel.
+ if (mpParentWindow->IsFloatingMode())
+ bHideLock = false;
+ else
+ bHideUnLock = false;
+ }
+ rMenu.set_visible("locktaskpanel", !bHideLock);
+ rMenu.set_visible("unlocktaskpanel", !bHideUnLock);
+
+ // No Restore or Customize options for LoKit.
+ rMenu.set_visible("customization", !comphelper::LibreOfficeKit::isActive());
+}
+
+IMPL_LINK(SidebarController, OnMenuItemSelected, const OUString&, rCurItemId, void)
+{
+ if (rCurItemId == "unlocktaskpanel")
+ {
+ mpParentWindow->SetFloatingMode(true);
+ if (mpParentWindow->IsFloatingMode())
+ mpParentWindow->ToTop(ToTopFlags::GrabFocusOnly);
+ }
+ else if (rCurItemId == "locktaskpanel")
+ {
+ mpParentWindow->SetFloatingMode(false);
+ }
+ else if (rCurItemId == "hidesidebar")
+ {
+ if (!comphelper::LibreOfficeKit::isActive())
+ {
+ const util::URL aURL(Tools::GetURL(".uno:Sidebar"));
+ Reference<frame::XDispatch> xDispatch(Tools::GetDispatch(mxFrame, aURL));
+ if (xDispatch.is())
+ xDispatch->dispatch(aURL, Sequence<beans::PropertyValue>());
+ }
+ else
+ {
+ // In LOK we don't really destroy the sidebar when "closing";
+ // we simply hide it. This is because recreating it is problematic
+ // See notes in SidebarDockingWindow::NotifyResize().
+ RequestCloseDeck();
+ }
+ }
+ else
+ {
+ try
+ {
+ OUString sNumber;
+ if (rCurItemId.startsWith("select", &sNumber))
+ {
+ RequestOpenDeck();
+ SwitchToDeck(mpTabBar->GetDeckIdForIndex(sNumber.toInt32()));
+ }
+ mpParentWindow->GrabFocusToDocument();
+ }
+ catch (RuntimeException&)
+ {
+ }
+ }
+}
+
+IMPL_LINK(SidebarController, OnSubMenuItemSelected, const OUString&, rCurItemId, void)
+{
+ if (rCurItemId == "restoredefault")
+ mpTabBar->RestoreHideFlags();
+ else
+ {
+ try
+ {
+ OUString sNumber;
+ if (rCurItemId.startsWith("customize", &sNumber))
+ {
+ mpTabBar->ToggleHideFlag(sNumber.toInt32());
+
+ // Find the set of decks that could be displayed for the new context.
+ ResourceManager::DeckContextDescriptorContainer aDecks;
+ mpResourceManager->GetMatchingDecks (
+ aDecks,
+ GetCurrentContext(),
+ IsDocumentReadOnly(),
+ mxFrame->getController());
+ // Notify the tab bar about the updated set of decks.
+ maFocusManager.Clear();
+ mpTabBar->SetDecks(aDecks);
+ mpTabBar->HighlightDeck(mpCurrentDeck->GetId());
+ mpTabBar->UpdateFocusManager(maFocusManager);
+ }
+ mpParentWindow->GrabFocusToDocument();
+ }
+ catch (RuntimeException&)
+ {
+ }
+ }
+}
+
+
+void SidebarController::RequestCloseDeck()
+{
+ if (comphelper::LibreOfficeKit::isActive() && mpCurrentDeck)
+ {
+ const SfxViewShell* pViewShell = SfxViewShell::Current();
+ if (pViewShell && pViewShell->isLOKMobilePhone())
+ {
+ // Mobile phone - TODO: unify with desktop
+ tools::JsonWriter aJsonWriter;
+ aJsonWriter.put("id", mpParentWindow->get_id());
+ aJsonWriter.put("type", "dockingwindow");
+ aJsonWriter.put("text", mpParentWindow->GetText());
+ aJsonWriter.put("enabled", false);
+ pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_JSDIALOG, aJsonWriter.finishAndGetAsOString());
+ }
+ else if (pViewShell)
+ {
+ tools::JsonWriter aJsonWriter;
+ aJsonWriter.put("id", mpParentWindow->get_id());
+ aJsonWriter.put("action", "close");
+ aJsonWriter.put("jsontype", "sidebar");
+ pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_JSDIALOG, aJsonWriter.finishAndGetAsOString());
+ }
+ }
+
+ mbIsDeckRequestedOpen = false;
+ UpdateDeckOpenState();
+
+ mpTabBar->RemoveDeckHighlight();
+}
+
+void SidebarController::RequestOpenDeck()
+{
+ SfxSplitWindow* pSplitWindow = GetSplitWindow();
+ if ( pSplitWindow && !pSplitWindow->IsFadeIn() )
+ // tdf#83546 Collapsed sidebar should expand first
+ pSplitWindow->FadeIn();
+
+ mbIsDeckRequestedOpen = true;
+ UpdateDeckOpenState();
+}
+
+bool SidebarController::IsDeckOpen(const sal_Int32 nIndex)
+{
+ if (nIndex >= 0)
+ {
+ OUString asDeckId(mpTabBar->GetDeckIdForIndex(nIndex));
+ return IsDeckVisible(asDeckId);
+ }
+ return mbIsDeckOpen.has_value() && *mbIsDeckOpen;
+}
+
+bool SidebarController::IsDeckVisible(std::u16string_view rsDeckId)
+{
+ return mbIsDeckOpen.has_value() && *mbIsDeckOpen && msCurrentDeckId == rsDeckId;
+}
+
+void SidebarController::UpdateDeckOpenState()
+{
+ if ( ! mbIsDeckRequestedOpen.has_value() )
+ // No state requested.
+ return;
+
+ const sal_Int32 nTabBarDefaultWidth = TabBar::GetDefaultWidth();
+
+ // Update (change) the open state when it either has not yet been initialized
+ // or when its value differs from the requested state.
+ if ( mbIsDeckOpen.has_value() && *mbIsDeckOpen == *mbIsDeckRequestedOpen )
+ return;
+
+ if (*mbIsDeckRequestedOpen)
+ {
+ if (!mpParentWindow->IsFloatingMode())
+ {
+ if (mnSavedSidebarWidth <= nTabBarDefaultWidth)
+ SetChildWindowWidth(SidebarChildWindow::GetDefaultWidth(mpParentWindow));
+ else
+ SetChildWindowWidth(mnSavedSidebarWidth);
+ }
+ else
+ {
+ // Show the Deck by resizing back to the original size (before hiding).
+ Size aNewSize(mpParentWindow->GetFloatingWindow()->GetSizePixel());
+ Point aNewPos(mpParentWindow->GetFloatingWindow()->GetPosPixel());
+
+ aNewPos.setX(aNewPos.X() - mnSavedSidebarWidth + nTabBarDefaultWidth);
+ aNewSize.setWidth(mnSavedSidebarWidth);
+
+ mpParentWindow->GetFloatingWindow()->SetPosSizePixel(aNewPos, aNewSize);
+
+ if (comphelper::LibreOfficeKit::isActive())
+ {
+ // Sidebar wide enough to render the menu; enable it.
+ mpTabBar->EnableMenuButton(true);
+
+ if (const SfxViewShell* pViewShell = mpViewFrame->GetViewShell())
+ {
+ const std::string uno = UnoNameFromDeckId(msCurrentDeckId, GetCurrentContext());
+ if (!uno.empty())
+ pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_STATE_CHANGED,
+ OString(uno + "=true"));
+ }
+ }
+ }
+ }
+ else
+ {
+ if ( ! mpParentWindow->IsFloatingMode())
+ mnSavedSidebarWidth = SetChildWindowWidth(nTabBarDefaultWidth);
+ else
+ {
+ // Hide the Deck by resizing to the width of the TabBar.
+ Size aNewSize(mpParentWindow->GetFloatingWindow()->GetSizePixel());
+ Point aNewPos(mpParentWindow->GetFloatingWindow()->GetPosPixel());
+ mnSavedSidebarWidth = aNewSize.Width(); // Save the current width to restore.
+
+ aNewPos.setX(aNewPos.X() + mnSavedSidebarWidth - nTabBarDefaultWidth);
+ if (comphelper::LibreOfficeKit::isActive())
+ {
+ // Hide by collapsing, otherwise with 0x0 the client might expect
+ // to get valid dimensions on rendering and not collapse the sidebar.
+ aNewSize.setWidth(1);
+ }
+ else
+ aNewSize.setWidth(nTabBarDefaultWidth);
+
+ mpParentWindow->GetFloatingWindow()->SetPosSizePixel(aNewPos, aNewSize);
+
+ if (comphelper::LibreOfficeKit::isActive())
+ {
+ // Sidebar too narrow to render the menu; disable it.
+ mpTabBar->EnableMenuButton(false);
+
+ if (const SfxViewShell* pViewShell = mpViewFrame->GetViewShell())
+ {
+ const std::string uno = UnoNameFromDeckId(msCurrentDeckId, GetCurrentContext());
+ if (!uno.empty())
+ pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_STATE_CHANGED,
+ OString(uno + "=false"));
+ }
+ }
+ }
+
+ if (mnWidthOnSplitterButtonDown > nTabBarDefaultWidth)
+ mnSavedSidebarWidth = mnWidthOnSplitterButtonDown;
+ mpParentWindow->SetStyle(mpParentWindow->GetStyle() & ~WB_SIZEABLE);
+ }
+
+ NotifyResize();
+}
+
+bool SidebarController::CanModifyChildWindowWidth()
+{
+ SfxSplitWindow* pSplitWindow = GetSplitWindow();
+ if (pSplitWindow == nullptr)
+ return false;
+
+ sal_uInt16 nRow (0xffff);
+ sal_uInt16 nColumn (0xffff);
+ if (pSplitWindow->GetWindowPos(mpParentWindow, nColumn, nRow))
+ {
+ sal_uInt16 nRowCount (pSplitWindow->GetWindowCount(nColumn));
+ return nRowCount==1;
+ }
+ else
+ return false;
+}
+
+sal_Int32 SidebarController::SetChildWindowWidth (const sal_Int32 nNewWidth)
+{
+ SfxSplitWindow* pSplitWindow = GetSplitWindow();
+ if (pSplitWindow == nullptr)
+ return 0;
+
+ sal_uInt16 nRow (0xffff);
+ sal_uInt16 nColumn (0xffff);
+ pSplitWindow->GetWindowPos(mpParentWindow, nColumn, nRow);
+ const tools::Long nColumnWidth (pSplitWindow->GetLineSize(nColumn));
+
+ vcl::Window* pWindow = mpParentWindow;
+ const Size aWindowSize (pWindow->GetSizePixel());
+
+ pSplitWindow->MoveWindow(
+ mpParentWindow,
+ Size(nNewWidth, aWindowSize.Height()),
+ nColumn,
+ nRow,
+ false);
+ static_cast<SplitWindow*>(pSplitWindow)->Split();
+
+ return static_cast<sal_Int32>(nColumnWidth);
+}
+
+void SidebarController::RestrictWidth (sal_Int32 nWidth)
+{
+ SfxSplitWindow* pSplitWindow = GetSplitWindow();
+ if (pSplitWindow != nullptr)
+ {
+ const sal_uInt16 nId (pSplitWindow->GetItemId(mpParentWindow.get()));
+ const sal_uInt16 nSetId (pSplitWindow->GetSet(nId));
+ const sal_Int32 nRequestedWidth = TabBar::GetDefaultWidth() + nWidth;
+
+ pSplitWindow->SetItemSizeRange(
+ nSetId,
+ Range(nRequestedWidth, std::max(nRequestedWidth, getMaximumWidth())));
+ }
+}
+
+SfxSplitWindow* SidebarController::GetSplitWindow()
+{
+ if (mpParentWindow != nullptr)
+ {
+ SfxSplitWindow* pSplitWindow = dynamic_cast<SfxSplitWindow*>(mpParentWindow->GetParent());
+ if (pSplitWindow != mpSplitWindow)
+ {
+ if (mpSplitWindow != nullptr)
+ mpSplitWindow->RemoveEventListener(LINK(this, SidebarController, WindowEventHandler));
+
+ mpSplitWindow = pSplitWindow;
+
+ if (mpSplitWindow != nullptr)
+ mpSplitWindow->AddEventListener(LINK(this, SidebarController, WindowEventHandler));
+ }
+ return mpSplitWindow;
+ }
+ else
+ return nullptr;
+}
+
+void SidebarController::UpdateCloseIndicator (const bool bCloseAfterDrag)
+{
+ if (mpParentWindow == nullptr)
+ return;
+
+ if (bCloseAfterDrag)
+ {
+ // Make sure that the indicator exists.
+ if (!mpCloseIndicator)
+ mpCloseIndicator.reset(VclPtr<CloseIndicator>::Create(mpParentWindow));
+
+ // Place and show the indicator.
+ const Size aWindowSize (mpParentWindow->GetSizePixel());
+ const Size aImageSize (mpCloseIndicator->GetSizePixel());
+ mpCloseIndicator->SetPosPixel(
+ Point(
+ aWindowSize.Width() - TabBar::GetDefaultWidth() - aImageSize.Width(),
+ (aWindowSize.Height() - aImageSize.Height())/2));
+ mpCloseIndicator->Show();
+ }
+ else
+ {
+ // Hide but don't delete the indicator.
+ if (mpCloseIndicator)
+ mpCloseIndicator->Hide();
+ }
+}
+
+void SidebarController::UpdateTitleBarIcons()
+{
+ if ( ! mpCurrentDeck)
+ return;
+
+ const bool bIsHighContrastModeActive (Theme::IsHighContrastMode());
+
+ const ResourceManager& rResourceManager = *mpResourceManager;
+
+ // Update the deck icon.
+ std::shared_ptr<DeckDescriptor> xDeckDescriptor = rResourceManager.GetDeckDescriptor(mpCurrentDeck->GetId());
+ if (xDeckDescriptor && mpCurrentDeck->GetTitleBar())
+ {
+ const OUString sIconURL(
+ bIsHighContrastModeActive
+ ? xDeckDescriptor->msHighContrastTitleBarIconURL
+ : xDeckDescriptor->msTitleBarIconURL);
+ mpCurrentDeck->GetTitleBar()->SetIcon(Tools::GetImage(sIconURL, mxFrame));
+ }
+
+ // Update the panel icons.
+ const SharedPanelContainer& rPanels (mpCurrentDeck->GetPanels());
+ for (const auto& rxPanel : rPanels)
+ {
+ if ( ! rxPanel)
+ continue;
+ if (!rxPanel->GetTitleBar())
+ continue;
+ std::shared_ptr<PanelDescriptor> xPanelDescriptor = rResourceManager.GetPanelDescriptor(rxPanel->GetId());
+ if (!xPanelDescriptor)
+ continue;
+ const OUString sIconURL (
+ bIsHighContrastModeActive
+ ? xPanelDescriptor->msHighContrastTitleBarIconURL
+ : xPanelDescriptor->msTitleBarIconURL);
+ rxPanel->GetTitleBar()->SetIcon(Tools::GetImage(sIconURL, mxFrame));
+ }
+}
+
+void SidebarController::ShowPanel (const Panel& rPanel)
+{
+ if (mpCurrentDeck)
+ {
+ if (!IsDeckOpen())
+ RequestOpenDeck();
+ mpCurrentDeck->ShowPanel(rPanel);
+ }
+}
+
+ResourceManager::DeckContextDescriptorContainer SidebarController::GetMatchingDecks()
+{
+ ResourceManager::DeckContextDescriptorContainer aDecks;
+ mpResourceManager->GetMatchingDecks (aDecks,
+ GetCurrentContext(),
+ IsDocumentReadOnly(),
+ mxFrame->getController());
+ return aDecks;
+}
+
+ResourceManager::PanelContextDescriptorContainer SidebarController::GetMatchingPanels(std::u16string_view rDeckId)
+{
+ ResourceManager::PanelContextDescriptorContainer aPanels;
+
+ mpResourceManager->GetMatchingPanels(aPanels,
+ GetCurrentContext(),
+ rDeckId,
+ mxFrame->getController());
+ return aPanels;
+}
+
+void SidebarController::updateModel(const css::uno::Reference<css::frame::XModel>& xModel)
+{
+ mpResourceManager->UpdateModel(xModel);
+}
+
+void SidebarController::FadeOut()
+{
+ if (mpSplitWindow)
+ mpSplitWindow->FadeOut();
+}
+
+void SidebarController::FadeIn()
+{
+ if (mpSplitWindow)
+ mpSplitWindow->FadeIn();
+}
+
+tools::Rectangle SidebarController::GetDeckDragArea() const
+{
+ tools::Rectangle aRect;
+ if (mpCurrentDeck)
+ {
+ if (DeckTitleBar* pTitleBar = mpCurrentDeck->GetTitleBar())
+ {
+ aRect = pTitleBar->GetDragArea();
+ }
+ }
+ return aRect;
+}
+
+void SidebarController::frameAction(const css::frame::FrameActionEvent& rEvent)
+{
+ if (rEvent.Frame == mxFrame)
+ {
+ if (rEvent.Action == css::frame::FrameAction_COMPONENT_DETACHING)
+ unregisterSidebarForFrame(mxFrame->getController());
+ else if (rEvent.Action == css::frame::FrameAction_COMPONENT_REATTACHED)
+ registerSidebarForFrame(mxFrame->getController());
+ }
+}
+
+void SidebarController::saveDeckState()
+{
+ // Impress shutdown : context (frame) is disposed before sidebar disposing
+ // calc writer : context (frame) is disposed after sidebar disposing
+ // so need to test if GetCurrentContext is still valid regarding msApplication
+ if (GetCurrentContext().msApplication != "none")
+ {
+ mpResourceManager->SaveDecksSettings(GetCurrentContext());
+ mpResourceManager->SaveLastActiveDeck(GetCurrentContext(), msCurrentDeckId);
+ }
+}
+
+static bool isChartOrMathContext(const Context& context)
+{
+ return context.msApplication == "com.sun.star.chart2.ChartDocument"
+ || context.msApplication == "com.sun.star.formula.FormulaProperties";
+}
+
+bool SidebarController::hasChartOrMathContextCurrently() const
+{
+ if ((maRequestedContext != maCurrentContext) && isChartOrMathContext(maRequestedContext))
+ return true; // We are not yet changed, but in the process
+
+ return isChartOrMathContext(maCurrentContext);
+}
+
+sfx2::sidebar::SidebarController* SidebarController::GetSidebarControllerForView(const SfxViewShell* pViewShell)
+{
+ if (!pViewShell)
+ return nullptr;
+
+ Reference<css::frame::XController2> xController(pViewShell->GetController(), UNO_QUERY);
+ if (!xController.is())
+ return nullptr;
+
+ // Make sure there is a model behind the controller, otherwise getSidebar() can crash.
+ if (!xController->getModel().is())
+ return nullptr;
+
+ Reference<css::ui::XSidebarProvider> xSidebarProvider = xController->getSidebar();
+ if (!xSidebarProvider.is())
+ return nullptr;
+
+ Reference<css::ui::XSidebar> xSidebar = xSidebarProvider->getSidebar();
+ if (!xSidebar.is())
+ return nullptr;
+
+ return dynamic_cast<sfx2::sidebar::SidebarController*>(xSidebar.get());
+}
+
+} // end of namespace sfx2::sidebar
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/sidebar/SidebarDockingWindow.cxx b/sfx2/source/sidebar/SidebarDockingWindow.cxx
new file mode 100644
index 0000000000..23c3b459c3
--- /dev/null
+++ b/sfx2/source/sidebar/SidebarDockingWindow.cxx
@@ -0,0 +1,216 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#include <sfx2/sidebar/SidebarDockingWindow.hxx>
+#include <sfx2/sidebar/SidebarChildWindow.hxx>
+#include <sfx2/sidebar/SidebarController.hxx>
+#include <sidebar/PanelDescriptor.hxx>
+
+#include <comphelper/dispatchcommand.hxx>
+#include <comphelper/lok.hxx>
+#include <comphelper/processfactory.hxx>
+#include <sfx2/bindings.hxx>
+#include <sfx2/dispatch.hxx>
+#include <sfx2/viewfrm.hxx>
+#include <svtools/acceleratorexecute.hxx>
+#include <tools/gen.hxx>
+#include <vcl/event.hxx>
+#include <osl/diagnose.h>
+
+#include <boost/property_tree/json_parser.hpp>
+
+using namespace css;
+using namespace css::uno;
+
+namespace sfx2::sidebar {
+
+SidebarDockingWindow::SidebarDockingWindow(SfxBindings* pSfxBindings, SidebarChildWindow& rChildWindow,
+ vcl::Window* pParentWindow, WinBits nBits)
+ : SfxDockingWindow(pSfxBindings, &rChildWindow, pParentWindow, nBits)
+ , mbIsReadyToDrag(false)
+{
+ // Get the XFrame from the bindings.
+ if (pSfxBindings==nullptr || pSfxBindings->GetDispatcher()==nullptr)
+ {
+ OSL_ASSERT(pSfxBindings!=nullptr);
+ OSL_ASSERT(pSfxBindings->GetDispatcher()!=nullptr);
+ }
+ else if (!comphelper::LibreOfficeKit::isActive())
+ {
+ GetOrCreateSidebarController();
+ }
+}
+
+rtl::Reference<sfx2::sidebar::SidebarController>& SidebarDockingWindow::GetOrCreateSidebarController()
+{
+ if (!mpSidebarController)
+ {
+ const SfxViewFrame* pViewFrame = GetBindings().GetDispatcher()->GetFrame();
+ mpSidebarController = sfx2::sidebar::SidebarController::create(this, pViewFrame);
+ }
+
+ return mpSidebarController;
+}
+
+SidebarDockingWindow::~SidebarDockingWindow()
+{
+ disposeOnce();
+}
+
+void SidebarDockingWindow::dispose()
+{
+ Reference<lang::XComponent> xComponent(mpSidebarController);
+ mpSidebarController.clear();
+ if (xComponent.is())
+ xComponent->dispose();
+
+ SfxDockingWindow::dispose();
+}
+
+void SidebarDockingWindow::GetFocus()
+{
+ if (mpSidebarController.is())
+ {
+ mpSidebarController->RequestOpenDeck();
+ mpSidebarController->GetFocusManager().GrabFocus();
+ }
+ else
+ SfxDockingWindow::GetFocus();
+}
+
+bool SidebarDockingWindow::Close()
+{
+ if (mpSidebarController.is())
+ mpSidebarController->SetFloatingDeckClosed(true);
+
+ return SfxDockingWindow::Close();
+}
+
+void SidebarDockingWindow::SyncUpdate()
+{
+ if (mpSidebarController.is())
+ mpSidebarController->SyncUpdate();
+}
+
+SfxChildAlignment SidebarDockingWindow::CheckAlignment (
+ SfxChildAlignment eCurrentAlignment,
+ SfxChildAlignment eRequestedAlignment)
+{
+ switch (eRequestedAlignment)
+ {
+ case SfxChildAlignment::TOP:
+ case SfxChildAlignment::HIGHESTTOP:
+ case SfxChildAlignment::LOWESTTOP:
+ case SfxChildAlignment::BOTTOM:
+ case SfxChildAlignment::LOWESTBOTTOM:
+ case SfxChildAlignment::HIGHESTBOTTOM:
+ return eCurrentAlignment;
+
+ case SfxChildAlignment::LEFT:
+ case SfxChildAlignment::RIGHT:
+ case SfxChildAlignment::FIRSTLEFT:
+ case SfxChildAlignment::LASTLEFT:
+ case SfxChildAlignment::FIRSTRIGHT:
+ case SfxChildAlignment::LASTRIGHT:
+ return eRequestedAlignment;
+
+ default:
+ return eRequestedAlignment;
+ }
+}
+
+bool SidebarDockingWindow::EventNotify(NotifyEvent& rEvent)
+{
+ NotifyEventType nType = rEvent.GetType();
+ if (NotifyEventType::KEYINPUT == nType)
+ {
+ const vcl::KeyCode& rKeyCode = rEvent.GetKeyEvent()->GetKeyCode();
+ switch (rKeyCode.GetCode())
+ {
+ case KEY_UP:
+ case KEY_DOWN:
+ case KEY_PAGEUP:
+ case KEY_PAGEDOWN:
+ case KEY_HOME:
+ case KEY_END:
+ case KEY_LEFT:
+ case KEY_RIGHT:
+ case KEY_BACKSPACE:
+ case KEY_DELETE:
+ case KEY_INSERT:
+ case KEY_RETURN:
+ case KEY_ESCAPE:
+ {
+ return true;
+ }
+ default:
+ break;
+ }
+ if (!mpAccel)
+ {
+ mpAccel = svt::AcceleratorExecute::createAcceleratorHelper();
+ mpAccel->init(comphelper::getProcessComponentContext(), mpSidebarController->getXFrame());
+ }
+ const OUString aCommand(mpAccel->findCommand(svt::AcceleratorExecute::st_VCLKey2AWTKey(rKeyCode)));
+ if (".uno:DesignerDialog" == aCommand)
+ {
+ std::shared_ptr<PanelDescriptor> xPanelDescriptor =
+ mpSidebarController->GetResourceManager()->GetPanelDescriptor( u"StyleListPanel" );
+ if ( xPanelDescriptor && mpSidebarController->IsDeckVisible( xPanelDescriptor->msDeckId ) )
+ Close();
+ return true;
+ }
+ if (".uno:Undo" == aCommand || ".uno:Redo" == aCommand)
+ {
+ comphelper::dispatchCommand(aCommand, {});
+ return true;
+ }
+ }
+ else if (NotifyEventType::MOUSEBUTTONDOWN == nType)
+ {
+ const MouseEvent *mEvt = rEvent.GetMouseEvent();
+ if (mEvt->IsLeft())
+ {
+ tools::Rectangle aGrip = mpSidebarController->GetDeckDragArea();
+ if ( aGrip.Contains( mEvt->GetPosPixel() ) )
+ mbIsReadyToDrag = true;
+ }
+ }
+ else if (NotifyEventType::MOUSEMOVE == nType)
+ {
+ const MouseEvent *mEvt = rEvent.GetMouseEvent();
+ tools::Rectangle aGrip = mpSidebarController->GetDeckDragArea();
+ if (mEvt->IsLeft() && aGrip.Contains( mEvt->GetPosPixel() ) && mbIsReadyToDrag )
+ {
+ Point aPos = mEvt->GetPosPixel();
+ vcl::Window* pWindow = rEvent.GetWindow();
+ if ( pWindow != this )
+ {
+ aPos = pWindow->OutputToScreenPixel( aPos );
+ aPos = ScreenToOutputPixel( aPos );
+ }
+ ImplStartDocking( aPos );
+ }
+ }
+
+ return SfxDockingWindow::EventNotify(rEvent);
+}
+
+} // end of namespace sfx2::sidebar
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/sidebar/SidebarModelUpdate.cxx b/sfx2/source/sidebar/SidebarModelUpdate.cxx
new file mode 100644
index 0000000000..42821bc564
--- /dev/null
+++ b/sfx2/source/sidebar/SidebarModelUpdate.cxx
@@ -0,0 +1,17 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <sfx2/sidebar/SidebarModelUpdate.hxx>
+
+namespace sfx2::sidebar
+{
+SidebarModelUpdate::~SidebarModelUpdate() {}
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/sidebar/SidebarPanelBase.cxx b/sfx2/source/sidebar/SidebarPanelBase.cxx
new file mode 100644
index 0000000000..1bf3fe0fee
--- /dev/null
+++ b/sfx2/source/sidebar/SidebarPanelBase.cxx
@@ -0,0 +1,198 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#include <sfx2/sidebar/SidebarPanelBase.hxx>
+#include <sfx2/sidebar/ILayoutableWindow.hxx>
+#include <sfx2/sidebar/IContextChangeReceiver.hxx>
+#include <sfx2/sidebar/PanelLayout.hxx>
+#include <sfx2/sidebar/SidebarModelUpdate.hxx>
+#include <utility>
+#include <vcl/EnumContext.hxx>
+#include <vcl/svapp.hxx>
+#include <comphelper/processfactory.hxx>
+#include <com/sun/star/awt/XWindowPeer.hpp>
+#include <com/sun/star/ui/ContextChangeEventMultiplexer.hpp>
+#include <com/sun/star/ui/UIElementType.hpp>
+
+using namespace css;
+using namespace css::uno;
+
+namespace sfx2::sidebar {
+
+Reference<ui::XUIElement> SidebarPanelBase::Create (
+ const OUString& rsResourceURL,
+ const css::uno::Reference<css::frame::XFrame>& rxFrame,
+ std::unique_ptr<PanelLayout> xControl,
+ const css::ui::LayoutSize& rLayoutSize)
+{
+ Reference<ui::XUIElement> xUIElement (
+ new SidebarPanelBase(
+ rsResourceURL,
+ rxFrame,
+ std::move(xControl),
+ rLayoutSize));
+ return xUIElement;
+}
+
+SidebarPanelBase::SidebarPanelBase (
+ OUString sResourceURL,
+ css::uno::Reference<css::frame::XFrame> xFrame,
+ std::unique_ptr<PanelLayout> xControl,
+ const css::ui::LayoutSize& rLayoutSize)
+ : mxFrame(std::move(xFrame)),
+ mxControl(std::move(xControl)),
+ msResourceURL(std::move(sResourceURL)),
+ maLayoutSize(rLayoutSize)
+{
+ if (mxFrame.is())
+ {
+ css::uno::Reference<css::ui::XContextChangeEventMultiplexer> xMultiplexer (
+ css::ui::ContextChangeEventMultiplexer::get(
+ ::comphelper::getProcessComponentContext()));
+ xMultiplexer->addContextChangeEventListener(this, mxFrame->getController());
+ }
+}
+
+SidebarPanelBase::~SidebarPanelBase()
+{
+}
+
+void SidebarPanelBase::SetParentPanel(sfx2::sidebar::Panel* pPanel)
+{
+ if (!mxControl)
+ return;
+ mxControl->SetPanel(pPanel);
+}
+
+void SidebarPanelBase::disposing(std::unique_lock<std::mutex>&)
+{
+ SolarMutexGuard aGuard;
+
+ mxControl.reset();
+
+ if (mxFrame.is())
+ {
+ css::uno::Reference<css::ui::XContextChangeEventMultiplexer> xMultiplexer (
+ css::ui::ContextChangeEventMultiplexer::get(
+ ::comphelper::getProcessComponentContext()));
+ xMultiplexer->removeAllContextChangeEventListeners(this);
+ mxFrame = nullptr;
+ }
+}
+
+// XContextChangeEventListener
+void SAL_CALL SidebarPanelBase::notifyContextChangeEvent (
+ const ui::ContextChangeEventObject& rEvent)
+{
+ SolarMutexGuard aGuard;
+
+ IContextChangeReceiver* pContextChangeReceiver
+ = dynamic_cast<IContextChangeReceiver*>(mxControl.get());
+ if (pContextChangeReceiver != nullptr)
+ {
+ const vcl::EnumContext aContext(
+ vcl::EnumContext::GetApplicationEnum(rEvent.ApplicationName),
+ vcl::EnumContext::GetContextEnum(rEvent.ContextName));
+ pContextChangeReceiver->HandleContextChange(aContext);
+ }
+}
+
+void SAL_CALL SidebarPanelBase::disposing (
+ const css::lang::EventObject&)
+{
+ SolarMutexGuard aGuard;
+
+ mxFrame = nullptr;
+ mxControl.reset();
+}
+
+css::uno::Reference<css::frame::XFrame> SAL_CALL SidebarPanelBase::getFrame()
+{
+ return mxFrame;
+}
+
+OUString SAL_CALL SidebarPanelBase::getResourceURL()
+{
+ return msResourceURL;
+}
+
+sal_Int16 SAL_CALL SidebarPanelBase::getType()
+{
+ return ui::UIElementType::TOOLPANEL;
+}
+
+Reference<XInterface> SAL_CALL SidebarPanelBase::getRealInterface()
+{
+ return getXWeak();
+}
+
+Reference<accessibility::XAccessible> SAL_CALL SidebarPanelBase::createAccessible (
+ const Reference<accessibility::XAccessible>&)
+{
+ // Not implemented.
+ return nullptr;
+}
+
+Reference<awt::XWindow> SAL_CALL SidebarPanelBase::getWindow()
+{
+ // Not implemented
+ return nullptr;
+}
+
+ui::LayoutSize SAL_CALL SidebarPanelBase::getHeightForWidth (const sal_Int32 nWidth)
+{
+ SolarMutexGuard aGuard;
+
+ if (maLayoutSize.Minimum >= 0)
+ return maLayoutSize;
+
+ ILayoutableWindow* pLayoutableWindow = dynamic_cast<ILayoutableWindow*>(mxControl.get());
+ if (pLayoutableWindow)
+ return pLayoutableWindow->GetHeightForWidth(nWidth);
+ else
+ {
+ // widget layout-based sidebar
+ mxControl->queue_resize();
+ Size aSize(mxControl->get_preferred_size());
+ return ui::LayoutSize(aSize.Height(), aSize.Height(), aSize.Height());
+ }
+}
+
+sal_Int32 SAL_CALL SidebarPanelBase::getMinimalWidth ()
+{
+ SolarMutexGuard aGuard;
+
+ // widget layout-based sidebar
+ Size aSize(mxControl->get_preferred_size());
+ return aSize.Width();
+}
+
+void SAL_CALL SidebarPanelBase::updateModel(const css::uno::Reference<css::frame::XModel>& xModel)
+{
+ SolarMutexGuard aGuard;
+
+ SidebarModelUpdate* pModelUpdate = dynamic_cast<SidebarModelUpdate*>(mxControl.get());
+ if (!pModelUpdate)
+ return;
+
+ pModelUpdate->updateModel(xModel);
+}
+
+} // end of namespace sfx2::sidebar
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/sidebar/SidebarToolBox.cxx b/sfx2/source/sidebar/SidebarToolBox.cxx
new file mode 100644
index 0000000000..349e5bef14
--- /dev/null
+++ b/sfx2/source/sidebar/SidebarToolBox.cxx
@@ -0,0 +1,343 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sidebar/SidebarToolBox.hxx>
+#include <sidebar/ControllerFactory.hxx>
+#include <sfx2/viewfrm.hxx>
+
+#include <officecfg/Office/Common.hxx>
+#include <vcl/commandinfoprovider.hxx>
+#include <vcl/event.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/svapp.hxx>
+#include <toolkit/helper/vclunohelper.hxx>
+#include <svtools/miscopt.hxx>
+#include <com/sun/star/frame/XSubToolbarController.hpp>
+#include <com/sun/star/lang/XComponent.hpp>
+#include <com/sun/star/frame/XFrame.hpp>
+#include <com/sun/star/frame/XToolbarController.hpp>
+
+using namespace css;
+using namespace css::uno;
+
+namespace {
+ void lcl_RTLizeCommandURL( OUString& rCommandURL )
+ {
+ if (rCommandURL == ".uno:ParaLeftToRight")
+ rCommandURL = ".uno:ParaRightToLeft";
+ else if (rCommandURL == ".uno:ParaRightToLeft")
+ rCommandURL = ".uno:ParaLeftToRight";
+ else if (rCommandURL == ".uno:LeftPara")
+ rCommandURL = ".uno:RightPara";
+ else if (rCommandURL == ".uno:RightPara")
+ rCommandURL = ".uno:LeftPara";
+ else if (rCommandURL == ".uno:AlignLeft")
+ rCommandURL = ".uno:AlignRight";
+ else if (rCommandURL == ".uno:AlignRight")
+ rCommandURL = ".uno:AlignLeft";
+ }
+}
+
+namespace sfx2::sidebar {
+
+SidebarToolBox::SidebarToolBox (vcl::Window* pParentWindow)
+ : ToolBox(pParentWindow, 0),
+ mbAreHandlersRegistered(false),
+ mbUseDefaultButtonSize(true),
+ mbSideBar(true)
+{
+ SetBackground(Wallpaper());
+ SetPaintTransparent(true);
+ SetToolboxButtonSize(GetDefaultButtonSize());
+
+ SvtMiscOptions().AddListenerLink(LINK(this, SidebarToolBox, ChangedIconHandler));
+ SetDataChangedHdl(LINK(this, SidebarToolBox, ChangedDataHandler));
+ if (SfxViewFrame* pViewFrm = SfxViewFrame::Current())
+ {
+ auto xFrame(pViewFrm->GetFrame().GetFrameInterface());
+ auto xWidget(VCLUnoHelper::GetInterface(this));
+ mxImageController = sfx2::sidebar::ControllerFactory::CreateImageController(xFrame, xWidget);
+ }
+
+#ifdef DEBUG
+ SetText(OUString("SidebarToolBox"));
+#endif
+}
+
+SidebarToolBox::~SidebarToolBox()
+{
+ disposeOnce();
+}
+
+void SidebarToolBox::dispose()
+{
+ SvtMiscOptions().RemoveListenerLink(LINK(this, SidebarToolBox, ChangedIconHandler));
+ SetDataChangedHdl(Link<const DataChangedEvent*, void>());
+
+ ControllerContainer aControllers;
+ aControllers.swap(maControllers);
+ for (auto const& controller : aControllers)
+ {
+ Reference<lang::XComponent> xComponent(controller.second, UNO_QUERY);
+ if (xComponent.is())
+ xComponent->dispose();
+ }
+
+ if (mxImageController)
+ mxImageController->dispose();
+
+ if (mbAreHandlersRegistered)
+ {
+ SetDropdownClickHdl(Link<ToolBox *, void>());
+ SetClickHdl(Link<ToolBox *, void>());
+ SetDoubleClickHdl(Link<ToolBox *, void>());
+ SetSelectHdl(Link<ToolBox *, void>());
+ SetActivateHdl(Link<ToolBox *, void>());
+ SetDeactivateHdl(Link<ToolBox *, void>());
+ mbAreHandlersRegistered = false;
+ }
+
+ ToolBox::dispose();
+}
+
+ToolBoxButtonSize SidebarToolBox::GetDefaultButtonSize() const
+{
+ return static_cast<ToolBoxButtonSize>(officecfg::Office::Common::Misc::SidebarIconSize::get());
+}
+
+void SidebarToolBox::InsertItem(const OUString& rCommand,
+ const css::uno::Reference<css::frame::XFrame>& rFrame,
+ ToolBoxItemBits nBits, const Size& rRequestedSize, ImplToolItems::size_type nPos)
+{
+ OUString aCommand( rCommand );
+
+ if( AllSettings::GetLayoutRTL() )
+ {
+ lcl_RTLizeCommandURL( aCommand );
+ }
+
+ ToolBox::InsertItem(aCommand, rFrame, nBits, rRequestedSize, nPos);
+
+ CreateController(GetItemId(aCommand), rFrame, std::max(rRequestedSize.Width(), ::tools::Long(0)), mbSideBar);
+ RegisterHandlers();
+}
+
+bool SidebarToolBox::EventNotify (NotifyEvent& rEvent)
+{
+ if (rEvent.GetType() == NotifyEventType::KEYINPUT)
+ {
+ if (rEvent.GetKeyEvent()->GetKeyCode().GetCode() == KEY_TAB)
+ {
+ // Special handling for transferring handling of KEY_TAB
+ // that becomes necessary because of our parent that is
+ // not the dialog but a background control.
+ return DockingWindow::EventNotify(rEvent);
+ }
+ }
+ return ToolBox::EventNotify(rEvent);
+}
+
+void SidebarToolBox::KeyInput(const KeyEvent& rKEvt)
+{
+ if (KEY_ESCAPE != rKEvt.GetKeyCode().GetCode())
+ ToolBox::KeyInput(rKEvt);
+}
+
+void SidebarToolBox::CreateController (
+ const ToolBoxItemId nItemId,
+ const css::uno::Reference<css::frame::XFrame>& rxFrame,
+ const sal_Int32 nItemWidth, bool bSideBar)
+{
+ const OUString sCommandName (GetItemCommand(nItemId));
+
+ uno::Reference<frame::XToolbarController> xController(sfx2::sidebar::ControllerFactory::CreateToolBoxController(
+ this, nItemId, sCommandName, rxFrame, rxFrame->getController(),
+ VCLUnoHelper::GetInterface(this), nItemWidth, bSideBar));
+
+ if (xController.is())
+ maControllers.insert(std::make_pair(nItemId, xController));
+}
+
+Reference<frame::XToolbarController> SidebarToolBox::GetControllerForItemId (const ToolBoxItemId nItemId) const
+{
+ ControllerContainer::const_iterator iController (maControllers.find(nItemId));
+ if (iController != maControllers.end())
+ return iController->second;
+
+ return Reference<frame::XToolbarController>();
+}
+
+void SidebarToolBox::RegisterHandlers()
+{
+ if ( ! mbAreHandlersRegistered)
+ {
+ mbAreHandlersRegistered = true;
+ SetDropdownClickHdl(LINK(this, SidebarToolBox, DropDownClickHandler));
+ SetClickHdl(LINK(this, SidebarToolBox, ClickHandler));
+ SetDoubleClickHdl(LINK(this, SidebarToolBox, DoubleClickHandler));
+ SetSelectHdl(LINK(this, SidebarToolBox, SelectHandler));
+ }
+}
+
+IMPL_LINK(SidebarToolBox, DropDownClickHandler, ToolBox*, pToolBox, void)
+{
+ if (pToolBox != nullptr)
+ {
+ Reference<frame::XToolbarController> xController (GetControllerForItemId(pToolBox->GetCurItemId()));
+ if (xController.is())
+ {
+ Reference<awt::XWindow> xWindow = xController->createPopupWindow();
+ if (xWindow.is() )
+ xWindow->setFocus();
+ }
+ }
+}
+
+IMPL_LINK(SidebarToolBox, ClickHandler, ToolBox*, pToolBox, void)
+{
+ if (pToolBox == nullptr)
+ return;
+
+ Reference<frame::XToolbarController> xController (GetControllerForItemId(pToolBox->GetCurItemId()));
+ if (xController.is())
+ xController->click();
+}
+
+IMPL_LINK(SidebarToolBox, DoubleClickHandler, ToolBox*, pToolBox, void)
+{
+ if (pToolBox == nullptr)
+ return;
+
+ Reference<frame::XToolbarController> xController (GetControllerForItemId(pToolBox->GetCurItemId()));
+ if (xController.is())
+ xController->doubleClick();
+}
+
+IMPL_LINK(SidebarToolBox, SelectHandler, ToolBox*, pToolBox, void)
+{
+ if (pToolBox == nullptr)
+ return;
+
+ Reference<frame::XToolbarController> xController (GetControllerForItemId(pToolBox->GetCurItemId()));
+ if (xController.is())
+ xController->execute(static_cast<sal_Int16>(pToolBox->GetModifier()));
+}
+
+IMPL_LINK_NOARG(SidebarToolBox, ChangedIconHandler, LinkParamNone*, void)
+{
+ SolarMutexGuard g;
+
+ if (mbUseDefaultButtonSize)
+ SetToolboxButtonSize(GetDefaultButtonSize());
+
+ for (auto const& it : maControllers)
+ {
+ Reference<frame::XSubToolbarController> xController(it.second, UNO_QUERY);
+ if (xController.is() && xController->opensSubToolbar())
+ {
+ // The button should show the last function that was selected from the
+ // dropdown. The controller should know better than us what it was.
+ xController->updateImage();
+ }
+ else if (SfxViewFrame* pViewFrm = SfxViewFrame::Current())
+ {
+ OUString aCommandURL = GetItemCommand(it.first);
+ css::uno::Reference<frame::XFrame> xFrame = pViewFrm->GetFrame().GetFrameInterface();
+ Image aImage = vcl::CommandInfoProvider::GetImageForCommand(aCommandURL, xFrame, GetImageSize());
+ SetItemImage(it.first, aImage);
+ }
+ }
+
+ Resize();
+ queue_resize();
+}
+
+IMPL_LINK(SidebarToolBox, ChangedDataHandler, const DataChangedEvent*, pDataChangedEvent, void)
+{
+ if ((( pDataChangedEvent->GetType() == DataChangedEventType::SETTINGS ) ||
+ ( pDataChangedEvent->GetType() == DataChangedEventType::DISPLAY )) &&
+ ( pDataChangedEvent->GetFlags() & AllSettingsFlags::STYLE ))
+ {
+ ChangedIconHandler(nullptr);
+ }
+}
+
+void SidebarToolBox::InitToolBox(VclBuilder::stringmap& rMap)
+{
+ for (const auto& it : rMap)
+ {
+ if (it.first == "toolbar-style")
+ {
+ if (it.second == "text")
+ SetButtonType(ButtonType::TEXT);
+ else if (it.second == "both-horiz")
+ SetButtonType(ButtonType::SYMBOLTEXT);
+ else if (it.second == "both")
+ {
+ SetButtonType(ButtonType::SYMBOLTEXT);
+ SetToolBoxTextPosition(ToolBoxTextPosition::Bottom);
+ }
+ }
+ else if (it.first == "icon-size")
+ {
+ mbUseDefaultButtonSize = false;
+ if (it.second == "1" || it.second == "2" || it.second == "4")
+ SetToolboxButtonSize(ToolBoxButtonSize::Small);
+ else if (it.second == "3")
+ SetToolboxButtonSize(ToolBoxButtonSize::Large);
+ else if (it.second == "5")
+ SetToolboxButtonSize(ToolBoxButtonSize::Size32);
+ }
+ else if (it.first == "orientation" && it.second == "vertical")
+ SetAlign(WindowAlign::Left);
+ }
+}
+
+namespace {
+
+class NotebookbarToolBox : public SidebarToolBox
+{
+public:
+ explicit NotebookbarToolBox(vcl::Window* pParentWindow)
+ : SidebarToolBox(pParentWindow)
+ {
+ mbSideBar = false;
+ SetToolboxButtonSize(GetDefaultButtonSize());
+ }
+
+ virtual ToolBoxButtonSize GetDefaultButtonSize() const override
+ {
+ return static_cast<ToolBoxButtonSize>(officecfg::Office::Common::Misc::NotebookbarIconSize::get());
+ }
+};
+
+}
+
+extern "C" SAL_DLLPUBLIC_EXPORT void makeNotebookbarToolBox(VclPtr<vcl::Window> & rRet, const VclPtr<vcl::Window> & pParent, VclBuilder::stringmap & rMap)
+{
+ static_assert(std::is_same_v<std::remove_pointer_t<VclBuilder::customMakeWidget>,
+ decltype(makeNotebookbarToolBox)>);
+ VclPtrInstance<NotebookbarToolBox> pBox(pParent);
+ pBox->InitToolBox(rMap);
+ rRet = pBox;
+}
+
+} // end of namespace sfx2::sidebar
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/sidebar/TabBar.cxx b/sfx2/source/sidebar/TabBar.cxx
new file mode 100644
index 0000000000..9d92d24cd9
--- /dev/null
+++ b/sfx2/source/sidebar/TabBar.cxx
@@ -0,0 +1,392 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sfx2/sidebar/TabBar.hxx>
+#include <sidebar/DeckDescriptor.hxx>
+#include <sfx2/sidebar/Theme.hxx>
+#include <sidebar/Tools.hxx>
+#include <sfx2/sidebar/FocusManager.hxx>
+#include <sfx2/sidebar/SidebarController.hxx>
+
+#include <comphelper/lok.hxx>
+#include <comphelper/processfactory.hxx>
+#include <o3tl/safeint.hxx>
+#include <utility>
+#include <vcl/commandevent.hxx>
+#include <vcl/commandinfoprovider.hxx>
+#include <vcl/event.hxx>
+#include <vcl/svapp.hxx>
+#include <svtools/acceleratorexecute.hxx>
+#include <osl/diagnose.h>
+
+#include "uiobject.hxx"
+
+using namespace css;
+using namespace css::uno;
+
+static int gDefaultWidth;
+
+namespace sfx2::sidebar {
+
+TabBar::TabBar(vcl::Window* pParentWindow,
+ const Reference<frame::XFrame>& rxFrame,
+ std::function<void (const OUString&)> aDeckActivationFunctor,
+ PopupMenuProvider aPopupMenuProvider,
+ SidebarController* rParentSidebarController
+ )
+ : InterimItemWindow(pParentWindow, "sfx/ui/tabbar.ui", "TabBar")
+ , mxFrame(rxFrame)
+ , mxAuxBuilder(Application::CreateBuilder(m_xContainer.get(), "sfx/ui/tabbarcontents.ui"))
+ , mxTempToplevel(mxAuxBuilder->weld_box("toplevel"))
+ , mxContents(mxAuxBuilder->weld_widget("TabBarContents"))
+ , mxMeasureBox(mxAuxBuilder->weld_widget("measure"))
+ , maDeckActivationFunctor(std::move(aDeckActivationFunctor))
+ , maPopupMenuProvider(std::move(aPopupMenuProvider))
+ , pParentSidebarController(rParentSidebarController)
+{
+ set_id("TabBar"); // for uitest
+
+ InitControlBase(mxMenuButton.get());
+
+ mxTempToplevel->move(mxContents.get(), m_xContainer.get());
+
+ // For Gtk4 defer menu_button until after the contents have been
+ // transferred to its final home (where the old parent is a GtkWindow to
+ // support loading the accelerators in the menu for Gtk3)
+ mxMenuButton = mxAuxBuilder->weld_menu_button("menubutton");
+ mxMainMenu = mxAuxBuilder->weld_menu("mainmenu");
+ mxSubMenu = mxAuxBuilder->weld_menu("submenu");
+
+ gDefaultWidth = m_xContainer->get_preferred_size().Width();
+
+ // we have this widget just so we can measure best width for static TabBar::GetDefaultWidth
+ mxMeasureBox->hide();
+
+ SetBackground(Wallpaper(Theme::GetColor(Theme::Color_TabBarBackground)));
+
+ mxMenuButton->connect_toggled(LINK(this, TabBar, OnToolboxClicked));
+
+#ifdef DEBUG
+ SetText(OUString("TabBar"));
+#endif
+}
+
+TabBar::~TabBar()
+{
+ disposeOnce();
+}
+
+void TabBar::dispose()
+{
+ maItems.clear();
+ mxMeasureBox.reset();
+ mxSubMenu.reset();
+ mxMainMenu.reset();
+ mxMenuButton.reset();
+ m_xContainer->move(mxContents.get(), mxTempToplevel.get());
+ mxContents.reset();
+ mxTempToplevel.reset();
+ mxAuxBuilder.reset();
+ InterimItemWindow::dispose();
+}
+
+sal_Int32 TabBar::GetDefaultWidth()
+{
+ if (!gDefaultWidth)
+ {
+ std::unique_ptr<weld::Builder> xBuilder(Application::CreateBuilder(nullptr, "sfx/ui/tabbarcontents.ui"));
+ std::unique_ptr<weld::Widget> xContainer(xBuilder->weld_widget("TabBarContents"));
+ gDefaultWidth = xContainer->get_preferred_size().Width();
+ }
+ return gDefaultWidth;
+}
+
+void TabBar::SetDecks(const ResourceManager::DeckContextDescriptorContainer& rDecks)
+{
+ // invisible with LOK, so keep empty to avoid invalidations
+ if (comphelper::LibreOfficeKit::isActive())
+ return;
+
+ // Remove the current buttons.
+ maItems.clear();
+ for (auto const& deck : rDecks)
+ {
+ std::shared_ptr<DeckDescriptor> xDescriptor = pParentSidebarController->GetResourceManager()->GetDeckDescriptor(deck.msId);
+ if (xDescriptor == nullptr)
+ {
+ OSL_ASSERT(xDescriptor!=nullptr);
+ continue;
+ }
+
+ maItems.emplace_back(std::make_unique<Item>(*this));
+ auto& xItem(maItems.back());
+ xItem->msDeckId = xDescriptor->msId;
+ CreateTabItem(*xItem->mxButton, *xDescriptor);
+ xItem->mxButton->connect_clicked(LINK(xItem.get(), TabBar::Item, HandleClick));
+ xItem->maDeckActivationFunctor = maDeckActivationFunctor;
+ xItem->mbIsHidden = !xDescriptor->mbIsEnabled;
+ xItem->mbIsHiddenByDefault = xItem->mbIsHidden; // the default is the state while creating
+
+ xItem->mxButton->set_sensitive(deck.mbIsEnabled);
+ }
+
+ UpdateButtonIcons();
+}
+
+void TabBar::UpdateButtonIcons()
+{
+ for (auto const& item : maItems)
+ {
+ std::shared_ptr<DeckDescriptor> xDeckDescriptor = pParentSidebarController->GetResourceManager()->GetDeckDescriptor(item->msDeckId);
+ if (!xDeckDescriptor)
+ continue;
+ item->mxButton->set_item_image("toggle", GetItemImage(*xDeckDescriptor));
+ }
+}
+
+void TabBar::HighlightDeck(std::u16string_view rsDeckId)
+{
+ for (auto const& item : maItems)
+ item->mxButton->set_item_active("toggle", item->msDeckId == rsDeckId);
+}
+
+void TabBar::RemoveDeckHighlight()
+{
+ for (auto const& item : maItems)
+ item->mxButton->set_item_active("toggle", false);
+}
+
+void TabBar::DataChanged(const DataChangedEvent& rDataChangedEvent)
+{
+ SetBackground(Theme::GetColor(Theme::Color_TabBarBackground));
+ UpdateButtonIcons();
+
+ InterimItemWindow::DataChanged(rDataChangedEvent);
+}
+
+bool TabBar::EventNotify(NotifyEvent& rEvent)
+{
+ NotifyEventType nType = rEvent.GetType();
+ if(NotifyEventType::KEYINPUT == nType)
+ {
+ const vcl::KeyCode& rKeyCode = rEvent.GetKeyEvent()->GetKeyCode();
+ if (!mpAccel)
+ {
+ mpAccel = svt::AcceleratorExecute::createAcceleratorHelper();
+ mpAccel->init(comphelper::getProcessComponentContext(), mxFrame);
+ }
+ const OUString aCommand(mpAccel->findCommand(svt::AcceleratorExecute::st_VCLKey2AWTKey(rKeyCode)));
+ if (".uno:Sidebar" == aCommand ||
+ (rKeyCode.IsMod1() && rKeyCode.IsShift() && rKeyCode.GetCode() == KEY_F10))
+ return InterimItemWindow::EventNotify(rEvent);
+ return true;
+ }
+ else if(NotifyEventType::COMMAND == nType)
+ {
+ const CommandEvent& rCommandEvent = *rEvent.GetCommandEvent();
+ if(rCommandEvent.GetCommand() == CommandEventId::Wheel)
+ {
+ const CommandWheelData* pData = rCommandEvent.GetWheelData();
+ if(!pData->GetModifier() && (pData->GetMode() == CommandWheelMode::SCROLL))
+ {
+ auto pItem = std::find_if(maItems.begin(), maItems.end(),
+ [] (const auto& item) { return item->mxButton->get_item_active("toggle"); });
+ if(pItem == maItems.end())
+ return true;
+ if(pData->GetNotchDelta()<0)
+ {
+ if(pItem+1 == maItems.end())
+ return true;
+ ++pItem;
+ }
+ else
+ {
+ if(pItem == maItems.begin())
+ return true;
+ --pItem;
+ }
+ try
+ {
+ (*pItem)->maDeckActivationFunctor((*pItem)->msDeckId);
+ }
+ catch(const css::uno::Exception&) {};
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+void TabBar::CreateTabItem(weld::Toolbar& rItem, const DeckDescriptor& rDeckDescriptor)
+{
+ rItem.set_accessible_name(rDeckDescriptor.msTitle);
+ rItem.set_accessible_description(rDeckDescriptor.msHelpText);
+ rItem.set_tooltip_text(rDeckDescriptor.msHelpText);
+ const OUString sCommand = ".uno:SidebarDeck." + rDeckDescriptor.msId;
+ OUString sShortcut = vcl::CommandInfoProvider::GetCommandShortcut(sCommand, mxFrame);
+ if (!sShortcut.isEmpty())
+ sShortcut = u" (" + sShortcut + u")";
+ rItem.set_item_tooltip_text("toggle", rDeckDescriptor.msHelpText + sShortcut);
+}
+
+css::uno::Reference<css::graphic::XGraphic> TabBar::GetItemImage(const DeckDescriptor& rDeckDescriptor) const
+{
+ return Tools::GetImage(
+ rDeckDescriptor.msIconURL,
+ rDeckDescriptor.msHighContrastIconURL,
+ mxFrame);
+}
+
+TabBar::Item::Item(TabBar& rTabBar)
+ : mrTabBar(rTabBar)
+ , mxBuilder(Application::CreateBuilder(rTabBar.GetContainer(), "sfx/ui/tabbutton.ui"))
+ , mxButton(mxBuilder->weld_toolbar("button"))
+ , mbIsHidden(false)
+ , mbIsHiddenByDefault(false)
+{
+}
+
+TabBar::Item::~Item()
+{
+ mrTabBar.GetContainer()->move(mxButton.get(), nullptr);
+}
+
+IMPL_LINK_NOARG(TabBar::Item, HandleClick, const OUString&, void)
+{
+ // tdf#143146 copy the functor and arg before calling
+ // GrabFocusToDocument which may destroy this object
+ auto aDeckActivationFunctor = maDeckActivationFunctor;
+ auto sDeckId = msDeckId;
+
+ mrTabBar.GrabFocusToDocument();
+ try
+ {
+ aDeckActivationFunctor(sDeckId);
+ }
+ catch(const css::uno::Exception&)
+ {} // workaround for #i123198#
+}
+
+OUString const & TabBar::GetDeckIdForIndex (const sal_Int32 nIndex) const
+{
+ if (nIndex<0 || o3tl::make_unsigned(nIndex)>=maItems.size())
+ throw RuntimeException();
+ return maItems[nIndex]->msDeckId;
+}
+
+void TabBar::ToggleHideFlag (const sal_Int32 nIndex)
+{
+ if (nIndex<0 || o3tl::make_unsigned(nIndex) >= maItems.size())
+ throw RuntimeException();
+
+ maItems[nIndex]->mbIsHidden = ! maItems[nIndex]->mbIsHidden;
+
+ std::shared_ptr<DeckDescriptor> xDeckDescriptor = pParentSidebarController->GetResourceManager()->GetDeckDescriptor(maItems[nIndex]->msDeckId);
+ if (xDeckDescriptor)
+ {
+ xDeckDescriptor->mbIsEnabled = ! maItems[nIndex]->mbIsHidden;
+
+ Context aContext;
+ aContext.msApplication = pParentSidebarController->GetCurrentContext().msApplication;
+ // leave aContext.msContext on default 'any' ... this func is used only for decks
+ // and we don't have context-sensitive decks anyway
+
+ xDeckDescriptor->maContextList.ToggleVisibilityForContext(
+ aContext, xDeckDescriptor->mbIsEnabled );
+ }
+}
+
+void TabBar::RestoreHideFlags()
+{
+ for (auto & item : maItems)
+ {
+ if (item->mbIsHidden != item->mbIsHiddenByDefault)
+ {
+ item->mbIsHidden = item->mbIsHiddenByDefault;
+ std::shared_ptr<DeckDescriptor> xDeckDescriptor = pParentSidebarController->GetResourceManager()->GetDeckDescriptor(item->msDeckId);
+ if (xDeckDescriptor)
+ xDeckDescriptor->mbIsEnabled = !item->mbIsHidden;
+
+ }
+ }
+}
+
+void TabBar::UpdateFocusManager(FocusManager& rFocusManager)
+{
+ std::vector<weld::Widget*> aButtons;
+ aButtons.reserve(maItems.size()+1);
+ aButtons.push_back(mxMenuButton.get());
+ for (auto const& item : maItems)
+ {
+ aButtons.push_back(item->mxButton.get());
+ }
+ rFocusManager.SetButtons(aButtons);
+}
+
+IMPL_LINK_NOARG(TabBar, OnToolboxClicked, weld::Toggleable&, void)
+{
+ if (!mxMenuButton->get_active())
+ return;
+
+ std::vector<DeckMenuData> aMenuData;
+
+ for (auto const& item : maItems)
+ {
+ std::shared_ptr<DeckDescriptor> xDeckDescriptor = pParentSidebarController->GetResourceManager()->GetDeckDescriptor(item->msDeckId);
+
+ if (!xDeckDescriptor)
+ continue;
+
+ DeckMenuData aData;
+ aData.msDisplayName = xDeckDescriptor->msTitle;
+ aData.mbIsCurrentDeck = item->mxButton->get_item_active("toggle");
+ aData.mbIsActive = !item->mbIsHidden;
+ aData.mbIsEnabled = item->mxButton->get_sensitive();
+ aMenuData.push_back(aData);
+ }
+
+ for (int i = mxMainMenu->n_children() - 1; i >= 0; --i)
+ {
+ OUString sIdent = mxMainMenu->get_id(i);
+ if (sIdent.startsWith("select"))
+ mxMainMenu->remove(sIdent);
+ }
+ for (int i = mxSubMenu->n_children() - 1; i >= 0; --i)
+ {
+ OUString sIdent = mxSubMenu->get_id(i);
+ if (sIdent.indexOf("customize") != -1)
+ mxSubMenu->remove(sIdent);
+ }
+
+ maPopupMenuProvider(*mxMainMenu, *mxSubMenu, aMenuData);
+}
+
+void TabBar::EnableMenuButton(const bool bEnable)
+{
+ mxMenuButton->set_sensitive(bEnable);
+}
+
+FactoryFunction TabBar::GetUITestFactory() const
+{
+ return TabBarUIObject::create;
+}
+
+} // end of namespace sfx2::sidebar
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/sidebar/Theme.cxx b/sfx2/source/sidebar/Theme.cxx
new file mode 100644
index 0000000000..188910120e
--- /dev/null
+++ b/sfx2/source/sidebar/Theme.cxx
@@ -0,0 +1,675 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sfx2/sidebar/Theme.hxx>
+#include <sfx2/app.hxx>
+
+#include <vcl/svapp.hxx>
+#include <vcl/settings.hxx>
+#include <comphelper/diagnose_ex.hxx>
+
+using namespace css;
+using namespace css::uno;
+
+namespace sfx2::sidebar {
+
+Theme& Theme::GetCurrentTheme()
+{
+ OSL_ASSERT(SfxGetpApp());
+ return SfxGetpApp()->GetSidebarTheme();
+}
+
+Theme::Theme()
+ : mbIsHighContrastMode(Application::GetSettings().GetStyleSettings().GetHighContrastMode()),
+ mbIsHighContrastModeSetManually(false)
+{
+ SetupPropertyMaps();
+}
+
+Theme::~Theme()
+{
+}
+
+Color Theme::GetColor (const ThemeItem eItem)
+{
+ const PropertyType eType (GetPropertyType(eItem));
+ OSL_ASSERT(eType==PT_Color);
+ const sal_Int32 nIndex (GetIndex(eItem, eType));
+ const Theme& rTheme (GetCurrentTheme());
+ if (eType == PT_Color)
+ return rTheme.maColors[nIndex];
+ else
+ return COL_WHITE;
+}
+
+sal_Int32 Theme::GetInteger (const ThemeItem eItem)
+{
+ const PropertyType eType (GetPropertyType(eItem));
+ OSL_ASSERT(eType==PT_Integer);
+ const sal_Int32 nIndex (GetIndex(eItem, eType));
+ const Theme& rTheme (GetCurrentTheme());
+ return rTheme.maIntegers[nIndex];
+}
+
+bool Theme::IsHighContrastMode()
+{
+ const Theme& rTheme (GetCurrentTheme());
+ return rTheme.mbIsHighContrastMode;
+}
+
+void Theme::HandleDataChange()
+{
+ Theme& rTheme (GetCurrentTheme());
+
+ if ( ! rTheme.mbIsHighContrastModeSetManually)
+ {
+ // Do not modify mbIsHighContrastMode when it was manually set.
+ GetCurrentTheme().mbIsHighContrastMode = Application::GetSettings().GetStyleSettings().GetHighContrastMode();
+ rTheme.maRawValues[Bool_IsHighContrastModeActive] <<= GetCurrentTheme().mbIsHighContrastMode;
+ }
+
+ GetCurrentTheme().UpdateTheme();
+}
+
+void Theme::InitializeTheme()
+{
+ setPropertyValue(
+ maPropertyIdToNameMap[Bool_UseSystemColors],
+ Any(false));
+}
+
+void Theme::UpdateTheme()
+{
+ try
+ {
+ const StyleSettings& rStyle (Application::GetSettings().GetStyleSettings());
+
+ Color aBaseBackgroundColor (rStyle.GetDialogColor());
+ // UX says this should be a little brighter, but that looks off when compared to the other windows.
+ //aBaseBackgroundColor.IncreaseLuminance(7);
+ Color aSecondColor (aBaseBackgroundColor);
+ aSecondColor.DecreaseLuminance(15);
+
+ setPropertyValue(
+ maPropertyIdToNameMap[Color_DeckBackground],
+ Any(sal_Int32(aBaseBackgroundColor.GetRGBColor())));
+
+ setPropertyValue(
+ maPropertyIdToNameMap[Color_DeckTitleBarBackground],
+ Any(sal_Int32(aBaseBackgroundColor.GetRGBColor())));
+ setPropertyValue(
+ maPropertyIdToNameMap[Int_DeckSeparatorHeight],
+ Any(sal_Int32(1)));
+ setPropertyValue(
+ maPropertyIdToNameMap[Color_PanelBackground],
+ Any(sal_Int32(aBaseBackgroundColor.GetRGBColor())));
+
+ setPropertyValue(
+ maPropertyIdToNameMap[Color_PanelTitleBarBackground],
+ Any(sal_Int32(aSecondColor.GetRGBColor())));
+ setPropertyValue(
+ maPropertyIdToNameMap[Color_TabBarBackground],
+ Any(sal_Int32(aBaseBackgroundColor.GetRGBColor())));
+
+ setPropertyValue(
+ maPropertyIdToNameMap[Color_Highlight],
+ Any(sal_Int32(rStyle.GetHighlightColor().GetRGBColor())));
+ setPropertyValue(
+ maPropertyIdToNameMap[Color_HighlightText],
+ Any(sal_Int32(rStyle.GetHighlightTextColor().GetRGBColor())));
+ }
+ catch(beans::UnknownPropertyException const &)
+ {
+ DBG_UNHANDLED_EXCEPTION("sfx", "unknown property");
+ OSL_ASSERT(false);
+ }
+}
+
+void Theme::disposing(std::unique_lock<std::mutex>&)
+{
+ SolarMutexGuard aGuard;
+
+ ChangeListeners aListeners;
+ aListeners.swap(maChangeListeners);
+
+ const lang::EventObject aEvent (getXWeak());
+
+ for (const auto& rContainer : aListeners)
+ {
+ for (const auto& rxListener : rContainer.second)
+ {
+ try
+ {
+ rxListener->disposing(aEvent);
+ }
+ catch(const Exception&)
+ {
+ }
+ }
+ }
+}
+
+Reference<beans::XPropertySet> Theme::GetPropertySet()
+{
+ if (SfxGetpApp())
+ return Reference<beans::XPropertySet>(&GetCurrentTheme());
+ else
+ return Reference<beans::XPropertySet>();
+}
+
+Reference<beans::XPropertySetInfo> SAL_CALL Theme::getPropertySetInfo()
+{
+ return Reference<beans::XPropertySetInfo>(this);
+}
+
+void SAL_CALL Theme::setPropertyValue (
+ const OUString& rsPropertyName,
+ const css::uno::Any& rValue)
+{
+ SolarMutexGuard aGuard;
+
+ PropertyNameToIdMap::const_iterator iId (maPropertyNameToIdMap.find(rsPropertyName));
+ if (iId == maPropertyNameToIdMap.end())
+ throw beans::UnknownPropertyException(rsPropertyName);
+
+ const PropertyType eType (GetPropertyType(iId->second));
+ if (eType == PT_Invalid)
+ throw beans::UnknownPropertyException(rsPropertyName);
+
+ const ThemeItem eItem (iId->second);
+
+ if (rValue == maRawValues[eItem])
+ {
+ // Value is not different from the one in the property
+ // set => nothing to do.
+ return;
+ }
+
+ const Any aOldValue (maRawValues[eItem]);
+
+ const beans::PropertyChangeEvent aEvent(
+ getXWeak(),
+ rsPropertyName,
+ false,
+ eItem,
+ aOldValue,
+ rValue);
+
+ if (DoVetoableListenersVeto(GetVetoableListeners(AnyItem_, false), aEvent))
+ return;
+ if (DoVetoableListenersVeto(GetVetoableListeners(eItem, false), aEvent))
+ return;
+
+ maRawValues[eItem] = rValue;
+ ProcessNewValue(rValue, eItem, eType);
+
+ BroadcastPropertyChange(GetChangeListeners(AnyItem_, false), aEvent);
+ BroadcastPropertyChange(GetChangeListeners(eItem, false), aEvent);
+}
+
+Any SAL_CALL Theme::getPropertyValue (
+ const OUString& rsPropertyName)
+{
+ SolarMutexGuard aGuard;
+
+ PropertyNameToIdMap::const_iterator iId (maPropertyNameToIdMap.find(rsPropertyName));
+ if (iId == maPropertyNameToIdMap.end())
+ throw beans::UnknownPropertyException(rsPropertyName);
+
+ const PropertyType eType (GetPropertyType(iId->second));
+ if (eType == PT_Invalid)
+ throw beans::UnknownPropertyException(rsPropertyName);
+
+ const ThemeItem eItem (iId->second);
+
+ return maRawValues[eItem];
+}
+
+void SAL_CALL Theme::addPropertyChangeListener(
+ const OUString& rsPropertyName,
+ const css::uno::Reference<css::beans::XPropertyChangeListener>& rxListener)
+{
+ SolarMutexGuard aGuard;
+
+ ThemeItem eItem (AnyItem_);
+ if (rsPropertyName.getLength() > 0)
+ {
+ PropertyNameToIdMap::const_iterator iId (maPropertyNameToIdMap.find(rsPropertyName));
+ if (iId == maPropertyNameToIdMap.end())
+ throw beans::UnknownPropertyException(rsPropertyName);
+
+ const PropertyType eType (GetPropertyType(iId->second));
+ if (eType == PT_Invalid)
+ throw beans::UnknownPropertyException(rsPropertyName);
+
+ eItem = iId->second;
+ }
+ ChangeListenerContainer* pListeners = GetChangeListeners(eItem, true);
+ if (pListeners != nullptr)
+ pListeners->push_back(rxListener);
+}
+
+void SAL_CALL Theme::removePropertyChangeListener(
+ const OUString& rsPropertyName,
+ const css::uno::Reference<css::beans::XPropertyChangeListener>& rxListener)
+{
+ SolarMutexGuard aGuard;
+
+ ThemeItem eItem (AnyItem_);
+ if (rsPropertyName.getLength() > 0)
+ {
+ PropertyNameToIdMap::const_iterator iId (maPropertyNameToIdMap.find(rsPropertyName));
+ if (iId == maPropertyNameToIdMap.end())
+ throw beans::UnknownPropertyException(rsPropertyName);
+
+ const PropertyType eType (GetPropertyType(iId->second));
+ if (eType == PT_Invalid)
+ throw beans::UnknownPropertyException(rsPropertyName);
+
+ eItem = iId->second;
+ }
+ ChangeListenerContainer* pContainer = GetChangeListeners(eItem, false);
+ if (pContainer != nullptr)
+ {
+ ChangeListenerContainer::iterator iListener (::std::find(pContainer->begin(), pContainer->end(), rxListener));
+ if (iListener != pContainer->end())
+ {
+ pContainer->erase(iListener);
+
+ // Remove the listener container when empty.
+ if (pContainer->empty())
+ maChangeListeners.erase(eItem);
+ }
+ }
+}
+
+void SAL_CALL Theme::addVetoableChangeListener(
+ const OUString& rsPropertyName,
+ const css::uno::Reference<css::beans::XVetoableChangeListener>& rxListener)
+{
+ SolarMutexGuard aGuard;
+
+ ThemeItem eItem (AnyItem_);
+ if (rsPropertyName.getLength() > 0)
+ {
+ PropertyNameToIdMap::const_iterator iId (maPropertyNameToIdMap.find(rsPropertyName));
+ if (iId == maPropertyNameToIdMap.end())
+ throw beans::UnknownPropertyException(rsPropertyName);
+
+ const PropertyType eType (GetPropertyType(iId->second));
+ if (eType == PT_Invalid)
+ throw beans::UnknownPropertyException(rsPropertyName);
+
+ eItem = iId->second;
+ }
+ VetoableListenerContainer* pListeners = GetVetoableListeners(eItem, true);
+ if (pListeners != nullptr)
+ pListeners->push_back(rxListener);
+}
+
+void SAL_CALL Theme::removeVetoableChangeListener(
+ const OUString& rsPropertyName,
+ const css::uno::Reference<css::beans::XVetoableChangeListener>& rxListener)
+{
+ SolarMutexGuard aGuard;
+
+ ThemeItem eItem (AnyItem_);
+ if (rsPropertyName.getLength() > 0)
+ {
+ PropertyNameToIdMap::const_iterator iId (maPropertyNameToIdMap.find(rsPropertyName));
+ if (iId == maPropertyNameToIdMap.end())
+ throw beans::UnknownPropertyException(rsPropertyName);
+
+ const PropertyType eType (GetPropertyType(iId->second));
+ if (eType == PT_Invalid)
+ throw beans::UnknownPropertyException(rsPropertyName);
+
+ eItem = iId->second;
+ }
+ VetoableListenerContainer* pContainer = GetVetoableListeners(eItem, false);
+ if (pContainer != nullptr)
+ {
+ VetoableListenerContainer::iterator iListener (::std::find(pContainer->begin(), pContainer->end(), rxListener));
+ if (iListener != pContainer->end())
+ {
+ pContainer->erase(iListener);
+ // Remove container when empty.
+ if (pContainer->empty())
+ maVetoableListeners.erase(eItem);
+ }
+ }
+}
+
+css::uno::Sequence<css::beans::Property> SAL_CALL Theme::getProperties()
+{
+ SolarMutexGuard aGuard;
+
+ ::std::vector<beans::Property> aProperties;
+
+ sal_Int32 const nEnd(End_);
+ for (sal_Int32 nItem(Begin_); nItem!=nEnd; ++nItem)
+ {
+ const ThemeItem eItem (static_cast<ThemeItem>(nItem));
+ const PropertyType eType (GetPropertyType(eItem));
+ if (eType == PT_Invalid)
+ continue;
+
+ const beans::Property aProperty(
+ maPropertyIdToNameMap[eItem],
+ eItem,
+ GetCppuType(eType),
+ 0);
+ aProperties.push_back(aProperty);
+ }
+
+ return css::uno::Sequence<css::beans::Property>(
+ aProperties.data(),
+ aProperties.size());
+}
+
+beans::Property SAL_CALL Theme::getPropertyByName (const OUString& rsPropertyName)
+{
+ SolarMutexGuard aGuard;
+
+ PropertyNameToIdMap::const_iterator iId (maPropertyNameToIdMap.find(rsPropertyName));
+ if (iId == maPropertyNameToIdMap.end())
+ throw beans::UnknownPropertyException(rsPropertyName);
+
+ const PropertyType eType (GetPropertyType(iId->second));
+ if (eType == PT_Invalid)
+ throw beans::UnknownPropertyException(rsPropertyName);
+
+ const ThemeItem eItem (iId->second);
+
+ return beans::Property(
+ rsPropertyName,
+ eItem,
+ GetCppuType(eType),
+ 0);
+}
+
+sal_Bool SAL_CALL Theme::hasPropertyByName (const OUString& rsPropertyName)
+{
+ SolarMutexGuard aGuard;
+
+ PropertyNameToIdMap::const_iterator iId (maPropertyNameToIdMap.find(rsPropertyName));
+ if (iId == maPropertyNameToIdMap.end())
+ return false;
+
+ const PropertyType eType (GetPropertyType(iId->second));
+ if (eType == PT_Invalid)
+ return false;
+
+ return true;
+}
+
+void Theme::SetupPropertyMaps()
+{
+ maPropertyIdToNameMap.resize(Post_Bool_);
+ maColors.resize(Color_Int_ - Pre_Color_ - 1);
+ maIntegers.resize(Int_Bool_ - Color_Int_ - 1);
+ maBooleans.resize(Post_Bool_ - Int_Bool_ - 1);
+
+ maPropertyNameToIdMap["Color_Highlight"]=Color_Highlight;
+ maPropertyIdToNameMap[Color_Highlight]="Color_Highlight";
+
+ maPropertyNameToIdMap["Color_HighlightText"]=Color_HighlightText;
+ maPropertyIdToNameMap[Color_HighlightText]="Color_HighlightText";
+
+
+ maPropertyNameToIdMap["Color_DeckBackground"]=Color_DeckBackground;
+ maPropertyIdToNameMap[Color_DeckBackground]="Color_DeckBackground";
+
+ maPropertyNameToIdMap["Color_DeckTitleBarBackground"]=Color_DeckTitleBarBackground;
+ maPropertyIdToNameMap[Color_DeckTitleBarBackground]="Color_DeckTitleBarBackground";
+
+ maPropertyNameToIdMap["Color_PanelBackground"]=Color_PanelBackground;
+ maPropertyIdToNameMap[Color_PanelBackground]="Color_PanelBackground";
+
+ maPropertyNameToIdMap["Color_PanelTitleBarBackground"]=Color_PanelTitleBarBackground;
+ maPropertyIdToNameMap[Color_PanelTitleBarBackground]="Color_PanelTitleBarBackground";
+
+ maPropertyNameToIdMap["Color_TabBarBackground"]=Color_TabBarBackground;
+ maPropertyIdToNameMap[Color_TabBarBackground]="Color_TabBarBackground";
+
+
+ maPropertyNameToIdMap["Int_DeckBorderSize"]=Int_DeckBorderSize;
+ maPropertyIdToNameMap[Int_DeckBorderSize]="Int_DeckBorderSize";
+
+ maPropertyNameToIdMap["Int_DeckSeparatorHeight"]=Int_DeckSeparatorHeight;
+ maPropertyIdToNameMap[Int_DeckSeparatorHeight]="Int_DeckSeparatorHeight";
+
+ maPropertyNameToIdMap["Int_DeckLeftPadding"]=Int_DeckLeftPadding;
+ maPropertyIdToNameMap[Int_DeckLeftPadding]="Int_DeckLeftPadding";
+
+ maPropertyNameToIdMap["Int_DeckTopPadding"]=Int_DeckTopPadding;
+ maPropertyIdToNameMap[Int_DeckTopPadding]="Int_DeckTopPadding";
+
+ maPropertyNameToIdMap["Int_DeckRightPadding"]=Int_DeckRightPadding;
+ maPropertyIdToNameMap[Int_DeckRightPadding]="Int_DeckRightPadding";
+
+ maPropertyNameToIdMap["Int_DeckBottomPadding"]=Int_DeckBottomPadding;
+ maPropertyIdToNameMap[Int_DeckBottomPadding]="Int_DeckBottomPadding";
+
+
+ maPropertyNameToIdMap["Bool_UseSystemColors"]=Bool_UseSystemColors;
+ maPropertyIdToNameMap[Bool_UseSystemColors]="Bool_UseSystemColors";
+
+ maPropertyNameToIdMap["Bool_IsHighContrastModeActive"]=Bool_IsHighContrastModeActive;
+ maPropertyIdToNameMap[Bool_IsHighContrastModeActive]="Bool_IsHighContrastModeActive";
+
+ maRawValues.resize(maPropertyIdToNameMap.size());
+}
+
+Theme::PropertyType Theme::GetPropertyType (const ThemeItem eItem)
+{
+ switch(eItem)
+ {
+ case Color_Highlight:
+ case Color_HighlightText:
+ case Color_DeckBackground:
+ case Color_DeckTitleBarBackground:
+ case Color_PanelBackground:
+ case Color_PanelTitleBarBackground:
+ case Color_TabBarBackground:
+ return PT_Color;
+
+ case Int_DeckBorderSize:
+ case Int_DeckSeparatorHeight:
+ case Int_DeckLeftPadding:
+ case Int_DeckTopPadding:
+ case Int_DeckRightPadding:
+ case Int_DeckBottomPadding:
+ return PT_Integer;
+
+ case Bool_UseSystemColors:
+ case Bool_IsHighContrastModeActive:
+ return PT_Boolean;
+
+ default:
+ return PT_Invalid;
+ }
+}
+
+css::uno::Type const & Theme::GetCppuType (const PropertyType eType)
+{
+ switch(eType)
+ {
+ case PT_Color:
+ return cppu::UnoType<sal_uInt32>::get();
+
+ case PT_Integer:
+ return cppu::UnoType<sal_Int32>::get();
+
+ case PT_Boolean:
+ return cppu::UnoType<sal_Bool>::get();
+
+ case PT_Invalid:
+ default:
+ return cppu::UnoType<void>::get();
+ }
+}
+
+sal_Int32 Theme::GetIndex (const ThemeItem eItem, const PropertyType eType)
+{
+ switch(eType)
+ {
+ case PT_Color:
+ return eItem - Pre_Color_-1;
+ case PT_Integer:
+ return eItem - Color_Int_-1;
+ case PT_Boolean:
+ return eItem - Int_Bool_-1;
+ default:
+ OSL_ASSERT(false);
+ return 0;
+ }
+}
+
+Theme::VetoableListenerContainer* Theme::GetVetoableListeners (
+ const ThemeItem eItem,
+ const bool bCreate)
+{
+ VetoableListeners::iterator iContainer (maVetoableListeners.find(eItem));
+ if (iContainer != maVetoableListeners.end())
+ return &iContainer->second;
+ else if (bCreate)
+ {
+ maVetoableListeners[eItem] = VetoableListenerContainer();
+ return &maVetoableListeners[eItem];
+ }
+ else
+ return nullptr;
+}
+
+Theme::ChangeListenerContainer* Theme::GetChangeListeners (
+ const ThemeItem eItem,
+ const bool bCreate)
+{
+ ChangeListeners::iterator iContainer (maChangeListeners.find(eItem));
+ if (iContainer != maChangeListeners.end())
+ return &iContainer->second;
+ else if (bCreate)
+ {
+ maChangeListeners[eItem] = ChangeListenerContainer();
+ return &maChangeListeners[eItem];
+ }
+ else
+ return nullptr;
+}
+
+bool Theme::DoVetoableListenersVeto (
+ const VetoableListenerContainer* pListeners,
+ const beans::PropertyChangeEvent& rEvent)
+{
+ if (pListeners == nullptr)
+ return false;
+
+ VetoableListenerContainer aListeners (*pListeners);
+ try
+ {
+ for (const auto& rxListener : aListeners)
+ {
+ rxListener->vetoableChange(rEvent);
+ }
+ }
+ catch(const beans::PropertyVetoException&)
+ {
+ return true;
+ }
+ catch(const Exception&)
+ {
+ // Ignore any other errors (such as disposed listeners).
+ }
+ return false;
+}
+
+void Theme::BroadcastPropertyChange (
+ const ChangeListenerContainer* pListeners,
+ const beans::PropertyChangeEvent& rEvent)
+{
+ if (pListeners == nullptr)
+ return;
+
+ const ChangeListenerContainer aListeners (*pListeners);
+ try
+ {
+ for (const auto& rxListener : aListeners)
+ {
+ rxListener->propertyChange(rEvent);
+ }
+ }
+ catch(const Exception&)
+ {
+ // Ignore any errors (such as disposed listeners).
+ }
+}
+
+void Theme::ProcessNewValue (
+ const Any& rValue,
+ const ThemeItem eItem,
+ const PropertyType eType)
+{
+ const sal_Int32 nIndex (GetIndex (eItem, eType));
+ switch (eType)
+ {
+ case PT_Color:
+ {
+ Color nColorValue;
+ if (rValue >>= nColorValue)
+ maColors[nIndex] = nColorValue;
+ break;
+ }
+ case PT_Integer:
+ {
+ sal_Int32 nValue (0);
+ if (rValue >>= nValue)
+ {
+ maIntegers[nIndex] = nValue;
+ }
+ break;
+ }
+ case PT_Boolean:
+ {
+ bool bValue (false);
+ if (rValue >>= bValue)
+ {
+ maBooleans[nIndex] = bValue;
+ if (eItem == Bool_IsHighContrastModeActive)
+ {
+ mbIsHighContrastModeSetManually = true;
+ mbIsHighContrastMode = maBooleans[nIndex];
+ HandleDataChange();
+ }
+ else if (eItem == Bool_UseSystemColors)
+ {
+ HandleDataChange();
+ }
+ }
+ break;
+ }
+ case PT_Invalid:
+ OSL_ASSERT(eType != PT_Invalid);
+ throw RuntimeException();
+ }
+}
+
+} // end of namespace sfx2::sidebar
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/sidebar/TitleBar.cxx b/sfx2/source/sidebar/TitleBar.cxx
new file mode 100644
index 0000000000..5bcf712dcf
--- /dev/null
+++ b/sfx2/source/sidebar/TitleBar.cxx
@@ -0,0 +1,91 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sidebar/TitleBar.hxx>
+#include <vcl/help.hxx>
+#include <vcl/svapp.hxx>
+
+namespace sfx2::sidebar {
+
+TitleBar::TitleBar(weld::Builder& rBuilder, Theme::ThemeItem eThemeItem)
+ : mrBuilder(rBuilder)
+ , mxTitlebar(rBuilder.weld_box("titlebar"))
+ , mxAddonImage(rBuilder.weld_image("addonimage"))
+ , mxToolBox(rBuilder.weld_toolbar("toolbar"))
+ , meThemeItem(eThemeItem)
+ , msToolBoxRId("")
+{
+ SetBackground();
+
+ mxToolBox->connect_clicked(LINK(this, TitleBar, SelectionHandler));
+}
+
+void TitleBar::SetBackground()
+{
+ Color aColor(Theme::GetColor(meThemeItem));
+ mxTitlebar->set_background(aColor);
+ mxToolBox->set_background(aColor);
+}
+
+void TitleBar::DataChanged()
+{
+ SetBackground();
+}
+
+TitleBar::~TitleBar()
+{
+}
+
+Size TitleBar::get_preferred_size() const
+{
+ return mxTitlebar->get_preferred_size();
+}
+
+void TitleBar::Show(bool bShow)
+{
+ mxTitlebar->set_visible(bShow);
+}
+
+bool TitleBar::GetVisible() const
+{
+ return mxTitlebar->get_visible();
+}
+
+void TitleBar::SetIcon(const css::uno::Reference<css::graphic::XGraphic>& rIcon)
+{
+ mxAddonImage->set_image(rIcon);
+ mxAddonImage->set_visible(rIcon.is());
+}
+
+void TitleBar::ShowHelp(const OUString& rHelpId)
+{
+ Help* pHelp = Application::GetHelp();
+ if (pHelp)
+ pHelp->Start(rHelpId);
+}
+
+IMPL_LINK(TitleBar, SelectionHandler, const OUString&, rId, void)
+{
+ msToolBoxRId = rId;
+ HandleToolBoxItemClick();
+}
+
+} // end of namespace sfx2::sidebar
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/sidebar/Tools.cxx b/sfx2/source/sidebar/Tools.cxx
new file mode 100644
index 0000000000..727e85a4fa
--- /dev/null
+++ b/sfx2/source/sidebar/Tools.cxx
@@ -0,0 +1,113 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sidebar/Tools.hxx>
+
+#include <sfx2/sidebar/Theme.hxx>
+
+#include <comphelper/namedvaluecollection.hxx>
+#include <comphelper/processfactory.hxx>
+#include <vcl/commandinfoprovider.hxx>
+
+#include <com/sun/star/frame/XDispatchProvider.hpp>
+#include <com/sun/star/frame/XFrame.hpp>
+#include <com/sun/star/util/URLTransformer.hpp>
+#include <com/sun/star/frame/ModuleManager.hpp>
+#include <com/sun/star/graphic/GraphicProvider.hpp>
+
+using namespace css;
+using namespace css::uno;
+
+namespace sfx2::sidebar {
+
+css::uno::Reference<css::graphic::XGraphic> Tools::GetImage(
+ const OUString& rsImageURL,
+ const OUString& rsHighContrastImageURL,
+ const Reference<frame::XFrame>& rxFrame)
+{
+ if (Theme::IsHighContrastMode() && !rsHighContrastImageURL.isEmpty())
+ return GetImage(rsHighContrastImageURL, rxFrame);
+ else
+ return GetImage(rsImageURL, rxFrame);
+}
+
+css::uno::Reference<css::graphic::XGraphic> Tools::GetImage(
+ const OUString& rsURL,
+ const Reference<frame::XFrame>& rxFrame)
+{
+ if (rsURL.getLength() > 0)
+ {
+ if (rsURL.startsWith(".uno:"))
+ return vcl::CommandInfoProvider::GetXGraphicForCommand(rsURL, rxFrame);
+
+ else
+ {
+ Reference<uno::XComponentContext> xContext(::comphelper::getProcessComponentContext());
+ Reference<graphic::XGraphicProvider> xProvider(graphic::GraphicProvider::create(xContext));
+ ::comphelper::NamedValueCollection aMediaProperties;
+ aMediaProperties.put("URL", rsURL);
+ return xProvider->queryGraphic(aMediaProperties.getPropertyValues());
+ }
+ }
+ return nullptr;
+}
+
+util::URL Tools::GetURL (const OUString& rsCommand)
+{
+ util::URL aURL;
+ aURL.Complete = rsCommand;
+
+ const Reference<XComponentContext> xComponentContext (::comphelper::getProcessComponentContext());
+ const Reference<util::XURLTransformer> xParser = util::URLTransformer::create( xComponentContext );
+ xParser->parseStrict(aURL);
+
+ return aURL;
+}
+
+Reference<frame::XDispatch> Tools::GetDispatch (
+ const css::uno::Reference<css::frame::XFrame>& rxFrame,
+ const util::URL& rURL)
+{
+ Reference<frame::XDispatchProvider> xProvider (rxFrame, UNO_QUERY_THROW);
+ Reference<frame::XDispatch> xDispatch (xProvider->queryDispatch(rURL, OUString(), 0));
+ return xDispatch;
+}
+
+OUString Tools::GetModuleName (
+ const css::uno::Reference<css::frame::XController>& rxController)
+{
+ if (!rxController.is())
+ return OUString();
+
+ try
+ {
+ const Reference<XComponentContext> xComponentContext (::comphelper::getProcessComponentContext());
+ const Reference<frame::XModuleManager> xModuleManager = frame::ModuleManager::create( xComponentContext );
+ return xModuleManager->identify(rxController);
+ }
+ catch (const Exception&)
+ {
+ // Ignored.
+ }
+ return OUString();
+}
+
+} // end of namespace sfx2::sidebar
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/sidebar/UnoDeck.cxx b/sfx2/source/sidebar/UnoDeck.cxx
new file mode 100644
index 0000000000..a2c45b2770
--- /dev/null
+++ b/sfx2/source/sidebar/UnoDeck.cxx
@@ -0,0 +1,280 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ */
+
+#include <sidebar/UnoDeck.hxx>
+
+#include <sidebar/UnoPanels.hxx>
+
+#include <sfx2/sidebar/ResourceManager.hxx>
+#include <sfx2/sidebar/SidebarController.hxx>
+#include <sidebar/DeckTitleBar.hxx>
+#include <sfx2/sidebar/Deck.hxx>
+#include <sidebar/DeckDescriptor.hxx>
+
+#include <utility>
+#include <vcl/svapp.hxx>
+
+using namespace css;
+using namespace ::sfx2::sidebar;
+
+SfxUnoDeck::SfxUnoDeck(uno::Reference<frame::XFrame> _xFrame, OUString deckId):
+xFrame(std::move(_xFrame)),
+mDeckId(std::move(deckId))
+{
+
+}
+SidebarController* SfxUnoDeck::getSidebarController()
+{
+ return SidebarController::GetSidebarControllerForFrame(xFrame);
+}
+
+OUString SAL_CALL SfxUnoDeck::getId()
+{
+ return mDeckId;
+}
+
+OUString SAL_CALL SfxUnoDeck::getTitle()
+{
+ SolarMutexGuard aGuard;
+
+ SidebarController* pSidebarController = getSidebarController();
+ VclPtr<Deck> pDeck = pSidebarController->GetResourceManager()->GetDeckDescriptor(mDeckId)->mpDeck;
+
+ if (!pDeck)
+ {
+ pSidebarController->CreateDeck(mDeckId);
+ pDeck = pSidebarController->GetResourceManager()->GetDeckDescriptor(mDeckId)->mpDeck;
+ }
+
+ DeckTitleBar* pTitleBar = pDeck->GetTitleBar();
+ return pTitleBar->GetTitle();
+}
+
+void SAL_CALL SfxUnoDeck::setTitle( const OUString& newTitle )
+{
+ SolarMutexGuard aGuard;
+
+ SidebarController* pSidebarController = getSidebarController();
+ pSidebarController->CreateDeck(mDeckId);
+
+ std::shared_ptr<DeckDescriptor> xDeckDescriptor = pSidebarController->GetResourceManager()->GetDeckDescriptor(mDeckId);
+
+ if (xDeckDescriptor)
+ {
+ Deck* pDeck = xDeckDescriptor->mpDeck;
+ DeckTitleBar* pTitleBar = pDeck->GetTitleBar();
+ pTitleBar->SetTitle(newTitle);
+
+ xDeckDescriptor->msTitle = newTitle;
+ xDeckDescriptor->msHelpText = newTitle;
+
+ pSidebarController->notifyDeckTitle(mDeckId);
+ }
+}
+
+sal_Bool SAL_CALL SfxUnoDeck::isActive()
+{
+ SolarMutexGuard aGuard;
+
+ SidebarController* pSidebarController = getSidebarController();
+ return pSidebarController->IsDeckVisible(mDeckId);
+}
+
+
+void SAL_CALL SfxUnoDeck::activate( const sal_Bool bActivate )
+{
+ SolarMutexGuard aGuard;
+
+ SidebarController* pSidebarController = getSidebarController();
+
+ // tdf#138160: OpenThenToggleDeck takes care of minimal width
+ if (bActivate)
+ pSidebarController->OpenThenToggleDeck(mDeckId);
+ else
+ {
+ pSidebarController->SwitchToDefaultDeck();
+ // update the sidebar
+ pSidebarController->NotifyResize();
+ }
+
+}
+
+uno::Reference<ui::XPanels> SAL_CALL SfxUnoDeck::getPanels()
+{
+ SolarMutexGuard aGuard;
+
+ uno::Reference<ui::XPanels> panels = new SfxUnoPanels(xFrame, mDeckId);
+ return panels;
+}
+
+sal_Int32 SAL_CALL SfxUnoDeck::getOrderIndex()
+{
+ SolarMutexGuard aGuard;
+ SidebarController* pSidebarController = getSidebarController();
+
+ sal_Int32 index = pSidebarController->GetResourceManager()->GetDeckDescriptor(mDeckId)->mnOrderIndex;
+ return index;
+}
+
+void SAL_CALL SfxUnoDeck::setOrderIndex( const sal_Int32 newOrderIndex )
+{
+ SolarMutexGuard aGuard;
+ SidebarController* pSidebarController = getSidebarController();
+
+ std::shared_ptr<DeckDescriptor> xDeckDescriptor = pSidebarController->GetResourceManager()->GetDeckDescriptor(mDeckId);
+
+ if (xDeckDescriptor)
+ {
+ xDeckDescriptor->mnOrderIndex = newOrderIndex;
+ // update the sidebar
+ pSidebarController->NotifyResize();
+ }
+}
+
+void SAL_CALL SfxUnoDeck::moveFirst()
+{
+ SolarMutexGuard aGuard;
+ SidebarController* pSidebarController = getSidebarController();
+
+ ResourceManager::DeckContextDescriptorContainer aDecks = pSidebarController->GetMatchingDecks();
+
+ sal_Int32 minIndex = GetMinOrderIndex(aDecks);
+ sal_Int32 curOrderIndex = getOrderIndex();
+
+ if (curOrderIndex != minIndex) // is deck already in place ?
+ {
+ minIndex -= 1;
+ std::shared_ptr<DeckDescriptor> xDeckDescriptor = pSidebarController->GetResourceManager()->GetDeckDescriptor(mDeckId);
+ if (xDeckDescriptor)
+ {
+ xDeckDescriptor->mnOrderIndex = minIndex;
+ // update the sidebar
+ pSidebarController->NotifyResize();
+ }
+ }
+}
+
+void SAL_CALL SfxUnoDeck::moveLast()
+{
+ SolarMutexGuard aGuard;
+ SidebarController* pSidebarController = getSidebarController();
+
+ ResourceManager::DeckContextDescriptorContainer aDecks = pSidebarController->GetMatchingDecks();
+
+ sal_Int32 maxIndex = GetMaxOrderIndex(aDecks);
+ sal_Int32 curOrderIndex = getOrderIndex();
+
+ if (curOrderIndex != maxIndex) // is deck already in place ?
+ {
+ maxIndex += 1;
+ std::shared_ptr<DeckDescriptor> xDeckDescriptor = pSidebarController->GetResourceManager()->GetDeckDescriptor(mDeckId);
+ if (xDeckDescriptor)
+ {
+ xDeckDescriptor->mnOrderIndex = maxIndex;
+ // update the sidebar
+ pSidebarController->NotifyResize();
+ }
+ }
+}
+
+void SAL_CALL SfxUnoDeck::moveUp()
+{
+ SolarMutexGuard aGuard;
+ SidebarController* pSidebarController = getSidebarController();
+
+ // Search for previous deck OrderIndex
+ ResourceManager::DeckContextDescriptorContainer aDecks = pSidebarController->GetMatchingDecks();
+
+ sal_Int32 curOrderIndex = getOrderIndex();
+ sal_Int32 previousIndex = GetMinOrderIndex(aDecks);
+
+ for (auto const& deck : aDecks)
+ {
+ sal_Int32 index = pSidebarController->GetResourceManager()->GetDeckDescriptor(deck.msId)->mnOrderIndex;
+ if( index < curOrderIndex && index > previousIndex)
+ previousIndex = index;
+ }
+
+ if (curOrderIndex != previousIndex) // is deck already in place ?
+ {
+ previousIndex -= 1;
+ std::shared_ptr<DeckDescriptor> xDeckDescriptor = pSidebarController->GetResourceManager()->GetDeckDescriptor(mDeckId);
+ if (xDeckDescriptor)
+ {
+ xDeckDescriptor->mnOrderIndex = previousIndex;
+ // update the sidebar
+ pSidebarController->NotifyResize();
+ }
+ }
+}
+
+void SAL_CALL SfxUnoDeck::moveDown()
+{
+ SolarMutexGuard aGuard;
+ SidebarController* pSidebarController = getSidebarController();
+
+ ResourceManager::DeckContextDescriptorContainer aDecks = pSidebarController->GetMatchingDecks();
+
+ // Search for next deck OrderIndex
+ sal_Int32 curOrderIndex = getOrderIndex();
+ sal_Int32 nextIndex = GetMaxOrderIndex(aDecks);
+
+ for (auto const& deck : aDecks)
+ {
+ sal_Int32 index = pSidebarController->GetResourceManager()->GetDeckDescriptor(deck.msId)->mnOrderIndex;
+ if( index > curOrderIndex && index < nextIndex)
+ nextIndex = index;
+ }
+
+ if (curOrderIndex != nextIndex) // is deck already in place ?
+ {
+ nextIndex += 1;
+ std::shared_ptr<DeckDescriptor> xDeckDescriptor = pSidebarController->GetResourceManager()->GetDeckDescriptor(mDeckId);
+ if (xDeckDescriptor)
+ {
+ xDeckDescriptor->mnOrderIndex = nextIndex;
+ // update the sidebar
+ pSidebarController->NotifyResize();
+ }
+ }
+}
+
+sal_Int32 SfxUnoDeck::GetMinOrderIndex(const ResourceManager::DeckContextDescriptorContainer& rDecks)
+{
+ SidebarController* pSidebarController = getSidebarController();
+
+ ResourceManager::DeckContextDescriptorContainer::const_iterator iDeck = rDecks.begin();
+ sal_Int32 minIndex = pSidebarController->GetResourceManager()->GetDeckDescriptor(iDeck->msId)->mnOrderIndex;
+
+ for (auto const& deck : rDecks)
+ {
+ sal_Int32 index = pSidebarController->GetResourceManager()->GetDeckDescriptor(deck.msId)->mnOrderIndex;
+ if(minIndex > index)
+ minIndex = index;
+ }
+ return minIndex;
+}
+
+sal_Int32 SfxUnoDeck::GetMaxOrderIndex(const ResourceManager::DeckContextDescriptorContainer& rDecks)
+{
+ SidebarController* pSidebarController = getSidebarController();
+
+ sal_Int32 maxIndex = pSidebarController->GetResourceManager()->GetDeckDescriptor(rDecks.begin()->msId)->mnOrderIndex;
+
+ for (auto const& deck : rDecks)
+ {
+ sal_Int32 index = pSidebarController->GetResourceManager()->GetDeckDescriptor(deck.msId)->mnOrderIndex;
+ if(maxIndex < index)
+ maxIndex = index;
+ }
+ return maxIndex;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/sidebar/UnoDecks.cxx b/sfx2/source/sidebar/UnoDecks.cxx
new file mode 100644
index 0000000000..6ae5e69485
--- /dev/null
+++ b/sfx2/source/sidebar/UnoDecks.cxx
@@ -0,0 +1,144 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ */
+
+#include <sal/config.h>
+
+#include <com/sun/star/lang/IndexOutOfBoundsException.hpp>
+#include <sidebar/UnoDecks.hxx>
+#include <sidebar/UnoDeck.hxx>
+
+#include <sfx2/sidebar/ResourceManager.hxx>
+#include <sfx2/sidebar/SidebarController.hxx>
+
+#include <utility>
+#include <vcl/svapp.hxx>
+
+#include <algorithm>
+
+using namespace css;
+using namespace ::sfx2::sidebar;
+
+SfxUnoDecks::SfxUnoDecks(uno::Reference<frame::XFrame> _xFrame):
+xFrame(std::move(_xFrame))
+{
+}
+
+SidebarController* SfxUnoDecks::getSidebarController()
+{
+ return SidebarController::GetSidebarControllerForFrame(xFrame);
+}
+
+// XNameAccess
+
+uno::Any SAL_CALL SfxUnoDecks::getByName( const OUString& aName )
+{
+ SolarMutexGuard aGuard;
+
+ if (!hasByName(aName))
+ throw container::NoSuchElementException();
+
+ uno::Reference<ui::XDeck> xDeck = new SfxUnoDeck(xFrame, aName);
+ return uno::Any(xDeck);
+}
+
+
+uno::Sequence< OUString > SAL_CALL SfxUnoDecks::getElementNames()
+{
+ SolarMutexGuard aGuard;
+
+ SidebarController* pSidebarController = getSidebarController();
+
+ ResourceManager::DeckContextDescriptorContainer aDecks;
+ css::uno::Sequence< OUString > deckList(aDecks.size());
+
+ if (pSidebarController)
+ {
+ pSidebarController->GetResourceManager()->GetMatchingDecks (
+ aDecks,
+ pSidebarController->GetCurrentContext(),
+ pSidebarController->IsDocumentReadOnly(),
+ xFrame->getController());
+
+ deckList.realloc(aDecks.size());
+ std::transform(aDecks.begin(), aDecks.end(), deckList.getArray(),
+ [](const auto& rDeck) { return rDeck.msId; });
+ }
+
+ return deckList;
+
+}
+
+sal_Bool SAL_CALL SfxUnoDecks::hasByName( const OUString& aName )
+{
+ SolarMutexGuard aGuard;
+
+ SidebarController* pSidebarController = getSidebarController();
+
+ bool bFound = false;
+
+ if (pSidebarController)
+ {
+ ResourceManager::DeckContextDescriptorContainer aDecks;
+
+ pSidebarController->GetResourceManager()->GetMatchingDecks (
+ aDecks,
+ pSidebarController->GetCurrentContext(),
+ pSidebarController->IsDocumentReadOnly(),
+ xFrame->getController());
+
+ bFound = std::any_of(aDecks.begin(), aDecks.end(),
+ [&aName](const ResourceManager::DeckContextDescriptor& rDeck) { return rDeck.msId == aName; });
+ }
+
+ return bFound;
+
+}
+
+// XIndexAccess
+
+sal_Int32 SAL_CALL SfxUnoDecks::getCount()
+{
+ SolarMutexGuard aGuard;
+
+ uno::Sequence< OUString > decks = getElementNames();
+ return decks.getLength();
+}
+
+uno::Any SAL_CALL SfxUnoDecks::getByIndex( sal_Int32 Index )
+{
+ SolarMutexGuard aGuard;
+ uno::Any aRet;
+
+ uno::Sequence< OUString > decks = getElementNames();
+
+ if (Index > decks.getLength()-1 || Index < 0)
+ throw lang::IndexOutOfBoundsException();
+
+ uno::Reference<ui::XDeck> xDeck = new SfxUnoDeck(xFrame, decks[Index]);
+ aRet <<= xDeck;
+ return aRet;
+
+}
+
+// XElementAccess
+uno::Type SAL_CALL SfxUnoDecks::getElementType()
+{
+ return uno::Type();
+}
+
+sal_Bool SAL_CALL SfxUnoDecks::hasElements()
+{
+ SolarMutexGuard aGuard;
+
+ uno::Sequence< OUString > decks = getElementNames();
+ return decks.hasElements();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/sidebar/UnoPanel.cxx b/sfx2/source/sidebar/UnoPanel.cxx
new file mode 100644
index 0000000000..275672f04e
--- /dev/null
+++ b/sfx2/source/sidebar/UnoPanel.cxx
@@ -0,0 +1,296 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ */
+
+#include <sidebar/UnoPanel.hxx>
+
+#include <sfx2/sidebar/ResourceManager.hxx>
+#include <sfx2/sidebar/SidebarController.hxx>
+
+#include <sidebar/PanelDescriptor.hxx>
+#include <sidebar/PanelTitleBar.hxx>
+#include <sfx2/sidebar/Panel.hxx>
+#include <sfx2/sidebar/Deck.hxx>
+#include <sidebar/DeckDescriptor.hxx>
+
+#include <utility>
+#include <vcl/svapp.hxx>
+
+using namespace css;
+using namespace ::sfx2::sidebar;
+
+SfxUnoPanel::SfxUnoPanel(uno::Reference<frame::XFrame> _xFrame, OUString panelId, OUString deckId):
+xFrame(std::move(_xFrame)),
+mPanelId(std::move(panelId)),
+mDeckId(std::move(deckId))
+{
+ SidebarController* pSidebarController = getSidebarController();
+
+ pSidebarController->CreateDeck(mDeckId); // creates deck object is not already
+ mpDeck = pSidebarController->GetResourceManager()->GetDeckDescriptor(mDeckId)->mpDeck;
+ mxPanel = mpDeck->GetPanel(mPanelId);
+}
+
+SidebarController* SfxUnoPanel::getSidebarController()
+{
+ return SidebarController::GetSidebarControllerForFrame(xFrame);
+}
+
+OUString SAL_CALL SfxUnoPanel::getId()
+{
+ SolarMutexGuard aGuard;
+
+ return mPanelId;
+}
+
+OUString SAL_CALL SfxUnoPanel::getTitle()
+{
+ SolarMutexGuard aGuard;
+
+ auto xPanel = mxPanel.lock();
+ PanelTitleBar* pTitleBar = xPanel ? xPanel->GetTitleBar() : nullptr;
+ if (pTitleBar)
+ return pTitleBar->GetTitle();
+ else
+ return OUString();
+}
+
+void SAL_CALL SfxUnoPanel::setTitle( const OUString& newTitle )
+{
+ SolarMutexGuard aGuard;
+
+ SidebarController* pSidebarController = getSidebarController();
+ std::shared_ptr<PanelDescriptor> xPanelDescriptor = pSidebarController->GetResourceManager()->GetPanelDescriptor(mPanelId);
+
+ if (xPanelDescriptor)
+ {
+ xPanelDescriptor->msTitle = newTitle;
+ auto xPanel = mxPanel.lock();
+ PanelTitleBar* pTitleBar = xPanel ? xPanel->GetTitleBar() : nullptr;
+ if (pTitleBar)
+ pTitleBar->SetTitle(newTitle);
+ }
+}
+
+sal_Bool SAL_CALL SfxUnoPanel::isExpanded()
+{
+ SolarMutexGuard aGuard;
+
+ auto xPanel = mxPanel.lock();
+ return xPanel && xPanel->IsExpanded();
+}
+
+
+void SAL_CALL SfxUnoPanel::expand( const sal_Bool bCollapseOther )
+{
+
+ SolarMutexGuard aGuard;
+
+ auto xPanel = mxPanel.lock();
+ if (xPanel)
+ xPanel->SetExpanded(true);
+
+ if (bCollapseOther)
+ {
+ SharedPanelContainer aPanels = mpDeck->GetPanels();
+ for (auto const& panel : aPanels)
+ {
+ if (! panel->HasIdPredicate(mPanelId))
+ panel->SetExpanded(false);
+ }
+ }
+
+ SidebarController* pSidebarController = getSidebarController();
+ pSidebarController->NotifyResize();
+
+}
+
+void SAL_CALL SfxUnoPanel::collapse()
+{
+ SolarMutexGuard aGuard;
+
+ auto xPanel = mxPanel.lock();
+ if (xPanel)
+ xPanel->SetExpanded(false);
+ SidebarController* pSidebarController = getSidebarController();
+ pSidebarController->NotifyResize();
+}
+
+uno::Reference<awt::XWindow> SAL_CALL SfxUnoPanel::getDialog()
+{
+ SolarMutexGuard aGuard;
+
+ auto xPanel = mxPanel.lock();
+ return xPanel ? xPanel->GetElementWindow() : nullptr;
+}
+
+sal_Int32 SAL_CALL SfxUnoPanel::getOrderIndex()
+{
+ SolarMutexGuard aGuard;
+ SidebarController* pSidebarController = getSidebarController();
+
+ sal_Int32 index = pSidebarController->GetResourceManager()->GetPanelDescriptor(mPanelId)->mnOrderIndex;
+ return index;
+}
+
+void SAL_CALL SfxUnoPanel::setOrderIndex( const sal_Int32 newOrderIndex )
+{
+ SolarMutexGuard aGuard;
+ SidebarController* pSidebarController = getSidebarController();
+
+ std::shared_ptr<PanelDescriptor> xPanelDescriptor = pSidebarController->GetResourceManager()->GetPanelDescriptor(mPanelId);
+
+ if (xPanelDescriptor)
+ {
+ xPanelDescriptor->mnOrderIndex = newOrderIndex;
+ // update the sidebar
+ pSidebarController->NotifyResize();
+ }
+}
+
+void SAL_CALL SfxUnoPanel::moveFirst()
+{
+ SolarMutexGuard aGuard;
+ SidebarController* pSidebarController = getSidebarController();
+
+ ResourceManager::PanelContextDescriptorContainer aPanels = pSidebarController->GetMatchingPanels(mDeckId);
+
+ sal_Int32 curOrderIndex = getOrderIndex();
+ sal_Int32 minIndex = GetMinOrderIndex(aPanels);
+
+ if (curOrderIndex != minIndex) // is current panel already in place ?
+ {
+ minIndex -= 1;
+ std::shared_ptr<PanelDescriptor> xPanelDescriptor = pSidebarController->GetResourceManager()->GetPanelDescriptor(mPanelId);
+ if (xPanelDescriptor)
+ {
+ xPanelDescriptor->mnOrderIndex = minIndex;
+ // update the sidebar
+ pSidebarController->NotifyResize();
+ }
+ }
+}
+
+void SAL_CALL SfxUnoPanel::moveLast()
+{
+ SolarMutexGuard aGuard;
+ SidebarController* pSidebarController = getSidebarController();
+
+ ResourceManager::PanelContextDescriptorContainer aPanels = pSidebarController->GetMatchingPanels(mDeckId);
+
+ sal_Int32 curOrderIndex = getOrderIndex();
+ sal_Int32 maxIndex = GetMaxOrderIndex(aPanels);
+
+ if (curOrderIndex != maxIndex) // is current panel already in place ?
+ {
+ maxIndex += 1;
+ std::shared_ptr<PanelDescriptor> xPanelDescriptor = pSidebarController->GetResourceManager()->GetPanelDescriptor(mPanelId);
+ if (xPanelDescriptor)
+ {
+ xPanelDescriptor->mnOrderIndex = maxIndex;
+ // update the sidebar
+ pSidebarController->NotifyResize();
+ }
+ }
+}
+
+void SAL_CALL SfxUnoPanel::moveUp()
+{
+ SolarMutexGuard aGuard;
+ SidebarController* pSidebarController = getSidebarController();
+
+ // Search for previous panel OrderIndex
+ ResourceManager::PanelContextDescriptorContainer aPanels = pSidebarController->GetMatchingPanels(mDeckId);
+
+ sal_Int32 curOrderIndex = getOrderIndex();
+ sal_Int32 previousIndex = GetMinOrderIndex(aPanels);
+
+ for (auto const& panel : aPanels)
+ {
+ sal_Int32 index = pSidebarController->GetResourceManager()->GetPanelDescriptor(panel.msId)->mnOrderIndex;
+ if( index < curOrderIndex && index > previousIndex)
+ previousIndex = index;
+ }
+
+ if (curOrderIndex != previousIndex) // is current panel already in place ?
+ {
+ previousIndex -= 1;
+ std::shared_ptr<PanelDescriptor> xPanelDescriptor = pSidebarController->GetResourceManager()->GetPanelDescriptor(mPanelId);
+ if (xPanelDescriptor)
+ {
+ xPanelDescriptor->mnOrderIndex = previousIndex;
+ // update the sidebar
+ pSidebarController->NotifyResize();
+ }
+ }
+}
+
+void SAL_CALL SfxUnoPanel::moveDown()
+{
+ SolarMutexGuard aGuard;
+ SidebarController* pSidebarController = getSidebarController();
+
+ ResourceManager::PanelContextDescriptorContainer aPanels = pSidebarController->GetMatchingPanels(mDeckId);
+
+ // Search for next panel OrderIndex
+ sal_Int32 curOrderIndex = getOrderIndex();
+ sal_Int32 nextIndex = GetMaxOrderIndex(aPanels);
+
+ for (auto const& panel : aPanels)
+ {
+ sal_Int32 index = pSidebarController->GetResourceManager()->GetPanelDescriptor(panel.msId)->mnOrderIndex;
+ if( index > curOrderIndex && index < nextIndex)
+ nextIndex = index;
+ }
+
+ if (curOrderIndex != nextIndex) // is current panel already in place ?
+ {
+ nextIndex += 1;
+ std::shared_ptr<PanelDescriptor> xPanelDescriptor = pSidebarController->GetResourceManager()->GetPanelDescriptor(mPanelId);
+ if (xPanelDescriptor)
+ {
+ xPanelDescriptor->mnOrderIndex = nextIndex;
+ // update the sidebar
+ pSidebarController->NotifyResize();
+ }
+ }
+}
+
+sal_Int32 SfxUnoPanel::GetMinOrderIndex(const ResourceManager::PanelContextDescriptorContainer& rPanels)
+{
+ SidebarController* pSidebarController = getSidebarController();
+
+ sal_Int32 minIndex = pSidebarController->GetResourceManager()->GetPanelDescriptor(rPanels.begin()->msId)->mnOrderIndex;
+
+ for (auto const& panel : rPanels)
+ {
+ sal_Int32 index = pSidebarController->GetResourceManager()->GetPanelDescriptor(panel.msId)->mnOrderIndex;
+ if(minIndex > index)
+ minIndex = index;
+ }
+ return minIndex;
+}
+
+sal_Int32 SfxUnoPanel::GetMaxOrderIndex(const ResourceManager::PanelContextDescriptorContainer& rPanels)
+{
+ SidebarController* pSidebarController = getSidebarController();
+
+ sal_Int32 maxIndex = pSidebarController->GetResourceManager()->GetPanelDescriptor(rPanels.begin()->msId)->mnOrderIndex;
+
+ for (auto const& panel : rPanels)
+ {
+ sal_Int32 index = pSidebarController->GetResourceManager()->GetPanelDescriptor(panel.msId)->mnOrderIndex;
+ if(maxIndex < index)
+ maxIndex = index;
+ }
+ return maxIndex;
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/sidebar/UnoPanels.cxx b/sfx2/source/sidebar/UnoPanels.cxx
new file mode 100644
index 0000000000..85416c0460
--- /dev/null
+++ b/sfx2/source/sidebar/UnoPanels.cxx
@@ -0,0 +1,154 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ */
+
+#include <sidebar/UnoPanels.hxx>
+
+#include <sfx2/sidebar/ResourceManager.hxx>
+#include <sfx2/sidebar/SidebarController.hxx>
+
+#include <com/sun/star/lang/IndexOutOfBoundsException.hpp>
+#include <com/sun/star/ui/XPanel.hpp>
+#include <sidebar/UnoPanel.hxx>
+
+#include <utility>
+#include <vcl/svapp.hxx>
+
+#include <algorithm>
+
+using namespace css;
+using namespace ::sfx2::sidebar;
+
+SfxUnoPanels::SfxUnoPanels(uno::Reference<frame::XFrame> _xFrame, const OUString& deckId):
+xFrame(std::move(_xFrame)),
+mDeckId(deckId)
+{
+}
+
+SidebarController* SfxUnoPanels::getSidebarController()
+{
+ return SidebarController::GetSidebarControllerForFrame(xFrame);
+}
+
+OUString SAL_CALL SfxUnoPanels::getDeckId()
+{
+ return mDeckId;
+}
+
+// XNameAccess
+
+uno::Any SAL_CALL SfxUnoPanels::getByName( const OUString& aName )
+{
+ SolarMutexGuard aGuard;
+
+ if (!hasByName(aName))
+ throw container::NoSuchElementException();
+
+ uno::Reference<ui::XPanel> xPanel = new SfxUnoPanel(xFrame, aName, mDeckId);
+ return uno::Any(xPanel);
+}
+
+
+uno::Sequence< OUString > SAL_CALL SfxUnoPanels::getElementNames()
+{
+
+ SolarMutexGuard aGuard;
+
+ SidebarController* pSidebarController = getSidebarController();
+
+ ResourceManager::PanelContextDescriptorContainer aPanels;
+ uno::Sequence< OUString > panelList(aPanels.size());
+
+ if (pSidebarController)
+ {
+ pSidebarController->GetResourceManager()->GetMatchingPanels(aPanels,
+ pSidebarController->GetCurrentContext(),
+ mDeckId,
+ xFrame->getController());
+
+ panelList.realloc(aPanels.size());
+ std::transform(aPanels.begin(), aPanels.end(), panelList.getArray(),
+ [](const auto& rPanel) { return rPanel.msId; });
+ }
+
+ return panelList;
+
+}
+
+sal_Bool SAL_CALL SfxUnoPanels::hasByName( const OUString& aName )
+{
+ SolarMutexGuard aGuard;
+
+ SidebarController* pSidebarController = getSidebarController();
+
+ if (pSidebarController)
+ {
+ ResourceManager::PanelContextDescriptorContainer aPanels;
+
+ pSidebarController->GetResourceManager()->GetMatchingPanels(aPanels,
+ pSidebarController->GetCurrentContext(),
+ mDeckId,
+ xFrame->getController());
+
+ bool bIsDocumentReadOnly = pSidebarController->IsDocumentReadOnly();
+
+ return std::any_of(aPanels.begin(), aPanels.end(),
+ [&bIsDocumentReadOnly, &aName](const ResourceManager::PanelContextDescriptor& rPanel) {
+ return (!bIsDocumentReadOnly || rPanel.mbShowForReadOnlyDocuments) // Determine if the panel can be displayed.
+ && (rPanel.msId == aName);
+ });
+ }
+
+ // nothing found
+ return false;
+
+}
+
+// XIndexAccess
+
+sal_Int32 SAL_CALL SfxUnoPanels::getCount()
+{
+ SolarMutexGuard aGuard;
+
+ uno::Sequence< OUString > panels = getElementNames();
+ return panels.getLength();
+}
+
+uno::Any SAL_CALL SfxUnoPanels::getByIndex( sal_Int32 Index )
+{
+ SolarMutexGuard aGuard;
+
+ uno::Any aRet;
+
+ uno::Sequence< OUString > panels = getElementNames();
+
+ if (Index > panels.getLength()-1 || Index < 0)
+ throw lang::IndexOutOfBoundsException();
+
+ uno::Reference<ui::XPanel> xPanel = new SfxUnoPanel(xFrame, panels[Index], mDeckId);
+ aRet <<= xPanel;
+ return aRet;
+
+}
+
+// XElementAccess
+uno::Type SAL_CALL SfxUnoPanels::getElementType()
+{
+ return uno::Type();
+}
+
+sal_Bool SAL_CALL SfxUnoPanels::hasElements()
+{
+ SolarMutexGuard aGuard;
+
+ uno::Sequence< OUString > panels = getElementNames();
+ return panels.hasElements();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/sidebar/UnoSidebar.cxx b/sfx2/source/sidebar/UnoSidebar.cxx
new file mode 100644
index 0000000000..5142d8a4fc
--- /dev/null
+++ b/sfx2/source/sidebar/UnoSidebar.cxx
@@ -0,0 +1,96 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ */
+
+#include <sidebar/UnoSidebar.hxx>
+#include <sidebar/Tools.hxx>
+
+#include <sfx2/sidebar/SidebarController.hxx>
+#include <sidebar/UnoDecks.hxx>
+
+#include <com/sun/star/frame/XDispatch.hpp>
+
+#include <utility>
+#include <vcl/svapp.hxx>
+
+using namespace css;
+using namespace ::sfx2::sidebar;
+
+using ::com::sun::star::uno::RuntimeException;
+
+SfxUnoSidebar::SfxUnoSidebar(uno::Reference<frame::XFrame> _xFrame)
+ : xFrame(std::move(_xFrame))
+{
+}
+
+SidebarController* SfxUnoSidebar::getSidebarController()
+{
+ return SidebarController::GetSidebarControllerForFrame(xFrame);
+}
+
+void SAL_CALL SfxUnoSidebar::showDecks(const sal_Bool bVisible)
+{
+ SolarMutexGuard aGuard;
+
+ SidebarController* pSidebarController = getSidebarController();
+
+ if (pSidebarController)
+ {
+ if (bVisible)
+ pSidebarController->RequestOpenDeck();
+ else
+ pSidebarController->RequestCloseDeck();
+ }
+}
+
+void SAL_CALL SfxUnoSidebar::setVisible(const sal_Bool bVisible)
+{
+ SolarMutexGuard aGuard;
+
+ SidebarController* pSidebarController = getSidebarController();
+
+ if ((bVisible && !pSidebarController) || (!bVisible && pSidebarController))
+ {
+ const util::URL aURL(Tools::GetURL(".uno:Sidebar"));
+ uno::Reference<frame::XDispatch> xDispatch(Tools::GetDispatch(xFrame, aURL));
+ if (xDispatch.is())
+ xDispatch->dispatch(aURL, uno::Sequence<beans::PropertyValue>());
+ }
+}
+
+sal_Bool SAL_CALL SfxUnoSidebar::isVisible()
+{
+ SolarMutexGuard aGuard;
+
+ SidebarController* pSidebarController = getSidebarController();
+
+ return pSidebarController != nullptr;
+}
+
+uno::Reference<frame::XFrame> SAL_CALL SfxUnoSidebar::getFrame()
+{
+ SolarMutexGuard aGuard;
+
+ if (!xFrame.is())
+ throw uno::RuntimeException();
+
+ return xFrame;
+}
+
+uno::Reference<ui::XDecks> SAL_CALL SfxUnoSidebar::getDecks()
+{
+ SolarMutexGuard aGuard;
+
+ uno::Reference<ui::XDecks> decks = new SfxUnoDecks(xFrame);
+ return decks;
+}
+
+uno::Reference<ui::XSidebar> SAL_CALL SfxUnoSidebar::getSidebar() { return getSidebarController(); }
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/sidebar/uiobject.cxx b/sfx2/source/sidebar/uiobject.cxx
new file mode 100644
index 0000000000..13c21d2220
--- /dev/null
+++ b/sfx2/source/sidebar/uiobject.cxx
@@ -0,0 +1,61 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include "uiobject.hxx"
+#include <sfx2/sidebar/SidebarController.hxx>
+
+namespace sfx2::sidebar
+{
+TabBarUIObject::TabBarUIObject(const VclPtr<TabBar>& xTabBar)
+ : WindowUIObject(xTabBar)
+ , mxTabBar(xTabBar)
+{
+}
+
+StringMap TabBarUIObject::get_state()
+{
+ StringMap aMap = WindowUIObject::get_state();
+ OUString rsHighlightedTabsIds;
+ for (auto const& item : mxTabBar->maItems)
+ {
+ if (item->mxButton->get_item_active("toggle"))
+ {
+ if (!rsHighlightedTabsIds.isEmpty())
+ rsHighlightedTabsIds += ",";
+ rsHighlightedTabsIds += item->msDeckId;
+ }
+ }
+ aMap["HighlightedTabsIds"] = rsHighlightedTabsIds;
+ return aMap;
+}
+
+void TabBarUIObject::execute(const OUString& rAction, const StringMap& rParameters)
+{
+ if (rAction == "CLICK")
+ {
+ if (rParameters.find("POS") != rParameters.end())
+ mxTabBar->pParentSidebarController->OpenThenToggleDeck(
+ mxTabBar->GetDeckIdForIndex(rParameters.find("POS")->second.toInt32()));
+ }
+ else
+ WindowUIObject::execute(rAction, rParameters);
+}
+
+std::unique_ptr<UIObject> TabBarUIObject::create(vcl::Window* pWindow)
+{
+ TabBar* pTabBar = dynamic_cast<TabBar*>(pWindow);
+ assert(pTabBar);
+ return std::unique_ptr<UIObject>(new TabBarUIObject(pTabBar));
+}
+
+OUString TabBarUIObject::get_name() const { return "TabBarUIObject"; }
+
+} // namespace sidebar
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/sidebar/uiobject.hxx b/sfx2/source/sidebar/uiobject.hxx
new file mode 100644
index 0000000000..8c7ae5eaab
--- /dev/null
+++ b/sfx2/source/sidebar/uiobject.hxx
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <vcl/uitest/uiobject.hxx>
+#include <sfx2/sidebar/TabBar.hxx>
+
+namespace sfx2::sidebar
+{
+class TabBarUIObject : public WindowUIObject
+{
+ VclPtr<sfx2::sidebar::TabBar> mxTabBar;
+
+ virtual OUString get_name() const override;
+
+public:
+ TabBarUIObject(const VclPtr<TabBar>& xTabBar);
+
+ virtual void execute(const OUString& rAction, const StringMap& rParameters) override;
+ virtual StringMap get_state() override;
+
+ static std::unique_ptr<UIObject> create(vcl::Window* pWindow);
+};
+
+} // namespace sidebar
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */