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