/* * Text.c -- Implementation of Text item. * * Authors : Patrick LECOANET * Creation date : Sat Mar 25 13:58:39 1995 */ /* * Copyright (c) 1993 - 1999 CENA, Patrick Lecoanet -- * * This code is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This code is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this code; if not, write to the Free * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * */ /* * Some functions in this file are derived from tkCanvText.c and thus * copyrighted: * * Copyright (c) 1991-1994 The Regents of the University of California. * Copyright (c) 1994-1995 Sun Microsystems, Inc. * */ #include #include #include #include #include #include #include "Item.h" #include "Geo.h" #include "Draw.h" #include "Types.h" #include "WidgetInfo.h" #include "tkZinc.h" #include "Image.h" static const char rcsid[] = "$Imagine: Text.c,v 1.13 1997/05/15 11:35:46 lecoanet Exp $"; static const char compile_id[] = "$Compile: " __FILE__ " " __DATE__ " " __TIME__ " $"; /* * Bit offset of flags. */ #define UNDERLINED 1 #define OVERSTRIKED 2 /* ********************************************************************************** * * Specific Text item record * ********************************************************************************** */ typedef struct _TextLineInfo { char *start; /* Index of first char in line */ int num_chars; /* Number of displayed chars in line */ ZnPoint text_origin; /* X pos for drawing the line */ int width; /* Line width in pixels */ } TextLineInfoStruct, *TextLineInfo; typedef struct _TextItemStruct { ItemStruct header; /* Public data */ ZnPoint pos; ZnAnchor anchor; ZnAnchor connection_anchor; ZnColor color; int alpha; char *text; Pixmap fill_pattern; ZnFont font; ZnJustify alignment; int width; int spacing; unsigned char flags; /* Private data */ ZnPoint pos_dev; int num_chars; int insert_index; ZnList text_info; int max_width; FontBitmap *fb; } TextItemStruct, *TextItem; static ZnAttrConfig text_attrs[] = { { ZN_CONFIG_JUSTIFY, "-alignment", NULL, Tk_Offset(TextItemStruct, alignment), 0, ZN_COORDS_FLAG|ZN_LAYOUT_FLAG, False }, { ZN_CONFIG_UINT, "-alpha", NULL, Tk_Offset(TextItemStruct, alpha), 0, ZN_DRAW_FLAG, False }, { ZN_CONFIG_ANCHOR, "-anchor", NULL, Tk_Offset(TextItemStruct, anchor), 0, ZN_COORDS_FLAG, False }, { ZN_CONFIG_COLOR, "-color", NULL, Tk_Offset(TextItemStruct, color), 0, ZN_DRAW_FLAG, False }, { ZN_CONFIG_BOOL, "-composerotation", NULL, Tk_Offset(TextItemStruct, header.flags), COMPOSE_ROTATION_BIT, ZN_COORDS_FLAG, False }, { ZN_CONFIG_BOOL, "-composescale", NULL, Tk_Offset(TextItemStruct, header.flags), COMPOSE_SCALE_BIT, ZN_COORDS_FLAG, False }, { ZN_CONFIG_ITEM, "-connecteditem", NULL, Tk_Offset(TextItemStruct, header.connected_item), 0, ZN_COORDS_FLAG|ZN_ITEM_FLAG, False }, { ZN_CONFIG_ANCHOR, "-connectionanchor", NULL, Tk_Offset(TextItemStruct, connection_anchor), 0, ZN_COORDS_FLAG, False }, { ZN_CONFIG_PATTERN, "-fillpattern", NULL, Tk_Offset(TextItemStruct, fill_pattern), 0, ZN_DRAW_FLAG, False }, { ZN_CONFIG_FONT, "-font", NULL, Tk_Offset(TextItemStruct, font), 0, ZN_COORDS_FLAG|ZN_LAYOUT_FLAG, False }, { ZN_CONFIG_BOOL, "-overstriked", NULL, Tk_Offset(TextItemStruct, flags), OVERSTRIKED, ZN_DRAW_FLAG, False }, { ZN_CONFIG_POINT, "-position", NULL, Tk_Offset(TextItemStruct, pos), 0, ZN_COORDS_FLAG, False}, { ZN_CONFIG_PRI, "-priority", NULL, Tk_Offset(TextItemStruct, header.priority), 0, ZN_DRAW_FLAG|ZN_REPICK_FLAG, False }, { ZN_CONFIG_BOOL, "-sensitive", NULL, Tk_Offset(TextItemStruct, header.flags), SENSITIVE_BIT, ZN_REPICK_FLAG, False }, { ZN_CONFIG_DIM, "-spacing", NULL, Tk_Offset(TextItemStruct, spacing), 0, ZN_COORDS_FLAG|ZN_LAYOUT_FLAG, False }, { ZN_CONFIG_TAGS, "-tags", NULL, Tk_Offset(TextItemStruct, header.tags), 0, 0, False }, { ZN_CONFIG_TEXT, "-text", NULL, Tk_Offset(TextItemStruct, text), 0, ZN_COORDS_FLAG|ZN_LAYOUT_FLAG, False }, { ZN_CONFIG_BOOL, "-underlined", NULL, Tk_Offset(TextItemStruct, flags), UNDERLINED, ZN_DRAW_FLAG, False }, { ZN_CONFIG_BOOL, "-visible", NULL, Tk_Offset(TextItemStruct, header.flags), VISIBLE_BIT, ZN_DRAW_FLAG|ZN_REPICK_FLAG|ZN_VIS_FLAG, False }, { ZN_CONFIG_DIM, "-width", NULL, Tk_Offset(TextItemStruct, width), 0, ZN_COORDS_FLAG|ZN_LAYOUT_FLAG, False }, { ZN_CONFIG_END, NULL, NULL, 0, 0, 0 } }; /* ********************************************************************************** * * Init -- * ********************************************************************************** */ static int Init(Item item, int *argc, Tcl_Obj *CONST *args[]) { WidgetInfo *wi = item->wi; TextItem text = (TextItem) item; text->text_info = NULL; /* Init attributes */ SET(item->flags, VISIBLE_BIT); SET(item->flags, SENSITIVE_BIT); SET(item->flags, COMPOSE_ROTATION_BIT); SET(item->flags, COMPOSE_SCALE_BIT); item->priority = DEFAULT_TEXT_PRIORITY; text->pos.x = text->pos.y = 0.0; text->text = ""; text->num_chars = 0; text->fill_pattern = ZnUnspecifiedPattern; text->anchor = ZnAnchorNW; text->connection_anchor = ZnAnchorSW; text->color = ZnGetColorByValue(wi->win, wi->fore_color); text->alpha = 255; text->alignment = ZnJustifyLeft; text->font = Tk_GetFont(wi->interp, wi->win, Tk_NameOfFont(wi->font)); text->width = 0; text->spacing = 0; text->fb = NULL; text->insert_index = 0; CLEAR(text->flags, UNDERLINED); CLEAR(text->flags, OVERSTRIKED); return ZN_OK; } /* ********************************************************************************** * * Clone -- * ********************************************************************************** */ static void Clone(Item item) { TextItem text = (TextItem) item; WidgetInfo *wi = item->wi; char *str; if (strlen(text->text) != 0) { str = ZnMalloc((strlen(text->text) + 1) * sizeof(char)); strcpy(str, text->text); text->text = str; } if (text->fill_pattern != ZnUnspecifiedPattern) { text->fill_pattern = Tk_GetBitmap(wi->interp, wi->win, Tk_NameOfBitmap(wi->dpy, text->fill_pattern)); } text->color = ZnGetColorByValue(wi->win, text->color); text->font = Tk_GetFont(wi->interp, wi->win, Tk_NameOfFont(text->font)); if (text->text_info) { text->text_info = ZnListDuplicate(text->text_info); } else { /* * Needed in case the layout of the model has not been * done yet. */ SET(item->inv_flags, ZN_LAYOUT_FLAG); } } /* ********************************************************************************** * * Destroy -- * ********************************************************************************** */ static void Destroy(Item item) { WidgetInfo *wi = item->wi; TextItem text = (TextItem) item; if (strlen(text->text) != 0) { ZnFree(text->text); } if (text->fill_pattern != ZnUnspecifiedPattern) { Tk_FreeBitmap(wi->dpy, text->fill_pattern); text->fill_pattern = ZnUnspecifiedPattern; } ZnFreeColor(text->color); Tk_FreeFont(text->font); if (text->text_info) { ZnListFree(text->text_info); } } /* ********************************************************************************** * * Configure -- * ********************************************************************************** */ static int Configure(Item item, int argc, Tcl_Obj *CONST argv[], int *flags) { Item old_connected; ZnFont old_font; TextItem text = (TextItem) item; old_connected = item->connected_item; old_font = text->font; if (ITEM_P.ConfigureAttributes((char *) item, -1, argc, argv, flags) == ZN_ERROR) { return ZN_ERROR; } if (ISSET(*flags, ZN_ITEM_FLAG)) { /* * If the new connected item is not appropriate back up * to the old one. */ if ((item->connected_item == ZN_NO_ITEM) || (item->connected_item->class->has_anchors && (item->parent == item->connected_item->parent))) { ITEM.UpdateItemDependency(item, old_connected); } else { item->connected_item = old_connected; } } if (old_font != text->font) { text->fb = NULL; } return ZN_OK; } /* ********************************************************************************** * * Query -- * ********************************************************************************** */ static int Query(Item item, int argc, Tcl_Obj *CONST argv[]) { if (ITEM_P.QueryAttribute((char *) item, -1, argv[0]) == ZN_ERROR) { return ZN_ERROR; } return ZN_OK; } /* ********************************************************************************** * * ComputeCoordinates -- * ********************************************************************************** */ static void ComputeCoordinates(Item item, ZnBool force) { WidgetInfo *wi = item->wi; TextItem text = (TextItem) item; TextLineInfo infos; Tk_FontMetrics fm; int i, fuzz, num_lines; int cur_dy; int font_height, height; ResetBBox(&item->item_bounding_box); text->num_chars = strlen(text->text); if (text->insert_index > text->num_chars) { text->insert_index = text->num_chars; } if (text->num_chars == 0) { return; } Tk_GetFontMetrics(text->font, &fm); font_height = fm.ascent+fm.descent; /* * The layout need not be done each time the item is moved, scaled * or rotated. */ if (ISSET(item->inv_flags, ZN_LAYOUT_FLAG)) { char *scan; int wrap, line_index, prev_num_lines; /*printf("layout\n");*/ text->max_width = 0; if (text->text_info != NULL) { prev_num_lines = ZnListSize(text->text_info); ZnListEmpty(text->text_info); } else { prev_num_lines = 0; text->text_info = ZnListNew(1, sizeof(TextLineInfoStruct)); } if (text->width > 0) { wrap = text->width; } else { wrap = 100000; } scan = text->text; while (*scan) { TextLineInfoStruct info; char *ptr; int num; line_index = scan - text->text; /* * Limit the excursion of Tk_MeasureChars to the end * of the line. Do not include \n in the measure done. */ ptr = strchr(scan, '\n'); if (ptr) { num = ptr-scan; } else { num = strlen(scan); } info.num_chars = Tk_MeasureChars(text->font, scan, num, wrap, TK_WHOLE_WORDS|TK_AT_LEAST_ONE, &info.width); /* * Adjust for the newline at the end of line. */ if (ptr) { info.num_chars++; } info.start = scan; text->max_width = MAX(info.width, text->max_width); scan += info.num_chars; /* Build a line info even for an empty line * at the end of text or for an empty text. * It is needed to enable selection and cursor * insertion to behave correctly. */ ZnListAdd(text->text_info, &info, ZnListTail); /*printf("adding a line : %s, num_chars : %d, width : %d\n", info.start, info.num_chars, info.width);*/ /* * Skip terminating space or return char if any. */ if (isspace(*scan) || *scan == '\n') { scan++; } } /* * Compute x & y positions for all lines in text_info. The coordinates are * in text natural units NOT transformed units. */ cur_dy = fm.ascent; num_lines = ZnListSize(text->text_info); infos = (TextLineInfo) ZnListArray(text->text_info); for (i = 0; i < num_lines; i++) { switch (text->alignment) { case ZnJustifyLeft: infos[i].text_origin.x = 0; break; case ZnJustifyCenter: infos[i].text_origin.x = (text->max_width + 1 - infos[i].width)/2; break; case ZnJustifyRight: infos[i].text_origin.x = text->max_width + 1 - infos[i].width; break; } infos[i].text_origin.y = cur_dy; cur_dy += font_height + text->spacing; /*printf("fixing line %d x : %f, y : %f\n", i, infos[i].text_origin.x, infos[i].text_origin.y);*/ } } /* ISSET(item->inv_flags, INV_TEXT_LAYOUT) */ num_lines = ZnListSize(text->text_info); height = num_lines * font_height + (num_lines-1) * text->spacing; /* * The connected item support anchors, this is checked by * configure. */ if (item->connected_item != ZN_NO_ITEM) { item->connected_item->class->GetAnchor(item->connected_item, text->connection_anchor, &text->pos_dev); } else { ZnTransformPoint(wi->current_transfo, &text->pos, &text->pos_dev); text->pos_dev.x = REAL_TO_INT(text->pos_dev.x); text->pos_dev.y = REAL_TO_INT(text->pos_dev.y); } Anchor2Origin(&text->pos_dev, text->max_width, height, text->anchor, &text->pos_dev); /* * Compute the bounding box. */ AddPointToBBox(&item->item_bounding_box, text->pos_dev.x, text->pos_dev.y); AddPointToBBox(&item->item_bounding_box, text->pos_dev.x+text->max_width+1, text->pos_dev.y+height); fuzz = 1+(wi->text_info.insert_width/2); item->item_bounding_box.orig.x -= fuzz; item->item_bounding_box.orig.y -= fuzz; item->item_bounding_box.corner.x += fuzz; item->item_bounding_box.corner.y += fuzz; /* * Update connected items. */ SET(item->flags, UPDATE_DEPENDENT_BIT); } /* ********************************************************************************** * * ToArea -- * Tell if the object is entirely outside (-1), * entirely inside (1) or in between (0). * ********************************************************************************** */ static int ToArea(Item item, ZnBBox *area, Tk_Uid tag_uid, int enclosed, ZnBool report) { TextItem text = (TextItem) item; int inside = -1; ZnBool first_done = False; int num_lines, i; TextLineInfo lines, lines_ptr; Tk_FontMetrics fm; int font_height; ZnBBox line_bbox; ZnPoint o; lines = (TextLineInfo) ZnListArray(text->text_info); num_lines = ZnListSize(text->text_info); Tk_GetFontMetrics(text->font, &fm); font_height = fm.descent + fm.ascent; if (text->spacing > 0) { font_height += text->spacing; } /*printf("text %d, num lines=%d\n", item->id, num_lines);*/ for (i = 0, lines_ptr = lines; i < num_lines; i++, lines_ptr++) { ResetBBox(&line_bbox); o.x = text->pos_dev.x + lines_ptr->text_origin.x; o.y = text->pos_dev.y + lines_ptr->text_origin.y - fm.ascent; AddPointToBBox(&line_bbox, o.x, o.y); AddPointToBBox(&line_bbox, o.x + lines_ptr->width, o.y + font_height); if (!first_done) { first_done = True; inside = BBoxInBBox(&line_bbox, area); if (inside == 0) { return 0; } } else { if (BBoxInBBox(&line_bbox, area) == 0) { return 0; } } } return inside; } /* ********************************************************************************** * * Draw -- * ********************************************************************************** */ static void Draw(Item item) { WidgetInfo *wi = item->wi; TextItem text = (TextItem) item; XGCValues values; int gc_mask = 0; Tk_FontMetrics fm; int font_height; int num_lines, i, line_index, char_index; TextLineInfo lines, lines_ptr; int underline_thickness, underline_pos=0, overstrike_pos=0; int sel_first_line=-1, sel_last_line=-1, cursor_line=-1; int sel_start_offset=0, sel_stop_offset=0, cursor_offset=0; lines = (TextLineInfo) ZnListArray(text->text_info); num_lines = ZnListSize(text->text_info); Tk_GetFontMetrics(text->font, &fm); font_height = fm.ascent+fm.descent; /* * Compute the selection and the cursor geometry. */ for (i = 0, lines_ptr = lines; i < num_lines; i++, lines_ptr++) { /* * Mark the line with the cursor and compute its * position along the X axis. */ line_index = lines_ptr->start - text->text; if ((wi->text_info.focus_item == item) && wi->text_info.got_focus && text->insert_index >= line_index && text->insert_index <= line_index + lines_ptr->num_chars) { cursor_line = i; cursor_offset = Tk_TextWidth(text->font, (char *) lines_ptr->start, text->insert_index - line_index); } /* * Compute the selection first and last line as well as * the positions along the X axis. */ if ((wi->text_info.sel_item == item) && (wi->text_info.sel_last >= line_index) && (wi->text_info.sel_first <= (line_index + lines_ptr->num_chars))) { if (sel_first_line < 0) { char_index = wi->text_info.sel_first - line_index; if (char_index <= 0) { sel_first_line = i; sel_start_offset = 0; /*printf("sel_start_offset 1 : %d\n", sel_start_offset);*/ } else if (char_index <= lines_ptr->num_chars) { sel_first_line = i; sel_start_offset = Tk_TextWidth(text->font, (char *) lines_ptr->start, char_index); /*printf("sel_start_offset 2 : %d\n", sel_start_offset);*/ } } char_index = wi->text_info.sel_last - line_index; sel_last_line = i; if (char_index == lines_ptr->num_chars+1) sel_stop_offset = lines_ptr->width; else if (char_index <= lines_ptr->num_chars) sel_stop_offset = Tk_TextWidth(text->font, (char *) lines_ptr->start, char_index); } } /*printf("sel 1st : %d offset : %d, sel last : %d offset : %d\n", sel_first_line, sel_start_offset, sel_last_line, sel_stop_offset);*/ /* * Setup the gc for the selection and fill the selection. */ if ((wi->text_info.sel_item == item) && (sel_first_line >= 0)) { int x, y; values.foreground = ZnPixel(wi->text_info.sel_color); values.fill_style = FillSolid; XChangeGC(wi->dpy, wi->gc, GCFillStyle | GCForeground, &values); if (sel_first_line == sel_last_line) { x = (int)(text->pos_dev.x + lines[sel_first_line].text_origin.x + sel_start_offset); y = (int)(text->pos_dev.y + lines[sel_first_line].text_origin.y - fm.ascent); XFillRectangle(wi->dpy, wi->draw_buffer, wi->gc, x, y, sel_stop_offset - sel_start_offset, font_height); } else { x = (int)(text->pos_dev.x + lines[sel_first_line].text_origin.x + sel_start_offset); y = (int)(text->pos_dev.y + lines[sel_first_line].text_origin.y - fm.ascent); XFillRectangle(wi->dpy, wi->draw_buffer, wi->gc, x, y, text->max_width-lines[sel_first_line].text_origin.x-sel_start_offset, font_height); for (i = sel_first_line+1, lines_ptr = &lines[sel_first_line+1]; i < sel_last_line; i++, lines_ptr++) { x = (int) text->pos_dev.x; y = (int) (text->pos_dev.y + lines_ptr->text_origin.y - fm.ascent); XFillRectangle(wi->dpy, wi->draw_buffer, wi->gc, x, y, text->max_width, font_height); } x = (int)text->pos_dev.x; y = (int)(text->pos_dev.y + lines[sel_last_line].text_origin.y - fm.ascent); XFillRectangle(wi->dpy, wi->draw_buffer, wi->gc, x, y, lines[sel_last_line].text_origin.x+sel_stop_offset, font_height); } } //printf("cursor line : %d, cursor offset : %d\n", cursor_line, cursor_offset); /* * Setup the gc for the cursor and draw it. */ if ((cursor_line >= 0) && (wi->text_info.focus_item == item) && wi->text_info.cursor_on) { int xs, ys; values.fill_style = FillSolid; values.line_width = wi->text_info.insert_width; values.foreground = ZnPixel(wi->text_info.insert_color); XChangeGC(wi->dpy, wi->gc, GCForeground|GCFillStyle|GCLineWidth, &values); xs = (int)(text->pos_dev.x + lines[cursor_line].text_origin.x + cursor_offset); ys = (int)(text->pos_dev.y + lines[cursor_line].text_origin.y - fm.ascent + 1); XDrawLine(wi->dpy, wi->draw_buffer, wi->gc, xs, ys, xs, ys + font_height - 1); } /* * Setup the gc to render the text and draw it. */ values.font = ZnFontId(text->font); values.foreground = ZnPixel(text->color); gc_mask = GCFont | GCForeground; if (text->fill_pattern != ZnUnspecifiedPattern) { values.fill_style = FillStippled; values.stipple = text->fill_pattern; gc_mask |= GCFillStyle | GCStipple; } else { values.fill_style = FillSolid; gc_mask |= GCFillStyle; } if (ISSET(text->flags, UNDERLINED) || ISSET(text->flags, OVERSTRIKED)) { /* * These 3 values should be fetched from the font. * Currently I don't know how without diving into * Tk internals. */ underline_thickness = 2; underline_pos = fm.descent/2; overstrike_pos = fm.ascent*3/10; values.line_style = LineSolid; values.line_width = underline_thickness; gc_mask |= GCLineStyle | GCLineWidth; } XChangeGC(wi->dpy, wi->gc, gc_mask, &values); for (i = 0, lines_ptr = lines; i < num_lines; i++, lines_ptr++) { int tmp_x, tmp_y; tmp_x = (int)(text->pos_dev.x + lines_ptr->text_origin.x); tmp_y = (int)(text->pos_dev.y + lines_ptr->text_origin.y); XDrawString(wi->dpy, wi->draw_buffer, wi->gc, tmp_x, tmp_y, (char *) lines_ptr->start, lines_ptr->num_chars); if (ISSET(text->flags, UNDERLINED)) { int y_under = tmp_y + underline_pos; XDrawLine(wi->dpy, wi->draw_buffer, wi->gc, tmp_x, y_under, tmp_x+lines_ptr->width, y_under); } if (ISSET(text->flags, OVERSTRIKED)) { int y_over = tmp_y-overstrike_pos; XDrawLine(wi->dpy, wi->draw_buffer, wi->gc, tmp_x, y_over, tmp_x+lines_ptr->width, y_over); } } } /* ********************************************************************************** * * Render -- * ********************************************************************************** */ static void Render(Item item) { WidgetInfo *wi = item->wi; TextItem text = (TextItem) item; TextLineInfo lines, lines_ptr; int i, num_lines; int tmp_x, tmp_y; int color; color = (((text->color->red & 0xff00) << 16) | ((text->color->green & 0xff00) << 8) | (text->color->blue & 0xff00) | (text->alpha & 0xff)); lines = (TextLineInfo) ZnListArray(text->text_info); num_lines = ZnListSize(text->text_info); if (!text->fb) { text->fb = GetFontBitmap(wi->win, text->font); } for (i = 0, lines_ptr = lines; i < num_lines; i++, lines_ptr++) { tmp_x = (int)(text->pos_dev.x + lines_ptr->text_origin.x); tmp_y = (int)(text->pos_dev.y + lines_ptr->text_origin.y) - text->fb->ascent; rgb_text(wi->buf.buf, wi->buf.ox, wi->buf.oy, wi->buf.cx, wi->buf.cy, wi->buf.rowstride, lines_ptr->start, lines_ptr->num_chars, tmp_x, tmp_y, text->fb, color); } } /* ********************************************************************************** * * IsSensitive -- * ********************************************************************************** */ static ZnBool IsSensitive(Item item, int item_part) { return (ISSET(item->flags, SENSITIVE_BIT) && item->parent->class->IsSensitive(item->parent, ZN_NO_PART)); } /* ********************************************************************************** * * Pick -- * ********************************************************************************** */ static double Pick(Item item, ZnPoint *p, Item start_item, int aperture, Item *a_item, int *part) { TextItem text = (TextItem) item; double dist = 1.0e40, new_dist; int num_lines, i; TextLineInfo lines, lines_ptr; Tk_FontMetrics fm; int font_height; ZnBBox line_bbox; ZnPoint o; if (!text->text_info) { return dist; } lines = (TextLineInfo) ZnListArray(text->text_info); num_lines = ZnListSize(text->text_info); Tk_GetFontMetrics(text->font, &fm); font_height = fm.descent + fm.ascent; if (text->spacing > 0) { font_height += text->spacing; } for (i = 0, lines_ptr = lines; i < num_lines; i++, lines_ptr++) { ResetBBox(&line_bbox); o.x = text->pos_dev.x + lines_ptr->text_origin.x; o.y = text->pos_dev.y + lines_ptr->text_origin.y - fm.ascent; AddPointToBBox(&line_bbox, o.x, o.y); AddPointToBBox(&line_bbox, o.x + lines_ptr->width, o.y + font_height); new_dist = RectangleToPointDist(&line_bbox, p); dist = MIN(dist, new_dist); if (dist <= 0.0) { dist = 0.0; break; } } return dist; } /* ********************************************************************************** * * PostScript -- * ********************************************************************************** */ static void PostScript(Item item, PostScriptInfo ps_info) { } /* ********************************************************************************** * * GetAnchor -- * ********************************************************************************** */ static void GetAnchor(Item item, ZnAnchor anchor, ZnPoint *p) { TextItem text = (TextItem) item; if (text->num_chars != 0) { Origin2Anchor(&text->pos_dev, item->item_bounding_box.corner.x-item->item_bounding_box.orig.x, item->item_bounding_box.corner.y-item->item_bounding_box.orig.y, anchor, p); } else { p->x = p->y = 0.0; } } /* ********************************************************************************** * * GetClipVertices -- * Get the clipping shape. * ********************************************************************************** */ static ZnBool GetClipVertices(Item item, ZnPoly *poly) { ZnPoint *points; ZnListAssertSize(item->wi->work_pts, 2); points = (ZnPoint *) ZnListArray(item->wi->work_pts); POLY_CONTOUR1(poly, points, 2); points[0] = item->item_bounding_box.orig; points[1] = item->item_bounding_box.corner; return True; } /* ********************************************************************************** * * Coords -- * Return or edit the item origin. This doesn't take care of * the possible attachment. The change will be effective at the * end of the attachment. * ********************************************************************************** */ static int Coords(Item item, int contour, int index, int cmd, ZnPoint **pts, int *num_pts) { TextItem text = (TextItem) item; if ((cmd == COORDS_ADD) || (cmd == COORDS_ADD_LAST) || (cmd == COORDS_REMOVE)) { Tcl_AppendResult(item->wi->interp, " texts can't add or remove vertices", NULL); return ZN_ERROR; } else if ((cmd == COORDS_REPLACE) || (cmd == COORDS_REPLACE_ALL)) { if (*num_pts == 0) { Tcl_AppendResult(item->wi->interp, " coords command need 1 point on texts", NULL); return ZN_ERROR; } text->pos = (*pts)[0]; ITEM.Invalidate(item, ZN_COORDS_FLAG); } else if ((cmd == COORDS_READ) || (cmd == COORDS_READ_ALL)) { *num_pts = 1; *pts = &text->pos; } return ZN_OK; } /* ********************************************************************************** * * Index -- * Parse a text index and return its value and aa * error status (standard Tcl result). * ********************************************************************************** */ static int PointToChar(Item item, int x, int y) { TextItem text = (TextItem) item; int i, n, num_lines, dummy; TextLineInfo lines, p; Tk_FontMetrics fm; x -= text->pos_dev.x; y -= text->pos_dev.y; /* * Point above text, returns index 0. */ if (y < 0) { return 0; } /* * Find the text line under point. */ num_lines = ZnListSize(text->text_info); lines = p = (TextLineInfo) ZnListArray(text->text_info); Tk_GetFontMetrics(text->font, &fm); for (i = 0; i < num_lines; i++, p++) { if (y < p->text_origin.y + fm.descent) { if (x < p->text_origin.x) { /* * Point to the left of the current line, returns * index of first char. */ return p->start - text->text; } if (x >= (p->text_origin.x + p->width)) { /* * Point to the right of the current line, returns * index past the last char. */ return p->start + p->num_chars - text->text; } n = Tk_MeasureChars(text->font, p->start, p->num_chars, x + 2 - p->text_origin.x, TK_PARTIAL_OK, &dummy); return (p->start + n - 1) - text->text; } } /* * Point below all lines, return the index after * the last char in text. */ p--; return p->start + p->num_chars - text->text; } static int Index(Item item, Tcl_Obj *index_spec, int *index) { TextItem text = (TextItem) item; WidgetInfo *wi = item->wi; int c, length; int x, y; double tmp; char *end, *p; p = Tcl_GetString(index_spec); c = p[0]; length = strlen(p); if ((c == 'e') && (strncmp(p, "end", length) == 0)) { *index = text->num_chars; } else if ((c == 'i') && (strncmp(p, "insert", length) == 0)) { *index = text->insert_index; } else if ((c == 's') && (strncmp(p, "sel.first", length) == 0) && (length >= 5)) { if (wi->text_info.sel_item != item) { Tcl_AppendResult(wi->interp, "selection isn't in item", (char *) NULL); return TCL_ERROR; } *index = wi->text_info.sel_first; } else if ((c == 's') && (strncmp(p, "sel.last", length) == 0) && (length >= 5)) { if (wi->text_info.sel_item != item) { Tcl_AppendResult(wi->interp, "selection isn't in item", (char *) NULL); return TCL_ERROR; } *index = wi->text_info.sel_last; } else if (c == '@') { p++; tmp = strtod(p, &end); if ((end == p) || (*end != ',')) { goto badIndex; } /*x = (int) ((tmp < 0) ? tmp - 0.5 : tmp + 0.5);*/ x = tmp; p = end+1; tmp = strtod(p, &end); if ((end == p) || (*end != 0)) { goto badIndex; } /*y = (int) ((tmp < 0) ? tmp - 0.5 : tmp + 0.5);*/ y = tmp; *index = PointToChar(item, x-wi->inset, y-wi->inset); } else if (Tcl_GetIntFromObj(wi->interp, index_spec, index) == TCL_OK) { if (*index < 0){ *index = 0; } else if (*index > text->num_chars) { *index = text->num_chars; } } else { badIndex: Tcl_AppendResult(wi->interp, "bad index \"", p, "\"", (char *) NULL); return TCL_ERROR; } return TCL_OK; } /* ********************************************************************************** * * InsertChars -- * ********************************************************************************** */ static void InsertChars(Item item, int index, char *chars) { TextItem text = (TextItem) item; WidgetInfo *wi = item->wi; int length = strlen(chars); char *new; if (length == 0) { return; } if (index < 0) { index = 0; } if (index > text->num_chars) { index = text->num_chars; } new = ZnMalloc((unsigned) (text->num_chars + length + 1)); strncpy(new, text->text, (size_t) index); strcpy(new+index, chars); strcpy(new+index+length, text->text+index); ZnFree(text->text); text->text = new; text->num_chars += length; /* * Inserting characters invalidates indices such as those for the * selection and cursor. Update the indices appropriately. */ if (wi->text_info.sel_item == item) { if (wi->text_info.sel_first >= index) { wi->text_info.sel_first += length; } if (wi->text_info.sel_last >= index) { wi->text_info.sel_last += length; } if ((wi->text_info.anchor_item == item) && (wi->text_info.sel_anchor >= index)) { wi->text_info.sel_anchor += length; } } if (text->insert_index >= index) { text->insert_index += length; } ITEM.Invalidate(item, ZN_COORDS_FLAG|ZN_LAYOUT_FLAG); } /* ********************************************************************************** * * DeleteChars -- * ********************************************************************************** */ static void DeleteChars(Item item, int first, int last) { TextItem text = (TextItem) item; WidgetInfo *wi = item->wi; int count; char *new; if (first < 0) { first = 0; } if (last >= text->num_chars) { last = text->num_chars-1; } if (first > last) { return; } count = last + 1 - first; new = (char *) ZnMalloc((unsigned) (text->num_chars + 1 - count)); strncpy(new, text->text, (size_t) first); strcpy(new+first, text->text+last+1); ZnFree(text->text); text->text = new; text->num_chars -= count; /* * Update indexes for the selection and cursor to reflect the * renumbering of the remaining characters. */ if (wi->text_info.sel_item == item) { if (wi->text_info.sel_first > first) { wi->text_info.sel_first -= count; if (wi->text_info.sel_first < first) { wi->text_info.sel_first = first; } } if (wi->text_info.sel_last >= first) { wi->text_info.sel_last -= count; if (wi->text_info.sel_last < (first-1)) { wi->text_info.sel_last = (first-1); } } if (wi->text_info.sel_first > wi->text_info.sel_last) { wi->text_info.sel_item = ZN_NO_ITEM; } if ((wi->text_info.anchor_item == item) && (wi->text_info.sel_anchor > first)) { wi->text_info.sel_anchor -= count; if (wi->text_info.sel_anchor < first) { wi->text_info.sel_anchor = first; } } } if (text->insert_index > first) { text->insert_index -= count; if (text->insert_index < first) { text->insert_index = first; } } ITEM.Invalidate(item, ZN_COORDS_FLAG|ZN_LAYOUT_FLAG); } /* ********************************************************************************** * * Cursor -- * ********************************************************************************** */ static void TextCursor(Item item, int index) { TextItem text = (TextItem) item; if (index < 0) { text->insert_index = 0; } else if (index > text->num_chars) { text->insert_index = text->num_chars; } else { text->insert_index = index; } } /* ********************************************************************************** * * Selection -- * ********************************************************************************** */ static int Selection(Item item, int offset, char *chars, int max_chars) { TextItem text = (TextItem) item; WidgetInfo *wi = item->wi; int count; count = wi->text_info.sel_last - wi->text_info.sel_first - offset; if (count > max_chars) { count = max_chars; } if (count <= 0) { return 0; } strncpy(chars, text->text + wi->text_info.sel_first + offset, (size_t) count); chars[count] = '\0'; return count; } /* ********************************************************************************** * * Exported functions struct * ********************************************************************************** */ static ItemClassStruct TEXT_ITEM_CLASS = { sizeof(TextItemStruct), False, /* has_fields */ 0, /* num_parts */ True, /* has_anchors */ "text", text_attrs, Init, Clone, Destroy, Configure, Query, NULL, /* GetFieldSet */ GetAnchor, GetClipVertices, Coords, InsertChars, DeleteChars, TextCursor, Index, NULL, /* Part */ Selection, NULL, /* Contour */ ComputeCoordinates, ToArea, Draw, Render, IsSensitive, Pick, NULL, /* PickVertex */ PostScript }; ZnItemClassId ZnText = (ZnItemClassId) &TEXT_ITEM_CLASS;