mirror of
https://github.com/ultimatepp/ultimatepp.git
synced 2026-05-15 06:05:58 -06:00
1908 lines
39 KiB
C++
1908 lines
39 KiB
C++
#include "CtrlLib.h"
|
|
|
|
namespace Upp {
|
|
|
|
TreeCtrl::TreeCtrl()
|
|
{
|
|
display = &StdDisplay();
|
|
levelcx = DPI(16);
|
|
nocursor = false;
|
|
noroot = false;
|
|
dirty = true;
|
|
multiselect = false;
|
|
nobg = false;
|
|
popupex = true;
|
|
mousemove = false;
|
|
accel = false;
|
|
treesize = Size(0, 0);
|
|
multiroot = false;
|
|
chldlck = false;
|
|
highlight_ctrl = false;
|
|
hasctrls = false;
|
|
Clear();
|
|
SetFrame(ViewFrame());
|
|
AddFrame(sb);
|
|
sb.WhenScroll = THISBACK(Scroll);
|
|
sb.Box(sb_box);
|
|
WhenLeftDouble = THISBACK(StdLeftDouble);
|
|
}
|
|
|
|
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 = Null;
|
|
if(cx > 0)
|
|
size.cx = cx;
|
|
if(cy > 0)
|
|
size.cy = cy;
|
|
}
|
|
|
|
void TreeCtrl::StdLeftDouble()
|
|
{
|
|
if(IsCursor())
|
|
Open(GetCursor(), !IsOpen(GetCursor()));
|
|
}
|
|
|
|
void TreeCtrl::CancelMode()
|
|
{
|
|
selclick = false;
|
|
dropinsert = 0;
|
|
dropitem = -1;
|
|
repoint = Null;
|
|
}
|
|
|
|
TreeCtrl::~TreeCtrl()
|
|
{
|
|
Shutdown();
|
|
}
|
|
|
|
void TreeCtrl::Layout()
|
|
{
|
|
Size full = sb.GetViewSize();
|
|
Size red = sb.GetReducedViewSize();
|
|
Size total = sb.GetTotal();
|
|
if(total.cx > full.cx) full.cy = red.cy;
|
|
if(total.cy > full.cy) full.cx = red.cx;
|
|
if(total.cx > full.cx) full.cy = red.cy;
|
|
sb.SetPage(full);
|
|
sb.SetLine(item[0].GetSize(display).cy);
|
|
SyncCtrls(false, NULL);
|
|
}
|
|
|
|
// 2008-04-08 mrjt
|
|
Size TreeCtrl::Item::GetValueSize(const Display *treedisplay) const
|
|
{
|
|
return display ? display->GetStdSize(value) : treedisplay->GetStdSize(value);
|
|
}
|
|
|
|
Size TreeCtrl::Item::GetCtrlSize() const
|
|
{
|
|
if(!ctrl) return Size(0, 0);
|
|
Size csz = ctrl->GetMinSize();
|
|
return Size(Nvl(size.cx, csz.cx), Nvl(size.cy, csz.cy));
|
|
}
|
|
|
|
Size TreeCtrl::Item::GetSize(const Display *treedisplay) const
|
|
{
|
|
Size sz = GetValueSize(treedisplay);
|
|
Size csz = GetCtrlSize();
|
|
Size isz = image.GetSize();
|
|
sz.cx += isz.cx;
|
|
sz.cy = max(sz.cy, isz.cy);
|
|
sz.cx += csz.cx;
|
|
sz.cy = max(sz.cy, csz.cy);
|
|
sz.cy = max(sz.cy, CtrlImg::treeminus().GetHeight(), CtrlImg::treeplus().GetHeight());
|
|
sz += Size(2 * margin, 2 * margin);
|
|
return sz;
|
|
}
|
|
|
|
void TreeCtrl::SetRoot(const TreeCtrl::Node& n)
|
|
{
|
|
KillEdit();
|
|
(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)
|
|
{
|
|
KillEdit();
|
|
int id;
|
|
if(freelist >= 0) {
|
|
id = freelist;
|
|
freelist = item[id].freelink;
|
|
}
|
|
else {
|
|
id = item.GetCount();
|
|
item.Add();
|
|
}
|
|
Item& m = item[id];
|
|
m.free = false;
|
|
m.isopen = false;
|
|
m.parent = parentid;
|
|
(TreeCtrl::Node&)m = n;
|
|
if(parentid >= 0) {
|
|
Item& parent = item[parentid];
|
|
parent.child.Insert(i, id);
|
|
}
|
|
dirty = true;
|
|
if(m.ctrl) // Done during SyncTree too, however we need widget to be child (and Open) until then
|
|
AddChild(m.ctrl);
|
|
Dirty(parentid);
|
|
return id;
|
|
}
|
|
|
|
TreeCtrl& TreeCtrl::SetDisplay(const Display& d)
|
|
{
|
|
display = &d;
|
|
Dirty();
|
|
return *this;
|
|
}
|
|
|
|
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::Insert(int parentid, int i, const Image& img, Value key, const String& value, bool withopen)
|
|
{
|
|
return Insert(parentid, i, img, key, (Value)value, withopen);
|
|
}
|
|
|
|
int TreeCtrl::Insert(int parentid, int i, const Image& img, Value key, const char *value, bool withopen)
|
|
{
|
|
return Insert(parentid, i, img, key, (Value)value, 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::Add(int parentid, const Image& img, Value key, const String& value, bool withopen)
|
|
{
|
|
return Add(parentid, img, key, (Value)value, withopen);
|
|
}
|
|
|
|
int TreeCtrl::Add(int parentid, const Image& img, Value key, const char *value, bool withopen)
|
|
{
|
|
return Add(parentid, img, key, (Value)value, 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::SetValue(const Value& v)
|
|
{
|
|
int id = GetCursor();
|
|
Set(id, Get(id), v);
|
|
}
|
|
|
|
void TreeCtrl::SetDisplay(int id, const Display& display)
|
|
{
|
|
item[id].SetDisplay(display);
|
|
Dirty(id);
|
|
}
|
|
|
|
const Display& TreeCtrl::GetDisplay(int id) const
|
|
{
|
|
if(id >= 0 && id < item.GetCount() && item[id].display)
|
|
return *item[id].display;
|
|
return *display;
|
|
}
|
|
|
|
bool TreeCtrl::IsValid(int id) const
|
|
{
|
|
return id >= 0 && id < item.GetCount() && !item[id].free;
|
|
}
|
|
|
|
void TreeCtrl::SetNode(int id, const TreeCtrl::Node& n)
|
|
{
|
|
TreeCtrl::Node& m = item[id];
|
|
m = n;
|
|
Dirty(id);
|
|
SetOption(id);
|
|
if(m.ctrl)
|
|
AddChildBefore(m.ctrl, GetLastChild());
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
int TreeCtrl::GetChildIndex(int id, int childid) const
|
|
{
|
|
for(int i = 0; i < GetChildCount(id); i++)
|
|
if(GetChild(id, i) == childid)
|
|
return i;
|
|
return -1;
|
|
}
|
|
|
|
void TreeCtrl::RemoveSubtree(int id)
|
|
{
|
|
Item& m = item[id];
|
|
if(m.sel)
|
|
selectcount--;
|
|
if(m.linei == cursor)
|
|
cursor = item[m.parent].linei;
|
|
if(m.ctrl)
|
|
m.ctrl->Remove();
|
|
m.key = Null;
|
|
m.value = Null;
|
|
m.image = Null;
|
|
RemoveChildren(id);
|
|
m.freelink = freelist;
|
|
freelist = id;
|
|
m.free = true;
|
|
}
|
|
|
|
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::Remove(const Vector<int>& id)
|
|
{
|
|
for(int i = 0; i < id.GetCount(); i++)
|
|
if(!item[id[i]].free)
|
|
Remove(id[i]);
|
|
}
|
|
|
|
void TreeCtrl::Clear()
|
|
{
|
|
for(int i = 0; i < item.GetCount(); i++)
|
|
if(item[i].ctrl)
|
|
item[i].ctrl->Remove();
|
|
item.Clear();
|
|
item.Add();
|
|
item[0].linei = -1;
|
|
item[0].parent = -1;
|
|
item[0].canopen = true;
|
|
freelist = -1;
|
|
selectcount = 0;
|
|
Dirty();
|
|
cursor = anchor = -1;
|
|
sb.Set(0, 0);
|
|
}
|
|
|
|
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.linei = ii;
|
|
Size msz = m.GetSize(display);
|
|
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 {
|
|
chldlck = true;
|
|
RemoveCtrls(m.child[i]);
|
|
chldlck = false;
|
|
}
|
|
}
|
|
|
|
void TreeCtrl::SyncAfterSync(Ptr<Ctrl> restorefocus)
|
|
{
|
|
if(treesize != sb.GetTotal()) {
|
|
sb.SetTotal(treesize);
|
|
Refresh();
|
|
}
|
|
SyncCtrls(true, restorefocus);
|
|
SyncInfo();
|
|
}
|
|
|
|
void TreeCtrl::SyncTree(bool immediate)
|
|
{
|
|
if(!dirty)
|
|
return;
|
|
if(noroot)
|
|
Open(0);
|
|
Ptr<Ctrl> restorefocus = GetFocusChildDeep();
|
|
hasctrls = false;
|
|
int cursorid = GetCursor();
|
|
for(int i = 0; i < item.GetCount(); i++)
|
|
item[i].linei = -1;
|
|
line.Clear();
|
|
treesize = Size(0, 0);
|
|
if(noroot) {
|
|
if(GetChildCount(0))
|
|
treesize.cy = -item[0].GetSize(display).cy;
|
|
ReLine(0, -1, treesize);
|
|
}
|
|
else
|
|
ReLine(0, 0, treesize);
|
|
treesize.cy = max(0, treesize.cy);
|
|
treesize.cx += levelcx;
|
|
cursor = -1;
|
|
dirty = false;
|
|
if(cursorid >= 0)
|
|
SetCursor(cursorid, false, false, false);
|
|
if(immediate)
|
|
SyncAfterSync(restorefocus);
|
|
else
|
|
PostCallback(PTEBACK1(SyncAfterSync, restorefocus));
|
|
}
|
|
|
|
void TreeCtrl::SyncCtrls(bool add, Ctrl *restorefocus)
|
|
{
|
|
if(!hasctrls)
|
|
return;
|
|
Point org = sb;
|
|
chldlck = true;
|
|
Size sz = GetSize();
|
|
int y0 = 0;
|
|
for(int i = noroot; i < line.GetCount(); i++) {
|
|
const Line& l = line[i];
|
|
Item& m = item[l.itemi];
|
|
int mcy = l.y - y0;
|
|
y0 = l.y;
|
|
if(m.ctrl) {
|
|
if(add)
|
|
AddChildBefore(m.ctrl, GetLastChild());
|
|
if(m.ctrl == restorefocus || m.ctrl->HasChildDeep(restorefocus))
|
|
restorefocus->SetFocus();
|
|
|
|
int yy = l.y - org.y;
|
|
if(yy + mcy < 0 || yy > sz.cy) // not in view, no need to evaluate precisely
|
|
m.ctrl->SetRect(levelcx + l.level * levelcx + m.margin - org.x,
|
|
2 * sz.cy, sz.cx, 32);
|
|
else {
|
|
Size msz = m.GetSize(display);
|
|
Size isz = m.image.GetSize();
|
|
Size csz = m.GetCtrlSize();
|
|
m.ctrl->SetRect(levelcx + l.level * levelcx + isz.cx + m.margin - org.x,
|
|
l.y + (msz.cy - csz.cy) / 2 - org.y, csz.cx, csz.cy);
|
|
}
|
|
}
|
|
}
|
|
chldlck = false;
|
|
}
|
|
|
|
bool TreeCtrl::IsOpen(int id) const
|
|
{
|
|
return item[id].isopen;
|
|
}
|
|
|
|
Vector<int> TreeCtrl::GetOpenIds() const
|
|
{
|
|
Vector<int> r;
|
|
for(int id = 0; id < item.GetCount(); id++)
|
|
if(!item[id].free && item[id].isopen)
|
|
r.Add(id);
|
|
return r;
|
|
}
|
|
|
|
void TreeCtrl::OpenIds(const Vector<int>& ids)
|
|
{
|
|
for(int id : ids)
|
|
if(IsValid(id))
|
|
Open(id);
|
|
}
|
|
|
|
void TreeCtrl::Dirty(int id)
|
|
{
|
|
// if(selectcount) SelClear(0);
|
|
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.canopen || !open || m.child.GetCount())) {
|
|
m.isopen = open;
|
|
int q = GetCursor();
|
|
while(q >= 0) {
|
|
q = GetParent(q);
|
|
if(q == id) {
|
|
SetCursor(id, true, true, true);
|
|
break;
|
|
}
|
|
}
|
|
if(selectcount) SelClear(0);
|
|
Dirty(id);
|
|
if(open)
|
|
WhenOpen(id);
|
|
else
|
|
WhenClose(id);
|
|
}
|
|
}
|
|
|
|
void TreeCtrl::OpenDeep(int id, bool open)
|
|
{
|
|
Open(id, open);
|
|
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, int ex)
|
|
{
|
|
SyncTree();
|
|
if(i >= 0) {
|
|
Size sz = GetSize();
|
|
int y = line[i].y - sb.GetY();
|
|
Refresh(0, y - ex, sz.cx, item[line[i].itemi].GetSize(display).cy + 2 * ex);
|
|
SyncInfo();
|
|
}
|
|
}
|
|
|
|
void TreeCtrl::RefreshItem(int id, int ex)
|
|
{
|
|
SyncTree();
|
|
if(id >= 0 && id < item.GetCount())
|
|
RefreshLine(item[id].linei, ex);
|
|
}
|
|
|
|
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::ScrollIntoLine(int i)
|
|
{
|
|
sb.ScrollIntoY(line[i].y, item[line[i].itemi].GetSize(display).cy);
|
|
}
|
|
|
|
void TreeCtrl::CenterLine(int i)
|
|
{
|
|
int top = line[i].y;
|
|
int bottom = top + item[line[i].itemi].GetSize(display).cy;
|
|
sb.SetY(top + ((bottom - GetSize().cy) >> 1));
|
|
}
|
|
|
|
void TreeCtrl::ScrollIntoCursor()
|
|
{
|
|
int c = GetCursorLine();
|
|
if(c >= 0)
|
|
ScrollIntoLine(c);
|
|
}
|
|
|
|
void TreeCtrl::CenterCursor()
|
|
{
|
|
int c = GetCursorLine();
|
|
if(c >= 0)
|
|
CenterLine(c);
|
|
}
|
|
|
|
void TreeCtrl::MoveCursorLine(int c, int incr)
|
|
{
|
|
int cnt = line.GetCount();
|
|
if (!incr) return;
|
|
else if (c < 0) c = cnt-1;
|
|
else if (c >= cnt) c = 0;
|
|
|
|
while (!item[line[c].itemi].canselect) {
|
|
c += incr;
|
|
if (c == cursor) return;
|
|
else if (c < 0) c = cnt-1;
|
|
else if (c >= cnt) c = 0;
|
|
}
|
|
SetCursorLineSync(c);
|
|
}
|
|
|
|
void TreeCtrl::SetCursorLine(int i, bool sc, bool sel, bool cb)
|
|
{
|
|
if(nocursor)
|
|
return;
|
|
if(sel && multiselect) {
|
|
if(selectcount) SelClear(0);
|
|
SelectOne(line[i].itemi, true);
|
|
}
|
|
if(i != cursor) {
|
|
i = minmax(i, 0, line.GetCount() - 1);
|
|
if(i < 0) return;
|
|
Item& m = item[line[i].itemi];
|
|
if (!multiselect && !m.canselect) return;
|
|
if(sc)
|
|
sb.ScrollIntoY(line[i].y, m.GetSize(display).cy);
|
|
RefreshLine(cursor);
|
|
cursor = i;
|
|
RefreshLine(cursor);
|
|
if(m.ctrl)
|
|
m.ctrl->SetWantFocus();
|
|
if(cb) {
|
|
WhenCursor();
|
|
if(!multiselect) WhenSel();
|
|
}
|
|
}
|
|
}
|
|
|
|
void TreeCtrl::SetCursorLine(int i)
|
|
{
|
|
SetCursorLine(i, true, true, true);
|
|
}
|
|
|
|
void TreeCtrl::SetCursorLineSync(int i)
|
|
{
|
|
if(nocursor)
|
|
return;
|
|
if(i != cursor) {
|
|
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) {
|
|
Sync();
|
|
sb.ScrollIntoY(line[cursor].y, m.GetSize(display).cy);
|
|
}
|
|
if(!(m.ctrl && m.ctrl->SetWantFocus()))
|
|
SetWantFocus();
|
|
WhenCursor();
|
|
if (!multiselect) WhenSel();
|
|
}
|
|
}
|
|
|
|
void TreeCtrl::KillCursor()
|
|
{
|
|
RefreshLine(cursor);
|
|
cursor = -1;
|
|
Refresh();
|
|
WhenCursor();
|
|
WhenSel();
|
|
SyncInfo();
|
|
}
|
|
|
|
void TreeCtrl::SetCursor(int id, bool sc, bool sel, bool cb)
|
|
{
|
|
while(id > 0) {
|
|
ASSERT(id >= 0 && id < item.GetCount());
|
|
MakeVisible(id);
|
|
SyncTree(true);
|
|
const Item& m = item[id];
|
|
if(m.linei >= 0) {
|
|
SetCursorLine(m.linei, sc, sel, cb);
|
|
return;
|
|
}
|
|
id = m.parent;
|
|
}
|
|
SetCursorLine(0, sc, sel, cb);
|
|
}
|
|
|
|
void TreeCtrl::SetCursor(int id)
|
|
{
|
|
SetCursor(id, true, true, 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(!item[i].free && 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::UpdateSelect()
|
|
{
|
|
WhenSelection();
|
|
WhenSel();
|
|
WhenAction();
|
|
}
|
|
|
|
bool TreeCtrl::IsSel(int id) const
|
|
{
|
|
return multiselect ? IsSelected(id) : GetCursor() == id;
|
|
}
|
|
|
|
bool TreeCtrl::IsSelDeep(int id) const
|
|
{
|
|
return IsSel(id) || id && IsSelDeep(GetParent(id));
|
|
}
|
|
|
|
void TreeCtrl::SelectOne0(int id, bool sel, bool cb)
|
|
{
|
|
if(!multiselect)
|
|
return;
|
|
if(item[id].canselect && item[id].sel != sel) {
|
|
item[id].sel = sel;
|
|
selectcount += 2 * sel - 1;
|
|
RefreshItem(id);
|
|
if (cb) UpdateSelect();
|
|
}
|
|
}
|
|
|
|
void TreeCtrl::SelectOne(int id, bool sel)
|
|
{
|
|
SelectOne0(id, sel, true);
|
|
}
|
|
|
|
void TreeCtrl::ShiftSelect(int l1, int l2)
|
|
{
|
|
if(!multiselect)
|
|
return;
|
|
if(l1 > l2)
|
|
UPP::Swap(l1, l2);
|
|
for(int i = 0; i < line.GetCount(); i++)
|
|
SelectOne0(line[i].itemi, i >= l1 && i <= l2, false);
|
|
UpdateSelect();
|
|
}
|
|
|
|
void TreeCtrl::LeftDrag(Point p, dword keyflags)
|
|
{
|
|
OkEdit();
|
|
if(p.y + sb.y > sb.GetTotal().cy)
|
|
return;
|
|
WhenDrag();
|
|
}
|
|
|
|
Rect TreeCtrl::GetValueRect(const Line& l) const
|
|
{
|
|
const Item& m = item[l.itemi];
|
|
Size msz = m.GetSize(display);
|
|
Size isz = m.image.GetSize();
|
|
Size vsz = m.GetValueSize(display);
|
|
Point org = sb;
|
|
int x = levelcx + l.level * levelcx + isz.cx - org.x + m.margin;
|
|
if(m.ctrl && !highlight_ctrl)
|
|
x += m.GetCtrlSize().cx;
|
|
return RectC(x, l.y - org.y + (msz.cy - vsz.cy) / 2, vsz.cx, vsz.cy);
|
|
}
|
|
|
|
void TreeCtrl::DoClick(Point p, dword flags, bool down, bool canedit)
|
|
{
|
|
Point org = sb;
|
|
itemclickpos = Null;
|
|
if(p.y + org.y > sb.GetTotal().cy)
|
|
return;
|
|
int i = FindLine(p.y + org.y);
|
|
const Line& l = line[i];
|
|
int itemx = levelcx + l.level * levelcx - org.x;
|
|
int x = itemx - (levelcx >> 1);
|
|
itemclickpos.x = p.x - itemx;
|
|
itemclickpos.y = p.y + org.y - l.y;
|
|
if(p.x > x - DPI(6) && p.x < x + DPI(6)) {
|
|
if(down)
|
|
Open(l.itemi, !IsOpen(l.itemi));
|
|
}
|
|
else {
|
|
auto DoEdit = [&](int qq) {
|
|
if(down)
|
|
KillEdit();
|
|
if(cursor == qq && qq >= 0 && cursor > 0 && !HasCapture() && WhenEdited && !(flags & (K_SHIFT|K_CTRL)) &&
|
|
GetValueRect(line[qq]).Contains(p) && canedit)
|
|
SetTimeCallback(750, THISBACK(StartEdit), TIMEID_STARTEDIT);
|
|
};
|
|
if(down && IsSel(l.itemi)) { // make possible DnD of multiple items
|
|
selclick = true;
|
|
if(down)
|
|
WhenLeftClick();
|
|
DoEdit(i);
|
|
return;
|
|
}
|
|
SetWantFocus();
|
|
int q = cursor;
|
|
int qq = cursor;
|
|
SetCursorLine(i, true, false, true);
|
|
if(multiselect) {
|
|
int id = GetCursor();
|
|
if(flags & K_CTRL) {
|
|
SelectOne(id, !IsSelected(id));
|
|
anchor = cursor;
|
|
}
|
|
else
|
|
if(flags & K_SHIFT)
|
|
ShiftSelect(anchor < 0 ? cursor : anchor, cursor);
|
|
else {
|
|
if(selectcount) SelClear(0);
|
|
SelectOne(id);
|
|
anchor = cursor;
|
|
}
|
|
}
|
|
if(cursor != q)
|
|
WhenAction();
|
|
if(down)
|
|
WhenLeftClick();
|
|
Select();
|
|
DoEdit(qq);
|
|
}
|
|
}
|
|
|
|
void TreeCtrl::KillEdit()
|
|
{
|
|
sb.x.Enable();
|
|
sb.y.Enable();
|
|
edit_cursor = -1;
|
|
KillTimeCallback(TIMEID_STARTEDIT);
|
|
if(editor && editor->GetParent() == this) {
|
|
int b = editor->HasFocus();
|
|
editor->Remove();
|
|
if(b) SetFocus();
|
|
}
|
|
}
|
|
|
|
void TreeCtrl::StartEdit() {
|
|
if(cursor < 0 || IsDragAndDropSource()) return;
|
|
if(!editor) {
|
|
edit_string.Create();
|
|
editor = ~edit_string;
|
|
}
|
|
AddChild(editor);
|
|
const Line& l = line[cursor];
|
|
const Item& m = item[l.itemi];
|
|
Rect r = GetValueRect(l);
|
|
r.Inflate(DPI(2));
|
|
editor->SetFrame(BlackFrame());
|
|
r.right = GetSize().cx;
|
|
editor->SetRect(r);
|
|
*editor <<= m.value;
|
|
editor->Show();
|
|
editor->SetFocus();
|
|
sb.x.Disable();
|
|
sb.y.Disable();
|
|
edit_cursor = GetCursor();
|
|
SyncInfo();
|
|
WhenStartEdit(edit_cursor);
|
|
}
|
|
|
|
void TreeCtrl::EndEdit() {
|
|
KillEdit();
|
|
}
|
|
|
|
void TreeCtrl::OkEdit() {
|
|
if(IsEdit()) {
|
|
int q = edit_cursor;
|
|
if(q >= 0 && editor)
|
|
WhenEdited(q, ~*editor);
|
|
EndEdit();
|
|
}
|
|
}
|
|
|
|
void TreeCtrl::SyncInfo()
|
|
{
|
|
if(IsShutdown() || IsPainting() || dirty)
|
|
return;
|
|
if((HasMouse() || info.HasMouse()) && popupex && !IsEdit()) {
|
|
Point p = GetMouseViewPos();
|
|
Point org = sb;
|
|
int i = FindLine(p.y + org.y);
|
|
if(i < line.GetCount()) {
|
|
Size sz = GetSize();
|
|
const Line& l = line[i];
|
|
const Item& m = item[l.itemi];
|
|
int x = levelcx + l.level * levelcx - org.x + m.image.GetSize().cx;
|
|
Size csz = m.GetCtrlSize();
|
|
if(m.ctrl && !highlight_ctrl)
|
|
x += csz.cx;
|
|
Rect r = RectC(x, l.y - org.y, sz.cx - x, m.GetValueSize(display).cy + 2 * m.margin);
|
|
if(r.Contains(p)) {
|
|
Color fg, bg;
|
|
dword st;
|
|
const Display *d = GetStyle(i, fg, bg, st);
|
|
info.UseDisplayStdSize();
|
|
info.Set(this, r, m.value, d, fg, bg, st, m.margin);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
info.Cancel();
|
|
}
|
|
|
|
void TreeCtrl::MouseMove(Point p, dword)
|
|
{
|
|
if(mousemove && !IsReadOnly()) {
|
|
Point org = sb;
|
|
if(p.y + org.y < treesize.cy) {
|
|
int i = FindLine(p.y + org.y);
|
|
if(!IsNull(i)) SetCursorLine(i);
|
|
}
|
|
}
|
|
SyncInfo();
|
|
}
|
|
|
|
void TreeCtrl::LeftDown(Point p, dword flags)
|
|
{
|
|
OkEdit();
|
|
DoClick(p, flags, true, true);
|
|
}
|
|
|
|
void TreeCtrl::LeftUp(Point p, dword keyflags)
|
|
{
|
|
if(selclick) {
|
|
DoClick(p, keyflags, false, false);
|
|
selclick = false;
|
|
}
|
|
}
|
|
|
|
void TreeCtrl::LeftDouble(Point p, dword flags)
|
|
{
|
|
OkEdit();
|
|
DoClick(p, flags, true, false);
|
|
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(i == GetCursorLine() && (flags & (K_SHIFT|K_CTRL)) == 0 && p.x > x + 6)
|
|
WhenLeftDouble();
|
|
}
|
|
|
|
void TreeCtrl::RightDown(Point p, dword flags)
|
|
{
|
|
OkEdit();
|
|
Point org = sb;
|
|
if(p.y + org.y < sb.GetTotal().cy) {
|
|
int i = FindLine(p.y + org.y);
|
|
if(i >= 0) {
|
|
SetWantFocus();
|
|
SetCursorLine(i, true, !IsSel(line[i].itemi), true);
|
|
}
|
|
}
|
|
if(WhenBar)
|
|
MenuBar::Execute(WhenBar);
|
|
}
|
|
|
|
const Display *TreeCtrl::GetStyle(int i, Color& fg, Color& bg, dword& st)
|
|
{
|
|
const Line& l = line[i];
|
|
const Item& m = item[l.itemi];
|
|
st = 0;
|
|
fg = SColorText;
|
|
bg = SColorPaper;
|
|
if(nobg)
|
|
bg = Null;
|
|
bool hasfocus = HasFocus() && !IsDragAndDropTarget();
|
|
bool drop = l.itemi == dropitem && dropinsert == 0;
|
|
if(IsReadOnly())
|
|
st |= Display::READONLY;
|
|
if(m.sel && multiselect)
|
|
st |= Display::SELECT;
|
|
if(i == cursor && !nocursor)
|
|
st |= Display::CURSOR;
|
|
if(drop) {
|
|
hasfocus = true;
|
|
st |= Display::CURSOR;
|
|
}
|
|
if(hasfocus)
|
|
st |= Display::FOCUS;
|
|
if((st & Display::SELECT) ||
|
|
!multiselect && (st & Display::CURSOR) && !nocursor ||
|
|
drop) {
|
|
fg = hasfocus ? SColorHighlightText : SColorText;
|
|
bg = hasfocus ? SColorHighlight : Blend(SColorDisabled, SColorPaper);
|
|
}
|
|
if(m.display)
|
|
return m.display;
|
|
return display;
|
|
}
|
|
|
|
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;
|
|
int start = multiroot ? 1 : 0;
|
|
for(int i = start; i < line.GetCount(); i++) {
|
|
Line& l = line[i];
|
|
if(l.ll >= 0) {
|
|
int yl = line[i].y + item[l.itemi].GetSize(display).cy - org.y;
|
|
int yh = line[l.ll].y + item[line[l.ll].itemi].GetSize(display).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);
|
|
}
|
|
}
|
|
}
|
|
Rect dri = Null;
|
|
for(int i = FindLine(org.y); i < line.GetCount(); i++) {
|
|
Line& l = line[i];
|
|
const Item& m = item[l.itemi];
|
|
Size msz = m.GetSize(display);
|
|
Size isz = m.image.GetSize();
|
|
Size vsz = m.GetValueSize(display);
|
|
int y = l.y - org.y;
|
|
if(y > sz.cy)
|
|
break;
|
|
int x = 0;
|
|
x = levelcx + l.level * levelcx - org.x;
|
|
Point op = Point(x - levelcx2, y + msz.cy / 2);
|
|
Rect fr = RectC(x, y, vsz.cx + 2 * m.margin + isz.cx, msz.cy);
|
|
if(l.itemi == dropitem) {
|
|
dri = fr;
|
|
if(i == 0)
|
|
dri.top++;
|
|
}
|
|
if(w.IsPainting(0, y, sz.cx, msz.cy) && msz.cy > 0) {
|
|
if(multiroot) {
|
|
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);
|
|
}
|
|
else if (l.level > 0)
|
|
w.DrawRect(op.x, op.y, levelcx2, 1, SColorShadow);
|
|
else {
|
|
op -= CtrlImg::cross().GetSize() / 2;
|
|
w.DrawImage(op.x, op.y, imgEmpty);
|
|
}
|
|
}
|
|
else {
|
|
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;
|
|
Color fg, bg;
|
|
dword st;
|
|
Size csz = m.GetCtrlSize();
|
|
if(m.ctrl && !highlight_ctrl)
|
|
x += csz.cx;
|
|
if(x < sz.cx) {
|
|
const Display *d = GetStyle(i, fg, bg, st);
|
|
int ctx = highlight_ctrl * csz.cx;
|
|
Rect br = RectC(x, y, vsz.cx + 2 * m.margin + ctx, msz.cy);
|
|
if(!IsNull(m.value) || m.ctrl && highlight_ctrl) {
|
|
w.DrawRect(br, bg);
|
|
Rect r = RectC(x + ctx + m.margin, y + (msz.cy - vsz.cy) / 2, vsz.cx, vsz.cy);
|
|
w.Clip(r);
|
|
d->Paint(w, r, m.value, fg, bg, st);
|
|
w.End();
|
|
}
|
|
if(i == cursor && !nocursor && multiselect && GetSelectCount() != 1 && HasFocus()
|
|
&& !IsDragAndDropTarget())
|
|
DrawFocus(w, br, st & Display::SELECT ? SColorPaper() : SColorText());
|
|
}
|
|
}
|
|
}
|
|
if(dropitem >= 0 && dropinsert)
|
|
DrawHorzDrop(w, dri.left - 2, dropinsert < 0 ? dri.top : dri.bottom - 1,
|
|
sz.cx - dri.left + 2);
|
|
}
|
|
|
|
Image TreeCtrl::GetDragSample()
|
|
{
|
|
SyncTree();
|
|
Size sz = StdSampleSize();
|
|
ImageDraw iw(sz);
|
|
iw.DrawRect(sz, SColorPaper);
|
|
int y = 0;
|
|
for(int i = 0; i < line.GetCount(); i++) {
|
|
Line& l = line[i];
|
|
const Item& m = item[l.itemi];
|
|
const Display *d = m.display;
|
|
if(!d) d = display;
|
|
Size msz = m.GetSize(display);
|
|
Size isz = m.image.GetSize();
|
|
Size vsz = m.GetValueSize(display);
|
|
int x = 0;
|
|
if(IsSel(l.itemi)) {
|
|
iw.DrawImage(x, y + (msz.cy - isz.cy) / 2, m.image);
|
|
x += isz.cx;
|
|
d->Paint(iw, RectC(x + m.margin, y + (msz.cy - vsz.cy) / 2, vsz.cx, vsz.cy), m.value,
|
|
SColorHighlightText(), SColorHighlight(), Display::SELECT|Display::FOCUS);
|
|
y += msz.cy;
|
|
}
|
|
}
|
|
return ColorMask(iw, SColorPaper);
|
|
}
|
|
|
|
void TreeCtrl::Scroll()
|
|
{
|
|
SyncTree();
|
|
if(hasctrls) {
|
|
Refresh();
|
|
SyncCtrls(false, NULL);
|
|
}
|
|
else
|
|
scroller.Scroll(*this, sb);
|
|
}
|
|
|
|
void TreeCtrl::MouseWheel(Point, int zdelta, dword keyflags)
|
|
{
|
|
if(keyflags & K_SHIFT)
|
|
sb.WheelX(zdelta);
|
|
else
|
|
sb.WheelY(zdelta);
|
|
}
|
|
|
|
void TreeCtrl::HorzMouseWheel(Point, int zdelta, dword keyflags)
|
|
{
|
|
sb.WheelX(zdelta);
|
|
}
|
|
|
|
void TreeCtrl::ChildGotFocus()
|
|
{
|
|
if(chldlck)
|
|
return;
|
|
for(int i = 0; i < line.GetCount(); i++) {
|
|
Item& m = item[line[i].itemi];
|
|
if(m.ctrl && m.ctrl->HasFocusDeep()) {
|
|
SetCursorLine(i);
|
|
return;
|
|
}
|
|
}
|
|
Ctrl::ChildGotFocus();
|
|
}
|
|
|
|
bool TreeCtrl::Tab(int d)
|
|
{
|
|
if(cursor < 0)
|
|
return false;
|
|
Item& m = item[line[cursor].itemi];
|
|
if(m.ctrl && m.ctrl->HasFocusDeep())
|
|
return false;
|
|
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();
|
|
if(Bar::Scan(WhenBar, key))
|
|
return true;
|
|
Size sz = GetSize();
|
|
int cid = GetCursor();
|
|
bool shift = key & K_SHIFT;
|
|
key &= ~K_SHIFT;
|
|
switch(key) {
|
|
case K_ENTER:
|
|
if(IsEdit())
|
|
OkEdit();
|
|
else
|
|
Select();
|
|
break;
|
|
case K_ESCAPE:
|
|
if(IsEdit())
|
|
EndEdit();
|
|
break;
|
|
case K_TAB:
|
|
return Tab(1);
|
|
case K_SHIFT_TAB:
|
|
return Tab(-1);
|
|
case K_UP:
|
|
MoveCursorLine(cursor - 1, -1);
|
|
break;
|
|
case K_DOWN:
|
|
MoveCursorLine(cursor + 1, 1);
|
|
break;
|
|
case K_PAGEDOWN:
|
|
MoveCursorLine(cursor >= 0 ? FindLine(line[cursor].y + sz.cy) : 0, 1);
|
|
break;
|
|
case K_PAGEUP:
|
|
MoveCursorLine(cursor >= 0 ? FindLine(line[cursor].y - sz.cy) : line.GetCount() - 1, -1);
|
|
break;
|
|
case K_LEFT:
|
|
if(cid >= 0)
|
|
Close(cid);
|
|
break;
|
|
case K_RIGHT:
|
|
if(cid >= 0)
|
|
Open(cid);
|
|
break;
|
|
default: {
|
|
if(accel && key >= ' ' && key < K_CHAR_LIM) {
|
|
int ascii_line = -1;
|
|
int upper_line = -1;
|
|
int exact_line = -1;
|
|
int l = GetCursorLine();
|
|
for(;;) {
|
|
if(++l >= GetLineCount())
|
|
l = 0;
|
|
if(l == GetCursorLine())
|
|
break;
|
|
Value v = GetValue(line[l].itemi);
|
|
WString w;
|
|
if(IsString(v)) w = v;
|
|
else w = StdFormat(v).ToWString();
|
|
word first = w[0];
|
|
if(ascii_line < 0 && ToAscii(first) == ToAscii((word)key))
|
|
ascii_line = l;
|
|
if(upper_line < 0 && ToUpper(first) == ToUpper((word)key))
|
|
upper_line = l;
|
|
if(exact_line < 0 && first == key)
|
|
exact_line = l;
|
|
}
|
|
int ln = (exact_line >= 0 ? exact_line : upper_line >= 0 ? upper_line : ascii_line);
|
|
if(ln < 0)
|
|
BeepExclamation();
|
|
else {
|
|
int id = GetItemAtLine(ln);
|
|
Open(id);
|
|
SetCursor(id);
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
if(GetCursor() != cid) {
|
|
if(multiselect && cursor >= 0) {
|
|
if(shift) {
|
|
if(anchor < 0)
|
|
anchor = cursor;
|
|
ShiftSelect(anchor, cursor);
|
|
}
|
|
else {
|
|
ClearSelection();
|
|
SelectOne(GetCursor());
|
|
anchor = cursor;
|
|
}
|
|
}
|
|
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;
|
|
}
|
|
RefreshLine(cursor);
|
|
if(GetSelectCount() > 1)
|
|
Refresh();
|
|
SyncInfo();
|
|
}
|
|
|
|
void TreeCtrl::LostFocus()
|
|
{
|
|
if(editor && !editor->HasFocus())
|
|
OkEdit();
|
|
if(dirty)
|
|
return;
|
|
RefreshLine(cursor);
|
|
if(GetSelectCount() > 1)
|
|
Refresh();
|
|
SyncInfo();
|
|
}
|
|
|
|
void TreeCtrl::ChildLostFocus()
|
|
{
|
|
if(editor && !editor->HasFocus())
|
|
OkEdit();
|
|
}
|
|
|
|
void TreeCtrl::ChildRemoved(Ctrl *)
|
|
{
|
|
if(!IsShutdown() && !chldlck)
|
|
Dirty();
|
|
}
|
|
|
|
struct TreeCtrl::SortOrder {
|
|
TreeCtrl *tree;
|
|
const ValueOrder *order;
|
|
const ValuePairOrder *pairorder;
|
|
bool byvalue;
|
|
|
|
bool operator()(int a, int b) const {
|
|
return pairorder ? (*pairorder)(tree->Get(a), tree->GetValue(a), tree->Get(b), tree->GetValue(b))
|
|
: byvalue ? (*order)(tree->GetValue(a), tree->GetValue(b))
|
|
: (*order)(tree->Get(a), tree->Get(b));
|
|
}
|
|
|
|
SortOrder() { pairorder = NULL; }
|
|
};
|
|
|
|
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::Sort0(int id, const ValuePairOrder& order)
|
|
{
|
|
SortOrder so;
|
|
so.tree = this;
|
|
so.pairorder = ℴ
|
|
UPP::Sort(item[id].child, so);
|
|
}
|
|
|
|
void TreeCtrl::Sort(int id, const ValuePairOrder& order)
|
|
{
|
|
SyncTree();
|
|
Sort0(id, order);
|
|
Dirty(id);
|
|
}
|
|
|
|
void TreeCtrl::SortDeep0(int id, const ValuePairOrder& order)
|
|
{
|
|
Sort0(id, order);
|
|
Item& m = item[id];
|
|
for(int i = 0; i < m.child.GetCount(); i++)
|
|
SortDeep0(m.child[i], order);
|
|
}
|
|
|
|
void TreeCtrl::SortDeep(int id, const ValuePairOrder& order)
|
|
{
|
|
SyncTree();
|
|
SortDeep0(id, order);
|
|
Dirty(id);
|
|
}
|
|
|
|
void TreeCtrl::Sort(int id, int (*compare)(const Value& k1, const Value& v1,
|
|
const Value& k2, const Value& v2))
|
|
{
|
|
SortDeep(id, FnValuePairOrder(compare));
|
|
}
|
|
|
|
void TreeCtrl::SortDeep(int id, int (*compare)(const Value& k1, const Value& v1,
|
|
const Value& k2, const Value& v2))
|
|
{
|
|
SortDeep(id, FnValuePairOrder(compare));
|
|
}
|
|
|
|
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::SelClear(int id)
|
|
{
|
|
Item& m = item[id];
|
|
if(m.sel) {
|
|
m.sel = false;
|
|
RefreshItem(id);
|
|
selectcount--;
|
|
}
|
|
for(int i = 0; i < m.child.GetCount(); i++)
|
|
SelClear(m.child[i]);
|
|
}
|
|
|
|
void TreeCtrl::GatherSel(int id, Vector<int>& sel) const
|
|
{
|
|
if(IsSel(id))
|
|
sel.Add(id);
|
|
const Item& m = item[id];
|
|
for(int i = 0; i < m.child.GetCount(); i++)
|
|
GatherSel(m.child[i], sel);
|
|
}
|
|
|
|
Vector<int> TreeCtrl::GetSel() const
|
|
{
|
|
Vector<int> v;
|
|
GatherSel(0, v);
|
|
return v;
|
|
}
|
|
|
|
void TreeCtrl::RefreshSel()
|
|
{
|
|
Size sz = GetSize();
|
|
for(int i = FindLine(sb.GetY()); i < line.GetCount(); i++) {
|
|
Line& l = line[i];
|
|
int y = l.y - sb.GetY();
|
|
if(y > sz.cy)
|
|
break;
|
|
if(IsSel(l.itemi))
|
|
RefreshItem(l.itemi);
|
|
}
|
|
}
|
|
|
|
void TreeCtrl::ClearSelection()
|
|
{
|
|
if(selectcount) {
|
|
SelClear(0);
|
|
WhenSelection();
|
|
WhenSel();
|
|
WhenAction();
|
|
selectcount = 0;
|
|
}
|
|
}
|
|
|
|
void TreeCtrl::SetOption(int id)
|
|
{
|
|
}
|
|
|
|
void TreeCtrl::Select()
|
|
{
|
|
}
|
|
|
|
void TreeCtrl::DnD(int itemid, int insert)
|
|
{
|
|
if(itemid != dropitem || insert != dropinsert)
|
|
{
|
|
RefreshItem(dropitem, 4);
|
|
dropitem = itemid;
|
|
dropinsert = insert;
|
|
RefreshItem(dropitem, 4);
|
|
}
|
|
}
|
|
|
|
bool TreeCtrl::DnDInserti(int ii, PasteClip& d, bool bottom)
|
|
{
|
|
if(ii >= 0 && ii < line.GetCount()) {
|
|
int itemi = line[ii].itemi;
|
|
int parent = GetParent(itemi);
|
|
int childi = GetChildIndex(parent, itemi);
|
|
int ins = -1;
|
|
if(bottom) {
|
|
if(childi != GetChildCount(parent) - 1 && ii + 1 < line.GetCount())
|
|
return DnDInserti(ii + 1, d, false);
|
|
else {
|
|
childi++;
|
|
ins = 1;
|
|
}
|
|
}
|
|
WhenDropInsert(parent, childi, d);
|
|
if(d.IsAccepted()) {
|
|
DnD(itemi, ins);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void TreeCtrl::DragEnter()
|
|
{
|
|
RefreshSel();
|
|
}
|
|
|
|
void TreeCtrl::DragAndDrop(Point p, PasteClip& d)
|
|
{
|
|
int py = p.y + sb.GetY();
|
|
if(py < sb.GetTotal().cy) {
|
|
int ii = FindLine(py);
|
|
const Line& l = line[ii];
|
|
if(l.itemi && WhenDropInsert) {
|
|
int y = l.y;
|
|
int cy = item[l.itemi].GetSize(display).cy;
|
|
if(py < y + cy / 4 && DnDInserti(ii, d, false))
|
|
return;
|
|
if(py >= y + 3 * cy / 4 && DnDInserti(ii, d, true))
|
|
return;
|
|
}
|
|
WhenDropItem(l.itemi, d);
|
|
if(d.IsAccepted()) {
|
|
DnD(l.itemi, 0);
|
|
return;
|
|
}
|
|
WhenDropInsert(l.itemi, GetChildCount(l.itemi), d);
|
|
if(d.IsAccepted()) {
|
|
DnD(l.itemi, 0);
|
|
return;
|
|
}
|
|
}
|
|
WhenDrop(d);
|
|
DnD(-1, 0);
|
|
}
|
|
|
|
void TreeCtrl::DragRepeat(Point p)
|
|
{
|
|
if(IsReadOnly())
|
|
return;
|
|
sb = sb + GetDragScroll(this, p, 16);
|
|
p.y += sb.y;
|
|
if(p == repoint) {
|
|
if(GetTimeClick() - retime > 1000 && p.y < sb.GetTotal().cy) {
|
|
int ii = FindLine(p.y);
|
|
const Line& l = line[ii];
|
|
int y = l.y;
|
|
int cy = item[l.itemi].GetSize(display).cy;
|
|
if(p.y >= y + cy / 4 && p.y < y + 3 * cy / 4 &&
|
|
!IsOpen(l.itemi) && GetChildCount(l.itemi)) {
|
|
Open(l.itemi, true);
|
|
retime = GetTimeClick();
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
retime = GetTimeClick();
|
|
repoint = p;
|
|
}
|
|
}
|
|
|
|
void TreeCtrl::DragLeave()
|
|
{
|
|
DnD(-1, 0);
|
|
RefreshSel();
|
|
repoint = Null;
|
|
}
|
|
|
|
void TreeCtrl::AdjustAction(int parent, PasteClip& d)
|
|
{
|
|
if(IsDragAndDropSource() && IsSelDeep(parent) && d.GetAction() == DND_MOVE)
|
|
d.SetAction(DND_COPY);
|
|
}
|
|
|
|
Vector<int> TreeCtrl::InsertDrop(int parent, int ii, const TreeCtrl& src, PasteClip& d)
|
|
{
|
|
TreeCtrl copy;
|
|
Vector<int> sel = src.GetSel();
|
|
for(int i = 0; i < sel.GetCount(); i++)
|
|
Copy(copy, 0, i, src, sel[i]);
|
|
Vector<int> did;
|
|
for(int i = 0; i < copy.GetChildCount(0); i++) {
|
|
did.Add(Copy(*this, parent, ii + i, copy, copy.GetChild(0, i)));
|
|
SetCursor(did.Top());
|
|
}
|
|
if(&src == this && d.GetAction() == DND_MOVE) {
|
|
Remove(sel);
|
|
d.SetAction(DND_COPY);
|
|
}
|
|
for(int i = 0; i < did.GetCount(); i++)
|
|
SelectOne(did[i], true);
|
|
return did;
|
|
}
|
|
|
|
Vector<int> TreeCtrl::InsertDrop(int parent, int ii, PasteClip& d)
|
|
{
|
|
return InsertDrop(parent, ii, GetInternal<TreeCtrl>(d), d);
|
|
}
|
|
|
|
void TreeCtrl::Swap(int id1, int id2)
|
|
{
|
|
SyncTree();
|
|
Item& i1 = item[id1];
|
|
Item& i2 = item[id2];
|
|
for(int i = 0; i < i1.child.GetCount(); i++)
|
|
item[i1.child[i]].parent = id2;
|
|
for(int i = 0; i < i2.child.GetCount(); i++)
|
|
item[i2.child[i]].parent = id1;
|
|
item.Swap(id1, id2);
|
|
Dirty(id1);
|
|
Dirty(id2);
|
|
}
|
|
|
|
void TreeCtrl::SwapChildren(int parentid, int i1, int i2)
|
|
{
|
|
SyncTree();
|
|
Item& parent = item[parentid];
|
|
UPP::Swap(parent.child[i1], parent.child[i2]);
|
|
Dirty(parentid);
|
|
}
|
|
|
|
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)
|
|
{
|
|
return Insert(parentid, GetChildCount(parentid), img, opt, text);
|
|
}
|
|
|
|
int OptionTree::Insert(int parentid, int i, const Image& img, Option& opt, const char *text)
|
|
{
|
|
if(text)
|
|
opt.SetLabel(text);
|
|
int id = TreeCtrl::Insert(parentid, i, img, opt);
|
|
option.At(id, NULL) = &opt;
|
|
opt.NoNotNull().BlackEdge();
|
|
opt <<= THISBACK1(SetOption, id);
|
|
SetOption(id);
|
|
return id;
|
|
}
|
|
|
|
|
|
int OptionTree::Insert(int parentid, int i, const Image& img, const char *text)
|
|
{
|
|
return Insert(parentid, i, img, aux.Add().NoNotNull().Set(Get(parentid)), text);
|
|
}
|
|
|
|
int OptionTree::Insert(int parentid, int i, const char *text)
|
|
{
|
|
return Insert(parentid, i, aux.Add().NoNotNull().Set(Get(parentid)), text);
|
|
}
|
|
|
|
|
|
int OptionTree::Add(int parentid, const Image& img, const char *text)
|
|
{
|
|
return Insert(parentid, GetChildCount(parentid), img, text);
|
|
}
|
|
|
|
int OptionTree::Add(int parentid, Option& opt, const char *text)
|
|
{
|
|
return Insert(parentid, GetChildCount(parentid), opt, text);
|
|
}
|
|
|
|
int OptionTree::Insert(int parentid, int i, Option& opt, const char *text)
|
|
{
|
|
return Insert(parentid, i, Null, opt.NoNotNull(), text);
|
|
}
|
|
|
|
int OptionTree::Add(int parentid, const char *text)
|
|
{
|
|
return Insert(parentid, GetChildCount(parentid), text);
|
|
}
|
|
|
|
void OptionTree::SetLabel(int id, const char *text)
|
|
{
|
|
Node n = GetNode(id);
|
|
Option *o = dynamic_cast<Option *>(~n.ctrl);
|
|
if(o)
|
|
o->SetLabel(text);
|
|
SetNode(id, n);
|
|
}
|
|
|
|
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);
|
|
if(manualmode) {
|
|
WhenOption();
|
|
return;
|
|
}
|
|
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() { manualmode = false; aux.Add(); }
|
|
OptionTree::~OptionTree() {}
|
|
|
|
int Copy(TreeCtrl& dst, int did, int i, const TreeCtrl& src, int id)
|
|
{
|
|
TreeCtrl::Node x = src.GetNode(id);
|
|
x.ctrl = NULL;
|
|
did = dst.Insert(did, i, x);
|
|
dst.Open(did, src.IsOpen(id));
|
|
for(int i = 0; i < src.GetChildCount(id); i++)
|
|
Copy(dst, did, i, src, src.GetChild(id, i));
|
|
return did;
|
|
}
|
|
|
|
}
|