]> git.donarmstrong.com Git - xournal.git/blob - src/xo-paint.c
Image patch
[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 <libart_lgpl/art_vpath_dash.h>
26
27 #include "xournal.h"
28 #include "xo-callbacks.h"
29 #include "xo-interface.h"
30 #include "xo-support.h"
31 #include "xo-misc.h"
32 #include "xo-paint.h"
33
34 /***** Win32 fix for gdk_cursor_new_from_pixmap() by Dirk Gerrits ****/
35
36 #ifdef WIN32
37 gboolean colors_too_similar(const GdkColor *colora, const GdkColor *colorb)
38 {
39   return (abs(colora->red - colorb->red) < 256 &&
40           abs(colora->green - colorb->green) < 256 &&
41           abs(colora->blue - colorb->blue) < 256);
42 }
43
44 /* gdk_cursor_new_from_pixmap is broken on Windows.
45    this is a workaround using gdk_cursor_new_from_pixbuf. */
46 GdkCursor* fixed_gdk_cursor_new_from_pixmap(GdkPixmap *source, GdkPixmap *mask,
47                                             const GdkColor *fg, const GdkColor *bg,
48                                             gint x, gint y)
49 {
50   GdkPixmap *rgb_pixmap;
51   GdkGC *gc;
52   GdkPixbuf *rgb_pixbuf, *rgba_pixbuf;
53   GdkCursor *cursor;
54   int width, height;
55
56   /* HACK!  It seems impossible to work with RGBA pixmaps directly in
57      GDK-Win32.  Instead we pick some third color, different from fg
58      and bg, and use that as the 'transparent color'.  We do this using
59      colors_too_similar (see above) because two colors could be
60      unequal in GdkColor's 16-bit/sample, but equal in GdkPixbuf's
61      8-bit/sample. */
62   GdkColor candidates[3] = {{0,65535,0,0}, {0,0,65535,0}, {0,0,0,65535}};
63   GdkColor *trans = &candidates[0];
64   if (colors_too_similar(trans, fg) || colors_too_similar(trans, bg)) {
65     trans = &candidates[1];
66     if (colors_too_similar(trans, fg) || colors_too_similar(trans, bg)) {
67       trans = &candidates[2];
68     }
69   } /* trans is now guaranteed to be unique from fg and bg */
70
71   /* create an empty pixmap to hold the cursor image */
72   gdk_drawable_get_size(source, &width, &height);
73   rgb_pixmap = gdk_pixmap_new(NULL, width, height, 24);
74
75   /* blit the bitmaps defining the cursor onto a transparent background */
76   gc = gdk_gc_new(rgb_pixmap);
77   gdk_gc_set_fill(gc, GDK_SOLID);
78   gdk_gc_set_rgb_fg_color(gc, trans);
79   gdk_draw_rectangle(rgb_pixmap, gc, TRUE, 0, 0, width, height);
80   gdk_gc_set_fill(gc, GDK_OPAQUE_STIPPLED);
81   gdk_gc_set_stipple(gc, source);
82   gdk_gc_set_clip_mask(gc, mask);
83   gdk_gc_set_rgb_fg_color(gc, fg);
84   gdk_gc_set_rgb_bg_color(gc, bg);
85   gdk_draw_rectangle(rgb_pixmap, gc, TRUE, 0, 0, width, height);
86   gdk_gc_unref(gc);
87
88   /* create a cursor out of the created pixmap */
89   rgb_pixbuf = gdk_pixbuf_get_from_drawable(
90     NULL, rgb_pixmap, gdk_colormap_get_system(), 0, 0, 0, 0, width, height);
91   gdk_pixmap_unref(rgb_pixmap);
92   rgba_pixbuf = gdk_pixbuf_add_alpha(
93     rgb_pixbuf, TRUE, trans->red, trans->green, trans->blue);
94   gdk_pixbuf_unref(rgb_pixbuf);
95   cursor = gdk_cursor_new_from_pixbuf(gdk_display_get_default(), rgba_pixbuf, x, y);
96   gdk_pixbuf_unref(rgba_pixbuf);
97
98   return cursor;
99 }
100 #define gdk_cursor_new_from_pixmap fixed_gdk_cursor_new_from_pixmap
101 #endif
102
103
104 /************** drawing nice cursors *********/
105
106 static char cursor_pen_bits[] = {
107    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
108    0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
109    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
110
111 static char cursor_eraser_bits[] = {
112    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x0f, 0x08, 0x08, 0x08, 0x08,
113    0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0xf8, 0x0f,
114    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
115
116 static char cursor_eraser_mask[] = {
117    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x0f, 0xf8, 0x0f, 0xf8, 0x0f,
118    0xf8, 0x0f, 0xf8, 0x0f, 0xf8, 0x0f, 0xf8, 0x0f, 0xf8, 0x0f, 0xf8, 0x0f,
119    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
120
121 void set_cursor_busy(gboolean busy)
122 {
123   GdkCursor *cursor;
124   
125   if (busy) {
126     cursor = gdk_cursor_new(GDK_WATCH);
127     gdk_window_set_cursor(GTK_WIDGET(winMain)->window, cursor);
128     gdk_window_set_cursor(GTK_WIDGET(canvas)->window, cursor);
129     gdk_cursor_unref(cursor);
130   }
131   else {
132     gdk_window_set_cursor(GTK_WIDGET(winMain)->window, NULL);
133     update_cursor();
134   }
135   gdk_display_sync(gdk_display_get_default());
136 }
137
138 void update_cursor(void)
139 {
140   GdkPixmap *source, *mask;
141   GdkColor fg = {0, 0, 0, 0}, bg = {0, 65535, 65535, 65535};
142
143   ui.is_sel_cursor = FALSE;
144   if (GTK_WIDGET(canvas)->window == NULL) return;
145   
146   if (ui.cursor!=NULL) { 
147     gdk_cursor_unref(ui.cursor);
148     ui.cursor = NULL;
149   }
150   if (ui.cur_item_type == ITEM_MOVESEL_VERT)
151     ui.cursor = gdk_cursor_new(GDK_SB_V_DOUBLE_ARROW);
152   else if (ui.cur_item_type == ITEM_MOVESEL)
153     ui.cursor = gdk_cursor_new(GDK_FLEUR);
154   else if (ui.toolno[ui.cur_mapping] == TOOL_PEN) {
155     fg.red = (ui.cur_brush->color_rgba >> 16) & 0xff00;
156     fg.green = (ui.cur_brush->color_rgba >> 8) & 0xff00;
157     fg.blue = (ui.cur_brush->color_rgba >> 0) & 0xff00;
158     source = gdk_bitmap_create_from_data(NULL, cursor_pen_bits, 16, 16);
159     ui.cursor = gdk_cursor_new_from_pixmap(source, source, &fg, &bg, 7, 7);
160     gdk_bitmap_unref(source);
161   }
162   else if (ui.toolno[ui.cur_mapping] == TOOL_ERASER) {
163     source = gdk_bitmap_create_from_data(NULL, cursor_eraser_bits, 16, 16);
164     mask = gdk_bitmap_create_from_data(NULL, cursor_eraser_mask, 16, 16);
165     ui.cursor = gdk_cursor_new_from_pixmap(source, mask, &fg, &bg, 7, 7);
166     gdk_bitmap_unref(source);
167     gdk_bitmap_unref(mask);
168   }
169   else if (ui.toolno[ui.cur_mapping] == TOOL_HIGHLIGHTER) {
170     source = gdk_bitmap_create_from_data(NULL, cursor_eraser_bits, 16, 16);
171     mask = gdk_bitmap_create_from_data(NULL, cursor_eraser_mask, 16, 16);
172     bg.red = (ui.cur_brush->color_rgba >> 16) & 0xff00;
173     bg.green = (ui.cur_brush->color_rgba >> 8) & 0xff00;
174     bg.blue = (ui.cur_brush->color_rgba >> 0) & 0xff00;
175     ui.cursor = gdk_cursor_new_from_pixmap(source, mask, &fg, &bg, 7, 7);
176     gdk_bitmap_unref(source);
177     gdk_bitmap_unref(mask);
178   }
179   else if (ui.cur_item_type == ITEM_SELECTRECT) {
180     ui.cursor = gdk_cursor_new(GDK_TCROSS);
181   }
182   else if (ui.toolno[ui.cur_mapping] == TOOL_HAND) {
183     ui.cursor = gdk_cursor_new(GDK_HAND1);
184   }
185   else if (ui.toolno[ui.cur_mapping] == TOOL_TEXT) {
186     ui.cursor = gdk_cursor_new(GDK_XTERM);
187   }
188   
189   gdk_window_set_cursor(GTK_WIDGET(canvas)->window, ui.cursor);
190 }
191
192 /* adjust the cursor shape if it hovers near a selection box */
193
194 void update_cursor_for_resize(double *pt)
195 {
196   gboolean in_range_x, in_range_y;
197   gboolean can_resize_left, can_resize_right, can_resize_bottom, can_resize_top;
198   gdouble resize_margin;
199   GdkCursorType newcursor;
200
201   // if we're not even close to the box in some direction, return immediately
202   resize_margin = RESIZE_MARGIN / ui.zoom;
203   if (pt[0]<ui.selection->bbox.left-resize_margin || pt[0]>ui.selection->bbox.right+resize_margin
204    || pt[1]<ui.selection->bbox.top-resize_margin || pt[1]>ui.selection->bbox.bottom+resize_margin)
205   {
206     if (ui.is_sel_cursor) update_cursor();
207     return;
208   }
209
210   ui.is_sel_cursor = TRUE;
211   can_resize_left = (pt[0] < ui.selection->bbox.left+resize_margin);
212   can_resize_right = (pt[0] > ui.selection->bbox.right-resize_margin);
213   can_resize_top = (pt[1] < ui.selection->bbox.top+resize_margin);
214   can_resize_bottom = (pt[1] > ui.selection->bbox.bottom-resize_margin);
215
216   if (can_resize_left) {
217     if (can_resize_top) newcursor = GDK_TOP_LEFT_CORNER;
218     else if (can_resize_bottom) newcursor = GDK_BOTTOM_LEFT_CORNER;
219     else newcursor = GDK_LEFT_SIDE;
220   }
221   else if (can_resize_right) {
222     if (can_resize_top) newcursor = GDK_TOP_RIGHT_CORNER;
223     else if (can_resize_bottom) newcursor = GDK_BOTTOM_RIGHT_CORNER;
224     else newcursor = GDK_RIGHT_SIDE;
225   }
226   else if (can_resize_top) newcursor = GDK_TOP_SIDE;
227   else if (can_resize_bottom) newcursor = GDK_BOTTOM_SIDE;
228   else newcursor = GDK_FLEUR;
229
230   if (ui.cursor!=NULL && ui.cursor->type == newcursor) return;
231   if (ui.cursor!=NULL) gdk_cursor_unref(ui.cursor);
232   ui.cursor = gdk_cursor_new(newcursor);
233   gdk_window_set_cursor(GTK_WIDGET(canvas)->window, ui.cursor);
234 }
235
236 /************** painting strokes *************/
237
238 #define SUBDIVIDE_MAXDIST 5.0
239
240 void subdivide_cur_path(null)
241 {
242   int n, pieces, k;
243   double *p;
244   double x0, y0, x1, y1;
245
246   for (n=0, p=ui.cur_path.coords; n<ui.cur_path.num_points-1; n++, p+=2) {
247     pieces = (int)floor(hypot(p[2]-p[0], p[3]-p[1])/SUBDIVIDE_MAXDIST);
248     if (pieces>1) {
249       x0 = p[0]; y0 = p[1];
250       x1 = p[2]; y1 = p[3];
251       realloc_cur_path(ui.cur_path.num_points+pieces-1);
252       g_memmove(ui.cur_path.coords+2*(n+pieces), ui.cur_path.coords+2*(n+1),
253                     2*(ui.cur_path.num_points-n-1)*sizeof(double));
254       p = ui.cur_path.coords+2*n;
255       ui.cur_path.num_points += pieces-1;
256       n += (pieces-1);
257       for (k=1; k<pieces; k++) {
258         p+=2;
259         p[0] = x0 + k*(x1-x0)/pieces;
260         p[1] = y0 + k*(y1-y0)/pieces;
261       } 
262     }
263   }
264 }
265
266 void create_new_stroke(GdkEvent *event)
267 {
268   ui.cur_item_type = ITEM_STROKE;
269   ui.cur_item = g_new(struct Item, 1);
270   ui.cur_item->type = ITEM_STROKE;
271   g_memmove(&(ui.cur_item->brush), ui.cur_brush, sizeof(struct Brush));
272   ui.cur_item->path = &ui.cur_path;
273   realloc_cur_path(2);
274   ui.cur_path.num_points = 1;
275   get_pointer_coords(event, ui.cur_path.coords);
276   
277   if (ui.cur_brush->ruler) {
278     ui.cur_item->canvas_item = gnome_canvas_item_new(ui.cur_layer->group,
279       gnome_canvas_line_get_type(),
280       "cap-style", GDK_CAP_ROUND, "join-style", GDK_JOIN_ROUND,
281       "fill-color-rgba", ui.cur_item->brush.color_rgba,
282       "width-units", ui.cur_item->brush.thickness, NULL);
283     ui.cur_item->brush.variable_width = FALSE;
284   } else
285     ui.cur_item->canvas_item = gnome_canvas_item_new(
286       ui.cur_layer->group, gnome_canvas_group_get_type(), NULL);
287 }
288
289 void continue_stroke(GdkEvent *event)
290 {
291   GnomeCanvasPoints seg;
292   double *pt, current_width;
293
294   if (ui.cur_brush->ruler) {
295     pt = ui.cur_path.coords;
296   } else {
297     realloc_cur_path(ui.cur_path.num_points+1);
298     pt = ui.cur_path.coords + 2*(ui.cur_path.num_points-1);
299   } 
300   
301   get_pointer_coords(event, pt+2);
302   
303   if (ui.cur_item->brush.variable_width) {
304     realloc_cur_widths(ui.cur_path.num_points);
305     current_width = ui.cur_item->brush.thickness*get_pressure_multiplier(event);
306     ui.cur_widths[ui.cur_path.num_points-1] = current_width;
307   }
308   else current_width = ui.cur_item->brush.thickness;
309   
310   if (ui.cur_brush->ruler)
311     ui.cur_path.num_points = 2;
312   else {
313     if (hypot(pt[0]-pt[2], pt[1]-pt[3]) < PIXEL_MOTION_THRESHOLD/ui.zoom)
314       return;  // not a meaningful motion
315     ui.cur_path.num_points++;
316   }
317
318   seg.coords = pt; 
319   seg.num_points = 2;
320   seg.ref_count = 1;
321   
322   /* note: we're using a piece of the cur_path array. This is ok because
323      upon creation the line just copies the contents of the GnomeCanvasPoints
324      into an internal structure */
325
326   if (ui.cur_brush->ruler)
327     gnome_canvas_item_set(ui.cur_item->canvas_item, "points", &seg, NULL);
328   else
329     gnome_canvas_item_new((GnomeCanvasGroup *)ui.cur_item->canvas_item,
330        gnome_canvas_line_get_type(), "points", &seg,
331        "cap-style", GDK_CAP_ROUND, "join-style", GDK_JOIN_ROUND,
332        "fill-color-rgba", ui.cur_item->brush.color_rgba,
333        "width-units", current_width, NULL);
334 }
335
336 void finalize_stroke(void)
337 {
338   if (ui.cur_path.num_points == 1) { // GnomeCanvas doesn't like num_points=1
339     ui.cur_path.coords[2] = ui.cur_path.coords[0]+0.1;
340     ui.cur_path.coords[3] = ui.cur_path.coords[1];
341     ui.cur_path.num_points = 2;
342     ui.cur_item->brush.variable_width = FALSE;
343   }
344   
345   if (!ui.cur_item->brush.variable_width)
346     subdivide_cur_path(); // split the segment so eraser will work
347
348   ui.cur_item->path = gnome_canvas_points_new(ui.cur_path.num_points);
349   g_memmove(ui.cur_item->path->coords, ui.cur_path.coords, 
350       2*ui.cur_path.num_points*sizeof(double));
351   if (ui.cur_item->brush.variable_width)
352     ui.cur_item->widths = (gdouble *)g_memdup(ui.cur_widths, 
353                             (ui.cur_path.num_points-1)*sizeof(gdouble));
354   else ui.cur_item->widths = NULL;
355   update_item_bbox(ui.cur_item);
356   ui.cur_path.num_points = 0;
357
358   if (!ui.cur_item->brush.variable_width) {
359     // destroy the entire group of temporary line segments
360     gtk_object_destroy(GTK_OBJECT(ui.cur_item->canvas_item));
361     // make a new line item to replace it
362     make_canvas_item_one(ui.cur_layer->group, ui.cur_item);
363   }
364
365   // add undo information
366   prepare_new_undo();
367   undo->type = ITEM_STROKE;
368   undo->item = ui.cur_item;
369   undo->layer = ui.cur_layer;
370
371   // store the item on top of the layer stack
372   ui.cur_layer->items = g_list_append(ui.cur_layer->items, ui.cur_item);
373   ui.cur_layer->nitems++;
374   ui.cur_item = NULL;
375   ui.cur_item_type = ITEM_NONE;
376 }
377
378 /************** eraser tool *************/
379
380 void erase_stroke_portions(struct Item *item, double x, double y, double radius,
381                    gboolean whole_strokes, struct UndoErasureData *erasure)
382 {
383   int i;
384   double *pt;
385   struct Item *newhead, *newtail;
386   gboolean need_recalc = FALSE;
387
388   for (i=0, pt=item->path->coords; i<item->path->num_points; i++, pt+=2) {
389     if (hypot(pt[0]-x, pt[1]-y) <= radius) { // found an intersection
390       // FIXME: need to test if line SEGMENT hits the circle
391       // hide the canvas item, and create erasure data if needed
392       if (erasure == NULL) {
393         item->type = ITEM_TEMP_STROKE;
394         gnome_canvas_item_hide(item->canvas_item);  
395             /*  we'll use this hidden item as an insertion point later */
396         erasure = (struct UndoErasureData *)g_malloc(sizeof(struct UndoErasureData));
397         item->erasure = erasure;
398         erasure->item = item;
399         erasure->npos = g_list_index(ui.cur_layer->items, item);
400         erasure->nrepl = 0;
401         erasure->replacement_items = NULL;
402       }
403       // split the stroke
404       newhead = newtail = NULL;
405       if (!whole_strokes) {
406         if (i>=2) {
407           newhead = (struct Item *)g_malloc(sizeof(struct Item));
408           newhead->type = ITEM_STROKE;
409           g_memmove(&newhead->brush, &item->brush, sizeof(struct Brush));
410           newhead->path = gnome_canvas_points_new(i);
411           g_memmove(newhead->path->coords, item->path->coords, 2*i*sizeof(double));
412           if (newhead->brush.variable_width)
413             newhead->widths = (gdouble *)g_memdup(item->widths, (i-1)*sizeof(gdouble));
414           else newhead->widths = NULL;
415         }
416         while (++i < item->path->num_points) {
417           pt+=2;
418           if (hypot(pt[0]-x, pt[1]-y) > radius) break;
419         }
420         if (i<item->path->num_points-1) {
421           newtail = (struct Item *)g_malloc(sizeof(struct Item));
422           newtail->type = ITEM_STROKE;
423           g_memmove(&newtail->brush, &item->brush, sizeof(struct Brush));
424           newtail->path = gnome_canvas_points_new(item->path->num_points-i);
425           g_memmove(newtail->path->coords, item->path->coords+2*i, 
426                            2*(item->path->num_points-i)*sizeof(double));
427           if (newtail->brush.variable_width)
428             newtail->widths = (gdouble *)g_memdup(item->widths+i, 
429               (item->path->num_points-i-1)*sizeof(gdouble));
430           else newtail->widths = NULL;
431           newtail->canvas_item = NULL;
432         }
433       }
434       if (item->type == ITEM_STROKE) { 
435         // it's inside an erasure list - we destroy it
436         gnome_canvas_points_free(item->path);
437         if (item->brush.variable_width) g_free(item->widths);
438         if (item->canvas_item != NULL) 
439           gtk_object_destroy(GTK_OBJECT(item->canvas_item));
440         erasure->nrepl--;
441         erasure->replacement_items = g_list_remove(erasure->replacement_items, item);
442         g_free(item);
443       }
444       // add the new head
445       if (newhead != NULL) {
446         update_item_bbox(newhead);
447         make_canvas_item_one(ui.cur_layer->group, newhead);
448         lower_canvas_item_to(ui.cur_layer->group,
449                   newhead->canvas_item, erasure->item->canvas_item);
450         erasure->replacement_items = g_list_prepend(erasure->replacement_items, newhead);
451         erasure->nrepl++;
452         // prepending ensures it won't get processed twice
453       }
454       // recurse into the new tail
455       need_recalc = (newtail!=NULL);
456       if (newtail == NULL) break;
457       item = newtail;
458       erasure->replacement_items = g_list_prepend(erasure->replacement_items, newtail);
459       erasure->nrepl++;
460       i=0; pt=item->path->coords;
461     }
462   }
463   // add the tail if needed
464   if (!need_recalc) return;
465   update_item_bbox(item);
466   make_canvas_item_one(ui.cur_layer->group, item);
467   lower_canvas_item_to(ui.cur_layer->group, item->canvas_item, 
468                                       erasure->item->canvas_item);
469 }
470
471
472 void do_eraser(GdkEvent *event, double radius, gboolean whole_strokes)
473 {
474   struct Item *item, *repl;
475   GList *itemlist, *repllist;
476   double pos[2];
477   struct BBox eraserbox;
478   
479   get_pointer_coords(event, pos);
480   eraserbox.left = pos[0]-radius;
481   eraserbox.right = pos[0]+radius;
482   eraserbox.top = pos[1]-radius;
483   eraserbox.bottom = pos[1]+radius;
484   for (itemlist = ui.cur_layer->items; itemlist!=NULL; itemlist = itemlist->next) {
485     item = (struct Item *)itemlist->data;
486     if (item->type == ITEM_STROKE) {
487       if (!have_intersect(&(item->bbox), &eraserbox)) continue;
488       erase_stroke_portions(item, pos[0], pos[1], radius, whole_strokes, NULL);
489     } else if (item->type == ITEM_TEMP_STROKE) {
490       repllist = item->erasure->replacement_items;
491       while (repllist!=NULL) {
492         repl = (struct Item *)repllist->data;
493           // we may delete the item soon! so advance now in the list
494         repllist = repllist->next; 
495         if (have_intersect(&(repl->bbox), &eraserbox))
496           erase_stroke_portions(repl, pos[0], pos[1], radius, whole_strokes, item->erasure);
497       }
498     }
499   }
500 }
501
502 void finalize_erasure(void)
503 {
504   GList *itemlist, *partlist;
505   struct Item *item;
506   
507   prepare_new_undo();
508   undo->type = ITEM_ERASURE;
509   undo->layer = ui.cur_layer;
510   undo->erasurelist = NULL;
511   
512   itemlist = ui.cur_layer->items;
513   while (itemlist!=NULL) {
514     item = (struct Item *)itemlist->data;
515     itemlist = itemlist->next;
516     if (item->type != ITEM_TEMP_STROKE) continue;
517     item->type = ITEM_STROKE;
518     ui.cur_layer->items = g_list_remove(ui.cur_layer->items, item);
519     // the item has an invisible canvas item, which used to act as anchor
520     if (item->canvas_item!=NULL) {
521       gtk_object_destroy(GTK_OBJECT(item->canvas_item));
522       item->canvas_item = NULL;
523     }
524     undo->erasurelist = g_list_append(undo->erasurelist, item->erasure);
525     // add the new strokes into the current layer
526     for (partlist = item->erasure->replacement_items; partlist!=NULL; partlist = partlist->next)
527       ui.cur_layer->items = g_list_insert_before(
528                       ui.cur_layer->items, itemlist, partlist->data);
529     ui.cur_layer->nitems += item->erasure->nrepl-1;
530   }
531     
532   ui.cur_item = NULL;
533   ui.cur_item_type = ITEM_NONE;
534   
535   /* NOTE: the list of erasures goes in the depth order of the layer;
536      this guarantees that, upon undo, the erasure->npos fields give the
537      correct position where each item should be reinserted as the list
538      is traversed in the forward direction */
539 }
540
541 /************ selection tools ***********/
542
543 void make_dashed(GnomeCanvasItem *item)
544 {
545   double dashlen[2];
546   ArtVpathDash dash;
547   
548   dash.n_dash = 2;
549   dash.offset = 3.0;
550   dash.dash = dashlen;
551   dashlen[0] = dashlen[1] = 6.0;
552   gnome_canvas_item_set(item, "dash", &dash, NULL);
553 }
554
555
556 void start_selectrect(GdkEvent *event)
557 {
558   double pt[2];
559   reset_selection();
560   
561   ui.cur_item_type = ITEM_SELECTRECT;
562   ui.selection = g_new(struct Selection, 1);
563   ui.selection->type = ITEM_SELECTRECT;
564   ui.selection->items = NULL;
565   ui.selection->layer = ui.cur_layer;
566
567   get_pointer_coords(event, pt);
568   ui.selection->bbox.left = ui.selection->bbox.right = pt[0];
569   ui.selection->bbox.top = ui.selection->bbox.bottom = pt[1];
570  
571   ui.selection->canvas_item = gnome_canvas_item_new(ui.cur_layer->group,
572       gnome_canvas_rect_get_type(), "width-pixels", 1, 
573       "outline-color-rgba", 0x000000ff,
574       "fill-color-rgba", 0x80808040,
575       "x1", pt[0], "x2", pt[0], "y1", pt[1], "y2", pt[1], NULL);
576   update_cursor();
577 }
578
579 void finalize_selectrect(void)
580 {
581   double x1, x2, y1, y2;
582   GList *itemlist;
583   struct Item *item;
584
585   
586   ui.cur_item_type = ITEM_NONE;
587
588   if (ui.selection->bbox.left > ui.selection->bbox.right) {
589     x1 = ui.selection->bbox.right;  x2 = ui.selection->bbox.left;
590     ui.selection->bbox.left = x1;   ui.selection->bbox.right = x2;
591   } else {
592     x1 = ui.selection->bbox.left;  x2 = ui.selection->bbox.right;
593   }
594
595   if (ui.selection->bbox.top > ui.selection->bbox.bottom) {
596     y1 = ui.selection->bbox.bottom;  y2 = ui.selection->bbox.top;
597     ui.selection->bbox.top = y1;   ui.selection->bbox.bottom = y2;
598   } else {
599     y1 = ui.selection->bbox.top;  y2 = ui.selection->bbox.bottom;
600   }
601   
602   for (itemlist = ui.selection->layer->items; itemlist!=NULL; itemlist = itemlist->next) {
603     item = (struct Item *)itemlist->data;
604     if (item->bbox.left >= x1 && item->bbox.right <= x2 &&
605           item->bbox.top >= y1 && item->bbox.bottom <= y2) {
606       ui.selection->items = g_list_append(ui.selection->items, item); 
607     }
608   }
609   
610   if (ui.selection->items == NULL) {
611     // if we clicked inside a text zone ?  
612     item = click_is_in_text(ui.selection->layer, x1, y1);
613     if (item!=NULL && item==click_is_in_text(ui.selection->layer, x2, y2)) {
614       ui.selection->items = g_list_append(ui.selection->items, item);
615       g_memmove(&(ui.selection->bbox), &(item->bbox), sizeof(struct BBox));
616       gnome_canvas_item_set(ui.selection->canvas_item,
617         "x1", item->bbox.left, "x2", item->bbox.right, 
618         "y1", item->bbox.top, "y2", item->bbox.bottom, NULL);
619     }
620   }
621   
622   if (ui.selection->items == NULL) reset_selection();
623   else make_dashed(ui.selection->canvas_item);
624   update_cursor();
625   update_copy_paste_enabled();
626   update_font_button();
627 }
628
629 gboolean start_movesel(GdkEvent *event)
630 {
631   double pt[2];
632   
633   if (ui.selection==NULL) return FALSE;
634   if (ui.cur_layer != ui.selection->layer) return FALSE;
635   
636   get_pointer_coords(event, pt);
637   if (ui.selection->type == ITEM_SELECTRECT) {
638     if (pt[0]<ui.selection->bbox.left || pt[0]>ui.selection->bbox.right ||
639         pt[1]<ui.selection->bbox.top  || pt[1]>ui.selection->bbox.bottom)
640       return FALSE;
641     ui.cur_item_type = ITEM_MOVESEL;
642     ui.selection->anchor_x = ui.selection->last_x = pt[0];
643     ui.selection->anchor_y = ui.selection->last_y = pt[1];
644     ui.selection->orig_pageno = ui.pageno;
645     ui.selection->move_pageno = ui.pageno;
646     ui.selection->move_layer = ui.selection->layer;
647     ui.selection->move_pagedelta = 0.;
648     gnome_canvas_item_set(ui.selection->canvas_item, "dash", NULL, NULL);
649     update_cursor();
650     return TRUE;
651   }
652   return FALSE;
653 }
654
655 gboolean start_resizesel(GdkEvent *event)
656 {
657   double pt[2], resize_margin, hmargin, vmargin;
658
659   if (ui.selection==NULL) return FALSE;
660   if (ui.cur_layer != ui.selection->layer) return FALSE;
661
662   get_pointer_coords(event, pt);
663
664   if (ui.selection->type == ITEM_SELECTRECT) {
665     resize_margin = RESIZE_MARGIN/ui.zoom;
666     hmargin = (ui.selection->bbox.right-ui.selection->bbox.left)*0.3;
667     if (hmargin>resize_margin) hmargin = resize_margin;
668     vmargin = (ui.selection->bbox.bottom-ui.selection->bbox.top)*0.3;
669     if (vmargin>resize_margin) vmargin = resize_margin;
670
671     // make sure the click is within a box slightly bigger than the selection rectangle
672     if (pt[0]<ui.selection->bbox.left-resize_margin || 
673         pt[0]>ui.selection->bbox.right+resize_margin ||
674         pt[1]<ui.selection->bbox.top-resize_margin || 
675         pt[1]>ui.selection->bbox.bottom+resize_margin)
676       return FALSE;
677
678     // now, if the click is near the edge, it's a resize operation
679     // keep track of which edges we're close to, since those are the ones which should move
680     ui.selection->resizing_left = (pt[0]<ui.selection->bbox.left+hmargin);
681     ui.selection->resizing_right = (pt[0]>ui.selection->bbox.right-hmargin);
682     ui.selection->resizing_top = (pt[1]<ui.selection->bbox.top+vmargin);
683     ui.selection->resizing_bottom = (pt[1]>ui.selection->bbox.bottom-vmargin);
684
685     // we're not near any edge, give up
686     if (!(ui.selection->resizing_left || ui.selection->resizing_right ||
687           ui.selection->resizing_top  || ui.selection->resizing_bottom)) 
688       return FALSE;
689
690     ui.cur_item_type = ITEM_RESIZESEL;
691     ui.selection->new_y1 = ui.selection->bbox.top;
692     ui.selection->new_y2 = ui.selection->bbox.bottom;
693     ui.selection->new_x1 = ui.selection->bbox.left;
694     ui.selection->new_x2 = ui.selection->bbox.right;
695     gnome_canvas_item_set(ui.selection->canvas_item, "dash", NULL, NULL);
696     update_cursor_for_resize(pt);
697     return TRUE;
698   }
699   return FALSE;
700 }
701
702
703 void start_vertspace(GdkEvent *event)
704 {
705   double pt[2];
706   GList *itemlist;
707   struct Item *item;
708
709   reset_selection();
710   ui.cur_item_type = ITEM_MOVESEL_VERT;
711   ui.selection = g_new(struct Selection, 1);
712   ui.selection->type = ITEM_MOVESEL_VERT;
713   ui.selection->items = NULL;
714   ui.selection->layer = ui.cur_layer;
715
716   get_pointer_coords(event, pt);
717   ui.selection->bbox.top = ui.selection->bbox.bottom = pt[1];
718   for (itemlist = ui.cur_layer->items; itemlist!=NULL; itemlist = itemlist->next) {
719     item = (struct Item *)itemlist->data;
720     if (item->bbox.top >= pt[1]) {
721       ui.selection->items = g_list_append(ui.selection->items, item); 
722       if (item->bbox.bottom > ui.selection->bbox.bottom)
723         ui.selection->bbox.bottom = item->bbox.bottom;
724     }
725   }
726
727   ui.selection->anchor_x = ui.selection->last_x = 0;
728   ui.selection->anchor_y = ui.selection->last_y = pt[1];
729   ui.selection->orig_pageno = ui.pageno;
730   ui.selection->move_pageno = ui.pageno;
731   ui.selection->move_layer = ui.selection->layer;
732   ui.selection->move_pagedelta = 0.;
733   ui.selection->canvas_item = gnome_canvas_item_new(ui.cur_layer->group,
734       gnome_canvas_rect_get_type(), "width-pixels", 1, 
735       "outline-color-rgba", 0x000000ff,
736       "fill-color-rgba", 0x80808040,
737       "x1", -100.0, "x2", ui.cur_page->width+100, "y1", pt[1], "y2", pt[1], NULL);
738   update_cursor();
739 }
740
741 void continue_movesel(GdkEvent *event)
742 {
743   double pt[2], dx, dy, upmargin;
744   GList *list;
745   struct Item *item;
746   int tmppageno;
747   struct Page *tmppage;
748   
749   get_pointer_coords(event, pt);
750   if (ui.cur_item_type == ITEM_MOVESEL_VERT) pt[0] = 0;
751   pt[1] += ui.selection->move_pagedelta;
752
753   // check for page jumps
754   if (ui.cur_item_type == ITEM_MOVESEL_VERT)
755     upmargin = ui.selection->bbox.bottom - ui.selection->bbox.top;
756   else upmargin = VIEW_CONTINUOUS_SKIP;
757   tmppageno = ui.selection->move_pageno;
758   tmppage = g_list_nth_data(journal.pages, tmppageno);
759   while (ui.view_continuous && (pt[1] < - upmargin)) {
760     if (tmppageno == 0) break;
761     tmppageno--;
762     tmppage = g_list_nth_data(journal.pages, tmppageno);
763     pt[1] += tmppage->height + VIEW_CONTINUOUS_SKIP;
764     ui.selection->move_pagedelta += tmppage->height + VIEW_CONTINUOUS_SKIP;
765   }
766   while (ui.view_continuous && (pt[1] > tmppage->height+VIEW_CONTINUOUS_SKIP)) {
767     if (tmppageno == journal.npages-1) break;
768     pt[1] -= tmppage->height + VIEW_CONTINUOUS_SKIP;
769     ui.selection->move_pagedelta -= tmppage->height + VIEW_CONTINUOUS_SKIP;
770     tmppageno++;
771     tmppage = g_list_nth_data(journal.pages, tmppageno);
772   }
773   
774   if (tmppageno != ui.selection->move_pageno) {
775     // move to a new page !
776     ui.selection->move_pageno = tmppageno;
777     if (tmppageno == ui.selection->orig_pageno)
778       ui.selection->move_layer = ui.selection->layer;
779     else
780       ui.selection->move_layer = (struct Layer *)(g_list_last(
781         ((struct Page *)g_list_nth_data(journal.pages, tmppageno))->layers)->data);
782     gnome_canvas_item_reparent(ui.selection->canvas_item, ui.selection->move_layer->group);
783     for (list = ui.selection->items; list!=NULL; list = list->next) {
784       item = (struct Item *)list->data;
785       if (item->canvas_item!=NULL)
786         gnome_canvas_item_reparent(item->canvas_item, ui.selection->move_layer->group);
787     }
788     // avoid a refresh bug
789     gnome_canvas_item_move(GNOME_CANVAS_ITEM(ui.selection->move_layer->group), 0., 0.);
790     if (ui.cur_item_type == ITEM_MOVESEL_VERT)
791       gnome_canvas_item_set(ui.selection->canvas_item,
792         "x2", tmppage->width+100, 
793         "y1", ui.selection->anchor_y+ui.selection->move_pagedelta, NULL);
794   }
795   
796   // now, process things normally
797
798   dx = pt[0] - ui.selection->last_x;
799   dy = pt[1] - ui.selection->last_y;
800   if (hypot(dx,dy) < 1) return; // don't move subpixel
801   ui.selection->last_x = pt[0];
802   ui.selection->last_y = pt[1];
803
804   // move the canvas items
805   if (ui.cur_item_type == ITEM_MOVESEL_VERT)
806     gnome_canvas_item_set(ui.selection->canvas_item, "y2", pt[1], NULL);
807   else 
808     gnome_canvas_item_move(ui.selection->canvas_item, dx, dy);
809   
810   for (list = ui.selection->items; list != NULL; list = list->next) {
811     item = (struct Item *)list->data;
812     if (item->canvas_item != NULL)
813       gnome_canvas_item_move(item->canvas_item, dx, dy);
814   }
815 }
816
817 void continue_resizesel(GdkEvent *event)
818 {
819   double pt[2];
820
821   get_pointer_coords(event, pt);
822
823   if (ui.selection->resizing_top) ui.selection->new_y1 = pt[1];
824   if (ui.selection->resizing_bottom) ui.selection->new_y2 = pt[1];
825   if (ui.selection->resizing_left) ui.selection->new_x1 = pt[0];
826   if (ui.selection->resizing_right) ui.selection->new_x2 = pt[0];
827
828   gnome_canvas_item_set(ui.selection->canvas_item, 
829     "x1", ui.selection->new_x1, "x2", ui.selection->new_x2,
830     "y1", ui.selection->new_y1, "y2", ui.selection->new_y2, NULL);
831 }
832
833 void finalize_movesel(void)
834 {
835   GList *list, *link;
836   
837   if (ui.selection->items != NULL) {
838     prepare_new_undo();
839     undo->type = ITEM_MOVESEL;
840     undo->itemlist = g_list_copy(ui.selection->items);
841     undo->val_x = ui.selection->last_x - ui.selection->anchor_x;
842     undo->val_y = ui.selection->last_y - ui.selection->anchor_y;
843     undo->layer = ui.selection->layer;
844     undo->layer2 = ui.selection->move_layer;
845     undo->auxlist = NULL;
846     // build auxlist = pointers to Item's just before ours (for depths)
847     for (list = ui.selection->items; list!=NULL; list = list->next) {
848       link = g_list_find(ui.selection->layer->items, list->data);
849       if (link!=NULL) link = link->prev;
850       undo->auxlist = g_list_append(undo->auxlist, ((link!=NULL) ? link->data : NULL));
851     }
852     ui.selection->layer = ui.selection->move_layer;
853     move_journal_items_by(undo->itemlist, undo->val_x, undo->val_y,
854                           undo->layer, undo->layer2, 
855                           (undo->layer == undo->layer2)?undo->auxlist:NULL);
856   }
857
858   if (ui.selection->move_pageno!=ui.selection->orig_pageno) 
859     do_switch_page(ui.selection->move_pageno, FALSE, FALSE);
860     
861   if (ui.cur_item_type == ITEM_MOVESEL_VERT)
862     reset_selection();
863   else {
864     ui.selection->bbox.left += undo->val_x;
865     ui.selection->bbox.right += undo->val_x;
866     ui.selection->bbox.top += undo->val_y;
867     ui.selection->bbox.bottom += undo->val_y;
868     make_dashed(ui.selection->canvas_item);
869     /* update selection box object's offset to be trivial, and its internal 
870        coordinates to agree with those of the bbox; need this since resize
871        operations will modify the box by setting its coordinates directly */
872     gnome_canvas_item_affine_absolute(ui.selection->canvas_item, NULL);
873     gnome_canvas_item_set(ui.selection->canvas_item, 
874       "x1", ui.selection->bbox.left, "x2", ui.selection->bbox.right,
875       "y1", ui.selection->bbox.top, "y2", ui.selection->bbox.bottom, NULL);
876   }
877   ui.cur_item_type = ITEM_NONE;
878   update_cursor();
879 }
880
881 #define SCALING_EPSILON 0.001
882
883 void finalize_resizesel(void)
884 {
885   struct Item *item;
886
887   // build the affine transformation
888   double offset_x, offset_y, scaling_x, scaling_y;
889   scaling_x = (ui.selection->new_x2 - ui.selection->new_x1) / 
890               (ui.selection->bbox.right - ui.selection->bbox.left);
891   scaling_y = (ui.selection->new_y2 - ui.selection->new_y1) /
892               (ui.selection->bbox.bottom - ui.selection->bbox.top);
893   // couldn't undo a resize-by-zero...
894   if (fabs(scaling_x)<SCALING_EPSILON) scaling_x = SCALING_EPSILON;
895   if (fabs(scaling_y)<SCALING_EPSILON) scaling_y = SCALING_EPSILON;
896   offset_x = ui.selection->new_x1 - ui.selection->bbox.left * scaling_x;
897   offset_y = ui.selection->new_y1 - ui.selection->bbox.top * scaling_y;
898
899   if (ui.selection->items != NULL) {
900     // create the undo information
901     prepare_new_undo();
902     undo->type = ITEM_RESIZESEL;
903     undo->itemlist = g_list_copy(ui.selection->items);
904     undo->auxlist = NULL;
905
906     undo->scaling_x = scaling_x;
907     undo->scaling_y = scaling_y;
908     undo->val_x = offset_x;
909     undo->val_y = offset_y;
910
911     // actually do the resize operation
912     resize_journal_items_by(ui.selection->items, scaling_x, scaling_y, offset_x, offset_y);
913   }
914
915   if (scaling_x>0) {
916     ui.selection->bbox.left = ui.selection->new_x1;
917     ui.selection->bbox.right = ui.selection->new_x2;
918   } else {
919     ui.selection->bbox.left = ui.selection->new_x2;
920     ui.selection->bbox.right = ui.selection->new_x1;
921   }
922   if (scaling_y>0) {
923     ui.selection->bbox.top = ui.selection->new_y1;
924     ui.selection->bbox.bottom = ui.selection->new_y2;
925   } else {
926     ui.selection->bbox.top = ui.selection->new_y2;
927     ui.selection->bbox.bottom = ui.selection->new_y1;
928   }
929   make_dashed(ui.selection->canvas_item);
930
931   ui.cur_item_type = ITEM_NONE;
932   update_cursor();
933 }
934
935 void selection_delete(void)
936 {
937   struct UndoErasureData *erasure;
938   GList *itemlist;
939   struct Item *item;
940   
941   if (ui.selection == NULL) return;
942   prepare_new_undo();
943   undo->type = ITEM_ERASURE;
944   undo->layer = ui.selection->layer;
945   undo->erasurelist = NULL;
946   for (itemlist = ui.selection->items; itemlist!=NULL; itemlist = itemlist->next) {
947     item = (struct Item *)itemlist->data;
948     if (item->canvas_item!=NULL)
949       gtk_object_destroy(GTK_OBJECT(item->canvas_item));
950     erasure = g_new(struct UndoErasureData, 1);
951     erasure->item = item;
952     erasure->npos = g_list_index(ui.selection->layer->items, item);
953     erasure->nrepl = 0;
954     erasure->replacement_items = NULL;
955     ui.selection->layer->items = g_list_remove(ui.selection->layer->items, item);
956     ui.selection->layer->nitems--;
957     undo->erasurelist = g_list_prepend(undo->erasurelist, erasure);
958   }
959   reset_selection();
960
961   /* NOTE: the erasurelist is built backwards; this guarantees that,
962      upon undo, the erasure->npos fields give the correct position
963      where each item should be reinserted as the list is traversed in
964      the forward direction */
965 }
966
967 // modify the color or thickness of pen strokes in a selection
968
969 void recolor_selection(int color_no, guint color_rgba)
970 {
971   GList *itemlist;
972   struct Item *item;
973   struct Brush *brush;
974   GnomeCanvasGroup *group;
975   
976   if (ui.selection == NULL) return;
977   prepare_new_undo();
978   undo->type = ITEM_REPAINTSEL;
979   undo->itemlist = NULL;
980   undo->auxlist = NULL;
981   for (itemlist = ui.selection->items; itemlist!=NULL; itemlist = itemlist->next) {
982     item = (struct Item *)itemlist->data;
983     if (item->type != ITEM_STROKE && item->type != ITEM_TEXT) continue;
984     if (item->type == ITEM_STROKE && item->brush.tool_type!=TOOL_PEN) continue;
985     // store info for undo
986     undo->itemlist = g_list_append(undo->itemlist, item);
987     brush = (struct Brush *)g_malloc(sizeof(struct Brush));
988     g_memmove(brush, &(item->brush), sizeof(struct Brush));
989     undo->auxlist = g_list_append(undo->auxlist, brush);
990     // repaint the stroke
991     item->brush.color_no = color_no;
992     item->brush.color_rgba = color_rgba | 0xff; // no alpha
993     if (item->canvas_item!=NULL) {
994       if (!item->brush.variable_width)
995         gnome_canvas_item_set(item->canvas_item, 
996            "fill-color-rgba", item->brush.color_rgba, NULL);
997       else {
998         group = (GnomeCanvasGroup *) item->canvas_item->parent;
999         gtk_object_destroy(GTK_OBJECT(item->canvas_item));
1000         make_canvas_item_one(group, item);
1001       }
1002     }
1003   }
1004 }
1005
1006 void rethicken_selection(int val)
1007 {
1008   GList *itemlist;
1009   struct Item *item;
1010   struct Brush *brush;
1011   GnomeCanvasGroup *group;
1012   
1013   if (ui.selection == NULL) return;
1014   prepare_new_undo();
1015   undo->type = ITEM_REPAINTSEL;
1016   undo->itemlist = NULL;
1017   undo->auxlist = NULL;
1018   for (itemlist = ui.selection->items; itemlist!=NULL; itemlist = itemlist->next) {
1019     item = (struct Item *)itemlist->data;
1020     if (item->type != ITEM_STROKE || item->brush.tool_type!=TOOL_PEN) continue;
1021     // store info for undo
1022     undo->itemlist = g_list_append(undo->itemlist, item);
1023     brush = (struct Brush *)g_malloc(sizeof(struct Brush));
1024     g_memmove(brush, &(item->brush), sizeof(struct Brush));
1025     undo->auxlist = g_list_append(undo->auxlist, brush);
1026     // repaint the stroke
1027     item->brush.thickness_no = val;
1028     item->brush.thickness = predef_thickness[TOOL_PEN][val];
1029     if (item->canvas_item!=NULL) {
1030       if (!item->brush.variable_width)
1031         gnome_canvas_item_set(item->canvas_item, 
1032            "width-units", item->brush.thickness, NULL);
1033       else {
1034         group = (GnomeCanvasGroup *) item->canvas_item->parent;
1035         gtk_object_destroy(GTK_OBJECT(item->canvas_item));
1036         item->brush.variable_width = FALSE;
1037         make_canvas_item_one(group, item);
1038       }
1039     }
1040   }
1041 }
1042
1043 gboolean do_hand_scrollto(gpointer data)
1044 {
1045   ui.hand_scrollto_pending = FALSE;
1046   gnome_canvas_scroll_to(canvas, ui.hand_scrollto_cx, ui.hand_scrollto_cy);
1047   return FALSE;
1048 }
1049
1050 void do_hand(GdkEvent *event)
1051 {
1052   double pt[2];
1053   int cx, cy;
1054   
1055   get_pointer_coords(event, pt);
1056   pt[0] += ui.cur_page->hoffset;
1057   pt[1] += ui.cur_page->voffset;
1058   gnome_canvas_get_scroll_offsets(canvas, &cx, &cy);
1059   ui.hand_scrollto_cx = cx - (pt[0]-ui.hand_refpt[0])*ui.zoom;
1060   ui.hand_scrollto_cy = cy - (pt[1]-ui.hand_refpt[1])*ui.zoom;
1061   if (!ui.hand_scrollto_pending) g_idle_add(do_hand_scrollto, NULL);
1062   ui.hand_scrollto_pending = TRUE;
1063 }
1064
1065 /************ TEXT FUNCTIONS **************/
1066
1067 // to make it easier to copy/paste at end of text box
1068 #define WIDGET_RIGHT_MARGIN 10
1069
1070 void resize_textview(gpointer *toplevel, gpointer *data)
1071 {
1072   GtkTextView *w;
1073   int width, height;
1074   
1075   /* when the text changes, resize the GtkTextView accordingly */
1076   if (ui.cur_item_type!=ITEM_TEXT) return;
1077   w = GTK_TEXT_VIEW(ui.cur_item->widget);
1078   width = w->width + WIDGET_RIGHT_MARGIN;
1079   height = w->height;
1080   gnome_canvas_item_set(ui.cur_item->canvas_item, 
1081     "size-pixels", TRUE, 
1082     "width", (gdouble)width, "height", (gdouble)height, NULL);
1083   ui.cur_item->bbox.right = ui.cur_item->bbox.left + width/ui.zoom;
1084   ui.cur_item->bbox.bottom = ui.cur_item->bbox.top + height/ui.zoom;
1085 }
1086
1087 void start_text(GdkEvent *event, struct Item *item)
1088 {
1089   double pt[2];
1090   GtkTextBuffer *buffer;
1091   GnomeCanvasItem *canvas_item;
1092   PangoFontDescription *font_desc;
1093   GdkColor color;
1094
1095   get_pointer_coords(event, pt);
1096
1097   ui.cur_item_type = ITEM_TEXT;
1098
1099   if (item==NULL) {
1100     item = g_new(struct Item, 1);
1101     item->text = NULL;
1102     item->canvas_item = NULL;
1103     item->bbox.left = pt[0];
1104     item->bbox.top = pt[1];
1105     item->bbox.right = ui.cur_page->width;
1106     item->bbox.bottom = pt[1]+100.;
1107     item->font_name = g_strdup(ui.font_name);
1108     item->font_size = ui.font_size;
1109     g_memmove(&(item->brush), ui.cur_brush, sizeof(struct Brush));
1110     ui.cur_layer->items = g_list_append(ui.cur_layer->items, item);
1111     ui.cur_layer->nitems++;
1112   }
1113   
1114   item->type = ITEM_TEMP_TEXT;
1115   ui.cur_item = item;
1116   
1117   font_desc = pango_font_description_from_string(item->font_name);
1118   pango_font_description_set_absolute_size(font_desc, 
1119       item->font_size*ui.zoom*PANGO_SCALE);
1120   item->widget = gtk_text_view_new();
1121   buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(item->widget));
1122   if (item->text!=NULL)
1123     gtk_text_buffer_set_text(buffer, item->text, -1);
1124   gtk_widget_modify_font(item->widget, font_desc);
1125   rgb_to_gdkcolor(item->brush.color_rgba, &color);
1126   gtk_widget_modify_text(item->widget, GTK_STATE_NORMAL, &color);
1127   pango_font_description_free(font_desc);
1128
1129   canvas_item = gnome_canvas_item_new(ui.cur_layer->group,
1130     gnome_canvas_widget_get_type(),
1131     "x", item->bbox.left, "y", item->bbox.top, 
1132     "width", item->bbox.right-item->bbox.left, 
1133     "height", item->bbox.bottom-item->bbox.top,
1134     "widget", item->widget, NULL);
1135   // TODO: width/height?
1136   if (item->canvas_item!=NULL) {
1137     lower_canvas_item_to(ui.cur_layer->group, canvas_item, item->canvas_item);
1138     gtk_object_destroy(GTK_OBJECT(item->canvas_item));
1139   }
1140   item->canvas_item = canvas_item;
1141
1142   gtk_widget_show(item->widget);
1143   ui.resize_signal_handler = 
1144     g_signal_connect((gpointer) winMain, "check_resize",
1145        G_CALLBACK(resize_textview), NULL);
1146   update_font_button();
1147   gtk_widget_set_sensitive(GET_COMPONENT("editPaste"), FALSE);
1148   gtk_widget_set_sensitive(GET_COMPONENT("buttonPaste"), FALSE);
1149   gtk_widget_grab_focus(item->widget); 
1150 }
1151
1152 void end_text(void)
1153 {
1154   GtkTextBuffer *buffer;
1155   GtkTextIter start, end;
1156   gchar *new_text;
1157   struct UndoErasureData *erasure;
1158   GnomeCanvasItem *tmpitem;
1159
1160   if (ui.cur_item_type!=ITEM_TEXT) return; // nothing for us to do!
1161
1162   // finalize the text that's been edited... 
1163   buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(ui.cur_item->widget));
1164   gtk_text_buffer_get_bounds(buffer, &start, &end);
1165   ui.cur_item->type = ITEM_TEXT;
1166   new_text = gtk_text_buffer_get_text(buffer, &start, &end, TRUE);
1167   ui.cur_item_type = ITEM_NONE;
1168   gtk_widget_set_sensitive(GET_COMPONENT("editPaste"), TRUE);
1169   gtk_widget_set_sensitive(GET_COMPONENT("buttonPaste"), TRUE);
1170   
1171   if (strlen(new_text)==0) { // erase object and cancel
1172     g_free(new_text);
1173     g_signal_handler_disconnect(winMain, ui.resize_signal_handler);
1174     gtk_object_destroy(GTK_OBJECT(ui.cur_item->canvas_item));
1175     ui.cur_item->canvas_item = NULL;
1176     if (ui.cur_item->text == NULL) // nothing happened
1177       g_free(ui.cur_item->font_name);
1178     else { // treat this as an erasure
1179       prepare_new_undo();
1180       undo->type = ITEM_ERASURE;
1181       undo->layer = ui.cur_layer;
1182       erasure = (struct UndoErasureData *)g_malloc(sizeof(struct UndoErasureData));
1183       erasure->item = ui.cur_item;
1184       erasure->npos = g_list_index(ui.cur_layer->items, ui.cur_item);
1185       erasure->nrepl = 0;
1186       erasure->replacement_items = NULL;
1187       undo->erasurelist = g_list_append(NULL, erasure);
1188     }
1189     ui.cur_layer->items = g_list_remove(ui.cur_layer->items, ui.cur_item);
1190     ui.cur_layer->nitems--;
1191     ui.cur_item = NULL;
1192     return;
1193   }
1194
1195   // store undo data
1196   if (ui.cur_item->text==NULL || strcmp(ui.cur_item->text, new_text)) {
1197     prepare_new_undo();
1198     if (ui.cur_item->text == NULL) undo->type = ITEM_TEXT; 
1199     else undo->type = ITEM_TEXT_EDIT;
1200     undo->layer = ui.cur_layer;
1201     undo->item = ui.cur_item;
1202     undo->str = ui.cur_item->text;
1203   }
1204   else g_free(ui.cur_item->text);
1205
1206   ui.cur_item->text = new_text;
1207   ui.cur_item->widget = NULL;
1208   // replace the canvas item
1209   tmpitem = ui.cur_item->canvas_item;
1210   make_canvas_item_one(ui.cur_layer->group, ui.cur_item);
1211   update_item_bbox(ui.cur_item);
1212   lower_canvas_item_to(ui.cur_layer->group, ui.cur_item->canvas_item, tmpitem);
1213   gtk_object_destroy(GTK_OBJECT(tmpitem));
1214 }
1215
1216 /* update the items in the canvas so they're of the right font size */
1217
1218 void update_text_item_displayfont(struct Item *item)
1219 {
1220   PangoFontDescription *font_desc;
1221
1222   if (item->type != ITEM_TEXT && item->type != ITEM_TEMP_TEXT) return;
1223   if (item->canvas_item==NULL) return;
1224   font_desc = pango_font_description_from_string(item->font_name);
1225   pango_font_description_set_absolute_size(font_desc, 
1226         item->font_size*ui.zoom*PANGO_SCALE);
1227   if (item->type == ITEM_TEMP_TEXT)
1228     gtk_widget_modify_font(item->widget, font_desc);
1229   else {
1230     gnome_canvas_item_set(item->canvas_item, "font-desc", font_desc, NULL);
1231     update_item_bbox(item);
1232   }
1233   pango_font_description_free(font_desc);
1234 }
1235
1236 void rescale_text_items(void)
1237 {
1238   GList *pagelist, *layerlist, *itemlist;
1239   
1240   for (pagelist = journal.pages; pagelist!=NULL; pagelist = pagelist->next)
1241     for (layerlist = ((struct Page *)pagelist->data)->layers; layerlist!=NULL; layerlist = layerlist->next)
1242       for (itemlist = ((struct Layer *)layerlist->data)->items; itemlist!=NULL; itemlist = itemlist->next)
1243         update_text_item_displayfont((struct Item *)itemlist->data);
1244 }
1245
1246 struct Item *click_is_in_text(struct Layer *layer, double x, double y)
1247 {
1248   GList *itemlist;
1249   struct Item *item, *val;
1250   
1251   val = NULL;
1252   for (itemlist = layer->items; itemlist!=NULL; itemlist = itemlist->next) {
1253     item = (struct Item *)itemlist->data;
1254     if (item->type != ITEM_TEXT) continue;
1255     if (x<item->bbox.left || x>item->bbox.right) continue;
1256     if (y<item->bbox.top || y>item->bbox.bottom) continue;
1257     val = item;
1258   }
1259   return val;
1260 }
1261
1262 void refont_text_item(struct Item *item, gchar *font_name, double font_size)
1263 {
1264   if (!strcmp(font_name, item->font_name) && font_size==item->font_size) return;
1265   if (item->text!=NULL) {
1266     prepare_new_undo();
1267     undo->type = ITEM_TEXT_ATTRIB;
1268     undo->item = item;
1269     undo->str = item->font_name;
1270     undo->val_x = item->font_size;
1271     undo->brush = (struct Brush *)g_memdup(&(item->brush), sizeof(struct Brush));
1272   }
1273   else g_free(item->font_name);
1274   item->font_name = g_strdup(font_name);
1275   if (font_size>0.) item->font_size = font_size;
1276   update_text_item_displayfont(item);
1277 }
1278
1279 void process_font_sel(gchar *str)
1280 {
1281   gchar *p, *q;
1282   struct Item *it;
1283   gdouble size;
1284   GList *list;
1285   gboolean undo_cont;
1286
1287   p = strrchr(str, ' ');
1288   if (p!=NULL) { 
1289     size = g_strtod(p+1, &q);
1290     if (*q!=0 || size<1.) size=0.;
1291     else *p=0;
1292   }
1293   else size=0.;
1294   g_free(ui.font_name);
1295   ui.font_name = str;  
1296   if (size>0.) ui.font_size = size;
1297   undo_cont = FALSE;   
1298   // if there's a current text item, re-font it
1299   if (ui.cur_item_type == ITEM_TEXT) {
1300     refont_text_item(ui.cur_item, str, size);
1301     undo_cont = (ui.cur_item->text!=NULL);   
1302   }
1303   // if there's a current selection, re-font it
1304   if (ui.selection!=NULL) 
1305     for (list=ui.selection->items; list!=NULL; list=list->next) {
1306       it = (struct Item *)list->data;
1307       if (it->type == ITEM_TEXT) {   
1308         if (undo_cont) undo->multiop |= MULTIOP_CONT_REDO;
1309         refont_text_item(it, str, size);
1310         if (undo_cont) undo->multiop |= MULTIOP_CONT_UNDO;
1311         undo_cont = TRUE;
1312       }
1313     }  
1314   update_font_button();
1315 }