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.
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.
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/>.
23 #include <libgnomecanvas/libgnomecanvas.h>
26 #include "xo-callbacks.h"
27 #include "xo-interface.h"
28 #include "xo-support.h"
32 /***** Win32 fix for gdk_cursor_new_from_pixmap() by Dirk Gerrits ****/
35 gboolean colors_too_similar(const GdkColor *colora, const GdkColor *colorb)
37 return (abs(colora->red - colorb->red) < 256 &&
38 abs(colora->green - colorb->green) < 256 &&
39 abs(colora->blue - colorb->blue) < 256);
42 /* gdk_cursor_new_from_pixmap is broken on Windows.
43 this is a workaround using gdk_cursor_new_from_pixbuf. */
44 GdkCursor* fixed_gdk_cursor_new_from_pixmap(GdkPixmap *source, GdkPixmap *mask,
45 const GdkColor *fg, const GdkColor *bg,
48 GdkPixmap *rgb_pixmap;
50 GdkPixbuf *rgb_pixbuf, *rgba_pixbuf;
54 /* HACK! It seems impossible to work with RGBA pixmaps directly in
55 GDK-Win32. Instead we pick some third color, different from fg
56 and bg, and use that as the 'transparent color'. We do this using
57 colors_too_similar (see above) because two colors could be
58 unequal in GdkColor's 16-bit/sample, but equal in GdkPixbuf's
60 GdkColor candidates[3] = {{0,65535,0,0}, {0,0,65535,0}, {0,0,0,65535}};
61 GdkColor *trans = &candidates[0];
62 if (colors_too_similar(trans, fg) || colors_too_similar(trans, bg)) {
63 trans = &candidates[1];
64 if (colors_too_similar(trans, fg) || colors_too_similar(trans, bg)) {
65 trans = &candidates[2];
67 } /* trans is now guaranteed to be unique from fg and bg */
69 /* create an empty pixmap to hold the cursor image */
70 gdk_drawable_get_size(source, &width, &height);
71 rgb_pixmap = gdk_pixmap_new(NULL, width, height, 24);
73 /* blit the bitmaps defining the cursor onto a transparent background */
74 gc = gdk_gc_new(rgb_pixmap);
75 gdk_gc_set_fill(gc, GDK_SOLID);
76 gdk_gc_set_rgb_fg_color(gc, trans);
77 gdk_draw_rectangle(rgb_pixmap, gc, TRUE, 0, 0, width, height);
78 gdk_gc_set_fill(gc, GDK_OPAQUE_STIPPLED);
79 gdk_gc_set_stipple(gc, source);
80 gdk_gc_set_clip_mask(gc, mask);
81 gdk_gc_set_rgb_fg_color(gc, fg);
82 gdk_gc_set_rgb_bg_color(gc, bg);
83 gdk_draw_rectangle(rgb_pixmap, gc, TRUE, 0, 0, width, height);
86 /* create a cursor out of the created pixmap */
87 rgb_pixbuf = gdk_pixbuf_get_from_drawable(
88 NULL, rgb_pixmap, gdk_colormap_get_system(), 0, 0, 0, 0, width, height);
89 gdk_pixmap_unref(rgb_pixmap);
90 rgba_pixbuf = gdk_pixbuf_add_alpha(
91 rgb_pixbuf, TRUE, trans->red, trans->green, trans->blue);
92 gdk_pixbuf_unref(rgb_pixbuf);
93 cursor = gdk_cursor_new_from_pixbuf(gdk_display_get_default(), rgba_pixbuf, x, y);
94 gdk_pixbuf_unref(rgba_pixbuf);
98 #define gdk_cursor_new_from_pixmap fixed_gdk_cursor_new_from_pixmap
102 /************** drawing nice cursors *********/
104 static char cursor_pen_bits[] = {
105 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
106 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
107 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
109 static char cursor_eraser_bits[] = {
110 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x0f, 0x08, 0x08, 0x08, 0x08,
111 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0xf8, 0x0f,
112 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
114 static char cursor_eraser_mask[] = {
115 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x0f, 0xf8, 0x0f, 0xf8, 0x0f,
116 0xf8, 0x0f, 0xf8, 0x0f, 0xf8, 0x0f, 0xf8, 0x0f, 0xf8, 0x0f, 0xf8, 0x0f,
117 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
119 void set_cursor_busy(gboolean busy)
124 cursor = gdk_cursor_new(GDK_WATCH);
125 gdk_window_set_cursor(GTK_WIDGET(winMain)->window, cursor);
126 gdk_window_set_cursor(GTK_WIDGET(canvas)->window, cursor);
127 gdk_cursor_unref(cursor);
130 gdk_window_set_cursor(GTK_WIDGET(winMain)->window, NULL);
133 gdk_display_sync(gdk_display_get_default());
136 void update_cursor(void)
138 GdkPixmap *source, *mask;
139 GdkColor fg = {0, 0, 0, 0}, bg = {0, 65535, 65535, 65535};
141 ui.is_sel_cursor = FALSE;
142 if (GTK_WIDGET(canvas)->window == NULL) return;
144 if (ui.cursor!=NULL) {
145 gdk_cursor_unref(ui.cursor);
148 if (ui.cur_item_type == ITEM_MOVESEL_VERT)
149 ui.cursor = gdk_cursor_new(GDK_SB_V_DOUBLE_ARROW);
150 else if (ui.cur_item_type == ITEM_MOVESEL)
151 ui.cursor = gdk_cursor_new(GDK_FLEUR);
152 else if (ui.toolno[ui.cur_mapping] == TOOL_PEN) {
153 fg.red = (ui.cur_brush->color_rgba >> 16) & 0xff00;
154 fg.green = (ui.cur_brush->color_rgba >> 8) & 0xff00;
155 fg.blue = (ui.cur_brush->color_rgba >> 0) & 0xff00;
156 source = gdk_bitmap_create_from_data(NULL, cursor_pen_bits, 16, 16);
157 ui.cursor = gdk_cursor_new_from_pixmap(source, source, &fg, &bg, 7, 7);
158 gdk_bitmap_unref(source);
160 else if (ui.toolno[ui.cur_mapping] == TOOL_ERASER) {
161 source = gdk_bitmap_create_from_data(NULL, cursor_eraser_bits, 16, 16);
162 mask = gdk_bitmap_create_from_data(NULL, cursor_eraser_mask, 16, 16);
163 ui.cursor = gdk_cursor_new_from_pixmap(source, mask, &fg, &bg, 7, 7);
164 gdk_bitmap_unref(source);
165 gdk_bitmap_unref(mask);
167 else if (ui.toolno[ui.cur_mapping] == TOOL_HIGHLIGHTER) {
168 source = gdk_bitmap_create_from_data(NULL, cursor_eraser_bits, 16, 16);
169 mask = gdk_bitmap_create_from_data(NULL, cursor_eraser_mask, 16, 16);
170 bg.red = (ui.cur_brush->color_rgba >> 16) & 0xff00;
171 bg.green = (ui.cur_brush->color_rgba >> 8) & 0xff00;
172 bg.blue = (ui.cur_brush->color_rgba >> 0) & 0xff00;
173 ui.cursor = gdk_cursor_new_from_pixmap(source, mask, &fg, &bg, 7, 7);
174 gdk_bitmap_unref(source);
175 gdk_bitmap_unref(mask);
177 else if (ui.cur_item_type == ITEM_SELECTRECT) {
178 ui.cursor = gdk_cursor_new(GDK_TCROSS);
180 else if (ui.toolno[ui.cur_mapping] == TOOL_HAND) {
181 ui.cursor = gdk_cursor_new(GDK_HAND1);
183 else if (ui.toolno[ui.cur_mapping] == TOOL_TEXT) {
184 ui.cursor = gdk_cursor_new(GDK_XTERM);
187 gdk_window_set_cursor(GTK_WIDGET(canvas)->window, ui.cursor);
190 /* adjust the cursor shape if it hovers near a selection box */
192 void update_cursor_for_resize(double *pt)
194 gboolean in_range_x, in_range_y;
195 gboolean can_resize_left, can_resize_right, can_resize_bottom, can_resize_top;
196 gdouble resize_margin;
197 GdkCursorType newcursor;
199 // if we're not even close to the box in some direction, return immediately
200 resize_margin = RESIZE_MARGIN / ui.zoom;
201 if (pt[0]<ui.selection->bbox.left-resize_margin || pt[0]>ui.selection->bbox.right+resize_margin
202 || pt[1]<ui.selection->bbox.top-resize_margin || pt[1]>ui.selection->bbox.bottom+resize_margin)
204 if (ui.is_sel_cursor) update_cursor();
208 ui.is_sel_cursor = TRUE;
209 can_resize_left = (pt[0] < ui.selection->bbox.left+resize_margin);
210 can_resize_right = (pt[0] > ui.selection->bbox.right-resize_margin);
211 can_resize_top = (pt[1] < ui.selection->bbox.top+resize_margin);
212 can_resize_bottom = (pt[1] > ui.selection->bbox.bottom-resize_margin);
214 if (can_resize_left) {
215 if (can_resize_top) newcursor = GDK_TOP_LEFT_CORNER;
216 else if (can_resize_bottom) newcursor = GDK_BOTTOM_LEFT_CORNER;
217 else newcursor = GDK_LEFT_SIDE;
219 else if (can_resize_right) {
220 if (can_resize_top) newcursor = GDK_TOP_RIGHT_CORNER;
221 else if (can_resize_bottom) newcursor = GDK_BOTTOM_RIGHT_CORNER;
222 else newcursor = GDK_RIGHT_SIDE;
224 else if (can_resize_top) newcursor = GDK_TOP_SIDE;
225 else if (can_resize_bottom) newcursor = GDK_BOTTOM_SIDE;
226 else newcursor = GDK_FLEUR;
228 if (ui.cursor!=NULL && ui.cursor->type == newcursor) return;
229 if (ui.cursor!=NULL) gdk_cursor_unref(ui.cursor);
230 ui.cursor = gdk_cursor_new(newcursor);
231 gdk_window_set_cursor(GTK_WIDGET(canvas)->window, ui.cursor);
234 /************** painting strokes *************/
236 #define SUBDIVIDE_MAXDIST 5.0
238 void subdivide_cur_path(null)
242 double x0, y0, x1, y1;
244 for (n=0, p=ui.cur_path.coords; n<ui.cur_path.num_points-1; n++, p+=2) {
245 pieces = (int)floor(hypot(p[2]-p[0], p[3]-p[1])/SUBDIVIDE_MAXDIST);
247 x0 = p[0]; y0 = p[1];
248 x1 = p[2]; y1 = p[3];
249 realloc_cur_path(ui.cur_path.num_points+pieces-1);
250 g_memmove(ui.cur_path.coords+2*(n+pieces), ui.cur_path.coords+2*(n+1),
251 2*(ui.cur_path.num_points-n-1)*sizeof(double));
252 p = ui.cur_path.coords+2*n;
253 ui.cur_path.num_points += pieces-1;
255 for (k=1; k<pieces; k++) {
257 p[0] = x0 + k*(x1-x0)/pieces;
258 p[1] = y0 + k*(y1-y0)/pieces;
264 void create_new_stroke(GdkEvent *event)
266 ui.cur_item_type = ITEM_STROKE;
267 ui.cur_item = g_new(struct Item, 1);
268 ui.cur_item->type = ITEM_STROKE;
269 g_memmove(&(ui.cur_item->brush), ui.cur_brush, sizeof(struct Brush));
270 ui.cur_item->path = &ui.cur_path;
272 ui.cur_path.num_points = 1;
273 get_pointer_coords(event, ui.cur_path.coords);
275 if (ui.cur_brush->ruler) {
276 ui.cur_item->canvas_item = gnome_canvas_item_new(ui.cur_layer->group,
277 gnome_canvas_line_get_type(),
278 "cap-style", GDK_CAP_ROUND, "join-style", GDK_JOIN_ROUND,
279 "fill-color-rgba", ui.cur_item->brush.color_rgba,
280 "width-units", ui.cur_item->brush.thickness, NULL);
281 ui.cur_item->brush.variable_width = FALSE;
283 ui.cur_item->canvas_item = gnome_canvas_item_new(
284 ui.cur_layer->group, gnome_canvas_group_get_type(), NULL);
287 void continue_stroke(GdkEvent *event)
289 GnomeCanvasPoints seg;
290 double *pt, current_width;
292 if (ui.cur_brush->ruler) {
293 pt = ui.cur_path.coords;
295 realloc_cur_path(ui.cur_path.num_points+1);
296 pt = ui.cur_path.coords + 2*(ui.cur_path.num_points-1);
299 get_pointer_coords(event, pt+2);
301 if (ui.cur_item->brush.variable_width) {
302 realloc_cur_widths(ui.cur_path.num_points);
303 current_width = ui.cur_item->brush.thickness*get_pressure_multiplier(event);
304 ui.cur_widths[ui.cur_path.num_points-1] = current_width;
306 else current_width = ui.cur_item->brush.thickness;
308 if (ui.cur_brush->ruler)
309 ui.cur_path.num_points = 2;
311 if (hypot(pt[0]-pt[2], pt[1]-pt[3]) < PIXEL_MOTION_THRESHOLD/ui.zoom)
312 return; // not a meaningful motion
313 ui.cur_path.num_points++;
320 /* note: we're using a piece of the cur_path array. This is ok because
321 upon creation the line just copies the contents of the GnomeCanvasPoints
322 into an internal structure */
324 if (ui.cur_brush->ruler)
325 gnome_canvas_item_set(ui.cur_item->canvas_item, "points", &seg, NULL);
327 gnome_canvas_item_new((GnomeCanvasGroup *)ui.cur_item->canvas_item,
328 gnome_canvas_line_get_type(), "points", &seg,
329 "cap-style", GDK_CAP_ROUND, "join-style", GDK_JOIN_ROUND,
330 "fill-color-rgba", ui.cur_item->brush.color_rgba,
331 "width-units", current_width, NULL);
334 void finalize_stroke(void)
336 if (ui.cur_path.num_points == 1) { // GnomeCanvas doesn't like num_points=1
337 ui.cur_path.coords[2] = ui.cur_path.coords[0]+0.1;
338 ui.cur_path.coords[3] = ui.cur_path.coords[1];
339 ui.cur_path.num_points = 2;
340 ui.cur_item->brush.variable_width = FALSE;
343 if (!ui.cur_item->brush.variable_width)
344 subdivide_cur_path(); // split the segment so eraser will work
346 ui.cur_item->path = gnome_canvas_points_new(ui.cur_path.num_points);
347 g_memmove(ui.cur_item->path->coords, ui.cur_path.coords,
348 2*ui.cur_path.num_points*sizeof(double));
349 if (ui.cur_item->brush.variable_width)
350 ui.cur_item->widths = (gdouble *)g_memdup(ui.cur_widths,
351 (ui.cur_path.num_points-1)*sizeof(gdouble));
352 else ui.cur_item->widths = NULL;
353 update_item_bbox(ui.cur_item);
354 ui.cur_path.num_points = 0;
356 if (!ui.cur_item->brush.variable_width) {
357 // destroy the entire group of temporary line segments
358 gtk_object_destroy(GTK_OBJECT(ui.cur_item->canvas_item));
359 // make a new line item to replace it
360 make_canvas_item_one(ui.cur_layer->group, ui.cur_item);
363 // add undo information
365 undo->type = ITEM_STROKE;
366 undo->item = ui.cur_item;
367 undo->layer = ui.cur_layer;
369 // store the item on top of the layer stack
370 ui.cur_layer->items = g_list_append(ui.cur_layer->items, ui.cur_item);
371 ui.cur_layer->nitems++;
373 ui.cur_item_type = ITEM_NONE;
376 /************** eraser tool *************/
378 void erase_stroke_portions(struct Item *item, double x, double y, double radius,
379 gboolean whole_strokes, struct UndoErasureData *erasure)
383 struct Item *newhead, *newtail;
384 gboolean need_recalc = FALSE;
386 for (i=0, pt=item->path->coords; i<item->path->num_points; i++, pt+=2) {
387 if (hypot(pt[0]-x, pt[1]-y) <= radius) { // found an intersection
388 // FIXME: need to test if line SEGMENT hits the circle
389 // hide the canvas item, and create erasure data if needed
390 if (erasure == NULL) {
391 item->type = ITEM_TEMP_STROKE;
392 gnome_canvas_item_hide(item->canvas_item);
393 /* we'll use this hidden item as an insertion point later */
394 erasure = (struct UndoErasureData *)g_malloc(sizeof(struct UndoErasureData));
395 item->erasure = erasure;
396 erasure->item = item;
397 erasure->npos = g_list_index(ui.cur_layer->items, item);
399 erasure->replacement_items = NULL;
402 newhead = newtail = NULL;
403 if (!whole_strokes) {
405 newhead = (struct Item *)g_malloc(sizeof(struct Item));
406 newhead->type = ITEM_STROKE;
407 g_memmove(&newhead->brush, &item->brush, sizeof(struct Brush));
408 newhead->path = gnome_canvas_points_new(i);
409 g_memmove(newhead->path->coords, item->path->coords, 2*i*sizeof(double));
410 if (newhead->brush.variable_width)
411 newhead->widths = (gdouble *)g_memdup(item->widths, (i-1)*sizeof(gdouble));
412 else newhead->widths = NULL;
414 while (++i < item->path->num_points) {
416 if (hypot(pt[0]-x, pt[1]-y) > radius) break;
418 if (i<item->path->num_points-1) {
419 newtail = (struct Item *)g_malloc(sizeof(struct Item));
420 newtail->type = ITEM_STROKE;
421 g_memmove(&newtail->brush, &item->brush, sizeof(struct Brush));
422 newtail->path = gnome_canvas_points_new(item->path->num_points-i);
423 g_memmove(newtail->path->coords, item->path->coords+2*i,
424 2*(item->path->num_points-i)*sizeof(double));
425 if (newtail->brush.variable_width)
426 newtail->widths = (gdouble *)g_memdup(item->widths+i,
427 (item->path->num_points-i-1)*sizeof(gdouble));
428 else newtail->widths = NULL;
429 newtail->canvas_item = NULL;
432 if (item->type == ITEM_STROKE) {
433 // it's inside an erasure list - we destroy it
434 gnome_canvas_points_free(item->path);
435 if (item->brush.variable_width) g_free(item->widths);
436 if (item->canvas_item != NULL)
437 gtk_object_destroy(GTK_OBJECT(item->canvas_item));
439 erasure->replacement_items = g_list_remove(erasure->replacement_items, item);
443 if (newhead != NULL) {
444 update_item_bbox(newhead);
445 make_canvas_item_one(ui.cur_layer->group, newhead);
446 lower_canvas_item_to(ui.cur_layer->group,
447 newhead->canvas_item, erasure->item->canvas_item);
448 erasure->replacement_items = g_list_prepend(erasure->replacement_items, newhead);
450 // prepending ensures it won't get processed twice
452 // recurse into the new tail
453 need_recalc = (newtail!=NULL);
454 if (newtail == NULL) break;
456 erasure->replacement_items = g_list_prepend(erasure->replacement_items, newtail);
458 i=0; pt=item->path->coords;
461 // add the tail if needed
462 if (!need_recalc) return;
463 update_item_bbox(item);
464 make_canvas_item_one(ui.cur_layer->group, item);
465 lower_canvas_item_to(ui.cur_layer->group, item->canvas_item,
466 erasure->item->canvas_item);
470 void do_eraser(GdkEvent *event, double radius, gboolean whole_strokes)
472 struct Item *item, *repl;
473 GList *itemlist, *repllist;
475 struct BBox eraserbox;
477 get_pointer_coords(event, pos);
478 eraserbox.left = pos[0]-radius;
479 eraserbox.right = pos[0]+radius;
480 eraserbox.top = pos[1]-radius;
481 eraserbox.bottom = pos[1]+radius;
482 for (itemlist = ui.cur_layer->items; itemlist!=NULL; itemlist = itemlist->next) {
483 item = (struct Item *)itemlist->data;
484 if (item->type == ITEM_STROKE) {
485 if (!have_intersect(&(item->bbox), &eraserbox)) continue;
486 erase_stroke_portions(item, pos[0], pos[1], radius, whole_strokes, NULL);
487 } else if (item->type == ITEM_TEMP_STROKE) {
488 repllist = item->erasure->replacement_items;
489 while (repllist!=NULL) {
490 repl = (struct Item *)repllist->data;
491 // we may delete the item soon! so advance now in the list
492 repllist = repllist->next;
493 if (have_intersect(&(repl->bbox), &eraserbox))
494 erase_stroke_portions(repl, pos[0], pos[1], radius, whole_strokes, item->erasure);
500 void finalize_erasure(void)
502 GList *itemlist, *partlist;
506 undo->type = ITEM_ERASURE;
507 undo->layer = ui.cur_layer;
508 undo->erasurelist = NULL;
510 itemlist = ui.cur_layer->items;
511 while (itemlist!=NULL) {
512 item = (struct Item *)itemlist->data;
513 itemlist = itemlist->next;
514 if (item->type != ITEM_TEMP_STROKE) continue;
515 item->type = ITEM_STROKE;
516 ui.cur_layer->items = g_list_remove(ui.cur_layer->items, item);
517 // the item has an invisible canvas item, which used to act as anchor
518 if (item->canvas_item!=NULL) {
519 gtk_object_destroy(GTK_OBJECT(item->canvas_item));
520 item->canvas_item = NULL;
522 undo->erasurelist = g_list_append(undo->erasurelist, item->erasure);
523 // add the new strokes into the current layer
524 for (partlist = item->erasure->replacement_items; partlist!=NULL; partlist = partlist->next)
525 ui.cur_layer->items = g_list_insert_before(
526 ui.cur_layer->items, itemlist, partlist->data);
527 ui.cur_layer->nitems += item->erasure->nrepl-1;
531 ui.cur_item_type = ITEM_NONE;
533 /* NOTE: the list of erasures goes in the depth order of the layer;
534 this guarantees that, upon undo, the erasure->npos fields give the
535 correct position where each item should be reinserted as the list
536 is traversed in the forward direction */
540 gboolean do_hand_scrollto(gpointer data)
542 ui.hand_scrollto_pending = FALSE;
543 gnome_canvas_scroll_to(canvas, ui.hand_scrollto_cx, ui.hand_scrollto_cy);
547 void do_hand(GdkEvent *event)
552 get_pointer_coords(event, pt);
553 pt[0] += ui.cur_page->hoffset;
554 pt[1] += ui.cur_page->voffset;
555 gnome_canvas_get_scroll_offsets(canvas, &cx, &cy);
556 ui.hand_scrollto_cx = cx - (pt[0]-ui.hand_refpt[0])*ui.zoom;
557 ui.hand_scrollto_cy = cy - (pt[1]-ui.hand_refpt[1])*ui.zoom;
558 if (!ui.hand_scrollto_pending) g_idle_add(do_hand_scrollto, NULL);
559 ui.hand_scrollto_pending = TRUE;
562 /************ TEXT FUNCTIONS **************/
564 // to make it easier to copy/paste at end of text box
565 #define WIDGET_RIGHT_MARGIN 10
567 void resize_textview(gpointer *toplevel, gpointer *data)
572 /* when the text changes, resize the GtkTextView accordingly */
573 if (ui.cur_item_type!=ITEM_TEXT) return;
574 w = GTK_TEXT_VIEW(ui.cur_item->widget);
575 width = w->width + WIDGET_RIGHT_MARGIN;
577 gnome_canvas_item_set(ui.cur_item->canvas_item,
579 "width", (gdouble)width, "height", (gdouble)height, NULL);
580 ui.cur_item->bbox.right = ui.cur_item->bbox.left + width/ui.zoom;
581 ui.cur_item->bbox.bottom = ui.cur_item->bbox.top + height/ui.zoom;
584 void start_text(GdkEvent *event, struct Item *item)
587 GtkTextBuffer *buffer;
588 GnomeCanvasItem *canvas_item;
589 PangoFontDescription *font_desc;
592 get_pointer_coords(event, pt);
594 ui.cur_item_type = ITEM_TEXT;
597 item = g_new(struct Item, 1);
599 item->canvas_item = NULL;
600 item->bbox.left = pt[0];
601 item->bbox.top = pt[1];
602 item->bbox.right = ui.cur_page->width;
603 item->bbox.bottom = pt[1]+100.;
604 item->font_name = g_strdup(ui.font_name);
605 item->font_size = ui.font_size;
606 g_memmove(&(item->brush), ui.cur_brush, sizeof(struct Brush));
607 ui.cur_layer->items = g_list_append(ui.cur_layer->items, item);
608 ui.cur_layer->nitems++;
611 item->type = ITEM_TEMP_TEXT;
614 font_desc = pango_font_description_from_string(item->font_name);
615 pango_font_description_set_absolute_size(font_desc,
616 item->font_size*ui.zoom*PANGO_SCALE);
617 item->widget = gtk_text_view_new();
618 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(item->widget));
619 if (item->text!=NULL)
620 gtk_text_buffer_set_text(buffer, item->text, -1);
621 gtk_widget_modify_font(item->widget, font_desc);
622 rgb_to_gdkcolor(item->brush.color_rgba, &color);
623 gtk_widget_modify_text(item->widget, GTK_STATE_NORMAL, &color);
624 pango_font_description_free(font_desc);
626 canvas_item = gnome_canvas_item_new(ui.cur_layer->group,
627 gnome_canvas_widget_get_type(),
628 "x", item->bbox.left, "y", item->bbox.top,
629 "width", item->bbox.right-item->bbox.left,
630 "height", item->bbox.bottom-item->bbox.top,
631 "widget", item->widget, NULL);
632 // TODO: width/height?
633 if (item->canvas_item!=NULL) {
634 lower_canvas_item_to(ui.cur_layer->group, canvas_item, item->canvas_item);
635 gtk_object_destroy(GTK_OBJECT(item->canvas_item));
637 item->canvas_item = canvas_item;
639 gtk_widget_show(item->widget);
640 ui.resize_signal_handler =
641 g_signal_connect((gpointer) winMain, "check_resize",
642 G_CALLBACK(resize_textview), NULL);
643 update_font_button();
644 gtk_widget_set_sensitive(GET_COMPONENT("editPaste"), FALSE);
645 gtk_widget_set_sensitive(GET_COMPONENT("buttonPaste"), FALSE);
646 gtk_widget_grab_focus(item->widget);
651 GtkTextBuffer *buffer;
652 GtkTextIter start, end;
654 struct UndoErasureData *erasure;
655 GnomeCanvasItem *tmpitem;
657 if (ui.cur_item_type!=ITEM_TEXT) return; // nothing for us to do!
659 // finalize the text that's been edited...
660 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(ui.cur_item->widget));
661 gtk_text_buffer_get_bounds(buffer, &start, &end);
662 ui.cur_item->type = ITEM_TEXT;
663 new_text = gtk_text_buffer_get_text(buffer, &start, &end, TRUE);
664 ui.cur_item_type = ITEM_NONE;
665 gtk_widget_set_sensitive(GET_COMPONENT("editPaste"), TRUE);
666 gtk_widget_set_sensitive(GET_COMPONENT("buttonPaste"), TRUE);
668 if (strlen(new_text)==0) { // erase object and cancel
670 g_signal_handler_disconnect(winMain, ui.resize_signal_handler);
671 gtk_object_destroy(GTK_OBJECT(ui.cur_item->canvas_item));
672 ui.cur_item->canvas_item = NULL;
673 if (ui.cur_item->text == NULL) // nothing happened
674 g_free(ui.cur_item->font_name);
675 else { // treat this as an erasure
677 undo->type = ITEM_ERASURE;
678 undo->layer = ui.cur_layer;
679 erasure = (struct UndoErasureData *)g_malloc(sizeof(struct UndoErasureData));
680 erasure->item = ui.cur_item;
681 erasure->npos = g_list_index(ui.cur_layer->items, ui.cur_item);
683 erasure->replacement_items = NULL;
684 undo->erasurelist = g_list_append(NULL, erasure);
686 ui.cur_layer->items = g_list_remove(ui.cur_layer->items, ui.cur_item);
687 ui.cur_layer->nitems--;
693 if (ui.cur_item->text==NULL || strcmp(ui.cur_item->text, new_text)) {
695 if (ui.cur_item->text == NULL) undo->type = ITEM_TEXT;
696 else undo->type = ITEM_TEXT_EDIT;
697 undo->layer = ui.cur_layer;
698 undo->item = ui.cur_item;
699 undo->str = ui.cur_item->text;
701 else g_free(ui.cur_item->text);
703 ui.cur_item->text = new_text;
704 ui.cur_item->widget = NULL;
705 // replace the canvas item
706 tmpitem = ui.cur_item->canvas_item;
707 make_canvas_item_one(ui.cur_layer->group, ui.cur_item);
708 update_item_bbox(ui.cur_item);
709 lower_canvas_item_to(ui.cur_layer->group, ui.cur_item->canvas_item, tmpitem);
710 gtk_object_destroy(GTK_OBJECT(tmpitem));
713 /* update the items in the canvas so they're of the right font size */
715 void update_text_item_displayfont(struct Item *item)
717 PangoFontDescription *font_desc;
719 if (item->type != ITEM_TEXT && item->type != ITEM_TEMP_TEXT) return;
720 if (item->canvas_item==NULL) return;
721 font_desc = pango_font_description_from_string(item->font_name);
722 pango_font_description_set_absolute_size(font_desc,
723 item->font_size*ui.zoom*PANGO_SCALE);
724 if (item->type == ITEM_TEMP_TEXT)
725 gtk_widget_modify_font(item->widget, font_desc);
727 gnome_canvas_item_set(item->canvas_item, "font-desc", font_desc, NULL);
728 update_item_bbox(item);
730 pango_font_description_free(font_desc);
733 void rescale_text_items(void)
735 GList *pagelist, *layerlist, *itemlist;
737 for (pagelist = journal.pages; pagelist!=NULL; pagelist = pagelist->next)
738 for (layerlist = ((struct Page *)pagelist->data)->layers; layerlist!=NULL; layerlist = layerlist->next)
739 for (itemlist = ((struct Layer *)layerlist->data)->items; itemlist!=NULL; itemlist = itemlist->next)
740 update_text_item_displayfont((struct Item *)itemlist->data);
743 struct Item *click_is_in_text(struct Layer *layer, double x, double y)
746 struct Item *item, *val;
749 for (itemlist = layer->items; itemlist!=NULL; itemlist = itemlist->next) {
750 item = (struct Item *)itemlist->data;
751 if (item->type != ITEM_TEXT) continue;
752 if (x<item->bbox.left || x>item->bbox.right) continue;
753 if (y<item->bbox.top || y>item->bbox.bottom) continue;
759 struct Item *click_is_in_text_or_image(struct Layer *layer, double x, double y)
762 struct Item *item, *val;
765 for (itemlist = layer->items; itemlist!=NULL; itemlist = itemlist->next) {
766 item = (struct Item *)itemlist->data;
767 if (item->type != ITEM_TEXT && item->type != ITEM_IMAGE) continue;
768 if (x<item->bbox.left || x>item->bbox.right) continue;
769 if (y<item->bbox.top || y>item->bbox.bottom) continue;
775 void refont_text_item(struct Item *item, gchar *font_name, double font_size)
777 if (!strcmp(font_name, item->font_name) && font_size==item->font_size) return;
778 if (item->text!=NULL) {
780 undo->type = ITEM_TEXT_ATTRIB;
782 undo->str = item->font_name;
783 undo->val_x = item->font_size;
784 undo->brush = (struct Brush *)g_memdup(&(item->brush), sizeof(struct Brush));
786 else g_free(item->font_name);
787 item->font_name = g_strdup(font_name);
788 if (font_size>0.) item->font_size = font_size;
789 update_text_item_displayfont(item);
792 void process_font_sel(gchar *str)
800 p = strrchr(str, ' ');
802 size = g_strtod(p+1, &q);
803 if (*q!=0 || size<1.) size=0.;
807 g_free(ui.font_name);
809 if (size>0.) ui.font_size = size;
811 // if there's a current text item, re-font it
812 if (ui.cur_item_type == ITEM_TEXT) {
813 refont_text_item(ui.cur_item, str, size);
814 undo_cont = (ui.cur_item->text!=NULL);
816 // if there's a current selection, re-font it
817 if (ui.selection!=NULL)
818 for (list=ui.selection->items; list!=NULL; list=list->next) {
819 it = (struct Item *)list->data;
820 if (it->type == ITEM_TEXT) {
821 if (undo_cont) undo->multiop |= MULTIOP_CONT_REDO;
822 refont_text_item(it, str, size);
823 if (undo_cont) undo->multiop |= MULTIOP_CONT_UNDO;
827 update_font_button();