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