Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 50497 Content-Disposition: inline; filename="tkTextTag.c" Last-Modified: Wed, 18 Sep 2024 10:28:10 GMT Expires: Sat, 16 Sep 2034 10:28:10 GMT ETag: "97929bd71e4f497a9c919b656f0e7d3f31b56eda" /* * tkTextTag.c -- * * This module implements the "tag" subcommand of the widget command for * text widgets, plus most of the other high-level functions related to * tags. * * Copyright (c) 1992-1994 The Regents of the University of California. * Copyright (c) 1994-1997 Sun Microsystems, Inc. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. * * RCS: @(#) $Id: tkTextTag.c,v 1.27 2007/12/13 15:24:17 dgp Exp $ */ #include "default.h" #include "tkInt.h" #include "tkText.h" /* * The 'TkWrapMode' enum in tkText.h is used to define a type for the -wrap * option of tags in a Text widget. These values are used as indices into the * string table below. Tags are allowed an empty wrap value, but the widget as * a whole is not. */ static char *wrapStrings[] = { "char", "none", "word", "", NULL }; /* * The 'TkTextTabStyle' enum in tkText.h is used to define a type for the * -tabstyle option of the Text widget. These values are used as indices into * the string table below. Tags are allowed an empty wrap value, but the * widget as a whole is not. */ static char *tabStyleStrings[] = { "tabular", "wordprocessor", "", NULL }; static const Tk_OptionSpec tagOptionSpecs[] = { {TK_OPTION_BORDER, "-background", NULL, NULL, NULL, -1, Tk_Offset(TkTextTag, border), TK_OPTION_NULL_OK, 0, 0}, {TK_OPTION_BITMAP, "-bgstipple", NULL, NULL, NULL, -1, Tk_Offset(TkTextTag, bgStipple), TK_OPTION_NULL_OK, 0, 0}, {TK_OPTION_PIXELS, "-borderwidth", NULL, NULL, "0", Tk_Offset(TkTextTag, borderWidthPtr), Tk_Offset(TkTextTag, borderWidth), TK_OPTION_DONT_SET_DEFAULT|TK_OPTION_NULL_OK, 0, 0}, {TK_OPTION_STRING, "-elide", NULL, NULL, "0", -1, Tk_Offset(TkTextTag, elideString), TK_OPTION_DONT_SET_DEFAULT|TK_OPTION_NULL_OK, 0, 0}, {TK_OPTION_BITMAP, "-fgstipple", NULL, NULL, NULL, -1, Tk_Offset(TkTextTag, fgStipple), TK_OPTION_NULL_OK, 0, 0}, {TK_OPTION_FONT, "-font", NULL, NULL, NULL, -1, Tk_Offset(TkTextTag, tkfont), TK_OPTION_NULL_OK, 0, 0}, {TK_OPTION_COLOR, "-foreground", NULL, NULL, NULL, -1, Tk_Offset(TkTextTag, fgColor), TK_OPTION_NULL_OK, 0, 0}, {TK_OPTION_STRING, "-justify", NULL, NULL, NULL, -1, Tk_Offset(TkTextTag, justifyString), TK_OPTION_NULL_OK, 0,0}, {TK_OPTION_STRING, "-lmargin1", NULL, NULL, NULL, -1, Tk_Offset(TkTextTag, lMargin1String), TK_OPTION_NULL_OK,0,0}, {TK_OPTION_STRING, "-lmargin2", NULL, NULL, NULL, -1, Tk_Offset(TkTextTag, lMargin2String), TK_OPTION_NULL_OK,0,0}, {TK_OPTION_STRING, "-offset", NULL, NULL, NULL, -1, Tk_Offset(TkTextTag, offsetString), TK_OPTION_NULL_OK, 0, 0}, {TK_OPTION_STRING, "-overstrike", NULL, NULL, NULL, -1, Tk_Offset(TkTextTag, overstrikeString), TK_OPTION_NULL_OK, 0, 0}, {TK_OPTION_STRING, "-relief", NULL, NULL, NULL, -1, Tk_Offset(TkTextTag, reliefString), TK_OPTION_NULL_OK, 0, 0}, {TK_OPTION_STRING, "-rmargin", NULL, NULL, NULL, -1, Tk_Offset(TkTextTag, rMarginString), TK_OPTION_NULL_OK, 0,0}, {TK_OPTION_STRING, "-spacing1", NULL, NULL, NULL, -1, Tk_Offset(TkTextTag, spacing1String), TK_OPTION_NULL_OK,0,0}, {TK_OPTION_STRING, "-spacing2", NULL, NULL, NULL, -1, Tk_Offset(TkTextTag, spacing2String), TK_OPTION_NULL_OK,0,0}, {TK_OPTION_STRING, "-spacing3", NULL, NULL, NULL, -1, Tk_Offset(TkTextTag, spacing3String), TK_OPTION_NULL_OK,0,0}, {TK_OPTION_STRING, "-tabs", NULL, NULL, NULL, Tk_Offset(TkTextTag, tabStringPtr), -1, TK_OPTION_NULL_OK, 0, 0}, {TK_OPTION_STRING_TABLE, "-tabstyle", NULL, NULL, NULL, -1, Tk_Offset(TkTextTag, tabStyle), TK_OPTION_NULL_OK, (ClientData) tabStyleStrings, 0}, {TK_OPTION_STRING, "-underline", NULL, NULL, NULL, -1, Tk_Offset(TkTextTag, underlineString), TK_OPTION_NULL_OK, 0, 0}, {TK_OPTION_STRING_TABLE, "-wrap", NULL, NULL, NULL, -1, Tk_Offset(TkTextTag, wrapMode), TK_OPTION_NULL_OK, (ClientData) wrapStrings, 0}, {TK_OPTION_END} }; /* * Forward declarations for functions defined later in this file: */ static void ChangeTagPriority(TkText *textPtr, TkTextTag *tagPtr, int prio); static TkTextTag * FindTag(Tcl_Interp *interp, TkText *textPtr, Tcl_Obj *tagName); static void SortTags(int numTags, TkTextTag **tagArrayPtr); static int TagSortProc(CONST VOID *first, CONST VOID *second); static void TagBindEvent(TkText *textPtr, XEvent *eventPtr, int numTags, TkTextTag **tagArrayPtr); /* *-------------------------------------------------------------- * * TkTextTagCmd -- * * This function is invoked to process the "tag" options of the widget * command for text widgets. See the user documentation for details on * what it does. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *-------------------------------------------------------------- */ int TkTextTagCmd( register TkText *textPtr, /* Information about text widget. */ Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *CONST objv[]) /* Argument objects. Someone else has already * parsed this command enough to know that * objv[1] is "tag". */ { static CONST char *tagOptionStrings[] = { "add", "bind", "cget", "configure", "delete", "lower", "names", "nextrange", "prevrange", "raise", "ranges", "remove", NULL }; enum tagOptions { TAG_ADD, TAG_BIND, TAG_CGET, TAG_CONFIGURE, TAG_DELETE, TAG_LOWER, TAG_NAMES, TAG_NEXTRANGE, TAG_PREVRANGE, TAG_RAISE, TAG_RANGES, TAG_REMOVE }; int optionIndex, i; register TkTextTag *tagPtr; TkTextIndex index1, index2; if (objc < 3) { Tcl_WrongNumArgs(interp, 2, objv, "option ?arg arg ...?"); return TCL_ERROR; } if (Tcl_GetIndexFromObj(interp, objv[2], tagOptionStrings, "tag option", 0, &optionIndex) != TCL_OK) { return TCL_ERROR; } switch ((enum tagOptions)optionIndex) { case TAG_ADD: case TAG_REMOVE: { int addTag; if (((enum tagOptions)optionIndex) == TAG_ADD) { addTag = 1; } else { addTag = 0; } if (objc < 5) { Tcl_WrongNumArgs(interp, 3, objv, "tagName index1 ?index2 index1 index2 ...?"); return TCL_ERROR; } tagPtr = TkTextCreateTag(textPtr, Tcl_GetString(objv[3]), NULL); for (i = 4; i < objc; i += 2) { if (TkTextGetObjIndex(interp, textPtr, objv[i], &index1) != TCL_OK) { return TCL_ERROR; } if (objc > (i+1)) { if (TkTextGetObjIndex(interp, textPtr, objv[i+1], &index2) != TCL_OK) { return TCL_ERROR; } if (TkTextIndexCmp(&index1, &index2) >= 0) { return TCL_OK; } } else { index2 = index1; TkTextIndexForwChars(NULL,&index2, 1, &index2, COUNT_INDICES); } if (tagPtr->affectsDisplay) { TkTextRedrawTag(textPtr->sharedTextPtr, NULL, &index1, &index2, tagPtr, !addTag); } else { /* * Still need to trigger enter/leave events on tags that have * changed. */ TkTextEventuallyRepick(textPtr); } if (TkBTreeTag(&index1, &index2, tagPtr, addTag)) { /* * If the tag is "sel", and we actually adjusted something * then grab the selection if we're supposed to export it and * don't already have it. * * Also, invalidate partially-completed selection retrievals. * We only need to check whether the tag is "sel" for this * textPtr (not for other peer widget's "sel" tags) because we * cannot reach this code path with a different widget's "sel" * tag. */ if (tagPtr == textPtr->selTagPtr) { /* * Send an event that the selection changed. This is * equivalent to: * event generate $textWidget <> */ TkTextSelectionEvent(textPtr); if (addTag && textPtr->exportSelection && !(textPtr->flags & GOT_SELECTION)) { Tk_OwnSelection(textPtr->tkwin, XA_PRIMARY, TkTextLostSelection, (ClientData) textPtr); textPtr->flags |= GOT_SELECTION; } textPtr->abortSelections = 1; } } } break; } case TAG_BIND: if ((objc < 4) || (objc > 6)) { Tcl_WrongNumArgs(interp, 3, objv, "tagName ?sequence? ?command?"); return TCL_ERROR; } tagPtr = TkTextCreateTag(textPtr, Tcl_GetString(objv[3]), NULL); /* * Make a binding table if the widget doesn't already have one. */ if (textPtr->sharedTextPtr->bindingTable == NULL) { textPtr->sharedTextPtr->bindingTable = Tk_CreateBindingTable(interp); } if (objc == 6) { int append = 0; unsigned long mask; char *fifth = Tcl_GetString(objv[5]); if (fifth[0] == 0) { return Tk_DeleteBinding(interp, textPtr->sharedTextPtr->bindingTable, (ClientData) tagPtr->name, Tcl_GetString(objv[4])); } if (fifth[0] == '+') { fifth++; append = 1; } mask = Tk_CreateBinding(interp, textPtr->sharedTextPtr->bindingTable, (ClientData) tagPtr->name, Tcl_GetString(objv[4]), fifth, append); if (mask == 0) { return TCL_ERROR; } if (mask & (unsigned) ~(ButtonMotionMask|Button1MotionMask |Button2MotionMask|Button3MotionMask|Button4MotionMask |Button5MotionMask|ButtonPressMask|ButtonReleaseMask |EnterWindowMask|LeaveWindowMask|KeyPressMask |KeyReleaseMask|PointerMotionMask|VirtualEventMask)) { Tk_DeleteBinding(interp, textPtr->sharedTextPtr->bindingTable, (ClientData) tagPtr->name, Tcl_GetString(objv[4])); Tcl_ResetResult(interp); Tcl_AppendResult(interp, "requested illegal events; ", "only key, button, motion, enter, leave, and virtual ", "events may be used", NULL); return TCL_ERROR; } } else if (objc == 5) { CONST char *command; command = Tk_GetBinding(interp, textPtr->sharedTextPtr->bindingTable, (ClientData) tagPtr->name, Tcl_GetString(objv[4])); if (command == NULL) { CONST char *string = Tcl_GetStringResult(interp); /* * Ignore missing binding errors. This is a special hack that * relies on the error message returned by FindSequence in * tkBind.c. */ if (string[0] != '\0') { return TCL_ERROR; } Tcl_ResetResult(interp); } else { Tcl_SetResult(interp, (char *) command, TCL_STATIC); } } else { Tk_GetAllBindings(interp, textPtr->sharedTextPtr->bindingTable, (ClientData) tagPtr->name); } break; case TAG_CGET: if (objc != 5) { Tcl_WrongNumArgs(interp, 1, objv, "tag cget tagName option"); return TCL_ERROR; } else { Tcl_Obj *objPtr; tagPtr = FindTag(interp, textPtr, objv[3]); if (tagPtr == NULL) { return TCL_ERROR; } objPtr = Tk_GetOptionValue(interp, (char *) tagPtr, tagPtr->optionTable, objv[4], textPtr->tkwin); if (objPtr == NULL) { return TCL_ERROR; } Tcl_SetObjResult(interp, objPtr); return TCL_OK; } break; case TAG_CONFIGURE: { int newTag; if (objc < 4) { Tcl_WrongNumArgs(interp, 3, objv, "tagName ?option? ?value? ?option value ...?"); return TCL_ERROR; } tagPtr = TkTextCreateTag(textPtr, Tcl_GetString(objv[3]), &newTag); if (objc <= 5) { Tcl_Obj *objPtr = Tk_GetOptionInfo(interp, (char *) tagPtr, tagPtr->optionTable, (objc == 5) ? objv[4] : NULL, textPtr->tkwin); if (objPtr == NULL) { return TCL_ERROR; } Tcl_SetObjResult(interp, objPtr); return TCL_OK; } else { int result = TCL_OK; if (Tk_SetOptions(interp, (char*)tagPtr, tagPtr->optionTable, objc-4, objv+4, textPtr->tkwin, NULL, NULL) != TCL_OK) { return TCL_ERROR; } /* * Some of the configuration options, like -underline and * -justify, require additional translation (this is needed * because we need to distinguish a particular value of an option * from "unspecified"). */ if (tagPtr->borderWidth < 0) { tagPtr->borderWidth = 0; } if (tagPtr->reliefString != NULL) { if (Tk_GetRelief(interp, tagPtr->reliefString, &tagPtr->relief) != TCL_OK) { return TCL_ERROR; } } if (tagPtr->justifyString != NULL) { if (Tk_GetJustify(interp, tagPtr->justifyString, &tagPtr->justify) != TCL_OK) { return TCL_ERROR; } } if (tagPtr->lMargin1String != NULL) { if (Tk_GetPixels(interp, textPtr->tkwin, tagPtr->lMargin1String, &tagPtr->lMargin1) != TCL_OK) { return TCL_ERROR; } } if (tagPtr->lMargin2String != NULL) { if (Tk_GetPixels(interp, textPtr->tkwin, tagPtr->lMargin2String, &tagPtr->lMargin2) != TCL_OK) { return TCL_ERROR; } } if (tagPtr->offsetString != NULL) { if (Tk_GetPixels(interp, textPtr->tkwin, tagPtr->offsetString, &tagPtr->offset) != TCL_OK) { return TCL_ERROR; } } if (tagPtr->overstrikeString != NULL) { if (Tcl_GetBoolean(interp, tagPtr->overstrikeString, &tagPtr->overstrike) != TCL_OK) { return TCL_ERROR; } } if (tagPtr->rMarginString != NULL) { if (Tk_GetPixels(interp, textPtr->tkwin, tagPtr->rMarginString, &tagPtr->rMargin) != TCL_OK) { return TCL_ERROR; } } if (tagPtr->spacing1String != NULL) { if (Tk_GetPixels(interp, textPtr->tkwin, tagPtr->spacing1String, &tagPtr->spacing1) != TCL_OK) { return TCL_ERROR; } if (tagPtr->spacing1 < 0) { tagPtr->spacing1 = 0; } } if (tagPtr->spacing2String != NULL) { if (Tk_GetPixels(interp, textPtr->tkwin, tagPtr->spacing2String, &tagPtr->spacing2) != TCL_OK) { return TCL_ERROR; } if (tagPtr->spacing2 < 0) { tagPtr->spacing2 = 0; } } if (tagPtr->spacing3String != NULL) { if (Tk_GetPixels(interp, textPtr->tkwin, tagPtr->spacing3String, &tagPtr->spacing3) != TCL_OK) { return TCL_ERROR; } if (tagPtr->spacing3 < 0) { tagPtr->spacing3 = 0; } } if (tagPtr->tabArrayPtr != NULL) { ckfree((char *) tagPtr->tabArrayPtr); tagPtr->tabArrayPtr = NULL; } if (tagPtr->tabStringPtr != NULL) { tagPtr->tabArrayPtr = TkTextGetTabs(interp, textPtr, tagPtr->tabStringPtr); if (tagPtr->tabArrayPtr == NULL) { return TCL_ERROR; } } if (tagPtr->underlineString != NULL) { if (Tcl_GetBoolean(interp, tagPtr->underlineString, &tagPtr->underline) != TCL_OK) { return TCL_ERROR; } } if (tagPtr->elideString != NULL) { if (Tcl_GetBoolean(interp, tagPtr->elideString, &tagPtr->elide) != TCL_OK) { return TCL_ERROR; } } /* * If the "sel" tag was changed, be sure to mirror information * from the tag back into the text widget record. NOTE: we don't * have to free up information in the widget record before * overwriting it, because it was mirrored in the tag and hence * freed when the tag field was overwritten. */ if (tagPtr == textPtr->selTagPtr) { textPtr->selBorder = tagPtr->border; textPtr->selBorderWidth = tagPtr->borderWidth; textPtr->selBorderWidthPtr = tagPtr->borderWidthPtr; textPtr->selFgColorPtr = tagPtr->fgColor; } tagPtr->affectsDisplay = 0; tagPtr->affectsDisplayGeometry = 0; if ((tagPtr->elideString != NULL) || (tagPtr->tkfont != None) || (tagPtr->justifyString != NULL) || (tagPtr->lMargin1String != NULL) || (tagPtr->lMargin2String != NULL) || (tagPtr->offsetString != NULL) || (tagPtr->rMarginString != NULL) || (tagPtr->spacing1String != NULL) || (tagPtr->spacing2String != NULL) || (tagPtr->spacing3String != NULL) || (tagPtr->tabStringPtr != NULL) || (tagPtr->tabStyle != TK_TEXT_TABSTYLE_NONE) || (tagPtr->wrapMode != TEXT_WRAPMODE_NULL)) { tagPtr->affectsDisplay = 1; tagPtr->affectsDisplayGeometry = 1; } if ((tagPtr->border != NULL) || (tagPtr->reliefString != NULL) || (tagPtr->bgStipple != None) || (tagPtr->fgColor != NULL) || (tagPtr->fgStipple != None) || (tagPtr->overstrikeString != NULL) || (tagPtr->underlineString != NULL)) { tagPtr->affectsDisplay = 1; } if (!newTag) { /* * This line is not necessary if this is a new tag, since it * can't possibly have been applied to anything yet. */ /* * VMD: If this is the 'sel' tag, then we don't need to call * this for all peers, unless we actually want to synchronize * sel-style changes across the peers. */ TkTextRedrawTag(textPtr->sharedTextPtr, NULL, NULL, NULL, tagPtr, 1); } return result; } break; } case TAG_DELETE: { Tcl_HashEntry *hPtr; if (objc < 4) { Tcl_WrongNumArgs(interp, 3, objv, "tagName ?tagName ...?"); return TCL_ERROR; } for (i = 3; i < objc; i++) { hPtr = Tcl_FindHashEntry(&textPtr->sharedTextPtr->tagTable, Tcl_GetString(objv[i])); if (hPtr == NULL) { /* * Either this tag doesn't exist or it's the 'sel' tag (which * is not in the hash table). Either way we don't want to * delete it. */ continue; } tagPtr = (TkTextTag *) Tcl_GetHashValue(hPtr); if (tagPtr == textPtr->selTagPtr) { continue; } if (tagPtr->affectsDisplay) { TkTextRedrawTag(textPtr->sharedTextPtr, NULL, NULL, NULL, tagPtr, 1); } TkTextDeleteTag(textPtr, tagPtr); Tcl_DeleteHashEntry(hPtr); } break; } case TAG_LOWER: { TkTextTag *tagPtr2; int prio; if ((objc != 4) && (objc != 5)) { Tcl_WrongNumArgs(interp, 3, objv, "tagName ?belowThis?"); return TCL_ERROR; } tagPtr = FindTag(interp, textPtr, objv[3]); if (tagPtr == NULL) { return TCL_ERROR; } if (objc == 5) { tagPtr2 = FindTag(interp, textPtr, objv[4]); if (tagPtr2 == NULL) { return TCL_ERROR; } if (tagPtr->priority < tagPtr2->priority) { prio = tagPtr2->priority - 1; } else { prio = tagPtr2->priority; } } else { prio = 0; } ChangeTagPriority(textPtr, tagPtr, prio); /* * If this is the 'sel' tag, then we don't actually need to call this * for all peers. */ TkTextRedrawTag(textPtr->sharedTextPtr, NULL, NULL, NULL, tagPtr, 1); break; } case TAG_NAMES: { TkTextTag **arrayPtr; int arraySize; Tcl_Obj *listObj; if ((objc != 3) && (objc != 4)) { Tcl_WrongNumArgs(interp, 3, objv, "?index?"); return TCL_ERROR; } if (objc == 3) { Tcl_HashSearch search; Tcl_HashEntry *hPtr; arrayPtr = (TkTextTag **) ckalloc((unsigned) (textPtr->sharedTextPtr->numTags * sizeof(TkTextTag *))); for (i=0, hPtr = Tcl_FirstHashEntry( &textPtr->sharedTextPtr->tagTable, &search); hPtr != NULL; i++, hPtr = Tcl_NextHashEntry(&search)) { arrayPtr[i] = (TkTextTag *) Tcl_GetHashValue(hPtr); } /* * The 'sel' tag is not in the hash table. */ arrayPtr[i] = textPtr->selTagPtr; arraySize = ++i; } else { if (TkTextGetObjIndex(interp, textPtr, objv[3], &index1) != TCL_OK) { return TCL_ERROR; } arrayPtr = TkBTreeGetTags(&index1, textPtr, &arraySize); if (arrayPtr == NULL) { return TCL_OK; } } SortTags(arraySize, arrayPtr); listObj = Tcl_NewListObj(0, NULL); for (i = 0; i < arraySize; i++) { tagPtr = arrayPtr[i]; Tcl_ListObjAppendElement(interp, listObj, Tcl_NewStringObj(tagPtr->name,-1)); } Tcl_SetObjResult(interp, listObj); ckfree((char *) arrayPtr); break; } case TAG_NEXTRANGE: { TkTextIndex last; TkTextSearch tSearch; char position[TK_POS_CHARS]; if ((objc != 5) && (objc != 6)) { Tcl_WrongNumArgs(interp, 3, objv, "tagName index1 ?index2?"); return TCL_ERROR; } tagPtr = FindTag(NULL, textPtr, objv[3]); if (tagPtr == NULL) { return TCL_OK; } if (TkTextGetObjIndex(interp, textPtr, objv[4], &index1) != TCL_OK) { return TCL_ERROR; } TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr, TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr), 0, &last); if (objc == 5) { index2 = last; } else if (TkTextGetObjIndex(interp, textPtr, objv[5], &index2) != TCL_OK) { return TCL_ERROR; } /* * The search below is a bit tricky. Rather than use the B-tree * facilities to stop the search at index2, let it search up until the * end of the file but check for a position past index2 ourselves. * The reason for doing it this way is that we only care whether the * *start* of the range is before index2; once we find the start, we * don't want TkBTreeNextTag to abort the search because the end of * the range is after index2. */ TkBTreeStartSearch(&index1, &last, tagPtr, &tSearch); if (TkBTreeCharTagged(&index1, tagPtr)) { TkTextSegment *segPtr; int offset; /* * The first character is tagged. See if there is an on-toggle * just before the character. If not, then skip to the end of this * tagged range. */ for (segPtr = index1.linePtr->segPtr, offset = index1.byteIndex; offset >= 0; offset -= segPtr->size, segPtr = segPtr->nextPtr) { if ((offset == 0) && (segPtr->typePtr == &tkTextToggleOnType) && (segPtr->body.toggle.tagPtr == tagPtr)) { goto gotStart; } } if (!TkBTreeNextTag(&tSearch)) { return TCL_OK; } } /* * Find the start of the tagged range. */ if (!TkBTreeNextTag(&tSearch)) { return TCL_OK; } gotStart: if (TkTextIndexCmp(&tSearch.curIndex, &index2) >= 0) { return TCL_OK; } TkTextPrintIndex(textPtr, &tSearch.curIndex, position); Tcl_AppendElement(interp, position); TkBTreeNextTag(&tSearch); TkTextPrintIndex(textPtr, &tSearch.curIndex, position); Tcl_AppendElement(interp, position); break; } case TAG_PREVRANGE: { TkTextIndex last; TkTextSearch tSearch; char position1[TK_POS_CHARS]; char position2[TK_POS_CHARS]; if ((objc != 5) && (objc != 6)) { Tcl_WrongNumArgs(interp, 3, objv, "tagName index1 ?index2?"); return TCL_ERROR; } tagPtr = FindTag(NULL, textPtr, objv[3]); if (tagPtr == NULL) { return TCL_OK; } if (TkTextGetObjIndex(interp, textPtr, objv[4], &index1) != TCL_OK) { return TCL_ERROR; } if (objc == 5) { TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr, 0, 0, &index2); } else if (TkTextGetObjIndex(interp, textPtr, objv[5], &index2) != TCL_OK) { return TCL_ERROR; } /* * The search below is a bit weird. The previous toggle can be either * an on or off toggle. If it is an on toggle, then we need to turn * around and search forward for the end toggle. Otherwise we keep * searching backwards. */ TkBTreeStartSearchBack(&index1, &index2, tagPtr, &tSearch); if (!TkBTreePrevTag(&tSearch)) { /* * Special case, there may be a tag off toggle at index1, and a * tag on toggle before the start of a partial peer widget. In * this case we missed it. */ if (textPtr->start != NULL && (textPtr->start == index2.linePtr) && (index2.byteIndex == 0) && TkBTreeCharTagged(&index2, tagPtr) && (TkTextIndexCmp(&index2, &index1) < 0)) { /* * The first character is tagged, so just add the range from * the first char to the start of the range. */ TkTextPrintIndex(textPtr, &index2, position1); TkTextPrintIndex(textPtr, &index1, position2); Tcl_AppendElement(interp, position1); Tcl_AppendElement(interp, position2); } return TCL_OK; } if (tSearch.segPtr->typePtr == &tkTextToggleOnType) { TkTextPrintIndex(textPtr, &tSearch.curIndex, position1); if (textPtr->start != NULL) { /* * Make sure the first index is not before the first allowed * text index in this widget. */ TkTextIndex firstIndex; firstIndex.linePtr = textPtr->start; firstIndex.byteIndex = 0; firstIndex.textPtr = NULL; if (TkTextIndexCmp(&tSearch.curIndex, &firstIndex) < 0) { if (TkTextIndexCmp(&firstIndex, &index1) >= 0) { /* * But now the new first index is actually too far * along in the text, so nothing is returned. */ return TCL_OK; } TkTextPrintIndex(textPtr, &firstIndex, position1); } } TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr, TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr), 0, &last); TkBTreeStartSearch(&tSearch.curIndex, &last, tagPtr, &tSearch); TkBTreeNextTag(&tSearch); TkTextPrintIndex(textPtr, &tSearch.curIndex, position2); } else { TkTextPrintIndex(textPtr, &tSearch.curIndex, position2); TkBTreePrevTag(&tSearch); TkTextPrintIndex(textPtr, &tSearch.curIndex, position1); if (TkTextIndexCmp(&tSearch.curIndex, &index2) < 0) { if (textPtr->start != NULL && index2.linePtr == textPtr->start && index2.byteIndex == 0) { /* It's ok */ TkTextPrintIndex(textPtr, &index2, position1); } else { return TCL_OK; } } } Tcl_AppendElement(interp, position1); Tcl_AppendElement(interp, position2); break; } case TAG_RAISE: { TkTextTag *tagPtr2; int prio; if ((objc != 4) && (objc != 5)) { Tcl_WrongNumArgs(interp, 3, objv, "tagName ?aboveThis?"); return TCL_ERROR; } tagPtr = FindTag(interp, textPtr, objv[3]); if (tagPtr == NULL) { return TCL_ERROR; } if (objc == 5) { tagPtr2 = FindTag(interp, textPtr, objv[4]); if (tagPtr2 == NULL) { return TCL_ERROR; } if (tagPtr->priority <= tagPtr2->priority) { prio = tagPtr2->priority; } else { prio = tagPtr2->priority + 1; } } else { prio = textPtr->sharedTextPtr->numTags-1; } ChangeTagPriority(textPtr, tagPtr, prio); /* * If this is the 'sel' tag, then we don't actually need to call this * for all peers. */ TkTextRedrawTag(textPtr->sharedTextPtr, NULL, NULL, NULL, tagPtr, 1); break; } case TAG_RANGES: { TkTextIndex first, last; TkTextSearch tSearch; Tcl_Obj *listObj = Tcl_NewListObj(0, NULL); int count = 0; if (objc != 4) { Tcl_WrongNumArgs(interp, 3, objv, "tagName"); return TCL_ERROR; } tagPtr = FindTag(NULL, textPtr, objv[3]); if (tagPtr == NULL) { return TCL_OK; } TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr, 0, 0, &first); TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr, TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr), 0, &last); TkBTreeStartSearch(&first, &last, tagPtr, &tSearch); if (TkBTreeCharTagged(&first, tagPtr)) { Tcl_ListObjAppendElement(interp, listObj, TkTextNewIndexObj(textPtr, &first)); count++; } while (TkBTreeNextTag(&tSearch)) { Tcl_ListObjAppendElement(interp, listObj, TkTextNewIndexObj(textPtr, &tSearch.curIndex)); count++; } if (count % 2 == 1) { /* * If a text widget uses '-end', it won't necessarily run to the * end of the B-tree, and therefore the tag range might not be * closed. In this case we add the end of the range. */ Tcl_ListObjAppendElement(interp, listObj, TkTextNewIndexObj(textPtr, &last)); } Tcl_SetObjResult(interp, listObj); break; } } return TCL_OK; } /* *---------------------------------------------------------------------- * * TkTextCreateTag -- * * Find the record describing a tag within a given text widget, creating * a new record if one doesn't already exist. * * Results: * The return value is a pointer to the TkTextTag record for tagName. * * Side effects: * A new tag record is created if there isn't one already defined for * tagName. * *---------------------------------------------------------------------- */ TkTextTag * TkTextCreateTag( TkText *textPtr, /* Widget in which tag is being used. */ CONST char *tagName, /* Name of desired tag. */ int *newTag) /* If non-NULL, then return 1 if new, or 0 if * already exists. */ { register TkTextTag *tagPtr; Tcl_HashEntry *hPtr = NULL; int isNew; CONST char *name; if (!strcmp(tagName, "sel")) { if (textPtr->selTagPtr != NULL) { if (newTag != NULL) { *newTag = 0; } return textPtr->selTagPtr; } if (newTag != NULL) { *newTag = 1; } name = "sel"; } else { hPtr = Tcl_CreateHashEntry(&textPtr->sharedTextPtr->tagTable, tagName, &isNew); if (newTag != NULL) { *newTag = isNew; } if (!isNew) { return (TkTextTag *) Tcl_GetHashValue(hPtr); } name = Tcl_GetHashKey(&textPtr->sharedTextPtr->tagTable, hPtr); } /* * No existing entry. Create a new one, initialize it, and add a pointer * to it to the hash table entry. */ tagPtr = (TkTextTag *) ckalloc(sizeof(TkTextTag)); tagPtr->name = name; tagPtr->textPtr = NULL; tagPtr->toggleCount = 0; tagPtr->tagRootPtr = NULL; tagPtr->priority = textPtr->sharedTextPtr->numTags; tagPtr->border = NULL; tagPtr->borderWidth = 0; tagPtr->borderWidthPtr = NULL; tagPtr->reliefString = NULL; tagPtr->relief = TK_RELIEF_FLAT; tagPtr->bgStipple = None; tagPtr->fgColor = NULL; tagPtr->tkfont = NULL; tagPtr->fgStipple = None; tagPtr->justifyString = NULL; tagPtr->justify = TK_JUSTIFY_LEFT; tagPtr->lMargin1String = NULL; tagPtr->lMargin1 = 0; tagPtr->lMargin2String = NULL; tagPtr->lMargin2 = 0; tagPtr->offsetString = NULL; tagPtr->offset = 0; tagPtr->overstrikeString = NULL; tagPtr->overstrike = 0; tagPtr->rMarginString = NULL; tagPtr->rMargin = 0; tagPtr->spacing1String = NULL; tagPtr->spacing1 = 0; tagPtr->spacing2String = NULL; tagPtr->spacing2 = 0; tagPtr->spacing3String = NULL; tagPtr->spacing3 = 0; tagPtr->tabStringPtr = NULL; tagPtr->tabArrayPtr = NULL; tagPtr->tabStyle = TK_TEXT_TABSTYLE_NONE; tagPtr->underlineString = NULL; tagPtr->underline = 0; tagPtr->elideString = NULL; tagPtr->elide = 0; tagPtr->wrapMode = TEXT_WRAPMODE_NULL; tagPtr->affectsDisplay = 0; tagPtr->affectsDisplayGeometry = 0; textPtr->sharedTextPtr->numTags++; if (!strcmp(tagName, "sel")) { tagPtr->textPtr = textPtr; textPtr->refCount++; } else { Tcl_SetHashValue(hPtr, tagPtr); } tagPtr->optionTable = Tk_CreateOptionTable(textPtr->interp, tagOptionSpecs); return tagPtr; } /* *---------------------------------------------------------------------- * * FindTag -- * * See if tag is defined for a given widget. * * Results: * If tagName is defined in textPtr, a pointer to its TkTextTag structure * is returned. Otherwise NULL is returned and an error message is * recorded in the interp's result unless interp is NULL. * * Side effects: * None. * *---------------------------------------------------------------------- */ static TkTextTag * FindTag( Tcl_Interp *interp, /* Interpreter to use for error message; if * NULL, then don't record an error * message. */ TkText *textPtr, /* Widget in which tag is being used. */ Tcl_Obj *tagName) /* Name of desired tag. */ { Tcl_HashEntry *hPtr; int len; CONST char *str; str = Tcl_GetStringFromObj(tagName, &len); if (len == 3 && !strcmp(str,"sel")) { return textPtr->selTagPtr; } hPtr = Tcl_FindHashEntry(&textPtr->sharedTextPtr->tagTable, Tcl_GetString(tagName)); if (hPtr != NULL) { return (TkTextTag *) Tcl_GetHashValue(hPtr); } if (interp != NULL) { Tcl_AppendResult(interp, "tag \"", Tcl_GetString(tagName), "\" isn't defined in text widget", NULL); } return NULL; } /* *---------------------------------------------------------------------- * * TkTextDeleteTag -- * * This function is called to carry out most actions associated with the * 'tag delete' sub-command. It will remove all evidence of the tag from * the B-tree, and then call TkTextFreeTag to clean up the tag structure * itself. * * The only actions this doesn't carry out it to check if the deletion of * the tag requires something to be re-displayed, and to remove the tag * from the tagTable (hash table) if that is necessary (i.e. if it's not * the 'sel' tag). It is expected that the caller carry out both of these * actions. * * Results: * None. * * Side effects: * Memory and other resources are freed, the B-tree is manipulated. * *---------------------------------------------------------------------- */ void TkTextDeleteTag( TkText *textPtr, /* Info about overall widget. */ register TkTextTag *tagPtr) /* Tag being deleted. */ { TkTextIndex first, last; TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr, 0, 0, &first); TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr, TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr), 0, &last), TkBTreeTag(&first, &last, tagPtr, 0); if (tagPtr == textPtr->selTagPtr) { /* * Send an event that the selection changed. This is equivalent to: * event generate $textWidget <> */ TkTextSelectionEvent(textPtr); } else { /* * Since all peer widgets have an independent "sel" tag, we * don't want removal of one sel tag to remove bindings which * are still valid in other peer widgets. */ if (textPtr->sharedTextPtr->bindingTable != NULL) { Tk_DeleteAllBindings(textPtr->sharedTextPtr->bindingTable, (ClientData) tagPtr->name); } } /* * Update the tag priorities to reflect the deletion of this tag. */ ChangeTagPriority(textPtr, tagPtr, textPtr->sharedTextPtr->numTags-1); textPtr->sharedTextPtr->numTags -= 1; TkTextFreeTag(textPtr, tagPtr); } /* *---------------------------------------------------------------------- * * TkTextFreeTag -- * * This function is called when a tag is deleted to free up the memory * and other resources associated with the tag. * * Results: * None. * * Side effects: * Memory and other resources are freed. * *---------------------------------------------------------------------- */ void TkTextFreeTag( TkText *textPtr, /* Info about overall widget. */ register TkTextTag *tagPtr) /* Tag being deleted. */ { int i; /* * Let Tk do most of the hard work for us. */ Tk_FreeConfigOptions((char *) tagPtr, tagPtr->optionTable, textPtr->tkwin); /* * This associated information is managed by us. */ if (tagPtr->tabArrayPtr != NULL) { ckfree((char *) tagPtr->tabArrayPtr); } /* * Make sure this tag isn't referenced from the 'current' tag array. */ for (i = 0; i < textPtr->numCurTags; i++) { if (textPtr->curTagArrayPtr[i] == tagPtr) { for (; i < textPtr->numCurTags-1; i++) { textPtr->curTagArrayPtr[i] = textPtr->curTagArrayPtr[i+1]; } textPtr->curTagArrayPtr[textPtr->numCurTags-1] = NULL; textPtr->numCurTags--; break; } } /* * If this tag is widget-specific (peer widgets) then clean up the * refCount it holds. */ if (tagPtr->textPtr != NULL) { if (textPtr != tagPtr->textPtr) { Tcl_Panic("Tag being deleted from wrong widget"); } textPtr->refCount--; if (textPtr->refCount == 0) { ckfree((char *) textPtr); } tagPtr->textPtr = NULL; } /* * Finally free the tag's memory. */ ckfree((char *) tagPtr); } /* *---------------------------------------------------------------------- * * SortTags -- * * This function sorts an array of tag pointers in increasing order of * priority, optimizing for the common case where the array is small. * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------- */ static void SortTags( int numTags, /* Number of tag pointers at *tagArrayPtr. */ TkTextTag **tagArrayPtr) /* Pointer to array of pointers. */ { int i, j, prio; register TkTextTag **tagPtrPtr; TkTextTag **maxPtrPtr, *tmp; if (numTags < 2) { return; } if (numTags < 20) { for (i = numTags-1; i > 0; i--, tagArrayPtr++) { maxPtrPtr = tagPtrPtr = tagArrayPtr; prio = tagPtrPtr[0]->priority; for (j = i, tagPtrPtr++; j > 0; j--, tagPtrPtr++) { if (tagPtrPtr[0]->priority < prio) { prio = tagPtrPtr[0]->priority; maxPtrPtr = tagPtrPtr; } } tmp = *maxPtrPtr; *maxPtrPtr = *tagArrayPtr; *tagArrayPtr = tmp; } } else { qsort(tagArrayPtr,(unsigned)numTags,sizeof(TkTextTag *),TagSortProc); } } /* *---------------------------------------------------------------------- * * TagSortProc -- * * This function is called by qsort() when sorting an array of tags in * priority order. * * Results: * The return value is -1 if the first argument should be before the * second element (i.e. it has lower priority), 0 if it's equivalent * (this should never happen!), and 1 if it should be after the second * element. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int TagSortProc( CONST void *first, CONST void *second) /* Elements to be compared. */ { TkTextTag *tagPtr1, *tagPtr2; tagPtr1 = * (TkTextTag **) first; tagPtr2 = * (TkTextTag **) second; return tagPtr1->priority - tagPtr2->priority; } /* *---------------------------------------------------------------------- * * ChangeTagPriority -- * * This function changes the priority of a tag by modifying its priority * and the priorities of other tags that are affected by the change. * * Results: * None. * * Side effects: * Priorities may be changed for some or all of the tags in textPtr. The * tags will be arranged so that there is exactly one tag at each * priority level between 0 and textPtr->sharedTextPtr->numTags-1, with * tagPtr at priority "prio". * *---------------------------------------------------------------------- */ static void ChangeTagPriority( TkText *textPtr, /* Information about text widget. */ TkTextTag *tagPtr, /* Tag whose priority is to be changed. */ int prio) /* New priority for tag. */ { int low, high, delta; register TkTextTag *tagPtr2; Tcl_HashEntry *hPtr; Tcl_HashSearch search; if (prio < 0) { prio = 0; } if (prio >= textPtr->sharedTextPtr->numTags) { prio = textPtr->sharedTextPtr->numTags-1; } if (prio == tagPtr->priority) { return; } if (prio < tagPtr->priority) { low = prio; high = tagPtr->priority-1; delta = 1; } else { low = tagPtr->priority+1; high = prio; delta = -1; } /* * Adjust first the 'sel' tag, then all others from the hash table */ if ((textPtr->selTagPtr->priority >= low) && (textPtr->selTagPtr->priority <= high)) { textPtr->selTagPtr->priority += delta; } for (hPtr = Tcl_FirstHashEntry(&textPtr->sharedTextPtr->tagTable, &search); hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) { tagPtr2 = (TkTextTag *) Tcl_GetHashValue(hPtr); if ((tagPtr2->priority >= low) && (tagPtr2->priority <= high)) { tagPtr2->priority += delta; } } tagPtr->priority = prio; } /* *-------------------------------------------------------------- * * TkTextBindProc -- * * This function is invoked by the Tk dispatcher to handle events * associated with bindings on items. * * Results: * None. * * Side effects: * Depends on the command invoked as part of the binding (if there was * any). * *-------------------------------------------------------------- */ void TkTextBindProc( ClientData clientData, /* Pointer to canvas structure. */ XEvent *eventPtr) /* Pointer to X event that just happened. */ { TkText *textPtr = (TkText *) clientData; int repick = 0; # define AnyButtonMask \ (Button1Mask|Button2Mask|Button3Mask|Button4Mask|Button5Mask) textPtr->refCount++; /* * This code simulates grabs for mouse buttons by keeping track of whether * a button is pressed and refusing to pick a new current character while * a button is pressed. */ if (eventPtr->type == ButtonPress) { textPtr->flags |= BUTTON_DOWN; } else if (eventPtr->type == ButtonRelease) { int mask; switch (eventPtr->xbutton.button) { case Button1: mask = Button1Mask; break; case Button2: mask = Button2Mask; break; case Button3: mask = Button3Mask; break; case Button4: mask = Button4Mask; break; case Button5: mask = Button5Mask; break; default: mask = 0; break; } if ((eventPtr->xbutton.state & AnyButtonMask) == (unsigned) mask) { textPtr->flags &= ~BUTTON_DOWN; repick = 1; } } else if ((eventPtr->type == EnterNotify) || (eventPtr->type == LeaveNotify)) { if (eventPtr->xcrossing.state & AnyButtonMask) { textPtr->flags |= BUTTON_DOWN; } else { textPtr->flags &= ~BUTTON_DOWN; } TkTextPickCurrent(textPtr, eventPtr); goto done; } else if (eventPtr->type == MotionNotify) { if (eventPtr->xmotion.state & AnyButtonMask) { textPtr->flags |= BUTTON_DOWN; } else { textPtr->flags &= ~BUTTON_DOWN; } TkTextPickCurrent(textPtr, eventPtr); } if ((textPtr->numCurTags > 0) && (textPtr->sharedTextPtr->bindingTable != NULL) && (textPtr->tkwin != NULL) && !(textPtr->flags & DESTROYED)) { TagBindEvent(textPtr, eventPtr, textPtr->numCurTags, textPtr->curTagArrayPtr); } if (repick) { unsigned int oldState; oldState = eventPtr->xbutton.state; eventPtr->xbutton.state &= ~(Button1Mask|Button2Mask |Button3Mask|Button4Mask|Button5Mask); if (!(textPtr->flags & DESTROYED)) { TkTextPickCurrent(textPtr, eventPtr); } eventPtr->xbutton.state = oldState; } done: if (--textPtr->refCount == 0) { ckfree((char *) textPtr); } } /* *-------------------------------------------------------------- * * TkTextPickCurrent -- * * Find the character containing the coordinates in an event and place * the "current" mark on that character. If the "current" mark has moved * then generate a fake leave event on the old current character and a * fake enter event on the new current character. * * Results: * None. * * Side effects: * The current mark for textPtr may change. If it does, then the commands * associated with character entry and leave could do just about * anything. For example, the text widget might be deleted. It is up to * the caller to protect itself by incrementing the refCount of the text * widget. * *-------------------------------------------------------------- */ void TkTextPickCurrent( register TkText *textPtr, /* Text widget in which to select current * character. */ XEvent *eventPtr) /* Event describing location of mouse cursor. * Must be EnterWindow, LeaveWindow, * ButtonRelease, or MotionNotify. */ { TkTextIndex index; TkTextTag **oldArrayPtr, **newArrayPtr; TkTextTag **copyArrayPtr = NULL; /* Initialization needed to prevent compiler * warning. */ int numOldTags, numNewTags, i, j, size, nearby; XEvent event; /* * If a button is down, then don't do anything at all; we'll be called * again when all buttons are up, and we can repick then. This implements * a form of mouse grabbing. */ if (textPtr->flags & BUTTON_DOWN) { if (((eventPtr->type == EnterNotify) || (eventPtr->type == LeaveNotify)) && ((eventPtr->xcrossing.mode == NotifyGrab) || (eventPtr->xcrossing.mode == NotifyUngrab))) { /* * Special case: the window is being entered or left because of a * grab or ungrab. In this case, repick after all. Furthermore, * clear BUTTON_DOWN to release the simulated grab. */ textPtr->flags &= ~BUTTON_DOWN; } else { return; } } /* * Save information about this event in the widget in case we have to * synthesize more enter and leave events later (e.g. because a character * was deleted, causing a new character to be underneath the mouse * cursor). Also translate MotionNotify events into EnterNotify events, * since that's what gets reported to event handlers when the current * character changes. */ if (eventPtr != &textPtr->pickEvent) { if ((eventPtr->type == MotionNotify) || (eventPtr->type == ButtonRelease)) { textPtr->pickEvent.xcrossing.type = EnterNotify; textPtr->pickEvent.xcrossing.serial = eventPtr->xmotion.serial; textPtr->pickEvent.xcrossing.send_event = eventPtr->xmotion.send_event; textPtr->pickEvent.xcrossing.display = eventPtr->xmotion.display; textPtr->pickEvent.xcrossing.window = eventPtr->xmotion.window; textPtr->pickEvent.xcrossing.root = eventPtr->xmotion.root; textPtr->pickEvent.xcrossing.subwindow = None; textPtr->pickEvent.xcrossing.time = eventPtr->xmotion.time; textPtr->pickEvent.xcrossing.x = eventPtr->xmotion.x; textPtr->pickEvent.xcrossing.y = eventPtr->xmotion.y; textPtr->pickEvent.xcrossing.x_root = eventPtr->xmotion.x_root; textPtr->pickEvent.xcrossing.y_root = eventPtr->xmotion.y_root; textPtr->pickEvent.xcrossing.mode = NotifyNormal; textPtr->pickEvent.xcrossing.detail = NotifyNonlinear; textPtr->pickEvent.xcrossing.same_screen = eventPtr->xmotion.same_screen; textPtr->pickEvent.xcrossing.focus = False; textPtr->pickEvent.xcrossing.state = eventPtr->xmotion.state; } else { textPtr->pickEvent = *eventPtr; } } /* * Find the new current character, then find and sort all of the tags * associated with it. */ if (textPtr->pickEvent.type != LeaveNotify) { TkTextPixelIndex(textPtr, textPtr->pickEvent.xcrossing.x, textPtr->pickEvent.xcrossing.y, &index, &nearby); if (nearby) { newArrayPtr = NULL; numNewTags = 0; } else { newArrayPtr = TkBTreeGetTags(&index, textPtr, &numNewTags); SortTags(numNewTags, newArrayPtr); } } else { newArrayPtr = NULL; numNewTags = 0; } /* * Resort the tags associated with the previous marked character (the * priorities might have changed), then make a copy of the new tags, and * compare the old tags to the copy, nullifying any tags that are present * in both groups (i.e. the tags that haven't changed). */ SortTags(textPtr->numCurTags, textPtr->curTagArrayPtr); if (numNewTags > 0) { size = numNewTags * sizeof(TkTextTag *); copyArrayPtr = (TkTextTag **) ckalloc((unsigned) size); memcpy(copyArrayPtr, newArrayPtr, (size_t) size); for (i = 0; i < textPtr->numCurTags; i++) { for (j = 0; j < numNewTags; j++) { if (textPtr->curTagArrayPtr[i] == copyArrayPtr[j]) { textPtr->curTagArrayPtr[i] = NULL; copyArrayPtr[j] = NULL; break; } } } } /* * Invoke the binding system with a LeaveNotify event for all of the tags * that have gone away. We have to be careful here, because it's possible * that the binding could do something (like calling tkwait) that * eventually modifies textPtr->curTagArrayPtr. To avoid problems in * situations like this, update curTagArrayPtr to its new value before * invoking any bindings, and don't use it any more here. */ numOldTags = textPtr->numCurTags; textPtr->numCurTags = numNewTags; oldArrayPtr = textPtr->curTagArrayPtr; textPtr->curTagArrayPtr = newArrayPtr; if (numOldTags != 0) { if ((textPtr->sharedTextPtr->bindingTable != NULL) && (textPtr->tkwin != NULL) && !(textPtr->flags & DESTROYED)) { event = textPtr->pickEvent; event.type = LeaveNotify; /* * Always use a detail of NotifyAncestor. Besides being * consistent, this avoids problems where the binding code will * discard NotifyInferior events. */ event.xcrossing.detail = NotifyAncestor; TagBindEvent(textPtr, &event, numOldTags, oldArrayPtr); } ckfree((char *) oldArrayPtr); } /* * Reset the "current" mark (be careful to recompute its location, since * it might have changed during an event binding). Then invoke the binding * system with an EnterNotify event for all of the tags that have just * appeared. */ TkTextPixelIndex(textPtr, textPtr->pickEvent.xcrossing.x, textPtr->pickEvent.xcrossing.y, &index, &nearby); TkTextSetMark(textPtr, "current", &index); if (numNewTags != 0) { if ((textPtr->sharedTextPtr->bindingTable != NULL) && (textPtr->tkwin != NULL) && !(textPtr->flags & DESTROYED) && !nearby) { event = textPtr->pickEvent; event.type = EnterNotify; event.xcrossing.detail = NotifyAncestor; TagBindEvent(textPtr, &event, numNewTags, copyArrayPtr); } ckfree((char *) copyArrayPtr); } } /* *-------------------------------------------------------------- * * TagBindEvent -- * * Trigger given events for all tags that match the relevant bindings. * To handle the "sel" tag correctly in all peer widgets, we must use the * name of the tags as the binding table element. * * Results: * None. * * Side effects: * Almost anything can be triggered by tag bindings, including deletion * of the text widget. * *-------------------------------------------------------------- */ static void TagBindEvent( TkText *textPtr, /* Text widget to fire bindings in. */ XEvent *eventPtr, /* What actually happened. */ int numTags, /* Number of relevant tags. */ TkTextTag **tagArrayPtr) /* Array of relevant tags. */ { #define NUM_BIND_TAGS 10 CONST char *nameArray[NUM_BIND_TAGS]; CONST char **nameArrPtr; int i; /* * Try to avoid allocation unless there are lots of tags. */ if (numTags > NUM_BIND_TAGS) { nameArrPtr = (CONST char **) ckalloc(numTags * sizeof(CONST char *)); } else { nameArrPtr = nameArray; } /* * We use tag names as keys in the hash table. We do this instead of using * the actual tagPtr objects because we want one "sel" tag binding for all * peer widgets, despite the fact that each has its own tagPtr object. */ for (i = 0; i < numTags; i++) { TkTextTag *tagPtr = tagArrayPtr[i]; if (tagPtr != NULL) { nameArrPtr[i] = tagPtr->name; } else { /* * Tag has been deleted elsewhere, and therefore nulled out in * this array. Tk_BindEvent is clever enough to cope with NULLs * being thrown at it. */ nameArrPtr[i] = NULL; } } Tk_BindEvent(textPtr->sharedTextPtr->bindingTable, eventPtr, textPtr->tkwin, numTags, (ClientData *) nameArrPtr); if (numTags > NUM_BIND_TAGS) { ckfree((char *) nameArrPtr); } } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ href='#n1373'>1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384
/******************************************************************************
 *
 * 
 *
 *
 * Copyright (C) 1997-2015 by Dimitri van Heesch.
 *
 * Permission to use, copy, modify, and distribute this software and its
 * documentation under the terms of the GNU General Public License is hereby 
 * granted. No representations are made about the suitability of this software 
 * for any purpose. It is provided "as is" without express or implied warranty.
 * See the GNU General Public License for more details.
 *
 * Documents produced by Doxygen are derivative works derived from the
 * input used in their production; they are not affected by this license.
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <qlist.h>
#include <qarray.h>
#include "ftextstream.h"
#include <qfile.h>

#include "diagram.h"
#include "image.h"
#include "classdef.h"
#include "config.h"
#include "message.h"
#include "util.h"
#include "doxygen.h"
#include "portable.h"
#include "index.h"
#include "classlist.h"

//-----------------------------------------------------------------------------

class DiagramItemList;

/** Class representing a single node in the built-in class diagram */
class DiagramItem 
{
  public:
    DiagramItem(DiagramItem *p,int number,const ClassDef *cd,
                Protection prot,Specifier virt,const char *ts);
   ~DiagramItem();
    QCString label() const;
    QCString fileName() const;
    DiagramItem *parentItem() { return parent; } 
    DiagramItemList *getChildren() { return children; }
    void move(int dx,int dy) { x+=dx; y+=dy; }
    int xPos() const { return x; }
    int yPos() const { return y; }
    int avgChildPos() const;
    int numChildren() const;
    void addChild(DiagramItem *di);
    int number() const { return num; }
    Protection protection() const { return prot; }
    Specifier virtualness() const { return virt; }
    void putInList() { inList=TRUE; }
    bool isInList() const { return inList; } 
    const ClassDef *getClassDef() const { return classDef; }
  private:
    DiagramItemList *children;
    DiagramItem *parent;
    int x,y;
    int num;
    Protection prot;
    Specifier virt;
    QCString templSpec;
    bool inList;
    const ClassDef *classDef;
};

