From b6aacd264dfc2cacd43cd155fa3c772a45b6e8fc Mon Sep 17 00:00:00 2001 From: Mark Liversedge Date: Mon, 3 Jan 2011 19:47:37 +0000 Subject: [PATCH] Allow user to drag/resize tiles on homewindow. --- src/GoldenCheetah.cpp | 306 +++++++++++++++++++++++++++++++++++++++++- src/GoldenCheetah.h | 45 +++++-- src/HomeWindow.cpp | 34 +++-- src/HomeWindow.h | 2 +- 4 files changed, 358 insertions(+), 29 deletions(-) diff --git a/src/GoldenCheetah.cpp b/src/GoldenCheetah.cpp index 9ab97b3ac..fef409f04 100644 --- a/src/GoldenCheetah.cpp +++ b/src/GoldenCheetah.cpp @@ -21,6 +21,8 @@ #include #include #include +#include +#include QWidget *GcWindow::controls() const { @@ -65,33 +67,43 @@ void GcWindow::setRideItem(RideItem* x) emit rideItemChanged(_rideItem); } -int GcWindow::widthFactor() const +double GcWindow::widthFactor() const { return _widthFactor; } -void GcWindow::setWidthFactor(int x) +void GcWindow::setWidthFactor(double x) { _widthFactor = x; emit widthFactorChanged(x); } -int GcWindow::heightFactor() const +double GcWindow::heightFactor() const { return _heightFactor; } -void GcWindow::setHeightFactor(int x) +void GcWindow::setHeightFactor(double x) { _heightFactor = x; emit heightFactorChanged(x); } +void GcWindow::setResizable(bool x) +{ + _resizable = x; +} + +bool GcWindow::resizable() const +{ + return _resizable; +} + GcWindow::GcWindow() { } -GcWindow::GcWindow(QWidget *parent) : QFrame(parent) { +GcWindow::GcWindow(QWidget *parent) : QFrame(parent), dragState(None) { qRegisterMetaType("controls"); qRegisterMetaType("ride"); qRegisterMetaType("type"); @@ -100,6 +112,8 @@ GcWindow::GcWindow(QWidget *parent) : QFrame(parent) { setRideItem(NULL); setTitle(""); setContentsMargins(0,0,0,0); + setResizable(false); + setMouseTracking(true); } GcWindow::~GcWindow() @@ -118,7 +132,7 @@ GcWindow::amVisible() void -GcWindow::paintEvent(QPaintEvent * /*event*/) +GcWindow::paintEvent(QPaintEvent *event) { static QPixmap closeImage = QPixmap(":images/toolbar/popbutton.png"); static QPixmap aluBar = QPixmap(":images/aluBar.png"); @@ -181,3 +195,283 @@ GcWindow::paintEvent(QPaintEvent * /*event*/) } } } + +/*---------------------------------------------------------------------- + * Drag and resize tiles + *--------------------------------------------------------------------*/ + +bool +GcWindow::eventFilter(QObject *, QEvent *e) +{ + if (!resizable()) return false; + + // handle moving / resizing activity + if (dragState != None) { + switch (e->type()) { + case QEvent::MouseMove: + mouseMoveEvent((QMouseEvent*)e); + return false; + break; + case QEvent::MouseButtonRelease: + mouseReleaseEvent((QMouseEvent*)e); + return false; + break; + default: + break; + } + } + return false; +} + +void +GcWindow::mousePressEvent(QMouseEvent *e) +{ + if (!resizable() || e->button() == Qt::NoButton || isHidden()) { + setDragState(None); + return; + } + + DragState h = spotHotSpot(e); + + // is it on the close icon? + if (h == Close) { + setDragState(None); + hide(); + //emit exit(); + return; + } else if (h == Flip) { + setDragState(None); + //flip(); + } + + // get current window state + oWidth = width(); + oHeight = height(); + oWidthFactor = widthFactor(); + oHeightFactor = heightFactor(); + oX = pos().x(); + oY = pos().y(); + mX = e->globalX(); + mY = e->globalY(); + + setDragState(h); // set drag state then! + + repaint(); +} + +void +GcWindow::mouseReleaseEvent(QMouseEvent *) +{ + setDragState(None); + repaint(); +} + +// for the mouse position, are we in a hotspot? +// if so, what would the drag state become if we +// clicked? +GcWindow::DragState +GcWindow::spotHotSpot(QMouseEvent *e) +{ + // corner + int corner = 9; + int borderWidth = 3; + + // account for offset XXX map to GcWindow geom + int _y = e->y(); + int _x = e->x(); + int _height = height(); + int _width = width(); + + if (e->x() > (2 + width() - corner) && e->y() < corner) return (Close); + else if (_x <= corner && _y <= corner) return (TLCorner); + else if (_x >= (_width-corner) && _y <= corner) return (TRCorner); + else if (_x <= corner && _y >= (_height-corner)) return (BLCorner); + else if (_x >= (_width-corner) && _y >= (_height-corner)) return (BRCorner); + else if (_x <= borderWidth) return (Left); + else if (_x >= (_width-borderWidth)) return (Right); + else if (_y <= borderWidth) return (Top); + else if (_y >= (_height-borderWidth)) return (Bottom); + else return (Move); +} + +void +GcWindow::mouseMoveEvent(QMouseEvent *e) +{ + if (!resizable()) return; + + if (dragState == None) { + // set the cursor shape + setCursorShape(spotHotSpot(e)); + return; + } + + // work out the relative move x and y + int relx = e->globalX() - mX; + int rely = e->globalY() - mY; + + switch (dragState) { + + default: + case Move : + move(oX + relx, oY + rely); + break; + + case TLCorner : + { + int newWidth = oWidth - relx; + int newHeight = oHeight - rely; + + // need to move and resize + if (newWidth > 30 && newHeight > 30) { + move(oX + relx, oY + rely); + setNewSize(newWidth, newHeight); + } + } + break; + + case TRCorner : + { + int newWidth = oWidth + relx; + int newHeight = oHeight - rely; + + // need to move and resize if changes on y plane + if (newWidth > 30 && newHeight > 30) { + move(oX, oY + rely); + setNewSize(newWidth, newHeight); + } + } + break; + + case BLCorner : + { + int newWidth = oWidth - relx; + int newHeight = oHeight + rely; + + // need to move and resize + if (newWidth > 30 && newHeight > 30) { + move(oX + relx, oY); + setNewSize(newWidth, newHeight); + } + } + break; + + case BRCorner : + { + int newWidth = oWidth + relx; + int newHeight = oHeight + rely; + + // need to move and resize + if (newWidth > 30 && newHeight > 30) { + setNewSize(newWidth, newHeight); + } + } + break; + + case Top : + { + int newHeight = oHeight - rely; + + // need to move and resize + if (newHeight > 30) { + move (oX, oY + rely); + setNewSize(oWidth, newHeight); + } + } + break; + + case Bottom : + { + int newHeight = oHeight + rely; + + // need to move and resize + if (newHeight > 30) { + setNewSize(oWidth, newHeight); + } + } + break; + + case Left : + { + int newWidth = oWidth - relx; + + // need to move and resize + if (newWidth > 30) { + move (oX + relx, oY); + setNewSize(newWidth, oHeight); + } + } + break; + + case Right : + { + int newWidth = oWidth + relx; + + // need to move and resize + if (newWidth > 30) { + setNewSize(newWidth, oHeight); + } + } + break; + } + + //repaint(); + //QApplication::processEvents(); // flicker... +} + +void +GcWindow::setNewSize(int w, int h) +{ + // convert to height factor + double newHF = (double(oHeight) * oHeightFactor) / double(h); + double newWF = (double(oWidth) * oWidthFactor) / double(w); + + // don't get too big! + if (newHF < 1 || newWF < 1) return; // too big + + // now apply + setFixedSize(QSize(w,h)); + + // adjust factors + setHeightFactor(newHF); + setWidthFactor(newWF); +} + +void +GcWindow::setDragState(DragState d) +{ + dragState = d; + setCursorShape(d); +} + +void +GcWindow::setCursorShape(DragState d) +{ + // set cursor + switch (d) { + case Bottom: + case Top: + setCursor(Qt::SizeVerCursor); + break; + case Left: + case Right: + setCursor(Qt::SizeHorCursor); + break; + case TLCorner: + case BRCorner: + setCursor(Qt::SizeFDiagCursor); + break; + case TRCorner: + case BLCorner: + setCursor(Qt::SizeBDiagCursor); + break; + case Move: + //setCursor(Qt::OpenHandCursor); //XXX sub widgets don't set the cursor... + setCursor(Qt::ArrowCursor); + break; + default: + case Close: + case None: + setCursor(Qt::ArrowCursor); + break; + } +} diff --git a/src/GoldenCheetah.h b/src/GoldenCheetah.h index b24ef8654..9ec5f4c1d 100644 --- a/src/GoldenCheetah.h +++ b/src/GoldenCheetah.h @@ -65,26 +65,37 @@ private: Q_PROPERTY(RideItem* ride READ rideItem WRITE setRideItem NOTIFY rideItemChanged) // geometry factor - Q_PROPERTY(int widthFactor READ widthFactor WRITE setWidthFactor NOTIFY widthFactorChanged USER true); - Q_PROPERTY(int heightFactor READ heightFactor WRITE setHeightFactor NOTIFY widthFactorChanged USER true); + Q_PROPERTY(double widthFactor READ widthFactor WRITE setWidthFactor NOTIFY widthFactorChanged USER true); + Q_PROPERTY(double heightFactor READ heightFactor WRITE setHeightFactor NOTIFY widthFactorChanged USER true); + + // can be resized + Q_PROPERTY(bool resizable READ resizable WRITE setResizable USER true); QWidget *_controls; QString _title; QString _instanceName; RideItem *_rideItem; GcWinID _type; - int _widthFactor; - int _heightFactor; + double _widthFactor; + double _heightFactor; + bool _resizable; // we paint a heading if there is space in the top margin void paintEvent (QPaintEvent * event); + enum drag { None, Close, Flip, Move, Left, Right, Top, Bottom, TLCorner, TRCorner, BLCorner, BRCorner }; + typedef enum drag DragState; + // state data for resizing tiles + DragState dragState; + int oWidth, oHeight, oX, oY, mX, mY; + double oHeightFactor, oWidthFactor; + signals: void controlsChanged(QWidget*); void titleChanged(QString); void rideItemChanged(RideItem*); - void heightFactorChanged(int); - void widthFactorChanged(int); + void heightFactorChanged(double); + void widthFactorChanged(double); public: @@ -105,16 +116,30 @@ public: void setRideItem(RideItem *); RideItem *rideItem() const; - void setWidthFactor(int); - int widthFactor() const; + void setWidthFactor(double); + double widthFactor() const; - void setHeightFactor(int); - int heightFactor() const; + void setHeightFactor(double); + double heightFactor() const; + + void setResizable(bool); + bool resizable() const; GcWinID type() const { return _type; } void setType(GcWinID x) { _type = x; } // only really used by the window registry virtual bool amVisible(); + + // mouse actions -- resizing and dragging tiles + bool eventFilter(QObject *object, QEvent *e); + virtual void mousePressEvent(QMouseEvent *); + virtual void mouseReleaseEvent(QMouseEvent *); + virtual void mouseMoveEvent(QMouseEvent *); + void setDragState(DragState); + void setCursorShape(DragState); + DragState spotHotSpot(QMouseEvent *); + void setNewSize(int w, int h); + }; diff --git a/src/HomeWindow.cpp b/src/HomeWindow.cpp index 5913bed1d..62b4fe422 100644 --- a/src/HomeWindow.cpp +++ b/src/HomeWindow.cpp @@ -238,16 +238,19 @@ HomeWindow::styleChanged(int id) case 0 : // they are tabs in a TabWidget tabbed->addTab(charts[i], charts[i]->property("title").toString()); charts[i]->setContentsMargins(0,0,0,0); + charts[i]->setResizable(false); // we need to show on tab selection! charts[i]->hide(); // we need to show on tab selection! break; case 1 : // they are lists in a GridLayout tileGrid->addWidget(charts[i], i,0); charts[i]->setContentsMargins(0,25,0,0); + charts[i]->setResizable(false); // we need to show on tab selection! charts[i]->show(); break; case 2 : // thet are in a FlowLayout winFlow->addWidget(charts[i]); charts[i]->setContentsMargins(0,15,0,0); + charts[i]->setResizable(true); // we need to show on tab selection! charts[i]->show(); default: break; @@ -327,6 +330,7 @@ HomeWindow::addChart(GcWindow* newone) case 0 : newone->setContentsMargins(0,0,0,0); + newone->setResizable(false); // we need to show on tab selection! tabbed->addTab(newone, newone->property("title").toString()); break; case 1 : @@ -335,6 +339,7 @@ HomeWindow::addChart(GcWindow* newone) // set geometry newone->setFixedWidth((tileArea->width()-50)); newone->setFixedHeight(newone->width() * 0.7); + newone->setResizable(false); // we need to show on tab selection! int row = chartnum; // / 2; int column = 0; //chartnum % 2; newone->setContentsMargins(0,25,0,0); @@ -343,18 +348,18 @@ HomeWindow::addChart(GcWindow* newone) break; case 2 : { - int heightFactor = newone->property("heightFactor").toInt(); - int widthFactor = newone->property("widthFactor").toInt(); + double heightFactor = newone->property("heightFactor").toDouble(); + double widthFactor = newone->property("widthFactor").toDouble(); // width of area minus content margins and spacing around each item // divided by the number of items - int newwidth = (winArea->width() - 20 /* scrollbar */ + double newwidth = (winArea->width() - 20 /* scrollbar */ - 40 /* left and right marings */ - ((widthFactor-1) * 20) /* internal spacing */ ) / widthFactor; - int newheight = (winArea->height() + double newheight = (winArea->height() - 40 /* top and bottom marings */ - ((heightFactor-1) * 20) /* internal spacing */ ) / heightFactor; @@ -366,6 +371,7 @@ HomeWindow::addChart(GcWindow* newone) if (newheight < minHeight) newheight = minHeight; else newone->setFixedHeight(newheight); newone->setContentsMargins(0,15,0,0); + newone->setResizable(true); // we need to show on tab selection! winFlow->addWidget(newone); } break; @@ -422,7 +428,7 @@ HomeWindow::removeChart(int num) } void -HomeWindow::resizeEvent(QResizeEvent *) +HomeWindow::resizeEvent(QResizeEvent *e) { foreach (GcWindow *x, charts) { @@ -440,16 +446,16 @@ HomeWindow::resizeEvent(QResizeEvent *) case 2 : // flow { - int heightFactor = x->property("heightFactor").toInt(); - int widthFactor = x->property("widthFactor").toInt(); + double heightFactor = x->property("heightFactor").toDouble(); + double widthFactor = x->property("widthFactor").toDouble(); - int newwidth = (winArea->width() - 20 /* scrollbar */ + double newwidth = (winArea->width() - 20 /* scrollbar */ - 40 /* left and right marings */ - ((widthFactor-1) * 20) /* internal spacing */ ) / widthFactor; - int newheight = (winArea->height() + double newheight = (winArea->height() - 40 /* top and bottom marings */ - ((heightFactor-1) * 20) /* internal spacing */ ) / heightFactor; @@ -534,7 +540,7 @@ HomeWindow::eventFilter(QObject *object, QEvent *e) clicked = NULL; } } - return true; + return false; } } } @@ -565,11 +571,11 @@ GcWindowDialog::GcWindowDialog(GcWinID type, MainWindow *mainWindow) : mainWindo chartLayout->addWidget(win); controlLayout = new QFormLayout; - height=new QSpinBox(this); + height=new QDoubleSpinBox(this); height->setRange(1,10); height->setSingleStep(1); height->setValue(2); - width=new QSpinBox(this); + width=new QDoubleSpinBox(this); width->setRange(1,10); width->setSingleStep(1); width->setValue(2); @@ -625,6 +631,9 @@ GcWindowDialog::exec() } else return NULL; } +/*---------------------------------------------------------------------- + * Save and restore state (xxxx-layout.xml) + *--------------------------------------------------------------------*/ // static helper to protect special xml characters // ideally we would use XMLwriter to do this but // the file format is trivial and this implementation @@ -820,3 +829,4 @@ bool ViewParser::endDocument() { return TRUE; } + diff --git a/src/HomeWindow.h b/src/HomeWindow.h index b3a9e4d6a..0de19cde0 100644 --- a/src/HomeWindow.h +++ b/src/HomeWindow.h @@ -128,7 +128,7 @@ class GcWindowDialog : public QDialog QPushButton *ok, *cancel; GcWindow *win; QLineEdit *title; - QSpinBox *height, *width; + QDoubleSpinBox *height, *width; }; class ViewParser : public QXmlDefaultHandler