#include "TabBar.h"

#define TFILE <TabBar/TabBar.t>
#include <Core/t.h>

#define IMAGECLASS TabBarImg
#define IMAGEFILE <TabBar/TabBar.iml>
#include <Draw/iml_source.h>

namespace Upp {

// AlignedFrame
void AlignedFrame::FrameLayout(Rect &r)
{
	switch(layout)
	{
		case LEFT:
			LayoutFrameLeft(r, this, framesize);
			break;
		case TOP:
			LayoutFrameTop(r, this, framesize);
			break;
		case RIGHT:
			LayoutFrameRight(r, this, framesize);
			break;
		case BOTTOM:
			LayoutFrameBottom(r, this, framesize);
			break;
	}
	r.top += border;
	r.left += border;
	r.right -= border;
	r.bottom -= border;
}

void AlignedFrame::FrameAddSize(Size& sz)
{
	sz += border * 2;
	IsVert() ? sz.cx += framesize : sz.cy += framesize;
}

void AlignedFrame::FramePaint(Draw& w, const Rect& r)
{
	if(border > 0)
	{
		Rect n = r;
		switch(layout)
		{
			case LEFT:
				n.left += framesize;
				break;
			case TOP:
				n.top += framesize;
				break;
			case RIGHT:
				n.right -= framesize;
				break;
			case BOTTOM:
				n.bottom -= framesize;
				break;
		}
		ViewFrame().FramePaint(w, n);
	}
	else
		FrameCtrl<Ctrl>::FramePaint(w, r);
}

AlignedFrame& AlignedFrame::SetFrameSize(int sz, bool refresh)
{
	framesize = sz;
	if (refresh) RefreshParentLayout();
	return *this;
}

void AlignedFrame::Fix(Size& sz)
{
	if(IsVert())
		Swap(sz.cx, sz.cy);
}

void AlignedFrame::Fix(Point& p)
{
	if(IsVert())
		Swap(p.x, p.y);
}

Size AlignedFrame::Fixed(const Size& sz)
{
	return IsVert() ? Size(sz.cy, sz.cx) : Size(sz.cx, sz.cy);
}

Point AlignedFrame::Fixed(const Point& p)
{
	return IsVert() ? Point(p.y, p.x) : Point(p.x, p.y);
}

// TabScrollBar
TabScrollBar::TabScrollBar()
{
	Clear();
}

void TabScrollBar::Clear()
{
	total = 0;
	pos = 0;
	ps = 0;
	start_pos = 0;
	new_pos = 0;
	old_pos = 0;
	sz.Clear();
	ready = false;
}

void TabScrollBar::UpdatePos(bool update)
{
	sz = GetSize();
	Fix(sz);
	if(total <= 0 || sz.cx <= 0)
		cs = ics = 0;
	else
	{
		cs = sz.cx / ((double) total + 0.5);
		ics = total / ((double) sz.cx);
	}
	size = sz.cx * cs;
	if(update)
		pos = new_pos - start_pos;
	if(pos < 0)
		pos = 0;
	else if(pos + size > sz.cx)
		pos = sz.cx - size;

	ps = total > sz.cx ? pos * ics : 0;
}

void TabScrollBar::Paint(Draw &w)
{
	if(!ready)
	{
		UpdatePos();
		ready = true;
	}
	Size rsz = GetSize();
	#ifdef TABBAR_DEBUG
	w.DrawRect(rsz, Red);
	#else
	w.DrawRect(rsz, White);
	#endif
	Point p;
	
	if(total > sz.cx) {
		p = Point(ffloor(pos), 1);
		rsz = Size(fceil(size), (IsVert() ? rsz.cx : rsz.cy) - 2);
	}
	else {
		p = Point(0, 1);
		rsz = Size(sz.cx, (IsVert() ? rsz.cx : rsz.cy) - 2);
	}
	Fix(p);
	Fix(rsz);
	w.DrawRect(p.x, p.y, rsz.cx, rsz.cy, Blue);
}

void TabScrollBar::Layout()
{
	UpdatePos(false);
}

void TabScrollBar::LeftDown(Point p, dword keyflags)
{
	SetCapture();
	Fix(p);
	old_pos = new_pos = p.x;
	if(p.x < pos || p.x > pos + size)
		start_pos = size / 2;
	else
		start_pos = tabs(p.x - pos);
	UpdatePos();
	UpdateActionRefresh();
}

void TabScrollBar::LeftUp(Point p, dword keyflags)
{
	ReleaseCapture();
	Fix(p);
	old_pos = p.x;
}

void TabScrollBar::MouseMove(Point p, dword keyflags)
{
	if(!HasCapture())
		return;

	Fix(p);
	new_pos = p.x;
	UpdatePos();
	UpdateActionRefresh();
}

void TabScrollBar::MouseWheel(Point p, int zdelta, dword keyflags)
{
	AddPos(-zdelta / 4, true);
	UpdateActionRefresh();
}

int TabScrollBar::GetPos() const
{
	return ffloor(ps);
}

void TabScrollBar::SetPos(int p, bool dontscale)
{
	pos = total > 0 ? dontscale ? p : iscale(p, sz.cx, total) : 0;
	UpdatePos(false);
	Refresh();
}

void TabScrollBar::AddPos(int p, bool dontscale)
{
	pos += total > 0 ? dontscale ? p : iscale(p, sz.cx, total) : 0;
	UpdatePos(false);
	Refresh();
}

int TabScrollBar::GetTotal() const
{
	return total;
}

void TabScrollBar::SetTotal(int t)
{
	bool upd = total < t;
	total = t;
	UpdatePos(upd);
	Refresh();
}

void TabScrollBar::AddTotal(int t)
{
	sz = GetSize();
	Fix(sz);
	total += t;
	if(total <= 0 || sz.cx <= 0)
		cs = ics = 0;
	else
		cs = sz.cx / ((double) total + 0.5);
	size = sz.cx * cs;
	ps = min(ps, (double)(total-sz.cx));
	pos = (int)(ps * cs);
	old_pos = new_pos = (int)(pos - start_pos);
	
	Refresh();
}

void TabScrollBar::GoEnd()
{
	pos = total;
	UpdatePos(false);
	Refresh();
}

void TabScrollBar::GoBegin()
{
	pos = 0;
	UpdatePos(false);
	Refresh();
}

void TabScrollBar::Set(const TabScrollBar& t)
{
	total = t.total;
	pos = t.pos;
	ps = t.ps;
	Refresh();
}

bool TabScrollBar::IsScrollable() const
{
	// Note: sz already 'fixed'
	return total > sz.cx && sz.cx > 0;
}

// Group

void TabBar::Group::Serialize(Stream& s)
{
	s % name % active % count % first % last;
}


// TabBar

TabBar::TabBar()
{
	Clear();

	id = 0;
	display = NULL;
	crosses = true;
	crosses_side = RIGHT;
	grouping = true;
	isctrl = false;
	isdrag = false;
	inactivedisabled = false;
	autoscrollhide = true;
	stacking = false;
	groupseps = false;
	allownullcursor = false;
	icons = true;
	mintabcount = 1;
	scrollbar_sz = DPI(TB_SBHEIGHT);
	allowreorder = true;
	style = &StyleDefault();
	
	// Init sorting
	groupsort = false;
	tabsort = false;
	stacksort = true;
	contextmenu = true;
	keysorter_inst.vo = &Single<StdValueOrder>();
	valuesorter_inst.vo = &Single<StdValueOrder>();
	stacksorter_inst.vo = &Single<StdValueOrder>();
	tabsorter = &keysorter_inst;
	groupsorter = &Single<TabGroupSort>();
	stacksorter = &stacksorter_inst;

	SetAlign(TOP);
	SetFrameSize(GetHeight(false));
	BackPaint();
	
	ConfirmClose = [](Value) { return true; };
	ConfirmCloseAll = []() { return true; };
	ConfirmCloseSome = [](ValueArray) { return true; };
}

int TabBar::GetLR( int c, int jd )
{
	int new_tab;
	if ( jd == JumpDirLeft )
		new_tab = GetPrev( c );
	else
		new_tab = GetNext( c );
	return new_tab;
}

int TabBar::GetTabStackLR( int jd )
{
	int nt = -1;
	if ( HasCursor() ) {
		int c = GetCursor();
    
		if ( IsStacking() ) {
			int c_stack = tabs[ c ].stack;
			if ( jd == JumpDirLeft )
				nt = FindStackTail( c_stack );
			else
				nt = c + 1;
		}
	}
	return nt;
}

int TabBar::GetTabLR( int jd )
{
	int lt = -1;
	bool js_NeedReset = true;

	if ( HasCursor() ) {
	    int c = GetCursor();
	    int tc = GetCount();
    
		if ( IsStacking() ) {
			int c_stack = tabs[ c ].stack;

			if ( jd == JumpDirRight && jump_stack.IsReset() ) {
        
				int c_stack_count = GetStackCount( c_stack );

		        if ( c_stack_count > 1 ) {
					jump_stack.Activate( c_stack_count - 1, jd );
					js_NeedReset = false;
				}
			}
      
			if ( jump_stack.IsReset() || ( jump_stack.Rest == 0 ) ) {
				lt = GetLR( c, jd );
				if ( ( lt >= 0 ) && ( lt < tc ) ) {
        
					int lt_stack = tabs[ lt ].stack;
					int lt_stack_count = GetStackCount( lt_stack );
					
					if ( lt_stack_count > 1 ) {
						lt = FindStackHead( lt_stack );
						jump_stack.Activate( lt_stack_count - 1, jd );
						js_NeedReset = false;
					}
				}
			} else {
				if ( jump_stack.Rest > 0 ) {
					if ( jd == jump_stack.jump_direct ) {
						lt = c + 1;
						--jump_stack.Rest;
						js_NeedReset = false;
					} else  {
						if ( jump_stack.IsFull() ) {
							lt = GetLR( c, jd );
						} else {
							lt = FindStackTail( c_stack );
							++jump_stack.Rest;
							js_NeedReset = false;
						}
					}
				}
			}
		} else /* !IsStacking() */ {
			lt = GetLR( c, jd );
		}
	}

	if ( js_NeedReset )
		jump_stack.Reset();
	return lt;
}

void TabBar::Set(const TabBar& t)
{
	CopyBaseSettings(t);
	
	id = t.id;
	
	tabs.Clear();
	tabs <<= t.tabs;
	groups.Clear();
	groups <<= t.groups;
	separators.Clear();
	separators <<= t.separators;
	
	group = t.group;
	stackcount = t.stackcount;
	
	active = t.active;
	cross = -1;
	highlight = -1;
	target = -1;
	
	mouse.Clear();
	oldp.Clear();
	
	sc.Set(t.sc);
	SetAlign(t.GetAlign());
}

int TabBar::GetNextId()
{
	return id++;
}

void TabBar::ContextMenu(Bar& bar)
{
	int ii = GetHighlight(); // Need copy to freeze it, [=] copies 'this' and thus reference to highlight
	if (GetCursor() >= 0 && ii >= 0 && !IsCancelClose(ii))
		bar.Add(tabs.GetCount() > mintabcount, t_("Close"), [=] {
			if (!CancelClose(tabs[ii].key)) {
				WhenClose(tabs[ii].key);
				TabClosed(tabs[ii].key);
				tabs.Remove(ii);
				MakeGroups();
				Repos();
				SetCursor(-1);
			}
		});
	if (ii >= 0 && !IsCancelCloseAll(ii))
		bar.Add(t_("Close others"), [=] { CloseAll(ii); });
    if (ii >= 0 && ii < GetCount() - 1 && !IsCancelCloseAll(-1, ii + 1))
		bar.Add(t_("Close right tabs"), [=] { CloseAll(-1, ii + 1); });
	if (mintabcount <= 0 && !IsCancelCloseAll(-1))
		bar.Add(t_("Close all"), [=] { CloseAll(-1); });
	bar.Add(false, t_("Dock"), [=] {});
	if(ii >= 1)
		bar.Sub(t_("Move left before"), [=](Bar& bar) {
			for(int i = 0; i < ii; i++)
			   bar.Add(tabs[i].value.ToString(), [=] {
				tabs.Move(ii,i);
				SetCursor0(i);
				Repos();
				Refresh();
			});;
		});
	if(tabs.GetCount() - 2 >= ii && ii >= 0)
		bar.Sub(t_("Move right after"),  [=](Bar& bar)  {
			for(int i = ii+1; i < tabs.GetCount(); i++)
				bar.Add(tabs[i].value.ToString(),[=] {
				tabs.Move(ii,i+1);
				SetCursor0(i);
				Repos();
				Refresh();
			});
		});
	if(grouping && ii >= 0) {
		if(group > 0)
			bar.Add(t_("Close group"), THISBACK(CloseGroup));
		bar.Separator();
		int cnt = groups.GetCount();
		for(int i = 0; i < cnt; i++)
		{
			String name = Format("%s (%d)", groups[i].name, groups[i].count);
			Bar::Item &it = bar.Add(name, THISBACK1(GoGrouping, i));
			if(i == group)
				it.Image(TabBarImg::CHK);
			if(i == 0 && cnt > 1)
				bar.Separator();
		}
	}
}

void TabBar::CloseAll(int exception, int last_closed)
{
	ValueArray vv;
	for(int i = last_closed; i < tabs.GetCount(); i++)
		if(i != exception)
			vv.Add(tabs[i].key);
		
	if(exception < 0 && last_closed == 0 ? !ConfirmCloseAll() || CancelCloseAll()
	                                     : !ConfirmCloseSome(vv) || CancelCloseSome(vv))
		return;
	
	WhenCloseSome(vv);
	if(exception < 0 && last_closed == 0)
		WhenCloseAll();

	for(int i = tabs.GetCount() - 1; i >= last_closed; i--)
		if(i != exception) {
			if (!CancelClose(tabs[i].key) && ConfirmClose(tabs[i].key)) {
				WhenClose(tabs[i].key);
				TabClosed(tabs[i].key);
				tabs.Remove(i);
			}
		}

	SetCursor(last_closed ? last_closed - 1 : 0);
	
	MakeGroups();
	Repos();
	Refresh();
}

void TabBar::CloseGroup()
{
	if(group <= 0)
		return;
	Value v = GetData();
	DoCloseGroup(group);
	SetData(v);
}

bool TabBar::IsCancelClose(int id)
{
	if (CancelCloseAll())
		return true;
	
	if (CancelCloseSome) {
		ValueArray vv;
		vv.Add(tabs[id].key);
		if (CancelCloseSome(vv))
			return true;
	}
	
	if (CancelClose(tabs[id].key))
		return true;
	return false;
}

bool TabBar::IsCancelCloseAll(int exception, int last_closed)
{
	ValueArray vv;
	for(int i = last_closed; i < tabs.GetCount(); i++)
		if(i != exception)
			vv.Add(tabs[i].key);
		
	if(exception < 0 && last_closed == 0 ? CancelCloseAll() : CancelCloseSome(vv))
		return true;
	
	if (CancelClose) {
		for(int i = tabs.GetCount() - 1; i >= last_closed; i--)
			if(i != exception) {
				if (!CancelClose(tabs[i].key))
					return false;
			}
		return true;
	}
	return false;
}

TabBar::Tab::Tab()
{
	id = -1;
	stack = -1;
	visible = true;
	itn = 0;
	items.SetCount(5);

	pos = cross_pos = tab_pos = Point(0, 0);
	cross_size = size = tab_size = Size(0, 0);
}

void TabBar::Tab::Set(const Tab& t)
{
	id = t.id;
	
	img = t.img;
	col = t.col;
	key = t.key;
	value = t.value;
	group = t.group;
	
	stackid = t.stackid;
	stack = t.stack;

	visible = t.visible;

	pos = t.pos;
	size = t.size;
	
	cross_pos = t.cross_pos;
	cross_size = t.cross_size;
	
	tab_pos = t.tab_pos;
	tab_size = t.tab_size;
	
	items <<= t.items;
}

void TabBar::Tab::Serialize(Stream& s)
{
	s % id % key % value % group % stackid % stack % visible;
}

bool TabBar::Tab::HasMouse(const Point& p) const
{
	if(!visible)
		return false;
	
	return p.x >= tab_pos.x && p.x < tab_pos.x + tab_size.cx &&
	       p.y >= tab_pos.y && p.y < tab_pos.y + tab_size.cy;
}

bool TabBar::Tab::HasMouseCross(const Point& p) const
{
	if(!visible)
		return false;

	return p.x >= cross_pos.x && p.x < cross_pos.x + cross_size.cx &&
	       p.y >= cross_pos.y && p.y < cross_pos.y + cross_size.cy;
}

int TabBar::FindGroup(const String& g) const
{
	for(int i = 0; i < groups.GetCount(); i++)
		if(groups[i].name == g)
			return i;
	return -1;
}

void TabBar::DoStacking()
{
	Value v = GetData();
		
	// Reset stack info
	for (int i = 0; i < tabs.GetCount(); i++) {
		Tab &t = tabs[i];
		t.stack = -1;
		t.stackid = GetStackId(t);
	}
	// Create stacks
	Vector< Array<Tab> > tstack;
	for (int i = 0; i < tabs.GetCount(); i++) {
		Tab &ti = tabs[i];
		if (ti.stack < 0) {
			ti.stack = tstack.GetCount();
			Array<Tab> &ttabs = tstack.Add();
			ttabs.Add(ti);
			for (int j = i + 1; j < tabs.GetCount(); j++)	{
				Tab &tj = tabs[j];
				if (tj.stack < 0 && tj.stackid == ti.stackid && (!grouping || tj.group == ti.group)) {
					tj.stack = ti.stack;
					ttabs.Add(tj);
				}
			}
		}
	}
	stackcount = tstack.GetCount();
	// Recombine
	tabs.SetCount(0);
	for (int i = 0; i < tstack.GetCount(); i++) {
		if (stacksort)
			StableSort(tstack[i], *stacksorter);
		tabs.AppendPick(pick(tstack[i]));
	}
	highlight = -1;
	SetData(v);
	MakeGroups();
	Repos();
}

void TabBar::DoUnstacking()
{
	stackcount = 0;
	for (int i = 0; i < tabs.GetCount(); i++)
		tabs[i].stack = -1;
	highlight = -1;
	MakeGroups();
	Repos();
	if (HasCursor())
		SetCursor(-1);
	else
		Refresh();
}

void TabBar::SortStack(int stackix)
{
	if (!stacksort) return;
	
	int head = FindStackHead(stackix);
	int tail = head;
	while (tail < tabs.GetCount() && tabs[tail].stack == stackix)
		++tail;
	SortStack(stackix, head, tail-1);
}

void TabBar::SortStack(int stackix, int head, int tail)
{
	if (!stacksort) return;
	
	int headid = tabs[head].id;
	StableSort(SubRange(tabs, head, tail - head).Write(), *stacksorter);
	while (tabs[head].id != headid)
		CycleTabStack(head, stackix);
}

void TabBar::MakeGroups()
{
	for(const auto& tab : tabs)
		if(FindGroup(tab.group) < 0)
			NewGroup(tab.group);
	
	groups[0].count = tabs.GetCount();
	groups[0].first = 0;
	groups[0].last = tabs.GetCount() - 1;

	if (groupsort)
		StableSort(tabs, *groupsorter);

	for(int i = 1; i < groups.GetCount(); i++)
	{
		groups[i].count = 0;
		groups[i].first = 10000000;
		groups[i].last = 0;
	}

	for(int i = 0; i < tabs.GetCount(); i++)
	{
		Tab &tab = tabs[i];
		int n = FindGroup(tab.group);
		ASSERT(n >= 0);
		if (n > 0) {
			if(groups[n].active < 0)
				groups[n].active = tab.id;
			groups[n].count++;
			groups[n].last = i;
	
			if(i < groups[n].first)
				groups[n].first = i;
			if(i > groups[n].last)
				groups[n].last = i;
		}
	}
	
	int cnt = groups.GetCount() - 1;
	for(int i = cnt; i > 0; i--)
		if(groups[i].count == 0)
			groups.Remove(i);
		
	if(group > groups.GetCount() - 1 && group > 0)
		group--;
}

void TabBar::GoGrouping(int n)
{
	Value c = GetData();
	group = n;
	String g = GetGroupName();
	for(int i = 0; i < tabs.GetCount(); i++)
		if(tabs[i].group == g)
			c = GetKey(i);
	Repos();
	SyncScrollBar();
	SetData(c);
	Refresh();
}

void TabBar::DoGrouping(int n)
{
	group = n;
	Repos();
	SyncScrollBar();
}


void TabBar::DoCloseGroup(int n)
{
	int cnt = groups.GetCount();
	if(cnt <= 0)
		return;

	String groupName = groups[n].name;
	
	/*
		do WhenCloseSome()/CancelCloseSome() checking
		before WhenClose()/CancelClose() stuff
		(that code must be reviewed anyways...)
		In order to leave existing code as it is, following
		changes have effect *ONLY* if WhenCloseSome()/CancelCloseSome()
		callbacks are used, otherwise previous path is taken.
		I think we should anyways review some parts of it later
	*/
	
	if(WhenCloseSome || CancelCloseSome)
	{
		ValueArray vv;
		int nTabs = 0;
		for(int i = 0; i < tabs.GetCount(); i++)
			if(groupName == tabs[i].group) {
				vv.Add(tabs[i].key);
				nTabs++;
			}
		// at first, we check for CancelCloseSome()
		if(vv.GetCount() && !CancelCloseSome(vv) && ConfirmCloseSome(vv)) {
			// we didn't cancel globally, now we check CancelClose()
			// for each tab -- group gets removed ONLY if ALL of
			// group tabs are closed
			vv.Clear();
			Vector<int>vi;
			for(int i = tabs.GetCount() - 1; i >= 0; i--) {
				if(groupName == tabs[i].group && tabs.GetCount() > 1) {
					Value v = tabs[i].key;
					if(!CancelClose(v) && ConfirmClose(v))
					{
						nTabs--;
						WhenClose(v);
						// record keys and indexes of tabs to remove
						vv << v;
						vi << i;
					}
				}
			}
			// and now do the true removal
			WhenCloseSome(vv);
			for(int i = 0; i < vv.GetCount(); i++)
			{
				if (!CancelClose(vv[i]) && ConfirmClose(vv[i])) {
					WhenClose(vv[i]);
					TabClosed(vv[i]);
					tabs.Remove(vi[i]);
				}
			}
			// remove group if all of its tabs get closed
			if(!nTabs) {
				if(cnt == n)
					group--;
				if(cnt > 1)
					groups.Remove(n);
			}
			MakeGroups();
			Repos();
			SetCursor(-1);
		}
		return;
	}

	// previous code path, taken if WhenCancelSome()/WhenCloseSome()
	for(int i = tabs.GetCount() - 1; i >= 0; i--)
	{
		if(groupName == tabs[i].group && tabs.GetCount() > 1) {
			Value v = tabs[i].value; // should be key ??
			if (!CancelClose(v) && ConfirmClose(v)) {
				WhenClose(v);
				TabClosed(v);
				tabs.Remove(i);
			}
		}
	}

	if (cnt == n)
		group--;

	if(cnt > 1) // what if CancelClose suppressed some tab closing ?
		groups.Remove(n);
	MakeGroups();
	Repos();
	SetCursor(-1);
}

void TabBar::NewGroup(const String &name)
{
	Group &g = groups.Add();
	g.name = name;
	g.count = 0;
	g.first = 10000000;
	g.last = 0;
	g.active = -1;
}

Image TabBar::AlignImage(int align, const Image& img)
{
	switch(align) {
		case AlignedFrame::LEFT:
			return RotateAntiClockwise(img);
		case AlignedFrame::RIGHT:
			return RotateClockwise(img);
		case AlignedFrame::BOTTOM:
			return MirrorVert(img);
		default:
			return img;
	}
}

void TabBar::TabItem::Clear()
{
	text.Clear();
	ink = Null;
	img = Null;
	side = LEFT;
	clickable = false;
	cross = false;
	stacked_tab = -1;
}

TabBar::TabItem& TabBar::Tab::AddItem()
{
	if(itn < items.GetCount())
	{
		TabItem& ti = items[itn++];
		ti.Clear();
		return ti;
	}
	else
	{
		++itn;
		return items.Add();
	}
}

void TabBar::Tab::Clear()
{
	itn = 0;
}

TabBar::TabItem& TabBar::Tab::AddImage(const Image& img, int side)
{
	TabItem& ti = AddItem();
	ti.img = img;
	ti.size = img.GetSize();
	ti.side = side;
	return ti;
}

TabBar::TabItem& TabBar::Tab::AddValue(const Value& q, const Font& font, const Color& ink)
{
	TabItem& ti = AddItem();
	
	ti.font = font;
	ti.ink = ink;
	
	if(IsType<AttrText>(q)) {
		const AttrText& t = ValueTo<AttrText>(q);
		ti.text = t.text;
		if(!IsNull(t.font))
			ti.font = t.font;
		
		if(!IsNull(t.ink))
			ti.ink = t.ink;
	}
	else
		ti.text = IsString(q) ? q : StdConvert().Format(q);
	
	ti.size = GetTextSize(ti.text, ti.font);
	return ti;
}

TabBar::TabItem& TabBar::Tab::AddText(const WString& s, const Font& font, const Color& ink)
{
	TabItem& ti = AddItem();

	ti.font = font;
	ti.ink = ink;
	ti.text = s;
	ti.size = GetTextSize(ti.text, ti.font);
	return ti;
}

TabBar::TabItem& TabBar::Tab::AddSpace(int space, int side)
{
	TabItem& ti = AddItem();
	
	ti.size.cx = space;
	ti.size.cy = 0;
	ti.side = side;
	return ti;
}

void TabBar::ComposeTab(Tab& tab, const Font &font, Color ink, int style)
{
	if(PaintIcons() && tab.HasIcon())
	{
		tab.AddImage(tab.img);
		tab.AddSpace(DPI(TB_SPACEICON));
	}
	tab.AddValue(tab.value, font, ink).Clickable();
}

void TabBar::ComposeStackedTab(Tab& tab, const Tab& stacked_tab, const Font& font, Color ink, int style)
{
	tab.AddImage(stacked_tab.img);
	tab.AddText("|...", font, ink);
}

int TabBar::GetTextAngle()
{
	return AlignedFrame::IsVert() ? (GetAlign() == LEFT ? 900 : 2700) : 0;
}

Point TabBar::GetTextPosition(int align, const Rect& r, int cy, int space) const
{
	Point p;
	
	if(align == LEFT)
	{
		p.y = r.bottom - space;
		p.x = r.left + (r.GetWidth() - cy) / 2;
	}
	else if(align == RIGHT)
	{
		p.y = r.top + space;
		p.x = r.right - (r.GetWidth() - cy) / 2;
	}
	else
	{
		p.x = r.left + space;
		p.y = r.top + (r.GetHeight() - cy) / 2;
	}
	return p;
}

Point TabBar::GetImagePosition(int align, const Rect& r, int cx, int cy, int space, int side, int offset) const
{
	Point p;

	if (align == LEFT)
	{
		p.x = r.left + (r.GetWidth() - cy) / 2 + offset;
		p.y = side == LEFT ? r.bottom - space - cx : r.top + space;
	}
	else if (align == RIGHT)
	{
		p.x = r.right - (r.GetWidth() + cy) / 2 - offset;
		p.y = side == LEFT ? r.top + space : r.bottom - space - cx;
	}
	else if (align == TOP)
	{
		p.x = side == LEFT ? r.left + space : r.right - cx - space;
		p.y = r.top + (r.GetHeight() - cy) / 2 + offset;
	}
	else if (align == BOTTOM)
	{
		p.x = side == LEFT ? r.left + space : r.right - cx - space;
		p.y = r.bottom - (r.GetHeight() + cy) / 2 - offset;
	}
	return p;
}

void TabBar::PaintTabItems(Tab& t, Draw &w, const Rect& rn, int align)
{
	int pos_left = DPI(TB_MARGIN);
	int pos_right = (IsVert() ? rn.GetHeight() : rn.GetWidth()) - DPI(TB_MARGIN);
	
	for(int i = 0; i < t.itn; i++)
	{
		const TabItem& ti = t.items[i];
		
		Point p(0, 0);
		int pos = ti.side == LEFT ? pos_left : pos_right - ti.size.cx;
		
		if(!IsNull(ti.img))
		{
			p = GetImagePosition(align, rn, ti.size.cx, ti.size.cy, pos, LEFT);
			w.DrawImage(p.x, p.y, IsVert() ? AlignImage(align, ti.img) : ti.img);
		}
		
		if(!IsNull(ti.text))
		{
			p = GetTextPosition(align, rn, ti.size.cy, pos);
			w.DrawText(p.x, p.y, GetTextAngle(), ti.text, ti.font, ti.ink);
		}
		
		if(ti.cross)
		{
			t.cross_size = ti.size;
			t.cross_pos = p;
		}
		
		if(ti.stacked_tab >= 0 && ti.clickable)
		{
			Tab& st = tabs[ti.stacked_tab];
			
			if(align == RIGHT)
			{
				st.tab_pos = Point(rn.left, rn.top + pos);
				st.tab_size = Size(rn.GetWidth(), ti.size.cx);
			}
			else if(align == LEFT)
			{
				st.tab_pos = Point(rn.left, rn.bottom - pos - ti.size.cx);
				st.tab_size = Size(rn.GetWidth(), ti.size.cx);
			}
			else
			{
				st.tab_pos = Point(rn.left + pos, rn.top);
				st.tab_size = Size(ti.size.cx, rn.GetHeight());
			}
			
			#ifdef TABBAR_DEBUG
			DrawFrame(w, Rect(st.tab_pos, st.tab_size), Red);
			#endif
		}
				
		if(ti.side == LEFT)
			pos_left += ti.size.cx;
		else
			pos_right -= ti.size.cx;
	}
}

void TabBar::PaintTab(Draw &w, const Size &sz, int n, bool enable, bool dragsample)
{
	TabBar::Tab &t = tabs[n];
	const Style& s = *style;
	int align = GetAlign();
	int cnt = dragsample ? 1 : tabs.GetCount();
	
	bool ac = (n == active && enable);
	bool hl = (n == highlight && enable) || (stacking && highlight >= 0 && tabs[highlight].stack == t.stack);

	int c = align == LEFT ? cnt - n : n;
	int lx = n > 0 ? s.extendleft : 0;
	int x = t.pos.x - sc.GetPos() - lx + s.margin;
	
	int dy = -s.sel.top * ac;
	int sel = s.sel.top;

	int df = 0;
	
	if (IsBR())
	{
		dy = -dy;
		sel = s.sel.bottom;
		df = Fixed(sz).cy;
	}

	Size  sa = Size(t.size.cx + lx + s.sel.right + s.sel.left, t.size.cy + s.sel.bottom);
	Point pa = Point(x - s.sel.left, IsBR() ? df - sa.cy : 0);

	Size  sn = Size(t.size.cx + lx, t.size.cy - s.sel.top);
	Point pn = Point(x, IsBR() ? df - sn.cy - s.sel.top : s.sel.top);

	Rect ra(Fixed(pa), Fixed(sa));
	Rect rn(Fixed(pn), Fixed(sn));
	
	t.tab_pos = (ac ? ra : rn).TopLeft();
	t.tab_size = (ac ? ra : rn).GetSize();

	int ndx = !enable ? CTRL_DISABLED :
		       ac ? CTRL_PRESSED :
		       hl ? CTRL_HOT : CTRL_NORMAL;

	struct TabMaker : ImageMaker {
		int    c;
		int    cnt;
		int    ndx;
		int    align;
		Size   size;
		const Style *s;
		
		String Key() const override {
			String h;
			RawCat(h, c);
			RawCat(h, cnt);
			RawCat(h, ndx);
			RawCat(h, align);
			RawCat(h, size);
			RawCat(h, s);
			return h;
		}

		Image Make() const override {
			const Value& sv = (cnt == 1 ? s->both : c == 0 ? s->first : c == cnt - 1 ? s->last : s->normal)[ndx];
			Size isz = size;
			if(align == AlignedFrame::LEFT || align == AlignedFrame::RIGHT)
				Swap(isz.cx, isz.cy);
		
			Image h[2];
			for(int i = 0; i < 2; i++) {
				ImageDraw w(isz.cx, isz.cy);
				w.DrawRect(isz, i ? Black() : White());
				ChPaint(w, isz, sv);
				h[i] = w;
			}
			return AlignImage(align, RecreateAlpha(h[0], h[1]));
		}
	};
	
	TabMaker tm;
	tm.c = c;
	tm.cnt = cnt;
	tm.ndx = ndx;
	tm.align = align;
	tm.size = t.tab_size;
	tm.s = style;

	Image img = MakeImage(tm);
	
	if(!IsNull(t.col))
	{
		img = Colorize(img, t.col);
	}

	if(dragsample)
	{
		w.DrawImage(Rect(Point(0, 0), t.tab_size), img);
		rn = Rect(Fixed(Point(s.sel.left * ac, sel * ac + dy)), Fixed(sn));
	}
	else
	{
		w.DrawImage(Rect(t.tab_pos, t.tab_size), img);
		rn = Rect(Fixed(Point(pn.x, pn.y + dy)), Fixed(sn));
	}
	
	#ifdef TABBAR_DEBUG
	DrawFrame(w, rn, Green);
	#endif
		
	if (display)
		display->Paint(w, rn, t.value, s.text_color[ndx], SColorDisabled(), ndx);

	t.Clear();

	if(crosses && cnt > mintabcount && !dragsample && !IsCancelClose(n)) {
		TabItem& ti = t.AddItem();
		ti.img = s.crosses[cross == n ? 2 : ac || hl ? 1 : 0];
		ti.side = crosses_side;
		ti.cross = true;
		ti.size = s.crosses[0].GetSize();
		t.AddSpace(DPI(3), crosses_side);
	}
	
	ComposeTab(t, s.font, s.text_color[ndx], ndx);
	
	if (stacking) {
		int ix = n + 1;

		while (ix < tabs.GetCount() && tabs[ix].stack == t.stack) {
			Tab &q = tabs[ix];
			int ndx = !enable ? CTRL_DISABLED :
					   highlight == ix ? CTRL_HOT : CTRL_NORMAL;

			int sn = t.itn;
			ComposeStackedTab(t, q, s.font, s.text_color[ndx], ndx);
			if(t.itn > sn)
				for(; sn < t.itn; sn++)
					t.items[sn].stacked_tab = ix;
			
			ix++;
		}
	}
	
	PaintTabItems(t, w, rn, align);
}

void TabBar::Paint(Draw &w)
{
	int align = GetAlign();
	const Style &st = *style;
	Size ctrlsz = GetSize();
	Size sz = GetBarSize(ctrlsz);
	
	if (align == BOTTOM || align == RIGHT)
		w.Offset(ctrlsz.cx - sz.cx, ctrlsz.cy - sz.cy);
	
	#ifdef TABBAR_DEBUG
	w.DrawRect(sz, Yellow);
	#else
	w.DrawRect(sz, SColorFace());
	#endif

	if(IsVert())
		w.DrawRect(align == LEFT ? sz.cx - 1 : 0, 0, 1, sz.cy, Blend(SColorDkShadow, SColorShadow));
	else
		w.DrawRect(0, align == TOP ? sz.cy - 1 : 0, sz.cx, 1, Blend(SColorDkShadow, SColorShadow));

	if (!tabs.GetCount()) {
		if (align == BOTTOM || align == RIGHT)
			w.End();
		return;
	}
	
	int limt = sc.GetPos() + (IsVert() ? sz.cy : sz.cx);
	int first = 0;
	int last = tabs.GetCount() - 1;
	// Find first visible tab
	for(int i = 0; i < tabs.GetCount(); i++) {
		Tab &tab = tabs[i];
		if (tab.pos.x + tab.size.cx > sc.GetPos()) {
			first = i;
			break;
		}
	}
	// Find last visible tab
	for(int i = first + 1; i < tabs.GetCount(); i++) {
		if (tabs[i].visible && tabs[i].pos.x > limt) {
			last = i;
			break;
		}
	}
	// Draw active group
	for (int i = first; i <= last; i++) {
		if(tabs[i].visible && i != active)
			PaintTab(w, sz, i, IsEnabled());
	}
	// Clear tab_size for non-visible tabs to prevent mouse handling bugs
	for (int i = 0; i < first; i++)
		tabs[i].tab_size = Size(0, 0);
	for (int i = last + 1; i < tabs.GetCount(); i++)
		tabs[i].tab_size = Size(0, 0);
	// Draw inactive groups
	if (inactivedisabled)
		for (int i = first; i <= last; i++) {
			if(!tabs[i].visible && i != active && (!stacking || IsStackHead(i)))
				PaintTab(w, sz, i, !IsEnabled());
		}
			
	// Draw selected tab
	if(active >= first && active <= last)
		PaintTab(w, sz, active, true);
	
	// Separators
	if (grouping && groupseps) {
		int cy = IsVert() ? sz.cx : sz.cy;
		for (int i = 0; i < separators.GetCount(); i++) {
			int x = separators[i];
			if (x > sc.GetPos() && x < limt) {
				// Paint separator
				ChPaint(w, Rect(Fixed(Point(x - sc.GetPos() + GetStyle().sel.left, 0)),
					Fixed(Size(DPI(TB_SPACE) - GetStyle().sel.left, cy-1))),
					st.group_separators[IsVert() ? 1 : 0]);
			}
		}
	}
	
	// Draw drag highlights
	if(target >= 0)
	{
		// Draw target marker
		int drag = isctrl ? highlight : active;
		if(target != drag && target != GetNext(drag, true))
		{
			last = GetLast();
			first = GetFirst();
			int x = (target == last + 1 ? tabs[last].Right() : tabs[target].pos.x)
			        - sc.GetPos() - (target <= first ? 1 : 2)
			        + st.margin - (target > 0 ? st.extendleft : 0);

			if (IsHorz())
				DrawVertDrop(w, x + 1, 0, sz.cy);
			else
				DrawHorzDrop(w, 0, x + 1, sz.cx);
		}
		// Draw transparent drag image
		Point mouse = GetMousePos() - GetScreenRect().TopLeft();
		Size isz = dragtab.GetSize();
		int p = 0;
		int sep = DPI(TB_SBSEPARATOR) * sc.IsVisible();
		
		int top = drag == active ? st.sel.bottom : st.sel.top;
		if (align == BOTTOM || align == RIGHT)
			p = int(drag == active) * -top + sep;
		else
			p = int(drag != active) * top;
		
		if (IsHorz())
			w.DrawImage(mouse.x - isz.cx / 2, p, isz.cx, isz.cy, dragtab);
		else
			w.DrawImage(p, mouse.y - isz.cy / 2, isz.cx, isz.cy, dragtab);
	}
	
	if (align == BOTTOM || align == RIGHT)
		w.End();

	// If not in a frame fill any spare area
	if (!InFrame())
		w.DrawRect(GetClientArea(), SColorFace());
}

Image TabBar::GetDragSample()
{
	int h = drag_highlight;
	if(stacking)
		h = FindStackHead(tabs[h].stack);
	return GetDragSample(h);
}

Image TabBar::GetDragSample(int n)
{
	if (n < 0) return Image();
	Tab &t = tabs[n];

	Size tsz(t.tab_size);
	ImageDraw iw(tsz);
	iw.DrawRect(tsz, SColorFace()); //this need to be fixed - if inactive tab is dragged gray edges are visible
	
	PaintTab(iw, tsz, n, true, true);
	
	Image img = iw;
	ImageBuffer ib(img);
	Unmultiply(ib);
	RGBA *s = ~ib;
	RGBA *e = s + ib.GetLength();
	while(s < e) {
		s->a = 180;
		s++;
	}
	Premultiply(ib);
	return Image(ib);
}

void TabBar::Scroll()
{
	Refresh();
}

int TabBar::GetWidth(int n)
{
	return GetStdSize(tabs[n]).cx + GetExtraWidth(n);
}

int TabBar::GetExtraWidth(int n)
{
	return DPI(TB_MARGIN) * 2 + (DPI(TB_SPACE) + GetStyle().crosses[0].GetSize().cx) * (crosses && !IsCancelClose(n));
}

Size TabBar::GetStdSize(const Value &q)
{
	if (display)
		return display->GetStdSize(q);
	else if (q.GetType() == STRING_V || q.GetType() == WSTRING_V)
		return GetTextSize(WString(q), GetStyle().font);
	else
		return GetTextSize("A Tab", GetStyle().font);
}

Size TabBar::GetStackedSize(const Tab &t)
{
	if (!IsNull(t.img))
		return t.img.GetSize();
	return GetTextSize("...", GetStyle().font, 3);
}

Size TabBar::GetStdSize(const Tab &t)
{
	return (PaintIcons() && t.HasIcon()) ? (GetStdSize(t.value) + Size(DPI(TB_ICON + 2), 0)) : GetStdSize(t.value);
}

TabBar& TabBar::Add(const Value &value, Image icon, String group, bool make_active)
{
	return InsertKey(tabs.GetCount(), value, value, icon, group, make_active);
}

TabBar& TabBar::Insert(int ix, const Value &value, Image icon, String group, bool make_active)
{
	return InsertKey(tabs.GetCount(), value, value, icon, group, make_active);
}

TabBar& TabBar::AddKey(const Value &key, const Value &value, Image icon, String group, bool make_active)
{
	return InsertKey(tabs.GetCount(), key, value, icon, group, make_active);
}

TabBar& TabBar::InsertKey(int ix, const Value &key, const Value &value, Image icon, String group, bool make_active)
{
	int id = InsertKey0(ix, key, value, icon, group);
	
	SortTabs0();
	MakeGroups();
	Repos();
	active = -1;
	if (make_active || (!allownullcursor && active < 0))
		SetCursor((groupsort || stacking) ? FindId(id) : ( minmax(ix, 0, tabs.GetCount() - 1)));
	return *this;
}

int TabBar::InsertKey0(int ix, const Value &key, const Value &value, Image icon, String group)
{
	ASSERT(ix >= 0);
	int g = 0;
	if (!group.IsEmpty()) {
		g = FindGroup(group);
		if (g < 0) {
			NewGroup(group);
			g = groups.GetCount() - 1;
		}
	}
	
	group = groups[g].name;
	Tab t;
	t.value = value;
	t.key = key;
	t.img = icon;
	t.id = GetNextId();
	t.group = Nvl(TrimBoth(group), "Unnamed Group");
	if (stacking) {
		t.stackid = GetStackId(t);
		
		// Override index
		int tail = -1;
		for (int i = 0; i < tabs.GetCount(); i++) {
			if (tabs[i].stackid == t.stackid && (!grouping || tabs[i].group == t.group)) {
				tail = FindStackTail(tabs[i].stack);
				break;
			}
		}
		if (tail >= 0) {
			ix = tail+1;
			t.stack = tabs[tail].stack;
			tail++;
		}
		else {
			ix = (ix < tabs.GetCount()) ? FindStackHead(tabs[ix].stack) : ix;
			t.stack = stackcount++;
		}
		tabs.Insert(ix, t);
		if (tail >= 0)
			SortStack(t.stack, FindStackHead(t.stack), ix);
			
	}
	else
		tabs.Insert(ix, t);
	return t.id;
}

int TabBar::GetWidth() const
{
	if (!tabs.GetCount()) return 0;
	int ix = GetLast();
	const Style& s = StyleDefault();
	if (IsStackHead(ix))
		return tabs[ix].Right() + s.margin * 2;
	int stack = tabs[ix].stack;
	ix--;
	while (ix >= 0 && tabs[ix].stack == stack)
		ix--;
	return tabs[ix + 1].Right() + s.margin * 2;
	
}

int TabBar::GetHeight(bool scrollbar) const
{
	return TabBar::GetStyleHeight() + DPI(TB_SBSEPARATOR) * int(scrollbar);
}

int TabBar::GetStyleHeight() const
{
	const Style& s = GetStyle();
	return s.tabheight + s.sel.top;
}

void TabBar::Repos()
{
	if(!tabs.GetCount())
		return;

	String g = GetGroupName();
	int j;
	bool first = true;
	j = 0;
	separators.Clear();
	for(int i = 0; i < tabs.GetCount(); i++)
		j = TabPos(g, first, i, j, false);
	if (inactivedisabled)
		for(int i = 0; i < tabs.GetCount(); i++)
			if (!tabs[i].visible)
				j = TabPos(g, first, i, j, true);
	SyncScrollBar();
}

Size TabBar::GetBarSize(Size ctrlsz) const
{
	return IsVert() ? Size(GetFrameSize() - scrollbar_sz * int(sc.IsShown()), ctrlsz.cy)
			: Size(ctrlsz.cx, GetFrameSize() - scrollbar_sz * int(sc.IsShown()));
}

Rect TabBar::GetClientArea() const
{
	Rect rect = GetSize();
	switch (GetAlign()) {
		case TOP:
			rect.top += GetFrameSize();
			break;
		case BOTTOM:
			rect.bottom -= GetFrameSize();
			break;
		case LEFT:
			rect.left += GetFrameSize();
			break;
		case RIGHT:
			rect.right -= GetFrameSize();
			break;
	};
	return rect;
}

int TabBar::TabPos(const String &g, bool &first, int i, int j, bool inactive)
{
	bool ishead = IsStackHead(i);
	bool v = IsNull(g) ? true : g == tabs[i].group;
	Tab& t = tabs[i];

	if(ishead && (v || inactive))
	{
		t.visible = v;
		t.pos.y = 0;
		t.size.cy = GetStyleHeight();
				
		// Normal visible or inactive but greyed out tabs
		t.pos.x = first ? 0 : tabs[j].Right();
		
		// Separators
		if (groupseps && grouping && !first && t.group != tabs[j].group) {
			separators.Add(t.pos.x);
			t.pos.x += DPI(TB_SPACE);
		}
		
		int cx = GetStdSize(t).cx;

		// Stacked/shortened tabs
		if (stacking) {
			for(int n = i + 1; n < tabs.GetCount() && tabs[n].stack == t.stack; n++)
				cx += GetStackedSize(tabs[n]).cx;
		}
			
		t.size.cx = cx + GetExtraWidth(i);

		if (stacking) {
			for(int n = i + 1; n < tabs.GetCount() && tabs[n].stack == t.stack; n++) {
				Tab &q = tabs[n];
				q.visible = false;
				q.pos = t.pos;
				q.size = t.size;
			}
		}
		
		j = i;
		first = false;
	}
	else if (!(v || inactive)) {
		t.visible = false;
		t.pos.x = sc.GetTotal() + GetBarSize(GetSize()).cx;
	}
	return j;
}

void TabBar::ShowScrollbarFrame(bool b)
{
	SetFrameSize((b ? sc.GetFrameSize() : DPI(TB_SBSEPARATOR)) + GetHeight(b), false);
	sc.Show(b);
	RefreshParentLayout();
}

void TabBar::SyncScrollBar(bool synctotal)
{
	if (synctotal)
		sc.SetTotal(GetWidth());
	if (autoscrollhide) {
		bool v = sc.IsScrollable();
		if (sc.IsShown() != v) {
			PostCallback(THISBACK1(ShowScrollbarFrame, v));
		}
	}
	else {
		SetFrameSize(sc.GetFrameSize() + GetHeight(true), false);
		sc.Show();
	}
}

int TabBar::FindId(int id) const
{
	for(int i = 0; i < tabs.GetCount(); i++)
		if(tabs[i].id == id)
			return i;
	return -1;
}

int TabBar::GetNext(int n, bool drag) const
{
	for(int i = n + 1; i < tabs.GetCount(); i++)
		if(tabs[i].visible)
			return i;
	return drag ? tabs.GetCount() : -1;
}

int TabBar::GetPrev(int n, bool drag) const
{
	for(int i = n - 1; i >= 0; i--)
		if(tabs[i].visible)
			return i;
	return -1;
}

void TabBar::Clear()
{
	highlight = -1;
	drag_highlight = -1;
	active = -1;
	target = -1;
	cross = -1;
	stackcount = 0;
	tabs.Clear();
	groups.Clear();
	NewGroup(t_("TabBarGroupAll\aAll"));
	group = 0;
	Refresh();
	jump_stack.Reset();
}

TabBar& TabBar::Crosses(bool b, int side)
{
	crosses = b;
	crosses_side = side;
	Repos();
	Refresh();
	return *this;
}

TabBar& TabBar::SortTabs(bool b)
{
	tabsort = b;
	if (b)
		DoTabSort(*tabsorter);
	return *this;
}

TabBar& TabBar::SortTabsOnce()
{
	DoTabSort(*tabsorter);
	return *this;
}

TabBar& TabBar::SortTabsOnce(TabSort &sort)
{
	DoTabSort(sort);
	return *this;
}

TabBar& TabBar::SortTabs(TabSort &sort)
{
	tabsorter = &sort;
	return SortTabs(true);
}

TabBar& TabBar::SortTabValues(ValueOrder &sort)
{
	valuesorter_inst.vo = &sort;
	tabsorter = &valuesorter_inst;
	return SortTabs(true);
}

TabBar& TabBar::SortTabValuesOnce(ValueOrder &sort)
{
	TabValueSort q;
	q.vo = &sort;
	DoTabSort(q);
	return *this;
}

TabBar& TabBar::SortTabKeys(ValueOrder &sort)
{
	keysorter_inst.vo = &sort;
	tabsorter = &keysorter_inst;
	return SortTabs(true);
}

TabBar& TabBar::SortTabKeysOnce(ValueOrder &sort)
{
	TabKeySort q;
	q.vo = &sort;
	DoTabSort(q);
	return *this;
}

TabBar& TabBar::SortGroups(bool b)
{
	groupsort = b;
	if (!b) return *this;;
	
	Value v = GetData();
	MakeGroups();
	Repos();
	if (!IsNull(v))
		SetData(v);
	Refresh();
	return *this;
}

TabBar& TabBar::SortGroupsOnce()
{
	if (!grouping) return *this;;
	
	Value v = GetData();
	MakeGroups();
	Repos();
	if (!IsNull(v))
		SetData(v);
	Refresh();
	return *this;
}

TabBar& TabBar::SortGroupsOnce(TabSort &sort)
{
	TabSort *current = groupsorter;
	groupsorter = &sort;
	SortGroupsOnce();
	groupsorter = current;
	return *this;
}

TabBar& TabBar::SortGroups(TabSort &sort)
{
	groupsorter = &sort;
	return SortGroups(true);
}

TabBar& TabBar::SortStacks(bool b)
{
	stacksort = b;
	if (stacking) {
		DoStacking();
		Refresh();
	}
	return *this;
}

TabBar& TabBar::SortStacksOnce()
{
	if (stacking) {
		DoStacking();
		Refresh();
	}
	return *this;
}

TabBar& TabBar::SortStacksOnce(TabSort &sort)
{
	TabSort *current = stacksorter;
	stacksorter = &sort;
	SortStacksOnce();
	stacksorter = current;
	return *this;
}

TabBar& TabBar::SortStacks(TabSort &sort)
{
	stacksorter = &sort;
	return SortStacks(true);
}

TabBar& TabBar::SortStacks(ValueOrder &sort)
{
	stacksorter_inst.vo = &sort;
	stacksorter = &stacksorter_inst;
	return SortStacks(true);
}

void TabBar::DoTabSort(TabSort &sort)
{
	Value v = GetData();
	StableSort(tabs, sort);
	Repos();
	if (!IsNull(v))
		SetData(v);
	Refresh();
}

void TabBar::SortTabs0()
{
	if (tabsort)
		StableSort(tabs, *tabsorter);
}

TabBar& TabBar::Grouping(bool b)
{
	grouping = b;
	Repos();
	Refresh();
	return *this;
}

TabBar& TabBar::ContextMenu(bool b)
{
	contextmenu = b;
	return *this;
}

TabBar& TabBar::GroupSeparators(bool b)
{
	groupseps = b;
	Repos();
	Refresh();
	return *this;
}

TabBar& TabBar::AutoScrollHide(bool b)
{
	autoscrollhide = b;
	sc.Hide();
	SetFrameSize(GetHeight(false), false);
	SyncScrollBar(GetWidth());
	return *this;
}

TabBar& TabBar::InactiveDisabled(bool b)
{
	inactivedisabled = b;
	Repos();
	Refresh();
	return *this;
}

TabBar& TabBar::AllowNullCursor(bool b)
{
	allownullcursor = b;
	return *this;
}

TabBar& TabBar::Icons(bool v)
{
	icons = v;
	Repos();
	Refresh();
	return *this;
}

TabBar& TabBar::Stacking(bool b)
{
	stacking = b;
	if (b)
		DoStacking();
	else
		DoUnstacking();
	Refresh();
	return *this;
}

void TabBar::FrameSet()
{
	int al = GetAlign();
	Ctrl::ClearFrames();
	sc.Clear();
	sc.SetFrameSize(scrollbar_sz).SetAlign((al >= 2) ? al - 2 : al + 2);
	sc <<= THISBACK(Scroll);
	sc.Hide();
	
	if (sc.IsChild()) sc.Remove();
	switch (al) {
		case LEFT:
			Ctrl::Add(sc.LeftPos(GetHeight(), scrollbar_sz).VSizePos());
			break;
		case RIGHT:
			Ctrl::Add(sc.RightPos(GetHeight(), scrollbar_sz).VSizePos());
			break;
		case TOP:
			Ctrl::Add(sc.TopPos(GetHeight(), scrollbar_sz).HSizePos());
			break;
		case BOTTOM:
			Ctrl::Add(sc.BottomPos(GetHeight(), scrollbar_sz).HSizePos());
			break;
	};

	SyncScrollBar(true);
}

TabBar& TabBar::SetScrollThickness(int sz)
{
	scrollbar_sz = max(sz + 2, 3);
	FrameSet();
	RefreshLayout();
	return *this;
}

void TabBar::Layout()
{
	if (autoscrollhide && tabs.GetCount())
		SyncScrollBar(false);
	Repos();
}

int TabBar::FindValue(const Value &v) const
{
	for (int i = 0; i < tabs.GetCount(); i++)
		if (tabs[i].value == v)
			return i;
	return -1;
}

int TabBar::FindKey(const Value &v) const
{
	for (int i = 0; i < tabs.GetCount(); i++)
		if (tabs[i].key == v)
			return i;
	return -1;
}

bool TabBar::IsStackHead(int n) const
{
	return tabs[n].stack < 0
		|| n == 0
		|| (n > 0 && tabs[n - 1].stack != tabs[n].stack);
}

bool TabBar::IsStackTail(int n) const
{
	return tabs[n].stack < 0
		|| n >= tabs.GetCount() - 1
		|| (n < tabs.GetCount() && tabs[n + 1].stack != tabs[n].stack);
}

int TabBar::GetStackCount(int stackix) const
{
	int tc = tabs.GetCount();
	int L = 0;

	for ( int i = 0; i < tc; ++i )
		if ( tabs[ i ].stack == stackix )
		  ++L;

	return L;
}

int TabBar::FindStackHead(int stackix) const
{
	int i = 0;
	while (tabs[i].stack != stackix)
		i++;
	return i;
}

int TabBar::FindStackTail(int stackix) const
{
	int i = tabs.GetCount() - 1;
	while (tabs[i].stack != stackix)
		i--;
	return i;
}

int TabBar::SetStackHead(Tab &t)
// Returns index of stack head
{
	ASSERT(stacking);
	int id = t.id;
	int stack = t.stack;
	int head = FindStackHead(stack);
	while (tabs[head].id != id)
		CycleTabStack(head, stack);
	return head;
}

int TabBar::CycleTabStack(int n)
// Returns index of stack head
{
	int head = FindStackHead(n);
	CycleTabStack(head, n);
	return head;
}

void TabBar::CycleTabStack(int head, int n)
{
	// Swap tab to end of stack
	int ix = head;
	while (!IsStackTail(ix)) {
		tabs.Swap(ix, ix + 1);
		++ix;
	}
}

Value TabBar::GetData() const
{
	return (HasCursor() && active < GetCount())
		? GetKey(active)
		: Value();
}

void TabBar::SetData(const Value &key)
{
	int n = FindKey(key);
	if (n >= 0) {
		if (stacking && tabs[n].stack >= 0)
			n = SetStackHead(tabs[n]);
		SetCursor(n);
	}
}

void TabBar::Set(int n, const Value &newkey, const Value &newvalue)
{
	Set(n, newkey, newvalue, tabs[n].img);
}

void TabBar::Set(int n, const Value &newkey, const Value &newvalue, Image icon)
{
	ASSERT(n >= 0 && n < tabs.GetCount());
	tabs[n].key = newkey;
	tabs[n].value = newvalue;
	tabs[n].img = icon;
	if (stacking) {
		String id = tabs[n].stackid;
		tabs[n].stackid = GetStackId(tabs[n]);
		if (tabs[n].stackid != id) {
			tabs.Remove(n);
			InsertKey0(GetCount(), newkey, newvalue, tabs[n].img, tabs[n].group);
		}
	}
	Repos();
	Refresh();
}

void TabBar::SetValue(const Value &key, const Value &newvalue)
{
	Set(FindKey(key), key, newvalue);
}

void TabBar::SetValue(int n, const Value &newvalue)
{
	Set(n, tabs[n].key, newvalue);
}

void TabBar::SetKey(int n, const Value &newkey)
{
	Set(n, newkey, tabs[n].value);
}

void TabBar::SetIcon(int n, Image icon)
{
	ASSERT(n >= 0 && n < tabs.GetCount());
	tabs[n].img = icon;
	Repos();
	Refresh();
}

void TabBar::LeftDown(Point p, dword keyflags)
{
	p = AdjustMouse(p);
	SetCapture();
	
	if(keyflags & K_SHIFT)
	{
		highlight = -1;
		Refresh();
		Fix(p);
		oldp = p;
		return;
	}

	drag_highlight = highlight;
	
	isctrl = keyflags & K_CTRL;
	if(isctrl)
		return;

	if(cross != -1) {
		if (cross < tabs.GetCount()) {
			int tempCross = cross;
			Value v = tabs[cross].key;
			ValueArray vv;
			vv.Add(v);
			int ix = cross;
			if (!CancelClose(v) && !CancelCloseSome(vv) && ConfirmClose(v)) {
				WhenClose(v);
				WhenCloseSome(vv);
				TabClosed(v);
				Close(ix);
			}
			if (tempCross >= 0 && tempCross < tabs.GetCount())
				ProcessMouse(tempCross, p);
		}
	}
	else if(highlight >= 0) {
		if (stacking && highlight == active) {
			CycleTabStack(tabs[active].stack);
			Repos();
			CursorChanged();
			UpdateActionRefresh();
		}
		else
			SetCursor0(highlight, true);
	}
}

void TabBar::LeftUp(Point p, dword keyflags)
{
	ReleaseCapture();
}

void TabBar::LeftDouble(Point p, dword keysflags)
{
	WhenLeftDouble();
}

void TabBar::RightDown(Point p, dword keyflags)
{
	if (contextmenu)
	{
		// 2014/03/07 needed on X11 otherwise may crash
		// if focus is nowhere (probable bug somewhere else...)
		if(!GetActiveCtrl())
			GetParent()->SetFocus();
		MenuBar::Execute(THISBACK(ContextMenu), GetMousePos());
	}
}

void TabBar::MiddleDown(Point p, dword keyflags)
{
    if (highlight >= 0)
    {
        Value v = tabs[highlight].key;
        ValueArray vv;
        vv.Add(v);
        int highlightBack = highlight;
        if (!CancelClose(v) && !CancelCloseSome(vv) && ConfirmCloseSome(vv) && ConfirmClose(v)) {
            // highlight can be changed by the prompt. When reading "v", it can be invalid. I use the value from before the prompt to fix it
            Value v = tabs[highlightBack].key;
            // 2014/03/06 - FIRST the callbacks, THEN remove the tab
            // otherwise keys in WhenCloseSome() are invalid
            WhenClose(v);
            WhenCloseSome(vv);
            TabClosed(v);
            Close(highlightBack);
        }
    }
}

void TabBar::MiddleUp(Point p, dword keyflags)
{
}

int TabBar::GetTargetTab(Point p)
{
	p.x += sc.GetPos();

	int f = GetFirst();
	int l = GetLast();
	int n = tabs.GetCount();

	if(f < 0 || f >= n || l < 0 || l >= n)
		return -1;
	
	if(tabs[f].visible && p.x < tabs[f].pos.x + tabs[f].size.cx / 2)
		return f;

	int t = -1;
	
	for(int i = l; i >= f; i--)
		if(tabs[i].visible && p.x >= tabs[i].pos.x + tabs[i].size.cx / 2)
		{
			t = i;
			break;
		}
	
	if(stacking)
		l = FindStackHead(tabs[l].stack);
		
	if(t == l)
		t = n;
	else
		t = GetNext(t);

	return t;
}

void TabBar::MouseWheel(Point p, int zdelta, dword keyflags)
{
	sc.AddPos(zdelta / 4, true);
	Scroll();
	MouseMove(p, 0);
}

void TabBar::HorzMouseWheel(Point p, int zdelta, dword keyflags)
{
	MouseWheel(p, zdelta, keyflags);
}

Point TabBar::AdjustMouse(Point const &p) const
{
	int align = GetAlign();
	if(align == TOP || align == LEFT)
		return p;

	Size ctrlsz = GetSize();
	Size sz = GetBarSize(ctrlsz);
	return Point(p.x - ctrlsz.cx + sz.cx, p.y - ctrlsz.cy + sz.cy);
}

bool TabBar::ProcessMouse(int i, const Point& p)
{
	if(i >= 0 && i < tabs.GetCount() && tabs[i].HasMouse(p))
	{
		if (stacking && ProcessStackMouse(i, p))
			return true;
		bool iscross = crosses && !IsCancelClose(i) ? tabs[i].HasMouseCross(p) : false;
		if(highlight != i || (iscross && cross != i || !iscross && cross == i))
		{
			cross = iscross ? i : -1;
			SetHighlight(i);
		}
		return true;
	}
	return false;
}

bool TabBar::ProcessStackMouse(int i, const Point& p)
{
	int j = i + 1;
	while (j < tabs.GetCount() && tabs[j].stack == tabs[i].stack) {
		if (Rect(tabs[j].tab_pos, tabs[j].tab_size).Contains(p)) {
			cross = -1;
			if (highlight != j)
				SetHighlight(j);
			return true;
		}
		j++;
	}
	return false;
}

void TabBar::SetHighlight(int n)
{
	highlight = n;
	WhenHighlight();
	Refresh();
}

void TabBar::SetColor(int n, Color c)
{
	tabs[n].col = c;
	Refresh();
}

void TabBar::MouseMove(Point p, dword keyflags)
{
	p = AdjustMouse(p);
	if(HasCapture() && (keyflags & K_SHIFT))
	{
		Fix(p);
		sc.AddPos(p.x - oldp.x, true);
		oldp = p;
		Refresh();
		return;
	}
	
	if(HasCapture())
		return;
	
	if(ProcessMouse(active, p))
		return;
		
	for(int i = 0; i < tabs.GetCount(); i++)
	{
		if(i == active)
			continue;
		
		if(ProcessMouse(i, p))
			return;
	}

	if(highlight >= 0 || cross >= 0)
	{
		highlight = cross = -1;
		WhenHighlight();
		Refresh();
	}
}

void TabBar::MouseLeave()
{
	if(isdrag)
		return;
	highlight = cross = -1;
	WhenHighlight();
	Refresh();
}

void TabBar::DragAndDrop(Point p, PasteClip& d)
{
	Fix(p);
	int c = GetTargetTab(p);
	int tab = isctrl ? drag_highlight : active;

	if (&GetInternal<TabBar>(d) != this || tabsort || c < 0 || !allowreorder) return;

	if (stacking) {
		tab = FindStackHead(tabs[tab].stack);
		if(c < tabs.GetCount())
			c = FindStackHead(tabs[c].stack);
	}

	bool sametab = c == tab || c == GetNext(tab, true);
	bool internal = AcceptInternal<TabBar>(d, "tabs");

	if (CancelDragAndDrop && CancelDragAndDrop(tab, c > tab ? c-1 : c))
	{
		target = -1;
		isdrag = false;
		d.Reject();
		return;
	}
	
	if(!sametab && internal && d.IsAccepted())
	{
		int id = active >= 0 ? tabs[active].id : -1;
		
		// Count stack
		int count = 1;
		if (stacking) {
			int ix = tab + 1;
			int stack = tabs[tab].stack;
			while (ix < tabs.GetCount() && tabs[ix].stack == stack)
				ix++;
			count = ix - tab;
		}
		// Copy tabs
		Array<Tab> stacktemp;
		stacktemp.SetCount(count);
		//Copy(&stacktemp[0], &tabs[tab], count);
		for(int i = 0; i < count; i++)
			stacktemp[i].Set(tabs[tab + i]);
		// Remove
		tabs.Remove(tab, count);
		if (tab < c)
			c -= count;
		// Re-insert
		tabs.InsertPick(c, pick(stacktemp));
		
		active = id >= 0 ? FindId(id) : -1;
		isdrag = false;
		target = -1;
		MakeGroups();
		Repos();
		Refresh();
		Sync();
		MouseMove(p, 0);
	}
	else if(isdrag)
	{
		if(internal)
		{
			target = -1;
			isdrag = false;
		}
		else
			target = c;
		Refresh();
	}
}

void TabBar::CancelMode()
{
	isdrag = false;
	target = -1;
	Refresh();
}

void TabBar::LeftDrag(Point p, dword keyflags)
{
	if(keyflags & K_SHIFT)
		return;
	if(highlight < 0)
		return;

	Sync();
	isdrag = true;
	dragtab = GetDragSample();
	DoDragAndDrop(InternalClip(*this, "tabs"));
}

void TabBar::DragEnter()
{
}

void TabBar::DragLeave()
{
	target = -1;
	Refresh();
}

void TabBar::DragRepeat(Point p)
{
	if(target >= 0)
	{
		Point dx = GetDragScroll(this, p, 16);
		Fix(dx);
		if(dx.x != 0)
			sc.AddPos(dx.x);
	}
}

bool TabBar::SetCursor0(int n, bool action)
{
	if(tabs.GetCount() == 0)
		return false;

	if(n < 0)
	{
		n = max(0, FindId(GetGroupActive()));
		active = -1;
		highlight = -1;
		drag_highlight = -1;
		if (allownullcursor)
			return true;
	}

	bool is_all = IsGroupAll();
	bool same_group = tabs[n].group == GetGroupName();

	if((same_group || is_all) && active == n)
		return false;

	bool repos = false;

	if (!IsStackHead(n))
	{
		n = SetStackHead(tabs[n]);
		repos = true;
	}

	active = n;

	if(!is_all && !same_group)
	{
		SetGroup(tabs[n].group);
		repos = true;
	}
	if (repos)
		Repos();

	SetGroupActive(tabs[n].id);

	int cx = tabs[n].pos.x - sc.GetPos();
	if(cx < 0)
		sc.AddPos(cx - 10);
	else
	{
		Size sz = Ctrl::GetSize();
		Fix(sz);
		cx = tabs[n].pos.x + tabs[n].size.cx - sz.cx - sc.GetPos();
		if(cx > 0)
			sc.AddPos(cx + 10);
	}
	
	if(action)
	{
		CursorChanged();
		UpdateAction();
	}

	Refresh();

	if(Ctrl::HasMouse())
	{
		Refresh();
		Sync();
		MouseMove(GetMouseViewPos(), 0);
	}
	return true;
}

void TabBar::SetCursor(int n)
{
	SetCursor0(n, true);
}

void TabBar::SetTabGroup(int n, const String &group)
{
	ASSERT(n >= 0 && n < tabs.GetCount());
	int g = FindGroup(group);
	if (g <= 0)
		NewGroup(group);
	else if (groups[g].active == tabs[n].id)
		SetGroupActive(tabs[n].id);
	tabs[n].group = group;
	MakeGroups();
	Repos();
}

void TabBar::CloseForce(int n, bool action)
{
	if(n < 0 || n >= tabs.GetCount())
		return;
	if(n == active)
	{
		int c = FindId(tabs[n].id);
		int nc = GetNext(c);
		if(nc < 0)
			nc = max(0, GetPrev(c));
		SetGroupActive(tabs[nc].id);
	}
	sc.AddTotal(-tabs[n].size.cx);
	tabs.Remove(n);
	MakeGroups();
	Repos();
	
	if(n == active)
		SetCursor0(-1, action);
	else {
		if (n < active)
			active--;
		Refresh();
		if (n == highlight && Ctrl::HasMouse()) {
			//TODO: That must be refactored
			highlight = -1;
			drag_highlight = -1;
			Refresh();
			Sync();
			MouseMove(GetMouseViewPos(), 0);
		}
	}
}

void TabBar::Close(int n, bool action)
{
	if(tabs.GetCount() <= mintabcount)
		return;

	CloseForce(n, action);
}

void TabBar::CloseKey(const Value &key)
{
	int tabix = FindKey(key);
	if (tabix < 0) return;
	Close(tabix);
}

TabBar::Style& TabBar::Style::DefaultCrosses()
{
	crosses[0] = TabBarImg::CR0();
	crosses[1] = TabBarImg::CR1();
	crosses[2] = TabBarImg::CR2();
	return *this;
}

TabBar::Style& TabBar::Style::Variant1Crosses()
{
	crosses[0] = TabBarImg::VARIANT1_CR0();
	crosses[1] = TabBarImg::VARIANT1_CR1();
	crosses[2] = TabBarImg::VARIANT1_CR2();
	return *this;
}

TabBar::Style& TabBar::Style::Variant2Crosses()
{
	crosses[0] = TabBarImg::VARIANT2_CR0();
	crosses[1] = TabBarImg::VARIANT2_CR1();
	crosses[2] = TabBarImg::VARIANT2_CR2();
	return *this;
}

TabBar::Style& TabBar::Style::Variant3Crosses()
{
	crosses[0] = TabBarImg::VARIANT3_CR0();
	crosses[1] = TabBarImg::VARIANT3_CR1();
	crosses[2] = TabBarImg::VARIANT3_CR2();
	return *this;
}

TabBar::Style& TabBar::Style::GroupSeparators(Value horz, Value vert)
{
	group_separators[0] = horz;
	group_separators[0] = vert;
	return *this;
}

TabBar::Style& TabBar::Style::DefaultGroupSeparators()
{
	return GroupSeparators(TabBarImg::SEP(), TabBarImg::SEPV());
}

Vector<Value> TabBar::GetKeys() const
{
	Vector<Value> keys;
	keys.SetCount(tabs.GetCount());
	for (int i = 0; i < tabs.GetCount(); i++)
		keys[i] = tabs[i].key;
	return keys;
}

Vector<Image> TabBar::GetIcons() const
{
	Vector<Image> img;
	img.SetCount(tabs.GetCount());
	for (int i = 0; i < tabs.GetCount(); i++)
		img[i] = tabs[i].img;
	return img;
}

TabBar& TabBar::CopyBaseSettings(const TabBar& src)
{
	crosses = src.crosses;
	crosses_side = src.crosses_side;
	grouping = src.grouping;
	contextmenu = src.contextmenu;
	autoscrollhide = src.autoscrollhide;
	nosel = src.nosel;
	nohl = src.nohl;
	inactivedisabled = src.inactivedisabled;
	stacking = src.stacking;
	groupsort = src.groupsort;
	groupseps = src.groupseps;
	tabsort = src.tabsort;
	allownullcursor = src.allownullcursor;
	icons = src.icons;
	mintabcount = src.mintabcount;
	return *this;
}

TabBar& TabBar::CopySettings(const TabBar &src)
{
	
	CopyBaseSettings(src);
	
	if (stacking != src.stacking)
		Stacking(src.stacking);
	else {
		MakeGroups();
		Repos();
		Refresh();
	}
	return *this;
}

void TabBar::Serialize(Stream& s)
{
	int version = 1;
	s / version;

	s % id;
	s % crosses;
	s % crosses_side;
	s % grouping;
	s % autoscrollhide;
	s % nosel;
	s % nohl;
	s % inactivedisabled;
	s % stacking;
	s % groupsort;
	s % groupseps;
	s % tabsort;
	s % allownullcursor;
	s % icons;
	s % mintabcount;
	s % active;
	
	cross = -1;
	highlight = -1;
	drag_highlight = -1;
	target = -1;
	
	int n = groups.GetCount();
	s % n;
	groups.SetCount(clamp(n, 0, 10000));
	
	for(int i = 0; i < groups.GetCount(); i++)
		s % groups[i];
	
	n = tabs.GetCount();
	s % n;
	tabs.SetCount(clamp(n, 0, 10000));
	
	for(int i = 0; i < tabs.GetCount(); i++)
		s % tabs[i];
	
	int g = GetGroup();
	s % g;
	group = g;
	
	Repos();
}

TabBar& TabBar::SetStyle(const TabBar::Style& s)	{
	if(style != &s) {
		style = &s;
		RefreshLayout();
		Refresh();
	}
	return *this;
}

CH_STYLE(TabBar, Style, StyleDefault)
{
	Assign(TabCtrl::StyleDefault());
	Variant3Crosses(); // these actually look most inline with any GUI today (2023)
#if 0
#ifdef PLATFORM_WIN32
	if(IsWinVista())
		Variant3Crosses();
	else
		DefaultCrosses();
#else
	DefaultCrosses();
#endif
#endif
	DefaultGroupSeparators();
}

}
