#include "GeomCtrl.h" #pragma hdrstop NAMESPACE_UPP #define LAYOUTFILE #include #define IMAGEFILE #define IMAGECLASS PathImg #include 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 // > 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 // > 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.GetClip()) & full; Rect cvis = UnitsToClient(vis) & draw.GetClip(); DrawRectMinusRect(draw, draw.GetClip(), 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 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& 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& 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& 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& 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 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 PathEditorCtrl::FindObject(const Rectf& rc) const { Vector 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 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 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 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 WithPopupEdit::WithPopupEdit() { this->SetFrame(BlackFrame()); open = false; } template void WithPopupEdit::Deactivate() { if(open) { if(!this->Accept()) { this->SetFocus(); return; } DoClose(true); this->IgnoreMouseClick(); WhenSelect(); } } template void WithPopupEdit::DoClose(bool ok) { open = false; // EndModalLoop(ok ? 1 : 0); this->EndLoop(ok ? 1 : 0); } template bool WithPopupEdit::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 bool WithPopupEdit::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.GetClip(); 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 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 { 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 *maps; // Vector 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(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