/** Class representing a list of DiagramItem object. */
class DiagramItemList : public QList<DiagramItem>
{
  public:
    DiagramItemList() : QList<DiagramItem>() {}
   ~DiagramItemList() {}
};

/** Class representing a row in the built-in class diagram */
class DiagramRow : public QList<DiagramItem> 
{
  public:
    DiagramRow(TreeDiagram *d,int l) : QList<DiagramItem>() 
    { 
      diagram=d; 
      level=l;
      setAutoDelete(TRUE); 
    }
    void insertClass(DiagramItem *parent,const ClassDef *cd,bool doBases,
                     Protection prot,Specifier virt,const char *ts);
    uint number() { return level; }
  private:
    TreeDiagram *diagram;
    uint level;
};

/** Class representing iterator for the rows in the built-in class diagram. */
class DiagramRowIterator : public QListIterator<DiagramRow>
{
  public:
    DiagramRowIterator(const QList<DiagramRow> &d) 
      : QListIterator<DiagramRow>(d) {}
};

/** Class represeting the tree layout for the built-in class diagram. */
class TreeDiagram : public QList<DiagramRow>
{
  public:
    TreeDiagram(const ClassDef *root,bool doBases);
   ~TreeDiagram();
    void computeLayout();
    uint computeRows();
    //uint computeCols();
    void moveChildren(DiagramItem *root,int dx);
    void computeExtremes(uint *labelWidth,uint *xpos);
    void drawBoxes(FTextStream &t,Image *image,
                   bool doBase,bool bitmap,
                   uint baseRows,uint superRows,
                   uint cellWidth,uint cellHeight,
                   QCString relPath="",
                   bool generateMap=TRUE);
    void drawConnectors(FTextStream &t,Image *image,
                   bool doBase,bool bitmap,
                   uint baseRows,uint superRows,
                   uint cellWidth,uint cellheight);
  private:
    bool layoutTree(DiagramItem *root,int row);
    TreeDiagram &operator=(const TreeDiagram &);
    TreeDiagram(const TreeDiagram &);
};



