#include "GeomCtrl.h"
#pragma hdrstop

NAMESPACE_UPP

#define LAYOUTFILE <Geom/Ctrl/pathedit.lay>
#include           <CtrlCore/lay.h>

#define IMAGEFILE  <Geom/Ctrl/pathedit.iml>
#define IMAGECLASS PathImg
#include           <Draw/iml.h>

class DlgPathStyleSetup
{
public:
	typedef DlgPathStyleSetup CLASSNAME;
	DlgPathStyleSetup();

	bool Run(PathStyleMisc& style);

	void PutHelp();

private:
	void Pump(PathStyleMisc& style, bool write);
	void OnMiter();

private:
//	WithHelp<
	WithPathStyleSetupLayout<TopWindow>
//	>
	dialog;
};

bool RunDlgPathStyleSetup(PathStyleMisc& style) { return DlgPathStyleSetup().Run(style); }

RegisterHelpTopicObjectTitle(DlgPathStyleSetup, "Vlastnosti stylu")

DlgPathStyleSetup::DlgPathStyleSetup()
{
	CtrlLayoutOKCancel(dialog, DlgPathStyleSetupHelpTitle());
	dialog.HelpTopic("DlgPathStyleSetup");
	dialog.width.MinMax(0.001, 20);
	dialog.begin.MinMax(0, 100);
	dialog.segment.MinMax(0, 100);
	dialog.end.MinMax(0, 100);
	dialog.miter.Hide();
	dialog.miter.GetPrev()->Hide();
	dialog.miter.Add(PathStyle::MITER_ROUND, "zaoblit");
	dialog.miter.Add(PathStyle::MITER_SHARP, "ostrý roh");
	dialog.miter.Add(PathStyle::MITER_FLAT,  "zkosit");
	dialog.miter <<= THISBACK(OnMiter);
	dialog.chamfer.NotNull().MinMax(0, 5);
}

bool DlgPathStyleSetup::Run(PathStyleMisc& style)
{
	Pump(style, false);
	OnMiter();
	if(dialog.Run() != IDOK)
		return false;
	Pump(style, true);
	return true;
}

void DlgPathStyleSetup::OnMiter()
{
	dialog.chamfer.Enable((int)~dialog.miter == PathStyle::MITER_SHARP);
	if(dialog.chamfer.IsEnabled() && dialog.chamfer.GetText().IsEmpty())
		dialog.chamfer <<= STD_CHAMFER;
}

void DlgPathStyleSetup::Pump(PathStyleMisc& style, bool write)
{
	UPP::Pump pump;
	pump
		<< PumpData(style.width,   dialog.width)
		<< PumpData(style.begin,   dialog.begin)
		<< PumpData(style.segment, dialog.segment)
		<< PumpData(style.end,     dialog.end)
		<< PumpData(style.miter,   dialog.miter);
	if(style.miter == style.MITER_SHARP)
		pump << PumpData(style.chamfer, dialog.chamfer);
	pump << write;
}

static double CalcDecadicStep(double scale, int minfine, int mincoarse, int& fine)
{
	double mu = mincoarse / scale;
	if(mu <= 1e-20)
	{
		fine = 0;
		return Null;
	}
	int exp = ffloor(log10(mu));
	double lo = pow(10.0, exp);
	int tc = (lo * 5 <= mu ? 5 : lo * 2 <= mu ? 2 : 1);
	double mf = scale * lo * tc / minfine;
	int tf = 1;
	while(tf < 100 && mf >= 10)
		mf /= 10, tf *= 10;
	if(tf <= tc)
		tf = 1;
	else
		tf *= (mf >= 5 && tc != 2 ? 5 : mf >= 2 && tc != 5 ? 2 : 1);
	fine = tf;
	return lo * tc;
}

//////////////////////////////////////////////////////////////////////
// DlgPathEditorSetup::

class DlgPathEditorSetup
{
public:
	typedef DlgPathEditorSetup CLASSNAME;
	DlgPathEditorSetup();

	bool Run(PathEditorCtrl::Setup& setup);
	void PutHelp();

private:
	void Pump(PathEditorCtrl::Setup& setup, bool write);

private:
//	WithHelp<
	WithPathEditorSetupLayout<TopWindow>
//	>
	dialog;
};

bool RunDlgPathEditorSetup(PathEditorCtrl::Setup& setup) { return DlgPathEditorSetup().Run(setup); }

RegisterHelpTopicObjectTitle(DlgPathEditorSetup, "Vlastnosti editoru")

DlgPathEditorSetup::DlgPathEditorSetup()
{
	CtrlLayoutOKCancel(dialog, DlgPathEditorSetupHelpTitle());
	dialog.HelpTopic("DlgPathEditorSetup");
	dialog.grid.MinMax(1e-5, 100);
	dialog.snap.MinMax(1e-5, 100);
}

bool DlgPathEditorSetup::Run(PathEditorCtrl::Setup& setup)
{
	Pump(setup, false);
	if(dialog.Run() != IDOK)
		return false;
	Pump(setup, true);
	return true;
}

void DlgPathEditorSetup::Pump(PathEditorCtrl::Setup& setup, bool write)
{
	UPP::Pump pump;
	pump
		<< PumpEnumData(setup.do_grid,   dialog.do_grid)
		<< PumpData    (setup.grid,      dialog.grid)
		<< PumpEnumData(setup.do_snap,   dialog.do_snap)
		<< PumpData    (setup.snap,      dialog.snap)
		<< PumpEnumData(setup.do_ruler,  dialog.do_ruler)
		<< PumpEnumData(setup.do_axis,   dialog.do_axis)
		<< write;
}

//////////////////////////////////////////////////////////////////////
// PathEditorCtrl::Setup::

void PathEditorCtrl::Setup::Serialize(Stream& stream)
{
	int version = StreamHeading(stream, 4, 4, 4, "PathEditorCtrl::Setup");
	if(version >= 1)
	{
		stream.Pack(do_grid, do_snap, do_ruler, do_axis);
		stream % grid % snap / sample_size / sample_width;
	}
}

//////////////////////////////////////////////////////////////////////
// PathEditorCtrl::ViewPlotter::

PathEditorCtrl::ViewPlotter::ViewPlotter(PathEditorCtrl *ctrl)
: draw(ctrl)
{
	Set(draw, ctrl->GetScale(), ctrl->GetDelta(), 10);
	PathMap(&PathStyleMap::App());
}

PathEditorCtrl::PathEditorCtrl()
: style(0)
{
	edit_mode = EDIT_NORMAL;
	drag_mode = DRAG_NONE;
	SetFrame(InsetFrame());
	zoom_horz_in  .SetImage(PathImg::view_zoom_in());
	zoom_vert_in  .SetImage(PathImg::view_zoom_in());
	zoom_horz_out .SetImage(PathImg::view_zoom_out());
	zoom_vert_out .SetImage(PathImg::view_zoom_out());
	zoom_horz_full.SetImage(PathImg::view_zoom_horz_full());
	zoom_vert_full.SetImage(PathImg::view_zoom_vert_full());
	zoom_full     .SetImage(PathImg::view_zoom_full());
	zoom_horz_in   <<= THISBACK(OnViewZoomHorzIn);
	zoom_horz_out  <<= THISBACK(OnViewZoomHorzOut);
	zoom_vert_in   <<= THISBACK(OnViewZoomVertIn);
	zoom_vert_out  <<= THISBACK(OnViewZoomVertOut);
	zoom_horz_full <<= THISBACK(OnViewZoomHorzFull);
	zoom_vert_full <<= THISBACK(OnViewZoomVertFull);
	zoom_full      <<= THISBACK(OnViewZoomFull);
	sample_size_tag.SetLabel("Ukázka: ");
	sample_size.Add(0,  "skrýt");
	sample_size.Add(40, "malá");
	sample_size.Add(70, "střední");
	sample_size.Add(100, "velká");
//	sample_size.SetRect(LayoutZoom(80, 0));
	sample_size <<= 40;
	sample_size <<= THISBACK(OnSampleSize);
	sample_width_tag.SetLabel("Šířka: ");
	sample_width.NotNull().MinMax(0, MAX_WIDTH);
	sample_width <<= THISBACK(OnSampleWidth);
	sample_width <<= 2;
//	sample_width.SetRect(LayoutZoom(40, 0));
	zoom_horz_in.NoWantFocus();
	zoom_horz_out.NoWantFocus();
	zoom_horz_full.NoWantFocus();
	zoom_vert_in.NoWantFocus();
	zoom_vert_out.NoWantFocus();
	zoom_vert_full.NoWantFocus();
	zoom_full.NoWantFocus();
	AddFrame(*this);
	hruler.Horz();
	vruler.Vert();
	hruler.SetZoom(500, 0);
	hruler.MinMax(0, 20);
//	hruler.Units(1, 5);
	vruler.SetZoom(500, 0);
	vruler.MinMax(-5, 5);
//	vruler.Units(1, 5);
	hscroll <<= vscroll <<= THISBACK(OnScroll);
	hscroll.NoAutoDisable();
	vscroll.NoAutoDisable();
}

