summaryrefslogtreecommitdiffstats
path: root/generic/ttk/ttkTreeview.c
diff options
context:
space:
mode:
Diffstat (limited to 'generic/ttk/ttkTreeview.c')
-rw-r--r--generic/ttk/ttkTreeview.c259
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;