From 4051e69852cd648942e03cd36e14c2c34124c19f Mon Sep 17 00:00:00 2001
From: Jan Arve Saether <jan-arve.saether@digia.com>
Date: Fri, 31 May 2013 15:38:00 +0200
Subject: [PATCH] Fixed some bugs in how effective size hints were calculated

Do not bound explicit preferred size with implicit sizes. This
means that if Layout.preferredWidth was *explicitly* set to 50,
and Layout.maximumWidth had the implicit value 20, the effective
maximum width would be expanded to 50 in order to not disregard
the explicitly set preferred width. (covered by the tag
"expandMaxToExplicitPref" in test_sizeHint)

Note that this doesn't break any autotests, but the row layout
autotest was slightly changed to be able to add the new test cases.

This should make the behavior match the behavior of
QGraphicsGridLayout and QGraphicsLinearLayout

Change-Id: Ia23c8ef909827f14349906c003c72bb83689ef9a
Reviewed-by: Caroline Chao <caroline.chao@digia.com>
Reviewed-by: Jens Bache-Wiig <jens.bache-wiig@digia.com>
---
 src/layouts/qquickgridlayoutengine.cpp     | 95 ++++++++++++++++------
 src/layouts/qquicklayout_p.h               | 16 ++++
 tests/auto/controls/data/tst_rowlayout.qml | 21 +++--
 3 files changed, 97 insertions(+), 35 deletions(-)

diff --git a/src/layouts/qquickgridlayoutengine.cpp b/src/layouts/qquickgridlayoutengine.cpp
index bacfd965b..7f65d0d5b 100644
--- a/src/layouts/qquickgridlayoutengine.cpp
+++ b/src/layouts/qquickgridlayoutengine.cpp
@@ -98,14 +98,62 @@ static inline void combineHints(qreal &current, qreal fallbackHint)
         current = fallbackHint;
 }
 
