2007-09-01 14:34:22 +02:00
|
|
|
/*
|
|
|
|
* Copyright 2007 by Robert Knight <robertknight@gmail.com>
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or modify
|
2007-11-23 01:03:27 +01:00
|
|
|
* 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.
|
2007-09-01 14:34:22 +02:00
|
|
|
*
|
|
|
|
* 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 <QtDebug>
|
|
|
|
|
|
|
|
#include "layoutanimator.h"
|
|
|
|
|
|
|
|
using namespace Plasma;
|
|
|
|
|
|
|
|
class FlowLayout::Private
|
|
|
|
{
|
|
|
|
public:
|
2007-11-06 03:46:01 +01:00
|
|
|
Private() : columnWidth( -1 ) {}
|
2007-09-01 14:34:22 +02:00
|
|
|
QList<LayoutItem*> items;
|
2007-11-06 03:46:01 +01:00
|
|
|
qreal columnWidth;
|
2007-09-01 14:34:22 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
FlowLayout::FlowLayout(LayoutItem* parent)
|
|
|
|
: Layout(parent)
|
|
|
|
, d(new Private)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
FlowLayout::~FlowLayout()
|
|
|
|
{
|
|
|
|
delete d;
|
|
|
|
}
|
|
|
|
|
|
|
|
int FlowLayout::count() const
|
|
|
|
{
|
|
|
|
return d->items.count();
|
|
|
|
}
|
2007-11-19 23:04:30 +01:00
|
|
|
|
2007-09-01 14:34:22 +02:00
|
|
|
void FlowLayout::addItem(LayoutItem* item)
|
|
|
|
{
|
2007-12-04 00:53:56 +01:00
|
|
|
if (!item || d->items.contains(item)) {
|
2007-11-19 23:04:30 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2007-12-15 10:08:09 +01:00
|
|
|
item->setManagingLayout(this);
|
2007-09-01 14:34:22 +02:00
|
|
|
d->items << item;
|
|
|
|
|
2007-11-19 23:04:30 +01:00
|
|
|
if (animator()) {
|
2007-09-01 14:34:22 +02:00
|
|
|
animator()->setCurrentState(item,LayoutAnimator::InsertedState);
|
2007-11-19 23:04:30 +01:00
|
|
|
}
|
2007-09-01 14:34:22 +02:00
|
|
|
|
2007-12-15 10:08:09 +01:00
|
|
|
updateGeometry();
|
|
|
|
startAnimation();
|
2007-09-01 14:34:22 +02:00
|
|
|
}
|
|
|
|
void FlowLayout::removeItem(LayoutItem* item)
|
|
|
|
{
|
2007-12-04 00:53:56 +01:00
|
|
|
if (!item) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2007-11-19 23:04:30 +01:00
|
|
|
item->unsetManagingLayout(this);
|
2007-09-01 14:34:22 +02:00
|
|
|
d->items.removeAll(item);
|
|
|
|
|
2007-11-19 23:04:30 +01:00
|
|
|
if (animator()) {
|
2007-09-01 14:34:22 +02:00
|
|
|
animator()->setCurrentState(item,LayoutAnimator::RemovedState);
|
2007-11-19 23:04:30 +01:00
|
|
|
}
|
2007-12-15 10:08:09 +01:00
|
|
|
|
|
|
|
updateGeometry();
|
|
|
|
startAnimation();
|
2007-09-01 14:34:22 +02:00
|
|
|
}
|
|
|
|
int FlowLayout::indexOf(LayoutItem* item) const
|
|
|
|
{
|
2007-12-04 00:53:56 +01:00
|
|
|
if (!item) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2007-09-01 14:34:22 +02:00
|
|
|
return d->items.indexOf(item);
|
|
|
|
}
|
|
|
|
LayoutItem* FlowLayout::itemAt(int i) const
|
|
|
|
{
|
2007-12-04 00:53:56 +01:00
|
|
|
if (i >= d->items.count()) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2007-09-01 14:34:22 +02:00
|
|
|
return d->items[i];
|
|
|
|
}
|
2007-12-04 00:53:56 +01:00
|
|
|
|
2007-09-01 14:34:22 +02:00
|
|
|
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);
|
|
|
|
}
|
2007-12-04 00:53:56 +01:00
|
|
|
|
2007-09-01 14:34:22 +02:00
|
|
|
LayoutItem* FlowLayout::takeAt(int i)
|
|
|
|
{
|
2007-12-04 00:53:56 +01:00
|
|
|
if (i >= d->items.count()) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2007-09-01 14:34:22 +02:00
|
|
|
return d->items.takeAt(i);
|
2007-12-15 10:08:09 +01:00
|
|
|
// FIXME: Should updateGeometry() and startAnimation() be called?
|
2007-09-01 14:34:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
template <class T>
|
|
|
|
T qSum(const QList<T>& container)
|
|
|
|
{
|
|
|
|
T total = 0;
|
|
|
|
foreach( const T& item , container ) {
|
|
|
|
total += item;
|
|
|
|
}
|
|
|
|
return total;
|
|
|
|
}
|
|
|
|
|
2007-11-20 00:45:56 +01:00
|
|
|
void FlowLayout::relayout()
|
2007-09-01 14:34:22 +02:00
|
|
|
{
|
2007-11-20 00:45:56 +01:00
|
|
|
QRectF rect = geometry().adjusted(margin(LeftMargin), margin(TopMargin), -margin(RightMargin), -margin(BottomMargin));
|
2007-11-19 04:30:24 +01:00
|
|
|
|
2007-11-20 00:45:56 +01:00
|
|
|
qDebug() << "Flow layout geometry set to " << geometry();
|
2007-09-01 14:34:22 +02:00
|
|
|
|
|
|
|
// calculate average size of items
|
|
|
|
qreal totalWidth = 0;
|
|
|
|
qreal totalHeight = 0;
|
|
|
|
|
2007-12-04 00:53:56 +01:00
|
|
|
foreach(LayoutItem *item , d->items) {
|
2007-09-01 14:34:22 +02:00
|
|
|
totalWidth += item->sizeHint().width();
|
|
|
|
totalHeight += item->sizeHint().height();
|
|
|
|
}
|
|
|
|
|
|
|
|
// use the average item width as the column width.
|
|
|
|
// Also include the spacing either side of each item as part of the
|
|
|
|
// average width, this provides the spacing between the items and
|
|
|
|
// also allows some tolerance for small differences in item widths
|
2007-11-06 03:46:01 +01:00
|
|
|
qreal averageWidth;
|
2007-11-20 00:45:56 +01:00
|
|
|
if (d->columnWidth == -1) {
|
|
|
|
averageWidth = totalWidth / d->items.count() + 2*spacing();
|
|
|
|
} else {
|
2007-11-06 03:46:01 +01:00
|
|
|
averageWidth = d->columnWidth;
|
2007-11-20 00:45:56 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
const int columnCount = (int)(rect.width() / averageWidth);
|
2007-09-01 14:34:22 +02:00
|
|
|
|
|
|
|
int insertColumn = 0;
|
|
|
|
qreal rowPos = 0;
|
|
|
|
qreal rowHeight = 0;
|
|
|
|
|
|
|
|
// lay the items out in left-to-right , top-to-bottom order
|
2007-12-04 00:53:56 +01:00
|
|
|
foreach(LayoutItem *item , d->items) {
|
2007-09-01 14:34:22 +02:00
|
|
|
const QSizeF& itemSize = item->sizeHint();
|
|
|
|
|
|
|
|
int columnSpan = (int)ceil(itemSize.width() / averageWidth);
|
|
|
|
|
|
|
|
if ( insertColumn + columnSpan > columnCount ) {
|
|
|
|
// start a new row
|
|
|
|
insertColumn = 0;
|
|
|
|
rowPos += rowHeight + spacing();
|
|
|
|
}
|
|
|
|
|
|
|
|
// qDebug() << "Inserting item at column" << insertColumn
|
|
|
|
// << "spanning" << columnSpan << "columns"
|
|
|
|
// << "with offset" << offset;
|
|
|
|
|
|
|
|
|
|
|
|
// try to expand the item to fill its allocated number of columns
|
|
|
|
qreal itemWidth = itemSize.width();
|
|
|
|
const qreal idealWidth = columnSpan * averageWidth - spacing();
|
|
|
|
if ( itemWidth < idealWidth &&
|
|
|
|
idealWidth < item->maximumSize().width() ) {
|
|
|
|
itemWidth = idealWidth;
|
|
|
|
}
|
|
|
|
|
|
|
|
// calculate offset to horizontally center item
|
|
|
|
qreal offset = (columnSpan * averageWidth) - itemWidth;
|
|
|
|
if ( insertColumn == 0 )
|
|
|
|
offset -= spacing();
|
|
|
|
offset /= 2;
|
|
|
|
|
|
|
|
// try to restrict the item width to the available geometry's
|
|
|
|
// width
|
2007-11-20 00:45:56 +01:00
|
|
|
if ( itemWidth > rect.width() ) {
|
|
|
|
itemWidth = qMax(rect.width(),item->minimumSize().width());
|
2007-09-01 14:34:22 +02:00
|
|
|
offset = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// position the item
|
2007-11-20 00:45:56 +01:00
|
|
|
const QRectF newGeometry(rect.left() + insertColumn * averageWidth + offset,
|
|
|
|
rect.top() + rowPos,
|
|
|
|
itemWidth,
|
|
|
|
itemSize.height());
|
2007-09-01 14:34:22 +02:00
|
|
|
|
|
|
|
rowHeight = qMax(rowHeight,itemSize.height());
|
|
|
|
insertColumn += columnSpan;
|
|
|
|
|
|
|
|
if ( animator() )
|
|
|
|
animator()->setGeometry( item , newGeometry );
|
|
|
|
else
|
|
|
|
item->setGeometry( newGeometry );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Qt::Orientations FlowLayout::expandingDirections() const
|
|
|
|
{
|
|
|
|
return Qt::Vertical | Qt::Horizontal;
|
|
|
|
}
|
|
|
|
|
2007-11-06 03:46:01 +01:00
|
|
|
qreal FlowLayout::columnWidth() const
|
|
|
|
{
|
|
|
|
return d->columnWidth;
|
|
|
|
}
|
|
|
|
|
|
|
|
void FlowLayout::setColumnWidth( const qreal width )
|
|
|
|
{
|
|
|
|
d->columnWidth = width;
|
|
|
|
}
|