This breaks the existing Plasma applet API, see the contentSize()

comments below.

* New Flow Layout.  This provides simple icon view-esque layout of items.
  Useful for icons for documents , applications or other tasks on the desktop
  for example.  Supports non-equally sized items.

  Works well when used with the LayoutAnimator class to animate insertions
  and removals.

* Re-wrote BoxLayout and removed old HBoxLayout,VBoxLayout classes which had
  a lot of code duplication.  BoxLayout class now takes a direction argument
  in the constructor, ala. QBoxLayout.  New BoxLayout class actually takes
  minimumSize() , maximumSize() of items into account.  The Qt layout code
  for box and grid layouts is surprisingly sophisticated, so the results
  from BoxLayout probably will not be as good in certain situations but
  it should do for the panel.  New BoxLayout also has support for LayoutAnimator

* Fix Plasma::HBoxLayout and Plasma::VBoxLayout to use margin() 
  rather than spacing() for the distance from the top and left
  margins respectively.

* Fix Plasma::Applet::contentSize() to return the actual content size
  rather than a size hint.  Added a new method contentSizeHint() which
  applets use to provide a hint about suitable content size.  

  Existing implementations of contentSize() in applets need to be renamed
  to contentSizeHint().  The arguments and return type are the same as before.

* Install the LayoutAnimator header so that applets can use it

svn path=/trunk/KDE/kdebase/workspace/libs/plasma/; revision=707275
This commit is contained in:
Robert Knight 2007-09-01 12:34:22 +00:00
parent 04375c4b09
commit a29ae57a7d
13 changed files with 789 additions and 405 deletions

View File