+static inline void combineSize(QSizeF &result, const QSizeF &fallbackSize)
+{
+    combineHints(result.rwidth(), fallbackSize.width());
+    combineHints(result.rheight(), fallbackSize.height());
+}
+
+static inline void combineImplicitHints(QQuickLayoutAttached *info, Qt::SizeHint which, QSizeF *size)
+{
+    if (!info) return;
+
+    Q_ASSERT(which == Qt::MinimumSize || which == Qt::MaximumSize);
+
+    const QSizeF constraint(which == Qt::MinimumSize
+                            ? QSizeF(info->minimumWidth(), info->minimumHeight())
+                            : QSizeF(info->maximumWidth(), info->maximumHeight()));
+
+    if (!info->isExtentExplicitlySet(Qt::Horizontal, which))
+        combineHints(size->rwidth(),  constraint.width());
+    if (!info->isExtentExplicitlySet(Qt::Vertical, which))
+        combineHints(size->rheight(), constraint.height());
+}
+
 /*!
     \internal
     Note: Can potentially return the attached QQuickLayoutAttached object through \a attachedInfo.
 
     It is like this is because it enables it to be reused.
+
+    The goal of this function is to return the effective minimum, preferred and maximum size hints
+    that the layout will use for this item.
+    This function takes care of gathering all explicitly set size hints, normalizes them so
+    that min < pref < max.
+    Further, the hints _not_explicitly_ set will then be initialized with the implicit size hints,
+    which is usually derived from the content of the layouts (or items).
+
+    The following table illustrates the preference of the properties used for measuring layout
+    items. If present, the USER properties will be preferred. If USER properties are not present,
+    the HINT properties will be preferred. Finally, the FALLBACK properties will be used as an
+    ultimate fallback.
+
+    Note that one can query if the value of Layout.minimumWidth or Layout.maximumWidth has been
+    explicitly or implicitly set with QQuickLayoutAttached::isExtentExplicitlySet(). This
+    determines if it should be used as a USER or as a HINT value.
+
+
+                 | *Minimum*            | *Preferred*           | *Maximum*                |
++----------------+----------------------+-----------------------+--------------------------+
+|USER (explicit) | Layout.minimumWidth  | Layout.preferredWidth | Layout.maximumWidth      |
+|HINT (implicit) | Layout.minimumWidth  | implicitWidth         | Layout.maximumWidth      |
+|FALLBACK        | 0                    | width                 | Number.POSITIVE_INFINITY |
++----------------+----------------------+-----------------------+--------------------------+
  */
 void QQuickGridLayoutItem::effectiveSizeHints_helper(QQuickItem *item, QSizeF *cachedSizeHints, QQuickLayoutAttached **attachedInfo, bool useFallbackToWidthOrHeight)
 {
+    for (int i = 0; i < Qt::NSizeHints; ++i)
+        cachedSizeHints[i] = QSizeF();
     QQuickLayoutAttached *info = attachedLayoutObject(item, false);
     // First, retrieve the user-specified hints from the attached "Layout." properties
     if (info) {
@@ -123,16 +171,16 @@ void QQuickGridLayoutItem::effectiveSizeHints_helper(QQuickItem *item, QSizeF *c
         for (int i = 0; i < NSizes; ++i) {
             SizeGetter getter = horGetters.call[i];
             Q_ASSERT(getter);
-            cachedSizeHints[i].setWidth((info->*getter)());
+
+            if (info->isExtentExplicitlySet(Qt::Horizontal, (Qt::SizeHint)i))
+                cachedSizeHints[i].setWidth((info->*getter)());
+
             getter = verGetters.call[i];
             Q_ASSERT(getter);
-            cachedSizeHints[i].setHeight((info->*getter)());
+            if (info->isExtentExplicitlySet(Qt::Vertical, (Qt::SizeHint)i))
+                cachedSizeHints[i].setHeight((info->*getter)());
         }
-    } else {
-        for (int i = 0; i < NSizes; ++i)
-            cachedSizeHints[i] = QSize();
     }
-    cachedSizeHints[Qt::MinimumDescent] = QSize();  //### FIXME when baseline support is added
 
     QSizeF &minS = cachedSizeHints[Qt::MinimumSize];
     QSizeF &prefS = cachedSizeHints[Qt::PreferredSize];
@@ -144,23 +192,21 @@ void QQuickGridLayoutItem::effectiveSizeHints_helper(QQuickItem *item, QSizeF *c
     // to:   [10, 10, 60]
     normalizeHints(minS.rwidth(), prefS.rwidth(), maxS.rwidth(), descentS.rwidth());
     normalizeHints(minS.rheight(), prefS.rheight(), maxS.rheight(), descentS.rheight());
-/*
-  The following table illustrates the preference of the properties used for measuring layout
-  items. If present, the USER properties will be preferred. If USER properties are not present,
-  the HINT 1 properties will be preferred. Finally, the HINT 2 properties will be used as an
-  ultimate fallback.
-
-       | USER                           | HINT 1            | HINT 2
-  -----+--------------------------------+-------------------+-------
-  MIN  | Layout.minimumWidth            |                   | 0
-  PREF | Layout.preferredWidth          | implicitWidth     | width
-  MAX  | Layout.maximumWidth            |                   | 1000000000 (-1)
-  -----+--------------------------------+-------------------+--------
-Fixed    | Layout.fillWidth               | Expanding if layout, Fixed if item |
-
-*/
+
+    // All explicit values gathered, now continue to gather the implicit sizes
+
+    //--- GATHER MAXIMUM SIZE HINTS ---
+    combineImplicitHints(info, Qt::MaximumSize, &maxS);
+    combineSize(maxS, QSizeF(std::numeric_limits<qreal>::infinity(), std::numeric_limits<qreal>::infinity()));
+    // implicit max or min sizes should not limit an explicitly set preferred size
+    expandSize(maxS, prefS);
+    expandSize(maxS, minS);
+
     //--- GATHER MINIMUM SIZE HINTS ---
-    // They are always 0
+    combineImplicitHints(info, Qt::MinimumSize, &minS);
+    expandSize(minS, QSizeF(0,0));
+    boundSize(minS, prefS);
+    boundSize(minS, maxS);
 
     //--- GATHER PREFERRED SIZE HINTS ---
     // First, from implicitWidth/Height
@@ -203,17 +249,12 @@ Fixed    | Layout.fillWidth               | Expanding if layout, Fixed if item |
             item->blockSignals(false);
         }
     }
-    //--- GATHER MAXIMUM SIZE HINTS ---
-    combineHints(cachedSizeHints[Qt::MaximumSize].rwidth(), std::numeric_limits<qreal>::infinity());
-    combineHints(cachedSizeHints[Qt::MaximumSize].rheight(), std::numeric_limits<qreal>::infinity());
 
     //--- GATHER DESCENT
     // ### Not implemented
 
 
     // Normalize again after the implicit hints have been gathered
-    expandSize(minS, QSizeF(0,0));
-    boundSize(minS, maxS);
     expandSize(prefS, minS);
     boundSize(prefS, maxS);
 
diff --git a/src/layouts/qquicklayout_p.h b/src/layouts/qquicklayout_p.h
index 8a334677a..0239739a6 100644
--- a/src/layouts/qquicklayout_p.h
+++ b/src/layouts/qquicklayout_p.h
@@ -175,6 +175,22 @@ public:
 
     qreal sizeHint(Qt::SizeHint which, Qt::Orientation orientation) const;
 
+    bool isExtentExplicitlySet(Qt::Orientation o, Qt::SizeHint whichSize) const
+    {
+        switch (whichSize) {
+        case Qt::MinimumSize:
+            return o == Qt::Horizontal ? m_isMinimumWidthSet : m_isMinimumHeightSet;
+        case Qt::MaximumSize:
+            return o == Qt::Horizontal ? m_isMaximumWidthSet : m_isMaximumHeightSet;
+        case Qt::PreferredSize:
+            return true;            // Layout.preferredWidth is always explicitly set
+        case Qt::MinimumDescent:    // Not supported
+        case Qt::NSizeHints:
+            return false;
+        }
+        return false;
+    }
+
 signals:
     void minimumWidthChanged();
     void minimumHeightChanged();
diff --git a/tests/auto/controls/data/tst_rowlayout.qml b/tests/auto/controls/data/tst_rowlayout.qml
index 458a2885f..e92dad47e 100644
--- a/tests/auto/controls/data/tst_rowlayout.qml
+++ b/tests/auto/controls/data/tst_rowlayout.qml
@@ -459,6 +459,12 @@ Item {
                     { tag: "propagateMaximumWidth",    layoutHints: [10, 20, -1], childHints: [11, 21, 30], expected:[10, 20, 30]},
                     { tag: "propagateAll",             layoutHints: [-1, -1, -1], childHints: [10, 20, 30], expected:[10, 20, 30]},
                     { tag: "propagateCrazy",           layoutHints: [-1, -1, -1], childHints: [40, 21, 30], expected:[30, 30, 30]},
+                    { tag: "expandMinToExplicitPref",  layoutHints: [-1,  1, -1], childHints: [11, 21, 31], expected:[ 1,  1, 31]},
+                    { tag: "expandMaxToExplicitPref",  layoutHints: [-1, 99, -1], childHints: [11, 21, 31], expected:[11, 99, 99]},
+                    { tag: "expandAllToExplicitMin",   layoutHints: [99, -1, -1], childHints: [11, 21, 31], expected:[99, 99, 99]},
+                    { tag: "expandPrefToExplicitMin",  layoutHints: [24, -1, -1], childHints: [11, 21, 31], expected:[24, 24, 31]},
+                    { tag: "boundPrefToExplicitMax",   layoutHints: [-1, -1, 19], childHints: [11, 21, 31], expected:[11, 19, 19]},
+                    { tag: "boundAllToExplicitMax",    layoutHints: [-1, -1,  9], childHints: [11, 21, 31], expected:[ 9,  9,  9]},
                     ];
         }
 
