plasma-framework/layouts/flowlayout.cpp
Bill Egert 74e2ce31ff Make some better decisions.
svn path=/trunk/KDE/kdebase/workspace/libs/plasma/; revision=777237
2008-02-20 03:00:26 +00:00

278 lines
7.3 KiB
C++

/*
* Copyright 2007 by Robert Knight <robertknight@gmail.com>
* Copyright 2008 by William Egert <begert@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License
* as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details
*
* You should have received a copy of the GNU Library General Public
* License along with this program; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "flowlayout.h"
#include <limits.h>
#include <math.h>
#include <QtCore/QList>
#include <QtCore/QRectF>
#include <QtCore/QTimeLine>
#include <KDebug>
#include "layoutanimator.h"
using namespace Plasma;
class FlowLayout::Private
{
public:
Private() : columnWidth( -1 ) {}
QList<LayoutItem*> items;
qreal columnWidth;
};
FlowLayout::FlowLayout(LayoutItem* parent)
: Layout(parent)
, d(new Private)
{
}
FlowLayout::~FlowLayout()
{
releaseManagedItems();
delete d;
}
int FlowLayout::count() const
{
return d->items.count();
}
void FlowLayout::addItem(LayoutItem* item)
{
if (!item || d->items.contains(item)) {
return;
}
item->setManagingLayout(this);
d->items << item;
if (animator()) {
animator()->setCurrentState(item,LayoutAnimator::InsertedState);
}
updateGeometry();
}
void FlowLayout::removeItem(LayoutItem* item)
{
if (!item) {
return;
}
item->unsetManagingLayout(this);
d->items.removeAll(item);
if (animator()) {
animator()->setCurrentState(item,LayoutAnimator::RemovedState);
}
updateGeometry();
}
int FlowLayout::indexOf(LayoutItem* item) const
{
if (!item) {
return -1;
}
return d->items.indexOf(item);
}
LayoutItem* FlowLayout::itemAt(int i) const
{
if (i >= d->items.count()) {
return 0;
}
return d->items[i];
}
QSizeF FlowLayout::sizeHint() const
{
// TODO A proper algorithm here
//
// Idea: Return a size hint based on the golden ratio to
// make it aesthetically good
// eg. Longer side is 1.61x the length of the shorter side
//
// testing
return QSizeF(500,500);
}
LayoutItem* FlowLayout::takeAt(int i)
{
if (i >= d->items.count()) {
return 0;
}
return d->items.takeAt(i);
// FIXME: Should updateGeometry() be called?
}
template <class T>
T qSum(const QList<T>& container)
{
T total = 0;
foreach( const T& item , container ) {
total += item;
}
return total;
}
void FlowLayout::relayout()
{
const QRectF rect = adjustToMargins(geometry());
const qreal space = spacing();
const qreal rectWidth = rect.width();
const qreal rectHeight = rect.height();
const int count = d->items.count();
//kDebug() << "Flow layout geometry set to " << geometry();
// calculate average size of items
qreal colWidth = 0;
qreal rowHeight = 0;
qreal maxItemWidth = 0;
qreal minItemWidth = 0;
//qreal maxItemHeight = 0;
qreal minItemHeight = 0;
int colCnt = 0;
int rowCnt = 0;
foreach(LayoutItem *item , d->items) {
maxItemWidth = (maxItemWidth < item->maximumSize().width()) ?
item->maximumSize().width() : maxItemWidth;
minItemWidth = (minItemWidth < item->minimumSize().width()) ?
item->minimumSize().width() : minItemWidth;
//maxItemHeight = (maxItemHeight < item->maximumSize().height()) ?
// item->maximumSize().height() : maxItemHeight;
minItemHeight = (minItemHeight < item->minimumSize().height()) ?
item->minimumSize().height() : minItemHeight;
}
const int rowMax = ((minItemHeight != 0) && (minItemHeight != rectHeight)) ?
(int)(rectHeight / (minItemHeight + space)) : 1;
if( maxItemWidth == 0 && minItemWidth != 0 ) {
kDebug() << "******POSSIBLE DIVIDE BY ZERO: maxItemWidth = minItemWidth ********";
maxItemWidth = minItemWidth + space;
} else if( maxItemWidth == 0 && minItemWidth == 0 ) {
kDebug() << "******POSSIBLE DIVIDE BY ZERO: maxItemWidth = rectWidth ********";
maxItemWidth = rectWidth + space;
}
// try to use the maxwidth if there is room
// need usedSpace so we don't try to use the leftover
// area of our rect to make a item location.
maxItemWidth += space;
qreal usedSpace = floor( (rectWidth / maxItemWidth ) ) * maxItemWidth;
for(int i = 1; (i <= rowMax) && (colWidth == 0); i++) {
if( i * (usedSpace / maxItemWidth ) >= count) {
colWidth = maxItemWidth;
rowHeight = (rectHeight + space) / i;
rowCnt = i;
colCnt = (int)(usedSpace / colWidth);
}
}
//crazy algorithm to make items fit in available space
if( colWidth == 0) {
// These gave me the most trouble and should
// be taken into account if you try and change this:
// - maxRow = 3 with 9 items and 3 columns.
// - maxRow = 5 with 8 items and 3 colums.
// - maxRow = 1 with odd number columns.
const qreal tmp = (qreal)(count + (count % 2)) / rowMax;
if( (tmp - floor(tmp)) > 0.5) {
colCnt = (int)ceil(tmp) + 1;
} else {
colCnt = (int)ceil(tmp);
}
rowCnt = (int)ceil((qreal)count / colCnt);
if( (rowCnt == 1) && (colCnt&2) ) {
colCnt--;
}
colWidth = rectWidth / colCnt;
rowHeight = (rectHeight + space) / rowCnt;
}
if( minItemHeight > (rowHeight - space) ) {
rowHeight = minItemHeight + space;
}
// kDebug() << "colWidth: " << colWidth << "rowHeight: " << rowHeight
// << "rowCnt: " << rowCnt << "rowMax: " << rowMax << "colCnt: " << colCnt;
// lay the items out in left-to-right , top-to-bottom order
int insertColumn = 0;
qreal rowPos = 0;
foreach(LayoutItem *item , d->items) {
if(insertColumn >= colCnt) {
insertColumn = 0;
rowPos += rowHeight;
}
// position the item
const QRectF newGeometry(rect.left() + (insertColumn * colWidth),
rect.top() + rowPos,
colWidth - space,
rowHeight - space);
//kDebug() << "newGeometry: " << newGeometry;
insertColumn++;
if ( animator() ){
animator()->setGeometry( item , newGeometry );
} else {
item->setGeometry( newGeometry );
}
}
startAnimation();
}
void FlowLayout::releaseManagedItems()
{
foreach (LayoutItem *item, d->items) {
item->unsetManagingLayout(this);
}
}
Qt::Orientations FlowLayout::expandingDirections() const
{
return Qt::Vertical | Qt::Horizontal;
}
qreal FlowLayout::columnWidth() const
{
return d->columnWidth;
}
void FlowLayout::setColumnWidth( const qreal width )
{
d->columnWidth = width;
}