From 28feec5dd97b8dc6bae6213ef280ab88e7a640dc Mon Sep 17 00:00:00 2001 From: Hombre Date: Mon, 5 Jul 2010 14:54:34 +0200 Subject: [PATCH] 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. --- rtgui/cursormanager.cc | 28 ++++-- rtgui/cursormanager.h | 5 +- rtgui/curveeditor.cc | 34 ++++--- rtgui/mycurve.cc | 201 ++++++++++++++++++++++++++++------------- rtgui/mycurve.h | 16 +++- 5 files changed, 191 insertions(+), 93 deletions(-) diff --git a/rtgui/cursormanager.cc b/rtgui/cursormanager.cc index 44479a0b0..0159dcf62 100644 --- a/rtgui/cursormanager.cc +++ b/rtgui/cursormanager.cc @@ -30,21 +30,25 @@ void CursorManager::init (Glib::RefPtr 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 hand = safe_create_from_file(argv0+"/images/openhand22.png"); - Glib::RefPtr close_hand = safe_create_from_file(argv0+"/images/closedhand22.png"); - Glib::RefPtr 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 hand = safe_create_from_file(argv0+"/images/openhand22.png"); + Glib::RefPtr close_hand = safe_create_from_file(argv0+"/images/closedhand22.png"); + Glib::RefPtr wbpick = safe_create_from_file(argv0+"/images/wbpicker16.png"); + Glib::RefPtr 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 window, CursorShape shape) { if (shape==CSArrow) @@ -67,6 +71,14 @@ void CursorManager::setCursor (Glib::RefPtr 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); +} diff --git a/rtgui/cursormanager.h b/rtgui/cursormanager.h index 6417d3058..d1bc6c05f 100644 --- a/rtgui/cursormanager.h +++ b/rtgui/cursormanager.h @@ -21,7 +21,7 @@ #include -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 mainWindow; public: void init (Glib::RefPtr mainWin); void setCursor (Glib::RefPtr window, CursorShape shape); + void setCursor (CursorShape shape); }; extern CursorManager cursorManager; diff --git a/rtgui/curveeditor.cc b/rtgui/curveeditor.cc index 7130d711b..ebac4b8b6 100644 --- a/rtgui/curveeditor.cc +++ b/rtgui/curveeditor.cc @@ -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); diff --git a/rtgui/mycurve.cc b/rtgui/mycurve.cc index eb1c78642..9abe04692 100644 --- a/rtgui/mycurve.cc +++ b/rtgui/mycurve.cc @@ -19,18 +19,17 @@ #include #include #include - -#define RADIUS 3 /* radius of the control points */ -#define MIN_DISTANCE 8 /* min distance between control points */ +#include 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 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::iterator itx, ity; + Glib::RefPtr rt_display = Gtk::Widget::get_display(); + Glib::RefPtr 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 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; } diff --git a/rtgui/mycurve.h b/rtgui/mycurve.h index f9af30825..0e297be31 100644 --- a/rtgui/mycurve.h +++ b/rtgui/mycurve.h @@ -22,6 +22,11 @@ #include #include #include +#include + +#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 pixmap; - int height; - int grab_point; + int height; + int grab_point; + int lit_point; int last; std::vector point; std::vector 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 get_vector (int veclen);