#include "RichEdit.h" namespace Upp { #define IMAGECLASS DiagramImg #define IMAGEFILE #include DiagramEditor::DiagramEditor() { { // estimate the size of icons DropList shape; shape.SetRect(0, 0, IconDlCx(), shape.MultiButton::GetStdSize().cy); icon_sz = shape.GetContentRect().GetSize(); icon_sz.cx = max(icon_sz.cx, 8); // sanity icon_sz.cy = max(icon_sz.cy, 8); } AddFrame(toolbar); Add(text_editor); text_editor.NoRuler().ViewBorder(0); text_editor.WhenRefreshBar = [=] { SetBar(); }; text_editor.WhenEnter << [=] { FinishText(); }; text_editor.WhenEsc << [=] { edit_text = false; Sync(); }; text_editor.WhenAction << [=] { SyncEditorRect(); }; ink.ColorImage(DiagramImg::Ink()) .NullImage(DiagramImg::InkNull()) .StaticImage(DiagramImg::InkA()); ink.Tip(t_("Line color")); ink << [=] { SetAttrs(ATTR_INK); }; paper.ColorImage(DiagramImg::Paper()) .NullImage(DiagramImg::PaperNull()) .StaticImage(DiagramImg::PaperA()); paper.Tip(t_("Background color")); paper << [=] { SetAttrs(ATTR_PAPER); }; shape << [=] { SetAttrs(ATTR_SHAPE); SetFocus();}; Shapes(shape.popup); shape.popup.count = DiagramItem::SHAPE_SVGPATH; Caps(line_start.popup, true); Caps(line_end.popup, false); line_start << [=] { SetAttrs(ATTR_CAP0); SetFocus(); }; line_end << [=] { SetAttrs(ATTR_CAP1); SetFocus(); }; Widths(line_width.popup); line_width << [=] { SetAttrs(ATTR_WIDTH); SetFocus(); }; Dashes(line_dash.popup); line_dash << [=] { SetAttrs(ATTR_DASH); SetFocus(); }; tl[0].shape = DiagramItem::SHAPE_LINE; tl[1].shape = DiagramItem::SHAPE_ROUNDRECT; ResetUndo(); Sync(); sb.AutoHide(); sb.WithSizeGrip(); AddFrame(sb); sb.WhenScroll << [=] { Sync(); }; editor = true; GetAttrs(DiagramItem()); } void DiagramEditor::SerializeSettings(Stream& s) { int version = 1; s % version; s % grid % display_grid; for(int i = 0; i < 2; i++) s % tl[i]; } void DiagramEditor::SetupDark(ColorPusher& c) const { c.AllowDarkContent(allow_dark_content); c.DarkContent(dark_content); } bool DiagramEditor::IsDarkContent() const { return dark_content || allow_dark_content && IsDarkTheme(); } DiagramEditor& DiagramEditor::DarkContent(bool b) { dark_content = b; Sync(); return *this; } DiagramEditor& DiagramEditor::AllowDarkContent(bool b) { allow_dark_content = b; Sync(); return *this; } void DiagramEditor::Skin() { SetBar(); editor_bar.Skin(); } void DiagramEditor::Paint(Draw& w) { fast = HasCapture() && paint_ms > 50 && moved; int t0 = msecs(); { DrawPainter iw(w, GetSize()); iw.Co(); iw.Clear(SWhite()); iw.Scale(GetZoom()); iw.Offset(-(Point)sb); Size dsz = data.GetSize(); iw.Move(dsz.cx, 0).Line(dsz.cx, dsz.cy).Line(0, dsz.cy); if(IsNull(data.size)) iw.Dash("2").Stroke(1, Gray()); else iw.Stroke(0.2, SColorHighlight()); if(display_grid) for(int x = 0; x < dsz.cx; x += 8) for(int y = 0; y < dsz.cy; y += 8) iw.DrawRect(x, y, 1, 1, Blend(SWhite(), SGreen())); dark = IsDarkContent(); data.Paint(iw, *this); if(HasCapture() && doselection && tool < 0) { Rect r(dragstart, dragcurrent); r.Normalize(); iw.Rectangle(r).Fill(30 * SColorHighlight()).Stroke(1, LtRed()).Dash("4").Stroke(1, White()); } } if(!fast) paint_ms = msecs() - t0; PaintBasicHints(this, w, "Right-click to insert item(s)&Double-click to edit text", "#DiagramEditor"); } void DiagramEditor::Sync() { Refresh(); SetBar(); sb.SetTotal(data.GetEditSize() + Point(10, 10)); sb.SetPage(sb.GetReducedViewSize() / GetZoom()); sb.SetLine(8, 8); SyncEditor(); if(!IsCursor() && findarg((int)~shape, DiagramItem::SHAPE_SVGPATH, DiagramItem::SHAPE_IMAGE) >= 0) shape <<= DiagramItem::SHAPE_ROUNDRECT; } void DiagramEditor::Layout() { Sync(); } void DiagramEditor::ResetUndo() { Index blob_ids; for(const DiagramItem& m : data.item) if(m.blob_id.GetCount()) blob_ids.FindAdd(m.blob_id); undoredo.Reset(GetCurrent(), ValueArray(blob_ids.PickKeys())); } void DiagramEditor::Commit() { if(IsCursor()) { DiagramItem& m = CursorItem(); if(!m.IsLine()) m.Normalize(); } Index blob_ids; size_t blobsz = 0; for(const DiagramItem& m : data.item) { if(m.blob_id.GetCount()) { if(blob_ids.Find(m.blob_id) < 0) { blob_ids.Add(m.blob_id); blobsz += data.GetBlob(m.blob_id).GetCount(); } } } if(undoredo.Commit(GetCurrent(), ValueArray(clone(blob_ids.GetKeys())))) { size_t limit = max((size_t)20000000, 2 * blobsz); for(;;) { // make sure that blobs are not excessive Index ublob_ids; size_t ublobsz = 0; auto AddIds = [&](const ValueArray& va) { for(Value v : va) { String id = ~v; if(ublob_ids.Find(id) < 0) { ublob_ids.Add(id); ublobsz += data.GetBlob(id).GetCount(); } } }; AddIds(undoredo.GetCommitInfo()); for(int i = 0; i < undoredo.GetUndoCount(); i++) AddIds(undoredo.GetUndoInfo(i)); for(int i = 0; i < undoredo.GetRedoCount(); i++) AddIds(undoredo.GetRedoInfo(i)); if(ublobsz <= limit) { data.SweepBlobs(ublob_ids); // remove blobs that are not used anymore break; } if(undoredo.GetUndoCount()) undoredo.DropUndo(); else if(undoredo.GetRedoCount()) undoredo.DropRedo(); else break; } SetBar(); Sync(); } conns.Clear(); } String DiagramEditor::GetCurrent() { return StoreAsString(data); } bool DiagramEditor::SetCurrent(const String& s) { KillCursor(); bool b = LoadFromString(data, s); Sync(); return b; } void DiagramEditor::CancelSelection() { sel.Clear(); FinishText(); KillCursor(); Sync(); } void DiagramEditor::Zoom() { zoom_percent = (zoom_percent / 25 + 1) * 25; if(zoom_percent > 400) zoom_percent = 25; Sync(); } void DiagramEditor::HorzMouseWheel(Point, int zdelta, dword) { sb.WheelX(zdelta); } String DiagramEditor::Save() const { StringBuffer r; data.Save(r); return String(r); } bool DiagramEditor::Load(const String& s) { CParser p(s); try { data.Load(p); } catch(CParser::Error) { Reset(); return false; } Reset(); return true; } void DiagramEditor::Reset() { doselection = false; grid = true; edit_text = false; ResetUndo(); Sync(); } bool DiagramEditor::Key(dword key, int count) { switch(key) { case K_ESCAPE: if(tool >= 0) { tool = -1; SetBar(); return true; } if(IsCursor()) { KillCursor(); return true; } break; } return Ctrl::Key(key, count); } }