/* * Copyright (c) 2014 Damien Grauser (Damien.Grauser@pev-geneve.ch) * * 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 "AllPlotInterval.h" #include "Colors.h" #include "Units.h" #include "Athlete.h" #include "AllPlot.h" #include "IntervalItem.h" #include #include #include #include #include class AllPlotIntervalData : public QwtArraySeriesData { public: AllPlotIntervalData(AllPlotInterval *plot, Context *context, int level, int max, RideItem *rideItem, const IntervalItem *interval) : plot(plot), context(context), level(level), max(max), rideItem(rideItem), interval(interval) {} double x(size_t i) const ; double ymin(size_t) const ; double ymax(size_t i) const ; size_t size() const ; void init() ; IntervalItem *intervalNum(int n) const; int intervalCount() const; AllPlotInterval *plot; Context *context; int level; int max; RideItem *rideItem; const IntervalItem *interval; virtual QwtIntervalSample sample(size_t i) const; virtual QRectF boundingRect() const; }; class TimeScaleDraw: public ScaleScaleDraw { public: TimeScaleDraw(bool *bydist) : ScaleScaleDraw(), bydist(bydist) {} virtual QwtText label(double v) const { if (*bydist) { return QString("%1").arg(v); } else { QTime t = QTime(0,0,0,0).addSecs(v*60.00); if (scaleMap().sDist() > 5) return t.toString("hh:mm"); return t.toString("hh:mm:ss"); } } private: bool *bydist; }; AllPlotInterval::AllPlotInterval(QWidget *parent, Context *context): QwtPlot(parent), bydist(false), context(context), rideItem(NULL), groupMatch(false) { // tick draw //TimeScaleDraw *tsd = new TimeScaleDraw(&this->bydist) ; //tsd->setTickLength(QwtScaleDiv::MajorTick, 3); //setAxisScaleDraw(QwtPlot::xBottom, tsd); //pal.setColor(QPalette::WindowText, GColor(CPLOTMARKER)); //pal.setColor(QPalette::Text, GColor(CPLOTMARKER)); //axisWidget(QwtPlot::xBottom)->setPalette(pal); enableAxis(xBottom, false); setAxisVisible(xBottom, false); setAxisVisible(yLeft, false); tooltip = new LTMToolTip(QwtPlot::xBottom, QwtAxis::yLeft, QwtPicker::NoRubberBand, QwtPicker::AlwaysOn, canvas(), ""); canvasPicker = new AllPlotIntervalCanvasPicker(this); connect(context, SIGNAL(intervalHover(IntervalItem*)), this, SLOT(intervalHover(IntervalItem*))); connect(canvasPicker, SIGNAL(pointHover(QwtPlotIntervalCurve*, int)), this, SLOT(intervalCurveHover(QwtPlotIntervalCurve*))); connect(canvasPicker, SIGNAL(pointClicked(QwtPlotIntervalCurve*,int)), this, SLOT(intervalCurveClick(QwtPlotIntervalCurve*))); connect(canvasPicker, SIGNAL(pointDblClicked(QwtPlotIntervalCurve*,int)), this, SLOT(intervalCurveDblClick(QwtPlotIntervalCurve*))); configChanged(CONFIG_APPEARANCE); } void AllPlotInterval::configChanged(qint32) { QPalette pal = palette(); pal.setBrush(QPalette::Background, QBrush(GColor(CRIDEPLOTBACKGROUND))); setPalette(pal); setCanvasBackground(GColor(CRIDEPLOTBACKGROUND)); static_cast(canvas())->setFrameStyle(QFrame::NoFrame); update(); replot(); } void AllPlotInterval::setByDistance(int id) { bydist = (id == 1); if (rideItem == NULL) return; if (intervalLigns.count() == 0) return; refreshIntervals(); } void AllPlotInterval::setDataFromRide(RideItem *_rideItem) { rideItem = _rideItem; if (rideItem == NULL) return; // set bottom scale to match the ride refreshIntervals(); } void AllPlotInterval::refreshIntervals() { placeIntervals(); refreshIntervalCurve(); //refreshIntervalMarkers(); } // Compare two RideFileInterval on duration. bool intervalBiggerThan(const IntervalItem* i1, const IntervalItem* i2) { return (i1->stop-i1->start) > (i2->stop-i2->start); } void AllPlotInterval::sortIntervals(QList &intervals, QList< QList > &intervalsGroups) { // Sort by duration qSort(intervals.begin(), intervals.end(), intervalBiggerThan); QList matchesGroup; for (int i=0; itype == RideFileInterval::MATCH) { matchesGroup.append(interval); intervals.removeOne(interval); //intervals.move(i, place++); } } if (matchesGroup.count() > 0) intervalsGroups.append(matchesGroup); } void AllPlotInterval::placeIntervals() { QList intervals = rideItem->intervals(); QList< QList > intervalsGroups; sortIntervals(intervals, intervalsGroups); intervalLigns.clear(); if (intervalsGroups.count()>0) intervalLigns.append(intervalsGroups.at(0)); else { QList intervalsLign1; intervalLigns.append(intervalsLign1); } while (intervals.count()>0) { IntervalItem *interval = intervals.first(); int lign = 0; bool placed = false; while (!placed) { bool place = true; /*if (interval.isPeak()) { intervals.removeFirst(); placed = true; }*/ foreach(const IntervalItem* placedinterval, intervalLigns.at(lign)) { if (interval->stop>placedinterval->start && interval->startstop) place = false; } if (place) { intervalLigns[lign].append(interval); intervals.removeFirst(); placed = true; } else { lign++; if (intervalLigns.count()<=lign) { QList intervalsLign; intervalLigns.append(intervalsLign); } } } } setFixedHeight((1+intervalLigns.count())*10); setAxisScale(yLeft, 0, 3000*intervalLigns.count()); } void AllPlotInterval::setColorForIntervalCurve(QwtPlotIntervalCurve *intervalCurve, const IntervalItem *interval, bool selected) { QColor color = interval->color; QPen ihlPen = QPen(color); intervalCurve->setPen(ihlPen); QColor ihlbrush = QColor(color); if (!selected) ihlbrush.setAlpha(128); intervalCurve->setPen(ihlbrush); // fill below the line intervalCurve->setBrush(ihlbrush); // fill below the line } void AllPlotInterval::refreshIntervalCurve() { foreach(QwtPlotIntervalCurve *curve, curves.values()) { curve->detach(); delete curve; } curves.clear(); int level=0; foreach(const QList &intervalsLign, intervalLigns) { foreach(IntervalItem *interval, intervalsLign) { QwtPlotIntervalCurve *intervalCurve = new QwtPlotIntervalCurve(); intervalCurve->setYAxis(QwtAxis::yLeft); setColorForIntervalCurve(intervalCurve, interval, false); int max = 3000*intervalLigns.count(); intervalCurve->setSamples(new AllPlotIntervalData(this, context, level, max, rideItem, interval)); intervalCurve->attach(this); curves.insert(interval, intervalCurve); } level++; } } void AllPlotInterval::intervalHover(IntervalItem *chosen) { foreach(IntervalItem *interval, curves.keys()) { if (chosen == interval) { setColorForIntervalCurve(curves.value(interval), interval, true); } else { setColorForIntervalCurve(curves.value(interval), interval, false); } } replot(); } void AllPlotInterval::intervalCurveHover(QwtPlotIntervalCurve *curve) { if (curve != NULL) { IntervalItem *interval = curves.key(curve); //intervalHover(interval); // tell the charts -- and block signals whilst they occur tooltip->setText(interval->name); blockSignals(true); context->notifyIntervalHover(interval); blockSignals(false); } else { context->notifyIntervalHover(NULL); // clear tooltip->setText(""); } } void AllPlotInterval::intervalCurveClick(QwtPlotIntervalCurve *curve) { IntervalItem *interval = curves.key(curve); int idx = rideItem->intervals().indexOf(interval); //XXX REFACTOR HOW TO SELECT/DESELECT AN INTERVAL #if 0 if (idx != -1) { context->athlete->allIntervalItems()->child(idx)->setSelected(!context->athlete->allIntervalItems()->child(idx)->isSelected()); if (QApplication::keyboardModifiers() != Qt::ControlModifier) { const QTreeWidgetItem *allIntervals = context->athlete->allIntervalItems(); for (int i=0; ichildCount(); i++) { if (i!=idx) context->athlete->allIntervalItems()->child(i)->setSelected(false); } } } #endif } void AllPlotInterval::intervalCurveDblClick(QwtPlotIntervalCurve *curve) { IntervalItem *interval = curves.key(curve); int idx = rideItem->intervals().indexOf(interval); //XXX REFACTOR HOW TO SELECT/DESELECT AN INTERVAL #if 0 if (idx != -1) { context->athlete->allIntervalItems()->child(idx)->setSelected(!context->athlete->allIntervalItems()->child(idx)->isSelected()); context->notifyIntervalZoom((IntervalItem *)context->athlete->allIntervalItems()->child(idx)); } #endif } /* * */ double AllPlotIntervalData::x(size_t i) const { //if (interval == NULL) return 0; // out of bounds !? double multiplier = context->athlete->useMetricUnits ? 1 : MILES_PER_KM; // which point are we returning? switch (i%4) { case 0 : return plot->bydist ? (multiplier * interval->startKM) : interval->start/60; // bottom left case 1 : return plot->bydist ? (multiplier * interval->startKM) : interval->start/60; // top left case 2 : return plot->bydist ? (multiplier * interval->stopKM) : interval->stop/60; // top right case 3 : return plot->bydist ? (multiplier * interval->stopKM) : interval->stop/60; // bottom right } return 0; // shouldn't get here, but keeps compiler happy } double AllPlotIntervalData::ymin(size_t) const { return max-2000-3000*(level); } double AllPlotIntervalData::ymax(size_t i) const { switch (i%4) { case 0 : return ymin(i); // bottom left case 1 : return max-3000*(level); // top left case 2 : return max-3000*(level); // top right case 3 : return ymin(i); // bottom right } return 0; // shouldn't get here, but keeps compiler happy } size_t AllPlotIntervalData::size() const { return 4; } QwtIntervalSample AllPlotIntervalData::sample(size_t i) const { return QwtIntervalSample( x(i), ymin(i), ymax(i) ); } QRectF AllPlotIntervalData::boundingRect() const { return QRectF(0, 5000, 5100, 5100); } AllPlotIntervalCanvasPicker::AllPlotIntervalCanvasPicker(QwtPlot *plot): QObject(plot), d_selectedCurve(NULL), d_selectedPoint(-1) { canvas = static_cast(plot->canvas()); canvas->installEventFilter(this); // We want the focus, but no focus rect. The canvas->setFocusPolicy(Qt::StrongFocus); canvas->setFocusIndicator(QwtPlotCanvas::ItemFocusIndicator); } bool AllPlotIntervalCanvasPicker::event(QEvent *e) { if ( e->type() == QEvent::User ) { //showCursor(true); return true; } return QObject::event(e); } bool AllPlotIntervalCanvasPicker::eventFilter(QObject *object, QEvent *e) { // for our canvas ? if (object != canvas) return false; switch(e->type()) { default: QApplication::postEvent(this, new QEvent(QEvent::User)); break; case QEvent::MouseButtonDblClick: select(((QMouseEvent *)e)->pos(), true, true); break; case QEvent::MouseButtonPress: select(((QMouseEvent *)e)->pos(), true, false); break; case QEvent::MouseMove: select(((QMouseEvent *)e)->pos(), false, false); break; } return QObject::eventFilter(object, e); } // Select the point at a position. If there is no point // deselect the selected point void AllPlotIntervalCanvasPicker::select(const QPoint &pos, bool clicked, bool dblClicked) { QwtPlotIntervalCurve *curve = NULL; int index = -1; const QwtPlotItemList& itmList = plot()->itemList(); for ( QwtPlotItemIterator it = itmList.begin(); it != itmList.end(); ++it ) { if ( (*it)->rtti() == QwtPlotItem::Rtti_PlotIntervalCurve ) { QwtPlotIntervalCurve *c = (QwtPlotIntervalCurve*)(*it); double xmin = c->plot()->transform(c->xAxis(),c->sample(0).value); double xmax = c->plot()->transform(c->xAxis(),c->sample(2).value); double ymax = c->plot()->transform(c->yAxis(),c->sample(2).interval.minValue()); double ymin = c->plot()->transform(c->yAxis(),c->sample(2).interval.maxValue()); if ( pos.x()>=xmin && pos.x()<=xmax && pos.y()>=ymin && pos.y()<=ymax) { curve = c; } } } d_selectedCurve = NULL; d_selectedPoint = -1; if ( curve ) { // picked one d_selectedCurve = curve; d_selectedPoint = index; if (dblClicked) pointDblClicked(curve, index); // emit else if (clicked) pointClicked(curve, index); // emit else pointHover(curve, index); // emit } else { // didn't if (dblClicked) pointDblClicked(NULL, index); // emit else if (clicked) pointClicked(NULL, -1); // emit else pointHover(NULL, -1); // emit } }