1 /*
2 * tkTextMark.c --
3 *
4 * This file contains the procedure that implement marks for
5 * text widgets.
6 *
7 * Copyright (c) 1994 The Regents of the University of California.
8 * Copyright (c) 1994-1995 Sun Microsystems, Inc.
9 *
10 * See the file "license.terms" for information on usage and redistribution
11 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
12 *
13 * SCCS: @(#) tkTextMark.c 1.15 96/02/15 18:52:59
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 procedures defined in this file:
28 */
29
30 static void InsertUndisplayProc _ANSI_ARGS_((TkText *textPtr,
31 TkTextDispChunk *chunkPtr));
32 static int MarkDeleteProc _ANSI_ARGS_((TkTextSegment *segPtr,
33 TkTextLine *linePtr, int treeGone));
34 static TkTextSegment * MarkCleanupProc _ANSI_ARGS_((TkTextSegment *segPtr,
35 TkTextLine *linePtr));
36 static void MarkCheckProc _ANSI_ARGS_((TkTextSegment *segPtr,
37 TkTextLine *linePtr));
38 static int MarkLayoutProc _ANSI_ARGS_((TkText *textPtr,
39 TkTextIndex *indexPtr, TkTextSegment *segPtr,
40 int offset, int maxX, int maxChars,
41 int noCharsYet, Tk_Uid wrapMode,
42 TkTextDispChunk *chunkPtr));
43 static int MarkFindNext _ANSI_ARGS_((Tcl_Interp *interp,
44 TkText *textPtr, char *markName));
45 static int MarkFindPrev _ANSI_ARGS_((Tcl_Interp *interp,
46 TkText *textPtr, char *markName));
47
48
49 /*
50 * The following structures declare the "mark" segment types.
51 * There are actually two types for marks, one with left gravity
52 * and one with right gravity. They are identical except for
53 * their gravity property.
54 */
55
56 Tk_SegType tkTextRightMarkType = {
57 "mark", /* name */
58 0, /* leftGravity */
59 (Tk_SegSplitProc *) NULL, /* splitProc */
60 MarkDeleteProc, /* deleteProc */
61 MarkCleanupProc, /* cleanupProc */
62 (Tk_SegLineChangeProc *) NULL, /* lineChangeProc */
63 MarkLayoutProc, /* layoutProc */
64 MarkCheckProc /* checkProc */
65 };
66
67 Tk_SegType tkTextLeftMarkType = {
68 "mark", /* name */
69 1, /* leftGravity */
70 (Tk_SegSplitProc *) NULL, /* splitProc */
71 MarkDeleteProc, /* deleteProc */
72 MarkCleanupProc, /* cleanupProc */
73 (Tk_SegLineChangeProc *) NULL, /* lineChangeProc */
74 MarkLayoutProc, /* layoutProc */
75 MarkCheckProc /* checkProc */
76 };
77
78 /*
79 *--------------------------------------------------------------
80 *
81 * TkTextMarkCmd --
82 *
83 * This procedure is invoked to process the "mark" options of
84 * the widget command for text widgets. See the user documentation
85 * for details on what it does.
86 *
87 * Results:
88 * A standard Tcl result.
89 *
90 * Side effects:
91 * See the user documentation.
92 *
93 *--------------------------------------------------------------
94 */
95
96 int
TkTextMarkCmd(textPtr,interp,argc,argv)97 TkTextMarkCmd(textPtr, interp, argc, argv)
98 register TkText *textPtr; /* Information about text widget. */
99 Tcl_Interp *interp; /* Current interpreter. */
100 int argc; /* Number of arguments. */
101 char **argv; /* Argument strings. Someone else has already
102 * parsed this command enough to know that
103 * argv[1] is "mark". */
104 {
105 int c, i;
106 size_t length;
107 Tcl_HashEntry *hPtr;
108 TkTextSegment *markPtr;
109 Tcl_HashSearch search;
110 TkTextIndex index;
111 Tk_SegType *newTypePtr;
112
113 if (argc < 3) {
114 Tcl_AppendResult(interp, "wrong # args: should be \"",
115 argv[0], " mark option ?arg arg ...?\"", (char *) NULL);
116 return TCL_ERROR;
117 }
118 c = argv[2][0];
119 length = strlen(argv[2]);
120 if ((c == 'g') && (strncmp(argv[2], "gravity", length) == 0)) {
121 if (argc > 5) {
122 Tcl_AppendResult(interp, "wrong # args: should be \"",
123 argv[0], " mark gravity markName ?gravity?",
124 (char *) NULL);
125 return TCL_ERROR;
126 }
127 hPtr = Tcl_FindHashEntry(&textPtr->markTable, argv[3]);
128 if (hPtr == NULL) {
129 Tcl_AppendResult(interp, "there is no mark named \"",
130 argv[3], "\"", (char *) NULL);
131 return TCL_ERROR;
132 }
133 markPtr = (TkTextSegment *) Tcl_GetHashValue(hPtr);
134 if (argc == 4) {
135 if (markPtr->typePtr == &tkTextRightMarkType) {
136 interp->result = "right";
137 } else {
138 interp->result = "left";
139 }
140 return TCL_OK;
141 }
142 length = strlen(argv[4]);
143 c = argv[4][0];
144 if ((c == 'l') && (strncmp(argv[4], "left", length) == 0)) {
145 newTypePtr = &tkTextLeftMarkType;
146 } else if ((c == 'r') && (strncmp(argv[4], "right", length) == 0)) {
147 newTypePtr = &tkTextRightMarkType;
148 } else {
149 Tcl_AppendResult(interp, "bad mark gravity \"",
150 argv[4], "\": must be left or right", (char *) NULL);
151 return TCL_ERROR;
152 }
153 TkTextMarkSegToIndex(textPtr, markPtr, &index);
154 TkBTreeUnlinkSegment(textPtr->tree, markPtr,
155 markPtr->body.mark.linePtr);
156 markPtr->typePtr = newTypePtr;
157 TkBTreeLinkSegment(markPtr, &index);
158 } else if ((c == 'n') && (strncmp(argv[2], "names", length) == 0)) {
159 if (argc != 3) {
160 Tcl_AppendResult(interp, "wrong # args: should be \"",
161 argv[0], " mark names\"", (char *) NULL);
162 return TCL_ERROR;
163 }
164 for (hPtr = Tcl_FirstHashEntry(&textPtr->markTable, &search);
165 hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) {
166 Tcl_AppendElement(interp,
167 Tcl_GetHashKey(&textPtr->markTable, hPtr));
168 }
169 } else if ((c == 'n') && (strncmp(argv[2], "next", length) == 0)) {
170 if (argc != 4) {
171 Tcl_AppendResult(interp, "wrong # args: should be \"",
172 argv[0], " mark next index\"", (char *) NULL);
173 return TCL_ERROR;
174 }
175 return MarkFindNext(interp, textPtr, argv[3]);
176 } else if ((c == 'p') && (strncmp(argv[2], "previous", length) == 0)) {
177 if (argc != 4) {
178 Tcl_AppendResult(interp, "wrong # args: should be \"",
179 argv[0], " mark previous index\"", (char *) NULL);
180 return TCL_ERROR;
181 }
182 return MarkFindPrev(interp, textPtr, argv[3]);
183 } else if ((c == 's') && (strncmp(argv[2], "set", length) == 0)) {
184 if (argc != 5) {
185 Tcl_AppendResult(interp, "wrong # args: should be \"",
186 argv[0], " mark set markName index\"", (char *) NULL);
187 return TCL_ERROR;
188 }
189 if (TkTextGetIndex(interp, textPtr, argv[4], &index) != TCL_OK) {
190 return TCL_ERROR;
191 }
192 TkTextSetMark(textPtr, argv[3], &index);
193 } else if ((c == 'u') && (strncmp(argv[2], "unset", length) == 0)) {
194 for (i = 3; i < argc; i++) {
195 hPtr = Tcl_FindHashEntry(&textPtr->markTable, argv[i]);
196 if (hPtr != NULL) {
197 markPtr = (TkTextSegment *) Tcl_GetHashValue(hPtr);
198 if ((markPtr == textPtr->insertMarkPtr)
199 || (markPtr == textPtr->currentMarkPtr)) {
200 continue;
201 }
202 TkBTreeUnlinkSegment(textPtr->tree, markPtr,
203 markPtr->body.mark.linePtr);
204 Tcl_DeleteHashEntry(hPtr);
205 ckfree((char *) markPtr);
206 }
207 }
208 } else {
209 Tcl_AppendResult(interp, "bad mark option \"", argv[2],
210 "\": must be gravity, names, next, previous, set, or unset",
211 (char *) NULL);
212 return TCL_ERROR;
213 }
214 return TCL_OK;
215 }
216
217 /*
218 *----------------------------------------------------------------------
219 *
220 * TkTextSetMark --
221 *
222 * Set a mark to a particular position, creating a new mark if
223 * one doesn't already exist.
224 *
225 * Results:
226 * The return value is a pointer to the mark that was just set.
227 *
228 * Side effects:
229 * A new mark is created, or an existing mark is moved.
230 *
231 *----------------------------------------------------------------------
232 */
233
234 TkTextSegment *
TkTextSetMark(textPtr,name,indexPtr)235 TkTextSetMark(textPtr, name, indexPtr)
236 TkText *textPtr; /* Text widget in which to create mark. */
237 char *name; /* Name of mark to set. */
238 TkTextIndex *indexPtr; /* Where to set mark. */
239 {
240 Tcl_HashEntry *hPtr;
241 TkTextSegment *markPtr;
242 TkTextIndex insertIndex;
243 int new;
244
245 hPtr = Tcl_CreateHashEntry(&textPtr->markTable, name, &new);
246 markPtr = (TkTextSegment *) Tcl_GetHashValue(hPtr);
247 if (!new) {
248 /*
249 * If this is the insertion point that's being moved, be sure
250 * to force a display update at the old position. Also, don't
251 * let the insertion cursor be after the final newline of the
252 * file.
253 */
254
255 if (markPtr == textPtr->insertMarkPtr) {
256 TkTextIndex index, index2;
257 TkTextMarkSegToIndex(textPtr, textPtr->insertMarkPtr, &index);
258 TkTextIndexForwChars(&index, 1, &index2);
259 TkTextChanged(textPtr, &index, &index2);
260 if (TkBTreeLineIndex(indexPtr->linePtr)
261 == TkBTreeNumLines(textPtr->tree)) {
262 TkTextIndexBackChars(indexPtr, 1, &insertIndex);
263 indexPtr = &insertIndex;
264 }
265 }
266 TkBTreeUnlinkSegment(textPtr->tree, markPtr,
267 markPtr->body.mark.linePtr);
268 } else {
269 markPtr = (TkTextSegment *) ckalloc(MSEG_SIZE);
270 markPtr->typePtr = &tkTextRightMarkType;
271 markPtr->size = 0;
272 markPtr->body.mark.textPtr = textPtr;
273 markPtr->body.mark.linePtr = indexPtr->linePtr;
274 markPtr->body.mark.hPtr = hPtr;
275 Tcl_SetHashValue(hPtr, markPtr);
276 }
277 TkBTreeLinkSegment(markPtr, indexPtr);
278
279 /*
280 * If the mark is the insertion cursor, then update the screen at the
281 * mark's new location.
282 */
283
284 if (markPtr == textPtr->insertMarkPtr) {
285 TkTextIndex index2;
286
287 TkTextIndexForwChars(indexPtr, 1, &index2);
288 TkTextChanged(textPtr, indexPtr, &index2);
289 }
290 return markPtr;
291 }
292
293 /*
294 *--------------------------------------------------------------
295 *
296 * TkTextMarkSegToIndex --
297 *
298 * Given a segment that is a mark, create an index that
299 * refers to the next text character (or other text segment
300 * with non-zero size) after the mark.
301 *
302 * Results:
303 * *IndexPtr is filled in with index information.
304 *
305 * Side effects:
306 * None.
307 *
308 *--------------------------------------------------------------
309 */
310
311 void
TkTextMarkSegToIndex(textPtr,markPtr,indexPtr)312 TkTextMarkSegToIndex(textPtr, markPtr, indexPtr)
313 TkText *textPtr; /* Text widget containing mark. */
314 TkTextSegment *markPtr; /* Mark segment. */
315 TkTextIndex *indexPtr; /* Index information gets stored here. */
316 {
317 TkTextSegment *segPtr;
318
319 indexPtr->tree = textPtr->tree;
320 indexPtr->linePtr = markPtr->body.mark.linePtr;
321 indexPtr->charIndex = 0;
322 for (segPtr = indexPtr->linePtr->segPtr; segPtr != markPtr;
323 segPtr = segPtr->nextPtr) {
324 indexPtr->charIndex += segPtr->size;
325 }
326 }
327
328 /*
329 *--------------------------------------------------------------
330 *
331 * TkTextMarkNameToIndex --
332 *
333 * Given the name of a mark, return an index corresponding
334 * to the mark name.
335 *
336 * Results:
337 * The return value is TCL_OK if "name" exists as a mark in
338 * the text widget. In this case *indexPtr is filled in with
339 * the next segment whose after the mark whose size is
340 * non-zero. TCL_ERROR is returned if the mark doesn't exist
341 * in the text widget.
342 *
343 * Side effects:
344 * None.
345 *
346 *--------------------------------------------------------------
347 */
348
349 int
TkTextMarkNameToIndex(textPtr,name,indexPtr)350 TkTextMarkNameToIndex(textPtr, name, indexPtr)
351 TkText *textPtr; /* Text widget containing mark. */
352 char *name; /* Name of mark. */
353 TkTextIndex *indexPtr; /* Index information gets stored here. */
354 {
355 Tcl_HashEntry *hPtr;
356
357 hPtr = Tcl_FindHashEntry(&textPtr->markTable, name);
358 if (hPtr == NULL) {
359 return TCL_ERROR;
360 }
361 TkTextMarkSegToIndex(textPtr, (TkTextSegment *) Tcl_GetHashValue(hPtr),
362 indexPtr);
363 return TCL_OK;
364 }
365
366 /*
367 *--------------------------------------------------------------
368 *
369 * MarkDeleteProc --
370 *
371 * This procedure is invoked by the text B-tree code whenever
372 * a mark lies in a range of characters being deleted.
373 *
374 * Results:
375 * Returns 1 to indicate that deletion has been rejected.
376 *
377 * Side effects:
378 * None (even if the whole tree is being deleted we don't
379 * free up the mark; it will be done elsewhere).
380 *
381 *--------------------------------------------------------------
382 */
383
384 /* ARGSUSED */
385 static int
MarkDeleteProc(segPtr,linePtr,treeGone)386 MarkDeleteProc(segPtr, linePtr, treeGone)
387 TkTextSegment *segPtr; /* Segment being deleted. */
388 TkTextLine *linePtr; /* Line containing segment. */
389 int treeGone; /* Non-zero means the entire tree is
390 * being deleted, so everything must
391 * get cleaned up. */
392 {
393 return 1;
394 }
395
396 /*
397 *--------------------------------------------------------------
398 *
399 * MarkCleanupProc --
400 *
401 * This procedure is invoked by the B-tree code whenever a
402 * mark segment is moved from one line to another.
403 *
404 * Results:
405 * None.
406 *
407 * Side effects:
408 * The linePtr field of the segment gets updated.
409 *
410 *--------------------------------------------------------------
411 */
412
413 static TkTextSegment *
MarkCleanupProc(markPtr,linePtr)414 MarkCleanupProc(markPtr, linePtr)
415 TkTextSegment *markPtr; /* Mark segment that's being moved. */
416 TkTextLine *linePtr; /* Line that now contains segment. */
417 {
418 markPtr->body.mark.linePtr = linePtr;
419 return markPtr;
420 }
421
422 /*
423 *--------------------------------------------------------------
424 *
425 * MarkLayoutProc --
426 *
427 * This procedure is the "layoutProc" for mark segments.
428 *
429 * Results:
430 * If the mark isn't the insertion cursor then the return
431 * value is -1 to indicate that this segment shouldn't be
432 * displayed. If the mark is the insertion character then
433 * 1 is returned and the chunkPtr structure is filled in.
434 *
435 * Side effects:
436 * None, except for filling in chunkPtr.
437 *
438 *--------------------------------------------------------------
439 */
440
441 /*ARGSUSED*/
442 static int
MarkLayoutProc(textPtr,indexPtr,segPtr,offset,maxX,maxChars,noCharsYet,wrapMode,chunkPtr)443 MarkLayoutProc(textPtr, indexPtr, segPtr, offset, maxX, maxChars,
444 noCharsYet, wrapMode, chunkPtr)
445 TkText *textPtr; /* Text widget being layed out. */
446 TkTextIndex *indexPtr; /* Identifies first character in chunk. */
447 TkTextSegment *segPtr; /* Segment corresponding to indexPtr. */
448 int offset; /* Offset within segPtr corresponding to
449 * indexPtr (always 0). */
450 int maxX; /* Chunk must not occupy pixels at this
451 * position or higher. */
452 int maxChars; /* Chunk must not include more than this
453 * many characters. */
454 int noCharsYet; /* Non-zero means no characters have been
455 * assigned to this line yet. */
456 Tk_Uid wrapMode; /* Not used. */
457 register TkTextDispChunk *chunkPtr;
458 /* Structure to fill in with information
459 * about this chunk. The x field has already
460 * been set by the caller. */
461 {
462 if (segPtr != textPtr->insertMarkPtr) {
463 return -1;
464 }
465
466 chunkPtr->displayProc = TkTextInsertDisplayProc;
467 chunkPtr->undisplayProc = InsertUndisplayProc;
468 chunkPtr->measureProc = (Tk_ChunkMeasureProc *) NULL;
469 chunkPtr->bboxProc = (Tk_ChunkBboxProc *) NULL;
470 chunkPtr->numChars = 0;
471 chunkPtr->minAscent = 0;
472 chunkPtr->minDescent = 0;
473 chunkPtr->minHeight = 0;
474 chunkPtr->width = 0;
475
476 /*
477 * Note: can't break a line after the insertion cursor: this
478 * prevents the insertion cursor from being stranded at the end
479 * of a line.
480 */
481
482 chunkPtr->breakIndex = -1;
483 chunkPtr->clientData = (ClientData) textPtr;
484 return 1;
485 }
486
487 /*
488 *--------------------------------------------------------------
489 *
490 * TkTextInsertDisplayProc --
491 *
492 * This procedure is called to display the insertion
493 * cursor.
494 *
495 * Results:
496 * None.
497 *
498 * Side effects:
499 * Graphics are drawn.
500 *
501 *--------------------------------------------------------------
502 */
503
504 /* ARGSUSED */
505 void
TkTextInsertDisplayProc(chunkPtr,x,y,height,baseline,display,dst,screenY)506 TkTextInsertDisplayProc(chunkPtr, x, y, height, baseline, display, dst, screenY)
507 TkTextDispChunk *chunkPtr; /* Chunk that is to be drawn. */
508 int x; /* X-position in dst at which to
509 * draw this chunk (may differ from
510 * the x-position in the chunk because
511 * of scrolling). */
512 int y; /* Y-position at which to draw this
513 * chunk in dst (x-position is in
514 * the chunk itself). */
515 int height; /* Total height of line. */
516 int baseline; /* Offset of baseline from y. */
517 Display *display; /* Display to use for drawing. */
518 Drawable dst; /* Pixmap or window in which to draw
519 * chunk. */
520 int screenY; /* Y-coordinate in text window that
521 * corresponds to y. */
522 {
523 TkText *textPtr = (TkText *) chunkPtr->clientData;
524 int halfWidth = textPtr->insertWidth/2;
525
526 if ((x + halfWidth) <= 0) {
527 /*
528 * The insertion cursor is off-screen. Just return.
529 */
530
531 return;
532 }
533
534 /*
535 * As a special hack to keep the cursor visible on mono displays
536 * (or anywhere else that the selection and insertion cursors
537 * have the same color) write the default background in the cursor
538 * area (instead of nothing) when the cursor isn't on. Otherwise
539 * the selection might hide the cursor.
540 */
541
542 if (textPtr->flags & INSERT_ON) {
543 Tk_Fill3DRectangle(textPtr->tkwin, dst, textPtr->insertBorder,
544 x - textPtr->insertWidth/2, y, textPtr->insertWidth,
545 height, textPtr->insertBorderWidth, TK_RELIEF_RAISED);
546 } else if (textPtr->selBorder == textPtr->insertBorder) {
547 Tk_Fill3DRectangle(textPtr->tkwin, dst, textPtr->border,
548 x - textPtr->insertWidth/2, y, textPtr->insertWidth,
549 height, 0, TK_RELIEF_FLAT);
550 }
551 }
552
553 /*
554 *--------------------------------------------------------------
555 *
556 * InsertUndisplayProc --
557 *
558 * This procedure is called when the insertion cursor is no
559 * longer at a visible point on the display. It does nothing
560 * right now.
561 *
562 * Results:
563 * None.
564 *
565 * Side effects:
566 * None.
567 *
568 *--------------------------------------------------------------
569 */
570
571 /* ARGSUSED */
572 static void
InsertUndisplayProc(textPtr,chunkPtr)573 InsertUndisplayProc(textPtr, chunkPtr)
574 TkText *textPtr; /* Overall information about text
575 * widget. */
576 TkTextDispChunk *chunkPtr; /* Chunk that is about to be freed. */
577 {
578 return;
579 }
580
581 /*
582 *--------------------------------------------------------------
583 *
584 * MarkCheckProc --
585 *
586 * This procedure is invoked by the B-tree code to perform
587 * consistency checks on mark segments.
588 *
589 * Results:
590 * None.
591 *
592 * Side effects:
593 * The procedure panics if it detects anything wrong with
594 * the mark.
595 *
596 *--------------------------------------------------------------
597 */
598
599 static void
MarkCheckProc(markPtr,linePtr)600 MarkCheckProc(markPtr, linePtr)
601 TkTextSegment *markPtr; /* Segment to check. */
602 TkTextLine *linePtr; /* Line containing segment. */
603 {
604 Tcl_HashSearch search;
605 Tcl_HashEntry *hPtr;
606
607 if (markPtr->body.mark.linePtr != linePtr) {
608 panic("MarkCheckProc: markPtr->body.mark.linePtr bogus");
609 }
610
611 /*
612 * Make sure that the mark is still present in the text's mark
613 * hash table.
614 */
615
616 for (hPtr = Tcl_FirstHashEntry(&markPtr->body.mark.textPtr->markTable,
617 &search); hPtr != markPtr->body.mark.hPtr;
618 hPtr = Tcl_NextHashEntry(&search)) {
619 if (hPtr == NULL) {
620 panic("MarkCheckProc couldn't find hash table entry for mark");
621 }
622 }
623 }
624
625 /*
626 *--------------------------------------------------------------
627 *
628 * MarkFindNext --
629 *
630 * This procedure searches forward for the next mark.
631 *
632 * Results:
633 * A standard Tcl result, which is a mark name or an empty string.
634 *
635 * Side effects:
636 * None.
637 *
638 *--------------------------------------------------------------
639 */
640
641 static int
MarkFindNext(interp,textPtr,string)642 MarkFindNext(interp, textPtr, string)
643 Tcl_Interp *interp; /* For error reporting */
644 TkText *textPtr; /* The widget */
645 char *string; /* The starting index or mark name */
646 {
647 TkTextIndex index;
648 Tcl_HashEntry *hPtr;
649 register TkTextSegment *segPtr;
650 int offset;
651
652
653 hPtr = Tcl_FindHashEntry(&textPtr->markTable, string);
654 if (hPtr != NULL) {
655 /*
656 * If given a mark name, return the next mark in the list of
657 * segments, even if it happens to be at the same character position.
658 */
659 segPtr = (TkTextSegment *) Tcl_GetHashValue(hPtr);
660 TkTextMarkSegToIndex(textPtr, segPtr, &index);
661 segPtr = segPtr->nextPtr;
662 } else {
663 /*
664 * For non-mark name indices we want to return any marks that
665 * are right at the index.
666 */
667 if (TkTextGetIndex(interp, textPtr, string, &index) != TCL_OK) {
668 return TCL_ERROR;
669 }
670 for (offset = 0, segPtr = index.linePtr->segPtr;
671 segPtr != NULL && offset < index.charIndex;
672 offset += segPtr->size, segPtr = segPtr->nextPtr) {
673 /* Empty loop body */ ;
674 }
675 }
676 while (1) {
677 /*
678 * segPtr points at the first possible candidate,
679 * or NULL if we ran off the end of the line.
680 */
681 for ( ; segPtr != NULL ; segPtr = segPtr->nextPtr) {
682 if (segPtr->typePtr == &tkTextRightMarkType ||
683 segPtr->typePtr == &tkTextLeftMarkType) {
684 Tcl_SetResult(interp,
685 Tcl_GetHashKey(&textPtr->markTable, segPtr->body.mark.hPtr),
686 TCL_STATIC);
687 return TCL_OK;
688 }
689 }
690 index.linePtr = TkBTreeNextLine(index.linePtr);
691 if (index.linePtr == (TkTextLine *) NULL) {
692 return TCL_OK;
693 }
694 index.charIndex = 0;
695 segPtr = index.linePtr->segPtr;
696 }
697 }
698
699 /*
700 *--------------------------------------------------------------
701 *
702 * MarkFindPrev --
703 *
704 * This procedure searches backwards for the previous mark.
705 *
706 * Results:
707 * A standard Tcl result, which is a mark name or an empty string.
708 *
709 * Side effects:
710 * None.
711 *
712 *--------------------------------------------------------------
713 */
714
715 static int
MarkFindPrev(interp,textPtr,string)716 MarkFindPrev(interp, textPtr, string)
717 Tcl_Interp *interp; /* For error reporting */
718 TkText *textPtr; /* The widget */
719 char *string; /* The starting index or mark name */
720 {
721 TkTextIndex index;
722 Tcl_HashEntry *hPtr;
723 register TkTextSegment *segPtr, *seg2Ptr, *prevPtr;
724 int offset;
725
726
727 hPtr = Tcl_FindHashEntry(&textPtr->markTable, string);
728 if (hPtr != NULL) {
729 /*
730 * If given a mark name, return the previous mark in the list of
731 * segments, even if it happens to be at the same character position.
732 */
733 segPtr = (TkTextSegment *) Tcl_GetHashValue(hPtr);
734 TkTextMarkSegToIndex(textPtr, segPtr, &index);
735 } else {
736 /*
737 * For non-mark name indices we do not return any marks that
738 * are right at the index.
739 */
740 if (TkTextGetIndex(interp, textPtr, string, &index) != TCL_OK) {
741 return TCL_ERROR;
742 }
743 for (offset = 0, segPtr = index.linePtr->segPtr;
744 segPtr != NULL && offset < index.charIndex;
745 offset += segPtr->size, segPtr = segPtr->nextPtr) {
746 /* Empty loop body */ ;
747 }
748 }
749 while (1) {
750 /*
751 * segPtr points just past the first possible candidate,
752 * or at the begining of the line.
753 */
754 for (prevPtr = NULL, seg2Ptr = index.linePtr->segPtr;
755 seg2Ptr != NULL && seg2Ptr != segPtr;
756 seg2Ptr = seg2Ptr->nextPtr) {
757 if (seg2Ptr->typePtr == &tkTextRightMarkType ||
758 seg2Ptr->typePtr == &tkTextLeftMarkType) {
759 prevPtr = seg2Ptr;
760 }
761 }
762 if (prevPtr != NULL) {
763 Tcl_SetResult(interp,
764 Tcl_GetHashKey(&textPtr->markTable, prevPtr->body.mark.hPtr),
765 TCL_STATIC);
766 return TCL_OK;
767 }
768 index.linePtr = TkBTreePreviousLine(index.linePtr);
769 if (index.linePtr == (TkTextLine *) NULL) {
770 return TCL_OK;
771 }
772 segPtr = NULL;
773 }
774 }
775