diff options
Diffstat (limited to 'Src/Wasabi/api/wnd/wndclass/treewnd.cpp')
-rw-r--r-- | Src/Wasabi/api/wnd/wndclass/treewnd.cpp | 1645 |
1 files changed, 1645 insertions, 0 deletions
diff --git a/Src/Wasabi/api/wnd/wndclass/treewnd.cpp b/Src/Wasabi/api/wnd/wndclass/treewnd.cpp new file mode 100644 index 00000000..99b03c85 --- /dev/null +++ b/Src/Wasabi/api/wnd/wndclass/treewnd.cpp @@ -0,0 +1,1645 @@ +#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(); +} + |