void PathEditorCtrl::FrameLayout(Rect& rc)
{
	if(!style)
		return;

	outer_sample = rc;
	rc.right -= setup.sample_size;
	rc.bottom -= setup.sample_size;
	inner_sample = rc;

	Size avail = rc.Size();
	int box = ScrollBarSize();
	int rsz = RulerCtrl::GetStdWidth();
	if(setup.do_ruler)
		avail -= rsz;
	double pwd = hruler.GetLength() * hruler.GetScale() + 2 * GAP + HGAP;
	double pht = vruler.GetLength() * vruler.GetScale() + 2 * GAP;
	zoom_horz_in.SetFrameRect(rc.left, rc.bottom - box, box, box);
	hscroll.SetFrameRect(rc.left + box, rc.bottom - box, rc.Width() - 4 * box, box);
	zoom_horz_out.SetFrameRect(rc.right - 3 * box, rc.bottom - box, box, box);
	zoom_horz_full.SetFrameRect(rc.right - 2 * box, rc.bottom - box, box, box);
	hscroll.Set(hscroll, avail.cx - box, fround(pwd));
	zoom_vert_in.SetFrameRect(rc.right - box, rc.top, box, box);
	vscroll.SetFrameRect(rc.right - box, rc.top + box, box, rc.Height() - 4 * box);
	zoom_vert_out.SetFrameRect(rc.right - box, rc.bottom - 3 * box, box, box);
	zoom_vert_full.SetFrameRect(rc.right - box, rc.bottom - 2 * box, box, box);
	vscroll.Set(vscroll, avail.cy - box, fround(pht));
	zoom_full.SetFrameRect(rc.right - box, rc.bottom - box, box, box);
	rc.right  -= box;
	rc.bottom -= box;
	if(setup.do_ruler)
	{
		hruler.SetFrameRect(rc.left + rsz, rc.top, rc.Width() - rsz, rsz);
		vruler.SetFrameRect(rc.left, rc.top + rsz, rsz, rc.Height() - rsz);
		rc.left += rsz;
		rc.top += rsz;
	}
}

void PathEditorCtrl::FrameAddSize(Size& sz)
{
	if(!style)
		return;
	int box = ScrollBarSize();
	int rsz = RulerCtrl::GetStdWidth();
	if(setup.do_ruler)
		sz += rsz;
	sz += box;
}

static void PaintSamplePath(PathDraw& path, const Rect& mid)
{
	path.MoveTo(mid.left, mid.bottom);
	path.LineTo(mid.left + 20, mid.bottom);
	path.LineTo(mid.left + 40, mid.bottom - 10);
	path.LineTo(mid.left + 50, mid.bottom + 10);
	path.LineTo(mid.left + 70, mid.bottom);
	if(mid.Width() >= 200)
	{
		path.LineTo(mid.left + 100, mid.bottom);
		path.LineTo(mid.left + 130, mid.bottom - 10);
		path.LineTo(mid.left + 170, mid.bottom + 10);
		path.LineTo(mid.left + 200, mid.bottom);
	}
	if(mid.Width() >= 300)
	{
		path.LineTo(mid.left + 240, mid.bottom - 10);
		path.LineTo(mid.left + 250, mid.bottom + 10);
		path.LineTo(mid.left + 260, mid.bottom - 10);
		path.LineTo(mid.left + 300, mid.bottom);
	}
	path.LineTo(mid.right, mid.bottom);
	path.LineTo(mid.right, mid.bottom - 20);
	path.LineTo(mid.right - 10, mid.bottom - 40);
	path.LineTo(mid.right + 10, mid.bottom - 50);
	path.LineTo(mid.right, mid.bottom - 70);
	if(mid.Height() >= 200)
	{
		path.LineTo(mid.right,      mid.bottom - 100);
		path.LineTo(mid.right - 10, mid.bottom - 130);
		path.LineTo(mid.right + 10, mid.bottom - 170);
		path.LineTo(mid.right,      mid.bottom - 200);
	}
	path.LineTo(mid.right, mid.top);
	path.Paint();
}

void PathEditorCtrl::FramePaint(Draw& draw, const Rect& r)
{
	Rect rc = r;
	if(setup.sample_size > 0)
	{
		PumpTraces(true);
		draw.Clip(outer_sample);
		draw.ExcludeClip(inner_sample);
		draw.DrawRect(outer_sample, SLtGray);
		PathDraw path(draw, *style, Black, setup.sample_width);
		int half = setup.sample_size >> 1;
		Rect mid(outer_sample.left + 10, outer_sample.top + 10,
			(outer_sample.right  + inner_sample.right)  >> 1,
			(outer_sample.bottom + inner_sample.bottom) >> 1);
		PaintSamplePath(path, mid);
		if(setup.do_axis)
		{
			path.Set(draw, PathStyle::solid(), LtRed, 0);
			PaintSamplePath(path, mid);
		}
		draw.End();
		rc = inner_sample;
	}
	if(setup.do_ruler)
	{
		int r = RulerCtrl::GetStdWidth();
		draw.DrawRect(rc.left, rc.top, r, r, SGray);
	}
	Size scb(vscroll.GetRect().Width(), hscroll.GetRect().Height());
	if(scb.cx && scb.cy)
		draw.DrawRect(rc.right - scb.cx, rc.bottom - scb.cy, scb.cx, scb.cy, SGray);
}

void PathEditorCtrl::FrameAdd(Ctrl& ctrl)
{
	ctrl << hruler << vruler << (Ctrl &)hscroll << (Ctrl &)vscroll
		<< zoom_horz_in << zoom_horz_out << zoom_horz_full
		<< zoom_vert_in << zoom_vert_out << zoom_vert_full
		<< zoom_full;
}

void PathEditorCtrl::FrameRemove()
{
	hruler.Remove();
	vruler.Remove();
	hscroll.Remove();
	vscroll.Remove();
	zoom_horz_in.Remove();
	zoom_horz_out.Remove();
	zoom_horz_full.Remove();
	zoom_vert_in.Remove();
	zoom_vert_out.Remove();
	zoom_vert_full.Remove();
	zoom_full.Remove();
}

static inline bool PathStyleTraceLeftLess(const PathStyle::Trace& a, const PathStyle::Trace& b)
{ return a.left < b.left; }

void PathEditorCtrl::PumpTraces(bool write)
{
	if(write)
	{
		style->traces <<= traces;
		Sort(style->traces, &PathStyleTraceLeftLess);
	}
	else
	{
		selection.Clear();
		traces <<= style->traces;
	}
}

void PathEditorCtrl::Layout()
{
	if(!style)
		return;
	Size size = GetSize();
	double cx = style->begin + style->segment + style->end, cy = style->width / 2;
	int fine;
	double dstep = CalcDecadicStep(hruler.GetScale(), 5, 100, fine);
	hruler.MinMax(0, cx);
	hruler.SetTextStep(dstep);
	hruler.SetSmallStep(dstep / fine);
	vruler.MinMax(-cy, cy);
	dstep = CalcDecadicStep(vruler.GetScale(), 5, 100, fine);
	vruler.SetTextStep(dstep);
	vruler.SetSmallStep(dstep / fine);
/*	if(cx * hruler.GetScale() <= size.cx)
		hruler.SetZoomDelta(GAP);
	if(style->width * vruler.GetScale() <= size.cy)
		vruler.Delta(GAP + cy * vruler.GetScale());
*/	OnScroll();
	Refresh();
}

void PathEditorCtrl::UpdateScroll()
{
	Size size = GetSize();
	double pwd = hruler.GetLength() * hruler.GetScale() + 2 * GAP + HGAP;
	double pht = vruler.GetLength() * vruler.GetScale() + 2 * GAP;
	hruler.SetZoom(hruler.GetScale(), pwd <= size.cx ? GAP : minmax(hruler.GetDelta(), size.cx - double(GAP) - pwd, double(GAP)));
	double t = style->width / 2 * vruler.GetScale();
	vruler.SetZoom(vruler.GetScale(), pht <= size.cy ? pht / 2 : minmax(vruler.GetDelta(), size.cy - double(GAP) - t, double(GAP) + t));
	int hpos = GAP - fround(hruler.GetDelta());
	int vpos = GAP + fround(style->width * vruler.GetScale() / 2 - vruler.GetDelta());
	Layout();
	hscroll = hpos;
	vscroll = vpos;
	OnScroll();
	Refresh();
}

void PathEditorCtrl::OnScroll()
{
	double dx = hruler.GetDelta(), dy = vruler.GetDelta();
	if(!hscroll.GetRect().IsEmpty())
		dx = GAP - (int)hscroll;
	if(!vscroll.GetRect().IsEmpty())
		dy = GAP + style->width * vruler.GetScale() / 2 - (int)vscroll;
	if(dx != hruler.GetDelta() || dy != vruler.GetDelta())
	{
		hruler.SetZoom(hruler.GetScale(), dx);
		vruler.SetZoom(vruler.GetScale(), dy);
		Refresh();
	}
}

void PathEditorCtrl::UpdateSetup()
{
	sample_size <<= setup.sample_size;
	sample_width <<= setup.sample_width;
	Layout();
	sample_width.Enable(setup.sample_size > 0);
}

void PathEditorCtrl::OnSampleSize()
{
	setup.sample_size = ~sample_size;
	Layout();
	sample_width.Enable(setup.sample_size > 0);
}

void PathEditorCtrl::OnSampleWidth()
{
	setup.sample_width = ~sample_width;
	UpdateSample();
}

static void PaintSizeBreak(PlotterTool& tool, double x, double y, Pointf size)
{
	tool.MoveTo(x, -y + size.y);
	tool.LineTo(x - size.x, -y);
	tool.LineTo(x + size.x, -y);
	tool.LineTo(x, -y + size.y);
	tool.LineTo(x, y - size.y);
	tool.LineTo(x + size.x, y);
	tool.LineTo(x - size.x, y);
	tool.LineTo(x, y - size.y);
	tool.Paint();
}

