rawTherapee/rtgui/myflatcurve.cc
Hombre 8b2eac9a3d Pipette and "On Preview Widgets" branch. See issue 227
The pipette part is already working quite nice but need to be finished. The widgets part needs more work...
2014-01-21 23:37:36 +01:00

1276 lines
37 KiB
C++

/*
* This file is part of RawTherapee.
*
* Copyright (c) 2004-2010 Gabor Horvath <hgabor@rawtherapee.com>
*
* RawTherapee is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* RawTherapee is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with RawTherapee. If not, see <http://www.gnu.org/licenses/>.
*/
#include "myflatcurve.h"
#include "../rtengine/curves.h"
#include <cstring>
#include <gdkmm/types.h>
MyFlatCurve::MyFlatCurve () {
graphW = get_allocation().get_width() - RADIUS * 2;
graphH = get_allocation().get_height() - RADIUS * 2;
prevGraphW = graphW;
prevGraphH = graphH;
lit_point = -1;
closest_point = 0;
buttonPressed = false;
editedHandle = FCT_EditedHandle_None;
area = FCT_Area_None;
tanHandlesDisplayed = false;
periodic = true;
//bghist = new unsigned int[256];
signal_event().connect( sigc::mem_fun(*this, &MyFlatCurve::handleEvents) );
// By default, we create a curve with 8 control points
curve.type = FCT_MinMaxCPoints;
defaultCurve();
}
/*MyFlatCurve::~MyFlatCurve () {
}*/
std::vector<double> MyFlatCurve::get_vector (int veclen) {
// Create the output variable
std::vector<double> convertedValues;
// Get the curve control points
std::vector<double> curveDescr = getPoints ();
rtengine::FlatCurve* rtcurve = new rtengine::FlatCurve (curveDescr, periodic, veclen*1.2 > 5000 ? 5000 : veclen*1.2);
// Create the sample values that will be converted
std::vector<double> samples;
samples.resize (veclen);
for (int i = 0; i < veclen; i++)
samples.at(i) = (double) i / (veclen - 1.0);
// Converting the values
rtcurve->getVal (samples, convertedValues);
// Cleanup and return
delete rtcurve;
return convertedValues;
}
void MyFlatCurve::interpolate () {
prevGraphW = graphW;
prevGraphH = graphH;
int nbPoints = graphW-2;
point.resize (nbPoints);
std::vector<double> vector = get_vector (nbPoints);
for (int i = 0; i < nbPoints; ++i) {
float currX = float(i)/float(nbPoints-1);
point.at(i).setCoords(float(graphX)+1.5f+float(graphW-3)*currX, float(graphY)-1.5f-float(graphH-3)*float(vector.at(i)));
}
upoint.clear ();
lpoint.clear ();
curveIsDirty = false;
}
void MyFlatCurve::draw () {
if (!isDirty()) {
return;
}
Glib::RefPtr<Gdk::Window> win = get_window();
if (!surfaceCreated() || !win)
return;
// re-calculate curve if dimensions changed
if (curveIsDirty || prevGraphW != graphW || prevGraphH != graphH || (int)point.size() != graphW-2)
interpolate ();
double innerW = double(graphW-2);
double innerH = double(graphH-2);
Gtk::StateType state = !is_sensitive() ? Gtk::STATE_INSENSITIVE : Gtk::STATE_NORMAL;
Glib::RefPtr<Gtk::Style> style = get_style ();
Cairo::RefPtr<Cairo::Context> cr = getContext();
cr->set_line_cap(Cairo::LINE_CAP_SQUARE);
// clear background
Gdk::Color c = style->get_bg (Gtk::STATE_NORMAL);
cr->set_source_rgb (c.get_red_p(), c.get_green_p(), c.get_blue_p());
cr->rectangle (0, 0, double(getWidth()), double(getHeight()));
cr->fill ();
cr->set_line_width (1.0);
// draw f(x)=0.5 line
c = style->get_dark (state);
cr->set_source_rgb (c.get_red_p(), c.get_green_p(), c.get_blue_p());
std::valarray<double> ds (1);
ds[0] = 4;
cr->set_dash (ds, 0);
cr->move_to (double(graphX)+1.5, double(graphY-graphH/2)-0.5);
cr->rel_line_to (double(graphW-3), 0.);
cr->stroke ();
cr->unset_dash ();
cr->set_antialias (Cairo::ANTIALIAS_SUBPIXEL);
cr->set_line_width (1.0);
// draw the left colored bar
if (leftBar) {
// first the background
int bWidth = getBarWidth();
BackBuffer *bb = this;
leftBar->setDrawRectangle(win, 1, graphY-graphH+1, bWidth-2, graphH-2);
leftBar->expose(bb);
// now the border
c = style->get_dark (state);
cr->set_source_rgb (c.get_red_p(), c.get_green_p(), c.get_blue_p());
cr->rectangle(0.5, graphY-graphH+0.5, bWidth-1, graphH-1);
cr->stroke();
}
// draw the bottom colored bar
if (bottomBar) {
// first the background
int bWidth = getBarWidth();
BackBuffer *bb = this;
bottomBar->setDrawRectangle(win, graphX+1, graphY+CBAR_MARGIN+1, graphW-2, bWidth-2);
bottomBar->expose(bb);
// now the border
c = style->get_dark (state);
cr->set_source_rgb (c.get_red_p(), c.get_green_p(), c.get_blue_p());
cr->rectangle(graphX+0.5, graphY+CBAR_MARGIN+0.5, graphW-1, bWidth-1 );
cr->stroke();
}
cr->set_line_cap(Cairo::LINE_CAP_BUTT);
// draw the color feedback of the control points
if (colorProvider) {
//if (curve.type!=FCT_Parametric)
for (int i=0; i<(int)curve.x.size(); ++i) {
if (curve.x[i] != -1.) {
int coloredLineWidth = min( max(75,graphW)/75, 8 );
cr->set_line_width (coloredLineWidth);
colorProvider->colorForValue(curve.x[i], 0.5, colorCallerId, this);
cr->set_source_rgb (ccRed, ccGreen, ccBlue);
if ( i==lit_point && (editedHandle&(FCT_EditedHandle_CPointUD|FCT_EditedHandle_CPoint|FCT_EditedHandle_CPointX)) ) {
cr->set_line_width (2*coloredLineWidth);
}
cr->move_to (double(graphX)+1 + innerW*curve.x[i], double(graphY-1));
cr->rel_line_to (0., -innerH);
cr->stroke ();
cr->set_line_width (coloredLineWidth);
// draw the lit_point's horizontal line
if (i == lit_point) {
if ( (area&(FCT_Area_H|FCT_Area_V|FCT_Area_Point)) || editedHandle==FCT_EditedHandle_CPointUD) {
if (editedHandle&(FCT_EditedHandle_CPointUD|FCT_EditedHandle_CPoint|FCT_EditedHandle_CPointY)) {
cr->set_line_width (2*coloredLineWidth);
}
colorProvider->colorForValue(curve.x[i], curve.y[i], colorCallerId, this);
cr->set_source_rgb (ccRed, ccGreen, ccBlue);
cr->move_to (double(graphX+1) , double(graphY-1) - innerH*curve.y[lit_point]);
cr->rel_line_to (innerW, 0.);
cr->stroke ();
}
}
}
}
// endif
cr->set_line_width (1.0);
}
else {
cr->set_source_rgb (0.5, 0.0, 0.0);
if ( (area&(FCT_Area_H|FCT_Area_V|FCT_Area_Point)) || editedHandle==FCT_EditedHandle_CPointUD ) {
// draw the lit_point's vertical line
if (editedHandle&(FCT_EditedHandle_CPointUD|FCT_EditedHandle_CPoint|FCT_EditedHandle_CPointY)) {
cr->set_line_width (2.0);
}
cr->move_to (double(graphX)+1 + innerW*curve.x[lit_point], double(graphY-1));
cr->rel_line_to (0., -innerH);
cr->stroke ();
cr->set_line_width (1.0);
// draw the lit_point's horizontal line
if (editedHandle&(FCT_EditedHandle_CPointUD|FCT_EditedHandle_CPoint|FCT_EditedHandle_CPointY)) {
cr->set_line_width (2.0);
}
cr->move_to (double(graphX+1) , double(graphY-1) - innerH*curve.y[lit_point]);
cr->rel_line_to (innerW, 0.);
cr->stroke ();
cr->set_line_width (1.0);
}
}
cr->set_line_cap(Cairo::LINE_CAP_SQUARE);
// draw the graph's borders:
c = style->get_dark (state);
cr->set_source_rgb (c.get_red_p(), c.get_green_p(), c.get_blue_p());
cr->rectangle(double(graphX)+0.5, double(graphY)-0.5, double(graphW-1), double(-graphH+1));
cr->stroke ();
double lineMinLength = 1. / graphW * SQUARE * 0.9;
if (lit_point!=-1 && getHandles(lit_point) && curve.x[lit_point]!=-1.) {
double x = double(graphX+1) + innerW*curve.x[lit_point];
double y = double(graphY) - innerH*curve.y[lit_point];
double x2;
double square;
bool crossingTheFrame;
// left handle is yellow
// TODO: finding a way to set the left handle color for flat curve editor
cr->set_source_rgb (1.0, 1.0, 0.0);
// draw tangential vectors
crossingTheFrame = false;
// We display the line only if it's longer than the handle knot half-size...
if (leftTanX < -0.00001) {
leftTanX += 1.0;
crossingTheFrame = true;
}
x2 = double(graphX+1) + innerW*leftTanX;
if (curve.x[lit_point] - leftTanX > lineMinLength || crossingTheFrame) {
// The left tangential vector reappear on the right side
// draw the line
cr->move_to (x, y);
if (crossingTheFrame) {
cr->line_to (double(graphX+1), y);
cr->stroke ();
cr->move_to (double(graphX) + innerW, y);
}
cr->line_to (x2, y);
cr->stroke ();
}
// draw tangential knot
square = area == FCT_Area_LeftTan ? SQUARE*2. : SQUARE;
cr->rectangle(x2-square, y-square, 2.*square, 2.*square);
cr->fill();
// right handle is blue
// TODO: finding a way to set the right handle color for flat curve editor
cr->set_source_rgb (0.0, 0.0, 1.0);
// draw tangential vectors
crossingTheFrame = false;
// We display the line only if it's longer than the handle knot half-size...
if (rightTanX > 1.00001) {
rightTanX -= 1.0;
crossingTheFrame = true;
}
x2 = double(graphX+1) +innerW*rightTanX;
if (rightTanX - curve.x[lit_point] > lineMinLength || crossingTheFrame) {
// The left tangential vector reappear on the right side
// draw the line
cr->move_to (x, y);
if (crossingTheFrame) {
cr->line_to (double(graphX) + innerW, y);
cr->stroke ();
cr->move_to (double(graphX+1), y);
}
cr->line_to (x2, y);
cr->stroke ();
}
// draw tangential knot
square = area == FCT_Area_RightTan ? SQUARE*2. : SQUARE;
cr->rectangle(x2-square, y-square, 2.*square, 2.*square);
cr->fill();
}
// draw curve
c = style->get_fg (state);
cr->set_source_rgb (c.get_red_p(), c.get_green_p(), c.get_blue_p());
cr->move_to (point.at(0).x, point.at(0).y);
for (int i=1; i<(int)point.size(); i++)
cr->line_to (double(point.at(i).x), double(point.at(i).y));
cr->stroke ();
// draw bullets
//if (curve.type!=FCT_Parametric)
for (int i = 0; i < (int)curve.x.size(); ++i) {
if (curve.x[i] != -1.) {
if (i == lit_point) {
if (colorProvider) {
colorProvider->colorForValue(curve.x[i], curve.y[i], colorCallerId, this);
cr->set_source_rgb (ccRed, ccGreen, ccBlue);
}
else
cr->set_source_rgb (1.0, 0.0, 0.0);
}
else if (i == snapToElmt) {
cr->set_source_rgb (1.0, 0.0, 0.0);
}
else if (curve.y[i] == 0.5)
cr->set_source_rgb (0.0, 0.5, 0.0);
else
cr->set_source_rgb (c.get_red_p(), c.get_green_p(), c.get_blue_p());
double x = double(graphX+1) + innerW * curve.x[i]; // project (curve.x[i], 0, 1, graphW);
double y = double(graphY-1) - innerH * curve.y[i]; // project (curve.y[i], 0, 1, graphH);
cr->arc (x, y, (double)RADIUS, 0, 2*M_PI);
cr->fill ();
}
}
// endif
// draw the left and right tangent handles
if (tanHandlesDisplayed) {
double halfSquareSizeX = minDistanceX/2.;
double halfSquareSizeY = minDistanceY/2.;
// LEFT handle
// yellow
cr->set_source_rgb (1.0, 1.0, 0.0);
cr->rectangle(double(graphX+1) + innerW*(leftTanHandle.centerX-halfSquareSizeX),
double(graphY-1) - innerH*(leftTanHandle.centerY+halfSquareSizeY),
innerW*minDistanceX,
innerW*minDistanceY);
cr->fill();
// RIGHT handle
// blue
cr->set_source_rgb (0.0, 0.0, 1.0);
cr->rectangle(double(graphX+1) + innerW*(rightTanHandle.centerX-halfSquareSizeX),
double(graphY-1) - innerH*(rightTanHandle.centerY+halfSquareSizeY),
innerW*minDistanceX,
innerW*minDistanceY);
cr->fill();
}
setDirty(false);
queue_draw();
}
/*
* Return the X1, X2, Y position of the tangential handles.
*/
bool MyFlatCurve::getHandles(int n) {
int N = curve.x.size();
double prevX, nextX;
double x, leftTan, rightTan;
if (n == -1) return false;
x = curve.x[n];
leftTan = curve.leftTangent[n];
rightTan = curve.rightTangent[n];
if (!n) {
// first point, the left handle is then computed with the last point's right handle
prevX = curve.x[N-1]-1.0;
nextX = curve.x[n+1];
}
else if (n == N-1) {
// last point, the right handle is then computed with the first point's left handle
prevX = curve.x[n-1];
nextX = curve.x[0]+1.0;
}
else {
// last point, the right handle is then computed with the first point's left handle
prevX = curve.x[n-1];
nextX = curve.x[n+1];
}
if (leftTan == 0.0) leftTanX = x;
else if (leftTan == 1.0) leftTanX = prevX;
else {
leftTanX = (prevX - x) * leftTan + x;
}
if (rightTan == 0.0) rightTanX = x;
else if (rightTan == 1.0) rightTanX = nextX;
else {
rightTanX = (nextX - x) * rightTan + x;
}
return true;
}
bool MyFlatCurve::handleEvents (GdkEvent* event) {
CursorShape new_type = cursor_type;
int src, dst;
std::vector<double>::iterator itx, ity, itlt, itrt;
snapToElmt = -100;
bool retval = false;
int num = (int)curve.x.size();
/* graphW and graphH are the size of the graph */
calcDimensions();
minDistanceX = double(MIN_DISTANCE) / double(graphW-1);
minDistanceY = double(MIN_DISTANCE) / double(graphH-1);
if ((graphW < 0) || (graphH < 0))
return false;
switch (event->type) {
case Gdk::CONFIGURE: {
// Happen when the the window is resized
if (sized & (RS_Pending | RS_Force)) {
set_size_request(-1, calcDimensions());
sized = RS_Done;
}
retval = true;
break;
}
case Gdk::EXPOSE:
{
Glib::RefPtr<Gdk::Window> win = get_window();
if (sized & (RS_Pending | RS_Force)) {
set_size_request(-1, calcDimensions());
}
sized = RS_Pending;
// setDrawRectangle will allocate the backbuffer Surface
if (setDrawRectangle(win, 0, 0, get_allocation().get_width(), get_allocation().get_height())) {
setDirty(true);
curveIsDirty = true;
}
draw ();
GdkRectangle *rectangle = &(event->expose.area);
copySurface(win, rectangle);
retval = true;
break;
}
case Gdk::BUTTON_PRESS:
//if (curve.type!=FCT_Parametric) {
if (event->button.button == 1) {
buttonPressed = true;
add_modal_grab ();
// get the pointer position
getCursorPosition(event);
getMouseOverArea();
// hide the tangent handles
tanHandlesDisplayed = false;
// Action on BUTTON_PRESS and no edited point
switch (area) {
case (FCT_Area_Insertion):
new_type = CSMove;
/* insert a new control point */
if (num > 0) {
if (clampedX > curve.x[closest_point])
++closest_point;
}
itx = curve.x.begin();
ity = curve.y.begin();
itlt = curve.leftTangent.begin();
itrt = curve.rightTangent.begin();
for (int i=0; i<closest_point; i++) { itx++; ity++; itlt++; itrt++; }
curve.x.insert (itx, 0);
curve.y.insert (ity, 0);
curve.leftTangent.insert (itlt, 0);
curve.rightTangent.insert (itrt, 0);
num++;
// the graph is refreshed only if a new point is created
curve.x[closest_point] = clampedX;
curve.y[closest_point] = clampedY;
curve.leftTangent[closest_point] = 0.35;
curve.rightTangent[closest_point] = 0.35;
curveIsDirty = true;
setDirty(true);
draw ();
notifyListener ();
lit_point = closest_point;
// point automatically activated
editedHandle = FCT_EditedHandle_CPoint;
ugpX = curve.x[lit_point];
ugpY = curve.y[lit_point];
break;
case (FCT_Area_Point):
new_type = CSMove;
editedHandle = FCT_EditedHandle_CPoint;
ugpX = curve.x[lit_point];
ugpY = curve.y[lit_point];
break;
case (FCT_Area_H):
case (FCT_Area_V):
new_type = CSMove;
editedHandle = FCT_EditedHandle_CPointUD;
ugpX = curve.x[lit_point];
ugpY = curve.y[lit_point];
break;
case (FCT_Area_LeftTan):
new_type = CSEmpty;
editedHandle = FCT_EditedHandle_LeftTan;
ugpX = curve.leftTangent[lit_point];
break;
case (FCT_Area_RightTan):
new_type = CSEmpty;
editedHandle = FCT_EditedHandle_RightTan;
ugpX = curve.rightTangent[lit_point];
break;
default:
break;
}
}
if (buttonPressed) retval = true;
//}
break;
case Gdk::BUTTON_RELEASE:
//if (curve.type!=FCT_Parametric) {
if (buttonPressed && event->button.button == 1) {
buttonPressed = false;
enum MouseOverAreas prevArea = area;
remove_modal_grab ();
// Removing any deleted point if we were previously modifying the point position
if (editedHandle & (FCT_EditedHandle_CPoint|FCT_EditedHandle_CPointX|FCT_EditedHandle_CPointY)) {
/* delete inactive points: */
itx = curve.x.begin();
ity = curve.y.begin();
itlt = curve.leftTangent.begin();
itrt = curve.rightTangent.begin();
for (src = dst = 0; src < num; ++src)
if (curve.x[src] >= 0.0) {
curve.x[dst] = curve.x[src];
curve.y[dst] = curve.y[src];
curve.leftTangent[dst] = curve.leftTangent[src];
curve.rightTangent[dst] = curve.rightTangent[src];
++dst;
++itx;
++ity;
++itlt;
++itrt;
}
if (dst < src) {
// curve cleanup
curve.x.erase (itx, curve.x.end());
curve.y.erase (ity, curve.y.end());
curve.leftTangent.erase (itlt, curve.leftTangent.end());
curve.rightTangent.erase (itrt, curve.rightTangent.end());
if (curve.x.empty()) {
curve.x.push_back (0.5);
curve.y.push_back (0.5);
curve.leftTangent.push_back (0.3);
curve.rightTangent.push_back (0.3);
curveIsDirty = true;
}
}
}
editedHandle = FCT_EditedHandle_None;
lit_point = -1;
// get the pointer position
getCursorPosition(event);
getMouseOverArea();
switch (area) {
case (FCT_Area_Insertion):
new_type = CSArrow;
break;
case (FCT_Area_Point):
new_type = CSMove;
break;
case (FCT_Area_H):
new_type = CSResizeHeight;
break;
case (FCT_Area_V):
new_type = CSMove;
break;
case (FCT_Area_LeftTan):
new_type = CSEmpty;
break;
case (FCT_Area_RightTan):
new_type = CSEmpty;
break;
default:
break;
}
setDirty(true);
draw ();
retval = true;
//notifyListener ();
}
//}
break;
case Gdk::MOTION_NOTIFY:
if (curve.type == FCT_Linear || curve.type == FCT_MinMaxCPoints) {
int previous_lit_point = lit_point;
enum MouseOverAreas prevArea = area;
snapToMinDistY = snapToMinDistX = 10.;
snapToValY = snapToValX = 0.;
snapToElmt = -100;
// get the pointer position
getCursorPosition(event);
getMouseOverArea();
if (editedHandle == FCT_EditedHandle_CPointUD) {
double dX = deltaX;
double dY = deltaY;
if (dX < 0.) dX = -dX;
if (dY < 0.) dY = -dY;
if (dX > dY) {
editedHandle = FCT_EditedHandle_CPointX;
area = FCT_Area_V;
new_type = CSResizeWidth;
}
else {
editedHandle = FCT_EditedHandle_CPointY;
area = FCT_Area_H;
new_type = CSResizeHeight;
}
}
switch (editedHandle) {
case (FCT_EditedHandle_None): {
if ((lit_point != -1 && previous_lit_point != lit_point) && (area&(FCT_Area_V|FCT_Area_Point))) {
bool sameSide = false;
// display the handles
tanHandlesDisplayed = true;
if (curve.x[lit_point] < 3.*minDistanceX) {
// lit_point too near of the left border -> both handles are displayed on the right of the vertical line
rightTanHandle.centerX = leftTanHandle.centerX = curve.x[lit_point] + 2.*minDistanceX;
sameSide = true;
}
else if (curve.x[lit_point] > 1.-3.*minDistanceX) {
// lit_point too near of the left border -> both handles are displayed on the right of the vertical line
rightTanHandle.centerX = leftTanHandle.centerX = curve.x[lit_point] - 2.*minDistanceX;
sameSide = true;
}
else {
leftTanHandle.centerX = curve.x[lit_point] - 2.*minDistanceX;
rightTanHandle.centerX = curve.x[lit_point] + 2.*minDistanceX;
}
if (sameSide) {
if (clampedY > 1.-2.*minDistanceY) {
// lit_point too near of the top border
leftTanHandle.centerY = 1. - minDistanceY;
rightTanHandle.centerY = 1. - 3.*minDistanceY;
}
else if (clampedY < 2.*minDistanceY) {
// lit_point too near of the bottom border
leftTanHandle.centerY = 3.*minDistanceY;
rightTanHandle.centerY = minDistanceY;
}
else {
leftTanHandle.centerY = clampedY + minDistanceY;
rightTanHandle.centerY = clampedY - minDistanceY;
}
}
else {
if (clampedY > 1.-minDistanceY) {
rightTanHandle.centerY = leftTanHandle.centerY = 1. - minDistanceY;
}
else if (clampedY < minDistanceY) {
rightTanHandle.centerY = leftTanHandle.centerY = minDistanceY;
}
else {
rightTanHandle.centerY = leftTanHandle.centerY = clampedY;
}
}
}
else if (lit_point == -1) {
tanHandlesDisplayed = false;
}
switch (area) {
case (FCT_Area_Insertion):
new_type = CSPlus;
break;
case (FCT_Area_Point):
//new_type = CSMove;
//break;
case (FCT_Area_V):
new_type = CSMove;
break;
case (FCT_Area_H):
new_type = CSResizeHeight;
break;
case (FCT_Area_LeftTan):
new_type = CSMoveLeft;
break;
case (FCT_Area_RightTan):
new_type = CSMoveRight;
break;
case (FCT_Area_None):
default:
new_type = CSArrow;
break;
}
if ((lit_point != previous_lit_point) || (prevArea != area)) {
setDirty(true);
draw ();
}
break;
}
case (FCT_EditedHandle_CPoint):
movePoint(true, true);
break;
case (FCT_EditedHandle_CPointX):
movePoint(true, false);
break;
case (FCT_EditedHandle_CPointY):
movePoint(false, true);
break;
case (FCT_EditedHandle_LeftTan): {
double prevValue = curve.leftTangent[lit_point];
ugpX -= deltaX*3;
ugpX = CLAMP(ugpX, 0., 1.);
if (snapTo) {
// since this handle can only move in one direction, we can reuse the snapCoordinateX mechanism
snapCoordinateX(0.0, ugpX);
snapCoordinateX(0.35, ugpX);
snapCoordinateX(0.5, ugpX);
snapCoordinateX(1.0, ugpX);
curve.leftTangent[lit_point] = snapToValX;
}
else {
curve.leftTangent[lit_point] = ugpX;
}
if (curve.leftTangent[lit_point] != prevValue) {
curveIsDirty = true;
setDirty(true);
draw ();
notifyListener ();
}
break;
}
case (FCT_EditedHandle_RightTan): {
double prevValue = curve.rightTangent[lit_point];
ugpX += deltaX*3;
ugpX = CLAMP(ugpX, 0., 1.);
if (snapTo) {
// since this handle can only move in one direction, we can reuse the snapCoordinateX mechanism
snapCoordinateX(0.0, ugpX);
snapCoordinateX(0.35, ugpX);
snapCoordinateX(0.5, ugpX);
snapCoordinateX(1.0, ugpX);
curve.rightTangent[lit_point] = snapToValX;
}
else {
curve.rightTangent[lit_point] = ugpX;
}
if (curve.rightTangent[lit_point] != prevValue) {
curveIsDirty = true;
setDirty(true);
draw ();
notifyListener ();
}
break;
}
// already processed before the "switch" instruction
//case (FCT_EditedHandle_CPointUD):
default:
break;
}
}
retval = true;
break;
case Gdk::LEAVE_NOTIFY:
// Pointer can LEAVE even when dragging the point, so we don't modify the cursor in this case
// The cursor will have to LEAVE another time after the drag...
if (editedHandle == FCT_EditedHandle_None) {
new_type = CSArrow;
lit_point = -1;
tanHandlesDisplayed = false;
setDirty(true);
draw ();
}
retval = true;
break;
default:
break;
}
if (new_type != cursor_type) {
cursor_type = new_type;
cursorManager.setCursor(cursor_type);
}
return retval;
}
void MyFlatCurve::movePoint(bool moveX, bool moveY) {
// bounds of the grabbed point
double leftBound;
double rightBound;
double const bottomBound = 0.;
double const topBound = 1.;
// we memorize the previous position of the point, for optimization purpose
double prevPosX = curve.x[lit_point];
double prevPosY = curve.y[lit_point];
int nbPoints = (int)curve.x.size();
// left and right bound rely on curve periodicity
leftBound = (lit_point == 0 ) ? (periodic && !snapTo ? curve.x[nbPoints-1]-1. : 0.) : curve.x[lit_point-1];
rightBound = (lit_point == nbPoints-1) ? (periodic && !snapTo ? curve.x[0]+1. : 1.) : curve.x[lit_point+1];
double leftDeletionBound = leftBound - minDistanceX;
double rightDeletionBound = rightBound + minDistanceX;
double bottomDeletionBound = bottomBound - minDistanceY;
double topDeletionBound = topBound + minDistanceY;
if (moveX) {
// we memorize the previous position of the point, for optimization purpose
ugpX += deltaX;
// handling periodicity (the first and last point can reappear at the other side of the X range)
if (periodic) {
if (snapTo) {
if (lit_point==0) {
snapCoordinateX(0.0, ugpX);
curve.x[0] = snapToValX;
}
else if (lit_point==(nbPoints-1)) {
snapCoordinateX(1.0, ugpX);
curve.x[nbPoints-1] = snapToValX;
}
}
else if (lit_point==0 && ugpX<0.) {
// the first point has to be placed at the tail of the point list
std::vector<double>::iterator itx, ity, itlt, itrt;
ugpX += 1.;
leftBound += 1.;
rightBound += 1.;
leftDeletionBound += 1.;
rightDeletionBound += 1.;
// adding a copy of the first point to the tail of the list
curve.x.push_back(curve.x[0]);
curve.y.push_back(curve.y[0]);
curve.leftTangent.push_back(curve.leftTangent[0]);
curve.rightTangent.push_back(curve.rightTangent[0]);
// deleting the first point
itx = curve.x.begin();
ity = curve.y.begin();
itlt = curve.leftTangent.begin();
itrt = curve.rightTangent.begin();
curve.x.erase(itx);
curve.y.erase(ity);
curve.leftTangent.erase(itlt);
curve.rightTangent.erase(itrt);
lit_point = nbPoints-1;
}
else if (lit_point==(nbPoints-1) && ugpX>1.) {
// the last point has to be placed at the head of the point list
std::vector<double>::iterator itx, ity, itlt, itrt;
ugpX -= 1.;
leftBound -= 1.;
rightBound -= 1.;
leftDeletionBound -= 1.;
rightDeletionBound -= 1.;
// adding a copy of the last point to the head of the list
itx = curve.x.begin();
ity = curve.y.begin();
itlt = curve.leftTangent.begin();
itrt = curve.rightTangent.begin();
curve.x.insert(itx,0);
curve.y.insert(ity,0);
curve.leftTangent.insert(itlt,0);
curve.rightTangent.insert(itrt,0);
curve.x[0] = curve.x[nbPoints];
curve.y[0] = curve.y[nbPoints];
curve.leftTangent[0] = curve.leftTangent[nbPoints];
curve.rightTangent[0] = curve.rightTangent[nbPoints];
// deleting the last point
curve.x.pop_back();
curve.y.pop_back();
curve.leftTangent.pop_back();
curve.rightTangent.pop_back();
lit_point = 0;
}
}
// handling limitations along X axis
if (ugpX >= rightDeletionBound && nbPoints>2 && !snapTo) {
curve.x[lit_point] = -1.;
}
else if (ugpX <= leftDeletionBound && nbPoints>2 && !snapTo) {
curve.x[lit_point] = -1.;
}
else
// nextPosX is in bounds
curve.x[lit_point] = CLAMP(ugpX, leftBound, rightBound);
}
if (moveY) {
// we memorize the previous position of the point, for optimization purpose
ugpY += deltaY;
// snapping point to specific values
if (snapTo && curve.x[lit_point] != -1) {
// the unclamped grabbed point is brought back in the range
ugpY = CLAMP(ugpY, 0.0, 1.0);
if (lit_point == 0) {
int prevP = curve.y.size()-1;
if (snapCoordinateY(curve.y[prevP], ugpY)) snapToElmt = prevP;
}
else {
int prevP = lit_point-1;
if (snapCoordinateY(curve.y[prevP], ugpY)) snapToElmt = prevP;
}
if (curve.y.size() > 2) {
if (lit_point == (curve.y.size()-1)) {
if (snapCoordinateY(curve.y[0], ugpY)) snapToElmt = 0;
}
else {
int nextP = lit_point+1;
if (snapCoordinateY(curve.y[nextP], ugpY)) snapToElmt = nextP;
}
}
if (snapCoordinateY(1.0, ugpY)) snapToElmt = -3;
if (snapCoordinateY(0.5, ugpY)) snapToElmt = -2;
if (snapCoordinateY(0.0, ugpY)) snapToElmt = -1;
curve.y[lit_point] = snapToValY;
}
// Handling limitations along Y axis
if (ugpY >= topDeletionBound && nbPoints>2) {
if (curve.x[lit_point] != -1.) {
deletedPointX = curve.x[lit_point];
curve.x[lit_point] = -1.;
curve.y[lit_point] = ugpY; // This is only to force the redraw of the curve
}
}
else if (ugpY <= bottomDeletionBound && nbPoints>2) {
if (curve.x[lit_point] != -1.) {
deletedPointX = curve.x[lit_point];
curve.x[lit_point] = -1.;
curve.y[lit_point] = ugpY; // This is only to force the redraw of the curve
}
}
else {
// nextPosY is in the bounds
if (!snapTo) curve.y[lit_point] = CLAMP(ugpY, 0.0, 1.0);
if (!moveX && curve.x[lit_point] == -1.) {
// bring back the X value of the point if it reappear
curve.x[lit_point] = deletedPointX;
}
}
}
if (curve.x[lit_point] != prevPosX || curve.y[lit_point] != prevPosY) {
// we recompute the curve only if we have to
curveIsDirty = true;
setDirty(true);
draw ();
notifyListener ();
}
}
// Set datas relative to cursor position
void MyFlatCurve::getCursorPosition(GdkEvent* event) {
int tx, ty;
int prevCursorX, prevCursorY;
double incrementX = 1. / double(graphW);
double incrementY = 1. / double(graphH);
switch (event->type) {
case (Gdk::MOTION_NOTIFY) :
if (event->motion.is_hint) {
get_window()->get_pointer (tx, ty, mod_type);
}
else {
tx = (int)event->button.x;
ty = (int)event->button.y;
mod_type = (Gdk::ModifierType)event->button.state;
}
break;
case (Gdk::BUTTON_PRESS) :
case (Gdk::BUTTON_RELEASE) :
tx = (int)event->button.x;
ty = (int)event->button.y;
mod_type = (Gdk::ModifierType)event->button.state;
break;
default :
// The cursor position is not available
return;
break;
}
if (editedHandle != FCT_EditedHandle_None) {
prevCursorX = cursorX;
prevCursorY = cursorY;
}
cursorX = tx - graphX;
cursorY = graphY - ty;
preciseCursorX = cursorX * incrementX;
preciseCursorY = cursorY * incrementY;
snapTo = false;
// update deltaX/Y if the user drags a point
if (editedHandle != FCT_EditedHandle_None) {
// set the dragging factor
int control_key = mod_type & GDK_CONTROL_MASK;
int shift_key = mod_type & GDK_SHIFT_MASK;
// the increment get smaller if modifier key are used, and "snap to" may be enabled
if (control_key) { incrementX *= 0.05; incrementY *= 0.05; }
if (shift_key) { snapTo = true; }
deltaX = (double)(cursorX - prevCursorX) * incrementX;
deltaY = (double)(cursorY - prevCursorY) * incrementY;
}
// otherwise set the position of the new point (modifier keys has no effect here)
else {
clampedX = CLAMP (preciseCursorX, 0., 1.); // X position of the pointer from the origin of the graph
clampedY = CLAMP (preciseCursorY, 0., 1.); // Y position of the pointer from the origin of the graph
}
}
// Find out the active area under the cursor
void MyFlatCurve::getMouseOverArea () {
// When dragging an element, editedHandle keep its value
if (editedHandle == FCT_EditedHandle_None) { // && curve.type!=Parametric
double minDist = 1000; // used to find out the point pointed by the cursor (over it)
double minDistX = 1000; // used to find out the closest point
double dX, dY;
double absDX;
double dist;
bool aboveVLine = false;
// NB: this function assume that the graph's shape is a square
// Check if the cursor is over a tangent handle
if (tanHandlesDisplayed) {
if (preciseCursorX>=(leftTanHandle.centerX-minDistanceX) && preciseCursorX<=(leftTanHandle.centerX+minDistanceX+0.00001)
&& preciseCursorY>=(leftTanHandle.centerY-minDistanceY) && preciseCursorY<=(leftTanHandle.centerY+minDistanceY)) {
area = FCT_Area_LeftTan;
return;
}
if (preciseCursorX>=(rightTanHandle.centerX-minDistanceX-0.00001) && preciseCursorX<=(rightTanHandle.centerX+minDistanceX)
&& preciseCursorY>=(rightTanHandle.centerY-minDistanceY ) && preciseCursorY<=(rightTanHandle.centerY+minDistanceY)) {
area = FCT_Area_RightTan;
return;
}
}
area = FCT_Area_None;
closest_point = 0;
lit_point = -1;
for (int i = 0; i < (int)curve.x.size(); i++) {
if (curve.x[i] != -1) {
dX = curve.x[i] - preciseCursorX;
absDX = dX>0 ? dX : -dX;
if (absDX < minDistX) {
minDistX = absDX;
closest_point = i;
lit_point = i;
}
if (absDX <= minDistanceX) {
aboveVLine = true;
dY = curve.y[i] - preciseCursorY;
dist = sqrt(dX*dX + dY*dY);
if (dist < minDist) {
minDist = dist;
}
}
}
}
if (minDist <= minDistanceX) {
// the cursor is over the point
area = FCT_Area_Point;
}
else if (aboveVLine) {
area = FCT_Area_V;
}
else {
// Check if the cursor is in an insertion area
lit_point = -1;
if (preciseCursorX>=0.0 && preciseCursorX<=1.0 && preciseCursorY>=0.0 && preciseCursorY<=1.0)
area = FCT_Area_Insertion;
}
}
}
std::vector<double> MyFlatCurve::getPoints () {
std::vector<double> result;
/*if (curve.type==FCT_Parametric) {
result.push_back ((double)(Parametric));
for (int i=0; i<(int)curve.x.size(); i++) {
result.push_back (curve.x[i]);
}
}
else {*/
// the first value gives the type of the curve
if (curve.type==FCT_Linear)
result.push_back ((double)(FCT_Linear));
else if (curve.type==FCT_MinMaxCPoints)
result.push_back ((double)(FCT_MinMaxCPoints));
// then we push all the points coordinate
for (int i=0; i<(int)curve.x.size(); i++) {
if (curve.x[i]>=0) {
result.push_back (curve.x[i]);
result.push_back (curve.y[i]);
result.push_back (curve.leftTangent[i]);
result.push_back (curve.rightTangent[i]);
}
}
//}
return result;
}
void MyFlatCurve::setPoints (const std::vector<double>& p) {
int ix = 0;
FlatCurveType t = (FlatCurveType)p[ix++];
curve.type = t;
if (t==FCT_MinMaxCPoints) {
curve.x.clear ();
curve.y.clear ();
curve.leftTangent.clear();
curve.rightTangent.clear();
for (int i=0; i<(int)p.size()/4; i++) {
curve.x.push_back (p[ix++]);
curve.y.push_back (p[ix++]);
curve.leftTangent.push_back (p[ix++]);
curve.rightTangent.push_back (p[ix++]);
}
}
curveIsDirty = true;
setDirty(true);
queue_draw ();
}
void MyFlatCurve::setType (FlatCurveType t) {
curve.type = t;
setDirty(true);
}
void MyFlatCurve::reset(double identityValue) {
calcDimensions();
switch (curve.type) {
case FCT_MinMaxCPoints :
defaultCurve(identityValue);
lit_point = -1;
curveIsDirty = true;
break;
//case Parametric :
// Nothing to do (?)
default:
break;
}
setDirty(true);
draw();
}
void MyFlatCurve::defaultCurve (double iVal) {
curve.x.resize(6);
curve.y.resize(6);
curve.leftTangent.resize(6);
curve.rightTangent.resize(6);
// Point for RGBCMY colors
for (int i=0; i<6; i++) {
curve.x.at(i) = (1./6.)*i;
curve.y.at(i) = iVal;
curve.leftTangent.at(i) = 0.35;
curve.rightTangent.at(i) = 0.35;
}
}