]> git.donarmstrong.com Git - xournal.git/blob - src/xo-selection.c
split off selection code from xo-paint.c to xo-selection.c
[xournal.git] / src / xo-selection.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 <math.h>
21 #include <string.h>
22 #include <gtk/gtk.h>
23 #include <libgnomecanvas/libgnomecanvas.h>
24 #include <libart_lgpl/art_vpath_dash.h>
25
26 #include "xournal.h"
27 #include "xo-callbacks.h"
28 #include "xo-interface.h"
29 #include "xo-support.h"
30 #include "xo-misc.h"
31 #include "xo-paint.h"
32 #include "xo-selection.h"
33
34 /************ selection tools ***********/
35
36 void make_dashed(GnomeCanvasItem *item)
37 {
38   double dashlen[2];
39   ArtVpathDash dash;
40   
41   dash.n_dash = 2;
42   dash.offset = 3.0;
43   dash.dash = dashlen;
44   dashlen[0] = dashlen[1] = 6.0;
45   gnome_canvas_item_set(item, "dash", &dash, NULL);
46 }
47
48
49 void start_selectrect(GdkEvent *event)
50 {
51   double pt[2];
52   reset_selection();
53   
54   ui.cur_item_type = ITEM_SELECTRECT;
55   ui.selection = g_new(struct Selection, 1);
56   ui.selection->type = ITEM_SELECTRECT;
57   ui.selection->items = NULL;
58   ui.selection->layer = ui.cur_layer;
59
60   get_pointer_coords(event, pt);
61   ui.selection->bbox.left = ui.selection->bbox.right = pt[0];
62   ui.selection->bbox.top = ui.selection->bbox.bottom = pt[1];
63  
64   ui.selection->canvas_item = gnome_canvas_item_new(ui.cur_layer->group,
65       gnome_canvas_rect_get_type(), "width-pixels", 1, 
66       "outline-color-rgba", 0x000000ff,
67       "fill-color-rgba", 0x80808040,
68       "x1", pt[0], "x2", pt[0], "y1", pt[1], "y2", pt[1], NULL);
69   update_cursor();
70 }
71
72 void finalize_selectrect(void)
73 {
74   double x1, x2, y1, y2;
75   GList *itemlist;
76   struct Item *item;
77
78   
79   ui.cur_item_type = ITEM_NONE;
80
81   if (ui.selection->bbox.left > ui.selection->bbox.right) {
82     x1 = ui.selection->bbox.right;  x2 = ui.selection->bbox.left;
83     ui.selection->bbox.left = x1;   ui.selection->bbox.right = x2;
84   } else {
85     x1 = ui.selection->bbox.left;  x2 = ui.selection->bbox.right;
86   }
87
88   if (ui.selection->bbox.top > ui.selection->bbox.bottom) {
89     y1 = ui.selection->bbox.bottom;  y2 = ui.selection->bbox.top;
90     ui.selection->bbox.top = y1;   ui.selection->bbox.bottom = y2;
91   } else {
92     y1 = ui.selection->bbox.top;  y2 = ui.selection->bbox.bottom;
93   }
94   
95   for (itemlist = ui.selection->layer->items; itemlist!=NULL; itemlist = itemlist->next) {
96     item = (struct Item *)itemlist->data;
97     if (item->bbox.left >= x1 && item->bbox.right <= x2 &&
98           item->bbox.top >= y1 && item->bbox.bottom <= y2) {
99       ui.selection->items = g_list_append(ui.selection->items, item); 
100     }
101   }
102   
103   if (ui.selection->items == NULL) {
104     // if we clicked inside a text zone or image?  
105     item = click_is_in_text_or_image(ui.selection->layer, x1, y1);
106     if (item!=NULL && item==click_is_in_text_or_image(ui.selection->layer, x2, y2)) {
107       ui.selection->items = g_list_append(ui.selection->items, item);
108       g_memmove(&(ui.selection->bbox), &(item->bbox), sizeof(struct BBox));
109       gnome_canvas_item_set(ui.selection->canvas_item,
110         "x1", item->bbox.left, "x2", item->bbox.right, 
111         "y1", item->bbox.top, "y2", item->bbox.bottom, NULL);
112     }
113   }
114   
115   if (ui.selection->items == NULL) reset_selection();
116   else make_dashed(ui.selection->canvas_item);
117   update_cursor();
118   update_copy_paste_enabled();
119   update_font_button();
120 }
121
122 gboolean start_movesel(GdkEvent *event)
123 {
124   double pt[2];
125   
126   if (ui.selection==NULL) return FALSE;
127   if (ui.cur_layer != ui.selection->layer) return FALSE;
128   
129   get_pointer_coords(event, pt);
130   if (ui.selection->type == ITEM_SELECTRECT) {
131     if (pt[0]<ui.selection->bbox.left || pt[0]>ui.selection->bbox.right ||
132         pt[1]<ui.selection->bbox.top  || pt[1]>ui.selection->bbox.bottom)
133       return FALSE;
134     ui.cur_item_type = ITEM_MOVESEL;
135     ui.selection->anchor_x = ui.selection->last_x = pt[0];
136     ui.selection->anchor_y = ui.selection->last_y = pt[1];
137     ui.selection->orig_pageno = ui.pageno;
138     ui.selection->move_pageno = ui.pageno;
139     ui.selection->move_layer = ui.selection->layer;
140     ui.selection->move_pagedelta = 0.;
141     gnome_canvas_item_set(ui.selection->canvas_item, "dash", NULL, NULL);
142     update_cursor();
143     return TRUE;
144   }
145   return FALSE;
146 }
147
148 gboolean start_resizesel(GdkEvent *event)
149 {
150   double pt[2], resize_margin, hmargin, vmargin;
151
152   if (ui.selection==NULL) return FALSE;
153   if (ui.cur_layer != ui.selection->layer) return FALSE;
154
155   get_pointer_coords(event, pt);
156
157   if (ui.selection->type == ITEM_SELECTRECT) {
158     resize_margin = RESIZE_MARGIN/ui.zoom;
159     hmargin = (ui.selection->bbox.right-ui.selection->bbox.left)*0.3;
160     if (hmargin>resize_margin) hmargin = resize_margin;
161     vmargin = (ui.selection->bbox.bottom-ui.selection->bbox.top)*0.3;
162     if (vmargin>resize_margin) vmargin = resize_margin;
163
164     // make sure the click is within a box slightly bigger than the selection rectangle
165     if (pt[0]<ui.selection->bbox.left-resize_margin || 
166         pt[0]>ui.selection->bbox.right+resize_margin ||
167         pt[1]<ui.selection->bbox.top-resize_margin || 
168         pt[1]>ui.selection->bbox.bottom+resize_margin)
169       return FALSE;
170
171     // now, if the click is near the edge, it's a resize operation
172     // keep track of which edges we're close to, since those are the ones which should move
173     ui.selection->resizing_left = (pt[0]<ui.selection->bbox.left+hmargin);
174     ui.selection->resizing_right = (pt[0]>ui.selection->bbox.right-hmargin);
175     ui.selection->resizing_top = (pt[1]<ui.selection->bbox.top+vmargin);
176     ui.selection->resizing_bottom = (pt[1]>ui.selection->bbox.bottom-vmargin);
177
178     // we're not near any edge, give up
179     if (!(ui.selection->resizing_left || ui.selection->resizing_right ||
180           ui.selection->resizing_top  || ui.selection->resizing_bottom)) 
181       return FALSE;
182
183     ui.cur_item_type = ITEM_RESIZESEL;
184     ui.selection->new_y1 = ui.selection->bbox.top;
185     ui.selection->new_y2 = ui.selection->bbox.bottom;
186     ui.selection->new_x1 = ui.selection->bbox.left;
187     ui.selection->new_x2 = ui.selection->bbox.right;
188     gnome_canvas_item_set(ui.selection->canvas_item, "dash", NULL, NULL);
189     update_cursor_for_resize(pt);
190     return TRUE;
191   }
192   return FALSE;
193 }
194
195
196 void start_vertspace(GdkEvent *event)
197 {
198   double pt[2];
199   GList *itemlist;
200   struct Item *item;
201
202   reset_selection();
203   ui.cur_item_type = ITEM_MOVESEL_VERT;
204   ui.selection = g_new(struct Selection, 1);
205   ui.selection->type = ITEM_MOVESEL_VERT;
206   ui.selection->items = NULL;
207   ui.selection->layer = ui.cur_layer;
208
209   get_pointer_coords(event, pt);
210   ui.selection->bbox.top = ui.selection->bbox.bottom = pt[1];
211   for (itemlist = ui.cur_layer->items; itemlist!=NULL; itemlist = itemlist->next) {
212     item = (struct Item *)itemlist->data;
213     if (item->bbox.top >= pt[1]) {
214       ui.selection->items = g_list_append(ui.selection->items, item); 
215       if (item->bbox.bottom > ui.selection->bbox.bottom)
216         ui.selection->bbox.bottom = item->bbox.bottom;
217     }
218   }
219
220   ui.selection->anchor_x = ui.selection->last_x = 0;
221   ui.selection->anchor_y = ui.selection->last_y = pt[1];
222   ui.selection->orig_pageno = ui.pageno;
223   ui.selection->move_pageno = ui.pageno;
224   ui.selection->move_layer = ui.selection->layer;
225   ui.selection->move_pagedelta = 0.;
226   ui.selection->canvas_item = gnome_canvas_item_new(ui.cur_layer->group,
227       gnome_canvas_rect_get_type(), "width-pixels", 1, 
228       "outline-color-rgba", 0x000000ff,
229       "fill-color-rgba", 0x80808040,
230       "x1", -100.0, "x2", ui.cur_page->width+100, "y1", pt[1], "y2", pt[1], NULL);
231   update_cursor();
232 }
233
234 void continue_movesel(GdkEvent *event)
235 {
236   double pt[2], dx, dy, upmargin;
237   GList *list;
238   struct Item *item;
239   int tmppageno;
240   struct Page *tmppage;
241   
242   get_pointer_coords(event, pt);
243   if (ui.cur_item_type == ITEM_MOVESEL_VERT) pt[0] = 0;
244   pt[1] += ui.selection->move_pagedelta;
245
246   // check for page jumps
247   if (ui.cur_item_type == ITEM_MOVESEL_VERT)
248     upmargin = ui.selection->bbox.bottom - ui.selection->bbox.top;
249   else upmargin = VIEW_CONTINUOUS_SKIP;
250   tmppageno = ui.selection->move_pageno;
251   tmppage = g_list_nth_data(journal.pages, tmppageno);
252   while (ui.view_continuous && (pt[1] < - upmargin)) {
253     if (tmppageno == 0) break;
254     tmppageno--;
255     tmppage = g_list_nth_data(journal.pages, tmppageno);
256     pt[1] += tmppage->height + VIEW_CONTINUOUS_SKIP;
257     ui.selection->move_pagedelta += tmppage->height + VIEW_CONTINUOUS_SKIP;
258   }
259   while (ui.view_continuous && (pt[1] > tmppage->height+VIEW_CONTINUOUS_SKIP)) {
260     if (tmppageno == journal.npages-1) break;
261     pt[1] -= tmppage->height + VIEW_CONTINUOUS_SKIP;
262     ui.selection->move_pagedelta -= tmppage->height + VIEW_CONTINUOUS_SKIP;
263     tmppageno++;
264     tmppage = g_list_nth_data(journal.pages, tmppageno);
265   }
266   
267   if (tmppageno != ui.selection->move_pageno) {
268     // move to a new page !
269     ui.selection->move_pageno = tmppageno;
270     if (tmppageno == ui.selection->orig_pageno)
271       ui.selection->move_layer = ui.selection->layer;
272     else
273       ui.selection->move_layer = (struct Layer *)(g_list_last(
274         ((struct Page *)g_list_nth_data(journal.pages, tmppageno))->layers)->data);
275     gnome_canvas_item_reparent(ui.selection->canvas_item, ui.selection->move_layer->group);
276     for (list = ui.selection->items; list!=NULL; list = list->next) {
277       item = (struct Item *)list->data;
278       if (item->canvas_item!=NULL)
279         gnome_canvas_item_reparent(item->canvas_item, ui.selection->move_layer->group);
280     }
281     // avoid a refresh bug
282     gnome_canvas_item_move(GNOME_CANVAS_ITEM(ui.selection->move_layer->group), 0., 0.);
283     if (ui.cur_item_type == ITEM_MOVESEL_VERT)
284       gnome_canvas_item_set(ui.selection->canvas_item,
285         "x2", tmppage->width+100, 
286         "y1", ui.selection->anchor_y+ui.selection->move_pagedelta, NULL);
287   }
288   
289   // now, process things normally
290
291   dx = pt[0] - ui.selection->last_x;
292   dy = pt[1] - ui.selection->last_y;
293   if (hypot(dx,dy) < 1) return; // don't move subpixel
294   ui.selection->last_x = pt[0];
295   ui.selection->last_y = pt[1];
296
297   // move the canvas items
298   if (ui.cur_item_type == ITEM_MOVESEL_VERT)
299     gnome_canvas_item_set(ui.selection->canvas_item, "y2", pt[1], NULL);
300   else 
301     gnome_canvas_item_move(ui.selection->canvas_item, dx, dy);
302   
303   for (list = ui.selection->items; list != NULL; list = list->next) {
304     item = (struct Item *)list->data;
305     if (item->canvas_item != NULL)
306       gnome_canvas_item_move(item->canvas_item, dx, dy);
307   }
308 }
309
310 void continue_resizesel(GdkEvent *event)
311 {
312   double pt[2];
313
314   get_pointer_coords(event, pt);
315
316   if (ui.selection->resizing_top) ui.selection->new_y1 = pt[1];
317   if (ui.selection->resizing_bottom) ui.selection->new_y2 = pt[1];
318   if (ui.selection->resizing_left) ui.selection->new_x1 = pt[0];
319   if (ui.selection->resizing_right) ui.selection->new_x2 = pt[0];
320
321   gnome_canvas_item_set(ui.selection->canvas_item, 
322     "x1", ui.selection->new_x1, "x2", ui.selection->new_x2,
323     "y1", ui.selection->new_y1, "y2", ui.selection->new_y2, NULL);
324 }
325
326 void finalize_movesel(void)
327 {
328   GList *list, *link;
329   
330   if (ui.selection->items != NULL) {
331     prepare_new_undo();
332     undo->type = ITEM_MOVESEL;
333     undo->itemlist = g_list_copy(ui.selection->items);
334     undo->val_x = ui.selection->last_x - ui.selection->anchor_x;
335     undo->val_y = ui.selection->last_y - ui.selection->anchor_y;
336     undo->layer = ui.selection->layer;
337     undo->layer2 = ui.selection->move_layer;
338     undo->auxlist = NULL;
339     // build auxlist = pointers to Item's just before ours (for depths)
340     for (list = ui.selection->items; list!=NULL; list = list->next) {
341       link = g_list_find(ui.selection->layer->items, list->data);
342       if (link!=NULL) link = link->prev;
343       undo->auxlist = g_list_append(undo->auxlist, ((link!=NULL) ? link->data : NULL));
344     }
345     ui.selection->layer = ui.selection->move_layer;
346     move_journal_items_by(undo->itemlist, undo->val_x, undo->val_y,
347                           undo->layer, undo->layer2, 
348                           (undo->layer == undo->layer2)?undo->auxlist:NULL);
349   }
350
351   if (ui.selection->move_pageno!=ui.selection->orig_pageno) 
352     do_switch_page(ui.selection->move_pageno, FALSE, FALSE);
353     
354   if (ui.cur_item_type == ITEM_MOVESEL_VERT)
355     reset_selection();
356   else {
357     ui.selection->bbox.left += undo->val_x;
358     ui.selection->bbox.right += undo->val_x;
359     ui.selection->bbox.top += undo->val_y;
360     ui.selection->bbox.bottom += undo->val_y;
361     make_dashed(ui.selection->canvas_item);
362     /* update selection box object's offset to be trivial, and its internal 
363        coordinates to agree with those of the bbox; need this since resize
364        operations will modify the box by setting its coordinates directly */
365     gnome_canvas_item_affine_absolute(ui.selection->canvas_item, NULL);
366     gnome_canvas_item_set(ui.selection->canvas_item, 
367       "x1", ui.selection->bbox.left, "x2", ui.selection->bbox.right,
368       "y1", ui.selection->bbox.top, "y2", ui.selection->bbox.bottom, NULL);
369   }
370   ui.cur_item_type = ITEM_NONE;
371   update_cursor();
372 }
373
374 #define SCALING_EPSILON 0.001
375
376 void finalize_resizesel(void)
377 {
378   struct Item *item;
379
380   // build the affine transformation
381   double offset_x, offset_y, scaling_x, scaling_y;
382   scaling_x = (ui.selection->new_x2 - ui.selection->new_x1) / 
383               (ui.selection->bbox.right - ui.selection->bbox.left);
384   scaling_y = (ui.selection->new_y2 - ui.selection->new_y1) /
385               (ui.selection->bbox.bottom - ui.selection->bbox.top);
386   // couldn't undo a resize-by-zero...
387   if (fabs(scaling_x)<SCALING_EPSILON) scaling_x = SCALING_EPSILON;
388   if (fabs(scaling_y)<SCALING_EPSILON) scaling_y = SCALING_EPSILON;
389   offset_x = ui.selection->new_x1 - ui.selection->bbox.left * scaling_x;
390   offset_y = ui.selection->new_y1 - ui.selection->bbox.top * scaling_y;
391
392   if (ui.selection->items != NULL) {
393     // create the undo information
394     prepare_new_undo();
395     undo->type = ITEM_RESIZESEL;
396     undo->itemlist = g_list_copy(ui.selection->items);
397     undo->auxlist = NULL;
398
399     undo->scaling_x = scaling_x;
400     undo->scaling_y = scaling_y;
401     undo->val_x = offset_x;
402     undo->val_y = offset_y;
403
404     // actually do the resize operation
405     resize_journal_items_by(ui.selection->items, scaling_x, scaling_y, offset_x, offset_y);
406   }
407
408   if (scaling_x>0) {
409     ui.selection->bbox.left = ui.selection->new_x1;
410     ui.selection->bbox.right = ui.selection->new_x2;
411   } else {
412     ui.selection->bbox.left = ui.selection->new_x2;
413     ui.selection->bbox.right = ui.selection->new_x1;
414   }
415   if (scaling_y>0) {
416     ui.selection->bbox.top = ui.selection->new_y1;
417     ui.selection->bbox.bottom = ui.selection->new_y2;
418   } else {
419     ui.selection->bbox.top = ui.selection->new_y2;
420     ui.selection->bbox.bottom = ui.selection->new_y1;
421   }
422   make_dashed(ui.selection->canvas_item);
423
424   ui.cur_item_type = ITEM_NONE;
425   update_cursor();
426 }
427
428 void selection_delete(void)
429 {
430   struct UndoErasureData *erasure;
431   GList *itemlist;
432   struct Item *item;
433   
434   if (ui.selection == NULL) return;
435   prepare_new_undo();
436   undo->type = ITEM_ERASURE;
437   undo->layer = ui.selection->layer;
438   undo->erasurelist = NULL;
439   for (itemlist = ui.selection->items; itemlist!=NULL; itemlist = itemlist->next) {
440     item = (struct Item *)itemlist->data;
441     if (item->canvas_item!=NULL)
442       gtk_object_destroy(GTK_OBJECT(item->canvas_item));
443     erasure = g_new(struct UndoErasureData, 1);
444     erasure->item = item;
445     erasure->npos = g_list_index(ui.selection->layer->items, item);
446     erasure->nrepl = 0;
447     erasure->replacement_items = NULL;
448     ui.selection->layer->items = g_list_remove(ui.selection->layer->items, item);
449     ui.selection->layer->nitems--;
450     undo->erasurelist = g_list_prepend(undo->erasurelist, erasure);
451   }
452   reset_selection();
453
454   /* NOTE: the erasurelist is built backwards; this guarantees that,
455      upon undo, the erasure->npos fields give the correct position
456      where each item should be reinserted as the list is traversed in
457      the forward direction */
458 }
459
460 // modify the color or thickness of pen strokes in a selection
461
462 void recolor_selection(int color_no, guint color_rgba)
463 {
464   GList *itemlist;
465   struct Item *item;
466   struct Brush *brush;
467   GnomeCanvasGroup *group;
468   
469   if (ui.selection == NULL) return;
470   prepare_new_undo();
471   undo->type = ITEM_REPAINTSEL;
472   undo->itemlist = NULL;
473   undo->auxlist = NULL;
474   for (itemlist = ui.selection->items; itemlist!=NULL; itemlist = itemlist->next) {
475     item = (struct Item *)itemlist->data;
476     if (item->type != ITEM_STROKE && item->type != ITEM_TEXT) continue;
477     if (item->type == ITEM_STROKE && item->brush.tool_type!=TOOL_PEN) continue;
478     // store info for undo
479     undo->itemlist = g_list_append(undo->itemlist, item);
480     brush = (struct Brush *)g_malloc(sizeof(struct Brush));
481     g_memmove(brush, &(item->brush), sizeof(struct Brush));
482     undo->auxlist = g_list_append(undo->auxlist, brush);
483     // repaint the stroke
484     item->brush.color_no = color_no;
485     item->brush.color_rgba = color_rgba | 0xff; // no alpha
486     if (item->canvas_item!=NULL) {
487       if (!item->brush.variable_width)
488         gnome_canvas_item_set(item->canvas_item, 
489            "fill-color-rgba", item->brush.color_rgba, NULL);
490       else {
491         group = (GnomeCanvasGroup *) item->canvas_item->parent;
492         gtk_object_destroy(GTK_OBJECT(item->canvas_item));
493         make_canvas_item_one(group, item);
494       }
495     }
496   }
497 }
498
499 void rethicken_selection(int val)
500 {
501   GList *itemlist;
502   struct Item *item;
503   struct Brush *brush;
504   GnomeCanvasGroup *group;
505   
506   if (ui.selection == NULL) return;
507   prepare_new_undo();
508   undo->type = ITEM_REPAINTSEL;
509   undo->itemlist = NULL;
510   undo->auxlist = NULL;
511   for (itemlist = ui.selection->items; itemlist!=NULL; itemlist = itemlist->next) {
512     item = (struct Item *)itemlist->data;
513     if (item->type != ITEM_STROKE || item->brush.tool_type!=TOOL_PEN) continue;
514     // store info for undo
515     undo->itemlist = g_list_append(undo->itemlist, item);
516     brush = (struct Brush *)g_malloc(sizeof(struct Brush));
517     g_memmove(brush, &(item->brush), sizeof(struct Brush));
518     undo->auxlist = g_list_append(undo->auxlist, brush);
519     // repaint the stroke
520     item->brush.thickness_no = val;
521     item->brush.thickness = predef_thickness[TOOL_PEN][val];
522     if (item->canvas_item!=NULL) {
523       if (!item->brush.variable_width)
524         gnome_canvas_item_set(item->canvas_item, 
525            "width-units", item->brush.thickness, NULL);
526       else {
527         group = (GnomeCanvasGroup *) item->canvas_item->parent;
528         gtk_object_destroy(GTK_OBJECT(item->canvas_item));
529         item->brush.variable_width = FALSE;
530         make_canvas_item_one(group, item);
531       }
532     }
533   }
534 }
535