void PathEditorCtrl::Paint(Draw& draw)
{
	Rectf full = GetFullExtent();
	Rectf vis = ClientToUnits(draw.GetPaintRect()) & full;
	Rect cvis = UnitsToClient(vis) & draw.GetPaintRect();
	DrawRectMinusRect(draw, draw.GetPaintRect(), cvis, SGray);
	if(cvis.IsEmpty())
		return;
	draw.Clip(cvis);
	draw.DrawRect(cvis, White);
	if(setup.do_grid && setup.grid >= 1e-10)
	{
		double s = setup.grid;
		while(s * hruler.GetScale() < 4)
			s *= 10;
		int l, h;
		for(l = ffloor(vis.left / s), h = fceil(vis.right / s); l <= h; l++)
		{
			Point top = UnitsToClient(Pointf(l * s, vis.top));
			draw.DrawRect(top.x, cvis.top, 1, cvis.Height(), LtGray);
		}
		s = setup.grid;
		while(s * vruler.GetScale() < 4)
			s *= 10;
		for(l = ffloor(vis.top / s), h = fceil(vis.bottom / s); l <= h; l++)
		{
			Point left = UnitsToClient(Pointf(vis.left, l * s));
			draw.DrawRect(cvis.left, left.y, cvis.Width(), 1, LtGray);
		}
	}
	Plotter plotter;
	plotter.Set(draw, GetScale(), GetDelta(), 10);
	plotter.PathMap(&PathStyleMap::App());
	AreaTool area;
	PathTool path;
	for(int i = 0; i < traces.GetCount(); i++)
	{
		const PathStyle::Trace& trace = traces[i];
		int s = (selection.Find(i) >= 0 ? i == selection.Top() ? 2 : 1 : 0);
		Color color = Nvl(trace.color, Black);
		Color outline = (s == 0 ? color : s == 1 ? LtBlue : Color(192, 192, 255));
		if(trace.IsAreaEmpty())
		{
			path.Set(plotter, ".dot", outline, 3);
			if(path.SetExtent(trace.GetExtent()))
				trace.Paint(path, true, outline);
		}
		else
		{
			area.Set(plotter, Nvl(trace.color, Black), INT64(0xaa55aa55aa55aa55), Null, outline, 3);
			if(area.SetExtent(trace.GetExtent()))
				trace.Paint(area, false, outline);
		}
	}
	draw.End();
	{ // draw begin & end bar
		Pointf size = Pointf(16, 16) / GetScale();
		AreaTool area;
		area.Set(plotter, LtGreen, 0, Null, Green, 2);
		double x = style->begin;
		double h = style->width / 2;
		PaintSizeBreak(area, x, h, size);
		x += style->segment;
		PaintSizeBreak(area, x, h, size);
		x += style->end;
		PaintSizeBreak(area, x, h, size);
	}
	if(!selection.IsEmpty())
	{
		const PathStyle::Trace& trace = traces[selection.Top()];
		Pointf list[] =
		{
			trace.LeftTop(), trace.CenterTop(), trace.RightTop(),
			trace.LeftCenter(), trace.RightCenter(),
			trace.LeftBottom(), trace.CenterBottom(), trace.RightBottom(),
		};
		for(int i = 0; i < __countof(list); i++)
			if(vis.Contains(list[i]))
			{
				enum { DELTA = 3 };
				Point pt = UnitsToClient(list[i]);
				Rect rc(pt.x - DELTA, pt.y - DELTA, pt.x + DELTA + 1, pt.y + DELTA + 1);
				draw.DrawRect(rc, LtRed);
				rc.Inflate(1);
				DrawFrame(draw, rc, White, Black);
			}
	}
}

Image PathEditorCtrl::CursorImage(Point pt, dword keyflags)
{
	switch(edit_mode)
	{
	case EDIT_ZOOM: return PathImg::drag_zoom_cursor();
	case EDIT_PAN:  return PathImg::drag_pan_cursor();
	}
	if(IsDragging())
		switch(drag_mode)
		{
		default: NEVER();
		case DRAG_SELECT: return Image::Arrow();
		case DRAG_BEGIN:
		case DRAG_SEGMENT:
		case DRAG_END:    return Image::SizeHorz();
		case DRAG_INSERT: return PathImg::drag_insert_cursor();
		case DRAG_TRACK:  return PathStyle::Trace::GetTrackCursor(track_style);
		case DRAG_MOVE:   return Image::SizeAll();
		}
	Pointf up = ClientToUnits(pt), start;
	int i = GetTrackStyle(up, start);
	if(i)
		return PathStyle::Trace::GetTrackCursor(i);
	if(GetDragSize(up))
		return Image::SizeHorz();
	i = FindObject(up);
	if(i >= 0 && selection.Find(i) >= 0)
		return Image::SizeAll(); // move
	return Image::Arrow();
}

int PathEditorCtrl::AddObject(const PathStyle::Trace& trace)
{
	int i = traces.GetCount();
	traces.Add(trace);
	RefreshObject(i);
	Update();
	UpdateSample();
	return i;
}

void PathEditorCtrl::SetObject(int i, const PathStyle::Trace& trace)
{
	RefreshObject(i);
	traces[i] = trace;
	RefreshObject(i);
	Update();
	UpdateSample();
}

void PathEditorCtrl::UpdateSample()
{
	if(setup.sample_size > 0)
	{
		RefreshFrame(Rect(outer_sample.left,  outer_sample.top,    outer_sample.right, inner_sample.top));
		RefreshFrame(Rect(outer_sample.left,  inner_sample.bottom, outer_sample.right, outer_sample.bottom));
		RefreshFrame(Rect(outer_sample.left,  inner_sample.top,    outer_sample.right, inner_sample.bottom));
		RefreshFrame(Rect(inner_sample.right, inner_sample.top,    outer_sample.right, inner_sample.bottom));
	}
}

void PathEditorCtrl::RefreshObject(int item)
{
	if(item >= 0 && item < traces.GetCount())
	{
		const PathStyle::Trace& trace = traces[item];
		Rectf rc(trace.left, min(trace.left_top, trace.right_top), trace.right, max(trace.left_bottom, trace.right_bottom));
		Rect cl = UnitsToClient(rc);
		cl.Inflate(selection.Find(item) >= 0 ? 10 : 5);
		Refresh(cl);
	}
}

bool PathEditorCtrl::WriteClipboard()
{
	if(!IsSelection())
		return false; // no-op
	PathStyle::Clip clip;
	for(int i = 0; i < selection.GetCount(); i++)
		clip.traces.Add(traces[selection[i]]);
	clip.Write();
	return true;
//	return clip.Write();
}

bool PathEditorCtrl::ReadClipboard()
{
	PathStyle::Clip clip;
	if(!clip.Read())
		return false;
	Rectf extent = clip.GetExtent();
	Rectf full = GetFullExtent();
	bool setwd = false, setsg = false;
	if(extent.top < full.top || extent.bottom > full.bottom)
	{
		int r = PromptYesNoCancel("Vybrané objekty přesahují limit daný šířkou čáry. "
			"Chcete šířku čáry upravit?");
		if(r < 0)
			return true;
		setwd = !!r;
	}
	if(extent.right > full.right)
	{
		int r = PromptYesNoCancel("Vybrané objekty přesahují limit daný délkou segmentu. "
			"Chcete délku segmentu upravit?");
		if(r < 0)
			return true;
		setsg = !!r;
	}

	if(setwd)
		style->width = max(style->width, max(-extent.top, extent.bottom));

	if(setsg)
		style->segment += full.right - extent.right;

	ClearSelection();
	int c = traces.GetCount();
	Append(traces, clip.traces);
	Vector<int> to_add;
	while(c < traces.GetCount())
		to_add.Add(c++);
	AddSelection(to_add);

	if(setwd || setsg)
		Layout();
	UpdateSample();
	return true;
}

Rectf PathEditorCtrl::GetFullExtent() const
{
	return Rectf(0, -style->width / 2,
		style->begin + style->segment + style->end, style->width / 2);
}

Rectf PathEditorCtrl::GetSelectionExtent() const
{
	if(selection.IsEmpty())
		return Null;
	Rectf rc = traces[selection[0]].GetExtent();
	for(int i = 1; i < selection.GetCount(); i++)
		rc |= traces[selection[i]].GetExtent();
	return rc;
}

int PathEditorCtrl::GetSelectionLeader() const
{
	return selection.IsEmpty() ? -1 : selection.Top();
}

void PathEditorCtrl::AddSelection(const Vector<int>& list)
{
	int old = GetSelectionLeader();
	for(int i = 0; i < list.GetCount(); i++)
		if(selection.Find(list[i]) < 0)
		{
			RefreshObject(list[i]);
			selection.Add(list[i]);
		}
	RefreshObject(old);
	WhenRescan();
}

void PathEditorCtrl::XorSelection(const Vector<int>& list)
{
	int old_leader = GetSelectionLeader();
	for(int i = 0; i < list.GetCount(); i++)
	{
		RefreshObject(list[i]);
		int pos = selection.Find(list[i]);
		if(pos >= 0)
			selection.Remove(pos);
		else
			selection.Add(list[i]);
	}
	int new_leader = GetSelectionLeader();
	if(new_leader != old_leader)
	{
		RefreshObject(old_leader);
		RefreshObject(new_leader);
	}
	WhenRescan();
}

void PathEditorCtrl::RemoveSelection(const Vector<int>& list)
{
	int count = selection.GetCount();
	for(int i = 0; i < list.GetCount(); i++)
	{
		int pos = selection.Find(list[i]);
		if(pos >= 0)
		{
			RefreshObject(list[i]);
			selection.Remove(pos);
		}
	}
	if(selection.GetCount() != count && !selection.IsEmpty())
		RefreshObject(selection.Top());
	WhenRescan();
}

void PathEditorCtrl::RefreshSelection()
{
	for(int i = 0; i < selection.GetCount(); i++)
		RefreshObject(selection[i]);
}

void PathEditorCtrl::ClearSelection()
{
	RefreshSelection();
	selection.Clear();
	WhenRescan();
}

void PathEditorCtrl::SetSelection(const Vector<int>& list)
{
	ClearSelection();
	Append(selection, list);
//	selection <<= list;
	RefreshSelection();
	WhenRescan();
}

Pointf PathEditorCtrl::ClientToUnits(Point pt) const
{
	return Pointf(hruler.FromClient(pt.x), vruler.FromClient(pt.y));
}

