mirror of
https://github.com/GoldenCheetah/GoldenCheetah.git
synced 2026-04-15 05:32:21 +00:00
.. drag animation for tile shrink is very slow on my MBP. Trying to get to the bottom of why. It may be a HW issue with my PC.
1299 lines
43 KiB
C++
1299 lines
43 KiB
C++
/*
|
|
* Copyright (c) 2017 Mark Liversedge (liversedge@gmail.com)
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU 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 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 "OverviewWindow.h"
|
|
|
|
#include "TabView.h"
|
|
#include "Athlete.h"
|
|
#include "RideCache.h"
|
|
|
|
#include "Zones.h"
|
|
#include "HrZones.h"
|
|
#include "PaceZones.h"
|
|
|
|
#include <QGraphicsSceneMouseEvent>
|
|
|
|
OverviewWindow::OverviewWindow(Context *context) :
|
|
GcChartWindow(context), mode(CONFIG), state(NONE), context(context), group(NULL), _viewY(0),
|
|
yresizecursor(false), xresizecursor(false), block(false), scrolling(false),
|
|
setscrollbar(false), lasty(-1)
|
|
{
|
|
setContentsMargins(0,0,0,0);
|
|
setProperty("color", GColor(COVERVIEWBACKGROUND));
|
|
setProperty("nomenu", true);
|
|
setShowTitle(false);
|
|
setControls(NULL);
|
|
|
|
QHBoxLayout *main = new QHBoxLayout;
|
|
|
|
// add a view and scene and centre
|
|
scene = new QGraphicsScene(this);
|
|
view = new QGraphicsView(this);
|
|
scrollbar = new QScrollBar(Qt::Vertical, this);
|
|
|
|
// how to move etc
|
|
//view->setDragMode(QGraphicsView::ScrollHandDrag);
|
|
view->setRenderHint(QPainter::Antialiasing, true);
|
|
view->setFrameStyle(QFrame::NoFrame);
|
|
view->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
|
view->setScene(scene);
|
|
|
|
// layout
|
|
main->addWidget(view);
|
|
main->addWidget(scrollbar);
|
|
|
|
// all the widgets
|
|
setChartLayout(main);
|
|
|
|
// default column widths - max 10 columns;
|
|
// note the sizing is such that each card is the equivalent of a full screen
|
|
// so we can embed charts etc without compromising what they can display
|
|
columns << 1200 << 1200 << 1200 << 1200 << 1200 << 1200 << 1200 << 1200 << 1200 << 1200;
|
|
|
|
// XXX lets hack in some tiles to start (will load from config later) XXX
|
|
|
|
// column 0
|
|
newCard("Sport", 0, 0, 5, Card::META, "Sport");
|
|
newCard("Duration", 0, 1, 5, Card::METRIC, "workout_time");
|
|
newCard("Route", 0, 2, 10);
|
|
newCard("Distance", 0, 3, 9, Card::METRIC, "total_distance");
|
|
newCard("Climbing", 0, 4, 5, Card::METRIC, "elevation_gain");
|
|
newCard("Speed", 0, 6, 5, Card::METRIC, "average_speed");
|
|
|
|
// column 1
|
|
newCard("Heartrate", 1, 0, 9, Card::METRIC, "average_hr");
|
|
newCard("HRV", 1, 1, 5);
|
|
newCard("Heartrate Zones", 1, 2, 10, Card::ZONE, RideFile::hr);
|
|
newCard("Pace Zones", 1, 3, 11, Card::ZONE, RideFile::kph);
|
|
newCard("Cadence", 1, 4, 5, Card::METRIC, "average_cad");
|
|
|
|
// column 2
|
|
newCard("RPE", 2, 0, 5, Card::META, "RPE");
|
|
newCard("Stress", 2, 1, 9, Card::METRIC, "coggan_tss");
|
|
newCard("W'bal Zones", 2, 2, 10, Card::ZONE, RideFile::wbal);
|
|
newCard("Intervals", 2, 3, 17);
|
|
|
|
// column 3
|
|
newCard("Power", 3, 0, 9, Card::METRIC, "average_power");
|
|
newCard("Intensity", 3, 1, 5, Card::METRIC, "coggan_if");
|
|
newCard("Power Zones", 3, 2, 10, Card::ZONE, RideFile::watts);
|
|
newCard("Equivalent Power", 3, 3, 5, Card::METRIC, "coggan_np");
|
|
newCard("Power Model", 3, 4, 11);
|
|
|
|
// for changing the view
|
|
group = new QParallelAnimationGroup(this);
|
|
viewchange = new QPropertyAnimation(this, "viewRect");
|
|
viewchange->setEasingCurve(QEasingCurve(QEasingCurve::OutQuint));
|
|
|
|
// for scrolling the view
|
|
scroller = new QPropertyAnimation(this, "viewY");
|
|
scroller->setEasingCurve(QEasingCurve(QEasingCurve::Linear));
|
|
|
|
// sort out the view
|
|
updateGeometry();
|
|
|
|
// watch the view for mouse events
|
|
view->setMouseTracking(true);
|
|
scene->installEventFilter(this);
|
|
|
|
// once all widgets created we can connect the signals
|
|
connect(context, SIGNAL(configChanged(qint32)), this, SLOT(configChanged(qint32)));
|
|
connect(scroller, SIGNAL(finished()), this, SLOT(scrollFinished()));
|
|
connect(scrollbar, SIGNAL(valueChanged(int)), this, SLOT(scrollbarMoved(int)));
|
|
connect(this, SIGNAL(rideItemChanged(RideItem*)), this, SLOT(rideSelected()));
|
|
|
|
// set the widgets etc
|
|
configChanged(CONFIG_APPEARANCE);
|
|
}
|
|
|
|
// when a ride is selected we need to notify all the cards
|
|
void
|
|
OverviewWindow::rideSelected()
|
|
{
|
|
// ride item changed
|
|
foreach(Card *card, cards) card->setData(myRideItem);
|
|
|
|
// update
|
|
updateView();
|
|
}
|
|
|
|
// configure the cards
|
|
void
|
|
Card::setType(CardType type, RideFile::SeriesType series)
|
|
{
|
|
this->type = type;
|
|
settings.series = series;
|
|
|
|
// basic chart setup
|
|
chart = new QChart(this);
|
|
chart->setBackgroundVisible(false); // draw on canvas
|
|
chart->legend()->setVisible(false); // no legends
|
|
chart->setTitle(""); // none wanted
|
|
chart->setAnimationOptions(QChart::NoAnimation);
|
|
|
|
// we have a mid sized font for chart labels etc
|
|
QFont mid;
|
|
#ifdef Q_OS_MAC
|
|
mid.setPointSize(double(ROWHEIGHT) * 0.75f);
|
|
#else
|
|
mid.setPointSize(ROWHEIGHT/2);
|
|
#endif
|
|
chart->setFont(mid);
|
|
|
|
if (type == Card::ZONE) {
|
|
|
|
// needs a set of bars
|
|
barset = new QBarSet(tr("Time In Zone"), this);
|
|
barset->setLabelFont(mid);
|
|
|
|
if (settings.series == RideFile::hr) {
|
|
barset->setLabelColor(GColor(CHEARTRATE));
|
|
barset->setBorderColor(GColor(CHEARTRATE));
|
|
barset->setBrush(GColor(CHEARTRATE));
|
|
} else if (settings.series == RideFile::watts) {
|
|
barset->setLabelColor(GColor(CPOWER));
|
|
barset->setBorderColor(GColor(CPOWER));
|
|
barset->setBrush(GColor(CPOWER));
|
|
} else if (settings.series == RideFile::wbal) {
|
|
barset->setLabelColor(GColor(CWBAL));
|
|
barset->setBorderColor(GColor(CWBAL));
|
|
barset->setBrush(GColor(CWBAL));
|
|
} else if (settings.series == RideFile::kph) {
|
|
barset->setLabelColor(GColor(CSPEED));
|
|
barset->setBorderColor(GColor(CSPEED));
|
|
barset->setBrush(GColor(CSPEED));
|
|
}
|
|
|
|
|
|
//
|
|
// HEARTRATE
|
|
//
|
|
if (series == RideFile::hr && parent->context->athlete->hrZones(false)) {
|
|
// set the zero values
|
|
for(int i=0; i<parent->context->athlete->hrZones(false)->getScheme().nzones_default; i++) {
|
|
*barset << 0;
|
|
categories << parent->context->athlete->hrZones(false)->getScheme().zone_default_name[i];
|
|
}
|
|
}
|
|
|
|
//
|
|
// POWER
|
|
//
|
|
if (series == RideFile::watts && parent->context->athlete->zones(false)) {
|
|
// set the zero values
|
|
for(int i=0; i<parent->context->athlete->zones(false)->getScheme().nzones_default; i++) {
|
|
*barset << 0;
|
|
categories << parent->context->athlete->zones(false)->getScheme().zone_default_name[i];
|
|
}
|
|
}
|
|
|
|
//
|
|
// PACE
|
|
//
|
|
if (series == RideFile::kph && parent->context->athlete->paceZones(false)) {
|
|
// set the zero values
|
|
for(int i=0; i<parent->context->athlete->paceZones(false)->getScheme().nzones_default; i++) {
|
|
*barset << 0;
|
|
categories << parent->context->athlete->paceZones(false)->getScheme().zone_default_name[i];
|
|
}
|
|
}
|
|
|
|
//
|
|
// W'BAL
|
|
//
|
|
if (series == RideFile::wbal) {
|
|
categories << "Low" << "Med" << "High" << "Ext";
|
|
*barset << 0 << 0 << 0 << 0;
|
|
}
|
|
|
|
// bar series and categories setup, same for all
|
|
barseries = new QBarSeries(this);
|
|
barseries->setLabelsPosition(QAbstractBarSeries::LabelsOutsideEnd);
|
|
barseries->setLabelsVisible(true);
|
|
barseries->setLabelsFormat("@value %");
|
|
barseries->append(barset);
|
|
chart->addSeries(barseries);
|
|
|
|
|
|
// x-axis labels etc
|
|
barcategoryaxis = new QBarCategoryAxis(this);
|
|
barcategoryaxis->setLabelsFont(mid);
|
|
barcategoryaxis->setLabelsColor(QColor(100,100,100));
|
|
barcategoryaxis->setGridLineVisible(false);
|
|
barcategoryaxis->setCategories(categories);
|
|
|
|
// config axes
|
|
QPen axisPen(GColor(CCARDBACKGROUND));
|
|
axisPen.setWidth(0.5); // almost invisibke
|
|
chart->createDefaultAxes();
|
|
chart->setAxisX(barcategoryaxis, barseries);
|
|
barcategoryaxis->setLinePen(axisPen);
|
|
barcategoryaxis->setLineVisible(false);
|
|
chart->axisY(barseries)->setLinePen(axisPen);
|
|
chart->axisY(barseries)->setLineVisible(false);
|
|
chart->axisY(barseries)->setLabelsVisible(false);
|
|
chart->axisY(barseries)->setRange(0,100);
|
|
chart->axisY(barseries)->setGridLineVisible(false);
|
|
}
|
|
}
|
|
|
|
void
|
|
Card::setType(CardType type, QString symbol)
|
|
{
|
|
// metric or meta
|
|
this->type = type;
|
|
settings.symbol = symbol;
|
|
|
|
// we may plot the metric sparkline if the tile is big enough
|
|
if (type == METRIC) {
|
|
chart = new QChart(this);
|
|
|
|
// usual setup so no background or legend
|
|
chart->setBackgroundVisible(false); // draw on canvas
|
|
chart->legend()->setVisible(false); // no legends
|
|
chart->setTitle(""); // none wanted
|
|
chart->setAnimationOptions(QChart::NoAnimation);
|
|
|
|
// line series shows last 10 rides
|
|
QPen pen(QColor(200,200,200));
|
|
pen.setWidth(15);
|
|
lineseries = new QLineSeries(this);
|
|
lineseries->setPen(pen);
|
|
for(int i=0; i<SPARKDAYS+1; i++) lineseries->append(QPointF(i,0));
|
|
chart->addSeries(lineseries);
|
|
|
|
me = new QScatterSeries(this);
|
|
me->append(SPARKDAYS,0);
|
|
me->setMarkerShape(QScatterSeries::MarkerShapeCircle);
|
|
me->setMarkerSize(50);
|
|
me->setColor(GColor(CPLOTMARKER));
|
|
chart->addSeries(me);
|
|
chart->createDefaultAxes();
|
|
|
|
// set axis, and then hide it!
|
|
chart->axisX(lineseries)->setLineVisible(false);
|
|
chart->axisX(lineseries)->setLabelsVisible(false);
|
|
chart->axisX(lineseries)->setGridLineVisible(false);
|
|
chart->axisX(lineseries)->setRange(0,SPARKDAYS+5);
|
|
chart->axisY(lineseries)->setLineVisible(false);
|
|
chart->axisY(lineseries)->setLabelsVisible(false);
|
|
chart->axisY(lineseries)->setGridLineVisible(false);
|
|
chart->axisY(lineseries)->setRange(-25,250);
|
|
|
|
chart->axisY(me)->setRange(-25,250);
|
|
chart->axisX(me)->setRange(0,SPARKDAYS+5);
|
|
}
|
|
}
|
|
|
|
static const QStringList timeInZones = QStringList()
|
|
<< "percent_in_zone_L1"
|
|
<< "percent_in_zone_L2"
|
|
<< "percent_in_zone_L3"
|
|
<< "percent_in_zone_L4"
|
|
<< "percent_in_zone_L5"
|
|
<< "percent_in_zone_L6"
|
|
<< "percent_in_zone_L7"
|
|
<< "percent_in_zone_L8"
|
|
<< "percent_in_zone_L9"
|
|
<< "percent_in_zone_L10";
|
|
|
|
static const QStringList paceTimeInZones = QStringList()
|
|
<< "percent_in_zone_P1"
|
|
<< "percent_in_zone_P2"
|
|
<< "percent_in_zone_P3"
|
|
<< "percent_in_zone_P4"
|
|
<< "percent_in_zone_P5"
|
|
<< "percent_in_zone_P6"
|
|
<< "percent_in_zone_P7"
|
|
<< "percent_in_zone_P8"
|
|
<< "percent_in_zone_P9"
|
|
<< "percent_in_zone_P10";
|
|
|
|
static const QStringList timeInZonesHR = QStringList()
|
|
<< "percent_in_zone_H1"
|
|
<< "percent_in_zone_H2"
|
|
<< "percent_in_zone_H3"
|
|
<< "percent_in_zone_H4"
|
|
<< "percent_in_zone_H5"
|
|
<< "percent_in_zone_H6"
|
|
<< "percent_in_zone_H7"
|
|
<< "percent_in_zone_H8"
|
|
<< "percent_in_zone_H9"
|
|
<< "percent_in_zone_H10";
|
|
|
|
static const QStringList timeInZonesWBAL = QStringList()
|
|
<< "wtime_in_zone_L1"
|
|
<< "wtime_in_zone_L2"
|
|
<< "wtime_in_zone_L3"
|
|
<< "wtime_in_zone_L4";
|
|
|
|
void
|
|
Card::setData(RideItem *item)
|
|
{
|
|
if (type == METRIC) {
|
|
|
|
// get last 30 days, if they exist
|
|
QList<QPointF> points;
|
|
|
|
// include current activity value
|
|
value = item->getStringForSymbol(settings.symbol, parent->context->athlete->useMetricUnits);
|
|
me->replace(0, SPARKDAYS, value.toDouble());
|
|
points << QPointF(SPARKDAYS, value.toDouble());
|
|
|
|
// set the chart values with the last 10 rides
|
|
int index = parent->context->athlete->rideCache->rides().indexOf(item);
|
|
|
|
// enable animation when setting values (disabled at all other times)
|
|
chart->setAnimationOptions(QChart::SeriesAnimations);
|
|
|
|
int offset = 1;
|
|
min = value.toDouble();
|
|
max = value.toDouble();
|
|
while(index-offset >=0) { // ultimately go no further back than first ever ride
|
|
|
|
// get value from items before me
|
|
RideItem *prior = parent->context->athlete->rideCache->rides().at(index-offset);
|
|
|
|
// are we still in range ?
|
|
int old= prior->dateTime.daysTo(item->dateTime);
|
|
if (old > SPARKDAYS) break;
|
|
|
|
double v = prior->getStringForSymbol(settings.symbol, parent->context->athlete->useMetricUnits).toDouble();
|
|
|
|
// new no zero value
|
|
if (v) {
|
|
points<<QPointF(SPARKDAYS-old, v);
|
|
if (v < min) min = v;
|
|
if (v > max) max = v;
|
|
}
|
|
offset++;
|
|
}
|
|
|
|
// add some space, if only one value +/- 10%
|
|
double diff = (max-min)/10.0f;
|
|
showrange=true;
|
|
if (diff==0) {
|
|
showrange=false;
|
|
diff = value.toDouble()/10.0f;
|
|
}
|
|
|
|
// update the sparkline
|
|
lineseries->replace(points);
|
|
|
|
// set range
|
|
chart->axisY(lineseries)->setRange(min-diff,max+diff); // add 10% to each direction
|
|
chart->axisY(me)->setRange(min-diff,max+diff); // add 10% to each direction
|
|
}
|
|
|
|
if (type == META) {
|
|
value = item->getText(settings.symbol, "");
|
|
}
|
|
|
|
if (type == ZONE) {
|
|
|
|
// enable animation when setting values (disabled at all other times)
|
|
chart->setAnimationOptions(QChart::SeriesAnimations);
|
|
|
|
switch(settings.series) {
|
|
|
|
//
|
|
// HEARTRATE
|
|
//
|
|
case RideFile::hr:
|
|
{
|
|
if (parent->context->athlete->hrZones(item->isRun)) {
|
|
|
|
int numhrzones;
|
|
int hrrange = parent->context->athlete->hrZones(item->isRun)->whichRange(item->dateTime.date());
|
|
|
|
if (hrrange > -1) {
|
|
|
|
numhrzones = parent->context->athlete->hrZones(item->isRun)->numZones(hrrange);
|
|
for(int i=0; i<categories.count() && i < numhrzones;i++) {
|
|
barset->replace(i, round(item->getForSymbol(timeInZonesHR[i])));
|
|
}
|
|
|
|
} else {
|
|
|
|
for(int i=0; i<5; i++) barset->replace(i, 0);
|
|
}
|
|
|
|
} else {
|
|
|
|
for(int i=0; i<5; i++) barset->replace(i, 0);
|
|
}
|
|
}
|
|
break;
|
|
|
|
//
|
|
// POWER
|
|
//
|
|
default:
|
|
case RideFile::watts:
|
|
{
|
|
if (parent->context->athlete->zones(item->isRun)) {
|
|
|
|
int numzones;
|
|
int range = parent->context->athlete->hrZones(item->isRun)->whichRange(item->dateTime.date());
|
|
|
|
if (range > -1) {
|
|
|
|
numzones = parent->context->athlete->zones(item->isRun)->numZones(range);
|
|
for(int i=0; i<categories.count() && i < numzones;i++) {
|
|
barset->replace(i, round(item->getForSymbol(timeInZones[i])));
|
|
}
|
|
|
|
} else {
|
|
|
|
for(int i=0; i<5; i++) barset->replace(i, 0);
|
|
}
|
|
|
|
} else {
|
|
|
|
for(int i=0; i<5; i++) barset->replace(i, 0);
|
|
}
|
|
}
|
|
break;
|
|
|
|
//
|
|
// PACE
|
|
//
|
|
case RideFile::kph:
|
|
{
|
|
if ((item->isRun || item->isSwim) && parent->context->athlete->paceZones(item->isSwim)) {
|
|
|
|
int numzones;
|
|
int range = parent->context->athlete->paceZones(item->isSwim)->whichRange(item->dateTime.date());
|
|
|
|
if (range > -1) {
|
|
|
|
numzones = parent->context->athlete->paceZones(item->isSwim)->numZones(range);
|
|
for(int i=0; i<categories.count() && i < numzones;i++) {
|
|
barset->replace(i, round(item->getForSymbol(paceTimeInZones[i])));
|
|
}
|
|
|
|
} else {
|
|
|
|
for(int i=0; i<5; i++) barset->replace(i, 0);
|
|
}
|
|
|
|
} else {
|
|
|
|
for(int i=0; i<5; i++) barset->replace(i, 0);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case RideFile::wbal:
|
|
{
|
|
// get total time in zones
|
|
double sum=0;
|
|
for(int i=0; i<4; i++) sum += round(item->getForSymbol(timeInZonesWBAL[i]));
|
|
|
|
// update as percent of total
|
|
for(int i=0; i<4; i++) {
|
|
double time =round(item->getForSymbol(timeInZonesWBAL[i]));
|
|
if (time > 0 && sum > 0) barset->replace(i, round((time/sum) * 100));
|
|
else barset->replace(i, 0);
|
|
}
|
|
}
|
|
break;
|
|
|
|
} // switch
|
|
}
|
|
}
|
|
|
|
void
|
|
Card::setDrag(bool x)
|
|
{
|
|
drag = x;
|
|
|
|
// hide stuff
|
|
if (drag && chart) chart->hide();
|
|
if (!drag) geometryChanged();
|
|
}
|
|
|
|
void
|
|
Card::geometryChanged() {
|
|
|
|
QRectF geom = geometry();
|
|
|
|
// if we contain charts etc lets update their geom
|
|
if ((type == ZONE || type == SERIES) && chart) {
|
|
|
|
if (!drag) chart->show();
|
|
// disable animation when changing geometry
|
|
chart->setAnimationOptions(QChart::NoAnimation);
|
|
chart->setGeometry(20,20+(ROWHEIGHT*2), geom.width()-40, geom.height()-(40+(ROWHEIGHT*2)));
|
|
}
|
|
|
|
if (type == METRIC) {
|
|
|
|
// disable animation when changing geometry
|
|
chart->setAnimationOptions(QChart::NoAnimation);
|
|
|
|
// space enough?
|
|
if (!drag && geom.height() > (ROWHEIGHT*6)) {
|
|
chart->show();
|
|
chart->setGeometry(20, ROWHEIGHT*4, geom.width()-40, geom.height()-20-(ROWHEIGHT*4));
|
|
} else {
|
|
chart->hide();
|
|
}
|
|
}
|
|
}
|
|
|
|
// cards need to show they are in config mode
|
|
void
|
|
Card::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) {
|
|
painter->setBrush(brush);
|
|
QPainterPath path;
|
|
path.addRoundedRect(QRectF(0,0,geometry().width(),geometry().height()), ROWHEIGHT/5, ROWHEIGHT/5);
|
|
painter->setPen(Qt::NoPen);
|
|
painter->fillPath(path, brush.color());
|
|
painter->drawPath(path);
|
|
painter->setPen(GColor(CPLOTGRID));
|
|
//XXXpainter->drawLine(QLineF(0,ROWHEIGHT*2,geometry().width(),ROWHEIGHT*2));
|
|
//painter->fillRect(QRectF(0,0,geometry().width()+1,geometry().height()+1), brush);
|
|
QFont titlefont;
|
|
titlefont.setPointSize(ROWHEIGHT-18); // need a bit of space
|
|
//titlefont.setWeight(QFont::Bold);
|
|
painter->setPen(QColor(200,200,200));
|
|
painter->setFont(titlefont);
|
|
painter->drawText(QPointF(ROWHEIGHT /2.0f, QFontMetrics(titlefont, parent->device()).height()), name);
|
|
|
|
// only paint contents if not dragging
|
|
if (drag) return;
|
|
|
|
if (type == METRIC || type == META) {
|
|
|
|
// we need the metric units
|
|
if (type == METRIC && metric == NULL) {
|
|
// get the metric details
|
|
RideMetricFactory &factory = RideMetricFactory::instance();
|
|
metric = const_cast<RideMetric*>(factory.rideMetric(settings.symbol));
|
|
if (metric) units = metric->units(parent->context->athlete->useMetricUnits);
|
|
}
|
|
|
|
// paint the value in the middle using a font 2xROWHEIGHT
|
|
QFont bigfont;
|
|
#ifdef Q_OS_MAC
|
|
bigfont.setPointSize(double(ROWHEIGHT)*2.5f);
|
|
#else
|
|
bigfont.setPointSize(ROWHEIGHT*2);
|
|
#endif
|
|
painter->setPen(GColor(CPLOTMARKER));
|
|
painter->setFont(bigfont);
|
|
|
|
QFont smallfont;
|
|
#ifdef Q_OS_MAC
|
|
smallfont.setPointSize(ROWHEIGHT);
|
|
#else
|
|
smallfont.setPointSize(ROWHEIGHT*0.6f);
|
|
#endif
|
|
|
|
double addy = 0;
|
|
if (units != "" && units != tr("seconds")) addy = QFontMetrics(smallfont).height();
|
|
|
|
// mid is slightly higher to account for space around title, move mid up
|
|
double mid = (ROWHEIGHT*1.5f) + ((geometry().height() - (ROWHEIGHT*2)) / 2.0f) - (addy/2);
|
|
|
|
// if we're deep enough to show the sparkline then stop
|
|
if (geometry().height() > (ROWHEIGHT*6)) mid=((ROWHEIGHT*1.5f) + (ROWHEIGHT*3) / 2.0f) - (addy/2);
|
|
|
|
// we align centre and mid
|
|
QFontMetrics fm(bigfont);
|
|
QRectF rect = QFontMetrics(bigfont, parent->device()).boundingRect(value);
|
|
|
|
painter->drawText(QPointF((geometry().width() - rect.width()) / 2.0f,
|
|
mid + (fm.ascent() / 3.0f)), value); // divided by 3 to account for "gap" at top of font
|
|
|
|
// now units
|
|
if (addy > 0) {
|
|
painter->setPen(QColor(100,100,100));
|
|
painter->setFont(smallfont);
|
|
|
|
painter->drawText(QPointF((geometry().width() - QFontMetrics(smallfont).width(units)) / 2.0f,
|
|
mid + (fm.ascent() / 3.0f) + addy), units); // divided by 3 to account for "gap" at top of font
|
|
}
|
|
|
|
// paint the range if the chart is shown
|
|
if (showrange && chart->isVisible()) {
|
|
// in small font max min at top bottom right of chart
|
|
double top = chart->geometry().top();
|
|
double bottom = chart->geometry().bottom();
|
|
double right = chart->geometry().right();
|
|
|
|
painter->setPen(QColor(100,100,100));
|
|
painter->setFont(smallfont);
|
|
|
|
QString upper=QString("%1").arg(max);
|
|
QString lower=QString("%1").arg(min);
|
|
painter->drawText(QPointF(right - QFontMetrics(smallfont).width(upper) - 40,
|
|
top - 40 + (fm.ascent() / 3.0f)), upper);
|
|
painter->drawText(QPointF(right - QFontMetrics(smallfont).width(lower) - 40,
|
|
bottom -40), lower);
|
|
}
|
|
}
|
|
}
|
|
|
|
static bool cardSort(const Card* left, const Card* right)
|
|
{
|
|
return (left->column < right->column ? true : (left->column == right->column && left->order < right->order ? true : false));
|
|
}
|
|
|
|
void
|
|
OverviewWindow::updateGeometry()
|
|
{
|
|
bool animated=false;
|
|
|
|
// prevent a memory leak
|
|
group->stop();
|
|
delete group;
|
|
group = new QParallelAnimationGroup(this);
|
|
|
|
// order the items to their positions
|
|
qSort(cards.begin(), cards.end(), cardSort);
|
|
|
|
int y=SPACING;
|
|
int maxy = y;
|
|
int column=-1;
|
|
|
|
int x=SPACING;
|
|
|
|
// just set their geometry for now, no interaction
|
|
for(int i=0; i<cards.count(); i++) {
|
|
|
|
// don't show hidden
|
|
if (!cards[i]->isVisible()) continue;
|
|
|
|
// move on to next column, check if first item too
|
|
if (cards[i]->column > column) {
|
|
|
|
// once past the first column we need to update x
|
|
if (column >= 0) x+= columns[column] + SPACING;
|
|
|
|
int diff = cards[i]->column - column - 1;
|
|
if (diff > 0) {
|
|
|
|
// there are empty columns so shift the cols to the right
|
|
// to the left to fill the gap left and all the column
|
|
// widths also need to move down too
|
|
for(int j=cards[i]->column-1; j < 8; j++) columns[j]=columns[j+1];
|
|
for(int j=i; j<cards.count();j++) cards[j]->column -= diff;
|
|
}
|
|
y=SPACING; column = cards[i]->column;
|
|
|
|
}
|
|
|
|
// set geometry
|
|
int ty = y;
|
|
int tx = x;
|
|
int twidth = columns[column];
|
|
int theight = cards[i]->deep * ROWHEIGHT;
|
|
|
|
// make em smaller when configuring visual cue stolen from Windows Start Menu
|
|
int add = (state == DRAG) ? (ROWHEIGHT/2) : 0;
|
|
|
|
|
|
// for setting the scene rectangle - but ignore a card if we are dragging it
|
|
if (maxy < ty+theight+SPACING) maxy = ty+theight+SPACING;
|
|
|
|
// add to scene if new
|
|
if (!cards[i]->onscene) {
|
|
cards[i]->setGeometry(tx, ty, twidth, theight);
|
|
scene->addItem(cards[i]);
|
|
cards[i]->onscene = true;
|
|
|
|
} else if (cards[i]->invisible == false &&
|
|
(cards[i]->geometry().x() != tx+add ||
|
|
cards[i]->geometry().y() != ty+add ||
|
|
cards[i]->geometry().width() != twidth-(add*2) ||
|
|
cards[i]->geometry().height() != theight-(add*2))) {
|
|
|
|
// we've got an animation to perform
|
|
animated = true;
|
|
|
|
// add an animation for this movement
|
|
QPropertyAnimation *animation = new QPropertyAnimation(cards[i], "geometry");
|
|
animation->setDuration(300);
|
|
animation->setStartValue(cards[i]->geometry());
|
|
animation->setEndValue(QRect(tx+add,ty+add,twidth-(add*2),theight-(add*2)));
|
|
|
|
// when placing a little feedback helps
|
|
if (cards[i]->placing) {
|
|
animation->setEasingCurve(QEasingCurve(QEasingCurve::OutBack));
|
|
cards[i]->placing = false;
|
|
} else animation->setEasingCurve(QEasingCurve(QEasingCurve::OutQuint));
|
|
|
|
group->addAnimation(animation);
|
|
}
|
|
|
|
// set spot for next tile
|
|
y += theight + SPACING;
|
|
}
|
|
|
|
// set the scene rectangle, columns start at 0
|
|
sceneRect = QRectF(0, 0, columns[column] + x + SPACING, maxy);
|
|
|
|
if (animated) group->start();
|
|
}
|
|
|
|
void
|
|
OverviewWindow::configChanged(qint32)
|
|
{
|
|
setProperty("color", GColor(COVERVIEWBACKGROUND));
|
|
view->setBackgroundBrush(QBrush(GColor(COVERVIEWBACKGROUND)));
|
|
scene->setBackgroundBrush(QBrush(GColor(COVERVIEWBACKGROUND)));
|
|
scrollbar->setStyleSheet(TabView::ourStyleSheet());
|
|
|
|
// text edit colors
|
|
QPalette palette;
|
|
palette.setColor(QPalette::Window, GColor(COVERVIEWBACKGROUND));
|
|
palette.setColor(QPalette::Background, GColor(COVERVIEWBACKGROUND));
|
|
|
|
// only change base if moved away from white plots
|
|
// which is a Mac thing
|
|
#ifndef Q_OS_MAC
|
|
if (GColor(COVERVIEWBACKGROUND) != Qt::white)
|
|
#endif
|
|
{
|
|
//palette.setColor(QPalette::Base, GCColor::alternateColor(GColor(CTRAINPLOTBACKGROUND)));
|
|
palette.setColor(QPalette::Base, GColor(COVERVIEWBACKGROUND));
|
|
palette.setColor(QPalette::Window, GColor(COVERVIEWBACKGROUND));
|
|
}
|
|
|
|
#ifndef Q_OS_MAC // the scrollers appear when needed on Mac, we'll keep that
|
|
//code->setStyleSheet(TabView::ourStyleSheet());
|
|
#endif
|
|
|
|
palette.setColor(QPalette::WindowText, GCColor::invertColor(GColor(COVERVIEWBACKGROUND)));
|
|
palette.setColor(QPalette::Text, GCColor::invertColor(GColor(COVERVIEWBACKGROUND)));
|
|
//code->setPalette(palette);
|
|
repaint();
|
|
}
|
|
|
|
void
|
|
OverviewWindow::updateView()
|
|
{
|
|
scene->setSceneRect(sceneRect);
|
|
scene->update();
|
|
|
|
// don'r scale whilst resizing on x?
|
|
if (scrolling || (state != YRESIZE && state != XRESIZE && state != DRAG)) {
|
|
|
|
// much of a resize / change ?
|
|
double dx = fabs(viewRect.x() - sceneRect.x());
|
|
double dy = fabs(viewRect.y() - sceneRect.y());
|
|
double vy = fabs(viewRect.y()-double(_viewY));
|
|
double dwidth = fabs(viewRect.width() - sceneRect.width());
|
|
double dheight = fabs(viewRect.height() - sceneRect.height());
|
|
|
|
// scale immediately if not a bit change
|
|
// otherwise it feels unresponsive
|
|
if (viewRect.width() == 0 || (vy < 20 && dx < 20 && dy < 20 && dwidth < 20 && dheight < 20)) {
|
|
setViewRect(sceneRect);
|
|
} else {
|
|
|
|
// tempting to make this longer but feels ponderous at longer durations
|
|
viewchange->setDuration(400);
|
|
viewchange->setStartValue(viewRect);
|
|
viewchange->setEndValue(sceneRect);
|
|
viewchange->start();
|
|
}
|
|
}
|
|
|
|
if (view->sceneRect().height() >= scene->sceneRect().height()) {
|
|
scrollbar->setEnabled(false);
|
|
} else {
|
|
|
|
// now set scrollbar
|
|
setscrollbar = true;
|
|
scrollbar->setMinimum(0);
|
|
scrollbar->setMaximum(scene->sceneRect().height()-view->sceneRect().height());
|
|
scrollbar->setValue(_viewY);
|
|
scrollbar->setPageStep(view->sceneRect().height());
|
|
scrollbar->setEnabled(true);
|
|
setscrollbar = false;
|
|
}
|
|
}
|
|
|
|
void
|
|
OverviewWindow::edgeScroll(bool down)
|
|
{
|
|
// already scrolling, so don't move
|
|
if (scrolling) return;
|
|
// we basically scroll the view if the cursor is at or above
|
|
// the top of the view, or at or below the bottom and the mouse
|
|
// is moving away. Needs to work in normal and full screen.
|
|
if (state == DRAG || state == YRESIZE) {
|
|
|
|
QPointF pos =this->mapFromGlobal(QCursor::pos());
|
|
|
|
if (!down && pos.y() <= 0) {
|
|
|
|
// at the top of the screen, go up a qtr of a screen
|
|
scrollTo(_viewY - (view->sceneRect().height()/4));
|
|
|
|
} else if (down && (geometry().height()-pos.y()) <= 0) {
|
|
|
|
// at the bottom of the screen, go down a qtr of a screen
|
|
scrollTo(_viewY + (view->sceneRect().height()/4));
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
OverviewWindow::scrollTo(int newY)
|
|
{
|
|
|
|
// bound the target to the top or a screenful from the bottom, except when we're
|
|
// resizing on Y as we are expanding the scene by increasing the size of an object
|
|
if ((state != YRESIZE) && (newY +view->sceneRect().height()) > sceneRect.bottom())
|
|
newY = sceneRect.bottom() - view->sceneRect().height();
|
|
if (newY < 0)
|
|
newY = 0;
|
|
|
|
if (_viewY != newY) {
|
|
|
|
if (abs(_viewY - newY) < 20) {
|
|
|
|
// for small scroll increments just do it, its tedious to wait for animations
|
|
_viewY = newY;
|
|
updateView();
|
|
|
|
} else {
|
|
|
|
// disable other view updates whilst scrolling
|
|
scrolling = true;
|
|
|
|
// make it snappy for short distances - ponderous for drag scroll
|
|
// and vaguely snappy for page by page scrolling
|
|
if (state == DRAG || state == YRESIZE) scroller->setDuration(300);
|
|
else if (abs(_viewY-newY) < 100) scroller->setDuration(150);
|
|
else scroller->setDuration(250);
|
|
scroller->setStartValue(_viewY);
|
|
scroller->setEndValue(newY);
|
|
scroller->start();
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
OverviewWindow::setViewRect(QRectF rect)
|
|
{
|
|
viewRect = rect;
|
|
|
|
// fit to scene width XXX need to fix scrollbars.
|
|
double scale = view->frameGeometry().width() / viewRect.width();
|
|
QRectF scaledRect(0,_viewY, viewRect.width(), view->frameGeometry().height() / scale);
|
|
|
|
// scale to selection
|
|
view->scale(scale,scale);
|
|
view->setSceneRect(scaledRect);
|
|
view->fitInView(scaledRect, Qt::KeepAspectRatio);
|
|
|
|
// if we're dragging, as the view changes it can be really jarring
|
|
// as the dragged item is not under the mouse then snaps back
|
|
// this might need to be cleaned up as a little too much of spooky
|
|
// action at a distance going on here !
|
|
if (state == DRAG) {
|
|
|
|
// update drag point
|
|
QPoint vpos = view->mapFromGlobal(QCursor::pos());
|
|
QPointF pos = view->mapToScene(vpos);
|
|
|
|
// move the card being dragged
|
|
stateData.drag.card->setPos(pos.x()-stateData.drag.offx, pos.y()-stateData.drag.offy);
|
|
}
|
|
|
|
view->update();
|
|
|
|
}
|
|
|
|
bool
|
|
OverviewWindow::eventFilter(QObject *, QEvent *event)
|
|
{
|
|
if (block || (event->type() != QEvent::KeyPress && event->type() != QEvent::GraphicsSceneWheel &&
|
|
event->type() != QEvent::GraphicsSceneMousePress && event->type() != QEvent::GraphicsSceneMouseRelease &&
|
|
event->type() != QEvent::GraphicsSceneMouseMove)) {
|
|
return false;
|
|
}
|
|
block = true;
|
|
bool returning = false;
|
|
|
|
// we only filter out keyboard shortcuts for undo redo etc
|
|
// in the qwkcode editor, anything else is of no interest.
|
|
if (event->type() == QEvent::KeyPress) {
|
|
|
|
// we care about cmd / ctrl
|
|
Qt::KeyboardModifiers kmod = static_cast<QInputEvent*>(event)->modifiers();
|
|
bool ctrl = (kmod & Qt::ControlModifier) != 0;
|
|
|
|
switch(static_cast<QKeyEvent*>(event)->key()) {
|
|
|
|
case Qt::Key_Y:
|
|
if (ctrl) {
|
|
//workout->redo();
|
|
returning = true; // we grab all key events
|
|
}
|
|
break;
|
|
|
|
case Qt::Key_Z:
|
|
if (ctrl) {
|
|
//workout->undo();
|
|
returning=true;
|
|
}
|
|
break;
|
|
|
|
case Qt::Key_Home:
|
|
scrollTo(0);
|
|
break;
|
|
|
|
case Qt::Key_End:
|
|
scrollTo(scene->sceneRect().bottom());
|
|
break;
|
|
|
|
case Qt::Key_PageDown:
|
|
scrollTo(_viewY + view->sceneRect().height());
|
|
break;
|
|
|
|
case Qt::Key_PageUp:
|
|
scrollTo(_viewY - view->sceneRect().height());
|
|
break;
|
|
|
|
case Qt::Key_Down:
|
|
scrollTo(_viewY + ROWHEIGHT);
|
|
break;
|
|
|
|
case Qt::Key_Up:
|
|
scrollTo(_viewY - ROWHEIGHT);
|
|
break;
|
|
}
|
|
|
|
} else if (event->type() == QEvent::GraphicsSceneWheel) {
|
|
|
|
// take it as applied
|
|
QGraphicsSceneWheelEvent *w = static_cast<QGraphicsSceneWheelEvent*>(event);
|
|
scrollTo(_viewY - (w->delta()*2));
|
|
event->accept();
|
|
returning = true;
|
|
|
|
} else if (event->type() == QEvent::GraphicsSceneMousePress) {
|
|
|
|
// we will process clicks when configuring so long as we're
|
|
// not in the middle of something else - this is to start
|
|
// dragging a card around
|
|
if (mode == CONFIG && state == NONE) {
|
|
|
|
// we always trap clicks when configuring, to avoid
|
|
// any inadvertent processing of clicks in the widget
|
|
event->accept();
|
|
returning = true;
|
|
|
|
// where am i ?
|
|
QPointF pos = static_cast<QGraphicsSceneMouseEvent*>(event)->scenePos();
|
|
QGraphicsItem *item = scene->itemAt(pos, view->transform());
|
|
Card *card = static_cast<Card*>(item);
|
|
|
|
// ignore other scene elements (e.g. charts)
|
|
if (!cards.contains(card)) card=NULL;
|
|
|
|
if (card) {
|
|
|
|
// are we on the boundary of the card?
|
|
double offx = pos.x()-card->geometry().x();
|
|
double offy = pos.y()-card->geometry().y();
|
|
|
|
|
|
if (card->geometry().height()-offy < 10) {
|
|
|
|
state = YRESIZE;
|
|
|
|
stateData.yresize.card = card;
|
|
stateData.yresize.deep = card->deep;
|
|
stateData.yresize.posy = pos.y();
|
|
|
|
} else if (card->geometry().width()-offx < 10) {
|
|
|
|
state = XRESIZE;
|
|
|
|
stateData.xresize.column = card->column;
|
|
stateData.xresize.width = columns[card->column];
|
|
stateData.xresize.posx = pos.x();
|
|
|
|
} else {
|
|
|
|
// we're grabbing a card, so lets
|
|
// work out the offset so we can move
|
|
// it around when we start dragging
|
|
state = DRAG;
|
|
card->invisible = true;
|
|
card->setDrag(true);
|
|
card->brush = GColor(CPLOTMARKER); //XXX hack whilst they're tiles
|
|
card->setZValue(100);
|
|
|
|
stateData.drag.card = card;
|
|
stateData.drag.offx = offx;
|
|
stateData.drag.offy = offy;
|
|
stateData.drag.width = columns[card->column];
|
|
|
|
// what is the offset?
|
|
//updateGeometry();
|
|
scene->update();
|
|
view->update();
|
|
}
|
|
}
|
|
}
|
|
|
|
} else if (event->type() == QEvent::GraphicsSceneMouseRelease) {
|
|
|
|
// stop dragging
|
|
if (mode == CONFIG && (state == DRAG || state == YRESIZE || state == XRESIZE)) {
|
|
|
|
// we want this one
|
|
event->accept();
|
|
returning = true;
|
|
|
|
// set back to visible if dragging
|
|
if (state == DRAG) {
|
|
stateData.drag.card->invisible = false;
|
|
stateData.drag.card->setZValue(10);
|
|
stateData.drag.card->placing = true;
|
|
stateData.drag.card->setDrag(false);
|
|
stateData.drag.card->brush = GColor(CCARDBACKGROUND);
|
|
}
|
|
|
|
// end state;
|
|
state = NONE;
|
|
|
|
// drop it down
|
|
updateGeometry();
|
|
updateView();
|
|
}
|
|
|
|
} else if (event->type() == QEvent::GraphicsSceneMouseMove) {
|
|
|
|
// where is the mouse now?
|
|
QPointF pos = static_cast<QGraphicsSceneMouseEvent*>(event)->scenePos();
|
|
|
|
// check for autoscrolling at edges
|
|
if (state == DRAG || state == YRESIZE) edgeScroll(lasty < pos.y());
|
|
|
|
// remember pos
|
|
lasty = pos.y();
|
|
|
|
// thanks we'll intercept that
|
|
if (mode == CONFIG) {
|
|
event->accept();
|
|
returning = true;
|
|
}
|
|
|
|
if (mode == CONFIG && state == NONE) { // hovering
|
|
|
|
// where am i ?
|
|
QGraphicsItem *item = scene->itemAt(pos, view->transform());
|
|
Card *card = static_cast<Card*>(item);
|
|
|
|
// ignore other scene elements (e.g. charts)
|
|
if (!cards.contains(card)) card=NULL;
|
|
|
|
if (card) {
|
|
|
|
// are we on the boundary of the card?
|
|
double offx = pos.x()-card->geometry().x();
|
|
double offy = pos.y()-card->geometry().y();
|
|
|
|
if (yresizecursor == false && card->geometry().height()-offy < 10) {
|
|
|
|
yresizecursor = true;
|
|
setCursor(QCursor(Qt::SizeVerCursor));
|
|
|
|
} else if (yresizecursor == true && card->geometry().height()-offy > 10) {
|
|
|
|
yresizecursor = false;
|
|
setCursor(QCursor(Qt::ArrowCursor));
|
|
|
|
}
|
|
|
|
if (xresizecursor == false && card->geometry().width()-offx < 10) {
|
|
|
|
xresizecursor = true;
|
|
setCursor(QCursor(Qt::SizeHorCursor));
|
|
|
|
} else if (xresizecursor == true && card->geometry().width()-offx > 10) {
|
|
|
|
xresizecursor = false;
|
|
setCursor(QCursor(Qt::ArrowCursor));
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// not hovering over tile, so if still have a resize cursor
|
|
// set it back to the normal arrow pointer
|
|
if (yresizecursor || xresizecursor) {
|
|
xresizecursor = yresizecursor = false;
|
|
setCursor(QCursor(Qt::ArrowCursor));
|
|
}
|
|
}
|
|
|
|
} else if (mode == CONFIG && state == DRAG && !scrolling) { // dragging?
|
|
|
|
// move the card being dragged
|
|
stateData.drag.card->setPos(pos.x()-stateData.drag.offx, pos.y()-stateData.drag.offy);
|
|
|
|
// should I move?
|
|
QList<QGraphicsItem *> overlaps;
|
|
foreach(QGraphicsItem *p, scene->items(pos))
|
|
if(cards.contains(static_cast<Card*>(p)))
|
|
overlaps << p;
|
|
|
|
// we always overlap with ourself, so see if more
|
|
if (overlaps.count() > 1) {
|
|
|
|
Card *over = static_cast<Card*>(overlaps[1]);
|
|
if (pos.y()-over->geometry().y() > over->geometry().height()/2) {
|
|
|
|
// place below the one its over
|
|
stateData.drag.card->column = over->column;
|
|
stateData.drag.card->order = over->order+1;
|
|
for(int i=cards.indexOf(over); i< cards.count(); i++) {
|
|
if (i>=0 && cards[i]->column == over->column && cards[i]->order > over->order && cards[i] != stateData.drag.card)
|
|
cards[i]->order += 1;
|
|
}
|
|
|
|
} else {
|
|
|
|
// place above the one its over
|
|
stateData.drag.card->column = over->column;
|
|
stateData.drag.card->order = over->order;
|
|
for(int i=0; i< cards.count(); i++) {
|
|
if (i>=0 && cards[i]->column == over->column && cards[i]->order >= (over->order) && cards[i] != stateData.drag.card)
|
|
cards[i]->order += 1;
|
|
}
|
|
|
|
}
|
|
} else {
|
|
|
|
// columns are now variable width
|
|
// create a new column to the right?
|
|
int x=SPACING;
|
|
int targetcol = -1;
|
|
for(int i=0; i<10; i++) {
|
|
if (pos.x() > x && pos.x() < (x+columns[i]+SPACING)) {
|
|
targetcol = i;
|
|
break;
|
|
}
|
|
x += columns[i]+SPACING;
|
|
}
|
|
|
|
if (cards.last()->column < 9 && targetcol < 0) {
|
|
|
|
// don't keep moving - if we're already alone in column 0 then no move is needed
|
|
if (stateData.drag.card->column != 0 || (cards.count()>1 && cards[1]->column == 0)) {
|
|
|
|
// new col to left
|
|
for(int i=0; i< cards.count(); i++) cards[i]->column += 1;
|
|
stateData.drag.card->column = 0;
|
|
stateData.drag.card->order = 0;
|
|
|
|
// shift columns widths to the right
|
|
for(int i=9; i>0; i--) columns[i] = columns[i-1];
|
|
columns[0] = stateData.drag.width;
|
|
}
|
|
|
|
} else if (cards.last()->column < 9 && cards.last() && cards.last()->column < targetcol) {
|
|
|
|
// new col to the right
|
|
stateData.drag.card->column = cards.last()->column + 1;
|
|
stateData.drag.card->order = 0;
|
|
|
|
// make column width same as source width
|
|
columns[stateData.drag.card->column] = stateData.drag.width;
|
|
|
|
} else {
|
|
|
|
// add to the end of the column
|
|
int last = -1;
|
|
for(int i=0; i<cards.count() && cards[i]->column <= targetcol; i++) {
|
|
if (cards[i]->column == targetcol) last=i;
|
|
}
|
|
|
|
// so long as its been dragged below the last entry on the column !
|
|
if (last >= 0 && pos.y() > cards[last]->geometry().bottom()) {
|
|
stateData.drag.card->column = targetcol;
|
|
stateData.drag.card->order = cards[last]->order+1;
|
|
}
|
|
}
|
|
}
|
|
|
|
// drop it down
|
|
updateGeometry();
|
|
updateView();
|
|
|
|
} else if (mode == CONFIG && state == YRESIZE) {
|
|
|
|
// resize in rows, so in 75px units
|
|
int addrows = (pos.y() - stateData.yresize.posy) / ROWHEIGHT;
|
|
int setdeep = stateData.yresize.deep + addrows;
|
|
|
|
//min height
|
|
if (setdeep < 5) setdeep=5; // min of 5 rows
|
|
|
|
stateData.yresize.card->deep = setdeep;
|
|
|
|
// drop it down
|
|
updateGeometry();
|
|
updateView();
|
|
|
|
} else if (mode == CONFIG && state == XRESIZE) {
|
|
|
|
// multiples of 50 (smaller than margin)
|
|
int addblocks = (pos.x() - stateData.xresize.posx) / 50;
|
|
int setcolumn = stateData.xresize.width + (addblocks * 50);
|
|
|
|
// min max width
|
|
if (setcolumn < 800) setcolumn = 800;
|
|
if (setcolumn > 1600) setcolumn = 1600;
|
|
|
|
columns[stateData.xresize.column] = setcolumn;
|
|
|
|
// animate
|
|
updateGeometry();
|
|
updateView();
|
|
}
|
|
}
|
|
|
|
block = false;
|
|
return returning;
|
|
}
|
|
|
|
|
|
void
|
|
Card::clicked()
|
|
{
|
|
if (isVisible()) hide();
|
|
else show();
|
|
|
|
//if (brush.color() == GColor(CCARDBACKGROUND)) brush.setColor(Qt::red);
|
|
//else brush.setColor(GColor(CCARDBACKGROUND));
|
|
|
|
update(geometry());
|
|
}
|