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