Point PathEditorCtrl::UnitsToClient(Pointf pt) const
{
	return Point(hruler.ToClient(pt.x), vruler.ToClient(pt.y));
}

Rectf PathEditorCtrl::ClientToUnits(const Rect& rc) const
{
	return SortRectf(ClientToUnits(rc.TopLeft()), ClientToUnits(rc.BottomRight()));
}

Rect PathEditorCtrl::UnitsToClient(const Rectf& rc) const
{
	return RectSort(UnitsToClient(rc.TopLeft()), UnitsToClient(rc.BottomRight()));
}

Pointf PathEditorCtrl::Snap(Pointf pt) const
{
	if(IsNull(pt) || !setup.do_snap || setup.snap <= 1e-10)
		return pt;
	return Pointf(floor(pt.x / setup.snap + 0.5) * setup.snap, floor(pt.y / setup.snap + 0.5) * setup.snap);
}

Pointf PathEditorCtrl::ClientToSnap(Point pt) const
{
	return Snap(ClientToUnits(pt));
}

bool PathEditorCtrl::Key(dword key, int repcnt)
{
	Size shift(0, 0);
	switch(key)
	{
	case K_ADD:      if(setup.sample_width < MAX_WIDTH) { sample_width <<= ++setup.sample_width; UpdateSample(); } return true;
	case K_SUBTRACT: if(setup.sample_width > 0)         { sample_width <<= --setup.sample_width; UpdateSample(); }; return true;

	case K_LEFT:  shift.cx = -1; break;
	case K_UP:    shift.cy = -1; break;
	case K_RIGHT: shift.cx = +1; break;
	case K_DOWN:  shift.cy = +1; break;

	default:
		return DragDropCtrl::Key(key, repcnt);
	}

	if((shift.cx | shift.cy) && IsSelection())
	{
		Rectf ext = GetSelectionExtent();
		Rectf full = GetFullExtent();
		Pointf snap(setup.snap, setup.snap);
		if(!setup.do_snap || setup.snap <= 1e-10)
			snap = Pointf(1, 1) / GetScale();
		Pointf step = snap * shift;
		Pointf lt = traces[GetSelectionLeader()].LeftTop();
		if(step.x)
			step.x = snap.x * floor((lt.x + step.x) / snap.x + 0.5) - lt.x;
		if(step.y)
			step.y = snap.y * floor((lt.y + step.y) / snap.y + 0.5) - lt.y;
		for(int i = 0; i < selection.GetCount(); i++)
			SetObject(selection[i], traces[selection[i]].GetMove(step));
	}

	return true;
}

bool PathEditorCtrl::Push(Point pt, dword keyflags)
{
	if(edit_mode == EDIT_ZOOM)
	{
		drag_mode = DRAG_ZOOM;
		return true;
	}
	if(edit_mode == EDIT_PAN)
	{
		track_start = GetDelta();
		drag_mode = DRAG_PAN;
		return true;
	}

	track_limit = GetFullExtent();

	Pointf up = ClientToUnits(pt);
	if(track_style = GetTrackStyle(up, track_start))
	{
		drag_mode = DRAG_TRACK;
		return true;
	}
	int i = FindObject(up);
	if(i < 0)
	{
		if((drag_mode = GetDragSize(up)) == 0)
			drag_mode = (keyflags & (K_SHIFT | K_CTRL) ? DRAG_SELECT : DRAG_INSERT);
		return true;
	}
	if(keyflags & K_CTRL)
		XorSelection(i);
	else if(keyflags & K_SHIFT)
	{
		RemoveSelection(i);
		AddSelection(i);
	}
	else if(selection.Find(i) < 0)
		SetSelection(i);
	track_start = traces[i].LeftTop();
	drag_mode = DRAG_MOVE;
	Rectf rc = GetSelectionExtent();
	track_limit.left   -= rc.left;
	track_limit.top    -= rc.top;
	track_limit.right  -= rc.right;
	track_limit.bottom -= rc.bottom;
	return true;
}

void PathEditorCtrl::MouseMove(Point pt, dword keyflags)
{
	Pointf up = ClientToSnap(pt);
//	hruler.Mouse(up.x);
	hruler.Sync();
//	vruler.Mouse(up.y);
	vruler.Sync();
	if(coords)
	{
		String s;
		s << "x = " << up.x << ", y = " << up.y;
		*coords <<= s;
		coords->Sync();
	}

	DragDropCtrl::MouseMove(pt, keyflags);
}

void PathEditorCtrl::RightDown(Point pt, dword keyflags)
{
	Pointf up = ClientToUnits(pt);
	int i = FindObject(up);
	if(i >= 0)
	{
		RemoveSelection(i);
		AddSelection(i);
	}
	MenuBar::Execute(THISBACK(ToolEdit));
}

void PathEditorCtrl::Drag(Point pt, Point last, Point next, dword keyflags)
{
	ViewPlotter plotter(this);
	PathTool path;
	bool drag_size = (drag_mode == DRAG_BEGIN || drag_mode == DRAG_SEGMENT || drag_mode == DRAG_END);
	path.Set(plotter, ".dot", drag_mode == DRAG_SELECT ? LtRed : drag_size ? Green : LtBlue, 3);
	Pointf up = ClientToSnap(pt);
//	int rop = SetROP2(plotter.draw, R2_NOTXORPEN);
	if(drag_mode == DRAG_INSERT)
	{
		if(!IsNull(next))
			path.Rectangle(SortRectf(up, ClientToSnap(next)) & track_limit, true);
		if(!IsNull(last))
			path.Rectangle(SortRectf(up, ClientToSnap(last)) & track_limit, true);
	}
	else if(drag_mode == DRAG_SELECT)
	{
		up = ClientToUnits(pt);
		if(!IsNull(next))
			path.Rectangle(SortRectf(up, ClientToUnits(next)), true);
		if(!IsNull(last))
			path.Rectangle(SortRectf(up, ClientToUnits(last)), true);
	}
	else if(drag_mode == DRAG_TRACK)
	{
		if(IsSelection())
		{
			const PathStyle::Trace& base = traces[selection.Top()];
			if(!IsNull(next))
				PathStyle::Trace(base).Track(ClientToSnap(next) - track_start, track_style).Bind(track_limit).Paint(path);
			if(!IsNull(last))
				PathStyle::Trace(base).Track(ClientToSnap(last) - track_start, track_style).Bind(track_limit).Paint(path);
		}
		else
			NEVER();
	}
	else if(drag_mode == DRAG_MOVE)
	{
		if(IsSelection())
		{
			Size delta = UnitsToClient(track_start) - pt;
			Pointf un(Null), ul(Null);
			if(!IsNull(next))
				un = fpminmax(ClientToSnap(next + delta) - track_start, track_limit);
			if(!IsNull(last))
				ul = fpminmax(ClientToSnap(last + delta) - track_start, track_limit);
			for(int i = 0; i < selection.GetCount(); i++)
			{
				const PathStyle::Trace& base = traces[selection[i]];
				if(!IsNull(next))
					base.GetMove(un).Paint(path, true);
				if(!IsNull(last))
					base.GetMove(ul).Paint(path, true);
			}
		}
		else
			NEVER();
	}
	else if(drag_size)
	{
		Pointf size = Pointf(16, 16) / GetScale();
		if(!IsNull(next))
		{
			Pointf un = ClientToSnap(next);
			PaintSizeBreak(path, un.x, style->width / 2, size);
		}
		if(!IsNull(last))
		{
			Pointf ul = ClientToSnap(last);
			PaintSizeBreak(path, ul.x, style->width / 2, size);
		}
	}
	else if(drag_mode == DRAG_ZOOM)
	{
		up = ClientToUnits(pt);
		if(!IsNull(next))
			path.Rectangle(SortRectf(up, ClientToUnits(next)), true);
		if(!IsNull(last))
			path.Rectangle(SortRectf(up, ClientToUnits(last)), true);
		path.Paint();
	}
	else if(drag_mode == DRAG_PAN)
	{
		Pointf new_offset = track_start;
		if(!IsNull(next))
			new_offset += next - pt;
		hruler.SetZoom(hruler.GetScale(), new_offset.x);
		vruler.SetZoom(vruler.GetScale(), new_offset.y);
		UpdateScroll();
		if(!IsNull(next))
			Sync();
	}
	else
		NEVER();
	path.Paint();
}

