winamp/Src/Wasabi/api/wnd/wndclass/treewnd.cpp
2024-09-24 14:54:57 +02:00

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();
}