1
0
mirror of https://github.com/tumic0/GPXSee.git synced 2024-10-06 06:43:22 +02:00

Initial commit

This commit is contained in:
Martin Tůma 2015-10-05 01:43:48 +02:00
commit 928e259d0c
39 changed files with 3394 additions and 0 deletions

36
gpxsee.pro Normal file
View File

@ -0,0 +1,36 @@
TARGET = gpxsee
QT += core \
gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
greaterThan(QT_MAJOR_VERSION, 4): QT += printsupport
HEADERS += src/config.h \
src/icons.h \
src/gui.h \
src/gpx.h \
src/graph.h \
src/track.h \
src/parser.h \
src/poi.h \
src/rtree.h \
src/ll.h \
src/axisitem.h \
src/poiitem.h \
src/colorshop.h \
src/keys.h \
src/slideritem.h \
src/markeritem.h
SOURCES += src/main.cpp \
src/gui.cpp \
src/gpx.cpp \
src/graph.cpp \
src/track.cpp \
src/parser.cpp \
src/poi.cpp \
src/ll.cpp \
src/axisitem.cpp \
src/poiitem.cpp \
src/colorshop.cpp \
src/slideritem.cpp \
src/markeritem.cpp
RESOURCES += gpxsee.qrc
TRANSLATIONS = lang/gpxsee_cs.ts

14
gpxsee.qrc Normal file
View File

@ -0,0 +1,14 @@
<RCC>
<qresource>
<file>icons/dialog-close.png</file>
<file>icons/document-open.png</file>
<file>icons/document-save-as.png</file>
<file>icons/document-save.png</file>
<file>icons/flag.png</file>
<file>icons/gpxsee.png</file>
</qresource>
<qresource>
<file>lang/gpxsee_cs.qm</file>
</qresource>
</RCC>

BIN
icons/dialog-close.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
icons/document-open.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 796 B

BIN
icons/document-save-as.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
icons/document-save.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 798 B

BIN
icons/flag.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

BIN
icons/gpxsee.dia Normal file

Binary file not shown.

BIN
icons/gpxsee.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

165
lang/gpxsee_cs.ts Normal file
View File

@ -0,0 +1,165 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="cs_CZ">
<context>
<name>GUI</name>
<message>
<location filename="../src/gui.cpp" line="56"/>
<source>Exit</source>
<translation>Konec</translation>
</message>
<message>
<location filename="../src/gui.cpp" line="62"/>
<source>About Qt</source>
<translation>O Qt</translation>
</message>
<message>
<location filename="../src/gui.cpp" line="164"/>
<source>GPXSee is distributed under the terms of the GNU General Public License version 3. For more info about GPXSee visit the project homepage at </source>
<translation>Program GPXSee je distribuován pod podmínkami licence GNU General Public License verze 3. Pro více informací navštivte stránky programu na adrese </translation>
</message>
<message>
<location filename="../src/gui.cpp" line="172"/>
<source>Open file</source>
<translation>Otevřít soubor</translation>
</message>
<message>
<location filename="../src/gui.cpp" line="74"/>
<source>Save as</source>
<translation>Uložit jako</translation>
</message>
<message>
<location filename="../src/gui.cpp" line="212"/>
<source>Open POI file</source>
<translation>Otevřít POI soubor</translation>
</message>
<message>
<location filename="../src/gui.cpp" line="67"/>
<source>Open</source>
<translation>Otevřít</translation>
</message>
<message>
<location filename="../src/gui.cpp" line="70"/>
<source>Save</source>
<translation>Uložit</translation>
</message>
<message>
<location filename="../src/gui.cpp" line="78"/>
<source>Close</source>
<translation>Zavřít</translation>
</message>
<message>
<location filename="../src/gui.cpp" line="84"/>
<source>Load file</source>
<translation>Nahrát soubor</translation>
</message>
<message>
<location filename="../src/gui.cpp" line="87"/>
<source>Show</source>
<translation>Zobrazit</translation>
</message>
<message>
<location filename="../src/gui.cpp" line="94"/>
<location filename="../src/gui.cpp" line="112"/>
<source>File</source>
<translation>Soubor</translation>
</message>
<message>
<location filename="../src/gui.cpp" line="101"/>
<location filename="../src/gui.cpp" line="117"/>
<source>POI</source>
<translation>POI</translation>
</message>
<message>
<location filename="../src/gui.cpp" line="105"/>
<source>Help</source>
<translation>Nápověda</translation>
</message>
<message>
<location filename="../src/gui.cpp" line="129"/>
<location filename="../src/gui.cpp" line="134"/>
<source>Distance [km]</source>
<translation>Vzdálenost [km]</translation>
</message>
<message>
<location filename="../src/gui.cpp" line="130"/>
<source>Elevation [m.a.s.l.]</source>
<translation>Výška [m.n.m.]</translation>
</message>
<message>
<location filename="../src/gui.cpp" line="135"/>
<source>Speed [km/h]</source>
<translation>Rychlost [km/h]</translation>
</message>
<message>
<location filename="../src/gui.cpp" line="140"/>
<source>Elevation</source>
<translation>Výška</translation>
</message>
<message>
<location filename="../src/gui.cpp" line="141"/>
<source>Speed</source>
<translation>Rychlost</translation>
</message>
<message>
<location filename="../src/gui.cpp" line="60"/>
<location filename="../src/gui.cpp" line="161"/>
<source>About GPXSee</source>
<translation>O aplikaci GPXSee</translation>
</message>
<message>
<location filename="../src/gui.cpp" line="163"/>
<source>GPX viewer and analyzer</source>
<translation>Prohlížeč a analyzátor GPX</translation>
</message>
<message>
<location filename="../src/gui.cpp" line="202"/>
<location filename="../src/gui.cpp" line="216"/>
<source>Error</source>
<translation>Chyba</translation>
</message>
<message>
<location filename="../src/gui.cpp" line="203"/>
<source>Error loading GPX file:
%1</source>
<translation>Soubor GPX nelze otevřít:
%1</translation>
</message>
<message>
<location filename="../src/gui.cpp" line="217"/>
<source>Error loading POI file:
%1</source>
<translation>Soubor POI nelze otevřít:
%1</translation>
</message>
</context>
<context>
<name>QObject</name>
<message>
<location filename="../src/parser.cpp" line="73"/>
<source>Not a GPX file.</source>
<translation>Neplatný GPX soubor.</translation>
</message>
<message>
<location filename="../src/parser.cpp" line="82"/>
<source>%1
Line %2</source>
<translation>%1
Rádka %2</translation>
</message>
<message>
<location filename="../src/poi.cpp" line="31"/>
<source>Invalid latitude
Line %1</source>
<translation>Neplatná zeměpisná šířka
Rádka %1</translation>
</message>
<message>
<location filename="../src/poi.cpp" line="36"/>
<source>Invalid longitude
Line %1</source>
<translation>Neplatná zeměpisná délka
Rádka %1</translation>
</message>
</context>
</TS>

204
src/axisitem.cpp Normal file
View File

