Compare commits

..

5 Commits

Author SHA1 Message Date
Sean Rhea
30c877a5f2 fix FTDI required for SRM download bug
GC supports two download port types: serial ports and D2XX.  Before, if
either of these failed to load, the download dialog wouldn't show either
port type.  With this patch, if both fail, GC displays a warning, but if
either one succeeds, GC will proceed with only that port type.  This
change should fix the problem that users were having to download and
install both the FTDI drivers and the PL2303 ones in order to download
from the SRM PCV.
2010-02-06 11:23:26 -08:00
Robert Carlsen
92d2601a5a Added sanity checking to ignore missing metrics
There is a possibility that ride metrics may become unavailable yet
remain requested by QSettings (stored in
~/Library/Preferences/org.goldencheetah.GoldenCheetah.plist on OS X).

This patch ignores any metrics listed in the preferences yet are not
supported by the running version of Golden Cheetah.
2010-02-05 08:09:34 -08:00
Sean Rhea
113375669f don't check dependencies until newMetric is called
Before, we checked them during addMetric, and that left us vulnerable
link-order errors.  With this patch, we wait until someone actually asks
for an instance of a metric, and then we check all metrics' dependencies.
That way, since the Ride Summary always creates at least one metric, we'll
still check the dependencies of them all.  We just do it a little later in
the program's execution than before.
2010-02-05 08:08:21 -08:00
Sean Rhea
cd0e9c184b add Erase Ride(s) button to download dialog
This is a workaround for the SRM erase bug.  It gives the user a way to
try erasing the device's memory without re-downloading a ride.
2010-02-04 20:26:16 -08:00
Sean Rhea
28cbe359d4 regenerate stress cache after config change
fixes #32
2010-02-04 05:15:02 -08:00
289 changed files with 4449 additions and 138186 deletions

View File

@@ -1,3 +1,2 @@
TEMPLATE = subdirs
SUBDIRS = qwt src
CONFIG += ordered

Binary file not shown.

Before

Width:  |  Height:  |  Size: 122 KiB

View File

@@ -2,16 +2,12 @@
CONTENT=$(wildcard *.content)
HTML=$(subst .content,.html,$(CONTENT))
TARBALLS=$(wildcard gc_*.tgz)
OTHER= 3d.png choose-a-cyclist.png cpint.gp cpint.png critical-power-plot.png critical-power.png \
cyclist-info.png editor.png gui-preview.png histogram-analysis.png logo.jpg logo.png \
main-window.png map.png metrics-power.png metrics-timedist.png metrics-tiz.png pf-pv-plot.png \
pm.png power.zones realtime.png ride-plot.png ride-plot2.png ride-summary.png sample.gp \
sample.png weekly-summary.png google-earth.png aerolab.png
OTHER=logo.jpg sample.gp sample.png cpint.gp cpint.png \
critical-power-plot.png histogram-analysis.png pf-pv-plot.png \
ride-plot.png ride-summary.png weekly-summary.png \
choose-a-cyclist.png main-window.png critical-power.png \
power.zones cyclist-info.png
BIN= GoldenCheetah_2.0.0_Linux_x86_64.gz \
GoldenCheetah_2.0.0_Linux_x86.gz \
GoldenCheetah_2.0.0_Mac_Universal.dmg \
GoldenCheetah_2.0.0_Windows_Installer.exe
all: $(HTML)
.PHONY: all clean install
@@ -21,10 +17,6 @@ clean:
install:
rsync -avz -e ssh $(HTML) $(TARBALLS) $(OTHER) \
liversedge@srhea.net:/home/srhea/wwwroot/goldencheetah.org/
install-bin:
rsync -avz -e ssh $(BIN) \
srhea.net:/home/srhea/wwwroot/goldencheetah.org/
bug-tracker.html: bug-tracker.content genpage.pl
@@ -42,15 +34,9 @@ contrib.html: contrib.content genpage.pl
developers-guide.html: developers-guide.content genpage.pl
./genpage.pl "Developer's Guide" $< > $@
older-releases.html: older-releases.content genpage.pl
./genpage.pl "Older Releases" $< > $@
download.html: download.content genpage.pl
./genpage.pl "Download" $< > $@
release-notes.html: release-notes.content genpage.pl
./genpage.pl "Release Notes" $< > $@
faq.html: faq.content genpage.pl
./genpage.pl "Frequently Asked Questions" $< > $@

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

View File

@@ -22,40 +22,34 @@ other support, including:</p>
<ul>
<li>Robert Carlsen</li>
<li>Rainer Clasen</li>
<li>Chris Cleeland</li>
<li>J.T. Conklin</li>
<li>Dan Connelly</li>
<li>Damien Grauser</li>
<li>Steve Gribble</li>
<li>Dag Gruneau</li>
<li>Damian Grauser</li>
<li>Ned Harding</li>
<li>Aldy Hernandez</li>
</ul>
</td>
<td valign="top" width="33%">
<ul>
<li>Aldy Hernandez</li>
<li>Jamie Kimberley</li>
<li>Justin Knotzke</li>
<li>Andrew Kruse</li>
<li>Mark Liversedge</li>
<li>Greg Lonnon</li>
<li>Tom Montgomery</li>
<li>Eric Murray</li>
<li>Scott Overfield</li>
</ul>
</td>
<td valign="top">
<ul>
<li>Eric Murray</li>
<li>Scott Overfield</li>
<li>Mark Rages</li>
<li>Robb Romans</li>
<li>Mitsukuni Sato</li>
<li>Berend de Schouwer</li>
<li>Julian Simioni</li>
<li>Greg Steele</li>
<li>Tom Weichmann</li>
<li>Keisuke Yamaguchi</li>
</ul>
</td>
</tr>
</table>

View File

@@ -1,53 +1,15 @@
<!-- $Id: download.content,v 1.6 2009/01/09 20:45:03 rcarlsen Exp $ -->
<p>
Golden Cheetah is available in binary form for
Linux x86, Mac OS X (universal binary), and Windows.
It is also available as source code.
Golden Cheetah is available as source code and in binary form for
Mac OS X Universal Binary, Linux on x86 processors and Windows 32-bit.
</p>
<p>
Golden Cheetah downloads data from all versions of the PowerTap
computer including the new Joule. If you're using the PowerTap USB cradle
(as opposed to the older, serial cable), you may need to install the
<a href="http://www.ftdichip.com/Drivers/D2XX.htm">FTDI USB driver</a>
before downloading.
Depending on your operating system, you may need to install the <a
href="http://www.ftdichip.com/Drivers/D2XX.htm">FTDI USB
driver</a> if you're using the PowerTap's new USB download cradle. The FTDI USB drivers are an optional install if you do not plan on downloading from your device using Golden Cheetah.
</p>
<p>
On Linux and Mac OS X, Golden Cheetah also downloads from the SRM PCV. On Mac
OS X, you'll need to install <a href="http://osx-pl2303.sourceforge.net/">the
open source PL2303 driver</a> to download from an SRM.
</p>
<p>
<font face="arial,helvetica,sanserif">
<big><strong>Download Release 2.0</strong></big>
</font>
<ul>
<li><a href="GoldenCheetah_2.0.0_Linux_x86.gz">Linux x86</a><br>
<li><a href="GoldenCheetah_2.0.0_Linux_x86_64.gz">Linux x86_64</a><br>
<li><a href="GoldenCheetah_2.0.0_Mac_Universal.dmg">Mac OS X Universal 10.5 & 10.6</a><br>
<li><a href="GoldenCheetah_2.0.0_Mac_PPC.dmg">Mac OS X 10.4</a><br>
<li><a href="GoldenCheetah_2.0.0_Windows_Installer.exe">Windows 32-bit</a>
</ul>
<p>
You can also <a href="release-notes.html">view the release notes</a> for 2.0
or <a href="older-releases.html">download older releases</a> of Golden Cheetah.
</p>
<p>
<font face="arial,helvetica,sanserif">
<big><strong>Development Releases</strong></big>
</font>
<p>Gareth Coco has also made
<a href="http://goldencheetah.stand2surf.net/">nightly development builds</a>
available. These binaries are based on the latest code, so they have more
features and (sometimes) more bugs than the stable 2.0 release above.
<p>
<font face="arial,helvetica,sanserif">
<big><strong>Source Code</strong></big>
@@ -59,4 +21,296 @@ The Golden Cheetah source code is available via git. See the
You can also <a href="http://github.com/srhea/GoldenCheetah/tree/master/">browse
the source on github</a>.
<p>
<font face="arial,helvetica,sanserif">
<big><strong>Binaries</strong></big>
</font>
<p>
<center>
<table width="100%" cellspacing="5">
<tr>
<td width="15%"><i>Version</i></td>
<td width="25%"><i>Files</i></td>
<td><i>Description</i></td>
</tr>
<tr>
<td valign="top">1.2.0</td>
<td valign="top">
<a href="GoldenCheetah_1.2.0_Linux_x86.tgz">Linux x86</a><br>
<a href="GoldenCheetah_1.2.0_Linux_x86_64.tgz">Linux x86_64</a><br>
<a href="GoldenCheetah_1.2.0_Darwin_Universal.dmg">Mac OS X Universal</a><br>
<a href="GoldenCheetah_1.2.0_Windows_Installer.exe">Windows 32-bit</a>
</td>
<td valign="top">
<p>
Lots of new features in this release, including:
</p>
<ul>
<li>Direct download from SRM (R. Clasen and S. Rhea)
<li>WKO+ file import (M. Liversedge)
<li>Qollector support (M. Rages)
<li>Altitude plotting (T. Weichmann)
<li>Manual ride entry (E. Murray)
<li>Power zones shading (D. Connell)
<li>Weekly summary histograms (R. Carlsen)
<li>Automatic CP estimation from CP graph (D. Connell)
<li>Support for running off a USB stick (J. Knotzke)
<li>OS-specific directory layout (J. Simioni)
<li>PF/PV plot improvements (B. de Schouwer)
<li>Memory leak fixes (G. Lonnon)
</ul>
<p>Thanks also to Jamie Kimberley for extensive testing.
</td>
</tr>
<tr>
<td valign="top">1.1.325</td>
<td valign="top">
<a href="GoldenCheetah_1.1.325_Linux_x86.gz">Linux x86</a><br>
<a href="GoldenCheetah_1.1.325_Linux_x86_64.gz">Linux x86_64</a><br>
<a href="GoldenCheetah_1.1.325_Darwin_Universal.dmg">Mac OS X Universal</a><br>
<a href="GoldenCheetah_1.1.325_Windows_Installer.exe">Windows 32-bit</a>
</td>
<td valign="top">
<p>
First official Windows release courtesy of Ned Harding. Ned put much effort into the port to make the download reliable and created a nice installer, too (Thanks Ned!). He also provided the long-awaited Split Ride feature - break up a ride file into separate rides easily using long time gaps and intervals.
<ul>
<li>Ant+Sport PowerTap support.
<li>Split Rides by time gaps or intervals.
<li>Delete ride from list.
<li>Use distance or time for x-axis in Ride Plot (Thanks Damain).
<li>Numerous bug fixes (Thanks Tom, Dan).
</p>
</td>
</tr>
<tr>
<td valign="top">1.0.277</td>
<td valign="top">
<a href="GoldenCheetah_1.0.277_Linux_x86.gz">Linux x86</a>,<br>
<a href="GoldenCheetah_1.0.277_Linux_x86_64.tar.gz">Linux x86_64</a>,<br>
<a href="GoldenCheetah_1.0.277_Darwin_Universal.dmg">Mac OS X Universal</a>
</td>
<td valign="top">
<p>*Note: Beginning with this release we are changing to a numbered versioning system. Minor point releases will generally indicate builds with new features, while bugfix releases will increment the final number, which represents the svn revision*</p>
<p>Several new features in this release: Critical Power calculator, find best intervals utility, Pedal Force / Pedal Velocity chart, iBike and Ergomo CSV import, GUI power zones creator, separate vertical axes for Power / HR / Cadence and Speed in the Ride plot, sorting rides with the most recent at the top of the list, and many bug fixes courtesy of JT Conklin.
</p>
<p>You may need to install <a href="http://www.ftdichip.com/Drivers/D2XX.htm">USB drivers</a> from FTDI.
</p>
<p>
For posterity, the <a href="http://robertcarlsen.net/blog/?page_id=49">beta version</a> for Windows, based on r295.
</p>
</td>
</tr>
<tr>
<td valign="top">Mar 10, 2008</td>
<td valign="top">
<a href="GoldenCheetah_2008-03-10_Linux_x86.tgz">Linux x86</a>,<br>
<a href="GoldenCheetah_2008-03-10_Darwin_Universal.dmg">Mac OS X Universal</a>
</td>
<td valign="top">
This release introduces <a href="http://www.physfarm.com/Analysis%20of%20Power%20Output%20and%20Training%20Stress%20in%20Cyclists-%20BikeScore.pdf">BikeScore&#8482;</a>,
a metric of training stress developed by Dr. Philip Skiba. It also
fixes several small bugs in earlier releases.
</td>
</tr>
<tr>
<td valign="top">Sep 23, 2007</td>
<td valign="top">
<a href="GoldenCheetah_2007-09-23_Linux_x86.tgz">Linux x86</a>,<br>
<a href="GoldenCheetah_2007-09-23_Darwin_i386.dmg">Mac OS X x86</a>,<br>
<a href="GoldenCheetah_2007-09-23_Darwin_powerpc.dmg">Mac OS X PowerPC</a>
</td>
<td valign="top">
Bug fix release. CVS imports weren't quite working in the last one.
</td>
</tr>
<tr>
<td valign="top">Sep 18, 2007</td>
<td valign="top">
<a href="GoldenCheetah_2007-09-18_Linux_x86.tgz">Linux x86</a>,<br>
<a href="GoldenCheetah_2007-09-18_Darwin_powerpc.dmg">Mac OS X PowerPC</a>
</td>
<td valign="top">
This release adds two small, but excellent features from Justin Knotzke:
CSV file imports and visual interval markers in the ride plot.
</td>
</tr>
<tr>
<td valign="top">Aug 7, 2007</td>
<td valign="top">
<a href="GoldenCheetah_2007-08-07_Linux_x86.tgz">Linux x86</a>,<br>
<a href="GoldenCheetah_2007-08-07_Darwin_powerpc.dmg">Mac OS X PowerPC</a>
</td>
<td valign="top">This release fixes a bug in the critical power
intervals graph where you could get bad data if you started an interval
after a long period of not moving. It also adds really basic zooming to
the ride plot: use the left mouse button to zoom in and the right one to
return to the previous zoom state. It's pretty crappy right now, but
it's better than nothing.
</td>
</tr>
<tr>
<td valign="top">Apr 26, 2007</td>
<td valign="top">
<a href="GoldenCheetah_2007-04-26_Linux_x86.tgz">Linux x86</a>,<br>
<a href="GoldenCheetah_2007-04-26_Darwin_powerpc.dmg">Mac OS X PowerPC</a>
</td>
<td valign="top">This release fixes some bugs and adds a whole bunch of
new features:
<ul>
<li>Now imports .srm files (direct download from SRM hopefully coming
soon)
<li>New "Weekly Summary" tab shows total weekly hours, miles, and work
<li>Power zones can now be entered into a text file, after which GC will
display time in each zone in the ride and weekly summaries; for more
information on the zone file format, <a href="zones.html">see this
page</a>.
</ul>
</td>
</tr>
<tr>
<td valign="top">Apr 1, 2007</td>
<td valign="top">
<a href="GoldenCheetah_2007-04-01_Linux_x86.tgz">Linux x86</a>,<br>
<a href="GoldenCheetah_2007-04-01_Darwin_powerpc.dmg">Mac OS X PowerPC</a>
</td>
<td valign="top">This release fixes a bug that was introduced with the
hardware echo detection code. If you're using the CycleOps-supplied USB
cable to download from your PowerTap unit, this release should make
downloads more reliable. (Those using the KeySpan USB-to-serial adaptor
or a plain-old serial port shouldn't see any difference.)</td>
</tr>
<tr>
<td valign="top">Feb 22, 2007</td>
<td valign="top">
<a href="GoldenCheetah_2007-02-22_Linux_x86.tgz">Linux x86</a>,<br>
<a href="GoldenCheetah_2007-02-22_Darwin_powerpc.dmg">Mac OS X PowerPC</a>
</td>
<td valign="top">Clicking on the Critical Power Plot now displays the
interval duration, maximum power for that ride, and maximum power for all
rides below the plot. Also fixes a bug for recording intervals longer than
two seconds.</td>
</tr>
<tr>
<td valign="top">Feb 12, 2007</td>
<td valign="top">
<a href="GoldenCheetah_2007-02-12_Linux_x86.tgz">Linux x86</a>,<br>
<a href="GoldenCheetah_2007-02-12_Darwin_powerpc.dmg">Mac OS X PowerPC</a>
</td>
<td valign="top">Interval information now included in ride summary, rides can
now be exported as comma-separated values for import into Excel, and better
automatic detection of hardware echo. Also includes a number of bux
fixes.</td>
</tr>
<tr>
<td valign="top">Jan 30, 2007</td>
<td valign="top">
<a href="GoldenCheetah_2007-01-30_Linux_x86.tgz">Linux x86</a>,<br>
<a href="GoldenCheetah_2007-01-30_Darwin_powerpc.dmg">Mac OS X PowerPC</a>
</td>
<td valign="top">Bug fix release.</td>
</tr>
<tr>
<td valign="top">Jan 6, 2007</td>
<td valign="top"><a href="GoldenCheetah_2007-01-06_Linux_x86.tgz">Linux
x86</a></td>
<td valign="top">First release for Linux.</td>
</tr>
<tr>
<td valign="top">Dec 25, 2006</td>
<td valign="top"><a href="GoldenCheetah_2006-12-25_Darwin_powerpc.dmg">Mac OS
X PowerPC</a></td>
<td valign="top">Adds the Power Histogram, which shows how much time a rider
spent at each particular power level during a ride.</td>
</tr>
<tr>
<td valign="top">Sep 19, 2006</td>
<td valign="top"><a href="GoldenCheetah_2006-09-19_Darwin_powerpc.dmg">Mac OS
X PowerPC</a></td>
<td valign="top">Adds the Critical Power Plot, which shows the highest average
power you've achieved for every interval length over all your rides and the
selected ride. Also shows download progress in minutes of ride data
downloaded.</td>
</tr>
<tr>
<td valign="top">Sep 7, 2006</td>
<td valign="top"><a href="GoldenCheetah_2006-09-07_Darwin_powerpc.dmg">Mac OS
X PowerPC</a></td>
<td valign="top">Adds speed and cadence to the ride plot. Fixes a bug
found by George Gilliland where reseting the time during a ride could cause
the GUI to crash.</td>
</tr>
<tr>
<td valign="top">Sep 6, 2006</td>
<td valign="top"><a href="GoldenCheetah_2006-09-06_Darwin_powerpc.dmg">Mac OS
X PowerPC</a></td>
<td valign="top">The first release of the Golden Cheetah GUI.</td>
</tr>
</table>
</center>
<p>
<hr width="50%">
<p>
<font face="arial,helvetica,sanserif">
<big><strong>Older Stuff</strong></big>
</font>
<p>
These are the older, source-only, command-line distributions. I've left them
up for historical purposes only; I don't recommend using them.
<center>
<table width="100%" cellspacing="10">
<tr>
<td width="20%"><i>Date</i></td>
<td width="30%"><i>File</i></td>
<td><i>Description</i></td>
</tr>
<tr>
<td valign="top">Aug 11, 2006</td>
<td valign="top"><a href="gc_2006-08-11.tgz">gc_2006-08-11.tgz</a></td>
<td valign="top">ptdl now works with Keyspan USB-to-serial adaptor, after
debugging help from Rob Carlsen.
</td>
</tr>
<tr>
<td valign="top">May 27, 2006</td>
<td valign="top"><a href="gc_2006-05-27.tgz">gc_2006-05-27.tgz</a></td>
<td valign="top">Adds the <code>cpint</code> program for computing critical
power intervals and the <code>ptpk</code> program for converting from
PowerTuned data files (see the <a href="users-guide.html">User's
Guide</a>).</td>
</tr>
<tr>
<td valign="top">May 16, 2006</td>
<td valign="top"><a href="gc_2006-05-16.tgz">gc_2006-05-16.tgz</a></td>
<td valign="top">The first code release, containing <code>ptdl</code> and
<code>ptunpk</code>.</td>
</tr>
</table>
</center>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 155 KiB

View File

@@ -55,7 +55,6 @@ body {
<p> <b><a href="index.html">Introduction</a></b>
<br> <b><a href="screenshots.html">Screenshots</a>
<br> <b><a href="http://bugs.goldencheetah.org/projects/goldencheetah/wiki">Wiki</a>
<br> <b><a href="users-guide.html">User's Guide</a>
<br> <b><a href="developers-guide.html">Developer's Guide</a>
<br> <b><a href="faq.html">FAQ</a>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 311 KiB

View File

@@ -13,8 +13,7 @@ GoldenCheetah is a software package that:
<ul>
<li>Downloads ride data directly from the CycleOps PowerTap and the SRM
PowerControl V. Support for SRM PowerControl VI and VII is planned for the
future.<p>
PowerControl.<p>
<li>Imports ride data downloaded with other programs, including TrainingPeaks
WKO+ and the manufacturers' software for the Ergomo, Garmin, Polar, PowerTap,

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 159 KiB

After

Width:  |  Height:  |  Size: 229 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 267 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

View File

@@ -1,399 +0,0 @@
<p>
This page contains older releases of Golden Cheetah. For the latest version,
please see <a href="download.html">the download page</a> instead.
</p>
<p>
<font face="arial,helvetica,sanserif">
<big><strong>Golden Cheetah</strong></big>
</font>
<p>
<center>
<table width="100%" cellspacing="5">
<tr>
<td width="15%"><i>Version</i></td>
<td width="25%"><i>Files</i></td>
<td><i>Description</i></td>
</tr>
<tr>
<td valign="top">1.3.0</td>
<td valign="top">
<a href="GoldenCheetah_1.3.0_Linux_x86.gz">Linux x86</a><br>
<a href="GoldenCheetah_1.3.0_Linux_x86_64.gz">Linux x86_64</a><br>
<a href="GoldenCheetah_1.3.0_Mac_Universal.zip">Mac OS X Universal</a><br>
<a href="GoldenCheetah_1.3.0_Windows_Installer.exe">Windows 32-bit</a>
</td>
<td valign="top">
<p>
Lots of new features:
<p>
Realtime Mode:
<ul>
<li>Graph data as you ride (Mark Liversedge, Justin Knotzke, Steve Gribble)</li>
</ul>
</p>
<p>
Charts:
<ul>
<li>Added Performance Manager (Eric Murray)</li>
<li>Added 3D Modeling (Mark Liversedge and Greg Steele)</li>
<li>Up to four y-axes on Ride Plot (Sean Rhea)</li>
<li>Option to show work instead of power in Critical Power Plot (Sean Rhea)</li>
</ul>
</p>
<p>
Intervals:
<ul>
<li>Configurable metrics for intervals (Sean Rhea)</li>
<li>Find peak powers and add to intervals (Mark Liversedge)</li>
<li>Highlight intervals in plots (Damien Grauser)</li>
</ul>
</p>
<p>
Device support:
<ul>
<li>Serial port support on Windows (Mark Liversedge)</li>
<li>Erase SRM memory without downloading (Sean Rhea)</li>
</ul>
</p>
<p>
Imports:
<ul>
<li>New ride import wizard (Mark Liversedge, Jamie Kimberley)</li>
<li>Support Computrainer 3dp file format (Greg Lonnon)</li>
<li>Support WKO v3 file format (Mark Liversedge)</li>
<li>Support files with Garmin "smart recording" (Greg Lonnon)</li>
<li>New GoldenCheetah (.gc) file format (Sean Rhea)</li>
</ul>
</p>
<p>
New/improved ride metrics:
<ul>
<li>Added Joe Friel's Aerobic Decoupling (Sean Rhea)</li>
<li>Added training points system by running coach Jack Daniels (Sean Rhea)</li>
<li>Better elevation gain estimates (Sean Rhea)</li>
</ul>
</p>
<p>
Support for more languages:
<ul>
<li>French (Damien Grauser)</li>
<li>Japanese (Mitsukuni Sato, Keisuke Yamaguchi)</li>
</ul>
</p>
<p>
Other new features:
<ul>
<li>Group rides into seasons (Justin Knotzke)</li>
<li>Better ride calendar (Berend De Schouwer)</li>
<li>Ride list pop-up menu (Thomas Weichmann)</li>
</ul>
</p>
</p>
<ul>
<li>Direct download from SRM (R. Clasen and S. Rhea)
<li>WKO+ file import (M. Liversedge)
<li>Qollector support (M. Rages)
<li>Altitude plotting (T. Weichmann)
<li>Manual ride entry (E. Murray)
<li>Power zones shading (D. Connell)
<li>Weekly summary histograms (R. Carlsen)
<li>Automatic CP estimation from CP graph (D. Connell)
<li>Support for running off a USB stick (J. Knotzke)
<li>OS-specific directory layout (J. Simioni)
<li>PF/PV plot improvements (B. de Schouwer)
<li>Memory leak fixes (G. Lonnon)
</ul>
<p>Thanks also to Jamie Kimberley for extensive testing.
</td>
</tr>
<tr>
<td valign="top">1.2.0</td>
<td valign="top">
<a href="GoldenCheetah_1.2.0_Linux_x86.tgz">Linux x86</a><br>
<a href="GoldenCheetah_1.2.0_Linux_x86_64.tgz">Linux x86_64</a><br>
<a href="GoldenCheetah_1.2.0_Darwin_Universal.dmg">Mac OS X Universal</a><br>
<a href="GoldenCheetah_1.2.0_Windows_Installer.exe">Windows 32-bit</a>
</td>
<td valign="top">
<p>
Lots of new features in this release, including:
</p>
<ul>
<li>Direct download from SRM (R. Clasen and S. Rhea)
<li>WKO+ file import (M. Liversedge)
<li>Qollector support (M. Rages)
<li>Altitude plotting (T. Weichmann)
<li>Manual ride entry (E. Murray)
<li>Power zones shading (D. Connell)
<li>Weekly summary histograms (R. Carlsen)
<li>Automatic CP estimation from CP graph (D. Connell)
<li>Support for running off a USB stick (J. Knotzke)
<li>OS-specific directory layout (J. Simioni)
<li>PF/PV plot improvements (B. de Schouwer)
<li>Memory leak fixes (G. Lonnon)
</ul>
<p>Thanks also to Jamie Kimberley for extensive testing.
</td>
</tr>
<tr>
<td valign="top">1.1.325</td>
<td valign="top">
<a href="GoldenCheetah_1.1.325_Linux_x86.gz">Linux x86</a><br>
<a href="GoldenCheetah_1.1.325_Linux_x86_64.gz">Linux x86_64</a><br>
<a href="GoldenCheetah_1.1.325_Darwin_Universal.dmg">Mac OS X Universal</a><br>
<a href="GoldenCheetah_1.1.325_Windows_Installer.exe">Windows 32-bit</a>
</td>
<td valign="top">
<p>
First official Windows release courtesy of Ned Harding. Ned put much effort into the port to make the download reliable and created a nice installer, too (Thanks Ned!). He also provided the long-awaited Split Ride feature - break up a ride file into separate rides easily using long time gaps and intervals.
<ul>
<li>Ant+Sport PowerTap support.
<li>Split Rides by time gaps or intervals.
<li>Delete ride from list.
<li>Use distance or time for x-axis in Ride Plot (Thanks Damain).
<li>Numerous bug fixes (Thanks Tom, Dan).
</p>
</td>
</tr>
<tr>
<td valign="top">1.0.277</td>
<td valign="top">
<a href="GoldenCheetah_1.0.277_Linux_x86.gz">Linux x86</a>,<br>
<a href="GoldenCheetah_1.0.277_Linux_x86_64.tar.gz">Linux x86_64</a>,<br>
<a href="GoldenCheetah_1.0.277_Darwin_Universal.dmg">Mac OS X Universal</a>
</td>
<td valign="top">
<p>*Note: Beginning with this release we are changing to a numbered versioning system. Minor point releases will generally indicate builds with new features, while bugfix releases will increment the final number, which represents the svn revision*</p>
<p>Several new features in this release: Critical Power calculator, find best intervals utility, Pedal Force / Pedal Velocity chart, iBike and Ergomo CSV import, GUI power zones creator, separate vertical axes for Power / HR / Cadence and Speed in the Ride plot, sorting rides with the most recent at the top of the list, and many bug fixes courtesy of JT Conklin.
</p>
<p>You may need to install <a href="http://www.ftdichip.com/Drivers/D2XX.htm">USB drivers</a> from FTDI.
</p>
<p>
For posterity, the <a href="http://robertcarlsen.net/blog/?page_id=49">beta version</a> for Windows, based on r295.
</p>
</td>
</tr>
<tr>
<td valign="top">Mar 10, 2008</td>
<td valign="top">
<a href="GoldenCheetah_2008-03-10_Linux_x86.tgz">Linux x86</a>,<br>
<a href="GoldenCheetah_2008-03-10_Darwin_Universal.dmg">Mac OS X Universal</a>
</td>
<td valign="top">
This release introduces <a href="http://www.physfarm.com/Analysis%20of%20Power%20Output%20and%20Training%20Stress%20in%20Cyclists-%20BikeScore.pdf">BikeScore&#8482;</a>,
a metric of training stress developed by Dr. Philip Skiba. It also
fixes several small bugs in earlier releases.
</td>
</tr>
<tr>
<td valign="top">Sep 23, 2007</td>
<td valign="top">
<a href="GoldenCheetah_2007-09-23_Linux_x86.tgz">Linux x86</a>,<br>
<a href="GoldenCheetah_2007-09-23_Darwin_i386.dmg">Mac OS X x86</a>,<br>
<a href="GoldenCheetah_2007-09-23_Darwin_powerpc.dmg">Mac OS X PowerPC</a>
</td>
<td valign="top">
Bug fix release. CVS imports weren't quite working in the last one.
</td>
</tr>
<tr>
<td valign="top">Sep 18, 2007</td>
<td valign="top">
<a href="GoldenCheetah_2007-09-18_Linux_x86.tgz">Linux x86</a>,<br>
<a href="GoldenCheetah_2007-09-18_Darwin_powerpc.dmg">Mac OS X PowerPC</a>
</td>
<td valign="top">
This release adds two small, but excellent features from Justin Knotzke:
CSV file imports and visual interval markers in the ride plot.
</td>
</tr>
<tr>
<td valign="top">Aug 7, 2007</td>
<td valign="top">
<a href="GoldenCheetah_2007-08-07_Linux_x86.tgz">Linux x86</a>,<br>
<a href="GoldenCheetah_2007-08-07_Darwin_powerpc.dmg">Mac OS X PowerPC</a>
</td>
<td valign="top">This release fixes a bug in the critical power
intervals graph where you could get bad data if you started an interval
after a long period of not moving. It also adds really basic zooming to
the ride plot: use the left mouse button to zoom in and the right one to
return to the previous zoom state. It's pretty crappy right now, but
it's better than nothing.
</td>
</tr>
<tr>
<td valign="top">Apr 26, 2007</td>
<td valign="top">
<a href="GoldenCheetah_2007-04-26_Linux_x86.tgz">Linux x86</a>,<br>
<a href="GoldenCheetah_2007-04-26_Darwin_powerpc.dmg">Mac OS X PowerPC</a>
</td>
<td valign="top">This release fixes some bugs and adds a whole bunch of
new features:
<ul>
<li>Now imports .srm files (direct download from SRM hopefully coming
soon)
<li>New "Weekly Summary" tab shows total weekly hours, miles, and work
<li>Power zones can now be entered into a text file, after which GC will
display time in each zone in the ride and weekly summaries; for more
information on the zone file format, <a href="zones.html">see this
page</a>.
</ul>
</td>
</tr>
<tr>
<td valign="top">Apr 1, 2007</td>
<td valign="top">
<a href="GoldenCheetah_2007-04-01_Linux_x86.tgz">Linux x86</a>,<br>
<a href="GoldenCheetah_2007-04-01_Darwin_powerpc.dmg">Mac OS X PowerPC</a>
</td>
<td valign="top">This release fixes a bug that was introduced with the
hardware echo detection code. If you're using the CycleOps-supplied USB
cable to download from your PowerTap unit, this release should make
downloads more reliable. (Those using the KeySpan USB-to-serial adaptor
or a plain-old serial port shouldn't see any difference.)</td>
</tr>
<tr>
<td valign="top">Feb 22, 2007</td>
<td valign="top">
<a href="GoldenCheetah_2007-02-22_Linux_x86.tgz">Linux x86</a>,<br>
<a href="GoldenCheetah_2007-02-22_Darwin_powerpc.dmg">Mac OS X PowerPC</a>
</td>
<td valign="top">Clicking on the Critical Power Plot now displays the
interval duration, maximum power for that ride, and maximum power for all
rides below the plot. Also fixes a bug for recording intervals longer than
two seconds.</td>
</tr>
<tr>
<td valign="top">Feb 12, 2007</td>
<td valign="top">
<a href="GoldenCheetah_2007-02-12_Linux_x86.tgz">Linux x86</a>,<br>
<a href="GoldenCheetah_2007-02-12_Darwin_powerpc.dmg">Mac OS X PowerPC</a>
</td>
<td valign="top">Interval information now included in ride summary, rides can
now be exported as comma-separated values for import into Excel, and better
automatic detection of hardware echo. Also includes a number of bux
fixes.</td>
</tr>
<tr>
<td valign="top">Jan 30, 2007</td>
<td valign="top">
<a href="GoldenCheetah_2007-01-30_Linux_x86.tgz">Linux x86</a>,<br>
<a href="GoldenCheetah_2007-01-30_Darwin_powerpc.dmg">Mac OS X PowerPC</a>
</td>
<td valign="top">Bug fix release.</td>
</tr>
<tr>
<td valign="top">Jan 6, 2007</td>
<td valign="top"><a href="GoldenCheetah_2007-01-06_Linux_x86.tgz">Linux
x86</a></td>
<td valign="top">First release for Linux.</td>
</tr>
<tr>
<td valign="top">Dec 25, 2006</td>
<td valign="top"><a href="GoldenCheetah_2006-12-25_Darwin_powerpc.dmg">Mac OS
X PowerPC</a></td>
<td valign="top">Adds the Power Histogram, which shows how much time a rider
spent at each particular power level during a ride.</td>
</tr>
<tr>
<td valign="top">Sep 19, 2006</td>
<td valign="top"><a href="GoldenCheetah_2006-09-19_Darwin_powerpc.dmg">Mac OS
X PowerPC</a></td>
<td valign="top">Adds the Critical Power Plot, which shows the highest average
power you've achieved for every interval length over all your rides and the
selected ride. Also shows download progress in minutes of ride data
downloaded.</td>
</tr>
<tr>
<td valign="top">Sep 7, 2006</td>
<td valign="top"><a href="GoldenCheetah_2006-09-07_Darwin_powerpc.dmg">Mac OS
X PowerPC</a></td>
<td valign="top">Adds speed and cadence to the ride plot. Fixes a bug
found by George Gilliland where reseting the time during a ride could cause
the GUI to crash.</td>
</tr>
<tr>
<td valign="top">Sep 6, 2006</td>
<td valign="top"><a href="GoldenCheetah_2006-09-06_Darwin_powerpc.dmg">Mac OS
X PowerPC</a></td>
<td valign="top">The first release of the Golden Cheetah GUI.</td>
</tr>
</table>
</center>
<p>
<hr width="50%">
<p>
<font face="arial,helvetica,sanserif">
<big><strong>Older Stuff</strong></big>
</font>
<p>
These are the older, source-only, command-line distributions. I've left them
up for historical purposes only; I don't recommend using them.
<center>
<table width="100%" cellspacing="10">
<tr>
<td width="20%"><i>Date</i></td>
<td width="30%"><i>File</i></td>
<td><i>Description</i></td>
</tr>
<tr>
<td valign="top">Aug 11, 2006</td>
<td valign="top"><a href="gc_2006-08-11.tgz">gc_2006-08-11.tgz</a></td>
<td valign="top">ptdl now works with Keyspan USB-to-serial adaptor, after
debugging help from Rob Carlsen.
</td>
</tr>
<tr>
<td valign="top">May 27, 2006</td>
<td valign="top"><a href="gc_2006-05-27.tgz">gc_2006-05-27.tgz</a></td>
<td valign="top">Adds the <code>cpint</code> program for computing critical
power intervals and the <code>ptpk</code> program for converting from
PowerTuned data files (see the <a href="users-guide.html">User's
Guide</a>).</td>
</tr>
<tr>
<td valign="top">May 16, 2006</td>
<td valign="top"><a href="gc_2006-05-16.tgz">gc_2006-05-16.tgz</a></td>
<td valign="top">The first code release, containing <code>ptdl</code> and
<code>ptunpk</code>.</td>
</tr>
</table>
</center>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

View File

@@ -1,81 +0,0 @@
<p>
<font face="arial,helvetica,sanserif">
<big><strong>GoldenCheetah 2.0</strong></big>
</font>
</p>
<p>
New Features
<ul>
<li>Aerolab (Andy Froncioni)</li>
<li>View ride in Google Maps (Greg Lonnon)</li>
<li>Long Term Metrics (Mark Liversedge)</li>
<li>User configurable ride metadata (Mark Liversedge)</li>
<li>Ride editor and tools (Mark Liversedge)</li>
<li>HR Zones and TRIMP Metrics (Damien Grauser)</li>
<li>Twitter support (Justin Knotzke)</li>
</ul>
</p>
<p>
Internationalisation
<ul>
<li>Updates to French translation (Damien Grauser)</li>
<li>Japanese translation (Mitsukuni Sato)</li>
</ul>
</p>
<p>
New Logo and Icons
<ul>
<li>Golden Cheetah Logo(Dan Schmalz)</li>
</ul>
</p>
<p>
Enhanced Ride Plot
<ul>
<li>Ride plot stacked view (Damien Grauser)</li>
<li>Scrolling Ride Plot (Mark Liversedge)</li>
</ul>
</p>
<p>
New Devices and File Formats Supported
<ul>
<li>Support for Joule BIN File Format (Damien Grauser)</li>
<li>Tacx CAF Ride File Format Support (Ilja Booij)</li>
<li>Garmin FIT ride file support (Sean Rhea)</li>
<li>Export to Google Earth 5.2 KML (Mark Liversedge)</li>
<li>Training Peaks PWX ride file support (Mark Liversedge)</li>
<li>Polar SRD ride file support (Mark Liversedge)</li>
<li>Racermate CompCS/Ergvideo .TXT ride file support (Mark Liversedge)</li>
</ul>
<p>
<p>
Numerous enhancements and bug fixes from
<ul>
<li>Julian Baumgartner</li>
<li>Robert Carlsen</li>
<li>Rainer Clasen</li>
<li>Gareth Coco</li>
<li>Dag Gruneau</li>
<li>Jamie Kimberley</li>
<li>Jim Ley</li>
<li>Patrick J. McNerthney</li>
<li>Austin Roach</li>
<li>Ken Sallot</li>
<li>Thomas Weichmann</li>
</ul>
</p>
<p>
Builds, testing and support
<ul>
<li>Robert Carlsen</li>
<li>Gareth Coco</li>
<li>Jamie Kimberley</li>
<li>Justin Knotzke</li>
</ul>
</p>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 192 KiB

After

Width:  |  Height:  |  Size: 197 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 195 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 134 KiB

After

Width:  |  Height:  |  Size: 179 KiB

View File

@@ -17,13 +17,6 @@ Plotting Altitude, Cadence, Heart Rate, Power, and Speed
<p>
<img src="ride-plot.png" alt="Power and HR Plot" align="center">
<p>
<big><font face="arial,helvetica,sanserif">
Plotting in a Stacked View
</font></big>
<p>
<img src="ride-plot2.png" alt="Stacked Power and HR Plot" align="center">
<p>
<big><font face="arial,helvetica,sanserif">
Plotting Critical Power
@@ -52,66 +45,6 @@ The Weekly Summary
<p>
<img src="weekly-summary.png" alt="The Weekly Summary" align="center">
<p>
<big><font face="arial,helvetica,sanserif">
Plot from a selection of over 30 metrics
</font></big>
<p>
<img src="metrics-power.png" alt="The Power Metrics" align="center">
<p>
<big><font face="arial,helvetica,sanserif">
Including Time and Distance
</font></big>
<p>
<img src="metrics-timedist.png" alt="The Time and Distance" align="center">
<p>
<big><font face="arial,helvetica,sanserif">
Time In Zone
</font></big>
<p>
<img src="metrics-tiz.png" alt="The Time In Zone" align="center">
<p>
<big><font face="arial,helvetica,sanserif">
Plot with Google Maps
</font></big>
<p>
<img src="map.png" alt="Google Maps" align="center">
<p>
<big><font face="arial,helvetica,sanserif">
Plot in 3 Dimensions
</font></big>
<p>
<img src="3d.png" alt="The 3d plot" align="center">
<p>
<big><font face="arial,helvetica,sanserif">
Edit and Correct Ride Data
</font></big>
<p>
<img src="editor.png" alt="The Ride Editor" align="center">
<p>
<big><font face="arial,helvetica,sanserif">
The Performance Manager
</font></big>
<p>
<img src="pm.png" alt="The Performance Manager" align="center">
<p>
<big><font face="arial,helvetica,sanserif">
Train with a Computrainer or ANT+ Device
</font></big>
<p>
<img src="realtime.png" alt="The Realtime Window" align="center">
<p>
<big><font face="arial,helvetica,sanserif">
Export to Google Earth 5.2
</font></big>
<p>
<img src="google-earth.png" alt="Google Earth 5.2" align="center">
<p>
<big><font face="arial,helvetica,sanserif">
The Aerolab
</font></big>
<p>
<img src="aerolab.png" alt="Aerolab" align="center">
</center>

View File

@@ -1,35 +1,17 @@
<!-- $Id: users-guide.content,v 1.5 2006/05/27 16:32:46 srhea Exp $ -->
<p>
Note that more detailed information is often available on the
<a href = http://bugs.goldencheetah.org/projects/goldencheetah/wiki>
Golden Cheetah Wiki</a>.
<p>
What follows is a brief step-by-step guide to installing and setting up
Golden Cheetah.
<p>
<big><font face="arial,helvetica,sanserif">
Step 1 (optional): Installing the FTDI drivers
Step 1: Installing the FTDI drivers
</font></big>
<p>
This step is only needed if you want to download rides from a powertap
pro/comp/cervo head unit via the supplied USB cradle. Furthermore, most
Windows and Linux systems should recognize the device without installing
the drivers below.
<p>
Depending on your operating system, you <i>may</i> need to install the
<ahref="http://www.ftdichip.com/Drivers/D2XX.htm">FTDI D2XX driver</a> if
you're using the PowerTap's new USB download cradle.
Note: version 0.1.7 of the FTDI drivers for Mac seems to be buggy. Until they
post a patched version, you can download version 0.1.6
<a href="http://bugs.goldencheetah.org/attachments/download/1/Universal_D2XX0.1.6.dmg"> here</a>
and install via the terminal. Or if you are not terminal savvy, download an installer that will
perform the installation of the 0.1.6 drivers for you
<a href="http://bugs.goldencheetah.org/attachments/download/248/Install_D2XX_drivers.mpkg.zip">
here</a>.
Depending on your operating system, you may need to install the <a
href="http://www.ftdichip.com/Drivers/D2XX.htm">FTDI USB
driver</a> if you're using the PowerTap's new USB download cradle. The FTDI USB drivers are an optional install if you do not plan on downloading from your device using Golden Cheetah.
(Note: version 1.7 of the FTDI drivers for Mac seems to be buggy. Until they
post a patched version, you can download version 1.6
<a href="http://bugs.goldencheetah.org/attachments/download/1/Universal_D2XX0.1.6.dmg">here</a>.)
</p>
<p>
If you're running Linux, you may also need to uninstall the <code>brtty</code>
@@ -64,20 +46,20 @@ the <code>.dmg</code> file for you. If not, double-click to open it. Drag
the GoldenCheetah icon into your Applications folder, and you're done.
<p>
The Linux version of GoldenCheetah is distributed as a GZipped archive.
Download this file and save it to <code>/tmp</code>, then from a terminal:
The Linux version of GoldenCheetah is distributed as a tarball. Download this
file and save it to <code>/tmp</code>, then from a terminal:
<pre>
cd /tmp
gunzip -vf GoldenCheetah_X.X.X_Linux_x86.gz
cd GoldenCheetah_X.X.X_Linux_x86
tar xzvf GoldenCheetah_DATE_Linux_x86.tgz
cd GoldenCheetah_DATE_Linux_x86
sudo cp GoldenCheetah /usr/local/bin
cd ..
rm -rf GoldenCheetah_X.X.X_Linux_x86.gz
rm -rf GoldenCheetah_DATE_Linux_x86.tgz
</pre>
Be sure to replace "X.X.X" with the version of the release you downloaded,
such as "2.0.0".
Be sure to replace "DATE" with the date of the revision you downloaded, such
as "2007-09-23".
<p>
<big><font face="arial,helvetica,sanserif">
@@ -249,7 +231,7 @@ based on percentages of your CP value. The zones are:
<td>150%</td>
</tr>
<tr>
<td>Z7</td>
<td>Z1</td>
<td>Neuromuscular</td>
<td>150%</td>
<td>MAX</td>

View File

@@ -8,9 +8,18 @@ tracker</a>. Instructions for doing so are <a href="bug-tracker.html">here</a>.
<p>
Examples of some features are:
<ul>
<li>Graph ride metrics (daily hours, work, BikeScore) over the long
term (weeks, seasons)</li>
<li>Display the numbers at the bottom of the ride plot, like the
critical power graph does</li>
<li>Select intervals in the ride plot and display metrics for them
at the bottom</li>
<li>Pop up an "importing rides" thermometer when importing</li>
<li>Remember last settings for showPower, showHr, etc., in ride plot</li>
<li>Group rides list into seasons</li>
<li>Group rides list by type, course</li>
<li>Add lines to CP plot for seasons, last six (eight?) weeks, etc.</li>
<li>Create new intervals</li>
<li>Show mulitple rides (seasons, etc.) in power histogram</li>
<li>Annotate ride plot</li>
<li>Label rides by type, course</li>

View File

@@ -114,7 +114,7 @@ CONFIG += QwtWidgets
# Otherwise you have to build it from the designer directory.
######################################################################
#CONFIG += QwtDesigner
CONFIG += QwtDesigner
######################################################################
# If you want to auto build the examples, enable the line below

View File

@@ -1,207 +0,0 @@
/****************************************************************************
**
** Copyright (C) Qxt Foundation. Some rights reserved.
**
** This file is part of the QxtCore module of the Qxt library.
**
** This library is free software; you can redistribute it and/or modify it
** under the terms of the Common Public License, version 1.0, as published
** by IBM, and/or under the terms of the GNU Lesser General Public License,
** version 2.1, as published by the Free Software Foundation.
**
** This file is provided "AS IS", without WARRANTIES OR CONDITIONS OF ANY
** KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY
** WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR
** FITNESS FOR A PARTICULAR PURPOSE.
**
** You should have received a copy of the CPL and the LGPL along with this
** file. See the LICENSE file and the cpl1.0.txt/lgpl-2.1.txt files
** included with the source distribution for more information.
** If you did not receive a copy of the licenses, contact the Qxt Foundation.
**
** <http://libqxt.org> <foundation@libqxt.org>
**
****************************************************************************/
#ifndef QXTGLOBAL_H
#define QXTGLOBAL_H
#include <QtGlobal>
#define QXT_VERSION 0x000700
#define QXT_VERSION_STR "0.7.0"
//--------------------------global macros------------------------------
#ifndef QXT_NO_MACROS
#endif // QXT_NO_MACROS
//--------------------------export macros------------------------------
#define QXT_DLLEXPORT DO_NOT_USE_THIS_ANYMORE
#if !defined(QXT_STATIC)
# if defined(BUILD_QXT_CORE)
# define QXT_CORE_EXPORT Q_DECL_EXPORT
# else
# define QXT_CORE_EXPORT Q_DECL_IMPORT
# endif
#else
# define QXT_CORE_EXPORT
#endif // BUILD_QXT_CORE
#if !defined(QXT_STATIC)
# if defined(BUILD_QXT_GUI)
# define QXT_GUI_EXPORT Q_DECL_EXPORT
# else
# define QXT_GUI_EXPORT Q_DECL_IMPORT
# endif
#else
# define QXT_GUI_EXPORT
#endif // BUILD_QXT_GUI
#if !defined(QXT_STATIC)
# if defined(BUILD_QXT_NETWORK)
# define QXT_NETWORK_EXPORT Q_DECL_EXPORT
# else
# define QXT_NETWORK_EXPORT Q_DECL_IMPORT
# endif
#else
# define QXT_NETWORK_EXPORT
#endif // BUILD_QXT_NETWORK
#if !defined(QXT_STATIC)
# if defined(BUILD_QXT_SQL)
# define QXT_SQL_EXPORT Q_DECL_EXPORT
# else
# define QXT_SQL_EXPORT Q_DECL_IMPORT
# endif
#else
# define QXT_SQL_EXPORT
#endif // BUILD_QXT_SQL
#if !defined(QXT_STATIC)
# if defined(BUILD_QXT_WEB)
# define QXT_WEB_EXPORT Q_DECL_EXPORT
# else
# define QXT_WEB_EXPORT Q_DECL_IMPORT
# endif
#else
# define QXT_WEB_EXPORT
#endif // BUILD_QXT_WEB
#if !defined(QXT_STATIC)
# if defined(BUILD_QXT_BERKELEY)
# define QXT_BERKELEY_EXPORT Q_DECL_EXPORT
# else
# define QXT_BERKELEY_EXPORT Q_DECL_IMPORT
# endif
#else
# define QXT_BERKELEY_EXPORT
#endif // BUILD_QXT_BERKELEY
#if !defined(QXT_STATIC)
# if defined(BUILD_QXT_ZEROCONF)
# define QXT_ZEROCONF_EXPORT Q_DECL_EXPORT
# else
# define QXT_ZEROCONF_EXPORT Q_DECL_IMPORT
# endif
#else
# define QXT_ZEROCONF_EXPORT
#endif // QXT_ZEROCONF_EXPORT
#if defined BUILD_QXT_CORE || defined BUILD_QXT_GUI || defined BUILD_QXT_SQL || defined BUILD_QXT_NETWORK || defined BUILD_QXT_WEB || defined BUILD_QXT_BERKELEY || defined BUILD_QXT_ZEROCONF
# define BUILD_QXT
#endif
QXT_CORE_EXPORT const char* qxtVersion();
#ifndef QT_BEGIN_NAMESPACE
#define QT_BEGIN_NAMESPACE
#endif
#ifndef QT_END_NAMESPACE
#define QT_END_NAMESPACE
#endif
#ifndef QT_FORWARD_DECLARE_CLASS
#define QT_FORWARD_DECLARE_CLASS(Class) class Class;
#endif
/****************************************************************************
** This file is derived from code bearing the following notice:
** The sole author of this file, Adam Higerd, has explicitly disclaimed all
** copyright interest and protection for the content within. This file has
** been placed in the public domain according to United States copyright
** statute and case law. In jurisdictions where this public domain dedication
** is not legally recognized, anyone who receives a copy of this file is
** permitted to use, modify, duplicate, and redistribute this file, in whole
** or in part, with no restrictions or conditions. In these jurisdictions,
** this file shall be copyright (C) 2006-2008 by Adam Higerd.
****************************************************************************/
#define QXT_DECLARE_PRIVATE(PUB) friend class PUB##Private; QxtPrivateInterface<PUB, PUB##Private> qxt_d;
#define QXT_DECLARE_PUBLIC(PUB) friend class PUB;
#define QXT_INIT_PRIVATE(PUB) qxt_d.setPublic(this);
#define QXT_D(PUB) PUB##Private& d = qxt_d()
#define QXT_P(PUB) PUB& p = qxt_p()
template <typename PUB>
class QxtPrivate
{
public:
virtual ~QxtPrivate()
{}
inline void QXT_setPublic(PUB* pub)
{
qxt_p_ptr = pub;
}
protected:
inline PUB& qxt_p()
{
return *qxt_p_ptr;
}
inline const PUB& qxt_p() const
{
return *qxt_p_ptr;
}
private:
PUB* qxt_p_ptr;
};
template <typename PUB, typename PVT>
class QxtPrivateInterface
{
friend class QxtPrivate<PUB>;
public:
QxtPrivateInterface()
{
pvt = new PVT;
}
~QxtPrivateInterface()
{
delete pvt;
}
inline void setPublic(PUB* pub)
{
pvt->QXT_setPublic(pub);
}
inline PVT& operator()()
{
return *static_cast<PVT*>(pvt);
}
inline const PVT& operator()() const
{
return *static_cast<PVT*>(pvt);
}
private:
QxtPrivateInterface(const QxtPrivateInterface&) { }
QxtPrivateInterface& operator=(const QxtPrivateInterface&) { }
QxtPrivate<PUB>* pvt;
};
#endif // QXT_GLOBAL

View File

@@ -1,106 +0,0 @@
/****************************************************************************
**
** Copyright (C) Qxt Foundation. Some rights reserved.
**
** This file is part of the QxtCore module of the Qxt library.
**
** This library is free software; you can redistribute it and/or modify it
** under the terms of the Common Public License, version 1.0, as published
** by IBM, and/or under the terms of the GNU Lesser General Public License,
** version 2.1, as published by the Free Software Foundation.
**
** This file is provided "AS IS", without WARRANTIES OR CONDITIONS OF ANY
** KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY
** WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR
** FITNESS FOR A PARTICULAR PURPOSE.
**
** You should have received a copy of the CPL and the LGPL along with this
** file. See the LICENSE file and the cpl1.0.txt/lgpl-2.1.txt files
** included with the source distribution for more information.
** If you did not receive a copy of the licenses, contact the Qxt Foundation.
**
** <http://libqxt.org> <foundation@libqxt.org>
**
****************************************************************************/
#ifndef QXTNAMESPACE_H
#define QXTNAMESPACE_H
#include <qxtglobal.h>
#if (defined BUILD_QXT | defined Q_MOC_RUN) && !defined(QXT_DOXYGEN_RUN)
#include <QObject>
class QXT_CORE_EXPORT Qxt : public QObject
{
Q_OBJECT
Q_ENUMS(Rotation)
Q_ENUMS(DecorationStyle)
Q_ENUMS(ErrorCode)
public:
#else
namespace Qxt
{
#endif
enum Rotation
{
NoRotation = 0,
UpsideDown = 180,
Clockwise = 90,
CounterClockwise = 270
};
enum DecorationStyle
{
NoDecoration,
Buttonlike,
Menulike
};
enum ErrorCode
{
NoError,
UnknownError,
LogicalError,
Bug,
UnexpectedEndOfFunction,
NotImplemented,
CodecError,
NotInitialised,
EndOfFile,
FileIOError,
FormatError,
DeviceError,
SDLError,
InsufficientMemory,
SeeErrorString,
UnexpectedNullParameter,
ClientTimeout,
SocketIOError,
ParserError,
HeaderTooLong,
Auth,
Overflow
};
enum QxtItemDataRole
{
ItemStartTimeRole = Qt::UserRole + 1,
ItemDurationRole = ItemStartTimeRole + 1,
UserRole = ItemDurationRole + 23
};
enum Timeunit
{
Second,
Minute,
Hour,
Day,
Week,
Month,
Year
};
};
#endif // QXTNAMESPACE_H

View File

@@ -1,748 +0,0 @@
/****************************************************************************
**
** Copyright (C) Qxt Foundation. Some rights reserved.
**
** This file is part of the QxtGui module of the Qxt library.
**
** This library is free software; you can redistribute it and/or modify it
** under the terms of the Common Public License, version 1.0, as published
** by IBM, and/or under the terms of the GNU Lesser General Public License,
** version 2.1, as published by the Free Software Foundation.
**
** This file is provided "AS IS", without WARRANTIES OR CONDITIONS OF ANY
** KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY
** WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR
** FITNESS FOR A PARTICULAR PURPOSE.
**
** You should have received a copy of the CPL and the LGPL along with this
** file. See the LICENSE file and the cpl1.0.txt/lgpl-2.1.txt files
** included with the source distribution for more information.
** If you did not receive a copy of the licenses, contact the Qxt Foundation.
**
** <http://libqxt.org> <foundation@libqxt.org>
**
****************************************************************************/
#include "qxtspanslider.h"
#include "qxtspanslider_p.h"
#include <QKeyEvent>
#include <QMouseEvent>
#include <QApplication>
#include <QStylePainter>
#include <QStyleOptionSlider>
QxtSpanSliderPrivate::QxtSpanSliderPrivate() :
lower(0),
upper(0),
lowerPos(0),
upperPos(0),
offset(0),
position(0),
lastPressed(QxtSpanSlider::NoHandle),
mainControl(QxtSpanSlider::LowerHandle),
lowerPressed(QStyle::SC_None),
upperPressed(QStyle::SC_None),
movement(QxtSpanSlider::FreeMovement),
firstMovement(false),
blockTracking(false)
{
}
void QxtSpanSliderPrivate::initStyleOption(QStyleOptionSlider* option, QxtSpanSlider::SpanHandle handle) const
{
const QxtSpanSlider* p = &qxt_p();
p->initStyleOption(option);
option->sliderPosition = (handle == QxtSpanSlider::LowerHandle ? lowerPos : upperPos);
option->sliderValue = (handle == QxtSpanSlider::LowerHandle ? lower : upper);
}
int QxtSpanSliderPrivate::pixelPosToRangeValue(int pos) const
{
QStyleOptionSlider opt;
initStyleOption(&opt);
int sliderMin = 0;
int sliderMax = 0;
int sliderLength = 0;
const QSlider* p = &qxt_p();
const QRect gr = p->style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderGroove, p);
const QRect sr = p->style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderHandle, p);
if (p->orientation() == Qt::Horizontal)
{
sliderLength = sr.width();
sliderMin = gr.x();
sliderMax = gr.right() - sliderLength + 1;
}
else
{
sliderLength = sr.height();
sliderMin = gr.y();
sliderMax = gr.bottom() - sliderLength + 1;
}
return QStyle::sliderValueFromPosition(p->minimum(), p->maximum(), pos - sliderMin,
sliderMax - sliderMin, opt.upsideDown);
}
void QxtSpanSliderPrivate::handleMousePress(const QPoint& pos, QStyle::SubControl& control, int value, QxtSpanSlider::SpanHandle handle)
{
QStyleOptionSlider opt;
initStyleOption(&opt, handle);
QxtSpanSlider* p = &qxt_p();
const QStyle::SubControl oldControl = control;
control = p->style()->hitTestComplexControl(QStyle::CC_Slider, &opt, pos, p);
const QRect sr = p->style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderHandle, p);
if (control == QStyle::SC_SliderHandle)
{
position = value;
offset = pick(pos - sr.topLeft());
lastPressed = handle;
p->setSliderDown(true);
emit p->sliderPressed(handle);
}
if (control != oldControl)
p->update(sr);
}
void QxtSpanSliderPrivate::setupPainter(QPainter* painter, Qt::Orientation orientation, qreal x1, qreal y1, qreal x2, qreal y2) const
{
QColor highlight = qxt_p().palette().color(QPalette::Highlight);
QLinearGradient gradient(x1, y1, x2, y2);
gradient.setColorAt(0, highlight.dark(120));
gradient.setColorAt(1, highlight.light(108));
painter->setBrush(gradient);
if (orientation == Qt::Horizontal)
painter->setPen(QPen(highlight.dark(130), 0));
else
painter->setPen(QPen(highlight.dark(150), 0));
}
void QxtSpanSliderPrivate::drawSpan(QStylePainter* painter, const QRect& rect) const
{
QStyleOptionSlider opt;
initStyleOption(&opt);
const QSlider* p = &qxt_p();
// area
QRect groove = p->style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderGroove, p);
if (opt.orientation == Qt::Horizontal)
groove.adjust(0, 0, -1, 0);
else
groove.adjust(0, 0, 0, -1);
// pen & brush
painter->setPen(QPen(p->palette().color(QPalette::Dark).light(110), 0));
if (opt.orientation == Qt::Horizontal)
setupPainter(painter, opt.orientation, groove.center().x(), groove.top(), groove.center().x(), groove.bottom());
else
setupPainter(painter, opt.orientation, groove.left(), groove.center().y(), groove.right(), groove.center().y());
// draw groove
painter->drawRect(rect.intersected(groove));
}
void QxtSpanSliderPrivate::drawHandle(QStylePainter* painter, QxtSpanSlider::SpanHandle handle) const
{
QStyleOptionSlider opt;
initStyleOption(&opt, handle);
opt.subControls = QStyle::SC_SliderHandle;
QStyle::SubControl pressed = (handle == QxtSpanSlider::LowerHandle ? lowerPressed : upperPressed);
if (pressed == QStyle::SC_SliderHandle)
{
opt.activeSubControls = pressed;
opt.state |= QStyle::State_Sunken;
}
painter->drawComplexControl(QStyle::CC_Slider, opt);
}
void QxtSpanSliderPrivate::triggerAction(QAbstractSlider::SliderAction action, bool main)
{
int value = 0;
bool no = false;
bool up = false;
const int min = qxt_p().minimum();
const int max = qxt_p().maximum();
const QxtSpanSlider::SpanHandle altControl = (mainControl == QxtSpanSlider::LowerHandle ? QxtSpanSlider::UpperHandle : QxtSpanSlider::LowerHandle);
blockTracking = true;
switch (action)
{
case QAbstractSlider::SliderSingleStepAdd:
if ((main && mainControl == QxtSpanSlider::UpperHandle) || (!main && altControl == QxtSpanSlider::UpperHandle))
{
value = qBound(min, upper + qxt_p().singleStep(), max);
up = true;
break;
}
value = qBound(min, lower + qxt_p().singleStep(), max);
break;
case QAbstractSlider::SliderSingleStepSub:
if ((main && mainControl == QxtSpanSlider::UpperHandle) || (!main && altControl == QxtSpanSlider::UpperHandle))
{
value = qBound(min, upper - qxt_p().singleStep(), max);
up = true;
break;
}
value = qBound(min, lower - qxt_p().singleStep(), max);
break;
case QAbstractSlider::SliderToMinimum:
value = min;
if ((main && mainControl == QxtSpanSlider::UpperHandle) || (!main && altControl == QxtSpanSlider::UpperHandle))
up = true;
break;
case QAbstractSlider::SliderToMaximum:
value = max;
if ((main && mainControl == QxtSpanSlider::UpperHandle) || (!main && altControl == QxtSpanSlider::UpperHandle))
up = true;
break;
case QAbstractSlider::SliderMove:
if ((main && mainControl == QxtSpanSlider::UpperHandle) || (!main && altControl == QxtSpanSlider::UpperHandle))
up = true;
case QAbstractSlider::SliderNoAction:
no = true;
break;
default:
qWarning("QxtSpanSliderPrivate::triggerAction: Unknown action");
break;
}
if (!no && !up)
{
if (movement == QxtSpanSlider::NoCrossing)
value = qMin(value, upper);
else if (movement == QxtSpanSlider::NoOverlapping)
value = qMin(value, upper - 1);
if (movement == QxtSpanSlider::FreeMovement && value > upper)
{
swapControls();
qxt_p().setUpperPosition(value);
}
else
{
qxt_p().setLowerPosition(value);
}
}
else if (!no)
{
if (movement == QxtSpanSlider::NoCrossing)
value = qMax(value, lower);
else if (movement == QxtSpanSlider::NoOverlapping)
value = qMax(value, lower + 1);
if (movement == QxtSpanSlider::FreeMovement && value < lower)
{
swapControls();
qxt_p().setLowerPosition(value);
}
else
{
qxt_p().setUpperPosition(value);
}
}
blockTracking = false;
qxt_p().setLowerValue(lowerPos);
qxt_p().setUpperValue(upperPos);
}
void QxtSpanSliderPrivate::swapControls()
{
qSwap(lower, upper);
qSwap(lowerPressed, upperPressed);
lastPressed = (lastPressed == QxtSpanSlider::LowerHandle ? QxtSpanSlider::UpperHandle : QxtSpanSlider::LowerHandle);
mainControl = (mainControl == QxtSpanSlider::LowerHandle ? QxtSpanSlider::UpperHandle : QxtSpanSlider::LowerHandle);
}
void QxtSpanSliderPrivate::updateRange(int min, int max)
{
Q_UNUSED(min);
Q_UNUSED(max);
// setSpan() takes care of keeping span in range
qxt_p().setSpan(lower, upper);
}
void QxtSpanSliderPrivate::movePressedHandle()
{
switch (lastPressed)
{
case QxtSpanSlider::LowerHandle:
if (lowerPos != lower)
{
bool main = (mainControl == QxtSpanSlider::LowerHandle);
triggerAction(QAbstractSlider::SliderMove, main);
}
break;
case QxtSpanSlider::UpperHandle:
if (upperPos != upper)
{
bool main = (mainControl == QxtSpanSlider::UpperHandle);
triggerAction(QAbstractSlider::SliderMove, main);
}
break;
default:
break;
}
}
/*!
\class QxtSpanSlider
\inmodule QxtGui
\brief The QxtSpanSlider widget is a QSlider with two handles.
QxtSpanSlider is a slider with two handles. QxtSpanSlider is
handy for letting user to choose an span between min/max.
The span color is calculated based on QPalette::Highlight.
The keys are bound according to the following table:
\table
\header \o Orientation \o Key \o Handle
\row \o Qt::Horizontal \o Qt::Key_Left \o lower
\row \o Qt::Horizontal \o Qt::Key_Right \o lower
\row \o Qt::Horizontal \o Qt::Key_Up \o upper
\row \o Qt::Horizontal \o Qt::Key_Down \o upper
\row \o Qt::Vertical \o Qt::Key_Up \o lower
\row \o Qt::Vertical \o Qt::Key_Down \o lower
\row \o Qt::Vertical \o Qt::Key_Left \o upper
\row \o Qt::Vertical \o Qt::Key_Right \o upper
\endtable
Keys are bound by the time the slider is created. A key is bound
to same handle for the lifetime of the slider. So even if the handle
representation might change from lower to upper, the same key binding
remains.
\image qxtspanslider.png "QxtSpanSlider in Plastique style."
\bold {Note:} QxtSpanSlider inherits QSlider for implementation specific
reasons. Adjusting any single handle specific properties like
\list
\o QAbstractSlider::sliderPosition
\o QAbstractSlider::value
\endlist
has no effect. However, all slider specific properties like
\list
\o QAbstractSlider::invertedAppearance
\o QAbstractSlider::invertedControls
\o QAbstractSlider::minimum
\o QAbstractSlider::maximum
\o QAbstractSlider::orientation
\o QAbstractSlider::pageStep
\o QAbstractSlider::singleStep
\o QSlider::tickInterval
\o QSlider::tickPosition
\endlist
are taken into consideration.
*/
/*!
\enum QxtSpanSlider::HandleMovementMode
This enum describes the available handle movement modes.
\value FreeMovement The handles can be moved freely.
\value NoCrossing The handles cannot cross, but they can still overlap each other. The lower and upper values can be the same.
\value NoOverlapping The handles cannot overlap each other. The lower and upper values cannot be the same.
*/
/*!
\enum QxtSpanSlider::SpanHandle
This enum describes the available span handles.
\omitvalue NoHandle \omit Internal only (for now). \endomit
\value LowerHandle The lower boundary handle.
\value UpperHandle The upper boundary handle.
*/
/*!
\fn QxtSpanSlider::lowerValueChanged(int lower)
This signal is emitted whenever the \a lower value has changed.
*/
/*!
\fn QxtSpanSlider::upperValueChanged(int upper)
This signal is emitted whenever the \a upper value has changed.
*/
/*!
\fn QxtSpanSlider::spanChanged(int lower, int upper)
This signal is emitted whenever both the \a lower and the \a upper
values have changed ie. the span has changed.
*/
/*!
\fn QxtSpanSlider::lowerPositionChanged(int lower)
This signal is emitted whenever the \a lower position has changed.
*/
/*!
\fn QxtSpanSlider::upperPositionChanged(int upper)
This signal is emitted whenever the \a upper position has changed.
*/
/*!
\fn QxtSpanSlider::sliderPressed(SpanHandle handle)
This signal is emitted whenever the \a handle has been pressed.
*/
/*!
Constructs a new QxtSpanSlider with \a parent.
*/
QxtSpanSlider::QxtSpanSlider(QWidget* parent) : QSlider(parent)
{
QXT_INIT_PRIVATE(QxtSpanSlider);
connect(this, SIGNAL(rangeChanged(int, int)), &qxt_d(), SLOT(updateRange(int, int)));
connect(this, SIGNAL(sliderReleased()), &qxt_d(), SLOT(movePressedHandle()));
}
/*!
Constructs a new QxtSpanSlider with \a orientation and \a parent.
*/
QxtSpanSlider::QxtSpanSlider(Qt::Orientation orientation, QWidget* parent) : QSlider(orientation, parent)
{
QXT_INIT_PRIVATE(QxtSpanSlider);
connect(this, SIGNAL(rangeChanged(int, int)), &qxt_d(), SLOT(updateRange(int, int)));
connect(this, SIGNAL(sliderReleased()), &qxt_d(), SLOT(movePressedHandle()));
}
/*!
Destructs the span slider.
*/
QxtSpanSlider::~QxtSpanSlider()
{
}
/*!
\property QxtSpanSlider::handleMovementMode
\brief the handle movement mode
*/
QxtSpanSlider::HandleMovementMode QxtSpanSlider::handleMovementMode() const
{
return qxt_d().movement;
}
void QxtSpanSlider::setHandleMovementMode(QxtSpanSlider::HandleMovementMode mode)
{
qxt_d().movement = mode;
}
/*!
\property QxtSpanSlider::lowerValue
\brief the lower value of the span
*/
int QxtSpanSlider::lowerValue() const
{
return qMin(qxt_d().lower, qxt_d().upper);
}
void QxtSpanSlider::setLowerValue(int lower)
{
setSpan(lower, qxt_d().upper);
}
/*!
\property QxtSpanSlider::upperValue
\brief the upper value of the span
*/
int QxtSpanSlider::upperValue() const
{
return qMax(qxt_d().lower, qxt_d().upper);
}
void QxtSpanSlider::setUpperValue(int upper)
{
setSpan(qxt_d().lower, upper);
}
/*!
Sets the span from \a lower to \a upper.
*/
void QxtSpanSlider::setSpan(int lower, int upper)
{
const int low = qBound(minimum(), qMin(lower, upper), maximum());
const int upp = qBound(minimum(), qMax(lower, upper), maximum());
if (low != qxt_d().lower || upp != qxt_d().upper)
{
if (low != qxt_d().lower)
{
qxt_d().lower = low;
qxt_d().lowerPos = low;
emit lowerValueChanged(low);
}
if (upp != qxt_d().upper)
{
qxt_d().upper = upp;
qxt_d().upperPos = upp;
emit upperValueChanged(upp);
}
emit spanChanged(qxt_d().lower, qxt_d().upper);
update();
}
}
/*!
\property QxtSpanSlider::lowerPosition
\brief the lower position of the span
*/
int QxtSpanSlider::lowerPosition() const
{
return qxt_d().lowerPos;
}
void QxtSpanSlider::setLowerPosition(int lower)
{
if (qxt_d().lowerPos != lower)
{
qxt_d().lowerPos = lower;
if (!hasTracking())
update();
if (isSliderDown())
emit lowerPositionChanged(lower);
if (hasTracking() && !qxt_d().blockTracking)
{
bool main = (qxt_d().mainControl == QxtSpanSlider::LowerHandle);
qxt_d().triggerAction(SliderMove, main);
}
}
}
/*!
\property QxtSpanSlider::upperPosition
\brief the upper position of the span
*/
int QxtSpanSlider::upperPosition() const
{
return qxt_d().upperPos;
}
void QxtSpanSlider::setUpperPosition(int upper)
{
if (qxt_d().upperPos != upper)
{
qxt_d().upperPos = upper;
if (!hasTracking())
update();
if (isSliderDown())
emit upperPositionChanged(upper);
if (hasTracking() && !qxt_d().blockTracking)
{
bool main = (qxt_d().mainControl == QxtSpanSlider::UpperHandle);
qxt_d().triggerAction(SliderMove, main);
}
}
}
/*!
\reimp
*/
void QxtSpanSlider::keyPressEvent(QKeyEvent* event)
{
QSlider::keyPressEvent(event);
bool main = true;
SliderAction action = SliderNoAction;
switch (event->key())
{
case Qt::Key_Left:
main = (orientation() == Qt::Horizontal);
action = !invertedAppearance() ? SliderSingleStepSub : SliderSingleStepAdd;
break;
case Qt::Key_Right:
main = (orientation() == Qt::Horizontal);
action = !invertedAppearance() ? SliderSingleStepAdd : SliderSingleStepSub;
break;
case Qt::Key_Up:
main = (orientation() == Qt::Vertical);
action = invertedControls() ? SliderSingleStepSub : SliderSingleStepAdd;
break;
case Qt::Key_Down:
main = (orientation() == Qt::Vertical);
action = invertedControls() ? SliderSingleStepAdd : SliderSingleStepSub;
break;
case Qt::Key_Home:
main = (qxt_d().mainControl == QxtSpanSlider::LowerHandle);
action = SliderToMinimum;
break;
case Qt::Key_End:
main = (qxt_d().mainControl == QxtSpanSlider::UpperHandle);
action = SliderToMaximum;
break;
default:
event->ignore();
break;
}
if (action)
qxt_d().triggerAction(action, main);
}
/*!
\reimp
*/
void QxtSpanSlider::mousePressEvent(QMouseEvent* event)
{
if (minimum() == maximum() || (event->buttons() ^ event->button()))
{
event->ignore();
return;
}
qxt_d().handleMousePress(event->pos(), qxt_d().upperPressed, qxt_d().upper, QxtSpanSlider::UpperHandle);
if (qxt_d().upperPressed != QStyle::SC_SliderHandle)
qxt_d().handleMousePress(event->pos(), qxt_d().lowerPressed, qxt_d().lower, QxtSpanSlider::LowerHandle);
qxt_d().firstMovement = true;
event->accept();
}
/*!
\reimp
*/
void QxtSpanSlider::mouseMoveEvent(QMouseEvent* event)
{
if (qxt_d().lowerPressed != QStyle::SC_SliderHandle && qxt_d().upperPressed != QStyle::SC_SliderHandle)
{
event->ignore();
return;
}
QStyleOptionSlider opt;
qxt_d().initStyleOption(&opt);
const int m = style()->pixelMetric(QStyle::PM_MaximumDragDistance, &opt, this);
int newPosition = qxt_d().pixelPosToRangeValue(qxt_d().pick(event->pos()) - qxt_d().offset);
if (m >= 0)
{
const QRect r = rect().adjusted(-m, -m, m, m);
if (!r.contains(event->pos()))
{
newPosition = qxt_d().position;
}
}
// pick the preferred handle on the first movement
if (qxt_d().firstMovement)
{
if (qxt_d().lower == qxt_d().upper)
{
if (newPosition < lowerValue())
{
qxt_d().swapControls();
qxt_d().firstMovement = false;
}
}
else
{
qxt_d().firstMovement = false;
}
}
if (qxt_d().lowerPressed == QStyle::SC_SliderHandle)
{
if (qxt_d().movement == NoCrossing)
newPosition = qMin(newPosition, upperValue());
else if (qxt_d().movement == NoOverlapping)
newPosition = qMin(newPosition, upperValue() - 1);
if (qxt_d().movement == FreeMovement && newPosition > qxt_d().upper)
{
qxt_d().swapControls();
setUpperPosition(newPosition);
}
else
{
setLowerPosition(newPosition);
}
}
else if (qxt_d().upperPressed == QStyle::SC_SliderHandle)
{
if (qxt_d().movement == NoCrossing)
newPosition = qMax(newPosition, lowerValue());
else if (qxt_d().movement == NoOverlapping)
newPosition = qMax(newPosition, lowerValue() + 1);
if (qxt_d().movement == FreeMovement && newPosition < qxt_d().lower)
{
qxt_d().swapControls();
setLowerPosition(newPosition);
}
else
{
setUpperPosition(newPosition);
}
}
event->accept();
}
/*!
\reimp
*/
void QxtSpanSlider::mouseReleaseEvent(QMouseEvent* event)
{
QSlider::mouseReleaseEvent(event);
setSliderDown(false);
qxt_d().lowerPressed = QStyle::SC_None;
qxt_d().upperPressed = QStyle::SC_None;
update();
}
/*!
\reimp
*/
void QxtSpanSlider::paintEvent(QPaintEvent* event)
{
Q_UNUSED(event);
QStylePainter painter(this);
// ticks
QStyleOptionSlider opt;
qxt_d().initStyleOption(&opt);
opt.subControls = QStyle::SC_SliderTickmarks;
painter.drawComplexControl(QStyle::CC_Slider, opt);
// groove
opt.sliderValue = 0;
opt.sliderPosition = 0;
opt.subControls = QStyle::SC_SliderGroove;
painter.drawComplexControl(QStyle::CC_Slider, opt);
// handle rects
opt.sliderPosition = qxt_d().lowerPos;
const QRect lr = style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderHandle, this);
const int lrv = qxt_d().pick(lr.center());
opt.sliderPosition = qxt_d().upperPos;
const QRect ur = style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderHandle, this);
const int urv = qxt_d().pick(ur.center());
// span
const int minv = qMin(lrv, urv);
const int maxv = qMax(lrv, urv);
const QPoint c = style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderGroove, this).center();
QRect spanRect;
if (orientation() == Qt::Horizontal)
spanRect = QRect(QPoint(minv, c.y() - 2), QPoint(maxv, c.y() + 1));
else
spanRect = QRect(QPoint(c.x() - 2, minv), QPoint(c.x() + 1, maxv));
qxt_d().drawSpan(&painter, spanRect);
// handles
switch (qxt_d().lastPressed)
{
case QxtSpanSlider::LowerHandle:
qxt_d().drawHandle(&painter, QxtSpanSlider::UpperHandle);
qxt_d().drawHandle(&painter, QxtSpanSlider::LowerHandle);
break;
case QxtSpanSlider::UpperHandle:
default:
qxt_d().drawHandle(&painter, QxtSpanSlider::LowerHandle);
qxt_d().drawHandle(&painter, QxtSpanSlider::UpperHandle);
break;
}
}

View File

@@ -1,99 +0,0 @@
/****************************************************************************
**
** Copyright (C) Qxt Foundation. Some rights reserved.
**
** This file is part of the QxtGui module of the Qxt library.
**
** This library is free software; you can redistribute it and/or modify it
** under the terms of the Common Public License, version 1.0, as published
** by IBM, and/or under the terms of the GNU Lesser General Public License,
** version 2.1, as published by the Free Software Foundation.
**
** This file is provided "AS IS", without WARRANTIES OR CONDITIONS OF ANY
** KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY
** WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR
** FITNESS FOR A PARTICULAR PURPOSE.
**
** You should have received a copy of the CPL and the LGPL along with this
** file. See the LICENSE file and the cpl1.0.txt/lgpl-2.1.txt files
** included with the source distribution for more information.
** If you did not receive a copy of the licenses, contact the Qxt Foundation.
**
** <http://libqxt.org> <foundation@libqxt.org>
**
****************************************************************************/
#ifndef QXTSPANSLIDER_H
#define QXTSPANSLIDER_H
#include <QSlider>
#include "qxtnamespace.h"
#include "qxtglobal.h"
class QxtSpanSliderPrivate;
class QXT_GUI_EXPORT QxtSpanSlider : public QSlider
{
Q_OBJECT
QXT_DECLARE_PRIVATE(QxtSpanSlider)
Q_PROPERTY(int lowerValue READ lowerValue WRITE setLowerValue)
Q_PROPERTY(int upperValue READ upperValue WRITE setUpperValue)
Q_PROPERTY(int lowerPosition READ lowerPosition WRITE setLowerPosition)
Q_PROPERTY(int upperPosition READ upperPosition WRITE setUpperPosition)
Q_PROPERTY(HandleMovementMode handleMovementMode READ handleMovementMode WRITE setHandleMovementMode)
Q_ENUMS(HandleMovementMode)
public:
explicit QxtSpanSlider(QWidget* parent = 0);
explicit QxtSpanSlider(Qt::Orientation orientation, QWidget* parent = 0);
virtual ~QxtSpanSlider();
enum HandleMovementMode
{
FreeMovement,
NoCrossing,
NoOverlapping
};
enum SpanHandle
{
NoHandle,
LowerHandle,
UpperHandle
};
HandleMovementMode handleMovementMode() const;
void setHandleMovementMode(HandleMovementMode mode);
int lowerValue() const;
int upperValue() const;
int lowerPosition() const;
int upperPosition() const;
public Q_SLOTS:
void setLowerValue(int lower);
void setUpperValue(int upper);
void setSpan(int lower, int upper);
void setLowerPosition(int lower);
void setUpperPosition(int upper);
Q_SIGNALS:
void spanChanged(int lower, int upper);
void lowerValueChanged(int lower);
void upperValueChanged(int upper);
void lowerPositionChanged(int lower);
void upperPositionChanged(int upper);
void sliderPressed(SpanHandle handle);
protected:
virtual void keyPressEvent(QKeyEvent* event);
virtual void mousePressEvent(QMouseEvent* event);
virtual void mouseMoveEvent(QMouseEvent* event);
virtual void mouseReleaseEvent(QMouseEvent* event);
virtual void paintEvent(QPaintEvent* event);
};
#endif // QXTSPANSLIDER_H

View File

@@ -1,75 +0,0 @@
/****************************************************************************
**
** Copyright (C) Qxt Foundation. Some rights reserved.
**
** This file is part of the QxtGui module of the Qxt library.
**
** This library is free software; you can redistribute it and/or modify it
** under the terms of the Common Public License, version 1.0, as published
** by IBM, and/or under the terms of the GNU Lesser General Public License,
** version 2.1, as published by the Free Software Foundation.
**
** This file is provided "AS IS", without WARRANTIES OR CONDITIONS OF ANY
** KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY
** WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR
** FITNESS FOR A PARTICULAR PURPOSE.
**
** You should have received a copy of the CPL and the LGPL along with this
** file. See the LICENSE file and the cpl1.0.txt/lgpl-2.1.txt files
** included with the source distribution for more information.
** If you did not receive a copy of the licenses, contact the Qxt Foundation.
**
** <http://libqxt.org> <foundation@libqxt.org>
**
****************************************************************************/
#ifndef QXTSPANSLIDER_P_H
#define QXTSPANSLIDER_P_H
#include <QStyle>
#include <QObject>
#include "qxtspanslider.h"
QT_FORWARD_DECLARE_CLASS(QStylePainter)
QT_FORWARD_DECLARE_CLASS(QStyleOptionSlider)
class QxtSpanSliderPrivate : public QObject, public QxtPrivate<QxtSpanSlider>
{
Q_OBJECT
public:
QXT_DECLARE_PUBLIC(QxtSpanSlider)
QxtSpanSliderPrivate();
void initStyleOption(QStyleOptionSlider* option, QxtSpanSlider::SpanHandle handle = QxtSpanSlider::UpperHandle) const;
int pick(const QPoint& pt) const
{
return qxt_p().orientation() == Qt::Horizontal ? pt.x() : pt.y();
}
int pixelPosToRangeValue(int pos) const;
void handleMousePress(const QPoint& pos, QStyle::SubControl& control, int value, QxtSpanSlider::SpanHandle handle);
void drawHandle(QStylePainter* painter, QxtSpanSlider::SpanHandle handle) const;
void setupPainter(QPainter* painter, Qt::Orientation orientation, qreal x1, qreal y1, qreal x2, qreal y2) const;
void drawSpan(QStylePainter* painter, const QRect& rect) const;
void triggerAction(QAbstractSlider::SliderAction action, bool main);
void swapControls();
int lower;
int upper;
int lowerPos;
int upperPos;
int offset;
int position;
QxtSpanSlider::SpanHandle lastPressed;
QxtSpanSlider::SpanHandle mainControl;
QStyle::SubControl lowerPressed;
QStyle::SubControl upperPressed;
QxtSpanSlider::HandleMovementMode movement;
bool firstMovement;
bool blockTracking;
public Q_SLOTS:
void updateRange(int min, int max);
void movePressedHandle();
};
#endif // QXTSPANSLIDER_P_H

5
src/.gitignore vendored
View File

@@ -17,11 +17,6 @@ profile
moc_*
qrc_application.cpp
# ignore lex/yacc generated files
*_lex.cpp
*_yacc.cpp
*_yacc.h
# ignore other object files
*.o

View File

@@ -21,7 +21,7 @@
#include "QuarqdClient.h"
#include "RealtimeData.h"
ANTplusController::ANTplusController(RealtimeWindow *parent, DeviceConfiguration *dc) : RealtimeController(parent, dc)
ANTplusController::ANTplusController(RealtimeWindow *parent, DeviceConfiguration *dc) : RealtimeController(parent)
{
myANTplus = new QuarqdClient (parent, dc);
}
@@ -82,12 +82,10 @@ ANTplusController::getRealtimeData(RealtimeData &rtData)
msgBox.setText("Cannot Connect to Quarqd");
msgBox.setIcon(QMessageBox::Critical);
msgBox.exec();
parent->Stop(1);
return;
parent->Stop();
}
// get latest telemetry
rtData = myANTplus->getRealtimeData();
processRealtimeData(rtData);
}
void ANTplusController::pushRealtimeData(RealtimeData &) { } // update realtime data with current values

View File

@@ -17,7 +17,6 @@
*/
#include "RideMetric.h"
#include <QApplication>
// This metric computes aerobic decoupling percentage as described
// by Joe Friel:
@@ -37,27 +36,18 @@
// in heart rate to power ratio as described by Friel.
class AerobicDecoupling : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(AerobicDecoupling)
double percent;
public:
AerobicDecoupling() : percent(0.0)
{
setSymbol("aerobic_decoupling");
#ifdef ENABLE_METRICS_TRANSLATION
setInternalName("Aerobic Decoupling");
}
void initialize() {
#endif
setName(tr("Aerobic Decoupling"));
setType(RideMetric::Average);
setMetricUnits(tr("%"));
setImperialUnits(tr("%"));
setPrecision(2);
}
void compute(const RideFile *ride, const Zones *, int, const HrZones *, int,
AerobicDecoupling() : percent(0.0) {}
QString symbol() const { return "aerobic_decoupling"; }
QString name() const { return QObject::tr("Aerobic Decoupling"); }
QString units(bool) const { return "%"; }
int precision() const { return 2; }
double value(bool) const { return percent; }
void compute(const RideFile *ride, const Zones *, int,
const QHash<QString,RideMetric*> &) {
double firstHalfPower = 0.0, secondHalfPower = 0.0;
double firstHalfHR = 0.0, secondHalfHR = 0.0;
@@ -65,7 +55,6 @@ class AerobicDecoupling : public RideMetric {
int count = 0;
int firstHalfCount = 0;
int secondHalfCount = 0;
percent = 0;
foreach(const RideFilePoint *point, ride->dataPoints()) {
if (count++ < halfway) {
if (point->hr > 0) {
@@ -82,7 +71,7 @@ class AerobicDecoupling : public RideMetric {
}
}
}
if ((firstHalfPower > 0) && (secondHalfPower > 0)) {
if ((firstHalfCount > 0) && (secondHalfCount > 0)) {
firstHalfPower /= firstHalfCount;
secondHalfPower /= secondHalfCount;
firstHalfHR /= firstHalfCount;
@@ -91,7 +80,6 @@ class AerobicDecoupling : public RideMetric {
double secondHalfRatio = secondHalfHR / secondHalfPower;
percent = 100.0 * (secondHalfRatio - firstHalfRatio) / firstHalfRatio;
}
setValue(percent);
}
RideMetric *clone() const { return new AerobicDecoupling(*this); }

View File

@@ -1,752 +0,0 @@
/*
* Copyright (c) 2009 Andy M. Froncioni (me@andyfroncioni.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 "Aerolab.h"
#include "AerolabWindow.h"
#include "MainWindow.h"
#include "RideFile.h"
#include "RideItem.h"
#include "Settings.h"
#include "Units.h"
#include "Colors.h"
#include <math.h>
#include <assert.h>
#include <qwt_data.h>
#include <qwt_legend.h>
#include <qwt_plot_curve.h>
#include <qwt_plot_grid.h>
#include <qwt_plot_marker.h>
#include <qwt_symbol.h>
#include <set>
#include <QDebug>
#define PI M_PI
static inline double
max(double a, double b) { if (a > b) return a; else return b; }
static inline double
min(double a, double b) { if (a < b) return a; else return b; }
/*----------------------------------------------------------------------
* Interval plotting
*--------------------------------------------------------------------*/
class IntervalAerolabData : public QwtData
{
public:
Aerolab *aerolab;
MainWindow *mainWindow;
IntervalAerolabData
(
Aerolab *aerolab,
MainWindow *mainWindow
) : aerolab( aerolab ), mainWindow( mainWindow ) { }
double x( size_t ) const;
double y( size_t ) const;
size_t size() const;
virtual QwtData *copy() const;
void init();
IntervalItem *intervalNum( int ) const;
int intervalCount() const;
};
/*
* HELPER FUNCTIONS:
* intervalNum - returns a pointer to the nth selected interval
* intervalCount - returns the number of highlighted intervals
*/
// ------------------------------------------------------------------------------------------------------------
// note this is operating on the children of allIntervals and not the
// intervalWidget (QTreeWidget) -- this is why we do not use the
// selectedItems() member. N starts a one not zero.
// ------------------------------------------------------------------------------------------------------------
IntervalItem *IntervalAerolabData::intervalNum
(
int number
) const
{
int highlighted = 0;
const QTreeWidgetItem *allIntervals = mainWindow->allIntervalItems();
for ( int ii = 0; ii < allIntervals->childCount(); ii++)
{
IntervalItem *current = (IntervalItem *) allIntervals->child( ii );
if ( current == NULL)
{
return NULL;
}
if ( current->isSelected() == true )
{
++highlighted;
}
if ( highlighted == number )
{
return current;
}
}
return NULL;
}
// ------------------------------------------------------------------------------------------------------------
// how many intervals selected?
// ------------------------------------------------------------------------------------------------------------
int IntervalAerolabData::intervalCount() const
{
int highlighted = 0;
if ( mainWindow->allIntervalItems() != NULL )
{
const QTreeWidgetItem *allIntervals = mainWindow->allIntervalItems();
for ( int ii = 0; ii < allIntervals->childCount(); ii++)
{
IntervalItem *current = (IntervalItem *) allIntervals->child( ii );
if ( current != NULL )
{
if ( current->isSelected() == true )
{
++highlighted;
}
}
}
}
return highlighted;
}
/*
* INTERVAL HIGHLIGHTING CURVE
* IntervalAerolabData - implements the qwtdata interface where
* x,y return point co-ordinates and
* size returns the number of points
*/
// The interval curve data is derived from the intervals that have
// been selected in the MainWindow leftlayout for each selected
// interval we return 4 data points; bottomleft, topleft, topright
// and bottom right.
//
// the points correspond to:
// bottom left = interval start, 0 watts
// top left = interval start, maxwatts
// top right = interval stop, maxwatts
// bottom right = interval stop, 0 watts
//
double IntervalAerolabData::x
(
size_t number
) const
{
// for each interval there are four points, which interval is this for?
// interval numbers start at 1 not ZERO in the utility functions
double result = 0;
int interval_no = number ? 1 + number / 4 : 1;
// get the interval
IntervalItem *current = intervalNum( interval_no );
if ( current != NULL )
{
double multiplier = aerolab->useMetricUnits ? 1 : MILES_PER_KM;
// which point are we returning?
//qDebug() << "number = " << number << endl;
switch ( number % 4 )
{
case 0 : result = aerolab->bydist ? multiplier * current->startKM : current->start/60; // bottom left
break;
case 1 : result = aerolab->bydist ? multiplier * current->startKM : current->start/60; // top left
break;
case 2 : result = aerolab->bydist ? multiplier * current->stopKM : current->stop/60; // bottom right
break;
case 3 : result = aerolab->bydist ? multiplier * current->stopKM : current->stop/60; // top right
break;
}
}
return result;
}
double IntervalAerolabData::y
(
size_t number
) const
{
// which point are we returning?
double result = 0;
switch ( number % 4 )
{
case 0 : result = -5000; // bottom left
break;
case 1 : result = 5000; // top left - set to out of bound value
break;
case 2 : result = 5000; // top right - set to out of bound value
break;
case 3 : result = -5000; // bottom right
break;
}
return result;
}
size_t IntervalAerolabData::size() const
{
return intervalCount() * 4;
}
QwtData *IntervalAerolabData::copy() const
{
return new IntervalAerolabData( aerolab, mainWindow );
}
//**********************************************
//** END IntervalAerolabData **
//**********************************************
Aerolab::Aerolab(
AerolabWindow *parent,
MainWindow *mainWindow
):
QwtPlot(parent),
parent(parent),
unit(0),
rideItem(NULL),
smooth(1), bydist(true), autoEoffset(true) {
crr = 0.005;
cda = 0.500;
totalMass = 85.0;
rho = 1.236;
eta = 1.0;
eoffset = 0.0;
boost::shared_ptr<QSettings> settings = GetApplicationSettings();
unit = settings->value(GC_UNIT);
useMetricUnits = true;
insertLegend(new QwtLegend(), QwtPlot::BottomLegend);
setCanvasBackground(Qt::white);
setXTitle();
setAxisTitle(yLeft, tr("Elevation (m)"));
setAxisScale(yLeft, -300, 300);
setAxisTitle(xBottom, tr("Distance (km)"));
setAxisScale(xBottom, 0, 60);
veCurve = new QwtPlotCurve(tr("V-Elevation"));
altCurve = new QwtPlotCurve(tr("Elevation"));
// get rid of nasty blank space on right of the plot
veCurve->setYAxis( yLeft );
altCurve->setYAxis( yLeft );
intervalHighlighterCurve = new QwtPlotCurve();
intervalHighlighterCurve->setBaseline(-5000);
intervalHighlighterCurve->setYAxis( yLeft );
intervalHighlighterCurve->setData( IntervalAerolabData( this, mainWindow ) );
intervalHighlighterCurve->attach( this );
this->legend()->remove( intervalHighlighterCurve ); // don't show in legend
grid = new QwtPlotGrid();
grid->enableX(false);
grid->attach(this);
configChanged();
}
void
Aerolab::configChanged()
{
// set colors
setCanvasBackground(GColor(CPLOTBACKGROUND));
QPen vePen = QPen(GColor(CAEROVE));
vePen.setWidth(1);
veCurve->setPen(vePen);
QPen altPen = QPen(GColor(CAEROEL));
altPen.setWidth(1);
altCurve->setPen(altPen);
QPen gridPen(GColor(CPLOTGRID));
gridPen.setStyle(Qt::DotLine);
grid->setPen(gridPen);
QPen ihlPen = QPen( GColor( CINTERVALHIGHLIGHTER ) );
ihlPen.setWidth(1);
intervalHighlighterCurve->setPen( ihlPen );
QColor ihlbrush = QColor(GColor(CINTERVALHIGHLIGHTER));
ihlbrush.setAlpha(40);
intervalHighlighterCurve->setBrush(ihlbrush); // fill below the line
this->legend()->remove( intervalHighlighterCurve ); // don't show in legend
}
void
Aerolab::setData(RideItem *_rideItem, bool new_zoom) {
// HARD-CODED DATA: p1->kph
double vfactor = 3.600;
double m = totalMass;
double small_number = 0.00001;
rideItem = _rideItem;
RideFile *ride = rideItem->ride();
veArray.clear();
altArray.clear();
distanceArray.clear();
timeArray.clear();
useMetricUnits = true;
if( ride ) {
const RideFileDataPresent *dataPresent = ride->areDataPresent();
setTitle(ride->startTime().toString(GC_DATETIME_FORMAT));
if( dataPresent->watts ) {
// If watts are present, then we can fill the veArray data:
const RideFileDataPresent *dataPresent = ride->areDataPresent();
int npoints = ride->dataPoints().size();
double dt = ride->recIntSecs();
veArray.resize(dataPresent->watts ? npoints : 0);
altArray.resize(dataPresent->alt ? npoints : 0);
timeArray.resize(dataPresent->watts ? npoints : 0);
distanceArray.resize(dataPresent->watts ? npoints : 0);
// quickly erase old data
veCurve->setVisible(false);
altCurve->setVisible(false);
// detach and re-attach the ve curve:
veCurve->detach();
if (!veArray.empty()) {
veCurve->attach(this);
veCurve->setVisible(dataPresent->watts);
}
// detach and re-attach the ve curve:
bool have_recorded_alt_curve = false;
altCurve->detach();
if (!altArray.empty()) {
have_recorded_alt_curve = true;
altCurve->attach(this);
altCurve->setVisible(dataPresent->alt);
}
// Fill the virtual elevation profile with data from the ride data:
double t = 0.0;
double vlast = 0.0;
double e = 0.0;
double d = 0;
arrayLength = 0;
foreach(const RideFilePoint *p1, ride->dataPoints()) {
if ( arrayLength == 0 )
e = eoffset;
timeArray[arrayLength] = p1->secs / 60.0;
if ( have_recorded_alt_curve )
altArray[arrayLength] = (useMetricUnits
? p1->alt
: p1->alt * FEET_PER_METER);
// Unpack:
double power = max(0, p1->watts);
double v = p1->kph/vfactor;
double headwind = v;
if( dataPresent->headwind ) {
headwind = p1->headwind/vfactor;
}
double f = 0.0;
double a = 0.0;
// Use km data insteed of formula for file with a stop (gap).
//d += v * dt;
//distanceArray[arrayLength] = d/1000;
distanceArray[arrayLength] = p1->km;
if( v > small_number ) {
f = power/v;
a = ( v*v - vlast*vlast ) / ( 2.0 * dt * v );
} else {
a = ( v - vlast ) / dt;
}
f *= eta; // adjust for drivetrain efficiency if using a crank-based meter
double s = slope( f, a, m, crr, cda, rho, headwind );
double de = s * v * dt;
e += de;
t += dt;
veArray[arrayLength] = e;
vlast = v;
++arrayLength;
}
} else {
veCurve->setVisible(false);
altCurve->setVisible(false);
}
recalc(new_zoom);
adjustEoffset();
} else {
setTitle("no data");
}
}
void
Aerolab::adjustEoffset() {
if (autoEoffset && !altArray.empty()) {
double idx = axisScaleDiv( QwtPlot::xBottom )->lowerBound();
parent->eoffsetSlider->setEnabled(false);
if (bydist) {
int v = 100*(altArray.at(rideItem->ride()->distanceIndex(idx))-veArray.at(rideItem->ride()->distanceIndex(idx)));
parent->eoffsetSlider->setValue(intEoffset()+v);
}
else {
int v = 100*(altArray.at(rideItem->ride()->timeIndex(60*idx))-veArray.at(rideItem->ride()->timeIndex(60*idx)));
parent->eoffsetSlider->setValue(intEoffset()+v);
}
} else
parent->eoffsetSlider->setEnabled(true);
}
struct DataPoint {
double time, hr, watts, speed, cad, alt;
DataPoint(double t, double h, double w, double s, double c, double a) :
time(t), hr(h), watts(w), speed(s), cad(c), alt(a) {}
};
void
Aerolab::recalc( bool new_zoom ) {
if (timeArray.empty())
return;
int rideTimeSecs = (int) ceil(timeArray[arrayLength - 1]);
int totalRideDistance = (int ) ceil(distanceArray[arrayLength - 1]);
// If the ride is really long, then avoid it like the plague.
if (rideTimeSecs > 7*24*60*60) {
QwtArray<double> data;
if (!veArray.empty()){
veCurve->setData(data, data);
}
if( !altArray.empty()) {
altCurve->setData(data, data);
}
return;
}
QVector<double> &xaxis = (bydist?distanceArray:timeArray);
int startingIndex = 0;
int totalPoints = arrayLength - startingIndex;
// set curves
if (!veArray.empty())
veCurve->setData(xaxis.data() + startingIndex,
veArray.data() + startingIndex, totalPoints);
if (!altArray.empty()){
altCurve->setData(xaxis.data() + startingIndex,
altArray.data() + startingIndex, totalPoints);
}
if( new_zoom )
setAxisScale(xBottom, 0.0, (bydist?totalRideDistance:rideTimeSecs));
setYMax(new_zoom );
refreshIntervalMarkers();
replot();
}
void
Aerolab::setYMax(bool new_zoom)
{
if (veCurve->isVisible())
{
if ( useMetricUnits )
{
setAxisTitle( yLeft, "Elevation (m)" );
}
else
{
setAxisTitle( yLeft, "Elevation (')" );
}
double minY = 0.0;
double maxY = 0.0;
//************
//if (veCurve->isVisible()) {
// setAxisTitle(yLeft, tr("Elevation"));
if ( !altArray.empty() ) {
// setAxisScale(yLeft,
// min( veCurve->minYValue(), altCurve->minYValue() ) - 10,
// 10.0 + max( veCurve->maxYValue(), altCurve->maxYValue() ) );
minY = min( veCurve->minYValue(), altCurve->minYValue() ) - 10;
maxY = 10.0 + max( veCurve->maxYValue(), altCurve->maxYValue() );
} else {
//setAxisScale(yLeft,
// veCurve->minYValue() ,
// 1.05 * veCurve->maxYValue() );
if ( new_zoom )
{
minY = veCurve->minYValue();
maxY = veCurve->maxYValue();
}
else
{
minY = parent->getCanvasTop();
maxY = parent->getCanvasBottom();
}
//adjust eooffset
// TODO
}
setAxisScale( yLeft, minY, maxY );
setAxisLabelRotation(yLeft,270);
setAxisLabelAlignment(yLeft,Qt::AlignVCenter);
}
enableAxis(yLeft, veCurve->isVisible());
}
void
Aerolab::setXTitle() {
if (bydist)
setAxisTitle(xBottom, tr("Distance ")+QString(unit.toString() == "Metric"?"(km)":"(miles)"));
else
setAxisTitle(xBottom, tr("Time (minutes)"));
}
void
Aerolab::setAutoEoffset(int value)
{
autoEoffset = value;
adjustEoffset();
}
void
Aerolab::setByDistance(int value) {
bydist = value;
setXTitle();
recalc(true);
}
double
Aerolab::slope(
double f,
double a,
double m,
double crr,
double cda,
double rho,
double v
) {
double g = 9.80665;
// Small angle version of slope calculation:
double s = f/(m*g) - crr - cda*rho*v*v/(2.0*m*g) - a/g;
return s;
}
// At slider 1000, we want to get max Crr=0.1000
// At slider 1 , we want to get min Crr=0.0001
void
Aerolab::setIntCrr(
int value
) {
crr = (double) value / 1000000.0;
recalc(false);
}
// At slider 1000, we want to get max CdA=1.000
// At slider 1 , we want to get min CdA=0.001
void
Aerolab::setIntCda(
int value
) {
cda = (double) value / 10000.0;
recalc(false);
}
// At slider 1000, we want to get max CdA=1.000
// At slider 1 , we want to get min CdA=0.001
void
Aerolab::setIntTotalMass(
int value
) {
totalMass = (double) value / 100.0;
recalc(false);
}
// At slider 1000, we want to get max CdA=1.000
// At slider 1 , we want to get min CdA=0.001
void
Aerolab::setIntRho(
int value
) {
rho = (double) value / 10000.0;
recalc(false);
}
// At slider 1000, we want to get max CdA=1.000
// At slider 1 , we want to get min CdA=0.001
void
Aerolab::setIntEta(
int value
) {
eta = (double) value / 10000.0;
recalc(false);
}
// At slider 1000, we want to get max CdA=1.000
// At slider 1 , we want to get min CdA=0.001
void
Aerolab::setIntEoffset(
int value
) {
eoffset = (double) value / 100.0;
recalc(false);
}
void Aerolab::pointHover (QwtPlotCurve *curve, int index)
{
if ( index >= 0 && curve != intervalHighlighterCurve )
{
double x_value = curve->x( index );
double y_value = curve->y( index );
// output the tooltip
QString text = QString( "%1 %2 %3 %4 %5" )
. arg( this->axisTitle( curve->xAxis() ).text() )
. arg( x_value, 0, 'f', 3 )
. arg( "\n" )
. arg( this->axisTitle( curve->yAxis() ).text() )
. arg( y_value, 0, 'f', 3 );
// set that text up
tooltip->setText( text );
}
else
{
// no point
tooltip->setText( "" );
}
}
void Aerolab::refreshIntervalMarkers()
{
foreach( QwtPlotMarker *mrk, d_mrk )
{
mrk->detach();
delete mrk;
}
d_mrk.clear();
QRegExp wkoAuto("^(Peak *[0-9]*(s|min)|Entire workout|Find #[0-9]*) *\\([^)]*\\)$");
if ( rideItem->ride() )
{
foreach(const RideFileInterval &interval, rideItem->ride()->intervals()) {
// skip WKO autogenerated peak intervals
if (wkoAuto.exactMatch(interval.name))
continue;
QwtPlotMarker *mrk = new QwtPlotMarker;
d_mrk.append(mrk);
mrk->attach(this);
mrk->setLineStyle(QwtPlotMarker::VLine);
mrk->setLabelAlignment(Qt::AlignRight | Qt::AlignTop);
mrk->setLinePen(QPen(GColor(CPLOTMARKER), 0, Qt::DashDotLine));
QwtText text(interval.name);
text.setFont(QFont("Helvetica", 10, QFont::Bold));
text.setColor(GColor(CPLOTMARKER));
if (!bydist)
mrk->setValue(interval.start / 60.0, 0.0);
else
mrk->setValue((useMetricUnits ? 1 : MILES_PER_KM) *
rideItem->ride()->timeToDistance(interval.start), 0.0);
mrk->setLabel(text);
}
}
}

View File

@@ -1,139 +0,0 @@
/*
* Copyright (c) 2009 Andy M. Froncioni (me@andyfroncioni.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
*/
#ifndef _GC_Aerolab_h
#define _GC_Aerolab_h 1
#include <qwt_plot.h>
#include <qwt_data.h>
#include <QtGui>
#include "LTMWindow.h" // for tooltip/canvaspicker
// forward references
class RideItem;
class RideFilePoint;
class QwtPlotCurve;
class QwtPlotGrid;
class QwtPlotMarker;
class AerolabWindow;
class MainWindow;
class IntervalAerolabData;
class LTMToolTip;
class LTMCanvasPicker;
class Aerolab : public QwtPlot {
Q_OBJECT
public:
Aerolab( AerolabWindow *, MainWindow * );
bool byDistance() const { return bydist; }
bool useMetricUnits; // whether metric units are used (or imperial)
void setData(RideItem *_rideItem, bool new_zoom);
void refreshIntervalMarkers();
private:
AerolabWindow *parent;
LTMToolTip *tooltip;
LTMCanvasPicker *_canvasPicker; // allow point selection/hover
void adjustEoffset();
public slots:
void setAutoEoffset(int value);
void setByDistance(int value);
void configChanged();
void pointHover( QwtPlotCurve *, int );
signals:
protected:
friend class ::AerolabWindow;
friend class ::IntervalAerolabData;
QVariant unit;
QwtPlotGrid *grid;
QVector<QwtPlotMarker*> d_mrk;
// One curve to plot in the Course Profile:
QwtPlotCurve *veCurve; // virtual elevation curve
QwtPlotCurve *altCurve; // recorded elevation curve, if available
QwtPlotCurve *intervalHighlighterCurve; // highlight selected intervals on the Plot
RideItem *rideItem;
QVector<double> hrArray;
QVector<double> wattsArray;
QVector<double> speedArray;
QVector<double> cadArray;
// We store virtual elevation, time, altitude,and distance:
QVector<double> veArray;
QVector<double> altArray;
QVector<double> timeArray;
QVector<double> distanceArray;
int smooth;
bool bydist;
bool autoEoffset;
int arrayLength;
int iCrr;
int iCda;
double crr;
double cda;
double totalMass; // Bike + Rider mass
double rho;
double eta;
double eoffset;
double slope(double, double, double, double, double, double, double);
void recalc(bool);
void setYMax(bool);
void setXTitle();
void setIntCrr(int);
void setIntCda(int);
void setIntRho(int);
void setIntEta(int);
void setIntEoffset(int);
void setIntTotalMass(int);
double getCrr() const { return (double)crr; }
double getCda() const { return (double)cda; }
double getTotalMass() const { return (double)totalMass; }
double getRho() const { return (double)rho; }
double getEta() const { return (double)eta; }
double getEoffset() const { return (double)eoffset; }
int intCrr() const { return (int)( crr * 1000000 ); }
int intCda() const { return (int)( cda * 10000); }
int intTotalMass() const { return (int)( totalMass * 100); }
int intRho() const { return (int)( rho * 10000); }
int intEta() const { return (int)( eta * 10000); }
int intEoffset() const { return (int)( eoffset * 100); }
};
#endif // _GC_Aerolab_h

View File

@@ -1,518 +0,0 @@
/*
* Copyright (c) 2009 Andy M. Froncioni (me@andyfroncioni.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 "MainWindow.h"
#include "AerolabWindow.h"
#include "Aerolab.h"
#include "RideItem.h"
#include "Colors.h"
#include <QtGui>
#include <qwt_plot_zoomer.h>
AerolabWindow::AerolabWindow(MainWindow *mainWindow) :
QWidget(mainWindow), mainWindow(mainWindow) {
// Aerolab tab layout:
QVBoxLayout *vLayout = new QVBoxLayout;
QHBoxLayout *cLayout = new QHBoxLayout;
// Plot:
aerolab = new Aerolab(this, mainWindow);
// Left controls layout:
QVBoxLayout *leftControls = new QVBoxLayout;
QFontMetrics metrics(QApplication::font());
int labelWidth1 = metrics.width("Crr") + 10;
// Crr:
QHBoxLayout *crrLayout = new QHBoxLayout;
QLabel *crrLabel = new QLabel(tr("Crr"), this);
crrLabel->setFixedWidth(labelWidth1);
crrLineEdit = new QLineEdit();
crrLineEdit->setFixedWidth(70);
crrLineEdit->setText(QString("%1").arg(aerolab->getCrr()) );
/*crrQLCDNumber = new QLCDNumber(7);
crrQLCDNumber->setMode(QLCDNumber::Dec);
crrQLCDNumber->setSmallDecimalPoint(false);
crrQLCDNumber->setSegmentStyle(QLCDNumber::Flat);
crrQLCDNumber->display(QString("%1").arg(aerolab->getCrr()) );*/
crrSlider = new QSlider(Qt::Horizontal);
crrSlider->setTickPosition(QSlider::TicksBelow);
crrSlider->setTickInterval(1000);
crrSlider->setMinimum(1000);
crrSlider->setMaximum(10000);
crrSlider->setValue(aerolab->intCrr());
crrLayout->addWidget( crrLabel );
crrLayout->addWidget( crrLineEdit );
//crrLayout->addWidget( crrQLCDNumber );
crrLayout->addWidget( crrSlider );
// CdA:
QHBoxLayout *cdaLayout = new QHBoxLayout;
QLabel *cdaLabel = new QLabel(tr("CdA"), this);
cdaLabel->setFixedWidth(labelWidth1);
cdaLineEdit = new QLineEdit();
cdaLineEdit->setFixedWidth(70);
cdaLineEdit->setText(QString("%1").arg(aerolab->getCda()) );
/*cdaQLCDNumber = new QLCDNumber(7);
cdaQLCDNumber->setMode(QLCDNumber::Dec);
cdaQLCDNumber->setSmallDecimalPoint(false);
cdaQLCDNumber->setSegmentStyle(QLCDNumber::Flat);
cdaQLCDNumber->display(QString("%1").arg(aerolab->getCda()) );*/
cdaSlider = new QSlider(Qt::Horizontal);
cdaSlider->setTickPosition(QSlider::TicksBelow);
cdaSlider->setTickInterval(100);
cdaSlider->setMinimum(1);
cdaSlider->setMaximum(6000);
cdaSlider->setValue(aerolab->intCda());
cdaLayout->addWidget( cdaLabel );
//cdaLayout->addWidget( cdaQLCDNumber );
cdaLayout->addWidget( cdaLineEdit );
cdaLayout->addWidget( cdaSlider );
// Eta:
QHBoxLayout *etaLayout = new QHBoxLayout;
QLabel *etaLabel = new QLabel(tr("Eta"), this);
etaLabel->setFixedWidth(labelWidth1);
etaLineEdit = new QLineEdit();
etaLineEdit->setFixedWidth(70);
etaLineEdit->setText(QString("%1").arg(aerolab->getEta()) );
/*etaQLCDNumber = new QLCDNumber(7);
etaQLCDNumber->setMode(QLCDNumber::Dec);
etaQLCDNumber->setSmallDecimalPoint(false);
etaQLCDNumber->setSegmentStyle(QLCDNumber::Flat);
etaQLCDNumber->display(QString("%1").arg(aerolab->getEta()) );*/
etaSlider = new QSlider(Qt::Horizontal);
etaSlider->setTickPosition(QSlider::TicksBelow);
etaSlider->setTickInterval(1000);
etaSlider->setMinimum(8000);
etaSlider->setMaximum(12000);
etaSlider->setValue(aerolab->intEta());
etaLayout->addWidget( etaLabel );
etaLayout->addWidget( etaLineEdit );
//etaLayout->addWidget( etaQLCDNumber );
etaLayout->addWidget( etaSlider );
// Add to leftControls:
leftControls->addLayout( crrLayout );
leftControls->addLayout( cdaLayout );
leftControls->addLayout( etaLayout );
// Right controls layout:
QVBoxLayout *rightControls = new QVBoxLayout;
int labelWidth2 = metrics.width("Total Mass (kg)") + 10;
// Total mass:
QHBoxLayout *mLayout = new QHBoxLayout;
QLabel *mLabel = new QLabel(tr("Total Mass (kg)"), this);
mLabel->setFixedWidth(labelWidth2);
mLineEdit = new QLineEdit();
mLineEdit->setFixedWidth(70);
mLineEdit->setText(QString("%1").arg(aerolab->getTotalMass()) );
/*mQLCDNumber = new QLCDNumber(7);
mQLCDNumber->setMode(QLCDNumber::Dec);
mQLCDNumber->setSmallDecimalPoint(false);
mQLCDNumber->setSegmentStyle(QLCDNumber::Flat);
mQLCDNumber->display(QString("%1").arg(aerolab->getTotalMass()) );*/
mSlider = new QSlider(Qt::Horizontal);
mSlider->setTickPosition(QSlider::TicksBelow);
mSlider->setTickInterval(1000);
mSlider->setMinimum(3500);
mSlider->setMaximum(15000);
mSlider->setValue(aerolab->intTotalMass());
mLayout->addWidget( mLabel );
mLayout->addWidget( mLineEdit );
//mLayout->addWidget( mQLCDNumber );
mLayout->addWidget( mSlider );
// Rho:
QHBoxLayout *rhoLayout = new QHBoxLayout;
QLabel *rhoLabel = new QLabel(tr("Rho (kg/m^3)"), this);
rhoLabel->setFixedWidth(labelWidth2);
rhoLineEdit = new QLineEdit();
rhoLineEdit->setFixedWidth(70);
rhoLineEdit->setText(QString("%1").arg(aerolab->getRho()) );
/*rhoQLCDNumber = new QLCDNumber(7);
rhoQLCDNumber->setMode(QLCDNumber::Dec);
rhoQLCDNumber->setSmallDecimalPoint(false);
rhoQLCDNumber->setSegmentStyle(QLCDNumber::Flat);
rhoQLCDNumber->display(QString("%1").arg(aerolab->getRho()) );*/
rhoSlider = new QSlider(Qt::Horizontal);
rhoSlider->setTickPosition(QSlider::TicksBelow);
rhoSlider->setTickInterval(1000);
rhoSlider->setMinimum(9000);
rhoSlider->setMaximum(14000);
rhoSlider->setValue(aerolab->intRho());
rhoLayout->addWidget( rhoLabel );
rhoLayout->addWidget( rhoLineEdit );
//rhoLayout->addWidget( rhoQLCDNumber );
rhoLayout->addWidget( rhoSlider );
// Elevation offset:
QHBoxLayout *eoffsetLayout = new QHBoxLayout;
QLabel *eoffsetLabel = new QLabel(tr("Eoffset (m)"), this);
eoffsetLabel->setFixedWidth(labelWidth2);
eoffsetLineEdit = new QLineEdit();
eoffsetLineEdit->setFixedWidth(70);
eoffsetLineEdit->setText(QString("%1").arg(aerolab->getEoffset()) );
/*eoffsetQLCDNumber = new QLCDNumber(7);
eoffsetQLCDNumber->setMode(QLCDNumber::Dec);
eoffsetQLCDNumber->setSmallDecimalPoint(false);
eoffsetQLCDNumber->setSegmentStyle(QLCDNumber::Flat);
eoffsetQLCDNumber->display(QString("%1").arg(aerolab->getEoffset()) );*/
eoffsetSlider = new QSlider(Qt::Horizontal);
eoffsetSlider->setTickPosition(QSlider::TicksBelow);
eoffsetSlider->setTickInterval(20000);
eoffsetSlider->setMinimum(-30000);
eoffsetSlider->setMaximum(250000);
eoffsetSlider->setValue(aerolab->intEoffset());
eoffsetLayout->addWidget( eoffsetLabel );
eoffsetLayout->addWidget( eoffsetLineEdit );
//eoffsetLayout->addWidget( eoffsetQLCDNumber );
eoffsetLayout->addWidget( eoffsetSlider );
QCheckBox *eoffsetAuto = new QCheckBox(tr("eoffset auto"), this);
eoffsetAuto->setCheckState(Qt::Checked);
eoffsetLayout->addWidget(eoffsetAuto);
QHBoxLayout *smoothLayout = new QHBoxLayout;
QComboBox *comboDistance = new QComboBox();
comboDistance->addItem(tr("X Axis Shows Time"));
comboDistance->addItem(tr("X Axis Shows Distance"));
comboDistance->setCurrentIndex(1);
smoothLayout->addWidget(comboDistance);
// Add to leftControls:
rightControls->addLayout( mLayout );
rightControls->addLayout( rhoLayout );
rightControls->addLayout( eoffsetLayout );
rightControls->addLayout( smoothLayout );
// Assemble controls layout:
cLayout->addLayout(leftControls);
cLayout->addLayout(rightControls);
// Zoomer:
allZoomer = new QwtPlotZoomer(aerolab->canvas());
allZoomer->setRubberBand(QwtPicker::RectRubberBand);
allZoomer->setSelectionFlags(QwtPicker::DragSelection
| QwtPicker::CornerToCorner);
allZoomer->setTrackerMode(QwtPicker::AlwaysOff);
allZoomer->setEnabled(true);
allZoomer->setMousePattern( QwtEventPattern::MouseSelect2, Qt::RightButton, Qt::ControlModifier );
allZoomer->setMousePattern( QwtEventPattern::MouseSelect3, Qt::RightButton );
// SIGNALs to SLOTs:
connect(mainWindow, SIGNAL(rideSelected()), this, SLOT(rideSelected()));
connect(crrSlider, SIGNAL(valueChanged(int)),this, SLOT(setCrrFromSlider()));
connect(crrLineEdit, SIGNAL(textChanged(const QString)), this, SLOT(setCrrFromText(const QString)));
connect(cdaSlider, SIGNAL(valueChanged(int)), this, SLOT(setCdaFromSlider()));
connect(cdaLineEdit, SIGNAL(textChanged(const QString)), this, SLOT(setCdaFromText(const QString)));
connect(mSlider, SIGNAL(valueChanged(int)),this, SLOT(setTotalMassFromSlider()));
connect(mLineEdit, SIGNAL(textChanged(const QString)), this, SLOT(setTotalMassFromText(const QString)));
connect(rhoSlider, SIGNAL(valueChanged(int)), this, SLOT(setRhoFromSlider()));
connect(rhoLineEdit, SIGNAL(textChanged(const QString)), this, SLOT(setRhoFromText(const QString)));
connect(etaSlider, SIGNAL(valueChanged(int)), this, SLOT(setEtaFromSlider()));
connect(etaLineEdit, SIGNAL(textChanged(const QString)), this, SLOT(setEtaFromText(const QString)));
connect(eoffsetSlider, SIGNAL(valueChanged(int)), this, SLOT(setEoffsetFromSlider()));
connect(eoffsetLineEdit, SIGNAL(textChanged(const QString)), this, SLOT(setEoffsetFromText(const QString)));
connect(eoffsetAuto, SIGNAL(stateChanged(int)), this, SLOT(setAutoEoffset(int)));
connect(comboDistance, SIGNAL(currentIndexChanged(int)), this, SLOT(setByDistance(int)));
connect(mainWindow, SIGNAL(configChanged()), aerolab, SLOT(configChanged()));
connect(mainWindow, SIGNAL(configChanged()), this, SLOT(configChanged()));
connect(mainWindow, SIGNAL( intervalSelected() ), this, SLOT(intervalSelected()));
connect(allZoomer, SIGNAL( zoomed(const QwtDoubleRect) ), this, SLOT(zoomChanged()));
// Build the tab layout:
vLayout->addWidget(aerolab);
vLayout->addLayout(cLayout);
setLayout(vLayout);
// tooltip on hover over point
//************************************
aerolab->tooltip = new LTMToolTip( QwtPlot::xBottom,
QwtPlot::yLeft,
QwtPicker::PointSelection,
QwtPicker::VLineRubberBand,
QwtPicker::AlwaysOn,
aerolab->canvas(),
""
);
aerolab->tooltip->setSelectionFlags( QwtPicker::PointSelection | QwtPicker::RectSelection | QwtPicker::DragSelection);
aerolab->tooltip->setRubberBand( QwtPicker::VLineRubberBand );
aerolab->tooltip->setMousePattern( QwtEventPattern::MouseSelect1, Qt::LeftButton, Qt::ShiftModifier );
aerolab->tooltip->setTrackerPen( QColor( Qt::black ) );
QColor inv( Qt::white );
inv.setAlpha( 0 );
aerolab->tooltip->setRubberBandPen( inv );
aerolab->tooltip->setEnabled( true );
aerolab->_canvasPicker = new LTMCanvasPicker( aerolab );
connect( aerolab->_canvasPicker, SIGNAL( pointHover( QwtPlotCurve*, int ) ),
aerolab, SLOT ( pointHover( QwtPlotCurve*, int ) ) );
configChanged(); // pickup colors etc
}
void
AerolabWindow::zoomChanged()
{
RideItem *ride = mainWindow->rideItem();
aerolab->setData(ride, false);
}
void
AerolabWindow::configChanged()
{
allZoomer->setRubberBandPen(GColor(CPLOTSELECT));
}
void
AerolabWindow::rideSelected() {
if (mainWindow->activeTab() != this)
return;
RideItem *ride = mainWindow->rideItem();
if (!ride)
return;
aerolab->setData(ride, true);
allZoomer->setZoomBase();
}
void
AerolabWindow::setCrrFromText(const QString text) {
int value = 1000000 * text.toDouble();
if (aerolab->intCrr() != value) {
aerolab->setIntCrr(value);
//crrQLCDNumber->display(QString("%1").arg(aerolab->getCrr()));
crrSlider->setValue(aerolab->intCrr());
RideItem *ride = mainWindow->rideItem();
aerolab->setData(ride, false);
}
}
void
AerolabWindow::setCrrFromSlider() {
if (aerolab->intCrr() != crrSlider->value()) {
aerolab->setIntCrr(crrSlider->value());
//crrQLCDNumber->display(QString("%1").arg(aerolab->getCrr()));
crrLineEdit->setText(QString("%1").arg(aerolab->getCrr()) );
RideItem *ride = mainWindow->rideItem();
aerolab->setData(ride, false);
}
}
void
AerolabWindow::setCdaFromText(const QString text) {
int value = 10000 * text.toDouble();
if (aerolab->intCda() != value) {
aerolab->setIntCda(value);
//cdaQLCDNumber->display(QString("%1").arg(aerolab->getCda()));
cdaSlider->setValue(aerolab->intCda());
RideItem *ride = mainWindow->rideItem();
aerolab->setData(ride, false);
}
}
void
AerolabWindow::setCdaFromSlider() {
if (aerolab->intCda() != cdaSlider->value()) {
aerolab->setIntCda(cdaSlider->value());
//cdaQLCDNumber->display(QString("%1").arg(aerolab->getCda()));
cdaLineEdit->setText(QString("%1").arg(aerolab->getCda()) );
RideItem *ride = mainWindow->rideItem();
aerolab->setData(ride, false);
}
}
void
AerolabWindow::setTotalMassFromText(const QString text) {
int value = 100 * text.toDouble();
if (value == 0)
value = 1; // mass can not be zero !
if (aerolab->intTotalMass() != value) {
aerolab->setIntTotalMass(value);
//mQLCDNumber->display(QString("%1").arg(aerolab->getTotalMass()));
mSlider->setValue(aerolab->intTotalMass());
RideItem *ride = mainWindow->rideItem();
aerolab->setData(ride, false);
}
}
void
AerolabWindow::setTotalMassFromSlider() {
if (aerolab->intTotalMass() != mSlider->value()) {
aerolab->setIntTotalMass(mSlider->value());
//mQLCDNumber->display(QString("%1").arg(aerolab->getTotalMass()));
mLineEdit->setText(QString("%1").arg(aerolab->getTotalMass()) );
RideItem *ride = mainWindow->rideItem();
aerolab->setData(ride, false);
}
}
void
AerolabWindow::setRhoFromText(const QString text) {
int value = 10000 * text.toDouble();
if (aerolab->intRho() != value) {
aerolab->setIntRho(value);
//rhoQLCDNumber->display(QString("%1").arg(aerolab->getRho()));
rhoSlider->setValue(aerolab->intRho());
RideItem *ride = mainWindow->rideItem();
aerolab->setData(ride, false);
}
}
void
AerolabWindow::setRhoFromSlider() {
if (aerolab->intRho() != rhoSlider->value()) {
aerolab->setIntRho(rhoSlider->value());
//rhoQLCDNumber->display(QString("%1").arg(aerolab->getRho()));
rhoLineEdit->setText(QString("%1").arg(aerolab->getRho()) );
RideItem *ride = mainWindow->rideItem();
aerolab->setData(ride, false);
}
}
void
AerolabWindow::setEtaFromText(const QString text) {
int value = 10000 * text.toDouble();
if (aerolab->intEta() != value) {
aerolab->setIntEta(value);
//etaQLCDNumber->display(QString("%1").arg(aerolab->getEta()));
etaSlider->setValue(aerolab->intEta());
RideItem *ride = mainWindow->rideItem();
aerolab->setData(ride, false);
}
}
void
AerolabWindow::setEtaFromSlider() {
if (aerolab->intEta() != etaSlider->value()) {
aerolab->setIntEta(etaSlider->value());
//etaQLCDNumber->display(QString("%1").arg(aerolab->getEta()));
etaLineEdit->setText(QString("%1").arg(aerolab->getEta()) );
RideItem *ride = mainWindow->rideItem();
aerolab->setData(ride, false);
}
}
void
AerolabWindow::setEoffsetFromText(const QString text) {
int value = 100 * text.toDouble();
if (aerolab->intEoffset() != value) {
aerolab->setIntEoffset(value);
//eoffsetQLCDNumber->display(QString("%1").arg(aerolab->getEoffset()));
eoffsetSlider->setValue(aerolab->intEoffset());
RideItem *ride = mainWindow->rideItem();
aerolab->setData(ride, false);
}
}
void
AerolabWindow::setEoffsetFromSlider() {
if (aerolab->intEoffset() != eoffsetSlider->value()) {
aerolab->setIntEoffset(eoffsetSlider->value());
//eoffsetQLCDNumber->display(QString("%1").arg(aerolab->getEoffset()));
eoffsetLineEdit->setText(QString("%1").arg(aerolab->getEoffset()) );
RideItem *ride = mainWindow->rideItem();
aerolab->setData(ride, false);
}
}
void
AerolabWindow::setAutoEoffset(int value)
{
aerolab->setAutoEoffset(value);
}
void
AerolabWindow::setByDistance(int value)
{
aerolab->setByDistance(value);
// refresh
RideItem *ride = mainWindow->rideItem();
aerolab->setData(ride, false);
}
void
AerolabWindow::zoomInterval(IntervalItem *which) {
QwtDoubleRect rect;
if (!aerolab->byDistance()) {
rect.setLeft(which->start/60);
rect.setRight(which->stop/60);
} else {
rect.setLeft(which->startKM);
rect.setRight(which->stopKM);
}
rect.setTop(aerolab->veCurve->maxYValue()*1.1);
rect.setBottom(aerolab->veCurve->minYValue()-10);
allZoomer->zoom(rect);
aerolab->recalc(false);
}
void AerolabWindow::intervalSelected()
{
if ( mainWindow->activeTab() != this )
{
return;
}
RideItem *ride = mainWindow->rideItem();
if ( !ride )
{
return;
}
// set the elevation data
aerolab->setData( ride, true );
}
double AerolabWindow::getCanvasTop() const
{
const QwtDoubleRect &canvasRect = allZoomer->zoomRect();
return canvasRect.top();
}
double AerolabWindow::getCanvasBottom() const
{
const QwtDoubleRect &canvasRect = allZoomer->zoomRect();
return canvasRect.bottom();
}

View File

@@ -1,97 +0,0 @@
/*
* Copyright (c) 2009 Andy M. Froncioni (me@andyfroncioni.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
*/
#ifndef _GC_AerolabWindow_h
#define _GC_AerolabWindow_h 1
#include <QtGui>
class Aerolab;
class MainWindow;
class QCheckBox;
class QwtPlotZoomer;
class QwtPlotPicker;
class QLineEdit;
class QLCDNumber;
class RideItem;
class IntervalItem;
class AerolabWindow : public QWidget {
Q_OBJECT
public:
AerolabWindow(MainWindow *mainWindow);
void setData(RideItem *ride);
void zoomInterval(IntervalItem *); // zoom into a specified interval
double getCanvasTop() const;
double getCanvasBottom() const;
QSlider *eoffsetSlider;
public slots:
void setCrrFromSlider();
void setCrrFromText(const QString text);
void setCdaFromSlider();
void setCdaFromText(const QString text);
void setTotalMassFromSlider();
void setTotalMassFromText(const QString text);
void setRhoFromSlider();
void setRhoFromText(const QString text);
void setEtaFromSlider();
void setEtaFromText(const QString text);
void setEoffsetFromSlider();
void setEoffsetFromText(const QString text);
void setAutoEoffset(int value);
void setByDistance(int value);
void rideSelected();
void zoomChanged();
void configChanged();
void intervalSelected();
protected slots:
protected:
MainWindow *mainWindow;
Aerolab *aerolab;
QwtPlotZoomer *allZoomer;
// Bike parameter controls:
QSlider *crrSlider;
QLineEdit *crrLineEdit;
//QLCDNumber *crrQLCDNumber;
QSlider *cdaSlider;
QLineEdit *cdaLineEdit;
//QLCDNumber *cdaQLCDNumber;
QSlider *mSlider;
QLineEdit *mLineEdit;
//QLCDNumber *mQLCDNumber;
QSlider *rhoSlider;
QLineEdit *rhoLineEdit;
//QLCDNumber *rhoQLCDNumber;
QSlider *etaSlider;
QLineEdit *etaLineEdit;
//QLCDNumber *etaQLCDNumber;
QLineEdit *eoffsetLineEdit;
//QLCDNumber *eoffsetQLCDNumber;
};
#endif // _GC_AerolabWindow_h

View File

@@ -25,12 +25,10 @@
#include "Settings.h"
#include "Units.h"
#include "Zones.h"
#include "Colors.h"
#include <assert.h>
#include <qwt_plot_curve.h>
#include <qwt_plot_grid.h>
#include <qwt_plot_layout.h>
#include <qwt_plot_marker.h>
#include <qwt_text.h>
#include <qwt_legend.h>
@@ -84,6 +82,7 @@ class AllPlotBackground: public QwtPlotItem
const Zones *zones = rideItem->zones;
int zone_range = rideItem->zoneRange();
if (parent->shadeZones() && (zone_range >= 0)) {
QList <int> zone_lows = zones->getZoneLows(zone_range);
int num_zones = zone_lows.size();
@@ -104,7 +103,6 @@ class AllPlotBackground: public QwtPlotItem
painter->fillRect(r, shading_color);
}
}
} else {
}
}
};
@@ -154,12 +152,7 @@ class AllPlotZoneLabel: public QwtPlotItem
);
text = QwtText(zone_names[zone_number]);
if (_parent->referencePlot == NULL) {
text.setFont(QFont("Helvetica",24, QFont::Bold));
} else {
text.setFont(QFont("Helvetica",12, QFont::Bold));
}
text.setFont(QFont("Helvetica",24, QFont::Bold));
QColor text_color = zoneColor(zone_number, num_zones);
text_color.setAlpha(64);
text.setColor(text_color);
@@ -193,127 +186,90 @@ class AllPlotZoneLabel: public QwtPlotItem
static inline double
max(double a, double b) { if (a > b) return a; else return b; }
AllPlot::AllPlot(AllPlotWindow *parent, MainWindow *mainWindow):
AllPlot::AllPlot(QWidget *parent, MainWindow *mainWindow):
QwtPlot(parent),
rideItem(NULL),
settings(NULL),
unit(0),
rideItem(NULL),
bydist(false),
shade_zones(true),
showPowerState(3),
showPowerState(0),
showHrState(Qt::Checked),
showSpeedState(Qt::Checked),
showCadState(Qt::Checked),
showAltState(Qt::Checked),
bydist(false),
parent(parent)
showAltState(Qt::Checked)
{
boost::shared_ptr<QSettings> settings = GetApplicationSettings();
unit = settings->value(GC_UNIT);
referencePlot = NULL;
useMetricUnits = (unit.toString() == "Metric");
// options for turning off/on shading on all plot
// will come in with a future patch, for now we
// enable zone shading by default, since this is
// the current default behaviour
if (false) shade_zones = false;
else shade_zones = true;
smooth = settings->value(GC_RIDE_PLOT_SMOOTHING).toInt();
if (smooth < 1) smooth = 1;
if (smooth < 2)
smooth = 30;
// create a background object for shading
bg = new AllPlotBackground(this);
bg->attach(this);
insertLegend(new QwtLegend(), QwtPlot::BottomLegend);
setCanvasBackground(GColor(CPLOTBACKGROUND));
setCanvasBackground(Qt::white);
setXTitle();
wattsCurve = new QwtPlotCurve(tr("Power"));
QPen wattsPen = QPen(Qt::red);
wattsPen.setWidth(2);
wattsCurve->setPen(wattsPen);
hrCurve = new QwtPlotCurve(tr("Heart Rate"));
QPen hrPen = QPen(Qt::blue);
hrPen.setWidth(2);
hrCurve->setPen(hrPen);
hrCurve->setYAxis(yLeft2);
speedCurve = new QwtPlotCurve(tr("Speed"));
QPen speedPen = QPen(QColor(0, 204, 0));
speedPen.setWidth(2);
speedCurve->setPen(speedPen);
speedCurve->setYAxis(yRight);
cadCurve = new QwtPlotCurve(tr("Cadence"));
QPen cadPen = QPen(QColor(0, 204, 204));
cadPen.setWidth(2);
cadCurve->setPen(cadPen);
cadCurve->setYAxis(yLeft2);
altCurve = new QwtPlotCurve(tr("Altitude"));
// altCurve->setRenderHint(QwtPlotItem::RenderAntialiased);
QPen altPen(QColor(124, 91, 31));
altPen.setWidth(1);
altCurve->setPen(altPen);
QColor brush_color = QColor(124, 91, 31);
brush_color.setAlpha(64);
altCurve->setBrush(brush_color); // fill below the line
altCurve->setYAxis(yRight2);
intervalHighlighterCurve = new QwtPlotCurve();
QPen ihlPen = QPen(Qt::blue);
ihlPen.setWidth(2);
intervalHighlighterCurve->setPen(ihlPen);
intervalHighlighterCurve->setYAxis(yLeft);
QColor ihlbrush = QColor(Qt::blue);
ihlbrush.setAlpha(64);
intervalHighlighterCurve->setBrush(ihlbrush); // fill below the line
intervalHighlighterCurve->setData(IntervalPlotData(this, mainWindow));
intervalHighlighterCurve->attach(this);
this->legend()->remove(intervalHighlighterCurve); // don't show in legend
grid = new QwtPlotGrid();
grid->enableX(false);
grid->attach(this);
// get rid of nasty blank space on right of the plot
plotLayout()->setAlignCanvasToScales(true);
configChanged(); // set colors
}
void
AllPlot::configChanged()
{
double width = 1.0;
boost::shared_ptr<QSettings> settings = GetApplicationSettings();
useMetricUnits = (settings->value(GC_UNIT).toString() == "Metric");
// placeholder for setting antialiasing, will come
// in with a future patch. For now antialiasing is
// not enabled since it can slow down plotting on
// windows and linux platforms.
if (false) {
wattsCurve->setRenderHint(QwtPlotItem::RenderAntialiased);
hrCurve->setRenderHint(QwtPlotItem::RenderAntialiased);
speedCurve->setRenderHint(QwtPlotItem::RenderAntialiased);
cadCurve->setRenderHint(QwtPlotItem::RenderAntialiased);
altCurve->setRenderHint(QwtPlotItem::RenderAntialiased);
intervalHighlighterCurve->setRenderHint(QwtPlotItem::RenderAntialiased);
}
setCanvasBackground(GColor(CPLOTBACKGROUND));
QPen wattsPen = QPen(GColor(CPOWER));
wattsPen.setWidth(width);
wattsCurve->setPen(wattsPen);
QPen hrPen = QPen(GColor(CHEARTRATE));
hrPen.setWidth(width);
hrCurve->setPen(hrPen);
QPen speedPen = QPen(GColor(CSPEED));
speedPen.setWidth(width);
speedCurve->setPen(speedPen);
QPen cadPen = QPen(GColor(CCADENCE));
cadPen.setWidth(width);
cadCurve->setPen(cadPen);
QPen altPen(GColor(CALTITUDE));
altPen.setWidth(width);
altCurve->setPen(altPen);
QColor brush_color = GColor(CALTITUDEBRUSH);
brush_color.setAlpha(200);
altCurve->setBrush(brush_color); // fill below the line
QPen ihlPen = QPen(GColor(CINTERVALHIGHLIGHTER));
ihlPen.setWidth(width);
intervalHighlighterCurve->setPen(ihlPen);
QColor ihlbrush = QColor(GColor(CINTERVALHIGHLIGHTER));
ihlbrush.setAlpha(64);
intervalHighlighterCurve->setBrush(ihlbrush); // fill below the line
this->legend()->remove(intervalHighlighterCurve); // don't show in legend
QPen gridPen(GColor(CPLOTGRID));
QPen gridPen;
gridPen.setStyle(Qt::DotLine);
grid->setPen(gridPen);
grid->attach(this);
zoneLabels = QList <AllPlotZoneLabel *>::QList();
}
struct DataPoint {
@@ -324,7 +280,7 @@ struct DataPoint {
bool AllPlot::shadeZones() const
{
return shade_zones;
return (shade_zones && !wattsArray.empty());
}
void AllPlot::refreshZoneLabels()
@@ -355,13 +311,8 @@ void AllPlot::refreshZoneLabels()
void
AllPlot::recalc()
{
if (referencePlot !=NULL){
return;
}
if (timeArray.empty())
return;
int rideTimeSecs = (int) ceil(timeArray[arrayLength - 1]);
if (rideTimeSecs > 7*24*60*60) {
QwtArray<double> data;
@@ -386,13 +337,13 @@ AllPlot::recalc()
QList<DataPoint> list;
smoothWatts.resize(rideTimeSecs + 1); //(rideTimeSecs + 1);
smoothHr.resize(rideTimeSecs + 1);
smoothSpeed.resize(rideTimeSecs + 1);
smoothCad.resize(rideTimeSecs + 1);
smoothTime.resize(rideTimeSecs + 1);
smoothDistance.resize(rideTimeSecs + 1);
smoothAltitude.resize(rideTimeSecs + 1);
QVector<double> smoothWatts(rideTimeSecs + 1);
QVector<double> smoothHr(rideTimeSecs + 1);
QVector<double> smoothSpeed(rideTimeSecs + 1);
QVector<double> smoothCad(rideTimeSecs + 1);
QVector<double> smoothTime(rideTimeSecs + 1);
QVector<double> smoothDistance(rideTimeSecs + 1);
QVector<double> smoothAltitude(rideTimeSecs + 1);
for (int secs = 0; ((secs < smooth)
&& (secs < rideTimeSecs)); ++secs) {
@@ -472,11 +423,12 @@ AllPlot::recalc()
if (!altArray.empty())
altCurve->setData(xaxis.data() + startingIndex, smoothAltitude.data() + startingIndex, totalPoints);
setAxisScale(xBottom, 0.0, bydist ? totalDist : smoothTime[rideTimeSecs]);
setYMax();
refreshIntervalMarkers();
refreshZoneLabels();
//replot();
replot();
}
void
@@ -498,10 +450,10 @@ AllPlot::refreshIntervalMarkers()
mrk->attach(this);
mrk->setLineStyle(QwtPlotMarker::VLine);
mrk->setLabelAlignment(Qt::AlignRight | Qt::AlignTop);
mrk->setLinePen(QPen(GColor(CPLOTMARKER), 0, Qt::DashDotLine));
mrk->setLinePen(QPen(Qt::black, 0, Qt::DashDotLine));
QwtText text(interval.name);
text.setFont(QFont("Helvetica", 10, QFont::Bold));
text.setColor(GColor(CPLOTMARKER));
text.setColor(Qt::black);
if (!bydist)
mrk->setValue(interval.start / 60.0, 0.0);
else
@@ -510,17 +462,16 @@ AllPlot::refreshIntervalMarkers()
mrk->setLabel(text);
}
}
}
void
AllPlot::setYMax()
{
if (wattsCurve->isVisible()) {
setAxisTitle(yLeft, tr("Watts"));
if (referencePlot == NULL)
setAxisScale(yLeft, 0.0, 1.05 * wattsCurve->maxYValue());
else
setAxisScale(yLeft, 0.0, 1.05 * referencePlot->wattsCurve->maxYValue());
setAxisTitle(yLeft, "Watts");
setAxisScale(yLeft, 0.0, 1.05 * wattsCurve->maxYValue());
setAxisLabelRotation(yLeft,270);
setAxisLabelAlignment(yLeft,Qt::AlignVCenter);
}
@@ -528,18 +479,12 @@ AllPlot::setYMax()
double ymax = 0;
QStringList labels;
if (hrCurve->isVisible()) {
labels << tr("BPM");
if (referencePlot == NULL)
ymax = hrCurve->maxYValue();
else
ymax = referencePlot->hrCurve->maxYValue();
labels << "BPM";
ymax = hrCurve->maxYValue();
}
if (cadCurve->isVisible()) {
labels << tr("RPM");
if (referencePlot == NULL)
ymax = qMax(ymax, cadCurve->maxYValue());
else
ymax = qMax(ymax, referencePlot->cadCurve->maxYValue());
labels << "RPM";
ymax = qMax(ymax, cadCurve->maxYValue());
}
setAxisTitle(yLeft2, labels.join(" / "));
setAxisScale(yLeft2, 0.0, 1.05 * ymax);
@@ -547,25 +492,15 @@ AllPlot::setYMax()
setAxisLabelAlignment(yLeft2,Qt::AlignVCenter);
}
if (speedCurve->isVisible()) {
setAxisTitle(yRight, (useMetricUnits ? tr("km/h") : tr("MPH")));
if (referencePlot == NULL)
setAxisScale(yRight, 0.0, 1.05 * speedCurve->maxYValue());
else
setAxisScale(yRight, 0.0, 1.05 * referencePlot->speedCurve->maxYValue());
setAxisTitle(yRight, (useMetricUnits ? tr("KPH") : tr("MPH")));
setAxisScale(yRight, 0.0, 1.05 * speedCurve->maxYValue());
setAxisLabelRotation(yRight,90);
setAxisLabelAlignment(yRight,Qt::AlignVCenter);
}
if (altCurve->isVisible()) {
setAxisTitle(yRight2, useMetricUnits ? tr("Meters") : tr("Feet"));
double ymin,ymax;
if (referencePlot == NULL) {
ymin = altCurve->minYValue();
ymax = qMax(ymin + 100, 1.05 * altCurve->maxYValue());
} else {
ymin = referencePlot->altCurve->minYValue();
ymax = qMax(ymin + 100, 1.05 * referencePlot->altCurve->maxYValue());
}
double ymin = altCurve->minYValue();
double ymax = qMax(ymin + 100, 1.05 * altCurve->maxYValue());
setAxisScale(yRight2, ymin, ymax);
setAxisLabelRotation(yRight2,90);
setAxisLabelAlignment(yRight2,Qt::AlignVCenter);
@@ -588,88 +523,15 @@ AllPlot::setXTitle()
}
void
AllPlot::setDataFromPlot(AllPlot *plot, int startidx, int stopidx)
{
if (plot == NULL) return;
referencePlot = plot;
setTitle(plot->rideItem->ride()->startTime().toString(GC_DATETIME_FORMAT));
// reference the plot for data and state
rideItem = plot->rideItem;
bydist = plot->bydist;
arrayLength = stopidx-startidx;
if (bydist) {
startidx = plot->distanceIndex(plot->distanceArray[startidx]);
stopidx = plot->distanceIndex(plot->distanceArray[(stopidx>=plot->distanceArray.size()?plot->distanceArray.size()-1:stopidx)])-1;
} else {
startidx = plot->timeIndex(plot->timeArray[startidx]/60);
stopidx = plot->timeIndex(plot->timeArray[(stopidx>=plot->timeArray.size()?plot->timeArray.size()-1:stopidx)]/60)-1;
}
// make sure indexes are still valid
if (startidx > stopidx || startidx < 0 || stopidx < 0) return;
double *smoothW = &plot->smoothWatts[startidx];
double *smoothT = &plot->smoothTime[startidx];
double *smoothHR = &plot->smoothHr[startidx];
double *smoothS = &plot->smoothSpeed[startidx];
double *smoothC = &plot->smoothCad[startidx];
double *smoothA = &plot->smoothAltitude[startidx];
double *smoothD = &plot->smoothDistance[startidx];
double *xaxis = bydist ? smoothD : smoothT;
// attach appropriate curves
if (this->legend()) this->legend()->hide();
wattsCurve->detach();
hrCurve->detach();
speedCurve->detach();
cadCurve->detach();
altCurve->detach();
wattsCurve->setData(xaxis,smoothW,stopidx-startidx);
hrCurve->setData(xaxis, smoothHR,stopidx-startidx);
speedCurve->setData(xaxis, smoothS, stopidx-startidx);
cadCurve->setData(xaxis, smoothC, stopidx-startidx);
altCurve->setData(xaxis, smoothA, stopidx-startidx);
setYMax();
setAxisMaxMajor(yLeft, 5);
setAxisMaxMajor(yLeft2, 5);
setAxisMaxMajor(yRight, 5);
setAxisMaxMajor(yRight2, 5);
setAxisScale(xBottom, xaxis[0], xaxis[stopidx-startidx-1]);
if (!plot->smoothAltitude.empty()) altCurve->attach(this);
if (!plot->smoothWatts.empty()) wattsCurve->attach(this);
if (!plot->smoothHr.empty()) hrCurve->attach(this);
if (!plot->smoothSpeed.empty()) speedCurve->attach(this);
if (!plot->smoothCad.empty()) cadCurve->attach(this);
refreshIntervalMarkers();
refreshZoneLabels();
if (this->legend()) this->legend()->show();
//replot();
}
void
AllPlot::setDataFromRide(RideItem *_rideItem)
AllPlot::setData(RideItem *_rideItem)
{
rideItem = _rideItem;
if (_rideItem == NULL) return;
wattsArray.clear();
RideFile *ride = rideItem->ride();
if (ride && ride->deviceType() != QString("Manual CSV")) {
setTitle(ride->startTime().toString(GC_DATETIME_FORMAT));
const RideFileDataPresent *dataPresent = ride->areDataPresent();
int npoints = ride->dataPoints().size();
@@ -687,11 +549,11 @@ AllPlot::setDataFromRide(RideItem *_rideItem)
speedCurve->detach();
cadCurve->detach();
altCurve->detach();
if (!altArray.empty()) altCurve->attach(this);
if (!wattsArray.empty()) wattsCurve->attach(this);
if (!hrArray.empty()) hrCurve->attach(this);
if (!speedArray.empty()) speedCurve->attach(this);
if (!cadArray.empty()) cadCurve->attach(this);
if (!altArray.empty()) altCurve->attach(this);
wattsCurve->setVisible(dataPresent->watts && showPowerState < 2);
hrCurve->setVisible(dataPresent->hr && showHrState == Qt::Checked);
@@ -701,21 +563,7 @@ AllPlot::setDataFromRide(RideItem *_rideItem)
arrayLength = 0;
foreach (const RideFilePoint *point, ride->dataPoints()) {
// we round the time to nearest 100th of a second
// before adding to the array, to avoid situation
// where 'high precision' time slice is an artefact
// of double precision or slight timing anomalies
// e.g. where realtime gives timestamps like
// 940.002 followed by 940.998 and were previouslt
// both rounded to 940s
//
// NOTE: this rounding mechanism is identical to that
// used by the Ride Editor.
double secs = floor(point->secs);
double msecs = round((point->secs - secs) * 100) * 10;
timeArray[arrayLength] = secs + msecs/1000;
timeArray[arrayLength] = point->secs;
if (!wattsArray.empty())
wattsArray[arrayLength] = max(0, point->watts);
if (!hrArray.empty())
@@ -737,11 +585,11 @@ AllPlot::setDataFromRide(RideItem *_rideItem)
: point->km * MILES_PER_KM));
++arrayLength;
}
recalc();
}
else {
setTitle("no data");
wattsCurve->detach();
hrCurve->detach();
speedCurve->detach();
@@ -756,17 +604,11 @@ AllPlot::setDataFromRide(RideItem *_rideItem)
void
AllPlot::showPower(int id)
{
if (showPowerState == id) return;
showPowerState = id;
wattsCurve->setVisible(id < 2);
shade_zones = (id == 0);
setYMax();
if (shade_zones) {
bg->attach(this);
refreshZoneLabels();
} else
bg->detach();
recalc();
}
void
@@ -834,35 +676,6 @@ AllPlot::setByDistance(int id)
recalc();
}
struct ComparePoints {
bool operator()(const double p1, const double p2) {
return p1 < p2;
}
};
int
AllPlot::timeIndex(double min) const
{
// return index offset for specified time
QVector<double>::const_iterator i = std::lower_bound(
smoothTime.begin(), smoothTime.end(), min, ComparePoints());
if (i == smoothTime.end())
return smoothTime.size();
return i - smoothTime.begin();
}
int
AllPlot::distanceIndex(double km) const
{
// return index offset for specified distance in km
QVector<double>::const_iterator i = std::lower_bound(
smoothDistance.begin(), smoothDistance.end(), km, ComparePoints());
if (i == smoothDistance.end())
return smoothDistance.size();
return i - smoothDistance.begin();
}
/*----------------------------------------------------------------------
* Interval plotting
*--------------------------------------------------------------------*/
@@ -945,10 +758,10 @@ double IntervalPlotData::x(size_t i) const
// which point are we returning?
switch (i%4) {
case 0 : return allPlot->bydist ? multiplier * current->startKM : current->start/60; // bottom left
case 1 : return allPlot->bydist ? multiplier * current->startKM : current->start/60; // top left
case 2 : return allPlot->bydist ? multiplier * current->stopKM : current->stop/60; // bottom right
case 3 : return allPlot->bydist ? multiplier * current->stopKM : current->stop/60; // bottom right
case 0 : return allPlot->byDistance() ? multiplier * current->startKM : current->start/60; // bottom left
case 1 : return allPlot->byDistance() ? multiplier * current->startKM : current->start/60; // top left
case 2 : return allPlot->byDistance() ? multiplier * current->stopKM : current->stop/60; // bottom right
case 3 : return allPlot->byDistance() ? multiplier * current->stopKM : current->stop/60; // bottom right
}
return 0; // shouldn't get here, but keeps compiler happy
}
@@ -971,25 +784,3 @@ size_t IntervalPlotData::size() const { return intervalCount()*4; }
QwtData *IntervalPlotData::copy() const {
return new IntervalPlotData(allPlot, mainWindow);
}
void
AllPlot::pointHover(QwtPlotCurve *curve, int index)
{
if (index >= 0 && curve != intervalHighlighterCurve) {
double value = curve->y(index);
// output the tooltip
QString text = QString("%1 %2")
.arg(value, 0, 'f', 0)
.arg(this->axisTitle(curve->yAxis()).text());
// set that text up
tooltip->setText(text);
} else {
// no point
tooltip->setText("");
}
}

View File

@@ -32,10 +32,7 @@ class AllPlotZoneLabel;
class AllPlotWindow;
class AllPlot;
class IntervalItem;
class IntervalPlotData;
class MainWindow;
class LTMToolTip;
class LTMCanvasPicker;
class AllPlot : public QwtPlot
{
@@ -43,25 +40,19 @@ class AllPlot : public QwtPlot
public:
AllPlot(AllPlotWindow *parent, MainWindow *mainWindow);
AllPlot(QWidget *parent, MainWindow *mainWindow);
// set the curve data e.g. when a ride is selected
void setDataFromRide(RideItem *_rideItem);
void setDataFromPlot(AllPlot *plot, int startidx, int stopidx);
int smoothing() const { return smooth; }
// convert from time/distance to index in *smoothed* datapoints
int timeIndex(double) const;
int distanceIndex(double) const;
bool byDistance() const { return bydist; }
// plot redraw functions
bool shadeZones() const;
void refreshZoneLabels();
void refreshIntervalMarkers();
bool useMetricUnits; // whether metric units are used (or imperial)
// refresh data / plot parameters
void recalc();
void setYMax();
void setXTitle();
bool shadeZones() const;
void refreshZoneLabels();
void refreshIntervalMarkers();
void setData(RideItem *_rideItem);
public slots:
@@ -71,48 +62,32 @@ class AllPlot : public QwtPlot
void showCad(int state);
void showAlt(int state);
void showGrid(int state);
void setShadeZones(bool x) { shade_zones=x; }
void setSmoothing(int value);
void setByDistance(int value);
void configChanged();
void pointHover(QwtPlotCurve*, int);
protected:
friend class ::AllPlotBackground;
friend class ::AllPlotZoneLabel;
friend class ::AllPlotWindow;
friend class ::IntervalPlotData;
// cached state
RideItem *rideItem;
AllPlotBackground *bg;
AllPlotBackground *bg;
QSettings *settings;
QVariant unit;
bool useMetricUnits;
// controls
bool shade_zones;
int showPowerState;
int showHrState;
int showSpeedState;
int showCadState;
int showAltState;
// plot objects
QwtPlotGrid *grid;
QVector<QwtPlotMarker*> d_mrk;
QwtPlotMarker *allMarker1;
QwtPlotMarker *allMarker2;
QwtPlotCurve *wattsCurve;
QwtPlotCurve *hrCurve;
QwtPlotCurve *speedCurve;
QwtPlotCurve *cadCurve;
QwtPlotCurve *altCurve;
QwtPlotCurve *intervalHighlighterCurve; // highlight selected intervals on the Plot
QList <AllPlotZoneLabel *> zoneLabels;
QVector<QwtPlotMarker*> d_mrk;
QList <AllPlotZoneLabel *> zoneLabels;
RideItem *rideItem;
QwtPlotGrid *grid;
// source data
QVector<double> hrArray;
QVector<double> wattsArray;
QVector<double> speedArray;
@@ -120,26 +95,23 @@ class AllPlot : public QwtPlot
QVector<double> timeArray;
QVector<double> distanceArray;
QVector<double> altArray;
// smoothed data
QVector<double> smoothWatts;
QVector<double> smoothHr;
QVector<double> smoothSpeed;
QVector<double> smoothCad;
QVector<double> smoothTime;
QVector<double> smoothDistance;
QVector<double> smoothAltitude;
// array / smooth state
int arrayLength;
int smooth;
bool bydist;
private:
AllPlot *referencePlot;
AllPlotWindow *parent;
LTMToolTip *tooltip;
LTMCanvasPicker *_canvasPicker; // allow point selection/hover
void recalc();
void setYMax();
void setXTitle();
bool shade_zones; // whether power should be shaded
int showPowerState;
int showHrState;
int showSpeedState;
int showCadState;
int showAltState;
};
#endif // _GC_AllPlot_h

File diff suppressed because it is too large Load Diff

View File

@@ -27,13 +27,8 @@ class QwtPlotPanner;
class QwtPlotZoomer;
class QwtPlotPicker;
class QwtPlotMarker;
class QwtArrowButton;
class RideItem;
class IntervalItem;
class QxtSpanSlider;
class QxtGroupBox;
#include "LTMWindow.h" // for tooltip/canvaspicker
class AllPlotWindow : public QWidget
{
@@ -43,42 +38,20 @@ class AllPlotWindow : public QWidget
AllPlotWindow(MainWindow *mainWindow);
void setData(RideItem *ride);
// highlight a selection on the plots
void setStartSelection(AllPlot* plot, double xValue);
void setEndSelection(AllPlot* plot, double xValue, bool newInterval, QString name);
void setStartSelection(double seconds);
void setEndSelection(double seconds, bool newInterval, QString name);
void clearSelection();
void hideSelection();
// zoom to interval range (via span-slider)
void zoomInterval(IntervalItem *);
void zoomInterval(IntervalItem *); // zoom into a specified interval
public slots:
// trap GC signals
void setSmoothingFromSlider();
void setSmoothingFromLineEdit();
void rideSelected();
void intervalSelected();
void zonesChanged();
void intervalsChanged();
void configChanged();
// trap child widget signals
void setSmoothingFromSlider();
void setSmoothingFromLineEdit();
void setStackZoomUp();
void setStackZoomDown();
void zoomChanged();
void moveLeft();
void moveRight();
void showStackChanged(int state);
void setShowPower(int state);
void setShowHr(int state);
void setShowSpeed(int state);
void setShowCad(int state);
void setShowAlt(int state);
void setShowGrid(int state);
void setSmoothing(int value);
void setByDistance(int value);
protected:
@@ -86,63 +59,29 @@ class AllPlotWindow : public QWidget
friend class IntervalPlotData;
friend class MainWindow;
void setAllPlotWidgets(RideItem *rideItem);
void setAllPlotWidgets(RideItem *rideItem);
// cached state
RideItem *current;
int selection;
MainWindow *mainWindow;
// All the plot widgets
AllPlot *allPlot;
AllPlot *fullPlot;
QList <AllPlot *> allPlots;
QwtPlotPanner *allPanner;
QwtPlotZoomer *allZoomer;
// Stacked view
QScrollArea *stackFrame;
QVBoxLayout *stackPlotLayout;
QWidget *stackWidget;
QwtArrowButton *stackZoomDown;
QwtArrowButton *stackZoomUp;
// Normal view
QScrollArea *allPlotFrame;
QPushButton *scrollLeft, *scrollRight;
// Common controls
QGridLayout *controlsLayout;
QCheckBox *showStack;
QCheckBox *showHr;
QCheckBox *showSpeed;
QCheckBox *showCad;
QCheckBox *showAlt;
QComboBox *showPower;
QwtPlotPicker *allPicker;
int selection;
QwtPlotMarker *allMarker1;
QwtPlotMarker *allMarker2;
QwtPlotMarker *allMarker3;
QCheckBox *showHr;
QCheckBox *showSpeed;
QCheckBox *showCad;
QCheckBox *showAlt;
QComboBox *showPower;
QSlider *smoothSlider;
QLineEdit *smoothLineEdit;
QxtSpanSlider *spanSlider;
private:
// reset/redraw all the plots
void setupStackPlots();
void redrawAllPlot();
void redrawFullPlot();
void redrawStackPlot();
void showInfo(QString);
void resetStackedDatas();
int stackWidth;
bool active;
bool stale;
private slots:
void addPickers(AllPlot *allPlot2);
bool stackZoomUpShouldEnable(int sw);
bool stackZoomDownShouldEnable(int sw);
void plotPickerMoved(const QPoint &);
void plotPickerSelected(const QPoint &);
};

View File

@@ -19,33 +19,27 @@
#include "RideMetric.h"
#include "Units.h"
#include <algorithm>
#include <QApplication>
#define tr(s) QObject::tr(s)
class WorkoutTime : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(WorkoutTime)
double seconds;
public:
WorkoutTime() : seconds(0.0)
{
setSymbol("workout_time");
#ifdef ENABLE_METRICS_TRANSLATION
setInternalName("Duration");
}
void initialize() {
#endif
setName(tr("Duration"));
setMetricUnits(tr("seconds"));
setImperialUnits(tr("seconds"));
}
void compute(const RideFile *ride, const Zones *, int, const HrZones *, int,
WorkoutTime() : seconds(0.0) {}
QString symbol() const { return "workout_time"; }
QString name() const { return tr("Duration"); }
QString units(bool) const { return "seconds"; }
int precision() const { return 0; }
double value(bool) const { return seconds; }
void compute(const RideFile *ride, const Zones *, int,
const QHash<QString,RideMetric*> &) {
seconds = ride->dataPoints().back()->secs -
ride->dataPoints().front()->secs + ride->recIntSecs();
setValue(seconds);
ride->dataPoints().front()->secs + ride->recIntSecs();
}
bool canAggregate() const { return true; }
void aggregateWith(const RideMetric &other) { seconds += other.value(true); }
RideMetric *clone() const { return new WorkoutTime(*this); }
};
@@ -55,37 +49,31 @@ static bool workoutTimeAdded =
//////////////////////////////////////////////////////////////////////////////
class TimeRiding : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(TimeRiding)
double secsMovingOrPedaling;
public:
TimeRiding() : secsMovingOrPedaling(0.0)
{
setSymbol("time_riding");
#ifdef ENABLE_METRICS_TRANSLATION
setInternalName("Time Riding");
}
void initialize() {
#endif
setName(tr("Time Riding"));
setMetricUnits(tr("seconds"));
setImperialUnits(tr("seconds"));
}
void compute(const RideFile *ride, const Zones *, int, const HrZones *, int,
TimeRiding() : secsMovingOrPedaling(0.0) {}
QString symbol() const { return "time_riding"; }
QString name() const { return tr("Time Riding"); }
QString units(bool) const { return "seconds"; }
int precision() const { return 0; }
double value(bool) const { return secsMovingOrPedaling; }
void compute(const RideFile *ride, const Zones *, int,
const QHash<QString,RideMetric*> &) {
secsMovingOrPedaling = 0;
foreach (const RideFilePoint *point, ride->dataPoints()) {
if ((point->kph > 0.0) || (point->cad > 0.0))
secsMovingOrPedaling += ride->recIntSecs();
}
setValue(secsMovingOrPedaling);
}
void override(const QMap<QString,QString> &map) {
if (map.contains("value"))
secsMovingOrPedaling = map.value("value").toDouble();
}
bool canAggregate() const { return true; }
void aggregateWith(const RideMetric &other) {
secsMovingOrPedaling += other.value(true);
}
RideMetric *clone() const { return new TimeRiding(*this); }
};
@@ -95,37 +83,28 @@ static bool timeRidingAdded =
//////////////////////////////////////////////////////////////////////////////
class TotalDistance : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(TotalDistance)
double km;
public:
TotalDistance() : km(0.0)
{
setSymbol("total_distance");
#ifdef ENABLE_METRICS_TRANSLATION
setInternalName("Distance");
TotalDistance() : km(0.0) {}
QString symbol() const { return "total_distance"; }
QString name() const { return tr("Distance"); }
QString units(bool metric) const { return metric ? "km" : "miles"; }
int precision() const { return 1; }
double value(bool metric) const {
return metric ? km : (km * MILES_PER_KM);
}
void initialize() {
#endif
setName(tr("Distance"));
setType(RideMetric::Total);
setMetricUnits(tr("km"));
setImperialUnits(tr("miles"));
setPrecision(1);
setConversion(MILES_PER_KM);
}
void compute(const RideFile *ride, const Zones *, int, const HrZones *, int,
void compute(const RideFile *ride, const Zones *, int,
const QHash<QString,RideMetric*> &) {
// Note: The 'km' in each sample is the distance travelled by the
// *end* of the sampling period. The last term in this equation
// accounts for the distance traveled *during* the first sample.
km = ride->dataPoints().back()->km - ride->dataPoints().front()->km
+ ride->dataPoints().front()->kph / 3600.0 * ride->recIntSecs();
setValue(km);
}
bool canAggregate() const { return true; }
void aggregateWith(const RideMetric &other) { km += other.value(true); }
RideMetric *clone() const { return new TotalDistance(*this); }
};
@@ -136,28 +115,20 @@ static bool totalDistanceAdded =
class ElevationGain : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(ElevationGain)
double elegain;
double prevalt;
public:
ElevationGain() : elegain(0.0), prevalt(0.0)
{
setSymbol("elevation_gain");
#ifdef ENABLE_METRICS_TRANSLATION
setInternalName("Elevation Gain");
ElevationGain() : elegain(0.0), prevalt(0.0) {}
QString symbol() const { return "elevation_gain"; }
QString name() const { return tr("Elevation Gain"); }
QString units(bool metric) const { return metric ? "meters" : "feet"; }
int precision() const { return 0; }
double value(bool metric) const {
return metric ? elegain : (elegain * FEET_PER_METER);
}
void initialize() {
#endif
setName(tr("Elevation Gain"));
setType(RideMetric::Total);
setMetricUnits(tr("meters"));
setImperialUnits(tr("feet"));
setConversion(FEET_PER_METER);
}
void compute(const RideFile *ride, const Zones *, int, const HrZones *, int,
void compute(const RideFile *ride, const Zones *, int,
const QHash<QString,RideMetric*> &) {
const double hysteresis = 3.0;
bool first = true;
@@ -174,7 +145,10 @@ class ElevationGain : public RideMetric {
prevalt = point->alt;
}
}
setValue(elegain);
}
bool canAggregate() const { return true; }
void aggregateWith(const RideMetric &other) {
elegain += other.value(true);
}
RideMetric *clone() const { return new ElevationGain(*this); }
};
@@ -185,31 +159,28 @@ static bool elevationGainAdded =
//////////////////////////////////////////////////////////////////////////////
class TotalWork : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(TotalWork)
double joules;
public:
TotalWork() : joules(0.0)
{
setSymbol("total_work");
#ifdef ENABLE_METRICS_TRANSLATION
setInternalName("Work");
}
void initialize() {
#endif
setName(tr("Work"));
setMetricUnits(tr("kJ"));
setImperialUnits(tr("kJ"));
}
void compute(const RideFile *ride, const Zones *, int, const HrZones *, int,
TotalWork() : joules(0.0) {}
QString symbol() const { return "total_work"; }
QString name() const { return tr("Work"); }
QString units(bool) const { return "kJ"; }
int precision() const { return 0; }
double value(bool) const { return joules / 1000.0; }
void compute(const RideFile *ride, const Zones *, int,
const QHash<QString,RideMetric*> &) {
foreach (const RideFilePoint *point, ride->dataPoints()) {
if (point->watts >= 0.0)
joules += point->watts * ride->recIntSecs();
}
setValue(joules/1000);
}
bool canAggregate() const { return true; }
void aggregateWith(const RideMetric &other) {
assert(symbol() == other.symbol());
const TotalWork &tw = dynamic_cast<const TotalWork&>(other);
joules += tw.joules;
}
RideMetric *clone() const { return new TotalWork(*this); }
};
@@ -220,45 +191,34 @@ static bool totalWorkAdded =
//////////////////////////////////////////////////////////////////////////////
class AvgSpeed : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(AvgSpeed)
double secsMoving;
double km;
public:
AvgSpeed() : secsMoving(0.0), km(0.0)
{
setSymbol("average_speed");
#ifdef ENABLE_METRICS_TRANSLATION
setInternalName("Average Speed");
AvgSpeed() : secsMoving(0.0), km(0.0) {}
QString symbol() const { return "average_speed"; }
QString name() const { return tr("Average Speed"); }
QString units(bool metric) const { return metric ? "kph" : "mph"; }
int precision() const { return 1; }
double value(bool metric) const {
if (secsMoving == 0.0) return 0.0;
double kph = km / secsMoving * 3600.0;
return metric ? kph : (kph * MILES_PER_KM);
}
void initialize() {
#endif
setName(tr("Average Speed"));
setMetricUnits(tr("km/h"));
setImperialUnits(tr("mph"));
setType(RideMetric::Average);
setPrecision(1);
setConversion(MILES_PER_KM);
}
void compute(const RideFile *ride, const Zones *, int, const HrZones *, int,
void compute(const RideFile *ride, const Zones *, int,
const QHash<QString,RideMetric*> &deps) {
assert(deps.contains("total_distance"));
km = deps.value("total_distance")->value(true);
foreach (const RideFilePoint *point, ride->dataPoints())
if (point->kph > 0.0) secsMoving += ride->recIntSecs();
setValue(secsMoving ? km / secsMoving * 3600.0 : 0.0);
}
bool canAggregate() const { return true; }
void aggregateWith(const RideMetric &other) {
assert(symbol() == other.symbol());
const AvgSpeed &as = dynamic_cast<const AvgSpeed&>(other);
secsMoving += as.secsMoving;
km += as.km;
setValue(secsMoving ? km / secsMoving * 3600.0 : 0.0);
}
RideMetric *clone() const { return new AvgSpeed(*this); }
};
@@ -269,37 +229,20 @@ static bool avgSpeedAdded =
//////////////////////////////////////////////////////////////////////////////
class AvgPower : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(AvgPower)
struct AvgPower : public AvgRideMetric {
double count, total;
public:
AvgPower()
{
setSymbol("average_power");
#ifdef ENABLE_METRICS_TRANSLATION
setInternalName("Average Power");
}
void initialize() {
#endif
setName(tr("Average Power"));
setMetricUnits(tr("watts"));
setImperialUnits(tr("watts"));
setType(RideMetric::Average);
}
void compute(const RideFile *ride, const Zones *, int, const HrZones *, int,
QString symbol() const { return "average_power"; }
QString name() const { return tr("Average Power"); }
QString units(bool) const { return "watts"; }
int precision() const { return 0; }
void compute(const RideFile *ride, const Zones *, int,
const QHash<QString,RideMetric*> &) {
total = count = 0;
foreach (const RideFilePoint *point, ride->dataPoints()) {
if (point->watts >= 0.0) {
total += point->watts;
++count;
}
}
setValue(count > 0 ? total / count : 0);
setCount(count);
}
RideMetric *clone() const { return new AvgPower(*this); }
};
@@ -309,37 +252,20 @@ static bool avgPowerAdded =
//////////////////////////////////////////////////////////////////////////////
class AvgHeartRate : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(AvgHeartRate)
struct AvgHeartRate : public AvgRideMetric {
double total, count;
public:
AvgHeartRate()
{
setSymbol("average_hr");
#ifdef ENABLE_METRICS_TRANSLATION
setInternalName("Average Heart Rate");
}
void initialize() {
#endif
setName(tr("Average Heart Rate"));
setMetricUnits(tr("bpm"));
setImperialUnits(tr("bpm"));
setType(RideMetric::Average);
}
void compute(const RideFile *ride, const Zones *, int, const HrZones *, int,
QString symbol() const { return "average_hr"; }
QString name() const { return tr("Average Heart Rate"); }
QString units(bool) const { return "bpm"; }
int precision() const { return 0; }
void compute(const RideFile *ride, const Zones *, int,
const QHash<QString,RideMetric*> &) {
total = count = 0;
foreach (const RideFilePoint *point, ride->dataPoints()) {
if (point->hr > 0) {
total += point->hr;
++count;
}
}
setValue(count > 0 ? total / count : 0);
setCount(count);
}
RideMetric *clone() const { return new AvgHeartRate(*this); }
};
@@ -349,37 +275,20 @@ static bool avgHeartRateAdded =
//////////////////////////////////////////////////////////////////////////////
class AvgCadence : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(AvgCadence)
struct AvgCadence : public AvgRideMetric {
double total, count;
public:
AvgCadence()
{
setSymbol("average_cad");
#ifdef ENABLE_METRICS_TRANSLATION
setInternalName("Average Cadence");
}
void initialize() {
#endif
setName(tr("Average Cadence"));
setMetricUnits(tr("rpm"));
setImperialUnits(tr("rpm"));
setType(RideMetric::Average);
}
void compute(const RideFile *ride, const Zones *, int, const HrZones *, int,
QString symbol() const { return "average_cad"; }
QString name() const { return tr("Average Cadence"); }
QString units(bool) const { return "rpm"; }
int precision() const { return 0; }
void compute(const RideFile *ride, const Zones *, int,
const QHash<QString,RideMetric*> &) {
total = count = 0;
foreach (const RideFilePoint *point, ride->dataPoints()) {
if (point->cad > 0) {
total += point->cad;
++count;
}
}
setValue(count > 0 ? total / count : count);
setCount(count);
}
RideMetric *clone() const { return new AvgCadence(*this); }
};
@@ -390,30 +299,20 @@ static bool avgCadenceAdded =
//////////////////////////////////////////////////////////////////////////////
class MaxPower : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(MaxPower)
double max;
public:
MaxPower() : max(0.0)
{
setSymbol("max_power");
#ifdef ENABLE_METRICS_TRANSLATION
setInternalName("Max Power");
}
void initialize() {
#endif
setName(tr("Max Power"));
setMetricUnits(tr("watts"));
setImperialUnits(tr("watts"));
setType(RideMetric::Peak);
}
void compute(const RideFile *ride, const Zones *, int, const HrZones *, int,
MaxPower() : max(0.0) {}
QString symbol() const { return "max_power"; }
QString name() const { return tr("Max Power"); }
QString units(bool) const { return "watts"; }
int precision() const { return 0; }
double value(bool) const { return max; }
void compute(const RideFile *ride, const Zones *, int,
const QHash<QString,RideMetric*> &) {
foreach (const RideFilePoint *point, ride->dataPoints()) {
if (point->watts >= max)
max = point->watts;
}
setValue(max);
}
RideMetric *clone() const { return new MaxPower(*this); }
};
@@ -423,59 +322,16 @@ static bool maxPowerAdded =
//////////////////////////////////////////////////////////////////////////////
class MaxHr : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(MaxHr)
double max;
public:
MaxHr() : max(0.0)
{
setSymbol("max_heartrate");
#ifdef ENABLE_METRICS_TRANSLATION
setInternalName("Max Heartrate");
}
void initialize() {
#endif
setName(tr("Max Heartrate"));
setMetricUnits(tr("bpm"));
setImperialUnits(tr("bpm"));
setType(RideMetric::Peak);
}
void compute(const RideFile *ride, const Zones *, int, const HrZones *, int,
const QHash<QString,RideMetric*> &) {
foreach (const RideFilePoint *point, ride->dataPoints()) {
if (point->hr >= max)
max = point->hr;
}
setValue(max);
}
RideMetric *clone() const { return new MaxHr(*this); }
};
static bool maxHrAdded =
RideMetricFactory::instance().addMetric(MaxHr());
//////////////////////////////////////////////////////////////////////////////
class NinetyFivePercentHeartRate : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(NinetyFivePercentHeartRate)
double hr;
public:
NinetyFivePercentHeartRate() : hr(0.0)
{
setSymbol("ninety_five_percent_hr");
#ifdef ENABLE_METRICS_TRANSLATION
setInternalName("95% Heartrate");
}
void initialize() {
#endif
setName(tr("95% Heartrate"));
setMetricUnits(tr("bpm"));
setImperialUnits(tr("bpm"));
setType(RideMetric::Average);
}
void compute(const RideFile *ride, const Zones *, int, const HrZones *, int,
NinetyFivePercentHeartRate() : hr(0.0) {}
QString symbol() const { return "ninety_five_percent_hr"; }
QString name() const { return tr("95% Heart Rate"); }
QString units(bool) const { return "bpm"; }
int precision() const { return 0; }
double value(bool) const { return hr; }
void compute(const RideFile *ride, const Zones *, int,
const QHash<QString,RideMetric*> &) {
QVector<double> hrs;
foreach (const RideFilePoint *point, ride->dataPoints()) {
@@ -486,7 +342,6 @@ class NinetyFivePercentHeartRate : public RideMetric {
std::sort(hrs.begin(), hrs.end());
hr = hrs[hrs.size() * 0.95];
}
setValue(hr);
}
RideMetric *clone() const { return new NinetyFivePercentHeartRate(*this); }
};

View File

@@ -267,6 +267,7 @@ BestIntervalDialog::findBests(const RideFile *ride, double windowSizeSecs,
totalWatts += point->watts;
window.append(point);
double duration = intervalDuration(window.first(), window.last(), ride);
assert(duration < windowSizeSecs + secsDelta);
if (duration >= windowSizeSecs) {
double start = window.first()->secs;
double stop = start + duration;

View File

@@ -19,7 +19,8 @@
#include "RideMetric.h"
#include "Zones.h"
#include <math.h>
#include <QApplication>
#define tr(s) QObject::tr(s)
const double bikeScoreN = 4.0;
@@ -33,28 +34,21 @@ const double bikeScoreN = 4.0;
// a spreadsheet provided by Dr. Skiba.
class XPower : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(XPower)
double xpower;
double secs;
friend class RelativeIntensity;
friend class BikeScore;
public:
XPower() : xpower(0.0), secs(0.0)
{
setSymbol("skiba_xpower");
#ifdef ENABLE_METRICS_TRANSLATION
setInternalName("xPower");
}
void initialize() {
#endif
setName(tr("xPower"));
setType(RideMetric::Average);
setMetricUnits(tr("watts"));
setImperialUnits(tr("watts"));
}
void compute(const RideFile *ride, const Zones *, int, const HrZones *, int,
XPower() : xpower(0.0), secs(0.0) {}
QString symbol() const { return "skiba_xpower"; }
QString name() const { return tr("xPower"); }
QString units(bool) const { return "watts"; }
int precision() const { return 0; }
double value(bool) const { return xpower; }
void compute(const RideFile *ride, const Zones *, int,
const QHash<QString,RideMetric*> &) {
static const double EPSILON = 0.1;
@@ -87,96 +81,53 @@ class XPower : public RideMetric {
}
xpower = pow(total / count, 0.25);
secs = count * secsDelta;
setValue(xpower);
setCount(secs);
}
RideMetric *clone() const { return new XPower(*this); }
};
class VariabilityIndex : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(VariabilityIndex)
double vi;
double secs;
public:
VariabilityIndex() : vi(0.0), secs(0.0)
{
setSymbol("skiba_variability_index");
#ifdef ENABLE_METRICS_TRANSLATION
setInternalName("Skiba VI");
}
void initialize() {
#endif
setName(tr("Skiba VI"));
setType(RideMetric::Average);
setMetricUnits(tr(""));
setImperialUnits(tr(""));
setPrecision(3);
}
void compute(const RideFile *, const Zones *, int, const HrZones *, int,
const QHash<QString,RideMetric*> &deps) {
assert(deps.contains("skiba_xpower"));
assert(deps.contains("average_power"));
XPower *xp = dynamic_cast<XPower*>(deps.value("skiba_xpower"));
assert(xp);
RideMetric *ap = dynamic_cast<RideMetric*>(deps.value("average_power"));
assert(ap);
vi = xp->value(true) / ap->value(true);
secs = xp->count();
setValue(vi);
}
RideMetric *clone() const { return new VariabilityIndex(*this); }
};
class RelativeIntensity : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(RelativeIntensity)
double reli;
double secs;
public:
RelativeIntensity() : reli(0.0), secs(0.0)
{
setSymbol("skiba_relative_intensity");
#ifdef ENABLE_METRICS_TRANSLATION
setInternalName("Relative Intensity");
}
void initialize() {
#endif
setName(tr("Relative Intensity"));
setType(RideMetric::Average);
setMetricUnits(tr(""));
setImperialUnits(tr(""));
setPrecision(3);
}
void compute(const RideFile *, const Zones *zones, int zoneRange, const HrZones *, int,
const QHash<QString,RideMetric*> &deps) {
if (zones && zoneRange >= 0) {
assert(deps.contains("skiba_xpower"));
XPower *xp = dynamic_cast<XPower*>(deps.value("skiba_xpower"));
assert(xp);
reli = xp->value(true) / zones->getCP(zoneRange);
secs = xp->count();
}
setValue(reli);
setCount(secs);
}
// added djconnel: allow RI to be combined across rides
bool canAggregate() const { return true; }
void aggregateWith(const RideMetric &other) {
assert(symbol() == other.symbol());
const RelativeIntensity &ap = dynamic_cast<const RelativeIntensity&>(other);
reli = secs * pow(reli, bikeScoreN) + ap.count() * pow(ap.value(true), bikeScoreN);
secs += ap.count();
reli = pow(reli / secs, 1.0 / bikeScoreN);
setValue(reli);
const XPower &ap = dynamic_cast<const XPower&>(other);
xpower = pow(xpower, bikeScoreN) * secs + pow(ap.xpower, bikeScoreN) * ap.secs;
secs += ap.secs;
xpower = pow(xpower / secs, 1 / bikeScoreN);
}
// end added djconnel
RideMetric *clone() const { return new XPower(*this); }
};
class RelativeIntensity : public RideMetric {
double reli;
double secs;
public:
RelativeIntensity() : reli(0.0), secs(0.0) {}
QString symbol() const { return "skiba_relative_intensity"; }
QString name() const { return tr("Relative Intensity"); }
QString units(bool) const { return ""; }
int precision() const { return 3; }
double value(bool) const { return reli; }
void compute(const RideFile *, const Zones *zones, int zoneRange,
const QHash<QString,RideMetric*> &deps) {
if (zones && zoneRange >= 0) {
assert(deps.contains("skiba_xpower"));
XPower *xp = dynamic_cast<XPower*>(deps.value("skiba_xpower"));
assert(xp);
reli = xp->xpower / zones->getCP(zoneRange);
secs = xp->secs;
}
}
// added djconnel: allow RI to be combined across rides
bool canAggregate() const { return true; }
void aggregateWith(const RideMetric &other) {
assert(symbol() == other.symbol());
const RelativeIntensity &ap = dynamic_cast<const RelativeIntensity&>(other);
reli = secs * pow(reli, bikeScoreN) + ap.secs * pow(ap.reli, bikeScoreN);
secs += ap.secs;
reli = pow(reli / secs, 1.0 / bikeScoreN);
}
// end added djconnel
@@ -184,58 +135,48 @@ class RelativeIntensity : public RideMetric {
};
class BikeScore : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(BikeScore)
double score;
public:
BikeScore() : score(0.0)
{
setSymbol("skiba_bike_score");
#ifdef ENABLE_METRICS_TRANSLATION
setInternalName("BikeScore&#8482;");
}
void initialize() {
#endif
setName(tr("BikeScore&#8482;"));
setMetricUnits("");
setImperialUnits("");
}
void compute(const RideFile *, const Zones *zones, int zoneRange,const HrZones *, int,
BikeScore() : score(0.0) {}
QString symbol() const { return "skiba_bike_score"; }
QString name() const { return tr("BikeScore&#8482;"); }
QString units(bool) const { return ""; }
int precision() const { return 0; }
double value(bool) const { return score; }
void compute(const RideFile *, const Zones *zones, int zoneRange,
const QHash<QString,RideMetric*> &deps) {
if (!zones || zoneRange < 0)
return;
if (!zones || zoneRange < 0)
return;
assert(deps.contains("skiba_xpower"));
assert(deps.contains("skiba_relative_intensity"));
XPower *xp = dynamic_cast<XPower*>(deps.value("skiba_xpower"));
RideMetric *ri = deps.value("skiba_relative_intensity");
assert(ri);
double normWork = xp->value(true) * xp->count();
double normWork = xp->xpower * xp->secs;
double rawBikeScore = normWork * ri->value(true);
double workInAnHourAtCP = zones->getCP(zoneRange) * 3600;
score = rawBikeScore / workInAnHourAtCP * 100.0;
setValue(score);
}
void override(const QMap<QString,QString> &map) {
if (map.contains("value"))
score = map.value("value").toDouble();
}
RideMetric *clone() const { return new BikeScore(*this); }
bool canAggregate() const { return true; }
void aggregateWith(const RideMetric &other) { score += other.value(true); }
};
static bool addAllFour() {
static bool addAllThree() {
RideMetricFactory::instance().addMetric(XPower());
QVector<QString> deps;
deps.append("skiba_xpower");
RideMetricFactory::instance().addMetric(RelativeIntensity(), &deps);
deps.append("skiba_relative_intensity");
RideMetricFactory::instance().addMetric(BikeScore(), &deps);
deps.clear();
deps.append("skiba_xpower");
deps.append("average_power");
RideMetricFactory::instance().addMetric(VariabilityIndex(), &deps);
return true;
}
static bool allFourAdded = addAllFour();
static bool allThreeAdded = addAllThree();

View File

@@ -1,768 +0,0 @@
/*
* Copyright (c) 2010 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 "BinRideFile.h"
#include <QSharedPointer>
#include <QMap>
#include <QSet>
#include <assert.h>
#include <stdio.h>
#include <stdint.h>
#define RECORD_TYPE__META 0
#define RECORD_TYPE__RIDE_DATA 1
#define RECORD_TYPE__RAW_DATA 2
#define RECORD_TYPE__SPARSE_DATA 3
#define RECORD_TYPE__INTERVAL_DATA 4
#define RECORD_TYPE__DATA_ERROR 5
#define RECORD_TYPE__HISTORY 6
#define FORMAT_ID__RIDE_START 0
#define FORMAT_ID__RIDE_SAMPLE_RATE 1
#define FORMAT_ID__FIRMWARE_VERSION 2
#define FORMAT_ID__LAST_UPDATE 3
#define FORMAT_ID__ODOMETER 4
#define FORMAT_ID__PRIMARY_POWER_ID 5
#define FORMAT_ID__SECONDARY_POWER_ID 6
#define FORMAT_ID__CHEST_STRAP_ID 7
#define FORMAT_ID__CADENCE_ID 8
#define FORMAT_ID__SPEED_ID 9
#define FORMAT_ID__RESISTANCE_UNITID 10
#define FORMAT_ID__WORKOUT_ID 11
#define FORMAT_ID__USER_WEIGHT 12
#define FORMAT_ID__USER_CATEGORY 13
#define FORMAT_ID__USER_HR_ZONE_1 14
#define FORMAT_ID__USER_HR_ZONE_2 15
#define FORMAT_ID__USER_HR_ZONE_3 16
#define FORMAT_ID__USER_HR_ZONE_4 17
#define FORMAT_ID__USER_POWER_ZONE_1 18
#define FORMAT_ID__USER_POWER_ZONE_2 19
#define FORMAT_ID__USER_POWER_ZONE_3 20
#define FORMAT_ID__USER_POWER_ZONE_4 21
#define FORMAT_ID__USER_POWER_ZONE_5 22
#define FORMAT_ID__WHEEL_CIRC 23
#define FORMAT_ID__RIDE_DISTANCE 24
#define FORMAT_ID__RIDE_TIME 25
#define FORMAT_ID__POWER 26
#define FORMAT_ID__TORQUE 27
#define FORMAT_ID__SPEED 28
#define FORMAT_ID__CADENCE 29
#define FORMAT_ID__HEART_RATE 30
#define FORMAT_ID__GRADE 31
#define FORMAT_ID__ALTITUDE_OLD 32
#define FORMAT_ID__RAW_DATA 33
#define FORMAT_ID__TEMPERATURE 34
#define FORMAT_ID__INTERVAL_NUM 35
#define FORMAT_ID__DROPOUT_FLAGS 36
#define FORMAT_ID__RAW_DATA_FORMAT 37
#define FORMAT_ID__RAW_BARO_SENSOR 38
#define FORMAT_ID__ALTITUDE 39
#define FORMAT_ID__THRESHOLD_POWER 40
#define FORMAT_ID__UNKNOW_41 41
#define FORMAT_ID__UNKNOW_42 42
#define FORMAT_ID__UNKNOW_43 43
#define FORMAT_ID__UNKNOW_44 44
#define FORMAT_ID__UNKNOW_45 45
#define FORMAT_ID__UNKNOW_46 46
#define FORMAT_ID__UNKNOW_47 47
static int binFileReaderRegistered =
RideFileFactory::instance().registerReader(
"bin", "Joule Bin File", new BinFileReader());
struct BinField {
int num;
int id;
int size; // in bytes
};
struct BinDefinition {
int format_identifier;
std::vector<BinField> fields;
};
struct BinFileReaderState
{
static QMap<int,QString> global_record_types;
static QMap<int,QString> global_format_identifiers;
QMap<int, BinDefinition> local_format_identifiers;
QSet<int> unknown_record_types, unknown_format_identifiers;
QSet<int> unused_record_types;
QSet<int> unexpected_record_types;
QMap<int, QSet<int> > unknown_format_identifiers_for_record_types;
QMap<int, QSet<int> > unused_format_identifiers_for_record_types;
QMap<int, QSet<int> > unexpected_format_identifiers_for_record_types;
QFile &file;
QStringList &errors;
RideFile *rideFile;
time_t start_time;
int interval;
double last_interval_secs;
bool stopped;
BinFileReaderState(QFile &file, QStringList &errors) :
file(file), errors(errors), rideFile(NULL), start_time(0),
interval(0), last_interval_secs(0.0), stopped(true)
{
if (global_record_types.isEmpty()) {
global_record_types.insert(RECORD_TYPE__META, "Meta Data");
global_record_types.insert(RECORD_TYPE__RIDE_DATA, "1 sec detail ride data");
global_record_types.insert(RECORD_TYPE__RAW_DATA, "Raw data packet");
global_record_types.insert(RECORD_TYPE__SPARSE_DATA, "Sparse ride data");
global_record_types.insert(RECORD_TYPE__INTERVAL_DATA, "Interval");
global_record_types.insert(RECORD_TYPE__DATA_ERROR, "Data error");
global_record_types.insert(RECORD_TYPE__HISTORY, "History summary record");
}
if (global_format_identifiers.isEmpty()) {
global_format_identifiers.insert(FORMAT_ID__RIDE_START, "Ride start");
global_format_identifiers.insert(FORMAT_ID__RIDE_SAMPLE_RATE, "Ride sample rate");
global_format_identifiers.insert(FORMAT_ID__FIRMWARE_VERSION, "Firmware Version");
global_format_identifiers.insert(FORMAT_ID__LAST_UPDATE, "Last update");
global_format_identifiers.insert(FORMAT_ID__ODOMETER, "Odometer");
global_format_identifiers.insert(FORMAT_ID__PRIMARY_POWER_ID, "Primary Power Radio ID");
global_format_identifiers.insert(FORMAT_ID__SECONDARY_POWER_ID, "Secondary Power Radio ID");
global_format_identifiers.insert(FORMAT_ID__CHEST_STRAP_ID, "Chest strap Radio ID");
global_format_identifiers.insert(FORMAT_ID__CADENCE_ID, "Cadense sensor Radio ID");
global_format_identifiers.insert(FORMAT_ID__SPEED_ID, "Speed sensor Radio ID");
global_format_identifiers.insert(FORMAT_ID__RESISTANCE_UNITID, "Resistance Unit Radio ID");
global_format_identifiers.insert(FORMAT_ID__WORKOUT_ID, "Workout ID");
global_format_identifiers.insert(FORMAT_ID__USER_WEIGHT, "User weight");
global_format_identifiers.insert(FORMAT_ID__USER_CATEGORY, "User training category");
global_format_identifiers.insert(FORMAT_ID__USER_HR_ZONE_1, "User HR Zone 1");
global_format_identifiers.insert(FORMAT_ID__USER_HR_ZONE_2, "User HR Zone 2");
global_format_identifiers.insert(FORMAT_ID__USER_HR_ZONE_3, "User HR Zone 3");
global_format_identifiers.insert(FORMAT_ID__USER_HR_ZONE_4, "User HR Zone 4");
global_format_identifiers.insert(FORMAT_ID__USER_POWER_ZONE_1, "User Power Zone 1");
global_format_identifiers.insert(FORMAT_ID__USER_POWER_ZONE_2, "User Power Zone 2");
global_format_identifiers.insert(FORMAT_ID__USER_POWER_ZONE_3, "User Power Zone 3");
global_format_identifiers.insert(FORMAT_ID__USER_POWER_ZONE_4, "User Power Zone 4");
global_format_identifiers.insert(FORMAT_ID__USER_POWER_ZONE_5, "User Power Zone 5");
global_format_identifiers.insert(FORMAT_ID__WHEEL_CIRC, "Wheel circumference");
global_format_identifiers.insert(FORMAT_ID__RIDE_DISTANCE, "Ride distance");
global_format_identifiers.insert(FORMAT_ID__RIDE_TIME, "Ride time");
global_format_identifiers.insert(FORMAT_ID__POWER, "Power");
global_format_identifiers.insert(FORMAT_ID__TORQUE, "Torque");
global_format_identifiers.insert(FORMAT_ID__SPEED, "Speed");
global_format_identifiers.insert(FORMAT_ID__CADENCE, "Cadence");
global_format_identifiers.insert(FORMAT_ID__HEART_RATE, "Heart rate");
global_format_identifiers.insert(FORMAT_ID__GRADE, "Grade");
global_format_identifiers.insert(FORMAT_ID__ALTITUDE_OLD, "Altitude");
global_format_identifiers.insert(FORMAT_ID__RAW_DATA, "Raw Data");
global_format_identifiers.insert(FORMAT_ID__TEMPERATURE, "Temperature");
global_format_identifiers.insert(FORMAT_ID__INTERVAL_NUM, "Interval number");
global_format_identifiers.insert(FORMAT_ID__DROPOUT_FLAGS, "Dropout flags");
global_format_identifiers.insert(FORMAT_ID__RAW_DATA_FORMAT, "Raw Data Format ID");
global_format_identifiers.insert(FORMAT_ID__RAW_BARO_SENSOR, "Raw Baro Sensor Value");
global_format_identifiers.insert(FORMAT_ID__ALTITUDE, "Altitude");
global_format_identifiers.insert(FORMAT_ID__THRESHOLD_POWER, "Threshold power");
global_format_identifiers.insert(FORMAT_ID__UNKNOW_41, "Unknow 41");
global_format_identifiers.insert(FORMAT_ID__UNKNOW_42, "Unknow 42");
global_format_identifiers.insert(FORMAT_ID__UNKNOW_43, "Unknow 43");
global_format_identifiers.insert(FORMAT_ID__UNKNOW_44, "Unknow 44");
global_format_identifiers.insert(FORMAT_ID__UNKNOW_45, "Unknow 45");
global_format_identifiers.insert(FORMAT_ID__UNKNOW_46, "Unknow 46");
global_format_identifiers.insert(FORMAT_ID__UNKNOW_47, "Unknow 47");
}
}
int read_byte(int *count = NULL, int *sum = NULL) {
char c;
if (file.read(&c, 1) != 1)
return -1;
if (sum)
*sum += (0xff & c);
if (count)
*count += 1;
return 0xff & c;
}
int read_double_byte(int *count = NULL, int *sum = NULL) {
char c1,c2;
if (file.read(&c1, 1) != 1)
return -1;
if (count)
*count += 1;
if (file.read(&c2, 1) != 1)
return -1;
if (count)
*count += 1;
if (sum)
*sum += (0xff & c1) + (0xff & c2);
return 256*(0xff & c1) + (0xff & c2);
}
int read_four_byte(int *count = NULL, int *sum = NULL) {
char c1,c2,c3,c4;
if (file.read(&c1, 1) != 1)
return -1;
if (count)
*count += 1;
if (file.read(&c2, 1) != 1)
return -1;
if (count)
*count += 1;
if (file.read(&c3, 1) != 1)
return -1;
if (count)
*count += 1;
if (file.read(&c4, 1) != 1)
return -1;
if (count)
*count += 1;
if (sum)
*sum += (0xff & c1) + (0xff & c2) + (0xff & c3) + (0xff & c4);
return 256*256*256*(0xff & c1) + 256*256*(0xff & c2) + 256*(0xff & c3) + (0xff & c4);
}
int read_date(int *count = NULL, int *sum = NULL) {
char c1,c2,c3,c4,c5,c6,c7;
if (file.read(&c1, 1) != 1)
return -1;
if (count)
*count += 1;
if (file.read(&c2, 1) != 1)
return -1;
if (count)
*count += 1;
if (file.read(&c3, 1) != 1)
return -1;
if (count)
*count += 1;
if (file.read(&c4, 1) != 1)
return -1;
if (count)
*count += 1;
if (file.read(&c5, 1) != 1)
return -1;
if (count)
*count += 1;
if (file.read(&c6, 1) != 1)
return -1;
if (count)
*count += 1;
if (file.read(&c7, 1) != 1)
return -1;
if (count)
*count += 1;
if (sum)
*sum += (0xff & c1) + (0xff & c2) + (0xff & c3) + (0xff & c4) + (0xff & c5) + (0xff & c6) + (0xff & c7);
QDateTime dateTime(QDate((0xff & c1)*256+(0xff & c2), (0xff & c3), (0xff & c4)), QTime((0xff & c5), (0xff & c6), (0xff & c7)), Qt::LocalTime);
return dateTime.toTime_t();
}
void decodeMetaData(const BinDefinition &def, const std::vector<int> values) {
int i = 0;
QString deviceInfo = "";
QDateTime t;
foreach(const BinField &field, def.fields) {
if (!global_format_identifiers.contains(field.id)) {
unknown_format_identifiers.insert(field.id);
} else {
int value = values[i++];
switch (field.id) {
case FORMAT_ID__RIDE_START : {
start_time = value;
t.setTime_t(value);
rideFile->setStartTime(t);
break;
}
case FORMAT_ID__RIDE_SAMPLE_RATE :
rideFile->setRecIntSecs(value/1000.0);
break;
case FORMAT_ID__FIRMWARE_VERSION :
//rideFile->setDeviceType(rideFile->deviceType()+ QString(" (%1)").arg(value));
deviceInfo += rideFile->deviceType()+QString(" Version %1\n").arg(value);
break;
case FORMAT_ID__LAST_UPDATE :
//t.setTime_t(value);
//deviceInfo += QString("Last update %1\n").arg(t.toString());
unused_format_identifiers_for_record_types[def.format_identifier].insert(field.id);
break;
case FORMAT_ID__ODOMETER :
if (value>0)
deviceInfo += QString("Odometer %1km\n").arg(value/10.0);
break;
case FORMAT_ID__PRIMARY_POWER_ID :
if (value>0)
deviceInfo += QString("Primary Power Id %1\n").arg(value);
break;
case FORMAT_ID__SECONDARY_POWER_ID :
if (value>0)
deviceInfo += QString("Secondary Power Id %1\n").arg(value);
break;
case FORMAT_ID__CHEST_STRAP_ID :
if (value>0)
deviceInfo += QString("Chest strap Id %1\n").arg(value);
break;
case FORMAT_ID__CADENCE_ID :
if (value>0)
deviceInfo += QString("Cadence Id %1\n").arg(value);
break;
case FORMAT_ID__SPEED_ID :
if (value>0)
deviceInfo += QString("Speed Id %1\n").arg(value);
break;
case FORMAT_ID__RESISTANCE_UNITID :
if (value>0)
deviceInfo += QString("Resistance Unit Id %1\n").arg(value);
break;
case FORMAT_ID__WORKOUT_ID :
rideFile->setTag("Workout Code", QString(value));
break;
case FORMAT_ID__USER_WEIGHT :
unused_format_identifiers_for_record_types[def.format_identifier].insert(field.id);
break;
case FORMAT_ID__USER_CATEGORY :
unused_format_identifiers_for_record_types[def.format_identifier].insert(field.id);
break;
case FORMAT_ID__USER_HR_ZONE_1 :
unused_format_identifiers_for_record_types[def.format_identifier].insert(field.id);
break;
case FORMAT_ID__USER_HR_ZONE_2 :
unused_format_identifiers_for_record_types[def.format_identifier].insert(field.id);
break;
case FORMAT_ID__USER_HR_ZONE_3 :
unused_format_identifiers_for_record_types[def.format_identifier].insert(field.id);
break;
case FORMAT_ID__USER_HR_ZONE_4 :
unused_format_identifiers_for_record_types[def.format_identifier].insert(field.id);
break;
case FORMAT_ID__USER_POWER_ZONE_1 :
unused_format_identifiers_for_record_types[def.format_identifier].insert(field.id);
break;
case FORMAT_ID__USER_POWER_ZONE_2 :
unused_format_identifiers_for_record_types[def.format_identifier].insert(field.id);
break;
case FORMAT_ID__USER_POWER_ZONE_3 :
unused_format_identifiers_for_record_types[def.format_identifier].insert(field.id);
break;
case FORMAT_ID__USER_POWER_ZONE_4 :
unused_format_identifiers_for_record_types[def.format_identifier].insert(field.id);
break;
case FORMAT_ID__USER_POWER_ZONE_5 :
unused_format_identifiers_for_record_types[def.format_identifier].insert(field.id);
break;
case FORMAT_ID__WHEEL_CIRC :
deviceInfo += QString("Wheel Circ. %1mm\n").arg(value);
break;
case FORMAT_ID__THRESHOLD_POWER :
deviceInfo += QString("Threshold Power %1W\n").arg(value);
break;
case FORMAT_ID__UNKNOW_41 :
break;
case FORMAT_ID__UNKNOW_42 :
break;
case FORMAT_ID__UNKNOW_43 :
break;
default:
unexpected_format_identifiers_for_record_types[def.format_identifier].insert(field.id);
}
}
}
rideFile->setTag("Device Info", deviceInfo);
}
void decodeRideData(const BinDefinition &def, const std::vector<int> values) {
int i = 0;
double secs = 0, alt = 0, cad = 0, km = 0, grade = 0, hr = 0;
double nm = 0, kph = 0, watts = 0;
foreach(const BinField &field, def.fields) {
if (!global_format_identifiers.contains(field.id)) {
unknown_format_identifiers.insert(field.id);
} else {
int value = values[i++];
switch (field.id) {
case FORMAT_ID__RIDE_DISTANCE :
km = value/10000.0;
case FORMAT_ID__RIDE_TIME :
secs = value / 1000.0;
break;
case FORMAT_ID__POWER :
if (value <= 2999) // Limit from definition
watts = value;
break;
case FORMAT_ID__TORQUE :
nm = value;
break;
case FORMAT_ID__SPEED :
kph = value*3.6/100.0;
if (kph > 145) // Limit for data error
kph = 0;
break;
case FORMAT_ID__CADENCE :
if (value < 255) // Limit for data error
cad = value;
break;
case FORMAT_ID__HEART_RATE :
if (value < 255) // Limit for data error
hr = value;
break;
case FORMAT_ID__GRADE :
grade = value;
break;
case FORMAT_ID__ALTITUDE :
alt = value/10.0;
break;
case FORMAT_ID__ALTITUDE_OLD :
alt = value/10.0;
break;
case FORMAT_ID__UNKNOW_44 :
break;
case FORMAT_ID__UNKNOW_45 :
break;
case FORMAT_ID__UNKNOW_46 :
break;
case FORMAT_ID__UNKNOW_47 :
break;
default:
unexpected_format_identifiers_for_record_types[def.format_identifier].insert(field.id);
}
}
}
double headwind = 0.0;
int interval = 0;
int lng = 0;
int lat = 0;
rideFile->appendPoint(secs, cad, hr, km, kph, nm, watts, alt, lng, lat, headwind, interval);
//printf("addPoint time %f hr %f speed %f dist %f alt %f\n", secs, hr, kph, km, alt);
}
void decodeSparseData(const BinDefinition &def, const std::vector<int> values) {
int i = 0;
int temperature_count = 0;
double temperature = 0.0;
foreach(const BinField &field, def.fields) {
if (!global_format_identifiers.contains(field.id)) {
unknown_format_identifiers.insert(field.id);
} else {
int value = values[i++];
switch (field.id) {
case FORMAT_ID__TEMPERATURE :
// use for average
temperature += value/10.0;
temperature_count ++;
break;
case FORMAT_ID__RIDE_TIME :
unused_format_identifiers_for_record_types[def.format_identifier].insert(field.id);
break;
default:
unexpected_format_identifiers_for_record_types[def.format_identifier].insert(field.id);
}
}
}
rideFile->setTag("Temperature", QString("%1").arg(temperature/temperature_count));
}
void decodeIntervalData(const BinDefinition &def, const std::vector<int> values) {
int i = 0;
double secs = 0;
foreach(const BinField &field, def.fields) {
if (!global_format_identifiers.contains(field.id)) {
unknown_format_identifiers.insert(field.id);
} else {
int value = values[i++];
switch (field.id) {
case FORMAT_ID__INTERVAL_NUM :
interval = value;
break;
case FORMAT_ID__RIDE_TIME :
secs = value / 1000.0;;
break;
default:
unexpected_format_identifiers_for_record_types[def.format_identifier].insert(field.id);
}
}
}
if (interval>1) {
rideFile->addInterval(last_interval_secs, secs, QString("%1").arg(interval-1));
}
last_interval_secs = secs;
}
void decodeDataError(const BinDefinition &def, const std::vector<int> values) {
int i = 0;
foreach(const BinField &field, def.fields) {
if (!global_format_identifiers.contains(field.id)) {
unknown_format_identifiers.insert(field.id);
} else {
int value = values[i++];
bool b0 = (value % 2);
bool b1 = (value / 2>1);
bool b2 = (value / 4>1);
bool b3 = (value / 8>1);
bool b4 = (value / 16>1);
bool b5 = (value / 32>1);
bool b6 = (value / 64>1);
bool b7 = (value / 128>1);
QString b = QString("DataError : %1 %2 %3 %4 %5 %6 %7 %8").arg(b0?"0":"").arg(b1?"1":"").arg(b2?"2":"").arg(b3?"3":"").arg(b4?"4":"").arg(b5?"5":"").arg(b6?"6":"").arg(b7?"7":"");
switch (field.id) {
case FORMAT_ID__DROPOUT_FLAGS :
//errors << QString("DataError field.id %1 value %2").arg(field.id).arg(b);
unused_format_identifiers_for_record_types[def.format_identifier].insert(field.id);
break;
case FORMAT_ID__RIDE_TIME :
//errors << QString("DataError time field.id %1 value %2").arg(field.id).arg(value/1000);
unused_format_identifiers_for_record_types[def.format_identifier].insert(field.id);
break;
default:
unexpected_format_identifiers_for_record_types[def.format_identifier].insert(field.id);
}
}
}
}
void decodeHistoryData(const BinDefinition &def, const std::vector<int> /*values*/) {
//int i = 0;
foreach(const BinField &field, def.fields) {
if (!global_format_identifiers.contains(field.id)) {
unknown_format_identifiers.insert(field.id);
} else {
//int value = values[i++];
switch (field.id) {
default:
unexpected_format_identifiers_for_record_types[def.format_identifier].insert(field.id);
}
}
}
}
int read_record(bool &stop, QStringList &errors) {
int sum = 0;
int bytes_read = 0;
int record_type = read_byte(&bytes_read, &sum); // Always 0xFF
if (record_type == -1) {
errors << QString("Truncated file");
//bytes_read++;
return bytes_read;
} else if (record_type == 255) {
int format_identifier = read_byte(&bytes_read, &sum);
if (format_identifier == -1) {
errors << QString("Truncated file");
//bytes_read++;
return bytes_read;
} else if (!global_record_types.contains(format_identifier)) {
errors << QString("unknown format_identifier %1").arg(format_identifier);
stop = true;
return bytes_read;
}
int nb_meta = read_double_byte(&bytes_read, &sum);
local_format_identifiers.insert(format_identifier, BinDefinition());
//printf("- format_identifier : %d\n", format_identifier);
BinDefinition &def = local_format_identifiers[format_identifier];
def.format_identifier = format_identifier;
for (int i = 0; i < nb_meta; ++i) {
def.fields.push_back(BinField());
BinField &field = def.fields.back();
int field_id = read_double_byte(&bytes_read,&sum);
field.id = field_id;
int field_size = read_double_byte(&bytes_read,&sum);
field.size = field_size;
//printf("- field %d : %d\n", field_id, field_size);
}
int checksum = read_double_byte(&bytes_read);
//printf("- checksum %d : %d\n", checksum, sum);
if (checksum == -1) {
errors << QString("Truncated file");
return bytes_read;
} else if (checksum != sum) {
errors << QString("bad checksum: %1").arg(sum);
stop = true;
return bytes_read;
}
}
else {
int format_identifier = record_type;
//printf("- data for format identifier : %d\n", format_identifier);
if (!local_format_identifiers.contains(format_identifier)) {
errors << QString("undefined format_identifier: %1").arg(format_identifier);
stop = true;
return bytes_read;
} else {
const BinDefinition &def = local_format_identifiers[format_identifier];
//printf("- fields type : %d\n", def.format_identifier);
std::vector<int> values;
foreach(const BinField &field, def.fields) {
//printf("- field : %d \n", field.id);
int v;
switch (field.size) {
case 1: v = read_byte(&bytes_read,&sum); break;
case 2: v = read_double_byte(&bytes_read,&sum); break;
case 4: v = read_four_byte(&bytes_read,&sum); break;
case 7: v = read_date(&bytes_read,&sum); break;
default:
for (int i = 0; i < field.size; ++i)
read_byte(&bytes_read,&sum);
errors << QString("unsupported field size %1").arg(field.size);
}
values.push_back(v);
//printf("- %d : %d\n", field.id, v);
}
int checksum = read_double_byte(&bytes_read);
//printf("- checksum %d : %d\n", checksum, sum);
if (checksum == -1) {
errors << QString("Truncated file");
return bytes_read;
} else if (checksum != sum) {
errors << QString("bad checksum: %1").arg(sum);
stop = true;
return bytes_read;
}
switch (def.format_identifier) {
case RECORD_TYPE__META: decodeMetaData(def, values); break;
case RECORD_TYPE__RIDE_DATA: decodeRideData(def, values); break;
case RECORD_TYPE__RAW_DATA:
unused_record_types.insert(def.format_identifier);
break;
case RECORD_TYPE__SPARSE_DATA: decodeSparseData(def, values); break;
case RECORD_TYPE__INTERVAL_DATA: decodeIntervalData(def, values); break;
case RECORD_TYPE__DATA_ERROR: decodeDataError(def, values); break;
case RECORD_TYPE__HISTORY: decodeHistoryData(def, values); break;
default:
unexpected_record_types.insert(def.format_identifier);
}
}
}
return bytes_read;
}
RideFile * run() {
errors.clear();
rideFile = new RideFile;
rideFile->setDeviceType("Joule");
if (!file.open(QIODevice::ReadOnly)) {
delete rideFile;
return NULL;
}
//
bool stop = false;
int data_size = file.size();
int bytes_read = 0;
while (!stop && (bytes_read < data_size)) {
bytes_read += read_record(stop, errors);
}
if (last_interval_secs>0) {
rideFile->addInterval(last_interval_secs, rideFile->dataPoints().last()->secs, QString("%1").arg(interval));
}
if (stop) {
delete rideFile;
return NULL;
}
else {
foreach(int num, unknown_record_types) {
errors << QString("unknow record type %1; ignoring it").arg(num);
}
foreach(int num, unknown_format_identifiers) {
errors << QString("unknow format identifier %1; ignoring it").arg(num);
}
/*foreach(int num, unused_record_types) {
errors << QString("unused record type \"%1\" (%2)\n").arg(global_record_types[num].toAscii().constData())
.arg(num);
}
foreach(QSet<int> set, unused_format_identifiers_for_record_types) {
foreach(int num, set) {
int record_type = unused_format_identifiers_for_record_types.keys(set).takeFirst();
errors << QString("unused format identifier \"%1\" (%2) in \"%3\" (%4)\n")
.arg(global_format_identifiers[num].toAscii().constData())
.arg(num)
.arg(global_record_types[record_type].toAscii().constData())
.arg(record_type);
}
}*/
foreach(int num, unexpected_record_types) {
errors << QString("unexpected record type %1 (%2)\n").arg(global_record_types[num]).arg(num);
}
foreach(QSet<int> set, unexpected_format_identifiers_for_record_types) {
foreach(int num, set) {
int record_type = unexpected_format_identifiers_for_record_types.keys(set).takeFirst();
errors << QString("unexpected format identifier \"%1\" (%2) in \"%3\" (%4)\n")
.arg(global_format_identifiers[num].toAscii().constData())
.arg(num)
.arg(global_record_types[record_type].toAscii().constData())
.arg(record_type);
}
}
return rideFile;
}
}
};
QMap<int,QString> BinFileReaderState::global_record_types;
QMap<int,QString> BinFileReaderState::global_format_identifiers;
RideFile *BinFileReader::openRideFile(QFile &file, QStringList &errors) const
{
QSharedPointer<BinFileReaderState> state(new BinFileReaderState(file, errors));
return state->run();
}

View File

@@ -1,29 +0,0 @@
/*
* Copyright (c) 2007 Sean C. Rhea (srhea@srhea.net)
*
* 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
*/
#ifndef _BinRideFile_h
#define _BinRideFile_h
#include "RideFile.h"
struct BinFileReader : public RideFileReader {
virtual RideFile *openRideFile(QFile &file, QStringList &errors) const;
};
#endif // _BinRideFile_h

View File

@@ -1,66 +0,0 @@
/*
* 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 "ColorButton.h"
#include <QPainter>
#include <QColorDialog>
ColorButton::ColorButton(QWidget *parent, QString name, QColor color) : QPushButton("", parent), color(color), name(name)
{
setColor(color);
connect(this, SIGNAL(clicked()), this, SLOT(clicked()));
};
void
ColorButton::setColor(QColor ncolor)
{
color = ncolor;
QPixmap pix(24, 24);
QPainter painter(&pix);
if (color.isValid()) {
painter.setPen(Qt::gray);
painter.setBrush(QBrush(color));
painter.drawRect(0, 0, 24, 24);
}
QIcon icon;
icon.addPixmap(pix);
setIcon(icon);
setContentsMargins(2,2,2,2);
setFlat(true);
setFixedWidth(34);
setMinimumWidth(34);
setMaximumWidth(34);
}
void
ColorButton::clicked()
{
// Color picker dialog
QColorDialog picker(this);
picker.setCurrentColor(color);
QColor rcolor = picker.getColor();
// if we got a good color use it and notify others
if (rcolor.isValid()) {
setColor(rcolor);
colorChosen(color);
}
}

View File

@@ -1,47 +0,0 @@
/*
* 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
*/
#ifndef _GC_ColorButton_h
#define _GC_ColorButton_h 1
#include <QPushButton>
#include <QColor>
class ColorButton : public QPushButton
{
Q_OBJECT
public:
ColorButton(QWidget *parent, QString, QColor);
void setColor(QColor);
QColor getColor() { return color; }
QString getName() { return name; }
public slots:
void clicked();
signals:
void colorChosen(QColor);
protected:
QColor color;
QString name;
};
#endif

View File

@@ -1,114 +0,0 @@
/*
* 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 "Colors.h"
#include "MainWindow.h"
#include <QObject>
#include <QDir>
#include "Settings.h"
static Colors *ColorList; // Initialization moved to GCColor constructor to enable translation
GCColor::GCColor(MainWindow *main) : QObject(main)
{
static Colors ColorListInit[45] = {
{ tr("Plot Background"), "COLORPLOTBACKGROUND", Qt::white },
{ tr("Plot Thumbnail Background"), "COLORPLOTTHUMBNAIL", Qt::gray },
{ tr("Plot Title"), "COLORPLOTTITLE", Qt::black },
{ tr("Plot Selection Pen"), "COLORPLOTSELECT", Qt::blue },
{ tr("Plot TrackerPen"), "COLORPLOTTRACKER", Qt::blue },
{ tr("Plot Markers"), "COLORPLOTMARKER", Qt::black },
{ tr("Plot Grid"), "COLORGRID", Qt::black },
{ tr("Interval Highlighter"), "COLORINTERVALHIGHLIGHTER", Qt::blue },
{ tr("Heart Rate"), "COLORHEARTRATE", Qt::blue },
{ tr("Speed"), "COLORSPEED", Qt::green },
{ tr("Power"), "COLORPOWER", Qt::red },
{ tr("Critical Power"), "COLORCP", Qt::red },
{ tr("Cadence"), "COLORCADENCE", QColor(0,204,204) },
{ tr("Altitude"), "COLORALTITUTDE", QColor(124,91,31) },
{ tr("Altitude Shading"), "COLORALTITUDESHADE", QColor(124,91,31) },
{ tr("Wind Speed"), "COLORWINDSPEED", Qt::darkGreen },
{ tr("Torque"), "COLORTORQUE", Qt::magenta },
{ tr("Short Term Stress"), "COLORSTS", Qt::blue },
{ tr("Long Term Stress"), "COLORLTS", Qt::green },
{ tr("Stress Balance"), "COLORSB", Qt::black },
{ tr("Daily Stress"), "COLORDAILYSTRESS", Qt::red },
{ tr("Calendar Text"), "COLORCALENDARTEXT", Qt::black },
{ tr("Power Zone 1 Shading"), "COLORZONE1", QColor(255,0,255) },
{ tr("Power Zone 2 Shading"), "COLORZONE2", QColor(42,0,255) },
{ tr("Power Zone 3 Shading"), "COLORZONE3", QColor(0,170,255) },
{ tr("Power Zone 4 Shading"), "COLORZONE4", QColor(0,255,128) },
{ tr("Power Zone 5 Shading"), "COLORZONE5", QColor(85,255,0) },
{ tr("Power Zone 6 Shading"), "COLORZONE6", QColor(255,213,0) },
{ tr("Power Zone 7 Shading"), "COLORZONE7", QColor(255,0,0) },
{ tr("Power Zone 8 Shading"), "COLORZONE8", Qt::gray },
{ tr("Power Zone 9 Shading"), "COLORZONE9", Qt::gray },
{ tr("Power Zone 10 Shading"), "COLORZONE10", Qt::gray },
{ tr("Heartrate Zone 1 Shading"), "COLORHRZONE1", QColor(255,0,255) },
{ tr("Heartrate Zone 2 Shading"), "COLORHRZONE2", QColor(42,0,255) },
{ tr("Heartrate Zone 3 Shading"), "COLORHRZONE3", QColor(0,170,255) },
{ tr("Heartrate Zone 4 Shading"), "COLORHRZONE4", QColor(0,255,128) },
{ tr("Heartrate Zone 5 Shading"), "COLORHRZONE5", QColor(85,255,0) },
{ tr("Heartrate Zone 6 Shading"), "COLORHRZONE6", QColor(255,213,0) },
{ tr("Heartrate Zone 7 Shading"), "COLORHRZONE7", QColor(255,0,0) },
{ tr("Heartrate Zone 8 Shading"), "COLORHRZONE8", Qt::gray },
{ tr("Heartrate Zone 9 Shading"), "COLORHRZONE9", Qt::gray },
{ tr("Heartrate Zone 10 Shading"), "COLORHRZONE10", Qt::gray },
{ tr("Aerolab VE"), "COLORAEROVE", Qt::blue },
{ tr("Aerolab Elevation"), "COLORAEROEL", Qt::green },
{ "", "", QColor(0,0,0) },
};
ColorList = ColorListInit;
readConfig();
connect(main, SIGNAL(configChanged()), this, SLOT(readConfig()));
}
const Colors * GCColor::colorSet()
{
return ColorList;
}
QColor
GCColor::invert(QColor color)
{
return QColor(255-color.red(), 255-color.green(), 255-color.blue());
}
void
GCColor::readConfig()
{
boost::shared_ptr<QSettings> settings = GetApplicationSettings();
// read in config settings and populate the color table
for (unsigned int i=0; ColorList[i].name != ""; i++) {
QString colortext = settings->value(ColorList[i].setting, "").toString();
if (colortext != "") {
// color definitions are stored as "r:g:b"
QStringList rgb = colortext.split(":");
ColorList[i].color = QColor(rgb[0].toInt(),
rgb[1].toInt(),
rgb[2].toInt());
}
}
}
QColor
GCColor::getColor(int colornum)
{
return ColorList[colornum].color;
}

View File

@@ -1,97 +0,0 @@
/*
* 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
*/
#ifndef _GC_Colors_h
#define _GC_Colors_h 1
#include <QObject>
#include <QString>
#include <QColor>
class MainWindow;
struct Colors
{
QString name,
setting;
QColor color;
};
class GCColor : public QObject
{
Q_OBJECT
public:
GCColor(MainWindow*);
static QColor getColor(int);
static const Colors *colorSet();
static QColor invert(QColor);
public slots:
void readConfig();
};
// shorthand
#define GColor(x) GCColor::getColor(x)
#define CPLOTBACKGROUND 0
#define CPLOTTHUMBNAIL 1
#define CPLOTTITLE 2
#define CPLOTSELECT 3
#define CPLOTTRACKER 4
#define CPLOTMARKER 5
#define CPLOTGRID 6
#define CINTERVALHIGHLIGHTER 7
#define CHEARTRATE 8
#define CSPEED 9
#define CPOWER 10
#define CCP 11
#define CCADENCE 12
#define CALTITUDE 13
#define CALTITUDEBRUSH 14
#define CWINDSPEED 15
#define CTORQUE 16
#define CSTS 17
#define CLTS 18
#define CSB 19
#define CDAILYSTRESS 20
#define CCALENDARTEXT 21
#define CZONE1 22
#define CZONE2 23
#define CZONE3 24
#define CZONE4 25
#define CZONE5 26
#define CZONE6 27
#define CZONE7 28
#define CZONE8 29
#define CZONE9 30
#define CZONE10 31
#define CHZONE1 32
#define CHZONE2 33
#define CHZONE3 34
#define CHZONE4 35
#define CHZONE5 36
#define CHZONE6 37
#define CHZONE7 38
#define CHZONE8 39
#define CHZONE9 40
#define CHZONE10 41
#define CAEROVE 42
#define CAEROEL 43
#endif

View File

@@ -821,22 +821,15 @@ int Computrainer::openPort()
cfsetspeed(&deviceSettings, B2400);
// further attributes
deviceSettings.c_iflag &= ~(IGNBRK | BRKINT | ICRNL | INLCR | PARMRK | INPCK | ICANON | ISTRIP | IXON | IXOFF | IXANY);
deviceSettings.c_iflag |= IGNPAR;
deviceSettings.c_cflag &= (~CSIZE & ~CSTOPB);
deviceSettings.c_iflag= IGNPAR;
deviceSettings.c_oflag=0;
deviceSettings.c_cflag &= (~CSIZE & ~CSTOPB);
#if defined(Q_OS_MACX)
deviceSettings.c_cflag &= (~CCTS_OFLOW & ~CRTS_IFLOW); // no hardware flow control
deviceSettings.c_cflag |= (CS8 | CLOCAL | CREAD | HUPCL);
deviceSettings.c_cflag |= (CS8 | CREAD | HUPCL | CCTS_OFLOW | CRTS_IFLOW);
#else
deviceSettings.c_cflag &= (~CRTSCTS); // no hardware flow control
deviceSettings.c_cflag |= (CS8 | CLOCAL | CREAD | HUPCL);
deviceSettings.c_cflag |= (CS8 | CREAD | HUPCL | CRTSCTS);
#endif
deviceSettings.c_lflag=0;
deviceSettings.c_cc[VSTART] = 0x11;
deviceSettings.c_cc[VSTOP] = 0x13;
deviceSettings.c_cc[VEOF] = 0x20;
deviceSettings.c_cc[VMIN]=0;
deviceSettings.c_cc[VTIME]=0;
@@ -844,7 +837,6 @@ int Computrainer::openPort()
if(tcsetattr(devicePort, TCSANOW, &deviceSettings) == -1) return errno;
tcgetattr(devicePort, &deviceSettings);
tcflush(devicePort, TCIOFLUSH); // clear out the garbage
#else
// WINDOWS USES SET/GETCOMMSTATE AND READ/WRITEFILE
@@ -875,19 +867,12 @@ int Computrainer::openPort()
deviceSettings.fParity = NOPARITY;
deviceSettings.ByteSize = 8;
deviceSettings.StopBits = ONESTOPBIT;
deviceSettings.XonChar = 11;
deviceSettings.XoffChar = 13;
deviceSettings.EofChar = 0x0;
deviceSettings.ErrorChar = 0x0;
deviceSettings.EvtChar = 0x0;
deviceSettings.fBinary = true;
deviceSettings.fOutX = 0;
deviceSettings.fInX = 0;
deviceSettings.XonLim = 0;
deviceSettings.XoffLim = 0;
deviceSettings.fRtsControl = RTS_CONTROL_ENABLE;
deviceSettings.fDtrControl = DTR_CONTROL_ENABLE;
deviceSettings.fOutxCtsFlow = FALSE; //TRUE;
deviceSettings.fRtsControl = RTS_CONTROL_HANDSHAKE;
deviceSettings.fOutxCtsFlow = TRUE;
if (SetCommState(devicePort, &deviceSettings) == false) {

View File

@@ -73,16 +73,10 @@ RideFile *Computrainer3dpFileReader::openRideFile(QFile & file,
// looks like the first part is a header... ignore it.
is.skipRawData(4);
// the next 4 bytes are the ASCII characters 'perf'
// the next 4 bytes are the ASCII characters 'Perf'
char perfStr[5];
is.readRawData(perfStr, 4);
perfStr[4] = NULL;
if(strcmp(perfStr,"perf"))
{
errors << "File is encrypted.";
return NULL;
}
// not sure what the next 8 bytes are; skip them
is.skipRawData(0x8);
@@ -149,7 +143,7 @@ RideFile *Computrainer3dpFileReader::openRideFile(QFile & file,
// use that to offset distances that we report to GC so that they
// are zero-based (i.e., so that the first data point is at
// distance zero).
float firstKM = 0;
float firstKM;
bool gotFirstKM = false;
// computrainer doesn't have a fixed inter-sample-interval; GC
@@ -237,7 +231,7 @@ RideFile *Computrainer3dpFileReader::openRideFile(QFile & file,
// special case first data point
rideFile->appendPoint((double) ms/1000, (double) cad,
(double) hr, km, speed, 0.0, watts,
altitude, 0, 0, 0.0, 0);
altitude, 0, 0, 0);
}
// while loop since an interval in the .3dp file might
// span more than one CT_EMIT_MS interval
@@ -285,7 +279,6 @@ RideFile *Computrainer3dpFileReader::openRideFile(QFile & file,
interpol_alt,
0, // lon
0, // lat
0.0, // headwind
0);
// reset averaging sums

View File

@@ -20,7 +20,7 @@
#include "Computrainer.h"
#include "RealtimeData.h"
ComputrainerController::ComputrainerController(RealtimeWindow *parent, DeviceConfiguration *dc) : RealtimeController(parent, dc)
ComputrainerController::ComputrainerController(RealtimeWindow *parent, DeviceConfiguration *dc) : RealtimeController(parent)
{
myComputrainer = new Computrainer (parent, dc->portSpec);
}
@@ -81,8 +81,7 @@ ComputrainerController::getRealtimeData(RealtimeData &rtData)
msgBox.setText("Cannot Connect to Computrainer");
msgBox.setIcon(QMessageBox::Critical);
msgBox.exec();
parent->Stop(1);
return;
parent->Stop();
}
// get latest telemetry
myComputrainer->getTelemetry(Power, HeartRate, Cadence, Speed,
@@ -93,17 +92,9 @@ ComputrainerController::getRealtimeData(RealtimeData &rtData)
//
rtData.setWatts(Power);
rtData.setHr(HeartRate);
rtData.setCadence(Cadence);
rtData.setRPM(Cadence);
rtData.setSpeed(Speed);
// post processing, probably not used
// since its used to compute power for
// non-power devices, but we may add other
// calculations later that might apply
// means we could calculate power based
// upon speed even for CT!
processRealtimeData(rtData);
//
// BUTTONS
//
@@ -137,7 +128,7 @@ ComputrainerController::getRealtimeData(RealtimeData &rtData)
// if Buttons == 0 we just pressed stop!
if (Buttons&CT_RESET) {
parent->Stop(0);
parent->Stop();
}
// displaymode

View File

@@ -30,28 +30,27 @@ ConfigDialog::ConfigDialog(QDir _home, Zones *_zones, MainWindow *mainWindow) :
home = _home;
cyclistPage = new CyclistPage(mainWindow);
cyclistPage = new CyclistPage(zones);
contentsWidget = new QListWidget;
contentsWidget->setViewMode(QListView::IconMode);
contentsWidget->setIconSize(QSize(96, 84));
contentsWidget->setMovement(QListView::Static);
contentsWidget->setMinimumWidth(112);
contentsWidget->setMaximumWidth(112);
//contentsWidget->setMinimumHeight(200);
contentsWidget->setMinimumWidth(128);
contentsWidget->setMaximumWidth(128);
contentsWidget->setMinimumHeight(128);
contentsWidget->setSpacing(12);
contentsWidget->setUniformItemSizes(true);
configPage = new ConfigurationPage(mainWindow);
configPage = new ConfigurationPage();
intervalMetricsPage = new IntervalMetricsPage;
devicePage = new DevicePage(this);
pagesWidget = new QStackedWidget;
pagesWidget->addWidget(configPage);
pagesWidget->addWidget(cyclistPage);
pagesWidget->addWidget(intervalMetricsPage);
pagesWidget->addWidget(devicePage);
#ifdef GC_HAVE_LIBOAUTH
twitterPage = new TwitterPage(this);
pagesWidget->addWidget(twitterPage);
#endif
closeButton = new QPushButton(tr("Close"));
saveButton = new QPushButton(tr("Save"));
@@ -62,6 +61,10 @@ ConfigDialog::ConfigDialog(QDir _home, Zones *_zones, MainWindow *mainWindow) :
// connect(closeButton, SIGNAL(clicked()), this, SLOT(reject()));
// connect(saveButton, SIGNAL(clicked()), this, SLOT(accept()));
connect(closeButton, SIGNAL(clicked()), this, SLOT(accept()));
connect(cyclistPage->btnBack, SIGNAL(clicked()), this, SLOT(back_Clicked()));
connect(cyclistPage->btnForward, SIGNAL(clicked()), this, SLOT(forward_Clicked()));
connect(cyclistPage->btnDelete, SIGNAL(clicked()), this, SLOT(delete_Clicked()));
connect(cyclistPage->calendar, SIGNAL(selectionChanged()), this, SLOT(calendarDateChanged()));
// connect the pieces...
connect(devicePage->typeSelector, SIGNAL(currentIndexChanged(int)), this, SLOT(changedType(int)));
@@ -80,50 +83,41 @@ ConfigDialog::ConfigDialog(QDir _home, Zones *_zones, MainWindow *mainWindow) :
mainLayout = new QVBoxLayout;
mainLayout->addLayout(horizontalLayout);
//mainLayout->addStretch(1);
//mainLayout->addSpacing(12);
mainLayout->addStretch(1);
mainLayout->addSpacing(12);
mainLayout->addLayout(buttonsLayout);
setLayout(mainLayout);
// We go fixed width to ensure a consistent layout for
// tabs, sub-tabs and internal widgets and lists
#ifdef Q_OS_MACX
setWindowTitle(tr("Preferences"));
#else
setWindowTitle(tr("Options"));
#endif
setFixedSize(QSize(800, 600));
setWindowTitle(tr("Config Dialog"));
}
void ConfigDialog::createIcons()
{
QListWidgetItem *configButton = new QListWidgetItem(contentsWidget);
configButton->setIcon(QIcon(":/images/config.png"));
configButton->setText(tr("Settings"));
configButton->setText(tr("Configuration"));
configButton->setTextAlignment(Qt::AlignHCenter);
configButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
QListWidgetItem *cyclistButton = new QListWidgetItem(contentsWidget);
cyclistButton->setIcon(QIcon(":images/cyclist.png"));
cyclistButton->setText(tr("Athlete"));
cyclistButton->setText(tr("Cyclist Info"));
cyclistButton->setTextAlignment(Qt::AlignHCenter);
cyclistButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
QListWidgetItem *intervalMetricsButton = new QListWidgetItem(contentsWidget);
intervalMetricsButton->setIcon(QIcon(":images/imetrics.png"));
intervalMetricsButton->setText(tr("Interval Metrics"));
intervalMetricsButton->setTextAlignment(Qt::AlignHCenter);
intervalMetricsButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
QListWidgetItem *realtimeButton = new QListWidgetItem(contentsWidget);
realtimeButton->setIcon(QIcon(":images/arduino.png"));
realtimeButton->setText(tr("Devices"));
realtimeButton->setTextAlignment(Qt::AlignHCenter);
realtimeButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
#ifdef GC_HAVE_LIBOAUTH
QListWidgetItem *twitterButton = new QListWidgetItem(contentsWidget);
twitterButton->setIcon(QIcon(":images/twitter.png"));
twitterButton->setText(tr("Twitter"));
twitterButton->setTextAlignment(Qt::AlignHCenter);
twitterButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
#endif
connect(contentsWidget,
SIGNAL(currentItemChanged(QListWidgetItem *, QListWidgetItem *)),
this, SLOT(changePage(QListWidgetItem *, QListWidgetItem*)));
@@ -132,6 +126,10 @@ void ConfigDialog::createIcons()
}
void ConfigDialog::createNewRange()
{
}
void ConfigDialog::changePage(QListWidgetItem *current, QListWidgetItem *previous)
{
if (!current)
@@ -153,20 +151,6 @@ void ConfigDialog::save_Clicked()
settings->setValue(GC_LANG, "fr");
else if (configPage->langCombo->currentIndex()==2)
settings->setValue(GC_LANG, "ja");
else if (configPage->langCombo->currentIndex()==3)
settings->setValue(GC_LANG, "pt-br");
else if (configPage->langCombo->currentIndex()==4)
settings->setValue(GC_LANG, "it");
else if (configPage->langCombo->currentIndex()==5)
settings->setValue(GC_LANG, "de");
else if (configPage->langCombo->currentIndex()==6)
settings->setValue(GC_LANG, "ru");
else if (configPage->langCombo->currentIndex()==7)
settings->setValue(GC_LANG, "cs");
else if (configPage->langCombo->currentIndex()==8)
settings->setValue(GC_LANG, "es");
else if (configPage->langCombo->currentIndex()==9)
settings->setValue(GC_LANG, "pt");
if (configPage->unitCombo->currentIndex()==0)
settings->setValue(GC_UNIT, "Metric");
@@ -174,9 +158,6 @@ void ConfigDialog::save_Clicked()
settings->setValue(GC_UNIT, "Imperial");
settings->setValue(GC_ALLRIDES_ASCENDING, configPage->allRidesAscending->checkState());
settings->setValue(GC_GARMIN_SMARTRECORD, configPage->garminSmartRecord->checkState());
settings->setValue(GC_GARMIN_HWMARK, configPage->garminHWMarkedit->text());
settings->setValue(GC_MAP_INTERVAL, configPage->mapIntervaledit->text());
settings->setValue(GC_CRANKLENGTH, configPage->crankLengthCombo->currentText());
settings->setValue(GC_BIKESCOREDAYS, configPage->BSdaysEdit->text());
settings->setValue(GC_BIKESCOREMODE, configPage->bsModeCombo->currentText());
@@ -185,8 +166,6 @@ void ConfigDialog::save_Clicked()
settings->setValue(GC_INITIAL_LTS, cyclistPage->perfManStart->text());
settings->setValue(GC_STS_DAYS, cyclistPage->perfManSTSavg->text());
settings->setValue(GC_LTS_DAYS, cyclistPage->perfManLTSavg->text());
settings->setValue(GC_SB_TODAY, (int) cyclistPage->showSBToday->isChecked());
settings->setValue(GC_PM_DAYS, cyclistPage->perfManDays->text());
// set default stress names if not set:
settings->setValue(GC_STS_NAME, settings->value(GC_STS_NAME,tr("Short Term Stress")));
@@ -196,16 +175,34 @@ void ConfigDialog::save_Clicked()
settings->setValue(GC_SB_NAME, settings->value(GC_SB_NAME,tr("Stress Balance")));
settings->setValue(GC_SB_ACRONYM, settings->value(GC_SB_ACRONYM,tr("SB")));
// Save Cyclist page stuff
cyclistPage->saveClicked();
// save interval metrics and ride data pages
configPage->saveClicked();
// if the CP text entry reads invalid, there's nothing we can do
int cp = cyclistPage->getCP();
if (cp == 0) {
QMessageBox::warning(this, tr("Invalid CP"), "Please enter valid CP and try again.");
cyclistPage->setCPFocus();
return;
}
// if for some reason we have no zones yet, then create them
int range = cyclistPage->getCurrentRange();
// if this is new mode, or if no zone ranges are yet defined, set up the new range
if ((range == -1) || (cyclistPage->isNewMode()))
cyclistPage->setCurrentRange(range = zones->insertRangeAtDate(cyclistPage->selectedDate(), cp));
else
zones->setCP(range, cyclistPage->getText().toInt());
zones->setZonesFromCP(range);
// update the "new zone" checkbox to visible and unchecked
cyclistPage->checkboxNew->setChecked(Qt::Unchecked);
cyclistPage->checkboxNew->setEnabled(true);
zones->write(home);
intervalMetricsPage->saveClicked();
#ifdef GC_HAVE_LIBOAUTH
//Call Twitter Save Dialog to get Access Token
twitterPage->saveClicked();
#endif
// Save the device configuration...
DeviceConfigurations all;
all.writeConfig(devicePage->deviceListModel->Configuration);
@@ -213,9 +210,70 @@ void ConfigDialog::save_Clicked()
// Tell MainWindow we changed config, so it can emit the signal
// configChanged() to all its children
mainWindow->notifyConfigChanged();
}
// close
accept();
void ConfigDialog::moveCalendarToCurrentRange() {
int range = cyclistPage->getCurrentRange();
if (range < 0)
return;
QDate date;
// put the cursor at the beginning of the selected range if it's not the first
if (range > 0)
date = zones->getStartDate(cyclistPage->getCurrentRange());
// unless the range is the first range, in which case it goes at the end of that range
// use JulianDay to subtract one day from the end date, which is actually the first
// day of the following range
else
date = QDate::fromJulianDay(zones->getEndDate(cyclistPage->getCurrentRange()).toJulianDay() - 1);
cyclistPage->setSelectedDate(date);
}
void ConfigDialog::back_Clicked()
{
QDate date;
cyclistPage->setCurrentRange(cyclistPage->getCurrentRange() - 1);
moveCalendarToCurrentRange();
}
void ConfigDialog::forward_Clicked()
{
QDate date;
cyclistPage->setCurrentRange(cyclistPage->getCurrentRange() + 1);
moveCalendarToCurrentRange();
}
void ConfigDialog::delete_Clicked() {
int range = cyclistPage->getCurrentRange();
int num_ranges = zones->getRangeSize();
assert (num_ranges > 1);
QMessageBox msgBox;
msgBox.setText(
tr("Are you sure you want to delete the zone range\n"
"from %1 to %2?\n"
"(%3 range will extend to this date range):") .
arg(zones->getStartDateString(cyclistPage->getCurrentRange())) .
arg(zones->getEndDateString(cyclistPage->getCurrentRange())) .
arg((range > 0) ? "previous" : "next")
);
QPushButton *deleteButton = msgBox.addButton(tr("Delete"),QMessageBox::YesRole);
msgBox.setStandardButtons(QMessageBox::Cancel);
msgBox.setDefaultButton(QMessageBox::Cancel);
msgBox.setIcon(QMessageBox::Critical);
msgBox.exec();
if(msgBox.clickedButton() == deleteButton)
cyclistPage->setCurrentRange(zones->deleteRange(range));
zones->write(home);
}
void ConfigDialog::calendarDateChanged() {
int range = zones->whichRange(cyclistPage->selectedDate());
assert(range >= 0);
cyclistPage->setCurrentRange(range);
}
//
@@ -253,7 +311,6 @@ ConfigDialog::devaddClicked()
add.type = devicePage->typeSelector->itemData(devicePage->typeSelector->currentIndex()).toInt();
add.portSpec = devicePage->deviceSpecifier->displayText();
add.deviceProfile = devicePage->deviceProfile->displayText();
add.postProcess = devicePage->virtualPower->currentIndex();
// NOT IMPLEMENTED IN THIS RELEASE XXX
//add.isDefaultDownload = devicePage->isDefaultDownload->isChecked() ? true : false;

View File

@@ -21,6 +21,10 @@ class ConfigDialog : public QDialog
public slots:
void changePage(QListWidgetItem *current, QListWidgetItem *previous);
void save_Clicked();
void back_Clicked();
void forward_Clicked();
void delete_Clicked();
void calendarDateChanged();
// device config slots
void changedType(int);
@@ -38,7 +42,7 @@ class ConfigDialog : public QDialog
ConfigurationPage *configPage;
CyclistPage *cyclistPage;
DevicePage *devicePage;
TwitterPage *twitterPage;
IntervalMetricsPage *intervalMetricsPage;
QPushButton *saveButton;
QStackedWidget *pagesWidget;
QPushButton *closeButton;

View File

@@ -17,7 +17,6 @@
*/
#include "Zones.h"
#include "Colors.h"
#include "CpintPlot.h"
#include <assert.h>
#include <unistd.h>
@@ -49,6 +48,7 @@ CpintPlot::CpintPlot(QString p, const Zones *zones) :
assert(!USE_T0_IN_CP_MODEL); // doesn't work with energyMode=true
insertLegend(new QwtLegend(), QwtPlot::BottomLegend);
setCanvasBackground(Qt::white);
setAxisTitle(yLeft, tr("Average Power (watts)"));
setAxisTitle(xBottom, tr("Interval Length"));
setAxisScaleDraw(xBottom, new LogTimeScaleDraw);
@@ -57,18 +57,10 @@ CpintPlot::CpintPlot(QString p, const Zones *zones) :
grid = new QwtPlotGrid();
grid->enableX(false);
grid->attach(this);
configChanged(); // apply colors
}
void
CpintPlot::configChanged()
{
setCanvasBackground(GColor(CPLOTBACKGROUND));
QPen gridPen(GColor(CPLOTGRID));
QPen gridPen;
gridPen.setStyle(Qt::DotLine);
grid->setPen(gridPen);
grid->attach(this);
}
struct cpi_file_info {
@@ -125,7 +117,7 @@ update_cpi_file(const cpi_file_info *info, QProgressDialog *progress,
QStringList errors;
boost::scoped_ptr<RideFile> rideFile(
RideFileFactory::instance().openRideFile(file, errors));
if (!rideFile || rideFile->dataPoints().isEmpty())
if (! rideFile)
return;
cpint_data data;
data.rec_int_ms = (int) round(rideFile->recIntSecs() * 1000.0);
@@ -134,7 +126,6 @@ update_cpi_file(const cpi_file_info *info, QProgressDialog *progress,
if (secs > 0)
data.points.append(cpint_point(secs, (int) round(p->watts)));
}
if (!data.points.count()) return;
FILE *out = fopen(info->outname.toAscii().constData(), "w");
assert(out);
@@ -450,7 +441,7 @@ CpintPlot::plot_CP_curve(CpintPlot *thisPlot, // the plot we're currently di
CPCurve = new QwtPlotCurve(curve_title);
CPCurve->setRenderHint(QwtPlotItem::RenderAntialiased);
QPen pen(GColor(CCP));
QPen pen(Qt::red);
pen.setWidth(2.0);
pen.setStyle(Qt::DashLine);
CPCurve->setPen(pen);
@@ -549,7 +540,7 @@ CpintPlot::plot_allCurve(CpintPlot *thisPlot,
allZoneLabels.append(label_mark);
}
high = low;
high = low - 1;
++zone;
}
}
@@ -557,10 +548,10 @@ CpintPlot::plot_allCurve(CpintPlot *thisPlot,
else {
QwtPlotCurve *curve = new QwtPlotCurve(tr("maximal power"));
curve->setRenderHint(QwtPlotItem::RenderAntialiased);
QPen pen(GColor(CCP));
QPen pen(Qt::red);
pen.setWidth(2.0);
curve->setPen(pen);
QColor brush_color = GColor(CCP);
QColor brush_color = Qt::red;
brush_color.setAlpha(64);
curve->setBrush(brush_color); // brush fills below the line
if (energyMode_)
@@ -693,13 +684,6 @@ CpintPlot::calculate(RideItem *rideItem)
if (!needToScanRides) {
if (!CPCurve)
plot_CP_curve(this, cp, tau, t0);
else {
// make sure color reflects latest config
QPen pen(GColor(CCP));
pen.setWidth(2.0);
pen.setStyle(Qt::DashLine);
CPCurve->setPen(pen);
}
if (allCurves.empty()) {
int maxNonZero = 0;
for (int i = 0; i < bests.size(); ++i) {

View File

@@ -57,7 +57,6 @@ class CpintPlot : public QwtPlot
void calculate(RideItem *rideItem);
void plot_CP_curve(CpintPlot *plot, double cp, double tau, double t0n);
void plot_allCurve(CpintPlot *plot, int n_values, const double *power_values);
void configChanged();
protected:

View File

@@ -26,12 +26,11 @@
#include <QFile>
#include "Season.h"
#include "SeasonParser.h"
#include "Colors.h"
#include <QXmlInputSource>
#include <QXmlSimpleReader>
CriticalPowerWindow::CriticalPowerWindow(const QDir &home, MainWindow *parent) :
QWidget(parent), home(home), mainWindow(parent), currentRide(NULL)
QWidget(parent), home(home), mainWindow(parent), active(false)
{
QVBoxLayout *vlayout = new QVBoxLayout;
@@ -54,7 +53,7 @@ CriticalPowerWindow::CriticalPowerWindow(const QDir &home, MainWindow *parent) :
cpintAllValue->setFixedWidth(width);
cpintCPValue->setFixedWidth(width); // so lines up nicely
cpintTimeValue->setReadOnly(false);
cpintTimeValue->setReadOnly(true);
cpintTodayValue->setReadOnly(true);
cpintAllValue->setReadOnly(true);
cpintCPValue->setReadOnly(true);
@@ -90,12 +89,10 @@ CriticalPowerWindow::CriticalPowerWindow(const QDir &home, MainWindow *parent) :
QwtPicker::PointSelection,
QwtPicker::VLineRubberBand,
QwtPicker::AlwaysOff, cpintPlot->canvas());
picker->setRubberBandPen(GColor(CPLOTTRACKER));
picker->setRubberBandPen(QColor(Qt::blue));
connect(picker, SIGNAL(moved(const QPoint &)),
SLOT(pickerMoved(const QPoint &)));
connect(cpintTimeValue, SIGNAL(editingFinished()),
this, SLOT(cpintTimeValueEntered()));
connect(cpintSetCPButton, SIGNAL(clicked()),
this, SLOT(cpintSetCPButtonClicked()));
connect(cComboSeason, SIGNAL(currentIndexChanged(int)),
@@ -103,10 +100,6 @@ CriticalPowerWindow::CriticalPowerWindow(const QDir &home, MainWindow *parent) :
connect(yAxisCombo, SIGNAL(currentIndexChanged(int)),
this, SLOT(setEnergyMode(int)));
connect(mainWindow, SIGNAL(rideSelected()), this, SLOT(rideSelected()));
connect(mainWindow, SIGNAL(configChanged()), cpintPlot, SLOT(configChanged()));
// redraw on config change -- this seems the simplest approach
connect(mainWindow, SIGNAL(configChanged()), this, SLOT(rideSelected()));
}
void
@@ -122,17 +115,24 @@ CriticalPowerWindow::deleteCpiFile(QString rideFilename)
ride_filename_to_cpi_filename(rideFilename));
}
void
CriticalPowerWindow::setActive(bool new_value)
{
bool was_active = active;
active = new_value;
if (active && !was_active) {
currentRide = mainWindow->rideItem();
if (currentRide)
cpintPlot->calculate(currentRide);
}
}
void
CriticalPowerWindow::rideSelected()
{
if (mainWindow->activeTab() != this)
return;
currentRide = mainWindow->rideItem();
if (currentRide) {
if (active && currentRide) {
cpintPlot->calculate(currentRide);
// apply latest colors
picker->setRubberBandPen(GColor(CPLOTTRACKER));
cpintSetCPButton->setEnabled(cpintPlot->cp > 0);
}
}
@@ -185,16 +185,19 @@ curve_to_point(double x, const QwtPlotCurve *curve)
}
void
CriticalPowerWindow::updateCpint(double minutes)
CriticalPowerWindow::pickerMoved(const QPoint &pos)
{
double minutes = cpintPlot->invTransform(QwtPlot::xBottom, pos.x());
cpintTimeValue->setText(interval_to_str(60.0*minutes));
// current ride
{
unsigned watts = curve_to_point(minutes, cpintPlot->getThisCurve());
QString label;
if (watts > 0)
label = QString(cpintPlot->energyMode() ? "%1 kJ" : "%1 watts").arg(watts);
label = QString(cpintPlot->energyMode() ? "%1 kJ" : "%1 watts").arg(watts);
else
label = tr("no data");
label = tr("no data");
cpintTodayValue->setText(label);
}
@@ -203,9 +206,9 @@ CriticalPowerWindow::updateCpint(double minutes)
unsigned watts = curve_to_point(minutes, cpintPlot->getCPCurve());
QString label;
if (watts > 0)
label = QString(cpintPlot->energyMode() ? "%1 kJ" : "%1 watts").arg(watts);
label = QString(cpintPlot->energyMode() ? "%1 kJ" : "%1 watts").arg(watts);
else
label = tr("no data");
label = tr("no data");
cpintCPValue->setText(label);
}
@@ -214,7 +217,7 @@ CriticalPowerWindow::updateCpint(double minutes)
QString label;
int index = (int) ceil(minutes * 60);
if (cpintPlot->getBests().count() > index) {
QDate date = cpintPlot->getBestDates()[index];
QDate date = cpintPlot->getBestDates()[index];
unsigned watts = cpintPlot->getBests()[index];
if (cpintPlot->energyMode())
label = QString("%1 kJ (%2)").arg(watts * minutes * 60.0 / 1000.0, 0, 'f', 0);
@@ -222,28 +225,12 @@ CriticalPowerWindow::updateCpint(double minutes)
label = QString("%1 watts (%2)").arg(watts);
label = label.arg(date.isValid() ? date.toString(tr("MM/dd/yyyy")) : tr("no date"));
}
else {
label = tr("no data");
}
else
label = tr("no data");
cpintAllValue->setText(label);
}
}
void
CriticalPowerWindow::cpintTimeValueEntered()
{
double minutes = str_to_interval(cpintTimeValue->text()) / 60.0;
updateCpint(minutes);
}
void
CriticalPowerWindow::pickerMoved(const QPoint &pos)
{
double minutes = cpintPlot->invTransform(QwtPlot::xBottom, pos.x());
cpintTimeValue->setText(interval_to_str(60.0*minutes));
updateCpint(minutes);
}
void CriticalPowerWindow::addSeasons()
{
QFile seasonFile(home.absolutePath() + "/seasons.xml");
@@ -252,7 +239,10 @@ void CriticalPowerWindow::addSeasons()
SeasonParser( handler );
xmlReader.setContentHandler(&handler);
xmlReader.setErrorHandler(&handler);
xmlReader.parse( source );
bool ok = xmlReader.parse( source );
if(!ok)
qWarning("Failed to parse seasons.xml");
seasons = handler.getSeasons();
Season season;
season.setName(tr("All Seasons"));

View File

@@ -37,18 +37,16 @@ class CriticalPowerWindow : public QWidget
void newRideAdded();
void deleteCpiFile(QString filename);
void setActive(bool value);
protected slots:
void cpintTimeValueEntered();
void cpintSetCPButtonClicked();
void pickerMoved(const QPoint &pos);
void rideSelected();
void seasonSelected(int season);
void setEnergyMode(int index);
private:
void updateCpint(double minutes);
protected:
QDir home;
@@ -59,11 +57,12 @@ class CriticalPowerWindow : public QWidget
QLineEdit *cpintAllValue;
QLineEdit *cpintCPValue;
QComboBox *cComboSeason;
QPushButton *cpintSetCPButton;
QPushButton *cpintSetCPButton;
QwtPlotPicker *picker;
void addSeasons();
QList<Season> seasons;
RideItem *currentRide;
bool active;
};
#endif // _GC_CriticalPowerWindow_h

View File

@@ -46,8 +46,6 @@ RideFile *CsvFileReader::openRideFile(QFile &file, QStringList &errors) const
*/
QRegExp ergomoCSV("(ZEIT|STRECKE)", Qt::CaseInsensitive);
bool ergomo = false;
QChar ergomo_separator;
int unitsHeader = 1;
int total_pause = 0;
int currentInterval = 0;
@@ -77,8 +75,6 @@ RideFile *CsvFileReader::openRideFile(QFile &file, QStringList &errors) const
QTextStream is(&file);
RideFile *rideFile = new RideFile();
int iBikeInterval = 0;
bool dfpmExists = false;
int iBikeVersion = 0;
while (!is.atEnd()) {
// the readLine() method doesn't handle old Macintosh CR line endings
// this workaround will load the the entire file if it has CR endings
@@ -99,14 +95,6 @@ RideFile *CsvFileReader::openRideFile(QFile &file, QStringList &errors) const
ergomo = true;
rideFile->setDeviceType("Ergomo CSV");
unitsHeader = 2;
QStringList headers = line.split(';');
if (headers.size()>1)
ergomo_separator = ';';
else
ergomo_separator = ',';
++lineno;
continue;
}
@@ -115,7 +103,6 @@ RideFile *CsvFileReader::openRideFile(QFile &file, QStringList &errors) const
iBike = true;
rideFile->setDeviceType("iBike CSV");
unitsHeader = 5;
iBikeVersion = line.section( ',', 1, 1 ).toInt();
++lineno;
continue;
}
@@ -150,9 +137,8 @@ RideFile *CsvFileReader::openRideFile(QFile &file, QStringList &errors) const
}
}
else if (lineno > unitsHeader) {
double minutes,nm,kph,watts,km,cad,alt,hr,dfpm;
double minutes,nm,kph,watts,km,cad,alt,hr;
double lat = 0.0, lon = 0.0;
double headwind = 0.0;
int interval;
int pause;
if (!ergomo && !iBike) {
@@ -176,21 +162,10 @@ RideFile *CsvFileReader::openRideFile(QFile &file, QStringList &errors) const
// can't find time as a column.
// will we have to extrapolate based on the recording interval?
// reading recording interval from config data in ibike csv file
//
// For iBike software version 11 or higher:
// use "power" field until a the "dfpm" field becomes non-zero.
minutes = (recInterval * lineno - unitsHeader)/60.0;
nm = NULL; //no torque
kph = line.section(',', 0, 0).toDouble();
dfpm = line.section( ',', 11, 11).toDouble();
if( iBikeVersion >= 11 && ( dfpm > 0.0 || dfpmExists ) ) {
dfpmExists = true;
watts = dfpm;
headwind = line.section(',', 1, 1).toDouble();
}
else {
watts = line.section(',', 2, 2).toDouble();
}
watts = line.section(',', 2, 2).toDouble();
km = line.section(',', 3, 3).toDouble();
cad = line.section(',', 4, 4).toDouble();
hr = line.section(',', 5, 5).toDouble();
@@ -206,29 +181,24 @@ RideFile *CsvFileReader::openRideFile(QFile &file, QStringList &errors) const
km *= KM_PER_MILE;
kph *= KM_PER_MILE;
alt *= METERS_PER_FOOT;
headwind *= KM_PER_MILE;
}
}
else {
// for ergomo formatted CSV files
minutes = line.section(ergomo_separator, 0, 0).toDouble() + total_pause;
QString km_string = line.section(ergomo_separator, 1, 1);
km_string.replace(",",".");
km = km_string.toDouble();
watts = line.section(ergomo_separator, 2, 2).toDouble();
cad = line.section(ergomo_separator, 3, 3).toDouble();
QString kph_string = line.section(ergomo_separator, 4, 4);
kph_string.replace(",",".");
kph = kph_string.toDouble();
hr = line.section(ergomo_separator, 5, 5).toDouble();
alt = line.section(ergomo_separator, 6, 6).toDouble();
minutes = line.section(',', 0, 0).toDouble() + total_pause;
km = line.section(',', 1, 1).toDouble();
watts = line.section(',', 2, 2).toDouble();
cad = line.section(',', 3, 3).toDouble();
kph = line.section(',', 4, 4).toDouble();
hr = line.section(',', 5, 5).toDouble();
alt = line.section(',', 6, 6).toDouble();
interval = line.section(',', 8, 8).toInt();
if (interval != prevInterval) {
prevInterval = interval;
if (interval != 0) currentInterval++;
}
if (interval != 0) interval = currentInterval;
pause = line.section(ergomo_separator, 9, 9).toInt();
pause = line.section(',', 9, 9).toInt();
total_pause += pause;
nm = NULL; // torque is not provided in the Ergomo file
@@ -250,7 +220,7 @@ RideFile *CsvFileReader::openRideFile(QFile &file, QStringList &errors) const
watts = 0;
rideFile->appendPoint(minutes * 60.0, cad, hr, km,
kph, nm, watts, alt, lon, lat, headwind, interval);
kph, nm, watts, alt, lat, lon, interval);
}
++lineno;
}
@@ -284,15 +254,10 @@ RideFile *CsvFileReader::openRideFile(QFile &file, QStringList &errors) const
QRegExp rideTime("^.*/(\\d\\d\\d\\d)_(\\d\\d)_(\\d\\d)_"
"(\\d\\d)_(\\d\\d)_(\\d\\d)\\.csv$");
rideTime.setCaseSensitivity(Qt::CaseInsensitive);
if (startTime != QDateTime()) {
// Start time was already set above?
rideFile->setStartTime(startTime);
} else if (rideTime.indexIn(file.fileName()) >= 0) {
// It matches the GC naming convention?
}
else if (rideTime.indexIn(file.fileName()) >= 0) {
QDateTime datetime(QDate(rideTime.cap(1).toInt(),
rideTime.cap(2).toInt(),
rideTime.cap(3).toInt()),
@@ -300,9 +265,7 @@ RideFile *CsvFileReader::openRideFile(QFile &file, QStringList &errors) const
rideTime.cap(5).toInt(),
rideTime.cap(6).toInt()));
rideFile->setStartTime(datetime);
} else {
// Could be yyyyddmm_hhmmss_NAME.csv (case insensitive)
rideTime.setPattern("(\\d\\d\\d\\d)(\\d\\d)(\\d\\d)_(\\d\\d)(\\d\\d)(\\d\\d)[^\\.]*\\.csv$");
if (rideTime.indexIn(file.fileName()) >= 0) {
@@ -314,39 +277,9 @@ RideFile *CsvFileReader::openRideFile(QFile &file, QStringList &errors) const
rideTime.cap(6).toInt()));
rideFile->setStartTime(datetime);
} else {
// is it in poweragent format "name yyyy-mm-dd hh-mm-ss.csv"
rideTime.setPattern("(\\d\\d\\d\\d)-(\\d\\d)-(\\d\\d) (\\d\\d)-(\\d\\d)-(\\d\\d)\\.csv$");
if (rideTime.indexIn(file.fileName()) >=0) {
QDateTime datetime(QDate(rideTime.cap(1).toInt(),
rideTime.cap(2).toInt(),
rideTime.cap(3).toInt()),
QTime(rideTime.cap(4).toInt(),
rideTime.cap(5).toInt(),
rideTime.cap(6).toInt()));
rideFile->setStartTime(datetime);
} else {
// NO DICE
// XXX Note: qWarning("Failed to set start time");
// console messages are no use, so commented out
// this problem will ONLY occur during the import
// process which traps these and corrects them
// so no need to do anything here
}
qWarning("Failed to set start time");
}
}
// did we actually read any samples?
if (rideFile->dataPoints().count() > 0) {
return rideFile;
} else {
errors << "No samples present.";
delete rideFile;
return NULL;
}
return rideFile;
}

View File

@@ -28,296 +28,147 @@
#include <assert.h>
#include <math.h>
#include <QtXml/QtXml>
#include <QFile>
#include <QFileInfo>
#include "SummaryMetrics.h"
#include "RideMetadata.h"
#include "SpecialFields.h"
#include <boost/scoped_array.hpp>
#include <boost/crc.hpp>
// DB Schema Version - YOU MUST UPDATE THIS IF THE SCHEMA VERSION CHANGES!!!
// Schema version will change if a) the default metadata.xml is updated
// or b) new metrics are added / old changed
static int DBSchemaVersion = 17;
DBAccess::DBAccess(MainWindow* main, QDir home) : main(main), home(home)
DBAccess::DBAccess(QDir home)
{
initDatabase(home);
}
void DBAccess::closeConnection()
{
dbconn.close();
db.close();
}
DBAccess::~DBAccess()
{
closeConnection();
}
void
DBAccess::initDatabase(QDir home)
QSqlDatabase DBAccess::initDatabase(QDir home)
{
if(dbconn.isOpen()) return;
QString cyclist = QFileInfo(home.path()).baseName();
sessionid = QString("%1%2").arg(cyclist).arg(main->session++);
if (main->session == 1) {
// first
main->db = QSqlDatabase::addDatabase("QSQLITE", sessionid);
main->db.setDatabaseName(home.absolutePath() + "/metricDB");
//dbconn = db.database(QString("GC"));
dbconn = main->db.database(sessionid);
} else {
// clone the first one!
dbconn = QSqlDatabase::cloneDatabase(main->db, sessionid);
dbconn.open();
}
if (!dbconn.isOpen()) {
if(db.isOpen())
return db;
db = QSqlDatabase::addDatabase("QSQLITE");
db.setDatabaseName(home.absolutePath() + "/metricDB");
if (!db.open()) {
QMessageBox::critical(0, qApp->translate("DBAccess","Cannot open database"),
qApp->translate("DBAccess","Unable to establish a database connection.\n"
"This feature requires SQLite support. Please read "
"This example needs SQLite support. Please read "
"the Qt SQL driver documentation for information how "
"to build it.\n\n"
"Click Cancel to exit."), QMessageBox::Cancel);
} else {
// create database - does nothing if its already there
createDatabase();
return db;
}
return db;
}
bool DBAccess::createMetricsTable()
{
SpecialFields sp;
QSqlQuery query(dbconn);
bool rc;
bool createTables = true;
// does the table exist?
rc = query.exec("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name;");
if (rc) {
while (query.next()) {
QString table = query.value(0).toString();
if (table == "metrics") {
createTables = false;
break;
}
}
}
// we need to create it!
if (rc && createTables) {
QString createMetricTable = "create table metrics (filename varchar primary key,"
"timestamp integer,"
"ride_date date,"
"fingerprint integer";
// Add columns for all the metric factory metrics
const RideMetricFactory &factory = RideMetricFactory::instance();
for (int i=0; i<factory.metricCount(); i++)
createMetricTable += QString(", X%1 double").arg(factory.metricName(i));
// And all the metadata metrics
foreach(FieldDefinition field, main->rideMetadata()->getFields()) {
if (!sp.isMetric(field.name) && (field.type == 3 || field.type == 4)) {
createMetricTable += QString(", Z%1 double").arg(sp.makeTechName(field.name));
}
}
createMetricTable += " )";
rc = query.exec(createMetricTable);
if (!rc) {
qDebug()<<"create table failed!" << query.lastError();
}
}
QSqlQuery query;
bool rc = query.exec("create table metrics (id integer primary key autoincrement, "
"filename varchar,"
"ride_date date,"
"ride_time double, "
"average_cad double,"
"workout_time double, "
"total_distance double,"
"x_power double,"
"average_speed double,"
"total_work double,"
"average_power double,"
"average_hr double,"
"relative_intensity double,"
"bike_score double)");
if(!rc)
qDebug() << query.lastError();
return rc;
}
bool DBAccess::dropMetricTable()
{
QSqlQuery query("DROP TABLE metrics", dbconn);
return query.exec();
}
bool DBAccess::createDatabase()
{
// check schema version and if missing recreate database
checkDBVersion();
// at present only one table!
bool rc = createMetricsTable();
if(!rc) return rc;
bool rc = false;
rc = createMetricsTable();
if(!rc)
return rc;
rc = createIndex();
if(!rc)
return rc;
// other tables here
return true;
}
static int
computeFileCRC(QString filename)
bool DBAccess::createIndex()
{
QFile file(filename);
QFileInfo fileinfo(file);
// open file
if (!file.open(QFile::ReadOnly)) return 0;
// allocate space
boost::scoped_array<char> data(new char[file.size()]);
// read entire file into memory
QDataStream *rawstream(new QDataStream(&file));
rawstream->readRawData(&data[0], file.size());
file.close();
// calculate the CRC
boost::crc_optimal<16, 0x1021, 0xFFFF, 0, false, false> CRC;
CRC.process_bytes(&data[0], file.size());
return CRC.checksum();
}
void DBAccess::checkDBVersion()
{
int currentversion = 0;
int metadatacrc; // crc for metadata.xml when last refreshed
int metadatacrcnow; // current value for metadata.xml crc
int creationdate;
// get a CRC for metadata.xml
QString metadataXML = QString(home.absolutePath()) + "/metadata.xml";
metadatacrcnow = computeFileCRC(metadataXML);
// can we get a version number?
QSqlQuery query("SELECT schema_version, creation_date, metadata_crc from version;", dbconn);
bool rc = query.exec();
while (rc && query.next()) {
currentversion = query.value(0).toInt();
creationdate = query.value(1).toInt();
metadatacrc = query.value(2).toInt();
}
// if its not up-to-date
if (!rc || currentversion != DBSchemaVersion || metadatacrc != metadatacrcnow) {
// drop tables
QSqlQuery dropV("DROP TABLE version", dbconn);
dropV.exec();
QSqlQuery dropM("DROP TABLE metrics", dbconn);
dropM.exec();
// recreate version table and add one entry
QSqlQuery version("CREATE TABLE version ( schema_version integer primary key, creation_date date, metadata_crc integer );", dbconn);
version.exec();
// insert current version number
QDateTime timestamp = QDateTime::currentDateTime();
QSqlQuery insert("INSERT INTO version ( schema_version, creation_date, metadata_crc ) values (?,?,?)", dbconn);
insert.addBindValue(DBSchemaVersion);
insert.addBindValue(timestamp.toTime_t());
insert.addBindValue(metadatacrcnow);
insert.exec();
createMetricsTable();
}
}
/*----------------------------------------------------------------------
* CRUD routines for Metrics table
*----------------------------------------------------------------------*/
bool DBAccess::importRide(SummaryMetrics *summaryMetrics, RideFile *ride, unsigned long fingerprint, bool modify)
{
SpecialFields sp;
QSqlQuery query(dbconn);
QDateTime timestamp = QDateTime::currentDateTime();
if (modify) {
// zap the current row
query.prepare("DELETE FROM metrics WHERE filename = ?;");
query.addBindValue(summaryMetrics->getFileName());
query.exec();
}
// construct an insert statement
QString insertStatement = "insert into metrics ( filename, timestamp, ride_date, fingerprint ";
const RideMetricFactory &factory = RideMetricFactory::instance();
for (int i=0; i<factory.metricCount(); i++)
insertStatement += QString(", X%1 ").arg(factory.metricName(i));
// And all the metadata metrics
foreach(FieldDefinition field, main->rideMetadata()->getFields()) {
if (!sp.isMetric(field.name) && (field.type == 3 || field.type == 4)) {
insertStatement += QString(", Z%1 ").arg(sp.makeTechName(field.name));
}
}
insertStatement += " ) values (?,?,?,?"; // filename, timestamp, ride_date
for (int i=0; i<factory.metricCount(); i++)
insertStatement += ",?";
foreach(FieldDefinition field, main->rideMetadata()->getFields()) {
if (!sp.isMetric(field.name) && (field.type == 3 || field.type == 4)) {
insertStatement += ",?";
}
}
insertStatement += ")";
query.prepare(insertStatement);
// filename, timestamp, ride date
query.addBindValue(summaryMetrics->getFileName());
query.addBindValue(timestamp.toTime_t());
query.addBindValue(summaryMetrics->getRideDate());
query.addBindValue((int)fingerprint);
// values
for (int i=0; i<factory.metricCount(); i++) {
query.addBindValue(summaryMetrics->getForSymbol(factory.metricName(i)));
}
// And all the metadata metrics
foreach(FieldDefinition field, main->rideMetadata()->getFields()) {
if (!sp.isMetric(field.name) && (field.type == 3 || field.type == 4)) {
query.addBindValue(ride->getTag(field.name, "0.0").toDouble());
} else if (!sp.isMetric(field.name)) {
if (field.name == "Recording Interval") // XXX Special - need a better way...
query.addBindValue(ride->recIntSecs());
}
}
// go do it!
bool rc = query.exec();
if(!rc)
QSqlQuery query;
query.prepare("create INDEX IDX_FILENAME on metrics(filename)");
bool rc = query.exec();
if(!rc)
qDebug() << query.lastError();
return rc;
}
bool
DBAccess::deleteRide(QString name)
bool DBAccess::importRide(SummaryMetrics *summaryMetrics )
{
QSqlQuery query(dbconn);
QSqlQuery query;
query.prepare("insert into metrics (filename, ride_date, ride_time, average_cad, workout_time, total_distance,"
"x_power, average_speed, total_work, average_power, average_hr,"
"relative_intensity, bike_score) values (?,?,?,?,?,?,?,?,?,?,?,?,?)");
query.addBindValue(summaryMetrics->getFileName());
query.addBindValue(summaryMetrics->getRideDate());
query.addBindValue(summaryMetrics->getRideTime());
query.addBindValue(summaryMetrics->getCadence());
query.addBindValue(summaryMetrics->getWorkoutTime());
query.addBindValue(summaryMetrics->getDistance());
query.addBindValue(summaryMetrics->getXPower());
query.addBindValue(summaryMetrics->getSpeed());
query.addBindValue(summaryMetrics->getTotalWork());
query.addBindValue(summaryMetrics->getWatts());
query.addBindValue(summaryMetrics->getHeartRate());
query.addBindValue(summaryMetrics->getRelativeIntensity());
query.addBindValue(summaryMetrics->getBikeScore());
bool rc = query.exec();
if(!rc)
{
qDebug() << query.lastError();
}
return rc;
}
query.prepare("DELETE FROM metrics WHERE filename = ?;");
query.addBindValue(name);
return query.exec();
QStringList DBAccess::getAllFileNames()
{
QSqlQuery query("SELECT filename from metrics");
QStringList fileList;
while(query.next())
{
QString filename = query.value(0).toString();
fileList << filename;
}
return fileList;
}
QList<QDateTime> DBAccess::getAllDates()
{
QSqlQuery query("SELECT ride_date from metrics ORDER BY ride_date;", dbconn);
QSqlQuery query("SELECT ride_date from metrics");
QList<QDateTime> dates;
query.exec();
while(query.next())
{
QDateTime date = query.value(0).toDateTime();
@@ -328,52 +179,48 @@ QList<QDateTime> DBAccess::getAllDates()
QList<SummaryMetrics> DBAccess::getAllMetricsFor(QDateTime start, QDateTime end)
{
SpecialFields sp;
QList<SummaryMetrics> metrics;
// null date range fetches all, but not currently used by application code
// since it relies too heavily on the results of the QDateTime constructor
if (start == QDateTime()) start = QDateTime::currentDateTime().addYears(-10);
if (end == QDateTime()) end = QDateTime::currentDateTime().addYears(+10);
// construct the select statement
QString selectStatement = "SELECT filename, ride_date";
const RideMetricFactory &factory = RideMetricFactory::instance();
for (int i=0; i<factory.metricCount(); i++)
selectStatement += QString(", X%1 ").arg(factory.metricName(i));
foreach(FieldDefinition field, main->rideMetadata()->getFields()) {
if (!sp.isMetric(field.name) && (field.type == 3 || field.type == 4)) {
selectStatement += QString(", Z%1 ").arg(sp.makeTechName(field.name));
}
}
selectStatement += " FROM metrics where DATE(ride_date) >=DATE(:start) AND DATE(ride_date) <=DATE(:end) "
" ORDER BY ride_date;";
// execute the select statement
QSqlQuery query(selectStatement, dbconn);
query.bindValue(":start", start.date());
query.bindValue(":end", end.date());
query.exec();
QSqlQuery query("SELECT filename, ride_date, ride_time, average_cad, workout_time, total_distance,"
"x_power, average_speed, total_work, average_power, average_hr,"
"relative_intensity, bike_scoreFROM metrics WHERE ride_date >=:start AND ride_date <=:end");
query.bindValue(":start", start);
query.bindValue(":end", end);
while(query.next())
{
SummaryMetrics summaryMetrics;
// filename and date
summaryMetrics.setFileName(query.value(0).toString());
summaryMetrics.setRideDate(query.value(1).toDateTime());
// the values
int i=0;
for (; i<factory.metricCount(); i++)
summaryMetrics.setForSymbol(factory.metricName(i), query.value(i+2).toDouble());
foreach(FieldDefinition field, main->rideMetadata()->getFields()) {
if (!sp.isMetric(field.name) && (field.type == 3 || field.type == 4)) {
QString underscored = field.name;
summaryMetrics.setForSymbol(underscored.replace(" ","_"), query.value(i+2).toDouble());
i++;
}
}
summaryMetrics.setRideTime(query.value(2).toDouble());
summaryMetrics.setCadence(query.value(3).toDouble());
summaryMetrics.setWorkoutTime(query.value(4).toDouble());
summaryMetrics.setDistance(query.value(5).toDouble());
summaryMetrics.setXPower(query.value(6).toDouble());
summaryMetrics.setSpeed(query.value(7).toDouble());
summaryMetrics.setTotalWork(query.value(8).toDouble());
summaryMetrics.setWatts(query.value(9).toDouble());
summaryMetrics.setHeartRate(query.value(10).toDouble());
summaryMetrics.setRelativeIntensity(query.value(11).toDouble());
summaryMetrics.setBikeScore(query.value(12).toDouble());
metrics << summaryMetrics;
}
return metrics;
}
bool DBAccess::dropMetricTable()
{
QStringList tableList = db.tables(QSql::Tables);
if(!tableList.contains("metrics"))
return true;
QSqlQuery query("DROP TABLE metrics");
return query.exec();
}

View File

@@ -25,9 +25,7 @@
#include <QHash>
#include <QtSql>
#include "SummaryMetrics.h"
#include "MainWindow.h"
#include "Season.h"
#include "RideFile.h"
class RideFile;
class Zones;
@@ -36,41 +34,23 @@ class DBAccess
{
public:
// get connection name
QSqlDatabase connection() { return dbconn; }
// check the db structure is up to date
void checkDBVersion();
// create and drop connections
DBAccess(MainWindow *main, QDir home);
~DBAccess();
// Create/Delete Records
bool importRide(SummaryMetrics *summaryMetrics, RideFile *ride, unsigned long, bool);
bool deleteRide(QString);
// Query Records
DBAccess(QDir home);
typedef QHash<QString,RideMetric*> MetricMap;
void importAllRides(QDir path, Zones *zones);
bool importRide(SummaryMetrics *summaryMetrics);
bool createDatabase();
QStringList getAllFileNames();
void closeConnection();
QList<QDateTime> getAllDates();
QList<SummaryMetrics> getAllMetricsFor(QDateTime start, QDateTime end);
bool createMetricsTable();
QList<Season> getAllSeasons();
bool dropMetricTable();
private:
MainWindow *main;
QDir home;
QSqlDatabase dbconn;
QString sessionid;
typedef QHash<QString,RideMetric*> MetricMap;
bool createDatabase();
void closeConnection();
bool createMetricsTable();
bool dropMetricTable();
QSqlDatabase db;
bool createIndex();
void initDatabase(QDir home);
QSqlDatabase initDatabase(QDir home);
};
#endif
#endif

View File

@@ -18,9 +18,7 @@
#include "RideMetric.h"
#include "Zones.h"
#include <QObject>
#include <math.h>
#include <QApplication>
// The idea: Fit a curve to the points system in Table 2.2 of "Daniel's Running
// Formula", Second Edition, assume that power at VO2Max is 1.2 * FTP, further
@@ -32,34 +30,38 @@
class DanielsPoints : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(DanielsPoints)
static const double K;
double score;
void inc(double secs, double watts, double cp) {
void count(double secs, double watts, double cp) {
score += K * secs * pow(watts / cp, 4);
}
public:
static const double K;
DanielsPoints() : score(0.0)
{
setSymbol("daniels_points");
#ifdef ENABLE_METRICS_TRANSLATION
setInternalName("Daniels Points");
}
void initialize() {
#endif
setName(tr("Daniels Points"));
setMetricUnits("");
setImperialUnits("");
setType(RideMetric::Total);
}
DanielsPoints() : score(0.0) {}
QString symbol() const { return "daniels_points"; }
QString name() const { return QObject::tr("Daniels Points"); }
QString units(bool) const { return ""; }
int precision() const { return 0; }
double value(bool) const { return score; }
void compute(const RideFile *ride, const Zones *zones,
int zoneRange, const HrZones *, int, const QHash<QString,RideMetric*> &) {
if (!zones || zoneRange < 0) {
setValue(0);
int zoneRange, const QHash<QString,RideMetric*> &) {
if (!zones || zoneRange < 0)
return;
if (ride->deviceType() == QString("Manual CSV")) {
// Manual entry: use BS from dataPoints with a scaling factor
// that works about right for long, steady rides.
double scaling_factor = 0.55;
if (ride->metricOverrides.contains("skiba_bike_score")) {
const QMap<QString,QString> bsm =
ride->metricOverrides.value("skiba_bike_score");
if (bsm.contains("value")) {
double bs = bsm.value("value").toDouble();
score = bs * scaling_factor;
}
}
return;
}
@@ -82,77 +84,34 @@ class DanielsPoints : public RideMetric {
&& (point->secs > lastSecs + secsDelta + EPSILON)) {
weighted *= attenuation;
lastSecs += secsDelta;
inc(secsDelta, weighted, cp);
count(secsDelta, weighted, cp);
}
weighted *= attenuation;
weighted += sampleWeight * point->watts;
lastSecs = point->secs;
inc(secsDelta, weighted, cp);
count(secsDelta, weighted, cp);
}
while (weighted > NEGLIGIBLE) {
weighted *= attenuation;
lastSecs += secsDelta;
inc(secsDelta, weighted, cp);
count(secsDelta, weighted, cp);
}
setValue(score);
}
void override(const QMap<QString,QString> &map) {
if (map.contains("value"))
score = map.value("value").toDouble();
}
void aggregateWith(const RideMetric &other) { score += other.value(true); }
RideMetric *clone() const { return new DanielsPoints(*this); }
};
// Choose K such that 1 hour at FTP yields a score of 100.
const double DanielsPoints::K = 100.0 / 3600.0;
class DanielsEquivalentPower : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(DanielsEquivalentPower)
double watts;
public:
DanielsEquivalentPower() : watts(0.0)
{
setSymbol("daniels_equivalent_power");
#ifdef ENABLE_METRICS_TRANSLATION
setInternalName("Daniels EqP");
}
void initialize() {
#endif
setName(tr("Daniels EqP"));
setMetricUnits(tr("watts"));
setImperialUnits(tr("watts"));
setType(RideMetric::Average);
}
void compute(const RideFile *, const Zones *zones, int zoneRange, const HrZones *, int,
const QHash<QString,RideMetric*> &deps)
{
if (!zones || zoneRange < 0) {
setValue(0);
return;
}
double cp = zones->getCP(zoneRange);
assert(deps.contains("daniels_points"));
assert(deps.contains("time_riding"));
const RideMetric *danielsPoints = deps.value("daniels_points");
const RideMetric *timeRiding = deps.value("time_riding");
assert(danielsPoints);
assert(timeRiding);
double score = danielsPoints->value(true);
double secs = timeRiding->value(true);
watts = secs == 0.0 ? 0.0 : cp * pow(score / DanielsPoints::K / secs, 0.25);
setValue(watts);
}
RideMetric *clone() const { return new DanielsEquivalentPower(*this); }
};
static bool added() {
RideMetricFactory::instance().addMetric(DanielsPoints());
QVector<QString> deps;
deps.append("time_riding");
deps.append("daniels_points");
RideMetricFactory::instance().addMetric(DanielsEquivalentPower(), &deps);
return true;
}
static bool added_ = added();

View File

@@ -1,121 +0,0 @@
/*
* 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 "DataProcessor.h"
#include "MainWindow.h"
#include "AllPlot.h"
#include "Settings.h"
#include "Units.h"
#include <assert.h>
DataProcessorFactory *DataProcessorFactory::instance_;
DataProcessorFactory &DataProcessorFactory::instance()
{
if (!instance_) instance_ = new DataProcessorFactory();
return *instance_;
}
bool
DataProcessorFactory::registerProcessor(QString name, DataProcessor *processor)
{
assert(!processors.contains(name));
processors.insert(name, processor);
return true;
}
bool
DataProcessorFactory::autoProcess(RideFile *ride)
{
boost::shared_ptr<QSettings> settings = GetApplicationSettings();
bool changed = false;
// run through the processors and execute them!
QMapIterator<QString, DataProcessor*> i(processors);
i.toFront();
while (i.hasNext()) {
i.next();
QString configsetting = QString("dp/%1/apply").arg(i.key());
if (settings->value(configsetting, "Manual").toString() == "Auto")
i.value()->postProcess(ride);
}
return changed;
}
ManualDataProcessorDialog::ManualDataProcessorDialog(MainWindow *main, QString name, RideItem *ride) : main(main), ride(ride)
{
setAttribute(Qt::WA_DeleteOnClose);
setWindowTitle(name);
QVBoxLayout *mainLayout = new QVBoxLayout(this);
// find our processor
const DataProcessorFactory &factory = DataProcessorFactory::instance();
QMap<QString, DataProcessor*> processors = factory.getProcessors();
processor = processors.value(name, NULL);
if (processor == NULL) reject();
// Change window title to Localized Name
setWindowTitle(processor->name());
QFont font;
font.setWeight(QFont::Black);
QLabel *configLabel = new QLabel(tr("Settings"), this);
configLabel->setFont(font);
QLabel *explainLabel = new QLabel(tr("Description"), this);
explainLabel->setFont(font);
config = processor->processorConfig(this);
config->readConfig();
explain = new QTextEdit(this);
explain->setText(config->explain());
explain->setReadOnly(true);
mainLayout->addWidget(configLabel);
mainLayout->addWidget(config);
mainLayout->addWidget(explainLabel);
mainLayout->addWidget(explain);
ok = new QPushButton(tr("OK"), this);
cancel = new QPushButton(tr("Cancel"), this);
QHBoxLayout *buttons = new QHBoxLayout();
buttons->addStretch();
buttons->addWidget(cancel);
buttons->addWidget(ok);
mainLayout->addLayout(buttons);
connect(ok, SIGNAL(clicked()), this, SLOT(okClicked()));
connect(cancel, SIGNAL(clicked()), this, SLOT(cancelClicked()));
}
void
ManualDataProcessorDialog::okClicked()
{
if (processor->postProcess((RideFile *)ride->ride(), config) == true) {
main->notifyRideSelected(); // XXX to remain compatible with rest of GC for now
}
accept();
}
void
ManualDataProcessorDialog::cancelClicked()
{
reject();
}

View File

@@ -1,115 +0,0 @@
/*
* 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
*/
#ifndef _DataProcessor_h
#define _DataProcessor_h
#include "RideFile.h"
#include "RideFileCommand.h"
#include "RideItem.h"
#include <QDate>
#include <QDir>
#include <QFile>
#include <QList>
#include <QMap>
#include <QVector>
// This file defines four classes:
//
// DataProcessorConfig is a base QWidget that must be supplied by the
// DataProcessor to enable the user to configure its options
//
// DataProcessor is an abstract base class for function-objects that take a
// rideFile and manipulate it. Examples include fixing gaps in recording or
// creating the .notes or .cpi file
//
// DataProcessorFactory is a singleton that maintains a mapping of
// all DataProcessor objects that can be applied to rideFiles
//
// ManualDataProcessorDialog is a dialog box to manually execute a
// dataprocessor on the current ride and is called from the mainWindow menus
//
#include <QtGui>
// every data processor must supply a configuration Widget
// when its processorConfig member is called
class DataProcessorConfig : public QWidget
{
Q_OBJECT
public:
DataProcessorConfig(QWidget *parent=0) : QWidget(parent) {}
virtual ~DataProcessorConfig() {}
virtual void readConfig() = 0;
virtual void saveConfig() = 0;
virtual QString explain() = 0;
};
// the data processor abstract base class
class DataProcessor
{
public:
DataProcessor() {}
virtual ~DataProcessor() {}
virtual bool postProcess(RideFile *, DataProcessorConfig*settings=0) = 0;
virtual DataProcessorConfig *processorConfig(QWidget *parent) = 0;
virtual QString name() = 0; // Localized Name for user interface
};
// all data processors
class DataProcessorFactory {
private:
static DataProcessorFactory *instance_;
QMap<QString,DataProcessor*> processors;
DataProcessorFactory() {}
public:
static DataProcessorFactory &instance();
bool registerProcessor(QString name, DataProcessor *processor);
QMap<QString,DataProcessor*> getProcessors() const { return processors; }
bool autoProcess(RideFile *); // run auto processes (after open rideFile)
};
class MainWindow;
class ManualDataProcessorDialog : public QDialog
{
Q_OBJECT
public:
ManualDataProcessorDialog(MainWindow *, QString, RideItem *);
private slots:
void cancelClicked();
void okClicked();
private:
MainWindow *main;
RideItem *ride;
DataProcessor *processor;
DataProcessorConfig *config;
QTextEdit *explain;
QPushButton *ok, *cancel;
};
#endif // _DataProcessor_h

View File

@@ -21,13 +21,10 @@
* Provides specialized formatting for Plot axes
*/
#include <QApplication>
#include <qwt_scale_draw.h>
class DaysScaleDraw: public QwtScaleDraw
{
Q_DECLARE_TR_FUNCTIONS(DaysScaleDraw)
public:
DaysScaleDraw()
{
@@ -37,25 +34,25 @@ class DaysScaleDraw: public QwtScaleDraw
switch(int(v))
{
case 1:
return QString(tr("Mon"));
return QString("Mon");
break;
case 2:
return QString(tr("Tue"));
return QString("Tue");
break;
case 3:
return QString(tr("Wed"));
return QString("Wed");
break;
case 4:
return QString(tr("Thu"));
return QString("Thu");
break;
case 5:
return QString(tr("Fri"));
return QString("Fri");
break;
case 6:
return QString(tr("Sat"));
return QString("Sat");
break;
case 7:
return QString(tr("Sun"));
return QString("Sun");
break;
default:
return QString(int(v));

View File

@@ -33,7 +33,6 @@ DeviceConfiguration::DeviceConfiguration()
type=0;
isDefaultDownload=false;
isDefaultRealtime=false;
postProcess=0;
}
@@ -99,10 +98,6 @@ DeviceConfigurations::readConfig()
configVal = settings->value(configStr);
Entry.isDefaultRealtime = configVal.toInt();
configStr = QString("%1%2").arg(GC_DEV_VIRTUAL).arg(i+1);
configVal = settings->value(configStr);
Entry.postProcess = configVal.toInt();
Entries.append(Entry);
}
return Entries;
@@ -143,10 +138,6 @@ DeviceConfigurations::writeConfig(QList<DeviceConfiguration> Configuration)
// isDefaultRealtime
configStr = QString("%1%2").arg(GC_DEV_DEFR).arg(i+1);
settings->setValue(configStr, Configuration.at(i).isDefaultRealtime);
// virtual post Process...
configStr = QString("%1%2").arg(GC_DEV_VIRTUAL).arg(i+1);
settings->setValue(configStr, Configuration.at(i).postProcess);
}
}

View File

@@ -36,8 +36,6 @@ class DeviceConfiguration
bool isDefaultDownload, // not implemented yet
isDefaultRealtime; // not implemented yet
int postProcess;
};
class DeviceConfigurations

View File

@@ -33,7 +33,7 @@ DownloadRideDialog::DownloadRideDialog(MainWindow *mainWindow,
downloadInProgress(false)
{
setAttribute(Qt::WA_DeleteOnClose);
setWindowTitle(tr("Download Ride Data"));
setWindowTitle("Download Ride Data");
portCombo = new QComboBox(this);
@@ -57,7 +57,6 @@ DownloadRideDialog::DownloadRideDialog(MainWindow *mainWindow,
connect(eraseRideButton, SIGNAL(clicked()), this, SLOT(eraseClicked()));
connect(rescanButton, SIGNAL(clicked()), this, SLOT(scanCommPorts()));
connect(cancelButton, SIGNAL(clicked()), this, SLOT(cancelClicked()));
connect(deviceCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(setReadyInstruct()));
QHBoxLayout *buttonLayout = new QHBoxLayout;
buttonLayout->addWidget(downloadButton);
@@ -91,9 +90,9 @@ DownloadRideDialog::setReadyInstruct()
Device &device = Device::device(deviceCombo->currentText());
QString inst = device.downloadInstructions();
if (inst.size() == 0)
label->setText(tr("Click Download to begin downloading."));
label->setText("Click Download to begin downloading.");
else
label->setText(inst + tr(", \nthen click Download."));
label->setText(inst + ", \nthen click Download.");
downloadButton->setEnabled(true);
if (deviceCombo->currentText() == "SRM") // only SRM supports erase ride for now
eraseRideButton->setEnabled(true);
@@ -107,9 +106,9 @@ DownloadRideDialog::scanCommPorts()
QString err;
devList = CommPort::listCommPorts(err);
if (err != "") {
QString msg = tr("Warning(s):\n\n") + err + tr("\n\nYou may need to (re)install "
"the FTDI or PL2303 drivers before downloading.");
QMessageBox::warning(0, tr("Error Loading Device Drivers"), msg,
QString msg = "Warning(s):\n\n" + err + "\n\nYou may need to (re)install "
"the FTDI or PL2303 drivers before downloading.";
QMessageBox::warning(0, "Error Loading Device Drivers", msg,
QMessageBox::Ok, QMessageBox::NoButton);
}
for (int i = 0; i < devList.size(); ++i) {

View File

@@ -99,7 +99,7 @@ ErgFile::ErgFile(QString filename, int &mode, double Cp)
mode = format = ERG;
} else if (mrcformat.exactMatch(line)) {
// save away the format
mode = format = MRC;
mode = format = ERG;
} else if (crsformat.exactMatch(line)) {
// save away the format
mode = format = CRS;

View File

@@ -1,626 +0,0 @@
/*
* Copyright (c) 2007-2008 Sean C. Rhea (srhea@srhea.net)
*
* 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 "FitRideFile.h"
#include <QSharedPointer>
#include <QMap>
#include <QSet>
#include <QtEndian>
#include <QDebug>
#include <stdio.h>
#include <stdint.h>
#include <limits>
#define RECORD_TYPE 20
static int fitFileReaderRegistered =
RideFileFactory::instance().registerReader(
"fit", "Garmin FIT", new FitFileReader());
static const QDateTime qbase_time(QDate(1989, 12, 31), QTime(0, 0, 0), Qt::UTC);
struct FitField {
int num;
int type; // FIT base_type
int size; // in bytes
};
struct FitDefinition {
int global_msg_num;
bool is_big_endian;
std::vector<FitField> fields;
};
/* FIT has uint32 as largest integer type. So qint64 is large enough to
* store all integer types - no matter if they're signed or not */
// XXX this needs to get changed to support non-integer values
typedef qint64 fit_value_t;
#define NA_VALUE std::numeric_limits<fit_value_t>::max()
struct FitFileReaderState
{
QFile &file;
QStringList &errors;
RideFile *rideFile;
time_t start_time;
time_t last_time;
double last_distance;
QMap<int, FitDefinition> local_msg_types;
QSet<int> unknown_record_fields, unknown_global_msg_nums, unknown_base_type;
int interval;
int devices;
bool stopped;
int last_event_type;
int last_event;
int last_msg_type;
FitFileReaderState(QFile &file, QStringList &errors) :
file(file), errors(errors), rideFile(NULL), start_time(0),
last_time(0), last_distance(0.00f), interval(0), devices(0), stopped(true),
last_event_type(-1), last_event(-1), last_msg_type(-1)
{
}
struct TruncatedRead {};
struct BadDelta {};
void read_unknown( int size, int *count = NULL ){
char c[size+1];
// XXX: just seek instead of read?
if (file.read(c, size ) != size)
throw TruncatedRead();
if (count)
(*count) += size;
}
fit_value_t read_int8(int *count = NULL) {
qint8 i;
if (file.read(reinterpret_cast<char*>( &i), 1) != 1)
throw TruncatedRead();
if (count)
(*count) += 1;
return i == 0x7f ? NA_VALUE : i;
}
fit_value_t read_uint8(int *count = NULL) {
quint8 i;
if (file.read(reinterpret_cast<char*>( &i), 1) != 1)
throw TruncatedRead();
if (count)
(*count) += 1;
return i == 0xff ? NA_VALUE : i;
}
fit_value_t read_uint8z(int *count = NULL) {
quint8 i;
if (file.read(reinterpret_cast<char*>( &i), 1) != 1)
throw TruncatedRead();
if (count)
(*count) += 1;
return i == 0x00 ? NA_VALUE : i;
}
fit_value_t read_int16(bool is_big_endian, int *count = NULL) {
qint16 i;
if (file.read(reinterpret_cast<char*>(&i), 2) != 2)
throw TruncatedRead();
if (count)
(*count) += 2;
i = is_big_endian
? qFromBigEndian<qint16>( i )
: qFromLittleEndian<qint16>( i );
return i == 0x7fff ? NA_VALUE : i;
}
fit_value_t read_uint16(bool is_big_endian, int *count = NULL) {
quint16 i;
if (file.read(reinterpret_cast<char*>(&i), 2) != 2)
throw TruncatedRead();
if (count)
(*count) += 2;
i = is_big_endian
? qFromBigEndian<quint16>( i )
: qFromLittleEndian<quint16>( i );
return i == 0xffff ? NA_VALUE : i;
}
fit_value_t read_uint16z(bool is_big_endian, int *count = NULL) {
quint16 i;
if (file.read(reinterpret_cast<char*>(&i), 2) != 2)
throw TruncatedRead();
if (count)
(*count) += 2;
i = is_big_endian
? qFromBigEndian<quint16>( i )
: qFromLittleEndian<quint16>( i );
return i == 0x0000 ? NA_VALUE : i;
}
fit_value_t read_int32(bool is_big_endian, int *count = NULL) {
qint32 i;
if (file.read(reinterpret_cast<char*>(&i), 4) != 4)
throw TruncatedRead();
if (count)
(*count) += 4;
i = is_big_endian
? qFromBigEndian<qint32>( i )
: qFromLittleEndian<qint32>( i );
return i == 0x7fffffff ? NA_VALUE : i;
}
fit_value_t read_uint32(bool is_big_endian, int *count = NULL) {
quint32 i;
if (file.read(reinterpret_cast<char*>(&i), 4) != 4)
throw TruncatedRead();
if (count)
(*count) += 4;
i = is_big_endian
? qFromBigEndian<quint32>( i )
: qFromLittleEndian<quint32>( i );
return i == 0xffffffff ? NA_VALUE : i;
}
fit_value_t read_uint32z(bool is_big_endian, int *count = NULL) {
quint32 i;
if (file.read(reinterpret_cast<char*>(&i), 4) != 4)
throw TruncatedRead();
if (count)
(*count) += 4;
i = is_big_endian
? qFromBigEndian<quint32>( i )
: qFromLittleEndian<quint32>( i );
return i == 0x00000000 ? NA_VALUE : i;
}
void decodeFileId(const FitDefinition &def, int, const std::vector<fit_value_t> values) {
int i = 0;
int manu = -1, prod = -1;
foreach(const FitField &field, def.fields) {
fit_value_t value = values[i++];
if( value == NA_VALUE )
continue;
switch (field.num) {
case 1: manu = value; break;
case 2: prod = value; break;
default: ; // do nothing
}
}
if (manu == 1) {
switch (prod) {
case 717: rideFile->setDeviceType("Garmin FR405"); break;
case 782: rideFile->setDeviceType("Garmin FR50"); break;
case 988: rideFile->setDeviceType("Garmin FR60"); break;
case 1018: rideFile->setDeviceType("Garmin FR310XT"); break;
case 1036: rideFile->setDeviceType("Garmin Edge 500"); break;
case 1169: rideFile->setDeviceType("Garmin Edge 800"); break;
default: rideFile->setDeviceType(QString("Unknown Garmin Device %1").arg(prod));
}
}
else {
rideFile->setDeviceType(QString("Unknown FIT Device %1:%2").arg(manu).arg(prod));
}
}
void decodeEvent(const FitDefinition &def, int, const std::vector<fit_value_t> values) {
time_t time = 0;
int event = -1;
int event_type = -1;
int i = 0;
foreach(const FitField &field, def.fields) {
fit_value_t value = values[i++];
if( value == NA_VALUE )
continue;
switch (field.num) {
case 253: time = value + qbase_time.toTime_t(); break;
case 0: event = value; break;
case 1: event_type = value; break;
default: ; // do nothing
}
}
if (event == 0) { // Timer event
switch (event_type) {
case 0: // start
stopped = false;
break;
case 1: // stop
stopped = true;
break;
case 2: // consecutive_depreciated
case 3: // marker
break;
case 4: // stop all
stopped = true;
break;
case 5: // begin_depreciated
case 6: // end_depreciated
case 7: // end_all_depreciated
case 8: // stop_disable
stopped = true;
break;
case 9: // stop_disable_all
stopped = true;
break;
default:
errors << QString("Unknown event type %1").arg(event_type);
}
}
// printf("event type %d\n", event_type);
last_event = event;
last_event_type = event_type;
}
void decodeLap(const FitDefinition &def, int time_offset, const std::vector<fit_value_t> values) {
time_t time = 0;
if (time_offset > 0)
time = last_time + time_offset;
else
time = last_time;
int i = 0;
time_t this_start_time = 0;
++interval;
foreach(const FitField &field, def.fields) {
fit_value_t value = values[i++];
if( value == NA_VALUE )
continue;
switch (field.num) {
case 253: time = value + qbase_time.toTime_t(); break;
case 2: this_start_time = value + qbase_time.toTime_t(); break;
default: ; // ignore it
}
}
if (this_start_time == 0 || this_start_time-start_time < 0)
errors << QString("lap %1 has invalid start time").arg(interval);
else {
if (rideFile->dataPoints().count()) // no samples means no laps..
rideFile->addInterval(this_start_time - start_time, time - start_time, QString("%1").arg(interval));
}
}
void decodeRecord(const FitDefinition &def, int time_offset, const std::vector<fit_value_t> values) {
time_t time = 0;
if (time_offset > 0)
time = last_time + time_offset;
double alt = 0, cad = 0, km = 0, grade = 0, hr = 0, lat = 0, lng = 0, badgps = 0;
double resistance = 0, kph = 0, temperature = 0, time_from_course = 0, watts = 0;
fit_value_t lati = NA_VALUE, lngi = NA_VALUE;
int i = 0;
foreach(const FitField &field, def.fields) {
fit_value_t value = values[i++];
if( value == NA_VALUE )
continue;
switch (field.num) {
case 253: time = value + qbase_time.toTime_t();
// Time MUST NOT go backwards
// You canny break the laws of physics, Jim
if (time < last_time) time = last_time;
break;
case 0: lati = value; break;
case 1: lngi = value; break;
case 2: alt = value / 5.0 - 500.0; break;
case 3: hr = value; break;
case 4: cad = value; break;
case 5: km = value / 100000.0; break;
case 6: kph = value * 3.6 / 1000.0; break;
case 7: watts = value; break;
case 8: break; // XXX packed speed/dist
case 9: grade = value / 100.0; break;
case 10: resistance = value; break;
case 11: time_from_course = value / 1000.0; break;
case 12: break; // XXX "cycle_length"
case 13: temperature = value; break;
default: unknown_record_fields.insert(field.num);
}
}
if (time == last_time)
return; // Sketchy, but some FIT files do this.
if (stopped) {
// As it turns out, this happens all the time in some FIT files.
// Since we don't really understand the meaning, don't make noise.
/*
errors << QString("At %1 seconds, time is stopped, but got record "
"anyway. Ignoring it. Last event type was "
"%2 for event %3.").arg(time-start_time).arg(last_event_type).arg(last_event);
return;
*/
}
if (lati != NA_VALUE && lngi != NA_VALUE) {
lat = lati * 180.0 / 0x7fffffff;
lng = lngi * 180.0 / 0x7fffffff;
} else
{
// If lat/lng are missng, set to 0/0 and fill point from last point as 0/0)
lat = 0;
lng = 0;
badgps = 1;
}
if (start_time == 0) {
start_time = time - 1; // XXX: recording interval?
QDateTime t;
t.setTime_t(start_time);
rideFile->setStartTime(t);
}
//printf( "point time=%d lat=%.2lf lon=%.2lf alt=%.1lf hr=%.0lf "
// "cad=%.0lf km=%.1lf kph=%.1lf watts=%.0lf grade=%.1lf "
// "resist=%.1lf off=%.1lf temp=%.1lf\n",
// time, lat, lng, alt, hr,
// cad, km, kph, watts, grade,
// resistance, time_from_course, temperature );
double secs = time - start_time;
double nm = 0;
double headwind = 0.0;
int interval = 0;
if ((last_msg_type == RECORD_TYPE) && (last_time != 0) && (time > last_time + 1)) {
// Evil smart recording. Linearly interpolate missing points.
RideFilePoint *prevPoint = rideFile->dataPoints().back();
int deltaSecs = (int) (secs - prevPoint->secs);
if(deltaSecs != secs - prevPoint->secs)
throw BadDelta(); // no fractional part
// This is only true if the previous record was of type record:
if(deltaSecs != time - last_time)
throw BadDelta();
// If the last lat/lng was missing (0/0) then all points up to lat/lng are marked as 0/0.
if (prevPoint->lat == 0 && prevPoint->lon == 0 ) {
badgps = 1;
}
double deltaCad = cad - prevPoint->cad;
double deltaHr = hr - prevPoint->hr;
double deltaDist = km - prevPoint->km;
if (km < 0.00001) deltaDist = 0.000f; // effectively zero distance
double deltaSpeed = kph - prevPoint->kph;
double deltaTorque = nm - prevPoint->nm;
double deltaPower = watts - prevPoint->watts;
double deltaAlt = alt - prevPoint->alt;
double deltaLon = lng - prevPoint->lon;
double deltaLat = lat - prevPoint->lat;
double deltaHeadwind = headwind - prevPoint->headwind;
for (int i = 1; i < deltaSecs; i++) {
double weight = 1.0 * i / deltaSecs;
rideFile->appendPoint(
prevPoint->secs + (deltaSecs * weight),
prevPoint->cad + (deltaCad * weight),
prevPoint->hr + (deltaHr * weight),
prevPoint->km + (deltaDist * weight),
prevPoint->kph + (deltaSpeed * weight),
prevPoint->nm + (deltaTorque * weight),
prevPoint->watts + (deltaPower * weight),
prevPoint->alt + (deltaAlt * weight),
(badgps == 1) ? 0 : prevPoint->lon + (deltaLon * weight),
(badgps == 1) ? 0 : prevPoint->lat + (deltaLat * weight),
prevPoint->headwind + (deltaHeadwind * weight),
interval);
}
prevPoint = rideFile->dataPoints().back();
}
if (km < 0.00001f) km = last_distance;
rideFile->appendPoint(secs, cad, hr, km, kph, nm, watts, alt, lng, lat, headwind, interval);
last_time = time;
last_distance = km;
}
int read_record(bool &stop, QStringList &errors) {
stop = false;
int count = 0;
int header_byte = read_uint8(&count);
if (!(header_byte & 0x80) && (header_byte & 0x40)) {
// Definition record
int local_msg_type = header_byte & 0xf;
local_msg_types.insert(local_msg_type, FitDefinition());
FitDefinition &def = local_msg_types[local_msg_type];
int reserved = read_uint8(&count); (void) reserved; // unused
def.is_big_endian = read_uint8(&count);
def.global_msg_num = read_uint16(def.is_big_endian, &count);
int num_fields = read_uint8(&count);
//printf("definition: local type=%d global=%d arch=%d fields=%d\n",
// local_msg_type, def.global_msg_num, def.is_big_endian,
// num_fields );
for (int i = 0; i < num_fields; ++i) {
def.fields.push_back(FitField());
FitField &field = def.fields.back();
field.num = read_uint8(&count);
field.size = read_uint8(&count);
int base_type = read_uint8(&count);
field.type = base_type & 0x1f;
//printf(" field %d: %d bytes, num %d, type %d\n",
// i, field.size, field.num, field.type );
}
}
else {
// Data record
int local_msg_type = 0;
int time_offset = 0;
if (header_byte & 0x80) {
// compressed time record
local_msg_type = (header_byte >> 5) & 0x3;
time_offset = header_byte & 0x1f;
}
else {
local_msg_type = header_byte & 0xf;
}
if (!local_msg_types.contains(local_msg_type)) {
errors << QString("local type %1 without previous definition").arg(local_msg_type);
stop = true;
return count;
}
const FitDefinition &def = local_msg_types[local_msg_type];
//printf( "message local=%d global=%d\n", local_msg_type,
// def.global_msg_num );
std::vector<fit_value_t> values;
foreach(const FitField &field, def.fields) {
fit_value_t v;
switch (field.type) {
case 0: v = read_uint8(&count); break;
case 1: v = read_int8(&count); break;
case 2: v = read_uint8(&count); break;
case 3: v = read_int16(def.is_big_endian, &count); break;
case 4: v = read_uint16(def.is_big_endian, &count); break;
case 5: v = read_int32(def.is_big_endian, &count); break;
case 6: v = read_uint32(def.is_big_endian, &count); break;
case 10: v = read_uint8z(&count); break;
case 11: v = read_uint16z(def.is_big_endian, &count); break;
case 12: v = read_uint32z(def.is_big_endian, &count); break;
//XXX: support float, string + byte base types
default:
read_unknown( field.size, &count );
v = NA_VALUE;
unknown_base_type.insert(field.num);
}
values.push_back(v);
//printf( " field: type=%d num=%d value=%lld\n",
// field.type, field.num, v );
}
// Most of the record types in the FIT format aren't actually all
// that useful. FileId, Lap, and Record clearly are. The one
// other one that might be useful is DeviceInfo, but it doesn't
// seem to be filled in properly. Sean's Cinqo, for example,
// shows up as manufacturer #65535, even though it should be #7.
switch (def.global_msg_num) {
case 0: decodeFileId(def, time_offset, values); break;
case 19: decodeLap(def, time_offset, values); break;
case RECORD_TYPE: decodeRecord(def, time_offset, values); break;
case 21: decodeEvent(def, time_offset, values); break;
case 23: /* device info */
case 18: /* session */
case 22: /* undocumented */
case 72: /* undocumented - new for garmin 800*/
case 34: /* activity */
case 49: /* file creator */
case 79: /* unknown */
break;
default:
unknown_global_msg_nums.insert(def.global_msg_num);
}
last_msg_type = def.global_msg_num;
}
return count;
}
RideFile * run() {
rideFile = new RideFile;
rideFile->setDeviceType("Garmin FIT"); // XXX: read from device msg?
rideFile->setRecIntSecs(1.0); // XXX: always?
if (!file.open(QIODevice::ReadOnly)) {
delete rideFile;
return NULL;
}
int header_size = read_uint8();
if (header_size != 12 && header_size != 14) {
errors << QString("bad header size: %1").arg(header_size);
delete rideFile;
return NULL;
}
int protocol_version = read_uint8();
(void) protocol_version;
// if the header size is 14 we have profile minor then profile major
// version. We still don't do anything with this information
int profile_version = read_uint16(false); // always littleEndian
(void) profile_version; // not sure what to do with this
int data_size = read_uint32(false); // always littleEndian
char fit_str[5];
if (file.read(fit_str, 4) != 4) {
errors << "truncated header";
delete rideFile;
return NULL;
}
fit_str[4] = '\0';
if (strcmp(fit_str, ".FIT") != 0) {
errors << QString("bad header, expected \".FIT\" but got \"%1\"").arg(fit_str);
delete rideFile;
return NULL;
}
// read the rest of the header
if (header_size == 14) read_uint16(false);
int bytes_read = 0;
bool stop = false;
try {
while (!stop && (bytes_read < data_size))
bytes_read += read_record(stop, errors);
}
catch (TruncatedRead &e) {
errors << "truncated file body";
delete rideFile;
return NULL;
}
catch (BadDelta &e) {
errors << "Unsupported smart recording interval found";
delete rideFile;
return NULL;
}
if (stop) {
delete rideFile;
return NULL;
}
else {
int crc = read_uint16( false ); // always littleEndian
(void) crc;
foreach(int num, unknown_global_msg_nums)
qDebug() << QString("FitRideFile: unknown global message number %1; ignoring it").arg(num);
foreach(int num, unknown_record_fields)
qDebug() << QString("FitRideFile: unknown record field %1; ignoring it").arg(num);
foreach(int num, unknown_base_type)
qDebug() << QString("FitRideFile: unknown base type %1; skipped").arg(num);
return rideFile;
}
}
};
RideFile *FitFileReader::openRideFile(QFile &file, QStringList &errors) const
{
QSharedPointer<FitFileReaderState> state(new FitFileReaderState(file, errors));
return state->run();
}
// vi:expandtab tabstop=4 shiftwidth=4

View File

@@ -1,29 +0,0 @@
/*
* Copyright (c) 2010 Sean C. Rhea (srhea@srhea.net)
*
* 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
*/
#ifndef _FitRideFile_h
#define _FitRideFile_h
#include "RideFile.h"
struct FitFileReader : public RideFileReader {
virtual RideFile *openRideFile(QFile &file, QStringList &errors) const;
};
#endif // _FitRideFile_h

View File

@@ -1,132 +0,0 @@
/*
* 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 "DataProcessor.h"
#include "Settings.h"
#include "Units.h"
#include <algorithm>
#include <QVector>
// Config widget used by the Preferences/Options config panes
class FixGPS;
class FixGPSConfig : public DataProcessorConfig
{
Q_DECLARE_TR_FUNCTIONS(FixGPSConfig)
friend class ::FixGPS;
protected:
public:
// there is no config
FixGPSConfig(QWidget *parent) : DataProcessorConfig(parent) {}
QString explain() {
return(QString(tr("Remove GPS errors and interpolate positional "
"data where the GPS device did not record any data, "
"or the data that was recorded is invalid.")));
}
void readConfig() {}
void saveConfig() {}
};
// RideFile Dataprocessor -- used to handle gaps in recording
// by inserting interpolated/zero samples
// to ensure dataPoints are contiguous in time
//
class FixGPS : public DataProcessor {
Q_DECLARE_TR_FUNCTIONS(FixGPS)
public:
FixGPS() {}
~FixGPS() {}
// the processor
bool postProcess(RideFile *, DataProcessorConfig* config);
// the config widget
DataProcessorConfig* processorConfig(QWidget *parent) {
return new FixGPSConfig(parent);
}
// Localized Name
QString name() {
return (tr("Fix GPS errors"));
}
};
static bool fixGPSAdded = DataProcessorFactory::instance().registerProcessor((QString("Fix GPS errors")), new FixGPS());
bool
FixGPS::postProcess(RideFile *ride, DataProcessorConfig *)
{
// ignore null or files without GPS data
if (!ride || ride->areDataPresent()->lat == false || ride->areDataPresent()->lon == false)
return false;
int errors=0;
ride->command->startLUW("Fix GPS Errors");
int lastgood = -1; // where did we last have decent GPS data?
for (int i=0; i<ride->dataPoints().count(); i++) {
// is this one decent?
if (ride->dataPoints()[i]->lat && ride->dataPoints()[i]->lat >= double(-90) && ride->dataPoints()[i]->lat <= double(90) &&
ride->dataPoints()[i]->lon && ride->dataPoints()[i]->lon >= double(-180) && ride->dataPoints()[i]->lon <= double(180)) {
if (lastgood != -1 && (lastgood+1) != i) {
// interpolate from last good to here
// then set last good to here
double deltaLat = (ride->dataPoints()[i]->lat - ride->dataPoints()[lastgood]->lat) / double(i-lastgood);
double deltaLon = (ride->dataPoints()[i]->lon - ride->dataPoints()[lastgood]->lon) / double(i-lastgood);
for (int j=lastgood+1; j<i; j++) {
ride->command->setPointValue(j, RideFile::lat, ride->dataPoints()[lastgood]->lat + (double(j-lastgood)*deltaLat));
ride->command->setPointValue(j, RideFile::lon, ride->dataPoints()[lastgood]->lon + (double(j-lastgood)*deltaLon));
errors++;
}
} else if (lastgood == -1) {
// fill to front
for (int j=0; j<i; j++) {
ride->command->setPointValue(j, RideFile::lat, ride->dataPoints()[i]->lat);
ride->command->setPointValue(j, RideFile::lon, ride->dataPoints()[i]->lon);
errors++;
}
}
lastgood = i;
}
}
// fill to end...
if (lastgood != -1 && lastgood != (ride->dataPoints().count()-1)) {
// fill from lastgood to end with lastgood
for (int j=lastgood+1; j<ride->dataPoints().count(); j++) {
ride->command->setPointValue(j, RideFile::lat, ride->dataPoints()[lastgood]->lat);
ride->command->setPointValue(j, RideFile::lon, ride->dataPoints()[lastgood]->lon);
errors++;
}
} else {
// they are all bad!!
// XXX do nothing?
}
ride->command->endLUW();
if (errors) {
ride->setTag("GPS errors", QString("%1").arg(errors));
return true;
} else
return false;
}

View File

@@ -1,253 +0,0 @@
/*
* 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 "DataProcessor.h"
#include "Settings.h"
#include "Units.h"
#include <algorithm>
#include <QVector>
// Config widget used by the Preferences/Options config panes
class FixGaps;
class FixGapsConfig : public DataProcessorConfig
{
Q_DECLARE_TR_FUNCTIONS(FixGapsConfig)
friend class ::FixGaps;
protected:
QHBoxLayout *layout;
QLabel *toleranceLabel, *beerandburritoLabel;
QDoubleSpinBox *tolerance,
*beerandburrito;
public:
FixGapsConfig(QWidget *parent) : DataProcessorConfig(parent) {
layout = new QHBoxLayout(this);
layout->setContentsMargins(0,0,0,0);
setContentsMargins(0,0,0,0);
toleranceLabel = new QLabel(tr("Tolerance"));
beerandburritoLabel = new QLabel(tr("Stop"));
tolerance = new QDoubleSpinBox();
tolerance->setMaximum(99.99);
tolerance->setMinimum(0);
tolerance->setSingleStep(0.1);
beerandburrito = new QDoubleSpinBox();
beerandburrito->setMaximum(99999.99);
beerandburrito->setMinimum(0);
beerandburrito->setSingleStep(0.1);
layout->addWidget(toleranceLabel);
layout->addWidget(tolerance);
layout->addWidget(beerandburritoLabel);
layout->addWidget(beerandburrito);
layout->addStretch();
}
//~FixGapsConfig() {} // deliberately not declared since Qt will delete
// the widget and its children when the config pane is deleted
QString explain() {
return(QString(tr("Many devices, especially wireless devices, will "
"drop connections to the bike computer. This leads "
"to lost samples in the resulting data, or so-called "
"drops in recording.\n\n"
"In order to calculate peak powers and averages, it "
"is very helpful to remove these gaps, and either "
"smooth the data where it is missing or just "
"replace with zero value samples\n\n"
"This function performs this task, taking two "
"parameters;\n\n"
"tolerance - this defines the minimum size of a "
"recording gap (in seconds) that will be processed. "
"any gap shorter than this will not be affected.\n\n"
"stop - this defines the maximum size of "
"gap (in seconds) that will have a smoothing algorithm "
"applied. Where a gap is shorter than this value it will "
"be filled with values interpolated from the values "
"recorded before and after the gap. If it is longer "
"than this value, it will be filled with zero values.")));
}
void readConfig() {
boost::shared_ptr<QSettings> settings = GetApplicationSettings();
double tol = settings->value(GC_DPFG_TOLERANCE, "1.0").toDouble();
double stop = settings->value(GC_DPFG_STOP, "1.0").toDouble();
tolerance->setValue(tol);
beerandburrito->setValue(stop);
}
void saveConfig() {
boost::shared_ptr<QSettings> settings = GetApplicationSettings();
settings->setValue(GC_DPFG_TOLERANCE, tolerance->value());
settings->setValue(GC_DPFG_STOP, beerandburrito->value());
}
};
// RideFile Dataprocessor -- used to handle gaps in recording
// by inserting interpolated/zero samples
// to ensure dataPoints are contiguous in time
//
class FixGaps : public DataProcessor {
Q_DECLARE_TR_FUNCTIONS(FixGaps)
public:
FixGaps() {}
~FixGaps() {}
// the processor
bool postProcess(RideFile *, DataProcessorConfig* config);
// the config widget
DataProcessorConfig* processorConfig(QWidget *parent) {
return new FixGapsConfig(parent);
}
// Localized Name
QString name() {
return (tr("Fix Gaps in Recording"));
}
};
static bool fixGapsAdded = DataProcessorFactory::instance().registerProcessor((QString("Fix Gaps in Recording")), new FixGaps());
bool
FixGaps::postProcess(RideFile *ride, DataProcessorConfig *config=0)
{
// get settings
double tolerance, stop;
if (config == NULL) { // being called automatically
boost::shared_ptr<QSettings> settings = GetApplicationSettings();
tolerance = settings->value(GC_DPFG_TOLERANCE, "1.0").toDouble();
stop = settings->value(GC_DPFG_STOP, "1.0").toDouble();
} else { // being called manually
tolerance = ((FixGapsConfig*)(config))->tolerance->value();
stop = ((FixGapsConfig*)(config))->beerandburrito->value();
}
// if the number of duration / number of samples
// equals the recording interval then we don't need
// to post-process for gaps
// XXX commented out since it is not always true and
// is purely to improve performance
//if ((ride->recIntSecs() + ride->dataPoints()[ride->dataPoints().count()-1]->secs -
// ride->dataPoints()[0]->secs) / (double) ride->dataPoints().count() == ride->recIntSecs())
// return false;
// Additionally, If there are less than 2 dataPoints then there
// is no way of post processing anyway (e.g. manual workouts)
if (ride->dataPoints().count() < 2) return false;
// OK, so there are probably some gaps, lets post process them
RideFilePoint *last = NULL;
int dropouts = 0;
double dropouttime = 0.0;
// put it all in a LUW
ride->command->startLUW("Fix Gaps in Recording");
for (int position = 0; position < ride->dataPoints().count(); position++) {
RideFilePoint *point = ride->dataPoints()[position];
if (last == NULL) last = point;
else {
double gap = point->secs - last->secs - ride->recIntSecs();
// if we have gps and we moved, then this isn't a stop
bool stationary = ((last->lat || last->lon) && (point->lat || point->lon)) // gps is present
&& last->lat == point->lat && last->lon == point->lon;
// moved for less than 30 seconds ... interpolate
if (!stationary && gap > tolerance && gap < stop) {
// what's needed?
dropouts++;
dropouttime += gap;
int count = gap/ride->recIntSecs();
double hrdelta = (point->hr - last->hr) / (double) count;
double pwrdelta = (point->watts - last->watts) / (double) count;
double kphdelta = (point->kph - last->kph) / (double) count;
double kmdelta = (point->km - last->km) / (double) count;
double caddelta = (point->cad - last->cad) / (double) count;
double altdelta = (point->alt - last->alt) / (double) count;
double nmdelta = (point->nm - last->nm) / (double) count;
double londelta = (point->lon - last->lon) / (double) count;
double latdelta = (point->lat - last->lat) / (double) count;
double hwdelta = (point->headwind - last->headwind) / (double) count;
// add the points
for(int i=0; i<count; i++) {
RideFilePoint *add = new RideFilePoint(last->secs+((i+1)*ride->recIntSecs()),
last->cad+((i+1)*caddelta),
last->hr + ((i+1)*hrdelta),
last->km + ((i+1)*kmdelta),
last->kph + ((i+1)*kphdelta),
last->nm + ((i+1)*nmdelta),
last->watts + ((i+1)*pwrdelta),
last->alt + ((i+1)*altdelta),
last->lon + ((i+1)*londelta),
last->lat + ((i+1)*latdelta),
last->headwind + ((i+1)*hwdelta),
last->interval);
ride->command->insertPoint(position++, add);
}
// stationary or greater than 30 seconds... fill with zeroes
} else if (gap > stop) {
dropouts++;
dropouttime += gap;
int count = gap/ride->recIntSecs();
double kmdelta = (point->km - last->km) / (double) count;
// add zero value points
for(int i=0; i<count; i++) {
RideFilePoint *add = new RideFilePoint(last->secs+((i+1)*ride->recIntSecs()),
0,
0,
last->km + ((i+1)*kmdelta),
0,
0,
0,
last->alt,
0,
0,
0,
last->interval);
ride->command->insertPoint(position++, add);
}
}
}
last = point;
}
// end the Logical unit of work here
ride->command->endLUW();
ride->setTag("Dropouts", QString("%1").arg(dropouts));
ride->setTag("Dropout Time", QString("%1").arg(dropouttime));
if (dropouts) return true;
else return false;
}

View File

@@ -1,203 +0,0 @@
/*
* 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 "DataProcessor.h"
#include "LTMOutliers.h"
#include "Settings.h"
#include "Units.h"
#include <algorithm>
#include <QVector>
// Config widget used by the Preferences/Options config panes
class FixSpikes;
class FixSpikesConfig : public DataProcessorConfig
{
Q_DECLARE_TR_FUNCTIONS(FixSpikesConfig)
friend class ::FixSpikes;
protected:
QHBoxLayout *layout;
QLabel *maxLabel, *varianceLabel;
QDoubleSpinBox *max,
*variance;
public:
FixSpikesConfig(QWidget *parent) : DataProcessorConfig(parent) {
layout = new QHBoxLayout(this);
layout->setContentsMargins(0,0,0,0);
setContentsMargins(0,0,0,0);
maxLabel = new QLabel(tr("Max"));
varianceLabel = new QLabel(tr("Variance"));
max = new QDoubleSpinBox();
max->setMaximum(9999.99);
max->setMinimum(0);
max->setSingleStep(1);
variance = new QDoubleSpinBox();
variance->setMaximum(9999);
variance->setMinimum(0);
variance->setSingleStep(50);
layout->addWidget(maxLabel);
layout->addWidget(max);
layout->addWidget(varianceLabel);
layout->addWidget(variance);
layout->addStretch();
}
//~FixSpikesConfig() {} // deliberately not declared since Qt will delete
// the widget and its children when the config pane is deleted
QString explain() {
return(QString(tr("Occasionally power meters will erroneously "
"report high values for power. For crank based "
"power meters such as SRM and Quarq this is "
"caused by an erroneous cadence reading "
"as a result of triggering a reed switch "
"whilst pushing off\n\n"
"This function will look for spikes/anomalies "
"in power data and replace the erroneous data "
"by smoothing/interpolating the data from either "
"side of the point in question\n\n"
"It takes the following parameters:\n\n"
"Absolute Max - this defines an absolute value "
"for watts, and will smooth any values above this "
"absolute value that have been identified as being "
"anomalies (i.e. at odds with the data surrounding it)\n\n"
"Variance (%) - this will smooth any values which "
"are higher than this percentage of the rolling "
"average wattage for the 30 seconds leading up "
"to the spike.")));
}
void readConfig() {
boost::shared_ptr<QSettings> settings = GetApplicationSettings();
double tol = settings->value(GC_DPFS_MAX, "1500").toDouble();
double stop = settings->value(GC_DPFS_VARIANCE, "1000").toDouble();
max->setValue(tol);
variance->setValue(stop);
}
void saveConfig() {
boost::shared_ptr<QSettings> settings = GetApplicationSettings();
settings->setValue(GC_DPFS_MAX, max->value());
settings->setValue(GC_DPFS_VARIANCE, variance->value());
}
};
// RideFile Dataprocessor -- used to handle gaps in recording
// by inserting interpolated/zero samples
// to ensure dataPoints are contiguous in time
//
class FixSpikes : public DataProcessor {
Q_DECLARE_TR_FUNCTIONS(FixSpikes)
public:
FixSpikes() {}
~FixSpikes() {}
// the processor
bool postProcess(RideFile *, DataProcessorConfig* config);
// the config widget
DataProcessorConfig* processorConfig(QWidget *parent) {
return new FixSpikesConfig(parent);
}
// Localized Name
QString name() {
return (tr("Fix Power Spikes"));
}
};
static bool fixSpikesAdded = DataProcessorFactory::instance().registerProcessor((QString("Fix Power Spikes")), new FixSpikes());
bool
FixSpikes::postProcess(RideFile *ride, DataProcessorConfig *config=0)
{
// does this ride have power?
if (ride->areDataPresent()->watts == false) return false;
// get settings
double variance, max;
if (config == NULL) { // being called automatically
boost::shared_ptr<QSettings> settings = GetApplicationSettings();
max = settings->value(GC_DPFS_MAX, "1500").toDouble();
variance = settings->value(GC_DPFS_VARIANCE, "1000").toDouble();
} else { // being called manually
max = ((FixSpikesConfig*)(config))->max->value();
variance = ((FixSpikesConfig*)(config))->variance->value();
}
int windowsize = 30 / ride->recIntSecs();
// We use a window size of 30s to find spikes
// if the ride is shorter, don't bother
// is no way of post processing anyway (e.g. manual workouts)
if (windowsize > ride->dataPoints().count()) return false;
// Find the power outliers
int spikes = 0;
double spiketime = 0.0;
// create a data array for the outlier algorithm
QVector<double> power;
QVector<double> secs;
foreach (RideFilePoint *point, ride->dataPoints()) {
power.append(point->watts);
secs.append(point->secs);
}
LTMOutliers *outliers = new LTMOutliers(secs.data(), power.data(), power.count(), windowsize, false);
ride->command->startLUW("Fix Spikes in Recording");
for (int i=0; i<secs.count(); i++) {
// is this over variance threshold?
if (outliers->getDeviationForRank(i) < variance) break;
// ok, so its highly variant but is it over
// the max value we are willing to accept?
if (outliers->getYForRank(i) < max) continue;
// Houston, we have a spike
spikes++;
spiketime += ride->recIntSecs();
// which one is it
int pos = outliers->getIndexForRank(i);
double left=0.0, right=0.0;
if (pos > 0) left = ride->dataPoints()[pos-1]->watts;
if (pos < (ride->dataPoints().count()-1)) right = ride->dataPoints()[pos+1]->watts;
ride->command->setPointValue(pos, RideFile::watts, (left+right)/2.0);
}
ride->command->endLUW();
ride->setTag("Spikes", QString("%1").arg(spikes));
ride->setTag("Spike Time", QString("%1").arg(spiketime));
if (spikes) return true;
else return false;
}

View File

@@ -1,155 +0,0 @@
/*
* 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 "DataProcessor.h"
#include "LTMOutliers.h"
#include "Settings.h"
#include "Units.h"
#include <algorithm>
#include <QVector>
// Config widget used by the Preferences/Options config panes
class FixTorque;
class FixTorqueConfig : public DataProcessorConfig
{
Q_DECLARE_TR_FUNCTIONS(FixTorqueConfig)
friend class ::FixTorque;
protected:
QHBoxLayout *layout;
QLabel *taLabel;
QLineEdit *ta;
public:
FixTorqueConfig(QWidget *parent) : DataProcessorConfig(parent) {
layout = new QHBoxLayout(this);
layout->setContentsMargins(0,0,0,0);
setContentsMargins(0,0,0,0);
taLabel = new QLabel(tr("Torque Adjust"));
ta = new QLineEdit();
layout->addWidget(taLabel);
layout->addWidget(ta);
layout->addStretch();
}
//~FixTorqueConfig() {} // deliberately not declared since Qt will delete
// the widget and its children when the config pane is deleted
QString explain() {
return(QString(tr("Adjusting torque values allows you to "
"uplift or degrade the torque values when the calibration "
"of your power meter was incorrect. It "
"takes a single parameter:\n\n"
"Torque Adjust - this defines an absolute value "
"in poinds per square inch or newton meters to "
"modify values by. Negative values are supported. (e.g. enter \"1.2 nm\" or "
"\"-0.5 pi\").")));
}
void readConfig() {
boost::shared_ptr<QSettings> settings = GetApplicationSettings();
ta->setText(settings->value(GC_DPTA, "0 nm").toString());
}
void saveConfig() {
boost::shared_ptr<QSettings> settings = GetApplicationSettings();
settings->setValue(GC_DPTA, ta->text());
}
};
// RideFile Dataprocessor -- used to handle gaps in recording
// by inserting interpolated/zero samples
// to ensure dataPoints are contiguous in time
//
class FixTorque : public DataProcessor {
Q_DECLARE_TR_FUNCTIONS(FixTorque)
public:
FixTorque() {}
~FixTorque() {}
// the processor
bool postProcess(RideFile *, DataProcessorConfig* config);
// the config widget
DataProcessorConfig* processorConfig(QWidget *parent) {
return new FixTorqueConfig(parent);
}
// Localized Name
QString name() {
return (tr("Adjust Torque Values"));
}
};
static bool fixTorqueAdded = DataProcessorFactory::instance().registerProcessor((QString("Adjust Torque Values")), new FixTorque());
bool
FixTorque::postProcess(RideFile *ride, DataProcessorConfig *config=0)
{
// does this ride have torque?
if (ride->areDataPresent()->nm == false) return false;
// Lets do it then!
QString ta;
double nmAdjust;
if (config == NULL) { // being called automatically
boost::shared_ptr<QSettings> settings = GetApplicationSettings();
ta = settings->value(GC_DPTA, "0 nm").toString();
} else { // being called manually
ta = ((FixTorqueConfig*)(config))->ta->text();
}
// patrick's torque adjustment code
bool pi = ta.endsWith("pi", Qt::CaseInsensitive);
if (pi || ta.endsWith("nm", Qt::CaseInsensitive)) {
nmAdjust = ta.left(ta.length() - 2).toDouble();
if (pi) {
nmAdjust *= 0.11298482933;
}
} else {
nmAdjust = ta.toDouble();
}
// no adjustment required
if (nmAdjust == 0) return false;
// apply the change
ride->command->startLUW("Adjust Torque");
for (int i=0; i<ride->dataPoints().count(); i++) {
RideFilePoint *point = ride->dataPoints()[i];
if (point->nm != 0) {
double newnm = point->nm + nmAdjust;
ride->command->setPointValue(i, RideFile::watts, point->watts * (newnm / point->nm));
ride->command->setPointValue(i, RideFile::nm, newnm);
}
}
ride->command->endLUW();
double currentta = ride->getTag("Torque Adjust", "0.0").toDouble();
ride->setTag("Torque Adjust", QString("%1 nm").arg(currentta + nmAdjust));
return true;
}

View File

@@ -22,8 +22,6 @@
#include <QVector>
#include <assert.h>
#include <QDebug>
#define DATETIME_FORMAT "yyyy/MM/dd hh:mm:ss' UTC'"
static int gcFileReaderRegistered =
@@ -56,50 +54,8 @@ GcFileReader::openRideFile(QFile &file, QStringList &errors) const
QString value = attr.attribute("value");
if (key == "Device type")
rideFile->setDeviceType(value);
if (key == "Start time") {
// by default QDateTime is localtime - the source however is UTC
QDateTime aslocal = QDateTime::fromString(value, DATETIME_FORMAT);
// construct in UTC so we can honour the conversion to localtime
QDateTime asUTC = QDateTime(aslocal.date(), aslocal.time(), Qt::UTC);
// now set in localtime
rideFile->setStartTime(asUTC.toLocalTime());
}
}
// read in metric overrides:
// <override>
// <metric name="skiba_bike_score" value="100"/>
// <metric name="average_speed" secs="3600" km="30"/>
// </override>
QDomNode overrides = root.firstChildElement("override");
if (!overrides.isNull()) {
for (QDomElement override = overrides.firstChildElement("metric");
!override.isNull();
override = override.nextSiblingElement("metric")) {
// setup the metric overrides QMap
QMap<QString, QString> bsm;
// for now only value is known to be maintained
bsm.insert("value", override.attribute("value"));
// insert into the rideFile overrides
rideFile->metricOverrides.insert(override.attribute("name"), bsm);
}
}
// read in the name/value metadata pairs
QDomNode tags = root.firstChildElement("tags");
if (!tags.isNull()) {
for (QDomElement tag = tags.firstChildElement("tag");
!tag.isNull();
tag = tag.nextSiblingElement("tag")) {
rideFile->setTag(tag.attribute("name"), tag.attribute("value"));
}
if (key == "Start time")
rideFile->setStartTime(QDateTime::fromString(value, DATETIME_FORMAT).toLocalTime());
}
QVector<double> intervalStops; // used to set the interval number for each point
@@ -124,13 +80,15 @@ GcFileReader::openRideFile(QFile &file, QStringList &errors) const
int interval = 0;
QDomElement samples = root.firstChildElement("samples");
if (samples.isNull()) return rideFile; // manual file will have no samples
if (samples.isNull()) {
errors << "no sample section in ride file";
return NULL;
}
bool recIntSet = false;
for (QDomElement sample = samples.firstChildElement("sample");
!sample.isNull(); sample = sample.nextSiblingElement("sample")) {
double secs, cad, hr, km, kph, nm, watts, alt, lon, lat;
double headwind = 0.0;
secs = sample.attribute("secs", "0.0").toDouble();
cad = sample.attribute("cad", "0.0").toDouble();
hr = sample.attribute("hr", "0.0").toDouble();
@@ -143,7 +101,7 @@ GcFileReader::openRideFile(QFile &file, QStringList &errors) const
lat = sample.attribute("lat", "0.0").toDouble();
while ((interval < intervalStops.size()) && (secs >= intervalStops[interval]))
++interval;
rideFile->appendPoint(secs, cad, hr, km, kph, nm, watts, alt, lon, lat, headwind, interval);
rideFile->appendPoint(secs, cad, hr, km, kph, nm, watts, alt, lon, lat, interval);
if (!recIntSet) {
rideFile->setRecIntSecs(sample.attribute("len").toDouble());
recIntSet = true;
@@ -158,15 +116,10 @@ GcFileReader::openRideFile(QFile &file, QStringList &errors) const
return rideFile;
}
// normal precision (Qt defaults)
#define add_sample(name) \
if (present->name) \
sample.setAttribute(#name, QString("%1").arg(point->name));
// high precision (6 decimals)
#define add_sample_hp(name) \
if (present->name) \
sample.setAttribute(#name, QString("%1").arg(point->name, 0, 'g', 11));
void
GcFileReader::writeRideFile(const RideFile *ride, QFile &file) const
{
@@ -187,45 +140,6 @@ GcFileReader::writeRideFile(const RideFile *ride, QFile &file) const
attribute.setAttribute("key", "Device type");
attribute.setAttribute("value", ride->deviceType());
// write out in metric overrides:
// <override>
// <metric name="skiba_bike_score" value="100"/>
// <metric name="average_speed" secs="3600" km="30"/>
// </override>
// write out the QMap tag/value pairs
QDomElement overrides = doc.createElement("override");
root.appendChild(overrides);
QMap<QString,QMap<QString, QString> >::const_iterator k;
for (k=ride->metricOverrides.constBegin(); k != ride->metricOverrides.constEnd(); k++) {
// may not contain anything
if (k.value().isEmpty()) continue;
QDomElement override = doc.createElement("metric");
overrides.appendChild(override);
// metric name
override.setAttribute("name", k.key());
// key/value pairs
QMap<QString, QString>::const_iterator j;
for (j=k.value().constBegin(); j != k.value().constEnd(); j++) {
override.setAttribute(j.key(), j.value());
}
}
// write out the QMap tag/value pairs
QDomElement tags = doc.createElement("tags");
root.appendChild(tags);
QMap<QString,QString>::const_iterator i;
for (i=ride->tags().constBegin(); i != ride->tags().constEnd(); i++) {
QDomElement tag = doc.createElement("tag");
tags.appendChild(tag);
tag.setAttribute("name", i.key());
tag.setAttribute("value", i.value());
}
if (!ride->intervals().empty()) {
QDomElement intervals = doc.createElement("intervals");
root.appendChild(intervals);
@@ -247,7 +161,7 @@ GcFileReader::writeRideFile(const RideFile *ride, QFile &file) const
QDomElement sample = doc.createElement("sample");
samples.appendChild(sample);
assert(present->secs);
add_sample_hp(secs);
add_sample(secs);
add_sample(cad);
add_sample(hr);
add_sample(km);
@@ -255,8 +169,8 @@ GcFileReader::writeRideFile(const RideFile *ride, QFile &file) const
add_sample(nm);
add_sample(watts);
add_sample(alt);
add_sample_hp(lon);
add_sample_hp(lat);
add_sample(lon);
add_sample(lat);
sample.setAttribute("len", QString("%1").arg(ride->recIntSecs()));
}
}

View File

@@ -1,627 +0,0 @@
/*
* Copyright (c) 2009 Greg Lonnon (greg.lonnon@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 "GoogleMapControl.h"
#include "RideItem.h"
#include "RideFile.h"
#include "MainWindow.h"
#include "Zones.h"
#include "Settings.h"
#include "Units.h"
#include "TimeUtils.h"
#include <QDebug>
#include <string>
#include <vector>
#include <algorithm>
#include <boost/foreach.hpp>
#include <boost/circular_buffer.hpp>
using namespace std;
namespace gm
{
// quick ideas on a math pipeline, kindof like this...
// but more of a pipeline...
// it makes the math somewhat easier to do and understand...
class RideFilePointAlgorithm
{
protected:
RideFilePoint prevRfp;
bool first;
RideFilePointAlgorithm() { first = false; }
};
// Algorithm to find the meidan of a set of data
template<typename T> class Median
{
boost::circular_buffer<T> buffer;
public:
Median(int size)
{
buffer.set_capacity(size);
}
// add the new data
void add(T a) { buffer.push_back(a); }
operator T()
{
if(buffer.size() == 0)
{
return 0;
}
T total = 0;
BOOST_FOREACH(T point, buffer)
{
total += point;
}
return total / buffer.size();
}
};
class AltGained : private RideFilePointAlgorithm
{
protected:
double gained;
double curAlt, prevAlt;
Median<double> median;
public:
AltGained(): gained(0), curAlt(0), prevAlt(0), median(20) {}
void operator()(RideFilePoint rfp)
{
median.add(rfp.alt);
curAlt = median;
if(prevAlt == 0)
{
prevAlt = median;
}
if(curAlt> prevAlt)
{
gained += curAlt - prevAlt;
}
prevAlt = curAlt;
}
int TotalGained() { return gained; }
operator int() { return TotalGained(); }
};
class AvgHR
{
int samples;
int totalHR;
public:
AvgHR() : samples(0),totalHR(0.0) {}
void operator()(RideFilePoint rfp)
{
totalHR += rfp.hr;
samples++;
}
int HR() {
if(samples == 0) return 0;
return totalHR / samples;
}
operator int() { return HR(); }
};
class AvgPower
{
int samples;
double totalPower;
public:
AvgPower() : samples(0), totalPower(0.0) { }
void operator()(RideFilePoint rfp)
{
totalPower += rfp.watts;
samples++;
}
int Power() { return (int) (totalPower / samples); }
operator int() { return Power(); }
};
}
using namespace gm;
#define GOOGLE_KEY "ABQIAAAAS9Z2oFR8vUfLGYSzz40VwRQ69UCJw2HkJgivzGoninIyL8-QPBTtnR-6pM84ljHLEk3PDql0e2nJmg"
GoogleMapControl::GoogleMapControl(MainWindow *mw) : main(mw), range(-1), current(NULL)
{
parent = mw;
view = new QWebView();
layout = new QVBoxLayout();
layout->addWidget(view);
setLayout(layout);
connect(parent, SIGNAL(rideSelected()), this, SLOT(rideSelected()));
connect(view, SIGNAL(loadStarted()), this, SLOT(loadStarted()));
connect(view, SIGNAL(loadFinished(bool)), this, SLOT(loadFinished(bool)));
loadingPage = false;
newRideToLoad = false;
}
void
GoogleMapControl::rideSelected()
{
if (parent->activeTab() != this) return;
RideItem * ride = parent->rideItem();
if (ride == current || !ride || !ride->ride())
return;
else
current = ride;
range =ride->zoneRange();
if(range < 0)
{
rideCP = 300; // default cp to 300 watts
}
else
{
rideCP = ride->zones->getCP(range);
}
rideData.clear();
double prevLon = 1000;
double prevLat = 1000;
foreach(RideFilePoint *rfp,ride->ride()->dataPoints())
{
RideFilePoint curRfp = *rfp;
// wko imports can have -320 type of values for roughly 40 degrees
// This if range is a guess that we only have these -ve type numbers for actual 0 to 90 deg of Latitude
if(curRfp.lat <= -270 && curRfp.lat >= -360)
{
curRfp.lat = 360 + curRfp.lat;
}
// Longitude = -180 to 180 degrees, Latitude = -90 to 90 degrees - anything else is invalid.
if((curRfp.lon < -180 || curRfp.lon > 180 || curRfp.lat < -90 || curRfp.lat > 90)
// Garmin FIT records a missed GPS signal as 0/0.
|| ((curRfp.lon == 0) && (curRfp.lat == 0)))
{
curRfp.lon = 999;
curRfp.lat = 999;
}
prevLon = curRfp.lon;
prevLat = curRfp.lat;
rideData.push_back(curRfp);
}
newRideToLoad = true;
loadRide();
}
void GoogleMapControl::resizeEvent(QResizeEvent * )
{
static bool first = true;
if (parent->activeTab() != this) return;
if (first == true) {
first = false;
} else {
newRideToLoad = true;
loadRide();
}
}
void GoogleMapControl::loadStarted()
{
loadingPage = true;
}
/// called after the load is finished
void GoogleMapControl::loadFinished(bool)
{
loadingPage = false;
loadRide();
}
void GoogleMapControl::loadRide()
{
if(loadingPage == true)
{
return;
}
if(newRideToLoad == true)
{
createHtml(current);
newRideToLoad = false;
loadingPage = true;
QString htmlFile(QDir::tempPath());
htmlFile.append("/maps.html");
QFile file(htmlFile);
file.remove();
file.open(QIODevice::ReadWrite);
file.write(currentPage.str().c_str(),currentPage.str().length());
file.flush();
file.close();
QString filename("file:///");
filename.append(htmlFile);
QUrl url(filename);
view->load(url);
}
}
void GoogleMapControl::createHtml(RideItem *ride)
{
currentPage.str("");
double minLat, minLon, maxLat, maxLon;
minLat = minLon = 1000;
maxLat = maxLon = -1000; // larger than 360
BOOST_FOREACH(RideFilePoint rfp, rideData)
{
if (rfp.lat != 999 && rfp.lon != 999)
{
minLat = std::min(minLat,rfp.lat);
maxLat = std::max(maxLat,rfp.lat);
minLon = std::min(minLon,rfp.lon);
maxLon = std::max(maxLon,rfp.lon);
}
}
if(minLon == 1000)
{
currentPage << tr("No GPS Data Present").toStdString();
return;
}
/// seems to be the magic number... to stop the scrollbars
int width = view->width() -16;
int height = view->height() -16;
std::ostringstream oss;
oss.precision(6);
oss.setf(ios::fixed,ios::floatfield);
oss << "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\"" << endl
<< "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">" << endl
<< "<html xmlns=\"http://www.w3.org/1999/xhtml\" xmlns:v=\"urn:schemas-microsoft-com:vml\">" << endl
<< "<head>" << endl
<< "<meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\"/>" << endl
<< "<title>GoldenCheetah</title>" << endl
<< "<script src=\"http://maps.google.com/maps?file=api&amp;v=2&amp;key=" << GOOGLE_KEY <<"\"" << endl
<< "type=\"text/javascript\"></script>" << endl
<< CreateMapToolTipJavaScript() << endl
<< "<script type=\"text/javascript\">"<< endl
<< "var map;" << endl
<< "function initialize() {" << endl
<< "if (GBrowserIsCompatible()) {" << endl
<< "map = new GMap2(document.getElementById(\"map_canvas\")," << endl
<< " {size: new GSize(" << width << "," << height << ") } );" << endl
<< "map.setUIToDefault()" << endl
<< CreatePolyLine() << endl
<< CreateMarkers(ride) << endl
<< "var sw = new GLatLng(" << minLat << "," << minLon << ");" << endl
<< "var ne = new GLatLng(" << maxLat << "," << maxLon << ");" << endl
<< "var bounds = new GLatLngBounds(sw,ne);" << endl
<< "var zoomLevel = map.getBoundsZoomLevel(bounds);" << endl
<< "var center = bounds.getCenter(); " << endl
<< "map.setCenter(bounds.getCenter(),map.getBoundsZoomLevel(bounds),G_PHYSICAL_MAP);" << endl
<< "map.enableScrollWheelZoom();" << endl
<< "}" << endl
<< "}" << endl
<< "function animate() {" << endl
<< "map.panTo(new GLatLng(" << maxLat << "," << minLon << "));" << endl
<< "}" << endl
<< "</script>" << endl
<< "</head>" << endl
<< "<body onload=\"initialize()\" onunload=\"GUnload()\">" << endl
<< "<div id=\"map_canvas\" style=\"width: " << width <<"px; height: "
<< height <<"px\"></div>" << endl
<< "<form action=\"#\" onsubmit=\"animate(); return false\">" << endl
<< "</form>" << endl
<< "</body>" << endl
<< "</html>" << endl;
currentPage << oss.str();
}
QColor GoogleMapControl::GetColor(int watts)
{
if (range < 0) return Qt::red;
else return zoneColor(main->zones()->whichZone(range, watts), 7);
}
/// create the ride line
string GoogleMapControl::CreatePolyLine()
{
std::vector<RideFilePoint> intervalPoints;
ostringstream oss;
boost::shared_ptr<QSettings> settings = GetApplicationSettings();
QVariant intervalTime = settings->value(GC_MAP_INTERVAL).toInt();
if (intervalTime.isNull() || intervalTime.toInt() == 0)
intervalTime.setValue(30);
BOOST_FOREACH(RideFilePoint rfp, rideData)
{
intervalPoints.push_back(rfp);
if((intervalPoints.back().secs - intervalPoints.front().secs) >
intervalTime.toInt())
{
// find the avg power and color code it and create a polyline...
AvgPower avgPower = for_each(intervalPoints.begin(),
intervalPoints.end(),
AvgPower());
// find the color
// create the polyline
CreateSubPolyLine(intervalPoints,oss,avgPower);
intervalPoints.clear();
intervalPoints.push_back(rfp);
}
}
return oss.str();
}
void GoogleMapControl::CreateSubPolyLine(const std::vector<RideFilePoint> &points,
std::ostringstream &oss,
int avgPower)
{
boost::shared_ptr<QSettings> settings = GetApplicationSettings();
QVariant intervalTime = settings->value(GC_MAP_INTERVAL);
if (intervalTime.isNull() || intervalTime.toInt() == 0)
intervalTime.setValue(30);
oss.precision(6);
QColor color = GetColor(avgPower);
QString colorstr = color.name();
oss.setf(ios::fixed,ios::floatfield);
oss << "var polyline = new GPolyline([";
BOOST_FOREACH(RideFilePoint rfp, points)
{
if (!(rfp.lat == 999 && rfp.lon == 999))
{
oss << "new GLatLng(" << rfp.lat << "," << rfp.lon << ")," << endl;
}
}
oss << "],\"" << colorstr.toStdString() << "\",4);" << endl;
oss << "GEvent.addListener(polyline, 'mouseover', function() {" << endl
<< "var tooltip_text = '" << intervalTime.toInt() << "s Power: " << avgPower << "';" << endl
<< "var ss={'weight':8};" << endl
<< "this.setStrokeStyle(ss);" << endl
<< "this.overlay = new MapTooltip(this,tooltip_text);" << endl
<< "map.addOverlay(this.overlay);" << endl
<< "});" << endl
<< "GEvent.addListener(polyline, 'mouseout', function() {" << endl
<< "map.removeOverlay(this.overlay);" << endl
<< "var ss={'weight':5};" << endl
<< "this.setStrokeStyle(ss);" << endl
<< "});" << endl;
oss << "map.addOverlay (polyline);" << endl;
}
string GoogleMapControl::CreateIntervalMarkers(RideItem *rideItem)
{
ostringstream oss;
RideFile *ride = rideItem->ride();
if(ride->intervals().size() == 0)
return "";
boost::shared_ptr<QSettings> settings = GetApplicationSettings();
QVariant unit = settings->value(GC_UNIT);
bool metricUnits = (unit.toString() == "Metric");
QString s;
if (settings->contains(GC_SETTINGS_INTERVAL_METRICS))
s = settings->value(GC_SETTINGS_INTERVAL_METRICS).toString();
else
s = GC_SETTINGS_INTERVAL_METRICS_DEFAULT;
QStringList intervalMetrics = s.split(",");
int row = 0;
foreach (RideFileInterval interval, ride->intervals()) {
// create a temp RideFile to be used to figure out the metrics for the interval
RideFile f(ride->startTime(), ride->recIntSecs());
for (int i = ride->intervalBegin(interval); i < ride->dataPoints().size(); ++i) {
const RideFilePoint *p = ride->dataPoints()[i];
if (p->secs >= interval.stop)
break;
f.appendPoint(p->secs, p->cad, p->hr, p->km, p->kph, p->nm,
p->watts, p->alt, p->lon, p->lat, p->headwind, 0);
}
if (f.dataPoints().size() == 0) {
// Interval empty, do not compute any metrics
continue;
}
QHash<QString,RideMetricPtr> metrics =
RideMetric::computeMetrics(&f, parent->zones(), parent->hrZones(), intervalMetrics);
string html = CreateIntervalHtml(metrics,intervalMetrics,interval.name,metricUnits);
row++;
oss << CreateMarker(row,f.dataPoints().front()->lat,f.dataPoints().front()->lon,html);
}
return oss.str();
}
string GoogleMapControl::CreateIntervalHtml(QHash<QString,RideMetricPtr> &metrics, QStringList &intervalMetrics,
QString &intervalName, bool metricUnits)
{
ostringstream oss;
oss << "<p><h2>" << intervalName.toStdString() << "</h2>";
oss << "<table align=\\\"center\\\" cellspacing=0 border=0>";
int row = 0;
foreach (QString symbol, intervalMetrics) {
RideMetricPtr m = metrics.value(symbol);
if(m == NULL)
continue;
if (row % 2)
oss << "<tr>";
else {
QColor color = QApplication::palette().alternateBase().color();
color = QColor::fromHsv(color.hue(), color.saturation() * 2, color.value());
oss << "<tr bgcolor='" + color.name().toStdString() << "'>";
}
oss.setf(ios::fixed);
oss.precision(m->precision());
oss << "<td align=\\\"left\\\">" + m->name().toStdString() << "</td>";
oss << "<td align=\\\"left\\\">";
if (m->units(metricUnits) == "seconds" || m->units(metricUnits) == tr("seconds"))
oss << time_to_string(m->value(metricUnits)).toStdString();
else
{
oss << m->value(metricUnits);
oss << " " << m->units(metricUnits).toStdString();
}
oss <<"</td>";
oss << "</tr>";
row++;
}
oss << "</table>";
return oss.str();
}
string GoogleMapControl::CreateMarkers(RideItem *ride)
{
ostringstream oss;
oss.precision(6);
oss.setf(ios::fixed,ios::floatfield);
// start marker
/*
oss << "var marker;" << endl;
oss << "var greenIcon = new GIcon(G_DEFAULT_ICON);" << endl;
oss << "greenIcon.image = \"http://gmaps-samples.googlecode.com/svn/trunk/markers/green/blank.png\"" << endl;
oss << "markerOptions = { icon:greenIcon };" << endl;
oss << "marker = new GMarker(new GLatLng(";
oss << rideData.front().lat << "," << rideData.front().lon << "),greenIcon);" << endl;
oss << "marker.bindInfoWindowHtml(\"<h3>Start</h3>\");" << endl;
oss << "map.addOverlay(marker);" << endl;
*/
oss << CreateIntervalMarkers(ride);
// end marker
boost::shared_ptr<QSettings> settings = GetApplicationSettings();
QVariant unit = settings->value(GC_UNIT);
bool metricUnits = (unit.toString() == "Metric");
QString s;
if (settings->contains(GC_SETTINGS_INTERVAL_METRICS))
s = settings->value(GC_SETTINGS_INTERVAL_METRICS).toString();
else
s = GC_SETTINGS_INTERVAL_METRICS_DEFAULT;
QStringList intervalMetrics = s.split(",");
QHash<QString,RideMetricPtr> metrics =
RideMetric::computeMetrics(ride->ride(), parent->zones(), parent->hrZones(), intervalMetrics);
QString name = "Ride Summary";
string html = CreateIntervalHtml(metrics,intervalMetrics,name,metricUnits);
oss << "var redIcon = new GIcon(G_DEFAULT_ICON);" << endl;
oss << "redIcon.image = \"http://gmaps-samples.googlecode.com/svn/trunk/markers/red/blank.png\"" << endl;
oss << "markerOptions = { icon:redIcon };" << endl;
oss << "marker = new GMarker(new GLatLng(";
oss << rideData.back().lat << "," << rideData.back().lon << "),redIcon);" << endl;
oss << "marker.bindInfoWindowHtml(\""<< html << "\");" << endl;
oss << "map.addOverlay(marker);" << endl;
return oss.str();
}
std::string GoogleMapControl::CreateMarker(int number, double lat, double lon, string &html)
{
ostringstream oss;
oss.precision(6);
oss << "intervalIcon = new GIcon(G_DEFAULT_ICON);" << endl;
if ( number == 1 )
oss << "intervalIcon.image = \"http://gmaps-samples.googlecode.com/svn/trunk/markers/green/marker" << number << ".png\"" << endl;
else
oss << "intervalIcon.image = \"http://gmaps-samples.googlecode.com/svn/trunk/markers/blue/marker" << number << ".png\"" << endl;
oss << "markerOptions = { icon:intervalIcon };" << endl;
oss << "marker = new GMarker(new GLatLng( ";
oss<< lat << "," << lon << "),intervalIcon);" << endl;
oss << "marker.bindInfoWindowHtml(\"" << html << "\");";
oss.precision(1);
oss << "map.addOverlay(marker);" << endl;
return oss.str();
}
string GoogleMapControl::CreateMapToolTipJavaScript()
{
ostringstream oss;
oss << "<script type=\"text/javascript\">" << endl
<< "var MapTooltip = function(obj, html, options) {"<< endl
<< "this.obj = obj;"<< endl
<< "this.html = html;"<< endl
<< "this.options = options || {};"<< endl
<< "}"<< endl
<< "MapTooltip.prototype = new GOverlay();"<< endl
<< "MapTooltip.prototype.initialize = function(map) {"<< endl
<< "var div = document.getElementById('MapTooltipContainer');"<< endl
<< "var that = this;"<< endl
<< "if (!div) {"<< endl
<< "var div = document.createElement('div');"<< endl
<< "div.setAttribute('id', 'MapTooltipContainer');"<< endl
<< "}"<< endl
<< "if (this.options.maxWidth || this.options.minWidth) {"<< endl
<< "div.style.maxWidth = this.options.maxWidth || '150px';"<< endl
<< "div.style.minWidth = this.options.minWidth || '150px';"<< endl
<< "} else {"<< endl
<< "div.style.width = this.options.width || '150px';"<< endl
<< "}"<< endl
<< "div.style.padding = this.options.padding || '5px';"<< endl
<< "div.style.backgroundColor = this.options.backgroundColor || '#ffffff';"<< endl
<< "div.style.border = this.options.border || '1px solid #000000';"<< endl
<< "div.style.fontSize = this.options.fontSize || '80%';"<< endl
<< "div.style.color = this.options.color || '#000';"<< endl
<< "div.innerHTML = this.html;"<< endl
<< "div.style.position = 'absolute';"<< endl
<< "div.style.zIndex = '1000';"<< endl
<< "var offsetX = this.options.offsetX || 10;"<< endl
<< "var offsetY = this.options.offsetY || 0;"<< endl
<< "var bounds = map.getBounds();"<< endl
<< "rightEdge = map.fromLatLngToDivPixel(bounds.getNorthEast()).x;"<< endl
<< "bottomEdge = map.fromLatLngToDivPixel(bounds.getSouthWest()).y;"<< endl
<< "var mapev = GEvent.addListener(map, 'mousemove', function(latlng) {"<< endl
<< "GEvent.removeListener(mapev);"<< endl
<< "var pixelPosX = (map.fromLatLngToDivPixel(latlng)).x + offsetX;"<< endl
<< "var pixelPosY = (map.fromLatLngToDivPixel(latlng)).y - offsetY;"<< endl
<< "div.style.left = pixelPosX + 'px';"<< endl
<< "div.style.top = pixelPosY + 'px';"<< endl
<< "map.getPane(G_MAP_FLOAT_PANE).appendChild(div);"<< endl
<< "if ( (pixelPosX + div.offsetWidth) > rightEdge ) {"<< endl
<< "div.style.left = (rightEdge - div.offsetWidth - 10) + 'px';"<< endl
<< "}"<< endl
<< "if ( (pixelPosY + div.offsetHeight) > bottomEdge ) {"<< endl
<< "div.style.top = (bottomEdge - div.offsetHeight - 10) + 'px';"<< endl
<< "}"<< endl
<< "});"<< endl
<< "this._map = map;"<< endl
<< "this._div = div;"<< endl
<< "}"<< endl
<< "MapTooltip.prototype.remove = function() {"<< endl
<< "if(this._div != null) {"<< endl
<< "this._div.parentNode.removeChild(this._div);"<< endl
<< "}"<< endl
<< "}"<< endl
<< "MapTooltip.prototype.redraw = function(force) {"<< endl
<< "}"<< endl
<< "</script> "<< endl;
return oss.str();
}

View File

@@ -1,93 +0,0 @@
/*
* Copyright (c) 2009 Greg Lonnon (greg.lonnon@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
*/
#ifndef _GC_GoogleMapControl_h
#define _GC_GoogleMapControl_h
#include <QWidget>
#include <QtWebKit>
#include <string>
#include <iostream>
#include <sstream>
#include <string>
#include "RideFile.h"
#include "MainWindow.h"
class QMouseEvent;
class RideItem;
class MainWindow;
class QColor;
class QVBoxLayout;
class QTabWidget;
class GoogleMapControl : public QWidget
{
Q_OBJECT
private:
MainWindow *main;
QVBoxLayout *layout;
QWebView *view;
MainWindow *parent;
GoogleMapControl(); // default ctor
int range;
std::string CreatePolyLine();
void CreateSubPolyLine(const std::vector<RideFilePoint> &points,
std::ostringstream &oss,
int avgPower);
std::string CreateMapToolTipJavaScript();
std::string CreateIntervalHtml(QHash<QString,RideMetricPtr> &metrics, QStringList &intervalMetrics,
QString &intervalName, bool useMetrics);
std::string CreateMarkers(RideItem *ride);
std::string CreateIntervalMarkers(RideItem *ride);
void loadRide();
std::string CreateMarker(int number, double lat, double lon, std::string &html);
// the web browser is loading a page, do NOT start another load
bool loadingPage;
// the ride has changed, load a new page
bool newRideToLoad;
QColor GetColor(int watts);
// a GPS normalized vectory of ride data points,
// when a GPS unit loses signal it seems to
// put a coordinate close to 180 into the data
std::vector<RideFilePoint> rideData;
// current ride CP
int rideCP;
// current HTML for the ride
std::ostringstream currentPage;
RideItem *current;
public slots:
void rideSelected();
private slots:
void loadStarted();
void loadFinished(bool);
protected:
void createHtml(RideItem *);
void resizeEvent(QResizeEvent *);
public:
GoogleMapControl(MainWindow *);
virtual ~GoogleMapControl() { }
};
#endif

View File

@@ -1,233 +0,0 @@
/*
* Copyright (c) 2010 Greg Lonnon (greg.lonnon@gmail.com) copied from
* TcxParser.cpp
* Copyright (c) 2008 Sean C. Rhea (srhea@srhea.net),
* J.T Conklin (jtc@acorntoolworks.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 <QString>
#include <QDebug>
#include "GpxParser.h"
#include "TimeUtils.h"
#include <math.h>
// use stc strtod to bypass Qt toDouble() issues
#include <stdlib.h>
GpxParser::GpxParser (RideFile* rideFile)
: rideFile(rideFile)
{
boost::shared_ptr<QSettings> settings = GetApplicationSettings();
isGarminSmartRecording = settings->value(GC_GARMIN_SMARTRECORD,Qt::Checked);
GarminHWM = settings->value(GC_GARMIN_HWMARK);
if (GarminHWM.isNull() || GarminHWM.toInt() == 0)
GarminHWM.setValue(25); // default to 25 seconds.
distance = 0;
lastLat = lastLon = 0;
alt =0;
lon = 0;
lat = 0;
firstTime = true;
metadata = false;
}
bool GpxParser::startElement( const QString&, const QString&,
const QString& qName,
const QXmlAttributes& qAttributes)
{
buffer.clear();
if(metadata)
return true;
if(qName == "metadata")
{
metadata = true;
}
else if(qName == "trkpt")
{
int i = qAttributes.index("lat");
if(i >= 0)
{
lat = qAttributes.value(i).toDouble();
}
else
{
lat = lastLat;
}
i = qAttributes.index("lon");
if( i >= 0)
{
lon = qAttributes.value(i).toDouble();
}
else
{
lon = lastLon;
}
}
return TRUE;
}
#define PI 3.14159265
inline double toRadians(double degrees)
{
return degrees * 2 * PI / 360;
}
bool
GpxParser::endElement( const QString&, const QString&, const QString& qName)
{
if(qName == "metadata")
{
metadata = false;
}
else if(metadata == true)
{
return true;
}
else if (qName == "time")
{
time = convertToLocalTime(buffer);
if(firstTime)
{
start_time = time;
rideFile->setStartTime(time);
firstTime = false;
}
}
else if (qName == "ele")
{
alt = buffer.toDouble(); // metric
}
else if (qName == "trkpt")
{
if(lastLon == 0)
{
// update the "lasts" and find the next point
last_time = time;
lastLon = lon;
lastLat = lat;
return TRUE;
}
// we need to figure out the distance by using the lon,lat
// using teh haversine formula
double r = 6371;
double dlat = toRadians(lat -lastLat); // convert to radians
double dlon = toRadians(lon - lastLon);
double a = sin(dlat /2) * sin(dlat/2) + cos(lat) * cos(lastLat) * sin(dlon/2) * sin(dlon /2);
double c = 2 * atan2(sqrt(a),sqrt(1-a));
double delta_d = r * c;
if(lastLat != 0)
distance += delta_d;
// compute the elapsed time and distance traveled since the
// last recorded trackpoint
double delta_t = last_time.secsTo(time);
if (delta_d<0)
{
delta_d=0;
}
// compute speed for this trackpoint by dividing the distance
// traveled by the elapsed time. The elapsed time will be 0.0
// for the first trackpoint -- so set speed to 0.0 instead of
// dividing by zero.
double speed = 0.0;
if (delta_t > 0.0)
{
speed=delta_d / delta_t * 3600.0;
}
// Time from beginning of activity
double secs = start_time.secsTo(time);
// Record trackpoint
// for smart recording, the delta_t will not be constant
// average all the calculations based on the previous
// point.
if(rideFile->dataPoints().empty()) {
// first point
rideFile->appendPoint(secs, 0, 0, distance,
speed, 0, 0, alt, lon, lat, 0, 0);
}
else {
// assumption that the change in ride is linear... :)
RideFilePoint *prevPoint = rideFile->dataPoints().back();
double deltaSecs = secs - prevPoint->secs;
double deltaDist = distance - prevPoint->km;
double deltaSpeed = speed - prevPoint->kph;
double deltaAlt = alt - prevPoint->alt;
double deltaLon = lon - prevPoint->lon;
double deltaLat = lat - prevPoint->lat;
// Smart Recording High Water Mark.
if ((isGarminSmartRecording.toInt() == 0) || (deltaSecs == 1) || (deltaSecs >= GarminHWM.toInt())) {
// no smart recording, or delta exceeds HW treshold, just insert the data
rideFile->appendPoint(secs, 0, 0, distance,
speed, 0,0, alt, lon, lat, 0, 0);
}
else {
// smart recording is on and delta is less than GarminHWM seconds.
for(int i = 1; i <= deltaSecs; i++) {
double weight = i/ deltaSecs;
double kph = prevPoint->kph + (deltaSpeed *weight);
// need to make sure speed goes to zero
kph = kph > 0.35 ? kph : 0;
double lat = prevPoint->lat + (deltaLat * weight);
double lon = prevPoint->lon + (deltaLon * weight);
rideFile->appendPoint(
prevPoint->secs + (deltaSecs * weight),
0,
0,
prevPoint->km + (deltaDist * weight),
kph,
0,
0,
prevPoint->alt + (deltaAlt * weight),
lon, // lon
lat, // lat
0,
0);
}
prevPoint = rideFile->dataPoints().back();
}
}
// update the "lasts" and find the next point
last_time = time;
lastLon = lon;
lastLat = lat;
}
return TRUE;
}
bool GpxParser::characters( const QString& str )
{
buffer += str;
return TRUE;
}

View File

@@ -1,68 +0,0 @@
/*
* Copyright (c) 2010 Greg Lonnon (greg.lonnon@gmail.com) copied from
* TcxParser.cpp
* Copyright (c) 2008 Sean C. Rhea (srhea@srhea.net),
* J.T Conklin (jtc@acorntoolworks.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
*/
#ifndef _TcxParser_h
#define _TcxParser_h
#include "RideFile.h"
#include <QString>
#include <QDateTime>
#include <QXmlDefaultHandler>
#include "Settings.h"
class GpxParser : public QXmlDefaultHandler
{
public:
GpxParser(RideFile* rideFile);
bool startElement( const QString&, const QString&, const QString&,
const QXmlAttributes& );
bool endElement( const QString&, const QString&, const QString& );
bool characters( const QString& );
private:
RideFile* rideFile;
QString buffer;
QVariant isGarminSmartRecording;
QVariant GarminHWM;
QDateTime start_time;
QDateTime last_time;
QDateTime time;
double distance;
double lastLat, lastLon;
double alt;
double lat;
double lon;
// set to false after the first time element is seen (not in metadata)
bool firstTime;
// throw away the metadata, it doesn't look useful
bool metadata;
};
#endif // _TcxParser_h

View File

@@ -1,44 +0,0 @@
/*
* Copyright (c) 2010 Greg Lonnon (greg.lonnon@gmail.com) copied from TcxRideFile.cpp
*
* Copyright (c) 2008 Sean C. Rhea (srhea@srhea.net),
* J.T Conklin (jtc@acorntoolworks.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 "GpxRideFile.h"
#include "GpxParser.h"
static int tcxFileReaderRegistered =
RideFileFactory::instance().registerReader(
"gpx", "GGPS Exchange format", new GpxFileReader());
RideFile *GpxFileReader::openRideFile(QFile &file, QStringList &errors) const
{
(void) errors;
RideFile *rideFile = new RideFile();
rideFile->setRecIntSecs(1.0);
rideFile->setDeviceType("GPS Exchange Format");
GpxParser handler(rideFile);
QXmlInputSource source (&file);
QXmlSimpleReader reader;
reader.setContentHandler (&handler);
reader.parse (source);
return rideFile;
}

View File

@@ -1,32 +0,0 @@
/*
* Copyright (c) 2010 Greg Lonnon (greg.lonnon@gmail.com)
*
* Copyright (c) 2008 Sean C. Rhea (srhea@srhea.net),
* J.T Conklin (jtc@acorntoolworks.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
*/
#ifndef _GpxRideFile_h
#define _GpxRideFile_h
#include "RideFile.h"
struct GpxFileReader : public RideFileReader {
virtual RideFile *openRideFile(QFile &file, QStringList &errors) const;
};
#endif // _GpxRideFile_h

View File

@@ -21,13 +21,9 @@
#include "PowerHist.h"
#include "RideFile.h"
#include "RideItem.h"
#include "Settings.h"
#include <QtGui>
#include <assert.h>
#include "Zones.h"
#include "HrZones.h"
HistogramWindow::HistogramWindow(MainWindow *mainWindow) :
QWidget(mainWindow), mainWindow(mainWindow)
{
@@ -53,20 +49,9 @@ HistogramWindow::HistogramWindow(MainWindow *mainWindow) :
withZerosCheckBox = new QCheckBox;
withZerosCheckBox->setText(tr("With zeros"));
binWidthLayout->addWidget(withZerosCheckBox);
histShadeZones = new QCheckBox;
histShadeZones->setText(tr("Shade zones"));
histShadeZones->setChecked(true);
binWidthLayout->addWidget(histShadeZones);
histParameterCombo = new QComboBox();
binWidthLayout->addWidget(histParameterCombo);
histSumY = new QComboBox();
histSumY->addItem(tr("Absolute Time"));
histSumY->addItem(tr("Percentage Time"));
binWidthLayout->addWidget(histSumY);
powerHist = new PowerHist(mainWindow);
setHistTextValidator();
@@ -88,29 +73,17 @@ HistogramWindow::HistogramWindow(MainWindow *mainWindow) :
this, SLOT(setWithZerosFromCheckBox()));
connect(histParameterCombo, SIGNAL(currentIndexChanged(int)),
this, SLOT(setHistSelection(int)));
connect(histShadeZones, SIGNAL(stateChanged(int)),
this, SLOT(setHistSelection(int)));
connect(histSumY, SIGNAL(currentIndexChanged(int)),
this, SLOT(setSumY(int)));
connect(mainWindow, SIGNAL(rideSelected()), this, SLOT(rideSelected()));
connect(mainWindow, SIGNAL(intervalSelected()), this, SLOT(intervalSelected()));
connect(mainWindow, SIGNAL(zonesChanged()), this, SLOT(zonesChanged()));
connect(mainWindow, SIGNAL(configChanged()), powerHist, SLOT(configChanged()));
}
void
HistogramWindow::rideSelected()
{
if (mainWindow->activeTab() != this)
return;
RideItem *ride = mainWindow->rideItem();
if (!ride)
return;
// get range that applies to this ride
powerRange = mainWindow->zones()->whichRange(ride->dateTime.date());
hrRange = mainWindow->hrZones()->whichRange(ride->dateTime.date());
// set the histogram data
powerHist->setData(ride);
// make sure the histogram has a legal selection
@@ -122,8 +95,6 @@ HistogramWindow::rideSelected()
void
HistogramWindow::intervalSelected()
{
if (mainWindow->activeTab() != this)
return;
RideItem *ride = mainWindow->rideItem();
if (!ride) return;
@@ -134,8 +105,6 @@ HistogramWindow::intervalSelected()
void
HistogramWindow::zonesChanged()
{
if (mainWindow->activeTab() != this)
return;
powerHist->refreshZoneLabels();
powerHist->replot();
}
@@ -156,13 +125,6 @@ HistogramWindow::setlnYHistFromCheckBox()
powerHist->setlnY(! powerHist->islnY());
}
void
HistogramWindow::setSumY(int index)
{
if (index < 0) return; // being destroyed
else powerHist->setSumY(index == 0);
}
void
HistogramWindow::setWithZerosFromCheckBox()
{
@@ -202,40 +164,22 @@ HistogramWindow::setHistTextValidator()
}
void
HistogramWindow::setHistSelection(int /*id*/)
HistogramWindow::setHistSelection(int id)
{
// Set shading first, since the dataseries selection
// below will trigger a redraw, and we need to have
// set the shading beforehand. OK, so we could make
// either change trigger it, but this makes for simpler
// code here and in powerhist.cpp
if (histShadeZones->isChecked()) powerHist->setShading(true);
else powerHist->setShading(false);
// lets get the selection since we are called from
// the checkbox and combobox signal
int id = histParameterCombo->currentIndex();
// Which data series are we plotting?
if (id == histWattsID)
powerHist->setSelection(PowerHist::watts);
else if (id == histWattsZoneID) // we can zone power!
powerHist->setSelection(PowerHist::wattsZone);
if (id == histWattsShadedID)
powerHist->setSelection(PowerHist::wattsShaded);
else if (id == histWattsUnshadedID)
powerHist->setSelection(PowerHist::wattsUnshaded);
else if (id == histNmID)
powerHist->setSelection(PowerHist::nm);
powerHist->setSelection(PowerHist::nm);
else if (id == histHrID)
powerHist->setSelection(PowerHist::hr);
else if (id == histHrZoneID) // we can zone HR!
powerHist->setSelection(PowerHist::hrZone);
powerHist->setSelection(PowerHist::hr);
else if (id == histKphID)
powerHist->setSelection(PowerHist::kph);
powerHist->setSelection(PowerHist::kph);
else if (id == histCadID)
powerHist->setSelection(PowerHist::cad);
powerHist->setSelection(PowerHist::cad);
else
powerHist->setSelection(PowerHist::watts);
// just in case it gets switch off...
if (!powerHist->islnY()) lnYHistCheckBox->setChecked(false);
fprintf(stderr, "Illegal id encountered: %d", id);
setHistBinWidthText();
setHistTextValidator();
@@ -264,22 +208,23 @@ HistogramWindow::setHistWidgets(RideItem *rideItem)
if (ride) {
// we want to retain the present selection
PowerHist::Selection s = powerHist->selection();
PowerHist::Selection s = powerHist->selection();
histParameterCombo->clear();
histWattsID = histWattsZoneID =
histNmID = histHrID = histHrZoneID =
histKphID = histCadID = -1;
histWattsShadedID =
histWattsUnshadedID =
histNmID =
histHrID =
histKphID =
histCadID =
-1;
if (ride->areDataPresent()->watts) {
histWattsID = count ++;
histParameterCombo->addItem(tr("Watts"));
if (powerRange != -1) {
histWattsZoneID = count ++;
histParameterCombo->addItem(tr("Watts (by Zone)"));
}
histWattsShadedID = count ++;
histParameterCombo->addItem(tr("Watts(shaded)"));
histWattsUnshadedID = count ++;
histParameterCombo->addItem(tr("Watts(unshaded)"));
}
if (ride->areDataPresent()->nm) {
histNmID = count ++;
@@ -288,10 +233,6 @@ HistogramWindow::setHistWidgets(RideItem *rideItem)
if (ride->areDataPresent()->hr) {
histHrID = count ++;
histParameterCombo->addItem(tr("Heartrate"));
if (hrRange != -1) {
histHrZoneID = count ++;
histParameterCombo->addItem(tr("Heartrate (by Zone)"));
}
}
if (ride->areDataPresent()->kph) {
histKphID = count ++;
@@ -309,16 +250,14 @@ HistogramWindow::setHistWidgets(RideItem *rideItem)
lnYHistCheckBox->setEnabled(true);
// set widget to proper value
if ((s == PowerHist::watts) && (histWattsID >= 0))
histParameterCombo->setCurrentIndex(histWattsID);
else if ((s == PowerHist::wattsZone) && (histWattsZoneID >= 0))
histParameterCombo->setCurrentIndex(histWattsZoneID);
if ((s == PowerHist::wattsShaded) && (histWattsShadedID >= 0))
histParameterCombo->setCurrentIndex(histWattsShadedID);
else if ((s == PowerHist::wattsUnshaded) && (histWattsUnshadedID >= 0))
histParameterCombo->setCurrentIndex(histWattsUnshadedID);
else if ((s == PowerHist::nm) && (histNmID >= 0))
histParameterCombo->setCurrentIndex(histNmID);
else if ((s == PowerHist::hr) && (histHrID >= 0))
histParameterCombo->setCurrentIndex(histHrID);
else if ((s == PowerHist::hrZone) && (histHrZoneID >= 0))
histParameterCombo->setCurrentIndex(histHrZoneID);
else if ((s == PowerHist::kph) && (histKphID >= 0))
histParameterCombo->setCurrentIndex(histKphID);
else if ((s == PowerHist::cad) && (histCadID >= 0))

View File

@@ -51,7 +51,6 @@ class HistogramWindow : public QWidget
void setlnYHistFromCheckBox();
void setWithZerosFromCheckBox();
void setHistSelection(int id);
void setSumY(int);
protected:
@@ -64,20 +63,15 @@ class HistogramWindow : public QWidget
QLineEdit *binWidthLineEdit;
QCheckBox *lnYHistCheckBox;
QCheckBox *withZerosCheckBox;
QCheckBox *histShadeZones;
QComboBox *histParameterCombo;
QComboBox *histSumY;
int histWattsID;
int histWattsZoneID;
int histWattsShadedID;
int histWattsUnshadedID;
int histNmID;
int histHrID;
int histHrZoneID;
int histKphID;
int histCadID;
int histAltID;
int powerRange, hrRange;
};
#endif // _GC_HistogramWindow_h

View File

@@ -1,222 +0,0 @@
/*
* Copyright (c) 2010 Damien Grauser (Damien.Grauser@pev-geneve.ch), 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 "RideMetric.h"
#include "BestIntervalDialog.h"
#include "HrZones.h"
#include <math.h>
#include <QApplication>
class HrZoneTime : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(HrZoneTime)
int level;
double seconds;
QList<int> lo;
QList<int> hi;
public:
HrZoneTime() : level(0), seconds(0.0)
{
setType(RideMetric::Total);
setMetricUnits("seconds");
setImperialUnits("seconds");
setPrecision(0);
setConversion(1.0);
}
void setLevel(int level) { this->level=level-1; } // zones start from zero not 1
void compute(const RideFile *ride, const Zones *, int, const HrZones *hrZone, int hrZoneRange,
const QHash<QString,RideMetric*> &)
{
seconds = 0;
// get zone ranges
if (hrZone && hrZoneRange >= 0) {
// iterate and compute
foreach(const RideFilePoint *point, ride->dataPoints()) {
if (hrZone->whichZone(hrZoneRange, point->hr) == level)
seconds += ride->recIntSecs();
}
}
setValue(seconds);
}
bool canAggregate() const { return false; }
void aggregateWith(const RideMetric &) {}
RideMetric *clone() const { return new HrZoneTime(*this); }
};
class HrZoneTime1 : public HrZoneTime {
Q_DECLARE_TR_FUNCTIONS(HrZoneTime1)
public:
HrZoneTime1()
{
setLevel(1);
setSymbol("time_in_zone_H1");
#ifdef ENABLE_METRICS_TRANSLATION
setInternalName("H1 Time in Zone");
}
void initialize () {
#endif
setName(tr("H1 Time in Zone"));
}
RideMetric *clone() const { return new HrZoneTime1(*this); }
};
class HrZoneTime2 : public HrZoneTime {
Q_DECLARE_TR_FUNCTIONS(HrZoneTime2)
public:
HrZoneTime2()
{
setLevel(2);
setSymbol("time_in_zone_H2");
#ifdef ENABLE_METRICS_TRANSLATION
setInternalName("H2 Time in Zone");
}
void initialize () {
#endif
setName(tr("H2 Time in Zone"));
}
RideMetric *clone() const { return new HrZoneTime2(*this); }
};
class HrZoneTime3 : public HrZoneTime {
Q_DECLARE_TR_FUNCTIONS(HrZoneTime3)
public:
HrZoneTime3()
{
setLevel(3);
setSymbol("time_in_zone_H3");
#ifdef ENABLE_METRICS_TRANSLATION
setInternalName("H3 Time in Zone");
}
void initialize () {
#endif
setName(tr("H3 Time in Zone"));
}
RideMetric *clone() const { return new HrZoneTime3(*this); }
};
class HrZoneTime4 : public HrZoneTime {
Q_DECLARE_TR_FUNCTIONS(HrZoneTime4)
public:
HrZoneTime4()
{
setLevel(4);
setSymbol("time_in_zone_H4");
#ifdef ENABLE_METRICS_TRANSLATION
setInternalName("H4 Time in Zone");
}
void initialize () {
#endif
setName(tr("H4 Time in Zone"));
}
RideMetric *clone() const { return new HrZoneTime4(*this); }
};
class HrZoneTime5 : public HrZoneTime {
Q_DECLARE_TR_FUNCTIONS(HrZoneTime5)
public:
HrZoneTime5()
{
setLevel(5);
setSymbol("time_in_zone_H5");
#ifdef ENABLE_METRICS_TRANSLATION
setInternalName("H5 Time in Zone");
}
void initialize () {
#endif
setName(tr("H5 Time in Zone"));
}
RideMetric *clone() const { return new HrZoneTime5(*this); }
};
class HrZoneTime6 : public HrZoneTime {
Q_DECLARE_TR_FUNCTIONS(HrZoneTime6)
public:
HrZoneTime6()
{
setLevel(6);
setSymbol("time_in_zone_H6");
#ifdef ENABLE_METRICS_TRANSLATION
setInternalName("H6 Time in Zone");
}
void initialize () {
#endif
setName(tr("H6 Time in Zone"));
}
RideMetric *clone() const { return new HrZoneTime6(*this); }
};
class HrZoneTime7 : public HrZoneTime {
Q_DECLARE_TR_FUNCTIONS(HrZoneTime7)
public:
HrZoneTime7()
{
setLevel(7);
setSymbol("time_in_zone_H7");
#ifdef ENABLE_METRICS_TRANSLATION
setInternalName("H7 Time in Zone");
}
void initialize () {
#endif
setName(tr("H7 Time in Zone"));
}
RideMetric *clone() const { return new HrZoneTime7(*this); }
};
class HrZoneTime8 : public HrZoneTime {
Q_DECLARE_TR_FUNCTIONS(HrZoneTime8)
public:
HrZoneTime8()
{
setLevel(8);
setSymbol("time_in_zone_H8");
#ifdef ENABLE_METRICS_TRANSLATION
setInternalName("H8 Time in Zone");
}
void initialize () {
#endif
setName(tr("H8 Time in Zone"));
}
RideMetric *clone() const { return new HrZoneTime8(*this); }
};
static bool addAllHrZones() {
RideMetricFactory::instance().addMetric(HrZoneTime1());
RideMetricFactory::instance().addMetric(HrZoneTime2());
RideMetricFactory::instance().addMetric(HrZoneTime3());
RideMetricFactory::instance().addMetric(HrZoneTime4());
RideMetricFactory::instance().addMetric(HrZoneTime5());
RideMetricFactory::instance().addMetric(HrZoneTime6());
RideMetricFactory::instance().addMetric(HrZoneTime7());
RideMetricFactory::instance().addMetric(HrZoneTime8());
return true;
}
static bool allHrZonesAdded = addAllHrZones();

View File

@@ -1,880 +0,0 @@
/*
* Copyright (c) 2010 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 <QMessageBox>
#include "HrZones.h"
#include "Colors.h"
#include "TimeUtils.h"
#include <QtGui>
#include <QtAlgorithms>
#include <qcolor.h>
#include <assert.h>
#include <math.h>
#include <boost/crc.hpp>
// the infinity endpoints are indicated with extreme date ranges
// but not zero dates so we can edit and compare them
static const QDate date_zero(1900, 01, 01);
static const QDate date_infinity(9999,12,31);
// initialize default static zone parameters
void HrZones::initializeZoneParameters()
{
static int initial_zone_default[] = {
0, 68, 83, 94, 105
};
static double initial_zone_default_trimp[] = {
0.9, 1.1, 1.2, 2.0, 5.0
};
static const QString initial_zone_default_desc[] = {
tr("Active Recovery"), tr("Endurance"), tr("Tempo"), tr("Threshold"),
tr("VO2Max")
};
static const char *initial_zone_default_name[] = {
"Z1", "Z2", "Z3", "Z4", "Z5"
};
static int initial_nzones_default =
sizeof(initial_zone_default) /
sizeof(initial_zone_default[0]);
scheme.zone_default.clear();
scheme.zone_default_is_pct.clear();
scheme.zone_default_desc.clear();
scheme.zone_default_name.clear();
scheme.zone_default_trimp.clear();
scheme.nzones_default = 0;
scheme.nzones_default = initial_nzones_default;
for (int z = 0; z < scheme.nzones_default; z ++) {
scheme.zone_default.append(initial_zone_default[z]);
scheme.zone_default_is_pct.append(true);
scheme.zone_default_name.append(QString(initial_zone_default_name[z]));
scheme.zone_default_desc.append(initial_zone_default_desc[z]);
scheme.zone_default_trimp.append(initial_zone_default_trimp[z]);
}
}
// read zone file, allowing for zones with or without end dates
bool HrZones::read(QFile &file)
{
defaults_from_user = false;
scheme.zone_default.clear();
scheme.zone_default_is_pct.clear();
scheme.zone_default_name.clear();
scheme.zone_default_desc.clear();
scheme.zone_default_trimp.clear();
scheme.nzones_default = 0;
ranges.clear();
// set up possible warning dialog
warning = QString();
int warning_lines = 0;
const int max_warning_lines = 100;
// macro to append lines to the warning
#define append_to_warning(s) \
if (warning_lines < max_warning_lines) \
warning.append(s); \
else if (warning_lines == max_warning_lines) \
warning.append("...\n"); \
warning_lines ++;
// read using text mode takes care of end-lines
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
err = "can't open file";
return false;
}
QTextStream fileStream(&file);
QRegExp commentrx("\\s*#.*$");
QRegExp blankrx("^[ \t]*$");
QRegExp rangerx[] = {
QRegExp("^\\s*(?:from\\s+)?" // optional "from"
"((\\d\\d\\d\\d)[-/](\\d{1,2})[-/](\\d{1,2})|BEGIN)" // begin date
"\\s*([,:]?\\s*(LT)\\s*=\\s*(\\d+))?" // optional {LT = integer (optional %)}
"\\s*([,:]?\\s*(RestHr)\\s*=\\s*(\\d+))?" // optional {RestHr = integer (optional %)}
"\\s*([,:]?\\s*(MaxHr)\\s*=\\s*(\\d+))?" // optional {MaxHr = integer (optional %)}
"\\s*:?\\s*$", // optional :
Qt::CaseInsensitive),
QRegExp("^\\s*(?:from\\s+)?" // optional "from"
"((\\d\\d\\d\\d)[-/](\\d{1,2})[-/](\\d{1,2})|BEGIN)" // begin date
"\\s+(?:until|to|-)\\s+" // until
"((\\d\\d\\d\\d)[-/](\\d{1,2})[-/](\\d{1,2})|END)?" // end date
"\\s*:?,?\\s*((LT)\\s*=\\s*(\\d+))?" // optional {LT = integer (optional %)}
"\\s*:?,?\\s*((RestHr)\\s*=\\s*(\\d+))?" // optional {RestHr = integer (optional %)}
"\\s*:?,?\\s*((MaxHr)\\s*=\\s*(\\d+))?" // optional {MaxHr = integer (optional %)}
"\\s*:?\\s*$", // optional :
Qt::CaseInsensitive)
};
QRegExp zonerx("^\\s*([^ ,][^,]*),\\s*([^ ,][^,]*),\\s*"
"(\\d+)\\s*(%?)\\s*(?:,\\s*(\\d+(\\.\\d+)?)\\s*)?$",
Qt::CaseInsensitive);//
QRegExp zonedefaultsx("^\\s*(?:zone)?\\s*defaults?\\s*:?\\s*$",
Qt::CaseInsensitive);
int lineno = 0;
// the current range in the file
// ZoneRange *range = NULL;
bool in_range = false;
QDate begin = date_zero, end = date_infinity;
int lt = 0;
int restHr = 0;
int maxHr = 0;
QList<HrZoneInfo> zoneInfos;
// true if zone defaults are found in the file (then we need to write them)
bool zones_are_defaults = false;
while (! fileStream.atEnd() ) {
++lineno;
QString line = fileStream.readLine();
int pos = commentrx.indexIn(line, 0);
if (pos != -1)
line = line.left(pos);
if (blankrx.indexIn(line, 0) == 0)
goto next_line;
// check for default zone range definition (may be followed by hr zone definitions)
if (zonedefaultsx.indexIn(line, 0) != -1) {
zones_are_defaults = true;
// defaults are allowed only at the beginning of the file
if (ranges.size()) {
err = "HR Zone defaults must be specified at head of hr.zones file";
return false;
}
// only one set of defaults is allowed
if (scheme.nzones_default) {
err = "Only one set of zone defaults may be specified in hr.zones file";
return false;
}
goto next_line;
}
// check for range specification (may be followed by zone definitions)
for (int r=0; r<2; r++) {
if (rangerx[r].indexIn(line, 0) != -1) {
if (in_range) {
// if zones are empty, then generate them
HrZoneRange range(begin, end, lt, restHr, maxHr);
range.zones = zoneInfos;
if (range.zones.empty()) {
if (range.lt > 0) setHrZonesFromLT(range);
else {
err = tr("line %1: read new range without reading "
"any zones for previous one").arg(lineno);
file.close();
return false;
}
} else {
qSort(range.zones);
}
ranges.append(range);
}
in_range = true;
zones_are_defaults = false;
zoneInfos.clear();
// process the beginning date
if (rangerx[r].cap(1) == "BEGIN")
begin = date_zero;
else {
begin = QDate(rangerx[r].cap(2).toInt(),
rangerx[r].cap(3).toInt(),
rangerx[r].cap(4).toInt());
}
// process an end date, if any, else it is null
if (rangerx[r].cap(5) == "END") end = date_infinity;
else if (rangerx[r].cap(6).toInt() || rangerx[r].cap(7).toInt() ||
rangerx[r].cap(8).toInt()) {
end = QDate(rangerx[r].cap(6).toInt(),
rangerx[r].cap(7).toInt(),
rangerx[r].cap(8).toInt());
} else {
end = QDate();
}
// set up the range, capturing LT if it's specified
// range = new ZoneRange(begin, end);
int nLT = (r ? 11 : 7);
if (rangerx[r].numCaptures() >= (nLT)) lt = rangerx[r].cap(nLT).toInt();
else lt = 0;
int nRestHr = (r ? 14 : 10);
if (rangerx[r].numCaptures() >= (nRestHr)) restHr = rangerx[r].cap(nRestHr).toInt();
else restHr = 0;
int nMaxHr = (r ? 17 : 13);
if (rangerx[r].numCaptures() >= (nRestHr)) maxHr = rangerx[r].cap(nMaxHr).toInt();
else maxHr = 0;
// bleck
goto next_line;
}
}
// check for zone definition
if (zonerx.indexIn(line, 0) != -1) {
if (! (in_range || zones_are_defaults)) {
err = tr("line %1: read zone without "
"preceeding date range").arg(lineno);
file.close();
return false;
}
int lo = zonerx.cap(3).toInt();
double trimp = zonerx.cap(5).toDouble();
// allow for zone specified as % of LT
bool lo_is_pct = false;
if (zonerx.cap(4) == "%") {
if (zones_are_defaults) lo_is_pct = true;
else if (lt > 0) lo = int(lo * lt / 100);
else {
err = tr("attempt to set zone based on % of "
"LT without setting LT in line number %1.\n").
arg(lineno);
file.close();
return false;
}
}
int hi = -1; // signal an undefined number
double tr = zonerx.cap(5).toDouble();
if (zones_are_defaults) {
scheme.nzones_default ++;
scheme.zone_default_is_pct.append(lo_is_pct);
scheme.zone_default.append(lo);
scheme.zone_default_name.append(zonerx.cap(1));
scheme.zone_default_desc.append(zonerx.cap(2));
scheme.zone_default_trimp.append(trimp);
defaults_from_user = true;
}
else {
HrZoneInfo zone(zonerx.cap(1), zonerx.cap(2), lo, hi, tr);
zoneInfos.append(zone);
}
}
next_line: {}
}
if (in_range) {
HrZoneRange range(begin, end, lt, restHr, maxHr);
range.zones = zoneInfos;
if (range.zones.empty()) {
if (range.lt > 0)
setHrZonesFromLT(range);
else {
err = tr("file ended without reading any zones for last range");
file.close();
return false;
}
}
else {
qSort(range.zones);
}
ranges.append(range);
}
file.close();
// sort the ranges
qSort(ranges);
// set the default zones if not in file
if (!scheme.nzones_default) {
// do we have a zone which is explicitly set?
for (int i=0; i<ranges.count(); i++) {
if (ranges[i].hrZonesSetFromLT == false) {
// set the defaults using this one!
scheme.nzones_default = ranges[i].zones.count();
for (int j=0; j<scheme.nzones_default; j++) {
scheme.zone_default.append(((double)ranges[i].zones[j].lo / (double)ranges[i].lt) * 100.00);
scheme.zone_default_is_pct.append(true);
scheme.zone_default_name.append(ranges[i].zones[j].name);
scheme.zone_default_desc.append(ranges[i].zones[j].desc);
scheme.zone_default_trimp.append(ranges[i].zones[j].trimp);
}
}
}
// still not set then reset to defaults as usual
if (!scheme.nzones_default) initializeZoneParameters();
}
// resolve undefined endpoints in ranges and zones
for (int nr = 0; nr < ranges.size(); nr ++) {
// clean up gaps or overlaps in zone ranges
if (ranges[nr].end.isNull())
ranges[nr].end =
(nr < ranges.size() - 1) ?
ranges[nr + 1].begin :
date_infinity;
else if ((nr < ranges.size() - 1) &&
(ranges[nr + 1].begin != ranges[nr].end)) {
append_to_warning(tr("Setting end date of range %1 "
"to start date of range %2.\n").
arg(nr + 1).
arg(nr + 2)
);
ranges[nr].end = ranges[nr + 1].begin;
}
else if ((nr == ranges.size() - 1) &&
(ranges[nr].end < QDate::currentDate())) {
append_to_warning(tr("Extending final range %1 to infinite "
"to include present date.\n").arg(nr + 1));
ranges[nr].end = date_infinity;
}
if (ranges[nr].lt <= 0) {
err = tr("LT must be greater than zero in zone "
"range %1 of hr.zones").arg(nr + 1);
return false;
}
if (ranges[nr].zones.size()) {
// check that the first zone starts with zero
ranges[nr].zones[0].lo = 0;
// resolve zone end powers
for (int nz = 0; nz < ranges[nr].zones.size(); nz ++) {
if (ranges[nr].zones[nz].hi == -1)
ranges[nr].zones[nz].hi =
(nz < ranges[nr].zones.size() - 1) ?
ranges[nr].zones[nz + 1].lo :
INT_MAX;
else if ((nz < ranges[nr].zones.size() - 1) &&
(ranges[nr].zones[nz].hi != ranges[nr].zones[nz + 1].lo)) {
if (abs(ranges[nr].zones[nz].hi - ranges[nr].zones[nz + 1].lo) > 4) {
append_to_warning(tr("Range %1: matching top of zone %2 "
"(%3) to bottom of zone %4 (%5).\n").
arg(nr + 1).
arg(ranges[nr].zones[nz].name).
arg(ranges[nr].zones[nz].hi).
arg(ranges[nr].zones[nz + 1].name).
arg(ranges[nr].zones[nz + 1].lo)
);
}
ranges[nr].zones[nz].hi = ranges[nr].zones[nz + 1].lo;
} else if ((nz == ranges[nr].zones.size() - 1) &&
(ranges[nr].zones[nz].hi < INT_MAX)) {
append_to_warning(tr("Range %1: setting top of zone %2 from %3 to MAX.\n").
arg(nr + 1).
arg(ranges[nr].zones[nz].name).
arg(ranges[nr].zones[nz].hi)
);
ranges[nr].zones[nz].hi = INT_MAX;
}
}
}
}
// mark zones as modified so pages which depend on zones can be updated
modificationTime = QDateTime::currentDateTime();
return true;
}
// note empty dates are treated as automatic matches for begin or
// end of range
int HrZones::whichRange(const QDate &date) const
{
for (int rnum = 0; rnum < ranges.size(); ++rnum) {
const HrZoneRange &range = ranges[rnum];
if (((date >= range.begin) || (range.begin.isNull())) &&
((date < range.end) || (range.end.isNull())))
return rnum;
}
return -1;
}
int HrZones::numZones(int rnum) const
{
assert(rnum < ranges.size());
return ranges[rnum].zones.size();
}
int HrZones::whichZone(int rnum, double value) const
{
assert(rnum < ranges.size());
const HrZoneRange &range = ranges[rnum];
for (int j = 0; j < range.zones.size(); ++j) {
const HrZoneInfo &info = range.zones[j];
// note: the "end" of range is actually in the next zone
if ((value >= info.lo) && (value < info.hi))
return j;
}
// if we got here either it is negative, nan, inf or way high
if (value < 0 || isnan(value)) return 0;
else return range.zones.size()-1;
}
void HrZones::zoneInfo(int rnum, int znum,
QString &name, QString &description,
int &low, int &high, double &trimp) const
{
assert(rnum < ranges.size());
const HrZoneRange &range = ranges[rnum];
assert(znum < range.zones.size());
const HrZoneInfo &zone = range.zones[znum];
name = zone.name;
description = zone.desc;
low = zone.lo;
high = zone.hi;
trimp= zone.trimp;
}
int HrZones::getLT(int rnum) const
{
assert(rnum < ranges.size());
return ranges[rnum].lt;
}
void HrZones::setLT(int rnum, int lt)
{
ranges[rnum].lt = lt;
modificationTime = QDateTime::currentDateTime();
}
// generate a list of zones from LT
int HrZones::lowsFromLT(QList <int> *lows, int lt) const {
lows->clear();
for (int z = 0; z < scheme.nzones_default; z++)
lows->append(scheme.zone_default_is_pct[z] ?
scheme.zone_default[z] * lt / 100 : scheme.zone_default[z]);
return scheme.nzones_default;
}
int HrZones::getRestHr(int rnum) const
{
assert(rnum < ranges.size());
return ranges[rnum].restHr;
}
void HrZones::setRestHr(int rnum, int restHr)
{
ranges[rnum].restHr = restHr;
modificationTime = QDateTime::currentDateTime();
}
int HrZones::getMaxHr(int rnum) const
{
assert(rnum < ranges.size());
return ranges[rnum].maxHr;
}
void HrZones::setMaxHr(int rnum, int maxHr)
{
ranges[rnum].maxHr = maxHr;
modificationTime = QDateTime::currentDateTime();
}
// access the zone name
QString HrZones::getDefaultZoneName(int z) const {
return scheme.zone_default_name[z];
}
// access the zone description
QString HrZones::getDefaultZoneDesc(int z) const {
return scheme.zone_default_desc[z];
}
// set the zones from the LT value (the cp variable)
void HrZones::setHrZonesFromLT(HrZoneRange &range) {
range.zones.clear();
if (scheme.nzones_default == 0)
initializeZoneParameters();
for (int i = 0; i < scheme.nzones_default; i++) {
int lo = scheme.zone_default_is_pct[i] ? scheme.zone_default[i] * range.lt / 100 : scheme.zone_default[i];
int hi = lo;
double trimp = scheme.zone_default_trimp[i];
HrZoneInfo zone(scheme.zone_default_name[i], scheme.zone_default_desc[i], lo, hi, trimp);
range.zones.append(zone);
}
// sort the zones (some may be pct, others absolute, so zones need to be sorted,
// rather than the defaults
qSort(range.zones);
// set zone end dates
for (int i = 0; i < range.zones.size(); i ++)
range.zones[i].hi =
(i < scheme.nzones_default - 1) ?
range.zones[i + 1].lo :
INT_MAX;
// mark that the zones were set from LT, so if zones are subsequently
// written, only LT is saved
range.hrZonesSetFromLT = true;
}
void HrZones::setHrZonesFromLT(int rnum) {
assert((rnum >= 0) && (rnum < ranges.size()));
setHrZonesFromLT(ranges[rnum]);
}
// return the list of starting values of zones for a given range
QList <int> HrZones::getZoneLows(int rnum) const {
if (rnum >= ranges.size())
return QList <int>();
const HrZoneRange &range = ranges[rnum];
QList <int> return_values;
for (int i = 0; i < range.zones.size(); i ++)
return_values.append(ranges[rnum].zones[i].lo);
return return_values;
}
// return the list of ending values of zones for a given range
QList <int> HrZones::getZoneHighs(int rnum) const {
if (rnum >= ranges.size())
return QList <int>();
const HrZoneRange &range = ranges[rnum];
QList <int> return_values;
for (int i = 0; i < range.zones.size(); i ++)
return_values.append(ranges[rnum].zones[i].hi);
return return_values;
}
// return the list of zone names
QList <QString> HrZones::getZoneNames(int rnum) const {
if (rnum >= ranges.size())
return QList <QString>();
const HrZoneRange &range = ranges[rnum];
QList <QString> return_values;
for (int i = 0; i < range.zones.size(); i ++)
return_values.append(ranges[rnum].zones[i].name);
return return_values;
}
// return the list of zone trimp coef
QList <double> HrZones::getZoneTrimps(int rnum) const {
if (rnum >= ranges.size())
return QList <double>();
const HrZoneRange &range = ranges[rnum];
QList <double> return_values;
for (int i = 0; i < range.zones.size(); i ++)
return_values.append(ranges[rnum].zones[i].trimp);
return return_values;
}
QString HrZones::summarize(int rnum, QVector<double> &time_in_zone) const
{
assert(rnum < ranges.size());
const HrZoneRange &range = ranges[rnum];
assert(time_in_zone.size() == range.zones.size());
QString summary;
if(range.lt > 0){
summary += "<table align=\"center\" width=\"70%\" border=\"0\">";
summary += "<tr><td align=\"center\">";
summary += tr("Threshold (bpm): %1").arg(range.lt);
summary += "</td></tr></table>";
}
summary += "<table align=\"center\" width=\"70%\" ";
summary += "border=\"0\">";
summary += "<tr>";
summary += tr("<td align=\"center\">Zone</td>");
summary += tr("<td align=\"center\">Description</td>");
summary += tr("<td align=\"center\">Low (bpm)</td>");
summary += tr("<td align=\"center\">High (bpm)</td>");
summary += tr("<td align=\"center\">Time</td>");
summary += tr("<td align=\"center\">%</td>");
summary += "</tr>";
QColor color = QApplication::palette().alternateBase().color();
color = QColor::fromHsv(color.hue(), color.saturation() * 2, color.value());
double duration = 0;
foreach(double v, time_in_zone) { duration += v; }
for (int zone = 0; zone < time_in_zone.size(); ++zone) {
if (time_in_zone[zone] > 0.0) {
QString name, desc;
int lo, hi;
double trimp;
zoneInfo(rnum, zone, name, desc, lo, hi, trimp);
if (zone % 2 == 0)
summary += "<tr bgcolor='" + color.name() + "'>";
else
summary += "<tr>";
summary += QString("<td align=\"center\">%1</td>").arg(name);
summary += QString("<td align=\"center\">%1</td>").arg(desc);
summary += QString("<td align=\"center\">%1</td>").arg(lo);
if (hi == INT_MAX)
summary += "<td align=\"center\">MAX</td>";
else
summary += QString("<td align=\"center\">%1</td>").arg(hi);
summary += QString("<td align=\"center\">%1</td>")
.arg(time_to_string((unsigned) round(time_in_zone[zone])));
summary += QString("<td align=\"center\">%1</td>")
.arg((double)time_in_zone[zone]/duration * 100, 0, 'f', 0);
summary += "</tr>";
}
}
summary += "</table>";
return summary;
}
#define USE_SHORT_POWER_ZONES_FORMAT true /* whether a less redundent format should be used */
void HrZones::write(QDir home)
{
QString strzones;
// always write the defaults (config pane can adjust)
strzones += QString("DEFAULTS:\n");
for (int z = 0 ; z < scheme.nzones_default; z ++)
strzones += QString("%1,%2,%3%4,%5\n").
arg(scheme.zone_default_name[z]).
arg(scheme.zone_default_desc[z]).
arg(scheme.zone_default[z]).
arg(scheme.zone_default_is_pct[z]?"%":"").
arg(scheme.zone_default_trimp[z]);
strzones += QString("\n");
for (int i = 0; i < ranges.size(); i++) {
int lt = getLT(i);
int restHr = getRestHr(i);
int maxHr = getMaxHr(i);
// print header for range
// note this explicitly sets the first and last ranges such that all time is spanned
// note: BEGIN is not needed anymore
// since it becomes Jan 01 1900
strzones += QString("%1: LT=%2, RestHr=%3, MaxHr=%4").arg(getStartDate(i).toString("yyyy/MM/dd")).arg(lt).arg(restHr).arg(maxHr);
strzones += QString("\n");
// step through and print the zones if they've been explicitly set
if (! ranges[i].hrZonesSetFromLT) {
for (int j = 0; j < ranges[i].zones.size(); j ++) {
const HrZoneInfo &zi = ranges[i].zones[j];
strzones += QString("%1,%2,%3,%4\n").arg(zi.name).arg(zi.desc).arg(zi.lo).arg(zi.trimp);
}
strzones += QString("\n");
}
}
QFile file(home.absolutePath() + "/hr.zones");
if (file.open(QFile::WriteOnly))
{
QTextStream stream(&file);
stream << strzones;
file.close();
}
}
void HrZones::addHrZoneRange(QDate _start, QDate _end, int _lt, int _restHr, int _maxHr)
{
ranges.append(HrZoneRange(_start, _end, _lt, _restHr, _maxHr));
}
// insert a new zone range using the current scheme
// return the range number
int HrZones::addHrZoneRange(QDate _start, int _lt, int _restHr, int _maxHr)
{
int rnum;
// where to add this range?
for(rnum=0; rnum < ranges.count(); rnum++) if (ranges[rnum].begin > _start) break;
// at the end ?
if (rnum == ranges.count()) ranges.append(HrZoneRange(_start, date_infinity, _lt, _restHr, _maxHr));
else ranges.insert(rnum, HrZoneRange(_start, ranges[rnum].begin, _lt, _restHr, _maxHr));
// modify previous end date
if (rnum) ranges[rnum-1].end = _start;
// set zones from LT
if (_lt > 0) {
setLT(rnum, _lt);
setHrZonesFromLT(rnum);
}
return rnum;
}
void HrZones::addHrZoneRange()
{
ranges.append(HrZoneRange(date_zero, date_infinity));
}
void HrZones::setEndDate(int rnum, QDate endDate)
{
ranges[rnum].end = endDate;
modificationTime = QDateTime::currentDateTime();
}
void HrZones::setStartDate(int rnum, QDate startDate)
{
ranges[rnum].begin = startDate;
modificationTime = QDateTime::currentDateTime();
}
QDate HrZones::getStartDate(int rnum) const
{
assert(rnum >= 0);
return ranges[rnum].begin;
}
QString HrZones::getStartDateString(int rnum) const
{
assert(rnum >= 0);
QDate d = ranges[rnum].begin;
return (d.isNull() ? "BEGIN" : d.toString());
}
QDate HrZones::getEndDate(int rnum) const
{
assert(rnum >= 0);
return ranges[rnum].end;
}
QString HrZones::getEndDateString(int rnum) const
{
assert(rnum >= 0);
QDate d = ranges[rnum].end;
return (d.isNull() ? "END" : d.toString());
}
int HrZones::getRangeSize() const
{
return ranges.size();
}
// generate a zone color with a specific number of zones
QColor hrZoneColor(int z, int) {
switch(z) {
case 0 : return GColor(CHZONE1); break;
case 1 : return GColor(CHZONE2); break;
case 2 : return GColor(CHZONE3); break;
case 3 : return GColor(CHZONE4); break;
case 4 : return GColor(CHZONE5); break;
case 5 : return GColor(CHZONE6); break;
case 6 : return GColor(CHZONE7); break;
case 7 : return GColor(CHZONE8); break;
case 8 : return GColor(CHZONE9); break;
case 9 : return GColor(CHZONE10); break;
default: return QColor(128,128,128); break;
}
}
// delete a range, extend an adjacent (prior if available, otherwise next)
// range to cover the same time period, then return the number of the new range
// covering the date range of the deleted range or -1 if none left
int HrZones::deleteRange(int rnum) {
// check bounds - silently fail, don't assert
assert (rnum < ranges.count() && rnum >= 0);
// extend the previous range to the end of this range
// but only if we have a previous range
if (rnum > 0) setEndDate(rnum-1, getEndDate(rnum));
// delete this range then
ranges.removeAt(rnum);
return rnum-1;
}
// insert a new range starting at the given date extending to the end of the zone currently
// containing that date. If the start date of that zone is prior to the specified start
// date, then that zone range is shorted.
int HrZones::insertRangeAtDate(QDate date, int lt) {
assert(date.isValid());
int rnum;
if (ranges.empty()) {
addHrZoneRange();
rnum = 0;
}
else {
rnum = whichRange(date);
assert(rnum >= 0);
QDate date1 = getStartDate(rnum);
// if the old range has dates before the specified, then truncate
// the old range and shift up the existing ranges
if (date > date1) {
QDate endDate = getEndDate(rnum);
setEndDate(rnum, date);
ranges.insert(++ rnum, HrZoneRange(date, endDate));
}
}
if (lt > 0) {
setLT(rnum, lt);
setHrZonesFromLT(rnum);
}
return rnum;
}
unsigned long
HrZones::getFingerprint() const
{
boost::crc_optimal<16, 0x1021, 0xFFFF, 0, false, false> CRC;
for (int i=0; i<ranges.size(); i++) {
// from
int x = ranges[i].begin.toJulianDay();
CRC.process_bytes(&x, sizeof(int));
// to
x = ranges[i].end.toJulianDay();
CRC.process_bytes(&x, sizeof(int));
// CP
x = ranges[i].lt;
CRC.process_bytes(&x, sizeof(int));
// each zone definition (manual edit/default changed)
for (int j=0; j<ranges[i].zones.count(); j++) {
x = ranges[i].zones[j].lo;
CRC.process_bytes(&x, sizeof(int));
}
}
return CRC.checksum();
}

View File

@@ -1,212 +0,0 @@
/*
* Copyright (c) 2010 Damien Grauser (Damien.Grauser@pev-geneve.ch), Sean C. Rhea (srhea@srhea.net)
*
* 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
*/
#ifndef _HrZones_h
#define _HrZones_h
#include <QtCore>
// A zone "scheme" defines how power zones
// are calculated as a percentage of LT
// The default is to use Coggan percentages
// but this can be overriden in hr.zones
struct HrZoneScheme {
QList <int> zone_default;
QList <bool> zone_default_is_pct;
QList <QString> zone_default_name;
QList <QString> zone_default_desc;
QList <double> zone_default_trimp;
int nzones_default;
};
// A zone "info" defines a *single zone*
// in absolute watts terms e.g.
// "L4" "Threshold"
struct HrZoneInfo {
QString name, desc;
int lo, hi;
double trimp;
HrZoneInfo(const QString &n, const QString &d, int l, int h, double t) :
name(n), desc(d), lo(l), hi(h), trimp(t) {}
// used by qSort()
bool operator< (HrZoneInfo right) const {
return ((lo < right.lo) || ((lo == right.lo) && (hi < right.hi)));
}
};
// A zone "range" defines the power zones
// that are active for a *specific date period*
// e.g. between 01/01/2008 and 01/04/2008
// my LT was 170 and I chose to setup
// 5 zoneinfos from Active Recovery through
// to VO2Max
struct HrZoneRange {
QDate begin, end;
int lt;
int restHr;
int maxHr;
QList<HrZoneInfo> zones;
bool hrZonesSetFromLT;
HrZoneRange(const QDate &b, const QDate &e) :
begin(b), end(e), lt(0), hrZonesSetFromLT(false) {}
HrZoneRange(const QDate &b, const QDate &e, int _lt, int _restHr, int _maxHr) :
begin(b), end(e), lt(_lt), restHr(_restHr), maxHr(_maxHr), hrZonesSetFromLT(false) {}
// used by qSort()
bool operator< (HrZoneRange right) const {
return (((! right.begin.isNull()) &&
(begin.isNull() || begin < right.begin )) ||
((begin == right.begin) && (! end.isNull()) &&
( right.end.isNull() || end < right.end )));
}
};
class HrZones : public QObject
{
Q_OBJECT
private:
// Scheme
bool defaults_from_user;
HrZoneScheme scheme;
// LT History
QList<HrZoneRange> ranges;
// utility
QString err, warning;
void setHrZonesFromLT(HrZoneRange &range);
public:
HrZones() : defaults_from_user(false) {
initializeZoneParameters();
}
//
// Zone settings - Scheme (& default scheme)
//
HrZoneScheme getScheme() const { return scheme; }
void setScheme(HrZoneScheme x) { scheme = x; }
// get defaults from the current scheme
QString getDefaultZoneName(int z) const;
QString getDefaultZoneDesc(int z) const;
// set zone parameters to either user-specified defaults
// or to defaults using Coggan's coefficients
void initializeZoneParameters();
//
// Zone history - Ranges
//
// How many ranges in our history
int getRangeSize() const;
// Add ranges
void addHrZoneRange(QDate _start, QDate _end, int _lt, int _restHr, int _maxHr);
int addHrZoneRange(QDate _start, int _lt, int _restHr, int _maxHr);
void addHrZoneRange();
// insert a range from the given date to the end date of the range
// presently including the date
int insertRangeAtDate(QDate date, int lt = 0);
// Get / Set ZoneRange details
HrZoneRange getHrZoneRange(int rnum) { return ranges[rnum]; }
void setHrZoneRange(int rnum, HrZoneRange x) { ranges[rnum] = x; }
// get and set LT for a given range
int getLT(int rnum) const;
void setLT(int rnum, int cp);
// get and set Rest Hr for a given range
int getRestHr(int rnum) const;
void setRestHr(int rnum, int restHr);
// get and set LT for a given range
int getMaxHr(int rnum) const;
void setMaxHr(int rnum, int maxHr);
// calculate and then set zoneinfo for a given range
void setHrZonesFromLT(int rnum);
// delete the range rnum, and adjust dates on adjacent zone; return
// the range number of the range extended to cover the deleted zone
int deleteRange(const int rnum);
//
// read and write hr.zones
//
bool read(QFile &file);
void write(QDir home);
const QString &errorString() const { return err; }
const QString &warningString() const { return warning; }
//
// Typical APIs to get details of ranges and zones
//
// which range is active for a particular date
int whichRange(const QDate &date) const;
// which zone is the power value in for a given range
int whichZone(int range, double value) const;
// how many zones are there for a given range
int numZones(int range) const;
// get zoneinfo for a given range and zone
void zoneInfo(int range, int zone,
QString &name, QString &description,
int &low, int &high, double &trimp) const;
QString summarize(int rnum, QVector<double> &time_in_zone) const;
// get all highs/lows for zones (plot shading uses these)
int lowsFromLT(QList <int> *lows, int LT) const;
QList <int> getZoneLows(int rnum) const;
QList <int> getZoneHighs(int rnum) const;
QList <double> getZoneTrimps(int rnum) const;
QList <QString> getZoneNames(int rnum) const;
// get/set range start and end date
QDate getStartDate(int rnum) const;
QDate getEndDate(int rnum) const;
QString getStartDateString(int rnum) const;
QString getEndDateString(int rnum) const;
void setEndDate(int rnum, QDate date);
void setStartDate(int rnum, QDate date);
// When was this last updated?
QDateTime modificationTime;
// calculate a CRC for the zones data - used to see if zones
// data is changed since last referenced in Metric code
// could also be used in Configuration pages (later)
unsigned long getFingerprint() const;
};
QColor hrZoneColor(int zone, int num_zones);
#endif // _HrZones_h

View File

@@ -1,38 +0,0 @@
/*
* Copyright (c) 2009 Sean C. Rhea (srhea@srhea.net)
*
* 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
*/
#ifndef _JsonRideFile_h
#define _JsonRideFile_h
#include "RideFile.h"
#include <stdio.h>
#include <algorithm> // for std::sort
#include <QDomDocument>
#include <QVector>
#include <assert.h>
#include <QDebug>
#define DATETIME_FORMAT "yyyy/MM/dd hh:mm:ss' UTC'"
struct JsonFileReader : public RideFileReader {
virtual RideFile *openRideFile(QFile &file, QStringList &errors) const;
virtual void writeRideFile(const RideFile *ride, QFile &file) const;
};
#endif // _JsonRideFile_h

View File

@@ -1,73 +0,0 @@
%{
/*
* 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 "JsonRideFile.h"
// we use stdio for reading from FILE *JsonRideFilein
// because thats what lex likes to do, and since we're
// reading files that seems ok anyway
#include <stdio.h>
// The parser defines the token values for us
// so lets include them before declaring the
// token patterns
#include "JsonRideFile_yacc.h"/* generated by the scanner */
// the options below tell flex to no bother with
// yywrap since we only ever read a single file
// anyway. And yyunput() isn't needed for our
// parser, we read in one pass with no swanky
// interactions
%}
%option noyywrap
%option nounput
%%
\"RIDE\" return RIDE;
\"STARTTIME\" return STARTTIME;
\"RECINTSECS\" return RECINTSECS;
\"DEVICETYPE\" return DEVICETYPE;
\"IDENTIFIER\" return IDENTIFIER;
\"OVERRIDES\" return OVERRIDES;
\"TAGS\" return TAGS;
\"INTERVALS\" return INTERVALS;
\"NAME\" return NAME;
\"START\" return START;
\"STOP\" return STOP;
\"SAMPLES\" return SAMPLES;
\"SECS\" return SECS;
\"KM\" return KM;
\"WATTS\" return WATTS;
\"NM\" return NM;
\"CAD\" return CAD;
\"KPH\" return KPH;
\"HR\" return HR;
\"ALT\" return ALTITUDE; // ALT clashes with qtnamespace.h:46
\"LAT\" return LAT;
\"LON\" return LON;
\"HEADWIND\" return HEADWIND;
\"SLOPE\" return SLOPE;
\"TEMP\" return TEMP;
[-+]?[0-9]+ return INTEGER;
[-+]?[0-9]+e-[0-9]+ return FLOAT;
[-+]?[0-9]+\.[-e0-9]* return FLOAT;
\"([^\"]|\\\")*\" return STRING; /* contains non-quotes or escaped-quotes */
[ \n\t\r] ; /* we just ignore whitespace */
. return JsonRideFiletext[0]; /* any other character, typically :, { or } */
%%

View File

@@ -1,417 +0,0 @@
%{
/*
* 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
*/
// This grammar should work with yacc and bison, but has
// only been tested with bison. In addition, since qmake
// uses the -p flag to rename all the yy functions to
// enable multiple grammars in a single executable you
// should make sure you use the very latest bison since it
// has been known to be problematic in the past. It is
// know to work well with bison v2.4.1.
//
// To make the grammar readable I have placed the code
// for each nterm at column 40, this source file is best
// edited / viewed in an editor which is at least 120
// columns wide (e.g. vi in xterm of 120x40)
//
//
// The grammar is specific to the RideFile format serialised
// in writeRideFile below, this is NOT a generic json parser.
#include "JsonRideFile.h"
// Set during parser processing, using same
// naming conventions as yacc/lex -p
static RideFile *JsonRide;
// term state data is held in these variables
static RideFilePoint JsonPoint;
static RideFileInterval JsonInterval;
static QString JsonString,
JsonTagKey, JsonTagValue,
JsonOverName, JsonOverKey, JsonOverValue;
static double JsonNumber;
static QStringList JsonRideFileerrors;
static QMap <QString, QString> JsonOverrides;
// Standard yacc/lex variables / functions
extern int JsonRideFilelex(); // the lexer aka yylex()
extern void JsonRideFilerestart (FILE *input_file); // the lexer file restart aka yyrestart()
extern FILE *JsonRideFilein; // used by the lexer aka yyin
extern char *JsonRideFiletext; // set by the lexer aka yytext
void JsonRideFileerror(const char *error) // used by parser aka yyerror()
{ JsonRideFileerrors << error; }
//
// Utility functions
//
// Escape special characters (JSON compliance)
static QString protect(const QString string)
{
QString s = string;
s.replace("\\", "\\\\"); // backslash
s.replace("\"", "\\\""); // quote
s.replace("\t", "\\t"); // tab
s.replace("\n", "\\n"); // newline
s.replace("\r", "\\r"); // carriage-return
s.replace("\b", "\\b"); // backspace
s.replace("\f", "\\f"); // formfeed
s.replace("/", "\\/"); // solidus
return s;
}
// Un-Escape special characters (JSON compliance)
static QString unprotect(const QString string)
{
QString string2 = QString::fromLocal8Bit(string.toAscii().data());
// this is a lexer string so it will be enclosed
// in quotes. Lets strip those first
QString s = string2.mid(1,string2.length()-2);
// now un-escape the control characters
s.replace("\\t", "\t"); // tab
s.replace("\\n", "\n"); // newline
s.replace("\\r", "\r"); // carriage-return
s.replace("\\b", "\b"); // backspace
s.replace("\\f", "\f"); // formfeed
s.replace("\\/", "/"); // solidus
s.replace("\\\"", "\""); // quote
s.replace("\\\\", "\\"); // backslash
return s;
}
%}
%token STRING INTEGER FLOAT
%token RIDE STARTTIME RECINTSECS DEVICETYPE IDENTIFIER
%token OVERRIDES
%token TAGS INTERVALS NAME START STOP
%token SAMPLES SECS KM WATTS NM CAD KPH HR ALTITUDE LAT LON HEADWIND SLOPE TEMP
%start document
%%
/* We allow a .json file to be encapsulated within optional braces */
document: '{' ride_list '}'
| ride_list
;
/* multiple rides in a single file are supported, rides will be joined */
ride_list:
ride
| ride_list ',' ride
;
ride: RIDE ':' '{' rideelement_list '}' ;
rideelement_list: rideelement_list ',' rideelement
| rideelement
;
rideelement: starttime
| recordint
| devicetype
| identifier
| overrides
| tags
| intervals
| samples
;
/*
* First class variables
*/
starttime: STARTTIME ':' string {
QDateTime aslocal = QDateTime::fromString(JsonString, DATETIME_FORMAT);
QDateTime asUTC = QDateTime(aslocal.date(), aslocal.time(), Qt::UTC);
JsonRide->setStartTime(asUTC.toLocalTime());
}
recordint: RECINTSECS ':' number { JsonRide->setRecIntSecs(JsonNumber); }
devicetype: DEVICETYPE ':' string { JsonRide->setDeviceType(JsonString); }
identifier: IDENTIFIER ':' string { /* not supported in v2.1 */ }
/*
* Metric Overrides
*/
overrides: OVERRIDES ':' '[' overrides_list ']' ;
overrides_list: override | overrides_list ',' override ;
override: '{' override_name ':' override_values '}' { JsonRide->metricOverrides.insert(JsonOverName, JsonOverrides);
JsonOverrides.clear();
}
override_name: string { JsonOverName = JsonString; }
override_values: '{' override_value_list '}';
override_value_list: override_value | override_value_list ',' override_value ;
override_value: override_key ':' override_value { JsonOverrides.insert(JsonOverKey, JsonOverValue); }
override_key : string { JsonOverKey = JsonString; }
override_value : string { JsonOverValue = JsonString; }
/*
* Ride metadata tags
*/
tags: TAGS ':' '{' tags_list '}'
tags_list: tag | tags_list ',' tag ;
tag: tag_key ':' tag_value { JsonRide->setTag(JsonTagKey, JsonTagValue); }
tag_key : string { JsonTagKey = JsonString; }
tag_value : string { JsonTagValue = JsonString; }
/*
* Intervals
*/
intervals: INTERVALS ':' '[' interval_list ']' ;
interval_list: interval | interval_list ',' interval ;
interval: '{' NAME ':' string ',' { JsonInterval.name = JsonString; }
START ':' number ',' { JsonInterval.start = JsonNumber; }
STOP ':' number { JsonInterval.stop = JsonNumber; }
'}'
{ JsonRide->addInterval(JsonInterval.start,
JsonInterval.stop,
JsonInterval.name);
JsonInterval = RideFileInterval();
}
/*
* Ride datapoints
*/
samples: SAMPLES ':' '[' sample_list ']' ;
sample_list: sample | sample_list ',' sample ;
sample: '{' series_list '}' { JsonRide->appendPoint(JsonPoint.secs, JsonPoint.cad,
JsonPoint.hr, JsonPoint.km, JsonPoint.kph,
JsonPoint.nm, JsonPoint.watts, JsonPoint.alt,
JsonPoint.lon, JsonPoint.lat,
JsonPoint.headwind, JsonPoint.interval);
JsonPoint = RideFilePoint();
}
series_list: series | series_list ',' series ;
series: SECS ':' number { JsonPoint.secs = JsonNumber; }
| KM ':' number { JsonPoint.km = JsonNumber; }
| WATTS ':' number { JsonPoint.watts = JsonNumber; }
| NM ':' number { JsonPoint.nm = JsonNumber; }
| CAD ':' number { JsonPoint.cad = JsonNumber; }
| KPH ':' number { JsonPoint.kph = JsonNumber; }
| HR ':' number { JsonPoint.hr = JsonNumber; }
| ALTITUDE ':' number { JsonPoint.alt = JsonNumber; }
| LAT ':' number { JsonPoint.lat = JsonNumber; }
| LON ':' number { JsonPoint.lon = JsonNumber; }
| HEADWIND ':' number { JsonPoint.headwind = JsonNumber; }
| SLOPE ':' number { }
| TEMP ':' number { }
;
/*
* Primitives
*/
number: INTEGER { JsonNumber = QString(JsonRideFiletext).toInt(); }
| FLOAT { JsonNumber = QString(JsonRideFiletext).toDouble(); }
;
string: STRING { JsonString = unprotect(JsonRideFiletext); }
;
%%
static int jsonFileReaderRegistered =
RideFileFactory::instance().registerReader(
"json", "GoldenCheetah Json Format", new JsonFileReader());
RideFile *
JsonFileReader::openRideFile(QFile &file, QStringList &errors) const
{
// jsonRide is local and static, used in the parser
// JsonRideFilein is the FILE * used by the lexer
JsonRideFilein = fopen(file.fileName().toLatin1(), "r");
if (JsonRideFilein == NULL) {
errors << "unable to open file" + file.fileName();
}
// inform the parser/lexer we have a new file
JsonRideFilerestart(JsonRideFilein);
// setup
JsonRide = new RideFile;
JsonRideFileerrors.clear();
// set to non-zero if you want to
// to debug the yyparse() state machine
// sending state transitions to stderr
//yydebug = 0;
// parse it
JsonRideFileparse();
// release the file handle
fclose(JsonRideFilein);
// Only get errors so fail if we have any
if (errors.count()) {
errors << JsonRideFileerrors;
delete JsonRide;
return NULL;
} else return JsonRide;
}
// Writes valid .json (validated at www.jsonlint.com)
void
JsonFileReader::writeRideFile(const RideFile *ride, QFile &file) const
{
// can we open the file for writing?
if (!file.open(QIODevice::WriteOnly)) return;
// truncate existing
file.resize(0);
// setup streamer
QTextStream out(&file);
// start of document and ride
out << "{\n\t\"RIDE\":{\n";
// first class variables
out << "\t\t\"STARTTIME\":\"" << protect(ride->startTime().toUTC().toString(DATETIME_FORMAT)) << "\",\n";
out << "\t\t\"RECINTSECS\":" << ride->recIntSecs() << ",\n";
out << "\t\t\"DEVICETYPE\":\"" << protect(ride->deviceType()) << "\",\n";
// not available in v2.1 -- out << "\t\t\"IDENTIFIER\":\"" << protect(ride->id()) << "\"";
//
// OVERRIDES
//
bool nonblanks = false; // if an override has been deselected it may be blank
// so we only output the OVERRIDES section if we find an
// override whilst iterating over the QMap
if (ride->metricOverrides.count()) {
QMap<QString,QMap<QString, QString> >::const_iterator k;
for (k=ride->metricOverrides.constBegin(); k != ride->metricOverrides.constEnd(); k++) {
// may not contain anything
if (k.value().isEmpty()) continue;
if (nonblanks == false) {
out << ",\n\t\t\"OVERRIDES\":[\n";
nonblanks = true;
}
// begin of overrides
out << "\t\t\t{ \"" << k.key() << "\":{ ";
// key/value pairs
QMap<QString, QString>::const_iterator j;
for (j=k.value().constBegin(); j != k.value().constEnd(); j++) {
// comma separated
out << "\"" << j.key() << "\":\"" << j.value() << "\"";
if (j+1 != k.value().constEnd()) out << ", ";
}
if (k+1 != ride->metricOverrides.constEnd()) out << " }},\n";
else out << " }}\n";
}
if (nonblanks == true) {
// end of the overrides
out << "\t\t]";
}
}
//
// TAGS
//
if (ride->tags().count()) {
out << ",\n\t\t\"TAGS\":{\n";
QMap<QString,QString>::const_iterator i;
for (i=ride->tags().constBegin(); i != ride->tags().constEnd(); i++) {
out << "\t\t\t\"" << i.key() << "\":\"" << protect(i.value()) << "\"";
if (i+1 != ride->tags().constEnd()) out << ",\n";
else out << "\n";
}
// end of the tags
out << "\t\t}";
}
//
// INTERVALS
//
if (!ride->intervals().empty()) {
out << ",\n\t\t\"INTERVALS\":[\n";
bool first = true;
foreach (RideFileInterval i, ride->intervals()) {
if (first) first=false;
else out << ",\n";
out << "\t\t\t{ ";
out << "\"NAME\":\"" << protect(i.name) << "\"";
out << ", \"START\": " << QString("%1").arg(i.start);
out << ", \"STOP\": " << QString("%1").arg(i.stop) << " }";
}
out <<"\n\t\t]";
}
//
// SAMPLES
//
if (ride->dataPoints().count()) {
out << ",\n\t\t\"SAMPLES\":[\n";
bool first = true;
foreach (RideFilePoint *p, ride->dataPoints()) {
if (first) first=false;
else out << ",\n";
out << "\t\t\t{ ";
// always store time
out << "\"SECS\":" << QString("%1").arg(p->secs);
if (ride->areDataPresent()->km) out << ", \"KM\":" << QString("%1").arg(p->km);
if (ride->areDataPresent()->watts) out << ", \"WATTS\":" << QString("%1").arg(p->watts);
if (ride->areDataPresent()->nm) out << ", \"NM\":" << QString("%1").arg(p->nm);
if (ride->areDataPresent()->cad) out << ", \"CAD\":" << QString("%1").arg(p->cad);
if (ride->areDataPresent()->kph) out << ", \"KPH\":" << QString("%1").arg(p->kph);
if (ride->areDataPresent()->hr) out << ", \"HR\":" << QString("%1").arg(p->hr);
if (ride->areDataPresent()->alt) out << ", \"ALT\":" << QString("%1").arg(p->alt);
if (ride->areDataPresent()->lat)
out << ", \"LAT\":" << QString("%1").arg(p->lat, 0, 'g', 11);
if (ride->areDataPresent()->lon)
out << ", \"LON\":" << QString("%1").arg(p->lon, 0, 'g', 11);
if (ride->areDataPresent()->headwind) out << ", \"HEADWIND\":" << QString("%1").arg(p->headwind);
// sample points in here!
out << " }";
}
out <<"\n\t\t]";
}
// end of ride and document
out << "\n\t}\n}\n";
// close
file.close();
}

View File

@@ -1,305 +0,0 @@
/*
* 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 "KmlRideFile.h"
#include <QDebug>
#include <time.h>
#include <iostream>
#include <string>
#include "boost/scoped_ptr.hpp"
#include "kml/base/date_time.h"
#include "kml/base/expat_parser.h"
#include "kml/base/file.h"
#include "kml/base/vec3.h"
#include "kml/convenience/convenience.h"
#include "kml/convenience/gpx_trk_pt_handler.h"
#include "kml/dom.h"
#include "kml/dom/kml_ptr.h"
// majority of code swiped from the libkml example gpx2kml.cc
using kmlbase::ExpatParser;
using kmlbase::DateTime;
using kmlbase::Vec3;
using kmlbase::Attributes;
using kmldom::ContainerPtr;
using kmldom::FolderPtr;
using kmldom::IconStylePtr;
using kmldom::IconStyleIconPtr;
using kmldom::KmlFactory;
using kmldom::KmlPtr;
using kmldom::LabelStylePtr;
using kmldom::ListStylePtr;
using kmldom::PairPtr;
using kmldom::PointPtr;
using kmldom::SchemaPtr;
using kmldom::ExtendedDataPtr;
using kmldom::SchemaDataPtr;
using kmldom::GxSimpleArrayFieldPtr;
using kmldom::GxSimpleArrayDataPtr;
using kmldom::GxMultiTrackPtr;
using kmldom::GxTrackPtr;
using kmldom::PlacemarkPtr;
using kmldom::TimeStampPtr;
using kmldom::StylePtr;
using kmldom::StyleMapPtr;
//
// Utility functions
//
static const char kDotIcon[] =
"http://maps.google.com/mapfiles/kml/shapes/shaded_dot.png";
static ExtendedDataPtr CreateExtendedData() {
KmlFactory* kml_factory = KmlFactory::GetFactory();
ExtendedDataPtr ed = kml_factory->CreateExtendedData();
return ed;
}
static SchemaDataPtr CreateSchemaData(string name) {
KmlFactory* kml_factory = KmlFactory::GetFactory();
SchemaDataPtr sd = kml_factory->CreateSchemaData();
sd->set_schemaurl("#" + name);
return sd;
}
static GxSimpleArrayDataPtr CreateGxSimpleArrayData(string name) {
KmlFactory* kml_factory = KmlFactory::GetFactory();
GxSimpleArrayDataPtr sa = kml_factory->CreateGxSimpleArrayData();
sa->set_name(name);
return sa;
}
static PlacemarkPtr CreateGxTrackPlacemark(string name, GxTrackPtr tracks) {
KmlFactory* kml_factory = KmlFactory::GetFactory();
PlacemarkPtr placemark = kml_factory->CreatePlacemark();
placemark->set_name(name);
placemark->set_id(name);
placemark->set_geometry(tracks);
return placemark;
}
static GxTrackPtr CreateGxTrack(string name) {
KmlFactory* kml_factory = KmlFactory::GetFactory();
GxTrackPtr track = kml_factory->CreateGxTrack();
track->set_id(name);
return track;
}
static SchemaPtr CreateSchema(string name) {
KmlFactory* kml_factory = KmlFactory::GetFactory();
SchemaPtr schema = kml_factory->CreateSchema();
schema->set_id(name);
schema->set_name(name);
return schema;
}
static GxSimpleArrayFieldPtr CreateGxSimpleArrayField(string name) {
KmlFactory* kml_factory = KmlFactory::GetFactory();
GxSimpleArrayFieldPtr field = kml_factory->CreateGxSimpleArrayField();
field->set_type("float");
field->set_name(name);
field->set_displayname(name);
return field;
}
static IconStylePtr CreateIconStyle(double scale) {
KmlFactory* kml_factory = KmlFactory::GetFactory();
IconStyleIconPtr icon = kml_factory->CreateIconStyleIcon();
icon->set_href(kDotIcon);
IconStylePtr icon_style = kml_factory->CreateIconStyle();
icon_style->set_icon(icon);
icon_style->set_scale(scale);
return icon_style;
}
static LabelStylePtr CreateLabelStyle(double scale) {
LabelStylePtr label_style = KmlFactory::GetFactory()->CreateLabelStyle();
label_style->set_scale(scale);
return label_style;
}
static PairPtr CreatePair(int style_state, double icon_scale) {
KmlFactory* kml_factory = KmlFactory::GetFactory();
PairPtr pair = kml_factory->CreatePair();
pair->set_key(style_state);
StylePtr style = kml_factory->CreateStyle();
style->set_iconstyle(CreateIconStyle(icon_scale));
// Hide the label in normal style state, visible in highlight.
style->set_labelstyle(CreateLabelStyle(
style_state == kmldom::STYLESTATE_NORMAL ? 0 : 1 ));
pair->set_styleselector(style);
return pair;
}
static StylePtr CreateRadioFolder(const char* id) {
KmlFactory* kml_factory = KmlFactory::GetFactory();
ListStylePtr list_style = kml_factory->CreateListStyle();
list_style->set_listitemtype(kmldom::LISTITEMTYPE_RADIOFOLDER);
StylePtr style = kml_factory->CreateStyle();
style->set_liststyle(list_style);
style->set_id(id);
return style;
}
static StyleMapPtr CreateStyleMap(const char* id) {
KmlFactory* kml_factory = KmlFactory::GetFactory();
StyleMapPtr style_map = kml_factory->CreateStyleMap();
style_map->set_id(id);
style_map->add_pair(CreatePair(kmldom::STYLESTATE_NORMAL, 0.1));
style_map->add_pair(CreatePair(kmldom::STYLESTATE_HIGHLIGHT, 0.3));
return style_map;
}
//
// Serialise the ride
//
bool
KmlFileReader::writeRideFile(const RideFile * ride, QFile &file) const
{
// Create a new DOM document and setup styles et al
kmldom::KmlFactory* kml_factory = kmldom::KmlFactory::GetFactory();
kmldom::DocumentPtr document = kml_factory->CreateDocument();
const char* kRadioFolderId = "radio-folder-style";
document->add_styleselector(CreateRadioFolder(kRadioFolderId));
document->set_styleurl(std::string("#") + kRadioFolderId);
const char* kStyleMapId = "style-map";
document->add_styleselector(CreateStyleMap(kStyleMapId));
document->set_name("Golden Cheetah");
// add the schema elements for each data series
SchemaPtr schemadef = CreateSchema("schema");
// gx:SimpleArrayField ...
if (ride->areDataPresent()->cad)
schemadef->add_gx_simplearrayfield(CreateGxSimpleArrayField("cadence"));
if (ride->areDataPresent()->hr)
schemadef->add_gx_simplearrayfield(CreateGxSimpleArrayField("heartrate"));
if (ride->areDataPresent()->watts)
schemadef->add_gx_simplearrayfield(CreateGxSimpleArrayField("power"));
if (ride->areDataPresent()->nm)
schemadef->add_gx_simplearrayfield(CreateGxSimpleArrayField("torque"));
if (ride->areDataPresent()->headwind)
schemadef->add_gx_simplearrayfield(CreateGxSimpleArrayField("headwind"));
document->add_schema(schemadef);
// setup trip folder (shown on lhs of google earth
FolderPtr folder = kmldom::KmlFactory::GetFactory()->CreateFolder();
folder->set_name("Bike Rides");
document->add_feature(folder);
// Create a track for the entire ride
GxTrackPtr track = CreateGxTrack("Entire Ride");
PlacemarkPtr placemark = CreateGxTrackPlacemark(QString("Bike %1")
.arg(ride->startTime().toString()).toStdString(), track);
folder->add_feature(placemark);
//
// Basic Data -- Lat/Lon/Alt and Timestamp
//
foreach (RideFilePoint *datapoint, ride->dataPoints()) {
// lots of arsing around with dates XXX clean this up
QDateTime timestamp(ride->startTime().addSecs(datapoint->secs));
string stdctimestamp = timestamp.toString(Qt::ISODate).toStdString() + "Z"; //<< 'Z' fixes crash!
kmlbase::DateTime *when = kmlbase::DateTime::Create(stdctimestamp.data());
if (datapoint->lat && datapoint->lon) track->add_when(when->GetXsdDateTime());
}
// <when> loop through the entire ride
foreach (RideFilePoint *datapoint, ride->dataPoints()) {
if (datapoint->lat && datapoint->lon) track->add_gx_coord(kmlbase::Vec3(datapoint->lon, datapoint->lat, datapoint->alt));
}
//
// Extended Data -- cadence, heartrate, power, torque, headwind
//
ExtendedDataPtr extended = CreateExtendedData();
track->set_extendeddata(extended);
SchemaDataPtr schema = CreateSchemaData("schema");
extended->add_schemadata(schema);
// power
if (ride->areDataPresent()->watts) {
GxSimpleArrayDataPtr power = CreateGxSimpleArrayData("power");
schema->add_gx_simplearraydata(power);
// now create a GxSimpleArrayData
foreach (RideFilePoint *datapoint, ride->dataPoints()) {
if (datapoint->lat && datapoint->lon) power->add_gx_value(QString("%1").arg(datapoint->watts).toStdString());
}
}
// cadence
if (ride->areDataPresent()->cad) {
GxSimpleArrayDataPtr cadence = CreateGxSimpleArrayData("cadence");
schema->add_gx_simplearraydata(cadence);
// now create a GxSimpleArrayData
foreach (RideFilePoint *datapoint, ride->dataPoints()) {
if (datapoint->lat && datapoint->lon) cadence->add_gx_value(QString("%1").arg(datapoint->cad).toStdString());
}
}
// heartrate
if (ride->areDataPresent()->hr) {
GxSimpleArrayDataPtr heartrate = CreateGxSimpleArrayData("heartrate");
schema->add_gx_simplearraydata(heartrate);
// now create a GxSimpleArrayData
foreach (RideFilePoint *datapoint, ride->dataPoints()) {
if (datapoint->lat && datapoint->lon) heartrate->add_gx_value(QString("%1").arg(datapoint->hr).toStdString());
}
}
// torque
if (ride->areDataPresent()->nm) {
GxSimpleArrayDataPtr torque = CreateGxSimpleArrayData("torque");
schema->add_gx_simplearraydata(torque);
// now create a GxSimpleArrayData
foreach (RideFilePoint *datapoint, ride->dataPoints()) {
if (datapoint->lat && datapoint->lon) torque->add_gx_value(QString("%1").arg(datapoint->nm).toStdString());
}
}
// headwind
if (ride->areDataPresent()->headwind) {
GxSimpleArrayDataPtr headwind = CreateGxSimpleArrayData("headwind");
schema->add_gx_simplearraydata(headwind);
// now create a GxSimpleArrayData
foreach (RideFilePoint *datapoint, ride->dataPoints()) {
if (datapoint->lat && datapoint->lon) headwind->add_gx_value(QString("%1").arg(datapoint->headwind).toStdString());
}
}
// Create KML document
kmldom::KmlPtr kml = kml_factory->CreateKml();
kml->set_feature(document);
// make sure the google extensions are added in!
kmlbase::Attributes gxxmlns22;
gxxmlns22.SetValue("gx", "http://www.google.com/kml/ext/2.2");
kml->MergeXmlns(gxxmlns22);
// Serialize
if (!file.open(QIODevice::WriteOnly)) return(false);
file.write(kmldom::SerializePretty(kml).data());
file.close();
return(true);
}

View File

@@ -1,29 +0,0 @@
/*
* 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
*/
#ifndef _KmlRideFile_h
#define _KmlRideFile_h
#include "RideFile.h"
struct KmlFileReader : public RideFileReader {
virtual RideFile *openRideFile(QFile &, QStringList &) const { return NULL; } // does not support reading
virtual bool writeRideFile(const RideFile *ride, QFile &file) const;
};
#endif // _KmlRideFile_h

View File

@@ -1,111 +0,0 @@
// code borrowed from event_filter qwt examples
// and modified for GC LTM purposes
#include <qapplication.h>
#include <qevent.h>
#include <qwhatsthis.h>
#include <qpainter.h>
#include <qwt_plot.h>
#include <qwt_symbol.h>
#include <qwt_scale_map.h>
#include <qwt_plot_canvas.h>
#include <qwt_plot_curve.h>
#include "LTMCanvasPicker.h"
#include <QDebug>
LTMCanvasPicker::LTMCanvasPicker(QwtPlot *plot):
QObject(plot),
d_selectedCurve(NULL),
d_selectedPoint(-1)
{
QwtPlotCanvas *canvas = plot->canvas();
canvas->installEventFilter(this);
// We want the focus, but no focus rect. The
canvas->setFocusPolicy(Qt::StrongFocus);
canvas->setFocusIndicator(QwtPlotCanvas::ItemFocusIndicator);
canvas->setFocus();
}
bool LTMCanvasPicker::event(QEvent *e)
{
if ( e->type() == QEvent::User )
{
//showCursor(true);
return true;
}
return QObject::event(e);
}
bool LTMCanvasPicker::eventFilter(QObject *object, QEvent *e)
{
if ( object != (QObject *)plot()->canvas() )
return false;
switch(e->type())
{
default:
QApplication::postEvent(this, new QEvent(QEvent::User));
break;
case QEvent::MouseButtonPress:
select(((QMouseEvent *)e)->pos(), true);
break;
case QEvent::MouseMove:
select(((QMouseEvent *)e)->pos(), false);
break;
}
return QObject::eventFilter(object, e);
}
// Select the point at a position. If there is no point
// deselect the selected point
void LTMCanvasPicker::select(const QPoint &pos, bool clicked)
{
QwtPlotCurve *curve = NULL;
double dist = 10e10;
int index = -1;
const QwtPlotItemList& itmList = plot()->itemList();
for ( QwtPlotItemIterator it = itmList.begin();
it != itmList.end(); ++it )
{
if ( (*it)->rtti() == QwtPlotItem::Rtti_PlotCurve )
{
QwtPlotCurve *c = (QwtPlotCurve*)(*it);
double d = -1;
int idx = c->closestPoint(pos, &d);
if ( d != -1 && d < dist )
{
curve = c;
index = idx;
dist = d;
}
}
}
d_selectedCurve = NULL;
d_selectedPoint = -1;
if ( curve && dist < 10 ) // 10 pixels tolerance
{
// picked one
d_selectedCurve = curve;
d_selectedPoint = index;
if (clicked)
pointClicked(curve, index); // emit
else
pointHover(curve, index); // emit
} else {
// didn't
if (clicked)
pointClicked(NULL, -1); // emit
else
pointHover(NULL, -1); // emit
}
}

View File

@@ -1,34 +0,0 @@
// code stolen from the event_filter qwt example
// and modified for GC LTM
#ifndef GC_LTMCanvasPicker_H
#define GC_LTMCanvasPicker_H 1
#include <qobject.h>
class QPoint;
class QCustomEvent;
class QwtPlot;
class QwtPlotCurve;
class LTMCanvasPicker: public QObject
{
Q_OBJECT
public:
LTMCanvasPicker(QwtPlot *plot);
virtual bool eventFilter(QObject *, QEvent *);
virtual bool event(QEvent *);
signals:
void pointClicked(QwtPlotCurve *, int);
void pointHover(QwtPlotCurve *, int);
private:
void select(const QPoint &, bool);
QwtPlot *plot() { return (QwtPlot *)parent(); }
const QwtPlot *plot() const { return (QwtPlot *)parent(); }
QwtPlotCurve *d_selectedCurve;
int d_selectedPoint;
};
#endif

View File

@@ -1,297 +0,0 @@
/*
* 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 "LTMChartParser.h"
#include "LTMSettings.h"
#include "LTMTool.h"
#include <QDate>
#include <QDebug>
#include <assert.h>
// local helper functions to convert Qwt enums to ints and back
static int curveToInt(QwtPlotCurve::CurveStyle x)
{
switch (x) {
case QwtPlotCurve::NoCurve : return 0;
case QwtPlotCurve::Lines : return 1;
case QwtPlotCurve::Sticks : return 2;
case QwtPlotCurve::Steps : return 3;
case QwtPlotCurve::Dots : return 4;
default : return 100;
}
}
static QwtPlotCurve::CurveStyle intToCurve(int x)
{
switch (x) {
default:
case 0 : return QwtPlotCurve::NoCurve;
case 1 : return QwtPlotCurve::Lines;
case 2 : return QwtPlotCurve::Sticks;
case 3 : return QwtPlotCurve::Steps;
case 4 : return QwtPlotCurve::Dots;
case 100: return QwtPlotCurve::UserCurve;
}
}
static int symbolToInt(QwtSymbol::Style x)
{
switch (x) {
default:
case QwtSymbol::NoSymbol: return -1;
case QwtSymbol::Ellipse: return 0;
case QwtSymbol::Rect: return 1;
case QwtSymbol::Diamond: return 2;
case QwtSymbol::Triangle: return 3;
case QwtSymbol::DTriangle: return 4;
case QwtSymbol::UTriangle: return 5;
case QwtSymbol::LTriangle: return 6;
case QwtSymbol::RTriangle: return 7;
case QwtSymbol::Cross: return 8;
case QwtSymbol::XCross: return 9;
case QwtSymbol::HLine: return 10;
case QwtSymbol::VLine: return 11;
case QwtSymbol::Star1: return 12;
case QwtSymbol::Star2: return 13;
case QwtSymbol::Hexagon: return 14;
case QwtSymbol::StyleCnt: return 15;
}
}
static QwtSymbol::Style intToSymbol(int x)
{
switch (x) {
default:
case -1: return QwtSymbol::NoSymbol;
case 0 : return QwtSymbol::Ellipse;
case 1 : return QwtSymbol::Rect;
case 2 : return QwtSymbol::Diamond;
case 3 : return QwtSymbol::Triangle;
case 4 : return QwtSymbol::DTriangle;
case 5 : return QwtSymbol::UTriangle;
case 6 : return QwtSymbol::LTriangle;
case 7 : return QwtSymbol::RTriangle;
case 8 : return QwtSymbol::Cross;
case 9 : return QwtSymbol::XCross;
case 10 : return QwtSymbol::HLine;
case 11 : return QwtSymbol::VLine;
case 12 : return QwtSymbol::Star1;
case 13 : return QwtSymbol::Star2;
case 14 : return QwtSymbol::Hexagon;
case 15 : return QwtSymbol::StyleCnt;
}
}
bool LTMChartParser::startDocument()
{
buffer.clear();
return TRUE;
}
static QString unprotect(QString buffer)
{
// get local TM character code
QTextEdit trademark("&#8482;"); // process html encoding of(TM)
QString tm = trademark.toPlainText();
// remove quotes
QString t = buffer.trimmed();
QString s = t.mid(1,t.length()-2);
// replace html (TM) with local TM character
s.replace( "&#8482;", tm );
// html special chars are automatically handled
// XXX other special characters will not work
// cross-platform but will work locally, so not a biggie
// i.e. if thedefault charts.xml has a special character
// in it it should be added here
return s;
}
// to see the format of the charts.xml file, look at the serialize()
// function at the bottom of this source file.
bool LTMChartParser::endElement( const QString&, const QString&, const QString &qName )
{
//
// Single Attribute elements
//
if(qName == "chartname") setting.name = unprotect(buffer);
else if(qName == "metricname") metric.symbol = buffer.trimmed();
else if(qName == "metricdesc") metric.name = unprotect(buffer);
else if(qName == "metricuname") metric.uname = unprotect(buffer);
else if(qName == "metricuunits") metric.uunits = unprotect(buffer);
else if(qName == "metricbaseline") metric.baseline = buffer.trimmed().toDouble();
else if(qName == "metricsmooth") metric.smooth = buffer.trimmed().toInt();
else if(qName == "metrictrend") metric.trend = buffer.trimmed().toInt();
else if(qName == "metrictopn") metric.topN = buffer.trimmed().toInt();
else if(qName == "metriccurve") metric.curveStyle = intToCurve(buffer.trimmed().toInt());
else if(qName == "metricsymbol") metric.symbolStyle = intToSymbol(buffer.trimmed().toInt());
else if(qName == "metricpencolor") {
// the r,g,b values are in red="xx",green="xx" and blue="xx" attributes
// of this element and captured in startelement below
metric.penColor = QColor(red,green,blue);
}
else if(qName == "metricpenalpha") metric.penAlpha = buffer.trimmed().toInt();
else if(qName == "metricpenwidth") metric.penWidth = buffer.trimmed().toInt();
else if(qName == "metricpenstyle") metric.penStyle = buffer.trimmed().toInt();
else if(qName == "metricbrushcolor") {
// the r,g,b values are in red="xx",green="xx" and blue="xx" attributes
// of this element and captured in startelement below
metric.brushColor = QColor(red,green,blue);
} else if(qName == "metricbrushalpha") metric.penAlpha = buffer.trimmed().toInt();
//
// Complex Elements
//
else if(qName == "metric") // <metric></metric> block
setting.metrics.append(metric);
else if (qName == "LTM-chart") // <LTM-chart></LTM-chart> block
settings.append(setting);
else if (qName == "charts") { // <charts></charts> block top-level
} // do nothing for now
return TRUE;
}
bool LTMChartParser::startElement( const QString&, const QString&, const QString &name, const QXmlAttributes &attrs )
{
buffer.clear();
if(name == "charts")
; // do nothing for now
else if (name == "LTM-chart")
setting = LTMSettings();
else if (name == "metric")
metric = MetricDetail();
else if (name == "metricpencolor" || name == "metricbrushcolor") {
// red="x" green="x" blue="x" attributes for pen/brush color
for(int i=0; i<attrs.count(); i++) {
if (attrs.qName(i) == "red") red=attrs.value(i).toInt();
if (attrs.qName(i) == "green") green=attrs.value(i).toInt();
if (attrs.qName(i) == "blue") blue=attrs.value(i).toInt();
}
}
return TRUE;
}
bool LTMChartParser::characters( const QString& str )
{
buffer += str;
return TRUE;
}
QList<LTMSettings>
LTMChartParser::getSettings()
{
return settings;
}
bool LTMChartParser::endDocument()
{
return TRUE;
}
// static helper to protect special xml characters
// ideally we would use XMLwriter to do this but
// the file format is trivial and this implementation
// is easier to follow and modify... for now.
static QString xmlprotect(QString string)
{
QTextEdit trademark("&#8482;"); // process html encoding of(TM)
QString tm = trademark.toPlainText();
QString s = string;
s.replace( tm, "&#8482;" );
s.replace( "&", "&amp;" );
s.replace( ">", "&gt;" );
s.replace( "<", "&lt;" );
s.replace( "\"", "&quot;" );
s.replace( "\'", "&apos;" );
return s;
}
//
// Write out the charts.xml file
//
void
LTMChartParser::serialize(QString filename, QList<LTMSettings> charts)
{
// open file - truncate contents
QFile file(filename);
file.open(QFile::WriteOnly);
file.resize(0);
QTextStream out(&file);
// Character set encoding added to support international characters in names
out.setCodec("UTF-8");
out << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
// begin document
out << "<charts>\n";
// write out to file
foreach (LTMSettings chart, charts) {
// chart name
out<<QString("\t<LTM-chart>\n\t\t<chartname>\"%1\"</chartname>\n").arg(xmlprotect(chart.name));
// all the metrics
foreach (MetricDetail metric, chart.metrics) {
out<<QString("\t\t<metric>\n");
out<<QString("\t\t\t<metricdesc>\"%1\"</metricdesc>\n").arg(xmlprotect(metric.name));
out<<QString("\t\t\t<metricname>%1</metricname>\n").arg(metric.symbol);
out<<QString("\t\t\t<metricuname>\"%1\"</metricuname>\n").arg(xmlprotect(metric.uname));
out<<QString("\t\t\t<metricuunits>\"%1\"</metricuunits>\n").arg(xmlprotect(metric.uunits));
// SMOOTH, TREND, TOPN
out<<QString("\t\t\t<metricsmooth>%1</metricsmooth>\n").arg(metric.smooth);
out<<QString("\t\t\t<metrictrend>%1</metrictrend>\n").arg(metric.trend);
out<<QString("\t\t\t<metrictopn>%1</metrictopn>\n").arg(metric.topN);
out<<QString("\t\t\t<metricbaseline>%1</metricbaseline>\n").arg(metric.baseline);
// CURVE, SYMBOL
out<<QString("\t\t\t<metriccurve>%1</metriccurve>\n").arg(curveToInt(metric.curveStyle));
out<<QString("\t\t\t<metricsymbol>%1</metricsymbol>\n").arg(symbolToInt(metric.symbolStyle));
// PEN
out<<QString("\t\t\t<metricpencolor red=\"%1\" green=\"%3\" blue=\"%4\"></metricpencolor>\n")
.arg(metric.penColor.red())
.arg(metric.penColor.green())
.arg(metric.penColor.blue());
out<<QString("\t\t\t<metricpenalpha>%1</metricpenalpha>\n").arg(metric.penAlpha);
out<<QString("\t\t\t<metricpenwidth>%1</metricpenwidth>\n").arg(metric.penWidth);
out<<QString("\t\t\t<metricpenstyle>%1</metricpenstyle>\n").arg(metric.penStyle);
// BRUSH
out<<QString("\t\t\t<metricbrushcolor red=\"%1\" green=\"%3\" blue=\"%4\"></metricbrushcolor>\n")
.arg(metric.brushColor.red())
.arg(metric.brushColor.green())
.arg(metric.brushColor.blue());
out<<QString("\t\t\t<metricbrushalpha>%1</metricbrushalpha>\n").arg(metric.brushAlpha);
out<<QString("\t\t</metric>\n");
}
out<<QString("\t</LTM-chart>\n");
}
// end document
out << "</charts>\n";
// close file
file.close();
}

View File

@@ -1,47 +0,0 @@
/*
* 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
*/
#ifndef _GC_LTMChartParser_h
#define _GC_LTMChartParser_h 1
#include <QXmlDefaultHandler>
#include "LTMSettings.h"
#include "LTMTool.h"
class LTMChartParser : public QXmlDefaultHandler
{
public:
static void serialize(QString, QList<LTMSettings>);
// unmarshall
bool startDocument();
bool endDocument();
bool endElement( const QString&, const QString&, const QString &qName );
bool startElement( const QString&, const QString&, const QString &name, const QXmlAttributes &attrs );
bool characters( const QString& str );
QList<LTMSettings> getSettings();
protected:
QString buffer;
LTMSettings setting;
MetricDetail metric;
int red, green, blue;
QList<LTMSettings> settings;
};
#endif

View File

@@ -1,93 +0,0 @@
/*
* 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 <math.h>
#include <float.h>
#include <assert.h>
#include "LTMOutliers.h"
#include <QDebug>
LTMOutliers::LTMOutliers(double *xdata, double *ydata, int count, int windowsize, bool absolute) : stdDeviation(0.0)
{
double sum = 0;
int points = 0;
double allSum = 0.0;
int pos=0;
assert(count >= windowsize);
// initial samples from point 0 to windowsize
for (; pos < windowsize; ++pos) {
// we could either use a deviation of zero
// or base it on what we have so far...
// I chose to use sofar since spikes
// are common at the start of a ride
xdev add;
add.x = xdata[pos];
add.y = ydata[pos];
add.pos = pos;
if (absolute) add.deviation = fabs(ydata[pos] - (sum/windowsize));
else add.deviation = ydata[pos] - (sum/windowsize);
rank.append(add);
// when using -ve and +ve values stdDeviation is
// based upon the absolute value of deviation
// when not, we should only look at +ve values
if ((!absolute && add.deviation > 0) || absolute) {
allSum += add.deviation;
points++;
}
sum += ydata[pos]; // initialise the moving average
}
// bulk of samples from windowsize to the end
for (; pos<count; pos++) {
// ranked list
xdev add;
add.x = xdata[pos];
add.y = ydata[pos];
add.pos = pos;
if (absolute) add.deviation = fabs(ydata[pos] - (sum/windowsize));
else add.deviation = ydata[pos] - (sum/windowsize);
rank.append(add);
// calculate the sum for moving average
sum += ydata[pos] - ydata[pos-windowsize];
// when using -ve and +ve values stdDeviation is
// based upon the absolute value of deviation
// when not, we should only look at +ve values
if ((!absolute && add.deviation > 0) || absolute) {
allSum += add.deviation;
points++;
}
}
// and to the list of deviations
// calculate the average deviation across all points
stdDeviation = allSum / (double)points;
// create a ranked list
qSort(rank);
}

Some files were not shown because too many files have changed in this diff Show More