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