void PathEditorCtrl::Drop(Point pt, Point end, dword keyflags)
{
	Pointf up = ClientToSnap(end);
	if(drag_mode == DRAG_INSERT)
	{
		Rectf rc = SortRectf(ClientToSnap(pt), up) & track_limit;
		if(rc.left == rc.right && rc.top == rc.bottom)
			return;
		PathStyle::Trace trace;
		trace.left = rc.left;
		trace.right = rc.right;
		trace.left_top = trace.right_top = rc.top;
		trace.left_bottom = trace.right_bottom = rc.bottom;
		SetSelection(AddObject(trace));
	}
	else if(drag_mode == DRAG_SELECT)
	{
		Rectf rc = SortRectf(ClientToUnits(pt), ClientToUnits(end));
		Vector<int> list = FindObject(rc);
		if(keyflags & K_SHIFT)
			AddSelection(list);
		else if(keyflags & K_CTRL)
			XorSelection(list);
		else
			SetSelection(list);
	}
	else if(drag_mode == DRAG_TRACK)
	{
		up -= track_start;
		if(IsSelection())
		{
			int i = selection.Top();
			SetObject(i, PathStyle::Trace(traces[i]).Track(up, track_style).Bind(track_limit));
		}
		else
			NEVER();
	}
	else if(drag_mode == DRAG_MOVE)
	{
		Size delta = UnitsToClient(track_start) - pt;
		up = fpminmax(ClientToSnap(end + delta) - track_start, track_limit);
		for(int i = 0; i < selection.GetCount(); i++)
			SetObject(selection[i], traces[selection[i]].GetMove(up));
	}
	else if(drag_mode == DRAG_BEGIN || drag_mode == DRAG_SEGMENT || drag_mode == DRAG_END)
	{
		double lse = style->begin + style->segment;
		double le = lse + style->end;
		switch(drag_mode)
		{
		case DRAG_BEGIN:
			style->begin = max(0.0, up.x);
			style->segment = max(0.0, lse - style->begin);
			style->end = max(0.0, le - style->segment - style->begin);
			break;

		case DRAG_SEGMENT:
			style->segment = up.x - style->begin;
			if(style->segment < 0)
			{
				style->begin = max(0.0, style->begin + style->segment);
				style->segment = 0;
			}
			style->end = max(0.0, le - style->begin - style->segment);
			break;

		case DRAG_END:
			style->end = up.x - style->segment - style->begin;
			if(style->end < 0)
			{
				if(up.x < style->begin)
					style->begin = max(0.0, up.x);
				if(up.x < style->begin + style->segment)
					style->segment = max(0.0, up.x - style->begin);
				style->end = 0;
			}
			break;

		default:
			NEVER();
		}
		Layout();
		UpdateSample();
	}
	else if(drag_mode == DRAG_ZOOM)
	{
		Rectf rc = SortRectf(ClientToUnits(pt), ClientToUnits(end));
		rc &= GetFullExtent();
		Size client = GetSize();
		Size avail = max(client - 2 * GAP, Size(1, 1));
		double ratio = min(avail.cx / max(rc.Width(), 1e-3), avail.cy / max(rc.Height(), 1e-3));
		hruler.SetZoom(ratio, (client.cx - (rc.left + rc.right)  * ratio) / 2);
		vruler.SetZoom(ratio, (client.cy - (rc.top  + rc.bottom) * ratio) / 2);
		UpdateScroll();
	}
	else if(drag_mode == DRAG_PAN)
	{
		Pointf dest = track_start + Sizef(end - pt);
		hruler.SetZoom(hruler.GetScale(), dest.x);
		vruler.SetZoom(vruler.GetScale(), dest.y);
		UpdateScroll();
	}
	else
		NEVER();
}

void PathEditorCtrl::Click(Point pt, dword keyflags)
{
	if(drag_mode == DRAG_INSERT && !(keyflags & (K_CTRL | K_SHIFT)))
		ClearSelection();
}

int PathEditorCtrl::FindObject(Pointf pt) const
{
	enum { TOLERANCE = 10 };
	Pointf inflate = Pointf(TOLERANCE, TOLERANCE) / GetScale();
	double best = TOLERANCE;
	int found = -1;
	for(int i = 0; i < traces.GetCount(); i++)
	{
		PathStyle::Trace t = traces[i];
		t.Inflate(inflate);
		if(!t.Contains(pt))
			continue;
		double d = t.GetDistance(pt);
		if(d < best)
		{
			best = d;
			found = i;
		}
	}
	return found;
}

Vector<int> PathEditorCtrl::FindObject(const Rectf& rc) const
{
	Vector<int> list;
	for(int i = 0; i < traces.GetCount(); i++)
		if(rc.Contains(traces[i].GetExtent()))
			list.Add(i);
	return list;
}

int PathEditorCtrl::GetTrackStyle(Pointf pt, Pointf& track_start) const
{
	Pointf tolerance = Pointf(10, 10) / GetScale();
	return selection.IsEmpty() ? 0
		: traces[selection.Top()].GetTrackStyle(pt, tolerance, track_start);
}

int PathEditorCtrl::GetDragSize(Pointf pt) const
{
	Pointf tolerance = Pointf(16, 16) / GetScale();
	double h = style->width / 2;
	if(pt.y >= -h + tolerance.y && pt.y <= h - tolerance.y)
		return 0;
	double db = fabs(pt.x - style->begin);
	double ds = fabs(pt.x - style->begin - style->segment);
	double de = fabs(pt.x - style->begin - style->segment - style->end);
	if(db >= tolerance.x && ds >= tolerance.x && de >= tolerance.x)
		return 0;
	return (ds <= db && ds <= de ? DRAG_SEGMENT : de <= db && de <= ds ? DRAG_END : DRAG_BEGIN);
}

void PathEditorCtrl::ToolEdit(Bar& bar)
{
	ToolEditColor(bar);
	bar.Separator();
	ToolEditCut(bar);
	ToolEditCopy(bar);
	ToolEditPaste(bar);
	ToolEditDelete(bar);
	bar.Separator();
//	bar.Add("Základní", THISBACK(ToolEditBasic))
//		.Help("Načíst do editoru jeden ze základních stylů čár");
//	bar.Separator();
	ToolEditSelectAll(bar);
}

void PathEditorCtrl::ToolEditCopy(Bar& bar)
{
	bar.Add(IsSelection(), "Kopírovat", CtrlImg::copy(), THISBACK(OnEditCopy))
		.Key(K_CTRL_C) //, K_CTRL_INSERT)
		.Help("Zkopírovat vybrané objekty do schránky");
}

void PathEditorCtrl::OnEditCopy()
{
	if(IsSelection() && !WriteClipboard())
		PromptOK("Chyba při zápisu do schránky.");
}

void PathEditorCtrl::ToolEditCut(Bar& bar)
{
	bar.Add(IsSelection(), "Vyjmout", CtrlImg::cut(), THISBACK(OnEditCut))
		.Key(K_CTRL_X) //, K_CTRL_DELETE)
		.Help("Odstranit vybrané objekty ze stylu a přesunout je do schránky");
}

void PathEditorCtrl::OnEditCut()
{
	if(IsSelection())
		if(WriteClipboard())
			OnEditDelete();
		else
			PromptOK("Chyba při zápisu do schránky.");
}

void PathEditorCtrl::ToolEditPaste(Bar& bar)
{
	bar.Add("Vložit", CtrlImg::paste(), THISBACK(OnEditPaste))
		.Key(K_CTRL_V) //, K_SHIFT_INSERT)
		.Help("Zkopírovat vybrané objekty do schránky");
}

void PathEditorCtrl::OnEditPaste()
{
	if(IsSelection() && !ReadClipboard())
		PromptOK("Chyba při zápisu do schránky.");
}

void PathEditorCtrl::ToolEditSelectAll(Bar& bar)
{
	bar.Add("Vybrat vše", THISBACK(OnEditSelectAll))
		.Key(K_CTRL_A)
		.Help("Označit všechny úseky jako vybrané");
}

void PathEditorCtrl::OnEditSelectAll()
{
	ClearSelection();
	Vector<int> to_add;
	for(int i = 0; i < traces.GetCount(); i++)
		to_add.Add(i);
	AddSelection(to_add);
}

void PathEditorCtrl::ToolEditColor(Bar& bar)
{
	bar.Add(IsSelection(), "Barva...", PathImg::edit_color(), THISBACK(OnEditColor))
		.Key(K_CTRL_R)
		.Help("Nastavit barvu vybraných objektů");
}

void PathEditorCtrl::OnEditColor()
{
	if(!IsSelection())
		return;
	bool ok;
	Color c = RunDlgSelectColor(traces[GetSelectionLeader()].color, false, "Barva objektů...", &ok);
	if(ok)
		for(int i = 0; i < selection.GetCount(); i++)
			SetObject(selection[i], traces[selection[i]].GetTraceColor(c));
}

void PathEditorCtrl::ToolEditDelete(Bar& bar)
{
	bar.Add(IsSelection(), "Smazat", CtrlImg::remove(), THISBACK(OnEditDelete))
		.Key(K_DELETE)
		.Help("Smazat vybrané objekty");
}

void PathEditorCtrl::OnEditDelete()
{
	RefreshSelection();
	Vector<int> sel = selection.PickKeys();
	selection.Clear();
	Sort(sel);
	while(!sel.IsEmpty())
		traces.Remove(sel.Pop());
	WhenRescan();
	Update();
	UpdateSample();
}

void PathEditorCtrl::ToolView(Bar& bar)
{
	ToolViewZoomIn(bar);
	ToolViewZoomOut(bar);
	if(bar.IsMenuBar())
		ToolViewZoomFull(bar);
	ToolViewPan(bar);
	bar.MenuSeparator();
	ToolViewZoomHorzIn(bar);
	ToolViewZoomHorzOut(bar);
	ToolViewZoomVertIn(bar);
	ToolViewZoomVertOut(bar);
}

void PathEditorCtrl::ToolViewZoomIn(Bar& bar)
{
	bar.Add("Zvětšit", PathImg::view_zoom_in(), THISBACK(OnViewZoomIn))
		.Check(edit_mode == EDIT_ZOOM)
		.Help("Zvětšit vybrané místo nebo oblast");
}

void PathEditorCtrl::OnViewZoomIn()
{
	edit_mode = (edit_mode == EDIT_ZOOM ? EDIT_NORMAL : EDIT_ZOOM);
	WhenRescan();
}

void PathEditorCtrl::ToolViewZoomOut(Bar& bar)
{
	bar.Add("Zmenšit", PathImg::view_zoom_out(), THISBACK(OnViewZoomOut))
		.Help("Zmenšit měřítko zobrazení stylu čáry");
}

void PathEditorCtrl::OnViewZoomOut()
{
	OnViewZoomHorzOut();
	OnViewZoomVertOut();
}

void PathEditorCtrl::ToolViewZoomFull(Bar& bar)
{
	bar.Add("Podle okna", PathImg::view_zoom_full(), THISBACK(OnViewZoomFull))
		.Help("Nastavit měřítko zobrazení podle velikosti okna");
}

void PathEditorCtrl::OnViewZoomFull()
{
	OnViewZoomHorzFull();
	OnViewZoomVertFull();
}

void PathEditorCtrl::ToolViewZoomHorzIn(Bar& bar)
{
	bar.Add("Horiz. zvětšit", THISBACK(OnViewZoomHorzIn))
		.Help("Zvětšit měřítko vodorovné osy");
}