//-----------------------------------------------------------------------------

const uint maxTreeWidth = 8;
const int gridWidth  = 100;
const int gridHeight = 100;

const uint labelHorSpacing  = 10;  // horizontal distance between labels
const uint labelVertSpacing = 32;  // vertical distance between labels
const uint labelHorMargin   = 6;   // horiz. spacing between label and box
const uint fontHeight       = 12;  // height of a character

//static QCString escapeLatex(const char *s)
//{
//  QCString result;
//  char c;
//  while ((c=*s++))
//  {
//    if (c=='_') result+="\\_";
//           else result+=c;
//  }
//  return result;
//}

static uint protToMask(Protection p)
{
  switch(p)
  {
    case Public:    return 0xffffffff;
    case Package: // package is not possible!
    case Protected: return 0xcccccccc;
    case Private:   return 0xaaaaaaaa;
  }
  return 0;
}

static uint protToColor(Protection p)
{
  switch(p)
  {
    case Public:    return 6;
    case Package: // package is not possible!
    case Protected: return 5;
    case Private:   return 4;
  }
  return 0;
}

static QCString protToString(Protection p)
{
  switch(p)
  {
    case Public:    return "solid";
    case Package: // package is not possible!
    case Protected: return "dashed";
    case Private:   return "dotted";
  }
  return 0;
}