@ -0,0 +1,204 @@
#include <cmath>
#include <QPainter>
#include "axisitem.h"
#include <QDebug>
#define TICK 6
#define PADDING 6
#define XTICKS 15
#define YTICKS 10
#define FONT_FAMILY "Arial"
#define FONT_SIZE 12
struct Label {
double min;
double max;
double d;
};
static double niceNum(double x, int round)
{
int expv;
double f;
double nf;
expv = floor(log10(x));
f = x / pow(10.0, expv);
if (round) {
if (f < 1.5)
nf = 1.0;
else if (f < 3.0)
nf = 2.0;
else if (f < 7.0)
nf = 5.0;
else
nf = 10.0;
} else {
if (f <= 1.0)
nf = 1.;
else if (f <= 2.0)
nf = 2.0;
else if (f <= 5.0)
nf = 5.0;
else
nf = 10.0;
}
return nf * pow(10.0, expv);
}
static struct Label label(double min, double max, int ticks)
{
double range;
struct Label l;
range = niceNum(max - min, 0);
l.d = niceNum(range / ticks, 1);
l.min = ceil(min / l.d) * l.d;
l.max = floor(max / l.d) * l.d;
return l;
}
AxisItem::AxisItem(Type type)
{
_type = type;
_size = 0;
}
void AxisItem::setRange(const QPointF &range)
{
_range = range;
updateBoundingRect();
prepareGeometryChange();
}
void AxisItem::setSize(qreal size)
{
_size = size;
updateBoundingRect();
prepareGeometryChange();
}
void AxisItem::setLabel(const QString& label)
{
_label = label;
updateBoundingRect();
prepareGeometryChange();
}
void AxisItem::updateBoundingRect()
{
QFont font;
font.setPixelSize(FONT_SIZE);
font.setFamily(FONT_FAMILY);
QFontMetrics fm(font);
QRect ss, es, ls;
struct Label l;
l = label(_range.x(), _range.y(), (_type == X) ? XTICKS : YTICKS);
es = fm.tightBoundingRect(QString::number(l.max));
ss = fm.tightBoundingRect(QString::number(l.min));
ls = fm.tightBoundingRect(_label);
if (_type == X) {
_boundingRect = QRectF(-ss.width()/2, -TICK/2,
_size + es.width()/2 + ss.width()/2,
ls.height() + es.height() - fm.descent() + TICK + 2*PADDING);
} else {
_boundingRect = QRectF(-(ls.height() + es.width() + 2*PADDING
- fm.descent() + TICK/2), -(_size + es.height()/2
+ fm.descent()), ls.height() -fm.descent() + es.width() + 2*PADDING
+ TICK, _size + es.height()/2 + fm.descent() + ss.height()/2);
}
}
void AxisItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
QWidget *widget)
{
Q_UNUSED(option);
Q_UNUSED(widget);
QFont font;
font.setPixelSize(FONT_SIZE);
font.setFamily(FONT_FAMILY);
painter->setFont(font);
QFontMetrics fm(font);
QRect ts, ls;
struct Label l;
qreal range = _range.y() - _range.x();
qreal val;
ls = fm.tightBoundingRect(_label);
if (_type == X) {
painter->drawLine(0, 0, _size, 0);
l = label(_range.x(), _range.y(), XTICKS);
for (int i = 0; i < ((l.max - l.min) / l.d) + 1; i++) {
val = l.min + i * l.d;
QString str = QString::number(val);
painter->drawLine((_size/range) * (val - _range.x()), TICK/2,
(_size/range) * (val - _range.x()), -TICK/2);
ts = fm.tightBoundingRect(str);
painter->drawText(((_size/range) * (val - _range.x()))
- (ts.width()/2), ts.height() + TICK/2 + PADDING, str);
}
painter->drawText(_size/2 - ls.width()/2, ls.height() + ts.height()
- 2*fm.descent() + TICK/2 + 2*PADDING, _label);
} else {
painter->drawLine(0, 0, 0, -_size);
l = label(_range.x(), _range.y(), YTICKS);
for (int i = 0; i < ((l.max - l.min) / l.d) + 1; i++) {
val = l.min + i * l.d;
QString str = QString::number(val);
painter->drawLine(TICK/2, -((_size/range) * (val - _range.x())),
-TICK/2, -((_size/range) * (val - _range.x())));
ts = fm.tightBoundingRect(str);
painter->drawText(-(ts.width() + PADDING + TICK/2), -((_size/range)
* (val - _range.x())) + (ts.height()/2), str);
}
painter->rotate(-90);
painter->drawText(_size/2 - ls.width()/2, -(ts.width()
+ 2*PADDING + TICK/2), _label);
painter->rotate(90);
}
/*
painter->setPen(Qt::red);
painter->drawRect(boundingRect());
*/
}
QSizeF AxisItem::margin()
{
QFont font;
font.setPixelSize(FONT_SIZE);
QFontMetrics fm(font);
QRect ss, es, ls;
struct Label l;
l = label(_range.x(), _range.y(), (_type == X) ? XTICKS : YTICKS);
es = fm.tightBoundingRect(QString::number(l.max));
ss = fm.tightBoundingRect(QString::number(l.min));
ls = fm.tightBoundingRect(_label);
if (_type == X) {
return QSizeF(es.width()/2,
ls.height() + es.height() - fm.descent() + TICK/2 + 2*PADDING);
} else {
return QSizeF(ls.height() -fm.descent() + es.width() + 2*PADDING
+ TICK/2, es.height()/2 + fm.descent());
}
}

33
src/axisitem.h Normal file
View File

@ -0,0 +1,33 @@
#ifndef AXISITEM_H
#define AXISITEM_H
#include <QGraphicsItem>
class AxisItem : public QGraphicsItem
{
public:
enum Type {X, Y};
AxisItem(Type type);
QRectF boundingRect() const {return _boundingRect;}
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
QWidget *widget);
void setRange(const QPointF &range);
void setSize(qreal size);
void setLabel(const QString& label);
QSizeF margin();
private:
void updateBoundingRect();
Type _type;
QPointF _range;
qreal _size;
QString _label;
QRectF _boundingRect;
};
#endif // AXISITEM_H

62
src/colorshop.cpp Normal file
View File

@ -0,0 +1,62 @@
#include "colorshop.h"
#define HUE_INIT 0.1f
#define HUE_INCREMENT 0.62f
#define SATURATION 0.99f
#define VALUE 0.99f
static unsigned hsv2rgb(float h, float s, float v)
{
unsigned hi;
float r = 0, g = 0, b = 0, p, q, t, f;
hi = (unsigned)(h * 6.0f);
f = h * 6.0f - hi;
p = v * (1.0f - s);
q = v * (1.0f - f * s);
t = v * (1.0f - (1.0f - f) * s);
switch (hi) {
case 0:
r = v; g = t; b = p;
break;
case 1:
r = q; g = v; b = p;
break;
case 2:
r = p; g = v; b = t;
break;
case 3:
r = p; g = q; b = v;
break;
case 4:
r = t; g = p; b = v;
break;
case 5:
r = v; g = p; b = q;
break;
}
return ((unsigned)(r * 256) << 16)
+ ((unsigned)(g * 256) << 8)
+ (unsigned)(b * 256);
}
ColorShop::ColorShop()
{
_hueState = HUE_INIT;
}
QColor ColorShop::color()
{
_hueState += HUE_INCREMENT;
_hueState -= (int) _hueState;
return QColor(hsv2rgb(_hueState, SATURATION, VALUE));
}
void ColorShop::reset()
{
_hueState = HUE_INIT;
}