void PathEditorCtrl::OnViewZoomHorzIn()
{
	int half = GetSize().cx >> 1;
	double mpos = hruler.FromClient(half);
	hruler.SetZoom(min(hruler.GetScale() * 1.5, 1000.0), 0);
	hruler.SetZoom(hruler.GetScale(), half - hruler.ToClient(mpos));
	UpdateScroll();
}

void PathEditorCtrl::ToolViewZoomHorzOut(Bar& bar)
{
	bar.Add("Horiz. zmenšit", THISBACK(OnViewZoomHorzOut))
		.Help("Zmenšit měřítko vodorovné osy");
}

void PathEditorCtrl::OnViewZoomHorzOut()
{
	int half = GetSize().cx >> 1;
	double mpos = hruler.FromClient(half);
	hruler.SetZoom(max(hruler.GetScale() / 1.5, 1.0), 0);
	hruler.SetZoom(hruler.GetScale(), half - hruler.ToClient(mpos));
	UpdateScroll();
}

void PathEditorCtrl::ToolViewZoomHorzFull(Bar& bar)
{
	bar.Add("Horiz. podle okna", THISBACK(OnViewZoomHorzFull))
		.Help("Nastavit vodorovné měřítko podle velikosti okna");
}

void PathEditorCtrl::OnViewZoomHorzFull()
{
	int avail = GetSize().cx - 2 * GAP;
	double wd = max(1e-3, style->begin + style->segment + style->end);
	hruler.SetZoom(avail / wd, GAP);
	UpdateScroll();
}

void PathEditorCtrl::ToolViewZoomVertIn(Bar& bar)
{
	bar.Add("Vert. zvětšit", THISBACK(OnViewZoomVertIn))
		.Help("Zvětšit měřítko svislé osy");
}

void PathEditorCtrl::OnViewZoomVertIn()
{
	int half = GetSize().cy >> 1;
	double mpos = vruler.FromClient(half);
	vruler.SetZoom(min(vruler.GetScale() * 1.5, 1000.0), 0);
	vruler.SetZoom(vruler.GetScale(), half - vruler.ToClient(mpos));
	UpdateScroll();
}

void PathEditorCtrl::ToolViewZoomVertOut(Bar& bar)
{
	bar.Add("Vert. zmenšit", THISBACK(OnViewZoomVertOut))
		.Help("Zmenšit měřítko svislé osy");
}

void PathEditorCtrl::OnViewZoomVertOut()
{
	int half = GetSize().cy >> 1;
	double mpos = vruler.FromClient(half);
	vruler.SetZoom(max(vruler.GetScale() / 1.5, 1.0), 0);
	vruler.SetZoom(vruler.GetScale(), half - vruler.ToClient(mpos));
	UpdateScroll();
}

void PathEditorCtrl::ToolViewZoomVertFull(Bar& bar)
{
	bar.Add("Vert. podle okna", THISBACK(OnViewZoomVertFull))
		.Help("Nastavit měřítko svislé osy podle velikosti okna");
}

void PathEditorCtrl::OnViewZoomVertFull()
{
	int avail = GetSize().cy - 2 * GAP;
	double ht = max(1e-3, style->width);
	vruler.SetZoom(avail / ht, GetSize().cy >> 1);
	UpdateScroll();
}

void PathEditorCtrl::ToolViewPan(Bar& bar)
{
	bar.Add("Posunout", PathImg::view_pan(), THISBACK(OnViewPan))
		.Check(edit_mode == EDIT_PAN)
		.Help("Posunout myší zobrazený výřez");
}

void PathEditorCtrl::OnViewPan()
{
	edit_mode = (edit_mode == EDIT_PAN ? EDIT_NORMAL : EDIT_PAN);
	WhenRescan();
}

void PathEditorCtrl::ToolSetup(Bar& bar)
{
	ToolSetupGrid(bar);
	ToolSetupSnap(bar);
	ToolSetupRuler(bar);
	ToolSetupAxis(bar);
//	bar.ThinBar();
	bar.Separator();
	ToolSetupStyle(bar);
	ToolSetupSetup(bar);
	if(bar.IsToolBar())
	{
		bar.Separator();
//		bar.ThinBar();
		bar.Add(sample_size_tag);
		bar.Add(sample_size.SizePos(), 80);
		bar.Add(sample_width_tag);
		bar.Add(sample_width.SizePos(), 40);
	}
}

void PathEditorCtrl::ToolSetupGrid(Bar& bar)
{
	bar.Add("Mřížka", PathImg::setup_grid(), THISBACK(OnSetupGrid))
		.Check(setup.do_grid)
		.Help("Zobrazit / skrýt pomocnou mřížku");
}

void PathEditorCtrl::OnSetupGrid()
{
	setup.do_grid = !setup.do_grid;
	WhenRescan();
	Refresh();
}

void PathEditorCtrl::ToolSetupRuler(Bar& bar)
{
	bar.Add("Pravítko", PathImg::setup_ruler(), THISBACK(OnSetupRuler))
		.Check(setup.do_ruler)
		.Help("Zobrazit / skrýt pravítko");
}

void PathEditorCtrl::OnSetupRuler()
{
	setup.do_ruler = !setup.do_ruler;
	WhenRescan();
	Layout();
}

void PathEditorCtrl::ToolSetupAxis(Bar& bar)
{
	bar.Add("Osa", PathImg::setup_axis(), THISBACK(OnSetupAxis))
		.Check(setup.do_axis)
		.Help("Zobrazit / skrýt osu ukázkové úsečky");
}

void PathEditorCtrl::OnSetupAxis()
{
	setup.do_axis = !setup.do_axis;
	WhenRescan();
	UpdateSample();
}

void PathEditorCtrl::ToolSetupSnap(Bar& bar)
{
	bar.Add("Přichytit do mřížky", PathImg::setup_snap(), THISBACK(OnSetupSnap))
		.Check(setup.do_snap)
		.Help("Přichytit souřadnice myši do mřížky");
}

void PathEditorCtrl::OnSetupSnap()
{
	setup.do_snap = !setup.do_snap;
	WhenRescan();
	Refresh();
}

void PathEditorCtrl::ToolSetupStyle(Bar& bar)
{
	bar.Add("Styl čáry", THISBACK(OnSetupStyle))
		.Help("Zobrazit/změnit vlastnosti stylu čáry");
}

void PathEditorCtrl::OnSetupStyle()
{
	if(RunDlgPathStyleSetup(*style))
	{
		WhenRescan();
		Layout();
		UpdateSample();
	}
}

void PathEditorCtrl::ToolSetupSetup(Bar& bar)
{
	bar.Add("Editor", PathImg::setup_setup(), THISBACK(OnSetupSetup))
		.Help("Zobrazit/změnit vlastnosti editoru stylů čar");
}

void PathEditorCtrl::OnSetupSetup()
{
	if(RunDlgPathEditorSetup(setup))
	{
		WhenRescan();
		Layout();
	}
}

//////////////////////////////////////////////////////////////////////
// DlgPathEditor::

class DlgPathEditor
{
public:
	typedef DlgPathEditor CLASSNAME;
	DlgPathEditor();

	bool              Run(PathStyle& style, const String& title);
	void              Serialize(Stream& stream);
	static ConfigItem& config();

	void              Rescan() { tool_bar.Set(THISBACK(ToolRoot)); menu_bar.Set(THISBACK(ToolRoot)); }

public:
	TOOL(Root)
	TOOL(Edit)
		TOOL(EditSave)

private:
	TopWindow         dialog;
	MenuBar           menu_bar;
	ToolBar           tool_bar;
	StatusBar         status_bar;
	EditField         coords;

	PathEditorCtrl    path;
};

CONFIG_ITEM(DlgPathEditor::config, "DlgPathEditor", 1, 1, 1)

bool RunDlgPathEditor(PathStyle& style, const String& title) { return DlgPathEditor().Run(style, title); }

//////////////////////////////////////////////////////////////////////
// DlgPathEditor::

DlgPathEditor::DlgPathEditor()
{
#ifdef DEBUG_DRAW
	dialog.NoBackPaint();
#endif
	dialog.AddFrame(menu_bar);
	dialog.AddFrame(tool_bar);
	dialog.AddFrame(status_bar);
	status_bar << coords.VSizePos(0, 0).RightPos(0, 200);
	coords.SetReadOnly();
	menu_bar.Set(THISBACK(ToolRoot));
	tool_bar.Set(THISBACK(ToolRoot));
	dialog << path.SizePos();
//	dialog.SetRect(CalcWindowRect(dialog, Ctrl::GetWorkStock.GetScreenSize() >> 1));
	dialog.Sizeable().Zoomable();
	dialog.SetMinSize(Size(300, 200));
	path.WhenRescan = THISBACK(Rescan);
}

bool DlgPathEditor::Run(PathStyle& style, const String& title)
{
	dialog.Title(Nvl(title, "Styl čáry").ToWString());
	ReadConfigSelf();
	path.SetOwner(style);
	path.SetCoords(coords);
	path.PumpTraces(false);
	dialog.Open();
	path.OnViewZoomFull();
	bool ok = (dialog.Run() == IDOK);
	WriteConfigSelf();
	if(ok)
		path.PumpTraces(true);
	Ctrl::IgnoreMouseUp();
	return ok;
}

void DlgPathEditor::Serialize(Stream& stream)
{
	stream % path.setup;
	if(stream.IsLoading())
		path.UpdateSetup();
}

void DlgPathEditor::ToolRoot(Bar& bar)
{
	bar.Add("Edit", THISBACK(ToolEdit))
		.Help("Základní editační příkazy");
	bar.Add("Pohled", callback(&path, &PathEditorCtrl::ToolView))
		.Help("Měřítko a poloha zobrazeného výřezu");
	bar.Add("Vlastnosti", callback(&path, &PathEditorCtrl::ToolSetup))
		.Help("Vlastnosti čáry a editoru");
}

