/*
* Text.c -- Implementation of Text item.
*
* Authors : Patrick LECOANET
* Creation date : Sat Mar 25 13:58:39 1995
*/
/*
* Copyright (c) 1993 - 2005 CENA, Patrick Lecoanet --
*
* See the file "Copyright" for information on usage and redistribution
* of this file, and for a DISCLAIMER OF ALL WARRANTIES.
*
*/
/*
* 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 <math.h>
#include <ctype.h>
#include <X11/Xatom.h>
#include <string.h>
#include <stdlib.h>
#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 */
unsigned short num_bytes; /* Number of displayed bytes in line (NOT chars)*/
unsigned short width; /* Line width in pixels */
unsigned short origin_x; /* X pos for drawing the line */
unsigned short origin_y;
} TextLineInfoStruct, *TextLineInfo;
typedef struct _TextItemStruct {
ZnItemStruct header;
/* Public data */
ZnPoint pos; /* Position at anchor */
ZnGradient *color;
char *text;
ZnImage fill_pattern;
Tk_Font font;
unsigned short width;
short spacing;
unsigned short flags;
Tk_Anchor anchor;
Tk_Anchor connection_anchor;
Tk_Justify alignment;
/* Private data */
unsigned short num_chars;
unsigned short insert_index;
ZnList text_info;
unsigned short max_width;
unsigned short height;
ZnPoint poly[4];
#ifdef GL
ZnTexFontInfo *tfi;
#endif
} TextItemStruct, *TextItem;
static ZnAttrConfig text_attrs[] = {
{ ZN_CONFIG_ALIGNMENT, "-alignment", NULL,
Tk_Offset(TextItemStruct, alignment), 0,
ZN_COORDS_FLAG|ZN_LAYOUT_FLAG, False },
{ ZN_CONFIG_ANCHOR, "-anchor", NULL,
Tk_Offset(TextItemStruct, anchor), 0, ZN_COORDS_FLAG, False },
{ ZN_CONFIG_GRADIENT, "-color", NULL,
Tk_Offset(TextItemStruct, color), 0, ZN_DRAW_FLAG, False },
{ ZN_CONFIG_BOOL, "-composealpha", NULL,
Tk_Offset(TextItemStruct, header.flags), ZN_COMPOSE_ALPHA_BIT,
ZN_DRAW_FLAG, False },
{ ZN_CONFIG_BOOL, "-composerotation", NULL,
Tk_Offset(TextItemStruct, header.flags), ZN_COMPOSE_ROTATION_BIT,
ZN_COORDS_FLAG, False },
{ ZN_CONFIG_BOOL, "-composescale", NULL,
Tk_Offset(TextItemStruct, header.flags), ZN_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_BITMAP, "-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), ZN_SENSITIVE_BIT,
ZN_REPICK_FLAG, False },
{ ZN_CONFIG_SHORT, "-spacing", NULL,
Tk_Offset(TextItemStruct, spacing), 0,
ZN_COORDS_FLAG|ZN_LAYOUT_FLAG, False },
{ ZN_CONFIG_TAG_LIST, "-tags", NULL,
Tk_Offset(TextItemStruct, header.tags), 0, 0, False },
{ ZN_CONFIG_STRING, "-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), ZN_VISIBLE_BIT,
ZN_DRAW_FLAG|ZN_REPICK_FLAG|ZN_VIS_FLAG, False },
{ ZN_CONFIG_USHORT, "-width", NULL,
Tk_Offset(TextItemStruct, width), 0,
ZN_COORDS_FLAG|ZN_LAYOUT_FLAG, False },
{ ZN_CONFIG_END, NULL, NULL, 0, 0, 0, False }
};
/*
**********************************************************************************
*
* Init --
*
**********************************************************************************
*/
static int
Init(ZnItem item,
int *argc,
Tcl_Obj *CONST *args[])
{
ZnWInfo *wi = item->wi;
TextItem text = (TextItem) item;
/*printf("size of a text(header) = %d(%d) %d\n",
sizeof(TextItemStruct), sizeof(ZnItemStruct), sizeof(TextLineInfoStruct));*/
text->text_info = NULL;
/* Init attributes */
SET(item->flags, ZN_VISIBLE_BIT);
SET(item->flags, ZN_SENSITIVE_BIT);
SET(item->flags, ZN_COMPOSE_ALPHA_BIT);
CLEAR(item->flags, ZN_COMPOSE_ROTATION_BIT);
CLEAR(item->flags, ZN_COMPOSE_SCALE_BIT);
item->priority = 1;
text->pos.x = text->pos.y = 0.0;
text->text = NULL;
text->num_chars = 0;
text->fill_pattern = ZnUnspecifiedImage;
text->anchor = TK_ANCHOR_NW;
text->connection_anchor = TK_ANCHOR_SW;
text->color = ZnGetGradientByValue(wi->fore_color);
text->alignment = TK_JUSTIFY_LEFT;
text->font = Tk_GetFont(wi->interp, wi->win, Tk_NameOfFont(wi->font));
#ifdef GL
text->tfi = ZnGetTexFont(wi, text->font);
#endif
text->width = 0;
text->spacing = 0;
text->insert_index = 0;
CLEAR(text->flags, UNDERLINED);
CLEAR(text->flags, OVERSTRIKED);
return TCL_OK;
}
/*
**********************************************************************************
*
* Clone --
*
**********************************************************************************
*/
static void
Clone(ZnItem item)
{
TextItem text = (TextItem) item;
ZnWInfo *wi = item->wi;
char *str;
if (text->text) {
str = ZnMalloc((strlen(text->text) + 1) * sizeof(char));
strcpy(str, text->text);
text->text = str;
}
if (text->fill_pattern != ZnUnspecifiedImage) {
text->fill_pattern = ZnGetImageByValue(text->fill_pattern, NULL, NULL);
}
text->color = ZnGetGradientByValue(text->color);
text->font = Tk_GetFont(wi->interp, wi->win, Tk_NameOfFont(text->font));
#ifdef GL
text->tfi = ZnGetTexFont(wi, text->font);
#endif
/*
* We always need to invalidate, either because the model
* has not done its layout (text_info == NULL) or because
* we must unshare the pointers to the text that are in
* text_info.
*/
text->text_info = NULL;
ZnITEM.Invalidate(item, ZN_COORDS_FLAG|ZN_LAYOUT_FLAG);
}
/*
**********************************************************************************
*
* Destroy --
*
**********************************************************************************
*/
static void
Destroy(ZnItem item)
{
TextItem text = (TextItem) item;
if (text->text) {
ZnFree(text->text);
}
if (text->fill_pattern != ZnUnspecifiedImage) {
ZnFreeImage(text->fill_pattern, NULL, NULL);
text->fill_pattern = ZnUnspecifiedImage;
}
ZnFreeGradient(text->color);
Tk_FreeFont(text->font);
#ifdef GL
if (text->tfi) {
ZnFreeTexFont(text->tfi);
}
#endif
if (text->text_info) {
ZnListFree(text->text_info);
}
}
/*
**********************************************************************************
*
* Configure --
*
**********************************************************************************
*/
static int
Configure(ZnItem item,
int argc,
Tcl_Obj *CONST argv[],
int *flags)
{
TextItem text = (TextItem) item;
ZnItem old_connected = item->connected_item;
unsigned int num_chars;
#ifdef GL
Tk_Font old_font = text->font;
#endif
if (ZnConfigureAttributes(item->wi, item, item, text_attrs,
argc, argv, flags) == TCL_ERROR) {
return TCL_ERROR;
}
#ifdef GL
if (old_font != text->font) {
if (text->tfi) {
ZnFreeTexFont(text->tfi);
text->tfi = ZnGetTexFont(item->wi, text->font);
}
}
#endif
num_chars = 0;
if (text->text) {
num_chars = Tcl_NumUtfChars(text->text, (int) strlen(text->text));
}
if (text->num_chars != num_chars) {
ZnTextInfo *ti = &item->wi->text_info;
/*
* The text has changed, update the selection and
* insertion pos to keep them valid.
*/
if (item == ti->sel_item) {
if (ti->sel_last > (int) num_chars) {
ti->sel_last = num_chars;
}
if (ti->sel_first >= ti->sel_last) {
ti->sel_item = ZN_NO_ITEM;
ti->sel_field = ZN_NO_PART;
}
if ((ti->anchor_item == item) && (ti->sel_anchor > (int) num_chars)) {
ti->sel_anchor = num_chars;
}
}
if (text->insert_index > num_chars) {
text->insert_index = num_chars;
}
text->num_chars = num_chars;
}
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) ||
(ISSET(item->connected_item->class->flags, ZN_CLASS_HAS_ANCHORS) &&
(item->parent == item->connected_item->parent))) {
ZnITEM.UpdateItemDependency(item, old_connected);
}
else {
item->connected_item = old_connected;
}
}
return TCL_OK;
}
/*
**********************************************************************************
*
* Query --
*
**********************************************************************************
*/
static int
Query(ZnItem item,
int argc,
Tcl_Obj *CONST argv[])
{
if (ZnQueryAttribute(item->wi->interp, item, text_attrs, argv[0]) == TCL_ERROR) {
return TCL_ERROR;
}
return TCL_OK;
}
/*
* Compute the transformation to be used and the origin
* of the text (upper left point in item coordinates).
*/
static ZnTransfo *
ComputeTransfoAndOrigin(ZnItem item,
ZnPoint *origin)
{
TextItem text = (TextItem) item;
/*
* The connected item support anchors, this is checked by configure.
*/
if (item->connected_item != ZN_NO_ITEM) {
ZnTransfo inv;
item->connected_item->class->GetAnchor(item->connected_item,
text->connection_anchor,
origin);
/* GetAnchor return a position in device coordinates not in
* the item coordinate space. To compute the text origin
* (upper left corner), we must apply the inverse transform
* to the ref point before calling anchor2origin.
*/
ZnTransfoInvert(item->transfo, &inv);
ZnTransformPoint(&inv, origin, origin);
ZnAnchor2Origin(origin, (ZnReal) text->max_width,
(ZnReal) text->height, text->anchor, origin);
origin->x = ZnNearestInt(origin->x);
origin->y = ZnNearestInt(origin->y);
/*
* The relevant transform in case of an attachment is the item
* transform alone. This is case of local coordinate space where
* only the translation is a function of the whole transform
* stack, scale and rotation are reset.
*/
return item->transfo;
}
else {
ZnPoint p;
p.x = p.y = 0;
ZnAnchor2Origin(&p, (ZnReal) text->max_width,
(ZnReal) text->height, text->anchor, origin);
origin->x = ZnNearestInt(origin->x);
origin->y = ZnNearestInt(origin->y);
return item->wi->current_transfo;
}
}
/*
* Compute the selection and the cursor geometry.
*/
void
ComputeCursor(ZnItem item,
int *cursor_line,
unsigned int *cursor_offset)
{
TextItem text = (TextItem) item;
ZnWInfo *wi = item->wi;
ZnTextInfo *ti = &wi->text_info;
TextLineInfo lines, lines_ptr;
unsigned int i, line_index, insert_index, num_lines;
num_lines = ZnListSize(text->text_info);
if (num_lines == 0) {
*cursor_line = 0;
}
lines = ZnListArray(text->text_info);
if ((wi->focus_item == item) && ISSET(wi->flags, ZN_GOT_FOCUS) && ti->cursor_on) {
insert_index = Tcl_UtfAtIndex(text->text, (int) text->insert_index)-text->text;
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 ((insert_index >= line_index) &&
(insert_index <= line_index + lines_ptr->num_bytes)) {
*cursor_line = i;
*cursor_offset = Tk_TextWidth(text->font, (char *) lines_ptr->start,
insert_index - line_index);
}
}
}
}
static void
ComputeSelection(ZnItem item,
int *sel_first_line,
int *sel_last_line,
unsigned int *sel_start_offset,
unsigned int *sel_stop_offset)
{
TextItem text = (TextItem) item;
ZnWInfo *wi = item->wi;
ZnTextInfo *ti = &wi->text_info;
TextLineInfo lines_ptr, lines;
int i, num_lines, byte_index;
unsigned int line_index;
unsigned int sel_first, sel_last;
num_lines = ZnListSize(text->text_info);
if ((ti->sel_item != item) || !num_lines) {
return;
}
lines = ZnListArray(text->text_info);
sel_first = Tcl_UtfAtIndex(text->text, ti->sel_first)-text->text;
sel_last = Tcl_UtfAtIndex(text->text, ti->sel_last+1)-text->text;
for (i = 0, lines_ptr = lines; i < num_lines; i++, lines_ptr++) {
/*
* Compute the selection first and last line as well as
* the positions along the X axis.
*/
line_index = lines_ptr->start - text->text;
if ((sel_last >= line_index) &&
(sel_first <= (line_index + lines_ptr->num_bytes))) {
if (*sel_first_line < 0) {
byte_index = sel_first - line_index;
if (byte_index <= 0) {
*sel_first_line = i;
*sel_start_offset = 0;
/*printf("sel_start_offset 1 : %d\n", *sel_start_offset);*/
}
else if (byte_index <= lines_ptr->num_bytes) {
*sel_first_line = i;
*sel_start_offset = Tk_TextWidth(text->font, (char *) lines_ptr->start,
byte_index);
/*printf("sel_start_offset 2 : %d\n", *sel_start_offset);*/
}
}
byte_index = ti->sel_last+1 - line_index;
*sel_last_line = i;
if (byte_index == lines_ptr->num_bytes+1)
*sel_stop_offset = lines_ptr->width;
else if (byte_index <= lines_ptr->num_bytes)
*sel_stop_offset = Tk_TextWidth(text->font, (char *) lines_ptr->start,
byte_index);
}
}
}
/*
**********************************************************************************
*
* ComputeCoordinates --
*
**********************************************************************************
*/
static void
ComputeCoordinates(ZnItem item,
ZnBool force)
{
ZnWInfo *wi = item->wi;
TextItem text = (TextItem) item;
TextLineInfo infos;
Tk_FontMetrics fm;
ZnTransfo *transfo;
int i, num_lines, cur_dy, font_height;
ZnResetBBox(&item->item_bounding_box);
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, prev_num_lines;
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;
}
if ((scan = text->text) != NULL) {
TextLineInfoStruct info;
while (*scan) {
char *special;
int num, w;
/*
* Limit the excursion of Tk_MeasureChars to the end
* of the line. Do not include \n in the measure done.
*/
num = strcspn(scan, "\r\n");
special = scan + num;
info.num_bytes = Tk_MeasureChars(text->font, scan, num, wrap,
TK_WHOLE_WORDS|TK_AT_LEAST_ONE, &w);
info.width = w;
info.start = scan;
text->max_width = MAX(info.width, text->max_width);
scan += info.num_bytes;
/*
* Skip the newline line character.
*/
if ((*scan == '\r') || (*scan == '\n')) {
scan++;
}
else {
/*
* Skip white spaces occuring after an
* automatic line break.
*/
while (*scan == ' ') {
scan++;
}
}
ZnListAdd(text->text_info, &info, ZnListTail);
/*printf("adding a text info : %s, num_bytes : %d, width : %d\n",
info.start, info.num_bytes, info.width);*/
}
if (*text->text && ((scan[-1] == '\r') || (scan[-1] == '\n'))) {
/* Build a text 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.
*/
info.num_bytes = 0;
info.width = 0;
info.start = scan;
ZnListAdd(text->text_info, &info, ZnListTail);
}
}
/*
* 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 TK_JUSTIFY_LEFT:
infos[i].origin_x = 0;
break;
case TK_JUSTIFY_CENTER:
infos[i].origin_x = (text->max_width + 1 - infos[i].width)/2;
break;
case TK_JUSTIFY_RIGHT:
infos[i].origin_x = text->max_width + 1 - infos[i].width;
break;
}
infos[i].origin_y = cur_dy;
cur_dy += font_height + text->spacing;
/*printf("fixing line %d x : %d, y : %d\n", i, infos[i].origin_x,
infos[i].origin_y);*/
}
} /* ISSET(item->inv_flags, INV_TEXT_LAYOUT) */
text->height = font_height;
if (text->text_info && text->max_width) {
unsigned int h, cursor_offset;
int cursor_line;
ZnPoint origin, box[4];
num_lines = ZnListSize(text->text_info);
infos = ZnListArray(text->text_info);
h = num_lines * font_height + (num_lines-1) * text->spacing;
text->height = MAX(text->height, h);
transfo = ComputeTransfoAndOrigin(item, &origin);
text->poly[0].x = origin.x;
text->poly[0].y = origin.y;
text->poly[3].x = text->poly[0].x + text->max_width;
text->poly[3].y = text->poly[0].y + text->height;
text->poly[1].x = text->poly[0].x;
text->poly[1].y = text->poly[3].y;
text->poly[2].x = text->poly[3].x;
text->poly[2].y = text->poly[0].y;
ZnTransformPoints(transfo, text->poly, text->poly, 4);
/*
* Add to the bounding box.
*/
ZnAddPointsToBBox(&item->item_bounding_box, text->poly, 4);
/*
* Add the cursor shape to the bbox.
*/
cursor_line = -1;
ComputeCursor(item, &cursor_line, &cursor_offset);
if (cursor_line >= 0) {
if (num_lines) {
box[0].x = origin.x + infos[cursor_line].origin_x + cursor_offset -
wi->text_info.insert_width/2;
box[0].y = origin.y + infos[cursor_line].origin_y - fm.ascent + 1;
}
else {
box[0].x = origin.x;
box[0].y = origin.y;
}
box[2].x = box[0].x + wi->text_info.insert_width;
box[2].y = box[0].y + font_height - 1;
box[1].x = box[2].x;
box[1].y = box[0].y;
box[3].x = box[0].x;
box[3].y = box[2].y;
ZnTransformPoints(transfo, box, box, 4);
ZnAddPointsToBBox(&item->item_bounding_box, box, 4);
}
}
/*printf("bbox origin: %g %g corner %g %g\n",
item->item_bounding_box.orig.x, item->item_bounding_box.orig.y,
item->item_bounding_box.corner.x, item->item_bounding_box.corner.y);*/
/*
* Update connected items.
*/
SET(item->flags, ZN_UPDATE_DEPENDENT_BIT);
}
/*
**********************************************************************************
*
* ToArea --
* Tell if the object is entirely outside (-1),
* entirely inside (1) or in between (0).
*
**********************************************************************************
*/
static int
ToArea(ZnItem item,
ZnToArea ta)
{
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, *area = ta->area;
ZnPoint box[4], p, origin;
ZnTransfo inv, *transfo;
if (!text->text_info || !text->text) {
return -1;
}
transfo = ComputeTransfoAndOrigin(item, &origin);
box[0] = area->orig;
box[2] = area->corner;
box[1].x = box[2].x;
box[1].y = box[0].y;
box[3].x = box[0].x;
box[3].y = box[2].y;
ZnTransfoInvert(transfo, &inv);
ZnTransformPoints(&inv, box, box, 4);
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++) {
ZnResetBBox(&line_bbox);
p.x = origin.x + lines_ptr->origin_x;
p.y = origin.y + lines_ptr->origin_y - fm.ascent;
ZnAddPointToBBox(&line_bbox, p.x, p.y);
ZnAddPointToBBox(&line_bbox, p.x + lines_ptr->width, p.y + font_height);
if (!first_done) {
first_done = True;
inside = ZnPolygonInBBox(box, 4, &line_bbox, NULL);
if (inside == 0) {
return 0;
}
}
else {
if (ZnPolygonInBBox(box, 4, &line_bbox, NULL) == 0) {
return 0;
}
}
}
return inside;
}
/*
**********************************************************************************
*
* Draw --
*
**********************************************************************************
*/
static void
Draw(ZnItem item)
{
ZnWInfo *wi = item->wi;
TextItem text = (TextItem) item;
XGCValues values;
ZnPoint pos, box[4], origin;
ZnTransfo *transfo;
Drawable drw;
GC gc;
XImage *src_im, *dest_im=NULL;
unsigned int dest_im_width=0, dest_im_height=0;
unsigned int gc_mask = 0;
Tk_FontMetrics fm;
unsigned int font_height;
int num_lines, i;
TextLineInfo lines, lines_ptr;
ZnTextInfo *ti = &wi->text_info;
unsigned int underline_thickness, underline_pos=0, overstrike_pos=0;
int sel_first_line=-1, sel_last_line=-1, cursor_line=-1;
unsigned int sel_start_offset=0, sel_stop_offset=0, cursor_offset=0;
if (!text->text_info/* || !text->text*/) {
return;
}
lines = (TextLineInfo) ZnListArray(text->text_info);
num_lines = ZnListSize(text->text_info);
Tk_GetFontMetrics(text->font, &fm);
font_height = fm.ascent+fm.descent;
transfo = ComputeTransfoAndOrigin(item, &origin);
/*
* Compute the selection and the cursor geometry.
*/
ComputeCursor(item, &cursor_line, &cursor_offset);
ComputeSelection(item, &sel_first_line, &sel_last_line,
&sel_start_offset, &sel_stop_offset);
ZnTransformPoint(transfo, &origin, &pos);
/*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 ((ti->sel_item == item) && (sel_first_line >= 0)) {
XPoint xp[4];
values.foreground = ZnGetGradientPixel(ti->sel_color, 0.0);
values.fill_style = FillSolid;
XChangeGC(wi->dpy, wi->gc, GCFillStyle | GCForeground, &values);
if (sel_first_line == sel_last_line) {
box[0].x = origin.x +
lines[sel_first_line].origin_x + sel_start_offset;
box[0].y = origin.y +
lines[sel_first_line].origin_y - fm.ascent;
box[2].x = box[0].x + sel_stop_offset - sel_start_offset;
box[2].y = box[0].y + font_height;
box[1].x = box[2].x;
box[1].y = box[0].y;
box[3].x = box[0].x;
box[3].y = box[2].y;
ZnTransformPoints(transfo, box, box, 4);
for (i = 0; i < 4; i++) {
xp[i].x = (short) box[i].x;
xp[i].y = (short) box[i].y;
}
XFillPolygon(wi->dpy, wi->draw_buffer, wi->gc, xp, 4, Convex, CoordModeOrigin);
}
else {
box[0].x = origin.x +
lines[sel_first_line].origin_x + sel_start_offset;
box[0].y = origin.y +
lines[sel_first_line].origin_y - fm.ascent;
box[2].x = box[0].x +
text->max_width-lines[sel_first_line].origin_x-sel_start_offset;
box[2].y = box[0].y + font_height;
box[1].x = box[2].x;
box[1].y = box[0].y;
box[3].x = box[0].x;
box[3].y = box[2].y;
ZnTransformPoints(transfo, box, box, 4);
for (i = 0; i < 4; i++) {
xp[i].x = (short) box[i].x;
xp[i].y = (short) box[i].y;
}
XFillPolygon(wi->dpy, wi->draw_buffer, wi->gc, xp, 4, Convex, CoordModeOrigin);
for (i = sel_first_line+1, lines_ptr = &lines[sel_first_line+1];
i < sel_last_line; i++, lines_ptr++) {
box[0].x = origin.x;
box[0].y = origin.y + lines_ptr->origin_y - fm.ascent;
box[2].x = box[0].x + text->max_width;
box[2].y = box[0].y + font_height;
box[1].x = box[2].x;
box[1].y = box[0].y;
box[3].x = box[0].x;
box[3].y = box[2].y;
ZnTransformPoints(transfo, box, box, 4);
for (i = 0; i < 4; i++) {
xp[i].x = (short) box[i].x;
xp[i].y = (short) box[i].y;
}
XFillPolygon(wi->dpy, wi->draw_buffer, wi->gc, xp, 4, Convex, CoordModeOrigin);
}
box[0].x = origin.x;
box[0].y = origin.y + lines[sel_last_line].origin_y - fm.ascent;
box[2].x = box[0].x + lines[sel_last_line].origin_x + sel_stop_offset;
box[2].y = box[0].y + font_height;
box[1].x = box[2].x;
box[1].y = box[0].y;
box[3].x = box[0].x;
box[3].y = box[2].y;
ZnTransformPoints(transfo, box, box, 4);
for (i = 0; i < 4; i++) {
xp[i].x = (short) box[i].x;
xp[i].y = (short) box[i].y;
}
XFillPolygon(wi->dpy, wi->draw_buffer, wi->gc, xp, 4, Convex, CoordModeOrigin);
}
}
/*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->focus_item == item) && ti->cursor_on) {
values.fill_style = FillSolid;
values.line_width = ti->insert_width;
values.foreground = ZnGetGradientPixel(ti->insert_color, 0.0);
XChangeGC(wi->dpy, wi->gc, GCForeground|GCFillStyle|GCLineWidth, &values);
box[0].x = origin.x + lines[cursor_line].origin_x + cursor_offset;
box[0].y = origin.y + lines[cursor_line].origin_y - fm.ascent + 1;
box[1].x = box[0].x;
box[1].y = box[0].y + font_height - 1;
ZnTransformPoints(transfo, box, box, 2);
XDrawLine(wi->dpy, wi->draw_buffer, wi->gc,
(int) box[0].x, (int) box[0].y, (int) box[1].x, (int) box[1].y);
}
/*
* If the current transform is a pure translation, it is
* possible to optimize by directly drawing to the X back
* buffer. Else, we draw in a temporary buffer, get
* its content as an image, transform the image into another
* one and use this last image as a mask to draw in the X
* back buffer.
*/
if (ZnTransfoIsTranslation(transfo)) {
drw = wi->draw_buffer;
gc = wi->gc;
values.foreground = ZnGetGradientPixel(text->color, 0.0);
}
else {
dest_im_width = (unsigned int) (item->item_bounding_box.corner.x -
item->item_bounding_box.orig.x);
dest_im_height = (unsigned int) (item->item_bounding_box.corner.y -
item->item_bounding_box.orig.y);
drw = Tk_GetPixmap(wi->dpy, wi->draw_buffer,
MAX(dest_im_width, text->max_width),
MAX(dest_im_height, text->height), 1);
gc = XCreateGC(wi->dpy, drw, 0, NULL);
XSetForeground(wi->dpy, gc, 0);
XFillRectangle(wi->dpy, drw, gc, 0, 0,
MAX(dest_im_width, text->max_width),
MAX(dest_im_height, text->height));
dest_im = XCreateImage(wi->dpy, Tk_Visual(wi->win), 1,
XYPixmap, 0, NULL, dest_im_width, dest_im_height,
8, 0);
dest_im->data = ZnMalloc(dest_im->bytes_per_line * dest_im->height);
memset(dest_im->data, 0, dest_im->bytes_per_line * dest_im->height);
values.foreground = 1;
pos.x = 0;
pos.y = 0;
}
/*
* Setup the gc to render the text and draw it.
*/
values.font = Tk_FontId(text->font);
gc_mask = GCFont | GCForeground;
if (text->fill_pattern != ZnUnspecifiedImage) {
values.fill_style = FillStippled;
values.stipple = ZnImagePixmap(text->fill_pattern, wi->win);
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, gc, gc_mask, &values);
for (i = 0, lines_ptr = lines; i < num_lines; i++, lines_ptr++) {
int tmp_x, tmp_y;
tmp_x = (int)(pos.x + lines_ptr->origin_x);
tmp_y = (int)(pos.y + lines_ptr->origin_y);
Tk_DrawChars(wi->dpy, drw, gc,
text->font, (char *) lines_ptr->start,
(int) lines_ptr->num_bytes, tmp_x, tmp_y);
if (ISSET(text->flags, UNDERLINED)) {
int y_under = tmp_y + underline_pos;
XDrawLine(wi->dpy, drw, gc,
tmp_x, y_under, tmp_x + (int) lines_ptr->width, y_under);
}
if (ISSET(text->flags, OVERSTRIKED)) {
int y_over = tmp_y-overstrike_pos;
XDrawLine(wi->dpy, drw, gc,
tmp_x, y_over, tmp_x + (int) lines_ptr->width, y_over);
}
}
if (dest_im != NULL) {
src_im = XGetImage(wi->dpy, drw, 0, 0, text->max_width, text->height,
1, XYPixmap);
box[0].x = origin.x;
box[0].y = origin.y;
box[3].x = box[0].x + text->max_width;
box[3].y = box[0].y + text->height;
box[1].x = box[0].x;
box[1].y = box[3].y;
box[2].x = box[3].x;
box[2].y = box[0].y;
ZnTransformPoints(transfo, box, box, 4);
for (i = 0; i < 4; i++) {
box[i].x -= item->item_bounding_box.orig.x;
box[i].y -= item->item_bounding_box.orig.y;
box[i].x = ZnNearestInt(box[i].x);
box[i].y = ZnNearestInt(box[i].y);
}
ZnMapImage(src_im, dest_im, box);
TkPutImage(NULL, 0,wi->dpy, drw, gc, dest_im,
0, 0, 0, 0, dest_im_width, dest_im_height);
values.foreground = ZnGetGradientPixel(text->color, 0.0);
values.stipple = drw;
values.ts_x_origin = (int) item->item_bounding_box.orig.x;
values.ts_y_origin = (int) item->item_bounding_box.orig.y;
values.fill_style = FillStippled;
XChangeGC(wi->dpy, wi->gc,
GCFillStyle|GCStipple|GCTileStipXOrigin|GCTileStipYOrigin|GCForeground,
&values);
XFillRectangle(wi->dpy, wi->draw_buffer, wi->gc,
(int) item->item_bounding_box.orig.x,
(int) item->item_bounding_box.orig.y,
dest_im_width, dest_im_height);
XFreeGC(wi->dpy, gc);
Tk_FreePixmap(wi->dpy, drw);
XDestroyImage(src_im);
XDestroyImage(dest_im);
}
}
/*
**********************************************************************************
*
* Render --
*
**********************************************************************************
*/
#ifdef GL
static void
Render(ZnItem item)
{
ZnWInfo *wi = item->wi;
TextItem text = (TextItem) item;
TextLineInfo lines, lines_ptr;
ZnTextInfo *ti = &wi->text_info;
ZnPoint o, c, origin;
ZnTransfo *transfo;
GLdouble m[16];
int i, num_lines;
XColor *color;
unsigned short alpha;
Tk_FontMetrics fm;
int font_height;
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;
if (!text->text_info) {
return;
}
#ifdef GL_LIST
if (!item->gl_list) {
item->gl_list = glGenLists(1);
glNewList(item->gl_list, GL_COMPILE);
#endif
lines = (TextLineInfo) ZnListArray(text->text_info);
num_lines = ZnListSize(text->text_info);
Tk_GetFontMetrics(text->font, &fm);
font_height = fm.ascent+fm.descent;
/*
* 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;
transfo = ComputeTransfoAndOrigin(item, &origin);
/*
* Compute the selection and the cursor geometry.
*/
ComputeCursor(item, &cursor_line, &cursor_offset);
ComputeSelection(item, &sel_first_line, &sel_last_line,
&sel_start_offset, &sel_stop_offset);
ZnGLMakeCurrent(wi->dpy, wi);
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
glPushMatrix();
memset(m, 0, sizeof(m));
m[0] = m[5] = m[15] = 1.0;
if (transfo) {
m[0] = transfo->_[0][0]; m[1] = transfo->_[0][1];
m[4] = transfo->_[1][0]; m[5] = transfo->_[1][1];
m[12] = ZnNearestInt(transfo->_[2][0]);
m[13] = ZnNearestInt(transfo->_[2][1]);
}
glLoadMatrixd(m);
glTranslated(origin.x, origin.y, 0.0);
glPushMatrix();
/*
* Render the selection.
*/
if ((ti->sel_item == item) && (sel_first_line >= 0)) {
color = ZnGetGradientColor(ti->sel_color, 0.0, &alpha);
alpha = ZnComposeAlpha(alpha, wi->alpha);
glColor4us(color->red, color->green, color->blue, alpha);
o.x = lines[sel_first_line].origin_x + sel_start_offset;
o.y = lines[sel_first_line].origin_y - fm.ascent;
glBegin(GL_QUADS);
if (sel_first_line == sel_last_line) {
c.x = o.x + sel_stop_offset - sel_start_offset;
c.y = o.y + font_height;
glVertex2d(o.x, o.y);
glVertex2d(o.x, c.y);
glVertex2d(c.x, c.y);
glVertex2d(c.x, o.y);
}
else {
c.x = o.x + (text->max_width -
lines[sel_first_line].origin_x - sel_start_offset);
c.y = o.y + font_height;
glVertex2d(o.x, o.y);
glVertex2d(o.x, c.y);
glVertex2d(c.x, c.y);
glVertex2d(c.x, o.y);
for (i = sel_first_line+1, lines_ptr = &lines[sel_first_line+1];
i < sel_last_line; i++, lines_ptr++) {
o.x = 0;
o.y = lines_ptr->origin_y - fm.ascent;
c.x = o.x + text->max_width;
c.y = o.y + font_height;
glVertex2d(o.x, o.y);
glVertex2d(o.x, c.y);
glVertex2d(c.x, c.y);
glVertex2d(c.x, o.y);
}
o.x = 0;
o.y = lines[sel_last_line].origin_y - fm.ascent;
c.x = o.x + lines[sel_last_line].origin_x + sel_stop_offset;
c.y = o.y + font_height;
glVertex2d(o.x, o.y);
glVertex2d(o.x, c.y);
glVertex2d(c.x, c.y);
glVertex2d(c.x, o.y);
}
glEnd();
}
/*
* Render the cursor.
*/
if ((cursor_line >= 0) &&
(wi->focus_item == item) && ti->cursor_on) {
color = ZnGetGradientColor(ti->insert_color, 0.0, &alpha);
alpha = ZnComposeAlpha(alpha, wi->alpha);
glColor4us(color->red, color->green, color->blue, alpha);
glLineWidth((GLfloat) ti->insert_width);
o.x = lines[cursor_line].origin_x + cursor_offset;
o.y = lines[cursor_line].origin_y - fm.ascent + 1;
c.x = o.x;
c.y = o.y + font_height - 1;
glBegin(GL_LINES);
glVertex2d(o.x, o.y);
glVertex2d(c.x, c.y);
glEnd();
}
/*
* Render the text.
*/
glEnable(GL_TEXTURE_2D);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
glBindTexture(GL_TEXTURE_2D, ZnTexFontTex(text->tfi));
color = ZnGetGradientColor(text->color, 0.0, &alpha);
alpha = ZnComposeAlpha(alpha, wi->alpha);
glColor4us(color->red, color->green, color->blue, alpha);
for (i = 0, lines_ptr = lines; i < num_lines; i++, lines_ptr++) {
glTranslated(lines_ptr->origin_x, lines_ptr->origin_y, 0.0);
if (ISSET(text->flags, UNDERLINED) || ISSET(text->flags, OVERSTRIKED)) {
glLineWidth((GLfloat) underline_thickness);
glDisable(GL_TEXTURE_2D);
if (ISSET(text->flags, UNDERLINED)) {
glBegin(GL_LINES);
glVertex2d(0, underline_pos);
glVertex2d(lines_ptr->width, underline_pos);
glEnd();
}
if (ISSET(text->flags, OVERSTRIKED)) {
glBegin(GL_LINES);
glVertex2d(0, -overstrike_pos);
glVertex2d(lines_ptr->width, -overstrike_pos);
glEnd();
}
glEnable(GL_TEXTURE_2D);
}
ZnRenderString(text->tfi, lines_ptr->start, lines_ptr->num_bytes);
glPopMatrix();
glPushMatrix();
}
glPopMatrix();
glPopMatrix();
glDisable(GL_TEXTURE_2D);
#ifdef GL_LIST
glEndList();
}
glCallList(item->gl_list);
#endif
}
#else
static void
Render(ZnItem item)
{
}
#endif
/*
**********************************************************************************
*
* IsSensitive --
*
**********************************************************************************
*/
static ZnBool
IsSensitive(ZnItem item,
int item_part)
{
return (ISSET(item->flags, ZN_SENSITIVE_BIT) &&
item->parent->class->IsSensitive(item->parent, ZN_NO_PART));
}
/*
**********************************************************************************
*
* Pick --
*
**********************************************************************************
*/
static double
Pick(ZnItem item,
ZnPick ps)
{
TextItem text = (TextItem) item;
double dist = 1.0e40, new_dist;
int num_lines, i;
TextLineInfo lines, lines_ptr;
Tk_FontMetrics fm;
int font_height;
ZnPoint box[4], origin, *p = ps->point;
ZnTransfo *transfo;
if (!text->text_info || !text->text) {
return dist;
}
transfo = ComputeTransfoAndOrigin(item, &origin);
lines = 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++) {
box[0].x = origin.x + lines_ptr->origin_x;
box[0].y = origin.y + lines_ptr->origin_y - fm.ascent;
box[2].x = box[0].x + lines_ptr->width;
box[2].y = box[0].y + font_height;
box[1].x = box[2].x;
box[1].y = box[0].y;
box[3].x = box[0].x;
box[3].y = box[2].y;
ZnTransformPoints(transfo, box, box, 4);
new_dist = ZnPolygonToPointDist(box, 4, p);
dist = MIN(dist, new_dist);
if (dist <= 0.0) {
dist = 0.0;
break;
}
}
return dist;
}
/*
**********************************************************************************
*
* PostScript --
*
**********************************************************************************
*/
static int
PostScript(ZnItem item,
ZnBool prepass,
ZnBBox *area)
{
ZnWInfo *wi = item->wi;
TextItem text = (TextItem) item;
Tk_FontMetrics fm;
TextLineInfo lines, lines_ptr;
ZnPoint origin;
ZnReal alignment;
int i, num_lines;
char path[150];
lines = (TextLineInfo) ZnListArray(text->text_info);
num_lines = ZnListSize(text->text_info);
if (Tk_PostscriptFont(wi->interp, wi->ps_info, text->font) != TCL_OK) {
return TCL_ERROR;
}
if (Tk_PostscriptColor(wi->interp, wi->ps_info,
ZnGetGradientColor(text->color, 0.0, NULL)) != TCL_OK) {
return TCL_ERROR;
}
if (text->fill_pattern != ZnUnspecifiedImage) {
Tcl_AppendResult(wi->interp, "/StippleText {\n ", NULL);
Tk_PostscriptStipple(wi->interp, wi->win, wi->ps_info,
ZnImagePixmap(text->fill_pattern, wi->win));
Tcl_AppendResult(wi->interp, "} bind def\n", NULL);
}
ComputeTransfoAndOrigin(item, &origin);
sprintf(path, "/InitialTransform load setmatrix\n"
"[%.15g %.15g %.15g %.15g %.15g %.15g] concat\n"
"1 -1 scale\n",
wi->current_transfo->_[0][0], wi->current_transfo->_[0][1],
wi->current_transfo->_[1][0], wi->current_transfo->_[1][1],
wi->current_transfo->_[2][0], wi->current_transfo->_[2][1]);
Tcl_AppendResult(wi->interp, path, NULL);
sprintf(path, "%.15g %.15g [\n", origin.x, origin.y);
Tcl_AppendResult(wi->interp, path, NULL);
/*
* Emit code to draw the lines.
*/
for (i = 0, lines_ptr = lines; i < num_lines; i++, lines_ptr++) {
ZnPostscriptString(wi->interp, lines_ptr->start, lines_ptr->num_bytes);
}
switch (text->alignment) {
default:
case TK_JUSTIFY_LEFT:
alignment = 0;
break;
case TK_JUSTIFY_CENTER:
alignment = 0.5;
break;
case TK_JUSTIFY_RIGHT:
alignment = 1;
break;
}
Tk_GetFontMetrics(text->font, &fm);
/* DrawText should not mess with anchors, they are already accounted for */
sprintf(path, "] %d %g %g %g %s DrawText\n", fm.linespace, 0.0, 0.0,
alignment, (text->fill_pattern == ZnUnspecifiedImage) ? "false" : "true");
Tcl_AppendResult(wi->interp, path, NULL);
return TCL_OK;
}
/*
**********************************************************************************
*
* GetAnchor --
*
**********************************************************************************
*/
static void
GetAnchor(ZnItem item,
Tk_Anchor anchor,
ZnPoint *p)
{
TextItem text = (TextItem) item;
if (text->num_chars != 0) {
ZnRectOrigin2Anchor(text->poly, anchor, p);
}
else {
*p = *text->poly;
}
}
/*
**********************************************************************************
*
* GetClipVertices --
* Get the clipping shape.
* Never ever call ZnTriFree on the tristrip returned by GetClipVertices.
*
**********************************************************************************
*/
static ZnBool
GetClipVertices(ZnItem item,
ZnTriStrip *tristrip)
{
TextItem text = (TextItem) item;
ZnTriStrip1(tristrip, text->poly, 4, False);
return False;
}
/*
**********************************************************************************
*
* 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(ZnItem item,
int contour,
int index,
int cmd,
ZnPoint **pts,
char **controls,
unsigned int *num_pts)
{
TextItem text = (TextItem) item;
if ((cmd == ZN_COORDS_ADD) || (cmd == ZN_COORDS_ADD_LAST) || (cmd == ZN_COORDS_REMOVE)) {
Tcl_AppendResult(item->wi->interp,
" texts can't add or remove vertices", NULL);
return TCL_ERROR;
}
else if ((cmd == ZN_COORDS_REPLACE) || (cmd == ZN_COORDS_REPLACE_ALL)) {
if (*num_pts == 0) {
Tcl_AppendResult(item->wi->interp,
" coords command need 1 point on texts", NULL);
return TCL_ERROR;
}
text->pos = (*pts)[0];
ZnITEM.Invalidate(item, ZN_COORDS_FLAG);
}
else if ((cmd == ZN_COORDS_READ) || (cmd == ZN_COORDS_READ_ALL)) {
*num_pts = 1;
*pts = &text->pos;
}
return TCL_OK;
}
/*
**********************************************************************************
*
* Index --
* Parse a text index and return its value and a
* error status (standard Tcl result).
*
**********************************************************************************
*/
static int
PointToChar(TextItem text,
int x,
int y)
{
int i, n, num_lines, dummy, byte_index;
ZnPoint p;
TextLineInfo ti;
Tk_FontMetrics fm;
ZnReal a, b;
byte_index = 0;
if (!text->text_info) {
return 0;
}
p.x = x;
p.y = y;
a = ZnLineToPointDist(&text->poly[0], &text->poly[2], &p, NULL);
b = ZnLineToPointDist(&text->poly[0], &text->poly[1], &p, NULL);
p.x = (text->max_width * b /
hypot(text->poly[2].x - text->poly[0].x, text->poly[2].y - text->poly[0].y));
p.y = (text->height * a /
hypot(text->poly[1].x - text->poly[0].x, text->poly[1].y - text->poly[0].y));
p.x = ZnNearestInt(p.x);
p.y = ZnNearestInt(p.y);
/*
* Point above text, returns index 0.
*/
if (p.y < 0) {
return 0;
}
/*
* Find the text line under point.
*/
num_lines = ZnListSize(text->text_info);
ti = ZnListArray(text->text_info);
Tk_GetFontMetrics(text->font, &fm);
for (i = 0; i < num_lines; i++, ti++) {
if (p.y < ti->origin_y + fm.descent) {
if (p.x < ti->origin_x) {
/*
* Point to the left of the current line, returns
* index of first char.
*/
byte_index = ti->start - text->text;
break;
}
if (p.x >= (ti->origin_x + ti->width)) {
/*
* Point to the right of the current line, returns
* index past the last char.
*/
byte_index = ti->start + ti->num_bytes - text->text;
break;
}
n = Tk_MeasureChars(text->font, ti->start, (int) ti->num_bytes,
(int) (p.x + 2 - ti->origin_x), TK_PARTIAL_OK,
&dummy);
#ifdef PTK_800
byte_index = (ti->start + n - 1) - text->text;
#else
byte_index = Tcl_UtfPrev(ti->start + n, ti->start) - text->text;
#endif
break;
}
}
if (i == num_lines) {
/*
* Point below all lines, return the index after
* the last char in text.
*/
ti--;
byte_index = ti->start + ti->num_bytes - text->text;
}
return Tcl_NumUtfChars(text->text, byte_index);
}
/*
* Return a new index from a current index and a
* move command.
*
* 0 end of index line
* 1 beginning of index line
* 2 next word or end of word from index
* 3 previous word or beginning of word from index
* 4 previous line from index line
* 5 next line from index line
*
*/
static int
MoveFromIndex(TextItem text,
unsigned int char_index,
int move)
{
unsigned int num_lines, byte_index, num_bytes=0;
unsigned int line_index, line_start=0;
TextLineInfo lines, p;
char *strp;
if (!text->text_info || !text->text) {
return char_index;
}
byte_index = Tcl_UtfAtIndex(text->text, (int) char_index)-text->text;
num_lines = ZnListSize(text->text_info);
lines = p = ZnListArray(text->text_info);
for (line_index = 0; line_index < num_lines; line_index++, p++) {
line_start = p->start - text->text;
num_bytes = p->num_bytes;
if (line_start + num_bytes >= byte_index) {
break;
}
}
if (line_index == num_lines) {
line_index--;
p--;
}
switch (move) {
case 0:
byte_index = line_start + num_bytes;
goto convert_it;
case 1:
byte_index = line_start;
goto convert_it;
case 2:
strp = &text->text[byte_index];
while ((strp[1] == ' ') || (strp[1] == '\n')) {
strp++;
}
while ((strp[1] != ' ') && (strp[1] != '\n') && strp[1]) {
strp++;
}
byte_index = strp + 1 - text->text;
goto convert_it;
case 3:
strp = &text->text[byte_index];
while ((strp != text->text) && ((strp[-1] == ' ') || (strp[-1] == '\n'))) {
strp--;
}
while ((strp != text->text) && (strp[-1] != ' ') && (strp[-1] != '\n')) {
strp--;
}
byte_index = strp - text->text;
goto convert_it;
case 4:
if (line_index > 0) {
byte_index -= line_start;
p = &lines[line_index-1];
byte_index = MIN(byte_index, p->num_bytes);
line_start = p->start - text->text;
byte_index += line_start;
}
goto convert_it;
case 5:
if (line_index < num_lines-1) {
byte_index -= line_start;
p = &lines[line_index+1];
byte_index = MIN(byte_index, p->num_bytes);
line_start = p->start - text->text;
byte_index += line_start;
}
convert_it:
char_index = Tcl_NumUtfChars(text->text, (int) byte_index);
default:
return char_index;
}
}
static int
Index(ZnItem item,
int field,
Tcl_Obj *index_spec,
int *index)
{
TextItem text = (TextItem) item;
ZnWInfo *wi = item->wi;
ZnTextInfo *ti = &wi->text_info;
unsigned int length;
int c, x, y;
double tmp;
char *end, *p;
p = Tcl_GetString(index_spec);
c = p[0];
length = strlen(p);
if ((c == 'e') && (length > 1) && (strncmp(p, "end", length) == 0)) {
*index = text->num_chars;
}
else if ((c == 'e') && (length > 1) && (strncmp(p, "eol", length) == 0)) {
*index = MoveFromIndex(text, text->insert_index, 0);
}
else if ((c == 'b') && (length > 1) && (strncmp(p, "bol", length) == 0)) {
*index = MoveFromIndex(text, text->insert_index, 1);
}
else if ((c == 'e') && (length > 1) && (strncmp(p, "eow", length) == 0)) {
*index = MoveFromIndex(text, text->insert_index, 2);
}
else if ((c == 'b') && (length > 1) && (strncmp(p, "bow", length) == 0)) {
*index = MoveFromIndex(text, text->insert_index, 3);
}
else if ((c == 'u') && (strncmp(p, "up", length) == 0)) {
*index = MoveFromIndex(text, text->insert_index, 4);
}
else if ((c == 'd') && (strncmp(p, "down", length) == 0)) {
*index = MoveFromIndex(text, text->insert_index, 5);
}
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 (ti->sel_item != item) {
Tcl_AppendResult(wi->interp, "selection isn't in item", (char *) NULL);
return TCL_ERROR;
}
*index = ti->sel_first;
}
else if ((c == 's') && (strncmp(p, "sel.last", length) == 0) &&
(length >= 5)) {
if (ti->sel_item != item) {
Tcl_AppendResult(wi->interp, "selection isn't in item", (char *) NULL);
return TCL_ERROR;
}
/*
* We return a modified selection end so that it reflect
* the text index of the last character _not_ the insertion
* point between the last and the next.
*/
*index = ti->sel_last-1;
}
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 = (int) 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 = (int) tmp;
*index = PointToChar(text, x, y);
}
else if (Tcl_GetIntFromObj(wi->interp, index_spec, index) == TCL_OK) {
if (*index < 0){
*index = 0;
}
else if ((unsigned int) *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(ZnItem item,
int field,
int *index,
char *chars)
{
TextItem text = (TextItem) item;
ZnTextInfo *ti = &item->wi->text_info;
unsigned int num_chars, byte_index, num_bytes = strlen(chars);
char *new;
if (num_bytes == 0) {
return;
}
if (*index < 0) {
*index = 0;
}
if ((unsigned int) *index > text->num_chars) {
*index = text->num_chars;
}
num_chars = Tcl_NumUtfChars(chars, (int) num_bytes);
if (text->text) {
byte_index = Tcl_UtfAtIndex(text->text, *index)-text->text;
new = ZnMalloc(strlen(text->text) + num_bytes + 1);
memcpy(new, text->text, (size_t) byte_index);
strcpy(new + byte_index + num_bytes, text->text + byte_index);
ZnFree(text->text);
}
else {
byte_index = 0;
new = ZnMalloc(num_bytes + 1);
new[num_bytes] = 0;
}
memcpy(new + byte_index, chars, num_bytes);
text->text = new;
text->num_chars += num_chars;
if (text->insert_index >= (unsigned int) *index) {
text->insert_index += num_chars;
}
if (ti->sel_item == item) {
if (ti->sel_first >= *index) {
ti->sel_first += num_chars;
}
if (ti->sel_last >= *index) {
ti->sel_last += num_chars;
}
if ((ti->anchor_item == item) && (ti->sel_anchor >= *index)) {
ti->sel_anchor += num_chars;
}
}
ZnITEM.Invalidate(item, ZN_COORDS_FLAG|ZN_LAYOUT_FLAG);
}
/*
**********************************************************************************
*
* DeleteChars --
*
**********************************************************************************
*/
static void
DeleteChars(ZnItem item,
int field,
int *first,
int *last)
{
TextItem text = (TextItem) item;
int byte_count, first_offset;
int char_count, num_bytes;
ZnTextInfo *ti = &item->wi->text_info;
char *new;
if (!text->text) {
return;
}
if (*first < 0) {
*first = 0;
}
if (*last >= (int) text->num_chars) {
*last = text->num_chars-1;
}
if (*first > *last) {
return;
}
char_count = *last + 1 - *first;
first_offset = Tcl_UtfAtIndex(text->text, *first)-text->text;
byte_count = Tcl_UtfAtIndex(text->text + first_offset, char_count)-
(text->text+first_offset);
num_bytes = strlen(text->text);
if (num_bytes - byte_count) {
new = (char *) ZnMalloc((unsigned) (num_bytes + 1 - byte_count));
memcpy(new, text->text, (size_t) first_offset);
strcpy(new + first_offset, text->text + first_offset + byte_count);
ZnFree(text->text);
text->text = new;
text->num_chars -= char_count;
}
else {
ZnFree(text->text);
text->text = NULL;
text->num_chars = 0;
}
if (text->insert_index > (unsigned int) *first) {
text->insert_index -= char_count;
if (text->insert_index < (unsigned int) *first) {
text->insert_index = *first;
}
else if (*first == 0) {
text->insert_index = 0;
}
}
if (ti->sel_item == item) {
if (ti->sel_first > *first) {
ti->sel_first -= char_count;
if (ti->sel_first < *first) {
ti->sel_first = *first;
}
}
if (ti->sel_last >= *first) {
ti->sel_last -= char_count;
if (ti->sel_last < *first - 1) {
ti->sel_last = *first - 1;
}
}
if (ti->sel_first > ti->sel_last) {
ti->sel_item = ZN_NO_ITEM;
}
if ((ti->anchor_item == item) && (ti->sel_anchor > *first)) {
ti->sel_anchor -= char_count;
if (ti->sel_anchor < *first) {
ti->sel_anchor = *first;
}
}
}
ZnITEM.Invalidate(item, ZN_COORDS_FLAG|ZN_LAYOUT_FLAG);
}
/*
**********************************************************************************
*
* Cursor --
*
**********************************************************************************
*/
static void
TextCursor(ZnItem item,
int field,
int index)
{
TextItem text = (TextItem) item;
if (index < 0) {
text->insert_index = 0;
}
else if ((unsigned int) index > text->num_chars) {
text->insert_index = text->num_chars;
}
else {
text->insert_index = index;
}
}
/*
**********************************************************************************
*
* Selection --
*
**********************************************************************************
*/
static int
Selection(ZnItem item,
int field,
int offset,
char *chars,
int max_bytes)
{
TextItem text = (TextItem) item;
ZnWInfo *wi = item->wi;
ZnTextInfo *ti = &wi->text_info;
int count;
char const *sel_first, *sel_last;
if (!text->text) {
return 0;
}
if ((ti->sel_first < 0) ||
(ti->sel_first > ti->sel_last)) {
return 0;
}
sel_first = Tcl_UtfAtIndex(text->text, ti->sel_first);
sel_last = Tcl_UtfAtIndex(sel_first, ti->sel_last + 1 - ti->sel_first);
count = sel_last - sel_first - offset;
if (count <= 0) {
return 0;
}
if (count > max_bytes) {
count = max_bytes;
}
memcpy(chars, sel_first + offset, (size_t) count);
chars[count] = 0;
return count;
}
/*
**********************************************************************************
*
* Exported functions struct
*
**********************************************************************************
*/
static ZnItemClassStruct TEXT_ITEM_CLASS = {
"text",
sizeof(TextItemStruct),
text_attrs,
0, /* num_parts */
ZN_CLASS_HAS_ANCHORS|ZN_CLASS_ONE_COORD, /* flags */
Tk_Offset(TextItemStruct, pos),
Init,
Clone,
Destroy,
Configure,
Query,
NULL, /* GetFieldSet */
GetAnchor,
GetClipVertices,
NULL, /* GetContours */
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;