17
src/colorshop.h Normal file
View File

@ -0,0 +1,17 @@
#ifndef COLORSHOP_H
#define COLORSHOP_H
#include <QColor>
class ColorShop
{
public:
ColorShop();
QColor color();
void reset();
private:
float _hueState;
};
#endif // COLORSHOP_H

8
src/config.h Normal file
View File

@ -0,0 +1,8 @@
#ifndef CONFIG_H
#define CONFIG_H
#define APP_NAME "GPXSee"
#define APP_HOMEPAGE "http://tumic.wz.cz/gpxsee"
#define APP_VERSION "0.1"
#endif /* CONFIG_H */

82
src/gpx.cpp Normal file
View File

@ -0,0 +1,82 @@
#include <QFile>
#include <QLineF>
#include "ll.h"
#include "gpx.h"
#define ALPHA_E 0.9
#define ALPHA_S 0.1
bool GPX::loadFile(const QString &fileName)
{
QFile file(fileName);
bool ret;
_data.clear();
_error.clear();
if (!file.open(QFile::ReadOnly | QFile::Text)) {
_error = qPrintable(file.errorString());
return false;
}
if (!(ret = _parser.loadFile(&file, _data)))
_error = _parser.errorString();
file.close();
return ret;
}
QVector<QPointF> GPX::elevationGraph() const
{
QVector<QPointF> graph;
qreal dist = 0, ds, dh, acc;
if (!_data.size())
return graph;
graph.append(QPointF(0, _data.at(0).elevation));
for (int i = 1; i < _data.size(); i++) {
ds = llDistance(_data.at(i).coordinates, _data.at(i-1).coordinates);
dh = _data.at(i).elevation;
dist += ds;
acc = (i == 1) ? dh : (ALPHA_E * dh) + (1.0 - ALPHA_E) * acc;
graph.append(QPointF(dist, acc));
}
return graph;
}
QVector<QPointF> GPX::speedGraph() const
{
QVector<QPointF> graph;
qreal dist = 0, v, ds, dt, acc;
if (!_data.size())
return graph;
graph.append(QPointF(0, 0));
for (int i = 1; i < _data.size(); i++) {
ds = llDistance(_data.at(i).coordinates, _data.at(i-1).coordinates);
dt = _data.at(i-1).timestamp.msecsTo(_data.at(i).timestamp) / 1000.0;
dist += ds;
v = ds / dt;
acc = (i == 1) ? v : (ALPHA_S * v) + (1.0 - ALPHA_S) * acc;
graph.append(QPointF(dist, acc));
}
return graph;
}
QVector<QPointF> GPX::track() const
{
QVector<QPointF> track;
QPointF p;
for (int i = 0; i < _data.size(); i++) {
ll2mercator(_data.at(i).coordinates, p);
track.append(p);
}
return track;
}

24
src/gpx.h Normal file
View File

@ -0,0 +1,24 @@
#ifndef GPX_H
#define GPX_H
#include <QVector>
#include <QPointF>
#include <QString>
#include "parser.h"
class GPX
{
public:
bool loadFile(const QString &fileName);
const QString &errorString() const {return _error;}
QVector<QPointF> elevationGraph() const;
QVector<QPointF> speedGraph() const;
QVector<QPointF> track() const;
private:
Parser _parser;
QVector<TrackPoint> _data;
QString _error;
};
#endif // GPX_H

187
src/graph.cpp Normal file
View File

@ -0,0 +1,187 @@
#include <float.h>
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QEvent>
#include "graph.h"
#include <QDebug>
#define MARGIN 10.0
Graph::Graph(QWidget *parent)
: QGraphicsView(parent)
{
_scene = new QGraphicsScene(this);
setScene(_scene);
_xAxis = new AxisItem(AxisItem::X);
_yAxis = new AxisItem(AxisItem::Y);
_slider = new SliderItem();
connect(_slider, SIGNAL(positionChanged(const QPointF&)), this,
SLOT(emitSliderPositionChanged(const QPointF&)));
_xMax = -FLT_MAX;
_xMin = FLT_MAX;
_yMax = -FLT_MAX;
_yMin = FLT_MAX;
_xScale = 1;
_yScale = 1;
}
Graph::~Graph()
{
if (_xAxis->scene() != _scene)
delete _xAxis;
if (_yAxis->scene() != _scene)
delete _yAxis;
if (_slider->scene() != _scene)
delete _slider;
delete _scene;
}
void Graph::updateBounds(const QPointF &point)
{
if (point.x() < _xMin)
_xMin = point.x();
if (point.x() > _xMax)
_xMax = point.x();
if (point.y() < _yMin)
_yMin = point.y();
if (point.y() > _yMax)
_yMax = point.y();
}
void Graph::loadData(const QVector<QPointF> &data)
{
QPainterPath path;
QGraphicsPathItem *pi;
QColor color = _colorShop.color();
if (data.size() < 2)
return;
updateBounds(data.at(0));
path.moveTo(data.at(0).x(), -data.at(0).y());
for (int i = 1; i < data.size(); i++) {
path.lineTo(data.at(i).x(), -data.at(i).y());
updateBounds(data.at(i));
}
QBrush brush(color, Qt::SolidPattern);
QPen pen(brush, 0);
pi = new QGraphicsPathItem(path);
pi->setPen(pen);
_scene->addItem(pi);
_graphs.append(pi);
resize(viewport()->size() - QSizeF(MARGIN, MARGIN));
}
void Graph::resize(const QSizeF &size)
{
QRectF r;
QSizeF mx, my;
QTransform transform;
qreal xs, ys;
if (_xAxis->scene() == _scene)
_scene->removeItem(_xAxis);
if (_yAxis->scene() == _scene)
_scene->removeItem(_yAxis);
_xAxis->setRange(QPointF(_xMin * _xScale, _xMax * _xScale));
_yAxis->setRange(QPointF(_yMin * _yScale, _yMax * _yScale));
if (_slider->scene() == _scene)
_scene->removeItem(_slider);
for (int i = 0; i < _graphs.size(); i++)
_graphs.at(i)->resetTransform();
mx = _xAxis->margin();
my = _yAxis->margin();
r = _scene->itemsBoundingRect();
xs = (size.width() - (my.width() + mx.width())) / r.width();
ys = (size.height() - (mx.height() + my.height())) / r.height();
transform.scale(xs, ys);
for (int i = 0; i < _graphs.size(); i++)
_graphs.at(i)->setTransform(transform);
r = _scene->itemsBoundingRect();
_xAxis->setSize(r.width());
_yAxis->setSize(r.height());
_xAxis->setPos(r.bottomLeft());
_yAxis->setPos(r.bottomLeft());
_scene->addItem(_xAxis);
_scene->addItem(_yAxis);
_slider->setArea(r);
_slider->setPos(r.bottomLeft());
_scene->addItem(_slider);
_scene->setSceneRect(_scene->itemsBoundingRect());
}
void Graph::resizeEvent(QResizeEvent *)
{
if (!_graphs.empty())
resize(viewport()->size() - QSizeF(MARGIN, MARGIN));
}
void Graph::plot(QPainter *painter, const QRectF &target)
{
qreal ratio = target.width() / target.height();
QSizeF orig = _scene->sceneRect().size();
QSizeF canvas = QSizeF(orig.height() * ratio, orig.height());
resize(canvas);
_slider->setVisible(false);
_scene->render(painter, target, QRectF(), Qt::KeepAspectRatioByExpanding);
_slider->setVisible(true);
resize(orig);
}
void Graph::clear()
{
if (_xAxis->scene() == _scene)
_scene->removeItem(_xAxis);
if (_yAxis->scene() == _scene)
_scene->removeItem(_yAxis);
if (_slider->scene() == _scene)
_scene->removeItem(_slider);
_scene->clear();
_graphs.clear();
_colorShop.reset();
_xMax = -FLT_MAX;
_xMin = FLT_MAX;
_yMax = -FLT_MAX;
_yMin = FLT_MAX;
_scene->setSceneRect(0, 0, 0, 0);
}
void Graph::emitSliderPositionChanged(const QPointF &pos)
{
qreal val = pos.x() / _slider->area().width();
emit sliderPositionChanged(val);
}
qreal Graph::sliderPosition() const
{
return _slider->pos().x() / _slider->area().width();
}
void Graph::setSliderPosition(qreal pos)
{
_slider->setPos(pos * _slider->area().width(), 0);
}

