diff options
Diffstat (limited to 'sfx2/source/sidebar')
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: */ |