1 /*
2  *  This file is part of RawTherapee.
3  *
4  *  Copyright (c) 2004-2010 Gabor Horvath <hgabor@rawtherapee.com>
5  *
6  *  RawTherapee is free software: you can redistribute it and/or modify
7  *  it under the terms of the GNU General Public License as published by
8  *  the Free Software Foundation, either version 3 of the License, or
9  *  (at your option) any later version.
10  *
11  *  RawTherapee is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *  GNU General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public License
17  *  along with RawTherapee.  If not, see <https://www.gnu.org/licenses/>.
18  */
19 #include <cstring>
20 
21 #include <gdkmm/types.h>
22 
23 #include "myflatcurve.h"
24 
25 #include "editcallbacks.h"
26 #include "rtscalable.h"
27 
28 #include "../rtengine/curves.h"
29 
MyFlatCurve()30 MyFlatCurve::MyFlatCurve () :
31     MyCurve(),
32     clampedX(0.0),
33     clampedY(0.0),
34     deltaX(0.0),
35     deltaY(0.0),
36     distanceX(0.0),
37     distanceY(0.0),
38     ugpX(0.0),
39     ugpY(0.0),
40     leftTanX(0.0),
41     rightTanX(0.0),
42     preciseCursorX(0.0),
43     preciseCursorY(0.0),
44     minDistanceX(0.0),
45     minDistanceY(0.0),
46     deletedPointX(0.0),
47     leftTanHandle({0.0, 0.0}),
48     rightTanHandle({0.0, 0.0}),
49     draggingElement(false)
50 {
51 
52     lit_point = -1;
53     closest_point = 0;
54     editedHandle = FCT_EditedHandle_None;
55     area = FCT_Area_None;
56     tanHandlesDisplayed = false;
57     periodic = true;
58 
59     //bghist = new unsigned int[256];
60 
61     editedPos.resize(4);
62     editedPos.at(0) = editedPos.at(1) = editedPos.at(2) = editedPos.at(3) = 0.0;
63 
64     signal_event().connect( sigc::mem_fun(*this, &MyFlatCurve::handleEvents) );
65 
66     // By default, we create a curve with 6 control points
67     curve.type = FCT_MinMaxCPoints;
68 
69     defaultCurve();
70 }
71 
72 /*MyFlatCurve::~MyFlatCurve () {
73 }*/
74 
get_vector(int veclen)75 std::vector<double> MyFlatCurve::get_vector (int veclen)
76 {
77 
78     // Create the output variable
79     std::vector<double> convertedValues;
80 
81     // Get the curve control points
82     std::vector<double> curveDescr = getPoints ();
83     rtengine::FlatCurve rtcurve(curveDescr, periodic, veclen * 1.2 > 5000 ? 5000 : veclen * 1.2);
84 
85     // Create the sample values that will be converted
86     std::vector<double> samples;
87     samples.resize (veclen);
88 
89     for (int i = 0; i < veclen; i++) {
90         samples.at(i) = (double) i / (veclen - 1.0);
91     }
92 
93     // Converting the values
94     rtcurve.getVal (samples, convertedValues);
95 
96     // Cleanup and return
97     return convertedValues;
98 }
99 
get_LUT(LUTf & lut)100 void MyFlatCurve::get_LUT (LUTf &lut)
101 {
102 
103     int size = lut.getSize();
104 
105     // Get the curve control points
106     std::vector<double> curveDescr = getPoints ();
107     rtengine::FlatCurve rtcurve(curveDescr, periodic, lut.getUpperBound() * 1.2 > 5000 ? 5000 : lut.getUpperBound() * 1.2);
108 
109     double maxVal = double(lut.getUpperBound());
110 
111     for (int i = 0; i < size; i++) {
112         double t = double(i) / maxVal;
113         lut[i] = rtcurve.getVal (t);
114     }
115 
116     return;
117 }
118 
interpolate()119 void MyFlatCurve::interpolate ()
120 {
121 
122     prevGraphW = graphW;
123     prevGraphH = graphH;
124     point((unsigned int)graphW);
125     get_LUT (point);
126     upoint.reset ();
127     lpoint.reset ();
128 
129     curveIsDirty = false;
130 }
131 
draw()132 void MyFlatCurve::draw ()
133 {
134     if (!isDirty()) {
135         return;
136     }
137 
138     if (!surfaceCreated()) {
139         return;
140     }
141 
142     double s = (double)RTScalable::getScale();
143 
144     // re-calculate curve if dimensions changed
145     int currLUTSize = point.getUpperBound();
146 
147     if (curveIsDirty
148         || (currLUTSize == (GRAPH_SIZE * s) && (graphW > (GRAPH_SIZE * s)))
149         || (currLUTSize >  (GRAPH_SIZE * s) && (graphW <= (GRAPH_SIZE * s) || graphW != currLUTSize)) )
150     {
151         interpolate ();
152     }
153 
154     Gtk::StateFlags state = !is_sensitive() ? Gtk::STATE_FLAG_INSENSITIVE : Gtk::STATE_FLAG_NORMAL;
155 
156     Glib::RefPtr<Gtk::StyleContext> style = get_style_context();
157     Cairo::RefPtr<Cairo::Context> cr = getContext();
158     cr->set_line_cap(Cairo::LINE_CAP_SQUARE);
159 
160     // clear background
161     cr->set_source_rgba (0., 0., 0., 0.);
162     cr->set_operator (Cairo::OPERATOR_CLEAR);
163     cr->paint ();
164     cr->set_operator (Cairo::OPERATOR_OVER);
165 
166     style->render_background(cr, graphX, graphY-graphH, graphW, graphH);
167 
168     Gdk::RGBA c;
169 
170     cr->set_line_width (1.0 * s);
171 
172     // draw the left colored bar
173     if (leftBar) {
174         // first the background
175         BackBuffer *bb = this;
176         leftBar->setDrawRectangle(1. * s, graphY - graphH - 0.5, CBAR_WIDTH * s, graphH);
177         leftBar->expose(*this, bb);
178 
179         // now the border
180         c = style->get_border_color(state);
181         cr->set_source_rgb (c.get_red(), c.get_green(), c.get_blue());
182         cr->rectangle(0.5 * s, graphY - graphH - 0.5 - 0.5 * s, (CBAR_WIDTH + 1) * s, (double)graphH + 1. + 1. * s);
183         cr->stroke();
184     }
185 
186     // draw the bottom colored bar
187     if (bottomBar) {
188         // first the background
189         BackBuffer *bb = this;
190         bottomBar->setDrawRectangle(graphX - 0.5, graphY + (RADIUS + CBAR_MARGIN + 1.) * s, graphW + 1., CBAR_WIDTH * s);
191         bottomBar->expose(*this, bb);
192 
193         // now the border
194         c = style->get_border_color(state);
195         cr->set_source_rgb (c.get_red(), c.get_green(), c.get_blue());
196         cr->rectangle(graphX - 0.5 - 0.5 * s, graphY + (RADIUS + CBAR_MARGIN + 0.5) * s, graphW + 1. + 0.5 * s, (CBAR_WIDTH + 1.) * s);
197         cr->stroke();
198     }
199 
200     // draw f(x)=0.5 line
201     c = style->get_border_color(state);
202     cr->set_source_rgb (c.get_red(), c.get_green(), c.get_blue());
203     std::valarray<double> ds (1);
204     ds[0] = 4 * s;
205     cr->set_dash (ds, 0);
206     cr->move_to (graphX - 1. * s, graphY - graphH / 2.);
207     cr->rel_line_to (graphW + 2 * s, 0.);
208     cr->stroke ();
209 
210     cr->unset_dash ();
211     cr->set_antialias (Cairo::ANTIALIAS_SUBPIXEL);
212     cr->set_line_width (1.0 * s);
213     cr->set_line_cap(Cairo::LINE_CAP_BUTT);
214 
215     // draw the pipette values
216     if (pipetteR > -1.f || pipetteG > -1.f || pipetteB > -1.f) {
217         cr->set_line_width (0.75 * s);
218         cr->set_source_rgb (c.get_red(), c.get_green(), c.get_blue());
219         int n = 0;
220 
221         if (pipetteR > -1.f) {
222             ++n;
223         }
224 
225         if (pipetteG > -1.f) {
226             ++n;
227         }
228 
229         if (pipetteB > -1.f) {
230             ++n;
231         }
232 
233         if (n > 1) {
234             if (pipetteR > -1.f) {
235                 cr->set_source_rgba (1., 0., 0., 0.5); // WARNING: assuming that red values are stored in pipetteR, which might not be the case!
236                 cr->move_to (graphX + graphW*pipetteR, graphY + 1. * s);
237                 cr->rel_line_to (0, -graphH - 1. * s);
238                 cr->stroke ();
239             }
240 
241             if (pipetteG > -1.f) {
242                 cr->set_source_rgba (0., 1., 0., 0.5); // WARNING: assuming that green values are stored in pipetteG, which might not be the case!
243                 cr->move_to (graphX + graphW*pipetteG, graphY + 1. * s);
244                 cr->rel_line_to (0, -graphH - 1. * s);
245                 cr->stroke ();
246             }
247 
248             if (pipetteB > -1.f) {
249                 cr->set_source_rgba (0., 0., 1., 0.5); // WARNING: assuming that blue values are stored in pipetteB, which might not be the case!
250                 cr->move_to (graphX + graphW*pipetteB, graphY + 1. * s);
251                 cr->rel_line_to (0, -graphH - 1. * s);
252                 cr->stroke ();
253             }
254         }
255 
256         if (pipetteVal > -1.f) {
257             cr->set_line_width (2. * s);
258             c = style->get_color (state);
259             cr->set_source_rgb (c.get_red(), c.get_green(), c.get_blue());
260             cr->move_to (graphX + graphW*pipetteVal, graphY + 1. * s);
261             cr->rel_line_to (0, -graphH - 1. * s);
262             cr->stroke ();
263             cr->set_line_width (1. * s);
264         }
265     }
266 
267     // draw the color feedback of the control points
268     if (colorProvider) {
269 
270         //if (curve.type!=FCT_Parametric)
271         for (int i = 0; i < (int)curve.x.size(); ++i) {
272 
273             if (curve.x.at(i) != -1.) {
274                 double coloredLineWidth = rtengine::min<double>( rtengine::max<double>(75. * s, graphW) / (75. * s), 8. * s);
275 
276                 cr->set_line_width (coloredLineWidth);
277                 colorProvider->colorForValue(curve.x.at(i), curve.y.at(i), CCET_VERTICAL_BAR, colorCallerId, this);
278                 cr->set_source_rgb (ccRed, ccGreen, ccBlue);
279 
280                 if ( i == lit_point && (editedHandle & (FCT_EditedHandle_CPointUD | FCT_EditedHandle_CPoint | FCT_EditedHandle_CPointX)) ) {
281                     cr->set_line_width (2 * coloredLineWidth);
282                 }
283 
284                 cr->move_to (graphX + graphW * curve.x.at(i), graphY + 0.5 + 0.5 * s );
285                 cr->rel_line_to (0., -graphH - 1. - s);
286                 cr->stroke ();
287                 cr->set_line_width (coloredLineWidth);
288 
289                 // draw the lit_point's horizontal line
290                 bool drawHLine = false;
291 
292                 if (edited_point > -1) {
293                     if (i == edited_point) {
294                         cr->set_line_width (2 * coloredLineWidth);
295                         drawHLine = true;
296                     }
297                 } else if (i == lit_point) {
298                     if ( (area & (FCT_Area_H | FCT_Area_V | FCT_Area_Point)) || editedHandle == FCT_EditedHandle_CPointUD) {
299                         if (editedHandle & (FCT_EditedHandle_CPointUD | FCT_EditedHandle_CPoint | FCT_EditedHandle_CPointY)) {
300                             cr->set_line_width (2 * coloredLineWidth);
301                             drawHLine = true;
302                         }
303                     }
304                 }
305 
306                 if (drawHLine) {
307                     int point = edited_point > -1 ? edited_point : lit_point;
308                     colorProvider->colorForValue(curve.x.at(i), curve.y.at(i), CCET_HORIZONTAL_BAR, colorCallerId, this);
309                     cr->set_source_rgb (ccRed, ccGreen, ccBlue);
310 
311                     cr->move_to (graphX - 0.5 - 0.5 * s , graphY - graphH * curve.y.at(point));
312                     cr->rel_line_to (graphW + 1. + s, 0.);
313                     cr->stroke ();
314                 }
315             }
316         }
317 
318         // endif
319         cr->set_line_width (1.0 * s);
320     } else {
321         cr->set_source_rgb (0.5, 0.0, 0.0);
322 
323         if (edited_point > -1 || ((lit_point > -1) && ((area & (FCT_Area_H | FCT_Area_V | FCT_Area_Point)) || editedHandle == FCT_EditedHandle_CPointUD)) ) {
324             // draw the lit_point's vertical line
325             if (edited_point > -1 || (editedHandle & (FCT_EditedHandle_CPointUD | FCT_EditedHandle_CPoint | FCT_EditedHandle_CPointY))) {
326                 cr->set_line_width (2.0 * s);
327             }
328 
329             int point = edited_point > -1 ? edited_point : lit_point;
330             cr->move_to (graphX + graphW * curve.x.at(point), graphY + 0.5 + 0.5 * s );
331             cr->rel_line_to (0., -graphH - 1. - s);
332             cr->stroke ();
333             cr->set_line_width (1.0 * s);
334 
335             // draw the lit_point's horizontal line
336             if (editedHandle & (FCT_EditedHandle_CPointUD | FCT_EditedHandle_CPoint | FCT_EditedHandle_CPointY)) {
337                 cr->set_line_width (2.0 * s);
338             }
339 
340             cr->move_to (graphX - 0.5 - 0.5 * s , graphY - graphH * curve.y.at(point));
341             cr->rel_line_to (graphW + 1. + s, 0.);
342             cr->stroke ();
343             cr->set_line_width (1.0 * s);
344         }
345     }
346 
347     cr->set_line_cap(Cairo::LINE_CAP_SQUARE);
348 
349     // draw the graph's borders:
350     c = style->get_border_color(state);
351     cr->set_source_rgb (c.get_red(), c.get_green(), c.get_blue());
352     cr->rectangle(graphX - 0.5 - 0.5 * s, graphY + 0.5 + 0.5 * s, graphW + 1. + 1. * s, -(graphH + 1. + 1. * s));
353     cr->stroke ();
354 
355     double lineMinLength = 1. / graphW * (double)(SQUARE) * 0.9 * s;
356 
357     if (tanHandlesDisplayed && lit_point != -1 && getHandles(lit_point) && curve.x.at(lit_point) != -1.) {
358         double x = graphX + graphW * curve.x.at(lit_point);
359         double y = graphY - graphH * curve.y.at(lit_point);
360         double x2;
361         double square;
362         bool crossingTheFrame;
363 
364         // left handle is yellow
365         // TODO: finding a way to set the left handle color for flat curve editor
366         cr->set_source_rgb (1.0, 1.0, 0.0);
367 
368         // draw tangential vectors
369 
370         crossingTheFrame = false;
371 
372         // We display the line only if it's longer than the handle knot half-size...
373         if (leftTanX < -0.00001) {
374             leftTanX += 1.0;
375             crossingTheFrame = true;
376         }
377 
378         x2 = graphX + graphW * leftTanX;
379 
380         if (curve.x.at(lit_point) - leftTanX > lineMinLength || crossingTheFrame) {
381             // The left tangential vector reappear on the right side
382             // draw the line
383             cr->move_to (x, y);
384 
385             if (crossingTheFrame) {
386                 cr->line_to (graphX - 0.5 - 0.5 * s, y);
387                 cr->stroke ();
388                 cr->move_to (graphX + graphW + 0.5 + 0.5 * s, y);
389             }
390 
391             cr->line_to (x2, y);
392             cr->stroke ();
393         }
394 
395         // draw tangential knot
396         square = (area == FCT_Area_LeftTan ? SQUARE * 2. : SQUARE) * s;
397         cr->rectangle(x2 - square, y - square, 2.*square, 2.*square);
398         cr->fill();
399 
400         // right handle is blue
401         // TODO: finding a way to set the right handle color for flat curve editor
402         cr->set_source_rgb (0.0, 0.0, 1.0);
403 
404         // draw tangential vectors
405 
406         crossingTheFrame = false;
407 
408         // We display the line only if it's longer than the handle knot half-size...
409         if (rightTanX > 1.00001) {
410             rightTanX -= 1.0;
411             crossingTheFrame = true;
412         }
413 
414         x2 = graphX + graphW * rightTanX;
415 
416         if (rightTanX - curve.x.at(lit_point) > lineMinLength || crossingTheFrame) {
417             // The left tangential vector reappear on the right side
418             // draw the line
419             cr->move_to (x, y);
420 
421             if (crossingTheFrame) {
422                 cr->line_to (graphX + graphW + 0.5 + 0.5 * s, y);
423                 cr->stroke ();
424                 cr->move_to (graphX - 0.5 - 0.5 * s, y);
425             }
426 
427             cr->line_to (x2, y);
428             cr->stroke ();
429         }
430 
431         // draw tangential knot
432         square = (area == FCT_Area_RightTan ? SQUARE * 2. : SQUARE) * s;
433         cr->rectangle(x2 - square, y - square, 2.*square, 2.*square);
434         cr->fill();
435     }
436 
437     // draw curve
438     c = style->get_color(state);
439     cr->set_source_rgb (c.get_red(), c.get_green(), c.get_blue());
440     cr->move_to (graphX, getVal(point, 0) * -graphH + graphY);
441 
442     for (int i = 1; i < graphW; ++i) {
443         cr->line_to ((double)i + graphX, (double)getVal(point, i) * -graphH + graphY);
444     }
445 
446     cr->stroke ();
447 
448     // draw bullets
449     for (int i = 0; i < (int)curve.x.size(); ++i) {
450         if (curve.x.at(i) != -1.) {
451             if (i == edited_point) {
452                 cr->set_source_rgb (1.0, 0.0, 0.0);
453             } else if (i == lit_point) {
454                 if (colorProvider && edited_point == -1) {
455                     colorProvider->colorForValue(curve.x.at(i), curve.y.at(i), CCET_POINT, colorCallerId, this);
456                     cr->set_source_rgb (ccRed, ccGreen, ccBlue);
457                 } else {
458                     cr->set_source_rgb (1.0, 0.0, 0.0);
459                 }
460             } else if (i == snapToElmt || i == edited_point) {
461                 cr->set_source_rgb (1.0, 0.0, 0.0);
462             } else if (curve.y.at(i) == 0.5) {
463                 cr->set_source_rgb (0.0, 0.5, 0.0);
464             } else {
465                 cr->set_source_rgb (c.get_red(), c.get_green(), c.get_blue());
466             }
467 
468             double x = graphX + graphW * curve.x.at(i); // project (curve.x.at(i), 0, 1, graphW);
469             double y = graphY - graphH * curve.y.at(i); // project (curve.y.at(i), 0, 1, graphH);
470 
471             cr->arc (x, y, RADIUS * s + 0.5, 0, 2 * rtengine::RT_PI);
472             cr->fill ();
473 
474             if (i == edited_point) {
475                 cr->set_source_rgb (1.0, 0.0, 0.0);
476                 cr->set_line_width(2. * s);
477                 cr->arc (x, y, (RADIUS + 2.) * s, 0, 2 * rtengine::RT_PI);
478                 cr->stroke();
479                 cr->set_line_width(1. * s);
480             }
481 
482         }
483     }
484 
485     // endif
486 
487     // draw the left and right tangent handles
488     if (tanHandlesDisplayed) {
489         double halfSquareSizeX = minDistanceX / 2.;
490         double halfSquareSizeY = minDistanceY / 2.;
491 
492         // LEFT handle
493 
494         // yellow
495         cr->set_source_rgb (1.0, 1.0, 0.0);
496         cr->rectangle(graphX + graphW * (leftTanHandle.centerX - halfSquareSizeX),
497                       graphY - graphH * (leftTanHandle.centerY + halfSquareSizeY),
498                       graphW * minDistanceX,
499                       graphW * minDistanceY);
500         cr->fill();
501 
502         // RIGHT handle
503 
504         // blue
505         cr->set_source_rgb (0.0, 0.0, 1.0);
506         cr->rectangle(graphX + graphW * (rightTanHandle.centerX - halfSquareSizeX),
507                       graphY - graphH * (rightTanHandle.centerY + halfSquareSizeY),
508                       graphW * minDistanceX,
509                       graphW * minDistanceY);
510         cr->fill();
511     }
512 
513     setDirty(false);
514     queue_draw();
515 }
516 
on_draw(const::Cairo::RefPtr<Cairo::Context> & cr)517 bool MyFlatCurve::on_draw(const ::Cairo::RefPtr< Cairo::Context> &cr)
518 {
519     Gtk::Allocation allocation = get_allocation();
520     allocation.set_x(0);
521     allocation.set_y(0);
522 
523     int s = RTScalable::getScale();
524 
525     // setDrawRectangle will allocate the backbuffer Surface
526     if (setDrawRectangle(Cairo::FORMAT_ARGB32, allocation)) {
527         setDirty(true);
528 
529         if (prevGraphW > (GRAPH_SIZE * s) || graphW > (GRAPH_SIZE * s)) {
530             curveIsDirty = true;
531         }
532     }
533 
534     draw ();
535     copySurface(cr);
536     return false;
537 }
538 
539 
540 /*
541  * Return the X1, X2, Y position of the tangential handles.
542  */
getHandles(int n)543 bool MyFlatCurve::getHandles(int n)
544 {
545     int N = curve.x.size();
546     double prevX, nextX;
547     double x, leftTan, rightTan;
548 
549     if (n == -1) {
550         return false;
551     }
552 
553     x = curve.x.at(n);
554     leftTan = curve.leftTangent.at(n);
555     rightTan = curve.rightTangent.at(n);
556 
557     if (!n) {
558         // first point, the left handle is then computed with the last point's right handle
559         prevX = curve.x.at(N - 1) - 1.0;
560         nextX = curve.x.at(1);
561     } else if (n == N - 1) {
562         // last point, the right handle is then computed with the first point's left handle
563         prevX = curve.x.at(n - 1);
564         nextX = curve.x.at(0) + 1.0;
565     } else {
566         // last point, the right handle is then computed with the first point's left handle
567         prevX = curve.x.at(n - 1);
568         nextX = curve.x.at(n + 1);
569     }
570 
571     if (leftTan == 0.0) {
572         leftTanX = x;
573     } else if (leftTan == 1.0) {
574         leftTanX = prevX;
575     } else {
576         leftTanX = (prevX - x) * leftTan + x;
577     }
578 
579     if (rightTan == 0.0) {
580         rightTanX = x;
581     } else if (rightTan == 1.0) {
582         rightTanX = nextX;
583     } else {
584         rightTanX = (nextX - x) * rightTan + x;
585     }
586 
587     return true;
588 }
589 
handleEvents(GdkEvent * event)590 bool MyFlatCurve::handleEvents (GdkEvent* event)
591 {
592 
593     CursorShape new_type = cursor_type;
594     std::vector<double>::iterator itx, ity, itlt, itrt;
595 
596     snapToElmt = -100;
597     bool retval = false;
598     int num = (int)curve.x.size();
599 
600     /* graphW and graphH are the size of the graph */
601     calcDimensions();
602 
603     if ((graphW < 0) || (graphH < 0)) {
604         return false;
605     }
606 
607     double s = RTScalable::getScale();
608 
609     minDistanceX = double(MIN_DISTANCE) / graphW * s;
610     minDistanceY = double(MIN_DISTANCE) / graphH * s;
611 
612     switch (event->type) {
613 
614     case GDK_BUTTON_PRESS:
615         if (edited_point == -1) { //curve.type!=FCT_Parametric) {
616             if (event->button.button == 1) {
617                 buttonPressed = true;
618                 add_modal_grab ();
619 
620                 // get the pointer position
621                 getCursorPosition(Gdk::EventType(event->type), event->motion.is_hint != 0, int(event->button.x), int(event->button.y), Gdk::ModifierType(event->button.state));
622                 getMouseOverArea();
623 
624                 // hide the tangent handles
625                 tanHandlesDisplayed = false;
626 
627                 // Action on BUTTON_PRESS and no edited point
628                 switch (area) {
629 
630                 case (FCT_Area_Insertion):
631                     new_type = CSMove2D; // Shown when adding a new node in a blank area, both click and drag.
632 
633                     /* insert a new control point */
634                     if (num > 0) {
635                         if (clampedX > curve.x.at(closest_point)) {
636                             ++closest_point;
637                         }
638                     }
639 
640                     itx = curve.x.begin();
641                     ity = curve.y.begin();
642                     itlt = curve.leftTangent.begin();
643                     itrt = curve.rightTangent.begin();
644 
645                     for (int i = 0; i < closest_point; i++) {
646                         ++itx;
647                         ++ity;
648                         ++itlt;
649                         ++itrt;
650                     }
651 
652                     curve.x.insert (itx, 0);
653                     curve.y.insert (ity, 0);
654                     curve.leftTangent.insert (itlt, 0);
655                     curve.rightTangent.insert (itrt, 0);
656 
657                     if (mod_type & GDK_CONTROL_MASK) {
658                         clampedY = point.getVal01(clampedX);
659                     }
660 
661                     // the graph is refreshed only if a new point is created
662                     curve.x.at(closest_point) = clampedX;
663                     curve.y.at(closest_point) = clampedY;
664                     curve.leftTangent.at(closest_point) = 0.35;
665                     curve.rightTangent.at(closest_point) = 0.35;
666 
667                     curveIsDirty = true;
668                     setDirty(true);
669                     draw ();
670                     notifyListener ();
671 
672                     lit_point = closest_point;
673 
674                     // point automatically activated
675                     editedHandle = FCT_EditedHandle_CPoint;
676                     ugpX = curve.x.at(lit_point);
677                     ugpY = curve.y.at(lit_point);
678                     break;
679 
680                 case (FCT_Area_Point):
681                     new_type = CSMove2D; // Shown when node clicked and dragged.
682                     editedHandle = FCT_EditedHandle_CPoint;
683                     ugpX = curve.x.at(lit_point);
684                     ugpY = curve.y.at(lit_point);
685                     break;
686 
687                 case (FCT_Area_H):
688                 case (FCT_Area_V):
689                     new_type = CSMove2D; // Shown when vertical line clicked, not dragged.
690                     editedHandle = FCT_EditedHandle_CPointUD;
691                     ugpX = curve.x.at(lit_point);
692                     ugpY = curve.y.at(lit_point);
693                     break;
694 
695                 case (FCT_Area_LeftTan):
696                     new_type = CSEmpty;
697                     editedHandle = FCT_EditedHandle_LeftTan;
698                     ugpX = curve.leftTangent.at(lit_point);
699                     break;
700 
701                 case (FCT_Area_RightTan):
702                     new_type = CSEmpty;
703                     editedHandle = FCT_EditedHandle_RightTan;
704                     ugpX = curve.rightTangent.at(lit_point);
705                     break;
706 
707                 default:
708                     break;
709                 }
710             } else if (event->button.button == 3) {
711                 /*  get the pointer position  */
712                 getCursorPosition(Gdk::EventType(event->type), event->motion.is_hint != 0, int(event->button.x), int(event->button.y), Gdk::ModifierType(event->button.state));
713                 getMouseOverArea();
714 
715                 if (lit_point > -1 && lit_point != edited_point) {
716                     if (editedHandle == FCT_EditedHandle_None) {
717                         if (area == FCT_Area_Point || area == FCT_Area_V) {
718                             // the cursor is close to an existing point
719                             if (!coordinateAdjuster->is_visible()) {
720                                 coordinateAdjuster->showMe(this);
721                             }
722 
723                             new_type = CSArrow;
724                             tanHandlesDisplayed = false;
725                             edited_point = lit_point;
726                             setDirty(true);
727                             draw ();
728                             std::vector<CoordinateAdjuster::Boundaries> newBoundaries(4);
729                             int size = curve.x.size();
730 
731                             if      (edited_point == 0)      {
732                                 newBoundaries.at(0).minVal = 0.;
733                                 newBoundaries.at(0).maxVal = curve.x.at(1);
734                             } else if (edited_point == size - 1) {
735                                 newBoundaries.at(0).minVal = curve.x.at(edited_point - 1);
736                                 newBoundaries.at(0).maxVal = 1.;
737                             } else if (curve.x.size() > 2)     {
738                                 newBoundaries.at(0).minVal = curve.x.at(edited_point - 1);
739                                 newBoundaries.at(0).maxVal = curve.x.at(edited_point + 1);
740                             }
741 
742                             newBoundaries.at(1).minVal = 0.;
743                             newBoundaries.at(1).maxVal = 1.;
744                             newBoundaries.at(2).minVal = 0.;
745                             newBoundaries.at(2).maxVal = 1.;
746                             newBoundaries.at(3).minVal = 0.;
747                             newBoundaries.at(3).maxVal = 1.;
748                             editedPos.at(0) = curve.x.at(edited_point);
749                             editedPos.at(1) = curve.y.at(edited_point);
750                             editedPos.at(2) = curve.leftTangent.at(edited_point);
751                             editedPos.at(3) = curve.rightTangent.at(edited_point);
752                             coordinateAdjuster->setPos(editedPos);
753                             coordinateAdjuster->startNumericalAdjustment(newBoundaries);
754                         }
755                     }
756                 }
757 
758                 retval = true;
759             }
760 
761             if (buttonPressed) {
762                 retval = true;
763             }
764         } else { // if (edited_point > -1)
765             if (event->button.button == 3) {
766                 // do we edit another point?
767                 /*  get the pointer position  */
768                 getCursorPosition(Gdk::EventType(event->type), event->motion.is_hint != 0, int(event->button.x), int(event->button.y), Gdk::ModifierType(event->button.state));
769                 getMouseOverArea();
770 
771                 if (area == FCT_Area_Point || area == FCT_Area_V) {
772                     // the cursor is close to an existing point
773                     if (lit_point != edited_point) {
774                         edited_point = lit_point;
775                         setDirty(true);
776                         draw ();
777                         std::vector<CoordinateAdjuster::Boundaries> newBoundaries(4);
778                         int size = curve.x.size();
779 
780                         if      (edited_point == 0)      {
781                             newBoundaries.at(0).minVal = 0.;
782                             newBoundaries.at(0).maxVal = curve.x.at(1);
783                         } else if (edited_point == size - 1) {
784                             newBoundaries.at(0).minVal = curve.x.at(edited_point - 1);
785                             newBoundaries.at(0).maxVal = 1.;
786                         } else if (curve.x.size() > 2)     {
787                             newBoundaries.at(0).minVal = curve.x.at(edited_point - 1);
788                             newBoundaries.at(0).maxVal = curve.x.at(edited_point + 1);
789                         }
790 
791                         newBoundaries.at(1).minVal = 0.;
792                         newBoundaries.at(1).maxVal = 1.;
793                         newBoundaries.at(2).minVal = 0.;
794                         newBoundaries.at(2).maxVal = 1.;
795                         newBoundaries.at(3).minVal = 0.;
796                         newBoundaries.at(3).maxVal = 1.;
797                         editedPos.at(0) = curve.x.at(edited_point);
798                         editedPos.at(1) = curve.y.at(edited_point);
799                         editedPos.at(2) = curve.leftTangent.at(edited_point);
800                         editedPos.at(3) = curve.rightTangent.at(edited_point);
801                         coordinateAdjuster->switchAdjustedPoint(editedPos, newBoundaries);
802                         retval = true;
803                     }
804                 } else if (area == FCT_Area_Insertion) {
805                     // the cursor is inside the graph but away from existing points
806                     new_type = CSPlus;
807                     curveIsDirty = true;
808                     stopNumericalAdjustment();
809                 }
810             }
811         }
812 
813         break;
814 
815     case GDK_BUTTON_RELEASE:
816         if (edited_point == -1) { //curve.type!=FCT_Parametric) {
817             if (buttonPressed && event->button.button == 1) {
818                 buttonPressed = false;
819                 remove_modal_grab ();
820 
821                 // Removing any deleted point if we were previously modifying the point position
822                 if (editedHandle & (FCT_EditedHandle_CPoint | FCT_EditedHandle_CPointX | FCT_EditedHandle_CPointY)) {
823                     /* delete inactive points: */
824                     int src, dst;
825                     itx = curve.x.begin();
826                     ity = curve.y.begin();
827                     itlt = curve.leftTangent.begin();
828                     itrt = curve.rightTangent.begin();
829 
830                     for (src = dst = 0; src < num; ++src)
831                         if (curve.x.at(src) >= 0.0) {
832                             curve.x.at(dst) = curve.x.at(src);
833                             curve.y.at(dst) = curve.y.at(src);
834                             curve.leftTangent.at(dst) = curve.leftTangent.at(src);
835                             curve.rightTangent.at(dst) = curve.rightTangent.at(src);
836                             ++dst;
837                             ++itx;
838                             ++ity;
839                             ++itlt;
840                             ++itrt;
841                         }
842 
843                     if (dst < src) {
844 
845                         // curve cleanup
846                         curve.x.erase (itx, curve.x.end());
847                         curve.y.erase (ity, curve.y.end());
848                         curve.leftTangent.erase (itlt, curve.leftTangent.end());
849                         curve.rightTangent.erase (itrt, curve.rightTangent.end());
850 
851                         if (curve.x.empty()) {
852                             curve.x.push_back (0.5);
853                             curve.y.push_back (0.5);
854                             curve.leftTangent.push_back (0.3);
855                             curve.rightTangent.push_back (0.3);
856                             curveIsDirty = true;
857                         }
858                     }
859                 }
860 
861                 editedHandle = FCT_EditedHandle_None;
862                 lit_point = -1;
863 
864                 // get the pointer position
865                 getCursorPosition(Gdk::EventType(event->type), event->motion.is_hint != 0, int(event->button.x), int(event->button.y), Gdk::ModifierType(event->button.state));
866                 getMouseOverArea();
867 
868                 switch (area) {
869 
870                 case (FCT_Area_Insertion):
871                     new_type = CSArrow;
872                     break;
873 
874                 case (FCT_Area_Point):
875                     new_type = CSMove2D; // Shown when node released.
876                     break;
877 
878                 case (FCT_Area_H):
879                     new_type = CSResizeHeight;
880                     break;
881 
882                 case (FCT_Area_V):
883                     new_type = CSMove2D; // Shown when line released.
884                     break;
885 
886                 case (FCT_Area_LeftTan):
887                     new_type = CSEmpty;
888                     break;
889 
890                 case (FCT_Area_RightTan):
891                     new_type = CSEmpty;
892                     break;
893 
894                 default:
895                     break;
896                 }
897 
898                 setDirty(true);
899                 draw ();
900                 retval = true;
901                 //notifyListener ();
902             }
903         }
904 
905         break;
906 
907     case GDK_MOTION_NOTIFY:
908         if (curve.type == FCT_Linear || curve.type == FCT_MinMaxCPoints) {
909 
910             int previous_lit_point = lit_point;
911             enum MouseOverAreas prevArea = area;
912 
913             snapToMinDistY = snapToMinDistX = 10.;
914             snapToValY = snapToValX = 0.;
915             snapToElmt = -100;
916 
917             // get the pointer position
918             getCursorPosition(Gdk::EventType(event->type), event->motion.is_hint != 0, int(event->button.x), int(event->button.y), Gdk::ModifierType(event->button.state));
919             getMouseOverArea();
920 
921             if (editedHandle == FCT_EditedHandle_CPointUD) {
922                 double dX = deltaX;
923                 double dY = deltaY;
924 
925                 if (dX < 0.) {
926                     dX = -dX;
927                 }
928 
929                 if (dY < 0.) {
930                     dY = -dY;
931                 }
932 
933                 if (dX > dY) {
934                     editedHandle = FCT_EditedHandle_CPointX;
935                     area = FCT_Area_V;
936                     new_type = CSResizeWidth;
937                 } else {
938                     editedHandle = FCT_EditedHandle_CPointY;
939                     area = FCT_Area_H;
940                     new_type = CSResizeHeight;
941                 }
942             }
943 
944             switch (editedHandle) {
945 
946             case (FCT_EditedHandle_None): {
947 
948                 if ((lit_point != -1 && previous_lit_point != lit_point) && (area & (FCT_Area_V | FCT_Area_Point)) && edited_point == -1) {
949 
950                     bool sameSide = false;
951 
952                     // display the handles
953                     tanHandlesDisplayed = true;
954 
955                     if (curve.x.at(lit_point) < 3.*minDistanceX) {
956                         // lit_point too near of the left border -> both handles are displayed on the right of the vertical line
957                         rightTanHandle.centerX  = leftTanHandle.centerX  = curve.x.at(lit_point) + 2.*minDistanceX;
958                         sameSide = true;
959                     } else if (curve.x.at(lit_point) > 1. - 3.*minDistanceX) {
960                         // lit_point too near of the left border -> both handles are displayed on the right of the vertical line
961                         rightTanHandle.centerX = leftTanHandle.centerX = curve.x.at(lit_point)  - 2.*minDistanceX;
962                         sameSide = true;
963                     } else {
964                         leftTanHandle.centerX = curve.x.at(lit_point) - 2.*minDistanceX;
965                         rightTanHandle.centerX = curve.x.at(lit_point) + 2.*minDistanceX;
966                     }
967 
968                     if (sameSide) {
969                         if (clampedY > 1. - 2.*minDistanceY) {
970                             // lit_point too near of the top border
971                             leftTanHandle.centerY = 1. - minDistanceY;
972                             rightTanHandle.centerY = 1. - 3.*minDistanceY;
973                         } else if (clampedY < 2.*minDistanceY) {
974                             // lit_point too near of the bottom border
975                             leftTanHandle.centerY = 3.*minDistanceY;
976                             rightTanHandle.centerY = minDistanceY;
977                         } else {
978                             leftTanHandle.centerY = clampedY + minDistanceY;
979                             rightTanHandle.centerY = clampedY - minDistanceY;
980                         }
981                     } else {
982                         if (clampedY > 1. - minDistanceY) {
983                             rightTanHandle.centerY = leftTanHandle.centerY = 1. - minDistanceY;
984                         } else if (clampedY < minDistanceY) {
985                             rightTanHandle.centerY = leftTanHandle.centerY = minDistanceY;
986                         } else {
987                             rightTanHandle.centerY = leftTanHandle.centerY = clampedY;
988                         }
989                     }
990                 } else if (lit_point == -1 || edited_point > -1) {
991                     tanHandlesDisplayed = false;
992                 }
993 
994 
995                 if (edited_point == -1) {
996                     switch (area) {
997 
998                     case (FCT_Area_Insertion):
999                         new_type = CSPlus;
1000                         break;
1001 
1002                     case (FCT_Area_Point):
1003 
1004                     //new_type = CSMove;
1005                     //break;
1006                     case (FCT_Area_V):
1007                         new_type = CSPlus; // Shown when hovering over vertical line.
1008                         break;
1009 
1010                     case (FCT_Area_H):
1011                         new_type = CSResizeHeight;
1012                         break;
1013 
1014                     case (FCT_Area_LeftTan):
1015                         new_type = CSMoveLeft;
1016                         break;
1017 
1018                     case (FCT_Area_RightTan):
1019                         new_type = CSMoveRight;
1020                         break;
1021 
1022                     case (FCT_Area_None):
1023                     default:
1024                         new_type = CSArrow;
1025                         break;
1026                     }
1027                 }
1028 
1029                 if ((lit_point != previous_lit_point) || (prevArea != area)) {
1030                     setDirty(true);
1031                     draw ();
1032                 }
1033 
1034                 if (coordinateAdjuster->is_visible() && edited_point == -1) {
1035                     if (lit_point > -1) {
1036                         if (lit_point != previous_lit_point) {
1037                             editedPos.at(0) = curve.x.at(lit_point);
1038                             editedPos.at(1) = curve.y.at(lit_point);
1039                             editedPos.at(2) = curve.leftTangent.at(lit_point);
1040                             editedPos.at(3) = curve.rightTangent.at(lit_point);
1041                         }
1042 
1043                         coordinateAdjuster->setPos(editedPos);
1044                     } else if (area == FCT_Area_Insertion) {
1045                         editedPos.at(0) = clampedX;
1046                         editedPos.at(1) = clampedY;
1047                         editedPos.at(2) = 0.;
1048                         editedPos.at(3) = 0.;
1049                         coordinateAdjuster->setPos(editedPos);
1050                     } else {
1051                         editedPos.at(0) = editedPos.at(1) = editedPos.at(2) = editedPos.at(3) = 0;
1052                         coordinateAdjuster->setPos(editedPos);
1053                     }
1054                 }
1055 
1056                 break;
1057             }
1058 
1059             case (FCT_EditedHandle_CPoint):
1060                 movePoint(true, true);
1061 
1062                 if (coordinateAdjuster->is_visible()) {
1063                     editedPos.at(0) = curve.x.at(lit_point);
1064                     editedPos.at(1) = curve.y.at(lit_point);
1065                     coordinateAdjuster->setPos(editedPos);
1066                 }
1067 
1068                 break;
1069 
1070             case (FCT_EditedHandle_CPointX):
1071                 movePoint(true, false);
1072 
1073                 if (coordinateAdjuster->is_visible()) {
1074                     editedPos.at(0) = curve.x.at(lit_point);
1075                     coordinateAdjuster->setPos(editedPos);
1076                 }
1077 
1078                 break;
1079 
1080             case (FCT_EditedHandle_CPointY):
1081                 movePoint(false, true);
1082 
1083                 if (coordinateAdjuster->is_visible()) {
1084                     editedPos.at(1) = curve.y.at(lit_point);
1085                     coordinateAdjuster->setPos(editedPos);
1086                 }
1087 
1088                 break;
1089 
1090             case (FCT_EditedHandle_LeftTan): {
1091                 double prevValue = curve.leftTangent.at(lit_point);
1092 
1093                 ugpX -= deltaX * 3;
1094                 ugpX = CLAMP(ugpX, 0., 1.);
1095 
1096                 if (snapTo) {
1097                     // since this handle can only move in one direction, we can reuse the snapCoordinateX mechanism
1098                     snapCoordinateX(0.0,  ugpX);
1099                     snapCoordinateX(0.35, ugpX);
1100                     snapCoordinateX(0.5,  ugpX);
1101                     snapCoordinateX(1.0,  ugpX);
1102                     curve.leftTangent.at(lit_point) = snapToValX;
1103                 } else {
1104                     curve.leftTangent.at(lit_point) = ugpX;
1105                 }
1106 
1107                 if (curve.leftTangent.at(lit_point) != prevValue) {
1108                     curveIsDirty = true;
1109                     setDirty(true);
1110                     draw ();
1111                     notifyListener ();
1112 
1113                     if (coordinateAdjuster->is_visible()) {
1114                         editedPos.at(2) = curve.leftTangent.at(lit_point);
1115                         coordinateAdjuster->setPos(editedPos);
1116                     }
1117                 }
1118 
1119                 break;
1120             }
1121 
1122             case (FCT_EditedHandle_RightTan): {
1123                 double prevValue = curve.rightTangent.at(lit_point);
1124 
1125                 ugpX += deltaX * 3;
1126                 ugpX = CLAMP(ugpX, 0., 1.);
1127 
1128                 if (snapTo) {
1129                     // since this handle can only move in one direction, we can reuse the snapCoordinateX mechanism
1130                     snapCoordinateX(0.0,  ugpX);
1131                     snapCoordinateX(0.35, ugpX);
1132                     snapCoordinateX(0.5,  ugpX);
1133                     snapCoordinateX(1.0,  ugpX);
1134                     curve.rightTangent.at(lit_point) = snapToValX;
1135                 } else {
1136                     curve.rightTangent.at(lit_point) = ugpX;
1137                 }
1138 
1139                 if (curve.rightTangent.at(lit_point) != prevValue) {
1140                     curveIsDirty = true;
1141                     setDirty(true);
1142                     draw ();
1143                     notifyListener ();
1144                     editedPos.at(3) = curve.rightTangent.at(lit_point);
1145                     coordinateAdjuster->setPos(editedPos);
1146                 }
1147 
1148                 break;
1149             }
1150 
1151             // already processed before the "switch" instruction
1152             //case (FCT_EditedHandle_CPointUD):
1153 
1154             default:
1155                 break;
1156             }
1157 
1158             if (edited_point == -1) {
1159                 if (lit_point == -1) {
1160                     editedPos.at(0) = editedPos.at(1) = editedPos.at(2) = editedPos.at(3) = 0;
1161                 } else if (editedPos.at(0) != curve.x.at(lit_point)
1162                            || editedPos.at(1) != curve.y.at(lit_point)
1163                            || editedPos.at(2) != curve.leftTangent.at(lit_point)
1164                            || editedPos.at(3) != curve.rightTangent.at(lit_point)) {
1165                     editedPos.at(0) = curve.x.at(lit_point);
1166                     editedPos.at(1) = curve.y.at(lit_point);
1167                     editedPos.at(2) = curve.leftTangent.at(lit_point);
1168                     editedPos.at(3) = curve.rightTangent.at(lit_point);
1169                     coordinateAdjuster->setPos(editedPos);
1170                 }
1171             }
1172         }
1173 
1174         retval = true;
1175         break;
1176 
1177     case GDK_LEAVE_NOTIFY:
1178 
1179         // Pointer can LEAVE even when dragging the point, so we don't modify the cursor in this case
1180         // The cursor will have to LEAVE another time after the drag...
1181         if (editedHandle == FCT_EditedHandle_None) {
1182             new_type = CSArrow;
1183             lit_point = -1;
1184             tanHandlesDisplayed = false;
1185             pipetteR = pipetteG = pipetteB = -1.f;
1186             setDirty(true);
1187             draw ();
1188         }
1189 
1190         retval = true;
1191         break;
1192 
1193     default:
1194         break;
1195     }
1196 
1197     if (new_type != cursor_type) {
1198         cursor_type = new_type;
1199         CursorManager::setCursorOfMainWindow(get_window(), cursor_type);
1200     }
1201 
1202     return retval;
1203 }
1204 
pipetteMouseOver(CurveEditor * ce,EditDataProvider * provider,int modifierKey)1205 void MyFlatCurve::pipetteMouseOver (CurveEditor *ce, EditDataProvider *provider, int modifierKey)
1206 {
1207     if (!provider) {
1208         // occurs when leaving the preview area -> cleanup the curve editor
1209         pipetteR = pipetteG = pipetteB = -1.f;
1210         lit_point = -1;
1211         editedHandle = FCT_EditedHandle_None;
1212         return;
1213     }
1214 
1215     pipetteR = provider->getPipetteVal1();
1216     pipetteG = provider->getPipetteVal2();
1217     pipetteB = provider->getPipetteVal3();
1218     pipetteVal = 0.f;
1219 
1220     if (listener) {
1221         pipetteVal = listener->blendPipetteValues(ce, pipetteR, pipetteG, pipetteB);
1222     } else {
1223         int n = 0;
1224 
1225         if (pipetteR != -1.f) {
1226             pipetteVal += pipetteR;
1227             ++n;
1228         }
1229 
1230         if (pipetteG != -1.f) {
1231             pipetteVal += pipetteG;
1232             ++n;
1233         }
1234 
1235         if (pipetteB != -1.f) {
1236             pipetteVal += pipetteB;
1237             ++n;
1238         }
1239 
1240         if (n > 1) {
1241             pipetteVal /= n;
1242         } else if (!n) {
1243             pipetteVal = -1.f;
1244         }
1245     }
1246 
1247     snapToElmt = -100;
1248 
1249     /* graphW and graphH are the size of the graph */
1250     calcDimensions();
1251 
1252     if ((graphW < 0) || (graphH < 0)) {
1253         return;
1254     }
1255 
1256     int previous_lit_point = lit_point;
1257 
1258     // hide the handles
1259     tanHandlesDisplayed = false;
1260 
1261     // get the pointer position
1262     int px = graphX + int(float(graphW) * pipetteVal); // WARNING: converting pipetteVal from float to int, precision loss here!
1263     getCursorPosition(Gdk::EventType(Gdk::BUTTON_PRESS), false, px, graphY, Gdk::ModifierType(modifierKey));
1264 
1265     if (edited_point == -1) {
1266         getMouseOverArea();
1267     }
1268 
1269     if (area == FCT_Area_Point) {
1270         area = FCT_Area_V;
1271     }
1272 
1273     snapToMinDistY = snapToMinDistX = 10.;
1274     snapToValY = snapToValX = 0.;
1275 
1276     if (edited_point == -1) {
1277         if (editedHandle == FCT_EditedHandle_None && lit_point != previous_lit_point) {
1278             setDirty(true);
1279             draw ();
1280         }
1281     } else {
1282         draw();
1283     }
1284 
1285     if (edited_point == -1) {
1286         editedPos.at(0) = pipetteVal;
1287         editedPos.at(1) = point.getVal01(pipetteVal);
1288         coordinateAdjuster->setPos(editedPos);
1289     }
1290 }
1291 
1292 // returns true if a point is being dragged
pipetteButton1Pressed(EditDataProvider * provider,int modifierKey)1293 bool MyFlatCurve::pipetteButton1Pressed(EditDataProvider *provider, int modifierKey)
1294 {
1295     if (edited_point > -1) {
1296         return false;
1297     }
1298 
1299     buttonPressed = true;
1300 
1301     // get the pointer position
1302     int px = graphX + int(float(graphW) * pipetteVal); // WARNING: converting pipetteVal from float to int, precision loss here!
1303     getCursorPosition(Gdk::EventType(Gdk::BUTTON_PRESS), false, px, graphY, Gdk::ModifierType(modifierKey));
1304     getMouseOverArea();
1305 
1306     // hide the tangent handles
1307     tanHandlesDisplayed = false;
1308 
1309     int s = RTScalable::getScale();
1310 
1311     // Action on BUTTON_PRESS and no edited point
1312     switch (area) {
1313 
1314     case (FCT_Area_Insertion): {
1315         rtengine::FlatCurve rtCurve(getPoints(), true, GRAPH_SIZE * s);
1316 
1317         std::vector<double>::iterator itx, ity, itlt, itrt;
1318         int num = (int)curve.x.size();
1319 
1320         /* insert a new control point */
1321         if (num > 0) {
1322             if (clampedX > curve.x.at(closest_point)) {
1323                 ++closest_point;
1324             }
1325         }
1326 
1327         itx = curve.x.begin();
1328         ity = curve.y.begin();
1329         itlt = curve.leftTangent.begin();
1330         itrt = curve.rightTangent.begin();
1331 
1332         for (int i = 0; i < closest_point; i++) {
1333             ++itx;
1334             ++ity;
1335             ++itlt;
1336             ++itrt;
1337         }
1338 
1339         curve.x.insert (itx, 0);
1340         curve.y.insert (ity, 0);
1341         curve.leftTangent.insert (itlt, 0);
1342         curve.rightTangent.insert (itrt, 0);
1343         num++;
1344 
1345         // the graph is refreshed only if a new point is created
1346         curve.x.at(closest_point) = clampedX;
1347         curve.y.at(closest_point) = clampedY = rtCurve.getVal(pipetteVal);
1348         curve.leftTangent.at(closest_point) = 0.35;
1349         curve.rightTangent.at(closest_point) = 0.35;
1350 
1351         curveIsDirty = true;
1352         setDirty(true);
1353         draw ();
1354         notifyListener ();
1355 
1356         lit_point = closest_point;
1357 
1358         // point automatically activated
1359         editedHandle = FCT_EditedHandle_CPointY;
1360         ugpX = curve.x.at(lit_point);
1361         ugpY = curve.y.at(lit_point);
1362         break;
1363     }
1364 
1365     case (FCT_Area_V):
1366         editedHandle = FCT_EditedHandle_CPointY;
1367         ugpX = curve.x.at(lit_point);
1368         ugpY = curve.y.at(lit_point);
1369         break;
1370 
1371     default:
1372         break;
1373     }
1374 
1375     return true;
1376 }
1377 
pipetteButton1Released(EditDataProvider * provider)1378 void MyFlatCurve::pipetteButton1Released(EditDataProvider *provider)
1379 {
1380     if (edited_point > -1) {
1381         return;
1382     }
1383 
1384     buttonPressed = false;
1385     remove_modal_grab ();
1386 
1387     editedHandle = FCT_EditedHandle_None;
1388     lit_point = -1;
1389 
1390     // get the pointer position
1391     int px = graphX + int(float(graphW) * pipetteVal); // WARNING: converting pipetteVal from float to int, precision loss here!
1392     getCursorPosition(Gdk::EventType(Gdk::BUTTON_PRESS), false, px, graphY, Gdk::ModifierType(0));
1393     getMouseOverArea();
1394 
1395     setDirty(true);
1396     draw ();
1397     //notifyListener ();
1398 }
1399 
pipetteDrag(EditDataProvider * provider,int modifierKey)1400 void MyFlatCurve::pipetteDrag(EditDataProvider *provider, int modifierKey)
1401 {
1402     if (edited_point > -1) {
1403         return;
1404     }
1405 
1406     snapToMinDistY = snapToMinDistX = 10.;
1407     snapToValY = snapToValX = 0.;
1408     snapToElmt = -100;
1409 
1410     // get the pointer position
1411     getCursorPosition(Gdk::MOTION_NOTIFY, false, cursorX + graphX, graphY + provider->deltaScreen.y, Gdk::ModifierType(modifierKey));
1412     getMouseOverArea();
1413 
1414     if (editedHandle == FCT_EditedHandle_CPointY) {
1415         movePoint(false, true, true);
1416     }
1417 }
1418 
movePoint(bool moveX,bool moveY,bool pipetteDrag)1419 void MyFlatCurve::movePoint(bool moveX, bool moveY, bool pipetteDrag)
1420 {
1421 
1422     // bounds of the grabbed point
1423     double leftBound;
1424     double rightBound;
1425     double const bottomBound = 0.;
1426     double const topBound    = 1.;
1427 
1428     // we memorize the previous position of the point, for optimization purpose
1429     double prevPosX = curve.x.at(lit_point);
1430     double prevPosY = curve.y.at(lit_point);
1431 
1432     int nbPoints = (int)curve.x.size();
1433 
1434     // left and right bound rely on curve periodicity
1435     leftBound         = (lit_point == 0         ) ? (periodic && !snapTo ? curve.x.at(nbPoints - 1) - 1. : 0.) : curve.x.at(lit_point - 1);
1436     rightBound        = (lit_point == nbPoints - 1) ? (periodic && !snapTo ? curve.x.at(0) + 1.          : 1.) : curve.x.at(lit_point + 1);
1437 
1438     double leftDeletionBound   = leftBound   - minDistanceX;
1439     double rightDeletionBound  = rightBound  + minDistanceX;
1440     double bottomDeletionBound = bottomBound - minDistanceY;
1441     double topDeletionBound    = topBound    + minDistanceY;
1442 
1443     if (moveX) {
1444         // we memorize the previous position of the point, for optimization purpose
1445         ugpX += deltaX;
1446 
1447         // handling periodicity (the first and last point can reappear at the other side of the X range)
1448         if (periodic) {
1449             if (snapTo) {
1450                 if (lit_point == 0) {
1451                     snapCoordinateX(0.0, ugpX);
1452                     curve.x.at(0) = snapToValX;
1453                 } else if (lit_point == (nbPoints - 1)) {
1454                     snapCoordinateX(1.0, ugpX);
1455                     curve.x.at(nbPoints - 1) = snapToValX;
1456                 }
1457             } else if (lit_point == 0 && ugpX < 0.) {
1458                 // the first point has to be placed at the tail of the point list
1459                 std::vector<double>::iterator itx, ity, itlt, itrt;
1460 
1461                 ugpX += 1.;
1462                 leftBound += 1.;
1463                 rightBound += 1.;
1464                 leftDeletionBound += 1.;
1465                 rightDeletionBound += 1.;
1466 
1467                 // adding a copy of the first point to the tail of the list
1468                 curve.x.push_back(curve.x.at(0));
1469                 curve.y.push_back(curve.y.at(0));
1470                 curve.leftTangent.push_back(curve.leftTangent.at(0));
1471                 curve.rightTangent.push_back(curve.rightTangent.at(0));
1472 
1473                 // deleting the first point
1474                 itx = curve.x.begin();
1475                 ity = curve.y.begin();
1476                 itlt = curve.leftTangent.begin();
1477                 itrt = curve.rightTangent.begin();
1478 
1479                 curve.x.erase(itx);
1480                 curve.y.erase(ity);
1481                 curve.leftTangent.erase(itlt);
1482                 curve.rightTangent.erase(itrt);
1483 
1484                 lit_point = nbPoints - 1;
1485             } else if (lit_point == (nbPoints - 1) && ugpX > 1.) {
1486                 // the last point has to be placed at the head of the point list
1487                 std::vector<double>::iterator itx, ity, itlt, itrt;
1488 
1489                 ugpX -= 1.;
1490                 leftBound -= 1.;
1491                 rightBound -= 1.;
1492                 leftDeletionBound -= 1.;
1493                 rightDeletionBound -= 1.;
1494 
1495                 // adding a copy of the last point to the head of the list
1496                 itx = curve.x.begin();
1497                 ity = curve.y.begin();
1498                 itlt = curve.leftTangent.begin();
1499                 itrt = curve.rightTangent.begin();
1500 
1501                 curve.x.insert(itx, 0);
1502                 curve.y.insert(ity, 0);
1503                 curve.leftTangent.insert(itlt, 0);
1504                 curve.rightTangent.insert(itrt, 0);
1505 
1506                 curve.x.at(0) = curve.x.at(nbPoints);
1507                 curve.y.at(0) = curve.y.at(nbPoints);
1508                 curve.leftTangent.at(0) = curve.leftTangent.at(nbPoints);
1509                 curve.rightTangent.at(0) = curve.rightTangent.at(nbPoints);
1510 
1511                 // deleting the last point
1512                 curve.x.pop_back();
1513                 curve.y.pop_back();
1514                 curve.leftTangent.pop_back();
1515                 curve.rightTangent.pop_back();
1516 
1517                 lit_point = 0;
1518             }
1519         }
1520 
1521         // handling limitations along X axis
1522         if (ugpX >= rightDeletionBound && nbPoints > 2 && !snapTo) {
1523             curve.x.at(lit_point) = -1.;
1524         } else if (ugpX <= leftDeletionBound && nbPoints > 2 && !snapTo) {
1525             curve.x.at(lit_point) = -1.;
1526         } else
1527             // nextPosX is in bounds
1528         {
1529             curve.x.at(lit_point) = CLAMP(ugpX, leftBound, rightBound);
1530         }
1531     }
1532 
1533     if (moveY) {
1534 
1535         // we memorize the previous position of the point, for optimization purpose
1536         ugpY += deltaY;
1537 
1538         // the points stay in the bounds (and can't be deleted) in pipette drag mode
1539         if (pipetteDrag) {
1540             ugpY = CLAMP(ugpY, 0.0, 1.0);
1541         }
1542 
1543         // snapping point to specific values
1544         if (snapTo && curve.x.at(lit_point) != -1) {
1545 
1546             // the unclamped grabbed point is brought back in the range
1547             ugpY = CLAMP(ugpY, 0.0, 1.0);
1548 
1549             if (lit_point == 0) {
1550                 int prevP = curve.y.size() - 1;
1551 
1552                 if (snapCoordinateY(curve.y.at(prevP), ugpY)) {
1553                     snapToElmt = prevP;
1554                 }
1555             } else {
1556                 int prevP = lit_point - 1;
1557 
1558                 if (snapCoordinateY(curve.y.at(prevP), ugpY)) {
1559                     snapToElmt = prevP;
1560                 }
1561             }
1562 
1563             if (curve.y.size() > 2) {
1564                 if (lit_point == int(curve.y.size()) - 1) {
1565                     if (snapCoordinateY(curve.y.at(0), ugpY)) {
1566                         snapToElmt = 0;
1567                     }
1568                 } else {
1569                     int nextP = lit_point + 1;
1570 
1571                     if (snapCoordinateY(curve.y.at(nextP), ugpY)) {
1572                         snapToElmt = nextP;
1573                     }
1574                 }
1575             }
1576 
1577             if (snapCoordinateY(1.0, ugpY)) {
1578                 snapToElmt = -3;
1579             }
1580 
1581             if (snapCoordinateY(0.5, ugpY)) {
1582                 snapToElmt = -2;
1583             }
1584 
1585             if (snapCoordinateY(0.0, ugpY)) {
1586                 snapToElmt = -1;
1587             }
1588 
1589             curve.y.at(lit_point) = snapToValY;
1590         }
1591 
1592         // Handling limitations along Y axis
1593         if (ugpY >= topDeletionBound && nbPoints > 2) {
1594             if (curve.x.at(lit_point) != -1.) {
1595                 deletedPointX = curve.x.at(lit_point);
1596                 curve.x.at(lit_point) = -1.;
1597                 curve.y.at(lit_point) = ugpY; // This is only to force the redraw of the curve
1598             }
1599         } else if (ugpY <= bottomDeletionBound  && nbPoints > 2) {
1600             if (curve.x.at(lit_point) != -1.) {
1601                 deletedPointX = curve.x.at(lit_point);
1602                 curve.x.at(lit_point) = -1.;
1603                 curve.y.at(lit_point) = ugpY; // This is only to force the redraw of the curve
1604             }
1605         } else {
1606             // nextPosY is in the bounds
1607             if (!snapTo) {
1608                 curve.y.at(lit_point) = CLAMP(ugpY, 0.0, 1.0);
1609             }
1610 
1611             if (!moveX && curve.x.at(lit_point) == -1.) {
1612                 // bring back the X value of the point if it reappear
1613                 curve.x.at(lit_point) = deletedPointX;
1614             }
1615         }
1616     }
1617 
1618     if (curve.x.at(lit_point) != prevPosX || curve.y.at(lit_point) != prevPosY) {
1619         // we recompute the curve only if we have to
1620         curveIsDirty = true;
1621         setDirty(true);
1622         draw ();
1623         notifyListener ();
1624     }
1625 }
1626 
1627 // Set data relative to cursor position
getCursorPosition(Gdk::EventType evType,bool isHint,int evX,int evY,Gdk::ModifierType modifierKey)1628 void MyFlatCurve::getCursorPosition(Gdk::EventType evType, bool isHint, int evX, int evY, Gdk::ModifierType modifierKey)
1629 {
1630     int tx, ty;
1631     int prevCursorX, prevCursorY;
1632     double incrementX = 1. / double(graphW);
1633     double incrementY = 1. / double(graphH);
1634 
1635     switch (evType) {
1636     case (Gdk::MOTION_NOTIFY) :
1637         if (isHint) {
1638             get_window()->get_pointer (tx, ty, mod_type);
1639         } else {
1640             tx = evX;
1641             ty = evY;
1642             mod_type = modifierKey;
1643         }
1644 
1645         break;
1646 
1647     case (Gdk::BUTTON_PRESS) :
1648     case (Gdk::BUTTON_RELEASE) :
1649         tx = evX;
1650         ty = evY;
1651         mod_type = modifierKey;
1652         break;
1653 
1654     default :
1655         // The cursor position is not available
1656         return;
1657         break;
1658     }
1659 
1660     if (editedHandle != FCT_EditedHandle_None) {
1661         prevCursorX = cursorX;
1662         prevCursorY = cursorY;
1663     }
1664 
1665     cursorX = tx - graphX;
1666     cursorY = graphY - ty;
1667 
1668     preciseCursorX = cursorX * incrementX;
1669     preciseCursorY = cursorY * incrementY;
1670 
1671     snapTo = false;
1672 
1673     // update deltaX/Y if the user drags a point
1674     if (editedHandle != FCT_EditedHandle_None) {
1675         // set the dragging factor
1676         int control_key = mod_type & GDK_CONTROL_MASK;
1677         int shift_key = mod_type & GDK_SHIFT_MASK;
1678 
1679         // the increment get smaller if modifier key are used, and "snap to" may be enabled
1680         if (control_key) {
1681             incrementX *= 0.05;
1682             incrementY *= 0.05;
1683         }
1684 
1685         if (shift_key)   {
1686             snapTo = true;
1687         }
1688 
1689         deltaX = (double)(cursorX - prevCursorX) * incrementX;
1690         deltaY = (double)(cursorY - prevCursorY) * incrementY;
1691     }
1692     // otherwise set the position of the new point (modifier keys has no effect here)
1693     else {
1694         clampedX = CLAMP (preciseCursorX, 0., 1.); // X position of the pointer from the origin of the graph
1695         clampedY = CLAMP (preciseCursorY, 0., 1.); // Y position of the pointer from the origin of the graph
1696     }
1697 
1698 }
1699 
1700 // Find out the active area under the cursor
getMouseOverArea()1701 void MyFlatCurve::getMouseOverArea ()
1702 {
1703 
1704     // When dragging an element, editedHandle keep its value
1705     if (editedHandle == FCT_EditedHandle_None) { // && curve.type!=Parametric
1706 
1707         double minDist = 1000;  // used to find out the point pointed by the cursor (over it)
1708         double minDistX = 1000; // used to find out the closest point
1709         double dX, dY;
1710         double absDX;
1711         double dist;
1712         bool aboveVLine = false;
1713 
1714         // NB: this function assume that the graph's shape is a square
1715 
1716         // Check if the cursor is over a tangent handle
1717         if (tanHandlesDisplayed) {
1718 
1719             if (preciseCursorX >= (leftTanHandle.centerX - minDistanceX) && preciseCursorX <= (leftTanHandle.centerX + minDistanceX + 0.00001)
1720                     &&  preciseCursorY >= (leftTanHandle.centerY - minDistanceY) && preciseCursorY <= (leftTanHandle.centerY + minDistanceY)) {
1721                 area = FCT_Area_LeftTan;
1722                 return;
1723             }
1724 
1725             if (preciseCursorX >= (rightTanHandle.centerX - minDistanceX - 0.00001) && preciseCursorX <= (rightTanHandle.centerX + minDistanceX)
1726                     &&  preciseCursorY >= (rightTanHandle.centerY - minDistanceY        ) && preciseCursorY <= (rightTanHandle.centerY + minDistanceY)) {
1727                 area = FCT_Area_RightTan;
1728                 return;
1729             }
1730         }
1731 
1732         area = FCT_Area_None;
1733         closest_point = 0;
1734         lit_point = -1;
1735 
1736         for (int i = 0; i < (int)curve.x.size(); i++) {
1737             if (curve.x.at(i) != -1) {
1738                 dX = curve.x.at(i) - preciseCursorX;
1739                 absDX = dX > 0 ? dX : -dX;
1740 
1741                 if (absDX < minDistX) {
1742                     minDistX = absDX;
1743                     closest_point = i;
1744                     lit_point = i;
1745                 }
1746 
1747                 if (absDX <= minDistanceX) {
1748                     aboveVLine = true;
1749                     dY = curve.y.at(i) - preciseCursorY;
1750                     dist = sqrt(dX * dX + dY * dY);
1751 
1752                     if (dist < minDist) {
1753                         minDist = dist;
1754                     }
1755                 }
1756             }
1757         }
1758 
1759         if (minDist <= minDistanceX) {
1760             // the cursor is over the point
1761             area = FCT_Area_Point;
1762         } else if (aboveVLine) {
1763             area = FCT_Area_V;
1764         } else {
1765             // Check if the cursor is in an insertion area
1766             lit_point = -1;
1767 
1768             if (preciseCursorX >= 0.0 && preciseCursorX <= 1.0 && preciseCursorY >= 0.0 && preciseCursorY <= 1.0) {
1769                 area = FCT_Area_Insertion;
1770             }
1771         }
1772     }
1773 }
1774 
getPoints()1775 std::vector<double> MyFlatCurve::getPoints ()
1776 {
1777     std::vector<double> result;
1778 
1779     /*if (curve.type==FCT_Parametric) {
1780         result.push_back ((double)(Parametric));
1781         for (int i=0; i<(int)curve.x.size(); i++) {
1782             result.push_back (curve.x.at(i));
1783         }
1784     }
1785     else {*/
1786     // the first value gives the type of the curve
1787     if (curve.type == FCT_Linear) {
1788         result.push_back ((double)(FCT_Linear));
1789     } else if (curve.type == FCT_MinMaxCPoints) {
1790         result.push_back ((double)(FCT_MinMaxCPoints));
1791     }
1792 
1793     // then we push all the points coordinate
1794     for (int i = 0; i < (int)curve.x.size(); i++) {
1795         if (curve.x.at(i) >= 0) {
1796             result.push_back (curve.x.at(i));
1797             result.push_back (curve.y.at(i));
1798             result.push_back (curve.leftTangent.at(i));
1799             result.push_back (curve.rightTangent.at(i));
1800         }
1801     }
1802 
1803     //}
1804     return result;
1805 }
1806 
setPoints(const std::vector<double> & p)1807 void MyFlatCurve::setPoints (const std::vector<double>& p)
1808 {
1809     int ix = 0;
1810     stopNumericalAdjustment();
1811     FlatCurveType t = (FlatCurveType)p[ix++];
1812     curve.type = t;
1813     lit_point = -1;
1814 
1815     if (t == FCT_MinMaxCPoints) {
1816         curve.x.clear ();
1817         curve.y.clear ();
1818         curve.leftTangent.clear();
1819         curve.rightTangent.clear();
1820 
1821         for (int i = 0; i < (int)p.size() / 4; i++) {
1822             curve.x.push_back (p[ix++]);
1823             curve.y.push_back (p[ix++]);
1824             curve.leftTangent.push_back (p[ix++]);
1825             curve.rightTangent.push_back (p[ix++]);
1826         }
1827     }
1828 
1829     curveIsDirty = true;
1830     setDirty(true);
1831     queue_draw ();
1832 }
1833 
setPos(double pos,int chanIdx)1834 void MyFlatCurve::setPos(double pos, int chanIdx)
1835 {
1836     assert (edited_point > -1);
1837 
1838     switch (chanIdx) {
1839     case (0):
1840         curve.x.at(edited_point) = pos;
1841         break;
1842 
1843     case (1):
1844         curve.y.at(edited_point) = pos;
1845         break;
1846 
1847     case (2):
1848         curve.leftTangent.at(edited_point) = pos;
1849         break;
1850 
1851     case (3):
1852         curve.rightTangent.at(edited_point) = pos;
1853         break;
1854     }
1855 
1856     curveIsDirty = true;
1857     setDirty(true);
1858     draw();
1859     notifyListener ();
1860 }
1861 
stopNumericalAdjustment()1862 void MyFlatCurve::stopNumericalAdjustment()
1863 {
1864     if (edited_point > -1) {
1865         edited_point = lit_point = -1;
1866         area = FCT_Area_None;
1867         coordinateAdjuster->stopNumericalAdjustment();
1868         setDirty(true);
1869         draw();
1870     }
1871 }
1872 
setType(FlatCurveType t)1873 void MyFlatCurve::setType (FlatCurveType t)
1874 {
1875 
1876     curve.type = t;
1877     setDirty(true);
1878 }
1879 
reset(const std::vector<double> & resetCurve,double identityValue)1880 void MyFlatCurve::reset(const std::vector<double> &resetCurve, double identityValue)
1881 {
1882     calcDimensions();
1883 
1884     stopNumericalAdjustment();
1885 
1886     // If a resetCurve exist (non empty)
1887     if (!resetCurve.empty()) {
1888         setPoints(resetCurve);
1889         return;
1890     }
1891 
1892     switch (curve.type) {
1893     case  FCT_MinMaxCPoints :
1894         defaultCurve(identityValue);
1895         lit_point = -1;
1896         curveIsDirty = true;
1897         break;
1898 
1899     //case Parametric :
1900     // Nothing to do (?)
1901     default:
1902         break;
1903     }
1904 
1905     setDirty(true);
1906     draw();
1907 }
1908 
defaultCurve(double iVal)1909 void MyFlatCurve::defaultCurve (double iVal)
1910 {
1911 
1912     curve.x.resize(6);
1913     curve.y.resize(6);
1914     curve.leftTangent.resize(6);
1915     curve.rightTangent.resize(6);
1916 
1917     // Point for RGBCMY colors
1918     for (int i = 0; i < 6; i++) {
1919         curve.x.at(i) = (1. / 6.) * i;
1920         curve.y.at(i) = iVal;
1921         curve.leftTangent.at(i) = 0.35;
1922         curve.rightTangent.at(i) = 0.35;
1923     }
1924 }
1925