+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <math.h>
+#include <string.h>
+#include <gtk/gtk.h>
+#include <libgnomecanvas/libgnomecanvas.h>
+
+#include <libart_lgpl/art_vpath_dash.h>
+
+#include "xournal.h"
+#include "xo-callbacks.h"
+#include "xo-interface.h"
+#include "xo-support.h"
+#include "xo-misc.h"
+#include "xo-paint.h"
+
+/************** drawing nice cursors *********/
+
+static char cursor_pen_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+static char cursor_eraser_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x0f, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0xf8, 0x0f,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+static char cursor_eraser_mask[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x0f, 0xf8, 0x0f, 0xf8, 0x0f,
+ 0xf8, 0x0f, 0xf8, 0x0f, 0xf8, 0x0f, 0xf8, 0x0f, 0xf8, 0x0f, 0xf8, 0x0f,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+void set_cursor_busy(gboolean busy)
+{
+ GdkCursor *cursor;
+
+ if (busy) {
+ cursor = gdk_cursor_new(GDK_WATCH);
+ gdk_window_set_cursor(GTK_WIDGET(winMain)->window, cursor);
+ gdk_window_set_cursor(GTK_WIDGET(canvas)->window, cursor);
+ gdk_cursor_unref(cursor);
+ }
+ else {
+ gdk_window_set_cursor(GTK_WIDGET(winMain)->window, NULL);
+ update_cursor();
+ }
+ gdk_display_sync(gdk_display_get_default());
+}
+
+void update_cursor(void)
+{
+ GdkPixmap *source, *mask;
+ GdkColor fg = {0, 0, 0, 0}, bg = {0, 65535, 65535, 65535};
+
+ if (GTK_WIDGET(canvas)->window == NULL) return;
+
+ if (ui.cursor!=NULL) {
+ gdk_cursor_unref(ui.cursor);
+ ui.cursor = NULL;
+ }
+ if (ui.toolno == TOOL_PEN) {
+ fg.red = (ui.cur_brush->color_rgba >> 16) & 0xff00;
+ fg.green = (ui.cur_brush->color_rgba >> 8) & 0xff00;
+ fg.blue = (ui.cur_brush->color_rgba >> 0) & 0xff00;
+ source = gdk_bitmap_create_from_data(NULL, cursor_pen_bits, 16, 16);
+ ui.cursor = gdk_cursor_new_from_pixmap(source, source, &fg, &bg, 7, 7);
+ gdk_bitmap_unref(source);
+ }
+ else if (ui.toolno == TOOL_ERASER) {
+ source = gdk_bitmap_create_from_data(NULL, cursor_eraser_bits, 16, 16);
+ mask = gdk_bitmap_create_from_data(NULL, cursor_eraser_mask, 16, 16);
+ ui.cursor = gdk_cursor_new_from_pixmap(source, mask, &fg, &bg, 7, 7);
+ gdk_bitmap_unref(source);
+ gdk_bitmap_unref(mask);
+ }
+ else if (ui.toolno == TOOL_HIGHLIGHTER) {
+ source = gdk_bitmap_create_from_data(NULL, cursor_eraser_bits, 16, 16);
+ mask = gdk_bitmap_create_from_data(NULL, cursor_eraser_mask, 16, 16);
+ bg.red = (ui.cur_brush->color_rgba >> 16) & 0xff00;
+ bg.green = (ui.cur_brush->color_rgba >> 8) & 0xff00;
+ bg.blue = (ui.cur_brush->color_rgba >> 0) & 0xff00;
+ ui.cursor = gdk_cursor_new_from_pixmap(source, mask, &fg, &bg, 7, 7);
+ gdk_bitmap_unref(source);
+ gdk_bitmap_unref(mask);
+ }
+ else if (ui.cur_item_type == ITEM_MOVESEL) {
+ if (ui.toolno == TOOL_VERTSPACE)
+ ui.cursor = gdk_cursor_new(GDK_SB_V_DOUBLE_ARROW);
+ else
+ ui.cursor = gdk_cursor_new(GDK_FLEUR);
+ }
+ else if (ui.cur_item_type == ITEM_SELECTRECT) {
+ ui.cursor = gdk_cursor_new(GDK_TCROSS);
+ }
+
+ gdk_window_set_cursor(GTK_WIDGET(canvas)->window, ui.cursor);
+}
+
+
+/************** painting strokes *************/
+
+#define SUBDIVIDE_MAXDIST 5.0
+
+void subdivide_cur_path(null)
+{
+ int n, pieces, k;
+ double *p;
+ double x0, y0, x1, y1;
+
+ for (n=0, p=ui.cur_path.coords; n<ui.cur_path.num_points-1; n++, p+=2) {
+ pieces = (int)floor(hypot(p[2]-p[0], p[3]-p[1])/SUBDIVIDE_MAXDIST);
+ if (pieces>1) {
+ x0 = p[0]; y0 = p[1];
+ x1 = p[2]; y1 = p[3];
+ realloc_cur_path(ui.cur_path.num_points+pieces-1);
+ g_memmove(ui.cur_path.coords+2*(n+pieces), ui.cur_path.coords+2*(n+1),
+ 2*(ui.cur_path.num_points-n-1)*sizeof(double));
+ p = ui.cur_path.coords+2*n;
+ ui.cur_path.num_points += pieces-1;
+ n += (pieces-1);
+ for (k=1; k<pieces; k++) {
+ p+=2;
+ p[0] = x0 + k*(x1-x0)/pieces;
+ p[1] = y0 + k*(y1-y0)/pieces;
+ }
+ }
+ }
+}
+
+void create_new_stroke(GdkEvent *event)
+{
+ ui.cur_item_type = ITEM_STROKE;
+ ui.cur_item = g_new(struct Item, 1);
+ ui.cur_item->type = ITEM_STROKE;
+ g_memmove(&(ui.cur_item->brush), ui.cur_brush, sizeof(struct Brush));
+ ui.cur_item->path = &ui.cur_path;
+ realloc_cur_path(2);
+ ui.cur_path.num_points = 1;
+ get_pointer_coords(event, ui.cur_path.coords);
+
+ if (ui.ruler)
+ ui.cur_item->canvas_item = gnome_canvas_item_new(ui.cur_layer->group,
+ gnome_canvas_line_get_type(),
+ "cap-style", GDK_CAP_ROUND, "join-style", GDK_JOIN_ROUND,
+ "fill-color-rgba", ui.cur_item->brush.color_rgba,
+ "width-units", ui.cur_item->brush.thickness, NULL);
+ else
+ ui.cur_item->canvas_item = gnome_canvas_item_new(
+ ui.cur_layer->group, gnome_canvas_group_get_type(), NULL);
+}
+
+void continue_stroke(GdkEvent *event)
+{
+ GnomeCanvasPoints seg;
+ double *pt;
+
+ if (ui.ruler) {
+ pt = ui.cur_path.coords;
+ } else {
+ realloc_cur_path(ui.cur_path.num_points+1);
+ pt = ui.cur_path.coords + 2*(ui.cur_path.num_points-1);
+ }
+
+ get_pointer_coords(event, pt+2);
+
+ if (ui.ruler)
+ ui.cur_path.num_points = 2;
+ else {
+ if (hypot(pt[0]-pt[2], pt[1]-pt[3]) < PIXEL_MOTION_THRESHOLD/ui.zoom)
+ return; // not a meaningful motion
+ ui.cur_path.num_points++;
+ }
+
+ seg.coords = pt;
+ seg.num_points = 2;
+ seg.ref_count = 1;
+
+ /* note: we're using a piece of the cur_path array. This is ok because
+ upon creation the line just copies the contents of the GnomeCanvasPoints
+ into an internal structure */
+
+ if (ui.ruler)
+ gnome_canvas_item_set(ui.cur_item->canvas_item, "points", &seg, NULL);
+ else
+ gnome_canvas_item_new((GnomeCanvasGroup *)ui.cur_item->canvas_item,
+ gnome_canvas_line_get_type(), "points", &seg,
+ "cap-style", GDK_CAP_ROUND, "join-style", GDK_JOIN_ROUND,
+ "fill-color-rgba", ui.cur_item->brush.color_rgba,
+ "width-units", ui.cur_item->brush.thickness, NULL);
+}
+
+void finalize_stroke(void)
+{
+ if (ui.cur_path.num_points == 1) { // GnomeCanvas doesn't like num_points=1
+ ui.cur_path.coords[2] = ui.cur_path.coords[0]+0.1;
+ ui.cur_path.coords[3] = ui.cur_path.coords[1];
+ ui.cur_path.num_points = 2;
+ }
+
+ subdivide_cur_path(); // split the segment so eraser will work
+
+ ui.cur_item->path = gnome_canvas_points_new(ui.cur_path.num_points);
+ g_memmove(ui.cur_item->path->coords, ui.cur_path.coords,
+ 2*ui.cur_path.num_points*sizeof(double));
+ update_item_bbox(ui.cur_item);
+ ui.cur_path.num_points = 0;
+
+ // destroy the entire group of temporary line segments
+ gtk_object_destroy(GTK_OBJECT(ui.cur_item->canvas_item));
+ // make a new line item to replace it
+ ui.cur_item->canvas_item = gnome_canvas_item_new(ui.cur_layer->group,
+ gnome_canvas_line_get_type(), "points", ui.cur_item->path,
+ "cap-style", GDK_CAP_ROUND, "join-style", GDK_JOIN_ROUND,
+ "fill-color-rgba", ui.cur_item->brush.color_rgba,
+ "width-units", ui.cur_item->brush.thickness, NULL);
+
+ // add undo information
+ prepare_new_undo();
+ undo->type = ITEM_STROKE;
+ undo->item = ui.cur_item;
+ undo->layer = ui.cur_layer;
+
+ // store the item on top of the layer stack
+ ui.cur_layer->items = g_list_append(ui.cur_layer->items, ui.cur_item);
+ ui.cur_layer->nitems++;
+ ui.cur_item = NULL;
+ ui.cur_item_type = ITEM_NONE;
+}
+
+/************** eraser tool *************/
+
+void erase_stroke_portions(struct Item *item, double x, double y, double radius,
+ gboolean whole_strokes, struct UndoErasureData *erasure)
+{
+ int i;
+ double *pt;
+ struct Item *newhead, *newtail;
+ gboolean need_recalc = FALSE;
+
+ for (i=0, pt=item->path->coords; i<item->path->num_points; i++, pt+=2) {
+ if (hypot(pt[0]-x, pt[1]-y) <= radius) { // found an intersection
+ // FIXME: need to test if line SEGMENT hits the circle
+ // hide the canvas item, and create erasure data if needed
+ if (erasure == NULL) {
+ item->type = ITEM_TEMP_STROKE;
+ gnome_canvas_item_hide(item->canvas_item);
+ /* we'll use this hidden item as an insertion point later */
+ erasure = (struct UndoErasureData *)g_malloc(sizeof(struct UndoErasureData));
+ item->erasure = erasure;
+ erasure->item = item;
+ erasure->npos = g_list_index(ui.cur_layer->items, item);
+ erasure->nrepl = 0;
+ erasure->replacement_items = NULL;
+ }
+ // split the stroke
+ newhead = newtail = NULL;
+ if (!whole_strokes) {
+ if (i>=2) {
+ newhead = (struct Item *)g_malloc(sizeof(struct Item));
+ newhead->type = ITEM_STROKE;
+ g_memmove(&newhead->brush, &item->brush, sizeof(struct Brush));
+ newhead->path = gnome_canvas_points_new(i);
+ g_memmove(newhead->path->coords, item->path->coords, 2*i*sizeof(double));
+ }
+ while (++i < item->path->num_points) {
+ pt+=2;
+ if (hypot(pt[0]-x, pt[1]-y) > radius) break;
+ }
+ if (i<item->path->num_points-1) {
+ newtail = (struct Item *)g_malloc(sizeof(struct Item));
+ newtail->type = ITEM_STROKE;
+ g_memmove(&newtail->brush, &item->brush, sizeof(struct Brush));
+ newtail->path = gnome_canvas_points_new(item->path->num_points-i);
+ g_memmove(newtail->path->coords, item->path->coords+2*i,
+ 2*(item->path->num_points-i)*sizeof(double));
+ newtail->canvas_item = NULL;
+ }
+ }
+ if (item->type == ITEM_STROKE) {
+ // it's inside an erasure list - we destroy it
+ gnome_canvas_points_free(item->path);
+ if (item->canvas_item != NULL)
+ gtk_object_destroy(GTK_OBJECT(item->canvas_item));
+ erasure->nrepl--;
+ erasure->replacement_items = g_list_remove(erasure->replacement_items, item);
+ g_free(item);
+ }
+ // add the new head
+ if (newhead != NULL) {
+ update_item_bbox(newhead);
+ newhead->canvas_item = gnome_canvas_item_new(ui.cur_layer->group,
+ gnome_canvas_line_get_type(), "points", newhead->path,
+ "cap-style", GDK_CAP_ROUND, "join-style", GDK_JOIN_ROUND,
+ "fill-color-rgba", newhead->brush.color_rgba,
+ "width-units", newhead->brush.thickness, NULL);
+ lower_canvas_item_to(ui.cur_layer->group,
+ newhead->canvas_item, erasure->item->canvas_item);
+ erasure->replacement_items = g_list_prepend(erasure->replacement_items, newhead);
+ erasure->nrepl++;
+ // prepending ensures it won't get processed twice
+ }
+ // recurse into the new tail
+ need_recalc = (newtail!=NULL);
+ if (newtail == NULL) break;
+ item = newtail;
+ erasure->replacement_items = g_list_prepend(erasure->replacement_items, newtail);
+ erasure->nrepl++;
+ i=0; pt=item->path->coords;
+ }
+ }
+ // add the tail if needed
+ if (!need_recalc) return;
+ update_item_bbox(item);
+ item->canvas_item = gnome_canvas_item_new(ui.cur_layer->group,
+ gnome_canvas_line_get_type(), "points", item->path,
+ "cap-style", GDK_CAP_ROUND, "join-style", GDK_JOIN_ROUND,
+ "fill-color-rgba", item->brush.color_rgba,
+ "width-units", item->brush.thickness, NULL);
+ lower_canvas_item_to(ui.cur_layer->group, item->canvas_item,
+ erasure->item->canvas_item);
+}
+
+
+void do_eraser(GdkEvent *event, double radius, gboolean whole_strokes)
+{
+ struct Item *item, *repl;
+ GList *itemlist, *repllist;
+ double pos[2];
+ struct BBox eraserbox;
+
+ get_pointer_coords(event, pos);
+ eraserbox.left = pos[0]-radius;
+ eraserbox.right = pos[0]+radius;
+ eraserbox.top = pos[1]-radius;
+ eraserbox.bottom = pos[1]+radius;
+ for (itemlist = ui.cur_layer->items; itemlist!=NULL; itemlist = itemlist->next) {
+ item = (struct Item *)itemlist->data;
+ if (item->type == ITEM_STROKE) {
+ if (!have_intersect(&(item->bbox), &eraserbox)) continue;
+ erase_stroke_portions(item, pos[0], pos[1], radius, whole_strokes, NULL);
+ } else if (item->type == ITEM_TEMP_STROKE) {
+ repllist = item->erasure->replacement_items;
+ while (repllist!=NULL) {
+ repl = (struct Item *)repllist->data;
+ // we may delete the item soon! so advance now in the list
+ repllist = repllist->next;
+ if (have_intersect(&(repl->bbox), &eraserbox))
+ erase_stroke_portions(repl, pos[0], pos[1], radius, whole_strokes, item->erasure);
+ }
+ }
+ }
+}
+
+void finalize_erasure(void)
+{
+ GList *itemlist, *partlist;
+ struct Item *item, *part;
+
+ prepare_new_undo();
+ undo->type = ITEM_ERASURE;
+ undo->layer = ui.cur_layer;
+ undo->erasurelist = NULL;
+
+ itemlist = ui.cur_layer->items;
+ while (itemlist!=NULL) {
+ item = (struct Item *)itemlist->data;
+ itemlist = itemlist->next;
+ if (item->type != ITEM_TEMP_STROKE) continue;
+ item->type = ITEM_STROKE;
+ ui.cur_layer->items = g_list_remove(ui.cur_layer->items, item);
+ // the item has an invisible canvas item, which used to act as anchor
+ if (item->canvas_item!=NULL) {
+ gtk_object_destroy(GTK_OBJECT(item->canvas_item));
+ item->canvas_item = NULL;
+ }
+ undo->erasurelist = g_list_append(undo->erasurelist, item->erasure);
+ // add the new strokes into the current layer
+ for (partlist = item->erasure->replacement_items; partlist!=NULL; partlist = partlist->next)
+ ui.cur_layer->items = g_list_insert_before(
+ ui.cur_layer->items, itemlist, partlist->data);
+ ui.cur_layer->nitems += item->erasure->nrepl-1;
+ }
+
+ ui.cur_item = NULL;
+ ui.cur_item_type = ITEM_NONE;
+
+ /* NOTE: the list of erasures goes in the depth order of the layer;
+ this guarantees that, upon undo, the erasure->npos fields give the
+ correct position where each item should be reinserted as the list
+ is traversed in the forward direction */
+}
+
+/************ selection tools ***********/
+
+void make_dashed(GnomeCanvasItem *item)
+{
+ double dashlen[2];
+ ArtVpathDash dash;
+
+ dash.n_dash = 2;
+ dash.offset = 3.0;
+ dash.dash = dashlen;
+ dashlen[0] = dashlen[1] = 6.0;
+ gnome_canvas_item_set(item, "dash", &dash, NULL);
+}
+
+
+void start_selectrect(GdkEvent *event)
+{
+ double pt[2];
+ reset_selection();
+
+ ui.cur_item_type = ITEM_SELECTRECT;
+ ui.selection = g_new(struct Selection, 1);
+ ui.selection->type = ITEM_SELECTRECT;
+ ui.selection->items = NULL;
+ ui.selection->layer = ui.cur_layer;
+
+ get_pointer_coords(event, pt);
+ ui.selection->bbox.left = ui.selection->bbox.right = pt[0];
+ ui.selection->bbox.top = ui.selection->bbox.bottom = pt[1];
+
+ ui.selection->canvas_item = gnome_canvas_item_new(ui.cur_layer->group,
+ gnome_canvas_rect_get_type(), "width-pixels", 1,
+ "outline-color-rgba", 0x000000ff,
+ "fill-color-rgba", 0x80808040,
+ "x1", pt[0], "x2", pt[0], "y1", pt[1], "y2", pt[1], NULL);
+ update_cursor();
+}
+
+void finalize_selectrect(void)
+{
+ double x1, x2, y1, y2;
+ GList *itemlist;
+ struct Item *item;
+
+
+ ui.cur_item_type = ITEM_NONE;
+
+ if (ui.selection->bbox.left > ui.selection->bbox.right) {
+ x1 = ui.selection->bbox.right; x2 = ui.selection->bbox.left;
+ ui.selection->bbox.left = x1; ui.selection->bbox.right = x2;
+ } else {
+ x1 = ui.selection->bbox.left; x2 = ui.selection->bbox.right;
+ }
+
+ if (ui.selection->bbox.top > ui.selection->bbox.bottom) {
+ y1 = ui.selection->bbox.bottom; y2 = ui.selection->bbox.top;
+ ui.selection->bbox.top = y1; ui.selection->bbox.bottom = y2;
+ } else {
+ y1 = ui.selection->bbox.top; y2 = ui.selection->bbox.bottom;
+ }
+
+ for (itemlist = ui.selection->layer->items; itemlist!=NULL; itemlist = itemlist->next) {
+ item = (struct Item *)itemlist->data;
+ if (item->bbox.left >= x1 && item->bbox.right <= x2 &&
+ item->bbox.top >= y1 && item->bbox.bottom <= y2) {
+ ui.selection->items = g_list_append(ui.selection->items, item);
+ }
+ }
+
+ if (ui.selection->items == NULL) reset_selection();
+ else make_dashed(ui.selection->canvas_item);
+ update_cursor();
+ update_copy_paste_enabled();
+}
+
+gboolean start_movesel(GdkEvent *event)
+{
+ double pt[2];
+
+ if (ui.selection==NULL) return FALSE;
+ if (ui.cur_layer != ui.selection->layer) return FALSE;
+
+ get_pointer_coords(event, pt);
+ if (ui.selection->type == ITEM_SELECTRECT) {
+ if (pt[0]<ui.selection->bbox.left || pt[0]>ui.selection->bbox.right ||
+ pt[1]<ui.selection->bbox.top || pt[1]>ui.selection->bbox.bottom)
+ return FALSE;
+ ui.cur_item_type = ITEM_MOVESEL;
+ ui.selection->anchor_x = ui.selection->last_x = pt[0];
+ ui.selection->anchor_y = ui.selection->last_y = pt[1];
+ gnome_canvas_item_set(ui.selection->canvas_item, "dash", NULL, NULL);
+ update_cursor();
+ return TRUE;
+ }
+ return FALSE;
+}
+
+void start_vertspace(GdkEvent *event)
+{
+ double pt[2];
+ GList *itemlist;
+ struct Item *item;
+
+ reset_selection();
+ ui.cur_item_type = ITEM_MOVESEL;
+ ui.selection = g_new(struct Selection, 1);
+ ui.selection->type = ITEM_MOVESEL;
+ ui.selection->items = NULL;
+ ui.selection->layer = ui.cur_layer;
+
+ get_pointer_coords(event, pt);
+ for (itemlist = ui.cur_layer->items; itemlist!=NULL; itemlist = itemlist->next) {
+ item = (struct Item *)itemlist->data;
+ if (item->bbox.top >= pt[1]) {
+ ui.selection->items = g_list_append(ui.selection->items, item);
+ }
+ }
+
+ ui.selection->anchor_x = ui.selection->last_x = 0;
+ ui.selection->anchor_y = ui.selection->last_y = pt[1];
+ ui.selection->canvas_item = gnome_canvas_item_new(ui.cur_layer->group,
+ gnome_canvas_rect_get_type(), "width-pixels", 1,
+ "outline-color-rgba", 0x000000ff,
+ "fill-color-rgba", 0x80808040,
+ "x1", -100.0, "x2", ui.cur_page->width+100, "y1", pt[1], "y2", pt[1], NULL);
+ update_cursor();
+}
+
+void continue_movesel(GdkEvent *event)
+{
+ double pt[2], dx, dy;
+ GList *list;
+ struct Item *item;
+
+ get_pointer_coords(event, pt);
+ if (ui.toolno == TOOL_VERTSPACE) pt[0] = 0;
+ dx = pt[0] - ui.selection->last_x;
+ dy = pt[1] - ui.selection->last_y;
+
+ if (hypot(dx,dy) < 1) return; // don't move subpixel
+ ui.selection->last_x = pt[0];
+ ui.selection->last_y = pt[1];
+
+ // move the canvas items
+ if (ui.toolno == TOOL_VERTSPACE)
+ gnome_canvas_item_set(ui.selection->canvas_item, "y2", pt[1], NULL);
+ else
+ gnome_canvas_item_move(ui.selection->canvas_item, dx, dy);
+
+ for (list = ui.selection->items; list != NULL; list = list->next) {
+ item = (struct Item *)list->data;
+ if (item->canvas_item != NULL)
+ gnome_canvas_item_move(item->canvas_item, dx, dy);
+ }
+
+ /* consider: if view_continuous, move items to a different page if
+ y value gets out of range (same algo as in button_down event
+ processing); then need to reparent the canvas items, delete the
+ Items from the old Layer, insert them on the new Layer, ...
+ and make an undo-event, probably cut-and-paste style... */
+}
+
+void finalize_movesel(void)
+{
+ if (ui.selection->items != NULL) {
+ prepare_new_undo();
+ undo->type = ITEM_MOVESEL;
+ undo->itemlist = g_list_copy(ui.selection->items);
+ undo->val_x = ui.selection->last_x - ui.selection->anchor_x;
+ undo->val_y = ui.selection->last_y - ui.selection->anchor_y;
+ move_journal_items_by(undo->itemlist, undo->val_x, undo->val_y);
+ }
+
+ if (ui.toolno == TOOL_VERTSPACE)
+ reset_selection();
+ else {
+ ui.selection->bbox.left += undo->val_x;
+ ui.selection->bbox.right += undo->val_x;
+ ui.selection->bbox.top += undo->val_y;
+ ui.selection->bbox.bottom += undo->val_y;
+ make_dashed(ui.selection->canvas_item);
+ }
+ ui.cur_item_type = ITEM_NONE;
+ update_cursor();
+}
+
+
+void selection_delete(void)
+{
+ struct UndoErasureData *erasure;
+ GList *itemlist;
+ struct Item *item;
+
+ if (ui.selection == NULL) return;
+ prepare_new_undo();
+ undo->type = ITEM_ERASURE;
+ undo->layer = ui.selection->layer;
+ undo->erasurelist = NULL;
+ for (itemlist = ui.selection->items; itemlist!=NULL; itemlist = itemlist->next) {
+ item = (struct Item *)itemlist->data;
+ if (item->canvas_item!=NULL)
+ gtk_object_destroy(GTK_OBJECT(item->canvas_item));
+ erasure = g_new(struct UndoErasureData, 1);
+ erasure->item = item;
+ erasure->npos = g_list_index(ui.selection->layer->items, item);
+ erasure->nrepl = 0;
+ erasure->replacement_items = NULL;
+ ui.selection->layer->items = g_list_remove(ui.selection->layer->items, item);
+ ui.selection->layer->nitems--;
+ undo->erasurelist = g_list_prepend(undo->erasurelist, erasure);
+ }
+ reset_selection();
+
+ /* NOTE: the erasurelist is built backwards; this guarantees that,
+ upon undo, the erasure->npos fields give the correct position
+ where each item should be reinserted as the list is traversed in
+ the forward direction */
+}
+
+void callback_clipboard_get(GtkClipboard *clipboard,
+ GtkSelectionData *selection_data,
+ guint info, gpointer user_data)
+{
+ int length;
+
+ g_memmove(&length, user_data, sizeof(int));
+ gtk_selection_data_set(selection_data,
+ gdk_atom_intern("_XOURNAL", FALSE), 8, user_data, length);
+}
+
+void callback_clipboard_clear(GtkClipboard *clipboard, gpointer user_data)
+{
+ g_free(user_data);
+}
+
+void selection_to_clip(void)
+{
+ int bufsz, nitems;
+ char *buf, *p;
+ GList *list;
+ struct Item *item;
+ GtkTargetEntry target;
+
+ if (ui.selection == NULL) return;
+ bufsz = 2*sizeof(int) // bufsz, nitems
+ + sizeof(struct BBox); // bbox
+ nitems = 0;
+ for (list = ui.selection->items; list != NULL; list = list->next) {
+ item = (struct Item *)list->data;
+ nitems++;
+ if (item->type == ITEM_STROKE) {
+ bufsz+= sizeof(int) // type
+ + sizeof(struct Brush) // brush
+ + sizeof(int) // num_points
+ + 2*item->path->num_points*sizeof(double); // the points
+ }
+ else bufsz+= sizeof(int); // type
+ }
+ p = buf = g_malloc(bufsz);
+ g_memmove(p, &bufsz, sizeof(int)); p+= sizeof(int);
+ g_memmove(p, &nitems, sizeof(int)); p+= sizeof(int);
+ g_memmove(p, &ui.selection->bbox, sizeof(struct BBox)); p+= sizeof(struct BBox);
+ for (list = ui.selection->items; list != NULL; list = list->next) {
+ item = (struct Item *)list->data;
+ g_memmove(p, &item->type, sizeof(int)); p+= sizeof(int);
+ if (item->type == ITEM_STROKE) {
+ g_memmove(p, &item->brush, sizeof(struct Brush)); p+= sizeof(struct Brush);
+ g_memmove(p, &item->path->num_points, sizeof(int)); p+= sizeof(int);
+ g_memmove(p, item->path->coords, 2*item->path->num_points*sizeof(double));
+ p+= 2*item->path->num_points*sizeof(double);
+ }
+ }
+
+ target.target = "_XOURNAL";
+ target.flags = 0;
+ target.info = 0;
+
+ gtk_clipboard_set_with_data(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
+ &target, 1,
+ callback_clipboard_get, callback_clipboard_clear, buf);
+}
+
+
+void clipboard_paste(void)
+{
+ GtkSelectionData *sel_data;
+ unsigned char *p;
+ int nitems, npts, i;
+ GList *list;
+ struct Item *item;
+ double hoffset, voffset, cx, cy;
+ double *pf;
+ int sx, sy, wx, wy;
+
+ if (ui.cur_layer == NULL) return;
+
+ ui.cur_item_type = ITEM_PASTE;
+ sel_data = gtk_clipboard_wait_for_contents(
+ gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
+ gdk_atom_intern("_XOURNAL", FALSE));
+ ui.cur_item_type = ITEM_NONE;
+ if (sel_data == NULL) return; // paste failed
+
+ reset_selection();
+ ui.toolno = TOOL_SELECTRECT;
+ ui.ruler = FALSE;
+ update_tool_buttons();
+ update_tool_menu();
+ update_color_menu();
+ update_cursor();
+
+ ui.selection = g_new(struct Selection, 1);
+ p = sel_data->data + sizeof(int);
+ g_memmove(&nitems, p, sizeof(int)); p+= sizeof(int);
+ ui.selection->type = ITEM_SELECTRECT;
+ ui.selection->layer = ui.cur_layer;
+ g_memmove(&ui.selection->bbox, p, sizeof(struct BBox)); p+= sizeof(struct BBox);
+ ui.selection->items = NULL;
+
+ // find by how much we translate the pasted selection
+ gnome_canvas_get_scroll_offsets(canvas, &sx, &sy);
+ gdk_window_get_geometry(GTK_WIDGET(canvas)->window, NULL, NULL, &wx, &wy, NULL);
+ gnome_canvas_window_to_world(canvas, sx + wx/2, sy + wy/2, &cx, &cy);
+ cx -= ui.cur_page->hoffset;
+ cy -= ui.cur_page->voffset;
+ if (cx + (ui.selection->bbox.right-ui.selection->bbox.left)/2 > ui.cur_page->width)
+ cx = ui.cur_page->width - (ui.selection->bbox.right-ui.selection->bbox.left)/2;
+ if (cx - (ui.selection->bbox.right-ui.selection->bbox.left)/2 < 0)
+ cx = (ui.selection->bbox.right-ui.selection->bbox.left)/2;
+ if (cy + (ui.selection->bbox.bottom-ui.selection->bbox.top)/2 > ui.cur_page->height)
+ cy = ui.cur_page->height - (ui.selection->bbox.bottom-ui.selection->bbox.top)/2;
+ if (cy - (ui.selection->bbox.bottom-ui.selection->bbox.top)/2 < 0)
+ cy = (ui.selection->bbox.bottom-ui.selection->bbox.top)/2;
+ hoffset = cx - (ui.selection->bbox.right+ui.selection->bbox.left)/2;
+ voffset = cy - (ui.selection->bbox.top+ui.selection->bbox.bottom)/2;
+ ui.selection->bbox.left += hoffset;
+ ui.selection->bbox.right += hoffset;
+ ui.selection->bbox.top += voffset;
+ ui.selection->bbox.bottom += voffset;
+
+ ui.selection->canvas_item = gnome_canvas_item_new(ui.cur_layer->group,
+ gnome_canvas_rect_get_type(), "width-pixels", 1,
+ "outline-color-rgba", 0x000000ff,
+ "fill-color-rgba", 0x80808040,
+ "x1", ui.selection->bbox.left, "x2", ui.selection->bbox.right,
+ "y1", ui.selection->bbox.top, "y2", ui.selection->bbox.bottom, NULL);
+ make_dashed(ui.selection->canvas_item);
+
+ while (nitems-- > 0) {
+ item = g_new(struct Item, 1);
+ ui.selection->items = g_list_append(ui.selection->items, item);
+ ui.cur_layer->items = g_list_append(ui.cur_layer->items, item);
+ ui.cur_layer->nitems++;
+ g_memmove(&item->type, p, sizeof(int)); p+= sizeof(int);
+ if (item->type == ITEM_STROKE) {
+ g_memmove(&item->brush, p, sizeof(struct Brush)); p+= sizeof(struct Brush);
+ g_memmove(&npts, p, sizeof(int)); p+= sizeof(int);
+ item->path = gnome_canvas_points_new(npts);
+ pf = (double *)p;
+ for (i=0; i<npts; i++) {
+ item->path->coords[2*i] = pf[2*i] + hoffset;
+ item->path->coords[2*i+1] = pf[2*i+1] + voffset;
+ }
+ p+= 2*item->path->num_points*sizeof(double);
+ update_item_bbox(item);
+ item->canvas_item = gnome_canvas_item_new(ui.cur_layer->group,
+ gnome_canvas_line_get_type(), "points", item->path,
+ "cap-style", GDK_CAP_ROUND, "join-style", GDK_JOIN_ROUND,
+ "fill-color-rgba", item->brush.color_rgba,
+ "width-units", item->brush.thickness, NULL);
+ }
+ }
+
+ prepare_new_undo();
+ undo->type = ITEM_PASTE;
+ undo->layer = ui.cur_layer;
+ undo->itemlist = g_list_copy(ui.selection->items);
+
+ gtk_selection_data_free(sel_data);
+ update_copy_paste_enabled();
+}