1 /* MyPaint - pressure sensitive painting application
2 * Copyright (C) 2005-2007 Martin Renold
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 // gtk stock code - left gtk prefix to use the pygtk wrapper-generator easier
20 #include "gtkmydrawwidget.h"
21 #include "stroke_recorder.h"
23 static void gtk_my_draw_widget_class_init (GtkMyDrawWidgetClass *klass);
24 static void gtk_my_draw_widget_init (GtkMyDrawWidget *mdw);
25 static void gtk_my_draw_widget_finalize (GObject *object);
26 static void gtk_my_draw_widget_realize (GtkWidget *widget);
27 static gint gtk_my_draw_widget_button_updown (GtkWidget *widget, GdkEventButton *event);
28 static gint gtk_my_draw_widget_motion_notify (GtkWidget *widget, GdkEventMotion *event);
29 static gint gtk_my_draw_widget_proximity_inout (GtkWidget *widget, GdkEventProximity *event);
30 static gint gtk_my_draw_widget_expose (GtkWidget *widget, GdkEventExpose *event);
31 static void gtk_my_draw_widget_surface_modified (GtkMySurface *s, gint x, gint y, gint w, gint h, GtkMyDrawWidget *mdw);
34 static gpointer parent_class;
40 guint gtk_my_draw_widget_signals[LAST_SIGNAL] = { 0 };
43 gtk_my_draw_widget_get_type (void)
45 static GType type = 0;
49 static const GTypeInfo info =
51 sizeof (GtkMyDrawWidgetClass),
53 NULL, /* base_finalize */
54 (GClassInitFunc) gtk_my_draw_widget_class_init,
55 NULL, /* class_finalize */
56 NULL, /* class_data */
57 sizeof (GtkMyDrawWidget),
59 (GInstanceInitFunc) gtk_my_draw_widget_init,
63 g_type_register_static (GTK_TYPE_DRAWING_AREA, "GtkMyDrawWidget",
71 gtk_my_draw_widget_class_init (GtkMyDrawWidgetClass *class)
73 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
74 GObjectClass *gobject_class = G_OBJECT_CLASS (class);
76 parent_class = g_type_class_peek_parent (class);
78 gobject_class->finalize = gtk_my_draw_widget_finalize;
79 widget_class->realize = gtk_my_draw_widget_realize;
81 widget_class->expose_event = gtk_my_draw_widget_expose;
82 widget_class->motion_notify_event = gtk_my_draw_widget_motion_notify;
83 widget_class->button_press_event = gtk_my_draw_widget_button_updown;
84 widget_class->button_release_event = gtk_my_draw_widget_button_updown;
85 widget_class->proximity_in_event = gtk_my_draw_widget_proximity_inout;
86 widget_class->proximity_out_event = gtk_my_draw_widget_proximity_inout;
89 gtk_my_draw_widget_signals[DRAGGING_FINISHED] = g_signal_new
91 G_TYPE_FROM_CLASS (class),
93 G_STRUCT_OFFSET (GtkMyDrawWidgetClass, dragging_finished),
95 g_cclosure_marshal_VOID__VOID,
101 gtk_my_draw_widget_realize (GtkWidget *widget)
103 GtkMyDrawWidget *mdw;
104 GdkWindowAttr attributes;
105 gint attributes_mask;
107 g_return_if_fail (GTK_IS_MY_DRAW_WIDGET (widget));
109 mdw = GTK_MY_DRAW_WIDGET (widget);
110 GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
112 attributes.window_type = GDK_WINDOW_CHILD;
113 attributes.x = widget->allocation.x;
114 attributes.y = widget->allocation.y;
115 attributes.width = widget->allocation.width;
116 attributes.height = widget->allocation.height;
117 attributes.wclass = GDK_INPUT_OUTPUT;
118 attributes.visual = gtk_widget_get_visual (widget);
119 attributes.colormap = gtk_widget_get_colormap (widget);
121 attributes.event_mask = gtk_widget_get_events (widget);
122 attributes.event_mask |= (GDK_EXPOSURE_MASK |
123 GDK_LEAVE_NOTIFY_MASK |
124 GDK_BUTTON_PRESS_MASK |
125 GDK_BUTTON_RELEASE_MASK |
126 GDK_POINTER_MOTION_MASK |
127 GDK_PROXIMITY_IN_MASK |
128 GDK_PROXIMITY_OUT_MASK);
130 attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
132 widget->window = gdk_window_new (gtk_widget_get_parent_window (widget), &attributes, attributes_mask);
133 gdk_window_set_user_data (widget->window, mdw);
135 widget->style = gtk_style_attach (widget->style, widget->window);
136 gtk_style_set_background (widget->style, widget->window, GTK_STATE_NORMAL);
138 // needed for some unknown reason
139 gtk_widget_add_events (widget, attributes.event_mask);
140 // needed for known reason
141 gtk_widget_set_extension_events (widget, GDK_EXTENSION_EVENTS_ALL);
143 //gtk_drawing_area_send_configure (GTK_DRAWING_AREA (widget));
147 gtk_my_draw_widget_finalize (GObject *object)
149 GtkMyDrawWidget * mdw;
150 g_return_if_fail (object != NULL);
151 g_return_if_fail (GTK_IS_MY_DRAW_WIDGET (object));
152 mdw = GTK_MY_DRAW_WIDGET (object);
153 // can be called multiple times
155 g_signal_handlers_disconnect_by_func (mdw->surface, gtk_my_draw_widget_surface_modified, mdw);
156 g_object_unref (mdw->surface);
159 if (mdw->replaying) {
160 g_array_free (mdw->replaying, TRUE);
161 mdw->replaying = NULL;
163 if (mdw->recording) {
164 g_array_free (mdw->recording, TRUE);
165 mdw->recording = NULL;
167 G_OBJECT_CLASS (parent_class)->finalize (object);
171 gtk_my_draw_widget_new (void)
173 return g_object_new (GTK_TYPE_MY_DRAW_WIDGET, NULL);
176 void gtk_my_draw_widget_init (GtkMyDrawWidget *mdw)
179 // Dummy width, must be set later using discard_and_resize.
180 // I decided for an empty constructor because [complicated excuse
181 // removed] since http://live.gnome.org/PyGTK/WhatsNew28
182 gtk_my_draw_widget_discard_and_resize (mdw, 1, 1);
185 mdw->one_over_zoom = 1.0;
188 mdw->recording = NULL;
189 mdw->replaying = NULL;
193 void gtk_my_draw_widget_discard_and_resize (GtkMyDrawWidget *mdw, int width, int height)
196 g_signal_handlers_disconnect_by_func (mdw->surface, gtk_my_draw_widget_surface_modified, mdw);
197 g_object_unref (mdw->surface);
199 mdw->surface = gtk_my_surface_old_new (width, height);
200 g_signal_connect (mdw->surface, "surface_modified", G_CALLBACK (gtk_my_draw_widget_surface_modified), mdw);
205 gtk_my_draw_widget_process_motion_or_button (GtkWidget *widget, guint32 time, gdouble x, gdouble y, gdouble pressure)
208 GtkMyDrawWidget * mdw;
209 mdw = GTK_MY_DRAW_WIDGET (widget);
211 g_assert (pressure >= 0 && pressure <= 1);
214 if (!mdw->last_time) {
217 dtime = time - mdw->last_time;
219 mdw->last_time = time;
221 if (mdw->recording) {
226 e.pressure = pressure;
227 g_array_append_val (mdw->recording, e);
231 gtk_my_brush_stroke_to (mdw->brush, mdw->surface,
232 x*mdw->one_over_zoom + mdw->viewport_x, y*mdw->one_over_zoom + mdw->viewport_y,
233 pressure, (double)dtime / 1000.0 /* in seconds */);
238 gtk_my_draw_widget_surface_modified (GtkMySurface *s, gint x, gint y, gint w, gint h, GtkMyDrawWidget *mdw)
240 x -= (int)(mdw->viewport_x+0.5);
241 y -= (int)(mdw->viewport_y+0.5);
242 if (mdw->zoom != 1.0) {
243 x = (int)(x * mdw->zoom);
244 y = (int)(y * mdw->zoom);
245 w = (int)(w * mdw->zoom);
246 h = (int)(h * mdw->zoom);
247 // worst-case rounding problem
251 gtk_widget_queue_draw_area (GTK_WIDGET (mdw), x, y, w, h);
252 //printf ("queued %d %d %d %d\n", x, y, w, h);
256 gtk_my_draw_widget_button_updown (GtkWidget *widget, GdkEventButton *event)
258 GtkMyDrawWidget * mdw;
259 g_return_val_if_fail (widget != NULL, FALSE);
260 g_return_val_if_fail (GTK_IS_MY_DRAW_WIDGET (widget), FALSE);
261 mdw = GTK_MY_DRAW_WIDGET (widget);
264 if (!gdk_event_get_axis ((GdkEvent *)event, GDK_AXIS_PRESSURE, &pressure)) {
265 pressure = (event->state & GDK_BUTTON1_MASK) ? 0.5 : 0;
267 gtk_my_draw_widget_process_motion_or_button (widget, event->time, event->x, event->y, pressure);
272 gtk_my_draw_widget_motion_notify (GtkWidget *widget, GdkEventMotion *event)
274 GtkMyDrawWidget * mdw;
275 g_return_val_if_fail (widget != NULL, FALSE);
276 g_return_val_if_fail (GTK_IS_MY_DRAW_WIDGET (widget), FALSE);
277 mdw = GTK_MY_DRAW_WIDGET (widget);
279 if ((event->state & GDK_BUTTON2_MASK) && mdw->allow_dragging) {
280 if (!mdw->dragging) {
282 mdw->dragging_last_x = (int) event->x;
283 mdw->dragging_last_y = (int) event->y;
288 dx = x - mdw->dragging_last_x;
289 dy = y - mdw->dragging_last_y;
290 if (dx == 0 && dy == 0) return TRUE;
291 dx *= mdw->one_over_zoom;
292 dy *= mdw->one_over_zoom;
293 mdw->dragging_last_x = x;
294 mdw->dragging_last_y = y;
295 gtk_my_draw_widget_set_viewport (mdw, mdw->viewport_x - dx, mdw->viewport_y - dy);
296 g_signal_emit (mdw, gtk_my_draw_widget_signals[DRAGGING_FINISHED], 0);
304 if (!gdk_event_get_axis ((GdkEvent *)event, GDK_AXIS_PRESSURE, &pressure)) {
305 pressure = (event->state & GDK_BUTTON1_MASK) ? 0.5 : 0;
307 gtk_my_draw_widget_process_motion_or_button (widget, event->time, event->x, event->y, pressure);
312 gtk_my_draw_widget_proximity_inout (GtkWidget *widget, GdkEventProximity *event)
314 // handled in python now
319 gtk_my_draw_widget_expose (GtkWidget *widget, GdkEventExpose *event)
321 GtkMyDrawWidget * mdw;
325 g_return_val_if_fail (widget != NULL, FALSE);
326 g_return_val_if_fail (GTK_IS_MY_DRAW_WIDGET (widget), FALSE);
327 g_return_val_if_fail (event != NULL, FALSE);
329 mdw = GTK_MY_DRAW_WIDGET (widget);
331 rowstride = event->area.width * 3;
332 rowstride = (rowstride + 3) & -4; /* align to 4-byte boundary */
333 rgb = g_new (guchar, event->area.height * rowstride);
335 //printf("Zoom = %f\n", mdw->zoom);
336 if (mdw->zoom == 0.0) mdw->zoom = 1.0; // whyever.
337 if (mdw->zoom == 1.0) {
338 gtk_my_surface_old_render
341 event->area.x + (int)(mdw->viewport_x+0.5), event->area.y + (int)(mdw->viewport_y+0.5),
342 event->area.width, event->area.height,
345 gtk_my_surface_old_render_zoom
348 event->area.x + mdw->viewport_x*mdw->zoom, event->area.y + mdw->viewport_y*mdw->zoom,
349 event->area.width, event->area.height,
354 gdk_draw_rgb_image (widget->window,
355 widget->style->black_gc,
356 event->area.x, event->area.y,
357 event->area.width, event->area.height,
367 gtk_my_draw_widget_clear (GtkMyDrawWidget *mdw)
369 gtk_my_surface_clear (GTK_MY_SURFACE (mdw->surface));
370 gtk_widget_queue_draw (GTK_WIDGET (mdw));
375 gtk_my_draw_widget_set_brush (GtkMyDrawWidget *mdw, GtkMyBrush * brush)
377 GtkMyBrush* brush_old = mdw->brush;
378 if (brush) g_object_ref (brush);
381 // The caller owns the reference now (caller-owns-return in
382 // fix_generated_defs.py) thus we don't g_object_unref here.
383 // Cannot return a borrowed reference instead because we just
384 // discarded the pointer.
388 void gtk_my_draw_widget_allow_dragging (GtkMyDrawWidget *mdw, int allow)
390 mdw->allow_dragging = allow;
392 void gtk_my_draw_widget_set_viewport (GtkMyDrawWidget *mdw, float x, float y)
396 gtk_widget_queue_draw (GTK_WIDGET (mdw));
398 float gtk_my_draw_widget_get_viewport_x (GtkMyDrawWidget *mdw)
400 return mdw->viewport_x;
402 float gtk_my_draw_widget_get_viewport_y (GtkMyDrawWidget *mdw)
404 return mdw->viewport_y;
407 void gtk_my_draw_widget_set_zoom (GtkMyDrawWidget *mdw, float zoom)
409 if (mdw->zoom == zoom) return;
410 if (zoom > 0.99 && zoom < 1.01) zoom = 1.0;
412 mdw->one_over_zoom = 1.0/zoom;
413 gtk_widget_queue_draw (GTK_WIDGET (mdw));
416 float gtk_my_draw_widget_get_zoom (GtkMyDrawWidget *mdw)
421 GdkPixbuf* gtk_my_draw_widget_get_as_pixbuf (GtkMyDrawWidget *mdw)
424 pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, /*has_alpha*/0, /*bits_per_sample*/8,
425 mdw->surface->w, mdw->surface->h);
427 gtk_my_surface_old_render (mdw->surface,
428 gdk_pixbuf_get_pixels (pixbuf),
429 gdk_pixbuf_get_rowstride (pixbuf),
430 0, 0, mdw->surface->w, mdw->surface->h,
436 GdkPixbuf* gtk_my_draw_widget_get_nonwhite_as_pixbuf (GtkMyDrawWidget *mdw)
440 gtk_my_surface_old_get_nonwhite_region (mdw->surface, &r);
442 pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, /*has_alpha*/0, /*bits_per_sample*/8,
445 gtk_my_surface_old_render (mdw->surface,
446 gdk_pixbuf_get_pixels (pixbuf),
447 gdk_pixbuf_get_rowstride (pixbuf),
454 void gtk_my_draw_widget_set_from_pixbuf (GtkMyDrawWidget *mdw, GdkPixbuf* pixbuf)
456 int w, h, n_channels;
458 n_channels = gdk_pixbuf_get_n_channels (pixbuf);
460 g_assert (gdk_pixbuf_get_colorspace (pixbuf) == GDK_COLORSPACE_RGB);
461 g_assert (gdk_pixbuf_get_bits_per_sample (pixbuf) == 8);
462 //ignore - g_assert (gdk_pixbuf_get_has_alpha (pixbuf));
463 g_assert (n_channels == 4 || n_channels == 3);
465 w = gdk_pixbuf_get_width (pixbuf);
466 h = gdk_pixbuf_get_height (pixbuf);
468 gtk_my_surface_old_load (mdw->surface,
469 gdk_pixbuf_get_pixels (pixbuf),
470 gdk_pixbuf_get_rowstride (pixbuf),
472 /*bpp*/n_channels*8);
473 gtk_widget_queue_draw (GTK_WIDGET (mdw));
476 void gtk_my_draw_widget_start_recording (GtkMyDrawWidget *mdw)
478 g_assert (!mdw->recording);
479 mdw->recording = g_array_new (FALSE, FALSE, sizeof(StrokeEvent));
482 GString* gtk_my_draw_widget_stop_recording (GtkMyDrawWidget *mdw)
484 // see also mydrawwidget.override
486 s = event_array_to_string (mdw->recording);
487 g_array_free (mdw->recording, TRUE); mdw->recording = NULL;
491 void gtk_my_draw_widget_stop_replaying (GtkMyDrawWidget *mdw)
494 g_assert (!mdw->replaying);
498 void gtk_my_draw_widget_replay (GtkMyDrawWidget *mdw, GString* data, int immediately)
500 // see also mydrawwidget.override
501 if (mdw->replaying) {
502 g_print ("Attempting to start replay while replaying.\n");
505 mdw->replaying = string_to_event_array (data);
509 for (i=0; i<mdw->replaying->len; i++) {
511 e = &g_array_index (mdw->replaying, StrokeEvent, i);
512 //g_print ("Replay: dtime=%d, x=%f.\n", e->dtime, e->x);
513 gtk_my_brush_stroke_to (mdw->brush, mdw->surface,
514 e->x*mdw->one_over_zoom + mdw->viewport_x, e->y*mdw->one_over_zoom + mdw->viewport_y,
515 e->pressure, (double)(e->dtime) / 1000.0 /* in seconds */);
517 g_array_free (mdw->replaying, TRUE); mdw->replaying = NULL;
527 clock_timer = g_timer_new();
528 g_idle_add(continue_replay, NULL);
529 g_timer_start(clock_timer);
533 update_clock(gpointer dummy)
536 sprintf(buf, "%d", (int)g_timer_elapsed(clock_timer, NULL));
537 gtk_label_set_text(GTK_LABEL(clock_label), buf);