]> git.donarmstrong.com Git - xournal.git/blob - src/xo-clipboard.c
3eaec5f73551f09ca96cb5b340c4bf9eb80ace87
[xournal.git] / src / xo-clipboard.c
1 /*
2  *  This program is free software; you can redistribute it and/or
3  *  modify it under the terms of the GNU General Public
4  *  License as published by the Free Software Foundation; either
5  *  version 2 of the License, or (at your option) any later version.
6  *
7  *  This software is distributed in the hope that it will be useful,
8  *  but WITHOUT ANY WARRANTY; without even the implied warranty of  
9  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
10  *  General Public License for more details.
11  *
12  *  You should have received a copy of the GNU General Public License
13  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
14  */
15
16 #ifdef HAVE_CONFIG_H
17 #  include <config.h>
18 #endif
19
20 #include <string.h>
21 #include <gtk/gtk.h>
22
23 #include "xournal.h"
24 #include "xo-callbacks.h"
25 #include "xo-interface.h"
26 #include "xo-support.h"
27 #include "xo-misc.h"
28 #include "xo-paint.h"
29 #include "xo-image.h"
30
31 #define TARGET_XOURNAL 1
32 #define TARGET_TEXT    2
33 #define TARGET_PIXBUF  3
34 #define XOURNAL_TARGET_ATOM "_XOURNAL" 
35   /* change when serialized data format changes incompatibly */
36
37 void callback_clipboard_get(GtkClipboard *clipboard,
38                             GtkSelectionData *selection_data,
39                             guint info, gpointer user_data)
40 {
41   int length;
42   char *p;
43   gchar *text;
44   GdkPixbuf *pixbuf;
45   
46   g_memmove(&length, user_data, sizeof(int));
47   p = ((char *)user_data) + length;
48   g_memmove(&text, p, sizeof(gchar *)); p+= sizeof(gchar *);
49   g_memmove(&pixbuf, p, sizeof(GdkPixbuf *)); p += sizeof(GdkPixbuf *);
50   
51   switch (info) {
52     case TARGET_XOURNAL:
53       gtk_selection_data_set(selection_data,
54         gdk_atom_intern(XOURNAL_TARGET_ATOM, FALSE), 8, user_data, length);
55       break;
56     case TARGET_TEXT:
57       if (text!=NULL) 
58         gtk_selection_data_set_text(selection_data, text, -1);
59       break;
60     case TARGET_PIXBUF:
61       if (pixbuf!=NULL)
62         gtk_selection_data_set_pixbuf(selection_data, pixbuf);
63       break;
64   }
65 }
66
67 void callback_clipboard_clear(GtkClipboard *clipboard, gpointer user_data)
68 {
69   int length;
70   char *p;
71   gchar *text;
72   GdkPixbuf *pixbuf;
73   
74   g_memmove(&length, user_data, sizeof(int));
75   p = ((char *)user_data) + length;
76   g_memmove(&text, p, sizeof(gchar *)); p+= sizeof(gchar *);
77   g_memmove(&pixbuf, p, sizeof(GdkPixbuf *)); p += sizeof(GdkPixbuf *);
78
79   if (text!=NULL) g_free(text);
80   if (pixbuf!=NULL) gdk_pixbuf_unref(pixbuf);
81   g_free(user_data);
82 }
83
84 void selection_to_clip(void)
85 {
86   int bufsz, nitems, val;
87   char *buf, *p;
88   GList *list;
89   struct Item *item;
90   GtkTargetList *targetlist;
91   GtkTargetEntry *targets;
92   GdkPixbuf *target_pixbuf;
93   gchar *target_text;
94   int n_targets;
95   
96   if (ui.selection == NULL) return;
97   bufsz = 2*sizeof(int) // bufsz, nitems
98         + sizeof(struct BBox); // bbox
99   nitems = 0;
100   for (list = ui.selection->items; list != NULL; list = list->next) {
101     item = (struct Item *)list->data;
102     nitems++;
103     if (item->type == ITEM_STROKE) {
104       bufsz+= sizeof(int) // type
105             + sizeof(struct Brush) // brush
106             + sizeof(int) // num_points
107             + 2*item->path->num_points*sizeof(double); // the points
108       if (item->brush.variable_width)
109         bufsz += (item->path->num_points-1)*sizeof(double); // the widths
110     }
111     else if (item->type == ITEM_TEXT) {
112       bufsz+= sizeof(int) // type
113             + sizeof(struct Brush) // brush
114             + 2*sizeof(double) // bbox upper-left
115             + sizeof(int) // text len
116             + strlen(item->text)+1 // text
117             + sizeof(int) // font_name len
118             + strlen(item->font_name)+1 // font_name
119             + sizeof(double); // font_size
120     }
121     else if (item->type == ITEM_IMAGE) {
122       if (item->image_png == NULL) {
123         set_cursor_busy(TRUE);
124         if (!gdk_pixbuf_save_to_buffer(item->image, &item->image_png, &item->image_png_len, "png", NULL, NULL))
125           item->image_png_len = 0;       // failed for some reason, so forget it
126         set_cursor_busy(FALSE);
127       }
128       bufsz+= sizeof(int) // type
129         + sizeof(struct BBox)
130         + sizeof(gsize) // png_buflen
131         + item->image_png_len;
132     }
133     else bufsz+= sizeof(int); // type
134   }
135   // allocate buffer; allow space for alternative target representations at the end
136   p = buf = g_malloc(bufsz + sizeof(gchar *) + sizeof(GdkPixbuf *));
137   target_pixbuf = NULL;
138   target_text = NULL;
139   g_memmove(p, &bufsz, sizeof(int)); p+= sizeof(int);
140   g_memmove(p, &nitems, sizeof(int)); p+= sizeof(int);
141   g_memmove(p, &ui.selection->bbox, sizeof(struct BBox)); p+= sizeof(struct BBox);
142   for (list = ui.selection->items; list != NULL; list = list->next) {
143     item = (struct Item *)list->data;
144     g_memmove(p, &item->type, sizeof(int)); p+= sizeof(int);
145     if (item->type == ITEM_STROKE) {
146       g_memmove(p, &item->brush, sizeof(struct Brush)); p+= sizeof(struct Brush);
147       g_memmove(p, &item->path->num_points, sizeof(int)); p+= sizeof(int);
148       g_memmove(p, item->path->coords, 2*item->path->num_points*sizeof(double));
149       p+= 2*item->path->num_points*sizeof(double);
150       if (item->brush.variable_width) {
151         g_memmove(p, item->widths, (item->path->num_points-1)*sizeof(double));
152         p+= (item->path->num_points-1)*sizeof(double);
153       }
154     }
155     if (item->type == ITEM_TEXT) {
156       g_memmove(p, &item->brush, sizeof(struct Brush)); p+= sizeof(struct Brush);
157       g_memmove(p, &item->bbox.left, sizeof(double)); p+= sizeof(double);
158       g_memmove(p, &item->bbox.top, sizeof(double)); p+= sizeof(double);
159       val = strlen(item->text);
160       g_memmove(p, &val, sizeof(int)); p+= sizeof(int);
161       g_memmove(p, item->text, val+1); p+= val+1;
162       val = strlen(item->font_name);
163       g_memmove(p, &val, sizeof(int)); p+= sizeof(int);
164       g_memmove(p, item->font_name, val+1); p+= val+1;
165       g_memmove(p, &item->font_size, sizeof(double)); p+= sizeof(double);
166       if (nitems==1) target_text = g_strdup(item->text); // single text item
167     }
168     if (item->type == ITEM_IMAGE) {
169       g_memmove(p, &item->bbox, sizeof(struct BBox)); p+= sizeof(struct BBox);
170       g_memmove(p, &item->image_png_len, sizeof(gsize)); p+= sizeof(gsize);
171       if (item->image_png_len > 0) {
172         g_memmove(p, item->image_png, item->image_png_len); p+= item->image_png_len;
173       }
174       if (nitems==1) target_pixbuf = gdk_pixbuf_copy(item->image); // single image
175     }
176   }
177   
178   /* add pointers to alternative representations; 
179      note those are not serialized, but they're only used internally,
180      and not sent along to the GtkSelection */
181   g_memmove(p, &target_text, sizeof(gchar *)); p+= sizeof(gchar *);
182   g_memmove(p, &target_pixbuf, sizeof(GdkPixbuf *)); p += sizeof(GdkPixbuf *);
183   
184   /* build list of valid targets */
185   targetlist = gtk_target_list_new(NULL, 0);
186   gtk_target_list_add(targetlist, 
187     gdk_atom_intern(XOURNAL_TARGET_ATOM, FALSE), 0, TARGET_XOURNAL);
188   if (target_pixbuf!=NULL)
189     gtk_target_list_add_image_targets(targetlist, TARGET_PIXBUF, TRUE);
190   if (target_text!=NULL) 
191     gtk_target_list_add_text_targets(targetlist, TARGET_TEXT);
192   targets = gtk_target_table_new_from_list(targetlist, &n_targets);
193   gtk_target_list_unref(targetlist);
194   
195   gtk_clipboard_set_with_data(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD), 
196        targets, n_targets,
197        callback_clipboard_get, callback_clipboard_clear, buf);
198   gtk_target_table_free(targets, n_targets);
199 }
200
201 // local paste within xournal
202 void clipboard_paste_from_xournal(GtkSelectionData *sel_data)
203 {
204   unsigned char *p;
205   int nitems, npts, i, len;
206   struct Item *item;
207   double hoffset, voffset, cx, cy;
208   double *pf;
209   int sx, sy, wx, wy;
210   
211   reset_selection();
212   
213   ui.selection = g_new(struct Selection, 1);
214   p = sel_data->data + sizeof(int);
215   g_memmove(&nitems, p, sizeof(int)); p+= sizeof(int);
216   ui.selection->type = ITEM_SELECTRECT;
217   ui.selection->layer = ui.cur_layer;
218   g_memmove(&ui.selection->bbox, p, sizeof(struct BBox)); p+= sizeof(struct BBox);
219   ui.selection->items = NULL;
220   
221   // find by how much we translate the pasted selection
222   gnome_canvas_get_scroll_offsets(canvas, &sx, &sy);
223   gdk_window_get_geometry(GTK_WIDGET(canvas)->window, NULL, NULL, &wx, &wy, NULL);
224   gnome_canvas_window_to_world(canvas, sx + wx/2, sy + wy/2, &cx, &cy);
225   cx -= ui.cur_page->hoffset;
226   cy -= ui.cur_page->voffset;
227   if (cx + (ui.selection->bbox.right-ui.selection->bbox.left)/2 > ui.cur_page->width)
228     cx = ui.cur_page->width - (ui.selection->bbox.right-ui.selection->bbox.left)/2;
229   if (cx - (ui.selection->bbox.right-ui.selection->bbox.left)/2 < 0)
230     cx = (ui.selection->bbox.right-ui.selection->bbox.left)/2;
231   if (cy + (ui.selection->bbox.bottom-ui.selection->bbox.top)/2 > ui.cur_page->height)
232     cy = ui.cur_page->height - (ui.selection->bbox.bottom-ui.selection->bbox.top)/2;
233   if (cy - (ui.selection->bbox.bottom-ui.selection->bbox.top)/2 < 0)
234     cy = (ui.selection->bbox.bottom-ui.selection->bbox.top)/2;
235   hoffset = cx - (ui.selection->bbox.right+ui.selection->bbox.left)/2;
236   voffset = cy - (ui.selection->bbox.top+ui.selection->bbox.bottom)/2;
237   ui.selection->bbox.left += hoffset;
238   ui.selection->bbox.right += hoffset;
239   ui.selection->bbox.top += voffset;
240   ui.selection->bbox.bottom += voffset;
241
242   ui.selection->canvas_item = gnome_canvas_item_new(ui.cur_layer->group,
243       gnome_canvas_rect_get_type(), "width-pixels", 1,
244       "outline-color-rgba", 0x000000ff,
245       "fill-color-rgba", 0x80808040,
246       "x1", ui.selection->bbox.left, "x2", ui.selection->bbox.right, 
247       "y1", ui.selection->bbox.top, "y2", ui.selection->bbox.bottom, NULL);
248   make_dashed(ui.selection->canvas_item);
249
250   while (nitems-- > 0) {
251     item = g_new(struct Item, 1);
252     ui.selection->items = g_list_append(ui.selection->items, item);
253     ui.cur_layer->items = g_list_append(ui.cur_layer->items, item);
254     ui.cur_layer->nitems++;
255     g_memmove(&item->type, p, sizeof(int)); p+= sizeof(int);
256     if (item->type == ITEM_STROKE) {
257       g_memmove(&item->brush, p, sizeof(struct Brush)); p+= sizeof(struct Brush);
258       g_memmove(&npts, p, sizeof(int)); p+= sizeof(int);
259       item->path = gnome_canvas_points_new(npts);
260       pf = (double *)p;
261       for (i=0; i<npts; i++) {
262         item->path->coords[2*i] = pf[2*i] + hoffset;
263         item->path->coords[2*i+1] = pf[2*i+1] + voffset;
264       }
265       p+= 2*item->path->num_points*sizeof(double);
266       if (item->brush.variable_width) {
267         item->widths = g_memdup(p, (item->path->num_points-1)*sizeof(double));
268         p+= (item->path->num_points-1)*sizeof(double);
269       }
270       else item->widths = NULL;
271       update_item_bbox(item);
272       make_canvas_item_one(ui.cur_layer->group, item);
273     }
274     if (item->type == ITEM_TEXT) {
275       g_memmove(&item->brush, p, sizeof(struct Brush)); p+= sizeof(struct Brush);
276       g_memmove(&item->bbox.left, p, sizeof(double)); p+= sizeof(double);
277       g_memmove(&item->bbox.top, p, sizeof(double)); p+= sizeof(double);
278       item->bbox.left += hoffset;
279       item->bbox.top += voffset;
280       g_memmove(&len, p, sizeof(int)); p+= sizeof(int);
281       item->text = g_malloc(len+1);
282       g_memmove(item->text, p, len+1); p+= len+1;
283       g_memmove(&len, p, sizeof(int)); p+= sizeof(int);
284       item->font_name = g_malloc(len+1);
285       g_memmove(item->font_name, p, len+1); p+= len+1;
286       g_memmove(&item->font_size, p, sizeof(double)); p+= sizeof(double);
287       make_canvas_item_one(ui.cur_layer->group, item);
288     }
289     if (item->type == ITEM_IMAGE) {
290       item->canvas_item = NULL;
291       item->image_png = NULL;
292       item->image_png_len = 0;
293       g_memmove(&item->bbox, p, sizeof(struct BBox)); p+= sizeof(struct BBox);
294       item->bbox.left += hoffset;
295       item->bbox.right += hoffset;
296       item->bbox.top += voffset;
297       item->bbox.bottom += voffset;
298       g_memmove(&item->image_png_len, p, sizeof(gsize)); p+= sizeof(gsize);
299       if (item->image_png_len > 0) {
300         item->image_png = g_memdup(p, item->image_png_len);
301         item->image = pixbuf_from_buffer(item->image_png, item->image_png_len);
302         p+= item->image_png_len;
303       } else {
304         item->image = NULL;
305       }
306       make_canvas_item_one(ui.cur_layer->group, item);
307     }
308   }
309
310   prepare_new_undo();
311   undo->type = ITEM_PASTE;
312   undo->layer = ui.cur_layer;
313   undo->itemlist = g_list_copy(ui.selection->items);  
314   
315   gtk_selection_data_free(sel_data);
316   update_copy_paste_enabled();
317   update_color_menu();
318   update_thickness_buttons();
319   update_color_buttons();
320   update_font_button();  
321   update_cursor(); // FIXME: can't know if pointer is within selection!
322 }
323
324 // paste external text
325 void clipboard_paste_text(gchar *text)
326 {
327   struct Item *item;
328   double pt[2];
329
330   reset_selection();
331   get_current_pointer_coords(pt);
332   set_current_page(pt);  
333
334   ui.selection = g_new(struct Selection, 1);
335   ui.selection->type = ITEM_SELECTRECT;
336   ui.selection->layer = ui.cur_layer;
337   ui.selection->items = NULL;
338
339   item = g_new(struct Item, 1);
340   ui.selection->items = g_list_append(ui.selection->items, item);
341   ui.cur_layer->items = g_list_append(ui.cur_layer->items, item);
342   ui.cur_layer->nitems++;
343   item->type = ITEM_TEXT;
344   g_memmove(&(item->brush), ui.cur_brush, sizeof(struct Brush));
345   item->text = text; // text was newly allocated, we keep it
346   item->font_name = g_strdup(ui.font_name);
347   item->font_size = ui.font_size;
348   item->bbox.left = pt[0]; item->bbox.top = pt[1];
349   make_canvas_item_one(ui.cur_layer->group, item);
350   update_item_bbox(item);
351
352   // move the text to fit on the page if needed
353   if (item->bbox.right > ui.cur_page->width) item->bbox.left += ui.cur_page->width-item->bbox.right;
354   if (item->bbox.left < 0) item->bbox.left = 0;
355   if (item->bbox.bottom > ui.cur_page->height) item->bbox.top += ui.cur_page->height-item->bbox.bottom;
356   if (item->bbox.top < 0) item->bbox.top = 0;
357   gnome_canvas_item_set(item->canvas_item, "x", item->bbox.left, "y", item->bbox.top, NULL);
358   update_item_bbox(item);
359   
360   ui.selection->bbox = item->bbox;
361   ui.selection->canvas_item = gnome_canvas_item_new(ui.cur_layer->group,
362       gnome_canvas_rect_get_type(), "width-pixels", 1,
363       "outline-color-rgba", 0x000000ff,
364       "fill-color-rgba", 0x80808040,
365       "x1", ui.selection->bbox.left, "x2", ui.selection->bbox.right, 
366       "y1", ui.selection->bbox.top, "y2", ui.selection->bbox.bottom, NULL);
367   make_dashed(ui.selection->canvas_item);
368
369   prepare_new_undo();
370   undo->type = ITEM_PASTE;
371   undo->layer = ui.cur_layer;
372   undo->itemlist = g_list_copy(ui.selection->items);  
373   
374   update_copy_paste_enabled();
375   update_color_menu();
376   update_thickness_buttons();
377   update_color_buttons();
378   update_font_button();  
379   update_cursor(); // FIXME: can't know if pointer is within selection!
380 }
381
382 // paste an external image
383 void clipboard_paste_image(GdkPixbuf *pixbuf)
384 {
385   double pt[2];
386
387   reset_selection();
388
389   get_current_pointer_coords(pt);
390   set_current_page(pt);  
391
392   create_image_from_pixbuf(pixbuf, pt);
393 }
394
395 // work out what format the clipboard data is in, and paste accordingly
396 void clipboard_paste(void)
397 {
398   GtkSelectionData *sel_data;
399   GtkClipboard *clipboard;
400   GdkPixbuf *pixbuf;
401   gchar *text;
402
403   if (ui.cur_layer == NULL) return;
404   
405   ui.cur_item_type = ITEM_PASTE;
406   clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
407   // try xournal data
408   sel_data = gtk_clipboard_wait_for_contents(
409       clipboard,
410       gdk_atom_intern(XOURNAL_TARGET_ATOM, FALSE));
411   ui.cur_item_type = ITEM_NONE;
412   if (sel_data != NULL) { 
413     clipboard_paste_from_xournal(sel_data);
414     return;
415   } 
416   // try image data
417   pixbuf = gtk_clipboard_wait_for_image(clipboard);
418   if (pixbuf != NULL) {
419     clipboard_paste_image(pixbuf);
420     return;
421   }
422   // try text data
423   text = gtk_clipboard_wait_for_text(clipboard);
424   if (text != NULL) {
425     clipboard_paste_text(text);
426     return;
427   }
428 }