1#!/usr/bin/env python
2
3
4#############################################################################
5##
6## Copyright (C) 2013 Riverbank Computing Limited.
7## Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
8## All rights reserved.
9##
10## This file is part of the examples of PyQt.
11##
12## $QT_BEGIN_LICENSE:BSD$
13## You may use this file under the terms of the BSD license as follows:
14##
15## "Redistribution and use in source and binary forms, with or without
16## modification, are permitted provided that the following conditions are
17## met:
18##   * Redistributions of source code must retain the above copyright
19##     notice, this list of conditions and the following disclaimer.
20##   * Redistributions in binary form must reproduce the above copyright
21##     notice, this list of conditions and the following disclaimer in
22##     the documentation and/or other materials provided with the
23##     distribution.
24##   * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
25##     the names of its contributors may be used to endorse or promote
26##     products derived from this software without specific prior written
27##     permission.
28##
29## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
30## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
31## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
32## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
33## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
34## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
35## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
36## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
37## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
38## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
39## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
40## $QT_END_LICENSE$
41##
42#############################################################################
43
44
45from PyQt5.QtCore import QRect, QSize, Qt
46from PyQt5.QtWidgets import (QApplication, QFrame, QLabel, QLayout,
47        QTextBrowser, QWidget, QWidgetItem)
48
49
50class ItemWrapper(object):
51    def __init__(self, i, p):
52        self.item = i
53        self.position = p
54
55
56class BorderLayout(QLayout):
57    West, North, South, East, Center = range(5)
58    MinimumSize, SizeHint = range(2)
59
60    def __init__(self, parent=None, margin=None, spacing=-1):
61        super(BorderLayout, self).__init__(parent)
62
63        if margin is not None:
64            self.setContentsMargins(margin, margin, margin, margin)
65
66        self.setSpacing(spacing)
67        self.list = []
68
69    def __del__(self):
70        l = self.takeAt(0)
71        while l is not None:
72            l = self.takeAt(0)
73
74    def addItem(self, item):
75        self.add(item, self.West)
76
77    def addWidget(self, widget, position):
78        self.add(QWidgetItem(widget), position)
79
80    def expandingDirections(self):
81        return Qt.Horizontal | Qt.Vertical
82
83    def hasHeightForWidth(self):
84        return False
85
86    def count(self):
87        return len(self.list)
88
89    def itemAt(self, index):
90        if index < len(self.list):
91            return self.list[index].item
92
93        return None
94
95    def minimumSize(self):
96        return self.calculateSize(self.MinimumSize)
97
98    def setGeometry(self, rect):
99        center = None
100        eastWidth = 0
101        westWidth = 0
102        northHeight = 0
103        southHeight = 0
104        centerHeight = 0
105
106        super(BorderLayout, self).setGeometry(rect)
107
108        for wrapper in self.list:
109            item = wrapper.item
110            position = wrapper.position
111
112            if position == self.North:
113                item.setGeometry(QRect(rect.x(), northHeight,
114                        rect.width(), item.sizeHint().height()))
115
116                northHeight += item.geometry().height() + self.spacing()
117
118            elif position == self.South:
119                item.setGeometry(QRect(item.geometry().x(),
120                        item.geometry().y(), rect.width(),
121                        item.sizeHint().height()))
122
123                southHeight += item.geometry().height() + self.spacing()
124
125                item.setGeometry(QRect(rect.x(),
126                        rect.y() + rect.height() - southHeight + self.spacing(),
127                        item.geometry().width(), item.geometry().height()))
128
129            elif position == self.Center:
130                center = wrapper
131
132        centerHeight = rect.height() - northHeight - southHeight
133
134        for wrapper in self.list:
135            item = wrapper.item
136            position = wrapper.position
137
138            if position == self.West:
139                item.setGeometry(QRect(rect.x() + westWidth,
140                        northHeight, item.sizeHint().width(), centerHeight))
141
142                westWidth += item.geometry().width() + self.spacing()
143
144            elif position == self.East:
145                item.setGeometry(QRect(item.geometry().x(),
146                        item.geometry().y(), item.sizeHint().width(),
147                        centerHeight))
148
149                eastWidth += item.geometry().width() + self.spacing()
150
151                item.setGeometry(QRect(rect.x() + rect.width() - eastWidth + self.spacing(),
152                        northHeight, item.geometry().width(),
153                        item.geometry().height()))
154
155        if center:
156            center.item.setGeometry(QRect(westWidth, northHeight,
157                    rect.width() - eastWidth - westWidth, centerHeight))
158
159    def sizeHint(self):
160        return self.calculateSize(self.SizeHint)
161
162    def takeAt(self, index):
163        if index >= 0 and index < len(self.list):
164            layoutStruct = self.list.pop(index)
165            return layoutStruct.item
166
167        return None
168
169    def add(self, item, position):
170        self.list.append(ItemWrapper(item, position))
171
172    def calculateSize(self, sizeType):
173        totalSize = QSize()
174
175        for wrapper in self.list:
176            position = wrapper.position
177            itemSize = QSize()
178
179            if sizeType == self.MinimumSize:
180                itemSize = wrapper.item.minimumSize()
181            else: # sizeType == self.SizeHint
182                itemSize = wrapper.item.sizeHint()
183
184            if position in (self.North, self.South, self.Center):
185                totalSize.setHeight(totalSize.height() + itemSize.height())
186
187            if position in (self.West, self.East, self.Center):
188                totalSize.setWidth(totalSize.width() + itemSize.width())
189
190        return totalSize
191
192
193class Window(QWidget):
194    def __init__(self):
195        super(Window, self).__init__()
196
197        centralWidget = QTextBrowser()
198        centralWidget.setPlainText("Central widget")
199
200        layout = BorderLayout()
201        layout.addWidget(centralWidget, BorderLayout.Center)
202
203        # Because BorderLayout doesn't call its super-class addWidget() it
204        # doesn't take ownership of the widgets until setLayout() is called.
205        # Therefore we keep a local reference to each label to prevent it being
206        # garbage collected too soon.
207        label_n = self.createLabel("North")
208        layout.addWidget(label_n, BorderLayout.North)
209
210        label_w = self.createLabel("West")
211        layout.addWidget(label_w, BorderLayout.West)
212
213        label_e1 = self.createLabel("East 1")
214        layout.addWidget(label_e1, BorderLayout.East)
215
216        label_e2 = self.createLabel("East 2")
217        layout.addWidget(label_e2, BorderLayout.East)
218
219        label_s = self.createLabel("South")
220        layout.addWidget(label_s, BorderLayout.South)
221
222        self.setLayout(layout)
223
224        self.setWindowTitle("Border Layout")
225
226    def createLabel(self, text):
227        label = QLabel(text)
228        label.setFrameStyle(QFrame.Box | QFrame.Raised)
229
230        return label
231
232
233if __name__ == '__main__':
234
235    import sys
236
237    app = QApplication(sys.argv)
238    window = Window()
239    window.show()
240    sys.exit(app.exec_())
241