static uint virtToMask(Specifier p)
{
  switch(p)
  {
    case Normal:    return 0xffffffff;
    case Virtual:   return 0xf0f0f0f0;
    default:        return 0;
  }
  return 0;
}

// pre: dil is not empty
static Protection getMinProtectionLevel(DiagramItemList *dil)
{
  QListIterator<DiagramItem> it(*dil);
  DiagramItem *di=it.current();
  Protection result=di->protection();
  for (++it;(di=it.current());++it)
  {
    Protection p=di->protection();
    if (p!=result)
    {
      if (result==Protected && p==Public) result=p;
      else if (result==Private) result=p;
    }
  }
  return result;
}

static void writeBitmapBox(DiagramItem *di,Image *image,
                           int x,int y,int w,int h,bool firstRow,
                           bool hasDocs,bool children=FALSE)
{
  int colFill = hasDocs ? (firstRow ? 0 : 2) : 7;
  int colBorder = (firstRow || !hasDocs) ? 1 : 3;
  int l = Image::stringLength(di->label());
  uint mask=virtToMask(di->virtualness());
  image->fillRect(x+1,y+1,w-2,h-2,colFill,mask);
  image->drawRect(x,y,w,h,colBorder,mask);
  image->writeString(x+(w-l)/2, y+(h-fontHeight)/2, di->label(),1);
  if (children)
  {
    int i;
    for (i=0;i<5;i++)
      image->drawHorzLine(y+h+i-6,x+w-2-i,x+w-2,firstRow?1:3,0xffffffff);
  }
}

