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