56
src/graph.h Normal file
View File

@ -0,0 +1,56 @@
#ifndef GRAPH_H
#define GRAPH_H
#include <QGraphicsView>
#include <QVector>
#include <QList>
#include <QPointF>
#include "axisitem.h"
#include "slideritem.h"
#include "colorshop.h"
class Graph : public QGraphicsView
{
Q_OBJECT
public:
Graph(QWidget *parent = 0);
~Graph();
void loadData(const QVector<QPointF> &data);
void setXLabel(const QString &label) {_xAxis->setLabel(label);}
void setYLabel(const QString &label) {_yAxis->setLabel(label);}
void setXScale(qreal scale) {_xScale = scale;}
void setYScale(qreal scale) {_yScale = scale;}
void plot(QPainter *painter, const QRectF &target);
void clear();
qreal sliderPosition() const;
void setSliderPosition(qreal pos);
signals:
void sliderPositionChanged(qreal);
protected:
void resizeEvent(QResizeEvent *);
private slots:
void emitSliderPositionChanged(const QPointF &pos);
private:
void updateBounds(const QPointF &point);
void resize(const QSizeF &size);
QGraphicsScene *_scene;
AxisItem *_xAxis, *_yAxis;
SliderItem *_slider;
qreal _xMin, _xMax, _yMin, _yMax;
QList<QGraphicsPathItem*> _graphs;
qreal _xScale, _yScale;
ColorShop _colorShop;
};
#endif // GRAPH_H

320
src/gui.cpp Normal file
View File

