+ AutoHell: add missing headers to Makefile.am in header directory
[calf.git] / src / ctl_curve.cpp
blob99f36c17f8d74801672c1042ff965e03ef49c9f6
1 /* Calf DSP Library
2 * Barely started curve editor widget. Standard GtkCurve is
3 * unreliable and deprecated, so I need to make my own.
5 * Copyright (C) 2008 Krzysztof Foltman
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2 of the License, or (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General
18 * Public License along with this program; if not, write to the
19 * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
20 * Boston, MA 02111-1307, USA.
22 #include <calf/ctl_curve.h>
23 #include <malloc.h>
24 #include <stdint.h>
25 #include <math.h>
27 static gpointer parent_class = NULL;
29 GtkWidget *
30 calf_curve_new(unsigned int point_limit)
32 GtkWidget *widget = GTK_WIDGET( g_object_new (CALF_TYPE_CURVE, NULL ));
33 g_assert(CALF_IS_CURVE(widget));
35 CalfCurve *self = CALF_CURVE(widget);
36 self->point_limit = point_limit;
37 return widget;
40 static gboolean
41 calf_curve_expose (GtkWidget *widget, GdkEventExpose *event)
43 g_assert(CALF_IS_CURVE(widget));
45 CalfCurve *self = CALF_CURVE(widget);
46 GdkWindow *window = widget->window;
47 cairo_t *c = gdk_cairo_create(GDK_DRAWABLE(window));
48 GdkColor scHot = { 0, 65535, 0, 0 };
49 GdkColor scPoint = { 0, 65535, 65535, 65535 };
50 GdkColor scLine = { 0, 32767, 32767, 32767 };
51 if (self->points->size())
53 gdk_cairo_set_source_color(c, &scLine);
54 for (size_t i = 0; i < self->points->size(); i++)
56 const CalfCurve::point &pt = (*self->points)[i];
57 if (i == (size_t)self->cur_pt && self->hide_current)
58 continue;
59 float x = pt.first, y = pt.second;
60 self->log2phys(x, y);
61 if (!i)
62 cairo_move_to(c, x, y);
63 else
64 cairo_line_to(c, x, y);
66 cairo_stroke(c);
68 for (size_t i = 0; i < self->points->size(); i++)
70 if (i == (size_t)self->cur_pt && self->hide_current)
71 continue;
72 const CalfCurve::point &pt = (*self->points)[i];
73 float x = pt.first, y = pt.second;
74 self->log2phys(x, y);
75 gdk_cairo_set_source_color(c, (i == (size_t)self->cur_pt) ? &scHot : &scPoint);
76 cairo_rectangle(c, x - 2, y - 2, 5, 5);
77 cairo_fill(c);
79 cairo_destroy(c);
81 return TRUE;
84 static void
85 calf_curve_realize(GtkWidget *widget)
87 GTK_WIDGET_SET_FLAGS(widget, GTK_REALIZED);
89 GdkWindowAttr attributes;
90 attributes.event_mask = GDK_EXPOSURE_MASK | GDK_BUTTON1_MOTION_MASK |
91 GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK |
92 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
93 GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK;
94 attributes.x = widget->allocation.x;
95 attributes.y = widget->allocation.y;
96 attributes.width = widget->allocation.width;
97 attributes.height = widget->allocation.height;
98 attributes.wclass = GDK_INPUT_OUTPUT;
99 attributes.window_type = GDK_WINDOW_CHILD;
101 widget->window = gdk_window_new(gtk_widget_get_parent_window (widget), &attributes, GDK_WA_X | GDK_WA_Y);
103 gdk_window_set_user_data(widget->window, widget);
104 widget->style = gtk_style_attach(widget->style, widget->window);
107 static void
108 calf_curve_size_request (GtkWidget *widget,
109 GtkRequisition *requisition)
111 g_assert(CALF_IS_CURVE(widget));
113 requisition->width = 64;
114 requisition->height = 32;
117 static void
118 calf_curve_size_allocate (GtkWidget *widget,
119 GtkAllocation *allocation)
121 g_assert(CALF_IS_CURVE(widget));
123 widget->allocation = *allocation;
125 if (GTK_WIDGET_REALIZED(widget))
126 gdk_window_move_resize(widget->window, allocation->x, allocation->y, allocation->width, allocation->height );
129 static int
130 find_nearest(CalfCurve *self, int ex, int ey, int &insert_pt)
132 float dist = 5;
133 int found_pt = -1;
134 for (int i = 0; i < (int)self->points->size(); i++)
136 float x = (*self->points)[i].first, y = (*self->points)[i].second;
137 self->log2phys(x, y);
138 float thisdist = std::max(fabs(ex - x), fabs(ey - y));
139 if (thisdist < dist)
141 dist = thisdist;
142 found_pt = i;
144 if (ex > x)
145 insert_pt = i + 1;
147 return found_pt;
150 static gboolean
151 calf_curve_button_press (GtkWidget *widget, GdkEventButton *event)
153 g_assert(CALF_IS_CURVE(widget));
154 CalfCurve *self = CALF_CURVE(widget);
155 int found_pt, insert_pt = -1;
156 found_pt = find_nearest(self, event->x, event->y, insert_pt);
157 if (found_pt == -1 && insert_pt != -1)
159 // if at point limit, do not start anything
160 if (self->points->size() >= self->point_limit)
161 return TRUE;
162 float x = event->x, y = event->y;
163 bool hide = false;
164 self->phys2log(x, y);
165 self->points->insert(self->points->begin() + insert_pt, CalfCurve::point(x, y));
166 self->clip(insert_pt, x, y, hide);
167 if (hide)
169 // give up
170 self->points->erase(self->points->begin() + insert_pt);
171 return TRUE;
173 (*self->points)[insert_pt] = CalfCurve::point(x, y);
174 found_pt = insert_pt;
176 gtk_widget_grab_focus(widget);
177 self->cur_pt = found_pt;
178 gtk_widget_queue_draw(widget);
179 if (self->sink)
180 self->sink->curve_changed(self, *self->points);
181 gdk_window_set_cursor(widget->window, self->hand_cursor);
182 return TRUE;
185 static gboolean
186 calf_curve_button_release (GtkWidget *widget, GdkEventButton *event)
188 g_assert(CALF_IS_CURVE(widget));
189 CalfCurve *self = CALF_CURVE(widget);
190 if (self->cur_pt != -1 && self->hide_current)
191 self->points->erase(self->points->begin() + self->cur_pt);
192 self->cur_pt = -1;
193 self->hide_current = false;
194 if (self->sink)
195 self->sink->curve_changed(self, *self->points);
196 gtk_widget_queue_draw(widget);
197 gdk_window_set_cursor(widget->window, self->points->size() >= self->point_limit ? self->arrow_cursor : self->pencil_cursor);
198 return FALSE;
201 static gboolean
202 calf_curve_pointer_motion (GtkWidget *widget, GdkEventMotion *event)
204 g_assert(CALF_IS_CURVE(widget));
205 if (event->is_hint)
206 gdk_event_request_motions(event);
207 CalfCurve *self = CALF_CURVE(widget);
208 if (self->cur_pt != -1)
210 float x = event->x, y = event->y;
211 self->phys2log(x, y);
212 self->clip(self->cur_pt, x, y, self->hide_current);
213 (*self->points)[self->cur_pt] = CalfCurve::point(x, y);
214 if (self->sink)
215 self->sink->curve_changed(self, *self->points);
216 gtk_widget_queue_draw(widget);
218 else
220 int insert_pt = -1;
221 if (find_nearest(self, event->x, event->y, insert_pt) == -1)
222 gdk_window_set_cursor(widget->window, self->points->size() >= self->point_limit ? self->arrow_cursor : self->pencil_cursor);
223 else
224 gdk_window_set_cursor(widget->window, self->hand_cursor);
226 return FALSE;
229 static void
230 calf_curve_finalize (GObject *obj)
232 g_assert(CALF_IS_CURVE(obj));
233 CalfCurve *self = CALF_CURVE(obj);
235 delete self->points;
236 self->points = NULL;
238 G_OBJECT_CLASS(parent_class)->finalize(obj);
241 static void
242 calf_curve_class_init (CalfCurveClass *klass)
244 parent_class = g_type_class_peek_parent (klass);
246 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
247 widget_class->realize = calf_curve_realize;
248 widget_class->expose_event = calf_curve_expose;
249 widget_class->size_request = calf_curve_size_request;
250 widget_class->size_allocate = calf_curve_size_allocate;
251 widget_class->button_press_event = calf_curve_button_press;
252 widget_class->button_release_event = calf_curve_button_release;
253 widget_class->motion_notify_event = calf_curve_pointer_motion;
254 // widget_class->key_press_event = calf_curve_key_press;
255 // widget_class->scroll_event = calf_curve_scroll;
257 G_OBJECT_CLASS(klass)->finalize = calf_curve_finalize;
260 static void
261 calf_curve_init (CalfCurve *self)
263 GtkWidget *widget = GTK_WIDGET(self);
264 GTK_WIDGET_SET_FLAGS (widget, GTK_CAN_FOCUS);
265 self->points = new CalfCurve::point_vector;
266 // XXXKF: destructor
267 self->points->push_back(CalfCurve::point(0.f, 1.f));
268 self->points->push_back(CalfCurve::point(1.f, 1.f));
269 self->x0 = 0.f;
270 self->x1 = 1.f;
271 self->y0 = 1.f;
272 self->y1 = 0.f;
273 self->cur_pt = -1;
274 self->hide_current = false;
275 self->pencil_cursor = gdk_cursor_new(GDK_PENCIL);
276 self->hand_cursor = gdk_cursor_new(GDK_FLEUR);
277 self->arrow_cursor = gdk_cursor_new(GDK_ARROW);
280 void CalfCurve::log2phys(float &x, float &y) {
281 x = (x - x0) / (x1 - x0) * (parent.allocation.width - 2) + 1;
282 y = (y - y0) / (y1 - y0) * (parent.allocation.height - 2) + 1;
285 void CalfCurve::phys2log(float &x, float &y) {
286 x = x0 + (x - 1) * (x1 - x0) / (parent.allocation.width - 2);
287 y = y0 + (y - 1) * (y1 - y0) / (parent.allocation.height - 2);
290 void CalfCurve::clip(int pt, float &x, float &y, bool &hide)
292 hide = false;
293 sink->clip(this, pt, x, y, hide);
295 float ymin = std::min(y0, y1), ymax = std::max(y0, y1);
296 float yamp = ymax - ymin;
297 if (pt != 0 && pt != (int)(points->size() - 1))
299 if (y < ymin - yamp || y > ymax + yamp)
300 hide = true;
302 if (x < x0) x = x0;
303 if (y < ymin) y = ymin;
304 if (x > x1) x = x1;
305 if (y > ymax) y = ymax;
306 if (pt == 0) x = 0;
307 if (pt == (int)(points->size() - 1))
308 x = (*points)[pt].first;
309 if (pt > 0 && x < (*points)[pt - 1].first)
310 x = (*points)[pt - 1].first;
311 if (pt < (int)(points->size() - 1) && x > (*points)[pt + 1].first)
312 x = (*points)[pt + 1].first;
315 void calf_curve_set_points(GtkWidget *widget, const CalfCurve::point_vector &src)
317 g_assert(CALF_IS_CURVE(widget));
318 CalfCurve *self = CALF_CURVE(widget);
319 if (self->points->size() != src.size())
320 self->cur_pt = -1;
321 *self->points = src;
323 gtk_widget_queue_draw(widget);
326 GType
327 calf_curve_get_type (void)
329 static GType type = 0;
330 if (!type) {
332 static const GTypeInfo type_info = {
333 sizeof(CalfCurveClass),
334 NULL, /* base_init */
335 NULL, /* base_finalize */
336 (GClassInitFunc)calf_curve_class_init,
337 NULL, /* class_finalize */
338 NULL, /* class_data */
339 sizeof(CalfCurve),
340 0, /* n_preallocs */
341 (GInstanceInitFunc)calf_curve_init
344 for (int i = 0; ; i++) {
345 char *name = g_strdup_printf("CalfCurve%u%d",
346 ((unsigned int)(intptr_t)calf_curve_class_init) >> 16, i);
347 if (g_type_from_name(name)) {
348 free(name);
349 continue;
351 type = g_type_register_static(GTK_TYPE_WIDGET,
352 name,
353 &type_info,
354 (GTypeFlags)0);
355 free(name);
356 break;
359 return type;