4 * This file contains the procedure that implement marks for
7 * Copyright (c) 1994 The Regents of the University of California.
8 * Copyright (c) 1994-1997 Sun Microsystems, Inc.
10 * See the file "license.terms" for information on usage and redistribution
11 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
21 * Macro that determines the size of a mark segment:
24 #define MSEG_SIZE ((unsigned) (Tk_Offset(TkTextSegment, body) \
25 + sizeof(TkTextMark)))
28 * Forward references for procedures defined in this file:
31 static void InsertUndisplayProc _ANSI_ARGS_((TkText *textPtr,
32 TkTextDispChunk *chunkPtr));
33 static int MarkDeleteProc _ANSI_ARGS_((TkTextSegment *segPtr,
34 TkTextLine *linePtr, int treeGone));
35 static TkTextSegment * MarkCleanupProc _ANSI_ARGS_((TkTextSegment *segPtr,
36 TkTextLine *linePtr));
37 static void MarkCheckProc _ANSI_ARGS_((TkTextSegment *segPtr,
38 TkTextLine *linePtr));
39 static int MarkLayoutProc _ANSI_ARGS_((TkText *textPtr,
40 TkTextIndex *indexPtr, TkTextSegment *segPtr,
41 int offset, int maxX, int maxChars,
42 int noCharsYet, TkWrapMode wrapMode,
43 TkTextDispChunk *chunkPtr));
44 static int MarkFindNext _ANSI_ARGS_((Tcl_Interp *interp,
45 TkText *textPtr, CONST char *markName));
46 static int MarkFindPrev _ANSI_ARGS_((Tcl_Interp *interp,
47 TkText *textPtr, CONST char *markName));
51 * The following structures declare the "mark" segment types.
52 * There are actually two types for marks, one with left gravity
53 * and one with right gravity. They are identical except for
54 * their gravity property.
57 Tk_SegType tkTextRightMarkType = {
60 (Tk_SegSplitProc *) NULL, /* splitProc */
61 MarkDeleteProc, /* deleteProc */
62 MarkCleanupProc, /* cleanupProc */
63 (Tk_SegLineChangeProc *) NULL, /* lineChangeProc */
64 MarkLayoutProc, /* layoutProc */
65 MarkCheckProc /* checkProc */
68 Tk_SegType tkTextLeftMarkType = {
71 (Tk_SegSplitProc *) NULL, /* splitProc */
72 MarkDeleteProc, /* deleteProc */
73 MarkCleanupProc, /* cleanupProc */
74 (Tk_SegLineChangeProc *) NULL, /* lineChangeProc */
75 MarkLayoutProc, /* layoutProc */
76 MarkCheckProc /* checkProc */
80 *--------------------------------------------------------------
84 * This procedure is invoked to process the "mark" options of
85 * the widget command for text widgets. See the user documentation
86 * for details on what it does.
89 * A standard Tcl result.
92 * See the user documentation.
94 *--------------------------------------------------------------
98 TkTextMarkCmd(textPtr, interp, argc, argv)
99 register TkText *textPtr; /* Information about text widget. */
100 Tcl_Interp *interp; /* Current interpreter. */
101 int argc; /* Number of arguments. */
102 CONST char **argv; /* Argument strings. Someone else has already
103 * parsed this command enough to know that
104 * argv[1] is "mark". */
109 TkTextSegment *markPtr;
110 Tcl_HashSearch search;
112 Tk_SegType *newTypePtr;
115 Tcl_AppendResult(interp, "wrong # args: should be \"",
116 argv[0], " mark option ?arg arg ...?\"", (char *) NULL);
120 length = strlen(argv[2]);
121 if ((c == 'g') && (strncmp(argv[2], "gravity", length) == 0)) {
122 if (argc < 4 || argc > 5) {
123 Tcl_AppendResult(interp, "wrong # args: should be \"",
124 argv[0], " mark gravity markName ?gravity?\"",
128 hPtr = Tcl_FindHashEntry(&textPtr->markTable, argv[3]);
130 Tcl_AppendResult(interp, "there is no mark named \"",
131 argv[3], "\"", (char *) NULL);
134 markPtr = (TkTextSegment *) Tcl_GetHashValue(hPtr);
136 if (markPtr->typePtr == &tkTextRightMarkType) {
137 Tcl_SetResult(interp, "right", TCL_STATIC);
139 Tcl_SetResult(interp, "left", TCL_STATIC);
143 length = strlen(argv[4]);
145 if ((c == 'l') && (strncmp(argv[4], "left", length) == 0)) {
146 newTypePtr = &tkTextLeftMarkType;
147 } else if ((c == 'r') && (strncmp(argv[4], "right", length) == 0)) {
148 newTypePtr = &tkTextRightMarkType;
150 Tcl_AppendResult(interp, "bad mark gravity \"",
151 argv[4], "\": must be left or right", (char *) NULL);
154 TkTextMarkSegToIndex(textPtr, markPtr, &index);
155 TkBTreeUnlinkSegment(textPtr->tree, markPtr,
156 markPtr->body.mark.linePtr);
157 markPtr->typePtr = newTypePtr;
158 TkBTreeLinkSegment(markPtr, &index);
159 } else if ((c == 'n') && (strncmp(argv[2], "names", length) == 0)) {
161 Tcl_AppendResult(interp, "wrong # args: should be \"",
162 argv[0], " mark names\"", (char *) NULL);
165 for (hPtr = Tcl_FirstHashEntry(&textPtr->markTable, &search);
166 hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) {
167 Tcl_AppendElement(interp,
168 Tcl_GetHashKey(&textPtr->markTable, hPtr));
170 } else if ((c == 'n') && (strncmp(argv[2], "next", length) == 0)) {
172 Tcl_AppendResult(interp, "wrong # args: should be \"",
173 argv[0], " mark next index\"", (char *) NULL);
176 return MarkFindNext(interp, textPtr, argv[3]);
177 } else if ((c == 'p') && (strncmp(argv[2], "previous", length) == 0)) {
179 Tcl_AppendResult(interp, "wrong # args: should be \"",
180 argv[0], " mark previous index\"", (char *) NULL);
183 return MarkFindPrev(interp, textPtr, argv[3]);
184 } else if ((c == 's') && (strncmp(argv[2], "set", length) == 0)) {
186 Tcl_AppendResult(interp, "wrong # args: should be \"",
187 argv[0], " mark set markName index\"", (char *) NULL);
190 if (TkTextGetIndex(interp, textPtr, argv[4], &index) != TCL_OK) {
193 TkTextSetMark(textPtr, argv[3], &index);
194 } else if ((c == 'u') && (strncmp(argv[2], "unset", length) == 0)) {
195 for (i = 3; i < argc; i++) {
196 hPtr = Tcl_FindHashEntry(&textPtr->markTable, argv[i]);
198 markPtr = (TkTextSegment *) Tcl_GetHashValue(hPtr);
199 if ((markPtr == textPtr->insertMarkPtr)
200 || (markPtr == textPtr->currentMarkPtr)) {
203 TkBTreeUnlinkSegment(textPtr->tree, markPtr,
204 markPtr->body.mark.linePtr);
205 Tcl_DeleteHashEntry(hPtr);
206 ckfree((char *) markPtr);
210 Tcl_AppendResult(interp, "bad mark option \"", argv[2],
211 "\": must be gravity, names, next, previous, set, or unset",
219 *----------------------------------------------------------------------
223 * Set a mark to a particular position, creating a new mark if
224 * one doesn't already exist.
227 * The return value is a pointer to the mark that was just set.
230 * A new mark is created, or an existing mark is moved.
232 *----------------------------------------------------------------------
236 TkTextSetMark(textPtr, name, indexPtr)
237 TkText *textPtr; /* Text widget in which to create mark. */
238 CONST char *name; /* Name of mark to set. */
239 TkTextIndex *indexPtr; /* Where to set mark. */
242 TkTextSegment *markPtr;
243 TkTextIndex insertIndex;
246 hPtr = Tcl_CreateHashEntry(&textPtr->markTable, name, &new);
247 markPtr = (TkTextSegment *) Tcl_GetHashValue(hPtr);
250 * If this is the insertion point that's being moved, be sure
251 * to force a display update at the old position. Also, don't
252 * let the insertion cursor be after the final newline of the
256 if (markPtr == textPtr->insertMarkPtr) {
257 TkTextIndex index, index2;
258 TkTextMarkSegToIndex(textPtr, textPtr->insertMarkPtr, &index);
259 TkTextIndexForwChars(&index, 1, &index2);
260 TkTextChanged(textPtr, &index, &index2);
261 if (TkBTreeLineIndex(indexPtr->linePtr)
262 == TkBTreeNumLines(textPtr->tree)) {
263 TkTextIndexBackChars(indexPtr, 1, &insertIndex);
264 indexPtr = &insertIndex;
267 TkBTreeUnlinkSegment(textPtr->tree, markPtr,
268 markPtr->body.mark.linePtr);
270 markPtr = (TkTextSegment *) ckalloc(MSEG_SIZE);
271 markPtr->typePtr = &tkTextRightMarkType;
273 markPtr->body.mark.textPtr = textPtr;
274 markPtr->body.mark.linePtr = indexPtr->linePtr;
275 markPtr->body.mark.hPtr = hPtr;
276 Tcl_SetHashValue(hPtr, markPtr);
278 TkBTreeLinkSegment(markPtr, indexPtr);
281 * If the mark is the insertion cursor, then update the screen at the
282 * mark's new location.
285 if (markPtr == textPtr->insertMarkPtr) {
288 TkTextIndexForwChars(indexPtr, 1, &index2);
289 TkTextChanged(textPtr, indexPtr, &index2);
295 *--------------------------------------------------------------
297 * TkTextMarkSegToIndex --
299 * Given a segment that is a mark, create an index that
300 * refers to the next text character (or other text segment
301 * with non-zero size) after the mark.
304 * *IndexPtr is filled in with index information.
309 *--------------------------------------------------------------
313 TkTextMarkSegToIndex(textPtr, markPtr, indexPtr)
314 TkText *textPtr; /* Text widget containing mark. */
315 TkTextSegment *markPtr; /* Mark segment. */
316 TkTextIndex *indexPtr; /* Index information gets stored here. */
318 TkTextSegment *segPtr;
320 indexPtr->tree = textPtr->tree;
321 indexPtr->linePtr = markPtr->body.mark.linePtr;
322 indexPtr->byteIndex = 0;
323 for (segPtr = indexPtr->linePtr->segPtr; segPtr != markPtr;
324 segPtr = segPtr->nextPtr) {
325 indexPtr->byteIndex += segPtr->size;
330 *--------------------------------------------------------------
332 * TkTextMarkNameToIndex --
334 * Given the name of a mark, return an index corresponding
338 * The return value is TCL_OK if "name" exists as a mark in
339 * the text widget. In this case *indexPtr is filled in with
340 * the next segment whose after the mark whose size is
341 * non-zero. TCL_ERROR is returned if the mark doesn't exist
342 * in the text widget.
347 *--------------------------------------------------------------
351 TkTextMarkNameToIndex(textPtr, name, indexPtr)
352 TkText *textPtr; /* Text widget containing mark. */
353 CONST char *name; /* Name of mark. */
354 TkTextIndex *indexPtr; /* Index information gets stored here. */
358 hPtr = Tcl_FindHashEntry(&textPtr->markTable, name);
362 TkTextMarkSegToIndex(textPtr, (TkTextSegment *) Tcl_GetHashValue(hPtr),
368 *--------------------------------------------------------------
372 * This procedure is invoked by the text B-tree code whenever
373 * a mark lies in a range of characters being deleted.
376 * Returns 1 to indicate that deletion has been rejected.
379 * None (even if the whole tree is being deleted we don't
380 * free up the mark; it will be done elsewhere).
382 *--------------------------------------------------------------
387 MarkDeleteProc(segPtr, linePtr, treeGone)
388 TkTextSegment *segPtr; /* Segment being deleted. */
389 TkTextLine *linePtr; /* Line containing segment. */
390 int treeGone; /* Non-zero means the entire tree is
391 * being deleted, so everything must
398 *--------------------------------------------------------------
402 * This procedure is invoked by the B-tree code whenever a
403 * mark segment is moved from one line to another.
409 * The linePtr field of the segment gets updated.
411 *--------------------------------------------------------------
414 static TkTextSegment *
415 MarkCleanupProc(markPtr, linePtr)
416 TkTextSegment *markPtr; /* Mark segment that's being moved. */
417 TkTextLine *linePtr; /* Line that now contains segment. */
419 markPtr->body.mark.linePtr = linePtr;
424 *--------------------------------------------------------------
428 * This procedure is the "layoutProc" for mark segments.
431 * If the mark isn't the insertion cursor then the return
432 * value is -1 to indicate that this segment shouldn't be
433 * displayed. If the mark is the insertion character then
434 * 1 is returned and the chunkPtr structure is filled in.
437 * None, except for filling in chunkPtr.
439 *--------------------------------------------------------------
444 MarkLayoutProc(textPtr, indexPtr, segPtr, offset, maxX, maxChars,
445 noCharsYet, wrapMode, chunkPtr)
446 TkText *textPtr; /* Text widget being layed out. */
447 TkTextIndex *indexPtr; /* Identifies first character in chunk. */
448 TkTextSegment *segPtr; /* Segment corresponding to indexPtr. */
449 int offset; /* Offset within segPtr corresponding to
450 * indexPtr (always 0). */
451 int maxX; /* Chunk must not occupy pixels at this
452 * position or higher. */
453 int maxChars; /* Chunk must not include more than this
454 * many characters. */
455 int noCharsYet; /* Non-zero means no characters have been
456 * assigned to this line yet. */
457 TkWrapMode wrapMode; /* Not used. */
458 register TkTextDispChunk *chunkPtr;
459 /* Structure to fill in with information
460 * about this chunk. The x field has already
461 * been set by the caller. */
463 if (segPtr != textPtr->insertMarkPtr) {
467 chunkPtr->displayProc = TkTextInsertDisplayProc;
468 chunkPtr->undisplayProc = InsertUndisplayProc;
469 chunkPtr->measureProc = (Tk_ChunkMeasureProc *) NULL;
470 chunkPtr->bboxProc = (Tk_ChunkBboxProc *) NULL;
471 chunkPtr->numBytes = 0;
472 chunkPtr->minAscent = 0;
473 chunkPtr->minDescent = 0;
474 chunkPtr->minHeight = 0;
478 * Note: can't break a line after the insertion cursor: this
479 * prevents the insertion cursor from being stranded at the end
483 chunkPtr->breakIndex = -1;
484 chunkPtr->clientData = (ClientData) textPtr;
489 *--------------------------------------------------------------
491 * TkTextInsertDisplayProc --
493 * This procedure is called to display the insertion
500 * Graphics are drawn.
502 *--------------------------------------------------------------
507 TkTextInsertDisplayProc(chunkPtr, x, y, height, baseline, display, dst, screenY)
508 TkTextDispChunk *chunkPtr; /* Chunk that is to be drawn. */
509 int x; /* X-position in dst at which to
510 * draw this chunk (may differ from
511 * the x-position in the chunk because
513 int y; /* Y-position at which to draw this
514 * chunk in dst (x-position is in
515 * the chunk itself). */
516 int height; /* Total height of line. */
517 int baseline; /* Offset of baseline from y. */
518 Display *display; /* Display to use for drawing. */
519 Drawable dst; /* Pixmap or window in which to draw
521 int screenY; /* Y-coordinate in text window that
522 * corresponds to y. */
524 TkText *textPtr = (TkText *) chunkPtr->clientData;
525 int halfWidth = textPtr->insertWidth/2;
527 if ((x + halfWidth) < 0) {
529 * The insertion cursor is off-screen.
530 * Indicate caret at 0,0 and return.
533 Tk_SetCaretPos(textPtr->tkwin, 0, 0, height);
537 Tk_SetCaretPos(textPtr->tkwin, x - halfWidth, screenY, height);
540 * As a special hack to keep the cursor visible on mono displays
541 * (or anywhere else that the selection and insertion cursors
542 * have the same color) write the default background in the cursor
543 * area (instead of nothing) when the cursor isn't on. Otherwise
544 * the selection might hide the cursor.
547 if (textPtr->flags & INSERT_ON) {
548 Tk_Fill3DRectangle(textPtr->tkwin, dst, textPtr->insertBorder,
549 x - halfWidth, y, textPtr->insertWidth, height,
550 textPtr->insertBorderWidth, TK_RELIEF_RAISED);
551 } else if (textPtr->selBorder == textPtr->insertBorder) {
552 Tk_Fill3DRectangle(textPtr->tkwin, dst, textPtr->border,
553 x - halfWidth, y, textPtr->insertWidth, height,
559 *--------------------------------------------------------------
561 * InsertUndisplayProc --
563 * This procedure is called when the insertion cursor is no
564 * longer at a visible point on the display. It does nothing
573 *--------------------------------------------------------------
578 InsertUndisplayProc(textPtr, chunkPtr)
579 TkText *textPtr; /* Overall information about text
581 TkTextDispChunk *chunkPtr; /* Chunk that is about to be freed. */
587 *--------------------------------------------------------------
591 * This procedure is invoked by the B-tree code to perform
592 * consistency checks on mark segments.
598 * The procedure panics if it detects anything wrong with
601 *--------------------------------------------------------------
605 MarkCheckProc(markPtr, linePtr)
606 TkTextSegment *markPtr; /* Segment to check. */
607 TkTextLine *linePtr; /* Line containing segment. */
609 Tcl_HashSearch search;
612 if (markPtr->body.mark.linePtr != linePtr) {
613 panic("MarkCheckProc: markPtr->body.mark.linePtr bogus");
617 * Make sure that the mark is still present in the text's mark
621 for (hPtr = Tcl_FirstHashEntry(&markPtr->body.mark.textPtr->markTable,
622 &search); hPtr != markPtr->body.mark.hPtr;
623 hPtr = Tcl_NextHashEntry(&search)) {
625 panic("MarkCheckProc couldn't find hash table entry for mark");
631 *--------------------------------------------------------------
635 * This procedure searches forward for the next mark.
638 * A standard Tcl result, which is a mark name or an empty string.
643 *--------------------------------------------------------------
647 MarkFindNext(interp, textPtr, string)
648 Tcl_Interp *interp; /* For error reporting */
649 TkText *textPtr; /* The widget */
650 CONST char *string; /* The starting index or mark name */
654 register TkTextSegment *segPtr;
658 hPtr = Tcl_FindHashEntry(&textPtr->markTable, string);
661 * If given a mark name, return the next mark in the list of
662 * segments, even if it happens to be at the same character position.
664 segPtr = (TkTextSegment *) Tcl_GetHashValue(hPtr);
665 TkTextMarkSegToIndex(textPtr, segPtr, &index);
666 segPtr = segPtr->nextPtr;
669 * For non-mark name indices we want to return any marks that
670 * are right at the index.
672 if (TkTextGetIndex(interp, textPtr, string, &index) != TCL_OK) {
675 for (offset = 0, segPtr = index.linePtr->segPtr;
676 segPtr != NULL && offset < index.byteIndex;
677 offset += segPtr->size, segPtr = segPtr->nextPtr) {
678 /* Empty loop body */ ;
683 * segPtr points at the first possible candidate,
684 * or NULL if we ran off the end of the line.
686 for ( ; segPtr != NULL ; segPtr = segPtr->nextPtr) {
687 if (segPtr->typePtr == &tkTextRightMarkType ||
688 segPtr->typePtr == &tkTextLeftMarkType) {
689 Tcl_SetResult(interp,
690 Tcl_GetHashKey(&textPtr->markTable, segPtr->body.mark.hPtr),
695 index.linePtr = TkBTreeNextLine(index.linePtr);
696 if (index.linePtr == (TkTextLine *) NULL) {
700 segPtr = index.linePtr->segPtr;
705 *--------------------------------------------------------------
709 * This procedure searches backwards for the previous mark.
712 * A standard Tcl result, which is a mark name or an empty string.
717 *--------------------------------------------------------------
721 MarkFindPrev(interp, textPtr, string)
722 Tcl_Interp *interp; /* For error reporting */
723 TkText *textPtr; /* The widget */
724 CONST char *string; /* The starting index or mark name */
728 register TkTextSegment *segPtr, *seg2Ptr, *prevPtr;
732 hPtr = Tcl_FindHashEntry(&textPtr->markTable, string);
735 * If given a mark name, return the previous mark in the list of
736 * segments, even if it happens to be at the same character position.
738 segPtr = (TkTextSegment *) Tcl_GetHashValue(hPtr);
739 TkTextMarkSegToIndex(textPtr, segPtr, &index);
742 * For non-mark name indices we do not return any marks that
743 * are right at the index.
745 if (TkTextGetIndex(interp, textPtr, string, &index) != TCL_OK) {
748 for (offset = 0, segPtr = index.linePtr->segPtr;
749 segPtr != NULL && offset < index.byteIndex;
750 offset += segPtr->size, segPtr = segPtr->nextPtr) {
751 /* Empty loop body */ ;
756 * segPtr points just past the first possible candidate,
757 * or at the begining of the line.
759 for (prevPtr = NULL, seg2Ptr = index.linePtr->segPtr;
760 seg2Ptr != NULL && seg2Ptr != segPtr;
761 seg2Ptr = seg2Ptr->nextPtr) {
762 if (seg2Ptr->typePtr == &tkTextRightMarkType ||
763 seg2Ptr->typePtr == &tkTextLeftMarkType) {
767 if (prevPtr != NULL) {
768 Tcl_SetResult(interp,
769 Tcl_GetHashKey(&textPtr->markTable, prevPtr->body.mark.hPtr),
773 index.linePtr = TkBTreePreviousLine(index.linePtr);
774 if (index.linePtr == (TkTextLine *) NULL) {