1 /*
2  * tkImage.c --
3  *
4  *	This file contains code that allows images to be nested inside text
5  *	widgets. It also implements the "image" widget command for texts.
6  *
7  * Copyright (c) 1997 Sun Microsystems, Inc.
8  *
9  * See the file "license.terms" for information on usage and redistribution of
10  * this file, and for a DISCLAIMER OF ALL WARRANTIES.
11  */
12 
13 #include "tkPort.h"
14 #include "tkText.h"
15 
16 /*
17  * Macro that determines the size of an embedded image segment:
18  */
19 
20 #define EI_SEG_SIZE \
21 	((unsigned) (Tk_Offset(TkTextSegment, body) + sizeof(TkTextEmbImage)))
22 
23 /*
24  * Prototypes for functions defined in this file:
25  */
26 
27 static TkTextSegment *	EmbImageCleanupProc(TkTextSegment *segPtr,
28 			    TkTextLine *linePtr);
29 static void		EmbImageCheckProc(TkTextSegment *segPtr,
30 			    TkTextLine *linePtr);
31 static void		EmbImageBboxProc(TkText *textPtr,
32 			    TkTextDispChunk *chunkPtr, int index, int y,
33 			    int lineHeight, int baseline, int *xPtr, int *yPtr,
34 			    int *widthPtr, int *heightPtr);
35 static int		EmbImageConfigure(TkText *textPtr,
36 			    TkTextSegment *eiPtr, int objc,
37 			    Tcl_Obj *const objv[]);
38 static int		EmbImageDeleteProc(TkTextSegment *segPtr,
39 			    TkTextLine *linePtr, int treeGone);
40 static void		EmbImageDisplayProc(TkText *textPtr,
41 			    TkTextDispChunk *chunkPtr, int x, int y,
42 			    int lineHeight, int baseline, Display *display,
43 			    Drawable dst, int screenY);
44 static int		EmbImageLayoutProc(TkText *textPtr,
45 			    TkTextIndex *indexPtr, TkTextSegment *segPtr,
46 			    int offset, int maxX, int maxChars,
47 			    int noCharsYet, TkWrapMode wrapMode,
48 			    TkTextDispChunk *chunkPtr);
49 static void		EmbImageProc(ClientData clientData, int x, int y,
50 			    int width, int height, int imageWidth,
51 			    int imageHeight);
52 
53 /*
54  * The following structure declares the "embedded image" segment type.
55  */
56 
57 static const Tk_SegType tkTextEmbImageType = {
58     "image",			/* name */
59     0,				/* leftGravity */
60     NULL,			/* splitProc */
61     EmbImageDeleteProc,		/* deleteProc */
62     EmbImageCleanupProc,	/* cleanupProc */
63     NULL,			/* lineChangeProc */
64     EmbImageLayoutProc,		/* layoutProc */
65     EmbImageCheckProc		/* checkProc */
66 };
67 
68 /*
69  * Definitions for alignment values:
70  */
71 
72 static const char *alignStrings[] = {
73     "baseline", "bottom", "center", "top", NULL
74 };
75 
76 typedef enum {
77     ALIGN_BASELINE, ALIGN_BOTTOM, ALIGN_CENTER, ALIGN_TOP
78 } alignMode;
79 
80 /*
81  * Information used for parsing image configuration options:
82  */
83 
84 static const Tk_OptionSpec optionSpecs[] = {
85     {TK_OPTION_STRING_TABLE, "-align", NULL, NULL,
86 	"center", -1, Tk_Offset(TkTextEmbImage, align),
87 	0, (ClientData) alignStrings, 0},
88     {TK_OPTION_PIXELS, "-padx", NULL, NULL,
89 	"0", -1, Tk_Offset(TkTextEmbImage, padX), 0, 0, 0},
90     {TK_OPTION_PIXELS, "-pady", NULL, NULL,
91 	"0", -1, Tk_Offset(TkTextEmbImage, padY), 0, 0, 0},
92     {TK_OPTION_STRING, "-image", NULL, NULL,
93 	NULL, -1, Tk_Offset(TkTextEmbImage, imageString),
94 	TK_OPTION_NULL_OK, 0, 0},
95     {TK_OPTION_STRING, "-name", NULL, NULL,
96 	NULL, -1, Tk_Offset(TkTextEmbImage, imageName),
97 	TK_OPTION_NULL_OK, 0, 0},
98     {TK_OPTION_END}
99 };
100 
101 
102 /*
103  *--------------------------------------------------------------
104  *
105  * TkTextImageCmd --
106  *
107  *	This function implements the "image" widget command for text widgets.
108  *	See the user documentation for details on what it does.
109  *
110  * Results:
111  *	A standard Tcl result or error.
112  *
113  * Side effects:
114  *	See the user documentation.
115  *
116  *--------------------------------------------------------------
117  */
118 
119 int
TkTextImageCmd(register TkText * textPtr,Tcl_Interp * interp,int objc,Tcl_Obj * const objv[])120 TkTextImageCmd(
121     register TkText *textPtr,	/* Information about text widget. */
122     Tcl_Interp *interp,		/* Current interpreter. */
123     int objc,			/* Number of arguments. */
124     Tcl_Obj *const objv[])	/* Argument objects. Someone else has already
125 				 * parsed this command enough to know that
126 				 * objv[1] is "image". */
127 {
128     int idx;
129     register TkTextSegment *eiPtr;
130     TkTextIndex index;
131     static const char *optionStrings[] = {
132 	"cget", "configure", "create", "names", NULL
133     };
134     enum opts {
135 	CMD_CGET, CMD_CONF, CMD_CREATE, CMD_NAMES
136     };
137 
138     if (objc < 3) {
139 	Tcl_WrongNumArgs(interp, 2, objv, "option ?arg arg ...?");
140 	return TCL_ERROR;
141     }
142     if (Tcl_GetIndexFromObj(interp, objv[2], optionStrings, "option", 0,
143 	    &idx) != TCL_OK) {
144 	return TCL_ERROR;
145     }
146     switch ((enum opts) idx) {
147     case CMD_CGET: {
148 	Tcl_Obj *objPtr;
149 
150 	if (objc != 5) {
151 	    Tcl_WrongNumArgs(interp, 3, objv, "index option");
152 	    return TCL_ERROR;
153 	}
154 	if (TkTextGetObjIndex(interp, textPtr, objv[3], &index) != TCL_OK) {
155 	    return TCL_ERROR;
156 	}
157 	eiPtr = TkTextIndexToSeg(&index, NULL);
158 	if (eiPtr->typePtr != &tkTextEmbImageType) {
159 	    Tcl_AppendResult(interp, "no embedded image at index \"",
160 		    Tcl_GetString(objv[3]), "\"", NULL);
161 	    return TCL_ERROR;
162 	}
163 	objPtr = Tk_GetOptionValue(interp, (char *) &eiPtr->body.ei,
164 		eiPtr->body.ei.optionTable, objv[4], textPtr->tkwin);
165 	if (objPtr == NULL) {
166 	    return TCL_ERROR;
167 	} else {
168 	    Tcl_SetObjResult(interp, objPtr);
169 	    return TCL_OK;
170 	}
171     }
172     case CMD_CONF:
173 	if (objc < 4) {
174 	    Tcl_WrongNumArgs(interp, 3, objv, "index ?option value ...?");
175 	    return TCL_ERROR;
176 	}
177 	if (TkTextGetObjIndex(interp, textPtr, objv[3], &index) != TCL_OK) {
178 	    return TCL_ERROR;
179 	}
180 	eiPtr = TkTextIndexToSeg(&index, NULL);
181 	if (eiPtr->typePtr != &tkTextEmbImageType) {
182 	    Tcl_AppendResult(interp, "no embedded image at index \"",
183 		    Tcl_GetString(objv[3]), "\"", NULL);
184 	    return TCL_ERROR;
185 	}
186 	if (objc <= 5) {
187 	    Tcl_Obj *objPtr = Tk_GetOptionInfo(interp,
188 		    (char *) &eiPtr->body.ei, eiPtr->body.ei.optionTable,
189 		    (objc == 5) ? objv[4] : NULL, textPtr->tkwin);
190 	    if (objPtr == NULL) {
191 		return TCL_ERROR;
192 	    } else {
193 		Tcl_SetObjResult(interp, objPtr);
194 		return TCL_OK;
195 	    }
196 	} else {
197 	    TkTextChanged(textPtr->sharedTextPtr, NULL, &index, &index);
198 
199 	    /*
200 	     * It's probably not true that all window configuration can change
201 	     * the line height, so we could be more efficient here and only
202 	     * call this when necessary.
203 	     */
204 
205 	    TkTextInvalidateLineMetrics(textPtr->sharedTextPtr, NULL,
206 		    index.linePtr, 0, TK_TEXT_INVALIDATE_ONLY);
207 	    return EmbImageConfigure(textPtr, eiPtr, objc-4, objv+4);
208 	}
209     case CMD_CREATE: {
210 	int lineIndex;
211 
212 	/*
213 	 * Add a new image. Find where to put the new image, and mark that
214 	 * position for redisplay.
215 	 */
216 
217 	if (objc < 4) {
218 	    Tcl_WrongNumArgs(interp, 3, objv, "index ?option value ...?");
219 	    return TCL_ERROR;
220 	}
221 	if (TkTextGetObjIndex(interp, textPtr, objv[3], &index) != TCL_OK) {
222 	    return TCL_ERROR;
223 	}
224 
225 	/*
226 	 * Don't allow insertions on the last (dummy) line of the text.
227 	 */
228 
229 	lineIndex = TkBTreeLinesTo(textPtr, index.linePtr);
230 	if (lineIndex == TkBTreeNumLines(textPtr->sharedTextPtr->tree,
231 		textPtr)) {
232 	    lineIndex--;
233 	    TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr,
234 		    lineIndex, 1000000, &index);
235 	}
236 
237 	/*
238 	 * Create the new image segment and initialize it.
239 	 */
240 
241 	eiPtr = (TkTextSegment *) ckalloc(EI_SEG_SIZE);
242 	eiPtr->typePtr = &tkTextEmbImageType;
243 	eiPtr->size = 1;
244 	eiPtr->body.ei.sharedTextPtr = textPtr->sharedTextPtr;
245 	eiPtr->body.ei.linePtr = NULL;
246 	eiPtr->body.ei.imageName = NULL;
247 	eiPtr->body.ei.imageString = NULL;
248 	eiPtr->body.ei.name = NULL;
249 	eiPtr->body.ei.image = NULL;
250 	eiPtr->body.ei.align = ALIGN_CENTER;
251 	eiPtr->body.ei.padX = eiPtr->body.ei.padY = 0;
252 	eiPtr->body.ei.chunkCount = 0;
253 	eiPtr->body.ei.optionTable = Tk_CreateOptionTable(interp, optionSpecs);
254 
255 	/*
256 	 * Link the segment into the text widget, then configure it (delete it
257 	 * again if the configuration fails).
258 	 */
259 
260 	TkTextChanged(textPtr->sharedTextPtr, NULL, &index, &index);
261 	TkBTreeLinkSegment(eiPtr, &index);
262 	if (EmbImageConfigure(textPtr, eiPtr, objc-4, objv+4) != TCL_OK) {
263 	    TkTextIndex index2;
264 
265 	    TkTextIndexForwChars(NULL, &index, 1, &index2, COUNT_INDICES);
266 	    TkBTreeDeleteIndexRange(textPtr->sharedTextPtr->tree, &index, &index2);
267 	    return TCL_ERROR;
268 	}
269 	TkTextInvalidateLineMetrics(textPtr->sharedTextPtr, NULL,
270 		index.linePtr, 0, TK_TEXT_INVALIDATE_ONLY);
271 	return TCL_OK;
272     }
273     case CMD_NAMES: {
274 	Tcl_HashSearch search;
275 	Tcl_HashEntry *hPtr;
276 
277 	if (objc != 3) {
278 	    Tcl_WrongNumArgs(interp, 3, objv, NULL);
279 	    return TCL_ERROR;
280 	}
281 	for (hPtr = Tcl_FirstHashEntry(&textPtr->sharedTextPtr->imageTable,
282 		&search); hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) {
283 	    Tcl_AppendElement(interp,
284 		    Tcl_GetHashKey(&textPtr->sharedTextPtr->markTable, hPtr));
285 	}
286 	return TCL_OK;
287     }
288     default:
289 	Tcl_Panic("unexpected switch fallthrough");
290     }
291     return TCL_ERROR;
292 }
293 
294 /*
295  *--------------------------------------------------------------
296  *
297  * EmbImageConfigure --
298  *
299  *	This function is called to handle configuration options for an
300  *	embedded image, using an objc/objv list.
301  *
302  * Results:
303  *	The return value is a standard Tcl result. If TCL_ERROR is returned,
304  *	then the interp's result contains an error message..
305  *
306  * Side effects:
307  *	Configuration information for the embedded image changes, such as
308  *	alignment, or name of the image.
309  *
310  *--------------------------------------------------------------
311  */
312 
313 static int
EmbImageConfigure(TkText * textPtr,TkTextSegment * eiPtr,int objc,Tcl_Obj * const objv[])314 EmbImageConfigure(
315     TkText *textPtr,		/* Information about text widget that contains
316 				 * embedded image. */
317     TkTextSegment *eiPtr,	/* Embedded image to be configured. */
318     int objc,			/* Number of strings in objv. */
319     Tcl_Obj *const objv[])	/* Array of strings describing configuration
320 				 * options. */
321 {
322     Tk_Image image;
323     Tcl_DString newName;
324     Tcl_HashEntry *hPtr;
325     Tcl_HashSearch search;
326     char *name;
327     int count = 0;		/* The counter for picking a unique name */
328     int conflict = 0;		/* True if we have a name conflict */
329     size_t len;		/* length of image name */
330 
331     if (Tk_SetOptions(textPtr->interp, (char*)&eiPtr->body.ei,
332 	    eiPtr->body.ei.optionTable,
333 	    objc, objv, textPtr->tkwin, NULL, NULL) != TCL_OK) {
334 	return TCL_ERROR;
335     }
336 
337     /*
338      * Create the image. Save the old image around and don't free it until
339      * after the new one is allocated. This keeps the reference count from
340      * going to zero so the image doesn't have to be recreated if it hasn't
341      * changed.
342      */
343 
344     if (eiPtr->body.ei.imageString != NULL) {
345 	image = Tk_GetImage(textPtr->interp, textPtr->tkwin,
346 		eiPtr->body.ei.imageString, EmbImageProc, (ClientData) eiPtr);
347 	if (image == NULL) {
348 	    return TCL_ERROR;
349 	}
350     } else {
351 	image = NULL;
352     }
353     if (eiPtr->body.ei.image != NULL) {
354 	Tk_FreeImage(eiPtr->body.ei.image);
355     }
356     eiPtr->body.ei.image = image;
357 
358     if (eiPtr->body.ei.name != NULL) {
359     	return TCL_OK;
360     }
361 
362     /*
363      * Find a unique name for this image. Use imageName (or imageString) if
364      * available, otherwise tack on a #nn and use it. If a name is already
365      * associated with this image, delete the name.
366      */
367 
368     name = eiPtr->body.ei.imageName;
369     if (name == NULL) {
370     	name = eiPtr->body.ei.imageString;
371     }
372     if (name == NULL) {
373 	Tcl_AppendResult(textPtr->interp, "Either a \"-name\" ",
374 		"or a \"-image\" argument must be provided ",
375 		"to the \"image create\" subcommand.", NULL);
376 	return TCL_ERROR;
377     }
378     len = strlen(name);
379     for (hPtr = Tcl_FirstHashEntry(&textPtr->sharedTextPtr->imageTable,
380 	    &search); hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) {
381 	char *haveName =
382 		Tcl_GetHashKey(&textPtr->sharedTextPtr->imageTable, hPtr);
383 
384 	if (strncmp(name, haveName, len) == 0) {
385 	    int newVal = 0;
386 
387 	    sscanf(haveName+len, "#%d", &newVal);
388 	    if (newVal > count) {
389 		count = newVal;
390 	    }
391 	    if (len == strlen(haveName)) {
392 	    	conflict = 1;
393 	    }
394 	}
395     }
396 
397     Tcl_DStringInit(&newName);
398     Tcl_DStringAppend(&newName, name, -1);
399 
400     if (conflict) {
401     	char buf[4 + TCL_INTEGER_SPACE];
402 
403 	sprintf(buf, "#%d", count+1);
404 	Tcl_DStringAppend(&newName, buf, -1);
405     }
406     name = Tcl_DStringValue(&newName);
407     {
408 	int dummy;
409 
410 	hPtr = Tcl_CreateHashEntry(&textPtr->sharedTextPtr->imageTable, name,
411 		&dummy);
412     }
413     Tcl_SetHashValue(hPtr, eiPtr);
414     Tcl_AppendResult(textPtr->interp, name, NULL);
415     eiPtr->body.ei.name = ckalloc((unsigned) Tcl_DStringLength(&newName)+1);
416     strcpy(eiPtr->body.ei.name, name);
417     Tcl_DStringFree(&newName);
418 
419     return TCL_OK;
420 }
421 
422 /*
423  *--------------------------------------------------------------
424  *
425  * EmbImageDeleteProc --
426  *
427  *	This function is invoked by the text B-tree code whenever an embedded
428  *	image lies in a range of characters being deleted.
429  *
430  * Results:
431  *	Returns 0 to indicate that the deletion has been accepted.
432  *
433  * Side effects:
434  *	The embedded image is deleted, if it exists, and any resources
435  *	associated with it are released.
436  *
437  *--------------------------------------------------------------
438  */
439 
440 	/* ARGSUSED */
441 static int
EmbImageDeleteProc(TkTextSegment * eiPtr,TkTextLine * linePtr,int treeGone)442 EmbImageDeleteProc(
443     TkTextSegment *eiPtr,	/* Segment being deleted. */
444     TkTextLine *linePtr,	/* Line containing segment. */
445     int treeGone)		/* Non-zero means the entire tree is being
446 				 * deleted, so everything must get cleaned
447 				 * up. */
448 {
449     Tcl_HashEntry *hPtr;
450 
451     if (eiPtr->body.ei.image != NULL) {
452 	hPtr = Tcl_FindHashEntry(&eiPtr->body.ei.sharedTextPtr->imageTable,
453 		eiPtr->body.ei.name);
454 	if (hPtr != NULL) {
455 	    /*
456 	     * (It's possible for there to be no hash table entry for this
457 	     * image, if an error occurred while creating the image segment
458 	     * but before the image got added to the table)
459 	     */
460 
461 	    Tcl_DeleteHashEntry(hPtr);
462 	}
463 	Tk_FreeImage(eiPtr->body.ei.image);
464     }
465 
466     /*
467      * No need to supply a tkwin argument, since we have no window-specific
468      * options.
469      */
470 
471     Tk_FreeConfigOptions((char *) &eiPtr->body.ei, eiPtr->body.ei.optionTable,
472 	    NULL);
473     if (eiPtr->body.ei.name) {
474 	ckfree(eiPtr->body.ei.name);
475     }
476     ckfree((char *) eiPtr);
477     return 0;
478 }
479 
480 /*
481  *--------------------------------------------------------------
482  *
483  * EmbImageCleanupProc --
484  *
485  *	This function is invoked by the B-tree code whenever a segment
486  *	containing an embedded image is moved from one line to another.
487  *
488  * Results:
489  *	None.
490  *
491  * Side effects:
492  *	The linePtr field of the segment gets updated.
493  *
494  *--------------------------------------------------------------
495  */
496 
497 static TkTextSegment *
EmbImageCleanupProc(TkTextSegment * eiPtr,TkTextLine * linePtr)498 EmbImageCleanupProc(
499     TkTextSegment *eiPtr,	/* Mark segment that's being moved. */
500     TkTextLine *linePtr)	/* Line that now contains segment. */
501 {
502     eiPtr->body.ei.linePtr = linePtr;
503     return eiPtr;
504 }
505 
506 /*
507  *--------------------------------------------------------------
508  *
509  * EmbImageLayoutProc --
510  *
511  *	This function is the "layoutProc" for embedded image segments.
512  *
513  * Results:
514  *	1 is returned to indicate that the segment should be displayed. The
515  *	chunkPtr structure is filled in.
516  *
517  * Side effects:
518  *	None, except for filling in chunkPtr.
519  *
520  *--------------------------------------------------------------
521  */
522 
523 	/*ARGSUSED*/
524 static int
EmbImageLayoutProc(TkText * textPtr,TkTextIndex * indexPtr,TkTextSegment * eiPtr,int offset,int maxX,int maxChars,int noCharsYet,TkWrapMode wrapMode,register TkTextDispChunk * chunkPtr)525 EmbImageLayoutProc(
526     TkText *textPtr,		/* Text widget being layed out. */
527     TkTextIndex *indexPtr,	/* Identifies first character in chunk. */
528     TkTextSegment *eiPtr,	/* Segment corresponding to indexPtr. */
529     int offset,			/* Offset within segPtr corresponding to
530 				 * indexPtr (always 0). */
531     int maxX,			/* Chunk must not occupy pixels at this
532 				 * position or higher. */
533     int maxChars,		/* Chunk must not include more than this many
534 				 * characters. */
535     int noCharsYet,		/* Non-zero means no characters have been
536 				 * assigned to this line yet. */
537     TkWrapMode wrapMode,	/* Wrap mode to use for line:
538 				 * TEXT_WRAPMODE_CHAR, TEXT_WRAPMODE_NONE, or
539 				 * TEXT_WRAPMODE_WORD. */
540     register TkTextDispChunk *chunkPtr)
541 				/* Structure to fill in with information about
542 				 * this chunk. The x field has already been
543 				 * set by the caller. */
544 {
545     int width, height;
546 
547     if (offset != 0) {
548 	Tcl_Panic("Non-zero offset in EmbImageLayoutProc");
549     }
550 
551     /*
552      * See if there's room for this image on this line.
553      */
554 
555     if (eiPtr->body.ei.image == NULL) {
556 	width = 0;
557 	height = 0;
558     } else {
559 	Tk_SizeOfImage(eiPtr->body.ei.image, &width, &height);
560 	width += 2*eiPtr->body.ei.padX;
561 	height += 2*eiPtr->body.ei.padY;
562     }
563     if ((width > (maxX - chunkPtr->x))
564 	    && !noCharsYet && (textPtr->wrapMode != TEXT_WRAPMODE_NONE)) {
565 	return 0;
566     }
567 
568     /*
569      * Fill in the chunk structure.
570      */
571 
572     chunkPtr->displayProc = EmbImageDisplayProc;
573     chunkPtr->undisplayProc = NULL;
574     chunkPtr->measureProc = NULL;
575     chunkPtr->bboxProc = EmbImageBboxProc;
576     chunkPtr->numBytes = 1;
577     if (eiPtr->body.ei.align == ALIGN_BASELINE) {
578 	chunkPtr->minAscent = height - eiPtr->body.ei.padY;
579 	chunkPtr->minDescent = eiPtr->body.ei.padY;
580 	chunkPtr->minHeight = 0;
581     } else {
582 	chunkPtr->minAscent = 0;
583 	chunkPtr->minDescent = 0;
584 	chunkPtr->minHeight = height;
585     }
586     chunkPtr->width = width;
587     chunkPtr->breakIndex = -1;
588     chunkPtr->breakIndex = 1;
589     chunkPtr->clientData = (ClientData) eiPtr;
590     eiPtr->body.ei.chunkCount += 1;
591     return 1;
592 }
593 
594 /*
595  *--------------------------------------------------------------
596  *
597  * EmbImageCheckProc --
598  *
599  *	This function is invoked by the B-tree code to perform consistency
600  *	checks on embedded images.
601  *
602  * Results:
603  *	None.
604  *
605  * Side effects:
606  *	The function panics if it detects anything wrong with the embedded
607  *	image.
608  *
609  *--------------------------------------------------------------
610  */
611 
612 static void
EmbImageCheckProc(TkTextSegment * eiPtr,TkTextLine * linePtr)613 EmbImageCheckProc(
614     TkTextSegment *eiPtr,	/* Segment to check. */
615     TkTextLine *linePtr)	/* Line containing segment. */
616 {
617     if (eiPtr->nextPtr == NULL) {
618 	Tcl_Panic("EmbImageCheckProc: embedded image is last segment in line");
619     }
620     if (eiPtr->size != 1) {
621 	Tcl_Panic("EmbImageCheckProc: embedded image has size %d",
622 		eiPtr->size);
623     }
624 }
625 
626 /*
627  *--------------------------------------------------------------
628  *
629  * EmbImageDisplayProc --
630  *
631  *	This function is invoked by the text displaying code when it is time
632  *	to actually draw an embedded image chunk on the screen.
633  *
634  * Results:
635  *	None.
636  *
637  * Side effects:
638  *	The embedded image gets moved to the correct location and drawn onto
639  *	the display.
640  *
641  *--------------------------------------------------------------
642  */
643 
644 static void
EmbImageDisplayProc(TkText * textPtr,TkTextDispChunk * chunkPtr,int x,int y,int lineHeight,int baseline,Display * display,Drawable dst,int screenY)645 EmbImageDisplayProc(
646     TkText *textPtr,
647     TkTextDispChunk *chunkPtr,	/* Chunk that is to be drawn. */
648     int x,			/* X-position in dst at which to draw this
649 				 * chunk (differs from the x-position in the
650 				 * chunk because of scrolling). */
651     int y,			/* Top of rectangular bounding box for line:
652 				 * tells where to draw this chunk in dst
653 				 * (x-position is in the chunk itself). */
654     int lineHeight,		/* Total height of line. */
655     int baseline,		/* Offset of baseline from y. */
656     Display *display,		/* Display to use for drawing. */
657     Drawable dst,		/* Pixmap or window in which to draw */
658     int screenY)		/* Y-coordinate in text window that
659 				 * corresponds to y. */
660 {
661     TkTextSegment *eiPtr = (TkTextSegment *) chunkPtr->clientData;
662     int lineX, imageX, imageY, width, height;
663     Tk_Image image;
664 
665     image = eiPtr->body.ei.image;
666     if (image == NULL) {
667 	return;
668     }
669     if ((x + chunkPtr->width) <= 0) {
670 	return;
671     }
672 
673     /*
674      * Compute the image's location and size in the text widget, taking into
675      * account the align value for the image.
676      */
677 
678     EmbImageBboxProc(textPtr, chunkPtr, 0, y, lineHeight, baseline, &lineX,
679 	    &imageY, &width, &height);
680     imageX = lineX - chunkPtr->x + x;
681 
682     Tk_RedrawImage(image, 0, 0, width, height, dst, imageX, imageY);
683 }
684 
685 /*
686  *--------------------------------------------------------------
687  *
688  * EmbImageBboxProc --
689  *
690  *	This function is called to compute the bounding box of the area
691  *	occupied by an embedded image.
692  *
693  * Results:
694  *	There is no return value. *xPtr and *yPtr are filled in with the
695  *	coordinates of the upper left corner of the image, and *widthPtr and
696  *	*heightPtr are filled in with the dimensions of the image in pixels.
697  *	Note: not all of the returned bbox is necessarily visible on the
698  *	screen (the rightmost part might be off-screen to the right, and the
699  *	bottommost part might be off-screen to the bottom).
700  *
701  * Side effects:
702  *	None.
703  *
704  *--------------------------------------------------------------
705  */
706 
707 static void
EmbImageBboxProc(TkText * textPtr,TkTextDispChunk * chunkPtr,int index,int y,int lineHeight,int baseline,int * xPtr,int * yPtr,int * widthPtr,int * heightPtr)708 EmbImageBboxProc(
709     TkText *textPtr,
710     TkTextDispChunk *chunkPtr,	/* Chunk containing desired char. */
711     int index,			/* Index of desired character within the
712 				 * chunk. */
713     int y,			/* Topmost pixel in area allocated for this
714 				 * line. */
715     int lineHeight,		/* Total height of line. */
716     int baseline,		/* Location of line's baseline, in pixels
717 				 * measured down from y. */
718     int *xPtr, int *yPtr,	/* Gets filled in with coords of character's
719 				 * upper-left pixel. */
720     int *widthPtr,		/* Gets filled in with width of image, in
721 				 * pixels. */
722     int *heightPtr)		/* Gets filled in with height of image, in
723 				 * pixels. */
724 {
725     TkTextSegment *eiPtr = (TkTextSegment *) chunkPtr->clientData;
726     Tk_Image image;
727 
728     image = eiPtr->body.ei.image;
729     if (image != NULL) {
730 	Tk_SizeOfImage(image, widthPtr, heightPtr);
731     } else {
732 	*widthPtr = 0;
733 	*heightPtr = 0;
734     }
735 
736     *xPtr = chunkPtr->x + eiPtr->body.ei.padX;
737 
738     switch (eiPtr->body.ei.align) {
739     case ALIGN_BOTTOM:
740 	*yPtr = y + (lineHeight - *heightPtr - eiPtr->body.ei.padY);
741 	break;
742     case ALIGN_CENTER:
743 	*yPtr = y + (lineHeight - *heightPtr)/2;
744 	break;
745     case ALIGN_TOP:
746 	*yPtr = y + eiPtr->body.ei.padY;
747 	break;
748     case ALIGN_BASELINE:
749 	*yPtr = y + (baseline - *heightPtr);
750 	break;
751     }
752 }
753 
754 /*
755  *--------------------------------------------------------------
756  *
757  * TkTextImageIndex --
758  *
759  *	Given the name of an embedded image within a text widget, returns an
760  *	index corresponding to the image's position in the text.
761  *
762  * Results:
763  *	The return value is 1 if there is an embedded image by the given name
764  *	in the text widget, 0 otherwise. If the image exists, *indexPtr is
765  *	filled in with its index.
766  *
767  * Side effects:
768  *	None.
769  *
770  *--------------------------------------------------------------
771  */
772 
773 int
TkTextImageIndex(TkText * textPtr,const char * name,TkTextIndex * indexPtr)774 TkTextImageIndex(
775     TkText *textPtr,		/* Text widget containing image. */
776     const char *name,		/* Name of image. */
777     TkTextIndex *indexPtr)	/* Index information gets stored here. */
778 {
779     Tcl_HashEntry *hPtr;
780     TkTextSegment *eiPtr;
781 
782     if (textPtr == NULL) {
783 	return 0;
784     }
785 
786     hPtr = Tcl_FindHashEntry(&textPtr->sharedTextPtr->imageTable, name);
787     if (hPtr == NULL) {
788 	return 0;
789     }
790     eiPtr = (TkTextSegment *) Tcl_GetHashValue(hPtr);
791     indexPtr->tree = textPtr->sharedTextPtr->tree;
792     indexPtr->linePtr = eiPtr->body.ei.linePtr;
793     indexPtr->byteIndex = TkTextSegToOffset(eiPtr, indexPtr->linePtr);
794     return 1;
795 }
796 
797 /*
798  *--------------------------------------------------------------
799  *
800  * EmbImageProc --
801  *
802  *	This function is called by the image code whenever an image or its
803  *	contents changes.
804  *
805  * Results:
806  *	None.
807  *
808  * Side effects:
809  *	The image will be redisplayed.
810  *
811  *--------------------------------------------------------------
812  */
813 
814 static void
EmbImageProc(ClientData clientData,int x,int y,int width,int height,int imgWidth,int imgHeight)815 EmbImageProc(
816     ClientData clientData,	/* Pointer to widget record. */
817     int x, int y,		/* Upper left pixel (within image) that must
818 				 * be redisplayed. */
819     int width, int height,	/* Dimensions of area to redisplay (may be
820 				 * <= 0). */
821     int imgWidth, int imgHeight)/* New dimensions of image. */
822 
823 {
824     TkTextSegment *eiPtr = (TkTextSegment *) clientData;
825     TkTextIndex index;
826 
827     index.tree = eiPtr->body.ei.sharedTextPtr->tree;
828     index.linePtr = eiPtr->body.ei.linePtr;
829     index.byteIndex = TkTextSegToOffset(eiPtr, eiPtr->body.ei.linePtr);
830     TkTextChanged(eiPtr->body.ei.sharedTextPtr, NULL, &index, &index);
831 
832     /*
833      * It's probably not true that all image changes can change the line
834      * height, so we could be more efficient here and only call this when
835      * necessary.
836      */
837 
838     TkTextInvalidateLineMetrics(eiPtr->body.ei.sharedTextPtr, NULL,
839 	    index.linePtr, 0, TK_TEXT_INVALIDATE_ONLY);
840 }
841 
842 /*
843  * Local Variables:
844  * mode: c
845  * c-basic-offset: 4
846  * fill-column: 78
847  * End:
848  */
849