mirror of
https://github.com/GoldenCheetah/GoldenCheetah.git
synced 2026-02-14 08:38:45 +00:00
This patch adds support for temperature and slope across the ridefile readers. For the most part their is no functional change although it is now possible to view and edit these data series in the editor. File formats that can provide temp or slope include; .bin, .fit, .srm, .sync, .wko Further updates will be required to display the data in the ride plot and histograms.
402 lines
12 KiB
C++
402 lines
12 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
|
|
*/
|
|
|
|
#include "RideFileTableModel.h"
|
|
|
|
RideFileTableModel::RideFileTableModel(RideFile *ride) : ride(ride)
|
|
{
|
|
setRide(ride);
|
|
}
|
|
|
|
void
|
|
RideFileTableModel::setRide(RideFile *newride)
|
|
{
|
|
// QPointer helps us check if the current ride has been deleted before trying to disconnect
|
|
static QPointer<RideFileCommand> connection = NULL;
|
|
if (connection) {
|
|
disconnect(connection, SIGNAL(beginCommand(bool,RideCommand*)), this, SLOT(beginCommand(bool,RideCommand*)));
|
|
disconnect(connection, SIGNAL(endCommand(bool,RideCommand*)), this, SLOT(endCommand(bool,RideCommand*)));
|
|
}
|
|
|
|
ride = newride;
|
|
tooltips.clear(); // remove the tooltips -- rideEditor will set them (this is fugly, but efficient)
|
|
|
|
if (ride) {
|
|
|
|
// set the headings to reflect the data that is present
|
|
setHeadings();
|
|
|
|
// Trap commands
|
|
connection = ride->command;
|
|
connect(ride->command, SIGNAL(beginCommand(bool,RideCommand*)), this, SLOT(beginCommand(bool,RideCommand*)));
|
|
connect(ride->command, SIGNAL(endCommand(bool,RideCommand*)), this, SLOT(endCommand(bool,RideCommand*)));
|
|
connect(ride, SIGNAL(deleted()), this, SLOT(deleted()));
|
|
|
|
// refresh
|
|
emit layoutChanged();
|
|
}
|
|
}
|
|
|
|
void
|
|
RideFileTableModel::deleted()
|
|
{
|
|
// we don't need to disconnect since they're free'd up by QT
|
|
ride = NULL;
|
|
beginResetModel();
|
|
endResetModel();
|
|
dataChanged(createIndex(0,0), createIndex(90,999999));
|
|
}
|
|
|
|
void
|
|
RideFileTableModel::setHeadings(RideFile::SeriesType series)
|
|
{
|
|
headings_.clear();
|
|
headingsType.clear();
|
|
|
|
// set the headings array
|
|
if (series == RideFile::secs || ride->areDataPresent()->secs) {
|
|
headings_ << tr("Time");
|
|
headingsType << RideFile::secs;
|
|
}
|
|
if (series == RideFile::km || ride->areDataPresent()->km) {
|
|
headings_ << tr("Distance");
|
|
headingsType << RideFile::km;
|
|
}
|
|
if (series == RideFile::watts || ride->areDataPresent()->watts) {
|
|
headings_ << tr("Power");
|
|
headingsType << RideFile::watts;
|
|
}
|
|
if (series == RideFile::nm || ride->areDataPresent()->nm) {
|
|
headings_ << tr("Torque");
|
|
headingsType << RideFile::nm;
|
|
}
|
|
if (series == RideFile::cad || ride->areDataPresent()->cad) {
|
|
headings_ << tr("Cadence");
|
|
headingsType << RideFile::cad;
|
|
}
|
|
if (series == RideFile::hr || ride->areDataPresent()->hr) {
|
|
headings_ << tr("Heartrate");
|
|
headingsType << RideFile::hr;
|
|
}
|
|
if (series == RideFile::kph || ride->areDataPresent()->kph) {
|
|
headings_ << tr("Speed");
|
|
headingsType << RideFile::kph;
|
|
}
|
|
if (series == RideFile::alt || ride->areDataPresent()->alt) {
|
|
headings_ << tr("Altitude");
|
|
headingsType << RideFile::alt;
|
|
}
|
|
if (series == RideFile::lat || ride->areDataPresent()->lat) {
|
|
headings_ << tr("Latitude");
|
|
headingsType << RideFile::lat;
|
|
}
|
|
if (series == RideFile::lon || ride->areDataPresent()->lon) {
|
|
headings_ << tr("Longitude");
|
|
headingsType << RideFile::lon;
|
|
}
|
|
if (series == RideFile::headwind || ride->areDataPresent()->headwind) {
|
|
headings_ << tr("Headwind");
|
|
headingsType << RideFile::headwind;
|
|
}
|
|
if (series == RideFile::slope || ride->areDataPresent()->slope) {
|
|
headings_ << tr("Slope");
|
|
headingsType << RideFile::slope;
|
|
}
|
|
if (series == RideFile::temp || ride->areDataPresent()->temp) {
|
|
headings_ << tr("Temperature");
|
|
headingsType << RideFile::temp;
|
|
}
|
|
if (series == RideFile::interval || ride->areDataPresent()->interval) {
|
|
headings_ << tr("Interval");
|
|
headingsType << RideFile::interval;
|
|
}
|
|
|
|
}
|
|
|
|
Qt::ItemFlags
|
|
RideFileTableModel::flags(const QModelIndex &index) const
|
|
{
|
|
if (!index.isValid())
|
|
return Qt::ItemIsEditable;
|
|
else
|
|
return QAbstractTableModel::flags(index) | Qt::ItemIsEditable |
|
|
Qt::ItemIsEnabled | Qt::ItemIsSelectable;
|
|
}
|
|
|
|
QVariant
|
|
RideFileTableModel::data(const QModelIndex & index, int role) const
|
|
{
|
|
if (role == Qt::ToolTipRole) return toolTip(index.row(), columnType(index.column()));
|
|
|
|
if (index.row() >= ride->dataPoints().count() || index.column() >= headings_.count())
|
|
return QVariant();
|
|
else {
|
|
return ride->getPoint(index.row(), headingsType[index.column()]);
|
|
}
|
|
}
|
|
|
|
QVariant
|
|
RideFileTableModel::headerData(int section, Qt::Orientation orient, int role) const
|
|
{
|
|
if (role != Qt::DisplayRole) return QVariant();
|
|
|
|
if (orient == Qt::Horizontal) {
|
|
if (section >= headings_.count())
|
|
return QVariant();
|
|
else {
|
|
return headings_[section];
|
|
}
|
|
} else {
|
|
return QString("%1").arg(section+1);
|
|
}
|
|
}
|
|
|
|
int
|
|
RideFileTableModel::rowCount(const QModelIndex &) const
|
|
{
|
|
if (ride) return ride->dataPoints().count();
|
|
else return 0;
|
|
}
|
|
|
|
int
|
|
RideFileTableModel::columnCount(const QModelIndex &) const
|
|
{
|
|
if (ride) return headingsType.count();
|
|
else return 0;
|
|
}
|
|
|
|
bool
|
|
RideFileTableModel::setData(const QModelIndex & index, const QVariant &value, int role)
|
|
{
|
|
if (index.row() >= ride->dataPoints().count() || index.column() >= headings_.count())
|
|
return false;
|
|
else if (role == Qt::EditRole) {
|
|
ride->command->setPointValue(index.row(), headingsType[index.column()], value.toDouble());
|
|
return true;
|
|
} else return false;
|
|
}
|
|
|
|
bool
|
|
RideFileTableModel::setHeaderData(int section, Qt::Orientation , const QVariant & value, int)
|
|
{
|
|
if (section >= headings_.count()) return false;
|
|
else {
|
|
headings_[section] = value.toString();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
bool
|
|
RideFileTableModel::insertRow(int row, const QModelIndex &parent)
|
|
{
|
|
return insertRows(row, 1, parent);
|
|
}
|
|
|
|
bool
|
|
RideFileTableModel::insertRows(int row, int count, const QModelIndex &)
|
|
{
|
|
if (row >= ride->dataPoints().count()) return false;
|
|
else {
|
|
while (count--) {
|
|
struct RideFilePoint *p = new RideFilePoint;
|
|
ride->command->insertPoint(row, p);
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
bool
|
|
RideFileTableModel::appendRows(QVector<RideFilePoint>newRows)
|
|
{
|
|
ride->command->appendPoints(newRows);
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
RideFileTableModel::removeRows(int row, int count, const QModelIndex &)
|
|
{
|
|
if ((row + count) > ride->dataPoints().count()) return false;
|
|
ride->command->deletePoints(row, count);
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
RideFileTableModel::insertColumn(RideFile::SeriesType series)
|
|
{
|
|
if (headingsType.contains(series)) return false; // already there
|
|
|
|
ride->command->setDataPresent(series, true);
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
RideFileTableModel::insertColumns(int, int, const QModelIndex &)
|
|
{
|
|
// WE DON'T SUPPORT THIS
|
|
// use insertColumn(RideFile::SeriesType) instead.
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
RideFileTableModel::removeColumn(RideFile::SeriesType series)
|
|
{
|
|
if (headingsType.contains(series)) {
|
|
ride->command->setDataPresent(series, false);
|
|
return true;
|
|
} else
|
|
return false; // its not there
|
|
}
|
|
|
|
bool
|
|
RideFileTableModel::removeColumns (int , int , const QModelIndex &)
|
|
{
|
|
// WE DON'T SUPPORT THIS
|
|
// use removeColumn(RideFile::SeriesType) instead.
|
|
return false;
|
|
}
|
|
|
|
void
|
|
RideFileTableModel::setValue(int row, int column, double value)
|
|
{
|
|
ride->command->setPointValue(row, headingsType[column], value);
|
|
}
|
|
|
|
double
|
|
RideFileTableModel::getValue(int row, int column)
|
|
{
|
|
return ride->getPointValue(row, headingsType[column]);
|
|
}
|
|
|
|
void
|
|
RideFileTableModel::forceRedraw()
|
|
{
|
|
// tell the view to redraw everything
|
|
if (ride)
|
|
dataChanged(createIndex(0,0), createIndex(headingsType.count(), ride->dataPoints().count()));
|
|
}
|
|
|
|
//
|
|
// RideCommand has made changes...
|
|
//
|
|
void
|
|
RideFileTableModel::beginCommand(bool undo, RideCommand *cmd)
|
|
{
|
|
switch (cmd->type) {
|
|
|
|
case RideCommand::SetPointValue:
|
|
break;
|
|
|
|
case RideCommand::InsertPoint:
|
|
{
|
|
InsertPointCommand *dp = (InsertPointCommand *)cmd;
|
|
if (!undo) beginInsertRows(QModelIndex(), dp->row, dp->row);
|
|
else beginRemoveRows(QModelIndex(), dp->row, dp->row);
|
|
break;
|
|
}
|
|
|
|
case RideCommand::DeletePoint:
|
|
{
|
|
DeletePointCommand *dp = (DeletePointCommand *)cmd;
|
|
if (undo) beginInsertRows(QModelIndex(), dp->row, dp->row);
|
|
else beginRemoveRows(QModelIndex(), dp->row, dp->row);
|
|
break;
|
|
}
|
|
|
|
case RideCommand::DeletePoints:
|
|
{
|
|
DeletePointsCommand *ds = (DeletePointsCommand *)cmd;
|
|
if (undo) beginInsertRows(QModelIndex(), ds->row, ds->row + ds->count - 1);
|
|
else beginRemoveRows(QModelIndex(), ds->row, ds->row + ds->count - 1);
|
|
break;
|
|
}
|
|
|
|
case RideCommand::AppendPoints:
|
|
{
|
|
AppendPointsCommand *ap = (AppendPointsCommand *)cmd;
|
|
if (!undo) beginInsertRows(QModelIndex(), ap->row, ap->row + ap->count - 1);
|
|
else beginRemoveRows(QModelIndex(), ap->row, ap->row + ap->count - 1);
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void
|
|
RideFileTableModel::endCommand(bool undo, RideCommand *cmd)
|
|
{
|
|
switch (cmd->type) {
|
|
|
|
case RideCommand::SetPointValue:
|
|
{
|
|
SetPointValueCommand *spv = (SetPointValueCommand*)cmd;
|
|
QModelIndex cell(index(spv->row,headingsType.indexOf(spv->series)));
|
|
dataChanged(cell, cell);
|
|
break;
|
|
}
|
|
case RideCommand::InsertPoint:
|
|
if (!undo) endInsertRows();
|
|
else endRemoveRows();
|
|
break;
|
|
|
|
case RideCommand::DeletePoint:
|
|
case RideCommand::DeletePoints:
|
|
if (undo) endInsertRows();
|
|
else endRemoveRows();
|
|
break;
|
|
|
|
case RideCommand::AppendPoints:
|
|
if (undo) endRemoveRows();
|
|
else endInsertRows();
|
|
break;
|
|
|
|
case RideCommand::SetDataPresent:
|
|
setHeadings();
|
|
emit layoutChanged();
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Tooltips are kept in a QMap, since they SHOULD be sparse
|
|
static QString xsstring(int x, RideFile::SeriesType series)
|
|
{
|
|
return QString("%1:%2").arg((int)x).arg(static_cast<int>(series));
|
|
}
|
|
|
|
void
|
|
RideFileTableModel::setToolTip(int row, RideFile::SeriesType series, QString text)
|
|
{
|
|
QString key = xsstring(row, series);
|
|
|
|
// if text is blank we are removing it
|
|
if (text == "") tooltips.remove(key);
|
|
|
|
// if text is non-blank we are changing it
|
|
if (text != "") tooltips.insert(key, text);
|
|
}
|
|
|
|
QString
|
|
RideFileTableModel::toolTip(int row, RideFile::SeriesType series) const
|
|
{
|
|
QString key = xsstring(row, series);
|
|
return tooltips.value(key, "");
|
|
}
|