static void writeVectorBox(FTextStream &t,DiagramItem *di,
                           float x,float y,bool children=FALSE)
{
  if (di->virtualness()==Virtual) t << "dashed\n";
  t << " (" << di->label() << ") " << x << " " << y << " box\n";
  if (children) t << x << " " << y << " mark\n";
  if (di->virtualness()==Virtual) t << "solid\n";
}

static void writeMapArea(FTextStream &t,const ClassDef *cd,QCString relPath,
                         int x,int y,int w,int h)
{
  if (cd->isLinkable())
  {
    QCString ref=cd->getReference();
    t << "<area ";
    if (!ref.isEmpty()) 
    {
      t << externalLinkTarget();
    }
    t << "href=\"";
    t << externalRef(relPath,ref,TRUE);
    t << cd->getOutputFileBase() << Doxygen::htmlFileExtension;
    if (!cd->anchor().isEmpty())
    {
      t << "#" << cd->anchor();
    }
    t << "\" ";
    QCString tooltip = cd->briefDescriptionAsTooltip();
    if (!tooltip.isEmpty())
    {
      t << "title=\"" << convertToHtml(tooltip) << "\" ";
    }
    t << "alt=\"" << convertToXML(cd->displayName()); 
    t << "\" shape=\"rect\" coords=\"" << x << "," << y << ",";
    t << (x+w) << "," << (y+h) << "\"/>" << endl;
  }
}
//-----------------------------------------------------------------------------

