]> git.donarmstrong.com Git - xournal.git/blob - src/xo-paint.c
Initial revision
[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   if (GTK_WIDGET(canvas)->window == NULL) return;
59   
60   if (ui.cursor!=NULL) { 
61     gdk_cursor_unref(ui.cursor);
62     ui.cursor = NULL;
63   }
64   if (ui.toolno == TOOL_PEN) {
65     fg.red = (ui.cur_brush->color_rgba >> 16) & 0xff00;
66     fg.green = (ui.cur_brush->color_rgba >> 8) & 0xff00;
67     fg.blue = (ui.cur_brush->color_rgba >> 0) & 0xff00;
68     source = gdk_bitmap_create_from_data(NULL, cursor_pen_bits, 16, 16);
69     ui.cursor = gdk_cursor_new_from_pixmap(source, source, &fg, &bg, 7, 7);
70     gdk_bitmap_unref(source);
71   }
72   else if (ui.toolno == TOOL_ERASER) {
73     source = gdk_bitmap_create_from_data(NULL, cursor_eraser_bits, 16, 16);
74     mask = gdk_bitmap_create_from_data(NULL, cursor_eraser_mask, 16, 16);
75     ui.cursor = gdk_cursor_new_from_pixmap(source, mask, &fg, &bg, 7, 7);
76     gdk_bitmap_unref(source);
77     gdk_bitmap_unref(mask);
78   }
79   else if (ui.toolno == TOOL_HIGHLIGHTER) {
80     source = gdk_bitmap_create_from_data(NULL, cursor_eraser_bits, 16, 16);
81     mask = gdk_bitmap_create_from_data(NULL, cursor_eraser_mask, 16, 16);
82     bg.red = (ui.cur_brush->color_rgba >> 16) & 0xff00;
83     bg.green = (ui.cur_brush->color_rgba >> 8) & 0xff00;
84     bg.blue = (ui.cur_brush->color_rgba >> 0) & 0xff00;
85     ui.cursor = gdk_cursor_new_from_pixmap(source, mask, &fg, &bg, 7, 7);
86     gdk_bitmap_unref(source);
87     gdk_bitmap_unref(mask);
88   }
89   else if (ui.cur_item_type == ITEM_MOVESEL) {
90     if (ui.toolno == TOOL_VERTSPACE) 
91       ui.cursor = gdk_cursor_new(GDK_SB_V_DOUBLE_ARROW);
92     else 
93       ui.cursor = gdk_cursor_new(GDK_FLEUR);
94   }
95   else if (ui.cur_item_type == ITEM_SELECTRECT) {
96     ui.cursor = gdk_cursor_new(GDK_TCROSS);
97   }
98   
99   gdk_window_set_cursor(GTK_WIDGET(canvas)->window, ui.cursor);
100 }
101
102
103 /************** painting strokes *************/
104
105 #define SUBDIVIDE_MAXDIST 5.0
106
107 void subdivide_cur_path(null)
108 {
109   int n, pieces, k;
110   double *p;
111   double x0, y0, x1, y1;
112
113   for (n=0, p=ui.cur_path.coords; n<ui.cur_path.num_points-1; n++, p+=2) {
114     pieces = (int)floor(hypot(p[2]-p[0], p[3]-p[1])/SUBDIVIDE_MAXDIST);
115     if (pieces>1) {
116       x0 = p[0]; y0 = p[1];
117       x1 = p[2]; y1 = p[3];
118       realloc_cur_path(ui.cur_path.num_points+pieces-1);
119       g_memmove(ui.cur_path.coords+2*(n+pieces), ui.cur_path.coords+2*(n+1),
120                     2*(ui.cur_path.num_points-n-1)*sizeof(double));
121       p = ui.cur_path.coords+2*n;
122       ui.cur_path.num_points += pieces-1;
123       n += (pieces-1);
124       for (k=1; k<pieces; k++) {
125         p+=2;
126         p[0] = x0 + k*(x1-x0)/pieces;
127         p[1] = y0 + k*(y1-y0)/pieces;
128       } 
129     }
130   }
131 }
132
133 void create_new_stroke(GdkEvent *event)
134 {
135   ui.cur_item_type = ITEM_STROKE;
136   ui.cur_item = g_new(struct Item, 1);
137   ui.cur_item->type = ITEM_STROKE;
138   g_memmove(&(ui.cur_item->brush), ui.cur_brush, sizeof(struct Brush));
139   ui.cur_item->path = &ui.cur_path;
140   realloc_cur_path(2);
141   ui.cur_path.num_points = 1;
142   get_pointer_coords(event, ui.cur_path.coords);
143   
144   if (ui.ruler) 
145     ui.cur_item->canvas_item = gnome_canvas_item_new(ui.cur_layer->group,
146       gnome_canvas_line_get_type(),
147       "cap-style", GDK_CAP_ROUND, "join-style", GDK_JOIN_ROUND,
148       "fill-color-rgba", ui.cur_item->brush.color_rgba,
149       "width-units", ui.cur_item->brush.thickness, NULL);
150   else
151     ui.cur_item->canvas_item = gnome_canvas_item_new(
152       ui.cur_layer->group, gnome_canvas_group_get_type(), NULL);
153 }
154
155 void continue_stroke(GdkEvent *event)
156 {
157   GnomeCanvasPoints seg;
158   double *pt;
159
160   if (ui.ruler) {
161     pt = ui.cur_path.coords;
162   } else {
163     realloc_cur_path(ui.cur_path.num_points+1);
164     pt = ui.cur_path.coords + 2*(ui.cur_path.num_points-1);
165   } 
166   
167   get_pointer_coords(event, pt+2);
168   
169   if (ui.ruler)
170     ui.cur_path.num_points = 2;
171   else {
172     if (hypot(pt[0]-pt[2], pt[1]-pt[3]) < PIXEL_MOTION_THRESHOLD/ui.zoom)
173       return;  // not a meaningful motion
174     ui.cur_path.num_points++;
175   }
176
177   seg.coords = pt; 
178   seg.num_points = 2;
179   seg.ref_count = 1;
180   
181   /* note: we're using a piece of the cur_path array. This is ok because
182      upon creation the line just copies the contents of the GnomeCanvasPoints
183      into an internal structure */
184
185   if (ui.ruler)
186     gnome_canvas_item_set(ui.cur_item->canvas_item, "points", &seg, NULL);
187   else
188     gnome_canvas_item_new((GnomeCanvasGroup *)ui.cur_item->canvas_item,
189        gnome_canvas_line_get_type(), "points", &seg,
190        "cap-style", GDK_CAP_ROUND, "join-style", GDK_JOIN_ROUND,
191        "fill-color-rgba", ui.cur_item->brush.color_rgba,
192        "width-units", ui.cur_item->brush.thickness, NULL);
193 }
194
195 void finalize_stroke(void)
196 {
197   if (ui.cur_path.num_points == 1) { // GnomeCanvas doesn't like num_points=1
198     ui.cur_path.coords[2] = ui.cur_path.coords[0]+0.1;
199     ui.cur_path.coords[3] = ui.cur_path.coords[1];
200     ui.cur_path.num_points = 2;
201   }
202   
203   subdivide_cur_path(); // split the segment so eraser will work
204
205   ui.cur_item->path = gnome_canvas_points_new(ui.cur_path.num_points);
206   g_memmove(ui.cur_item->path->coords, ui.cur_path.coords, 
207       2*ui.cur_path.num_points*sizeof(double));
208   update_item_bbox(ui.cur_item);
209   ui.cur_path.num_points = 0;
210
211   // destroy the entire group of temporary line segments
212   gtk_object_destroy(GTK_OBJECT(ui.cur_item->canvas_item));
213   // make a new line item to replace it
214   ui.cur_item->canvas_item = gnome_canvas_item_new(ui.cur_layer->group, 
215      gnome_canvas_line_get_type(), "points", ui.cur_item->path,
216      "cap-style", GDK_CAP_ROUND, "join-style", GDK_JOIN_ROUND,
217      "fill-color-rgba", ui.cur_item->brush.color_rgba,
218      "width-units", ui.cur_item->brush.thickness, NULL);
219
220   // add undo information
221   prepare_new_undo();
222   undo->type = ITEM_STROKE;
223   undo->item = ui.cur_item;
224   undo->layer = ui.cur_layer;
225
226   // store the item on top of the layer stack
227   ui.cur_layer->items = g_list_append(ui.cur_layer->items, ui.cur_item);
228   ui.cur_layer->nitems++;
229   ui.cur_item = NULL;
230   ui.cur_item_type = ITEM_NONE;
231 }
232
233 /************** eraser tool *************/
234
235 void erase_stroke_portions(struct Item *item, double x, double y, double radius,
236                    gboolean whole_strokes, struct UndoErasureData *erasure)
237 {
238   int i;
239   double *pt;
240   struct Item *newhead, *newtail;
241   gboolean need_recalc = FALSE;
242
243   for (i=0, pt=item->path->coords; i<item->path->num_points; i++, pt+=2) {
244     if (hypot(pt[0]-x, pt[1]-y) <= radius) { // found an intersection
245       // FIXME: need to test if line SEGMENT hits the circle
246       // hide the canvas item, and create erasure data if needed
247       if (erasure == NULL) {
248         item->type = ITEM_TEMP_STROKE;
249         gnome_canvas_item_hide(item->canvas_item);  
250             /*  we'll use this hidden item as an insertion point later */
251         erasure = (struct UndoErasureData *)g_malloc(sizeof(struct UndoErasureData));
252         item->erasure = erasure;
253         erasure->item = item;
254         erasure->npos = g_list_index(ui.cur_layer->items, item);
255         erasure->nrepl = 0;
256         erasure->replacement_items = NULL;
257       }
258       // split the stroke
259       newhead = newtail = NULL;
260       if (!whole_strokes) {
261         if (i>=2) {
262           newhead = (struct Item *)g_malloc(sizeof(struct Item));
263           newhead->type = ITEM_STROKE;
264           g_memmove(&newhead->brush, &item->brush, sizeof(struct Brush));
265           newhead->path = gnome_canvas_points_new(i);
266           g_memmove(newhead->path->coords, item->path->coords, 2*i*sizeof(double));
267         }
268         while (++i < item->path->num_points) {
269           pt+=2;
270           if (hypot(pt[0]-x, pt[1]-y) > radius) break;
271         }
272         if (i<item->path->num_points-1) {
273           newtail = (struct Item *)g_malloc(sizeof(struct Item));
274           newtail->type = ITEM_STROKE;
275           g_memmove(&newtail->brush, &item->brush, sizeof(struct Brush));
276           newtail->path = gnome_canvas_points_new(item->path->num_points-i);
277           g_memmove(newtail->path->coords, item->path->coords+2*i, 
278                            2*(item->path->num_points-i)*sizeof(double));
279           newtail->canvas_item = NULL;
280         }
281       }
282       if (item->type == ITEM_STROKE) { 
283         // it's inside an erasure list - we destroy it
284         gnome_canvas_points_free(item->path);
285         if (item->canvas_item != NULL) 
286           gtk_object_destroy(GTK_OBJECT(item->canvas_item));
287         erasure->nrepl--;
288         erasure->replacement_items = g_list_remove(erasure->replacement_items, item);
289         g_free(item);
290       }
291       // add the new head
292       if (newhead != NULL) {
293         update_item_bbox(newhead);
294         newhead->canvas_item = gnome_canvas_item_new(ui.cur_layer->group,
295             gnome_canvas_line_get_type(), "points", newhead->path,
296             "cap-style", GDK_CAP_ROUND, "join-style", GDK_JOIN_ROUND,
297             "fill-color-rgba", newhead->brush.color_rgba,
298             "width-units", newhead->brush.thickness, NULL);
299         lower_canvas_item_to(ui.cur_layer->group,
300                   newhead->canvas_item, erasure->item->canvas_item);
301         erasure->replacement_items = g_list_prepend(erasure->replacement_items, newhead);
302         erasure->nrepl++;
303         // prepending ensures it won't get processed twice
304       }
305       // recurse into the new tail
306       need_recalc = (newtail!=NULL);
307       if (newtail == NULL) break;
308       item = newtail;
309       erasure->replacement_items = g_list_prepend(erasure->replacement_items, newtail);
310       erasure->nrepl++;
311       i=0; pt=item->path->coords;
312     }
313   }
314   // add the tail if needed
315   if (!need_recalc) return;
316   update_item_bbox(item);
317   item->canvas_item = gnome_canvas_item_new(ui.cur_layer->group,
318        gnome_canvas_line_get_type(), "points", item->path,
319        "cap-style", GDK_CAP_ROUND, "join-style", GDK_JOIN_ROUND,
320        "fill-color-rgba", item->brush.color_rgba,
321        "width-units", item->brush.thickness, NULL);
322   lower_canvas_item_to(ui.cur_layer->group, item->canvas_item, 
323                                       erasure->item->canvas_item);
324 }
325
326
327 void do_eraser(GdkEvent *event, double radius, gboolean whole_strokes)
328 {
329   struct Item *item, *repl;
330   GList *itemlist, *repllist;
331   double pos[2];
332   struct BBox eraserbox;
333   
334   get_pointer_coords(event, pos);
335   eraserbox.left = pos[0]-radius;
336   eraserbox.right = pos[0]+radius;
337   eraserbox.top = pos[1]-radius;
338   eraserbox.bottom = pos[1]+radius;
339   for (itemlist = ui.cur_layer->items; itemlist!=NULL; itemlist = itemlist->next) {
340     item = (struct Item *)itemlist->data;
341     if (item->type == ITEM_STROKE) {
342       if (!have_intersect(&(item->bbox), &eraserbox)) continue;
343       erase_stroke_portions(item, pos[0], pos[1], radius, whole_strokes, NULL);
344     } else if (item->type == ITEM_TEMP_STROKE) {
345       repllist = item->erasure->replacement_items;
346       while (repllist!=NULL) {
347         repl = (struct Item *)repllist->data;
348           // we may delete the item soon! so advance now in the list
349         repllist = repllist->next; 
350         if (have_intersect(&(repl->bbox), &eraserbox))
351           erase_stroke_portions(repl, pos[0], pos[1], radius, whole_strokes, item->erasure);
352       }
353     }
354   }
355 }
356
357 void finalize_erasure(void)
358 {
359   GList *itemlist, *partlist;
360   struct Item *item, *part;
361   
362   prepare_new_undo();
363   undo->type = ITEM_ERASURE;
364   undo->layer = ui.cur_layer;
365   undo->erasurelist = NULL;
366   
367   itemlist = ui.cur_layer->items;
368   while (itemlist!=NULL) {
369     item = (struct Item *)itemlist->data;
370     itemlist = itemlist->next;
371     if (item->type != ITEM_TEMP_STROKE) continue;
372     item->type = ITEM_STROKE;
373     ui.cur_layer->items = g_list_remove(ui.cur_layer->items, item);
374     // the item has an invisible canvas item, which used to act as anchor
375     if (item->canvas_item!=NULL) {
376       gtk_object_destroy(GTK_OBJECT(item->canvas_item));
377       item->canvas_item = NULL;
378     }
379     undo->erasurelist = g_list_append(undo->erasurelist, item->erasure);
380     // add the new strokes into the current layer
381     for (partlist = item->erasure->replacement_items; partlist!=NULL; partlist = partlist->next)
382       ui.cur_layer->items = g_list_insert_before(
383                       ui.cur_layer->items, itemlist, partlist->data);
384     ui.cur_layer->nitems += item->erasure->nrepl-1;
385   }
386     
387   ui.cur_item = NULL;
388   ui.cur_item_type = ITEM_NONE;
389   
390   /* NOTE: the list of erasures goes in the depth order of the layer;
391      this guarantees that, upon undo, the erasure->npos fields give the
392      correct position where each item should be reinserted as the list
393      is traversed in the forward direction */
394 }
395
396 /************ selection tools ***********/
397
398 void make_dashed(GnomeCanvasItem *item)
399 {
400   double dashlen[2];
401   ArtVpathDash dash;
402   
403   dash.n_dash = 2;
404   dash.offset = 3.0;
405   dash.dash = dashlen;
406   dashlen[0] = dashlen[1] = 6.0;
407   gnome_canvas_item_set(item, "dash", &dash, NULL);
408 }
409
410
411 void start_selectrect(GdkEvent *event)
412 {
413   double pt[2];
414   reset_selection();
415   
416   ui.cur_item_type = ITEM_SELECTRECT;
417   ui.selection = g_new(struct Selection, 1);
418   ui.selection->type = ITEM_SELECTRECT;
419   ui.selection->items = NULL;
420   ui.selection->layer = ui.cur_layer;
421
422   get_pointer_coords(event, pt);
423   ui.selection->bbox.left = ui.selection->bbox.right = pt[0];
424   ui.selection->bbox.top = ui.selection->bbox.bottom = pt[1];
425  
426   ui.selection->canvas_item = gnome_canvas_item_new(ui.cur_layer->group,
427       gnome_canvas_rect_get_type(), "width-pixels", 1, 
428       "outline-color-rgba", 0x000000ff,
429       "fill-color-rgba", 0x80808040,
430       "x1", pt[0], "x2", pt[0], "y1", pt[1], "y2", pt[1], NULL);
431   update_cursor();
432 }
433
434 void finalize_selectrect(void)
435 {
436   double x1, x2, y1, y2;
437   GList *itemlist;
438   struct Item *item;
439
440   
441   ui.cur_item_type = ITEM_NONE;
442
443   if (ui.selection->bbox.left > ui.selection->bbox.right) {
444     x1 = ui.selection->bbox.right;  x2 = ui.selection->bbox.left;
445     ui.selection->bbox.left = x1;   ui.selection->bbox.right = x2;
446   } else {
447     x1 = ui.selection->bbox.left;  x2 = ui.selection->bbox.right;
448   }
449
450   if (ui.selection->bbox.top > ui.selection->bbox.bottom) {
451     y1 = ui.selection->bbox.bottom;  y2 = ui.selection->bbox.top;
452     ui.selection->bbox.top = y1;   ui.selection->bbox.bottom = y2;
453   } else {
454     y1 = ui.selection->bbox.top;  y2 = ui.selection->bbox.bottom;
455   }
456   
457   for (itemlist = ui.selection->layer->items; itemlist!=NULL; itemlist = itemlist->next) {
458     item = (struct Item *)itemlist->data;
459     if (item->bbox.left >= x1 && item->bbox.right <= x2 &&
460           item->bbox.top >= y1 && item->bbox.bottom <= y2) {
461       ui.selection->items = g_list_append(ui.selection->items, item); 
462     }
463   }
464   
465   if (ui.selection->items == NULL) reset_selection();
466   else make_dashed(ui.selection->canvas_item);
467   update_cursor();
468   update_copy_paste_enabled();
469 }
470
471 gboolean start_movesel(GdkEvent *event)
472 {
473   double pt[2];
474   
475   if (ui.selection==NULL) return FALSE;
476   if (ui.cur_layer != ui.selection->layer) return FALSE;
477   
478   get_pointer_coords(event, pt);
479   if (ui.selection->type == ITEM_SELECTRECT) {
480     if (pt[0]<ui.selection->bbox.left || pt[0]>ui.selection->bbox.right ||
481         pt[1]<ui.selection->bbox.top  || pt[1]>ui.selection->bbox.bottom)
482       return FALSE;
483     ui.cur_item_type = ITEM_MOVESEL;
484     ui.selection->anchor_x = ui.selection->last_x = pt[0];
485     ui.selection->anchor_y = ui.selection->last_y = pt[1];
486     gnome_canvas_item_set(ui.selection->canvas_item, "dash", NULL, NULL);
487     update_cursor();
488     return TRUE;
489   }
490   return FALSE;
491 }
492
493 void start_vertspace(GdkEvent *event)
494 {
495   double pt[2];
496   GList *itemlist;
497   struct Item *item;
498
499   reset_selection();
500   ui.cur_item_type = ITEM_MOVESEL;
501   ui.selection = g_new(struct Selection, 1);
502   ui.selection->type = ITEM_MOVESEL;
503   ui.selection->items = NULL;
504   ui.selection->layer = ui.cur_layer;
505
506   get_pointer_coords(event, pt);
507   for (itemlist = ui.cur_layer->items; itemlist!=NULL; itemlist = itemlist->next) {
508     item = (struct Item *)itemlist->data;
509     if (item->bbox.top >= pt[1]) {
510       ui.selection->items = g_list_append(ui.selection->items, item); 
511     }
512   }
513
514   ui.selection->anchor_x = ui.selection->last_x = 0;
515   ui.selection->anchor_y = ui.selection->last_y = pt[1];
516   ui.selection->canvas_item = gnome_canvas_item_new(ui.cur_layer->group,
517       gnome_canvas_rect_get_type(), "width-pixels", 1, 
518       "outline-color-rgba", 0x000000ff,
519       "fill-color-rgba", 0x80808040,
520       "x1", -100.0, "x2", ui.cur_page->width+100, "y1", pt[1], "y2", pt[1], NULL);
521   update_cursor();
522 }
523
524 void continue_movesel(GdkEvent *event)
525 {
526   double pt[2], dx, dy;
527   GList *list;
528   struct Item *item;
529   
530   get_pointer_coords(event, pt);
531   if (ui.toolno == TOOL_VERTSPACE) pt[0] = 0;
532   dx = pt[0] - ui.selection->last_x;
533   dy = pt[1] - ui.selection->last_y;
534   
535   if (hypot(dx,dy) < 1) return; // don't move subpixel
536   ui.selection->last_x = pt[0];
537   ui.selection->last_y = pt[1];
538   
539   // move the canvas items
540   if (ui.toolno == TOOL_VERTSPACE)
541     gnome_canvas_item_set(ui.selection->canvas_item, "y2", pt[1], NULL);
542   else 
543     gnome_canvas_item_move(ui.selection->canvas_item, dx, dy);
544   
545   for (list = ui.selection->items; list != NULL; list = list->next) {
546     item = (struct Item *)list->data;
547     if (item->canvas_item != NULL)
548       gnome_canvas_item_move(item->canvas_item, dx, dy);
549   }
550   
551   /* consider: if view_continuous, move items to a different page if
552      y value gets out of range (same algo as in button_down event
553      processing); then need to reparent the canvas items, delete the
554      Items from the old Layer, insert them on the new Layer, ...
555      and make an undo-event, probably cut-and-paste style... */
556 }
557
558 void finalize_movesel(void)
559 {
560   if (ui.selection->items != NULL) {
561     prepare_new_undo();
562     undo->type = ITEM_MOVESEL;
563     undo->itemlist = g_list_copy(ui.selection->items);
564     undo->val_x = ui.selection->last_x - ui.selection->anchor_x;
565     undo->val_y = ui.selection->last_y - ui.selection->anchor_y;
566     move_journal_items_by(undo->itemlist, undo->val_x, undo->val_y);
567   }
568
569   if (ui.toolno == TOOL_VERTSPACE)
570     reset_selection();
571   else {
572     ui.selection->bbox.left += undo->val_x;
573     ui.selection->bbox.right += undo->val_x;
574     ui.selection->bbox.top += undo->val_y;
575     ui.selection->bbox.bottom += undo->val_y;
576     make_dashed(ui.selection->canvas_item);
577   }
578   ui.cur_item_type = ITEM_NONE;
579   update_cursor();
580 }
581
582
583 void selection_delete(void)
584 {
585   struct UndoErasureData *erasure;
586   GList *itemlist;
587   struct Item *item;
588   
589   if (ui.selection == NULL) return;
590   prepare_new_undo();
591   undo->type = ITEM_ERASURE;
592   undo->layer = ui.selection->layer;
593   undo->erasurelist = NULL;
594   for (itemlist = ui.selection->items; itemlist!=NULL; itemlist = itemlist->next) {
595     item = (struct Item *)itemlist->data;
596     if (item->canvas_item!=NULL)
597       gtk_object_destroy(GTK_OBJECT(item->canvas_item));
598     erasure = g_new(struct UndoErasureData, 1);
599     erasure->item = item;
600     erasure->npos = g_list_index(ui.selection->layer->items, item);
601     erasure->nrepl = 0;
602     erasure->replacement_items = NULL;
603     ui.selection->layer->items = g_list_remove(ui.selection->layer->items, item);
604     ui.selection->layer->nitems--;
605     undo->erasurelist = g_list_prepend(undo->erasurelist, erasure);
606   }
607   reset_selection();
608
609   /* NOTE: the erasurelist is built backwards; this guarantees that,
610      upon undo, the erasure->npos fields give the correct position
611      where each item should be reinserted as the list is traversed in
612      the forward direction */
613 }
614
615 void callback_clipboard_get(GtkClipboard *clipboard,
616                             GtkSelectionData *selection_data,
617                             guint info, gpointer user_data)
618 {
619   int length;
620   
621   g_memmove(&length, user_data, sizeof(int));
622   gtk_selection_data_set(selection_data,
623      gdk_atom_intern("_XOURNAL", FALSE), 8, user_data, length);
624 }
625
626 void callback_clipboard_clear(GtkClipboard *clipboard, gpointer user_data)
627 {
628   g_free(user_data);
629 }
630
631 void selection_to_clip(void)
632 {
633   int bufsz, nitems;
634   char *buf, *p;
635   GList *list;
636   struct Item *item;
637   GtkTargetEntry target;
638   
639   if (ui.selection == NULL) return;
640   bufsz = 2*sizeof(int) // bufsz, nitems
641         + sizeof(struct BBox); // bbox
642   nitems = 0;
643   for (list = ui.selection->items; list != NULL; list = list->next) {
644     item = (struct Item *)list->data;
645     nitems++;
646     if (item->type == ITEM_STROKE) {
647       bufsz+= sizeof(int) // type
648             + sizeof(struct Brush) // brush
649             + sizeof(int) // num_points
650             + 2*item->path->num_points*sizeof(double); // the points
651     }
652     else bufsz+= sizeof(int); // type
653   }
654   p = buf = g_malloc(bufsz);
655   g_memmove(p, &bufsz, sizeof(int)); p+= sizeof(int);
656   g_memmove(p, &nitems, sizeof(int)); p+= sizeof(int);
657   g_memmove(p, &ui.selection->bbox, sizeof(struct BBox)); p+= sizeof(struct BBox);
658   for (list = ui.selection->items; list != NULL; list = list->next) {
659     item = (struct Item *)list->data;
660     g_memmove(p, &item->type, sizeof(int)); p+= sizeof(int);
661     if (item->type == ITEM_STROKE) {
662       g_memmove(p, &item->brush, sizeof(struct Brush)); p+= sizeof(struct Brush);
663       g_memmove(p, &item->path->num_points, sizeof(int)); p+= sizeof(int);
664       g_memmove(p, item->path->coords, 2*item->path->num_points*sizeof(double));
665       p+= 2*item->path->num_points*sizeof(double);
666     }
667   }
668   
669   target.target = "_XOURNAL";
670   target.flags = 0;
671   target.info = 0;
672   
673   gtk_clipboard_set_with_data(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD), 
674        &target, 1,
675        callback_clipboard_get, callback_clipboard_clear, buf);
676 }
677
678
679 void clipboard_paste(void)
680 {
681   GtkSelectionData *sel_data;
682   unsigned char *p;
683   int nitems, npts, i;
684   GList *list;
685   struct Item *item;
686   double hoffset, voffset, cx, cy;
687   double *pf;
688   int sx, sy, wx, wy;
689   
690   if (ui.cur_layer == NULL) return;
691   
692   ui.cur_item_type = ITEM_PASTE;
693   sel_data = gtk_clipboard_wait_for_contents(
694       gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
695       gdk_atom_intern("_XOURNAL", FALSE));
696   ui.cur_item_type = ITEM_NONE;
697   if (sel_data == NULL) return; // paste failed
698   
699   reset_selection();
700   ui.toolno = TOOL_SELECTRECT;
701   ui.ruler = FALSE;
702   update_tool_buttons();
703   update_tool_menu();
704   update_color_menu();
705   update_cursor();
706   
707   ui.selection = g_new(struct Selection, 1);
708   p = sel_data->data + sizeof(int);
709   g_memmove(&nitems, p, sizeof(int)); p+= sizeof(int);
710   ui.selection->type = ITEM_SELECTRECT;
711   ui.selection->layer = ui.cur_layer;
712   g_memmove(&ui.selection->bbox, p, sizeof(struct BBox)); p+= sizeof(struct BBox);
713   ui.selection->items = NULL;
714   
715   // find by how much we translate the pasted selection
716   gnome_canvas_get_scroll_offsets(canvas, &sx, &sy);
717   gdk_window_get_geometry(GTK_WIDGET(canvas)->window, NULL, NULL, &wx, &wy, NULL);
718   gnome_canvas_window_to_world(canvas, sx + wx/2, sy + wy/2, &cx, &cy);
719   cx -= ui.cur_page->hoffset;
720   cy -= ui.cur_page->voffset;
721   if (cx + (ui.selection->bbox.right-ui.selection->bbox.left)/2 > ui.cur_page->width)
722     cx = ui.cur_page->width - (ui.selection->bbox.right-ui.selection->bbox.left)/2;
723   if (cx - (ui.selection->bbox.right-ui.selection->bbox.left)/2 < 0)
724     cx = (ui.selection->bbox.right-ui.selection->bbox.left)/2;
725   if (cy + (ui.selection->bbox.bottom-ui.selection->bbox.top)/2 > ui.cur_page->height)
726     cy = ui.cur_page->height - (ui.selection->bbox.bottom-ui.selection->bbox.top)/2;
727   if (cy - (ui.selection->bbox.bottom-ui.selection->bbox.top)/2 < 0)
728     cy = (ui.selection->bbox.bottom-ui.selection->bbox.top)/2;
729   hoffset = cx - (ui.selection->bbox.right+ui.selection->bbox.left)/2;
730   voffset = cy - (ui.selection->bbox.top+ui.selection->bbox.bottom)/2;
731   ui.selection->bbox.left += hoffset;
732   ui.selection->bbox.right += hoffset;
733   ui.selection->bbox.top += voffset;
734   ui.selection->bbox.bottom += voffset;
735
736   ui.selection->canvas_item = gnome_canvas_item_new(ui.cur_layer->group,
737       gnome_canvas_rect_get_type(), "width-pixels", 1,
738       "outline-color-rgba", 0x000000ff,
739       "fill-color-rgba", 0x80808040,
740       "x1", ui.selection->bbox.left, "x2", ui.selection->bbox.right, 
741       "y1", ui.selection->bbox.top, "y2", ui.selection->bbox.bottom, NULL);
742   make_dashed(ui.selection->canvas_item);
743
744   while (nitems-- > 0) {
745     item = g_new(struct Item, 1);
746     ui.selection->items = g_list_append(ui.selection->items, item);
747     ui.cur_layer->items = g_list_append(ui.cur_layer->items, item);
748     ui.cur_layer->nitems++;
749     g_memmove(&item->type, p, sizeof(int)); p+= sizeof(int);
750     if (item->type == ITEM_STROKE) {
751       g_memmove(&item->brush, p, sizeof(struct Brush)); p+= sizeof(struct Brush);
752       g_memmove(&npts, p, sizeof(int)); p+= sizeof(int);
753       item->path = gnome_canvas_points_new(npts);
754       pf = (double *)p;
755       for (i=0; i<npts; i++) {
756         item->path->coords[2*i] = pf[2*i] + hoffset;
757         item->path->coords[2*i+1] = pf[2*i+1] + voffset;
758       }
759       p+= 2*item->path->num_points*sizeof(double);
760       update_item_bbox(item);
761       item->canvas_item = gnome_canvas_item_new(ui.cur_layer->group,
762              gnome_canvas_line_get_type(), "points", item->path,
763              "cap-style", GDK_CAP_ROUND, "join-style", GDK_JOIN_ROUND,
764              "fill-color-rgba", item->brush.color_rgba,
765              "width-units", item->brush.thickness, NULL);
766     }
767   }
768
769   prepare_new_undo();
770   undo->type = ITEM_PASTE;
771   undo->layer = ui.cur_layer;
772   undo->itemlist = g_list_copy(ui.selection->items);  
773   
774   gtk_selection_data_free(sel_data);
775   update_copy_paste_enabled();
776 }