+ Filter: implement drawing optimization
[calf.git] / src / ctl_curve.cpp
blobd721ecd6b7678bfdce9a2becb042171b7d445ae2
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)
207 #if GTK_CHECK_VERSION(2,12,0)
208 gdk_event_request_motions(event);
209 #else
210 int a, b;
211 GdkModifierType mod;
212 gdk_window_get_pointer (event->window, &a, &b, (GdkModifierType*)&mod);
213 #endif
215 CalfCurve *self = CALF_CURVE(widget);
216 if (self->cur_pt != -1)
218 float x = event->x, y = event->y;
219 self->phys2log(x, y);
220 self->clip(self->cur_pt, x, y, self->hide_current);
221 (*self->points)[self->cur_pt] = CalfCurve::point(x, y);
222 if (self->sink)
223 self->sink->curve_changed(self, *self->points);
224 gtk_widget_queue_draw(widget);
226 else
228 int insert_pt = -1;
229 if (find_nearest(self, event->x, event->y, insert_pt) == -1)
230 gdk_window_set_cursor(widget->window, self->points->size() >= self->point_limit ? self->arrow_cursor : self->pencil_cursor);
231 else
232 gdk_window_set_cursor(widget->window, self->hand_cursor);
234 return FALSE;
237 static void
238 calf_curve_finalize (GObject *obj)
240 g_assert(CALF_IS_CURVE(obj));
241 CalfCurve *self = CALF_CURVE(obj);
243 delete self->points;
244 self->points = NULL;
246 G_OBJECT_CLASS(parent_class)->finalize(obj);
249 static void
250 calf_curve_class_init (CalfCurveClass *klass)
252 parent_class = g_type_class_peek_parent (klass);
254 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
255 widget_class->realize = calf_curve_realize;
256 widget_class->expose_event = calf_curve_expose;
257 widget_class->size_request = calf_curve_size_request;
258 widget_class->size_allocate = calf_curve_size_allocate;
259 widget_class->button_press_event = calf_curve_button_press;
260 widget_class->button_release_event = calf_curve_button_release;
261 widget_class->motion_notify_event = calf_curve_pointer_motion;
262 // widget_class->key_press_event = calf_curve_key_press;
263 // widget_class->scroll_event = calf_curve_scroll;
265 G_OBJECT_CLASS(klass)->finalize = calf_curve_finalize;
268 static void
269 calf_curve_init (CalfCurve *self)
271 GtkWidget *widget = GTK_WIDGET(self);
272 GTK_WIDGET_SET_FLAGS (widget, GTK_CAN_FOCUS);
273 self->points = new CalfCurve::point_vector;
274 // XXXKF: destructor
275 self->points->push_back(CalfCurve::point(0.f, 1.f));
276 self->points->push_back(CalfCurve::point(1.f, 1.f));
277 self->x0 = 0.f;
278 self->x1 = 1.f;
279 self->y0 = 1.f;
280 self->y1 = 0.f;
281 self->cur_pt = -1;
282 self->hide_current = false;
283 self->pencil_cursor = gdk_cursor_new(GDK_PENCIL);
284 self->hand_cursor = gdk_cursor_new(GDK_FLEUR);
285 self->arrow_cursor = gdk_cursor_new(GDK_ARROW);
288 void CalfCurve::log2phys(float &x, float &y) {
289 x = (x - x0) / (x1 - x0) * (parent.allocation.width - 2) + 1;
290 y = (y - y0) / (y1 - y0) * (parent.allocation.height - 2) + 1;
293 void CalfCurve::phys2log(float &x, float &y) {
294 x = x0 + (x - 1) * (x1 - x0) / (parent.allocation.width - 2);
295 y = y0 + (y - 1) * (y1 - y0) / (parent.allocation.height - 2);
298 void CalfCurve::clip(int pt, float &x, float &y, bool &hide)
300 hide = false;
301 sink->clip(this, pt, x, y, hide);
303 float ymin = std::min(y0, y1), ymax = std::max(y0, y1);
304 float yamp = ymax - ymin;
305 if (pt != 0 && pt != (int)(points->size() - 1))
307 if (y < ymin - yamp || y > ymax + yamp)
308 hide = true;
310 if (x < x0) x = x0;
311 if (y < ymin) y = ymin;
312 if (x > x1) x = x1;
313 if (y > ymax) y = ymax;
314 if (pt == 0) x = 0;
315 if (pt == (int)(points->size() - 1))
316 x = (*points)[pt].first;
317 if (pt > 0 && x < (*points)[pt - 1].first)
318 x = (*points)[pt - 1].first;
319 if (pt < (int)(points->size() - 1) && x > (*points)[pt + 1].first)
320 x = (*points)[pt + 1].first;
323 void calf_curve_set_points(GtkWidget *widget, const CalfCurve::point_vector &src)
325 g_assert(CALF_IS_CURVE(widget));
326 CalfCurve *self = CALF_CURVE(widget);
327 if (self->points->size() != src.size())
328 self->cur_pt = -1;
329 *self->points = src;
331 gtk_widget_queue_draw(widget);
334 GType
335 calf_curve_get_type (void)
337 static GType type = 0;
338 if (!type) {
340 static const GTypeInfo type_info = {
341 sizeof(CalfCurveClass),
342 NULL, /* base_init */
343 NULL, /* base_finalize */
344 (GClassInitFunc)calf_curve_class_init,
345 NULL, /* class_finalize */
346 NULL, /* class_data */
347 sizeof(CalfCurve),
348 0, /* n_preallocs */
349 (GInstanceInitFunc)calf_curve_init
352 for (int i = 0; ; i++) {
353 char *name = g_strdup_printf("CalfCurve%u%d",
354 ((unsigned int)(intptr_t)calf_curve_class_init) >> 16, i);
355 if (g_type_from_name(name)) {
356 free(name);
357 continue;
359 type = g_type_register_static(GTK_TYPE_WIDGET,
360 name,
361 &type_info,
362 (GTypeFlags)0);
363 free(name);
364 break;
367 return type;