Take page as input and take advantage of s_visit_page.
[geda-gaf/berndj.git] / gschem / src / o_picture.c
blob4f30a8eedf268d8cee40c2010cdcb30ee70553e0
1 /* gEDA - GPL Electronic Design Automation
2 * gschem - gEDA Schematic Capture
3 * Copyright (C) 1998-2007 Ales Hvezda
4 * Copyright (C) 1998-2007 gEDA Contributors (see ChangeLog for details)
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111 USA
20 #include <config.h>
21 #include <math.h>
22 #include <stdio.h>
24 #include "gschem.h"
26 /* This works, but using one macro inside of other doesn't */
27 #define GET_PICTURE_WIDTH(w) \
28 abs((w)->second_wx - (w)->first_wx)
29 #define GET_PICTURE_HEIGHT(w) \
30 (w)->pixbuf_wh_ratio == 0 ? 0 : abs((w)->second_wx - (w)->first_wx)/(w)->pixbuf_wh_ratio
31 #define GET_PICTURE_LEFT(w) \
32 min((w)->first_wx, (w)->second_wx)
33 #define GET_PICTURE_TOP(w) \
34 (w)->first_wy > (w)->second_wy ? (w)->first_wy : \
35 (w)->first_wy+abs((w)->second_wx - (w)->first_wx)/(w)->pixbuf_wh_ratio
37 struct grip_foreach_context {
38 GSCHEM_TOPLEVEL *w_current;
39 enum { PICTURE_GRIP_DRAW, PICTURE_GRIP_ERASE } action;
40 int min_x;
41 int min_y;
42 int max_x;
43 int max_y;
46 /*! \brief Start process to input a new picture.
47 * \par Function Description
48 * This function starts the process to input a new picture. Parameters
49 * for this picture are put into/extracted from the <B>w_current</B> toplevel
50 * structure.
51 * <B>w_x</B> and <B>w_y</B> are current coordinates of the pointer in world
52 * coordinates.
54 * The first step is to input one corner of the picture. This corner is
55 * (<B>w_x</B>,<B>w_y</B>) snapped to the grid and saved in
56 * <B>w_current->first_wx</B> and <B>w_current->first_wy</B>.
58 * The other corner will be saved in (<B>w_current->second_wx</B>,
59 * <B>w_current->second_wy</B>).
61 * \param [in] w_current The GSCHEM_TOPLEVEL object.
62 * \param [in] w_x Current x coordinate of pointer in world units.
63 * \param [in] w_y Current y coordinate of pointer in world units.
65 void o_picture_start(GSCHEM_TOPLEVEL *w_current, int w_x, int w_y)
67 /* init first_w[x|y], second_w[x|y] to describe box */
68 w_current->first_wx = w_current->second_wx = w_x;
69 w_current->first_wy = w_current->second_wy = w_y;
71 /* start to draw the box */
72 o_picture_rubberbox_xor(w_current);
73 w_current->rubber_visible = 1;
76 /*! \brief End the input of a circle.
77 * \par Function Description
78 * This function ends the input of the second corner of a picture.
79 * The picture is defined by (<B>w_current->first_wx</B>,<B>w_current->first_wy</B>
80 * and (<B>w_current->second_wx</B>,<B>w_current->second_wy</B>.
82 * The temporary picture frame is erased ; a new picture object is allocated,
83 * initialized and linked to the object list ; The object is finally
84 * drawn on the current sheet.
86 * \param [in] w_current The GSCHEM_TOPLEVEL object.
87 * \param [in] w_x (unused)
88 * \param [in] w_y (unused)
90 void o_picture_end(GSCHEM_TOPLEVEL *w_current, int w_x, int w_y)
92 TOPLEVEL *toplevel = w_current->toplevel;
93 OBJECT *new_obj;
94 int picture_width, picture_height;
95 int picture_left, picture_top;
97 g_assert( w_current->inside_action != 0 );
99 /* erase the temporary picture */
100 o_picture_rubberbox_xor(w_current);
101 w_current->rubber_visible = 0;
103 picture_width = GET_PICTURE_WIDTH (w_current);
104 picture_height = GET_PICTURE_HEIGHT(w_current);
105 picture_left = GET_PICTURE_LEFT (w_current);
106 picture_top = GET_PICTURE_TOP (w_current);
108 /* pictures with null width and height are not allowed */
109 if ((picture_width == 0) && (picture_height == 0)) {
110 /* cancel the object creation */
111 return;
114 /* create the object */
115 new_obj = o_picture_new(toplevel, w_current->current_pixbuf,
116 NULL, 0, w_current->pixbuf_filename,
117 w_current->pixbuf_wh_ratio, OBJ_PICTURE,
118 picture_left, picture_top,
119 picture_left + picture_width,
120 picture_top - picture_height,
121 0, FALSE, FALSE);
122 s_page_append(toplevel->page_current, new_obj);
124 /* draw it */
125 o_redraw_single(w_current, toplevel->page_current->object_tail);
127 toplevel->page_current->CHANGED = 1;
128 o_undo_savestate(w_current, UNDO_ALL);
131 /*! \brief Creates the add image dialog
132 * \par Function Description
133 * This function creates the add image dialog and loads the selected picture.
135 void picture_selection_dialog (GSCHEM_TOPLEVEL *w_current)
137 TOPLEVEL *toplevel = w_current->toplevel;
138 gchar *filename;
139 GdkPixbuf *pixbuf;
140 GError *error = NULL;
142 w_current->pfswindow = gtk_file_chooser_dialog_new ("Select a picture file...",
143 GTK_WINDOW(w_current->main_window),
144 GTK_FILE_CHOOSER_ACTION_OPEN,
145 GTK_STOCK_CANCEL,
146 GTK_RESPONSE_CANCEL,
147 GTK_STOCK_OPEN,
148 GTK_RESPONSE_ACCEPT,
149 NULL);
150 /* Set the alternative button order (ok, cancel, help) for other systems */
151 gtk_dialog_set_alternative_button_order(GTK_DIALOG(w_current->pfswindow),
152 GTK_RESPONSE_ACCEPT,
153 GTK_RESPONSE_CANCEL,
154 -1);
156 if (w_current->pixbuf_filename)
157 gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(w_current->pfswindow),
158 w_current->pixbuf_filename);
160 if (gtk_dialog_run (GTK_DIALOG (w_current->pfswindow)) == GTK_RESPONSE_ACCEPT) {
162 filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (w_current->pfswindow));
163 gtk_widget_destroy(w_current->pfswindow);
164 w_current->pfswindow=NULL;
166 pixbuf = gdk_pixbuf_new_from_file (filename, &error);
168 if (!pixbuf) {
169 GtkWidget *dialog;
171 dialog = gtk_message_dialog_new (GTK_WINDOW (w_current->main_window),
172 GTK_DIALOG_DESTROY_WITH_PARENT,
173 GTK_MESSAGE_ERROR,
174 GTK_BUTTONS_CLOSE,
175 _("Failed to load picture: %s"),
176 error->message);
177 /* Wait for any user response */
178 gtk_dialog_run (GTK_DIALOG (dialog));
180 g_error_free (error);
181 gtk_widget_destroy(dialog);
183 else {
184 #if DEBUG
185 printf("Picture loaded successfully.\n");
186 #endif
188 o_erase_rubber(w_current);
189 i_update_middle_button(w_current, i_callback_add_picture, _("Picture"));
190 w_current->inside_action = 0;
192 o_picture_set_pixbuf(w_current, pixbuf, filename);
194 toplevel->page_current->CHANGED=1;
195 i_set_state(w_current, DRAWPICTURE);
197 g_free (filename);
200 i_update_toolbar(w_current);
201 if (w_current->pfswindow) {
202 gtk_widget_destroy(w_current->pfswindow);
203 w_current->pfswindow=NULL;
207 /*! \todo Finish function documentation!!!
208 * \brief
209 * \par Function Description
211 * \note
212 * used in button cancel code in x_events.c
214 void o_picture_eraserubber(GSCHEM_TOPLEVEL *w_current)
216 #if DEBUG
217 printf("o_picture_eraserubber called\n");
218 #endif
219 o_picture_rubberbox_xor(w_current);
222 /*! \brief Draw temporary picture while dragging edge.
223 * \par Function Description
224 * This function is used to draw the box while dragging one of its edge or
225 * angle. It erases the previous temporary box drawn before, and draws
226 * a new updated one. <B>w_x</B> and <B>w_y</B> are the new position of the mobile
227 * point, ie the mouse.
229 * The old values are inside the <B>w_current</B> pointed structure. Old
230 * width, height and left and top values are recomputed by the corresponding
231 * macros. The box is then erased by performing a xor-drawing over the box.
233 * \param [in] w_current The GSCHEM_TOPLEVEL object.
234 * \param [in] w_x Current x coordinate of pointer in world units.
235 * \param [in] w_y Current y coordinate of pointer in world units.
237 void o_picture_rubberbox(GSCHEM_TOPLEVEL *w_current, int w_x, int w_y)
239 #if DEBUG
240 printf("o_picture_rubberbox called\n");
241 #endif
242 g_assert( w_current->inside_action != 0 );
244 /* erase the previous temporary box */
245 if (w_current->rubber_visible)
246 o_picture_rubberbox_xor(w_current);
249 * New values are fixed according to the <B>w_x</B> and <B>w_y</B> parameters.
250 * These are saved in <B>w_current</B> pointed structure as new temporary values.
251 * The new box is then drawn.
254 /* update the coords of the corner */
255 w_current->second_wx = w_x;
256 w_current->second_wy = w_y;
258 /* draw the new temporary box */
259 o_picture_rubberbox_xor(w_current);
260 w_current->rubber_visible = 1;
263 /*! \brief Draw picture from GSCHEM_TOPLEVEL object.
264 * \par Function Description
265 * This function draws the box from the variables in the GSCHEM_TOPLEVEL
266 * structure <B>*w_current</B>.
267 * One corner of the box is at (<B>w_current->first_wx</B>,
268 * <B>w_current->first_wy</B>) and the second corner is at
269 * (<B>w_current->second_wx</B>,<B>w_current->second_wy</B>.
271 * The box is drawn with a xor-function over the current sheet with the
272 * selection color.
274 * \param [in] w_current The GSCHEM_TOPLEVEL object.
276 void o_picture_rubberbox_xor(GSCHEM_TOPLEVEL *w_current)
278 TOPLEVEL *toplevel = w_current->toplevel;
279 PAGE *page = toplevel->page_current;
280 int left, top, width, height;
282 /* get the width/height and the upper left corner of the picture */
283 WORLDtoSCREEN(page,
284 GET_PICTURE_LEFT(w_current), GET_PICTURE_TOP(w_current),
285 &left, &top);
286 width = SCREENabs(page, GET_PICTURE_WIDTH (w_current));
287 height = SCREENabs(page, GET_PICTURE_HEIGHT(w_current));
289 #if DEBUG
290 printf("o_picture_rubberbox_xor called:\n");
291 printf("pixbuf wh ratio: %i\n", w_current->pixbuf_wh_ratio);
292 printf("first: %i, %i\n", w_current->first_wx, w_current->first_wy);
293 printf("second: %i, %i\n", w_current->second_wx, w_current->second_wy);
294 printf("Left: %i\nTop: %i\nWidth: %i\nHeight: %i\n",
295 picture_left, picture_top, picture_width, picture_height);
296 #endif
297 /* draw the picture from the previous variables */
298 gdk_gc_set_foreground(w_current->xor_gc,
299 x_get_darkcolor(w_current->select_color));
300 gdk_gc_set_line_attributes(w_current->xor_gc, 0,
301 GDK_LINE_SOLID, GDK_CAP_NOT_LAST,
302 GDK_JOIN_MITER);
303 gdk_draw_rectangle(w_current->backingstore, w_current->xor_gc,
304 FALSE, left, top, width, height);
305 o_invalidate_rect(w_current,
306 left, top, left + width, top + height);
309 /*! \brief Draw a picture on the screen.
310 * \par Function Description
311 * This function is used to draw a picture on screen. The picture is
312 * described in the OBJECT which is referred by <B>o_current</B>. The picture
313 * is displayed according to the current state, described in the
314 * GSCHEM_TOPLEVEL object pointed by <B>w_current</B>.
316 * It first checks if the OBJECT pointed is valid or not. If not it
317 * returns and do not output anything. That should never happen though.
319 * \param [in] w_current The GSCHEM_TOPLEVEL object.
320 * \param [in] o_current Picture OBJECT to draw.
322 void o_picture_draw(GSCHEM_TOPLEVEL *w_current, OBJECT *o_current)
324 TOPLEVEL *toplevel = w_current->toplevel;
325 PAGE *page = toplevel->page_current;
326 int wleft, wright, wtop, wbottom; /* world bounds */
327 int s_upper_x, s_upper_y, s_lower_x, s_lower_y;
329 if (o_current->picture == NULL) {
330 return;
333 /* Get read to check for visibility of this line by using it's
334 * bounding picture
336 world_get_single_object_bounds(o_current, &wleft, &wtop, &wright, &wbottom);
338 if (!visible(page, wleft, wtop, wright, wbottom)) {
339 return;
342 WORLDtoSCREEN(page, wleft, wbottom, &s_upper_x, &s_upper_y);
343 WORLDtoSCREEN(page, wright, wtop, &s_lower_x, &s_lower_y);
345 #if DEBUG
346 printf("drawing picture\n\n");
348 printf("drawing picture: %d %d %d %d\n",
349 s_upper_x, s_upper_y,
350 s_upper_x + abs(s_lower_x - s_upper_x),
351 s_upper_y + abs(s_lower_y - s_upper_y));
352 #endif
355 * First, the picture is drawn.
356 * Finally the function takes care of the grips.
359 /* If it's not drawing using the background color then draw the image */
360 if (toplevel->override_color != toplevel->background_color) {
361 GdkPixbuf *pixbuf;
363 pixbuf = o_picture_make_drawable(o_current,
364 abs(s_lower_x - s_upper_x),
365 abs(s_lower_y - s_upper_y));
366 if (pixbuf == NULL) {
367 s_log_message(_("Can't draw picture - not enough memory?\n"));
368 return;
371 if (toplevel->DONT_REDRAW == 0) {
372 gdk_draw_pixbuf(w_current->backingstore, w_current->gc,
373 pixbuf,
374 0, 0, s_upper_x, s_upper_y,
375 -1, -1, GDK_RGB_DITHER_NONE, 0, 0);
378 else {
379 if (toplevel->DONT_REDRAW == 0) {
380 /* Erase the picture, drawing a rectangle with the background color */
381 gdk_gc_set_foreground(w_current->gc,
382 x_get_color(toplevel->background_color));
383 gdk_draw_rectangle(w_current->backingstore, w_current->gc, TRUE,
384 s_upper_x, s_upper_y,
385 abs(s_lower_x -s_upper_x),
386 abs(s_lower_y - s_upper_y));
390 /* Grip specific stuff */
391 if ((o_current->draw_grips == TRUE) && (w_current->draw_grips == TRUE)) {
392 if (toplevel->DONT_REDRAW == 0) {
393 GdkColor *color;
395 if (!o_current->selected) {
396 /* Deselected: use the background color. */
397 color = x_get_color(toplevel->background_color);
398 } else if (toplevel->override_color != -1 ) {
399 /* override : use the override_color instead */
400 color = x_get_color(toplevel->override_color);
401 } else {
402 /* use the normal selection color */
403 color = x_get_color(w_current->select_color);
406 gdk_gc_set_foreground(w_current->gc, color);
407 gdk_draw_rectangle(w_current->backingstore, w_current->gc, FALSE,
408 s_upper_x, s_upper_y,
409 abs(s_lower_x -s_upper_x),
410 abs(s_lower_y - s_upper_y));
415 /*! \brief Draw a picture described by OBJECT with translation
416 * \par Function Description
417 * This function draws the picture object described by <B>*o_current</B>
418 * translated by the vector (<B>dx</B>,<B>dy</B>) with an xor-function over
419 * the current sheet.
420 * The translation vector is in world unit.
422 * The picture is displayed with the color of the object.
424 * \param [in] w_current The GSCHEM_TOPLEVEL object.
425 * \param [in] dx Delta x coordinate for picture.
426 * \param [in] dy Delta y coordinate for picture.
427 * \param [in] o_current Picture OBJECT to draw.
429 void o_picture_draw_xor(GSCHEM_TOPLEVEL *w_current, int dx, int dy, OBJECT *o_current)
431 TOPLEVEL *toplevel = w_current->toplevel;
432 PAGE *page = toplevel->page_current;
433 int screen_x1, screen_y1;
434 int screen_x2, screen_y2;
435 int wleft, world_y1;
436 int wright, world_y2;
437 int color;
439 #if DEBUG
440 printf("o_picture_draw_xor called.\n");
441 #endif
442 if (o_current->picture == NULL) {
443 return;
446 world_get_single_object_bounds(o_current, &wleft, &world_y1, &wright, &world_y2);
447 WORLDtoSCREEN(page, wleft + dx, world_y2 + dy, &screen_x1, &screen_y1);
448 WORLDtoSCREEN(page, wright + dx, world_y1 + dy, &screen_x2, &screen_y2);
450 if (o_current->saved_color != -1) {
451 color = o_current->saved_color;
452 } else {
453 color = o_current->color;
456 gdk_gc_set_foreground(w_current->outline_xor_gc,
457 x_get_darkcolor(color));
458 gdk_draw_rectangle(w_current->backingstore,
459 w_current->outline_xor_gc, FALSE,
460 screen_x1,
461 screen_y1,
462 abs(screen_x2 - screen_x1),
463 abs(screen_y2 - screen_y1));
466 /*! \brief Replace all selected pictures with a new picture
467 * \par Function Description
468 * This function replaces all pictures in the current selection with a
469 * new image.
471 * \param [in] w_current The GSCHEM_TOPLEVEL object
472 * \param [in] pixbuf New GdkPixbuf object
473 * \param [in] filename The filename of the new picture
476 void o_picture_exchange (GSCHEM_TOPLEVEL *w_current, GdkPixbuf *pixbuf,
477 const gchar *filename)
479 TOPLEVEL *toplevel = w_current->toplevel;
480 GList *list;
482 list = geda_list_get_glist( toplevel->page_current->selection_list );
483 while (list != NULL) {
484 OBJECT *object;
486 object = list->data;
487 if (object == NULL) {
488 fprintf(stderr, _("ERROR: NULL object!\n"));
489 exit(-1);
491 if (!object->attached_to) {
492 /* It's selected. Then change picture if it's a picture */
493 if (object->type == OBJ_PICTURE) {
494 /* Erase previous picture */
495 o_erase_single(w_current, object);
497 o_picture_set(object, pixbuf, filename);
499 /* Draw new picture */
500 o_picture_draw(w_current, object);
503 list = g_list_next(list);
507 /*! \brief Create dialog to exchange picture objects
508 * \par Function Description
509 * This function opens a file chooser and replaces all pictures of the selections
510 * with the new picture.
512 * \todo Maybe merge this dialog function with picture_selection_dialog()
514 void picture_change_filename_dialog (GSCHEM_TOPLEVEL *w_current)
516 TOPLEVEL *toplevel = w_current->toplevel;
517 gchar *filename;
518 GdkPixbuf *pixbuf;
519 GError *error = NULL;
521 w_current->pfswindow = gtk_file_chooser_dialog_new ("Select a picture file...",
522 GTK_WINDOW(w_current->main_window),
523 GTK_FILE_CHOOSER_ACTION_OPEN,
524 GTK_STOCK_CANCEL,
525 GTK_RESPONSE_CANCEL,
526 GTK_STOCK_OPEN,
527 GTK_RESPONSE_ACCEPT,
528 NULL);
530 /* Set the alternative button order (ok, cancel, help) for other systems */
531 gtk_dialog_set_alternative_button_order(GTK_DIALOG(w_current->pfswindow),
532 GTK_RESPONSE_ACCEPT,
533 GTK_RESPONSE_CANCEL,
534 -1);
536 if (w_current->pixbuf_filename)
537 gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(w_current->pfswindow),
538 w_current->pixbuf_filename);
540 if (gtk_dialog_run (GTK_DIALOG (w_current->pfswindow)) == GTK_RESPONSE_ACCEPT) {
542 filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (w_current->pfswindow));
543 gtk_widget_destroy(w_current->pfswindow);
544 w_current->pfswindow=NULL;
546 pixbuf = gdk_pixbuf_new_from_file (filename, &error);
548 if (!pixbuf) {
549 GtkWidget *dialog;
551 dialog = gtk_message_dialog_new (GTK_WINDOW (w_current->main_window),
552 GTK_DIALOG_DESTROY_WITH_PARENT,
553 GTK_MESSAGE_ERROR,
554 GTK_BUTTONS_CLOSE,
555 _("Failed to load picture: %s"),
556 error->message);
557 /* Wait for any user response */
558 gtk_dialog_run (GTK_DIALOG (dialog));
560 g_error_free (error);
561 gtk_widget_destroy(dialog);
563 else {
564 #if DEBUG
565 printf("Picture loaded successfully.\n");
566 #endif
568 o_erase_rubber(w_current);
569 w_current->inside_action = 0;
571 /* \FIXME Should we set the pixbuf buffer in GSCHEM_TOPLEVEL to store
572 the current pixbuf? (Werner)
573 o_picture_set_pixbuf(w_current, pixbuf, filename); */
575 o_picture_exchange(w_current, pixbuf, filename);
577 g_object_unref(pixbuf);
578 toplevel->page_current->CHANGED=1;
580 g_free (filename);
583 i_update_toolbar(w_current);
584 if (w_current->pfswindow) {
585 gtk_widget_destroy(w_current->pfswindow);
586 w_current->pfswindow=NULL;
590 /*! \todo Finish function documentation!!!
591 * \brief
592 * \par Function Description
594 * \param [in] w_current The GSCHEM_TOPLEVEL object.
595 * \param [in] pixbuf
596 * \param [in] filename
598 void o_picture_set_pixbuf(GSCHEM_TOPLEVEL *w_current,
599 GdkPixbuf *pixbuf, char *filename)
602 /* need to put an error messages here */
603 if (pixbuf == NULL) {
604 fprintf(stderr, "error! picture in set pixbuf was NULL\n");
605 return;
608 if (w_current->current_pixbuf != NULL) {
609 g_object_unref(w_current->current_pixbuf);
610 w_current->current_pixbuf=NULL;
613 if (w_current->pixbuf_filename != NULL) {
614 g_free(w_current->pixbuf_filename);
615 w_current->pixbuf_filename=NULL;
618 w_current->current_pixbuf = pixbuf;
619 w_current->pixbuf_filename = (char *) g_strdup(filename);
621 w_current->pixbuf_wh_ratio = (double) gdk_pixbuf_get_width(pixbuf) /
622 gdk_pixbuf_get_height(pixbuf);
624 /* be sure to free this pixbuf somewhere */