DiagramItem::DiagramItem(DiagramItem *p,int number,const ClassDef *cd,
                         Protection pr,Specifier vi,const char *ts) 
{ 
  parent=p; 
  x=y=0; 
  //name=n;
  num=number;
  children = new DiagramItemList;
  prot=pr;
  virt=vi;
  inList=FALSE;
  classDef=cd;
  templSpec=ts;
}
 
DiagramItem::~DiagramItem() 
{ 
  delete children;
}

QCString DiagramItem::label() const
{
  QCString result;
  if (!templSpec.isEmpty())
  {
    // we use classDef->name() here and not diplayName() in order
    // to get the name used in the inheritance relation.
    QCString n = classDef->name();
    if (/*n.right(2)=="-g" ||*/ n.right(2)=="-p")
    {
      n = n.left(n.length()-2);
    }
    result=insertTemplateSpecifierInScope(n,templSpec);
  }
  else
  {
    result=classDef->displayName();
  }
  if (Config_getBool(HIDE_SCOPE_NAMES)) result=stripScope(result);
  return result;
}

QCString DiagramItem::fileName() const
{
  return classDef->getOutputFileBase();
}

int DiagramItem::avgChildPos() const
{
  DiagramItem *di;
  int c=children->count();
  if (c==0) // no children -> don't move
    return xPos();
  if ((di=children->getFirst())->isInList()) // children should be in a list
    return di->xPos();
  if (c&1) // odd number of children -> get pos of middle child
    return children->at(c/2)->xPos();
  else // even number of children -> get middle of most middle children
    return (children->at(c/2-1)->xPos()+children->at(c/2)->xPos())/2;
}

int DiagramItem::numChildren() const
{
  return children->count();
}

void DiagramItem::addChild(DiagramItem *di)
{
  children->append(di);
}

void DiagramRow::insertClass(DiagramItem *parent,const ClassDef *cd,bool doBases,
                             Protection prot,Specifier virt,const char *ts)
{
  //if (cd->visited) return; // the visit check does not work in case of
                             // multiple inheritance of the same class!
  DiagramItem *di=new DiagramItem(parent, diagram->at(level)->count(), 
                                  cd,prot,virt,ts);
  //cd->visited=TRUE;
  if (parent) parent->addChild(di);
  di->move(count()*gridWidth,level*gridHeight);
  append(di);
  BaseClassList *bcl=doBases ? cd->baseClasses() : cd->subClasses();
  int count=0;
  if (bcl)
  {
    /* there are base/sub classes */
    BaseClassListIterator it(*bcl);
    BaseClassDef *bcd;
    for (;(bcd=it.current());++it)
    {
      ClassDef *ccd=bcd->classDef;
      if (ccd && ccd->isVisibleInHierarchy() /*&& !ccd->visited*/) count++;
    }
  }
  if (count>0 && (prot!=Private || !doBases))
  {
    DiagramRow *row=0;
    if (diagram->count()<=level+1) /* add new row */
    {
      row = new DiagramRow(diagram,level+1);
      diagram->append(row);
    }
    else /* get next row */
    {
      row=diagram->at(level+1);
    }
    /* insert base classes in the next row */
    BaseClassListIterator it(*bcl);
    BaseClassDef *bcd;
    for (;(bcd=it.current());++it)
    {
      ClassDef *ccd=bcd->classDef;
      if (ccd && ccd->isVisibleInHierarchy() /*&& !ccd->visited*/)
      {
        row->insertClass(di,ccd,doBases,bcd->prot,
            doBases?bcd->virt:Normal,
            doBases?bcd->templSpecifiers.data():"");
      }
    }
  }
}

TreeDiagram::TreeDiagram(const ClassDef *root,bool doBases)
{
  setAutoDelete(TRUE); 
  DiagramRow *row=new DiagramRow(this,0);
  append(row);
  row->insertClass(0,root,doBases,Public,Normal,0);
}

TreeDiagram::~TreeDiagram()
{
}


void TreeDiagram::moveChildren(DiagramItem *root,int dx)
{
  DiagramItemList *dil=root->getChildren();
  QListIterator<DiagramItem> it(*dil);
  DiagramItem *di;
  for (;(di=it.current());++it)
  {
    di->move(dx,0);
    moveChildren(di,dx);
  }
}

bool TreeDiagram::layoutTree(DiagramItem *root,int r)
{
  bool moved=FALSE;
  //printf("layoutTree(%s,%d)\n",root->label().data(),r);

  DiagramItemList *dil=root->getChildren(); 
  if (dil->count()>0)
  {
    uint k;
    int pPos=root->xPos();
    int cPos=root->avgChildPos();
    if (pPos>cPos) // move children
    {
      DiagramRow *row=at(r+1);
      //printf("Moving children %d-%d in row %d\n",
      //    dil->getFirst()->number(),row->count()-1,r+1);
      for (k=dil->getFirst()->number();k<row->count();k++)
        row->at(k)->move(pPos-cPos,0);
      moved=TRUE;
    }
    else if (pPos<cPos) // move parent
    {
      DiagramRow *row=at(r);
      //printf("Moving parents %d-%d in row %d\n",
      //    root->number(),row->count()-1,r);
      for (k=root->number();k<row->count();k++)
        row->at(k)->move(cPos-pPos,0);
      moved=TRUE;
    }

    // recurse to children
    QListIterator<DiagramItem> it(*dil);
    DiagramItem *di;
    for (;(di=it.current()) && !moved && !di->isInList();++it)
    {
      moved = layoutTree(di,r+1);
    }
  }
  return moved;
}

void TreeDiagram::computeLayout()
{
  QListIterator<DiagramRow> it(*this);
  DiagramRow *row;
  for (;(row=it.current()) && row->count()<maxTreeWidth;++it) {}
  if (row)
  {
    //printf("computeLayout() list row at %d\n",row->number());
    QListIterator<DiagramItem> rit(*row);
    DiagramItem *di;
    DiagramItem *opi=0;
    int delta=0;
    bool first=TRUE;
    for (;(di=rit.current());++rit)
    {
      DiagramItem *pi=di->parentItem();
      if (pi==opi && !first) { delta-=gridWidth; }
      first = pi!=opi;
      opi=pi;
      di->move(delta,0); // collapse all items in the same
                         // list (except the first)
      di->putInList();
    }
  }

  // re-organize the diagram items
  DiagramItem *root=getFirst()->getFirst();
  while (layoutTree(root,0)) { }

  // move first items of the lists
  if (row)
  {
    QListIterator<DiagramItem> rit(*row);
    DiagramItem *di;
    while ((di=rit.current()))
    {
      DiagramItem *pi=di->parentItem();
      if (pi->getChildren()->count()>1)
      {
        di->move(gridWidth,0);
        while (di && di->parentItem()==pi) { ++rit; di=rit.current(); }
      }
      else
      {
        ++rit;
      }
    }
  }
}

uint TreeDiagram::computeRows()
{
  //printf("TreeDiagram::computeRows()=%d\n",count());
  int count=0;
  QListIterator<DiagramRow> it(*this);
  DiagramRow *row;
  for (;(row=it.current()) && !row->getFirst()->isInList();++it)
  {
    count++;
  }
  //printf("count=%d row=%p\n",count,row);
  if (row)
  {
    int maxListLen=0;
    int curListLen=0;
    DiagramItem *opi=0;
    QListIterator<DiagramItem> rit(*row);
    DiagramItem *di;
    for (;(di=rit.current());++rit)
    {
      if (di->parentItem()!=opi) curListLen=1; else curListLen++; 
      if (curListLen>maxListLen) maxListLen=curListLen;
      opi=di->parentItem();
    }
    //printf("maxListLen=%d\n",maxListLen);
    count+=maxListLen;
  }
  return count;
}

void TreeDiagram::computeExtremes(uint *maxLabelLen,uint *maxXPos)
{
  uint ml=0,mx=0;
  QListIterator<DiagramRow> it(*this);
  DiagramRow *dr;
  bool done=FALSE;
  for (;(dr=it.current()) && !done;++it)
  {
    QListIterator<DiagramItem> rit(*dr);
    DiagramItem *di;
    for (;(di=rit.current());++rit)
    {
      if (di->isInList()) done=TRUE;
      if (maxXPos) mx=QMAX(mx,(uint)di->xPos());
      if (maxLabelLen) ml=QMAX(ml,Image::stringLength(di->label()));
    }
  }
  if (maxLabelLen) *maxLabelLen=ml;
  if (maxXPos)     *maxXPos=mx;
}

void TreeDiagram::drawBoxes(FTextStream &t,Image *image, 
                            bool doBase,bool bitmap,
                            uint baseRows,uint superRows,
                            uint cellWidth,uint cellHeight,
                            QCString relPath,
                            bool generateMap)
{
  QListIterator<DiagramRow> it(*this);
  DiagramRow *dr;
  if (!doBase) ++it;
  bool done=FALSE;
  bool firstRow = doBase;
  for (;(dr=it.current()) && !done;++it)
  {
    int x=0,y=0;
    float xf=0.0f,yf=0.0f;
    QListIterator<DiagramItem> rit(*dr);
    DiagramItem *di = rit.current();
    if (di->isInList()) // put boxes in a list
    {
      DiagramItem *opi=0;
      if (doBase) rit.toLast(); else rit.toFirst();
      while ((di=rit.current()))
      {
        if (di->parentItem()==opi)
        {
          if (bitmap)
          {
            if (doBase) y -= cellHeight+labelVertSpacing;
            else        y += cellHeight+labelVertSpacing;
          }
          else
          {
            if (doBase) yf += 1.0f;
            else        yf -= 1.0f;
          }
        }
        else
        {
          if (bitmap)
          {
            x = di->xPos()*(cellWidth+labelHorSpacing)/gridWidth;
            if (doBase)
            {
              y = image->getHeight()-
                superRows*cellHeight-
                (superRows-1)*labelVertSpacing-
                di->yPos()*(cellHeight+labelVertSpacing)/gridHeight;
            }
            else
            {
              y = (baseRows-1)*(cellHeight+labelVertSpacing)+
                di->yPos()*(cellHeight+labelVertSpacing)/gridHeight;
            }
          }
          else
          {
            xf = di->xPos()/(float)gridWidth;
            if (doBase)
            {
              yf = di->yPos()/(float)gridHeight+superRows-1;
            }
            else
            {
              yf = superRows-1-di->yPos()/(float)gridHeight;
            }
          }
        }
        opi=di->parentItem();
        
        if (bitmap)
        {
          bool hasDocs=di->getClassDef()->isLinkable();
          writeBitmapBox(di,image,x,y,cellWidth,cellHeight,firstRow,
              hasDocs,di->getChildren()->count()>0); 
          if (!firstRow && generateMap) 
            writeMapArea(t,di->getClassDef(),relPath,x,y,cellWidth,cellHeight);
        }
        else
        {
          writeVectorBox(t,di,xf,yf,di->getChildren()->count()>0);
        }
        
        if (doBase) --rit; else ++rit;
      }
      done=TRUE;
    }
    else // draw a tree of boxes
    {
      for (rit.toFirst();(di=rit.current());++rit)
      {
        if (bitmap)
        {
          x = di->xPos()*(cellWidth+labelHorSpacing)/gridWidth;
          if (doBase)
          {
            y = image->getHeight()-
              superRows*cellHeight-
              (superRows-1)*labelVertSpacing-
              di->yPos()*(cellHeight+labelVertSpacing)/gridHeight;
          }
          else
          {
            y = (baseRows-1)*(cellHeight+labelVertSpacing)+
              di->yPos()*(cellHeight+labelVertSpacing)/gridHeight;
          }
          bool hasDocs=di->getClassDef()->isLinkable();
          writeBitmapBox(di,image,x,y,cellWidth,cellHeight,firstRow,hasDocs); 
          if (!firstRow && generateMap) 
            writeMapArea(t,di->getClassDef(),relPath,x,y,cellWidth,cellHeight);
        }
        else
        {
          xf=di->xPos()/(float)gridWidth;
          if (doBase)
          {
            yf = di->yPos()/(float)gridHeight+superRows-1;
          }
          else
          {
            yf = superRows-1-di->yPos()/(float)gridHeight;
          }
          writeVectorBox(t,di,xf,yf);
        }
      }
    }
    firstRow=FALSE;
  }
}

