]> www.wagner.pp.ru Git - oss/ck.git/blob - ckTextMark.c
Ck console graphics toolkit
[oss/ck.git] / ckTextMark.c
1 /* 
2  * ckTextMark.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  * Copyright (c) 1995 Christian Werner
10  *
11  * See the file "license.terms" for information on usage and redistribution
12  * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
13  */
14
15 #include "ckPort.h"
16 #include "ck.h"
17 #include "ckText.h"
18
19 /*
20  * Macro that determines the size of a mark segment:
21  */
22
23 #define MSEG_SIZE ((unsigned) (Ck_Offset(CkTextSegment, body) \
24         + sizeof(CkTextMark)))
25
26 /*
27  * Forward references for procedures defined in this file:
28  */
29
30 static void             InsertUndisplayProc _ANSI_ARGS_((CkText *textPtr,
31                             CkTextDispChunk *chunkPtr));
32 static int              MarkDeleteProc _ANSI_ARGS_((CkTextSegment *segPtr,
33                             CkTextLine *linePtr, int treeGone));
34 static CkTextSegment *  MarkCleanupProc _ANSI_ARGS_((CkTextSegment *segPtr,
35                             CkTextLine *linePtr));
36 static void             MarkCheckProc _ANSI_ARGS_((CkTextSegment *segPtr,
37                             CkTextLine *linePtr));
38 static int              MarkLayoutProc _ANSI_ARGS_((CkText *textPtr,
39                             CkTextIndex *indexPtr, CkTextSegment *segPtr,
40                             int offset, int maxX, int maxChars,
41                             int noCharsYet, Ck_Uid wrapMode,
42                             CkTextDispChunk *chunkPtr));
43
44 /*
45  * The following structures declare the "mark" segment types.
46  * There are actually two types for marks, one with left gravity
47  * and one with right gravity.  They are identical except for
48  * their gravity property.
49  */
50
51 Ck_SegType ckTextRightMarkType = {
52     "mark",                                     /* name */
53     0,                                          /* leftGravity */
54     (Ck_SegSplitProc *) NULL,                   /* splitProc */
55     MarkDeleteProc,                             /* deleteProc */
56     MarkCleanupProc,                            /* cleanupProc */
57     (Ck_SegLineChangeProc *) NULL,              /* lineChangeProc */
58     MarkLayoutProc,                             /* layoutProc */
59     MarkCheckProc                               /* checkProc */
60 };
61
62 Ck_SegType ckTextLeftMarkType = {
63     "mark",                                     /* name */
64     1,                                          /* leftGravity */
65     (Ck_SegSplitProc *) NULL,                   /* splitProc */
66     MarkDeleteProc,                             /* deleteProc */
67     MarkCleanupProc,                            /* cleanupProc */
68     (Ck_SegLineChangeProc *) NULL,              /* lineChangeProc */
69     MarkLayoutProc,                             /* layoutProc */
70     MarkCheckProc                               /* checkProc */
71 };
72 \f
73 /*
74  *--------------------------------------------------------------
75  *
76  * CkTextMarkCmd --
77  *
78  *      This procedure is invoked to process the "mark" options of
79  *      the widget command for text widgets. See the user documentation
80  *      for details on what it does.
81  *
82  * Results:
83  *      A standard Tcl result.
84  *
85  * Side effects:
86  *      See the user documentation.
87  *
88  *--------------------------------------------------------------
89  */
90
91 int
92 CkTextMarkCmd(textPtr, interp, argc, argv)
93     register CkText *textPtr;   /* Information about text widget. */
94     Tcl_Interp *interp;         /* Current interpreter. */
95     int argc;                   /* Number of arguments. */
96     char **argv;                /* Argument strings.  Someone else has already
97                                  * parsed this command enough to know that
98                                  * argv[1] is "mark". */
99 {
100     int c, i;
101     size_t length;
102     Tcl_HashEntry *hPtr;
103     CkTextSegment *markPtr;
104     Tcl_HashSearch search;
105     CkTextIndex index;
106     Ck_SegType *newTypePtr;
107
108     if (argc < 3) {
109         Tcl_AppendResult(interp, "wrong # args: should be \"",
110                 argv[0], " mark option ?arg arg ...?\"", (char *) NULL);
111         return TCL_ERROR;
112     }
113     c = argv[2][0];
114     length = strlen(argv[2]);
115     if ((c == 'g') && (strncmp(argv[2], "gravity", length) == 0)) {
116         if (argc > 5) {
117             Tcl_AppendResult(interp, "wrong # args: should be \"",
118                     argv[0], " mark gravity markName ?gravity?",
119                     (char *) NULL);
120             return TCL_ERROR;
121         }
122         hPtr = Tcl_FindHashEntry(&textPtr->markTable, argv[3]);
123         if (hPtr == NULL) {
124             Tcl_AppendResult(interp, "there is no mark named \"",
125                     argv[3], "\"", (char *) NULL);
126             return TCL_ERROR;
127         }
128         markPtr = (CkTextSegment *) Tcl_GetHashValue(hPtr);
129         if (argc == 4) {
130             if (markPtr->typePtr == &ckTextRightMarkType) {
131                 interp->result = "right";
132             } else {
133                 interp->result = "left";
134             }
135             return TCL_OK;
136         }
137         length = strlen(argv[4]);
138         c = argv[4][0];
139         if ((c == 'l') && (strncmp(argv[4], "left", length) == 0)) {
140             newTypePtr = &ckTextLeftMarkType;
141         } else if ((c == 'r') && (strncmp(argv[4], "right", length) == 0)) {
142             newTypePtr = &ckTextRightMarkType;
143         } else {
144             Tcl_AppendResult(interp, "bad mark gravity \"",
145                     argv[4], "\": must be left or right", (char *) NULL);
146             return TCL_ERROR;
147         }
148         CkTextMarkSegToIndex(textPtr, markPtr, &index);
149         CkBTreeUnlinkSegment(textPtr->tree, markPtr,
150                 markPtr->body.mark.linePtr);
151         markPtr->typePtr = newTypePtr;
152         CkBTreeLinkSegment(markPtr, &index);
153     } else if ((c == 'n') && (strncmp(argv[2], "names", length) == 0)) {
154         if (argc != 3) {
155             Tcl_AppendResult(interp, "wrong # args: should be \"",
156                     argv[0], " mark names\"", (char *) NULL);
157             return TCL_ERROR;
158         }
159         for (hPtr = Tcl_FirstHashEntry(&textPtr->markTable, &search);
160                 hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) {
161             Tcl_AppendElement(interp,
162                     Tcl_GetHashKey(&textPtr->markTable, hPtr));
163         }
164     } else if ((c == 's') && (strncmp(argv[2], "set", length) == 0)) {
165         if (argc != 5) {
166             Tcl_AppendResult(interp, "wrong # args: should be \"",
167                     argv[0], " mark set markName index\"", (char *) NULL);
168             return TCL_ERROR;
169         }
170         if (CkTextGetIndex(interp, textPtr, argv[4], &index) != TCL_OK) {
171             return TCL_ERROR;
172         }
173         CkTextSetMark(textPtr, argv[3], &index);
174     } else if ((c == 'u') && (strncmp(argv[2], "unset", length) == 0)) {
175         for (i = 3; i < argc; i++) {
176             hPtr = Tcl_FindHashEntry(&textPtr->markTable, argv[i]);
177             if (hPtr != NULL) {
178                 markPtr = (CkTextSegment *) Tcl_GetHashValue(hPtr);
179                 if ((markPtr == textPtr->insertMarkPtr)
180                         || (markPtr == textPtr->currentMarkPtr)) {
181                     continue;
182                 }
183                 CkBTreeUnlinkSegment(textPtr->tree, markPtr,
184                         markPtr->body.mark.linePtr);
185                 Tcl_DeleteHashEntry(hPtr);
186                 ckfree((char *) markPtr);
187             }
188         }
189     } else {
190         Tcl_AppendResult(interp, "bad mark option \"", argv[2],
191                 "\":  must be gravity, names, set, or unset",
192                 (char *) NULL);
193         return TCL_ERROR;
194     }
195     return TCL_OK;
196 }
197 \f
198 /*
199  *----------------------------------------------------------------------
200  *
201  * CkTextSetMark --
202  *
203  *      Set a mark to a particular position, creating a new mark if
204  *      one doesn't already exist.
205  *
206  * Results:
207  *      The return value is a pointer to the mark that was just set.
208  *
209  * Side effects:
210  *      A new mark is created, or an existing mark is moved.
211  *
212  *----------------------------------------------------------------------
213  */
214
215 CkTextSegment *
216 CkTextSetMark(textPtr, name, indexPtr)
217     CkText *textPtr;            /* Text widget in which to create mark. */
218     char *name;                 /* Name of mark to set. */
219     CkTextIndex *indexPtr;      /* Where to set mark. */
220 {
221     Tcl_HashEntry *hPtr;
222     CkTextSegment *markPtr;
223     CkTextIndex insertIndex;
224     int new;
225
226     hPtr = Tcl_CreateHashEntry(&textPtr->markTable, name, &new);
227     markPtr = (CkTextSegment *) Tcl_GetHashValue(hPtr);
228     if (!new) {
229         /*
230          * If this is the insertion point that's being moved, be sure
231          * to force a display update at the old position.  Also, don't
232          * let the insertion cursor be after the final newline of the
233          * file.
234          */
235
236         if (markPtr == textPtr->insertMarkPtr) {
237             CkTextIndex index, index2;
238             CkTextMarkSegToIndex(textPtr, textPtr->insertMarkPtr, &index);
239             CkTextIndexForwChars(&index, 1, &index2);
240             CkTextChanged(textPtr, &index, &index2);
241             if (CkBTreeLineIndex(indexPtr->linePtr)
242                     == CkBTreeNumLines(textPtr->tree))  {
243                 CkTextIndexBackChars(indexPtr, 1, &insertIndex);
244                 indexPtr = &insertIndex;
245             }
246         }
247         CkBTreeUnlinkSegment(textPtr->tree, markPtr,
248                 markPtr->body.mark.linePtr);
249     } else {
250         markPtr = (CkTextSegment *) ckalloc(MSEG_SIZE);
251         markPtr->typePtr = &ckTextRightMarkType;
252         markPtr->size = 0;
253         markPtr->body.mark.textPtr = textPtr;
254         markPtr->body.mark.linePtr = indexPtr->linePtr;
255         markPtr->body.mark.hPtr = hPtr;
256         Tcl_SetHashValue(hPtr, markPtr);
257     }
258     CkBTreeLinkSegment(markPtr, indexPtr);
259
260     /*
261      * If the mark is the insertion cursor, then update the screen at the
262      * mark's new location.
263      */
264
265     if (markPtr == textPtr->insertMarkPtr) {
266         CkTextIndex index2;
267
268         CkTextIndexForwChars(indexPtr, 1, &index2);
269         CkTextChanged(textPtr, indexPtr, &index2);
270     }
271     return markPtr;
272 }
273 \f
274 /*
275  *--------------------------------------------------------------
276  *
277  * CkTextMarkSegToIndex --
278  *
279  *      Given a segment that is a mark, create an index that
280  *      refers to the next text character (or other text segment
281  *      with non-zero size) after the mark.
282  *
283  * Results:
284  *      *IndexPtr is filled in with index information.
285  *
286  * Side effects:
287  *      None.
288  *
289  *--------------------------------------------------------------
290  */
291
292 void
293 CkTextMarkSegToIndex(textPtr, markPtr, indexPtr)
294     CkText *textPtr;            /* Text widget containing mark. */
295     CkTextSegment *markPtr;     /* Mark segment. */
296     CkTextIndex *indexPtr;      /* Index information gets stored here.  */
297 {
298     CkTextSegment *segPtr;
299
300     indexPtr->tree = textPtr->tree;
301     indexPtr->linePtr = markPtr->body.mark.linePtr;
302     indexPtr->charIndex = 0;
303     for (segPtr = indexPtr->linePtr->segPtr; segPtr != markPtr;
304             segPtr = segPtr->nextPtr) {
305         indexPtr->charIndex += segPtr->size;
306     }
307 }
308 \f
309 /*
310  *--------------------------------------------------------------
311  *
312  * CkTextMarkNameToIndex --
313  *
314  *      Given the name of a mark, return an index corresponding
315  *      to the mark name.
316  *
317  * Results:
318  *      The return value is TCL_OK if "name" exists as a mark in
319  *      the text widget.  In this case *indexPtr is filled in with
320  *      the next segment whose after the mark whose size is
321  *      non-zero.  TCL_ERROR is returned if the mark doesn't exist
322  *      in the text widget.
323  *
324  * Side effects:
325  *      None.
326  *
327  *--------------------------------------------------------------
328  */
329
330 int
331 CkTextMarkNameToIndex(textPtr, name, indexPtr)
332     CkText *textPtr;            /* Text widget containing mark. */
333     char *name;                 /* Name of mark. */
334     CkTextIndex *indexPtr;      /* Index information gets stored here. */
335 {
336     Tcl_HashEntry *hPtr;
337
338     hPtr = Tcl_FindHashEntry(&textPtr->markTable, name);
339     if (hPtr == NULL) {
340         return TCL_ERROR;
341     }
342     CkTextMarkSegToIndex(textPtr, (CkTextSegment *) Tcl_GetHashValue(hPtr),
343             indexPtr);
344     return TCL_OK;
345 }
346 \f
347 /*
348  *--------------------------------------------------------------
349  *
350  * MarkDeleteProc --
351  *
352  *      This procedure is invoked by the text B-tree code whenever
353  *      a mark lies in a range of characters being deleted.
354  *
355  * Results:
356  *      Returns 1 to indicate that deletion has been rejected.
357  *
358  * Side effects:
359  *      None (even if the whole tree is being deleted we don't
360  *      free up the mark;  it will be done elsewhere).
361  *
362  *--------------------------------------------------------------
363  */
364
365         /* ARGSUSED */
366 static int
367 MarkDeleteProc(segPtr, linePtr, treeGone)
368     CkTextSegment *segPtr;              /* Segment being deleted. */
369     CkTextLine *linePtr;                /* Line containing segment. */
370     int treeGone;                       /* Non-zero means the entire tree is
371                                          * being deleted, so everything must
372                                          * get cleaned up. */
373 {
374     return 1;
375 }
376 \f
377 /*
378  *--------------------------------------------------------------
379  *
380  * MarkCleanupProc --
381  *
382  *      This procedure is invoked by the B-tree code whenever a
383  *      mark segment is moved from one line to another.
384  *
385  * Results:
386  *      None.
387  *
388  * Side effects:
389  *      The linePtr field of the segment gets updated.
390  *
391  *--------------------------------------------------------------
392  */
393
394 static CkTextSegment *
395 MarkCleanupProc(markPtr, linePtr)
396     CkTextSegment *markPtr;             /* Mark segment that's being moved. */
397     CkTextLine *linePtr;                /* Line that now contains segment. */
398 {
399     markPtr->body.mark.linePtr = linePtr;
400     return markPtr;
401 }
402 \f
403 /*
404  *--------------------------------------------------------------
405  *
406  * MarkLayoutProc --
407  *
408  *      This procedure is the "layoutProc" for mark segments.
409  *
410  * Results:
411  *      If the mark isn't the insertion cursor then the return
412  *      value is -1 to indicate that this segment shouldn't be
413  *      displayed.  If the mark is the insertion character then
414  *      1 is returned and the chunkPtr structure is filled in.
415  *
416  * Side effects:
417  *      None, except for filling in chunkPtr.
418  *
419  *--------------------------------------------------------------
420  */
421
422         /*ARGSUSED*/
423 static int
424 MarkLayoutProc(textPtr, indexPtr, segPtr, offset, maxX, maxChars,
425         noCharsYet, wrapMode, chunkPtr)
426     CkText *textPtr;            /* Text widget being layed out. */
427     CkTextIndex *indexPtr;      /* Identifies first character in chunk. */
428     CkTextSegment *segPtr;      /* Segment corresponding to indexPtr. */
429     int offset;                 /* Offset within segPtr corresponding to
430                                  * indexPtr (always 0). */
431     int maxX;                   /* Chunk must not occupy pixels at this
432                                  * position or higher. */
433     int maxChars;               /* Chunk must not include more than this
434                                  * many characters. */
435     int noCharsYet;             /* Non-zero means no characters have been
436                                  * assigned to this line yet. */
437     Ck_Uid wrapMode;            /* Not used. */
438     register CkTextDispChunk *chunkPtr;
439                                 /* Structure to fill in with information
440                                  * about this chunk.  The x field has already
441                                  * been set by the caller. */
442 {
443     if (segPtr != textPtr->insertMarkPtr) {
444         return -1;
445     }
446
447     chunkPtr->displayProc = CkTextInsertDisplayProc;
448     chunkPtr->undisplayProc = InsertUndisplayProc;
449     chunkPtr->measureProc = (Ck_ChunkMeasureProc *) NULL;
450     chunkPtr->bboxProc = (Ck_ChunkBboxProc *) NULL;
451     chunkPtr->numChars = 0;
452     chunkPtr->minHeight = 0;
453     chunkPtr->width = 0;
454
455     /*
456      * Note: can't break a line after the insertion cursor:  this
457      * prevents the insertion cursor from being stranded at the end
458      * of a line.
459      */
460
461     chunkPtr->breakIndex = -1;
462     chunkPtr->clientData = (ClientData) textPtr;
463     return 1;
464 }
465 \f
466 /*
467  *--------------------------------------------------------------
468  *
469  * CkTextInsertDisplayProc --
470  *
471  *      This procedure is called to display the insertion
472  *      cursor.
473  *
474  * Results:
475  *      None.
476  *
477  * Side effects:
478  *      Graphics are drawn.
479  *
480  *--------------------------------------------------------------
481  */
482
483         /* ARGSUSED */
484 void
485 CkTextInsertDisplayProc(chunkPtr, x, y, height, baseline, window, screenY)
486     CkTextDispChunk *chunkPtr;          /* Chunk that is to be drawn. */
487     int x;                              /* X-position in dst at which to
488                                          * draw this chunk (may differ from
489                                          * the x-position in the chunk because
490                                          * of scrolling). */
491     int y;                              /* Y-position at which to draw this
492                                          * chunk in dst (x-position is in
493                                          * the chunk itself). */
494     int height;                         /* Total height of line. */
495     int baseline;                       /* Offset of baseline from y. */
496     WINDOW *window;                     /* Curses window. */
497     int screenY;                        /* Y-coordinate in text window that
498                                          * corresponds to y. */
499 {
500     CkText *textPtr = (CkText *) chunkPtr->clientData;
501
502     textPtr->insertY = screenY;
503     textPtr->insertX = x;
504 }
505 \f
506 /*
507  *--------------------------------------------------------------
508  *
509  * InsertUndisplayProc --
510  *
511  *      This procedure is called when the insertion cursor is no
512  *      longer at a visible point on the display.  It does nothing
513  *      right now.
514  *
515  * Results:
516  *      None.
517  *
518  * Side effects:
519  *      None.
520  *
521  *--------------------------------------------------------------
522  */
523
524         /* ARGSUSED */
525 static void
526 InsertUndisplayProc(textPtr, chunkPtr)
527     CkText *textPtr;                    /* Overall information about text
528                                          * widget. */
529     CkTextDispChunk *chunkPtr;          /* Chunk that is about to be freed. */
530 {
531     return;
532 }
533 \f
534 /*
535  *--------------------------------------------------------------
536  *
537  * MarkCheckProc --
538  *
539  *      This procedure is invoked by the B-tree code to perform
540  *      consistency checks on mark segments.
541  *
542  * Results:
543  *      None.
544  *
545  * Side effects:
546  *      The procedure panics if it detects anything wrong with
547  *      the mark.
548  *
549  *--------------------------------------------------------------
550  */
551
552 static void
553 MarkCheckProc(markPtr, linePtr)
554     CkTextSegment *markPtr;             /* Segment to check. */
555     CkTextLine *linePtr;                /* Line containing segment. */
556 {
557     Tcl_HashSearch search;
558     Tcl_HashEntry *hPtr;
559
560     if (markPtr->body.mark.linePtr != linePtr) {
561         panic("MarkCheckProc: markPtr->body.mark.linePtr bogus");
562     }
563
564     /*
565      * Make sure that the mark is still present in the text's mark
566      * hash table.
567      */
568
569     for (hPtr = Tcl_FirstHashEntry(&markPtr->body.mark.textPtr->markTable,
570             &search); hPtr != markPtr->body.mark.hPtr;
571             hPtr = Tcl_NextHashEntry(&search)) {
572         if (hPtr == NULL) {
573             panic("MarkCheckProc couldn't find hash table entry for mark");
574         }
575     }
576 }