@ -0,0 +1,320 @@
#include <QApplication>
#include <QVBoxLayout>
#include <QMenuBar>
#include <QStatusBar>
#include <QMessageBox>
#include <QFileDialog>
#include <QPrinter>
#include <QPainter>
#include <QKeyEvent>
#include <QDir>
#include "gui.h"
#include "config.h"
#include "icons.h"
#include "keys.h"
#include "gpx.h"
#include "graph.h"
#include "track.h"
#include <QDebug>
GUI::GUI()
{
createActions();
createMenus();
createToolBars();
createTrackView();
createTrackGraphs();
createStatusBar();
connect(_elevationGraph, SIGNAL(sliderPositionChanged(qreal)), _track,
SLOT(movePositionMarker(qreal)));
connect(_speedGraph, SIGNAL(sliderPositionChanged(qreal)), _track,
SLOT(movePositionMarker(qreal)));
QVBoxLayout *layout = new QVBoxLayout;
layout->addWidget(_track);
layout->addWidget(_trackGraphs);
QWidget *widget = new QWidget;
widget->setLayout(layout);
setCentralWidget(widget);
setWindowTitle(APP_NAME);
setUnifiedTitleAndToolBarOnMac(true);
_dirIndex = -1;
resize(600, 800);
}
void GUI::createActions()
{
// Action Groups
_fileActionGroup = new QActionGroup(this);
_fileActionGroup->setExclusive(false);
_fileActionGroup->setEnabled(false);
// General actions
_exitAction = new QAction(tr("Exit"), this);
connect(_exitAction, SIGNAL(triggered()), this, SLOT(close()));
_aboutAction = new QAction(QIcon(QPixmap(APP_ICON)),
tr("About GPXSee"), this);
connect(_aboutAction, SIGNAL(triggered()), this, SLOT(about()));
_aboutQtAction = new QAction(tr("About Qt"), this);
connect(_aboutQtAction, SIGNAL(triggered()), qApp, SLOT(aboutQt()));
// File related actions
_openFileAction = new QAction(QIcon(QPixmap(OPEN_FILE_ICON)),
tr("Open"), this);
connect(_openFileAction, SIGNAL(triggered()), this, SLOT(openFile()));
_saveFileAction = new QAction(QIcon(QPixmap(SAVE_FILE_ICON)),
tr("Save"), this);
_saveFileAction->setActionGroup(_fileActionGroup);
connect(_saveFileAction, SIGNAL(triggered()), this, SLOT(saveFile()));
_saveAsAction = new QAction(QIcon(QPixmap(SAVE_AS_ICON)),
tr("Save as"), this);
_saveAsAction->setActionGroup(_fileActionGroup);
connect(_saveAsAction, SIGNAL(triggered()), this, SLOT(saveAs()));
_closeFileAction = new QAction(QIcon(QPixmap(CLOSE_FILE_ICON)),
tr("Close"), this);
_closeFileAction->setActionGroup(_fileActionGroup);
connect(_closeFileAction, SIGNAL(triggered()), this, SLOT(closeFile()));
// POI actions
_openPOIAction = new QAction(QIcon(QPixmap(OPEN_FILE_ICON)),
tr("Load file"), this);
connect(_openPOIAction, SIGNAL(triggered()), this, SLOT(openPOIFile()));
_showPOIAction = new QAction(QIcon(QPixmap(SHOW_POI_ICON)),
tr("Show"), this);
_showPOIAction->setCheckable(true);
connect(_showPOIAction, SIGNAL(triggered()), this, SLOT(showPOI()));
}
void GUI::createMenus()
{
_fileMenu = menuBar()->addMenu(tr("File"));
_fileMenu->addAction(_openFileAction);
_fileMenu->addAction(_saveFileAction);
_fileMenu->addAction(_saveAsAction);
_fileMenu->addAction(_closeFileAction);
_fileMenu->addAction(_exitAction);
_poiMenu = menuBar()->addMenu(tr("POI"));
_poiMenu->addAction(_openPOIAction);
_poiMenu->addAction(_showPOIAction);
_aboutMenu = menuBar()->addMenu(tr("Help"));
_aboutMenu->addAction(_aboutAction);
_aboutMenu->addAction(_aboutQtAction);
}
void GUI::createToolBars()
{
_fileToolBar = addToolBar(tr("File"));
_fileToolBar->addAction(_openFileAction);
_fileToolBar->addAction(_saveFileAction);
_fileToolBar->addAction(_closeFileAction);
_poiToolBar = addToolBar(tr("POI"));
_poiToolBar->addAction(_showPOIAction);
}
void GUI::createTrackView()
{
_track = new Track(this);
}
void GUI::createTrackGraphs()
{
_elevationGraph = new Graph;
_elevationGraph->setXLabel(tr("Distance [km]"));
_elevationGraph->setYLabel(tr("Elevation [m.a.s.l.]"));
_elevationGraph->setXScale(0.001);
_speedGraph = new Graph;
_speedGraph->setXLabel(tr("Distance [km]"));
_speedGraph->setYLabel(tr("Speed [km/h]"));
_speedGraph->setXScale(0.001);
_speedGraph->setYScale(3.6);
_trackGraphs = new QTabWidget;
_trackGraphs->addTab(_elevationGraph, tr("Elevation"));
_trackGraphs->addTab(_speedGraph, tr("Speed"));
connect(_trackGraphs, SIGNAL(currentChanged(int)), this,
SLOT(graphChanged(int)));
_trackGraphs->setFixedHeight(200);
_trackGraphs->setSizePolicy(
QSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed));
}
void GUI::createStatusBar()
{
_fileName = new QLabel();
_zoom = new QLabel();
_zoom->setAlignment(Qt::AlignHCenter);
statusBar()->addPermanentWidget(_fileName, 9);
statusBar()->addPermanentWidget(_zoom, 1);
statusBar()->setSizeGripEnabled(false);
}
void GUI::about()
{
QMessageBox::about(this, tr("About GPXSee"),
QString("<h3>") + QString(APP_NAME" "APP_VERSION)
+ QString("</h3><p>") + tr("GPX viewer and analyzer") + QString("<p/>")
+ QString("<p>") + tr("GPXSee is distributed under the terms of the "
"GNU General Public License version 3. For more info about GPXSee visit "
"the project homepage at ")
+ QString("<a href=\""APP_HOMEPAGE"\">"APP_HOMEPAGE"</a>.</p>"));
}
void GUI::openFile()
{
QStringList files = QFileDialog::getOpenFileNames(this, tr("Open file"));
QStringList list = files;
QStringList::Iterator it = list.begin();
while(it != list.end()) {
openFile(*it);
++it;
}
if (!list.empty())
setDir(list.back());
}
bool GUI::openFile(const QString &fileName)
{
GPX gpx;
if (!fileName.isEmpty()) {
if (gpx.loadFile(fileName)) {
_elevationGraph->loadData(gpx.elevationGraph());
_speedGraph->loadData(gpx.speedGraph());
_track->loadData(gpx.track());
if (_showPOIAction->isChecked())
_track->loadPOI(_poi);
_fileActionGroup->setEnabled(true);
_fileName->setText(fileName);
return true;
} else {
QMessageBox::critical(this, tr("Error"), fileName + QString("\n\n")
+ tr("Error loading GPX file:\n%1").arg(gpx.errorString()));
}
}
return false;
}
void GUI::openPOIFile()
{
QString fileName = QFileDialog::getOpenFileName(this, tr("Open POI file"));
if (!fileName.isEmpty()) {
if (!_poi.loadFile(fileName)) {
QMessageBox::critical(this, tr("Error"),
tr("Error loading POI file:\n%1").arg(_poi.errorString()));
} else {
_showPOIAction->setChecked(true);
_track->loadPOI(_poi);
}
}
}
void GUI::saveAs()
{
QString fileName = QFileDialog::getSaveFileName(this, "Export to PDF",
QString(), "*.pdf");
if (!fileName.isEmpty()) {
saveFile(fileName);
_saveFileName = fileName;
}
}
void GUI::saveFile()
{
if (_saveFileName.isEmpty())
emit saveAs();
else
saveFile(_saveFileName);
}
void GUI::saveFile(const QString &fileName)
{
QPrinter printer(QPrinter::HighResolution);
printer.setPageSize(QPrinter::A4);
printer.setOrientation(_track->orientation());
printer.setOutputFormat(QPrinter::PdfFormat);
printer.setOutputFileName(fileName);
QPainter p(&printer);
int margin = (printer.paperRect().height() - printer.pageRect().height())
/ 2;
_track->plot(&p, QRectF(0, 0, printer.width(), (0.80 * printer.height())
- margin));
_elevationGraph->plot(&p, QRectF(0, 0.80 * printer.height(),
printer.width(), printer.height() * 0.20));
p.end();
}
void GUI::closeFile()
{
_elevationGraph->clear();
_speedGraph->clear();
_track->clear();
_fileName->clear();
_fileActionGroup->setEnabled(false);
}
void GUI::showPOI()
{
if (_showPOIAction->isChecked())
_track->loadPOI(_poi);
else
_track->clearPOI();
}
void GUI::graphChanged(int index)
{
if (_trackGraphs->widget(index) == _elevationGraph)
_elevationGraph->setSliderPosition(_speedGraph->sliderPosition());
else if (_trackGraphs->widget(index) == _speedGraph)
_speedGraph->setSliderPosition(_elevationGraph->sliderPosition());
}
void GUI::keyPressEvent(QKeyEvent *event)
{
if (_dirIndex < 0 || _dirFiles.count() == 1)
return;
if (event->key() == PREV_KEY) {
if (_dirIndex == 0)
return;
closeFile();
openFile(_dirFiles.at(--_dirIndex).absoluteFilePath());
}
if (event->key() == NEXT_KEY) {
if (_dirIndex == _dirFiles.size() - 1)
return;
closeFile();
openFile(_dirFiles.at(++_dirIndex).absoluteFilePath());
}
}
void GUI::setDir(const QString &file)
{
QDir dir = QFileInfo(file).absoluteDir();
_dirFiles = dir.entryInfoList(QStringList("*.gpx"), QDir::Files);
_dirIndex = _dirFiles.empty() ? -1 : _dirFiles.indexOf(file);
}

85
src/gui.h Normal file
View File

@ -0,0 +1,85 @@
#ifndef GUI_H
#define GUI_H
#include <QMainWindow>
#include <QMenu>
#include <QToolBar>
#include <QTabWidget>
#include <QGraphicsView>
#include <QActionGroup>
#include <QAction>
#include <QLabel>
#include <QFileInfoList>
#include "poi.h"
class Graph;
class Track;
class GUI : public QMainWindow
{
Q_OBJECT
public:
GUI();
bool openFile(const QString &fileName);
void setDir(const QString &file);
private slots:
void about();
void saveFile();
void saveAs();
void openFile();
void closeFile();
void openPOIFile();
void showPOI();
void graphChanged(int);
private:
void createActions();
void createMenus();
void createToolBars();
void createStatusBar();
void createTrackView();
void createTrackGraphs();
void keyPressEvent(QKeyEvent * event);
void saveFile(const QString &fileName);
QMenu *_fileMenu;
QMenu *_aboutMenu;
QMenu *_poiMenu;
QToolBar *_fileToolBar;
QToolBar *_poiToolBar;
QTabWidget *_trackGraphs;
QActionGroup *_fileActionGroup;
QAction *_exitAction;
QAction *_aboutAction;
QAction *_aboutQtAction;
QAction *_saveFileAction;
QAction *_saveAsAction;
QAction *_openFileAction;
QAction *_closeFileAction;
QAction *_openPOIAction;
QAction *_showPOIAction;
QLabel *_fileName;
QLabel *_zoom;
Graph *_elevationGraph;
Graph *_speedGraph;
Track *_track;
POI _poi;
QFileInfoList _dirFiles;
int _dirIndex;
QString _saveFileName;
};
#endif // GUI_H