void TreeDiagram::drawConnectors(FTextStream &t,Image *image,
                                 bool doBase,bool bitmap,
                                 uint baseRows,uint superRows,
                                 uint cellWidth,uint cellHeight)
{
  QListIterator<DiagramRow> it(*this);
  DiagramRow *dr;
  bool done=FALSE;
  for (;(dr=it.current()) && !done;++it) // for each row
  {
    QListIterator<DiagramItem> rit(*dr);
    DiagramItem *di = rit.current();
    if (di->isInList()) // row consists of list connectors
    {
      int x=0,y=0,ys=0;
      float xf=0.0f,yf=0.0f,ysf=0.0f;
      for (;(di=rit.current());++rit)
      {
        DiagramItem *pi=di->parentItem();
        DiagramItemList *dil=pi->getChildren();
        DiagramItem *last=dil->getLast();
        if (di==last) // single child
        {
          if (bitmap) // draw pixels
          {
            x = di->xPos()*(cellWidth+labelHorSpacing)/gridWidth + cellWidth/2;
            if (doBase) // base classes
            {
              y = image->getHeight()-
                (superRows-1)*(cellHeight+labelVertSpacing)-
                di->yPos()*(cellHeight+labelVertSpacing)/gridHeight;
              image->drawVertArrow(x,y,y+labelVertSpacing/2,
                                   protToColor(di->protection()),
                                   protToMask(di->protection()));
            }
            else // super classes
            {
              y = (baseRows-1)*(cellHeight+labelVertSpacing)-
                labelVertSpacing/2+
                di->yPos()*(cellHeight+labelVertSpacing)/gridHeight;
              image->drawVertLine(x,y,y+labelVertSpacing/2,
                                  protToColor(di->protection()),
                                  protToMask(di->protection()));
            }
          }
          else // draw vectors
          {
            t << protToString(di->protection()) << endl;
            if (doBase)
            {
              t << "1 " << (di->xPos()/(float)gridWidth) << " " 
                << (di->yPos()/(float)gridHeight+superRows-1) << " in\n";
            }
            else
            {
              t << "0 " << (di->xPos()/(float)gridWidth) << " " 
                << ((float)superRows-0.25-di->yPos()/(float)gridHeight)
                << " in\n";
            }
          }
        }
        else // multiple children, put them in a vertical list
        {
          if (bitmap)
          {
            x = di->parentItem()->xPos()*
              (cellWidth+labelHorSpacing)/gridWidth+cellWidth/2;
            if (doBase) // base classes
            {
              ys = image->getHeight()-
                (superRows-1)*(cellHeight+labelVertSpacing)-
                di->yPos()*(cellHeight+labelVertSpacing)/gridHeight;
              y = ys - cellHeight/2;
            }
            else // super classes
            {
              ys = (baseRows-1)*(cellHeight+labelVertSpacing)+
                di->yPos()*(cellHeight+labelVertSpacing)/gridHeight;
              y = ys + cellHeight/2;
            }
          }
          else
          {
            xf = di->parentItem()->xPos()/(float)gridWidth;
            if (doBase)
            {
              ysf = di->yPos()/(float)gridHeight+superRows-1;
              yf = ysf + 0.5f;
            }
            else
            {
              ysf = (float)superRows-0.25f-di->yPos()/(float)gridHeight;
              yf = ysf - 0.25f;
            }
          }
          while (di!=last) // more children to add
          {
            if (bitmap)
            {
              if (doBase) // base classes
              {
                image->drawHorzArrow(y,x,x+cellWidth/2+labelHorSpacing,
                    protToColor(di->protection()),
                    protToMask(di->protection()));
                y -= cellHeight+labelVertSpacing;
              }
              else // super classes
              {
                image->drawHorzLine(y,x,x+cellWidth/2+labelHorSpacing,
                    protToColor(di->protection()),
                    protToMask(di->protection()));
                y += cellHeight+labelVertSpacing;
              }
            }
            else
            {
              t << protToString(di->protection()) << endl;
              if (doBase)
              {
                t << "1 " << xf << " " << yf << " hedge\n";
                yf += 1.0f;
              }
              else
              {
                t << "0 " << xf << " " << yf << " hedge\n";
                yf -= 1.0f;
              }
            }
            ++rit; di=rit.current();
          }
          // add last horizontal line and a vertical connection line
          if (bitmap)
          {
            if (doBase) // base classes
            {
              image->drawHorzArrow(y,x,x+cellWidth/2+labelHorSpacing,
                  protToColor(di->protection()),
                  protToMask(di->protection()));
              image->drawVertLine(x,y,ys+labelVertSpacing/2,
                  protToColor(getMinProtectionLevel(dil)),
                  protToMask(getMinProtectionLevel(dil)));
            }
            else // super classes
            {
              image->drawHorzLine(y,x,x+cellWidth/2+labelHorSpacing,
                  protToColor(di->protection()),
                  protToMask(di->protection()));
              image->drawVertLine(x,ys-labelVertSpacing/2,y,
                  protToColor(getMinProtectionLevel(dil)),
                  protToMask(getMinProtectionLevel(dil)));
            }
          }
          else
          {
            t << protToString(di->protection()) << endl;
            if (doBase)
            {
              t << "1 " << xf << " " << yf << " hedge\n";
            }
            else
            {
              t << "0 " << xf << " " << yf << " hedge\n";
            }
            t << protToString(getMinProtectionLevel(dil)) << endl;
            if (doBase)
            {
              t << xf << " " << ysf << " " << yf << " vedge\n";
            }
            else
            {
              t << xf << " " << (ysf + 0.25) << " " << yf << " vedge\n";
            }
          }
        }
      }
      done=TRUE; // the tree is drawn now
    }
    else // normal tree connector
    {
      for (;(di=rit.current());++rit)
      {
        int x=0,y=0;
        DiagramItemList *dil = di->getChildren();
        DiagramItem *parent  = di->parentItem();
        if (parent) // item has a parent -> connect to it
        {
          if (bitmap) // draw pixels
          {
            x = di->xPos()*(cellWidth+labelHorSpacing)/gridWidth + cellWidth/2;
            if (doBase) // base classes
            {
              y = image->getHeight()-
                (superRows-1)*(cellHeight+labelVertSpacing)-
                di->yPos()*(cellHeight+labelVertSpacing)/gridHeight;
              /* write input line */
              image->drawVertArrow(x,y,y+labelVertSpacing/2,
                  protToColor(di->protection()),
                  protToMask(di->protection()));
            }
            else // super classes
            {
              y = (baseRows-1)*(cellHeight+labelVertSpacing)-
                labelVertSpacing/2+
                di->yPos()*(cellHeight+labelVertSpacing)/gridHeight;
              /* write output line */
              image->drawVertLine(x,y,y+labelVertSpacing/2,
                  protToColor(di->protection()),
                  protToMask(di->protection()));
            }
          }
          else // draw pixels
          {
            t << protToString(di->protection()) << endl;
            if (doBase)
            {
              t << "1 " << di->xPos()/(float)gridWidth << " " 
                << (di->yPos()/(float)gridHeight+superRows-1) << " in\n";
            }
            else
            {
              t << "0 " << di->xPos()/(float)gridWidth << " " 
                << ((float)superRows-0.25-di->yPos()/(float)gridHeight)
                << " in\n";
            }
          }
        }
        if (dil->count()>0)
        {
          Protection p=getMinProtectionLevel(dil);
          uint mask=protToMask(p);
          uint col=protToColor(p);
          if (bitmap)
          {
            x = di->xPos()*(cellWidth+labelHorSpacing)/gridWidth + cellWidth/2;
            if (doBase) // base classes
            {
              y = image->getHeight()-
                (superRows-1)*(cellHeight+labelVertSpacing)-
                cellHeight-labelVertSpacing/2-
                di->yPos()*(cellHeight+labelVertSpacing)/gridHeight;
              image->drawVertLine(x,y,y+labelVertSpacing/2-1,col,mask);
            }
            else // super classes
            {
              y = (baseRows-1)*(cellHeight+labelVertSpacing)+
                cellHeight+
                di->yPos()*(cellHeight+labelVertSpacing)/gridHeight;
              image->drawVertArrow(x,y,y+labelVertSpacing/2-1,col,mask);
            }
          }
          else
          {
            t << protToString(p) << endl;
            if (doBase)
            {
              t << "0 " << di->xPos()/(float)gridWidth  << " " 
                << (di->yPos()/(float)gridHeight+superRows-1) << " out\n";
            }
            else
            {
              t << "1 " << di->xPos()/(float)gridWidth  << " " 
                << ((float)superRows-1.75-di->yPos()/(float)gridHeight)
                << " out\n";
            }
          }
          /* write input line */
          DiagramItem *first = dil->getFirst();
          DiagramItem *last  = dil->getLast();
          if (first!=last && !first->isInList()) /* connect with all base classes */
          {
            if (bitmap)
            {
              int xs = first->xPos()*(cellWidth+labelHorSpacing)/gridWidth
                + cellWidth/2;
              int xe = last->xPos()*(cellWidth+labelHorSpacing)/gridWidth
                + cellWidth/2; 
              if (doBase) // base classes
              {
                image->drawHorzLine(y,xs,xe,col,mask); 
              }
              else // super classes
              {
                image->drawHorzLine(y+labelVertSpacing/2,xs,xe,col,mask); 
              }
            }
            else
            {
              t << protToString(p) << endl;
              if (doBase)
              {
                t << first->xPos()/(float)gridWidth << " " 
                  << last->xPos()/(float)gridWidth << " "
                  << (first->yPos()/(float)gridHeight+superRows-1) 
                  << " conn\n";
              }
              else
              {
                t << first->xPos()/(float)gridWidth << " " 
                  << last->xPos()/(float)gridWidth << " "
                  << ((float)superRows-first->yPos()/(float)gridHeight)
                  << " conn\n";
              }
            }
          }
        }
      }
    }
  }
}


void clearVisitFlags()
{
  ClassSDict::Iterator cli(*Doxygen::classSDict);
  ClassDef *cd;
  for (;(cd=cli.current());++cli)
  {
    cd->setVisited(FALSE);
  }
}

ClassDiagram::ClassDiagram(const ClassDef *root)
{
  clearVisitFlags();
  base  = new TreeDiagram(root,TRUE);
  base->computeLayout();
  clearVisitFlags();
  super = new TreeDiagram(root,FALSE);
  super->computeLayout();
  DiagramItem *baseItem  = base->getFirst()->getFirst();
  DiagramItem *superItem = super->getFirst()->getFirst();
  int xbase  = baseItem->xPos();
  int xsuper = superItem->xPos();
  if (xbase>xsuper)
  {
    superItem->move(xbase-xsuper,0);
    super->moveChildren(superItem,xbase-xsuper);
  }
  else if (xbase<xsuper)
  {
    baseItem->move(xsuper-xbase,0);
    base->moveChildren(baseItem,xsuper-xbase);
  }
}

ClassDiagram::~ClassDiagram()
{
  delete base;
  delete super;
}

