#include "RichEdit.h" namespace Upp { void DiagramEditor::Map(Point& p) { p = (Pointf)p / GetZoom() + (Pointf)(Point)sb; } Point DiagramEditor::GetSizeHandle(Point p) const { Point h = { 0, 0 }; if(IsNull(data.size)) // automatic size - no resize return h; Size sz = data.GetSize(); if(abs(sz.cx - p.x) < 8) h.x = 1; if(abs(sz.cy - p.y) < 8) h.y = 1; return h; } Point DiagramEditor::GetHandle(int i, Point p_) const { // -1 top/left, 1 right/botom Point h(0, 0); Pointf p = p_; if(i >= 0) { const DiagramItem& m = data.item[i]; if(m.IsLine()) { double r = (m.width + 12) / 2 - 1; if(Distance(m.pos, p) <= r) return Point(-1, -1); if(Distance(m.pos + m.size, p) <= r) return Point(1, 1); } else { Rect r = m.GetRect(); p -= r.CenterPoint(); r -= r.CenterPoint(); p = m.Rotation(-1).Transform(p); Rect rr = r.Inflated(5); r.Deflate(min(10, r.GetWidth() / 2), min(10, r.GetHeight() / 2)); if(rr.Contains(p)) { if(p.y < r.top) h.y = -1; if(p.y > r.bottom) h.y = 1; if(p.x < r.left) h.x = -1; if(p.x > r.right) h.x = 1; } } } return h; } int DiagramEditor::FindItem(Point p) const { for(int pass = 0; pass < 2; pass++) for(int i = data.item.GetCount() - 1; i >= 0; i--) if(data.item[i].IsClick(p, data, pass)) return i; return -1; } Image DiagramEditor::CursorImage(Point p, dword keyflags) { Map(p); if(edit_text) return Image::Arrow(); Point h = HasCapture() ? sizehandle : GetSizeHandle(p); if(h.x && h.y) return Image::SizeBottomRight(); if(h.x) return Image::SizeHorz(); if(h.y) return Image::SizeVert(); if(HasCapture() && doselection) return Image::Arrow(); h = HasCapture() ? draghandle : IsCursor() ? GetHandle(cursor, p) : Null; if(IsNull(h)) return Image::Arrow(); if(HasCapture() && IsCursor() && CursorItem().IsLine()) return Image::Arrow(); int m = h.x * h.y; if((h.x || h.y) && IsCursor() && CursorItem().IsLine()) return DiagramImg::LineCursor(); if(h.x == -1 && h.y == 1) return DiagramImg::RotateCursor(); double rot; if(m > 0) rot = -M_PI / 4; else if(m < 0) rot = M_PI / 4; else if(h.x) rot = M_PI / 2; else if(h.y) rot = 0; else return Image::Arrow(); rot += M_2PI * CursorItem().rotate / 360; return MakeImage( [&] { return String((const char *)&rot, sizeof(rot)); }, [&] { ImagePainter w(DPI(32, 32)); w.Clear(); const double x1 = 10; const double x2 = 14; const double x3 = 18; const double x4 = 22; const double y1 = 2; const double y2 = 11; const double y3 = 21; const double y4 = 30; const double m = 16; w.Scale(DPI(1)); w.Translate(m, m); w.Rotate(rot); w.Translate(-m, -m); w.Move(m, y1).Line(x4, y2).Line(x3, y2).Line(x3, y3).Line(x4, y3) .Line(m, y4).Line(x1, y3).Line(x2, y3).Line(x2, y2).Line(x1, y2) .Close(); w.Stroke(2, White()); w.Fill(White()); w.Stroke(1, Black()); Image img = w.GetResult(); SetHotSpots(img, DPI(16, 16)); return img; } ); } void DiagramEditor::MouseWheel(Point, int zdelta, dword keyflags) { if(keyflags & K_ALT) { if(IsCursor()) { DiagramItem& m = CursorItem(); if(m.IsLine()) { int angle = int(Bearing(m.size) * 180 / M_PI); angle = ((angle + 360 + sgn(zdelta) * 15) / 15 * 15) % 360; m.size = Length(m.size) * Polar(angle * M_PI / 180); } else m.rotate = ((int(m.rotate) + sgn(zdelta) * 15) / 15 * 15) % 360; Commit(); Sync(); } return; } if(keyflags & K_CTRL) { zoom_percent = clamp((zoom_percent / 25 + sgn(zdelta)) * 25, 25, 400); Sync(); return; } if(keyflags & K_SHIFT) sb.WheelX(zdelta); else sb.WheelY(zdelta); } void DiagramEditor::LeftDouble(Point p, dword keyflags) { if(IsCursor() && !(keyflags & K_CTRL)) StartText(); } void DiagramEditor::Grid(Point& p) { if(grid && !GetShift()) p = (p + Point(3, 3)) / 8 * 8; } void DiagramEditor::Grid(Pointf& p) { Point pp = p; Grid(pp); p = pp; } void DiagramEditor::LeftDown(Point p, dword keyflags) { moved = moving = false; conns.Clear(); Map(p); FinishText(); dragstart = dragcurrent = p; base_rotate = CursorItem().rotate; SetCapture(); sizehandle = GetSizeHandle(p); if(sizehandle.x || sizehandle.y) return; if(IsCursor()) { drag_cp = CursorItem().pos; Point h = GetHandle(cursor, p); if(h.x || h.y) { draghandle = h; Sync(); PrepareConns(); return; } } if(sel.GetCount() == 1 && !(keyflags & K_CTRL)) { sel.Clear(); cursor = -1; } int i = FindItem(p); if(i >= 0) { if((keyflags & K_CTRL) && sel.Find(i) >= 0) { sel.RemoveKey(i); if(sel.GetCount()) SetCursor(sel.Top()); else KillCursor(); } else SetCursor(i); if(IsCursor()) { dragfrom = CursorItem().pos; sdragfrom.SetCount(sel.GetCount()); for(int i = 0; i < sel.GetCount(); i++) sdragfrom[i] = data.item[sel[i]].pos; if(sel.GetCount() > 1 || !CursorItem().IsLine()) PrepareConns(); else conns.Clear(); draghandle = Null; Point h = GetHandle(cursor, p); if(h.x || h.y) draghandle = h; } } else { if(!(keyflags & K_CTRL)) { sel.Clear(); SetCursor(-1); } doselection = true; } Sync(); } void DiagramEditor::MouseMove(Point p, dword keyflags) { Map(p); moved = moved || p != dragstart; if(HasCapture() && IsCursor() && draghandle == Point(999,999) && tool >= 0) { // adding tool based shape DiagramItem& m = CursorItem(); Pointf p0 = dragstart; Grid(p0); Pointf p1 = p; Grid(p1); m.size = p1 - p0; if(m.IsLine()) m.pos = p0; else { m.size.cx = max(0.5 * m.size.cx, 4.0); m.size.cy = max(0.5 * m.size.cy, 4.0); m.pos = p0 + m.size; ASSERT(m.pos - m.size == p0); m.FixPosition(); ASSERT(m.pos - m.size == p0); } m.FixPosition(); Sync(); return; } if(HasCapture() && doselection) { // do rectangular selection if(tool >= 0) { // start tool if(Distance(dragstart, p) >= 8) { KillCursor(); DiagramItem& m = AddItem(tl[tool].shape); m = tl[tool]; Grid(p); m.pos = p; m.size = Sizef(8, 8); draghandle = Point(999,999); } return; } dragcurrent = p; Rectf r(dragstart, dragcurrent); r.Normalize(); sel.Clear(); KillCursor(); for(int i = 0; i < data.item.GetCount(); i++) { Rectf m = data.item[i].GetRect(); if(r.Contains(m.TopLeft()) && r.Contains(m.BottomRight())) { sel.FindAdd(i); SetCursor(i); } } Sync(); return; } if(HasCapture() && (sizehandle.x || sizehandle.y)) { // resize canvas Grid(p); if(IsNull(data.size)) data.size = data.GetSize(); if(sizehandle.x) data.size.cx = p.x; if(sizehandle.y) data.size.cy = p.y; Sync(); return; } if(HasCapture() && IsCursor() && (moving || Distance(dragstart, p) >= 8)) { moving = true; DiagramItem& m = CursorItem(); if(IsNull(draghandle)) { // move selection Pointf offset = Point(p - dragstart); Pointf p = dragfrom + offset; Grid(p); offset = p - dragfrom; for(int i = 0; i < sel.GetCount(); i++) { int ii = sel[i]; if(ii >= 0 && ii < data.item.GetCount() && i < sdragfrom.GetCount()) { data.item[ii].pos = sdragfrom[i] + offset; Grid(data.item[ii].pos); data.item[ii].FixPosition(); } } } else { Pointf p0 = p; Grid(p); if(!m.IsLine()) m.Normalize(); Rectf r = m.GetRect(); if(m.IsLine()) { Pointf pf = p; if(!GetShift()) { double d0 = SquaredDistance(p0, pf); for(const DiagramItem& m : data.item) for(Pointf cs : m.GetConnections()) { double d1 = SquaredDistance(p0, cs); if(d1 < d0) { d0 = d1; pf = cs; } } } Pointf p2 = m.pos + m.size; if(draghandle.x < 0) m.pos = pf; else p2 = pf; m.size = p2 - m.pos; } else if(draghandle.x == -1 && draghandle.y == 1) { Pointf bl = Xform2D::Rotation(M_PI * base_rotate / 180.0).Transform(r.BottomLeft() - drag_cp); m.rotate = base_rotate + 180.0 * (Bearing((Pointf)p0 - drag_cp) - Bearing(bl)) / M_PI; if(grid && !GetShift()) m.rotate = (int(m.rotate + 360 + 7) / 15 * 15) % 360; } else { bool rotated = m.rotate; r -= drag_cp; if(rotated) p = m.Rotation(-1).Transform(Pointf(p) - drag_cp) + drag_cp; Sizef hsz = r.GetSize() / 2; auto Do = [](int h, double& hsz, double a, double cp) { if(h) hsz = abs(a - cp); }; Do(draghandle.x, hsz.cx, p.x, drag_cp.x); Do(draghandle.y, hsz.cy, p.y, drag_cp.y); hsz.cx = max(hsz.cx, 8.0); hsz.cy = max(hsz.cy, 8.0); m.size = hsz; if(m.aspect_ratio) { Sizef sz1, sz2; ComputeAspectSize(m, sz1, sz2); m.size = (draghandle.x && draghandle.y && sz1.cx < sz2.cx || draghandle.x ? sz1 : sz2) / 2; } } } UseConns(); Sync(); return; } } void DiagramEditor::LeftUp(Point p, dword flags) { Map(p); if(!moving && !(flags & K_CTRL) && !doselection) { sel.Clear(); SetCursor(FindItem(p)); } moving = doselection = false; conns.Clear(); Sync(); Commit(); } }