1 /*
2  * tkTextMark.c --
3  *
4  *	This file contains the functions that implement marks for text
5  *	widgets.
6  *
7  * Copyright © 1994 The Regents of the University of California.
8  * Copyright © 1994-1997 Sun Microsystems, Inc.
9  *
10  * See the file "license.terms" for information on usage and redistribution of
11  * this file, and for a DISCLAIMER OF ALL WARRANTIES.
12  */
13 
14 #include "tkInt.h"
15 #include "tkText.h"
16 #include "tk3d.h"
17 
18 /*
19  * Macro that determines the size of a mark segment:
20  */
21 
22 #define MSEG_SIZE (offsetof(TkTextSegment, body) \
23 	+ sizeof(TkTextMark))
24 
25 /*
26  * Forward references for functions defined in this file:
27  */
28 
29 static Tcl_Obj *	GetMarkName(TkText *textPtr, TkTextSegment *segPtr);
30 static void		InsertUndisplayProc(TkText *textPtr,
31 			    TkTextDispChunk *chunkPtr);
32 static int		MarkDeleteProc(TkTextSegment *segPtr,
33 			    TkTextLine *linePtr, int treeGone);
34 static TkTextSegment *	MarkCleanupProc(TkTextSegment *segPtr,
35 			    TkTextLine *linePtr);
36 static void		MarkCheckProc(TkTextSegment *segPtr,
37 			    TkTextLine *linePtr);
38 static int		MarkLayoutProc(TkText *textPtr, TkTextIndex *indexPtr,
39 			    TkTextSegment *segPtr, TkSizeT offset, int maxX,
40 			    TkSizeT maxChars, int noCharsYet, TkWrapMode wrapMode,
41 			    TkTextDispChunk *chunkPtr);
42 static int		MarkFindNext(Tcl_Interp *interp,
43 			    TkText *textPtr, Tcl_Obj *markName);
44 static int		MarkFindPrev(Tcl_Interp *interp,
45 			    TkText *textPtr, Tcl_Obj *markName);
46 
47 
48 /*
49  * The following structures declare the "mark" segment types. There are
50  * actually two types for marks, one with left gravity and one with right
51  * gravity. They are identical except for their gravity property.
52  */
53 
54 const Tk_SegType tkTextRightMarkType = {
55     "mark",			/* name */
56     0,				/* leftGravity */
57     NULL,			/* splitProc */
58     MarkDeleteProc,		/* deleteProc */
59     MarkCleanupProc,		/* cleanupProc */
60     NULL,			/* lineChangeProc */
61     MarkLayoutProc,		/* layoutProc */
62     MarkCheckProc		/* checkProc */
63 };
64 
65 const Tk_SegType tkTextLeftMarkType = {
66     "mark",			/* name */
67     1,				/* leftGravity */
68     NULL,			/* splitProc */
69     MarkDeleteProc,		/* deleteProc */
70     MarkCleanupProc,		/* cleanupProc */
71     NULL,			/* lineChangeProc */
72     MarkLayoutProc,		/* layoutProc */
73     MarkCheckProc		/* checkProc */
74 };
75 
76 /*
77  *--------------------------------------------------------------
78  *
79  * TkTextMarkCmd --
80  *
81  *	This function is invoked to process the "mark" options of the widget
82  *	command for text widgets. See the user documentation for details on
83  *	what it does.
84  *
85  * Results:
86  *	A standard Tcl result.
87  *
88  * Side effects:
89  *	See the user documentation.
90  *
91  *--------------------------------------------------------------
92  */
93 
94 int
TkTextMarkCmd(TkText * textPtr,Tcl_Interp * interp,int objc,Tcl_Obj * const objv[])95 TkTextMarkCmd(
96     TkText *textPtr,	/* Information about text widget. */
97     Tcl_Interp *interp,		/* Current interpreter. */
98     int objc,			/* Number of arguments. */
99     Tcl_Obj *const objv[])	/* Argument objects. Someone else has already
100 				 * parsed this command enough to know that
101 				 * objv[1] is "mark". */
102 {
103     Tcl_HashEntry *hPtr;
104     TkTextSegment *markPtr;
105     Tcl_HashSearch search;
106     TkTextIndex index;
107     const Tk_SegType *newTypePtr;
108     int optionIndex;
109     static const char *const markOptionStrings[] = {
110 	"gravity", "names", "next", "previous", "set", "unset", NULL
111     };
112     enum markOptions {
113 	MARK_GRAVITY, MARK_NAMES, MARK_NEXT, MARK_PREVIOUS, MARK_SET,
114 	MARK_UNSET
115     };
116 
117     if (objc < 3) {
118 	Tcl_WrongNumArgs(interp, 2, objv, "option ?arg ...?");
119 	return TCL_ERROR;
120     }
121     if (Tcl_GetIndexFromObjStruct(interp, objv[2], markOptionStrings,
122 	    sizeof(char *), "mark option", 0, &optionIndex) != TCL_OK) {
123 	return TCL_ERROR;
124     }
125 
126     switch ((enum markOptions) optionIndex) {
127     case MARK_GRAVITY: {
128 	char c;
129 	TkSizeT length;
130 	const char *str;
131 
132 	if (objc < 4 || objc > 5) {
133 	    Tcl_WrongNumArgs(interp, 3, objv, "markName ?gravity?");
134 	    return TCL_ERROR;
135 	}
136 	str = Tcl_GetStringFromObj(objv[3], &length);
137 	if (length == 6 && !strcmp(str, "insert")) {
138 	    markPtr = textPtr->insertMarkPtr;
139 	} else if (length == 7 && !strcmp(str, "current")) {
140 	    markPtr = textPtr->currentMarkPtr;
141 	} else {
142 	    hPtr = Tcl_FindHashEntry(&textPtr->sharedTextPtr->markTable, str);
143 	    if (hPtr == NULL) {
144 		Tcl_SetObjResult(interp, Tcl_ObjPrintf(
145 			"there is no mark named \"%s\"", str));
146 		Tcl_SetErrorCode(interp, "TK", "LOOKUP", "TEXT_MARK", str,
147 			NULL);
148 		return TCL_ERROR;
149 	    }
150 	    markPtr = (TkTextSegment *)Tcl_GetHashValue(hPtr);
151 	}
152 	if (objc == 4) {
153 	    const char *typeStr;
154 
155 	    if (markPtr->typePtr == &tkTextRightMarkType) {
156 		typeStr = "right";
157 	    } else {
158 		typeStr = "left";
159 	    }
160 	    Tcl_SetObjResult(interp, Tcl_NewStringObj(typeStr, -1));
161 	    return TCL_OK;
162 	}
163 	str = Tcl_GetStringFromObj(objv[4],&length);
164 	c = str[0];
165 	if ((c == 'l') && (strncmp(str, "left", length) == 0)) {
166 	    newTypePtr = &tkTextLeftMarkType;
167 	} else if ((c == 'r') &&
168 		(strncmp(str, "right", length) == 0)) {
169 	    newTypePtr = &tkTextRightMarkType;
170 	} else {
171 	    Tcl_SetObjResult(interp, Tcl_ObjPrintf(
172 		    "bad mark gravity \"%s\": must be left or right", str));
173 	    Tcl_SetErrorCode(interp, "TK", "VALUE", "MARK_GRAVITY", NULL);
174 	    return TCL_ERROR;
175 	}
176 	TkTextMarkSegToIndex(textPtr, markPtr, &index);
177 	TkBTreeUnlinkSegment(markPtr, markPtr->body.mark.linePtr);
178 	markPtr->typePtr = newTypePtr;
179 	TkBTreeLinkSegment(markPtr, &index);
180 	break;
181     }
182     case MARK_NAMES: {
183 	Tcl_Obj *resultObj;
184 
185 	if (objc != 3) {
186 	    Tcl_WrongNumArgs(interp, 3, objv, NULL);
187 	    return TCL_ERROR;
188 	}
189 	resultObj = Tcl_NewObj();
190 	Tcl_ListObjAppendElement(NULL, resultObj, Tcl_NewStringObj(
191 		"insert", -1));
192 	Tcl_ListObjAppendElement(NULL, resultObj, Tcl_NewStringObj(
193 		"current", -1));
194 	for (hPtr = Tcl_FirstHashEntry(&textPtr->sharedTextPtr->markTable,
195 		&search); hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) {
196 	    Tcl_ListObjAppendElement(NULL, resultObj, Tcl_NewStringObj(
197 		    (const char *)Tcl_GetHashKey(&textPtr->sharedTextPtr->markTable, hPtr),
198 		    -1));
199 	}
200 	Tcl_SetObjResult(interp, resultObj);
201 	break;
202     }
203     case MARK_NEXT:
204 	if (objc != 4) {
205 	    Tcl_WrongNumArgs(interp, 3, objv, "index");
206 	    return TCL_ERROR;
207 	}
208 	return MarkFindNext(interp, textPtr, objv[3]);
209     case MARK_PREVIOUS:
210 	if (objc != 4) {
211 	    Tcl_WrongNumArgs(interp, 3, objv, "index");
212 	    return TCL_ERROR;
213 	}
214 	return MarkFindPrev(interp, textPtr, objv[3]);
215     case MARK_SET:
216 	if (objc != 5) {
217 	    Tcl_WrongNumArgs(interp, 3, objv, "markName index");
218 	    return TCL_ERROR;
219 	}
220 	if (TkTextGetObjIndex(interp, textPtr, objv[4], &index) != TCL_OK) {
221 	    return TCL_ERROR;
222 	}
223 	TkTextSetMark(textPtr, Tcl_GetString(objv[3]), &index);
224 	return TCL_OK;
225     case MARK_UNSET: {
226 	int i;
227 
228 	for (i = 3; i < objc; i++) {
229 	    hPtr = Tcl_FindHashEntry(&textPtr->sharedTextPtr->markTable,
230 		    Tcl_GetString(objv[i]));
231 	    if (hPtr != NULL) {
232 		markPtr = (TkTextSegment *)Tcl_GetHashValue(hPtr);
233 
234 		/*
235 		 * Special case not needed with peer widgets.
236 		 */
237 
238 		if ((markPtr == textPtr->insertMarkPtr)
239 			|| (markPtr == textPtr->currentMarkPtr)) {
240 		    continue;
241 		}
242 		TkBTreeUnlinkSegment(markPtr, markPtr->body.mark.linePtr);
243 		Tcl_DeleteHashEntry(hPtr);
244 		ckfree(markPtr);
245 	    }
246 	}
247 	break;
248     }
249     }
250     return TCL_OK;
251 }
252 
253 /*
254  *----------------------------------------------------------------------
255  *
256  * TkTextSetMark --
257  *
258  *	Set a mark to a particular position, creating a new mark if one
259  *	doesn't already exist.
260  *
261  * Results:
262  *	The return value is a pointer to the mark that was just set.
263  *
264  * Side effects:
265  *	A new mark is created, or an existing mark is moved.
266  *
267  *----------------------------------------------------------------------
268  */
269 
270 TkTextSegment *
TkTextSetMark(TkText * textPtr,const char * name,TkTextIndex * indexPtr)271 TkTextSetMark(
272     TkText *textPtr,		/* Text widget in which to create mark. */
273     const char *name,		/* Name of mark to set. */
274     TkTextIndex *indexPtr)	/* Where to set mark. */
275 {
276     Tcl_HashEntry *hPtr = NULL;
277     TkTextSegment *markPtr;
278     TkTextIndex insertIndex;
279     int isNew, widgetSpecific;
280 
281     if (!strcmp(name, "insert")) {
282 	widgetSpecific = 1;
283 	markPtr = textPtr->insertMarkPtr;
284 	isNew = (markPtr == NULL ? 1 : 0);
285     } else if (!strcmp(name, "current")) {
286 	widgetSpecific = 2;
287 	markPtr = textPtr->currentMarkPtr;
288 	isNew = (markPtr == NULL ? 1 : 0);
289     } else {
290 	widgetSpecific = 0;
291 	hPtr = Tcl_CreateHashEntry(&textPtr->sharedTextPtr->markTable, name,
292 		&isNew);
293 	markPtr = (TkTextSegment *)Tcl_GetHashValue(hPtr);
294     }
295     if (!isNew) {
296 	/*
297 	 * If this is the insertion point that's being moved, be sure to force
298 	 * a display update at the old position. Also, don't let the insertion
299 	 * cursor be after the final newline of the file.
300 	 */
301 
302 	if (markPtr == textPtr->insertMarkPtr) {
303 	    TkTextIndex index, index2;
304             int nblines;
305 
306 	    TkTextMarkSegToIndex(textPtr, textPtr->insertMarkPtr, &index);
307 	    TkTextIndexForwChars(NULL, &index, 1, &index2, COUNT_INDICES);
308 
309 	    /*
310 	     * While we wish to redisplay, no heights have changed, so no need
311 	     * to call TkTextInvalidateLineMetrics.
312 	     */
313 
314 	    TkTextChanged(NULL, textPtr, &index, &index2);
315 
316             /*
317              * The number of lines in the widget is zero if and only if it is
318              * a partial peer with -startline == -endline, i.e. an empty
319              * peer. In this case the mark shall be set exactly at the given
320              * index, and not one character backwards (bug 3487407).
321              */
322 
323 	    nblines = TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr);
324 	    if ((TkBTreeLinesTo(textPtr, indexPtr->linePtr) == nblines)
325 		    && (nblines > 0))  {
326 		TkTextIndexBackChars(NULL,indexPtr, 1, &insertIndex,
327 			COUNT_INDICES);
328 		indexPtr = &insertIndex;
329 	    }
330 	}
331 	TkBTreeUnlinkSegment(markPtr, markPtr->body.mark.linePtr);
332     } else {
333 	markPtr = (TkTextSegment *)ckalloc(MSEG_SIZE);
334 	markPtr->typePtr = &tkTextRightMarkType;
335 	markPtr->size = 0;
336 	markPtr->body.mark.textPtr = textPtr;
337 	markPtr->body.mark.linePtr = indexPtr->linePtr;
338 	markPtr->body.mark.hPtr = hPtr;
339 	if (widgetSpecific == 0) {
340 	    Tcl_SetHashValue(hPtr, markPtr);
341 	} else if (widgetSpecific == 1) {
342 	    textPtr->insertMarkPtr = markPtr;
343 	} else {
344 	    textPtr->currentMarkPtr = markPtr;
345 	}
346     }
347     TkBTreeLinkSegment(markPtr, indexPtr);
348 
349     /*
350      * If the mark is the insertion cursor, then update the screen at the
351      * mark's new location.
352      */
353 
354     if (markPtr == textPtr->insertMarkPtr) {
355 	TkTextIndex index2;
356 
357 	TkTextIndexForwChars(NULL, indexPtr, 1, &index2, COUNT_INDICES);
358 
359 	/*
360 	 * While we wish to redisplay, no heights have changed, so no need to
361 	 * call TkTextInvalidateLineMetrics
362 	 */
363 
364 	TkTextChanged(NULL, textPtr, indexPtr, &index2);
365     }
366     return markPtr;
367 }
368 
369 /*
370  *--------------------------------------------------------------
371  *
372  * TkTextMarkSegToIndex --
373  *
374  *	Given a segment that is a mark, create an index that refers to the
375  *	next text character (or other text segment with non-zero size) after
376  *	the mark.
377  *
378  * Results:
379  *	*IndexPtr is filled in with index information.
380  *
381  * Side effects:
382  *	None.
383  *
384  *--------------------------------------------------------------
385  */
386 
387 void
TkTextMarkSegToIndex(TkText * textPtr,TkTextSegment * markPtr,TkTextIndex * indexPtr)388 TkTextMarkSegToIndex(
389     TkText *textPtr,		/* Text widget containing mark. */
390     TkTextSegment *markPtr,	/* Mark segment. */
391     TkTextIndex *indexPtr)	/* Index information gets stored here.  */
392 {
393     TkTextSegment *segPtr;
394 
395     indexPtr->tree = textPtr->sharedTextPtr->tree;
396     indexPtr->linePtr = markPtr->body.mark.linePtr;
397     indexPtr->byteIndex = 0;
398     for (segPtr = indexPtr->linePtr->segPtr; segPtr != markPtr;
399 	    segPtr = segPtr->nextPtr) {
400 	indexPtr->byteIndex += segPtr->size;
401     }
402 }
403 
404 /*
405  *--------------------------------------------------------------
406  *
407  * TkTextMarkNameToIndex --
408  *
409  *	Given the name of a mark, return an index corresponding to the mark
410  *	name.
411  *
412  * Results:
413  *	The return value is TCL_OK if "name" exists as a mark in the text
414  *	widget and is located within its -starline/-endline range. In this
415  *	case *indexPtr is filled in with the next segment who is after the
416  *	mark whose size is non-zero. TCL_ERROR is returned if the mark
417  *	doesn't exist in the text widget, or if it is out of its -starline/
418  *	-endline range. In this latter case *indexPtr still contains valid
419  *	information, in particular TkTextMarkNameToIndex called with the
420  *	"insert" or "current" mark name may return TCL_ERROR, but *indexPtr
421  *	contains the correct index of this mark before -startline or after
422  *	-endline.
423  *
424  * Side effects:
425  *	None.
426  *
427  *--------------------------------------------------------------
428  */
429 
430 int
TkTextMarkNameToIndex(TkText * textPtr,const char * name,TkTextIndex * indexPtr)431 TkTextMarkNameToIndex(
432     TkText *textPtr,		/* Text widget containing mark. */
433     const char *name,		/* Name of mark. */
434     TkTextIndex *indexPtr)	/* Index information gets stored here. */
435 {
436     TkTextSegment *segPtr;
437 
438     if (textPtr == NULL) {
439         return TCL_ERROR;
440     }
441 
442     if (!strcmp(name, "insert")) {
443 	segPtr = textPtr->insertMarkPtr;
444     } else if (!strcmp(name, "current")) {
445 	segPtr = textPtr->currentMarkPtr;
446     } else {
447 	Tcl_HashEntry *hPtr =
448 		Tcl_FindHashEntry(&textPtr->sharedTextPtr->markTable, name);
449 
450 	if (hPtr == NULL) {
451 	    return TCL_ERROR;
452 	}
453 	segPtr = (TkTextSegment *)Tcl_GetHashValue(hPtr);
454     }
455     TkTextMarkSegToIndex(textPtr, segPtr, indexPtr);
456 
457     /*
458      * If indexPtr refers to somewhere outside the -startline/-endline
459      * range limits of the widget, error out since the mark indeed is not
460      * reachable from this text widget (it may be reachable from a peer)
461      * (bug 1630271).
462      */
463 
464     if (TkTextIndexAdjustToStartEnd(textPtr, indexPtr, 1) == TCL_ERROR) {
465 	return TCL_ERROR;
466     }
467 
468     return TCL_OK;
469 }
470 
471 /*
472  *--------------------------------------------------------------
473  *
474  * MarkDeleteProc --
475  *
476  *	This function is invoked by the text B-tree code whenever a mark lies
477  *	in a range of characters being deleted.
478  *
479  * Results:
480  *	Returns 1 to indicate that deletion has been rejected.
481  *
482  * Side effects:
483  *	None (even if the whole tree is being deleted we don't free up the
484  *	mark; it will be done elsewhere).
485  *
486  *--------------------------------------------------------------
487  */
488 
489 static int
MarkDeleteProc(TCL_UNUSED (TkTextSegment *),TCL_UNUSED (TkTextLine *),TCL_UNUSED (int))490 MarkDeleteProc(
491     TCL_UNUSED(TkTextSegment *),	/* Segment being deleted. */
492     TCL_UNUSED(TkTextLine *),	/* Line containing segment. */
493     TCL_UNUSED(int))		/* Non-zero means the entire tree is being
494 				 * deleted, so everything must get cleaned
495 				 * up. */
496 {
497     return 1;
498 }
499 
500 /*
501  *--------------------------------------------------------------
502  *
503  * MarkCleanupProc --
504  *
505  *	This function is invoked by the B-tree code whenever a mark segment is
506  *	moved from one line to another.
507  *
508  * Results:
509  *	None.
510  *
511  * Side effects:
512  *	The linePtr field of the segment gets updated.
513  *
514  *--------------------------------------------------------------
515  */
516 
517 static TkTextSegment *
MarkCleanupProc(TkTextSegment * markPtr,TkTextLine * linePtr)518 MarkCleanupProc(
519     TkTextSegment *markPtr,	/* Mark segment that's being moved. */
520     TkTextLine *linePtr)	/* Line that now contains segment. */
521 {
522     markPtr->body.mark.linePtr = linePtr;
523     return markPtr;
524 }
525 
526 /*
527  *--------------------------------------------------------------
528  *
529  * MarkLayoutProc --
530  *
531  *	This function is the "layoutProc" for mark segments.
532  *
533  * Results:
534  *	If the mark isn't the insertion cursor then the return value is -1 to
535  *	indicate that this segment shouldn't be displayed. If the mark is the
536  *	insertion character then 1 is returned and the chunkPtr structure is
537  *	filled in.
538  *
539  * Side effects:
540  *	None, except for filling in chunkPtr.
541  *
542  *--------------------------------------------------------------
543  */
544 
545 static int
MarkLayoutProc(TkText * textPtr,TCL_UNUSED (TkTextIndex *),TkTextSegment * segPtr,TCL_UNUSED (TkSizeT),TCL_UNUSED (int),TCL_UNUSED (TkSizeT),TCL_UNUSED (int),TCL_UNUSED (TkWrapMode),TkTextDispChunk * chunkPtr)546 MarkLayoutProc(
547     TkText *textPtr,		/* Text widget being layed out. */
548     TCL_UNUSED(TkTextIndex *),	/* Identifies first character in chunk. */
549     TkTextSegment *segPtr,	/* Segment corresponding to indexPtr. */
550     TCL_UNUSED(TkSizeT),		/* Offset within segPtr corresponding to
551 				 * indexPtr (always 0). */
552     TCL_UNUSED(int),			/* Chunk must not occupy pixels at this
553 				 * position or higher. */
554     TCL_UNUSED(TkSizeT),		/* Chunk must not include more than this many
555 				 * characters. */
556     TCL_UNUSED(int),		/* Non-zero means no characters have been
557 				 * assigned to this line yet. */
558     TCL_UNUSED(TkWrapMode),	/* Not used. */
559     TkTextDispChunk *chunkPtr)
560 				/* Structure to fill in with information about
561 				 * this chunk. The x field has already been
562 				 * set by the caller. */
563 {
564     if (segPtr != textPtr->insertMarkPtr) {
565 	return -1;
566     }
567 
568     chunkPtr->displayProc = TkTextInsertDisplayProc;
569     chunkPtr->undisplayProc = InsertUndisplayProc;
570     chunkPtr->measureProc = NULL;
571     chunkPtr->bboxProc = NULL;
572     chunkPtr->numBytes = 0;
573     chunkPtr->minAscent = 0;
574     chunkPtr->minDescent = 0;
575     chunkPtr->minHeight = 0;
576     chunkPtr->width = 0;
577 
578     /*
579      * Note: can't break a line after the insertion cursor: this prevents the
580      * insertion cursor from being stranded at the end of a line.
581      */
582 
583     chunkPtr->breakIndex = -1;
584     chunkPtr->clientData = textPtr;
585     return 1;
586 }
587 
588 /*
589  *--------------------------------------------------------------
590  *
591  * TkTextInsertDisplayProc --
592  *
593  *	This function is called to display the insertion cursor.
594  *
595  * Results:
596  *	None.
597  *
598  * Side effects:
599  *	Graphics are drawn.
600  *
601  *--------------------------------------------------------------
602  */
603 
604 void
TkTextInsertDisplayProc(TkText * textPtr,TCL_UNUSED (TkTextDispChunk *),int x,int y,int height,TCL_UNUSED (int),TCL_UNUSED (Display *),Drawable dst,int screenY)605 TkTextInsertDisplayProc(
606     TkText *textPtr,		/* The current text widget. */
607     TCL_UNUSED(TkTextDispChunk *),	/* Chunk that is to be drawn. */
608     int x,			/* X-position in dst at which to draw this
609 				 * chunk (may differ from the x-position in
610 				 * the chunk because of scrolling). */
611     int y,			/* Y-position at which to draw this chunk in
612 				 * dst (x-position is in the chunk itself). */
613     int height,			/* Total height of line. */
614     TCL_UNUSED(int),		/* Offset of baseline from y. */
615     TCL_UNUSED(Display *),		/* Display to use for drawing. */
616     Drawable dst,		/* Pixmap or window in which to draw chunk. */
617     int screenY)		/* Y-coordinate in text window that
618 				 * corresponds to y. */
619 {
620     /*
621      * We have no need for the clientData.
622      */
623 
624     /* TkText *textPtr = chunkPtr->clientData; */
625     TkTextIndex index;
626     int halfWidth = textPtr->insertWidth/2;
627     int rightSideWidth;
628     int ix = 0, iy = 0, iw = 0, ih = 0, charWidth = 0;
629 
630     if (textPtr->insertCursorType) {
631 	TkTextMarkSegToIndex(textPtr, textPtr->insertMarkPtr, &index);
632 	TkTextIndexBbox(textPtr, &index, &ix, &iy, &iw, &ih, &charWidth);
633 	rightSideWidth = charWidth + halfWidth;
634     } else {
635 	rightSideWidth = halfWidth;
636     }
637 
638     if ((x + rightSideWidth) < 0) {
639 	/*
640 	 * The insertion cursor is off-screen. Indicate caret at 0,0 and
641 	 * return.
642 	 */
643 
644 	Tk_SetCaretPos(textPtr->tkwin, 0, 0, height);
645 	return;
646     }
647 
648     Tk_SetCaretPos(textPtr->tkwin, x - halfWidth, screenY, height);
649 
650     /*
651      * As a special hack to keep the cursor visible on mono displays (or
652      * anywhere else that the selection and insertion cursors have the same
653      * color) write the default background in the cursor area (instead of
654      * nothing) when the cursor isn't on. Otherwise the selection might hide
655      * the cursor.
656      */
657 
658     if (textPtr->flags & GOT_FOCUS) {
659 	if (textPtr->flags & INSERT_ON) {
660 	    Tk_Fill3DRectangle(textPtr->tkwin, dst, textPtr->insertBorder,
661 		    x - halfWidth, y, charWidth + textPtr->insertWidth,
662 		    height, textPtr->insertBorderWidth, TK_RELIEF_RAISED);
663 	} else if (textPtr->selBorder == textPtr->insertBorder) {
664 	    Tk_Fill3DRectangle(textPtr->tkwin, dst, textPtr->border,
665 		    x - halfWidth, y, charWidth + textPtr->insertWidth,
666 		    height, 0, TK_RELIEF_FLAT);
667 	}
668     } else if (textPtr->insertUnfocussed == TK_TEXT_INSERT_NOFOCUS_HOLLOW) {
669 	if (textPtr->insertBorderWidth < 1) {
670 	    /*
671 	     * Hack to work around the fact that a "solid" border always
672 	     * paints in black.
673 	     */
674 
675 	    TkBorder *borderPtr = (TkBorder *) textPtr->insertBorder;
676 
677 	    XDrawRectangle(Tk_Display(textPtr->tkwin), dst, borderPtr->bgGC,
678 		    x - halfWidth, y, charWidth + textPtr->insertWidth - 1,
679 		    height - 1);
680 	} else {
681 	    Tk_Draw3DRectangle(textPtr->tkwin, dst, textPtr->insertBorder,
682 		    x - halfWidth, y, charWidth + textPtr->insertWidth,
683 		    height, textPtr->insertBorderWidth, TK_RELIEF_RAISED);
684 	}
685     } else if (textPtr->insertUnfocussed == TK_TEXT_INSERT_NOFOCUS_SOLID) {
686 	Tk_Fill3DRectangle(textPtr->tkwin, dst, textPtr->insertBorder,
687 		x - halfWidth, y, charWidth + textPtr->insertWidth, height,
688 		textPtr->insertBorderWidth, TK_RELIEF_RAISED);
689     }
690 }
691 
692 /*
693  *--------------------------------------------------------------
694  *
695  * InsertUndisplayProc --
696  *
697  *	This function is called when the insertion cursor is no longer at a
698  *	visible point on the display. It does nothing right now.
699  *
700  * Results:
701  *	None.
702  *
703  * Side effects:
704  *	None.
705  *
706  *--------------------------------------------------------------
707  */
708 
709 static void
InsertUndisplayProc(TCL_UNUSED (TkText *),TCL_UNUSED (TkTextDispChunk *))710 InsertUndisplayProc(
711     TCL_UNUSED(TkText *),		/* Overall information about text widget. */
712     TCL_UNUSED(TkTextDispChunk *))	/* Chunk that is about to be freed. */
713 {
714     return;
715 }
716 
717 /*
718  *--------------------------------------------------------------
719  *
720  * MarkCheckProc --
721  *
722  *	This function is invoked by the B-tree code to perform consistency
723  *	checks on mark segments.
724  *
725  * Results:
726  *	None.
727  *
728  * Side effects:
729  *	The function panics if it detects anything wrong with
730  *	the mark.
731  *
732  *--------------------------------------------------------------
733  */
734 
735 static void
MarkCheckProc(TkTextSegment * markPtr,TkTextLine * linePtr)736 MarkCheckProc(
737     TkTextSegment *markPtr,	/* Segment to check. */
738     TkTextLine *linePtr)	/* Line containing segment. */
739 {
740     Tcl_HashSearch search;
741     Tcl_HashEntry *hPtr;
742 
743     if (markPtr->body.mark.linePtr != linePtr) {
744 	Tcl_Panic("MarkCheckProc: markPtr->body.mark.linePtr bogus");
745     }
746 
747     /*
748      * These two marks are not in the hash table
749      */
750 
751     if (markPtr->body.mark.textPtr->insertMarkPtr == markPtr) {
752         return;
753     }
754     if (markPtr->body.mark.textPtr->currentMarkPtr == markPtr) {
755 	return;
756     }
757 
758     /*
759      * Make sure that the mark is still present in the text's mark hash table.
760      */
761 
762     for (hPtr = Tcl_FirstHashEntry(
763 	    &markPtr->body.mark.textPtr->sharedTextPtr->markTable,
764 	    &search); hPtr != markPtr->body.mark.hPtr;
765 	    hPtr = Tcl_NextHashEntry(&search)) {
766 	if (hPtr == NULL) {
767 	    Tcl_Panic("MarkCheckProc couldn't find hash table entry for mark");
768 	}
769     }
770 }
771 
772 /*
773  *--------------------------------------------------------------
774  *
775  * MarkFindNext --
776  *
777  *	This function searches forward for the next mark.
778  *
779  * Results:
780  *	A standard Tcl result, which is a mark name or an empty string.
781  *
782  * Side effects:
783  *	None.
784  *
785  *--------------------------------------------------------------
786  */
787 
788 static int
MarkFindNext(Tcl_Interp * interp,TkText * textPtr,Tcl_Obj * obj)789 MarkFindNext(
790     Tcl_Interp *interp,		/* For error reporting */
791     TkText *textPtr,		/* The widget */
792     Tcl_Obj *obj)			/* The starting index or mark name */
793 {
794     TkTextIndex index;
795     Tcl_HashEntry *hPtr;
796     TkTextSegment *segPtr;
797     int offset;
798     const char *string = Tcl_GetString(obj);
799 
800     if (!strcmp(string, "insert")) {
801 	segPtr = textPtr->insertMarkPtr;
802 	TkTextMarkSegToIndex(textPtr, segPtr, &index);
803 	segPtr = segPtr->nextPtr;
804     } else if (!strcmp(string, "current")) {
805 	segPtr = textPtr->currentMarkPtr;
806 	TkTextMarkSegToIndex(textPtr, segPtr, &index);
807 	segPtr = segPtr->nextPtr;
808     } else {
809 	hPtr = Tcl_FindHashEntry(&textPtr->sharedTextPtr->markTable, string);
810 	if (hPtr != NULL) {
811 	    /*
812 	     * If given a mark name, return the next mark in the list of
813 	     * segments, even if it happens to be at the same character
814 	     * position.
815 	     */
816 
817 	    segPtr = (TkTextSegment *)Tcl_GetHashValue(hPtr);
818 	    TkTextMarkSegToIndex(textPtr, segPtr, &index);
819 	    segPtr = segPtr->nextPtr;
820 	} else {
821 	    /*
822 	     * For non-mark name indices we want to return any marks that are
823 	     * right at the index.
824 	     */
825 
826 	    if (TkTextGetObjIndex(interp, textPtr, obj, &index) != TCL_OK) {
827 		return TCL_ERROR;
828 	    }
829 	    for (offset = 0, segPtr = index.linePtr->segPtr;
830 		    segPtr != NULL && offset < index.byteIndex;
831 		    offset += segPtr->size, segPtr = segPtr->nextPtr) {
832 		/* Empty loop body */ ;
833 	    }
834 	}
835     }
836 
837     while (1) {
838 	/*
839 	 * segPtr points at the first possible candidate, or NULL if we ran
840 	 * off the end of the line.
841 	 */
842 
843 	for ( ; segPtr != NULL ; segPtr = segPtr->nextPtr) {
844 	    if (segPtr->typePtr == &tkTextRightMarkType ||
845 		    segPtr->typePtr == &tkTextLeftMarkType) {
846 		Tcl_Obj *markName = GetMarkName(textPtr, segPtr);
847 
848 		if (markName != NULL) {
849 		    Tcl_SetObjResult(interp, markName);
850 		    return TCL_OK;
851 		}
852 	    }
853 	}
854 	index.linePtr = TkBTreeNextLine(textPtr, index.linePtr);
855 	if (index.linePtr == NULL) {
856 	    return TCL_OK;
857 	}
858 	index.byteIndex = 0;
859 	segPtr = index.linePtr->segPtr;
860     }
861 }
862 
863 /*
864  *--------------------------------------------------------------
865  *
866  * MarkFindPrev --
867  *
868  *	This function searches backwards for the previous mark.
869  *
870  * Results:
871  *	A standard Tcl result, which is a mark name or an empty string.
872  *
873  * Side effects:
874  *	None.
875  *
876  *--------------------------------------------------------------
877  */
878 
879 static int
MarkFindPrev(Tcl_Interp * interp,TkText * textPtr,Tcl_Obj * obj)880 MarkFindPrev(
881     Tcl_Interp *interp,		/* For error reporting */
882     TkText *textPtr,		/* The widget */
883     Tcl_Obj *obj)			/* The starting index or mark name */
884 {
885     TkTextIndex index;
886     Tcl_HashEntry *hPtr;
887     TkTextSegment *segPtr, *seg2Ptr, *prevPtr;
888     int offset;
889     const char *string = Tcl_GetString(obj);
890 
891     if (!strcmp(string, "insert")) {
892 	segPtr = textPtr->insertMarkPtr;
893 	TkTextMarkSegToIndex(textPtr, segPtr, &index);
894     } else if (!strcmp(string, "current")) {
895 	segPtr = textPtr->currentMarkPtr;
896 	TkTextMarkSegToIndex(textPtr, segPtr, &index);
897     } else {
898 	hPtr = Tcl_FindHashEntry(&textPtr->sharedTextPtr->markTable, string);
899 	if (hPtr != NULL) {
900 	    /*
901 	     * If given a mark name, return the previous mark in the list of
902 	     * segments, even if it happens to be at the same character
903 	     * position.
904 	     */
905 
906 	    segPtr = (TkTextSegment *)Tcl_GetHashValue(hPtr);
907 	    TkTextMarkSegToIndex(textPtr, segPtr, &index);
908 	} else {
909 	    /*
910 	     * For non-mark name indices we do not return any marks that are
911 	     * right at the index.
912 	     */
913 
914 	    if (TkTextGetObjIndex(interp, textPtr, obj, &index) != TCL_OK) {
915 		return TCL_ERROR;
916 	    }
917 	    for (offset = 0, segPtr = index.linePtr->segPtr;
918 		    segPtr != NULL && offset < index.byteIndex;
919 		    offset += segPtr->size, segPtr = segPtr->nextPtr) {
920 		/* Empty loop body */
921 	    }
922 	}
923     }
924 
925     while (1) {
926 	/*
927 	 * segPtr points just past the first possible candidate, or at the
928 	 * beginning of the line.
929 	 */
930 
931 	for (prevPtr = NULL, seg2Ptr = index.linePtr->segPtr;
932 		seg2Ptr != NULL && seg2Ptr != segPtr;
933 		seg2Ptr = seg2Ptr->nextPtr) {
934 	    if (seg2Ptr->typePtr == &tkTextRightMarkType ||
935 		    seg2Ptr->typePtr == &tkTextLeftMarkType) {
936 	        if (seg2Ptr->body.mark.hPtr == NULL) {
937                     if (seg2Ptr != textPtr->currentMarkPtr &&
938                             seg2Ptr != textPtr->insertMarkPtr) {
939 	                /*
940                          * This is an insert or current mark from a
941                          * peer of textPtr.
942                          */
943                         continue;
944                     }
945 	        }
946 		prevPtr = seg2Ptr;
947 	    }
948 	}
949 	if (prevPtr != NULL) {
950 	    Tcl_Obj *markName = GetMarkName(textPtr, prevPtr);
951 
952 	    if (markName != NULL) {
953 		Tcl_SetObjResult(interp, markName);
954 		return TCL_OK;
955 	    }
956 	}
957 	index.linePtr = TkBTreePreviousLine(textPtr, index.linePtr);
958 	if (index.linePtr == NULL) {
959 	    return TCL_OK;
960 	}
961 	segPtr = NULL;
962     }
963 }
964 
965 /*
966  * ------------------------------------------------------------------------
967  *
968  * GetMarkName --
969  *	Returns the name of the mark that is the given text segment, or NULL
970  *	if it is unnamed (i.e., a widget-specific mark that isn't "current" or
971  *	"insert").
972  *
973  * ------------------------------------------------------------------------
974  */
975 
976 static Tcl_Obj *
GetMarkName(TkText * textPtr,TkTextSegment * segPtr)977 GetMarkName(
978     TkText *textPtr,
979     TkTextSegment *segPtr)
980 {
981     const char *markName;
982 
983     if (segPtr == textPtr->currentMarkPtr) {
984 	markName = "current";
985     } else if (segPtr == textPtr->insertMarkPtr) {
986 	markName = "insert";
987     } else if (segPtr->body.mark.hPtr == NULL) {
988 	/*
989 	 * Ignore widget-specific marks for the other widgets. This is either
990 	 * an insert or a current mark (markPtr->body.mark.hPtr actually
991 	 * receives NULL for these marks in TkTextSetMark). The insert and
992 	 * current marks for textPtr having already been tested above, the
993 	 * current segment is an insert or current mark from a peer of
994 	 * textPtr, which we don't want to return.
995 	 */
996 
997 	return NULL;
998     } else {
999 	markName = (const char *)Tcl_GetHashKey(&textPtr->sharedTextPtr->markTable,
1000 		segPtr->body.mark.hPtr);
1001     }
1002     return Tcl_NewStringObj(markName, -1);
1003 }
1004 
1005 /*
1006  * Local Variables:
1007  * mode: c
1008  * c-basic-offset: 4
1009  * fill-column: 78
1010  * End:
1011  */
1012