2 Copyright 2009 by Hans Baier, Krzysztof Foltman
10 public class CurvePoint
{
11 public float x
{get;set;}
12 public float y
{get;set;}
13 public CurvePoint (float x
, float 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
) ;
27 List
<CurvePoint
> points
;
29 /// Coordinate ranges (in logical coordinates for top left and bottom right)
35 /// Currently selected point (when dragging/adding), or -1 if none is selected
38 /// If currently selected point is a candidate for deletion (ie. outside of graph+margin range)
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
));
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);
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
) {
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
)
101 if (y
< ymin
) y
= ymin
;
103 if (y
> ymax
) y
= ymax
;
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
);
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
) {
143 float x
= pt
.x
, y
= pt
.y
;
145 log2phys (ref x
, ref y
);
156 for (uint i
= 0; i
< points
.length (); i
++)
158 if (i
== (size_t
)cur_pt
&& hide_current
) {
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);
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
;
207 window
.move_resize (allocation
.x
, allocation
.y
, allocation
.width
, allocation
.height
);
211 int find_nearest (int ex
, int ey
, ref int insert_pt
) {
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
));
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
);
245 float x
= (float)event
.x
;
246 float y
= (float)event
.y
;
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
);
255 points
.remove (points
.nth_data (insert_pt
));
259 points
.nth_data(insert_pt
).x
= x
;
260 points
.nth_data(insert_pt
).y
= y
;
261 found_pt
= insert_pt
;
268 curve_changed (ref points
);
270 window
.set_cursor (hand_cursor
);
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
));
278 hide_current
= false;
280 curve_changed (ref points
);
283 window
.set_cursor (points
.length () >= point_limit ? arrow_cursor
: pencil_cursor
);
287 public override bool motion_notify_event (Gdk
.EventMotion event
) {
290 Gdk
.Event
.request_motions (event
);
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
);
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
);
313 window
.set_cursor (hand_cursor
);
319 void set_points (ref List
<CurvePoint
> src
)
321 if (points
.length () != src
.length ())
329 } // namespace Prolooks