void DlgPathEditor::ToolEdit(Bar& bar)
{
	if(bar.IsToolBar())
	{
		ToolEditSave(bar);
//		bar.ThinBar();
		bar.Separator();
	}
	path.ToolEdit(bar);
	if(bar.IsMenuBar())
	{
		bar.Separator();
		ToolEditSave(bar);
	}
}

void DlgPathEditor::ToolEditSave(Bar& bar)
{
	bar.Add("Uložit a zavřít", CtrlImg::save(), THISBACK(OnEditSave))
		.Key(K_CTRL_S)
		.Help("Zavřít editor a použít nadefinovaný styl čáry");
}

void DlgPathEditor::OnEditSave()
{
	dialog.AcceptBreak(IDOK);
}

template <class T>
class WithPopupEdit : public T
{
public:
	WithPopupEdit();

	virtual void Deactivate();
	virtual bool Key(dword key, int);
	bool         PopUp(Ctrl *parent, const Rect& prect);

public:
	Callback     WhenCancel;
	Callback     WhenSelect;

protected:
	virtual void DoClose(bool ok);

protected:
	bool         open;
};

template <class T>
WithPopupEdit<T>::WithPopupEdit()
{
	this->SetFrame(BlackFrame());
	open = false;
}

template <class T>
void WithPopupEdit<T>::Deactivate()
{
	if(open) {
		if(!this->Accept())
		{
			this->SetFocus();
			return;
		}
		DoClose(true);
		this->IgnoreMouseClick();
		WhenSelect();
	}
}

template <class T>
void WithPopupEdit<T>::DoClose(bool ok)
{
	open = false;
//	EndModalLoop(ok ? 1 : 0);
	this->EndLoop(ok ? 1 : 0);
}

template <class T>
bool WithPopupEdit<T>::Key(dword key, int repcnt)
{
	if(key == K_ENTER)
	{
		if(this->Accept())
		{
			DoClose(true);
			WhenSelect();
		}
		return true;
	}
	else if(key == K_ESCAPE)
	{
		this->Reject();
		DoClose(false);
		WhenCancel();
		return true;
	}
	return T::Key(key, repcnt);
}

template <class T>
bool WithPopupEdit<T>::PopUp(Ctrl *parent, const Rect& prect)
{
	ASSERT(parent);
	if(!this->IsOpen())
	{
		Ctrl *wnd = parent->GetTopCtrl();
		if(!wnd || !wnd->IsOpen() || !wnd->IsVisible())
			return false;
		open = false;
		this->SetRect(prect);
		T::PopUp(parent);
	}
	this->Enable();
	this->SetFocus();
	open = true;
	EventLoop(this);
	bool ok = !!GetExitCode();
//	bool ok = !!RunModalLoop();
	this->Disable();
	return ok;
}

PathStyleMapCtrl::PathStyleMapCtrl()
{
	cursor = -1;
	map = 0;
	scroll_pos = 0;
	SetFrame(InsetFrame());
	scroll.NoAutoHide().NoAutoDisable();
	scroll.WhenScroll = THISBACK(OnScroll);
	AddFrame(scroll);
	WhenBar = THISBACK(ToolLocal);
}

void PathStyleMapCtrl::Set(PathStyleMap *m)
{
	map = m;
	Layout();
}

bool PathStyleMapCtrl::Key(dword key, int repcnt)
{
	int c = cursor;
	int pg = max(1, GetSize().cy / pos_add.cx - 1);
	switch(key)
	{
	case K_LEFT:     c--; break;
	case K_RIGHT:    c++; break;
	case K_UP:       c -= count.cx; break;
	case K_DOWN:     c += count.cx; break;
	case K_PAGEUP:   c -= count.cx * pg; break;
	case K_PAGEDOWN: c += count.cx * pg; break;
	case K_HOME:     c = 0; break;
	case K_END:      c = map->GetCount() - 1; break;
	default:
		return Bar::Scan(WhenBar, key) || Ctrl::Key(key, repcnt);
	}
	SetCursor(minmax(c, 0, map->GetCount() - 1));
	return true;
}

void PathStyleMapCtrl::Layout()
{
	if(!map)
	{
		count = total = Size(0, 0);
		pos_add = Size(1, 1);
		cell = Size(0, 0);
		gap_offset = offset = Point(0, 0);
		scroll_pos = 0;
		return;
	}
	Size size = scroll.GetReducedViewSize();
	count.cx = max(1, (size.cx - GAP) / WIDTH);
	count.cy = idivceil(map->map.GetCount(), count.cx);
	pos_add.cx = (size.cx - GAP) / count.cx;
	pos_add.cy = HEIGHT + GAP;
	cell = pos_add - GAP;
	total = count * pos_add - GAP;
	offset.x = (size.cx - total.cx) >> 1;
	offset.y = GAP;
	gap_offset.x = offset.x - (GAP >> 1);
	gap_offset.y = GAP >> 1;
	scroll.Set(scroll_pos, size.cy, 2 * GAP + total.cy);
	scroll_pos = scroll;
	Refresh();
}

Rect PathStyleMapCtrl::GetEditRect(int right, int bottom)
{
	Size size = PathImg::rename().GetSize();
	return RectC(right - size.cx, bottom - size.cy, size.cx, size.cy);
}

bool PathStyleMapCtrl::InEditRect(int right, int bottom, Point mouse)
{
	Rect rc = GetEditRect(right, bottom).Inflated(2);
	if(!rc.Contains(mouse))
		return false;
	Point pt = rc.CenterPoint();
	return mouse.x + mouse.y >= pt.x + pt.y;
}

void PathStyleMapCtrl::Paint(Draw& draw)
{
	Rect rc = draw.GetPaintRect();
	draw.Begin();
	Size todo = ClientToRange(rc);
	for(int i = todo.cx; i < todo.cy; i++)
	{
		Rect item = IndexToClient(i);
		if(item && rc)
		{
			DrawFrame(draw, item, Black, White);
			draw.DrawRect(item.Deflated(1), i == cursor ? LtCyan : SLtGray);
			Rect box = item.Deflated(IGAP);
			draw.Clipoff(Rect(box.left, box.top, box.right, box.top + TEXT_HEIGHT));
			String qtf;
			qtf << "[=A+108";
			if(i == cursor)
				qtf << '*';
			qtf << " \1" << map->GetSortName(i);
			Document doc(qtf);
			doc.Paint(DOC_SCREEN_ZOOM, draw, 0, 0, box.Width(), SLtGray);
			draw.DrawImage(GetEditRect(box.Width(), TEXT_HEIGHT), PathImg::rename());
			draw.End();
			draw.Clipoff(Rect(box.left, box.top + TEXT_HEIGHT, box.right, box.bottom));
			PathDraw path;
			path.Set(draw, map->GetSortStyle(i), Black, LINE_HEIGHT / 2);
			Rect er = GetEditRect(box.Width(), box.Height() - TEXT_HEIGHT);
			path.Line(0, LINE_HEIGHT / 2, er.left, LINE_HEIGHT / 2);
			path.Paint();
			draw.DrawImage(er, PathImg::edit());
			draw.End();
			draw.ExcludeClip(item);
		}
	}
	draw.DrawRect(rc, SLtGray);
	draw.End();
}

int PathStyleMapCtrl::ClientToIndex(Point pt) const
{
	pt = idivfloor(pt - gap_offset + Size(0, scroll_pos), pos_add);
	if(pt.x < 0 || pt.x >= count.cx || pt.y < 0)
		return -1;
	return pt.x + pt.y * count.cx;
}

Size PathStyleMapCtrl::ClientToRange(const Rect& rc) const
{
	if(rc.IsEmpty())
		return Size(-1, -1);
	int top = idivfloor(rc.top - gap_offset.y + scroll_pos, pos_add.cy);
	int bottom = idivfloor(rc.bottom - 1 - gap_offset.y + scroll_pos, pos_add.cy);
	int left = idivfloor(rc.left - gap_offset.x, pos_add.cx);
	int right = idivfloor(rc.right - 1 - gap_offset.x, pos_add.cx);
	if(bottom < 0 || right < 0 || left >= count.cx)
		return Size(-1, -1);
	if(top < bottom) // multiple lines
		return Size(max(0, count.cx * top), min(map->GetCount(), count.cx * (bottom + 1)));
	return Size(max(0, count.cx * top + max(0, left)), min(map->GetCount(), count.cx * top + min(count.cx, right + 1)));
}

Rect PathStyleMapCtrl::IndexToClient(int i) const
{
	return Rect(offset - Size(0, scroll_pos) + pos_add * Size(i % count.cx, i / count.cx), cell);
}

void PathStyleMapCtrl::OnScroll()
{
	scroll_pos = scroll;
	Refresh();
}

void PathStyleMapCtrl::LeftDown(Point pt, dword keyflags)
{
	int i = ClientToIndex(pt);
	if(IsValid(i))
		SetCursor(i);
	SetWantFocus();
	Rect rc = IndexToClient(i).Deflated(IGAP);
	if(InEditRect(rc.right, rc.top + TEXT_HEIGHT, pt))
		OnRename();
	if(InEditRect(rc.right, rc.bottom, pt))
		OnEdit();
}

void PathStyleMapCtrl::LeftDouble(Point pt, dword keyflags)
{
	int i = ClientToIndex(pt);
	if(IsCursor() && i == cursor)
		WhenLeftDouble();
}

void PathStyleMapCtrl::LeftUp(Point pt, dword keyflags)
{
}

void PathStyleMapCtrl::RightDown(Point pt, dword keyflags)
{
	int i = ClientToIndex(pt);
	if(IsValid(i))
		SetCursor(i);
	SetWantFocus();
	MenuBar::Execute(this, WhenBar, UPP::GetMousePos());
}

