Make a branch to make krunner Good Enough For Aaron™.
[kdebase/uwolfer.git] / workspace / libs / plasma / layouts / flowlayout.cpp
blob344883a7d672eb2e1e1588d684ad7e1e11efb966
1 /*
2 * Copyright 2007 by Robert Knight <robertknight@gmail.com>
3 * Copyright 2008 by William Egert <begert@gmail.com>
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU Library General Public License
7 * as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details
15 * You should have received a copy of the GNU Library General Public
16 * License along with this program; if not, write to the
17 * Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21 #include "flowlayout.h"
23 #include <limits.h>
24 #include <math.h>
26 #include <QtCore/QList>
27 #include <QtCore/QRectF>
28 #include <QtCore/QTimeLine>
30 #include <KDebug>
32 // #include "layoutanimator.h"
34 using namespace Plasma;
36 class FlowLayout::Private
38 public:
39 Private() : columnWidth( -1 ), spacing(6.0) {}
40 QList<QGraphicsLayoutItem*> items;
41 qreal columnWidth;
42 qreal spacing;
45 FlowLayout::FlowLayout(QGraphicsLayoutItem* parent)
46 : QGraphicsLayout(parent)
47 , d(new Private)
49 setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding,QSizePolicy::DefaultType);
52 FlowLayout::~FlowLayout()
54 delete d;
57 int FlowLayout::count() const
59 return d->items.count();
62 void FlowLayout::addItem(QGraphicsLayoutItem* item)
64 if (!item || d->items.contains(item)) {
65 return;
68 d->items << item;
69 //FIXME: Port
70 // if (animator()) {
71 // animator()->setCurrentState(item,LayoutAnimator::InsertedState);
72 // }
74 updateGeometry();
76 void FlowLayout::removeItem(QGraphicsLayoutItem* item)
78 if (!item) {
79 return;
82 d->items.removeAll(item);
83 //FIXME: Port
84 // if (animator()) {
85 // animator()->setCurrentState(item,LayoutAnimator::RemovedState);
86 // }
88 updateGeometry();
90 int FlowLayout::indexOf(QGraphicsLayoutItem* item) const
92 if (!item) {
93 return -1;
96 return d->items.indexOf(item);
98 QGraphicsLayoutItem* FlowLayout::itemAt(int i) const
100 if (i >= d->items.count()) {
101 return 0;
104 return d->items[i];
107 QSizeF FlowLayout::sizeHint(Qt::SizeHint which, const QSizeF &constraint) const
109 // TODO A proper algorithm here
111 // Idea: Return a size hint based on the golden ratio to
112 // make it aesthetically good
113 // eg. Longer side is 1.61x the length of the shorter side
116 // testing
117 return QSizeF(500,500);
120 QGraphicsLayoutItem* FlowLayout::takeAt(int i)
122 if (i >= d->items.count()) {
123 return 0;
126 return d->items.takeAt(i);
127 // FIXME: Should updateGeometry() be called?
130 void FlowLayout::removeAt(int i)
132 if (i >= d->items.count()) {
133 return;
136 d->items.removeAt(i);
139 QRectF FlowLayout::geometry() const
141 if (parentLayoutItem()) {
142 return parentLayoutItem()->geometry();
145 return QRectF(QPointF(0, 0), maximumSize());
148 void FlowLayout::setGeometry(const QRectF &geom)
150 if (!geom.isValid() || geom == geometry()) {
151 return;
154 // QRectF newGeom = geom;
156 // if (d->parent && !dynamic_cast<QGraphicsLayout*>(d->parent)) {
157 // newGeom = d->parent->adjustToMargins(newGeom);
158 // //kDebug() << "parent rect is" << d->parent->topLeft() << d->parent->size()
159 // // << "and we are" << geometry() << "but aiming for"
160 // // << newGeom << "from" << geom;
161 // }
163 // d->pos = newGeom.topLeft();
164 setPreferredSize(geom.size());
165 // TODO: respect minimum and maximum sizes: is it possible?
166 //setSize(newGeom.size().expandedTo(minimumSize()).boundedTo(maximumSize()));
168 //kDebug() << "geometry is now" << geometry();
169 invalidate();
172 qreal FlowLayout::spacing() const
174 return d->spacing;
177 void FlowLayout::setSpacing(qreal s)
179 d->spacing = s;
182 template <class T>
183 T qSum(const QList<T>& container)
185 T total = 0;
186 foreach( const T& item , container ) {
187 total += item;
189 return total;
192 void FlowLayout::relayout()
194 // const QRectF rect = adjustToMargins(geometry());
195 const QRectF rect = geometry();
197 const qreal space = spacing();
198 const qreal rectWidth = rect.width();
199 const qreal rectHeight = rect.height();
200 const int count = d->items.count();
201 //kDebug() << "Flow layout geometry set to " << geometry();
203 // calculate average size of items
204 qreal colWidth = 0;
205 qreal rowHeight = 0;
206 qreal maxItemWidth = 0;
207 qreal minItemWidth = 0;
208 //qreal maxItemHeight = 0;
209 qreal minItemHeight = 0;
210 int colCnt = 0;
211 int rowCnt = 0;
213 foreach(QGraphicsLayoutItem *item , d->items) {
214 maxItemWidth = (maxItemWidth < item->maximumSize().width()) ?
215 item->maximumSize().width() : maxItemWidth;
216 minItemWidth = (minItemWidth < item->minimumSize().width()) ?
217 item->minimumSize().width() : minItemWidth;
218 //maxItemHeight = (maxItemHeight < item->maximumSize().height()) ?
219 // item->maximumSize().height() : maxItemHeight;
220 minItemHeight = (minItemHeight < item->minimumSize().height()) ?
221 item->minimumSize().height() : minItemHeight;
224 const int rowMax = ((minItemHeight != 0) && (minItemHeight != rectHeight)) ?
225 (int)(rectHeight / (minItemHeight + space)) : 1;
227 if( maxItemWidth == 0 && minItemWidth != 0 ) {
228 kDebug() << "******POSSIBLE DIVIDE BY ZERO: maxItemWidth = minItemWidth ********";
229 maxItemWidth = minItemWidth + space;
230 } else if( maxItemWidth == 0 && minItemWidth == 0 ) {
231 kDebug() << "******POSSIBLE DIVIDE BY ZERO: maxItemWidth = rectWidth ********";
232 maxItemWidth = rectWidth + space;
235 // try to use the maxwidth if there is room
236 // need usedSpace so we don't try to use the leftover
237 // area of our rect to make a item location.
238 maxItemWidth += space;
239 qreal usedSpace = floor( (rectWidth / maxItemWidth ) ) * maxItemWidth;
240 for(int i = 1; (i <= rowMax) && (colWidth == 0); i++) {
241 if( i * (usedSpace / maxItemWidth ) >= count) {
242 colWidth = maxItemWidth;
243 rowHeight = (rectHeight + space) / i;
244 rowCnt = i;
245 colCnt = (int)(usedSpace / colWidth);
249 //crazy algorithm to make items fit in available space
250 if( colWidth == 0) {
251 // These gave me the most trouble and should
252 // be taken into account if you try and change this:
253 // - maxRow = 3 with 9 items and 3 columns.
254 // - maxRow = 5 with 8 items and 3 colums.
255 // - maxRow = 1 with odd number columns.
257 const qreal tmp = (qreal)(count + (count % 2)) / rowMax;
258 if( (tmp - floor(tmp)) > 0.5) {
259 colCnt = (int)ceil(tmp) + 1;
260 } else {
261 colCnt = (int)ceil(tmp);
263 rowCnt = (int)ceil((qreal)count / colCnt);
264 if( (rowCnt == 1) && (colCnt&2) ) {
265 colCnt--;
267 colWidth = rectWidth / colCnt;
268 rowHeight = (rectHeight + space) / rowCnt;
272 if( minItemHeight > (rowHeight - space) ) {
273 rowHeight = minItemHeight + space;
276 // kDebug() << "colWidth: " << colWidth << "rowHeight: " << rowHeight
277 // << "rowCnt: " << rowCnt << "rowMax: " << rowMax << "colCnt: " << colCnt;
280 // lay the items out in left-to-right , top-to-bottom order
281 int insertColumn = 0;
282 qreal rowPos = 0;
283 foreach(QGraphicsLayoutItem *item , d->items) {
285 if(insertColumn >= colCnt) {
286 insertColumn = 0;
287 rowPos += rowHeight;
290 // position the item
291 const QRectF newGeometry(rect.left() + (insertColumn * colWidth),
292 rect.top() + rowPos,
293 colWidth - space,
294 rowHeight - space);
296 //kDebug() << "newGeometry: " << newGeometry;
297 insertColumn++;
299 //FIXME: Port
300 /* if ( animator() ){
301 animator()->setGeometry( item , newGeometry );
302 } else {*/
303 item->setGeometry( newGeometry );
304 // }
306 //FIXME: Port
307 // startAnimation();
310 qreal FlowLayout::columnWidth() const
312 return d->columnWidth;
315 void FlowLayout::setColumnWidth( const qreal width )
317 d->columnWidth = width;