mirror of
https://github.com/WinampDesktop/winamp.git
synced 2024-12-20 19:25:56 +01:00
1646 lines
36 KiB
C++
1646 lines
36 KiB
C++
|
#include "precomp.h"
|
||
|
|
||
|
#include "treewnd.h"
|
||
|
|
||
|
#include <tataki/canvas/ifc_canvas.h>
|
||
|
#include <bfc/stack.h>
|
||
|
#include <api/wnd/wndclass/scrollbar.h>
|
||
|
#include <tataki/color/skinclr.h>
|
||
|
#include <api/wnd/notifmsg.h>
|
||
|
#include <api/wnd/accessible.h>
|
||
|
#include <api/wnd/PaintCanvas.h>
|
||
|
|
||
|
#define DEF_TEXT_SIZE 14
|
||
|
#define CHILD_INDENT itemHeight
|
||
|
#define X_SHIFT 2
|
||
|
#define Y_SHIFT 2
|
||
|
#define DRAG_THRESHOLD 4
|
||
|
|
||
|
#define TIMER_EDIT_DELAY 1000
|
||
|
#define TIMER_EDIT_ID 1249
|
||
|
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
// TreeWnd
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
static SkinColor textcolor(L"wasabi.tree.text");
|
||
|
static SkinColor drophilitecolor(L"wasabi.tree.hiliteddrop");
|
||
|
static SkinColor selectedcolor(L"wasabi.tree.selected");
|
||
|
|
||
|
int CompareTreeItem::compareItem(TreeItem *p1, TreeItem *p2) {
|
||
|
return p1->getTree()->compareItem(p1, p2);
|
||
|
}
|
||
|
|
||
|
TreeWnd::TreeWnd() {
|
||
|
tabClosed = NULL;
|
||
|
tabOpen = NULL;
|
||
|
linkTopBottom = NULL;
|
||
|
linkTopRight = NULL;
|
||
|
linkTopRightBottom = NULL;
|
||
|
linkTabTopBottom = NULL;
|
||
|
linkTabTopRight = NULL;
|
||
|
linkTabTopRightBottom = NULL;
|
||
|
curSelected = NULL;
|
||
|
mousedown_item = NULL;
|
||
|
hitItem = NULL;
|
||
|
draggedItem = NULL;
|
||
|
tipitem = NULL;
|
||
|
edited = NULL;
|
||
|
editwnd = NULL;
|
||
|
metrics_ok = FALSE;
|
||
|
setSorted(TRUE);
|
||
|
setFontSize(DEF_TEXT_SIZE);
|
||
|
redraw = TRUE;
|
||
|
prevbdownitem = NULL;
|
||
|
autoedit=0;
|
||
|
autocollapse=1;
|
||
|
|
||
|
tabClosed = L"wasabi.tree.tab.closed";
|
||
|
tabOpen = L"wasabi.tree.tab.open";
|
||
|
linkTopBottom = L"wasabi.tree.link.top.bottom";
|
||
|
linkTopRight = L"wasabi.tree.link.top.right";
|
||
|
linkTopRightBottom = L"wasabi.tree.link.top.rightBottom";
|
||
|
linkTabTopBottom = L"wasabi.tree.link.tab.top.bottom";
|
||
|
linkTabTopRight = L"wasabi.tree.link.tab.top.right";
|
||
|
linkTabTopRightBottom = L"wasabi.tree.link.tab.top.rightBottom";
|
||
|
}
|
||
|
|
||
|
TreeWnd::~TreeWnd() {
|
||
|
// delete all root items
|
||
|
deleteAllItems();
|
||
|
drawList.removeAll();
|
||
|
}
|
||
|
|
||
|
int TreeWnd::onInit() {
|
||
|
TREEWND_PARENT::onInit();
|
||
|
|
||
|
setBgBitmap(L"wasabi.tree.background");
|
||
|
setLineHeight(itemHeight);
|
||
|
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
void TreeWnd::setRedraw(bool r) {
|
||
|
int old = redraw;
|
||
|
redraw = r;
|
||
|
if (!old && redraw)
|
||
|
invalidate();
|
||
|
}
|
||
|
|
||
|
int TreeWnd::onPaint(Canvas *canvas) {
|
||
|
|
||
|
PaintCanvas paintcanvas;
|
||
|
PaintBltCanvas paintbcanvas;
|
||
|
|
||
|
if (canvas == NULL) {
|
||
|
if (needDoubleBuffer()) {
|
||
|
if (!paintbcanvas.beginPaintNC(this)) return 0;
|
||
|
canvas = &paintbcanvas;
|
||
|
} else {
|
||
|
if (!paintcanvas.beginPaint(this)) return 0;
|
||
|
canvas = &paintcanvas;
|
||
|
}
|
||
|
}
|
||
|
TREEWND_PARENT::onPaint(canvas);
|
||
|
|
||
|
/* uncomment if you add columns or anything that should be not be drawn over by onPaint in which case you'll have to clip->subtract(your_region)
|
||
|
api_region *clip = new RegionI();
|
||
|
canvas->getClipRgn(clip); */
|
||
|
|
||
|
/*RECT r;
|
||
|
getNonClientRect(&r);
|
||
|
|
||
|
int y = -getScrollY()+Y_SHIFT+r.top;
|
||
|
int x = -getScrollX()+X_SHIFT;*/
|
||
|
|
||
|
Wasabi::FontInfo fontInfo;
|
||
|
fontInfo.color = textcolor;
|
||
|
fontInfo.opaque=false;
|
||
|
fontInfo.pointSize = getFontSize();
|
||
|
|
||
|
firstItemVisible = NULL;
|
||
|
lastItemVisible = NULL;
|
||
|
|
||
|
ensureMetricsValid();
|
||
|
|
||
|
//drawSubItems(canvas, x, &y, items, r.top, r.bottom, 0);
|
||
|
drawItems(canvas, &fontInfo);
|
||
|
|
||
|
canvas->selectClipRgn(NULL); // reset cliping region - NEEDED;
|
||
|
|
||
|
// delete clip; uncomment if necessary
|
||
|
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
void TreeWnd::drawItems(Canvas *canvas, const Wasabi::FontInfo *fontInfo)
|
||
|
{
|
||
|
RECT r, c, ir;
|
||
|
RegionI *orig=NULL;
|
||
|
getClientRect(&r);
|
||
|
if (!canvas->getClipBox(&c)) {
|
||
|
getClientRect(&c);
|
||
|
orig = new RegionI(&c);
|
||
|
} else
|
||
|
orig = new RegionI(canvas);
|
||
|
|
||
|
int first = ((c.top-r.top) + getScrollY() - Y_SHIFT) / itemHeight;
|
||
|
int last = ((c.bottom-r.top) + getScrollY() - Y_SHIFT) / itemHeight + 1;
|
||
|
POINT pt;
|
||
|
TreeItem *item;
|
||
|
bool hastab;
|
||
|
|
||
|
for (int i=first;i<=last;i++)
|
||
|
{
|
||
|
|
||
|
if (i >= drawList.getNumItems()) break;
|
||
|
|
||
|
item = drawList[i];
|
||
|
if (!item) continue;
|
||
|
item->getCurRect(&ir);
|
||
|
pt.x = r.left + X_SHIFT+item->getIndent()*itemHeight - getScrollX();//ir.left;
|
||
|
pt.y = ir.top;
|
||
|
|
||
|
// if we need the +/- icon and any of the link lines, draw them
|
||
|
if (item->needTab()) {
|
||
|
// pt.x += itemHeight;
|
||
|
RECT _r={pt.x-itemHeight, pt.y, pt.x, pt.y+itemHeight};
|
||
|
(item->isCollapsed() ? tabClosed : tabOpen).stretchToRectAlpha(canvas, &_r);
|
||
|
hastab=TRUE;
|
||
|
} else hastab = FALSE;
|
||
|
|
||
|
int indent = item->getIndent();
|
||
|
|
||
|
for (int j=0;j<indent;j++)
|
||
|
{
|
||
|
RECT _r={pt.x-itemHeight*(j+1), pt.y, pt.x-itemHeight*j, pt.y+itemHeight};
|
||
|
int l = getLinkLine(item, j);
|
||
|
if (l == (LINK_RIGHT | LINK_TOP)) {
|
||
|
((hastab && j == 0) ? linkTabTopRight : linkTopRight).stretchToRectAlpha(canvas, &_r);
|
||
|
}
|
||
|
if (l == (LINK_RIGHT | LINK_TOP | LINK_BOTTOM)) {
|
||
|
((hastab && j == 0) ? linkTabTopRightBottom : linkTopRightBottom).stretchToRectAlpha(canvas, &_r);
|
||
|
}
|
||
|
if (l == (LINK_BOTTOM | LINK_TOP)) {
|
||
|
((hastab && j == 0) ? linkTabTopBottom : linkTopBottom).stretchToRectAlpha(canvas, &_r);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
item->customDraw(canvas, pt, itemHeight, (pt.x+getScrollX())-r.left-X_SHIFT, r, fontInfo);
|
||
|
}
|
||
|
|
||
|
delete orig;
|
||
|
}
|
||
|
|
||
|
TreeItem *TreeWnd::hitTest(int x, int y) {
|
||
|
POINT pt={x,y};
|
||
|
return hitTest(pt);
|
||
|
}
|
||
|
|
||
|
TreeItem *TreeWnd::hitTest(POINT pt) {
|
||
|
RECT r, ir;
|
||
|
getClientRect(&r);
|
||
|
|
||
|
int first = (getScrollY() - Y_SHIFT) / itemHeight;
|
||
|
int last = ((r.bottom-r.top) + getScrollY() - Y_SHIFT) / itemHeight + 1;
|
||
|
|
||
|
for (int i=first;i<=last;i++) {
|
||
|
|
||
|
if (i >= drawList.getNumItems()) break;
|
||
|
|
||
|
TreeItem *item = drawList.enumItem(i);
|
||
|
|
||
|
if (item) {
|
||
|
item->getCurRect(&ir);
|
||
|
if (Wasabi::Std::pointInRect(ir, pt) && item->isHitTestable())
|
||
|
return item;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
void TreeWnd::getMetrics(int *numItemsShown, int *mWidth) {
|
||
|
*mWidth=0;
|
||
|
*numItemsShown=0;
|
||
|
drawList.removeAll();
|
||
|
countSubItems(drawList, &items, X_SHIFT, numItemsShown, mWidth, 0);
|
||
|
}
|
||
|
|
||
|
void TreeWnd::countSubItems(PtrList<TreeItem> &drawlist, TreeItemList *_list, int indent, int *count, int *maxwidth, int z) {
|
||
|
|
||
|
TreeItemList &list = *_list;
|
||
|
|
||
|
for (int i=0;i<list.getNumItems();i++) {
|
||
|
|
||
|
TreeItem *nextitem = list[i];
|
||
|
|
||
|
int w = nextitem->getItemWidth(itemHeight, indent-X_SHIFT);
|
||
|
if (indent+w > *maxwidth) *maxwidth = w+indent;
|
||
|
|
||
|
int j = indent-(nextitem->needTab() ? itemHeight : 0);
|
||
|
int k;
|
||
|
k = indent + w;
|
||
|
|
||
|
nextitem->setCurRect(j, Y_SHIFT+(*count * itemHeight), k, Y_SHIFT+((*count+1) * itemHeight), z);
|
||
|
(*count)++;
|
||
|
|
||
|
drawlist.addItem(nextitem);
|
||
|
|
||
|
if (nextitem->isExpanded())
|
||
|
countSubItems(drawlist, &nextitem->subitems, indent+CHILD_INDENT, count, maxwidth, z+1);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void TreeWnd::timerCallback(int c) {
|
||
|
switch (c) {
|
||
|
case TIMER_EDIT_ID:
|
||
|
prevbdownitem = NULL;
|
||
|
killTimer(TIMER_EDIT_ID);
|
||
|
break;
|
||
|
default:
|
||
|
TREEWND_PARENT::timerCallback(c);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int TreeWnd::onLeftButtonDown(int x, int y) {
|
||
|
|
||
|
if (edited)
|
||
|
{
|
||
|
delete editwnd; editwnd = NULL;
|
||
|
endEditLabel(editbuffer);
|
||
|
}
|
||
|
|
||
|
POINT pt={x,y};
|
||
|
TreeItem *item = hitTest(pt);
|
||
|
|
||
|
if (item) {
|
||
|
mousedown_item = item;
|
||
|
mousedown_anchor.x = pt.x;
|
||
|
mousedown_anchor.y = pt.y;
|
||
|
mousedown_dragdone = FALSE;
|
||
|
// only do expand/collapse if was already selected
|
||
|
setCurItem(item, autocollapse?(curSelected == item):0, FALSE);
|
||
|
beginCapture();
|
||
|
}
|
||
|
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
int TreeWnd::onLeftButtonUp(int x, int y) {
|
||
|
if (getCapture())
|
||
|
endCapture();
|
||
|
TREEWND_PARENT::onLeftButtonUp(x, y);
|
||
|
POINT pt={x,y};
|
||
|
TreeItem *item = hitTest(pt);
|
||
|
if (autoedit && item == mousedown_item && item == prevbdownitem)
|
||
|
setCurItem(item, FALSE, TRUE);
|
||
|
else
|
||
|
if (autoedit) {
|
||
|
prevbdownitem = getCurItem();
|
||
|
setTimer(TIMER_EDIT_ID, TIMER_EDIT_DELAY);
|
||
|
}
|
||
|
|
||
|
mousedown_item = NULL;
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
int TreeWnd::onRightButtonUp(int x, int y){
|
||
|
TREEWND_PARENT::onRightButtonUp(x, y);
|
||
|
POINT pos={x,y};
|
||
|
|
||
|
TreeItem *ti = hitTest(pos);
|
||
|
if (ti != NULL) {
|
||
|
selectItem(ti);
|
||
|
if (onPreItemContextMenu(ti, x, y) == 0) {
|
||
|
int ret = ti->onContextMenu(x, y);
|
||
|
onPostItemContextMenu(ti, x, y, ret);
|
||
|
return ret;
|
||
|
}
|
||
|
return 1;
|
||
|
} else {
|
||
|
return onContextMenu(x, y);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int TreeWnd::onMouseMove(int x, int y) {
|
||
|
|
||
|
TREEWND_PARENT::onMouseMove(x, y);
|
||
|
|
||
|
POINT pt={x,y};
|
||
|
|
||
|
if (mousedown_item) {
|
||
|
if (!mousedown_dragdone && (ABS(pt.x - mousedown_anchor.x) > DRAG_THRESHOLD || ABS(pt.y - mousedown_anchor.y) > DRAG_THRESHOLD)) {
|
||
|
mousedown_dragdone = TRUE;
|
||
|
if (getCapture())
|
||
|
endCapture();
|
||
|
onBeginDrag(mousedown_item);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
TreeItem *item = hitTest(pt);
|
||
|
if (item) {
|
||
|
if (tipitem != item) {
|
||
|
tipitem = item;
|
||
|
RECT r;
|
||
|
RECT c;
|
||
|
getClientRect(&c);
|
||
|
item->getCurRect(&r);
|
||
|
const wchar_t *tt = item->getTip();
|
||
|
if (tt != NULL && *tt != '\0')
|
||
|
setLiveTip(tt);
|
||
|
else if (r.right > c.right || r.bottom > c.bottom || r.top < c.top || r.left < c.left)
|
||
|
setLiveTip(item->getLabel());
|
||
|
else
|
||
|
setLiveTip(NULL);
|
||
|
}
|
||
|
} else {
|
||
|
setLiveTip(NULL);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
int TreeWnd::onLeftButtonDblClk(int x, int y) {
|
||
|
TreeItem *item = hitTest(x, y);
|
||
|
if (item == NULL) return 0;
|
||
|
return item->onLeftDoubleClick();
|
||
|
}
|
||
|
|
||
|
int TreeWnd::onRightButtonDblClk(int x, int y) {
|
||
|
TreeItem *item = hitTest(x, y);
|
||
|
if (item == NULL) return 0;
|
||
|
return item->onRightDoubleClick();
|
||
|
}
|
||
|
|
||
|
void TreeWnd::setLiveTip(const wchar_t *tip)
|
||
|
{
|
||
|
if (!tip)
|
||
|
{
|
||
|
setTip(oldtip);
|
||
|
oldtip = L"";
|
||
|
return;
|
||
|
}
|
||
|
oldtip = TREEWND_PARENT::getTip();
|
||
|
setTip(tip);
|
||
|
}
|
||
|
|
||
|
int TreeWnd::onBeginDrag(TreeItem *treeitem)
|
||
|
{
|
||
|
wchar_t title[WA_MAX_PATH]=L"";
|
||
|
// item calls addDragItem()
|
||
|
if (!treeitem->onBeginDrag(title)) return 0;
|
||
|
ASSERT(draggedItem == NULL);
|
||
|
draggedItem = treeitem;
|
||
|
if (*title != 0) setSuggestedDropTitle(title);
|
||
|
handleDrag();
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
int TreeWnd::dragEnter(ifc_window *sourceWnd) {
|
||
|
// uh... we don't know yet, but we can accept drops in general
|
||
|
hitItem = NULL;
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
int TreeWnd::dragOver(int x, int y, ifc_window *sourceWnd) {
|
||
|
POINT pos={x,y};
|
||
|
screenToClient(&pos);
|
||
|
TreeItem *prevItem;
|
||
|
|
||
|
prevItem = hitItem;
|
||
|
hitItem = hitTest(pos);
|
||
|
|
||
|
// no dropping on yourself! :)
|
||
|
if (hitItem == draggedItem) hitItem = NULL;
|
||
|
|
||
|
// unselect previous item
|
||
|
if (prevItem != hitItem && prevItem != NULL) {
|
||
|
unhiliteDropItem(prevItem);
|
||
|
repaint(); // commit invalidation of unhilited item so no trouble with scrolling
|
||
|
prevItem->dragLeave(sourceWnd);
|
||
|
}
|
||
|
|
||
|
|
||
|
RECT r;
|
||
|
getClientRect(&r);
|
||
|
if (pos.y < r.top + 16) {
|
||
|
if (getScrollY() >= 0) {
|
||
|
scrollToY(MAX(0, getScrollY()-itemHeight));
|
||
|
}
|
||
|
} else if (pos.y > r.bottom - 16) {
|
||
|
if (getScrollY() < getMaxScrollY()) {
|
||
|
scrollToY(MIN(getMaxScrollY(), getScrollY()+itemHeight));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (hitItem != NULL) {
|
||
|
// hilight it
|
||
|
if (prevItem != hitItem) {
|
||
|
hiliteDropItem(hitItem);
|
||
|
repaint(); // commit invalidation of hilited so no trouble with scrolling
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (hitItem == NULL) return defaultDragOver(x, y, sourceWnd);
|
||
|
|
||
|
// ask the item if it can really accept such a drop
|
||
|
return hitItem->dragOver(sourceWnd);
|
||
|
}
|
||
|
|
||
|
int TreeWnd::dragLeave(ifc_window *sourceWnd) {
|
||
|
if (hitItem != NULL) {
|
||
|
unhiliteDropItem(hitItem);
|
||
|
hitItem->dragLeave(sourceWnd);
|
||
|
}
|
||
|
hitItem = NULL;
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
int TreeWnd::dragDrop(ifc_window *sourceWnd, int x, int y) {
|
||
|
int res;
|
||
|
if (hitItem == NULL) return defaultDragDrop(sourceWnd, x, y);
|
||
|
// unhilite the dest
|
||
|
unhiliteDropItem(hitItem);
|
||
|
// the actual drop
|
||
|
res = hitItem->dragDrop(sourceWnd);
|
||
|
if (res) {
|
||
|
onItemRecvDrop(hitItem);
|
||
|
}
|
||
|
hitItem = NULL;
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
int TreeWnd::dragComplete(int success) {
|
||
|
int ret;
|
||
|
ASSERT(draggedItem != NULL);
|
||
|
ret = draggedItem->dragComplete(success);
|
||
|
draggedItem = NULL;
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
void TreeItem::setTip(const wchar_t *tip)
|
||
|
{
|
||
|
tooltip = tip;
|
||
|
}
|
||
|
|
||
|
const wchar_t *TreeItem::getTip()
|
||
|
{
|
||
|
return tooltip;
|
||
|
}
|
||
|
|
||
|
void TreeWnd::hiliteDropItem(TreeItem *item) {
|
||
|
if (item)
|
||
|
item->setHilitedDrop(TRUE);
|
||
|
}
|
||
|
|
||
|
void TreeWnd::hiliteItem(TreeItem *item) {
|
||
|
if (item)
|
||
|
item->setHilited(TRUE);
|
||
|
}
|
||
|
|
||
|
void TreeWnd::selectItem(TreeItem *item) {
|
||
|
setCurItem(item, FALSE);
|
||
|
}
|
||
|
|
||
|
void TreeWnd::selectItemDeferred(TreeItem *item) {
|
||
|
postDeferredCallback(DC_SETITEM, (intptr_t)item);
|
||
|
}
|
||
|
|
||
|
void TreeWnd::delItemDeferred(TreeItem *item) {
|
||
|
postDeferredCallback(DC_DELITEM, (intptr_t)item);
|
||
|
}
|
||
|
|
||
|
void TreeWnd::unhiliteItem(TreeItem *item) {
|
||
|
if (item)
|
||
|
item->setHilited(FALSE);
|
||
|
}
|
||
|
|
||
|
void TreeWnd::unhiliteDropItem(TreeItem *item) {
|
||
|
if (item)
|
||
|
item->setHilitedDrop(FALSE);
|
||
|
}
|
||
|
|
||
|
void TreeWnd::setCurItem(TreeItem *item, bool expandCollapse, bool editifselected) {
|
||
|
if (curSelected && curSelected != item) {
|
||
|
onDeselectItem(curSelected);
|
||
|
curSelected->setSelected(FALSE);
|
||
|
}
|
||
|
if (item) {
|
||
|
curSelected = item;
|
||
|
onSelectItem(curSelected);
|
||
|
item->setSelected(TRUE, expandCollapse, editifselected);
|
||
|
setSlidersPosition();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Returns the current tree width in pixels
|
||
|
int TreeWnd::getContentsWidth() {
|
||
|
ensureMetricsValid();
|
||
|
return maxWidth;
|
||
|
}
|
||
|
|
||
|
// Returns the current tree height in pixels
|
||
|
int TreeWnd::getContentsHeight() {
|
||
|
ensureMetricsValid();
|
||
|
return maxHeight;
|
||
|
}
|
||
|
|
||
|
void TreeWnd::ensureMetricsValid() {
|
||
|
if (metrics_ok) return;
|
||
|
int n;
|
||
|
getMetrics(&n, &maxWidth);
|
||
|
maxWidth += X_SHIFT*2;
|
||
|
maxHeight = n*itemHeight+Y_SHIFT*2;
|
||
|
metrics_ok = TRUE;
|
||
|
setSlidersPosition();
|
||
|
}
|
||
|
|
||
|
// Gets notification from sliders
|
||
|
int TreeWnd::childNotify(ifc_window *child, int msg, intptr_t param1, intptr_t param2) {
|
||
|
switch (msg) {
|
||
|
case ChildNotify::EDITWND_ENTER_PRESSED:
|
||
|
if (child == editwnd && editwnd != NULL) {
|
||
|
endEditLabel(editbuffer);
|
||
|
return 1;
|
||
|
}
|
||
|
break;
|
||
|
case ChildNotify::EDITWND_CANCEL_PRESSED:
|
||
|
if (child == editwnd && editwnd != NULL) {
|
||
|
cancelEditLabel();
|
||
|
return 1;
|
||
|
}
|
||
|
break;
|
||
|
case ChildNotify::EDITWND_DATA_MODIFIED:
|
||
|
if (child == editwnd && editwnd != NULL) {
|
||
|
editUpdate();
|
||
|
return 1;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return TREEWND_PARENT::childNotify(child, msg, param1, param2);
|
||
|
}
|
||
|
|
||
|
void TreeWnd::editUpdate() {
|
||
|
ASSERT(edited != NULL && editwnd != NULL);
|
||
|
if (!edited || !editwnd) return;
|
||
|
int w = editwnd->getTextLength()+16;
|
||
|
RECT i, r, e;
|
||
|
edited->getCurRect(&i);
|
||
|
getClientRect(&r);
|
||
|
editwnd->getClientRect(&e);
|
||
|
e.left += i.left;
|
||
|
e.right += i.left;
|
||
|
e.top += i.top;
|
||
|
e.bottom += i.top;
|
||
|
e.right = i.left+w;
|
||
|
e.right = MIN<int>(r.right - X_SHIFT, e.right);
|
||
|
editwnd->resize(&e);
|
||
|
editwnd->invalidate();
|
||
|
}
|
||
|
|
||
|
TreeItem *TreeWnd::addTreeItem(TreeItem *item, TreeItem *par, int _sorted, int haschildtab) {
|
||
|
|
||
|
ASSERT(item != NULL);
|
||
|
ASSERTPR(item->getTree() == NULL, "can't transplant TreeItems");
|
||
|
ASSERTPR(item->getLabel() != NULL, "tree items must have a label to be inserted");
|
||
|
|
||
|
item->setSorted(_sorted);
|
||
|
item->setChildTab(haschildtab ? TAB_AUTO : TAB_NO/*&& par != NULL*/);
|
||
|
item->setTree(this);
|
||
|
item->linkTo(par);
|
||
|
|
||
|
if (par == NULL)
|
||
|
items.addItem(item);
|
||
|
|
||
|
all_items.addItem(item);
|
||
|
|
||
|
metrics_ok = FALSE;
|
||
|
|
||
|
if (redraw)
|
||
|
invalidate();
|
||
|
|
||
|
item->onTreeAdd();
|
||
|
|
||
|
return item;
|
||
|
}
|
||
|
|
||
|
int TreeWnd::removeTreeItem(TreeItem *item) {
|
||
|
ASSERT(item != NULL);
|
||
|
ASSERT(item->getTree() == this);
|
||
|
if (item->isSelected()) item->setSelected(FALSE);
|
||
|
if (curSelected == item) curSelected = NULL;
|
||
|
//CUT item->deleteSubitems();
|
||
|
TreeItem *par = item->getParent();
|
||
|
if (!par) { // is root item ?
|
||
|
ASSERT(items.haveItem(item));
|
||
|
items.removeItem(item);
|
||
|
} else {
|
||
|
if (!par->removeSubitem(item))
|
||
|
return 0;
|
||
|
}
|
||
|
all_items.removeItem(item);
|
||
|
metrics_ok = FALSE;
|
||
|
drawList.removeItem(item);
|
||
|
if (redraw)
|
||
|
invalidate();
|
||
|
|
||
|
item->setTree(NULL);
|
||
|
item->onTreeRemove();
|
||
|
|
||
|
if (par != NULL) par->onChildItemRemove(item);
|
||
|
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
void TreeWnd::moveTreeItem(TreeItem *item, TreeItem *newparent) {
|
||
|
ASSERT(item != NULL);
|
||
|
ASSERTPR(item->getTree() == this, "can't move between trees (fucks up Freelist)");
|
||
|
removeTreeItem(item);
|
||
|
addTreeItem(item, newparent, item->subitems.getAutoSort(), item->childTab);
|
||
|
}
|
||
|
|
||
|
void TreeWnd::deleteAllItems() {
|
||
|
bool save_redraw = redraw;
|
||
|
setRedraw(FALSE);
|
||
|
|
||
|
TreeItem *item;
|
||
|
while ((item = enumRootItem(0)) != NULL)
|
||
|
delete item;
|
||
|
|
||
|
setRedraw(save_redraw);
|
||
|
}
|
||
|
|
||
|
void TreeWnd::setSorted(bool dosort) {
|
||
|
items.setAutoSort(dosort);
|
||
|
}
|
||
|
|
||
|
bool TreeWnd::getSorted() {
|
||
|
return items.getAutoSort();
|
||
|
}
|
||
|
|
||
|
void TreeWnd::sortTreeItems() {
|
||
|
items.sort(TRUE);
|
||
|
metrics_ok = FALSE;
|
||
|
if (redraw)
|
||
|
invalidate();
|
||
|
}
|
||
|
|
||
|
TreeItem *TreeWnd::getSibling(TreeItem *item) {
|
||
|
for (int i=0;i<items.getNumItems();i++) {
|
||
|
if (items[i] == item) {
|
||
|
if (i == items.getNumItems()-1) return NULL;
|
||
|
return items[i+1];
|
||
|
}
|
||
|
}
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
void TreeWnd::setAutoCollapse(bool doautocollase) {
|
||
|
autocollapse=doautocollase;
|
||
|
}
|
||
|
|
||
|
int TreeWnd::onContextMenu(int x, int y) {
|
||
|
POINT pos={x,y};
|
||
|
screenToClient(&pos);
|
||
|
TreeItem *ti = hitTest(pos);
|
||
|
if (ti != NULL) {
|
||
|
selectItem(ti);
|
||
|
return ti->onContextMenu(x, y);
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int TreeWnd::onDeferredCallback(intptr_t param1, intptr_t param2) {
|
||
|
switch (param1) {
|
||
|
case DC_SETITEM:
|
||
|
setCurItem((TreeItem *)param2, FALSE);
|
||
|
return 1;
|
||
|
case DC_DELITEM:
|
||
|
delete (TreeItem *)param2;
|
||
|
return 1;
|
||
|
case DC_EXPAND:
|
||
|
expandItem((TreeItem *)param2);
|
||
|
return 1;
|
||
|
case DC_COLLAPSE:
|
||
|
collapseItem((TreeItem *)param2);
|
||
|
return 1;
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int TreeWnd::getNumRootItems() {
|
||
|
return items.getNumItems();
|
||
|
}
|
||
|
|
||
|
TreeItem *TreeWnd::enumRootItem(int which) {
|
||
|
return items[which];
|
||
|
}
|
||
|
|
||
|
void TreeWnd::invalidateMetrics() {
|
||
|
metrics_ok = FALSE;
|
||
|
}
|
||
|
|
||
|
int TreeWnd::getLinkLine(TreeItem *item, int level) {
|
||
|
|
||
|
ASSERT(item != NULL);
|
||
|
|
||
|
int l = 0;
|
||
|
int r = 0;
|
||
|
|
||
|
if (item->parent == NULL)
|
||
|
return 0;
|
||
|
|
||
|
TreeItem *cur=item;
|
||
|
|
||
|
while (cur->getParent() && l < level) {
|
||
|
cur = cur->getParent();
|
||
|
l++;
|
||
|
}
|
||
|
|
||
|
if (cur->getSibling()) r |= LINK_BOTTOM | LINK_TOP;
|
||
|
if (level == 0) r |= LINK_RIGHT;
|
||
|
if (level == 0 && cur->getParent()) r |= LINK_TOP;
|
||
|
|
||
|
return r;
|
||
|
|
||
|
}
|
||
|
|
||
|
int TreeWnd::onMouseWheelDown(int clicked, int lines) {
|
||
|
if (!clicked)
|
||
|
scrollToY(MIN(getMaxScrollY(), getScrollY()+itemHeight));
|
||
|
else
|
||
|
scrollToX(MIN(getMaxScrollX(), getScrollX()+itemHeight));
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
int TreeWnd::onMouseWheelUp(int clicked, int lines) {
|
||
|
if (!clicked)
|
||
|
scrollToY(MAX(0, getScrollY()-itemHeight));
|
||
|
else
|
||
|
scrollToX(MAX(0, getScrollX()-itemHeight));
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
int TreeWnd::expandItem(TreeItem *item) {
|
||
|
ASSERT(item != NULL);
|
||
|
|
||
|
return item->expand();
|
||
|
}
|
||
|
|
||
|
void TreeWnd::expandItemDeferred(TreeItem *item) {
|
||
|
postDeferredCallback(DC_EXPAND, (intptr_t)item);
|
||
|
}
|
||
|
|
||
|
int TreeWnd::collapseItem(TreeItem *item) {
|
||
|
ASSERT(item != NULL);
|
||
|
|
||
|
return item->collapse();
|
||
|
}
|
||
|
|
||
|
void TreeWnd::collapseItemDeferred(TreeItem *item) {
|
||
|
postDeferredCallback(DC_COLLAPSE, (intptr_t)item);
|
||
|
}
|
||
|
|
||
|
TreeItem *TreeWnd::getCurItem() {
|
||
|
return curSelected;
|
||
|
}
|
||
|
|
||
|
int TreeWnd::getItemRect(TreeItem *item, RECT *r) {
|
||
|
ASSERT(item != NULL);
|
||
|
|
||
|
return item->getCurRect(r);
|
||
|
}
|
||
|
|
||
|
void TreeWnd::editItemLabel(TreeItem *item) {
|
||
|
|
||
|
if (edited) {
|
||
|
edited->setEdition(FALSE);
|
||
|
edited->invalidate();
|
||
|
}
|
||
|
|
||
|
|
||
|
ASSERT(item != NULL);
|
||
|
if (item == NULL) return;
|
||
|
|
||
|
if (item->onBeginLabelEdit()) return;
|
||
|
item->setEdition(TRUE);
|
||
|
edited = item;
|
||
|
|
||
|
editwnd = new EditWnd();
|
||
|
editwnd->setModal(TRUE);
|
||
|
editwnd->setAutoSelect(TRUE);
|
||
|
editwnd->setStartHidden(TRUE);
|
||
|
editwnd->init(getOsModuleHandle(), getOsWindowHandle());
|
||
|
editwnd->setParent(this);
|
||
|
RECT r;
|
||
|
edited->getCurRect(&r);
|
||
|
RECT cr;
|
||
|
getClientRect(&cr);
|
||
|
r.right = cr.right;
|
||
|
if (r.bottom - r.top < 24) r.bottom = r.top + 24;
|
||
|
editwnd->resize(&r);
|
||
|
wcsncpy(editbuffer, edited->getLabel(), 256);
|
||
|
editwnd->setBuffer(editbuffer, 255);
|
||
|
editUpdate();
|
||
|
editwnd->setVisible(TRUE);
|
||
|
}
|
||
|
|
||
|
void TreeWnd::endEditLabel(const wchar_t *newlabel)
|
||
|
{
|
||
|
editwnd = NULL; // editwnd self destructs
|
||
|
if (edited->onEndLabelEdit(newlabel))
|
||
|
edited->setLabel(newlabel);
|
||
|
edited->setEdition(FALSE);
|
||
|
edited->invalidate();
|
||
|
onLabelChange(edited);
|
||
|
edited = NULL;
|
||
|
invalidateMetrics();
|
||
|
setSlidersPosition();
|
||
|
}
|
||
|
|
||
|
void TreeWnd::cancelEditLabel(int destroyit) {
|
||
|
ASSERT(edited != NULL);
|
||
|
if (!edited) return;
|
||
|
|
||
|
if (destroyit)
|
||
|
delete editwnd;
|
||
|
|
||
|
editwnd = NULL; // editwnd self destructs (update> except if destroyit for cancelling from treewnd)
|
||
|
edited->setEdition(FALSE);
|
||
|
edited->invalidate();
|
||
|
edited = NULL;
|
||
|
}
|
||
|
|
||
|
void TreeWnd::setAutoEdit(int ae) {
|
||
|
autoedit = ae;
|
||
|
}
|
||
|
|
||
|
int TreeWnd::getAutoEdit() {
|
||
|
return autoedit;
|
||
|
}
|
||
|
|
||
|
TreeItem *TreeWnd::getByLabel(TreeItem *item, const wchar_t *name)
|
||
|
{
|
||
|
TreeItem *ti;
|
||
|
// handle root-level searching
|
||
|
if (item == NULL) {
|
||
|
int n = getNumRootItems();
|
||
|
for (int i = 0; i < n; i++) {
|
||
|
ti = enumRootItem(i);
|
||
|
if (!wcscmp(name, ti->getLabel())) return ti;
|
||
|
ti = getByLabel(ti, name);
|
||
|
if (ti) return ti;
|
||
|
}
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
// check the given item
|
||
|
if (!wcscmp(name, item->getLabel())) return item;
|
||
|
|
||
|
// depth first search
|
||
|
ti = item->getChild();
|
||
|
if (ti != NULL) {
|
||
|
ti = getByLabel(ti, name);
|
||
|
if (ti != NULL) return ti;
|
||
|
}
|
||
|
|
||
|
// recursively check siblings
|
||
|
ti = item->getSibling();
|
||
|
if (ti != NULL) ti = getByLabel(ti, name);
|
||
|
|
||
|
return ti;
|
||
|
}
|
||
|
|
||
|
int TreeWnd::onGetFocus() {
|
||
|
int r = TREEWND_PARENT::onGetFocus();
|
||
|
|
||
|
#if 0
|
||
|
DebugString("yay got focus");
|
||
|
TreeItem *ti = getCurItem();
|
||
|
if (ti != NULL) {
|
||
|
ti->setSelected(FALSE);
|
||
|
selectItemDeferred(ti);
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
return r;
|
||
|
}
|
||
|
|
||
|
int TreeWnd::onKillFocus() {
|
||
|
|
||
|
TREEWND_PARENT::onKillFocus();
|
||
|
mousedown_item=NULL;
|
||
|
/* if (edited)
|
||
|
cancelEditLabel();*/
|
||
|
#if 0
|
||
|
DebugString("no mo focus");
|
||
|
#endif
|
||
|
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
int TreeWnd::onChar(unsigned int c)
|
||
|
{
|
||
|
int r = 0;
|
||
|
|
||
|
if (c == 27) {
|
||
|
if (edited)
|
||
|
cancelEditLabel(1);
|
||
|
}
|
||
|
|
||
|
if (curSelected != NULL && (r = curSelected->onChar(c)) != 0) return r;
|
||
|
|
||
|
wchar_t b = TOUPPERW(c);
|
||
|
if (b >= 'A' && b <= 'Z')
|
||
|
{
|
||
|
jumpToNext(b);
|
||
|
r = 1;
|
||
|
}
|
||
|
|
||
|
return r ? r : TREEWND_PARENT::onChar(c);
|
||
|
}
|
||
|
|
||
|
int TreeWnd::getNumVisibleChildItems(TreeItem *c) {
|
||
|
int nb=0;
|
||
|
for(int i=0;i<c->getNumChildren();i++) {
|
||
|
TreeItem *t=c->getNthChild(i);
|
||
|
if(t->hasSubItems() && t->isExpanded())
|
||
|
nb+=getNumVisibleChildItems(t);
|
||
|
nb++;
|
||
|
}
|
||
|
return nb;
|
||
|
}
|
||
|
|
||
|
int TreeWnd::getNumVisibleItems() {
|
||
|
int nb=0;
|
||
|
for(int i=0;i<items.getNumItems();i++) {
|
||
|
TreeItem *t=items.enumItem(i);
|
||
|
if(t->hasSubItems() && t->isExpanded())
|
||
|
nb+=getNumVisibleChildItems(t);
|
||
|
nb++;
|
||
|
}
|
||
|
return nb;
|
||
|
}
|
||
|
|
||
|
TreeItem *TreeWnd::enumVisibleChildItems(TreeItem *c, int n) {
|
||
|
int nb=0;
|
||
|
for(int i=0;i<c->getNumChildren();i++) {
|
||
|
TreeItem *t=c->getNthChild(i);
|
||
|
if(nb==n) return t;
|
||
|
if(t->hasSubItems() && t->isExpanded()) {
|
||
|
TreeItem *t2=enumVisibleChildItems(t, n-nb-1);
|
||
|
if(t2) return t2;
|
||
|
nb+=getNumVisibleChildItems(t);
|
||
|
}
|
||
|
nb++;
|
||
|
}
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
TreeItem *TreeWnd::enumVisibleItems(int n) {
|
||
|
int nb=0;
|
||
|
for(int i=0;i<items.getNumItems();i++) {
|
||
|
TreeItem *t=items.enumItem(i);
|
||
|
if(nb==n) return t;
|
||
|
if(t->hasSubItems() && t->isExpanded()) {
|
||
|
TreeItem *t2=enumVisibleChildItems(t, n-nb-1);
|
||
|
if(t2) return t2;
|
||
|
nb+=getNumVisibleChildItems(t);
|
||
|
}
|
||
|
nb++;
|
||
|
}
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
int TreeWnd::findChildItem(TreeItem *c, TreeItem *i, int *nb) {
|
||
|
for(int j=0;j<c->getNumChildren();j++) {
|
||
|
TreeItem *t=c->getNthChild(j); (*nb)++;
|
||
|
if (t == i) return *nb;
|
||
|
if(t->hasSubItems() && t->isExpanded()) {
|
||
|
int n = findChildItem(t, i, nb);
|
||
|
if (n != -1) return *nb;
|
||
|
}
|
||
|
}
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
int TreeWnd::findItem(TreeItem *i) {
|
||
|
int nb=-1;
|
||
|
for(int j=0;j<items.getNumItems();j++) {
|
||
|
TreeItem *t=items.enumItem(j); nb++;
|
||
|
if (t == i) return nb;
|
||
|
if(t->hasSubItems() && t->isExpanded()) {
|
||
|
int n = findChildItem(t, i, &nb);
|
||
|
if (n != -1) return nb;
|
||
|
}
|
||
|
}
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
|
||
|
TreeItem *TreeWnd::enumAllItems(int n) {
|
||
|
return all_items[n];
|
||
|
}
|
||
|
|
||
|
int TreeWnd::onKeyDown(int keycode)
|
||
|
{
|
||
|
switch(keycode)
|
||
|
{
|
||
|
case 113: {
|
||
|
TreeItem *item = getCurItem();
|
||
|
if (item)
|
||
|
item->editLabel();
|
||
|
return 1;
|
||
|
}
|
||
|
case STDKEY_UP: {
|
||
|
TreeItem *t=getCurItem();
|
||
|
int l=getNumVisibleItems();
|
||
|
if (t == NULL) {
|
||
|
if (l > 0) setCurItem(enumVisibleItems(getNumVisibleItems()-1), FALSE, FALSE);
|
||
|
} else {
|
||
|
for(int i=0;i<l;i++)
|
||
|
if(enumVisibleItems(i)==t) {
|
||
|
if(i-1>=0) {
|
||
|
TreeItem *t2=enumVisibleItems(i-1);
|
||
|
if(t2) setCurItem(t2,FALSE,FALSE);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return 1;
|
||
|
}
|
||
|
case STDKEY_DOWN: {
|
||
|
TreeItem *t=getCurItem();
|
||
|
int l=getNumVisibleItems();
|
||
|
if (t == NULL) {
|
||
|
if (l > 0) setCurItem(enumVisibleItems(0), FALSE, FALSE);
|
||
|
} else {
|
||
|
for(int i=0;i<l;i++)
|
||
|
if(enumVisibleItems(i)==t) {
|
||
|
TreeItem *t2=enumVisibleItems(i+1);
|
||
|
if(t2) setCurItem(t2,FALSE,FALSE);
|
||
|
}
|
||
|
}
|
||
|
return 1;
|
||
|
}
|
||
|
case VK_PRIOR: {
|
||
|
TreeItem *t=getCurItem();
|
||
|
int l=getNumVisibleItems();
|
||
|
for(int i=0;i<l;i++)
|
||
|
if(enumVisibleItems(i)==t) {
|
||
|
int a=MAX(i-5,0);
|
||
|
TreeItem *t2=enumVisibleItems(a);
|
||
|
if(t2) setCurItem(t2,FALSE,FALSE);
|
||
|
}
|
||
|
return 1;
|
||
|
}
|
||
|
case VK_NEXT: {
|
||
|
TreeItem *t=getCurItem();
|
||
|
int l=getNumVisibleItems();
|
||
|
for(int i=0;i<l;i++)
|
||
|
if(enumVisibleItems(i)==t) {
|
||
|
int a=MIN(i+5,l-1);
|
||
|
TreeItem *t2=enumVisibleItems(a);
|
||
|
if(t2) setCurItem(t2,FALSE,FALSE);
|
||
|
}
|
||
|
return 1;
|
||
|
}
|
||
|
case STDKEY_HOME: {
|
||
|
TreeItem *t=enumVisibleItems(0);
|
||
|
if(t) setCurItem(t,FALSE,FALSE);
|
||
|
return 1;
|
||
|
}
|
||
|
case STDKEY_END: {
|
||
|
TreeItem *t=enumVisibleItems(getNumVisibleItems()-1);
|
||
|
if(t) setCurItem(t,FALSE,FALSE);
|
||
|
return 1;
|
||
|
}
|
||
|
case STDKEY_LEFT: {
|
||
|
TreeItem *t=getCurItem();
|
||
|
if(t) t->collapse();
|
||
|
return 1;
|
||
|
}
|
||
|
case STDKEY_RIGHT: {
|
||
|
TreeItem *t=getCurItem();
|
||
|
if(t) t->expand();
|
||
|
return 1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return TREEWND_PARENT::onKeyDown(keycode);
|
||
|
}
|
||
|
|
||
|
void TreeWnd::jumpToNext(wchar_t c) {
|
||
|
firstFound=FALSE;
|
||
|
if (jumpToNextSubItems(&items, c)) return;
|
||
|
firstFound=TRUE;
|
||
|
jumpToNextSubItems(&items, c);
|
||
|
}
|
||
|
|
||
|
int TreeWnd::jumpToNextSubItems(TreeItemList *list, wchar_t c) {
|
||
|
|
||
|
for (int i=0;i<list->getNumItems();i++) {
|
||
|
|
||
|
TreeItem *nextitem = list->enumItem(i);
|
||
|
const wchar_t *l = nextitem->getLabel();
|
||
|
wchar_t b = l ? TOUPPERW(*l) : 0;
|
||
|
if (b == c && firstFound)
|
||
|
{
|
||
|
selectItem(nextitem);
|
||
|
nextitem->ensureVisible();
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
if (nextitem->isSelected()) firstFound = TRUE;
|
||
|
|
||
|
if (nextitem->isExpanded())
|
||
|
if (jumpToNextSubItems(&nextitem->subitems, c)) return 1;
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
void TreeWnd::ensureItemVisible(TreeItem *item) {
|
||
|
ASSERT(item != NULL);
|
||
|
|
||
|
// walk the parent tree to make sure item is visible
|
||
|
for (TreeItem *cur = item->getParent(); cur; cur = cur->getParent()) {
|
||
|
if (cur->isCollapsed()) cur->expand();
|
||
|
}
|
||
|
|
||
|
RECT r;
|
||
|
RECT c;
|
||
|
item->getCurRect(&r);
|
||
|
getClientRect(&c);
|
||
|
if (r.top < c.top || r.bottom > c.bottom) {
|
||
|
if (r.top + (c.bottom - c.top) <= getContentsHeight())
|
||
|
scrollToY(r.top);
|
||
|
else {
|
||
|
scrollToY(getContentsHeight()-(c.bottom-c.top));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void TreeWnd::setHilitedColor(const wchar_t *colorname) {
|
||
|
// we have to store it in a String because SkinColor does not make a copy
|
||
|
hilitedColorName = colorname;
|
||
|
hilitedColor = hilitedColorName;
|
||
|
}
|
||
|
|
||
|
ARGB32 TreeWnd::getHilitedColor() {
|
||
|
return hilitedColor;
|
||
|
}
|
||
|
|
||
|
int TreeWnd::compareItem(TreeItem *p1, TreeItem *p2)
|
||
|
{
|
||
|
int r = wcscmp(p1->getLabel(), p2->getLabel());
|
||
|
if (r == 0) return CMP3(p1, p2);
|
||
|
return r;
|
||
|
}
|
||
|
|
||
|
int TreeWnd::setFontSize(int newsize)
|
||
|
{
|
||
|
TREEWND_PARENT::setFontSize(newsize);
|
||
|
if (newsize >= 0) textsize = newsize;
|
||
|
TextInfoCanvas c(this);
|
||
|
Wasabi::FontInfo fontInfo;
|
||
|
fontInfo.pointSize = getFontSize();
|
||
|
itemHeight = c.getTextHeight(&fontInfo);
|
||
|
redraw = 1;
|
||
|
metrics_ok = 0;
|
||
|
invalidate();
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
int TreeWnd::getFontSize() {
|
||
|
#ifndef WASABINOMAINAPI
|
||
|
return textsize + api->metrics_getDelta();
|
||
|
#else
|
||
|
//MULTIAPI-FIXME: not handling delta
|
||
|
return textsize;
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
|
||
|
void TreeWnd::onSelectItem(TreeItem *i) {
|
||
|
Accessible *a = getAccessibleObject();
|
||
|
if (a != NULL)
|
||
|
a->onGetFocus(findItem(i));
|
||
|
}
|
||
|
|
||
|
void TreeWnd::onDeselectItem(TreeItem *i) {
|
||
|
}
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////////////////////////
|
||
|
// TreeItem
|
||
|
////////////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
TreeItem::TreeItem(const wchar_t *label) {
|
||
|
parent=NULL;
|
||
|
MEMZERO(&curRect, sizeof(RECT));
|
||
|
childTab = TAB_AUTO;
|
||
|
tree = NULL;
|
||
|
expandStatus = STATUS_COLLAPSED;
|
||
|
icon = NULL;
|
||
|
_z = 0;
|
||
|
|
||
|
if (label != NULL)
|
||
|
setLabel(label);
|
||
|
|
||
|
selected = FALSE;
|
||
|
hilitedDrop = FALSE;
|
||
|
hilited = FALSE;
|
||
|
being_edited = FALSE;
|
||
|
|
||
|
setSorted(TRUE);
|
||
|
}
|
||
|
|
||
|
TreeItem::~TreeItem() {
|
||
|
// the subitem will call parent tree which will remove item from our list
|
||
|
deleteSubitems();
|
||
|
|
||
|
// remove from parent tree
|
||
|
if (tree) tree->removeTreeItem(this);
|
||
|
|
||
|
delete icon;
|
||
|
}
|
||
|
|
||
|
void TreeItem::deleteSubitems() {
|
||
|
while (subitems.getNumItems() > 0) {
|
||
|
delete subitems.enumItem(0);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void TreeItem::setSorted(int issorted) {
|
||
|
subitems.setAutoSort(!!issorted);
|
||
|
}
|
||
|
|
||
|
void TreeItem::setChildTab(int haschildtab) {
|
||
|
childTab = haschildtab;
|
||
|
}
|
||
|
|
||
|
void TreeItem::linkTo(TreeItem *par) {
|
||
|
parent = par;
|
||
|
|
||
|
if (par == NULL) return;
|
||
|
|
||
|
par->addSubItem(this);
|
||
|
}
|
||
|
|
||
|
void TreeItem::addSubItem(TreeItem *item) {
|
||
|
subitems.addItem(item);
|
||
|
}
|
||
|
|
||
|
int TreeItem::removeSubitem(TreeItem *item) {
|
||
|
if (subitems.searchItem(item) == -1) return 0;
|
||
|
subitems.removeItem(item);
|
||
|
if (tree->redraw)
|
||
|
tree->invalidate();
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
TreeItem *TreeItem::getChild() {
|
||
|
return subitems.getFirst();
|
||
|
}
|
||
|
|
||
|
TreeItem *TreeItem::getChildSibling(TreeItem *item) { // locate item in children and return its sibling
|
||
|
for (int i=0;i<subitems.getNumItems();i++) {
|
||
|
if (subitems.enumItem(i) == item) {
|
||
|
if (i == subitems.getNumItems()-1) return NULL;
|
||
|
return subitems.enumItem(i+1);
|
||
|
}
|
||
|
}
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
TreeItem *TreeItem::getSibling() { // returns next item
|
||
|
if (!parent)
|
||
|
return tree->getSibling(this);
|
||
|
else
|
||
|
return parent->getChildSibling(this);
|
||
|
}
|
||
|
|
||
|
void TreeItem::setTree(TreeWnd *newtree) {
|
||
|
tree = newtree;
|
||
|
// recursively reset tree for children, if any
|
||
|
for (int i = 0; ; i++) {
|
||
|
TreeItem *item = getNthChild(i);
|
||
|
if (item == NULL) break;
|
||
|
item->setTree(tree);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void TreeItem::ensureVisible()
|
||
|
{
|
||
|
if (tree) tree->ensureItemVisible(this);
|
||
|
}
|
||
|
|
||
|
const wchar_t *TreeItem::getLabel()
|
||
|
{
|
||
|
return label;
|
||
|
}
|
||
|
|
||
|
void TreeItem::setLabel(const wchar_t *newlabel)
|
||
|
{
|
||
|
label = newlabel;
|
||
|
if (newlabel) {
|
||
|
if (tree)
|
||
|
tree->invalidateMetrics();
|
||
|
invalidate();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int TreeItem::customDraw(Canvas *canvas, const POINT &pt, int txtHeight, int indentation, const RECT &clientRect, const Wasabi::FontInfo *fontInfo)
|
||
|
{
|
||
|
if (being_edited) return 0;
|
||
|
|
||
|
SkinBitmap *icon = getIcon();
|
||
|
|
||
|
int cw = clientRect.right - clientRect.left;
|
||
|
|
||
|
int iconw = MIN(icon ? icon->getWidth() : 0, cw);
|
||
|
|
||
|
if (isSelected() || isHilitedDrop())
|
||
|
{
|
||
|
RECT r;
|
||
|
r.left = pt.x;
|
||
|
r.top = pt.y;
|
||
|
//r.right = r.left + canvas->getTextWidth(label)+2+(icon ? txtHeight : 0);
|
||
|
r.right = r.left + canvas->getTextWidth(label, fontInfo)+2+iconw;
|
||
|
r.bottom = r.top + txtHeight;
|
||
|
canvas->fillRect(&r, isHilitedDrop() ? drophilitecolor : selectedcolor);
|
||
|
}
|
||
|
|
||
|
if (isHilited())
|
||
|
{
|
||
|
RECT r;
|
||
|
r.left = pt.x;
|
||
|
r.top = pt.y;
|
||
|
//r.right = r.left + canvas->getTextWidth(label)+2+(icon ? txtHeight : 0);
|
||
|
r.right = r.left + canvas->getTextWidth(label, fontInfo)+2+iconw;
|
||
|
r.bottom = r.top + txtHeight;
|
||
|
canvas->drawRect(&r, 1, tree->getHilitedColor());
|
||
|
}
|
||
|
|
||
|
POINT d=pt;
|
||
|
|
||
|
if (icon) {
|
||
|
RECT i;
|
||
|
i.left = pt.x+1;
|
||
|
i.right = i.left + iconw;
|
||
|
// i.top = pt.y+1;
|
||
|
int lh = MIN(icon->getHeight(), txtHeight);
|
||
|
i.top = pt.y + (txtHeight - lh) / 2;
|
||
|
// i.bottom = i.top + txtHeight-2;
|
||
|
i.bottom = i.top + lh;
|
||
|
icon->stretchToRectAlpha(canvas, &i);
|
||
|
//d.x += txtHeight;
|
||
|
d.x += icon->getWidth();
|
||
|
}
|
||
|
|
||
|
canvas->textOut(d.x+1, d.y, label, fontInfo);
|
||
|
|
||
|
return canvas->getTextWidth(label, fontInfo)+2+iconw;
|
||
|
}
|
||
|
|
||
|
int TreeItem::getItemWidth(int txtHeight, int indentation) {
|
||
|
SkinBitmap *icon = getIcon();
|
||
|
if (!label) return (icon ? txtHeight : 0);
|
||
|
TextInfoCanvas c(tree);
|
||
|
Wasabi::FontInfo fontInfo;
|
||
|
fontInfo.pointSize = getTree()->getFontSize();
|
||
|
int width = c.getTextWidth(label, &fontInfo)+2;
|
||
|
width += (icon ? txtHeight : 0);
|
||
|
return width;
|
||
|
}
|
||
|
|
||
|
int TreeItem::getNumChildren() {
|
||
|
return subitems.getNumItems();
|
||
|
}
|
||
|
|
||
|
TreeItem *TreeItem::getNthChild(int nth) {
|
||
|
if (nth >= subitems.getNumItems()) return NULL;
|
||
|
return subitems.enumItem(nth);
|
||
|
}
|
||
|
|
||
|
void TreeItem::setCurRect(int x1, int y1, int x2, int y2, int z) {
|
||
|
curRect.left = x1;
|
||
|
curRect.right = x2;
|
||
|
curRect.top = y1;
|
||
|
curRect.bottom = y2;
|
||
|
_z = z;
|
||
|
}
|
||
|
|
||
|
int TreeItem::getIndent() {
|
||
|
return _z;
|
||
|
}
|
||
|
|
||
|
bool TreeItem::hasSubItems() {
|
||
|
return subitems.getNumItems()>0;
|
||
|
}
|
||
|
|
||
|
bool TreeItem::needTab() {
|
||
|
return (childTab == TAB_YES || (childTab == TAB_AUTO && hasSubItems()));
|
||
|
}
|
||
|
|
||
|
bool TreeItem::isExpanded() {
|
||
|
if (!hasSubItems()) return FALSE;
|
||
|
return expandStatus == STATUS_EXPANDED;
|
||
|
}
|
||
|
|
||
|
bool TreeItem::isCollapsed() {
|
||
|
if (!hasSubItems()) return TRUE;
|
||
|
return expandStatus == STATUS_COLLAPSED;
|
||
|
}
|
||
|
|
||
|
int TreeItem::getCurRect(RECT *r) {
|
||
|
r->left = curRect.left-tree->getScrollX();
|
||
|
r->top = curRect.top-tree->getScrollY();
|
||
|
r->right = curRect.right-tree->getScrollX();
|
||
|
r->bottom = curRect.bottom-tree->getScrollY();
|
||
|
RECT c;
|
||
|
tree->getClientRect(&c);
|
||
|
r->top += c.top;
|
||
|
r->bottom += c.top;
|
||
|
r->left += c.left;
|
||
|
r->right += c.left;
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
TreeWnd *TreeItem::getTree() const {
|
||
|
return tree;
|
||
|
}
|
||
|
|
||
|
void TreeItem::setSelected(bool isSelected, bool expandCollapse, bool editifselected) {
|
||
|
bool wasselected = selected;
|
||
|
selected = !!isSelected;
|
||
|
|
||
|
if (selected != wasselected) {
|
||
|
invalidate();
|
||
|
tree->repaint();
|
||
|
if (selected) {
|
||
|
onSelect();
|
||
|
ASSERT(tree != NULL);
|
||
|
tree->onItemSelected(this);
|
||
|
} else {
|
||
|
onDeselect();
|
||
|
ASSERT(tree != NULL);
|
||
|
tree->onItemDeselected(this);
|
||
|
}
|
||
|
} else {
|
||
|
if (selected && editifselected) {
|
||
|
editLabel();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (expandCollapse) {
|
||
|
if (isCollapsed())
|
||
|
expand();
|
||
|
else
|
||
|
collapse();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void TreeItem::invalidate() {
|
||
|
if (tree) {
|
||
|
RECT r;
|
||
|
getCurRect(&r);
|
||
|
tree->invalidateRect(&r);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool TreeItem::isSelected() {
|
||
|
return selected;
|
||
|
}
|
||
|
|
||
|
int TreeItem::collapse() {
|
||
|
int old = expandStatus;
|
||
|
|
||
|
if (hasSubItems()) {
|
||
|
if (expandStatus == STATUS_COLLAPSED)
|
||
|
return 0;
|
||
|
expandStatus = STATUS_COLLAPSED;
|
||
|
RECT c;
|
||
|
tree->getClientRect(&c);
|
||
|
RECT r;
|
||
|
getCurRect(&r);
|
||
|
r.bottom = c.bottom;
|
||
|
r.left = c.left;
|
||
|
r.right = c.right;
|
||
|
if (tree) {
|
||
|
tree->invalidateRect(&r);
|
||
|
tree->invalidateMetrics();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
onCollapse();
|
||
|
|
||
|
return old != expandStatus;
|
||
|
}
|
||
|
|
||
|
int TreeItem::expand() {
|
||
|
int old = expandStatus;
|
||
|
|
||
|
if (hasSubItems()) {
|
||
|
if (expandStatus == STATUS_EXPANDED)
|
||
|
return 0;
|
||
|
expandStatus = STATUS_EXPANDED;
|
||
|
RECT c;
|
||
|
tree->getClientRect(&c);
|
||
|
RECT r;
|
||
|
getCurRect(&r);
|
||
|
r.bottom = c.bottom;
|
||
|
r.left = c.left;
|
||
|
r.right = c.right;
|
||
|
if (tree) {
|
||
|
tree->invalidateRect(&r);
|
||
|
tree->invalidateMetrics();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
onExpand();
|
||
|
|
||
|
return old != expandStatus;
|
||
|
}
|
||
|
|
||
|
int TreeItem::onContextMenu(int x, int y) {
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
void TreeItem::setHilitedDrop(bool ishilitedDrop) {
|
||
|
bool washilighted = hilitedDrop;
|
||
|
hilitedDrop = !!ishilitedDrop;
|
||
|
|
||
|
if (washilighted != hilitedDrop)
|
||
|
invalidate();
|
||
|
}
|
||
|
|
||
|
void TreeItem::setHilited(bool ishilited) {
|
||
|
bool washilighted = hilited;
|
||
|
hilited = !!ishilited;
|
||
|
|
||
|
if (washilighted != hilited)
|
||
|
invalidate();
|
||
|
}
|
||
|
|
||
|
TreeItem *TreeItem::getParent() {
|
||
|
return parent;
|
||
|
}
|
||
|
|
||
|
bool TreeItem::isHilitedDrop() {
|
||
|
return hilitedDrop;
|
||
|
}
|
||
|
|
||
|
bool TreeItem::isHilited() {
|
||
|
return hilited;
|
||
|
}
|
||
|
|
||
|
void TreeItem::setIcon(SkinBitmap *newicon) {
|
||
|
if (icon) {
|
||
|
delete icon;
|
||
|
icon = NULL;
|
||
|
}
|
||
|
icon = newicon;
|
||
|
invalidate();
|
||
|
}
|
||
|
|
||
|
SkinBitmap *TreeItem::getIcon() {
|
||
|
return icon;
|
||
|
}
|
||
|
|
||
|
void TreeItem::sortItems() {
|
||
|
subitems.sort();
|
||
|
}
|
||
|
|
||
|
void TreeItem::editLabel() {
|
||
|
if (!tree) return;
|
||
|
tree->editItemLabel(this);
|
||
|
}
|
||
|
|
||
|
int TreeItem::onBeginLabelEdit() {
|
||
|
return 1; // disable editing by default
|
||
|
}
|
||
|
|
||
|
int TreeItem::onEndLabelEdit(const wchar_t *newlabel)
|
||
|
{
|
||
|
return 1; // accept new label by default
|
||
|
}
|
||
|
|
||
|
void TreeItem::setEdition(bool isedited) {
|
||
|
being_edited = !!isedited;
|
||
|
invalidate();
|
||
|
}
|
||
|
|
||
|
bool TreeItem::getEdition() {
|
||
|
return being_edited;
|
||
|
}
|
||
|
|
||
|
bool TreeItem::isSorted()
|
||
|
{
|
||
|
return subitems.getAutoSort();
|
||
|
}
|
||
|
|