summaryrefslogtreecommitdiffstats
path: root/sfx2/source/sidebar/DeckLayouter.cxx
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--sfx2/source/sidebar/DeckLayouter.cxx555
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: */