void PathStyleMapCtrl::SetCursor(int c)
{
	RefreshItem(cursor);
	RefreshItem(cursor = c);
	if(IsCursor() && IsOpen() && IsVisible())
	{
		Rect rc = IndexToClient(c) + Size(0, scroll);
		rc.Inflate(GAP);
		scroll.ScrollInto(rc.top, rc.Height());
	}
}

void PathStyleMapCtrl::RefreshItem(int i)
{
	if(i >= 0 && i < map->map.GetCount())
		Refresh(IndexToClient(i));
}

void PathStyleMapCtrl::ToolLocal(Bar& bar)
{
	ToolNew(bar);
	ToolEdit(bar);
	ToolRename(bar);
	ToolCopy(bar);
	bar.Separator();
	ToolExport(bar);
	ToolImport(bar);
	bar.Separator();
	ToolRemove(bar);
}

void PathStyleMapCtrl::ToolNew(Bar& bar)
{
	bar.Add("Nový", THISBACK(OnNew));
}

void PathStyleMapCtrl::OnNew()
{
	PathStyle new_style;
	new_style <<= PathStyle::solid();
	String px = "Nový styl";
	if(RunDlgPathEditor(new_style, px))
	{
		String n = map->GetUniqueName(px);
		map->Set(n, new_style);
		map->Touch();
		Layout();
		SetCursor(n);
		OnRename();
	}
}

void PathStyleMapCtrl::ToolEdit(Bar& bar)
{
	bar.Add(IsCursor(), "Upravit", THISBACK(OnEdit));
}

void PathStyleMapCtrl::OnEdit()
{
	if(IsCursor())
	{
		int c = GetCursor();
		String n = map->GetSortName(c);
		PathStyle edited_style;
		edited_style <<= map->GetSortStyle(c);
		if(RunDlgPathEditor(edited_style, "Upravit styl '" + n + "'"))
		{
			map->Set(n, edited_style);
			map->Touch();
			RefreshItem(c);
		}
	}
}

void PathStyleMapCtrl::ToolRename(Bar& bar)
{
	bar.Add(IsCursor(), "Přejmenovat", THISBACK(OnRename));
}

void PathStyleMapCtrl::OnRename()
{
	if(!IsCursor())
	{
		BeepExclamation();
		return;
	}
	int i = GetCursor();
	Rect rc = IndexToClient(i) + Size(0, scroll);
	scroll.ScrollInto(rc.top, rc.Height());
	rc = IndexToClient(i) + GetScreenView().TopLeft();
	rc.bottom = rc.top + 2 * IGAP + TEXT_HEIGHT;
	rc.Deflate(IGAP);
	WithPopupEdit<DocEdit> pedit;
	pedit <<= map->GetSortName(i);
	while(pedit.PopUp(this, rc))
	{
		String n = ~pedit;
		if(map->FindSortName(n, i) >= 0)
		{ // duplicate name
			Exclamation("Styl čáry [* \1" + n + "\1] již v systému existuje. Zadejte prosím jiný název.");
			continue;
		}
		int x = map->GetSort(i);
		map->Rename(x, n);
		map->Touch();
		Refresh();
		SetCursor(n);
		break;
	}
}

void PathStyleMapCtrl::ToolCopy(Bar& bar)
{
	bar.Add("Duplikovat", THISBACK(OnCopy));
}

void PathStyleMapCtrl::OnCopy()
{
	if(IsCursor())
	{
		int c = GetCursor();
		String prefix = map->GetSortName(c);
		int l = prefix.GetLength();
		if(l > 0 && IsDigit(prefix[l - 1]))
		{
			while(l > 0 && IsDigit(prefix[l - 1]))
				l--;
			if(l > 0 && prefix[l - 1] == ' ')
				prefix.Trim(l - 1);
		}
		PathStyle new_style;
		new_style <<= map->GetSortStyle(c);
		String q = map->GetUniqueName(prefix);
		map->Set(q, new_style);
		map->Touch();
		Layout();
		SetCursor(q);
		OnRename();
	}
}

void PathStyleMapCtrl::ToolExport(Bar& bar)
{
	bar.Add("Export", THISBACK(OnExport))
		.Help("Exportovat styly čar do souboru");
}

String recent;

void PathStyleMapCtrl::OnExport()
{
	if(map->IsEmpty())
	{
		PromptOK("V systému nebyly nalezeny žádné styly čar.");
		return;
	}
	FileSelector fsel;
	fsel.Type("Styly čar (*.wml)", "*.wml")
		.DefaultExt("wml");
	fsel <<= recent;
	if(!fsel.ExecuteSaveAs("Exportovat styly čar..."))
		return;
	String exp = map->Export();
	if(!SaveFile(~fsel, exp))
		PromptOK("Chyba při zápisu do souboru [* \1" + ~fsel + "\1].");
}

void PathStyleMapCtrl::ToolImport(Bar& bar)
{
	bar.Add("Import", THISBACK(OnImport))
		.Help("Importovat styly čar ze souboru...");
}

void PathStyleMapCtrl::OnImport()
{
	FileSelector fsel;
	fsel.Type("Styly čar (*.wml)", "*.wml")
		.DefaultExt("wml");
	fsel <<= recent;
	if(!fsel.ExecuteOpen("Importovat styly čar"))
		return;
	String imp = LoadFile(recent = ~fsel);
	if(imp.IsVoid())
	{
		PromptOK("Nelze načíst soubor [* \1" + ~fsel + "\1].");
		return;
	}
	int old_count = map->GetCount();
	String old_name = map->GetName();
	try
	{
		map->Import(imp);
	}
	catch(Exc e)
	{
		PromptOK("Chyba při importu souboru [* \1" + ~fsel + "\1]: \1" + e + "\1.");
	}
	map->Touch();
	if((old_count > 0 && !IsNull(old_name)) || IsNull(map->GetName()))
		map->SetName(old_name);
	if(map->GetCount() > old_count)
		SetCursor(map->map.GetKey(old_count));
	WhenRename();
}

void PathStyleMapCtrl::ToolRemove(Bar& bar)
{
	bar.Add("Smazat", THISBACK(OnRemove))
		.Help("Odstranit styl čáry ze systému");
}

void PathStyleMapCtrl::OnRemove()
{
	if(IsCursor())
	{
		int c = cursor;
		map->Remove(map->GetSortName(c));
		map->Touch();
		c = min(c, map->GetCount() - 1);
		Layout();
		SetCursor(c);
	}
}

class PathStyleMapLayoutEx : public WithPathStyleMapLayout<TopWindow>
{
public:
	PathStyleMapCtrl browser;
};

//////////////////////////////////////////////////////////////////////
// DlgPathStyleMap::

class DlgPathStyleMap
{
public:
	typedef DlgPathStyleMap CLASSNAME;
	DlgPathStyleMap();

	bool                 Run(PathStyleMap& map, String& style, bool editor = false, bool read_only = false);

private:
//	void                 UpdateMaps();
//	void                 OnTab();

private:
	PathStyleMapLayoutEx dialog;
	PathStyleMap        *map;
//	const Vector<PathStyleMap *> *maps;
//	Vector<int> map_index;
};

bool RunDlgPathStyleMap(PathStyleMap& map, String& style, bool editor, bool read_only)
{ return DlgPathStyleMap().Run(map, style, editor, read_only); }

RegisterHelpTopicObjectTitle(DlgPathStyleMap, "Mapa stylů")

DlgPathStyleMap::DlgPathStyleMap()
{
	map = 0;
//	HelpLayoutOKCancel(dialog, this);
	CtrlLayoutOKCancel(dialog, DlgPathStyleMapHelpTitle());
	dialog.HelpTopic("DlgPathStyleMap");
	dialog.Sizeable().MaximizeBox();
//	dialog.browser.WhenRename = THISBACK(UpdateMaps);
	dialog.tab.Hide();
//	dialog.tab <<= THISBACK(OnTab);
}

bool DlgPathStyleMap::Run(PathStyleMap& m, String& style, bool editor, bool read_only)
{
	if(editor) {
		dialog.ok.Hide();
		dialog.cancel.SetLabel(t_("Close"));
	}

	map = &m;
//	ASSERT(!maps->IsEmpty());
//	UpdateMaps();

	dialog.browser.Set(map);
	dialog.browser.SetCursor(Nvl(style, ".solid"));
	while(dialog.Run() == IDOK) {
		int c = dialog.browser.GetCursor();
		if(c >= 0) {
			style = map->GetSortName(c);
			if(style == ".solid") style = Null;
			return true;
		}
		BeepExclamation();
	}
	return false;
}


/*
void DlgPathStyleMap::UpdateMaps()
{
	int tab = dialog.tab.Get();
	tab = (tab >= 0 && tab < map_index.GetCount() ? map_index[tab] : -1);
	map_index = GetSortOrder(*maps, XRelation<const PathStyleMap *>(StdCsNumSort, XDeref(XField(&PathStyleMap::name))));
	dialog.tab.Clear();
	int new_tab = -1;
	for(int i = 0; i < map_index.GetCount(); i++)
	{
		int x = map_index[i];
		dialog.tab.Add(Nvl((*maps)[x]->GetName(), "(bez názvu)"));
		if(x == tab)
			new_tab = i;
	}
	dialog.tab.Set(new_tab);
}
*/

/*
void DlgPathStyleMap::OnTab()
{
	int x = dialog.tab.Get();
	if(x >= 0 && x < map_index.GetCount())
		dialog.browser.Set((*maps)[map_index[x]]);
}
*/

PathStyleCtrl::PathStyleCtrl()
: path_map(0)
{
	SetDisplay(display);
}

void PathStyleCtrl::DoAction()
{
	String s = GetData();
	if(RunDlgPathStyleMap(*path_map, s, false, false))
		SetDataAction(s);
}

END_UPP_NAMESPACE
