/*
* tkTreeDisplay.c --
*
* This module implements treectrl widget's main display code.
*
* Copyright (c) 2002-2011 Tim Baker
*/
#include "tkTreeCtrl.h"
/* Window -> Canvas */
#define W2Cx(x) ((x) + tree->xOrigin)
#define W2Cy(y) ((y) + tree->yOrigin)
#define DW2Cx(x) ((x) + dInfo->xOrigin)
#define DW2Cy(y) ((y) + dInfo->yOrigin)
/* Canvas -> Window */
#define C2Wx(x) ((x) - tree->xOrigin)
#define C2Wy(y) ((y) - tree->yOrigin)
#define COMPLEX_WHITESPACE
#define REDRAW_RGN 0
#define CACHE_BG_IMG 1
typedef struct TreeColumnDInfo_ TreeColumnDInfo_;
typedef struct TreeDInfo_ TreeDInfo_;
typedef struct RItem RItem;
typedef struct Range Range;
typedef struct DItem DItem;
typedef struct DItemArea DItemArea;
typedef struct DScrollIncrements DScrollIncrements;
static void CheckPendingHeaderUpdate(TreeCtrl *tree);
static void Range_RedoIfNeeded(TreeCtrl *tree);
static int Range_TotalWidth(TreeCtrl *tree, Range *range_);
static int Range_TotalHeight(TreeCtrl *tree, Range *range_);
static void Range_Redo(TreeCtrl *tree);
static Range *Range_UnderPoint(TreeCtrl *tree, int *x_, int *y_, int nearest);
static Pixmap DisplayGetPixmap(TreeCtrl *tree, TreeDrawable *dPixmap,
int width, int height);
#if COLUMNGRID == 1
static int GridLinesInWhiteSpace(TreeCtrl *tree);
#endif
/* One of these per TreeItem that is ReallyVisible(). */
struct RItem
{
TreeItem item; /* The item. */
Range *range; /* Range the item is in. */
int size; /* Height or width consumed in Range. */
int offset; /* Vertical or horizontal offset in Range. */
struct { /* Spacing between adjacent items: */
int x, y; /* x is to right of this item. */
} gap; /* y is below this item. */
int index; /* 0-based index in Range. */
};
/* A collection of visible TreeItems. */
struct Range
{
RItem *first;
RItem *last;
int totalWidth;
int totalHeight;
int index; /* 0-based index in list of Ranges. */
struct {
int x, y; /* vertical/horizontal offset from canvas */
} offset; /* top/left. */
Range *prev;
Range *next;
};
struct DItemArea
{
int x; /* Where it should be drawn, window coords. */
int width; /* Current width. */
int dirty[4]; /* Dirty area in item coords. */
#define DITEM_DIRTY 0x0001
#define DITEM_ALL_DIRTY 0x0002
#define DITEM_DRAWN 0x0004
int flags;
};
/* Display information for a TreeItem that is onscreen. */
struct DItem
{
#ifdef TREECTRL_DEBUG
char magic[4];
#endif
TreeItem item;
int y; /* Where it should be drawn, window coords. */
int height; /* Current height. */
DItemArea area; /* COLUMN_LOCK_NONE. */
DItemArea left, right; /* COLUMN_LOCK_LEFT, COLUMN_LOCK_RIGHT. */
#define DITEM_INVALIDATE_ON_SCROLL_X 0x0001
#define DITEM_INVALIDATE_ON_SCROLL_Y 0x0002
int flags;
int oldX, oldY; /* Where it was last drawn, window coords. */
Range *range; /* Range the TreeItem is in. */
int index; /* Used for alternating background colors. */
int oldIndex; /* Used for alternating background colors. */
int *spans; /* span[n] is the column index of the item
* column displayed at the n'th tree column. */
DItem *next; /* Linked list of displayed items. */
};
/* Display information for a TreeColumn. */
struct TreeColumnDInfo_
{
int offset; /* Last seen x-offset */
int width; /* Last seen column width */
};
struct DScrollIncrements
{
int *increments; /* When TreeCtrl.x|yScrollIncrement is zero */
int count; /* Size of increments[]. */
};
/* Display information for a TreeCtrl. */
struct TreeDInfo_
{
GC scrollGC;
int xOrigin; /* Last seen TreeCtrl.xOrigin */
int yOrigin; /* Last seen TreeCtrl.yOrigin */
int totalWidth; /* Last seen Tree_CanvasWidth() */
int totalHeight; /* Last seen Tree_CanvasHeight() */
int fakeCanvasWidth; /* totalWidth plus any "fake" width resulting
* from the view being locked to the edge of
* a scroll increment. */
int fakeCanvasHeight; /* totalHeight plus any "fake" height resulting
* from the view being locked to the edge of
* a scroll increment.*/
int headerHeight; /* Last seen TreeCtrl.headerHeight */
DItem *dItem; /* Head of list for each displayed item */
DItem *dItemHeader; /* Head of list for each displayed header */
DItem *dItemLast; /* Temp for UpdateDInfo() */
DItem *dItemFree; /* List of unused DItems */
Range *rangeFirst; /* Head of Ranges */
Range *rangeLast; /* Tail of Ranges */
Range *rangeFirstD; /* First range with valid display info */
Range *rangeLastD; /* Last range with valid display info */
RItem *rItem; /* Block of RItems for all Ranges */
int rItemMax; /* size of rItem[] */
int itemHeight; /* Observed max TreeItem height */
int itemWidth; /* Observed max TreeItem width */
TreeDrawable pixmapW; /* Pixmap as big as the window */
TreeDrawable pixmapI; /* Pixmap as big as the largest item */
TreeDrawable pixmapT; /* Pixmap as big as the window.
Use on non-composited desktops when
displaying non-XOR dragimage, marquee
and/or proxies. */
TkRegion dirtyRgn; /* DOUBLEBUFFER_WINDOW */
int flags; /* DINFO_XXX */
DScrollIncrements xScrollIncrements;
DScrollIncrements yScrollIncrements;
TkRegion wsRgn; /* Region containing whitespace */
#ifdef COMPLEX_WHITESPACE
int complexWhitespace;
#endif
Tcl_HashTable itemVisHash; /* Table of visible items */
Tcl_HashTable headerVisHash;/* Table of visible header items */
int requests; /* Incremented for every call to
Tree_EventuallyRedraw */
TreeRectangle bounds; /* Bounds of TREE_AREA_CONTENT. */
TreeRectangle boundsL; /* Bounds of TREE_AREA_LEFT. */
TreeRectangle boundsR; /* Bounds of TREE_AREA_RIGHT. */
int empty, emptyL, emptyR; /* Whether bounds above are zero-sized.*/
int widthOfColumnsLeft; /* Last seen Tree_WidthOfLeftColumns() */
int widthOfColumnsRight; /* Last seen Tree_WidthOfRightColumns() */
Range *rangeLock; /* If there is no Range for non-locked
* columns, this range holds the vertical
* offset and height of each ReallyVisible
* item for displaying locked columns. */
#if REDRAW_RGN == 1
TkRegion redrawRgn; /* Contains all redrawn (not copied) pixels
* during a single Tree_Display call. */
#endif /* REDRAW_RGN */
int overlays; /* TRUE if the dragimage|marquee|proxy
* were drawn in non-XOR mode. */
#if CACHE_BG_IMG
TreeDrawable pixmapBgImg; /* Pixmap containing the -backgroundimage
* for efficient blitting. Not used if the
* -backgroundimage is transparent. */
#endif
};
#ifdef COMPLEX_WHITESPACE
static int ComplexWhitespace(TreeCtrl *tree);
#endif
static int CalcBgImageBounds(TreeCtrl *tree, TreeRectangle *trImage);
/*========*/
void
Tree_FreeItemRInfo(
TreeCtrl *tree,
TreeItem item
)
{
TreeItem_SetRInfo(tree, item, NULL);
}
static Range *
Range_Free(
TreeCtrl *tree,
Range *range
)
{
Range *next = range->next;
WFREE(range, Range);
return next;
}
/*
*----------------------------------------------------------------------
*
* ItemWidthParams --
*
* Calculates the fixed or incremental width of all items.
* This is called when -orient=horizontal.
*
* Results:
* Returns the fixed width all items should be, or -1.
* Returns the width all item widths should be a multiple of, or -1.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
static void
ItemWidthParams(
TreeCtrl *tree, /* Widget info. */
int *fixedWidthPtr, /* Returned fixed width or -1. */
int *stepWidthPtr /* Returned incremental width or -1. */
)
{
int fixedWidth = -1, stepWidth = -1;
/* More than one item column, so all items have the same width */
if (tree->columnCountVis > 1)
fixedWidth = Tree_WidthOfColumns(tree);
/* Single item column, fixed width for all items */
else if (tree->itemWidth > 0)
fixedWidth = tree->itemWidth;
/* Single item column, fixed width for all items */
/* THIS IS FOR COMPATIBILITY ONLY */
else if (TreeColumn_FixedWidth(tree->columnVis) != -1)
fixedWidth = TreeColumn_FixedWidth(tree->columnVis);
/* Single item column, want all items same width */
else if (tree->itemWidthEqual
#ifdef DEPRECATED
|| TreeColumn_WidthHack(tree->columnVis)
#endif /* DEPRECATED */
) {
fixedWidth = TreeColumn_WidthOfItems(tree->columnVis);
/* Each item is a multiple of this width */
if (tree->itemWidMult > 0)
stepWidth = tree->itemWidMult;
#ifdef DEPRECATED
else
stepWidth = TreeColumn_StepWidth(tree->columnVis);
#endif /* DEPRECATED */
if ((stepWidth != -1) && (fixedWidth % stepWidth))
fixedWidth += stepWidth - fixedWidth % stepWidth;
}
/* Single item column, variable item width */
else {
/* Each item is a multiple of this width */
if (tree->itemWidMult > 0)
stepWidth = tree->itemWidMult;
#ifdef DEPRECATED
else
stepWidth = TreeColumn_StepWidth(tree->columnVis);
#endif /* DEPRECATED */
}
(*fixedWidthPtr) = fixedWidth;
(*stepWidthPtr) = stepWidth;
}
/*
*----------------------------------------------------------------------
*
* Range_Redo --
*
* This procedure puts all ReallyVisible() TreeItems into a list of
* Ranges. If tree->wrapMode is TREE_WRAP_NONE and no visible items
* have the -wrap=true option there will only be a single Range.
*
* Results:
* None.
*
* Side effects:
* Memory may be allocated.
*
*----------------------------------------------------------------------
*/
static void
Range_Redo(
TreeCtrl *tree /* Widget info. */
)
{
TreeDInfo dInfo = tree->dInfo;
Range *rangeList = dInfo->rangeFirst;
Range *range;
RItem *rItem;
TreeItem item = tree->root;
int fixedWidth = -1, stepWidth = -1;
int wrapCount = 0, wrapPixels = 0;
int count, pixels, rItemCount = 0;
int rangeIndex = 0, rItemIndex;
int *canvasPad;
if (tree->debug.enable && tree->debug.display)
dbwin("Range_Redo %s\n", Tk_PathName(tree->tkwin));
/* Update column variables */
(void) Tree_WidthOfColumns(tree);
dInfo->rangeFirst = NULL;
dInfo->rangeLast = NULL;
if (tree->columnCountVis < 1)
goto freeRanges;
switch (tree->wrapMode) {
case TREE_WRAP_NONE:
break;
case TREE_WRAP_ITEMS:
wrapCount = tree->wrapArg;
break;
case TREE_WRAP_PIXELS:
wrapPixels = tree->wrapArg;
break;
case TREE_WRAP_WINDOW:
wrapPixels = tree->vertical ?
Tree_ContentHeight(tree) :
Tree_ContentWidth(tree);
if (wrapPixels < 0)
wrapPixels = 0;
break;
}
/* For horizontal layout with wrapping I need to know how wide each item
* is. */
if ((wrapPixels > 0) && !tree->vertical) {
ItemWidthParams(tree, &fixedWidth, &stepWidth);
}
/* Speed up ReallyVisible() and get itemVisCount */
Tree_UpdateItemIndex(tree);
if (dInfo->rItemMax < tree->itemVisCount) {
dInfo->rItem = (RItem *) ckrealloc((char *) dInfo->rItem,
tree->itemVisCount * sizeof(RItem));
dInfo->rItemMax = tree->itemVisCount;
}
if (!TreeItem_ReallyVisible(tree, item))
item = TreeItem_NextVisible(tree, item);
while (item != NULL) {
if (rangeList == NULL)
range = (Range *) ckalloc(sizeof(Range));
else {
range = rangeList;
rangeList = rangeList->next;
}
memset(range, '\0', sizeof(Range));
range->totalWidth = -1;
range->totalHeight = -1;
range->index = rangeIndex++;
count = 0;
canvasPad = tree->vertical ? tree->canvasPadY : tree->canvasPadX;
pixels = 0;
rItemIndex = 0;
while (1) {
rItem = dInfo->rItem + rItemCount;
if (rItemCount >= dInfo->rItemMax)
panic("rItemCount > dInfo->rItemMax");
if (range->first == NULL)
range->first = range->last = rItem;
TreeItem_SetRInfo(tree, item, (TreeItemRInfo) rItem);
rItem->item = item;
rItem->range = range;
rItem->index = rItemIndex;
rItem->gap.x = rItem->gap.y = 0;
/* Range must be <= this number of pixels */
if (wrapPixels > 0) {
rItem->offset = pixels;
if (tree->vertical) {
rItem->size = TreeItem_Height(tree, item);
} else {
if (fixedWidth != -1) {
rItem->size = fixedWidth;
} else {
TreeItemColumn itemColumn =
TreeItem_FindColumn(tree, item,
TreeColumn_Index(tree->columnVis));
if (itemColumn != NULL) {
int columnWidth =
TreeItemColumn_NeededWidth(tree, item,
itemColumn);
columnWidth += TreeItem_Indent(tree, tree->columnVis, item);
rItem->size = columnWidth;
} else
rItem->size = 0;
if ((stepWidth != -1) && (rItem->size % stepWidth))
rItem->size += stepWidth - rItem->size % stepWidth;
}
}
/* Check if this item's bounds exceeds the -wrap distance. */
if (canvasPad[PAD_TOP_LEFT]
+ pixels + rItem->size
+ canvasPad[PAD_BOTTOM_RIGHT] > wrapPixels) {
/* Remove the gap from the previous item in this range. */
if (rItem->index > 0) {
if (tree->vertical) {
pixels -= (rItem-1)->gap.y;
(rItem-1)->gap.y = 0;
} else {
pixels -= (rItem-1)->gap.x;
(rItem-1)->gap.x = 0;
}
}
/* Ensure at least one item is in this Range */
if (rItemIndex == 0) {
if (tree->vertical) {
rItem->gap.y = tree->itemGapY;
pixels += rItem->gap.y;
} else {
rItem->gap.x = tree->itemGapX;
pixels += rItem->gap.x;
}
pixels += rItem->size;
rItemCount++;
}
break;
}
if (tree->vertical) {
rItem->gap.y = tree->itemGapY;
pixels += rItem->gap.y;
} else {
rItem->gap.x = tree->itemGapX;
pixels += rItem->gap.x;
}
pixels += rItem->size;
}
range->last = rItem;
rItemIndex++;
rItemCount++;
if (++count == wrapCount)
break;
item = TreeItem_NextVisible(tree, item);
if (item == NULL)
break;
if (TreeItem_GetWrap(tree, item))
break;
}
/* If we needed to calculate the height or width of this range,
* we don't need to do it later in Range_TotalWidth/Height() */
if (wrapPixels > 0) {
if (tree->vertical) {
/* Remove the gap from the last item in this range. */
if (range->last->gap.y > 0) {
pixels -= range->last->gap.y;
range->last->gap.y = 0;
}
range->totalHeight = pixels;
} else {
/* Remove the gap from the last item in this range. */
if (range->last->gap.x > 0) {
pixels -= range->last->gap.x;
range->last->gap.x = 0;
}
range->totalWidth = pixels;
}
}
if (dInfo->rangeFirst == NULL)
dInfo->rangeFirst = range;
else {
range->prev = dInfo->rangeLast;
dInfo->rangeLast->next = range;
}
dInfo->rangeLast = range;
item = TreeItem_NextVisible(tree, range->last->item);
}
freeRanges:
while (rangeList != NULL)
rangeList = Range_Free(tree, rangeList);
/* If there are no visible non-locked columns, we won't have a Range.
* But we need to know the offset/size of each item for drawing any
* locked columns (and for vertical scrolling... and hit testing). */
if (dInfo->rangeLock != NULL) {
(void) Range_Free(tree, dInfo->rangeLock);
dInfo->rangeLock = NULL;
}
(void) Tree_WidthOfColumns(tree); /* update columnCountVisLeft etc */
if ((dInfo->rangeFirst == NULL) &&
(tree->columnCountVisLeft ||
tree->columnCountVisRight)) {
/* Speed up ReallyVisible() and get itemVisCount */
Tree_UpdateItemIndex(tree);
if (tree->itemVisCount == 0)
return;
if (dInfo->rItemMax < tree->itemVisCount) {
dInfo->rItem = (RItem *) ckrealloc((char *) dInfo->rItem,
tree->itemVisCount * sizeof(RItem));
dInfo->rItemMax = tree->itemVisCount;
}
dInfo->rangeLock = (Range *) ckalloc(sizeof(Range));
range = dInfo->rangeLock;
pixels = 0;
rItemIndex = 0;
rItem = dInfo->rItem;
item = tree->root;
if (!TreeItem_ReallyVisible(tree, item))
item = TreeItem_NextVisible(tree, item);
while (item != NULL) {
rItem->item = item;
rItem->range = range;
rItem->size = TreeItem_Height(tree, item);
rItem->offset = pixels;
rItem->gap.x = 0;
if (TreeItem_NextVisible(tree, item) != NULL) {
rItem->gap.y = tree->itemGapY;
} else {
rItem->gap.y = 0;
}
pixels += rItem->gap.y;
rItem->index = rItemIndex++;
TreeItem_SetRInfo(tree, item, (TreeItemRInfo) rItem);
pixels += rItem->size;
rItem++;
item = TreeItem_NextVisible(tree, item);
}
range->offset.x = 0;
range->offset.y = 0;
range->first = dInfo->rItem;
range->last = dInfo->rItem + tree->itemVisCount - 1;
range->totalWidth = 1;
range->totalHeight = pixels;
range->prev = range->next = NULL;
}
}
/*
*----------------------------------------------------------------------
*
* Range_TotalWidth --
*
* Return the width of a Range. The width is only calculated if
* it hasn't been done yet by Range_Redo().
*
* Results:
* Pixel width of the Range.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
static int
Range_TotalWidth(
TreeCtrl *tree, /* Widget info. */
Range *range /* Range to return the width of. */
)
{
TreeItem item;
TreeItemColumn itemColumn;
RItem *rItem;
int fixedWidth = -1, stepWidth = -1;
int itemWidth;
if (range->totalWidth >= 0)
return range->totalWidth;
if (tree->vertical) {
/* More than one item column, so all ranges have the same width */
if (tree->columnCountVis > 1)
return range->totalWidth = Tree_WidthOfColumns(tree);
/* If wrapping is disabled, then use the column width,
* since it may expand to fill the window */
if ((tree->wrapMode == TREE_WRAP_NONE) && (tree->itemWrapCount <= 0))
return range->totalWidth = TreeColumn_UseWidth(tree->columnVis);
/* Single item column, fixed width for all ranges */
if (tree->itemWidth > 0)
return range->totalWidth = tree->itemWidth;
/* Single item column, fixed width for all ranges */
/* THIS IS FOR COMPATIBILITY ONLY */
if (TreeColumn_FixedWidth(tree->columnVis) != -1)
return range->totalWidth = TreeColumn_FixedWidth(tree->columnVis);
/* Single item column, each item is a multiple of this width */
if (tree->itemWidMult > 0)
stepWidth = tree->itemWidMult;
#ifdef DEPRECATED
else
stepWidth = TreeColumn_StepWidth(tree->columnVis);
#endif /* DEPRECATED */
/* Single item column, want all items same width */
if (tree->itemWidthEqual
#ifdef DEPRECATED
|| TreeColumn_WidthHack(tree->columnVis)
#endif /* DEPRECATED */
) {
range->totalWidth = TreeColumn_WidthOfItems(tree->columnVis);
if ((stepWidth != -1) && (range->totalWidth % stepWidth))
range->totalWidth += stepWidth - range->totalWidth % stepWidth;
return range->totalWidth;
}
/* Max needed width of items in this range */
range->totalWidth = 0;
rItem = range->first;
while (1) {
item = rItem->item;
itemColumn = TreeItem_FindColumn(tree, item,
TreeColumn_Index(tree->columnVis));
if (itemColumn != NULL)
itemWidth = TreeItemColumn_NeededWidth(tree, item,
itemColumn);
else
itemWidth = 0;
itemWidth += TreeItem_Indent(tree, tree->columnVis, item);
if (itemWidth > range->totalWidth)
range->totalWidth = itemWidth;
if (rItem == range->last)
break;
rItem++;
}
if ((stepWidth != -1) && (range->totalWidth % stepWidth))
range->totalWidth += stepWidth - range->totalWidth % stepWidth;
return range->totalWidth;
/* -orient=horizontal */
} else {
ItemWidthParams(tree, &fixedWidth, &stepWidth);
/* Sum of widths of items in this range */
range->totalWidth = 0;
rItem = range->first;
while (1) {
item = rItem->item;
if (fixedWidth != -1)
itemWidth = fixedWidth;
else {
itemColumn = TreeItem_FindColumn(tree, item,
TreeColumn_Index(tree->columnVis));
if (itemColumn != NULL)
itemWidth = TreeItemColumn_NeededWidth(tree, item,
itemColumn);
else
itemWidth = 0;
itemWidth += TreeItem_Indent(tree, tree->columnVis, item);
if ((stepWidth != -1) && (itemWidth % stepWidth))
itemWidth += stepWidth - itemWidth % stepWidth;
}
rItem = (RItem *) TreeItem_GetRInfo(tree, item);
rItem->offset = range->totalWidth;
rItem->size = itemWidth;
if (rItem != range->last) {
rItem->gap.x = tree->itemGapX;
} else {
rItem->gap.x = 0;
}
range->totalWidth += rItem->gap.x;
range->totalWidth += rItem->size;
if (rItem == range->last)
break;
rItem++;
}
return range->totalWidth;
}
}
/*
*----------------------------------------------------------------------
*
* Range_TotalHeight --
*
* Return the height of a Range. The height is only calculated if
* it hasn't been done yet by Range_Redo().
*
* Results:
* Pixel height of the Range.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
static int
Range_TotalHeight(
TreeCtrl *tree, /* Widget info. */
Range *range /* Range to return the height of. */
)
{
TreeItem item;
RItem *rItem;
int itemHeight;
if (range->totalHeight >= 0)
return range->totalHeight;
range->totalHeight = 0;
rItem = range->first;
while (1) {
item = rItem->item;
itemHeight = TreeItem_Height(tree, item);
if (tree->vertical) {
rItem->offset = range->totalHeight;
rItem->size = itemHeight;
if (rItem != range->last) {
rItem->gap.y = tree->itemGapY;
} else {
rItem->gap.y = 0;
}
range->totalHeight += rItem->gap.y;
range->totalHeight += rItem->size;
}
else {
if (itemHeight > range->totalHeight)
range->totalHeight = itemHeight;
}
if (rItem == range->last)
break;
rItem++;
}
return range->totalHeight;
}
/*
*----------------------------------------------------------------------
*
* Tree_CanvasWidth --
*
* Return the width needed by all the Ranges. The width is only
* recalculated if it was marked out-of-date.
*
* Results:
* Pixel width of the "canvas".
*
* Side effects:
* The list of Ranges will be recalculated if needed.
*
*----------------------------------------------------------------------
*/
int
Tree_CanvasWidth(
TreeCtrl *tree /* Widget info. */
)
{
TreeDInfo dInfo = tree->dInfo;
Range *range;
int rangeWidth;
Range_RedoIfNeeded(tree);
if (tree->totalWidth >= 0)
return tree->totalWidth;
if (dInfo->rangeFirst == NULL)
return tree->totalWidth =
tree->canvasPadX[PAD_TOP_LEFT]
+ Tree_WidthOfColumns(tree)
+ tree->canvasPadX[PAD_BOTTOM_RIGHT];
tree->totalWidth = tree->canvasPadX[PAD_TOP_LEFT];
range = dInfo->rangeFirst;
while (range != NULL) {
rangeWidth = Range_TotalWidth(tree, range);
if (tree->vertical) {
range->offset.x = tree->totalWidth;
tree->totalWidth += rangeWidth;
if (range->next != NULL)
tree->totalWidth += tree->itemGapX;
}
else {
range->offset.x = tree->canvasPadX[PAD_TOP_LEFT];
if (range->offset.x + rangeWidth > tree->totalWidth)
tree->totalWidth = range->offset.x + rangeWidth;
}
range = range->next;
}
tree->totalWidth += tree->canvasPadX[PAD_BOTTOM_RIGHT];
return tree->totalWidth;
}
/*
*----------------------------------------------------------------------
*
* Tree_CanvasHeight --
*
* Return the height needed by all the Ranges. The height is only
* recalculated if it was marked out-of-date.
*
* Results:
* Pixel height of the "canvas".
*
* Side effects:
* The list of Ranges will be recalculated if needed.
*
*----------------------------------------------------------------------
*/
int
Tree_CanvasHeight(
TreeCtrl *tree /* Widget info. */
)
{
TreeDInfo dInfo = tree->dInfo;
Range *range;
int rangeHeight;
Range_RedoIfNeeded(tree);
if (tree->totalHeight >= 0)
return tree->totalHeight;
tree->totalHeight = tree->canvasPadY[PAD_TOP_LEFT];
/* If dInfo->rangeLock is not NULL, then we are displaying some items
* in locked columns but no non-locked columns. */
range = dInfo->rangeFirst ? dInfo->rangeFirst : dInfo->rangeLock;
while (range != NULL) {
rangeHeight = Range_TotalHeight(tree, range);
if (tree->vertical) {
range->offset.y = tree->canvasPadY[PAD_TOP_LEFT];
if (range->offset.y + rangeHeight > tree->totalHeight)
tree->totalHeight = range->offset.y + rangeHeight;
}
else {
range->offset.y = tree->totalHeight;
tree->totalHeight += rangeHeight;
if (range->next != NULL)
tree->totalHeight += tree->itemGapY;
}
range = range->next;
}
tree->totalHeight += tree->canvasPadY[PAD_BOTTOM_RIGHT];
return tree->totalHeight;
}
/*
*----------------------------------------------------------------------
*
* Range_UnderPoint --
*
* Return the Range containing the given coordinates.
*
* Results:
* Range containing the coordinates or NULL if the point is
* outside any Range.
*
* Side effects:
* The list of Ranges will be recalculated if needed.
*
*----------------------------------------------------------------------
*/
static Range *
Range_UnderPoint(
TreeCtrl *tree, /* Widget info. */
int *x_, /* In: window x coordinate.
* Out: x coordinate relative to the top-left
* of the Range. */
int *y_, /* In: window y coordinate.
* Out: y coordinate relative to the top-left
* of the Range. */
int nearest /* TRUE if the Range nearest the coordinates
* should be returned. */
)
{
TreeDInfo dInfo = tree->dInfo;
Range *range;
int x = *x_, y = *y_;
Range_RedoIfNeeded(tree);
if (Tree_CanvasWidth(tree) - tree->canvasPadX[PAD_TOP_LEFT]
- tree->canvasPadX[PAD_BOTTOM_RIGHT] <= 0)
return NULL;
if (Tree_CanvasHeight(tree) - tree->canvasPadY[PAD_TOP_LEFT]
- tree->canvasPadY[PAD_BOTTOM_RIGHT] <= 0)
return NULL;
range = dInfo->rangeFirst;
if (nearest) {
TreeRectangle tr;
/* Keep inside borders and header. Perhaps another flag needed. */
if (!Tree_AreaBbox(tree, TREE_AREA_CONTENT, &tr))
return NULL;
TreeRect_ClipPoint(tr, &x, &y);
x = W2Cx(x);
y = W2Cy(y);
if (tree->vertical) {
while (range != NULL) {
if (x < range->offset.x)
x = range->offset.x;
if (y < range->offset.y)
y = range->offset.y;
if (x < range->offset.x + range->totalWidth || range->next == NULL) {
(*x_) = MIN(x - range->offset.x, range->totalWidth - 1);
(*y_) = MIN(y - range->offset.y, range->totalHeight - 1);
return range;
}
if (x - (range->offset.x + range->totalWidth) <
range->next->offset.x - x) {
(*x_) = MIN(x - range->offset.x, range->totalWidth - 1);
(*y_) = MIN(y - range->offset.y, range->totalHeight - 1);
return range;
}
range = range->next;
}
} else {
while (range != NULL) {
if (x < range->offset.x)
x = range->offset.x;
if (y < range->offset.y)
y = range->offset.y;
if (y < range->offset.y + range->totalHeight || range->next == NULL) {
(*x_) = MIN(x - range->offset.x, range->totalWidth - 1);
(*y_) = MIN(y - range->offset.y, range->totalHeight - 1);
return range;
}
if (y - (range->offset.y + range->totalHeight) <
range->next->offset.y - y) {
(*x_) = MIN(x - range->offset.x, range->totalWidth - 1);
(*y_) = MIN(y - range->offset.y, range->totalHeight - 1);
return range;
}
range = range->next;
}
}
return NULL;
}
x = W2Cx(x);
y = W2Cy(y);
while (range != NULL) {
if (tree->vertical && x < range->offset.x)
return NULL;
if (!tree->vertical && y < range->offset.y)
return NULL;
if ((x >= range->offset.x) &&
(x < range->offset.x + range->totalWidth) &&
(y >= range->offset.y) &&
(y < range->offset.y + range->totalHeight)) {
(*x_) = x - range->offset.x;
(*y_) = y - range->offset.y;
return range;
}
range = range->next;
}
return NULL;
}
/*
*----------------------------------------------------------------------
*
* Range_ItemUnderPoint --
*
* Return the RItem containing the given x and/or y coordinates.
*
* Results:
* RItem containing the coordinates, or the RItem nearest the
* coordinates, or NULL.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
static RItem *
Range_ItemUnderPoint(
TreeCtrl *tree, /* Widget info. */
Range *range, /* Range to search. */
int rcX, /* x coordinate relative to top-left of
* the Range. */
int rcY, /* y coordinate relative to top-left of
* the Range. */
int *icX, /* x coordinate relative to top-left of
* the returned RItem. May be NULL. */
int *icY, /* y coordinate relative to top-left of
* the returned RItem. May be NULL. */
int nearest /* '0' means return NULL if point is outside
* every item.
* '1' means return the nearest item.
* '2' means return the nearest item. If the
* point is in whitespace before/between
* items, the item before the gap is returned.
* '3' means return the nearest item. If the
* point is in whitespace before/between
* items, the item after the gap is returned.
*/
)
{
RItem *rItem;
int i, l, u;
int rcOffset = tree->vertical ? rcY : rcX;
if (nearest == 0) {
if (!tree->vertical && (rcX < 0 || rcX >= range->totalWidth))
return NULL;
if (tree->vertical && (rcY < 0 || rcY >= range->totalHeight))
return NULL;
/* Binary search */
l = 0;
u = range->last->index;
while (l <= u) {
i = (l + u) / 2;
rItem = range->first + i;
if ((rcOffset >= rItem->offset) && (rcOffset < rItem->offset + rItem->size)) {
foundItem:
/* Range -> item coords */
if (tree->vertical) {
if (icX != NULL) (*icX) = rcX;
if (icY != NULL) (*icY) = rcY - rItem->offset;
} else {
if (icX != NULL) (*icX) = rcX - rItem->offset;
if (icY != NULL) (*icY) = rcY;
}
return rItem;
}
if ((rcOffset < rItem->offset) && ((rItem == range->first)
|| (rcOffset >= (rItem-1)->offset + (rItem-1)->size))) {
/* Between previous item and this one. */
return NULL;
}
if ((rcOffset >= rItem->offset + rItem->size) &&
((rItem == range->last)
|| (rcOffset < (rItem+1)->offset))) {
/* Between next item and this one. */
return NULL;
}
if (rcOffset < rItem->offset)
u = i - 1;
else
l = i + 1;
}
return NULL;
}
/* Binary search */
l = 0;
u = range->last->index;
while (l <= u) {
i = (l + u) / 2;
rItem = range->first + i;
if ((rcOffset >= rItem->offset) && (rcOffset < rItem->offset + rItem->size)) {
goto foundItem;
}
if ((rcOffset < rItem->offset) && ((rItem == range->first)
|| (rcOffset >= (rItem-1)->offset + (rItem-1)->size))) {
/* Between previous item and this one. */
if (rItem != range->first) {
RItem *prev = rItem - 1;
if (nearest == 1) {
int edgeMin = prev->offset + prev->size;
int edgeMax = rItem->offset;
if (rcOffset < edgeMin + (edgeMax - edgeMin) / 2.0f)
rItem = prev;
} else if (nearest == 2)
rItem = prev;
}
goto foundItem;
}
if ((rcOffset >= rItem->offset + rItem->size) &&
((rItem == range->last)
|| (rcOffset < (rItem+1)->offset))) {
/* Between next item and this one. */
if (rItem != range->last) {
RItem *next = rItem + 1;
if (nearest == 1) {
int edgeMin = rItem->offset + rItem->size;
int edgeMax = next->offset;
if (rcOffset >= edgeMin + (edgeMax - edgeMin) / 2.0f)
rItem = next;
} else if (nearest == 3)
rItem = next;
}
goto foundItem;
}
if (rcOffset < rItem->offset)
u = i - 1;
else
l = i + 1;
}
return NULL;
}
/*
*----------------------------------------------------------------------
*
* Increment_AddX --
*
* Appends one or more values to the list of horizontal scroll
* increments.
*
* Results:
* New size of DInfo.xScrollIncrements.
*
* Side effects:
* Memory may be allocated.
*
*----------------------------------------------------------------------
*/
static int
Increment_AddX(
TreeCtrl *tree, /* Widget info. */
int offset, /* Offset to add. */
int size /* Current size of DInfo.xScrollIncrements. */
)
{
TreeDInfo dInfo = tree->dInfo;
DScrollIncrements *dIncr = &dInfo->xScrollIncrements;
int visWidth = Tree_ContentWidth(tree);
while ((visWidth > 1) && (dIncr->count > 0) &&
(offset - dIncr->increments[dIncr->count - 1] > visWidth)) {
size = Increment_AddX(tree,
dIncr->increments[dIncr->count - 1] + visWidth,
size);
}
#ifdef TREECTRL_DEBUG
if ((dIncr->count > 0) &&
(offset == dIncr->increments[dIncr->count - 1])) {
panic("Increment_AddX: offset %d same as previous offset", offset);
}
#endif
if (dIncr->count + 1 > size) {
size *= 2;
dIncr->increments = (int *) ckrealloc(
(char *) dIncr->increments, size * sizeof(int));
}
dIncr->increments[dIncr->count++] = offset;
return size;
}
/*
*----------------------------------------------------------------------
*
* Increment_AddY --
*
* Appends one or more values to the list of vertical scroll
* increments.
*
* Results:
* New size of DInfo.yScrollIncrements.
*
* Side effects:
* Memory may be allocated.
*
*----------------------------------------------------------------------
*/
static int
Increment_AddY(
TreeCtrl *tree, /* Widget info. */
int offset, /* Offset to add. */
int size /* Current size of DInfo.yScrollIncrements. */
)
{
TreeDInfo dInfo = tree->dInfo;
DScrollIncrements *dIncr = &dInfo->yScrollIncrements;
int visHeight = Tree_ContentHeight(tree);
while ((visHeight > 1) && (dIncr->count > 0) &&
(offset - dIncr->increments[dIncr->count - 1] > visHeight)) {
size = Increment_AddY(tree,
dIncr->increments[dIncr->count - 1] + visHeight,
size);
}
#ifdef TREECTRL_DEBUG
if ((dIncr->count > 0) &&
(offset == dIncr->increments[dIncr->count - 1])) {
panic("Increment_AddY: offset %d same as previous offset", offset);
}
#endif
if (dIncr->count + 1 > size) {
size *= 2;
dIncr->increments = (int *) ckrealloc(
(char *) dIncr->increments, size * sizeof(int));
}
dIncr->increments[dIncr->count++] = offset;
return size;
}
/*
*----------------------------------------------------------------------
*
* RItemsToIncrementsX --
*
* Recalculate the list of horizontal scroll increments. This gets
* called when -orient=horizontal and -xscrollincrement=0.
*
* Results:
* DInfo.xScrollIncrements is updated if the canvas width is > 0.
*
* Side effects:
* Memory may be allocated.
*
*----------------------------------------------------------------------
*/
static void
RItemsToIncrementsX(
TreeCtrl *tree /* Widget info. */
)
{
TreeDInfo dInfo = tree->dInfo;
DScrollIncrements *dIncr = &dInfo->xScrollIncrements;
Range *range, *rangeFirst = dInfo->rangeFirst;
RItem *rItem;
int visWidth = Tree_ContentWidth(tree);
int totalWidth = Tree_CanvasWidth(tree);
int x1, increment, prev;
int size;
if (totalWidth <= 0)
return;
/* First increment is zero */
size = 10;
dIncr->increments = (int *) ckalloc(size * sizeof(int));
dIncr->increments[dIncr->count++] = 0;
prev = 0;
if (rangeFirst == NULL) {
/* Only the column headers are shown. */
} else if (rangeFirst->next == NULL) {
/* A single horizontal range is easy. Add one increment for the
* left edge of each item. */
rItem = rangeFirst->first;
while (1) {
increment = rangeFirst->offset.x + rItem->offset;
if (increment > prev) {
size = Increment_AddX(tree, increment, size);
prev = increment;
}
if (rItem == rangeFirst->last)
break;
rItem++;
}
} else {
x1 = 0;
while (1) {
int minLeft1 = totalWidth, minLeft2 = totalWidth;
increment = totalWidth;
/* Find the RItem whose left edge is >= x1 by the smallest
* amount. */
for (range = rangeFirst; range != NULL; range = range->next) {
if (x1 >= range->offset.x + range->totalWidth)
continue;
rItem = Range_ItemUnderPoint(tree, range,
x1 - range->offset.x, -666, NULL, NULL, 3);
if (range->offset.x + rItem->offset >= x1)
increment = MIN(increment, range->offset.x + rItem->offset);
if (range->offset.x + rItem->offset > x1)
minLeft1 = MIN(minLeft1, range->offset.x + rItem->offset);
if (rItem != range->last) {
rItem += 1;
if (range->offset.x + rItem->offset > x1)
minLeft2 = MIN(minLeft2, range->offset.x + rItem->offset);
}
}
if (increment == totalWidth)
break;
if (increment > prev) {
size = Increment_AddX(tree, increment, size);
prev = increment;
}
if (minLeft1 == totalWidth && minLeft2 == totalWidth)
break; /* no increments > x1 */
if (minLeft1 != totalWidth && minLeft1 > increment)
x1 = minLeft1;
else
x1 = minLeft2;
}
}
if ((visWidth > 1) && (totalWidth -
dIncr->increments[dIncr->count - 1] > visWidth)) {
Increment_AddX(tree, totalWidth, size);
dIncr->count--;
dIncr->increments[dIncr->count - 1] = totalWidth - visWidth;
}
}
/*
*----------------------------------------------------------------------
*
* RItemsToIncrementsY --
*
* Recalculate the list of vertical scroll increments. This gets
* called when -orient=vertical and -yscrollincrement=0.
*
* Results:
* DInfo.yScrollIncrements is updated if the canvas height is > 0.
*
* Side effects:
* Memory may be allocated.
*
*----------------------------------------------------------------------
*/
static void
RItemsToIncrementsY(
TreeCtrl *tree /* Widget info. */
)
{
TreeDInfo dInfo = tree->dInfo;
DScrollIncrements *dIncr = &dInfo->yScrollIncrements;
Range *range, *rangeFirst;
RItem *rItem;
int visHeight = Tree_ContentHeight(tree);
int totalHeight = Tree_CanvasHeight(tree);
int y1, increment, prev;
int size;
if (totalHeight <= 0)
return;
/* First increment is zero */
size = 10;
dIncr->increments = (int *) ckalloc(size * sizeof(int));
dIncr->increments[dIncr->count++] = 0;
prev = 0;
/* If only locked columns are visible, we still scroll vertically. */
rangeFirst = dInfo->rangeFirst;
if (rangeFirst == NULL)
rangeFirst = dInfo->rangeLock;
if (rangeFirst == NULL) {
/* Only -canvaspady spacing, no items! */
} else if (rangeFirst->next == NULL) {
/* A single vertical range is easy. Add one increment for the
* top edge of each item. */
rItem = rangeFirst->first;
while (1) {
increment = rangeFirst->offset.y + rItem->offset;
if (increment > prev) {
size = Increment_AddY(tree, increment, size);
prev = increment;
}
if (rItem == rangeFirst->last)
break;
rItem++;
}
} else {
y1 = 0;
while (1) {
int minTop1 = totalHeight, minTop2 = totalHeight;
increment = totalHeight;
/* Find the RItem whose top edge is >= y1 by smallest amount. */
for (range = rangeFirst; range != NULL; range = range->next) {
if (y1 >= range->totalHeight)
continue;
rItem = Range_ItemUnderPoint(tree, range, -666,
y1 - range->offset.y, NULL, NULL, 3);
if (range->offset.y + rItem->offset >= y1)
increment = MIN(increment, range->offset.y + rItem->offset);
if (range->offset.y + rItem->offset > y1)
minTop1 = MIN(minTop1, range->offset.y + rItem->offset);
if (rItem != range->last) {
rItem += 1;
if (range->offset.y + rItem->offset > y1)
minTop2 = MIN(minTop2, range->offset.y + rItem->offset);
}
}
if (increment == totalHeight)
break;
if (increment > prev) {
size = Increment_AddY(tree, increment, size);
prev = increment;
}
if (minTop1 == totalHeight && minTop2 == totalHeight)
break; /* no increments > y1 */
if (minTop1 != totalHeight && minTop1 > increment)
y1 = minTop1;
else
y1 = minTop2;
}
}
if ((visHeight > 1) && (totalHeight -
dIncr->increments[dIncr->count - 1] > visHeight)) {
size = Increment_AddY(tree, totalHeight, size);
dIncr->count--;
dIncr->increments[dIncr->count - 1] = totalHeight - visHeight;
}
}
/*
*----------------------------------------------------------------------
*
* RangesToIncrementsX --
*
* Recalculate the list of horizontal scroll increments. This gets
* called when -orient=vertical and -xscrollincrement=0.
*
* Results:
* DInfo.xScrollIncrements is updated if there are any Ranges.
*
* Side effects:
* Memory may be allocated.
*
*----------------------------------------------------------------------
*/
static void
RangesToIncrementsX(
TreeCtrl *tree /* Widget info. */
)
{
TreeDInfo dInfo = tree->dInfo;
DScrollIncrements *dIncr = &dInfo->xScrollIncrements;
Range *range = dInfo->rangeFirst;
int visWidth = Tree_ContentWidth(tree);
int totalWidth = Tree_CanvasWidth(tree);
int size, prev;
if (totalWidth <= 0)
return;
/* First increment is zero */
size = 10;
dIncr->increments = (int *) ckalloc(size * sizeof(int));
dIncr->increments[dIncr->count++] = 0;
prev = 0;
while (range != NULL) {
if (range->offset.x > prev) {
size = Increment_AddX(tree, range->offset.x, size);
prev = range->offset.x;
}
range = range->next;
}
if ((visWidth > 1) && (totalWidth -
dIncr->increments[dIncr->count - 1] > visWidth)) {
size = Increment_AddX(tree, totalWidth, size);
dIncr->count--;
dIncr->increments[dIncr->count - 1] = totalWidth - visWidth;
}
}
/*
*----------------------------------------------------------------------
*
* RangesToIncrementsY --
*
* Recalculate the list of vertical scroll increments. This gets
* called when -orient=horizontal and -yscrollincrement=0.
*
* Results:
* DInfo.yScrollIncrements is updated if there are any Ranges.
*
* Side effects:
* Memory may be allocated.
*
*----------------------------------------------------------------------
*/
static void
RangesToIncrementsY(
TreeCtrl *tree /* Widget info. */
)
{
TreeDInfo dInfo = tree->dInfo;
DScrollIncrements *dIncr = &dInfo->yScrollIncrements;
Range *range = dInfo->rangeFirst;
int visHeight = Tree_ContentHeight(tree);
int totalHeight = Tree_CanvasHeight(tree);
int size, prev;
if (totalHeight <= 0)
return;
/* First increment is zero */
size = 10;
dIncr->increments = (int *) ckalloc(size * sizeof(int));
dIncr->increments[dIncr->count++] = 0;
prev = 0;
while (range != NULL) {
if (range->offset.y > prev) {
size = Increment_AddY(tree, range->offset.y, size);
prev = range->offset.y;
}
range = range->next;
}
if ((visHeight > 1) && (totalHeight -
dIncr->increments[dIncr->count - 1] > visHeight)) {
size = Increment_AddY(tree, totalHeight, size);
dIncr->count--;
dIncr->increments[dIncr->count - 1] = totalHeight - visHeight;
}
}
/*
*----------------------------------------------------------------------
*
* Increment_Redo --
*
* Recalculate the lists of scroll increments.
*
* Results:
* DInfo.xScrollIncrements and DInfo.yScrollIncrements are updated.
* Either may be set to NULL. The old values are freed, if any.
*
* Side effects:
* Memory may be allocated.
*
*----------------------------------------------------------------------
*/
static void
Increment_Redo(
TreeCtrl *tree /* Widget info. */
)
{
TreeDInfo dInfo = tree->dInfo;
DScrollIncrements *xIncr = &dInfo->xScrollIncrements;
DScrollIncrements *yIncr = &dInfo->yScrollIncrements;
/* Free x */
if (xIncr->increments != NULL)
ckfree((char *) xIncr->increments);
xIncr->increments = NULL;
xIncr->count = 0;
/* Free y */
if (yIncr->increments != NULL)
ckfree((char *) yIncr->increments);
yIncr->increments = NULL;
yIncr->count = 0;
if (tree->vertical) {
/* No xScrollIncrement is given. Snap to left edge of a Range */
if (tree->xScrollIncrement <= 0)
RangesToIncrementsX(tree);
/* No yScrollIncrement is given. Snap to top edge of a TreeItem */
if (tree->yScrollIncrement <= 0)
RItemsToIncrementsY(tree);
} else {
/* No xScrollIncrement is given. Snap to left edge of a TreeItem */
if (tree->xScrollIncrement <= 0)
RItemsToIncrementsX(tree);
/* No yScrollIncrement is given. Snap to top edge of a Range */
if (tree->yScrollIncrement <= 0)
RangesToIncrementsY(tree);
}
}
/*
*----------------------------------------------------------------------
*
* Increment_RedoIfNeeded --
*
* Recalculate the lists of scroll increments if needed.
*
* Results:
* DInfo.xScrollIncrements and DInfo.yScrollIncrements may be
* updated.
*
* Side effects:
* Memory may be allocated. The list of Ranges will be recalculated
* if needed.
*
*----------------------------------------------------------------------
*/
static void
Increment_RedoIfNeeded(
TreeCtrl *tree /* Widget info. */
)
{
TreeDInfo dInfo = tree->dInfo;
Range_RedoIfNeeded(tree);
if (dInfo->flags & DINFO_REDO_INCREMENTS) {
Increment_Redo(tree);
dInfo->fakeCanvasWidth = dInfo->fakeCanvasHeight = -1;
dInfo->flags &= ~DINFO_REDO_INCREMENTS;
}
}
/*
*----------------------------------------------------------------------
*
* B_IncrementFind --
*
* Search a list of increments and return one nearest to the
* given offset.
*
* Results:
* Index of the nearest increment <= the given offset.
* Panic() if no appropriate offset if found.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
static int
B_IncrementFind(
int *increments, /* DInfo.x|yScrollIncrements. */
int count, /* Length of increments[]. */
int offset /* Offset to search for. */
)
{
int i, l, u, v;
if (offset < 0)
offset = 0;
/* Binary search */
l = 0;
u = count - 1;
while (l <= u) {
i = (l + u) / 2;
v = increments[i];
if ((offset >= v) && ((i == count - 1) || (offset < increments[i + 1])))
return i;
if (offset < v)
u = i - 1;
else
l = i + 1;
}
panic("B_IncrementFind failed (count %d offset %d)", count, offset);
return -1;
}
/*
*----------------------------------------------------------------------
*
* B_IncrementFindX --
*
* Search DInfo.xScrollIncrements and return one nearest to the
* given offset.
*
* Results:
* Index of the nearest increment <= the given offset.
* Panic() if no appropriate offset if found.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
static int
B_IncrementFindX(
TreeCtrl *tree, /* Widget info. */
int offset /* Offset to search for. */
)
{
TreeDInfo dInfo = tree->dInfo;
DScrollIncrements *dIncr = &dInfo->xScrollIncrements;
return B_IncrementFind(
dIncr->increments,
dIncr->count,
offset);
}
/*
*----------------------------------------------------------------------
*
* B_IncrementFindY --
*
* Search DInfo.yScrollIncrements and return one nearest to the
* given offset.
*
* Results:
* Index of the nearest increment <= the given offset.
* Panic() if no appropriate offset if found.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
static int
B_IncrementFindY(
TreeCtrl *tree, /* Widget info. */
int offset /* Offset to search with. */
)
{
TreeDInfo dInfo = tree->dInfo;
DScrollIncrements *dIncr = &dInfo->yScrollIncrements;
return B_IncrementFind(
dIncr->increments,
dIncr->count,
offset);
}
/*
*--------------------------------------------------------------
*
* TreeXviewCmd --
*
* This procedure is invoked to process the "xview" option for
* the widget command for a TreeCtrl. See the user documentation
* for details on what it does.
*
* Results:
* A standard Tcl result.
*
* Side effects:
* See the user documentation.
*
*--------------------------------------------------------------
*/
int
TreeXviewCmd(
TreeCtrl *tree, /* Widget info. */
int objc, /* Number of arguments. */
Tcl_Obj *CONST objv[] /* Argument values. */
)
{
Tcl_Interp *interp = tree->interp;
if (objc == 2) {
double fractions[2];
Tcl_Obj *listObj;
Tree_GetScrollFractionsX(tree, fractions);
listObj = Tcl_NewListObj(0, NULL);
Tcl_ListObjAppendElement(interp, listObj,
Tcl_NewDoubleObj(fractions[0]));
Tcl_ListObjAppendElement(interp, listObj,
Tcl_NewDoubleObj(fractions[1]));
Tcl_SetObjResult(interp, listObj);
} else {
int count, index = 0, indexMax, offset, type;
double fraction;
int visWidth = Tree_ContentWidth(tree);
int totWidth = Tree_CanvasWidth(tree);
if (visWidth < 0)
visWidth = 0;
if (totWidth <= visWidth)
return TCL_OK;
type = Tk_GetScrollInfoObj(interp, objc, objv, &fraction, &count);
Tree_SetScrollSmoothingX(tree, type != TK_SCROLL_UNITS);
totWidth = Tree_FakeCanvasWidth(tree);
if (visWidth > 1) {
indexMax = Increment_FindX(tree, totWidth - visWidth);
} else {
indexMax = Increment_FindX(tree, totWidth);
visWidth = 1;
}
switch (type) {
case TK_SCROLL_ERROR:
return TCL_ERROR;
case TK_SCROLL_MOVETO:
offset = (int) (fraction * totWidth + 0.5);
index = Increment_FindX(tree, offset);
break;
case TK_SCROLL_PAGES:
offset = W2Cx(Tree_ContentLeft(tree));
offset += (int) (count * visWidth * 0.9);
index = Increment_FindX(tree, offset);
if ((count > 0) && (index ==
Increment_FindX(tree, W2Cx(Tree_ContentLeft(tree)))))
index++;
break;
case TK_SCROLL_UNITS:
offset = W2Cx(Tree_ContentLeft(tree));
index = Increment_FindX(tree, offset);
offset = Increment_ToOffsetX(tree, index);
/* If the left-most increment is partially visible due to
* non-unit scrolling, then scrolling by -1 units should
* snap to the left of the partially-visible increment. */
if ((C2Wx(offset) < Tree_ContentLeft(tree)) && (count < 0))
index++;
index += count;
break;
}
/* Don't scroll too far left */
if (index < 0)
index = 0;
/* Don't scroll too far right */
if (index > indexMax)
index = indexMax;
offset = Increment_ToOffsetX(tree, index);
if (tree->xOrigin != offset - Tree_ContentLeft(tree)) {
tree->xOrigin = offset - Tree_ContentLeft(tree);
tree->xOriginV2 = offset;
Tree_EventuallyRedraw(tree);
}
}
return TCL_OK;
}
/*
*--------------------------------------------------------------
*
* TreeYviewCmd --
*
* This procedure is invoked to process the "yview" option for
* the widget command for a TreeCtrl. See the user documentation
* for details on what it does.
*
* Results:
* A standard Tcl result.
*
* Side effects:
* See the user documentation.
*
*--------------------------------------------------------------
*/
int
TreeYviewCmd(
TreeCtrl *tree, /* Widget info. */
int objc, /* Number of arguments. */
Tcl_Obj *CONST objv[] /* Argument values. */
)
{
Tcl_Interp *interp = tree->interp;
if (objc == 2) {
double fractions[2];
Tcl_Obj *listObj;
Tree_GetScrollFractionsY(tree, fractions);
listObj = Tcl_NewListObj(0, NULL);
Tcl_ListObjAppendElement(interp, listObj,
Tcl_NewDoubleObj(fractions[0]));
Tcl_ListObjAppendElement(interp, listObj,
Tcl_NewDoubleObj(fractions[1]));
Tcl_SetObjResult(interp, listObj);
}
else {
int count, index = 0, indexMax, offset, type;
double fraction;
int visHeight = Tree_ContentHeight(tree);
int totHeight = Tree_CanvasHeight(tree);
if (visHeight < 0)
visHeight = 0;
if (totHeight <= visHeight)
return TCL_OK;
type = Tk_GetScrollInfoObj(interp, objc, objv, &fraction, &count);
Tree_SetScrollSmoothingY(tree, type != TK_SCROLL_UNITS);
totHeight = Tree_FakeCanvasHeight(tree);
if (visHeight > 1) {
indexMax = Increment_FindY(tree, totHeight - visHeight);
} else {
indexMax = Increment_FindY(tree, totHeight);
visHeight = 1;
}
switch (type) {
case TK_SCROLL_ERROR:
return TCL_ERROR;
case TK_SCROLL_MOVETO:
offset = (int) (fraction * totHeight + 0.5);
index = Increment_FindY(tree, offset);
break;
case TK_SCROLL_PAGES:
offset = W2Cy(Tree_ContentTop(tree));
offset += (int) (count * visHeight * 0.9);
index = Increment_FindY(tree, offset);
if ((count > 0) && (index ==
Increment_FindY(tree, W2Cy(Tree_ContentTop(tree)))))
index++;
break;
case TK_SCROLL_UNITS:
offset = W2Cy(Tree_ContentTop(tree));
index = Increment_FindY(tree, offset);
offset = Increment_ToOffsetY(tree, index);
/* If the top-most increment is partially visible due to
* non-unit scrolling, then scrolling by -1 units should
* snap to the top of the partially-visible increment. */
if ((C2Wy(offset) < Tree_ContentTop(tree)) && (count < 0))
index++;
index += count;
break;
}
/* Don't scroll too far up */
if (index < 0)
index = 0;
/* Don't scroll too far down */
if (index > indexMax)
index = indexMax;
offset = Increment_ToOffsetY(tree, index);
if (tree->yOrigin != offset - Tree_ContentTop(tree)) {
tree->yOrigin = offset - Tree_ContentTop(tree);
tree->yOriginV2 = offset;
Tree_EventuallyRedraw(tree);
}
}
return TCL_OK;
}
/*
*--------------------------------------------------------------
*
* Tree_ItemUnderPoint --
*
* Return a TreeItem containing the given coordinates.
*
* Results:
* TreeItem token or NULL if no item contains the point.
*
* Side effects:
* The list of Ranges will be recalculated if needed.
*
*--------------------------------------------------------------
*/
TreeItem
Tree_ItemUnderPoint(
TreeCtrl *tree, /* Widget info. */
int *x_, int *y_, /* In: window coordinates.
* Out: coordinates relative to top-left
* corner of the returned item. */
int *lock, /* Out: COLUMN_LOCK_XXX */
int nearest /* TRUE if the item nearest the coordinates
* should be returned. */
)
{
Range *range;
RItem *rItem;
int hit;
hit = Tree_HitTest(tree, *x_, *y_);
if (!nearest && ((hit == TREE_AREA_LEFT) || (hit == TREE_AREA_RIGHT))) {
TreeDInfo dInfo = tree->dInfo;
Range_RedoIfNeeded(tree);
range = dInfo->rangeFirst;
/* If range is NULL use dInfo->rangeLock. */
if (range == NULL) {
if (dInfo->rangeLock == NULL)
return NULL;
range = dInfo->rangeLock;
}
if (W2Cy(*y_) < range->offset.y + range->totalHeight) {
int x = *x_;
int y = *y_;
if (hit == TREE_AREA_RIGHT) {
x -= Tree_ContentRight(tree);
if (lock != NULL) (*lock) = COLUMN_LOCK_RIGHT;
} else {
x -= Tree_BorderLeft(tree);
if (lock != NULL) (*lock) = COLUMN_LOCK_LEFT;
}
y = W2Cy(y) - range->offset.y;
rItem = Range_ItemUnderPoint(tree, range, -666, y, NULL, &y, 0);
if (rItem != NULL) {
*x_ = x;
*y_ = y;
return rItem->item;
}
}
return NULL;
}
if (lock != NULL) (*lock) = COLUMN_LOCK_NONE;
range = Range_UnderPoint(tree, x_, y_, nearest);
if (range == NULL)
return NULL;
rItem = Range_ItemUnderPoint(tree, range, *x_, *y_, x_, y_, nearest ? 1 : 0);
if (rItem != NULL)
return rItem->item;
return NULL;
}
/*
*--------------------------------------------------------------
*
* Tree_AreaBbox --
*
* Return the bounding box of a visible area.
*
* Results:
* Return value is TRUE if the area is non-empty.
*
* Side effects:
* Column and item layout will be updated if needed.
*
*--------------------------------------------------------------
*/
int
Tree_AreaBbox(
TreeCtrl *tree,
int area,
TreeRectangle *tr
)
{
int x1 = 0, y1 = 0, x2 = 0, y2 = 0;
switch (area) {
case TREE_AREA_HEADER:
x1 = Tree_HeaderLeft(tree);
y1 = Tree_HeaderTop(tree);
x2 = Tree_HeaderRight(tree);
y2 = Tree_HeaderBottom(tree);
break;
case TREE_AREA_CONTENT:
x1 = Tree_ContentLeft(tree);
y1 = Tree_ContentTop(tree);
x2 = Tree_ContentRight(tree);
y2 = Tree_ContentBottom(tree);
break;
case TREE_AREA_LEFT:
x1 = Tree_BorderLeft(tree);
y1 = Tree_ContentTop(tree);
x2 = Tree_ContentLeft(tree);
y2 = Tree_ContentBottom(tree);
/* Don't overlap right-locked columns. */
if (x2 > Tree_ContentRight(tree))
x2 = Tree_ContentRight(tree);
break;
case TREE_AREA_RIGHT:
x1 = Tree_ContentRight(tree);
y1 = Tree_ContentTop(tree);
x2 = Tree_BorderRight(tree);
y2 = Tree_ContentBottom(tree);
break;
case TREE_AREA_HEADER_NONE:
x1 = Tree_ContentLeft(tree);
y1 = Tree_HeaderTop(tree);
x2 = Tree_ContentRight(tree);
y2 = Tree_HeaderBottom(tree);
break;
case TREE_AREA_HEADER_LEFT:
x1 = Tree_BorderLeft(tree);
y1 = Tree_HeaderTop(tree);
x2 = Tree_ContentLeft(tree);
y2 = Tree_HeaderBottom(tree);
/* Don't overlap right-locked columns. */
if (x2 > Tree_ContentRight(tree))
x2 = Tree_ContentRight(tree);
break;
case TREE_AREA_HEADER_RIGHT:
x1 = Tree_ContentRight(tree);
y1 = Tree_HeaderTop(tree);
x2 = Tree_BorderRight(tree);
y2 = Tree_HeaderBottom(tree);
break;
}
if (x2 <= x1 || y2 <= y1)
return FALSE;
if (x1 < Tree_BorderLeft(tree))
x1 = Tree_BorderLeft(tree);
if (x2 > Tree_BorderRight(tree))
x2 = Tree_BorderRight(tree);
if (y1 < Tree_BorderTop(tree))
y1 = Tree_BorderTop(tree);
if (y2 > Tree_BorderBottom(tree))
y2 = Tree_BorderBottom(tree);
TreeRect_SetXYXY(*tr, x1, y1, x2, y2);
return (x2 > x1) && (y2 > y1);
}
/*
*--------------------------------------------------------------
*
* Tree_HitTest --
*
* Determine which are of the window contains the given point.
*
* Results:
* Return value is one of the TREE_AREA_xxx constants.
*
* Side effects:
* Column layout will be updated if needed.
*
*--------------------------------------------------------------
*/
int
Tree_HitTest(
TreeCtrl *tree,
int x,
int y
)
{
if ((x < Tree_BorderLeft(tree)) || (x >= Tree_BorderRight(tree)))
return TREE_AREA_NONE;
if ((y < Tree_BorderTop(tree)) || (y >= Tree_BorderBottom(tree)))
return TREE_AREA_NONE;
if (y < Tree_HeaderBottom(tree)) {
return TREE_AREA_HEADER;
}
/* Right-locked columns are drawn over the left. */
if (x >= Tree_ContentRight(tree)) {
return TREE_AREA_RIGHT;
}
/* Left-locked columns are drawn over the non-locked. */
if (x < Tree_ContentLeft(tree)) {
return TREE_AREA_LEFT;
}
if (Tree_ContentLeft(tree) >= Tree_ContentRight(tree)) {
return TREE_AREA_NONE;
}
return TREE_AREA_CONTENT;
}
/*
*--------------------------------------------------------------
*
* Tree_ItemBbox --
*
* Return the bounding box for an item.
*
* Results:
* Return value is -1 if the item is not ReallyVisible()
* or if there are no visible columns. The coordinates
* are relative to the top-left corner of the canvas.
*
* Side effects:
* Column layout will be updated if needed.
* The list of Ranges will be recalculated if needed.
*
*--------------------------------------------------------------
*/
int
Tree_ItemBbox(
TreeCtrl *tree, /* Widget info. */
TreeItem item, /* Item whose bbox is needed. */
int lock, /* COLUMN_LOCK_xxx. */
TreeRectangle *tr /* Returned bbox. */
)
{
Range *range;
RItem *rItem;
if (!TreeItem_ReallyVisible(tree, item))
return -1;
/* Update columnCountVisXXX if needed */
(void) Tree_WidthOfColumns(tree);
/* FIXME: update tree->xOrigin and tree->yOrigin also!!! */
if (TreeItem_GetHeader(tree, item) != NULL) {
TreeItem walk = tree->headerItems;
tr->y = W2Cy(Tree_BorderTop(tree));
while (walk != item) {
tr->y += TreeItem_Height(tree, walk);
walk = TreeItem_NextSiblingVisible(tree, walk);
}
tr->height = TreeItem_Height(tree, item);
switch (lock) {
case COLUMN_LOCK_LEFT:
if (tree->columnCountVisLeft == 0)
return -1;
tr->x = W2Cx(Tree_BorderLeft(tree));
tr->width = Tree_WidthOfLeftColumns(tree);
break;
case COLUMN_LOCK_NONE:
/* Unlike regular items, headers cover the width of the whole
* canvas (or content width, whichever is greater) because
* headers include the tail column. */
/* FIXME: what if the tail column has -visible=0 */
tr->x = 0;
tr->width = tree->canvasPadX[PAD_TOP_LEFT] + Tree_WidthOfColumns(tree);
if (tr->width < Tree_FakeCanvasWidth(tree)) {
tr->width = Tree_FakeCanvasWidth(tree);
}
tr->width += tree->tailExtend;
break;
case COLUMN_LOCK_RIGHT:
if (tree->columnCountVisRight == 0)
return -1;
tr->x = W2Cx(Tree_ContentRight(tree));
tr->width = Tree_WidthOfRightColumns(tree);
break;
}
return 0;
}
Range_RedoIfNeeded(tree);
rItem = (RItem *) TreeItem_GetRInfo(tree, item);
range = rItem->range;
switch (lock) {
case COLUMN_LOCK_LEFT:
if (tree->columnCountVisLeft == 0)
return -1;
TreeRect_SetXYWH(*tr,
W2Cx(Tree_BorderLeft(tree)),
range->offset.y + rItem->offset,
Tree_WidthOfLeftColumns(tree),
rItem->size);
return 0;
case COLUMN_LOCK_NONE:
break;
case COLUMN_LOCK_RIGHT:
if (tree->columnCountVisRight == 0)
return -1;
TreeRect_SetXYWH(*tr,
W2Cx(Tree_ContentRight(tree)),
range->offset.y + rItem->offset,
Tree_WidthOfRightColumns(tree),
rItem->size);
return 0;
}
if (tree->columnCountVis < 1)
return -1;
if (tree->vertical) {
TreeRect_SetXYWH(*tr,
range->offset.x,
range->offset.y + rItem->offset,
range->totalWidth,
rItem->size);
}
else {
TreeRect_SetXYWH(*tr,
range->offset.x + rItem->offset,
range->offset.y,
rItem->size,
range->totalHeight);
}
return 0;
}
/*
*--------------------------------------------------------------
*
* Tree_ItemLARB --
*
* Return an adjacent item above, below, to the left or to the
* right of the given item.
*
* Results:
* An adjacent item or NULL if there is no such item.
*
* Side effects:
* The list of Ranges will be recalculated if needed.
*
*--------------------------------------------------------------
*/
TreeItem
Tree_ItemLARB(
TreeCtrl *tree, /* Widget info. */
TreeItem item, /* Item to use as a reference. */
int vertical, /* TRUE if items are arranged
* from top-to-bottom in each Range. */
int prev /* TRUE for above/left,
* FALSE for below/right. */
)
{
RItem *rItem;
Range *range;
if (!TreeItem_ReallyVisible(tree, item) || (tree->columnCountVis < 1))
return NULL;
Range_RedoIfNeeded(tree);
rItem = (RItem *) TreeItem_GetRInfo(tree, item);
if (vertical) {
if (prev) {
if (rItem == rItem->range->first)
return NULL;
rItem--;
}
else {
if (rItem == rItem->range->last)
return NULL;
rItem++;
}
return rItem->item;
}
else {
range = prev ? rItem->range->prev : rItem->range->next;
if (range == NULL)
return NULL;
/* Find item with same index */
if (range->last->index < rItem->index)
return NULL;
return (range->first + rItem->index)->item;
}
return NULL;
}
TreeItem
Tree_ItemLeft(
TreeCtrl *tree,
TreeItem item)
{
return Tree_ItemLARB(tree, item, !tree->vertical, TRUE);
}
TreeItem
Tree_ItemAbove(
TreeCtrl *tree,
TreeItem item)
{
return Tree_ItemLARB(tree, item, tree->vertical, TRUE);
}
TreeItem
Tree_ItemRight(
TreeCtrl *tree,
TreeItem item)
{
return Tree_ItemLARB(tree, item, !tree->vertical, FALSE);
}
TreeItem
Tree_ItemBelow(
TreeCtrl *tree,
TreeItem item)
{
return Tree_ItemLARB(tree, item, tree->vertical, FALSE);
}
/*
*--------------------------------------------------------------
*
* Tree_ItemFL --
*
* Return the first or last item in the same row or column
* as the given item.
*
* Results:
* First/last item or NULL if there is no such item.
*
* Side effects:
* The list of Ranges will be recalculated if needed.
*
*--------------------------------------------------------------
*/
TreeItem
Tree_ItemFL(
TreeCtrl *tree, /* Widget info. */
TreeItem item, /* Item to use as a reference. */
int vertical, /* TRUE if items are arranged
* from top-to-bottom in each Range. */
int first /* TRUE for top/left,
* FALSE for bottom/right. */
)
{
TreeDInfo dInfo = tree->dInfo;
RItem *rItem;
Range *range;
if (!TreeItem_ReallyVisible(tree, item) || (tree->columnCountVis < 1)) {
return NULL;
}
Range_RedoIfNeeded(tree);
rItem = (RItem *) TreeItem_GetRInfo(tree, item);
if (vertical) {
return (first ? rItem->range->first->item : rItem->range->last->item);
} else {
/* Find the first/last range */
range = first ? dInfo->rangeFirst : dInfo->rangeLast;
/* Check next/prev range until happy */
while (1) {
if (range == rItem->range)
return item;
if (range->last->index >= rItem->index)
return (range->first + rItem->index)->item;
range = first ? range->next : range->prev;
}
}
return NULL;
}
TreeItem
Tree_ItemTop(
TreeCtrl *tree,
TreeItem item)
{
return Tree_ItemFL(tree, item, tree->vertical, TRUE);
}
TreeItem
Tree_ItemBottom(
TreeCtrl *tree,
TreeItem item)
{
return Tree_ItemFL(tree, item, tree->vertical, FALSE);
}
TreeItem
Tree_ItemLeftMost(
TreeCtrl *tree,
TreeItem item)
{
return Tree_ItemFL(tree, item, !tree->vertical, TRUE);
}
TreeItem
Tree_ItemRightMost(
TreeCtrl *tree,
TreeItem item)
{
return Tree_ItemFL(tree, item, !tree->vertical, FALSE);
}
/*
*--------------------------------------------------------------
*
* Tree_ItemToRNC --
*
* Return the row and column for the given item.
*
* Results:
* A standard Tcl result.
*
* Side effects:
* The list of Ranges will be recalculated if needed.
*
*--------------------------------------------------------------
*/
int
Tree_ItemToRNC(
TreeCtrl *tree, /* Widget info. */
TreeItem item, /* Item to get row n' column of. */
int *row, int *col /* Returned row and column. */
)
{
RItem *rItem;
if (!TreeItem_ReallyVisible(tree, item) || (tree->columnCountVis < 1))
return TCL_ERROR;
Range_RedoIfNeeded(tree);
rItem = (RItem *) TreeItem_GetRInfo(tree, item);
if (tree->vertical) {
(*row) = rItem->index;
(*col) = rItem->range->index;
}
else {
(*row) = rItem->range->index;
(*col) = rItem->index;
}
return TCL_OK;
}
/*
*--------------------------------------------------------------
*
* Tree_RNCToItem --
*
* Return the item at a given row and column.
*
* Results:
* Token for the item. Never returns NULL unless there are no
* Ranges.
*
* Side effects:
* The list of Ranges will be recalculated if needed.
*
*--------------------------------------------------------------
*/
TreeItem
Tree_RNCToItem(
TreeCtrl *tree, /* Widget info. */
int row, int col /* Row and column. These values are
* clipped to valid values. */
)
{
TreeDInfo dInfo = tree->dInfo;
Range *range;
RItem *rItem;
Range_RedoIfNeeded(tree);
range = dInfo->rangeFirst;
if (range == NULL)
return NULL;
if (row < 0)
row = 0;
if (col < 0)
col = 0;
if (tree->vertical) {
if (col > dInfo->rangeLast->index)
col = dInfo->rangeLast->index;
while (range->index != col)
range = range->next;
rItem = range->last;
if (row > rItem->index)
row = rItem->index;
return (range->first + row)->item;
}
else {
if (row > dInfo->rangeLast->index)
row = dInfo->rangeLast->index;
while (range->index != row)
range = range->next;
rItem = range->last;
if (col > rItem->index)
col = rItem->index;
return (range->first + col)->item;
}
return rItem->item;
}
/*=============*/
static void
DisplayDelay(TreeCtrl *tree)
{
if (tree->debug.enable &&
tree->debug.display &&
tree->debug.displayDelay > 0) {
#if !defined(WIN32) && !defined(MAC_OSX_TK)
XSync(tree->display, False);
#endif
Tcl_Sleep(tree->debug.displayDelay);
}
}
/*
*--------------------------------------------------------------
*
* DItem_Alloc --
*
* Allocate and initialize a new DItem, and store a pointer to it
* in the given item.
*
* Results:
* Pointer to the DItem which may come from an existing pool of
* unused DItems.
*
* Side effects:
* Memory may be allocated.
*
*--------------------------------------------------------------
*/
static DItem *
DItem_Alloc(
TreeCtrl *tree, /* Widget info. */
RItem *rItem /* Range info for the item. */
)
{
TreeDInfo dInfo = tree->dInfo;
DItem *dItem;
dItem = (DItem *) TreeItem_GetDInfo(tree, rItem->item);
if (dItem != NULL)
panic("tried to allocate duplicate DItem");
/* Pop unused DItem from stack */
if (dInfo->dItemFree != NULL) {
dItem = dInfo->dItemFree;
dInfo->dItemFree = dItem->next;
/* No free DItems, alloc a new one */
} else {
dItem = (DItem *) ckalloc(sizeof(DItem));
}
memset(dItem, '\0', sizeof(DItem));
#ifdef TREECTRL_DEBUG
strncpy(dItem->magic, "MAGC", 4);
#endif
dItem->item = rItem->item;
dItem->area.flags = DITEM_DIRTY | DITEM_ALL_DIRTY;
dItem->left.flags = DITEM_DIRTY | DITEM_ALL_DIRTY;
dItem->right.flags = DITEM_DIRTY | DITEM_ALL_DIRTY;
TreeItem_SetDInfo(tree, rItem->item, (TreeItemDInfo) dItem);
return dItem;
}
/*
*--------------------------------------------------------------
*
* DItem_Unlink --
*
* Remove a DItem from a linked list of DItems.
*
* Results:
* Pointer to the given list of DItems.
*
* Side effects:
* None.
*
*--------------------------------------------------------------
*/
static DItem *
DItem_Unlink(
DItem *head, /* First in linked list. */
DItem *dItem /* DItem to remove from list. */
)
{
DItem *prev;
if (head == dItem)
head = dItem->next;
else {
for (prev = head;
prev->next != dItem;
prev = prev->next) {
/* nothing */
}
prev->next = dItem->next;
}
dItem->next = NULL;
return head;
}
/*
*--------------------------------------------------------------
*
* DItem_Free --
*
* Add a DItem to the pool of unused DItems. If the DItem belongs
* to a TreeItem the pointer to the DItem is set to NULL in that
* TreeItem.
*
* Results:
* Pointer to the next DItem.
*
* Side effects:
* None.
*
*--------------------------------------------------------------
*/
static DItem *
DItem_Free(
TreeCtrl *tree, /* Widget info. */
DItem *dItem /* DItem to free. */
)
{
TreeDInfo dInfo = tree->dInfo;
DItem *next = dItem->next;
#ifdef TREECTRL_DEBUG
if (strncmp(dItem->magic, "MAGC", 4) != 0)
panic("DItem_Free: dItem.magic != MAGC");
#endif
if (dItem->item != NULL) {
TreeItem_SetDInfo(tree, dItem->item, (TreeItemDInfo) NULL);
dItem->item = NULL;
}
/* Push unused DItem on the stack */
dItem->next = dInfo->dItemFree;
dInfo->dItemFree = dItem;
return next;
}
/*
*--------------------------------------------------------------
*
* FreeDItems --
*
* Add a list of DItems to the pool of unused DItems,
* optionally removing the DItems from a linked list.
*
* Results:
* None.
*
* Side effects:
* None.
*
*--------------------------------------------------------------
*/
static void
FreeDItems(
TreeCtrl *tree, /* Widget info. */
DItem **headPtr, /* Head of list to unlink from, or NULL. */
DItem *first, /* First DItem to free. */
DItem *last /* DItem after the last one to free. */
)
{
DItem *prev;
if (headPtr != NULL) {
if ((*headPtr) == first)
(*headPtr) = last;
else {
for (prev = (*headPtr);
prev->next != first;
prev = prev->next) {
/* nothing */
}
prev->next = last;
}
}
while (first != last)
first = DItem_Free(tree, first);
}
/*
*--------------------------------------------------------------
*
* Tree_ItemsInArea --
*
* Return a list of items overlapping the given area.
*
* Results:
* Initializes the given TreeItemList and appends any items
* in the given area.
*
* Side effects:
* The list of Ranges will be recalculated if needed. Memory may
* be allocated.
*
*--------------------------------------------------------------
*/
void
Tree_ItemsInArea(
TreeCtrl *tree, /* Widget info. */
TreeItemList *items, /* Uninitialized list. The caller must free
* it with TreeItemList_Free. */
int minX, int minY, /* Left, top in canvas coordinates. */
int maxX, int maxY /* Right, bottom in canvas coordinates.
* Points on the right/bottom edge are not
* included in the area. */
)
{
TreeDInfo dInfo = tree->dInfo;
int rx, ry;
Range *range;
RItem *rItem;
TreeItemList_Init(tree, items, 0);
Range_RedoIfNeeded(tree);
range = dInfo->rangeFirst;
if (tree->vertical) {
/* Find the first range which could be in the area horizontally */
while (range != NULL) {
if ((range->offset.x < maxX) &&
(range->offset.x + range->totalWidth > minX)) {
break;
}
range = range->next;
}
}
else {
/* Find the first range which could be in the area vertically */
while (range != NULL) {
if ((range->offset.y < maxY) &&
(range->offset.y + range->totalHeight > minY)) {
break;
}
range = range->next;
}
}
if (range == NULL)
return;
while (range != NULL) {
rx = range->offset.x;
ry = range->offset.y;
if ((rx + range->totalWidth > minX) &&
(ry + range->totalHeight > minY)) {
rItem = Range_ItemUnderPoint(tree, range, minX - rx, minY - ry,
NULL, NULL, 3);
while (1) {
if (tree->vertical) {
if (ry + rItem->offset >= maxY)
break;
}
else {
if (rx + rItem->offset >= maxX)
break;
}
TreeItemList_Append(items, rItem->item);
if (rItem == range->last)
break;
rItem++;
}
}
if (tree->vertical) {
if (rx + range->totalWidth >= maxX)
break;
rx += range->totalWidth;
}
else {
if (ry + range->totalHeight >= maxY)
break;
ry += range->totalHeight;
}
range = range->next;
}
}
#define DCOLUMN
#ifdef DCOLUMN
/*
*--------------------------------------------------------------
*
* GetOnScreenColumnsForItem --
*
* Determine which columns of an item are onscreen.
*
* Results:
* Sets the column list.
*
* Side effects:
* Memory may be allocated.
*
*--------------------------------------------------------------
*/
static int
GetOnScreenColumnsForItem(
TreeCtrl *tree, /* Widget info. */
DItem *dItem, /* Display info for an item. */
TreeColumnList *columns /* Initialized list to append to. */
)
{
TreeDInfo dInfo = tree->dInfo;
if (TreeItem_GetHeader(tree, dItem->item) != NULL) {
TreeRectangle bounds;
if (Tree_AreaBbox(tree, TREE_AREA_HEADER_LEFT, &bounds)) {
TreeItem_GetOnScreenColumns(tree, dItem->item, COLUMN_LOCK_LEFT,
dItem->left.x, dItem->y, dItem->left.width, dItem->height,
columns);
}
if (Tree_AreaBbox(tree, TREE_AREA_HEADER_NONE, &bounds)) {
TreeItem_GetOnScreenColumns(tree, dItem->item, COLUMN_LOCK_NONE,
dItem->area.x, dItem->y, dItem->area.width, dItem->height,
columns);
}
if (Tree_AreaBbox(tree, TREE_AREA_HEADER_RIGHT, &bounds)) {
TreeItem_GetOnScreenColumns(tree, dItem->item, COLUMN_LOCK_RIGHT,
dItem->right.x, dItem->y, dItem->right.width, dItem->height,
columns);
}
} else {
if (!dInfo->emptyL) {
TreeItem_GetOnScreenColumns(tree, dItem->item, COLUMN_LOCK_LEFT,
dItem->left.x, dItem->y, dItem->left.width, dItem->height,
columns);
}
if (!dInfo->empty && dInfo->rangeFirstD != NULL) {
TreeItem_GetOnScreenColumns(tree, dItem->item, COLUMN_LOCK_NONE,
dItem->area.x, dItem->y, dItem->area.width, dItem->height,
columns);
}
if (!dInfo->emptyR) {
TreeItem_GetOnScreenColumns(tree, dItem->item, COLUMN_LOCK_RIGHT,
dItem->right.x, dItem->y, dItem->right.width, dItem->height,
columns);
}
}
return TreeColumnList_Count(columns);
}
/*
*--------------------------------------------------------------
*
* TrackOnScreenColumnsForItem --
*
* Compares the list of onscreen columns for an item to the
* list of previously-onscreen columns for the item.
*
* Results:
* Hides window elements for columns that are no longer
* onscreen.
*
* Side effects:
* Memory may be allocated.
*
*--------------------------------------------------------------
*/
static void
TrackOnScreenColumnsForItem(
TreeCtrl *tree, /* Widget info. */
TreeItem item, /* Item token. */
Tcl_HashEntry *hPtr /* DInfo.itemVisHash entry. */
)
{
TreeColumnList columns;
TreeColumn column, *value;
DItem *dItem;
int i, j, count = 0, n = 0;
Tcl_DString dString;
TreeColumnList_Init(tree, &columns, 0);
Tcl_DStringInit(&dString);
/* dItem is NULL if the item just went offscreen. */
dItem = (DItem *) TreeItem_GetDInfo(tree, item);
if (dItem != NULL)
count = GetOnScreenColumnsForItem(tree, dItem, &columns);
if (tree->debug.enable && tree->debug.span)
DStringAppendf(&dString, "onscreen columns for %s %d:",
TreeItem_GetHeader(tree, item) ? "header" : "item",
TreeItem_GetID(tree, item));
/* value is NULL if the item just came onscreen. */
value = (TreeColumn *) Tcl_GetHashValue(hPtr);
if (value == NULL) {
value = (TreeColumn *) ckalloc(sizeof(TreeColumn) * (count + 1));
value[0] = NULL;
}
/* Track newly-visible columns */
for (i = 0; i < count; i++) {
column = TreeColumnList_Nth(&columns, i);
for (j = 0; value[j] != NULL; j++) {
if (column == value[j])
break;
}
if (value[j] == NULL) {
if (tree->debug.enable && tree->debug.span) {
if (column == tree->columnTail)
DStringAppendf(&dString, " +tail");
else
DStringAppendf(&dString, " +%d", TreeColumn_GetID(column));
}
n++;
}
}
/* Track newly-hidden columns */
for (j = 0; value[j] != NULL; j++) {
column = value[j];
for (i = 0; i < count; i++) {
if (TreeColumnList_Nth(&columns, i) == column)
break;
}
if (i == count) {
TreeItemColumn itemColumn = TreeItem_FindColumn(tree, item,
TreeColumn_Index(column));
if (itemColumn != NULL) {
TreeStyle style = TreeItemColumn_GetStyle(tree, itemColumn);
if (style != NULL)
TreeStyle_OnScreen(tree, style, FALSE);
}
if (tree->debug.enable && tree->debug.span) {
if (column == tree->columnTail)
DStringAppendf(&dString, " -tail");
else
DStringAppendf(&dString, " -%d", TreeColumn_GetID(column));
}
n++;
}
}
if (n && tree->debug.enable && tree->debug.span)
dbwin("%s\n", Tcl_DStringValue(&dString));
/* Set the list of onscreen columns unless it is the same or the item
* is hidden. */
if (n > 0 && dItem != NULL) {
value = (TreeColumn *) ckrealloc((char *) value,
sizeof(TreeColumn) * (count + 1));
memcpy(value, (TreeColumn *) columns.pointers,
sizeof(TreeColumn) * count);
value[count] = NULL;
Tcl_SetHashValue(hPtr, (ClientData) value);
}
Tcl_DStringFree(&dString);
TreeColumnList_Free(&columns);
}
#endif /* DCOLUMN */
/*
*--------------------------------------------------------------
*
* UpdateDInfoForRange --
*
* Allocates or updates a DItem for every on-screen item in a Range.
* If an item already has a DItem (because the item was previously
* displayed), then the DItem may be marked dirty if there were
* changes to the item's on-screen size or position.
*
* Results:
* The return value is the possibly-updated dItemHead.
*
* Side effects:
* Memory may be allocated.
*
*--------------------------------------------------------------
*/
static DItem *
UpdateDInfoForRange(
TreeCtrl *tree, /* Widget info. */
DItem *dItemHead, /* Linked list of used DItems. */
Range *range, /* Range to update DItems for. */
RItem *rItem, /* First item in the Range we care about. */
int x, int y /* Left & top window coordinates of rItem. */
)
{
TreeDInfo dInfo = tree->dInfo;
DItem *dItem;
DItemArea *area;
TreeItem item;
int maxX, maxY;
int index, indexVis;
int bgImgWidth, bgImgHeight;
if (tree->backgroundImage != NULL) {
Tk_SizeOfImage(tree->backgroundImage, &bgImgWidth, &bgImgHeight);
}
maxX = Tree_ContentRight(tree);
maxY = Tree_ContentBottom(tree);
/* Top-to-bottom */
if (tree->vertical) {
while (1) {
item = rItem->item;
/* Update item/style layout. This can be needed when using fixed
* column widths. */
(void) TreeItem_Height(tree, item);
TreeItem_ToIndex(tree, item, &index, &indexVis);
switch (tree->backgroundMode) {
#ifdef DEPRECATED
case BG_MODE_INDEX:
#endif
case BG_MODE_ORDER: break;
#ifdef DEPRECATED
case BG_MODE_VISINDEX:
#endif
case BG_MODE_ORDERVIS: index = indexVis; break;
case BG_MODE_COLUMN: index = range->index; break;
case BG_MODE_ROW: index = rItem->index; break;
}
y = C2Wy(range->offset.y + rItem->offset);
dItem = (DItem *) TreeItem_GetDInfo(tree, item);
/* Re-use a previously allocated DItem */
if (dItem != NULL) {
dItemHead = DItem_Unlink(dItemHead, dItem);
area = &dItem->area;
/* This item is already marked for total redraw */
if (area->flags & DITEM_ALL_DIRTY)
; /* nothing */
/* All display info is marked as invalid */
else if (dInfo->flags & DINFO_INVALIDATE)
area->flags |= DITEM_DIRTY | DITEM_ALL_DIRTY;
else if ((dItem->flags & DITEM_INVALIDATE_ON_SCROLL_X)
&& (x != dItem->oldX))
area->flags |= DITEM_DIRTY | DITEM_ALL_DIRTY;
else if ((dItem->flags & DITEM_INVALIDATE_ON_SCROLL_Y)
&& (y != dItem->oldY))
area->flags |= DITEM_DIRTY | DITEM_ALL_DIRTY;
/* The range may have changed size */
else if ((area->width != range->totalWidth) ||
(dItem->height != rItem->size))
area->flags |= DITEM_DIRTY | DITEM_ALL_DIRTY;
/* Items may have alternating background colors. */
else if ((tree->columnBgCnt > 1) &&
((index % tree->columnBgCnt) !=
(dItem->index % tree->columnBgCnt)))
area->flags |= DITEM_DIRTY | DITEM_ALL_DIRTY;
/* We don't copy items horizontally to their new position,
* except for horizontal scrolling which moves the whole
* range */
else if (x - dItem->oldX != dInfo->xOrigin - tree->xOrigin)
area->flags |= DITEM_DIRTY | DITEM_ALL_DIRTY;
/* If we are displaying dotted lines and the item has moved
* from odd-top to non-odd-top or vice versa, must redraw
* the lines for this item. */
else if (tree->showLines &&
(tree->lineStyle == LINE_STYLE_DOT) &&
tree->columnTreeVis &&
(TreeColumn_Lock(tree->columnTree) == COLUMN_LOCK_NONE) &&
((DW2Cy(dItem->oldY) & 1) != (W2Cy(y) & 1)))
area->flags |= DITEM_DIRTY | DITEM_ALL_DIRTY;
/* We can't copy the item to its new position unless it
* has the same part of the background image behind it */
else if ((tree->backgroundImage != NULL) &&
(dInfo->xOrigin != tree->xOrigin) &&
!(tree->bgImageScroll & BGIMG_SCROLL_X))
area->flags |= DITEM_DIRTY | DITEM_ALL_DIRTY;
else if ((tree->backgroundImage != NULL) &&
(dInfo->yOrigin != tree->yOrigin) &&
!(tree->bgImageScroll & BGIMG_SCROLL_Y))
area->flags |= DITEM_DIRTY | DITEM_ALL_DIRTY;
else if ((tree->backgroundImage != NULL) &&
((DW2Cy(dItem->oldY) % bgImgHeight) !=
(W2Cy(y) % bgImgHeight)))
area->flags |= DITEM_DIRTY | DITEM_ALL_DIRTY;
}
/* Make a new DItem */
else {
dItem = DItem_Alloc(tree, rItem);
area = &dItem->area;
}
area->x = x;
dItem->y = y;
area->width = Range_TotalWidth(tree, range);
dItem->height = rItem->size;
dItem->range = range;
dItem->index = index;
dItem->spans = TreeItem_GetSpans(tree, dItem->item);
/* Keep track of the maximum item size */
if (area->width > dInfo->itemWidth)
dInfo->itemWidth = area->width;
if (dItem->height > dInfo->itemHeight)
dInfo->itemHeight = dItem->height;
/* Linked list of DItems */
if (dInfo->dItem == NULL)
dInfo->dItem = dItem;
else
dInfo->dItemLast->next = dItem;
dInfo->dItemLast = dItem;
if (rItem == range->last)
break;
rItem++;
/* Stop when out of bounds */
if (dItem->y + dItem->height >= maxY)
break;
}
}
/* Left-to-right */
else {
while (1) {
item = rItem->item;
/* Update item/style layout. This can be needed when using fixed
* column widths. */
(void) TreeItem_Height(tree, item);
TreeItem_ToIndex(tree, item, &index, &indexVis);
switch (tree->backgroundMode) {
#ifdef DEPRECATED
case BG_MODE_INDEX:
#endif
case BG_MODE_ORDER: break;
#ifdef DEPRECATED
case BG_MODE_VISINDEX:
#endif
case BG_MODE_ORDERVIS: index = indexVis; break;
case BG_MODE_COLUMN: index = rItem->index; break;
case BG_MODE_ROW: index = range->index; break;
}
x = C2Wx(range->offset.x + rItem->offset);
dItem = (DItem *) TreeItem_GetDInfo(tree, item);
/* Re-use a previously allocated DItem */
if (dItem != NULL) {
dItemHead = DItem_Unlink(dItemHead, dItem);
area = &dItem->area;
/* This item is already marked for total redraw */
if (area->flags & DITEM_ALL_DIRTY)
; /* nothing */
/* All display info is marked as invalid */
else if (dInfo->flags & DINFO_INVALIDATE)
area->flags |= DITEM_DIRTY | DITEM_ALL_DIRTY;
else if ((dItem->flags & DITEM_INVALIDATE_ON_SCROLL_X)
&& (x != dItem->oldX))
area->flags |= DITEM_DIRTY | DITEM_ALL_DIRTY;
else if ((dItem->flags & DITEM_INVALIDATE_ON_SCROLL_Y)
&& (y != dItem->oldY))
area->flags |= DITEM_DIRTY | DITEM_ALL_DIRTY;
/* The range may have changed size */
else if ((area->width != rItem->size) ||
(dItem->height != range->totalHeight))
area->flags |= DITEM_DIRTY | DITEM_ALL_DIRTY;
/* Items may have alternating background colors. */
else if ((tree->columnBgCnt > 1) &&
((index % tree->columnBgCnt) !=
(dItem->index % tree->columnBgCnt)))
area->flags |= DITEM_DIRTY | DITEM_ALL_DIRTY;
/* We don't copy items vertically to their new position,
* except for vertical scrolling which moves the whole range */
else if (y - dItem->oldY != dInfo->yOrigin - tree->yOrigin)
area->flags |= DITEM_DIRTY | DITEM_ALL_DIRTY;
/* If we are displaying dotted lines and the item has moved
* from odd-top to non-odd-top or vice versa, must redraw
* the lines for this item. */
else if (tree->showLines &&
(tree->lineStyle == LINE_STYLE_DOT) &&
tree->columnTreeVis &&
((DW2Cy(dItem->oldY) & 1) != (W2Cy(y) & 1)))
area->flags |= DITEM_DIRTY | DITEM_ALL_DIRTY;
/* We can't copy the item to its new position unless it
* has the same part of the background image behind it */
else if ((tree->backgroundImage != NULL) &&
(dInfo->xOrigin != tree->xOrigin) &&
!(tree->bgImageScroll & BGIMG_SCROLL_X))
area->flags |= DITEM_DIRTY | DITEM_ALL_DIRTY;
else if ((tree->backgroundImage != NULL) &&
(dInfo->yOrigin != tree->yOrigin) &&
!(tree->bgImageScroll & BGIMG_SCROLL_Y))
area->flags |= DITEM_DIRTY | DITEM_ALL_DIRTY;
else if ((tree->backgroundImage != NULL) &&
((DW2Cx(dItem->oldX) % bgImgWidth) !=
(W2Cx(x) % bgImgWidth)))
area->flags |= DITEM_DIRTY | DITEM_ALL_DIRTY;
}
/* Make a new DItem */
else {
dItem = DItem_Alloc(tree, rItem);
area = &dItem->area;
}
area->x = x;
dItem->y = y;
area->width = rItem->size;
dItem->height = Range_TotalHeight(tree, range);
dItem->range = range;
dItem->index = index;
dItem->spans = TreeItem_GetSpans(tree, dItem->item);
/* Keep track of the maximum item size */
if (area->width > dInfo->itemWidth)
dInfo->itemWidth = area->width;
if (dItem->height > dInfo->itemHeight)
dInfo->itemHeight = dItem->height;
/* Linked list of DItems */
if (dInfo->dItem == NULL)
dInfo->dItem = dItem;
else
dInfo->dItemLast->next = dItem;
dInfo->dItemLast = dItem;
if (rItem == range->last)
break;
rItem++;
/* Stop when out of bounds */
if (area->x + area->width >= maxX)
break;
}
}
return dItemHead;
}
/*
*--------------------------------------------------------------
*
* Tree_UpdateDInfo --
*
* At the finish of this procedure every on-screen item will have
* a DItem associated with it and no off-screen item will have
* a DItem.
*
* Results:
* None.
*
* Side effects:
* Memory may be allocated.
*
*--------------------------------------------------------------
*/
static void
Tree_UpdateDInfo(
TreeCtrl *tree /* Widget info. */
)
{
TreeDInfo dInfo = tree->dInfo;
DItem *dItemHead = dInfo->dItem;
int x, y, rx = 0, ry = 0, ix, iy, dx, dy;
int minX, minY, maxX, maxY;
Range *range;
RItem *rItem;
DItem *dItem;
if (tree->debug.enable && tree->debug.display)
dbwin("Tree_UpdateDInfo %s\n", Tk_PathName(tree->tkwin));
dInfo->dItem = dInfo->dItemLast = NULL;
dInfo->rangeFirstD = dInfo->rangeLastD = NULL;
dInfo->itemWidth = dInfo->itemHeight = 0;
dInfo->empty = !Tree_AreaBbox(tree, TREE_AREA_CONTENT, &dInfo->bounds);
dInfo->emptyL = !Tree_AreaBbox(tree, TREE_AREA_LEFT, &dInfo->boundsL);
dInfo->emptyR = !Tree_AreaBbox(tree, TREE_AREA_RIGHT, &dInfo->boundsR);
if (dInfo->empty)
goto done;
TreeRect_XYXY(dInfo->bounds, &minX, &minY, &maxX, &maxY);
range = dInfo->rangeFirst;
if (tree->vertical) {
/* Find the first range which could be onscreen horizontally.
* It may not be onscreen if it has less height than other ranges. */
while (range != NULL) {
if ((range->offset.x < W2Cx(maxX)) &&
(range->offset.x + range->totalWidth > W2Cx(minX))) {
rx = range->offset.x;
ry = range->offset.y;
break;
}
range = range->next;
}
}
else {
/* Find the first range which could be onscreen vertically.
* It may not be onscreen if it has less width than other ranges. */
while (range != NULL) {
if ((range->offset.y < W2Cy(maxY)) &&
(range->offset.y + range->totalHeight > W2Cy(minY))) {
rx = range->offset.x;
ry = range->offset.y;
break;
}
range = range->next;
}
}
while (range != NULL) {
rx = range->offset.x;
ry = range->offset.y;
if (tree->vertical) {
if (rx >= W2Cx(maxX))
break;
}
else {
if (ry >= W2Cy(maxY))
break;
}
if ((rx + range->totalWidth > W2Cx(minX)) &&
(ry + range->totalHeight > W2Cy(minY))) {
dx = MAX(W2Cx(minX) - rx, 0);
dy = MAX(W2Cy(minY) - ry, 0);
rItem = Range_ItemUnderPoint(tree, range, dx, dy, &ix, &iy, 3);
/* Window coords of top-left of item */
x = C2Wx(rx) + dx - ix;
y = C2Wy(ry) + dy - iy;
dItemHead = UpdateDInfoForRange(tree, dItemHead, range, rItem, x, y);
}
/* Track this range even if it has no DItems, so whitespace is
* erased */
if (dInfo->rangeFirstD == NULL)
dInfo->rangeFirstD = range;
dInfo->rangeLastD = range;
range = range->next;
}
if (dInfo->dItemLast != NULL)
dInfo->dItemLast->next = NULL;
done:
if (dInfo->dItem != NULL)
goto skipLock;
if (!tree->itemVisCount)
goto skipLock;
if (dInfo->emptyL && dInfo->emptyR)
goto skipLock;
range = dInfo->rangeFirst;
if ((range != NULL) && !range->totalHeight)
goto skipLock;
{
int y = W2Cy(Tree_ContentTop(tree));
int index, indexVis;
/* If no non-locked columns are displayed, we have no Range and
* must use dInfo->rangeLock. */
if (range == NULL) {
range = dInfo->rangeLock;
}
/* Find the first item on-screen vertically. */
rItem = Range_ItemUnderPoint(tree, range, -666, y, NULL, &y, 3);
y = C2Wy(range->offset.y + rItem->offset);
while (1) {
DItem *dItem = (DItem *) TreeItem_GetDInfo(tree, rItem->item);
/* Re-use a previously allocated DItem */
if (dItem != NULL) {
dItemHead = DItem_Unlink(dItemHead, dItem);
} else {
dItem = DItem_Alloc(tree, rItem);
}
TreeItem_ToIndex(tree, rItem->item, &index, &indexVis);
switch (tree->backgroundMode) {
#ifdef DEPRECATED
case BG_MODE_INDEX:
#endif
case BG_MODE_ORDER: break;
#ifdef DEPRECATED
case BG_MODE_VISINDEX:
#endif
case BG_MODE_ORDERVIS: index = indexVis; break;
case BG_MODE_COLUMN: index = range->index; break;
case BG_MODE_ROW: index = rItem->index; break;
}
dItem->y = C2Wy(range->offset.y + rItem->offset); /* Canvas -> Window */
dItem->height = rItem->size;
dItem->range = range;
dItem->index = index;
dItem->spans = TreeItem_GetSpans(tree, dItem->item);
/* Keep track of the maximum item size */
if (dItem->height > dInfo->itemHeight)
dInfo->itemHeight = dItem->height;
/* Linked list of DItems */
if (dInfo->dItem == NULL)
dInfo->dItem = dItem;
else
dInfo->dItemLast->next = dItem;
dInfo->dItemLast = dItem;
if (rItem == range->last)
break;
if (dItem->y + dItem->height >= Tree_ContentBottom(tree))
break;
rItem++;
}
}
skipLock:
if (!dInfo->emptyL || !dInfo->emptyR) {
int bgImgWidth, bgImgHeight;
DItemArea *area;
if (!dInfo->emptyL) {
/* Keep track of the maximum item size */
if (dInfo->widthOfColumnsLeft > dInfo->itemWidth)
dInfo->itemWidth = dInfo->widthOfColumnsLeft;
}
if (!dInfo->emptyR) {
/* Keep track of the maximum item size */
if (dInfo->widthOfColumnsRight > dInfo->itemWidth)
dInfo->itemWidth = dInfo->widthOfColumnsRight;
}
if (tree->backgroundImage != NULL)
Tk_SizeOfImage(tree->backgroundImage, &bgImgWidth, &bgImgHeight);
for (dItem = dInfo->dItem; dItem != NULL; dItem = dItem->next) {
if (!dInfo->emptyL) {
area = &dItem->left;
area->x = Tree_BorderLeft(tree);
area->width = dInfo->widthOfColumnsLeft;
/* This item is already marked for total redraw */
if (area->flags & DITEM_ALL_DIRTY) {
; /* nothing */
/* All display info is marked as invalid */
} else if (dInfo->flags & DINFO_INVALIDATE) {
area->flags |= DITEM_DIRTY | DITEM_ALL_DIRTY;
/* Items may have alternating background colors. */
} else if ((tree->columnBgCnt > 1) &&
((dItem->oldIndex % tree->columnBgCnt) !=
(dItem->index % tree->columnBgCnt))) {
area->flags |= DITEM_DIRTY | DITEM_ALL_DIRTY;
/* If we are displaying dotted lines and the item has moved
* from odd-top to non-odd-top or vice versa, must redraw
* the lines for this item. */
} else if (tree->showLines &&
(tree->lineStyle == LINE_STYLE_DOT) &&
tree->columnTreeVis &&
(TreeColumn_Lock(tree->columnTree) == COLUMN_LOCK_LEFT) &&
((DW2Cy(dItem->oldY) & 1) != (W2Cy(dItem->y) & 1))) {
area->flags |= DITEM_DIRTY | DITEM_ALL_DIRTY;
/* We can't copy the item to its new position unless it
* has the same part of the background image behind it */
} else if ((tree->backgroundImage != NULL) &&
((dInfo->xOrigin % bgImgWidth) !=
(tree->xOrigin % bgImgWidth))) {
area->flags |= DITEM_DIRTY | DITEM_ALL_DIRTY;
}
}
if (!dInfo->emptyR) {
area = &dItem->right;
area->x = Tree_ContentRight(tree);
area->width = dInfo->widthOfColumnsRight;
/* This item is already marked for total redraw */
if (area->flags & DITEM_ALL_DIRTY) {
; /* nothing */
/* All display info is marked as invalid */
} else if (dInfo->flags & DINFO_INVALIDATE) {
area->flags |= DITEM_DIRTY | DITEM_ALL_DIRTY;
/* Items may have alternating background colors. */
} else if ((tree->columnBgCnt > 1) &&
((dItem->oldIndex % tree->columnBgCnt) !=
(dItem->index % tree->columnBgCnt))) {
area->flags |= DITEM_DIRTY | DITEM_ALL_DIRTY;
/* If we are displaying dotted lines and the item has moved
* from odd-top to non-odd-top or vice versa, must redraw
* the lines for this item. */
} else if (tree->showLines &&
(tree->lineStyle == LINE_STYLE_DOT) &&
tree->columnTreeVis &&
(TreeColumn_Lock(tree->columnTree) == COLUMN_LOCK_RIGHT) &&
((DW2Cy(dItem->oldY) & 1) != (W2Cy(dItem->y) & 1))) {
area->flags |= DITEM_DIRTY | DITEM_ALL_DIRTY;
/* We can't copy the item to its new position unless it
* has the same part of the background image behind it */
} else if ((tree->backgroundImage != NULL) &&
((dInfo->xOrigin % bgImgWidth) !=
(tree->xOrigin % bgImgWidth))) {
area->flags |= DITEM_DIRTY | DITEM_ALL_DIRTY;
}
}
}
}
while (dItemHead != NULL)
dItemHead = DItem_Free(tree, dItemHead);
dInfo->flags &= ~DINFO_INVALIDATE;
}
/*
*--------------------------------------------------------------
*
* InvalidateWhitespace --
*
* Subtract a rectangular area from the current whitespace
* region.
*
* Results:
* None.
*
* Side effects:
* None.
*
*--------------------------------------------------------------
*/
static void
InvalidateWhitespace(
TreeCtrl *tree, /* Widget info. */
int x1, int y1, /* Window coords to invalidate. */
int x2, int y2) /* Window coords to invalidate. */
{
TreeDInfo dInfo = tree->dInfo;
if ((x1 < x2 && y1 < y2) && TkRectInRegion(dInfo->wsRgn, x1, y1,
x2 - x1, y2 - y1)) {
XRectangle rect;
TkRegion rgn = Tree_GetRegion(tree);
rect.x = x1;
rect.y = y1;
rect.width = x2 - x1;
rect.height = y2 - y1;
TkUnionRectWithRegion(&rect, rgn, rgn);
TkSubtractRegion(dInfo->wsRgn, rgn, dInfo->wsRgn);
Tree_FreeRegion(tree, rgn);
}
}
/*
*--------------------------------------------------------------
*
* InvalidateDItemX --
*
* Mark a horizontal span of a DItem as dirty (needing to be
* redrawn). The caller must set the DITEM_DIRTY flag afterwards.
*
* Results:
* None.
*
* Side effects:
* None.
*
*--------------------------------------------------------------
*/
static void
InvalidateDItemX(
DItem *dItem, /* Item to mark dirty. */
DItemArea *area,
int itemX, /* x-coordinate of item. */
int dirtyX, /* Left edge of area to mark as dirty. */
int dirtyWidth /* Width of area to mark as dirty. */
)
{
int x1, x2;
if (dirtyX <= itemX)
area->dirty[LEFT] = 0;
else {
x1 = dirtyX - itemX;
if (!(area->flags & DITEM_DIRTY) || (x1 < area->dirty[LEFT]))
area->dirty[LEFT] = x1;
}
if (dirtyX + dirtyWidth >= itemX + area->width)
area->dirty[RIGHT] = area->width;
else {
x2 = dirtyX + dirtyWidth - itemX;
if (!(area->flags & DITEM_DIRTY) || (x2 > area->dirty[RIGHT]))
area->dirty[RIGHT] = x2;
}
}
/*
*--------------------------------------------------------------
*
* InvalidateDItemY --
*
* Mark a vertical span of a DItem as dirty (needing to be
* redrawn). The caller must set the DITEM_DIRTY flag afterwards.
*
* Results:
* None.
*
* Side effects:
* None.
*
*--------------------------------------------------------------
*/
static void
InvalidateDItemY(
DItem *dItem, /* Item to mark dirty. */
DItemArea *area,
int itemY, /* y-coordinate of item. */
int dirtyY, /* Top edge of area to mark as dirty. */
int dirtyHeight /* Height of area to mark as dirty. */
)
{
int y1, y2;
if (dirtyY <= itemY)
area->dirty[TOP] = 0;
else {
y1 = dirtyY - itemY;
if (!(area->flags & DITEM_DIRTY) || (y1 < area->dirty[TOP]))
area->dirty[TOP] = y1;
}
if (dirtyY + dirtyHeight >= itemY + dItem->height)
area->dirty[BOTTOM] = dItem->height;
else {
y2 = dirtyY + dirtyHeight - itemY;
if (!(area->flags & DITEM_DIRTY) || (y2 > area->dirty[BOTTOM]))
area->dirty[BOTTOM] = y2;
}
}
/*
*--------------------------------------------------------------
*
* Range_RedoIfNeeded --
*
* Recalculate the list of Ranges if they are marked out-of-date.
* Also calculate the height and width of the canvas based on the
* list of Ranges.
*
* Results:
* None.
*
* Side effects:
* Memory may be allocated.
*
*--------------------------------------------------------------
*/
static void
Range_RedoIfNeeded(
TreeCtrl *tree /* Widget info. */
)
{
TreeDInfo dInfo = tree->dInfo;
CheckPendingHeaderUpdate(tree);
if (dInfo->flags & DINFO_REDO_RANGES) {
dInfo->rangeFirstD = dInfo->rangeLastD = NULL;
dInfo->flags |= DINFO_OUT_OF_DATE;
Range_Redo(tree);
dInfo->flags &= ~DINFO_REDO_RANGES;
#ifdef COMPLEX_WHITESPACE
if (ComplexWhitespace(tree)) {
dInfo->flags |= DINFO_DRAW_WHITESPACE;
}
#endif
#if COLUMNGRID == 1
if (GridLinesInWhiteSpace(tree)) {
dInfo->flags |= DINFO_DRAW_WHITESPACE;
}
#endif
/* Do this after clearing REDO_RANGES to prevent infinite loop */
tree->totalWidth = tree->totalHeight = -1;
(void) Tree_CanvasWidth(tree);
(void) Tree_CanvasHeight(tree);
dInfo->flags |= DINFO_REDO_INCREMENTS;
}
}
/*
*--------------------------------------------------------------
*
* DblBufWinDirty --
*
* Add a rectangle to the dirty region of the "-doublebuffer window"
* pixmap.
*
* Results:
* None.
*
* Side effects:
* None.
*
*--------------------------------------------------------------
*/
static void
DblBufWinDirty(
TreeCtrl *tree,
int x1,
int y1,
int x2,
int y2
)
{
TreeDInfo dInfo = tree->dInfo;
XRectangle rect;
/* Fix BUG ID: 3015429 */
if (x1 >= x2 || y1 >= y2)
return;
rect.x = x1;
rect.y = y1;
rect.width = x2 - x1;
rect.height = y2 - y1;
TkUnionRectWithRegion(&rect, dInfo->dirtyRgn, dInfo->dirtyRgn);
}
#if REDRAW_RGN == 1
static void
AddRgnToRedrawRgn(
TreeCtrl *tree,
TkRegion rgn
)
{
TreeDInfo dInfo = tree->dInfo;
Tree_UnionRegion(dInfo->redrawRgn, rgn, dInfo->redrawRgn);
}
static void
AddRectToRedrawRgn(
TreeCtrl *tree,
int minX,
int minY,
int maxX,
int maxY
)
{
TkRegion rgn = Tree_GetRegion(tree);
TreeRectangle rect;
rect.x = minX;
rect.y = minY;
rect.width = maxX - minX;
rect.height = maxY - minY;
Tree_SetRectRegion(rgn, &rect);
AddRgnToRedrawRgn(tree, rgn);
Tree_FreeRegion(tree, rgn);
}
#endif /* REDRAW_RGN */
/*
*--------------------------------------------------------------
*
* DItemAllDirty --
*
* Determine if a DItem will be entirely redrawn in all columns.
*
* Results:
* None.
*
* Side effects:
* None.
*
*--------------------------------------------------------------
*/
static int
DItemAllDirty(
TreeCtrl *tree,
DItem *dItem
)
{
if ((dItem->area.flags & DITEM_DRAWN) &&
!(dItem->area.flags & DITEM_ALL_DIRTY))
return 0;
if ((dItem->left.flags & DITEM_DRAWN) &&
!(dItem->left.flags & DITEM_ALL_DIRTY))
return 0;
if ((dItem->right.flags & DITEM_DRAWN) &&
!(dItem->right.flags & DITEM_ALL_DIRTY))
return 0;
return 1;
}
/*
*--------------------------------------------------------------
*
* ScrollHeaders --
*
* Scrolls the header area horizontally if needed.
*
* Results:
* Pixels are copied in the TreeCtrl window or in the
* offscreen pixmap (if double-buffering is used).
*
* Side effects:
* None.
*
*--------------------------------------------------------------
*/
static void
ScrollHeaders(
TreeCtrl *tree /* Widget info. */
)
{
TreeDInfo dInfo = tree->dInfo;
TreeRectangle bounds;
DItem *dItem;
TkRegion damageRgn;
int minX, minY, maxX, maxY;
int width, offset;
int x;
int dirtyMin, dirtyMax;
if (dInfo->xOrigin == tree->xOrigin)
return;
if (!Tree_AreaBbox(tree, TREE_AREA_HEADER_NONE, &bounds))
return;
TreeRect_XYXY(bounds, &minX, &minY, &maxX, &maxY);
offset = dInfo->xOrigin - tree->xOrigin;
/* Update oldX */
for (dItem = dInfo->dItemHeader;
dItem != NULL;
dItem = dItem->next) {
dItem->oldX = dItem->area.x;
}
/* Simplify if a whole screen was scrolled. */
if (abs(offset) >= maxX - minX) {
for (dItem = dInfo->dItemHeader;
dItem != NULL;
dItem = dItem->next) {
dItem->area.flags |= DITEM_DIRTY | DITEM_ALL_DIRTY;
dItem->left.flags |= DITEM_DIRTY | DITEM_ALL_DIRTY;
dItem->right.flags |= DITEM_DIRTY | DITEM_ALL_DIRTY;
}
return;
}
width = maxX - minX - abs(offset);
/* Move pixels right */
if (offset > 0) {
x = minX;
dirtyMin = minX;
dirtyMax = maxX - width;
/* Move pixels left */
} else {
x = maxX - width;
dirtyMin = minX + width;
dirtyMax = maxX;
}
if (tree->doubleBuffer == DOUBLEBUFFER_WINDOW) {
XCopyArea(tree->display, dInfo->pixmapW.drawable,
dInfo->pixmapW.drawable,
tree->copyGC,
x, minY, width, maxY - minY,
x + offset, minY);
} else {
damageRgn = Tree_GetRegion(tree);
if (Tree_ScrollWindow(tree, dInfo->scrollGC,
x, minY, width, maxY - minY, offset, 0, damageRgn)) {
DisplayDelay(tree);
Tree_InvalidateRegion(tree, damageRgn);
}
Tree_FreeRegion(tree, damageRgn);
}
Tree_InvalidateArea(tree, dirtyMin, minY, dirtyMax, maxY);
}
/*
*--------------------------------------------------------------
*
* ScrollVerticalComplex --
*
* Perform scrolling by copying the pixels of items from the
* previous display position to the current position. Any areas
* of items copied over by the moved items are marked dirty.
* This is called when -orient=vertical.
*
* Results:
* The number of items whose pixels were copied.
*
* Side effects:
* Pixels are copied in the TreeCtrl window or in the
* offscreen pixmap (if double-buffering is used).
*
*--------------------------------------------------------------
*/
static int
ScrollVerticalComplex(
TreeCtrl *tree /* Widget info. */
)
{
TreeDInfo dInfo = tree->dInfo;
DItem *dItem, *dItem2;
Range *range;
TkRegion damageRgn;
int minX, minY, maxX, maxY;
int oldX, oldY, width, height, offset;
int y;
int numCopy = 0;
if (dInfo->empty && dInfo->emptyL && dInfo->emptyR)
return 0;
minX = Tree_BorderLeft(tree);
minY = Tree_ContentTop(tree);
maxX = Tree_BorderRight(tree);
maxY = Tree_ContentBottom(tree);
/* Try updating the display by copying items on the screen to their
* new location */
for (dItem = dInfo->dItem;
dItem != NULL;
dItem = dItem->next) {
/* Copy an item to its new location unless:
* (a) item display info is invalid
* (b) item is in same location as last draw */
if (DItemAllDirty(tree, dItem) ||
(dItem->oldY == dItem->y))
continue;
numCopy++;
range = dItem->range;
/* This item was previously displayed so it only needs to be
* copied to the new location. Copy all such items as one */
offset = dItem->y - dItem->oldY;
height = dItem->height;
for (dItem2 = dItem->next;
dItem2 != NULL;
dItem2 = dItem2->next) {
if ((dItem2->range != range) ||
DItemAllDirty(tree, dItem2) ||
(dItem2->oldY + offset != dItem2->y))
break;
numCopy++;
height = dItem2->y + dItem2->height - dItem->y;
}
y = dItem->y;
oldY = dItem->oldY;
/* Don't copy part of the window border */
if (oldY < minY) {
height -= minY - oldY;
oldY = minY;
}
if (oldY + height > maxY)
height = maxY - oldY;
/* Don't copy over the window border */
if (oldY + offset < minY) {
height -= minY - (oldY + offset);
oldY += minY - (oldY + offset);
}
if (oldY + offset + height > maxY)
height = maxY - (oldY + offset);
if (!dInfo->emptyL || !dInfo->emptyR) {
oldX = minX;
width = maxX - minX;
} else {
oldX = dItem->oldX;
width = dItem->area.width;
}
if (oldX < minX) {
width -= minX - oldX;
oldX = minX;
}
if (oldX + width > maxX)
width = maxX - oldX;
/* Update oldY of copied items */
while (1) {
/* If an item was partially visible, invalidate the exposed area */
if ((dItem->oldY < minY) && (offset > 0)) {
if (!dInfo->empty && dInfo->rangeFirstD != NULL) {
InvalidateDItemX(dItem, &dItem->area, dItem->oldX, oldX, width);
InvalidateDItemY(dItem, &dItem->area, dItem->oldY, dItem->oldY, minY - dItem->oldY);
dItem->area.flags |= DITEM_DIRTY;
}
if (!dInfo->emptyL) {
InvalidateDItemX(dItem, &dItem->left, dItem->left.x, oldX, width);
InvalidateDItemY(dItem, &dItem->left, dItem->oldY, dItem->oldY, minY - dItem->oldY);
dItem->left.flags |= DITEM_DIRTY;
}
if (!dInfo->emptyR) {
InvalidateDItemX(dItem, &dItem->right, dItem->right.x, oldX, width);
InvalidateDItemY(dItem, &dItem->right, dItem->oldY, dItem->oldY, minY - dItem->oldY);
dItem->right.flags |= DITEM_DIRTY;
}
}
if ((dItem->oldY + dItem->height > maxY) && (offset < 0)) {
if (!dInfo->empty && dInfo->rangeFirstD != NULL) {
InvalidateDItemX(dItem, &dItem->area, dItem->oldX, oldX, width);
InvalidateDItemY(dItem, &dItem->area, dItem->oldY, maxY, maxY - dItem->oldY + dItem->height);
dItem->area.flags |= DITEM_DIRTY;
}
if (!dInfo->emptyL) {
InvalidateDItemX(dItem, &dItem->left, dItem->left.x, oldX, width);
InvalidateDItemY(dItem, &dItem->left, dItem->oldY, maxY, maxY - dItem->oldY + dItem->height);
dItem->left.flags |= DITEM_DIRTY;
}
if (!dInfo->emptyR) {
InvalidateDItemX(dItem, &dItem->right, dItem->right.x, oldX, width);
InvalidateDItemY(dItem, &dItem->right, dItem->oldY, maxY, maxY - dItem->oldY + dItem->height);
dItem->right.flags |= DITEM_DIRTY;
}
}
dItem->oldY = dItem->y;
if (dItem->next == dItem2)
break;
dItem = dItem->next;
}
/* Invalidate parts of items being copied over */
for ( ; dItem2 != NULL; dItem2 = dItem2->next) {
if (dItem2->range != range)
break;
if (!DItemAllDirty(tree, dItem2) &&
(dItem2->oldY + dItem2->height > y) &&
(dItem2->oldY < y + height)) {
if (!dInfo->empty && dInfo->rangeFirstD != NULL) {
InvalidateDItemX(dItem2, &dItem2->area, dItem2->oldX, oldX, width);
InvalidateDItemY(dItem2, &dItem2->area, dItem2->oldY, y, height);
dItem2->area.flags |= DITEM_DIRTY;
}
if (!dInfo->emptyL) {
InvalidateDItemX(dItem2, &dItem2->left, dItem2->left.x, oldX, width);
InvalidateDItemY(dItem2, &dItem2->left, dItem2->oldY, y, height);
dItem2->left.flags |= DITEM_DIRTY;
}
if (!dInfo->emptyR) {
InvalidateDItemX(dItem2, &dItem2->right, dItem2->right.x, oldX, width);
InvalidateDItemY(dItem2, &dItem2->right, dItem2->oldY, y, height);
dItem2->right.flags |= DITEM_DIRTY;
}
}
}
if (tree->doubleBuffer == DOUBLEBUFFER_WINDOW) {
int dirtyMin, dirtyMax;
XCopyArea(tree->display, dInfo->pixmapW.drawable,
dInfo->pixmapW.drawable, tree->copyGC,
oldX, oldY, width, height,
oldX, oldY + offset);
if (offset < 0) {
dirtyMin = oldY + offset + height;
dirtyMax = oldY + height;
} else {
dirtyMin = oldY;
dirtyMax = oldY + offset;
}
Tree_InvalidateArea(tree, oldX, dirtyMin, oldX + width, dirtyMax);
DblBufWinDirty(tree, oldX, oldY + offset,
oldX + width, oldY + offset + height);
continue;
}
/* Copy */
damageRgn = Tree_GetRegion(tree);
if (Tree_ScrollWindow(tree, dInfo->scrollGC,
oldX, oldY, width, height, 0, offset, damageRgn)) {
DisplayDelay(tree);
Tree_InvalidateRegion(tree, damageRgn);
}
Tree_FreeRegion(tree, damageRgn);
}
return numCopy;
}
/*
*--------------------------------------------------------------
*
* ScrollHorizontalSimple --
*
* Perform scrolling by shifting the pixels in the content
* area of the list to the left or right.
* This is called when -orient=vertical.
*
* Results:
* None.
*
* Side effects:
* Stuff is copied/scrolled in the TreeCtrl window or in the
* offscreen pixmap (if double-buffering is used).
*
*--------------------------------------------------------------
*/
static void
ScrollHorizontalSimple(
TreeCtrl *tree /* Widget info. */
)
{
TreeDInfo dInfo = tree->dInfo;
DItem *dItem;
TkRegion damageRgn;
int minX, minY, maxX, maxY;
int width, offset;
int x, y;
int dirtyMin, dirtyMax;
if (dInfo->xOrigin == tree->xOrigin)
return;
/* Only column headers are visible. */
if (dInfo->rangeFirst == NULL)
return;
if (dInfo->empty)
return;
TreeRect_XYXY(dInfo->bounds, &minX, &minY, &maxX, &maxY);
/* Update oldX */
for (dItem = dInfo->dItem;
dItem != NULL;
dItem = dItem->next) {
dItem->oldX = dItem->area.x;
}
offset = dInfo->xOrigin - tree->xOrigin;
/* We only scroll the content, not the whitespace */
y = C2Wy(Tree_CanvasHeight(tree));
if (y < maxY)
maxY = y;
/* Simplify if a whole screen was scrolled. */
if (abs(offset) >= maxX - minX) {
Tree_InvalidateArea(tree, minX, minY, maxX, maxY);
return;
}
/* We only scroll the content, not the whitespace */
x = C2Wx(Tree_CanvasWidth(tree));
if (x < maxX)
maxX = x;
width = maxX - minX - abs(offset);
/* Move pixels right */
if (offset > 0) {
x = minX;
dirtyMin = minX;
dirtyMax = maxX - width;
/* Move pixels left */
} else {
x = maxX - width;
dirtyMin = minX + width;
dirtyMax = maxX;
}
if (tree->doubleBuffer == DOUBLEBUFFER_WINDOW) {
XCopyArea(tree->display, dInfo->pixmapW.drawable,
dInfo->pixmapW.drawable,
tree->copyGC,
x, minY, width, maxY - minY,
x + offset, minY);
} else {
damageRgn = Tree_GetRegion(tree);
if (Tree_ScrollWindow(tree, dInfo->scrollGC,
x, minY, width, maxY - minY, offset, 0, damageRgn)) {
DisplayDelay(tree);
Tree_InvalidateRegion(tree, damageRgn);
}
Tree_FreeRegion(tree, damageRgn);
}
Tree_InvalidateArea(tree, dirtyMin, minY, dirtyMax, maxY);
/* Invalidate the part of the whitespace that the content was copied
* over. */
{
TkRegion rgn;
rgn = Tree_GetRectRegion(tree, &dInfo->bounds);
TkSubtractRegion(rgn, dInfo->wsRgn, rgn);
Tree_OffsetRegion(rgn, offset, 0);
TkSubtractRegion(dInfo->wsRgn, rgn, dInfo->wsRgn);
Tree_FreeRegion(tree, rgn);
}
}
/*
*--------------------------------------------------------------
*
* ScrollVerticalSimple --
*
* Perform scrolling by shifting the pixels in the content area of
* the list up or down. This is called when -orient=horizontal.
*
* Results:
* None.
*
* Side effects:
* Stuff is copied/scrolled in the TreeCtrl window or in the
* offscreen pixmap (if double-buffering is used).
*
*--------------------------------------------------------------
*/
static void
ScrollVerticalSimple(
TreeCtrl *tree /* Widget info. */
)
{
TreeDInfo dInfo = tree->dInfo;
DItem *dItem;
TkRegion damageRgn;
int minX, minY, maxX, maxY;
int height, offset;
int x, y;
int dirtyMin, dirtyMax;
if (dInfo->yOrigin == tree->yOrigin)
return;
/* Update oldY */
for (dItem = dInfo->dItem;
dItem != NULL;
dItem = dItem->next) {
dItem->oldY = dItem->y;
}
if (dInfo->empty)
return;
TreeRect_XYXY(dInfo->bounds, &minX, &minY, &maxX, &maxY);
offset = dInfo->yOrigin - tree->yOrigin;
/* Scroll the items, not the whitespace to the right */
x = C2Wx(Tree_CanvasWidth(tree));
if (x < maxX)
maxX = x;
/* Simplify if a whole screen was scrolled. */
if (abs(offset) > maxY - minY) {
Tree_InvalidateArea(tree, minX, minY, maxX, maxY);
return;
}
height = maxY - minY - abs(offset);
/* Move pixels down */
if (offset > 0) {
y = minY;
dirtyMin = minY;
dirtyMax = maxY - height;
/* Move pixels up */
} else {
y = maxY - height;
dirtyMin = minY + height;
dirtyMax = maxY;
}
if (tree->doubleBuffer == DOUBLEBUFFER_WINDOW) {
XCopyArea(tree->display, dInfo->pixmapW.drawable,
dInfo->pixmapW.drawable, tree->copyGC,
minX, y, maxX - minX, height,
minX, y + offset);
} else {
damageRgn = Tree_GetRegion(tree);
if (Tree_ScrollWindow(tree, dInfo->scrollGC,
minX, y, maxX - minX, height, 0, offset, damageRgn)) {
DisplayDelay(tree);
Tree_InvalidateRegion(tree, damageRgn);
}
Tree_FreeRegion(tree, damageRgn);
}
Tree_InvalidateArea(tree, minX, dirtyMin, maxX, dirtyMax);
/* Invalidate the part of the whitespace that the content was copied
* over. */
{
TkRegion rgn;
rgn = Tree_GetRectRegion(tree, &dInfo->bounds);
TkSubtractRegion(rgn, dInfo->wsRgn, rgn);
Tree_OffsetRegion(rgn, 0, offset);
TkSubtractRegion(dInfo->wsRgn, rgn, dInfo->wsRgn);
Tree_FreeRegion(tree, rgn);
}
}
/*
*--------------------------------------------------------------
*
* ScrollHorizontalComplex --
*
* Perform scrolling by copying the pixels of items from the
* previous display position to the current position. Any areas
* of items copied over by the moved items are marked dirty.
* This is called when -orient=horizontal.
*
* Results:
* The number of items whose pixels were copied.
*
* Side effects:
* Pixels are copied in the TreeCtrl window or in the
* offscreen pixmap (if double-buffering is used).
*
*--------------------------------------------------------------
*/
static int
ScrollHorizontalComplex(
TreeCtrl *tree /* Widget info. */
)
{
TreeDInfo dInfo = tree->dInfo;
DItem *dItem, *dItem2;
Range *range;
TkRegion damageRgn;
TreeRectangle tr;
int minX, minY, maxX, maxY;
int oldX, oldY, width, height, offset;
int x;
int numCopy = 0;
if (!Tree_AreaBbox(tree, TREE_AREA_CONTENT, &tr))
return 0;
TreeRect_XYXY(tr, &minX, &minY, &maxX, &maxY);
/* Try updating the display by copying items on the screen to their
* new location */
for (dItem = dInfo->dItem;
dItem != NULL;
dItem = dItem->next) {
/* Copy an item to its new location unless:
* (a) item display info is invalid
* (b) item is in same location as last draw */
if ((dItem->area.flags & DITEM_ALL_DIRTY) ||
(dItem->oldX == dItem->area.x))
continue;
numCopy++;
range = dItem->range;
/* This item was previously displayed so it only needs to be
* copied to the new location. Copy all such items as one */
offset = dItem->area.x - dItem->oldX;
width = dItem->area.width;
for (dItem2 = dItem->next;
dItem2 != NULL;
dItem2 = dItem2->next) {
if ((dItem2->range != range) ||
(dItem2->area.flags & DITEM_ALL_DIRTY) ||
(dItem2->oldX + offset != dItem2->area.x))
break;
numCopy++;
width = dItem2->area.x + dItem2->area.width - dItem->area.x;
}
x = dItem->area.x;
oldX = dItem->oldX;
/* Don't copy part of the window border */
if (oldX < minX) {
width -= minX - oldX;
oldX = minX;
}
if (oldX + width > maxX)
width = maxX - oldX;
/* Don't copy over the window border */
if (oldX + offset < minX) {
width -= minX - (oldX + offset);
oldX += minX - (oldX + offset);
}
if (oldX + offset + width > maxX)
width = maxX - (oldX + offset);
oldY = dItem->oldY;
height = dItem->height; /* range->totalHeight */
if (oldY < minY) {
height -= minY - oldY;
oldY = minY;
}
if (oldY + height > maxY)
height = maxY - oldY;
/* Update oldX of copied items */
while (1) {
/* If an item was partially visible, invalidate the exposed area */
if ((dItem->oldX < minX) && (offset > 0)) {
InvalidateDItemX(dItem, &dItem->area, dItem->oldX, dItem->oldX, minX - dItem->oldX);
InvalidateDItemY(dItem, &dItem->area, oldY, oldY, height);
dItem->area.flags |= DITEM_DIRTY;
}
if ((dItem->oldX + dItem->area.width > maxX) && (offset < 0)) {
InvalidateDItemX(dItem, &dItem->area, dItem->oldX, maxX, maxX - dItem->oldX + dItem->area.width);
InvalidateDItemY(dItem, &dItem->area, oldY, oldY, height);
dItem->area.flags |= DITEM_DIRTY;
}
dItem->oldX = dItem->area.x;
if (dItem->next == dItem2)
break;
dItem = dItem->next;
}
/* Invalidate parts of items being copied over */
for ( ; dItem2 != NULL; dItem2 = dItem2->next) {
if (dItem2->range != range)
break;
if (!(dItem2->area.flags & DITEM_ALL_DIRTY) &&
(dItem2->oldX + dItem2->area.width > x) &&
(dItem2->oldX < x + width)) {
InvalidateDItemX(dItem2, &dItem2->area, dItem2->oldX, x, width);
InvalidateDItemY(dItem2, &dItem2->area, oldY, oldY, height);
dItem2->area.flags |= DITEM_DIRTY;
}
}
if (tree->doubleBuffer == DOUBLEBUFFER_WINDOW) {
int dirtyMin, dirtyMax;
XCopyArea(tree->display, dInfo->pixmapW.drawable,
dInfo->pixmapW.drawable, tree->copyGC,
oldX, oldY, width, height,
oldX + offset, oldY);
if (offset < 0) {
dirtyMin = oldX + offset + width;
dirtyMax = oldX + width;
} else {
dirtyMin = oldX;
dirtyMax = oldX + offset;
}
Tree_InvalidateArea(tree, dirtyMin, oldY, dirtyMax, oldY + height);
DblBufWinDirty(tree, oldX + offset, oldY, oldX + offset + width,
oldY + height);
continue;
}
/* Copy */
damageRgn = Tree_GetRegion(tree);
if (Tree_ScrollWindow(tree, dInfo->scrollGC,
oldX, oldY, width, height, offset, 0, damageRgn)) {
DisplayDelay(tree);
Tree_InvalidateRegion(tree, damageRgn);
}
Tree_FreeRegion(tree, damageRgn);
}
return numCopy;
}
/*
*----------------------------------------------------------------------
*
* Proxy_IsXOR --
*
* Return true if the column/row proxies should be drawn with XOR.
*
* Results:
* None.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
static int
Proxy_IsXOR(void)
{
#if defined(WIN32)
return FALSE; /* TRUE on XP, FALSE on Win7 (lots of flickering) */
#elif defined(MAC_OSX_TK)
return FALSE; /* Cocoa doesn't have XOR */
#else
return TRUE; /* X11 */
#endif
}
/*
*--------------------------------------------------------------
*
* Proxy_DrawXOR --
*
* Draw (or erase) the visual indicator used when the user is
* resizing a column or row (and -columnresizemode is "proxy").
*
* Results:
* None.
*
* Side effects:
* Stuff is drawn in the TreeCtrl window (or erased, since this
* is XOR drawing).
*
*--------------------------------------------------------------
*/
static void
Proxy_DrawXOR(
TreeCtrl *tree, /* Widget info. */
int x1, /* Vertical or horizontal line window coords. */
int y1,
int x2,
int y2
)
{
XGCValues gcValues;
unsigned long gcMask;
GC gc;
#if defined(MAC_OSX_TK)
gcValues.function = GXcopy;
#else
gcValues.function = GXinvert;
#endif
gcValues.graphics_exposures = False;
gcMask = GCFunction | GCGraphicsExposures;
gc = Tree_GetGC(tree, gcMask, &gcValues);
#if defined(WIN32)
/* GXinvert doesn't work with XFillRectangle() on Win32 */
XDrawLine(tree->display, Tk_WindowId(tree->tkwin), gc, x1, y1, x2, y2);
#else
XFillRectangle(tree->display, Tk_WindowId(tree->tkwin), gc,
x1, y1, MAX(x2 - x1, 1), MAX(y2 - y1, 1));
#endif
}
/*
*--------------------------------------------------------------
*
* TreeColumnProxy_Display --
*
* Display the visual indicator used when the user is
* resizing a column (if it isn't displayed and should be
* displayed).
*
* Results:
* None.
*
* Side effects:
* Stuff is drawn in the TreeCtrl window.
*
*--------------------------------------------------------------
*/
void
TreeColumnProxy_Display(
TreeCtrl *tree /* Widget info. */
)
{
if (!tree->columnProxy.onScreen && (tree->columnProxy.xObj != NULL)) {
tree->columnProxy.sx = tree->columnProxy.x;
if (Proxy_IsXOR()) {
Proxy_DrawXOR(tree, tree->columnProxy.x, Tree_BorderTop(tree),
tree->columnProxy.x, Tree_BorderBottom(tree));
} else {
Tree_EventuallyRedraw(tree);
}
tree->columnProxy.onScreen = TRUE;
}
}
/*
*--------------------------------------------------------------
*
* TreeColumnProxy_Undisplay --
*
* Hide the visual indicator used when the user is
* resizing a column (if it is displayed).
*
* Results:
* None.
*
* Side effects:
* Stuff is erased in the TreeCtrl window.
*
*--------------------------------------------------------------
*/
void
TreeColumnProxy_Undisplay(
TreeCtrl *tree /* Widget info. */
)
{
if (tree->columnProxy.onScreen) {
if (Proxy_IsXOR()) {
Proxy_DrawXOR(tree, tree->columnProxy.sx, Tree_BorderTop(tree),
tree->columnProxy.sx, Tree_BorderBottom(tree));
} else {
Tree_EventuallyRedraw(tree);
}
tree->columnProxy.onScreen = FALSE;
}
}
/*
*--------------------------------------------------------------
*
* TreeRowProxy_Display --
*
* Display the visual indicator used when the user is
* resizing a row (if it isn't displayed and should be
* displayed).
*
* Results:
* None.
*
* Side effects:
* Stuff is drawn in the TreeCtrl window.
*
*--------------------------------------------------------------
*/
void
TreeRowProxy_Display(
TreeCtrl *tree /* Widget info. */
)
{
if (!tree->rowProxy.onScreen && (tree->rowProxy.yObj != NULL)) {
tree->rowProxy.sy = tree->rowProxy.y;
if (Proxy_IsXOR()) {
Proxy_DrawXOR(tree, Tree_BorderLeft(tree), tree->rowProxy.y,
Tree_BorderRight(tree), tree->rowProxy.y);
} else {
Tree_EventuallyRedraw(tree);
}
tree->rowProxy.onScreen = TRUE;
}
}
/*
*--------------------------------------------------------------
*
* TreeRowProxy_Undisplay --
*
* Hide the visual indicator used when the user is
* resizing a row (if it is displayed).
*
* Results:
* None.
*
* Side effects:
* Stuff is erased in the TreeCtrl window.
*
*--------------------------------------------------------------
*/
void
TreeRowProxy_Undisplay(
TreeCtrl *tree /* Widget info. */
)
{
if (tree->rowProxy.onScreen) {
if (Proxy_IsXOR()) {
Proxy_DrawXOR(tree, Tree_BorderLeft(tree), tree->rowProxy.sy,
Tree_BorderRight(tree), tree->rowProxy.sy);
} else {
Tree_EventuallyRedraw(tree);
}
tree->rowProxy.onScreen = FALSE;
}
}
/*
*--------------------------------------------------------------
*
* Proxy_Draw --
*
* Draw the non-XOR -columnproxy or -rowproxy indicator.
*
* Results:
* None.
*
* Side effects:
* Stuff is drawn into a drawable.
*
*--------------------------------------------------------------
*/
static void
Proxy_Draw(
TreeCtrl *tree, /* Widget info. */
TreeDrawable td, /* Where to draw. */
int x1, /* Vertical or horizontal line window coords. */
int y1,
int x2,
int y2
)
{
XGCValues gcValues;
unsigned long gcMask;
GC gc;
gcValues.function = GXcopy;
gcValues.graphics_exposures = False;
gcMask = GCFunction | GCGraphicsExposures;
gc = Tree_GetGC(tree, gcMask, &gcValues);
XDrawLine(tree->display, td.drawable, gc, x1, y1, x2, y2);
}
/*
*--------------------------------------------------------------
*
* TreeColumnProxy_Draw --
*
* Draw the non-XOR -columnproxy indicator if it is visible.
*
* Results:
* None.
*
* Side effects:
* Stuff is drawn into a drawable.
*
*--------------------------------------------------------------
*/
static void
TreeColumnProxy_Draw(
TreeCtrl *tree, /* Widget info. */
TreeDrawable td /* Where to draw. */
)
{
if (tree->columnProxy.xObj == NULL)
return;
Proxy_Draw(tree, td, tree->columnProxy.x, Tree_BorderTop(tree),
tree->columnProxy.x, Tree_BorderBottom(tree));
}
/*
*--------------------------------------------------------------
*
* TreeRowProxy_Draw --
*
* Draw the non-XOR -rowproxy indicator if it is visible.
*
* Results:
* None.
*
* Side effects:
* Stuff is drawn into a drawable.
*
*--------------------------------------------------------------
*/
static void
TreeRowProxy_Draw(
TreeCtrl *tree, /* Widget info. */
TreeDrawable td /* Where to draw. */
)
{
if (tree->rowProxy.yObj == NULL)
return;
Proxy_Draw(tree, td, Tree_BorderLeft(tree), tree->rowProxy.y,
Tree_BorderRight(tree), tree->rowProxy.y);
}
/*
*--------------------------------------------------------------
*
* CalcWhiteSpaceRegion --
*
* Create a new region containing all the whitespace of the list
* The whitespace is the area inside the borders/header where items
* are not displayed.
*
* Results:
* The new whitespace region, which may be empty.
*
* Side effects:
* A new region is allocated.
*
*--------------------------------------------------------------
*/
static TkRegion
CalcWhiteSpaceRegion(
TreeCtrl *tree /* Widget info. */
)
{
TreeDInfo dInfo = tree->dInfo;
int minX, minY, maxX, maxY;
int left, right, top, bottom;
TkRegion wsRgn;
TkRegion itemRgn;
XRectangle rect;
Range *range;
wsRgn = Tree_GetRegion(tree);
/* Start with a region as big as the window minus borders + headers */
minX = Tree_BorderLeft(tree);
minY = Tree_HeaderBottom(tree);
maxX = Tree_BorderRight(tree);
maxY = Tree_BorderBottom(tree);
/* Nothing is visible? Return empty region. */
if (minX >= maxX || minY >= maxY)
return wsRgn;
rect.x = minX;
rect.y = minY;
rect.width = maxX - minX;
rect.height = maxY - minY;
TkUnionRectWithRegion(&rect, wsRgn, wsRgn);
itemRgn = Tree_GetRegion(tree);
if (tree->itemGapX > 0 || tree->itemGapY > 0) {
TreeRectangle boundsRect, boundsRectL, boundsRectR;
DItem *dItem = dInfo->dItem;
boundsRect = dInfo->bounds;
boundsRectL = dInfo->boundsL;
boundsRectR = dInfo->boundsR;
while (dItem != NULL) {
TreeRectangle tr;
if (!dInfo->emptyL) {
tr.x = dItem->left.x;
tr.y = dItem->y;
tr.width = dItem->left.width;
tr.height = dItem->height;
TreeRect_Intersect(&tr, &tr, &boundsRectL);
TreeRect_ToXRect(tr, &rect);
TkUnionRectWithRegion(&rect, itemRgn, itemRgn);
}
if (!dInfo->emptyR) {
tr.x = dItem->right.x;
tr.y = dItem->y;
tr.width = dItem->right.width;
tr.height = dItem->height;
TreeRect_Intersect(&tr, &tr, &boundsRectR);
TreeRect_ToXRect(tr, &rect);
TkUnionRectWithRegion(&rect, itemRgn, itemRgn);
}
if (!dInfo->empty) {
tr.x = dItem->area.x;
tr.y = dItem->y;
tr.width = dItem->area.width;
tr.height = dItem->height;
TreeRect_Intersect(&tr, &tr, &boundsRect);
TreeRect_ToXRect(tr, &rect);
TkUnionRectWithRegion(&rect, itemRgn, itemRgn);
}
dItem = dItem->next;
}
TkSubtractRegion(wsRgn, itemRgn, wsRgn);
Tree_FreeRegion(tree, itemRgn);
return wsRgn;
}
/* Subtract area covered by items in left columns */
if (!dInfo->emptyL) {
int pad1 = tree->canvasPadY[PAD_TOP_LEFT];
int pad2 = tree->canvasPadY[PAD_BOTTOM_RIGHT];
TreeRect_XYXY(dInfo->boundsL, &minX, &minY, &maxX, &maxY);
left = minX;
top = MAX(C2Wy(pad1), minY);
right = maxX;
bottom = MIN(C2Wy(Tree_CanvasHeight(tree) - pad2), maxY);
if (top < bottom) {
rect.x = left;
rect.y = top;
rect.width = right - left;
rect.height = bottom - top;
TkUnionRectWithRegion(&rect, itemRgn, itemRgn);
}
}
/* Subtract area covered by items in right columns */
if (!dInfo->emptyR) {
int pad1 = tree->canvasPadY[PAD_TOP_LEFT];
int pad2 = tree->canvasPadY[PAD_BOTTOM_RIGHT];
TreeRect_XYXY(dInfo->boundsR, &minX, &minY, &maxX, &maxY);
left = minX;
top = MAX(C2Wy(pad1), minY);
right = maxX;
bottom = MIN(C2Wy(Tree_CanvasHeight(tree) - pad2), maxY);
if (top < bottom) {
rect.x = left;
rect.y = top;
rect.width = right - left;
rect.height = bottom - top;
TkUnionRectWithRegion(&rect, itemRgn, itemRgn);
}
}
/* Subtract area covered by items in unlocked columns */
if (!dInfo->empty) {
TreeRect_XYXY(dInfo->bounds, &minX, &minY, &maxX, &maxY);
for (range = dInfo->rangeFirstD;
range != NULL;
range = range->next) {
left = MAX(C2Wx(range->offset.x), minX);
top = MAX(C2Wy(range->offset.y), minY);
right = MIN(C2Wx(range->offset.x + range->totalWidth), maxX);
bottom = MIN(C2Wy(range->offset.y + range->totalHeight), maxY);
if (left < right && top < bottom) {
rect.x = left;
rect.y = top;
rect.width = right - left;
rect.height = bottom - top;
TkUnionRectWithRegion(&rect, itemRgn, itemRgn);
}
if (range == dInfo->rangeLastD)
break;
}
}
TkSubtractRegion(wsRgn, itemRgn, wsRgn);
Tree_FreeRegion(tree, itemRgn);
return wsRgn;
}
#ifdef COMPLEX_WHITESPACE
/*
*--------------------------------------------------------------
*
* TreeRect_Intersect --
*
* Determine the area of overlap between two rectangles.
*
* Results:
* If the rectangles have non-zero size and overlap, resultPtr
* holds the area of overlap, and the return value is 1.
* Otherwise 0 is returned and resultPtr is untouched.
*
* Side effects:
* None.
*
*--------------------------------------------------------------
*/
int
TreeRect_Intersect(
TreeRectangle *resultPtr, /* Out: area of overlap. May be the same
* as r1 or r2. */
CONST TreeRectangle *r1, /* First rectangle. */
CONST TreeRectangle *r2 /* Second rectangle. */
)
{
TreeRectangle result;
if (r1->width == 0 || r1->height == 0) return 0;
if (r2->width == 0 || r2->height == 0) return 0;
if (r1->x >= r2->x + r2->width) return 0;
if (r2->x >= r1->x + r1->width) return 0;
if (r1->y >= r2->y + r2->height) return 0;
if (r2->y >= r1->y + r1->height) return 0;
result.x = MAX(r1->x, r2->x);
result.width = MIN(r1->x + r1->width, r2->x + r2->width) - result.x;
result.y = MAX(r1->y, r2->y);
result.height = MIN(r1->y + r1->height, r2->y + r2->height) - result.y;
*resultPtr = result;
return 1;
}
/*
*--------------------------------------------------------------
*
* GetItemBgIndex --
*
* Determine the index used to pick an -itembackground color
* for a displayed item.
* This is only valid for tree->vertical=1.
*
* Results:
* Integer index.
*
* Side effects:
* None.
*
*--------------------------------------------------------------
*/
static int
GetItemBgIndex(
TreeCtrl *tree, /* Widget info. */
RItem *rItem /* Range info for an item. */
)
{
Range *range = rItem->range;
int index, indexVis;
TreeItem_ToIndex(tree, rItem->item, &index, &indexVis);
switch (tree->backgroundMode) {
#ifdef DEPRECATED
case BG_MODE_INDEX:
#endif
case BG_MODE_ORDER:
break;
#ifdef DEPRECATED
case BG_MODE_VISINDEX:
#endif
case BG_MODE_ORDERVIS:
index = indexVis;
break;
case BG_MODE_COLUMN:
index = range->index;
break;
case BG_MODE_ROW:
index = rItem->index;
break;
}
return index;
}
#ifdef ITEMBG_ABOVE
/*
*--------------------------------------------------------------
*
* DrawColumnBackgroundReverse --
*
* Draws rows of -itembackground colors in a column in the
* whitespace region.
*
* Results:
* None.
*
* Side effects:
* None.
*
*--------------------------------------------------------------
*/
static void
DrawColumnBackgroundReverse(
TreeCtrl *tree, /* Widget info. */
TreeDrawable td, /* Where to draw. */
TreeColumn treeColumn, /* Column to get background colors from. */
TkRegion dirtyRgn, /* Area that needs painting. Will be
* inside 'bounds' and inside borders. */
TreeRectangle *bounds, /* Window coords of column to paint. */
RItem *rItem, /* Item(s) to get row heights from when drawing
* in the tail column, otherwise NULL. */
int height, /* Height of each row below actual items. */
int index /* Used for alternating background colors. */
)
{
#if 0 /* REMOVED BECAUSE OF GRADIENTS */
int bgCount = TreeColumn_BackgroundCount(treeColumn);
#endif
GC gc = None, backgroundGC;
TreeRectangle dirtyBox, drawBox, rowBox;
int top, bottom;
TreeColor *tc;
Tree_GetRegionBounds(dirtyRgn, &dirtyBox);
if (!dirtyBox.width || !dirtyBox.height)
return;
backgroundGC = Tk_3DBorderGC(tree->tkwin, tree->border, TK_3D_FLAT_GC);
#if 0 /* REMOVED BECAUSE OF GRADIENTS */
/* If the column has zero -itembackground colors, paint with the
* treectrl's -background color. If a single -itembackground color
* is specified, then paint with it. */
if (bgCount < 2) {
if (bgCount == 1)
gc = TreeColumn_BackgroundGC(treeColumn, 0);
if (gc == None)
gc = backgroundGC;
Tree_FillRegion(tree->display, drawable, gc, dirtyRgn);
return;
}
#endif
#if 0
/* If -itembackground colors are transparent, we must draw the tree background
* color first then the -itembackground colors. This results in flickering
* when drawing directly to the toplevel. */
if (tree->doubleBuffer == DOUBLE_BUFFER_ITEM) {
tpixmap.width = drawBox.width
tpixmap.drawable = DisplayGetPixmap(tree, &dInfo->pixmapI,
tpixmap.width, tpixmap.height);
}
#endif
top = dirtyBox.y;
bottom = bounds->y + bounds->height;
while (top < bottom) {
/* Can't use clipping regions with XFillRectangle
* because the clip region is ignored on Win32. */
rowBox.x = bounds->x;
rowBox.width = bounds->width;
rowBox.height = rItem ? rItem->size : height;
rowBox.y = bottom - rowBox.height;
if (TreeRect_Intersect(&drawBox, &rowBox, &dirtyBox)) {
if (rItem != NULL) {
index = GetItemBgIndex(tree, rItem);
}
tc = TreeColumn_BackgroundColor(treeColumn, index);
if (tc == NULL) {
gc = backgroundGC;
XFillRectangle(tree->display, td.drawable, gc,
drawBox.x, drawBox.y, drawBox.width, drawBox.height);
} else {
if (!TreeColor_IsOpaque(tree ,tc)) {
XFillRectangle(tree->display, td.drawable, backgroundGC,
drawBox.x, drawBox.y, drawBox.width, drawBox.height);
}
TreeColor_FillRect(tree, td, NULL, tc, rowBox, drawBox);
}
}
if (rItem != NULL && rItem == rItem->range->last) {
index = GetItemBgIndex(tree, rItem);
rItem = NULL;
}
if (rItem != NULL) {
rItem++;
}
index++; /* FIXME: -- */
bottom -= rowBox.height;
}
}
/*
*--------------------------------------------------------------
*
* DrawWhitespaceAboveItem --
*
* Draws rows of -itembackground colors in each column in the
* whitespace region.
*
* Results:
* None.
*
* Side effects:
* None.
*
*--------------------------------------------------------------
*/
static void
DrawWhitespaceAboveItem(
TreeCtrl *tree, /* Widget info. */
TreeDrawable td, /* Where to draw. */
int lock, /* Which columns to draw. */
int bounds[4], /* TREE_AREA_xxx bounds. */
int left, /* Window coord of first column's left edge. */
int bottom, /* Window coord just above the first item. */
TkRegion dirtyRgn, /* Area of whitespace that needs painting. */
TkRegion columnRgn, /* Existing region to set and use. */
int height, /* Height of each row. */
int index /* Used for alternating background colors. */
)
{
int i = 0, width;
TreeColumn treeColumn = NULL;
TreeRectangle boundsBox, columnBox, visBox;
switch (lock) {
case COLUMN_LOCK_LEFT:
treeColumn = tree->columnLockLeft;
break;
case COLUMN_LOCK_NONE:
treeColumn = tree->columnLockNone;
break;
case COLUMN_LOCK_RIGHT:
treeColumn = tree->columnLockRight;
break;
}
boundsBox.x = bounds[0];
boundsBox.y = bounds[1];
boundsBox.width = bounds[2] - bounds[0];
boundsBox.height = bounds[3] - bounds[1];
for (i = TreeColumn_Index(treeColumn); i < tree->columnCount; i++) {
if (TreeColumn_Lock(treeColumn) != lock)
break;
width = TreeColumn_GetDInfo(treeColumn)->width;
if (width == 0) /* also handles hidden columns */
goto next;
columnBox.x = left;
columnBox.y = bounds[1];
columnBox.width = width;
columnBox.height = bottom - columnBox.y;
if (TreeRect_Intersect(&visBox, &boundsBox, &columnBox)) {
Tree_SetRectRegion(columnRgn, &visBox);
TkIntersectRegion(dirtyRgn, columnRgn, columnRgn);
DrawColumnBackgroundReverse(tree, td, treeColumn,
columnRgn, &columnBox, (RItem *) NULL, height, index);
}
left += width;
next:
treeColumn = TreeColumn_Next(treeColumn);
}
}
#endif /* ITEMBG_ABOVE */
/*
*--------------------------------------------------------------
*
* DrawColumnBackground --
*
* Draws rows of -itembackground colors in a column in the
* whitespace region.
*
* Results:
* None.
*
* Side effects:
* None.
*
*--------------------------------------------------------------
*/
static void
DrawColumnBackground(
TreeCtrl *tree, /* Widget info. */
TreeDrawable td, /* Where to draw. */
TreeColumn treeColumn, /* Column to get background colors from. */
TkRegion dirtyRgn, /* Area that needs painting. Will be
* inside 'bounds' and inside borders. */
TreeRectangle *bounds, /* Window coords of column to paint. */
RItem *rItem, /* Item(s) to get row heights from when drawing
* in the tail column, otherwise NULL. */
int height, /* Height of each row below actual items. */
int index /* Used for alternating background colors. */
)
{
#if 0 /* REMOVED BECAUSE OF GRADIENTS */
int bgCount = TreeColumn_BackgroundCount(treeColumn);
#endif
GC gc = None, backgroundGC;
TreeRectangle dirtyBox, drawBox, rowBox;
int top, bottom;
TreeColor *tc;
Tree_GetRegionBounds(dirtyRgn, &dirtyBox);
if (!dirtyBox.width || !dirtyBox.height)
return;
backgroundGC = Tk_3DBorderGC(tree->tkwin, tree->border, TK_3D_FLAT_GC);
#if 0 /* REMOVED BECAUSE OF GRADIENTS */
/* If the column has zero -itembackground colors, paint with the
* treectrl's -background color. If a single -itembackground color
* is specified, then paint with it. */
if (bgCount < 2) {
if (bgCount == 1)
gc = TreeColumn_BackgroundGC(treeColumn, 0);
if (gc == None)
gc = backgroundGC;
Tree_FillRegion(tree->display, drawable, gc, dirtyRgn);
return;
}
#endif
#if 0
/* If -itembackground colors are transparent, we must draw the tree background
* color first then the -itembackground colors. This results in flickering
* when drawing directly to the toplevel. */
if (tree->doubleBuffer == DOUBLE_BUFFER_ITEM) {
tpixmap.width = drawBox.width
tpixmap.drawable = DisplayGetPixmap(tree, &dInfo->pixmapI,
tpixmap.width, tpixmap.height);
}
#endif
top = bounds->y;
bottom = dirtyBox.y + dirtyBox.height;
while (top < bottom) {
/* Can't use clipping regions with XFillRectangle
* because the clip region is ignored on Win32. */
TreeRect_SetXYWH(rowBox,
bounds->x,
top,
bounds->width,
rItem ? rItem->size : height);
if (TreeRect_Intersect(&drawBox, &rowBox, &dirtyBox)) {
TreeRectangle trBrush;
if (rItem != NULL) {
index = GetItemBgIndex(tree, rItem);
}
tc = TreeColumn_BackgroundColor(treeColumn, index);
/* Handle the drawable offset from the top-left of the window */
drawBox.x -= tree->drawableXOrigin;
drawBox.y -= tree->drawableYOrigin;
if (tc == NULL) {
gc = backgroundGC;
XFillRectangle(tree->display, td.drawable, gc,
drawBox.x, drawBox.y, drawBox.width, drawBox.height);
} else {
TreeColor_GetBrushBounds(tree, tc, rowBox,
tree->xOrigin, tree->yOrigin,
treeColumn, (TreeItem) NULL, &trBrush);
if (!TreeColor_IsOpaque(tree, tc)
|| (trBrush.width <= 0)
|| (trBrush.height <= 0)) {
XFillRectangle(tree->display, td.drawable, backgroundGC,
drawBox.x, drawBox.y, drawBox.width, drawBox.height);
}
/* Handle the drawable offset from the top-left of the window */
trBrush.x -= tree->drawableXOrigin;
trBrush.y -= tree->drawableYOrigin;
TreeColor_FillRect(tree, td, NULL, tc, trBrush, drawBox);
}
}
if (rItem != NULL && rItem == rItem->range->last) {
index = GetItemBgIndex(tree, rItem);
rItem = NULL;
}
if (rItem != NULL) {
rItem++;
}
if (tree->backgroundMode != BG_MODE_COLUMN)
index++;
top += rowBox.height;
top += tree->itemGapY;
}
}
/*
*--------------------------------------------------------------
*
* DrawWhitespaceBelowItem --
*
* Draws rows of -itembackground colors in each column in the
* whitespace region.
*
* Results:
* None.
*
* Side effects:
* None.
*
*--------------------------------------------------------------
*/
static void
DrawWhitespaceBelowItem(
TreeCtrl *tree, /* Widget info. */
TreeDrawable td, /* Where to draw. */
TreeColumn treeColumn, /* Which columns to draw. */
TreeRectangle bounds, /* TREE_AREA_xxx bounds. */
int left, /* Window coord of first column's left edge. */
int rangeWidth, /* Width of range, needed when only 1 column
* is visible with wrapping. */
int top, /* Window coord just below the last item. */
TkRegion dirtyRgn, /* Area of whitespace that needs painting. */
TkRegion columnRgn, /* Existing region to set and use. */
int height, /* Height of each row. */
int index /* Used for alternating background colors. */
)
{
int lock = TreeColumn_Lock(treeColumn);
int width;
TreeRectangle boundsBox, columnBox, visBox;
boundsBox = bounds;
for (;
(treeColumn != NULL) && (TreeColumn_Lock(treeColumn) == lock);
treeColumn = TreeColumn_Next(treeColumn)) {
width = TreeColumn_GetDInfo(treeColumn)->width;
if (width == 0) /* also handles hidden columns */
continue;
if (tree->columnCountVis == 1 && rangeWidth != -1)
width = rangeWidth;
TreeRect_SetXYWH(columnBox,
left, top,
width, TreeRect_Bottom(bounds) - top);
if (TreeRect_Intersect(&visBox, &boundsBox, &columnBox)) {
Tree_SetRectRegion(columnRgn, &visBox);
TkIntersectRegion(dirtyRgn, columnRgn, columnRgn);
DrawColumnBackground(tree, td, treeColumn,
columnRgn, &columnBox, (RItem *) NULL, height, index);
}
left += width;
}
}
/*
*--------------------------------------------------------------
*
* ComplexWhitespace --
*
* Return 1 if -itembackground colors should be drawn into the
* whitespace region.
*
* Results:
* None.
*
* Side effects:
* None.
*
*--------------------------------------------------------------
*/
static int
ComplexWhitespace(
TreeCtrl *tree
)
{
if (tree->columnBgCnt == 0 &&
TreeColumn_BackgroundCount(tree->columnTail) == 0)
return 0;
if (!tree->vertical /*|| (tree->wrapMode != TREE_WRAP_NONE) ||
(tree->itemWrapCount > 0)*/)
return 0;
if (tree->itemHeight <= 0 && tree->minItemHeight <= 0)
return 0;
return 1;
}
/*
*--------------------------------------------------------------
*
* DrawWhitespace --
*
* Paints part of the whitespace region.
*
* Results:
* If -itembackground colors are not being drawn into the
* whitespace region, the dirtyRgn is filled with the treectrl's
* -background color. Otherwise rows of color are drawn below
* the last item and in the tail column if those columns have
* any -itembackground colors specified.
*
* Side effects:
* None.
*
*--------------------------------------------------------------
*/
static void
DrawWhitespace(
TreeCtrl *tree, /* Widget info. */
TreeDrawable td, /* Where to draw. */
TkRegion dirtyRgn /* The region that needs repainting. */
)
{
TreeDInfo dInfo = tree->dInfo;
int minX, minY, maxX, maxY;
int top, bottom;
int height, index;
TreeRectangle columnBox;
TkRegion columnRgn;
Range *range;
RItem *rItem;
/* If we aren't drawing -itembackground colors in the whitespace region,
* then just paint the entire dirty area with the treectrl's -background
* color. */
if (!ComplexWhitespace(tree)) {
GC gc = Tk_3DBorderGC(tree->tkwin, tree->border, TK_3D_FLAT_GC);
/* Handle the drawable offset from the top-left of the window */
Tree_OffsetRegion(dirtyRgn, -tree->drawableXOrigin, -tree->drawableYOrigin);
Tree_FillRegion(tree->display, td.drawable, gc, dirtyRgn);
/* Handle the drawable offset from the top-left of the window */
Tree_OffsetRegion(dirtyRgn, tree->drawableXOrigin, tree->drawableYOrigin);
return;
}
/* Erase whitespace in the gaps between items using the treectrl's
* background color. */
if (tree->itemGapX > 0 || tree->itemGapY > 0) {
GC gc = Tk_3DBorderGC(tree->tkwin, tree->border, TK_3D_FLAT_GC);
Tree_FillRegion(tree->display, td.drawable, gc, dirtyRgn);
}
/* Figure out the height of each row of color below the items. */
if (tree->backgroundMode == BG_MODE_COLUMN)
height = -1; /* solid block of color */
else if (tree->itemHeight > 0)
height = tree->itemHeight;
else
height = tree->minItemHeight;
columnRgn = Tree_GetRegion(tree);
range = dInfo->rangeFirst;
if (range == NULL)
range = dInfo->rangeLock;
if (!dInfo->empty) {
int leftEdgeOfColumns = tree->canvasPadX[PAD_TOP_LEFT];
int rightEdgeOfColumns = Tree_CanvasWidth(tree) - tree->canvasPadX[PAD_BOTTOM_RIGHT];
TreeRect_XYXY(dInfo->bounds, &minX, &minY, &maxX, &maxY);
if (tree->backgroundMode == BG_MODE_COLUMN) {
top = MAX(C2Wy(tree->canvasPadY[PAD_TOP_LEFT]),
minY);
bottom = maxY;
height = bottom - top; /* solid block of color */
}
/* Draw to the right of the items using the tail column's
* -itembackground colors. The height of each row matches
* the height of the adjacent item. */
if (C2Wx(rightEdgeOfColumns) < maxX) {
columnBox.y = minY;
if (range == NULL) {
rItem = NULL;
index = 0;
} else {
/* Get the item at the top of the screen. */
if (range->totalHeight == 0) {
rItem = range->last; /* all items have zero height */
} else {
int ccContentTop = W2Cy(minY);
int rcContentTop = ccContentTop - range->offset.y; /* could be < 0 */
int rcY = MAX(rcContentTop, 0);
rItem = Range_ItemUnderPoint(tree, range, -666, rcY, NULL, NULL, 3);
columnBox.y = C2Wy(range->offset.y + rItem->offset);
}
index = GetItemBgIndex(tree, rItem);
}
columnBox.x = C2Wx(rightEdgeOfColumns);
columnBox.width = maxX - columnBox.x;
columnBox.height = maxY - columnBox.y;
Tree_SetRectRegion(columnRgn, &columnBox);
TkIntersectRegion(dirtyRgn, columnRgn, columnRgn);
DrawColumnBackground(tree, td, tree->columnTail,
columnRgn, &columnBox, rItem, height, index);
}
/* Draw to the left of the items using the first visible
* column's -itembackground colors. The height of each row matches
* the height of the adjacent item. */
if (C2Wx(leftEdgeOfColumns) > minX) {
columnBox.y = minY;
if (range == NULL) {
rItem = NULL;
index = 0;
} else {
/* Get the item at the top of the screen. */
if (range->totalHeight == 0) {
rItem = range->last; /* all items have zero height */
} else {
int ccContentTop = W2Cy(minY);
int rcContentTop = ccContentTop - range->offset.y; /* could be < 0 */
int rcY = MAX(rcContentTop,0);
rItem = Range_ItemUnderPoint(tree, range, -666, rcY, NULL, NULL, 3);
columnBox.y = C2Wy(range->offset.y + rItem->offset);
}
index = GetItemBgIndex(tree, rItem);
}
columnBox.x = minX;
columnBox.width = C2Wx(leftEdgeOfColumns) - columnBox.x;
columnBox.height = maxY - columnBox.y;
Tree_SetRectRegion(columnRgn, &columnBox);
TkIntersectRegion(dirtyRgn, columnRgn, columnRgn);
DrawColumnBackground(tree, td, tree->columnVis ?
tree->columnVis : tree->columnTail,
columnRgn, &columnBox, rItem, height, index);
}
}
/* Draw below non-locked columns. */
if (!dInfo->empty && tree->columnVis != NULL) {
if (dInfo->rangeFirst == NULL) {
index = 0;
top = Tree_ContentTop(tree);
bottom = Tree_ContentBottom(tree);
if (tree->backgroundMode == BG_MODE_COLUMN)
height = bottom - top; /* solid block of color */
DrawWhitespaceBelowItem(tree, td, tree->columnLockNone,
dInfo->bounds, C2Wx(tree->canvasPadX[PAD_TOP_LEFT]), -1,
top, dirtyRgn, columnRgn,
height, index);
} else {
int left = tree->canvasPadX[PAD_TOP_LEFT];
while (range != NULL) {
top = MAX(C2Wy(range->offset.y + range->totalHeight),
Tree_ContentTop(tree));
bottom = Tree_ContentBottom(tree);
if ((C2Wx(left + range->totalWidth) > TreeRect_Left(dInfo->bounds))
&& (top < bottom)) {
rItem = range->last;
index = GetItemBgIndex(tree, rItem);
if (tree->backgroundMode != BG_MODE_COLUMN) {
index++;
}
if (tree->backgroundMode == BG_MODE_COLUMN)
height = bottom - top; /* solid block of color */
DrawWhitespaceBelowItem(tree, td, tree->columnLockNone,
dInfo->bounds, C2Wx(left), range->totalWidth,
top, dirtyRgn, columnRgn,
height, index);
}
left += range->totalWidth;
if (C2Wx(left) >= TreeRect_Right(dInfo->bounds))
break;
range = range->next;
}
}
}
top = MAX(C2Wy(Tree_CanvasHeight(tree))
- tree->canvasPadY[PAD_BOTTOM_RIGHT]
+ tree->itemGapY, Tree_ContentTop(tree));
bottom = Tree_ContentBottom(tree);
if ((top < bottom) && !(dInfo->emptyL && dInfo->emptyR)) {
if (tree->backgroundMode == BG_MODE_COLUMN)
height = bottom - top; /* solid block of color */
range = dInfo->rangeFirst;
if (range == NULL)
range = dInfo->rangeLock;
/* Get the display index of the last visible item, if any. */
if (range == NULL) {
index = 0;
} else {
rItem = range->last;
index = GetItemBgIndex(tree, rItem);
if (tree->backgroundMode != BG_MODE_COLUMN) {
index++;
}
}
/* Draw below the left columns. */
if (!dInfo->emptyL) {
minX = TreeRect_Left(dInfo->boundsL);
DrawWhitespaceBelowItem(tree, td, tree->columnLockLeft,
dInfo->boundsL,
minX, -1, top, dirtyRgn, columnRgn,
height, index);
}
/* Draw below the right columns. */
if (!dInfo->emptyR) {
minX = TreeRect_Left(dInfo->boundsR);
DrawWhitespaceBelowItem(tree, td, tree->columnLockRight,
dInfo->boundsR,
minX, -1, top, dirtyRgn, columnRgn,
height, index);
}
}
top = MAX(C2Wy(0), Tree_ContentTop(tree));
bottom = MAX(C2Wy(tree->canvasPadY[PAD_TOP_LEFT]), Tree_ContentTop(tree));
if (top < bottom) {
#ifndef ITEMBG_ABOVE
GC gc = Tk_3DBorderGC(tree->tkwin, tree->border, TK_3D_FLAT_GC);
columnBox.x = Tree_BorderLeft(tree);
columnBox.y = Tree_ContentTop(tree);
columnBox.width = Tree_BorderRight(tree) - Tree_BorderLeft(tree);
columnBox.height = bottom - top;
Tree_SetRectRegion(columnRgn, &columnBox);
TkIntersectRegion(dirtyRgn, columnRgn, columnRgn);
/* Handle the drawable offset from the top-left of the window */
Tree_OffsetRegion(columnRgn, -tree->drawableXOrigin, -tree->drawableYOrigin);
Tree_FillRegion(tree->display, td.drawable, gc, columnRgn);
/* Handle the drawable offset from the top-left of the window */
Tree_OffsetRegion(columnRgn, tree->drawableXOrigin, tree->drawableYOrigin);
#else
/* Get the display index of the first visible item. */
if (range == NULL) {
index = 0;
} else {
rItem = range->first;
index = GetItemBgIndex(tree, rItem);
if (tree->backgroundMode != BG_MODE_COLUMN) {
index++; /* FIXME: -- */
}
}
/* Draw above non-locked columns. */
if (!dInfo->empty && Tree_CanvasWidth(tree)/* && dInfo->rangeFirst != NULL */) {
DrawWhitespaceAboveItem(tree, td, COLUMN_LOCK_NONE,
dInfo->bounds, x + tree->canvasPadX[PAD_TOP_LEFT], bottom, dirtyRgn, columnRgn,
height, index);
}
/* Draw above the left columns. */
if (!dInfo->emptyL) {
minX = TreeRect_Left(dInfo->boundsL);
DrawWhitespaceAboveItem(tree, td, COLUMN_LOCK_LEFT,
dInfo->boundsL,
minX, bottom, dirtyRgn, columnRgn,
height, index);
}
/* Draw above the right columns. */
if (!dInfo->emptyR) {
minX = TreeRect_Left(dInfo->boundsR);
DrawWhitespaceAboveItem(tree, td, COLUMN_LOCK_RIGHT,
dInfo->boundsR,
minX, bottom, dirtyRgn, columnRgn,
height, index);
}
#endif
}
Tree_FreeRegion(tree, columnRgn);
}
#endif /* COMPLEX_WHITESPACE */
#if COLUMNGRID == 1
static int
GridLinesInWhiteSpace(
TreeCtrl *tree
)
{
if (tree->columnsWithGridLines <= 0)
return 0;
if (!tree->vertical)
return 0;
/* if ((tree->wrapMode != TREE_WRAP_NONE) || (tree->itemWrapCount > 0))
return 0;*/
return 1;
}
static void
DrawColumnGridLinesAux(
TreeCtrl *tree, /* Widget info. */
TreeColumn treeColumn, /* First column. */
TreeDrawable td, /* Where to draw. */
const TreeRectangle *boundsPtr,/* TREA_AREA_xxx bounds. */
int left, /* Left edge of first column. */
int rangeWidth, /* Width of range, needed when only 1 column
* is visible with wrapping. */
int minY, int maxY, /* Top & bottom of area to draw in. */
TkRegion dirtyRgn /* The region that needs repainting. */
)
{
int lock = TreeColumn_Lock(treeColumn);
int columnWidth;
TreeRectangle columnBox, gridBox, trBrush;
TreeColor *leftColor, *rightColor;
int leftWidth, rightWidth;
TreeClip clip;
clip.type = TREE_CLIP_REGION;
clip.region = dirtyRgn;
for (;
treeColumn != NULL && TreeColumn_Lock(treeColumn) == lock;
treeColumn = TreeColumn_Next(treeColumn)) {
if (TreeColumn_GridColors(treeColumn, &leftColor, &rightColor,
&leftWidth, &rightWidth) == 0) {
continue;
}
columnWidth = TreeColumn_GetDInfo(treeColumn)->width;
if (columnWidth == 0) /* also handles hidden columns */
continue;
if (tree->columnCountVis == 1 && rangeWidth != -1)
columnWidth = rangeWidth;
TreeRect_SetXYWH(columnBox, left + TreeColumn_Offset(treeColumn),
minY, columnWidth, maxY - minY);
if (TreeRect_Right(columnBox) <= TreeRect_Left(*boundsPtr))
continue;
if (TreeRect_Left(columnBox) >= TreeRect_Right(*boundsPtr))
break;
if (leftColor != NULL && leftWidth > 0) {
TreeRect_SetXYWH(gridBox, columnBox.x, columnBox.y, leftWidth,
columnBox.height);
if (TreeRect_Intersect(&gridBox, boundsPtr, &gridBox)) {
TreeColor_GetBrushBounds(tree, leftColor, gridBox,
tree->xOrigin, tree->yOrigin,
treeColumn, (TreeItem) NULL, &trBrush);
TreeColor_FillRect(tree, td, &clip, leftColor, trBrush,
gridBox);
}
}
if (rightColor != NULL && rightWidth > 0) {
TreeRect_SetXYWH(gridBox, columnBox.x + columnBox.width - rightWidth,
columnBox.y, rightWidth, columnBox.height);
if (TreeRect_Intersect(&gridBox, boundsPtr, &gridBox)) {
TreeColor_GetBrushBounds(tree, rightColor, gridBox,
tree->xOrigin, tree->yOrigin,
treeColumn, (TreeItem) NULL, &trBrush);
TreeColor_FillRect(tree, td, &clip, rightColor, trBrush,
gridBox);
}
}
}
}
/*
*----------------------------------------------------------------------
*
* DrawColumnGridLines --
*
* Draws the column gridlines in the whitespace region below any
* items.
*
* Results:
* None.
*
* Side effects:
* Stuff is drawn.
*
*----------------------------------------------------------------------
*/
static void
DrawColumnGridLines(
TreeCtrl *tree, /* Widget info. */
TreeDrawable td, /* Where to draw. */
TkRegion dirtyRgn /* The region that needs repainting. */
)
{
TreeDInfo dInfo = tree->dInfo;
int minY, maxY;
Range *range = dInfo->rangeFirst;
if (!GridLinesInWhiteSpace(tree))
return;
maxY = Tree_ContentBottom(tree);
/* Draw gridlines below non-locked columns in every Range. */
if (!dInfo->empty && tree->columnVis != NULL) {
int left = tree->canvasPadX[PAD_TOP_LEFT];
if (range == NULL) {
minY = Tree_ContentTop(tree);
if (minY < maxY) {
DrawColumnGridLinesAux(tree, tree->columnLockNone, td,
&dInfo->bounds,
C2Wx(left - tree->canvasPadX[PAD_TOP_LEFT]),
-1,
minY, maxY, dirtyRgn);
}
} else {
while (range != NULL) {
minY = MAX(C2Wy(range->offset.y + range->totalHeight),
Tree_ContentTop(tree));
if ((C2Wx(left + range->totalWidth) > TreeRect_Left(dInfo->bounds))
&& (minY < maxY)) {
DrawColumnGridLinesAux(tree, tree->columnLockNone, td,
&dInfo->bounds,
C2Wx(left - tree->canvasPadX[PAD_TOP_LEFT]),
range->totalWidth,
minY, maxY, dirtyRgn);
}
left += range->totalWidth;
if (C2Wx(left) >= TreeRect_Right(dInfo->bounds))
break;
range = range->next;
}
}
}
minY = MAX(C2Wy(Tree_CanvasHeight(tree))
- tree->canvasPadY[PAD_BOTTOM_RIGHT],
Tree_ContentTop(tree));
if (minY >= maxY)
return;
if (!dInfo->emptyL) {
DrawColumnGridLinesAux(tree, tree->columnLockLeft, td, &dInfo->boundsL,
Tree_BorderLeft(tree), -1, minY, maxY, dirtyRgn);
}
if (!dInfo->emptyR) {
DrawColumnGridLinesAux(tree, tree->columnLockRight, td, &dInfo->boundsR,
Tree_ContentRight(tree), -1, minY, maxY, dirtyRgn);
}
}
#endif
/*
*----------------------------------------------------------------------
*
* Tree_IsBgImageOpaque --
*
* Determines if there is any need to erase before drawing the
* -backgroundimage.
*
* Results:
* Return 1 if the -backgroundimage will completely fill any
* whitespace it is drawn into.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
int
Tree_IsBgImageOpaque(
TreeCtrl *tree /* Widget info. */
)
{
if (tree->backgroundImage == NULL)
return 0;
if ((tree->bgImageTile & (BGIMG_TILE_X|BGIMG_TILE_Y)) !=
(BGIMG_TILE_X|BGIMG_TILE_Y))
return 0;
return tree->bgImageOpaque;
}
/*
*----------------------------------------------------------------------
*
* Tree_DrawTiledImage --
*
* This procedure draws a tiled image in the indicated box.
*
* Results:
* None.
*
* Side effects:
* Stuff is drawn.
*
*----------------------------------------------------------------------
*/
int
Tree_DrawTiledImage(
TreeCtrl *tree, /* Widget info. */
TreeDrawable td, /* Where to draw. */
Tk_Image image, /* The image to draw. */
TreeRectangle tr, /* Area to paint, may not be filled. */
int xOffset, int yOffset, /* X and Y coord of where to start tiling. */
int tileX, int tileY /* Axes to tile along. */
)
{
int imgWidth, imgHeight;
TreeRectangle trImage, trPaint;
int drawn = 0;
#if CACHE_BG_IMG
Pixmap pixmap = None;
#endif
Tk_SizeOfImage(image, &imgWidth, &imgHeight);
if (imgWidth <= 0 || imgHeight <= 0)
return 0;
#if CACHE_BG_IMG
/* This pixmap is destroyed at the end of each call to Tree_Display,
* so any changes to -backgroundimage will be seen. */
if ((image == tree->backgroundImage) && tree->bgImageOpaque) {
pixmap = tree->dInfo->pixmapBgImg.drawable;
if (pixmap == None) {
pixmap = DisplayGetPixmap(tree,
&tree->dInfo->pixmapBgImg, imgWidth, imgHeight);
Tk_RedrawImage(image, 0, 0, imgWidth, imgHeight, pixmap, 0, 0);
}
}
#endif
while (tileX && xOffset > tr.x)
xOffset -= imgWidth;
while (tileY && yOffset > tr.y)
yOffset -= imgHeight;
trImage.x = xOffset, trImage.y = yOffset;
trImage.width = imgWidth, trImage.height = imgHeight;
do {
do {
if (TreeRect_Intersect(&trPaint, &trImage, &tr)) {
#if CACHE_BG_IMG
if (pixmap != None) {
XCopyArea(tree->display, pixmap, td.drawable, tree->copyGC,
trPaint.x - trImage.x,
trPaint.y - trImage.y,
trPaint.width, trPaint.height,
trPaint.x, trPaint.y);
} else
#endif
Tk_RedrawImage(image, trPaint.x - trImage.x,
trPaint.y - trImage.y, trPaint.width, trPaint.height,
td.drawable, trPaint.x, trPaint.y);
drawn = 1;
}
trImage.y += trImage.height;
} while (tileY && trImage.y < tr.y + tr.height);
trImage.x += trImage.width;
trImage.y = yOffset;
} while (tileX && trImage.x < tr.x + tr.width);
return drawn;
}
/*
*----------------------------------------------------------------------
*
* CalcBgImageBounds --
*
* Calculate the bounds (in canvas coordinates) of the background
* image. The background image may be tiled relative to the
* returned bounds.
*
* Results:
* Sets trImage with the bounds and returns 1.
*
* Side effects:
* May update item layout.
*
*----------------------------------------------------------------------
*/
static int
CalcBgImageBounds(
TreeCtrl *tree, /* Widget info. */
TreeRectangle *trImage /* Returned image bounds. */
)
{
int x1, y1, x2, y2;
int imgWidth, imgHeight;
if (tree->bgImageScroll & BGIMG_SCROLL_X) {
x1 = 0;
x2 = Tree_FakeCanvasWidth(tree);
} else {
x1 = W2Cx(Tree_ContentLeft(tree));
x2 = x1 + Tree_ContentWidth(tree);
}
if (tree->bgImageScroll & BGIMG_SCROLL_Y) {
y1 = 0;
y2 = Tree_FakeCanvasHeight(tree);
} else {
y1 = W2Cy(Tree_ContentTop(tree));
y2 = y1 + Tree_ContentHeight(tree);
}
Tk_SizeOfImage(tree->backgroundImage, &imgWidth, &imgHeight);
switch (tree->bgImageAnchor) {
case TK_ANCHOR_NW:
case TK_ANCHOR_W:
case TK_ANCHOR_SW:
break;
case TK_ANCHOR_N:
case TK_ANCHOR_CENTER:
case TK_ANCHOR_S:
x1 = x1 + (x2 - x1) / 2 - imgWidth / 2;
break;
case TK_ANCHOR_NE:
case TK_ANCHOR_E:
case TK_ANCHOR_SE:
x1 = x2 - imgWidth;
break;
}
switch (tree->bgImageAnchor) {
case TK_ANCHOR_NW:
case TK_ANCHOR_N:
case TK_ANCHOR_NE:
break;
case TK_ANCHOR_W:
case TK_ANCHOR_CENTER:
case TK_ANCHOR_E:
y1 = y1 + (y2 - y1) / 2 - imgHeight / 2;
break;
case TK_ANCHOR_SW:
case TK_ANCHOR_S:
case TK_ANCHOR_SE:
y1 = y2 - imgHeight;
break;
}
trImage->x = x1, trImage->y = y1;
trImage->width = /*(tree->bgImageTile & BGIMG_TILE_X) ? (x2 - x1) :*/ imgWidth;
trImage->height = /*(tree->bgImageTile & BGIMG_TILE_Y) ? (y2 - y1) :*/ imgHeight;
return 1;
}
/*
*----------------------------------------------------------------------
*
* Tree_DrawBgImage --
*
* Draws the -backgroundimage into the specified drawable inside
* the given rectangle, positioning and tiling the image according
* to the various bgimage widget options.
*
* Results:
* None.
*
* Side effects:
* Stuff is drawn.
*
*----------------------------------------------------------------------
*/
int
Tree_DrawBgImage(
TreeCtrl *tree, /* Widget info. */
TreeDrawable td, /* Where to draw. */
TreeRectangle tr, /* Rect to paint with the image. */
int xOrigin, /* Origin of the given drawable in */
int yOrigin /* canvas coordinates. */
)
{
TreeRectangle trImage;
(void) CalcBgImageBounds(tree, &trImage);
return Tree_DrawTiledImage(tree, td, tree->backgroundImage, tr,
trImage.x - xOrigin, trImage.y - yOrigin,
(tree->bgImageTile & BGIMG_TILE_X) != 0,
(tree->bgImageTile & BGIMG_TILE_Y) != 0);
}
/*
*----------------------------------------------------------------------
*
* DisplayDItem --
*
* Draw a single item.
*
* Results:
* None.
*
* Side effects:
* Stuff is drawn.
*
*----------------------------------------------------------------------
*/
static int
DisplayDItem(
TreeCtrl *tree, /* Widget info. */
DItem *dItem, /* Display info for an item. */
DItemArea *area,
int lock, /* Which set of columns. */
TreeRectangle bounds, /* TREE_AREA_xxx bounds of drawing. */
TreeDrawable pixmap, /* Where to draw. */
TreeDrawable drawable /* Where to copy to. */
)
{
Tk_Window tkwin = tree->tkwin;
int left, top, right, bottom;
left = area->x;
right = left + area->width;
top = dItem->y;
bottom = top + dItem->height;
if (!(area->flags & DITEM_ALL_DIRTY)) {
left += area->dirty[LEFT];
right = area->x + area->dirty[RIGHT];
top += area->dirty[TOP];
bottom = dItem->y + area->dirty[BOTTOM];
}
area->flags &= ~(DITEM_DIRTY | DITEM_ALL_DIRTY);
area->flags |= DITEM_DRAWN;
dItem->flags &= ~(DITEM_INVALIDATE_ON_SCROLL_X | DITEM_INVALIDATE_ON_SCROLL_Y);
if (left < TreeRect_Left(bounds))
left = TreeRect_Left(bounds);
if (right > TreeRect_Right(bounds))
right = TreeRect_Right(bounds);
if (top < TreeRect_Top(bounds))
top = TreeRect_Top(bounds);
if (bottom > TreeRect_Bottom(bounds))
bottom = TreeRect_Bottom(bounds);
if (right <= left || bottom <= top)
return 0;
if (tree->debug.enable && tree->debug.display && tree->debug.drawColor) {
XFillRectangle(tree->display, Tk_WindowId(tkwin),
tree->debug.gcDraw, left, top, right - left, bottom - top);
DisplayDelay(tree);
}
#if USE_ITEM_PIXMAP == 0
if (tree->doubleBuffer == DOUBLEBUFFER_WINDOW) {
DblBufWinDirty(tree, left, top, right, bottom);
}
/* The top-left corner of the drawable is at this
* point in the canvas */
tree->drawableXOrigin = tree->xOrigin;
tree->drawableYOrigin = tree->yOrigin;
TreeItem_Draw(tree, dItem->item,
lock,
area->x,
dItem->y,
area->width, dItem->height,
drawable,
left, right,
dItem->index);
#else
if (tree->doubleBuffer != DOUBLEBUFFER_NONE) {
if (tree->doubleBuffer == DOUBLEBUFFER_WINDOW) {
DblBufWinDirty(tree, left, top, right, bottom);
}
#ifdef TREECTRL_DEBUG
if (tree->debug.eraseColor)
XFillRectangle(tree->display, pixmap.drawable,
tree->debug.gcErase, 0, 0, right - left, bottom - top);
#endif
/* The top-left corner of the drawable is at this
* point in the canvas */
tree->drawableXOrigin = W2Cx(left);
tree->drawableYOrigin = W2Cy(top);
TreeItem_Draw(tree, dItem->item, lock,
area->x - left, dItem->y - top,
area->width, dItem->height,
pixmap,
0, right - left,
dItem->index);
XCopyArea(tree->display, pixmap.drawable, drawable.drawable,
tree->copyGC,
0, 0,
right - left, bottom - top,
left, top);
} else {
/* The top-left corner of the drawable is at this
* point in the canvas */
tree->drawableXOrigin = tree->xOrigin;
tree->drawableYOrigin = tree->yOrigin;
TreeItem_Draw(tree, dItem->item,
lock,
area->x,
dItem->y,
area->width, dItem->height,
drawable,
left, right,
dItem->index);
}
#endif
#if REDRAW_RGN == 1
AddRectToRedrawRgn(tree, left, top, right, bottom);
#endif /* REDRAW_RGN */
return 1;
}
/*
*--------------------------------------------------------------
*
* UpdateDItemsForHeaders --
*
* Allocates or updates a DItem for every on-screen header.
* If a header already has a DItem (because it was previously
* displayed), then the DItem may be marked dirty if there were
* changes to the header's on-screen size or position.
*
* Results:
* None.
*
* Side effects:
* Memory may be allocated.
*
*--------------------------------------------------------------
*/
static void
UpdateDItemsForHeaders(
TreeCtrl *tree, /* Widget info. */
DItem *dItemHead, /* Linked list of used DItems. */
TreeItem item /* First header item. */
)
{
TreeDInfo dInfo = tree->dInfo;
DItem *dItem, *last = NULL;
RItem fakeRItem;
TreeRectangle itemBbox, boundsL, bounds, boundsR, tr;
int emptyL, empty, emptyR, i;
DItemArea *areas[3], *area;
if (item == NULL)
return;
emptyL = !Tree_AreaBbox(tree, TREE_AREA_HEADER_LEFT, &boundsL);
empty = !Tree_AreaBbox(tree, TREE_AREA_HEADER_NONE, &bounds);
emptyR = !Tree_AreaBbox(tree, TREE_AREA_HEADER_RIGHT, &boundsR);
boundsL.x = W2Cx(boundsL.x), boundsL.y = W2Cy(boundsL.y);
bounds.x = W2Cx(bounds.x), bounds.y = W2Cy(bounds.y);
boundsR.x = W2Cx(boundsR.x), boundsR.y = W2Cy(boundsR.y);
dInfo->dItemHeader = NULL;
while (item != NULL) {
if (TreeItem_Height(tree, item) > 0) {
dItem = (DItem *) TreeItem_GetDInfo(tree, item);
/* Re-use a previously allocated DItem */
if (dItem != NULL) {
dItemHead = DItem_Unlink(dItemHead, dItem);
/* Make a new DItem */
} else {
fakeRItem.item = item;
dItem = DItem_Alloc(tree, &fakeRItem);
area = &dItem->area;
}
if (!emptyL &&
Tree_ItemBbox(tree, item, COLUMN_LOCK_LEFT, &itemBbox) != -1 &&
TreeRect_Intersect(&tr, &boundsL, &itemBbox)) {
dItem->left.x = C2Wx(itemBbox.x);
dItem->left.width = itemBbox.width;
dItem->y = C2Wy(itemBbox.y);
dItem->height = itemBbox.height;
}
if (!empty &&
Tree_ItemBbox(tree, item, COLUMN_LOCK_NONE, &itemBbox) != -1 &&
TreeRect_Intersect(&tr, &bounds, &itemBbox)) {
dItem->area.x = C2Wx(itemBbox.x);
dItem->area.width = itemBbox.width;
dItem->y = C2Wy(itemBbox.y);
dItem->height = itemBbox.height;
}
if (!emptyR &&
Tree_ItemBbox(tree, item, COLUMN_LOCK_RIGHT, &itemBbox) != -1 &&
TreeRect_Intersect(&tr, &boundsR, &itemBbox)) {
dItem->right.x = C2Wx(itemBbox.x);
dItem->right.width = itemBbox.width;
dItem->y = C2Wy(itemBbox.y);
dItem->height = itemBbox.height;
}
dItem->spans = TreeItem_GetSpans(tree, item);
areas[0] = empty ? NULL : &dItem->area;
areas[1] = emptyL ? NULL : &dItem->left;
areas[2] = emptyR ? NULL : &dItem->right;
for (i = 0; i < 3; i++) {
area = areas[i];
if (area == NULL)
continue;
/* This item is already marked for total redraw */
if (area->flags & DITEM_ALL_DIRTY)
; /* nothing */
/* All display info is marked as invalid */
else if (dInfo->flags & DINFO_INVALIDATE)
area->flags |= DITEM_DIRTY | DITEM_ALL_DIRTY;
/* Y-coord can change if another header's -visible option changes. */
else if (dItem->y != dItem->oldY)
area->flags |= DITEM_DIRTY | DITEM_ALL_DIRTY;
else if ((i == 0) &&
(dItem->flags & DITEM_INVALIDATE_ON_SCROLL_X)
&& (area->x != dItem->oldX))
area->flags |= DITEM_DIRTY | DITEM_ALL_DIRTY;
}
/* Linked list of DItems */
if (dInfo->dItemHeader == NULL)
dInfo->dItemHeader = dItem;
else
last->next = dItem;
last = dItem;
}
item = TreeItem_GetNextSibling(tree, item);
}
if (last != NULL)
last->next = NULL;
while (dItemHead != NULL)
dItemHead = DItem_Free(tree, dItemHead);
}
static void
DebugDrawBorder(
TreeCtrl *tree,
int inset,
int left,
int top,
int right,
int bottom
)
{
Tk_Window tkwin = tree->tkwin;
if (tree->debug.enable && tree->debug.display && tree->debug.drawColor) {
if (left > 0) {
XFillRectangle(tree->display, Tk_WindowId(tkwin),
tree->debug.gcDraw,
inset, inset,
left, Tk_Height(tkwin) - inset * 2);
}
if (top > 0) {
XFillRectangle(tree->display, Tk_WindowId(tkwin),
tree->debug.gcDraw,
inset, inset,
Tk_Width(tkwin) - inset * 2, top);
}
if (right > 0) {
XFillRectangle(tree->display, Tk_WindowId(tkwin),
tree->debug.gcDraw,
Tk_Width(tkwin) - inset - right, inset,
right, Tk_Height(tkwin) - inset * 2);
}
if (bottom > 0) {
XFillRectangle(tree->display, Tk_WindowId(tkwin),
tree->debug.gcDraw,
inset, Tk_Height(tkwin) - inset - bottom,
Tk_Width(tkwin) - inset * 2, bottom);
}
DisplayDelay(tree);
}
}
/*
*--------------------------------------------------------------
*
* TreeDisplay_GetReadyForTrouble --
* TreeDisplay_WasThereTrouble --
*
* These 2 procedures are used to detect when something happens
* during a display update that requests another display update.
* If that happens, then the current display is aborted and we
* try again (unless the window was destroyed).
*
* Results:
* None.
*
* Side effects:
* None.
*
*--------------------------------------------------------------
*/
void
TreeDisplay_GetReadyForTrouble(
TreeCtrl *tree,
int *requestsPtr
)
{
TreeDInfo dInfo = tree->dInfo;
*requestsPtr = dInfo->requests;
}
int
TreeDisplay_WasThereTrouble(
TreeCtrl *tree,
int requests
)
{
TreeDInfo dInfo = tree->dInfo;
if (tree->deleted || (requests != dInfo->requests)) {
if (tree->debug.enable)
dbwin("TreeDisplay_WasThereTrouble: %p\n", tree);
return 1;
}
return 0;
}
/*
*----------------------------------------------------------------------
*
* DisplayGetPixmap --
*
* Allocate or reallocate a pixmap of needed size.
*
* Results:
* None.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
static Pixmap
DisplayGetPixmap(
TreeCtrl *tree,
TreeDrawable *dPixmap,
int width,
int height
)
{
Tk_Window tkwin = tree->tkwin;
if (dPixmap->drawable == None) {
dPixmap->drawable = Tk_GetPixmap(tree->display,
Tk_WindowId(tkwin), width, height, Tk_Depth(tkwin));
dPixmap->width = width;
dPixmap->height = height;
} else if ((dPixmap->width < width) || (dPixmap->height < height)) {
Tk_FreePixmap(tree->display, dPixmap->drawable);
dPixmap->drawable = Tk_GetPixmap(tree->display,
Tk_WindowId(tkwin), width, height, Tk_Depth(tkwin));
dPixmap->width = width;
dPixmap->height = height;
}
return dPixmap->drawable;
}
/*
*--------------------------------------------------------------
*
* CheckPendingHeaderUpdate --
*
* This block of code used to be in Tree_Display but it needs to
* be checked wherever Range_RedoIfNeeded is called because it
* may set the DINFO_REDO_RANGES flag.
*
* Results:
* None.
*
* Side effects:
* None.
*
*--------------------------------------------------------------
*/
static void
CheckPendingHeaderUpdate(
TreeCtrl *tree
)
{
TreeDInfo dInfo = tree->dInfo;
/* DINFO_REDO_COLUMN_WIDTH - A column was created or deleted. */
/* DINFO_CHECK_COLUMN_WIDTH - The width, offset or visibility of one or
* more columns *might* have changed. */
if (dInfo->flags & (DINFO_REDO_COLUMN_WIDTH | DINFO_CHECK_COLUMN_WIDTH)) {
TreeColumn treeColumn = Tree_FirstColumn(tree, -1, TRUE);
TreeColumnDInfo dColumn;
int force = (dInfo->flags & DINFO_REDO_COLUMN_WIDTH) != 0;
int redoRanges = force, drawItems = force, drawHeader = force;
int offset, width;
/* Set max -itembackground as well. */
tree->columnBgCnt = 0;
while (treeColumn != NULL) {
offset = TreeColumn_Offset(treeColumn);
width = TreeColumn_UseWidth(treeColumn);
dColumn = TreeColumn_GetDInfo(treeColumn);
/* Haven't seen this column before. */
if (dColumn == NULL) {
dColumn = (TreeColumnDInfo) ckalloc(sizeof(TreeColumnDInfo_));
TreeColumn_SetDInfo(treeColumn, dColumn);
if (width > 0)
redoRanges = drawItems = drawHeader = TRUE;
} else {
/* Changes to observed width also detects column visibililty
* changing. */
if (dColumn->width != width) {
redoRanges = drawItems = drawHeader = TRUE;
} else if ((dColumn->offset != offset) && (width > 0)) {
drawItems = drawHeader = TRUE;
}
}
dColumn->offset = offset;
dColumn->width = width;
if (TreeColumn_Visible(treeColumn) &&
(TreeColumn_BackgroundCount(treeColumn) > tree->columnBgCnt))
tree->columnBgCnt = TreeColumn_BackgroundCount(treeColumn);
treeColumn = Tree_ColumnToTheRight(treeColumn, FALSE, TRUE);
}
if (redoRanges) dInfo->flags |= DINFO_REDO_RANGES | DINFO_OUT_OF_DATE;
if (drawHeader) dInfo->flags |= DINFO_DRAW_HEADER;
if (drawItems) dInfo->flags |= DINFO_INVALIDATE;
dInfo->flags &= ~(DINFO_REDO_COLUMN_WIDTH | DINFO_CHECK_COLUMN_WIDTH);
}
if (dInfo->headerHeight != Tree_HeaderHeight(tree)) {
dInfo->headerHeight = Tree_HeaderHeight(tree);
dInfo->flags |=
DINFO_INVALIDATE | /* FIXME: only do this for items covered/exposed */
DINFO_OUT_OF_DATE |
DINFO_SET_ORIGIN_Y |
DINFO_UPDATE_SCROLLBAR_Y |
DINFO_DRAW_HEADER;
if (tree->vertical && (tree->wrapMode == TREE_WRAP_WINDOW))
dInfo->flags |= DINFO_REDO_RANGES;
}
if (dInfo->widthOfColumnsLeft != Tree_WidthOfLeftColumns(tree) ||
dInfo->widthOfColumnsRight != Tree_WidthOfRightColumns(tree)) {
dInfo->widthOfColumnsLeft = Tree_WidthOfLeftColumns(tree);
dInfo->widthOfColumnsRight = Tree_WidthOfRightColumns(tree);
dInfo->flags |=
DINFO_SET_ORIGIN_X |
DINFO_UPDATE_SCROLLBAR_X/* |
DINFO_OUT_OF_DATE |
DINFO_REDO_RANGES |
DINFO_DRAW_HEADER*/;
}
}
/*
*----------------------------------------------------------------------
*
* TrackItemVisibility --
*
* Keeps track of the items and individual item-columns that
* are visible onscreen.
*
* Results:
* One of the DISPLAY_XXX constants.
*
* Side effects:
* May generate an <ItemVisibility> event.
*
*----------------------------------------------------------------------
*/
enum {
DISPLAY_OK,
DISPLAY_RETRY,
DISPLAY_EXIT
};
static int
TrackItemVisibility(
TreeCtrl *tree, /* Widget info. */
DItem *dItemHead, /* Linked list of onscreen item info. */
int doHeaders /* TRUE to operate on headers, FALSE
* to operate on items. */
)
{
TreeDInfo dInfo = tree->dInfo;
Tcl_HashTable *tablePtr = doHeaders ? &dInfo->headerVisHash : &dInfo->itemVisHash;
DItem *dItem;
int requests;
Tcl_HashEntry *hPtr;
Tcl_HashSearch search;
TreeItemList newV, newH;
TreeItem item;
int isNew, i, count;
TreeItemList_Init(tree, &newV, 0);
TreeItemList_Init(tree, &newH, 0);
TreeDisplay_GetReadyForTrouble(tree, &requests);
for (dItem = dItemHead;
dItem != NULL;
dItem = dItem->next) {
hPtr = Tcl_FindHashEntry(tablePtr, (char *) dItem->item);
if (hPtr == NULL) {
/* This item is now visible, wasn't before */
TreeItemList_Append(&newV, dItem->item);
TreeItem_OnScreen(tree, dItem->item, TRUE);
}
#ifdef DCOLUMN
/* The item was onscreen and still is. Figure out which
* item-columns have become visible or hidden. */
else {
TrackOnScreenColumnsForItem(tree, dItem->item, hPtr);
}
#endif /* DCOLUMN */
}
hPtr = Tcl_FirstHashEntry(tablePtr, &search);
while (hPtr != NULL) {
item = (TreeItem) Tcl_GetHashKey(tablePtr, hPtr);
if (TreeItem_GetDInfo(tree, item) == NULL) {
/* This item was visible but isn't now */
TreeItemList_Append(&newH, item);
TreeItem_OnScreen(tree, item, FALSE);
}
hPtr = Tcl_NextHashEntry(&search);
}
/* Remove newly-hidden items from itemVisHash */
count = TreeItemList_Count(&newH);
for (i = 0; i < count; i++) {
item = TreeItemList_Nth(&newH, i);
hPtr = Tcl_FindHashEntry(tablePtr, (char *) item);
#ifdef DCOLUMN
TrackOnScreenColumnsForItem(tree, item, hPtr);
ckfree((char *) Tcl_GetHashValue(hPtr));
#endif
Tcl_DeleteHashEntry(hPtr);
}
/* Add newly-visible items to itemVisHash */
count = TreeItemList_Count(&newV);
for (i = 0; i < count; i++) {
item = TreeItemList_Nth(&newV, i);
hPtr = Tcl_CreateHashEntry(tablePtr, (char *) item, &isNew);
#ifdef DCOLUMN
TrackOnScreenColumnsForItem(tree, item, hPtr);
#endif /* DCOLUMN */
}
if (!doHeaders) {
/*
* Generate an <ItemVisibility> event here. This can be used to set
* an item's styles when the item is about to be displayed, and to
* clear an item's styles when the item is no longer displayed.
*/
if (TreeItemList_Count(&newV) || TreeItemList_Count(&newH)) {
TreeNotify_ItemVisibility(tree, &newV, &newH);
}
}
TreeItemList_Free(&newV);
TreeItemList_Free(&newH);
if (tree->deleted || !Tk_IsMapped(tree->tkwin))
return DISPLAY_EXIT;
if (TreeDisplay_WasThereTrouble(tree, requests))
return DISPLAY_RETRY;
return DISPLAY_OK;
}
/*
*----------------------------------------------------------------------
*
* SetBuffering --
*
* Chooses the appropriate level of offscreen buffering depending
* on whether we need to draw the dragimage|marquee|proxies in non-XOR.
*
* Results:
* tree->doubleBuffer is possibly updated.
*
* Side effects:
* If the buffering level changes then the whole list is redrawn.
*
*----------------------------------------------------------------------
*/
static void
SetBuffering(
TreeCtrl *tree)
{
TreeDInfo dInfo = tree->dInfo;
int overlays = FALSE;
if ((TreeDragImage_IsVisible(tree->dragImage) &&
!TreeDragImage_IsXOR(tree->dragImage)) ||
(TreeMarquee_IsVisible(tree->marquee) &&
!TreeMarquee_IsXOR(tree->marquee)) ||
((tree->columnProxy.xObj || tree->rowProxy.yObj) &&
!Proxy_IsXOR())) {
overlays = TRUE;
}
#if 1
tree->doubleBuffer = DOUBLEBUFFER_WINDOW;
#else
#if defined(MAC_OSX_TK) && (TK_MAJOR_VERSION==8) && (TK_MINOR_VERSION>=6)
/* Do NOT call TkScrollWindow(), it generates an <Expose> event which redraws *all*
* child windows of children of the toplevel this treectrl is in. See Tk bug 3086887. */
tree->doubleBuffer = DOUBLEBUFFER_WINDOW;
#else
if (overlays) {
tree->doubleBuffer = DOUBLEBUFFER_WINDOW;
} else {
tree->doubleBuffer = DOUBLEBUFFER_ITEM;
}
#endif
#endif
if (overlays != dInfo->overlays) {
dInfo->flags |=
DINFO_DRAW_HEADER |
DINFO_INVALIDATE |
DINFO_DRAW_WHITESPACE;
dInfo->overlays = overlays;
}
}
#if 0
struct ExposeRestrictClientData {
TreeCtrl *tree;
unsigned long serial;
};
static Tk_RestrictAction
ExposeRestrictProc(
ClientData clientData,
XEvent *eventPtr
)
{
struct ExposeRestrictClientData *cd = clientData;
TreeCtrl *tree = cd->tree;
dbwin("ExposeRestrictProc type=%d", eventPtr->type);
if ((eventPtr->type == Expose) &&
(eventPtr->xexpose.window == Tk_WindowId(tree->tkwin)) &&
(eventPtr->xany.serial == cd->serial)) {
int x = eventPtr->xexpose.x, y = eventPtr->xexpose.y;
int w = eventPtr->xexpose.width, h = eventPtr->xexpose.height;
if (w > 0 && h > 0)
DblBufWinDirty(tree, x, y, x + w, y + h); /* FIXME: DINFO_DRAW_BORDERS */
return TK_DISCARD_EVENT;
}
return TK_DEFER_EVENT;
}
#endif
/*
*--------------------------------------------------------------
*
* Tree_Display --
*
* This procedure is called at idle time when something has happened
* that might require the list to be redisplayed. An effort is made
* to only redraw what is needed.
*
* Results:
* None.
*
* Side effects:
* Stuff is drawn in the TreeCtrl window.
*
*--------------------------------------------------------------
*/
static void
Tree_Display(
ClientData clientData /* Widget info. */
)
{
TreeCtrl *tree = clientData;
TreeDInfo dInfo = tree->dInfo;
DItem *dItem;
Tk_Window tkwin = tree->tkwin;
int didScrollX = 0, didScrollY = 0;
Drawable drawable;
TreeDrawable tdrawable;
int count;
int numCopy = 0, numDraw = 0;
TkRegion wsRgnNew, wsRgnDif;
#ifdef COMPLEX_WHITESPACE
int complexWhitespace;
#endif
TreeRectangle wsBox;
int requests;
if (tree->debug.enable && tree->debug.display && 0)
dbwin("Tree_Display %s\n", Tk_PathName(tkwin));
if (tree->deleted) {
dInfo->flags &= ~(DINFO_REDRAW_PENDING);
return;
}
/* After this point this function must only exit via the displayExit
* label. */
Tcl_Preserve((ClientData) tree);
Tree_PreserveItems(tree);
displayRetry:
SetBuffering(tree);
/* Some change requires selection changes */
if (dInfo->flags & DINFO_REDO_SELECTION) {
#ifdef SELECTION_VISIBLE
/* Possible <Selection> event. */
Tree_DeselectHidden(tree);
if (tree->deleted)
goto displayExit;
#endif
dInfo->flags &= ~(DINFO_REDO_SELECTION);
}
Range_RedoIfNeeded(tree);
Increment_RedoIfNeeded(tree);
if (dInfo->xOrigin != tree->xOrigin) {
dInfo->flags |=
DINFO_UPDATE_SCROLLBAR_X |
DINFO_OUT_OF_DATE |
DINFO_DRAW_HEADER;
}
if (dInfo->yOrigin != tree->yOrigin) {
dInfo->flags |=
DINFO_UPDATE_SCROLLBAR_Y |
DINFO_OUT_OF_DATE;
}
if (dInfo->totalWidth != Tree_CanvasWidth(tree)) {
dInfo->totalWidth = Tree_CanvasWidth(tree);
dInfo->flags |=
DINFO_SET_ORIGIN_X |
DINFO_UPDATE_SCROLLBAR_X |
DINFO_OUT_OF_DATE;
}
if (dInfo->totalHeight != Tree_CanvasHeight(tree)) {
dInfo->totalHeight = Tree_CanvasHeight(tree);
dInfo->flags |=
DINFO_SET_ORIGIN_Y |
DINFO_UPDATE_SCROLLBAR_Y |
DINFO_OUT_OF_DATE;
}
if (dInfo->flags & DINFO_SET_ORIGIN_X) {
Tree_GetOriginX(tree);
dInfo->flags &= ~DINFO_SET_ORIGIN_X;
}
if (dInfo->flags & DINFO_SET_ORIGIN_Y) {
Tree_GetOriginY(tree);
dInfo->flags &= ~DINFO_SET_ORIGIN_Y;
}
#ifdef COMPLEX_WHITESPACE
/* If -itembackground colors are being drawn in the whitespace region,
* then redraw all the whitespace if:
* a) scrolling occurs, or
* b) all the display info was marked as invalid (such as when
* -itembackground colors change, or a column moves), or
* c) item/column sizes change (handled by Range_RedoIfNeeded).
*/
complexWhitespace = ComplexWhitespace(tree);
if (complexWhitespace) {
if ((dInfo->xOrigin != tree->xOrigin) ||
(dInfo->yOrigin != tree->yOrigin) ||
(dInfo->flags & DINFO_INVALIDATE)) {
dInfo->flags |= DINFO_DRAW_WHITESPACE;
}
}
/* If tree->columnBgCnt was > 0 but is now 0, redraw whitespace. */
if (complexWhitespace != dInfo->complexWhitespace) {
dInfo->complexWhitespace = complexWhitespace;
dInfo->flags |= DINFO_DRAW_WHITESPACE;
}
#endif
#if COLUMNGRID == 1
/* If gridlines are drawn in the whitespace region then redraw all the
* whitespace if:
* a) horizontal scrolling occurs
* b) all the display info was marked as invalid (such as when
* -itembackground colors change, or a column moves)
* c) item/column sizes change (handled by Range_RedoIfNeeded).
*/
if (GridLinesInWhiteSpace(tree)) {
if ((dInfo->xOrigin != tree->xOrigin)
|| (dInfo->flags & DINFO_INVALIDATE)) {
dInfo->flags |= DINFO_DRAW_WHITESPACE;
}
}
#endif
/*
* dInfo->requests counts the number of calls to Tree_EventuallyRedraw().
* If binding scripts do something that causes a redraw to be requested,
* then we abort the current draw and start again.
*/
TreeDisplay_GetReadyForTrouble(tree, &requests);
if (dInfo->flags & DINFO_UPDATE_SCROLLBAR_X) {
/* Possible <Scroll-x> event. */
Tree_UpdateScrollbarX(tree);
dInfo->flags &= ~DINFO_UPDATE_SCROLLBAR_X;
}
if (dInfo->flags & DINFO_UPDATE_SCROLLBAR_Y) {
/* Possible <Scroll-y> event. */
Tree_UpdateScrollbarY(tree);
dInfo->flags &= ~DINFO_UPDATE_SCROLLBAR_Y;
}
if (tree->deleted || !Tk_IsMapped(tkwin))
goto displayExit;
if (TreeDisplay_WasThereTrouble(tree, requests)) {
goto displayRetry;
}
UpdateDItemsForHeaders(tree, dInfo->dItemHeader, tree->headerItems);
if (dInfo->flags & DINFO_OUT_OF_DATE) {
Tree_UpdateDInfo(tree);
dInfo->flags &= ~DINFO_OUT_OF_DATE;
}
if (dInfo->flags & DINFO_INVALIDATE) {
for (dItem = dInfo->dItem; dItem != NULL; dItem = dItem->next) {
dItem->area.flags |= DITEM_DIRTY | DITEM_ALL_DIRTY;
dItem->left.flags |= DITEM_DIRTY | DITEM_ALL_DIRTY;
dItem->right.flags |= DITEM_DIRTY | DITEM_ALL_DIRTY;
}
dInfo->flags &= ~DINFO_INVALIDATE;
}
/*
* When an item goes from visible to hidden, "window" elements in the
* item must be hidden. An item may become hidden because of scrolling,
* or because an ancestor was collapsed, or because the -visible option
* of the item changed.
*/
switch (TrackItemVisibility(tree, dInfo->dItem, FALSE)) {
case DISPLAY_RETRY: goto displayRetry; break;
case DISPLAY_EXIT: goto displayExit; break;
}
/* Also track visibility of header items, but don't generate an
* <ItemVisibility> event. Just make sure that window elements
* in any displayed styles in the headers know when they go offscreen. */
switch (TrackItemVisibility(tree, dInfo->dItemHeader, TRUE)) {
case DISPLAY_RETRY: goto displayRetry; break;
case DISPLAY_EXIT: goto displayExit; break;
}
tdrawable.width = Tk_Width(tkwin);
tdrawable.height = Tk_Height(tkwin);
if (tree->doubleBuffer == DOUBLEBUFFER_WINDOW) {
tdrawable.drawable = DisplayGetPixmap(tree, &dInfo->pixmapW,
tdrawable.width, tdrawable.height);
} else {
tdrawable.drawable = Tk_WindowId(tkwin);
}
drawable = tdrawable.drawable;
/* XOR off */
if (Proxy_IsXOR())
TreeColumnProxy_Undisplay(tree);
if (Proxy_IsXOR())
TreeRowProxy_Undisplay(tree);
if (TreeDragImage_IsXOR(tree->dragImage))
TreeDragImage_Undisplay(tree->dragImage);
if (TreeMarquee_IsXOR(tree->marquee))
TreeMarquee_Undisplay(tree->marquee);
#if REDRAW_RGN == 1
/* Collect all the pixels that are redrawn below into the redrawRgn.
* The redrawRgn is used to clip drawing of the marquee and dragimage. */
Tree_SetEmptyRegion(dInfo->redrawRgn);
#endif /* REDRAW_RGN */
if ((dInfo->flags & DINFO_DRAW_HEADER) && (dInfo->dItemHeader != NULL)) {
TreeDrawable tpixmap = tdrawable;
/* Scroll the headers if needed. */
ScrollHeaders(tree);
for (dItem = dInfo->dItemHeader;
dItem != NULL;
dItem = dItem->next) {
int drawn = 0;
TreeRectangle bounds;
#if USE_ITEM_PIXMAP == 1
/* Allocate pixmap for largest header */
tpixmap.width = /*MIN(*/Tk_Width(tkwin)/*, dItem->width)*/;
tpixmap.height = MIN(Tk_Height(tkwin), dItem->height);
tpixmap.drawable = DisplayGetPixmap(tree, &dInfo->pixmapI,
tpixmap.width, tpixmap.height);
#endif
if (Tree_AreaBbox(tree, TREE_AREA_HEADER_NONE, &bounds)) {
tree->drawableXOrigin = tree->xOrigin;
tree->drawableYOrigin = tree->yOrigin;
TreeItem_UpdateWindowPositions(tree, dItem->item, COLUMN_LOCK_NONE,
dItem->area.x, dItem->y, dItem->area.width, dItem->height);
if (TreeDisplay_WasThereTrouble(tree, requests)) {
if (tree->deleted || !Tk_IsMapped(tree->tkwin))
goto displayExit;
goto displayRetry;
}
if (dItem->area.flags & DITEM_DIRTY) {
drawn += DisplayDItem(tree, dItem, &dItem->area,
COLUMN_LOCK_NONE, bounds, tpixmap, tdrawable);
}
} else {
dItem->area.flags &= ~DITEM_DRAWN;
}
if (Tree_AreaBbox(tree, TREE_AREA_HEADER_LEFT, &bounds)) {
tree->drawableXOrigin = tree->xOrigin;
tree->drawableYOrigin = tree->yOrigin;
TreeItem_UpdateWindowPositions(tree, dItem->item,
COLUMN_LOCK_LEFT, dItem->left.x, dItem->y,
dItem->left.width, dItem->height);
if (TreeDisplay_WasThereTrouble(tree, requests)) {
if (tree->deleted || !Tk_IsMapped(tree->tkwin))
goto displayExit;
goto displayRetry;
}
if (dItem->left.flags & DITEM_DIRTY) {
drawn += DisplayDItem(tree, dItem, &dItem->left, COLUMN_LOCK_LEFT,
bounds, tpixmap, tdrawable);
}
} else {
dItem->left.flags &= ~DITEM_DRAWN;
}
if (Tree_AreaBbox(tree, TREE_AREA_HEADER_RIGHT, &bounds)) {
tree->drawableXOrigin = tree->xOrigin;
tree->drawableYOrigin = tree->yOrigin;
TreeItem_UpdateWindowPositions(tree, dItem->item,
COLUMN_LOCK_RIGHT, dItem->right.x, dItem->y,
dItem->right.width, dItem->height);
if (TreeDisplay_WasThereTrouble(tree, requests)) {
if (tree->deleted || !Tk_IsMapped(tree->tkwin))
goto displayExit;
goto displayRetry;
}
if (dItem->right.flags & DITEM_DIRTY) {
drawn += DisplayDItem(tree, dItem, &dItem->right, COLUMN_LOCK_RIGHT,
bounds, tpixmap, tdrawable);
}
} else {
dItem->right.flags &= ~DITEM_DRAWN;
}
numDraw += drawn ? 1 : 0;
dItem->oldX = dItem->area.x; /* FIXME: could have dInfo->empty */
dItem->oldY = dItem->y;
dItem->oldIndex = dItem->index;
}
dInfo->flags &= ~DINFO_DRAW_HEADER;
}
if (tree->vertical) {
numCopy = ScrollVerticalComplex(tree);
ScrollHorizontalSimple(tree);
}
else {
ScrollVerticalSimple(tree);
numCopy = ScrollHorizontalComplex(tree);
}
/* If we scrolled, then copy the entire pixmap, plus the header
* if needed. */
if (tree->doubleBuffer == DOUBLEBUFFER_WINDOW) {
if ((dInfo->xOrigin != tree->xOrigin) ||
(dInfo->yOrigin != tree->yOrigin)) {
DblBufWinDirty(tree, Tree_BorderLeft(tree), Tree_HeaderTop(tree),
Tree_BorderRight(tree), Tree_ContentBottom(tree));
}
}
if (dInfo->flags & DINFO_DRAW_WHITESPACE) {
Tree_SetEmptyRegion(dInfo->wsRgn);
dInfo->flags &= ~DINFO_DRAW_WHITESPACE;
}
didScrollX = dInfo->xOrigin != tree->xOrigin;
didScrollY = dInfo->yOrigin != tree->yOrigin;
dInfo->xOrigin = tree->xOrigin;
dInfo->yOrigin = tree->yOrigin;
dInfo->flags &= ~(DINFO_REDRAW_PENDING);
if (tree->backgroundImage != NULL) {
wsRgnNew = CalcWhiteSpaceRegion(tree);
/* If we scrolled, redraw entire whitespace area */
if ((didScrollX /*&& (tree->bgImageScroll & BGIMG_SCROLL_X)*/) ||
(didScrollY /*&& (tree->bgImageScroll & BGIMG_SCROLL_Y)*/)) {
wsRgnDif = wsRgnNew;
} else {
wsRgnDif = Tree_GetRegion(tree);
TkSubtractRegion(wsRgnNew, dInfo->wsRgn, wsRgnDif);
}
Tree_GetRegionBounds(wsRgnDif, &wsBox);
if ((wsBox.width > 0) && (wsBox.height > 0)) {
TreeDrawable tdPixmap;
TreeRectangle trPaint = wsBox;
tdPixmap.width = wsBox.width;
tdPixmap.height = wsBox.height;
tdPixmap.drawable = Tk_GetPixmap(tree->display, Tk_WindowId(tkwin),
wsBox.width, wsBox.height, Tk_Depth(tkwin));
if (tree->debug.enable && tree->debug.display && tree->debug.drawColor) {
Tree_FillRegion(tree->display, Tk_WindowId(tkwin),
tree->debug.gcDraw, wsRgnDif);
DisplayDelay(tree);
}
if (!Tree_IsBgImageOpaque(tree)) {
#ifdef COMPLEX_WHITESPACE
/* The drawable offset from the top-left of the window */
tree->drawableXOrigin = wsBox.x;
tree->drawableYOrigin = wsBox.y;
DrawWhitespace(tree, tdPixmap, wsRgnDif);
#else
GC gc = Tk_3DBorderGC(tkwin, tree->border, TK_3D_FLAT_GC);
Tree_OffsetRegion(wsRgnDif, -wsBox.x, -wsBox.y);
Tree_FillRegion(tree->display, tdPixmap.drawable, gc, wsRgnDif);
Tree_OffsetRegion(wsRgnDif, wsBox.x, wsBox.y);
#endif
}
trPaint.x = trPaint.y = 0;
Tree_DrawBgImage(tree, tdPixmap, trPaint,
W2Cx(wsBox.x), W2Cy(wsBox.y));
TkSetRegion(tree->display, tree->copyGC, wsRgnDif);
XCopyArea(tree->display, tdPixmap.drawable, drawable, tree->copyGC,
0, 0, wsBox.width, wsBox.height,
wsBox.x, wsBox.y);
XSetClipMask(tree->display, tree->copyGC, None);
Tk_FreePixmap(tree->display, tdPixmap.drawable);
#if COLUMNGRID == 1
DrawColumnGridLines(tree, tdrawable, wsRgnDif);
#endif
if (tree->doubleBuffer == DOUBLEBUFFER_WINDOW) {
DblBufWinDirty(tree, wsBox.x, wsBox.y, wsBox.x + wsBox.width,
wsBox.y + wsBox.height);
}
#if REDRAW_RGN == 1
AddRgnToRedrawRgn(tree, wsRgnDif);
#endif /* REDRAW_RGN */
}
if (wsRgnDif != wsRgnNew)
Tree_FreeRegion(tree, wsRgnDif);
Tree_FreeRegion(tree, dInfo->wsRgn);
dInfo->wsRgn = wsRgnNew;
}
if (tree->backgroundImage == NULL) {
/* Calculate the current whitespace region, subtract the old whitespace
* region, and fill the difference with the background color. */
wsRgnNew = CalcWhiteSpaceRegion(tree);
wsRgnDif = Tree_GetRegion(tree);
TkSubtractRegion(wsRgnNew, dInfo->wsRgn, wsRgnDif);
Tree_GetRegionBounds(wsRgnDif, &wsBox);
if ((wsBox.width > 0) && (wsBox.height > 0)) {
#ifndef COMPLEX_WHITESPACE
GC gc = Tk_3DBorderGC(tkwin, tree->border, TK_3D_FLAT_GC);
#endif
if (tree->debug.enable && tree->debug.display && tree->debug.drawColor) {
Tree_FillRegion(tree->display, Tk_WindowId(tkwin),
tree->debug.gcDraw, wsRgnDif);
DisplayDelay(tree);
}
#ifdef COMPLEX_WHITESPACE
/* The drawable offset from the top-left of the window */
tree->drawableXOrigin = 0;
tree->drawableYOrigin = 0;
DrawWhitespace(tree, tdrawable, wsRgnDif);
#else
Tree_FillRegion(tree->display, drawable, gc, wsRgnDif);
#endif
if (tree->doubleBuffer == DOUBLEBUFFER_WINDOW) {
DblBufWinDirty(tree, wsBox.x, wsBox.y, wsBox.x + wsBox.width,
wsBox.y + wsBox.height);
}
#if COLUMNGRID==1
DrawColumnGridLines(tree, tdrawable, wsRgnDif);
#endif
#if REDRAW_RGN == 1
AddRgnToRedrawRgn(tree, wsRgnDif);
#endif /* REDRAW_RGN */
}
Tree_FreeRegion(tree, wsRgnDif);
Tree_FreeRegion(tree, dInfo->wsRgn);
dInfo->wsRgn = wsRgnNew;
}
/* See if there are any dirty items */
count = 0;
for (dItem = dInfo->dItem;
dItem != NULL;
dItem = dItem->next) {
if ((!dInfo->empty && dInfo->rangeFirstD != NULL) &&
(dItem->area.flags & DITEM_DIRTY)) {
count++;
break;
}
if (!dInfo->emptyL && (dItem->left.flags & DITEM_DIRTY)) {
count++;
break;
}
if (!dInfo->emptyR && (dItem->right.flags & DITEM_DIRTY)) {
count++;
break;
}
}
/* This is the main loop for redrawing dirty items as well as
* updating window-element positions. The positions of window
* elements must be updated whenever scrolling occurs even if
* the scrolling did not invalidate any items. */
if (count > 0 || didScrollX || didScrollY) {
TreeDrawable tpixmap = tdrawable;
#if USE_ITEM_PIXMAP == 1
if (count > 0 && tree->doubleBuffer != DOUBLEBUFFER_NONE) {
/* Allocate pixmap for largest item */
tpixmap.width = MIN(Tk_Width(tkwin), dInfo->itemWidth);
tpixmap.height = MIN(Tk_Height(tkwin), dInfo->itemHeight);
tpixmap.drawable = DisplayGetPixmap(tree, &dInfo->pixmapI,
tpixmap.width, tpixmap.height);
}
#endif
for (dItem = dInfo->dItem;
dItem != NULL;
dItem = dItem->next) {
int drawn = 0;
if (!dInfo->empty && dInfo->rangeFirstD != NULL) {
tree->drawableXOrigin = tree->xOrigin;
tree->drawableYOrigin = tree->yOrigin;
TreeItem_UpdateWindowPositions(tree, dItem->item, COLUMN_LOCK_NONE,
dItem->area.x, dItem->y, dItem->area.width, dItem->height);
if (TreeDisplay_WasThereTrouble(tree, requests)) {
if (tree->deleted || !Tk_IsMapped(tree->tkwin))
goto displayExit;
goto displayRetry;
}
if (dItem->area.flags & DITEM_DIRTY) {
drawn += DisplayDItem(tree, dItem, &dItem->area,
COLUMN_LOCK_NONE, dInfo->bounds, tpixmap, tdrawable);
}
} else {
dItem->area.flags &= ~DITEM_DRAWN;
}
if (!dInfo->emptyL) {
tree->drawableXOrigin = tree->xOrigin;
tree->drawableYOrigin = tree->yOrigin;
TreeItem_UpdateWindowPositions(tree, dItem->item,
COLUMN_LOCK_LEFT, dItem->left.x, dItem->y,
dItem->left.width, dItem->height);
if (TreeDisplay_WasThereTrouble(tree, requests)) {
if (tree->deleted || !Tk_IsMapped(tree->tkwin))
goto displayExit;
goto displayRetry;
}
if (dItem->left.flags & DITEM_DIRTY) {
drawn += DisplayDItem(tree, dItem, &dItem->left, COLUMN_LOCK_LEFT,
dInfo->boundsL, tpixmap, tdrawable);
}
} else {
dItem->left.flags &= ~DITEM_DRAWN;
}
if (!dInfo->emptyR) {
tree->drawableXOrigin = tree->xOrigin;
tree->drawableYOrigin = tree->yOrigin;
TreeItem_UpdateWindowPositions(tree, dItem->item,
COLUMN_LOCK_RIGHT, dItem->right.x, dItem->y,
dItem->right.width, dItem->height);
if (TreeDisplay_WasThereTrouble(tree, requests)) {
if (tree->deleted || !Tk_IsMapped(tree->tkwin))
goto displayExit;
goto displayRetry;
}
if (dItem->right.flags & DITEM_DIRTY) {
drawn += DisplayDItem(tree, dItem, &dItem->right, COLUMN_LOCK_RIGHT,
dInfo->boundsR, tpixmap, tdrawable);
}
} else {
dItem->right.flags &= ~DITEM_DRAWN;
}
numDraw += drawn ? 1 : 0;
dItem->oldX = dItem->area.x; /* FIXME: could have dInfo->empty */
dItem->oldY = dItem->y;
dItem->oldIndex = dItem->index;
}
}
if (tree->debug.enable && tree->debug.display)
dbwin("copy %d draw %d %s\n", numCopy, numDraw, Tk_PathName(tkwin));
#if 0
/* Eat <Expose> events caused by embedded windows during scrolling. */
if ((tree->doubleBuffer == DOUBLEBUFFER_WINDOW) && (didScrollX || didScrollY)) {
ClientData oldArg;
Tk_RestrictProc *oldProc;
struct ExposeRestrictClientData cd;
int oldMode;
while (Tcl_DoOneEvent(TCL_IDLE_EVENTS|TCL_DONT_WAIT)) {};
oldMode = Tcl_SetServiceMode(TCL_SERVICE_ALL);
cd.tree = tree;
cd.serial = LastKnownRequestProcessed(tree->display);
TkpSync(tree->display);
oldProc = Tk_RestrictEvents(ExposeRestrictProc, &cd, &oldArg);
while (Tcl_ServiceEvent(TCL_WINDOW_EVENTS)) {}
Tk_RestrictEvents(oldProc, oldArg, &oldArg);
/* while (Tcl_DoOneEvent(TCL_IDLE_EVENTS|TCL_DONT_WAIT)) {}*/
Tcl_SetServiceMode(oldMode);
}
#endif
#if 0 && REDRAW_RGN == 1
tree->drawableXOrigin = tree->xOrigin;
tree->drawableYOrigin = tree->yOrigin;
if (TreeDragImage_IsXOR(tree->dragImage) == FALSE)
TreeDragImage_DrawClipped(tree->dragImage, tdrawable, dInfo->redrawRgn);
if (TreeMarquee_IsXOR(tree->marquee) == FALSE)
TreeMarquee_DrawClipped(tree->marquee, tdrawable, dInfo->redrawRgn);
Tree_SetEmptyRegion(dInfo->redrawRgn);
#endif /* REDRAW_RGN */
if (dInfo->overlays) {
tdrawable.width = Tk_Width(tkwin);
tdrawable.height = Tk_Height(tkwin);
if (TreeTheme_IsDesktopComposited(tree)) {
tdrawable.drawable = Tk_WindowId(tkwin);
} else {
tdrawable.drawable = DisplayGetPixmap(tree, &dInfo->pixmapT,
Tk_Width(tree->tkwin), Tk_Height(tree->tkwin));
}
/* Copy double-buffer */
/* FIXME: only copy what is in dirtyRgn plus overlays */
XCopyArea(tree->display, dInfo->pixmapW.drawable,
tdrawable.drawable,
tree->copyGC,
Tree_BorderLeft(tree), Tree_BorderTop(tree),
Tree_BorderRight(tree) - Tree_BorderLeft(tree),
Tree_BorderBottom(tree) - Tree_BorderTop(tree),
Tree_BorderLeft(tree), Tree_BorderTop(tree));
/* Draw dragimage|marquee|proxies */
tree->drawableXOrigin = tree->xOrigin;
tree->drawableYOrigin = tree->yOrigin;
if (TreeDragImage_IsXOR(tree->dragImage) == FALSE)
TreeDragImage_Draw(tree->dragImage, tdrawable);
if (TreeMarquee_IsXOR(tree->marquee) == FALSE)
TreeMarquee_Draw(tree->marquee, tdrawable);
if (Proxy_IsXOR() == FALSE)
TreeColumnProxy_Draw(tree, tdrawable);
if (Proxy_IsXOR() == FALSE)
TreeRowProxy_Draw(tree, tdrawable);
if (TreeTheme_IsDesktopComposited(tree) == FALSE) {
/* Copy tripple-buffer to window */
/* FIXME: only copy what is in dirtyRgn plus overlays */
XCopyArea(tree->display, dInfo->pixmapT.drawable,
Tk_WindowId(tkwin),
tree->copyGC,
Tree_BorderLeft(tree), Tree_BorderTop(tree),
Tree_BorderRight(tree) - Tree_BorderLeft(tree),
Tree_BorderBottom(tree) - Tree_BorderTop(tree),
Tree_BorderLeft(tree), Tree_BorderTop(tree));
}
Tree_SetEmptyRegion(dInfo->dirtyRgn);
DisplayDelay(tree);
}
else if (tree->doubleBuffer == DOUBLEBUFFER_WINDOW) {
TreeRectangle box;
drawable = Tk_WindowId(tkwin);
Tree_GetRegionBounds(dInfo->dirtyRgn, &box);
if (box.width > 0 && box.height > 0) {
TkSetRegion(tree->display, tree->copyGC, dInfo->dirtyRgn);
XCopyArea(tree->display, dInfo->pixmapW.drawable, drawable,
tree->copyGC,
box.x, box.y,
box.width, box.height,
box.x, box.y);
XSetClipMask(tree->display, tree->copyGC, None);
}
Tree_SetEmptyRegion(dInfo->dirtyRgn);
DisplayDelay(tree);
}
/* XOR on */
if (TreeMarquee_IsXOR(tree->marquee))
TreeMarquee_Display(tree->marquee);
if (TreeDragImage_IsXOR(tree->dragImage))
TreeDragImage_Display(tree->dragImage);
if (Proxy_IsXOR())
TreeRowProxy_Display(tree);
if (Proxy_IsXOR())
TreeColumnProxy_Display(tree);
if (tree->doubleBuffer == DOUBLEBUFFER_NONE)
dInfo->flags |= DINFO_DRAW_HIGHLIGHT | DINFO_DRAW_BORDER;
if (dInfo->flags & (DINFO_DRAW_BORDER | DINFO_DRAW_HIGHLIGHT)) {
drawable = Tk_WindowId(tkwin);
if (tree->useTheme && TreeTheme_DrawBorders(tree, drawable) == TCL_OK) {
/* nothing */
} else {
/* Draw focus rectangle (outside of 3D-border) */
if ((dInfo->flags & DINFO_DRAW_HIGHLIGHT) &&
(tree->highlightWidth > 0)) {
GC fgGC, bgGC;
DebugDrawBorder(tree, 0, tree->highlightWidth,
tree->highlightWidth, tree->highlightWidth,
tree->highlightWidth);
bgGC = Tk_GCForColor(tree->highlightBgColorPtr, drawable);
if (tree->gotFocus)
fgGC = Tk_GCForColor(tree->highlightColorPtr, drawable);
else
fgGC = bgGC;
TkpDrawHighlightBorder(tkwin, fgGC, bgGC, tree->highlightWidth,
drawable);
dInfo->flags &= ~DINFO_DRAW_HIGHLIGHT;
}
/* Draw 3D-border (inside of focus rectangle) */
if ((dInfo->flags & DINFO_DRAW_BORDER) && (tree->borderWidth > 0)) {
DebugDrawBorder(tree, tree->highlightWidth,
tree->borderWidth, tree->borderWidth,
tree->borderWidth, tree->borderWidth);
Tk_Draw3DRectangle(tkwin, drawable, tree->border,
tree->highlightWidth, tree->highlightWidth,
Tk_Width(tkwin) - tree->highlightWidth * 2,
Tk_Height(tkwin) - tree->highlightWidth * 2,
tree->borderWidth, tree->relief);
dInfo->flags &= ~DINFO_DRAW_BORDER;
}
}
dInfo->flags &= ~(DINFO_DRAW_BORDER | DINFO_DRAW_HIGHLIGHT);
}
displayExit:
#if CACHE_BG_IMG
if (dInfo->pixmapBgImg.drawable != None) {
Tk_FreePixmap(tree->display, dInfo->pixmapBgImg.drawable);
dInfo->pixmapBgImg.drawable = None;
}
#endif
dInfo->flags &= ~(DINFO_REDRAW_PENDING);
Tree_ReleaseItems(tree);
Tcl_Release((ClientData) tree);
}
/*
*--------------------------------------------------------------
*
* A_IncrementFindX --
*
* Return a horizontal scroll position nearest to the given
* offset.
*
* Results:
* Index of the nearest increment <= the given offset.
*
* Side effects:
* None.
*
*--------------------------------------------------------------
*/
static int
A_IncrementFindX(
TreeCtrl *tree, /* Widget info. */
int offset /* Canvas x-coordinate. */
)
{
int totWidth = Tree_CanvasWidth(tree);
int xIncr = tree->xScrollIncrement;
int index, indexMax;
indexMax = totWidth / xIncr;
if (totWidth % xIncr == 0)
indexMax--;
if (offset < 0)
offset = 0;
index = offset / xIncr;
if (index > indexMax)
index = indexMax;
return index;
}
/*
*--------------------------------------------------------------
*
* A_IncrementFindY --
*
* Return a vertical scroll position nearest to the given
* offset.
*
* Results:
* Index of the nearest increment <= the given offset.
*
* Side effects:
* None.
*
*--------------------------------------------------------------
*/
static int
A_IncrementFindY(
TreeCtrl *tree, /* Widget info. */
int offset /* Canvas y-coordinate. */
)
{
int totHeight = Tree_CanvasHeight(tree);
int yIncr = tree->yScrollIncrement;
int index, indexMax;
indexMax = totHeight / yIncr;
if (totHeight % yIncr == 0)
indexMax--;
if (offset < 0)
offset = 0;
index = offset / yIncr;
if (index > indexMax)
index = indexMax;
return index;
}
int
Tree_FakeCanvasWidth(
TreeCtrl *tree
)
{
TreeDInfo dInfo = tree->dInfo;
int visWidth, totWidth;
int oldSmoothing = tree->scrollSmoothing;
int indexMax, offset;
Increment_RedoIfNeeded(tree);
/* Use the cached value, if valid. */
if (dInfo->fakeCanvasWidth >= 0)
return dInfo->fakeCanvasWidth;
totWidth = Tree_CanvasWidth(tree);
if (totWidth <= 0)
return dInfo->fakeCanvasWidth = MAX(0, Tree_BorderRight(tree) - Tree_BorderLeft(tree));
visWidth = Tree_ContentWidth(tree);
if (visWidth > 1) {
tree->scrollSmoothing = 0;
/* Find the increment at the left edge of the content area when
* the view is scrolled all the way to the right. */
indexMax = Increment_FindX(tree, totWidth - visWidth);
offset = Increment_ToOffsetX(tree, indexMax);
/* If the increment starts before the left edge of the content area
* then use the next increment to the right. */
if (offset < totWidth - visWidth) {
indexMax++;
offset = Increment_ToOffsetX(tree, indexMax);
}
/* Add some fake content to right. */
if (offset + visWidth > totWidth)
totWidth = offset + visWidth;
tree->scrollSmoothing = oldSmoothing;
}
return dInfo->fakeCanvasWidth = totWidth;
}
int
Tree_FakeCanvasHeight(
TreeCtrl *tree
)
{
TreeDInfo dInfo = tree->dInfo;
int visHeight, totHeight;
int oldSmoothing = tree->scrollSmoothing;
int indexMax, offset;
Increment_RedoIfNeeded(tree);
/* Use the cached value, if valid. */
if (dInfo->fakeCanvasHeight >= 0)
return dInfo->fakeCanvasHeight;
totHeight = Tree_CanvasHeight(tree);
if (totHeight <= 0)
return dInfo->fakeCanvasHeight = MAX(0, Tree_ContentHeight(tree));
visHeight = Tree_ContentHeight(tree);
if (visHeight > 1) {
tree->scrollSmoothing = 0;
/* Find the increment at the top edge of the content area when
* the view is scrolled all the way down. */
indexMax = Increment_FindY(tree, totHeight - visHeight);
offset = Increment_ToOffsetY(tree, indexMax);
/* If the increment starts before the top edge of the content area
* then use the next increment down. */
if (offset < totHeight - visHeight) {
indexMax++;
offset = Increment_ToOffsetY(tree, indexMax);
}
/* Add some fake content to bottom. */
if (offset + visHeight > totHeight)
totHeight = offset + visHeight;
tree->scrollSmoothing = oldSmoothing;
}
return dInfo->fakeCanvasHeight = totHeight;
}
static int
Smooth_IncrementFindX(
TreeCtrl *tree, /* Widget info. */
int offset /* Canvas x-coordinate. */
)
{
int totWidth = Tree_FakeCanvasWidth(tree);
int xIncr = 1;
int index, indexMax;
indexMax = totWidth / xIncr;
if (totWidth % xIncr == 0)
indexMax--;
if (offset < 0)
offset = 0;
index = offset / xIncr;
if (index > indexMax)
index = indexMax;
return index;
}
static int
Smooth_IncrementFindY(
TreeCtrl *tree, /* Widget info. */
int offset /* Canvas y-coordinate. */
)
{
int totHeight = Tree_FakeCanvasHeight(tree);
int yIncr = 1;
int index, indexMax;
indexMax = totHeight / yIncr;
if (totHeight % yIncr == 0)
indexMax--;
if (offset < 0)
offset = 0;
index = offset / yIncr;
if (index > indexMax)
index = indexMax;
return index;
}
/*
*--------------------------------------------------------------
*
* Increment_FindX --
*
* Return a horizontal scroll position nearest to the given
* offset.
*
* Results:
* Index of the nearest increment <= the given offset.
*
* Side effects:
* None.
*
*--------------------------------------------------------------
*/
int
Increment_FindX(
TreeCtrl *tree, /* Widget info. */
int offset /* Canvas x-coordinate. */
)
{
if (tree->scrollSmoothing & SMOOTHING_X)
return Smooth_IncrementFindX(tree, offset);
if (tree->xScrollIncrement <= 0) {
Increment_RedoIfNeeded(tree);
return B_IncrementFindX(tree, offset);
}
return A_IncrementFindX(tree, offset);
}
/*
*--------------------------------------------------------------
*
* Increment_FindY --
*
* Return a vertical scroll position nearest to the given
* offset.
*
* Results:
* Index of the nearest increment <= the given offset.
*
* Side effects:
* None.
*
*--------------------------------------------------------------
*/
int
Increment_FindY(
TreeCtrl *tree, /* Widget info. */
int offset /* Canvas y-coordinate. */
)
{
if (tree->scrollSmoothing & SMOOTHING_Y)
return Smooth_IncrementFindY(tree, offset);
if (tree->yScrollIncrement <= 0) {
Increment_RedoIfNeeded(tree);
return B_IncrementFindY(tree, offset);
}
return A_IncrementFindY(tree, offset);
}
/*
*--------------------------------------------------------------
*
* Increment_ToOffsetX --
*
* Return the canvas coordinate for a scroll position.
*
* Results:
* Pixel distance.
*
* Side effects:
* None.
*
*--------------------------------------------------------------
*/
int
Increment_ToOffsetX(
TreeCtrl *tree, /* Widget info. */
int index /* Index of the increment. */
)
{
TreeDInfo dInfo = tree->dInfo;
int xIncr = tree->xScrollIncrement;
if (tree->scrollSmoothing & SMOOTHING_X)
return index * 1;
if (xIncr <= 0) {
DScrollIncrements *dIncr = &dInfo->xScrollIncrements;
if (index < 0 || index >= dIncr->count) {
panic("Increment_ToOffsetX: bad index %d (must be 0-%d)",
index, dIncr->count-1);
}
return dIncr->increments[index];
}
return index * xIncr;
}
/*
*--------------------------------------------------------------
*
* Increment_ToOffsetY --
*
* Return the canvas coordinate for a scroll position.
*
* Results:
* Pixel distance.
*
* Side effects:
* None.
*
*--------------------------------------------------------------
*/
int
Increment_ToOffsetY(
TreeCtrl *tree, /* Widget info. */
int index /* Index of the increment. */
)
{
TreeDInfo dInfo = tree->dInfo;
int yIncr = tree->yScrollIncrement;
if (tree->scrollSmoothing & SMOOTHING_Y)
return index * 1;
if (yIncr <= 0) {
DScrollIncrements *dIncr = &dInfo->yScrollIncrements;
if (index < 0 || index >= dIncr->count) {
panic("Increment_ToOffsetY: bad index %d (must be 0-%d)\ntotHeight %d visHeight %d",
index, dIncr->count - 1,
Tree_CanvasHeight(tree), Tree_ContentHeight(tree));
}
return dIncr->increments[index];
}
return index * yIncr;
}
/*
*--------------------------------------------------------------
*
* GetScrollFractions --
*
* Return the fractions that may be passed to a scrollbar "set"
* command.
*
* Results:
* Two fractions from 0.0 to 1.0.
*
* Side effects:
* None.
*
*--------------------------------------------------------------
*/
static void
GetScrollFractions(
int screen1, int screen2, /* Min/max coordinates that are visible in
* the window. */
int object1, int object2, /* Min/max coordinates of the scrollable
* content (usually 0 to N where N is the
* total width or height of the canvas). */
double fractions[2] /* Returned values. */
)
{
double range, f1, f2;
range = object2 - object1;
if (range <= 0) {
f1 = 0;
f2 = 1.0;
}
else {
f1 = (screen1 - object1) / range;
if (f1 < 0)
f1 = 0.0;
f2 = (screen2 - object1) / range;
if (f2 > 1.0)
f2 = 1.0;
if (f2 < f1)
f2 = f1;
}
fractions[0] = f1;
fractions[1] = f2;
}
/*
*--------------------------------------------------------------
*
* Tree_GetScrollFractionsX --
*
* Return the fractions that may be passed to a scrollbar "set"
* command for a horizontal scrollbar.
*
* Results:
* Two fractions from 0 to 1.0.
*
* Side effects:
* None.
*
*--------------------------------------------------------------
*/
void
Tree_GetScrollFractionsX(
TreeCtrl *tree, /* Widget info. */
double fractions[2] /* Returned values. */
)
{
int left = W2Cx(Tree_ContentLeft(tree));
int visWidth = Tree_ContentWidth(tree);
int totWidth = Tree_CanvasWidth(tree);
/* The tree is empty, or everything fits in the window */
if (visWidth < 0)
visWidth = 0;
if (totWidth <= visWidth) {
fractions[0] = 0.0;
fractions[1] = 1.0;
return;
}
if (visWidth <= 1) {
GetScrollFractions(left, left + 1, 0, totWidth, fractions);
return;
}
totWidth = Tree_FakeCanvasWidth(tree);
GetScrollFractions(left, left + visWidth, 0, totWidth, fractions);
}
/*
*--------------------------------------------------------------
*
* Tree_GetScrollFractionsY --
*
* Return the fractions that may be passed to a scrollbar "set"
* command for a vertical scrollbar.
*
* Results:
* Two fractions from 0 to 1.0.
*
* Side effects:
* None.
*
*--------------------------------------------------------------
*/
void
Tree_GetScrollFractionsY(
TreeCtrl *tree, /* Widget info. */
double fractions[2] /* Returned values. */
)
{
int top = W2Cy(Tree_ContentTop(tree));
int visHeight = Tree_ContentHeight(tree);
int totHeight = Tree_CanvasHeight(tree);
/* The tree is empty, or everything fits in the window */
if (visHeight < 0)
visHeight = 0;
if (totHeight <= visHeight) {
fractions[0] = 0.0;
fractions[1] = 1.0;
return;
}
if (visHeight <= 1) {
GetScrollFractions(top, top + 1, 0, totHeight, fractions);
return;
}
totHeight = Tree_FakeCanvasHeight(tree);
GetScrollFractions(top, top + visHeight, 0, totHeight, fractions);
}
void
Tree_SetScrollSmoothingX(
TreeCtrl *tree, /* Widget info. */
int smoothing /* TRUE or FALSE */
)
{
if (smoothing && tree->xScrollSmoothing)
tree->scrollSmoothing |= SMOOTHING_X;
else
tree->scrollSmoothing &= ~SMOOTHING_X;
}
void
Tree_SetScrollSmoothingY(
TreeCtrl *tree, /* Widget info. */
int smoothing /* TRUE or FALSE */
)
{
if (smoothing && tree->yScrollSmoothing)
tree->scrollSmoothing |= SMOOTHING_Y;
else
tree->scrollSmoothing &= ~SMOOTHING_Y;
}
/*
*--------------------------------------------------------------
*
* Tree_SetOriginX --
*
* Change the horizontal scroll position.
*
* Results:
* None.
*
* Side effects:
* If the horizontal scroll position changes, then the widget is
* redisplayed at idle time.
*
*--------------------------------------------------------------
*/
void
Tree_SetOriginX(
TreeCtrl *tree, /* Widget info. */
int xOrigin /* The desired offset from the left edge
* of the window to the left edge of the
* canvas. The actual value will be clipped
* to the nearest scroll increment. */
)
{
int totWidth = Tree_CanvasWidth(tree);
int visWidth = Tree_ContentWidth(tree);
int index, indexMax, offset;
/* The tree is empty, or everything fits in the window */
if (visWidth < 0)
visWidth = 0;
if (totWidth <= visWidth) {
xOrigin = 0 - Tree_ContentLeft(tree);
if (xOrigin != tree->xOrigin) {
tree->xOrigin = xOrigin;
tree->xOriginV2 = 0;
Tree_EventuallyRedraw(tree);
}
return;
}
totWidth = Tree_FakeCanvasWidth(tree);
if (visWidth > 1) {
indexMax = Increment_FindX(tree, totWidth - visWidth);
} else {
indexMax = Increment_FindX(tree, totWidth);
}
xOrigin += Tree_ContentLeft(tree); /* origin -> canvas */
index = Increment_FindX(tree, xOrigin);
/* Don't scroll too far left */
if (index < 0)
index = 0;
/* Don't scroll too far right */
if (index > indexMax)
index = indexMax;
offset = Increment_ToOffsetX(tree, index);
xOrigin = offset - Tree_ContentLeft(tree);
if (xOrigin == tree->xOrigin)
return;
tree->xOrigin = xOrigin;
tree->xOriginV2 = offset;
Tree_EventuallyRedraw(tree);
}
/*
*--------------------------------------------------------------
*
* Tree_SetOriginY --
*
* Change the vertical scroll position.
*
* Results:
* None.
*
* Side effects:
* If the vertical scroll position changes, then the widget is
* redisplayed at idle time.
*
*--------------------------------------------------------------
*/
void
Tree_SetOriginY(
TreeCtrl *tree, /* Widget info. */
int yOrigin /* The desired offset from the top edge
* of the window to the top edge of the
* canvas. The actual value will be clipped
* to the nearest scroll increment. */
)
{
int visHeight = Tree_ContentHeight(tree);
int totHeight = Tree_CanvasHeight(tree);
int index, indexMax, offset;
/* The tree is empty, or everything fits in the window */
if (visHeight < 0)
visHeight = 0;
if (totHeight <= visHeight) {
yOrigin = 0 - Tree_ContentTop(tree);
if (yOrigin != tree->yOrigin) {
tree->yOrigin = yOrigin;
tree->yOriginV2 = 0;
Tree_EventuallyRedraw(tree);
}
return;
}
totHeight = Tree_FakeCanvasHeight(tree);
if (visHeight > 1) {
indexMax = Increment_FindY(tree, totHeight - visHeight);
} else {
indexMax = Increment_FindY(tree, totHeight);
}
yOrigin += Tree_ContentTop(tree); /* origin -> canvas */
index = Increment_FindY(tree, yOrigin);
/* Don't scroll too far up */
if (index < 0)
index = 0;
/* Don't scroll too far down */
if (index > indexMax)
index = indexMax;
offset = Increment_ToOffsetY(tree, index);
yOrigin = offset - Tree_ContentTop(tree);
if (yOrigin == tree->yOrigin)
return;
tree->yOrigin = yOrigin;
tree->yOriginV2 = offset;
Tree_EventuallyRedraw(tree);
}
/*
*--------------------------------------------------------------
*
* Tree_GetOriginX --
*
* Return the horizontal scroll position.
*
* Results:
* None.
*
* Side effects:
* May update the horizontal scroll position.
* If the horizontal scroll position changes, then the widget is
* redisplayed at idle time.
*
*--------------------------------------------------------------
*/
int
Tree_GetOriginX(
TreeCtrl *tree /* Widget info. */
)
{
/* Update the value if needed. */
Tree_SetOriginX(tree, tree->xOriginV2 - Tree_ContentLeft(tree));
return tree->xOrigin;
}
/*
*--------------------------------------------------------------
*
* Tree_GetOriginY --
*
* Return the vertical scroll position.
*
* Results:
* None.
*
* Side effects:
* May update the vertical scroll position.
* If the vertical scroll position changes, then the widget is
* redisplayed at idle time.
*
*--------------------------------------------------------------
*/
int
Tree_GetOriginY(
TreeCtrl *tree /* Widget info. */
)
{
/* Update the value if needed. */
Tree_SetOriginY(tree, tree->yOriginV2 - Tree_ContentTop(tree));
return tree->yOrigin;
}
/*
*--------------------------------------------------------------
*
* Tree_EventuallyRedraw --
*
* Schedule an idle task to redisplay the widget, if one is not
* already scheduled and the widget is mapped and the widget
* hasn't been deleted.
*
* Results:
* None.
*
* Side effects:
* The widget may be redisplayed at idle time.
*
*--------------------------------------------------------------
*/
void
Tree_EventuallyRedraw(
TreeCtrl *tree /* Widget info. */
)
{
TreeDInfo dInfo = tree->dInfo;
dInfo->requests++;
if ((dInfo->flags & DINFO_REDRAW_PENDING) ||
tree->deleted ||
!Tk_IsMapped(tree->tkwin)) {
return;
}
dInfo->flags |= DINFO_REDRAW_PENDING;
Tcl_DoWhenIdle(Tree_Display, (ClientData) tree);
}
/*
*--------------------------------------------------------------
*
* Tree_RelayoutWindow --
*
* Invalidate all the layout info for the widget and schedule a
* redisplay at idle time. This gets called when certain config
* options change and when the size of the widget changes.
*
* Results:
* None.
*
* Side effects:
* The widget will be redisplayed at idle time.
*
*--------------------------------------------------------------
*/
void
Tree_RelayoutWindow(
TreeCtrl *tree /* Widget info. */
)
{
TreeDInfo dInfo = tree->dInfo;
FreeDItems(tree, NULL, dInfo->dItem, NULL);
dInfo->dItem = NULL;
FreeDItems(tree, NULL, dInfo->dItemHeader, NULL);
dInfo->dItemHeader = NULL;
dInfo->flags |=
DINFO_REDO_RANGES |
DINFO_OUT_OF_DATE |
DINFO_CHECK_COLUMN_WIDTH |
DINFO_DRAW_HEADER |
DINFO_DRAW_HIGHLIGHT |
DINFO_DRAW_BORDER |
DINFO_SET_ORIGIN_X |
DINFO_SET_ORIGIN_Y |
DINFO_UPDATE_SCROLLBAR_X |
DINFO_UPDATE_SCROLLBAR_Y;
dInfo->xOrigin = tree->xOrigin;
dInfo->yOrigin = tree->yOrigin;
/* Needed if -background color changes. */
dInfo->flags |= DINFO_DRAW_WHITESPACE;
if (tree->doubleBuffer != DOUBLEBUFFER_WINDOW) {
if (dInfo->pixmapW.drawable != None) {
Tk_FreePixmap(tree->display, dInfo->pixmapW.drawable);
dInfo->pixmapW.drawable = None;
}
}
if (tree->doubleBuffer == DOUBLEBUFFER_NONE) {
if (dInfo->pixmapI.drawable != None) {
Tk_FreePixmap(tree->display, dInfo->pixmapI.drawable);
dInfo->pixmapI.drawable = None;
}
}
if (tree->useTheme) {
TreeTheme_Relayout(tree);
TreeTheme_SetBorders(tree);
}
Tree_EventuallyRedraw(tree);
}
/*
*--------------------------------------------------------------
*
* Tree_FocusChanged --
*
* This procedure handles the widget gaining or losing the input
* focus. The state of every item has STATE_ITEM_FOCUS toggled on or
* off.
*
* Results:
* None.
*
* Side effects:
* The widget may be redisplayed at idle time if -highlightthickness
* is > 0, or if any Elements change appearance because of the
* state change.
*
*--------------------------------------------------------------
*/
void
Tree_FocusChanged(
TreeCtrl *tree, /* Widget info. */
int gotFocus /* TRUE if the widget has the focus,
* otherwise FALSE. */
)
{
TreeDInfo dInfo = tree->dInfo;
TreeItem item;
Tcl_HashEntry *hPtr;
Tcl_HashSearch search;
int stateOn, stateOff;
tree->gotFocus = gotFocus;
if (gotFocus)
stateOff = 0, stateOn = STATE_HEADER_FOCUS;
else
stateOff = STATE_HEADER_FOCUS, stateOn = 0;
/* Slow. Change state of every header */
item = tree->headerItems;
while (item != NULL) {
TreeItem_ChangeState(tree, item, stateOff, stateOn);
item = TreeItem_GetNextSibling(tree, item);
}
if (gotFocus)
stateOff = 0, stateOn = STATE_ITEM_FOCUS;
else
stateOff = STATE_ITEM_FOCUS, stateOn = 0;
/* Slow. Change state of every item */
hPtr = Tcl_FirstHashEntry(&tree->itemHash, &search);
while (hPtr != NULL) {
item = (TreeItem) Tcl_GetHashValue(hPtr);
TreeItem_ChangeState(tree, item, stateOff, stateOn);
hPtr = Tcl_NextHashEntry(&search);
}
#ifdef USE_TTK
dInfo->flags |= DINFO_DRAW_HIGHLIGHT;
Tree_EventuallyRedraw(tree);
#else
if (tree->highlightWidth > 0) {
dInfo->flags |= DINFO_DRAW_HIGHLIGHT;
Tree_EventuallyRedraw(tree);
}
#endif
}
/*
*--------------------------------------------------------------
*
* Tree_Activate --
*
* This procedure handles the widget's toplevel being the "active"
* foreground window (on Macintosh and Windows). Currently it just
* redraws the header if -usetheme is TRUE and the header is
* visible.
*
* Results:
* None.
*
* Side effects:
* The widget may be redisplayed at idle time.
*
*--------------------------------------------------------------
*/
void
Tree_Activate(
TreeCtrl *tree, /* Widget info. */
int isActive /* TRUE if the widget's toplevel is the
* active window, otherwise FALSE. */
)
{
TreeDInfo dInfo = tree->dInfo;
int stateOff, stateOn;
TreeItem item;
tree->isActive = isActive;
/* Change the state of every header. */
if (isActive)
stateOff = STATE_HEADER_BG, stateOn = 0;
else
stateOff = 0, stateOn = STATE_HEADER_BG;
item = tree->headerItems;
while (item != NULL) {
TreeItem_ChangeState(tree, item, stateOff, stateOn);
item = TreeItem_GetNextSibling(tree, item);
}
/* TODO: Like Tree_FocusChanged, change state of every item. */
/* Would need a new item state STATE_ACTIVEWINDOW or something. */
/* Would want to merge this with Tree_FocusChanged code to avoid
* double-iteration of items. */
/* Aqua column header looks different when window is not active */
if (tree->useTheme && tree->showHeader) {
dInfo->flags |= DINFO_DRAW_HEADER;
Tree_EventuallyRedraw(tree);
}
}
/*
*--------------------------------------------------------------
*
* Tree_FreeItemDInfo --
*
* Free any DItem associated with each item in a range of items.
* This is called when the size of an item changed or an item is
* deleted.
*
* Results:
* None.
*
* Side effects:
* The widget will be redisplayed at idle time.
*
*--------------------------------------------------------------
*/
void
Tree_FreeItemDInfo(
TreeCtrl *tree, /* Widget info. */
TreeItem item1, /* First item in the range. */
TreeItem item2 /* Last item in the range, or NULL. */
)
{
TreeDInfo dInfo = tree->dInfo;
DItem *dItem, **dItemHeadPtr = &dInfo->dItem;
TreeItem item = item1;
int changed = 0;
while (item != NULL) {
if (TreeItem_GetHeader(tree, item) != NULL) {
tree->headerHeight = -1;
dInfo->flags |= DINFO_DRAW_HEADER;
dItemHeadPtr = &dInfo->dItemHeader;
}
dItem = (DItem *) TreeItem_GetDInfo(tree, item);
if (dItem != NULL) {
FreeDItems(tree, dItemHeadPtr, dItem, dItem->next);
changed = 1;
}
if (item == item2 || item2 == NULL)
break;
item = TreeItem_Next(tree, item);
}
changed = 1;
if (changed) {
if (TreeItem_GetHeader(tree, item1) == NULL)
dInfo->flags |= DINFO_OUT_OF_DATE;
Tree_EventuallyRedraw(tree);
}
}
/*
*--------------------------------------------------------------
*
* Tree_InvalidateItemDInfo --
*
* Mark as dirty any DItem associated with each item in a range
* of items. This is called when the appearance of an item changed
* (but not its size).
*
* Results:
* None.
*
* Side effects:
* The widget will be redisplayed at idle time if any of the items
* had a DItem.
*
*--------------------------------------------------------------
*/
void
Tree_InvalidateItemDInfo(
TreeCtrl *tree, /* Widget info. */
TreeColumn column, /* Column to invalidate, or NULL for all. */
TreeItem item1, /* First item in the range. */
TreeItem item2 /* Last item in the range, or NULL. */
)
{
TreeDInfo dInfo = tree->dInfo;
TreeColumn column2;
DItem *dItem;
TreeItem item = item1;
int changed = 0;
if (item != NULL && TreeItem_GetHeader(tree, item) != NULL) {
dInfo->flags |= DINFO_DRAW_HEADER;
}
if (dInfo->flags & (DINFO_INVALIDATE | DINFO_REDO_COLUMN_WIDTH))
return;
while (item != NULL) {
dItem = (DItem *) TreeItem_GetDInfo(tree, item);
if ((dItem == NULL) || DItemAllDirty(tree, dItem))
goto next;
if (column == NULL) {
dItem->area.flags |= (DITEM_DIRTY | DITEM_ALL_DIRTY);
dItem->left.flags |= (DITEM_DIRTY | DITEM_ALL_DIRTY);
dItem->right.flags |= (DITEM_DIRTY | DITEM_ALL_DIRTY);
changed = 1;
} else {
TreeColumnDInfo dColumn = TreeColumn_GetDInfo(column);
int columnIndex, left, width, i, extraWidth = 0;
DItemArea *area = NULL;
switch (TreeColumn_Lock(column)) {
case COLUMN_LOCK_NONE:
area = &dItem->area;
break;
case COLUMN_LOCK_LEFT:
area = &dItem->left;
break;
case COLUMN_LOCK_RIGHT:
area = &dItem->right;
break;
}
if (area->flags & DITEM_ALL_DIRTY)
goto next;
columnIndex = TreeColumn_Index(column);
left = dColumn->offset;
if (TreeColumn_Lock(column) == COLUMN_LOCK_NONE) {
if (TreeItem_GetHeader(tree, item) != NULL) {
if (TreeColumn_VisIndex(column) == 0) {
extraWidth = tree->canvasPadX[PAD_TOP_LEFT];
left = 0;
}
} else
left -= tree->canvasPadX[PAD_TOP_LEFT]; /* canvas -> item coords */
}
if (column == tree->columnTail)
width = area->width - dColumn->offset;
/* If only one column is visible, the width may be
* different than the column width. */
else if ((TreeColumn_Lock(column) == COLUMN_LOCK_NONE) &&
(tree->columnCountVis == 1)) {
width = area->width;
/* All spans are 1. */
} else if (dItem->spans == NULL) {
width = dColumn->width + extraWidth;
/* If the column being redrawn is not the first in the span,
* then do nothing. */
} else if (columnIndex != dItem->spans[columnIndex]) {
goto next;
/* Calculate the width of the entire span. */
/* Do NOT call TreeColumn_UseWidth() or another routine
* that calls Tree_WidthOfColumns() because that may end
* up recalculating the size of items whose display info
* is currently being invalidated. */
} else {
width = 0 + extraWidth;
column2 = column;
i = columnIndex;
while (dItem->spans[i] == columnIndex) {
width += TreeColumn_GetDInfo(column2)->width;
if (++i == tree->columnCount)
break;
column2 = TreeColumn_Next(column2);
}
}
#ifdef MAC_OSX_TK
/* Aqua headers overlap one pixel on the left edge. */
if (TreeItem_GetHeader(tree, item) != NULL) {
left -= 1;
width += 2;
}
#endif
if (width > 0) {
InvalidateDItemX(dItem, area, 0, left, width);
InvalidateDItemY(dItem, area, 0, 0, dItem->height);
area->flags |= DITEM_DIRTY;
changed = 1;
}
}
next:
if (item == item2 || item2 == NULL)
break;
item = TreeItem_Next(tree, item);
}
if (changed) {
Tree_EventuallyRedraw(tree);
}
}
/*
*--------------------------------------------------------------
*
* TreeDisplay_ItemDeleted --
*
* Removes an item from the hash table of on-screen items.
*
* Results:
* None.
*
* Side effects:
* None.
*
*--------------------------------------------------------------
*/
void
TreeDisplay_ItemDeleted(
TreeCtrl *tree, /* Widget info. */
TreeItem item /* Item to remove. */
)
{
TreeDInfo dInfo = tree->dInfo;
Tcl_HashEntry *hPtr;
hPtr = Tcl_FindHashEntry(&dInfo->itemVisHash, (char *) item);
if (hPtr != NULL) {
#ifdef DCOLUMN
ckfree((char *) Tcl_GetHashValue(hPtr));
#endif
Tcl_DeleteHashEntry(hPtr);
}
hPtr = Tcl_FindHashEntry(&dInfo->headerVisHash, (char *) item);
if (hPtr != NULL) {
#ifdef DCOLUMN
ckfree((char *) Tcl_GetHashValue(hPtr));
#endif
Tcl_DeleteHashEntry(hPtr);
}
}
/*
*--------------------------------------------------------------
*
* TreeDisplay_ColumnDeleted --
*
* Removes a column from the list of on-screen columns for
* all on-screen items.
*
* Results:
* None.
*
* Side effects:
* None.
*
*--------------------------------------------------------------
*/
void
TreeDisplay_ColumnDeleted(
TreeCtrl *tree, /* Widget info. */
TreeColumn column /* Column to remove. */
)
{
#ifdef DCOLUMN
TreeDInfo dInfo = tree->dInfo;
Tcl_HashTable *tablePtr = &dInfo->itemVisHash;
Tcl_HashSearch search;
Tcl_HashEntry *hPtr;
TreeColumn *value;
int i;
hPtr = Tcl_FirstHashEntry(tablePtr, &search);
if (hPtr == NULL) {
tablePtr = &dInfo->headerVisHash;
hPtr = Tcl_FirstHashEntry(tablePtr, &search);
}
while (hPtr != NULL) {
value = (TreeColumn *) Tcl_GetHashValue(hPtr);
if (value == NULL) panic("TreeDisplay_ColumnDeleted value == NULL");
for (i = 0; value[i] != NULL; i++) {
if (value[i] == column) {
while (value[i] != NULL) {
value[i] = value[i + 1];
++i;
}
if (tree->debug.enable && tree->debug.span)
dbwin("TreeDisplay_ColumnDeleted item %d column %d\n",
TreeItem_GetID(tree, (TreeItem) Tcl_GetHashKey(
tablePtr, hPtr)),
TreeColumn_GetID(column));
break;
}
}
hPtr = Tcl_NextHashEntry(&search);
if (hPtr == NULL && tablePtr == &dInfo->itemVisHash) {
tablePtr = &dInfo->headerVisHash;
hPtr = Tcl_FirstHashEntry(tablePtr, &search);
}
}
#endif
}
/*
*--------------------------------------------------------------
*
* TreeDisplay_FreeColumnDInfo --
*
* Free any display info associated with a column when it is
* deleted.
*
* Results:
* None.
*
* Side effects:
* None.
*
*--------------------------------------------------------------
*/
void
TreeDisplay_FreeColumnDInfo(
TreeCtrl *tree, /* Widget info. */
TreeColumn column /* Column info. */
)
{
TreeColumnDInfo dColumn = TreeColumn_GetDInfo(column);
if (dColumn != NULL)
ckfree((char *) dColumn);
}
/*
*--------------------------------------------------------------
*
* Tree_ShouldDisplayLockedColumns --
*
* Figure out if we are allowed to draw any locked columns.
*
* Results:
* TRUE if locked columns should be displayed, otherwise FALSE.
*
* Side effects:
* None.
*
*--------------------------------------------------------------
*/
int
Tree_ShouldDisplayLockedColumns(
TreeCtrl *tree /* Widget info. */
)
{
if (!tree->vertical)
return 0;
if (tree->wrapMode != TREE_WRAP_NONE)
return 0;
Tree_UpdateItemIndex(tree); /* update tree->itemWrapCount */
if (tree->itemWrapCount > 0)
return 0;
return 1;
}
/*
*--------------------------------------------------------------
*
* Tree_DInfoChanged --
*
* Set some DINFO_xxx flags and schedule a redisplay.
*
* Results:
* None.
*
* Side effects:
* The widget will be redisplayed at idle time.
*
*--------------------------------------------------------------
*/
void
Tree_DInfoChanged(
TreeCtrl *tree, /* Widget info. */
int flags /* DINFO_xxx flags. */
)
{
TreeDInfo dInfo = tree->dInfo;
dInfo->flags |= flags;
Tree_EventuallyRedraw(tree);
}
/*
*--------------------------------------------------------------
*
* Tree_InvalidateArea --
*
* Mark as dirty parts of any DItems in the given area. If the given
* area overlaps the borders they are marked as needing to be
* redrawn.
*
* Results:
* None.
*
* Side effects:
* None.
*
*--------------------------------------------------------------
*/
void
Tree_InvalidateArea(
TreeCtrl *tree, /* Widget info. */
int x1, int y1, /* Left & top of dirty area in window
* coordinates. */
int x2, int y2 /* Right & bottom of dirty area in window
* coordinates. */
)
{
TreeDInfo dInfo = tree->dInfo;
DItem *dItem;
if (x1 >= x2 || y1 >= y2)
return;
if ((y2 > Tree_HeaderTop(tree)) && (y1 < Tree_HeaderBottom(tree))) {
TreeRectangle boundsL, bounds, boundsR;
int emptyL, empty, emptyR;
dInfo->flags |= DINFO_DRAW_HEADER;
emptyL = !Tree_AreaBbox(tree, TREE_AREA_HEADER_LEFT, &boundsL);
empty = !Tree_AreaBbox(tree, TREE_AREA_HEADER_NONE, &bounds);
emptyR = !Tree_AreaBbox(tree, TREE_AREA_HEADER_RIGHT, &boundsR);
dItem = dInfo->dItemHeader;
while (dItem != NULL) {
if ((!empty && (dItem->area.flags & DITEM_DRAWN)) &&
!(dItem->area.flags & DITEM_ALL_DIRTY) &&
(x2 > dItem->area.x) && (x1 < dItem->area.x + dItem->area.width) &&
(y2 > dItem->y) && (y1 < dItem->y + dItem->height)) {
InvalidateDItemX(dItem, &dItem->area, dItem->area.x, x1, x2 - x1);
InvalidateDItemY(dItem, &dItem->area, dItem->y, y1, y2 - y1);
dItem->area.flags |= DITEM_DIRTY;
}
if (!emptyL && !(dItem->left.flags & DITEM_ALL_DIRTY) &&
(x2 > TreeRect_Left(boundsL)) && (x1 < TreeRect_Right(boundsL)) &&
(y2 > dItem->y) && (y1 < dItem->y + dItem->height)) {
InvalidateDItemX(dItem, &dItem->left, dItem->left.x, x1, x2 - x1);
InvalidateDItemY(dItem, &dItem->left, dItem->y, y1, y2 - y1);
dItem->left.flags |= DITEM_DIRTY;
}
if (!emptyR && !(dItem->right.flags & DITEM_ALL_DIRTY) &&
(x2 > TreeRect_Left(boundsR)) && (x1 < TreeRect_Right(boundsR)) &&
(y2 > dItem->y) && (y1 < dItem->y + dItem->height)) {
InvalidateDItemX(dItem, &dItem->right, dItem->right.x, x1, x2 - x1);
InvalidateDItemY(dItem, &dItem->right, dItem->y, y1, y2 - y1);
dItem->right.flags |= DITEM_DIRTY;
}
dItem = dItem->next;
}
}
dItem = dInfo->dItem;
while (dItem != NULL) {
if ((!dInfo->empty && (dItem->area.flags & DITEM_DRAWN)/*dInfo->rangeFirstD != NULL*/) &&
!(dItem->area.flags & DITEM_ALL_DIRTY) &&
(x2 > dItem->area.x) && (x1 < dItem->area.x + dItem->area.width) &&
(y2 > dItem->y) && (y1 < dItem->y + dItem->height)) {
InvalidateDItemX(dItem, &dItem->area, dItem->area.x, x1, x2 - x1);
InvalidateDItemY(dItem, &dItem->area, dItem->y, y1, y2 - y1);
dItem->area.flags |= DITEM_DIRTY;
}
if (!dInfo->emptyL && !(dItem->left.flags & DITEM_ALL_DIRTY) &&
(x2 > TreeRect_Left(dInfo->boundsL)) && (x1 < TreeRect_Right(dInfo->boundsL)) &&
(y2 > dItem->y) && (y1 < dItem->y + dItem->height)) {
InvalidateDItemX(dItem, &dItem->left, dItem->left.x, x1, x2 - x1);
InvalidateDItemY(dItem, &dItem->left, dItem->y, y1, y2 - y1);
dItem->left.flags |= DITEM_DIRTY;
}
if (!dInfo->emptyR && !(dItem->right.flags & DITEM_ALL_DIRTY) &&
(x2 > TreeRect_Left(dInfo->boundsR)) && (x1 < TreeRect_Right(dInfo->boundsR)) &&
(y2 > dItem->y) && (y1 < dItem->y + dItem->height)) {
InvalidateDItemX(dItem, &dItem->right, dItem->right.x, x1, x2 - x1);
InvalidateDItemY(dItem, &dItem->right, dItem->y, y1, y2 - y1);
dItem->right.flags |= DITEM_DIRTY;
}
dItem = dItem->next;
}
if ((x1 < Tree_BorderLeft(tree)) ||
(y1 < Tree_BorderTop(tree)) ||
(x2 > Tree_BorderRight(tree)) ||
(y2 > Tree_BorderBottom(tree))) {
dInfo->flags |= DINFO_DRAW_HIGHLIGHT;
dInfo->flags |= DINFO_DRAW_BORDER;
}
/* Invalidate part of the whitespace */
InvalidateWhitespace(tree, x1, y1, x2, y2);
if (tree->debug.enable && tree->debug.display && tree->debug.eraseColor) {
XFillRectangle(tree->display, Tk_WindowId(tree->tkwin),
tree->debug.gcErase, x1, y1, x2 - x1, y2 - y1);
DisplayDelay(tree);
}
}
/*
*--------------------------------------------------------------
*
* Tree_InvalidateRegion --
*
* Mark as dirty parts of any DItems in the given area. If the given
* area overlaps the borders they are marked as needing to be
* redrawn.
*
* Results:
* None.
*
* Side effects:
* None.
*
*--------------------------------------------------------------
*/
void
Tree_InvalidateRegion(
TreeCtrl *tree, /* Widget info. */
TkRegion region /* Region to mark as dirty, in window
* coordinates. */
)
{
TreeDInfo dInfo = tree->dInfo;
DItem *dItem;
TreeRectangle rect;
int x1, x2, y1, y2;
TkRegion rgn;
Tree_GetRegionBounds(region, &rect);
if (!rect.width || !rect.height)
return;
/* FIXME: Should do for headers what I do for items, but this code isn't
* even called with DOUBLEBUFFER_WINDOW. */
if (Tree_AreaBbox(tree, TREE_AREA_HEADER, &rect) &&
TkRectInRegion(region, TreeRect_Left(rect), TreeRect_Top(rect),
TreeRect_Width(rect), TreeRect_Height(rect))
!= RectangleOut) {
dInfo->flags |= DINFO_DRAW_HEADER;
}
rgn = Tree_GetRegion(tree);
dItem = dInfo->dItem;
while (dItem != NULL) {
if ((!dInfo->empty && (dItem->area.flags & DITEM_DRAWN)/*dInfo->rangeFirstD != NULL*/) && !(dItem->area.flags & DITEM_ALL_DIRTY)) {
rect.x = dItem->area.x;
rect.y = dItem->y;
rect.width = dItem->area.width;
rect.height = dItem->height;
Tree_SetRectRegion(rgn, &rect);
TkIntersectRegion(region, rgn, rgn);
Tree_GetRegionBounds(rgn, &rect);
if (rect.width > 0 && rect.height > 0) {
InvalidateDItemX(dItem, &dItem->area, dItem->area.x, rect.x, rect.width);
InvalidateDItemY(dItem, &dItem->area, dItem->y, rect.y, rect.height);
dItem->area.flags |= DITEM_DIRTY;
}
}
if (!dInfo->emptyL && !(dItem->left.flags & DITEM_ALL_DIRTY)) {
rect.x = dItem->left.x;
rect.y = dItem->y;
rect.width = dItem->left.width;
rect.height = dItem->height;
Tree_SetRectRegion(rgn, &rect);
TkIntersectRegion(region, rgn, rgn);
Tree_GetRegionBounds(rgn, &rect);
if (rect.width > 0 && rect.height > 0) {
InvalidateDItemX(dItem, &dItem->left, dItem->left.x, rect.x, rect.width);
InvalidateDItemY(dItem, &dItem->left, dItem->y, rect.y, rect.height);
dItem->left.flags |= DITEM_DIRTY;
}
}
if (!dInfo->emptyR && !(dItem->right.flags & DITEM_ALL_DIRTY)) {
rect.x = dItem->right.x;
rect.y = dItem->y;
rect.width = dItem->right.width;
rect.height = dItem->height;
Tree_SetRectRegion(rgn, &rect);
TkIntersectRegion(region, rgn, rgn);
Tree_GetRegionBounds(rgn, &rect);
if (rect.width > 0 && rect.height > 0) {
InvalidateDItemX(dItem, &dItem->right, dItem->right.x, rect.x, rect.width);
InvalidateDItemY(dItem, &dItem->right, dItem->y, rect.y, rect.height);
dItem->right.flags |= DITEM_DIRTY;
}
}
dItem = dItem->next;
}
Tree_GetRegionBounds(region, &rect);
x1 = rect.x, x2 = rect.x + rect.width;
y1 = rect.y, y2 = rect.y + rect.height;
if ((x1 < Tree_BorderLeft(tree)) ||
(y1 < Tree_BorderTop(tree)) ||
(x2 > Tree_BorderRight(tree)) ||
(y2 > Tree_BorderBottom(tree))) {
dInfo->flags |= DINFO_DRAW_HIGHLIGHT;
dInfo->flags |= DINFO_DRAW_BORDER;
}
/* Invalidate part of the whitespace */
TkSubtractRegion(dInfo->wsRgn, region, dInfo->wsRgn);
Tree_FreeRegion(tree, rgn);
if (tree->debug.enable && tree->debug.display && tree->debug.eraseColor) {
Tree_FillRegion(tree->display, Tk_WindowId(tree->tkwin),
tree->debug.gcErase, region);
DisplayDelay(tree);
}
}
/*
*--------------------------------------------------------------
*
* Tree_InvalidateItemArea --
*
* Mark as dirty parts of any DItems in the given area. This is
* like Tree_InvalidateArea() but the given area is clipped inside
* the borders/header.
*
* Results:
* None.
*
* Side effects:
* None.
*
*--------------------------------------------------------------
*/
void
Tree_InvalidateItemArea(
TreeCtrl *tree, /* Widget info. */
int x1, int y1, /* Left & top of dirty area in window
* coordinates. */
int x2, int y2 /* Right & bottom of dirty area in window
* coordinates. */
)
{
if (x1 < Tree_ContentLeft(tree))
x1 = Tree_ContentLeft(tree);
if (y1 < Tree_ContentTop(tree))
y1 = Tree_ContentTop(tree);
if (x2 > Tree_ContentRight(tree))
x2 = Tree_ContentRight(tree);
if (y2 > Tree_ContentBottom(tree))
y2 = Tree_ContentBottom(tree);
Tree_InvalidateArea(tree, x1, y1, x2, y2);
}
/*
*--------------------------------------------------------------
*
* Tree_InvalidateItemOnScrollX --
*
* Mark an item as needing to be redrawn completely if
* the list scrolls horizontally. This is called when a
* gradient whose coordinates aren't canvas-relative
* is drawn in the item.
*
* Results:
* None.
*
* Side effects:
* None.
*
*--------------------------------------------------------------
*/
void
Tree_InvalidateItemOnScrollX(
TreeCtrl *tree, /* Widget info. */
TreeItem item
)
{
DItem *dItem = (DItem *) TreeItem_GetDInfo(tree, item);
if ((dItem == NULL) || DItemAllDirty(tree, dItem))
return;
dItem->flags |= DITEM_INVALIDATE_ON_SCROLL_X;
}
/*
*--------------------------------------------------------------
*
* Tree_InvalidateItemOnScrollY --
*
* Mark an item as needing to be redrawn completely if
* the list scrolls vertically. This is called when a
* gradient whose coordinates aren't canvas-relative
* is drawn in the item.
*
* Results:
* None.
*
* Side effects:
* None.
*
*--------------------------------------------------------------
*/
void
Tree_InvalidateItemOnScrollY(
TreeCtrl *tree, /* Widget info. */
TreeItem item
)
{
DItem *dItem = (DItem *) TreeItem_GetDInfo(tree, item);
if ((dItem == NULL) || DItemAllDirty(tree, dItem))
return;
dItem->flags |= DITEM_INVALIDATE_ON_SCROLL_Y;
}
/*
*--------------------------------------------------------------
*
* Tree_RedrawArea --
*
* Mark as dirty parts of any DItems in the given area. If the given
* area overlaps the borders they are marked as needing to be
* redrawn. The given area is subtracted from the whitespace region
* so that that part of the whitespace region will be redrawn.
*
* Results:
* None.
*
* Side effects:
* The widget will be redisplayed at idle time.
*
*--------------------------------------------------------------
*/
void
Tree_RedrawArea(
TreeCtrl *tree, /* Widget info. */
int x1, int y1, /* Left & top of dirty area in window
* coordinates. */
int x2, int y2 /* Right & bottom of dirty area in window
* coordinates. */
)
{
Tree_InvalidateArea(tree, x1, y1, x2, y2);
Tree_EventuallyRedraw(tree);
}
/*
*--------------------------------------------------------------
*
* Tree_ExposeArea --
*
* Called in response to <Expose> events. Causes part of the window
* to be redisplayed. With "-doublebuffer window", part of the
* offscreen pixmap is marked as needing to be copied but no redrawing
* of items is done. Without "-doublebuffer window", items will be
* redrawn.
*
* Results:
* None.
*
* Side effects:
* The widget will be redisplayed at idle time.
*
*--------------------------------------------------------------
*/
void
Tree_ExposeArea(
TreeCtrl *tree, /* Widget info. */
int x1, int y1, /* Left & top of dirty area in window
* coordinates. */
int x2, int y2 /* Right & bottom of dirty area in window
* coordinates. */
)
{
TreeDInfo dInfo = tree->dInfo;
if (tree->doubleBuffer == DOUBLEBUFFER_WINDOW) {
if ((x1 < Tree_BorderLeft(tree)) ||
(y1 < Tree_BorderTop(tree)) ||
(x2 > Tree_BorderRight(tree)) ||
(y2 > Tree_BorderBottom(tree))) {
dInfo->flags |= DINFO_DRAW_HIGHLIGHT;
dInfo->flags |= DINFO_DRAW_BORDER;
Tree_EventuallyRedraw(tree);
}
if (x1 < Tree_BorderLeft(tree))
x1 = Tree_BorderLeft(tree);
if (x2 > Tree_BorderRight(tree))
x2 = Tree_BorderRight(tree);
if (y1 < Tree_BorderTop(tree))
y1 = Tree_BorderTop(tree);
if (y2 > Tree_BorderBottom(tree))
y2 = Tree_BorderBottom(tree);
/* Got some 0,0,0,0 expose events from Windows Tk. */
if (x1 >= x2 || y1 >= y2)
return;
DblBufWinDirty(tree, x1, y1, x2, y2);
if (tree->debug.enable && tree->debug.display && tree->debug.eraseColor) {
XFillRectangle(tree->display, Tk_WindowId(tree->tkwin),
tree->debug.gcErase, x1, y1, x2 - x1, y2 - y1);
DisplayDelay(tree);
}
} else {
Tree_InvalidateArea(tree, x1, y1, x2, y2);
}
Tree_EventuallyRedraw(tree);
}
/*
*--------------------------------------------------------------
*
* TreeDisplay_InitWidget --
*
* Perform display-related initialization when a new TreeCtrl is
* created.
*
* Results:
* None.
*
* Side effects:
* Memory is allocated.
*
*--------------------------------------------------------------
*/
void
TreeDisplay_InitWidget(
TreeCtrl *tree /* Widget info. */
)
{
TreeDInfo dInfo;
XGCValues gcValues;
dInfo = (TreeDInfo) ckalloc(sizeof(TreeDInfo_));
memset(dInfo, '\0', sizeof(TreeDInfo_));
gcValues.graphics_exposures = True;
dInfo->scrollGC = Tk_GetGC(tree->tkwin, GCGraphicsExposures, &gcValues);
dInfo->flags = DINFO_OUT_OF_DATE;
dInfo->wsRgn = Tree_GetRegion(tree);
dInfo->dirtyRgn = TkCreateRegion();
Tcl_InitHashTable(&dInfo->itemVisHash, TCL_ONE_WORD_KEYS);
Tcl_InitHashTable(&dInfo->headerVisHash, TCL_ONE_WORD_KEYS);
#if REDRAW_RGN == 1
dInfo->redrawRgn = TkCreateRegion();
#endif /* REDRAW_RGN */
tree->dInfo = dInfo;
}
/*
*--------------------------------------------------------------
*
* TreeDisplay_FreeWidget --
*
* Free display-related resources for a deleted TreeCtrl.
*
* Results:
* None.
*
* Side effects:
* Memory is allocated.
*
*--------------------------------------------------------------
*/
void
TreeDisplay_FreeWidget(
TreeCtrl *tree /* Widget info. */
)
{
TreeDInfo dInfo = tree->dInfo;
Range *range = dInfo->rangeFirst;
Tcl_HashEntry *hPtr;
Tcl_HashSearch search;
if (dInfo->rItem != NULL)
ckfree((char *) dInfo->rItem);
if (dInfo->rangeLock != NULL)
ckfree((char *) dInfo->rangeLock);
while (dInfo->dItem != NULL) {
DItem *next = dInfo->dItem->next;
WFREE(dInfo->dItem, DItem);
dInfo->dItem = next;
}
while (dInfo->dItemHeader != NULL) {
DItem *next = dInfo->dItemHeader->next;
WFREE(dInfo->dItem, DItem);
dInfo->dItemHeader = next;
}
while (dInfo->dItemFree != NULL) {
DItem *next = dInfo->dItemFree->next;
WFREE(dInfo->dItemFree, DItem);
dInfo->dItemFree = next;
}
while (range != NULL)
range = Range_Free(tree, range);
Tk_FreeGC(tree->display, dInfo->scrollGC);
if (dInfo->flags & DINFO_REDRAW_PENDING)
Tcl_CancelIdleCall(Tree_Display, (ClientData) tree);
if (dInfo->pixmapW.drawable != None)
Tk_FreePixmap(tree->display, dInfo->pixmapW.drawable);
if (dInfo->pixmapI.drawable != None)
Tk_FreePixmap(tree->display, dInfo->pixmapI.drawable);
if (dInfo->pixmapT.drawable != None)
Tk_FreePixmap(tree->display, dInfo->pixmapT.drawable);
#if CACHE_BG_IMG
if (dInfo->pixmapBgImg.drawable != None)
Tk_FreePixmap(tree->display, dInfo->pixmapBgImg.drawable);
#endif
if (dInfo->xScrollIncrements.increments != NULL)
ckfree((char *) dInfo->xScrollIncrements.increments);
if (dInfo->yScrollIncrements.increments != NULL)
ckfree((char *) dInfo->yScrollIncrements.increments);
Tree_FreeRegion(tree, dInfo->wsRgn);
TkDestroyRegion(dInfo->dirtyRgn);
#ifdef DCOLUMN
hPtr = Tcl_FirstHashEntry(&dInfo->itemVisHash, &search);
while (hPtr != NULL) {
ckfree((char *) Tcl_GetHashValue(hPtr));
hPtr = Tcl_NextHashEntry(&search);
}
hPtr = Tcl_FirstHashEntry(&dInfo->headerVisHash, &search);
while (hPtr != NULL) {
ckfree((char *) Tcl_GetHashValue(hPtr));
hPtr = Tcl_NextHashEntry(&search);
}
#endif
Tcl_DeleteHashTable(&dInfo->itemVisHash);
Tcl_DeleteHashTable(&dInfo->headerVisHash);
#if REDRAW_RGN == 1
TkDestroyRegion(dInfo->redrawRgn);
#endif /* REDRAW_RGN */
WFREE(dInfo, TreeDInfo_);
}
int
Tree_DumpDInfo(
TreeCtrl *tree, /* Widget info. */
int objc, /* Number of arguments. */
Tcl_Obj *CONST objv[] /* Argument values. */
)
{
Tcl_Interp *interp = tree->interp;
TreeDInfo dInfo = tree->dInfo;
Tcl_DString dString;
DItem *dItem;
Range *range;
RItem *rItem;
int index;
static CONST char *optionNames[] = {
"alloc", "ditem", "onscreen", "range", (char *) NULL
};
#undef DUMP_ALLOC /* [BUG 2233922] SunOS: build error */
enum { DUMP_ALLOC, DUMP_DITEM, DUMP_ONSCREEN, DUMP_RANGE };
if (objc != 4) {
Tcl_WrongNumArgs(interp, 3, objv, "option");
return TCL_ERROR;
}
if (Tcl_GetIndexFromObj(interp, objv[3], optionNames, "option", 0,
&index) != TCL_OK) {
return TCL_ERROR;
}
Tcl_DStringInit(&dString);
if (index == DUMP_ALLOC) {
int count = 0, size = 0;
for (dItem = dInfo->dItem; dItem != NULL; dItem = dItem->next) {
count += 1;
}
for (dItem = dInfo->dItemFree; dItem != NULL; dItem = dItem->next) {
count += 1;
}
size = count * sizeof(DItem);
DStringAppendf(&dString, "%-20s: %8d : %8d B %5d KB\n",
"DItem", count, size, (size + 1023) / 1024);
count = dInfo->rItemMax;
size = count * sizeof(RItem);
DStringAppendf(&dString, "%-20s: %8d : %8d B %5d KB\n",
"RItem", count, size, (size + 1023) / 1024);
}
if (index == DUMP_DITEM) {
DStringAppendf(&dString, "DumpDInfo: itemW,H %d,%d totalW,H %d,%d flags 0x%0x vertical %d itemVisCount %d\n",
dInfo->itemWidth, dInfo->itemHeight,
dInfo->totalWidth, dInfo->totalHeight,
dInfo->flags, tree->vertical, tree->itemVisCount);
DStringAppendf(&dString, " empty=%d bounds=%d,%d,%d,%d\n", dInfo->empty,
dInfo->bounds.x, dInfo->bounds.y,
TreeRect_Right(dInfo->bounds), TreeRect_Bottom(dInfo->bounds));
DStringAppendf(&dString, " emptyL=%d boundsL=%d,%d,%d,%d\n", dInfo->emptyL,
dInfo->boundsL.x, dInfo->boundsL.y,
TreeRect_Right(dInfo->boundsL), TreeRect_Bottom(dInfo->boundsL));
DStringAppendf(&dString, " emptyR=%d boundsR=%d,%d,%d,%d\n", dInfo->emptyR,
dInfo->boundsR.x, dInfo->boundsR.y,
TreeRect_Right(dInfo->boundsR), TreeRect_Bottom(dInfo->boundsR));
dItem = dInfo->dItem;
while (dItem != NULL) {
if (dItem->item == NULL) {
DStringAppendf(&dString, " item NULL\n");
} else {
DStringAppendf(&dString, " item %d x,y,w,h %d,%d,%d,%d dirty %d,%d,%d,%d flags %0X\n",
TreeItem_GetID(tree, dItem->item),
dItem->area.x, dItem->y, dItem->area.width, dItem->height,
dItem->area.dirty[LEFT], dItem->area.dirty[TOP],
dItem->area.dirty[RIGHT], dItem->area.dirty[BOTTOM],
dItem->area.flags);
DStringAppendf(&dString, " left: dirty %d,%d,%d,%d flags %0X\n",
dItem->left.dirty[LEFT], dItem->left.dirty[TOP],
dItem->left.dirty[RIGHT], dItem->left.dirty[BOTTOM],
dItem->left.flags);
DStringAppendf(&dString, " right: dirty %d,%d,%d,%d flags %0X\n",
dItem->right.dirty[LEFT], dItem->right.dirty[TOP],
dItem->right.dirty[RIGHT], dItem->right.dirty[BOTTOM],
dItem->right.flags);
}
dItem = dItem->next;
}
}
if (index == DUMP_ONSCREEN) {
dItem = dInfo->dItem;
while (dItem != NULL) {
Tcl_HashEntry *hPtr = Tcl_FindHashEntry(&dInfo->itemVisHash, (char *) dItem->item);
TreeColumn *value = (TreeColumn *) Tcl_GetHashValue(hPtr);
DStringAppendf(&dString, "item %d:", TreeItem_GetID(tree, dItem->item));
while (*value != NULL) {
DStringAppendf(&dString, " %d", TreeColumn_GetID(*value));
++value;
}
DStringAppendf(&dString, "\n");
dItem = dItem->next;
}
}
if (index == DUMP_RANGE) {
DStringAppendf(&dString, " dInfo.rangeFirstD %p dInfo.rangeLastD %p dInfo.rangeLock %p\n",
dInfo->rangeFirstD, dInfo->rangeLastD, dInfo->rangeLock);
for (range = dInfo->rangeFirstD ? dInfo->rangeFirstD : dInfo->rangeLock;
range != NULL;
range = range->next) {
DStringAppendf(&dString, " Range: x,y,w,h %d,%d,%d,%d\n",
range->offset.x, range->offset.y,
range->totalWidth, range->totalHeight);
if (range == dInfo->rangeLastD)
break;
}
DStringAppendf(&dString, " dInfo.rangeFirst %p dInfo.rangeLast %p\n",
dInfo->rangeFirst, dInfo->rangeLast);
for (range = dInfo->rangeFirst;
range != NULL;
range = range->next) {
DStringAppendf(&dString, " Range: first %p last %p x,y,w,h %d,%d,%d,%d\n",
range->first, range->last,
range->offset.x, range->offset.y,
range->totalWidth, range->totalHeight, range->offset);
rItem = range->first;
while (1) {
DStringAppendf(&dString, " RItem: item %d index %d offset %d size %d\n",
TreeItem_GetID(tree, rItem->item), rItem->index, rItem->offset, rItem->size);
if (rItem == range->last)
break;
rItem++;
}
}
}
Tcl_DStringResult(tree->interp, &dString);
return TCL_OK;
}