1
0
Fork 0
libreoffice/sfx2/source/sidebar/DeckLayouter.cxx
Daniel Baumann 8e63e14cf6
Adding upstream version 4:25.2.3.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
2025-06-22 16:20:04 +02:00

549 lines
18 KiB
C++

/* -*- 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 <utility>
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(std::shared_ptr<Panel> pPanel)
: mpPanel(std::move(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;
}
}
}
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: */