#include "CtrlLib.h" NAMESPACE_UPP TreeCtrl::Node::Node() { Init(); } TreeCtrl::Node::Node(const Image& img, const Value& v) { Init(); image = img; key = value = v; } TreeCtrl::Node::Node(const Image& img, const Value& v, const Value& t) { Init(); image = img; key = v; value = t; } TreeCtrl::Node::Node(const Value& v) { Init(); key = value = v; } TreeCtrl::Node::Node(const Value& v, const Value& t) { Init(); key = v; value = t; } TreeCtrl::Node::Node(Ctrl& ctrl) { Init(); SetCtrl(ctrl); } TreeCtrl::Node::Node(const Image& img, Ctrl& ctrl, int cx, int cy) { Init(); SetCtrl(ctrl); image = img; size = ctrl.GetMinSize(); if(cx > 0) size.cx = cx; if(cy > 0) size.cy = cy; } TreeCtrl::TreeCtrl() { levelcx = 16; nocursor = false; noroot = false; dirty = true; isselection = false; multiselect = false; nobg = false; Clear(); SetFrame(ViewFrame()); AddFrame(sb); sb.WhenScroll = THISBACK(Scroll); } TreeCtrl::~TreeCtrl() {} void TreeCtrl::Layout() { sb.SetPage(sb.GetReducedViewSize()); sb.SetLine(item[0].GetSize().cy); } Size TreeCtrl::Item::GetValueSize() const { if(IsNull(size)) if(ctrl) return ctrl->GetMinSize(); else return display ? display->GetStdSize(value) : StdDisplay().GetStdSize(value); else return size; } Size TreeCtrl::Item::GetSize() const { Size sz = GetValueSize(); sz += Size(2 * margin, 2 * margin); Size isz = image.GetSize(); sz.cx += isz.cx; sz.cy = max(sz.cy, isz.cy); return sz; } void TreeCtrl::SetRoot(const TreeCtrl::Node& n) { (TreeCtrl::Node &)item[0] = n; RefreshItem(0); } void TreeCtrl::SetRoot(const Image& img, Value v) { SetRoot(TreeCtrl::Node(img, v).CanOpen()); } void TreeCtrl::SetRoot(const Image& img, Value v, Value t) { SetRoot(TreeCtrl::Node(img, v, t).CanOpen()); } void TreeCtrl::SetRoot(const Image& img, Ctrl& ctrl, int cx, int cy) { SetRoot(TreeCtrl::Node(img, ctrl, cx, cy).CanOpen()); } int TreeCtrl::Insert(int parentid, int i, const TreeCtrl::Node& n) { int id; if(freelist >= 0) { id = freelist; freelist = item[id].freelink; } else { id = item.GetCount(); item.Add(); } Item& m = item[id]; m.parent = parentid; (TreeCtrl::Node&)m = n; if(parentid >= 0) { Item& parent = item[parentid]; parent.child.Insert(i, id); } dirty = true; Dirty(parentid); return id; } int TreeCtrl::Add(int parentid, const TreeCtrl::Node& n) { return Insert(parentid, item[parentid].child.GetCount(), n); } int TreeCtrl::Insert(int parentid, int i) { return Insert(parentid, i, TreeCtrl::Node()); } int TreeCtrl::Add(int parentid) { return Add(parentid, TreeCtrl::Node()); } int TreeCtrl::Insert(int parentid, int i, const Image& img, Value v, bool withopen) { return Insert(parentid, i, TreeCtrl::Node(img, v).CanOpen(withopen)); } int TreeCtrl::Insert(int parentid, int i, const Image& img, Value v, Value t, bool withopen) { return Insert(parentid, i, TreeCtrl::Node(img, v, t).CanOpen(withopen)); } int TreeCtrl::Add(int parentid, const Image& img, Value v, bool withopen) { return Add(parentid, TreeCtrl::Node(img, v).CanOpen(withopen)); } int TreeCtrl::Add(int parentid, const Image& img, Value v, Value t, bool withopen) { return Add(parentid, TreeCtrl::Node(img, v, t).CanOpen(withopen)); } int TreeCtrl::Insert(int parentid, int i, const Image& img, Ctrl& ctrl, int cx, int cy, bool withopen) { return Insert(parentid, i, TreeCtrl::Node(img, ctrl, cx, cy).CanOpen(withopen)); } int TreeCtrl::Add(int parentid, const Image& img, Ctrl& ctrl, int cx, int cy, bool withopen) { return Add(parentid, TreeCtrl::Node(img, ctrl, cx, cy).CanOpen(withopen)); } Value TreeCtrl::Get(int id) const { const Item& m = item[id]; return m.ctrl ? m.ctrl->GetData() : m.key; } Value TreeCtrl::GetValue(int id) const { const Item& m = item[id]; return m.ctrl ? m.ctrl->GetData() : m.value; } void TreeCtrl::Set(int id, Value v) { Item& m = item[id]; if(m.ctrl) m.ctrl->SetData(v); else { m.value = m.key = v; RefreshItem(id); } SetOption(id); } void TreeCtrl::Set(int id, Value k, Value v) { Item& m = item[id]; if(m.ctrl) m.ctrl->SetData(v); else { m.key = k; m.value = v; RefreshItem(id); } SetOption(id); } void TreeCtrl::SetNode(int id, const TreeCtrl::Node& n) { (TreeCtrl::Node&)item[id] = n; Dirty(id); SetOption(id); } void TreeCtrl::RemoveChildren(int id) { Item& m = item[id]; for(int i = 0; i < m.child.GetCount(); i++) RemoveSubtree(m.child[i]); m.child.Clear(); Dirty(id); } void TreeCtrl::RemoveSubtree(int id) { Item& m = item[id]; if(m.linei == cursor) cursor = item[m.parent].linei; m.value = Null; m.image = Null; RemoveChildren(id); m.freelink = freelist; freelist = id; } void TreeCtrl::Remove(int id) { ASSERT(id > 0); int pi = item[id].parent; Item& parent = item[item[id].parent]; parent.child.Remove(FindIndex(parent.child, id)); RemoveSubtree(id); Dirty(pi); } void TreeCtrl::Clear() { item.Clear(); item.Add(); item[0].linei = -1; item[0].parent = -1; freelist = -1; Dirty(); cursor = -1; } void TreeCtrl::RemoveCtrls(int itemi) { Item& m = item[itemi]; if(m.ctrl) m.ctrl->Remove(); for(int i = 0; i < m.child.GetCount(); i++) RemoveCtrls(m.child[i]); } void TreeCtrl::ReLine(int itemi, int level, Size& sz) { int ii = line.GetCount(); Line& l = line.Add(); l.level = level; l.itemi = itemi; l.y = sz.cy; l.ll = -1; Item& m = item[itemi]; if(m.ctrl) { hasctrls = true; m.ctrl->Remove(); } m.linei = ii; Size msz = m.GetSize(); sz.cy += msz.cy; sz.cx = max(sz.cx, level * levelcx + msz.cx); level++; for(int i = 0; i < m.child.GetCount(); i++) if(m.isopen) { line[ii].ll = line.GetCount(); ReLine(m.child[i], level, sz); } else RemoveCtrls(m.child[i]); } void TreeCtrl::SyncTree() { if(!dirty) return; if(noroot) Open(0); Ptr restorefocus = GetFocusChildDeep(); hasctrls = false; int cursorid = GetCursor(); for(int i = 0; i < item.GetCount(); i++) item[i].linei = -1; line.Clear(); Size treesize = Size(0, 0); if(noroot) { if(GetChildCount(0)) treesize.cy = -item[0].GetSize().cy; ReLine(0, -1, treesize); } else ReLine(0, 0, treesize); treesize.cy = max(0, treesize.cy); treesize.cx += levelcx; sb.SetTotal(treesize); cursor = -1; dirty = false; if(cursorid >= 0) SetCursor(cursorid, false); SyncCtrls(true, restorefocus); } void TreeCtrl::SyncCtrls(bool add, Ctrl *restorefocus) { if(!hasctrls) return; Point org = sb; for(int i = noroot; i < line.GetCount(); i++) { const Line& l = line[i]; Item& m = item[l.itemi]; if(m.ctrl) { if(add) AddChildBefore(m.ctrl, GetLastChild()); if(m.ctrl == restorefocus || m.ctrl->HasChildDeep(restorefocus)) restorefocus->SetFocus(); Size msz = m.GetSize(); Size isz = m.image.GetSize(); Size vsz = m.GetValueSize(); m.ctrl->SetRect(levelcx + l.level * levelcx + isz.cx + m.margin - org.x, l.y + (msz.cy - vsz.cy) / 2 - org.y, vsz.cx, vsz.cy); } } } bool TreeCtrl::IsOpen(int id) const { return item[id].isopen; } void TreeCtrl::Dirty(int id) { ClearSelection(); Size sz = GetSize(); dirty = true; while(id >= 0) { int q = item[id].linei; if(q >= 0) { int y = line[q].y - sb.GetY(); Refresh(0, y, sz.cx, sz.cy - y); return; } id = item[id].parent; } Refresh(); } void TreeCtrl::Open(int id, bool open) { Item& m = item[id]; if(m.isopen != open) { m.isopen = open; int q = GetCursor(); while(q >= 0) { if(q == id) { SetCursor(id); break; } q = GetParent(q); } Dirty(id); if(open) WhenOpen(id); else WhenClose(id); } } void TreeCtrl::OpenDeep(int id, bool open) { Open(id); Item& m = item[id]; for(int i = 0; i < m.child.GetCount(); i++) OpenDeep(m.child[i], open); } void TreeCtrl::MakeVisible(int id) { id = GetParent(id); while(id >= 0) { Open(id); id = GetParent(id); } } int TreeCtrl::FindLine(int y) const { int l = FindUpperBound(line, y, LineLess()); return l > 0 ? l - 1 : 0; } void TreeCtrl::RefreshLine(int i) { SyncTree(); if(i >= 0) { Size sz = GetSize(); int y = line[i].y - sb.GetY(); Refresh(0, y, sz.cx, item[line[i].itemi].GetSize().cy); } } void TreeCtrl::RefreshItem(int id) { SyncTree(); RefreshLine(item[id].linei); } int TreeCtrl::GetItemAtLine(int i) { SyncTree(); return line[i].itemi; } int TreeCtrl::GetLineAtItem(int id) { SyncTree(); return item[id].linei; } int TreeCtrl::GetLineCount() { SyncTree(); return line.GetCount(); } void TreeCtrl::SetCursorLine(int i, bool sc) { if(nocursor) return; if(i != cursor) { i = minmax(i, 0, line.GetCount() - 1); if(i < 0) return; Item& m = item[line[i].itemi]; if(sc) sb.ScrollIntoY(line[i].y, m.GetSize().cy); RefreshLine(cursor); cursor = i; RefreshLine(cursor); if(m.ctrl && m.ctrl->SetWantFocus()) return; WhenCursor(); WhenSel(); } } void TreeCtrl::SetCursorLine(int i) { SetCursorLine(i, true); } void TreeCtrl::SetCursorLineSync(int i) { if(nocursor) return; if(i != cursor) { if(cursor < 0) Refresh(); RefreshLine(cursor); if(i < 0) cursor = -1; else cursor = minmax(i, (int)noroot, line.GetCount() - 1); RefreshLine(cursor); Item& m = item[line[cursor].itemi]; if(cursor < 0) Refresh(); else { Sync(); sb.ScrollIntoY(line[cursor].y, m.GetSize().cy); } if(!(m.ctrl && m.ctrl->SetWantFocus())) SetFocus(); WhenCursor(); WhenSel(); } } void TreeCtrl::KillCursor() { RefreshLine(cursor); cursor = -1; Refresh(); WhenCursor(); WhenSel(); } void TreeCtrl::SetCursor(int id, bool sc) { while(id > 0) { ASSERT(id >= 0 && id < item.GetCount()); MakeVisible(id); SyncTree(); const Item& m = item[id]; if(m.linei >= 0) { SetCursorLine(m.linei, sc); return; } id = m.parent; } SetCursorLine(0, sc); } void TreeCtrl::SetCursor(int id) { SetCursor(id, true); } int TreeCtrl::GetCursor() const { return cursor >= 0 ? line[cursor].itemi : -1; } Value TreeCtrl::Get() const { return IsCursor() ? Get(GetCursor()) : Value(); } Value TreeCtrl::GetValue() const { return IsCursor() ? GetValue(GetCursor()) : Value(); } int TreeCtrl::Find(Value key) { for(int i = 0; i < item.GetCount(); i++) if(Get(i) == key) return i; return -1; } bool TreeCtrl::FindSetCursor(Value key) { int q = Find(key); if(q < 0) return false; SetCursor(q); return true; } void TreeCtrl::ShiftSelect(int l1, int l2) { if(!multiselect) return; bool b = false; if(l1 > l2) Swap(l1, l2); for(int i = l1; i <= l2; i++) SelectOne(line[i].itemi, true); UpdateSelect(); } void TreeCtrl::LeftDown(Point p, dword flags) { Point org = sb; if(p.y + org.y > sb.GetTotal().cy) return; int i = FindLine(p.y + org.y); const Line& l = line[i]; int x = levelcx + l.level * levelcx - org.x - (levelcx >> 1) - org.x; if(p.x > x - 6 && p.x < x + 6) Open(l.itemi, !IsOpen(l.itemi)); else { SetFocus(); int q = cursor; SetCursorLine(i); if(cursor != q) WhenAction(); if(multiselect) { if(flags & K_CTRL) { int id = GetCursor(); SelectOne(id, !IsSelected(id)); } else { ClearSelection(); if(flags & K_SHIFT) { ShiftSelect(q, cursor); cursor = q; } } } } } void TreeCtrl::LeftDouble(Point p, dword flags) { Point org = sb; if(p.y + org.y > sb.GetTotal().cy) return; if(FindLine(p.y + org.y) == GetCursorLine()) WhenLeftDouble(); } void TreeCtrl::RightDown(Point p, dword flags) { Point org = sb; if(p.y + org.y < sb.GetTotal().cy) { int i = FindLine(p.y + org.y); if(i >= 0) { SetFocus(); SetCursorLine(i); } } if(WhenBar) MenuBar::Execute(WhenBar); } void TreeCtrl::Paint(Draw& w) { SyncTree(); Size sz = GetSize(); Point org = sb; scroller.Set(org); if(!nobg) w.DrawRect(sz, SColorPaper); int levelcx2 = levelcx >> 1; for(int i = 0; i < line.GetCount(); i++) { Line& l = line[i]; if(l.ll >= 0) { int yl = line[i].y + item[l.itemi].GetSize().cy - org.y; int yh = line[l.ll].y + item[line[l.ll].itemi].GetSize().cy / 2 - org.y; if(yh >= 0 && yl < sz.cy) { int x = levelcx + levelcx * l.level + levelcx2 - org.x; w.DrawRect(x, yl, 1, yh - yl, SColorShadow); } } } for(int i = FindLine(org.y); i < line.GetCount(); i++) { Line& l = line[i]; const Item& m = item[l.itemi]; const Display *d = m.display; if(!d) d = &StdDisplay(); Size msz = m.GetSize(); Size isz = m.image.GetSize(); Size vsz = m.GetValueSize(); int y = l.y - org.y; if(y > sz.cy) break; if(w.IsPainting(0, y, sz.cx, msz.cy)) { int x = 0; x = levelcx + l.level * levelcx - org.x; Point op = Point(x - levelcx2, y + msz.cy / 2); w.DrawRect(op.x, op.y, levelcx2, 1, SColorShadow); if(m.canopen || m.child.GetCount()) { Image im = m.isopen ? CtrlImg::treeminus() : CtrlImg::treeplus(); op -= im.GetSize() / 2; w.DrawImage(op.x, op.y, im); } w.DrawImage(x, y + (msz.cy - isz.cy) / 2, m.image); x += isz.cx; dword st = 0; Color fg = SColorText; Color bg = SColorPaper; if(nobg) bg = Null; bool hasfocus = HasFocus(); if(IsReadOnly()) st |= Display::READONLY; if(m.sel) { st |= Display::SELECT; fg = hasfocus ? SColorHighlightText : SColorText; bg = hasfocus ? SColorHighlight : SColorFace; } if(i == cursor && !nocursor) { st |= Display::CURSOR; fg = hasfocus ? SColorHighlightText : SColorText; bg = hasfocus ? (isselection ? Blend(SColorLight, SColorHighlight) : SColorHighlight) : SColorFace; } if(hasfocus) st |= Display::FOCUS; if(!(m.ctrl && m.ctrl->IsWantFocus())) { w.DrawRect(x, y, vsz.cx + 2 * m.margin, msz.cy, bg); d->Paint(w, RectC(x + m.margin, y + (msz.cy - vsz.cy) / 2, vsz.cx, vsz.cy), m.value, fg, bg, st); } } } } void TreeCtrl::Scroll() { SyncTree(); if(hasctrls) { Refresh(); SyncCtrls(false, NULL); } else scroller.Scroll(*this, sb); } void TreeCtrl::MouseWheel(Point, int zdelta, dword) { sb.WheelY(zdelta); } void TreeCtrl::ChildGotFocus() { for(int i = 0; i < line.GetCount(); i++) { Item& m = item[line[i].itemi]; if(m.ctrl && m.ctrl->HasFocusDeep()) { SetCursorLine(i); return; } } } bool TreeCtrl::Tab(int d) { if(cursor < 0) return false; Item& m = item[line[cursor].itemi]; if(m.ctrl && m.ctrl->HasFocusDeep()) return false; int i = cursor; for(int i = cursor + d; i >= 0 && i < line.GetCount(); i += d) { Item& m = item[line[i].itemi]; if(m.ctrl && m.ctrl->SetWantFocus()) return true; } return false; } bool TreeCtrl::Key(dword key, int) { SyncTree(); Size sz = GetSize(); int cid = GetCursor(); bool shift = key & K_SHIFT; key &= ~K_SHIFT; switch(key) { case K_TAB: return Tab(1); case K_SHIFT_TAB: return Tab(-1); case K_UP: SetCursorLineSync(cursor > 0 ? cursor - 1 : 0); break; case K_DOWN: SetCursorLineSync(cursor >= 0 ? cursor + 1 : line.GetCount() - 1); break; case K_PAGEDOWN: SetCursorLineSync(cursor >= 0 ? FindLine(line[cursor].y + sz.cy) : line.GetCount() - 1); break; case K_PAGEUP: SetCursorLineSync(cursor >= 0 ? FindLine(line[cursor].y - sz.cy) : 0); break; case K_LEFT: if(cid >= 0) Close(cid); break; case K_RIGHT: if(cid >= 0) Open(cid); break; default: return false; } if(cid != cursor) if(shift && multiselect) ShiftSelect(cid, cursor); else ClearSelection(); if(cursor != cid) WhenAction(); return true; } void TreeCtrl::GotFocus() { if(dirty) return; if(hasctrls && cursor >= 0 && item[line[cursor].itemi].ctrl) for(int i = 0; i < line.GetCount(); i++) { Item& m = item[line[i].itemi]; if(m.ctrl && m.ctrl->SetWantFocus()) break; } else RefreshLine(cursor); } void TreeCtrl::LostFocus() { if(dirty) return; RefreshLine(cursor); } void TreeCtrl::ChildRemoved(Ctrl *) { Dirty(); } struct TreeCtrl::SortOrder { TreeCtrl *tree; const ValueOrder *order; bool byvalue; bool operator()(int a, int b) const { return byvalue ? (*order)(tree->GetValue(a), tree->GetValue(b)) : (*order)(tree->Get(a), tree->Get(b)); } }; void TreeCtrl::Sort0(int id, const ValueOrder& order, bool byvalue) { SortOrder so; so.tree = this; so.order = ℴ so.byvalue = byvalue; UPP::Sort(item[id].child, so); } void TreeCtrl::Sort(int id, const ValueOrder& order, bool byvalue) { SyncTree(); Sort0(id, order, byvalue); Dirty(id); } void TreeCtrl::SortDeep0(int id, const ValueOrder& order, bool byvalue) { Sort0(id, order, byvalue); Item& m = item[id]; for(int i = 0; i < m.child.GetCount(); i++) SortDeep0(m.child[i], order, byvalue); } void TreeCtrl::SortDeep(int id, const ValueOrder& order, bool byvalue) { SyncTree(); SortDeep0(id, order, byvalue); Dirty(id); } void TreeCtrl::Sort(int id, int (*compare)(const Value& v1, const Value& v2), bool byvalue) { Sort(id, FnValueOrder(compare), byvalue); } void TreeCtrl::SortDeep(int id, int (*compare)(const Value& v1, const Value& v2), bool byvalue) { SortDeep(id, FnValueOrder(compare), byvalue); } void TreeCtrl::SortByValue(int id, const ValueOrder& order) { Sort(id, order, true); } void TreeCtrl::SortDeepByValue(int id, const ValueOrder& order) { SortDeep(id, order, true); } void TreeCtrl::SortByValue(int id, int (*compare)(const Value& v1, const Value& v2)) { Sort(id, compare, true); } void TreeCtrl::SortDeepByValue(int id, int (*compare)(const Value& v1, const Value& v2)) { SortDeep(id, compare, true); } void TreeCtrl::SetData(const Value& data) { FindSetCursor(data); } Value TreeCtrl::GetData() const { return Get(); } Point TreeCtrl::GetScroll() const { return sb; } void TreeCtrl::ScrollTo(Point sc) { sb = sc; } void TreeCtrl::ClearSelTree(int id) { Item& m = item[id]; m.sel = false; for(int i = 0; i < m.child.GetCount(); i++) ClearSelTree(m.child[i]); } void TreeCtrl::ClearSelection() { if(isselection) { ClearSelTree(0); isselection = false; WhenSelection(); WhenSel(); WhenAction(); Refresh(); } } bool TreeCtrl::UpdateSelTree(int id) { Item& m = item[id]; if(m.sel) return true; for(int i = 0; i < m.child.GetCount(); i++) if(UpdateSelTree(m.child[i])) return true; return false; } void TreeCtrl::UpdateSelect() { isselection = UpdateSelTree(0); WhenSelection(); WhenSel(); WhenAction(); } void TreeCtrl::SelectOne(int id, bool sel) { if(!multiselect) { ClearSelection(); return; } if(item[id].canselect) item[id].sel = sel; UpdateSelect(); RefreshItem(id); } bool TreeCtrl::IsSel(int id) const { return IsSelection() ? IsSelected(id) : GetCursor() == id; } void TreeCtrl::SetOption(int id) { } void OptionTree::SetRoot(const Image& img, Option& opt, const char *text) { if(text) opt.SetLabel(text); TreeCtrl::SetRoot(img, opt); opt.NoNotNull().BlackEdge(); option.At(0) = &opt; opt <<= THISBACK1(SetOption, 0); } void OptionTree::Clear() { TreeCtrl::Clear(); aux.Clear(); option.Clear(); aux.Add(); } void OptionTree::SetRoot(Option& option, const char *text) { SetRoot(Null, option, text); } void OptionTree::SetRoot(const Image& img, const char *text) { SetRoot(img, aux[0], text); } void OptionTree::SetRoot(const char *text) { SetRoot(Null, aux[0], text); } int OptionTree::Add(int parentid, const Image& img, Option& opt, const char *text) { if(text) opt.SetLabel(text); int id = TreeCtrl::Add(parentid, img, opt); option.At(id, NULL) = &opt; opt.NoNotNull().BlackEdge(); opt <<= THISBACK1(SetOption, id); SetOption(id); return id; } int OptionTree::Add(int parentid, Option& opt, const char *text) { return Add(parentid, Null, opt.NoNotNull(), text); } int OptionTree::Add(int parentid, const Image& img, const char *text) { return Add(parentid, img, aux.Add().NoNotNull(), text); } int OptionTree::Add(int parentid, const char *text) { return Add(parentid, aux.Add().NoNotNull(), text); } void OptionTree::SetChildren(int id, bool b) { for(int i = 0; i < GetChildCount(id); i++) { int chid = GetChild(id, i); Option *opt = option[chid]; if(opt) opt->Set(b); SetChildren(chid, b); } } void OptionTree::SetOption(int id) { Option *opt = option[id]; ASSERT(opt); SetChildren(id, opt->Get()); for(;;) { id = GetParent(id); if(id < 0) break; bool t = false; bool f = false; bool n = false; for(int i = 0; i < GetChildCount(id); i++) { int chid = GetChild(id, i); Option *opt = option[chid]; if(opt) { int x = opt->Get(); if(x == 1) t = true; else if(x == 0) f = true; else n = true; } } opt = option[id]; if(opt) { if(t && f || n) { opt->Set(Null); } else opt->Set(t); } } WhenOption(); } OptionTree::OptionTree() { aux.Add(); } OptionTree::~OptionTree() {} END_UPP_NAMESPACE