mirror of
https://github.com/GoldenCheetah/GoldenCheetah.git
synced 2026-02-13 16:18:42 +00:00
.. move the fluff that has collected into the src directory to somewhere more appropriate.
495 lines
16 KiB
C++
495 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 "Athlete.h"
|
|
#include "MainWindow.h"
|
|
#include "MetricAggregator.h"
|
|
#include "SummaryMetrics.h"
|
|
#include "DBAccess.h"
|
|
#include "Settings.h"
|
|
#include "Units.h"
|
|
|
|
GcBubble::GcBubble(Context *context) :
|
|
QWidget(context->mainWindow, Qt::FramelessWindowHint),
|
|
context(context), borderWidth(3), 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);
|
|
|
|
// we need smooth curves
|
|
painter.setRenderHints(QPainter::Antialiasing|QPainter::TextAntialiasing, true);
|
|
|
|
// 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 = context->mainWindow->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()) > context->mainWindow->geometry().x()+context->mainWindow->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()) < (context->mainWindow->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) < (context->mainWindow->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) > (context->mainWindow->geometry().y()+context->mainWindow->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) < context->mainWindow->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) > (context->mainWindow->geometry().x()+context->mainWindow->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 = context->athlete->metricDB->getAllMetricsFor(filename);
|
|
useMetricUnits = context->athlete->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();
|
|
}
|