From d8b09bc1eacdf97366058807cc021f81be171526 Mon Sep 17 00:00:00 2001
From: Tobias Markmann <tm@ayena.de>
Date: Fri, 24 Feb 2017 15:43:53 +0100
Subject: Use FlowLayout instead of QGridLayout in QtEmojiGrid
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

FlowLayout is an official BSD-licensed Qt example showing
how to implement custom layouts. It will layout items
dynamically in rows.

This way we don’t need static column/row calculations for
QGridLayout and it looks better.

Test-Information:

Build and ran on macOS 10.12.3 with Qt 5.7 to test that it has
a better, less spacious look.

Change-Id: Ief1299b0d3fb1e516a1973469f4f9a26824942f2

diff --git a/Swift/QtUI/FlowLayout.cpp b/Swift/QtUI/FlowLayout.cpp
new file mode 100644
index 0000000..c42b7e1
--- /dev/null
+++ b/Swift/QtUI/FlowLayout.cpp
@@ -0,0 +1,199 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** 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.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+**   * Redistributions of source code must retain the above copyright
+**     notice, this list of conditions and the following disclaimer.
+**   * Redistributions in binary form must reproduce the above copyright
+**     notice, this list of conditions and the following disclaimer in
+**     the documentation and/or other materials provided with the
+**     distribution.
+**   * Neither the name of The Qt Company Ltd nor the names of its
+**     contributors may be used to endorse or promote products derived
+**     from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QtWidgets>
+
+#include "FlowLayout.h"
+FlowLayout::FlowLayout(QWidget *parent, int margin, int hSpacing, int vSpacing)
+    : QLayout(parent), m_hSpace(hSpacing), m_vSpace(vSpacing)
+{
+    setContentsMargins(margin, margin, margin, margin);
+}
+
+FlowLayout::FlowLayout(int margin, int hSpacing, int vSpacing)
+    : m_hSpace(hSpacing), m_vSpace(vSpacing)
+{
+    setContentsMargins(margin, margin, margin, margin);
+}
+
+FlowLayout::~FlowLayout()
+{
+    QLayoutItem *item;
+    while ((item = takeAt(0)))
+        delete item;
+}
+
+void FlowLayout::addItem(QLayoutItem *item)
+{
+    itemList.append(item);
+}
+
+int FlowLayout::horizontalSpacing() const
+{
+    if (m_hSpace >= 0) {
+        return m_hSpace;
+    } else {
+        return smartSpacing(QStyle::PM_LayoutHorizontalSpacing);
+    }
+}
+
+int FlowLayout::verticalSpacing() const
+{
+    if (m_vSpace >= 0) {
+        return m_vSpace;
+    } else {
+        return smartSpacing(QStyle::PM_LayoutVerticalSpacing);
+    }
+}
+
+int FlowLayout::count() const
+{
+    return itemList.size();
+}
+
+QLayoutItem *FlowLayout::itemAt(int index) const
+{
+    return itemList.value(index);
+}
+
+QLayoutItem *FlowLayout::takeAt(int index)
+{
+    if (index >= 0 && index < itemList.size())
+        return itemList.takeAt(index);
+    else
+        return 0;
+}
+
+Qt::Orientations FlowLayout::expandingDirections() const
+{
+    return 0;
+}
+
+bool FlowLayout::hasHeightForWidth() const
+{
+    return true;
+}
+
+int FlowLayout::heightForWidth(int width) const
+{
+    int height = doLayout(QRect(0, 0, width, 0), true);
+    return height;
+}
+
+void FlowLayout::setGeometry(const QRect &rect)
+{
+    QLayout::setGeometry(rect);
+    doLayout(rect, false);
+}
+
+QSize FlowLayout::sizeHint() const
+{
+    return minimumSize();
+}
+
+QSize FlowLayout::minimumSize() const
+{
+    QSize size;
+    QLayoutItem *item;
+    foreach (item, itemList)
+        size = size.expandedTo(item->minimumSize());
+
+    size += QSize(2*margin(), 2*margin());
+    return size;
+}
+
+int FlowLayout::doLayout(const QRect &rect, bool testOnly) const
+{
+    int left, top, right, bottom;
+    getContentsMargins(&left, &top, &right, &bottom);
+    QRect effectiveRect = rect.adjusted(+left, +top, -right, -bottom);
+    int x = effectiveRect.x();
+    int y = effectiveRect.y();
+    int lineHeight = 0;
+
+    QLayoutItem *item;
+    foreach (item, itemList) {
+        QWidget *wid = item->widget();
+        int spaceX = horizontalSpacing();
+        if (spaceX == -1)
+            spaceX = wid->style()->layoutSpacing(
+                QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Horizontal);
+        int spaceY = verticalSpacing();
+        if (spaceY == -1)
+            spaceY = wid->style()->layoutSpacing(
+                QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Vertical);
+        int nextX = x + item->sizeHint().width() + spaceX;
+        if (nextX - spaceX > effectiveRect.right() && lineHeight > 0) {
+            x = effectiveRect.x();
+            y = y + lineHeight + spaceY;
+            nextX = x + item->sizeHint().width() + spaceX;
+            lineHeight = 0;
+        }
+
+        if (!testOnly)
+            item->setGeometry(QRect(QPoint(x, y), item->sizeHint()));
+
+        x = nextX;
+        lineHeight = qMax(lineHeight, item->sizeHint().height());
+    }
+    return y + lineHeight - rect.y() + bottom;
+}
+int FlowLayout::smartSpacing(QStyle::PixelMetric pm) const
+{
+    QObject *parent = this->parent();
+    if (!parent) {
+        return -1;
+    } else if (parent->isWidgetType()) {
+        QWidget *pw = static_cast<QWidget *>(parent);
+        return pw->style()->pixelMetric(pm, 0, pw);
+    } else {
+        return static_cast<QLayout *>(parent)->spacing();
+    }
+}
diff --git a/Swift/QtUI/FlowLayout.h b/Swift/QtUI/FlowLayout.h
new file mode 100644
index 0000000..1453d45
--- /dev/null
+++ b/Swift/QtUI/FlowLayout.h
@@ -0,0 +1,86 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** 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.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+**   * Redistributions of source code must retain the above copyright
+**     notice, this list of conditions and the following disclaimer.
+**   * Redistributions in binary form must reproduce the above copyright
+**     notice, this list of conditions and the following disclaimer in
+**     the documentation and/or other materials provided with the
+**     distribution.
+**   * Neither the name of The Qt Company Ltd nor the names of its
+**     contributors may be used to endorse or promote products derived
+**     from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef FLOWLAYOUT_H
+#define FLOWLAYOUT_H
+
+#include <QLayout>
+#include <QRect>
+#include <QStyle>
+class FlowLayout : public QLayout
+{
+public:
+    explicit FlowLayout(QWidget *parent, int margin = -1, int hSpacing = -1, int vSpacing = -1);
+    explicit FlowLayout(int margin = -1, int hSpacing = -1, int vSpacing = -1);
+    ~FlowLayout();
+
+    void addItem(QLayoutItem *item) override;
+    int horizontalSpacing() const;
+    int verticalSpacing() const;
+    Qt::Orientations expandingDirections() const override;
+    bool hasHeightForWidth() const override;
+    int heightForWidth(int) const override;
+    int count() const override;
+    QLayoutItem *itemAt(int index) const override;
+    QSize minimumSize() const override;
+    void setGeometry(const QRect &rect) override;
+    QSize sizeHint() const override;
+    QLayoutItem *takeAt(int index) override;
+
+private:
+    int doLayout(const QRect &rect, bool testOnly) const;
+    int smartSpacing(QStyle::PixelMetric pm) const;
+
+    QList<QLayoutItem *> itemList;
+    int m_hSpace;
+    int m_vSpace;
+};
+
+#endif // FLOWLAYOUT_H
diff --git a/Swift/QtUI/QtEmojiCell.cpp b/Swift/QtUI/QtEmojiCell.cpp
index 4932ece..3f2c652 100644
--- a/Swift/QtUI/QtEmojiCell.cpp
+++ b/Swift/QtUI/QtEmojiCell.cpp
@@ -21,7 +21,9 @@ namespace Swift {
         font.setBold(true);
         setFont(font);
 
-        setFixedWidth(fontMetrics().width("\xF0\x9F\x98\x83")+5);
+        const auto boundingRect = fontMetrics().boundingRect("\xF0\x9F\x98\x83");
+        setFixedWidth(qMax(boundingRect.width(), boundingRect.height()));
+        setFixedHeight(qMax(boundingRect.width(), boundingRect.height()));
 
         setFlat(true);
         setToolTip(shortname);
diff --git a/Swift/QtUI/QtEmojisGrid.cpp b/Swift/QtUI/QtEmojisGrid.cpp
index eadc64f..6064a66 100644
--- a/Swift/QtUI/QtEmojisGrid.cpp
+++ b/Swift/QtUI/QtEmojisGrid.cpp
@@ -18,11 +18,13 @@
 #include <Swift/QtUI/QtSwiftUtil.h>
 
 namespace Swift {
-QtEmojisGrid::QtEmojisGrid() {
+    static const int emojiCellSpacing = 2;
 
-}
+    QtEmojisGrid::QtEmojisGrid() : FlowLayout(0, emojiCellSpacing, emojiCellSpacing) {
+
+    }
 
-QtEmojisGrid::QtEmojisGrid(QString categoryName) {
+    QtEmojisGrid::QtEmojisGrid(QString categoryName) : FlowLayout(0, emojiCellSpacing, emojiCellSpacing) {
         auto category = EmojiMapper::categoryNameToEmojis(Q2PSTRING(categoryName));
 
         QVector<QString> categoryEmojis;
@@ -37,29 +39,17 @@ QtEmojisGrid::QtEmojisGrid(QString categoryName) {
     void QtEmojisGrid::setEmojis(const QVector<QString>& emojis) {
         clearEmojis();
 
-        int iEmoji = 0;
         for (const auto& unicodeEmoji : emojis) {
             QString shortname = QString::fromStdString(EmojiMapper::unicodeToShortname(Q2PSTRING(unicodeEmoji)));
-            QtEmojiCell* emoji = new QtEmojiCell(shortname, unicodeEmoji);
-            this->addWidget(emoji, iEmoji/6, iEmoji%6);
+            auto emoji = new QtEmojiCell(shortname, unicodeEmoji);
             connect(emoji, SIGNAL(emojiClicked(QString)), this, SIGNAL(onEmojiSelected(QString)));
-            iEmoji++;
-        }
-        for (int index = 0; index < columnCount(); index++) {
-            auto layoutItem = itemAtPosition(0, index);
-            if (layoutItem) {
-                auto cellWidget = layoutItem->widget();
-                if (cellWidget) {
-                    setColumnMinimumWidth(index, cellWidget->width());
-                }
-            }
+            addItem(new QWidgetItem(emoji));
         }
-        setSpacing(5);
     }
 
     void QtEmojisGrid::clearEmojis() {
         QLayoutItem* child = nullptr;
-        while ((child = this->takeAt(0)) != 0) {
+        while ((child = this->takeAt(0)) != nullptr) {
             if (child->widget()) {
                 child->widget()->hide();
                 removeWidget(child->widget());
diff --git a/Swift/QtUI/QtEmojisGrid.h b/Swift/QtUI/QtEmojisGrid.h
index e4bc664..b7ba87f 100644
--- a/Swift/QtUI/QtEmojisGrid.h
+++ b/Swift/QtUI/QtEmojisGrid.h
@@ -6,14 +6,15 @@
 
 #pragma once
 
-#include <QGridLayout>
 #include <QString>
 #include <QVector>
 
 #include <SwifTools/EmojiMapper.h>
 
+#include <Swift/QtUI/FlowLayout.h>
+
 namespace Swift {
-    class QtEmojisGrid : public QGridLayout {
+    class QtEmojisGrid : public FlowLayout {
         Q_OBJECT
         public:
             explicit QtEmojisGrid();
diff --git a/Swift/QtUI/QtEmojisScroll.cpp b/Swift/QtUI/QtEmojisScroll.cpp
index 3e9969b..9765aa5 100644
--- a/Swift/QtUI/QtEmojisScroll.cpp
+++ b/Swift/QtUI/QtEmojisScroll.cpp
@@ -14,17 +14,11 @@
 #include <Swift/QtUI/QtRecentEmojisGrid.h>
 
 namespace Swift {
-    QtEmojisScroll::QtEmojisScroll(QGridLayout* emojiLayout, QWidget *parent) : QWidget(parent) {
+    QtEmojisScroll::QtEmojisScroll(QLayout* emojiLayout, QWidget *parent) : QWidget(parent) {
         auto selector = new QWidget();
         auto scrollArea = new QScrollArea();
         scrollArea->setWidgetResizable(true);
         scrollArea->setWidget(selector);
-        // Set minimum width to fit GridLayout (no horizontal ScrollBar)
-        const int margin = style()->pixelMetric(QStyle::PM_MenuHMargin) * 2;
-        scrollArea->setMinimumWidth((emojiLayout->columnCount()+1)*(emojiLayout->columnMinimumWidth(0)+emojiLayout->spacing())+margin);
-        // Set height according to width (better ratio)
-        const double ratio = 16.0/9.0; //ratio width/height
-        scrollArea->setMinimumHeight(scrollArea->minimumWidth()/ratio);
         selector->setLayout(emojiLayout);
 
         this->setLayout(new QVBoxLayout);
diff --git a/Swift/QtUI/QtEmojisScroll.h b/Swift/QtUI/QtEmojisScroll.h
index 8ee9257..959ab5f 100644
--- a/Swift/QtUI/QtEmojisScroll.h
+++ b/Swift/QtUI/QtEmojisScroll.h
@@ -6,13 +6,13 @@
 
 #pragma once
 
-#include <QGridLayout>
+#include <QLayout>
 #include <QWidget>
 
 namespace Swift {
     class QtEmojisScroll : public QWidget {
         Q_OBJECT
     public:
-        QtEmojisScroll(QGridLayout* emojiLayout, QWidget *parent = 0);
+        QtEmojisScroll(QLayout* emojiLayout, QWidget *parent = 0);
     };
 }
diff --git a/Swift/QtUI/SConscript b/Swift/QtUI/SConscript
index 99759f4..8a39063 100644
--- a/Swift/QtUI/SConscript
+++ b/Swift/QtUI/SConscript
@@ -96,6 +96,7 @@ myenv.WriteVal("DefaultTheme.qrc", myenv.Value(generateQRCTheme(myenv.Dir("#/Swi
 
 sources = [
     "main.cpp",
+    "FlowLayout.cpp",
     "QtAboutWidget.cpp",
     "QtSpellCheckerWindow.cpp",
     "QtAvatarWidget.cpp",
-- 
cgit v0.10.2-6-g49f6