11
src/icons.h Normal file
View File

@ -0,0 +1,11 @@
#ifndef ICONS_H
#define ICONS_H
#define APP_ICON ":/icons/gpxsee.png"
#define OPEN_FILE_ICON ":/icons/document-open.png"
#define SAVE_FILE_ICON ":/icons/document-save.png"
#define SAVE_AS_ICON ":/icons/document-save-as.png"
#define CLOSE_FILE_ICON ":/icons/dialog-close.png"
#define SHOW_POI_ICON ":/icons/flag.png"
#endif /* ICONS_H */

7
src/keys.h Normal file
View File

@ -0,0 +1,7 @@
#ifndef KEYS_H
#define KEYS_H
#define NEXT_KEY Qt::Key_Space
#define PREV_KEY Qt::Key_Backspace
#endif // KEYS_H

27
src/ll.cpp Normal file
View File

@ -0,0 +1,27 @@
#include <cmath>
#include "ll.h"
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif // M_PI
#define WGS84_RADIUS 6378137.0
#define deg2rad(d) (((d)*M_PI)/180.0)
#define rad2deg(d) (((d)*180.0)/M_PI)
qreal llDistance(const QPointF &p1, const QPointF &p2)
{
qreal dLat = deg2rad(p2.y() - p1.y());
qreal dLon = deg2rad(p2.x() - p1.x());
qreal a = pow(sin(dLat / 2.0), 2.0)
+ cos(deg2rad(p1.y())) * cos(deg2rad(p2.y())) * pow(sin(dLon / 2.0), 2.0);
qreal c = 2.0 * atan2(sqrt(a), sqrt(1.0 - a));
return (WGS84_RADIUS * c);
}
void ll2mercator(const QPointF &src, QPointF &dst)
{
dst.setX(src.x());
dst.setY(rad2deg(log(tan(M_PI/4.0 + deg2rad(src.y())/2.0))));
}

9
src/ll.h Normal file
View File

@ -0,0 +1,9 @@
#ifndef LL_H
#define LL_H
#include <QPointF>
void ll2mercator(const QPointF &src, QPointF &dst);
qreal llDistance(const QPointF &p1, const QPointF &p2);
#endif // LL_H

28
src/main.cpp Normal file
View File

@ -0,0 +1,28 @@
#include <QApplication>
#include <QTranslator>
#include <QLocale>
#include "gui.h"
#include "icons.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QString locale = QLocale::system().name();
QTranslator translator;
translator.load(QString(":/lang/gpxsee_") + locale);
app.installTranslator(&translator);
GUI gui;
gui.setWindowIcon(QIcon(QPixmap(APP_ICON)));
gui.show();
for (int i = 1; i < argc; i++)
gui.openFile(argv[i]);
if (argc > 1)
gui.setDir(QString(argv[argc - 1]));
return app.exec();
}

27
src/markeritem.cpp Normal file
View File

@ -0,0 +1,27 @@
#include <QPainter>
#include "markeritem.h"
#define SIZE 8
MarkerItem::MarkerItem()
{
}
QRectF MarkerItem::boundingRect() const
{
return QRectF(-SIZE/2, -SIZE/2, SIZE, SIZE);
}
void MarkerItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
QWidget *widget)
{
Q_UNUSED(option);
Q_UNUSED(widget);
painter->setPen(Qt::red);
painter->drawLine(-SIZE/2, 0, SIZE/2, 0);
painter->drawLine(0, -SIZE/2, 0, SIZE/2);
// painter->drawRect(boundingRect());
}

16
src/markeritem.h Normal file
View File

@ -0,0 +1,16 @@
#ifndef MARKERITEM_H
#define MARKERITEM_H
#include <QGraphicsItem>
class MarkerItem : public QGraphicsItem
{
public:
MarkerItem();
QRectF boundingRect() const;
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
QWidget *widget);
};
#endif // MARKERITEM_H

93
src/parser.cpp Normal file
View File

@ -0,0 +1,93 @@
#include "parser.h"
void Parser::handleTrekPointData(QVector<TrackPoint> &data,
QStringRef element, const QString &value)
{
if (element == "ele")
data.last().elevation = value.toLatin1().toDouble();
if (element == "time")
data.last().timestamp = QDateTime::fromString(value.toLatin1(),
Qt::ISODate);
}
void Parser::handleTrekPointAttributes(QVector<TrackPoint> &data,
const QXmlStreamAttributes &attr)
{
data.last().coordinates.setY(attr.value("lat").toLatin1().toDouble());
data.last().coordinates.setX(attr.value("lon").toLatin1().toDouble());
}
void Parser::trekPointData(QVector<TrackPoint> &data)
{
while (_reader.readNextStartElement()) {
if (_reader.name() == "ele" || _reader.name() == "time")
handleTrekPointData(data, _reader.name(), _reader.readElementText());
else
_reader.skipCurrentElement();
}
}
void Parser::trekPoints(QVector<TrackPoint> &data)
{
QXmlStreamAttributes attr;
while (_reader.readNextStartElement()) {
if (_reader.name() == "trkpt") {
attr = _reader.attributes();
data.append(TrackPoint());
handleTrekPointAttributes(data, attr);
trekPointData(data);
} else
_reader.skipCurrentElement();
}
}
void Parser::trek(QVector<TrackPoint> &data)
{
while (_reader.readNextStartElement()) {
if (_reader.name() == "trkseg") {
trekPoints(data);
} else
_reader.skipCurrentElement();
}
}
void Parser::gpx(QVector<TrackPoint> &data)
{
while (_reader.readNextStartElement()) {
if (_reader.name() == "trk")
trek(data);
else
_reader.skipCurrentElement();
}
}
bool Parser::parse(QVector<TrackPoint> &data)
{
if (_reader.readNextStartElement()) {
if (_reader.name() == "gpx")
gpx(data);
else
_reader.raiseError(QObject::tr("Not a GPX file."));
}
return !_reader.error();
}
QString Parser::errorString() const
{
return QObject::tr("%1\nLine %2")
.arg(_reader.errorString())
.arg(_reader.lineNumber());
}
bool Parser::loadFile(QIODevice *device, QVector<TrackPoint> &data)
{
_reader.clear();
_reader.setDevice(device);
return parse(data);
}

36
src/parser.h Normal file
View File

