5 G_DEFINE_TYPE(KpPadArea, kp_padarea, GTK_TYPE_DRAWING_AREA);
8 #define MIN(a,b) (((a) < (b)) ? (a) : (b))
12 #define MAX(a,b) (((a) > (b)) ? (a) : (b))
15 static gint stroke_add_remove_signal_id = 0;
17 static void kp_padarea_free_stroke(GList *stroke)
19 GList *tmp_list = stroke;
22 g_free(tmp_list->data);
23 tmp_list = tmp_list->next;
28 static void kp_padarea_annotate_stroke(KpPadArea *self, GList *stroke, gint index)
32 /* Annotate the stroke with the stroke number - the algorithm
33 * for placing the digit is pretty simple. The text is inscribed
34 * in a circle tangent to the stroke. The circle will be above
35 * and/or to the left of the line */
38 old = (GdkPoint*)stroke->data;
42 cur = (GdkPoint*)stroke->data;
43 stroke = stroke->next;
45 while(stroke && abs(cur->x - old->x) < 5 && abs (cur->y - old->y) < 5);
54 double dx = cur->x - old->x;
55 double dy = cur->y - old->y;
56 double dl = sqrt(dx*dx+dy*dy);
57 int sign = (dy <= dx) ? 1 : -1;
58 GdkRectangle update_area;
59 GtkWidget *w = GTK_WIDGET(self);
61 sprintf(buffer, "%d", index);
62 layout = gtk_widget_create_pango_layout(GTK_WIDGET(self), buffer);
63 pango_layout_get_pixel_size(layout, &swidth, &sheight);
65 r = sqrt(swidth*swidth + sheight*sheight);
67 x = 0.5 + old->x + 0.5*r*dx/dl + sign * 0.5*r*dy/dl;
68 y = 0.5 + old->y + 0.5*r*dy/dl - sign * 0.5*r*dx/dl;
75 update_area.width = swidth;
76 update_area.height = sheight;
78 x = CLAMP(x, 0, w->allocation.width - swidth);
79 y = CLAMP(y, 0, w->allocation.height - sheight);
81 cairo_t *cr = gdk_cairo_create(GTK_WIDGET(self)->window);
82 cairo_move_to(cr, x, y);
83 pango_cairo_show_layout(cr, layout);
90 static void kp_padarea_draw_line(KpPadArea *self, GdkPoint *p0, GdkPoint *p1)
92 cairo_t *cr = gdk_cairo_create(GTK_WIDGET(self)->window);
93 cairo_set_source_rgb(cr, 0.0, 0.0, 0.0);
94 cairo_move_to(cr, p0->x, p0->y);
95 cairo_line_to(cr, p1->x, p1->y);
100 static void kp_padarea_draw_stroke(KpPadArea *self, GList *s)
102 for(;s != g_list_last(s); s = g_list_next(s))
104 GdkPoint *p0 = (GdkPoint *)s->data;
105 GdkPoint *p1 = (GdkPoint *)g_list_next(s)->data;
106 kp_padarea_draw_line(self, p0, p1);
110 static void kp_padarea_reset(KpPadArea *self)
112 GList *l = self->strokes;
114 GtkWidget *widget = GTK_WIDGET(self);
116 guint16 width = widget->allocation.width;
117 guint16 height = widget->allocation.height;
119 cairo_t *cr = gdk_cairo_create(GTK_WIDGET(self)->window);
120 cairo_set_source_rgb(cr, 1.0, 1.0, 1.0);
121 cairo_rectangle(cr, 0, 0, width, height);
125 for(l = self->strokes; l; l = g_list_next(l))
128 kp_padarea_annotate_stroke(self, l->data, index++);
130 kp_padarea_draw_stroke(self, l->data);
134 static int kp_padarea_configure_cb(GtkWidget *w, GdkEventConfigure *event, gpointer data)
136 KpPadArea *self = KP_PADAREA(w);
138 kp_padarea_reset(self);
143 static int kp_padarea_expose_cb(GtkWidget *w, GdkEventExpose *event, gpointer data)
145 KpPadArea *self = KP_PADAREA(w);
146 kp_padarea_reset(self);
150 static int kp_padarea_button_press_cb(GtkWidget *w, GdkEventButton *event, gpointer data)
152 KpPadArea *self = KP_PADAREA(w);
153 if(event->button == 1)
155 GdkPoint *p = g_new(GdkPoint, 1);
158 self->curstroke = g_list_append(self->curstroke, p);
159 self->instroke = TRUE;
160 kp_padarea_draw_line(self, p, p);
165 static int kp_padarea_button_release_cb(GtkWidget *w, GdkEventButton *event, gpointer data)
167 KpPadArea *self = KP_PADAREA(w);
169 kp_padarea_annotate_stroke(self, self->curstroke, g_list_length (self->strokes) + 1);
171 self->strokes = g_list_append(self->strokes, self->curstroke);
172 self->curstroke = NULL;
173 self->instroke = FALSE;
174 g_signal_emit(w, stroke_add_remove_signal_id, 0);
178 static int kp_padarea_motion_cb(GtkWidget *w, GdkEventMotion *event, gpointer data)
181 GdkModifierType state;
182 KpPadArea *self = KP_PADAREA(w);
186 gdk_window_get_pointer(w->window, &x, &y, &state);
192 state = event->state;
195 if(self->instroke && state & GDK_BUTTON1_MASK)
197 GdkPoint *p0 = (GdkPoint *)g_list_last(self->curstroke)->data;
198 GdkPoint *pt = g_new(GdkPoint, 1);
202 kp_padarea_draw_line(self, p0, pt);
203 self->curstroke = g_list_append(self->curstroke, pt);
209 static void kp_padarea_class_init(KpPadAreaClass *klass)
211 stroke_add_remove_signal_id = g_signal_new("stroke_add_remove",
212 G_TYPE_OBJECT, G_SIGNAL_RUN_LAST,
214 g_cclosure_marshal_VOID__VOID,
218 static void kp_padarea_init(KpPadArea *self)
220 GtkWidget *w = GTK_WIDGET(self);
222 g_signal_connect(self, "configure_event", G_CALLBACK(kp_padarea_configure_cb), NULL);
223 g_signal_connect(self, "expose_event", G_CALLBACK(kp_padarea_expose_cb), NULL);
224 g_signal_connect(self, "button_press_event", G_CALLBACK(kp_padarea_button_press_cb), NULL);
225 g_signal_connect(self, "button_release_event", G_CALLBACK(kp_padarea_button_release_cb), NULL);
226 g_signal_connect(self, "motion_notify_event", G_CALLBACK(kp_padarea_motion_cb), NULL);
228 gtk_widget_set_events(w, GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK);
230 self->strokes = NULL;
231 self->curstroke = NULL;
232 self->instroke = FALSE;
233 self->annotate = FALSE;
234 self->line_width = 3;
237 void kp_padarea_clear(KpPadArea *self)
241 tmp_list = self->strokes;
244 kp_padarea_free_stroke(tmp_list->data);
245 tmp_list = tmp_list->next;
247 g_list_free(self->strokes);
248 self->strokes = NULL;
250 g_list_free(self->curstroke);
251 self->curstroke = NULL;
253 kp_padarea_reset(self);
254 g_signal_emit(self, stroke_add_remove_signal_id, 0);
257 void kp_padarea_undo(KpPadArea *self)
262 GList *tmp = g_list_last(self->strokes);
263 self->strokes = g_list_remove_link(self->strokes, tmp);
264 kp_padarea_free_stroke(tmp->data);
266 kp_padarea_reset(self);
267 g_signal_emit(self, stroke_add_remove_signal_id, 0);
270 void kp_padarea_annotate(KpPadArea *self, gboolean annotate)
272 if(self->annotate != annotate)
274 self->annotate = annotate;
275 kp_padarea_reset(self);