1 /*
2  *  packedlayout.cpp  -  layout to pack items into rows
3  *  Program:  kalarm
4  *  SPDX-FileCopyrightText: 2007-2021 David Jarvie <djarvie@kde.org>
5  *
6  *  SPDX-License-Identifier: GPL-2.0-or-later
7  */
8 
9 #include "packedlayout.h"
10 
11 #include "kalarm_debug.h"
12 
13 #include <QWidget>
14 
PackedLayout(QWidget * parent,Qt::Alignment alignment)15 PackedLayout::PackedLayout(QWidget* parent, Qt::Alignment alignment)
16     : QLayout(parent)
17     , mAlignment(alignment)
18 {
19 }
20 
PackedLayout(Qt::Alignment alignment)21 PackedLayout::PackedLayout(Qt::Alignment alignment)
22     : QLayout()
23     , mAlignment(alignment)
24 {
25 }
26 
~PackedLayout()27 PackedLayout::~PackedLayout()
28 {
29     while (!mItems.isEmpty())
30         delete mItems.takeFirst();
31 }
32 
setHorizontalSpacing(int spacing)33 void PackedLayout::setHorizontalSpacing(int spacing)
34 {
35     mHorizontalSpacing = spacing;
36 }
37 
setVerticalSpacing(int spacing)38 void PackedLayout::setVerticalSpacing(int spacing)
39 {
40     mVerticalSpacing = spacing;
41 }
42 
horizontalSpacing() const43 int PackedLayout::horizontalSpacing() const
44 {
45     return (mHorizontalSpacing >= 0) ? mHorizontalSpacing : spacing();
46 }
47 
verticalSpacing() const48 int PackedLayout::verticalSpacing() const
49 {
50     return (mVerticalSpacing >= 0) ? mVerticalSpacing : spacing();
51 }
52 
53 /******************************************************************************
54 * Inserts a button into the group.
55 */
addItem(QLayoutItem * item)56 void PackedLayout::addItem(QLayoutItem* item)
57 {
58     mWidthCached = 0;
59     mItems.append(item);
60 }
61 
itemAt(int index) const62 QLayoutItem* PackedLayout::itemAt(int index) const
63 {
64     return (index >= 0 && index < mItems.count()) ? mItems[index] : nullptr;
65 }
66 
takeAt(int index)67 QLayoutItem* PackedLayout::takeAt(int index)
68 {
69     if (index < 0  ||  index >= mItems.count())
70         return nullptr;
71     return mItems.takeAt(index);
72 }
73 
heightForWidth(int w) const74 int PackedLayout::heightForWidth(int w) const
75 {
76     if (w != mWidthCached)
77     {
78         mHeightCached = arrange(QRect(0, 0, w, 0), false);
79         mWidthCached = w;
80     }
81     return mHeightCached;
82 }
83 
setGeometry(const QRect & rect)84 void PackedLayout::setGeometry(const QRect& rect)
85 {
86     QLayout::setGeometry(rect);
87     arrange(rect, true);
88 }
89 
90 // Return the maximum size of any widget.
minimumSize() const91 QSize PackedLayout::minimumSize() const
92 {
93     QSize size;
94     for (int i = 0, end = mItems.count();  i < end;  ++i)
95         size = size.expandedTo(mItems[i]->minimumSize());
96     int left, top, right, bottom;
97     getContentsMargins(&left, &top, &right, &bottom);
98     return {size.width() + left + right, size.height() + top + bottom};
99 }
100 
101 // Arranges widgets and returns height required.
arrange(const QRect & rect,bool set) const102 int PackedLayout::arrange(const QRect& rect, bool set) const
103 {
104     int x = rect.x();
105     int y = rect.y();
106     int yrow = 0;
107     QVector<QRect> posn;
108     QVector<QLayoutItem*> items;
109     for (QLayoutItem* item : std::as_const(mItems))
110     {
111         if (item->isEmpty())
112             continue;
113         const QSize size = item->sizeHint();
114         int right = x + size.width();
115         if (right > rect.right()  &&  x > rect.x())
116         {
117             x = rect.x();
118             y = y + yrow + verticalSpacing();
119             right = x + size.width();
120             yrow = size.height();
121         }
122         else
123             yrow = qMax(yrow, size.height());
124         items.append(item);
125         posn.append(QRect(QPoint(x, y), size));
126         x = right + horizontalSpacing();
127     }
128     if (set)
129     {
130         const int count = items.count();
131         if (mAlignment == Qt::AlignLeft)
132         {
133             // Left aligned: no position adjustment needed
134             // Set the positions of all the layout items
135             for (int i = 0;  i < count;  ++i)
136                 items[i]->setGeometry(alignRect(rect, posn[i]));
137         }
138         else
139         {
140             // Set the positions of all the layout items
141             for (int i = 0;  i < count; )
142             {
143                 // Adjust item positions a row at a time
144                 y = posn[i].y();
145                 int last;   // after last item in this row
146                 for (last = i + 1;  last < count && posn[last].y() == y;  ++last) ;
147                 const int n = last - i;   // number of items in this row
148                 int free = rect.right() - posn[last - 1].right();
149                 switch (mAlignment)
150                 {
151                     case Qt::AlignJustify:
152                         if (n == 1)
153                         {
154                             items[i]->setGeometry(alignRect(rect, posn[i]));
155                             ++i;
156                         }
157                         else if (n > 1)
158                         {
159                             for (int j = 0;  i < last;  ++j, ++i)
160                                 items[i]->setGeometry(alignRect(rect, QRect(QPoint(posn[i].x() + (free * j)/(n - 1), y), posn[i].size())));
161                         }
162                         break;
163                     case Qt::AlignHCenter:
164                         free /= 2;
165                         // fall through to AlignRight
166                         Q_FALLTHROUGH();
167                     case Qt::AlignRight:
168                         for ( ;  i < last;  ++i)
169                             items[i]->setGeometry(alignRect(rect, QRect(QPoint(posn[i].x() + free, y), posn[i].size())));
170                         break;
171                     default:
172                         break;
173                 }
174             }
175         }
176     }
177     return y + yrow - rect.y();
178 }
179 
180 /******************************************************************************
181 * Adjust an item's geometry if using right-to-left alignment.
182 */
alignRect(const QRect & rect,const QRect & itemRect) const183 QRect PackedLayout::alignRect(const QRect& rect, const QRect& itemRect) const
184 {
185     QRect result(itemRect);
186     if (parentWidget()->layoutDirection() == Qt::RightToLeft)
187         result.moveRight(rect.right() - (itemRect.left() - rect.left()));
188     return result;
189 }
190 
191 // vim: et sw=4:
192