@ -0,0 +1,36 @@
#ifndef PARSER_H
#define PARSER_H
#include <QXmlStreamReader>
#include <QDateTime>
#include <QPointF>
struct TrackPoint
{
QPointF coordinates;
QDateTime timestamp;
qreal elevation;
};
class Parser
{
public:
bool loadFile(QIODevice *device, QVector<TrackPoint> &data);
QString errorString() const;
private:
bool parse(QVector<TrackPoint> &data);
void gpx(QVector<TrackPoint> &data);
void trek(QVector<TrackPoint> &data);
void trekPoints(QVector<TrackPoint> &data);
void trekPointData(QVector<TrackPoint> &data);
void handleTrekPointAttributes(QVector<TrackPoint> &data,
const QXmlStreamAttributes &attr);
void handleTrekPointData(QVector<TrackPoint> &data, QStringRef element,
const QString &value);
QXmlStreamReader _reader;
};
#endif // PARSER_H

97
src/poi.cpp Normal file
View File

@ -0,0 +1,97 @@
#include <QFile>
#include <QSet>
#include <QList>
#include "ll.h"
#include "poi.h"
bool POI::loadFile(const QString &fileName)
{
QFile file(fileName);
bool ret;
int ln = 1;
_error.clear();
if (!file.open(QFile::ReadOnly | QFile::Text)) {
_error = qPrintable(file.errorString());
return false;
}
while (!file.atEnd()) {
QByteArray line = file.readLine();
QList<QByteArray> list = line.split(',');
if (list.size() < 3) {
_error = QString("Parse error\nLine %1").arg(ln);
return false;
}
qreal lat = list[0].trimmed().toDouble(&ret);
if (!ret) {
_error = QObject::tr("Invalid latitude\nLine %1").arg(ln);
return false;
}
qreal lon = list[1].trimmed().toDouble(&ret);
if (!ret) {
_error = QObject::tr("Invalid longitude\nLine %1").arg(ln);
return false;
}
QByteArray ba = list[2].trimmed();
QPointF p;
Entry entry;
ll2mercator(QPointF(lon, lat), p);
entry.description = QString::fromUtf8(ba.data(), ba.size());
entry.coordinates = p;
_data.append(entry);
ln++;
}
for (int i = 0; i < _data.size(); ++i) {
qreal c[2];
c[0] = _data.at(i).coordinates.x();
c[1] = _data.at(i).coordinates.y();
_tree.Insert(c, c, &_data.at(i));
}
return true;
}
static bool cb(const Entry* data, void* context)
{
QSet<const Entry*> *set = (QSet<const Entry*>*) context;
set->insert(data);
return true;
}
#define RECT 0.01
QVector<Entry> POI::points(const QVector<QPointF> &path) const
{
QVector<Entry> ret;
QSet<const Entry*> set;
qreal min[2], max[2];
for (int i = 0; i < path.count(); i++) {
min[0] = path.at(i).x() - RECT;
min[1] = path.at(i).y() - RECT;
max[0] = path.at(i).x() + RECT;
max[1] = path.at(i).y() + RECT;
_tree.Search(min, max, cb, &set);
}
QSet<const Entry *>::const_iterator i = set.constBegin();
while (i != set.constEnd()) {
ret.append(*(*i));
++i;
}
return ret;
}
void POI::clear()
{
_tree.RemoveAll();
_data.clear();
}

43
src/poi.h Normal file
View File

@ -0,0 +1,43 @@
#ifndef POI_H
#define POI_H
#include <QVector>
#include <QPointF>
#include <QString>
#include "rtree.h"
class Entry
{
public:
QPointF coordinates;
QString description;
bool operator==(const Entry &other) const
{return this->description == other.description
&& this->coordinates == other.coordinates;}
};
inline uint qHash(const Entry &key)
{
return ::qHash(key.description);
}
class POI
{
public:
bool loadFile(const QString &fileName);
QString errorString() const {return _error;}
QVector<Entry> points(const QVector<QPointF> &path) const;
void clear();
private:
typedef RTree<const Entry*, qreal, 2> POITree;
POITree _tree;
QVector<Entry> _data;
QString _error;
};
#endif // POI_H

51
src/poiitem.cpp Normal file
View File

@ -0,0 +1,51 @@
#include <QPainter>
#include "poiitem.h"
#include <QDebug>
#define FONT_FAMILY "Arial"
#define FONT_SIZE 12
#define POINT_SIZE 8
POIItem::POIItem(const QString &text)
{
_text = text;
updateBoundingRect();
}
void POIItem::updateBoundingRect()
{
QFont font;
font.setPixelSize(FONT_SIZE);
font.setFamily(FONT_FAMILY);
QFontMetrics fm(font);
QRect ts = fm.tightBoundingRect(_text);
_boundingRect = QRectF(0, 0, ts.width() + POINT_SIZE,
ts.height() + fm.descent() + POINT_SIZE);
}
void POIItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
QWidget *widget)
{
Q_UNUSED(option);
Q_UNUSED(widget);
QFont font;
font.setPixelSize(FONT_SIZE);
font.setFamily(FONT_FAMILY);
QFontMetrics fm(font);
QRect ts = fm.tightBoundingRect(_text);
painter->setFont(font);
painter->drawText(POINT_SIZE - qMax(ts.x(), 0), POINT_SIZE + ts.height(),
_text);
painter->setBrush(Qt::SolidPattern);
painter->drawEllipse(0, 0, POINT_SIZE, POINT_SIZE);
/*
painter->setPen(Qt::red);
painter->setBrush(Qt::NoBrush);
painter->drawRect(boundingRect());
*/
}

22
src/poiitem.h Normal file
View File

@ -0,0 +1,22 @@
#ifndef POIITEM_H
#define POIITEM_H
#include <QGraphicsItem>
class POIItem : public QGraphicsItem
{
public:
POIItem(const QString &text);
QRectF boundingRect() const {return _boundingRect;}
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
QWidget *widget);
private:
void updateBoundingRect();
QString _text;
QRectF _boundingRect;
};
#endif // POIITEM_H

1313
src/rtree.h Normal file

File diff suppressed because it is too large Load Diff

49
src/slideritem.cpp Normal file
View File

@ -0,0 +1,49 @@
#include <QPainter>
#include "slideritem.h"
#include <QDebug>
#define SIZE 10
SliderItem::SliderItem()
{
setFlag(ItemIsMovable);
setFlag(ItemSendsGeometryChanges);
}
QRectF SliderItem::boundingRect() const
{
return QRectF(-SIZE/2, -_area.height(), SIZE, _area.height());
}
void SliderItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
QWidget *widget)
{
Q_UNUSED(option);
Q_UNUSED(widget);
painter->setPen(Qt::red);
painter->drawLine(0, 0, 0, -_area.height());
//painter->drawRect(boundingRect());
}
QVariant SliderItem::itemChange(GraphicsItemChange change, const QVariant &value)
{
if (change == ItemPositionChange && scene()) {
QPointF pos = value.toPointF();
if (!_area.contains(QRectF(pos, boundingRect().size()))) {
pos.setX(qMin(_area.right(), qMax(pos.x(), _area.left())));
pos.setY(qMin(_area.bottom(), qMax(pos.y(), _area.top()
+ boundingRect().height())));
return pos;
}
}
if (change == ItemPositionHasChanged)
emit positionChanged(value.toPointF());
return QGraphicsItem::itemChange(change, value);
}

