1 /******************************************************************************************************
2  * (C) 2014 markummitchell@github.com. This file is part of Engauge Digitizer, which is released      *
3  * under GNU General Public License version 2 (GPLv2) or (at your option) any later version. See file *
4  * LICENSE or go to gnu.org/licenses for details. Distribution requires prior written permission.     *
5  ******************************************************************************************************/
6 
7 #include "DataKey.h"
8 #include "EngaugeAssert.h"
9 #include "EnumsToQt.h"
10 #include "GeometryWindow.h"
11 #include "GraphicsItemType.h"
12 #include "GraphicsLinesForCurve.h"
13 #include "GraphicsPoint.h"
14 #include "GraphicsScene.h"
15 #include "LineStyle.h"
16 #include "Logger.h"
17 #include "Point.h"
18 #include "PointStyle.h"
19 #include <QGraphicsItem>
20 #include <QMap>
21 #include <QPainterPath>
22 #include <QPen>
23 #include <QTextStream>
24 #include "QtToString.h"
25 #include "Spline.h"
26 #include "SplineDrawer.h"
27 #include "Transformation.h"
28 #include "ZValues.h"
29 
30 using namespace std;
31 
32 typedef QMap<double, double> XOrThetaToOrdinal;
33 
GraphicsLinesForCurve(const QString & curveName)34 GraphicsLinesForCurve::GraphicsLinesForCurve(const QString &curveName) :
35   m_curveName (curveName)
36 {
37   setZValue (Z_VALUE_CURVE);
38   setData (DATA_KEY_GRAPHICS_ITEM_TYPE,
39            GRAPHICS_ITEM_TYPE_LINE);
40   setData (DATA_KEY_IDENTIFIER,
41            QVariant (m_curveName));
42 }
43 
~GraphicsLinesForCurve()44 GraphicsLinesForCurve::~GraphicsLinesForCurve()
45 {
46   OrdinalToGraphicsPoint::iterator itr;
47   for (itr = m_graphicsPoints.begin(); itr != m_graphicsPoints.end(); itr++) {
48     GraphicsPoint *point = itr.value();
49     delete point;
50   }
51 
52   m_graphicsPoints.clear();
53 }
54 
addPoint(const QString & pointIdentifier,double ordinal,GraphicsPoint & graphicsPoint)55 void GraphicsLinesForCurve::addPoint (const QString &pointIdentifier,
56                                       double ordinal,
57                                       GraphicsPoint &graphicsPoint)
58 {
59   LOG4CPP_INFO_S ((*mainCat)) << "GraphicsLinesForCurve::addPoint"
60                               << " curve=" << m_curveName.toLatin1().data()
61                               << " identifier=" << pointIdentifier.toLatin1().data()
62                               << " ordinal=" << ordinal
63                               << " pos=" << QPointFToString (graphicsPoint.pos()).toLatin1().data()
64                               << " newPointCount=" << (m_graphicsPoints.count() + 1);
65 
66   m_graphicsPoints [ordinal] = &graphicsPoint;
67 }
68 
drawLinesSmooth(const LineStyle & lineStyle,SplineDrawer & splineDrawer,QPainterPath & pathMultiValued,LineStyle & lineMultiValued)69 QPainterPath GraphicsLinesForCurve::drawLinesSmooth (const LineStyle &lineStyle,
70                                                      SplineDrawer &splineDrawer,
71                                                      QPainterPath &pathMultiValued,
72                                                      LineStyle &lineMultiValued)
73 {
74   LOG4CPP_INFO_S ((*mainCat)) << "GraphicsLinesForCurve::drawLinesSmooth"
75                               << " curve=" << m_curveName.toLatin1().data();
76 
77   QPainterPath path;
78 
79   // Prepare spline inputs. Note that the ordinal values may not start at 0
80   vector<double> t;
81   vector<SplinePair> xy;
82   OrdinalToGraphicsPoint::const_iterator itr;
83   for (itr = m_graphicsPoints.begin(); itr != m_graphicsPoints.end(); itr++) {
84 
85     double ordinal = itr.key();
86     const GraphicsPoint *point = itr.value();
87 
88     t.push_back (ordinal);
89     xy.push_back (SplinePair (point->pos ().x(),
90                               point->pos ().y()));
91   }
92 
93   // Spline class requires at least one point
94   if (xy.size() > 0) {
95 
96     // Spline through points
97     Spline spline (t, xy);
98 
99     splineDrawer.bindToSpline (lineStyle,
100                                m_graphicsPoints.count(),
101                                spline);
102 
103     // Create QPainterPath through the points. Loop has one segment per stop point,
104     // with first point handled outside first
105     int segment; // Only incremented after a draw, corresponding to finishing a segment
106     OrdinalToGraphicsPoint::const_iterator itr = m_graphicsPoints.begin();
107 
108     const GraphicsPoint *point = itr.value();
109     path.moveTo (point->pos ());
110     pathMultiValued.moveTo (point->pos ());
111     ++itr;
112 
113     for (segment = 0;
114          itr != m_graphicsPoints.end();
115          segment++, itr++) {
116 
117       const GraphicsPoint *point = itr.value();
118 
119       SplineDrawerOperation operation = splineDrawer.segmentOperation (segment);
120 
121       QPointF p1 (spline.p1 (unsigned (segment)).x(),
122                   spline.p1 (unsigned (segment)).y());
123       QPointF p2 (spline.p2 (unsigned (segment)).x(),
124                   spline.p2 (unsigned (segment)).y());
125 
126       switch (operation) {
127       case SPLINE_DRAWER_ENUM_VISIBLE_DRAW:
128         {
129           // Show this segment
130           path.cubicTo (p1,
131                         p2,
132                         point->pos ());
133         }
134         break;
135 
136       case SPLINE_DRAWER_ENUM_INVISIBLE_MOVE:
137 
138         // Hide this segment as a regular curve, and show it as the error curve
139         path.moveTo (point->pos ());
140 
141         // Show curveMultiValued instead in what would have been the original curve's path
142         OrdinalToGraphicsPoint::const_iterator itrBefore = itr - 1;
143         const GraphicsPoint *pointBefore = itrBefore.value();
144         pathMultiValued.moveTo (pointBefore->pos ());
145         pathMultiValued.cubicTo (p1,
146                                  p2,
147                                  point->pos ());
148         lineMultiValued = lineStyle; // Remember to not use the same line style
149         break;
150 
151       }
152 
153       // Always move to next point for curveMultiValued
154       pathMultiValued.moveTo (point->pos ());
155     }
156   }
157 
158   return path;
159 }
160 
drawLinesStraight(QPainterPath &)161 QPainterPath GraphicsLinesForCurve::drawLinesStraight (QPainterPath  & /* pathMultiValued */)
162 {
163   LOG4CPP_INFO_S ((*mainCat)) << "GraphicsLinesForCurve::drawLinesStraight"
164                               << " curve=" << m_curveName.toLatin1().data();
165 
166   QPainterPath path;
167 
168   // Create QPainterPath through the points
169   bool isFirst = true;
170   OrdinalToGraphicsPoint::const_iterator itr;
171   for (itr = m_graphicsPoints.begin(); itr != m_graphicsPoints.end(); itr++) {
172 
173     const GraphicsPoint *point = itr.value();
174 
175     if (isFirst) {
176       isFirst = false;
177       path.moveTo (point->pos ());
178     } else {
179       path.lineTo (point->pos ());
180     }
181   }
182 
183   return path;
184 }
185 
identifierToOrdinal(const QString & identifier) const186 double GraphicsLinesForCurve::identifierToOrdinal (const QString &identifier) const
187 {
188   LOG4CPP_INFO_S ((*mainCat)) << "GraphicsLinesForCurve::identifierToOrdinal"
189                               << " identifier=" << identifier.toLatin1().data();
190 
191   OrdinalToGraphicsPoint::const_iterator itr;
192   for (itr = m_graphicsPoints.begin(); itr != m_graphicsPoints.end(); itr++) {
193 
194     const GraphicsPoint *point = itr.value();
195 
196     if (point->data (DATA_KEY_IDENTIFIER) == identifier) {
197       return itr.key();
198     }
199   }
200 
201   ENGAUGE_ASSERT (false);
202 
203   return 0;
204 }
205 
lineMembershipPurge(const LineStyle & lineStyle,SplineDrawer & splineDrawer,QPainterPath & pathMultiValued,LineStyle & lineMultiValued)206 void GraphicsLinesForCurve::lineMembershipPurge (const LineStyle &lineStyle,
207                                                  SplineDrawer &splineDrawer,
208                                                  QPainterPath &pathMultiValued,
209                                                  LineStyle &lineMultiValued)
210 {
211   LOG4CPP_INFO_S ((*mainCat)) << "GraphicsLinesForCurve::lineMembershipPurge"
212                               << " curve=" << m_curveName.toLatin1().data();
213 
214   OrdinalToGraphicsPoint::iterator itr, itrNext;
215   for (itr = m_graphicsPoints.begin(); itr != m_graphicsPoints.end(); itr = itrNext) {
216 
217     itrNext = itr;
218     ++itrNext;
219 
220     GraphicsPoint *point = *itr;
221 
222     if (!point->wanted ()) {
223 
224       double ordinal = itr.key ();
225 
226       delete point;
227       m_graphicsPoints.remove (ordinal);
228     }
229   }
230 
231   // Apply line style
232   QPen pen;
233   if (lineStyle.paletteColor() == COLOR_PALETTE_TRANSPARENT) {
234 
235     pen = QPen (Qt::NoPen);
236 
237   } else {
238 
239     pen = QPen (QBrush (ColorPaletteToQColor (lineStyle.paletteColor())),
240                 lineStyle.width());
241 
242   }
243 
244   setPen (pen);
245 
246   updateGraphicsLinesToMatchGraphicsPoints (lineStyle,
247                                             splineDrawer,
248                                             pathMultiValued,
249                                             lineMultiValued);
250 }
251 
lineMembershipReset()252 void GraphicsLinesForCurve::lineMembershipReset ()
253 {
254   LOG4CPP_INFO_S ((*mainCat)) << "GraphicsLinesForCurve::lineMembershipReset"
255                               << " curve=" << m_curveName.toLatin1().data();
256 
257   OrdinalToGraphicsPoint::iterator itr;
258   for (itr = m_graphicsPoints.begin(); itr != m_graphicsPoints.end(); itr++) {
259 
260     GraphicsPoint *point = itr.value();
261 
262     point->reset ();
263   }
264 }
265 
needOrdinalRenumbering() const266 bool GraphicsLinesForCurve::needOrdinalRenumbering () const
267 {
268   // Ordinals should be 0, 1, ...
269   bool needRenumbering = false;
270   for (int ordinalKeyWanted = 0; ordinalKeyWanted < m_graphicsPoints.count(); ordinalKeyWanted++) {
271 
272     double ordinalKeyGot = m_graphicsPoints.keys().at (ordinalKeyWanted);
273 
274     // Sanity checks
275     ENGAUGE_ASSERT (ordinalKeyGot != Point::UNDEFINED_ORDINAL ());
276 
277     if (ordinalKeyWanted != ordinalKeyGot) {
278       needRenumbering = true;
279       break;
280     }
281   }
282 
283   return needRenumbering;
284 }
285 
printStream(QString indentation,QTextStream & str) const286 void GraphicsLinesForCurve::printStream (QString indentation,
287                                          QTextStream &str) const
288 {
289   DataKey type = static_cast<DataKey> (data (DATA_KEY_GRAPHICS_ITEM_TYPE).toInt());
290 
291   str << indentation << "GraphicsLinesForCurve=" << m_curveName
292       << " dataIdentifier=" << data (DATA_KEY_IDENTIFIER).toString().toLatin1().data()
293       << " dataType=" << dataKeyToString (type).toLatin1().data() << "\n";
294 
295   indentation += INDENTATION_DELTA;
296 
297   OrdinalToGraphicsPoint::const_iterator itr;
298   for (itr = m_graphicsPoints.begin(); itr != m_graphicsPoints.end(); itr++) {
299 
300     double ordinalKey = itr.key();
301     const GraphicsPoint *point = itr.value();
302 
303     point->printStream (indentation,
304                         str,
305                         ordinalKey);
306   }
307 }
308 
removePoint(double ordinal)309 void GraphicsLinesForCurve::removePoint (double ordinal)
310 {
311   LOG4CPP_INFO_S ((*mainCat)) << "GraphicsLinesForCurve::removePoint"
312                               << " point=" << ordinal
313                               << " pointCount=" << m_graphicsPoints.count();
314 
315   ENGAUGE_ASSERT (m_graphicsPoints.contains (ordinal));
316   GraphicsPoint *graphicsPoint = m_graphicsPoints [ordinal];
317 
318   m_graphicsPoints.remove (ordinal);
319 
320   delete graphicsPoint;
321 }
322 
removeTemporaryPointIfExists()323 void GraphicsLinesForCurve::removeTemporaryPointIfExists()
324 {
325   LOG4CPP_INFO_S ((*mainCat)) << "GraphicsLinesForCurve::removeTemporaryPointIfExists";
326 
327   // Compiler warning about this loop only iterating once is not an issue since there
328   // is never more than one temporary point
329   OrdinalToGraphicsPoint::iterator itr;
330   for (itr = m_graphicsPoints.begin(); itr != m_graphicsPoints.end(); itr++) {
331 
332     GraphicsPoint *graphicsPoint = itr.value();
333 
334     m_graphicsPoints.remove (itr.key());
335 
336     delete graphicsPoint;
337 
338     break;
339   }
340 }
341 
renumberOrdinals()342 void GraphicsLinesForCurve::renumberOrdinals ()
343 {
344   LOG4CPP_INFO_S ((*mainCat)) << "GraphicsLinesForCurve::renumberOrdinals";
345 
346   int ordinalKeyWanted;
347 
348   // Ordinals should be 0, 1, and so on. Assigning a list to QMap::keys has no effect, so the
349   // approach is to copy to a temporary list and then copy back
350   QList<GraphicsPoint*> points;
351   for (ordinalKeyWanted = 0; ordinalKeyWanted < m_graphicsPoints.count(); ordinalKeyWanted++) {
352 
353     GraphicsPoint *graphicsPoint = m_graphicsPoints.values().at (ordinalKeyWanted);
354     points << graphicsPoint;
355   }
356 
357   m_graphicsPoints.clear ();
358 
359   for (ordinalKeyWanted = 0; ordinalKeyWanted < points.count(); ordinalKeyWanted++) {
360 
361     GraphicsPoint *graphicsPoint = points.at (ordinalKeyWanted);
362     m_graphicsPoints [ordinalKeyWanted] = graphicsPoint;
363   }
364 }
365 
updateAfterCommand(GraphicsScene & scene,const PointStyle & pointStyle,const Point & point,GeometryWindow * geometryWindow)366 void GraphicsLinesForCurve::updateAfterCommand (GraphicsScene &scene,
367                                                 const PointStyle &pointStyle,
368                                                 const Point &point,
369                                                 GeometryWindow *geometryWindow)
370 {
371   LOG4CPP_DEBUG_S ((*mainCat)) << "GraphicsLinesForCurve::updateAfterCommand"
372                                << " curve=" << m_curveName.toLatin1().data()
373                                << " pointCount=" << m_graphicsPoints.count();
374 
375   GraphicsPoint *graphicsPoint = nullptr;
376   if (m_graphicsPoints.contains (point.ordinal())) {
377 
378     graphicsPoint = m_graphicsPoints [point.ordinal()];
379 
380     // Due to ordinal renumbering, the coordinates may belong to some other point so we override
381     // them for consistent ordinal-position mapping. Updating the identifier also was added for
382     // better logging (i.e. consistency between Document and GraphicsScene dumps), but happened
383     // to fix a bug with the wrong set of points getting deleted from Cut and Delete
384     graphicsPoint->setPos (point.posScreen());
385     graphicsPoint->setData (DATA_KEY_IDENTIFIER, point.identifier());
386 
387   } else {
388 
389     // Point does not exist in scene so create it
390     graphicsPoint = scene.createPoint (point.identifier (),
391                                        pointStyle,
392                                        point.posScreen(),
393                                        geometryWindow);
394     m_graphicsPoints [point.ordinal ()] = graphicsPoint;
395 
396   }
397 
398   // Mark point as wanted
399   ENGAUGE_CHECK_PTR (graphicsPoint);
400   graphicsPoint->setWanted ();
401 }
402 
updateCurveStyle(const CurveStyle & curveStyle)403 void GraphicsLinesForCurve::updateCurveStyle (const CurveStyle &curveStyle)
404 {
405   LOG4CPP_INFO_S ((*mainCat)) << "GraphicsLinesForCurve::updateCurveStyle";
406 
407   OrdinalToGraphicsPoint::const_iterator itr;
408   for (itr = m_graphicsPoints.begin(); itr != m_graphicsPoints.end(); itr++) {
409 
410      GraphicsPoint *point = itr.value();
411      point->updateCurveStyle (curveStyle);
412   }
413 }
414 
updateHighlightOpacity(double highlightOpacity)415 void GraphicsLinesForCurve::updateHighlightOpacity (double highlightOpacity)
416 {
417   LOG4CPP_INFO_S ((*mainCat)) << "GraphicsLinesForCurve::updateCurveStyle"
418                               << " curve=" << m_curveName.toLatin1().data()
419                               << " highlightOpacity=" << highlightOpacity;
420 
421   OrdinalToGraphicsPoint::const_iterator itr;
422   for (itr = m_graphicsPoints.begin(); itr != m_graphicsPoints.end(); itr++) {
423 
424      GraphicsPoint *point = itr.value();
425      point->setHighlightOpacity (highlightOpacity);
426   }
427 }
428 
updateGraphicsLinesToMatchGraphicsPoints(const LineStyle & lineStyle,SplineDrawer & splineDrawer,QPainterPath & pathMultiValued,LineStyle & lineMultiValued)429 void GraphicsLinesForCurve::updateGraphicsLinesToMatchGraphicsPoints (const LineStyle &lineStyle,
430                                                                       SplineDrawer &splineDrawer,
431                                                                       QPainterPath &pathMultiValued,
432                                                                       LineStyle &lineMultiValued)
433 {
434   // LOG4CPP_INFO_S is below
435 
436   bool needRenumbering = needOrdinalRenumbering ();
437   if (needRenumbering) {
438 
439     renumberOrdinals();
440 
441   }
442 
443   LOG4CPP_INFO_S ((*mainCat)) << "GraphicsLinesForCurve::updateGraphicsLinesToMatchGraphicsPoints"
444                               << " numberPoints=" << m_graphicsPoints.count()
445                               << " ordinalRenumbering=" << (needRenumbering ? "true" : "false");
446 
447   if (lineStyle.curveConnectAs() != CONNECT_SKIP_FOR_AXIS_CURVE) {
448 
449     // Draw as either straight or smoothed. The function/relation differences were handled already with ordinals. The
450     // Spline algorithm will crash with fewer than three points so it is only called when there are enough points
451     QPainterPath path;
452     if (lineStyle.curveConnectAs() == CONNECT_AS_FUNCTION_STRAIGHT ||
453         lineStyle.curveConnectAs() == CONNECT_AS_RELATION_STRAIGHT ||
454         m_graphicsPoints.count () < 3) {
455 
456       path = drawLinesStraight (pathMultiValued);
457     } else {
458       path = drawLinesSmooth (lineStyle,
459                               splineDrawer,
460                               pathMultiValued,
461                               lineMultiValued);
462     }
463 
464    setPath (path);
465   }
466 }
467 
updatePointOrdinalsAfterDrag(const LineStyle & lineStyle,const Transformation & transformation)468 void GraphicsLinesForCurve::updatePointOrdinalsAfterDrag (const LineStyle &lineStyle,
469                                                           const Transformation &transformation)
470 {
471   CurveConnectAs curveConnectAs = lineStyle.curveConnectAs();
472 
473   LOG4CPP_DEBUG_S ((*mainCat)) << "GraphicsLinesForCurve::updatePointOrdinalsAfterDrag"
474                                << " curve=" << m_curveName.toLatin1().data()
475                                << " curveConnectAs=" << curveConnectAsToString(curveConnectAs).toLatin1().data();
476 
477   if (curveConnectAs == CONNECT_AS_FUNCTION_SMOOTH ||
478       curveConnectAs == CONNECT_AS_FUNCTION_STRAIGHT) {
479 
480     // Make sure ordinals are properly ordered
481 
482     // Get a map of x/theta values as keys with point identifiers as the values
483     XOrThetaToOrdinal xOrThetaToOrdinal;
484     OrdinalToGraphicsPoint::iterator itrP;
485     for (itrP = m_graphicsPoints.begin(); itrP != m_graphicsPoints.end(); itrP++) {
486 
487        double ordinal = itrP.key();
488        const GraphicsPoint *point = itrP.value();
489 
490        // Convert screen coordinate to graph coordinates, which gives us x/theta
491        QPointF pointGraph;
492        transformation.transformScreenToRawGraph(point->pos (),
493                                                 pointGraph);
494 
495        xOrThetaToOrdinal [pointGraph.x()] = ordinal;
496     }
497 
498     // Loop through the sorted x/theta values. Since QMap is used, the x/theta keys are sorted
499     OrdinalToGraphicsPoint temporaryList;
500     int ordinalNew = 0;
501     XOrThetaToOrdinal::const_iterator itrX;
502     for (itrX = xOrThetaToOrdinal.begin(); itrX != xOrThetaToOrdinal.end(); itrX++) {
503 
504       double ordinalOld = *itrX;
505       GraphicsPoint *point = m_graphicsPoints [ordinalOld];
506 
507       temporaryList [ordinalNew++] = point;
508     }
509 
510     // Copy from temporary back to original map
511     m_graphicsPoints.clear();
512     for (itrP = temporaryList.begin(); itrP != temporaryList.end(); itrP++) {
513 
514       double ordinal = itrP.key();
515       GraphicsPoint *point = itrP.value();
516 
517       m_graphicsPoints [ordinal] = point;
518     }
519   }
520 }
521