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
23 #include <libgeda/libgeda.h>
25 #include "../include/gschem_struct.h"
26 #include "../include/x_states.h"
27 #include "../include/prototype.h"
29 #ifdef HAVE_LIBDMALLOC
33 #define INVALIDATE_MARGIN 1
35 /*! \todo Lots of Gross code... needs lots of cleanup - mainly
39 /*! \todo Finish function documentation!!!
41 * \par Function Description
44 void o_redraw_all(GSCHEM_TOPLEVEL
*w_current
)
46 TOPLEVEL
*toplevel
= w_current
->toplevel
;
47 gboolean draw_selected
= TRUE
;
49 if (!toplevel
->DONT_REDRAW
) {
50 x_repaint_background(w_current
);
53 draw_selected
= !(w_current
->inside_action
&&
54 ((w_current
->event_state
== MOVE
) ||
55 (w_current
->event_state
== ENDMOVE
) ||
56 (w_current
->event_state
== GRIPS
)));
57 o_redraw(w_current
, toplevel
->page_current
->object_head
, draw_selected
);
58 o_cue_redraw_all(w_current
,
59 toplevel
->page_current
->object_head
, draw_selected
);
61 if (w_current
->inside_action
) {
62 switch(w_current
->event_state
) {
67 o_drawbounding(w_current
,
68 geda_list_get_glist( toplevel
->page_current
->selection_list
),
69 x_get_darkcolor(w_current
->bb_color
), TRUE
);
74 o_drawbounding(w_current
, toplevel
->page_current
->complex_place_list
,
75 x_get_darkcolor(w_current
->bb_color
), TRUE
);
80 o_drawbounding(w_current
, toplevel
->page_current
->attrib_place_list
,
81 x_get_darkcolor(w_current
->bb_color
), TRUE
);
86 w_current
->magnetic_visible
=0;
89 w_current
->rubber_visible
=0;
94 /*! \todo Finish function documentation!!!
96 * \par Function Description
99 void o_redraw(GSCHEM_TOPLEVEL
*w_current
, OBJECT
*list
, gboolean draw_selected
)
101 TOPLEVEL
*toplevel
= w_current
->toplevel
;
102 OBJECT
*o_current
= list
;
103 int redraw_state
= toplevel
->DONT_REDRAW
;
104 int left
, top
, bottom
, right
;
105 int s_left
, s_top
, s_bottom
, s_right
;
107 w_current
->inside_redraw
= 1;
108 while (o_current
!= NULL
) {
109 if ((o_current
->draw_func
!= NULL
) &&
110 (o_current
->type
!= OBJ_HEAD
)) {
111 toplevel
->DONT_REDRAW
= redraw_state
||
112 (!draw_selected
&& o_current
->selected
);
113 (*o_current
->draw_func
)(w_current
, o_current
);
116 o_current
= o_current
->next
;
118 w_current
->inside_redraw
= 0;
119 toplevel
->DONT_REDRAW
= redraw_state
;
121 if (world_get_object_list_bounds(toplevel
, list
, &left
, &top
,
123 WORLDtoSCREEN( toplevel
, left
, top
, &s_left
, &s_top
);
124 WORLDtoSCREEN( toplevel
, right
, bottom
, &s_right
, &s_bottom
);
125 o_invalidate_rect( w_current
, s_left
, s_top
, s_right
, s_bottom
);
129 /*! \brief Redraw an object on the screen.
130 * \par Function Description
131 * This function will redraw a single object on the screen.
133 * \param [in] w_current The GSCHEM_TOPLEVEL object.
134 * \param [in] o_current The OBJECT to redraw.
137 void o_redraw_single(GSCHEM_TOPLEVEL
*w_current
, OBJECT
*o_current
)
139 TOPLEVEL
*toplevel
= w_current
->toplevel
;
140 int left
, top
, right
, bottom
;
141 int s_left
, s_top
, s_right
, s_bottom
;
143 if (o_current
== NULL
)
146 if (toplevel
->DONT_REDRAW
) /* highly experimental */
149 if (o_current
->draw_func
!= NULL
&& o_current
->type
!= OBJ_HEAD
) {
150 w_current
->inside_redraw
= 1;
151 (*o_current
->draw_func
)(w_current
, o_current
);
152 w_current
->inside_redraw
= 0;
155 if (world_get_single_object_bounds(toplevel
, o_current
, &left
, &top
,
157 WORLDtoSCREEN(toplevel
, left
, top
, &s_left
, &s_top
);
158 WORLDtoSCREEN(toplevel
, right
, bottom
, &s_right
, &s_bottom
);
159 o_invalidate_rect (w_current
, s_left
, s_bottom
, s_right
, s_top
);
163 /*! \todo Finish function documentation!!!
165 * \par Function Description
168 void o_draw_list(GSCHEM_TOPLEVEL
*w_current
, GList
* list
)
173 if (w_current
->inside_redraw
) {
178 while (l_current
!= NULL
) {
180 o_current
= (OBJECT
*) l_current
->data
;
183 o_redraw_single(w_current
, o_current
);
186 l_current
= g_list_next(l_current
);
190 /*! \todo Finish function documentation!!!
192 * \par Function Description
195 void o_draw_selected(GSCHEM_TOPLEVEL
*w_current
)
197 TOPLEVEL
*toplevel
= w_current
->toplevel
;
200 if (w_current
->inside_redraw
) {
204 s_current
= geda_list_get_glist( toplevel
->page_current
->selection_list
);
205 while (s_current
!= NULL
) {
206 o_current
= (OBJECT
*) s_current
->data
;
209 o_redraw_single(w_current
, o_current
);
210 o_cue_draw_single(w_current
, o_current
);
212 s_current
=g_list_next(s_current
);
217 /*! \todo Finish function documentation!!!
219 * \par Function Description
222 void o_erase_selected(GSCHEM_TOPLEVEL
*w_current
)
224 TOPLEVEL
*toplevel
= w_current
->toplevel
;
228 int left
, top
, right
, bottom
;
229 int s_left
, s_top
, s_right
, s_bottom
;
231 if (w_current
->inside_redraw
) {
235 list
= iter
= geda_list_get_glist( toplevel
->page_current
->selection_list
);
236 while (iter
!= NULL
) {
237 o_current
= iter
->data
;
240 o_cue_erase_single(w_current
, o_current
);
241 o_erase_single(w_current
, o_current
);
244 iter
= g_list_next( iter
);
247 if (world_get_object_glist_bounds(toplevel
, list
, &left
, &top
,
249 WORLDtoSCREEN(toplevel
, left
, top
, &s_left
, &s_top
);
250 WORLDtoSCREEN(toplevel
, right
, bottom
, &s_right
, &s_bottom
);
251 o_invalidate_rect (w_current
, s_left
, s_bottom
, s_right
, s_top
);
255 /*! \brief Erase a given OBJECT
257 * \par Function Description
258 * This function erases the passed OBJECT, <B>object</B>.
260 * It makes a call to object's draw function after having set a
261 * color override to the background color. The object is drawn in
262 * the background color, causing it to dissapear.
264 * \bug No redrawing is done of occluded objects, including the grid.
266 * \param [in] w_current The GSCHEM_TOPLEVEL object.
267 * \param [in] o_current Circle OBJECT to erase.
269 void o_erase_single(GSCHEM_TOPLEVEL
*w_current
, OBJECT
*object
)
271 TOPLEVEL
*toplevel
= w_current
->toplevel
;
273 int left
, top
, right
, bottom
;
274 int s_left
, s_top
, s_right
, s_bottom
;
276 if (w_current
->inside_redraw
) {
282 toplevel
->override_color
= toplevel
->background_color
;
283 if (o_current
!= NULL
) {
284 if (o_current
->draw_func
&&
285 o_current
->type
!= OBJ_HEAD
) {
286 (*o_current
->draw_func
)(w_current
, o_current
);
289 toplevel
->override_color
= -1;
291 if (world_get_single_object_bounds(toplevel
, o_current
, &left
, &top
,
293 WORLDtoSCREEN(toplevel
, left
, top
, &s_left
, &s_top
);
294 WORLDtoSCREEN(toplevel
, right
, bottom
, &s_right
, &s_bottom
);
295 o_invalidate_rect (w_current
, s_left
, s_bottom
, s_right
, s_top
);
299 /*! \todo Finish function documentation!!!
301 * \par Function Description
304 void o_erase_list(GSCHEM_TOPLEVEL
*w_current
, GList
* list
)
309 if (w_current
->inside_redraw
) {
314 while (iter
!= NULL
) {
315 o_current
= iter
->data
;
316 o_erase_single(w_current
, o_current
);
317 iter
= g_list_next(iter
);
321 /*! \brief XOR draw a bounding box or outline for OBJECT placement
323 * \par Function Description
324 * This function XOR draws either the OBJECTS in the passed GList,
325 * or a rectangle around their bounding box, depending upon the
326 * currently selected w_current->actionfeedback_mode. This takes the
327 * value BOUNDINGBOX or OUTLINE.
329 * The function applies manhatten mode constraints to the coordinates
330 * before drawing if the CONTROL key is recording as being pressed in
331 * the w_current structure.
333 * The "drawing" parameter is used to indicate if this drawing should
334 * immediately use the selected feedback mode and positioning constraints.
336 * With drawing=TRUE, the selected conditions are used immediately,
337 * otherwise the conditions from the last drawing operation are used,
338 * saving the new state for next time.
340 * o_drawbounding() should be called with drawing=TRUE when starting a
341 * rubberbanding operation and when otherwise refreshing the rubberbanded
342 * outline (e.g. after a screen redraw). For any undraw operation, should
343 * be called with drawing=FALSE, ensuring that the undraw XOR matches the
344 * mode and constraints of the corresponding "draw" operation.
346 * If any mode / constraint changes are made between a undraw, redraw XOR
347 * pair, the latter (draw) operation must be called with drawing=TRUE. If
348 * no mode / constraint changes were made between the pair, it is not
349 * harmful to call the draw operation with "drawing=FALSE".
351 * \param [in] w_current GSCHEM_TOPLEVEL which we're drawing for.
352 * \param [in] o_glst GList of objects to XOR draw.
353 * \param [in] color GdkColor used for drawing in BOUNDINGBOX mode.
354 * \param [in] drawing Set to FALSE for undraw operations to ensure
355 * matching conditions to a previous draw operation.
357 void o_drawbounding(GSCHEM_TOPLEVEL
*w_current
, GList
*o_glist
,
358 GdkColor
*color
, int drawing
)
360 TOPLEVEL
*toplevel
= w_current
->toplevel
;
362 int left
, top
, bottom
, right
;
363 int s_left
, s_top
, s_bottom
, s_right
;
365 if (o_glist
== NULL
) {
369 /* BUG: temporary fix while switching to world corrds */
370 if (!(w_current
->event_state
== MOVE
)
371 && !(w_current
->event_state
== ENDMOVE
)
372 && !(w_current
->event_state
== DRAWTEXT
)
373 && !(w_current
->event_state
== ENDTEXT
)
374 && !(w_current
->event_state
== ENDPASTE
)) {
375 SCREENtoWORLD(toplevel
, w_current
->start_x
, w_current
->start_y
,
376 &(w_current
->first_wx
), &(w_current
->first_wy
));
377 SCREENtoWORLD(toplevel
, w_current
->last_x
, w_current
->last_y
,
378 &(w_current
->second_wx
), &(w_current
->second_wy
));
381 /* If drawing is true, then don't worry about the previous drawing
382 * method and movement constraints, use with the current settings */
384 w_current
->last_drawb_mode
= w_current
->actionfeedback_mode
;
385 w_current
->drawbounding_action_mode
= (w_current
->CONTROLKEY
)
386 ? CONSTRAINED
: FREE
;
389 /* Calculate delta of X-Y positions from buffer's origin */
390 diff_x
= w_current
->second_wx
- w_current
->first_wx
;
391 diff_y
= w_current
->second_wy
- w_current
->first_wy
;
393 /* Adjust the coordinates according to the movement constraints */
394 if (w_current
->drawbounding_action_mode
== CONSTRAINED
) {
395 if (abs(diff_x
) >= abs(diff_y
)) {
396 w_current
->second_wy
= w_current
->first_wy
;
398 w_current
->second_wx
= w_current
->first_wx
;
402 /* Find the bounds of the drawing to be done */
403 world_get_object_glist_bounds(toplevel
, o_glist
,
404 &left
, &top
, &right
, &bottom
);
405 WORLDtoSCREEN(toplevel
, left
+ diff_x
, top
+ diff_y
, &s_left
, &s_top
);
406 WORLDtoSCREEN(toplevel
, right
+ diff_x
, bottom
+ diff_y
, &s_right
, &s_bottom
);
408 /* XOR draw with the appropriate mode */
409 if (w_current
->last_drawb_mode
== BOUNDINGBOX
) {
410 gdk_gc_set_foreground(w_current
->bounding_xor_gc
, color
);
411 gdk_draw_rectangle(w_current
->backingstore
,
412 w_current
->bounding_xor_gc
, FALSE
,
414 s_right
- s_left
, s_top
- s_bottom
);
416 o_glist_draw_xor (w_current
, diff_x
, diff_y
, o_glist
);
419 /* Invalidate the screen buffer where we drew */
420 o_invalidate_rect(w_current
, s_left
, s_top
,
423 /* Save movement constraints and drawing method for any
424 * corresponding undraw operation. */
425 w_current
->last_drawb_mode
= w_current
->actionfeedback_mode
;
426 w_current
->drawbounding_action_mode
= (w_current
->CONTROLKEY
)
427 ? CONSTRAINED
: FREE
;
431 /*! \todo Finish function documentation!!!
433 * \par Function Description
436 int o_erase_rubber(GSCHEM_TOPLEVEL
*w_current
)
438 /* return FALSE if it did not erase anything */
440 if (!w_current
->inside_action
)
443 switch(w_current
->event_state
) {
448 o_bus_eraserubber(w_current
);
454 o_net_eraserubber(w_current
);
459 o_pin_eraserubber(w_current
);
464 o_line_eraserubber(w_current
);
469 o_box_eraserubber(w_current
);
474 o_picture_eraserubber(w_current
);
479 o_circle_eraserubber(w_current
);
484 o_arc_eraserubber(w_current
);
496 /*! \todo Finish function documentation!!!
498 * \par Function Description
499 * This function is neccesary to make jumps between event_states.
500 * If we are inside an drawing action that created something on the dc,
501 * e.g. if we are drawing a box and then jump to line drawing without
502 * leaving the box drawing mode, there will remain some rubberbands on the
504 * Usually a intermediate select state would clean (redraw) the screen.
506 int o_redraw_cleanstates(GSCHEM_TOPLEVEL
*w_current
)
508 TOPLEVEL
*toplevel
= w_current
->toplevel
;
509 /* returns FALSE if the function was'nt nessecary */
510 if (w_current
->inside_action
== 0) {
514 switch (w_current
->event_state
) {
515 /* all states with something on the dc */
518 /* De-select the lists in the component selector */
519 x_compselect_deselect (w_current
);
540 /* it is possible to cancel in the middle of a place,
541 * so lets be sure to clean up the complex_place_list
542 * structure and also clean up the attrib_place_list. */
544 /* If it is a move command, then free the complex place list WITHOUT
545 freeing the individual objects. */
546 if ((w_current
->event_state
== MOVE
) ||
547 (w_current
->event_state
== ENDMOVE
)) {
548 g_list_free (toplevel
->page_current
->complex_place_list
);
550 s_delete_object_glist(toplevel
,
551 toplevel
->page_current
->complex_place_list
);
553 toplevel
->page_current
->complex_place_list
= NULL
;
555 s_delete_object_glist (toplevel
,
556 toplevel
->page_current
->attrib_place_list
);
557 toplevel
->page_current
->attrib_place_list
= NULL
;
559 /* also free internal current_attribute */
560 o_attrib_free_current(toplevel
);
561 w_current
->inside_action
= 0;
563 /* touch the select state */
564 i_set_state(w_current
, SELECT
);
566 /* from i_callback_cancel() */
567 o_redraw_all(w_current
);
570 /* all remaining states without dc changes */
607 /*! \todo Finish function documentation!!!
609 * \par Function Description
612 void o_draw_xor(GSCHEM_TOPLEVEL
*w_current
, int dx
, int dy
, OBJECT
*object
)
614 void (*func
) (GSCHEM_TOPLEVEL
*, int, int, OBJECT
*) = NULL
;
616 switch (object
->type
) {
617 case OBJ_HEAD
: /* Do nothing for head nodes */ break;
618 case OBJ_LINE
: func
= o_line_draw_xor
; break;
619 case OBJ_NET
: func
= o_net_draw_xor
; break;
620 case OBJ_BUS
: func
= o_bus_draw_xor
; break;
621 case OBJ_BOX
: func
= o_box_draw_xor
; break;
622 case OBJ_PICTURE
: func
= o_picture_draw_xor
; break;
623 case OBJ_CIRCLE
: func
= o_circle_draw_xor
; break;
624 case OBJ_PLACEHOLDER
:
625 case OBJ_COMPLEX
: func
= o_complex_draw_xor
; break;
626 case OBJ_TEXT
: func
= o_text_draw_xor
; break;
627 case OBJ_PIN
: func
= o_pin_draw_xor
; break;
628 case OBJ_ARC
: func
= o_arc_draw_xor
; break;
630 g_assert_not_reached ();
634 (*func
) (w_current
, dx
, dy
, object
);
639 /*! \todo Finish function documentation!!!
641 * \par Function Description
644 void o_list_draw_xor(GSCHEM_TOPLEVEL
*w_current
, int dx
, int dy
, OBJECT
*list
)
646 OBJECT
*o_current
= list
;
648 while(o_current
!= NULL
) {
649 o_draw_xor(w_current
, dx
, dy
, o_current
);
650 o_current
= o_current
->next
;
655 /*! \todo Finish function documentation!!!
657 * \par Function Description
660 void o_glist_draw_xor(GSCHEM_TOPLEVEL
*w_current
, int dx
, int dy
, GList
*list
)
664 while (iter
!= NULL
) {
665 o_draw_xor(w_current
, dx
, dy
, (OBJECT
*)iter
->data
);
666 iter
= g_list_next(iter
);
671 /*! \brief Invalidates a rectangular region of the on screen drawing area
672 * \par Function Description
674 * Given a pair of (x,y) coordinates in SCREEN units, invalidate the
675 * rectangular on-screen drawing area which has those two coordinate
676 * pairs as opposite corners of its region. This will cause that region
677 * to be blitted from the back-buffer once the mainloop reaches idle.
679 * A margin, INVALIDATE_MARGIN is added to the invalidated region as
680 * a hacky workaround for rounding errors which may occur in the
681 * WORLD -> SCREEN coordinate transform. This margin may also be used
682 * to expand the invalidated region if anti-aliased drawing is ever
685 * If the GSCHEM_TOPLEVEL in question is not rendering to a GDK_WINDOW,
686 * (e.g. image export), this function call is a no-op. A test is used:
687 * GDK_IS_WINDOW(), which should be safe since in either case,
688 * w_current->window is a GObject. This is really a _HACK_,
689 * and should be fixed with a re-worked drawing model.
691 * \param [in] w_current The GSCHEM_TOPLEVEL who's drawing area is being invalidated.
692 * \param [in] x1 X coord for corner 1 (SCREEN units)
693 * \param [in] y1 Y coord for corner 1 (SCREEN units)
694 * \param [in] x2 X coord for corner 2 (SCREEN units)
695 * \param [in] y2 Y coord for corner 2 (SCREEN units)
697 void o_invalidate_rect( GSCHEM_TOPLEVEL
*w_current
,
698 int x1
, int y1
, int x2
, int y2
)
702 /* BUG: We get called when rendering an image, and w_current->window
703 * is a GdkPixmap. Ensure we only invalidate GdkWindows. */
704 if (!GDK_IS_WINDOW( w_current
->window
))
707 rect
.x
= MIN(x1
, x2
) - INVALIDATE_MARGIN
;
708 rect
.y
= MIN(y1
, y2
) - INVALIDATE_MARGIN
;
709 rect
.width
= 1 + abs( x1
- x2
) + 2 * INVALIDATE_MARGIN
;
710 rect
.height
= 1 + abs( y1
- y2
) + 2 * INVALIDATE_MARGIN
;
711 gdk_window_invalidate_rect( w_current
->window
, &rect
, FALSE
);