/* -*- 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>
// Needed since LLVM 15 libc++ (hence the ignored -Wunused-macros for older libc++) when
// #include <boost/multi_array.hpp> below includes Boost 1.79.0
// workdir/UnpackedTarball/boost/boost/functional.hpp using std::unary_function, but must
// come very early here in case <functional> is already (indirectly) included earlier:
#include <config_libcxx.h>
#if HAVE_LIBCPP
#if defined __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored
"-Wunused-macros"
#endif
// [-loplugin:reservedid]:
#define _LIBCPP_ENABLE_CXX17_REMOVED_UNARY_BINARY_FUNCTION
#if defined __clang__
#pragma clang diagnostic pop
#endif
#endif
#include <string_view>
#include <config_features.h>
#include <com/sun/star/accessibility/AccessibleRole.hpp>
#include <comphelper/base64.hxx>
#include <comphelper/lok.hxx>
#include <o3tl/enumarray.hxx>
#include <o3tl/enumrange.hxx>
#include <o3tl/string_view.hxx>
#include <tools/stream.hxx>
#include <utility>
#include <vcl/builder.hxx>
#include <vcl/toolkit/button.hxx>
#include <vcl/cvtgrf.hxx>
#include <vcl/decoview.hxx>
#include <vcl/help.hxx>
#include <vcl/toolkit/dialog.hxx>
#include <vcl/layout.hxx>
#include <vcl/toolkit/scrbar.hxx>
#include <vcl/stdtext.hxx>
#include <vcl/split.hxx>
#include <vcl/svapp.hxx>
#include <vcl/settings.hxx>
#include <vcl/virdev.hxx>
#include <bitmaps.hlst>
#include <messagedialog.hxx>
#include <svdata.hxx>
#include <window.h>
#include <boost/multi_array.hpp>
#include <vcl/toolkit/vclmedit.hxx>
#include <vcl/uitest/uiobject.hxx>
#include <sal/log.hxx>
#include <tools/json_writer.hxx>
VclContainer::VclContainer(vcl::Window *pParent, WinBits nStyle)
: Window(WindowType::CONTAINER)
, m_bLayoutDirty(
true )
{
ImplInit(pParent, nStyle, nullptr);
EnableChildTransparentMode();
SetPaintTransparent(
true );
SetBackground();
}
sal_uInt16 VclContainer::getDefaultAccessibleRole()
const
{
return css::accessibility::AccessibleRole::PANEL;
}
Size VclContainer::GetOptimalSize()
const
{
return calculateRequisition();
}
void VclContainer::setLayoutPosSize(vcl::Window &rWindow,
const Point &rPos,
const Size &rSize)
{
sal_Int32 nBorderWidth = rWindow.get_border_width();
sal_Int32 nLeft = rWindow.get_margin_start() + nBorderWidth;
sal_Int32 nTop = rWindow.get_margin_top() + nBorderWidth;
sal_Int32 nRight = rWindow.get_margin_end() + nBorderWidth;
sal_Int32 nBottom = rWindow.get_margin_bottom() + nBorderWidth;
Point aPos(rPos.X() + nLeft, rPos.Y() + nTop);
Size aSize(rSize.Width() - nLeft - nRight, rSize.Height() - nTop - nBottom);
rWindow.SetPosSizePixel(aPos, aSize);
}
void VclContainer::setLayoutAllocation(vcl::Window &rChild,
const Point &rAllocPos,
const Size &rChildAlloc)
{
VclAlign eHalign = rChild.get_halign();
VclAlign eValign = rChild.get_valign();
//typical case
if (eHalign == VclAlign::Fill && eValign == VclAlign::Fill)
{
setLayoutPosSize(rChild, rAllocPos, rChildAlloc);
return ;
}
Point aChildPos(rAllocPos);
Size aChildSize(rChildAlloc);
Size aChildPreferredSize(getLayoutRequisition(rChild));
switch (eHalign)
{
case VclAlign::Fill:
break ;
case VclAlign::Start:
if (aChildPreferredSize.Width() < rChildAlloc.Width())
aChildSize.setWidth( aChildPreferredSize.Width() );
break ;
case VclAlign::End:
if (aChildPreferredSize.Width() < rChildAlloc.Width())
aChildSize.setWidth( aChildPreferredSize.Width() );
aChildPos.AdjustX(rChildAlloc.Width() );
aChildPos.AdjustX( -(aChildSize.Width()) );
break ;
case VclAlign::Center:
if (aChildPreferredSize.Width() < aChildSize.Width())
aChildSize.setWidth( aChildPreferredSize.Width() );
aChildPos.AdjustX((rChildAlloc.Width() - aChildSize.Width()) / 2 );
break ;
}
switch (eValign)
{
case VclAlign::Fill:
break ;
case VclAlign::Start:
if (aChildPreferredSize.Height() < rChildAlloc.Height())
aChildSize.setHeight( aChildPreferredSize.Height() );
break ;
case VclAlign::End:
if (aChildPreferredSize.Height() < rChildAlloc.Height())
aChildSize.setHeight( aChildPreferredSize.Height() );
aChildPos.AdjustY(rChildAlloc.Height() );
aChildPos.AdjustY( -(aChildSize.Height()) );
break ;
case VclAlign::Center:
if (aChildPreferredSize.Height() < aChildSize.Height())
aChildSize.setHeight( aChildPreferredSize.Height() );
aChildPos.AdjustY((rChildAlloc.Height() - aChildSize.Height()) / 2 );
break ;
}
setLayoutPosSize(rChild, aChildPos, aChildSize);
}
namespace
{
Size subtractBorder(
const vcl::Window &rWindow,
const Size& rSize)
{
sal_Int32 nBorderWidth = rWindow.get_border_width();
sal_Int32 nLeft = rWindow.get_margin_start() + nBorderWidth;
sal_Int32 nTop = rWindow.get_margin_top() + nBorderWidth;
sal_Int32 nRight = rWindow.get_margin_end() + nBorderWidth;
sal_Int32 nBottom = rWindow.get_margin_bottom() + nBorderWidth;
Size aSize(rSize);
return Size(aSize.Width() + nLeft + nRight, aSize.Height() + nTop + nBottom);
}
}
Size VclContainer::getLayoutRequisition(
const vcl::Window &rWindow)
{
return subtractBorder(rWindow, rWindow.get_preferred_size());
}
void VclContainer::SetPosSizePixel(
const Point& rAllocPos,
const Size& rAlloca
tion)
{
bool bSizeChanged = rAllocation != GetOutputSizePixel();
Window::SetPosSizePixel(rAllocPos, rAllocation);
if (m_bLayoutDirty || bSizeChanged)
{
m_bLayoutDirty = false ;
setAllocation(rAllocation);
}
}
void VclContainer::SetPosPixel(const Point& rAllocPos)
{
Point aAllocPos = rAllocPos;
sal_Int32 nBorderWidth = get_border_width();
aAllocPos.AdjustX(nBorderWidth + get_margin_start() );
aAllocPos.AdjustY(nBorderWidth + get_margin_top() );
if (aAllocPos != GetPosPixel())
Window::SetPosPixel(aAllocPos);
}
void VclContainer::SetSizePixel(const Size& rAllocation)
{
Size aAllocation = rAllocation;
sal_Int32 nBorderWidth = get_border_width();
aAllocation.AdjustWidth( -(nBorderWidth*2 + get_margin_start() + get_margin_end()) );
aAllocation.AdjustHeight( -(nBorderWidth*2 + get_margin_top() + get_margin_bottom()) );
bool bSizeChanged = aAllocation != GetSizePixel();
if (bSizeChanged)
Window::SetSizePixel(aAllocation);
if (m_bLayoutDirty || bSizeChanged)
{
m_bLayoutDirty = false ;
setAllocation(aAllocation);
}
}
void VclContainer::queue_resize(StateChangedType eReason)
{
m_bLayoutDirty = true ;
Window::queue_resize(eReason);
}
// support for screenshot context menu
void VclContainer::Command(const CommandEvent& rCEvt)
{
if (CommandEventId::ContextMenu == rCEvt.GetCommand())
{
auto pParent = GetParent();
if (pParent)
{
CommandEvent aCEvt(rCEvt.GetMousePosPixel() + GetPosPixel(), rCEvt.GetCommand(), rCEvt.IsMouseEvent(), rCEvt.GetEventData());
pParent->Command(aCEvt);
return ;
}
}
// call parent (do not consume)
Window::Command(rCEvt);
}
void VclBox::accumulateMaxes(const Size &rChildSize, Size &rSize) const
{
tools::Long nSecondaryChildDimension = getSecondaryDimension(rChildSize);
tools::Long nSecondaryBoxDimension = getSecondaryDimension(rSize);
setSecondaryDimension(rSize, std::max(nSecondaryChildDimension, nSecondaryBoxDimension));
tools::Long nPrimaryChildDimension = getPrimaryDimension(rChildSize);
tools::Long nPrimaryBoxDimension = getPrimaryDimension(rSize);
if (m_bHomogeneous)
setPrimaryDimension(rSize, std::max(nPrimaryBoxDimension, nPrimaryChildDimension));
else
setPrimaryDimension(rSize, nPrimaryBoxDimension + nPrimaryChildDimension);
}
Size VclBox::calculateRequisition() const
{
sal_uInt16 nVisibleChildren = 0;
Size aSize;
for (vcl::Window *pChild = GetWindow(GetWindowType::FirstChild); pChild; pChild = pChild->GetWindow(GetWindowType::Next))
{
if (!pChild->IsVisible())
continue ;
++nVisibleChildren;
Size aChildSize = getLayoutRequisition(*pChild);
tools::Long nPrimaryDimension = getPrimaryDimension(aChildSize);
nPrimaryDimension += pChild->get_padding() * 2;
setPrimaryDimension(aChildSize, nPrimaryDimension);
accumulateMaxes(aChildSize, aSize);
}
return finalizeMaxes(aSize, nVisibleChildren);
}
void VclBox::setAllocation(const Size &rAllocation)
{
sal_uInt16 nVisibleChildren = 0, nExpandChildren = 0;
for (vcl::Window *pChild = GetWindow(GetWindowType::FirstChild); pChild; pChild = pChild->GetWindow(GetWindowType::Next))
{
if (!pChild->IsVisible())
continue ;
++nVisibleChildren;
bool bExpand = getPrimaryDimensionChildExpand(*pChild);
if (bExpand)
++nExpandChildren;
}
if (!nVisibleChildren)
return ;
tools::Long nAllocPrimaryDimension = getPrimaryDimension(rAllocation);
tools::Long nHomogeneousDimension = 0, nExtraSpace = 0;
if (m_bHomogeneous)
{
nHomogeneousDimension = (nAllocPrimaryDimension -
(nVisibleChildren - 1) * m_nSpacing) / nVisibleChildren;
}
else if (nExpandChildren)
{
Size aRequisition = calculateRequisition();
tools::Long nPrimaryDimension = getPrimaryDimension(rAllocation);
nExtraSpace = (nPrimaryDimension - getPrimaryDimension(aRequisition)) / nExpandChildren;
}
//Split into those we pack from the start onwards, and those we pack from the end backwards
o3tl::enumarray<VclPackType,std::vector<vcl::Window*>> aWindows;
for (vcl::Window *pChild = GetWindow(GetWindowType::FirstChild); pChild; pChild = pChild->GetWindow(GetWindowType::Next))
{
if (!pChild->IsVisible())
continue ;
VclPackType ePacking = pChild->get_pack_type();
aWindows[ePacking].push_back(pChild);
}
//See VclBuilder::sortIntoBestTabTraversalOrder for why they are in visual
//order under the parent which requires us to reverse them here to
//pack from the end back
std::reverse(aWindows[VclPackType::End].begin(),aWindows[VclPackType::End].end());
for (VclPackType ePackType : o3tl::enumrange<VclPackType>())
{
Point aPos(0, 0);
if (ePackType == VclPackType::End)
{
tools::Long nPrimaryCoordinate = getPrimaryCoordinate(aPos);
setPrimaryCoordinate(aPos, nPrimaryCoordinate + nAllocPrimaryDimension);
}
for (auto const & window : aWindows[ePackType])
{
vcl::Window *pChild = window;
tools::Long nPadding = pChild->get_padding();
Size aBoxSize;
if (m_bHomogeneous)
setPrimaryDimension(aBoxSize, nHomogeneousDimension);
else
{
aBoxSize = getLayoutRequisition(*pChild);
tools::Long nPrimaryDimension = getPrimaryDimension(aBoxSize);
nPrimaryDimension += nPadding * 2;
if (getPrimaryDimensionChildExpand(*pChild))
nPrimaryDimension += nExtraSpace;
setPrimaryDimension(aBoxSize, nPrimaryDimension);
}
setSecondaryDimension(aBoxSize, getSecondaryDimension(rAllocation));
Point aChildPos(aPos);
Size aChildSize(aBoxSize);
tools::Long nPrimaryCoordinate = getPrimaryCoordinate(aPos);
bool bFill = pChild->get_fill();
if (bFill)
{
setPrimaryDimension(aChildSize, std::max(static_cast <tools::Long >(1),
std::min(getPrimaryDimension(rAllocation), getPrimaryDimension(aBoxSize) - nPadding * 2)));
setPrimaryCoordinate(aChildPos, nPrimaryCoordinate + nPadding);
}
else
{
setPrimaryDimension(aChildSize,
getPrimaryDimension(getLayoutRequisition(*pChild)));
setPrimaryCoordinate(aChildPos, nPrimaryCoordinate +
(getPrimaryDimension(aBoxSize) - getPrimaryDimension(aChildSize)) / 2);
}
tools::Long nDiff = getPrimaryDimension(aBoxSize) + m_nSpacing;
if (ePackType == VclPackType::Start)
setPrimaryCoordinate(aPos, nPrimaryCoordinate + nDiff);
else
{
setPrimaryCoordinate(aPos, nPrimaryCoordinate - nDiff);
setPrimaryCoordinate(aChildPos, getPrimaryCoordinate(aChildPos) -
getPrimaryDimension(aBoxSize));
}
setLayoutAllocation(*pChild, aChildPos, aChildSize);
}
}
}
bool VclBox::set_property(const OUString &rKey, const OUString &rValue)
{
if (rKey == "spacing" )
set_spacing(rValue.toInt32());
else if (rKey == "homogeneous" )
set_homogeneous(toBool(rValue));
else
return VclContainer::set_property(rKey, rValue);
return true ;
}
void VclBox::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter)
{
VclContainer::DumpAsPropertyTree(rJsonWriter);
rJsonWriter.put("vertical" , m_bVerticalContainer);
}
sal_uInt16 VclBox::getDefaultAccessibleRole() const
{
// fdo#74284 call Boxes Panels, keep them as "Filler" under
// at least Linux seeing as that's what Gtk3 did for GtkBoxes.
// Though now with Gtk4 that uses GTK_ACCESSIBLE_ROLE_GROUP
// which maps to ATSPI_ROLE_PANEL
#if defined (_WIN32)
return css::accessibility::AccessibleRole::PANEL;
#else
static sal_uInt16 eRole = Application::GetToolkitName() == "gtk4" ?
css::accessibility::AccessibleRole::PANEL :
css::accessibility::AccessibleRole::FILLER;
return eRole;
#endif
}
#define DEFAULT_CHILD_MIN_WIDTH 85
#define DEFAULT_CHILD_MIN_HEIGHT 27
Size VclBox::finalizeMaxes(const Size &rSize, sal_uInt16 nVisibleChildren) const
{
Size aRet;
if (nVisibleChildren)
{
tools::Long nPrimaryDimension = getPrimaryDimension(rSize);
if (m_bHomogeneous)
nPrimaryDimension *= nVisibleChildren;
setPrimaryDimension(aRet, nPrimaryDimension + m_nSpacing * (nVisibleChildren-1));
setSecondaryDimension(aRet, getSecondaryDimension(rSize));
}
return aRet;
}
Size VclButtonBox::addReqGroups(const VclButtonBox::Requisition &rReq) const
{
Size aRet;
tools::Long nMainGroupDimension = getPrimaryDimension(rReq.m_aMainGroupSize);
tools::Long nSubGroupDimension = getPrimaryDimension(rReq.m_aSubGroupSize);
setPrimaryDimension(aRet, nMainGroupDimension + nSubGroupDimension);
setSecondaryDimension(aRet,
std::max(getSecondaryDimension(rReq.m_aMainGroupSize),
getSecondaryDimension(rReq.m_aSubGroupSize)));
return aRet;
}
static tools::Long getMaxNonOutlier(const std::vector<tools::Long > &rG, tools::Long nAvgDimension)
{
tools::Long nMaxDimensionNonOutlier = 0;
for (auto const & nPrimaryChildDimension : rG)
{
if (nPrimaryChildDimension < nAvgDimension * 1.5)
{
nMaxDimensionNonOutlier = std::max(nPrimaryChildDimension,
nMaxDimensionNonOutlier);
}
}
return nMaxDimensionNonOutlier;
}
static std::vector<tools::Long > setButtonSizes(const std::vector<tools::Long > &rG,
const std::vector<bool > &rNonHomogeneous,
tools::Long nAvgDimension, tools::Long nMaxNonOutlier, tools::Long nMinWidth)
{
std::vector<tools::Long > aVec;
//set everything < 1.5 times the average to the same width, leave the
//outliers un-touched
std::vector<bool >::const_iterator aJ = rNonHomogeneous.begin();
auto nNonOutlierWidth = std::max(nMaxNonOutlier, nMinWidth);
for (auto const & nPrimaryChildDimension : rG)
{
bool bNonHomogeneous = *aJ;
if (!bNonHomogeneous && nPrimaryChildDimension < nAvgDimension * 1.5)
{
aVec.push_back(nNonOutlierWidth);
}
else
{
aVec.push_back(std::max(nPrimaryChildDimension, nMinWidth));
}
++aJ;
}
return aVec;
}
VclButtonBox::Requisition VclButtonBox::calculatePrimarySecondaryRequisitions() const
{
Requisition aReq;
Size aMainGroupSize(DEFAULT_CHILD_MIN_WIDTH, DEFAULT_CHILD_MIN_HEIGHT); //to-do, pull from theme
Size aSubGroupSize(DEFAULT_CHILD_MIN_WIDTH, DEFAULT_CHILD_MIN_HEIGHT); //to-do, pull from theme
tools::Long nMinMainGroupPrimary = getPrimaryDimension(aMainGroupSize);
tools::Long nMinSubGroupPrimary = getPrimaryDimension(aSubGroupSize);
tools::Long nMainGroupSecondary = getSecondaryDimension(aMainGroupSize);
tools::Long nSubGroupSecondary = getSecondaryDimension(aSubGroupSize);
bool bIgnoreSecondaryPacking = (m_eLayoutStyle == VclButtonBoxStyle::Spread || m_eLayoutStyle == VclButtonBoxStyle::Center);
std::vector<tools::Long > aMainGroupSizes;
std::vector<bool > aMainGroupNonHomogeneous;
std::vector<tools::Long > aSubGroupSizes;
std::vector<bool > aSubGroupNonHomogeneous;
for (const vcl::Window *pChild = GetWindow(GetWindowType::FirstChild); pChild; pChild = pChild->GetWindow(GetWindowType::Next))
{
if (!pChild->IsVisible())
continue ;
Size aChildSize = getLayoutRequisition(*pChild);
if (bIgnoreSecondaryPacking || !pChild->get_secondary())
{
//set the max secondary dimension
nMainGroupSecondary = std::max(nMainGroupSecondary, getSecondaryDimension(aChildSize));
//collect the primary dimensions
aMainGroupSizes.push_back(getPrimaryDimension(aChildSize));
aMainGroupNonHomogeneous.push_back(pChild->get_non_homogeneous());
}
else
{
nSubGroupSecondary = std::max(nSubGroupSecondary, getSecondaryDimension(aChildSize));
aSubGroupSizes.push_back(getPrimaryDimension(aChildSize));
aSubGroupNonHomogeneous.push_back(pChild->get_non_homogeneous());
}
}
if (m_bHomogeneous)
{
tools::Long nMaxMainDimension = aMainGroupSizes.empty() ? 0 :
*std::max_element(aMainGroupSizes.begin(), aMainGroupSizes.end());
nMaxMainDimension = std::max(nMaxMainDimension, nMinMainGroupPrimary);
tools::Long nMaxSubDimension = aSubGroupSizes.empty() ? 0 :
*std::max_element(aSubGroupSizes.begin(), aSubGroupSizes.end());
nMaxSubDimension = std::max(nMaxSubDimension, nMinSubGroupPrimary);
tools::Long nMaxDimension = std::max(nMaxMainDimension, nMaxSubDimension);
aReq.m_aMainGroupDimensions.resize(aMainGroupSizes.size(), nMaxDimension);
aReq.m_aSubGroupDimensions.resize(aSubGroupSizes.size(), nMaxDimension);
}
else
{
//Ideally set everything to the same size, but find outlier widgets
//that are way wider than the average and leave them
//at their natural size and set the remainder to share the
//max size of the remaining members of the buttonbox
tools::Long nAccDimension = std::accumulate(aMainGroupSizes.begin(),
aMainGroupSizes.end(), 0);
nAccDimension = std::accumulate(aSubGroupSizes.begin(),
aSubGroupSizes.end(), nAccDimension);
size_t nTotalSize = aMainGroupSizes.size() + aSubGroupSizes.size();
tools::Long nAvgDimension = nTotalSize ? nAccDimension / nTotalSize : 0;
tools::Long nMaxMainNonOutlier = getMaxNonOutlier(aMainGroupSizes,
nAvgDimension);
tools::Long nMaxSubNonOutlier = getMaxNonOutlier(aSubGroupSizes,
nAvgDimension);
tools::Long nMaxNonOutlier = std::max(nMaxMainNonOutlier, nMaxSubNonOutlier);
aReq.m_aMainGroupDimensions = setButtonSizes(aMainGroupSizes,
aMainGroupNonHomogeneous,
nAvgDimension, nMaxNonOutlier, nMinMainGroupPrimary);
aReq.m_aSubGroupDimensions = setButtonSizes(aSubGroupSizes,
aSubGroupNonHomogeneous,
nAvgDimension, nMaxNonOutlier, nMinSubGroupPrimary);
}
if (!aReq.m_aMainGroupDimensions.empty())
{
setSecondaryDimension(aReq.m_aMainGroupSize, nMainGroupSecondary);
setPrimaryDimension(aReq.m_aMainGroupSize,
std::accumulate(aReq.m_aMainGroupDimensions.begin(),
aReq.m_aMainGroupDimensions.end(), 0));
}
if (!aReq.m_aSubGroupDimensions.empty())
{
setSecondaryDimension(aReq.m_aSubGroupSize, nSubGroupSecondary);
setPrimaryDimension(aReq.m_aSubGroupSize,
std::accumulate(aReq.m_aSubGroupDimensions.begin(),
aReq.m_aSubGroupDimensions.end(), 0));
}
return aReq;
}
Size VclButtonBox::addSpacing(const Size &rSize, sal_uInt16 nVisibleChildren) const
{
Size aRet;
if (nVisibleChildren)
{
tools::Long nPrimaryDimension = getPrimaryDimension(rSize);
setPrimaryDimension(aRet,
nPrimaryDimension + m_nSpacing * (nVisibleChildren-1));
setSecondaryDimension(aRet, getSecondaryDimension(rSize));
}
return aRet;
}
Size VclButtonBox::calculateRequisition() const
{
Requisition aReq(calculatePrimarySecondaryRequisitions());
sal_uInt16 nVisibleChildren = aReq.m_aMainGroupDimensions.size() +
aReq.m_aSubGroupDimensions.size();
return addSpacing(addReqGroups(aReq), nVisibleChildren);
}
bool VclButtonBox::set_property(const OUString &rKey, const OUString &rValue)
{
if (rKey == "layout-style" )
{
VclButtonBoxStyle eStyle = VclButtonBoxStyle::Default ;
if (rValue == "spread" )
eStyle = VclButtonBoxStyle::Spread;
else if (rValue == "edge" )
eStyle = VclButtonBoxStyle::Edge;
else if (rValue == "start" )
eStyle = VclButtonBoxStyle::Start;
else if (rValue == "end" )
eStyle = VclButtonBoxStyle::End;
else if (rValue == "center" )
eStyle = VclButtonBoxStyle::Center;
else
{
SAL_WARN("vcl.layout" , "unknown layout style " << rValue);
}
m_eLayoutStyle = eStyle;
}
else
return VclBox::set_property(rKey, rValue);
return true ;
}
void VclButtonBox::setAllocation(const Size &rAllocation)
{
Requisition aReq(calculatePrimarySecondaryRequisitions());
if (aReq.m_aMainGroupDimensions.empty() && aReq.m_aSubGroupDimensions.empty())
return ;
tools::Long nAllocPrimaryDimension = getPrimaryDimension(rAllocation);
Point aMainGroupPos, aOtherGroupPos;
int nSpacing = m_nSpacing;
//To-Do, other layout styles
switch (m_eLayoutStyle)
{
case VclButtonBoxStyle::Start:
if (!aReq.m_aSubGroupDimensions.empty())
{
tools::Long nOtherPrimaryDimension = getPrimaryDimension(
addSpacing(aReq.m_aSubGroupSize, aReq.m_aSubGroupDimensions.size()));
setPrimaryCoordinate(aOtherGroupPos,
nAllocPrimaryDimension - nOtherPrimaryDimension);
}
break ;
case VclButtonBoxStyle::Spread:
if (!aReq.m_aMainGroupDimensions.empty())
{
tools::Long nMainPrimaryDimension = getPrimaryDimension(
addSpacing(aReq.m_aMainGroupSize, aReq.m_aMainGroupDimensions.size()));
tools::Long nExtraSpace = nAllocPrimaryDimension - nMainPrimaryDimension;
nExtraSpace += (aReq.m_aMainGroupDimensions.size()-1) * nSpacing;
nSpacing = nExtraSpace/(aReq.m_aMainGroupDimensions.size()+1);
setPrimaryCoordinate(aMainGroupPos, nSpacing);
}
break ;
case VclButtonBoxStyle::Center:
if (!aReq.m_aMainGroupDimensions.empty())
{
tools::Long nMainPrimaryDimension = getPrimaryDimension(
addSpacing(aReq.m_aMainGroupSize, aReq.m_aMainGroupDimensions.size()));
tools::Long nExtraSpace = nAllocPrimaryDimension - nMainPrimaryDimension;
setPrimaryCoordinate(aMainGroupPos, nExtraSpace/2);
}
break ;
default :
SAL_WARN("vcl.layout" , "todo unimplemented layout style" );
[[fallthrough]];
case VclButtonBoxStyle::Default :
case VclButtonBoxStyle::End:
if (!aReq.m_aMainGroupDimensions.empty())
{
tools::Long nMainPrimaryDimension = getPrimaryDimension(
addSpacing(aReq.m_aMainGroupSize, aReq.m_aMainGroupDimensions.size()));
setPrimaryCoordinate(aMainGroupPos,
nAllocPrimaryDimension - nMainPrimaryDimension);
}
break ;
}
Size aChildSize;
setSecondaryDimension(aChildSize, getSecondaryDimension(rAllocation));
std::vector<tools::Long >::const_iterator aPrimaryI = aReq.m_aMainGroupDimensions.begin();
std::vector<tools::Long >::const_iterator aSecondaryI = aReq.m_aSubGroupDimensions.begin();
bool bIgnoreSecondaryPacking = (m_eLayoutStyle == VclButtonBoxStyle::Spread || m_eLayoutStyle == VclButtonBoxStyle::Center);
for (vcl::Window *pChild = GetWindow(GetWindowType::FirstChild); pChild; pChild = pChild->GetWindow(GetWindowType::Next))
{
if (!pChild->IsVisible())
continue ;
if (bIgnoreSecondaryPacking || !pChild->get_secondary())
{
tools::Long nMainGroupPrimaryDimension = *aPrimaryI++;
setPrimaryDimension(aChildSize, nMainGroupPrimaryDimension);
setLayoutAllocation(*pChild, aMainGroupPos, aChildSize);
tools::Long nPrimaryCoordinate = getPrimaryCoordinate(aMainGroupPos);
setPrimaryCoordinate(aMainGroupPos, nPrimaryCoordinate + nMainGroupPrimaryDimension + nSpacing);
}
else
{
tools::Long nSubGroupPrimaryDimension = *aSecondaryI++;
setPrimaryDimension(aChildSize, nSubGroupPrimaryDimension);
setLayoutAllocation(*pChild, aOtherGroupPos, aChildSize);
tools::Long nPrimaryCoordinate = getPrimaryCoordinate(aOtherGroupPos);
setPrimaryCoordinate(aOtherGroupPos, nPrimaryCoordinate + nSubGroupPrimaryDimension + nSpacing);
}
}
}
void VclButtonBox::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter)
{
VclBox::DumpAsPropertyTree(rJsonWriter);
rJsonWriter.put("type" , "buttonbox" );
switch (m_eLayoutStyle)
{
case VclButtonBoxStyle::Default :
rJsonWriter.put("layoutstyle" , "default" );
break ;
case VclButtonBoxStyle::Spread:
rJsonWriter.put("layoutstyle" , "spread" );
break ;
case VclButtonBoxStyle::Edge:
rJsonWriter.put("layoutstyle" , "edge" );
break ;
case VclButtonBoxStyle::Center:
rJsonWriter.put("layoutstyle" , "center" );
break ;
case VclButtonBoxStyle::Start:
rJsonWriter.put("layoutstyle" , "start" );
break ;
case VclButtonBoxStyle::End:
rJsonWriter.put("layoutstyle" , "end" );
break ;
}
}
namespace {
struct ButtonOrder
{
std::u16string_view m_aType;
int m_nPriority;
};
}
static int getButtonPriority(std::u16string_view rType)
{
static const size_t N_TYPES = 6;
static const ButtonOrder aDiscardCancelSave[N_TYPES] =
{
{ u"discard" , 0 },
{ u"cancel" , 1 },
{ u"no" , 2 },
{ u"save" , 3 },
{ u"yes" , 3 },
{ u"ok" , 3 }
};
static const ButtonOrder aSaveDiscardCancel[N_TYPES] =
{
{ u"save" , 0 },
{ u"yes" , 0 },
{ u"ok" , 0 },
{ u"discard" , 1 },
{ u"no" , 1 },
{ u"cancel" , 2 }
};
const ButtonOrder* pOrder = &aDiscardCancelSave[0];
const OUString &rEnv = Application::GetDesktopEnvironment();
if (rEnv.equalsIgnoreAsciiCase("windows" ) ||
rEnv.equalsIgnoreAsciiCase("lxqt" ) ||
rEnv.startsWithIgnoreAsciiCase("plasma" ))
{
pOrder = &aSaveDiscardCancel[0];
}
for (size_t i = 0; i < N_TYPES; ++i, ++pOrder)
{
if (rType == pOrder->m_aType)
return pOrder->m_nPriority;
}
return -1;
}
namespace {
class sortButtons
{
bool m_bVerticalContainer;
public :
explicit sortButtons(bool bVerticalContainer)
: m_bVerticalContainer(bVerticalContainer)
{
}
bool operator ()(const vcl::Window *pA, const vcl::Window *pB) const ;
};
}
bool sortButtons::operator ()(const vcl::Window *pA, const vcl::Window *pB) const
{
//sort into two groups of pack start and pack end
VclPackType ePackA = pA->get_pack_type();
VclPackType ePackB = pB->get_pack_type();
if (ePackA < ePackB)
return true ;
if (ePackA > ePackB)
return false ;
bool bPackA = pA->get_secondary();
bool bPackB = pB->get_secondary();
if (!m_bVerticalContainer)
{
//for horizontal boxes group secondaries before primaries
if (bPackA > bPackB)
return true ;
if (bPackA < bPackB)
return false ;
}
else
{
//for vertical boxes group secondaries after primaries
if (bPackA < bPackB)
return true ;
if (bPackA > bPackB)
return false ;
}
//now order within groups according to platform rules
return getButtonPriority(pA->get_id()) < getButtonPriority(pB->get_id());
}
void sort_native_button_order(const VclBox& rContainer)
{
std::vector<vcl::Window*> aChilds;
for (vcl::Window* pChild = rContainer.GetWindow(GetWindowType::FirstChild); pChild;
pChild = pChild->GetWindow(GetWindowType::Next))
{
aChilds.push_back(pChild);
}
//sort child order within parent so that we match the platform
//button order
std::stable_sort(aChilds.begin(), aChilds.end(), sortButtons(rContainer.get_orientation()));
BuilderUtils::reorderWithinParent(aChilds, true );
}
namespace {
struct GridEntry
{
VclPtr<vcl::Window> pChild;
sal_Int32 nSpanWidth;
sal_Int32 nSpanHeight;
int x;
int y;
GridEntry()
: pChild(nullptr)
, nSpanWidth(0)
, nSpanHeight(0)
, x(-1)
, y(-1)
{
}
};
}
typedef boost::multi_array<GridEntry, 2> array_type;
#if defined _MSC_VER
#pragma warning(push)
#pragma warning(disable : 4459)
#pragma warning(disable : 4996)
#endif
static array_type assembleGrid(const VclGrid &rGrid)
{
#if defined _MSC_VER
#pragma warning(pop)
#endif
array_type A;
for (vcl::Window* pChild = rGrid.GetWindow(GetWindowType::FirstChild); pChild;
pChild = pChild->GetWindow(GetWindowType::Next))
{
sal_Int32 nLeftAttach = std::max<sal_Int32>(pChild->get_grid_left_attach(), 0);
sal_Int32 nWidth = pChild->get_grid_width();
sal_Int32 nMaxXPos = nLeftAttach+nWidth-1;
sal_Int32 nTopAttach = std::max<sal_Int32>(pChild->get_grid_top_attach(), 0);
sal_Int32 nHeight = pChild->get_grid_height();
sal_Int32 nMaxYPos = nTopAttach+nHeight-1;
sal_Int32 nCurrentMaxXPos = A.shape()[0]-1;
sal_Int32 nCurrentMaxYPos = A.shape()[1]-1;
if (nMaxXPos > nCurrentMaxXPos || nMaxYPos > nCurrentMaxYPos)
{
nCurrentMaxXPos = std::max(nMaxXPos, nCurrentMaxXPos);
nCurrentMaxYPos = std::max(nMaxYPos, nCurrentMaxYPos);
A.resize(boost::extents[nCurrentMaxXPos+1][nCurrentMaxYPos+1]);
}
#if defined _MSC_VER
#pragma warning(push)
#pragma warning(disable : 4459)
#pragma warning(disable : 4996)
#endif
GridEntry &rEntry = A[nLeftAttach][nTopAttach];
#if defined _MSC_VER
#pragma warning(pop)
#endif
rEntry.pChild = pChild;
rEntry.nSpanWidth = nWidth;
rEntry.nSpanHeight = nHeight;
rEntry.x = nLeftAttach;
rEntry.y = nTopAttach;
for (sal_Int32 nSpanX = 0; nSpanX < nWidth; ++nSpanX)
{
for (sal_Int32 nSpanY = 0; nSpanY < nHeight; ++nSpanY)
{
GridEntry &rSpan = A[nLeftAttach+nSpanX][nTopAttach+nSpanY];
rSpan.x = nLeftAttach;
rSpan.y = nTopAttach;
}
}
}
//see if we have any empty rows/cols
sal_Int32 nMaxX = A.shape()[0];
sal_Int32 nMaxY = A.shape()[1];
std::vector<bool > aNonEmptyCols(nMaxX);
std::vector<bool > aNonEmptyRows(nMaxY);
for (sal_Int32 x = 0; x < nMaxX; ++x)
{
for (sal_Int32 y = 0; y < nMaxY; ++y)
{
#if defined __GNUC__ && !defined __clang__ && __GNUC__ >= 13 && __GNUC__ <= 16
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdangling-reference"
#endif
const GridEntry &rEntry = A[x][y];
#if defined __GNUC__ && !defined __clang__ && __GNUC__ >= 13 && __GNUC__ <= 16
#pragma GCC diagnostic pop
#endif
const vcl::Window *pChild = rEntry.pChild;
if (pChild && pChild->IsVisible())
{
aNonEmptyCols[x] = true ;
if (rGrid.get_column_homogeneous())
{
for (sal_Int32 nSpanX = 1; nSpanX < rEntry.nSpanWidth; ++nSpanX)
aNonEmptyCols[x+nSpanX] = true ;
}
aNonEmptyRows[y] = true ;
if (rGrid.get_row_homogeneous())
{
for (sal_Int32 nSpanY = 1; nSpanY < rEntry.nSpanHeight; ++nSpanY)
aNonEmptyRows[y+nSpanY] = true ;
}
}
}
}
if (!rGrid.get_column_homogeneous())
{
//reduce the spans of elements that span empty columns
for (sal_Int32 x = 0; x < nMaxX; ++x)
{
std::set<GridEntry*> candidates;
for (sal_Int32 y = 0; y < nMaxY; ++y)
{
if (aNonEmptyCols[x])
continue ;
GridEntry &rSpan = A[x][y];
//cell x/y is spanned by the widget at cell rSpan.x/rSpan.y,
//just points back to itself if there's no cell spanning
if ((rSpan.x == -1) || (rSpan.y == -1))
{
//there is no entry for this cell, i.e. this is a cell
//with no widget in it, or spanned by any other widget
continue ;
}
GridEntry &rEntry = A[rSpan.x][rSpan.y];
candidates.insert(&rEntry);
}
for (auto const & candidate : candidates)
{
GridEntry *pEntry = candidate;
--pEntry->nSpanWidth;
}
}
}
if (!rGrid.get_row_homogeneous())
{
//reduce the spans of elements that span empty rows
for (sal_Int32 y = 0; y < nMaxY; ++y)
{
std::set<GridEntry*> candidates;
for (sal_Int32 x = 0; x < nMaxX; ++x)
{
if (aNonEmptyRows[y])
continue ;
GridEntry &rSpan = A[x][y];
//cell x/y is spanned by the widget at cell rSpan.x/rSpan.y,
//just points back to itself if there's no cell spanning
if ((rSpan.x == -1) || (rSpan.y == -1))
{
//there is no entry for this cell, i.e. this is a cell
//with no widget in it, or spanned by any other widget
continue ;
}
GridEntry &rEntry = A[rSpan.x][rSpan.y];
candidates.insert(&rEntry);
}
for (auto const & candidate : candidates)
{
GridEntry *pEntry = candidate;
--pEntry->nSpanHeight;
}
}
}
sal_Int32 nNonEmptyCols = std::count(aNonEmptyCols.begin(), aNonEmptyCols.end(), true );
sal_Int32 nNonEmptyRows = std::count(aNonEmptyRows.begin(), aNonEmptyRows.end(), true );
//make new grid without empty rows and columns
#if defined _MSC_VER
#pragma warning(push)
#pragma warning(disable : 4459)
#pragma warning(disable : 4996)
#endif
array_type B(boost::extents[nNonEmptyCols][nNonEmptyRows]);
#if defined _MSC_VER
#pragma warning(pop)
#endif
for (sal_Int32 x = 0, x2 = 0; x < nMaxX; ++x)
{
if (!aNonEmptyCols[x])
continue ;
for (sal_Int32 y = 0, y2 = 0; y < nMaxY; ++y)
{
if (!aNonEmptyRows[y])
continue ;
GridEntry &rEntry = A[x][y];
B[x2][y2++] = rEntry;
}
++x2;
}
return B;
}
static bool isNullGrid(const array_type &A)
{
sal_Int32 nMaxX = A.shape()[0];
sal_Int32 nMaxY = A.shape()[1];
return !nMaxX || !nMaxY;
}
static void calcMaxs(const array_type &A, std::vector<VclGrid::Value> &rWidths, std::vector<VclGrid::Value> &rHeights)
{
sal_Int32 nMaxX = A.shape()[0];
sal_Int32 nMaxY = A.shape()[1];
rWidths.resize(nMaxX);
rHeights.resize(nMaxY);
//first use the non spanning entries to set default width/heights
for (sal_Int32 x = 0; x < nMaxX; ++x)
{
for (sal_Int32 y = 0; y < nMaxY; ++y)
{
#if defined __GNUC__ && !defined __clang__ && __GNUC__ >= 13 && __GNUC__ <= 16
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdangling-reference"
#elif defined _MSC_VER
#pragma warning(push)
#pragma warning(disable : 4459)
#pragma warning(disable : 4996)
#endif
const GridEntry &rEntry = A[x][y];
#if defined __GNUC__ && !defined __clang__ && __GNUC__ >= 13 && __GNUC__ <= 16
#pragma GCC diagnostic pop
#elif defined _MSC_VER
#pragma warning(pop)
#endif
const vcl::Window *pChild = rEntry.pChild;
if (!pChild || !pChild->IsVisible())
continue ;
sal_Int32 nWidth = rEntry.nSpanWidth;
sal_Int32 nHeight = rEntry.nSpanHeight;
for (sal_Int32 nSpanX = 0; nSpanX < nWidth; ++nSpanX)
rWidths[x+nSpanX].m_bExpand |= pChild->get_hexpand();
for (sal_Int32 nSpanY = 0; nSpanY < nHeight; ++nSpanY)
rHeights[y+nSpanY].m_bExpand |= pChild->get_vexpand();
if (nWidth == 1 || nHeight == 1)
{
Size aChildSize = VclContainer::getLayoutRequisition(*pChild);
if (nWidth == 1)
rWidths[x].m_nValue = std::max(rWidths[x].m_nValue, aChildSize.Width());
if (nHeight == 1)
rHeights[y].m_nValue = std::max(rHeights[y].m_nValue, aChildSize.Height());
}
}
}
//now use the spanning entries and split any extra sizes across expanding rows/cols
//where possible
for (sal_Int32 x = 0; x < nMaxX; ++x)
{
for (sal_Int32 y = 0; y < nMaxY; ++y)
{
#if defined __GNUC__ && !defined __clang__ && __GNUC__ >= 13 && __GNUC__ <= 16
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdangling-reference"
#endif
const GridEntry &rEntry = A[x][y];
#if defined __GNUC__ && !defined __clang__ && __GNUC__ >= 13 && __GNUC__ <= 16
#pragma GCC diagnostic pop
#endif
const vcl::Window *pChild = rEntry.pChild;
if (!pChild || !pChild->IsVisible())
continue ;
sal_Int32 nWidth = rEntry.nSpanWidth;
sal_Int32 nHeight = rEntry.nSpanHeight;
if (nWidth == 1 && nHeight == 1)
continue ;
Size aChildSize = VclContainer::getLayoutRequisition(*pChild);
if (nWidth > 1)
{
sal_Int32 nExistingWidth = 0;
for (sal_Int32 nSpanX = 0; nSpanX < nWidth; ++nSpanX)
nExistingWidth += rWidths[x+nSpanX].m_nValue;
sal_Int32 nExtraWidth = aChildSize.Width() - nExistingWidth;
if (nExtraWidth > 0)
{
bool bForceExpandAll = false ;
sal_Int32 nExpandables = 0;
for (sal_Int32 nSpanX = 0; nSpanX < nWidth; ++nSpanX)
if (rWidths[x+nSpanX].m_bExpand)
++nExpandables;
if (nExpandables == 0)
{
nExpandables = nWidth;
bForceExpandAll = true ;
}
for (sal_Int32 nSpanX = 0; nSpanX < nWidth; ++nSpanX)
{
if (rWidths[x+nSpanX].m_bExpand || bForceExpandAll)
rWidths[x+nSpanX].m_nValue += nExtraWidth/nExpandables;
}
}
}
if (nHeight > 1)
{
sal_Int32 nExistingHeight = 0;
for (sal_Int32 nSpanY = 0; nSpanY < nHeight; ++nSpanY)
nExistingHeight += rHeights[y+nSpanY].m_nValue;
sal_Int32 nExtraHeight = aChildSize.Height() - nExistingHeight;
if (nExtraHeight > 0)
{
bool bForceExpandAll = false ;
sal_Int32 nExpandables = 0;
for (sal_Int32 nSpanY = 0; nSpanY < nHeight; ++nSpanY)
if (rHeights[y+nSpanY].m_bExpand)
++nExpandables;
if (nExpandables == 0)
{
nExpandables = nHeight;
bForceExpandAll = true ;
}
for (sal_Int32 nSpanY = 0; nSpanY < nHeight; ++nSpanY)
{
if (rHeights[y+nSpanY].m_bExpand || bForceExpandAll)
rHeights[y+nSpanY].m_nValue += nExtraHeight/nExpandables;
}
}
}
}
}
}
static bool compareValues(const VclGrid::Value &i, const VclGrid::Value &j)
{
return i.m_nValue < j.m_nValue;
}
static VclGrid::Value accumulateValues(const VclGrid::Value &i, const VclGrid::Value &j)
{
VclGrid::Value aRet;
aRet.m_nValue = i.m_nValue + j.m_nValue;
aRet.m_bExpand = i.m_bExpand || j.m_bExpand;
return aRet;
}
Size VclGrid::calculateRequisition() const
{
return calculateRequisitionForSpacings(get_row_spacing(), get_column_spacing());
}
Size VclGrid::calculateRequisitionForSpacings(sal_Int32 nRowSpacing, sal_Int32 nColSpacing) const
{
array_type A = assembleGrid(*this );
if (isNullGrid(A))
return Size();
std::vector<Value> aWidths;
std::vector<Value> aHeights;
calcMaxs(A, aWidths, aHeights);
tools::Long nTotalWidth = 0;
if (get_column_homogeneous())
{
nTotalWidth = std::max_element(aWidths.begin(), aWidths.end(), compareValues)->m_nValue;
nTotalWidth *= aWidths.size();
}
else
{
nTotalWidth = std::accumulate(aWidths.begin(), aWidths.end(), Value(), accumulateValues).m_nValue;
}
nTotalWidth += nColSpacing * (aWidths.size()-1);
tools::Long nTotalHeight = 0;
if (get_row_homogeneous())
{
nTotalHeight = std::max_element(aHeights.begin(), aHeights.end(), compareValues)->m_nValue;
nTotalHeight *= aHeights.size();
}
else
{
nTotalHeight = std::accumulate(aHeights.begin(), aHeights.end(), Value(), accumulateValues).m_nValue;
}
nTotalHeight += nRowSpacing * (aHeights.size()-1);
return Size(nTotalWidth, nTotalHeight);
}
void VclGrid::setAllocation(const Size& rAllocation)
{
array_type A = assembleGrid(*this );
if (isNullGrid(A))
return ;
sal_Int32 nMaxX = A.shape()[0];
sal_Int32 nMaxY = A.shape()[1];
Size aRequisition;
std::vector<Value> aWidths(nMaxX);
std::vector<Value> aHeights(nMaxY);
if (!get_column_homogeneous() || !get_row_homogeneous())
{
aRequisition = calculateRequisition();
calcMaxs(A, aWidths, aHeights);
}
sal_Int32 nColSpacing(get_column_spacing());
sal_Int32 nRowSpacing(get_row_spacing());
tools::Long nAvailableWidth = rAllocation.Width();
if (nMaxX)
nAvailableWidth -= nColSpacing * (nMaxX - 1);
if (get_column_homogeneous())
{
for (sal_Int32 x = 0; x < nMaxX; ++x)
aWidths[x].m_nValue = nAvailableWidth/nMaxX;
}
else if (rAllocation.Width() != aRequisition.Width())
{
sal_Int32 nExpandables = 0;
for (sal_Int32 x = 0; x < nMaxX; ++x)
if (aWidths[x].m_bExpand)
++nExpandables;
tools::Long nExtraWidthForExpanders = nExpandables ? (rAllocation.Width() - aRequisition.Width()) / nExpandables : 0;
//We don't fit and there is no volunteer to be shrunk
if (!nExpandables && rAllocation.Width() < aRequisition.Width())
{
//first reduce spacing
while (nColSpacing)
{
nColSpacing /= 2;
aRequisition = calculateRequisitionForSpacings(nRowSpacing, nColSpacing);
if (aRequisition.Width() <= rAllocation.Width())
break ;
}
//share out the remaining pain to everyone
tools::Long nExtraWidth = (rAllocation.Width() - aRequisition.Width()) / nMaxX;
for (sal_Int32 x = 0; x < nMaxX; ++x)
aWidths[x].m_nValue += nExtraWidth;
}
if (nExtraWidthForExpanders)
{
for (sal_Int32 x = 0; x < nMaxX; ++x)
if (aWidths[x].m_bExpand)
aWidths[x].m_nValue += nExtraWidthForExpanders;
}
}
tools::Long nAvailableHeight = rAllocation.Height();
if (nMaxY)
nAvailableHeight -= nRowSpacing * (nMaxY - 1);
if (get_row_homogeneous())
{
for (sal_Int32 y = 0; y < nMaxY; ++y)
aHeights[y].m_nValue = nAvailableHeight/nMaxY;
}
else if (rAllocation.Height() != aRequisition.Height())
{
sal_Int32 nExpandables = 0;
for (sal_Int32 y = 0; y < nMaxY; ++y)
if (aHeights[y].m_bExpand)
++nExpandables;
tools::Long nExtraHeightForExpanders = nExpandables ? (rAllocation.Height() - aRequisition.Height()) / nExpandables : 0;
//We don't fit and there is no volunteer to be shrunk
if (!nExpandables && rAllocation.Height() < aRequisition.Height())
{
//first reduce spacing
while (nRowSpacing)
{
nRowSpacing /= 2;
aRequisition = calculateRequisitionForSpacings(nRowSpacing, nColSpacing);
if (aRequisition.Height() <= rAllocation.Height())
break ;
}
//share out the remaining pain to everyone
tools::Long nExtraHeight = (rAllocation.Height() - aRequisition.Height()) / nMaxY;
for (sal_Int32 y = 0; y < nMaxY; ++y)
aHeights[y].m_nValue += nExtraHeight;
}
if (nExtraHeightForExpanders)
{
for (sal_Int32 y = 0; y < nMaxY; ++y)
if (aHeights[y].m_bExpand)
aHeights[y].m_nValue += nExtraHeightForExpanders;
}
}
Point aAllocPos(0, 0);
for (sal_Int32 x = 0; x < nMaxX; ++x)
{
for (sal_Int32 y = 0; y < nMaxY; ++y)
{
#if defined __GNUC__ && !defined __clang__ && __GNUC__ == 13
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdangling-reference"
#endif
GridEntry &rEntry = A[x][y];
#if defined __GNUC__ && !defined __clang__ && __GNUC__ == 13
#pragma GCC diagnostic pop
#endif
vcl::Window *pChild = rEntry.pChild;
if (pChild)
{
Size aChildAlloc(0, 0);
sal_Int32 nWidth = rEntry.nSpanWidth;
for (sal_Int32 nSpanX = 0; nSpanX < nWidth; ++nSpanX)
aChildAlloc.AdjustWidth(aWidths[x+nSpanX].m_nValue );
aChildAlloc.AdjustWidth(nColSpacing*(nWidth-1) );
sal_Int32 nHeight = rEntry.nSpanHeight;
for (sal_Int32 nSpanY = 0; nSpanY < nHeight; ++nSpanY)
aChildAlloc.AdjustHeight(aHeights[y+nSpanY].m_nValue );
aChildAlloc.AdjustHeight(nRowSpacing*(nHeight-1) );
setLayoutAllocation(*pChild, aAllocPos, aChildAlloc);
}
aAllocPos.AdjustY(aHeights[y].m_nValue + nRowSpacing );
}
aAllocPos.AdjustX(aWidths[x].m_nValue + nColSpacing );
aAllocPos.setY( 0 );
}
}
void VclGrid::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter)
{
VclContainer::DumpAsPropertyTree(rJsonWriter);
rJsonWriter.put("type" , "grid" );
}
bool VclGrid::set_property(const OUString &rKey, const OUString &rValue)
{
if (rKey == "row-spacing" )
set_row_spacing(rValue.toInt32());
else if (rKey == "column-spacing" )
set_column_spacing(rValue.toInt32());
else if (rKey == "row-homogeneous" )
m_bRowHomogeneous = toBool(rValue);
else if (rKey == "column-homogeneous" )
m_bColumnHomogeneous = toBool(rValue);
else if (rKey == "n-rows" )
/*nothing to do*/;
else
return VclContainer::set_property(rKey, rValue);
return true ;
}
const vcl::Window *VclBin::get_child() const
{
const WindowImpl* pWindowImpl = ImplGetWindowImpl();
return pWindowImpl->mpFirstChild;
}
vcl::Window *VclBin::get_child()
{
return const_cast <vcl::Window*>(const_cast <const VclBin*>(this )->get_child());
}
Size VclBin::calculateRequisition() const
{
const vcl::Window *pChild = get_child();
if (pChild && pChild->IsVisible())
return getLayoutRequisition(*pChild);
return Size(0, 0);
}
void VclBin::setAllocation(const Size &rAllocation)
{
vcl::Window *pChild = get_child();
if (pChild && pChild->IsVisible())
setLayoutAllocation(*pChild, Point(0, 0), rAllocation);
}
VclFrame::~VclFrame()
{
disposeOnce();
}
void VclFrame::dispose()
{
m_pLabel.reset();
VclBin::dispose();
}
//To-Do, hook a DecorationView into VclFrame ?
Size VclFrame::calculateRequisition() const
{
Size aRet(0, 0);
const vcl::Window *pChild = get_child();
const vcl::Window *pLabel = get_label_widget();
if (pChild && pChild->IsVisible())
aRet = getLayoutRequisition(*pChild);
if (pLabel && pLabel->IsVisible())
{
Size aLabelSize = getLayoutRequisition(*pLabel);
aRet.AdjustHeight(aLabelSize.Height() );
aRet.setWidth( std::max(aLabelSize.Width(), aRet.Width()) );
}
return aRet;
}
void VclFrame::setAllocation(const Size &rAllocation)
{
//SetBackground( Color(0xFF, 0x00, 0xFF) );
Size aAllocation(rAllocation);
Point aChildPos;
vcl::Window *pChild = get_child();
vcl::Window *pLabel = get_label_widget();
if (pLabel && pLabel->IsVisible())
{
Size aLabelSize = getLayoutRequisition(*pLabel);
aLabelSize.setHeight( std::min(aLabelSize.Height(), aAllocation.Height()) );
aLabelSize.setWidth( std::min(aLabelSize.Width(), aAllocation.Width()) );
setLayoutAllocation(*pLabel, aChildPos, aLabelSize);
aAllocation.AdjustHeight( -(aLabelSize.Height()) );
aChildPos.AdjustY(aLabelSize.Height() );
}
if (pChild && pChild->IsVisible())
setLayoutAllocation(*pChild, aChildPos, aAllocation);
}
IMPL_LINK(VclFrame, WindowEventListener, VclWindowEvent&, rEvent, void )
{
if (rEvent.GetId() == VclEventId::ObjectDying)
designate_label(nullptr);
}
void VclFrame::designate_label(vcl::Window *pWindow)
{
assert(!pWindow || pWindow->GetParent() == this );
if (m_pLabel)
m_pLabel->RemoveEventListener(LINK(this , VclFrame, WindowEventListener));
m_pLabel = pWindow;
if (m_pLabel)
m_pLabel->AddEventListener(LINK(this , VclFrame, WindowEventListener));
}
const vcl::Window *VclFrame::get_label_widget() const
{
if (m_pLabel)
return m_pLabel;
assert(GetChildCount() <= 2);
//The label widget is normally the first (of two) children
const WindowImpl* pWindowImpl = ImplGetWindowImpl();
if (pWindowImpl->mpFirstChild == pWindowImpl->mpLastChild) //no label exists
return nullptr;
return pWindowImpl->mpFirstChild;
}
vcl::Window *VclFrame::get_label_widget()
{
return const_cast <vcl::Window*>(const_cast <const VclFrame*>(this )->get_label_widget());
}
const vcl::Window *VclFrame::get_child() const
{
//The child widget is the normally the last (of two) children
const WindowImpl* pWindowImpl = ImplGetWindowImpl();
assert(GetChildCount() == 2 || pWindowImpl->mbInDispose);
if (!m_pLabel)
return pWindowImpl->mpLastChild;
if (pWindowImpl->mpFirstChild == pWindowImpl->mpLastChild) //only label exists
return nullptr;
return pWindowImpl->mpLastChild;
}
vcl::Window *VclFrame::get_child()
{
return const_cast <vcl::Window*>(const_cast <const VclFrame*>(this )->get_child());
}
void VclFrame::set_label(const OUString &rLabel)
{
vcl::Window *pLabel = get_label_widget();
assert(pLabel);
pLabel->SetText(rLabel);
}
OUString VclFrame::get_label() const
{
const vcl::Window *pLabel = get_label_widget();
assert(pLabel);
return pLabel->GetText();
}
OUString VclFrame::getDefaultAccessibleName() const
{
const vcl::Window *pLabel = get_label_widget();
if (pLabel)
return pLabel->GetAccessibleName();
return VclBin::getDefaultAccessibleName();
}
void VclFrame::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter)
{
VclBin::DumpAsPropertyTree(rJsonWriter);
rJsonWriter.put("type" , "frame" );
}
class DisclosureButton final : public CheckBox
{
virtual void ImplDrawCheckBoxState(vcl::RenderContext& rRenderContext) override
{
/* HACK: DisclosureButton is currently assuming, that the disclosure sign
will fit into the rectangle occupied by a normal checkbox on all themes.
If this does not hold true for some theme, ImplGetCheckImageSize
would have to be overridden for DisclosureButton; also GetNativeControlRegion
for ControlType::ListNode would have to be implemented and taken into account
*/
tools::Rectangle aStateRect(GetStateRect());
ImplControlValue aControlValue(GetState() == TRISTATE_TRUE ? ButtonValue::On : ButtonValue::Off);
tools::Rectangle aCtrlRegion(aStateRect);
ControlState nState = ControlState::NONE;
if (HasFocus())
nState |= ControlState::FOCUSED;
if (GetButtonState() & DrawButtonFlags::Default )
nState |= ControlState::DEFAULT ;
if (Window::IsEnabled())
nState |= ControlState::ENABLED;
if (IsMouseOver() && GetMouseRect().Contains(GetPointerPosPixel()))
nState |= ControlState::ROLLOVER;
if (rRenderContext.DrawNativeControl(ControlType::ListNode, ControlPart::Entire, aCtrlRegion,
nState, aControlValue, OUString()))
return ;
ImplSVCtrlData& rCtrlData(ImplGetSVData()->maCtrlData);
if (!rCtrlData.moDisclosurePlus)
rCtrlData.moDisclosurePlus.emplace(StockImage::Yes, SV_DISCLOSURE_PLUS);
if (!rCtrlData.moDisclosureMinus)
rCtrlData.moDisclosureMinus.emplace(StockImage::Yes, SV_DISCLOSURE_MINUS);
Image* pImg
= IsChecked() ? &*rCtrlData.moDisclosureMinus : &*rCtrlData.moDisclosurePlus;
DrawImageFlags nStyle = DrawImageFlags::NONE;
if (!IsEnabled())
nStyle |= DrawImageFlags::Disable;
Size aSize(aStateRect.GetSize());
Size aImgSize(pImg->GetSizePixel());
Point aOff((aSize.Width() - aImgSize.Width()) / 2,
(aSize.Height() - aImgSize.Height()) / 2);
aOff += aStateRect.TopLeft();
rRenderContext.DrawImage(aOff, *pImg, nStyle);
}
public :
explicit DisclosureButton(vcl::Window* pParent)
: CheckBox(pParent, 0)
{
}
virtual void KeyInput( const KeyEvent& rKEvt ) override
{
vcl::KeyCode aKeyCode = rKEvt.GetKeyCode();
if ( !aKeyCode.GetModifier() &&
( ( aKeyCode.GetCode() == KEY_ADD ) ||
( aKeyCode.GetCode() == KEY_SUBTRACT ) )
)
{
Check( aKeyCode.GetCode() == KEY_ADD );
}
else
CheckBox::KeyInput( rKEvt );
}
};
VclExpander::VclExpander(vcl::Window *pParent)
: VclBin(pParent)
, m_bResizeTopLevel(false )
, m_pDisclosureButton(VclPtr<DisclosureButton>::Create(this ))
{
m_pDisclosureButton->SetToggleHdl(LINK(this , VclExpander, ClickHdl));
m_pDisclosureButton->Show();
}
VclExpander::~VclExpander()
{
disposeOnce();
}
bool VclExpander::get_expanded() const
{
return m_pDisclosureButton->IsChecked();
}
void VclExpander::set_expanded(bool bExpanded)
{
m_pDisclosureButton->Check(bExpanded);
}
void VclExpander::set_label(const OUString& rLabel)
{
m_pDisclosureButton->SetText(rLabel);
}
OUString VclExpander::get_label() const
{
return m_pDisclosureButton->GetText();
}
void VclExpander::dispose()
{
m_pDisclosureButton.disposeAndClear();
VclBin::dispose();
}
const vcl::Window *VclExpander::get_child() const
{
const WindowImpl* pWindowImpl = ImplGetWindowImpl();
assert(pWindowImpl->mpFirstChild == m_pDisclosureButton);
return pWindowImpl->mpFirstChild->GetWindow(GetWindowType::Next);
}
vcl::Window *VclExpander::get_child()
{
return const_cast <vcl::Window*>(const_cast <const VclExpander*>(this )->get_child());
}
Size VclExpander::calculateRequisition() const
{
Size aRet(0, 0);
WindowImpl* pWindowImpl = ImplGetWindowImpl();
const vcl::Window *pChild = get_child();
const vcl::Window *pLabel = pChild != pWindowImpl->mpLastChild ? pWindowImpl->mpLastChild.get() : nullptr;
if (pChild && pChild->IsVisible() && m_pDisclosureButton->IsChecked())
aRet = getLayoutRequisition(*pChild);
Size aExpanderSize = getLayoutRequisition(*m_pDisclosureButton);
if (pLabel && pLabel->IsVisible())
{
Size aLabelSize = getLayoutRequisition(*pLabel);
aExpanderSize.setHeight( std::max(aExpanderSize.Height(), aLabelSize.Height()) );
aExpanderSize.AdjustWidth(aLabelSize.Width() );
}
aRet.AdjustHeight(aExpanderSize.Height() );
aRet.setWidth( std::max(aExpanderSize.Width(), aRet.Width()) );
return aRet;
}
void VclExpander::setAllocation(const Size &rAllocation)
{
Size aAllocation(rAllocation);
Point aChildPos;
WindowImpl* pWindowImpl = ImplGetWindowImpl();
//The label widget is the last (of two) children
vcl::Window *pChild = get_child();
vcl::Window *pLabel = pChild != pWindowImpl->mpLastChild.get() ? pWindowImpl->mpLastChild.get() : nullptr;
Size aButtonSize = getLayoutRequisition(*m_pDisclosureButton);
Size aLabelSize;
Size aExpanderSize = aButtonSize;
if (pLabel && pLabel->IsVisible())
{
aLabelSize = getLayoutRequisition(*pLabel);
aExpanderSize.setHeight( std::max(aExpanderSize.Height(), aLabelSize.Height()) );
aExpanderSize.AdjustWidth(aLabelSize.Width() );
}
aExpanderSize.setHeight( std::min(aExpanderSize.Height(), aAllocation.Height()) );
aExpanderSize.setWidth( std::min(aExpanderSize.Width(), aAllocation.Width()) );
aButtonSize.setHeight( std::min(aButtonSize.Height(), aExpanderSize.Height()) );
aButtonSize.setWidth( std::min(aButtonSize.Width(), aExpanderSize.Width()) );
tools::Long nExtraExpanderHeight = aExpanderSize.Height() - aButtonSize.Height();
Point aButtonPos(aChildPos.X(), aChildPos.Y() + nExtraExpanderHeight/2);
setLayoutAllocation(*m_pDisclosureButton, aButtonPos, aButtonSize);
if (pLabel && pLabel->IsVisible())
{
aLabelSize.setHeight( std::min(aLabelSize.Height(), aExpanderSize.Height()) );
aLabelSize.setWidth( std::min(aLabelSize.Width(),
aExpanderSize.Width() - aButtonSize.Width()) );
tools::Long nExtraLabelHeight = aExpanderSize.Height() - aLabelSize.Height();
Point aLabelPos(aChildPos.X() + aButtonSize.Width(), aChildPos.Y() + nExtraLabelHeight/2);
setLayoutAllocation(*pLabel, aLabelPos, aLabelSize);
}
aAllocation.AdjustHeight( -(aExpanderSize.Height()) );
aChildPos.AdjustY(aExpanderSize.Height() );
if (pChild && pChild->IsVisible())
{
if (!m_pDisclosureButton->IsChecked())
aAllocation = Size();
setLayoutAllocation(*pChild, aChildPos, aAllocation);
}
}
bool VclExpander::set_property(const OUString &rKey, const OUString &rValue)
{
if (rKey == "expanded" )
set_expanded(toBool(rValue));
else if (rKey == "resize-toplevel" )
m_bResizeTopLevel = toBool(rValue);
else
return VclBin::set_property(rKey, rValue);
return true ;
}
void VclExpander::StateChanged(StateChangedType nType)
{
VclBin::StateChanged( nType );
if (nType == StateChangedType::InitShow)
{
vcl::Window *pChild = get_child();
if (pChild)
pChild->Show(m_pDisclosureButton->IsChecked());
}
}
const vcl::Window *VclExpander::get_label_widget() const
{
return m_pDisclosureButton;
}
vcl::Window *VclExpander::get_label_widget()
{
return const_cast <vcl::Window*>(const_cast <const VclExpander*>(this )->get_label_widget());
}
void VclExpander::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter)
{
VclContainer::DumpAsPropertyTree(rJsonWriter);
rJsonWriter.put("type" , "expander" );
}
FactoryFunction VclExpander::GetUITestFactory() const
{
return ExpanderUIObject::create;
}
IMPL_LINK( VclExpander, ClickHdl, CheckBox&, rBtn, void )
{
vcl::Window *pChild = get_child();
if (pChild)
{
pChild->Show(rBtn.IsChecked());
queue_resize();
Dialog* pResizeDialog = m_bResizeTopLevel ? GetParentDialog() : nullptr;
if (pResizeDialog)
pResizeDialog->setOptimalLayoutSize(true );
}
maExpandedHdl.Call(*this );
}
VclScrolledWindow::VclScrolledWindow(vcl::Window *pParent)
: VclBin(pParent, WB_HIDE | WB_CLIPCHILDREN | WB_AUTOHSCROLL | WB_AUTOVSCROLL | WB_TABSTOP)
, m_bUserManagedScrolling(false )
, m_eDrawFrameStyle(DrawFrameStyle::NONE)
, m_eDrawFrameFlags(DrawFrameFlags::WindowBorder)
, m_pVScroll(VclPtr<ScrollBar>::Create(this , WB_HIDE | WB_VERT))
, m_pHScroll(VclPtr<ScrollBar>::Create(this , WB_HIDE | WB_HORZ))
, m_aScrollBarBox(VclPtr<ScrollBarBox>::Create(this , WB_HIDE))
{
SetType(WindowType::SCROLLWINDOW);
AllSettings aAllSettings = GetSettings();
StyleSettings aStyle = aAllSettings.GetStyleSettings();
aStyle.SetMonoColor(aStyle.GetShadowColor());
aAllSettings.SetStyleSettings(aStyle);
GetOutDev()->SetSettings(aAllSettings);
Link<ScrollBar*,void > aLink( LINK( this , VclScrolledWindow, ScrollBarHdl ) );
m_pVScroll->SetScrollHdl(aLink);
m_pHScroll->SetScrollHdl(aLink);
m_nBorderWidth = CalcBorderWidth();
}
int VclScrolledWindow::CalcBorderWidth() const
{
if (m_eDrawFrameStyle == DrawFrameStyle::NONE)
return 0;
const tools::Rectangle aRect(tools::Rectangle(Point(0, 0), Size(100, 100)));
DecorationView aDecoView(const_cast <OutputDevice*>(GetOutDev()));
// don't actually draw anything, just measure what size it would be and the diff is the desired border size to reserve
const tools::Rectangle aContentRect = aDecoView.DrawFrame(aRect, m_eDrawFrameStyle, m_eDrawFrameFlags | DrawFrameFlags::NoDraw);
const auto nBorderWidth = (aRect.GetWidth() - aContentRect.GetWidth()) / 2;
return std::max<int >(nBorderWidth, 1);
}
void VclScrolledWindow::dispose()
{
m_pVScroll.disposeAndClear();
m_pHScroll.disposeAndClear();
m_aScrollBarBox.disposeAndClear();
VclBin::dispose();
}
IMPL_LINK_NOARG(VclScrolledWindow, ScrollBarHdl, ScrollBar*, void )
{
vcl::Window *pChild = get_child();
if (!pChild)
return ;
assert(dynamic_cast <VclViewport*>(pChild) && "scrolledwindow child should be a Viewport" );
pChild = pChild->GetWindow(GetWindowType::FirstChild);
if (!pChild)
return ;
Point aWinPos(-m_pHScroll->GetThumbPos(), -m_pVScroll->GetThumbPos());
pChild->SetPosPixel(aWinPos);
}
const vcl::Window *VclScrolledWindow::get_child() const
{
const WindowImpl* pWindowImpl = ImplGetWindowImpl();
assert(GetChildCount() == 4 || pWindowImpl->mbInDispose);
return pWindowImpl->mpLastChild;
}
vcl::Window *VclScrolledWindow::get_child()
{
return const_cast <vcl::Window*>(const_cast <const VclScrolledWindow*>(this )->get_child());
}
Size VclScrolledWindow::calculateRequisition() const
{
Size aRet(0, 0);
const vcl::Window *pChild = get_child();
if (pChild && pChild->IsVisible())
aRet = getLayoutRequisition(*pChild);
if (GetStyle() & WB_VSCROLL)
--> --------------------
--> maximum size reached
--> --------------------
Messung V0.5 C=98 H=88 G=93
¤ Dauer der Verarbeitung: 0.19 Sekunden
¤
*© Formatika GbR, Deutschland