@@ -468,22 +474,21 @@ Item {
 
         function test_sizeHint(data) {
             var layout = layout_sizeHint_Component.createObject(container)
-            layout.Layout.minimumWidth = data.layoutHints[0]
-            layout.Layout.preferredWidth = data.layoutHints[1]
-            layout.Layout.maximumWidth = data.layoutHints[2]
 
-            var child = layout.children[0].children[0]
+            var grid = layout.children[0]
+            grid.Layout.minimumWidth = data.layoutHints[0]
+            grid.Layout.preferredWidth = data.layoutHints[1]
+            grid.Layout.maximumWidth = data.layoutHints[2]
+
+            var child = grid.children[0]
             if (data.implicitWidth !== undefined) {
                 child.implicitWidth = data.implicitWidth
             }
-
             child.Layout.minimumWidth = data.childHints[0]
             child.Layout.preferredWidth = data.childHints[1]
             child.Layout.maximumWidth = data.childHints[2]
 
-            var grid = layout.children[0]
-            var preferredWidth = layout.Layout.preferredWidth >= 0 ? layout.Layout.preferredWidth : layout.implicitWidth
-            var effectiveSizeHintResult = [layout.Layout.minimumWidth, preferredWidth, layout.Layout.maximumWidth]
+            var effectiveSizeHintResult = [layout.Layout.minimumWidth, layout.implicitWidth, layout.Layout.maximumWidth]
             compare(effectiveSizeHintResult, data.expected)
             layout.destroy()
         }
-- 
GitLab