]> git.donarmstrong.com Git - xournal.git/blob - src/xo-paint.c
5d19c1b9a03718ca858804b0602611791454c5b8
[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.cur_item_type == ITEM_MOVESEL) {
65     if (ui.toolno[ui.cur_mapping] == TOOL_VERTSPACE) 
66       ui.cursor = gdk_cursor_new(GDK_SB_V_DOUBLE_ARROW);
67     else 
68       ui.cursor = gdk_cursor_new(GDK_FLEUR);
69   }
70   else if (ui.toolno[ui.cur_mapping] == TOOL_PEN) {
71     fg.red = (ui.cur_brush->color_rgba >> 16) & 0xff00;
72     fg.green = (ui.cur_brush->color_rgba >> 8) & 0xff00;
73     fg.blue = (ui.cur_brush->color_rgba >> 0) & 0xff00;
74     source = gdk_bitmap_create_from_data(NULL, cursor_pen_bits, 16, 16);
75     ui.cursor = gdk_cursor_new_from_pixmap(source, source, &fg, &bg, 7, 7);
76     gdk_bitmap_unref(source);
77   }
78   else if (ui.toolno[ui.cur_mapping] == TOOL_ERASER) {
79     source = gdk_bitmap_create_from_data(NULL, cursor_eraser_bits, 16, 16);
80     mask = gdk_bitmap_create_from_data(NULL, cursor_eraser_mask, 16, 16);
81     ui.cursor = gdk_cursor_new_from_pixmap(source, mask, &fg, &bg, 7, 7);
82     gdk_bitmap_unref(source);
83     gdk_bitmap_unref(mask);
84   }
85   else if (ui.toolno[ui.cur_mapping] == TOOL_HIGHLIGHTER) {
86     source = gdk_bitmap_create_from_data(NULL, cursor_eraser_bits, 16, 16);
87     mask = gdk_bitmap_create_from_data(NULL, cursor_eraser_mask, 16, 16);
88     bg.red = (ui.cur_brush->color_rgba >> 16) & 0xff00;
89     bg.green = (ui.cur_brush->color_rgba >> 8) & 0xff00;
90     bg.blue = (ui.cur_brush->color_rgba >> 0) & 0xff00;
91     ui.cursor = gdk_cursor_new_from_pixmap(source, mask, &fg, &bg, 7, 7);
92     gdk_bitmap_unref(source);
93     gdk_bitmap_unref(mask);
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[ui.cur_mapping]) 
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[ui.cur_mapping]) {
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[ui.cur_mapping])
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[ui.cur_mapping])
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     ui.selection->orig_pageno = ui.pageno;
487     ui.selection->move_pageno = ui.pageno;
488     ui.selection->move_layer = ui.selection->layer;
489     ui.selection->move_pagedelta = 0.;
490     gnome_canvas_item_set(ui.selection->canvas_item, "dash", NULL, NULL);
491     update_cursor();
492     return TRUE;
493   }
494   return FALSE;
495 }
496
497 void start_vertspace(GdkEvent *event)
498 {
499   double pt[2];
500   GList *itemlist;
501   struct Item *item;
502
503   reset_selection();
504   ui.cur_item_type = ITEM_MOVESEL_VERT;
505   ui.selection = g_new(struct Selection, 1);
506   ui.selection->type = ITEM_MOVESEL_VERT;
507   ui.selection->items = NULL;
508   ui.selection->layer = ui.cur_layer;
509
510   get_pointer_coords(event, pt);
511   ui.selection->bbox.top = ui.selection->bbox.bottom = pt[1];
512   for (itemlist = ui.cur_layer->items; itemlist!=NULL; itemlist = itemlist->next) {
513     item = (struct Item *)itemlist->data;
514     if (item->bbox.top >= pt[1]) {
515       ui.selection->items = g_list_append(ui.selection->items, item); 
516       if (item->bbox.bottom > ui.selection->bbox.bottom)
517         ui.selection->bbox.bottom = item->bbox.bottom;
518     }
519   }
520
521   ui.selection->anchor_x = ui.selection->last_x = 0;
522   ui.selection->anchor_y = ui.selection->last_y = pt[1];
523   ui.selection->orig_pageno = ui.pageno;
524   ui.selection->move_pageno = ui.pageno;
525   ui.selection->move_layer = ui.selection->layer;
526   ui.selection->move_pagedelta = 0.;
527   ui.selection->canvas_item = gnome_canvas_item_new(ui.cur_layer->group,
528       gnome_canvas_rect_get_type(), "width-pixels", 1, 
529       "outline-color-rgba", 0x000000ff,
530       "fill-color-rgba", 0x80808040,
531       "x1", -100.0, "x2", ui.cur_page->width+100, "y1", pt[1], "y2", pt[1], NULL);
532   update_cursor();
533 }
534
535 void continue_movesel(GdkEvent *event)
536 {
537   double pt[2], dx, dy, upmargin;
538   GList *list;
539   struct Item *item;
540   int tmppageno;
541   struct Page *tmppage;
542   
543   get_pointer_coords(event, pt);
544   if (ui.cur_item_type == ITEM_MOVESEL_VERT) pt[0] = 0;
545   pt[1] += ui.selection->move_pagedelta;
546
547   // check for page jumps
548   if (ui.cur_item_type == ITEM_MOVESEL_VERT)
549     upmargin = ui.selection->bbox.bottom - ui.selection->bbox.top;
550   else upmargin = VIEW_CONTINUOUS_SKIP;
551   tmppageno = ui.selection->move_pageno;
552   tmppage = g_list_nth_data(journal.pages, tmppageno);
553   while (ui.view_continuous && (pt[1] < - upmargin)) {
554     if (tmppageno == 0) break;
555     tmppageno--;
556     tmppage = g_list_nth_data(journal.pages, tmppageno);
557     pt[1] += tmppage->height + VIEW_CONTINUOUS_SKIP;
558     ui.selection->move_pagedelta += tmppage->height + VIEW_CONTINUOUS_SKIP;
559   }
560   while (ui.view_continuous && (pt[1] > tmppage->height+VIEW_CONTINUOUS_SKIP)) {
561     if (tmppageno == journal.npages-1) break;
562     pt[1] -= tmppage->height + VIEW_CONTINUOUS_SKIP;
563     ui.selection->move_pagedelta -= tmppage->height + VIEW_CONTINUOUS_SKIP;
564     tmppageno++;
565     tmppage = g_list_nth_data(journal.pages, tmppageno);
566   }
567   
568   if (tmppageno != ui.selection->move_pageno) {
569     // move to a new page !
570     ui.selection->move_pageno = tmppageno;
571     if (tmppageno == ui.selection->orig_pageno)
572       ui.selection->move_layer = ui.selection->layer;
573     else
574       ui.selection->move_layer = (struct Layer *)(g_list_last(
575         ((struct Page *)g_list_nth_data(journal.pages, tmppageno))->layers)->data);
576     gnome_canvas_item_reparent(ui.selection->canvas_item, ui.selection->move_layer->group);
577     for (list = ui.selection->items; list!=NULL; list = list->next) {
578       item = (struct Item *)list->data;
579       if (item->canvas_item!=NULL)
580         gnome_canvas_item_reparent(item->canvas_item, ui.selection->move_layer->group);
581     }
582     // avoid a refresh bug
583     gnome_canvas_item_move(GNOME_CANVAS_ITEM(ui.selection->move_layer->group), 0., 0.);
584     if (ui.cur_item_type == ITEM_MOVESEL_VERT)
585       gnome_canvas_item_set(ui.selection->canvas_item,
586         "x2", tmppage->width+100, 
587         "y1", ui.selection->anchor_y+ui.selection->move_pagedelta, NULL);
588   }
589   
590   // now, process things normally
591
592   dx = pt[0] - ui.selection->last_x;
593   dy = pt[1] - ui.selection->last_y;
594   if (hypot(dx,dy) < 1) return; // don't move subpixel
595   ui.selection->last_x = pt[0];
596   ui.selection->last_y = pt[1];
597
598   // move the canvas items
599   if (ui.cur_item_type == ITEM_MOVESEL_VERT)
600     gnome_canvas_item_set(ui.selection->canvas_item, "y2", pt[1], NULL);
601   else 
602     gnome_canvas_item_move(ui.selection->canvas_item, dx, dy);
603   
604   for (list = ui.selection->items; list != NULL; list = list->next) {
605     item = (struct Item *)list->data;
606     if (item->canvas_item != NULL)
607       gnome_canvas_item_move(item->canvas_item, dx, dy);
608   }
609 }
610
611 void finalize_movesel(void)
612 {
613   GList *list, *link;
614   struct Item *item;
615   
616   if (ui.selection->items != NULL) {
617     prepare_new_undo();
618     undo->type = ITEM_MOVESEL;
619     undo->itemlist = g_list_copy(ui.selection->items);
620     undo->val_x = ui.selection->last_x - ui.selection->anchor_x;
621     undo->val_y = ui.selection->last_y - ui.selection->anchor_y;
622     undo->layer = ui.selection->layer;
623     undo->layer2 = ui.selection->move_layer;
624     undo->auxlist = NULL;
625     // build auxlist = pointers to Item's just before ours (for depths)
626     for (list = ui.selection->items; list!=NULL; list = list->next) {
627       link = g_list_find(ui.selection->layer->items, list->data);
628       if (link!=NULL) link = link->prev;
629       undo->auxlist = g_list_append(undo->auxlist, ((link!=NULL) ? link->data : NULL));
630     }
631     ui.selection->layer = ui.selection->move_layer;
632     move_journal_items_by(undo->itemlist, undo->val_x, undo->val_y,
633                           undo->layer, undo->layer2, 
634                           (undo->layer == undo->layer2)?undo->auxlist:NULL);
635   }
636
637   if (ui.selection->move_pageno!=ui.selection->orig_pageno) 
638     do_switch_page(ui.selection->move_pageno, FALSE, FALSE);
639     
640   if (ui.cur_item_type == ITEM_MOVESEL_VERT)
641     reset_selection();
642   else {
643     ui.selection->bbox.left += undo->val_x;
644     ui.selection->bbox.right += undo->val_x;
645     ui.selection->bbox.top += undo->val_y;
646     ui.selection->bbox.bottom += undo->val_y;
647     make_dashed(ui.selection->canvas_item);
648   }
649   ui.cur_item_type = ITEM_NONE;
650   update_cursor();
651 }
652
653
654 void selection_delete(void)
655 {
656   struct UndoErasureData *erasure;
657   GList *itemlist;
658   struct Item *item;
659   
660   if (ui.selection == NULL) return;
661   prepare_new_undo();
662   undo->type = ITEM_ERASURE;
663   undo->layer = ui.selection->layer;
664   undo->erasurelist = NULL;
665   for (itemlist = ui.selection->items; itemlist!=NULL; itemlist = itemlist->next) {
666     item = (struct Item *)itemlist->data;
667     if (item->canvas_item!=NULL)
668       gtk_object_destroy(GTK_OBJECT(item->canvas_item));
669     erasure = g_new(struct UndoErasureData, 1);
670     erasure->item = item;
671     erasure->npos = g_list_index(ui.selection->layer->items, item);
672     erasure->nrepl = 0;
673     erasure->replacement_items = NULL;
674     ui.selection->layer->items = g_list_remove(ui.selection->layer->items, item);
675     ui.selection->layer->nitems--;
676     undo->erasurelist = g_list_prepend(undo->erasurelist, erasure);
677   }
678   reset_selection();
679
680   /* NOTE: the erasurelist is built backwards; this guarantees that,
681      upon undo, the erasure->npos fields give the correct position
682      where each item should be reinserted as the list is traversed in
683      the forward direction */
684 }
685
686 void callback_clipboard_get(GtkClipboard *clipboard,
687                             GtkSelectionData *selection_data,
688                             guint info, gpointer user_data)
689 {
690   int length;
691   
692   g_memmove(&length, user_data, sizeof(int));
693   gtk_selection_data_set(selection_data,
694      gdk_atom_intern("_XOURNAL", FALSE), 8, user_data, length);
695 }
696
697 void callback_clipboard_clear(GtkClipboard *clipboard, gpointer user_data)
698 {
699   g_free(user_data);
700 }
701
702 void selection_to_clip(void)
703 {
704   int bufsz, nitems;
705   char *buf, *p;
706   GList *list;
707   struct Item *item;
708   GtkTargetEntry target;
709   
710   if (ui.selection == NULL) return;
711   bufsz = 2*sizeof(int) // bufsz, nitems
712         + sizeof(struct BBox); // bbox
713   nitems = 0;
714   for (list = ui.selection->items; list != NULL; list = list->next) {
715     item = (struct Item *)list->data;
716     nitems++;
717     if (item->type == ITEM_STROKE) {
718       bufsz+= sizeof(int) // type
719             + sizeof(struct Brush) // brush
720             + sizeof(int) // num_points
721             + 2*item->path->num_points*sizeof(double); // the points
722     }
723     else bufsz+= sizeof(int); // type
724   }
725   p = buf = g_malloc(bufsz);
726   g_memmove(p, &bufsz, sizeof(int)); p+= sizeof(int);
727   g_memmove(p, &nitems, sizeof(int)); p+= sizeof(int);
728   g_memmove(p, &ui.selection->bbox, sizeof(struct BBox)); p+= sizeof(struct BBox);
729   for (list = ui.selection->items; list != NULL; list = list->next) {
730     item = (struct Item *)list->data;
731     g_memmove(p, &item->type, sizeof(int)); p+= sizeof(int);
732     if (item->type == ITEM_STROKE) {
733       g_memmove(p, &item->brush, sizeof(struct Brush)); p+= sizeof(struct Brush);
734       g_memmove(p, &item->path->num_points, sizeof(int)); p+= sizeof(int);
735       g_memmove(p, item->path->coords, 2*item->path->num_points*sizeof(double));
736       p+= 2*item->path->num_points*sizeof(double);
737     }
738   }
739   
740   target.target = "_XOURNAL";
741   target.flags = 0;
742   target.info = 0;
743   
744   gtk_clipboard_set_with_data(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD), 
745        &target, 1,
746        callback_clipboard_get, callback_clipboard_clear, buf);
747 }
748
749
750 void clipboard_paste(void)
751 {
752   GtkSelectionData *sel_data;
753   unsigned char *p;
754   int nitems, npts, i;
755   GList *list;
756   struct Item *item;
757   double hoffset, voffset, cx, cy;
758   double *pf;
759   int sx, sy, wx, wy;
760   
761   if (ui.cur_layer == NULL) return;
762   
763   ui.cur_item_type = ITEM_PASTE;
764   sel_data = gtk_clipboard_wait_for_contents(
765       gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
766       gdk_atom_intern("_XOURNAL", FALSE));
767   ui.cur_item_type = ITEM_NONE;
768   if (sel_data == NULL) return; // paste failed
769   
770   reset_selection();
771   
772   ui.selection = g_new(struct Selection, 1);
773   p = sel_data->data + sizeof(int);
774   g_memmove(&nitems, p, sizeof(int)); p+= sizeof(int);
775   ui.selection->type = ITEM_SELECTRECT;
776   ui.selection->layer = ui.cur_layer;
777   g_memmove(&ui.selection->bbox, p, sizeof(struct BBox)); p+= sizeof(struct BBox);
778   ui.selection->items = NULL;
779   
780   // find by how much we translate the pasted selection
781   gnome_canvas_get_scroll_offsets(canvas, &sx, &sy);
782   gdk_window_get_geometry(GTK_WIDGET(canvas)->window, NULL, NULL, &wx, &wy, NULL);
783   gnome_canvas_window_to_world(canvas, sx + wx/2, sy + wy/2, &cx, &cy);
784   cx -= ui.cur_page->hoffset;
785   cy -= ui.cur_page->voffset;
786   if (cx + (ui.selection->bbox.right-ui.selection->bbox.left)/2 > ui.cur_page->width)
787     cx = ui.cur_page->width - (ui.selection->bbox.right-ui.selection->bbox.left)/2;
788   if (cx - (ui.selection->bbox.right-ui.selection->bbox.left)/2 < 0)
789     cx = (ui.selection->bbox.right-ui.selection->bbox.left)/2;
790   if (cy + (ui.selection->bbox.bottom-ui.selection->bbox.top)/2 > ui.cur_page->height)
791     cy = ui.cur_page->height - (ui.selection->bbox.bottom-ui.selection->bbox.top)/2;
792   if (cy - (ui.selection->bbox.bottom-ui.selection->bbox.top)/2 < 0)
793     cy = (ui.selection->bbox.bottom-ui.selection->bbox.top)/2;
794   hoffset = cx - (ui.selection->bbox.right+ui.selection->bbox.left)/2;
795   voffset = cy - (ui.selection->bbox.top+ui.selection->bbox.bottom)/2;
796   ui.selection->bbox.left += hoffset;
797   ui.selection->bbox.right += hoffset;
798   ui.selection->bbox.top += voffset;
799   ui.selection->bbox.bottom += voffset;
800
801   ui.selection->canvas_item = gnome_canvas_item_new(ui.cur_layer->group,
802       gnome_canvas_rect_get_type(), "width-pixels", 1,
803       "outline-color-rgba", 0x000000ff,
804       "fill-color-rgba", 0x80808040,
805       "x1", ui.selection->bbox.left, "x2", ui.selection->bbox.right, 
806       "y1", ui.selection->bbox.top, "y2", ui.selection->bbox.bottom, NULL);
807   make_dashed(ui.selection->canvas_item);
808
809   while (nitems-- > 0) {
810     item = g_new(struct Item, 1);
811     ui.selection->items = g_list_append(ui.selection->items, item);
812     ui.cur_layer->items = g_list_append(ui.cur_layer->items, item);
813     ui.cur_layer->nitems++;
814     g_memmove(&item->type, p, sizeof(int)); p+= sizeof(int);
815     if (item->type == ITEM_STROKE) {
816       g_memmove(&item->brush, p, sizeof(struct Brush)); p+= sizeof(struct Brush);
817       g_memmove(&npts, p, sizeof(int)); p+= sizeof(int);
818       item->path = gnome_canvas_points_new(npts);
819       pf = (double *)p;
820       for (i=0; i<npts; i++) {
821         item->path->coords[2*i] = pf[2*i] + hoffset;
822         item->path->coords[2*i+1] = pf[2*i+1] + voffset;
823       }
824       p+= 2*item->path->num_points*sizeof(double);
825       update_item_bbox(item);
826       item->canvas_item = gnome_canvas_item_new(ui.cur_layer->group,
827              gnome_canvas_line_get_type(), "points", item->path,
828              "cap-style", GDK_CAP_ROUND, "join-style", GDK_JOIN_ROUND,
829              "fill-color-rgba", item->brush.color_rgba,
830              "width-units", item->brush.thickness, NULL);
831     }
832   }
833
834   prepare_new_undo();
835   undo->type = ITEM_PASTE;
836   undo->layer = ui.cur_layer;
837   undo->itemlist = g_list_copy(ui.selection->items);  
838   
839   gtk_selection_data_free(sel_data);
840   update_copy_paste_enabled();
841 }
842
843 // modify the color or thickness of pen strokes in a selection
844
845 void recolor_selection(int color)
846 {
847   GList *itemlist;
848   struct Item *item;
849   struct Brush *brush;
850   
851   if (ui.selection == NULL) return;
852   prepare_new_undo();
853   undo->type = ITEM_REPAINTSEL;
854   undo->itemlist = NULL;
855   undo->auxlist = NULL;
856   for (itemlist = ui.selection->items; itemlist!=NULL; itemlist = itemlist->next) {
857     item = (struct Item *)itemlist->data;
858     if (item->type != ITEM_STROKE || item->brush.tool_type!=TOOL_PEN) continue;
859     // store info for undo
860     undo->itemlist = g_list_append(undo->itemlist, item);
861     brush = (struct Brush *)g_malloc(sizeof(struct Brush));
862     g_memmove(brush, &(item->brush), sizeof(struct Brush));
863     undo->auxlist = g_list_append(undo->auxlist, brush);
864     // repaint the stroke
865     item->brush.color_no = color;
866     item->brush.color_rgba = predef_colors_rgba[color];
867     if (item->canvas_item!=NULL)
868       gnome_canvas_item_set(item->canvas_item, 
869          "fill-color-rgba", item->brush.color_rgba, NULL);
870   }
871 }
872
873 void rethicken_selection(int val)
874 {
875   GList *itemlist;
876   struct Item *item;
877   struct Brush *brush;
878   
879   if (ui.selection == NULL) return;
880   prepare_new_undo();
881   undo->type = ITEM_REPAINTSEL;
882   undo->itemlist = NULL;
883   undo->auxlist = NULL;
884   for (itemlist = ui.selection->items; itemlist!=NULL; itemlist = itemlist->next) {
885     item = (struct Item *)itemlist->data;
886     if (item->type != ITEM_STROKE || item->brush.tool_type!=TOOL_PEN) continue;
887     // store info for undo
888     undo->itemlist = g_list_append(undo->itemlist, item);
889     brush = (struct Brush *)g_malloc(sizeof(struct Brush));
890     g_memmove(brush, &(item->brush), sizeof(struct Brush));
891     undo->auxlist = g_list_append(undo->auxlist, brush);
892     // repaint the stroke
893     item->brush.thickness_no = val;
894     item->brush.thickness = predef_thickness[TOOL_PEN][val];
895     if (item->canvas_item!=NULL)
896       gnome_canvas_item_set(item->canvas_item, 
897          "width-units", item->brush.thickness, NULL);
898   }
899 }