@ -33,8 +33,7 @@ set(plasma_LIB_SRCS
karambamanager.cpp karambamanager.cpp
widgets/boxlayout.cpp widgets/boxlayout.cpp
widgets/checkbox.cpp widgets/checkbox.cpp
widgets/hboxlayout.cpp widgets/flowlayout.cpp
# widgets/flowlayout.cpp
widgets/flash.cpp widgets/flash.cpp
widgets/icon.cpp widgets/icon.cpp
widgets/label.cpp widgets/label.cpp
@ -46,7 +45,6 @@ set(plasma_LIB_SRCS
widgets/radiobutton.cpp widgets/radiobutton.cpp
# widgets/rectangle.cpp # widgets/rectangle.cpp
widgets/widget.cpp widgets/widget.cpp
widgets/vboxlayout.cpp
widgets/signalplotter.cpp widgets/signalplotter.cpp
) )
@ -105,17 +103,18 @@ install(FILES
install(FILES install(FILES
widgets/boxlayout.h widgets/boxlayout.h
widgets/hboxlayout.h widgets/hboxlayout.h
widgets/vboxlayout.h
widgets/flash.h widgets/flash.h
widgets/icon.h widgets/icon.h
widgets/label.h widgets/label.h
widgets/layout.h widgets/layout.h
widgets/layoutanimator.h
widgets/layoutitem.h widgets/layoutitem.h
widgets/lineedit.h widgets/lineedit.h
widgets/pushbutton.h widgets/pushbutton.h
widgets/checkbox.h widgets/checkbox.h
widgets/radiobutton.h widgets/radiobutton.h
# widgets/rectangle.h # widgets/rectangle.h
widgets/vboxlayout.h
widgets/widget.h widgets/widget.h
widgets/signalplotter.h widgets/signalplotter.h
DESTINATION ${INCLUDE_INSTALL_DIR}/plasma/widgets) DESTINATION ${INCLUDE_INSTALL_DIR}/plasma/widgets)

View File

@ -54,7 +54,7 @@
#include "plasma/widgets/widget.h" #include "plasma/widgets/widget.h"
#include "plasma/widgets/lineedit.h" #include "plasma/widgets/lineedit.h"
#include "plasma/widgets/pushbutton.h" #include "plasma/widgets/pushbutton.h"
#include "plasma/widgets/vboxlayout.h" #include "plasma/widgets/boxlayout.h"
//#define DYNAMIC_SHADOWS //#define DYNAMIC_SHADOWS
namespace Plasma namespace Plasma
@ -568,7 +568,7 @@ void Applet::setFailedToLaunch(bool failed, const QString& reason)
if (failed) { if (failed) {
setDrawStandardBackground(true); setDrawStandardBackground(true);
Layout* failureLayout = new VBoxLayout(this); Layout* failureLayout = new BoxLayout(BoxLayout::TopToBottom,this);
d->failureText = new LineEdit(this, scene()); d->failureText = new LineEdit(this, scene());
d->failureText->setFlags(0); d->failureText->setFlags(0);
d->failureText->setHtml(visibleFailureText(reason)); d->failureText->setHtml(visibleFailureText(reason));
@ -598,7 +598,7 @@ void Applet::setNeedsConfiguring(bool needsConfig)
if (needsConfig) { if (needsConfig) {
setDrawStandardBackground(true); setDrawStandardBackground(true);
Layout* layout = new VBoxLayout(this); Layout* layout = new BoxLayout(BoxLayout::TopToBottom,this);
PushButton* button = new PushButton(this); PushButton* button = new PushButton(this);
button->setText(i18n("Configure...")); button->setText(i18n("Configure..."));
connect(button, SIGNAL(clicked()), this, SLOT(performSetupConfig())); connect(button, SIGNAL(clicked()), this, SLOT(performSetupConfig()));
@ -629,6 +629,9 @@ QRectF Applet::boundingRect() const
d->getBorderSize(left,top,right,bottom); d->getBorderSize(left,top,right,bottom);
//qDebug() << "Background , Border size" << d->background << left << top << right << bottom;
return rect.adjusted(-left,-top,right,bottom); return rect.adjusted(-left,-top,right,bottom);
} }
@ -641,7 +644,9 @@ QSizeF Applet::sizeHint() const
d->getBorderSize(left,top,right,bottom); d->getBorderSize(left,top,right,bottom);
return contentSize() + QSizeF(left+right,top+bottom); //qDebug() << "Applet content size hint: " << contentSizeHint();
return contentSizeHint() + QSizeF(left+right,top+bottom);
} }
QList<QAction*> Applet::contextActions() QList<QAction*> Applet::contextActions()
@ -746,6 +751,17 @@ Location Applet::location() const
} }
QSizeF Applet::contentSize() const QSizeF Applet::contentSize() const
{
int top , left , right , bottom;
d->getBorderSize(left,top,right,bottom);
// qDebug() << "Geometry size: " << geometry().size();
// qDebug() << "Borders: " << left << top << right << bottom;
return geometry().size() - QSizeF(left+right,top+bottom);
}
QSizeF Applet::contentSizeHint() const
{ {
if (layout()) { if (layout()) {
return layout()->sizeHint(); return layout()->sizeHint();

View File

@ -197,39 +197,23 @@ class PLASMA_EXPORT Applet : public Widget
Location location() const; Location location() const;
/** /**
* Returns a maximum size hint based on the Corona's space availability. * Returns the area within which contents can be painted. If there is no
* * background, then this is equivalent to boundingRect().size()
* An applet may choose to violate this size hint, but should try and **/
* respect it as much as possible. QSizeF contentSize() const;
*/
// QRectF maxSizeHint() const;
/** /**
* Returns the area within which contents can be painted. If there is no * Returns an ideal size for the applet's content.
* background, then this is equivalent to boundingRect(). * Applets can re-implement this to provide a suitable size based
* on their contents.
* *
* Applets should implement contentSize() to tell Plasma::Applet how * Unlike sizeHint() , contentSizeHint() does not include the
* much space they need. * size of any borders surrounding the content area.
* *
* When drawing to the applet in the paintInterface() method, you can * The default implementation returns the sizeHint() of the applet's
* use the QRect passed to that function. Outside paintInterface(), for * layout if it has one, or a null size otherwise.
* example when positioning a Plasma::Widget, you can assume your drawing */
* area has (0,0) at the top left and is the size of contentSize(). virtual QSizeF contentSizeHint() const;
*
* If drawStandardBackground() == true, enough space will reserved
* within the borders for the content, and boundingRect() will the
* total size of the applet, including borders.
*
* If the applet has not asked Plasma::Applet to draw the default
* background, boundingRect().size() == contentSize(), and
* boundingRect().topLeft() == QPointF(0,0). Also, such applets will
* not break if they implement boundingRect() instead.
*
* Note that if the value returned by contentSize() (and hence
* boundingRect()) changes for any reason, you should call
* prepareGeometryChange() to notify the Corona.
**/
virtual QSizeF contentSize() const;
/** /**
* Returns a list of all known applets in a hash keyed by a unique * Returns a list of all known applets in a hash keyed by a unique
@ -470,6 +454,11 @@ class PLASMA_EXPORT Applet : public Widget
*/ */
bool isShadowShown() const; bool isShadowShown() const;
// reimplemented from LayoutItem
// value is the same as contentSizeHint() if drawStandardBackground() is false
// or contentSizeHint() plus the size of the border otherwise.
virtual QSizeF sizeHint() const;
Q_SIGNALS: Q_SIGNALS:
/** /**
* Emitted when the applet needs to take (or lose) keyboard focus. * Emitted when the applet needs to take (or lose) keyboard focus.
@ -550,10 +539,6 @@ class PLASMA_EXPORT Applet : public Widget
*/ */
QVariant itemChange(GraphicsItemChange change, const QVariant &value); QVariant itemChange(GraphicsItemChange change, const QVariant &value);
// reimplemented from LayoutItem
// value is the same as contentSize() if drawStandardBackground() is false
// or contentSize() plus the size of the border otherwise.
virtual QSizeF sizeHint() const;
protected Q_SLOTS: protected Q_SLOTS:
/** /**

View File

@ -37,8 +37,7 @@
#include "dataengine.h" #include "dataengine.h"
#include "karambamanager.h" #include "karambamanager.h"
#include "phase.h" #include "phase.h"
#include "widgets/hboxlayout.h" #include "widgets/boxlayout.h"
#include "widgets/vboxlayout.h"
#include "widgets/icon.h" #include "widgets/icon.h"
using namespace Plasma; using namespace Plasma;
@ -136,10 +135,10 @@ void Corona::setFormFactor(FormFactor formFactor)
case Planar: case Planar:
break; break;
case Horizontal: case Horizontal:
d->layout = new HBoxLayout; d->layout = new BoxLayout(BoxLayout::LeftToRight);
break; break;
case Vertical: case Vertical:
d->layout = new VBoxLayout; d->layout = new BoxLayout(BoxLayout::TopToBottom);
break; break;
case MediaCenter: case MediaCenter:
break; break;
@ -224,17 +223,31 @@ Applet* Corona::addApplet(const QString& name, const QVariantList& args, uint id
qreal appWidth = applet->boundingRect().width(); qreal appWidth = applet->boundingRect().width();
qreal appHeight = applet->boundingRect().height(); qreal appHeight = applet->boundingRect().height();
if (geometry.isValid()) {
applet->setGeometry(geometry);
} else {
applet->setGeometry( QRectF(geometry.topLeft(),applet->sizeHint()) );
}
#if 0
if (!geometry.size().isValid()) {
}
if (geometry.isValid()) { if (geometry.isValid()) {
applet->setGeometry(geometry); applet->setGeometry(geometry);
} else if (geometry.x() != -1 && geometry.y() != -1) { } else if (geometry.x() != -1 && geometry.y() != -1) {
// yes, this means we can't have items start -1, -1 // yes, this means we can't have items start -1, -1
applet->setPos(geometry.topLeft() - QPoint(applet->boundingRect().width()/2, applet->setPos(geometry.topLeft() - QPointF(applet->boundingRect().width()/2,
applet->boundingRect().height()/2)); applet->boundingRect().height()/2));
} else { } else {
//TODO: Make sure new applets don't overlap with existing ones //TODO: Make sure new applets don't overlap with existing ones
// Center exactly: // Center exactly:
applet->setPos((width() / 2) - (appWidth / 2), (height() / 2) - (appHeight / 2)); applet->setPos((width() / 2) - (appWidth / 2), (height() / 2) - (appHeight / 2));
} }
#endif
addItem(applet); addItem(applet);
applet->updateConstraints(); applet->updateConstraints();

View File

@ -1,5 +1,6 @@
/* /*
* Copyright 2007 by Matias Valdenegro T. <mvaldenegro@informatica.utem.cl> * Copyright 2007 by Matias Valdenegro T. <mvaldenegro@informatica.utem.cl>
* Copyright 2007 by Robert Knight <robertknight@gmail.com>
* *
* This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License version 2 as * it under the terms of the GNU Library General Public License version 2 as
@ -19,34 +20,253 @@
#include "boxlayout.h" #include "boxlayout.h"
#include <QtCore/QList> #include <QtCore/QList>
#include <QtCore/QTimeLine>
#include <KDebug> #include <KDebug>
#include "layoutanimator.h"
namespace Plasma namespace Plasma
{ {
class BoxLayout::Private class BoxLayout::Private
{ {
public: public:
BoxLayout *const q;
Direction direction;
QRectF geometry; QRectF geometry;
QList<LayoutItem *> childList; QList<LayoutItem*> children;
Qt::Orientations orientation;
Private(BoxLayout *parent)
: q(parent)
, direction(LeftToRight)
{
}
// returns the component of 'size' in the expanding direction
// of this layout
qreal size(const QSizeF& size) const
{
switch (direction) {
case LeftToRight:
case RightToLeft:
return size.width();
case TopToBottom:
case BottomToTop:
return size.height();
default:
Q_ASSERT(false);
return 0;
}
}
// returns the direction in which this layout expands
// or shrinks
Qt::Orientation expandingDirection() const
{
switch ( direction ) {
case LeftToRight:
case RightToLeft:
return Qt::Horizontal;
case TopToBottom:
case BottomToTop:
return Qt::Vertical;
default:
Q_ASSERT(false);
return Qt::Horizontal;
}
}
// returns the position from which layouting should
// begin depending on the direction of this layout
qreal startPos(const QRectF& geometry) const
{
switch ( direction ) {
case LeftToRight:
case TopToBottom:
return 0;
case RightToLeft:
return geometry.width();
case BottomToTop:
return geometry.height();
default:
Q_ASSERT(false);
return 0;
}
}
// lays out an item
//
// 'geometry' the geometry of the layout
// 'item' the item whoose geometry should be altered
// 'pos' the position of the item (in the expanding direction of the layout)
// 'size' the size of the item (in the expanding direction of the layout)
//
// returns the position for the next item in the layout
//
qreal layoutItem(const QRectF& geometry , LayoutItem *item , const qreal pos , qreal size)
{
//qDebug() << "layoutItem: " << direction << "item size" << size;
QRectF newGeometry;
qreal newPos = 0;
qreal top = 0;
qreal height = 0;
QSizeF minSize = item->minimumSize();
QSizeF maxSize = item->maximumSize();
switch ( direction ) {
case LeftToRight:
case RightToLeft:
height = qBound(minSize.height(),geometry.height(),maxSize.height());
top = geometry.top();
break;
case TopToBottom:
case BottomToTop:
height = qBound(minSize.width(),geometry.width(),maxSize.width());
top = geometry.left();
break;
}
switch ( direction ) {
case LeftToRight:
newGeometry = QRectF(pos,top,size,height);
newPos = pos+size+q->spacing();
break;
case RightToLeft:
newGeometry = QRectF(geometry.width()-pos-size,top,
size,height);
newPos = pos-size-q->spacing();
break;
case TopToBottom:
newGeometry = QRectF(top,pos,height,size);
newPos = pos+size+q->spacing();
break;
case BottomToTop:
newGeometry = QRectF(top,geometry.height()-pos-size,
height,size);
newPos = pos-size-q->spacing();
break;
}
// qDebug() << "Item geometry: " << newGeometry;
if ( q->animator() )
q->animator()->setGeometry(item,newGeometry);
else
item->setGeometry(newGeometry);
return newPos;
}
enum SizeType
{
MinSize,
MaxSize,
HintSize
};
// this provides a + function which can be passed as the 'op'
// argument to calculateSize
static qreal sum(const qreal a , const qreal b)
{
return a+b;
}
// calcualtes a size hint or value for this layout
// 'sizeType' - The item size ( minimum , maximum , hint ) to use
// 'dir' - The direction component of the item size to use
// 'op' - A function to apply to the size of each item in the layout
// , usually qMax,qMin or sum
template <class T>
qreal calculateSize(SizeType sizeType , Qt::Orientation dir , T (*op)(T,T)) const
{
qreal value = 0;
for ( int i = 0 ; i < children.count() ; i++ ) {
QSizeF itemSize;
switch ( sizeType ) {
case MinSize:
itemSize = children[i]->minimumSize();
break;
case MaxSize:
itemSize = children[i]->maximumSize();
break;
case HintSize:
itemSize = children[i]->sizeHint();
break;
}
if ( dir == Qt::Horizontal ) {
value = op(value,itemSize.width());
} else {
value = op(value,itemSize.height());
}
}
return value;
}
// calculates a size hint or value for this layout
// 'calculateSizeType' specifies the value to be calculated
QSizeF calculateSize(SizeType calculateSizeType) const
{
QSizeF result;
const qreal totalMargin = q->margin() * 2;
const qreal totalSpacing = q->spacing() * (children.count()-1);
switch ( direction ) {
case LeftToRight:
case RightToLeft:
result = QSizeF(calculateSize(calculateSizeType,Qt::Horizontal,sum),
calculateSize(calculateSizeType,Qt::Vertical,qMax<qreal>));
result.rwidth() += totalMargin + totalSpacing;
result.rheight() += totalMargin;
break;
case TopToBottom:
case BottomToTop:
result = QSizeF(calculateSize(calculateSizeType,Qt::Horizontal,qMax<qreal>),
calculateSize(calculateSizeType,Qt::Vertical,sum));
result.rheight() += totalMargin + totalSpacing;
result.rwidth() += totalMargin;
break;
}
return result;
}
}; };
BoxLayout::BoxLayout(Qt::Orientations orientation, LayoutItem *parent) BoxLayout::BoxLayout(Direction direction , LayoutItem *parent)
: Layout(parent), : Layout(parent),
d(new Private) d(new Private(this))
{ {
if (parent) { if (parent) {
parent->setLayout(this); parent->setLayout(this);
} }
d->orientation = orientation;
d->direction = direction;
}
void BoxLayout::setDirection(Direction direction)
{
d->direction = direction;
update();
}
BoxLayout::Direction BoxLayout::direction() const
{
return d->direction;
} }
BoxLayout::~BoxLayout() BoxLayout::~BoxLayout()
{ {
foreach (LayoutItem* item, children()) { foreach (LayoutItem* item, d->children) {
item->unsetManagingLayout(this); item->unsetManagingLayout(this);
} }
delete d; delete d;
@ -54,17 +274,17 @@ BoxLayout::~BoxLayout()
Qt::Orientations BoxLayout::expandingDirections() const Qt::Orientations BoxLayout::expandingDirections() const
{ {
return d->orientation; switch ( d->direction ) {
case LeftToRight:
case RightToLeft:
return Qt::Horizontal;
case TopToBottom:
case BottomToTop:
return Qt::Vertical;
default:
Q_ASSERT(false);
return 0;
} }
QSizeF BoxLayout::minimumSize() const
{
return QSizeF();
}
QSizeF BoxLayout::maximumSize() const
{
return QSizeF();
} }
QRectF BoxLayout::geometry() const QRectF BoxLayout::geometry() const
@ -72,162 +292,180 @@ QRectF BoxLayout::geometry() const
return d->geometry; return d->geometry;
} }
void BoxLayout::setGeometry(const QRectF& geometry)
{
if (!geometry.isValid() || geometry.isEmpty()) {
kDebug() << "Invalid Geometry " << geometry;
d->geometry = geometry;
return;
}
kDebug() << this << " Geometry process " << geometry << " for " << children().count() << " children";
QList<LayoutItem *> fixedChildren;
QList<LayoutItem *> expandingChildren;
QList<QSizeF> sizes;
QSizeF available = geometry.size() - QSizeF(2 * margin(), 2 * margin());
foreach (LayoutItem *l, children()) {
if (l->expandingDirections() & d->orientation) {
expandingChildren.append(l);
} else {
fixedChildren.append(l);
}
}
foreach (LayoutItem *l, fixedChildren) {
QSizeF hint = l->sizeHint();
if ( d->orientation == Qt::Vertical ) {
sizes.insert(indexOf(l), QSizeF(available.width(), hint.height()));
available -= QSizeF(0.0, hint.height() + spacing());
} else {
sizes.insert(indexOf(l), QSizeF(hint.width(), available.height()));
available -= QSizeF(hint.width() + spacing(), 0.0f);
}
}
if (expandingChildren.count() > 0) {
if ( d->orientation == Qt::Vertical ) {
qreal expandHeight = (available.height() - ((expandingChildren.count() - 1) * spacing())) / expandingChildren.count();
foreach (LayoutItem *l, expandingChildren) {
sizes.insert(indexOf(l),QSizeF(available.width(), expandHeight));
}
} else {
qreal expandWidth = (available.width() - ((expandingChildren.count() - 1) * spacing())) / expandingChildren.count();
foreach (LayoutItem *l, expandingChildren) {
sizes.insert(indexOf(l), QSizeF(expandWidth, available.height()));
}
}
}
QPointF start = geometry.topLeft();
start += QPointF(margin(), spacing());
for (int i = 0; i < sizes.size(); i++) {
LayoutItem *l = itemAt(i);
kDebug() << "Setting Geometry for child " << l << " to " << QRectF(start, sizes[i]);
l->setGeometry(QRectF(start, sizes[i]));
if ( d->orientation == Qt::Vertical ) {
start += QPointF(0.0, sizes[i].height() + spacing());
} else {
start += QPointF(sizes[i].width() + spacing(), 0.0);
}
}
d->geometry = geometry;
}
QSizeF BoxLayout::sizeHint() const
{
qreal hintHeight = 0.0;
qreal hintWidth = 0.0;
foreach(LayoutItem *l, children()) {
QSizeF hint = l->sizeHint();
if ( d->orientation == Qt::Vertical ) {
hintWidth = qMax(hint.width(), hintWidth);
hintHeight += hint.height() + spacing();
} else {
hintHeight = qMax(hint.height(), hintHeight);
hintWidth += hint.width() + spacing();
}
}
hintWidth += 2 * margin();
hintHeight += 2 * margin();
return QSizeF(hintWidth, hintHeight);
}
int BoxLayout::count() const int BoxLayout::count() const
{ {
return d->childList.count(); return d->children.count();
} }
bool BoxLayout::isEmpty() const void BoxLayout::insertItem(int index, LayoutItem *item)
{ {
return count() == 0; if (!item) {
}
void BoxLayout::insertItem(int index, LayoutItem *l)
{
if (!l) {
return; return;
} }
//l->setLayout(this); item->setManagingLayout(this);
d->childList.insert(index, l);
setGeometry(geometry()); if ( index == -1 )
index = d->children.size();
d->children.insert(index, item);
if ( animator() )
animator()->setCurrentState(item,LayoutAnimator::InsertedState);
update();
} }
void BoxLayout::addItem(LayoutItem *l) void BoxLayout::addItem(LayoutItem *item)
{ {
if (!l) { insertItem(-1,item);
}
void BoxLayout::removeItem(LayoutItem *item)
{
if (!item) {
return; return;
} }
l->setManagingLayout(this); item->unsetManagingLayout(this);
d->childList.append(l); d->children.removeAll(item);
setGeometry(geometry());
}
void BoxLayout::removeItem(LayoutItem *l) if ( animator() )
{ animator()->setCurrentState(item,LayoutAnimator::RemovedState);
if (!l) {
return;
}
l->unsetManagingLayout(this); update();
d->childList.removeAll(l);
setGeometry(geometry());
} }
int BoxLayout::indexOf(LayoutItem *l) const int BoxLayout::indexOf(LayoutItem *l) const
{ {
return d->childList.indexOf(l); return d->children.indexOf(l);
} }
LayoutItem *BoxLayout::itemAt(int i) const LayoutItem *BoxLayout::itemAt(int i) const
{ {
return d->childList[i]; return d->children[i];
} }
LayoutItem *BoxLayout::takeAt(int i) LayoutItem *BoxLayout::takeAt(int i)
{ {
return d->childList.takeAt(i); return d->children.takeAt(i);
setGeometry(geometry()); update();
} }
QSizeF BoxLayout::size() const void BoxLayout::setGeometry(const QRectF& geo)
{ {
return d->geometry.size(); QRectF geometry = geo.adjusted(margin(),margin(),-margin(),-margin());
//qDebug() << "geo before " << geo << "after" << geometry << "margin" << margin();
//qDebug() << "Box layout beginning with geo" << geometry;
//qDebug() << "This box max size" << maximumSize();
QVector<qreal> sizes(count());
QVector<qreal> expansionSpace(count());
qreal available = d->size(geometry.size()) - spacing()*(d->children.count()-1);
qreal perItemSize = available / count();
// initial distribution of space to items
for ( int i = 0 ; i < sizes.count() ; i++ ) {
const LayoutItem *item = d->children[i];
const bool isExpanding = item->expandingDirections() & d->expandingDirection();
if ( isExpanding )
sizes[i] = perItemSize;
else
sizes[i] = d->size(item->sizeHint());
const qreal minItemSize = d->size(item->minimumSize());
const qreal maxItemSize = d->size(item->maximumSize());
// qDebug() << "Layout max item " << i << "size: " << maxItemSize;
sizes[i] = qMin( sizes[i] , maxItemSize );
sizes[i] = qMax( sizes[i] , minItemSize );
// qDebug() << "Available: " << available << "per item:" << perItemSize <<
// "Initial size: " << sizes[i];
if ( isExpanding )
expansionSpace[i] = maxItemSize-sizes[i];
else
expansionSpace[i] = 0;
// adjust the per-item size if the space was over or under used
if ( sizes[i] != perItemSize && i != sizes.count()-1 ) {
perItemSize = available / (sizes.count()-i-1);
} }
QList<LayoutItem *> BoxLayout::children() const available -= sizes[i];
{
return d->childList;
} }
// distribute out any remaining space to items which can still expand
//
// space is distributed equally amongst remaining items until we run
// out of space or items to expand
int expandable = sizes.count();
const qreal threshold = 1.0;
while ( available > threshold && expandable > 0 ) {
qreal extraSpace = available / expandable;
for ( int i = 0 ; i < sizes.count() ; i++ ) {
if ( expansionSpace[i] > threshold ) {
qreal oldSize = sizes[i];
sizes[i] += qMin(extraSpace,expansionSpace[i]);
expansionSpace[i] -= sizes[i]-oldSize;
available -= sizes[i]-oldSize;
} else {
expandable--;
}
}
}
// set items' geometry according to new sizes
qreal pos = d->startPos(geometry);
for ( int i = 0 ; i < sizes.count() ; i++ ) {
//QObject *obj = dynamic_cast<QObject*>(d->children[i]);
//if ( obj )
//qDebug() << "Item " << i << obj->metaObject()->className() << "size:" << sizes[i];
pos = d->layoutItem( geometry , d->children[i] , pos , sizes[i] );
}
d->geometry = geometry;
if ( animator() && animator()->timeLine() )
animator()->timeLine()->start();
}
QSizeF BoxLayout::maximumSize() const
{
return Layout::maximumSize();
}
QSizeF BoxLayout::minimumSize() const
{
return d->calculateSize(Private::MinSize);
}
QSizeF BoxLayout::sizeHint() const
{
return d->calculateSize(Private::HintSize);
}
HBoxLayout::HBoxLayout(LayoutItem *parent)
: BoxLayout(LeftToRight,parent)
{
}
VBoxLayout::VBoxLayout(LayoutItem *parent)
: BoxLayout(TopToBottom,parent)
{
}
} // Plasma namespace } // Plasma namespace

View File

@ -1,5 +1,6 @@
/* /*
* Copyright 2007 by Matias Valdenegro T. <mvaldenegro@informatica.utem.cl> * Copyright 2007 by Matias Valdenegro T. <mvaldenegro@informatica.utem.cl>
* Copyright 2007 by Robert Knight <robertknight@gmail.com>
* *
* This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License version 2 as * it under the terms of the GNU Library General Public License version 2 as
@ -27,61 +28,89 @@
namespace Plasma namespace Plasma
{ {
/** /**
* Vertical Box Layout * The BoxLayout class lays out items in a horizontal or vertical line.
*
* @author Matias Valdenegro T. <mvaldenegro@informatica.utem.cl>
*
* This class implements a generic box Layout used as a common API for VBox and HBox implementations.
*/ */
class PLASMA_EXPORT BoxLayout : public Layout class PLASMA_EXPORT BoxLayout : public Layout
{ {
public: public:
/**
* This enum describes the directions in which items can be laid
* out.
*/
enum Direction
{
/** Lay items out horizontally, from left to right. */
LeftToRight,
/** Lay items out horizontally, from right to left. */
RightToLeft,
/** Lay items out vertically, from top to bottom. */
TopToBottom,
/** Lay items out vertically, from bottom to top. */
BottomToTop
};
/** /**
* Constructor. * Creates a new box layout which lays items out in the specified
*/ * @p direction
explicit BoxLayout(Qt::Orientations orientation, LayoutItem *parent = 0);
/**
* Destructor.
*/ */
explicit BoxLayout(Direction direction , LayoutItem *parent = 0);
~BoxLayout(); ~BoxLayout();
Qt::Orientations expandingDirections() const; /** Sets the direction in which items are laid out. */
void setDirection(Direction direction);
QSizeF minimumSize() const; /** Returns the direction in which items are laid out. */
QSizeF maximumSize() const; Direction direction() const;
QRectF geometry() const;
void setGeometry(const QRectF& geometry);
QSizeF sizeHint() const;
int count() const;
bool isEmpty() const;
/** Inserts a new item into the layout at the specified index. */
void insertItem(int index, LayoutItem *l); void insertItem(int index, LayoutItem *l);
void addItem(LayoutItem *l);
void removeItem(LayoutItem *l); // reimplemented from Layout
virtual void addItem(LayoutItem *l);
virtual void removeItem(LayoutItem *l);
virtual int indexOf(LayoutItem *l) const;
virtual LayoutItem *itemAt(int i) const;
virtual LayoutItem *takeAt(int i);
virtual Qt::Orientations expandingDirections() const;
virtual QRectF geometry() const;
virtual void setGeometry(const QRectF& geometry);
virtual int count() const;
int indexOf(LayoutItem *l) const; virtual QSizeF minimumSize() const;
LayoutItem *itemAt(int i) const; virtual QSizeF maximumSize() const;
LayoutItem *takeAt(int i); virtual QSizeF sizeHint() const;
QSizeF size() const;
protected:
QList<LayoutItem *> children() const;
private: private:
class Private; class Private;
Private *const d; Private *const d;
}; };
/**
* A BoxLayout which defaults to laying items out
* horizontally in a left-to-right order.
*
* Equivalent to creating a BoxLayout and passing LeftToRight
* in the constructor.
*/
class PLASMA_EXPORT HBoxLayout : public BoxLayout
{
public:
explicit HBoxLayout(LayoutItem *parent = 0);
};
/**
* A BoxLayout which defaults to laying items out
* vertically in a top-to-bottom order.
*
* Equivalent to creating a BoxLayout and passing TopToBottom
* in the constructor.
*/
class PLASMA_EXPORT VBoxLayout : public BoxLayout
{
public:
explicit VBoxLayout(LayoutItem *parent = 0);
};
} }
#endif /* _H_BOX_LAYOUT__ */ #endif /* PLASMA_BOX_LAYOUT */

204
widgets/flowlayout.cpp Normal file
View File

@ -0,0 +1,204 @@
/*
* Copyright 2007 by Robert Knight <robertknight@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 version 2,
* 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 <QtDebug>
#include "layoutanimator.h"
using namespace Plasma;
class FlowLayout::Private
{
public:
QList<LayoutItem*> items;
QRectF geometry;
};
FlowLayout::FlowLayout(LayoutItem* parent)
: Layout(parent)
, d(new Private)
{
}
FlowLayout::~FlowLayout()
{
delete d;
}
int FlowLayout::count() const
{
return d->items.count();
}
void FlowLayout::addItem(LayoutItem* item)
{
d->items << item;
if ( animator() )
animator()->setCurrentState(item,LayoutAnimator::InsertedState);
item->setManagingLayout(this);
}
void FlowLayout::removeItem(LayoutItem* item)
{
d->items.removeAll(item);
if ( animator() )
animator()->setCurrentState(item,LayoutAnimator::RemovedState);
}
int FlowLayout::indexOf(LayoutItem* item) const
{
return d->items.indexOf(item);
}
LayoutItem* FlowLayout::itemAt(int i) const
{
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)
{
return d->items.takeAt(i);
}
QRectF FlowLayout::geometry() const
{
return d->geometry;
}
template <class T>
T qSum(const QList<T>& container)
{
T total = 0;
foreach( const T& item , container ) {
total += item;
}
return total;
}
void FlowLayout::setGeometry(const QRectF& geo)
{
QRectF geometry(geo);
geometry.adjust(margin(),margin(),-margin(),-margin());
qDebug() << "Flow layout geometry set to " << geo;
// calculate average size of items
qreal totalWidth = 0;
qreal totalHeight = 0;
foreach( LayoutItem *item , d->items ) {
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
const qreal averageWidth = totalWidth / d->items.count() + 2*spacing();
const int columnCount = (int)(geometry.width() / averageWidth);
int insertColumn = 0;
qreal rowPos = 0;
qreal rowHeight = 0;
// lay the items out in left-to-right , top-to-bottom order
foreach( LayoutItem *item , d->items ) {
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
if ( itemWidth > geometry.width() ) {
itemWidth = qMax(geometry.width(),item->minimumSize().width());
offset = 0;
}
// position the item
const QRectF newGeometry( geometry.left() +
insertColumn * averageWidth + offset,
geometry.top() + rowPos ,
itemWidth ,
itemSize.height() );
rowHeight = qMax(rowHeight,itemSize.height());
insertColumn += columnSpan;
if ( animator() )
animator()->setGeometry( item , newGeometry );
else
item->setGeometry( newGeometry );
}
d->geometry = geo;
if ( animator() && animator()->timeLine() )
animator()->timeLine()->start();
}
Qt::Orientations FlowLayout::expandingDirections() const
{
return Qt::Vertical | Qt::Horizontal;
}

64
widgets/flowlayout.h Normal file
View File

@ -0,0 +1,64 @@
/*
* Copyright 2007 by Robert Knight <robertknight@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 version 2,
* 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.
*/
#ifndef __FLOWLAYOUT__
#define __FLOWLAYOUT__
#include <plasma/plasma_export.h>
#include "layout.h"
namespace Plasma
{
class LayoutItem;
/**
* A layout which lays items out left-to-right , top-to-bottom.
*
* This is similar to the layout of items in a QListView.
*/
class PLASMA_EXPORT FlowLayout : public Layout
{
public:
/** Construct a new flow layout with the specified parent. */
explicit FlowLayout(LayoutItem* parent);
virtual ~FlowLayout();
// reimplemented
virtual int count() const;
virtual void addItem(LayoutItem* item);
virtual void removeItem(LayoutItem* item);
virtual int indexOf(LayoutItem* item) const;
virtual LayoutItem* itemAt(int i) const;
virtual LayoutItem* takeAt(int i);
virtual QSizeF sizeHint() const;
virtual QRectF geometry() const;
virtual void setGeometry(const QRectF& geometry);
virtual Qt::Orientations expandingDirections() const;
private:
class Private;
Private *const d;
};
}
#endif // __FLOWLAYOUT__

View File

@ -1,45 +0,0 @@
/*
* Copyright 2007 by Matias Valdenegro T. <mvaldenegro@informatica.utem.cl>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License version 2 as
* published by the Free Software Foundation
*
* 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 "hboxlayout.h"
namespace Plasma
{
HBoxLayout::HBoxLayout(LayoutItem *parent)
: BoxLayout(Qt::Horizontal, parent),
d(0)
{
}
HBoxLayout::~HBoxLayout()
{
}
bool HBoxLayout::hasWidthForHeight() const
{
return true;
}
qreal HBoxLayout::widthForHeight(qreal w) const
{
Q_UNUSED(w);
return qreal();
}
}

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2007 by Matias Valdenegro T. <mvaldenegro@informatica.utem.cl> * Copyright 2007 by Robert Knight <robertknight@gmail.com>
* *
* This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License version 2 as * it under the terms of the GNU Library General Public License version 2 as
@ -16,48 +16,9 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/ */
#ifndef __H_BOX_LAYOUT__ #ifndef PLASMA_HBOXLAYOUT
#define __H_BOX_LAYOUT__ #define PLASMA_HBOXLAYOUT
#include <QtCore/QRectF>
#include <QtCore/QSizeF>
#include <plasma/plasma_export.h>
#include <plasma/widgets/boxlayout.h> #include <plasma/widgets/boxlayout.h>
namespace Plasma #endif /* PLASMA_HBOXLAYOUT */
{
/**
* Horizontal Box Layout
*
* @author Matias Valdenegro T. <mvaldenegro@informatica.utem.cl>
*
* This class implements a Horizontal Box Layout, it just lays items horizontally, from left to right.
*/
class PLASMA_EXPORT HBoxLayout : public BoxLayout
{
public:
/**
* Constructor.
*/
HBoxLayout(LayoutItem *parent = 0);
/**
* Virtual Destructor.
*/
virtual ~HBoxLayout();
bool hasWidthForHeight() const;
qreal widthForHeight(qreal w) const;
private:
class Private;
Private *const d;
};
}
#endif /* __H_BOX_LAYOUT__ */

View File

@ -10,8 +10,7 @@
#include "../pushbutton.h" #include "../pushbutton.h"
#include "../lineedit.h" #include "../lineedit.h"
#include "../vboxlayout.h" #include "../boxlayout.h"
#include "../hboxlayout.h"
#include "../widget.h" #include "../widget.h"
#include "../label.h" #include "../label.h"
@ -63,6 +62,8 @@ int main(int argc, char **argv)
h2->addItem(labelTwo); h2->addItem(labelTwo);
scene.addItem(labelTwo); scene.addItem(labelTwo);
widgetLayout->update();
view.show(); view.show();
return app.exec(); return app.exec();
} }

