gschem: write object visibility field with setter
[geda-gaf.git] / gschem / src / o_text.c
blobe6595478d178fe10f198ea8660f1f1ea281857eb
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20 #include <config.h>
22 #include <stdio.h>
23 #include <sys/stat.h>
24 #include <math.h>
25 #ifdef HAVE_STRING_H
26 #include <string.h>
27 #endif
28 #ifdef HAVE_UNISTD_H
29 #include <unistd.h>
30 #endif
32 #include "gschem.h"
34 #ifdef HAVE_LIBDMALLOC
35 #include <dmalloc.h>
36 #endif
38 #define MINIMUM_MARK_SMALL_DIST 1
40 #if 1
41 # define FONT_NAME "Arial"
42 #else
43 # define FONT_NAME "Helvetica"
44 #endif
46 #undef DEBUG_TEXT
49 char *unescape_text_and_overbars (char *text, PangoAttrList *attrs)
51 char *p, *sp, *strip_text;
52 char *overbar_start = NULL;
53 int escape = FALSE;
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 == '\\') {
64 escape = TRUE;
65 continue;
68 if (escape && *p == '_') {
69 /* Overbar start or end sequence */
70 if (overbar_start != NULL) {
71 finish_overbar = TRUE;
72 } else {
73 overbar_start = sp;
75 } else {
76 /* just append the character, which may have been escaped */
77 *sp++ = *p;
79 escape = FALSE;
81 if (overbar_start != NULL &&
82 (finish_overbar || *p == '\0')) {
83 PangoAttribute *attr;
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);
89 overbar_start = NULL;
92 /* end of the string, stop iterating */
93 if (*p == '\0')
94 break;
97 return strip_text;
101 static void calculate_position (OBJECT *object,
102 PangoFontMetrics *font_metrics,
103 PangoRectangle logical_rect,
104 PangoRectangle inked_rect,
105 double *x, double *y)
107 double temp;
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;
112 x_left = 0;
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
122 * entire text block.
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) {
138 default:
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;
161 double font_size_pt;
162 char *unescaped;
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,
175 o_current);
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);
187 g_free (unescaped);
188 pango_layout_set_attributes (layout, attrs);
189 pango_attr_list_unref (attrs);
191 return font_metrics;
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!!!
217 * \brief
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;
227 PangoLayout *layout;
228 cairo_t *cr;
229 double x, y;
230 PangoFontMetrics *font_metrics;
231 PangoRectangle logical_rect;
232 PangoRectangle inked_rect;
233 int angle;
234 double rx, ry;
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_is_visible (toplevel, o_current) &&
242 !toplevel->show_hidden_text)
243 return FALSE;
245 if (o_current->text->disp_string == NULL)
246 return FALSE;
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 */
269 if (angle == 180)
270 angle = 0;
272 rotate_vector (tleft, ttop, angle, &rx, &ry);
273 left = right = rx;
274 top = bottom = 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);
288 cairo_destroy (cr);
290 return TRUE;
294 #ifdef DEBUG_TEXT
295 static void draw_construction_lines (GSCHEM_TOPLEVEL *w_current,
296 double x, double y,
297 PangoFontMetrics *font_metrics,
298 PangoRectangle logical_rect)
300 double px = 1.;
301 double dashlength;
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 */
311 if (px < 2.)
312 return;
314 px = px / 10.;
316 /* baseline, descent, ascent, height */
317 cairo_set_line_width (cr, 2 * px);
318 dashlength = 9 * 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);
325 cairo_stroke (cr);
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);
331 cairo_stroke (cr);
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);
337 cairo_stroke (cr);
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);
342 dashlength = 3 * 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);
346 cairo_stroke (cr);
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);
351 cairo_fill (cr);
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);
357 cairo_fill (cr);
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);
362 cairo_fill (cr);
364 #endif
367 /*! \todo Finish function documentation!!!
368 * \brief
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;
377 int sx, sy;
378 double x, y;
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_is_visible (toplevel, o_current) &&
387 !toplevel->show_hidden_text)
388 return;
390 if (o_current->text->disp_string == NULL)
391 return;
393 font_metrics =
394 setup_pango_return_metrics (w_current, w_current->pl,
395 toplevel->page_current->to_screen_x_constant,
396 o_current);
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);
401 cairo_save (cr);
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);
421 #ifdef DEBUG_TEXT
422 draw_construction_lines (w_current, x, y, font_metrics, logical_rect);
423 #endif
425 pango_font_metrics_unref (font_metrics);
426 cairo_restore (cr);
430 /*! \todo Finish function documentation!!!
431 * \brief
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 (!o_is_visible (toplevel, o_current) && !toplevel->show_hidden_text) {
446 return;
449 if (!w_current->fast_mousepan || !w_current->doing_pan) {
451 o_text_draw_lowlevel (w_current, o_current, 0, 0,
452 o_drawing_color (w_current, o_current));
454 /* Indicate on the schematic that the text is invisible by */
455 /* drawing a little I on the screen at the origin */
456 if (!o_is_visible (toplevel, o_current) && toplevel->show_hidden_text) {
457 if (toplevel->override_color != -1 ) {
458 gdk_gc_set_foreground(w_current->gc,
459 x_get_color(toplevel->override_color));
460 } else {
462 gdk_gc_set_foreground (w_current->gc, x_get_color (LOCK_COLOR));
465 offset = SCREENabs (w_current, 10);
466 small_dist = SCREENabs (w_current, 20);
467 WORLDtoSCREEN (w_current, o_current->text->x, o_current->text->y, &screen_x1, &screen_y1);
468 screen_x1 += offset;
469 screen_y1 += offset;
470 /* Top part of the I */
471 gdk_draw_line (w_current->drawable, w_current->gc,
472 screen_x1,
473 screen_y1,
474 screen_x1+small_dist,
475 screen_y1);
476 /* Middle part of the I */
477 gdk_draw_line (w_current->drawable, w_current->gc,
478 screen_x1+small_dist/2,
479 screen_y1,
480 screen_x1+small_dist/2,
481 screen_y1+small_dist);
482 /* Bottom part of the I */
483 gdk_draw_line (w_current->drawable, w_current->gc,
484 screen_x1,
485 screen_y1+small_dist,
486 screen_x1+small_dist,
487 screen_y1+small_dist);
489 } else {
490 /* draw a box in it's place */
491 gschem_cairo_box (w_current, 0,
492 o_current->w_left, o_current->w_bottom,
493 o_current->w_right, o_current->w_top);
495 gschem_cairo_set_source_color (w_current,
496 o_drawing_color (w_current, o_current));
497 gschem_cairo_stroke (w_current, TYPE_SOLID, END_NONE, 0, -1, -1);
499 return;
502 /* return if text origin marker displaying is disabled */
503 if (w_current->text_origin_marker == FALSE) {
504 return;
507 small_dist = SCREENabs (w_current, 10);
509 /* Switch of mark drawing for non-selected text, and at small sizes */
510 if (!o_current->selected || small_dist < MINIMUM_MARK_SMALL_DIST)
511 return;
513 WORLDtoSCREEN (w_current, o_current->text->x, o_current->text->y, &screen_x1, &screen_y1);
515 /* this is not really a fix, but a lame patch */
516 /* not having this will cause a bad draw of things when coords */
517 /* get close to the 2^15 limit of X */
518 if (screen_x1+small_dist > 32767 || screen_y1+small_dist > 32767) {
519 return;
522 if (toplevel->override_color != -1 ) {
523 gdk_gc_set_foreground(w_current->gc,
524 x_get_color(toplevel->override_color));
525 } else {
527 gdk_gc_set_foreground (w_current->gc, x_get_color (LOCK_COLOR));
530 gdk_draw_line (w_current->drawable, w_current->gc,
531 screen_x1-small_dist,
532 screen_y1+small_dist,
533 screen_x1+small_dist,
534 screen_y1-small_dist);
536 gdk_draw_line (w_current->drawable, w_current->gc,
537 screen_x1+small_dist,
538 screen_y1+small_dist,
539 screen_x1-small_dist,
540 screen_y1-small_dist);
544 /*! \todo Finish function documentation!!!
545 * \brief
546 * \par Function Description
549 void o_text_draw_place (GSCHEM_TOPLEVEL *w_current, int dx, int dy, OBJECT *o_current)
551 TOPLEVEL *toplevel = w_current->toplevel;
552 int factor;
554 if (!o_is_visible (toplevel, o_current) && !toplevel->show_hidden_text) {
555 return;
558 /* always display text which is 12 or larger */
559 factor = (int) toplevel->page_current->to_world_x_constant;
560 if ((factor < w_current->text_display_zoomfactor) ||
561 o_current->text->size >= 12 ||
562 w_current->text_feedback == ALWAYS) {
564 o_text_draw_lowlevel (w_current, o_current, dx, dy,
565 x_color_lookup_dark (o_current->color));
567 } else {
568 /* text is too small so draw a box in it's place */
570 gschem_cairo_box (w_current, 0,
571 o_current->w_left + dx, o_current->w_bottom + dy,
572 o_current->w_right + dx, o_current->w_top + dy);
574 gschem_cairo_set_source_color (w_current,
575 x_color_lookup_dark (o_current->color));
576 gschem_cairo_stroke (w_current, TYPE_SOLID, END_NONE, 0, -1, -1);
581 /*! \todo Finish function documentation!!!
582 * \brief
583 * \par Function Description
586 void o_text_prepare_place(GSCHEM_TOPLEVEL *w_current, char *text)
588 TOPLEVEL *toplevel = w_current->toplevel;
590 /* Insert the new object into the buffer at world coordinates (0,0).
591 * It will be translated to the mouse coordinates during placement. */
593 w_current->first_wx = 0;
594 w_current->first_wy = 0;
596 w_current->last_drawb_mode = LAST_DRAWB_MODE_NONE;
598 /* remove the old place list if it exists */
599 s_delete_object_glist(toplevel, toplevel->page_current->place_list);
600 toplevel->page_current->place_list = NULL;
602 /* here you need to add OBJ_TEXT when it's done */
603 toplevel->page_current->place_list =
604 g_list_append(toplevel->page_current->place_list,
605 o_text_new (toplevel, OBJ_TEXT, TEXT_COLOR,
606 0, 0, LOWER_LEFT, 0, /* zero is angle */
607 text,
608 w_current->text_size,
609 /* has to be visible so you can place it */
610 /* visibility is set when you create the object */
611 VISIBLE, SHOW_NAME_VALUE));
613 w_current->inside_action = 1;
614 i_set_state (w_current, ENDTEXT);
618 /*! \todo Finish function documentation!!!
619 * \brief
620 * \par Function Description
623 void o_text_edit(GSCHEM_TOPLEVEL *w_current, OBJECT *o_current)
625 /* you need to check to make sure only one object is selected */
626 /* no actually this is okay... not here in o_edit */
627 text_edit_dialog(w_current,
628 o_text_get_string (w_current->toplevel, o_current),
629 o_current->text->size, o_current->text->alignment);
632 /*! \todo Finish function documentation!!!
633 * \brief
634 * \par Function Description
637 void o_text_edit_end(GSCHEM_TOPLEVEL *w_current, char *string, int len, int text_size,
638 int text_alignment)
640 TOPLEVEL *toplevel = w_current->toplevel;
641 OBJECT *object;
642 GList *s_current;
643 int numselect;
645 /* skip over head */
646 s_current = geda_list_get_glist( toplevel->page_current->selection_list );
647 numselect = g_list_length( geda_list_get_glist( toplevel->page_current->selection_list ));
649 while(s_current != NULL) {
650 object = (OBJECT *) s_current->data;
652 if (object) {
653 if (object->type == OBJ_TEXT) {
655 object->text->size = text_size;
656 object->text->alignment = text_alignment;
658 /* probably the text object should be extended to carry a color */
659 /* and we should pass it here with a function parameter (?) */
660 object->color = w_current->edit_color;
662 /* only change text string if there is only ONE text object selected */
663 if (numselect == 1 && string) {
664 o_text_set_string (w_current->toplevel, object, string);
665 /* handle slot= attribute, it's a special case */
666 if (object->attached_to != NULL &&
667 g_ascii_strncasecmp (string, "slot=", 5) == 0) {
668 o_slot_end (w_current, object->attached_to, string);
671 o_text_recreate(toplevel, object);
675 s_current = g_list_next(s_current);
678 toplevel->page_current->CHANGED = 1;
679 o_undo_savestate(w_current, UNDO_ALL);
682 /*! \todo Finish function documentation!!!
683 * \brief
684 * \par Function Description
686 * \note
687 * The object passed in should be the REAL object, NOT any copy in any
688 * selection list
690 void o_text_change(GSCHEM_TOPLEVEL *w_current, OBJECT *object, char *string,
691 int visibility, int show)
693 TOPLEVEL *toplevel = w_current->toplevel;
694 if (object == NULL) {
695 return;
698 if (object->type != OBJ_TEXT) {
699 return;
702 o_text_set_string (toplevel, object, string);
704 o_set_visibility (toplevel, object, visibility);
705 object->show_name_value = show;
706 o_text_recreate(toplevel, object);
708 /* handle slot= attribute, it's a special case */
709 if (object->attached_to != NULL &&
710 g_ascii_strncasecmp (string, "slot=", 5) == 0) {
711 o_slot_end (w_current, object->attached_to, string);
714 toplevel->page_current->CHANGED = 1;