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