Enhanced curve editor :

- graphical celan up (surrounding frame deleted, histogram realigned)
- a feedback tell what point is going to move
- on Button press, the point doesn't move to the cursor anymore. Instead, the cursor
  is hidden during the drag and reappear in the same place it disappeared
- by default, a 0.5 factor is used to move the point during the drag
  (i.e. you have to move the move of 2 pixel to move the point of one pixel)
- keyboard can be used to fine tune the point position (subpixel placement) :
   - CTRL : factor = 0.1
   - SHIFT : factor = 0.02
   - CTRL+SHIFT : factor = 0.005

In the mean time, i had to create a new "Empty" cursor to hide it.
A new method has been created to easily modify the cursor of the main window.
This commit is contained in:
Hombre
2010-07-05 14:54:34 +02:00
parent 6d5fc87c3d
commit 28feec5dd9
5 changed files with 191 additions and 93 deletions

View File

@@ -30,21 +30,25 @@ void CursorManager::init (Glib::RefPtr<Gdk::Window> mainWin) {
cCropMove = new Gdk::Cursor (Gdk::FLEUR);
cCropMoving = new Gdk::Cursor (Gdk::HAND2);
cCropSelection = new Gdk::Cursor (Gdk::CROSSHAIR);
#ifdef _WIN32
cNormal = new Gdk::Cursor (Gdk::LAST_CURSOR);
#else
cAdd = new Gdk::Cursor (Gdk::PLUS);
//#ifdef _WIN32
// cNormal = new Gdk::Cursor (Gdk::LAST_CURSOR);
//#else
cNormal = new Gdk::Cursor (Gdk::ARROW);
#endif
Glib::RefPtr<Gdk::Pixbuf> hand = safe_create_from_file(argv0+"/images/openhand22.png");
Glib::RefPtr<Gdk::Pixbuf> close_hand = safe_create_from_file(argv0+"/images/closedhand22.png");
Glib::RefPtr<Gdk::Pixbuf> wbpick = safe_create_from_file(argv0+"/images/wbpicker16.png");
cHand = hand ? new Gdk::Cursor (cNormal->get_display(), hand, 10, 10) : new Gdk::Cursor (Gdk::HAND2);
//#endif
Glib::RefPtr<Gdk::Pixbuf> hand = safe_create_from_file(argv0+"/images/openhand22.png");
Glib::RefPtr<Gdk::Pixbuf> close_hand = safe_create_from_file(argv0+"/images/closedhand22.png");
Glib::RefPtr<Gdk::Pixbuf> wbpick = safe_create_from_file(argv0+"/images/wbpicker16.png");
Glib::RefPtr<Gdk::Pixbuf> empty = safe_create_from_file(argv0+"/images/empty.png");
cHand = hand ? new Gdk::Cursor (cNormal->get_display(), hand, 10, 10) : new Gdk::Cursor (Gdk::HAND2);
cClosedHand = close_hand ? new Gdk::Cursor (cNormal->get_display(), close_hand, 10, 10) : new Gdk::Cursor (Gdk::HAND2);
cWB = wbpick ? new Gdk::Cursor (cNormal->get_display(), wbpick, 1, 12) : new Gdk::Cursor (Gdk::ARROW);
cHidden = empty ? new Gdk::Cursor (cNormal->get_display(), empty, 12, 12) : new Gdk::Cursor (Gdk::FLEUR);
mainWindow = mainWin;
}
/* Set the cursor of the given window */
void CursorManager::setCursor (Glib::RefPtr<Gdk::Window> window, CursorShape shape) {
if (shape==CSArrow)
@@ -67,6 +71,14 @@ void CursorManager::setCursor (Glib::RefPtr<Gdk::Window> window, CursorShape sha
window->set_cursor (*cCropSelection);
else if (shape==CSStraighten)
window->set_cursor (*cCropSelection);
else if (shape==CSPlus)
window->set_cursor (*cAdd);
else if (shape==CSEmpty)
window->set_cursor (*cHidden);
}
/* Set the cursor of the main window */
void CursorManager::setCursor (CursorShape shape) {
setCursor(mainWindow, shape);
}

View File

@@ -21,7 +21,7 @@
#include <gtkmm.h>
enum CursorShape {CSArrow, CSOpenHand, CSClosedHand, CSMove, CSResizeWidth, CSResizeHeight, CSResizeDiagonal, CSSpotWB, CSCropSelect, CSStraighten};
enum CursorShape {CSArrow, CSOpenHand, CSClosedHand, CSMove, CSResizeWidth, CSResizeHeight, CSResizeDiagonal, CSSpotWB, CSCropSelect, CSStraighten, CSPlus, CSEmpty};
class CursorManager {
@@ -33,14 +33,17 @@ class CursorManager {
Gdk::Cursor* cCropMoving;
Gdk::Cursor* cNormal;
Gdk::Cursor* cCropSelection;
Gdk::Cursor* cAdd;
Gdk::Cursor* cHand;
Gdk::Cursor* cClosedHand;
Gdk::Cursor* cWB;
Gdk::Cursor* cHidden;
Glib::RefPtr<Gdk::Window> mainWindow;
public:
void init (Glib::RefPtr<Gdk::Window> mainWin);
void setCursor (Glib::RefPtr<Gdk::Window> window, CursorShape shape);
void setCursor (CursorShape shape);
};
extern CursorManager cursorManager;

View File

@@ -40,13 +40,17 @@ CurveEditor::CurveEditor () : cl(NULL), activeParamControl(-1), realized(false),
// custom curve
customCurveBox = new Gtk::VBox ();
Gtk::HBox* tmpa = Gtk::manage (new Gtk::HBox ());
customCurve = Gtk::manage (new MyCurve ());
Gtk::AspectFrame* af = Gtk::manage (new Gtk::AspectFrame ("",Gtk::ALIGN_CENTER,Gtk::ALIGN_CENTER,1,false));
af->add (*customCurve);
customCurve->set_size_request (-1, 200);
Gtk::Table* cctab = Gtk::manage (new Gtk::Table (2,1));
//Gtk::AspectFrame* af = Gtk::manage (new Gtk::AspectFrame ("",Gtk::ALIGN_CENTER,Gtk::ALIGN_CENTER,1,false));
//af->add (*customCurve);
customCurve->set_size_request (GRAPH_SIZE+2*RADIUS, GRAPH_SIZE+2*RADIUS);
customCurve->setType (Spline);
customCurveBox->pack_start (*af, Gtk::PACK_EXPAND_WIDGET);
tmpa->pack_start (*customCurve, true, false, 4);
customCurveBox->pack_start (*tmpa, true, true,4);
//customCurveBox->set_size_request (0, -1);
Gtk::HBox* bbox = Gtk::manage (new Gtk::HBox ());
save = Gtk::manage (new Gtk::Button ());
save->add (*Gtk::manage (new Gtk::Image (Gtk::StockID("gtk-save"), Gtk::ICON_SIZE_BUTTON)));
@@ -55,7 +59,7 @@ CurveEditor::CurveEditor () : cl(NULL), activeParamControl(-1), realized(false),
bbox->pack_end (*save, Gtk::PACK_EXPAND_WIDGET, 4);
bbox->pack_end (*load, Gtk::PACK_EXPAND_WIDGET, 4);
customCurveBox->pack_end (*bbox, Gtk::PACK_SHRINK, 2);
customCurveBox->show_all ();
@@ -66,20 +70,20 @@ CurveEditor::CurveEditor () : cl(NULL), activeParamControl(-1), realized(false),
// parametric curve
paramCurveBox = new Gtk::VBox ();
paramCurve = Gtk::manage (new MyCurve ());
Gtk::Table* ctab = Gtk::manage (new Gtk::Table (2,1));
Gtk::AspectFrame* afp = Gtk::manage (new Gtk::AspectFrame ("",Gtk::ALIGN_CENTER,Gtk::ALIGN_CENTER,1,false));
afp->add (*paramCurve);
paramCurve->set_size_request (200, 200);
paramCurve = Gtk::manage (new MyCurve ());
Gtk::Table* paramctab = Gtk::manage (new Gtk::Table (2,1));
//Gtk::AspectFrame* afp = Gtk::manage (new Gtk::AspectFrame ("",Gtk::ALIGN_CENTER,Gtk::ALIGN_CENTER,1,false));
//afp->add (*paramCurve);
paramCurve->set_size_request (GRAPH_SIZE+2*RADIUS, GRAPH_SIZE+2*RADIUS);
paramCurve->setType (Parametric);
shcSelector = Gtk::manage (new SHCSelector ());
shcSelector->set_size_request (200, 20);
shcSelector->set_size_request (GRAPH_SIZE, 20);
ctab->attach (*afp, 0, 1, 0, 1, Gtk::FILL, Gtk::SHRINK, 2, 2);
ctab->attach (*shcSelector, 0, 1, 1, 2, Gtk::FILL, Gtk::SHRINK, 2, 2);
paramctab->attach (*paramCurve, 0, 1, 0, 1, Gtk::FILL, Gtk::SHRINK, RADIUS+2, RADIUS+2);
paramctab->attach (*shcSelector, 0, 1, 1, 2, Gtk::FILL, Gtk::SHRINK, RADIUS+2, 2);
Gtk::HBox* tmpb = Gtk::manage (new Gtk::HBox ());
tmpb->pack_start (*ctab, true, false);
tmpb->pack_start (*paramctab, true, false);
paramCurveBox->pack_start (*tmpb, true, true);

View File

@@ -19,18 +19,17 @@
#include <mycurve.h>
#include <curves.h>
#include <string.h>
#define RADIUS 3 /* radius of the control points */
#define MIN_DISTANCE 8 /* min distance between control points */
#include <gdkmm/types.h>
MyCurve::MyCurve () : listener(NULL), activeParam(-1), bghistvalid(false) {
cursor_type = Gdk::TOP_LEFT_ARROW;
cursor_type = CSArrow;
curve.type = Spline;
height = 0;
grab_point = -1;
lit_point = -1;
add_events(Gdk::EXPOSURE_MASK | Gdk::POINTER_MOTION_MASK | Gdk::POINTER_MOTION_HINT_MASK | Gdk::ENTER_NOTIFY_MASK | Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK | Gdk::BUTTON1_MOTION_MASK);
add_events(Gdk::EXPOSURE_MASK | Gdk::POINTER_MOTION_MASK | Gdk::POINTER_MOTION_HINT_MASK | Gdk::ENTER_NOTIFY_MASK | Gdk::LEAVE_NOTIFY_MASK | Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK | Gdk::BUTTON1_MOTION_MASK);
signal_event().connect( sigc::mem_fun(*this, &MyCurve::handleEvents) );
curve.x.push_back(0);
@@ -126,7 +125,8 @@ void MyCurve::interpolate (int width, int height) {
}
}
void MyCurve::draw (int width, int height) {
void MyCurve::draw (int width, int height, int handle) {
// width and heigth are the size of the graph
if (!pixmap)
return;
@@ -135,7 +135,6 @@ void MyCurve::draw (int width, int height) {
if (this->height != height || point.size() != width)
interpolate (width, height);
Gtk::StateType state = Gtk::STATE_NORMAL;
if (!is_sensitive())
state = Gtk::STATE_INSENSITIVE;
@@ -159,32 +158,32 @@ void MyCurve::draw (int width, int height) {
// draw histogram
cr->set_line_width (1.0);
double stepSize = (width-1) / 256.0;
cr->move_to (0, height-1);
cr->move_to (RADIUS, height-1+RADIUS);
cr->set_source_rgb (0.75, 0.75, 0.75);
for (int i=0; i<256; i++) {
double val = bghist[i] * (double)(height-2) / histheight;
if (val>height-1)
val = height-1;
if (i>0)
cr->line_to (i*stepSize, height-1-val);
cr->line_to (i*stepSize+RADIUS, height-1+RADIUS-val);
}
cr->line_to (width-1, height-1);
cr->line_to (width-1+RADIUS, height-1+RADIUS);
cr->fill ();
}
// draw the grid lines:
cr->set_line_width (1.0);
cr->set_line_width (1.0);
c = style->get_dark (state);
cr->set_source_rgb (c.get_red_p(), c.get_green_p(), c.get_blue_p());
cr->set_antialias (Cairo::ANTIALIAS_NONE);
for (int i = 0; i < 5; i++) {
cr->move_to (RADIUS, i * height / 4 + RADIUS);
cr->line_to (width + RADIUS, i * height / 4 + RADIUS);
cr->move_to (i * width / 4 + RADIUS, RADIUS);
cr->line_to (i * width / 4 + RADIUS, height + RADIUS);
cr->move_to (RADIUS, MAX(0,i * height / 4 - 1) + RADIUS);
cr->line_to (width + RADIUS, MAX(0,i * height / 4 - 1) + RADIUS);
cr->move_to (MAX(0,i * width / 4 - 1) + RADIUS, RADIUS);
cr->line_to (MAX(0,i * width / 4 - 1) + RADIUS, height + RADIUS);
}
cr->stroke ();
// draw f(x)=x line
cr->set_source_rgb (c.get_red_p(), c.get_green_p(), c.get_blue_p());
std::valarray<double> ds (1);
@@ -195,6 +194,7 @@ void MyCurve::draw (int width, int height) {
cr->stroke ();
cr->unset_dash ();
cr->set_antialias (Cairo::ANTIALIAS_SUBPIXEL);
cr->set_line_width (1.0);
@@ -221,10 +221,11 @@ void MyCurve::draw (int width, int height) {
// draw bullets
if (curve.type!=Parametric)
for (int i = 0; i < curve.x.size(); ++i) {
cr->set_source_rgb ((i == handle ? 1.0 : 0.0), 0.0, 0.0);
double x = ((width-1) * curve.x[i] + 0.5)+RADIUS; // project (curve.x[i], 0, 1, width);
double y = height - ((height-1) * curve.y[i] + 0.5)+RADIUS; // project (curve.y[i], 0, 1, height);
cr->arc (x, y, RADIUS, 0, 2*M_PI);
cr->arc (x, y, RADIUS+0.5, 0, 2*M_PI);
cr->fill ();
}
@@ -233,13 +234,17 @@ void MyCurve::draw (int width, int height) {
bool MyCurve::handleEvents (GdkEvent* event) {
Gdk::CursorType new_type = cursor_type;
CursorShape new_type = cursor_type;
int src, dst;
GdkEventMotion *mevent;
std::vector<double>::iterator itx, ity;
Glib::RefPtr<Gdk::Display> rt_display = Gtk::Widget::get_display();
Glib::RefPtr<Gdk::Screen> rt_screen = Gtk::Widget::get_screen();
bool retval = false;
/* width and height are the size of the graph */
int width = get_allocation().get_width() - RADIUS * 2;
int height = get_allocation().get_height() - RADIUS * 2;
@@ -250,13 +255,13 @@ bool MyCurve::handleEvents (GdkEvent* event) {
int tx, ty;
Gdk::ModifierType gm;
get_window()->get_pointer (tx, ty, gm);
int x = CLAMP ((tx - RADIUS), 0, width-1);
int y = CLAMP ((ty - RADIUS), 0, height-1);
int x = CLAMP ((tx - RADIUS), 0, width-1); // X position of the pointer from the origin of the graph
int y = height-1 - CLAMP ((ty - RADIUS), 0, height-1); // Y position of the pointer from the origin of the graph
unsigned int distance = ~0U;
int num = curve.x.size();
int closest_point = 0;
int closest_point = -1;
if (curve.type!=Parametric) {
for (int i = 0; i < num; ++i) {
int cx = (int)((width-1) * curve.x[i] + 0.5); //project (c->ctlpoint[i][0], min_x, c->max_x, width);
@@ -266,24 +271,30 @@ bool MyCurve::handleEvents (GdkEvent* event) {
}
}
}
switch (event->type) {
case Gdk::CONFIGURE:
if (pixmap)
pixmap.clear ();
case Gdk::EXPOSE:
// When does this event occurs ???
if (!pixmap) {
pixmap = Gdk::Pixmap::create (get_window(), get_allocation().get_width(), get_allocation().get_height());
interpolate (width, height);
}
draw (width, height);
draw (width, height, lit_point);
break;
case Gdk::BUTTON_PRESS:
if (curve.type!=Parametric) {
add_modal_grab ();
new_type = Gdk::PLUS;
// get cursor position
Gdk::ModifierType mod_type;
rt_display->get_pointer(cursor_x, cursor_y, mod_type);
new_type = CSEmpty;
if (distance > MIN_DISTANCE) {
/* insert a new control point */
if (num > 0) {
@@ -294,24 +305,30 @@ bool MyCurve::handleEvents (GdkEvent* event) {
itx = curve.x.begin();
ity = curve.y.begin();
for (int i=0; i<closest_point; i++) { itx++; ity++; }
curve.x.insert (itx, 0);
curve.y.insert (ity, 0);
num++;
}
grab_point = closest_point;
curve.x[grab_point] = (double) x / (width-1);
curve.y[grab_point] = (double) (height-y) / (height-1);
interpolate (width, height);
notifyListener ();
break;
curve.x.insert (itx, 0);
curve.y.insert (ity, 0);
num++;
// the graph is refreshed only if a new point is created (snaped to a pixel)
curve.x[closest_point] = (double) x / (width-1);
curve.y[closest_point] = (double) y / (height-1);
interpolate (width, height);
draw (width, height, closest_point);
}
grab_point = closest_point;
lit_point = closest_point;
ugp_x = curve.x[closest_point];
ugp_y = curve.y[closest_point];
notifyListener ();
break;
}
draw (width, height);
retval = true;
break;
case Gdk::BUTTON_RELEASE:
if (curve.type!=Parametric) {
remove_modal_grab ();
int previous_lit_point = lit_point;
/* delete inactive points: */
itx = curve.x.begin();
ity = curve.y.begin();
@@ -330,61 +347,115 @@ bool MyCurve::handleEvents (GdkEvent* event) {
curve.x.push_back (0);
curve.y.push_back (0);
interpolate (width, height);
draw (width, height);
draw (width, height, lit_point);
}
}
new_type = Gdk::FLEUR;
if (distance <= MIN_DISTANCE) {
new_type = CSMove;
lit_point = closest_point;
}
else {
new_type = CSPlus;
lit_point = -1;
}
if (lit_point != previous_lit_point)
draw (width, height, lit_point);
grab_point = -1;
retval = true;
notifyListener ();
}
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 (grab_point == -1)
new_type = CSArrow;
break;
case Gdk::MOTION_NOTIFY:
mevent = (GdkEventMotion *) event;
if (curve.type == Linear || curve.type == Spline) {
if (grab_point == -1) {
int previous_lit_point = lit_point;
/* if no point is grabbed... */
if (distance <= MIN_DISTANCE)
new_type = Gdk::FLEUR;
else
new_type = Gdk::PLUS;
if (distance <= MIN_DISTANCE) {
new_type = CSMove;
lit_point = closest_point;
}
else {
new_type = CSPlus;
lit_point = -1;
}
if ((new_type != cursor_type) || (lit_point != previous_lit_point))
draw (width, height, lit_point);
}
else {
/* drag the grabbed point */
new_type = Gdk::FLEUR;
int leftbound = -MIN_DISTANCE;
if (grab_point > 0)
leftbound = (int)((width-1)*curve.x[grab_point-1]+0.5);
int rightbound = width + RADIUS * 2 + MIN_DISTANCE;
if (grab_point + 1 < num)
rightbound = (int)((width-1)*curve.x[grab_point+1]+0.5);
if (tx <= leftbound || tx >= rightbound || ty > height + RADIUS * 2 + MIN_DISTANCE || ty < -MIN_DISTANCE)
curve.x[grab_point] = -1.0;
else {
curve.x[grab_point] = (double) x / (width-1);
curve.y[grab_point] = (double) (height-y) / (height-1);
int new_cursor_x, new_cursor_y;
double factor = 0.5;
// get cursor position
Gdk::ModifierType mod_type;
rt_display->get_pointer(new_cursor_x, new_cursor_y, mod_type);
// set the dragging factor
int control_key = gm & GDK_CONTROL_MASK;
int shift_key = gm & GDK_SHIFT_MASK;
// what is the speed factor
if (control_key && shift_key) factor = 0.005;
else if (shift_key) factor = 0.02;
else if (control_key) factor = 0.1;
// calculate the delta in [0.0 ; 1.0] range
double delta_x = (double)(new_cursor_x - cursor_x) * factor / (double)(width-1);
double delta_y = (double)(cursor_y - new_cursor_y) * factor / (double)(height-1);
// modification of the unclamped grabed point
ugp_x += delta_x;
ugp_y += delta_y;
// first and last point cannot be deleted anymore (there's no point to do it)
// for intermediate points, we look if the point must be deleted
if (grab_point > 0 && grab_point < num-1) {
double leftbound = curve.x[grab_point-1];
double rightbound = curve.x[grab_point+1];
double bottombound = (double)(-MIN_DISTANCE) * factor / (double)(height-1);
double topbound = (double)1.0 + (double)(MIN_DISTANCE) * factor / (double)(height-1);
if (ugp_x <= leftbound || ugp_x >= rightbound || ugp_y > topbound || ugp_y < bottombound) {
curve.x[grab_point] = -1.0;
}
}
// first and last points are clamped to the [0.0 ; 1.0] range
if (curve.x[grab_point] != -1.0) {
double new_curve_x = curve.x[grab_point] + delta_x;
double new_curve_y = curve.y[grab_point] + delta_y;
curve.x[grab_point] = CLAMP(new_curve_x,0.0,1.0);
curve.y[grab_point] = CLAMP(new_curve_y,0.0,1.0);
}
interpolate (width, height);
draw (width, height);
// move the cursor back (to avoid being limited by the screen)
rt_display->warp_pointer(rt_screen, cursor_x, cursor_y);
draw (width, height, lit_point);
notifyListener ();
}
}
if (new_type != cursor_type) {
cursor_type = new_type;
Gdk::Cursor* cursor = new Gdk::Cursor (get_display(), cursor_type);
get_window ()->set_cursor (*cursor);
delete cursor;
}
retval = true;
break;
default:
break;
}
if (new_type != cursor_type) {
cursor_type = new_type;
cursorManager.setCursor(cursor_type);
}
return retval;
}

View File

@@ -22,6 +22,11 @@
#include <gtkmm.h>
#include <vector>
#include <curvelistener.h>
#include <cursormanager.h>
#define RADIUS 3 /* radius of the control points. Assuming that the center of the spot is in the center of the pixel, the real RADIUS will be this value +0.5 */
#define MIN_DISTANCE 8 /* min distance between control points */
#define GRAPH_SIZE 200 /* size of the curve editor graphic */
enum CurveType {Linear, Spline, Parametric};
@@ -46,10 +51,11 @@ class MyCurve : public Gtk::DrawingArea {
protected:
CurveListener* listener;
CurveDescr curve;
Gdk::CursorType cursor_type;
CursorShape cursor_type;
Glib::RefPtr<Gdk::Pixmap> pixmap;
int height;
int grab_point;
int height;
int grab_point;
int lit_point;
int last;
std::vector<Gdk::Point> point;
std::vector<Gdk::Point> upoint;
@@ -58,8 +64,10 @@ class MyCurve : public Gtk::DrawingArea {
unsigned int bghist[256];
bool bghistvalid;
MyCurveIdleHelper* mcih;
int cursor_x, cursor_y;
double ugp_x, ugp_y; // unclamped grabed point coordinates
void draw (int width, int height);
void draw (int width, int height, int handle);
void interpolate (int width, int height);
std::vector<double> get_vector (int veclen);