mirror of
https://github.com/ultimatepp/ultimatepp.git
synced 2026-05-15 14:16:07 -06:00
This might bring some incompatibilities in the code that expects wchar to be 16 bit, which escpecially involves dealing with Win32 (and to lesser extend MacOS) APIs, so if your application is doing that, please check all instances of WCHAR (UniChar on MacOS) or even wchar especially type casts. To support host APIs, char16 is introduced (but there is no 16-bit String varian). Use ToSystemCharsetW, FromSystemCharsetW to convert texts to Win32 API. - Support of drawing non-BMP characters in GUI - Vastly improved character font replacement code (when drawing characters missing with requested font, replacement font is used) - Last instances of Win32 ANSI calls (those ending with A) are removed - UTF handling routines are refactored and their's naming is unified - RTF is now being able to handle non-BMP characters (RTF is used as clipboard format for RichText) Other minor changes: - fixed TryRealloc issue - improved MemoryCheck - Removed MemoryAlloc48/MemoryFree48 - In theide Background parsing should less often cause delays in the main thread
1900 lines
39 KiB
C++
1900 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;
|
|
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 += 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) 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(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) // 2008-04-08 mrjt
|
|
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(StdSampleSize());
|
|
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;
|
|
if(!(m.ctrl && m.ctrl->IsWantFocus())) {
|
|
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)
|
|
{
|
|
sb.WheelY(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;
|
|
}
|
|
|
|
}
|