1 /**********************************************************************************************
2 Copyright (C) 2014 Oliver Eichler <oliver.eichler@gmx.de>
3 Copyright (C) 2015 Christian Eichler <code@christian-eichler.de>
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18 **********************************************************************************************/
19
20 #include "plot/CPlotAxis.h"
21 #include "plot/IPlot.h"
22
23 #include "CMainWindow.h"
24 #include "gis/CGisWorkspace.h"
25 #include "gis/trk/CActivityTrk.h"
26 #include "gis/wpt/CGisItemWpt.h"
27 #include "helpers/CDraw.h"
28 #include "helpers/CSettings.h"
29 #include "misc.h"
30 #include "mouse/CMouseAdapter.h"
31 #include "mouse/range/CScrOptRangeTrk.h"
32 #include "widgets/CFadingIcon.h"
33
34 #include <QKeyEvent>
35 #include <QtWidgets>
36
37 const QPen IPlot::pens[] =
38 {
39 QPen(Qt::darkBlue, 3, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin)
40 , QPen(QColor(0xFFC00000), 3, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin)
41 , QPen(Qt::yellow, 3, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin)
42 , QPen(Qt::green, 3, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin)
43 };
44
45 const QPen IPlot::pensThin[] =
46 {
47 QPen(Qt::darkBlue, 2, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin)
48 , QPen(Qt::darkRed, 2, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin)
49 , QPen(Qt::darkYellow, 2, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin)
50 , QPen(Qt::darkGreen, 2, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin)
51 };
52
53 const QColor IPlot::colors[] =
54 {
55 QColor(Qt::blue)
56 , QColor(0, 0, 0, 0)
57 , QColor(0, 0, 0, 0)
58 , QColor(Qt::darkGreen)
59 };
60
61 int IPlot::cnt = 0;
62
IPlot(CGisItemTrk * trk,CPlotData::axistype_e type,mode_e mode,QWidget * parent)63 IPlot::IPlot(CGisItemTrk* trk, CPlotData::axistype_e type, mode_e mode, QWidget* parent)
64 : QWidget(parent)
65 , INotifyTrk(CGisItemTrk::eVisualPlot)
66 , mode(mode)
67 , trk(trk)
68 , fm(font())
69 {
70 cnt++;
71 setObjectName(QString("IPlot%1").arg(cnt));
72
73 setContextMenuPolicy(Qt::CustomContextMenu);
74 setMouseTracking(true);
75
76 if(trk)
77 {
78 trk->registerVisual(this);
79 }
80
81 data = new CPlotData(type, this);
82
83 if((mode == eModeIcon) || (mode == eModeSimple))
84 {
85 showScale = false;
86 thinLine = true;
87 }
88
89 if(mode == eModeWindow)
90 {
91 setWindowFlags(Qt::Tool);
92 setAttribute(Qt::WA_DeleteOnClose, true);
93 QPalette pal = palette();
94 pal.setColor(QPalette::Background, Qt::white);
95 setPalette(pal);
96 }
97
98 menu = new QMenu(this);
99 actionResetZoom = menu->addAction(QIcon("://icons/32x32/Zoom.png"), tr("Reset Zoom"), this, &IPlot::slotResetZoom);
100 actionStopRange = menu->addAction(QIcon("://icons/32x32/SelectReset.png"), tr("Reset Range"), this, &IPlot::slotStopRange);
101 actionPrint = menu->addAction(QIcon("://icons/32x32/Save.png"), tr("Save..."), this, &IPlot::slotSave);
102 menu->addSeparator();
103 actionAddWpt = menu->addAction(QIcon("://icons/32x32/AddWpt.png"), tr("Add Waypoint"), this, &IPlot::slotAddWpt);
104 actionAddTrkPtInfo = menu->addAction(QIcon("://icons/32x32/AddPointInfo.png"), tr("Add Trackpoint Info"), this, &IPlot::slotAddTrkPtInfo);
105 actionCutTrk = menu->addAction(QIcon("://icons/32x32/TrkCut.png"), tr("Cut Track..."), this, &IPlot::slotCutTrk);
106
107 connect(this, &IPlot::customContextMenuRequested, this, &IPlot::slotContextMenu);
108 }
109
~IPlot()110 IPlot::~IPlot()
111 {
112 cnt--;
113
114 if(trk)
115 {
116 trk->unregisterVisual(this);
117 /*
118 Always set the mode to normal. If the object is not owner
119 of the current mode, the request will be ignored.
120 */
121 trk->setMode(CGisItemTrk::eModeNormal, objectName());
122
123 /*
124 As having the user focus will always display an on screen plot, closing
125 the plot has to result into the track loosing the focus.
126 */
127 if(mode == eModeWindow)
128 {
129 trk->looseUserFocus();
130 CCanvas* canvas = dynamic_cast<CCanvas*>(parent());
131 if(canvas)
132 {
133 canvas->saveSizeTrackProfile();
134 canvas->slotTriggerCompleteUpdate(CCanvas::eRedrawGis);
135 }
136 }
137 }
138 }
139
clear()140 void IPlot::clear()
141 {
142 needsRedraw = true;
143 data->lines.clear();
144 data->tags.clear();
145 data->badData = true;
146 update();
147 }
148
setXTicScale(qreal scale)149 void IPlot::setXTicScale(qreal scale)
150 {
151 data->x().setTicScale(scale);
152 setSizes();
153 update();
154 }
155
setYLabel(const QString & str)156 void IPlot::setYLabel(const QString& str)
157 {
158 if(mode == eModeSimple)
159 {
160 return;
161 }
162 data->ylabel = str;
163 setSizes();
164 update();
165 }
166
167
setXLabel(const QString & str)168 void IPlot::setXLabel(const QString& str)
169 {
170 if(mode == eModeSimple)
171 {
172 return;
173 }
174 data->xlabel = str;
175 setSizes();
176 update();
177 }
178
179
newLine(const QPolygonF & line,const QString & label)180 void IPlot::newLine(const QPolygonF& line, const QString& label)
181 {
182 data->lines.clear();
183
184 QRectF r = line.boundingRect();
185 if((r.height() < 0) || (r.width() < 0) || line.isEmpty())
186 {
187 data->badData = true;
188 return;
189 }
190
191 CPlotData::line_t l;
192 l.points = line;
193 l.label = label;
194
195 data->badData = false;
196 data->lines << l;
197 setSizes();
198 data->x().setScale( rectGraphArea.width() );
199 data->y().setScale( rectGraphArea.height() );
200
201 needsRedraw = true;
202 update();
203 }
204
addLine(const QPolygonF & line,const QString & label)205 void IPlot::addLine(const QPolygonF& line, const QString& label)
206 {
207 QRectF r = line.boundingRect();
208 if(!r.isValid() || line.isEmpty())
209 {
210 return;
211 }
212
213 CPlotData::line_t l;
214 l.points = line;
215 l.label = label;
216
217 data->lines << l;
218 setSizes();
219 data->x().setScale( rectGraphArea.width() );
220 data->y().setScale( rectGraphArea.height() );
221
222 needsRedraw = true;
223 update();
224 }
225
226
setLimits()227 void IPlot::setLimits()
228 {
229 data->setLimits();
230 }
231
resetZoom()232 void IPlot::resetZoom()
233 {
234 data->x().resetZoom();
235 data->y().resetZoom();
236 setSizes();
237
238 needsRedraw = true;
239 update();
240 }
241
242
paintEvent(QPaintEvent *)243 void IPlot::paintEvent(QPaintEvent* /*e*/)
244 {
245 QPainter p(this);
246 draw(p);
247 }
248
resizeEvent(QResizeEvent * e)249 void IPlot::resizeEvent(QResizeEvent* e)
250 {
251 setSizes();
252
253 buffer = QImage(e->size(), QImage::Format_ARGB32);
254
255 needsRedraw = true;
256 update();
257 }
258
leaveEvent(QEvent *)259 void IPlot::leaveEvent(QEvent* /*e*/)
260 {
261 needsRedraw = true;
262 posMouse1 = NOPOINT;
263
264 CCanvas::restoreOverrideCursor("IPlot::leaveEvent");
265 update();
266 }
267
268
enterEvent(QEvent *)269 void IPlot::enterEvent(QEvent* /*e*/)
270 {
271 needsRedraw = true;
272 QCursor cursor = QCursor(QPixmap(":/cursors/cursorArrow.png"), 0, 0);
273 CCanvas::setOverrideCursor(cursor, "IPlot::enterEvent");
274 update();
275 }
276
draw(QPainter & p)277 void IPlot::draw(QPainter& p)
278 {
279 if(needsRedraw)
280 {
281 draw();
282 needsRedraw = false;
283 }
284
285 p.drawImage(0, 0, buffer);
286 drawDecoration(p);
287 }
288
keyPressEvent(QKeyEvent * e)289 void IPlot::keyPressEvent(QKeyEvent* e)
290 {
291 // close the current window if `Esc` was pressed
292 if(Qt::Key_Escape == e->key())
293 {
294 e->accept();
295 deleteLater();
296 }
297 else
298 {
299 QWidget::keyPressEvent(e);
300 }
301 }
302
graphAreaContainsMousePos(QPoint & pos)303 bool IPlot::graphAreaContainsMousePos(QPoint& pos)
304 {
305 if(rectGraphArea.contains(pos))
306 {
307 return true;
308 }
309
310 if((pos.y() < rectGraphArea.bottom()) && (pos.y() > rectGraphArea.top()))
311 {
312 if(pos.x() < rectGraphArea.left())
313 {
314 pos.rx() = rectGraphArea.left();
315 }
316
317 if(pos.x() > rectGraphArea.right())
318 {
319 pos.rx() = rectGraphArea.right();
320 }
321
322 return true;
323 }
324
325 return false;
326 }
327
mouseMoveEvent(QMouseEvent * e)328 void IPlot::mouseMoveEvent(QMouseEvent* e)
329 {
330 if(data->lines.isEmpty() || data->badData || !data->x().isValid() || !data->y().isValid())
331 {
332 return;
333 }
334
335 QPoint pos = e->pos();
336 if (!mouseDidMove
337 && (e->buttons() == Qt::LeftButton)
338 && ((pos - posLast).manhattanLength() >= CMouseAdapter::minimalMouseMovingDistance))
339 {
340 mouseDidMove = true;
341 }
342
343 if(mouseDidMove)
344 {
345 if(!scrOptRange.isNull())
346 {
347 delete scrOptRange;
348 }
349 QPoint diff = pos - posLast;
350
351 data->x().move(-diff.x());
352 data->y().move( diff.y());
353 needsRedraw = true;
354 update();
355
356 posLast = pos;
357 return;
358 }
359
360 posMouse1 = NOPOINT;
361 if(graphAreaContainsMousePos(pos))
362 {
363 posMouse1 = pos;
364
365 // set point of focus at track object
366 qreal x = data->x().pt2val(posMouse1.x() - left);
367 setMouseFocus(x, CGisItemTrk::eFocusMouseMove);
368
369 // update canvas if visible
370 CCanvas* canvas = CMainWindow::self().getVisibleCanvas();
371 if(canvas)
372 {
373 canvas->update();
374 }
375 e->accept();
376 }
377
378 update();
379 }
380
setMouseFocus(qreal pos,enum CGisItemTrk::focusmode_e fm)381 bool IPlot::setMouseFocus(qreal pos, enum CGisItemTrk::focusmode_e fm)
382 {
383 if(nullptr == trk)
384 {
385 return false;
386 }
387
388 if(fm == CGisItemTrk::eFocusMouseMove &&
389 trk->getMode() == CGisItemTrk::eModeRange &&
390 !is_in(trk->getRangState(), {
391 CGisItemTrk::eRangeStateIdle,
392 CGisItemTrk::eRangeStateClicked1st,
393 CGisItemTrk::eRangeStateMove1st,
394 CGisItemTrk::eRangeStateMove2nd,
395 }))
396 {
397 return false;
398 }
399
400 if(data->axisType == CPlotData::eAxisLinear)
401 {
402 return trk->setMouseFocusByDistance(pos, fm, objectName());
403 }
404 else if(data->axisType == CPlotData::eAxisTime)
405 {
406 return trk->setMouseFocusByTime(pos, fm, objectName());
407 }
408
409 return false;
410 }
411
mousePressEvent(QMouseEvent * e)412 void IPlot::mousePressEvent(QMouseEvent* e)
413 {
414 if((e->button() == Qt::LeftButton) && (mode == eModeIcon))
415 {
416 trk->edit();
417 }
418
419 mouseDidMove = false;
420 posLast = e->pos();
421 }
422
mouseReleaseEvent(QMouseEvent * e)423 void IPlot::mouseReleaseEvent(QMouseEvent* e)
424 {
425 bool wasProcessed = false;
426
427 if(e->button() == Qt::LeftButton)
428 {
429 if((mode == eModeIcon) || mouseDidMove)
430 {
431 mouseDidMove = false;
432 return;
433 }
434 else if(mode == eModeSimple)
435 {
436 wasProcessed = mouseReleaseEventSimple(e);
437 }
438 else
439 {
440 wasProcessed = mouseReleaseEventNormal(e);
441 }
442 }
443
444
445 // Update canvas only if the object is the owner of the range selection
446 if(wasProcessed)
447 {
448 emit sigMouseClickState(mouseClickState);
449
450 // update canvas if visible
451 CCanvas* canvas = CMainWindow::self().getVisibleCanvas();
452 if(canvas)
453 {
454 canvas->update();
455 }
456 }
457 e->accept();
458 update();
459 }
460
mouseReleaseEventSimple(QMouseEvent * e)461 bool IPlot::mouseReleaseEventSimple(QMouseEvent* e)
462 {
463 QPoint pos = e->pos();
464 posMouse1 = graphAreaContainsMousePos(pos) ? pos : NOPOINT;
465
466 // set point of focus at track object
467 qreal x = data->x().pt2val(posMouse1.x() - left);
468
469 bool wasProcessed = setMouseFocus(x, CGisItemTrk::eFocusMouseClick);
470 if(!wasProcessed)
471 {
472 new CFadingIcon(posMouse1, "://icons/48x48/NotPossible.png", this);
473 }
474
475 return wasProcessed;
476 }
477
mouseReleaseEventNormal(QMouseEvent * e)478 bool IPlot::mouseReleaseEventNormal(QMouseEvent* e)
479 {
480 bool wasProcessed = true;
481
482 QPoint pos = e->pos();
483 posMouse1 = graphAreaContainsMousePos(pos) ? pos : NOPOINT;
484
485 // set point of focus at track object
486 qreal x = data->x().pt2val(posMouse1.x() - left);
487
488 switch(mouseClickState)
489 {
490 case eMouseClickIdle:
491 {
492 // In idle state a mouse click will select the first point of a range
493 if(trk->setMode(CGisItemTrk::eModeRange, objectName()))
494 {
495 setMouseFocus(x, CGisItemTrk::eFocusMouseClick);
496 mouseClickState = eMouseClick1st;
497 }
498 else
499 {
500 /*
501 If the object is not the owner of the range selection, no action has to be taken.
502 However the user has to be informed, that he clicked on the wrong widget.
503 */
504 new CFadingIcon(posMouse1, "://icons/48x48/NotPossible.png", this);
505 wasProcessed = false;
506 }
507 break;
508 }
509
510 case eMouseClick1st:
511 {
512 // In 1st click state a mouse click will select the second point of a range and display options
513 setMouseFocus(x, CGisItemTrk::eFocusMouseClick);
514 /*
515 As the screen option is created on the fly it has to be connected to all slots,too.
516 Later, when destroyed the slots will be disconnected automatically.
517 */
518 delete scrOptRange;
519 scrOptRange = new CScrOptRangeTrk(pos, trk, nullptr, this);
520 connect(scrOptRange->toolHidePoints, &QToolButton::clicked, this, &IPlot::slotHidePoints);
521 connect(scrOptRange->toolShowPoints, &QToolButton::clicked, this, &IPlot::slotShowPoints);
522 connect(scrOptRange->toolCopy, &QToolButton::clicked, this, &IPlot::slotCopy);
523 connect(scrOptRange->toolActivity, &QToolButton::clicked, this, &IPlot::slotActivity);
524
525 /* Adjust position of screen option widget if the widget is out of the visible area*/
526 QRect r1 = scrOptRange->geometry();
527 QRect r2 = geometry();
528 r1.moveTopLeft(mapToParent(r1.topLeft()));
529 if(!r2.contains(r1))
530 {
531 // test if screen option is out of area on the right side
532 if(!r2.contains(r1.topRight()))
533 {
534 QPoint pt = QPoint(r2.width(), r2.height()) - QPoint(r1.width(), r1.height());
535 scrOptRange->move(pt);
536 }
537 // test if screen option is out of area on the left side
538 else if(!r2.contains(r1.topLeft()))
539 {
540 QPoint pt = QPoint(0, r2.height()) - QPoint(0, r1.height());
541 scrOptRange->move(pt);
542 }
543 // test if screen option is out of area on the bottom
544 else if(!r2.contains(r1.bottomLeft()))
545 {
546 QPoint pt = QPoint(r1.left(), r2.height()) - QPoint(r2.left(), r1.height());
547 scrOptRange->move(pt);
548 }
549 }
550
551 mouseClickState = eMouseClick2nd;
552 break;
553 }
554
555 case eMouseClick2nd:
556 {
557 // In second click state a mouse click will reset the range selection
558 delete scrOptRange;
559 trk->setMode(CGisItemTrk::eModeNormal, objectName());
560 idxSel1 = idxSel2 = NOIDX;
561 mouseClickState = eMouseClickIdle;
562 break;
563 }
564 }
565
566 return wasProcessed;
567 }
568
wheelEvent(QWheelEvent * e)569 void IPlot::wheelEvent(QWheelEvent* e)
570 {
571 bool in = CMainWindow::self().flipMouseWheel() ? (e->delta() < 0) : (e->delta() > 0);
572
573 bool doHorizontalZoom = false;
574 bool doVerticalZoom = false;
575
576 switch(QApplication::keyboardModifiers())
577 {
578 case Qt::AltModifier:
579 doHorizontalZoom = true;
580 break;
581
582 case Qt::ControlModifier:
583 doVerticalZoom = true;
584 break;
585
586 case Qt::NoModifier:
587 doHorizontalZoom = true;
588 doVerticalZoom = true;
589 break;
590 }
591
592 if(doHorizontalZoom)
593 {
594 data->x().zoom(in, e->pos().x() - left);
595 setSizes();
596 data->x().setScale(rectGraphArea.width());
597 }
598
599 if(doVerticalZoom)
600 {
601 data->y().zoom(in, bottom - e->pos().y());
602 setSizes();
603 data->y().setScale(rectGraphArea.height());
604 }
605
606
607 QPoint p = mapToGlobal(e->pos() + QPoint(32, 0));
608 QToolTip::showText(p, tr("Hold CTRL key for vertical zoom, only.\nHold ALT key for horizontal zoom, only."), this, QRect(), 500);
609 needsRedraw = true;
610 update();
611
612 e->accept();
613 }
614
setSizes()615 void IPlot::setSizes()
616 {
617 fm = QFontMetrics(CMainWindow::self().getMapFont());
618 left = 0;
619
620 scaleWidthX1 = showScale ? data->x().getScaleWidth( fm ) : 0;
621 scaleWidthY1 = showScale ? data->y().getScaleWidth( fm ) : 0;
622
623 scaleWidthY1 = (scaleWidthX1 / 2) > scaleWidthY1 ? scaleWidthX1 / 2 : scaleWidthY1;
624
625 fontWidth = fm.maxWidth();
626 fontHeight = fm.height();
627
628 if(mode == eModeSimple)
629 {
630 deadAreaX = 0;
631 deadAreaY = 0;
632 }
633 else
634 {
635 deadAreaX = fontWidth >> 1;
636 deadAreaY = ( fontHeight + 1 ) >> 1;
637 }
638
639 iconBarHeight = height() > 350 ? 21 : 9;
640
641 setLRTB();
642 setSizeIconArea();
643 setSizeXLabel();
644 setSizeYLabel();
645 setSizeTrackInfo();
646 setSizeDrawArea();
647 }
648
setLRTB()649 void IPlot::setLRTB()
650 {
651 left = 0;
652
653 left += data->ylabel.isEmpty() ? 0 : fontHeight;
654 left += scaleWidthY1;
655 left += deadAreaX;
656
657 right = size().width();
658 right -= deadAreaX;
659 right -= scaleWidthX1 / 2;
660
661 top = 0;
662 if(!data->tags.isEmpty())
663 {
664 top += iconBarHeight;
665 }
666 top += deadAreaY;
667
668 bottom = size().height();
669 bottom -= data->xlabel.isEmpty() ? 0 : fontHeight;
670 // tick marks
671 if(scaleWidthX1)
672 {
673 bottom -= fontHeight;
674 }
675 bottom -= deadAreaY;
676
677 if(!data->xlabel.isEmpty())
678 {
679 bottom -= deadAreaY;
680 }
681 }
682
683
setSizeIconArea()684 void IPlot::setSizeIconArea()
685 {
686 rectIconArea = QRect(left, deadAreaY, right - left, 16 + fontHeight + deadAreaY);
687 }
688
setSizeXLabel()689 void IPlot::setSizeXLabel()
690 {
691 if(mode == IPlot::eModeSimple)
692 {
693 rectX1Label = {0, 0, 0, 0};
694 }
695
696 int y;
697 if ( data->xlabel.isEmpty() )
698 {
699 rectX1Label = {0, 0, 0, 0};
700 }
701 else
702 {
703 rectX1Label.setWidth( right - left );
704 rectX1Label.setHeight( fontHeight );
705 y = ( size().height() - rectX1Label.height()) - deadAreaY;
706
707 rectX1Label.moveTopLeft( QPoint( left, y ) );
708 }
709 }
710
setSizeYLabel()711 void IPlot::setSizeYLabel()
712 {
713 if(mode == IPlot::eModeSimple)
714 {
715 rectX1Label = {0, 0, 0, 0};
716 }
717
718 if ( data->ylabel.isEmpty() )
719 {
720 rectY1Label = {0, 0, 0, 0};
721 }
722 else
723 {
724 rectY1Label.setWidth( bottom - top );
725 rectY1Label.setHeight( fontHeight );
726 rectY1Label.moveTopLeft( QPoint( size().height() - bottom, 0 ) );
727 }
728 }
729
setSizeTrackInfo()730 void IPlot::setSizeTrackInfo()
731 {
732 if(data->tags.isEmpty() /*|| !CResources::self().showTrackProfileEleInfo()*/)
733 {
734 rectTrackInfo = QRect();
735 return;
736 }
737
738 rectTrackInfo.setWidth(right - left);
739 rectTrackInfo.setHeight(qMax(fontHeight, iconBarHeight));
740 rectTrackInfo.moveLeft(left);
741 rectTrackInfo.moveTop(size().height() - fontHeight);
742 }
743
setSizeDrawArea()744 void IPlot::setSizeDrawArea()
745 {
746 rectGraphArea.setWidth( right - left );
747 rectGraphArea.setHeight( bottom - top );
748 rectGraphArea.moveTopLeft( QPoint( left, top ) );
749
750 data->x().setScale( rectGraphArea.width() );
751 data->y().setScale( rectGraphArea.height() );
752 }
753
754
draw()755 void IPlot::draw()
756 {
757 buffer.fill(Qt::transparent);
758 QPainter p(&buffer);
759 USE_ANTI_ALIASING(p, true);
760
761 if((mode == eModeNormal) || (mode == eModeSimple))
762 {
763 p.fillRect(rect(), Qt::white);
764 }
765 else if(mode == eModeIcon)
766 {
767 QRect r = rect();
768 r.adjust(2, 2, -2, -2);
769 if(underMouse() || posMouse1 != NOPOINT || solid)
770 {
771 p.setPen(solid ? CDraw::penBorderBlack : CDraw::penBorderBlue);
772 p.setOpacity(1.0);
773 }
774 else
775 {
776 p.setPen(CDraw::penBorderBlack);
777 p.setOpacity(0.6);
778 }
779 p.setBrush(QColor(255, 255, 255, 255));
780
781 PAINT_ROUNDED_RECT(p, r);
782 }
783
784 if(data->lines.isEmpty() || data->badData || !data->x().isValid() || !data->y().isValid())
785 {
786 p.drawText(rect(), Qt::AlignCenter, tr("No or bad data."));
787 return;
788 }
789
790 p.setFont(CMainWindow::self().getMapFont());
791 drawTags(p);
792 p.setClipping(true);
793 p.setClipRect(rectGraphArea);
794 drawData(p);
795 p.setClipping(false);
796 drawLabels(p);
797 if(showScale)
798 {
799 drawXScale(p);
800 drawYScale(p);
801 }
802 drawGridX(p);
803 drawGridY(p);
804 drawActivities(p);
805 drawXTic(p);
806 drawYTic(p);
807 p.setPen(QPen(Qt::black, 2));
808 p.drawRect(rectGraphArea);
809 drawLegend(p);
810
811 p.setClipping(false);
812 drawTagLabels(p);
813 }
814
getBasePoint(int ptx) const815 QPointF IPlot::getBasePoint(int ptx) const
816 {
817 CPlotAxis& yaxis = data->y();
818
819 if(0 >= data->ymin && 0 <= data->ymax)
820 {
821 return QPointF(ptx, bottom - yaxis.val2pt(0));
822 }
823 else if(data->ymin >= 0)
824 {
825 return QPointF(ptx, bottom - yaxis.val2pt(data->ymin));
826 }
827 else if(data->ymax <= 0)
828 {
829 return QPointF(ptx, bottom - yaxis.val2pt(data->ymax));
830 }
831
832 qWarning() << "Requesting basePoint for ptx = " << ptx << "; data->ymin/max = {" << data->ymin << ", " << data->ymax << "}";
833 return QPointF(ptx, bottom);
834 }
835
getVisiblePolygon(const QPolygonF & polyline,QPolygonF & line) const836 QPolygonF IPlot::getVisiblePolygon(const QPolygonF& polyline, QPolygonF& line) const
837 {
838 const CPlotAxis& xaxis = data->x();
839 const CPlotAxis& yaxis = data->y();
840
841 int ptx = NOINT;
842 int pty = NOINT;
843
844 for(const QPointF& pt : polyline)
845 {
846 int oldPtx = ptx;
847 int oldPty = pty;
848 ptx = left + xaxis.val2pt( pt.x() );
849 pty = bottom - yaxis.val2pt( pt.y() );
850
851 if(ptx >= left && ptx <= right)
852 {
853 // if oldPtx is < left, then ptx is the first visible point
854 if(NOINT == oldPtx || oldPtx < left)
855 {
856 // we may need to interpolate things if we just found the first visible point
857 if(NOINT != oldPtx && ptx > left)
858 {
859 line << getBasePoint(left);
860
861 int intPty = oldPty + ((oldPty - pty) * (left - oldPtx)) / (oldPtx - ptx);
862 line << QPointF(left, intPty);
863 }
864 else
865 {
866 line << getBasePoint(ptx);
867 }
868 }
869
870 line << QPointF(ptx, pty);
871 }
872 else if(ptx > right)
873 {
874 // handle the special case `no point in the visible interval`
875 // -> add interpolated left point
876 if(oldPtx < left)
877 {
878 oldPty = oldPty + (pty - oldPty) / (left - oldPtx);
879 oldPtx = left;
880
881 line << getBasePoint(oldPtx);
882 line << QPointF(oldPtx, oldPty);
883 }
884
885 // interpolate the value at `right`
886 pty = (ptx - oldPtx) == 0 ? NOINT : oldPty + ((pty - oldPty) * (right - oldPtx)) / (ptx - oldPtx);
887 ptx = right;
888 line << QPointF(ptx, pty);
889 }
890
891 if(ptx >= right)
892 {
893 break;
894 }
895 }
896 line << getBasePoint(ptx);
897 return line;
898 }
899
drawData(QPainter & p)900 void IPlot::drawData(QPainter& p)
901 {
902 int penIdx = 0;
903 const QList<CPlotData::line_t>& lines = data->lines;
904 for(const CPlotData::line_t& line : lines)
905 {
906 QPolygonF poly;
907 getVisiblePolygon(line.points, poly);
908
909 p.setPen(Qt::NoPen);
910 p.setBrush(colors[penIdx]);
911 p.drawPolygon(poly);
912
913 p.setPen(thinLine ? pensThin[penIdx++] : pens[penIdx++]);
914 p.setBrush(Qt::NoBrush);
915 poly.pop_front();
916 poly.pop_back();
917 p.drawPolyline(poly);
918 }
919 }
920
drawLabels(QPainter & p)921 void IPlot::drawLabels( QPainter& p )
922 {
923 if(mode == IPlot::eModeSimple)
924 {
925 return;
926 }
927
928 p.setPen(Qt::darkBlue);
929
930 if ( rectX1Label.isValid() )
931 {
932 p.drawText( rectX1Label, Qt::AlignCenter, data->xlabel );
933 }
934
935 p.save();
936 QMatrix m = p.matrix();
937 m.translate( 0, size().height() );
938 m.rotate( -90 );
939 p.setMatrix( m );
940
941 if ( rectY1Label.isValid() )
942 {
943 p.drawText( rectY1Label, Qt::AlignCenter, data->ylabel );
944 }
945 p.restore();
946 }
947
drawXScale(QPainter & p)948 void IPlot::drawXScale( QPainter& p )
949 {
950 QRect recText;
951
952 if ( data->x().getTicType() == CPlotAxis::eNoTic )
953 {
954 return;
955 }
956
957 p.setPen(Qt::darkBlue);
958 recText.setHeight( fontHeight );
959 recText.setWidth( scaleWidthX1 );
960
961 int ix_ = -1;
962
963 const int iy = bottom + deadAreaY;
964 const CPlotAxis::tic_t* t = data->x().ticmark();
965 while ( t )
966 {
967 int ix = left + data->x().val2pt( t->val ) - ( scaleWidthX1 + 1 ) / 2;
968 if ( ( ( ix_ < 0 ) || ( ( ix - ix_ ) > scaleWidthX1 + 5 ) ) && !t->lbl.isEmpty() )
969 {
970 recText.moveTopLeft( QPoint( ix, iy ) );
971 p.drawText( recText, Qt::AlignCenter, t->lbl );
972 ix_ = ix;
973 }
974 t = data->x().ticmark( t );
975 }
976
977 qreal limMin, limMax, useMin, useMax;
978 data->x().getLimits(limMin, limMax, useMin, useMax);
979
980 if(!isZoomed())
981 {
982 return;
983 }
984
985 qreal scale = (right - left) / (limMax - limMin);
986
987 int x = left + (useMin - limMin) * scale;
988 int y = bottom + 5;
989 int w = (useMax - useMin) * scale;
990
991 p.setPen(QPen(Qt::red, 3));
992 p.drawLine(x, y, x + w, y);
993 }
994
995
drawYScale(QPainter & p)996 void IPlot::drawYScale( QPainter& p )
997 {
998 QString format_single_prec;
999 QRect recText;
1000 if ( data->y().getTicType() == CPlotAxis::eNoTic )
1001 {
1002 return;
1003 }
1004
1005 p.setPen(Qt::darkBlue);
1006 recText.setHeight( fontHeight );
1007 recText.setWidth( scaleWidthY1 );
1008
1009 int ix = left - scaleWidthY1 - deadAreaX;
1010 int iy;
1011
1012 qreal limMin, limMax, useMin, useMax;
1013 data->y().getLimits(limMin, limMax, useMin, useMax);
1014
1015 // draw min/max labels 1st;
1016 QRect recTextMin;
1017 QRect recTextMax;
1018
1019 format_single_prec = data->y().fmtsgl(data->ymin);
1020 if(data->ymin >= useMin)
1021 {
1022 iy = bottom - data->y().val2pt( data->ymin ) - fontHeight / 2;
1023 recText.moveTopLeft( QPoint( ix, iy ) );
1024 p.drawText( recText, Qt::AlignRight, QString().sprintf( format_single_prec.toLatin1().data(), data->ymin ));
1025 recTextMin = recText;
1026 }
1027 format_single_prec = data->y().fmtsgl(data->ymax);
1028 if(data->ymax <= useMax)
1029 {
1030 iy = bottom - data->y().val2pt( data->ymax ) - fontHeight / 2;
1031 recText.moveTopLeft( QPoint( ix, iy ) );
1032 p.drawText( recText, Qt::AlignRight, QString().sprintf( format_single_prec.toLatin1().data(), data->ymax ));
1033 recTextMax = recText;
1034 }
1035
1036 // draw tic marks
1037 const CPlotAxis::tic_t* t = data->y().ticmark();
1038 while ( t )
1039 {
1040 iy = bottom - data->y().val2pt( t->val ) - fontHeight / 2;
1041
1042 recText.moveTopLeft( QPoint( ix, iy ) );
1043
1044 if(!recTextMin.intersects(recText) && !recTextMax.intersects(recText))
1045 {
1046 p.drawText( recText, Qt::AlignRight, t->lbl );
1047 }
1048
1049 t = data->y().ticmark( t );
1050 }
1051
1052 if(!isZoomed())
1053 {
1054 return;
1055 }
1056
1057 qreal scale = (top - bottom) / (limMax - limMin);
1058
1059 int x = left - 5;
1060 int y = bottom + (useMin - limMin) * scale;
1061 int h = (useMax - useMin) * scale;
1062
1063 p.setPen(QPen(Qt::red, 3));
1064 p.drawLine(x, y, x, y + h);
1065 }
1066
1067
drawGridX(QPainter & p)1068 void IPlot::drawGridX( QPainter& p )
1069 {
1070 CPlotAxis::tictype_e oldtic = data->x().setTicType( CPlotAxis::eTicNorm );
1071
1072 const int dy = rectGraphArea.height();
1073 const CPlotAxis::tic_t* t = data->x().ticmark();
1074
1075 QPen oldpen = p.pen();
1076 p.setPen( QPen( QColor(0, 150, 0, 128), 1, Qt::DotLine ) );
1077
1078 const int iy = rectGraphArea.top();
1079 while ( t )
1080 {
1081 int ix = left + data->x().val2pt( t->val );
1082 p.drawLine( ix, iy, ix, iy + dy );
1083 t = data->x().ticmark( t );
1084 }
1085 p.setPen( oldpen );
1086 data->x().setTicType( oldtic );
1087 }
1088
1089
drawGridY(QPainter & p)1090 void IPlot::drawGridY( QPainter& p )
1091 {
1092 CPlotAxis::tictype_e oldtic = data->y().setTicType( CPlotAxis::eTicNorm );
1093 const int dx = rectGraphArea.width();
1094 const CPlotAxis::tic_t* t = data->y().ticmark();
1095
1096 QPen oldpen = p.pen();
1097 p.setPen( QPen( QColor(0, 150, 0, 128), 1, Qt::DotLine ) );
1098
1099 const int ix = rectGraphArea.left();
1100 while(nullptr != t)
1101 {
1102 int iy = bottom - data->y().val2pt( t->val );
1103 p.drawLine( ix, iy, ix + dx, iy );
1104 t = data->y().ticmark( t );
1105 }
1106
1107 // draw min/max lines
1108 qreal limMin, limMax, useMin, useMax;
1109 data->y().getLimits(limMin, limMax, useMin, useMax);
1110
1111 if(data->ymin > useMin)
1112 {
1113 int iy = bottom - data->y().val2pt( data->ymin );
1114 p.drawLine( ix, iy, ix + dx, iy );
1115 }
1116 if(data->ymax < useMax)
1117 {
1118 int iy = bottom - data->y().val2pt( data->ymax );
1119 p.drawLine( ix, iy, ix + dx, iy );
1120 }
1121
1122 p.setPen( oldpen );
1123 data->y().setTicType( oldtic );
1124 }
1125
drawXTic(QPainter & p)1126 void IPlot::drawXTic( QPainter& p )
1127 {
1128 const CPlotAxis::tic_t* t = data->x().ticmark();
1129
1130 p.setPen(QPen(Qt::black, 2));
1131 const int iyb = rectGraphArea.bottom();
1132 const int iyt = rectGraphArea.top();
1133 while(nullptr != t)
1134 {
1135 const int ix = left + data->x().val2pt( t->val );
1136 p.drawLine( ix, iyb, ix, iyb - 5 );
1137 p.drawLine( ix, iyt, ix, iyt + 5 );
1138 t = data->x().ticmark( t );
1139 }
1140 }
1141
1142
drawYTic(QPainter & p)1143 void IPlot::drawYTic( QPainter& p )
1144 {
1145 const CPlotAxis::tic_t* t = data->y().ticmark();
1146
1147 p.setPen(QPen(Qt::black, 2));
1148 const int ixl = rectGraphArea.left();
1149 const int ixr = rectGraphArea.right();
1150 while ( t )
1151 {
1152 const int iy = bottom - data->y().val2pt( t->val );
1153 p.drawLine( ixl, iy, ixl + 5, iy );
1154 p.drawLine( ixr, iy, ixr - 5, iy );
1155 t = data->y().ticmark( t );
1156 }
1157 }
1158
drawLegend(QPainter & p)1159 void IPlot::drawLegend(QPainter& p)
1160 {
1161 if((data->lines.size() < 2) || (mode == eModeIcon) || (mode == eModeSimple))
1162 {
1163 return;
1164 }
1165
1166 int penIdx = 0;
1167 QFontMetrics fm(p.font());
1168 int h = fm.height();
1169
1170 int x = rectGraphArea.left() + 10;
1171 int y = rectGraphArea.top() + 2 + h;
1172
1173 const QList<CPlotData::line_t>& lines = data->lines;
1174 for(const CPlotData::line_t& line : lines)
1175 {
1176 p.setPen(Qt::black);
1177 p.drawText(x + 30, y, line.label);
1178 p.setPen(pens[penIdx++]);
1179 p.drawLine(x, y, x + 20, y);
1180
1181 y += fm.height();
1182 }
1183 }
1184
drawDecoration(QPainter & p)1185 void IPlot::drawDecoration( QPainter& p )
1186 {
1187 if(posMouse1 != NOPOINT)
1188 {
1189 // draw the vertical `you are here` line
1190 int x = posMouse1.x();
1191 p.setPen(QPen(Qt::red, 2));
1192 if(x >= left && x <= right)
1193 {
1194 p.drawLine(x, top, x, bottom);
1195
1196 // check if the mouse is near a waypoint
1197 if(!showWptLabels)
1198 {
1199 for(const CPlotData::point_t& tag : qAsConst(data->tags))
1200 {
1201 int ptx = left + data->x().val2pt( tag.point.x() );
1202
1203 if(qAbs(x - ptx) >= 10)
1204 {
1205 continue;
1206 }
1207 QFont f = CMainWindow::self().getMapFont();
1208 f.setBold(true);
1209 QFontMetrics fm(f);
1210 QRect r = fm.boundingRect(tag.label);
1211 r.moveCenter(QPoint(ptx, top - fm.height() / 2 - fm.descent()));
1212 r.adjust(-3, -2, 3, 0);
1213
1214 p.setPen(Qt::NoPen);
1215 p.setBrush(Qt::white);
1216 p.drawRoundedRect(r, RECT_RADIUS, RECT_RADIUS);
1217
1218 p.setFont(f);
1219 p.setPen(Qt::darkBlue);
1220 p.drawText(r, Qt::AlignCenter, tag.label);
1221
1222 break;
1223 }
1224 }
1225 }
1226 }
1227
1228 if((idxSel1 != NOIDX) && (idxSel2 != NOIDX) && !data->badData)
1229 {
1230 p.setClipRect(rectGraphArea);
1231
1232 int penIdx = 3;
1233
1234 const QPolygonF& polyline = data->lines.first().points.mid(idxSel1, idxSel2 - idxSel1 + 1);
1235 QPolygonF line;
1236 getVisiblePolygon(polyline, line);
1237
1238 // avoid drawing if the whole interval is outside the visible range
1239 if(!(line.first().x() >= right || line.last().x() <= left))
1240 {
1241 // draw the background
1242 p.setPen(Qt::NoPen);
1243 p.setBrush(colors[penIdx]);
1244 p.drawPolygon(line);
1245
1246 // draw the foreground
1247 p.setPen(thinLine ? pensThin[penIdx] : pens[penIdx]);
1248 p.setBrush(Qt::NoBrush);
1249 line.pop_front();
1250 line.pop_back();
1251 p.drawPolyline(line);
1252
1253 p.setPen(QPen(Qt::darkBlue, 2));
1254 p.drawLine(line.first().x(), top, line.first().x(), bottom);
1255 p.drawLine(line.last().x(), top, line.last().x(), bottom);
1256 }
1257 p.setClipping(false);
1258 }
1259
1260 if(!scrOptRange.isNull())
1261 {
1262 scrOptRange->draw(p);
1263 }
1264 }
1265
drawTags(QPainter & p)1266 void IPlot::drawTags(QPainter& p)
1267 {
1268 if(data->tags.isEmpty())
1269 {
1270 return;
1271 }
1272
1273 QFont f = CMainWindow::self().getMapFont();
1274 f.setBold(true);
1275 p.setFont(f);
1276
1277 CPlotAxis& xaxis = data->x();
1278 CPlotAxis& yaxis = data->y();
1279
1280 for(const CPlotData::point_t& tag : qAsConst(data->tags))
1281 {
1282 int ptx = left + xaxis.val2pt( tag.point.x() );
1283 int pty = bottom - yaxis.val2pt( tag.point.y() );
1284
1285 if (!((left < ptx) && (ptx < right)))
1286 {
1287 continue;
1288 }
1289
1290 if( iconBarHeight >= pty)
1291 {
1292 continue;
1293 }
1294
1295 QPixmap icon = tag.icon.scaled(iconBarHeight, iconBarHeight, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
1296 p.drawPixmap(ptx - icon.width() / 2, 2, icon);
1297
1298 if (pty > bottom)
1299 {
1300 pty = bottom;
1301 }
1302
1303 p.setPen(QPen(Qt::white, 3));
1304 p.drawLine(ptx, top, ptx, pty);
1305 p.setPen(QPen(Qt::black, 1));
1306 p.drawLine(ptx, top, ptx, pty);
1307 }
1308 }
1309
drawTagLabels(QPainter & p)1310 void IPlot::drawTagLabels(QPainter& p)
1311 {
1312 if(data->tags.isEmpty())
1313 {
1314 return;
1315 }
1316
1317 if(!showWptLabels)
1318 {
1319 return;
1320 }
1321
1322
1323 QFont f = CMainWindow::self().getMapFont();
1324 f.setBold(true);
1325 QFontMetrics fm(f);
1326 p.setFont(f);
1327
1328 CPlotAxis& xaxis = data->x();
1329
1330 for(const CPlotData::point_t& tag : qAsConst(data->tags))
1331 {
1332 int ptx = left + xaxis.val2pt( tag.point.x() );
1333
1334 if (!((left < ptx) && (ptx < right)))
1335 {
1336 continue;
1337 }
1338
1339 p.save();
1340 p.translate(ptx, top);
1341 p.rotate(90);
1342 p.translate(5, -3);
1343 CDraw::text(tag.label, p, fm.boundingRect(tag.label), Qt::black);
1344 p.restore();
1345 }
1346 }
1347
drawActivities(QPainter & p)1348 void IPlot::drawActivities(QPainter& p)
1349 {
1350 if((mode == eModeIcon) || (trk->getActivities().getAllActivities().isEmpty()))
1351 {
1352 return;
1353 }
1354
1355 if((trk->getActivities().getAllActivities().count() == 1)
1356 && (trk->getActivities().getAllActivities().toList().first() == CTrackData::trkpt_t::eAct20None))
1357 {
1358 return;
1359 }
1360
1361 const QList<CActivityTrk::range_t>& ranges = trk->getActivities().getActivityRanges();
1362
1363 int bar_height = (mode == eModeSimple) ? 18 : 26;
1364 int color_width = (mode == eModeSimple) ? 3 : 3;
1365 int icon_frame = (mode == eModeSimple) ? 12 : 20;
1366 int icon_size = (mode == eModeSimple) ? 8 : 16;
1367
1368 QRect rectClipping = QRect(0, 0, right - left, 27);
1369 p.save();
1370 p.setClipping(true);
1371 p.translate(left, bottom - bar_height);
1372 p.setBrush(QColor(0, 170, 0, 100));
1373 p.setPen(Qt::NoPen);
1374 p.drawRect(rectClipping);
1375
1376 QRect rectIconFrame(0, 0, icon_frame, icon_frame);
1377 QRect rectIcon(0, 0, icon_size, icon_size);
1378
1379 for(const CActivityTrk::range_t& range : ranges)
1380 {
1381 int x1, x2, y1 = 0;
1382 const CTrackData& trkData = trk->getTrackData();
1383
1384 const CTrackData::trkpt_t* trkptBeg = trkData.getTrkPtByTotalIndex(range.idxTotalBeg);
1385 const CTrackData::trkpt_t* trkptEnd = trkData.getTrkPtByTotalIndex(range.idxTotalEnd);
1386
1387 if(data->axisType == CPlotData::eAxisTime)
1388 {
1389 x1 = data->x().val2pt(trkptBeg->time.toTime_t());
1390 x2 = data->x().val2pt(trkptEnd->time.toTime_t());
1391 }
1392 else
1393 {
1394 x1 = data->x().val2pt(trkptBeg->distance);
1395 x2 = data->x().val2pt(trkptEnd->distance);
1396 }
1397
1398 if(trkptBeg != nullptr && !data->lines.isEmpty())
1399 {
1400 y1 = data->y().val2pt(data->lines[0].points[trkptBeg->idxVisible].y());
1401 }
1402
1403 const CActivityTrk::desc_t& desc = CActivityTrk::getDescriptor(range.activity);
1404
1405
1406 int d = (x2 - x1);
1407 if(d >= 20)
1408 {
1409 int c = x1 + d / 2;
1410
1411 p.setPen(QPen(desc.color, 1));
1412 rectIconFrame.moveCenter(QPoint(c, icon_frame / 2 + color_width));
1413 p.setBrush(QColor(255, 255, 255, 100));
1414 p.drawRoundedRect(rectIconFrame, RECT_RADIUS, RECT_RADIUS);
1415
1416 rectIcon.moveCenter(QPoint(c, icon_frame / 2 + color_width));
1417 p.drawPixmap(rectIcon, QPixmap(desc.iconSmall));
1418 }
1419
1420 p.setPen(QPen(Qt::darkGreen, 1));
1421 p.drawLine(x1, bar_height, x1, qMin(0, bar_height - y1));
1422
1423 p.setPen(QPen(desc.color, color_width, Qt::SolidLine, Qt::FlatCap));
1424 p.drawLine(x1, color_width / 2, x2, color_width / 2);
1425 }
1426
1427 p.restore();
1428 }
1429
save(QImage & image,const CTrackData::trkpt_t * pTrkpt)1430 void IPlot::save(QImage& image, const CTrackData::trkpt_t* pTrkpt)
1431 {
1432 resize(image.size());
1433 setSizes();
1434 buffer = QImage(image.size(), QImage::Format_ARGB32);
1435 draw();
1436 if(pTrkpt != nullptr)
1437 {
1438 posMouse1.rx() = left + data->x().val2pt(pTrkpt->distance);
1439 posMouse1.ry() = top + data->y().val2pt(pTrkpt->ele);
1440
1441 QPainter p(&buffer);
1442 drawDecoration(p);
1443 }
1444 image = buffer;
1445 }
1446
slotContextMenu(const QPoint & point)1447 void IPlot::slotContextMenu(const QPoint& point)
1448 {
1449 QPoint p = mapToGlobal(point);
1450
1451 if(mode == IPlot::eModeSimple)
1452 {
1453 actionResetZoom->setEnabled(isZoomed());
1454 actionStopRange->setEnabled(false);
1455 actionPrint->setEnabled(false);
1456 actionAddWpt->setEnabled(false);
1457 actionCutTrk->setEnabled(false);
1458 actionAddTrkPtInfo->setEnabled(false);
1459 }
1460 else
1461 {
1462 actionResetZoom->setEnabled(isZoomed());
1463 actionStopRange->setEnabled((mouseClickState != eMouseClickIdle) && !(idxSel1 == NOIDX || idxSel2 == NOIDX));
1464 actionPrint->setEnabled(mouseClickState != eMouseClick2nd);
1465 actionAddWpt->setDisabled(posMouse1 == NOPOINT);
1466 actionCutTrk->setDisabled(actionStopRange->isEnabled());
1467 }
1468 posMouse2 = posMouse1;
1469
1470 menu->exec(p);
1471
1472 posMouse2 = NOPOINT;
1473 }
1474
slotSave()1475 void IPlot::slotSave()
1476 {
1477 SETTINGS;
1478 QString path = cfg.value("Paths/lastGraphPath", QDir::homePath()).toString();
1479 QString filename = QFileDialog::getSaveFileName( this, tr("Select output file"), path, "PNG Image (*.png)");
1480
1481 if(filename.isEmpty())
1482 {
1483 return;
1484 }
1485
1486 QFileInfo fi(filename);
1487 if(fi.suffix().toLower() != "png")
1488 {
1489 filename += ".png";
1490 }
1491
1492 QImage img(size(), QImage::Format_ARGB32);
1493 QPainter p;
1494 p.begin(&img);
1495 p.fillRect(rect(), QBrush(Qt::white));
1496 draw(p);
1497 p.end();
1498
1499 img.save(filename);
1500
1501 path = fi.absolutePath();
1502 cfg.setValue("Paths/lastGraphPath", path);
1503 }
1504
1505
slotHidePoints()1506 void IPlot::slotHidePoints()
1507 {
1508 trk->hideSelectedPoints();
1509 slotStopRange();
1510 }
1511
slotShowPoints()1512 void IPlot::slotShowPoints()
1513 {
1514 trk->showSelectedPoints();
1515 slotStopRange();
1516 }
1517
slotActivity()1518 void IPlot::slotActivity()
1519 {
1520 CActivityTrk::getMenu(trk->getKey(), this, true);
1521 slotStopRange();
1522 }
1523
slotCopy()1524 void IPlot::slotCopy()
1525 {
1526 trk->copySelectedPoints();
1527 slotStopRange();
1528 }
1529
slotStopRange()1530 void IPlot::slotStopRange()
1531 {
1532 scrOptRange->deleteLater();
1533 trk->setMode(CGisItemTrk::eModeNormal, objectName());
1534 idxSel1 = idxSel2 = NOIDX;
1535 mouseClickState = eMouseClickIdle;
1536
1537 emit sigMouseClickState(mouseClickState);
1538
1539 // update canvas if visible
1540 CCanvas* canvas = CMainWindow::self().getVisibleCanvas();
1541 if(canvas)
1542 {
1543 canvas->update();
1544 }
1545 }
1546
slotResetZoom()1547 void IPlot::slotResetZoom()
1548 {
1549 data->x().resetZoom();
1550 data->y().resetZoom();
1551 setSizes();
1552
1553 needsRedraw = true;
1554 update();
1555 }
1556
1557
slotAddWpt()1558 void IPlot::slotAddWpt()
1559 {
1560 if(posMouse2 == NOPOINT)
1561 {
1562 return;
1563 }
1564
1565 const CTrackData::trkpt_t* trkpt = trk->getMouseMoveFocusPoint();
1566 if(trkpt == nullptr)
1567 {
1568 return;
1569 }
1570
1571 CGisWorkspace::self().addWptByPos({trkpt->lon, trkpt->lat});
1572 CCanvas::triggerCompleteUpdate(CCanvas::eRedrawGis);
1573 }
1574
slotAddTrkPtInfo()1575 void IPlot::slotAddTrkPtInfo()
1576 {
1577 if(posMouse2 == NOPOINT)
1578 {
1579 return;
1580 }
1581
1582 const CTrackData::trkpt_t* trkpt = trk->getMouseMoveFocusPoint();
1583 if(trkpt == nullptr)
1584 {
1585 return;
1586 }
1587
1588 trk->setMouseFocusByTotalIndex(trkpt->idxTotal, CGisItemTrk::eFocusMouseClick, "IPlot");
1589 CGisWorkspace::self().addTrkInfoByKey(trk->getKey());
1590 CCanvas::triggerCompleteUpdate(CCanvas::eRedrawGis);
1591 }
1592
slotCutTrk()1593 void IPlot::slotCutTrk()
1594 {
1595 // set point of mouse click focus to position of context menu stored in
1596 // secondary mouse point
1597 qreal x = data->x().pt2val(posMouse2.x() - left);
1598 setMouseFocus(x, CGisItemTrk::eFocusMouseClick);
1599
1600 /*
1601 Trigger cut by event not by direct call to API. This is because cutting the track
1602 might result into deleting the original one. The original one is the parent of this
1603 plot and needs to destroy it. This would be impossible if we are still in this method
1604 because the API call did not return yet.
1605 */
1606 CGisWorkspace::self().postEventForWks(new CEvtA2WCutTrk(trk->getKey()));
1607 }
1608
setMouseRangeFocus(const CTrackData::trkpt_t * ptRange1,const CTrackData::trkpt_t * ptRange2)1609 void IPlot::setMouseRangeFocus(const CTrackData::trkpt_t* ptRange1, const CTrackData::trkpt_t* ptRange2)
1610 {
1611 if(nullptr == ptRange1 || nullptr == ptRange2)
1612 {
1613 idxSel1 = NOIDX;
1614 idxSel2 = NOIDX;
1615 }
1616 else
1617 {
1618 if(ptRange1->idxTotal < ptRange2->idxTotal)
1619 {
1620 while(ptRange1->isHidden())
1621 {
1622 ptRange1++;
1623 }
1624 while(ptRange2->isHidden())
1625 {
1626 ptRange2--;
1627 }
1628 idxSel1 = ptRange1->idxVisible;
1629 idxSel2 = ptRange2->idxVisible;
1630 }
1631 else
1632 {
1633 while(ptRange1->isHidden())
1634 {
1635 ptRange1--;
1636 }
1637 while(ptRange2->isHidden())
1638 {
1639 ptRange2++;
1640 }
1641 idxSel1 = ptRange2->idxVisible;
1642 idxSel2 = ptRange1->idxVisible;
1643 }
1644 }
1645 update();
1646 }
1647
isZoomed() const1648 bool IPlot::isZoomed() const
1649 {
1650 bool zoomed = false;
1651 qreal limMin, limMax, useMin, useMax;
1652 data->x().getLimits(limMin, limMax, useMin, useMax);
1653 zoomed |= !((limMax - limMin) <= (useMax - useMin));
1654 data->y().getLimits(limMin, limMax, useMin, useMax);
1655 zoomed |= !((limMax - limMin) <= (useMax - useMin));
1656
1657 return zoomed;
1658 }
1659