OSDN Git Service

Initial commit
[kp123/kp123.git] / src / padarea.c
1
2 #include "defs.h"
3 #include "padarea.h"
4
5 G_DEFINE_TYPE(KpPadArea, kp_padarea, GTK_TYPE_DRAWING_AREA);
6
7 #ifndef MIN
8 #define MIN(a,b) (((a) < (b)) ? (a) : (b))
9 #endif
10
11 #ifndef MAX
12 #define MAX(a,b) (((a) > (b)) ? (a) : (b))
13 #endif
14
15 static gint stroke_add_remove_signal_id = 0;
16
17 static void kp_padarea_free_stroke(GList *stroke)
18 {
19     GList *tmp_list = stroke;
20     while(tmp_list)
21     {
22         g_free(tmp_list->data);
23         tmp_list = tmp_list->next;
24     }
25     g_list_free(stroke);
26 }
27
28 static void kp_padarea_annotate_stroke(KpPadArea *self, GList *stroke, gint index)
29 {
30     GdkPoint *cur, *old;
31
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 */
36     if(stroke)
37     {
38         old = (GdkPoint*)stroke->data;
39
40         do
41         {
42             cur = (GdkPoint*)stroke->data;
43             stroke = stroke->next;
44         }
45         while(stroke && abs(cur->x - old->x) < 5 && abs (cur->y - old->y) < 5);
46
47         if(stroke)
48         {
49             char buffer[16];
50             PangoLayout *layout;
51             int swidth, sheight;
52             gint16 x, y;
53             double r;
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);
60
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);
64
65             r = sqrt(swidth*swidth + sheight*sheight);
66
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;
69
70             x -= swidth/2;
71             y -= sheight/2;
72
73             update_area.x = x;
74             update_area.y = y;
75             update_area.width = swidth;
76             update_area.height = sheight;
77
78             x = CLAMP(x, 0, w->allocation.width - swidth);
79             y = CLAMP(y, 0, w->allocation.height - sheight);
80
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);
84             cairo_stroke(cr);
85             cairo_destroy(cr);
86         }
87     }
88 }
89
90 static void kp_padarea_draw_line(KpPadArea *self, GdkPoint *p0, GdkPoint *p1)
91 {
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);
96     cairo_stroke(cr);
97     cairo_destroy(cr);
98 }
99
100 static void kp_padarea_draw_stroke(KpPadArea *self, GList *s)
101 {
102     for(;s != g_list_last(s); s = g_list_next(s))
103     {
104         GdkPoint *p0 = (GdkPoint *)s->data;
105         GdkPoint *p1 = (GdkPoint *)g_list_next(s)->data;
106         kp_padarea_draw_line(self, p0, p1);
107     }
108 }
109
110 static void kp_padarea_reset(KpPadArea *self)
111 {
112     GList *l = self->strokes;
113     int index = 1;
114     GtkWidget *widget = GTK_WIDGET(self);
115
116     guint16 width = widget->allocation.width;
117     guint16 height = widget->allocation.height;
118
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);
122     cairo_fill(cr);
123     cairo_destroy(cr);
124
125     for(l = self->strokes; l; l = g_list_next(l))
126     {
127         if(self->annotate)
128             kp_padarea_annotate_stroke(self, l->data, index++);
129
130         kp_padarea_draw_stroke(self, l->data);
131     }
132 }
133
134 static int kp_padarea_configure_cb(GtkWidget *w, GdkEventConfigure *event, gpointer data)
135 {
136     KpPadArea *self = KP_PADAREA(w);
137
138     kp_padarea_reset(self);
139
140     return TRUE;
141 }
142
143 static int kp_padarea_expose_cb(GtkWidget *w, GdkEventExpose *event, gpointer data)
144 {
145     KpPadArea *self = KP_PADAREA(w);
146     kp_padarea_reset(self);
147     return TRUE;
148 }
149
150 static int kp_padarea_button_press_cb(GtkWidget *w, GdkEventButton *event, gpointer data)
151 {
152     KpPadArea *self = KP_PADAREA(w);
153     if(event->button == 1)
154     {
155         GdkPoint *p = g_new(GdkPoint, 1);
156         p->x = event->x;
157         p->y = event->y;
158         self->curstroke = g_list_append(self->curstroke, p);
159         self->instroke = TRUE;
160         kp_padarea_draw_line(self, p, p);
161     }
162     return TRUE;
163 }
164
165 static int kp_padarea_button_release_cb(GtkWidget *w, GdkEventButton *event, gpointer data)
166 {
167     KpPadArea *self = KP_PADAREA(w);
168     if(self->annotate)
169         kp_padarea_annotate_stroke(self, self->curstroke, g_list_length (self->strokes) + 1);
170
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);
175     return TRUE;
176 }
177
178 static int kp_padarea_motion_cb(GtkWidget *w, GdkEventMotion *event, gpointer data)
179 {
180     gint x, y;
181     GdkModifierType state;
182     KpPadArea *self = KP_PADAREA(w);
183
184     if(event->is_hint)
185     {
186         gdk_window_get_pointer(w->window, &x, &y, &state);
187     }
188     else
189     {
190         x = event->x;
191         y = event->y;
192         state = event->state;
193     }
194
195     if(self->instroke && state & GDK_BUTTON1_MASK)
196     {
197         GdkPoint *p0 = (GdkPoint *)g_list_last(self->curstroke)->data;
198         GdkPoint *pt = g_new(GdkPoint, 1);
199         pt->x = x;
200         pt->y = y;
201
202         kp_padarea_draw_line(self, p0, pt);
203         self->curstroke = g_list_append(self->curstroke, pt);
204     }
205
206     return TRUE;
207 }
208
209 static void kp_padarea_class_init(KpPadAreaClass *klass)
210 {
211     stroke_add_remove_signal_id = g_signal_new("stroke_add_remove",
212                 G_TYPE_OBJECT, G_SIGNAL_RUN_LAST,
213                 0, NULL, NULL,
214                 g_cclosure_marshal_VOID__VOID,
215                 G_TYPE_NONE, 0);
216 }
217
218 static void kp_padarea_init(KpPadArea *self)
219 {
220     GtkWidget *w = GTK_WIDGET(self);
221
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);
227
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);
229
230     self->strokes = NULL;
231     self->curstroke = NULL;
232     self->instroke = FALSE;
233     self->annotate = FALSE;
234     self->line_width = 3;
235 }
236
237 void kp_padarea_clear(KpPadArea *self)
238 {
239     GList *tmp_list;
240
241     tmp_list = self->strokes;
242     while(tmp_list)
243     {
244         kp_padarea_free_stroke(tmp_list->data);
245         tmp_list = tmp_list->next;
246     }
247     g_list_free(self->strokes);
248     self->strokes = NULL;
249
250     g_list_free(self->curstroke);
251     self->curstroke = NULL;
252
253     kp_padarea_reset(self);
254     g_signal_emit(self, stroke_add_remove_signal_id, 0);
255 }
256
257 void kp_padarea_undo(KpPadArea *self)
258 {
259     if(!self->strokes)
260         return;
261
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);
265     g_list_free(tmp);
266     kp_padarea_reset(self);
267     g_signal_emit(self, stroke_add_remove_signal_id, 0);
268 }
269
270 void kp_padarea_annotate(KpPadArea *self, gboolean annotate)
271 {
272     if(self->annotate != annotate)
273     {
274         self->annotate = annotate;
275         kp_padarea_reset(self);
276     }
277 }
278