549 lines
18 KiB
C++
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: */
|