diff options
Diffstat (limited to 'sfx2/source/sidebar/DeckLayouter.cxx')
-rw-r--r-- | sfx2/source/sidebar/DeckLayouter.cxx | 555 |
1 files changed, 555 insertions, 0 deletions
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: */ |