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