From 22a43f000dd7a521cf06150dfc1a63ee9563fa6c Mon Sep 17 00:00:00 2001 From: Mark Liversedge Date: Sat, 17 Sep 2011 16:01:53 +0100 Subject: [PATCH] Smart Layout in Tile View The tile view will now layout the charts in a more efficient manner. It still attempts to run from top to bottom and then left to right when laying out charts, but now, if there is any space and the chart will fit in it, it will be moved. This means you can layout the view with smaller charts surrounding or to the left of right of a larger one. This is particularly useful when looking at say a large summary window with a smaller map and a route profile etc. It can be a little disconcerting however, since charts will suddenly 'disappear' when you resize them -- of course they don't disappear they just move to a space that can accomodate them. We could make 'smart' layouts optional if users get thrown by it. Lastly, fixed a bug when resizing charts -- if they move position when resizing then the delta (size change) is miscalculated. This is avoided now by ending any resize action when a chart moves. Fixes #453. --- src/GcWindowLayout.cpp | 151 +++++++++++++++++++++++++++++++++++------ src/GoldenCheetah.cpp | 9 +++ src/GoldenCheetah.h | 1 + 3 files changed, 139 insertions(+), 22 deletions(-) diff --git a/src/GcWindowLayout.cpp b/src/GcWindowLayout.cpp index f0d376533..916eb322f 100644 --- a/src/GcWindowLayout.cpp +++ b/src/GcWindowLayout.cpp @@ -19,6 +19,8 @@ #include #include "GcWindowLayout.h" +#include + GcWindowLayout::GcWindowLayout(QWidget *parent, int margin, int hSpacing, int vSpacing) : QLayout(parent), m_hSpace(hSpacing), m_vSpace(vSpacing) { @@ -123,42 +125,147 @@ QSize GcWindowLayout::minimumSize() const return size; } +// we sort the points to try from top to bottom +// and then left to right since this is the sequence +// we follow to layout the charts +class QPointS : public QPoint +{ + public: + QPointS(int x, int y) : QPoint(x,y) {} + + bool operator< (QPointS right) const { + if (y() == right.y()) + return x() < right.x(); + else + return y() < right.y(); + } +}; + int GcWindowLayout::doLayout(const QRect &rect, bool testOnly) const { + // geometry for the layout int left, top, right, bottom; + int spaceX = horizontalSpacing(); + int spaceY = verticalSpacing(); getContentsMargins(&left, &top, &right, &bottom); - QRect effectiveRect = rect.adjusted(+left, +top, -right, -bottom); - int x = effectiveRect.x(); - int y = effectiveRect.y(); - int lineHeight = 0; - QLayoutItem *item; - foreach (item, itemList) { + // the space we have to line up our charts + QRect effectiveRect = rect.adjusted(+left, +top, -right, -bottom); + + // Charts already positioned are held here + QList placed; + + // iterate through each chart, placing it in the + // most optimal location top to bottom then + // left to right (packed flow) + foreach (QLayoutItem *item, itemList) { + + // what widget is in this item QWidget *wid = item->widget(); - int spaceX = horizontalSpacing(); + + // handle when no spacing is set (always is for HomeWindow layouts) if (spaceX == -1) - spaceX = wid->style()->layoutSpacing( - QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Horizontal); - int spaceY = verticalSpacing(); + spaceX = wid->style()->layoutSpacing(QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Horizontal); if (spaceY == -1) - spaceY = wid->style()->layoutSpacing( - QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Vertical); - int nextX = x + item->sizeHint().width() + spaceX; - if (nextX - spaceX > effectiveRect.right() && lineHeight > 0) { + spaceY = wid->style()->layoutSpacing( QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Vertical); + + + // the location we are going to place + int x=-1, y=-1; + + if (placed.count() == 0) { // is first item! + x = effectiveRect.x(); - y = y + lineHeight + spaceY; - nextX = x + item->sizeHint().width() + spaceX; - lineHeight = 0; + y = effectiveRect.y(); + + } else { + + // get a list of all the locations that the chart could be placed at + QList attempts; + + // iterate through the charts and create a list of placement + // points that we can attempt to put the current chart + foreach(QRect here, placed) { + + // always try to the right of another chart + attempts.append(QPointS(here.x()+here.width()+spaceX, here.y())); + + int ax = here.x(); + foreach(QRect there, placed) + attempts.append(QPointS(ax, there.y() + there.height() + spaceY)); + } + + // now sort the list in order of preference + qSort(attempts); + + // see if it fits! + foreach(QPointS attempt, attempts) { + + QRect mine(attempt.x(), + attempt.y(), + item->sizeHint().width()+spaceX, + item->sizeHint().height()+spaceY); + + // doesn't even fit in the boundary + if (mine.x()+mine.width()-spaceX > effectiveRect.right()) continue; + + + // lets assume it will fit and check if it overlaps with existing + bool fits = true; + foreach (QRect here, placed) { + + // add spacing + QRect bigger(here.x(), here.y(), here.width()+spaceY, here.height()+spaceX); + + // overlaps? + if (bigger.intersects(mine)) { + fits = false; + break; + } + } + + // it fits! + if (fits == true) { + x = attempt.x(); + y = attempt.y(); + break; + } + } } - if (!testOnly) - item->setGeometry(QRect(QPoint(x, y), item->sizeHint())); + // its just too big ... fnarr fnarr + // stick it at the bottom + // this can happen when the layout is being called + // to set geometry when width and height are being + // derived + if ((x < 0 || y < 0) && placed.count()) { + int maxy=0; + foreach(QRect geom, placed) { + if (geom.y()+geom.height()+spaceY > maxy) { + maxy = geom.y()+geom.height()+spaceY; + } + } + x = effectiveRect.x(); + y = maxy; + } + + QRect placement(QPoint(x,y), item->sizeHint()); + placed.append(placement); + + if (!testOnly) item->setGeometry(placement); - x = nextX; - lineHeight = qMax(lineHeight, item->sizeHint().height()); } - return y + lineHeight - rect.y() + bottom; + + // Return the length of the layout + int maxy=0; + foreach(QRect geom, placed) { + if (geom.y()+geom.height()+spaceY > maxy) { + maxy = geom.y()+geom.height()+spaceY; + } + } + return maxy; } + int GcWindowLayout::smartSpacing(QStyle::PixelMetric pm) const { QObject *parent = this->parent(); diff --git a/src/GoldenCheetah.cpp b/src/GoldenCheetah.cpp index 4de341fc1..130f77383 100644 --- a/src/GoldenCheetah.cpp +++ b/src/GoldenCheetah.cpp @@ -113,6 +113,15 @@ bool GcWindow::resizable() const return _resizable; } +// if a window moves whilst it is being +// resized we should let go, because the +// geometry tends to warp to stupid or +// tiny sizes as a result +void GcWindow::moveEvent(QMoveEvent *) +{ + if (dragState != Move) setDragState(None); +} + void GcWindow::setGripped(bool x) { _gripped = x; diff --git a/src/GoldenCheetah.h b/src/GoldenCheetah.h index 3b9b6a7a3..211004f19 100644 --- a/src/GoldenCheetah.h +++ b/src/GoldenCheetah.h @@ -135,6 +135,7 @@ public: void setResizable(bool); bool resizable() const; + void moveEvent(QMoveEvent *); // we trap move events to ungrab during resize void setGripped(bool); bool gripped() const;