View File

@ -1,45 +0,0 @@
/*
* Copyright 2007 by Matias Valdenegro T. <mvaldenegro@informatica.utem.cl>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License version 2 as
* published by the Free Software Foundation
*
* 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 "vboxlayout.h"
namespace Plasma
{
VBoxLayout::VBoxLayout(LayoutItem *parent)
: BoxLayout(Qt::Vertical, parent),
d(0)
{
}
VBoxLayout::~VBoxLayout()
{
}
bool VBoxLayout::hasHeightForWidth() const
{
return true;
}
qreal VBoxLayout::heightForWidth(qreal w) const
{
Q_UNUSED(w);
return qreal();
}
}

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2007 by Matias Valdenegro T. <mvaldenegro@informatica.utem.cl> * Copyright 2007 by Robert Knight <robertknight@gmail.com>
* *
* This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License version 2 as * it under the terms of the GNU Library General Public License version 2 as
@ -19,42 +19,6 @@
#ifndef PLASMA_VBOXLAYOUT #ifndef PLASMA_VBOXLAYOUT
#define PLASMA_VBOXLAYOUT #define PLASMA_VBOXLAYOUT
#include <plasma/plasma_export.h>
#include <plasma/widgets/boxlayout.h> #include <plasma/widgets/boxlayout.h>
namespace Plasma
{
/**
* Vertical Box Layout
*
* @author Matias Valdenegro T. <mvaldenegro@informatica.utem.cl>
*
* This class implements a Vertical Box Layout, it just lays items in vertical, from up to down.
*/
class PLASMA_EXPORT VBoxLayout : public BoxLayout
{
public:
/**
* Constructor.
*/
explicit VBoxLayout(LayoutItem *parent = 0);
/**
* Virtual Destructor.
*/
~VBoxLayout();
bool hasHeightForWidth() const;
qreal heightForWidth(qreal w) const;
private:
class Private;
Private *const d;
};
}
#endif /* PLASMA_VBOXLAYOUT */ #endif /* PLASMA_VBOXLAYOUT */