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