made prolooks more glade-friendly + assorted bugfixes
[libprolooks.git] / prolooks / Curve.vala
blob1f218c32cc2c4d961e24f40af050ab866f0f28ea
1 /*
2 Copyright 2009 by Hans Baier, Krzysztof Foltman
3 License: LGPLv2+
4 */
6 using Gtk;
8 namespace Prolooks {
10 public class CurvePoint {
11 public float x {get;set;}
12 public float y {get;set;}
13 public CurvePoint (float x, float y) {
14 this.x = x;
15 this.y = y;
19 public class Curve : Widget {
20 /// Called when a point has been edited, added or removed
21 public signal void curve_changed (ref List<CurvePoint> data);
23 /// Called to clip/snap/otherwise adjust candidate point coordinates
24 public signal void clipping (int pt, ref float x, ref float y, ref bool hide) ;
26 /// Array of points
27 List<CurvePoint> points;
29 /// Coordinate ranges (in logical coordinates for top left and bottom right)
30 float x0;
31 float y0;
32 float x1;
33 float y1;
35 /// Currently selected point (when dragging/adding), or -1 if none is selected
36 int cur_pt;
38 /// If currently selected point is a candidate for deletion (ie. outside of graph+margin range)
39 bool hide_current;
41 /// Cached hand (drag) cursor
42 Gdk.Cursor hand_cursor;
44 /// Cached pencil (add point) cursor
45 Gdk.Cursor pencil_cursor;
47 /// Cached arrow (do not add point) cursor
48 Gdk.Cursor arrow_cursor;
50 /// Maximum number of points
51 public uint point_limit { get; set; }
53 public Curve (uint point_limit = 10) {
54 this.point_limit = point_limit;
55 points = new List<CurvePoint> ();
56 points.append (new CurvePoint (0.0f, 1.0f));
57 points.append (new CurvePoint (1.0f, 1.0f));
58 x0 = 0.0f;
59 x1 = 1.0f;
60 y0 = 1.0f;
61 y1 = 0.0f;
62 cur_pt = -1;
63 hide_current = false;
64 pencil_cursor = new Gdk.Cursor (Gdk.CursorType.PENCIL);
65 hand_cursor = new Gdk.Cursor (Gdk.CursorType.FLEUR);
66 arrow_cursor = new Gdk.Cursor (Gdk.CursorType.ARROW);
69 /// Convert logical (mapping) to physical (screen) coordinates
70 void log2phys (ref float x, ref float y) {
71 x = (x - x0) / (x1 - x0) * (parent.allocation.width - 2) + 1;
72 y = (y - y0) / (y1 - y0) * (parent.allocation.height - 2) + 1;
75 /// Convert physical (screen) to logical (mapping) coordinates
76 void phys2log (ref float x, ref float y) {
77 x = x0 + (x - 1) * (x1 - x0) / (parent.allocation.width - 2);
78 y = y0 + (y - 1) * (y1 - y0) / (parent.allocation.height - 2);
82 /// Clip function
83 /// @param pt point being clipped
84 /// @param x horizontal logical coordinate
85 /// @param y vertical logical coordinate
86 /// @param hide true if point is outside "valid" range and about to be deleted
87 void clip (int pt, ref float x, ref float y, ref bool hide) {
88 hide = false;
89 clipping (pt, ref x, ref y, ref hide);
91 float ymin = float.min (y0, y1), ymax = float.max (y0, y1);
92 float yamp = ymax - ymin;
94 if (pt != 0 && pt != (int)(points.length () - 1))
96 if (y < ymin - yamp || y > ymax + yamp)
97 hide = true;
100 if (x < x0) x = x0;
101 if (y < ymin) y = ymin;
102 if (x > x1) x = x1;
103 if (y > ymax) y = ymax;
104 if (pt == 0) x = 0;
106 if (pt == (int)(points.length () - 1))
107 x = points.nth_data(pt).x;
109 if (pt > 0 && x < points.nth_data(pt - 1).x)
110 x = points.nth_data(pt - 1).x;
112 if (pt < (int)(points.length () - 1) && x > points.nth_data(pt + 1).x)
113 x = points.nth_data(pt + 1).x;
117 /* Widget is asked to draw itself */
118 public override bool expose_event (Gdk.EventExpose event) {
119 // Create a Cairo context
120 var c = Gdk.cairo_create (this.window);
122 // Set clipping area in order to avoid unnecessary drawing
123 c.rectangle (event.area.x, event.area.y,
124 event.area.width, event.area.height);
125 c.clip ();
127 Gdk.Color scHot = { 0, 65535, 0, 0 };
128 Gdk.Color scPoint = { 0, 65535, 65535, 65535 };
129 Gdk.Color scLine = { 0, 32767, 32767, 32767 };
131 if (points.length () > 0)
133 Gdk.cairo_set_source_color (c, scLine);
135 for (size_t i = 0; i < points.length (); i++)
137 CurvePoint pt = points.nth_data ((uint)i);
139 if (i == (size_t)cur_pt && hide_current) {
140 continue;
143 float x = pt.x, y = pt.y;
145 log2phys (ref x, ref y);
147 if (i == 0) {
148 c.move_to (x, y);
149 } else {
150 c.line_to (x, y);
153 c.stroke ();
156 for (uint i = 0; i < points.length (); i++)
158 if (i == (size_t)cur_pt && hide_current) {
159 continue;
162 CurvePoint pt = points.nth_data(i);
163 float x = pt.x, y = pt.y;
164 log2phys (ref x, ref y);
165 Gdk.cairo_set_source_color (c, (i == (size_t)cur_pt) ? scHot : scPoint);
166 c.rectangle (x - 2, y - 2, 5, 5);
167 c.fill ();
170 return true;
173 public override void realize () {
174 set_flags (WidgetFlags.REALIZED);
176 Gdk.WindowAttr attributes = Gdk.WindowAttr ();
177 attributes.event_mask = Gdk.EventMask.EXPOSURE_MASK | Gdk.EventMask.BUTTON1_MOTION_MASK |
178 Gdk.EventMask.KEY_PRESS_MASK | Gdk.EventMask.KEY_RELEASE_MASK |
179 Gdk.EventMask.BUTTON_PRESS_MASK | Gdk.EventMask.BUTTON_RELEASE_MASK |
180 Gdk.EventMask.POINTER_MOTION_MASK | Gdk.EventMask.POINTER_MOTION_HINT_MASK;
181 attributes.x = allocation.x;
182 attributes.y = allocation.y;
183 attributes.width = allocation.width;
184 attributes.height = allocation.height;
185 attributes.wclass = Gdk.WindowClass.INPUT_OUTPUT;
186 attributes.window_type = Gdk.WindowType.CHILD;
188 window = new Gdk.Window (get_parent_window (), attributes, Gdk.WindowAttributesType.X | Gdk.WindowAttributesType.Y);
190 window.set_user_data (this);
191 style = style.attach (window);
195 public override void size_request (out Gtk.Requisition requisition) {
196 requisition.width = 64;
197 requisition.height = 32;
200 public override void size_allocate (Gdk.Rectangle rect) {
201 allocation.x = rect.x;
202 allocation.y = rect.y;
203 allocation.width = rect.width;
204 allocation.height = rect.height;
206 if (is_realized ())
207 window.move_resize (allocation.x, allocation.y, allocation.width, allocation.height);
211 int find_nearest (int ex, int ey, ref int insert_pt) {
212 float dist = 5;
213 int found_pt = -1;
214 for (int i = 0; i < (int)points.length (); i++)
216 float x = points.nth_data(i).x;
217 float y = points.nth_data(i).y;
218 log2phys (ref x, ref y);
219 float thisdist = float.max (Math.fabsf (ex - x), Math.fabsf (ey - y));
221 if (thisdist < dist)
223 dist = thisdist;
224 found_pt = i;
227 if (ex > x)
228 insert_pt = i + 1;
230 return found_pt;
233 public override bool button_press_event (Gdk.EventButton event) {
234 int found_pt, insert_pt = -1;
235 found_pt = find_nearest ((int)event.x, (int)event.y, ref insert_pt);
237 if (found_pt == -1 && insert_pt != -1)
239 // if at point limit, do not start anything
240 if (points.length () >= point_limit) {
241 debug ("points.length () >= point_limit: %u >= %u", points.length (), point_limit);
242 return true;
245 float x = (float)event.x;
246 float y = (float)event.y;
247 bool hide = false;
248 phys2log (ref x, ref y);
249 points.insert (new CurvePoint (x, y), insert_pt);
250 clip (insert_pt, ref x, ref y, ref hide);
252 if (hide)
254 // give up
255 points.remove (points.nth_data (insert_pt));
256 return true;
259 points.nth_data(insert_pt).x = x;
260 points.nth_data(insert_pt).y = y;
261 found_pt = insert_pt;
264 grab_focus ();
265 cur_pt = found_pt;
266 queue_draw ();
268 curve_changed (ref points);
270 window.set_cursor (hand_cursor);
271 return true;
274 public override bool button_release_event (Gdk.EventButton event) {
275 if (cur_pt != -1 && hide_current)
276 points.remove (points.nth_data (cur_pt));
277 cur_pt = -1;
278 hide_current = false;
280 curve_changed (ref points);
282 queue_draw ();
283 window.set_cursor (points.length () >= point_limit ? arrow_cursor : pencil_cursor);
284 return false;
287 public override bool motion_notify_event (Gdk.EventMotion event) {
288 if (event.is_hint)
290 Gdk.Event.request_motions (event);
293 if (cur_pt != -1)
295 float x = (float)event.x;
296 float y = (float)event.y;
297 phys2log (ref x, ref y);
298 clip (cur_pt, ref x, ref y, ref hide_current);
300 points.nth_data(cur_pt).x = x;
301 points.nth_data(cur_pt).y = y;
303 curve_changed (ref points);
305 queue_draw ();
307 else
309 int insert_pt = -1;
310 if (find_nearest ((int)event.x, (int)event.y, ref insert_pt) == -1)
311 window.set_cursor (points.length () >= point_limit ? arrow_cursor : pencil_cursor);
312 else
313 window.set_cursor (hand_cursor);
315 return false;
319 void set_points (ref List<CurvePoint> src)
321 if (points.length () != src.length ())
322 cur_pt = -1;
323 points = #src;
325 queue_draw ();
329 } // namespace Prolooks