1 #include <QSet>
2 #include <QGraphicsScene>
3 #include <QEvent>
4 #include <QMouseEvent>
5 #include <QScrollBar>
6 #include <QGraphicsSimpleTextItem>
7 #include <QPalette>
8 #include <QLocale>
9 #include "data/graph.h"
10 #include "opengl.h"
11 #include "axisitem.h"
12 #include "slideritem.h"
13 #include "sliderinfoitem.h"
14 #include "infoitem.h"
15 #include "griditem.h"
16 #include "graphitem.h"
17 #include "pathitem.h"
18 #include "format.h"
19 #include "graphicsscene.h"
20 #include "graphview.h"
21 
22 
23 #define MARGIN 10.0
24 
GraphView(QWidget * parent)25 GraphView::GraphView(QWidget *parent)
26 	: QGraphicsView(parent)
27 {
28 	_scene = new GraphicsScene(this);
29 	setScene(_scene);
30 
31 	setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
32 	setRenderHint(QPainter::Antialiasing, true);
33 	setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
34 	setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
35 	setBackgroundBrush(QBrush(palette().brush(QPalette::Base)));
36 
37 	_xAxis = new AxisItem(AxisItem::X);
38 	_xAxis->setZValue(1.0);
39 	_yAxis = new AxisItem(AxisItem::Y);
40 	_yAxis->setZValue(1.0);
41 	_slider = new SliderItem();
42 	_slider->setZValue(4.0);
43 	_sliderInfo = new SliderInfoItem(_slider);
44 	_sliderInfo->setZValue(4.0);
45 	_info = new InfoItem();
46 	_grid = new GridItem();
47 	_message = new QGraphicsSimpleTextItem(tr("Data not available"));
48 	_message->setBrush(QPalette().brush(QPalette::Disabled,
49 	  QPalette::WindowText));
50 
51 	connect(_slider, SIGNAL(positionChanged(const QPointF&)), this,
52 	  SLOT(emitSliderPositionChanged(const QPointF&)));
53 
54 	_width = 1;
55 
56 	_xScale = 1;
57 	_yScale = 1;
58 	_yOffset = 0;
59 
60 	_precision = 0;
61 	_minYRange = 0.01;
62 
63 	_sliderPos = 0;
64 
65 	_units = Metric;
66 	_graphType = Distance;
67 	_xLabel = tr("Distance");
68 
69 	_zoom = 1.0;
70 }
71 
~GraphView()72 GraphView::~GraphView()
73 {
74 	delete _xAxis;
75 	delete _yAxis;
76 	delete _slider;
77 	delete _info;
78 	delete _grid;
79 	delete _message;
80 }
81 
createXLabel()82 void GraphView::createXLabel()
83 {
84 	_xAxis->setLabel(QString("%1 [%2]").arg(_xLabel,
85 	  _xUnits.isEmpty() ? "-" : _xUnits));
86 }
87 
createYLabel()88 void GraphView::createYLabel()
89 {
90 	_yAxis->setLabel(QString("%1 [%2]").arg(_yLabel,
91 	  _yUnits.isEmpty() ? "-" : _yUnits));
92 }
93 
setYLabel(const QString & label)94 void GraphView::setYLabel(const QString &label)
95 {
96 	_yLabel = label;
97 	createYLabel();
98 }
99 
setYUnits(const QString & units)100 void GraphView::setYUnits(const QString &units)
101 {
102 	_yUnits = units;
103 	createYLabel();
104 }
105 
setXUnits()106 void GraphView::setXUnits()
107 {
108 	if (_graphType == Distance) {
109 		if (_units == Imperial) {
110 			if (bounds().width() < MIINM) {
111 				_xUnits = tr("ft");
112 				_xScale = M2FT;
113 			} else {
114 				_xUnits = tr("mi");
115 				_xScale = M2MI;
116 			}
117 		} else if (_units == Nautical) {
118 			if (bounds().width() < NMIINM) {
119 				_xUnits = tr("ft");
120 				_xScale = M2FT;
121 			} else {
122 				_xUnits = tr("nmi");
123 				_xScale = M2NMI;
124 			}
125 		} else {
126 			if (bounds().width() < KMINM) {
127 				_xUnits = tr("m");
128 				_xScale = 1;
129 			} else {
130 				_xUnits = tr("km");
131 				_xScale = M2KM;
132 			}
133 		}
134 	} else {
135 		    if (bounds().width() < MININS) {
136 				_xUnits = tr("s");
137 				_xScale = 1;
138 			} else if (bounds().width() < HINS) {
139 				_xUnits = tr("min");
140 				_xScale = MIN2S;
141 			} else {
142 				_xUnits = tr("h");
143 				_xScale = H2S;
144 			}
145 	}
146 
147 	createXLabel();
148 }
149 
setUnits(Units units)150 void GraphView::setUnits(Units units)
151 {
152 	_units = units;
153 
154 	for (int i = 0; i < _graphs.count(); i++)
155 		_graphs.at(i)->setUnits(units);
156 
157 	setXUnits();
158 
159 	redraw();
160 }
161 
setGraphType(GraphType type)162 void GraphView::setGraphType(GraphType type)
163 {
164 	_graphType = type;
165 	_bounds = QRectF();
166 
167 	for (int i = 0; i < _graphs.count(); i++) {
168 		GraphItem *gi = _graphs.at(i);
169 		gi->setGraphType(type);
170 		if (gi->bounds().isNull())
171 			removeItem(gi);
172 		else
173 			addItem(gi);
174 		_bounds |= gi->bounds();
175 	}
176 
177 	if (type == Distance)
178 		_xLabel = tr("Distance");
179 	else
180 		_xLabel = tr("Time");
181 	setXUnits();
182 
183 	redraw();
184 }
185 
showGrid(bool show)186 void GraphView::showGrid(bool show)
187 {
188 	_grid->setVisible(show);
189 }
190 
showSliderInfo(bool show)191 void GraphView::showSliderInfo(bool show)
192 {
193 	_sliderInfo->setVisible(show);
194 }
195 
addGraph(GraphItem * graph)196 void GraphView::addGraph(GraphItem *graph)
197 {
198 	connect(this, SIGNAL(sliderPositionChanged(qreal)), graph,
199 	  SLOT(emitSliderPositionChanged(qreal)));
200 
201 	_graphs.append(graph);
202 	if (!graph->bounds().isNull())
203 		_scene->addItem(graph);
204 	_bounds |= graph->bounds();
205 
206 	setXUnits();
207 }
208 
removeGraph(GraphItem * graph)209 void GraphView::removeGraph(GraphItem *graph)
210 {
211 	disconnect(this, SIGNAL(sliderPositionChanged(qreal)), graph,
212 	  SLOT(emitSliderPositionChanged(qreal)));
213 
214 	_graphs.removeOne(graph);
215 	_scene->removeItem(graph);
216 
217 	_bounds = QRectF();
218 	for (int i = 0; i < _graphs.count(); i++)
219 		_bounds |= _graphs.at(i)->bounds();
220 
221 	setXUnits();
222 }
223 
removeItem(QGraphicsItem * item)224 void GraphView::removeItem(QGraphicsItem *item)
225 {
226 	if (item->scene() == _scene)
227 		_scene->removeItem(item);
228 }
229 
addItem(QGraphicsItem * item)230 void GraphView::addItem(QGraphicsItem *item)
231 {
232 	if (item->scene() != _scene)
233 		_scene->addItem(item);
234 }
235 
bounds() const236 QRectF GraphView::bounds() const
237 {
238 	QRectF br(_bounds);
239 	br.moveTopLeft(QPointF(br.left(), -br.top() - br.height()));
240 	return br;
241 }
242 
redraw()243 void GraphView::redraw()
244 {
245 	if (!_graphs.isEmpty())
246 		redraw(viewport()->size() - QSizeF(MARGIN, MARGIN));
247 }
248 
redraw(const QSizeF & size)249 void GraphView::redraw(const QSizeF &size)
250 {
251 	QRectF r;
252 	QSizeF mx, my;
253 	RangeF rx, ry;
254 	qreal sx, sy;
255 
256 
257 	if (_bounds.isNull()) {
258 		removeItem(_xAxis);
259 		removeItem(_yAxis);
260 		removeItem(_slider);
261 		removeItem(_info);
262 		removeItem(_grid);
263 		addItem(_message);
264 		_scene->setSceneRect(_scene->itemsBoundingRect());
265 		return;
266 	}
267 
268 	removeItem(_message);
269 	addItem(_xAxis);
270 	addItem(_yAxis);
271 	addItem(_slider);
272 	addItem(_info);
273 	addItem(_grid);
274 
275 	rx = RangeF(bounds().left() * _xScale, bounds().right() * _xScale);
276 	ry = RangeF(bounds().top() * _yScale + _yOffset, bounds().bottom() * _yScale
277 	  + _yOffset);
278 	if (ry.size() < _minYRange * _yScale)
279 		ry.resize(_minYRange * _yScale);
280 
281 	_xAxis->setRange(rx);
282 	_yAxis->setRange(ry);
283 	mx = _xAxis->margin();
284 	my = _yAxis->margin();
285 
286 	r = _bounds;
287 	if (r.height() < _minYRange)
288 		r.adjust(0, -(_minYRange/2 - r.height()/2), 0,
289 		  _minYRange/2 - r.height()/2);
290 
291 	sx = (size.width() - (my.width() + mx.width())) / r.width();
292 	sy = (size.height() - (mx.height() + my.height())
293 	  - _info->boundingRect().height()) / r.height();
294 	sx *= _zoom;
295 
296 	for (int i = 0; i < _graphs.size(); i++)
297 		_graphs.at(i)->setScale(sx, sy);
298 
299 	QPointF p(r.left() * sx, r.top() * sy);
300 	QSizeF s(r.width() * sx, r.height() * sy);
301 	r = QRectF(p, s);
302 	if (r.height() < _minYRange * sy)
303 		r.adjust(0, -(_minYRange/2 * sy - r.height()/2), 0,
304 		  (_minYRange/2) * sy - r.height()/2);
305 	r = r.toRect();
306 
307 	_xAxis->setSize(r.width());
308 	_yAxis->setSize(r.height());
309 	_xAxis->setPos(r.bottomLeft());
310 	_yAxis->setPos(r.bottomLeft());
311 
312 	_grid->setSize(r.size());
313 	_grid->setTicks(_xAxis->ticks(), _yAxis->ticks());
314 	_grid->setPos(r.bottomLeft());
315 
316 	_slider->setArea(r);
317 	updateSliderPosition();
318 
319 	r |= _xAxis->sceneBoundingRect();
320 	r |= _yAxis->sceneBoundingRect();
321 	_info->setPos(r.topLeft() + QPointF(r.width()/2
322 	  - _info->boundingRect().width()/2, -_info->boundingRect().height()));
323 
324 	_scene->setSceneRect(_scene->itemsBoundingRect());
325 }
326 
resizeEvent(QResizeEvent * e)327 void GraphView::resizeEvent(QResizeEvent *e)
328 {
329 	redraw(e->size() - QSizeF(MARGIN, MARGIN));
330 
331 	QGraphicsView::resizeEvent(e);
332 }
333 
mousePressEvent(QMouseEvent * e)334 void GraphView::mousePressEvent(QMouseEvent *e)
335 {
336 	if (e->button() == Qt::LeftButton)
337 		newSliderPosition(mapToScene(e->pos()));
338 
339 	QGraphicsView::mousePressEvent(e);
340 }
341 
wheelEvent(QWheelEvent * e)342 void GraphView::wheelEvent(QWheelEvent *e)
343 {
344 	static int deg = 0;
345 
346 	deg += e->delta() / 8;
347 	if (qAbs(deg) < 15)
348 		return;
349 	deg = 0;
350 
351 	QPointF pos = mapToScene(e->pos());
352 	QRectF gr(_grid->boundingRect());
353 	QPointF r(pos.x() / gr.width(), pos.y() / gr.height());
354 
355 	_zoom = (e->delta() > 0) ? _zoom * 1.25 : qMax(_zoom / 1.25, 1.0);
356 	redraw();
357 
358 	QRectF ngr(_grid->boundingRect());
359 	QPointF npos(mapFromScene(QPointF(r.x() * ngr.width(),
360 	  r.y() * ngr.height())));
361 	QScrollBar *sb = horizontalScrollBar();
362 	sb->setSliderPosition(sb->sliderPosition() + npos.x() - e->pos().x());
363 
364 	QGraphicsView::wheelEvent(e);
365 }
366 
paintEvent(QPaintEvent * e)367 void GraphView::paintEvent(QPaintEvent *e)
368 {
369 	QRectF viewRect(mapToScene(rect()).boundingRect());
370 	_info->setPos(QPointF(viewRect.left() + (viewRect.width()
371 	  - _info->boundingRect().width())/2.0, _info->pos().y()));
372 
373 	QGraphicsView::paintEvent(e);
374 }
375 
plot(QPainter * painter,const QRectF & target,qreal scale)376 void GraphView::plot(QPainter *painter, const QRectF &target, qreal scale)
377 {
378 	QSizeF canvas = QSizeF(target.width() / scale, target.height() / scale);
379 
380 	setUpdatesEnabled(false);
381 	redraw(canvas);
382 	if (_slider->pos().x() == _slider->area().left())
383 		_slider->hide();
384 	_scene->render(painter, target);
385 	_slider->show();
386 	redraw();
387 	setUpdatesEnabled(true);
388 }
389 
clear()390 void GraphView::clear()
391 {
392 	_graphs.clear();
393 
394 	_slider->clear();
395 	_info->clear();
396 
397 	_palette.reset();
398 
399 	_bounds = QRectF();
400 	_sliderPos = 0;
401 	_zoom = 1.0;
402 
403 	_scene->setSceneRect(0, 0, 0, 0);
404 }
405 
updateSliderPosition()406 void GraphView::updateSliderPosition()
407 {
408 	if (_sliderPos <= bounds().right() && _sliderPos >= bounds().left()) {
409 		_slider->setPos((_sliderPos / bounds().width())
410 		  * _slider->area().width(), _slider->area().bottom());
411 		_slider->setVisible(true);
412 		updateSliderInfo();
413 	} else {
414 		_slider->setPos(_slider->area().left(), _slider->area().bottom());
415 		_slider->setVisible(false);
416 	}
417 }
418 
updateSliderInfo()419 void GraphView::updateSliderInfo()
420 {
421 	QLocale l(QLocale::system());
422 	qreal r = 0, y = 0;
423 	GraphItem *cardinal = (_graphs.count() == 1 || (_graphs.count() == 2
424 	  && _graphs.first()->secondaryGraph())) ? _graphs.first() : 0;
425 
426 	if (cardinal) {
427 		QRectF br(_bounds);
428 		if (br.height() < _minYRange)
429 			br.adjust(0, -(_minYRange/2 - br.height()/2), 0,
430 			  _minYRange/2 - br.height()/2);
431 
432 		y = cardinal->yAtX(_sliderPos);
433 		r = (y - br.bottom()) / br.height();
434 	}
435 
436 	qreal pos = (_sliderPos / bounds().width()) * _slider->area().width();
437 	SliderInfoItem::Side s = (pos + _sliderInfo->boundingRect().width()
438 	  > _slider->area().right()) ? SliderInfoItem::Left : SliderInfoItem::Right;
439 
440 	_sliderInfo->setSide(s);
441 	_sliderInfo->setPos(QPointF(0, _slider->boundingRect().height() * r));
442 	QString xText(_graphType == Time ? Format::timeSpan(_sliderPos,
443 	  bounds().width() > 3600) : l.toString(_sliderPos * _xScale, 'f', 1)
444 	  + UNIT_SPACE + _xUnits);
445 	QString yText((!cardinal) ? QString() : l.toString(-y * _yScale + _yOffset,
446 	  'f', _precision) + UNIT_SPACE + _yUnits);
447 	if (cardinal && cardinal->secondaryGraph()) {
448 		qreal delta = y - cardinal->secondaryGraph()->yAtX(_sliderPos);
449 		yText += " " + QChar(0x0394) + l.toString(-delta * _yScale + _yOffset,
450 		  'f', _precision) + UNIT_SPACE + _yUnits;
451 	}
452 	_sliderInfo->setText(xText, yText);
453 }
454 
emitSliderPositionChanged(const QPointF & pos)455 void GraphView::emitSliderPositionChanged(const QPointF &pos)
456 {
457 	if (_slider->area().width() <= 0)
458 		return;
459 
460 	_sliderPos = (pos.x() / _slider->area().width()) * bounds().width();
461 	_sliderPos = qMax(_sliderPos, bounds().left());
462 	_sliderPos = qMin(_sliderPos, bounds().right());
463 	updateSliderPosition();
464 
465 	emit sliderPositionChanged(_sliderPos);
466 }
467 
setSliderPosition(qreal pos)468 void GraphView::setSliderPosition(qreal pos)
469 {
470 	if (_graphs.isEmpty())
471 		return;
472 
473 	_sliderPos = pos;
474 	updateSliderPosition();
475 }
476 
newSliderPosition(const QPointF & pos)477 void GraphView::newSliderPosition(const QPointF &pos)
478 {
479 	if (_slider->area().contains(pos))
480 		_slider->setPos(pos);
481 }
482 
addInfo(const QString & key,const QString & value)483 void GraphView::addInfo(const QString &key, const QString &value)
484 {
485 	_info->insert(key, value);
486 }
487 
clearInfo()488 void GraphView::clearInfo()
489 {
490 	_info->clear();
491 }
492 
setPalette(const Palette & palette)493 void GraphView::setPalette(const Palette &palette)
494 {
495 	_palette = palette;
496 	_palette.reset();
497 
498 	QSet<GraphItem*> secondary;
499 	for (int i = 0; i < _graphs.count(); i++) {
500 		GraphItem *g = _graphs[i];
501 		if (g->secondaryGraph())
502 			secondary.insert(g->secondaryGraph());
503 	}
504 
505 	for (int i = 0; i < _graphs.count(); i++) {
506 		GraphItem *g = _graphs[i];
507 		if (secondary.contains(g))
508 			continue;
509 
510 		QColor color(_palette.nextColor());
511 		g->setColor(color);
512 		if (g->secondaryGraph())
513 			g->secondaryGraph()->setColor(color);
514 	}
515 }
516 
setGraphWidth(int width)517 void GraphView::setGraphWidth(int width)
518 {
519 	_width = width;
520 
521 	for (int i = 0; i < _graphs.count(); i++)
522 		_graphs.at(i)->setWidth(width);
523 
524 	redraw();
525 }
526 
useOpenGL(bool use)527 void GraphView::useOpenGL(bool use)
528 {
529 	if (use)
530 		setViewport(new OPENGL_WIDGET);
531 	else
532 		setViewport(new QWidget);
533 }
534 
useAntiAliasing(bool use)535 void GraphView::useAntiAliasing(bool use)
536 {
537 	setRenderHint(QPainter::Antialiasing, use);
538 }
539 
setSliderColor(const QColor & color)540 void GraphView::setSliderColor(const QColor &color)
541 {
542 	_slider->setColor(color);
543 	_sliderInfo->setColor(color);
544 }
545 
changeEvent(QEvent * e)546 void GraphView::changeEvent(QEvent *e)
547 {
548 	if (e->type() == QEvent::PaletteChange) {
549 		_message->setBrush(QPalette().brush(QPalette::Disabled,
550 		  QPalette::WindowText));
551 		setBackgroundBrush(QBrush(palette().brush(QPalette::Base)));
552 	}
553 
554 	QGraphicsView::changeEvent(e);
555 }
556