summaryrefslogtreecommitdiffstats
path: root/sfx2/source/sidebar
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--sfx2/source/sidebar/Accessible.cxx52
-rw-r--r--sfx2/source/sidebar/AccessibleTitleBar.cxx60
-rw-r--r--sfx2/source/sidebar/AsynchronousCall.cxx71
-rw-r--r--sfx2/source/sidebar/Context.cxx85
-rw-r--r--sfx2/source/sidebar/ContextChangeBroadcaster.cxx130
-rw-r--r--sfx2/source/sidebar/ContextList.cxx104
-rw-r--r--sfx2/source/sidebar/ControlFactory.cxx38
-rw-r--r--sfx2/source/sidebar/ControllerFactory.cxx265
-rw-r--r--sfx2/source/sidebar/ControllerItem.cxx73
-rw-r--r--sfx2/source/sidebar/Deck.cxx447
-rw-r--r--sfx2/source/sidebar/DeckDescriptor.cxx62
-rw-r--r--sfx2/source/sidebar/DeckLayouter.cxx634
-rw-r--r--sfx2/source/sidebar/DeckTitleBar.cxx145
-rw-r--r--sfx2/source/sidebar/DrawHelper.cxx127
-rw-r--r--sfx2/source/sidebar/FocusManager.cxx633
-rw-r--r--sfx2/source/sidebar/IContextChangeReceiver.cxx29
-rw-r--r--sfx2/source/sidebar/ILayoutableWindow.cxx29
-rw-r--r--sfx2/source/sidebar/MenuButton.cxx102
-rw-r--r--sfx2/source/sidebar/Paint.cxx102
-rw-r--r--sfx2/source/sidebar/Panel.cxx191
-rw-r--r--sfx2/source/sidebar/PanelDescriptor.cxx62
-rw-r--r--sfx2/source/sidebar/PanelLayout.cxx121
-rw-r--r--sfx2/source/sidebar/PanelTitleBar.cxx207
-rw-r--r--sfx2/source/sidebar/ResourceManager.cxx789
-rw-r--r--sfx2/source/sidebar/Sidebar.cxx93
-rw-r--r--sfx2/source/sidebar/SidebarChildWindow.cxx95
-rw-r--r--sfx2/source/sidebar/SidebarController.cxx1617
-rw-r--r--sfx2/source/sidebar/SidebarDockingWindow.cxx318
-rw-r--r--sfx2/source/sidebar/SidebarModelUpdate.cxx20
-rw-r--r--sfx2/source/sidebar/SidebarPanelBase.cxx199
-rw-r--r--sfx2/source/sidebar/SidebarToolBox.cxx351
-rw-r--r--sfx2/source/sidebar/TabBar.cxx417
-rw-r--r--sfx2/source/sidebar/TabItem.cxx109
-rw-r--r--sfx2/source/sidebar/Theme.cxx1047
-rw-r--r--sfx2/source/sidebar/TitleBar.cxx172
-rw-r--r--sfx2/source/sidebar/Tools.cxx139
-rw-r--r--sfx2/source/sidebar/UnoDeck.cxx278
-rw-r--r--sfx2/source/sidebar/UnoDecks.cxx149
-rw-r--r--sfx2/source/sidebar/UnoPanel.cxx290
-rw-r--r--sfx2/source/sidebar/UnoPanels.cxx161
-rw-r--r--sfx2/source/sidebar/UnoSidebar.cxx101
41 files changed, 10114 insertions, 0 deletions
diff --git a/sfx2/source/sidebar/Accessible.cxx b/sfx2/source/sidebar/Accessible.cxx
new file mode 100644
index 000000000..8eb264f39
--- /dev/null
+++ b/sfx2/source/sidebar/Accessible.cxx
@@ -0,0 +1,52 @@
+/* -*- 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/Accessible.hxx>
+
+using namespace css;
+using namespace css::uno;
+
+namespace sfx2::sidebar {
+
+Accessible::Accessible (
+ const Reference<accessibility::XAccessibleContext>& rxContext)
+ : AccessibleInterfaceBase(m_aMutex),
+ mxContext(rxContext)
+{
+}
+
+Accessible::~Accessible()
+{
+}
+
+void SAL_CALL Accessible::disposing()
+{
+ Reference<XComponent> xComponent (mxContext, UNO_QUERY);
+ if (xComponent.is())
+ xComponent->dispose();
+}
+
+Reference<accessibility::XAccessibleContext> SAL_CALL Accessible::getAccessibleContext()
+{
+ return mxContext;
+}
+
+} // end of namespace sfx2::sidebar
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/sidebar/AccessibleTitleBar.cxx b/sfx2/source/sidebar/AccessibleTitleBar.cxx
new file mode 100644
index 000000000..c92fbf59e
--- /dev/null
+++ b/sfx2/source/sidebar/AccessibleTitleBar.cxx
@@ -0,0 +1,60 @@
+/* -*- 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/AccessibleTitleBar.hxx>
+#include <sidebar/Accessible.hxx>
+#include <sidebar/TitleBar.hxx>
+
+#include <com/sun/star/accessibility/AccessibleStateType.hpp>
+
+#include <unotools/accessiblestatesethelper.hxx>
+
+using namespace css;
+using namespace css::uno;
+
+namespace sfx2::sidebar {
+
+Reference<accessibility::XAccessible> AccessibleTitleBar::Create (TitleBar& rTitleBar)
+{
+ rTitleBar.GetComponentInterface();
+ VCLXWindow* pWindow = rTitleBar.GetWindowPeer();
+ if (pWindow != nullptr)
+ return new Accessible(new AccessibleTitleBar(pWindow));
+ else
+ return nullptr;
+}
+
+AccessibleTitleBar::AccessibleTitleBar (VCLXWindow* pWindow)
+ : VCLXAccessibleComponent(pWindow)
+{
+}
+
+AccessibleTitleBar::~AccessibleTitleBar()
+{
+}
+
+void AccessibleTitleBar::FillAccessibleStateSet (utl::AccessibleStateSetHelper& rStateSet)
+{
+ VCLXAccessibleComponent::FillAccessibleStateSet(rStateSet);
+ rStateSet.AddState(accessibility::AccessibleStateType::FOCUSABLE);
+}
+
+} // end of namespace sfx2::sidebar
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/sidebar/AsynchronousCall.cxx b/sfx2/source/sidebar/AsynchronousCall.cxx
new file mode 100644
index 000000000..5a3ce8db2
--- /dev/null
+++ b/sfx2/source/sidebar/AsynchronousCall.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 <sfx2/sidebar/AsynchronousCall.hxx>
+#include <vcl/svapp.hxx>
+
+namespace sfx2::sidebar {
+
+AsynchronousCall::AsynchronousCall (const Action& rAction)
+ : maAction(rAction),
+ 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 000000000..b2bfd68a3
--- /dev/null
+++ b/sfx2/source/sidebar/Context.cxx
@@ -0,0 +1,85 @@
+/* -*- 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>
+
+
+#define AnyApplicationName "any"
+#define AnyContextName "any"
+
+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 (
+ const OUString& rsApplication,
+ const OUString& rsContext)
+ : msApplication(rsApplication),
+ msContext(rsContext)
+{
+}
+
+sal_Int32 Context::EvaluateMatch (
+ const Context& rOther) const
+{
+ bool bApplicationNameIsAny (rOther.msApplication == AnyApplicationName);
+
+ // special case for charts which use a whole own set of decks
+ if (msApplication == "com.sun.star.chart2.ChartDocument")
+ {
+ bApplicationNameIsAny = false;
+ }
+
+ 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 000000000..8e213e68b
--- /dev/null
+++ b/sfx2/source/sidebar/ContextChangeBroadcaster.cxx
@@ -0,0 +1,130 @@
+/* -*- 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/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()
+ : msContextName(),
+ 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)
+ {
+ 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;
+ }
+
+ // notify the LOK too
+ if (comphelper::LibreOfficeKit::isActive())
+ {
+ if (SfxViewShell* pViewShell = SfxViewShell::Get(rxFrame->getController()))
+ SfxLokHelper::notifyContextChange(pViewShell, rsModuleName, rsContextName);
+ }
+
+ const css::ui::ContextChangeEventObject aEvent(
+ rxFrame->getController(),
+ rsModuleName,
+ rsContextName);
+
+ 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&)
+ {
+ OSL_ENSURE(false, "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 000000000..36e9872d3
--- /dev/null
+++ b/sfx2/source/sidebar/ContextList.cxx
@@ -0,0 +1,104 @@
+/* -*- 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()
+ : maEntries()
+{
+}
+
+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/ControlFactory.cxx b/sfx2/source/sidebar/ControlFactory.cxx
new file mode 100644
index 000000000..d2d3d27dd
--- /dev/null
+++ b/sfx2/source/sidebar/ControlFactory.cxx
@@ -0,0 +1,38 @@
+/* -*- 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/ControlFactory.hxx>
+
+#include <sidebar/MenuButton.hxx>
+#include <sidebar/TabItem.hxx>
+
+namespace sfx2::sidebar {
+
+VclPtr<CheckBox> ControlFactory::CreateMenuButton (vcl::Window* pParentWindow)
+{
+ return VclPtr<MenuButton>::Create(pParentWindow);
+}
+
+VclPtr<RadioButton> ControlFactory::CreateTabItem (vcl::Window* pParentWindow)
+{
+ return VclPtr<TabItem>::Create(pParentWindow);
+}
+
+} // 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 000000000..50ba57a00
--- /dev/null
+++ b/sfx2/source/sidebar/ControllerFactory.cxx
@@ -0,0 +1,265 @@
+/* -*- 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 <vcl/commandinfoprovider.hxx>
+#include <vcl/weldutils.hxx>
+#include <svtools/generictoolboxcontroller.hxx>
+#include <comphelper/processfactory.hxx>
+#include <toolkit/helper/vclunohelper.hxx>
+
+using namespace css;
+using namespace css::uno;
+
+namespace sfx2::sidebar {
+
+Reference<frame::XToolbarController> ControllerFactory::CreateToolBoxController(
+ ToolBox* pToolBox,
+ const sal_uInt16 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.set(
+ static_cast<XWeak*>(::framework::CreateToolBoxController(
+ rxFrame,
+ pToolBox,
+ nItemId,
+ rsCommandName)),
+ UNO_QUERY);
+ }
+ if ( ! xController.is())
+ {
+ xController.set(
+ static_cast<XWeak*>(new svt::GenericToolboxController(
+ ::comphelper::getProcessComponentContext(),
+ rxFrame,
+ pToolBox,
+ nItemId,
+ rsCommandName)),
+ UNO_QUERY);
+ }
+
+ // 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(makeAny(aPropValue));
+
+ aPropValue.Name = "ServiceManager";
+ aPropValue.Value <<= ::comphelper::getProcessServiceFactory();
+ aPropertyVector.push_back(makeAny(aPropValue));
+
+ aPropValue.Name = "CommandURL";
+ aPropValue.Value <<= rsCommandName;
+ aPropertyVector.push_back(makeAny(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, bool bSideBar)
+{
+ css::uno::Reference<css::awt::XWindow> xWidget(new weld::TransportAsXWindow(&rToolbar, &rBuilder));
+
+ Reference<frame::XToolbarController> xController(
+ CreateToolBarController(
+ xWidget,
+ rsCommandName,
+ rxFrame, rxFrame->getController(),
+ -1, bSideBar));
+
+ if (!xController.is())
+ {
+ xController.set(
+ static_cast<XWeak*>(new svt::GenericToolboxController(
+ ::comphelper::getProcessComponentContext(),
+ rxFrame,
+ rToolbar,
+ rsCommandName)),
+ UNO_QUERY);
+ }
+
+ // 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(makeAny(aPropValue));
+
+ aPropValue.Name = "ServiceManager";
+ aPropValue.Value <<= ::comphelper::getProcessServiceFactory();
+ aPropertyVector.push_back(makeAny(aPropValue));
+
+ aPropValue.Name = "CommandURL";
+ aPropValue.Value <<= rsCommandName;
+ aPropertyVector.push_back(makeAny(aPropValue));
+
+ Sequence<Any> aArgs (comphelper::containerToSequence(aPropertyVector));
+ xInitialization->initialize(aArgs);
+ }
+
+ 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( makeAny( aPropValue ));
+
+ aPropValue.Name = "Frame";
+ aPropValue.Value <<= rxFrame;
+ aPropertyVector.push_back( makeAny( aPropValue ));
+
+ aPropValue.Name = "ServiceManager";
+ aPropValue.Value <<= comphelper::getProcessServiceFactory();
+ aPropertyVector.push_back( makeAny( aPropValue ));
+
+ aPropValue.Name = "ParentWindow";
+ aPropValue.Value <<= rxToolbar;
+ aPropertyVector.push_back( makeAny( aPropValue ));
+
+ aPropValue.Name = "IsSidebar";
+ aPropValue.Value <<= bSideBar;
+ aPropertyVector.push_back( makeAny( aPropValue ));
+
+ if (nWidth > 0)
+ {
+ aPropValue.Name = "Width";
+ aPropValue.Value <<= nWidth;
+ aPropertyVector.push_back( makeAny( 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 000000000..a362e3d8d
--- /dev/null
+++ b/sfx2/source/sidebar/ControllerItem.cxx
@@ -0,0 +1,73 @@
+/* -*- 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>
+
+#include <com/sun/star/lang/XComponent.hpp>
+
+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::StateChanged (
+ 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 000000000..d96305c3e
--- /dev/null
+++ b/sfx2/source/sidebar/Deck.cxx
@@ -0,0 +1,447 @@
+/* -*- 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/DrawHelper.hxx>
+#include <sidebar/DeckTitleBar.hxx>
+#include <sidebar/PanelTitleBar.hxx>
+#include <sidebar/Paint.hxx>
+#include <sfx2/sidebar/Panel.hxx>
+#include <sfx2/sidebar/Theme.hxx>
+#include <sfx2/lokhelper.hxx>
+
+#include <vcl/event.hxx>
+#include <comphelper/lok.hxx>
+#include <vcl/scrbar.hxx>
+#include <vcl/commandevent.hxx>
+#include <vcl/IDialogRenderable.hxx>
+#include <toolkit/helper/vclunohelper.hxx>
+#include <tools/svborder.hxx>
+#include <boost/property_tree/ptree.hpp>
+#include <sal/log.hxx>
+
+using namespace css;
+using namespace css::uno;
+
+namespace sfx2::sidebar {
+
+Deck::Deck(const DeckDescriptor& rDeckDescriptor, vcl::Window* pParentWindow,
+ const std::function<void()>& rCloserAction)
+ : Window(pParentWindow, 0)
+ , msId(rDeckDescriptor.msId)
+ , mnMinimalWidth(0)
+ , mnMinimalHeight(0)
+ , maPanels()
+ , mpTitleBar(VclPtr<DeckTitleBar>::Create(rDeckDescriptor.msTitle, this, rCloserAction))
+ , mpScrollClipWindow(VclPtr<vcl::Window>::Create(this))
+ , mpScrollContainer(VclPtr<ScrollContainerWindow>::Create(mpScrollClipWindow.get()))
+ , mpFiller(VclPtr<vcl::Window>::Create(this))
+ , mpVerticalScrollBar(VclPtr<ScrollBar>::Create(this))
+{
+ mpScrollClipWindow->SetBackground(Wallpaper());
+ mpScrollClipWindow->Show();
+
+ mpScrollContainer->SetStyle(mpScrollContainer->GetStyle() | WB_DIALOGCONTROL);
+ mpScrollContainer->SetBackground(Wallpaper());
+ mpScrollContainer->Show();
+
+ mpVerticalScrollBar->SetScrollHdl(LINK(this, Deck, HandleVerticalScrollBarChange));
+ mpVerticalScrollBar->SetLineSize(10);
+ mpVerticalScrollBar->SetPageSize(100);
+
+#ifdef DEBUG
+ SetText(OUString("Deck"));
+ mpScrollClipWindow->SetText(OUString("ScrollClipWindow"));
+ mpFiller->SetText(OUString("Filler"));
+ mpVerticalScrollBar->SetText(OUString("VerticalScrollBar"));
+#endif
+}
+
+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 (VclPtr<Panel> & rpPanel : aPanels)
+ rpPanel.disposeAndClear();
+
+ maPanels.clear(); // just to keep the loplugin:vclwidgets happy
+ mpTitleBar.disposeAndClear();
+ mpFiller.disposeAndClear();
+ mpVerticalScrollBar.disposeAndClear();
+ mpScrollContainer.disposeAndClear();
+ mpScrollClipWindow.disposeAndClear();
+
+ vcl::Window::dispose();
+}
+
+VclPtr<DeckTitleBar> const & Deck::GetTitleBar() const
+{
+ return mpTitleBar;
+}
+
+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::ApplySettings(vcl::RenderContext& rRenderContext)
+{
+ rRenderContext.SetBackground(Wallpaper());
+}
+
+void Deck::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& /*rUpdateArea*/)
+{
+ const Size aWindowSize (GetSizePixel());
+ const SvBorder aPadding(Theme::GetInteger(Theme::Int_DeckLeftPadding),
+ Theme::GetInteger(Theme::Int_DeckTopPadding),
+ Theme::GetInteger(Theme::Int_DeckRightPadding),
+ Theme::GetInteger(Theme::Int_DeckBottomPadding));
+
+ // Paint deck background outside the border.
+ tools::Rectangle aBox(0, 0, aWindowSize.Width() - 1, aWindowSize.Height() - 1);
+ DrawHelper::DrawBorder(rRenderContext, aBox, aPadding,
+ Theme::GetPaint(Theme::Paint_DeckBackground),
+ Theme::GetPaint(Theme::Paint_DeckBackground));
+
+ // Paint the border.
+ const int nBorderSize(Theme::GetInteger(Theme::Int_DeckBorderSize));
+ aBox.AdjustLeft(aPadding.Left() );
+ aBox.AdjustTop(aPadding.Top() );
+ aBox.AdjustRight( -(aPadding.Right()) );
+ aBox.AdjustBottom( -(aPadding.Bottom()) );
+ const sfx2::sidebar::Paint& rHorizontalBorderPaint(Theme::GetPaint(Theme::Paint_HorizontalBorder));
+ DrawHelper::DrawBorder(rRenderContext, aBox,
+ SvBorder(nBorderSize, nBorderSize, nBorderSize, nBorderSize),
+ rHorizontalBorderPaint,
+ Theme::GetPaint(Theme::Paint_VerticalBorder));
+}
+
+void Deck::DataChanged (const DataChangedEvent&)
+{
+ RequestLayoutInternal();
+}
+
+bool Deck::EventNotify(NotifyEvent& rEvent)
+{
+ if (rEvent.GetType() == MouseNotifyEvent::COMMAND)
+ {
+ CommandEvent* pCommandEvent = static_cast<CommandEvent*>(rEvent.GetData());
+ if (pCommandEvent != nullptr)
+ switch (pCommandEvent->GetCommand())
+ {
+ case CommandEventId::Wheel:
+ return ProcessWheelEvent(pCommandEvent);
+
+ default:
+ break;
+ }
+ }
+
+ return Window::EventNotify(rEvent);
+}
+
+void Deck::Resize()
+{
+ Window::Resize();
+
+ if (const vcl::ILibreOfficeKitNotifier* pNotifier = GetLOKNotifier())
+ {
+ std::vector<vcl::LOKPayloadItem> aItems;
+ aItems.emplace_back("type", "deck");
+ aItems.emplace_back(std::make_pair("position", Point(GetOutOffXPixel(), GetOutOffYPixel()).toString()));
+ aItems.emplace_back(std::make_pair("size", GetSizePixel().toString()));
+ pNotifier->notifyWindow(GetLOKWindowId(), "size_changed", aItems);
+ }
+}
+
+/*
+ * Get the ordering as is shown in the layout, and our type as 'deck'
+ * also elide nested panel windows.
+ */
+boost::property_tree::ptree Deck::DumpAsPropertyTree()
+{
+ boost::property_tree::ptree aTree;
+ aTree.put("id", get_id()); // TODO could be missing - sort out
+ aTree.put("type", "deck");
+ aTree.put("text", GetText());
+ aTree.put("enabled", IsEnabled());
+
+ boost::property_tree::ptree aPanelNodes;
+ for (auto &it : maPanels)
+ {
+ if (it->IsLurking())
+ continue;
+
+ // collapse the panel itself out
+ auto xContent = it->GetElementWindow();
+ if (!xContent.is())
+ continue;
+ VclPtr<vcl::Window> pWindow = VCLUnoHelper::GetWindow(xContent);
+ if (!pWindow)
+ continue;
+
+ boost::property_tree::ptree aPanel;
+ aPanel.put("id", it->GetId());
+ aPanel.put("type", "panel");
+ aPanel.put("text", it->GetText());
+ aPanel.put("enabled", it->IsEnabled());
+
+ boost::property_tree::ptree aChildren;
+ aChildren.push_back(std::make_pair("", pWindow->DumpAsPropertyTree()));
+ aPanel.add_child("children", aChildren);
+
+ aPanelNodes.push_back(std::make_pair("", aPanel));
+ }
+ aTree.add_child("children", aPanelNodes);
+
+ return aTree;
+}
+
+bool Deck::ProcessWheelEvent(CommandEvent const * pCommandEvent)
+{
+ if ( ! mpVerticalScrollBar)
+ return false;
+ if ( ! mpVerticalScrollBar->IsVisible())
+ return false;
+
+ // Get the wheel data and check that it describes a valid vertical
+ // scroll.
+ const CommandWheelData* pData = pCommandEvent->GetWheelData();
+ if (pData==nullptr
+ || pData->GetModifier()
+ || pData->GetMode() != CommandWheelMode::SCROLL
+ || pData->IsHorz())
+ return false;
+
+ // Execute the actual scroll action.
+ long nDelta = pData->GetDelta();
+ mpVerticalScrollBar->DoScroll(
+ mpVerticalScrollBar->GetThumbPos() - nDelta);
+ return true;
+}
+
+/**
+ * This container may contain existing panels that are
+ * being re-used, and new ones too.
+ */
+void Deck::ResetPanels(const SharedPanelContainer& rPanelContainer)
+{
+ SharedPanelContainer aHiddens;
+
+ // First hide old panels we don't need just now.
+ for (VclPtr<Panel> & 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);
+ }
+ }
+ maPanels = rPanelContainer;
+
+ // Hidden ones always at the end
+ maPanels.insert(std::end(maPanels), std::begin(aHiddens), std::end(aHiddens));
+
+ RequestLayoutInternal();
+}
+
+void Deck::RequestLayoutInternal()
+{
+ mnMinimalWidth = 0;
+ mnMinimalHeight = 0;
+
+ DeckLayouter::LayoutDeck(GetContentArea(), mnMinimalWidth, mnMinimalHeight, maPanels,
+ *GetTitleBar(), *mpScrollClipWindow, *mpScrollContainer,
+ *mpFiller, *mpVerticalScrollBar);
+}
+
+void Deck::RequestLayout()
+{
+ RequestLayoutInternal();
+
+ if (comphelper::LibreOfficeKit::isActive())
+ {
+ bool bChangeNeeded = false;
+ Size aParentSize = GetParent()->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)
+ {
+ GetParent()->SetSizePixel(aParentSize);
+ setPosSizePixel(0, 0, aParentSize.Width(), aParentSize.Height());
+ }
+ else if (aParentSize != GetSizePixel()) //Sync parent & child sizes
+ setPosSizePixel(0, 0, aParentSize.Width(), aParentSize.Height());
+ }
+}
+
+vcl::Window* Deck::GetPanelParentWindow()
+{
+ return mpScrollContainer.get();
+}
+
+Panel* Deck::GetPanel(const OUString & panelId)
+{
+ for (const VclPtr<Panel> & pPanel : maPanels)
+ {
+ if(pPanel->GetId() == panelId)
+ {
+ return pPanel.get();
+ }
+ }
+ return nullptr;
+
+}
+
+void Deck::ShowPanel(const Panel& rPanel)
+{
+ if (!mpVerticalScrollBar || !mpVerticalScrollBar->IsVisible())
+ return;
+
+ // Get vertical extent of the panel.
+ sal_Int32 nPanelTop (rPanel.GetPosPixel().Y());
+ const sal_Int32 nPanelBottom (nPanelTop + rPanel.GetSizePixel().Height() - 1);
+ // Add the title bar into the extent.
+ if (rPanel.GetTitleBar() && rPanel.GetTitleBar()->IsVisible())
+ nPanelTop = rPanel.GetTitleBar()->GetPosPixel().Y();
+
+ // 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 (mpVerticalScrollBar->GetThumbPos());
+ if (nPanelBottom >= nNewThumbPos+mpVerticalScrollBar->GetVisibleSize())
+ nNewThumbPos = nPanelBottom - mpVerticalScrollBar->GetVisibleSize();
+ if (nPanelTop < nNewThumbPos)
+ nNewThumbPos = nPanelTop;
+
+ mpVerticalScrollBar->SetThumbPos(nNewThumbPos);
+ mpScrollContainer->SetPosPixel(
+ Point(
+ mpScrollContainer->GetPosPixel().X(),
+ -nNewThumbPos));
+}
+
+static OUString GetWindowClassification(const vcl::Window* pWindow)
+{
+ const OUString& rsName (pWindow->GetText());
+ if (!rsName.isEmpty())
+ {
+ return rsName;
+ }
+ else
+ {
+ return "window";
+ }
+}
+
+void Deck::PrintWindowSubTree(vcl::Window* pRoot, int nIndentation)
+{
+ static const char* const sIndentation = " ";
+ const Point aLocation (pRoot->GetPosPixel());
+ const Size aSize (pRoot->GetSizePixel());
+ SAL_INFO(
+ "sfx.sidebar",
+ sIndentation + strlen(sIndentation) - nIndentation * 4 << pRoot << " "
+ << GetWindowClassification(pRoot) << " "
+ << (pRoot->IsVisible() ? "visible" : "hidden") << " +"
+ << aLocation.X() << "+" << aLocation.Y() << " x" << aSize.Width()
+ << "x" << aSize.Height());
+
+ const sal_uInt16 nChildCount(pRoot->GetChildCount());
+ for (sal_uInt16 nIndex = 0; nIndex < nChildCount; ++nIndex)
+ PrintWindowSubTree(pRoot->GetChild(nIndex), nIndentation + 1);
+}
+
+IMPL_LINK_NOARG(Deck, HandleVerticalScrollBarChange, ScrollBar*, void)
+{
+ const sal_Int32 nYOffset (-mpVerticalScrollBar->GetThumbPos());
+ mpScrollContainer->SetPosPixel(Point(mpScrollContainer->GetPosPixel().X(),
+ nYOffset));
+ mpScrollContainer->Invalidate();
+}
+
+//----- Deck::ScrollContainerWindow -------------------------------------------
+
+Deck::ScrollContainerWindow::ScrollContainerWindow (vcl::Window* pParentWindow)
+ : Window(pParentWindow),
+ maSeparators()
+{
+#ifdef DEBUG
+ SetText(OUString("ScrollContainerWindow"));
+#endif
+}
+
+void Deck::ScrollContainerWindow::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& /*rUpdateArea*/)
+{
+ // Paint the separators.
+ const sal_Int32 nSeparatorHeight(Theme::GetInteger(Theme::Int_DeckSeparatorHeight));
+ const sal_Int32 nLeft(0);
+ const sal_Int32 nRight(GetSizePixel().Width() - 1);
+ const sfx2::sidebar::Paint& rHorizontalBorderPaint(Theme::GetPaint(Theme::Paint_HorizontalBorder));
+ for (auto const& separator : maSeparators)
+ {
+ DrawHelper::DrawHorizontalLine(rRenderContext, nLeft, nRight, separator,
+ nSeparatorHeight, rHorizontalBorderPaint);
+ }
+}
+
+void Deck::ScrollContainerWindow::SetSeparators (const ::std::vector<sal_Int32>& rSeparators)
+{
+ maSeparators = rSeparators;
+ Invalidate();
+}
+
+} // 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 000000000..79bb3afda
--- /dev/null
+++ b/sfx2/source/sidebar/DeckDescriptor.cxx
@@ -0,0 +1,62 @@
+/* -*- 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()
+ : msTitle(),
+ msId(),
+ msIconURL(),
+ msHighContrastIconURL(),
+ msTitleBarIconURL(),
+ msHighContrastTitleBarIconURL(),
+ msHelpText(),
+ maContextList(),
+ mbIsEnabled(true),
+ mnOrderIndex(10000), // Default value as defined in Sidebar.xcs
+ mbExperimental(false),
+ mpDeck()
+{
+}
+
+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),
+ 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 000000000..49a57bf4d
--- /dev/null
+++ b/sfx2/source/sidebar/DeckLayouter.cxx
@@ -0,0 +1,634 @@
+/* -*- 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 <sfx2/sidebar/Theme.hxx>
+#include <sfx2/sidebar/Panel.hxx>
+#include <sidebar/PanelTitleBar.hxx>
+#include <sfx2/sidebar/Deck.hxx>
+#include <sfx2/sidebar/SidebarController.hxx>
+#include <comphelper/lok.hxx>
+
+#include <comphelper/processfactory.hxx>
+#include <vcl/window.hxx>
+#include <vcl/scrbar.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>
+
+using namespace css;
+using namespace css::uno;
+
+namespace sfx2::sidebar {
+
+namespace {
+ static const sal_Int32 MinimalPanelHeight (25);
+
+ enum LayoutMode
+ {
+ MinimumOrLarger,
+ PreferredOrLarger,
+ Preferred
+ };
+ class LayoutItem
+ {
+ public:
+ VclPtr<Panel> mpPanel;
+ css::ui::LayoutSize maLayoutSize;
+ sal_Int32 mnDistributedHeight;
+ sal_Int32 mnWeight;
+ bool mbShowTitleBar;
+
+ LayoutItem(const VclPtr<Panel>& rPanel)
+ : mpPanel(rPanel)
+ , maLayoutSize(0, 0, 0)
+ , mnDistributedHeight(0)
+ , mnWeight(0)
+ , mbShowTitleBar(true)
+ {
+ }
+ };
+ tools::Rectangle LayoutPanels (
+ const tools::Rectangle& rContentArea,
+ sal_Int32& rMinimalWidth,
+ sal_Int32& rMinimalHeight,
+ ::std::vector<LayoutItem>& rLayoutItems,
+ vcl::Window& rScrollClipWindow,
+ vcl::Window& rScrollContainer,
+ ScrollBar& 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);
+ bool MoveResizePixel(const VclPtr<vcl::Window> &pWindow,
+ const Point &rNewPos, const Size &rNewSize);
+ sal_Int32 PlacePanels (
+ ::std::vector<LayoutItem>& rLayoutItems,
+ const sal_Int32 nWidth,
+ const LayoutMode eMode,
+ vcl::Window& rScrollContainer);
+ tools::Rectangle PlaceDeckTitle (
+ vcl::Window& rTittleBar,
+ const tools::Rectangle& rAvailableSpace);
+ tools::Rectangle PlaceVerticalScrollBar (
+ ScrollBar& rVerticalScrollBar,
+ const tools::Rectangle& rAvailableSpace,
+ const bool bShowVerticalScrollBar);
+ void SetupVerticalScrollBar(
+ ScrollBar& rVerticalScrollBar,
+ const sal_Int32 nContentHeight,
+ const sal_Int32 nVisibleHeight);
+ void UpdateFiller (
+ vcl::Window& rFiller,
+ const tools::Rectangle& rBox);
+}
+
+void DeckLayouter::LayoutDeck (
+ const tools::Rectangle& rContentArea,
+ sal_Int32& rMinimalWidth,
+ sal_Int32& rMinimalHeight,
+ SharedPanelContainer& rPanels,
+ vcl::Window& rDeckTitleBar,
+ vcl::Window& rScrollClipWindow,
+ vcl::Window& rScrollContainer,
+ vcl::Window& rFiller,
+ ScrollBar& rVerticalScrollBar)
+{
+ if (rContentArea.GetWidth()<=0 || rContentArea.GetHeight()<=0)
+ return;
+ tools::Rectangle aBox (PlaceDeckTitle(rDeckTitleBar, rContentArea));
+
+ if ( ! rPanels.empty())
+ {
+ // Prepare the layout item container.
+ ::std::vector<LayoutItem> aLayoutItems;
+ aLayoutItems.reserve(rPanels.size());
+ for (const auto& rPanel : rPanels)
+ aLayoutItems.emplace_back(rPanel);
+
+ aBox = LayoutPanels(
+ aBox,
+ rMinimalWidth,
+ rMinimalHeight,
+ aLayoutItems,
+ rScrollClipWindow,
+ rScrollContainer,
+ rVerticalScrollBar,
+ false);
+ }
+ UpdateFiller(rFiller, aBox);
+}
+
+namespace {
+
+tools::Rectangle LayoutPanels (
+ const tools::Rectangle& rContentArea,
+ sal_Int32& rMinimalWidth,
+ sal_Int32& rMinimalHeight,
+ ::std::vector<LayoutItem>& rLayoutItems,
+ vcl::Window& rScrollClipWindow,
+ vcl::Window& rScrollContainer,
+ ScrollBar& rVerticalScrollBar,
+ const bool bShowVerticalScrollBar)
+{
+ tools::Rectangle aBox (PlaceVerticalScrollBar(rVerticalScrollBar, rContentArea, bShowVerticalScrollBar));
+
+ const sal_Int32 nWidth (aBox.GetWidth());
+
+ // 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.
+ return LayoutPanels(
+ rContentArea,
+ rMinimalWidth,
+ rMinimalHeight,
+ rLayoutItems,
+ rScrollClipWindow,
+ rScrollContainer,
+ rVerticalScrollBar,
+ true);
+ }
+
+ // 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;
+ else if (nTotalPreferredHeight <= nAvailableHeight)
+ eMode = PreferredOrLarger;
+ else
+ eMode = MinimumOrLarger;
+
+ if (eMode != Preferred)
+ {
+ const sal_Int32 nTotalHeight (eMode==MinimumOrLarger ? nTotalMinimumHeight : nTotalPreferredHeight);
+
+ DistributeHeights(
+ rLayoutItems,
+ nAvailableHeight-nTotalHeight,
+ aBox.GetHeight(),
+ eMode==MinimumOrLarger);
+ }
+
+ // Set position and size of the mpScrollClipWindow to the available
+ // size. Its child, the mpScrollContainer, may have a bigger
+ // height.
+ rScrollClipWindow.setPosSizePixel(aBox.Left(), aBox.Top(), aBox.GetWidth(), aBox.GetHeight());
+
+ const sal_Int32 nContentHeight (
+ eMode==Preferred
+ ? nTotalPreferredHeight + nTotalDecorationHeight
+ : aBox.GetHeight());
+ sal_Int32 nY = rVerticalScrollBar.GetThumbPos();
+ if (nContentHeight-nY < aBox.GetHeight())
+ nY = nContentHeight-aBox.GetHeight();
+ if (nY < 0)
+ nY = 0;
+ rScrollContainer.setPosSizePixel(
+ 0,
+ -nY,
+ nWidth,
+ nContentHeight);
+
+ if (bShowVerticalScrollBar)
+ SetupVerticalScrollBar(rVerticalScrollBar, nContentHeight, aBox.GetHeight());
+
+ const sal_Int32 nUsedHeight (PlacePanels(rLayoutItems, nWidth, eMode, rScrollContainer));
+ aBox.AdjustTop(nUsedHeight );
+ rMinimalHeight = nUsedHeight;
+ return aBox;
+}
+
+bool MoveResizePixel(const VclPtr<vcl::Window> &pWindow,
+ const Point &rNewPos, const Size &rNewSize)
+{
+ Point aCurPos = pWindow->GetPosPixel();
+ Size aCurSize = pWindow->GetSizePixel();
+ if (rNewPos == aCurPos && aCurSize == rNewSize)
+ return false;
+ pWindow->setPosSizePixel(rNewPos.X(), rNewPos.Y(), rNewSize.Width(), rNewSize.Height());
+ return true;
+}
+
+sal_Int32 PlacePanels (
+ ::std::vector<LayoutItem>& rLayoutItems,
+ const sal_Int32 nWidth,
+ const LayoutMode eMode,
+ vcl::Window& rScrollContainer)
+{
+ ::std::vector<sal_Int32> aSeparators;
+ const sal_Int32 nDeckSeparatorHeight (Theme::GetInteger(Theme::Int_DeckSeparatorHeight));
+ sal_Int32 nY (0);
+
+ vcl::Region aInvalidRegions;
+
+ // 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);
+
+ // Separator above the panel title bar.
+ if (!rPanel.IsLurking())
+ {
+ aSeparators.push_back(nY);
+ nY += nDeckSeparatorHeight;
+ }
+
+ // Place the title bar.
+ VclPtr<PanelTitleBar> pTitleBar = rPanel.GetTitleBar();
+ if (pTitleBar)
+ {
+ const sal_Int32 nPanelTitleBarHeight (Theme::GetInteger(Theme::Int_PanelTitleBarHeight) * rPanel.GetDPIScaleFactor());
+
+ if (iItem->mbShowTitleBar)
+ {
+ pTitleBar->setPosSizePixel(0, nY, nWidth, nPanelTitleBarHeight);
+ pTitleBar->Show();
+ nY += nPanelTitleBarHeight;
+ }
+ else
+ {
+ pTitleBar->Hide();
+ }
+ }
+
+ if (rPanel.IsExpanded() && !rPanel.IsLurking())
+ {
+ rPanel.Show();
+
+ // Determine the height of the panel depending on layout
+ // mode and distributed heights.
+ sal_Int32 nPanelHeight (0);
+ 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;
+ }
+
+ // Place the panel.
+ Point aNewPos(0, nY);
+ Size aNewSize(nWidth, nPanelHeight);
+
+ // Only invalidate if we moved
+ if (MoveResizePixel(&rPanel, aNewPos, aNewSize))
+ {
+ tools::Rectangle aRect(aNewPos, aNewSize);
+ aInvalidRegions.Union(rPanel.PixelToLogic(aRect));
+ }
+
+ nY += nPanelHeight;
+ }
+ else
+ {
+ rPanel.Hide();
+
+ // 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.
+ aSeparators.push_back(nY);
+ nY += nDeckSeparatorHeight;
+ }
+ }
+ }
+
+ Deck::ScrollContainerWindow* pScrollContainerWindow
+ = dynamic_cast<Deck::ScrollContainerWindow*>(&rScrollContainer);
+ if (pScrollContainerWindow != nullptr)
+ pScrollContainerWindow->SetSeparators(aSeparators);
+
+ rScrollContainer.Invalidate(aInvalidRegions);
+
+ 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.
+ const sal_Int32 nPanelTitleBarHeight (Theme::GetInteger(Theme::Int_PanelTitleBarHeight) * rItem.mpPanel->GetDPIScaleFactor());
+
+ 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_WARN("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 = xPanel->getMinimalWidth();
+
+ 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 (
+ vcl::Window& rDeckTitleBar,
+ const tools::Rectangle& rAvailableSpace)
+{
+ if (static_cast<DockingWindow*>(rDeckTitleBar.GetParent()->GetParent())->IsFloatingMode())
+ {
+ // When the side bar is undocked then the outer system window displays the deck title.
+ rDeckTitleBar.Hide();
+ return rAvailableSpace;
+ }
+ else
+ {
+ const sal_Int32 nDeckTitleBarHeight (Theme::GetInteger(Theme::Int_DeckTitleBarHeight) * rDeckTitleBar.GetDPIScaleFactor());
+ rDeckTitleBar.setPosSizePixel(
+ rAvailableSpace.Left(),
+ rAvailableSpace.Top(),
+ rAvailableSpace.GetWidth(),
+ nDeckTitleBarHeight);
+ rDeckTitleBar.Show();
+ return tools::Rectangle(
+ rAvailableSpace.Left(),
+ rAvailableSpace.Top() + nDeckTitleBarHeight,
+ rAvailableSpace.Right(),
+ rAvailableSpace.Bottom());
+ }
+}
+
+tools::Rectangle PlaceVerticalScrollBar (
+ ScrollBar& rVerticalScrollBar,
+ const tools::Rectangle& rAvailableSpace,
+ const bool bShowVerticalScrollBar)
+{
+ if (bShowVerticalScrollBar)
+ {
+ const sal_Int32 nScrollBarWidth (rVerticalScrollBar.GetSizePixel().Width());
+ rVerticalScrollBar.setPosSizePixel(
+ rAvailableSpace.Right() - nScrollBarWidth + 1,
+ rAvailableSpace.Top(),
+ nScrollBarWidth,
+ rAvailableSpace.GetHeight());
+ rVerticalScrollBar.Show();
+ return tools::Rectangle(
+ rAvailableSpace.Left(),
+ rAvailableSpace.Top(),
+ rAvailableSpace.Right() - nScrollBarWidth,
+ rAvailableSpace.Bottom());
+ }
+ else
+ {
+ rVerticalScrollBar.Hide();
+ return rAvailableSpace;
+ }
+}
+
+void SetupVerticalScrollBar(
+ ScrollBar& rVerticalScrollBar,
+ const sal_Int32 nContentHeight,
+ const sal_Int32 nVisibleHeight)
+{
+ OSL_ASSERT(nContentHeight > nVisibleHeight);
+
+ rVerticalScrollBar.SetRangeMin(0);
+ rVerticalScrollBar.SetRangeMax(nContentHeight-1);
+ rVerticalScrollBar.SetVisibleSize(nVisibleHeight);
+}
+
+void UpdateFiller (
+ vcl::Window& rFiller,
+ const tools::Rectangle& rBox)
+{
+ if (rBox.GetHeight() > 0)
+ {
+ // Show the filler.
+ rFiller.SetBackground(Theme::GetPaint(Theme::Paint_PanelBackground).GetWallpaper());
+ rFiller.SetPosSizePixel(rBox.TopLeft(), rBox.GetSize());
+ rFiller.Show();
+ }
+ else
+ {
+ // Hide the filler.
+ rFiller.Hide();
+ }
+}
+
+}
+
+} // 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 000000000..4c2f833a4
--- /dev/null
+++ b/sfx2/source/sidebar/DeckTitleBar.cxx
@@ -0,0 +1,145 @@
+/* -*- 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 <sfx2/sfxresid.hxx>
+#include <sfx2/strings.hrc>
+
+#include <vcl/event.hxx>
+#include <vcl/image.hxx>
+#include <vcl/ptrstyle.hxx>
+
+#ifdef DEBUG
+#include <sfx2/sidebar/Tools.hxx>
+#endif
+
+namespace sfx2::sidebar {
+
+namespace
+{
+static const sal_Int32 gaLeftGripPadding (3);
+static const sal_Int32 gaRightGripPadding (6);
+}
+
+DeckTitleBar::DeckTitleBar (const OUString& rsTitle,
+ vcl::Window* pParentWindow,
+ const std::function<void()>& rCloserAction)
+ : TitleBar(rsTitle, pParentWindow, GetBackgroundPaint())
+ , maCloserAction(rCloserAction)
+ , mbIsCloserVisible(false)
+{
+ OSL_ASSERT(pParentWindow != nullptr);
+
+ if (maCloserAction)
+ SetCloserVisible(true);
+
+#ifdef DEBUG
+ SetText(OUString("DeckTitleBar"));
+#endif
+}
+
+void DeckTitleBar::SetCloserVisible (const bool bIsCloserVisible)
+{
+ if (mbIsCloserVisible == bIsCloserVisible)
+ return;
+
+ mbIsCloserVisible = bIsCloserVisible;
+
+ if (mbIsCloserVisible)
+ {
+ maToolBox->InsertItem(mnCloserItemIndex,
+ Theme::GetImage(Theme::Image_Closer));
+ maToolBox->SetQuickHelpText(mnCloserItemIndex,
+ SfxResId(SFX_STR_SIDEBAR_CLOSE_DECK));
+ }
+ else
+ maToolBox->RemoveItem(maToolBox->GetItemPos(mnCloserItemIndex));
+}
+
+tools::Rectangle DeckTitleBar::GetTitleArea (const tools::Rectangle& rTitleBarBox)
+{
+ Image aGripImage (Theme::GetImage(Theme::Image_Grip));
+ return tools::Rectangle(
+ aGripImage.GetSizePixel().Width() + gaLeftGripPadding + gaRightGripPadding,
+ rTitleBarBox.Top(),
+ rTitleBarBox.Right(),
+ rTitleBarBox.Bottom());
+}
+
+tools::Rectangle DeckTitleBar::GetDragArea()
+{
+ Image aGripImage (Theme::GetImage(Theme::Image_Grip));
+ return tools::Rectangle(0,0,
+ aGripImage.GetSizePixel().Width() + gaLeftGripPadding + gaRightGripPadding,
+ aGripImage.GetSizePixel().Height()
+ );
+}
+
+void DeckTitleBar::PaintDecoration(vcl::RenderContext& rRenderContext)
+{
+ Image aImage (Theme::GetImage(Theme::Image_Grip));
+ const Point aTopLeft(gaLeftGripPadding,
+ (GetSizePixel().Height() - aImage.GetSizePixel().Height()) / 2);
+ rRenderContext.DrawImage(aTopLeft, aImage);
+}
+
+sidebar::Paint DeckTitleBar::GetBackgroundPaint()
+{
+ return Theme::GetPaint(Theme::Paint_DeckTitleBarBackground);
+}
+
+void DeckTitleBar::HandleToolBoxItemClick (const sal_uInt16 nItemIndex)
+{
+ if (nItemIndex == mnCloserItemIndex && maCloserAction)
+ maCloserAction();
+}
+
+css::uno::Reference<css::accessibility::XAccessible> DeckTitleBar::CreateAccessible()
+{
+ SetAccessibleName(msTitle);
+ SetAccessibleDescription(msTitle);
+ return TitleBar::CreateAccessible();
+}
+
+void DeckTitleBar::DataChanged (const DataChangedEvent& rEvent)
+{
+ maToolBox->SetItemImage(
+ mnCloserItemIndex,
+ Theme::GetImage(Theme::Image_Closer));
+ TitleBar::DataChanged(rEvent);
+}
+
+
+void DeckTitleBar::MouseMove (const MouseEvent& rMouseEvent)
+{
+ tools::Rectangle aGrip = GetDragArea();
+ PointerStyle eStyle = PointerStyle::Arrow;
+
+ if ( aGrip.IsInside( rMouseEvent.GetPosPixel() ) )
+ eStyle = PointerStyle::Move;
+
+ SetPointer( eStyle );
+
+ Window::MouseMove( rMouseEvent );
+}
+
+} // end of namespace sfx2::sidebar
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/sidebar/DrawHelper.cxx b/sfx2/source/sidebar/DrawHelper.cxx
new file mode 100644
index 000000000..4c9f193ba
--- /dev/null
+++ b/sfx2/source/sidebar/DrawHelper.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/DrawHelper.hxx>
+#include <sidebar/Paint.hxx>
+
+#include <tools/svborder.hxx>
+
+namespace sfx2::sidebar {
+
+void DrawHelper::DrawBorder(vcl::RenderContext& rRenderContext, const tools::Rectangle& rBox, const SvBorder& rBorderSize,
+ const Paint& rHorizontalPaint, const Paint& rVerticalPaint)
+{
+ // Draw top line.
+ DrawHorizontalLine(rRenderContext, rBox.Left(), rBox.Right(),
+ rBox.Top(), rBorderSize.Top(), rHorizontalPaint);
+
+ // Draw bottom line.
+ DrawHorizontalLine(rRenderContext, rBox.Left() + rBorderSize.Left(), rBox.Right(),
+ rBox.Bottom() - rBorderSize.Bottom() + 1, rBorderSize.Bottom(),
+ rHorizontalPaint);
+ // Draw left line.
+ DrawVerticalLine(rRenderContext, rBox.Top() + rBorderSize.Top(), rBox.Bottom(),
+ rBox.Left(), rBorderSize.Left(), rVerticalPaint);
+ // Draw right line.
+ DrawVerticalLine(rRenderContext, rBox.Top() + rBorderSize.Top(), rBox.Bottom() - rBorderSize.Bottom(),
+ rBox.Right() - rBorderSize.Right() + 1, rBorderSize.Right(), rVerticalPaint);
+}
+
+void DrawHelper::DrawHorizontalLine(vcl::RenderContext& rRenderContext, const sal_Int32 nLeft, const sal_Int32 nRight,
+ const sal_Int32 nY, const sal_Int32 nHeight, const Paint& rPaint)
+{
+ switch (rPaint.GetType())
+ {
+ case Paint::ColorPaint:
+ {
+ const Color aColor(rPaint.GetColor());
+ rRenderContext.SetLineColor(aColor);
+ for (sal_Int32 nYOffset = 0; nYOffset < nHeight; ++nYOffset)
+ {
+ rRenderContext.DrawLine(Point(nLeft, nY + nYOffset),
+ Point(nRight, nY + nYOffset));
+ }
+ break;
+ }
+ case Paint::GradientPaint:
+ rRenderContext.DrawGradient(tools::Rectangle(nLeft, nY, nRight, nY + nHeight - 1),
+ rPaint.GetGradient());
+ break;
+
+ case Paint::NoPaint:
+ default:
+ break;
+ }
+}
+
+void DrawHelper::DrawVerticalLine(vcl::RenderContext& rRenderContext, const sal_Int32 nTop, const sal_Int32 nBottom,
+ const sal_Int32 nX, const sal_Int32 nWidth, const Paint& rPaint)
+{
+ switch (rPaint.GetType())
+ {
+ case Paint::ColorPaint:
+ {
+ const Color aColor(rPaint.GetColor());
+ rRenderContext.SetLineColor(aColor);
+ for (sal_Int32 nXOffset = 0; nXOffset < nWidth; ++nXOffset)
+ {
+ rRenderContext.DrawLine(Point(nX + nXOffset, nTop),
+ Point(nX + nXOffset, nBottom));
+ }
+ break;
+ }
+ case Paint::GradientPaint:
+ rRenderContext.DrawGradient(tools::Rectangle(nX, nTop, nX + nWidth - 1, nBottom),
+ rPaint.GetGradient());
+ break;
+
+ case Paint::NoPaint:
+ default:
+ break;
+ }
+}
+
+void DrawHelper::DrawRoundedRectangle(vcl::RenderContext& rRenderContext, const tools::Rectangle& rBox, const sal_Int32 nCornerRadius,
+ const Color& rBorderColor, const Paint& rFillPaint)
+{
+ rRenderContext.SetLineColor(rBorderColor);
+ switch (rFillPaint.GetType())
+ {
+ case Paint::ColorPaint:
+ rRenderContext.SetFillColor(rFillPaint.GetColor());
+ rRenderContext.DrawRect(rBox, nCornerRadius, nCornerRadius);
+ break;
+
+ case Paint::GradientPaint:
+ rRenderContext.DrawGradient(rBox, rFillPaint.GetGradient());
+ rRenderContext.SetFillColor();
+ rRenderContext.DrawRect(rBox, nCornerRadius, nCornerRadius);
+ break;
+
+ case Paint::NoPaint:
+ default:
+ rRenderContext.SetFillColor();
+ rRenderContext.DrawRect(rBox, nCornerRadius, nCornerRadius);
+ break;
+ }
+}
+
+} // 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 000000000..4a5140c1a
--- /dev/null
+++ b/sfx2/source/sidebar/FocusManager.cxx
@@ -0,0 +1,633 @@
+/* -*- 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/FocusManager.hxx>
+#include <sfx2/sidebar/Panel.hxx>
+#include <sidebar/DeckTitleBar.hxx>
+#include <sidebar/PanelTitleBar.hxx>
+#include <sidebar/TitleBar.hxx>
+#include <vcl/button.hxx>
+#include <vcl/event.hxx>
+#include <vcl/toolbox.hxx>
+#include <vcl/svapp.hxx>
+#include <toolkit/helper/vclunohelper.hxx>
+
+namespace sfx2::sidebar {
+
+FocusManager::FocusLocation::FocusLocation (const PanelComponent eComponent, const sal_Int32 nIndex)
+ : meComponent(eComponent),
+ mnIndex(nIndex)
+{
+}
+
+FocusManager::FocusManager(const std::function<void(const Panel&)>& rShowPanelFunctor,
+ const std::function<bool(const sal_Int32)>& rIsDeckOpenFunctor)
+ : mpDeckTitleBar(),
+ maPanels(),
+ maButtons(),
+ maShowPanelFunctor(rShowPanelFunctor),
+ mbIsDeckOpenFunctor(rIsDeckOpenFunctor)
+{
+}
+
+FocusManager::~FocusManager()
+{
+ Clear();
+}
+
+void FocusManager::GrabFocus()
+{
+ FocusDeckTitle();
+}
+
+void FocusManager::GrabFocusPanel()
+{
+ FocusPanel(0, false);
+}
+
+void FocusManager::Clear()
+{
+ SetDeckTitle(nullptr);
+ ClearPanels();
+ ClearButtons();
+}
+
+void FocusManager::ClearPanels()
+{
+ std::vector<VclPtr<Panel> > aPanels;
+ aPanels.swap(maPanels);
+ for (auto const& panel : aPanels)
+ {
+ UnregisterWindow(*panel);
+ if (panel->GetTitleBar())
+ {
+ UnregisterWindow(*panel->GetTitleBar());
+ UnregisterWindow(panel->GetTitleBar()->GetToolBox());
+ }
+
+ panel->RemoveChildEventListener(LINK(this, FocusManager, ChildEventListener));
+ }
+}
+
+void FocusManager::ClearButtons()
+{
+ std::vector<VclPtr<Button> > aButtons;
+ aButtons.swap(maButtons);
+ for (auto const& button : aButtons)
+ {
+ UnregisterWindow(*button);
+ }
+}
+
+void FocusManager::SetDeckTitle (DeckTitleBar* pDeckTitleBar)
+{
+ if (mpDeckTitleBar != nullptr)
+ {
+ UnregisterWindow(*mpDeckTitleBar);
+ UnregisterWindow(mpDeckTitleBar->GetToolBox());
+ }
+ mpDeckTitleBar = pDeckTitleBar;
+
+ if (mpDeckTitleBar != nullptr)
+ {
+ RegisterWindow(*mpDeckTitleBar);
+ RegisterWindow(mpDeckTitleBar->GetToolBox());
+ }
+}
+
+void FocusManager::SetPanels (const SharedPanelContainer& rPanels)
+{
+ ClearPanels();
+ for (auto const& panel : rPanels)
+ {
+ RegisterWindow(*panel);
+ if (panel->GetTitleBar())
+ {
+ RegisterWindow(*panel->GetTitleBar());
+ RegisterWindow(panel->GetTitleBar()->GetToolBox());
+ }
+
+ // Register also as child event listener at the panel.
+ panel->AddChildEventListener(LINK(this, FocusManager, ChildEventListener));
+
+ maPanels.emplace_back(panel.get());
+ }
+}
+
+void FocusManager::SetButtons (const ::std::vector<Button*>& rButtons)
+{
+ ClearButtons();
+ for (auto const& button : rButtons)
+ {
+ RegisterWindow(*button);
+ maButtons.emplace_back(button);
+ }
+}
+
+void FocusManager::RegisterWindow (vcl::Window& rWindow)
+{
+ rWindow.AddEventListener(LINK(this, FocusManager, WindowEventListener));
+}
+
+void FocusManager::UnregisterWindow (vcl::Window& rWindow)
+{
+ rWindow.RemoveEventListener(LINK(this, FocusManager, WindowEventListener));
+}
+
+FocusManager::FocusLocation FocusManager::GetFocusLocation (const vcl::Window& rWindow) const
+{
+ // Check the deck title.
+ if (mpDeckTitleBar != nullptr)
+ {
+ if (mpDeckTitleBar == &rWindow)
+ return FocusLocation(PC_DeckTitle, -1);
+ else if (&mpDeckTitleBar->GetToolBox() == &rWindow)
+ return FocusLocation(PC_DeckToolBox, -1);
+ }
+
+ // Search the panels.
+ for (size_t nIndex = 0; nIndex < maPanels.size(); ++nIndex)
+ {
+ if (maPanels[nIndex] == &rWindow)
+ return FocusLocation(PC_PanelContent, nIndex);
+ VclPtr<TitleBar> pTitleBar = maPanels[nIndex]->GetTitleBar();
+ if (pTitleBar == &rWindow)
+ return FocusLocation(PC_PanelTitle, nIndex);
+ if (pTitleBar!=nullptr && &pTitleBar->GetToolBox()==&rWindow)
+ return FocusLocation(PC_PanelToolBox, nIndex);
+ }
+
+ // Search the buttons.
+ for (size_t nIndex=0; nIndex < maButtons.size(); ++nIndex)
+ {
+ if (maButtons[nIndex] == &rWindow)
+ return FocusLocation(PC_TabBar, nIndex);
+ }
+ return FocusLocation(PC_None, -1);
+}
+
+void FocusManager::FocusDeckTitle()
+{
+ if (mpDeckTitleBar != nullptr)
+ {
+ if (IsDeckTitleVisible())
+ {
+ mpDeckTitleBar->GrabFocus();
+ }
+ else if (mpDeckTitleBar->GetToolBox().GetItemCount() > 0)
+ {
+ ToolBox& rToolBox = mpDeckTitleBar->GetToolBox();
+ rToolBox.GrabFocus();
+ rToolBox.Invalidate();
+ }
+ else
+ FocusPanel(0, false);
+ }
+ else
+ FocusPanel(0, false);
+}
+
+bool FocusManager::IsDeckTitleVisible() const
+{
+ return mpDeckTitleBar != nullptr && mpDeckTitleBar->IsVisible();
+}
+
+bool FocusManager::IsPanelTitleVisible (const sal_Int32 nPanelIndex) const
+{
+ if (nPanelIndex<0 || nPanelIndex>=static_cast<sal_Int32>(maPanels.size()))
+ return false;
+
+ VclPtr<TitleBar> pTitleBar = maPanels[nPanelIndex]->GetTitleBar();
+ if (!pTitleBar)
+ return false;
+ return pTitleBar->IsVisible();
+}
+
+void FocusManager::FocusPanel (
+ const sal_Int32 nPanelIndex,
+ const bool bFallbackToDeckTitle)
+{
+ if (nPanelIndex<0 || nPanelIndex>=static_cast<sal_Int32>(maPanels.size()))
+ {
+ if (bFallbackToDeckTitle)
+ FocusDeckTitle();
+ return;
+ }
+
+ Panel& rPanel (*maPanels[nPanelIndex]);
+ VclPtr<TitleBar> pTitleBar = rPanel.GetTitleBar();
+ if (pTitleBar && pTitleBar->IsVisible())
+ {
+ rPanel.SetExpanded(true);
+ pTitleBar->GrabFocus();
+ }
+ else if (bFallbackToDeckTitle)
+ {
+ // 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);
+
+ VclPtr<vcl::Window> pWindow = VCLUnoHelper::GetWindow(maPanels[nPanelIndex]->GetElementWindow());
+ if (pWindow)
+ pWindow->GrabFocus();
+}
+
+void FocusManager::FocusButton (const sal_Int32 nButtonIndex)
+{
+ maButtons[nButtonIndex]->GrabFocus();
+ maButtons[nButtonIndex]->Invalidate();
+}
+
+void FocusManager::ClickButton (const sal_Int32 nButtonIndex)
+{
+ if (mbIsDeckOpenFunctor)
+ {
+ if (!mbIsDeckOpenFunctor(-1) || !mbIsDeckOpenFunctor(nButtonIndex-1))
+ maButtons[nButtonIndex]->Click();
+ }
+ if (nButtonIndex > 0)
+ FocusPanel(0, true);
+ maButtons[nButtonIndex]->GetParent()->Invalidate();
+}
+
+void FocusManager::RemoveWindow (vcl::Window& rWindow)
+{
+ auto iPanel (::std::find(maPanels.begin(), maPanels.end(), &rWindow));
+ if (iPanel != maPanels.end())
+ {
+ UnregisterWindow(rWindow);
+ if ((*iPanel)->GetTitleBar() != nullptr)
+ {
+ UnregisterWindow(*(*iPanel)->GetTitleBar());
+ UnregisterWindow((*iPanel)->GetTitleBar()->GetToolBox());
+ }
+ maPanels.erase(iPanel);
+ return;
+ }
+
+ auto iButton (::std::find(maButtons.begin(), maButtons.end(), &rWindow));
+ if (iButton != maButtons.end())
+ {
+ UnregisterWindow(rWindow);
+ maButtons.erase(iButton);
+ return;
+ }
+}
+
+void FocusManager::MoveFocusInsidePanel (
+ const FocusLocation& rFocusLocation,
+ const sal_Int32 nDirection)
+{
+ const bool bHasToolBoxItem (
+ maPanels[rFocusLocation.mnIndex]->GetTitleBar()->GetToolBox().GetItemCount() > 0);
+ switch (rFocusLocation.meComponent)
+ {
+ case PC_PanelTitle:
+ if (nDirection > 0 && bHasToolBoxItem)
+ maPanels[rFocusLocation.mnIndex]->GetTitleBar()->GetToolBox().GrabFocus();
+ else
+ FocusPanelContent(rFocusLocation.mnIndex);
+ break;
+
+ case PC_PanelToolBox:
+ if (nDirection < 0 && bHasToolBoxItem)
+ maPanels[rFocusLocation.mnIndex]->GetTitleBar()->GrabFocus();
+ else
+ FocusPanelContent(rFocusLocation.mnIndex);
+ break;
+
+ default: break;
+ }
+}
+
+void FocusManager::MoveFocusInsideDeckTitle (
+ const FocusLocation& rFocusLocation,
+ const sal_Int32 nDirection)
+{
+ // Note that when the title bar of the first (and only) panel is
+ // not visible then the deck title takes its place and the focus
+ // is moved between a) deck title, b) deck closer and c) content
+ // of panel 0.
+ const bool bHasToolBoxItem (
+ mpDeckTitleBar->GetToolBox().GetItemCount() > 0);
+ switch (rFocusLocation.meComponent)
+ {
+ case PC_DeckTitle:
+ if (nDirection<0 && ! IsPanelTitleVisible(0))
+ FocusPanelContent(0);
+ else if (bHasToolBoxItem)
+ mpDeckTitleBar->GetToolBox().GrabFocus();
+ break;
+
+ case PC_DeckToolBox:
+ if (nDirection>0 && ! IsPanelTitleVisible(0))
+ FocusPanelContent(0);
+ else
+ mpDeckTitleBar->GrabFocus();
+ break;
+
+ default: break;
+ }
+}
+
+void FocusManager::HandleKeyEvent (
+ const vcl::KeyCode& rKeyCode,
+ const vcl::Window& rWindow)
+{
+ const FocusLocation aLocation (GetFocusLocation(rWindow));
+
+ switch (rKeyCode.GetCode())
+ {
+ case KEY_ESCAPE:
+ switch (aLocation.meComponent)
+ {
+ case PC_TabBar:
+ case PC_DeckTitle:
+ case PC_DeckToolBox:
+ case PC_PanelTitle:
+ case PC_PanelToolBox:
+ {
+ vcl::Window* pFocusWin = Application::GetFocusWindow();
+ if (pFocusWin)
+ pFocusWin->GrabFocusToDocument();
+ break;
+ }
+
+ default:
+ break;
+ }
+ return;
+
+ case KEY_SPACE:
+ switch (aLocation.meComponent)
+ {
+ case PC_PanelTitle:
+ // Toggle panel between expanded and collapsed.
+ maPanels[aLocation.mnIndex]->SetExpanded( ! maPanels[aLocation.mnIndex]->IsExpanded());
+ maPanels[aLocation.mnIndex]->GetTitleBar()->Invalidate();
+ break;
+
+ default:
+ break;
+ }
+ return;
+
+ case KEY_RETURN:
+ switch (aLocation.meComponent)
+ {
+ case PC_DeckToolBox:
+ FocusButton(0);
+ break;
+
+ case PC_PanelTitle:
+ // Enter the panel.
+ FocusPanelContent(aLocation.mnIndex);
+ break;
+
+ case PC_TabBar:
+ // Activate the button.
+ ClickButton(aLocation.mnIndex);
+ break;
+
+ default:
+ break;
+ }
+ return;
+
+ case KEY_TAB:
+ {
+ const sal_Int32 nDirection (
+ rKeyCode.IsShift()
+ ? -1
+ : +1);
+ switch (aLocation.meComponent)
+ {
+ case PC_PanelTitle:
+ case PC_PanelToolBox:
+ case PC_PanelContent:
+ MoveFocusInsidePanel(aLocation, nDirection);
+ break;
+
+ case PC_DeckTitle:
+ case PC_DeckToolBox:
+ MoveFocusInsideDeckTitle(aLocation, nDirection);
+ break;
+
+ default:
+ break;
+ }
+ break;
+ }
+
+ case KEY_LEFT:
+ case KEY_UP:
+ switch (aLocation.meComponent)
+ {
+ case PC_PanelTitle:
+ case PC_PanelToolBox:
+ case PC_PanelContent:
+ // Go to previous panel or the deck title.
+ if (aLocation.mnIndex > 0)
+ FocusPanel(aLocation.mnIndex-1, true);
+ else if (IsDeckTitleVisible())
+ FocusDeckTitle();
+ else
+ {
+ // Focus the last button.
+ sal_Int32 nIndex(maButtons.size()-1);
+ while(!maButtons[nIndex]->IsVisible() && --nIndex > 0);
+ FocusButton(nIndex);
+ }
+ break;
+
+ case PC_DeckTitle:
+ case PC_DeckToolBox:
+ {
+ // Focus the last button.
+ sal_Int32 nIndex(maButtons.size()-1);
+ while(!maButtons[nIndex]->IsVisible() && --nIndex > 0);
+ FocusButton(nIndex);
+ break;
+ }
+
+ case PC_TabBar:
+ // Go to previous tab bar item.
+ if (aLocation.mnIndex == 0)
+ FocusPanel(maPanels.size()-1, true);
+ else
+ {
+ sal_Int32 nIndex((aLocation.mnIndex + maButtons.size() - 1) % maButtons.size());
+ while(!maButtons[nIndex]->IsVisible() && --nIndex > 0);
+ FocusButton(nIndex);
+ }
+ break;
+
+ default:
+ break;
+ }
+ break;
+
+ case KEY_RIGHT:
+ case KEY_DOWN:
+ switch(aLocation.meComponent)
+ {
+ case PC_PanelTitle:
+ case PC_PanelToolBox:
+ case PC_PanelContent:
+ // Go to next panel.
+ if (aLocation.mnIndex < static_cast<sal_Int32>(maPanels.size())-1)
+ FocusPanel(aLocation.mnIndex+1, false);
+ else
+ FocusButton(0);
+ break;
+
+ case PC_DeckTitle:
+ case PC_DeckToolBox:
+ // Focus the first panel.
+ if (IsPanelTitleVisible(0))
+ FocusPanel(0, false);
+ else
+ FocusButton(0);
+ break;
+
+ case PC_TabBar:
+ // Go to next tab bar item.
+ if (aLocation.mnIndex < static_cast<sal_Int32>(maButtons.size())-1)
+ {
+ sal_Int32 nIndex(aLocation.mnIndex + 1);
+ while(!maButtons[nIndex]->IsVisible() && ++nIndex < static_cast<sal_Int32>(maButtons.size()));
+ if (nIndex < static_cast<sal_Int32>(maButtons.size()))
+ {
+ FocusButton(nIndex);
+ break;
+ }
+ }
+ if (IsDeckTitleVisible())
+ FocusDeckTitle();
+ else
+ FocusPanel(0, true);
+ break;
+
+ default:
+ break;
+ }
+ break;
+ }
+}
+
+IMPL_LINK(FocusManager, WindowEventListener, VclWindowEvent&, rWindowEvent, void)
+{
+ vcl::Window* pSource = rWindowEvent.GetWindow();
+ if (pSource == nullptr)
+ return;
+
+ switch (rWindowEvent.GetId())
+ {
+ case VclEventId::WindowKeyInput:
+ {
+ KeyEvent* pKeyEvent = static_cast<KeyEvent*>(rWindowEvent.GetData());
+ HandleKeyEvent(pKeyEvent->GetKeyCode(), *pSource);
+ break;
+ }
+
+ case VclEventId::ObjectDying:
+ RemoveWindow(*pSource);
+ break;
+
+ case VclEventId::WindowGetFocus:
+ case VclEventId::WindowLoseFocus:
+ pSource->Invalidate();
+ break;
+
+ default:
+ break;
+ }
+}
+
+IMPL_LINK(FocusManager, ChildEventListener, VclWindowEvent&, rEvent, void)
+{
+ vcl::Window* pSource = rEvent.GetWindow();
+ if (pSource == nullptr)
+ return;
+
+ switch (rEvent.GetId())
+ {
+ case VclEventId::WindowKeyInput:
+ {
+ KeyEvent* pKeyEvent = static_cast<KeyEvent*>(rEvent.GetData());
+
+ // Go up the window hierarchy to find out whether the
+ // parent of the event source is known to us.
+ vcl::Window* pWindow = pSource;
+ FocusLocation aLocation (PC_None, -1);
+ while (true)
+ {
+ if (pWindow == nullptr)
+ break;
+ aLocation = GetFocusLocation(*pWindow);
+ if (aLocation.meComponent != PC_None)
+ break;
+ pWindow = pWindow->GetParent();
+ }
+
+ if (aLocation.meComponent != PC_None)
+ {
+ switch (pKeyEvent->GetKeyCode().GetCode())
+ {
+ case KEY_ESCAPE:
+ // Return focus to tab bar sidebar settings button or panel title.
+ if (!IsDeckTitleVisible() && maPanels.size() == 1)
+ FocusButton(0);
+ else
+ FocusPanel(aLocation.mnIndex, true);
+ break;
+
+ default:
+ break;
+ }
+ }
+ return;
+ }
+
+ default:
+ break;
+ }
+}
+
+} // 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 000000000..6fc053921
--- /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 000000000..6a24cadbf
--- /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/MenuButton.cxx b/sfx2/source/sidebar/MenuButton.cxx
new file mode 100644
index 000000000..36be7fcfc
--- /dev/null
+++ b/sfx2/source/sidebar/MenuButton.cxx
@@ -0,0 +1,102 @@
+/* -*- 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/MenuButton.hxx>
+
+#include <sidebar/DrawHelper.hxx>
+#include <sfx2/sidebar/Theme.hxx>
+#include <vcl/event.hxx>
+
+using namespace css;
+using namespace css::uno;
+
+namespace sfx2::sidebar {
+
+MenuButton::MenuButton (vcl::Window* pParentWindow)
+ : CheckBox(pParentWindow),
+ mbIsLeftButtonDown(false)
+{
+#ifdef DEBUG
+ SetText(OUString("MenuButton"));
+#endif
+}
+
+void MenuButton::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& /*rUpdateArea*/)
+{
+ const bool bIsSelected (IsChecked());
+ const bool bIsHighlighted (IsMouseOver() || HasFocus());
+ DrawHelper::DrawRoundedRectangle(
+ rRenderContext,
+ tools::Rectangle(Point(0,0), GetSizePixel()),
+ 3,
+ (bIsHighlighted || bIsSelected
+ ? Theme::GetColor(Theme::Color_TabItemBorder)
+ : Color(0xffffffff)),
+ (bIsHighlighted
+ ? Theme::GetPaint(Theme::Paint_TabItemBackgroundHighlight)
+ : Theme::GetPaint(Theme::Paint_TabItemBackgroundNormal)));
+
+ const Image aIcon(Button::GetModeImage());
+ const Size aIconSize(aIcon.GetSizePixel());
+ const Point aIconLocation((GetSizePixel().Width() - aIconSize.Width()) / 2,
+ (GetSizePixel().Height() - aIconSize.Height()) / 2);
+ rRenderContext.DrawImage(aIconLocation, aIcon);
+}
+
+void MenuButton::MouseMove (const MouseEvent& rEvent)
+{
+ if (rEvent.IsEnterWindow() || rEvent.IsLeaveWindow())
+ Invalidate();
+ CheckBox::MouseMove(rEvent);
+}
+
+void MenuButton::MouseButtonDown (const MouseEvent& rMouseEvent)
+{
+ if (rMouseEvent.IsLeft())
+ {
+ mbIsLeftButtonDown = true;
+ CaptureMouse();
+ Invalidate();
+ }
+}
+
+void MenuButton::MouseButtonUp (const MouseEvent& rMouseEvent)
+{
+ if (IsMouseCaptured())
+ ReleaseMouse();
+
+ if (rMouseEvent.IsLeft())
+ {
+ if (mbIsLeftButtonDown)
+ {
+ Check();
+ Click();
+ GetParent()->Invalidate();
+ }
+ }
+ if (mbIsLeftButtonDown)
+ {
+ mbIsLeftButtonDown = false;
+ Invalidate();
+ }
+}
+
+} // end of namespace sfx2::sidebar
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/sidebar/Paint.cxx b/sfx2/source/sidebar/Paint.cxx
new file mode 100644
index 000000000..1ab8b5d6d
--- /dev/null
+++ b/sfx2/source/sidebar/Paint.cxx
@@ -0,0 +1,102 @@
+/* -*- 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/Paint.hxx>
+#include <sidebar/Tools.hxx>
+#include <com/sun/star/awt/Gradient.hpp>
+
+using namespace css;
+
+namespace sfx2::sidebar {
+
+Paint::Paint()
+ : meType(NoPaint)
+{
+}
+
+Paint::Paint (const Color& rColor)
+ : meType(ColorPaint),
+ maValue(rColor)
+{
+}
+
+Paint::Paint (const Gradient& rGradient)
+ : meType(GradientPaint),
+ maValue(rGradient)
+{
+}
+
+Paint Paint::Create (const css::uno::Any& rValue)
+{
+ Color aColor (0);
+ if (rValue >>= aColor)
+ return Paint(aColor);
+
+ awt::Gradient aAwtGradient;
+ if (rValue >>= aAwtGradient)
+ return Paint(Tools::AwtToVclGradient(aAwtGradient));
+
+ return Paint();
+}
+
+const Color& Paint::GetColor() const
+{
+ if (meType != ColorPaint)
+ {
+ assert(meType==ColorPaint);
+ static const Color aErrorColor;
+ return aErrorColor;
+ }
+ else
+ return ::boost::get<Color>(maValue);
+}
+
+const Gradient& Paint::GetGradient() const
+{
+ if (meType != GradientPaint)
+ {
+ assert(meType==GradientPaint);
+ static Gradient aErrorGradient;
+ return aErrorGradient;
+ }
+ else
+ return ::boost::get<Gradient>(maValue);
+}
+
+Wallpaper Paint::GetWallpaper() const
+{
+ switch (meType)
+ {
+ case Paint::NoPaint:
+ default:
+ return Wallpaper();
+ break;
+
+ case Paint::ColorPaint:
+ return Wallpaper(GetColor());
+ break;
+
+ case Paint::GradientPaint:
+ return Wallpaper(GetGradient());
+ break;
+ }
+}
+
+} // 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 000000000..85b6ebf14
--- /dev/null
+++ b/sfx2/source/sidebar/Panel.cxx
@@ -0,0 +1,191 @@
+/* -*- 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/Theme.hxx>
+#include <sidebar/Paint.hxx>
+#include <sfx2/sidebar/ResourceManager.hxx>
+
+#include <sfx2/sidebar/SidebarController.hxx>
+
+
+#ifdef DEBUG
+#include <sfx2/sidebar/Tools.hxx>
+#include <sfx2/sidebar/Deck.hxx>
+#endif
+
+#include <com/sun/star/awt/PosSize.hpp>
+#include <com/sun/star/ui/XToolPanel.hpp>
+#include <com/sun/star/ui/XSidebarPanel.hpp>
+#include <com/sun/star/ui/XUIElement.hpp>
+
+#include <boost/property_tree/ptree.hpp>
+
+using namespace css;
+using namespace css::uno;
+
+namespace sfx2::sidebar {
+
+Panel::Panel(const PanelDescriptor& rPanelDescriptor,
+ vcl::Window* pParentWindow,
+ const bool bIsInitiallyExpanded,
+ const std::function<void()>& rDeckLayoutTrigger,
+ const std::function<Context()>& rContextAccess,
+ const css::uno::Reference<css::frame::XFrame>& rxFrame
+ )
+ : Window(pParentWindow)
+ , msPanelId(rPanelDescriptor.msId)
+ , mpTitleBar(VclPtr<PanelTitleBar>::Create(rPanelDescriptor.msTitle, pParentWindow, this))
+ , mbIsTitleBarOptional(rPanelDescriptor.mbIsTitleBarOptional)
+ , mxElement()
+ , mxPanelComponent()
+ , mbIsExpanded(bIsInitiallyExpanded)
+ , mbLurking(false)
+ , maDeckLayoutTrigger(rDeckLayoutTrigger)
+ , maContextAccess(rContextAccess)
+ , mxFrame(rxFrame)
+{
+ SetText(rPanelDescriptor.msTitle);
+}
+
+Panel::~Panel()
+{
+ disposeOnce();
+ assert(!mpTitleBar);
+}
+
+void Panel::SetLurkMode(bool bLurk)
+{
+ // cf. DeckLayouter
+ mbLurking = bLurk;
+}
+
+void Panel::ApplySettings(vcl::RenderContext& rRenderContext)
+{
+ rRenderContext.SetBackground(Theme::GetPaint(Theme::Paint_PanelBackground).GetWallpaper());
+}
+
+boost::property_tree::ptree Panel::DumpAsPropertyTree()
+{
+ if (!IsLurking())
+ {
+ boost::property_tree::ptree aTree(vcl::Window::DumpAsPropertyTree());
+ aTree.put("type", "panel");
+ return aTree;
+ }
+ else
+ return boost::property_tree::ptree();
+}
+
+void Panel::dispose()
+{
+ 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();
+ }
+
+ mpTitleBar.disposeAndClear();
+
+ vcl::Window::dispose();
+}
+
+VclPtr<PanelTitleBar> const & Panel::GetTitleBar() const
+{
+ return mpTitleBar;
+}
+
+void Panel::SetUIElement (const Reference<ui::XUIElement>& rxElement)
+{
+ mxElement = rxElement;
+ if (mxElement.is())
+ {
+ mxPanelComponent.set(mxElement->getRealInterface(), UNO_QUERY);
+ }
+}
+
+void Panel::SetExpanded (const bool bIsExpanded)
+{
+ SidebarController* pSidebarController = SidebarController::GetSidebarControllerForFrame(mxFrame);
+
+ if (mbIsExpanded == bIsExpanded)
+ return;
+
+ mbIsExpanded = bIsExpanded;
+ maDeckLayoutTrigger();
+
+ if (maContextAccess && pSidebarController)
+ {
+ pSidebarController->GetResourceManager()->StorePanelExpansionState(
+ msPanelId,
+ bIsExpanded,
+ maContextAccess());
+ }
+}
+
+bool Panel::HasIdPredicate (const OUString& rsId) const
+{
+ return msPanelId == rsId;
+}
+
+void Panel::Resize()
+{
+ Window::Resize();
+
+ // Forward new size to window of XUIElement.
+ Reference<awt::XWindow> xElementWindow (GetElementWindow());
+ if(xElementWindow.is())
+ {
+ const Size aSize(GetSizePixel());
+ xElementWindow->setPosSize(0, 0, aSize.Width(), aSize.Height(),
+ awt::PosSize::POSSIZE);
+ }
+}
+
+void Panel::DataChanged (const DataChangedEvent&)
+{
+ Invalidate();
+}
+
+Reference<awt::XWindow> Panel::GetElementWindow()
+{
+ if (mxElement.is())
+ {
+ Reference<ui::XToolPanel> xToolPanel(mxElement->getRealInterface(), UNO_QUERY);
+ if (xToolPanel.is())
+ return xToolPanel->getWindow();
+ }
+
+ return nullptr;
+}
+
+} // 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 000000000..bdbec773f
--- /dev/null
+++ b/sfx2/source/sidebar/PanelDescriptor.cxx
@@ -0,0 +1,62 @@
+/* -*- 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()
+ : msTitle(),
+ mbIsTitleBarOptional(false),
+ msId(),
+ msDeckId(),
+ msTitleBarIconURL(),
+ msHighContrastTitleBarIconURL(),
+ maContextList(),
+ msImplementationURL(),
+ mnOrderIndex(10000), // Default value as defined in Sidebar.xcs
+ mbShowForReadOnlyDocuments(false),
+ mbWantsCanvas(false),
+ 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),
+ 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 000000000..6a3338192
--- /dev/null
+++ b/sfx2/source/sidebar/PanelLayout.cxx
@@ -0,0 +1,121 @@
+/* -*- 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/SidebarController.hxx>
+#include <sfx2/sidebar/TabBar.hxx>
+#include <sfx2/sidebar/PanelLayout.hxx>
+#include <vcl/layout.hxx>
+#include <vcl/accel.hxx>
+
+using namespace sfx2::sidebar;
+
+PanelLayout::PanelLayout(vcl::Window* pParent, const OString& rID, const OUString& rUIXMLDescription,
+ const css::uno::Reference<css::frame::XFrame> &rFrame)
+ : Control(pParent)
+ , m_bInClose(false)
+ , mxFrame(rFrame)
+{
+ m_aPanelLayoutIdle.SetPriority(TaskPriority::RESIZE);
+ m_aPanelLayoutIdle.SetInvokeHandler( LINK( this, PanelLayout, ImplHandlePanelLayoutTimerHdl ) );
+ m_aPanelLayoutIdle.SetDebugName("sfx2::PanelLayout m_aPanelLayoutIdle");
+
+ SetStyle(GetStyle() | WB_DIALOGCONTROL);
+
+ // Builder will trigger resize and start Idle
+ m_xVclContentArea = VclPtr<VclVBox>::Create(this);
+ m_xVclContentArea->Show();
+ m_xBuilder.reset(Application::CreateInterimBuilder(m_xVclContentArea, rUIXMLDescription));
+ m_xContainer = m_xBuilder->weld_container(rID);
+}
+
+PanelLayout::~PanelLayout()
+{
+ disposeOnce();
+}
+
+void PanelLayout::dispose()
+{
+ m_bInClose = true;
+ m_aPanelLayoutIdle.Stop();
+ m_xContainer.reset();
+ m_xBuilder.reset();
+ m_xVclContentArea.disposeAndClear();
+ Control::dispose();
+}
+
+Size PanelLayout::GetOptimalSize() const
+{
+ Size aSize = m_xContainer->get_preferred_size();
+
+ if (mxFrame)
+ {
+ SidebarController* pController
+ = SidebarController::GetSidebarControllerForFrame(mxFrame);
+ if (pController)
+ aSize.setWidth(std::min<long>(
+ aSize.Width(), (pController->getMaximumWidth() - TabBar::GetDefaultWidth())
+ * GetDPIScaleFactor()));
+ }
+
+ return aSize;
+}
+
+void PanelLayout::queue_resize(StateChangedType /*eReason*/)
+{
+ if (m_bInClose)
+ return;
+ if (m_aPanelLayoutIdle.IsActive())
+ return;
+ InvalidateSizeCache();
+ m_aPanelLayoutIdle.Start();
+}
+
+IMPL_LINK_NOARG( PanelLayout, ImplHandlePanelLayoutTimerHdl, Timer*, void )
+{
+ vcl::Window *pChild = GetWindow(GetWindowType::FirstChild);
+ assert(pChild);
+ VclContainer::setLayoutAllocation(*pChild, Point(0, 0), GetSizePixel());
+}
+
+void PanelLayout::setPosSizePixel(long nX, long nY, long nWidth, long nHeight, PosSizeFlags nFlags)
+{
+ bool bCanHandleSmallerWidth = false;
+ bool bCanHandleSmallerHeight = false;
+
+ vcl::Window *pChild = GetWindow(GetWindowType::FirstChild);
+
+ if (pChild->GetType() == WindowType::SCROLLWINDOW)
+ {
+ WinBits nStyle = pChild->GetStyle();
+ if (nStyle & (WB_AUTOHSCROLL | WB_HSCROLL))
+ bCanHandleSmallerWidth = true;
+ if (nStyle & (WB_AUTOVSCROLL | WB_VSCROLL))
+ bCanHandleSmallerHeight = true;
+ }
+
+ Size aSize(GetOptimalSize());
+ if (!bCanHandleSmallerWidth)
+ nWidth = std::max(nWidth,aSize.Width());
+ if (!bCanHandleSmallerHeight)
+ nHeight = std::max(nHeight,aSize.Height());
+
+ Control::setPosSizePixel(nX, nY, nWidth, nHeight, nFlags);
+
+ if (nFlags & PosSizeFlags::Size)
+ VclContainer::setLayoutAllocation(*pChild, Point(0, 0), Size(nWidth, nHeight));
+}
+
+bool PanelLayout::EventNotify(NotifyEvent& rNEvt)
+{
+ if (rNEvt.GetType() == MouseNotifyEvent::COMMAND)
+ Accelerator::ToggleMnemonicsOnHierarchy(*rNEvt.GetCommandEvent(), this);
+ return Control::EventNotify( rNEvt );
+}
+
+/* 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 000000000..e8613f6f9
--- /dev/null
+++ b/sfx2/source/sidebar/PanelTitleBar.cxx
@@ -0,0 +1,207 @@
+/* -*- 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/sfxresid.hxx>
+#include <sfx2/strings.hrc>
+#include <sidebar/Paint.hxx>
+#include <sfx2/sidebar/Panel.hxx>
+#include <sfx2/sidebar/Theme.hxx>
+#include <sidebar/ControllerFactory.hxx>
+#include <sidebar/Tools.hxx>
+#include <vcl/event.hxx>
+#include <vcl/image.hxx>
+#include <toolkit/helper/vclunohelper.hxx>
+#include <tools/diagnose_ex.h>
+
+#include <com/sun/star/frame/XDispatch.hpp>
+
+using namespace css;
+using namespace css::uno;
+
+namespace sfx2::sidebar {
+
+static const sal_Int32 gaLeftIconPadding (5);
+static const sal_Int32 gaRightIconPadding (5);
+
+PanelTitleBar::PanelTitleBar(const OUString& rsTitle,
+ vcl::Window* pParentWindow,
+ Panel* pPanel)
+ : TitleBar(rsTitle, pParentWindow, GetBackgroundPaint()),
+ mbIsLeftButtonDown(false),
+ mpPanel(pPanel),
+ mxFrame(),
+ msMoreOptionsCommand()
+{
+ OSL_ASSERT(mpPanel != nullptr);
+
+#ifdef DEBUG
+ SetText(OUString("PanelTitleBar"));
+#endif
+}
+
+PanelTitleBar::~PanelTitleBar()
+{
+ disposeOnce();
+}
+
+void PanelTitleBar::dispose()
+{
+ mpPanel.clear();
+ TitleBar::dispose();
+}
+
+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.getLength() > 0)
+ maToolBox->RemoveItem(maToolBox->GetItemPos(mnMenuItemIndex));
+
+ msMoreOptionsCommand = rsCommandName;
+ mxFrame = rxFrame;
+
+ if (msMoreOptionsCommand.getLength() <= 0)
+ return;
+
+ maToolBox->InsertItem(
+ mnMenuItemIndex,
+ Theme::GetImage(Theme::Image_PanelMenu));
+ Reference<frame::XToolbarController> xController (
+ ControllerFactory::CreateToolBoxController(
+ maToolBox.get(),
+ mnMenuItemIndex,
+ msMoreOptionsCommand,
+ rxFrame, rxController,
+ VCLUnoHelper::GetInterface(maToolBox.get()),
+ 0, true));
+ maToolBox->SetController(mnMenuItemIndex, xController);
+ maToolBox->SetOutStyle(TOOLBOX_STYLE_FLAT);
+ maToolBox->SetQuickHelpText(
+ mnMenuItemIndex,
+ SfxResId(SFX_STR_SIDEBAR_MORE_OPTIONS));
+}
+
+tools::Rectangle PanelTitleBar::GetTitleArea (const tools::Rectangle& rTitleBarBox)
+{
+ if (mpPanel != nullptr)
+ {
+ Image aImage (mpPanel->IsExpanded()
+ ? Theme::GetImage(Theme::Image_Expand)
+ : Theme::GetImage(Theme::Image_Collapse));
+ return tools::Rectangle(
+ aImage.GetSizePixel().Width() + gaLeftIconPadding + gaRightIconPadding,
+ rTitleBarBox.Top(),
+ rTitleBarBox.Right(),
+ rTitleBarBox.Bottom());
+ }
+ else
+ return rTitleBarBox;
+}
+
+void PanelTitleBar::PaintDecoration (vcl::RenderContext& rRenderContext)
+{
+ if (mpPanel != nullptr)
+ {
+ Image aImage (mpPanel->IsExpanded()
+ ? Theme::GetImage(Theme::Image_Collapse)
+ : Theme::GetImage(Theme::Image_Expand));
+ const Point aTopLeft(gaLeftIconPadding,
+ (GetSizePixel().Height() - aImage.GetSizePixel().Height()) / 2);
+ rRenderContext.DrawImage(aTopLeft, aImage);
+ }
+}
+
+Paint PanelTitleBar::GetBackgroundPaint()
+{
+ return Theme::GetPaint(Theme::Paint_PanelTitleBarBackground);
+}
+
+void PanelTitleBar::HandleToolBoxItemClick (const sal_uInt16 nItemIndex)
+{
+ if (nItemIndex != mnMenuItemIndex)
+ return;
+
+ if (msMoreOptionsCommand.getLength() <= 0)
+ return;
+
+ try
+ {
+ const util::URL aURL (Tools::GetURL(msMoreOptionsCommand));
+ Reference<frame::XDispatch> xDispatch (Tools::GetDispatch(mxFrame, aURL));
+ if (xDispatch.is())
+ xDispatch->dispatch(aURL, Sequence<beans::PropertyValue>());
+ }
+ catch(Exception const &)
+ {
+ DBG_UNHANDLED_EXCEPTION("sfx");
+ }
+}
+
+Reference<accessibility::XAccessible> PanelTitleBar::CreateAccessible()
+{
+ SetAccessibleName(msTitle);
+ SetAccessibleDescription(msTitle);
+ return TitleBar::CreateAccessible();
+}
+
+void PanelTitleBar::MouseButtonDown (const MouseEvent& rMouseEvent)
+{
+ if (rMouseEvent.IsLeft())
+ {
+ mbIsLeftButtonDown = true;
+ CaptureMouse();
+ }
+}
+
+void PanelTitleBar::MouseButtonUp (const MouseEvent& rMouseEvent)
+{
+ if (IsMouseCaptured())
+ ReleaseMouse();
+
+ if (rMouseEvent.IsLeft())
+ {
+ if (mbIsLeftButtonDown)
+ {
+ if (mpPanel != nullptr)
+ {
+ mpPanel->SetExpanded( ! mpPanel->IsExpanded());
+ Invalidate();
+ GrabFocus();
+ }
+ }
+ }
+ if (mbIsLeftButtonDown)
+ mbIsLeftButtonDown = false;
+}
+
+void PanelTitleBar::DataChanged (const DataChangedEvent& rEvent)
+{
+ maToolBox->SetItemImage(
+ mnMenuItemIndex,
+ Theme::GetImage(Theme::Image_PanelMenu));
+ TitleBar::DataChanged(rEvent);
+}
+
+} // 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 000000000..f3440e6dc
--- /dev/null
+++ b/sfx2/source/sidebar/ResourceManager.cxx
@@ -0,0 +1,789 @@
+/* -*- 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/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 <tools/diagnose_ex.h>
+#include <sal/log.hxx>
+#include <vcl/EnumContext.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());
+ 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;
+
+ result[i] = element;
+
+ ++i;
+ }
+
+ return result;
+
+}
+
+} //end anonymous namespace
+
+ResourceManager::ResourceManager()
+ : maDecks(),
+ maPanels(),
+ maProcessedApplications(),
+ maMiscOptions()
+{
+ 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(const OUString& rsDeckId) const
+{
+ for (auto const& deck : maDecks)
+ {
+ if (deck->mbExperimental && !maMiscOptions.IsExperimentalMode())
+ continue;
+ if (deck->msId == rsDeckId)
+ return deck;
+ }
+ return nullptr;
+}
+
+std::shared_ptr<DeckDescriptor> ResourceManager::GetDeckDescriptor(const OUString& rsDeckId) const
+{
+ return ImplGetDeckDescriptor( rsDeckId );
+}
+
+std::shared_ptr<PanelDescriptor> ResourceManager::ImplGetPanelDescriptor(const OUString& rsPanelId) const
+{
+ for (auto const& panel : maPanels)
+ {
+ if (panel->msId == rsPanelId)
+ return panel;
+ }
+ return nullptr;
+}
+
+std::shared_ptr<PanelDescriptor> ResourceManager::GetPanelDescriptor(const OUString& 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 && !maMiscOptions.IsExperimentalMode())
+ 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,
+ const OUString& 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 && !maMiscOptions.IsExperimentalMode())
+ 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 == "NavigatorDeck"
+ || 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.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(makeAny(pDeckDesc->msTitle));
+ css::uno::Any aOrder(makeAny(pDeckDesc->mnOrderIndex));
+ css::uno::Any aContextList(makeAny(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;
+
+ 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.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 ) );
+ }
+}
+
+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 (sValue.getToken(0, ',', nCharacterIndex).trim());
+ 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(sValue.getToken(0, ',', nCharacterIndex).trim());
+ if (nCharacterIndex < 0)
+ {
+ OSL_FAIL("expecting three or four values per ContextList entry, separated by comma");
+ continue;
+ }
+
+ const OUString sInitialState(sValue.getToken(0, ',', nCharacterIndex).trim());
+
+ // The fourth argument is optional.
+ const OUString sMenuCommandOverride(
+ nCharacterIndex < 0
+ ? OUString()
+ : sValue.getToken(0, ',', nCharacterIndex).trim());
+
+ 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 == "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 == "visible")
+ bIsInitiallyVisible = true;
+ else if (sInitialState == "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.maContextList.AddContextDescription(Context(sModuleName, "any"), true, OUString());
+ }
+}
+
+void ResourceManager::StorePanelExpansionState (
+ const OUString& 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)
+{
+ 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 (
+ const OUString& 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 000000000..6f205389e
--- /dev/null
+++ b/sfx2/source/sidebar/Sidebar.cxx
@@ -0,0 +1,93 @@
+/* -*- 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>
+
+using namespace css;
+
+namespace sfx2::sidebar {
+
+void Sidebar::ShowPanel (
+ const OUString& 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 (
+ const OUString& 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(
+ const OUString& 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 000000000..e75687dfe
--- /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 | WB_ROLLABLE);
+ 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")
+ {
+ // 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() * GetWindow()->GetDPIScaleFactor(),
+ 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() * GetWindow()->GetDPIScaleFactor(),
+ 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() * pWindow->GetDPIScaleFactor();
+ }
+ 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 000000000..08606a83f
--- /dev/null
+++ b/sfx2/source/sidebar/SidebarController.cxx
@@ -0,0 +1,1617 @@
+/* -*- 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 <sfx2/sidebar/Context.hxx>
+
+
+#include <sfx2/lokhelper.hxx>
+#include <sfx2/sfxresid.hxx>
+#include <sfx2/strings.hrc>
+#include <framework/ContextChangeEventMultiplexerTunnel.hxx>
+#include <vcl/floatwin.hxx>
+#include <vcl/fixed.hxx>
+#include <vcl/uitest/logger.hxx>
+#include <vcl/uitest/eventdescription.hxx>
+#include <vcl/svapp.hxx>
+#include <splitwin.hxx>
+#include <tools/diagnose_ex.h>
+#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 <boost/property_tree/ptree.hpp>
+#include <boost/property_tree/json_parser.hpp>
+
+#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>
+
+
+using namespace css;
+using namespace css::uno;
+
+namespace
+{
+ const static char gsReadOnlyCommandName[] = ".uno:EditDoc";
+ const static sal_Int32 gnWidthCloseThreshold (70);
+ const static sal_Int32 gnWidthOpenThreshold (40);
+
+ std::string UnoNameFromDeckId(const OUString& rsDeckId, bool isImpress = false)
+ {
+ if (rsDeckId == "SdCustomAnimationDeck")
+ return ".uno:CustomAnimation";
+
+ if (rsDeckId == "PropertyDeck")
+ return isImpress ? ".uno:ModifyPage" : ".uno:Sidebar";
+
+ if (rsDeckId == "SdLayoutsDeck")
+ return ".uno:ModifyPage";
+
+ if (rsDeckId == "SdSlideTransitionDeck")
+ return ".uno:SlideChangeWindow";
+
+ if (rsDeckId == "SdAllMasterPagesDeck")
+ return ".uno:MasterSlidesPanel";
+
+ if (rsDeckId == "SdMasterPagesDeck")
+ return ".uno:MasterSlidesPanel";
+
+ if (rsDeckId == "GalleryDeck")
+ return ".uno:Gallery";
+
+ return "";
+ }
+}
+
+namespace sfx2::sidebar {
+
+namespace {
+ enum MenuId
+ {
+ MID_UNLOCK_TASK_PANEL = 1,
+ MID_LOCK_TASK_PANEL,
+ MID_HIDE_SIDEBAR,
+ MID_CUSTOMIZATION,
+ MID_RESTORE_DEFAULT,
+ MID_FIRST_PANEL,
+ MID_FIRST_HIDE = 1000
+ };
+
+ /** When in doubt, show this deck.
+ */
+ static const char gsDefaultDeckId[] = "PropertyDeck";
+}
+
+SidebarController::SidebarController (
+ SidebarDockingWindow* pParentWindow,
+ const SfxViewFrame* pViewFrame)
+ : SidebarControllerInterfaceBase(m_aMutex),
+ mpCurrentDeck(),
+ mpParentWindow(pParentWindow),
+ mpViewFrame(pViewFrame),
+ mxFrame(pViewFrame->GetFrame().GetFrameInterface()),
+ mpTabBar(VclPtr<TabBar>::Create(
+ mpParentWindow,
+ mxFrame,
+ [this](const OUString& rsDeckId) { return this->OpenThenToggleDeck(rsDeckId); },
+ [this](const tools::Rectangle& rButtonBox,const ::std::vector<TabBar::DeckMenuData>& rMenuData) { return this->ShowPopupMenu(rButtonBox,rMenuData); },
+ this)),
+ maCurrentContext(OUString(), OUString()),
+ maRequestedContext(),
+ mnRequestedForceFlags(SwitchFlag_NoForce),
+ mnMaximumSidebarWidth(officecfg::Office::UI::Sidebar::General::MaximumWidth::get()),
+ msCurrentDeckId(gsDefaultDeckId),
+ maPropertyChangeForwarder([this](){ return this->BroadcastPropertyChange(); }),
+ maContextChangeUpdate([this](){ return this->UpdateConfigurations(); }),
+ mbIsDeckRequestedOpen(),
+ mbIsDeckOpen(),
+ mbFloatingDeckClosed(!pParentWindow->IsFloatingMode()),
+ mnSavedSidebarWidth(pParentWindow->GetSizePixel().Width()),
+ maFocusManager([this](const Panel& rPanel){ return this->ShowPanel(rPanel); },
+ [this](const sal_Int32 nIndex){ return this->IsDeckOpen(nIndex); }),
+ mxReadOnlyModeDispatch(),
+ mbIsDocumentReadOnly(false),
+ mpSplitWindow(nullptr),
+ mnWidthOnSplitterButtonDown(0),
+ mpResourceManager()
+{
+ // 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();
+ registerSidebarForFrame(instance.get(), rxFrame->getController());
+ rxFrame->addFrameActionListener(instance.get());
+ // Listen for window events.
+ instance->mpParentWindow->AddEventListener(LINK(instance.get(), SidebarController, WindowEventHandler));
+
+ // Listen for theme property changes.
+ Theme::GetPropertySet()->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.get(), 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(xController,
+ [] (uno::Reference<uno::XInterface> const& xRef)
+ { return nullptr != dynamic_cast<SidebarController*>(xRef.get()); }
+ ));
+
+ return dynamic_cast<SidebarController*>(xListener.get());
+}
+
+void SidebarController::registerSidebarForFrame(SidebarController* pController, 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*>(pController),
+ xController);
+}
+
+void SidebarController::unregisterSidebarForFrame(SidebarController* pController, const css::uno::Reference<css::frame::XController>& xController)
+{
+ pController->saveDeckState();
+ pController->disposeDecks();
+
+ css::uno::Reference<css::ui::XContextChangeEventMultiplexer> xMultiplexer (
+ css::ui::ContextChangeEventMultiplexer::get(
+ ::comphelper::getProcessComponentContext()));
+ xMultiplexer->removeContextChangeEventListener(
+ static_cast<css::ui::XContextChangeEventListener*>(pController),
+ xController);
+}
+
+void SidebarController::disposeDecks()
+{
+ SolarMutexGuard aSolarMutexGuard;
+
+ if (comphelper::LibreOfficeKit::isActive())
+ {
+ if (const SfxViewShell* pViewShell = mpViewFrame->GetViewShell())
+ {
+ const std::string hide = UnoNameFromDeckId(msCurrentDeckId, vcl::EnumContext::Application::Impress == vcl::EnumContext::GetApplicationEnum(GetCurrentContext().msApplication));
+ if (!hide.empty())
+ pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_STATE_CHANGED,
+ (hide + "=false").c_str());
+ }
+
+ mpParentWindow->ReleaseLOKNotifier();
+ }
+
+ mpCurrentDeck.clear();
+ maFocusManager.Clear();
+ mpResourceManager->disposeDecks();
+}
+
+void SAL_CALL SidebarController::disposing()
+{
+ 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();
+ }
+
+ uno::Reference<css::frame::XController> xController = mxFrame->getController();
+ if (!xController.is())
+ xController = mxCurrentController;
+
+ mxFrame->removeFrameActionListener(this);
+ unregisterSidebarForFrame(this, xController);
+
+ if (mxReadOnlyModeDispatch.is())
+ mxReadOnlyModeDispatch->removeStatusListener(this, Tools::GetURL(gsReadOnlyCommandName));
+ if (mpSplitWindow != nullptr)
+ {
+ mpSplitWindow->RemoveEventListener(LINK(this, SidebarController, WindowEventHandler));
+ mpSplitWindow = nullptr;
+ }
+
+ if (mpParentWindow != nullptr)
+ {
+ mpParentWindow->RemoveEventListener(LINK(this, SidebarController, WindowEventHandler));
+ mpParentWindow = nullptr;
+ }
+
+ Theme::GetPropertySet()->removePropertyChangeListener(
+ "",
+ static_cast<css::beans::XPropertyChangeListener*>(this));
+
+ maContextChangeUpdate.CancelRequest();
+}
+
+void SAL_CALL SidebarController::notifyContextChangeEvent (const css::ui::ContextChangeEventObject& rEvent)
+{
+ // 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();
+ // TODO: this call is redundant but mandatory for unit test to update context on document loading
+ UpdateConfigurations();
+ }
+}
+
+void SAL_CALL SidebarController::disposing (const css::lang::EventObject& )
+{
+ dispose();
+}
+
+void SAL_CALL SidebarController::propertyChange (const css::beans::PropertyChangeEvent& )
+{
+ maPropertyChangeForwarder.RequestCall();
+}
+
+void SAL_CALL SidebarController::statusChanged (const css::frame::FeatureStateEvent& rEvent)
+{
+ 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();
+ }
+}
+
+void SAL_CALL SidebarController::requestLayout()
+{
+ sal_Int32 nMinimalWidth = 0;
+ if (mpCurrentDeck && !mpCurrentDeck->isDisposed())
+ {
+ mpCurrentDeck->RequestLayout();
+ nMinimalWidth = mpCurrentDeck->GetMinimalWidth();
+ }
+ RestrictWidth(nMinimalWidth);
+}
+
+void SidebarController::BroadcastPropertyChange()
+{
+ mpParentWindow->Invalidate(InvalidateFlags::Children);
+}
+
+void SidebarController::NotifyResize()
+{
+ if (!mpTabBar)
+ {
+ OSL_ASSERT(mpTabBar!=nullptr);
+ return;
+ }
+
+ vcl::Window* pParentWindow = mpTabBar->GetParent();
+ const sal_Int32 nTabBarDefaultWidth = TabBar::GetDefaultWidth() * mpTabBar->GetDPIScaleFactor();
+
+ const sal_Int32 nWidth (pParentWindow->GetSizePixel().Width());
+ const sal_Int32 nHeight (pParentWindow->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;
+ 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.
+ // tdf#130348: Add special case for ChartDeck, too.
+ const sal_Int32 nExtHeight = (msCurrentDeckId == "PropertyDeck" ? 2000 :
+ (msCurrentDeckId == "ChartDeck" ? 1200 : 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();
+ }
+ 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())
+ {
+ VclPtr<DeckTitleBar> pTitleBar = mpCurrentDeck->GetTitleBar();
+ if (pTitleBar && pTitleBar->IsVisible())
+ pTitleBar->SetCloserVisible(CanModifyChildWindowWidth());
+ nMinimalWidth = mpCurrentDeck->GetMinimalWidth();
+ }
+
+ RestrictWidth(nMinimalWidth);
+
+ mpParentWindow->NotifyResize();
+}
+
+void SidebarController::ProcessNewWidth (const sal_Int32 nNewWidth)
+{
+ if ( ! mbIsDeckRequestedOpen)
+ return;
+
+ if (*mbIsDeckRequestedOpen)
+ {
+ // Deck became large enough to be shown. Show it.
+ mnSavedSidebarWidth = 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() * mpTabBar->GetDPIScaleFactor())
+ 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);
+
+ // 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;
+ }
+
+ // Tell the tab bar to highlight the button associated
+ // with the deck.
+ mpTabBar->HighlightDeck(sNewDeckId);
+
+ 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();
+ SwitchToDeck(rsDeckId);
+
+ // Make sure the sidebar is wide enough to fit the requested content
+ sal_Int32 nRequestedWidth = (mpCurrentDeck->GetMinimalWidth() + TabBar::GetDefaultWidth())
+ * mpTabBar->GetDPIScaleFactor();
+ if (mnSavedSidebarWidth < nRequestedWidth)
+ SetChildWindowWidth(nRequestedWidth);
+
+ collectUIInformation(rsDeckId);
+}
+
+void SidebarController::OpenThenSwitchToDeck (
+ const OUString& rsDeckId)
+{
+ RequestOpenDeck();
+ SwitchToDeck(rsDeckId);
+
+}
+
+void SidebarController::SwitchToDefaultDeck()
+{
+ SwitchToDeck(gsDefaultDeckId);
+}
+
+void SidebarController::SwitchToDeck (
+ const OUString& rsDeckId)
+{
+ if ( msCurrentDeckId != rsDeckId
+ || ! mbIsDeckOpen
+ || mnRequestedForceFlags!=SwitchFlag_NoForce)
+ {
+ std::shared_ptr<DeckDescriptor> xDeckDescriptor = mpResourceManager->GetDeckDescriptor(rsDeckId);
+
+ if (xDeckDescriptor)
+ SwitchToDeck(*xDeckDescriptor, maCurrentContext);
+ }
+}
+
+void SidebarController::CreateDeck(const OUString& rDeckId) {
+ CreateDeck(rDeckId, maCurrentContext);
+}
+
+void SidebarController::CreateDeck(const OUString& rDeckId, const Context& rContext, bool bForceCreate)
+{
+ std::shared_ptr<DeckDescriptor> xDeckDescriptor = mpResourceManager->GetDeckDescriptor(rDeckId);
+
+ if (!xDeckDescriptor)
+ return;
+
+ VclPtr<Deck> aDeck = xDeckDescriptor->mpDeck;
+ if (aDeck.get()==nullptr || bForceCreate)
+ {
+ if (aDeck.get()!=nullptr)
+ aDeck.disposeAndClear();
+
+ aDeck = VclPtr<Deck>::Create(
+ *xDeckDescriptor,
+ mpParentWindow,
+ [this]() { return this->RequestCloseDeck(); });
+ }
+ xDeckDescriptor->mpDeck = aDeck;
+ CreatePanels(rDeckId, rContext);
+}
+
+void SidebarController::CreatePanels(const OUString& 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;
+
+ Panel *const pPanel(pDeck->GetPanel(rPanelContexDescriptor.msId));
+ if (pPanel != nullptr)
+ {
+ pPanel->SetLurkMode(false);
+ aNewPanels[nWriteIndex] = pPanel;
+ pPanel->SetExpanded( rPanelContexDescriptor.mbIsInitiallyVisible );
+ ++nWriteIndex;
+ }
+ else
+ {
+ VclPtr<Panel> aPanel = CreatePanel(
+ rPanelContexDescriptor.msId,
+ pDeck->GetPanelParentWindow(),
+ rPanelContexDescriptor.mbIsInitiallyVisible,
+ rContext,
+ pDeck);
+ if (aPanel.get()!=nullptr )
+ {
+ aNewPanels[nWriteIndex] = aPanel;
+
+ // Depending on the context we have to change the command
+ // for the "more options" dialog.
+ VclPtr<PanelTitleBar> pTitleBar = aNewPanels[nWriteIndex]->GetTitleBar();
+ if (pTitleBar)
+ {
+ pTitleBar->SetMoreOptionsCommand(
+ rPanelContexDescriptor.msMenuCommand,
+ mxFrame, xController);
+ }
+ ++nWriteIndex;
+ }
+ }
+ }
+
+ // mpCurrentPanels - may miss stuff (?)
+ aNewPanels.resize(nWriteIndex);
+ pDeck->ResetPanels(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, vcl::EnumContext::Application::Impress == vcl::EnumContext::GetApplicationEnum(GetCurrentContext().msApplication));
+ if (!hide.empty())
+ pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_STATE_CHANGED,
+ (hide + "=false").c_str());
+ }
+
+ const std::string show = UnoNameFromDeckId(rDeckDescriptor.msId, vcl::EnumContext::Application::Impress == vcl::EnumContext::GetApplicationEnum(GetCurrentContext().msApplication));
+ if (!show.empty())
+ pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_STATE_CHANGED,
+ (show + "=true").c_str());
+ }
+ }
+
+ 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;
+ }
+ mpTabBar->Invalidate();
+ mpTabBar->HighlightDeck(msCurrentDeckId);
+
+ // 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.
+ VclPtr<DeckTitleBar> pDebugTitleBar = mpCurrentDeck->GetTitleBar();
+ if (pDebugTitleBar)
+ pDebugTitleBar->SetTitle(rDeckDescriptor.msTitle + " (" + maCurrentContext.msContext + ")");
+#endif
+
+ SfxSplitWindow* pSplitWindow = GetSplitWindow();
+ sal_Int32 nTabBarDefaultWidth = TabBar::GetDefaultWidth() * mpTabBar->GetDPIScaleFactor();
+ WindowAlign eAlign = pSplitWindow ? pSplitWindow->GetAlign() : WindowAlign::Right;
+ 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.SetDeckTitle(mpCurrentDeck->GetTitleBar());
+ maFocusManager.SetPanels(mpCurrentDeck->GetPanels());
+
+ mpTabBar->UpdateFocusManager(maFocusManager);
+ UpdateTitleBarIcons();
+}
+
+void SidebarController::notifyDeckTitle(const OUString& targetDeckId)
+{
+ if (msCurrentDeckId == targetDeckId)
+ {
+ maFocusManager.SetDeckTitle(mpCurrentDeck->GetTitleBar());
+ mpTabBar->UpdateFocusManager(maFocusManager);
+ UpdateTitleBarIcons();
+ }
+}
+
+VclPtr<Panel> SidebarController::CreatePanel (
+ const OUString& rsPanelId,
+ vcl::Window* 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.
+ VclPtr<Panel> pPanel = VclPtr<Panel>::Create(
+ *xPanelDescriptor,
+ pParentWindow,
+ bIsInitiallyExpanded,
+ [pDeck]() { return pDeck->RequestLayout(); },
+ [this]() { return this->GetCurrentContext(); },
+ mxFrame);
+
+ // Create the XUIElement.
+ Reference<ui::XUIElement> xUIElement (CreateUIElement(
+ pPanel->GetComponentInterface(),
+ xPanelDescriptor->msImplementationURL,
+ xPanelDescriptor->mbWantsCanvas,
+ rContext));
+ if (xUIElement.is())
+ {
+ // Initialize the panel and add it to the active deck.
+ pPanel->SetUIElement(xUIElement);
+ }
+ else
+ {
+ pPanel.disposeAndClear();
+ }
+
+ return pPanel;
+}
+
+Reference<ui::XUIElement> SidebarController::CreateUIElement (
+ const Reference<awt::XWindowPeer>& 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", makeAny(mxFrame));
+ aCreationArguments.put("ParentWindow", makeAny(rxWindow));
+ SfxDockingWindow* pSfxDockingWindow = dynamic_cast<SfxDockingWindow*>(mpParentWindow.get());
+ if (pSfxDockingWindow != nullptr)
+ aCreationArguments.put("SfxBindings", makeAny(reinterpret_cast<sal_uInt64>(&pSfxDockingWindow->GetBindings())));
+ aCreationArguments.put("Theme", Theme::GetPropertySet());
+ aCreationArguments.put("Sidebar", makeAny(Reference<ui::XSidebar>(static_cast<ui::XSidebar*>(this))));
+ if (bWantsCanvas)
+ {
+ Reference<rendering::XSpriteCanvas> xCanvas (VCLUnoHelper::GetWindow(rxWindow)->GetSpriteCanvas());
+ aCreationArguments.put("Canvas", makeAny(xCanvas));
+ }
+
+ if (mxCurrentController.is())
+ {
+ OUString aModule = Tools::GetModuleName(mxCurrentController);
+ if (!aModule.isEmpty())
+ {
+ aCreationArguments.put("Module", makeAny(aModule));
+ }
+ aCreationArguments.put("Controller", makeAny(mxCurrentController));
+ }
+
+ aCreationArguments.put("ApplicationName", makeAny(rContext.msApplication));
+ aCreationArguments.put("ContextName", makeAny(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());
+ mnWidthOnSplitterButtonDown = 0;
+ break;
+ }
+
+ case VclEventId::ObjectDying:
+ dispose();
+ break;
+
+ default: break;
+ }
+ }
+}
+
+void SidebarController::ShowPopupMenu (
+ const tools::Rectangle& rButtonBox,
+ const ::std::vector<TabBar::DeckMenuData>& rMenuData) const
+{
+ VclPtr<PopupMenu> pMenu = CreatePopupMenu(rMenuData);
+ pMenu->SetSelectHdl(LINK(const_cast<SidebarController*>(this), SidebarController, OnMenuItemSelected));
+
+ // pass toolbox button rect so the menu can stay open on button up
+ tools::Rectangle aBox (rButtonBox);
+ aBox.Move(mpTabBar->GetPosPixel().X(), 0);
+ const PopupMenuFlags aMenuDirection
+ = (comphelper::LibreOfficeKit::isActive() ? PopupMenuFlags::ExecuteLeft
+ : PopupMenuFlags::ExecuteDown);
+ pMenu->Execute(mpParentWindow, aBox, aMenuDirection);
+ pMenu.disposeAndClear();
+}
+
+VclPtr<PopupMenu>
+SidebarController::CreatePopupMenu(const ::std::vector<TabBar::DeckMenuData>& rMenuData) const
+{
+ // Create the top level popup menu.
+ auto pMenu = VclPtr<PopupMenu>::Create();
+ FloatingWindow* pMenuWindow = dynamic_cast<FloatingWindow*>(pMenu->GetWindow());
+ if (pMenuWindow != nullptr)
+ {
+ pMenuWindow->SetPopupModeFlags(pMenuWindow->GetPopupModeFlags()
+ | FloatWinPopupFlags::NoMouseUpClose);
+ }
+
+ // Create sub menu for customization (hiding of deck tabs), only on desktop.
+ VclPtr<PopupMenu> pCustomizationMenu
+ = (comphelper::LibreOfficeKit::isActive() ? nullptr : VclPtr<PopupMenu>::Create());
+
+ // 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)
+ {
+ const sal_Int32 nMenuIndex (nIndex+MID_FIRST_PANEL);
+ pMenu->InsertItem(nMenuIndex, rItem.msDisplayName, MenuItemBits::RADIOCHECK);
+ pMenu->CheckItem(nMenuIndex, rItem.mbIsCurrentDeck);
+ pMenu->EnableItem(nMenuIndex, rItem.mbIsEnabled && rItem.mbIsActive);
+
+ if (!comphelper::LibreOfficeKit::isActive())
+ {
+ const sal_Int32 nSubMenuIndex(nIndex + MID_FIRST_HIDE);
+ if (rItem.mbIsCurrentDeck)
+ {
+ // Don't allow the currently visible deck to be disabled.
+ pCustomizationMenu->InsertItem(nSubMenuIndex, rItem.msDisplayName,
+ MenuItemBits::RADIOCHECK);
+ pCustomizationMenu->CheckItem(nSubMenuIndex);
+ }
+ else
+ {
+ pCustomizationMenu->InsertItem(nSubMenuIndex, rItem.msDisplayName,
+ MenuItemBits::CHECKABLE);
+ pCustomizationMenu->CheckItem(nSubMenuIndex, rItem.mbIsEnabled && rItem.mbIsActive);
+ }
+ }
+
+ ++nIndex;
+ }
+
+ pMenu->InsertSeparator();
+
+ // 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())
+ {
+ pMenu->InsertItem(MID_LOCK_TASK_PANEL, SfxResId(STR_SFX_DOCK));
+ pMenu->SetAccelKey(MID_LOCK_TASK_PANEL, vcl::KeyCode(KEY_F10, true, true, false, false));
+ }
+ else
+ {
+ pMenu->InsertItem(MID_UNLOCK_TASK_PANEL, SfxResId(STR_SFX_UNDOCK));
+ pMenu->SetAccelKey(MID_UNLOCK_TASK_PANEL, vcl::KeyCode(KEY_F10, true, true, false, false));
+ }
+ }
+
+ pMenu->InsertItem(MID_HIDE_SIDEBAR, SfxResId(SFX_STR_SIDEBAR_HIDE_SIDEBAR));
+
+ // No Restore or Customize options for LoKit.
+ if (!comphelper::LibreOfficeKit::isActive())
+ {
+ pCustomizationMenu->InsertSeparator();
+ pCustomizationMenu->InsertItem(MID_RESTORE_DEFAULT, SfxResId(SFX_STR_SIDEBAR_RESTORE));
+
+ pMenu->InsertItem(MID_CUSTOMIZATION, SfxResId(SFX_STR_SIDEBAR_CUSTOMIZATION));
+ pMenu->SetPopupMenu(MID_CUSTOMIZATION, pCustomizationMenu);
+ }
+
+ pMenu->RemoveDisabledEntries(false);
+
+ return pMenu;
+}
+
+IMPL_LINK(SidebarController, OnMenuItemSelected, Menu*, pMenu, bool)
+{
+ if (pMenu == nullptr)
+ {
+ OSL_ENSURE(pMenu!=nullptr, "sfx2::sidebar::SidebarController::OnMenuItemSelected: illegal menu!");
+ return false;
+ }
+
+ pMenu->Deactivate();
+ const sal_Int32 nIndex (pMenu->GetCurItemId());
+ switch (nIndex)
+ {
+ case MID_UNLOCK_TASK_PANEL:
+ mpParentWindow->SetFloatingMode(true);
+ if (mpParentWindow->IsFloatingMode())
+ mpParentWindow->ToTop(ToTopFlags::GrabFocusOnly);
+ break;
+
+ case MID_LOCK_TASK_PANEL:
+ mpParentWindow->SetFloatingMode(false);
+ break;
+
+ case MID_RESTORE_DEFAULT:
+ mpTabBar->RestoreHideFlags();
+ break;
+
+ case MID_HIDE_SIDEBAR:
+ {
+ 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();
+ }
+ break;
+ }
+ default:
+ {
+ try
+ {
+ if (nIndex >= MID_FIRST_PANEL && nIndex<MID_FIRST_HIDE)
+ {
+ RequestOpenDeck();
+ SwitchToDeck(mpTabBar->GetDeckIdForIndex(nIndex - MID_FIRST_PANEL));
+ }
+ else if (nIndex >=MID_FIRST_HIDE)
+ if (pMenu->GetItemBits(nIndex) == MenuItemBits::CHECKABLE)
+ {
+ mpTabBar->ToggleHideFlag(nIndex-MID_FIRST_HIDE);
+
+ // 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.
+ mpTabBar->SetDecks(aDecks);
+ mpTabBar->HighlightDeck(mpCurrentDeck->GetId());
+ mpTabBar->UpdateFocusManager(maFocusManager);
+ }
+ mpParentWindow->GrabFocusToDocument();
+ }
+ catch (RuntimeException&)
+ {
+ }
+ }
+ break;
+ }
+
+ return true;
+}
+
+void SidebarController::RequestCloseDeck()
+{
+ if (comphelper::LibreOfficeKit::isActive() && mpCurrentDeck)
+ {
+ const vcl::ILibreOfficeKitNotifier* pNotifier = mpCurrentDeck->GetLOKNotifier();
+ auto pMobileNotifier = SfxViewShell::Current();
+ const SfxViewShell* pViewShell = SfxViewShell::Current();
+ if (pMobileNotifier && pViewShell && pViewShell->isLOKMobilePhone())
+ {
+ // Mobile phone.
+ std::stringstream aStream;
+ boost::property_tree::ptree aTree;
+ aTree.put("id", mpParentWindow->get_id()); // TODO could be missing - sort out
+ aTree.put("type", "dockingwindow");
+ aTree.put("text", mpParentWindow->GetText());
+ aTree.put("enabled", false);
+ boost::property_tree::write_json(aStream, aTree);
+ const std::string message = aStream.str();
+ pMobileNotifier->libreOfficeKitViewCallback(LOK_CALLBACK_JSDIALOG, message.c_str());
+ }
+ else if (pNotifier)
+ pNotifier->notifyWindow(mpCurrentDeck->GetLOKWindowId(), "close");
+ }
+
+ mbIsDeckRequestedOpen = false;
+ UpdateDeckOpenState();
+
+ if (!mpCurrentDeck)
+ 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 && *mbIsDeckOpen;
+}
+
+bool SidebarController::IsDeckVisible(const OUString& rsDeckId)
+{
+ return mbIsDeckOpen && *mbIsDeckOpen && msCurrentDeckId == rsDeckId;
+}
+
+void SidebarController::UpdateDeckOpenState()
+{
+ if ( ! mbIsDeckRequestedOpen)
+ // No state requested.
+ return;
+
+ const sal_Int32 nTabBarDefaultWidth = TabBar::GetDefaultWidth() * mpTabBar->GetDPIScaleFactor();
+
+ // Update (change) the open state when it either has not yet been initialized
+ // or when its value differs from the requested state.
+ if ( mbIsDeckOpen && *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, vcl::EnumContext::Application::Impress == vcl::EnumContext::GetApplicationEnum(GetCurrentContext().msApplication));
+ if (!uno.empty())
+ pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_STATE_CHANGED,
+ (uno + "=true").c_str());
+ }
+ }
+ }
+ }
+ 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, vcl::EnumContext::Application::Impress == vcl::EnumContext::GetApplicationEnum(GetCurrentContext().msApplication));
+ if (!uno.empty())
+ pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_STATE_CHANGED,
+ (uno + "=false").c_str());
+ }
+ }
+ }
+
+ if (mnWidthOnSplitterButtonDown > nTabBarDefaultWidth)
+ mnSavedSidebarWidth = mnWidthOnSplitterButtonDown;
+ mpParentWindow->SetStyle(mpParentWindow->GetStyle() & ~WB_SIZEABLE);
+ }
+
+ mbIsDeckOpen = *mbIsDeckRequestedOpen;
+ if (*mbIsDeckOpen && mpCurrentDeck)
+ mpCurrentDeck->Show(*mbIsDeckOpen);
+ 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 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) * mpTabBar->GetDPIScaleFactor();
+
+ pSplitWindow->SetItemSizeRange(
+ nSetId,
+ Range(nRequestedWidth,
+ getMaximumWidth() * mpTabBar->GetDPIScaleFactor()));
+ }
+}
+
+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<FixedImage>::Create(mpParentWindow));
+ FixedImage* pFixedImage = static_cast<FixedImage*>(mpCloseIndicator.get());
+ const Image aImage (Theme::GetImage(Theme::Image_CloseIndicator));
+ pFixedImage->SetImage(aImage);
+ pFixedImage->SetSizePixel(aImage.GetSizePixel());
+ pFixedImage->SetBackground(Theme::GetWallpaper(Theme::Paint_DeckBackground));
+ }
+
+ // Place and show the indicator.
+ const Size aWindowSize (mpParentWindow->GetSizePixel());
+ const Size aImageSize (mpCloseIndicator->GetSizePixel());
+ mpCloseIndicator->SetPosPixel(
+ Point(
+ aWindowSize.Width() - TabBar::GetDefaultWidth() * mpTabBar->GetDPIScaleFactor() - 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(const OUString& 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)
+ {
+ VclPtr<DeckTitleBar> pTitleBar(mpCurrentDeck->GetTitleBar());
+
+ if(pTitleBar)
+ {
+ aRect = DeckTitleBar::GetDragArea();
+ }
+ }
+
+ return aRect;
+}
+
+void SidebarController::frameAction(const css::frame::FrameActionEvent& rEvent)
+{
+ if (rEvent.Frame == mxFrame)
+ {
+ if (rEvent.Action == css::frame::FrameAction_COMPONENT_DETACHING)
+ unregisterSidebarForFrame(this, mxFrame->getController());
+ else if (rEvent.Action == css::frame::FrameAction_COMPONENT_REATTACHED)
+ registerSidebarForFrame(this, 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);
+ }
+}
+
+} // 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 000000000..7039983f9
--- /dev/null
+++ b/sfx2/source/sidebar/SidebarDockingWindow.cxx
@@ -0,0 +1,318 @@
+/* -*- 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/processfactory.hxx>
+#include <sfx2/lokhelper.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 <comphelper/lok.hxx>
+#include <LibreOfficeKit/LibreOfficeKitEnums.h>
+
+#include <boost/property_tree/json_parser.hpp>
+
+using namespace css;
+using namespace css::uno;
+
+namespace sfx2::sidebar {
+
+class SidebarNotifyIdle : public Idle
+{
+ SidebarDockingWindow& m_rSidebarDockingWin;
+ std::string m_LastNotificationMessage;
+ vcl::LOKWindowId m_LastLOKWindowId;
+
+public:
+ SidebarNotifyIdle(SidebarDockingWindow &rSidebarDockingWin) :
+ Idle("Sidebar notify"),
+ m_rSidebarDockingWin(rSidebarDockingWin),
+ m_LastNotificationMessage(),
+ m_LastLOKWindowId(0)
+ {
+ SetPriority(TaskPriority::POST_PAINT);
+ }
+
+ void Invoke() override
+ {
+ auto pNotifier = m_rSidebarDockingWin.GetLOKNotifier();
+ auto pMobileNotifier = SfxViewShell::Current();
+ if (!pNotifier || (!pMobileNotifier && !comphelper::LibreOfficeKit::isActive()))
+ return;
+
+ try
+ {
+ if (pMobileNotifier && pMobileNotifier->isLOKMobilePhone())
+ {
+ // Mobile phone.
+ std::stringstream aStream;
+ boost::property_tree::ptree aTree = m_rSidebarDockingWin.DumpAsPropertyTree();
+ aTree.put("id", m_rSidebarDockingWin.GetLOKWindowId());
+ boost::property_tree::write_json(aStream, aTree);
+ const std::string message = aStream.str();
+ if (message != m_LastNotificationMessage)
+ {
+ m_LastNotificationMessage = message;
+ pMobileNotifier->libreOfficeKitViewCallback(LOK_CALLBACK_JSDIALOG, message.c_str());
+ }
+ }
+
+ // Notify the sidebar is created, and its LOKWindowId, which
+ // is needed on mobile phones, tablets, and desktop.
+ const Point pos(m_rSidebarDockingWin.GetOutOffXPixel(),
+ m_rSidebarDockingWin.GetOutOffYPixel());
+ const OString posMessage = pos.toString();
+ const OString sizeMessage = m_rSidebarDockingWin.GetSizePixel().toString();
+
+ const std::string message = OString(posMessage + sizeMessage).getStr();
+ const vcl::LOKWindowId lokWindowId = m_rSidebarDockingWin.GetLOKWindowId();
+
+ if (lokWindowId != m_LastLOKWindowId || message != m_LastNotificationMessage)
+ {
+ m_LastLOKWindowId = lokWindowId;
+ m_LastNotificationMessage = message;
+
+ std::vector<vcl::LOKPayloadItem> aItems;
+ aItems.emplace_back("type", "deck");
+ aItems.emplace_back("position", posMessage);
+ aItems.emplace_back("size", sizeMessage);
+ pNotifier->notifyWindow(lokWindowId, "created", aItems);
+ }
+ }
+ catch (boost::property_tree::json_parser::json_parser_error& rError)
+ {
+ SAL_WARN("sfx.sidebar", rError.message());
+ }
+ }
+};
+
+SidebarDockingWindow::SidebarDockingWindow(SfxBindings* pSfxBindings, SidebarChildWindow& rChildWindow,
+ vcl::Window* pParentWindow, WinBits nBits)
+ : SfxDockingWindow(pSfxBindings, &rChildWindow, pParentWindow, nBits)
+ , mpSidebarController()
+ , mbIsReadyToDrag(false)
+ , mpIdleNotify(new SidebarNotifyIdle(*this))
+{
+ // Get the XFrame from the bindings.
+ if (pSfxBindings==nullptr || pSfxBindings->GetDispatcher()==nullptr)
+ {
+ OSL_ASSERT(pSfxBindings!=nullptr);
+ OSL_ASSERT(pSfxBindings->GetDispatcher()!=nullptr);
+ }
+ else
+ {
+ const SfxViewFrame* pViewFrame = pSfxBindings->GetDispatcher()->GetFrame();
+ mpSidebarController.set(sfx2::sidebar::SidebarController::create(this, pViewFrame).get());
+ }
+}
+
+SidebarDockingWindow::~SidebarDockingWindow()
+{
+ disposeOnce();
+}
+
+void SidebarDockingWindow::dispose()
+{
+ if (comphelper::LibreOfficeKit::isActive())
+ LOKClose();
+
+ Reference<lang::XComponent> xComponent (static_cast<XWeak*>(mpSidebarController.get()), UNO_QUERY);
+ mpSidebarController.clear();
+ if (xComponent.is())
+ xComponent->dispose();
+
+ SfxDockingWindow::dispose();
+}
+
+void SidebarDockingWindow::LOKClose()
+{
+ assert(comphelper::LibreOfficeKit::isActive());
+ if (const vcl::ILibreOfficeKitNotifier* pNotifier = GetLOKNotifier())
+ {
+ mpIdleNotify->Stop();
+
+ pNotifier->notifyWindow(GetLOKWindowId(), "close");
+ ReleaseLOKNotifier();
+ }
+}
+
+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::Resize()
+{
+ SfxDockingWindow::Resize();
+
+ NotifyResize();
+}
+
+void SidebarDockingWindow::SyncUpdate()
+{
+ if (mpSidebarController.is())
+ mpSidebarController->SyncUpdate();
+}
+
+void SidebarDockingWindow::NotifyResize()
+{
+ if (comphelper::LibreOfficeKit::isActive() && mpSidebarController.is() && SfxViewShell::Current())
+ {
+ const vcl::ILibreOfficeKitNotifier* pCurrentView = SfxViewShell::Current();
+ if (GetLOKNotifier() != pCurrentView)
+ {
+ // ViewShell not yet set, or has changed. Reset it.
+ // Note GetLOKWindowId will return a new value after resetting, so we must notify clients.
+ LOKClose();
+
+ SetLOKNotifier(pCurrentView);
+ }
+
+ mpIdleNotify->Start();
+ }
+}
+
+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)
+{
+ MouseNotifyEvent nType = rEvent.GetType();
+ if (MouseNotifyEvent::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( "StyleListPanel" );
+ if ( xPanelDescriptor && mpSidebarController->IsDeckVisible( xPanelDescriptor->msDeckId ) )
+ Close();
+ return true;
+ }
+ if (".uno:Undo" == aCommand || ".uno:Redo" == aCommand)
+ {
+ comphelper::dispatchCommand(aCommand, {});
+ return true;
+ }
+ }
+ else if (MouseNotifyEvent::MOUSEBUTTONDOWN == nType)
+ {
+ const MouseEvent *mEvt = rEvent.GetMouseEvent();
+ if (mEvt->IsLeft())
+ {
+ tools::Rectangle aGrip = mpSidebarController->GetDeckDragArea();
+ if ( aGrip.IsInside( mEvt->GetPosPixel() ) )
+ mbIsReadyToDrag = true;
+ }
+ }
+ else if (MouseNotifyEvent::MOUSEMOVE == nType)
+ {
+ const MouseEvent *mEvt = rEvent.GetMouseEvent();
+ tools::Rectangle aGrip = mpSidebarController->GetDeckDragArea();
+ if (mEvt->IsLeft() && aGrip.IsInside( 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 000000000..2f81b01f6
--- /dev/null
+++ b/sfx2/source/sidebar/SidebarModelUpdate.cxx
@@ -0,0 +1,20 @@
+/* -*- 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 000000000..666a573f6
--- /dev/null
+++ b/sfx2/source/sidebar/SidebarPanelBase.cxx
@@ -0,0 +1,199 @@
+/* -*- 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/Theme.hxx>
+#include <sfx2/sidebar/ILayoutableWindow.hxx>
+#include <sfx2/sidebar/IContextChangeReceiver.hxx>
+#include <sfx2/sidebar/SidebarModelUpdate.hxx>
+#include <vcl/layout.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,
+ vcl::Window* pWindow,
+ const css::ui::LayoutSize& rLayoutSize)
+{
+ Reference<ui::XUIElement> xUIElement (
+ new SidebarPanelBase(
+ rsResourceURL,
+ rxFrame,
+ pWindow,
+ rLayoutSize));
+ return xUIElement;
+}
+
+SidebarPanelBase::SidebarPanelBase (
+ const OUString& rsResourceURL,
+ const css::uno::Reference<css::frame::XFrame>& rxFrame,
+ vcl::Window* pWindow,
+ const css::ui::LayoutSize& rLayoutSize)
+ : SidebarPanelBaseInterfaceBase(m_aMutex),
+ mxFrame(rxFrame),
+ mpControl(pWindow),
+ msResourceURL(rsResourceURL),
+ maLayoutSize(rLayoutSize)
+{
+ if (mxFrame.is())
+ {
+ css::uno::Reference<css::ui::XContextChangeEventMultiplexer> xMultiplexer (
+ css::ui::ContextChangeEventMultiplexer::get(
+ ::comphelper::getProcessComponentContext()));
+ xMultiplexer->addContextChangeEventListener(this, mxFrame->getController());
+ }
+ if (mpControl != nullptr)
+ {
+ mpControl->SetBackground(Theme::GetWallpaper(Theme::Paint_PanelBackground));
+ mpControl->Show();
+ }
+}
+
+SidebarPanelBase::~SidebarPanelBase()
+{
+}
+
+void SAL_CALL SidebarPanelBase::disposing()
+{
+ mpControl.disposeAndClear();
+
+ 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)
+{
+ IContextChangeReceiver* pContextChangeReceiver
+ = dynamic_cast<IContextChangeReceiver*>(mpControl.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&)
+{
+ mxFrame = nullptr;
+ mpControl = nullptr;
+}
+
+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 Reference<XInterface>(static_cast<XWeak*>(this));
+}
+
+Reference<accessibility::XAccessible> SAL_CALL SidebarPanelBase::createAccessible (
+ const Reference<accessibility::XAccessible>&)
+{
+ // Not yet implemented.
+ return nullptr;
+}
+
+Reference<awt::XWindow> SAL_CALL SidebarPanelBase::getWindow()
+{
+ if (mpControl != nullptr)
+ return Reference<awt::XWindow>(
+ mpControl->GetComponentInterface(),
+ UNO_QUERY);
+ else
+ return nullptr;
+}
+
+ui::LayoutSize SAL_CALL SidebarPanelBase::getHeightForWidth (const sal_Int32 nWidth)
+{
+ if (maLayoutSize.Minimum >= 0)
+ return maLayoutSize;
+ else
+ {
+ ILayoutableWindow* pLayoutableWindow = dynamic_cast<ILayoutableWindow*>(mpControl.get());
+ if (pLayoutableWindow)
+ return pLayoutableWindow->GetHeightForWidth(nWidth);
+ else if (isLayoutEnabled(mpControl))
+ {
+ // widget layout-based sidebar
+ Size aSize(mpControl->get_preferred_size());
+ return ui::LayoutSize(aSize.Height(), aSize.Height(), aSize.Height());
+ }
+ else if (mpControl != nullptr)
+ {
+ const sal_Int32 nHeight (mpControl->GetSizePixel().Height());
+ return ui::LayoutSize(nHeight,nHeight,nHeight);
+ }
+ }
+
+ return ui::LayoutSize(0,0,0);
+}
+
+sal_Int32 SAL_CALL SidebarPanelBase::getMinimalWidth ()
+{
+ if (isLayoutEnabled(mpControl))
+ {
+ // widget layout-based sidebar
+ Size aSize(mpControl->get_preferred_size());
+ return aSize.Width();
+ }
+ return 0;
+}
+
+void SAL_CALL SidebarPanelBase::updateModel(const css::uno::Reference<css::frame::XModel>& xModel)
+{
+ SidebarModelUpdate* pModelUpdate = dynamic_cast<SidebarModelUpdate*>(mpControl.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 000000000..f77e6d5ff
--- /dev/null
+++ b/sfx2/source/sidebar/SidebarToolBox.cxx
@@ -0,0 +1,351 @@
+/* -*- 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/SidebarToolBox.hxx>
+#include <sidebar/ControllerFactory.hxx>
+#include <sfx2/viewfrm.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, ChangedIconSizeHandler));
+
+#ifdef DEBUG
+ SetText(OUString("SidebarToolBox"));
+#endif
+}
+
+SidebarToolBox::~SidebarToolBox()
+{
+ disposeOnce();
+}
+
+void SidebarToolBox::dispose()
+{
+ SvtMiscOptions().RemoveListenerLink(LINK(this, SidebarToolBox, ChangedIconSizeHandler));
+
+ ControllerContainer aControllers;
+ aControllers.swap(maControllers);
+ for (auto const& controller : aControllers)
+ {
+ Reference<lang::XComponent> xComponent(controller.second, UNO_QUERY);
+ if (xComponent.is())
+ xComponent->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 SvtMiscOptions().GetSidebarIconSize();
+}
+
+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(), 0L), mbSideBar);
+ RegisterHandlers();
+}
+
+bool SidebarToolBox::EventNotify (NotifyEvent& rEvent)
+{
+ if (rEvent.GetType() == MouseNotifyEvent::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 sal_uInt16 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 sal_uInt16 nItemId) const
+{
+ ControllerContainer::const_iterator iController (maControllers.find(nItemId));
+ if (iController != maControllers.end())
+ return iController->second;
+
+ return Reference<frame::XToolbarController>();
+}
+
+void SidebarToolBox::SetController(const sal_uInt16 nItemId,
+ const css::uno::Reference<css::frame::XToolbarController>& rxController)
+{
+ ControllerContainer::iterator iController (maControllers.find(nItemId));
+ if (iController != maControllers.end())
+ {
+ Reference<lang::XComponent> xComponent(rxController, UNO_QUERY);
+ if (xComponent.is())
+ xComponent->dispose();
+
+ iController->second = rxController;
+ }
+ else
+ {
+ maControllers[nItemId] = rxController;
+ }
+
+ if (rxController.is())
+ RegisterHandlers();
+}
+
+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, ChangedIconSizeHandler, 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::Current())
+ {
+ OUString aCommandURL = GetItemCommand(it.first);
+ css::uno::Reference<frame::XFrame> xFrame = SfxViewFrame::Current()->GetFrame().GetFrameInterface();
+ Image aImage = vcl::CommandInfoProvider::GetImageForCommand(aCommandURL, xFrame, GetImageSize());
+ SetItemImage(it.first, aImage);
+ }
+ }
+
+ Resize();
+ queue_resize();
+}
+
+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 SvtMiscOptions().GetNotebookbarIconSize();
+ }
+};
+
+}
+
+extern "C" SAL_DLLPUBLIC_EXPORT void makeSidebarToolBox(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(makeSidebarToolBox)>);
+ VclPtrInstance<SidebarToolBox> pBox(pParent);
+ pBox->InitToolBox(rMap);
+ rRet = pBox;
+}
+
+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 000000000..4f49bd461
--- /dev/null
+++ b/sfx2/source/sidebar/TabBar.cxx
@@ -0,0 +1,417 @@
+/* -*- 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/ControlFactory.hxx>
+#include <sidebar/DeckDescriptor.hxx>
+#include <sidebar/Paint.hxx>
+#include <sfx2/sidebar/Theme.hxx>
+#include <sidebar/Tools.hxx>
+#include <sfx2/sidebar/FocusManager.hxx>
+#include <sfx2/sidebar/SidebarController.hxx>
+#include <sfx2/strings.hrc>
+
+#include <sfx2/sfxresid.hxx>
+
+#include <comphelper/processfactory.hxx>
+#include <o3tl/safeint.hxx>
+#include <vcl/button.hxx>
+#include <vcl/commandevent.hxx>
+#include <vcl/event.hxx>
+#include <vcl/image.hxx>
+#include <vcl/svapp.hxx>
+#include <tools/svborder.hxx>
+#include <svtools/acceleratorexecute.hxx>
+
+using namespace css;
+using namespace css::uno;
+
+namespace sfx2::sidebar {
+
+TabBar::TabBar(vcl::Window* pParentWindow,
+ const Reference<frame::XFrame>& rxFrame,
+ const std::function<void (const OUString&)>& rDeckActivationFunctor,
+ const PopupMenuProvider& rPopupMenuProvider,
+ SidebarController* rParentSidebarController
+ )
+ : Window(pParentWindow, WB_DIALOGCONTROL),
+ mxFrame(rxFrame),
+ mpMenuButton(ControlFactory::CreateMenuButton(this)),
+ maItems(),
+ maDeckActivationFunctor(rDeckActivationFunctor),
+ maPopupMenuProvider(rPopupMenuProvider),
+ pParentSidebarController(rParentSidebarController)
+{
+
+ SetBackground(Theme::GetPaint(Theme::Paint_TabBarBackground).GetWallpaper());
+
+ mpMenuButton->SetModeImage(Theme::GetImage(Theme::Image_TabBarMenu));
+ mpMenuButton->SetClickHdl(LINK(this, TabBar, OnToolboxClicked));
+ mpMenuButton->SetQuickHelpText(SfxResId(SFX_STR_SIDEBAR_SETTINGS));
+ Layout();
+
+#ifdef DEBUG
+ SetText(OUString("TabBar"));
+#endif
+}
+
+TabBar::~TabBar()
+{
+ disposeOnce();
+}
+
+void TabBar::dispose()
+{
+ for (auto & item : maItems)
+ item.mpButton.disposeAndClear();
+ maItems.clear();
+ mpMenuButton.disposeAndClear();
+ vcl::Window::dispose();
+}
+
+void TabBar::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rUpdateArea)
+{
+ Window::Paint(rRenderContext, rUpdateArea);
+
+ const sal_Int32 nHorizontalPadding(Theme::GetInteger(Theme::Int_TabMenuSeparatorPadding));
+ rRenderContext.SetLineColor(Theme::GetColor(Theme::Color_TabMenuSeparator));
+ rRenderContext.DrawLine(Point(nHorizontalPadding, mnMenuSeparatorY),
+ Point(GetSizePixel().Width() - nHorizontalPadding, mnMenuSeparatorY));
+}
+
+sal_Int32 TabBar::GetDefaultWidth()
+{
+ return Theme::GetInteger(Theme::Int_TabItemWidth)
+ + Theme::GetInteger(Theme::Int_TabBarLeftPadding)
+ + Theme::GetInteger(Theme::Int_TabBarRightPadding);
+}
+
+void TabBar::SetDecks(const ResourceManager::DeckContextDescriptorContainer& rDecks)
+{
+ // Remove the current buttons.
+ {
+ for (auto & item : maItems)
+ {
+ item.mpButton.disposeAndClear();
+ }
+ maItems.clear();
+ }
+ maItems.resize(rDecks.size());
+ sal_Int32 nIndex (0);
+ for (auto const& deck : rDecks)
+ {
+ std::shared_ptr<DeckDescriptor> xDescriptor = pParentSidebarController->GetResourceManager()->GetDeckDescriptor(deck.msId);
+ if (xDescriptor == nullptr)
+ {
+ OSL_ASSERT(xDescriptor!=nullptr);
+ continue;
+ }
+
+ Item& rItem (maItems[nIndex++]);
+ rItem.msDeckId = xDescriptor->msId;
+ rItem.mpButton.disposeAndClear();
+ rItem.mpButton = CreateTabItem(*xDescriptor);
+ rItem.mpButton->SetClickHdl(LINK(&rItem, TabBar::Item, HandleClick));
+ rItem.maDeckActivationFunctor = maDeckActivationFunctor;
+ rItem.mbIsHidden = ! xDescriptor->mbIsEnabled;
+ rItem.mbIsHiddenByDefault = rItem.mbIsHidden; // the default is the state while creating
+
+ rItem.mpButton->Enable(deck.mbIsEnabled);
+ }
+
+ UpdateButtonIcons();
+ Layout();
+}
+
+void TabBar::UpdateButtonIcons()
+{
+ Image aImage = Theme::GetImage(Theme::Image_TabBarMenu);
+ mpMenuButton->SetModeImage(aImage);
+
+ for (auto const& item : maItems)
+ {
+ std::shared_ptr<DeckDescriptor> xDeckDescriptor = pParentSidebarController->GetResourceManager()->GetDeckDescriptor(item.msDeckId);
+
+ if (xDeckDescriptor)
+ {
+ aImage = GetItemImage(*xDeckDescriptor);
+ item.mpButton->SetModeImage(aImage);
+ }
+ }
+
+ Invalidate();
+}
+
+void TabBar::Layout()
+{
+ const SvBorder aPadding (
+ Theme::GetInteger(Theme::Int_TabBarLeftPadding),
+ Theme::GetInteger(Theme::Int_TabBarTopPadding),
+ Theme::GetInteger(Theme::Int_TabBarRightPadding),
+ Theme::GetInteger(Theme::Int_TabBarBottomPadding));
+ sal_Int32 nX (aPadding.Top());
+ sal_Int32 nY (aPadding.Left());
+ const Size aTabItemSize (
+ Theme::GetInteger(Theme::Int_TabItemWidth) * GetDPIScaleFactor(),
+ Theme::GetInteger(Theme::Int_TabItemHeight) * GetDPIScaleFactor());
+
+ // Place the menu button and the separator.
+ if (mpMenuButton != nullptr)
+ {
+ mpMenuButton->SetPosSizePixel(
+ Point(nX,nY),
+ aTabItemSize);
+ mpMenuButton->Show();
+ nY += mpMenuButton->GetSizePixel().Height() + 1 + Theme::GetInteger(Theme::Int_TabMenuPadding);
+ mnMenuSeparatorY = nY - Theme::GetInteger(Theme::Int_TabMenuPadding)/2 - 1;
+ }
+
+ // Place the deck selection buttons.
+ for (auto const& item : maItems)
+ {
+ Button& rButton (*item.mpButton);
+ rButton.Show( ! item.mbIsHidden);
+
+ if (item.mbIsHidden)
+ continue;
+
+ // Place and size the icon.
+ rButton.SetPosSizePixel(
+ Point(nX,nY),
+ aTabItemSize);
+ rButton.Show();
+
+ nY += rButton.GetSizePixel().Height() + 1 + aPadding.Bottom();
+ }
+ Invalidate();
+}
+
+void TabBar::HighlightDeck (const OUString& rsDeckId)
+{
+ for (auto const& item : maItems)
+ {
+ if (item.msDeckId == rsDeckId)
+ item.mpButton->Check();
+ else
+ item.mpButton->Check(false);
+ }
+}
+
+void TabBar::RemoveDeckHighlight ()
+{
+ for (auto const& item : maItems)
+ {
+ item.mpButton->Check(false);
+ }
+}
+
+void TabBar::DataChanged (const DataChangedEvent& rDataChangedEvent)
+{
+ SetBackground(Theme::GetPaint(Theme::Paint_TabBarBackground).GetWallpaper());
+ UpdateButtonIcons();
+
+ Window::DataChanged(rDataChangedEvent);
+}
+
+bool TabBar::EventNotify(NotifyEvent& rEvent)
+{
+ MouseNotifyEvent nType = rEvent.GetType();
+ if(MouseNotifyEvent::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 vcl::Window::EventNotify(rEvent);
+ return true;
+ }
+ else if(MouseNotifyEvent::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(),
+ [] (Item const& rItem) { return rItem.mpButton->IsChecked(); });
+ 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;
+}
+
+VclPtr<RadioButton> TabBar::CreateTabItem(const DeckDescriptor& rDeckDescriptor)
+{
+ VclPtr<RadioButton> pItem = ControlFactory::CreateTabItem(this);
+ pItem->SetAccessibleName(rDeckDescriptor.msTitle);
+ pItem->SetAccessibleDescription(rDeckDescriptor.msHelpText);
+ pItem->SetHelpText(rDeckDescriptor.msHelpText);
+ pItem->SetQuickHelpText(rDeckDescriptor.msHelpText);
+ return pItem;
+}
+
+Image TabBar::GetItemImage(const DeckDescriptor& rDeckDescriptor) const
+{
+ return Tools::GetImage(
+ rDeckDescriptor.msIconURL,
+ rDeckDescriptor.msHighContrastIconURL,
+ mxFrame);
+}
+
+IMPL_LINK_NOARG(TabBar::Item, HandleClick, Button*, void)
+{
+ vcl::Window* pFocusWin = Application::GetFocusWindow();
+ pFocusWin->GrabFocusToDocument();
+ try
+ {
+ maDeckActivationFunctor(msDeckId);
+ }
+ 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 );
+ }
+
+ Layout();
+}
+
+void TabBar::RestoreHideFlags()
+{
+ bool bNeedsLayout(false);
+ for (auto & item : maItems)
+ {
+ if (item.mbIsHidden != item.mbIsHiddenByDefault)
+ {
+ item.mbIsHidden = item.mbIsHiddenByDefault;
+ bNeedsLayout = true;
+
+ std::shared_ptr<DeckDescriptor> xDeckDescriptor = pParentSidebarController->GetResourceManager()->GetDeckDescriptor(item.msDeckId);
+ if (xDeckDescriptor)
+ xDeckDescriptor->mbIsEnabled = ! item.mbIsHidden;
+
+ }
+ }
+ if (bNeedsLayout)
+ Layout();
+}
+
+void TabBar::UpdateFocusManager(FocusManager& rFocusManager)
+{
+ std::vector<Button*> aButtons;
+ aButtons.reserve(maItems.size()+1);
+
+ aButtons.push_back(mpMenuButton.get());
+ for (auto const& item : maItems)
+ {
+ aButtons.push_back(item.mpButton.get());
+ }
+ rFocusManager.SetButtons(aButtons);
+}
+
+IMPL_LINK_NOARG(TabBar, OnToolboxClicked, Button*, void)
+{
+ if (!mpMenuButton)
+ return;
+
+ std::vector<DeckMenuData> aMenuData;
+
+ for (auto const& item : maItems)
+ {
+ std::shared_ptr<DeckDescriptor> xDeckDescriptor = pParentSidebarController->GetResourceManager()->GetDeckDescriptor(item.msDeckId);
+
+ if (xDeckDescriptor)
+ {
+ DeckMenuData aData;
+ aData.msDisplayName = xDeckDescriptor->msTitle;
+ aData.mbIsCurrentDeck = item.mpButton->IsChecked();
+ aData.mbIsActive = !item.mbIsHidden;
+ aData.mbIsEnabled = item.mpButton->IsEnabled();
+
+ aMenuData.push_back(aData);
+ }
+ }
+
+ maPopupMenuProvider(
+ tools::Rectangle(
+ mpMenuButton->GetPosPixel(),
+ mpMenuButton->GetSizePixel()),
+ aMenuData);
+ mpMenuButton->Check(false);
+}
+
+void TabBar::EnableMenuButton(const bool bEnable)
+{
+ mpMenuButton->Enable(bEnable);
+}
+
+} // end of namespace sfx2::sidebar
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/sidebar/TabItem.cxx b/sfx2/source/sidebar/TabItem.cxx
new file mode 100644
index 000000000..dcf005320
--- /dev/null
+++ b/sfx2/source/sidebar/TabItem.cxx
@@ -0,0 +1,109 @@
+/* -*- 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/TabItem.hxx>
+
+#include <sidebar/DrawHelper.hxx>
+#include <sidebar/Paint.hxx>
+
+#include <sfx2/sidebar/Theme.hxx>
+#include <vcl/event.hxx>
+
+using namespace css;
+using namespace css::uno;
+
+namespace sfx2::sidebar {
+
+TabItem::TabItem (vcl::Window* pParentWindow)
+ : RadioButton(pParentWindow, false, 0)
+ , mbIsLeftButtonDown(false)
+{
+ SetStyle(GetStyle() | WB_TABSTOP | WB_DIALOGCONTROL | WB_NOPOINTERFOCUS);
+ SetBackground(Theme::GetPaint(Theme::Paint_TabBarBackground).GetWallpaper());
+#ifdef DEBUG
+ SetText(OUString("TabItem"));
+#endif
+}
+
+void TabItem::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& /*UpdateArea*/)
+{
+ const bool bIsSelected (IsChecked());
+ const bool bIsHighlighted (IsMouseOver() || HasFocus());
+ DrawHelper::DrawRoundedRectangle(
+ rRenderContext,
+ tools::Rectangle(Point(0,0), GetSizePixel()),
+ Theme::GetInteger(Theme::Int_ButtonCornerRadius),
+ bIsHighlighted||bIsSelected
+ ? Theme::GetColor(Theme::Color_TabItemBorder)
+ : Color(0xffffffff),
+ bIsHighlighted
+ ? Theme::GetPaint(Theme::Paint_TabItemBackgroundHighlight)
+ : Theme::GetPaint(Theme::Paint_TabItemBackgroundNormal));
+
+ const Image aIcon(Button::GetModeImage());
+ const Size aIconSize (aIcon.GetSizePixel());
+ const Point aIconLocation((GetSizePixel().Width() - aIconSize.Width()) / 2,
+ (GetSizePixel().Height() - aIconSize.Height()) / 2);
+ rRenderContext.DrawImage(aIconLocation, aIcon, IsEnabled() ? DrawImageFlags::NONE : DrawImageFlags::Disable);
+}
+
+void TabItem::MouseMove(const MouseEvent& rEvent)
+{
+ if (rEvent.IsEnterWindow() || rEvent.IsLeaveWindow())
+ Invalidate();
+ RadioButton::MouseMove(rEvent);
+}
+
+void TabItem::MouseButtonDown(const MouseEvent& rMouseEvent)
+{
+ if (rMouseEvent.IsLeft())
+ {
+ mbIsLeftButtonDown = true;
+ CaptureMouse();
+ Invalidate();
+ }
+}
+
+void TabItem::MouseButtonUp(const MouseEvent& rMouseEvent)
+{
+ if (IsMouseCaptured())
+ ReleaseMouse();
+
+ if (rMouseEvent.IsLeft())
+ {
+ if (mbIsLeftButtonDown)
+ {
+ Check();
+ Click();
+ vcl::Window* pParent = GetParent();
+ if (pParent)
+ pParent->Invalidate();
+ }
+ }
+
+ if (mbIsLeftButtonDown)
+ {
+ mbIsLeftButtonDown = false;
+ Invalidate();
+ }
+}
+
+} // 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 000000000..7a4d6a785
--- /dev/null
+++ b/sfx2/source/sidebar/Theme.cxx
@@ -0,0 +1,1047 @@
+/* -*- 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 <sidebar/Paint.hxx>
+#include <sidebar/Tools.hxx>
+#include <sfx2/app.hxx>
+
+#include <vcl/svapp.hxx>
+#include <vcl/settings.hxx>
+#include <tools/diagnose_ex.h>
+
+#include <com/sun/star/awt/Rectangle.hpp>
+
+using namespace css;
+using namespace css::uno;
+
+namespace sfx2::sidebar {
+
+Theme& Theme::GetCurrentTheme()
+{
+ return SfxGetpApp()->GetSidebarTheme();
+}
+
+Theme::Theme()
+ : ThemeInterfaceBase(m_aMutex),
+ maImages(),
+ maColors(),
+ maPaints(),
+ maIntegers(),
+ maBooleans(),
+ mbIsHighContrastMode(Application::GetSettings().GetStyleSettings().GetHighContrastMode()),
+ mbIsHighContrastModeSetManually(false),
+ maPropertyNameToIdMap(),
+ maPropertyIdToNameMap(),
+ maRawValues(),
+ maChangeListeners(),
+ maVetoableListeners()
+
+{
+ SetupPropertyMaps();
+}
+
+Theme::~Theme()
+{
+}
+
+Image Theme::GetImage (const ThemeItem eItem)
+{
+ const PropertyType eType (GetPropertyType(eItem));
+ OSL_ASSERT(eType==PT_Image);
+ const sal_Int32 nIndex (GetIndex(eItem, eType));
+ const Theme& rTheme (GetCurrentTheme());
+ return rTheme.maImages[nIndex];
+}
+
+Color Theme::GetColor (const ThemeItem eItem)
+{
+ const PropertyType eType (GetPropertyType(eItem));
+ OSL_ASSERT(eType==PT_Color || eType==PT_Paint);
+ const sal_Int32 nIndex (GetIndex(eItem, eType));
+ const Theme& rTheme (GetCurrentTheme());
+ if (eType == PT_Color)
+ return rTheme.maColors[nIndex];
+ else if (eType == PT_Paint)
+ return rTheme.maPaints[nIndex].GetColor();
+ else
+ return COL_WHITE;
+}
+
+const Paint& Theme::GetPaint (const ThemeItem eItem)
+{
+ const PropertyType eType (GetPropertyType(eItem));
+ OSL_ASSERT(eType==PT_Paint);
+ const sal_Int32 nIndex (GetIndex(eItem, eType));
+ const Theme& rTheme (GetCurrentTheme());
+ return rTheme.maPaints[nIndex];
+}
+
+Wallpaper Theme::GetWallpaper (const ThemeItem eItem)
+{
+ return GetPaint(eItem).GetWallpaper();
+}
+
+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::GetBoolean (const ThemeItem eItem)
+{
+ const PropertyType eType (GetPropertyType(eItem));
+ OSL_ASSERT(eType==PT_Boolean);
+ const sal_Int32 nIndex (GetIndex(eItem, eType));
+ const Theme& rTheme (GetCurrentTheme());
+ return rTheme.maBooleans[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());
+ const bool bUseSystemColors (GetBoolean(Bool_UseSystemColors));
+
+#define Alternatives(n,hc,sys) (mbIsHighContrastMode ? hc : (bUseSystemColors ? sys : n))
+
+ 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 aBorderColor (aBaseBackgroundColor);
+ aBorderColor.DecreaseLuminance(15);
+ Color aSecondColor (aBaseBackgroundColor);
+ aSecondColor.DecreaseLuminance(15);
+
+ setPropertyValue(
+ maPropertyIdToNameMap[Paint_DeckBackground],
+ Any(sal_Int32(aBaseBackgroundColor.GetRGBColor())));
+
+ setPropertyValue(
+ maPropertyIdToNameMap[Paint_DeckTitleBarBackground],
+ Any(sal_Int32(aBaseBackgroundColor.GetRGBColor())));
+ setPropertyValue(
+ maPropertyIdToNameMap[Int_DeckLeftPadding],
+ Any(sal_Int32(2)));
+ setPropertyValue(
+ maPropertyIdToNameMap[Int_DeckTopPadding],
+ Any(sal_Int32(2)));
+ setPropertyValue(
+ maPropertyIdToNameMap[Int_DeckRightPadding],
+ Any(sal_Int32(2)));
+ setPropertyValue(
+ maPropertyIdToNameMap[Int_DeckBottomPadding],
+ Any(sal_Int32(2)));
+ setPropertyValue(
+ maPropertyIdToNameMap[Int_DeckBorderSize],
+ Any(sal_Int32(1)));
+ setPropertyValue(
+ maPropertyIdToNameMap[Int_DeckSeparatorHeight],
+ Any(sal_Int32(1)));
+ setPropertyValue(
+ maPropertyIdToNameMap[Int_ButtonCornerRadius],
+ Any(sal_Int32(3)));
+ setPropertyValue(
+ maPropertyIdToNameMap[Color_DeckTitleFont],
+ Any(sal_Int32(rStyle.GetFontColor().GetRGBColor())));
+ setPropertyValue(
+ maPropertyIdToNameMap[Int_DeckTitleBarHeight],
+ Any(sal_Int32(Alternatives(
+ 26,
+ 26,
+ rStyle.GetFloatTitleHeight()))));
+ setPropertyValue(
+ maPropertyIdToNameMap[Paint_PanelBackground],
+ Any(sal_Int32(aBaseBackgroundColor.GetRGBColor())));
+
+ setPropertyValue(
+ maPropertyIdToNameMap[Paint_PanelTitleBarBackground],
+ Any(Tools::VclToAwtGradient(Gradient(
+ GradientStyle::Linear,
+ aSecondColor.GetRGBColor(),
+ aBaseBackgroundColor.GetRGBColor()
+ ))));
+ setPropertyValue(
+ maPropertyIdToNameMap[Color_PanelTitleFont],
+ Any(sal_Int32(mbIsHighContrastMode ? 0x00ff00 : 0x262626)));
+ setPropertyValue(
+ maPropertyIdToNameMap[Int_PanelTitleBarHeight],
+ Any(sal_Int32(Alternatives(
+ 26,
+ 26,
+ rStyle.GetTitleHeight()))));
+ setPropertyValue(
+ maPropertyIdToNameMap[Paint_TabBarBackground],
+ Any(sal_Int32(aBaseBackgroundColor.GetRGBColor())));
+ setPropertyValue(
+ maPropertyIdToNameMap[Int_TabBarLeftPadding],
+ Any(sal_Int32(2)));
+ setPropertyValue(
+ maPropertyIdToNameMap[Int_TabBarTopPadding],
+ Any(sal_Int32(2)));
+ setPropertyValue(
+ maPropertyIdToNameMap[Int_TabBarRightPadding],
+ Any(sal_Int32(2)));
+ setPropertyValue(
+ maPropertyIdToNameMap[Int_TabBarBottomPadding],
+ Any(sal_Int32(2)));
+
+ setPropertyValue(
+ maPropertyIdToNameMap[Int_TabMenuPadding],
+ Any(sal_Int32(6)));
+ setPropertyValue(
+ maPropertyIdToNameMap[Color_TabMenuSeparator],
+ Any(sal_Int32(aBorderColor.GetRGBColor())));
+ setPropertyValue(
+ maPropertyIdToNameMap[Int_TabMenuSeparatorPadding],
+ Any(sal_Int32(7)));
+
+ setPropertyValue(
+ maPropertyIdToNameMap[Int_TabItemWidth],
+ Any(sal_Int32(32)));
+ setPropertyValue(
+ maPropertyIdToNameMap[Int_TabItemHeight],
+ Any(sal_Int32(32)));
+ setPropertyValue(
+ maPropertyIdToNameMap[Color_TabItemBorder],
+ Any(sal_Int32(rStyle.GetActiveBorderColor().GetRGBColor())));
+
+ setPropertyValue(
+ maPropertyIdToNameMap[Paint_DropDownBackground],
+ Any(sal_Int32(aBaseBackgroundColor.GetRGBColor())));
+ setPropertyValue(
+ maPropertyIdToNameMap[Color_DropDownBorder],
+ Any(sal_Int32(rStyle.GetActiveBorderColor().GetRGBColor())));
+
+ setPropertyValue(
+ maPropertyIdToNameMap[Color_Highlight],
+ Any(sal_Int32(rStyle.GetHighlightColor().GetRGBColor())));
+ setPropertyValue(
+ maPropertyIdToNameMap[Color_HighlightText],
+ Any(sal_Int32(rStyle.GetHighlightTextColor().GetRGBColor())));
+
+ setPropertyValue(
+ maPropertyIdToNameMap[Paint_TabItemBackgroundNormal],
+ Any());
+ setPropertyValue(
+ maPropertyIdToNameMap[Paint_TabItemBackgroundHighlight],
+ Any(sal_Int32(rStyle.GetActiveTabColor().GetRGBColor())));
+
+ setPropertyValue(
+ maPropertyIdToNameMap[Paint_HorizontalBorder],
+ Any(sal_Int32(aBorderColor.GetRGBColor())));
+
+ setPropertyValue(
+ maPropertyIdToNameMap[Paint_VerticalBorder],
+ Any(sal_Int32(aBorderColor.GetRGBColor())));
+ setPropertyValue(
+ maPropertyIdToNameMap[Image_Grip],
+ Any(OUString("private:graphicrepository/sfx2/res/grip.png")));
+ setPropertyValue(
+ maPropertyIdToNameMap[Image_Expand],
+ Any(OUString("private:graphicrepository/res/plus.png")));
+ setPropertyValue(
+ maPropertyIdToNameMap[Image_Collapse],
+ Any(OUString("private:graphicrepository/res/minus.png")));
+ setPropertyValue(
+ maPropertyIdToNameMap[Image_TabBarMenu],
+ Any(OUString("private:graphicrepository/sfx2/res/symphony/open_more.png")));
+ setPropertyValue(
+ maPropertyIdToNameMap[Image_PanelMenu],
+ Any(OUString("private:graphicrepository/sfx2/res/symphony/morebutton.png")));
+ setPropertyValue(
+ maPropertyIdToNameMap[Image_Closer],
+ Any(OUString("private:graphicrepository/sfx2/res/closedoc.png")));
+ setPropertyValue(
+ maPropertyIdToNameMap[Image_CloseIndicator],
+ Any(OUString("private:graphicrepository/cmd/lc_decrementlevel.png")));
+
+ // Gradient style
+ Color aGradientStop2 (aBaseBackgroundColor);
+ aGradientStop2.IncreaseLuminance(17);
+ Color aToolBoxBorderColor (aBaseBackgroundColor);
+ aToolBoxBorderColor.DecreaseLuminance(12);
+ setPropertyValue(
+ maPropertyIdToNameMap[Paint_ToolBoxBackground],
+ Any(Tools::VclToAwtGradient(Gradient(
+ GradientStyle::Linear,
+ aBaseBackgroundColor.GetRGBColor(),
+ aGradientStop2.GetRGBColor()
+ ))));
+ setPropertyValue(
+ maPropertyIdToNameMap[Paint_ToolBoxBorderTopLeft],
+ mbIsHighContrastMode
+ ? Any(util::Color(sal_uInt32(0x00ff00)))
+ : Any(util::Color(aToolBoxBorderColor.GetRGBColor())));
+ setPropertyValue(
+ maPropertyIdToNameMap[Paint_ToolBoxBorderCenterCorners],
+ mbIsHighContrastMode
+ ? Any(util::Color(sal_uInt32(0x00ff00)))
+ : Any(util::Color(aToolBoxBorderColor.GetRGBColor())));
+ setPropertyValue(
+ maPropertyIdToNameMap[Paint_ToolBoxBorderBottomRight],
+ mbIsHighContrastMode
+ ? Any(util::Color(sal_uInt32(0x00ff00)))
+ : Any(util::Color(aToolBoxBorderColor.GetRGBColor())));
+ setPropertyValue(
+ maPropertyIdToNameMap[Rect_ToolBoxPadding],
+ Any(awt::Rectangle(2,2,2,2)));
+ setPropertyValue(
+ maPropertyIdToNameMap[Rect_ToolBoxBorder],
+ Any(awt::Rectangle(1,1,1,1)));
+ }
+ catch(beans::UnknownPropertyException const &)
+ {
+ DBG_UNHANDLED_EXCEPTION("sfx", "unknown property");
+ OSL_ASSERT(false);
+ }
+}
+
+void SAL_CALL Theme::disposing()
+{
+ ChangeListeners aListeners;
+ aListeners.swap(maChangeListeners);
+
+ const lang::EventObject aEvent (static_cast<XWeak*>(this));
+
+ for (const auto& rContainer : aListeners)
+ {
+ for (const auto& rxListener : rContainer.second)
+ {
+ try
+ {
+ rxListener->disposing(aEvent);
+ }
+ catch(const Exception&)
+ {
+ }
+ }
+ }
+}
+
+Reference<beans::XPropertySet> Theme::GetPropertySet()
+{
+ return Reference<beans::XPropertySet>(static_cast<XWeak*>(&GetCurrentTheme()), UNO_QUERY);
+}
+
+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)
+{
+ 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(
+ static_cast<XWeak*>(this),
+ 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)
+{
+ 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)
+{
+ 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)
+{
+ 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)
+{
+ 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)
+{
+ 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()
+{
+ ::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)
+{
+ 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)
+{
+ 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_Rect_);
+ maImages.resize(Image_Color_ - Pre_Image_ - 1);
+ maColors.resize(Color_Paint_ - Image_Color_ - 1);
+ maPaints.resize(Paint_Int_ - Color_Paint_ - 1);
+ maIntegers.resize(Int_Bool_ - Paint_Int_ - 1);
+ maBooleans.resize(Bool_Rect_ - Int_Bool_ - 1);
+ maRectangles.resize(Post_Rect_ - Bool_Rect_ - 1);
+
+ maPropertyNameToIdMap["Image_Grip"]=Image_Grip;
+ maPropertyIdToNameMap[Image_Grip]="Image_Grip";
+
+ maPropertyNameToIdMap["Image_Expand"]=Image_Expand;
+ maPropertyIdToNameMap[Image_Expand]="Image_Expand";
+
+ maPropertyNameToIdMap["Image_Collapse"]=Image_Collapse;
+ maPropertyIdToNameMap[Image_Collapse]="Image_Collapse";
+
+ maPropertyNameToIdMap["Image_TabBarMenu"]=Image_TabBarMenu;
+ maPropertyIdToNameMap[Image_TabBarMenu]="Image_TabBarMenu";
+
+ maPropertyNameToIdMap["Image_PanelMenu"]=Image_PanelMenu;
+ maPropertyIdToNameMap[Image_PanelMenu]="Image_PanelMenu";
+
+ maPropertyNameToIdMap["Image_Closer"]=Image_Closer;
+ maPropertyIdToNameMap[Image_Closer]="Image_Closer";
+
+ maPropertyNameToIdMap["Image_CloseIndicator"]=Image_CloseIndicator;
+ maPropertyIdToNameMap[Image_CloseIndicator]="Image_CloseIndicator";
+
+
+ maPropertyNameToIdMap["Color_DeckTitleFont"]=Color_DeckTitleFont;
+ maPropertyIdToNameMap[Color_DeckTitleFont]="Color_DeckTitleFont";
+
+ maPropertyNameToIdMap["Color_PanelTitleFont"]=Color_PanelTitleFont;
+ maPropertyIdToNameMap[Color_PanelTitleFont]="Color_PanelTitleFont";
+
+ maPropertyNameToIdMap["Color_TabMenuSeparator"]=Color_TabMenuSeparator;
+ maPropertyIdToNameMap[Color_TabMenuSeparator]="Color_TabMenuSeparator";
+
+ maPropertyNameToIdMap["Color_TabItemBorder"]=Color_TabItemBorder;
+ maPropertyIdToNameMap[Color_TabItemBorder]="Color_TabItemBorder";
+
+ maPropertyNameToIdMap["Color_DropDownBorder"]=Color_DropDownBorder;
+ maPropertyIdToNameMap[Color_DropDownBorder]="Color_DropDownBorder";
+
+ maPropertyNameToIdMap["Color_Highlight"]=Color_Highlight;
+ maPropertyIdToNameMap[Color_Highlight]="Color_Highlight";
+
+ maPropertyNameToIdMap["Color_HighlightText"]=Color_HighlightText;
+ maPropertyIdToNameMap[Color_HighlightText]="Color_HighlightText";
+
+
+ maPropertyNameToIdMap["Paint_DeckBackground"]=Paint_DeckBackground;
+ maPropertyIdToNameMap[Paint_DeckBackground]="Paint_DeckBackground";
+
+ maPropertyNameToIdMap["Paint_DeckTitleBarBackground"]=Paint_DeckTitleBarBackground;
+ maPropertyIdToNameMap[Paint_DeckTitleBarBackground]="Paint_DeckTitleBarBackground";
+
+ maPropertyNameToIdMap["Paint_PanelBackground"]=Paint_PanelBackground;
+ maPropertyIdToNameMap[Paint_PanelBackground]="Paint_PanelBackground";
+
+ maPropertyNameToIdMap["Paint_PanelTitleBarBackground"]=Paint_PanelTitleBarBackground;
+ maPropertyIdToNameMap[Paint_PanelTitleBarBackground]="Paint_PanelTitleBarBackground";
+
+ maPropertyNameToIdMap["Paint_TabBarBackground"]=Paint_TabBarBackground;
+ maPropertyIdToNameMap[Paint_TabBarBackground]="Paint_TabBarBackground";
+
+ maPropertyNameToIdMap["Paint_TabItemBackgroundNormal"]=Paint_TabItemBackgroundNormal;
+ maPropertyIdToNameMap[Paint_TabItemBackgroundNormal]="Paint_TabItemBackgroundNormal";
+
+ maPropertyNameToIdMap["Paint_TabItemBackgroundHighlight"]=Paint_TabItemBackgroundHighlight;
+ maPropertyIdToNameMap[Paint_TabItemBackgroundHighlight]="Paint_TabItemBackgroundHighlight";
+
+ maPropertyNameToIdMap["Paint_HorizontalBorder"]=Paint_HorizontalBorder;
+ maPropertyIdToNameMap[Paint_HorizontalBorder]="Paint_HorizontalBorder";
+
+ maPropertyNameToIdMap["Paint_VerticalBorder"]=Paint_VerticalBorder;
+ maPropertyIdToNameMap[Paint_VerticalBorder]="Paint_VerticalBorder";
+
+ maPropertyNameToIdMap["Paint_ToolBoxBackground"]=Paint_ToolBoxBackground;
+ maPropertyIdToNameMap[Paint_ToolBoxBackground]="Paint_ToolBoxBackground";
+
+ maPropertyNameToIdMap["Paint_ToolBoxBorderTopLeft"]=Paint_ToolBoxBorderTopLeft;
+ maPropertyIdToNameMap[Paint_ToolBoxBorderTopLeft]="Paint_ToolBoxBorderTopLeft";
+
+ maPropertyNameToIdMap["Paint_ToolBoxBorderCenterCorners"]=Paint_ToolBoxBorderCenterCorners;
+ maPropertyIdToNameMap[Paint_ToolBoxBorderCenterCorners]="Paint_ToolBoxBorderCenterCorners";
+
+ maPropertyNameToIdMap["Paint_ToolBoxBorderBottomRight"]=Paint_ToolBoxBorderBottomRight;
+ maPropertyIdToNameMap[Paint_ToolBoxBorderBottomRight]="Paint_ToolBoxBorderBottomRight";
+
+ maPropertyNameToIdMap["Paint_DropDownBackground"]=Paint_DropDownBackground;
+ maPropertyIdToNameMap[Paint_DropDownBackground]="Paint_DropDownBackground";
+
+
+ maPropertyNameToIdMap["Int_DeckTitleBarHeight"]=Int_DeckTitleBarHeight;
+ maPropertyIdToNameMap[Int_DeckTitleBarHeight]="Int_DeckTitleBarHeight";
+
+ maPropertyNameToIdMap["Int_DeckBorderSize"]=Int_DeckBorderSize;
+ maPropertyIdToNameMap[Int_DeckBorderSize]="Int_DeckBorderSize";
+
+ maPropertyNameToIdMap["Int_DeckSeparatorHeight"]=Int_DeckSeparatorHeight;
+ maPropertyIdToNameMap[Int_DeckSeparatorHeight]="Int_DeckSeparatorHeight";
+
+ maPropertyNameToIdMap["Int_PanelTitleBarHeight"]=Int_PanelTitleBarHeight;
+ maPropertyIdToNameMap[Int_PanelTitleBarHeight]="Int_PanelTitleBarHeight";
+
+ maPropertyNameToIdMap["Int_TabMenuPadding"]=Int_TabMenuPadding;
+ maPropertyIdToNameMap[Int_TabMenuPadding]="Int_TabMenuPadding";
+
+ maPropertyNameToIdMap["Int_TabMenuSeparatorPadding"]=Int_TabMenuSeparatorPadding;
+ maPropertyIdToNameMap[Int_TabMenuSeparatorPadding]="Int_TabMenuSeparatorPadding";
+
+ maPropertyNameToIdMap["Int_TabItemWidth"]=Int_TabItemWidth;
+ maPropertyIdToNameMap[Int_TabItemWidth]="Int_TabItemWidth";
+
+ maPropertyNameToIdMap["Int_TabItemHeight"]=Int_TabItemHeight;
+ maPropertyIdToNameMap[Int_TabItemHeight]="Int_TabItemHeight";
+
+ 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["Int_TabBarLeftPadding"]=Int_TabBarLeftPadding;
+ maPropertyIdToNameMap[Int_TabBarLeftPadding]="Int_TabBarLeftPadding";
+
+ maPropertyNameToIdMap["Int_TabBarTopPadding"]=Int_TabBarTopPadding;
+ maPropertyIdToNameMap[Int_TabBarTopPadding]="Int_TabBarTopPadding";
+
+ maPropertyNameToIdMap["Int_TabBarRightPadding"]=Int_TabBarRightPadding;
+ maPropertyIdToNameMap[Int_TabBarRightPadding]="Int_TabBarRightPadding";
+
+ maPropertyNameToIdMap["Int_TabBarBottomPadding"]=Int_TabBarBottomPadding;
+ maPropertyIdToNameMap[Int_TabBarBottomPadding]="Int_TabBarBottomPadding";
+
+ maPropertyNameToIdMap["Int_ButtonCornerRadius"]=Int_ButtonCornerRadius;
+ maPropertyIdToNameMap[Int_ButtonCornerRadius]="Int_ButtonCornerRadius";
+
+
+ maPropertyNameToIdMap["Bool_UseSystemColors"]=Bool_UseSystemColors;
+ maPropertyIdToNameMap[Bool_UseSystemColors]="Bool_UseSystemColors";
+
+ maPropertyNameToIdMap["Bool_IsHighContrastModeActive"]=Bool_IsHighContrastModeActive;
+ maPropertyIdToNameMap[Bool_IsHighContrastModeActive]="Bool_IsHighContrastModeActive";
+
+
+ maPropertyNameToIdMap["Rect_ToolBoxPadding"]=Rect_ToolBoxPadding;
+ maPropertyIdToNameMap[Rect_ToolBoxPadding]="Rect_ToolBoxPadding";
+
+ maPropertyNameToIdMap["Rect_ToolBoxBorder"]=Rect_ToolBoxBorder;
+ maPropertyIdToNameMap[Rect_ToolBoxBorder]="Rect_ToolBoxBorder";
+
+ maRawValues.resize(maPropertyIdToNameMap.size());
+}
+
+Theme::PropertyType Theme::GetPropertyType (const ThemeItem eItem)
+{
+ switch(eItem)
+ {
+ case Image_Grip:
+ case Image_Expand:
+ case Image_Collapse:
+ case Image_TabBarMenu:
+ case Image_PanelMenu:
+ case Image_Closer:
+ case Image_CloseIndicator:
+ return PT_Image;
+
+ case Color_DeckTitleFont:
+ case Color_PanelTitleFont:
+ case Color_TabMenuSeparator:
+ case Color_TabItemBorder:
+ case Color_DropDownBorder:
+ case Color_Highlight:
+ case Color_HighlightText:
+ return PT_Color;
+
+ case Paint_DeckBackground:
+ case Paint_DeckTitleBarBackground:
+ case Paint_PanelBackground:
+ case Paint_PanelTitleBarBackground:
+ case Paint_TabBarBackground:
+ case Paint_TabItemBackgroundNormal:
+ case Paint_TabItemBackgroundHighlight:
+ case Paint_HorizontalBorder:
+ case Paint_VerticalBorder:
+ case Paint_ToolBoxBackground:
+ case Paint_ToolBoxBorderTopLeft:
+ case Paint_ToolBoxBorderCenterCorners:
+ case Paint_ToolBoxBorderBottomRight:
+ case Paint_DropDownBackground:
+ return PT_Paint;
+
+ case Int_DeckTitleBarHeight:
+ case Int_DeckBorderSize:
+ case Int_DeckSeparatorHeight:
+ case Int_PanelTitleBarHeight:
+ case Int_TabMenuPadding:
+ case Int_TabMenuSeparatorPadding:
+ case Int_TabItemWidth:
+ case Int_TabItemHeight:
+ case Int_DeckLeftPadding:
+ case Int_DeckTopPadding:
+ case Int_DeckRightPadding:
+ case Int_DeckBottomPadding:
+ case Int_TabBarLeftPadding:
+ case Int_TabBarTopPadding:
+ case Int_TabBarRightPadding:
+ case Int_TabBarBottomPadding:
+ case Int_ButtonCornerRadius:
+ return PT_Integer;
+
+ case Bool_UseSystemColors:
+ case Bool_IsHighContrastModeActive:
+ return PT_Boolean;
+
+ case Rect_ToolBoxBorder:
+ case Rect_ToolBoxPadding:
+ return PT_Rectangle;
+
+ default:
+ return PT_Invalid;
+ }
+}
+
+css::uno::Type const & Theme::GetCppuType (const PropertyType eType)
+{
+ switch(eType)
+ {
+ case PT_Image:
+ return cppu::UnoType<OUString>::get();
+
+ case PT_Color:
+ return cppu::UnoType<sal_uInt32>::get();
+
+ case PT_Paint:
+ return cppu::UnoType<void>::get();
+
+ case PT_Integer:
+ return cppu::UnoType<sal_Int32>::get();
+
+ case PT_Boolean:
+ return cppu::UnoType<sal_Bool>::get();
+
+ case PT_Rectangle:
+ return cppu::UnoType<awt::Rectangle>::get();
+
+ case PT_Invalid:
+ default:
+ return cppu::UnoType<void>::get();
+ }
+}
+
+sal_Int32 Theme::GetIndex (const ThemeItem eItem, const PropertyType eType)
+{
+ switch(eType)
+ {
+ case PT_Image:
+ return eItem - Pre_Image_-1;
+ case PT_Color:
+ return eItem - Image_Color_-1;
+ case PT_Paint:
+ return eItem - Color_Paint_-1;
+ case PT_Integer:
+ return eItem - Paint_Int_-1;
+ case PT_Boolean:
+ return eItem - Int_Bool_-1;
+ case PT_Rectangle:
+ return eItem - Bool_Rect_-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_Image:
+ {
+ OUString sURL;
+ if (rValue >>= sURL)
+ {
+ maImages[nIndex] = Tools::GetImage(sURL, nullptr);
+ }
+ break;
+ }
+ case PT_Color:
+ {
+ sal_Int32 nColorValue (0);
+ if (rValue >>= nColorValue)
+ {
+ maColors[nIndex] = Color(nColorValue);
+ }
+ break;
+ }
+ case PT_Paint:
+ {
+ maPaints[nIndex] = Paint::Create(rValue);
+ 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_Rectangle:
+ {
+ awt::Rectangle aBox;
+ if (rValue >>= aBox)
+ {
+ maRectangles[nIndex] = tools::Rectangle(
+ aBox.X,
+ aBox.Y,
+ aBox.Width,
+ aBox.Height);
+ }
+ 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 000000000..40627a6c2
--- /dev/null
+++ b/sfx2/source/sidebar/TitleBar.cxx
@@ -0,0 +1,172 @@
+/* -*- 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 <sidebar/Paint.hxx>
+#include <sidebar/AccessibleTitleBar.hxx>
+
+#include <com/sun/star/accessibility/AccessibleRole.hpp>
+
+namespace
+{
+ const static sal_Int32 gnLeftIconSpace (3);
+ const static sal_Int32 gnRightIconSpace (3);
+}
+
+namespace sfx2::sidebar {
+
+TitleBar::TitleBar(const OUString& rsTitle,
+ vcl::Window* pParentWindow,
+ const sidebar::Paint& rInitialBackgroundPaint)
+ : Window(pParentWindow)
+ , maToolBox(VclPtr<SidebarToolBox>::Create(this))
+ , msTitle(rsTitle)
+ , maIcon()
+ , maBackgroundPaint(rInitialBackgroundPaint)
+{
+ maToolBox->SetSelectHdl(LINK(this, TitleBar, SelectionHandler));
+}
+
+TitleBar::~TitleBar()
+{
+ disposeOnce();
+}
+
+void TitleBar::dispose()
+{
+ maToolBox.disposeAndClear();
+ vcl::Window::dispose();
+}
+
+void TitleBar::SetTitle(const OUString& rsTitle)
+{
+ msTitle = rsTitle;
+ Invalidate();
+}
+
+void TitleBar::SetIcon(const Image& rIcon)
+{
+ maIcon = rIcon;
+ Invalidate();
+}
+
+void TitleBar::ApplySettings(vcl::RenderContext& rRenderContext)
+{
+ rRenderContext.SetBackground(maBackgroundPaint.GetWallpaper());
+}
+
+void TitleBar::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& /*rUpdateArea*/)
+{
+ // Paint title bar background.
+ Size aWindowSize (GetSizePixel());
+ tools::Rectangle aTitleBarBox(0,0, aWindowSize.Width(), aWindowSize.Height());
+
+ PaintDecoration(rRenderContext);
+ const tools::Rectangle aTitleBox(GetTitleArea(aTitleBarBox));
+ PaintTitle(rRenderContext, aTitleBox);
+ PaintFocus(rRenderContext, aTitleBox);
+}
+
+void TitleBar::DataChanged (const DataChangedEvent& /*rEvent*/)
+{
+ maBackgroundPaint = GetBackgroundPaint();
+ Invalidate();
+}
+
+void TitleBar::setPosSizePixel (long nX, long nY, long nWidth, long nHeight, PosSizeFlags nFlags)
+{
+ Window::setPosSizePixel(nX, nY, nWidth, nHeight, nFlags);
+
+ // Place the toolbox.
+ const sal_Int32 nToolBoxWidth (maToolBox->GetItemPosRect(0).GetWidth());
+ maToolBox->setPosSizePixel(nWidth - nToolBoxWidth,0, nToolBoxWidth, nHeight);
+ maToolBox->Show();
+}
+
+void TitleBar::HandleToolBoxItemClick(const sal_uInt16 /*nItemIndex*/)
+{
+ // Any real processing has to be done in derived class.
+}
+
+css::uno::Reference<css::accessibility::XAccessible> TitleBar::CreateAccessible()
+{
+ SetAccessibleRole(css::accessibility::AccessibleRole::PANEL);
+ return AccessibleTitleBar::Create(*this);
+}
+
+void TitleBar::PaintTitle(vcl::RenderContext& rRenderContext, const tools::Rectangle& rTitleBox)
+{
+ rRenderContext.Push(PushFlags::FONT | PushFlags::TEXTCOLOR);
+
+ tools::Rectangle aTitleBox(rTitleBox);
+
+ // When there is an icon then paint it at the left of the given
+ // box.
+ if (!!maIcon)
+ {
+ rRenderContext.DrawImage(Point(aTitleBox.Left() + gnLeftIconSpace,
+ aTitleBox.Top() + (aTitleBox.GetHeight() - maIcon.GetSizePixel().Height()) / 2),
+ maIcon);
+ aTitleBox.AdjustLeft(gnLeftIconSpace + maIcon.GetSizePixel().Width() + gnRightIconSpace );
+ }
+
+ vcl::Font aFont(rRenderContext.GetFont());
+ aFont.SetWeight(WEIGHT_BOLD);
+ rRenderContext.SetFont(aFont);
+
+ // Paint title bar text.
+ rRenderContext.SetTextColor(rRenderContext.GetTextColor());
+ rRenderContext.DrawText(aTitleBox, msTitle, DrawTextFlags::Left | DrawTextFlags::VCenter);
+ rRenderContext.Pop();
+}
+
+void TitleBar::PaintFocus(vcl::RenderContext& rRenderContext, const tools::Rectangle& rFocusBox)
+{
+ rRenderContext.Push(PushFlags::FONT | PushFlags::TEXTCOLOR);
+
+ vcl::Font aFont(rRenderContext.GetFont());
+ aFont.SetWeight(WEIGHT_BOLD);
+ rRenderContext.SetFont(aFont);
+
+ const tools::Rectangle aTextBox(rRenderContext.GetTextRect(rFocusBox, msTitle, DrawTextFlags::Left | DrawTextFlags::VCenter));
+
+ const tools::Rectangle aLargerTextBox(aTextBox.Left() - 2,
+ aTextBox.Top() - 2,
+ aTextBox.Right() + 2,
+ aTextBox.Bottom() + 2);
+
+ if (HasFocus())
+ Window::ShowFocus(aLargerTextBox);
+ else
+ Window::HideFocus();
+
+ rRenderContext.Pop();
+}
+
+IMPL_LINK(TitleBar, SelectionHandler, ToolBox*, pToolBox, void)
+{
+ OSL_ASSERT(maToolBox.get()==pToolBox);
+ const sal_uInt16 nItemId (maToolBox->GetHighlightItemId());
+
+ HandleToolBoxItemClick(nItemId);
+}
+
+} // 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 000000000..eb936c4ff
--- /dev/null
+++ b/sfx2/source/sidebar/Tools.cxx
@@ -0,0 +1,139 @@
+/* -*- 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/processfactory.hxx>
+#include <vcl/commandinfoprovider.hxx>
+#include <vcl/gradient.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>
+
+using namespace css;
+using namespace css::uno;
+
+namespace sfx2::sidebar {
+
+Image Tools::GetImage (
+ const OUString& rsImageURL,
+ const OUString& rsHighContrastImageURL,
+ const Reference<frame::XFrame>& rxFrame)
+{
+ if (Theme::IsHighContrastMode())
+ return GetImage(rsHighContrastImageURL, rxFrame);
+ else
+ return GetImage(rsImageURL, rxFrame);
+}
+
+Image Tools::GetImage (
+ const OUString& rsURL,
+ const Reference<frame::XFrame>& rxFrame)
+{
+ if (rsURL.getLength() > 0)
+ {
+ if (rsURL.startsWith(".uno:"))
+ return vcl::CommandInfoProvider::GetImageForCommand(rsURL, rxFrame);
+
+ else
+ return Image(rsURL);
+ }
+ return Image();
+}
+
+css::awt::Gradient Tools::VclToAwtGradient (const Gradient& rVclGradient)
+{
+ css::awt::Gradient aAwtGradient (
+ awt::GradientStyle(rVclGradient.GetStyle()),
+ sal_Int32(rVclGradient.GetStartColor().GetRGBColor()),
+ sal_Int32(rVclGradient.GetEndColor().GetRGBColor()),
+ rVclGradient.GetAngle(),
+ rVclGradient.GetBorder(),
+ rVclGradient.GetOfsX(),
+ rVclGradient.GetOfsY(),
+ rVclGradient.GetStartIntensity(),
+ rVclGradient.GetEndIntensity(),
+ rVclGradient.GetSteps());
+ return aAwtGradient;
+}
+
+Gradient Tools::AwtToVclGradient (const css::awt::Gradient& rAwtGradient)
+{
+ Gradient aVclGradient (
+ GradientStyle(rAwtGradient.Style),
+ Color(rAwtGradient.StartColor),
+ Color(rAwtGradient.EndColor));
+ aVclGradient.SetAngle(rAwtGradient.Angle);
+ aVclGradient.SetBorder(rAwtGradient.Border);
+ aVclGradient.SetOfsX(rAwtGradient.XOffset);
+ aVclGradient.SetOfsY(rAwtGradient.YOffset);
+ aVclGradient.SetStartIntensity(rAwtGradient.StartIntensity);
+ aVclGradient.SetEndIntensity(rAwtGradient.EndIntensity);
+ aVclGradient.SetSteps(rAwtGradient.StepCount);
+
+ return aVclGradient;
+}
+
+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 000000000..122afbe01
--- /dev/null
+++ b/sfx2/source/sidebar/UnoDeck.cxx
@@ -0,0 +1,278 @@
+/* -*- 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 <vcl/svapp.hxx>
+
+using namespace css;
+using namespace ::sfx2::sidebar;
+
+SfxUnoDeck::SfxUnoDeck(const uno::Reference<frame::XFrame>& rFrame, const OUString& deckId):
+xFrame(rFrame),
+mDeckId(deckId)
+{
+
+}
+SidebarController* SfxUnoDeck::getSidebarController()
+{
+ return SidebarController::GetSidebarControllerForFrame(xFrame);
+}
+
+OUString SAL_CALL SfxUnoDeck::getId()
+{
+ SolarMutexGuard aGuard;
+
+ 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;
+ }
+
+ VclPtr<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;
+ const VclPtr<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();
+
+ if (bActivate)
+ pSidebarController->SwitchToDeck(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(ResourceManager::DeckContextDescriptorContainer aDecks)
+{
+ SidebarController* pSidebarController = getSidebarController();
+
+ ResourceManager::DeckContextDescriptorContainer::const_iterator iDeck = aDecks.begin();
+ sal_Int32 minIndex = pSidebarController->GetResourceManager()->GetDeckDescriptor(iDeck->msId)->mnOrderIndex;
+
+ for (auto const& deck : aDecks)
+ {
+ sal_Int32 index = pSidebarController->GetResourceManager()->GetDeckDescriptor(deck.msId)->mnOrderIndex;
+ if(minIndex > index)
+ minIndex = index;
+ }
+ return minIndex;
+}
+
+sal_Int32 SfxUnoDeck::GetMaxOrderIndex(ResourceManager::DeckContextDescriptorContainer aDecks)
+{
+ SidebarController* pSidebarController = getSidebarController();
+
+ sal_Int32 maxIndex = pSidebarController->GetResourceManager()->GetDeckDescriptor(aDecks.begin()->msId)->mnOrderIndex;
+
+ for (auto const& deck : aDecks)
+ {
+ 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 000000000..9b3ad8999
--- /dev/null
+++ b/sfx2/source/sidebar/UnoDecks.cxx
@@ -0,0 +1,149 @@
+/* -*- 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 <vcl/svapp.hxx>
+
+using namespace css;
+using namespace ::sfx2::sidebar;
+
+SfxUnoDecks::SfxUnoDecks(const uno::Reference<frame::XFrame>& rFrame):
+xFrame(rFrame)
+{
+}
+
+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());
+
+ long n = 0;
+
+ for (const auto& rDeck : aDecks)
+ {
+ deckList[n] = rDeck.msId;
+ n++;
+ }
+ }
+
+ 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()
+{
+ SolarMutexGuard aGuard;
+
+ 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 000000000..eee70429a
--- /dev/null
+++ b/sfx2/source/sidebar/UnoPanel.cxx
@@ -0,0 +1,290 @@
+/* -*- 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 <vcl/svapp.hxx>
+
+using namespace css;
+using namespace ::sfx2::sidebar;
+
+SfxUnoPanel::SfxUnoPanel(const uno::Reference<frame::XFrame>& rFrame, const OUString& panelId, const OUString& deckId):
+xFrame(rFrame),
+mPanelId(panelId),
+mDeckId(deckId),
+mpDeck(),
+mpPanel()
+{
+ SidebarController* pSidebarController = getSidebarController();
+
+ pSidebarController->CreateDeck(mDeckId); // creates deck object is not already
+ mpDeck = pSidebarController->GetResourceManager()->GetDeckDescriptor(mDeckId)->mpDeck;
+ mpPanel = 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;
+
+ VclPtr<PanelTitleBar> pTitleBar = mpPanel->GetTitleBar();
+ 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;
+ VclPtr<PanelTitleBar> pTitleBar = mpPanel->GetTitleBar();
+ if (pTitleBar)
+ pTitleBar->SetTitle(newTitle);
+ }
+}
+
+sal_Bool SAL_CALL SfxUnoPanel::isExpanded()
+{
+ SolarMutexGuard aGuard;
+
+ return mpPanel->IsExpanded();
+}
+
+
+void SAL_CALL SfxUnoPanel::expand( const sal_Bool bCollapseOther )
+{
+
+ SolarMutexGuard aGuard;
+
+ mpPanel->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;
+
+ mpPanel->SetExpanded(false);
+ SidebarController* pSidebarController = getSidebarController();
+ pSidebarController->NotifyResize();
+}
+
+uno::Reference<awt::XWindow> SAL_CALL SfxUnoPanel::getDialog()
+{
+ SolarMutexGuard aGuard;
+
+ return mpPanel->GetElementWindow();
+}
+
+
+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(ResourceManager::PanelContextDescriptorContainer aPanels)
+{
+ SidebarController* pSidebarController = getSidebarController();
+
+ sal_Int32 minIndex = pSidebarController->GetResourceManager()->GetPanelDescriptor(aPanels.begin()->msId)->mnOrderIndex;
+
+ for (auto const& panel : aPanels)
+ {
+ sal_Int32 index = pSidebarController->GetResourceManager()->GetPanelDescriptor(panel.msId)->mnOrderIndex;
+ if(minIndex > index)
+ minIndex = index;
+ }
+ return minIndex;
+}
+
+sal_Int32 SfxUnoPanel::GetMaxOrderIndex(ResourceManager::PanelContextDescriptorContainer aPanels)
+{
+ SidebarController* pSidebarController = getSidebarController();
+
+ sal_Int32 maxIndex = pSidebarController->GetResourceManager()->GetPanelDescriptor(aPanels.begin()->msId)->mnOrderIndex;
+
+ for (auto const& panel : aPanels)
+ {
+ 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 000000000..1bc0ded10
--- /dev/null
+++ b/sfx2/source/sidebar/UnoPanels.cxx
@@ -0,0 +1,161 @@
+/* -*- 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 <vcl/svapp.hxx>
+
+using namespace css;
+using namespace ::sfx2::sidebar;
+
+SfxUnoPanels::SfxUnoPanels(const uno::Reference<frame::XFrame>& rFrame, const OUString& deckId):
+xFrame(rFrame),
+mDeckId(deckId)
+{
+}
+
+SidebarController* SfxUnoPanels::getSidebarController()
+{
+ return SidebarController::GetSidebarControllerForFrame(xFrame);
+}
+
+OUString SAL_CALL SfxUnoPanels::getDeckId()
+{
+ SolarMutexGuard aGuard;
+
+ 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());
+
+ long n = 0;
+
+ for (const auto& rPanel : aPanels)
+ {
+ panelList[n] = rPanel.msId;
+ n++;
+ }
+ }
+
+ 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()
+{
+ SolarMutexGuard aGuard;
+
+ 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 000000000..20a8728d5
--- /dev/null
+++ b/sfx2/source/sidebar/UnoSidebar.cxx
@@ -0,0 +1,101 @@
+/* -*- 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 <vcl/svapp.hxx>
+
+
+using namespace css;
+using namespace ::sfx2::sidebar;
+
+using ::com::sun::star::uno::RuntimeException;
+
+SfxUnoSidebar::SfxUnoSidebar(const uno::Reference<frame::XFrame>& rFrame):
+xFrame(rFrame)
+{
+}
+
+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: */