/* * bltHierbox.c -- * * This module implements an hierarchy widget for the BLT toolkit. * * Copyright -1998 Lucent Technologies, Inc. * * Permission to use, copy, modify, and distribute this software and * its documentation for any purpose and without fee is hereby * granted, provided that the above copyright notice appear in all * copies and that both that the copyright notice and warranty * disclaimer appear in supporting documentation, and that the names * of Lucent Technologies or any of their entities not be used in * advertising or publicity pertaining to distribution of the software * without specific, written prior permission. * * Lucent Technologies disclaims all warranties with regard to this * software, including all implied warranties of merchantability and * fitness. In no event shall Lucent Technologies be liable for any * special, indirect or consequential damages or any damages * whatsoever resulting from loss of use, data or profits, whether in * an action of contract, negligence or other tortuous action, arising * out of or in connection with the use or performance of this * software. * * The "hierbox" widget was created by George A. Howlett. */ /* * TODO: * * BUGS: * 1. "open" operation should change scroll offset so that as many * new entries (up to half a screen) can be seen. * 2. "open" needs to adjust the scrolloffset so that the same entry * is seen at the same place. */ #include "bltInt.h" #ifndef NO_HIERBOX #include "bltBind.h" #include "bltImage.h" #include "bltHash.h" #include "bltChain.h" #include "bltList.h" #include "bltTile.h" #include #include #if HAVE_UTF #else #define Tcl_NumUtfChars(s,n) (((n) == -1) ? strlen((s)) : (n)) #define Tcl_UtfAtIndex(s,i) ((s) + (i)) #endif #define SEPARATOR_NONE ((char *)-1) #define SEPARATOR_LIST ((char *)NULL) #define APPEND (-1) /* * The macro below is used to modify a "char" value (e.g. by casting * it to an unsigned character) so that it can be used safely with * macros such as isspace. */ #define UCHAR(c) ((unsigned char) (c)) #define APPLY_BEFORE (1<<0) #define APPLY_OPEN_ONLY (1<<1) #define APPLY_RECURSE (1<<2) #define BUTTON_IPAD 1 #define BUTTON_SIZE 7 #define INSET_PAD 2 #define ICON_PADX 2 #define ICON_PADY 1 #define LABEL_PADX 3 #define LABEL_PADY 0 #define FOCUS_WIDTH 1 #define CLAMP(val,low,hi) \ (((val) < (low)) ? (low) : ((val) > (hi)) ? (hi) : (val)) #define ODD(x) ((x) | 0x01) #define TOGGLE(x, mask) \ (((x) & (mask)) ? ((x) & ~(mask)) : ((x) | (mask))) #define VPORTWIDTH(h) (Tk_Width((h)->tkwin) - 2 * (h)->inset) #define VPORTHEIGHT(h) (Tk_Height((h)->tkwin) - 2 * (h)->inset) #define WORLDX(h, sx) ((sx) - (h)->inset + (h)->xOffset) #define WORLDY(h, sy) ((sy) - (h)->inset + (h)->yOffset) #define SCREENX(h, wx) ((wx) - (h)->xOffset + (h)->inset) #define SCREENY(h, wy) ((wy) - (h)->yOffset + (h)->inset) #define LEVELWIDTH(d) (hboxPtr->levelInfo[(d)].width) #define LEVELX(d) (hboxPtr->levelInfo[(d)].x) #define GETFONT(h, f) (((f) == NULL) ? (h)->defFont : (f)) #define GETCOLOR(h, c) (((c) == NULL) ? (h)->defColor : (c)) /* * ---------------------------------------------------------------------------- * * Internal hierarchy widget flags: * * HIERBOX_LAYOUT The layout of the hierarchy needs to be * recomputed. * * HIERBOX_REDRAW A redraw request is pending for the widget. * * HIERBOX_XSCROLL X-scroll request is pending. * HIERBOX_YSCROLL Y-scroll request is pending. * HIERBOX_SCROLL Both X-scroll and Y-scroll requests are * pending. * * HIERBOX_FOCUS The widget is receiving keyboard events. * Draw the focus highlight border around the * widget. * * HIERBOX_DIRTY The hierarchy has changed, possibly invalidating * locations and pointers to entries. This widget * need to recompute its layout. * * HIERBOX_BORDERS The borders of the widget (highlight ring and * 3-D border) need to be redrawn. * * * Selection related flags: * * SELECTION_EXPORT Export the selection to X. * * SELECTION_PENDING A selection command idle task is pending. * * SELECTION_CLEAR Entry's selection flag is to be cleared. * * SELECTION_SET Entry's selection flag is to be set. * * SELECTION_TOGGLE Entry's selection flag is to be toggled. * * SELECTION_MASK Mask of selection set/clear/toggle flags. * * --------------------------------------------------------------------------- */ #define HIERBOX_LAYOUT (1<<0) #define HIERBOX_REDRAW (1<<1) #define HIERBOX_XSCROLL (1<<2) #define HIERBOX_YSCROLL (1<<3) #define HIERBOX_SCROLL (HIERBOX_XSCROLL | HIERBOX_YSCROLL) #define HIERBOX_FOCUS (1<<4) #define HIERBOX_DIRTY (1<<5) #define HIERBOX_BORDERS (1<<6) #define SELECTION_PENDING (1<<15) #define SELECTION_EXPORT (1<<16) #define SELECTION_CLEAR (1<<17) #define SELECTION_SET (1<<18) #define SELECTION_TOGGLE (SELECTION_SET | SELECTION_CLEAR) #define SELECTION_MASK (SELECTION_SET | SELECTION_CLEAR) /* * ------------------------------------------------------------------------- * * Internal entry flags: * * ENTRY_BUTTON Indicates that a button needs to be * drawn for this entry. * * ENTRY_OPEN Indicates that the entry is open and * its subentries should also be displayed. * * ENTRY_MAPPED Indicates that the entry is mapped (i.e. * can be viewed by opening or scrolling. * * BUTTON_AUTO * BUTTON_SHOW * BUTTON_MASK * * ------------------------------------------------------------------------- */ #define ENTRY_BUTTON (1<<0) #define ENTRY_OPEN (1<<2) #define ENTRY_MAPPED (1<<3) #define BUTTON_AUTO (1<<8) #define BUTTON_SHOW (1<<9) #define BUTTON_MASK (BUTTON_AUTO | BUTTON_SHOW) #define DEF_ENTRY_BACKGROUND (char *)NULL #define DEF_ENTRY_BG_MONO (char *)NULL #define DEF_ENTRY_BIND_TAGS "Entry all" #define DEF_ENTRY_BUTTON "auto" #define DEF_ENTRY_COMMAND (char *)NULL #define DEF_ENTRY_DATA (char *)NULL #define DEF_ENTRY_FOREGROUND (char *)NULL #define DEF_ENTRY_FG_MONO (char *)NULL #define DEF_ENTRY_FONT (char *)NULL #define DEF_ENTRY_ICONS (char *)NULL #define DEF_ENTRY_ACTIVE_ICONS (char *)NULL #define DEF_ENTRY_IMAGES (char *)NULL #define DEF_ENTRY_LABEL (char *)NULL #define DEF_ENTRY_SHADOW_COLOR (char *)NULL #define DEF_ENTRY_SHADOW_MONO (char *)NULL #define DEF_ENTRY_TEXT (char *)NULL #define DEF_BUTTON_ACTIVE_BACKGROUND RGB_WHITE #define DEF_BUTTON_ACTIVE_BG_MONO STD_ACTIVE_BG_MONO #define DEF_BUTTON_ACTIVE_FOREGROUND STD_ACTIVE_FOREGROUND #define DEF_BUTTON_ACTIVE_FG_MONO STD_ACTIVE_FG_MONO #define DEF_BUTTON_BORDERWIDTH "1" #if (TK_MAJOR_VERSION == 4) #define DEF_BUTTON_CLOSE_RELIEF "flat" #define DEF_BUTTON_OPEN_RELIEF "flat" #else #define DEF_BUTTON_CLOSE_RELIEF "solid" #define DEF_BUTTON_OPEN_RELIEF "solid" #endif #define DEF_BUTTON_IMAGES (char *)NULL #define DEF_BUTTON_NORMAL_BACKGROUND RGB_WHITE #define DEF_BUTTON_NORMAL_BG_MONO STD_NORMAL_BG_MONO #define DEF_BUTTON_NORMAL_FOREGROUND STD_NORMAL_FOREGROUND #define DEF_BUTTON_NORMAL_FG_MONO STD_NORMAL_FG_MONO #define DEF_BUTTON_SIZE "7" #define DEF_HIERBOX_ACTIVE_BACKGROUND RGB_LIGHTBLUE0 #define DEF_HIERBOX_ACTIVE_SELECT_BACKGROUND RGB_LIGHTBLUE1 #define DEF_HIERBOX_ACTIVE_BG_MONO STD_ACTIVE_BG_MONO #define DEF_HIERBOX_ACTIVE_FOREGROUND RGB_BLACK #define DEF_HIERBOX_ACTIVE_RELIEF "flat" #define DEF_HIERBOX_ACTIVE_STIPPLE "gray25" #define DEF_HIERBOX_ALLOW_DUPLICATES "yes" #define DEF_HIERBOX_BACKGROUND RGB_WHITE #define DEF_HIERBOX_BORDERWIDTH STD_BORDERWIDTH #define DEF_HIERBOX_COMMAND (char *)NULL #define DEF_HIERBOX_CURSOR (char *)NULL #define DEF_HIERBOX_DASHES "dot" #define DEF_HIERBOX_EXPORT_SELECTION "no" #define DEF_HIERBOX_FOREGROUND STD_NORMAL_FOREGROUND #define DEF_HIERBOX_FG_MONO STD_NORMAL_FG_MONO #define DEF_HIERBOX_FOCUS_DASHES "dot" #define DEF_HIERBOX_FOCUS_EDIT "no" #define DEF_HIERBOX_FOCUS_FOREGROUND STD_ACTIVE_FOREGROUND #define DEF_HIERBOX_FOCUS_FG_MONO STD_ACTIVE_FG_MONO #define DEF_HIERBOX_FONT STD_FONT #define DEF_HIERBOX_HEIGHT "400" #define DEF_HIERBOX_HIDE_ROOT "no" #define DEF_HIERBOX_HIGHLIGHT_BACKGROUND STD_NORMAL_BACKGROUND #define DEF_HIERBOX_HIGHLIGHT_BG_MONO STD_NORMAL_BG_MONO #define DEF_HIERBOX_HIGHLIGHT_COLOR RGB_BLACK #define DEF_HIERBOX_HIGHLIGHT_WIDTH "2" #define DEF_HIERBOX_LINE_COLOR RGB_GREY50 #define DEF_HIERBOX_LINE_MONO STD_NORMAL_FG_MONO #define DEF_HIERBOX_LINE_SPACING "0" #define DEF_HIERBOX_LINE_WIDTH "1" #define DEF_HIERBOX_MAKE_PATH "no" #define DEF_HIERBOX_NORMAL_BACKGROUND STD_NORMAL_BACKGROUND #define DEF_HIERBOX_NORMAL_FG_MONO STD_ACTIVE_FG_MONO #define DEF_HIERBOX_RELIEF "sunken" #define DEF_HIERBOX_SCROLL_INCREMENT "0" #define DEF_HIERBOX_SCROLL_MODE "hierbox" #define DEF_HIERBOX_SCROLL_TILE "yes" #define DEF_HIERBOX_SELECT_BACKGROUND RGB_LIGHTSKYBLUE1 #define DEF_HIERBOX_SELECT_BG_MONO STD_SELECT_BG_MONO #define DEF_HIERBOX_SELECT_BORDERWIDTH "1" #define DEF_HIERBOX_SELECT_CMD (char *)NULL #define DEF_HIERBOX_SELECT_FOREGROUND STD_SELECT_FOREGROUND #define DEF_HIERBOX_SELECT_FG_MONO STD_SELECT_FG_MONO #define DEF_HIERBOX_SELECT_MODE "single" #define DEF_HIERBOX_SELECT_RELIEF "flat" #define DEF_HIERBOX_SEPARATOR (char *)NULL #define DEF_HIERBOX_SHOW_ROOT "yes" #define DEF_HIERBOX_SORT_SELECTION "no" #define DEF_HIERBOX_TAKE_FOCUS "1" #define DEF_HIERBOX_TEXT_COLOR STD_NORMAL_FOREGROUND #define DEF_HIERBOX_TEXT_MONO STD_NORMAL_FG_MONO #define DEF_HIERBOX_TILE (char *)NULL #define DEF_HIERBOX_TRIMLEFT "" #define DEF_HIERBOX_WIDTH "200" typedef struct HierboxStruct Hierbox; typedef struct EntryStruct Entry; typedef struct TreeStruct Tree; typedef int (CompareProc) _ANSI_ARGS_((Tcl_Interp *interp, char *name, char *pattern)); typedef int (ApplyProc) _ANSI_ARGS_((Hierbox *hboxPtr, Tree * treePtr)); typedef Tree *(IterProc) _ANSI_ARGS_((Tree * treePtr, unsigned int mask)); extern Tk_CustomOption bltDashesOption; extern Tk_CustomOption bltDistanceOption; extern Tk_CustomOption bltShadowOption; extern Tk_CustomOption bltTileOption; extern Tk_CustomOption bltUidOption; static Tk_OptionParseProc StringToButton; static Tk_OptionPrintProc ButtonToString; static Tk_OptionParseProc StringToImages; static Tk_OptionPrintProc ImagesToString; static Tk_OptionParseProc StringToScrollMode; static Tk_OptionPrintProc ScrollModeToString; static Tk_OptionParseProc StringToSeparator; static Tk_OptionPrintProc SeparatorToString; /* * Contains a pointer to the widget that's currently being configured. * This is used in the custom configuration parse routine for images. */ static Hierbox *hierBox; static Tk_CustomOption imagesOption = { StringToImages, ImagesToString, (ClientData)&hierBox, }; static Tk_CustomOption buttonOption = { StringToButton, ButtonToString, (ClientData)0, }; static Tk_CustomOption scrollModeOption = { StringToScrollMode, ScrollModeToString, (ClientData)0, }; static Tk_CustomOption separatorOption = { StringToSeparator, SeparatorToString, (ClientData)0, }; /* * CachedImage -- * * Since instances of the same Tk image can be displayed in * different windows with possibly different color palettes, Tk * internally stores each instance in a linked list. But if * the instances are used in the same widget and therefore use * the same color palette, this adds a lot of overhead, * especially when deleting instances from the linked list. * * For the hierbox widget, we never need more than a single * instance of an image, regardless of how many times it's used. * So one solution is to cache the image, maintaining a reference * count for each image used in the widget. It's likely that the * hierarchy widget will use many instances of the same image * (for example the open/close icons). */ typedef struct CachedImageStruct { Tk_Image tkImage; /* The Tk image being cached. */ int refCount; /* Reference count for this image. */ short int width, height; /* Dimensions of the cached image. */ Blt_HashEntry *hashPtr; /* Hash table pointer to the image. */ } *CachedImage; #define ImageHeight(image) ((image)->height) #define ImageWidth(image) ((image)->width) #define ImageBits(image) ((image)->tkImage) /* * Tree -- * * Structure representing a general order tree. * */ struct TreeStruct { Blt_Uid nameId; /* String identifying the node within this * hierarchy. Does not necessarily have to * be unique. */ Entry *entryPtr; /* Points to the entry structure at this * node. */ Tree *parentPtr; /* Pointer to the parent node. * If NULL, this is the root node. */ Blt_Chain *chainPtr; /* Pointer to list of child nodes. The list * isn't allocated until the child nodes are * to be inserted. */ Blt_ChainLink *linkPtr; /* List link containing this node in parent. * This is used to remove the node from * its parent when destroying the node. */ short int level; /* The level of this node in the tree. */ }; /* * Entry -- * * Contains data-specific information how to represent the data * of a node of the hierarchy. * */ struct EntryStruct { int worldX, worldY; /* X-Y position in world coordinates * where the entry is positioned. */ short int width, height; /* Dimensions of the entry. */ int lineHeight; /* Length of the vertical line segment. */ unsigned int flags; /* Flags for this entry. For the * definitions of the various bit * fields see below. */ Blt_Uid dataUid; /* This value isn't used in C code. * It may be used in Tcl bindings to * associate extra data (other than * the label, image, or text) with the * entry. */ Blt_Uid tags; /* List of binding tags for this * entry. Blt_Uid, not a string, * because in the typical case most * entries will have the same * bintags. */ Blt_HashEntry *hashPtr; /* Points the hash entry containing this * entry. This is how the node index is * stored--as the key of the hash entry. */ Hierbox *hboxPtr; Blt_Uid openCmd, closeCmd; /* Tcl commands to invoke when entries * are opened or closed. They override * those specified globally. */ /* * Button information: */ short int buttonX, buttonY; /* X-Y coordinate offsets from to * upper left corner of the entry to * the upper-left corner of the * button. Used to pick the * button quickly */ CachedImage *icons; /* Tk images displayed for the entry. * The first image is the icon * displayed to the left of the * entry's label. The second is icon * displayed when entry is "open". */ GC iconGC; /* GC for drawing default bitmaps. */ short int iconWidth, iconHeight; /* Maximum dimensions for icons and * buttons for this entry. This is * used to align the button, icon, and * text. */ CachedImage *activeIcons; /* Tk images displayed for the entry. * The first image is the icon * displayed to the left of the * entry's label. The second is icon * displayed when entry is "open". */ short int levelX; /* X-coordinate offset of data image * or text for children nodes. */ /* * Label information: */ short int labelWidth, labelHeight; char *labelText; /* Text displayed right of the icon. */ Tk_Font labelFont; /* Font of label. Overrides global font * specification. */ XColor *labelColor; /* Color of label. Overrides default text color * specification. */ GC labelGC; Shadow labelShadow; /* * Data (text or image) information: */ Blt_Uid dataText; Tk_Font dataFont; XColor *dataColor; Shadow dataShadow; GC dataGC; CachedImage *images; }; static Tk_ConfigSpec entryConfigSpecs[] = { {TK_CONFIG_CUSTOM, "-activeicons", "activeIcons", "Icons", DEF_ENTRY_ICONS, Tk_Offset(Entry, activeIcons), TK_CONFIG_NULL_OK, &imagesOption}, {TK_CONFIG_CUSTOM, "-bindtags", "bindTags", "BindTags", DEF_ENTRY_BIND_TAGS, Tk_Offset(Entry, tags), TK_CONFIG_NULL_OK, &bltUidOption}, {TK_CONFIG_CUSTOM, "-closecommand", "entryCloseCommand", "EntryCloseCommand", DEF_ENTRY_COMMAND, Tk_Offset(Entry, closeCmd), TK_CONFIG_NULL_OK, &bltUidOption}, {TK_CONFIG_CUSTOM, "-data", "data", "data", DEF_ENTRY_DATA, Tk_Offset(Entry, dataUid), TK_CONFIG_NULL_OK, &bltUidOption}, {TK_CONFIG_CUSTOM, "-button", "button", "Button", DEF_ENTRY_BUTTON, Tk_Offset(Entry, flags), TK_CONFIG_DONT_SET_DEFAULT, &buttonOption}, {TK_CONFIG_CUSTOM, "-icons", "icons", "Icons", DEF_ENTRY_ICONS, Tk_Offset(Entry, icons), TK_CONFIG_NULL_OK, &imagesOption}, {TK_CONFIG_CUSTOM, "-images", "images", "Images", DEF_ENTRY_IMAGES, Tk_Offset(Entry, images), TK_CONFIG_NULL_OK, &imagesOption}, {TK_CONFIG_STRING, "-label", "label", "Label", DEF_ENTRY_LABEL, Tk_Offset(Entry, labelText), 0}, {TK_CONFIG_COLOR, "-labelcolor", "labelColor", "LabelColor", DEF_ENTRY_FOREGROUND, Tk_Offset(Entry, labelColor), TK_CONFIG_NULL_OK | TK_CONFIG_COLOR_ONLY}, {TK_CONFIG_COLOR, "-labelcolor", "labelColor", "LabelColor", DEF_ENTRY_FG_MONO, Tk_Offset(Entry, labelColor), TK_CONFIG_NULL_OK | TK_CONFIG_MONO_ONLY}, {TK_CONFIG_FONT, "-labelfont", "labelFont", "LabelFont", DEF_ENTRY_FONT, Tk_Offset(Entry, labelFont), 0}, {TK_CONFIG_CUSTOM, "-labelshadow", "labelShadow", "LabelShadow", DEF_ENTRY_SHADOW_COLOR, Tk_Offset(Entry, labelShadow), TK_CONFIG_NULL_OK | TK_CONFIG_COLOR_ONLY, &bltShadowOption}, {TK_CONFIG_CUSTOM, "-labelshadow", "labelShadow", "LabelShadow", DEF_ENTRY_SHADOW_MONO, Tk_Offset(Entry, labelShadow), TK_CONFIG_NULL_OK | TK_CONFIG_MONO_ONLY, &bltShadowOption}, {TK_CONFIG_CUSTOM, "-opencommand", "entryOpenCommand", "EntryOpenCommand", DEF_ENTRY_COMMAND, Tk_Offset(Entry, openCmd), TK_CONFIG_NULL_OK, &bltUidOption}, {TK_CONFIG_CUSTOM, "-text", "text", "Text", DEF_ENTRY_LABEL, Tk_Offset(Entry, dataText), TK_CONFIG_NULL_OK, &bltUidOption}, {TK_CONFIG_COLOR, "-textcolor", "textColor", "TextColor", DEF_ENTRY_FOREGROUND, Tk_Offset(Entry, dataColor), TK_CONFIG_NULL_OK | TK_CONFIG_COLOR_ONLY}, {TK_CONFIG_FONT, "-textfont", "textFont", "TextFont", DEF_ENTRY_FONT, Tk_Offset(Entry, dataFont), 0}, {TK_CONFIG_CUSTOM, "-textshadow", "textShadow", "Shadow", DEF_ENTRY_SHADOW_COLOR, Tk_Offset(Entry, dataShadow), TK_CONFIG_NULL_OK | TK_CONFIG_COLOR_ONLY, &bltShadowOption}, {TK_CONFIG_CUSTOM, "-textShadow", "textShadow", "Shadow", DEF_ENTRY_SHADOW_MONO, Tk_Offset(Entry, dataShadow), TK_CONFIG_NULL_OK | TK_CONFIG_MONO_ONLY, &bltShadowOption}, {TK_CONFIG_END, (char *)NULL, (char *)NULL, (char *)NULL, (char *)NULL, 0, 0} }; /* * ButtonAttributes -- * * A button is the open/close indicator at the far left of the * entry. It is displayed as a plus or minus in a solid * colored box with optionally an border. It has both "active" * and "normal" colors. */ typedef struct { XColor *fgColor; /* Foreground color. */ Tk_3DBorder border; /* Background color. */ XColor *activeFgColor; /* Active foreground color. */ Tk_3DBorder activeBorder; /* Active background color. */ GC lineGC, normalGC, activeGC; int reqSize; int borderWidth; int openRelief, closeRelief; int width, height; CachedImage *images; } ButtonAttributes; /* * TextEdit -- * * This structure is shared by entries when their labels are * edited via the keyboard. It maintains the location of the * insertion cursor and the text selection for the editted entry. * The structure is shared since we need only one. The "focus" * entry should be the only entry receiving KeyPress/KeyRelease * events at any time. Information from the previously editted * entry is overwritten. * * Note that all the indices internally are in terms of bytes, * not characters. This is because UTF-8 strings may encode a * single character into a multi-byte sequence. To find the * respective character position * * n = Tcl_NumUtfChars(string, index); * * where n is the resulting character number. */ typedef struct { int active; int insertPos; /* Position of the cursor within the * array of bytes of the entry's label. */ int x, y; /* Offsets of the insertion cursolsr from the * start of the label. Used to draw the * the cursor relative to the entry. */ int width, height; /* Size of the insertion cursor symbol. */ int selAnchor; /* Fixed end of selection. Used to extend * the selection while maintaining the * other end of the selection. */ int selFirst; /* Position of the first character in the * selection. */ int selLast; /* Position of the last character in the * selection. */ int cursorOn; /* Indicates if the cursor is displayed. */ int onTime, offTime; /* Time in milliseconds to wait before * changing the cursor from off-to-on * and on-to-off. Setting offTime to 0 makes * the cursor steady. */ Tcl_TimerToken timerToken; /* Handle for a timer event called periodically * to blink the cursor. */ } TextEdit; /* * LevelInfo -- * */ typedef struct { int x; int width; } LevelInfo; /* * Hierbox -- * * Represents the hierarchy widget that contains one or more * entries. * * Entries are positioned in "world" coordinates, refering to * the virtual hierbox space. Coordinate 0,0 is the upper-left * of the hierarchy box and the bottom is the end of the last * entry. The widget's Tk window acts as view port into this * virtual space. The hierbox's xOffset and yOffset fields specify * the location of the view port in the virtual world. Scrolling * the viewport is therefore simply changing the xOffset and/or * yOffset fields and redrawing. * * Note that world coordinates are integers, not signed short * integers like X11 screen coordinates. It's very easy to * create a hierarchy that is more than 0x7FFF pixels high. * */ struct HierboxStruct { Tk_Window tkwin; /* Window that embodies the widget. * NULL means that the window has been * destroyed but the data structures * haven't yet been cleaned up.*/ Display *display; /* Display containing widget; needed, * among other things, to release * resources after tkwin has already * gone away. */ Tcl_Interp *interp; /* Interpreter associated with widget. */ Tcl_Command cmdToken; /* Token for widget's command. */ int flags; /* For bitfield definitions, see below */ int allowDuplicates; /* Allow duplicate entries. */ int autoCreate; /* Automatically create path entries * as needed. */ int exportSelection; /* Export the selection to X. */ int hideRoot; /* Don't display the root entry. */ int scrollTile; /* Adjust the tile along with viewport * offsets as the widget is * scrolled. */ int inset; /* Total width of all borders, * including traversal highlight and * 3-D border. Indicates how much * interior stuff must be offset from * outside edges to leave room for * borders. */ Tk_3DBorder border; /* 3D border surrounding the window * (viewport). */ int borderWidth; /* Width of 3D border. */ int relief; /* 3D border relief. */ int highlightWidth; /* Width in pixels of highlight to draw * around widget when it has the focus. * <= 0 means don't draw a highlight. */ XColor *highlightBgColor; /* Color for drawing traversal highlight * area when highlight is off. */ XColor *highlightColor; /* Color for drawing traversal highlight. */ Blt_Tile tile; /* Tiled background */ char *separator; /* Pathname separators */ char *trimLeft; /* Leading characters to trim from * pathnames */ /* * Entries are connected by horizontal and vertical lines. They * may be drawn dashed or solid. */ int lineWidth; /* Width of lines connecting entries */ int dashes; /* Dash on-off value. */ XColor *lineColor; /* Color of connecting lines. */ /* * Button Information: * * The button is the open/close indicator at the far left of the * entry. It is usually displayed as a plus or minus in a solid * colored box with optionally an border. It has both "active" * and "normal" colors. */ ButtonAttributes button; /* * Selection Information: * * The selection is the rectangle that contains a selected entry. * There may be many selected entries. It is displayed as a * solid colored box with optionally a 3D border. */ Tk_3DBorder selBorder; /* Background color of an highlighted entry.*/ int selRelief; /* Relief of selected items. Currently * is always raised. */ int selBorderWidth; /* Border width of a selected entry.*/ XColor *selFgColor; /* Text color of a selected entry. */ Tree *selAnchorPtr; /* Fixed end of selection (i.e. node * at which selection was started.) */ Blt_HashTable selectTable; Blt_Chain selectChain; int sortSelection; /* Indicates if the entries in the * selection should be sorted or * displayed in the order they were * selected. */ char *selectMode; /* Selection style. This value isn't * used in C code (it's just a place * holder), but for the widget's Tcl * bindings. */ char *selectCmd; /* Tcl script that's invoked whenever * the selection changes. */ int leader; /* Number of pixels padding between entries */ Tk_Cursor cursor; /* X Cursor */ int reqWidth, reqHeight; /* Requested dimensions of the widget's * window. */ GC lineGC; /* GC for drawing dotted line between * entries. */ XColor *activeFgColor; Tk_3DBorder activeBorder; XColor *focusColor; Blt_Dashes focusDashes; /* Dash on-off value. */ GC focusGC; /* Graphics context for the active label */ int focusEdit; /* Indicates if the label of the "focus" entry * can be editted. */ TextEdit labelEdit; Tree *activePtr; Tree *focusPtr; /* Pointer to node that's currently active. */ Tree *activeButtonPtr; /* Pointer to last active button */ /* Number of pixels to move for 1 scroll unit. */ int reqScrollX, reqScrollY; int xScrollUnits, yScrollUnits; /* Command strings to control horizontal and vertical scrollbars. */ char *xScrollCmdPrefix, *yScrollCmdPrefix; int scrollMode; /* Selects mode of scrolling: either * BLT_SCROLL_MODE_HIERBOX, * BLT_SCROLL_MODE_LISTBOX, or * BLT_SCROLL_MODE_CANVAS. */ /* * Total size of all "open" entries. This represents the range of * world coordinates. */ int worldWidth, worldHeight; int xOffset, yOffset; /* Translation between view port and * world origin. */ int minHeight; /* Minimum entry height. Used to * to compute what the y-scroll unit * should be. */ LevelInfo *levelInfo; /* * Scanning information: */ int scanAnchorX, scanAnchorY; /* Scan anchor in screen coordinates. */ int scanX, scanY; /* X-Y world coordinate where the scan * started. */ Blt_HashTable nodeTable; /* Table of node identifiers */ Blt_HashTable imageTable; /* Table of Tk images */ Tree *rootPtr; /* Root of hierarchy */ int depth; /* Maximum depth of the hierarchy. */ Tree **visibleArr; /* Array of visible entries */ int nVisible; /* Number of entries in the above array */ int nextSerial; char *openCmd, *closeCmd; /* Tcl commands to invoke when entries * are opened or closed. */ Tk_Font defFont; /* Specifies a default font for labels and * text data. It may be overridden for a * single entry by its -*font option. */ XColor *defColor; /* Global text color for labels. This * may be overridden by the -color * option for an individual entry. */ Pixmap iconBitmap, iconMask;/* Default icon bitmaps */ XColor *iconColor; char *takeFocus; char *sortCmd; /* Tcl command to invoke to sort entries */ ClientData clientData; Blt_BindTable bindTable; /* Binding information for entries. */ Blt_BindTable buttonBindTable; /* Binding information for buttons. */ }; static Tk_ConfigSpec buttonConfigSpecs[] = { {TK_CONFIG_BORDER, "-activebackground", "activeBackground", "Background", DEF_BUTTON_ACTIVE_BG_MONO, Tk_Offset(Hierbox, button.activeBorder), TK_CONFIG_MONO_ONLY}, {TK_CONFIG_BORDER, "-activebackground", "activeBackground", "Background", DEF_BUTTON_ACTIVE_BACKGROUND, Tk_Offset(Hierbox, button.activeBorder), TK_CONFIG_COLOR_ONLY}, {TK_CONFIG_COLOR, "-activeforeground", "activeForeground", "Foreground", DEF_BUTTON_ACTIVE_FOREGROUND, Tk_Offset(Hierbox, button.activeFgColor), TK_CONFIG_COLOR_ONLY}, {TK_CONFIG_COLOR, "-activeforeground", "activeForeground", "Foreground", DEF_BUTTON_ACTIVE_FG_MONO, Tk_Offset(Hierbox, button.activeFgColor), TK_CONFIG_MONO_ONLY}, {TK_CONFIG_BORDER, "-background", "background", "Background", DEF_BUTTON_NORMAL_BACKGROUND, Tk_Offset(Hierbox, button.border), TK_CONFIG_COLOR_ONLY}, {TK_CONFIG_BORDER, "-background", "background", "Background", DEF_BUTTON_NORMAL_BG_MONO, Tk_Offset(Hierbox, button.border), TK_CONFIG_MONO_ONLY}, {TK_CONFIG_SYNONYM, "-bd", "borderWidth", (char *)NULL, (char *)NULL, 0, 0}, {TK_CONFIG_SYNONYM, "-bg", "background", (char *)NULL, (char *)NULL, 0, 0}, {TK_CONFIG_CUSTOM, "-borderwidth", "borderWidth", "BorderWidth", DEF_BUTTON_BORDERWIDTH, Tk_Offset(Hierbox, button.borderWidth), TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption}, {TK_CONFIG_RELIEF, "-closerelief", "closeRelief", "Relief", DEF_BUTTON_CLOSE_RELIEF, Tk_Offset(Hierbox, button.closeRelief), TK_CONFIG_DONT_SET_DEFAULT}, {TK_CONFIG_SYNONYM, "-fg", "foreground", (char *)NULL, (char *)NULL, 0, 0}, {TK_CONFIG_COLOR, "-foreground", "foreground", "Foreground", DEF_BUTTON_NORMAL_FOREGROUND, Tk_Offset(Hierbox, button.fgColor), TK_CONFIG_COLOR_ONLY}, {TK_CONFIG_COLOR, "-foreground", "foreground", "Foreground", DEF_BUTTON_NORMAL_FG_MONO, Tk_Offset(Hierbox, button.fgColor), TK_CONFIG_MONO_ONLY}, {TK_CONFIG_CUSTOM, "-images", "images", "Images", DEF_BUTTON_IMAGES, Tk_Offset(Hierbox, button.images), TK_CONFIG_NULL_OK, &imagesOption}, {TK_CONFIG_RELIEF, "-openrelief", "openRelief", "Relief", DEF_BUTTON_OPEN_RELIEF, Tk_Offset(Hierbox, button.openRelief), TK_CONFIG_DONT_SET_DEFAULT}, {TK_CONFIG_CUSTOM, "-size", "size", "Size", DEF_BUTTON_SIZE, Tk_Offset(Hierbox, button.reqSize), 0, &bltDistanceOption}, {TK_CONFIG_END, (char *)NULL, (char *)NULL, (char *)NULL, (char *)NULL, 0, 0} }; static Tk_ConfigSpec configSpecs[] = { {TK_CONFIG_BORDER, "-activebackground", "activeBackground", "ActiveBackground", DEF_HIERBOX_ACTIVE_BACKGROUND, Tk_Offset(Hierbox, activeBorder), TK_CONFIG_COLOR_ONLY | TK_CONFIG_NULL_OK}, {TK_CONFIG_BORDER, "-activebackground", "activeBackground", "ActiveBackground", DEF_HIERBOX_ACTIVE_BG_MONO, Tk_Offset(Hierbox, activeBorder), TK_CONFIG_MONO_ONLY | TK_CONFIG_NULL_OK}, {TK_CONFIG_COLOR, "-activeforeground", "activeForeground", "Foreground", DEF_HIERBOX_ACTIVE_FOREGROUND, Tk_Offset(Hierbox, activeFgColor), 0}, {TK_CONFIG_BOOLEAN, "-allowduplicates", "allowDuplicates", "AllowDuplicates", DEF_HIERBOX_ALLOW_DUPLICATES, Tk_Offset(Hierbox, allowDuplicates), TK_CONFIG_DONT_SET_DEFAULT}, {TK_CONFIG_BOOLEAN, "-autocreate", "autoCreate", "AutoCreate", DEF_HIERBOX_MAKE_PATH, Tk_Offset(Hierbox, autoCreate), TK_CONFIG_DONT_SET_DEFAULT}, {TK_CONFIG_BORDER, "-background", "background", "Background", DEF_HIERBOX_BACKGROUND, Tk_Offset(Hierbox, border), 0}, {TK_CONFIG_SYNONYM, "-bd", "borderWidth", (char *)NULL, (char *)NULL, 0, 0}, {TK_CONFIG_SYNONYM, "-bg", "background", (char *)NULL, (char *)NULL, 0, 0}, {TK_CONFIG_ACTIVE_CURSOR, "-cursor", "cursor", "Cursor", DEF_HIERBOX_CURSOR, Tk_Offset(Hierbox, cursor), TK_CONFIG_NULL_OK}, {TK_CONFIG_CUSTOM, "-borderwidth", "borderWidth", "BorderWidth", DEF_HIERBOX_BORDERWIDTH, Tk_Offset(Hierbox, borderWidth), TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption}, {TK_CONFIG_STRING, "-closecommand", "closeCommand", "CloseCommand", DEF_HIERBOX_COMMAND, Tk_Offset(Hierbox, closeCmd), TK_CONFIG_NULL_OK}, {TK_CONFIG_CUSTOM, "-dashes", "dashes", "Dashes", DEF_HIERBOX_DASHES, Tk_Offset(Hierbox, dashes), TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption}, {TK_CONFIG_BOOLEAN, "-exportselection", "exportSelection", "ExportSelection", DEF_HIERBOX_EXPORT_SELECTION, Tk_Offset(Hierbox, exportSelection), TK_CONFIG_DONT_SET_DEFAULT}, {TK_CONFIG_SYNONYM, "-fg", "foreground", (char *)NULL, (char *)NULL, 0, 0}, {TK_CONFIG_CUSTOM, "-focusdashes", "focusDashes", "FocusDashes", DEF_HIERBOX_FOCUS_DASHES, Tk_Offset(Hierbox, focusDashes), TK_CONFIG_NULL_OK, &bltDashesOption}, {TK_CONFIG_BOOLEAN, "-focusedit", "focusEdit", "FocusEdit", DEF_HIERBOX_FOCUS_EDIT, Tk_Offset(Hierbox, focusEdit), 0}, {TK_CONFIG_COLOR, "-focusforeground", "focusForeground", "FocusForeground", DEF_HIERBOX_FOCUS_FOREGROUND, Tk_Offset(Hierbox, focusColor), TK_CONFIG_COLOR_ONLY}, {TK_CONFIG_COLOR, "-focusforeground", "focusForeground", "FocusForeground", DEF_HIERBOX_FOCUS_FG_MONO, Tk_Offset(Hierbox, focusColor), TK_CONFIG_MONO_ONLY}, {TK_CONFIG_FONT, "-font", "font", "Font", DEF_HIERBOX_FONT, Tk_Offset(Hierbox, defFont), 0}, {TK_CONFIG_COLOR, "-foreground", "foreground", "Foreground", DEF_HIERBOX_TEXT_COLOR, Tk_Offset(Hierbox, defColor), TK_CONFIG_COLOR_ONLY}, {TK_CONFIG_COLOR, "-foreground", "foreground", "Foreground", DEF_HIERBOX_TEXT_MONO, Tk_Offset(Hierbox, defColor), TK_CONFIG_MONO_ONLY}, {TK_CONFIG_CUSTOM, "-height", "height", "Height", DEF_HIERBOX_HEIGHT, Tk_Offset(Hierbox, reqHeight), TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption}, {TK_CONFIG_BOOLEAN, "-hideroot", "hideRoot", "HideRoot", DEF_HIERBOX_HIDE_ROOT, Tk_Offset(Hierbox, hideRoot), TK_CONFIG_DONT_SET_DEFAULT}, {TK_CONFIG_COLOR, "-highlightbackground", "highlightBackground", "HighlightBackground", DEF_HIERBOX_HIGHLIGHT_BACKGROUND, Tk_Offset(Hierbox, highlightBgColor), TK_CONFIG_COLOR_ONLY}, {TK_CONFIG_COLOR, "-highlightbackground", "highlightBackground", "HighlightBackground", DEF_HIERBOX_HIGHLIGHT_BG_MONO, Tk_Offset(Hierbox, highlightBgColor), TK_CONFIG_MONO_ONLY}, {TK_CONFIG_COLOR, "-highlightcolor", "highlightColor", "HighlightColor", DEF_HIERBOX_HIGHLIGHT_COLOR, Tk_Offset(Hierbox, highlightColor), 0}, {TK_CONFIG_PIXELS, "-highlightthickness", "highlightThickness", "HighlightThickness", DEF_HIERBOX_HIGHLIGHT_WIDTH, Tk_Offset(Hierbox, highlightWidth), TK_CONFIG_DONT_SET_DEFAULT}, {TK_CONFIG_COLOR, "-linecolor", "lineColor", "LineColor", DEF_HIERBOX_LINE_COLOR, Tk_Offset(Hierbox, lineColor), TK_CONFIG_COLOR_ONLY}, {TK_CONFIG_COLOR, "-linecolor", "lineColor", "LineColor", DEF_HIERBOX_LINE_MONO, Tk_Offset(Hierbox, lineColor), TK_CONFIG_MONO_ONLY}, {TK_CONFIG_CUSTOM, "-linespacing", "lineSpacing", "LineSpacing", DEF_HIERBOX_LINE_SPACING, Tk_Offset(Hierbox, leader), TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption}, {TK_CONFIG_CUSTOM, "-linewidth", "lineWidth", "LineWidth", DEF_HIERBOX_LINE_WIDTH, Tk_Offset(Hierbox, lineWidth), TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption}, {TK_CONFIG_STRING, "-opencommand", "openCommand", "OpenCommand", DEF_HIERBOX_COMMAND, Tk_Offset(Hierbox, openCmd), TK_CONFIG_NULL_OK}, {TK_CONFIG_RELIEF, "-relief", "relief", "Relief", DEF_HIERBOX_RELIEF, Tk_Offset(Hierbox, relief), 0}, {TK_CONFIG_CUSTOM, "-scrollmode", "scrollMode", "ScrollMode", DEF_HIERBOX_SCROLL_MODE, Tk_Offset(Hierbox, scrollMode), TK_CONFIG_DONT_SET_DEFAULT, &scrollModeOption}, {TK_CONFIG_BOOLEAN, "-scrolltile", "scrollTile", "ScrollTile", DEF_HIERBOX_SCROLL_TILE, Tk_Offset(Hierbox, scrollTile), TK_CONFIG_DONT_SET_DEFAULT}, {TK_CONFIG_BORDER, "-selectbackground", "selectBackground", "Foreground", DEF_HIERBOX_SELECT_BG_MONO, Tk_Offset(Hierbox, selBorder), TK_CONFIG_MONO_ONLY}, {TK_CONFIG_BORDER, "-selectbackground", "selectBackground", "Foreground", DEF_HIERBOX_SELECT_BACKGROUND, Tk_Offset(Hierbox, selBorder), TK_CONFIG_COLOR_ONLY}, {TK_CONFIG_CUSTOM, "-selectborderwidth", "selectBorderWidth", "BorderWidth", DEF_HIERBOX_SELECT_BORDERWIDTH, Tk_Offset(Hierbox, selBorderWidth), TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption}, {TK_CONFIG_STRING, "-selectcommand", "selectCommand", "SelectCommand", DEF_HIERBOX_SELECT_CMD, Tk_Offset(Hierbox, selectCmd), TK_CONFIG_NULL_OK}, {TK_CONFIG_COLOR, "-selectforeground", "selectForeground", "Background", DEF_HIERBOX_SELECT_FG_MONO, Tk_Offset(Hierbox, selFgColor), TK_CONFIG_MONO_ONLY}, {TK_CONFIG_COLOR, "-selectforeground", "selectForeground", "Background", DEF_HIERBOX_SELECT_FOREGROUND, Tk_Offset(Hierbox, selFgColor), TK_CONFIG_COLOR_ONLY}, {TK_CONFIG_STRING, "-selectmode", "selectMode", "SelectMode", DEF_HIERBOX_SELECT_MODE, Tk_Offset(Hierbox, selectMode), TK_CONFIG_NULL_OK}, {TK_CONFIG_RELIEF, "-selectrelief", "selectRelief", "Relief", DEF_HIERBOX_SELECT_RELIEF, Tk_Offset(Hierbox, selRelief), TK_CONFIG_DONT_SET_DEFAULT}, {TK_CONFIG_CUSTOM, "-separator", "separator", "Separator", DEF_HIERBOX_SEPARATOR, Tk_Offset(Hierbox, separator), TK_CONFIG_NULL_OK, &separatorOption}, {TK_CONFIG_STRING, "-takefocus", "takeFocus", "TakeFocus", DEF_HIERBOX_TAKE_FOCUS, Tk_Offset(Hierbox, takeFocus), TK_CONFIG_NULL_OK}, {TK_CONFIG_CUSTOM, "-tile", "tile", "Tile", (char *)NULL, Tk_Offset(Hierbox, tile), TK_CONFIG_NULL_OK, &bltTileOption}, {TK_CONFIG_STRING, "-trimleft", "trimLeft", "Trim", DEF_HIERBOX_TRIMLEFT, Tk_Offset(Hierbox, trimLeft), TK_CONFIG_NULL_OK}, {TK_CONFIG_CUSTOM, "-width", "width", "Width", DEF_HIERBOX_WIDTH, Tk_Offset(Hierbox, reqWidth), TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption}, {TK_CONFIG_STRING, "-xscrollcommand", "xScrollCommand", "ScrollCommand", (char *)NULL, Tk_Offset(Hierbox, xScrollCmdPrefix), TK_CONFIG_NULL_OK}, {TK_CONFIG_CUSTOM, "-xscrollincrement", "xScrollIncrement", "ScrollIncrement", DEF_HIERBOX_SCROLL_INCREMENT, Tk_Offset(Hierbox, reqScrollX), TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption}, {TK_CONFIG_STRING, "-yscrollcommand", "yScrollCommand", "ScrollCommand", (char *)NULL, Tk_Offset(Hierbox, yScrollCmdPrefix), TK_CONFIG_NULL_OK}, {TK_CONFIG_CUSTOM, "-yscrollincrement", "yScrollIncrement", "ScrollIncrement", DEF_HIERBOX_SCROLL_INCREMENT, Tk_Offset(Hierbox, reqScrollY), TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption}, {TK_CONFIG_END, (char *)NULL, (char *)NULL, (char *)NULL, (char *)NULL, 0, 0} }; typedef struct { int x, y; /* Tracks the current world * coordinates as we traverse through * the tree. After a full-tree * traversal, the y-coordinate will be * the height of the virtual * hierarchy. */ int maxWidth; /* Maximum entry width. This is the * width of the virtual hierarchy. */ int labelOffset; int minHeight; /* Minimum entry height. Used to * to compute what the y-scroll unit * should be. */ int maxIconWidth; int level, depth; } LayoutInfo; #define DEF_ICON_WIDTH 16 #define DEF_ICON_HEIGHT 16 static unsigned char folderBits[] = { 0x00, 0x00, 0x7c, 0x00, 0x82, 0x00, 0x81, 0x3f, 0x41, 0x40, 0x3f, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0xff, 0xff, 0xfe, 0xff, 0x00, 0x00}; static unsigned char folderMaskBits[] = { 0x00, 0x00, 0x7c, 0x00, 0xfe, 0x00, 0xff, 0x3f, 0xff, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0x00, 0x00}; /* Forward Declarations */ static void DestroyHierbox _ANSI_ARGS_((DestroyData dataPtr)); static void HierboxEventProc _ANSI_ARGS_((ClientData clientdata, XEvent *eventPtr)); static void DrawButton _ANSI_ARGS_((Hierbox *hboxPtr, Tree * treePtr, Drawable drawable)); static void DisplayHierbox _ANSI_ARGS_((ClientData clientData)); static void HierboxInstCmdDeleteProc _ANSI_ARGS_((ClientData clientdata)); static int HierboxInstCmd _ANSI_ARGS_((ClientData clientdata, Tcl_Interp *interp, int argc, char **argv)); static void EventuallyRedraw _ANSI_ARGS_((Hierbox *hboxPtr)); static void SelectCmdProc _ANSI_ARGS_((ClientData clientData)); static void EventuallyInvokeSelectCmd _ANSI_ARGS_((Hierbox *hboxPtr)); static int ComputeVisibleEntries _ANSI_ARGS_((Hierbox *hboxPtr)); static int ConfigureEntry _ANSI_ARGS_((Hierbox *hboxPtr, Entry * entryPtr, int argc, char **argv, int flags)); static void ComputeLayout _ANSI_ARGS_((Hierbox *hboxPtr)); static CompareProc ExactCompare, GlobCompare, RegexpCompare; static ApplyProc SelectNode, GetSelectedLabels, CloseNode, SizeOfNode, IsSelectedNode, MapAncestors, FixUnmappedSelections, UnmapNode, SortNode, OpenNode; static IterProc NextNode, LastNode; static Tk_ImageChangedProc ImageChangedProc; static Blt_ChainCompareProc CompareNodesByTclCmd, CompareNodesByName; static Blt_BindPickProc PickButton, PickEntry; static Blt_BindTagProc GetTags; static Tk_SelectionProc SelectionProc; static Tk_LostSelProc LostSelection; static Tcl_CmdProc HierboxCmd; static Blt_TileChangedProc TileChangedProc; static Tcl_TimerProc LabelBlinkProc; static Tcl_FreeProc DestroyNode; /* *---------------------------------------------------------------------- * * StringToScrollMode -- * * Convert the string reprsenting a scroll mode, to its numeric * form. * * Results: * If the string is successfully converted, TCL_OK is returned. * Otherwise, TCL_ERROR is returned and an error message is left * in interpreter's result field. * *---------------------------------------------------------------------- */ /*ARGSUSED*/ static int StringToScrollMode(clientData, interp, tkwin, string, widgRec, offset) ClientData clientData; /* Not used. */ Tcl_Interp *interp; /* Interpreter to send results back to */ Tk_Window tkwin; /* Not used. */ char *string; /* New legend position string */ char *widgRec; /* Widget record */ int offset; /* offset to XPoint structure */ { int *modePtr = (int *)(widgRec + offset); if ((string[0] == 'l') && (strcmp(string, "listbox") == 0)) { *modePtr = BLT_SCROLL_MODE_LISTBOX; } else if ((string[0] == 'h') && (strcmp(string, "hierbox") == 0)) { *modePtr = BLT_SCROLL_MODE_HIERBOX; } else if ((string[0] == 'c') && (strcmp(string, "canvas") == 0)) { *modePtr = BLT_SCROLL_MODE_CANVAS; } else { Tcl_AppendResult(interp, "bad scroll mode \"", string, "\": should be \"hierbox\", \"listbox\", or \"canvas\"", (char *)NULL); return TCL_ERROR; } return TCL_OK; } /* *---------------------------------------------------------------------- * * ScrollModeToString -- * * Results: * The string representation of the button boolean is returned. * *---------------------------------------------------------------------- */ /*ARGSUSED*/ static char * ScrollModeToString(clientData, tkwin, widgRec, offset, freeProcPtr) ClientData clientData; /* Not used. */ Tk_Window tkwin; /* Not used. */ char *widgRec; /* Widget record */ int offset; /* offset of flags field in record */ Tcl_FreeProc **freeProcPtr; /* Memory deallocation scheme to use */ { int mode = *(int *)(widgRec + offset); switch (mode) { case BLT_SCROLL_MODE_LISTBOX: return "listbox"; case BLT_SCROLL_MODE_HIERBOX: return "hierbox"; case BLT_SCROLL_MODE_CANVAS: return "canvas"; default: return "unknown scroll mode"; } } /* *---------------------------------------------------------------------- * * StringToButton -- * * Convert a string to one of three values. * 0 - false, no, off * 1 - true, yes, on * 2 - auto * Results: * If the string is successfully converted, TCL_OK is returned. * Otherwise, TCL_ERROR is returned and an error message is left in * interpreter's result field. * *---------------------------------------------------------------------- */ /*ARGSUSED*/ static int StringToButton(clientData, interp, tkwin, string, widgRec, offset) ClientData clientData; /* Not used. */ Tcl_Interp *interp; /* Interpreter to send results back to */ Tk_Window tkwin; /* Not used. */ char *string; /* New legend position string */ char *widgRec; /* Widget record */ int offset; /* offset to XPoint structure */ { int *flags = (int *)(widgRec + offset); *flags &= ~BUTTON_MASK; if ((string[0] == 'a') && (strcmp(string, "auto") == 0)) { *flags |= BUTTON_AUTO; } else { int value; if (Tcl_GetBoolean(interp, string, &value) != TCL_OK) { return TCL_ERROR; } if (value) { *flags |= BUTTON_SHOW; } } return TCL_OK; } /* *---------------------------------------------------------------------- * * ButtonToString -- * * Results: * The string representation of the button boolean is returned. * *---------------------------------------------------------------------- */ /*ARGSUSED*/ static char * ButtonToString(clientData, tkwin, widgRec, offset, freeProcPtr) ClientData clientData; /* Not used. */ Tk_Window tkwin; /* Not used. */ char *widgRec; /* Widget record */ int offset; /* offset of flags field in record */ Tcl_FreeProc **freeProcPtr; /* Memory deallocation scheme to use */ { unsigned int flags = *(int *)(widgRec + offset); switch (flags & BUTTON_MASK) { case 0: return "0"; case BUTTON_SHOW: return "1"; case BUTTON_AUTO: return "auto"; default: return "unknown button value"; } } /* *---------------------------------------------------------------------- * * ImageChangedProc * * * Results: * None. * *---------------------------------------------------------------------- */ /* ARGSUSED */ static void ImageChangedProc(clientData, x, y, width, height, imageWidth, imageHeight) ClientData clientData; int x, y, width, height; /* Not used. */ int imageWidth, imageHeight;/* Not used. */ { Hierbox *hboxPtr = clientData; hboxPtr->flags |= (HIERBOX_LAYOUT | HIERBOX_SCROLL); EventuallyRedraw(hboxPtr); } static CachedImage GetCachedImage(hboxPtr, interp, tkwin, name) Hierbox *hboxPtr; Tcl_Interp *interp; Tk_Window tkwin; char *name; { struct CachedImageStruct *imagePtr; int isNew; Blt_HashEntry *hPtr; hPtr = Blt_CreateHashEntry(&(hboxPtr->imageTable), (char *)name, &isNew); if (isNew) { Tk_Image tkImage; int width, height; tkImage = Tk_GetImage(interp, tkwin, name, ImageChangedProc, hboxPtr); if (tkImage == NULL) { Blt_DeleteHashEntry(&(hboxPtr->imageTable), hPtr); return NULL; } Tk_SizeOfImage(tkImage, &width, &height); imagePtr = Blt_Malloc(sizeof(struct CachedImageStruct)); imagePtr->tkImage = tkImage; imagePtr->hashPtr = hPtr; imagePtr->refCount = 1; imagePtr->width = width, imagePtr->height = height; Blt_SetHashValue(hPtr, imagePtr); } else { imagePtr = (struct CachedImageStruct *)Blt_GetHashValue(hPtr); imagePtr->refCount++; } return imagePtr; } static void FreeCachedImage(hboxPtr, imagePtr) Hierbox *hboxPtr; struct CachedImageStruct *imagePtr; { imagePtr->refCount--; if (imagePtr->refCount == 0) { Blt_DeleteHashEntry(&(hboxPtr->imageTable), imagePtr->hashPtr); Tk_FreeImage(imagePtr->tkImage); Blt_Free(imagePtr); } } /* *---------------------------------------------------------------------- * * StringToImages -- * * Convert a list of image names into Tk images. * * Results: * If the string is successfully converted, TCL_OK is returned. * Otherwise, TCL_ERROR is returned and an error message is left in * interpreter's result field. * *---------------------------------------------------------------------- */ static int StringToImages(clientData, interp, tkwin, string, widgRec, offset) ClientData clientData; /* Not used. */ Tcl_Interp *interp; /* Interpreter to send results back to */ Tk_Window tkwin; /* Not used. */ char *string; /* New legend position string */ char *widgRec; /* Widget record */ int offset; /* offset to field in structure */ { Hierbox *hboxPtr = *(Hierbox **)clientData; CachedImage **imagePtrPtr = (CachedImage **) (widgRec + offset); CachedImage *imageArr; int result; result = TCL_OK; imageArr = NULL; if ((string != NULL) && (*string != '\0')) { int nNames; char **nameArr; if (Tcl_SplitList(interp, string, &nNames, &nameArr) != TCL_OK) { return TCL_ERROR; } if (nNames > 0) { register int i; imageArr = Blt_Malloc(sizeof(CachedImage *) * (nNames + 1)); assert(imageArr); for (i = 0; i < nNames; i++) { imageArr[i] = GetCachedImage(hboxPtr, interp, tkwin, nameArr[i]); if (imageArr[i] == NULL) { result = TCL_ERROR; break; } } Blt_Free(nameArr); imageArr[nNames] = NULL; } } if (*imagePtrPtr != NULL) { register CachedImage *imagePtr; for (imagePtr = *imagePtrPtr; *imagePtr != NULL; imagePtr++) { FreeCachedImage(hboxPtr, *imagePtr); } Blt_Free(*imagePtrPtr); } *imagePtrPtr = imageArr; return result; } /* *---------------------------------------------------------------------- * * ImagesToString -- * * Converts the image into its string representation (its name). * * Results: * The name of the image is returned. * *---------------------------------------------------------------------- */ /*ARGSUSED*/ static char * ImagesToString(clientData, tkwin, widgRec, offset, freeProcPtr) ClientData clientData; /* Not used. */ Tk_Window tkwin; /* Not used. */ char *widgRec; /* Widget record */ int offset; /* offset of images array in record */ Tcl_FreeProc **freeProcPtr; /* Memory deallocation scheme to use */ { CachedImage **imagePtrPtr = (CachedImage **) (widgRec + offset); Tcl_DString dString; char *result; Tcl_DStringInit(&dString); if (*imagePtrPtr != NULL) { register CachedImage *imagePtr; for (imagePtr = *imagePtrPtr; *imagePtr != NULL; imagePtr++) { Tcl_DStringAppendElement(&dString, Blt_NameOfImage((*imagePtr)->tkImage)); } } result = Blt_Strdup(Tcl_DStringValue(&dString)); Tcl_DStringFree(&dString); *freeProcPtr = (Tcl_FreeProc *)Blt_Free; return result; } /* *---------------------------------------------------------------------- * * StringToScrollmode -- * * Convert the string reprsenting a scroll mode, to its numeric * form. * * Results: * If the string is successfully converted, TCL_OK is returned. * Otherwise, TCL_ERROR is returned and an error message is left * in interpreter's result field. * *---------------------------------------------------------------------- */ /*ARGSUSED*/ static int StringToSeparator(clientData, interp, tkwin, string, widgRec, offset) ClientData clientData; /* Not used. */ Tcl_Interp *interp; /* Interpreter to send results back to */ Tk_Window tkwin; /* Not used. */ char *string; /* String representing new value */ char *widgRec; /* Widget record */ int offset; /* offset in structure */ { char **sepPtr = (char **)(widgRec + offset); if ((*sepPtr != SEPARATOR_LIST) && (*sepPtr != SEPARATOR_NONE)) { Blt_Free(*sepPtr); } if ((string == NULL) || (*string == '\0')) { *sepPtr = SEPARATOR_LIST; } else if (strcmp(string, "none") == 0) { *sepPtr = SEPARATOR_NONE; } else { *sepPtr = Blt_Strdup(string); } return TCL_OK; } /* *---------------------------------------------------------------------- * * SeparatorToString -- * * Results: * The string representation of the separator is returned. * *---------------------------------------------------------------------- */ /*ARGSUSED*/ static char * SeparatorToString(clientData, tkwin, widgRec, offset, freeProcPtr) ClientData clientData; /* Not used. */ Tk_Window tkwin; /* Not used. */ char *widgRec; /* Widget record */ int offset; /* offset of flags field in record */ Tcl_FreeProc **freeProcPtr; /* Memory deallocation scheme to use */ { char *separator = *(char **)(widgRec + offset); if (separator == SEPARATOR_NONE) { return ""; } else if (separator == SEPARATOR_LIST) { return "list"; } return separator; } static int ApplyToTree(hboxPtr, rootPtr, proc, flags) Hierbox *hboxPtr; Tree *rootPtr; ApplyProc *proc; unsigned int flags; { if (flags & APPLY_BEFORE) { if ((*proc) (hboxPtr, rootPtr) != TCL_OK) { return TCL_ERROR; } } if (flags & APPLY_RECURSE) { if (!(flags & APPLY_OPEN_ONLY) || (rootPtr->entryPtr->flags & ENTRY_OPEN)) { Blt_ChainLink *linkPtr, *nextPtr; Tree *treePtr; for (linkPtr = Blt_ChainFirstLink(rootPtr->chainPtr); linkPtr != NULL; linkPtr = nextPtr) { /* Get the next link in the chain before calling * ApplyToTree. This is because ApplyToTree may * delete the node and its link. */ nextPtr = Blt_ChainNextLink(linkPtr); treePtr = Blt_ChainGetValue(linkPtr); if (ApplyToTree(hboxPtr, treePtr, proc, flags) != TCL_OK) { return TCL_ERROR; } } } } if ((flags & APPLY_BEFORE) == 0) { if ((*proc) (hboxPtr, rootPtr) != TCL_OK) { return TCL_ERROR; } } return TCL_OK; } static void ConfigureButtons(hboxPtr) Hierbox *hboxPtr; { ButtonAttributes *buttonPtr = &(hboxPtr->button); unsigned long gcMask; XGCValues gcValues; GC newGC; gcMask = GCForeground; gcValues.foreground = buttonPtr->fgColor->pixel; newGC = Tk_GetGC(hboxPtr->tkwin, gcMask, &gcValues); if (buttonPtr->normalGC != NULL) { Tk_FreeGC(hboxPtr->display, buttonPtr->normalGC); } buttonPtr->normalGC = newGC; gcMask = (GCForeground | GCLineWidth); gcValues.foreground = hboxPtr->lineColor->pixel; gcValues.line_width = hboxPtr->lineWidth; newGC = Tk_GetGC(hboxPtr->tkwin, gcMask, &gcValues); if (buttonPtr->lineGC != NULL) { Tk_FreeGC(hboxPtr->display, buttonPtr->lineGC); } buttonPtr->lineGC = newGC; gcMask = GCForeground; gcValues.foreground = buttonPtr->activeFgColor->pixel; newGC = Tk_GetGC(hboxPtr->tkwin, gcMask, &gcValues); if (buttonPtr->activeGC != NULL) { Tk_FreeGC(hboxPtr->display, buttonPtr->activeGC); } buttonPtr->activeGC = newGC; buttonPtr->width = buttonPtr->height = ODD(buttonPtr->reqSize); if (buttonPtr->images != NULL) { register int i; for (i = 0; i < 2; i++) { if (buttonPtr->images[i] == NULL) { break; } if (buttonPtr->width < ImageWidth(buttonPtr->images[i])) { buttonPtr->width = ImageWidth(buttonPtr->images[i]); } if (buttonPtr->height < ImageHeight(buttonPtr->images[i])) { buttonPtr->height = ImageHeight(buttonPtr->images[i]); } } } buttonPtr->width += 2 * buttonPtr->borderWidth; buttonPtr->height += 2 * buttonPtr->borderWidth; } static void DestroyEntry(entryPtr) Entry *entryPtr; { Hierbox *hboxPtr = entryPtr->hboxPtr; register CachedImage *imagePtr; Tk_FreeOptions(entryConfigSpecs, (char *)entryPtr, hboxPtr->display, 0); if (entryPtr->labelGC != NULL) { Tk_FreeGC(hboxPtr->display, entryPtr->labelGC); } if (entryPtr->dataGC != NULL) { Tk_FreeGC(hboxPtr->display, entryPtr->dataGC); } if (entryPtr->hashPtr != NULL) { Blt_DeleteHashEntry(&(hboxPtr->nodeTable), entryPtr->hashPtr); } if (entryPtr->dataShadow.color != NULL) { Tk_FreeColor(entryPtr->dataShadow.color); } if (entryPtr->labelShadow.color != NULL) { Tk_FreeColor(entryPtr->labelShadow.color); } if (entryPtr->iconGC != NULL) { Tk_FreeGC(hboxPtr->display, entryPtr->iconGC); } if (entryPtr->openCmd != NULL) { Blt_FreeUid(entryPtr->openCmd); } if (entryPtr->closeCmd != NULL) { Blt_FreeUid(entryPtr->closeCmd); } if (entryPtr->dataUid != NULL) { Blt_FreeUid(entryPtr->dataUid); } if (entryPtr->dataText != NULL) { Blt_FreeUid(entryPtr->dataText); } if (entryPtr->tags != NULL) { Blt_FreeUid(entryPtr->tags); } if (entryPtr->icons != NULL) { for (imagePtr = entryPtr->icons; *imagePtr != NULL; imagePtr++) { FreeCachedImage(hboxPtr, *imagePtr); } Blt_Free(entryPtr->icons); } if (entryPtr->activeIcons != NULL) { for (imagePtr = entryPtr->activeIcons; *imagePtr != NULL; imagePtr++) { FreeCachedImage(hboxPtr, *imagePtr); } Blt_Free(entryPtr->activeIcons); } if (entryPtr->images != NULL) { for (imagePtr = entryPtr->images; *imagePtr != NULL; imagePtr++) { FreeCachedImage(hboxPtr, *imagePtr); } Blt_Free(entryPtr->images); } Blt_Free(entryPtr); } /*ARGSUSED*/ static Tree * LastNode(treePtr, mask) register Tree *treePtr; unsigned int mask; { Blt_ChainLink *linkPtr; if (treePtr->parentPtr == NULL) { return NULL; /* The root is the first node. */ } linkPtr = Blt_ChainPrevLink(treePtr->linkPtr); if (linkPtr == NULL) { /* There are no siblings previous to this one, so pick the parent. */ return treePtr->parentPtr; } /* * Traverse down the right-most thread, in order to select the * next entry. Stop if we find a "closed" entry or reach a leaf. */ treePtr = Blt_ChainGetValue(linkPtr); while ((treePtr->entryPtr->flags & mask) == mask) { linkPtr = Blt_ChainLastLink(treePtr->chainPtr); if (linkPtr == NULL) { break; /* Found a leaf. */ } treePtr = Blt_ChainGetValue(linkPtr); } return treePtr; } static Tree * NextNode(treePtr, mask) Tree *treePtr; unsigned int mask; { Blt_ChainLink *linkPtr; if ((treePtr->entryPtr->flags & mask) == mask) { /* Pick the first sub-node. */ linkPtr = Blt_ChainFirstLink(treePtr->chainPtr); if (linkPtr != NULL) { return Blt_ChainGetValue(linkPtr); } } /* * Back up until we can find a level where we can pick a "next" entry. * For the last entry we'll thread our way back to the root. */ while (treePtr->parentPtr != NULL) { linkPtr = Blt_ChainNextLink(treePtr->linkPtr); if (linkPtr != NULL) { return Blt_ChainGetValue(linkPtr); } treePtr = treePtr->parentPtr; } return NULL; /* At root, no next node. */ } /*ARGSUSED*/ static Tree * EndNode(treePtr, mask) Tree *treePtr; unsigned int mask; { Blt_ChainLink *linkPtr; linkPtr = Blt_ChainLastLink(treePtr->chainPtr); while (linkPtr != NULL) { treePtr = Blt_ChainGetValue(linkPtr); if ((treePtr->entryPtr->flags & mask) != mask) { break; } linkPtr = Blt_ChainLastLink(treePtr->chainPtr); } return treePtr; } static void ExposeAncestors(treePtr) register Tree *treePtr; { treePtr = treePtr->parentPtr; while (treePtr != NULL) { treePtr->entryPtr->flags |= (ENTRY_OPEN | ENTRY_MAPPED); treePtr = treePtr->parentPtr; } } static int IsBefore(t1Ptr, t2Ptr) register Tree *t1Ptr, *t2Ptr; { int depth; register int i; Blt_ChainLink *linkPtr; Tree *treePtr; depth = MIN(t1Ptr->level, t2Ptr->level); if (depth == 0) { /* One of the nodes is root. */ if (t1Ptr->parentPtr == NULL) { return 1; } return 0; } /* * Traverse back from the deepest node, until the both nodes are at the * same depth. Check if the ancestor node found is the other node. */ for (i = t1Ptr->level; i > depth; i--) { t1Ptr = t1Ptr->parentPtr; } if (t1Ptr == t2Ptr) { return 0; } for (i = t2Ptr->level; i > depth; i--) { t2Ptr = t2Ptr->parentPtr; } if (t2Ptr == t1Ptr) { return 1; } /* * First find the mutual ancestor of both nodes. Look at each * preceding ancestor level-by-level for both nodes. Eventually * we'll find a node that's the parent of both ancestors. Then * find the first ancestor in the parent's list of subnodes. */ for (i = depth; i > 0; i--) { if (t1Ptr->parentPtr == t2Ptr->parentPtr) { break; } t1Ptr = t1Ptr->parentPtr; t2Ptr = t2Ptr->parentPtr; } for (linkPtr = Blt_ChainFirstLink(t1Ptr->parentPtr->chainPtr); linkPtr != NULL; linkPtr = Blt_ChainNextLink(linkPtr)) { treePtr = Blt_ChainGetValue(linkPtr); if (treePtr == t1Ptr) { return 1; } else if (treePtr == t2Ptr) { return 0; } } assert(linkPtr != NULL); return 0; } static int IsAncestor(rootPtr, treePtr) Tree *rootPtr, *treePtr; { if (treePtr != NULL) { treePtr = treePtr->parentPtr; while (treePtr != NULL) { if (treePtr == rootPtr) { return 1; } treePtr = treePtr->parentPtr; } } return 0; } static int IsHidden(treePtr) register Tree *treePtr; { if (treePtr != NULL) { unsigned int mask; if (!(treePtr->entryPtr->flags & ENTRY_MAPPED)) { return TRUE; } treePtr = treePtr->parentPtr; mask = (ENTRY_OPEN | ENTRY_MAPPED); while (treePtr != NULL) { if ((treePtr->entryPtr->flags & mask) != mask) { return TRUE; } treePtr = treePtr->parentPtr; } } return FALSE; } static void SelectEntry(hboxPtr, treePtr) Hierbox *hboxPtr; Tree *treePtr; { int isNew; Blt_HashEntry *hPtr; hPtr = Blt_CreateHashEntry(&(hboxPtr->selectTable), (char *)treePtr, &isNew); if (isNew) { Blt_ChainLink *linkPtr; linkPtr = Blt_ChainAppend(&(hboxPtr->selectChain), treePtr); Blt_SetHashValue(hPtr, linkPtr); } } static void DeselectEntry(hboxPtr, treePtr) Hierbox *hboxPtr; Tree *treePtr; { Blt_HashEntry *hPtr; hPtr = Blt_FindHashEntry(&(hboxPtr->selectTable), (char *)treePtr); if (hPtr != NULL) { Blt_ChainLink *linkPtr; linkPtr = (Blt_ChainLink *)Blt_GetHashValue(hPtr); Blt_ChainDeleteLink(&(hboxPtr->selectChain), linkPtr); Blt_DeleteHashEntry(&(hboxPtr->selectTable), hPtr); } } static void ClearSelection(hboxPtr) Hierbox *hboxPtr; { Blt_DeleteHashTable(&(hboxPtr->selectTable)); Blt_InitHashTable(&(hboxPtr->selectTable), BLT_ONE_WORD_KEYS); Blt_ChainReset(&(hboxPtr->selectChain)); EventuallyRedraw(hboxPtr); if (hboxPtr->selectCmd != NULL) { EventuallyInvokeSelectCmd(hboxPtr); } } static void PruneSelection(hboxPtr, rootPtr) Hierbox *hboxPtr; Tree *rootPtr; { Blt_ChainLink *linkPtr, *nextPtr; Tree *treePtr; int selectionChanged; selectionChanged = FALSE; for (linkPtr = Blt_ChainFirstLink(&(hboxPtr->selectChain)); linkPtr != NULL; linkPtr = nextPtr) { nextPtr = Blt_ChainNextLink(linkPtr); treePtr = Blt_ChainGetValue(linkPtr); if (IsAncestor(rootPtr, treePtr)) { DeselectEntry(hboxPtr, treePtr); selectionChanged = TRUE; } } if (selectionChanged) { EventuallyRedraw(hboxPtr); if (hboxPtr->selectCmd != NULL) { EventuallyInvokeSelectCmd(hboxPtr); } } } static int IsSelected(hboxPtr, treePtr) Hierbox *hboxPtr; Tree *treePtr; { Blt_HashEntry *hPtr; hPtr = Blt_FindHashEntry(&(hboxPtr->selectTable), (char *)treePtr); return (hPtr != NULL); } static void GetFullPath(treePtr, separator, resultPtr) Tree *treePtr; char *separator; Tcl_DString *resultPtr; { char **nameArr; /* Used the stack the component names. */ register int i; int level; level = treePtr->level; nameArr = Blt_Malloc((level + 1) * sizeof(char *)); assert(nameArr); for (i = level; i >= 0; i--) { /* Save the name of each ancestor in the name array. */ nameArr[i] = treePtr->nameId; treePtr = treePtr->parentPtr; } Tcl_DStringInit(resultPtr); if ((separator == SEPARATOR_LIST) || (separator == SEPARATOR_NONE)) { for (i = 0; i <= level; i++) { Tcl_DStringAppendElement(resultPtr, nameArr[i]); } } else { Tcl_DStringAppend(resultPtr, nameArr[0], -1); if (strcmp(nameArr[0], separator) != 0) { Tcl_DStringAppend(resultPtr, separator, -1); } if (level > 0) { for (i = 1; i < level; i++) { Tcl_DStringAppend(resultPtr, nameArr[i], -1); Tcl_DStringAppend(resultPtr, separator, -1); } Tcl_DStringAppend(resultPtr, nameArr[i], -1); } } Blt_Free(nameArr); } static void InsertNode(parentPtr, position, nodePtr) Tree *parentPtr; /* Parent node where the new node will * be added. */ int position; /* Position in the parent of the new node. */ Tree *nodePtr; /* The node to be inserted. */ { Blt_ChainLink *linkPtr; /* * Create lists to contain subnodes as needed. We don't want to * unnecessarily allocate storage for leaves (of which there may * be many). */ if (parentPtr->chainPtr == NULL) { parentPtr->chainPtr = Blt_ChainCreate(); } linkPtr = Blt_ChainNewLink(); if (position == APPEND) { Blt_ChainAppendLink(parentPtr->chainPtr, linkPtr); } else { Blt_ChainLink *beforePtr; beforePtr = Blt_ChainGetNthLink(parentPtr->chainPtr, position); Blt_ChainLinkBefore(parentPtr->chainPtr, linkPtr, beforePtr); } nodePtr->level = parentPtr->level + 1; nodePtr->parentPtr = parentPtr; nodePtr->linkPtr = linkPtr; Blt_ChainSetValue(linkPtr, nodePtr); } static void DestroyNode(data) DestroyData data; { Tree *treePtr = (Tree *)data; if (treePtr->nameId != NULL) { Blt_FreeUid(treePtr->nameId); } if (treePtr->chainPtr != NULL) { Blt_ChainDestroy(treePtr->chainPtr); } if (treePtr->entryPtr != NULL) { DestroyEntry(treePtr->entryPtr); } treePtr->entryPtr = NULL; Blt_Free(treePtr); } /* *---------------------------------------------------------------------- * * CreateNode -- * * Creates and inserts a new node into the given tree at the * specified position. * * Results: * Returns a pointer to the newly created node. If an error * occurred, such as the entry could not be configured, NULL * is returned. * *---------------------------------------------------------------------- */ static Tree * CreateNode(hboxPtr, parentPtr, position, name) Hierbox *hboxPtr; /* Hierarchy widget record */ Tree *parentPtr; /* Pointer to parent node. */ int position; /* Position in node list to insert node. */ char *name; /* Name identifier for the new node. */ { Entry *entryPtr; Tree *treePtr; Blt_HashEntry *hPtr; int isNew; int serial; /* Create the entry structure */ entryPtr = Blt_Calloc(1, sizeof(Entry)); assert(entryPtr); entryPtr->flags |= (BUTTON_AUTO | ENTRY_MAPPED); entryPtr->hboxPtr = hboxPtr; if (name == NULL) { name = ""; } entryPtr->labelText = Blt_Strdup(name); if (ConfigureEntry(hboxPtr, entryPtr, 0, (char **)NULL, 0) != TCL_OK) { DestroyEntry(entryPtr); return NULL; } /* Create the container structure too */ treePtr = Blt_Calloc(1, sizeof(Tree)); assert(treePtr); treePtr->nameId = Blt_GetUid(name); treePtr->entryPtr = entryPtr; /* Generate a unique node serial number. */ do { serial = hboxPtr->nextSerial++; hPtr = Blt_CreateHashEntry(&(hboxPtr->nodeTable), (char *)serial, &isNew); } while (!isNew); Blt_SetHashValue(hPtr, treePtr); entryPtr->hashPtr = hPtr; if (parentPtr != NULL) { InsertNode(parentPtr, position, treePtr); } return treePtr; } /* *---------------------------------------------------------------------- * * FindComponent -- * * Searches for the given child node. The node is designated * by its node identifier string. * * Results: * If found, returns a node pointer. Otherwise NULL. * *---------------------------------------------------------------------- */ static Tree * FindComponent(parentPtr, name) Tree *parentPtr; char *name; { Blt_Uid nameId; nameId = Blt_FindUid(name); if (nameId != NULL) { register Tree *treePtr; Blt_ChainLink *linkPtr; /* The component identifier must already exist if the node exists. */ for (linkPtr = Blt_ChainFirstLink(parentPtr->chainPtr); linkPtr != NULL; linkPtr = Blt_ChainNextLink(linkPtr)) { treePtr = Blt_ChainGetValue(linkPtr); if (nameId == treePtr->nameId) { return treePtr; } } } return NULL; } /* *---------------------------------------------------------------------- * * SkipSeparators -- * * Moves the character pointer past one of more separators. * * Results: * Returns the updates character pointer. * *---------------------------------------------------------------------- */ static char * SkipSeparators(path, separator, length) char *path, *separator; int length; { while ((*path == separator[0]) && (strncmp(path, separator, length) == 0)) { path += length; } return path; } /* *---------------------------------------------------------------------- * * SplitPath -- * * Returns the trailing component of the given path. Trailing * separators are ignored. * * Results: * Returns the string of the tail component. * *---------------------------------------------------------------------- */ static int SplitPath(hboxPtr, path, levelPtr, compPtrPtr) Hierbox *hboxPtr; char *path; int *levelPtr; char ***compPtrPtr; { int skipLen, pathLen; char *sep; int level, listSize; char **components; register char *p; skipLen = strlen(hboxPtr->separator); path = SkipSeparators(path, hboxPtr->separator, skipLen); pathLen = strlen(path); level = pathLen / skipLen; listSize = (level + 1) * sizeof(char *); components = Blt_Malloc(listSize + (pathLen + 1)); assert(components); p = (char *)components + listSize; strcpy(p, path); sep = strstr(p, hboxPtr->separator); level = 0; while ((*p != '\0') && (sep != NULL)) { *sep = '\0'; components[level++] = p; p = SkipSeparators(sep + skipLen, hboxPtr->separator, skipLen); sep = strstr(p, hboxPtr->separator); } if (*p != '\0') { components[level++] = p; } components[level] = NULL; *levelPtr = level; *compPtrPtr = components; return TCL_OK; } /* *---------------------------------------------------------------------- * * FindPath -- * * Finds the node designated by the given path. Each path * component is searched for as the tree is traversed. * * A leading character string is trimmed off the path if it * matches the one designated (see the -trimleft option). * * If no separator is designated (see the -separator * configuration option), the path is considered a Tcl list. * Otherwise the each component of the path is separated by a * character string. Leading and trailing separators are * ignored. Multiple separators are treated as one. * * Results: * Returns the pointer to the designated node. If any component * can't be found, NULL is returned. * *---------------------------------------------------------------------- */ static Tree * FindPath(hboxPtr, rootPtr, path) Hierbox *hboxPtr; Tree *rootPtr; char *path; { register char *p; int skip; char *sep; Tree *treePtr; char save; /* Trim off characters that we don't want */ if (hboxPtr->trimLeft != NULL) { register char *s; /* Trim off leading character string if one exists. */ for (p = path, s = hboxPtr->trimLeft; *s != '\0'; s++, p++) { if (*p != *s) { break; } } if (*s == '\0') { path = p; } } if (*path == '\0') { return rootPtr; } if (hboxPtr->separator == SEPARATOR_NONE) { return FindComponent(rootPtr, path); } if (hboxPtr->separator == SEPARATOR_LIST) { char **nameArr; int nComp; register int i; /* * No separator, so this must be a Tcl list of components. */ if (Tcl_SplitList(hboxPtr->interp, path, &nComp, &nameArr) != TCL_OK) { return NULL; } for (i = 0; i < nComp; i++) { treePtr = FindComponent(rootPtr, nameArr[i]); if (treePtr == NULL) { Blt_Free(nameArr); return NULL; } rootPtr = treePtr; } Blt_Free(nameArr); return rootPtr; } skip = strlen(hboxPtr->separator); path = SkipSeparators(path, hboxPtr->separator, skip); sep = strstr(path, hboxPtr->separator); p = path; treePtr = rootPtr; while ((*p != '\0') && (sep != NULL)) { save = *sep, *sep = '\0'; treePtr = FindComponent(treePtr, p); *sep = save; if (treePtr == NULL) { return NULL; /* Bad component name. */ } p = SkipSeparators(sep + skip, hboxPtr->separator, skip); sep = strstr(p, hboxPtr->separator); } if (*p != '\0') { treePtr = FindComponent(treePtr, p); if (treePtr == NULL) { return NULL; } } return treePtr; } /* *---------------------------------------------------------------------- * * NearestNode -- * * Finds the entry closest to the given screen X-Y coordinates * in the viewport. * * Results: * Returns the pointer to the closest node. If no node is * visible (nodes may be hidden), NULL is returned. * *---------------------------------------------------------------------- */ /*ARGSUSED*/ static Tree * NearestNode(hboxPtr, x, y, selectOne) Hierbox *hboxPtr; int x, y; int selectOne; { register Tree *lastPtr, **treePtrPtr; register Entry *entryPtr; /* * We implicitly can pick only visible entries. So make sure that * the tree exists. */ if (hboxPtr->nVisible == 0) { return NULL; } /* * Since the entry positions were previously computed in world * coordinates, convert Y-coordinate from screen to world * coordinates too. */ y = WORLDY(hboxPtr, y); treePtrPtr = hboxPtr->visibleArr; lastPtr = *treePtrPtr; for ( /* empty */ ; *treePtrPtr != NULL; treePtrPtr++) { entryPtr = (*treePtrPtr)->entryPtr; /* * If the start of the next entry starts beyond the point, * use the last entry. */ if (entryPtr->worldY > y) { return (selectOne) ? lastPtr : NULL; } if (y < (entryPtr->worldY + entryPtr->height)) { return *treePtrPtr; /* Found it. */ } lastPtr = *treePtrPtr; } return (selectOne) ? lastPtr : NULL; } static Tree * GetNodeByIndex(hboxPtr, string) Hierbox *hboxPtr; char *string; { if (isdigit(UCHAR(string[0]))) { int serial; if (Tcl_GetInt(NULL, string, &serial) == TCL_OK) { Blt_HashEntry *hPtr; hPtr = Blt_FindHashEntry(&(hboxPtr->nodeTable), (char *)serial); if (hPtr != NULL) { return (Tree *) Blt_GetHashValue(hPtr); } } } return NULL; } /* *---------------------------------------------------------------------- * * NodeToString -- * * Converts a node pointer in its string representation. The * string is the node's identifier number. * * Results: * The string representation of the node is returned. Note that * the string is stored statically, so that callers must save the * string before the next call to this routine overwrites the * static array again. * *---------------------------------------------------------------------- */ static char * NodeToString(hboxPtr, nodePtr) Hierbox *hboxPtr; Tree *nodePtr; { static char string[200]; int serial; /* Node table keys are integers. Convert them to strings. */ serial = (int)Blt_GetHashKey(&(hboxPtr->nodeTable), nodePtr->entryPtr->hashPtr); sprintf(string, "%d", serial); return string; } /* *---------------------------------------------------------------------- * * GetNode -- * * Converts a string into node pointer. The string may be in one * of the following forms: * * @x,y - Closest node to the specified X-Y position. * NNN - inode. * "active" - Currently active node. * "anchor" - anchor of selected region. * "mark" - mark of selected region. * "current" - Currently picked node in bindtable. * "focus" - The node currently with focus. * "root" - Root node. * "end" - Last open node in the entire hierarchy. * "next" - Next open node from the currently active * node. Wraps around back to top. * "last" - Previous open node from the currently active * node. Wraps around back to bottom. * "up" - Next open node from the currently active * node. Does not wrap around. * "down" - Previous open node from the currently active * node. Does not wrap around. * "nextsibling" - Next sibling of the current node. * "prevsibling" - Previous sibling of the current node. * "parent" - Parent of the current node. * "view.top" - Top of viewport. * "view.bottom" - Bottom of viewport. * @path - Absolute path to a node. * * Results: * If the string is successfully converted, TCL_OK is returned. * The pointer to the node is returned via treePtrPtr. * Otherwise, TCL_ERROR is returned and an error message is left * in interpreter's result field. * *---------------------------------------------------------------------- */ static int GetNode(hboxPtr, string, treePtrPtr) Hierbox *hboxPtr; char *string; Tree **treePtrPtr; { Tree *nodePtr; char c; Tree *fromPtr; c = string[0]; fromPtr = *treePtrPtr; nodePtr = NULL; if (isdigit(UCHAR(string[0]))) { nodePtr = GetNodeByIndex(hboxPtr, string); } else if ((c == 'e') && (strcmp(string, "end") == 0)) { nodePtr = EndNode(hboxPtr->rootPtr, ENTRY_OPEN | ENTRY_MAPPED); } else if ((c == 'a') && (strcmp(string, "anchor") == 0)) { nodePtr = hboxPtr->selAnchorPtr; } else if ((c == 'f') && (strcmp(string, "focus") == 0)) { nodePtr = hboxPtr->focusPtr; } else if ((c == 'r') && (strcmp(string, "root") == 0)) { nodePtr = hboxPtr->rootPtr; } else if ((c == 'p') && (strcmp(string, "parent") == 0)) { nodePtr = fromPtr; if (nodePtr->parentPtr != NULL) { nodePtr = nodePtr->parentPtr; } } else if ((c == 'c') && (strcmp(string, "current") == 0)) { /* Can't trust picked item, if entries have been added or deleted. */ if (!(hboxPtr->flags & HIERBOX_DIRTY)) { nodePtr = (Tree *) Blt_GetCurrentItem(hboxPtr->bindTable); if (nodePtr == NULL) { nodePtr = (Tree *)Blt_GetCurrentItem(hboxPtr->buttonBindTable); } } } else if ((c == 'u') && (strcmp(string, "up") == 0)) { nodePtr = LastNode(fromPtr, ENTRY_OPEN | ENTRY_MAPPED); if (nodePtr == NULL) { nodePtr = fromPtr; } if ((nodePtr == hboxPtr->rootPtr) && (hboxPtr->hideRoot)) { nodePtr = NextNode(hboxPtr->rootPtr, ENTRY_OPEN | ENTRY_MAPPED); } } else if ((c == 'd') && (strcmp(string, "down") == 0)) { nodePtr = NextNode(fromPtr, ENTRY_OPEN | ENTRY_MAPPED); if (nodePtr == NULL) { nodePtr = fromPtr; } if ((nodePtr == hboxPtr->rootPtr) && (hboxPtr->hideRoot)) { nodePtr = NextNode(hboxPtr->rootPtr, ENTRY_OPEN | ENTRY_MAPPED); } } else if (((c == 'l') && (strcmp(string, "last") == 0)) || ((c == 'p') && (strcmp(string, "prev") == 0))) { nodePtr = LastNode(fromPtr, ENTRY_OPEN | ENTRY_MAPPED); if (nodePtr == NULL) { nodePtr = EndNode(hboxPtr->rootPtr, ENTRY_OPEN | ENTRY_MAPPED); } if ((nodePtr == hboxPtr->rootPtr) && (hboxPtr->hideRoot)) { nodePtr = NextNode(hboxPtr->rootPtr, ENTRY_OPEN | ENTRY_MAPPED); } } else if ((c == 'n') && (strcmp(string, "next") == 0)) { nodePtr = NextNode(fromPtr, ENTRY_OPEN | ENTRY_MAPPED); if (nodePtr == NULL) { if (hboxPtr->hideRoot) { nodePtr = NextNode(hboxPtr->rootPtr, ENTRY_OPEN | ENTRY_MAPPED); } else { nodePtr = hboxPtr->rootPtr; } } } else if ((c == 'n') && (strcmp(string, "nextsibling") == 0)) { if (fromPtr->linkPtr != NULL) { Blt_ChainLink *linkPtr; linkPtr = Blt_ChainNextLink(fromPtr->linkPtr); if (linkPtr != NULL) { nodePtr = Blt_ChainGetValue(linkPtr); } } } else if ((c == 'p') && (strcmp(string, "prevsibling") == 0)) { if (fromPtr->linkPtr != NULL) { Blt_ChainLink *linkPtr; linkPtr = Blt_ChainPrevLink(fromPtr->linkPtr); if (linkPtr != NULL) { nodePtr = Blt_ChainGetValue(linkPtr); } } } else if ((c == 'v') && (strcmp(string, "view.top") == 0)) { if (hboxPtr->nVisible > 0) { nodePtr = hboxPtr->visibleArr[0]; } } else if ((c == 'v') && (strcmp(string, "view.bottom") == 0)) { if (hboxPtr->nVisible > 0) { nodePtr = hboxPtr->visibleArr[hboxPtr->nVisible - 1]; } } else if (c == '@') { int x, y; if (Blt_GetXY(hboxPtr->interp, hboxPtr->tkwin, string, &x, &y) == TCL_OK) { nodePtr = NearestNode(hboxPtr, x, y, TRUE); } else { nodePtr = FindPath(hboxPtr, hboxPtr->rootPtr, string + 1); } if (nodePtr == NULL) { Tcl_ResetResult(hboxPtr->interp); Tcl_AppendResult(hboxPtr->interp, "can't find node entry \"", string, "\" in \"", Tk_PathName(hboxPtr->tkwin), "\"", (char *)NULL); return TCL_ERROR; } } else { nodePtr = FindPath(hboxPtr, hboxPtr->rootPtr, string); if (nodePtr == NULL) { Tcl_ResetResult(hboxPtr->interp); Tcl_AppendResult(hboxPtr->interp, "can't find node entry \"", string, "\" in \"", Tk_PathName(hboxPtr->tkwin), "\"", (char *)NULL); return TCL_ERROR; } } *treePtrPtr = nodePtr; return TCL_OK; } /* *---------------------------------------------------------------------- * * StringToNode -- * * Like GetNode but also finds nodes by serial number. * If the string starts with a digit, it's converted into a * number and then looked-up in a hash table. This means that * serial identifiers take precedence over node names with * the contain only numbers. * * Results: * If the string is successfully converted, TCL_OK is returned. * The pointer to the node is returned via treePtrPtr. * Otherwise, TCL_ERROR is returned and an error message is left * in interpreter's result field. * *---------------------------------------------------------------------- */ static int StringToNode(hboxPtr, string, treePtrPtr) Hierbox *hboxPtr; char *string; Tree **treePtrPtr; { *treePtrPtr = hboxPtr->focusPtr; if (GetNode(hboxPtr, string, treePtrPtr) != TCL_OK) { return TCL_ERROR; } if (*treePtrPtr == NULL) { Tcl_ResetResult(hboxPtr->interp); Tcl_AppendResult(hboxPtr->interp, "can't find node entry \"", string, "\" in \"", Tk_PathName(hboxPtr->tkwin), "\"", (char *)NULL); return TCL_ERROR; } return TCL_OK; } /* * Preprocess the command string for percent substitution. */ static void PercentSubst(hboxPtr, treePtr, command, resultPtr) Hierbox *hboxPtr; Tree *treePtr; char *command; Tcl_DString *resultPtr; { Tcl_DString dString; register char *last, *p; /* * Get the full path name of the node, in case we need to * substitute for it. */ GetFullPath(treePtr, hboxPtr->separator, &dString); Tcl_DStringInit(resultPtr); for (last = p = command; *p != '\0'; p++) { if (*p == '%') { char *string; char buf[3]; if (p > last) { *p = '\0'; Tcl_DStringAppend(resultPtr, last, -1); *p = '%'; } switch (*(p + 1)) { case '%': /* Percent sign */ string = "%"; break; case 'W': /* Widget name */ string = Tk_PathName(hboxPtr->tkwin); break; case 'P': /* Full pathname */ string = Tcl_DStringValue(&dString); break; case 'p': /* Name of the node */ string = treePtr->nameId; break; case 'n': /* Node identifier */ string = NodeToString(hboxPtr, treePtr); break; default: if (*(p + 1) == '\0') { p--; } buf[0] = *p, buf[1] = *(p + 1), buf[2] = '\0'; string = buf; break; } Tcl_DStringAppend(resultPtr, string, -1); p++; last = p + 1; } } if (p > last) { *p = '\0'; Tcl_DStringAppend(resultPtr, last, -1); } Tcl_DStringFree(&dString); } /* *---------------------------------------------------------------------- * * CloseNode -- * * Closes the node. If a close Tcl command is specified (see the * "-close" option), it's invoked after the node is closed. * * Results: * If an error occurred when executing the Tcl proc, TCL_ERROR is * returned. Otherwise TCL_OK is returned. * *---------------------------------------------------------------------- */ static int CloseNode(hboxPtr, treePtr) Hierbox *hboxPtr; Tree *treePtr; { Entry *entryPtr = treePtr->entryPtr; char *command; int result; /* * The tricky part here is that the "close" command can delete * this node. So we'll preserve it until after we mark it closed. */ Tcl_Preserve(treePtr); /* * Invoke the entry's "close" command, if there is one. Otherwise * try the hierbox's global "close" command. */ command = (entryPtr->closeCmd != NULL) ? entryPtr->closeCmd : hboxPtr->closeCmd; result = TCL_OK; if ((entryPtr->flags & ENTRY_OPEN) && (command != NULL)) { Tcl_DString cmdString; PercentSubst(hboxPtr, treePtr, command, &cmdString); result = Tcl_GlobalEval(hboxPtr->interp, Tcl_DStringValue(&cmdString)); Tcl_DStringFree(&cmdString); } /* * Mark the entry closed, only after the Tcl proc callback. */ entryPtr->flags &= ~ENTRY_OPEN; Tcl_Release(treePtr); return result; } /* *---------------------------------------------------------------------- * * OpenNode -- * * Opens the node. If an open Tcl command is specified (see the * "-open" option), it's invoked after the node is opened. * * Results: * If an error occurred when executing the Tcl proc, TCL_ERROR is * returned. Otherwise TCL_OK is returned. * *---------------------------------------------------------------------- */ static int OpenNode(hboxPtr, treePtr) Hierbox *hboxPtr; Tree *treePtr; { Entry *entryPtr = treePtr->entryPtr; char *string; int result; /* * The tricky part here is that the "open" command can delete * this node. So we'll preserve it until after we mark it closed. */ Tcl_Preserve(treePtr); /* * Invoke the entry's "open" command, if there is one. Otherwise * try the hierbox's global "open" command. */ result = TCL_OK; string = (entryPtr->openCmd != NULL) ? entryPtr->openCmd : hboxPtr->openCmd; if (!(entryPtr->flags & ENTRY_OPEN) && (string != NULL)) { Tcl_DString dString; PercentSubst(hboxPtr, treePtr, string, &dString); result = Tcl_GlobalEval(hboxPtr->interp, Tcl_DStringValue(&dString)); Tcl_DStringFree(&dString); } /* * Mark the entry open, only after the Tcl proc callback has run. */ entryPtr->flags |= ENTRY_OPEN; Tcl_Release(treePtr); return result; } /* *---------------------------------------------------------------------- * * SelectNode -- * * Sets the selection flag for a node. The selection flag is * set/cleared/toggled based upon the flag set in the hierarchy * widget. * * Results: * Always returns TCL_OK. * *---------------------------------------------------------------------- */ static int SelectNode(hboxPtr, treePtr) Hierbox *hboxPtr; Tree *treePtr; { switch (hboxPtr->flags & SELECTION_MASK) { case SELECTION_CLEAR: DeselectEntry(hboxPtr, treePtr); break; case SELECTION_SET: SelectEntry(hboxPtr, treePtr); break; case SELECTION_TOGGLE: if (IsSelected(hboxPtr, treePtr)) { DeselectEntry(hboxPtr, treePtr); } else { SelectEntry(hboxPtr, treePtr); } break; } return TCL_OK; } /* *---------------------------------------------------------------------- * * SelectRange -- * * Sets the selection flag for a range of nodes. The range is * determined by two pointers which designate the first/last * nodes of the range. * * Results: * Always returns TCL_OK. * *---------------------------------------------------------------------- */ static int SelectRange(hboxPtr, fromPtr, toPtr) Hierbox *hboxPtr; Tree *fromPtr, *toPtr; { register Tree *treePtr; IterProc *proc; /* From the range determine the direction to select entries. */ proc = (IsBefore(toPtr, fromPtr)) ? LastNode : NextNode; for (treePtr = fromPtr; treePtr != NULL; treePtr = (*proc)(treePtr, ENTRY_OPEN | ENTRY_MAPPED)) { SelectNode(hboxPtr, treePtr); if (treePtr == toPtr) { break; } } return TCL_OK; } /* *---------------------------------------------------------------------- * * IsSelectedNode -- * * Adds the name of the node the interpreter result if it is * currently selected. * * Results: * Always returns TCL_OK. * *---------------------------------------------------------------------- */ static int IsSelectedNode(hboxPtr, treePtr) Hierbox *hboxPtr; Tree *treePtr; { if (IsSelected(hboxPtr, treePtr)) { Tcl_AppendElement(hboxPtr->interp, NodeToString(hboxPtr, treePtr)); } return TCL_OK; } /* *---------------------------------------------------------------------- * * GetSelectedLabels -- * * This routine is invoked when the selection is exported. Each * selected entry is appended line-by-line to a dynamic string * passed via the clientData field of the hierarchy widget. * * Results: * Always returns TCL_OK. * *---------------------------------------------------------------------- */ static int GetSelectedLabels(hboxPtr, treePtr) Hierbox *hboxPtr; Tree *treePtr; { if (IsSelected(hboxPtr, treePtr)) { Tcl_DString *resultPtr = (Tcl_DString *) hboxPtr->clientData; Entry *entryPtr = treePtr->entryPtr; Tcl_DStringAppend(resultPtr, entryPtr->labelText, -1); Tcl_DStringAppend(resultPtr, "\n", -1); } return TCL_OK; } /* *---------------------------------------------------------------------- * * SizeOfNode -- * * Returns the number of children at the given node. The sum * is passed via the clientData field of the hierarchy widget. * * Results: * Always TCL_OK. * *---------------------------------------------------------------------- */ static int SizeOfNode(hboxPtr, treePtr) Hierbox *hboxPtr; Tree *treePtr; { int *sumPtr = (int *)&(hboxPtr->clientData); *sumPtr += Blt_ChainGetLength(treePtr->chainPtr); return TCL_OK; } /* *---------------------------------------------------------------------- * * CompareNodesByName -- * * Comparison routine (used by qsort) to sort a chain of subnodes. * A simple string comparison is performed on each node name. * * Results: * 1 is the first is greater, -1 is the second is greater, 0 * if equal. * *---------------------------------------------------------------------- */ static int CompareNodesByName(link1PtrPtr, link2PtrPtr) Blt_ChainLink **link1PtrPtr, **link2PtrPtr; { Tree *t1Ptr, *t2Ptr; t1Ptr = Blt_ChainGetValue(*link1PtrPtr); t2Ptr = Blt_ChainGetValue(*link2PtrPtr); return strcmp(t1Ptr->nameId, t2Ptr->nameId); } /* *---------------------------------------------------------------------- * * CompareNodesByTclCmd -- * * Comparison routine (used by qsort) to sort a list of subnodes. * A specified Tcl proc is invoked to compare the nodes. * * Results: * 1 is the first is greater, -1 is the second is greater, 0 * if equal. * *---------------------------------------------------------------------- */ static int CompareNodesByTclCmd(link1PtrPtr, link2PtrPtr) Blt_ChainLink **link1PtrPtr, **link2PtrPtr; { int result; Tree *t1Ptr, *t2Ptr; Hierbox *hboxPtr = hierBox; Tcl_Interp *interp = hboxPtr->interp; t1Ptr = Blt_ChainGetValue(*link1PtrPtr); t2Ptr = Blt_ChainGetValue(*link2PtrPtr); result = 0; /* Hopefully this will be Ok even if the * Tcl command fails to return the correct * result. */ if ((Tcl_VarEval(interp, hboxPtr->sortCmd, " ", Tk_PathName(hboxPtr->tkwin), " ", NodeToString(hboxPtr, t1Ptr), " ", NodeToString(hboxPtr, t2Ptr)) != TCL_OK) || (Tcl_GetInt(interp, Tcl_GetStringResult(interp), &result) != TCL_OK)) { Tcl_BackgroundError(interp); } Tcl_ResetResult(interp); return result; } /* *---------------------------------------------------------------------- * * SortNode -- * * Sorts the subnodes at a given node. * * Results: * Always returns TCL_OK. * *---------------------------------------------------------------------- */ static int SortNode(hboxPtr, treePtr) Hierbox *hboxPtr; Tree *treePtr; { if (treePtr->chainPtr != NULL) { if (hboxPtr->sortCmd != NULL) { hierBox = hboxPtr; Blt_ChainSort(treePtr->chainPtr, CompareNodesByTclCmd); } else { Blt_ChainSort(treePtr->chainPtr, CompareNodesByName); } } return TCL_OK; } /* *---------------------------------------------------------------------- * * UnmapNode -- * * Unmaps the given node. The node will not be drawn. Ignore * unmapping of the root node (it makes no sense). * * Results: * Always returns TCL_OK. * *---------------------------------------------------------------------- */ static int UnmapNode(hboxPtr, treePtr) Hierbox *hboxPtr; Tree *treePtr; { if (treePtr != hboxPtr->rootPtr) { treePtr->entryPtr->flags &= ~ENTRY_MAPPED; } return TCL_OK; } /* *---------------------------------------------------------------------- * * MapAncestors -- * * If a node in mapped, then all its ancestors must be mapped also. * This routine traverses upwards and maps each unmapped ancestor. * It's assumed that for any mapped ancestor, all it's ancestors * will already be mapped too. * * Results: * Always returns TCL_OK. * *---------------------------------------------------------------------- */ /*ARGSUSED*/ static int MapAncestors(hboxPtr, treePtr) Hierbox *hboxPtr; /* Not used. */ Tree *treePtr; { /* * Make sure that all the ancestors of this node are mapped too. */ treePtr = treePtr->parentPtr; while (treePtr != NULL) { if (treePtr->entryPtr->flags & ENTRY_MAPPED) { break; /* Assume ancestors are also mapped. */ } treePtr->entryPtr->flags |= ENTRY_MAPPED; treePtr = treePtr->parentPtr; } return TCL_OK; } /* *---------------------------------------------------------------------- * * MapNode -- * * Maps the given node. Only mapped nodes are drawn. * * Results: * Always returns TCL_OK. * *---------------------------------------------------------------------- */ static int MapNode(hboxPtr, treePtr) Hierbox *hboxPtr; Tree *treePtr; { treePtr->entryPtr->flags |= ENTRY_MAPPED; MapAncestors(hboxPtr, treePtr); return TCL_OK; } static int FixUnmappedSelections(hboxPtr, treePtr) Hierbox *hboxPtr; Tree *treePtr; { if (!(treePtr->entryPtr->flags & ENTRY_MAPPED)) { DeselectEntry(hboxPtr, treePtr); PruneSelection(hboxPtr, treePtr); if (IsAncestor(treePtr, hboxPtr->focusPtr)) { hboxPtr->focusPtr = treePtr->parentPtr; if (hboxPtr->focusPtr == NULL) { hboxPtr->focusPtr = hboxPtr->rootPtr; } Blt_SetFocusItem(hboxPtr->bindTable, hboxPtr->focusPtr, NULL); } } return TCL_OK; } static int DeleteNode(hboxPtr, treePtr) Hierbox *hboxPtr; Tree *treePtr; { /* * Indicate that the screen layout of the hierarchy may have changed * because the node was deleted. We don't want to access the * hboxPtr->visibleArr array if one of the nodes is bogus. */ hboxPtr->flags |= HIERBOX_DIRTY; if (treePtr == hboxPtr->activePtr) { hboxPtr->activePtr = treePtr->parentPtr; } if (treePtr == hboxPtr->activeButtonPtr) { hboxPtr->activeButtonPtr = NULL; } if (treePtr == hboxPtr->focusPtr) { hboxPtr->focusPtr = treePtr->parentPtr; Blt_SetFocusItem(hboxPtr->bindTable, hboxPtr->focusPtr, NULL); } if (treePtr == hboxPtr->selAnchorPtr) { hboxPtr->selAnchorPtr = NULL; } DeselectEntry(hboxPtr, treePtr); PruneSelection(hboxPtr, treePtr); if (treePtr->linkPtr != NULL) { /* Remove from parent's list */ Blt_ChainDeleteLink(treePtr->parentPtr->chainPtr, treePtr->linkPtr); treePtr->linkPtr = NULL; } /* * Node may still be in use, so we can't free it right now. We'll * mark the parent as NULL and remove it from the parent's list of * children. */ treePtr->parentPtr = NULL; Blt_DeleteBindings(hboxPtr->bindTable, treePtr); Blt_DeleteBindings(hboxPtr->buttonBindTable, treePtr); Tcl_EventuallyFree(treePtr, DestroyNode); return TCL_OK; } /* *---------------------------------------------------------------------- * * DestroyTree -- * * Recursively deletes the given node and all its subnodes. * * Results: * If successful, returns TCL_OK. Otherwise TCL_ERROR is * returned. * *---------------------------------------------------------------------- */ static int DestroyTree(hboxPtr, treePtr) Hierbox *hboxPtr; Tree *treePtr; { return ApplyToTree(hboxPtr, treePtr, DeleteNode, APPLY_RECURSE); } /*ARGSUSED*/ static void GetTags(table, object, context, list) Blt_BindTable table; /* Not used. */ ClientData object; ClientData context; /* Not used. */ Blt_List list; { Tree *treePtr; Blt_ListAppend(list, (char *)object, 0); treePtr = (Tree *) object; if (treePtr->entryPtr->tags != NULL) { int nNames; char **names; register char **p; if (Tcl_SplitList((Tcl_Interp *)NULL, treePtr->entryPtr->tags, &nNames, &names) == TCL_OK) { for (p = names; *p != NULL; p++) { Blt_ListAppend(list, Tk_GetUid(*p), 0); } Blt_Free(names); } } } /*ARGSUSED*/ static ClientData PickButton(clientData, x, y, contextPtr) ClientData clientData; int x, y; /* Screen coordinates of the test point. */ ClientData *contextPtr; /* Not used. */ { Hierbox *hboxPtr = clientData; Entry *entryPtr; Tree *treePtr; if (hboxPtr->flags & HIERBOX_DIRTY) { /* Can't trust selected entry, if entries have been added or deleted. */ if (hboxPtr->flags & HIERBOX_LAYOUT) { ComputeLayout(hboxPtr); } ComputeVisibleEntries(hboxPtr); } if (hboxPtr->nVisible == 0) { return (ClientData) 0; } treePtr = NearestNode(hboxPtr, x, y, FALSE); if (treePtr == NULL) { return (ClientData) 0; } entryPtr = treePtr->entryPtr; if (entryPtr->flags & ENTRY_BUTTON) { ButtonAttributes *buttonPtr = &(hboxPtr->button); int left, right, top, bottom; #define BUTTON_PAD 2 left = entryPtr->worldX + entryPtr->buttonX - BUTTON_PAD; right = left + buttonPtr->width + 2 * BUTTON_PAD; top = entryPtr->worldY + entryPtr->buttonY - BUTTON_PAD; bottom = top + buttonPtr->height + 2 * BUTTON_PAD; x = WORLDX(hboxPtr, x); y = WORLDY(hboxPtr, y); if ((x >= left) && (x < right) && (y >= top) && (y < bottom)) { return treePtr; } } return (ClientData) 0; } /*ARGSUSED*/ static ClientData PickEntry(clientData, x, y, contextPtr) ClientData clientData; int x, y; /* Screen coordinates of the test point. */ ClientData *contextPtr; /* Not used. */ { Hierbox *hboxPtr = clientData; Entry *entryPtr; Tree *treePtr; if (hboxPtr->flags & HIERBOX_DIRTY) { /* Can't trust selected entry, if entries have been added or deleted. */ if (hboxPtr->flags & HIERBOX_LAYOUT) { ComputeLayout(hboxPtr); } ComputeVisibleEntries(hboxPtr); } if (hboxPtr->nVisible == 0) { return (ClientData) 0; } treePtr = NearestNode(hboxPtr, x, y, FALSE); if (treePtr == NULL) { return (ClientData) 0; } entryPtr = treePtr->entryPtr; if (entryPtr->flags & ENTRY_BUTTON) { ButtonAttributes *buttonPtr = &(hboxPtr->button); int left, right, top, bottom; #define BUTTON_PAD 2 left = entryPtr->worldX + entryPtr->buttonX - BUTTON_PAD; right = left + buttonPtr->width + 2 * BUTTON_PAD; top = entryPtr->worldY + entryPtr->buttonY - BUTTON_PAD; bottom = top + buttonPtr->height + 2 * BUTTON_PAD; x = WORLDX(hboxPtr, x); y = WORLDY(hboxPtr, y); if ((x >= left) && (x < right) && (y >= top) && (y < bottom)) { return NULL; } } return treePtr; } static int ConfigureEntry(hboxPtr, entryPtr, argc, argv, flags) Hierbox *hboxPtr; Entry *entryPtr; int argc; char **argv; int flags; { GC newGC; XGCValues gcValues; unsigned long gcMask; int entryWidth, entryHeight; int width, height; Tk_Font font; XColor *colorPtr; hierBox = hboxPtr; if (Tk_ConfigureWidget(hboxPtr->interp, hboxPtr->tkwin, entryConfigSpecs, argc, argv, (char *)entryPtr, flags) != TCL_OK) { return TCL_ERROR; } entryPtr->iconWidth = entryPtr->iconHeight = 0; if (entryPtr->icons != NULL) { register int i; for (i = 0; i < 2; i++) { if (entryPtr->icons[i] == NULL) { break; } if (entryPtr->iconWidth < ImageWidth(entryPtr->icons[i])) { entryPtr->iconWidth = ImageWidth(entryPtr->icons[i]); } if (entryPtr->iconHeight < ImageHeight(entryPtr->icons[i])) { entryPtr->iconHeight = ImageHeight(entryPtr->icons[i]); } } } newGC = NULL; if ((entryPtr->icons == NULL) || (entryPtr->icons[0] == NULL)) { gcMask = GCClipMask | GCBackground; gcValues.clip_mask = hboxPtr->iconMask; gcValues.background = hboxPtr->iconColor->pixel; newGC = Tk_GetGC(hboxPtr->tkwin, gcMask, &gcValues); entryPtr->iconWidth = DEF_ICON_WIDTH; entryPtr->iconHeight = DEF_ICON_HEIGHT; } entryPtr->iconWidth += 2 * ICON_PADX; entryPtr->iconHeight += 2 * ICON_PADY; if (entryPtr->iconGC != NULL) { Tk_FreeGC(hboxPtr->display, entryPtr->iconGC); } entryPtr->iconGC = newGC; entryHeight = MAX(entryPtr->iconHeight, hboxPtr->button.height); entryWidth = 0; gcMask = GCForeground | GCFont; colorPtr = GETCOLOR(hboxPtr, entryPtr->labelColor); gcValues.foreground = colorPtr->pixel; font = GETFONT(hboxPtr, entryPtr->labelFont); gcValues.font = Tk_FontId(font); newGC = Tk_GetGC(hboxPtr->tkwin, gcMask, &gcValues); if (entryPtr->labelGC != NULL) { Tk_FreeGC(hboxPtr->display, entryPtr->labelGC); } entryPtr->labelGC = newGC; if (*entryPtr->labelText == '\0') { Tk_FontMetrics fontMetrics; Tk_GetFontMetrics(font, &fontMetrics); width = height = fontMetrics.linespace; } else { TextStyle ts; Blt_InitTextStyle(&ts); ts.shadow.offset = entryPtr->labelShadow.offset; ts.font = font; Blt_GetTextExtents(&ts, entryPtr->labelText, &width, &height); } width += 2 * (FOCUS_WIDTH + LABEL_PADX + hboxPtr->selBorderWidth); height += 2 * (FOCUS_WIDTH + LABEL_PADY + hboxPtr->selBorderWidth); width = ODD(width); height = ODD(height); entryWidth += width; if (entryHeight < height) { entryHeight = height; } entryPtr->labelWidth = width; entryPtr->labelHeight = height; width = height = 0; if (entryPtr->images != NULL) { register CachedImage *imagePtr; for (imagePtr = entryPtr->images; *imagePtr != NULL; imagePtr++) { width += ImageWidth(*imagePtr); if (height < ImageHeight(*imagePtr)) { height = ImageHeight(*imagePtr); } } } else if (entryPtr->dataText != NULL) { TextStyle ts; gcMask = GCForeground | GCFont; colorPtr = GETCOLOR(hboxPtr, entryPtr->dataColor); gcValues.foreground = colorPtr->pixel; font = GETFONT(hboxPtr, entryPtr->dataFont); gcValues.font = Tk_FontId(font); newGC = Tk_GetGC(hboxPtr->tkwin, gcMask, &gcValues); if (entryPtr->dataGC != NULL) { Tk_FreeGC(hboxPtr->display, entryPtr->dataGC); } entryPtr->dataGC = newGC; Blt_InitTextStyle(&ts); ts.font = font; ts.shadow.offset = entryPtr->dataShadow.offset; Blt_GetTextExtents(&ts, entryPtr->dataText, &width, &height); width += 2 * LABEL_PADX; height += 2 * LABEL_PADY; } entryWidth += width; if (entryHeight < height) { entryHeight = height; } entryPtr->width = entryWidth + 4; entryPtr->height = entryHeight + hboxPtr->leader; /* * Force the height of the entry to an even number. This is to * make the dots or the vertical line segments coincide with the * start of the horizontal lines. */ if (entryPtr->height & 0x01) { entryPtr->height++; } hboxPtr->flags |= HIERBOX_LAYOUT; EventuallyRedraw(hboxPtr); return TCL_OK; } /* * Hierbox Procedures */ /* *---------------------------------------------------------------------- * * EventuallyRedraw -- * * Queues a request to redraw the widget at the next idle point. * * Results: * None. * * Side effects: * Information gets redisplayed. Right now we don't do selective * redisplays: the whole window will be redrawn. * *---------------------------------------------------------------------- */ static void EventuallyRedraw(hboxPtr) Hierbox *hboxPtr; { if ((hboxPtr->tkwin != NULL) && !(hboxPtr->flags & HIERBOX_REDRAW)) { hboxPtr->flags |= HIERBOX_REDRAW; Tcl_DoWhenIdle(DisplayHierbox, hboxPtr); } } /* *---------------------------------------------------------------------- * * LabelBlinkProc -- * * This procedure is called as a timer handler to blink the * insertion cursor off and on. * * Results: * None. * * Side effects: * The cursor gets turned on or off, redisplay gets invoked, * and this procedure reschedules itself. * *---------------------------------------------------------------------- */ static void LabelBlinkProc(clientData) ClientData clientData; /* Pointer to record describing entry. */ { Hierbox *hboxPtr = clientData; TextEdit *editPtr = &(hboxPtr->labelEdit); int interval; if (!(hboxPtr->flags & HIERBOX_FOCUS) || (editPtr->offTime == 0)) { return; } if (editPtr->active) { editPtr->cursorOn ^= 1; interval = (editPtr->cursorOn) ? editPtr->onTime : editPtr->offTime; editPtr->timerToken = Tcl_CreateTimerHandler(interval, LabelBlinkProc, hboxPtr); EventuallyRedraw(hboxPtr); } } /* *---------------------------------------------------------------------- * * EventuallyInvokeSelectCmd -- * * Queues a request to execute the -selectcommand code associated * with the widget at the next idle point. Invoked whenever the * selection changes. * * Results: * None. * * Side effects: * Tcl code gets executed for some application-specific task. * *---------------------------------------------------------------------- */ static void EventuallyInvokeSelectCmd(hboxPtr) Hierbox *hboxPtr; { if (!(hboxPtr->flags & SELECTION_PENDING)) { hboxPtr->flags |= SELECTION_PENDING; Tcl_DoWhenIdle(SelectCmdProc, hboxPtr); } } /* * ---------------------------------------------------------------------- * * CreateHierbox -- * * ---------------------------------------------------------------------- */ static Hierbox * CreateHierbox(interp, tkwin) Tcl_Interp *interp; Tk_Window tkwin; { Hierbox *hboxPtr; hboxPtr = Blt_Calloc(1, sizeof(Hierbox)); assert(hboxPtr); Tk_SetClass(tkwin, "Hierbox"); hboxPtr->tkwin = tkwin; hboxPtr->display = Tk_Display(tkwin); hboxPtr->interp = interp; hboxPtr->leader = 0; hboxPtr->dashes = 1; hboxPtr->highlightWidth = 2; hboxPtr->selBorderWidth = 1; hboxPtr->borderWidth = 2; hboxPtr->relief = TK_RELIEF_SUNKEN; hboxPtr->selRelief = TK_RELIEF_FLAT; hboxPtr->scrollMode = BLT_SCROLL_MODE_HIERBOX; hboxPtr->button.closeRelief = hboxPtr->button.openRelief = TK_RELIEF_SOLID; hboxPtr->reqWidth = 200; hboxPtr->reqHeight = 400; hboxPtr->lineWidth = 1; hboxPtr->button.borderWidth = 1; hboxPtr->labelEdit.selAnchor = -1; hboxPtr->labelEdit.selFirst = hboxPtr->labelEdit.selLast = -1; hboxPtr->labelEdit.onTime = 600, hboxPtr->labelEdit.offTime = 300; Blt_ChainInit(&(hboxPtr->selectChain)); Blt_InitHashTable(&(hboxPtr->selectTable), BLT_ONE_WORD_KEYS); Blt_InitHashTable(&(hboxPtr->nodeTable), BLT_ONE_WORD_KEYS); Blt_InitHashTable(&(hboxPtr->imageTable), BLT_STRING_KEYS); hboxPtr->bindTable = Blt_CreateBindingTable(interp, tkwin, hboxPtr, PickEntry, GetTags); hboxPtr->buttonBindTable = Blt_CreateBindingTable(interp, tkwin, hboxPtr, PickButton, GetTags); #if (TK_MAJOR_VERSION > 4) Blt_SetWindowInstanceData(tkwin, hboxPtr); #endif return hboxPtr; } /* * ---------------------------------------------------------------------- * * DestroyHierbox -- * * This procedure is invoked by Tcl_EventuallyFree or Tcl_Release * to clean up the internal structure of a Hierbox at a safe time * (when no-one is using it anymore). * * Results: * None. * * Side effects: * Everything associated with the widget is freed up. * * ---------------------------------------------------------------------- */ static void DestroyHierbox(dataPtr) DestroyData dataPtr; /* Pointer to the widget record. */ { Hierbox *hboxPtr = (Hierbox *)dataPtr; ButtonAttributes *buttonPtr = &(hboxPtr->button); Tk_FreeOptions(configSpecs, (char *)hboxPtr, hboxPtr->display, 0); if (hboxPtr->tkwin != NULL) { Tk_DeleteSelHandler(hboxPtr->tkwin, XA_PRIMARY, XA_STRING); } if (hboxPtr->lineGC != NULL) { Tk_FreeGC(hboxPtr->display, hboxPtr->lineGC); } if (hboxPtr->focusGC != NULL) { Blt_FreePrivateGC(hboxPtr->display, hboxPtr->focusGC); } if (hboxPtr->tile != NULL) { Blt_FreeTile(hboxPtr->tile); } if (hboxPtr->visibleArr != NULL) { Blt_Free(hboxPtr->visibleArr); } if (hboxPtr->levelInfo != NULL) { Blt_Free(hboxPtr->levelInfo); } if (hboxPtr->iconBitmap != None) { Tk_FreeBitmap(hboxPtr->display, hboxPtr->iconBitmap); } if (hboxPtr->iconMask != None) { Tk_FreeBitmap(hboxPtr->display, hboxPtr->iconMask); } if (hboxPtr->iconColor != NULL) { Tk_FreeColor(hboxPtr->iconColor); } if (buttonPtr->images != NULL) { register CachedImage *imagePtr; for (imagePtr = buttonPtr->images; *imagePtr != NULL; imagePtr++) { FreeCachedImage(hboxPtr, *imagePtr); } Blt_Free(buttonPtr->images); } if (buttonPtr->activeGC != NULL) { Tk_FreeGC(hboxPtr->display, buttonPtr->activeGC); } if (buttonPtr->normalGC != NULL) { Tk_FreeGC(hboxPtr->display, buttonPtr->normalGC); } if (buttonPtr->lineGC != NULL) { Tk_FreeGC(hboxPtr->display, buttonPtr->lineGC); } DestroyTree(hboxPtr, hboxPtr->rootPtr); Blt_DeleteHashTable(&(hboxPtr->nodeTable)); Blt_ChainReset(&(hboxPtr->selectChain)); Blt_DeleteHashTable(&(hboxPtr->selectTable)); Blt_DestroyBindingTable(hboxPtr->bindTable); Blt_DestroyBindingTable(hboxPtr->buttonBindTable); Blt_Free(hboxPtr); } /* * -------------------------------------------------------------- * * HierboxEventProc -- * * This procedure is invoked by the Tk dispatcher for various * events on hierarchy widgets. * * Results: * None. * * Side effects: * When the window gets deleted, internal structures get * cleaned up. When it gets exposed, it is redisplayed. * * -------------------------------------------------------------- */ static void HierboxEventProc(clientData, eventPtr) ClientData clientData; /* Information about window. */ XEvent *eventPtr; /* Information about event. */ { Hierbox *hboxPtr = clientData; if (eventPtr->type == Expose) { if (eventPtr->xexpose.count == 0) { EventuallyRedraw(hboxPtr); } } else if (eventPtr->type == ConfigureNotify) { hboxPtr->flags |= (HIERBOX_LAYOUT | HIERBOX_SCROLL); EventuallyRedraw(hboxPtr); } else if ((eventPtr->type == FocusIn) || (eventPtr->type == FocusOut)) { if (eventPtr->xfocus.detail != NotifyInferior) { TextEdit *editPtr = &(hboxPtr->labelEdit); if (eventPtr->type == FocusIn) { hboxPtr->flags |= HIERBOX_FOCUS; } else { hboxPtr->flags &= ~HIERBOX_FOCUS; } Tcl_DeleteTimerHandler(editPtr->timerToken); if ((editPtr->active) && (hboxPtr->flags & HIERBOX_FOCUS)) { editPtr->cursorOn = TRUE; if (editPtr->offTime != 0) { editPtr->timerToken = Tcl_CreateTimerHandler(editPtr->onTime, LabelBlinkProc, clientData); } } else { editPtr->cursorOn = FALSE; editPtr->timerToken = (Tcl_TimerToken) NULL; } EventuallyRedraw(hboxPtr); } } else if (eventPtr->type == DestroyNotify) { if (hboxPtr->tkwin != NULL) { hboxPtr->tkwin = NULL; Tcl_DeleteCommandFromToken(hboxPtr->interp, hboxPtr->cmdToken); } if (hboxPtr->flags & HIERBOX_REDRAW) { Tcl_CancelIdleCall(DisplayHierbox, hboxPtr); } if (hboxPtr->flags & SELECTION_PENDING) { Tcl_CancelIdleCall(SelectCmdProc, hboxPtr); } Tcl_EventuallyFree(hboxPtr, DestroyHierbox); } } /* Selection Procedures */ /* *---------------------------------------------------------------------- * * SelectionProc -- * * This procedure is called back by Tk when the selection is * requested by someone. It returns part or all of the selection * in a buffer provided by the caller. * * Results: * The return value is the number of non-NULL bytes stored at * buffer. Buffer is filled (or partially filled) with a * NUL-terminated string containing part or all of the * selection, as given by offset and maxBytes. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int SelectionProc(clientData, offset, buffer, maxBytes) ClientData clientData; /* Information about the widget. */ int offset; /* Offset within selection of first * character to be returned. */ char *buffer; /* Location in which to place * selection. */ int maxBytes; /* Maximum number of bytes to place * at buffer, not including terminating * NULL character. */ { Hierbox *hboxPtr = clientData; int size; Tcl_DString dString; if (!hboxPtr->exportSelection) { return -1; } /* * Retrieve the names of the selected entries. */ Tcl_DStringInit(&dString); if (hboxPtr->sortSelection) { hboxPtr->clientData = &dString; ApplyToTree(hboxPtr, hboxPtr->rootPtr, GetSelectedLabels, APPLY_RECURSE | APPLY_BEFORE | APPLY_OPEN_ONLY); } else { Blt_ChainLink *linkPtr; Tree *treePtr; for (linkPtr = Blt_ChainFirstLink(&(hboxPtr->selectChain)); linkPtr != NULL; linkPtr = Blt_ChainNextLink(linkPtr)) { treePtr = Blt_ChainGetValue(linkPtr); Tcl_DStringAppend(&dString, treePtr->entryPtr->labelText, -1); Tcl_DStringAppend(&dString, "\n", -1); } } size = Tcl_DStringLength(&dString) - offset; strncpy(buffer, Tcl_DStringValue(&dString) + offset, maxBytes); Tcl_DStringFree(&dString); buffer[maxBytes] = '\0'; return (size > maxBytes) ? maxBytes : size; } /* *---------------------------------------------------------------------- * * LostSelection -- * * This procedure is called back by Tk when the selection is grabbed * away. * * Results: * None. * * Side effects: * The existing selection is unhighlighted, and the window is * marked as not containing a selection. * *---------------------------------------------------------------------- */ static void LostSelection(clientData) ClientData clientData; /* Information about the widget. */ { Hierbox *hboxPtr = clientData; if ((hboxPtr->selAnchorPtr != NULL) && (hboxPtr->exportSelection)) { ClearSelection(hboxPtr); } } /* *---------------------------------------------------------------------- * * HierboxInstCmdDeleteProc -- * * This procedure is invoked when a widget command is deleted. If * the widget isn't already in the process of being destroyed, * this command destroys it. * * Results: * None. * * Side effects: * The widget is destroyed. * *---------------------------------------------------------------------- */ static void HierboxInstCmdDeleteProc(clientData) ClientData clientData; /* Pointer to widget record for widget. */ { Hierbox *hboxPtr = clientData; /* * This procedure could be invoked either because the window was * destroyed and the command was then deleted (in which case tkwin * is NULL) or because the command was deleted, and then this * procedure destroys the widget. */ if (hboxPtr->tkwin != NULL) { Tk_Window tkwin; tkwin = hboxPtr->tkwin; hboxPtr->tkwin = NULL; Tk_DestroyWindow(tkwin); #ifdef ITCL_NAMESPACES Itk_SetWidgetCommand(tkwin, (Tcl_Command) NULL); #endif /* ITCL_NAMESPACES */ } } /* *---------------------------------------------------------------------- * * TileChangedProc * * Stub for image change notifications. Since we immediately draw * the image into a pixmap, we don't care about image changes. * * It would be better if Tk checked for NULL proc pointers. * * Results: * None. * *---------------------------------------------------------------------- */ /*ARGSUSED*/ static void TileChangedProc(clientData, tile) ClientData clientData; Blt_Tile tile; /* Not used. */ { Hierbox *hboxPtr = clientData; if (hboxPtr->tkwin != NULL) { EventuallyRedraw(hboxPtr); } } /* * ---------------------------------------------------------------------- * * ConfigureHierbox -- * * This procedure is called to process an argv/argc list, plus * the Tk option database, in order to configure (or reconfigure) * the widget. * * Results: * The return value is a standard Tcl result. If TCL_ERROR is * returned, then interp->result contains an error message. * * Side effects: * Configuration information, such as text string, colors, font, * etc. get set for hboxPtr; old resources get freed, if there * were any. The widget is redisplayed. * * ---------------------------------------------------------------------- */ static int ConfigureHierbox(interp, hboxPtr, argc, argv, flags) Tcl_Interp *interp; /* Interpreter to report errors back to. */ Hierbox *hboxPtr; /* Information about widget; may or may not * already have values for some fields. */ int argc; char **argv; int flags; { XGCValues gcValues; unsigned long gcMask; GC newGC; Tk_Uid nameId; Pixmap bitmap; hierBox = hboxPtr; if (Tk_ConfigureWidget(interp, hboxPtr->tkwin, configSpecs, argc, argv, (char *)hboxPtr, flags) != TCL_OK) { return TCL_ERROR; } if (Blt_ConfigModified(configSpecs, "-font", "-linespacing", "-width", "-height", "-hideroot", (char *)NULL)) { /* * These options change the layout of the box. Mark for * update. */ hboxPtr->flags |= (HIERBOX_LAYOUT | HIERBOX_SCROLL); } if ((hboxPtr->reqHeight != Tk_ReqHeight(hboxPtr->tkwin)) || (hboxPtr->reqWidth != Tk_ReqWidth(hboxPtr->tkwin))) { Tk_GeometryRequest(hboxPtr->tkwin, hboxPtr->reqWidth, hboxPtr->reqHeight); } gcMask = (GCForeground | GCLineWidth); gcValues.foreground = hboxPtr->lineColor->pixel; gcValues.line_width = hboxPtr->lineWidth; if (hboxPtr->dashes > 0) { gcMask |= (GCLineStyle | GCDashList); gcValues.line_style = LineOnOffDash; gcValues.dashes = hboxPtr->dashes; } newGC = Tk_GetGC(hboxPtr->tkwin, gcMask, &gcValues); if (hboxPtr->lineGC != NULL) { Tk_FreeGC(hboxPtr->display, hboxPtr->lineGC); } hboxPtr->lineGC = newGC; /* * GC for active label. Dashed outline. */ gcMask = GCForeground | GCLineStyle; gcValues.foreground = hboxPtr->focusColor->pixel; gcValues.line_style = (LineIsDashed(hboxPtr->focusDashes)) ? LineOnOffDash : LineSolid; newGC = Blt_GetPrivateGC(hboxPtr->tkwin, gcMask, &gcValues); if (LineIsDashed(hboxPtr->focusDashes)) { hboxPtr->focusDashes.offset = 2; Blt_SetDashes(hboxPtr->display, newGC, &(hboxPtr->focusDashes)); } if (hboxPtr->focusGC != NULL) { Blt_FreePrivateGC(hboxPtr->display, hboxPtr->focusGC); } hboxPtr->focusGC = newGC; if (hboxPtr->iconBitmap == None) { nameId = Tk_GetUid("HierboxFolder"); bitmap = Tk_GetBitmap(interp, hboxPtr->tkwin, nameId); if (bitmap == None) { if (Tk_DefineBitmap(interp, nameId, (char *)folderBits, DEF_ICON_WIDTH, DEF_ICON_HEIGHT) != TCL_OK) { return TCL_ERROR; } bitmap = Tk_GetBitmap(interp, hboxPtr->tkwin, nameId); if (bitmap == None) { return TCL_ERROR; } } hboxPtr->iconBitmap = bitmap; Tcl_ResetResult(interp); } if (hboxPtr->iconMask == None) { nameId = Tk_GetUid("HierboxFolderMask"); bitmap = Tk_GetBitmap(interp, hboxPtr->tkwin, nameId); if (bitmap == None) { if (Tk_DefineBitmap(interp, nameId, (char *)folderMaskBits, DEF_ICON_WIDTH, DEF_ICON_HEIGHT) != TCL_OK) { return TCL_ERROR; } bitmap = Tk_GetBitmap(interp, hboxPtr->tkwin, nameId); if (bitmap == None) { return TCL_ERROR; } } hboxPtr->iconMask = bitmap; Tcl_ResetResult(interp); } if (hboxPtr->iconColor == NULL) { hboxPtr->iconColor = Tk_GetColor(interp, hboxPtr->tkwin, Tk_GetUid("yellow")); if (hboxPtr->iconColor == NULL) { return TCL_ERROR; } } if (hboxPtr->tile != NULL) { Blt_SetTileChangedProc(hboxPtr->tile, TileChangedProc, hboxPtr); } ConfigureButtons(hboxPtr); hboxPtr->inset = hboxPtr->highlightWidth + hboxPtr->borderWidth + INSET_PAD; EventuallyRedraw(hboxPtr); return TCL_OK; } /* * ---------------------------------------------------------------------- * * ResetCoordinates -- * * Determines the maximum height of all visible entries. * * Results: * Returns 1 if beyond the last visible entry, 0 otherwise. * * Side effects: * The array of visible nodes is filled. * * ---------------------------------------------------------------------- */ static void ResetCoordinates(hboxPtr, treePtr, infoPtr) Hierbox *hboxPtr; Tree *treePtr; LayoutInfo *infoPtr; { Entry *entryPtr = treePtr->entryPtr; int width; /* * If the entry is hidden, then do nothing. * Otherwise, include it in the layout. */ entryPtr->worldY = infoPtr->y; if (!(entryPtr->flags & ENTRY_MAPPED)) { return; } treePtr->level = infoPtr->level; if (infoPtr->depth < infoPtr->level) { infoPtr->depth = infoPtr->level; } if ((entryPtr->flags & BUTTON_SHOW) || ((entryPtr->flags & BUTTON_AUTO) && (Blt_ChainGetLength(treePtr->chainPtr) > 0))) { entryPtr->flags |= ENTRY_BUTTON; } else { entryPtr->flags &= ~ENTRY_BUTTON; } if (entryPtr->height < infoPtr->minHeight) { infoPtr->minHeight = entryPtr->height; } /* * Note: The maximum entry width below does not take into account * the space for the icon (level offset). This has to be * deferred because it's dependent upon the maximum icon * size. */ width = infoPtr->x + entryPtr->width; if (width > infoPtr->maxWidth) { infoPtr->maxWidth = width; } if (infoPtr->maxIconWidth < entryPtr->iconWidth) { infoPtr->maxIconWidth = entryPtr->iconWidth; } entryPtr->lineHeight = -(infoPtr->y); infoPtr->y += entryPtr->height; if (entryPtr->flags & ENTRY_OPEN) { Blt_ChainLink *linkPtr; int labelOffset; Tree *bottomPtr; infoPtr->level++; labelOffset = infoPtr->labelOffset; infoPtr->labelOffset = 0; bottomPtr = treePtr; for (linkPtr = Blt_ChainFirstLink(treePtr->chainPtr); linkPtr != NULL; linkPtr = Blt_ChainNextLink(linkPtr)) { treePtr = Blt_ChainGetValue(linkPtr); if (treePtr->entryPtr->flags & ENTRY_MAPPED) { ResetCoordinates(hboxPtr, treePtr, infoPtr); bottomPtr = treePtr; } } infoPtr->level--; entryPtr->lineHeight += bottomPtr->entryPtr->worldY; entryPtr->levelX = infoPtr->labelOffset; infoPtr->labelOffset = labelOffset; } if (infoPtr->labelOffset < entryPtr->labelWidth) { infoPtr->labelOffset = entryPtr->labelWidth; } } /* * ---------------------------------------------------------------------- * * ComputeWidths -- * * Determines the maximum height of all visible entries. * * Results: * Returns 1 if beyond the last visible entry, 0 otherwise. * * Side effects: * The array of visible nodes is filled. * * ---------------------------------------------------------------------- */ static void ComputeWidths(hboxPtr, treePtr) Hierbox *hboxPtr; Tree *treePtr; { Entry *entryPtr = treePtr->entryPtr; if (!(entryPtr->flags & ENTRY_MAPPED)) { return; } if (entryPtr->iconWidth > LEVELWIDTH(treePtr->level + 1)) { LEVELWIDTH(treePtr->level + 1) = entryPtr->iconWidth; } if (entryPtr->flags & ENTRY_OPEN) { Blt_ChainLink *linkPtr; for (linkPtr = Blt_ChainFirstLink(treePtr->chainPtr); linkPtr != NULL; linkPtr = Blt_ChainNextLink(linkPtr)) { treePtr = Blt_ChainGetValue(linkPtr); if (treePtr->entryPtr->flags & ENTRY_MAPPED) { ComputeWidths(hboxPtr, treePtr); } } } } /* * ---------------------------------------------------------------------- * * ComputeLayout -- * * Recompute the layout when entries are opened/closed, * inserted/deleted, or when text attributes change (such as * font, linespacing). * * Results: * None. * * Side effects: * The world coordinates are set for all the opened entries. * * ---------------------------------------------------------------------- */ static void ComputeLayout(hboxPtr) Hierbox *hboxPtr; { LayoutInfo info; info.level = info.depth = 0; info.x = info.y = 0; info.maxWidth = hboxPtr->button.width; info.maxIconWidth = hboxPtr->button.width; info.minHeight = INT_MAX; info.labelOffset = 0; if (hboxPtr->hideRoot) { info.y = -(hboxPtr->rootPtr->entryPtr->height); } ResetCoordinates(hboxPtr, hboxPtr->rootPtr, &info); hboxPtr->xScrollUnits = info.maxIconWidth; hboxPtr->minHeight = hboxPtr->yScrollUnits = info.minHeight; if (hboxPtr->reqScrollX > 0) { hboxPtr->xScrollUnits = hboxPtr->reqScrollX; } if (hboxPtr->reqScrollY > 0) { hboxPtr->yScrollUnits = hboxPtr->reqScrollY; } hboxPtr->depth = info.depth + 1; hboxPtr->worldWidth = info.maxWidth + (hboxPtr->depth * info.maxIconWidth); if (hboxPtr->worldWidth < 1) { hboxPtr->worldWidth = 1; } hboxPtr->worldHeight = info.y; if (hboxPtr->worldHeight < 1) { hboxPtr->worldHeight = 1; } if (hboxPtr->yScrollUnits < 1) { hboxPtr->yScrollUnits = 1; } if (hboxPtr->xScrollUnits < 1) { hboxPtr->xScrollUnits = 1; } if (hboxPtr->levelInfo != NULL) { Blt_Free(hboxPtr->levelInfo); } hboxPtr->levelInfo = Blt_Calloc(hboxPtr->depth + 2, sizeof(LevelInfo)); assert(hboxPtr->levelInfo); ComputeWidths(hboxPtr, hboxPtr->rootPtr); { int sum, width; register int i; sum = 0; for (i = 0; i <= hboxPtr->depth; i++) { width = hboxPtr->levelInfo[i].width; width |= 0x01; hboxPtr->levelInfo[i].width = width; sum += width; hboxPtr->levelInfo[i + 1].x = sum; } } hboxPtr->flags &= ~HIERBOX_LAYOUT; } /* * ---------------------------------------------------------------------- * * ComputeVisibleEntries -- * * The entries visible in the viewport (the widget's window) are * inserted into the array of visible nodes. * * Results: * Returns 1 if beyond the last visible entry, 0 otherwise. * * Side effects: * The array of visible nodes is filled. * * ---------------------------------------------------------------------- */ static int ComputeVisibleEntries(hboxPtr) Hierbox *hboxPtr; { Entry *entryPtr; int height; Blt_ChainLink *linkPtr; register Tree *treePtr; int x, maxX; int nSlots; hboxPtr->xOffset = Blt_AdjustViewport(hboxPtr->xOffset, hboxPtr->worldWidth, VPORTWIDTH(hboxPtr), hboxPtr->xScrollUnits, hboxPtr->scrollMode); hboxPtr->yOffset = Blt_AdjustViewport(hboxPtr->yOffset, hboxPtr->worldHeight, VPORTHEIGHT(hboxPtr), hboxPtr->yScrollUnits, hboxPtr->scrollMode); height = VPORTHEIGHT(hboxPtr); /* Allocate worst case number of slots for entry array. */ nSlots = (height / hboxPtr->minHeight) + 3; if ((nSlots != hboxPtr->nVisible) && (hboxPtr->visibleArr != NULL)) { Blt_Free(hboxPtr->visibleArr); } hboxPtr->visibleArr = Blt_Calloc(nSlots, sizeof(Tree *)); assert(hboxPtr->visibleArr); hboxPtr->nVisible = 0; /* Find the node where the view port starts. */ treePtr = hboxPtr->rootPtr; entryPtr = treePtr->entryPtr; while ((entryPtr->worldY + entryPtr->height) <= hboxPtr->yOffset) { for (linkPtr = Blt_ChainLastLink(treePtr->chainPtr); linkPtr != NULL; linkPtr = Blt_ChainPrevLink(linkPtr)) { treePtr = Blt_ChainGetValue(linkPtr); if (IsHidden(treePtr)) { continue; /* Ignore hidden entries. */ } entryPtr = treePtr->entryPtr; if (entryPtr->worldY <= hboxPtr->yOffset) { break; } } /* * If we can't find the starting node, then the view must be * scrolled down, but some nodes were deleted. Reset the view * back to the top and try again. */ if (linkPtr == NULL) { if (hboxPtr->yOffset == 0) { return TCL_OK; /* All entries are hidden. */ } hboxPtr->yOffset = 0; continue; } } height += hboxPtr->yOffset; maxX = 0; while (treePtr != NULL) { if (!IsHidden(treePtr)) { entryPtr = treePtr->entryPtr; /* * Compute and save the entry's X-coordinate now that we know * what the maximum level offset for the entire Hierbox is. */ entryPtr->worldX = LEVELX(treePtr->level); x = entryPtr->worldX + LEVELWIDTH(treePtr->level) + LEVELWIDTH(treePtr->level + 1) + entryPtr->width; if (x > maxX) { maxX = x; } if (entryPtr->worldY >= height) { break; } hboxPtr->visibleArr[hboxPtr->nVisible] = treePtr; hboxPtr->nVisible++; } treePtr = NextNode(treePtr, ENTRY_OPEN | ENTRY_MAPPED); } hboxPtr->worldWidth = maxX; /* * ------------------------------------------------------------------- * * Note: It's assumed that the view port always starts at or * over an entry. Check that a change in the hierarchy * (e.g. closing a node) hasn't left the viewport beyond * the last entry. If so, adjust the viewport to start * on the last entry. * * ------------------------------------------------------------------- */ if (hboxPtr->xOffset > (hboxPtr->worldWidth - hboxPtr->xScrollUnits)) { hboxPtr->xOffset = hboxPtr->worldWidth - hboxPtr->xScrollUnits; } if (hboxPtr->yOffset > (hboxPtr->worldHeight - hboxPtr->yScrollUnits)) { hboxPtr->yOffset = hboxPtr->worldHeight - hboxPtr->yScrollUnits; } hboxPtr->xOffset = Blt_AdjustViewport(hboxPtr->xOffset, hboxPtr->worldWidth, VPORTWIDTH(hboxPtr), hboxPtr->xScrollUnits, hboxPtr->scrollMode); hboxPtr->yOffset = Blt_AdjustViewport(hboxPtr->yOffset, hboxPtr->worldHeight, VPORTHEIGHT(hboxPtr), hboxPtr->yScrollUnits, hboxPtr->scrollMode); hboxPtr->flags &= ~HIERBOX_DIRTY; return TCL_OK; } static int GetCursorLocation(hboxPtr, treePtr) Hierbox *hboxPtr; Tree *treePtr; { TextEdit *editPtr = &(hboxPtr->labelEdit); int x, y; int maxLines; Tk_Font font; TextStyle ts; TextLayout *textPtr; Tk_FontMetrics fontMetrics; int nBytes; int sum; Entry *entryPtr; TextFragment *fragPtr; register int i; entryPtr = treePtr->entryPtr; font = GETFONT(hboxPtr, entryPtr->labelFont); memset(&ts, 0, sizeof(TextStyle)); ts.font = font; ts.justify = TK_JUSTIFY_LEFT; ts.shadow.offset = entryPtr->labelShadow.offset; textPtr = Blt_GetTextLayout(entryPtr->labelText, &ts); Tk_GetFontMetrics(font, &fontMetrics); maxLines = (textPtr->height / fontMetrics.linespace) - 1; sum = 0; x = y = 0; fragPtr = textPtr->fragArr; for (i = 0; i <= maxLines; i++) { /* Total the number of bytes on each line. Include newlines. */ nBytes = fragPtr->count + 1; if ((sum + nBytes) > editPtr->insertPos) { x += Tk_TextWidth(font, fragPtr->text, editPtr->insertPos - sum); break; } y += fontMetrics.linespace; sum += nBytes; fragPtr++; } editPtr->x = x; editPtr->y = y; editPtr->height = fontMetrics.linespace; editPtr->width = 3; Blt_Free(textPtr); return TCL_OK; } /* * --------------------------------------------------------------------------- * * DrawVerticals -- * * Draws vertical lines for the ancestor nodes. While the entry * of the ancestor may not be visible, its vertical line segment * does extent into the viewport. So walk back up the hierarchy * drawing lines until we get to the root. * * Results: * None. * * Side Effects: * Vertical lines are drawn for the ancestor nodes. * * --------------------------------------------------------------------------- */ static void DrawVerticals(hboxPtr, treePtr, drawable) Hierbox *hboxPtr; /* Widget record containing the attribute * information for buttons. */ Tree *treePtr; /* Entry to be drawn. */ Drawable drawable; /* Pixmap or window to draw into. */ { Entry *entryPtr; /* Entry to be drawn. */ int x1, y1, x2, y2; int height; int x, y; while (treePtr->parentPtr != NULL) { treePtr = treePtr->parentPtr; entryPtr = treePtr->entryPtr; /* * World X-coordinates are computed only for entries that are in * the current view port. So for each of the off-screen ancestor * nodes we must compute it here too. */ entryPtr->worldX = LEVELX(treePtr->level); x = SCREENX(hboxPtr, entryPtr->worldX); y = SCREENY(hboxPtr, entryPtr->worldY); height = MAX(entryPtr->iconHeight, hboxPtr->button.height); y += (height - hboxPtr->button.height) / 2; x1 = x2 = x + LEVELWIDTH(treePtr->level) + LEVELWIDTH(treePtr->level + 1) / 2; y1 = y + hboxPtr->button.height / 2; y2 = y1 + entryPtr->lineHeight; if ((treePtr == hboxPtr->rootPtr) && (hboxPtr->hideRoot)) { y1 += entryPtr->height; } /* * Clip the line's Y-coordinates at the window border. */ if (y1 < 0) { y1 = 0; } if (y2 > Tk_Height(hboxPtr->tkwin)) { y2 = Tk_Height(hboxPtr->tkwin); } if ((y1 < Tk_Height(hboxPtr->tkwin)) && (y2 > 0)) { XDrawLine(hboxPtr->display, drawable, hboxPtr->lineGC, x1, y1, x2, y2); } } } /* * --------------------------------------------------------------------------- * * DrawButton -- * * Draws a button for the given entry. The button is drawn * centered in the region immediately to the left of the origin * of the entry (computed in the layout routines). The height * and width of the button were previously calculated from the * average row height. * * button height = entry height - (2 * some arbitrary padding). * button width = button height. * * The button may have a border. The symbol (either a plus or * minus) is slight smaller than the width or height minus the * border. * * x,y origin of entry * * +---+ * | + | icon label * +---+ * closed * * |----|----| horizontal offset * * +---+ * | - | icon label * +---+ * open * * Results: * None. * * Side Effects: * A button is drawn for the entry. * * --------------------------------------------------------------------------- */ static void DrawButton(hboxPtr, treePtr, drawable) Hierbox *hboxPtr; /* Widget record containing the attribute * information for buttons. */ Tree *treePtr; /* Node of entry. */ Drawable drawable; /* Pixmap or window to draw into. */ { ButtonAttributes *buttonPtr = &(hboxPtr->button); Entry *entryPtr; int relief; Tk_3DBorder border; GC gc, lineGC; int x, y; CachedImage image; int width, height; entryPtr = treePtr->entryPtr; width = LEVELWIDTH(treePtr->level); height = MAX(entryPtr->iconHeight, buttonPtr->height); entryPtr->buttonX = (width - buttonPtr->width) / 2; entryPtr->buttonY = (height - buttonPtr->height) / 2; x = SCREENX(hboxPtr, entryPtr->worldX) + entryPtr->buttonX; y = SCREENY(hboxPtr, entryPtr->worldY) + entryPtr->buttonY; if (treePtr == hboxPtr->activeButtonPtr) { border = buttonPtr->activeBorder; lineGC = buttonPtr->activeGC; } else { border = buttonPtr->border; lineGC = buttonPtr->lineGC; } relief = (entryPtr->flags & ENTRY_OPEN) ? buttonPtr->openRelief : buttonPtr->closeRelief; /* * FIXME: Reliefs "flat" and "solid" the same, since there's no * "solid" in pre-8.0 releases. Should change this when we go to a * pure 8.x release. */ if (relief == TK_RELIEF_SOLID) { relief = TK_RELIEF_FLAT; } Blt_Fill3DRectangle(hboxPtr->tkwin, drawable, border, x, y, buttonPtr->width, buttonPtr->height, buttonPtr->borderWidth, relief); if (relief == TK_RELIEF_FLAT) { XDrawRectangle(hboxPtr->display, drawable, lineGC, x, y, buttonPtr->width - 1, buttonPtr->height - 1); } x += buttonPtr->borderWidth; y += buttonPtr->borderWidth; width = buttonPtr->width - (2 * buttonPtr->borderWidth); height = buttonPtr->height - (2 * buttonPtr->borderWidth); image = NULL; if (buttonPtr->images != NULL) { /* Open or close button image? */ image = buttonPtr->images[0]; if ((entryPtr->flags & ENTRY_OPEN) && (buttonPtr->images[1] != NULL)) { image = buttonPtr->images[1]; } } /* Image or rectangle? */ if (image != NULL) { Tk_RedrawImage(ImageBits(image), 0, 0, width, height, drawable, x, y); } else { XSegment segArr[2]; int count; gc = (treePtr == hboxPtr->activeButtonPtr) ? buttonPtr->activeGC : buttonPtr->normalGC; count = 1; segArr[0].y1 = segArr[0].y2 = y + height / 2; segArr[0].x1 = x + BUTTON_IPAD; #ifdef WIN32 segArr[0].x2 = x + width - BUTTON_IPAD; #else segArr[0].x2 = x + width - BUTTON_IPAD - 1; #endif if (!(entryPtr->flags & ENTRY_OPEN)) { segArr[1].x1 = segArr[1].x2 = x + width / 2; segArr[1].y1 = y + BUTTON_IPAD; #ifdef WIN32 segArr[1].y2 = y + height - BUTTON_IPAD; #else segArr[1].y2 = y + height - BUTTON_IPAD - 1; #endif count++; } XDrawSegments(hboxPtr->display, drawable, gc, segArr, count); } } static void DisplayIcon(hboxPtr, treePtr, x, y, drawable) Hierbox *hboxPtr; /* Widget record containing the attribute * information for buttons. */ Tree *treePtr; /* Node of entry. */ int x, y; Drawable drawable; /* Pixmap or window to draw into. */ { Entry *entryPtr = treePtr->entryPtr; int entryHeight; CachedImage image; int isActive; entryHeight = MAX(entryPtr->iconHeight, hboxPtr->button.height); isActive = (treePtr == hboxPtr->activePtr); image = NULL; if ((isActive) && (entryPtr->activeIcons != NULL)) { image = entryPtr->activeIcons[0]; if ((treePtr == hboxPtr->focusPtr) && (entryPtr->activeIcons[1] != NULL)) { image = entryPtr->activeIcons[1]; } } else if (entryPtr->icons != NULL) { /* Selected or normal icon? */ image = entryPtr->icons[0]; if ((treePtr == hboxPtr->focusPtr) && (entryPtr->icons[1] != NULL)) { image = entryPtr->icons[1]; } } if (image != NULL) { /* Image or default icon bitmap? */ int width, height; int top, bottom; int inset; int maxY; height = ImageHeight(image); width = ImageWidth(image); x += (LEVELWIDTH(treePtr->level + 1) - width) / 2; y += (entryHeight - height) / 2; inset = hboxPtr->inset - INSET_PAD; maxY = Tk_Height(hboxPtr->tkwin) - inset; top = 0; bottom = y + height; if (y < inset) { height += y - inset; top = -y + inset; y = inset; } else if (bottom >= maxY) { height = maxY - y; } Tk_RedrawImage(ImageBits(image), 0, top, width, height, drawable, x, y); } else { x += (LEVELWIDTH(treePtr->level + 1) - DEF_ICON_WIDTH) / 2; y += (entryHeight - DEF_ICON_HEIGHT) / 2; XSetClipOrigin(hboxPtr->display, entryPtr->iconGC, x, y); XCopyPlane(hboxPtr->display, hboxPtr->iconBitmap, drawable, entryPtr->iconGC, 0, 0, DEF_ICON_WIDTH, DEF_ICON_HEIGHT, x, y, 1); } } static void DrawData(hboxPtr, treePtr, x, y, entryHeight, drawable) Hierbox *hboxPtr; /* Widget record containing the attribute * information for buttons. */ Tree *treePtr; /* Node of entry. */ int x, y; int entryHeight; Drawable drawable; /* Pixmap or window to draw into. */ { Entry *entryPtr = treePtr->entryPtr; /* * Auxillary data: text string or images. */ if (entryPtr->images != NULL) { register CachedImage *imagePtr; int imageY; for (imagePtr = entryPtr->images; *imagePtr != NULL; imagePtr++) { imageY = y; if (ImageHeight(*imagePtr) < entryHeight) { imageY += (entryHeight - ImageHeight(*imagePtr)) / 2; } Tk_RedrawImage(ImageBits(*imagePtr), 0, 0, ImageWidth(*imagePtr), ImageHeight(*imagePtr), drawable, x, imageY); x += ImageWidth(*imagePtr); } } else if (entryPtr->dataText != NULL) { TextStyle ts; Tk_Font font; XColor *colorPtr; int width, height; font = GETFONT(hboxPtr, entryPtr->dataFont); colorPtr = GETCOLOR(hboxPtr, entryPtr->dataColor); y += hboxPtr->selBorderWidth + LABEL_PADY; Blt_SetDrawTextStyle(&ts, font, entryPtr->dataGC, colorPtr, hboxPtr->selFgColor, entryPtr->dataShadow.color, 0.0, TK_ANCHOR_NW, TK_JUSTIFY_LEFT, 0, entryPtr->dataShadow.offset); Blt_GetTextExtents(&ts, entryPtr->dataText, &width, &height); if (height < entryHeight) { y += (entryHeight - height) / 2; } Blt_DrawText(hboxPtr->tkwin, drawable, entryPtr->dataText, &ts, x, y); } } static int DrawLabel(hboxPtr, treePtr, x, y, drawable) Hierbox *hboxPtr; /* Widget record containing the attribute * information for buttons. */ Tree *treePtr; /* Node of entry. */ int x, y; Drawable drawable; /* Pixmap or window to draw into. */ { Entry *entryPtr = treePtr->entryPtr; TextEdit *editPtr = &(hboxPtr->labelEdit); TextStyle ts; int width, height; /* Width and height of label. */ Tk_Font font; int isSelected, isFocused; int entryHeight; entryHeight = MAX(entryPtr->iconHeight, hboxPtr->button.height); font = GETFONT(hboxPtr, entryPtr->labelFont); isFocused = ((treePtr == hboxPtr->focusPtr) && (hboxPtr->flags & HIERBOX_FOCUS)); isSelected = IsSelected(hboxPtr, treePtr); /* Includes padding, selection 3-D border, and focus outline. */ width = entryPtr->labelWidth; height = entryPtr->labelHeight; /* Center the label, if necessary, vertically along the entry row. */ if (height < entryHeight) { y += (entryHeight - height) / 2; } #ifdef notdef /* Normal background color */ Blt_Fill3DRectangle(hboxPtr->tkwin, drawable, hboxPtr->border, x, y, width, height, 0, TK_RELIEF_FLAT); #endif if (isFocused) { /* Focus outline */ XDrawRectangle(hboxPtr->display, drawable, hboxPtr->focusGC, x, y, width - 1, height - 1); } x += FOCUS_WIDTH; y += FOCUS_WIDTH; if (isSelected) { Blt_Fill3DRectangle(hboxPtr->tkwin, drawable, hboxPtr->selBorder, x, y, width - 2 * FOCUS_WIDTH, height - 2 * FOCUS_WIDTH, hboxPtr->selBorderWidth, hboxPtr->selRelief); } x += LABEL_PADX + hboxPtr->selBorderWidth; y += LABEL_PADY + hboxPtr->selBorderWidth; if (*entryPtr->labelText != '\0') { XColor *normalColor; normalColor = GETCOLOR(hboxPtr, entryPtr->labelColor); Blt_SetDrawTextStyle(&ts, font, entryPtr->labelGC, normalColor, hboxPtr->selFgColor, entryPtr->labelShadow.color, 0.0, TK_ANCHOR_NW, TK_JUSTIFY_LEFT, 0, entryPtr->labelShadow.offset); ts.state = (isSelected) ? STATE_ACTIVE : 0; Blt_DrawText(hboxPtr->tkwin, drawable, entryPtr->labelText, &ts, x, y); } if ((isFocused) && (hboxPtr->focusEdit) && (editPtr->cursorOn)) { int x1, y1, x2, y2; GetCursorLocation(hboxPtr, treePtr); x1 = x + editPtr->x; x2 = x1 + 3; y1 = y + editPtr->y - 1; y2 = y1 + editPtr->height - 1; XDrawLine(hboxPtr->display, drawable, entryPtr->labelGC, x1, y1, x1, y2); XDrawLine(hboxPtr->display, drawable, entryPtr->labelGC, x1 - 2, y1, x2, y1); XDrawLine(hboxPtr->display, drawable, entryPtr->labelGC, x1 - 2, y2, x2, y2); } return entryHeight; } /* * --------------------------------------------------------------------------- * * DrawEntry -- * * Draws a button for the given entry. Note that buttons should only * be drawn if the entry has sub-entries to be opened or closed. It's * the responsibility of the calling routine to ensure this. * * The button is drawn centered in the region immediately to the left * of the origin of the entry (computed in the layout routines). The * height and width of the button were previously calculated from the * average row height. * * button height = entry height - (2 * some arbitrary padding). * button width = button height. * * The button has a border. The symbol (either a plus or minus) is * slight smaller than the width or height minus the border. * * x,y origin of entry * * +---+ * | + | icon label * +---+ * closed * * |----|----| horizontal offset * * +---+ * | - | icon label * +---+ * open * * Results: * None. * * Side Effects: * A button is drawn for the entry. * * --------------------------------------------------------------------------- */ static void DrawEntry(hboxPtr, treePtr, drawable) Hierbox *hboxPtr; /* Widget record containing the attribute * information for buttons. */ Tree *treePtr; /* Node of entry to be drawn. */ Drawable drawable; /* Pixmap or window to draw into. */ { ButtonAttributes *buttonPtr = &(hboxPtr->button); int x, y; int width, height; int entryHeight; int buttonY; int x1, y1, x2, y2; Entry *entryPtr; entryPtr = treePtr->entryPtr; x = SCREENX(hboxPtr, entryPtr->worldX); y = SCREENY(hboxPtr, entryPtr->worldY); width = LEVELWIDTH(treePtr->level); height = MAX(entryPtr->iconHeight, buttonPtr->height); entryPtr->buttonX = (width - buttonPtr->width) / 2; entryPtr->buttonY = (height - buttonPtr->height) / 2; buttonY = y + entryPtr->buttonY; x1 = x + (width / 2); y1 = y2 = buttonY + (buttonPtr->height / 2); x2 = x1 + (LEVELWIDTH(treePtr->level) + LEVELWIDTH(treePtr->level + 1)) / 2; if ((treePtr->parentPtr != NULL) && (hboxPtr->lineWidth > 0)) { /* * For every node except root, draw a horizontal line from * the vertical bar to the middle of the icon. */ XDrawLine(hboxPtr->display, drawable, hboxPtr->lineGC, x1, y1, x2, y2); } if ((entryPtr->flags & ENTRY_OPEN) && (hboxPtr->lineWidth > 0)) { /* * Entry is open, draw vertical line. */ y2 = y1 + entryPtr->lineHeight; if (y2 > Tk_Height(hboxPtr->tkwin)) { y2 = Tk_Height(hboxPtr->tkwin); /* Clip line at window border. */ } XDrawLine(hboxPtr->display, drawable, hboxPtr->lineGC, x2, y1, x2, y2); } if ((entryPtr->flags & ENTRY_BUTTON) && (treePtr->parentPtr != NULL)) { /* * Except for root, draw a button for every entry that needs * one. The displayed button can be either a Tk image or a * rectangle with plus or minus sign. */ DrawButton(hboxPtr, treePtr, drawable); } x += LEVELWIDTH(treePtr->level); DisplayIcon(hboxPtr, treePtr, x, y, drawable); x += LEVELWIDTH(treePtr->level + 1) + 4; /* Entry label. */ entryHeight = DrawLabel(hboxPtr, treePtr, x, y, drawable); if (treePtr->parentPtr != NULL) { x += treePtr->parentPtr->entryPtr->levelX + LABEL_PADX; } else { x += width + entryPtr->labelWidth + LABEL_PADX; } /* Auxilary data */ DrawData(hboxPtr, treePtr, x, y, entryHeight, drawable); } static void DrawOuterBorders(hboxPtr, drawable) Hierbox *hboxPtr; Drawable drawable; { /* Draw 3D border just inside of the focus highlight ring. */ if ((hboxPtr->borderWidth > 0) && (hboxPtr->relief != TK_RELIEF_FLAT)) { Blt_Draw3DRectangle(hboxPtr->tkwin, drawable, hboxPtr->border, hboxPtr->highlightWidth, hboxPtr->highlightWidth, Tk_Width(hboxPtr->tkwin) - 2 * hboxPtr->highlightWidth, Tk_Height(hboxPtr->tkwin) - 2 * hboxPtr->highlightWidth, hboxPtr->borderWidth, hboxPtr->relief); } /* Draw focus highlight ring. */ if (hboxPtr->highlightWidth > 0) { XColor *color; GC gc; color = (hboxPtr->flags & HIERBOX_FOCUS) ? hboxPtr->highlightColor : hboxPtr->highlightBgColor; gc = Tk_GCForColor(color, drawable); Tk_DrawFocusHighlight(hboxPtr->tkwin, gc, hboxPtr->highlightWidth, drawable); } hboxPtr->flags &= ~HIERBOX_BORDERS; } /* * ---------------------------------------------------------------------- * * DisplayHierbox -- * * This procedure is invoked to display the widget. * * Recompute the layout of the text if necessary. This is * necessary if the world coordinate system has changed. * Specifically, the following may have occurred: * * 1. a text attribute has changed (font, linespacing, etc.). * 2. an entry's option changed, possibly resizing the entry. * * This is deferred to the display routine since potentially * many of these may occur. * * Set the vertical and horizontal scrollbars. This is done * here since the window width and height are needed for the * scrollbar calculations. * * Results: * None. * * Side effects: * The widget is redisplayed. * * ---------------------------------------------------------------------- */ static void DisplayHierbox(clientData) ClientData clientData; /* Information about widget. */ { Hierbox *hboxPtr = clientData; Pixmap drawable; hboxPtr->flags &= ~HIERBOX_REDRAW; if (hboxPtr->tkwin == NULL) { return; /* Window has been destroyed. */ } if (hboxPtr->flags & HIERBOX_LAYOUT) { /* * Recompute the layout when entries are opened/closed, * inserted/deleted, or when text attributes change * (such as font, linespacing). */ ComputeLayout(hboxPtr); } if (hboxPtr->flags & HIERBOX_SCROLL) { int width, height; /* Scrolling means that the view port has changed and that the * visible entries need to be recomputed. */ ComputeVisibleEntries(hboxPtr); Blt_PickCurrentItem(hboxPtr->bindTable); Blt_PickCurrentItem(hboxPtr->buttonBindTable); width = VPORTWIDTH(hboxPtr); height = VPORTHEIGHT(hboxPtr); if (hboxPtr->flags & HIERBOX_XSCROLL) { if (hboxPtr->xScrollCmdPrefix != NULL) { Blt_UpdateScrollbar(hboxPtr->interp, hboxPtr->xScrollCmdPrefix, (double)hboxPtr->xOffset / hboxPtr->worldWidth, (double)(hboxPtr->xOffset + width) / hboxPtr->worldWidth); } } if (hboxPtr->flags & HIERBOX_YSCROLL) { if (hboxPtr->yScrollCmdPrefix != NULL) { Blt_UpdateScrollbar(hboxPtr->interp, hboxPtr->yScrollCmdPrefix, (double)hboxPtr->yOffset / hboxPtr->worldHeight, (double)(hboxPtr->yOffset + height) / hboxPtr->worldHeight); } } hboxPtr->flags &= ~HIERBOX_SCROLL; } if (!Tk_IsMapped(hboxPtr->tkwin)) { return; } drawable = Tk_GetPixmap(hboxPtr->display, Tk_WindowId(hboxPtr->tkwin), Tk_Width(hboxPtr->tkwin), Tk_Height(hboxPtr->tkwin), Tk_Depth(hboxPtr->tkwin)); /* * Clear the background either by tiling a pixmap or filling with * a solid color. Tiling takes precedence. */ if (hboxPtr->tile != NULL) { if (hboxPtr->scrollTile) { Blt_SetTSOrigin(hboxPtr->tkwin, hboxPtr->tile, -hboxPtr->xOffset, -hboxPtr->yOffset); } else { Blt_SetTileOrigin(hboxPtr->tkwin, hboxPtr->tile, 0, 0); } Blt_TileRectangle(hboxPtr->tkwin, drawable, hboxPtr->tile, 0, 0, Tk_Width(hboxPtr->tkwin), Tk_Height(hboxPtr->tkwin)); } else { Blt_Fill3DRectangle(hboxPtr->tkwin, drawable, hboxPtr->border, 0, 0, Tk_Width(hboxPtr->tkwin), Tk_Height(hboxPtr->tkwin), 0, TK_RELIEF_FLAT); } if (hboxPtr->nVisible > 0) { register Tree **treePtrPtr; if (hboxPtr->activePtr != NULL) { int y, height; y = SCREENY(hboxPtr, hboxPtr->activePtr->entryPtr->worldY); height = MAX(hboxPtr->activePtr->entryPtr->iconHeight, hboxPtr->activePtr->entryPtr->labelHeight); Blt_Fill3DRectangle(hboxPtr->tkwin, drawable, hboxPtr->activeBorder, 0, y, Tk_Width(hboxPtr->tkwin), height, 0, TK_RELIEF_FLAT); } if (hboxPtr->lineWidth > 0) { DrawVerticals(hboxPtr, hboxPtr->visibleArr[0], drawable); } for (treePtrPtr = hboxPtr->visibleArr; *treePtrPtr != NULL; treePtrPtr++) { DrawEntry(hboxPtr, *treePtrPtr, drawable); } } DrawOuterBorders(hboxPtr, drawable); /* Now copy the new view to the window. */ XCopyArea(hboxPtr->display, drawable, Tk_WindowId(hboxPtr->tkwin), hboxPtr->lineGC, 0, 0, Tk_Width(hboxPtr->tkwin), Tk_Height(hboxPtr->tkwin), 0, 0); Tk_FreePixmap(hboxPtr->display, drawable); } /* *---------------------------------------------------------------------- * * SelectCmdProc -- * * Invoked at the next idle point whenever the current * selection changes. Executes some application-specific code * in the -selectcommand option. This provides a way for * applications to handle selection changes. * * Results: * None. * * Side effects: * Tcl code gets executed for some application-specific task. * *---------------------------------------------------------------------- */ static void SelectCmdProc(clientData) ClientData clientData; /* Information about widget. */ { Hierbox *hboxPtr = clientData; /* * Preserve the widget structure in case the "select" callback * destroys it. */ Tcl_Preserve(hboxPtr); if (hboxPtr->selectCmd != NULL) { hboxPtr->flags &= ~SELECTION_PENDING; if (Tcl_GlobalEval(hboxPtr->interp, hboxPtr->selectCmd) != TCL_OK) { Tcl_BackgroundError(hboxPtr->interp); } } Tcl_Release(hboxPtr); } /* * -------------------------------------------------------------- * * HierboxCmd -- * * This procedure is invoked to process the Tcl command that * corresponds to a widget managed by this module. See the user * documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * * -------------------------------------------------------------- */ /* ARGSUSED */ static int HierboxCmd(clientData, interp, argc, argv) ClientData clientData; /* Main window associated with interpreter. */ Tcl_Interp *interp; /* Current interpreter. */ int argc; /* Number of arguments. */ char **argv; /* Argument strings. */ { Hierbox *hboxPtr; Tk_Window tkwin; Tree *treePtr; Tcl_CmdInfo cmdInfo; if (argc < 2) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " pathName ?option value?...\"", (char *)NULL); return TCL_ERROR; } tkwin = Tk_CreateWindowFromPath(interp, Tk_MainWindow(interp), argv[1], (char *)NULL); if (tkwin == NULL) { return TCL_ERROR; } hboxPtr = CreateHierbox(interp, tkwin); if (Blt_ConfigureWidgetComponent(interp, tkwin, "button", "Button", buttonConfigSpecs, 0, (char **)NULL, (char *)hboxPtr, 0) != TCL_OK) { goto error; } if (ConfigureHierbox(interp, hboxPtr, argc - 2, argv + 2, 0) != TCL_OK) { goto error; } treePtr = CreateNode(hboxPtr, (Tree *) NULL, APPEND, hboxPtr->separator); if (treePtr == NULL) { goto error; } hboxPtr->rootPtr = hboxPtr->focusPtr = treePtr; hboxPtr->selAnchorPtr = NULL; Blt_SetFocusItem(hboxPtr->bindTable, hboxPtr->focusPtr, NULL); Tk_CreateSelHandler(tkwin, XA_PRIMARY, XA_STRING, SelectionProc, hboxPtr, XA_STRING); Tk_CreateEventHandler(tkwin, ExposureMask | StructureNotifyMask | FocusChangeMask, HierboxEventProc, hboxPtr); hboxPtr->cmdToken = Tcl_CreateCommand(interp, argv[1], HierboxInstCmd, hboxPtr, HierboxInstCmdDeleteProc); #ifdef ITCL_NAMESPACES Itk_SetWidgetCommand(hboxPtr->tkwin, hboxPtr->cmdToken); #endif /* * Invoke a procedure to initialize various bindings on hierbox * entries. If the procedure doesn't already exist, source it * from "$blt_library/bltHierbox.tcl". We deferred sourcing the file * until now so that the variable $blt_library could be set within a * script. */ if (!Tcl_GetCommandInfo(interp, "blt::Hierbox::Init", &cmdInfo)) { static char initCmd[] = { "source [file join $blt_library hierbox.tcl]" }; if (Tcl_GlobalEval(interp, initCmd) != TCL_OK) { char info[200]; sprintf(info, "\n (while loading bindings for %s)", argv[0]); Tcl_AddErrorInfo(interp, info); goto error; } } if (Tcl_VarEval(interp, "blt::Hierbox::Init ", argv[1], (char *)NULL) != TCL_OK) { goto error; } treePtr->entryPtr->flags = (ENTRY_MAPPED); if (OpenNode(hboxPtr, treePtr) != TCL_OK) { goto error; } Tcl_SetResult(interp, Tk_PathName(hboxPtr->tkwin), TCL_VOLATILE); return TCL_OK; error: Tk_DestroyWindow(tkwin); return TCL_ERROR; } /* * -------------------------------------------------------------- * * Hierbox operations * * -------------------------------------------------------------- */ /*ARGSUSED*/ static int FocusOp(hboxPtr, interp, argc, argv) Hierbox *hboxPtr; Tcl_Interp *interp; int argc; char **argv; { if (argc == 3) { Tree *treePtr; treePtr = hboxPtr->focusPtr; if (GetNode(hboxPtr, argv[2], &treePtr) != TCL_OK) { return TCL_ERROR; } if ((treePtr != NULL) && (treePtr != hboxPtr->focusPtr)) { if (IsHidden(treePtr)) { /* Doesn't make sense to set focus to a node you can't see. */ ExposeAncestors(treePtr); } hboxPtr->flags |= (HIERBOX_LAYOUT | HIERBOX_SCROLL); hboxPtr->focusPtr = treePtr; hboxPtr->labelEdit.insertPos = strlen(treePtr->entryPtr->labelText); } EventuallyRedraw(hboxPtr); } Blt_SetFocusItem(hboxPtr->bindTable, hboxPtr->focusPtr, NULL); if (hboxPtr->focusPtr != NULL) { Tcl_SetResult(interp, NodeToString(hboxPtr, hboxPtr->focusPtr), TCL_VOLATILE); } return TCL_OK; } /* *---------------------------------------------------------------------- * * BboxOp -- * *---------------------------------------------------------------------- */ /*ARGSUSED*/ static int BboxOp(hboxPtr, interp, argc, argv) Hierbox *hboxPtr; Tcl_Interp *interp; int argc; /* Not used. */ char **argv; { Tree *treePtr; register int i; Entry *entryPtr; char string[200]; int width, height, yBot; int left, top, right, bottom; int screen; if (hboxPtr->flags & HIERBOX_LAYOUT) { /* * The layout is dirty. Recompute it now, before we use the * world dimensions. But remember, the "bbox" operation isn't * valid for hidden entries (since they're not visible, they * don't have world coordinates). */ ComputeLayout(hboxPtr); } left = hboxPtr->worldWidth; top = hboxPtr->worldHeight; right = bottom = 0; screen = FALSE; if ((argc > 2) && (argv[2][0] == '-') && (strcmp(argv[2], "-screen") == 0)) { screen = TRUE; argc--, argv++; } for (i = 2; i < argc; i++) { if ((argv[i][0] == 'a') && (strcmp(argv[i], "all") == 0)) { left = top = 0; right = hboxPtr->worldWidth; bottom = hboxPtr->worldHeight; break; } treePtr = hboxPtr->focusPtr; if (GetNode(hboxPtr, argv[i], &treePtr) != TCL_OK) { return TCL_ERROR; } if ((treePtr == NULL) || (IsHidden(treePtr))) { continue; } entryPtr = treePtr->entryPtr; yBot = entryPtr->worldY + entryPtr->height; height = VPORTHEIGHT(hboxPtr); if ((yBot <= hboxPtr->yOffset) && (entryPtr->worldY >= (hboxPtr->yOffset + height))) { continue; } if (bottom < yBot) { bottom = yBot; } if (top > entryPtr->worldY) { top = entryPtr->worldY; } if (right < (entryPtr->worldX + entryPtr->width + LEVELWIDTH(treePtr->level))) { right = (entryPtr->worldX + entryPtr->width + LEVELWIDTH(treePtr->level)); } if (left > entryPtr->worldX) { left = entryPtr->worldX; } } if (screen) { width = VPORTWIDTH(hboxPtr); height = VPORTHEIGHT(hboxPtr); /* * Do a min-max text for the intersection of the viewport and * the computed bounding box. If there is no intersection, return * the empty string. */ if ((right < hboxPtr->xOffset) || (bottom < hboxPtr->yOffset) || (left >= (hboxPtr->xOffset + width)) || (top >= (hboxPtr->yOffset + height))) { return TCL_OK; } /* Otherwise clip the coordinates at the view port boundaries. */ if (left < hboxPtr->xOffset) { left = hboxPtr->xOffset; } else if (right > (hboxPtr->xOffset + width)) { right = hboxPtr->xOffset + width; } if (top < hboxPtr->yOffset) { top = hboxPtr->yOffset; } else if (bottom > (hboxPtr->yOffset + height)) { bottom = hboxPtr->yOffset + height; } left = SCREENX(hboxPtr, left), top = SCREENY(hboxPtr, top); right = SCREENX(hboxPtr, right), bottom = SCREENY(hboxPtr, bottom); } if ((left < right) && (top < bottom)) { sprintf(string, "%d %d %d %d", left, top, right - left, bottom - top); Tcl_SetResult(interp, string, TCL_VOLATILE); } return TCL_OK; } /* *---------------------------------------------------------------------- * * ButtonActivateOp -- * * Selects the button to appear active. * *---------------------------------------------------------------------- */ /*ARGSUSED*/ static int ButtonActivateOp(hboxPtr, interp, argc, argv) Hierbox *hboxPtr; Tcl_Interp *interp; int argc; /* Not used. */ char **argv; { Tree *treePtr, *oldPtr; treePtr = hboxPtr->focusPtr; if (argv[3][0] == '\0') { treePtr = NULL; } else if (GetNode(hboxPtr, argv[3], &treePtr) != TCL_OK) { return TCL_ERROR; } oldPtr = hboxPtr->activeButtonPtr; hboxPtr->activeButtonPtr = treePtr; if (treePtr != oldPtr) { Drawable drawable; drawable = Tk_WindowId(hboxPtr->tkwin); if (oldPtr != NULL) { DrawButton(hboxPtr, oldPtr, drawable); } if (treePtr != NULL) { DrawButton(hboxPtr, treePtr, drawable); } DrawOuterBorders(hboxPtr, drawable); } return TCL_OK; } /* *---------------------------------------------------------------------- * * ButtonBindOp -- * * .t bind tag sequence command * *---------------------------------------------------------------------- */ /*ARGSUSED*/ static int ButtonBindOp(hboxPtr, interp, argc, argv) Hierbox *hboxPtr; Tcl_Interp *interp; int argc; /* Not used. */ char **argv; { ClientData object; /* * Individual entries are selected by inode only. All other * strings are interpreted as a binding tag. For example, if one * binds to "focus", it is assumed that this refers to a bind tag, * not the entry with focus. */ object = GetNodeByIndex(hboxPtr, argv[3]); if (object == 0) { object = (ClientData)Tk_GetUid(argv[3]); } return Blt_ConfigureBindings(interp, hboxPtr->buttonBindTable, object, argc - 4, argv + 4); } /* *---------------------------------------------------------------------- * * ButCgetOpOp -- * *---------------------------------------------------------------------- */ /*ARGSUSED*/ static int ButtonCgetOp(hboxPtr, interp, argc, argv) Hierbox *hboxPtr; Tcl_Interp *interp; int argc; /* Not used. */ char **argv; { return Tk_ConfigureValue(interp, hboxPtr->tkwin, buttonConfigSpecs, (char *)hboxPtr, argv[3], 0); } /* *---------------------------------------------------------------------- * * ButtonConfigureOp -- * * This procedure is called to process a list of configuration * options database, in order to reconfigure the one of more * entries in the widget. * * .h entryconfigure node node node node option value * * Results: * A standard Tcl result. If TCL_ERROR is returned, then * interp->result contains an error message. * * Side effects: * Configuration information, such as text string, colors, font, * etc. get set for hboxPtr; old resources get freed, if there * were any. The hypertext is redisplayed. * *---------------------------------------------------------------------- */ static int ButtonConfigureOp(hboxPtr, interp, argc, argv) Hierbox *hboxPtr; Tcl_Interp *interp; int argc; char **argv; { /* Figure out where the option value pairs begin */ argc -= 3; argv += 3; if (argc == 0) { return Tk_ConfigureInfo(interp, hboxPtr->tkwin, buttonConfigSpecs, (char *)hboxPtr, (char *)NULL, 0); } else if (argc == 1) { return Tk_ConfigureInfo(interp, hboxPtr->tkwin, buttonConfigSpecs, (char *)hboxPtr, argv[0], 0); } if (Tk_ConfigureWidget(hboxPtr->interp, hboxPtr->tkwin, buttonConfigSpecs, argc, argv, (char *)hboxPtr, TK_CONFIG_ARGV_ONLY) != TCL_OK) { return TCL_ERROR; } ConfigureButtons(hboxPtr); EventuallyRedraw(hboxPtr); return TCL_OK; } /* *---------------------------------------------------------------------- * * ButtonOp -- * * This procedure handles button operations. * * Results: * A standard Tcl result. * *---------------------------------------------------------------------- */ static Blt_OpSpec buttonOperSpecs[] = { {"activate", 1, (Blt_Op)ButtonActivateOp, 4, 4, "node",}, {"bind", 1, (Blt_Op)ButtonBindOp, 4, 6, "tagName ?sequence command?",}, {"cget", 2, (Blt_Op)ButtonCgetOp, 4, 4, "option",}, {"configure", 2, (Blt_Op)ButtonConfigureOp, 3, 0, "?option value?...",}, {"highlight", 1, (Blt_Op)ButtonActivateOp, 4, 4, "node",}, }; static int nButtonSpecs = sizeof(buttonOperSpecs) / sizeof(Blt_OpSpec); static int ButtonOp(hboxPtr, interp, argc, argv) Hierbox *hboxPtr; Tcl_Interp *interp; int argc; char **argv; { Blt_Op proc; int result; proc = Blt_GetOp(interp, nButtonSpecs, buttonOperSpecs, BLT_OP_ARG2, argc, argv, 0); if (proc == NULL) { return TCL_ERROR; } result = (*proc) (hboxPtr, interp, argc, argv); return result; } /* *---------------------------------------------------------------------- * * CgetOp -- * *---------------------------------------------------------------------- */ /*ARGSUSED*/ static int CgetOp(hboxPtr, interp, argc, argv) Hierbox *hboxPtr; Tcl_Interp *interp; int argc; /* Not used. */ char **argv; { return Tk_ConfigureValue(interp, hboxPtr->tkwin, configSpecs, (char *)hboxPtr, argv[2], 0); } /*ARGSUSED*/ static int CloseOp(hboxPtr, interp, argc, argv) Hierbox *hboxPtr; Tcl_Interp *interp; /* Not used. */ int argc; char **argv; { Tree *rootPtr; unsigned int flags; register int i; flags = 0; if (argc > 2) { int length; length = strlen(argv[2]); if ((argv[2][0] == '-') && (length > 1) && (strncmp(argv[2], "-recurse", length) == 0)) { argv++, argc--; flags |= APPLY_RECURSE; } } for (i = 2; i < argc; i++) { rootPtr = hboxPtr->focusPtr; if (GetNode(hboxPtr, argv[i], &rootPtr) != TCL_OK) { return TCL_ERROR; } if (rootPtr == NULL) { continue; } /* * Clear any selected entries that may become hidden by * closing the node. */ PruneSelection(hboxPtr, rootPtr); /* * ----------------------------------------------------------- * * Check if either the "focus" entry or selection anchor * is in this hierarchy. Must move it or disable it before * we close the node. Otherwise it may be deleted by a Tcl * "close" script, and we'll be left pointing to a bogus * memory location. * * ----------------------------------------------------------- */ if (IsAncestor(rootPtr, hboxPtr->focusPtr)) { hboxPtr->focusPtr = rootPtr; Blt_SetFocusItem(hboxPtr->bindTable, hboxPtr->focusPtr, NULL); } if (IsAncestor(rootPtr, hboxPtr->selAnchorPtr)) { hboxPtr->selAnchorPtr = NULL; } if (IsAncestor(rootPtr, hboxPtr->activePtr)) { hboxPtr->activePtr = rootPtr; } if (ApplyToTree(hboxPtr, rootPtr, CloseNode, flags) != TCL_OK) { return TCL_ERROR; } } hboxPtr->flags |= (HIERBOX_LAYOUT | HIERBOX_SCROLL); EventuallyRedraw(hboxPtr); return TCL_OK; } /* *---------------------------------------------------------------------- * * ConfigureOp -- * * This procedure is called to process an argv/argc list, plus * the Tk option database, in order to configure (or reconfigure) * the widget. * * Results: * A standard Tcl result. If TCL_ERROR is returned, then * interp->result contains an error message. * * Side effects: * Configuration information, such as text string, colors, font, * etc. get set for hboxPtr; old resources get freed, if there * were any. The widget is redisplayed. * *---------------------------------------------------------------------- */ static int ConfigureOp(hboxPtr, interp, argc, argv) Hierbox *hboxPtr; Tcl_Interp *interp; int argc; char **argv; { if (argc == 2) { return Tk_ConfigureInfo(interp, hboxPtr->tkwin, configSpecs, (char *)hboxPtr, (char *)NULL, 0); } else if (argc == 3) { return Tk_ConfigureInfo(interp, hboxPtr->tkwin, configSpecs, (char *)hboxPtr, argv[2], 0); } if (ConfigureHierbox(interp, hboxPtr, argc - 2, argv + 2, TK_CONFIG_ARGV_ONLY) != TCL_OK) { return TCL_ERROR; } EventuallyRedraw(hboxPtr); return TCL_OK; } /*ARGSUSED*/ static int CurselectionOp(hboxPtr, interp, argc, argv) Hierbox *hboxPtr; Tcl_Interp *interp; /* Not used. */ int argc; /* Not used. */ char **argv; /* Not used. */ { if (hboxPtr->sortSelection) { ApplyToTree(hboxPtr, hboxPtr->rootPtr, IsSelectedNode, APPLY_RECURSE | APPLY_OPEN_ONLY | APPLY_BEFORE); } else { Blt_ChainLink *linkPtr; Tree *treePtr; for (linkPtr = Blt_ChainFirstLink(&(hboxPtr->selectChain)); linkPtr != NULL; linkPtr = Blt_ChainNextLink(linkPtr)) { treePtr = Blt_ChainGetValue(linkPtr); Tcl_AppendElement(interp, NodeToString(hboxPtr, treePtr)); } } return TCL_OK; } /* *---------------------------------------------------------------------- * * ActivateOpOp -- * * Selects the tab to appear active. * *---------------------------------------------------------------------- */ /*ARGSUSED*/ static int ActivateOpOp(hboxPtr, interp, argc, argv) Hierbox *hboxPtr; Tcl_Interp *interp; int argc; /* Not used. */ char **argv; { Tree *treePtr, *oldPtr; treePtr = hboxPtr->focusPtr; if (argv[3][0] == '\0') { treePtr = NULL; } else if (GetNode(hboxPtr, argv[3], &treePtr) != TCL_OK) { return TCL_ERROR; } oldPtr = hboxPtr->activePtr; hboxPtr->activePtr = treePtr; if (treePtr != oldPtr) { EventuallyRedraw(hboxPtr); } return TCL_OK; } /* *---------------------------------------------------------------------- * * BindOp -- * * .t bind index sequence command * *---------------------------------------------------------------------- */ /*ARGSUSED*/ static int BindOp(hboxPtr, interp, argc, argv) Hierbox *hboxPtr; Tcl_Interp *interp; int argc; /* Not used. */ char **argv; { ClientData item; /* * Individual entries are selected by inode only. All other strings * are interpreted as a binding tag. */ item = GetNodeByIndex(hboxPtr, argv[2]); if (item == 0) { item = (ClientData)Tk_GetUid(argv[2]); } return Blt_ConfigureBindings(interp, hboxPtr->bindTable, item, argc - 3, argv + 3); } /* *---------------------------------------------------------------------- * * CgetOpOp -- * *---------------------------------------------------------------------- */ /*ARGSUSED*/ static int CgetOpOp(hboxPtr, interp, argc, argv) Hierbox *hboxPtr; Tcl_Interp *interp; int argc; /* Not used. */ char **argv; { Tree *treePtr; if (StringToNode(hboxPtr, argv[3], &treePtr) != TCL_OK) { return TCL_ERROR; } return Tk_ConfigureValue(interp, hboxPtr->tkwin, entryConfigSpecs, (char *)treePtr->entryPtr, argv[4], 0); } /* *---------------------------------------------------------------------- * * ConfigureOpOp -- * * This procedure is called to process a list of configuration * options database, in order to reconfigure the one of more * entries in the widget. * * .h entryconfigure node node node node option value * * Results: * A standard Tcl result. If TCL_ERROR is returned, then * interp->result contains an error message. * * Side effects: * Configuration information, such as text string, colors, font, * etc. get set for hboxPtr; old resources get freed, if there * were any. The hypertext is redisplayed. * *---------------------------------------------------------------------- */ static int ConfigureOpOp(hboxPtr, interp, argc, argv) Hierbox *hboxPtr; Tcl_Interp *interp; int argc; char **argv; { int nIds, nOpts; char **options; register int i; Tree *treePtr; /* Figure out where the option value pairs begin */ argc -= 3; argv += 3; for (i = 0; i < argc; i++) { if (argv[i][0] == '-') { break; } if (StringToNode(hboxPtr, argv[i], &treePtr) != TCL_OK) { return TCL_ERROR; /* Can't find node. */ } } nIds = i; /* Number of element names specified */ nOpts = argc - i; /* Number of options specified */ options = argv + i; /* Start of options in argv */ for (i = 0; i < nIds; i++) { StringToNode(hboxPtr, argv[i], &treePtr); if (argc == 1) { return Tk_ConfigureInfo(interp, hboxPtr->tkwin, entryConfigSpecs, (char *)treePtr->entryPtr, (char *)NULL, 0); } else if (argc == 2) { return Tk_ConfigureInfo(interp, hboxPtr->tkwin, entryConfigSpecs, (char *)treePtr->entryPtr, argv[2], 0); } if (ConfigureEntry(hboxPtr, treePtr->entryPtr, nOpts, options, TK_CONFIG_ARGV_ONLY) != TCL_OK) { return TCL_ERROR; } } hboxPtr->flags |= (HIERBOX_LAYOUT | HIERBOX_SCROLL); EventuallyRedraw(hboxPtr); return TCL_OK; } /* *---------------------------------------------------------------------- * * IsHiddenOpOp -- * *---------------------------------------------------------------------- */ /*ARGSUSED*/ static int IsHiddenOpOp(hboxPtr, interp, argc, argv) Hierbox *hboxPtr; Tcl_Interp *interp; int argc; /* Not used. */ char **argv; { Tree *treePtr; if (StringToNode(hboxPtr, argv[3], &treePtr) != TCL_OK) { return TCL_ERROR; } Blt_SetBooleanResult(interp, IsHidden(treePtr)); return TCL_OK; } /*ARGSUSED*/ static int IsBeforeOpOp(hboxPtr, interp, argc, argv) Hierbox *hboxPtr; Tcl_Interp *interp; int argc; /* Not used. */ char **argv; { Tree *n1Ptr, *n2Ptr; if ((StringToNode(hboxPtr, argv[3], &n1Ptr) != TCL_OK) || (StringToNode(hboxPtr, argv[4], &n2Ptr) != TCL_OK)) { return TCL_ERROR; } Blt_SetBooleanResult(interp, IsBefore(n1Ptr, n2Ptr)); return TCL_OK; } static int ScreenToIndex(hboxPtr, x, y) Hierbox *hboxPtr; int x, y; { Tk_Font font; TextStyle ts; TextLayout *textPtr; Tk_FontMetrics fontMetrics; TextFragment *fragPtr; int nBytes; Entry *entryPtr; Tree *treePtr; int lineNum; register int i; treePtr = hboxPtr->focusPtr; entryPtr = treePtr->entryPtr; if (*entryPtr->labelText == '\0') { return 0; } /* * Compute the X-Y offsets of the screen point from the start of * label. Force the offsets to point within the label. */ x -= SCREENX(hboxPtr, entryPtr->worldX) + LABEL_PADX + hboxPtr->selBorderWidth; y -= SCREENY(hboxPtr, entryPtr->worldY) + LABEL_PADY + hboxPtr->selBorderWidth; x -= LEVELWIDTH(treePtr->level) + LEVELWIDTH(treePtr->level + 1) + 4; font = GETFONT(hboxPtr, entryPtr->labelFont); memset(&ts, 0, sizeof(TextStyle)); ts.font = font; ts.justify = TK_JUSTIFY_LEFT; ts.shadow.offset = entryPtr->labelShadow.offset; textPtr = Blt_GetTextLayout(entryPtr->labelText, &ts); if (y < 0) { y = 0; } else if (y >= textPtr->height) { y = textPtr->height - 1; } Tk_GetFontMetrics(font, &fontMetrics); lineNum = y / fontMetrics.linespace; fragPtr = textPtr->fragArr + lineNum; if (x < 0) { nBytes = 0; } else if (x >= textPtr->width) { nBytes = fragPtr->count; } else { int newX; /* Find the character underneath the pointer. */ nBytes = Tk_MeasureChars(font, fragPtr->text, fragPtr->count, x, 0, &newX); if ((newX < x) && (nBytes < fragPtr->count)) { double fract; int length, charSize; char *next; next = fragPtr->text + nBytes; #if HAVE_UTF { Tcl_UniChar dummy; length = Tcl_UtfToUniChar(next, &dummy); } #else length = 1; #endif charSize = Tk_TextWidth(font, next, length); fract = ((double)(x - newX) / (double)charSize); if (ROUND(fract)) { nBytes += length; } } } for (i = lineNum - 1; i >= 0; i--) { fragPtr--; nBytes += fragPtr->count + 1; } Blt_Free(textPtr); return nBytes; } /* *--------------------------------------------------------------------------- * * GetLabelIndex -- * * Parse an index into an entry and return either its value * or an error. * * Results: * A standard Tcl result. If all went well, then *indexPtr is * filled in with the character index (into entryPtr) corresponding to * string. The index value is guaranteed to lie between 0 and * the number of characters in the string, inclusive. If an * error occurs then an error message is left in the interp's result. * * Side effects: * None. * *--------------------------------------------------------------------------- */ static int GetLabelIndex(hboxPtr, entryPtr, string, indexPtr) Hierbox *hboxPtr; Entry *entryPtr; char *string; int *indexPtr; { Tcl_Interp *interp = hboxPtr->interp; TextEdit *editPtr = &(hboxPtr->labelEdit); char c; c = string[0]; if ((c == 'a') && (strcmp(string, "anchor") == 0)) { *indexPtr = editPtr->selAnchor; } else if ((c == 'e') && (strcmp(string, "end") == 0)) { *indexPtr = strlen(entryPtr->labelText); } else if ((c == 'i') && (strcmp(string, "insert") == 0)) { *indexPtr = editPtr->insertPos; } else if ((c == 's') && (strcmp(string, "sel.first") == 0)) { if (editPtr->selFirst < 0) { Tcl_AppendResult(interp, "nothing is selected", (char *)NULL); return TCL_ERROR; } *indexPtr = editPtr->selFirst; } else if ((c == 's') && (strcmp(string, "sel.last") == 0)) { if (editPtr->selLast < 0) { Tcl_AppendResult(interp, "nothing is selected", (char *)NULL); return TCL_ERROR; } *indexPtr = editPtr->selLast; } else if (c == '@') { int x, y; if (Blt_GetXY(interp, hboxPtr->tkwin, string, &x, &y) != TCL_OK) { return TCL_ERROR; } *indexPtr = ScreenToIndex(hboxPtr, x, y); } else if (isdigit((int)c)) { int number; int maxChars; if (Tcl_GetInt(interp, string, &number) != TCL_OK) { return TCL_ERROR; } /* Don't allow the index to point outside the label. */ maxChars = Tcl_NumUtfChars(entryPtr->labelText, -1); if (number < 0) { *indexPtr = 0; } else if (number > maxChars) { *indexPtr = strlen(entryPtr->labelText); } else { *indexPtr = Tcl_UtfAtIndex(entryPtr->labelText, number) - entryPtr->labelText; } } else { Tcl_AppendResult(interp, "bad label index \"", string, "\"", (char *)NULL); return TCL_ERROR; } return TCL_OK; } /* *---------------------------------------------------------------------- * * IndexOpOp -- * * Returns the numeric index of the given string. Indices can be * one of the following: * * "anchor" Selection anchor. * "end" End of the label. * "insert" Insertion cursor. * "sel.first" First character selected. * "sel.last" Last character selected. * @x,y Index at X-Y screen coordinate. * number Returns the same number. * * Results: * A standard Tcl result. If the argument does not represent a * valid label index, then TCL_ERROR is returned and the interpreter * result will contain an error message. * *---------------------------------------------------------------------- */ /*ARGSUSED*/ static int IndexOpOp(hboxPtr, interp, argc, argv) Hierbox *hboxPtr; Tcl_Interp *interp; int argc; /* Not used. */ char **argv; { Entry *entryPtr; int nBytes, nChars; entryPtr = hboxPtr->focusPtr->entryPtr; if (GetLabelIndex(hboxPtr, entryPtr, argv[3], &nBytes) != TCL_OK) { return TCL_ERROR; } nChars = Tcl_NumUtfChars(entryPtr->labelText, nBytes); Tcl_SetResult(interp, Blt_Itoa(nChars), TCL_VOLATILE); return TCL_OK; } /* *---------------------------------------------------------------------- * * InsertOpOp -- * * Add new characters to the label of an entry. * * Results: * None. * * Side effects: * New information gets added to editPtr; it will be redisplayed * soon, but not necessarily immediately. * *---------------------------------------------------------------------- */ /*ARGSUSED*/ static int InsertOpOp(hboxPtr, interp, argc, argv) Hierbox *hboxPtr; Tcl_Interp *interp; /* Not used. */ int argc; char **argv; { Tree *treePtr; Entry *entryPtr; TextEdit *editPtr = &(hboxPtr->labelEdit); int oldSize, newSize; char *string; int extra; int insertPos; if (!hboxPtr->focusEdit) { return TCL_OK; /* Not in edit mode. */ } if (StringToNode(hboxPtr, argv[3], &treePtr) != TCL_OK) { return TCL_ERROR; } if (treePtr == NULL) { return TCL_OK; /* Not in edit mode. */ } entryPtr = treePtr->entryPtr; if (hboxPtr->focusPtr != treePtr) { hboxPtr->focusPtr = treePtr; editPtr->insertPos = strlen(entryPtr->labelText); editPtr->selAnchor = editPtr->selFirst = editPtr->selLast = -1; } if (GetLabelIndex(hboxPtr, entryPtr, argv[4], &insertPos) != TCL_OK) { return TCL_ERROR; } extra = strlen(argv[5]); if (extra == 0) { /* Nothing to insert. Move the cursor anyways. */ editPtr->insertPos = insertPos; EventuallyRedraw(hboxPtr); return TCL_OK; } oldSize = strlen(entryPtr->labelText); newSize = oldSize + extra; string = Blt_Malloc(sizeof(char) * (newSize + 1)); if (insertPos == oldSize) { /* Append */ strcpy(string, entryPtr->labelText); strcat(string, argv[5]); } else if (insertPos == 0) {/* Prepend */ strcpy(string, argv[5]); strcat(string, entryPtr->labelText); } else { /* Insert into existing. */ char *left, *right; char *p; left = entryPtr->labelText; right = left + insertPos; p = string; strncpy(p, left, insertPos); p += insertPos; strcpy(p, argv[5]); p += extra; strcpy(p, right); } /* * All indices from the start of the insertion to the end of the * string need to be updated. Simply move the indices down by the * number of characters added. */ if (editPtr->selFirst >= insertPos) { editPtr->selFirst += extra; } if (editPtr->selLast > insertPos) { editPtr->selLast += extra; } if ((editPtr->selAnchor > insertPos) || (editPtr->selFirst >= insertPos)) { editPtr->selAnchor += extra; } Blt_Free(entryPtr->labelText); entryPtr->labelText = string; editPtr->insertPos = insertPos + extra; GetCursorLocation(hboxPtr, treePtr); hboxPtr->flags |= (HIERBOX_LAYOUT | HIERBOX_SCROLL); EventuallyRedraw(hboxPtr); return TCL_OK; } /* *---------------------------------------------------------------------- * * DeleteOpOp -- * * Remove one or more characters from the label of an entry. * * Results: * None. * * Side effects: * Memory gets freed, the entry gets modified and (eventually) * redisplayed. * *---------------------------------------------------------------------- */ /*ARGSUSED*/ static int DeleteOpOp(hboxPtr, interp, argc, argv) Hierbox *hboxPtr; Tcl_Interp *interp; /* Not used. */ int argc; char **argv; { TextEdit *editPtr = &(hboxPtr->labelEdit); Tree *treePtr; Entry *entryPtr; int oldSize, newSize; int nDeleted; int first, last; char *string; char *p; if (!hboxPtr->focusEdit) { return TCL_OK; /* Not in edit mode. */ } if (StringToNode(hboxPtr, argv[3], &treePtr) != TCL_OK) { return TCL_ERROR; } if (treePtr == NULL) { return TCL_OK; /* Not in edit mode. */ } entryPtr = treePtr->entryPtr; if (hboxPtr->focusPtr != treePtr) { hboxPtr->focusPtr = treePtr; editPtr->insertPos = strlen(entryPtr->labelText); editPtr->selAnchor = editPtr->selFirst = editPtr->selLast = -1; } if ((GetLabelIndex(hboxPtr, entryPtr, argv[4], &first) != TCL_OK) || (GetLabelIndex(hboxPtr, entryPtr, argv[5], &last) != TCL_OK)) { return TCL_ERROR; } if (first >= last) { return TCL_OK; } if ((!hboxPtr->focusEdit) || (entryPtr == NULL)) { return TCL_OK; /* Not in edit mode. */ } oldSize = strlen(entryPtr->labelText); newSize = oldSize - (last - first); p = string = Blt_Malloc(sizeof(char) * (newSize + 1)); strncpy(p, entryPtr->labelText, first); p += first; strcpy(p, entryPtr->labelText + last); Blt_Free(entryPtr->labelText); entryPtr->labelText = string; nDeleted = last - first + 1; /* * Since deleting characters compacts the character array, we need to * update the various character indices according. It depends where * the index occurs in relation to range of deleted characters: * * before Ignore. * within Move the index back to the start of the deletion. * after Subtract off the deleted number of characters, * since the array has been compressed by that * many characters. * */ if (editPtr->selFirst >= first) { if (editPtr->selFirst >= last) { editPtr->selFirst -= nDeleted; } else { editPtr->selFirst = first; } } if (editPtr->selLast >= first) { if (editPtr->selLast >= last) { editPtr->selLast -= nDeleted; } else { editPtr->selLast = first; } } if (editPtr->selLast <= editPtr->selFirst) { editPtr->selFirst = editPtr->selLast = -1; /* Cut away the entire * selection. */ } if (editPtr->selAnchor >= first) { if (editPtr->selAnchor >= last) { editPtr->selAnchor -= nDeleted; } else { editPtr->selAnchor = first; } } if (editPtr->insertPos >= first) { if (editPtr->insertPos >= last) { editPtr->insertPos -= nDeleted; } else { editPtr->insertPos = first; } } hboxPtr->flags |= (HIERBOX_LAYOUT | HIERBOX_SCROLL); EventuallyRedraw(hboxPtr); return TCL_OK; } /* *---------------------------------------------------------------------- * * IsOpenOpOp -- * *---------------------------------------------------------------------- */ /*ARGSUSED*/ static int IsOpenOpOp(hboxPtr, interp, argc, argv) Hierbox *hboxPtr; Tcl_Interp *interp; int argc; /* Not used. */ char **argv; { Tree *treePtr; if (StringToNode(hboxPtr, argv[3], &treePtr) != TCL_OK) { return TCL_ERROR; } Blt_SetBooleanResult(interp, (treePtr->entryPtr->flags & ENTRY_OPEN)); return TCL_OK; } /*ARGSUSED*/ static int ChildrenOpOp(hboxPtr, interp, argc, argv) Hierbox *hboxPtr; Tcl_Interp *interp; int argc; char **argv; { Tree *parentPtr, *nodePtr; if (StringToNode(hboxPtr, argv[3], &parentPtr) != TCL_OK) { return TCL_ERROR; } if (argc == 4) { Blt_ChainLink *linkPtr; for (linkPtr = Blt_ChainFirstLink(parentPtr->chainPtr); linkPtr != NULL; linkPtr = Blt_ChainNextLink(linkPtr)) { nodePtr = Blt_ChainGetValue(linkPtr); Tcl_AppendElement(interp, NodeToString(hboxPtr, nodePtr)); } } else if (argc == 6) { Blt_ChainLink *firstPtr, *lastPtr; int first, last; int nNodes; if ((Blt_GetPosition(interp, argv[4], &first) != TCL_OK) || (Blt_GetPosition(interp, argv[5], &last) != TCL_OK)) { return TCL_ERROR; } nNodes = Blt_ChainGetLength(parentPtr->chainPtr); if (nNodes == 0) { return TCL_OK; } if ((last == -1) || (last >= nNodes)) { last = nNodes - 1; } if ((first == -1) || (first >= nNodes)) { first = nNodes - 1; } firstPtr = Blt_ChainGetNthLink(parentPtr->chainPtr, first); lastPtr = Blt_ChainGetNthLink(parentPtr->chainPtr, last); if (first > last) { for ( /*empty*/ ; lastPtr != NULL; lastPtr = Blt_ChainPrevLink(lastPtr)) { nodePtr = Blt_ChainGetValue(lastPtr); Tcl_AppendElement(interp, NodeToString(hboxPtr, nodePtr)); if (lastPtr == firstPtr) { break; } } } else { for ( /*empty*/ ; firstPtr != NULL; firstPtr = Blt_ChainNextLink(firstPtr)) { nodePtr = Blt_ChainGetValue(firstPtr); Tcl_AppendElement(interp, NodeToString(hboxPtr, nodePtr)); if (firstPtr == lastPtr) { break; } } } } else { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " ", argv[1], " ", argv[2], " index ?first last?", (char *)NULL); return TCL_ERROR; } return TCL_OK; } /* *---------------------------------------------------------------------- * * SizeOpOp -- * * Counts the number of entries at this node. * * Results: * A standard Tcl result. If an error occurred TCL_ERROR is * returned and interp->result will contain an error message. * Otherwise, TCL_OK is returned and interp->result contains * the number of entries. * *---------------------------------------------------------------------- */ static int SizeOpOp(hboxPtr, interp, argc, argv) Hierbox *hboxPtr; Tcl_Interp *interp; int argc; char **argv; { int length; Tree *rootPtr; int *sumPtr = (int *)&(hboxPtr->clientData); length = strlen(argv[3]); if ((argv[3][0] == '-') && (length > 1) && (strncmp(argv[3], "-recurse", length) == 0)) { argv++, argc--; } if (argc == 3) { Tcl_AppendResult(interp, "missing node argument: should be \"", argv[0], " entry open node\"", (char *)NULL); return TCL_ERROR; } if (StringToNode(hboxPtr, argv[3], &rootPtr) != TCL_OK) { return TCL_ERROR; } *sumPtr = 0; if (ApplyToTree(hboxPtr, rootPtr, SizeOfNode, 0) != TCL_OK) { return TCL_ERROR; } Tcl_SetResult(interp, Blt_Itoa(*sumPtr), TCL_VOLATILE); return TCL_OK; } /* *---------------------------------------------------------------------- * * EntryOp -- * * This procedure handles entry operations. * * Results: * A standard Tcl result. * *---------------------------------------------------------------------- */ static Blt_OpSpec entryOperSpecs[] = { {"activate", 1, (Blt_Op)ActivateOpOp, 4, 4, "node",}, {"cget", 2, (Blt_Op)CgetOpOp, 5, 5, "node option",}, {"children", 2, (Blt_Op)ChildrenOpOp, 4, 6, "node first last",}, {"configure", 2, (Blt_Op)ConfigureOpOp, 4, 0, "node ?node...? ?option value?...",}, {"delete", 1, (Blt_Op)DeleteOpOp, 6, 6, "node first last"}, {"highlight", 1, (Blt_Op)ActivateOpOp, 4, 4, "node",}, {"index", 3, (Blt_Op)IndexOpOp, 6, 6, "index"}, {"insert", 3, (Blt_Op)InsertOpOp, 6, 6, "node index string"}, {"isbefore", 3, (Blt_Op)IsBeforeOpOp, 5, 5, "node node",}, {"ishidden", 3, (Blt_Op)IsHiddenOpOp, 4, 4, "node",}, {"isopen", 3, (Blt_Op)IsOpenOpOp, 4, 4, "node",}, {"size", 1, (Blt_Op)SizeOpOp, 4, 5, "?-recurse? node",}, }; static int nEntrySpecs = sizeof(entryOperSpecs) / sizeof(Blt_OpSpec); static int EntryOp(hboxPtr, interp, argc, argv) Hierbox *hboxPtr; Tcl_Interp *interp; int argc; char **argv; { Blt_Op proc; int result; proc = Blt_GetOp(interp, nEntrySpecs, entryOperSpecs, BLT_OP_ARG2, argc, argv, 0); if (proc == NULL) { return TCL_ERROR; } result = (*proc) (hboxPtr, interp, argc, argv); return result; } /*ARGSUSED*/ static int ExactCompare(interp, name, pattern) Tcl_Interp *interp; /* Not used. */ char *name; char *pattern; { return (strcmp(name, pattern) == 0); } /*ARGSUSED*/ static int GlobCompare(interp, name, pattern) Tcl_Interp *interp; /* Not used. */ char *name; char *pattern; { return Tcl_StringMatch(name, pattern); } static int RegexpCompare(interp, name, pattern) Tcl_Interp *interp; char *name; char *pattern; { return Tcl_RegExpMatch(interp, name, pattern); } /* *---------------------------------------------------------------------- * * FindOp -- * * Find one or more nodes based upon the pattern provided. * * Results: * A standard Tcl result. The interpreter result will contain a * list of the node serial identifiers. * *---------------------------------------------------------------------- */ static int FindOp(hboxPtr, interp, argc, argv) Hierbox *hboxPtr; Tcl_Interp *interp; int argc; char **argv; { register Tree *treePtr; Tree *firstPtr, *lastPtr; int nMatches, maxMatches; char c; int length; CompareProc *compareProc; IterProc *nextProc; int invertMatch; /* normal search mode (matching entries) */ char *namePattern, *fullPattern; char *execCmd; register int i; int result; char *pattern; char *option; char *value; Tcl_DString dString, pathString; Blt_List optionList; register Blt_ListNode node; invertMatch = FALSE; maxMatches = 0; execCmd = namePattern = fullPattern = NULL; compareProc = ExactCompare; nextProc = NextNode; optionList = Blt_ListCreate(BLT_STRING_KEYS); /* * Step 1: Process flags for find operation. */ for (i = 2; i < argc; i++) { if (argv[i][0] != '-') { break; } option = argv[i] + 1; length = strlen(option); c = option[0]; if ((c == 'e') && (length > 2) && (strncmp(option, "exact", length) == 0)) { compareProc = ExactCompare; } else if ((c == 'g') && (strncmp(option, "glob", length) == 0)) { compareProc = GlobCompare; } else if ((c == 'r') && (strncmp(option, "regexp", length) == 0)) { compareProc = RegexpCompare; } else if ((c == 'n') && (length > 1) && (strncmp(option, "nonmatching", length) == 0)) { invertMatch = TRUE; } else if ((c == 'n') && (length > 1) && (strncmp(option, "name", length) == 0)) { if ((i + 1) == argc) { goto missingArg; } i++; namePattern = argv[i]; } else if ((c == 'f') && (strncmp(option, "full", length) == 0)) { if ((i + 1) == argc) { goto missingArg; } i++; fullPattern = argv[i]; } else if ((c == 'e') && (length > 2) && (strncmp(option, "exec", length) == 0)) { if ((i + 1) == argc) { goto missingArg; } i++; execCmd = argv[i]; } else if ((c == 'c') && (strncmp(option, "count", length) == 0)) { if ((i + 1) == argc) { goto missingArg; } i++; if (Tcl_GetInt(interp, argv[i], &maxMatches) != TCL_OK) { return TCL_ERROR; } if (maxMatches < 0) { Tcl_AppendResult(interp, "bad match count \"", argv[i], "\": should be a positive number", (char *)NULL); Blt_ListDestroy(optionList); return TCL_ERROR; } } else if ((option[0] == '-') && (option[1] == '\0')) { break; } else { /* * Verify that the switch is actually an node configuration * option. */ if (Tk_ConfigureValue(interp, hboxPtr->tkwin, entryConfigSpecs, (char *)hboxPtr->rootPtr->entryPtr, argv[i], 0) != TCL_OK) { Tcl_ResetResult(interp); Tcl_AppendResult(interp, "bad find switch \"", argv[i], "\"", (char *)NULL); Blt_ListDestroy(optionList); return TCL_ERROR; } if ((i + 1) == argc) { goto missingArg; } /* Save the option in the list of configuration options */ node = Blt_ListGetNode(optionList, argv[i]); if (node == NULL) { node = Blt_ListCreateNode(optionList, argv[i]); Blt_ListAppendNode(optionList, node); } Blt_ListSetValue(node, argv[i + 1]); i++; } } if ((argc - i) > 2) { Blt_ListDestroy(optionList); Tcl_AppendResult(interp, "too many args", (char *)NULL); return TCL_ERROR; } /* * Step 2: Find the range of the search. Check the order of two * nodes and arrange the search accordingly. * * Note: Be careful to treat "end" as the end of all nodes, instead * of the end of visible nodes. That way, we can search the * entire tree, even if the last folder is closed. */ firstPtr = hboxPtr->rootPtr;/* Default to root node */ lastPtr = EndNode(firstPtr, 0); if (i < argc) { if ((argv[i][0] == 'e') && (strcmp(argv[i], "end") == 0)) { firstPtr = EndNode(hboxPtr->rootPtr, 0); } else if (StringToNode(hboxPtr, argv[i], &firstPtr) != TCL_OK) { return TCL_ERROR; } i++; } if (i < argc) { if ((argv[i][0] == 'e') && (strcmp(argv[i], "end") == 0)) { lastPtr = EndNode(hboxPtr->rootPtr, 0); } else if (StringToNode(hboxPtr, argv[i], &lastPtr) != TCL_OK) { return TCL_ERROR; } } if (IsBefore(lastPtr, firstPtr)) { nextProc = LastNode; } nMatches = 0; /* * Step 3: Search through the tree and look for nodes that match the * current pattern specifications. Save the name of each of * the matching nodes. */ Tcl_DStringInit(&dString); for (treePtr = firstPtr; treePtr != NULL; treePtr = (*nextProc) (treePtr, 0)) { if (namePattern != NULL) { result = (*compareProc) (interp, treePtr->nameId, namePattern); if (result == invertMatch) { goto nextNode; /* Failed to match */ } } if (fullPattern != NULL) { GetFullPath(treePtr, hboxPtr->separator, &pathString); result = (*compareProc) (interp, Tcl_DStringValue(&pathString), fullPattern); Tcl_DStringFree(&pathString); if (result == invertMatch) { goto nextNode; /* Failed to match */ } } for (node = Blt_ListFirstNode(optionList); node != NULL; node = Blt_ListNextNode(node)) { option = (char *)Blt_ListGetKey(node); Tcl_ResetResult(interp); if (Tk_ConfigureValue(interp, hboxPtr->tkwin, entryConfigSpecs, (char *)treePtr->entryPtr, option, 0) != TCL_OK) { goto error; /* This shouldn't happen. */ } pattern = (char *)Blt_ListGetValue(node); value = (char *)Tcl_GetStringResult(interp); result = (*compareProc) (interp, value, pattern); if (result == invertMatch) { goto nextNode; /* Failed to match */ } } /* * As unlikely as it sounds, the "exec" callback may delete this * node. We need to preverse it until we're not using it anymore * (i.e. no longer accessing its fields). */ Tcl_Preserve(treePtr); if (execCmd != NULL) { Tcl_DString cmdString; PercentSubst(hboxPtr, treePtr, execCmd, &cmdString); result = Tcl_GlobalEval(interp, Tcl_DStringValue(&cmdString)); Tcl_DStringFree(&cmdString); if (result != TCL_OK) { Tcl_Release(treePtr); goto error; } } /* Finally, save the matching node name. */ Tcl_DStringAppendElement(&dString, NodeToString(hboxPtr, treePtr)); Tcl_Release(treePtr); nMatches++; if ((nMatches == maxMatches) && (maxMatches > 0)) { break; } nextNode: if (treePtr == lastPtr) { break; } } Tcl_ResetResult(interp); Blt_ListDestroy(optionList); Tcl_DStringResult(interp, &dString); return TCL_OK; missingArg: Tcl_AppendResult(interp, "missing argument for find option \"", argv[i], "\"", (char *)NULL); error: Tcl_DStringFree(&dString); Blt_ListDestroy(optionList); return TCL_ERROR; } /* *---------------------------------------------------------------------- * * GetOp -- * * Converts one or more node identifiers to its path component. * The path may be either the single entry name or the full path * of the entry. * * Results: * A standard Tcl result. The interpreter result will contain a * list of the convert names. * *---------------------------------------------------------------------- */ static int GetOp(hboxPtr, interp, argc, argv) Hierbox *hboxPtr; Tcl_Interp *interp; int argc; char **argv; { int fullName; Tree *treePtr; Tcl_DString dString; Tcl_DString pathString; register int i; fullName = FALSE; if ((argc > 2) && (argv[2][0] == '-') && (strcmp(argv[2], "-full") == 0)) { fullName = TRUE; argv++, argc--; } Tcl_DStringInit(&dString); Tcl_DStringInit(&pathString); for (i = 2; i < argc; i++) { treePtr = hboxPtr->focusPtr; if (GetNode(hboxPtr, argv[i], &treePtr) != TCL_OK) { return TCL_ERROR; } if (treePtr == NULL) { Tcl_DStringAppendElement(&dString, ""); continue; } if (fullName) { GetFullPath(treePtr, hboxPtr->separator, &pathString); Tcl_DStringAppendElement(&dString, Tcl_DStringValue(&pathString)); } else { Tcl_DStringAppendElement(&dString, treePtr->nameId); } } Tcl_DStringFree(&pathString); Tcl_DStringResult(interp, &dString); return TCL_OK; } /* *---------------------------------------------------------------------- * * SearchAndApplyToTree -- * * Searches through the current tree and applies a procedure * to matching nodes. The search specification is taken from * the following command-line arguments: * * ?-exact? ?-glob? ?-regexp? ?-nonmatching? * ?-data string? * ?-name string? * ?-full string? * ?--? * ?inode...? * * Results: * A standard Tcl result. If the result is valid, and if the * nonmatchPtr is specified, it returns a boolean value * indicating whether or not the search was inverted. This * is needed to fix things properly for the "hide nonmatching" * case. * *---------------------------------------------------------------------- */ static int SearchAndApplyToTree(hboxPtr, interp, argc, argv, proc, nonMatchPtr) Hierbox *hboxPtr; Tcl_Interp *interp; int argc; char **argv; ApplyProc *proc; int *nonMatchPtr; /* returns: inverted search indicator */ { CompareProc *compareProc; int invertMatch; /* normal search mode (matching entries) */ char *namePattern, *fullPattern; register int i; int length; int result; char *option, *pattern; char *value; Tree *treePtr; char c; Blt_List optionList; register Blt_ListNode node; optionList = Blt_ListCreate(BLT_STRING_KEYS); invertMatch = FALSE; namePattern = fullPattern = NULL; compareProc = ExactCompare; for (i = 2; i < argc; i++) { if (argv[i][0] != '-') { break; } option = argv[i] + 1; length = strlen(option); c = option[0]; if ((c == 'e') && (strncmp(option, "exact", length) == 0)) { compareProc = ExactCompare; } else if ((c == 'g') && (strncmp(option, "glob", length) == 0)) { compareProc = GlobCompare; } else if ((c == 'r') && (strncmp(option, "regexp", length) == 0)) { compareProc = RegexpCompare; } else if ((c == 'n') && (length > 1) && (strncmp(option, "nonmatching", length) == 0)) { invertMatch = TRUE; } else if ((c == 'f') && (strncmp(option, "full", length) == 0)) { if ((i + 1) == argc) { goto missingArg; } i++; fullPattern = argv[i]; } else if ((c == 'n') && (length > 1) && (strncmp(option, "name", length) == 0)) { if ((i + 1) == argc) { goto missingArg; } i++; namePattern = argv[i]; } else if ((option[0] == '-') && (option[1] == '\0')) { break; } else { /* * Verify that the switch is actually an entry configuration option. */ if (Tk_ConfigureValue(interp, hboxPtr->tkwin, entryConfigSpecs, (char *)hboxPtr->rootPtr->entryPtr, argv[i], 0) != TCL_OK) { Tcl_ResetResult(interp); Tcl_AppendResult(interp, "bad switch \"", argv[i], "\": must be -exact, -glob, -regexp, -name, -full, or -nonmatching", (char *)NULL); return TCL_ERROR; } if ((i + 1) == argc) { goto missingArg; } /* Save the option in the list of configuration options */ node = Blt_ListGetNode(optionList, argv[i]); if (node == NULL) { node = Blt_ListCreateNode(optionList, argv[i]); Blt_ListAppendNode(optionList, node); } Blt_ListSetValue(node, argv[i + 1]); } } if ((namePattern != NULL) || (fullPattern != NULL) || (Blt_ListGetLength(optionList) > 0)) { /* * Search through the tree and look for nodes that match the * current spec. Apply the input procedure to each of the * matching nodes. */ for (treePtr = hboxPtr->rootPtr; treePtr != NULL; treePtr = NextNode(treePtr, 0)) { if (namePattern != NULL) { result = (*compareProc) (interp, treePtr->nameId, namePattern); if (result == invertMatch) { continue; /* Failed to match */ } } if (fullPattern != NULL) { Tcl_DString dString; GetFullPath(treePtr, hboxPtr->separator, &dString); result = (*compareProc) (interp, Tcl_DStringValue(&dString), fullPattern); Tcl_DStringFree(&dString); if (result == invertMatch) { continue; /* Failed to match */ } } for (node = Blt_ListFirstNode(optionList); node != NULL; node = Blt_ListNextNode(node)) { option = (char *)Blt_ListGetKey(node); Tcl_ResetResult(interp); if (Tk_ConfigureValue(interp, hboxPtr->tkwin, entryConfigSpecs, (char *)treePtr->entryPtr, option, 0) != TCL_OK) { return TCL_ERROR; /* This shouldn't happen. */ } pattern = (char *)Blt_ListGetValue(node); value = (char *)Tcl_GetStringResult(interp); result = (*compareProc) (interp, value, pattern); if (result == invertMatch) { continue; /* Failed to match */ } } /* Finally, apply the procedure to the node */ (*proc) (hboxPtr, treePtr); } Tcl_ResetResult(interp); Blt_ListDestroy(optionList); } /* * Apply the procedure to nodes that have been specified * individually. */ for ( /*empty*/ ; i < argc; i++) { if ((argv[i][0] == 'a') && (strcmp(argv[i], "all") == 0)) { return ApplyToTree(hboxPtr, hboxPtr->rootPtr, proc, APPLY_RECURSE); } if (StringToNode(hboxPtr, argv[i], &treePtr) != TCL_OK) { return TCL_ERROR; } if ((*proc) (hboxPtr, treePtr) != TCL_OK) { return TCL_ERROR; } } if (nonMatchPtr != NULL) { *nonMatchPtr = invertMatch; /* return "inverted search" status */ } return TCL_OK; missingArg: Blt_ListDestroy(optionList); Tcl_AppendResult(interp, "missing pattern for search option \"", argv[i], "\"", (char *)NULL); return TCL_ERROR; } /* *---------------------------------------------------------------------- * * HideOp -- * * Hides one or more nodes. Nodes can be specified by their * inode, or by matching a name or data value pattern. By * default, the patterns are matched exactly. They can also * be matched using glob-style and regular expression rules. * * Results: * A standard Tcl result. * *---------------------------------------------------------------------- */ static int HideOp(hboxPtr, interp, argc, argv) Hierbox *hboxPtr; Tcl_Interp *interp; int argc; char **argv; { int status, nonmatching; status = SearchAndApplyToTree(hboxPtr, interp, argc, argv, UnmapNode, &nonmatching); if (status != TCL_OK) { return TCL_ERROR; } /* * If this was an inverted search, scan back through the * tree and make sure that the parents for all visible * nodes are also visible. After all, if a node is supposed * to be visible, its parent can't be hidden. */ if (nonmatching) { ApplyToTree(hboxPtr, hboxPtr->rootPtr, MapAncestors, APPLY_RECURSE); } /* * Make sure that selections arme cleared from any hidden * nodes. This wasn't done earlier--we had to delay it until * we fixed the visibility status for the parents. */ ApplyToTree(hboxPtr, hboxPtr->rootPtr, FixUnmappedSelections, APPLY_RECURSE); hboxPtr->flags |= (HIERBOX_LAYOUT | HIERBOX_SCROLL); EventuallyRedraw(hboxPtr); return TCL_OK; } /* *---------------------------------------------------------------------- * * ShowOp -- * * Mark one or more nodes to be exposed. Nodes can be specified * by their inode, or by matching a name or data value pattern. By * default, the patterns are matched exactly. They can also * be matched using glob-style and regular expression rules. * * Results: * A standard Tcl result. * *---------------------------------------------------------------------- */ static int ShowOp(hboxPtr, interp, argc, argv) Hierbox *hboxPtr; Tcl_Interp *interp; int argc; char **argv; { if (SearchAndApplyToTree(hboxPtr, interp, argc, argv, MapNode, (int *)NULL) != TCL_OK) { return TCL_ERROR; } hboxPtr->flags |= (HIERBOX_LAYOUT | HIERBOX_SCROLL); EventuallyRedraw(hboxPtr); return TCL_OK; } /* *---------------------------------------------------------------------- * * IndexOp -- * * Converts one of more words representing indices of the entries * in the hierarchy widget to their respective serial identifiers. * * Results: * A standard Tcl result. Interp->result will contain the * identifier of each inode found. If an inode could not be found, * then the serial identifier will be the empty string. * *---------------------------------------------------------------------- */ /*ARGSUSED*/ static int IndexOp(hboxPtr, interp, argc, argv) Hierbox *hboxPtr; Tcl_Interp *interp; int argc; /* Not used. */ char **argv; { Tree *nodePtr, *rootPtr; rootPtr = hboxPtr->focusPtr; if ((argv[2][0] == '-') && (strcmp(argv[2], "-at") == 0)) { if (StringToNode(hboxPtr, argv[3], &rootPtr) != TCL_OK) { return TCL_ERROR; } argv += 2, argc -= 2; } if (argc > 3) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " index ?-at index? index\"", (char *)NULL); return TCL_ERROR; } nodePtr = rootPtr; if ((GetNode(hboxPtr, argv[2], &nodePtr) == TCL_OK) && (nodePtr != NULL)) { Tcl_SetResult(interp, NodeToString(hboxPtr, nodePtr), TCL_VOLATILE); } else { Tcl_SetResult(interp, "", TCL_STATIC); } return TCL_OK; } /* *---------------------------------------------------------------------- * * InsertOp -- * * Add new entries into a hierarchy. If no node is specified, * new entries will be added to the root of the hierarchy. * *---------------------------------------------------------------------- */ /*ARGSUSED*/ static int InsertOp(hboxPtr, interp, argc, argv) Hierbox *hboxPtr; Tcl_Interp *interp; int argc; /* Not used. */ char **argv; { Tree *rootPtr, *nodePtr, *parentPtr; int position; int level, count; char *path; Tcl_DString dString; register int i, l; int nOpts; char **options; char **nameArr; rootPtr = hboxPtr->rootPtr; if ((argv[2][0] == '-') && (strcmp(argv[2], "-at") == 0)) { if (StringToNode(hboxPtr, argv[3], &rootPtr) != TCL_OK) { return TCL_ERROR; } argv += 2, argc -= 2; } if (Blt_GetPosition(hboxPtr->interp, argv[2], &position) != TCL_OK) { return TCL_ERROR; } argc -= 3, argv += 3; /* * Count the pathnames that follow. Count the arguments until we * spot one that looks like a configuration option (i.e. starts * with a minus ("-")). */ for (count = 0; count < argc; count++) { if (argv[count][0] == '-') { break; } } nOpts = argc - count; options = argv + count; Tcl_DStringInit(&dString); for (i = 0; i < count; i++) { path = argv[i]; if (hboxPtr->trimLeft != NULL) { register char *p, *s; /* Trim off leading character string if one exists. */ for (p = path, s = hboxPtr->trimLeft; *s != '\0'; s++, p++) { if (*p != *s) { break; } } if (*s == '\0') { path = p; } } /* * Split the path and find the parent node of the path. */ nameArr = &path; level = 1; if (hboxPtr->separator == SEPARATOR_LIST) { if (Tcl_SplitList(interp, path, &level, &nameArr) != TCL_OK) { goto error; } } else if (hboxPtr->separator != SEPARATOR_NONE) { if (SplitPath(hboxPtr, path, &level, &nameArr) != TCL_OK) { goto error; } } if (level == 0) { continue; /* Root already exists. */ } parentPtr = rootPtr; level--; for (l = 0; l < level; l++) { nodePtr = FindComponent(parentPtr, nameArr[l]); if (nodePtr == NULL) { if (!hboxPtr->autoCreate) { Tcl_AppendResult(interp, "can't find path component \"", nameArr[l], "\" in \"", path, "\"", (char *)NULL); goto error; } nodePtr = CreateNode(hboxPtr, parentPtr, APPEND, nameArr[l]); } parentPtr = nodePtr; } nodePtr = NULL; if (!hboxPtr->allowDuplicates) { nodePtr = FindComponent(parentPtr, nameArr[level]); } if (nodePtr == NULL) { nodePtr = CreateNode(hboxPtr, parentPtr, position, nameArr[level]); if (nodePtr == NULL) { goto error; } if (ConfigureEntry(hboxPtr, nodePtr->entryPtr, nOpts, options, TK_CONFIG_ARGV_ONLY) != TCL_OK) { DeleteNode(hboxPtr, nodePtr); goto error; } Tcl_DStringAppendElement(&dString, NodeToString(hboxPtr, nodePtr)); } else { if (ConfigureEntry(hboxPtr, nodePtr->entryPtr, nOpts, options, 0) != TCL_OK) { goto error; } } if (nameArr != &path) { Blt_Free(nameArr); } } hboxPtr->flags |= (HIERBOX_LAYOUT | HIERBOX_SCROLL | HIERBOX_DIRTY); EventuallyRedraw(hboxPtr); Tcl_DStringResult(hboxPtr->interp, &dString); return TCL_OK; error: if (nameArr != &path) { Blt_Free(nameArr); } Tcl_DStringFree(&dString); return TCL_ERROR; } /* *---------------------------------------------------------------------- * * DeleteOp -- * * Deletes nodes from the hierarchy. Deletes either a range of * entries from a hierarchy or a single node (except root). * In all cases, nodes are removed recursively. * *---------------------------------------------------------------------- */ /*ARGSUSED*/ static int DeleteOp(hboxPtr, interp, argc, argv) Hierbox *hboxPtr; Tcl_Interp *interp; int argc; /* Not used. */ char **argv; { Tree *treePtr; Blt_ChainLink *linkPtr; Blt_ChainLink *firstPtr, *lastPtr, *nextPtr; if (argc == 2) { return TCL_OK; } if (StringToNode(hboxPtr, argv[2], &treePtr) != TCL_OK) { return TCL_ERROR; /* Node or path doesn't already exist */ } firstPtr = lastPtr = NULL; switch (argc) { case 3: /* * Delete a single hierarchy. If the node specified is root, * delete only the children. */ if (treePtr != hboxPtr->rootPtr) { DestroyTree(hboxPtr, treePtr); /* Don't delete root */ goto done; } firstPtr = Blt_ChainFirstLink(treePtr->chainPtr); lastPtr = Blt_ChainLastLink(treePtr->chainPtr); break; case 4: /* * Delete a single node from hierarchy specified by its * numeric position. */ { int position; if (Blt_GetPosition(interp, argv[3], &position) != TCL_OK) { return TCL_ERROR; } if (position >= Blt_ChainGetLength(treePtr->chainPtr)) { return TCL_OK; /* Bad first index */ } if (position == APPEND) { linkPtr = Blt_ChainLastLink(treePtr->chainPtr); } else { linkPtr = Blt_ChainGetNthLink(treePtr->chainPtr, position); } firstPtr = lastPtr = linkPtr; } break; case 5: /* * Delete range of nodes in hierarchy specified by first/last * positions. */ { int first, last; int nEntries; if ((Blt_GetPosition(interp, argv[3], &first) != TCL_OK) || (Blt_GetPosition(interp, argv[4], &last) != TCL_OK)) { return TCL_ERROR; } nEntries = Blt_ChainGetLength(treePtr->chainPtr); if (nEntries == 0) { return TCL_OK; } if (first == APPEND) { first = nEntries - 1; } if (first >= nEntries) { Tcl_AppendResult(interp, "first position \"", argv[3], " is out of range", (char *)NULL); return TCL_ERROR; } if ((last == APPEND) || (last >= nEntries)) { last = nEntries - 1; } if (first > last) { Tcl_AppendResult(interp, "bad range: \"", argv[3], " > ", argv[4], "\"", (char *)NULL); return TCL_ERROR; } firstPtr = Blt_ChainGetNthLink(treePtr->chainPtr, first); lastPtr = Blt_ChainGetNthLink(treePtr->chainPtr, last); } break; } for (linkPtr = firstPtr; linkPtr != NULL; linkPtr = nextPtr) { nextPtr = Blt_ChainNextLink(linkPtr); treePtr = Blt_ChainGetValue(linkPtr); DestroyTree(hboxPtr, treePtr); if (linkPtr == lastPtr) { break; } } done: hboxPtr->flags |= (HIERBOX_LAYOUT | HIERBOX_SCROLL); EventuallyRedraw(hboxPtr); return TCL_OK; } /* *---------------------------------------------------------------------- * * MoveOp -- * * Move an entry into a new location in the hierarchy. * * *---------------------------------------------------------------------- */ /*ARGSUSED*/ static int MoveOp(hboxPtr, interp, argc, argv) Hierbox *hboxPtr; Tcl_Interp *interp; int argc; /* Not used. */ char **argv; { Tree *srcPtr, *destPtr, *parentPtr; char c; int action; #define MOVE_INTO (1<<0) #define MOVE_BEFORE (1<<1) #define MOVE_AFTER (1<<2) if (StringToNode(hboxPtr, argv[2], &srcPtr) != TCL_OK) { return TCL_ERROR; } c = argv[3][0]; if ((c == 'i') && (strcmp(argv[3], "into") == 0)) { action = MOVE_INTO; } else if ((c == 'b') && (strcmp(argv[3], "before") == 0)) { action = MOVE_BEFORE; } else if ((c == 'a') && (strcmp(argv[3], "after") == 0)) { action = MOVE_AFTER; } else { Tcl_AppendResult(interp, "bad position \"", argv[3], "\": should be into, before, or after", (char *)NULL); return TCL_ERROR; } if (StringToNode(hboxPtr, argv[4], &destPtr) != TCL_OK) { return TCL_ERROR; } /* Verify they aren't ancestors. */ if (IsAncestor(srcPtr, destPtr)) { Tcl_AppendResult(interp, "can't move node: \"", argv[2], "\" is an ancestor of \"", argv[4], "\"", (char *)NULL); return TCL_ERROR; } parentPtr = destPtr->parentPtr; if (parentPtr == NULL) { action = MOVE_INTO; } Blt_ChainUnlinkLink(srcPtr->parentPtr->chainPtr, srcPtr->linkPtr); switch (action) { case MOVE_INTO: Blt_ChainLinkBefore(destPtr->chainPtr, srcPtr->linkPtr, (Blt_ChainLink *) NULL); parentPtr = destPtr; break; case MOVE_BEFORE: Blt_ChainLinkBefore(parentPtr->chainPtr, srcPtr->linkPtr, destPtr->linkPtr); break; case MOVE_AFTER: Blt_ChainLinkAfter(parentPtr->chainPtr, srcPtr->linkPtr, destPtr->linkPtr); break; } srcPtr->parentPtr = parentPtr; srcPtr->level = parentPtr->level + 1; hboxPtr->flags |= (HIERBOX_LAYOUT | HIERBOX_SCROLL | HIERBOX_DIRTY); EventuallyRedraw(hboxPtr); return TCL_OK; } /*ARGSUSED*/ static int NearestOp(hboxPtr, interp, argc, argv) Hierbox *hboxPtr; Tcl_Interp *interp; int argc; /* Not used. */ char **argv; { ButtonAttributes *buttonPtr = &(hboxPtr->button); int x, y; /* Screen coordinates of the test point. */ register Entry *entryPtr; register Tree *treePtr; if ((Tk_GetPixels(interp, hboxPtr->tkwin, argv[2], &x) != TCL_OK) || (Tk_GetPixels(interp, hboxPtr->tkwin, argv[3], &y) != TCL_OK)) { return TCL_ERROR; } if (hboxPtr->nVisible == 0) { return TCL_OK; } treePtr = NearestNode(hboxPtr, x, y, TRUE); if (treePtr == NULL) { return TCL_OK; } x = WORLDX(hboxPtr, x); y = WORLDY(hboxPtr, y); entryPtr = treePtr->entryPtr; if (argc > 4) { char *where; int labelX; where = ""; if (entryPtr->flags & ENTRY_BUTTON) { int buttonX, buttonY; buttonX = entryPtr->worldX + entryPtr->buttonX; buttonY = entryPtr->worldY + entryPtr->buttonY; if ((x >= buttonX) && (x < (buttonX + buttonPtr->width)) && (y >= buttonY) && (y < (buttonY + buttonPtr->height))) { where = "gadget"; } } labelX = entryPtr->worldX + LEVELWIDTH(treePtr->level); if ((x >= labelX) && (x < (labelX + LEVELWIDTH(treePtr->level + 1) + entryPtr->width))) { where = "select"; } if (Tcl_SetVar(interp, argv[4], where, TCL_LEAVE_ERR_MSG) == NULL) { return TCL_ERROR; } } Tcl_SetResult(interp, NodeToString(hboxPtr, treePtr), TCL_VOLATILE); return TCL_OK; } /*ARGSUSED*/ static int OpenOp(hboxPtr, interp, argc, argv) Hierbox *hboxPtr; Tcl_Interp *interp; /* Not used. */ int argc; char **argv; { Tree *rootPtr; unsigned int flags; register int i; flags = 0; if (argc > 2) { int length; length = strlen(argv[2]); if ((argv[2][0] == '-') && (length > 1) && (strncmp(argv[2], "-recurse", length) == 0)) { argv++, argc--; flags |= APPLY_RECURSE; } } for (i = 2; i < argc; i++) { rootPtr = hboxPtr->focusPtr; if (GetNode(hboxPtr, argv[i], &rootPtr) != TCL_OK) { return TCL_ERROR; } if (rootPtr == NULL) { continue; } ExposeAncestors(rootPtr); /* Also make sure that all the ancestors * of this node are also not hidden. */ if (ApplyToTree(hboxPtr, rootPtr, OpenNode, flags) != TCL_OK) { return TCL_ERROR; } } hboxPtr->flags |= (HIERBOX_LAYOUT | HIERBOX_SCROLL); EventuallyRedraw(hboxPtr); return TCL_OK; } /* *---------------------------------------------------------------------- * * RangeOp -- * * Returns the node identifiers in a given range. * *---------------------------------------------------------------------- */ static int RangeOp(hboxPtr, interp, argc, argv) Hierbox *hboxPtr; Tcl_Interp *interp; int argc; char **argv; { Tree *firstPtr, *lastPtr; register Tree *treePtr; unsigned int flags; int length; flags = 0; length = strlen(argv[2]); if ((argv[2][0] == '-') && (length > 1) && (strncmp(argv[2], "-open", length) == 0)) { argv++, argc--; flags |= ENTRY_OPEN; } if (StringToNode(hboxPtr, argv[2], &firstPtr) != TCL_OK) { return TCL_ERROR; } lastPtr = EndNode(firstPtr, flags); if (argc > 3) { if (StringToNode(hboxPtr, argv[3], &lastPtr) != TCL_OK) { return TCL_ERROR; } } if (flags & ENTRY_OPEN) { if (IsHidden(firstPtr)) { Tcl_AppendResult(interp, "first node \"", argv[2], "\" is hidden.", (char *)NULL); return TCL_ERROR; } if (IsHidden(lastPtr)) { Tcl_AppendResult(interp, "last node \"", argv[3], "\" is hidden.", (char *)NULL); return TCL_ERROR; } } /* * The relative order of the first/last markers determines the * direction. */ if (IsBefore(lastPtr, firstPtr)) { for (treePtr = lastPtr; treePtr != NULL; treePtr = LastNode(treePtr, flags)) { Tcl_AppendElement(interp, NodeToString(hboxPtr, treePtr)); if (treePtr == firstPtr) { break; } } } else { for (treePtr = firstPtr; treePtr != NULL; treePtr = NextNode(treePtr, flags)) { Tcl_AppendElement(interp, NodeToString(hboxPtr, treePtr)); if (treePtr == lastPtr) { break; } } } return TCL_OK; } /* *---------------------------------------------------------------------- * * ScanOp -- * * Implements the quick scan. * *---------------------------------------------------------------------- */ /*ARGSUSED*/ static int ScanOp(hboxPtr, interp, argc, argv) Hierbox *hboxPtr; Tcl_Interp *interp; int argc; /* Not used. */ char **argv; { int x, y; char c; unsigned int length; int oper; #define SCAN_MARK 1 #define SCAN_DRAGTO 2 c = argv[2][0]; length = strlen(argv[2]); if ((c == 'm') && (strncmp(argv[2], "mark", length) == 0)) { oper = SCAN_MARK; } else if ((c == 'd') && (strncmp(argv[2], "dragto", length) == 0)) { oper = SCAN_DRAGTO; } else { Tcl_AppendResult(interp, "bad scan operation \"", argv[2], "\": should be either \"mark\" or \"dragto\"", (char *)NULL); return TCL_ERROR; } if ((Tk_GetPixels(interp, hboxPtr->tkwin, argv[3], &x) != TCL_OK) || (Tk_GetPixels(interp, hboxPtr->tkwin, argv[4], &y) != TCL_OK)) { return TCL_ERROR; } if (oper == SCAN_MARK) { hboxPtr->scanAnchorX = x; hboxPtr->scanAnchorY = y; hboxPtr->scanX = hboxPtr->xOffset; hboxPtr->scanY = hboxPtr->yOffset; } else { int worldX, worldY; int dx, dy; dx = hboxPtr->scanAnchorX - x; dy = hboxPtr->scanAnchorY - y; worldX = hboxPtr->scanX + (10 * dx); worldY = hboxPtr->scanY + (10 * dy); if (worldX < 0) { worldX = 0; } else if (worldX >= hboxPtr->worldWidth) { worldX = hboxPtr->worldWidth - hboxPtr->xScrollUnits; } if (worldY < 0) { worldY = 0; } else if (worldY >= hboxPtr->worldHeight) { worldY = hboxPtr->worldHeight - hboxPtr->yScrollUnits; } hboxPtr->xOffset = worldX; hboxPtr->yOffset = worldY; hboxPtr->flags |= (HIERBOX_LAYOUT | HIERBOX_SCROLL); EventuallyRedraw(hboxPtr); } return TCL_OK; } /*ARGSUSED*/ static int SeeOp(hboxPtr, interp, argc, argv) Hierbox *hboxPtr; Tcl_Interp *interp; /* Not used. */ int argc; char **argv; { Entry *entryPtr; int width, height; int x, y; Tree *treePtr; Tk_Anchor anchor; int left, right, top, bottom; anchor = TK_ANCHOR_W; /* Default anchor is West */ if ((argv[2][0] == '-') && (strcmp(argv[2], "-anchor") == 0)) { if (argc == 3) { Tcl_AppendResult(interp, "missing \"-anchor\" argument", (char *)NULL); return TCL_ERROR; } if (Tk_GetAnchor(interp, argv[3], &anchor) != TCL_OK) { return TCL_ERROR; } argc -= 2, argv += 2; } if (argc == 2) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], "see ?-anchor anchor? index\"", (char *)NULL); return TCL_ERROR; } treePtr = hboxPtr->focusPtr; if (GetNode(hboxPtr, argv[2], &treePtr) != TCL_OK) { return TCL_ERROR; } if (treePtr == NULL) { return TCL_OK; } if (IsHidden(treePtr)) { ExposeAncestors(treePtr); hboxPtr->flags |= (HIERBOX_LAYOUT | HIERBOX_SCROLL); /* * If the entry wasn't previously exposed, its world coordinates * aren't likely to be valid. So re-compute the layout before * we try to see the viewport to the entry's location. */ ComputeLayout(hboxPtr); } entryPtr = treePtr->entryPtr; width = VPORTWIDTH(hboxPtr); height = VPORTHEIGHT(hboxPtr); /* * XVIEW: If the entry is left or right of the current view, adjust * the offset. If the entry is nearby, adjust the view just * a bit. Otherwise, center the entry. */ left = hboxPtr->xOffset; right = hboxPtr->xOffset + width; switch (anchor) { case TK_ANCHOR_W: case TK_ANCHOR_NW: case TK_ANCHOR_SW: x = 0; break; case TK_ANCHOR_E: case TK_ANCHOR_NE: case TK_ANCHOR_SE: x = entryPtr->worldX + entryPtr->width + LEVELWIDTH(treePtr->level) - width; break; default: if (entryPtr->worldX < left) { x = entryPtr->worldX; } else if ((entryPtr->worldX + entryPtr->width) > right) { x = entryPtr->worldX + entryPtr->width - width; } else { x = hboxPtr->xOffset; } break; } /* * YVIEW: If the entry is above or below the current view, adjust * the offset. If the entry is nearby, adjust the view just * a bit. Otherwise, center the entry. */ top = hboxPtr->yOffset; bottom = hboxPtr->yOffset + height; switch (anchor) { case TK_ANCHOR_N: y = hboxPtr->yOffset; break; case TK_ANCHOR_NE: case TK_ANCHOR_NW: y = entryPtr->worldY - (height / 2); break; case TK_ANCHOR_S: case TK_ANCHOR_SE: case TK_ANCHOR_SW: y = entryPtr->worldY + entryPtr->height - height; break; default: if (entryPtr->worldY < top) { y = entryPtr->worldY; } else if ((entryPtr->worldY + entryPtr->height) > bottom) { y = entryPtr->worldY + entryPtr->height - height; } else { y = hboxPtr->yOffset; } break; } if ((y != hboxPtr->yOffset) || (x != hboxPtr->xOffset)) { hboxPtr->xOffset = x; hboxPtr->yOffset = y; hboxPtr->flags |= (HIERBOX_SCROLL | HIERBOX_LAYOUT); } EventuallyRedraw(hboxPtr); return TCL_OK; } /* *---------------------------------------------------------------------- * * AnchorOpOp -- * * Sets the selection anchor to the element given by a index. * The selection anchor is the end of the selection that is fixed * while dragging out a selection with the mouse. The index * "anchor" may be used to refer to the anchor element. * * Results: * None. * * Side effects: * The selection changes. * *---------------------------------------------------------------------- */ /*ARGSUSED*/ static int AnchorOpOp(hboxPtr, interp, argc, argv) Hierbox *hboxPtr; Tcl_Interp *interp; /* Not used. */ int argc; /* Not used. */ char **argv; { Tree *nodePtr; nodePtr = hboxPtr->focusPtr; if (GetNode(hboxPtr, argv[3], &nodePtr) != TCL_OK) { return TCL_ERROR; } hboxPtr->selAnchorPtr = nodePtr; if (nodePtr != NULL) { Tcl_SetResult(interp, NodeToString(hboxPtr, nodePtr), TCL_VOLATILE); } EventuallyRedraw(hboxPtr); return TCL_OK; } /* *---------------------------------------------------------------------- * * ClearallOpOp * * Clears the entire selection. * * Results: * None. * * Side effects: * The selection changes. * *---------------------------------------------------------------------- */ /*ARGSUSED*/ static int ClearallOpOp(hboxPtr, interp, argc, argv) Hierbox *hboxPtr; Tcl_Interp *interp; int argc; /* Not used. */ char **argv; { ClearSelection(hboxPtr); return TCL_OK; } /* *---------------------------------------------------------------------- * * IncludesOpOp * * Returns 1 if the element indicated by index is currently * selected, 0 if it isn't. * * Results: * None. * * Side effects: * The selection changes. * *---------------------------------------------------------------------- */ /*ARGSUSED*/ static int IncludesOpOp(hboxPtr, interp, argc, argv) Hierbox *hboxPtr; Tcl_Interp *interp; int argc; /* Not used. */ char **argv; { Tree *treePtr; treePtr = hboxPtr->focusPtr; if (GetNode(hboxPtr, argv[3], &treePtr) != TCL_OK) { return TCL_ERROR; } if (treePtr != NULL) { int bool; bool = IsSelected(hboxPtr, treePtr); Blt_SetBooleanResult(interp, bool); } return TCL_OK; } /*---------------------------------------------------------------------- * * MarkOpOp -- * * Sets the selection mark to the element given by a index. The * selection mark is the end of the selection that is not fixed * while dragging out a selection with the mouse. The index * "mark" may be used to refer to the anchor element. * * Results: * None. * * Side effects: * The selection changes. * *---------------------------------------------------------------------- */ /*ARGSUSED*/ static int MarkOpOp(hboxPtr, interp, argc, argv) Hierbox *hboxPtr; Tcl_Interp *interp; /* Not used. */ int argc; /* Not used. */ char **argv; { Tree *nodePtr; Blt_ChainLink *linkPtr, *nextPtr; Tree *selectPtr; nodePtr = hboxPtr->focusPtr; if (GetNode(hboxPtr, argv[3], &nodePtr) != TCL_OK) { return TCL_ERROR; } if (hboxPtr->selAnchorPtr == NULL) { Tcl_AppendResult(interp, "selection anchor must be set first", (char *)NULL); return TCL_ERROR; } /* Deselect entry from the list all the way back to the anchor. */ for (linkPtr = Blt_ChainLastLink(&(hboxPtr->selectChain)); linkPtr != NULL; linkPtr = nextPtr) { nextPtr = Blt_ChainPrevLink(linkPtr); selectPtr = Blt_ChainGetValue(linkPtr); if (selectPtr == hboxPtr->selAnchorPtr) { break; } DeselectEntry(hboxPtr, selectPtr); } if (nodePtr != NULL) { hboxPtr->flags &= ~SELECTION_MASK; hboxPtr->flags |= SELECTION_SET; SelectRange(hboxPtr, hboxPtr->selAnchorPtr, nodePtr); hboxPtr->flags &= ~SELECTION_MASK; Tcl_SetResult(interp, NodeToString(hboxPtr, nodePtr), TCL_VOLATILE); } EventuallyRedraw(hboxPtr); if (hboxPtr->selectCmd != NULL) { EventuallyInvokeSelectCmd(hboxPtr); } return TCL_OK; } /* *---------------------------------------------------------------------- * * PresentOpOp * * Indicates if there is a selection present. * * Results: * A standard Tcl result. * *---------------------------------------------------------------------- */ /*ARGSUSED*/ static int PresentOpOp(hboxPtr, interp, argc, argv) Hierbox *hboxPtr; Tcl_Interp *interp; int argc; /* Not used. */ char **argv; { int bool; bool = (Blt_ChainGetLength(&(hboxPtr->selectChain)) > 0); Blt_SetBooleanResult(interp, bool); return TCL_OK; } /* *---------------------------------------------------------------------- * * SelectOpOp * * Selects, deselects, or toggles all of the elements in the * range between first and last, inclusive, without affecting the * selection state of elements outside that range. * * Results: * None. * * Side effects: * The selection changes. * *---------------------------------------------------------------------- */ /*ARGSUSED*/ static int SelectOpOp(hboxPtr, interp, argc, argv) Hierbox *hboxPtr; Tcl_Interp *interp; int argc; /* Not used. */ char **argv; { Tree *firstPtr, *lastPtr; hboxPtr->flags &= ~SELECTION_MASK; switch (argv[2][0]) { case 's': hboxPtr->flags |= SELECTION_SET; break; case 'c': hboxPtr->flags |= SELECTION_CLEAR; break; case 't': hboxPtr->flags |= SELECTION_TOGGLE; break; } if (StringToNode(hboxPtr, argv[3], &firstPtr) != TCL_OK) { return TCL_ERROR; } if ((IsHidden(firstPtr)) && !(hboxPtr->flags & SELECTION_CLEAR)) { Tcl_AppendResult(interp, "can't select hidden node \"", argv[3], "\"", (char *)NULL); return TCL_ERROR; } lastPtr = firstPtr; if (argc > 4) { if (StringToNode(hboxPtr, argv[4], &lastPtr) != TCL_OK) { return TCL_ERROR; } if ((IsHidden(lastPtr)) && !(hboxPtr->flags & SELECTION_CLEAR)) { Tcl_AppendResult(interp, "can't select hidden node \"", argv[4], "\"", (char *)NULL); return TCL_ERROR; } } if (firstPtr == lastPtr) { SelectNode(hboxPtr, firstPtr); } else { SelectRange(hboxPtr, firstPtr, lastPtr); } hboxPtr->flags &= ~SELECTION_MASK; if (hboxPtr->flags & SELECTION_EXPORT) { Tk_OwnSelection(hboxPtr->tkwin, XA_PRIMARY, LostSelection, hboxPtr); } EventuallyRedraw(hboxPtr); if (hboxPtr->selectCmd != NULL) { EventuallyInvokeSelectCmd(hboxPtr); } return TCL_OK; } /* *---------------------------------------------------------------------- * * SelectionOp -- * * This procedure handles the individual options for text * selections. The selected text is designated by start and end * indices into the text pool. The selected segment has both a * anchored and unanchored ends. * * Results: * None. * * Side effects: * The selection changes. * *---------------------------------------------------------------------- */ static Blt_OpSpec selectionOperSpecs[] = { {"anchor", 1, (Blt_Op)AnchorOpOp, 4, 4, "index",}, {"clear", 5, (Blt_Op)SelectOpOp, 4, 5, "firstIndex ?lastIndex?",}, {"clearall", 6, (Blt_Op)ClearallOpOp, 3, 3, "",}, {"includes", 2, (Blt_Op)IncludesOpOp, 4, 4, "index",}, {"mark", 1, (Blt_Op)MarkOpOp, 4, 4, "index",}, {"present", 1, (Blt_Op)PresentOpOp, 3, 3, "",}, {"set", 1, (Blt_Op)SelectOpOp, 4, 5, "firstIndex ?lastIndex?",}, {"toggle", 1, (Blt_Op)SelectOpOp, 4, 5, "firstIndex ?lastIndex?",}, }; static int nSelectionSpecs = sizeof(selectionOperSpecs) / sizeof(Blt_OpSpec); static int SelectionOp(hboxPtr, interp, argc, argv) Hierbox *hboxPtr; Tcl_Interp *interp; int argc; char **argv; { Blt_Op proc; int result; proc = Blt_GetOp(interp, nSelectionSpecs, selectionOperSpecs, BLT_OP_ARG2, argc, argv, 0); if (proc == NULL) { return TCL_ERROR; } result = (*proc) (hboxPtr, interp, argc, argv); return result; } /*ARGSUSED*/ static int SortOp(hboxPtr, interp, argc, argv) Hierbox *hboxPtr; Tcl_Interp *interp; /* Not used. */ int argc; char **argv; { int length; Tree *rootPtr; register int i; unsigned int flags; flags = 0; hboxPtr->sortCmd = NULL; for (i = 2; i < argc; i++) { if (argv[i][0] != '-') { break; /* Found start of indices */ } length = strlen(argv[i]); if ((length > 1) && (strncmp(argv[i], "-recurse", length) == 0)) { flags |= APPLY_RECURSE; } else if ((length > 1) && (strncmp(argv[i], "-command", length) ==0)) { if ((i + 1) == argc) { Tcl_AppendResult(interp, "\"-command\" must be", " followed by comparison command", (char *)NULL); return TCL_ERROR; } i++; hboxPtr->sortCmd = argv[i]; } else if ((argv[i][1] == '-') && (argv[i][2] == '\0')) { break; /* Allow first index to start with a '-' */ } else { Tcl_AppendResult(interp, "bad switch \"", argv[i], "\": must be -command or -recurse", (char *)NULL); return TCL_ERROR; } } for ( /*empty*/ ; i < argc; i++) { if (StringToNode(hboxPtr, argv[i], &rootPtr) != TCL_OK) { return TCL_ERROR; } if (ApplyToTree(hboxPtr, rootPtr, SortNode, flags) != TCL_OK) { return TCL_ERROR; } } hboxPtr->flags |= HIERBOX_LAYOUT; EventuallyRedraw(hboxPtr); return TCL_OK; } /*ARGSUSED*/ static int ToggleOp(hboxPtr, interp, argc, argv) Hierbox *hboxPtr; Tcl_Interp *interp; /* Not used. */ int argc; char **argv; { Tree *rootPtr; int result; rootPtr = hboxPtr->focusPtr; if (GetNode(hboxPtr, argv[2], &rootPtr) != TCL_OK) { return TCL_ERROR; } if (rootPtr == NULL) { return TCL_OK; } if (rootPtr->entryPtr->flags & ENTRY_OPEN) { PruneSelection(hboxPtr, rootPtr); if (IsAncestor(rootPtr, hboxPtr->focusPtr)) { hboxPtr->focusPtr = rootPtr; Blt_SetFocusItem(hboxPtr->bindTable, hboxPtr->focusPtr, NULL); } if (IsAncestor(rootPtr, hboxPtr->selAnchorPtr)) { hboxPtr->selAnchorPtr = NULL; } result = CloseNode(hboxPtr, rootPtr); } else { result = OpenNode(hboxPtr, rootPtr); } if (result != TCL_OK) { return TCL_ERROR; } hboxPtr->flags |= (HIERBOX_LAYOUT | HIERBOX_SCROLL); EventuallyRedraw(hboxPtr); return TCL_OK; } static int XViewOp(hboxPtr, interp, argc, argv) Hierbox *hboxPtr; Tcl_Interp *interp; int argc; char **argv; { int width, worldWidth; width = VPORTWIDTH(hboxPtr); worldWidth = hboxPtr->worldWidth; if (argc == 2) { double fract; /* * Note that we are bounding the fractions between 0.0 and 1.0 * to support the "canvas"-style of scrolling. */ fract = (double)hboxPtr->xOffset / worldWidth; Tcl_AppendElement(interp, Blt_Dtoa(interp, CLAMP(fract, 0.0, 1.0))); fract = (double)(hboxPtr->xOffset + width) / worldWidth; Tcl_AppendElement(interp, Blt_Dtoa(interp, CLAMP(fract, 0.0, 1.0))); return TCL_OK; } if (Blt_GetScrollInfo(interp, argc - 2, argv + 2, &(hboxPtr->xOffset), worldWidth, width, hboxPtr->xScrollUnits, hboxPtr->scrollMode) != TCL_OK) { return TCL_ERROR; } hboxPtr->flags |= HIERBOX_XSCROLL; EventuallyRedraw(hboxPtr); return TCL_OK; } static int YViewOp(hboxPtr, interp, argc, argv) Hierbox *hboxPtr; Tcl_Interp *interp; int argc; char **argv; { int height, worldHeight; height = VPORTHEIGHT(hboxPtr); worldHeight = hboxPtr->worldHeight; if (argc == 2) { double fract; /* Report first and last fractions */ fract = (double)hboxPtr->yOffset / worldHeight; Tcl_AppendElement(interp, Blt_Dtoa(interp, CLAMP(fract, 0.0, 1.0))); fract = (double)(hboxPtr->yOffset + height) / worldHeight; Tcl_AppendElement(interp, Blt_Dtoa(interp, CLAMP(fract, 0.0, 1.0))); return TCL_OK; } if (Blt_GetScrollInfo(interp, argc - 2, argv + 2, &(hboxPtr->yOffset), worldHeight, height, hboxPtr->yScrollUnits, hboxPtr->scrollMode) != TCL_OK) { return TCL_ERROR; } hboxPtr->flags |= HIERBOX_SCROLL; EventuallyRedraw(hboxPtr); return TCL_OK; } /* * -------------------------------------------------------------- * * HierboxInstCmd -- * * This procedure is invoked to process the "hierbox" command. * See the user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * * -------------------------------------------------------------- */ static Blt_OpSpec operSpecs[] = { {"bbox", 2, (Blt_Op)BboxOp, 2, 0, "index...",}, {"bind", 2, (Blt_Op)BindOp, 3, 5, "tagName ?sequence command?",}, {"button", 2, (Blt_Op)ButtonOp, 2, 0, "args",}, {"cget", 2, (Blt_Op)CgetOp, 3, 3, "option",}, {"close", 2, (Blt_Op)CloseOp, 2, 0, "?-recurse? index...",}, {"configure", 2, (Blt_Op)ConfigureOp, 2, 0, "?option value?...",}, {"curselection", 2, (Blt_Op)CurselectionOp, 2, 2, "",}, {"delete", 1, (Blt_Op)DeleteOp, 2, 0, "?-recurse? index ?index...?",}, {"entry", 1, (Blt_Op)EntryOp, 2, 0, "oper args",}, {"find", 2, (Blt_Op)FindOp, 2, 0, "?flags...? ?firstIndex lastIndex?",}, {"focus", 2, (Blt_Op)FocusOp, 3, 3, "index",}, {"get", 1, (Blt_Op)GetOp, 2, 0, "?-full? index ?index...?",}, {"hide", 1, (Blt_Op)HideOp, 2, 0, "?-exact? ?-glob? ?-regexp? ?-nonmatching? ?-name string? ?-full string? ?-data string? ?--? ?index...?",}, {"index", 3, (Blt_Op)IndexOp, 3, 5, "?-at index? string",}, {"insert", 3, (Blt_Op)InsertOp, 3, 0, "?-at index? position label ?label...? ?option value?",}, {"move", 1, (Blt_Op)MoveOp, 5, 5, "index into|before|after index",}, {"nearest", 1, (Blt_Op)NearestOp, 4, 5, "x y ?varName?",}, {"open", 1, (Blt_Op)OpenOp, 2, 0, "?-recurse? index...",}, {"range", 1, (Blt_Op)RangeOp, 4, 5, "?-open? firstIndex lastIndex",}, {"scan", 2, (Blt_Op)ScanOp, 5, 5, "dragto|mark x y",}, {"see", 3, (Blt_Op)SeeOp, 3, 0, "?-anchor anchor? index",}, {"selection", 3, (Blt_Op)SelectionOp, 2, 0, "oper args",}, {"show", 2, (Blt_Op)ShowOp, 2, 0, "?-exact? ?-glob? ?-regexp? ?-nonmatching? ?-name string? ?-full string? ?-data string? ?--? ?index...?",}, {"sort", 2, (Blt_Op)SortOp, 2, 0, "?-recurse? ?-command string? index...",}, {"toggle", 1, (Blt_Op)ToggleOp, 3, 3, "index",}, {"xview", 1, (Blt_Op)XViewOp, 2, 5, "?moveto fract? ?scroll number what?",}, {"yview", 1, (Blt_Op)YViewOp, 2, 5, "?moveto fract? ?scroll number what?",}, }; static int nSpecs = sizeof(operSpecs) / sizeof(Blt_OpSpec); static int HierboxInstCmd(clientData, interp, argc, argv) ClientData clientData; /* Information about the widget. */ Tcl_Interp *interp; /* Interpreter to report errors back to. */ int argc; /* Number of arguments. */ char **argv; /* Vector of argument strings. */ { Blt_Op proc; Hierbox *hboxPtr = clientData; int result; proc = Blt_GetOp(interp, nSpecs, operSpecs, BLT_OP_ARG1, argc, argv, 0); if (proc == NULL) { return TCL_ERROR; } Tcl_Preserve(hboxPtr); result = (*proc) (hboxPtr, interp, argc, argv); Tcl_Release(hboxPtr); return result; } int Blt_HierboxInit(interp) Tcl_Interp *interp; { static Blt_CmdSpec cmdSpec = { "hierbox", HierboxCmd, }; if (Blt_InitCmd(interp, "blt", &cmdSpec) == NULL) { return TCL_ERROR; } return TCL_OK; } #ifdef notdef /* Selection Procedures */ /* *---------------------------------------------------------------------- * * SelectTextBlock -- * * Modify the selection by moving its un-anchored end. This * could make the selection either larger or smaller. * * Results: * None. * * Side effects: * The selection changes. * *---------------------------------------------------------------------- */ static int SelectTextBlock(hboxPtr, to) Hierbox *hboxPtr; /* Information about widget. */ int to; /* Index of element that is to * become the "other" end of the * selection. */ { TextEdit *editPtr = &(hboxPtr->labelEdit); int first, last; /* If the anchor hasn't been set yet, assume the beginning of the label. */ if (editPtr->selAnchor < 0) { editPtr->selAnchor = 0; } if (editPtr->selAnchor <= to) { first = editPtr->selAnchor; last = to; } else { first = to; last = editPtr->selAnchor; } if ((editPtr->selFirst != first) || (editPtr->selLast != last)) { editPtr->selFirst = first, editPtr->selLast = last; EventuallyRedraw(hboxPtr); } return TCL_OK; } /* *---------------------------------------------------------------------- * * ClearOpOp -- * * Clears the selection from the label. * * Results: * None. * * Side effects: * The selection changes. * *---------------------------------------------------------------------- */ static int ClearOpOp(hboxPtr, interp, argc, argv) Hierbox *hboxPtr; Tcl_Interp *interp; int argc; char **argv; { TextEdit *editPtr = &(hboxPtr->labelEdit); if (editPtr->selFirst != -1) { editPtr->selFirst = editPtr->selLast = -1; EventuallyRedraw(hboxPtr); } return TCL_OK; } static int FromOpOp(hboxPtr, interp, argc, argv) Hierbox *hboxPtr; Tcl_Interp *interp; int argc; char **argv; { TextEdit *editPtr = &(hboxPtr->labelEdit); int from; if (GetLabelIndex(hboxPtr, argv[3], &from) != TCL_OK) { return TCL_ERROR; } editPtr->selAnchor = from; return TCL_OK; } static int PresentOpOp(hboxPtr, interp, argc, argv) Hierbox *hboxPtr; Tcl_Interp *interp; int argc; char **argv; { TextEdit *editPtr = &(hboxPtr->labelEdit); Tcl_AppendResult(interp, (editPtr->selFirst != -1) ? "0" : "1", (char *)NULL); return TCL_OK; } static int AdjustOpOp(hboxPtr, interp, argc, argv) Hierbox *hboxPtr; Tcl_Interp *interp; int argc; char **argv; { TextEdit *editPtr = &(hboxPtr->labelEdit); int adjust; if (GetLabelIndex(hboxPtr, argv[3], &adjust) != TCL_OK) { return TCL_ERROR; } half1 = (editPtr->selFirst + editPtr->selLast) / 2; half2 = (editPtr->selFirst + editPtr->selLast + 1) / 2; if (adjust < half1) { editPtr->selAnchor = editPtr->selLast; } else if (adjust > half2) { editPtr->selAnchor = editPtr->selFirst; } result = SelectTextBlock(hboxPtr, adjust); return TCL_OK; } static int ToOpOp(hboxPtr, interp, argc, argv) Hierbox *hboxPtr; Tcl_Interp *interp; int argc; char **argv; { int to; if (GetLabelIndex(hboxPtr, argv[3], &to) != TCL_OK) { return TCL_ERROR; } SelectTextBlock(hboxPtr, to); return TCL_OK; } static int RangeOpOp(hboxPtr, interp, argc, argv) Hierbox *hboxPtr; Tcl_Interp *interp; int argc; char **argv; { TextEdit *editPtr = &(hboxPtr->labelEdit); int first, last; if (GetLabelIndex(hboxPtr, argv[4], &first) != TCL_OK) { return TCL_ERROR; } if (GetLabelIndex(hboxPtr, argv[5], &last) != TCL_OK) { return TCL_ERROR; } editPtr->selAnchor = first; return SelectTextBlock(hboxPtr, last); } /* *---------------------------------------------------------------------- * * SelectionOp -- * * This procedure handles the individual options for text * selections. The selected text is designated by a starting * and terminating index that points into the text. The selected * segment has both a anchored and unanchored ends. The following * selection operations are implemented: * * adjust index Resets either the first or last index * of the selection. * clear Clears the selection. Sets first/last * indices to -1. * from index Sets the index of the selection anchor. * present Return "1" if a selection is available, * "0" otherwise. * range first last Sets the selection. * to index Sets the index of the un-anchored end. * Results: * None. * * Side effects: * The selection changes. * *---------------------------------------------------------------------- */ static Blt_OpSpec entrySelectionOperSpecs[] = { {"adjust", 2, (Blt_Op)AdjustOpOp, 5, 5, "index",}, {"clear", 2, (Blt_Op)ClearOpOp, 4, 4, "",}, {"from", 1, (Blt_Op)FromOpOp, 5, 5, "index",}, {"present", 1, (Blt_Op)PresentOpOp, 4, 4, "",}, {"range", 1, (Blt_Op)RangeOpOp, 4, 5, "firstIndex lastIndex",}, {"to", 1, (Blt_Op)ToOpOp, 5, 5, "index",}, }; static int nEntrySelectionSpecs = sizeof(entrySelectionOperSpecs) / sizeof(Blt_OpSpec); static int EntrySelectionOp(hboxPtr, interp, argc, argv) Hierbox *hboxPtr; Tcl_Interp *interp; int argc; char **argv; { Blt_Op proc; int result; proc = Blt_GetOp(interp, nSelectionSpecs, selectionOperSpecs, BLT_OP_ARG2, argc, argv, 0); if (proc == NULL) { return TCL_ERROR; } result = (*proc) (hboxPtr, interp, argc, argv); return result; } #endif #endif /* NO_HIERBOX */