30
src/slideritem.h Normal file
View File

@ -0,0 +1,30 @@
#ifndef SLIDERITEM_H
#define SLIDERITEM_H
#include <QGraphicsItem>
class SliderItem : public QGraphicsObject
{
Q_OBJECT
public:
SliderItem();
QRectF boundingRect() const;
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
QWidget *widget);
QRectF area() {return _area;}
void setArea(const QRectF &area) {_area = area;}
signals:
void positionChanged(const QPointF&);
protected:
QVariant itemChange(GraphicsItemChange change, const QVariant &value);
private:
QRectF _area;
};
#endif // SLIDERITEM_H

192
src/track.cpp Normal file
View File

@ -0,0 +1,192 @@
#include <cmath>
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QPainterPath>
#include <QWheelEvent>
#include <QGraphicsEllipseItem>
#include "poiitem.h"
#include "markeritem.h"
#include "track.h"
#include <QDebug>
Track::Track(QWidget *parent)
: QGraphicsView(parent)
{
_scene = new QGraphicsScene(this);
setScene(_scene);
setResizeAnchor(QGraphicsView::AnchorViewCenter);
_maxLen = 0;
}
Track::~Track()
{
delete _scene;
}
void Track::loadData(const QVector<QPointF> &track)
{
QPainterPath path;
QGraphicsPathItem *pi;
MarkerItem *mi;
QColor color = _colorShop.color();
if (track.size() < 2)
return;
_tracks.append(track);
path.moveTo(track.at(0).x(), -track.at(0).y());
for (int i = 1; i < track.size(); i++)
path.lineTo(track.at(i).x(), -track.at(i).y());
_maxLen = qMax(path.length(), _maxLen);
for (int i = 0; i < _trackPaths.size(); i++) {
_trackPaths.at(i)->resetTransform();
_scene->removeItem(_markers.at(i));
}
QBrush brush(color, Qt::SolidPattern);
QPen pen(brush, 0);
pi = new QGraphicsPathItem(path);
pi->setPen(pen);
_scene->addItem(pi);
_trackPaths.append(pi);
QTransform t = transform();
mi = new MarkerItem();
mi->setPos(pi->path().pointAtPercent(0));
_markers.append(mi);
for (int i = 0; i < _trackPaths.size(); i++) {
_markers.at(i)->setTransform(t);
_scene->addItem(_markers.at(i));
}
_scene->setSceneRect(_scene->itemsBoundingRect());
fitInView(_scene->sceneRect(), Qt::KeepAspectRatio);
}
QTransform Track::transform() const
{
QPointF scale(_scene->itemsBoundingRect().width() / viewport()->width(),
_scene->itemsBoundingRect().height() / viewport()->height());
QTransform transform;
transform.scale(qMax(scale.x(), scale.y()), qMax(scale.x(), scale.y()));
return transform;
}
void Track::loadPOI(const POI &poi)
{
QHash<Entry, POIItem*>::const_iterator it,jt;
for (int i = 0; i < _tracks.size(); i++) {
QVector<Entry> p = poi.points(_tracks.at(i));
for (int i = 0; i < p.size(); i++) {
if (_pois.contains(p.at(i)))
continue;
POIItem *pi = new POIItem(p.at(i).description);
pi->setPos(p.at(i).coordinates.x(), -p.at(i).coordinates.y());
pi->setTransform(transform());
pi->setZValue(1);
_scene->addItem(pi);
_pois.insert(p.at(i), pi);
}
}
for (it = _pois.constBegin(); it != _pois.constEnd(); it++)
it.value()->setTransform(transform());
for (it = _pois.constBegin(); it != _pois.constEnd(); it++) {
for (jt = _pois.constBegin(); jt != _pois.constEnd(); jt++) {
if (it != jt && it.value()->isVisible() && jt.value()->isVisible()
&& it.value()->collidesWithItem(jt.value()))
jt.value()->hide();
}
}
_scene->setSceneRect(_scene->itemsBoundingRect());
fitInView(_scene->sceneRect(), Qt::KeepAspectRatio);
}
void Track::wheelEvent(QWheelEvent *event)
{
float factor;
factor = pow(2.0, -event->delta() / 400.0);
scale(factor, factor);
}
void Track::plot(QPainter *painter, const QRectF &target)
{
QRectF orig = sceneRect();
QRectF adj;
qreal ratio, diff;
if (target.width()/target.height() > orig.width()/orig.height()) {
ratio = target.width()/target.height();
diff = qAbs((orig.height() * ratio) - orig.width());
adj = orig.adjusted(-diff/2, 0, diff/2, 0);
} else {
ratio = target.height()/target.width();
diff = fabs((orig.width() * ratio) - orig.height());
adj = orig.adjusted(0, -diff/2, 0, diff/2);
}
for (int i = 0; i < _markers.size(); i++)
_markers.at(i)->setVisible(false);
_scene->render(painter, target, adj, Qt::KeepAspectRatioByExpanding);
for (int i = 0; i < _markers.size(); i++)
_markers.at(i)->setVisible(true);
}
enum QPrinter::Orientation Track::orientation() const
{
return (sceneRect().width() > sceneRect().height())
? QPrinter::Landscape : QPrinter::Portrait;
}
void Track::clearPOI()
{
QHash<Entry, POIItem*>::const_iterator it;
for (it = _pois.constBegin(); it != _pois.constEnd(); it++) {
_scene->removeItem(it.value());
delete it.value();
}
_pois.clear();
}
void Track::clear()
{
_pois.clear();
_tracks.clear();
_trackPaths.clear();
_markers.clear();
_scene->clear();
_colorShop.reset();
_maxLen = 0;
_scene->setSceneRect(0, 0, 0, 0);
}
void Track::movePositionMarker(qreal val)
{
for (int i = 0; i < _trackPaths.size(); i++) {
qreal f = _maxLen / _trackPaths.at(i)->path().length();
QPointF pos = _trackPaths.at(i)->path().pointAtPercent(qMin(val * f,
1.0));
_markers.at(i)->setPos(pos);
}
}

50
src/track.h Normal file
View File

@ -0,0 +1,50 @@
#ifndef TRACK_H
#define TRACK_H
#include <QGraphicsView>
#include <QVector>
#include <QHash>
#include <QList>
#include <QPrinter>
#include "poi.h"
#include "colorshop.h"
class POIItem;
class MarkerItem;
class Track : public QGraphicsView
{
Q_OBJECT
public:
Track(QWidget *parent = 0);
~Track();
void loadData(const QVector<QPointF> &track);
void loadPOI(const POI &poi);
void clearPOI();
void clear();
void plot(QPainter *painter, const QRectF &target);
enum QPrinter::Orientation orientation() const;
public slots:
void movePositionMarker(qreal val);
private:
QTransform transform() const;
void wheelEvent(QWheelEvent *event);
QGraphicsScene *_scene;
QList<QVector<QPointF> > _tracks;
QList<QGraphicsPathItem*> _trackPaths;
QList<MarkerItem*> _markers;
QHash<Entry, POIItem*> _pois;
ColorShop _colorShop;
qreal _maxLen;
};
#endif // TRACK_H