1 /* gEDA - GPL Electronic Design Automation
2 * gschem - gEDA Schematic Capture
3 * Copyright (C) 1998-2010 Ales Hvezda
4 * Copyright (C) 1998-2010 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
34 #ifdef HAVE_LIBDMALLOC
38 #define MINIMUM_MARK_SMALL_DIST 1
41 # define FONT_NAME "Arial"
43 # define FONT_NAME "Helvetica"
49 char *unescape_text_and_overbars (char *text
, PangoAttrList
*attrs
)
51 char *p
, *sp
, *strip_text
;
52 char *overbar_start
= NULL
;
55 /* The unescaped text is alwasys shorter than the original
56 string, so just allocate the same ammount of memory. */
57 sp
= strip_text
= g_malloc (strlen (text
) + 1);
59 for (p
= text
; p
!= NULL
; p
++) {
60 int finish_overbar
= FALSE
;
62 /* If we find an escape character "\", we note it and continue looping */
63 if (!escape
&& *p
== '\\') {
68 if (escape
&& *p
== '_') {
69 /* Overbar start or end sequence */
70 if (overbar_start
!= NULL
) {
71 finish_overbar
= TRUE
;
76 /* just append the character, which may have been escaped */
81 if (overbar_start
!= NULL
&&
82 (finish_overbar
|| *p
== '\0')) {
85 attr
= gschem_pango_attr_overbar_new (TRUE
);
86 attr
->start_index
= overbar_start
- strip_text
;
87 attr
->end_index
= sp
- strip_text
;
88 pango_attr_list_insert (attrs
, attr
);
92 /* end of the string, stop iterating */
101 static void calculate_position (OBJECT
*object
,
102 PangoFontMetrics
*font_metrics
,
103 PangoRectangle logical_rect
,
104 PangoRectangle inked_rect
,
105 double *x
, double *y
)
108 double y_lower
, y_middle
, y_upper
;
109 double x_left
, x_middle
, x_right
;
110 double descent
= pango_font_metrics_get_descent (font_metrics
) / PANGO_SCALE
;
113 x_middle
= -logical_rect
.width
/ 2.;
114 x_right
= -logical_rect
.width
;
116 /*! \note Ideally, we would be using just font / logical metrics for vertical
117 * alignment, however this way seems to be more backward compatible
118 * with the old gschem rendering.
120 * Lower alignment is at the baseline of the bottom text line, whereas
121 * middle and upper alignment is based upon the inked extents of the
124 y_upper
= -inked_rect
.y
; /* Top of inked extents */
125 y_middle
= y_upper
- inked_rect
.height
/ 2.; /* Middle of inked extents */
126 y_lower
= descent
- logical_rect
.height
; /* Baseline of bottom line */
128 /* Special case flips attachment point to opposite corner when
129 * the text is rotated to 180 degrees, since the drawing code
130 * does not rotate the text to be shown upside down.
132 if (object
->text
->angle
== 180) {
133 temp
= y_lower
; y_lower
= y_upper
; y_upper
= temp
;
134 temp
= x_left
; x_left
= x_right
; x_right
= temp
;
137 switch (object
->text
->alignment
) {
139 /* Fall through to LOWER_left case */
140 case LOWER_LEFT
: *y
= y_lower
; *x
= x_left
; break;
141 case MIDDLE_LEFT
: *y
= y_middle
; *x
= x_left
; break;
142 case UPPER_LEFT
: *y
= y_upper
; *x
= x_left
; break;
143 case LOWER_MIDDLE
: *y
= y_lower
; *x
= x_middle
; break;
144 case MIDDLE_MIDDLE
: *y
= y_middle
; *x
= x_middle
; break;
145 case UPPER_MIDDLE
: *y
= y_upper
; *x
= x_middle
; break;
146 case LOWER_RIGHT
: *y
= y_lower
; *x
= x_right
; break;
147 case MIDDLE_RIGHT
: *y
= y_middle
; *x
= x_right
; break;
148 case UPPER_RIGHT
: *y
= y_upper
; *x
= x_right
; break;
153 static PangoFontMetrics
*setup_pango_return_metrics (GSCHEM_TOPLEVEL
*w_current
, PangoLayout
*layout
,
154 double scale_factor
, OBJECT
*o_current
)
156 PangoContext
*context
;
157 PangoFontDescription
*desc
;
158 PangoFontMetrics
*font_metrics
;
159 PangoAttrList
*attrs
;
160 cairo_font_options_t
*options
;
164 context
= pango_layout_get_context (layout
);
166 /* Switch off metric hinting, set medium outline hinting */
167 options
= cairo_font_options_create ();
168 cairo_font_options_set_hint_metrics (options
, CAIRO_HINT_METRICS_OFF
);
169 cairo_font_options_set_hint_style (options
, CAIRO_HINT_STYLE_MEDIUM
);
170 pango_cairo_context_set_font_options (context
, options
);
171 cairo_font_options_destroy (options
);
173 pango_cairo_context_set_resolution (context
, 1000. * scale_factor
);
174 font_size_pt
= o_text_get_font_size_in_points (w_current
->toplevel
,
177 desc
= pango_font_description_from_string (FONT_NAME
);
178 pango_font_description_set_size (desc
, (double)PANGO_SCALE
* font_size_pt
);
180 pango_layout_set_font_description (layout
, desc
);
181 font_metrics
= pango_context_get_metrics (context
, desc
, NULL
);
182 pango_font_description_free (desc
);
184 attrs
= pango_attr_list_new ();
185 unescaped
= unescape_text_and_overbars (o_current
->text
->disp_string
, attrs
);
186 pango_layout_set_text (layout
, unescaped
, -1);
188 pango_layout_set_attributes (layout
, attrs
);
189 pango_attr_list_unref (attrs
);
195 static void rotate_vector (double x
, double y
, double angle
,
196 double *rx
, double *ry
)
198 double costheta
= cos (angle
* M_PI
/ 180.);
199 double sintheta
= sin (angle
* M_PI
/ 180.);
201 *rx
= costheta
* x
- sintheta
* y
;
202 *ry
= sintheta
* x
+ costheta
* y
;
206 static void expand_bounds (int *left
, int *top
, int *right
, int *bottom
,
207 int new_x
, int new_y
)
209 *left
= MIN (*left
, new_x
);
210 *right
= MAX (*right
, new_x
);
211 *top
= MIN (*top
, new_y
);
212 *bottom
= MAX (*bottom
, new_y
);
216 /*! \todo Finish function documentation!!!
218 * \par Function Description
221 int o_text_get_rendered_bounds (void *user_data
, OBJECT
*o_current
,
222 int *min_x
, int *min_y
,
223 int *max_x
, int *max_y
)
225 GSCHEM_TOPLEVEL
*w_current
= user_data
;
226 TOPLEVEL
*toplevel
= w_current
->toplevel
;
230 PangoFontMetrics
*font_metrics
;
231 PangoRectangle logical_rect
;
232 PangoRectangle inked_rect
;
235 double tleft
, ttop
, tright
, tbottom
;
236 int left
, right
, top
, bottom
;
238 g_return_val_if_fail (o_current
!= NULL
, FALSE
);
239 g_return_val_if_fail (o_current
->text
!= NULL
, FALSE
);
241 if (o_current
->visibility
== INVISIBLE
&&
242 !toplevel
->show_hidden_text
)
245 if (o_current
->text
->disp_string
== NULL
)
248 cr
= gdk_cairo_create (w_current
->drawable
);
249 layout
= pango_cairo_create_layout (cr
);
251 font_metrics
= setup_pango_return_metrics (w_current
, layout
, 1., o_current
);
253 pango_layout_get_pixel_extents (layout
, &inked_rect
, &logical_rect
);
254 calculate_position (o_current
, font_metrics
, logical_rect
, inked_rect
, &x
, &y
);
255 pango_font_metrics_unref (font_metrics
);
257 tleft
= x
+ inked_rect
.x
;
258 tright
= x
+ inked_rect
.x
+ inked_rect
.width
;
259 /* Deliberately include bounds up to the height of the logical rect,
260 * since we draw overbars in that space. In the unlikely event that
261 * the inked rect extends above the logical (inked_rect.y is -ve),
262 * do take that into account.
264 ttop
= -y
- (inked_rect
.y
< 0 ? inked_rect
.y
: 0.);
265 tbottom
= -y
- inked_rect
.y
- inked_rect
.height
;
267 angle
= o_current
->text
->angle
;
268 /* Special case turns upside down text back upright */
272 rotate_vector (tleft
, ttop
, angle
, &rx
, &ry
);
275 rotate_vector (tright
, ttop
, angle
, &rx
, &ry
);
276 expand_bounds (&left
, &top
, &right
, &bottom
, rx
, ry
);
277 rotate_vector (tleft
, tbottom
, angle
, &rx
, &ry
);
278 expand_bounds (&left
, &top
, &right
, &bottom
, rx
, ry
);
279 rotate_vector (tright
, tbottom
, angle
, &rx
, &ry
);
280 expand_bounds (&left
, &top
, &right
, &bottom
, rx
, ry
);
282 *min_x
= o_current
->text
->x
+ left
;
283 *max_x
= o_current
->text
->x
+ right
;
284 *min_y
= o_current
->text
->y
+ top
;
285 *max_y
= o_current
->text
->y
+ bottom
;
287 g_object_unref (layout
);
295 static void draw_construction_lines (GSCHEM_TOPLEVEL
*w_current
,
297 PangoFontMetrics
*font_metrics
,
298 PangoRectangle logical_rect
)
302 double ascent
= pango_font_metrics_get_ascent (font_metrics
) / PANGO_SCALE
;
303 double descent
= pango_font_metrics_get_descent (font_metrics
) / PANGO_SCALE
;
304 cairo_t
*cr
= w_current
->cr
;
306 /* Pick an arbitrary size constant for the construction lines */
307 /* Includes * 10 factor for precision */
308 px
= SCREENabs (w_current
, 4 * 10);
310 /* Threshold the drawing to be above a certain size */
316 /* baseline, descent, ascent, height */
317 cairo_set_line_width (cr
, 2 * px
);
319 cairo_set_dash (cr
, &dashlength
, 1, 0);
321 /* Underline logical text rect in green, y coord is as gschem text origin */
322 cairo_set_source_rgba (cr
, 0, 0.6, 0, 0.5);
323 cairo_move_to (cr
, x
+ logical_rect
.x
, y
+ ascent
);
324 cairo_rel_line_to (cr
, logical_rect
.width
, 0);
327 /* Underline descent height in red */
328 cairo_set_source_rgba (cr
, 1, 0, 0, 1);
329 cairo_move_to (cr
, x
+ logical_rect
.x
, y
+ ascent
+ descent
);
330 cairo_rel_line_to (cr
, logical_rect
.width
, 0);
333 /* Overbar ascent height in yellow */
334 cairo_set_source_rgba (cr
, 1, 1, 0, 1);
335 cairo_move_to (cr
, x
+ logical_rect
.x
, y
);
336 cairo_rel_line_to (cr
, logical_rect
.width
, 0);
339 /* extents: width & height in blue */
340 cairo_set_source_rgba (cr
, 0, 0, 0.75, 0.5);
341 cairo_set_line_width (cr
, px
);
343 cairo_set_dash (cr
, &dashlength
, 1, 0);
344 cairo_rectangle (cr
, x
+ logical_rect
.x
, y
+ logical_rect
.y
,
345 logical_rect
.width
, logical_rect
.height
);
348 /* show layout origin point in black */
349 cairo_arc (cr
, x
, y
, 3 * px
, 0, 2 * M_PI
);
350 cairo_set_source_rgba (cr
, 0.0, 0, 0, 0.5);
353 /* text's advance in blue */
354 cairo_set_source_rgba (cr
, 0, 0, 0.75, 0.5);
355 cairo_arc (cr
, x
+ logical_rect
.x
+ logical_rect
.width
, y
+ logical_rect
.height
- descent
,
356 3 * px
, 0, 2 * M_PI
);
359 /* reference point in red */
360 cairo_arc (cr
, x
, y
+ ascent
, 3 * px
, 0, 2 * M_PI
);
361 cairo_set_source_rgba (cr
, 0.75, 0, 0, 0.5);
367 /*! \todo Finish function documentation!!!
369 * \par Function Description
372 static void o_text_draw_lowlevel(GSCHEM_TOPLEVEL
*w_current
, OBJECT
*o_current
,
373 int dx
, int dy
, COLOR
*color
)
375 TOPLEVEL
*toplevel
= w_current
->toplevel
;
376 cairo_t
*cr
= w_current
->cr
;
379 PangoFontMetrics
*font_metrics
;
380 PangoRectangle logical_rect
;
381 PangoRectangle inked_rect
;
383 g_return_if_fail (o_current
!= NULL
);
384 g_return_if_fail (o_current
->text
!= NULL
);
386 if (o_current
->visibility
== INVISIBLE
&&
387 !toplevel
->show_hidden_text
)
390 if (o_current
->text
->disp_string
== NULL
)
394 setup_pango_return_metrics (w_current
, w_current
->pl
,
395 toplevel
->page_current
->to_screen_x_constant
,
398 pango_layout_get_pixel_extents (w_current
->pl
, &inked_rect
, &logical_rect
);
399 calculate_position (o_current
, font_metrics
, logical_rect
, inked_rect
, &x
, &y
);
403 WORLDtoSCREEN (w_current
, o_current
->text
->x
+ dx
,
404 o_current
->text
->y
+ dy
, &sx
, &sy
);
405 cairo_translate (cr
, sx
, sy
);
407 /* Special case turns upside-down text back upright */
408 if (o_current
->text
->angle
!= 180) {
409 cairo_rotate (cr
, - M_PI
* o_current
->text
->angle
/ 180.);
412 gschem_cairo_set_source_color (w_current
, color
);
414 /* NB: Shift the position by 0.5px to match the hinting applied to single
415 * pixel wide lines. This means the text will sit correctly on top of
416 * the grid lines, and ensures consistency with other lines when the
417 * page view is zoomed out. */
418 cairo_move_to (cr
, x
+ 0.5, y
+ 0.5);
419 gschem_pango_show_layout (cr
, w_current
->pl
);
422 draw_construction_lines (w_current
, x
, y
, font_metrics
, logical_rect
);
425 pango_font_metrics_unref (font_metrics
);
430 /*! \todo Finish function documentation!!!
432 * \par Function Description
435 void o_text_draw(GSCHEM_TOPLEVEL
*w_current
, OBJECT
*o_current
)
437 TOPLEVEL
*toplevel
= w_current
->toplevel
;
438 int screen_x1
, screen_y1
;
439 int small_dist
, offset
;
441 g_return_if_fail (o_current
!= NULL
);
442 g_return_if_fail (o_current
->type
== OBJ_TEXT
);
443 g_return_if_fail (o_current
->text
!= NULL
);
445 if (toplevel
->DONT_REDRAW
== 1 ||
446 (o_current
->visibility
== INVISIBLE
&& !toplevel
->show_hidden_text
)) {
450 if (!w_current
->fast_mousepan
|| !w_current
->doing_pan
) {
452 o_text_draw_lowlevel (w_current
, o_current
, 0, 0,
453 o_drawing_color (w_current
, o_current
));
455 /* Indicate on the schematic that the text is invisible by */
456 /* drawing a little I on the screen at the origin */
457 if (o_current
->visibility
== INVISIBLE
&& toplevel
->show_hidden_text
) {
458 if (toplevel
->override_color
!= -1 ) {
459 gdk_gc_set_foreground(w_current
->gc
,
460 x_get_color(toplevel
->override_color
));
463 gdk_gc_set_foreground (w_current
->gc
, x_get_color (LOCK_COLOR
));
466 offset
= SCREENabs (w_current
, 10);
467 small_dist
= SCREENabs (w_current
, 20);
468 WORLDtoSCREEN (w_current
, o_current
->text
->x
, o_current
->text
->y
, &screen_x1
, &screen_y1
);
471 if (toplevel
->DONT_REDRAW
== 0) {
472 /* Top part of the I */
473 gdk_draw_line (w_current
->drawable
, w_current
->gc
,
476 screen_x1
+small_dist
,
478 /* Middle part of the I */
479 gdk_draw_line (w_current
->drawable
, w_current
->gc
,
480 screen_x1
+small_dist
/2,
482 screen_x1
+small_dist
/2,
483 screen_y1
+small_dist
);
484 /* Bottom part of the I */
485 gdk_draw_line (w_current
->drawable
, w_current
->gc
,
487 screen_y1
+small_dist
,
488 screen_x1
+small_dist
,
489 screen_y1
+small_dist
);
493 /* draw a box in it's place */
494 gschem_cairo_box (w_current
, 0,
495 o_current
->w_left
, o_current
->w_bottom
,
496 o_current
->w_right
, o_current
->w_top
);
498 gschem_cairo_set_source_color (w_current
,
499 o_drawing_color (w_current
, o_current
));
500 gschem_cairo_stroke (w_current
, TYPE_SOLID
, END_NONE
, 0, -1, -1);
505 /* return if text origin marker displaying is disabled */
506 if (w_current
->text_origin_marker
== FALSE
) {
510 small_dist
= SCREENabs (w_current
, 10);
512 /* Switch of mark drawing for non-selected text, and at small sizes */
513 if (!o_current
->selected
|| small_dist
< MINIMUM_MARK_SMALL_DIST
)
516 WORLDtoSCREEN (w_current
, o_current
->text
->x
, o_current
->text
->y
, &screen_x1
, &screen_y1
);
518 /* this is not really a fix, but a lame patch */
519 /* not having this will cause a bad draw of things when coords */
520 /* get close to the 2^15 limit of X */
521 if (screen_x1
+small_dist
> 32767 || screen_y1
+small_dist
> 32767) {
525 if (toplevel
->override_color
!= -1 ) {
526 gdk_gc_set_foreground(w_current
->gc
,
527 x_get_color(toplevel
->override_color
));
530 gdk_gc_set_foreground (w_current
->gc
, x_get_color (LOCK_COLOR
));
533 if (toplevel
->DONT_REDRAW
== 0) {
534 gdk_draw_line (w_current
->drawable
, w_current
->gc
,
535 screen_x1
-small_dist
,
536 screen_y1
+small_dist
,
537 screen_x1
+small_dist
,
538 screen_y1
-small_dist
);
540 gdk_draw_line (w_current
->drawable
, w_current
->gc
,
541 screen_x1
+small_dist
,
542 screen_y1
+small_dist
,
543 screen_x1
-small_dist
,
544 screen_y1
-small_dist
);
549 /*! \todo Finish function documentation!!!
551 * \par Function Description
554 void o_text_draw_place (GSCHEM_TOPLEVEL
*w_current
, int dx
, int dy
, OBJECT
*o_current
)
556 TOPLEVEL
*toplevel
= w_current
->toplevel
;
559 if (o_current
->visibility
== INVISIBLE
&& !toplevel
->show_hidden_text
) {
563 /* always display text which is 12 or larger */
564 factor
= (int) toplevel
->page_current
->to_world_x_constant
;
565 if ((factor
< w_current
->text_display_zoomfactor
) ||
566 o_current
->text
->size
>= 12 ||
567 w_current
->text_feedback
== ALWAYS
) {
569 o_text_draw_lowlevel (w_current
, o_current
, dx
, dy
,
570 x_color_lookup_dark (o_current
->color
));
573 /* text is too small so draw a box in it's place */
575 gschem_cairo_box (w_current
, 0,
576 o_current
->w_left
+ dx
, o_current
->w_bottom
+ dy
,
577 o_current
->w_right
+ dx
, o_current
->w_top
+ dy
);
579 gschem_cairo_set_source_color (w_current
,
580 x_color_lookup_dark (o_current
->color
));
581 gschem_cairo_stroke (w_current
, TYPE_SOLID
, END_NONE
, 0, -1, -1);
586 /*! \todo Finish function documentation!!!
588 * \par Function Description
591 void o_text_prepare_place(GSCHEM_TOPLEVEL
*w_current
, char *text
)
593 TOPLEVEL
*toplevel
= w_current
->toplevel
;
595 /* Insert the new object into the buffer at world coordinates (0,0).
596 * It will be translated to the mouse coordinates during placement. */
598 w_current
->first_wx
= 0;
599 w_current
->first_wy
= 0;
601 w_current
->last_drawb_mode
= LAST_DRAWB_MODE_NONE
;
603 /* remove the old place list if it exists */
604 s_delete_object_glist(toplevel
, toplevel
->page_current
->place_list
);
605 toplevel
->page_current
->place_list
= NULL
;
607 /* here you need to add OBJ_TEXT when it's done */
608 toplevel
->page_current
->place_list
=
609 g_list_append(toplevel
->page_current
->place_list
,
610 o_text_new (toplevel
, OBJ_TEXT
, TEXT_COLOR
,
611 0, 0, LOWER_LEFT
, 0, /* zero is angle */
613 w_current
->text_size
,
614 /* has to be visible so you can place it */
615 /* visibility is set when you create the object */
616 VISIBLE
, SHOW_NAME_VALUE
));
618 w_current
->inside_action
= 1;
619 i_set_state (w_current
, ENDTEXT
);
623 /*! \todo Finish function documentation!!!
625 * \par Function Description
628 void o_text_edit(GSCHEM_TOPLEVEL
*w_current
, OBJECT
*o_current
)
630 /* you need to check to make sure only one object is selected */
631 /* no actually this is okay... not here in o_edit */
632 text_edit_dialog(w_current
,
633 o_text_get_string (w_current
->toplevel
, o_current
),
634 o_current
->text
->size
, o_current
->text
->alignment
);
637 /*! \todo Finish function documentation!!!
639 * \par Function Description
642 void o_text_edit_end(GSCHEM_TOPLEVEL
*w_current
, char *string
, int len
, int text_size
,
645 TOPLEVEL
*toplevel
= w_current
->toplevel
;
651 s_current
= geda_list_get_glist( toplevel
->page_current
->selection_list
);
652 numselect
= g_list_length( geda_list_get_glist( toplevel
->page_current
->selection_list
));
654 while(s_current
!= NULL
) {
655 object
= (OBJECT
*) s_current
->data
;
658 if (object
->type
== OBJ_TEXT
) {
659 o_invalidate (w_current
, object
);
661 object
->text
->size
= text_size
;
662 object
->text
->alignment
= text_alignment
;
664 /* probably the text object should be extended to carry a color */
665 /* and we should pass it here with a function parameter (?) */
666 object
->color
= w_current
->edit_color
;
668 /* only change text string if there is only ONE text object selected */
669 if (numselect
== 1 && string
) {
670 o_text_set_string (w_current
->toplevel
, object
, string
);
671 /* handle slot= attribute, it's a special case */
672 if (object
->attached_to
!= NULL
&&
673 g_ascii_strncasecmp (string
, "slot=", 5) == 0) {
674 o_slot_end (w_current
, object
->attached_to
, string
);
677 o_text_recreate(toplevel
, object
);
678 o_invalidate (w_current
, object
);
682 s_current
= g_list_next(s_current
);
685 toplevel
->page_current
->CHANGED
= 1;
686 o_undo_savestate(w_current
, UNDO_ALL
);
689 /*! \todo Finish function documentation!!!
691 * \par Function Description
694 * The object passed in should be the REAL object, NOT any copy in any
697 void o_text_change(GSCHEM_TOPLEVEL
*w_current
, OBJECT
*object
, char *string
,
698 int visibility
, int show
)
700 TOPLEVEL
*toplevel
= w_current
->toplevel
;
701 if (object
== NULL
) {
705 if (object
->type
!= OBJ_TEXT
) {
709 /* erase old object */
710 o_invalidate (w_current
, object
);
712 /* second change the real object */
713 o_text_set_string (toplevel
, object
, string
);
715 object
->visibility
= visibility
;
716 object
->show_name_value
= show
;
717 o_text_recreate(toplevel
, object
);
718 o_invalidate (w_current
, object
);
720 /* handle slot= attribute, it's a special case */
721 if (object
->attached_to
!= NULL
&&
722 g_ascii_strncasecmp (string
, "slot=", 5) == 0) {
723 o_slot_end (w_current
, object
->attached_to
, string
);
726 toplevel
->page_current
->CHANGED
= 1;