/* * bltTreeView.c -- * * This module implements an hierarchy widget for the BLT toolkit. * * Copyright 1998-1999 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 "treeview" 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_TREEVIEW #include "bltTreeView.h" #define BUTTON_PAD 2 #define BUTTON_IPAD 1 #define BUTTON_SIZE 7 #define COLUMN_PAD 2 #define FOCUS_WIDTH 1 #define ICON_PADX 2 #define ICON_PADY 1 #define INSET_PAD 0 #define LABEL_PADX 3 #define LABEL_PADY 0 #include #include #define DEF_ICON_WIDTH 16 #define DEF_ICON_HEIGHT 16 static Blt_TreeApplyProc DeleteApplyProc; static Blt_TreeApplyProc CreateApplyProc; extern Blt_CustomOption bltTreeViewDataOption; static Blt_OptionParseProc ObjToTree; static Blt_OptionPrintProc TreeToObj; static Blt_OptionFreeProc FreeTree; Blt_CustomOption bltTreeViewTreeOption = { ObjToTree, TreeToObj, FreeTree, NULL, }; static Blt_OptionParseProc ObjToIcons; static Blt_OptionPrintProc IconsToObj; static Blt_OptionFreeProc FreeIcons; Blt_CustomOption bltTreeViewIconsOption = { /* Contains a pointer to the widget that's currently being * configured. This is used in the custom configuration parse * routine for icons. */ ObjToIcons, IconsToObj, FreeIcons, NULL, }; static Blt_OptionParseProc ObjToButton; static Blt_OptionPrintProc ButtonToObj; static Blt_CustomOption buttonOption = { ObjToButton, ButtonToObj, NULL, NULL, }; static Blt_OptionParseProc ObjToUid; static Blt_OptionPrintProc UidToObj; static Blt_OptionFreeProc FreeUid; Blt_CustomOption bltTreeViewUidOption = { ObjToUid, UidToObj, FreeUid, NULL, }; static Blt_OptionParseProc ObjToScrollmode; static Blt_OptionPrintProc ScrollmodeToObj; static Blt_CustomOption scrollmodeOption = { ObjToScrollmode, ScrollmodeToObj, NULL, NULL, }; static Blt_OptionParseProc ObjToSelectmode; static Blt_OptionPrintProc SelectmodeToObj; static Blt_CustomOption selectmodeOption = { ObjToSelectmode, SelectmodeToObj, NULL, NULL, }; static Blt_OptionParseProc ObjToSeparator; static Blt_OptionPrintProc SeparatorToObj; static Blt_OptionFreeProc FreeSeparator; static Blt_CustomOption separatorOption = { ObjToSeparator, SeparatorToObj, FreeSeparator, NULL, }; static Blt_OptionParseProc ObjToLabel; static Blt_OptionPrintProc LabelToObj; static Blt_OptionFreeProc FreeLabel; static Blt_CustomOption labelOption = { ObjToLabel, LabelToObj, FreeLabel, 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_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" /* RGB_LIGHTBLUE1 */ #define DEF_TV_ACTIVE_FOREGROUND "black" #define DEF_TV_ACTIVE_ICONS \ "blt::tv::activeOpenFolder blt::tv::activeCloseFolder" #define DEF_TV_ACTIVE_RELIEF "flat" #define DEF_TV_ACTIVE_STIPPLE "gray25" #define DEF_TV_ALLOW_DUPLICATES "yes" #define DEF_TV_BACKGROUND "white" #define DEF_TV_BORDERWIDTH STD_BORDERWIDTH #define DEF_TV_BUTTON "auto" #define DEF_TV_DASHES "dot" #define DEF_TV_EXPORT_SELECTION "no" #define DEF_TV_FOREGROUND STD_NORMAL_FOREGROUND #define DEF_TV_FG_MONO STD_NORMAL_FG_MONO #define DEF_TV_FLAT "no" #define DEF_TV_FOCUS_DASHES "dot" #define DEF_TV_FOCUS_EDIT "no" #define DEF_TV_FOCUS_FOREGROUND STD_ACTIVE_FOREGROUND #define DEF_TV_FOCUS_FG_MONO STD_ACTIVE_FG_MONO #define DEF_TV_FONT "Courier 12" #define DEF_TV_HEIGHT "400" #define DEF_TV_HIDE_LEAVES "no" #define DEF_TV_HIDE_ROOT "yes" #define DEF_TV_FOCUS_HIGHLIGHT_BACKGROUND STD_NORMAL_BACKGROUND #define DEF_TV_FOCUS_HIGHLIGHT_COLOR "black" #define DEF_TV_FOCUS_HIGHLIGHT_WIDTH "2" #define DEF_TV_ICONS "blt::tv::normalOpenFolder blt::tv::normalCloseFolder" #define DEF_TV_VLINE_COLOR RGB_GREY50 #define DEF_TV_VLINE_MONO STD_NORMAL_FG_MONO #define DEF_TV_LINESPACING "0" #define DEF_TV_LINEWIDTH "1" #define DEF_TV_MAKE_PATH "no" #define DEF_TV_NEW_TAGS "no" #define DEF_TV_NORMAL_BACKGROUND STD_NORMAL_BACKGROUND #define DEF_TV_NORMAL_FG_MONO STD_ACTIVE_FG_MONO #define DEF_TV_RELIEF "sunken" #define DEF_TV_RESIZE_CURSOR "arrow" #define DEF_TV_SCROLL_INCREMENT "20" #define DEF_TV_SCROLL_MODE "hierbox" #define DEF_TV_SELECT_BACKGROUND STD_SELECT_BACKGROUND /* RGB_LIGHTBLUE1 */ #define DEF_TV_SELECT_BG_MONO STD_SELECT_BG_MONO #define DEF_TV_SELECT_BORDERWIDTH "1" #define DEF_TV_SELECT_FOREGROUND STD_SELECT_FOREGROUND #define DEF_TV_SELECT_FG_MONO STD_SELECT_FG_MONO #define DEF_TV_SELECT_MODE "single" #define DEF_TV_SELECT_RELIEF "flat" #define DEF_TV_SHOW_ROOT "yes" #define DEF_TV_SHOW_TITLES "yes" #define DEF_TV_SORT_SELECTION "no" #define DEF_TV_TAKE_FOCUS "1" #define DEF_TV_TEXT_COLOR STD_NORMAL_FOREGROUND #define DEF_TV_TEXT_MONO STD_NORMAL_FG_MONO #define DEF_TV_TRIMLEFT "" #define DEF_TV_WIDTH "200" Blt_ConfigSpec bltTreeViewButtonSpecs[] = { {BLT_CONFIG_BORDER, "-activebackground", "activeBackground", "Background", DEF_BUTTON_ACTIVE_BACKGROUND, Blt_Offset(TreeView, button.activeBorder), 0}, {BLT_CONFIG_SYNONYM, "-activebg", "activeBackground", (char *)NULL, (char *)NULL, 0, 0}, {BLT_CONFIG_SYNONYM, "-activefg", "activeForeground", (char *)NULL, (char *)NULL, 0, 0}, {BLT_CONFIG_COLOR, "-activeforeground", "activeForeground", "Foreground", DEF_BUTTON_ACTIVE_FOREGROUND, Blt_Offset(TreeView, button.activeFgColor), 0}, {BLT_CONFIG_BORDER, "-background", "background", "Background", DEF_BUTTON_NORMAL_BACKGROUND, Blt_Offset(TreeView, button.border), 0}, {BLT_CONFIG_SYNONYM, "-bd", "borderWidth", (char *)NULL, (char *)NULL, 0, 0}, {BLT_CONFIG_SYNONYM, "-bg", "background", (char *)NULL, (char *)NULL, 0, 0}, {BLT_CONFIG_DISTANCE, "-borderwidth", "borderWidth", "BorderWidth", DEF_BUTTON_BORDERWIDTH, Blt_Offset(TreeView, button.borderWidth), BLT_CONFIG_DONT_SET_DEFAULT}, {BLT_CONFIG_RELIEF, "-closerelief", "closeRelief", "Relief", DEF_BUTTON_CLOSE_RELIEF, Blt_Offset(TreeView, button.closeRelief), BLT_CONFIG_DONT_SET_DEFAULT}, {BLT_CONFIG_SYNONYM, "-fg", "foreground", (char *)NULL, (char *)NULL, 0, 0}, {BLT_CONFIG_COLOR, "-foreground", "foreground", "Foreground", DEF_BUTTON_NORMAL_FOREGROUND, Blt_Offset(TreeView, button.fgColor), 0}, {BLT_CONFIG_CUSTOM, "-images", "images", "Icons", (char *)NULL, Blt_Offset(TreeView, button.icons), BLT_CONFIG_NULL_OK, &bltTreeViewIconsOption}, {BLT_CONFIG_RELIEF, "-openrelief", "openRelief", "Relief", DEF_BUTTON_OPEN_RELIEF, Blt_Offset(TreeView, button.openRelief), BLT_CONFIG_DONT_SET_DEFAULT}, {BLT_CONFIG_DISTANCE, "-size", "size", "Size", DEF_BUTTON_SIZE, Blt_Offset(TreeView, button.reqSize), 0}, {BLT_CONFIG_END, (char *)NULL, (char *)NULL, (char *)NULL, (char *)NULL, 0, 0} }; Blt_ConfigSpec bltTreeViewEntrySpecs[] = { {BLT_CONFIG_CUSTOM, "-activeicons", (char *)NULL, (char *)NULL, (char *)NULL, Blt_Offset(TreeViewEntry, activeIcons), BLT_CONFIG_NULL_OK, &bltTreeViewIconsOption}, {BLT_CONFIG_CUSTOM, "-bindtags", (char *)NULL, (char *)NULL, (char *)NULL, Blt_Offset(TreeViewEntry, tagsUid), BLT_CONFIG_NULL_OK, &bltTreeViewUidOption}, {BLT_CONFIG_CUSTOM, "-button", (char *)NULL, (char *)NULL, DEF_TV_BUTTON, Blt_Offset(TreeViewEntry, flags), BLT_CONFIG_DONT_SET_DEFAULT, &buttonOption}, {BLT_CONFIG_CUSTOM, "-closecommand", (char *)NULL, (char *)NULL, (char *)NULL, Blt_Offset(TreeViewEntry, closeCmd), BLT_CONFIG_NULL_OK, &bltTreeViewUidOption}, {BLT_CONFIG_CUSTOM, "-data", (char *)NULL, (char *)NULL, (char *)NULL, 0, BLT_CONFIG_NULL_OK, &bltTreeViewDataOption}, {BLT_CONFIG_SYNONYM, "-fg", "foreground", (char *)NULL, (char *)NULL, 0, 0}, {BLT_CONFIG_FONT, "-font", (char *)NULL, (char *)NULL, (char *)NULL, Blt_Offset(TreeViewEntry, font), 0}, {BLT_CONFIG_COLOR, "-foreground", "foreground", (char *)NULL, (char *)NULL, Blt_Offset(TreeViewEntry, color), BLT_CONFIG_NULL_OK}, {BLT_CONFIG_DISTANCE, "-height", (char *)NULL, (char *)NULL, (char *)NULL, Blt_Offset(TreeViewEntry, reqHeight), BLT_CONFIG_DONT_SET_DEFAULT}, {BLT_CONFIG_CUSTOM, "-icons", (char *)NULL, (char *)NULL, (char *)NULL, Blt_Offset(TreeViewEntry, icons), BLT_CONFIG_NULL_OK, &bltTreeViewIconsOption}, {BLT_CONFIG_CUSTOM, "-label", (char *)NULL, (char *)NULL, (char *)NULL, Blt_Offset(TreeViewEntry, labelUid), 0, &labelOption}, {BLT_CONFIG_CUSTOM, "-opencommand", (char *)NULL, (char *)NULL, (char *)NULL, Blt_Offset(TreeViewEntry, openCmd), BLT_CONFIG_NULL_OK, &bltTreeViewUidOption}, {BLT_CONFIG_SHADOW, "-shadow", (char *)NULL, (char *)NULL, (char *)NULL, Blt_Offset(TreeViewEntry, shadow), BLT_CONFIG_NULL_OK | BLT_CONFIG_COLOR_ONLY}, {BLT_CONFIG_SHADOW, "-shadow", (char *)NULL, (char *)NULL, (char *)NULL, Blt_Offset(TreeViewEntry, shadow), BLT_CONFIG_NULL_OK | BLT_CONFIG_MONO_ONLY}, {BLT_CONFIG_END, (char *)NULL, (char *)NULL, (char *)NULL, (char *)NULL, 0, 0} }; Blt_ConfigSpec bltTreeViewSpecs[] = { {BLT_CONFIG_CUSTOM, "-activeicons", "activeIcons", "Icons", DEF_TV_ACTIVE_ICONS, Blt_Offset(TreeView, activeIcons), BLT_CONFIG_NULL_OK, &bltTreeViewIconsOption}, {BLT_CONFIG_BITFLAG, "-allowduplicates", "allowDuplicates", "AllowDuplicates", DEF_TV_ALLOW_DUPLICATES, Blt_Offset(TreeView, flags), BLT_CONFIG_DONT_SET_DEFAULT, (Blt_CustomOption *)TV_ALLOW_DUPLICATES}, {BLT_CONFIG_BITFLAG, "-autocreate", "autoCreate", "AutoCreate", DEF_TV_MAKE_PATH, Blt_Offset(TreeView, flags), BLT_CONFIG_DONT_SET_DEFAULT, (Blt_CustomOption *)TV_FILL_ANCESTORS}, {BLT_CONFIG_BORDER, "-background", "background", "Background", DEF_TV_BACKGROUND, Blt_Offset(TreeView, border), 0}, {BLT_CONFIG_SYNONYM, "-bd", "borderWidth", (char *)NULL, (char *)NULL, 0, 0}, {BLT_CONFIG_SYNONYM, "-bg", "background", (char *)NULL, (char *)NULL, 0, 0}, {BLT_CONFIG_DISTANCE, "-borderwidth", "borderWidth", "BorderWidth", DEF_TV_BORDERWIDTH, Blt_Offset(TreeView, borderWidth), BLT_CONFIG_DONT_SET_DEFAULT}, {BLT_CONFIG_CUSTOM, "-button", "button", "Button", DEF_TV_BUTTON, Blt_Offset(TreeView, buttonFlags), BLT_CONFIG_DONT_SET_DEFAULT, &buttonOption}, {BLT_CONFIG_STRING, "-closecommand", "closeCommand", "CloseCommand", (char *)NULL, Blt_Offset(TreeView, closeCmd), BLT_CONFIG_NULL_OK}, {BLT_CONFIG_ACTIVE_CURSOR, "-cursor", "cursor", "Cursor", (char *)NULL, Blt_Offset(TreeView, cursor), BLT_CONFIG_NULL_OK}, {BLT_CONFIG_DASHES, "-dashes", "dashes", "Dashes", DEF_TV_DASHES, Blt_Offset(TreeView, dashes), BLT_CONFIG_DONT_SET_DEFAULT}, {BLT_CONFIG_BITFLAG, "-exportselection", "exportSelection", "ExportSelection", DEF_TV_EXPORT_SELECTION, Blt_Offset(TreeView, flags), BLT_CONFIG_DONT_SET_DEFAULT, (Blt_CustomOption *)TV_SELECT_EXPORT}, {BLT_CONFIG_SYNONYM, "-fg", "foreground", (char *)NULL, (char *)NULL, 0, 0}, {BLT_CONFIG_BOOLEAN, "-flat", "flat", "Flat", DEF_TV_FLAT, Blt_Offset(TreeView, flatView), BLT_CONFIG_DONT_SET_DEFAULT}, {BLT_CONFIG_DASHES, "-focusdashes", "focusDashes", "FocusDashes", DEF_TV_FOCUS_DASHES, Blt_Offset(TreeView, focusDashes), BLT_CONFIG_NULL_OK}, {BLT_CONFIG_COLOR, "-focusforeground", "focusForeground", "FocusForeground", DEF_TV_FOCUS_FOREGROUND, Blt_Offset(TreeView, focusColor), BLT_CONFIG_COLOR_ONLY}, {BLT_CONFIG_COLOR, "-focusforeground", "focusForeground", "FocusForeground", DEF_TV_FOCUS_FG_MONO, Blt_Offset(TreeView, focusColor), BLT_CONFIG_MONO_ONLY}, {BLT_CONFIG_FONT, "-font", "font", "Font", DEF_TV_FONT, Blt_Offset(TreeView, font), 0}, {BLT_CONFIG_COLOR, "-foreground", "foreground", "Foreground", DEF_TV_TEXT_COLOR, Blt_Offset(TreeView, fgColor), BLT_CONFIG_COLOR_ONLY}, {BLT_CONFIG_COLOR, "-foreground", "foreground", "Foreground", DEF_TV_TEXT_MONO, Blt_Offset(TreeView, fgColor), BLT_CONFIG_MONO_ONLY}, {BLT_CONFIG_DISTANCE, "-height", "height", "Height", DEF_TV_HEIGHT, Blt_Offset(TreeView, reqHeight), BLT_CONFIG_DONT_SET_DEFAULT}, {BLT_CONFIG_BITFLAG, "-hideleaves", "hideLeaves", "HideLeaves", DEF_TV_HIDE_LEAVES, Blt_Offset(TreeView, flags), BLT_CONFIG_DONT_SET_DEFAULT, (Blt_CustomOption *)TV_HIDE_LEAVES}, {BLT_CONFIG_BITFLAG, "-hideroot", "hideRoot", "HideRoot", DEF_TV_HIDE_ROOT, Blt_Offset(TreeView, flags), BLT_CONFIG_DONT_SET_DEFAULT, (Blt_CustomOption *)TV_HIDE_ROOT}, {BLT_CONFIG_COLOR, "-highlightbackground", "highlightBackground", "HighlightBackground", DEF_TV_FOCUS_HIGHLIGHT_BACKGROUND, Blt_Offset(TreeView, highlightBgColor), 0}, {BLT_CONFIG_COLOR, "-highlightcolor", "highlightColor", "HighlightColor", DEF_TV_FOCUS_HIGHLIGHT_COLOR, Blt_Offset(TreeView, highlightColor), 0}, {BLT_CONFIG_PIXELS, "-highlightthickness", "highlightThickness", "HighlightThickness", DEF_TV_FOCUS_HIGHLIGHT_WIDTH, Blt_Offset(TreeView, highlightWidth), BLT_CONFIG_DONT_SET_DEFAULT}, {BLT_CONFIG_CUSTOM, "-icons", "icons", "Icons", DEF_TV_ICONS, Blt_Offset(TreeView, icons), BLT_CONFIG_NULL_OK, &bltTreeViewIconsOption}, {BLT_CONFIG_BORDER, "-nofocusselectbackground", "noFocusSelectBackground", "NoFocusSelectBackground", DEF_TV_SELECT_BACKGROUND, Blt_Offset(TreeView, selOutFocusBorder), TK_CONFIG_NULL_OK}, {BLT_CONFIG_COLOR, "-nofocusselectforeground", "noFocusSelectForeground", "NoFocusSelectForeground", DEF_TV_SELECT_FOREGROUND, Blt_Offset(TreeView, selOutFocusFgColor), TK_CONFIG_NULL_OK}, {BLT_CONFIG_COLOR, "-linecolor", "lineColor", "LineColor", DEF_TV_VLINE_COLOR, Blt_Offset(TreeView, lineColor), BLT_CONFIG_COLOR_ONLY}, {BLT_CONFIG_COLOR, "-linecolor", "lineColor", "LineColor", DEF_TV_VLINE_MONO, Blt_Offset(TreeView, lineColor), BLT_CONFIG_MONO_ONLY}, {BLT_CONFIG_DISTANCE, "-linespacing", "lineSpacing", "LineSpacing", DEF_TV_LINESPACING, Blt_Offset(TreeView, leader), BLT_CONFIG_DONT_SET_DEFAULT}, {BLT_CONFIG_DISTANCE, "-linewidth", "lineWidth", "LineWidth", DEF_TV_LINEWIDTH, Blt_Offset(TreeView, lineWidth), BLT_CONFIG_DONT_SET_DEFAULT}, {BLT_CONFIG_BITFLAG, "-newtags", "newTags", "NewTags", DEF_TV_NEW_TAGS, Blt_Offset(TreeView, flags), BLT_CONFIG_DONT_SET_DEFAULT, (Blt_CustomOption *)TV_NEW_TAGS}, {BLT_CONFIG_STRING, "-opencommand", "openCommand", "OpenCommand", (char *)NULL, Blt_Offset(TreeView, openCmd), BLT_CONFIG_NULL_OK}, {BLT_CONFIG_RELIEF, "-relief", "relief", "Relief", DEF_TV_RELIEF, Blt_Offset(TreeView, relief), 0}, {BLT_CONFIG_CURSOR, "-resizecursor", "resizeCursor", "ResizeCursor", DEF_TV_RESIZE_CURSOR, Blt_Offset(TreeView, resizeCursor), 0}, {BLT_CONFIG_CUSTOM, "-scrollmode", "scrollMode", "ScrollMode", DEF_TV_SCROLL_MODE, Blt_Offset(TreeView, scrollMode), BLT_CONFIG_DONT_SET_DEFAULT, &scrollmodeOption}, {BLT_CONFIG_BORDER, "-selectbackground", "selectBackground", "Foreground", DEF_TV_SELECT_BACKGROUND, Blt_Offset(TreeView, selInFocusBorder), 0}, {BLT_CONFIG_DISTANCE, "-selectborderwidth", "selectBorderWidth", "BorderWidth", DEF_TV_SELECT_BORDERWIDTH, Blt_Offset(TreeView, selBorderWidth), BLT_CONFIG_DONT_SET_DEFAULT}, {BLT_CONFIG_STRING, "-selectcommand", "selectCommand", "SelectCommand", (char *)NULL, Blt_Offset(TreeView, selectCmd), BLT_CONFIG_NULL_OK}, {BLT_CONFIG_COLOR, "-selectforeground", "selectForeground", "Background", DEF_TV_SELECT_FOREGROUND, Blt_Offset(TreeView, selInFocusFgColor), 0}, {BLT_CONFIG_CUSTOM, "-selectmode", "selectMode", "SelectMode", DEF_TV_SELECT_MODE, Blt_Offset(TreeView, selectMode), BLT_CONFIG_DONT_SET_DEFAULT, &selectmodeOption}, {BLT_CONFIG_RELIEF, "-selectrelief", "selectRelief", "Relief", DEF_TV_SELECT_RELIEF, Blt_Offset(TreeView, selRelief), BLT_CONFIG_DONT_SET_DEFAULT}, {BLT_CONFIG_CUSTOM, "-separator", "separator", "Separator", (char *)NULL, Blt_Offset(TreeView, pathSep), BLT_CONFIG_NULL_OK, &separatorOption}, {BLT_CONFIG_BITFLAG, "-showtitles", "showTitles", "ShowTitles", DEF_TV_SHOW_TITLES, Blt_Offset(TreeView, flags), 0, (Blt_CustomOption *)TV_SHOW_COLUMN_TITLES}, {BLT_CONFIG_BITFLAG, "-sortselection", "sortSelection", "SortSelection", DEF_TV_SORT_SELECTION, Blt_Offset(TreeView, flags), BLT_CONFIG_DONT_SET_DEFAULT, (Blt_CustomOption *)TV_SELECT_SORTED}, {BLT_CONFIG_STRING, "-takefocus", "takeFocus", "TakeFocus", DEF_TV_TAKE_FOCUS, Blt_Offset(TreeView, takeFocus), BLT_CONFIG_NULL_OK}, {BLT_CONFIG_CUSTOM, "-tree", "tree", "Tree", (char *)NULL, Blt_Offset(TreeView, tree), BLT_CONFIG_NULL_OK, &bltTreeViewTreeOption}, {BLT_CONFIG_STRING, "-trim", "trim", "Trim", DEF_TV_TRIMLEFT, Blt_Offset(TreeView, trimLeft), BLT_CONFIG_NULL_OK}, {BLT_CONFIG_DISTANCE, "-width", "width", "Width", DEF_TV_WIDTH, Blt_Offset(TreeView, reqWidth), BLT_CONFIG_DONT_SET_DEFAULT}, {BLT_CONFIG_STRING, "-xscrollcommand", "xScrollCommand", "ScrollCommand", (char *)NULL, Blt_Offset(TreeView, xScrollCmdPrefix), BLT_CONFIG_NULL_OK}, {BLT_CONFIG_DISTANCE, "-xscrollincrement", "xScrollIncrement", "ScrollIncrement", DEF_TV_SCROLL_INCREMENT, Blt_Offset(TreeView, xScrollUnits), BLT_CONFIG_DONT_SET_DEFAULT}, {BLT_CONFIG_STRING, "-yscrollcommand", "yScrollCommand", "ScrollCommand", (char *)NULL, Blt_Offset(TreeView, yScrollCmdPrefix), BLT_CONFIG_NULL_OK}, {BLT_CONFIG_DISTANCE, "-yscrollincrement", "yScrollIncrement", "ScrollIncrement", DEF_TV_SCROLL_INCREMENT, Blt_Offset(TreeView, yScrollUnits), BLT_CONFIG_DONT_SET_DEFAULT}, {BLT_CONFIG_END, (char *)NULL, (char *)NULL, (char *)NULL, (char *)NULL, 0, 0} }; /* Forward Declarations */ static Blt_TreeNotifyEventProc TreeEventProc; static Blt_TreeTraceProc TreeTraceProc; static Tcl_CmdDeleteProc WidgetInstCmdDeleteProc; static Tcl_FreeProc DestroyTreeView; static Tcl_IdleProc DisplayTreeView; static Tk_EventProc TreeViewEventProc; static int ComputeVisibleEntries _ANSI_ARGS_((TreeView *tvPtr)); EXTERN int Blt_TreeCmdGetToken _ANSI_ARGS_((Tcl_Interp *interp, CONST char *treeName, Blt_Tree *treePtr)); extern Blt_TreeApplyProc Blt_TreeViewSortApplyProc; static Blt_BindPickProc PickItem; static Blt_BindTagProc GetTags; static Tcl_FreeProc DestroyEntry; static Tcl_ObjCmdProc TreeViewObjCmd; static Tk_ImageChangedProc IconChangedProc; static Tk_SelectionProc SelectionProc; /* *---------------------------------------------------------------------- * * Blt_TreeViewEventuallyRedraw -- * * 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. * *---------------------------------------------------------------------- */ void Blt_TreeViewEventuallyRedraw(TreeView *tvPtr) { if ((tvPtr->tkwin != NULL) && ((tvPtr->flags & TV_REDRAW) == 0)) { tvPtr->flags |= TV_REDRAW; Tcl_DoWhenIdle(DisplayTreeView, tvPtr); } } void Blt_TreeViewTraceColumn(TreeView *tvPtr, TreeViewColumn *columnPtr) { Blt_TreeCreateTrace(tvPtr->tree, NULL /* Node */, columnPtr->key, NULL, TREE_TRACE_FOREIGN_ONLY | TREE_TRACE_WRITE | TREE_TRACE_UNSET, TreeTraceProc, tvPtr); } static void TraceColumns(TreeView *tvPtr) { Blt_ChainLink *linkPtr; TreeViewColumn *columnPtr; for(linkPtr = Blt_ChainFirstLink(tvPtr->colChainPtr); linkPtr != NULL; linkPtr = Blt_ChainNextLink(linkPtr)) { columnPtr = Blt_ChainGetValue(linkPtr); Blt_TreeCreateTrace( tvPtr->tree, NULL /* Node */, columnPtr->key /* Key pattern */, NULL /* Tag */, TREE_TRACE_FOREIGN_ONLY | TREE_TRACE_WRITE | TREE_TRACE_UNSET, TreeTraceProc /* Callback routine */, tvPtr /* Client data */); } } /* *---------------------------------------------------------------------- * * ObjToTree -- * * Convert the string representing the name of a tree object * into a tree token. * * 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 ObjToTree( ClientData clientData, /* Not used. */ Tcl_Interp *interp, /* Interpreter to send results back to */ Tk_Window tkwin, /* Not used. */ Tcl_Obj *objPtr, /* Tcl_Obj representing the new value. */ char *widgRec, int offset) { Blt_Tree *treePtr = (Blt_Tree *)(widgRec + offset); Blt_Tree tree; char *string; tree = NULL; string = Tcl_GetString(objPtr); if ((string[0] != '\0') && (Blt_TreeGetToken(interp, string, &tree) != TCL_OK)) { return TCL_ERROR; } *treePtr = tree; return TCL_OK; } /* *---------------------------------------------------------------------- * * TreeToObj -- * * Results: * The string representation of the button boolean is returned. * *---------------------------------------------------------------------- */ /*ARGSUSED*/ static Tcl_Obj * TreeToObj( ClientData clientData, /* Not used. */ Tcl_Interp *interp, Tk_Window tkwin, /* Not used. */ char *widgRec, int offset) { Blt_Tree tree = *(Blt_Tree *)(widgRec + offset); if (tree == NULL) { return bltEmptyStringObjPtr; } else { return Tcl_NewStringObj(Blt_TreeName(tree), -1); } } /*ARGSUSED*/ static void FreeTree( ClientData clientData, Display *display, /* Not used. */ char *widgRec, int offset) { Blt_Tree *treePtr = (Blt_Tree *)(widgRec + offset); if (*treePtr != NULL) { Blt_TreeNode root; TreeView *tvPtr = clientData; /* * Release the current tree, removing any entry fields. */ root = Blt_TreeRootNode(*treePtr); Blt_TreeApply(root, DeleteApplyProc, tvPtr); Blt_TreeViewClearSelection(tvPtr); Blt_TreeReleaseToken(*treePtr); } } /* *---------------------------------------------------------------------- * * ObjToScrollmode -- * * 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 ObjToScrollmode( ClientData clientData, /* Not used. */ Tcl_Interp *interp, /* Interpreter to send results back to */ Tk_Window tkwin, /* Not used. */ Tcl_Obj *objPtr, /* New legend position string */ char *widgRec, int offset) { char *string; char c; int *modePtr = (int *)(widgRec + offset); string = Tcl_GetString(objPtr); c = string[0]; if ((c == 'l') && (strcmp(string, "listbox") == 0)) { *modePtr = BLT_SCROLL_MODE_LISTBOX; } else if ((c == 't') && (strcmp(string, "treeview") == 0)) { *modePtr = BLT_SCROLL_MODE_HIERBOX; } else if ((c == 'h') && (strcmp(string, "hiertable") == 0)) { *modePtr = BLT_SCROLL_MODE_HIERBOX; } else if ((c == 'c') && (strcmp(string, "canvas") == 0)) { *modePtr = BLT_SCROLL_MODE_CANVAS; } else { Tcl_AppendResult(interp, "bad scroll mode \"", string, "\": should be \"treeview\", \"listbox\", or \"canvas\"", (char *)NULL); return TCL_ERROR; } return TCL_OK; } /* *---------------------------------------------------------------------- * * ScrollmodeToObj -- * * Results: * The string representation of the button boolean is returned. * *---------------------------------------------------------------------- */ /*ARGSUSED*/ static Tcl_Obj * ScrollmodeToObj( ClientData clientData, /* Not used. */ Tcl_Interp *interp, Tk_Window tkwin, /* Not used. */ char *widgRec, int offset) { int mode = *(int *)(widgRec + offset); switch (mode) { case BLT_SCROLL_MODE_LISTBOX: return Tcl_NewStringObj("listbox", -1); case BLT_SCROLL_MODE_HIERBOX: return Tcl_NewStringObj("hierbox", -1); case BLT_SCROLL_MODE_CANVAS: return Tcl_NewStringObj("canvas", -1); default: return Tcl_NewStringObj("unknown scroll mode", -1); } } /* *---------------------------------------------------------------------- * * ObjToSelectmode -- * * 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 ObjToSelectmode( ClientData clientData, /* Not used. */ Tcl_Interp *interp, /* Interpreter to send results back to */ Tk_Window tkwin, /* Not used. */ Tcl_Obj *objPtr, /* Tcl_Obj representing the new value. */ char *widgRec, int offset) { char *string; char c; int *modePtr = (int *)(widgRec + offset); string = Tcl_GetString(objPtr); c = string[0]; if ((c == 's') && (strcmp(string, "single") == 0)) { *modePtr = SELECT_MODE_SINGLE; } else if ((c == 'm') && (strcmp(string, "multiple") == 0)) { *modePtr = SELECT_MODE_MULTIPLE; } else if ((c == 'a') && (strcmp(string, "active") == 0)) { *modePtr = SELECT_MODE_SINGLE; } else { Tcl_AppendResult(interp, "bad select mode \"", string, "\": should be \"single\" or \"multiple\"", (char *)NULL); return TCL_ERROR; } return TCL_OK; } /* *---------------------------------------------------------------------- * * SelectmodeToObj -- * * Results: * The string representation of the button boolean is returned. * *---------------------------------------------------------------------- */ /*ARGSUSED*/ static Tcl_Obj * SelectmodeToObj( ClientData clientData, /* Not used. */ Tcl_Interp *interp, Tk_Window tkwin, /* Not used. */ char *widgRec, int offset) { int mode = *(int *)(widgRec + offset); switch (mode) { case SELECT_MODE_SINGLE: return Tcl_NewStringObj("single", -1); case SELECT_MODE_MULTIPLE: return Tcl_NewStringObj("multiple", -1); default: return Tcl_NewStringObj("unknown scroll mode", -1); } } /* *---------------------------------------------------------------------- * * ObjToButton -- * * 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 ObjToButton( ClientData clientData, /* Not used. */ Tcl_Interp *interp, /* Interpreter to send results back to */ Tk_Window tkwin, /* Not used. */ Tcl_Obj *objPtr, /* Tcl_Obj representing the new value. */ char *widgRec, int offset) { char *string; int *flagsPtr = (int *)(widgRec + offset); string = Tcl_GetString(objPtr); if ((string[0] == 'a') && (strcmp(string, "auto") == 0)) { *flagsPtr &= ~BUTTON_MASK; *flagsPtr |= BUTTON_AUTO; } else { int bool; if (Tcl_GetBooleanFromObj(interp, objPtr, &bool) != TCL_OK) { return TCL_ERROR; } *flagsPtr &= ~BUTTON_MASK; if (bool) { *flagsPtr |= BUTTON_SHOW; } } return TCL_OK; } /* *---------------------------------------------------------------------- * * ButtonToObj -- * * Results: * The string representation of the button boolean is returned. * *---------------------------------------------------------------------- */ /*ARGSUSED*/ static Tcl_Obj * ButtonToObj( ClientData clientData, /* Not used. */ Tcl_Interp *interp, Tk_Window tkwin, /* Not used. */ char *widgRec, int offset) { int bool; unsigned int flags = *(int *)(widgRec + offset); bool = (flags & BUTTON_MASK); if (bool == BUTTON_AUTO) { return Tcl_NewStringObj("auto", 4); } else { return Tcl_NewBooleanObj(bool); } } /* *---------------------------------------------------------------------- * * ObjToScrollmode -- * * 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 ObjToSeparator(clientData, interp, tkwin, objPtr, widgRec, offset) ClientData clientData; /* Not used. */ Tcl_Interp *interp; /* Interpreter to send results back to */ Tk_Window tkwin; /* Not used. */ Tcl_Obj *objPtr; /* Tcl_Obj representing the new value. */ char *widgRec; int offset; { char **sepPtr = (char **)(widgRec + offset); char *string; string = Tcl_GetString(objPtr); if (*string == '\0') { *sepPtr = SEPARATOR_LIST; } else if (strcmp(string, "none") == 0) { *sepPtr = SEPARATOR_NONE; } else { *sepPtr = Blt_Strdup(string); } return TCL_OK; } /* *---------------------------------------------------------------------- * * SeparatorToObj -- * * Results: * The string representation of the separator is returned. * *---------------------------------------------------------------------- */ /*ARGSUSED*/ static Tcl_Obj * SeparatorToObj( ClientData clientData, /* Not used. */ Tcl_Interp *interp, Tk_Window tkwin, /* Not used. */ char *widgRec, int offset) { char *separator = *(char **)(widgRec + offset); if (separator == SEPARATOR_NONE) { return bltEmptyStringObjPtr; } else if (separator == SEPARATOR_LIST) { return Tcl_NewStringObj("list", -1); } else { return Tcl_NewStringObj(separator, -1); } } /* *---------------------------------------------------------------------- * * FreeSeparator -- * * Free the UID from the widget record, setting it to NULL. * * Results: * The UID in the widget record is set to NULL. * *---------------------------------------------------------------------- */ /*ARGSUSED*/ static void FreeSeparator( ClientData clientData, Display *display, /* Not used. */ char *widgRec, int offset) { char *separator = *(char **)(widgRec + offset); if ((separator != SEPARATOR_LIST) && (separator != SEPARATOR_NONE)) { Blt_Free(separator); } } /* *---------------------------------------------------------------------- * * ObjToLabel -- * * Convert the string representing the label. * * 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 ObjToLabel( ClientData clientData, /* Not used. */ Tcl_Interp *interp, /* Interpreter to send results back to */ Tk_Window tkwin, /* Not used. */ Tcl_Obj *objPtr, /* Tcl_Obj representing the new value. */ char *widgRec, int offset) { UID *labelPtr = (UID *)(widgRec + offset); char *string; string = Tcl_GetString(objPtr); if (string[0] != '\0') { TreeView *tvPtr = clientData; *labelPtr = Blt_TreeViewGetUid(tvPtr, string); } return TCL_OK; } /* *---------------------------------------------------------------------- * * TreeToObj -- * * Results: * The string of the entry's label is returned. * *---------------------------------------------------------------------- */ /*ARGSUSED*/ static Tcl_Obj * LabelToObj( ClientData clientData, /* Not used. */ Tcl_Interp *interp, Tk_Window tkwin, /* Not used. */ char *widgRec, int offset) { UID labelUid = *(UID *)(widgRec + offset); char *string; if (labelUid == NULL) { TreeViewEntry *entryPtr = (TreeViewEntry *)widgRec; string = Blt_TreeNodeLabel(entryPtr->node); } else { string = labelUid; } return Tcl_NewStringObj(string, -1); } /*ARGSUSED*/ static void FreeLabel( ClientData clientData, Display *display, /* Not used. */ char *widgRec, int offset) { UID *labelPtr = (UID *)(widgRec + offset); if (*labelPtr != NULL) { TreeView *tvPtr = clientData; Blt_TreeViewFreeUid(tvPtr, *labelPtr); } } /* *---------------------------------------------------------------------- * * Blt_TreeViewGetUid -- * * Gets or creates a unique string identifier. Strings are * reference counted. The string is placed into a hashed table * local to the treeview. * * Results: * Returns the pointer to the hashed string. * *---------------------------------------------------------------------- */ UID Blt_TreeViewGetUid(TreeView *tvPtr, CONST char *string) { Blt_HashEntry *hPtr; int isNew; int refCount; hPtr = Blt_CreateHashEntry(&tvPtr->uidTable, string, &isNew); if (isNew) { refCount = 1; } else { refCount = (int)Blt_GetHashValue(hPtr); refCount++; } Blt_SetHashValue(hPtr, (ClientData)refCount); return Blt_GetHashKey(&tvPtr->uidTable, hPtr); } /* *---------------------------------------------------------------------- * * Blt_TreeViewFreeUid -- * * Releases the uid. Uids are reference counted, so only when * the reference count is zero (i.e. no one else is using the * string) is the entry removed from the hash table. * * Results: * None. * *---------------------------------------------------------------------- */ void Blt_TreeViewFreeUid(TreeView *tvPtr, UID uid) { Blt_HashEntry *hPtr; int refCount; hPtr = Blt_FindHashEntry(&tvPtr->uidTable, uid); assert(hPtr != NULL); refCount = (int)Blt_GetHashValue(hPtr); refCount--; if (refCount > 0) { Blt_SetHashValue(hPtr, (ClientData)refCount); } else { Blt_DeleteHashEntry(&tvPtr->uidTable, hPtr); } } /* *---------------------------------------------------------------------- * * ObjToUid -- * * Converts the string to a Uid. Uid's are hashed, reference * counted strings. * *---------------------------------------------------------------------- */ /*ARGSUSED*/ static int ObjToUid( ClientData clientData, /* Not used. */ Tcl_Interp *interp, /* Interpreter to send results back to */ Tk_Window tkwin, /* Not used. */ Tcl_Obj *objPtr, /* Tcl_Obj representing the new value. */ char *widgRec, int offset) { TreeView *tvPtr = clientData; UID *uidPtr = (UID *)(widgRec + offset); UID newId; char *string; newId = NULL; string = Tcl_GetString(objPtr); if (*string != '\0') { newId = Blt_TreeViewGetUid(tvPtr, string); } *uidPtr = newId; return TCL_OK; } /* *---------------------------------------------------------------------- * * UidToObj -- * * Returns the uid as a string. * * Results: * The fill style string is returned. * *---------------------------------------------------------------------- */ /*ARGSUSED*/ static Tcl_Obj * UidToObj( ClientData clientData, /* Not used. */ Tcl_Interp *interp, Tk_Window tkwin, /* Not used. */ char *widgRec, int offset) { UID uid = *(UID *)(widgRec + offset); if (uid == NULL) { return bltEmptyStringObjPtr; } return Tcl_NewStringObj(uid, -1); } /* *---------------------------------------------------------------------- * * FreeUid -- * * Free the UID from the widget record, setting it to NULL. * * Results: * The UID in the widget record is set to NULL. * *---------------------------------------------------------------------- */ /*ARGSUSED*/ static void FreeUid( ClientData clientData, Display *display, /* Not used. */ char *widgRec, int offset) { UID *uidPtr = (UID *)(widgRec + offset); if (*uidPtr != NULL) { TreeView *tvPtr = clientData; Blt_TreeViewFreeUid(tvPtr, *uidPtr); } } /* *---------------------------------------------------------------------- * * IconChangedProc * * * Results: * None. * *---------------------------------------------------------------------- */ /* ARGSUSED */ static void IconChangedProc( ClientData clientData, int x, /* Not used. */ int y, /* Not used. */ int width, /* Not used. */ int height, /* Not used. */ int imageWidth, /* Not used. */ int imageHeight) /* Not used. */ { TreeView *tvPtr = clientData; tvPtr->flags |= (TV_DIRTY | TV_LAYOUT | TV_SCROLL); Blt_TreeViewEventuallyRedraw(tvPtr); } TreeViewIcon Blt_TreeViewGetIcon(TreeView *tvPtr, CONST char *iconName) { Blt_HashEntry *hPtr; int isNew; struct TreeViewIconStruct *iconPtr; hPtr = Blt_CreateHashEntry(&tvPtr->iconTable, iconName, &isNew); if (isNew) { Tk_Image tkImage; int width, height; tkImage = Tk_GetImage(tvPtr->interp, tvPtr->tkwin, (char *)iconName, IconChangedProc, tvPtr); if (tkImage == NULL) { Blt_DeleteHashEntry(&tvPtr->iconTable, hPtr); return NULL; } Tk_SizeOfImage(tkImage, &width, &height); iconPtr = Blt_Malloc(sizeof(struct TreeViewIconStruct)); iconPtr->tkImage = tkImage; iconPtr->hashPtr = hPtr; iconPtr->refCount = 1; iconPtr->width = width; iconPtr->height = height; Blt_SetHashValue(hPtr, iconPtr); } else { iconPtr = Blt_GetHashValue(hPtr); iconPtr->refCount++; } return iconPtr; } void Blt_TreeViewFreeIcon( TreeView *tvPtr, struct TreeViewIconStruct *iconPtr) { iconPtr->refCount--; if (iconPtr->refCount == 0) { Blt_DeleteHashEntry(&tvPtr->iconTable, iconPtr->hashPtr); Tk_FreeImage(iconPtr->tkImage); Blt_Free(iconPtr); } } static void DumpIconTable(TreeView *tvPtr) { Blt_HashEntry *hPtr; Blt_HashSearch cursor; struct TreeViewIconStruct *iconPtr; for (hPtr = Blt_FirstHashEntry(&tvPtr->iconTable, &cursor); hPtr != NULL; hPtr = Blt_NextHashEntry(&cursor)) { iconPtr = Blt_GetHashValue(hPtr); Tk_FreeImage(iconPtr->tkImage); Blt_Free(iconPtr); } Blt_DeleteHashTable(&tvPtr->iconTable); } /* *---------------------------------------------------------------------- * * ObjToIcons -- * * 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. * *---------------------------------------------------------------------- */ /*ARGSUSED*/ static int ObjToIcons( ClientData clientData, /* Not used. */ Tcl_Interp *interp, /* Interpreter to send results back to */ Tk_Window tkwin, /* Not used. */ Tcl_Obj *objPtr, /* Tcl_Obj representing the new value. */ char *widgRec, int offset) { Tcl_Obj **objv; TreeView *tvPtr = clientData; TreeViewIcon **iconPtrPtr = (TreeViewIcon **)(widgRec + offset); TreeViewIcon *icons; int objc; int result; result = TCL_OK; icons = NULL; if (Tcl_ListObjGetElements(interp, objPtr, &objc, &objv) != TCL_OK) { return TCL_ERROR; } if (objc > 0) { register int i; icons = Blt_Malloc(sizeof(TreeViewIcon *) * (objc + 1)); assert(icons); for (i = 0; i < objc; i++) { icons[i] = Blt_TreeViewGetIcon(tvPtr, Tcl_GetString(objv[i])); if (icons[i] == NULL) { result = TCL_ERROR; break; } } icons[i] = NULL; } *iconPtrPtr = icons; return result; } /* *---------------------------------------------------------------------- * * IconsToObj -- * * Converts the icon into its string representation (its name). * * Results: * The name of the icon is returned. * *---------------------------------------------------------------------- */ /*ARGSUSED*/ static Tcl_Obj * IconsToObj( ClientData clientData, /* Not used. */ Tcl_Interp *interp, Tk_Window tkwin, /* Not used. */ char *widgRec, int offset) { TreeViewIcon *icons = *(TreeViewIcon **)(widgRec + offset); Tcl_Obj *listObjPtr; listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL); if (icons != NULL) { register TreeViewIcon *iconPtr; Tcl_Obj *objPtr; for (iconPtr = icons; *iconPtr != NULL; iconPtr++) { objPtr = Tcl_NewStringObj(Blt_NameOfImage((*iconPtr)->tkImage), -1); Tcl_ListObjAppendElement(interp, listObjPtr, objPtr); } } return listObjPtr; } /*ARGSUSED*/ static void FreeIcons( ClientData clientData, Display *display, /* Not used. */ char *widgRec, int offset) { TreeViewIcon *icons = *(TreeViewIcon **)(widgRec + offset); if (icons != NULL) { register TreeViewIcon *iconPtr; TreeView *tvPtr = clientData; for (iconPtr = icons; *iconPtr != NULL; iconPtr++) { Blt_TreeViewFreeIcon(tvPtr, *iconPtr); } Blt_Free(icons); } } TreeViewEntry * Blt_NodeToEntry(TreeView *tvPtr, Blt_TreeNode node) { Blt_HashEntry *hPtr; hPtr = Blt_FindHashEntry(&tvPtr->entryTable, (char *)node); if (hPtr == NULL) { abort(); return NULL; } return Blt_GetHashValue(hPtr); } int Blt_TreeViewApply( TreeView *tvPtr, TreeViewEntry *entryPtr, /* Root entry of subtree. */ TreeViewApplyProc *proc, /* Procedure to call for each entry. */ unsigned int flags) { if ((flags & ENTRY_HIDDEN) && (Blt_TreeViewEntryIsHidden(entryPtr))) { return TCL_OK; /* Hidden node. */ } if ((flags & ENTRY_HIDDEN) && (entryPtr->flags & ENTRY_HIDDEN)) { return TCL_OK; /* Hidden node. */ } if (((flags & ENTRY_CLOSED) == 0) || ((entryPtr->flags & ENTRY_CLOSED) == 0)) { TreeViewEntry *childPtr; Blt_TreeNode node, next; for (node = Blt_TreeFirstChild(entryPtr->node); node != NULL; node = next) { next = Blt_TreeNextSibling(node); /* * Get the next child before calling Blt_TreeViewApply * recursively. This is because the apply callback may * delete the node and its link. */ childPtr = Blt_NodeToEntry(tvPtr, node); if (Blt_TreeViewApply(tvPtr, childPtr, proc, flags) != TCL_OK) { return TCL_ERROR; } } } if ((*proc) (tvPtr, entryPtr) != TCL_OK) { return TCL_ERROR; } return TCL_OK; } int Blt_TreeViewEntryIsHidden(TreeViewEntry *entryPtr) { TreeView *tvPtr = entryPtr->tvPtr; if ((tvPtr->flags & TV_HIDE_LEAVES) && (Blt_TreeIsLeaf(entryPtr->node))) { return TRUE; } return (entryPtr->flags & ENTRY_HIDDEN) ? TRUE : FALSE; } #ifdef notdef int Blt_TreeViewEntryIsMapped(TreeViewEntry *entryPtr) { TreeView *tvPtr = entryPtr->tvPtr; /* Don't check if the entry itself is open, only that its * ancestors are. */ if (Blt_TreeViewEntryIsHidden(entryPtr)) { return FALSE; } if (entryPtr == tvPtr->rootPtr) { return TRUE; } entryPtr = Blt_TreeViewParentEntry(entryPtr); while (entryPtr != tvPtr->rootPtr) { if (entryPtr->flags & (ENTRY_CLOSED | ENTRY_HIDDEN)) { return FALSE; } entryPtr = Blt_TreeViewParentEntry(entryPtr); } return TRUE; } #endif TreeViewEntry * Blt_TreeViewFirstChild(TreeViewEntry *entryPtr, unsigned int mask) { Blt_TreeNode node; TreeView *tvPtr = entryPtr->tvPtr; for (node = Blt_TreeFirstChild(entryPtr->node); node != NULL; node = Blt_TreeNextSibling(node)) { entryPtr = Blt_NodeToEntry(tvPtr, node); if (((mask & ENTRY_HIDDEN) == 0) || (!Blt_TreeViewEntryIsHidden(entryPtr))) { return entryPtr; } } return NULL; } TreeViewEntry * Blt_TreeViewLastChild(TreeViewEntry *entryPtr, unsigned int mask) { Blt_TreeNode node; TreeView *tvPtr = entryPtr->tvPtr; for (node = Blt_TreeLastChild(entryPtr->node); node != NULL; node = Blt_TreePrevSibling(node)) { entryPtr = Blt_NodeToEntry(tvPtr, node); if (((mask & ENTRY_HIDDEN) == 0) || (!Blt_TreeViewEntryIsHidden(entryPtr))) { return entryPtr; } } return NULL; } TreeViewEntry * Blt_TreeViewNextSibling(TreeViewEntry *entryPtr, unsigned int mask) { Blt_TreeNode node; TreeView *tvPtr = entryPtr->tvPtr; for (node = Blt_TreeNextSibling(entryPtr->node); node != NULL; node = Blt_TreeNextSibling(node)) { entryPtr = Blt_NodeToEntry(tvPtr, node); if (((mask & ENTRY_HIDDEN) == 0) || (!Blt_TreeViewEntryIsHidden(entryPtr))) { return entryPtr; } } return NULL; } TreeViewEntry * Blt_TreeViewPrevSibling(TreeViewEntry *entryPtr, unsigned int mask) { Blt_TreeNode node; TreeView *tvPtr = entryPtr->tvPtr; for (node = Blt_TreePrevSibling(entryPtr->node); node != NULL; node = Blt_TreePrevSibling(node)) { entryPtr = Blt_NodeToEntry(tvPtr, node); if (((mask & ENTRY_HIDDEN) == 0) || (!Blt_TreeViewEntryIsHidden(entryPtr))) { return entryPtr; } } return NULL; } /* *---------------------------------------------------------------------- * * Blt_TreeViewPrevEntry -- * * Returns the "previous" node in the tree. This node (in * depth-first order) is its parent if the node has no siblings * that are previous to it. Otherwise it is the last descendant * of the last sibling. In this case, descend the sibling's * hierarchy, using the last child at any ancestor, until we * we find a leaf. * *---------------------------------------------------------------------- */ TreeViewEntry * Blt_TreeViewPrevEntry(TreeViewEntry *entryPtr, unsigned int mask) { TreeView *tvPtr = entryPtr->tvPtr; TreeViewEntry *prevPtr; if (entryPtr->node == Blt_TreeRootNode(tvPtr->tree)) { return NULL; /* The root is the first node. */ } prevPtr = Blt_TreeViewPrevSibling(entryPtr, mask); if (prevPtr == NULL) { /* There are no siblings previous to this one, so pick the parent. */ prevPtr = Blt_TreeViewParentEntry(entryPtr); } else { /* * Traverse down the right-most thread in order to select the * last entry. Stop if we find a "closed" entry or reach a leaf. */ entryPtr = prevPtr; while ((entryPtr->flags & mask) == 0) { entryPtr = Blt_TreeViewLastChild(entryPtr, mask); if (entryPtr == NULL) { break; /* Found a leaf. */ } prevPtr = entryPtr; } } if (prevPtr == NULL) { return NULL; } return prevPtr; } /* *---------------------------------------------------------------------- * * Blt_TreeViewNextNode -- * * Returns the "next" node in relation to the given node. * The next node (in depth-first order) is either the first * child of the given node the next sibling if the node has * no children (the node is a leaf). If the given node is the * last sibling, then try it's parent next sibling. Continue * until we either find a next sibling for some ancestor or * we reach the root node. In this case the current node is * the last node in the tree. * *---------------------------------------------------------------------- */ TreeViewEntry * Blt_TreeViewNextEntry(TreeViewEntry *entryPtr, unsigned int mask) { TreeView *tvPtr = entryPtr->tvPtr; TreeViewEntry *nextPtr; int ignoreLeaf; ignoreLeaf = ((tvPtr->flags & TV_HIDE_LEAVES) && (Blt_TreeIsLeaf(entryPtr->node))); if ((!ignoreLeaf) && ((entryPtr->flags & mask) == 0)) { nextPtr = Blt_TreeViewFirstChild(entryPtr, mask); if (nextPtr != NULL) { return nextPtr; /* Pick the first sub-node. */ } } /* * Back up until to a level where we can pick a "next sibling". * For the last entry we'll thread our way back to the root. */ while (entryPtr != tvPtr->rootPtr) { nextPtr = Blt_TreeViewNextSibling(entryPtr, mask); if (nextPtr != NULL) { return nextPtr; } entryPtr = Blt_TreeViewParentEntry(entryPtr); } return NULL; /* At root, no next node. */ } void Blt_TreeViewConfigureButtons(TreeView *tvPtr) { GC newGC; TreeViewButton *buttonPtr = &tvPtr->button; XGCValues gcValues; unsigned long gcMask; gcMask = GCForeground; gcValues.foreground = buttonPtr->fgColor->pixel; newGC = Tk_GetGC(tvPtr->tkwin, gcMask, &gcValues); if (buttonPtr->normalGC != NULL) { Tk_FreeGC(tvPtr->display, buttonPtr->normalGC); } buttonPtr->normalGC = newGC; gcMask = GCForeground; gcValues.foreground = buttonPtr->activeFgColor->pixel; newGC = Tk_GetGC(tvPtr->tkwin, gcMask, &gcValues); if (buttonPtr->activeGC != NULL) { Tk_FreeGC(tvPtr->display, buttonPtr->activeGC); } buttonPtr->activeGC = newGC; buttonPtr->width = buttonPtr->height = ODD(buttonPtr->reqSize); if (buttonPtr->icons != NULL) { register int i; int width, height; for (i = 0; i < 2; i++) { if (buttonPtr->icons[i] == NULL) { break; } width = TreeViewIconWidth(buttonPtr->icons[i]); height = TreeViewIconWidth(buttonPtr->icons[i]); if (buttonPtr->width < width) { buttonPtr->width = width; } if (buttonPtr->height < height) { buttonPtr->height = height; } } } buttonPtr->width += 2 * buttonPtr->borderWidth; buttonPtr->height += 2 * buttonPtr->borderWidth; } int Blt_TreeViewConfigureEntry( TreeView *tvPtr, TreeViewEntry *entryPtr, int objc, Tcl_Obj *CONST *objv, int flags) { GC newGC; Blt_ChainLink *linkPtr; TreeViewColumn *columnPtr; bltTreeViewIconsOption.clientData = tvPtr; bltTreeViewUidOption.clientData = tvPtr; labelOption.clientData = tvPtr; if (Blt_ConfigureWidgetFromObj(tvPtr->interp, tvPtr->tkwin, bltTreeViewEntrySpecs, objc, objv, (char *)entryPtr, flags) != TCL_OK) { return TCL_ERROR; } /* * Check if there are values that need to be added */ for(linkPtr = Blt_ChainFirstLink(tvPtr->colChainPtr); linkPtr != NULL; linkPtr = Blt_ChainNextLink(linkPtr)) { columnPtr = Blt_ChainGetValue(linkPtr); Blt_TreeViewAddValue(entryPtr, columnPtr); } newGC = NULL; if ((entryPtr->font != NULL) || (entryPtr->color != NULL)) { Tk_Font font; XColor *colorPtr; XGCValues gcValues; unsigned long gcMask; font = entryPtr->font; if (font == NULL) { font = Blt_TreeViewGetStyleFont(tvPtr, tvPtr->treeColumn.stylePtr); } colorPtr = CHOOSE(tvPtr->fgColor, entryPtr->color); gcMask = GCForeground | GCFont; gcValues.foreground = colorPtr->pixel; gcValues.font = Tk_FontId(font); newGC = Tk_GetGC(tvPtr->tkwin, gcMask, &gcValues); } if (entryPtr->gc != NULL) { Tk_FreeGC(tvPtr->display, entryPtr->gc); } /* Assume all changes require a new layout. */ entryPtr->gc = newGC; entryPtr->flags |= ENTRY_LAYOUT_PENDING; if (Blt_ObjConfigModified(bltTreeViewEntrySpecs, "-font", (char *)NULL)) { tvPtr->flags |= TV_UPDATE; } tvPtr->flags |= (TV_LAYOUT | TV_DIRTY | TV_RESORT); return TCL_OK; } void Blt_TreeViewDestroyValue(TreeView *tvPtr, TreeViewValue *valuePtr) { if (valuePtr->stylePtr != NULL) { Blt_TreeViewFreeStyle(tvPtr, valuePtr->stylePtr); } if (valuePtr->textPtr != NULL) { Blt_Free(valuePtr->textPtr); } } static void DestroyEntry(DestroyData data) { TreeViewEntry *entryPtr = (TreeViewEntry *)data; TreeView *tvPtr; tvPtr = entryPtr->tvPtr; bltTreeViewIconsOption.clientData = tvPtr; bltTreeViewUidOption.clientData = tvPtr; labelOption.clientData = tvPtr; Blt_FreeObjOptions(bltTreeViewEntrySpecs, (char *)entryPtr, tvPtr->display, 0); if (!Blt_TreeTagTableIsShared(tvPtr->tree)) { /* Don't clear tags unless this client is the only one using * the tag table.*/ Blt_TreeClearTags(tvPtr->tree, entryPtr->node); } if (entryPtr->gc != NULL) { Tk_FreeGC(tvPtr->display, entryPtr->gc); } if (entryPtr->shadow.color != NULL) { Tk_FreeColor(entryPtr->shadow.color); } /* Delete the chain of data values from the entry. */ if (entryPtr->values != NULL) { TreeViewValue *valuePtr, *nextPtr; for (valuePtr = entryPtr->values; valuePtr != NULL; valuePtr = nextPtr) { nextPtr = valuePtr->nextPtr; Blt_TreeViewDestroyValue(tvPtr, valuePtr); } entryPtr->values = NULL; } if (entryPtr->fullName != NULL) { Blt_Free(entryPtr->fullName); } if (entryPtr->textPtr != NULL) { Blt_Free(entryPtr->textPtr); } Blt_PoolFreeItem(tvPtr->entryPool, entryPtr); } TreeViewEntry * Blt_TreeViewParentEntry(TreeViewEntry *entryPtr) { TreeView *tvPtr = entryPtr->tvPtr; Blt_TreeNode node; if (entryPtr->node == Blt_TreeRootNode(tvPtr->tree)) { return NULL; } node = Blt_TreeNodeParent(entryPtr->node); if (node == NULL) { return NULL; } return Blt_NodeToEntry(tvPtr, node); } static void FreeEntry(TreeView *tvPtr, TreeViewEntry *entryPtr) { Blt_HashEntry *hPtr; if (entryPtr == tvPtr->activePtr) { tvPtr->activePtr = Blt_TreeViewParentEntry(entryPtr); } if (entryPtr == tvPtr->activeButtonPtr) { tvPtr->activeButtonPtr = NULL; } if (entryPtr == tvPtr->focusPtr) { tvPtr->focusPtr = Blt_TreeViewParentEntry(entryPtr); Blt_SetFocusItem(tvPtr->bindTable, tvPtr->focusPtr, ITEM_ENTRY); } if (entryPtr == tvPtr->selAnchorPtr) { tvPtr->selMarkPtr = tvPtr->selAnchorPtr = NULL; } Blt_TreeViewDeselectEntry(tvPtr, entryPtr); Blt_TreeViewPruneSelection(tvPtr, entryPtr); Blt_DeleteBindings(tvPtr->bindTable, entryPtr); hPtr = Blt_FindHashEntry(&tvPtr->entryTable, entryPtr->node); if (hPtr != NULL) { Blt_DeleteHashEntry(&tvPtr->entryTable, hPtr); } entryPtr->node = NULL; Tcl_EventuallyFree(entryPtr, DestroyEntry); /* * Indicate that the screen layout of the hierarchy may have changed * because this node was deleted. The screen positions of the nodes * in tvPtr->visibleArr are invalidated. */ tvPtr->flags |= (TV_LAYOUT | TV_DIRTY | TV_RESORT); Blt_TreeViewEventuallyRedraw(tvPtr); } int Blt_TreeViewEntryIsSelected(TreeView *tvPtr, TreeViewEntry *entryPtr) { Blt_HashEntry *hPtr; hPtr = Blt_FindHashEntry(&tvPtr->selectTable, (char *)entryPtr); return (hPtr != NULL); } void Blt_TreeViewSelectEntry(TreeView *tvPtr, TreeViewEntry *entryPtr) { int isNew; Blt_HashEntry *hPtr; hPtr = Blt_CreateHashEntry(&tvPtr->selectTable, (char *)entryPtr, &isNew); if (isNew) { Blt_ChainLink *linkPtr; linkPtr = Blt_ChainAppend(tvPtr->selChainPtr, entryPtr); Blt_SetHashValue(hPtr, linkPtr); } } void Blt_TreeViewDeselectEntry(TreeView *tvPtr, TreeViewEntry *entryPtr) { Blt_HashEntry *hPtr; hPtr = Blt_FindHashEntry(&tvPtr->selectTable, (char *)entryPtr); if (hPtr != NULL) { Blt_ChainLink *linkPtr; linkPtr = Blt_GetHashValue(hPtr); Blt_ChainDeleteLink(tvPtr->selChainPtr, linkPtr); Blt_DeleteHashEntry(&tvPtr->selectTable, hPtr); } } char * Blt_TreeViewGetFullName( TreeView *tvPtr, TreeViewEntry *entryPtr, int checkEntryLabel, Tcl_DString *resultPtr) { Blt_TreeNode node; char **names; /* Used the stack the component names. */ char *staticSpace[64]; int level; register int i; level = Blt_TreeNodeDepth(tvPtr->tree, entryPtr->node); if (tvPtr->rootPtr->labelUid == NULL) { level--; } if (level > 64) { names = Blt_Malloc((level + 2) * sizeof(char *)); assert(names); } else { names = staticSpace; } for (i = level; i >= 0; i--) { /* Save the name of each ancestor in the name array. */ if (checkEntryLabel) { names[i] = GETLABEL(entryPtr); } else { names[i] = Blt_TreeNodeLabel(entryPtr->node); } node = Blt_TreeNodeParent(entryPtr->node); if (node != NULL) { entryPtr = Blt_NodeToEntry(tvPtr, node); } } Tcl_DStringInit(resultPtr); if (level >= 0) { if ((tvPtr->pathSep == SEPARATOR_LIST) || (tvPtr->pathSep == SEPARATOR_NONE)) { for (i = 0; i <= level; i++) { Tcl_DStringAppendElement(resultPtr, names[i]); } } else { Tcl_DStringAppend(resultPtr, names[0], -1); for (i = 1; i <= level; i++) { Tcl_DStringAppend(resultPtr, tvPtr->pathSep, -1); Tcl_DStringAppend(resultPtr, names[i], -1); } } } else { if ((tvPtr->pathSep != SEPARATOR_LIST) && (tvPtr->pathSep != SEPARATOR_NONE)) { Tcl_DStringAppend(resultPtr, tvPtr->pathSep, -1); } } if (names != staticSpace) { Blt_Free(names); } return Tcl_DStringValue(resultPtr); } int Blt_TreeViewCloseEntry(TreeView *tvPtr, TreeViewEntry *entryPtr) { char *cmd; if (entryPtr->flags & ENTRY_CLOSED) { return TCL_OK; /* Entry is already closed. */ } entryPtr->flags |= ENTRY_CLOSED; /* * Invoke the entry's "close" command, if there is one. Otherwise * try the treeview's global "close" command. */ cmd = CHOOSE(tvPtr->closeCmd, entryPtr->closeCmd); if (cmd != NULL) { Tcl_DString dString; int result; Blt_TreeViewPercentSubst(tvPtr, entryPtr, cmd, &dString); Tcl_Preserve(entryPtr); result = Tcl_GlobalEval(tvPtr->interp, Tcl_DStringValue(&dString)); Tcl_Release(entryPtr); Tcl_DStringFree(&dString); if (result != TCL_OK) { return TCL_ERROR; } } tvPtr->flags |= TV_LAYOUT; return TCL_OK; } int Blt_TreeViewOpenEntry(TreeView *tvPtr, TreeViewEntry *entryPtr) { char *cmd; if ((entryPtr->flags & ENTRY_CLOSED) == 0) { return TCL_OK; /* Entry is already open. */ } entryPtr->flags &= ~ENTRY_CLOSED; /* * If there's a "open" command proc specified for the entry, use * that instead of the more general "open" proc for the entire * treeview. */ cmd = CHOOSE(tvPtr->openCmd, entryPtr->openCmd); if (cmd != NULL) { Tcl_DString dString; int result; Blt_TreeViewPercentSubst(tvPtr, entryPtr, cmd, &dString); Tcl_Preserve(entryPtr); result = Tcl_GlobalEval(tvPtr->interp, Tcl_DStringValue(&dString)); Tcl_Release(entryPtr); Tcl_DStringFree(&dString); if (result != TCL_OK) { return TCL_ERROR; } } tvPtr->flags |= TV_LAYOUT; return TCL_OK; } /* *---------------------------------------------------------------------- * * Blt_TreeViewCreateEntry -- * * This procedure is called by the Tree object when a node is * created and inserted into the tree. It adds a new treeview * entry field to the node. * * Results: * Returns the entry. * *---------------------------------------------------------------------- */ int Blt_TreeViewCreateEntry( TreeView *tvPtr, Blt_TreeNode node, /* Node that has just been created. */ int objc, Tcl_Obj *CONST *objv, int flags) { TreeViewEntry *entryPtr; int isNew; Blt_HashEntry *hPtr; hPtr = Blt_CreateHashEntry(&tvPtr->entryTable, (char *)node, &isNew); if (isNew) { /* Create the entry structure */ entryPtr = Blt_PoolAllocItem(tvPtr->entryPool, sizeof(TreeViewEntry)); memset(entryPtr, 0, sizeof(TreeViewEntry)); entryPtr->flags = tvPtr->buttonFlags | ENTRY_CLOSED; entryPtr->tvPtr = tvPtr; entryPtr->labelUid = NULL; entryPtr->node = node; Blt_SetHashValue(hPtr, entryPtr); } else { entryPtr = Blt_GetHashValue(hPtr); } if (Blt_TreeViewConfigureEntry(tvPtr, entryPtr, objc, objv, flags) != TCL_OK) { FreeEntry(tvPtr, entryPtr); return TCL_ERROR; /* Error configuring the entry. */ } tvPtr->flags |= (TV_LAYOUT | TV_DIRTY | TV_RESORT); Blt_TreeViewEventuallyRedraw(tvPtr); return TCL_OK; } /*ARGSUSED*/ static int CreateApplyProc( Blt_TreeNode node, /* Node that has just been created. */ ClientData clientData, int order) /* Not used. */ { TreeView *tvPtr = clientData; return Blt_TreeViewCreateEntry(tvPtr, node, 0, NULL, 0); } /*ARGSUSED*/ static int DeleteApplyProc( Blt_TreeNode node, ClientData clientData, int order) /* Not used. */ { TreeView *tvPtr = clientData; /* * Unsetting the tree value triggers a call back to destroy the entry * and also releases the Tcl_Obj that contains it. */ return Blt_TreeUnsetValueByKey(tvPtr->interp, tvPtr->tree, node, tvPtr->treeColumn.key); } static int TreeEventProc(ClientData clientData, Blt_TreeNotifyEvent *eventPtr) { Blt_TreeNode node; TreeView *tvPtr = clientData; node = Blt_TreeGetNode(eventPtr->tree, eventPtr->inode); switch (eventPtr->type) { case TREE_NOTIFY_CREATE: return Blt_TreeViewCreateEntry(tvPtr, node, 0, NULL, 0); case TREE_NOTIFY_DELETE: /* * Deleting the tree node triggers a call back to free the * treeview entry that is associated with it. */ if (node != NULL) { FreeEntry(tvPtr, Blt_NodeToEntry(tvPtr, node)); } break; case TREE_NOTIFY_RELABEL: if (node != NULL) { TreeViewEntry *entryPtr; entryPtr = Blt_NodeToEntry(tvPtr, node); entryPtr->flags |= ENTRY_DIRTY; } /*FALLTHRU*/ case TREE_NOTIFY_MOVE: case TREE_NOTIFY_SORT: Blt_TreeViewEventuallyRedraw(tvPtr); tvPtr->flags |= (TV_LAYOUT | TV_DIRTY); break; default: /* empty */ break; } return TCL_OK; } TreeViewValue * Blt_TreeViewFindValue(TreeViewEntry *entryPtr, TreeViewColumn *columnPtr) { register TreeViewValue *valuePtr; for (valuePtr = entryPtr->values; valuePtr != NULL; valuePtr = valuePtr->nextPtr) { if (valuePtr->columnPtr == columnPtr) { return valuePtr; } } return NULL; } void Blt_TreeViewAddValue(TreeViewEntry *entryPtr, TreeViewColumn *columnPtr) { if (Blt_TreeViewFindValue(entryPtr, columnPtr) == NULL) { Tcl_Obj *objPtr; if (Blt_TreeViewGetData(entryPtr, columnPtr->key, &objPtr) == TCL_OK) { TreeViewValue *valuePtr; /* Add a new value only if a data entry exists. */ valuePtr = Blt_PoolAllocItem(entryPtr->tvPtr->valuePool, sizeof(TreeViewValue)); valuePtr->columnPtr = columnPtr; valuePtr->nextPtr = entryPtr->values; valuePtr->textPtr = NULL; valuePtr->width = valuePtr->height = 0; valuePtr->stylePtr = NULL; valuePtr->string = NULL; entryPtr->values = valuePtr; } } entryPtr->tvPtr->flags |= (TV_LAYOUT | TV_DIRTY | TV_RESORT); entryPtr->flags |= ENTRY_DIRTY; } /* *---------------------------------------------------------------------- * * TreeTraceProc -- * * Mirrors the individual values of the tree object (they must * also be listed in the widget's columns chain). This is because * it must track and save the sizes of each individual data * entry, rather than re-computing all the sizes each time the * widget is redrawn. * * This procedure is called by the Tree object when a node data * value is set unset. * * Results: * Returns TCL_OK. * *---------------------------------------------------------------------- */ /*ARGSUSED*/ static int TreeTraceProc( ClientData clientData, Tcl_Interp *interp, Blt_TreeNode node, /* Node that has just been updated. */ Blt_TreeKey key, /* Key of value that's been updated. */ unsigned int flags) { Blt_HashEntry *hPtr; TreeView *tvPtr = clientData; TreeViewColumn *columnPtr; TreeViewEntry *entryPtr; TreeViewValue *valuePtr, *nextPtr, *lastPtr; hPtr = Blt_FindHashEntry(&tvPtr->entryTable, (char *)node); if (hPtr == NULL) { return TCL_OK; /* Not a node that we're interested in. */ } entryPtr = Blt_GetHashValue(hPtr); flags &= TREE_TRACE_WRITE | TREE_TRACE_READ | TREE_TRACE_UNSET; switch (flags) { case TREE_TRACE_WRITE: hPtr = Blt_FindHashEntry(&tvPtr->columnTable, key); if (hPtr == NULL) { return TCL_OK; /* Data value isn't used by widget. */ } columnPtr = Blt_GetHashValue(hPtr); if (columnPtr != &tvPtr->treeColumn) { Blt_TreeViewAddValue(entryPtr, columnPtr); } entryPtr->flags |= ENTRY_DIRTY; Blt_TreeViewEventuallyRedraw(tvPtr); tvPtr->flags |= (TV_LAYOUT | TV_DIRTY | TV_RESORT); break; case TREE_TRACE_UNSET: lastPtr = NULL; for(valuePtr = entryPtr->values; valuePtr != NULL; valuePtr = nextPtr) { nextPtr = valuePtr->nextPtr; if (valuePtr->columnPtr->key == key) { Blt_TreeViewDestroyValue(tvPtr, valuePtr); if (lastPtr == NULL) { entryPtr->values = nextPtr; } else { lastPtr->nextPtr = nextPtr; } entryPtr->flags |= ENTRY_DIRTY; Blt_TreeViewEventuallyRedraw(tvPtr); tvPtr->flags |= (TV_LAYOUT | TV_DIRTY | TV_RESORT); break; } lastPtr = valuePtr; } break; default: break; } return TCL_OK; } static void GetValueSize( TreeView *tvPtr, TreeViewEntry *entryPtr, TreeViewValue *valuePtr, TreeViewStyle *stylePtr) { TreeViewColumn *columnPtr; columnPtr = valuePtr->columnPtr; valuePtr->width = valuePtr->height = 0; if (entryPtr->flags & ENTRY_DIRTY) { /* Reparse the data. */ char *string; TreeViewIcon icon; Tcl_Obj *valueObjPtr; TreeViewStyle *newStylePtr; icon = NULL; newStylePtr = NULL; if (Blt_TreeViewGetData(entryPtr, valuePtr->columnPtr->key, &valueObjPtr) != TCL_OK) { return; /* No data ??? */ } string = Tcl_GetString(valueObjPtr); valuePtr->string = string; if (string[0] == '@') { /* Name of style or Tk image. */ int objc; Tcl_Obj **objv; if ((Tcl_ListObjGetElements(tvPtr->interp, valueObjPtr, &objc, &objv) != TCL_OK) || (objc < 1) || (objc > 2)) { goto handleString; } if (objc > 0) { char *name; name = Tcl_GetString(objv[0]) + 1; if (Blt_TreeViewGetStyle((Tcl_Interp *)NULL, tvPtr, name, &newStylePtr) != TCL_OK) { icon = Blt_TreeViewGetIcon(tvPtr, name); if (icon == NULL) { goto handleString; } /* Create a new style by the name of the image. */ newStylePtr = Blt_TreeViewCreateStyle((Tcl_Interp *)NULL, tvPtr, STYLE_TEXTBOX, name); assert(newStylePtr); Blt_TreeViewUpdateStyleGCs(tvPtr, newStylePtr); } } if (valuePtr->stylePtr != NULL) { Blt_TreeViewFreeStyle(tvPtr, valuePtr->stylePtr); } if (icon != NULL) { Blt_TreeViewSetStyleIcon(tvPtr, newStylePtr, icon); } valuePtr->stylePtr = newStylePtr; valuePtr->string = (objc > 1) ? Tcl_GetString(objv[1]) : NULL; } } handleString: stylePtr = CHOOSE(columnPtr->stylePtr, valuePtr->stylePtr); /* Measure the text string. */ (*stylePtr->classPtr->measProc)(tvPtr, stylePtr, valuePtr); } static void GetRowExtents( TreeView *tvPtr, TreeViewEntry *entryPtr, int *widthPtr, int *heightPtr) { TreeViewValue *valuePtr; int valueWidth; /* Width of individual value. */ int width, height; /* Compute dimensions of row. */ TreeViewStyle *stylePtr; width = height = 0; for (valuePtr = entryPtr->values; valuePtr != NULL; valuePtr = valuePtr->nextPtr) { stylePtr = valuePtr->stylePtr; if (stylePtr == NULL) { stylePtr = valuePtr->columnPtr->stylePtr; } if ((entryPtr->flags & ENTRY_DIRTY) || (stylePtr->flags & STYLE_DIRTY)) { GetValueSize(tvPtr, entryPtr, valuePtr, stylePtr); } if (valuePtr->height > height) { height = valuePtr->height; } valueWidth = valuePtr->width; width += valueWidth; } *widthPtr = width; *heightPtr = height; } /* *---------------------------------------------------------------------- * * Blt_TreeViewNearestEntry -- * * 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*/ TreeViewEntry * Blt_TreeViewNearestEntry(TreeView *tvPtr, int x, int y, int selectOne) { TreeViewEntry *lastPtr, *entryPtr; register TreeViewEntry **p; /* * We implicitly can pick only visible entries. So make sure that * the tree exists. */ if (tvPtr->nVisible == 0) { return NULL; } if (y < tvPtr->titleHeight) { return (selectOne) ? tvPtr->visibleArr[0] : NULL; } /* * Since the entry positions were previously computed in world * coordinates, convert Y-coordinate from screen to world * coordinates too. */ y = WORLDY(tvPtr, y); lastPtr = tvPtr->visibleArr[0]; for (p = tvPtr->visibleArr; *p != NULL; p++) { entryPtr = *p; /* * If the start of the next entry starts beyond the point, * use the last entry. */ if (entryPtr->worldY > y) { return (selectOne) ? entryPtr : NULL; } if (y < (entryPtr->worldY + entryPtr->height)) { return entryPtr; /* Found it. */ } lastPtr = entryPtr; } return (selectOne) ? lastPtr : NULL; } ClientData Blt_TreeViewEntryTag(TreeView *tvPtr, CONST char *string) { Blt_HashEntry *hPtr; int isNew; /* Not used. */ hPtr = Blt_CreateHashEntry(&tvPtr->entryTagTable, string, &isNew); return Blt_GetHashKey(&tvPtr->entryTagTable, hPtr); } ClientData Blt_TreeViewButtonTag(TreeView *tvPtr, CONST char *string) { Blt_HashEntry *hPtr; int isNew; /* Not used. */ hPtr = Blt_CreateHashEntry(&tvPtr->buttonTagTable, string, &isNew); return Blt_GetHashKey(&tvPtr->buttonTagTable, hPtr); } ClientData Blt_TreeViewColumnTag(TreeView *tvPtr, CONST char *string) { Blt_HashEntry *hPtr; int isNew; /* Not used. */ hPtr = Blt_CreateHashEntry(&tvPtr->columnTagTable, string, &isNew); return Blt_GetHashKey(&tvPtr->columnTagTable, hPtr); } ClientData Blt_TreeViewStyleTag(TreeView *tvPtr, CONST char *string) { Blt_HashEntry *hPtr; int isNew; /* Not used. */ hPtr = Blt_CreateHashEntry(&tvPtr->styleTagTable, string, &isNew); return Blt_GetHashKey(&tvPtr->styleTagTable, hPtr); } static void GetTags( Blt_BindTable table, ClientData object, /* Object picked. */ ClientData context, /* Context of object. */ Blt_List ids) /* (out) List of binding ids to be * applied for this object. */ { TreeView *tvPtr; int nNames; char **names; register char **p; tvPtr = Blt_GetBindingData(table); if (context == (ClientData)ITEM_ENTRY_BUTTON) { TreeViewEntry *entryPtr = object; Blt_ListAppend(ids, Blt_TreeViewButtonTag(tvPtr, "Button"), 0); if (entryPtr->tagsUid != NULL) { if (Tcl_SplitList((Tcl_Interp *)NULL, entryPtr->tagsUid, &nNames, &names) == TCL_OK) { for (p = names; *p != NULL; p++) { Blt_ListAppend(ids, Blt_TreeViewButtonTag(tvPtr, *p), 0); } Blt_Free(names); } } else { Blt_ListAppend(ids, Blt_TreeViewButtonTag(tvPtr, "Entry"), 0); Blt_ListAppend(ids, Blt_TreeViewButtonTag(tvPtr, "all"), 0); } } else if (context == (ClientData)ITEM_COLUMN_TITLE) { TreeViewColumn *columnPtr = object; Blt_ListAppend(ids, (char *)columnPtr, 0); if (columnPtr->tagsUid != NULL) { if (Tcl_SplitList((Tcl_Interp *)NULL, columnPtr->tagsUid, &nNames, &names) == TCL_OK) { for (p = names; *p != NULL; p++) { Blt_ListAppend(ids, Blt_TreeViewColumnTag(tvPtr, *p), 0); } Blt_Free(names); } } } else if (context == ITEM_COLUMN_RULE) { Blt_ListAppend(ids, Blt_TreeViewColumnTag(tvPtr, "Rule"), 0); } else { TreeViewEntry *entryPtr = object; Blt_ListAppend(ids, (char *)entryPtr, 0); if (entryPtr->tagsUid != NULL) { if (Tcl_SplitList((Tcl_Interp *)NULL, entryPtr->tagsUid, &nNames, &names) == TCL_OK) { for (p = names; *p != NULL; p++) { Blt_ListAppend(ids, Blt_TreeViewEntryTag(tvPtr, *p), 0); } Blt_Free(names); } } else if (context == ITEM_ENTRY){ Blt_ListAppend(ids, Blt_TreeViewEntryTag(tvPtr, "Entry"), 0); Blt_ListAppend(ids, Blt_TreeViewEntryTag(tvPtr, "all"), 0); } else { TreeViewValue *valuePtr = context; if (valuePtr != NULL) { TreeViewStyle *stylePtr = valuePtr->stylePtr; if (stylePtr == NULL) { stylePtr = valuePtr->columnPtr->stylePtr; } Blt_ListAppend(ids, Blt_TreeViewEntryTag(tvPtr, stylePtr->name), 0); Blt_ListAppend(ids, Blt_TreeViewEntryTag(tvPtr, valuePtr->columnPtr->key), 0); Blt_ListAppend(ids, Blt_TreeViewEntryTag(tvPtr, stylePtr->classPtr->className), 0); #ifndef notdef Blt_ListAppend(ids, Blt_TreeViewEntryTag(tvPtr, "Entry"), 0); Blt_ListAppend(ids, Blt_TreeViewEntryTag(tvPtr, "all"), 0); #endif } } } } /*ARGSUSED*/ static ClientData PickItem( ClientData clientData, int x, int y, /* Screen coordinates of the test point. */ ClientData *contextPtr) /* (out) Context of item selected: should * be ITEM_ENTRY, ITEM_ENTRY_BUTTON, * ITEM_COLUMN_TITLE, * ITEM_COLUMN_RULE, or * ITEM_STYLE. */ { TreeView *tvPtr = clientData; TreeViewEntry *entryPtr; TreeViewColumn *columnPtr; if (contextPtr != NULL) { *contextPtr = NULL; } if (tvPtr->flags & TV_DIRTY) { /* Can't trust the selected entry if nodes have been added or * deleted. So recompute the layout. */ if (tvPtr->flags & TV_LAYOUT) { Blt_TreeViewComputeLayout(tvPtr); } ComputeVisibleEntries(tvPtr); } columnPtr = Blt_TreeViewNearestColumn(tvPtr, x, y, contextPtr); if ((*contextPtr != NULL) && (tvPtr->flags & TV_SHOW_COLUMN_TITLES)) { return columnPtr; } if (tvPtr->nVisible == 0) { return NULL; } entryPtr = Blt_TreeViewNearestEntry(tvPtr, x, y, FALSE); if (entryPtr == NULL) { return NULL; } x = WORLDX(tvPtr, x); y = WORLDY(tvPtr, y); if (contextPtr != NULL) { *contextPtr = ITEM_ENTRY; if (columnPtr != NULL) { TreeViewValue *valuePtr; valuePtr = Blt_TreeViewFindValue(entryPtr, columnPtr); if (valuePtr != NULL) { TreeViewStyle *stylePtr; stylePtr = valuePtr->stylePtr; if (stylePtr == NULL) { stylePtr = valuePtr->columnPtr->stylePtr; } if ((stylePtr->classPtr->pickProc == NULL) || ((*stylePtr->classPtr->pickProc)(entryPtr, valuePtr, stylePtr, x, y))) { *contextPtr = valuePtr; } } } if (entryPtr->flags & ENTRY_HAS_BUTTON) { TreeViewButton *buttonPtr = &tvPtr->button; int left, right, top, bottom; 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; if ((x >= left) && (x < right) && (y >= top) && (y < bottom)) { *contextPtr = (ClientData)ITEM_ENTRY_BUTTON; } } } return entryPtr; } static void GetEntryExtents(TreeView *tvPtr, TreeViewEntry *entryPtr) { Tk_Font font; TreeViewIcon *icons; char *label; int entryWidth, entryHeight; int width, height; /* * FIXME: Use of DIRTY flag inconsistent. When does it * mean "dirty entry"? When does it mean "dirty column"? * Does it matter? probably */ if ((entryPtr->flags & ENTRY_DIRTY) || (tvPtr->flags & TV_UPDATE)) { Tk_FontMetrics fontMetrics; entryPtr->iconWidth = entryPtr->iconHeight = 0; icons = CHOOSE(tvPtr->icons, entryPtr->icons); if (icons != NULL) { register int i; for (i = 0; i < 2; i++) { if (icons[i] == NULL) { break; } if (entryPtr->iconWidth < TreeViewIconWidth(icons[i])) { entryPtr->iconWidth = TreeViewIconWidth(icons[i]); } if (entryPtr->iconHeight < TreeViewIconHeight(icons[i])) { entryPtr->iconHeight = TreeViewIconHeight(icons[i]); } } } if ((icons == NULL) || (icons[0] == NULL)) { entryPtr->iconWidth = DEF_ICON_WIDTH; entryPtr->iconHeight = DEF_ICON_HEIGHT; } entryPtr->iconWidth += 2 * ICON_PADX; entryPtr->iconHeight += 2 * ICON_PADY; entryHeight = MAX(entryPtr->iconHeight, tvPtr->button.height); font = entryPtr->font; if (font == NULL) { font = Blt_TreeViewGetStyleFont(tvPtr, tvPtr->treeColumn.stylePtr); } if (entryPtr->fullName != NULL) { Blt_Free(entryPtr->fullName); entryPtr->fullName = NULL; } if (entryPtr->textPtr != NULL) { Blt_Free(entryPtr->textPtr); entryPtr->textPtr = NULL; } Tk_GetFontMetrics(font, &fontMetrics); entryPtr->lineHeight = fontMetrics.linespace; entryPtr->lineHeight += 2 * (FOCUS_WIDTH + LABEL_PADY + tvPtr->selBorderWidth) + tvPtr->leader; label = GETLABEL(entryPtr); if (label[0] == '\0') { width = height = entryPtr->lineHeight; } else { TextStyle ts; Blt_InitTextStyle(&ts); ts.shadow.offset = entryPtr->shadow.offset; ts.font = font; if (tvPtr->flatView) { Tcl_DString dString; Blt_TreeViewGetFullName(tvPtr, entryPtr, TRUE, &dString); entryPtr->fullName = Blt_Strdup(Tcl_DStringValue(&dString)); Tcl_DStringFree(&dString); entryPtr->textPtr = Blt_GetTextLayout(entryPtr->fullName, &ts); } else { entryPtr->textPtr = Blt_GetTextLayout(label, &ts); } width = entryPtr->textPtr->width; height = entryPtr->textPtr->height; } width += 2 * (FOCUS_WIDTH + LABEL_PADX + tvPtr->selBorderWidth); height += 2 * (FOCUS_WIDTH + LABEL_PADY + tvPtr->selBorderWidth); width = ODD(width); if (entryPtr->reqHeight > height) { height = entryPtr->reqHeight; } height = ODD(height); entryWidth = width; if (entryHeight < height) { entryHeight = height; } entryPtr->labelWidth = width; entryPtr->labelHeight = height; } else { entryHeight = entryPtr->labelHeight; entryWidth = entryPtr->labelWidth; } /* * Find the maximum height of the data value entries. This also has * the side effect of contributing the maximum width of the column. */ GetRowExtents(tvPtr, entryPtr, &width, &height); if (entryHeight < height) { entryHeight = height; } entryPtr->width = entryWidth + COLUMN_PAD; entryPtr->height = entryHeight + tvPtr->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++; } entryPtr->flags &= ~ENTRY_DIRTY; } /* * TreeView Procedures */ /* * ---------------------------------------------------------------------- * * CreateTreeView -- * * ---------------------------------------------------------------------- */ static TreeView * CreateTreeView( Tcl_Interp *interp, Tcl_Obj *objPtr, /* Name of the new widget. */ CONST char *className) { Tk_Window tkwin; TreeView *tvPtr; char *name; Tcl_DString dString; int result; name = Tcl_GetString(objPtr); tkwin = Tk_CreateWindowFromPath(interp, Tk_MainWindow(interp), name, (char *)NULL); if (tkwin == NULL) { return NULL; } Tk_SetClass(tkwin, (char *)className); tvPtr = Blt_Calloc(1, sizeof(TreeView)); assert(tvPtr); tvPtr->tkwin = tkwin; tvPtr->display = Tk_Display(tkwin); tvPtr->interp = interp; tvPtr->flags = (TV_HIDE_ROOT | TV_SHOW_COLUMN_TITLES | TV_DIRTY | TV_LAYOUT | TV_RESORT); tvPtr->leader = 0; tvPtr->dashes = 1; tvPtr->highlightWidth = 2; tvPtr->selBorderWidth = 1; tvPtr->borderWidth = 2; tvPtr->relief = TK_RELIEF_SUNKEN; tvPtr->selRelief = TK_RELIEF_FLAT; tvPtr->scrollMode = BLT_SCROLL_MODE_HIERBOX; tvPtr->selectMode = SELECT_MODE_SINGLE; tvPtr->button.closeRelief = tvPtr->button.openRelief = TK_RELIEF_SOLID; tvPtr->reqWidth = 200; tvPtr->reqHeight = 400; tvPtr->xScrollUnits = tvPtr->yScrollUnits = 20; tvPtr->lineWidth = 1; tvPtr->button.borderWidth = 1; tvPtr->colChainPtr = Blt_ChainCreate(); tvPtr->buttonFlags = BUTTON_AUTO; tvPtr->selChainPtr = Blt_ChainCreate(); Blt_InitHashTableWithPool(&tvPtr->entryTable, BLT_ONE_WORD_KEYS); Blt_InitHashTable(&tvPtr->columnTable, BLT_ONE_WORD_KEYS); Blt_InitHashTable(&tvPtr->iconTable, BLT_STRING_KEYS); Blt_InitHashTable(&tvPtr->selectTable, BLT_ONE_WORD_KEYS); Blt_InitHashTable(&tvPtr->uidTable, BLT_STRING_KEYS); Blt_InitHashTable(&tvPtr->styleTable, BLT_STRING_KEYS); tvPtr->bindTable = Blt_CreateBindingTable(interp, tkwin, tvPtr, PickItem, GetTags); Blt_InitHashTable(&tvPtr->entryTagTable, BLT_STRING_KEYS); Blt_InitHashTable(&tvPtr->columnTagTable, BLT_STRING_KEYS); Blt_InitHashTable(&tvPtr->buttonTagTable, BLT_STRING_KEYS); Blt_InitHashTable(&tvPtr->styleTagTable, BLT_STRING_KEYS); tvPtr->entryPool = Blt_PoolCreate(BLT_FIXED_SIZE_ITEMS); tvPtr->valuePool = Blt_PoolCreate(BLT_FIXED_SIZE_ITEMS); #if (TK_MAJOR_VERSION > 4) Blt_SetWindowInstanceData(tkwin, tvPtr); #endif tvPtr->cmdToken = Tcl_CreateObjCommand(interp, Tk_PathName(tvPtr->tkwin), Blt_TreeViewWidgetInstCmd, tvPtr, WidgetInstCmdDeleteProc); #ifdef ITCL_NAMESPACES Itk_SetWidgetCommand(tvPtr->tkwin, tvPtr->cmdToken); #endif Tk_CreateSelHandler(tvPtr->tkwin, XA_PRIMARY, XA_STRING, SelectionProc, tvPtr, XA_STRING); Tk_CreateEventHandler(tvPtr->tkwin, ExposureMask | StructureNotifyMask | FocusChangeMask, TreeViewEventProc, tvPtr); /* * Create a default style. This must exist before we can create * the treeview column. */ tvPtr->stylePtr = Blt_TreeViewCreateStyle(interp, tvPtr, STYLE_TEXTBOX, "text"); if (tvPtr->stylePtr == NULL) { return NULL; } /* Create a default column to display the view of the tree. */ Tcl_DStringInit(&dString); Tcl_DStringAppend(&dString, "BLT TreeView ", -1); Tcl_DStringAppend(&dString, Tk_PathName(tvPtr->tkwin), -1); result = Blt_TreeViewCreateColumn(tvPtr, &tvPtr->treeColumn, Tcl_DStringValue(&dString), ""); Tcl_DStringFree(&dString); if (result != TCL_OK) { return NULL; } Blt_ChainAppend(tvPtr->colChainPtr, &tvPtr->treeColumn); return tvPtr; } /* * ---------------------------------------------------------------------- * * DestroyTreeView -- * * This procedure is invoked by Tcl_EventuallyFree or Tcl_Release * to clean up the internal structure of a TreeView 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 DestroyTreeView(DestroyData dataPtr) /* Pointer to the widget record. */ { Blt_HashEntry *hPtr; Blt_HashSearch cursor; TreeView *tvPtr = (TreeView *)dataPtr; TreeViewButton *buttonPtr; TreeViewEntry *entryPtr; TreeViewStyle *stylePtr; Blt_TreeDeleteEventHandler(tvPtr->tree, TREE_NOTIFY_ALL, TreeEventProc, tvPtr); for (hPtr = Blt_FirstHashEntry(&tvPtr->entryTable, &cursor); hPtr != NULL; hPtr = Blt_NextHashEntry(&cursor)) { entryPtr = Blt_GetHashValue(hPtr); DestroyEntry((ClientData)entryPtr); } bltTreeViewTreeOption.clientData = tvPtr; bltTreeViewIconsOption.clientData = tvPtr; Blt_FreeObjOptions(bltTreeViewSpecs, (char *)tvPtr, tvPtr->display, 0); if (tvPtr->tkwin != NULL) { Tk_DeleteSelHandler(tvPtr->tkwin, XA_PRIMARY, XA_STRING); } if (tvPtr->lineGC != NULL) { Tk_FreeGC(tvPtr->display, tvPtr->lineGC); } if (tvPtr->focusGC != NULL) { Blt_FreePrivateGC(tvPtr->display, tvPtr->focusGC); } if (tvPtr->visibleArr != NULL) { Blt_Free(tvPtr->visibleArr); } if (tvPtr->flatArr != NULL) { Blt_Free(tvPtr->flatArr); } if (tvPtr->levelInfo != NULL) { Blt_Free(tvPtr->levelInfo); } buttonPtr = &tvPtr->button; if (buttonPtr->activeGC != NULL) { Tk_FreeGC(tvPtr->display, buttonPtr->activeGC); } if (buttonPtr->normalGC != NULL) { Tk_FreeGC(tvPtr->display, buttonPtr->normalGC); } if (tvPtr->stylePtr != NULL) { Blt_TreeViewFreeStyle(tvPtr, tvPtr->stylePtr); } Blt_TreeViewDestroyColumns(tvPtr); Blt_DestroyBindingTable(tvPtr->bindTable); Blt_ChainDestroy(tvPtr->selChainPtr); Blt_DeleteHashTable(&tvPtr->entryTagTable); Blt_DeleteHashTable(&tvPtr->columnTagTable); Blt_DeleteHashTable(&tvPtr->buttonTagTable); Blt_DeleteHashTable(&tvPtr->styleTagTable); for (hPtr = Blt_FirstHashEntry(&tvPtr->styleTable, &cursor); hPtr != NULL; hPtr = Blt_NextHashEntry(&cursor)) { stylePtr = Blt_GetHashValue(hPtr); /* stylePtr->refCount = 0; */ stylePtr->flags &= ~STYLE_USER; Blt_TreeViewFreeStyle(tvPtr, stylePtr); } if (tvPtr->comboWin != NULL) { Tk_DestroyWindow(tvPtr->comboWin); } Blt_DeleteHashTable(&tvPtr->styleTable); Blt_DeleteHashTable(&tvPtr->selectTable); Blt_DeleteHashTable(&tvPtr->uidTable); Blt_DeleteHashTable(&tvPtr->entryTable); Blt_PoolDestroy(tvPtr->entryPool); Blt_PoolDestroy(tvPtr->valuePool); DumpIconTable(tvPtr); Blt_Free(tvPtr); } /* * -------------------------------------------------------------- * * TreeViewEventProc -- * * This procedure is invoked by the Tk dispatcher for various * events on treeview widgets. * * Results: * None. * * Side effects: * When the window gets deleted, internal structures get * cleaned up. When it gets exposed, it is redisplayed. * * -------------------------------------------------------------- */ static void TreeViewEventProc( ClientData clientData, /* Information about window. */ XEvent *eventPtr) /* Information about event. */ { TreeView *tvPtr = clientData; if (eventPtr->type == Expose) { if (eventPtr->xexpose.count == 0) { Blt_TreeViewEventuallyRedraw(tvPtr); Blt_PickCurrentItem(tvPtr->bindTable); } } else if (eventPtr->type == ConfigureNotify) { tvPtr->flags |= (TV_LAYOUT | TV_SCROLL); Blt_TreeViewEventuallyRedraw(tvPtr); } else if ((eventPtr->type == FocusIn) || (eventPtr->type == FocusOut)) { if (eventPtr->xfocus.detail != NotifyInferior) { if (eventPtr->type == FocusIn) { tvPtr->flags |= TV_FOCUS; } else { tvPtr->flags &= ~TV_FOCUS; } Blt_TreeViewEventuallyRedraw(tvPtr); } } else if (eventPtr->type == DestroyNotify) { if (tvPtr->tkwin != NULL) { tvPtr->tkwin = NULL; Tcl_DeleteCommandFromToken(tvPtr->interp, tvPtr->cmdToken); } if (tvPtr->flags & TV_REDRAW) { Tcl_CancelIdleCall(DisplayTreeView, tvPtr); } if (tvPtr->flags & TV_SELECT_PENDING) { Tcl_CancelIdleCall(Blt_TreeViewSelectCmdProc, tvPtr); } Tcl_EventuallyFree(tvPtr, DestroyTreeView); } } /* 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 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. */ { Tcl_DString dString; TreeView *tvPtr = clientData; TreeViewEntry *entryPtr; int size; if ((tvPtr->flags & TV_SELECT_EXPORT) == 0) { return -1; } /* * Retrieve the names of the selected entries. */ Tcl_DStringInit(&dString); if (tvPtr->flags & TV_SELECT_SORTED) { Blt_ChainLink *linkPtr; for (linkPtr = Blt_ChainFirstLink(tvPtr->selChainPtr); linkPtr != NULL; linkPtr = Blt_ChainNextLink(linkPtr)) { entryPtr = Blt_ChainGetValue(linkPtr); Tcl_DStringAppend(&dString, GETLABEL(entryPtr), -1); Tcl_DStringAppend(&dString, "\n", -1); } } else { for (entryPtr = tvPtr->rootPtr; entryPtr != NULL; entryPtr = Blt_TreeViewNextEntry(entryPtr, ENTRY_MASK)) { if (Blt_TreeViewEntryIsSelected(tvPtr, entryPtr)) { Tcl_DStringAppend(&dString, GETLABEL(entryPtr), -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; } /* *---------------------------------------------------------------------- * * WidgetInstCmdDeleteProc -- * * 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 WidgetInstCmdDeleteProc(ClientData clientData) { TreeView *tvPtr = 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 (tvPtr->tkwin != NULL) { Tk_Window tkwin; tkwin = tvPtr->tkwin; tvPtr->tkwin = NULL; Tk_DestroyWindow(tkwin); #ifdef ITCL_NAMESPACES Itk_SetWidgetCommand(tkwin, (Tcl_Command) NULL); #endif /* ITCL_NAMESPACES */ } } /* * ---------------------------------------------------------------------- * * Blt_TreeViewUpdateWidget -- * * Updates the GCs and other information associated with the * treeview 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 tvPtr; old resources get freed, if there * were any. The widget is redisplayed. * * ---------------------------------------------------------------------- */ int Blt_TreeViewUpdateWidget(Tcl_Interp *interp, TreeView *tvPtr) { GC newGC; XGCValues gcValues; int setupTree; unsigned long gcMask; /* * GC for dotted vertical line. */ gcMask = (GCForeground | GCLineWidth); gcValues.foreground = tvPtr->lineColor->pixel; gcValues.line_width = tvPtr->lineWidth; if (tvPtr->dashes > 0) { gcMask |= (GCLineStyle | GCDashList); gcValues.line_style = LineOnOffDash; gcValues.dashes = tvPtr->dashes; } newGC = Tk_GetGC(tvPtr->tkwin, gcMask, &gcValues); if (tvPtr->lineGC != NULL) { Tk_FreeGC(tvPtr->display, tvPtr->lineGC); } tvPtr->lineGC = newGC; /* * GC for active label. Dashed outline. */ gcMask = GCForeground | GCLineStyle; gcValues.foreground = tvPtr->focusColor->pixel; gcValues.line_style = (LineIsDashed(tvPtr->focusDashes)) ? LineOnOffDash : LineSolid; newGC = Blt_GetPrivateGC(tvPtr->tkwin, gcMask, &gcValues); if (LineIsDashed(tvPtr->focusDashes)) { tvPtr->focusDashes.offset = 2; Blt_SetDashes(tvPtr->display, newGC, &tvPtr->focusDashes); } if (tvPtr->focusGC != NULL) { Blt_FreePrivateGC(tvPtr->display, tvPtr->focusGC); } tvPtr->focusGC = newGC; Blt_TreeViewConfigureButtons(tvPtr); tvPtr->inset = tvPtr->highlightWidth + tvPtr->borderWidth + INSET_PAD; setupTree = FALSE; /* * If no tree object was named, allocate a new one. The name will * be the same as the widget pathname. */ if (tvPtr->tree == NULL) { Blt_Tree token; char *string; string = Tk_PathName(tvPtr->tkwin); if (Blt_TreeCreate(interp, string, &token) != TCL_OK) { return TCL_ERROR; } tvPtr->tree = token; setupTree = TRUE; } /* * If the tree object was changed, we need to setup the new one. */ if (Blt_ObjConfigModified(bltTreeViewSpecs, "-tree", (char *)NULL)) { setupTree = TRUE; } /* * These options change the layout of the box. Mark the widget for update. */ if (Blt_ObjConfigModified(bltTreeViewSpecs, "-font", "-linespacing", "-*width", "-height", "-hide*", "-tree", "-flat", (char *)NULL)) { tvPtr->flags |= (TV_LAYOUT | TV_SCROLL); } /* * If the tree view was changed, mark all the nodes dirty (we'll * be switching back to either the full path name or the label) * and free the array representing the flattened view of the tree. */ if (Blt_ObjConfigModified(bltTreeViewSpecs, "-hideleaves", "-flat", (char *)NULL)) { TreeViewEntry *entryPtr; tvPtr->flags |= (TV_DIRTY | TV_RESORT); /* Mark all entries dirty. */ for (entryPtr = tvPtr->rootPtr; entryPtr != NULL; entryPtr = Blt_TreeViewNextEntry(entryPtr, 0)) { entryPtr->flags |= ENTRY_DIRTY; } if ((!tvPtr->flatView) && (tvPtr->flatArr != NULL)) { Blt_Free(tvPtr->flatArr); tvPtr->flatArr = NULL; } } if ((tvPtr->reqHeight != Tk_ReqHeight(tvPtr->tkwin)) || (tvPtr->reqWidth != Tk_ReqWidth(tvPtr->tkwin))) { Tk_GeometryRequest(tvPtr->tkwin, tvPtr->reqWidth, tvPtr->reqHeight); } if (setupTree) { Blt_TreeNode root; Blt_TreeCreateEventHandler(tvPtr->tree, TREE_NOTIFY_ALL, TreeEventProc, tvPtr); TraceColumns(tvPtr); root = Blt_TreeRootNode(tvPtr->tree); /* Automatically add view-entry values to the new tree. */ Blt_TreeApply(root, CreateApplyProc, tvPtr); tvPtr->focusPtr = tvPtr->rootPtr = Blt_NodeToEntry(tvPtr, root); tvPtr->selMarkPtr = tvPtr->selAnchorPtr = NULL; Blt_SetFocusItem(tvPtr->bindTable, tvPtr->rootPtr, ITEM_ENTRY); /* Automatically open the root node. */ if (Blt_TreeViewOpenEntry(tvPtr, tvPtr->rootPtr) != TCL_OK) { return TCL_ERROR; } if (!(tvPtr->flags & TV_NEW_TAGS)) { Blt_Tree tree; if (Blt_TreeCmdGetToken(interp, Blt_TreeName(tvPtr->tree), &tree) == TCL_OK) { Blt_TreeShareTagTable(tree, tvPtr->tree); } } } if (Blt_ObjConfigModified(bltTreeViewSpecs, "-font", "-color", (char *)NULL)) { Blt_TreeViewUpdateColumnGCs(tvPtr, &tvPtr->treeColumn); } Blt_TreeViewEventuallyRedraw(tvPtr); return TCL_OK; } /* * ---------------------------------------------------------------------- * * ResetCoordinates -- * * Determines the maximum height of all visible entries. * * 1. Sets the worldY coordinate for all mapped/open entries. * 2. Determines if entry needs a button. * 3. Collects the minimum height of open/mapped entries. (Do for all * entries upon insert). * 4. Figures out horizontal extent of each entry (will be width of * tree view column). * 5. Collects maximum icon size for each level. * 6. The height of its vertical line * * Results: * Returns 1 if beyond the last visible entry, 0 otherwise. * * Side effects: * The array of visible nodes is filled. * * ---------------------------------------------------------------------- */ static void ResetCoordinates( TreeView *tvPtr, TreeViewEntry *entryPtr, int *yPtr) { int depth; entryPtr->worldY = -1; entryPtr->vertLineLength = -1; if ((entryPtr != tvPtr->rootPtr) && (Blt_TreeViewEntryIsHidden(entryPtr))) { return; /* If the entry is hidden, then do nothing. */ } entryPtr->worldY = *yPtr; entryPtr->vertLineLength = -(*yPtr); *yPtr += entryPtr->height; depth = DEPTH(tvPtr, entryPtr->node) + 1; if (tvPtr->levelInfo[depth].labelWidth < entryPtr->labelWidth) { tvPtr->levelInfo[depth].labelWidth = entryPtr->labelWidth; } if (tvPtr->levelInfo[depth].iconWidth < entryPtr->iconWidth) { tvPtr->levelInfo[depth].iconWidth = entryPtr->iconWidth; } tvPtr->levelInfo[depth].iconWidth |= 0x01; if ((entryPtr->flags & ENTRY_CLOSED) == 0) { TreeViewEntry *bottomPtr, *childPtr; bottomPtr = entryPtr; for (childPtr = Blt_TreeViewFirstChild(entryPtr, ENTRY_HIDDEN); childPtr != NULL; childPtr = Blt_TreeViewNextSibling(childPtr, ENTRY_HIDDEN)){ ResetCoordinates(tvPtr, childPtr, yPtr); bottomPtr = childPtr; } entryPtr->vertLineLength += bottomPtr->worldY; } } static void AdjustColumns(TreeView *tvPtr) { Blt_ChainLink *linkPtr; TreeViewColumn *columnPtr; double weight; int nOpen; int size, avail, ration, growth; growth = VPORTWIDTH(tvPtr) - tvPtr->worldWidth; nOpen = 0; weight = 0.0; /* Find out how many columns still have space available */ for (linkPtr = Blt_ChainFirstLink(tvPtr->colChainPtr); linkPtr != NULL; linkPtr = Blt_ChainNextLink(linkPtr)) { columnPtr = Blt_ChainGetValue(linkPtr); if ((columnPtr->hidden) || (columnPtr->weight == 0.0) || (columnPtr->width >= columnPtr->max) || (columnPtr->reqWidth > 0)) { continue; } nOpen++; weight += columnPtr->weight; } while ((nOpen > 0) && (weight > 0.0) && (growth > 0)) { ration = (int)(growth / weight); if (ration == 0) { ration = 1; } for (linkPtr = Blt_ChainFirstLink(tvPtr->colChainPtr); linkPtr != NULL; linkPtr = Blt_ChainNextLink(linkPtr)) { columnPtr = Blt_ChainGetValue(linkPtr); if ((columnPtr->hidden) || (columnPtr->weight == 0.0) || (columnPtr->width >= columnPtr->max) || (columnPtr->reqWidth > 0)) { continue; } size = (int)(ration * columnPtr->weight); if (size > growth) { size = growth; } avail = columnPtr->max - columnPtr->width; if (size > avail) { size = avail; nOpen--; weight -= columnPtr->weight; } growth -= size; columnPtr->width += size; } } } /* * ---------------------------------------------------------------------- * * ComputeFlatLayout -- * * 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 ComputeFlatLayout(TreeView *tvPtr) { Blt_ChainLink *linkPtr; TreeViewColumn *columnPtr; TreeViewEntry **p; TreeViewEntry *entryPtr; int count; int maxX; int y; /* * Pass 1: Reinitialize column sizes and loop through all nodes. * * 1. Recalculate the size of each entry as needed. * 2. The maximum depth of the tree. * 3. Minimum height of an entry. Dividing this by the * height of the widget gives a rough estimate of the * maximum number of visible entries. * 4. Build an array to hold level information to be filled * in on pass 2. */ if (tvPtr->flags & (TV_DIRTY | TV_UPDATE)) { int position; /* Reset the positions of all the columns and initialize the * column used to track the widest value. */ position = 1; for (linkPtr = Blt_ChainFirstLink(tvPtr->colChainPtr); linkPtr != NULL; linkPtr = Blt_ChainNextLink(linkPtr)) { columnPtr = Blt_ChainGetValue(linkPtr); columnPtr->maxWidth = 0; columnPtr->max = SHRT_MAX; if (columnPtr->reqMax > 0) { columnPtr->max = columnPtr->reqMax; } columnPtr->position = position; position++; } /* If the view needs to be resorted, free the old view. */ if ((tvPtr->flags & TV_RESORT) && (tvPtr->flatArr != NULL)) { Blt_Free(tvPtr->flatArr); tvPtr->flatArr = NULL; } /* Recreate the flat view of all the open and not-hidden entries. */ if (tvPtr->flatArr == NULL) { count = 0; /* Count the number of open entries to allocate for the array. */ for (entryPtr = tvPtr->rootPtr; entryPtr != NULL; entryPtr = Blt_TreeViewNextEntry(entryPtr, ENTRY_MASK)) { if ((tvPtr->flags & TV_HIDE_ROOT) && (entryPtr == tvPtr->rootPtr)) { continue; } count++; } tvPtr->nEntries = count; /* Allocate an array for the flat view. */ tvPtr->flatArr = Blt_Calloc((count + 1), sizeof(TreeViewEntry *)); assert(tvPtr->flatArr); /* Fill the array with open and not-hidden entries */ p = tvPtr->flatArr; for (entryPtr = tvPtr->rootPtr; entryPtr != NULL; entryPtr = Blt_TreeViewNextEntry(entryPtr, ENTRY_MASK)) { if ((tvPtr->flags & TV_HIDE_ROOT) && (entryPtr == tvPtr->rootPtr)) { continue; } *p++ = entryPtr; } *p = NULL; tvPtr->flags &= ~TV_SORTED; /* Indicate the view isn't sorted. */ } /* Collect the extents of the entries in the flat view. */ tvPtr->depth = 0; tvPtr->minHeight = SHRT_MAX; for (p = tvPtr->flatArr; *p != NULL; p++) { entryPtr = *p; GetEntryExtents(tvPtr, entryPtr); if (tvPtr->minHeight > entryPtr->height) { tvPtr->minHeight = entryPtr->height; } entryPtr->flags &= ~ENTRY_HAS_BUTTON; } if (tvPtr->levelInfo != NULL) { Blt_Free(tvPtr->levelInfo); } tvPtr->levelInfo = Blt_Calloc(tvPtr->depth + 2, sizeof(LevelInfo)); assert(tvPtr->levelInfo); tvPtr->flags &= ~(TV_DIRTY | TV_UPDATE | TV_RESORT); if (tvPtr->flags & TV_SORT_AUTO) { /* If we're auto-sorting, schedule the view to be resorted. */ tvPtr->flags |= TV_SORT_PENDING; } } if (tvPtr->flags & TV_SORT_PENDING) { Blt_TreeViewSortFlatView(tvPtr); } tvPtr->levelInfo[0].labelWidth = tvPtr->levelInfo[0].x = tvPtr->levelInfo[0].iconWidth = 0; /* * Pass 2: Loop through all open/mapped nodes. * * 1. Set world y-coordinates for entries. We must defer * setting the x-coordinates until we know the maximum * icon sizes at each level. * 2. Compute the maximum depth of the tree. * 3. Build an array to hold level information. */ y = 0; count = 0; for(p = tvPtr->flatArr; *p != NULL; p++) { entryPtr = *p; entryPtr->flatIndex = count++; entryPtr->worldY = y; entryPtr->vertLineLength = 0; y += entryPtr->height; if (tvPtr->levelInfo[0].labelWidth < entryPtr->labelWidth) { tvPtr->levelInfo[0].labelWidth = entryPtr->labelWidth; } if (tvPtr->levelInfo[0].iconWidth < entryPtr->iconWidth) { tvPtr->levelInfo[0].iconWidth = entryPtr->iconWidth; } } tvPtr->levelInfo[0].iconWidth |= 0x01; tvPtr->worldHeight = y; /* Set the scroll height of the hierarchy. */ if (tvPtr->worldHeight < 1) { tvPtr->worldHeight = 1; } maxX = tvPtr->levelInfo[0].iconWidth + tvPtr->levelInfo[0].labelWidth; tvPtr->treeColumn.maxWidth = maxX; tvPtr->treeWidth = maxX; tvPtr->flags |= TV_VIEWPORT; } /* * ---------------------------------------------------------------------- * * ComputeTreeLayout -- * * 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 ComputeTreeLayout(TreeView *tvPtr) { Blt_ChainLink *linkPtr; TreeViewColumn *columnPtr; TreeViewEntry *entryPtr; int maxX, x, y; int sum; register int i; /* * Pass 1: Reinitialize column sizes and loop through all nodes. * * 1. Recalculate the size of each entry as needed. * 2. The maximum depth of the tree. * 3. Minimum height of an entry. Dividing this by the * height of the widget gives a rough estimate of the * maximum number of visible entries. * 4. Build an array to hold level information to be filled * in on pass 2. */ if (tvPtr->flags & TV_DIRTY) { int position; position = 1; for (linkPtr = Blt_ChainFirstLink(tvPtr->colChainPtr); linkPtr != NULL; linkPtr = Blt_ChainNextLink(linkPtr)) { columnPtr = Blt_ChainGetValue(linkPtr); columnPtr->maxWidth = 0; columnPtr->max = SHRT_MAX; if (columnPtr->reqMax > 0) { columnPtr->max = columnPtr->reqMax; } columnPtr->position = position; position++; } tvPtr->minHeight = SHRT_MAX; tvPtr->depth = 0; for (entryPtr = tvPtr->rootPtr; entryPtr != NULL; entryPtr = Blt_TreeViewNextEntry(entryPtr, 0)) { GetEntryExtents(tvPtr, entryPtr); if (tvPtr->minHeight > entryPtr->height) { tvPtr->minHeight = entryPtr->height; } /* * Determine if the entry should display a button * (indicating that it has children) and mark the * entry accordingly. */ entryPtr->flags &= ~ENTRY_HAS_BUTTON; if (entryPtr->flags & BUTTON_SHOW) { entryPtr->flags |= ENTRY_HAS_BUTTON; } else if (entryPtr->flags & BUTTON_AUTO) { if (tvPtr->flags & TV_HIDE_LEAVES) { /* Check that a non-leaf child exists */ if (Blt_TreeViewFirstChild(entryPtr, ENTRY_HIDDEN) != NULL) { entryPtr->flags |= ENTRY_HAS_BUTTON; } } else if (!Blt_TreeIsLeaf(entryPtr->node)) { entryPtr->flags |= ENTRY_HAS_BUTTON; } } /* Determine the depth of the tree. */ if (tvPtr->depth < DEPTH(tvPtr, entryPtr->node)) { tvPtr->depth = DEPTH(tvPtr, entryPtr->node); } } if (tvPtr->flags & TV_SORT_PENDING) { Blt_TreeViewSortTreeView(tvPtr); } if (tvPtr->levelInfo != NULL) { Blt_Free(tvPtr->levelInfo); } tvPtr->levelInfo = Blt_Calloc(tvPtr->depth + 2, sizeof(LevelInfo)); assert(tvPtr->levelInfo); tvPtr->flags &= ~(TV_DIRTY | TV_RESORT); } for (i = 0; i <= (tvPtr->depth + 1); i++) { tvPtr->levelInfo[i].labelWidth = tvPtr->levelInfo[i].x = tvPtr->levelInfo[i].iconWidth = 0; } /* * Pass 2: Loop through all open/mapped nodes. * * 1. Set world y-coordinates for entries. We must defer * setting the x-coordinates until we know the maximum * icon sizes at each level. * 2. Compute the maximum depth of the tree. * 3. Build an array to hold level information. */ y = 0; if (tvPtr->flags & TV_HIDE_ROOT) { /* If the root entry is to be hidden, cheat by offsetting * the y-coordinates by the height of the entry. */ y = -(tvPtr->rootPtr->height); } ResetCoordinates(tvPtr, tvPtr->rootPtr, &y); tvPtr->worldHeight = y; /* Set the scroll height of the hierarchy. */ if (tvPtr->worldHeight < 1) { tvPtr->worldHeight = 1; } sum = maxX = 0; for (i = 0; i <= (tvPtr->depth + 1); i++) { sum += tvPtr->levelInfo[i].iconWidth; if (i <= tvPtr->depth) { tvPtr->levelInfo[i + 1].x = sum; } x = sum + tvPtr->levelInfo[i].labelWidth; if (x > maxX) { maxX = x; } } tvPtr->treeColumn.maxWidth = maxX; tvPtr->treeWidth = maxX; } static void LayoutColumns(TreeView *tvPtr) { Blt_ChainLink *linkPtr; TreeViewColumn *columnPtr; int sum; /* The width of the widget (in world coordinates) is the sum * of the column widths. */ tvPtr->worldWidth = tvPtr->titleHeight = 0; sum = 0; for (linkPtr = Blt_ChainFirstLink(tvPtr->colChainPtr); linkPtr != NULL; linkPtr = Blt_ChainNextLink(linkPtr)) { columnPtr = Blt_ChainGetValue(linkPtr); columnPtr->width = 0; if (!columnPtr->hidden) { if ((tvPtr->flags & TV_SHOW_COLUMN_TITLES) && (tvPtr->titleHeight < columnPtr->titleHeight)) { tvPtr->titleHeight = columnPtr->titleHeight; } if (columnPtr->reqWidth > 0) { columnPtr->width = columnPtr->reqWidth; } else { /* The computed width of a column is the maximum of * the title width and the widest entry. */ columnPtr->width = MAX(columnPtr->titleWidth, columnPtr->maxWidth); /* Check that the width stays within any constraints that * have been set. */ if ((columnPtr->reqMin > 0) && (columnPtr->reqMin > columnPtr->width)) { columnPtr->width = columnPtr->reqMin; } if ((columnPtr->reqMax > 0) && (columnPtr->reqMax < columnPtr->width)) { columnPtr->width = columnPtr->reqMax; } } columnPtr->width += PADDING(columnPtr->pad) + 2 * columnPtr->borderWidth; } columnPtr->worldX = sum; sum += columnPtr->width; } tvPtr->worldWidth = sum; if (VPORTWIDTH(tvPtr) > sum) { AdjustColumns(tvPtr); } sum = 0; for (linkPtr = Blt_ChainFirstLink(tvPtr->colChainPtr); linkPtr != NULL; linkPtr = Blt_ChainNextLink(linkPtr)) { columnPtr = Blt_ChainGetValue(linkPtr); columnPtr->worldX = sum; sum += columnPtr->width; } if (tvPtr->titleHeight > 0) { /* If any headings are displayed, add some extra padding to * the height. */ tvPtr->titleHeight += 4; } /* tvPtr->worldWidth += 10; */ if (tvPtr->yScrollUnits < 1) { tvPtr->yScrollUnits = 1; } if (tvPtr->xScrollUnits < 1) { tvPtr->xScrollUnits = 1; } if (tvPtr->worldWidth < 1) { tvPtr->worldWidth = 1; } tvPtr->flags &= ~TV_LAYOUT; tvPtr->flags |= TV_SCROLL; } /* * ---------------------------------------------------------------------- * * Blt_TreeViewComputeLayout -- * * 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. * * ---------------------------------------------------------------------- */ void Blt_TreeViewComputeLayout(TreeView *tvPtr) { Blt_ChainLink *linkPtr; TreeViewColumn *columnPtr; TreeViewEntry *entryPtr; TreeViewValue *valuePtr; if (tvPtr->flatView) { ComputeFlatLayout(tvPtr); } else { ComputeTreeLayout(tvPtr); } /* * Determine the width of each column based upon the entries * that as open (not hidden). The widest entry in a column * determines the width of that column. */ /* Initialize the columns. */ for (linkPtr = Blt_ChainFirstLink(tvPtr->colChainPtr); linkPtr != NULL; linkPtr = Blt_ChainNextLink(linkPtr)) { columnPtr = Blt_ChainGetValue(linkPtr); columnPtr->maxWidth = 0; columnPtr->max = SHRT_MAX; if (columnPtr->reqMax > 0) { columnPtr->max = columnPtr->reqMax; } } /* The treeview column width was computed earlier. */ tvPtr->treeColumn.maxWidth = tvPtr->treeWidth; /* * Look at all open entries and their values. Determine the column * widths by tracking the maximum width value in each column. */ for (entryPtr = tvPtr->rootPtr; entryPtr != NULL; entryPtr = Blt_TreeViewNextEntry(entryPtr, ENTRY_MASK)) { for (valuePtr = entryPtr->values; valuePtr != NULL; valuePtr = valuePtr->nextPtr) { if (valuePtr->columnPtr->maxWidth < valuePtr->width) { valuePtr->columnPtr->maxWidth = valuePtr->width; } } } /* Now layout the columns with the proper sizes. */ LayoutColumns(tvPtr); } /* * ---------------------------------------------------------------------- * * 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(TreeView *tvPtr) { int height; int level; int nSlots; int x, maxX; int xOffset, yOffset; xOffset = Blt_AdjustViewport(tvPtr->xOffset, tvPtr->worldWidth, VPORTWIDTH(tvPtr), tvPtr->xScrollUnits, tvPtr->scrollMode); yOffset = Blt_AdjustViewport(tvPtr->yOffset, tvPtr->worldHeight, VPORTHEIGHT(tvPtr), tvPtr->yScrollUnits, tvPtr->scrollMode); if ((xOffset != tvPtr->xOffset) || (yOffset != tvPtr->yOffset)) { tvPtr->yOffset = yOffset; tvPtr->xOffset = xOffset; tvPtr->flags |= TV_VIEWPORT; } height = VPORTHEIGHT(tvPtr); /* Allocate worst case number of slots for entry array. */ nSlots = (height / tvPtr->minHeight) + 3; if (nSlots != tvPtr->nVisible) { if (tvPtr->visibleArr != NULL) { Blt_Free(tvPtr->visibleArr); } tvPtr->visibleArr = Blt_Calloc(nSlots, sizeof(TreeViewEntry *)); assert(tvPtr->visibleArr); } tvPtr->nVisible = 0; if (tvPtr->rootPtr->flags & ENTRY_HIDDEN) { return TCL_OK; /* Root node is hidden. */ } /* Find the node where the view port starts. */ if (tvPtr->flatView) { register TreeViewEntry **p, *entryPtr; /* Find the starting entry visible in the viewport. It can't * be hidden or any of it's ancestors closed. */ again: for (p = tvPtr->flatArr; *p != NULL; p++) { entryPtr = *p; if ((entryPtr->worldY + entryPtr->height) > tvPtr->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 (*p == NULL) { if (tvPtr->yOffset == 0) { return TCL_OK; /* All entries are hidden. */ } tvPtr->yOffset = 0; goto again; } maxX = 0; height += tvPtr->yOffset; for (/* empty */; *p != NULL; p++) { entryPtr = *p; entryPtr->worldX = LEVELX(0) + tvPtr->treeColumn.worldX; x = entryPtr->worldX + ICONWIDTH(0) + entryPtr->width; if (x > maxX) { maxX = x; } if (entryPtr->worldY >= height) { break; } tvPtr->visibleArr[tvPtr->nVisible] = *p; tvPtr->nVisible++; } tvPtr->visibleArr[tvPtr->nVisible] = NULL; } else { TreeViewEntry *entryPtr; entryPtr = tvPtr->rootPtr; while ((entryPtr->worldY + entryPtr->height) <= tvPtr->yOffset) { for (entryPtr = Blt_TreeViewLastChild(entryPtr, ENTRY_HIDDEN); entryPtr != NULL; entryPtr = Blt_TreeViewPrevSibling(entryPtr, ENTRY_HIDDEN)) { if (entryPtr->worldY <= tvPtr->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 (entryPtr == NULL) { if (tvPtr->yOffset == 0) { return TCL_OK; /* All entries are hidden. */ } tvPtr->yOffset = 0; continue; } } height += tvPtr->yOffset; maxX = 0; tvPtr->treeColumn.maxWidth = tvPtr->treeWidth; for (/* empty */; entryPtr != NULL; entryPtr = Blt_TreeViewNextEntry(entryPtr, ENTRY_MASK)) { /* * Compute and save the entry's X-coordinate now that we know * the maximum level offset for the entire widget. */ level = DEPTH(tvPtr, entryPtr->node); entryPtr->worldX = LEVELX(level) + tvPtr->treeColumn.worldX; x = entryPtr->worldX + ICONWIDTH(level) + ICONWIDTH(level + 1) + entryPtr->width; if (x > maxX) { maxX = x; } if (entryPtr->worldY >= height) { break; } tvPtr->visibleArr[tvPtr->nVisible] = entryPtr; tvPtr->nVisible++; } tvPtr->visibleArr[tvPtr->nVisible] = NULL; } /* * ------------------------------------------------------------------- * * 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 (tvPtr->xOffset > (tvPtr->worldWidth - tvPtr->xScrollUnits)) { tvPtr->xOffset = tvPtr->worldWidth - tvPtr->xScrollUnits; } if (tvPtr->yOffset > (tvPtr->worldHeight - tvPtr->yScrollUnits)) { tvPtr->yOffset = tvPtr->worldHeight - tvPtr->yScrollUnits; } tvPtr->xOffset = Blt_AdjustViewport(tvPtr->xOffset, tvPtr->worldWidth, VPORTWIDTH(tvPtr), tvPtr->xScrollUnits, tvPtr->scrollMode); tvPtr->yOffset = Blt_AdjustViewport(tvPtr->yOffset, tvPtr->worldHeight, VPORTHEIGHT(tvPtr), tvPtr->yScrollUnits, tvPtr->scrollMode); Blt_PickCurrentItem(tvPtr->bindTable); tvPtr->flags &= ~TV_DIRTY; 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( TreeView *tvPtr, /* Widget record containing the attribute * information for buttons. */ TreeViewEntry *entryPtr, /* Entry to be drawn. */ Drawable drawable) /* Pixmap or window to draw into. */ { int height, level; int x, y; int x1, y1, x2, y2; while (entryPtr != tvPtr->rootPtr) { entryPtr = Blt_TreeViewParentEntry(entryPtr); if (entryPtr == NULL) { break; } level = DEPTH(tvPtr, entryPtr->node); /* * World X-coordinates aren't computed only for entries that are * outside the view port. So for each off-screen ancestor node * compute it here too. */ entryPtr->worldX = LEVELX(level) + tvPtr->treeColumn.worldX; x = SCREENX(tvPtr, entryPtr->worldX); y = SCREENY(tvPtr, entryPtr->worldY); height = MAX3(entryPtr->lineHeight, entryPtr->iconHeight, tvPtr->button.height); y += (height - tvPtr->button.height) / 2; x1 = x2 = x + ICONWIDTH(level) + ICONWIDTH(level + 1) / 2; y1 = y + tvPtr->button.height / 2; y2 = y1 + entryPtr->vertLineLength; if ((entryPtr == tvPtr->rootPtr) && (tvPtr->flags & TV_HIDE_ROOT)) { y1 += entryPtr->height; } /* * Clip the line's Y-coordinates at the viewport borders. */ if (y1 < 0) { y1 = (y1 & 0x1); /* Make sure the dotted line starts on * the same even/odd pixel. */ } if (y2 > Tk_Height(tvPtr->tkwin)) { y2 = Tk_Height(tvPtr->tkwin); } if ((y1 < Tk_Height(tvPtr->tkwin)) && (y2 > 0)) { XDrawLine(tvPtr->display, drawable, tvPtr->lineGC, x1, y1, x2, y2); } } } void Blt_TreeViewDrawRule( TreeView *tvPtr, /* Widget record containing the * attribute information for rules. */ TreeViewColumn *columnPtr, Drawable drawable) /* Pixmap or window to draw into. */ { int x, y1, y2; x = SCREENX(tvPtr, columnPtr->worldX) + columnPtr->width + tvPtr->ruleMark - tvPtr->ruleAnchor - 1; y1 = tvPtr->titleHeight + tvPtr->inset; y2 = Tk_Height(tvPtr->tkwin) - tvPtr->inset; XDrawLine(tvPtr->display, drawable, columnPtr->ruleGC, x, y1, x, y2); tvPtr->flags = TOGGLE(tvPtr->flags, TV_RULE_ACTIVE); } /* * --------------------------------------------------------------------------- * * Blt_TreeViewDrawButton -- * * 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. * * --------------------------------------------------------------------------- */ void Blt_TreeViewDrawButton( TreeView *tvPtr, /* Widget record containing the * attribute information for * buttons. */ TreeViewEntry *entryPtr, /* Entry. */ Drawable drawable, /* Pixmap or window to draw into. */ int x, int y) { Tk_3DBorder border; TreeViewButton *buttonPtr = &tvPtr->button; TreeViewIcon icon; int relief; int width, height; if (entryPtr == tvPtr->activeButtonPtr) { border = buttonPtr->activeBorder; } else { border = buttonPtr->border; } if (entryPtr->flags & ENTRY_CLOSED) { relief = buttonPtr->closeRelief; } else { relief = buttonPtr->openRelief; } if (relief == TK_RELIEF_SOLID) { relief = TK_RELIEF_FLAT; } Blt_Fill3DRectangle(tvPtr->tkwin, drawable, border, x, y, buttonPtr->width, buttonPtr->height, buttonPtr->borderWidth, relief); x += buttonPtr->borderWidth; y += buttonPtr->borderWidth; width = buttonPtr->width - (2 * buttonPtr->borderWidth); height = buttonPtr->height - (2 * buttonPtr->borderWidth); icon = NULL; if (buttonPtr->icons != NULL) { /* Open or close button icon? */ icon = buttonPtr->icons[0]; if (((entryPtr->flags & ENTRY_CLOSED) == 0) && (buttonPtr->icons[1] != NULL)) { icon = buttonPtr->icons[1]; } } if (icon != NULL) { /* Icon or rectangle? */ Tk_RedrawImage(TreeViewIconBits(icon), 0, 0, width, height, drawable, x, y); } else { int top, bottom, left, right; XSegment segments[6]; int count; GC gc; gc = (entryPtr == tvPtr->activeButtonPtr) ? buttonPtr->activeGC : buttonPtr->normalGC; if (relief == TK_RELIEF_FLAT) { /* Draw the box outline */ left = x - buttonPtr->borderWidth; top = y - buttonPtr->borderWidth; right = left + buttonPtr->width - 1; bottom = top + buttonPtr->height - 1; segments[0].x1 = left; segments[0].x2 = right; segments[0].y2 = segments[0].y1 = top; segments[1].x2 = segments[1].x1 = right; segments[1].y1 = top; segments[1].y2 = bottom; segments[2].x2 = segments[2].x1 = left; segments[2].y1 = top; segments[2].y2 = bottom; #ifdef WIN32 segments[2].y2++; #endif segments[3].x1 = left; segments[3].x2 = right; segments[3].y2 = segments[3].y1 = bottom; #ifdef WIN32 segments[3].x2++; #endif } top = y + height / 2; left = x + BUTTON_IPAD; right = x + width - BUTTON_IPAD; segments[4].y1 = segments[4].y2 = top; segments[4].x1 = left; segments[4].x2 = right - 1; #ifdef WIN32 segments[4].x2++; #endif count = 5; if (entryPtr->flags & ENTRY_CLOSED) { /* Draw the vertical * line for the plus. */ top = y + BUTTON_IPAD; bottom = y + height - BUTTON_IPAD; segments[5].y1 = top; segments[5].y2 = bottom - 1; segments[5].x1 = segments[5].x2 = x + width / 2; #ifdef WIN32 segments[5].y2++; #endif count = 6; } XDrawSegments(tvPtr->display, drawable, gc, segments, count); } } /* * --------------------------------------------------------------------------- * * Blt_TreeViewGetEntryIcon -- * * Selects the correct image for the entry's icon depending upon * the current state of the entry: active/inactive normal/selected. * * active - normal * active - selected * inactive - normal * inactive - selected * * Results: * Returns the image for the icon. * * --------------------------------------------------------------------------- */ TreeViewIcon Blt_TreeViewGetEntryIcon(TreeView *tvPtr, TreeViewEntry *entryPtr) { TreeViewIcon *icons; TreeViewIcon icon; int isActive, hasFocus; isActive = (entryPtr == tvPtr->activePtr); hasFocus = (entryPtr == tvPtr->focusPtr); icons = NULL; if (isActive) { icons = CHOOSE(tvPtr->activeIcons, entryPtr->activeIcons); } if (icons == NULL) { icons = CHOOSE(tvPtr->icons, entryPtr->icons); } icon = NULL; if (icons != NULL) { /* Selected or normal icon? */ icon = icons[0]; if ((hasFocus) && (icons[1] != NULL)) { icon = icons[1]; } } return icon; } int Blt_TreeViewDrawIcon( TreeView *tvPtr, /* Widget record containing the attribute * information for buttons. */ TreeViewEntry *entryPtr, /* Entry to display. */ Drawable drawable, /* Pixmap or window to draw into. */ int x, int y) { TreeViewIcon icon; icon = Blt_TreeViewGetEntryIcon(tvPtr, entryPtr); if (icon != NULL) { /* Icon or default icon bitmap? */ int entryHeight; int level; int maxY; int top, bottom; int topInset, botInset; int width, height; level = DEPTH(tvPtr, entryPtr->node); entryHeight = MAX3(entryPtr->lineHeight, entryPtr->iconHeight, tvPtr->button.height); height = TreeViewIconHeight(icon); width = TreeViewIconWidth(icon); if (tvPtr->flatView) { x += (ICONWIDTH(0) - width) / 2; } else { x += (ICONWIDTH(level + 1) - width) / 2; } y += (entryHeight - height) / 2; botInset = tvPtr->inset - INSET_PAD; topInset = tvPtr->titleHeight + tvPtr->inset; maxY = Tk_Height(tvPtr->tkwin) - botInset; top = 0; bottom = y + height; if (y < topInset) { height += y - topInset; top = -y + topInset; y = topInset; } else if (bottom >= maxY) { height = maxY - y; } Tk_RedrawImage(TreeViewIconBits(icon), 0, top, width, height, drawable, x, y); } return (icon != NULL); } static int DrawLabel( TreeView *tvPtr, /* Widget record. */ TreeViewEntry *entryPtr, /* Entry attribute information. */ Drawable drawable, /* Pixmap or window to draw into. */ int x, int y) { char *label; int entryHeight; int isFocused; int width, height; /* Width and height of label. */ int selected; entryHeight = MAX3(entryPtr->lineHeight, entryPtr->iconHeight, tvPtr->button.height); isFocused = ((entryPtr == tvPtr->focusPtr) && (tvPtr->flags & TV_FOCUS)); selected = Blt_TreeViewEntryIsSelected(tvPtr, entryPtr); /* 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; } if (isFocused) { /* Focus outline */ if (selected) { XColor *color; color = SELECT_FG(tvPtr); XSetForeground(tvPtr->display, tvPtr->focusGC, color->pixel); } XDrawRectangle(tvPtr->display, drawable, tvPtr->focusGC, x, y, width - 1, height - 1); if (selected) { XSetForeground(tvPtr->display, tvPtr->focusGC, tvPtr->focusColor->pixel); } } x += FOCUS_WIDTH + LABEL_PADX + tvPtr->selBorderWidth; y += FOCUS_WIDTH + LABEL_PADY + tvPtr->selBorderWidth; label = GETLABEL(entryPtr); if (label[0] != '\0') { GC gc; TreeViewStyle *stylePtr; TextStyle ts; Tk_Font font; XColor *normalColor, *activeColor; stylePtr = tvPtr->treeColumn.stylePtr; font = entryPtr->font; if (font == NULL) { font = Blt_TreeViewGetStyleFont(tvPtr, stylePtr); } normalColor = entryPtr->color; if (normalColor == NULL) { normalColor = Blt_TreeViewGetStyleFg(tvPtr, stylePtr); } activeColor = (selected) ? SELECT_FG(tvPtr) : normalColor; gc = entryPtr->gc; if (gc == NULL) { gc = Blt_TreeViewGetStyleGC(stylePtr); } Blt_SetDrawTextStyle(&ts, font, gc, normalColor, activeColor, entryPtr->shadow.color, 0.0, TK_ANCHOR_NW, TK_JUSTIFY_LEFT, 0, entryPtr->shadow.offset); ts.state = (selected || (entryPtr->gc == NULL)) ? STATE_ACTIVE : 0; Blt_DrawTextLayout(tvPtr->tkwin, drawable, entryPtr->textPtr, &ts, x, y); } return entryHeight; } /* * --------------------------------------------------------------------------- * * DrawFlatEntry -- * * 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 DrawFlatEntry( TreeView *tvPtr, /* Widget record containing the attribute * information for buttons. */ TreeViewEntry *entryPtr, /* Entry to be drawn. */ Drawable drawable) /* Pixmap or window to draw into. */ { int level; int x, y; entryPtr->flags &= ~ENTRY_REDRAW; x = SCREENX(tvPtr, entryPtr->worldX); y = SCREENY(tvPtr, entryPtr->worldY); if (!Blt_TreeViewDrawIcon(tvPtr, entryPtr, drawable, x, y)) { x -= (DEF_ICON_WIDTH * 2) / 3; } level = 0; x += ICONWIDTH(level); /* Entry label. */ DrawLabel(tvPtr, entryPtr, drawable, x, y); } /* * --------------------------------------------------------------------------- * * DrawTreeEntry -- * * 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 DrawTreeEntry( TreeView *tvPtr, /* Widget record. */ TreeViewEntry *entryPtr, /* Entry to be drawn. */ Drawable drawable) /* Pixmap or window to draw into. */ { TreeViewButton *buttonPtr = &tvPtr->button; int buttonY; int level; int width, height; int x, y; int x1, y1, x2, y2; entryPtr->flags &= ~ENTRY_REDRAW; x = SCREENX(tvPtr, entryPtr->worldX); y = SCREENY(tvPtr, entryPtr->worldY); level = DEPTH(tvPtr, entryPtr->node); width = ICONWIDTH(level); height = MAX3(entryPtr->lineHeight, 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 + (ICONWIDTH(level) + ICONWIDTH(level + 1)) / 2; if ((Blt_TreeNodeParent(entryPtr->node) != NULL) && (tvPtr->lineWidth > 0)) { /* * For every node except root, draw a horizontal line from * the vertical bar to the middle of the icon. */ XDrawLine(tvPtr->display, drawable, tvPtr->lineGC, x1, y1, x2, y2); } if (((entryPtr->flags & ENTRY_CLOSED) == 0) && (tvPtr->lineWidth > 0)) { /* * Entry is open, draw vertical line. */ y2 = y1 + entryPtr->vertLineLength; if (y2 > Tk_Height(tvPtr->tkwin)) { y2 = Tk_Height(tvPtr->tkwin); /* Clip line at window border. */ } XDrawLine(tvPtr->display, drawable, tvPtr->lineGC, x2, y1, x2, y2); } if ((entryPtr->flags & ENTRY_HAS_BUTTON) && (entryPtr != tvPtr->rootPtr)) { /* * Except for the root, draw a button for every entry that * needs one. The displayed button can be either an icon (Tk * image) or a line drawing (rectangle with plus or minus * sign). */ Blt_TreeViewDrawButton(tvPtr, entryPtr, drawable, x + entryPtr->buttonX, y + entryPtr->buttonY); } x += ICONWIDTH(level); if (!Blt_TreeViewDrawIcon(tvPtr, entryPtr, drawable, x, y)) { x -= (DEF_ICON_WIDTH * 2) / 3; } x += ICONWIDTH(level + 1) + 4; /* Entry label. */ DrawLabel(tvPtr, entryPtr, drawable, x, y); } /* * --------------------------------------------------------------------------- * * Blt_TreeViewDrawValue -- * * Draws a column value for the given entry. * * Results: * None. * * Side Effects: * A button is drawn for the entry. * * --------------------------------------------------------------------------- */ void Blt_TreeViewDrawValue( TreeView *tvPtr, /* Widget record. */ TreeViewEntry *entryPtr, /* Node of entry to be drawn. */ TreeViewValue *valuePtr, Drawable drawable, /* Pixmap or window to draw into. */ int x, int y) { TreeViewStyle *stylePtr; stylePtr = CHOOSE(valuePtr->columnPtr->stylePtr, valuePtr->stylePtr); (*stylePtr->classPtr->drawProc)(tvPtr, drawable, entryPtr, valuePtr, stylePtr, x, y); } static void DrawTitle( TreeView *tvPtr, TreeViewColumn *columnPtr, Drawable drawable, int x) { GC gc; Tk_3DBorder border; XColor *fgColor; int columnWidth; int width; int x0, cx, xOffset; columnWidth = columnPtr->width; cx = x; if (columnPtr->position == Blt_ChainGetLength(tvPtr->colChainPtr)) { /* If there's any room left over, let the last column take it. */ columnWidth = Tk_Width(tvPtr->tkwin) - x; } else if (columnPtr->position == 1) { columnWidth += x; cx = 0; } x0 = x + columnPtr->borderWidth; if (columnPtr == tvPtr->activeTitleColumnPtr) { border = columnPtr->activeTitleBorder; gc = columnPtr->activeTitleGC; fgColor = columnPtr->activeTitleFgColor; } else { border = columnPtr->titleBorder; gc = columnPtr->titleGC; fgColor = columnPtr->titleFgColor; } Blt_Fill3DRectangle(tvPtr->tkwin, drawable, border, cx + 1, tvPtr->inset + 1, columnWidth - 2, tvPtr->titleHeight - 2, 0, TK_RELIEF_FLAT); width = columnPtr->width; xOffset = x0 + columnPtr->pad.side1 + 1; if (width > columnPtr->titleWidth) { x += (width - columnPtr->titleWidth) / 2; } if (columnPtr == tvPtr->sortColumnPtr) { /* Make sure there's room for the sorting-direction triangle. */ if ((x - xOffset) <= (STD_ARROW_WIDTH + 4)) { x = xOffset + STD_ARROW_WIDTH + 4; } } if (columnPtr->titleIcon != NULL) { int iconX, iconY, iconWidth, iconHeight; iconHeight = TreeViewIconHeight(columnPtr->titleIcon); iconWidth = TreeViewIconWidth(columnPtr->titleIcon); iconX = x; if (columnPtr->titleTextPtr != NULL) { iconX += 2; } iconY = tvPtr->inset + (tvPtr->titleHeight - iconHeight) / 2; Tk_RedrawImage(TreeViewIconBits(columnPtr->titleIcon), 0, 0, iconWidth, iconHeight, drawable, iconX, iconY); x += iconWidth + 6; } if (columnPtr->titleTextPtr != NULL) { TextStyle ts; Blt_SetDrawTextStyle(&ts, columnPtr->titleFont, gc, fgColor, SELECT_FG(tvPtr), columnPtr->titleShadow.color, 0.0, TK_ANCHOR_NW, TK_JUSTIFY_LEFT, 0, columnPtr->titleShadow.offset); Blt_DrawTextLayout(tvPtr->tkwin, drawable, columnPtr->titleTextPtr, &ts, x, tvPtr->inset + 1); } if ((columnPtr == tvPtr->sortColumnPtr) && (tvPtr->flatView)) { Blt_DrawArrow(tvPtr->display, drawable, gc, xOffset + ARROW_OFFSET, tvPtr->inset + tvPtr->titleHeight / 2, STD_ARROW_HEIGHT, (tvPtr->sortDecreasing) ? ARROW_UP : ARROW_DOWN); } Blt_Draw3DRectangle(tvPtr->tkwin, drawable, border, cx, tvPtr->inset, columnWidth, tvPtr->titleHeight, columnPtr->titleBorderWidth, columnPtr->titleRelief); } void Blt_TreeViewDrawHeadings(tvPtr, drawable) TreeView *tvPtr; Drawable drawable; { Blt_ChainLink *linkPtr; TreeViewColumn *columnPtr; int x; for (linkPtr = Blt_ChainFirstLink(tvPtr->colChainPtr); linkPtr != NULL; linkPtr = Blt_ChainNextLink(linkPtr)) { columnPtr = Blt_ChainGetValue(linkPtr); if (columnPtr->hidden) { continue; } x = SCREENX(tvPtr, columnPtr->worldX); if ((x + columnPtr->width) < 0) { continue; /* Don't draw columns before the left edge. */ } if (x > Tk_Width(tvPtr->tkwin)) { break; /* Discontinue when a column starts beyond * the right edge. */ } DrawTitle(tvPtr, columnPtr, drawable, x); } } static void DrawTreeView(tvPtr, drawable, x) TreeView *tvPtr; Drawable drawable; int x; { register TreeViewEntry **p; Tk_3DBorder selBorder; /* * Draw the backgrounds of selected entries first. The vertical * lines connecting child entries will be draw on top. */ selBorder = SELECT_BORDER(tvPtr); for (p = tvPtr->visibleArr; *p != NULL; p++) { if (Blt_TreeViewEntryIsSelected(tvPtr, *p)) { int y; y = SCREENY(tvPtr, (*p)->worldY) - 1; Blt_Fill3DRectangle(tvPtr->tkwin, drawable, selBorder, x, y, tvPtr->treeColumn.width, (*p)->height + 1, tvPtr->selBorderWidth, tvPtr->selRelief); } } if ((tvPtr->lineWidth > 0) && (tvPtr->nVisible > 0)) { /* Draw all the vertical lines from topmost node. */ DrawVerticals(tvPtr, tvPtr->visibleArr[0], drawable); } for (p = tvPtr->visibleArr; *p != NULL; p++) { DrawTreeEntry(tvPtr, *p, drawable); } } static void DrawFlatView( TreeView *tvPtr, Drawable drawable, int x) { register TreeViewEntry **p; Tk_3DBorder selBorder; /* * Draw the backgrounds of selected entries first. The vertical * lines connecting child entries will be draw on top. */ selBorder = SELECT_BORDER(tvPtr); for (p = tvPtr->visibleArr; *p != NULL; p++) { if (Blt_TreeViewEntryIsSelected(tvPtr, *p)) { int y; y = SCREENY(tvPtr, (*p)->worldY) - 1; Blt_Fill3DRectangle(tvPtr->tkwin, drawable, selBorder, x, y, tvPtr->treeColumn.width, (*p)->height + 1, tvPtr->selBorderWidth, tvPtr->selRelief); } } for (p = tvPtr->visibleArr; *p != NULL; p++) { DrawFlatEntry(tvPtr, *p, drawable); } } void Blt_TreeViewDrawOuterBorders(tvPtr, drawable) TreeView *tvPtr; Drawable drawable; { /* Draw 3D border just inside of the focus highlight ring. */ if ((tvPtr->borderWidth > 0) && (tvPtr->relief != TK_RELIEF_FLAT)) { Blt_Draw3DRectangle(tvPtr->tkwin, drawable, tvPtr->border, tvPtr->highlightWidth, tvPtr->highlightWidth, Tk_Width(tvPtr->tkwin) - 2 * tvPtr->highlightWidth, Tk_Height(tvPtr->tkwin) - 2 * tvPtr->highlightWidth, tvPtr->borderWidth, tvPtr->relief); } /* Draw focus highlight ring. */ if (tvPtr->highlightWidth > 0) { XColor *color; GC gc; color = (tvPtr->flags & TV_FOCUS) ? tvPtr->highlightColor : tvPtr->highlightBgColor; gc = Tk_GCForColor(color, drawable); Tk_DrawFocusHighlight(tvPtr->tkwin, gc, tvPtr->highlightWidth, drawable); } tvPtr->flags &= ~TV_BORDERS; } /* * ---------------------------------------------------------------------- * * DisplayTreeView -- * * 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 DisplayTreeView(ClientData clientData) /* Information about widget. */ { Blt_ChainLink *linkPtr; TreeView *tvPtr = clientData; TreeViewColumn *columnPtr; Pixmap drawable; int width, height; int x; tvPtr->flags &= ~TV_REDRAW; if (tvPtr->tkwin == NULL) { return; /* Window has been destroyed. */ } if (tvPtr->flags & TV_LAYOUT) { /* * Recompute the layout when entries are opened/closed, * inserted/deleted, or when text attributes change (such as * font, linespacing). */ Blt_TreeViewComputeLayout(tvPtr); } if (tvPtr->flags & TV_SCROLL) { /* * Scrolling means that the view port has changed and that the * visible entries need to be recomputed. */ ComputeVisibleEntries(tvPtr); width = VPORTWIDTH(tvPtr); height = VPORTHEIGHT(tvPtr); if (tvPtr->flags & TV_XSCROLL) { if (tvPtr->xScrollCmdPrefix != NULL) { Blt_UpdateScrollbar(tvPtr->interp, tvPtr->xScrollCmdPrefix, (double)tvPtr->xOffset / tvPtr->worldWidth, (double)(tvPtr->xOffset + width) / tvPtr->worldWidth); } } if (tvPtr->flags & TV_YSCROLL) { if (tvPtr->yScrollCmdPrefix != NULL) { Blt_UpdateScrollbar(tvPtr->interp, tvPtr->yScrollCmdPrefix, (double)tvPtr->yOffset / tvPtr->worldHeight, (double)(tvPtr->yOffset + height) / tvPtr->worldHeight); } } tvPtr->flags &= ~TV_SCROLL; } if (tvPtr->reqWidth == 0) { /* * The first time through this routine, set the requested * width to the computed width. All we want is to * automatically set the width of the widget, not dynamically * grow/shrink it as attributes change. */ tvPtr->reqWidth = tvPtr->worldWidth + 2 * tvPtr->inset; Tk_GeometryRequest(tvPtr->tkwin, tvPtr->reqWidth, tvPtr->reqHeight); } if (!Tk_IsMapped(tvPtr->tkwin)) { return; } drawable = Tk_GetPixmap(tvPtr->display, Tk_WindowId(tvPtr->tkwin), Tk_Width(tvPtr->tkwin), Tk_Height(tvPtr->tkwin), Tk_Depth(tvPtr->tkwin)); tvPtr->flags |= TV_VIEWPORT; if ((tvPtr->flags & TV_RULE_ACTIVE) && (tvPtr->resizeColumnPtr != NULL)) { Blt_TreeViewDrawRule(tvPtr, tvPtr->resizeColumnPtr, drawable); } { register TreeViewEntry **p; Tk_3DBorder border, selBorder; int y; selBorder = SELECT_BORDER(tvPtr); for (linkPtr = Blt_ChainFirstLink(tvPtr->colChainPtr); linkPtr != NULL; linkPtr = Blt_ChainNextLink(linkPtr)) { columnPtr = Blt_ChainGetValue(linkPtr); columnPtr->flags &= ~COLUMN_DIRTY; if (columnPtr->hidden) { continue; } x = SCREENX(tvPtr, columnPtr->worldX); if ((x + columnPtr->width) < 0) { continue; /* Don't draw columns before the left edge. */ } if (x > Tk_Width(tvPtr->tkwin)) { break; /* Discontinue when a column starts beyond * the right edge. */ } /* Clear the column background. */ border = Blt_TreeViewGetStyleBorder(tvPtr, tvPtr->stylePtr); Blt_Fill3DRectangle(tvPtr->tkwin, drawable, border, x, 0, columnPtr->width, Tk_Height(tvPtr->tkwin), 0, TK_RELIEF_FLAT); if (columnPtr != &tvPtr->treeColumn) { TreeViewValue *valuePtr; TreeViewEntry *entryPtr; for (p = tvPtr->visibleArr; *p != NULL; p++) { entryPtr = *p; y = SCREENY(tvPtr, entryPtr->worldY); /* Draw the background of the value. */ if (Blt_TreeViewEntryIsSelected(tvPtr, entryPtr)) { Blt_Fill3DRectangle(tvPtr->tkwin, drawable, selBorder, x, y - 1, columnPtr->width, entryPtr->height + 1, tvPtr->selBorderWidth, tvPtr->selRelief); } /* Check if there's a corresponding value in the entry. */ valuePtr = Blt_TreeViewFindValue(entryPtr, columnPtr); if (valuePtr != NULL) { Blt_TreeViewDrawValue(tvPtr, entryPtr, valuePtr, drawable, x + columnPtr->pad.side1, y); } } } else { if (tvPtr->flatView) { DrawFlatView(tvPtr, drawable, x); } else { DrawTreeView(tvPtr, drawable, x); } } if (columnPtr->relief != TK_RELIEF_FLAT) { Blt_Draw3DRectangle(tvPtr->tkwin, drawable, border, x, 0, columnPtr->width, Tk_Height(tvPtr->tkwin), columnPtr->borderWidth, columnPtr->relief); } } } if (tvPtr->flags & TV_SHOW_COLUMN_TITLES) { Blt_TreeViewDrawHeadings(tvPtr, drawable); } Blt_TreeViewDrawOuterBorders(tvPtr, drawable); if ((tvPtr->flags & TV_RULE_NEEDED) && (tvPtr->resizeColumnPtr != NULL)) { Blt_TreeViewDrawRule(tvPtr, tvPtr->resizeColumnPtr, drawable); } /* Now copy the new view to the window. */ XCopyArea(tvPtr->display, drawable, Tk_WindowId(tvPtr->tkwin), tvPtr->lineGC, 0, 0, Tk_Width(tvPtr->tkwin), Tk_Height(tvPtr->tkwin), 0, 0); Tk_FreePixmap(tvPtr->display, drawable); tvPtr->flags &= ~TV_VIEWPORT; } /* *---------------------------------------------------------------------- * * Blt_TreeViewSelectCmdProc -- * * 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. * *---------------------------------------------------------------------- */ void Blt_TreeViewSelectCmdProc(ClientData clientData) { TreeView *tvPtr = clientData; Tcl_Preserve(tvPtr); if (tvPtr->selectCmd != NULL) { tvPtr->flags &= ~TV_SELECT_PENDING; if (Tcl_GlobalEval(tvPtr->interp, tvPtr->selectCmd) != TCL_OK) { Tcl_BackgroundError(tvPtr->interp); } } Tcl_Release(tvPtr); } /* * -------------------------------------------------------------- * * TreeViewObjCmd -- * * 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 TreeViewObjCmd(clientData, interp, objc, objv) ClientData clientData; /* Main window associated with interpreter. */ Tcl_Interp *interp; /* Current interpreter. */ int objc; /* Number of arguments. */ Tcl_Obj *CONST *objv; /* Argument strings. */ { Tcl_CmdInfo cmdInfo; Tcl_Obj *initObjv[2]; TreeView *tvPtr; char *className; char *string; string = Tcl_GetString(objv[0]); if (objc < 2) { Tcl_AppendResult(interp, "wrong # args: should be \"", string, " pathName ?option value?...\"", (char *)NULL); return TCL_ERROR; } className = (string[0] == 'h') ? "Hiertable" : "TreeView"; tvPtr = CreateTreeView(interp, objv[1], className); if (tvPtr == NULL) { goto error; } /* * Invoke a procedure to initialize various bindings on treeview * entries. If the procedure doesn't already exist, source it * from "$blt_library/treeview.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::tv::Initialize", &cmdInfo)) { char cmd[200]; sprintf(cmd, "set className %s\n\ source [file join $blt_library treeview.tcl]\n\ unset className\n", className); if (Tcl_GlobalEval(interp, cmd) != TCL_OK) { char info[200]; sprintf(info, "\n (while loading bindings for %.50s)", Tcl_GetString(objv[0])); Tcl_AddErrorInfo(interp, info); goto error; } } /* * Initialize the widget's configuration options here. The options * need to be set first, so that entry, column, and style * components can use them for their own GCs. */ bltTreeViewIconsOption.clientData = tvPtr; bltTreeViewTreeOption.clientData = tvPtr; if (Blt_ConfigureWidgetFromObj(interp, tvPtr->tkwin, bltTreeViewSpecs, objc - 2, objv + 2, (char *)tvPtr, 0) != TCL_OK) { return TCL_ERROR; } if (Blt_ConfigureComponentFromObj(interp, tvPtr->tkwin, "button", "Button", bltTreeViewButtonSpecs, 0, (Tcl_Obj **)NULL, (char *)tvPtr, 0) != TCL_OK) { goto error; } /* * Rebuild the widget's GC and other resources that are predicated * by the widget's configuration options. Do the same for the * default column. */ if (Blt_TreeViewUpdateWidget(interp, tvPtr) != TCL_OK) { goto error; } Blt_TreeViewUpdateColumnGCs(tvPtr, &tvPtr->treeColumn); Blt_TreeViewUpdateStyleGCs(tvPtr, tvPtr->stylePtr); /* * Invoke a procedure to initialize various bindings on treeview * entries. If the procedure doesn't already exist, source it * from "$blt_library/treeview.tcl". We deferred sourcing the * file until now so that the variable $blt_library could be set * within a script. */ initObjv[0] = Tcl_NewStringObj("blt::tv::Initialize", -1); initObjv[1] = objv[1]; if (Tcl_EvalObjv(interp, 2, initObjv, TCL_EVAL_GLOBAL) != TCL_OK) { goto error; } Tcl_DecrRefCount(initObjv[0]); Tcl_SetObjResult(interp, Tcl_NewStringObj(Tk_PathName(tvPtr->tkwin), -1)); return TCL_OK; error: Tk_DestroyWindow(tvPtr->tkwin); return TCL_ERROR; } int Blt_TreeViewInit(Tcl_Interp *interp) { static Blt_ObjCmdSpec cmdSpec[] = { { "treeview", TreeViewObjCmd, }, { "hiertable", TreeViewObjCmd, } }; if (Blt_InitObjCmd(interp, "blt", cmdSpec) == NULL) { return TCL_ERROR; } if (Blt_InitObjCmd(interp, "blt", cmdSpec + 1) == NULL) { return TCL_ERROR; } return TCL_OK; } #endif /* NO_TREEVIEW */