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