diff options
Diffstat (limited to 'generic/ttk/ttkTreeview.c')
| -rw-r--r-- | generic/ttk/ttkTreeview.c | 259 |
1 files changed, 204 insertions, 55 deletions
diff --git a/generic/ttk/ttkTreeview.c b/generic/ttk/ttkTreeview.c index 5106909..b43d764 100644 --- a/generic/ttk/ttkTreeview.c +++ b/generic/ttk/ttkTreeview.c @@ -10,6 +10,8 @@ #ifdef _WIN32 #include "tkWinInt.h" +#elif defined(MAC_OSX_TK) +#include "tkMacOSXPrivate.h" #endif #define DEF_TREE_ROWS "10" @@ -20,13 +22,11 @@ #define DEF_MINWIDTH "20" static const Tk_Anchor DEFAULT_IMAGEANCHOR = TK_ANCHOR_W; -static const int DEFAULT_INDENT = 20; -static const int HALO = 4; /* heading separator */ +static const int DEFAULT_INDENT = 20; +static const int HALO = 4; /* heading separator */ #define STATE_CHANGED (0x100) /* item state option changed */ -#define MAX(a,b) (((a) > (b)) ? (a) : (b)) - /*------------------------------------------------------------------------ * +++ Tree items. * @@ -54,8 +54,8 @@ struct TreeItemRec { Tcl_Obj *valuesObj; Tcl_Obj *openObj; Tcl_Obj *tagsObj; - Tcl_Obj *selObj; - Tcl_Obj *imageAnchorObj; + Tcl_Obj *selObj; + Tcl_Obj *imageAnchorObj; int hidden; int height; /* Height is in number of row heights */ @@ -284,7 +284,7 @@ typedef struct { int width; /* Column width, in pixels */ int minWidth; /* Minimum column width, in pixels */ int stretch; /* Should column stretch while resizing? */ - int separator; /* Should this column have a separator? */ + int separator; /* Should this column have a separator? */ Tcl_Obj *idObj; /* Column identifier, from -columns option */ Tcl_Obj *anchorObj; /* -anchor for cell data <<NOTE-ANCHOR>> */ @@ -301,7 +301,7 @@ typedef struct { /* Temporary storage for cell data */ Tcl_Obj *data; - int selected; + int selected; Ttk_TagSet tagset; } TreeColumn; @@ -550,10 +550,10 @@ static const Tk_OptionSpec TreeviewOptionSpecs[] = { 0, 0, GEOMETRY_CHANGED}, {TK_OPTION_STRING, "-xscrollcommand", "xScrollCommand", "ScrollCommand", - NULL, TCL_INDEX_NONE, offsetof(Treeview, tree.xscroll.scrollCmd), + NULL, offsetof(Treeview, tree.xscroll.scrollCmdObj), TCL_INDEX_NONE, TK_OPTION_NULL_OK, 0, SCROLLCMD_CHANGED}, {TK_OPTION_STRING, "-yscrollcommand", "yScrollCommand", "ScrollCommand", - NULL, TCL_INDEX_NONE, offsetof(Treeview, tree.yscroll.scrollCmd), + NULL, offsetof(Treeview, tree.yscroll.scrollCmdObj), TCL_INDEX_NONE, TK_OPTION_NULL_OK, 0, SCROLLCMD_CHANGED}, WIDGET_TAKEFOCUS_TRUE, @@ -1685,6 +1685,7 @@ static Tcl_Size IdentifyDisplayColumn(Treeview *tv, int x, int *x1) { Tcl_Size colno = FirstColumn(tv); int xpos = tv->tree.treeArea.x; + int scaledHALO = round(HALO * TkScalingLevel(tv->core.tkwin)); if (tv->tree.nTitleColumns <= colno) { xpos -= tv->tree.xscroll.first; @@ -1693,7 +1694,7 @@ static Tcl_Size IdentifyDisplayColumn(Treeview *tv, int x, int *x1) while (colno < tv->tree.nDisplayColumns) { TreeColumn *column = tv->tree.displayColumns[colno]; int next_xpos = xpos + column->width; - if (xpos <= x && x <= next_xpos + HALO) { + if (xpos <= x && x <= next_xpos + scaledHALO) { *x1 = next_xpos; return colno; } @@ -1740,6 +1741,26 @@ static int DisplayRow(int row, Treeview *tv) return row - tv->tree.yscroll.first + tv->tree.titleRows; } +/* Is an item detached? The root is never detached. */ +static int IsDetached(Treeview* tv, TreeItem* item) +{ + return item->next == NULL && item->prev == NULL && + item->parent == NULL && item != tv->tree.root; +} + +/* Is an item or one of its ancestors detached? */ +static int IsItemOrAncestorDetached(Treeview* tv, TreeItem* item) +{ + TreeItem *parent; + + for (parent = item; parent; parent = parent->parent) { + if (IsDetached(tv, parent)) { + return 1; + } + } + return 0; +} + /* + BoundingBox -- * Compute the parcel of the specified column of the specified item, * (or the entire item if column is NULL) @@ -1754,6 +1775,10 @@ static int BoundingBox( int dispRow; Ttk_Box bbox = tv->tree.treeArea; + /* Make sure the scroll information is current before use */ + TtkUpdateScrollInfo(tv->tree.xscrollHandle); + TtkUpdateScrollInfo(tv->tree.yscrollHandle); + if (tv->tree.rowPosNeedsUpdate) { UpdatePositionTree(tv); } @@ -1762,6 +1787,9 @@ static int BoundingBox( /* not viewable, or off-screen */ return 0; } + if (IsItemOrAncestorDetached(tv, item)) { + return 0; + } bbox.y += dispRow * tv->tree.rowHeight; bbox.height = tv->tree.rowHeight * item->height; @@ -1821,11 +1849,12 @@ static TreeRegion IdentifyRegion(Treeview *tv, int x, int y) { int x1 = 0; Tcl_Size colno = IdentifyDisplayColumn(tv, x, &x1); + int scaledHALO = round(HALO * TkScalingLevel(tv->core.tkwin)); if (Ttk_BoxContains(tv->tree.headingArea, x, y)) { if (colno < 0) { return REGION_NOTHING; - } else if (-HALO <= x1 - x && x1 - x <= HALO) { + } else if (-scaledHALO <= x1 - x && x1 - x <= scaledHALO) { return REGION_SEPARATOR; } else { return REGION_HEADING; @@ -1920,7 +1949,9 @@ static Ttk_Layout TreeviewGetLayout( if ((objPtr = Ttk_QueryOption(treeLayout, "-rowheight", 0))) { (void)Tk_GetPixelsFromObj(NULL, tv->core.tkwin, objPtr, &tv->tree.rowHeight); } - tv->tree.rowHeight = MAX(tv->tree.rowHeight, 1); + if (tv->tree.rowHeight < 1) { + tv->tree.rowHeight = 1; + } if ((objPtr = Ttk_QueryOption(treeLayout, "-columnseparatorwidth", 0))) { (void)Tk_GetPixelsFromObj(NULL, tv->core.tkwin, objPtr, &tv->tree.colSeparatorWidth); @@ -1973,15 +2004,6 @@ static void TreeviewDoLayout(void *clientData) first = tv->tree.yscroll.first; last = tv->tree.yscroll.first + visibleRows - tv->tree.titleRows; total = tv->tree.totalRows - tv->tree.titleRows; - if (tv->tree.treeArea.height % tv->tree.rowHeight) { - /* When the treeview height doesn't correspond to an exact number - * of rows, the last row count must be incremented to draw a - * partial row at the bottom. The total row count must also be - * incremented to be able to scroll all the way to the bottom. - */ - last++; - total++; - } TtkScrolled(tv->tree.yscrollHandle, first, last, total); } @@ -2174,7 +2196,8 @@ static void DrawCells( Ttk_Layout layout = tv->tree.cellLayout; Ttk_Style style = Ttk_LayoutStyle(tv->core.layout); Ttk_State state = ItemState(tv, item); - Ttk_Padding cellPadding = {4, 0, 4, 0}; + short horizPad = round(4 * TkScalingLevel(tv->core.tkwin)); + Ttk_Padding cellPadding = {horizPad, 0, horizPad, 0}; DisplayItem displayItemLocal; DisplayItem displayItemCell, displayItemCellSel; int rowHeight = tv->tree.rowHeight * item->height; @@ -2267,11 +2290,19 @@ static void DrawItem( Ttk_Style style = Ttk_LayoutStyle(tv->core.layout); Ttk_State state = ItemState(tv, item); DisplayItem displayItem, displayItemSel, displayItemLocal; - int rowHeight = tv->tree.rowHeight * item->height; - int x = tv->tree.treeArea.x - tv->tree.xscroll.first; - int xTitle = tv->tree.treeArea.x; - int dispRow = DisplayRow(item->rowPos, tv); - int y = tv->tree.treeArea.y + tv->tree.rowHeight * dispRow; + int x, y, h, xTitle, dispRow, rowHeight; + + dispRow = DisplayRow(item->rowPos, tv); + h = tv->tree.rowHeight * dispRow; + if (h >= tv->tree.treeArea.height) { + /* The item is outside the visible area */ + return; + } + + rowHeight = tv->tree.rowHeight * item->height; + x = tv->tree.treeArea.x - tv->tree.xscroll.first; + xTitle = tv->tree.treeArea.x; + y = tv->tree.treeArea.y + h; PrepareItem(tv, item, &displayItem, state); PrepareItem(tv, item, &displayItemSel, state | TTK_STATE_SELECTED); @@ -2279,7 +2310,8 @@ static void DrawItem( /* Draw row background: */ { - Ttk_Box rowBox = Ttk_MakeBox(x, y, TreeWidth(tv), rowHeight); + Ttk_Box rowBox = Ttk_MakeBox(tv->tree.treeArea.x, y, + TreeWidth(tv), rowHeight); DisplayLayout(tv->tree.rowLayout, &displayItem, state, rowBox, d); } @@ -2319,7 +2351,7 @@ static void DrawItem( if (column->selected) { displayItemUsed = &displayItemSel; - stateCell |= TTK_STATE_SELECTED; + stateCell |= TTK_STATE_SELECTED; } if (column->tagset) { @@ -2397,19 +2429,99 @@ static void DrawForest( } } +/* + DrawTreeArea -- + * Draw the tree area including the headings, if any + */ +static void DrawTreeArea(Treeview *tv, Drawable d) { + if (tv->tree.showFlags & SHOW_HEADINGS) { + DrawHeadings(tv, d); + } + DrawForest(tv, tv->tree.root->children, d, 0); + DrawSeparators(tv, d); +} + /* + TreeviewDisplay -- * Display() widget hook. Draw the widget contents. */ static void TreeviewDisplay(void *clientData, Drawable d) { Treeview *tv = (Treeview *)clientData; + Tk_Window tkwin = tv->core.tkwin; + int width, height, winWidth, winHeight; + /* Draw the general layout of the treeview widget */ Ttk_DrawLayout(tv->core.layout, tv->core.state, d); - if (tv->tree.showFlags & SHOW_HEADINGS) { - DrawHeadings(tv, d); + + /* When the tree area does not fit in the available space, there is a + * risk that it will be drawn over other areas of the layout. + */ + + winWidth = Tk_Width(tkwin); + winHeight = Tk_Height(tkwin); + width = tv->tree.treeArea.width; + height = tv->tree.headingArea.height + tv->tree.treeArea.height; + + if ((width == winWidth && height == winHeight) + || (tv->tree.treeArea.height % tv->tree.rowHeight == 0 + && TreeWidth(tv) <= width)) { + /* No protection is needed; either the tree area fills the entire + * widget, or everything fits within the available area. + */ + DrawTreeArea(tv, d); + } else { + /* The tree area needs to be clipped + */ + + int x, y; + + x = tv->tree.treeArea.x; + if (tv->tree.showFlags & SHOW_HEADINGS) { + y = tv->tree.headingArea.y; + } else { + y = tv->tree.treeArea.y; + } + +#ifndef TK_NO_DOUBLE_BUFFERING + Drawable p; + XGCValues gcValues; + GC gc; + + /* Create a temporary helper drawable */ + p = Tk_GetPixmap(Tk_Display(tkwin), Tk_WindowId(tkwin), + winWidth, winHeight, Tk_Depth(tkwin)); + + /* Get a graphics context for copying the drawable content */ + gcValues.function = GXcopy; + gcValues.graphics_exposures = False; + gc = Tk_GetGC(tkwin, GCFunction|GCGraphicsExposures, &gcValues); + + /* Copy the widget background into the helper */ + XCopyArea(Tk_Display(tkwin), d, p, gc, 0, 0, + (unsigned) winWidth, (unsigned) winHeight, 0, 0); + + /* Draw the tree onto the helper without regard for borders */ + DrawTreeArea(tv, p); + + /* Copy only the tree area inside the borders back */ + XCopyArea(Tk_Display(tkwin), p, d, gc, x, y, + (unsigned) width, (unsigned) height, x, y); + + /* Clean up the temporary resources */ + Tk_FreePixmap(Tk_Display(tkwin), p); + Tk_FreeGC(Tk_Display(tkwin), gc); +#else + Ttk_Theme currentTheme = Ttk_GetCurrentTheme(tv->core.interp); + Ttk_Theme aquaTheme = Ttk_GetTheme(tv->core.interp, "aqua"); + if (currentTheme == aquaTheme && [NSApp macOSVersion] > 100800) { + y -= 4; + height += 4; + } + + Tk_ClipDrawableToRect(Tk_Display(tkwin), d, x, y, width, height); + DrawTreeArea(tv, d); + Tk_ClipDrawableToRect(Tk_Display(tkwin), d, 0, 0, -1, -1); +#endif } - DrawForest(tv, tv->tree.root->children, d, 0); - DrawSeparators(tv, d); } /*------------------------------------------------------------------------ @@ -2769,6 +2881,7 @@ static int TreeviewHorribleIdentify( Tcl_Size dColumnNumber; char dcolbuf[32]; int x, y, x1; + int scaledHALO = round(HALO * TkScalingLevel(tv->core.tkwin)); /* ASSERT: objc == 4 */ @@ -2784,7 +2897,7 @@ static int TreeviewHorribleIdentify( snprintf(dcolbuf, sizeof(dcolbuf), "#%" TCL_SIZE_MODIFIER "d", dColumnNumber); if (Ttk_BoxContains(tv->tree.headingArea,x,y)) { - if (-HALO <= x1 - x && x1 - x <= HALO) { + if (-scaledHALO <= x1 - x && x1 - x <= scaledHALO) { what = "separator"; } else { what = "heading"; @@ -2868,6 +2981,10 @@ static int TreeviewIdentifyCommand( return TCL_ERROR; } + /* Make sure the scroll information is current before use */ + TtkUpdateScrollInfo(tv->tree.xscrollHandle); + TtkUpdateScrollInfo(tv->tree.yscrollHandle); + region = IdentifyRegion(tv, x, y); item = IdentifyItem(tv, y); colno = IdentifyDisplayColumn(tv, x, &x1); @@ -3197,7 +3314,7 @@ static int TreeviewInsertCommand( interp, newItem, tv->tree.itemOptionTable, tv->core.tkwin); newItem->tagset = Ttk_GetTagSetFromObj(NULL, tv->tree.tagTable, NULL); if (ConfigureItem(interp, tv, newItem, objc, objv) != TCL_OK) { - Tcl_DeleteHashEntry(entryPtr); + Tcl_DeleteHashEntry(entryPtr); FreeItem(newItem); return TCL_ERROR; } @@ -3253,13 +3370,6 @@ static int TreeviewDetachCommand( return TCL_OK; } -/* Is an item detached? The root is never detached. */ -static int IsDetached(Treeview *tv, TreeItem *item) -{ - return item->next == NULL && item->prev == NULL && - item->parent == NULL && item != tv->tree.root; -} - /* + $tv detached ?$item? -- * List detached items (in arbitrary order) or query the detached state of * $item. @@ -3473,6 +3583,12 @@ static int TreeviewSeeCommand( return TCL_ERROR; } + /* The item cannot be moved into view if any ancestor (or itself) is detached. + */ + if (IsItemOrAncestorDetached(tv, item)) { + return TCL_OK; + } + /* Make sure all ancestors are open: */ for (parent = item->parent; parent; parent = parent->parent) { @@ -3488,6 +3604,9 @@ static int TreeviewSeeCommand( UpdatePositionTree(tv); } + /* Update the scroll information, if necessary */ + TtkUpdateScrollInfo(tv->tree.yscrollHandle); + /* Make sure item is visible: */ if (item->rowPos < tv->tree.titleRows) { @@ -3497,11 +3616,18 @@ static int TreeviewSeeCommand( - tv->tree.titleRows; scrollRow1 = item->rowPos - tv->tree.titleRows; scrollRow2 = scrollRow1 + item->height - 1; + + if (scrollRow2 >= tv->tree.yscroll.first + visibleRows) { + scrollRow2 = 1 + scrollRow2 - visibleRows; + TtkScrollTo(tv->tree.yscrollHandle, scrollRow2, 1); + } + + /* On small widgets (shorter than one row high, which is also the case + * before the widget is initially mapped) the above command will have + * scrolled down too far. This is why both conditions must be checked. + */ if (scrollRow1 < tv->tree.yscroll.first || item->height > visibleRows) { TtkScrollTo(tv->tree.yscrollHandle, scrollRow1, 1); - } else if (scrollRow2 >= tv->tree.yscroll.first + visibleRows) { - scrollRow1 = 1 + scrollRow2 - visibleRows; - TtkScrollTo(tv->tree.yscrollHandle, scrollRow1, 1); } return TCL_OK; @@ -3624,7 +3750,7 @@ static int TreeviewSelectionCommand( } if (objc != 4) { - Tcl_WrongNumArgs(interp, 2, objv, "?add|remove|set|toggle items?"); + Tcl_WrongNumArgs(interp, 2, objv, "?add|remove|set|toggle items?"); return TCL_ERROR; } @@ -3852,7 +3978,7 @@ static int TreeviewCellSelectionCommand( } if (objc < 4 || objc > 5) { - Tcl_WrongNumArgs(interp, 2, objv, "?add|remove|set|toggle arg...?"); + Tcl_WrongNumArgs(interp, 2, objv, "?add|remove|set|toggle arg...?"); return TCL_ERROR; } @@ -3946,7 +4072,7 @@ static int TreeviewTagBindCommand( Ttk_Tag tag; if (objc < 4 || objc > 6) { - Tcl_WrongNumArgs(interp, 3, objv, "tagName ?sequence? ?script?"); + Tcl_WrongNumArgs(interp, 3, objv, "tagName ?sequence? ?script?"); return TCL_ERROR; } @@ -4080,7 +4206,7 @@ static int TreeviewTagHasCommand( Tcl_NewBooleanObj(Ttk_TagSetContains(item->tagset, tag))); return TCL_OK; } else { - Tcl_WrongNumArgs(interp, 3, objv, "tagName ?item?"); + Tcl_WrongNumArgs(interp, 3, objv, "tagName ?item?"); return TCL_ERROR; } } @@ -4142,7 +4268,7 @@ static int TreeviewCtagHasCommand( Tcl_SetObjResult(interp, Tcl_NewWideIntObj(result)); return TCL_OK; } else { - Tcl_WrongNumArgs(interp, 4, objv, "tagName ?cell?"); + Tcl_WrongNumArgs(interp, 4, objv, "tagName ?cell?"); return TCL_ERROR; } } @@ -4206,7 +4332,10 @@ static int TreeviewTagAddCommand( /* Make sure tagset at column is allocated and initialised */ static void AllocCellTagSets(Treeview *tv, TreeItem *item, Tcl_Size columnNumber) { - Tcl_Size i, newSize = MAX(columnNumber + 1, tv->tree.nColumns + 1); + Tcl_Size i, newSize = columnNumber + 1; + if (newSize < tv->tree.nColumns + 1) { + newSize = tv->tree.nColumns + 1; + } if (item->nTagSets < newSize) { if (item->cellTagSets == NULL) { item->cellTagSets = (Ttk_TagSet *) @@ -4518,11 +4647,12 @@ static void TreeitemIndicatorSize( TCL_UNUSED(Ttk_Padding *)) { TreeitemIndicator *indicator = (TreeitemIndicator *)elementRecord; - Ttk_Padding margins; int size = 0; + Ttk_Padding margins; - Ttk_GetPaddingFromObj(NULL, tkwin, indicator->marginsObj, &margins); Tk_GetPixelsFromObj(NULL, tkwin, indicator->sizeObj, &size); + if (size % 2 == 0) --size; /* An odd size is better for the indicator. */ + Ttk_GetPaddingFromObj(NULL, tkwin, indicator->marginsObj, &margins); *widthPtr = size + Ttk_PaddingWidth(margins); *heightPtr = size + Ttk_PaddingHeight(margins); @@ -4540,15 +4670,34 @@ static void TreeitemIndicatorDraw( ArrowDirection direction = (state & TTK_STATE_OPEN) ? ARROW_DOWN : ARROW_RIGHT; Ttk_Padding margins; + int cx, cy; XColor *borderColor = Tk_GetColorFromObj(tkwin, indicator->colorObj); XGCValues gcvalues; GC gc; unsigned mask; if (state & TTK_STATE_LEAF) /* don't draw anything */ return; - Ttk_GetPaddingFromObj(NULL,tkwin,indicator->marginsObj,&margins); + Ttk_GetPaddingFromObj(NULL, tkwin, indicator->marginsObj, &margins); b = Ttk_PadBox(b, margins); + switch (direction) { + case ARROW_DOWN: + TtkArrowSize(b.width/2, direction, &cx, &cy); + if ((b.height - cy) % 2 == 1) { + ++cy; + } + break; + case ARROW_RIGHT: + default: + TtkArrowSize(b.height/2, direction, &cx, &cy); + if ((b.width - cx) % 2 == 1) { + ++cx; + } + break; + } + + b = Ttk_AnchorBox(b, cx, cy, TK_ANCHOR_CENTER); + gcvalues.foreground = borderColor->pixel; gcvalues.line_width = 1; mask = GCForeground | GCLineWidth; |
