mirror of
https://github.com/GoldenCheetah/GoldenCheetah.git
synced 2026-02-15 00:49:55 +00:00
As a personal habit I tend to use the C pre-processor to comment out code blocks I don't want to remove. This is in case the code will be required in the future. I think it is now safe to say the code commented out is not required -- most of it is legacy and marks the transition from earlier designs or legacy code. I've done this in one big commit since in theory it has no functional change, and in future can look in this commit for any code we may want to reinstate.
489 lines
16 KiB
C++
489 lines
16 KiB
C++
/*
|
|
* Copyright (c) 2010 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
|
|
*/
|
|
|
|
// bubble config
|
|
static const int lineWidth = 3;
|
|
static const int spikeHeight = 10;
|
|
static const int spikeWidth = 20;
|
|
static const int radius = 7;
|
|
static const int alpha = 220;
|
|
static const int spikeMargin = 40;
|
|
|
|
#include "GcBubble.h"
|
|
|
|
#include "MetricAggregator.h"
|
|
#include "SummaryMetrics.h"
|
|
#include "DBAccess.h"
|
|
#include "Settings.h"
|
|
#include "Units.h"
|
|
|
|
GcBubble::GcBubble(MainWindow *parent) : QWidget(parent, Qt::FramelessWindowHint), mainWindow(parent), borderWidth(3), parent(parent), orientation(Qt::Horizontal)
|
|
{
|
|
setAttribute(Qt::WA_DeleteOnClose);
|
|
setAttribute(Qt::WA_TranslucentBackground);
|
|
setAttribute(Qt::WA_NoSystemBackground);
|
|
setWindowFlags(windowFlags() | Qt::WindowStaysOnTopHint);
|
|
setContentsMargins(0,0,0,0);
|
|
setStyleSheet(QString::fromUtf8(
|
|
"border-color: rgba(0,0,0,128); "
|
|
"color: rgba(255,255,255,255);"
|
|
"font: bold;"
|
|
"background-color: rgba(255, 255, 255, 0);"
|
|
));
|
|
|
|
|
|
QSize iconSize(23,23);
|
|
|
|
display = new QWidget(this);
|
|
display->setContentsMargins(3,3,3,3); // for the 3px border
|
|
display->setAutoFillBackground(false);
|
|
display->setGeometry(0,0, 200, 134);
|
|
|
|
topleft = new QLabel("WCODE", this);
|
|
topmiddle = new QLabel("RIDEDATE", this);
|
|
topright = new QLabel("SPORT", this);
|
|
|
|
// sport icons - in case we recognise the sport code
|
|
bike = new QLabel(this);
|
|
bike->setPixmap(QPixmap(":images/IconBike.png"));
|
|
|
|
run = new QLabel(this);
|
|
run->setPixmap(QPixmap(":images/IconRun.png"));
|
|
|
|
swim = new QLabel(this);
|
|
swim->setPixmap(QPixmap(":images/IconSwim.png"));
|
|
|
|
topmiddle2 = new QLabel("TIME", this); // time
|
|
|
|
m1 = new QLabel("DURATION", this);
|
|
m1->setAlignment(Qt::AlignLeft);
|
|
m2 = new QLabel("TSS", this);
|
|
m2->setAlignment(Qt::AlignRight);
|
|
m3 = new QLabel("DISTANCE", this);
|
|
m3->setAlignment(Qt::AlignLeft);
|
|
m4 = new QLabel("IF", this);
|
|
m4->setAlignment(Qt::AlignRight);
|
|
|
|
alt = new QLabel(this);
|
|
alt->setPixmap(QPixmap(":images/IconAltitude.png"));
|
|
|
|
cad = new QLabel(this);
|
|
cad->setPixmap(QPixmap(":images/IconCadence.png"));
|
|
|
|
gps = new QLabel(this);
|
|
gps->setPixmap(QPixmap(":images/IconGPS.png"));
|
|
|
|
hr = new QLabel(this);
|
|
hr->setPixmap(QPixmap(":images/IconHR.png"));
|
|
|
|
power = new QLabel(this);
|
|
power->setPixmap(QPixmap(":images/IconPower.png"));
|
|
|
|
speed = new QLabel(this);
|
|
speed->setPixmap(QPixmap(":images/IconSpeed.png"));
|
|
|
|
torque = new QLabel(this);
|
|
torque->setPixmap(QPixmap(":images/IconTorque.png"));
|
|
|
|
wind = new QLabel(this);
|
|
wind->setPixmap(QPixmap(":images/IconWind.png"));
|
|
|
|
temp = new QLabel(this);
|
|
temp->setPixmap(QPixmap(":images/IconTemp.png"));
|
|
|
|
// layout display
|
|
QVBoxLayout *displayLayout = new QVBoxLayout(display);
|
|
displayLayout->setContentsMargins(3,3,3,3);
|
|
displayLayout->setSpacing(0);
|
|
|
|
// workout code, date, sport, sport icon
|
|
QHBoxLayout *top = new QHBoxLayout;
|
|
top->addWidget(topleft);
|
|
top->addStretch();
|
|
top->addWidget(topmiddle);
|
|
top->addStretch();
|
|
top->addWidget(topright);
|
|
top->addWidget(swim);
|
|
top->addWidget(run);
|
|
top->addWidget(bike);
|
|
displayLayout->addLayout(top);
|
|
|
|
// time
|
|
QHBoxLayout *top2 = new QHBoxLayout;
|
|
top2->addStretch();
|
|
top2->addWidget(topmiddle2);
|
|
top2->addStretch();
|
|
displayLayout->addLayout(top2);
|
|
|
|
displayLayout->addStretch();
|
|
|
|
// metrics
|
|
QHBoxLayout *mid = new QHBoxLayout;
|
|
mid->addStretch();
|
|
mid->addWidget(m1);
|
|
mid->addStretch();
|
|
mid->addWidget(m2);
|
|
mid->addStretch();
|
|
displayLayout->addLayout(mid);
|
|
QHBoxLayout *mid2 = new QHBoxLayout;
|
|
mid2->addStretch();
|
|
mid2->addWidget(m3);
|
|
mid2->addStretch();
|
|
mid2->addWidget(m4);
|
|
mid2->addStretch();
|
|
displayLayout->addLayout(mid2);
|
|
|
|
displayLayout->addStretch();
|
|
|
|
// icons
|
|
QHBoxLayout *icons = new QHBoxLayout;
|
|
icons->setSpacing(0);
|
|
icons->setContentsMargins(0,0,0,0);
|
|
icons->addStretch();
|
|
icons->addWidget(speed);
|
|
icons->addWidget(power);
|
|
icons->addWidget(hr);
|
|
icons->addWidget(cad);
|
|
icons->addWidget(torque);
|
|
icons->addWidget(alt);
|
|
icons->addWidget(gps);
|
|
icons->addWidget(temp);
|
|
icons->addWidget(wind);
|
|
icons->addStretch();
|
|
displayLayout->addLayout(icons);
|
|
}
|
|
|
|
void
|
|
GcBubble::paintEvent(QPaintEvent *)
|
|
{
|
|
|
|
// Init paint settings
|
|
QPainter painter(this);
|
|
|
|
// outline border, adjust to offset, for now we outline
|
|
painter.setBrush(Qt::NoBrush);
|
|
QColor shadow = QColor(0,0,0,alpha);
|
|
QPen shadowPen(shadow);
|
|
shadowPen.setWidth(lineWidth);
|
|
painter.setPen(shadowPen);
|
|
|
|
painter.translate(0,1);
|
|
painter.drawPath(path);
|
|
|
|
// now draw for real
|
|
painter.translate(0,-1); // adjust to offset, for now we outline
|
|
|
|
// contents fill with a linear gradient
|
|
QLinearGradient linearGradient(0, 0, 0, height());
|
|
linearGradient.setColorAt(0.0, QColor(80,80,80, alpha));
|
|
linearGradient.setColorAt(1.0, QColor(0, 0, 0, alpha));
|
|
linearGradient.setSpread(QGradient::PadSpread);
|
|
painter.setBrush(linearGradient);
|
|
|
|
// border gray and opaque
|
|
QColor borderColor = QColor(240,240,240,200);
|
|
QPen border = QPen(borderColor);
|
|
border.setWidth(lineWidth);
|
|
painter.setPen(border);
|
|
|
|
// draw the background
|
|
painter.drawPath(path);
|
|
}
|
|
|
|
void
|
|
GcBubble::setPos(int x, int y, Qt::Orientation orientation) // always uses global co-ords
|
|
{
|
|
QPoint here = parent->mapFromGlobal(QPoint(x,y));
|
|
this->orientation = orientation;
|
|
|
|
// set to as big as possible
|
|
setGeometry(0,0,display->width()+spikeHeight+(lineWidth*2),display->height()+spikeHeight+(lineWidth*2));
|
|
|
|
// lets work out the position of the spike
|
|
// is it left or right or top or bottom?
|
|
if (orientation == Qt::Horizontal) {
|
|
|
|
// would the window be off mainwindow?
|
|
if ((x+width()) > parent->geometry().x()+parent->geometry().width()) {
|
|
spikePosition = right;
|
|
} else {
|
|
spikePosition = left;
|
|
}
|
|
setGeometry(0,0,display->width()+spikeHeight+(lineWidth*2),display->height()+(lineWidth*2));
|
|
|
|
} else {
|
|
|
|
// would the window be off mainwindow?
|
|
if ((y-height()) < (parent->geometry().y()+40)) { // +40 for menu and title bar
|
|
spikePosition = top;
|
|
} else {
|
|
spikePosition = bottom;
|
|
}
|
|
setGeometry(0,0,display->width()+(lineWidth*2),display->height()+spikeHeight+(lineWidth*2));
|
|
}
|
|
|
|
|
|
// lets put the spike in the middle but possibly
|
|
// move it up/down or left right to make the bubble
|
|
// stay visible in mainwindow
|
|
if (orientation == Qt::Horizontal) {
|
|
|
|
if (y-(height()/2) < (parent->geometry().y()+40)) { // +40 for menu and title bar of mainwindow
|
|
// put it at the top
|
|
if (spikePosition == left) start = QPoint(lineWidth, 40);
|
|
else start = QPoint(width()-lineWidth, 40);
|
|
|
|
} else if (y+(height()/2) > (parent->geometry().y()+parent->geometry().height())) {
|
|
// put it at the bottom
|
|
if (spikePosition == left) start = QPoint(lineWidth, height()-40);
|
|
else start = QPoint(width()-lineWidth, height()-40);
|
|
|
|
} else {
|
|
// put it in the middle
|
|
if (spikePosition == left) start = QPoint(lineWidth, height()/2);
|
|
else start = QPoint(width()-lineWidth, height()/2);
|
|
|
|
}
|
|
|
|
} else { // Qt::Vertical
|
|
|
|
if (x-(width()/2) < parent->geometry().x()) {
|
|
// put it at the left
|
|
if (spikePosition == top) start = QPoint(40, lineWidth);
|
|
else start = QPoint(40, height()-lineWidth);
|
|
|
|
} else if (x+(width()/2) > (parent->geometry().x()+parent->geometry().width())) {
|
|
// put it at the right
|
|
if (spikePosition == top) start = QPoint(width()-40, lineWidth);
|
|
else start = QPoint(width()-40, height()-lineWidth);
|
|
|
|
} else {
|
|
// put it in the middle
|
|
if (spikePosition == top) start = QPoint(width()/2, lineWidth);
|
|
else start = QPoint(width()/2, height()-lineWidth);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// bubble body
|
|
QRect body;
|
|
switch(spikePosition) {
|
|
|
|
case left:
|
|
body = QRect(spikeHeight+lineWidth, lineWidth,
|
|
display->width(),
|
|
display->height());
|
|
break;
|
|
|
|
case right:
|
|
body = QRect(lineWidth, lineWidth,
|
|
display->width(),
|
|
display->height());
|
|
break;
|
|
|
|
case top:
|
|
body = QRect(lineWidth, lineWidth+spikeHeight,
|
|
display->width(),
|
|
display->height());
|
|
break;
|
|
case bottom:
|
|
body = QRect(lineWidth, lineWidth,
|
|
display->width(),
|
|
display->height());
|
|
break;
|
|
|
|
}
|
|
|
|
display->move(body.x(), body.y());
|
|
|
|
// set draw path
|
|
path = QPainterPath(); // reset path
|
|
path.moveTo(start);
|
|
|
|
switch (spikePosition) {
|
|
|
|
case left:
|
|
path.lineTo(body.x(), start.y()-(spikeWidth/2)); // point to lhs
|
|
path.lineTo(body.topLeft() + QPoint(0, radius)); // to top left corner, before curve
|
|
path.quadTo(body.topLeft(), body.topLeft() + QPoint(radius, 0)); // curve to top
|
|
|
|
path.lineTo(body.topRight()-QPoint(radius,0)); // to top right corner, before curve
|
|
path.quadTo(body.topRight(), body.topRight() + QPoint(0, radius)); // curve to rhs
|
|
|
|
path.lineTo(body.bottomRight()-QPoint(0, radius)); // to bottom right corner, befor curve
|
|
path.quadTo(body.bottomRight(), body.bottomRight() - QPoint(radius, 0)); // curve to rhs
|
|
|
|
path.lineTo(body.bottomLeft()+QPoint(radius, 0)); // to bottom left corner, before curve
|
|
path.quadTo(body.bottomLeft(), body.bottomLeft() - QPoint(0, radius)); // curve to lhs
|
|
|
|
path.lineTo(body.x(), start.y()+(spikeWidth/2)); // lhs before back to point
|
|
break;
|
|
|
|
case right:
|
|
path.lineTo(body.right(), start.y()+(spikeWidth/2)); // point to rhs
|
|
|
|
path.lineTo(body.bottomRight()-QPoint(0, radius)); // to bottom right corner, befor curve
|
|
path.quadTo(body.bottomRight(), body.bottomRight() - QPoint(radius, 0)); // curve to rhs
|
|
|
|
path.lineTo(body.bottomLeft()+QPoint(radius, 0)); // to bottom left corner, before curve
|
|
path.quadTo(body.bottomLeft(), body.bottomLeft() - QPoint(0, radius)); // curve to lhs
|
|
|
|
path.lineTo(body.topLeft() + QPoint(0, radius)); // to top left corner, before curve
|
|
path.quadTo(body.topLeft(), body.topLeft() + QPoint(radius, 0)); // curve to top
|
|
|
|
path.lineTo(body.topRight()-QPoint(radius,0)); // to top right corner, before curve
|
|
path.quadTo(body.topRight(), body.topRight() + QPoint(0, radius)); // curve to rhs
|
|
|
|
path.lineTo(body.right(), start.y()-(spikeWidth/2)); // rhs before back to point
|
|
|
|
break;
|
|
|
|
case top:
|
|
path.lineTo(start.x()+(spikeWidth/2), body.top()); // point to top
|
|
|
|
path.lineTo(body.topRight()-QPoint(radius,0)); // to top right corner, before curve
|
|
path.quadTo(body.topRight(), body.topRight() + QPoint(0, radius)); // curve to rhs
|
|
|
|
path.lineTo(body.bottomRight()-QPoint(0, radius)); // to bottom right corner, befor curve
|
|
path.quadTo(body.bottomRight(), body.bottomRight() - QPoint(radius, 0)); // curve to rhs
|
|
|
|
path.lineTo(body.bottomLeft()+QPoint(radius, 0)); // to bottom left corner, before curve
|
|
path.quadTo(body.bottomLeft(), body.bottomLeft() - QPoint(0, radius)); // curve to lhs
|
|
|
|
path.lineTo(body.topLeft() + QPoint(0, radius)); // to top left corner, before curve
|
|
path.quadTo(body.topLeft(), body.topLeft() + QPoint(radius, 0)); // curve to top
|
|
|
|
path.lineTo(start.x()-(spikeWidth/2), body.top()); // rhs before back to point
|
|
break;
|
|
|
|
case bottom:
|
|
path.lineTo(start.x()-(spikeWidth/2), body.bottom()); // point to top
|
|
|
|
path.lineTo(body.bottomLeft()+QPoint(radius, 0)); // to bottom left corner, before curve
|
|
path.quadTo(body.bottomLeft(), body.bottomLeft() - QPoint(0, radius)); // curve to lhs
|
|
|
|
path.lineTo(body.topLeft() + QPoint(0, radius)); // to top left corner, before curve
|
|
path.quadTo(body.topLeft(), body.topLeft() + QPoint(radius, 0)); // curve to top
|
|
|
|
path.lineTo(body.topRight()-QPoint(radius,0)); // to top right corner, before curve
|
|
path.quadTo(body.topRight(), body.topRight() + QPoint(0, radius)); // curve to rhs
|
|
|
|
path.lineTo(body.bottomRight()-QPoint(0, radius)); // to bottom right corner, befor curve
|
|
path.quadTo(body.bottomRight(), body.bottomRight() - QPoint(radius, 0)); // curve to rhs
|
|
|
|
path.lineTo(start.x()+(spikeWidth/2), body.bottom()); // rhs before back to point
|
|
break;
|
|
}
|
|
path.lineTo(start);
|
|
|
|
// offset the position to avoid
|
|
// causing a mouse enter event onto the bubble(!)
|
|
QPoint offset;
|
|
switch (spikePosition) {
|
|
case left: offset = QPoint(5,0); break;
|
|
case right: offset = QPoint(-5,0); break;
|
|
case top: offset = QPoint(0,5); break;
|
|
case bottom: offset = QPoint(0,-5); break;
|
|
}
|
|
|
|
QWidget::move(here-start+offset);
|
|
}
|
|
|
|
void
|
|
GcBubble::setText(QString filename)
|
|
{
|
|
SummaryMetrics metrics = parent->metricDB->getAllMetricsFor(filename);
|
|
|
|
useMetricUnits = mainWindow->useMetricUnits;
|
|
|
|
|
|
//
|
|
// Workout code
|
|
//
|
|
QString wcode = metrics.getText("Workout Code", "");
|
|
if (wcode != "") {
|
|
topleft->setText(wcode);
|
|
topleft->show();
|
|
} else {
|
|
topleft->hide();
|
|
}
|
|
|
|
|
|
//
|
|
// Sport
|
|
//
|
|
QString sport = metrics.getText("Sport", "");
|
|
if (sport != "") {
|
|
topright->setText(sport);
|
|
topright->show();
|
|
} else {
|
|
topright->hide();
|
|
}
|
|
|
|
swim->hide();
|
|
run->hide();
|
|
bike->hide();
|
|
|
|
// icons instead of text?
|
|
if (sport == "Bike") { bike->show(); topright->hide(); }
|
|
if (sport == "Swim") { swim->show(); topright->hide(); }
|
|
if (sport == "Run") { run->show(); topright->hide(); }
|
|
|
|
//
|
|
// Ride Date
|
|
//
|
|
QDateTime rideDate = metrics.getRideDate();
|
|
topmiddle->setText(rideDate.toString("MMM d, yyyy")); // same format as ride list
|
|
topmiddle2->setText(rideDate.toString("h:mm AP"));
|
|
|
|
// Metrics 1,2,3,4
|
|
m1->setText(QTime(0,0,0,0).addSecs(metrics.getForSymbol("workout_time")).toString("hh:mm:ss")); //duration
|
|
|
|
if (useMetricUnits) {
|
|
m3->setText(QString("%1 km").arg(metrics.getForSymbol("total_distance"), 0, 'f', 2));
|
|
} else {
|
|
m3->setText(QString("%1 mi").arg(metrics.getForSymbol("total_distance")*MILES_PER_KM, 0, 'f', 2));
|
|
} //distance
|
|
|
|
m2->setText(QString("%1 TSS").arg(metrics.getForSymbol("coggan_tss"), 0, 'f', 0));
|
|
m4->setText(QString("%1 IF").arg(metrics.getForSymbol("coggan_if"), 0, 'f', 3));
|
|
|
|
// Icons
|
|
QString data = metrics.getText("Data", "");
|
|
|
|
if (data.contains("P")) power->show();
|
|
else power->hide();
|
|
if (data.contains("S")) speed->show();
|
|
else speed->hide();
|
|
if (data.contains("H")) hr->show();
|
|
else hr->hide();
|
|
if (data.contains("C")) cad->show();
|
|
else cad->hide();
|
|
if (data.contains("N")) torque->show();
|
|
else torque->hide();
|
|
if (data.contains("A")) alt->show();
|
|
else alt->hide();
|
|
if (data.contains("G")) gps->show();
|
|
else gps->hide();
|
|
if (data.contains("E")) temp->show();
|
|
else temp->hide();
|
|
if (data.contains("W")) wind->show();
|
|
else wind->hide();
|
|
}
|