/*
Copyright (C) 2012, University of Cambridge, Department of Psychiatry.
Created by Rudolf Cardinal (rnc1001@cam.ac.uk).
This file is part of CamCOPS.
CamCOPS is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
CamCOPS is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with CamCOPS. If not, see <https://www.gnu.org/licenses/>.
*/
/*
OPTIONAL LGPL: Alternatively, this file may be used under the terms of the
GNU Lesser General Public License version 3 as published by the Free
Software Foundation. You should have received a copy of the GNU Lesser
General Public License along with CamCOPS. If not, see
<https://www.gnu.org/licenses/>.
*/
// From qlayoutengine.cpp:
/* ============================================================================
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtWidgets module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
============================================================================ */
// #define DEBUG_LAYOUT
#include "qtlayouthelpers.h"
#include <QDebug>
#include <QWidget>
#include "layouts/widgetitemhfw.h"
// ============================================================================
// Constants
// ============================================================================
const QRect qtlayouthelpers::QT_DEFAULT_RECT(0, 0, 640, 480); // as per QWidgetPrivate::init()
// ============================================================================
// Ancillary structs/classes
// ============================================================================
QDebug qtlayouthelpers::operator<<(QDebug debug,
const qtlayouthelpers::QQLayoutStruct& ls)
{
debug.nospace()
<< "QQLayoutStruct(stretch " << ls.stretch
<< ", size_hint " << ls.size_hint
<< ", maximum_size " << ls.maximum_size
<< ", minimum_size " << ls.minimum_size
<< ", spacing " << ls.spacing
<< ", expansive " << ls.expansive
<< ", empty " << ls.empty
<< " [done " << ls.done
<< ", pos " << ls.pos
<< ", size " << ls.size
<< "])";
return debug;
}
// ============================================================================
// Helper functions for the helper functions
// ============================================================================
// from qlayoutengine_p.h
using Fixed64 = qint64;
static inline Fixed64 toFixed(const int i)
{
return static_cast<Fixed64>(i * 256);
}
static inline int fRound(const Fixed64 i)
{
return (i % 256 < 128)
? static_cast<int>(i / 256)
: static_cast<int>(1 + i / 256);
}
// ============================================================================
// Helper functions
// ============================================================================
/*
This is the main workhorse of the QGridLayout. It portions out
available space to the chain's children.
The calculation is done in fixed point: "fixed" variables are
scaled by a factor of 256.
If the layout runs "backwards" (i.e. RightToLeft or Up) the layout
is computed mirror-reversed, and it's the caller's responsibility
do reverse the values before use.
chain contains input and output parameters describing the geometry.
count is the count of items in the chain; pos and space give the
interval (relative to parentWidget topLeft).
RNC: this calculates in one direction only (e.g. in the vertical direction
for QVBoxLayout, or the horizontal direction for QHBoxLayout). For a
QGridLayout, it's called at least twice (e.g. QGridLayoutPrivate::distribute).
- pos: starting position
- space: available space
*/
void qtlayouthelpers::qGeomCalc(QVector<QQLayoutStruct>& chain,
const int start,
const int count,
const int pos,
const int space,
int spacer)
{
int c_hint = 0;
int c_min = 0;
int sum_stretch = 0;
int sum_spacing = 0;
int expanding_count = 0;
bool all_empty_nonstretch = true;
int pending_spacing = -1;
int spacer_count = 0;
int i;
for (i = start; i < start + count; i++) {
QQLayoutStruct* data = &chain[i]; // RNC: extra Q
data->done = false;
c_hint += data->smartSizeHint();
c_min += data->minimum_size;
sum_stretch += data->stretch;
if (!data->empty) {
// Using pending_spacing, we ensure that the spacing for the last
// (non-empty) item is ignored.
if (pending_spacing >= 0) {
sum_spacing += pending_spacing;
++spacer_count;
}
pending_spacing = data->effectiveSpacer(spacer);
}
if (data->expansive) {
expanding_count++;
}
all_empty_nonstretch = all_empty_nonstretch && data->empty &&
!data->expansive && data->stretch <= 0;
}
int extraspace = 0;
if (space < c_min + sum_spacing) {
// Less space than minimumSize; take from the biggest first
const int min_size = c_min + sum_spacing;
// shrink the spacers proportionally
if (spacer >= 0) {
spacer = min_size > 0 ? spacer * space / min_size : 0;
sum_spacing = spacer * spacer_count;
}
QVarLengthArray<int, 32> minimum_sizes;
minimum_sizes.reserve(count);
for (i = start; i < start + count; i++) {
minimum_sizes << chain.at(i).minimum_size;
}
std::sort(minimum_sizes.begin(), minimum_sizes.end());
const int space_left = space - sum_spacing;
int sum = 0;
int idx = 0;
int space_used = 0;
int current = 0;
while (idx < count && space_used < space_left) {
current = minimum_sizes.at(idx);
space_used = sum + current * (count - idx);
sum += current;
++idx;
}
--idx;
const int deficit = space_used - space_left;
const int items = count - idx;
// If we truncate all items to "current", we would get "deficit" too
// many pixels. Therefore, we have to remove deficit/items from each
// item bigger than maxval. The actual value to remove is
// deficitPerItem + remainder/items
// "rest" is the accumulated error from using integer arithmetic.
const int deficit_per_item = deficit / items;
const int remainder = deficit % items;
const int maxval = current - deficit_per_item;
int rest = 0;
for (i = start; i < start + count; i++) {
int maxv = maxval;
rest += remainder;
if (rest >= items) {
maxv--;
rest -= items;
}
QQLayoutStruct* data = &chain[i]; // RNC: extra Q
data->size = qMin(data->minimum_size, maxv);
data->done = true;
}
} else if (space < c_hint + sum_spacing) {
// Less space than smartSizeHint(), but more than minimumSize.
// Currently take space equally from each, as in Qt 2.x.
// Commented-out lines will give more space to stretchier items.
int n = count;
int space_left = space - sum_spacing;
int overdraft = c_hint - space_left;
// first give to the fixed ones:
for (i = start; i < start + count; i++) {
QQLayoutStruct* data = &chain[i]; // RNC: extra q
if (!data->done && data->minimum_size >= data->smartSizeHint()) {
data->size = data->smartSizeHint();
data->done = true;
space_left -= data->smartSizeHint();
// sumStretch -= data->stretch;
n--;
}
}
bool finished = n == 0;
while (!finished) {
finished = true;
const Fixed64 fp_over = toFixed(overdraft);
Fixed64 fp_w = 0;
for (i = start; i < start + count; i++) {
QQLayoutStruct* data = &chain[i];
if (data->done) {
continue;
}
// if (sumStretch <= 0)
fp_w += fp_over / n;
// else
// fp_w += (fp_over * data->stretch) / sumStretch;
int w = fRound(fp_w);
data->size = data->smartSizeHint() - w;
fp_w -= toFixed(w); // give the difference to the next
if (data->size < data->minimum_size) {
data->done = true;
data->size = data->minimum_size;
finished = false;
overdraft -= data->smartSizeHint() - data->minimum_size;
// sumStretch -= data->stretch;
n--;
break;
}
}
}
} else { // extra space
int n = count;
int space_left = space - sum_spacing;
// first give to the fixed ones, and handle non-expansiveness
for (i = start; i < start + count; i++) {
QQLayoutStruct* data = &chain[i]; // RNC: extra Q
if (!data->done &&
(data->maximum_size <= data->smartSizeHint() ||
(!all_empty_nonstretch && data->empty &&
!data->expansive && data->stretch == 0))) {
data->size = data->smartSizeHint();
data->done = true;
space_left -= data->size;
sum_stretch -= data->stretch;
if (data->expansive) {
expanding_count--;
}
n--;
}
}
extraspace = space_left;
// Do a trial distribution and calculate how much it is off.
// If there are more deficit pixels than surplus pixels, give
// the minimum size items what they need, and repeat.
// Otherwise give to the maximum size items, and repeat.
//
// Paul Olav Tvete has a wonderful mathematical proof of the
// correctness of this principle, but unfortunately this
// comment is too small to contain it.
int surplus, deficit;
do {
surplus = deficit = 0;
const Fixed64 fp_space = toFixed(space_left);
Fixed64 fp_w = 0;
for (i = start; i < start + count; i++) {
QQLayoutStruct* data = &chain[i]; // RNC: extra Q
if (data->done) {
continue;
}
extraspace = 0;
if (sum_stretch > 0) {
fp_w += (fp_space * data->stretch) / sum_stretch;
} else if (expanding_count > 0) {
fp_w += (fp_space * (data->expansive ? 1 : 0)) / expanding_count;
} else {
fp_w += fp_space * 1 / n;
}
const int w = fRound(fp_w);
data->size = w;
fp_w -= toFixed(w); // give the difference to the next
if (w < data->smartSizeHint()) {
deficit += data->smartSizeHint() - w;
} else if (w > data->maximum_size) {
surplus += w - data->maximum_size;
}
}
if (deficit > 0 && surplus <= deficit) {
// give to the ones that have too little
for (i = start; i < start + count; i++) {
QQLayoutStruct* data = &chain[i]; // RNC: extra Q
if (!data->done && data->size < data->smartSizeHint()) {
data->size = data->smartSizeHint();
data->done = true;
space_left -= data->smartSizeHint();
sum_stretch -= data->stretch;
if (data->expansive) {
expanding_count--;
}
n--;
}
}
}
if (surplus > 0 && surplus >= deficit) {
// take from the ones that have too much
for (i = start; i < start + count; i++) {
QQLayoutStruct* data = &chain[i]; // RNC: extra Q
if (!data->done && data->size > data->maximum_size) {
data->size = data->maximum_size;
data->done = true;
space_left -= data->maximum_size;
sum_stretch -= data->stretch;
if (data->expansive) {
expanding_count--;
}
n--;
}
}
}
} while (n > 0 && surplus != deficit);
if (n == 0) {
extraspace = space_left;
}
}
// As a last resort, we distribute the unwanted space equally
// among the spacers (counting the start and end of the chain). We
// could, but don't, attempt a sub-pixel allocation of the extra
// space.
const int extra = extraspace / (spacer_count + 2);
int p = pos + extra;
for (i = start; i < start + count; i++) {
QQLayoutStruct* data = &chain[i]; // RNC: extra Q
data->pos = p;
p += data->size;
if (!data->empty) {
p += data->effectiveSpacer(spacer) + extra;
}
}
#ifdef DEBUG_LAYOUT
qDebug() << Q_FUNC_INFO;
qDebug() << "- start" << start << "count" << count
<< "pos" << pos << "space" << space << "spacer" << spacer;
for (i = start; i < start + count; ++i) {
qDebug() << "- item" << i << ':'
<< "min" << chain[i].minimum_size
<< "hint" << chain[i].smartSizeHint()
<< "max" << chain[i].maximum_size
<< "stretch" << chain[i].stretch
<< "empty" << chain[i].empty
<< "expansive" << chain[i].expansive
<< "spacing" << chain[i].spacing;
qDebug() << "- result: pos" << chain[i].pos << "size" << chain[i].size;
}
#endif
}
QSize qtlayouthelpers::qSmartMinSize(
const QSize& sizeHint, const QSize& minSizeHint, const QSize& minSize,
const QSize& maxSize, const QSizePolicy& sizePolicy)
{
QSize s(0, 0);
if (sizePolicy.horizontalPolicy() != QSizePolicy::Ignored) {
if (sizePolicy.horizontalPolicy() & QSizePolicy::ShrinkFlag) {
s.setWidth(minSizeHint.width());
} else {
s.setWidth(qMax(sizeHint.width(), minSizeHint.width()));
}
}
if (sizePolicy.verticalPolicy() != QSizePolicy::Ignored) {
if (sizePolicy.verticalPolicy() & QSizePolicy::ShrinkFlag) {
s.setHeight(minSizeHint.height());
} else {
s.setHeight(qMax(sizeHint.height(), minSizeHint.height()));
}
}
s = s.boundedTo(maxSize);
if (minSize.width() > 0) {
s.setWidth(minSize.width());
}
if (minSize.height() > 0) {
s.setHeight(minSize.height());
}
return s.expandedTo(QSize(0, 0));
}
QSize qtlayouthelpers::qSmartMinSize(const QWidgetItem* i)
{
QWidget* w = const_cast<QWidgetItem*>(i)->widget(); // RNC: nasty!
return qSmartMinSize(w->sizeHint(), w->minimumSizeHint(),
w->minimumSize(), w->maximumSize(),
w->sizePolicy());
}
QSize qtlayouthelpers::qSmartMinSize(const QWidget* w)
{
return qSmartMinSize(w->sizeHint(), w->minimumSizeHint(),
w->minimumSize(), w->maximumSize(),
w->sizePolicy());
}
QSize qtlayouthelpers::qSmartMaxSize(
const QSize& sizeHint, const QSize& minSize, const QSize& maxSize,
const QSizePolicy& sizePolicy, const Qt::Alignment align)
{
if (align & Qt::AlignHorizontal_Mask && align & Qt::AlignVertical_Mask) {
return QSize(QLAYOUTSIZE_MAX, QLAYOUTSIZE_MAX);
}
QSize s = maxSize;
const QSize hint = sizeHint.expandedTo(minSize);
if (s.width() == QWIDGETSIZE_MAX && !(align & Qt::AlignHorizontal_Mask)) {
if (!(sizePolicy.horizontalPolicy() & QSizePolicy::GrowFlag)) {
s.setWidth(hint.width());
}
}
if (s.height() == QWIDGETSIZE_MAX && !(align & Qt::AlignVertical_Mask)) {
if (!(sizePolicy.verticalPolicy() & QSizePolicy::GrowFlag)) {
s.setHeight(hint.height());
}
}
if (align & Qt::AlignHorizontal_Mask) {
s.setWidth(QLAYOUTSIZE_MAX);
}
if (align & Qt::AlignVertical_Mask) {
s.setHeight(QLAYOUTSIZE_MAX);
}
return s;
}
QSize qtlayouthelpers::qSmartMaxSize(const QWidgetItem* i,
const Qt::Alignment align)
{
QWidget* w = const_cast<QWidgetItem*>(i)->widget(); // RNC: nasty!
return qSmartMaxSize(w->sizeHint().expandedTo(w->minimumSizeHint()),
w->minimumSize(),
w->maximumSize(),
w->sizePolicy(),
align);
}
QSize qtlayouthelpers::qSmartMaxSize(const QWidget* w, const
Qt::Alignment align)
{
return qSmartMaxSize(w->sizeHint().expandedTo(w->minimumSizeHint()),
w->minimumSize(),
w->maximumSize(),
w->sizePolicy(),
align);
}
int qtlayouthelpers::qSmartSpacing(const QLayout* layout,
const QStyle::PixelMetric pm)
{
QObject* parent = layout->parent();
if (!parent) {
return -1;
}
if (parent->isWidgetType()) {
auto pw = static_cast<QWidget*>(parent);
return pw->style()->pixelMetric(pm, nullptr, pw);
}
return static_cast<QLayout*>(parent)->spacing();
}
// from qlayoutengine_p.h
// original is static: http://stackoverflow.com/questions/558122/what-is-a-static-function
// ... and inline
/*
Modify total maximum (max), total expansion (exp), and total empty
when adding boxmax/boxexp.
Expansive boxes win over non-expansive boxes.
Non-empty boxes win over empty boxes.
*/
void qtlayouthelpers::qMaxExpCalc(int& max,
bool& exp,
bool &empty,
const int boxmax,
const bool boxexp,
const bool boxempty)
{
if (exp) {
if (boxexp) {
max = qMax(max, boxmax);
}
} else {
if (boxexp || (empty && (!boxempty || max == 0))) {
max = boxmax;
} else if (empty == boxempty) {
max = qMin(max, boxmax);
}
}
exp = exp || boxexp;
empty = empty && boxempty;
}
// ============================================================================
// Static-looking things from QLayoutPrivate
// ============================================================================
/*
RNC: REMOVED:
// Static item factory functions that allow for hooking things in Designer
QLayoutPrivate::QWidgetItemFactoryMethod QLayoutPrivate::widgetItemFactoryMethod = 0;
QLayoutPrivate::QSpacerItemFactoryMethod QLayoutPrivate::spacerItemFactoryMethod = 0;
*/
// was QLayoutPrivate::createWidgetItem
QWidgetItem* qtlayouthelpers::createWidgetItem(const QLayout* layout,
QWidget* widget,
const bool use_hfw_capable_item)
{
Q_UNUSED(layout) // RNC
/* // RNC: removed
if (widgetItemFactoryMethod)
if (QWidgetItem* wi = (*widgetItemFactoryMethod)(layout, widget))
return wi;
*/
if (use_hfw_capable_item) {
return new WidgetItemHfw(widget);
}
return new QWidgetItemV2(widget);
}
// was QLayoutPrivate::createSpacerItem
QSpacerItem* qtlayouthelpers::createSpacerItem(
const QLayout* layout,
const int w, const int h,
const QSizePolicy::Policy h_policy,
const QSizePolicy::Policy v_policy)
{
Q_UNUSED(layout) // RNC
/* // RNC: removed
if (spacerItemFactoryMethod)
if (QSpacerItem* si = (*spacerItemFactoryMethod)(layout, w, h, hPolicy, vPolicy))
return si;
*/
return new QSpacerItem(w, h, h_policy, v_policy);
}
/*
Returns \c true if the \a widget can be added to the \a layout;
otherwise returns \c false.
RNC: Was originally QLayoutPrivate::checkWidget
... "from" parameter added to make it a standalone function
*/
bool qtlayouthelpers::checkWidget(QWidget* widget, QLayout* from)
{
if (Q_UNLIKELY(!widget)) {
qWarning("QLayout: Cannot add a null widget to %s/%ls",
from->metaObject()->className(),
qUtf16Printable(from->objectName()));
return false;
}
if (Q_UNLIKELY(widget == from->parentWidget())) {
qWarning("QLayout: Cannot add parent widget %s/%ls to its child layout %s/%ls",
widget->metaObject()->className(),
qUtf16Printable(widget->objectName()),
from->metaObject()->className(),
qUtf16Printable(from->objectName()));
return false;
}
return true;
}
/*
Returns \c true if the \a otherLayout can be added to the \a layout;
otherwise returns \c false.
RNC: Was originally QLayoutPrivate::checkLayout
... "from" parameter added to make it a standalone function
*/
bool qtlayouthelpers::checkLayout(QLayout* other_layout, QLayout* from)
{
if (Q_UNLIKELY(!other_layout)) {
qWarning("QLayout: Cannot add a null layout to %s/%ls",
from->metaObject()->className(),
qUtf16Printable(from->objectName()));
return false;
}
if (Q_UNLIKELY(other_layout == from)) {
qWarning("QLayout: Cannot add layout %s/%ls to itself",
from->metaObject()->className(),
qUtf16Printable(from->objectName()));
return false;
}
return true;
}
// ============================================================================
// RNC extras
// ============================================================================
QRect qtlayouthelpers::defaultRectOfWidth(const int width)
{
return QRect(QPoint(0, 0), QSize(width, QLAYOUTSIZE_MAX));
}