]> git.donarmstrong.com Git - xournal.git/blob - src/xo-paint.c
c52a97dbfffbca57e13d88dc9a0d2bba19c3d3e0
[xournal.git] / src / xo-paint.c
1 /*
2  *  This program is free software; you can redistribute it and/or
3  *  modify it under the terms of the GNU General Public
4  *  License as published by the Free Software Foundation; either
5  *  version 2 of the License, or (at your option) any later version.
6  *
7  *  This software is distributed in the hope that it will be useful,
8  *  but WITHOUT ANY WARRANTY; without even the implied warranty of  
9  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
10  *  General Public License for more details.
11  *
12  *  You should have received a copy of the GNU General Public License
13  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
14  */
15
16 #ifdef HAVE_CONFIG_H
17 #  include <config.h>
18 #endif
19
20 #include <math.h>
21 #include <string.h>
22 #include <gtk/gtk.h>
23 #include <libgnomecanvas/libgnomecanvas.h>
24
25 #include "xournal.h"
26 #include "xo-callbacks.h"
27 #include "xo-interface.h"
28 #include "xo-support.h"
29 #include "xo-misc.h"
30 #include "xo-paint.h"
31
32 /***** Win32 fix for gdk_cursor_new_from_pixmap() by Dirk Gerrits ****/
33
34 #ifdef WIN32
35 gboolean colors_too_similar(const GdkColor *colora, const GdkColor *colorb)
36 {
37   return (abs(colora->red - colorb->red) < 256 &&
38           abs(colora->green - colorb->green) < 256 &&
39           abs(colora->blue - colorb->blue) < 256);
40 }
41
42 /* gdk_cursor_new_from_pixmap is broken on Windows.
43    this is a workaround using gdk_cursor_new_from_pixbuf. */
44 GdkCursor* fixed_gdk_cursor_new_from_pixmap(GdkPixmap *source, GdkPixmap *mask,
45                                             const GdkColor *fg, const GdkColor *bg,
46                                             gint x, gint y)
47 {
48   GdkPixmap *rgb_pixmap;
49   GdkGC *gc;
50   GdkPixbuf *rgb_pixbuf, *rgba_pixbuf;
51   GdkCursor *cursor;
52   int width, height;
53
54   /* HACK!  It seems impossible to work with RGBA pixmaps directly in
55      GDK-Win32.  Instead we pick some third color, different from fg
56      and bg, and use that as the 'transparent color'.  We do this using
57      colors_too_similar (see above) because two colors could be
58      unequal in GdkColor's 16-bit/sample, but equal in GdkPixbuf's
59      8-bit/sample. */
60   GdkColor candidates[3] = {{0,65535,0,0}, {0,0,65535,0}, {0,0,0,65535}};
61   GdkColor *trans = &candidates[0];
62   if (colors_too_similar(trans, fg) || colors_too_similar(trans, bg)) {
63     trans = &candidates[1];
64     if (colors_too_similar(trans, fg) || colors_too_similar(trans, bg)) {
65       trans = &candidates[2];
66     }
67   } /* trans is now guaranteed to be unique from fg and bg */
68
69   /* create an empty pixmap to hold the cursor image */
70   gdk_drawable_get_size(source, &width, &height);
71   rgb_pixmap = gdk_pixmap_new(NULL, width, height, 24);
72
73   /* blit the bitmaps defining the cursor onto a transparent background */
74   gc = gdk_gc_new(rgb_pixmap);
75   gdk_gc_set_fill(gc, GDK_SOLID);
76   gdk_gc_set_rgb_fg_color(gc, trans);
77   gdk_draw_rectangle(rgb_pixmap, gc, TRUE, 0, 0, width, height);
78   gdk_gc_set_fill(gc, GDK_OPAQUE_STIPPLED);
79   gdk_gc_set_stipple(gc, source);
80   gdk_gc_set_clip_mask(gc, mask);
81   gdk_gc_set_rgb_fg_color(gc, fg);
82   gdk_gc_set_rgb_bg_color(gc, bg);
83   gdk_draw_rectangle(rgb_pixmap, gc, TRUE, 0, 0, width, height);
84   gdk_gc_unref(gc);
85
86   /* create a cursor out of the created pixmap */
87   rgb_pixbuf = gdk_pixbuf_get_from_drawable(
88     NULL, rgb_pixmap, gdk_colormap_get_system(), 0, 0, 0, 0, width, height);
89   gdk_pixmap_unref(rgb_pixmap);
90   rgba_pixbuf = gdk_pixbuf_add_alpha(
91     rgb_pixbuf, TRUE, trans->red, trans->green, trans->blue);
92   gdk_pixbuf_unref(rgb_pixbuf);
93   cursor = gdk_cursor_new_from_pixbuf(gdk_display_get_default(), rgba_pixbuf, x, y);
94   gdk_pixbuf_unref(rgba_pixbuf);
95
96   return cursor;
97 }
98 #define gdk_cursor_new_from_pixmap fixed_gdk_cursor_new_from_pixmap
99 #endif
100
101
102 /************** drawing nice cursors *********/
103
104 static char cursor_pen_bits[] = {
105    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
106    0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
107    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
108
109 static char cursor_eraser_bits[] = {
110    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x0f, 0x08, 0x08, 0x08, 0x08,
111    0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0xf8, 0x0f,
112    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
113
114 static char cursor_eraser_mask[] = {
115    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x0f, 0xf8, 0x0f, 0xf8, 0x0f,
116    0xf8, 0x0f, 0xf8, 0x0f, 0xf8, 0x0f, 0xf8, 0x0f, 0xf8, 0x0f, 0xf8, 0x0f,
117    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
118
119 void set_cursor_busy(gboolean busy)
120 {
121   GdkCursor *cursor;
122   
123   if (busy) {
124     cursor = gdk_cursor_new(GDK_WATCH);
125     gdk_window_set_cursor(GTK_WIDGET(winMain)->window, cursor);
126     gdk_window_set_cursor(GTK_WIDGET(canvas)->window, cursor);
127     gdk_cursor_unref(cursor);
128   }
129   else {
130     gdk_window_set_cursor(GTK_WIDGET(winMain)->window, NULL);
131     update_cursor();
132   }
133   gdk_display_sync(gdk_display_get_default());
134 }
135
136 void update_cursor(void)
137 {
138   GdkPixmap *source, *mask;
139   GdkColor fg = {0, 0, 0, 0}, bg = {0, 65535, 65535, 65535};
140
141   ui.is_sel_cursor = FALSE;
142   if (GTK_WIDGET(canvas)->window == NULL) return;
143   
144   if (ui.cursor!=NULL) { 
145     gdk_cursor_unref(ui.cursor);
146     ui.cursor = NULL;
147   }
148   if (ui.cur_item_type == ITEM_MOVESEL_VERT)
149     ui.cursor = gdk_cursor_new(GDK_SB_V_DOUBLE_ARROW);
150   else if (ui.cur_item_type == ITEM_MOVESEL)
151     ui.cursor = gdk_cursor_new(GDK_FLEUR);
152   else if (ui.toolno[ui.cur_mapping] == TOOL_PEN) {
153     fg.red = (ui.cur_brush->color_rgba >> 16) & 0xff00;
154     fg.green = (ui.cur_brush->color_rgba >> 8) & 0xff00;
155     fg.blue = (ui.cur_brush->color_rgba >> 0) & 0xff00;
156     source = gdk_bitmap_create_from_data(NULL, cursor_pen_bits, 16, 16);
157     ui.cursor = gdk_cursor_new_from_pixmap(source, source, &fg, &bg, 7, 7);
158     gdk_bitmap_unref(source);
159   }
160   else if (ui.toolno[ui.cur_mapping] == TOOL_ERASER) {
161     source = gdk_bitmap_create_from_data(NULL, cursor_eraser_bits, 16, 16);
162     mask = gdk_bitmap_create_from_data(NULL, cursor_eraser_mask, 16, 16);
163     ui.cursor = gdk_cursor_new_from_pixmap(source, mask, &fg, &bg, 7, 7);
164     gdk_bitmap_unref(source);
165     gdk_bitmap_unref(mask);
166   }
167   else if (ui.toolno[ui.cur_mapping] == TOOL_HIGHLIGHTER) {
168     source = gdk_bitmap_create_from_data(NULL, cursor_eraser_bits, 16, 16);
169     mask = gdk_bitmap_create_from_data(NULL, cursor_eraser_mask, 16, 16);
170     bg.red = (ui.cur_brush->color_rgba >> 16) & 0xff00;
171     bg.green = (ui.cur_brush->color_rgba >> 8) & 0xff00;
172     bg.blue = (ui.cur_brush->color_rgba >> 0) & 0xff00;
173     ui.cursor = gdk_cursor_new_from_pixmap(source, mask, &fg, &bg, 7, 7);
174     gdk_bitmap_unref(source);
175     gdk_bitmap_unref(mask);
176   }
177   else if (ui.cur_item_type == ITEM_SELECTRECT) {
178     ui.cursor = gdk_cursor_new(GDK_TCROSS);
179   }
180   else if (ui.toolno[ui.cur_mapping] == TOOL_HAND) {
181     ui.cursor = gdk_cursor_new(GDK_HAND1);
182   }
183   else if (ui.toolno[ui.cur_mapping] == TOOL_TEXT) {
184     ui.cursor = gdk_cursor_new(GDK_XTERM);
185   }
186   
187   gdk_window_set_cursor(GTK_WIDGET(canvas)->window, ui.cursor);
188 }
189
190 /* adjust the cursor shape if it hovers near a selection box */
191
192 void update_cursor_for_resize(double *pt)
193 {
194   gboolean in_range_x, in_range_y;
195   gboolean can_resize_left, can_resize_right, can_resize_bottom, can_resize_top;
196   gdouble resize_margin;
197   GdkCursorType newcursor;
198
199   // if we're not even close to the box in some direction, return immediately
200   resize_margin = RESIZE_MARGIN / ui.zoom;
201   if (pt[0]<ui.selection->bbox.left-resize_margin || pt[0]>ui.selection->bbox.right+resize_margin
202    || pt[1]<ui.selection->bbox.top-resize_margin || pt[1]>ui.selection->bbox.bottom+resize_margin)
203   {
204     if (ui.is_sel_cursor) update_cursor();
205     return;
206   }
207
208   ui.is_sel_cursor = TRUE;
209   can_resize_left = (pt[0] < ui.selection->bbox.left+resize_margin);
210   can_resize_right = (pt[0] > ui.selection->bbox.right-resize_margin);
211   can_resize_top = (pt[1] < ui.selection->bbox.top+resize_margin);
212   can_resize_bottom = (pt[1] > ui.selection->bbox.bottom-resize_margin);
213
214   if (can_resize_left) {
215     if (can_resize_top) newcursor = GDK_TOP_LEFT_CORNER;
216     else if (can_resize_bottom) newcursor = GDK_BOTTOM_LEFT_CORNER;
217     else newcursor = GDK_LEFT_SIDE;
218   }
219   else if (can_resize_right) {
220     if (can_resize_top) newcursor = GDK_TOP_RIGHT_CORNER;
221     else if (can_resize_bottom) newcursor = GDK_BOTTOM_RIGHT_CORNER;
222     else newcursor = GDK_RIGHT_SIDE;
223   }
224   else if (can_resize_top) newcursor = GDK_TOP_SIDE;
225   else if (can_resize_bottom) newcursor = GDK_BOTTOM_SIDE;
226   else newcursor = GDK_FLEUR;
227
228   if (ui.cursor!=NULL && ui.cursor->type == newcursor) return;
229   if (ui.cursor!=NULL) gdk_cursor_unref(ui.cursor);
230   ui.cursor = gdk_cursor_new(newcursor);
231   gdk_window_set_cursor(GTK_WIDGET(canvas)->window, ui.cursor);
232 }
233
234 /************** painting strokes *************/
235
236 #define SUBDIVIDE_MAXDIST 5.0
237
238 void subdivide_cur_path(null)
239 {
240   int n, pieces, k;
241   double *p;
242   double x0, y0, x1, y1;
243
244   for (n=0, p=ui.cur_path.coords; n<ui.cur_path.num_points-1; n++, p+=2) {
245     pieces = (int)floor(hypot(p[2]-p[0], p[3]-p[1])/SUBDIVIDE_MAXDIST);
246     if (pieces>1) {
247       x0 = p[0]; y0 = p[1];
248       x1 = p[2]; y1 = p[3];
249       realloc_cur_path(ui.cur_path.num_points+pieces-1);
250       g_memmove(ui.cur_path.coords+2*(n+pieces), ui.cur_path.coords+2*(n+1),
251                     2*(ui.cur_path.num_points-n-1)*sizeof(double));
252       p = ui.cur_path.coords+2*n;
253       ui.cur_path.num_points += pieces-1;
254       n += (pieces-1);
255       for (k=1; k<pieces; k++) {
256         p+=2;
257         p[0] = x0 + k*(x1-x0)/pieces;
258         p[1] = y0 + k*(y1-y0)/pieces;
259       } 
260     }
261   }
262 }
263
264 void create_new_stroke(GdkEvent *event)
265 {
266   ui.cur_item_type = ITEM_STROKE;
267   ui.cur_item = g_new(struct Item, 1);
268   ui.cur_item->type = ITEM_STROKE;
269   g_memmove(&(ui.cur_item->brush), ui.cur_brush, sizeof(struct Brush));
270   ui.cur_item->path = &ui.cur_path;
271   realloc_cur_path(2);
272   ui.cur_path.num_points = 1;
273   get_pointer_coords(event, ui.cur_path.coords);
274   
275   if (ui.cur_brush->ruler) {
276     ui.cur_item->canvas_item = gnome_canvas_item_new(ui.cur_layer->group,
277       gnome_canvas_line_get_type(),
278       "cap-style", GDK_CAP_ROUND, "join-style", GDK_JOIN_ROUND,
279       "fill-color-rgba", ui.cur_item->brush.color_rgba,
280       "width-units", ui.cur_item->brush.thickness, NULL);
281     ui.cur_item->brush.variable_width = FALSE;
282   } else
283     ui.cur_item->canvas_item = gnome_canvas_item_new(
284       ui.cur_layer->group, gnome_canvas_group_get_type(), NULL);
285 }
286
287 void continue_stroke(GdkEvent *event)
288 {
289   GnomeCanvasPoints seg;
290   double *pt, current_width;
291
292   if (ui.cur_brush->ruler) {
293     pt = ui.cur_path.coords;
294   } else {
295     realloc_cur_path(ui.cur_path.num_points+1);
296     pt = ui.cur_path.coords + 2*(ui.cur_path.num_points-1);
297   } 
298   
299   get_pointer_coords(event, pt+2);
300   
301   if (ui.cur_item->brush.variable_width) {
302     realloc_cur_widths(ui.cur_path.num_points);
303     current_width = ui.cur_item->brush.thickness*get_pressure_multiplier(event);
304     ui.cur_widths[ui.cur_path.num_points-1] = current_width;
305   }
306   else current_width = ui.cur_item->brush.thickness;
307   
308   if (ui.cur_brush->ruler)
309     ui.cur_path.num_points = 2;
310   else {
311     if (hypot(pt[0]-pt[2], pt[1]-pt[3]) < PIXEL_MOTION_THRESHOLD/ui.zoom)
312       return;  // not a meaningful motion
313     ui.cur_path.num_points++;
314   }
315
316   seg.coords = pt; 
317   seg.num_points = 2;
318   seg.ref_count = 1;
319   
320   /* note: we're using a piece of the cur_path array. This is ok because
321      upon creation the line just copies the contents of the GnomeCanvasPoints
322      into an internal structure */
323
324   if (ui.cur_brush->ruler)
325     gnome_canvas_item_set(ui.cur_item->canvas_item, "points", &seg, NULL);
326   else
327     gnome_canvas_item_new((GnomeCanvasGroup *)ui.cur_item->canvas_item,
328        gnome_canvas_line_get_type(), "points", &seg,
329        "cap-style", GDK_CAP_ROUND, "join-style", GDK_JOIN_ROUND,
330        "fill-color-rgba", ui.cur_item->brush.color_rgba,
331        "width-units", current_width, NULL);
332 }
333
334 void finalize_stroke(void)
335 {
336   if (ui.cur_path.num_points == 1) { // GnomeCanvas doesn't like num_points=1
337     ui.cur_path.coords[2] = ui.cur_path.coords[0]+0.1;
338     ui.cur_path.coords[3] = ui.cur_path.coords[1];
339     ui.cur_path.num_points = 2;
340     ui.cur_item->brush.variable_width = FALSE;
341   }
342   
343   if (!ui.cur_item->brush.variable_width)
344     subdivide_cur_path(); // split the segment so eraser will work
345
346   ui.cur_item->path = gnome_canvas_points_new(ui.cur_path.num_points);
347   g_memmove(ui.cur_item->path->coords, ui.cur_path.coords, 
348       2*ui.cur_path.num_points*sizeof(double));
349   if (ui.cur_item->brush.variable_width)
350     ui.cur_item->widths = (gdouble *)g_memdup(ui.cur_widths, 
351                             (ui.cur_path.num_points-1)*sizeof(gdouble));
352   else ui.cur_item->widths = NULL;
353   update_item_bbox(ui.cur_item);
354   ui.cur_path.num_points = 0;
355
356   if (!ui.cur_item->brush.variable_width) {
357     // destroy the entire group of temporary line segments
358     gtk_object_destroy(GTK_OBJECT(ui.cur_item->canvas_item));
359     // make a new line item to replace it
360     make_canvas_item_one(ui.cur_layer->group, ui.cur_item);
361   }
362
363   // add undo information
364   prepare_new_undo();
365   undo->type = ITEM_STROKE;
366   undo->item = ui.cur_item;
367   undo->layer = ui.cur_layer;
368
369   // store the item on top of the layer stack
370   ui.cur_layer->items = g_list_append(ui.cur_layer->items, ui.cur_item);
371   ui.cur_layer->nitems++;
372   ui.cur_item = NULL;
373   ui.cur_item_type = ITEM_NONE;
374 }
375
376 /************** eraser tool *************/
377
378 void erase_stroke_portions(struct Item *item, double x, double y, double radius,
379                    gboolean whole_strokes, struct UndoErasureData *erasure)
380 {
381   int i;
382   double *pt;
383   struct Item *newhead, *newtail;
384   gboolean need_recalc = FALSE;
385
386   for (i=0, pt=item->path->coords; i<item->path->num_points; i++, pt+=2) {
387     if (hypot(pt[0]-x, pt[1]-y) <= radius) { // found an intersection
388       // FIXME: need to test if line SEGMENT hits the circle
389       // hide the canvas item, and create erasure data if needed
390       if (erasure == NULL) {
391         item->type = ITEM_TEMP_STROKE;
392         gnome_canvas_item_hide(item->canvas_item);  
393             /*  we'll use this hidden item as an insertion point later */
394         erasure = (struct UndoErasureData *)g_malloc(sizeof(struct UndoErasureData));
395         item->erasure = erasure;
396         erasure->item = item;
397         erasure->npos = g_list_index(ui.cur_layer->items, item);
398         erasure->nrepl = 0;
399         erasure->replacement_items = NULL;
400       }
401       // split the stroke
402       newhead = newtail = NULL;
403       if (!whole_strokes) {
404         if (i>=2) {
405           newhead = (struct Item *)g_malloc(sizeof(struct Item));
406           newhead->type = ITEM_STROKE;
407           g_memmove(&newhead->brush, &item->brush, sizeof(struct Brush));
408           newhead->path = gnome_canvas_points_new(i);
409           g_memmove(newhead->path->coords, item->path->coords, 2*i*sizeof(double));
410           if (newhead->brush.variable_width)
411             newhead->widths = (gdouble *)g_memdup(item->widths, (i-1)*sizeof(gdouble));
412           else newhead->widths = NULL;
413         }
414         while (++i < item->path->num_points) {
415           pt+=2;
416           if (hypot(pt[0]-x, pt[1]-y) > radius) break;
417         }
418         if (i<item->path->num_points-1) {
419           newtail = (struct Item *)g_malloc(sizeof(struct Item));
420           newtail->type = ITEM_STROKE;
421           g_memmove(&newtail->brush, &item->brush, sizeof(struct Brush));
422           newtail->path = gnome_canvas_points_new(item->path->num_points-i);
423           g_memmove(newtail->path->coords, item->path->coords+2*i, 
424                            2*(item->path->num_points-i)*sizeof(double));
425           if (newtail->brush.variable_width)
426             newtail->widths = (gdouble *)g_memdup(item->widths+i, 
427               (item->path->num_points-i-1)*sizeof(gdouble));
428           else newtail->widths = NULL;
429           newtail->canvas_item = NULL;
430         }
431       }
432       if (item->type == ITEM_STROKE) { 
433         // it's inside an erasure list - we destroy it
434         gnome_canvas_points_free(item->path);
435         if (item->brush.variable_width) g_free(item->widths);
436         if (item->canvas_item != NULL) 
437           gtk_object_destroy(GTK_OBJECT(item->canvas_item));
438         erasure->nrepl--;
439         erasure->replacement_items = g_list_remove(erasure->replacement_items, item);
440         g_free(item);
441       }
442       // add the new head
443       if (newhead != NULL) {
444         update_item_bbox(newhead);
445         make_canvas_item_one(ui.cur_layer->group, newhead);
446         lower_canvas_item_to(ui.cur_layer->group,
447                   newhead->canvas_item, erasure->item->canvas_item);
448         erasure->replacement_items = g_list_prepend(erasure->replacement_items, newhead);
449         erasure->nrepl++;
450         // prepending ensures it won't get processed twice
451       }
452       // recurse into the new tail
453       need_recalc = (newtail!=NULL);
454       if (newtail == NULL) break;
455       item = newtail;
456       erasure->replacement_items = g_list_prepend(erasure->replacement_items, newtail);
457       erasure->nrepl++;
458       i=0; pt=item->path->coords;
459     }
460   }
461   // add the tail if needed
462   if (!need_recalc) return;
463   update_item_bbox(item);
464   make_canvas_item_one(ui.cur_layer->group, item);
465   lower_canvas_item_to(ui.cur_layer->group, item->canvas_item, 
466                                       erasure->item->canvas_item);
467 }
468
469
470 void do_eraser(GdkEvent *event, double radius, gboolean whole_strokes)
471 {
472   struct Item *item, *repl;
473   GList *itemlist, *repllist;
474   double pos[2];
475   struct BBox eraserbox;
476   
477   get_pointer_coords(event, pos);
478   eraserbox.left = pos[0]-radius;
479   eraserbox.right = pos[0]+radius;
480   eraserbox.top = pos[1]-radius;
481   eraserbox.bottom = pos[1]+radius;
482   for (itemlist = ui.cur_layer->items; itemlist!=NULL; itemlist = itemlist->next) {
483     item = (struct Item *)itemlist->data;
484     if (item->type == ITEM_STROKE) {
485       if (!have_intersect(&(item->bbox), &eraserbox)) continue;
486       erase_stroke_portions(item, pos[0], pos[1], radius, whole_strokes, NULL);
487     } else if (item->type == ITEM_TEMP_STROKE) {
488       repllist = item->erasure->replacement_items;
489       while (repllist!=NULL) {
490         repl = (struct Item *)repllist->data;
491           // we may delete the item soon! so advance now in the list
492         repllist = repllist->next; 
493         if (have_intersect(&(repl->bbox), &eraserbox))
494           erase_stroke_portions(repl, pos[0], pos[1], radius, whole_strokes, item->erasure);
495       }
496     }
497   }
498 }
499
500 void finalize_erasure(void)
501 {
502   GList *itemlist, *partlist;
503   struct Item *item;
504   
505   prepare_new_undo();
506   undo->type = ITEM_ERASURE;
507   undo->layer = ui.cur_layer;
508   undo->erasurelist = NULL;
509   
510   itemlist = ui.cur_layer->items;
511   while (itemlist!=NULL) {
512     item = (struct Item *)itemlist->data;
513     itemlist = itemlist->next;
514     if (item->type != ITEM_TEMP_STROKE) continue;
515     item->type = ITEM_STROKE;
516     ui.cur_layer->items = g_list_remove(ui.cur_layer->items, item);
517     // the item has an invisible canvas item, which used to act as anchor
518     if (item->canvas_item!=NULL) {
519       gtk_object_destroy(GTK_OBJECT(item->canvas_item));
520       item->canvas_item = NULL;
521     }
522     undo->erasurelist = g_list_append(undo->erasurelist, item->erasure);
523     // add the new strokes into the current layer
524     for (partlist = item->erasure->replacement_items; partlist!=NULL; partlist = partlist->next)
525       ui.cur_layer->items = g_list_insert_before(
526                       ui.cur_layer->items, itemlist, partlist->data);
527     ui.cur_layer->nitems += item->erasure->nrepl-1;
528   }
529     
530   ui.cur_item = NULL;
531   ui.cur_item_type = ITEM_NONE;
532   
533   /* NOTE: the list of erasures goes in the depth order of the layer;
534      this guarantees that, upon undo, the erasure->npos fields give the
535      correct position where each item should be reinserted as the list
536      is traversed in the forward direction */
537 }
538
539
540 gboolean do_hand_scrollto(gpointer data)
541 {
542   ui.hand_scrollto_pending = FALSE;
543   gnome_canvas_scroll_to(canvas, ui.hand_scrollto_cx, ui.hand_scrollto_cy);
544   return FALSE;
545 }
546
547 void do_hand(GdkEvent *event)
548 {
549   double pt[2];
550   int cx, cy;
551   
552   get_pointer_coords(event, pt);
553   pt[0] += ui.cur_page->hoffset;
554   pt[1] += ui.cur_page->voffset;
555   gnome_canvas_get_scroll_offsets(canvas, &cx, &cy);
556   ui.hand_scrollto_cx = cx - (pt[0]-ui.hand_refpt[0])*ui.zoom;
557   ui.hand_scrollto_cy = cy - (pt[1]-ui.hand_refpt[1])*ui.zoom;
558   if (!ui.hand_scrollto_pending) g_idle_add(do_hand_scrollto, NULL);
559   ui.hand_scrollto_pending = TRUE;
560 }
561
562 /************ TEXT FUNCTIONS **************/
563
564 // to make it easier to copy/paste at end of text box
565 #define WIDGET_RIGHT_MARGIN 10
566
567 void resize_textview(gpointer *toplevel, gpointer *data)
568 {
569   GtkTextView *w;
570   int width, height;
571   
572   /* when the text changes, resize the GtkTextView accordingly */
573   if (ui.cur_item_type!=ITEM_TEXT) return;
574   w = GTK_TEXT_VIEW(ui.cur_item->widget);
575   width = w->width + WIDGET_RIGHT_MARGIN;
576   height = w->height;
577   gnome_canvas_item_set(ui.cur_item->canvas_item, 
578     "size-pixels", TRUE, 
579     "width", (gdouble)width, "height", (gdouble)height, NULL);
580   ui.cur_item->bbox.right = ui.cur_item->bbox.left + width/ui.zoom;
581   ui.cur_item->bbox.bottom = ui.cur_item->bbox.top + height/ui.zoom;
582 }
583
584 void start_text(GdkEvent *event, struct Item *item)
585 {
586   double pt[2];
587   GtkTextBuffer *buffer;
588   GnomeCanvasItem *canvas_item;
589   PangoFontDescription *font_desc;
590   GdkColor color;
591
592   get_pointer_coords(event, pt);
593
594   ui.cur_item_type = ITEM_TEXT;
595
596   if (item==NULL) {
597     item = g_new(struct Item, 1);
598     item->text = NULL;
599     item->canvas_item = NULL;
600     item->bbox.left = pt[0];
601     item->bbox.top = pt[1];
602     item->bbox.right = ui.cur_page->width;
603     item->bbox.bottom = pt[1]+100.;
604     item->font_name = g_strdup(ui.font_name);
605     item->font_size = ui.font_size;
606     g_memmove(&(item->brush), ui.cur_brush, sizeof(struct Brush));
607     ui.cur_layer->items = g_list_append(ui.cur_layer->items, item);
608     ui.cur_layer->nitems++;
609   }
610   
611   item->type = ITEM_TEMP_TEXT;
612   ui.cur_item = item;
613   
614   font_desc = pango_font_description_from_string(item->font_name);
615   pango_font_description_set_absolute_size(font_desc, 
616       item->font_size*ui.zoom*PANGO_SCALE);
617   item->widget = gtk_text_view_new();
618   buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(item->widget));
619   if (item->text!=NULL)
620     gtk_text_buffer_set_text(buffer, item->text, -1);
621   gtk_widget_modify_font(item->widget, font_desc);
622   rgb_to_gdkcolor(item->brush.color_rgba, &color);
623   gtk_widget_modify_text(item->widget, GTK_STATE_NORMAL, &color);
624   pango_font_description_free(font_desc);
625
626   canvas_item = gnome_canvas_item_new(ui.cur_layer->group,
627     gnome_canvas_widget_get_type(),
628     "x", item->bbox.left, "y", item->bbox.top, 
629     "width", item->bbox.right-item->bbox.left, 
630     "height", item->bbox.bottom-item->bbox.top,
631     "widget", item->widget, NULL);
632   // TODO: width/height?
633   if (item->canvas_item!=NULL) {
634     lower_canvas_item_to(ui.cur_layer->group, canvas_item, item->canvas_item);
635     gtk_object_destroy(GTK_OBJECT(item->canvas_item));
636   }
637   item->canvas_item = canvas_item;
638
639   gtk_widget_show(item->widget);
640   ui.resize_signal_handler = 
641     g_signal_connect((gpointer) winMain, "check_resize",
642        G_CALLBACK(resize_textview), NULL);
643   update_font_button();
644   gtk_widget_set_sensitive(GET_COMPONENT("editPaste"), FALSE);
645   gtk_widget_set_sensitive(GET_COMPONENT("buttonPaste"), FALSE);
646   gtk_widget_grab_focus(item->widget); 
647 }
648
649 void end_text(void)
650 {
651   GtkTextBuffer *buffer;
652   GtkTextIter start, end;
653   gchar *new_text;
654   struct UndoErasureData *erasure;
655   GnomeCanvasItem *tmpitem;
656
657   if (ui.cur_item_type!=ITEM_TEXT) return; // nothing for us to do!
658
659   // finalize the text that's been edited... 
660   buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(ui.cur_item->widget));
661   gtk_text_buffer_get_bounds(buffer, &start, &end);
662   ui.cur_item->type = ITEM_TEXT;
663   new_text = gtk_text_buffer_get_text(buffer, &start, &end, TRUE);
664   ui.cur_item_type = ITEM_NONE;
665   gtk_widget_set_sensitive(GET_COMPONENT("editPaste"), TRUE);
666   gtk_widget_set_sensitive(GET_COMPONENT("buttonPaste"), TRUE);
667   
668   if (strlen(new_text)==0) { // erase object and cancel
669     g_free(new_text);
670     g_signal_handler_disconnect(winMain, ui.resize_signal_handler);
671     gtk_object_destroy(GTK_OBJECT(ui.cur_item->canvas_item));
672     ui.cur_item->canvas_item = NULL;
673     if (ui.cur_item->text == NULL) // nothing happened
674       g_free(ui.cur_item->font_name);
675     else { // treat this as an erasure
676       prepare_new_undo();
677       undo->type = ITEM_ERASURE;
678       undo->layer = ui.cur_layer;
679       erasure = (struct UndoErasureData *)g_malloc(sizeof(struct UndoErasureData));
680       erasure->item = ui.cur_item;
681       erasure->npos = g_list_index(ui.cur_layer->items, ui.cur_item);
682       erasure->nrepl = 0;
683       erasure->replacement_items = NULL;
684       undo->erasurelist = g_list_append(NULL, erasure);
685     }
686     ui.cur_layer->items = g_list_remove(ui.cur_layer->items, ui.cur_item);
687     ui.cur_layer->nitems--;
688     ui.cur_item = NULL;
689     return;
690   }
691
692   // store undo data
693   if (ui.cur_item->text==NULL || strcmp(ui.cur_item->text, new_text)) {
694     prepare_new_undo();
695     if (ui.cur_item->text == NULL) undo->type = ITEM_TEXT; 
696     else undo->type = ITEM_TEXT_EDIT;
697     undo->layer = ui.cur_layer;
698     undo->item = ui.cur_item;
699     undo->str = ui.cur_item->text;
700   }
701   else g_free(ui.cur_item->text);
702
703   ui.cur_item->text = new_text;
704   ui.cur_item->widget = NULL;
705   // replace the canvas item
706   tmpitem = ui.cur_item->canvas_item;
707   make_canvas_item_one(ui.cur_layer->group, ui.cur_item);
708   update_item_bbox(ui.cur_item);
709   lower_canvas_item_to(ui.cur_layer->group, ui.cur_item->canvas_item, tmpitem);
710   gtk_object_destroy(GTK_OBJECT(tmpitem));
711 }
712
713 /* update the items in the canvas so they're of the right font size */
714
715 void update_text_item_displayfont(struct Item *item)
716 {
717   PangoFontDescription *font_desc;
718
719   if (item->type != ITEM_TEXT && item->type != ITEM_TEMP_TEXT) return;
720   if (item->canvas_item==NULL) return;
721   font_desc = pango_font_description_from_string(item->font_name);
722   pango_font_description_set_absolute_size(font_desc, 
723         item->font_size*ui.zoom*PANGO_SCALE);
724   if (item->type == ITEM_TEMP_TEXT)
725     gtk_widget_modify_font(item->widget, font_desc);
726   else {
727     gnome_canvas_item_set(item->canvas_item, "font-desc", font_desc, NULL);
728     update_item_bbox(item);
729   }
730   pango_font_description_free(font_desc);
731 }
732
733 void rescale_text_items(void)
734 {
735   GList *pagelist, *layerlist, *itemlist;
736   
737   for (pagelist = journal.pages; pagelist!=NULL; pagelist = pagelist->next)
738     for (layerlist = ((struct Page *)pagelist->data)->layers; layerlist!=NULL; layerlist = layerlist->next)
739       for (itemlist = ((struct Layer *)layerlist->data)->items; itemlist!=NULL; itemlist = itemlist->next)
740         update_text_item_displayfont((struct Item *)itemlist->data);
741 }
742
743 struct Item *click_is_in_text(struct Layer *layer, double x, double y)
744 {
745   GList *itemlist;
746   struct Item *item, *val;
747   
748   val = NULL;
749   for (itemlist = layer->items; itemlist!=NULL; itemlist = itemlist->next) {
750     item = (struct Item *)itemlist->data;
751     if (item->type != ITEM_TEXT) continue;
752     if (x<item->bbox.left || x>item->bbox.right) continue;
753     if (y<item->bbox.top || y>item->bbox.bottom) continue;
754     val = item;
755   }
756   return val;
757 }
758
759 struct Item *click_is_in_text_or_image(struct Layer *layer, double x, double y)
760 {
761   GList *itemlist;
762   struct Item *item, *val;
763   
764   val = NULL;
765   for (itemlist = layer->items; itemlist!=NULL; itemlist = itemlist->next) {
766     item = (struct Item *)itemlist->data;
767     if (item->type != ITEM_TEXT && item->type != ITEM_IMAGE) continue;
768     if (x<item->bbox.left || x>item->bbox.right) continue;
769     if (y<item->bbox.top || y>item->bbox.bottom) continue;
770     val = item;
771   }
772   return val;
773 }
774
775 void refont_text_item(struct Item *item, gchar *font_name, double font_size)
776 {
777   if (!strcmp(font_name, item->font_name) && font_size==item->font_size) return;
778   if (item->text!=NULL) {
779     prepare_new_undo();
780     undo->type = ITEM_TEXT_ATTRIB;
781     undo->item = item;
782     undo->str = item->font_name;
783     undo->val_x = item->font_size;
784     undo->brush = (struct Brush *)g_memdup(&(item->brush), sizeof(struct Brush));
785   }
786   else g_free(item->font_name);
787   item->font_name = g_strdup(font_name);
788   if (font_size>0.) item->font_size = font_size;
789   update_text_item_displayfont(item);
790 }
791
792 void process_font_sel(gchar *str)
793 {
794   gchar *p, *q;
795   struct Item *it;
796   gdouble size;
797   GList *list;
798   gboolean undo_cont;
799
800   p = strrchr(str, ' ');
801   if (p!=NULL) { 
802     size = g_strtod(p+1, &q);
803     if (*q!=0 || size<1.) size=0.;
804     else *p=0;
805   }
806   else size=0.;
807   g_free(ui.font_name);
808   ui.font_name = str;  
809   if (size>0.) ui.font_size = size;
810   undo_cont = FALSE;   
811   // if there's a current text item, re-font it
812   if (ui.cur_item_type == ITEM_TEXT) {
813     refont_text_item(ui.cur_item, str, size);
814     undo_cont = (ui.cur_item->text!=NULL);   
815   }
816   // if there's a current selection, re-font it
817   if (ui.selection!=NULL) 
818     for (list=ui.selection->items; list!=NULL; list=list->next) {
819       it = (struct Item *)list->data;
820       if (it->type == ITEM_TEXT) {   
821         if (undo_cont) undo->multiop |= MULTIOP_CONT_REDO;
822         refont_text_item(it, str, size);
823         if (undo_cont) undo->multiop |= MULTIOP_CONT_UNDO;
824         undo_cont = TRUE;
825       }
826     }  
827   update_font_button();
828 }