void ClassDiagram::writeFigure(FTextStream &output,const char *path,
                               const char *fileName) const
{
  uint baseRows=base->computeRows();
  uint superRows=super->computeRows();
  uint baseMaxX, baseMaxLabelWidth, superMaxX, superMaxLabelWidth;
  base->computeExtremes(&baseMaxLabelWidth,&baseMaxX);
  super->computeExtremes(&superMaxLabelWidth,&superMaxX);

  uint rows=baseRows+superRows-1;
  uint cols=(QMAX(baseMaxX,superMaxX)+gridWidth*2-1)/gridWidth;
  
  // Estimate the image aspect width and height in pixels.
  uint estHeight = rows*40;
  uint estWidth  = cols*(20+QMAX(baseMaxLabelWidth,superMaxLabelWidth));
  //printf("Estimated size %d x %d\n",estWidth,estHeight);
  
  const float pageWidth = 14.0f; // estimated page width in cm.
                                 // Somewhat lower to deal with estimation
                                 // errors. 
  
  // compute the image height in centimeters based on the estimates
  float realHeight = QMIN(rows,12); // real height in cm
  float realWidth  = realHeight * estWidth/(float)estHeight;
  if (realWidth>pageWidth) // assume that the page width is about 15 cm
  {
    realHeight*=pageWidth/realWidth; 
    realWidth=pageWidth;
  }

  //output << "}\n";
  output << "\\begin{figure}[H]\n"
            "\\begin{center}\n"
            "\\leavevmode\n";
  output << "\\includegraphics[height=" << realHeight << "cm]{" 
                                        << fileName << "}" << endl;
  output << "\\end{center}\n"
            "\\end{figure}\n";
  
  //printf("writeFigure rows=%d cols=%d\n",rows,cols);

  QCString epsBaseName=(QCString)path+"/"+fileName;
  QCString epsName=epsBaseName+".eps";
  QFile f1;
  f1.setName(epsName.data());
  if (!f1.open(IO_WriteOnly))
  {
    err("Could not open file %s for writing\n",f1.name().data());
    exit(1);
  }
  FTextStream t(&f1);
  
  //printf("writeEPS() rows=%d cols=%d\n",rows,cols);
  
  // generate EPS header and postscript variables and procedures
  
  t << "%!PS-Adobe-2.0 EPSF-2.0\n";
  t << "%%Title: ClassName\n";
  t << "%%Creator: Doxygen\n";
  t << "%%CreationDate: Time\n";
  t << "%%For: \n";
  t << "%Magnification: 1.00\n";
  t << "%%Orientation: Portrait\n";
  t << "%%BoundingBox: 0 0 500 " << estHeight*500.0/(float)estWidth << "\n";
  t << "%%Pages: 0\n";
  t << "%%BeginSetup\n";
  t << "%%EndSetup\n";
  t << "%%EndComments\n";
  t << "\n";
  t << "% ----- variables -----\n";
  t << "\n";
  t << "/boxwidth 0 def\n";
  t << "/boxheight 40 def\n";
  t << "/fontheight 24 def\n";
  t << "/marginwidth 10 def\n";
  t << "/distx 20 def\n";
  t << "/disty 40 def\n";
  t << "/boundaspect " << estWidth/(float)estHeight << " def  % aspect ratio of the BoundingBox (width/height)\n";
  t << "/boundx 500 def\n";
  t << "/boundy boundx boundaspect div def\n";
  t << "/xspacing 0 def\n";
  t << "/yspacing 0 def\n";
  t << "/rows " << rows << " def\n";
  t << "/cols " << cols << " def\n";
  t << "/scalefactor 0 def\n";
  t << "/boxfont /Times-Roman findfont fontheight scalefont def\n";
  t << "\n";
  t << "% ----- procedures -----\n";
  t << "\n";
  t << "/dotted { [1 4] 0 setdash } def\n";
  t << "/dashed { [5] 0 setdash } def\n";
  t << "/solid  { [] 0 setdash } def\n";
  t << "\n";
  t << "/max % result = MAX(arg1,arg2)\n";
  t << "{\n";
  t << "  /a exch def\n";
  t << "  /b exch def\n";
  t << "  a b gt {a} {b} ifelse\n";
  t << "} def\n";
  t << "\n";
  t << "/xoffset % result = MAX(0,(scalefactor-(boxwidth*cols+distx*(cols-1)))/2)\n";
  t << "{\n";
  t << "  0 scalefactor boxwidth cols mul distx cols 1 sub mul add sub 2 div max\n";
  t << "} def\n";
  t << "\n";
  t << "/cw % boxwidth = MAX(boxwidth, stringwidth(arg1))\n";
  t << "{\n";
  t << "  /str exch def\n";
  t << "  /boxwidth boxwidth str stringwidth pop max def\n";
  t << "} def\n";
  t << "\n";
  t << "/box % draws a box with text 'arg1' at grid pos (arg2,arg3)\n";
  t << "{ gsave\n";
  t << "  2 setlinewidth\n";
  t << "  newpath\n";
  t << "  exch xspacing mul xoffset add\n";
  t << "  exch yspacing mul\n";
  t << "  moveto\n";
  t << "  boxwidth 0 rlineto \n";
  t << "  0 boxheight rlineto \n";
  t << "  boxwidth neg 0 rlineto \n";
  t << "  0 boxheight neg rlineto \n";
  t << "  closepath\n";
  t << "  dup stringwidth pop neg boxwidth add 2 div\n";
  t << "  boxheight fontheight 2 div sub 2 div\n";
  t << "  rmoveto show stroke\n";
  t << "  grestore\n";
  t << "} def  \n";
  t << "\n";
  t << "/mark\n";
  t << "{ newpath\n";
  t << "  exch xspacing mul xoffset add boxwidth add\n";
  t << "  exch yspacing mul\n";
  t << "  moveto\n";
  t << "  0 boxheight 4 div rlineto\n";
  t << "  boxheight neg 4 div boxheight neg 4 div rlineto\n";
  t << "  closepath\n";
  t << "  eofill\n";
  t << "  stroke\n";
  t << "} def\n";
  t << "\n";
  t << "/arrow\n";
  t << "{ newpath\n";
  t << "  moveto\n";
  t << "  3 -8 rlineto\n";
  t << "  -6 0 rlineto\n";
  t << "  3 8 rlineto\n";
  t << "  closepath\n";
  t << "  eofill\n";
  t << "  stroke\n";
  t << "} def\n";
  t << "\n";
  t << "/out % draws an output connector for the block at (arg1,arg2)\n";
  t << "{\n";
  t << "  newpath\n";
  t << "  exch xspacing mul xoffset add boxwidth 2 div add\n";
  t << "  exch yspacing mul boxheight add\n";
  t << "  /y exch def\n";
  t << "  /x exch def\n";
  t << "  x y moveto\n";
  t << "  0 disty 2 div rlineto \n";
  t << "  stroke\n";
  t << "  1 eq { x y disty 2 div add arrow } if\n";
  t << "} def\n";
  t << "\n";
  t << "/in % draws an input connector for the block at (arg1,arg2)\n";
  t << "{\n";
  t << "  newpath\n";
  t << "  exch xspacing mul xoffset add boxwidth 2 div add\n";
  t << "  exch yspacing mul disty 2 div sub\n";
  t << "  /y exch def\n";
  t << "  /x exch def\n";
  t << "  x y moveto\n";
  t << "  0 disty 2 div rlineto\n";
  t << "  stroke\n";
  t << "  1 eq { x y disty 2 div add arrow } if\n";
  t << "} def\n";
  t << "\n";
  t << "/hedge\n";
  t << "{\n";
  t << "  exch xspacing mul xoffset add boxwidth 2 div add\n";
  t << "  exch yspacing mul boxheight 2 div sub\n";
  t << "  /y exch def\n";
  t << "  /x exch def\n";
  t << "  newpath\n";
  t << "  x y moveto\n";
  t << "  boxwidth 2 div distx add 0 rlineto\n";
  t << "  stroke\n";
  t << "  1 eq\n";
  t << "  { newpath x boxwidth 2 div distx add add y moveto\n";
  t << "    -8 3 rlineto\n";
  t << "    0 -6 rlineto\n";
  t << "    8 3 rlineto\n";
  t << "    closepath\n";
  t << "    eofill\n";
  t << "    stroke\n";
  t << "  } if\n";
  t << "} def\n";
  t << "\n";
  t << "/vedge\n";
  t << "{\n";
  t << "  /ye exch def\n";
  t << "  /ys exch def\n";
  t << "  /xs exch def\n";
  t << "  newpath\n";
  t << "  xs xspacing mul xoffset add boxwidth 2 div add dup\n";
  t << "  ys yspacing mul boxheight 2 div sub\n";
  t << "  moveto\n";
  t << "  ye yspacing mul boxheight 2 div sub\n";
  t << "  lineto\n";
  t << "  stroke\n";
  t << "} def\n";
  t << "\n";
  t << "/conn % connections the blocks from col 'arg1' to 'arg2' of row 'arg3'\n";
  t << "{\n";
  t << "  /ys exch def\n";
  t << "  /xe exch def\n";
  t << "  /xs exch def\n";
  t << "  newpath\n";
  t << "  xs xspacing mul xoffset add boxwidth 2 div add\n";
  t << "  ys yspacing mul disty 2 div sub\n";
  t << "  moveto\n";
  t << "  xspacing xe xs sub mul 0\n";
  t << "  rlineto\n";
  t << "  stroke\n";
  t << "} def\n";
  t << "\n";
  t << "% ----- main ------\n";
  t << "\n";
  t << "boxfont setfont\n";
  t << "1 boundaspect scale\n";


  bool done=FALSE;
  QListIterator<DiagramRow> bit(*base);
  DiagramRow *dr;
  for (;(dr=bit.current()) && !done;++bit)
  {
    QListIterator<DiagramItem> rit(*dr);
    DiagramItem *di;
    for (;(di=rit.current());++rit)
    {
      done=di->isInList();
      t << "(" << di->label() << ") cw\n";
    }
  }
  QListIterator<DiagramRow> sit(*super);
  ++sit;
  done=FALSE;
  for (;(dr=sit.current()) && !done;++sit)
  {
    QListIterator<DiagramItem> rit(*dr);
    DiagramItem *di;
    for (;(di=rit.current());++rit)
    {
      done=di->isInList();
      t << "(" << di->label() << ") cw\n";
    }
  }

  t << "/boxwidth boxwidth marginwidth 2 mul add def\n"
    << "/xspacing boxwidth distx add def\n"
    << "/yspacing boxheight disty add def\n"
    << "/scalefactor \n"
    << "  boxwidth cols mul distx cols 1 sub mul add\n"
    << "  boxheight rows mul disty rows 1 sub mul add boundaspect mul \n"
    << "  max def\n"
    << "boundx scalefactor div boundy scalefactor div scale\n";
  
  t << "\n% ----- classes -----\n\n";
  base->drawBoxes(t,0,TRUE,FALSE,baseRows,superRows,0,0);
  super->drawBoxes(t,0,FALSE,FALSE,baseRows,superRows,0,0);
  
  t << "\n% ----- relations -----\n\n";
  base->drawConnectors(t,0,TRUE,FALSE,baseRows,superRows,0,0);
  super->drawConnectors(t,0,FALSE,FALSE,baseRows,superRows,0,0);

  f1.close();
  if (Config_getBool(USE_PDFLATEX))
  {
    QCString epstopdfArgs(4096);
    epstopdfArgs.sprintf("\"%s.eps\" --outfile=\"%s.pdf\"",
                   epsBaseName.data(),epsBaseName.data());
    //printf("Converting eps using '%s'\n",epstopdfArgs.data());
    portable_sysTimerStart();
    if (portable_system("epstopdf",epstopdfArgs)!=0)
    {
       err("Problems running epstopdf. Check your TeX installation!\n");
       portable_sysTimerStop();
       return;
    }
    portable_sysTimerStop();
  }
}


void ClassDiagram::writeImage(FTextStream &t,const char *path,
                              const char *relPath,const char *fileName, 
                              bool generateMap) const
{
  uint baseRows=base->computeRows();
  uint superRows=super->computeRows();
  uint rows=baseRows+superRows-1;

  uint lb,ls,xb,xs;
  base->computeExtremes(&lb,&xb);
  super->computeExtremes(&ls,&xs);
 
  uint cellWidth  = QMAX(lb,ls)+labelHorMargin*2;
  uint maxXPos    = QMAX(xb,xs);
  uint labelVertMargin = 6; //QMAX(6,(cellWidth-fontHeight)/6); // aspect at least 1:3
  uint cellHeight = labelVertMargin*2+fontHeight;
  uint imageWidth = (maxXPos+gridWidth)*cellWidth/gridWidth+
                    (maxXPos*labelHorSpacing)/gridWidth;
  uint imageHeight = rows*cellHeight+(rows-1)*labelVertSpacing;

  Image image(imageWidth,imageHeight);

  base->drawBoxes(t,&image,TRUE,TRUE,baseRows,superRows,cellWidth,cellHeight,relPath,generateMap);
  super->drawBoxes(t,&image,FALSE,TRUE,baseRows,superRows,cellWidth,cellHeight,relPath,generateMap);
  base->drawConnectors(t,&image,TRUE,TRUE,baseRows,superRows,cellWidth,cellHeight);
  super->drawConnectors(t,&image,FALSE,TRUE,baseRows,superRows,cellWidth,cellHeight);

#define IMAGE_EXT ".png"
  image.save((QCString)path+"/"+fileName+IMAGE_EXT);
  Doxygen::indexList->addImageFile(QCString(fileName)+IMAGE_EXT);
}