rawTherapee/rtgui/myflatcurve.cc
Hombre a95deb4b98 Themes has been updated (solving issue #690), now that libclearlooks has been compiled for the Windows platform (to be downloaded in the dependancies folder of the FTP site)
A new checkbox has been added in the preference window to switch to a "slim" version of the selected theme, for low resolution screens
Curves now uses the theme colors too (was hard to see before with dark themes)
Themes has been named with this convention (of mine, yes) :
[Background ligthness]-[background color]-[Selected items color].gtkrc
Only themes with .gtkrc suffix are now displayed in the combo box
2011-06-08 01:46:51 +02:00

1353 lines
40 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 <curves.h>
#include <string.h>
#include <gdkmm/types.h>
MyFlatCurve::MyFlatCurve () {
innerWidth = get_allocation().get_width() - RADIUS * 2;
innerHeight = get_allocation().get_height() - RADIUS * 2;
prevInnerHeight = innerHeight;
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.5 > 5000 ? 5000 : veclen*1.5);
// Create the sample values that will be converted
std::vector<double> samples;
samples.resize (veclen);
for (int i = 0; i < veclen; i++)
samples[i] = (double) i / (veclen - 1.0);
// Converting the values
rtcurve->getVal (samples, convertedValues);
// Cleanup and return
delete rtcurve;
return convertedValues;
}
void MyFlatCurve::interpolate () {
prevInnerHeight = innerHeight;
point.resize (innerWidth+1);
std::vector<double> vector = get_vector (innerWidth+1);
for (int i = 0; i <= innerWidth; ++i)
point[i] = Gdk::Point ((double)RADIUS+0.5 + i, (double)RADIUS+0.5 + (double)innerHeight*(1.-vector[i]));
upoint.clear ();
lpoint.clear ();
/*if (curve.type==FCT_Parametric && activeParam>0) {
double tmp = curve.x[activeParam-1];
if (activeParam>=4) {
upoint.resize(innerWidth);
lpoint.resize(innerWidth);
curve.x[activeParam-1] = 100;
vector = get_vector (innerWidth);
for (int i = 0; i < innerWidth; ++i)
upoint[i] = Gdk::Point (RADIUS + i, RADIUS + innerHeight - (int)((innerHeight-1) * vector[i] + 0.5));
curve.x[activeParam-1] = -100;
vector = get_vector (innerWidth);
for (int i = 0; i < innerWidth; ++i)
lpoint[i] = Gdk::Point (RADIUS + i, RADIUS + innerHeight - (int)((innerHeight-1) * vector[i] + 0.5));
curve.x[activeParam-1] = tmp;
}
}*/
}
void MyFlatCurve::draw () {
if (!pixmap)
return;
// re-calculate curve if dimensions changed
if (prevInnerHeight != innerHeight || (int)point.size() != (innerWidth+1)) {
interpolate ();
}
Gtk::StateType state = !is_sensitive() ? Gtk::STATE_INSENSITIVE : Gtk::STATE_NORMAL;
Glib::RefPtr<Gtk::Style> style = get_style ();
Cairo::RefPtr<Cairo::Context> cr = pixmap->create_cairo_context();
// bounding rectangle
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, innerWidth+RADIUS*2+1.5, innerHeight+RADIUS*2+1.5);
cr->fill ();
// histogram in the background
/*if (bghistvalid) {
// find highest bin
unsigned int histheight = 0;
for (int i=0; i<256; i++)
if (bghist[i]>histheight)
histheight = bghist[i];
// draw histogram
cr->set_line_width (1.0);
double stepSize = (innerWidth-1) / 256.0;
cr->move_to (RADIUS, innerHeight-1+RADIUS);
c = style->get_fg (Gtk::STATE_INSENSITIVE);
cr->set_source_rgb (c.get_red_p(), c.get_green_p(), c.get_blue_p());
for (int i=0; i<256; i++) {
double val = bghist[i] * (double)(innerHeight-2) / (double)histheight;
if (val>innerHeight-1)
val = innerHeight-1;
if (i>0)
cr->line_to (i*stepSize+RADIUS, innerHeight-1+RADIUS-val);
}
cr->line_to (innerWidth-1+RADIUS, innerHeight-1+RADIUS);
cr->fill ();
}*/
cr->set_line_cap(Cairo::LINE_CAP_SQUARE);
// draw the grid lines:
cr->set_line_width (1.0);
cr->set_antialias (Cairo::ANTIALIAS_NONE);
c = style->get_dark (state);
cr->set_source_rgb (c.get_red_p(), c.get_green_p(), c.get_blue_p());
double x0 = (double)RADIUS-0.5;
double x1 = (double)RADIUS-0.5 + (double)innerWidth + 2.;
double y0 = (double)RADIUS-0.5;
double y1 = (double)RADIUS-0.5 + (double)innerHeight + 2.;
for (int i = 0; i < 5; i++) {
cr->move_to (x0, y0);
cr->line_to (x0, y1);
cr->line_to (x1, y1);
cr->line_to (x1, y0);
cr->line_to (x0, y0);
}
/*for (int i = 0; i < 5; i++) {
double currX = (double)RADIUS-0.5 + (double)i*((double)innerWidth + 2.)/4.;
double currY = (double)RADIUS-0.5 + (double)i*((double)innerHeight + 2.)/4.;
cr->move_to (x0, currY);
cr->line_to (x1, currY);
cr->move_to (currX, y0);
cr->line_to (currX, y1);
}*/
cr->stroke ();
// draw f(x)=0.5 line
c = style->get_fg (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 (x0, (double)RADIUS+0.5 + (double)innerHeight/2.);
cr->line_to (x1, (double)RADIUS+0.5 + (double)innerHeight/2.);
cr->stroke ();
cr->unset_dash ();
cr->set_antialias (Cairo::ANTIALIAS_SUBPIXEL);
cr->set_line_width (1.0);
// 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.) {
cr->set_line_width (1.0);
colorProvider->colorForValue(curve.x[i], 0.5);
cr->set_source_rgb (colorProvider->red, colorProvider->green, colorProvider->blue);
double x = (double)RADIUS+0.5 + innerWidth*curve.x[i];
if (i == lit_point && editedHandle&(FCT_EditedHandle_CPointUD|FCT_EditedHandle_CPoint|FCT_EditedHandle_CPointX)) {
cr->set_line_width (4.0);
}
cr->move_to (x, (double)RADIUS+0.5);
cr->line_to (x, (double)RADIUS+0.5 + innerHeight);
cr->stroke ();
cr->set_line_width (1.0);
// 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 (4.0);
}
colorProvider->colorForValue(curve.x[i], curve.y[i]);
cr->set_source_rgb (colorProvider->red, colorProvider->green, colorProvider->blue);
double y = (double)RADIUS+0.5 + (double)innerHeight*(1.-curve.y[lit_point]);
cr->move_to ( RADIUS, y);
cr->line_to ((double)RADIUS+0.5 + (double)innerWidth, y);
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) {
double position;
// draw the lit_point's vertical line
if (editedHandle==(FCT_EditedHandle_CPointUD|FCT_EditedHandle_CPoint|FCT_EditedHandle_CPointY)) {
cr->set_line_width (2.0);
}
position = (double)RADIUS+0.5 + (double)innerWidth*curve.x[lit_point];
cr->move_to (position, (double)RADIUS+0.5);
cr->line_to (position, (double)RADIUS+0.5 + (double)innerHeight);
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);
}
position = (double)RADIUS+0.5 + (double)innerHeight*(1.-curve.y[lit_point]);
cr->move_to ((double)RADIUS+0.5 , position);
cr->line_to ((double)RADIUS+0.5 + (double)innerWidth, position);
cr->stroke ();
cr->set_line_width (1.0);
}
}
double lineMinLength = 1. / innerWidth * SQUARE * 0.9;
if (lit_point!=-1 && getHandles(lit_point) && curve.x[lit_point]!=-1.) {
double x = (double)RADIUS+0.5 + (double)innerWidth * curve.x[lit_point];
double y = (double)RADIUS+0.5 + (double)innerHeight * (1.-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)RADIUS+0.5 + (double)innerWidth * 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)RADIUS+0.5, y);
cr->stroke ();
cr->move_to ((double)RADIUS+0.5 + (double)innerWidth, y);
}
cr->line_to (x2, y);
cr->stroke ();
}
// draw tangential knot
square = area == FCT_Area_LeftTan ? SQUARE*2. : SQUARE;
cr->move_to(x2-square, y+square);
cr->line_to(x2+square, y+square);
cr->line_to(x2+square, y-square);
cr->line_to(x2-square, y-square);
cr->line_to(x2-square, y+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)RADIUS+0.5 + (double)innerWidth * 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)RADIUS+0.5 + (double)innerWidth, y);
cr->stroke ();
cr->move_to ((double)RADIUS+0.5, y);
}
cr->line_to (x2, y);
cr->stroke ();
}
// draw tangential knot
square = area == FCT_Area_RightTan ? SQUARE*2. : SQUARE;
cr->move_to(x2-square, y+square);
cr->line_to(x2+square, y+square);
cr->line_to(x2+square, y-square);
cr->line_to(x2-square, y-square);
cr->line_to(x2-square, y+square);
cr->fill();
}
// draw curve
cr->set_source_rgb (c.get_red_p(), c.get_green_p(), c.get_blue_p());
cr->move_to (point[0].get_x(), point[0].get_y());
for (int i=1; i<(int)point.size(); i++)
cr->line_to (point[i].get_x(), point[i].get_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]);
cr->set_source_rgb (colorProvider->red, colorProvider->green, colorProvider->blue);
}
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)RADIUS+0.5 + (double)innerWidth * curve.x[i]; // project (curve.x[i], 0, 1, innerWidth);
double y = (double)RADIUS+0.5 + (double)innerHeight * (1.-curve.y[i]); // project (curve.y[i], 0, 1, innerHeight);
cr->arc (x, y, (double)RADIUS, 0, 2*M_PI);
cr->fill ();
}
}
// endif
// draw the left and right tangent handles
if (tanHandlesDisplayed) {
double top, bottom, left, right;
double halfSquareSizeX, halfSquareSizeY;
// LEFT handle
halfSquareSizeX = minDistanceX/2.;
halfSquareSizeY = minDistanceY/2.;
//halfSquareSizeX = area == FCT_Area_LeftTan ? minDistanceX : minDistanceX/2.;
//halfSquareSizeY = area == FCT_Area_LeftTan ? minDistanceY : minDistanceY/2.;
top = leftTanHandle.centerY + halfSquareSizeY;
bottom = leftTanHandle.centerY - halfSquareSizeY;
left = leftTanHandle.centerX - halfSquareSizeX;
right = leftTanHandle.centerX + halfSquareSizeX;
// yellow
cr->set_source_rgb (1.0, 1.0, 0.0);
cr->move_to((double)RADIUS+0.5 + (double)innerWidth * left, (double)RADIUS+0.5 + (double)innerHeight * (1.-top));
cr->line_to((double)RADIUS+0.5 + (double)innerWidth * right, (double)RADIUS+0.5 + (double)innerHeight * (1.-top));
cr->line_to((double)RADIUS+0.5 + (double)innerWidth * right, (double)RADIUS+0.5 + (double)innerHeight * (1.-bottom));
cr->line_to((double)RADIUS+0.5 + (double)innerWidth * left, (double)RADIUS+0.5 + (double)innerHeight * (1.-bottom));
cr->line_to((double)RADIUS+0.5 + (double)innerWidth * left, (double)RADIUS+0.5 + (double)innerHeight * (1.-top));
cr->fill();
// RIGHT handle
//halfSquareSizeX = area == FCT_Area_RightTan ? minDistanceX : minDistanceX/2.;
//halfSquareSizeY = area == FCT_Area_RightTan ? minDistanceY : minDistanceY/2.;
top = rightTanHandle.centerY + halfSquareSizeY;
bottom = rightTanHandle.centerY - halfSquareSizeY;
left = rightTanHandle.centerX - halfSquareSizeX;
right = rightTanHandle.centerX + halfSquareSizeX;
// blue
cr->set_source_rgb (0.0, 0.0, 1.0);
cr->move_to((double)RADIUS+0.5 + (double)innerWidth * left, (double)RADIUS+0.5 + (double)innerHeight * (1.-top));
cr->line_to((double)RADIUS+0.5 + (double)innerWidth * right, (double)RADIUS+0.5 + (double)innerHeight * (1.-top));
cr->line_to((double)RADIUS+0.5 + (double)innerWidth * right, (double)RADIUS+0.5 + (double)innerHeight * (1.-bottom));
cr->line_to((double)RADIUS+0.5 + (double)innerWidth * left, (double)RADIUS+0.5 + (double)innerHeight * (1.-bottom));
cr->line_to((double)RADIUS+0.5 + (double)innerWidth * left, (double)RADIUS+0.5 + (double)innerHeight * (1.-top));
cr->fill();
}
get_window()->draw_drawable (style->get_fg_gc (state), pixmap, 0, 0, 0, 0, innerWidth + RADIUS * 2 + 1, innerHeight + RADIUS * 2 + 1);
}
/*
* Return the X1, X2, Y position of the tangential handles.
*/
bool MyFlatCurve::getHandles(int n) {
int N = curve.x.size();
double prevX, nextX;
double prevY, nextY;
double prevTan, nextTan;
double x, y, leftTan, rightTan;
if (n == -1) return false;
x = curve.x[n];
y = curve.y[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;
prevY = curve.y[N-1];
prevTan = curve.rightTangent[N-1];
nextX = curve.x[n+1];
nextY = curve.y[n+1];
nextTan = curve.leftTangent[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];
prevY = curve.y[n-1];
prevTan = curve.rightTangent[n-1];
nextX = curve.x[0]+1.0;
nextY = curve.y[0];
nextTan = curve.leftTangent[0];
}
else {
// last point, the right handle is then computed with the first point's left handle
prevX = curve.x[n-1];
prevY = curve.y[n-1];
prevTan = curve.rightTangent[n-1];
nextX = curve.x[n+1];
nextY = curve.y[n+1];
nextTan = curve.leftTangent[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();
/* innerWidth and innerHeight are the size of the graph */
innerWidth = get_allocation().get_width() - 2*RADIUS - 1;
innerHeight = get_allocation().get_height() - 2*RADIUS - 1;
minDistanceX = (double)(MIN_DISTANCE) / (double)innerWidth;
minDistanceY = (double)(MIN_DISTANCE) / (double)innerHeight;
if ((innerWidth < 0) || (innerHeight < 0))
return false;
switch (event->type) {
case Gdk::CONFIGURE: {
// Happen when the the window is resized
if (sized & (RS_Pending | RS_Force)) {
int size = get_allocation().get_width();
set_size_request(-1, size);
sized = RS_Done;
}
if (pixmap)
pixmap.clear ();
retval = true;
break;
}
case Gdk::EXPOSE:
if (sized & (RS_Pending | RS_Force)) {
int size = get_allocation().get_width();
set_size_request(-1, size);
}
sized = RS_Pending;
if (!pixmap) {
pixmap = Gdk::Pixmap::create (get_window(), get_allocation().get_width(), get_allocation().get_height());
interpolate ();
}
draw ();
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;
interpolate ();
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 ();
int previous_lit_point = lit_point;
// 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.size()) {
curve.x.push_back (0.5);
curve.y.push_back (0.5);
curve.leftTangent.push_back (0.3);
curve.rightTangent.push_back (0.3);
interpolate ();
}
}
}
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;
}
if ((lit_point != previous_lit_point) || (prevArea != area))
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;
snapToMinDist = 10.;
snapToVal = 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))
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) {
snapCoordinate(0.0, ugpX);
snapCoordinate(0.35, ugpX);
snapCoordinate(0.5, ugpX);
snapCoordinate(1.0, ugpX);
curve.leftTangent[lit_point] = snapToVal;
}
else {
curve.leftTangent[lit_point] = ugpX;
}
if (curve.leftTangent[lit_point] != prevValue) {
interpolate ();
draw ();
notifyListener ();
}
break;
}
case (FCT_EditedHandle_RightTan): {
double prevValue = curve.rightTangent[lit_point];
ugpX += deltaX*3;
ugpX = CLAMP(ugpX, 0., 1.);
if (snapTo) {
snapCoordinate(0.0, ugpX);
snapCoordinate(0.35, ugpX);
snapCoordinate(0.5, ugpX);
snapCoordinate(1.0, ugpX);
curve.rightTangent[lit_point] = snapToVal;
}
else {
curve.rightTangent[lit_point] = ugpX;
}
if (curve.rightTangent[lit_point] != prevValue) {
interpolate ();
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;
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 ? curve.x[nbPoints-1]-1. : 0.) : curve.x[lit_point-1];
rightBound = (lit_point == nbPoints-1) ? (periodic ? 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 (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) {
curve.x[lit_point] = -1.;
}
else if (ugpX <= leftDeletionBound && nbPoints>2) {
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 (snapCoordinate(curve.y[prevP], ugpY)) snapToElmt = prevP;
}
else {
int prevP = lit_point-1;
if (snapCoordinate(curve.y[prevP], ugpY)) snapToElmt = prevP;
}
if (curve.y.size() > 2) {
if (lit_point == (curve.y.size()-1)) {
if (snapCoordinate(curve.y[0], ugpY)) snapToElmt = 0;
}
else {
int nextP = lit_point+1;
if (snapCoordinate(curve.y[nextP], ugpY)) snapToElmt = nextP;
}
}
if (snapCoordinate(1.0, ugpY)) snapToElmt = -3;
if (snapCoordinate(0.5, ugpY)) snapToElmt = -2;
if (snapCoordinate(0.0, ugpY)) snapToElmt = -1;
curve.y[lit_point] = snapToVal;
}
// 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
interpolate ();
draw ();
notifyListener ();
}
}
// Set datas relative to cursor position
void MyFlatCurve::getCursorPosition(GdkEvent* event) {
int tx, ty;
int prevCursorX, prevCursorY;
double incrementX = 1. / (double)innerWidth;
double incrementY = 1. / (double)innerHeight;
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 - RADIUS;
cursorY = innerHeight - (ty - RADIUS);
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++]);
}
}
pixmap.clear ();
queue_draw ();
}
void MyFlatCurve::setType (FlatCurveType t) {
curve.type = t;
pixmap.clear ();
}
/*int flatmchistupdate (void* data) {
gdk_threads_enter ();
MyFlatCurveIdleHelper* mcih = (MyFlatCurveIdleHelper*)data;
if (mcih->destroyed) {
if (mcih->pending == 1)
delete mcih;
else
mcih->pending--;
gdk_threads_leave ();
return 0;
}
mcih->clearPixmap ();
mcih->myCurve->queue_draw ();
mcih->pending--;
gdk_threads_leave ();
return 0;
}*/
/*void MyFlatCurve::updateBackgroundHistogram (unsigned int* hist) {
if (hist!=NULL) {
memcpy (bghist, hist, 256*sizeof(unsigned int));
bghistvalid = true;
}
else
bghistvalid = false;
mcih->pending++;
g_idle_add (flatmchistupdate, mcih);
}*/
void MyFlatCurve::reset() {
innerWidth = get_allocation().get_width() - RADIUS * 2;
innerHeight = get_allocation().get_height() - RADIUS * 2;
switch (curve.type) {
case FCT_MinMaxCPoints :
defaultCurve();
lit_point = -1;
interpolate ();
break;
//case Parametric :
// Nothing to do (?)
default:
break;
}
draw();
}
void MyFlatCurve::defaultCurve () {
curve.x.clear();
curve.y.clear();
curve.leftTangent.clear();
curve.rightTangent.clear();
// Point for RGBCMY colors
for (int i=0; i<6; i++) {
curve.x.push_back((1./6.)*i);
curve.y.push_back(0.5);
curve.leftTangent.push_back(0.35);
curve.rightTangent.push_back(0.35);
}
}