1
2############################################################################
3##
4## Copyright (C) 2013 Riverbank Computing Limited.
5## Copyright (C) 2020 The Qt Company Ltd.
6## Contact: http://www.qt.io/licensing/
7##
8## This file is part of the Qt for Python examples of the Qt Toolkit.
9##
10## $QT_BEGIN_LICENSE:BSD$
11## You may use this file under the terms of the BSD license as follows:
12##
13## "Redistribution and use in source and binary forms, with or without
14## modification, are permitted provided that the following conditions are
15## met:
16##   * Redistributions of source code must retain the above copyright
17##     notice, this list of conditions and the following disclaimer.
18##   * Redistributions in binary form must reproduce the above copyright
19##     notice, this list of conditions and the following disclaimer in
20##     the documentation and/or other materials provided with the
21##     distribution.
22##   * Neither the name of The Qt Company Ltd nor the names of its
23##     contributors may be used to endorse or promote products derived
24##     from this software without specific prior written permission.
25##
26##
27## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
28## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
29## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
30## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
31## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
32## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
33## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
34## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
35## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
36## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
37## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
38##
39## $QT_END_LICENSE$
40##
41#############################################################################
42
43"""PySide2 port of the widgets/layouts/flowlayout example from Qt v5.x"""
44
45import sys
46from PySide2.QtCore import Qt, QMargins, QPoint, QRect, QSize
47from PySide2.QtWidgets import (QApplication, QLayout, QPushButton,
48                               QSizePolicy, QWidget)
49
50
51class Window(QWidget):
52    def __init__(self):
53        super(Window, self).__init__()
54
55        flowLayout = FlowLayout(self)
56        flowLayout.addWidget(QPushButton("Short"))
57        flowLayout.addWidget(QPushButton("Longer"))
58        flowLayout.addWidget(QPushButton("Different text"))
59        flowLayout.addWidget(QPushButton("More text"))
60        flowLayout.addWidget(QPushButton("Even longer button text"))
61
62        self.setWindowTitle("Flow Layout")
63
64
65class FlowLayout(QLayout):
66    def __init__(self, parent=None):
67        super(FlowLayout, self).__init__(parent)
68
69        if parent is not None:
70            self.setContentsMargins(QMargins(0, 0, 0, 0))
71
72        self._item_list = []
73
74    def __del__(self):
75        item = self.takeAt(0)
76        while item:
77            item = self.takeAt(0)
78
79    def addItem(self, item):
80        self._item_list.append(item)
81
82    def count(self):
83        return len(self._item_list)
84
85    def itemAt(self, index):
86        if index >= 0 and index < len(self._item_list):
87            return self._item_list[index]
88
89        return None
90
91    def takeAt(self, index):
92        if index >= 0 and index < len(self._item_list):
93            return self._item_list.pop(index)
94
95        return None
96
97    def expandingDirections(self):
98        return Qt.Orientations(Qt.Orientation(0))
99
100    def hasHeightForWidth(self):
101        return True
102
103    def heightForWidth(self, width):
104        height = self._do_layout(QRect(0, 0, width, 0), True)
105        return height
106
107    def setGeometry(self, rect):
108        super(FlowLayout, self).setGeometry(rect)
109        self._do_layout(rect, False)
110
111    def sizeHint(self):
112        return self.minimumSize()
113
114    def minimumSize(self):
115        size = QSize()
116
117        for item in self._item_list:
118            size = size.expandedTo(item.minimumSize())
119
120        size += QSize(2 * self.contentsMargins().top(),
121                      2 * self.contentsMargins().top())
122        return size
123
124    def _do_layout(self, rect, test_only):
125        x = rect.x()
126        y = rect.y()
127        line_height = 0
128        spacing = self.spacing()
129
130        for item in self._item_list:
131            style = item.widget().style()
132            layout_spacing_x = style.layoutSpacing(QSizePolicy.PushButton,
133                                                   QSizePolicy.PushButton,
134                                                   Qt.Horizontal)
135            layout_spacing_y = style.layoutSpacing(QSizePolicy.PushButton,
136                                                   QSizePolicy.PushButton,
137                                                   Qt.Vertical)
138            space_x = spacing + layout_spacing_x
139            space_y = spacing + layout_spacing_y
140            next_x = x + item.sizeHint().width() + space_x
141            if next_x - space_x > rect.right() and line_height > 0:
142                x = rect.x()
143                y = y + line_height + space_y
144                next_x = x + item.sizeHint().width() + space_x
145                line_height = 0
146
147            if not test_only:
148                item.setGeometry(QRect(QPoint(x, y), item.sizeHint()))
149
150            x = next_x
151            line_height = max(line_height, item.sizeHint().height())
152
153        return y + line_height - rect.y()
154
155
156if __name__ == '__main__':
157    app = QApplication(sys.argv)
158    mainWin = Window()
159    mainWin.show()
160    sys.exit(app.exec_())
161