diff --git a/uppsrc/Core/BinUndoRedo.cpp b/uppsrc/Core/BinUndoRedo.cpp index be4df4f11..d4002478a 100644 --- a/uppsrc/Core/BinUndoRedo.cpp +++ b/uppsrc/Core/BinUndoRedo.cpp @@ -165,36 +165,53 @@ String BinDiff(const String& base, const String& data) return diff; } -void BinUndoRedo::Reset(const String& current, const String& ids) +void BinUndoRedo::Reset(const String& current, const Value& info) { undo.Clear(); redo.Clear(); undosize = 0; commit.data = current; - commit.ids = ids; + commit.info = info; } -bool BinUndoRedo::Commit(const String& current, const String& ids, int limit) +bool BinUndoRedo::DropUndo() +{ + if(undo.GetCount()) { + undosize -= undo[0].data.GetCount(); + undo.Remove(0); + return true; + } + return false; +} + +bool BinUndoRedo::DropRedo() +{ + if(redo.GetCount()) { + redo.Remove(0); + return true; + } + return false; +} + +bool BinUndoRedo::Commit(const String& current, const Value& info, int limit) { bool ret = false; if(redo.GetCount()) { redo.Clear(); ret = true; } - if(current != commit.data || ids != commit.ids) { + if(current != commit.data || info != commit.info) { String u = BinDiff(current, commit.data); - while(undo.GetCount() && undosize + u.GetCount() > limit) { - undosize -= undo[0].data.GetCount(); - undosize -= undo[0].ids.GetCount(); - undo.Remove(0); - } + while(undosize + u.GetCount() > limit) + if(!DropUndo()) + break; Entry& e = undo.Add(); e.data = u; - e.ids = commit.ids; + e.info = commit.info; undosize += u.GetCount(); - undosize += ids.GetCount(); + undosize += info.GetCount(); commit.data = current; - commit.ids = ids; + commit.info = info; ret = true; } return ret; @@ -205,10 +222,11 @@ String BinUndoRedo::Undo(const String& current) if(undo.GetCount()) { Entry prev = undo.Pop(); commit.data = BinUndiff(commit.data, prev.data); + commit.info = prev.info; Entry& e = redo.Add(); e.data = BinDiff(commit.data, current); - e.ids = prev.ids; + e.info = prev.info; return commit.data; } return String::GetVoid(); @@ -219,21 +237,14 @@ String BinUndoRedo::Redo(const String& current) if(redo.GetCount()) { Entry next = redo.Pop(); commit.data = BinUndiff(commit.data, next.data); + commit.info = next.info; + Entry& e = undo.Add(); e.data = BinDiff(commit.data, current); - e.ids = next.ids; + e.info = next.info; return commit.data; } return String::GetVoid(); } -void BinUndoRedo::Ids(Event fn) -{ - fn(commit.ids); - for(const Entry& e : undo) - fn(e.ids); - for(const Entry& e : redo) - fn(e.ids); -} - } diff --git a/uppsrc/Core/BinUndoRedo.h b/uppsrc/Core/BinUndoRedo.h index c741e1d61..aede1be9f 100644 --- a/uppsrc/Core/BinUndoRedo.h +++ b/uppsrc/Core/BinUndoRedo.h @@ -4,7 +4,7 @@ String BinUndiff(const String& base, const String& bin_diff); class BinUndoRedo { struct Entry : Moveable { String data; - String ids; + Value info; }; Entry commit; Vector undo; @@ -12,12 +12,18 @@ class BinUndoRedo { int undosize = 0; public: - void Reset(const String& current, const String& ids = String()); - bool Commit(const String& current, const String& ids, int limit = 4096*1024); + void Reset(const String& current, const Value& info = Value()); + bool Commit(const String& current, const Value& info, int limit = 4096*1024); bool Commit(const String& current, int limit = 4096*1024) { return Commit(current, String(), limit); } bool IsUndo() const { return undo.GetCount(); } bool IsRedo() const { return redo.GetCount(); } + int GetUndoCount() const { return undo.GetCount(); } + int GetRedoCount() const { return redo.GetCount(); } + bool DropUndo(); + bool DropRedo(); + Value GetUndoInfo(int i) const { return undo[i].info; } + Value GetRedoInfo(int i) const { return redo[i].info; } + Value GetCommitInfo() const { return commit.info; } String Undo(const String& current); String Redo(const String& current); - void Ids(Event fn); }; diff --git a/uppsrc/Core/Stream.cpp b/uppsrc/Core/Stream.cpp index a39545c74..adaf742d2 100644 --- a/uppsrc/Core/Stream.cpp +++ b/uppsrc/Core/Stream.cpp @@ -1334,6 +1334,28 @@ bool StoreToFile(Event serialize, const char *file, int version) { return !f.IsError(); } +String StoreAsString(Event serialize) { + StringStream ss; + Store(serialize, ss); + return ss; +} + +bool LoadFromString(Event serialize, const String& s) { + StringStream ss(s); + return Load(serialize, ss); +} + +Vector StoreAsStrings(Event serialize) { + StringsStreamOut ss; + Store(serialize, ss); + return ss.PickResult(); +} + +bool LoadFromStrings(Event serialize, const Vector& s) { + StringsStreamIn ss(s); + return Load(serialize, ss); +} + Stream& Pack16(Stream& s, int& i) { if(s.IsLoading()) { i = (int16) s.Get16le(); diff --git a/uppsrc/Core/Util.h b/uppsrc/Core/Util.h index 34a8afd08..45a87a532 100644 --- a/uppsrc/Core/Util.h +++ b/uppsrc/Core/Util.h @@ -428,6 +428,10 @@ bool Load(Event serialize, Stream& stream, int version = Null); bool Store(Event serialize, Stream& stream, int version = Null); bool LoadFromFile(Event serialize, const char *file = NULL, int version = Null); bool StoreToFile(Event serialize, const char *file = NULL, int version = Null); +String StoreAsString(Event serialize); +bool LoadFromString(Event serialize, const String& s); +Vector StoreAsStrings(Event serialize); +bool LoadFromStrings(Event serialize, const Vector& s); template bool Load(T& x, Stream& s, int version = Null) { @@ -451,33 +455,27 @@ bool StoreToFile(T& x, const char *name = NULL, int version = Null) { template String StoreAsString(T& x) { - StringStream ss; - Store(x, ss); - return ss; + return StoreAsString([&](Stream& s) { s % x; }); } template bool LoadFromString(T& x, const String& s) { - StringStream ss(s); - return Load(x, ss); + return LoadFromString([&](Stream& s) { s % x; }, s); } template Vector StoreAsStrings(T& x) { - StringsStreamOut ss; - Store(x, ss); - return ss.PickResult(); + return StoreAsStrings([&](Stream& s) { s % x; }); } template bool LoadFromStrings(T& x, const Vector& s) { - StringsStreamIn ss(s); - return Load(x, ss); + return LoadFromStrings([&](Stream& s) { s % x; }, s); } void RegisterGlobalConfig(const char *name); void RegisterGlobalSerialize(const char *name, Event WhenSerialize); -void RegisterGlobalConfig(const char *name, Event<> WhenFlush); +void RegisterGlobalConfig(const char *name, Event<> WhenFlush); String GetGlobalConfigData(const char *name); void SetGlobalConfigData(const char *name, const String& data); diff --git a/uppsrc/Core/src.tpp/BinDiff_en-us.tpp b/uppsrc/Core/src.tpp/BinDiff_en-us.tpp index 40cedeb16..e053e3b8b 100644 --- a/uppsrc/Core/src.tpp/BinDiff_en-us.tpp +++ b/uppsrc/Core/src.tpp/BinDiff_en-us.tpp @@ -36,19 +36,31 @@ data into BinUndoRedo. Lately, it can retrieve binary String with [* Undo]/[* Redo] a serialize it back into content.&] [s3; &] [s4; &] -[s5;:Upp`:`:BinUndoRedo`:`:Reset`(const String`&`): [@(0.0.255) void] -[* Reset]([@(0.0.255) const] String[@(0.0.255) `&] [*@3 current])&] +[s5;:Upp`:`:BinUndoRedo`:`:Reset`(const String`&`,const Value`&`): [@(0.0.255) void] +[* Reset]([@(0.0.255) const] String[@(0.0.255) `&] [*@3 current], [@(0.0.255) const] +Value[@(0.0.255) `&] [*@3 info] [@(0.0.255) `=] Value())&] [s2;%% Sets the initial data content. This is typically used after -loading the data into application or creating a new content.&] +loading the data into application or creating a new content. +[%-*@3 info] is additional client code information that is associated +with each undo/redo step and can be retrieved. Client code might +typically use this information to manage resources that are not +stored within [%-*@3 current] data.&] [s3; &] [s4; &] +[s5;:Upp`:`:BinUndoRedo`:`:Commit`(const String`&`,const Value`&`,int`): [@(0.0.255) bo +ol] [* Commit]([@(0.0.255) const] String[@(0.0.255) `&] [*@3 current], +[@(0.0.255) const] Value[@(0.0.255) `&] [*@3 info], [@(0.0.255) int] +[*@3 limit] [@(0.0.255) `=] [@3 4096] [@(0.0.255) `*][@3 1024])&] [s5;:Upp`:`:BinUndoRedo`:`:Commit`(const String`&`,int`): [@(0.0.255) bool] [* Commit]([@(0.0.255) const] String[@(0.0.255) `&] [*@3 current], [@(0.0.255) int] [*@3 limit] [@(0.0.255) `=] [@3 4096] [@(0.0.255) `*][@3 1024])&] [s2;%% Adds a snapshot of content [%-*@3 current] effectively creating single undo step. [%-*@3 limit] represents maximum memory that can be used to store undo steps (when there is more, oldest steps -are dropped).&] +are dropped). [%-*@3 info] is additional client code information +that is associated with each undo/redo step and can be retrieved. +Client code might typically use this information to manage resources +that are not stored within [%-*@3 current] data.&] [s3; &] [s4; &] [s5;:Upp`:`:BinUndoRedo`:`:IsUndo`(`)const: [@(0.0.255) bool] [* IsUndo]() @@ -61,6 +73,39 @@ are dropped).&] [s2;%% Returns true if undo steps are available..&] [s3; &] [s4; &] +[s5;:Upp`:`:BinUndoRedo`:`:GetUndoCount`(`)const: [@(0.0.255) int] +[* GetUndoCount]() [@(0.0.255) const]&] +[s2;%% Returns the number of undo steps.&] +[s3; &] +[s4; &] +[s5;:Upp`:`:BinUndoRedo`:`:GetRedoCount`(`)const: [@(0.0.255) int] +[* GetRedoCount]() [@(0.0.255) const]&] +[s2;%% Returns the number of redo steps.&] +[s3; &] +[s4; &] +[s5;:Upp`:`:BinUndoRedo`:`:DropUndo`(`): [@(0.0.255) bool] [* DropUndo]()&] +[s2;%% Deletes oldest undo step.&] +[s3; &] +[s4; &] +[s5;:Upp`:`:BinUndoRedo`:`:DropRedo`(`): [@(0.0.255) bool] [* DropRedo]()&] +[s2;%% Deletes oldest redo step.&] +[s3; &] +[s4; &] +[s5;:Upp`:`:BinUndoRedo`:`:GetUndoInfo`(int`)const: Value [* GetUndoInfo]([@(0.0.255) int +] [*@3 i]) [@(0.0.255) const]&] +[s2;%% Returns the information associated with undo step [%-*@3 i].&] +[s3; &] +[s4; &] +[s5;:Upp`:`:BinUndoRedo`:`:GetRedoInfo`(int`)const: Value [* GetRedoInfo]([@(0.0.255) int +] [*@3 i]) [@(0.0.255) const]&] +[s2;%% Returns the information associated with redo step [%-*@3 i].&] +[s3; &] +[s4; &] +[s5;:Upp`:`:BinUndoRedo`:`:GetCommitInfo`(`)const: Value [* GetCommitInfo]() +[@(0.0.255) const]&] +[s2;%% Returns the information associated with current commit.&] +[s3; &] +[s4; &] [s5;:Upp`:`:BinUndoRedo`:`:Undo`(const String`&`): String [* Undo]([@(0.0.255) const] String[@(0.0.255) `&] [*@3 current])&] [s2;%% Given snapshot of content [%-*@3 current], does one undo step diff --git a/uppsrc/Core/src.tpp/SerializationUtils_en-us.tpp b/uppsrc/Core/src.tpp/SerializationUtils_en-us.tpp index 78c7d956d..27d34e450 100644 --- a/uppsrc/Core/src.tpp/SerializationUtils_en-us.tpp +++ b/uppsrc/Core/src.tpp/SerializationUtils_en-us.tpp @@ -78,6 +78,32 @@ oad], restores data from the [%-*@3 file].&] tore], stores data to the [%-*@3 file].&] [s3; &] [s4; &] +[s5;:Upp`:`:StoreAsString`(Event`): String [* StoreAsString](Event + [*@3 serialize])&] +[s2;%% Using [%-*^topic`:`/`/Core`/src`/SerializationUtils`_en`-us`#Upp`:`:Store`(Event`,Stream`&`,int`)^ S +tore], stores data as String.&] +[s3; &] +[s4; &] +[s5;:Upp`:`:LoadFromString`(Event`,const String`&`): [@(0.0.255) bool] +[* LoadFromString](Event [*@3 serialize], [@(0.0.255) const] +String[@(0.0.255) `&] [*@3 s])&] +[s2;%% Using [%-*^topic`:`/`/Core`/src`/SerializationUtils`_en`-us`#Upp`:`:Load`(Event`,Stream`&`,int`)^ L +oad], restores data from the String.&] +[s3; &] +[s4; &] +[s5;:Upp`:`:StoreAsStrings`(Event`): Vector [* StoreAsStrings](Event [*@3 serialize])&] +[s2;%% Stores data as Vector. Useful in rare cases where +stored data is >2GB.&] +[s3; &] +[s4; &] +[s5;:Upp`:`:LoadFromStrings`(Event`,const Vector`&`): [@(0.0.255) bool] +[* LoadFromStrings](Event [*@3 serialize], [@(0.0.255) const] +Vector[@(0.0.255) `&] [*@3 s])&] +[s2;%% Loads data from Vector. Useful in rare cases where +stored data is >2GB.&] +[s3; &] +[s4; &] [s5;:Upp`:`:Load`(T`&`,Stream`&`,int`): [@(0.0.255) template] <[@(0.0.255) class] T> [@(0.0.255) bool] [* Load](T[@(0.0.255) `&] [*@3 x], Stream[@(0.0.255) `&] [*@3 s], [@(0.0.255) int] [*@3 version ][@(0.0.255) `=] [* Null])&] @@ -121,6 +147,19 @@ tring]_[* StoreAsString]([*@4 T][@(0.0.255) `&]_[*@3 x])&] [s2;%% Restores serialized data from the String (e.g. previously stored by StoreAsString).&] [s3;%% &] +[s4; &] +[s5;:Upp`:`:StoreAsStrings`(T`&`): [@(0.0.255) template] <[@(0.0.255) class] +T> Vector [* StoreAsStrings](T[@(0.0.255) `&] [*@3 x])&] +[s2;%% Stores [%-*@3 x] using its Serialize method a Vector. +Useful in rare cases where stored data is >2GB.&] +[s3; &] +[s4; &] +[s5;:Upp`:`:LoadFromStrings`(T`&`,const Vector`&`): [@(0.0.255) template] +<[@(0.0.255) class] T> [@(0.0.255) bool] [* LoadFromStrings](T[@(0.0.255) `&] +[*@3 x], [@(0.0.255) const] Vector[@(0.0.255) `&] [*@3 s])&] +[s2;%% Restores serialized data from the Vector (previously +stored by StoreAsStrings).&] +[s0;%% &] [s0;%% &] [s0;%% [*@3;4 Global modular serialization support]&] [s0;#%% Modular serialization is a viable option for storing configuration @@ -133,7 +172,7 @@ serialization of all such data with single stream.&] [s5;:RegisterGlobalConfig`(const char`*`): [@(0.0.255) void]_[* RegisterGlobalConfig]([@(0.0.255) c onst]_[@(0.0.255) char]_`*[*@3 name])&] [s7;%% Registers name as global configuration key.&] -[s3;%% &] +[s3; &] [s4; &] [s5;:Upp`:`:RegisterGlobalSerialize`(const char`*`,Upp`:`:Event``): [@(0.0.255) v oid]_[* RegisterGlobalSerialize]([@(0.0.255) const]_[@(0.0.255) char]_`*[*@3 name], diff --git a/uppsrc/Core/src.tpp/StringsStream_en-us.tpp b/uppsrc/Core/src.tpp/StringsStream_en-us.tpp index 26fbb24d7..c22e3f5c4 100644 --- a/uppsrc/Core/src.tpp/StringsStream_en-us.tpp +++ b/uppsrc/Core/src.tpp/StringsStream_en-us.tpp @@ -38,7 +38,7 @@ nt] [*@3 part`_size] [@(0.0.255) `=] [@N 1024`*1024 `- 256])&] ][@(0.0.255)3 :][3 ][@(0.0.255)3 public][3 Stream]&] [s2;%% Input stream corresponding to StringsStreamOut `- reads data from multiple chunks.&] -[s0;i448;a25;kKO9;:noref:@(0.0.255) &] +[s3; &] [ {{10000F(128)G(128)@1 [s0;%% [* Public Method List]]}}&] [s3; &] [s5;:Upp`:`:StringsStreamIn`:`:StringsStreamIn`(const Vector`&`): [* StringsStreamIn]([@(0.0.255) c diff --git a/uppsrc/CtrlCore/Ctrl.cpp b/uppsrc/CtrlCore/Ctrl.cpp index bef9afdb3..70dccba1b 100644 --- a/uppsrc/CtrlCore/Ctrl.cpp +++ b/uppsrc/CtrlCore/Ctrl.cpp @@ -1026,7 +1026,7 @@ String Ctrl::Name0() const { return s; } -String Ctrl::Name(Ctrl *ctrl) +String Ctrl::Name(const Ctrl *ctrl) { return Upp::Name(ctrl); } @@ -1057,7 +1057,7 @@ bool Ctrl::InLoop() const bool Ctrl::InCurrentLoop() const { GuiLock __; - return GetLoopCtrl() == this; + return GetLoopCtrl() && GetLoopCtrl()->GetOwner() == GetOwner(); } #ifdef HAS_TopFrameDraw diff --git a/uppsrc/CtrlCore/CtrlCore.h b/uppsrc/CtrlCore/CtrlCore.h index b45bcbb41..3a85d22a1 100644 --- a/uppsrc/CtrlCore/CtrlCore.h +++ b/uppsrc/CtrlCore/CtrlCore.h @@ -1431,7 +1431,7 @@ public: void DoSkin(); String Name() const; - static String Name(Ctrl *ctrl); + static String Name(const Ctrl *ctrl); #ifdef _DEBUG virtual void Dump() const; diff --git a/uppsrc/Painter/Context.cpp b/uppsrc/Painter/Context.cpp index 3bab5638c..f2abe4bdb 100644 --- a/uppsrc/Painter/Context.cpp +++ b/uppsrc/Painter/Context.cpp @@ -161,11 +161,11 @@ void BufferPainter::Create(ImageBuffer& ib, int mode_) if(mode_ != mode || (Size)size != ib.GetSize()) { mode = mode_; - + rasterizer.Create(ib.GetWidth(), ib.GetHeight(), mode == MODE_SUBPIXEL); cojob.Clear(); cofill.Clear(); - + render_cx = ib.GetWidth(); if(mode == MODE_SUBPIXEL) { render_cx *= 3; @@ -178,7 +178,7 @@ void BufferPainter::Create(ImageBuffer& ib, int mode_) co_subpixel.Clear(); co_span.Clear(); span.Clear(); - + co_clear.Clear(); } @@ -201,11 +201,11 @@ void BufferPainter::Create(ImageBuffer& ib, int mode_) attr.mask = false; attr.invert = false; attr.mtx_serial = 0; - + pathattr = attr; ClearPath(); - + jobcount = fillcount = emptycount = 0; attrstack.Clear(); @@ -227,4 +227,4 @@ BufferPainter::BufferPainter(PainterTarget& t, double tolerance) dummy.Create(1, 1); } -} +} \ No newline at end of file diff --git a/uppsrc/RichEdit/Clip.cpp b/uppsrc/RichEdit/Clip.cpp index 97a6d5de9..a6e85c874 100644 --- a/uppsrc/RichEdit/Clip.cpp +++ b/uppsrc/RichEdit/Clip.cpp @@ -12,6 +12,8 @@ RichObject RichEdit::Adjust(RichObject o) void RichEdit::InsertImage() { + if(!allow_objects) + return; if(!imagefs.ExecuteOpen(t_("Open image from file"))) return; String fn = ~imagefs; @@ -34,21 +36,18 @@ void RichEdit::InsertImage() void RichEdit::InsertDiagram() { - TopWindow app; - app.Icon(DiagramImg::Diagram()); - app.Title("Diagram"); - app.Sizeable().Zoomable(); - DiagramEditor de; - app.Add(de.SizePos()); - app.Execute(); + if(!allow_objects) + return; - RichText clip; - RichPara p; - RichObject o = RichObject("qdf", ZCompress(de.Save())); - o.InitSize(0, 0); - p.Cat(o, formatinfo); - clip.Cat(p); - ClipPaste(clip, "image/qdf"); + RichObject o; + if(EditDiagram(o)) { + RichText clip; + RichPara p; + o.InitSize(0, 0); + p.Cat(o, formatinfo); + clip.Cat(p); + ClipPaste(clip, "image/qdf"); + } } bool RichEdit::Accept(PasteClip& d, RichText& clip, String& fmt) diff --git a/uppsrc/RichEdit/ColumnPopUp.cpp b/uppsrc/RichEdit/ColumnPopUp.cpp index 6ef04f7a2..0d5da2ec1 100644 --- a/uppsrc/RichEdit/ColumnPopUp.cpp +++ b/uppsrc/RichEdit/ColumnPopUp.cpp @@ -81,6 +81,8 @@ int ColumnPopUp::Execute() void ColumnPopUp::Deactivate() { Close(); + IgnoreMouseClick(); + cursor = Null; } }; \ No newline at end of file diff --git a/uppsrc/RichEdit/Diagram.iml b/uppsrc/RichEdit/Diagram.iml index f91227101..e67891540 100644 --- a/uppsrc/RichEdit/Diagram.iml +++ b/uppsrc/RichEdit/Diagram.iml @@ -3,7 +3,7 @@ IMAGE_ID(DisplayGrid__UHD) IMAGE_ID(Diagram__UHD) IMAGE_ID(RoundRect__UHD) IMAGE_ID(RotateCursor__UHD) -IMAGE_ID(SelectMode__UHD) +IMAGE_ID(LineCursor__UHD) IMAGE_ID(Line__UHD) IMAGE_ID(Grid) IMAGE_ID(HorzCenter__UHD) @@ -36,7 +36,7 @@ IMAGE_ID(DisplayGrid) IMAGE_ID(Diagram) IMAGE_ID(RoundRect) IMAGE_ID(RotateCursor) -IMAGE_ID(SelectMode) +IMAGE_ID(LineCursor) IMAGE_ID(Line) IMAGE_ID(HorzCenter) IMAGE_ID(VertCenter) @@ -140,23 +140,45 @@ IMAGE_DATA(147,189,151,176,222,27,79,247,94,99,250,253,18,52,196,29,57,207,199,9 IMAGE_END_DATA(736, 1) IMAGE_BEGIN_DATA -IMAGE_DATA(120,156,237,217,81,110,131,48,16,69,209,183,156,46,43,251,223,68,42,71,85,36,192,6,143,25,123,92,124,143,148,79) -IMAGE_DATA(248,184,175,184,105,249,121,233,37,0,88,216,251,239,131,24,111,177,65,164,183,216,32,210,190,63,27,140,245,105,158,136) -IMAGE_DATA(13,34,124,251,179,65,136,77,127,54,24,238,208,159,13,134,202,246,103,131,97,138,253,119,27,160,143,211,254,108,208,157) -IMAGE_DATA(165,63,27,248,187,236,207,6,93,85,245,103,131,110,170,251,179,65,23,166,254,108,224,206,220,159,13,92,53,245,103,3) -IMAGE_DATA(55,205,253,217,192,197,173,254,108,112,219,237,254,108,112,139,75,127,54,104,230,214,159,13,154,184,246,103,3,51,247,254) -IMAGE_DATA(108,96,210,165,255,110,3,148,141,232,207,6,101,221,250,179,65,149,174,253,217,224,82,247,254,108,112,106,72,127,54,40) -IMAGE_DATA(26,214,159,13,178,134,246,103,131,131,225,253,217,96,35,164,63,27,124,133,245,103,131,143,208,254,108,16,223,127,241,13) -IMAGE_DATA(166,232,159,104,205,13,166,233,159,104,189,13,166,234,159,104,173,13,232,31,107,186,254,137,214,217,96,202,254,137,214,216) -IMAGE_DATA(96,218,254,137,158,191,193,212,253,19,61,123,131,233,251,39,122,238,6,255,162,127,162,103,110,48,250,253,175,231,231,9) -IMAGE_DATA(186,246,87,191,246,79,217,96,244,207,63,182,154,251,91,174,21,253,75,110,247,175,185,94,244,47,105,234,175,204,89,204) -IMAGE_DATA(6,77,204,253,117,60,207,233,223,206,212,95,199,246,82,229,51,144,185,14,109,191,67,247,13,57,131,218,89,187,149,218) -IMAGE_DATA(209,191,141,229,220,56,235,198,119,209,54,167,221,84,215,190,234,94,244,207,42,54,147,173,253,233,189,232,95,148,109,38) -IMAGE_DATA(123,251,226,189,216,224,212,161,153,218,218,23,239,71,255,83,155,94,186,215,254,112,191,138,254,171,111,240,237,37,159,30) -IMAGE_DATA(156,65,54,185,255,229,184,220,147,254,85,122,156,5,60,3,118,158,13,232,31,143,13,98,209,63,30,223,69,99,209,63) -IMAGE_DATA(22,103,80,60,54,136,69,255,88,213,253,217,160,27,158,129,88,244,143,213,210,159,13,124,241,183,64,44,158,129,120,151) -IMAGE_DATA(27,104,219,158,254,190,106,222,245,211,189,159,220,187,78,186,143,69,243,88,52,7,0,0,192,242,126,1,183,240,62,238) -IMAGE_END_DATA(512, 1) +IMAGE_DATA(120,156,237,214,81,174,219,56,12,5,208,44,103,118,53,91,237,206,58,83,160,254,121,136,37,203,150,68,74,62,167,240) +IMAGE_DATA(215,75,44,138,188,44,242,207,175,207,175,207,191,255,255,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0) +IMAGE_DATA(0,120,161,223,127,31,120,155,223,63,30,120,139,159,217,183,3,188,137,252,243,86,103,217,183,3,236,174,150,125,59,192) +IMAGE_DATA(174,174,102,223,14,176,155,214,236,219,1,118,34,255,188,213,105,190,15,165,207,132,84,12,125,84,179,111,7,216,84,49) +IMAGE_DATA(211,141,249,183,3,172,164,41,251,118,128,205,52,103,223,239,32,54,113,59,251,118,128,197,61,206,190,29,96,81,183,126) +IMAGE_DATA(243,223,204,191,29,32,147,174,217,183,3,44,166,123,246,47,238,0,68,27,150,125,59,64,114,195,179,111,7,72,106,200) +IMAGE_DATA(111,254,155,249,183,3,204,52,53,251,118,128,68,66,178,111,7,72,32,52,251,118,128,96,242,207,91,165,200,190,29,32) +IMAGE_DATA(64,170,236,219,1,38,74,153,125,59,192,36,105,179,127,40,213,24,210,49,118,145,250,255,254,67,173,206,128,190,177,190) +IMAGE_DATA(37,178,127,168,213,27,208,63,214,181,84,246,15,181,186,3,250,200,154,150,203,254,161,84,123,72,39,89,205,178,217,63) +IMAGE_DATA(148,238,16,210,81,86,177,228,239,158,159,106,247,8,232,43,249,109,145,253,67,237,62,1,253,37,175,173,178,127,168,221) +IMAGE_DATA(43,160,207,228,180,93,246,15,165,187,133,116,154,108,174,252,63,249,248,233,156,219,158,15,239,53,45,103,137,243,111,7) +IMAGE_DATA(222,105,106,198,146,231,223,14,188,143,252,203,255,91,77,207,215,2,249,183,3,239,16,146,173,69,242,111,7,120,106,116) +IMAGE_DATA(254,33,51,249,231,205,228,159,55,147,127,222,76,254,121,51,249,231,205,228,159,55,147,127,222,76,254,121,51,249,39,131) +IMAGE_DATA(168,172,236,152,127,123,183,150,232,172,236,152,127,59,176,134,232,188,236,150,255,232,243,105,19,157,153,157,242,31,125,62) +IMAGE_DATA(237,162,103,38,255,68,201,144,155,93,242,159,161,6,218,100,152,153,252,19,161,52,175,153,115,219,33,255,89,122,201,117) +IMAGE_DATA(87,102,54,99,110,171,231,63,75,31,105,147,101,110,242,207,108,87,103,54,122,118,167,103,118,204,127,72,253,147,235,160) +IMAGE_DATA(77,244,220,170,103,118,206,127,216,61,6,159,207,61,173,115,235,53,187,203,231,13,202,127,200,93,58,159,203,51,167,121) +IMAGE_DATA(59,251,219,167,207,236,134,101,63,104,7,138,245,15,60,151,103,138,153,59,251,251,231,254,236,134,231,62,96,15,170,119) +IMAGE_DATA(232,124,30,125,92,202,94,233,115,61,206,170,157,31,176,3,93,238,53,160,135,244,53,99,118,161,185,159,176,7,242,191) +IMAGE_DATA(174,203,25,60,251,236,167,60,191,84,217,191,120,151,46,247,107,60,151,249,154,50,120,246,249,207,247,249,165,204,125,227) +IMAGE_DATA(157,30,221,243,198,153,204,115,43,139,165,239,213,222,219,122,214,76,173,117,15,238,33,227,221,206,100,233,187,45,79,54) +IMAGE_DATA(179,238,85,249,62,115,132,229,63,187,209,247,171,124,159,241,30,103,179,244,142,167,239,206,98,228,29,43,239,97,172,199) +IMAGE_DATA(249,60,123,199,211,247,102,52,234,174,133,247,48,78,183,156,150,222,117,247,157,119,107,152,117,206,196,254,49,198,148,249) +IMAGE_DATA(141,150,237,220,222,253,27,158,130,247,234,154,155,94,239,185,123,94,239,251,220,173,163,215,123,62,242,63,90,247,188,244) +IMAGE_DATA(120,71,203,57,87,159,89,245,244,120,199,151,135,254,194,178,242,68,169,238,43,79,102,149,218,233,107,169,124,156,213,123) +IMAGE_DATA(247,201,170,80,51,125,45,145,139,179,58,123,61,217,20,106,165,159,213,179,80,203,136,29,160,36,117,14,206,234,43,60) +IMAGE_DATA(77,247,92,248,254,60,151,118,254,165,218,78,158,199,119,94,176,15,60,147,110,230,103,53,21,158,174,119,95,172,39,60) +IMAGE_DATA(147,106,214,103,245,20,158,33,247,95,168,47,60,147,98,198,223,234,168,60,195,251,80,122,18,245,136,251,194,103,123,86) +IMAGE_DATA(67,225,9,233,73,242,94,209,46,124,166,165,26,78,158,240,222,36,238,23,109,178,207,51,195,108,229,127,95,225,191,105) +IMAGE_DATA(191,213,240,201,57,211,20,249,175,244,141,235,82,204,177,48,203,108,51,77,147,253,74,207,184,102,133,89,102,154,167,252) +IMAGE_DATA(239,35,213,44,43,243,204,48,83,253,218,75,186,121,38,158,169,94,237,39,221,60,19,207,52,101,254,43,253,226,92,202) +IMAGE_DATA(89,30,206,234,251,196,204,53,109,246,15,137,122,181,138,85,231,41,255,95,36,234,213,42,82,207,243,143,179,26,63,115) +IMAGE_DATA(231,154,62,251,127,36,232,211,74,106,217,74,49,227,74,109,225,189,74,220,27,59,80,214,218,191,176,249,87,106,8,235) +IMAGE_DATA(83,130,187,203,255,61,61,251,58,37,31,149,179,166,247,105,226,221,236,64,127,51,123,222,37,59,149,119,78,239,83,242) +IMAGE_DATA(156,203,127,89,244,28,110,229,170,242,221,105,61,74,158,113,249,127,38,122,70,167,153,171,124,102,90,31,146,231,156,49) +IMAGE_DATA(162,231,58,115,246,209,247,144,243,117,68,231,96,247,252,179,174,21,243,178,98,205,172,33,123,158,178,215,199,158,178,228) +IMAGE_DATA(43,75,29,240,205,200,236,201,53,43,235,157,127,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0) +IMAGE_DATA(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,46,248,15,117,184,188,62,0,0,0,0,0,0) +IMAGE_END_DATA(1216, 1) IMAGE_BEGIN_DATA IMAGE_DATA(120,156,237,214,187,13,195,48,16,68,65,149,227,178,220,127,19,114,226,204,146,172,15,143,183,193,12,192,6,246,1,36) diff --git a/uppsrc/RichEdit/Diagram.lay b/uppsrc/RichEdit/Diagram.lay index 553ddf267..1950a53cc 100644 --- a/uppsrc/RichEdit/Diagram.lay +++ b/uppsrc/RichEdit/Diagram.lay @@ -1,8 +1,8 @@ LAYOUT(SizeLayout, 212, 164) ITEM(Upp::Switch, size, SetLabel(t_("Automatic\n1250x1000 (4:3)\n1600x900 (16:9)\n1600x800 (2:1)\n1000x1250 (3:4)\nCustom")).LeftPosZ(8, 192).TopPosZ(8, 112)) ITEM(Upp::Label, dv___1, SetLabel(t_("x")).LeftPosZ(92, 40).TopPosZ(104, 19)) - ITEM(Upp::WithDropChoice, cx, NotNull(true).LeftPosZ(24, 64).TopPosZ(104, 19)) - ITEM(Upp::WithDropChoice, cy, NotNull(true).LeftPosZ(100, 64).TopPosZ(104, 19)) + ITEM(Upp::EditInt, cx, NotNull(true).LeftPosZ(24, 64).TopPosZ(104, 19)) + ITEM(Upp::EditInt, cy, NotNull(true).LeftPosZ(100, 64).TopPosZ(104, 19)) ITEM(Upp::Button, ok, SetLabel(t_("OK")).LeftPosZ(68, 64).TopPosZ(136, 24)) ITEM(Upp::Button, cancel, SetLabel(t_("Cancel")).LeftPosZ(138, 64).TopPosZ(136, 24)) END_LAYOUT diff --git a/uppsrc/RichEdit/DiagramBar.cpp b/uppsrc/RichEdit/DiagramBar.cpp index 3b39adcb2..b31d1a366 100644 --- a/uppsrc/RichEdit/DiagramBar.cpp +++ b/uppsrc/RichEdit/DiagramBar.cpp @@ -33,8 +33,8 @@ void DiagramEditor::TheBar(Bar& bar) bar.Add(b, "Move back", DiagramImg::MoveBack(), [=] { MoveFrontBack(true); }); bar.Add(b, "Move front", DiagramImg::MoveFront(), [=] { MoveFrontBack(false); }); bar.Separator(); - bar.Add(b, DiagramImg::HorzCenter(), [=] { Align(true, ALIGN_NULL); }); - bar.Add(b, DiagramImg::VertCenter(), [=] { Align(false, ALIGN_NULL); }); + bar.Add(b, "Horizontal center", DiagramImg::HorzCenter(), [=] { Align(true, ALIGN_NULL); }); + bar.Add(b, "Vertical center", DiagramImg::VertCenter(), [=] { Align(false, ALIGN_NULL); }); bar.Separator(); bool multi = sel.GetCount() > 1; bar.Add(multi, "Align left", DiagramImg::AlignLeft(), [=] { Align(true, ALIGN_LEFT); }); @@ -55,11 +55,13 @@ void DiagramEditor::TheBar(Bar& bar) bar.Separator(); bar.Add("Diagram size", DiagramImg::Size(), [=] { ChangeSize(); }); bar.Separator(); - bar.Add(shape, DPI(45)); - bar.Add(line_start, DPI(45)); - bar.Add(line_end, DPI(45)); - bar.Add(line_width, DPI(45)); - bar.Add(line_dash, DPI(45)); + int icx = IconSz().cx + DPI(4) + DPI(18); + bar.Add(shape, icx); + shape.Enable(!(IsCursor() && findarg(CursorItem().shape, DiagramItem::SHAPE_SVGPATH, DiagramItem::SHAPE_IMAGE) >= 0)); + bar.Add(line_start, icx); + bar.Add(line_end, icx); + bar.Add(line_width, icx); + bar.Add(line_dash, icx); ink.DarkContent(IsDarkContent()); bar.Add(ink); paper.DarkContent(IsDarkContent()); @@ -86,7 +88,7 @@ void DiagramEditor::TheBar(Bar& bar) if(m.aspect_ratio && !m.IsLine()) { Sizef sz1, sz2; ComputeAspectSize(m, sz1, sz2); - m.pt[1] = m.pt[0] + (sz1.cx < sz2.cx ? sz1 : sz2); + m.size = (sz1.cx < sz2.cx ? sz1 : sz2) / 2; } }); }) @@ -95,8 +97,14 @@ void DiagramEditor::TheBar(Bar& bar) Size isz = IconSz(); for(int i = 0; i < tool_count; i++) { DiagramItem m = tl[i]; - m.pt[0] = Point(2, 2); - m.pt[1] = Point(isz.cx - 2, isz.cy - 2); + if(m.IsLine()) { + m.pos = Point(2, 2); + m.size = Size(isz.cx - 4, isz.cy - 4); + } + else { + m.pos = Pointf(Point(isz)) / 2; + m.size = m.pos - 2; + } m.width = log(m.width + 1); bar.Add(MakeIcon(m, isz), [=] { CancelSelection(); @@ -112,11 +120,31 @@ void DiagramEditor::TheBar(Bar& bar) .Check(tool == i); } bar.Break(); - text_editor.FontTools(bar); - text_editor.InkTool(bar); - text_editor.PaperTool(bar); + RichEdit& be = edit_text ? text_editor : editor_bar; + if(!edit_text) { + if(sel.GetCount()) { + editor_bar.formatinfo = GetSelectionFormatInfo(); + editor_bar.SetEditable(); + editor_bar.ShowFormat(); + editor_bar.diagram_bar_hack = true; + editor_bar.WhenSel = [=] { + for(int i : sel) { + String& qtf = data.item[i].qtf; + RichText txt = ParseQTF(qtf); + txt.ApplyFormatInfo(0, editor_bar.formatinfo, txt.GetLength()); + qtf = AsQTF(txt, CHARSET_UTF8, QTF_BODY|QTF_NOCHARSET|QTF_NOLANG|QTF_NOSTYLES); + } + Sync(); + }; + } + else + editor_bar.SetReadOnly(); + } + be.FontTools(bar); + be.InkTool(bar); + be.PaperTool(bar); bar.Separator(); - text_editor.ParaTools(bar); + be.ParaTools(bar); } void DiagramEditor::SetBar() diff --git a/uppsrc/RichEdit/DiagramClip.cpp b/uppsrc/RichEdit/DiagramClip.cpp index fd2e61d63..0218e2ef5 100644 --- a/uppsrc/RichEdit/DiagramClip.cpp +++ b/uppsrc/RichEdit/DiagramClip.cpp @@ -34,21 +34,25 @@ void DiagramEditor::Cut() Delete(); } +void DiagramEditor::AddImage(Pointf pos, const Image& img) +{ + if(IsNull(img)) + return; + Sizef sz = img.GetSize(); + DiagramItem& m = data.item.Add(); + m.shape = DiagramItem::SHAPE_IMAGE; + m.blob_id = data.AddBlob(PNGEncoder().SaveString(img)); + m.pos = pos; + m.size = sz * 0.5; + while(max(m.size.cx, m.size.cy) > 2000) + m.size *= 0.5; + SetCursor(data.item.GetCount() - 1); +} + void DiagramEditor::Paste() { - if(IsClipboardAvailableImage()) { - Image img = ReadClipboardImage(); - if(IsNull(img)) - return; - Sizef sz = img.GetSize(); - int ii = data.item.GetCount(); - DiagramItem& m = data.item.Add(); - m.shape = DiagramItem::SHAPE_IMAGE; - m.blob_id = data.AddBlob(PNGEncoder().SaveString(img)); - m.pt[0] = Rectf(Sizef(data.GetSize())).CenterPos(sz); - m.pt[1] = m.pt[0] + sz; - SetCursor(ii); - } + if(IsClipboardAvailableImage()) + AddImage(Rectf(Sizef(data.GetSize())).CenterPoint(), ReadClipboardImage()); else { String txt = ReadClipboardText(); Diagram clip; @@ -88,8 +92,7 @@ void DiagramEditor::Duplicate() for(int i : sel) { DiagramItem& m = data.item[q++]; m = data.item[i]; - m.pt[0] += offset; - m.pt[1] += offset; + m.pos += offset; } sel.Clear(); for(int i = p; i < data.item.GetCount(); i++) { diff --git a/uppsrc/RichEdit/DiagramEditor.cpp b/uppsrc/RichEdit/DiagramEditor.cpp index 3923ad341..1ebc1199f 100644 --- a/uppsrc/RichEdit/DiagramEditor.cpp +++ b/uppsrc/RichEdit/DiagramEditor.cpp @@ -63,6 +63,16 @@ DiagramEditor::DiagramEditor() GetAttrs(DiagramItem()); } +void DiagramEditor::SerializeSettings(Stream& s) +{ + int version = 1; + + 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); @@ -91,47 +101,7 @@ DiagramEditor& DiagramEditor::AllowDarkContent(bool b) void DiagramEditor::Skin() { SetBar(); -/* - Size icon_sz = IconSz(); - shape.ClearList(); - shape.SetLineCy(icon_sz.cy); - for(int i = 0; i < DiagramItem::SHAPE_SVGPATH; i++) { - DiagramItem m; - m.pt[0] = Point(2, 2); - m.pt[1] = Point(icon_sz.cx - 2, icon_sz.cy - 2); - m.width = DPI(1); - m.shape = i; - shape.Add(i, MakeIcon(m, icon_sz)); - } - - struct Dialine : DiagramItem { - Dialine() { - shape = SHAPE_LINE; - pt[0].y = pt[1].y = 7; - pt[0].x = -9999; - pt[1].x = 9999; - } - }; - - auto LDL = [=](DropList& dl, bool left) { - dl.SetLineCy(icon_sz.cy); - dl.ClearList(); - for(int i = DiagramItem::CAP_NONE; i < DiagramItem::CAP_COUNT; i++) { - dl.Add(i, CapIcon(left ? i : 0, left ? 0 : i)); - } - }; - - LDL(line_start, true); - LDL(line_end, false); - - line_dash.ClearList(); - line_dash.SetLineCy(icon_sz.cy); - for(int i = 0; i < DiagramItem::DASH_COUNT; i++) { - Dialine m; - m.dash = i; - line_dash.Add(i, MakeIcon(m, icon_sz)); - } -*/ + editor_bar.Skin(); } void DiagramEditor::Paint(Draw& w) @@ -159,19 +129,13 @@ void DiagramEditor::Paint(Draw& w) if(display_grid) for(int x = 0; x < dsz.cx; x += 8) - for(int y = 0; y < dsz.cy; y += 8) { - if(((x | y) & 15) == 0) { - iw.DrawRect(x - 2, y, 5, 1, Blend(SWhite(), SGreen(), 60)); - iw.DrawRect(x, y - 2, 1, 5, Blend(SWhite(), SGreen(), 60)); - } - else - iw.DrawRect(x, y, 1, 1, Blend(SWhite(), SGreen())); - } + 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) { + 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()); @@ -190,6 +154,9 @@ void DiagramEditor::Sync() 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() @@ -199,7 +166,11 @@ void DiagramEditor::Layout() void DiagramEditor::ResetUndo() { - undoredo.Reset(GetCurrent()); + 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() @@ -209,7 +180,48 @@ void DiagramEditor::Commit() if(!m.IsLine()) m.Normalize(); } - if(undoredo.Commit(GetCurrent())) { + 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(); } @@ -224,11 +236,7 @@ String DiagramEditor::GetCurrent() bool DiagramEditor::SetCurrent(const String& s) { KillCursor(); -// DLOG("==========="); -// DDUMP(data.item.GetCount()); bool b = LoadFromString(data, s); -// DDUMP(data.item.GetCount()); -// DDUMP(cursor); Sync(); return b; } diff --git a/uppsrc/RichEdit/DiagramEditor.h b/uppsrc/RichEdit/DiagramEditor.h index 8426f5837..b84c1ceff 100644 --- a/uppsrc/RichEdit/DiagramEditor.h +++ b/uppsrc/RichEdit/DiagramEditor.h @@ -5,9 +5,11 @@ #define LAYOUTFILE #include -struct DiaRichEdit : RichEdit { +class DiaRichEdit : public RichEdit { bool Key(dword key, int count) override; + void PasteFilter(RichText& txt, const String& fmt) override; +public: Event<> WhenEnter; Event<> WhenEsc; }; @@ -74,10 +76,10 @@ private: Point draghandle = Point(0, 0); Point dragstart = Point(0, 0); Point dragcurrent = Point(0, 0); - Rectf dragfrom = Rect(0, 0, 0, 0); + Pointf dragfrom = Point(0, 0); Pointf drag_cp = Point(0, 0); double base_rotate = 0; - Vector sdragfrom; + Vector sdragfrom; bool doselection = false; // we are doing rect selection bool grid = true; // snap to grid bool edit_text = false; // text editor is visible @@ -101,6 +103,7 @@ private: ToolBar toolbar; DropColumns shape, line_start, line_end, line_dash, line_width; DiaRichEdit text_editor; + RichEdit editor_bar; // misusing RichEdit to implement selection bar ColorButton ink, paper; @@ -109,7 +112,7 @@ private: DiagramItem tl[2]; ScrollBars sb; - + void SetupDark(ColorPusher& c) const; bool IsDarkContent() const; @@ -148,15 +151,18 @@ private: Image WidthIcon(int i); void PrepareConns(); void UseConns(); - void Grid(int shape, Point& p); - void Grid(const DiagramItem& m, Point& p) { Grid(m.shape, p); } + void Grid(Point& p); + void Grid(Pointf& p); void ChangeSize(); void PopPaint(Draw& w, const Image& m, bool sel); void Shapes(ColumnPopUp& shape); void Caps(ColumnPopUp& m, bool left); void Dashes(ColumnPopUp& m); void Widths(ColumnPopUp& m); + void AddImage(Pointf pos, const Image& img); + RichText::FormatInfo GetFormatInfo(int itemi) const; + RichText::FormatInfo GetSelectionFormatInfo() const; void FixPositions(); void ForEachConst(Event fn) const; @@ -176,7 +182,7 @@ private: void GetAttrs(const DiagramItem& m); void GetAttrs(); - void ComputeAspectSize(DiagramItem& m, Sizef& sz1, Sizef& sz2); + void ComputeAspectSize(DiagramItem& m, Sizef& sz_cx, Sizef& sz_cy); DiagramItem& AddItem(int shape); @@ -192,6 +198,8 @@ private: public: String Save() const; bool Load(const String& s); + + void SerializeSettings(Stream& s); DiagramEditor& DarkContent(bool b = true); DiagramEditor& AllowDarkContent(bool b = true); diff --git a/uppsrc/RichEdit/DiagramIcon.cpp b/uppsrc/RichEdit/DiagramIcon.cpp index b6e9fd325..f13a5e689 100644 --- a/uppsrc/RichEdit/DiagramIcon.cpp +++ b/uppsrc/RichEdit/DiagramIcon.cpp @@ -23,9 +23,10 @@ Image DiagramEditor::MakeIcon(DiagramItem& m, Size isz) }; IconMaker mk; + m.ink = SColorText(); + mk.dark = IsDarkTheme(); mk.m = m; mk.isz = isz; - mk.dark = IsDarkContent(); return MakeImage(mk); } @@ -33,10 +34,16 @@ Image DiagramEditor::ShapeIcon(int i) { Size isz = IconSz(); DiagramItem m; - m.pt[0] = Point(2, 2); - m.pt[1] = Point(isz.cx - 2, isz.cy - 2); - m.width = DPI(1); m.shape = i; + if(m.IsLine()) { + m.pos = Point(2, 2); + m.size = isz - 4; + } + else { + m.pos = Pointf(Point(isz)) / 2; + m.size = m.pos - 2; + } + m.width = DPI(1); m.paper = Null; return MakeIcon(m, isz); } @@ -45,10 +52,11 @@ Image DiagramEditor::CapIcon(int start, int end) { Size isz = IconSz(); DiagramItem m; - m.pt[0] = Point(DPI(6), isz.cy / 2); - m.pt[1] = Point(isz.cx - DPI(6), isz.cy / 2); + m.pos = Point(findarg(start, DiagramItem::CAP_CIRCLEL, DiagramItem::CAP_DISCL) >= 0 ? DPI(6) : DPI(4), isz.cy / 2); + m.size = Size(isz.cx - (findarg(end, DiagramItem::CAP_CIRCLEL, DiagramItem::CAP_DISCL) >= 0 ? DPI(10) : + findarg(end, DiagramItem::CAP_CIRCLE, DiagramItem::CAP_DISC) >= 0 ? DPI(8) : DPI(4)), 0); m.shape = DiagramItem::SHAPE_LINE; - m.width = DPI(2); + m.width = DPI(1); m.cap[0] = start; m.cap[1] = end; return MakeIcon(m, isz); @@ -57,7 +65,7 @@ Image DiagramEditor::CapIcon(int start, int end) Image DiagramEditor::DashIcon(int i) { return MakeValue( - [=] { return String((char *)&i, sizeof(i)); }, + [=] { return String((char *)&i, sizeof(i)) + String("D", (int)IsDarkTheme()); }, [=](Value& v) { Size isz = IconSz(); ImagePainter p(isz); @@ -69,7 +77,7 @@ Image DiagramEditor::DashIcon(int i) p.Move(DPI(2), isz.cy / 2 - (i & 1) * 0.5) .RelLine(isz.cx - DPI(4), 0) .Dash(h, 0) - .Stroke(DPI(2), dark ? White() : Black()); + .Stroke(DPI(2), SColorText()); Image m = p; v = m; return m.GetLength() * sizeof(RGBA); @@ -80,15 +88,14 @@ Image DiagramEditor::DashIcon(int i) Image DiagramEditor::WidthIcon(int i) { return MakeValue( - [=] { return String((char *)&i, sizeof(i)); }, + [=] { return String((char *)&i, sizeof(i)) + String("D", (int)IsDarkTheme()); }, [=](Value& v) { Size isz = IconSz(); - Color ink = dark ? White() : Black(); ImagePainter p(isz); p.Clear(); p.Move(DPI(2), isz.cy / 2.0 - (i & 1) * 0.5) .RelLine(isz.cx - DPI(4), 0) - .Stroke(i, ink); + .Stroke(i, SColorText()); Image m = p; v = m; return m.GetLength() * sizeof(RGBA); @@ -156,8 +163,11 @@ void Upp::DiagramEditor::DropColumns::Paint(Draw& w, const Rect& r, const Value& DiagramEditor::DropColumns::DropColumns() { AddButton().Main().WhenPush << [=] { - SetData(popup.Execute(GetScreenRect(), this)); - Action(); + int c = popup.Execute(GetScreenRect(), this); + if(IsNull(c)) + return; + SetData(c); + UpdateAction(); }; SetDisplay(*this); } diff --git a/uppsrc/RichEdit/DiagramMouse.cpp b/uppsrc/RichEdit/DiagramMouse.cpp index 82c678f5b..5dcdae4d2 100644 --- a/uppsrc/RichEdit/DiagramMouse.cpp +++ b/uppsrc/RichEdit/DiagramMouse.cpp @@ -27,9 +27,10 @@ Point DiagramEditor::GetHandle(int i, Point p_) const if(i >= 0) { const DiagramItem& m = data.item[i]; if(m.IsLine()) { - if(Distance(m.pt[0], p) < 6) + double r = (m.width + 12) / 2 - 1; + if(Distance(m.pos, p) <= r) return Point(-1, -1); - if(Distance(m.pt[1], p) < 6) + if(Distance(m.pos + m.size, p) <= r) return Point(1, 1); } @@ -97,19 +98,19 @@ Image DiagramEditor::CursorImage(Point p, dword keyflags) if(IsNull(h)) return Image::Arrow(); - if(HasCapture() && i >= 0 && data.item[i].IsLine()) + if(HasCapture() && IsCursor() && CursorItem().IsLine()) return Image::Arrow(); int m = h.x * h.y; - if((h.x || h.y) && i >= 0 && data.item[i].IsLine()) - return Image::SizeAll(); + 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; + rot = -M_PI / 4; else if(m < 0) rot = M_PI / 4; @@ -160,7 +161,13 @@ void DiagramEditor::MouseWheel(Point, int zdelta, dword keyflags) { if(keyflags & K_ALT) { if(IsCursor()) { DiagramItem& m = CursorItem(); - m.rotate = ((int(m.rotate) + sgn(zdelta) * 15) / 15 * 15) % 360; + 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(); } @@ -183,10 +190,17 @@ void DiagramEditor::LeftDouble(Point p, dword keyflags) StartText(); } -void DiagramEditor::Grid(int shape, Point& p) +void DiagramEditor::Grid(Point& p) { if(grid && !GetShift()) - p = shape == DiagramItem::SHAPE_LINE ? (p + Point(3, 3)) / 8 * 8 : (p + Point(7, 7)) / 16 * 16; + 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) @@ -207,19 +221,8 @@ void DiagramEditor::LeftDown(Point p, dword keyflags) if(sizehandle.x || sizehandle.y) return; - if(tool >= 0) { - KillCursor(); - DiagramItem& m = AddItem(tl[tool].shape); - m = tl[tool]; - Grid(m, p); - m.pt[0] = m.pt[1] = p; - m.FixPosition(); - draghandle = Point(1, 1); - return; - } - if(IsCursor()) { - drag_cp = CursorItem().GetRect().CenterPoint(); + drag_cp = CursorItem().pos; Point h = GetHandle(cursor, p); if(h.x || h.y) { draghandle = h; @@ -246,10 +249,10 @@ void DiagramEditor::LeftDown(Point p, dword keyflags) else SetCursor(i); if(IsCursor()) { - dragfrom = GetCursorRect(); + dragfrom = CursorItem().pos; sdragfrom.SetCount(sel.GetCount()); for(int i = 0; i < sel.GetCount(); i++) - sdragfrom[i] = data.item[sel[i]]; + sdragfrom[i] = data.item[sel[i]].pos; if(sel.GetCount() > 1 || !CursorItem().IsLine()) PrepareConns(); else @@ -277,22 +280,57 @@ void DiagramEditor::MouseMove(Point p, dword keyflags) moved = moved || p != dragstart; - if(HasCapture() && doselection) { // do rectangular selection - dragcurrent = p; - Rect r(dragstart, dragcurrent); - r.Normalize(); - sel.Clear(); - KillCursor(); - for(int i = 0; i < data.item.GetCount(); i++) - if(r.Contains(data.item[i].pt[0]) && r.Contains(data.item[i].pt[1])) { - sel.FindAdd(i); - SetCursor(i); - } + 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() && (sizehandle.x || sizehandle.y)) { - Grid(DiagramItem::SHAPE_RECT, p); + 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) @@ -302,39 +340,49 @@ void DiagramEditor::MouseMove(Point p, dword keyflags) Sync(); return; } + if(HasCapture() && IsCursor() && (moving || Distance(dragstart, p) >= 8)) { moving = true; DiagramItem& m = CursorItem(); - Pointf p0 = p; - Grid(m, p); if(IsNull(draghandle)) { // move selection - Rectf to = dragfrom.Offseted(p - dragstart); - Pointf tp = to.TopLeft(); - if(grid) - tp = (Point)tp / 16 * 16; - Sizef sz = to.GetSize(); - m.pt[0] = tp; - m.pt[1] = tp + sz; - Pointf offset = tp - dragfrom.TopLeft(); + 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()) { - (Point2 &)data.item[ii] = sdragfrom[i].Offseted(offset); + data.item[ii].pos = sdragfrom[i] + offset; + Grid(data.item[ii].pos); data.item[ii].FixPosition(); } } } else { - auto Do = [](int h, double& a1, double& a2, double a) { - if(h) - (h < 0 ? a1 : a2) = a; - }; + Pointf p0 = p; + Grid(p); if(!m.IsLine()) m.Normalize(); Rectf r = m.GetRect(); if(m.IsLine()) { - Do(draghandle.x, m.pt[0].x, m.pt[1].x, p.x); - Do(draghandle.y, m.pt[0].y, m.pt[1].y, p.y); + 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) { @@ -345,33 +393,24 @@ void DiagramEditor::MouseMove(Point p, dword keyflags) } else { bool rotated = m.rotate; + r -= drag_cp; if(rotated) p = m.Rotation(-1).Transform(Pointf(p) - drag_cp) + drag_cp; - Do(draghandle.x, r.left, r.right, p.x); - Do(draghandle.y, r.top, r.bottom, p.y); - if(m.aspect_ratio && 0) { - m.Normalize(); + 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); - Sizef sz; - if(draghandle.y == 0) - sz = sz1; - else - if(draghandle.x == 0) - sz = sz2; - else - sz = sz1.cx < sz2.cx ? sz1 : sz2; - if(draghandle.x < 0) - m.pt[0].x = m.pt[1].x - sz.cx; - else - m.pt[1].x = m.pt[0].x + sz.cx; - if(draghandle.y < 0) - m.pt[0].y = m.pt[1].y - sz.cy; - else - m.pt[1].y = m.pt[0].y + sz.cy; + m.size = (draghandle.x && draghandle.y && sz1.cx < sz2.cx || draghandle.x ? sz1 : sz2) / 2; } - m.pt[0] = r.TopLeft(); - m.pt[1] = r.BottomRight(); } } UseConns(); @@ -380,160 +419,17 @@ void DiagramEditor::MouseMove(Point p, dword keyflags) } } -void DiagramEditor::LeftUp(Point, dword) +void DiagramEditor::LeftUp(Point p, dword flags) { + Map(p); + if(!moving && !(flags & K_CTRL) && !doselection) { + sel.Clear(); + SetCursor(FindItem(p)); + } moving = doselection = false; - tool = -1; conns.Clear(); Sync(); Commit(); } -void DiagramEditor::RightDown(Point p, dword keyflags) -{ - Map(p); - - FinishText(); - - int ii = FindItem(p); - if(ii >= 0) { - DiagramItem& m = data.item[ii]; - if(m.IsLine()) { - SetCursor(ii); - Point h = GetHandle(cursor, p); - if(h.x) { - int i = h.x > 0; - ColumnPopUp menu; - Caps(menu, i == 0); - int cap = menu.Execute(); - if(cap < 0) - return; - m.cap[i] = cap; - GetAttrs(); - Sync(); - return; - } - if(m.IsClick(p, data)) { - ColumnPopUp menu; - Dashes(menu); - menu.count = DiagramItem::DASH_COUNT + 15; - menu.columns = 5; - menu.WhenPaintItem = [=](Draw& w, Size isz, int ii, bool sel) { - PopPaint(w, ii < DiagramItem::DASH_COUNT ? DashIcon(ii) : WidthIcon(ii - DiagramItem::DASH_COUNT), sel); - }; - - int n = menu.Execute(); - if(n < 0) - return; - if(n < DiagramItem::DASH_COUNT) - m.dash = n; - else - m.width = n - DiagramItem::DASH_COUNT; - GetAttrs(); - Sync(); - return; - } - } - } - - ColumnPopUp shape; - Shapes(shape); - - tool = -1; - - int si = shape.Execute(); - - if(si < 0) - return; - - Sizef size; - String mdata; - if(si == DiagramItem::SHAPE_SVGPATH) { - mdata = SelectFontSymbolSvg(size); - if(IsNull(mdata)) - return; - } - - if(si == DiagramItem::SHAPE_IMAGE) { - String path = SelectFileOpen("Images (*.png *.gif *.jpg *.bmp *.svg)\t*.png *.gif *.jpg *.bmp *.svg"); - - if(GetFileLength(path) > 17000000) { - Exclamation("Image is too large!"); - return; - } - mdata = LoadFile(path); - if(IsNull(mdata)) - return; - size = Null; - if(IsSVG(mdata)) { - Rectf f = GetSVGBoundingBox(mdata); - size = f.GetSize(); - } - else { - StringStream ss(mdata); - One r = StreamRaster::OpenAny(ss); - if(r) - size = r->GetSize(); - } - if(IsNull(size)) { - Exclamation(Format(t_("Unsupported image format in file [* \1%s\1]."), path)); - return; - } - } - - CancelSelection(); - - Point p0 = p; - Grid(si, p); - Pointf cp = Null; // connect line with nearest connection point - if(si == DiagramItem::SHAPE_LINE) { - double mind = DBL_MAX; - for(const DiagramItem& m : data.item) - for(Pointf c : m.GetConnections()) { - double d = Squared(c - (Pointf)p0); - if(d < mind) { - cp = c; - mind = d; - } - } - } - - Size sz; - - DiagramItem& m = AddItem(si); - if(mdata.GetCount()) - m.blob_id = data.AddBlob(mdata); - m.shape = si; // shape must be set before SetAttrs to avoid Normalise - Sizef szf = m.GetStdSize(data); - if(IsNull(cp)) { - m.pt[0] = p; - m.pt[1] = p + szf; - } - else { - m.pt[0] = cp; - m.pt[1] = p; - } - if(si == DiagramItem::SHAPE_IMAGE) { - m.ink = Null; - m.paper = Black(); - m.width = 0; - SetAttrs(ATTR_ALL & ~(ATTR_SHAPE|ATTR_PAPER|ATTR_INK|ATTR_WIDTH)); - } - else - if(si == DiagramItem::SHAPE_SVGPATH) { - m.ink = Null; - m.paper = Black(); - m.width = 0; - SetAttrs(ATTR_ALL & ~(ATTR_SHAPE|ATTR_PAPER|ATTR_INK)); - } - else - SetAttrs(ATTR_ALL & ~ATTR_SHAPE); - Sync(); -} - -void DiagramEditor::RightUp(Point, dword keyflags) -{ - Commit(); -} - } \ No newline at end of file diff --git a/uppsrc/RichEdit/DiagramOps.cpp b/uppsrc/RichEdit/DiagramOps.cpp index 52ee750e3..91e5661b4 100644 --- a/uppsrc/RichEdit/DiagramOps.cpp +++ b/uppsrc/RichEdit/DiagramOps.cpp @@ -142,8 +142,9 @@ void DiagramEditor::Align(bool horz, int align) if(ii != cursor || align == ALIGN_NULL) { DiagramItem& m = data.item[ii]; m.Normalize(); - Pointf& p1 = m.pt[0]; - Pointf& p2 = m.pt[1]; + Rectf r = m.GetRect(); + Pointf p1 = r.TopLeft(); + Pointf p2 = r.BottomRight(); double sz = abs(HoVe(p2) - HoVe(p1)); if(align == ALIGN_LEFT) { HoVe(p1) = HoVe(cp1); @@ -163,6 +164,14 @@ void DiagramEditor::Align(bool horz, int align) HoVe(p1) = (dsz - csz) / 2; HoVe(p2) = HoVe(p1) + csz; } + if(m.IsLine()) { + m.pos = p1; + m.size = p2 - p1; + } + else { + m.pos = (p1 + p2) / 2; + m.size = Pointf(abs(p1.x - p2.x), abs(p1.y - p2.y)) / 2; + } } } UseConns(); @@ -185,8 +194,8 @@ void DiagramEditor::PrepareConns() for(int i = 0; i < data.item.GetCount(); i++) { const DiagramItem& m = data.item[i]; if(m.IsLine()) { - for(int j = 0; j < 2; j++) { - auto *q = map.FindPtr(m.pt[j]); + auto Add = [&](Pointf p, int j) { + auto *q = map.FindPtr(p); if(q) { for(auto w : *q) { Cn& c = conns.Add(); @@ -196,7 +205,9 @@ void DiagramEditor::PrepareConns() c.pi = j; } } - } + }; + Add(m.pos, 0); + Add(m.pos + m.size, 1); } } } @@ -204,17 +215,24 @@ void DiagramEditor::PrepareConns() void DiagramEditor::UseConns() { for(const Cn& cn: conns) - if(sel.Find(cn.li) < 0 && sel.Find(cn.mi) >= 0) - data.item[cn.li].pt[cn.pi] = data.item[cn.mi].GetConnections()[cn.ci]; + if(sel.Find(cn.li) < 0 && sel.Find(cn.mi) >= 0) { + Pointf pt[2]; + DiagramItem& m = data.item[cn.li]; + pt[0] = m.pos; + pt[1] = pt[0] + m.size; + pt[cn.pi] = data.item[cn.mi].GetConnections()[cn.ci]; + m.pos = pt[0]; + m.size = pt[1] - m.pos; + } } -void DiagramEditor::ComputeAspectSize(DiagramItem& m, Sizef& sz1, Sizef& sz2) +void DiagramEditor::ComputeAspectSize(DiagramItem& m, Sizef& sz_cx, Sizef& sz_cy) { m.Normalize(); Sizef sz = m.GetRect().GetSize(); Sizef sz0 = m.GetStdSize(data); - sz1 = Sizef(max(sz.cx, 8.0), max(sz0.cy * sz.cx / sz0.cx, 8.0)); - sz2 = Sizef(max(sz0.cx * sz.cy / sz0.cy, 8.0), max(sz.cy, 8.0)); + sz_cx = Sizef(max(sz.cx, 8.0), max(sz0.cy * sz.cx / sz0.cx, 8.0)); + sz_cy = Sizef(max(sz0.cx * sz.cy / sz0.cy, 8.0), max(sz.cy, 8.0)); } struct SizeDlg : WithSizeLayout { diff --git a/uppsrc/RichEdit/DiagramRight.cpp b/uppsrc/RichEdit/DiagramRight.cpp new file mode 100644 index 000000000..2b10d4c2c --- /dev/null +++ b/uppsrc/RichEdit/DiagramRight.cpp @@ -0,0 +1,154 @@ +#include "RichEdit.h" + +namespace Upp { + +void DiagramEditor::RightDown(Point p, dword keyflags) +{ + Map(p); + + FinishText(); + + int ii = FindItem(p); + if(ii >= 0) { + DiagramItem& m = data.item[ii]; + if(m.IsLine()) { + SetCursor(ii); + Point h = GetHandle(cursor, p); + if(h.x) { + int i = h.x > 0; + ColumnPopUp menu; + Caps(menu, i == 0); + int cap = menu.Execute(); + if(cap < 0) + return; + m.cap[i] = cap; + GetAttrs(); + Sync(); + return; + } + if(m.IsClick(p, data)) { + ColumnPopUp menu; + Dashes(menu); + menu.count = DiagramItem::DASH_COUNT + 15; + menu.columns = 5; + menu.WhenPaintItem = [=](Draw& w, Size isz, int ii, bool sel) { + PopPaint(w, ii < DiagramItem::DASH_COUNT ? DashIcon(ii) : WidthIcon(ii - DiagramItem::DASH_COUNT), sel); + }; + + int n = menu.Execute(); + if(n < 0) + return; + if(n < DiagramItem::DASH_COUNT) + m.dash = n; + else + m.width = n - DiagramItem::DASH_COUNT; + GetAttrs(); + Sync(); + return; + } + } + } + + tool = -1; + SetBar(); + + ColumnPopUp shape; + Shapes(shape); + + int si = shape.Execute(); + + if(si < 0) + return; + + Sizef size; + String mdata; + if(si == DiagramItem::SHAPE_SVGPATH) { + mdata = SelectFontSymbolSvg(size); + if(IsNull(mdata)) + return; + } + + if(si == DiagramItem::SHAPE_IMAGE) { + String path = SelectFileOpen("Images (*.png *.gif *.jpg *.bmp *.svg)\t*.png *.gif *.jpg *.bmp *.svg"); + + if(GetFileLength(path) > 17000000) { + Exclamation("Image is too large!"); + return; + } + mdata = LoadFile(path); + if(IsNull(mdata)) + return; + bool loaded = false; + if(IsSVG(mdata)) { + loaded = true; + } + else { + StringStream ss(mdata); + One r = StreamRaster::OpenAny(ss); + loaded = true; + } + if(IsNull(size)) { + Exclamation(t_("Unsupported image format.")); + return; + } + } + + CancelSelection(); + + Point p0 = p; + Grid(p); + Pointf cp = Null; // connect line with nearest connection point + if(si == DiagramItem::SHAPE_LINE) { + double mind = DBL_MAX; + for(const DiagramItem& m : data.item) + for(Pointf c : m.GetConnections()) { + double d = Squared(c - (Pointf)p0); + if(d < mind) { + cp = c; + mind = d; + } + } + } + + Size sz; + + DiagramItem& m = AddItem(si); + if(mdata.GetCount()) + m.blob_id = data.AddBlob(mdata); + m.shape = si; // shape must be set before SetAttrs to avoid Normalise + Sizef szf = m.GetStdSize(data); + while(max(szf.cx, szf.cy) > 1000) + szf *= 0.5; + if(IsNull(cp)) { + m.pos = p; + m.size = szf / 2; + } + else { + m.pos = cp; + m.size = Pointf(p) - cp; + } + if(si == DiagramItem::SHAPE_IMAGE) { + m.ink = Null; + m.paper = Black(); + m.width = 0; + SetAttrs(ATTR_ALL & ~(ATTR_SHAPE|ATTR_PAPER|ATTR_INK|ATTR_WIDTH)); + } + else + if(si == DiagramItem::SHAPE_SVGPATH) { + m.ink = Null; + m.paper = Black(); + m.width = 0; + SetAttrs(ATTR_ALL & ~(ATTR_SHAPE|ATTR_PAPER|ATTR_INK)); + } + else + SetAttrs(ATTR_ALL & ~ATTR_SHAPE); + Sync(); + GetAttrs(); +} + +void DiagramEditor::RightUp(Point, dword keyflags) +{ + Commit(); +} + +} \ No newline at end of file diff --git a/uppsrc/RichEdit/DiagramText.cpp b/uppsrc/RichEdit/DiagramText.cpp index 4308c1b01..190cd459a 100644 --- a/uppsrc/RichEdit/DiagramText.cpp +++ b/uppsrc/RichEdit/DiagramText.cpp @@ -1,7 +1,7 @@ #include "RichEdit.h" namespace Upp { - + bool DiaRichEdit::Key(dword key, int count) { if(key == K_ENTER) { @@ -18,8 +18,26 @@ bool DiaRichEdit::Key(dword key, int count) return RichEdit::Key(key, count); } +void DiaRichEdit::PasteFilter(RichText& txt, const String& fmt) +{ + if(GetLength() + txt.GetLength() > 2000) { + txt.Clear(); + return; + } + + struct RichTextRemoveObjects : RichText::UpdateIterator { + virtual int operator()(int pos, RichPara& para) { + para.part.RemoveIf([&](int i) { return para[i].object; }); + return UPDATE; + } + } h; + + txt.Iterate(h); +} + void DiagramEditor::SyncEditor() { + text_editor.AllowObjects(false); text_editor.AllowDarkContent(allow_dark_content); text_editor.DarkContent(dark_content); if(edit_text && cursor >= 0) { @@ -67,7 +85,8 @@ void DiagramEditor::StartText() edit_text = true; Sync(); text_editor.SetFocus(); - text_editor.SetQTF("[= " + CursorItem().qtf); + const String& qtf = CursorItem().qtf; + text_editor.SetQTF(qtf.GetCount() ? qtf : "[= "); text_editor.Select(0, text_editor.GetLength()); SyncEditorRect(); } @@ -76,8 +95,26 @@ void DiagramEditor::FinishText() { if(edit_text && cursor >= 0) CursorItem().qtf = AsQTF(text_editor.Get(), CHARSET_UTF8, QTF_BODY|QTF_NOCHARSET|QTF_NOLANG|QTF_NOSTYLES); + edit_text = false; Sync(); } +RichText::FormatInfo DiagramEditor::GetFormatInfo(int itemi) const +{ + RichText text = ParseQTF(data.item[itemi].qtf); + return text.GetFormatInfo(0, text.GetLength()); +} + +RichText::FormatInfo DiagramEditor::GetSelectionFormatInfo() const +{ + RichText::FormatInfo fi; + if(cursor >= 0) + fi = GetFormatInfo(cursor); + for(int ci : sel) + if(ci != cursor) + fi.Combine(GetFormatInfo(ci)); + return fi; +} + } \ No newline at end of file diff --git a/uppsrc/RichEdit/Editor.cpp b/uppsrc/RichEdit/Editor.cpp index 9eb45527c..7b0bfce8d 100644 --- a/uppsrc/RichEdit/Editor.cpp +++ b/uppsrc/RichEdit/Editor.cpp @@ -555,7 +555,7 @@ void RichEdit::SpellCheck() void RichEdit::SerializeSettings(Stream& s) { - int version = 3; + int version = 4; s / version; s % unit; s % showcodes; @@ -575,6 +575,8 @@ void RichEdit::SerializeSettings(Stream& s) StyleKey& k = stylekey[i]; s % k.styleid % k.stylename % k.face % k.height % k.ink % k.paper; } + if(version >= 4) + s % diagram_editor_settings % diagram_editor_placement; } void RichEdit::Reset() @@ -744,6 +746,26 @@ RichEdit& RichEdit::OverridePaper(Color p) return *this; } +bool RichEdit::EditDiagram(RichObject& o) +{ + TopWindow app; + app.Icon(DiagramImg::Diagram()); + app.Title("Diagram"); + app.Sizeable().Zoomable(); + DiagramEditor de; + String s = ~o.GetData(); + if(s.GetCount()) + de.Load(ZDecompress(~o.GetData())); + LoadFromString([&](Stream& s) { de.SerializeSettings(s); }, diagram_editor_settings); + LoadFromString([&](Stream& s) { app.SerializePlacement(s); }, diagram_editor_placement); + app << de.SizePos(); + app.Execute(); + o = RichObject("qdf", ZCompress(de.Save())); + diagram_editor_settings = StoreAsString([&](Stream& s) { de.SerializeSettings(s); }); + diagram_editor_placement = StoreAsString([&](Stream& s) { app.SerializePlacement(s); }); + return true; +} + RichEdit::RichEdit() { floating_zoom = Null; diff --git a/uppsrc/RichEdit/Formating.cpp b/uppsrc/RichEdit/Formating.cpp index 3cf462db8..208adbe82 100644 --- a/uppsrc/RichEdit/Formating.cpp +++ b/uppsrc/RichEdit/Formating.cpp @@ -29,6 +29,11 @@ void RichEdit::ApplyFormat(dword charvalid, dword paravalid) RichText::FormatInfo f = formatinfo; f.charvalid = charvalid; f.paravalid = paravalid; + if(diagram_bar_hack) { + formatinfo = f; + WhenSel(); + return; + } if(objectpos >= 0) { ModifyFormat(objectpos, f, 1); Finish(); diff --git a/uppsrc/RichEdit/Mouse.cpp b/uppsrc/RichEdit/Mouse.cpp index 2e70dc722..79eaf1b27 100644 --- a/uppsrc/RichEdit/Mouse.cpp +++ b/uppsrc/RichEdit/Mouse.cpp @@ -307,8 +307,10 @@ void RichEdit::StdBar(Bar& menu) ObjectTool(menu); } } - LoadImageTool(menu); - InsertDiagramTool(menu); + if(allow_objects) { + LoadImageTool(menu); + InsertDiagramTool(menu); + } } } @@ -372,21 +374,15 @@ void RichEdit::LeftDouble(Point p, dword flags) if(objectpos == c) { RichObject object = GetObject(); Size osz = object.GetSize(); + Sizef pxsz = object.GetPixelSize(); if(!object) return; if(object.GetTypeName() == "qdf") { - TopWindow app; - app.Icon(DiagramImg::Diagram()); - app.Title("Diagram"); - app.Sizeable().Zoomable(); - DiagramEditor de; - de.Load(ZDecompress(~object.GetData())); - app.Add(de.SizePos()); - app.Execute(); - RichText clip; - RichPara p; - RichObject o = RichObject("qdf", ZCompress(de.Save())); - o.InitSize(osz.cx, osz.cy); - ReplaceObject(o); + RichObject o = object; + if(EditDiagram(o)) { + Sizef sz = osz / pxsz * o.GetPixelSize(); + o.SetSize(Size(max((int)round(sz.cx), 1), max((int)round(sz.cy), 1))); + ReplaceObject(o); + } } else { RichObject o = object; diff --git a/uppsrc/RichEdit/RichEdit.h b/uppsrc/RichEdit/RichEdit.h index 2b99d96e0..08fcc66ea 100644 --- a/uppsrc/RichEdit/RichEdit.h +++ b/uppsrc/RichEdit/RichEdit.h @@ -310,6 +310,7 @@ private: PaintInfo paint_info; bool ignore_physical_size; + bool allow_objects = true; bool pixel_mode = false; bool dark_content = false; @@ -318,6 +319,8 @@ private: bool show_zoom = false; Color override_paper = Null; + + bool diagram_bar_hack = false; // if true, calls WhenSel in ApplyFormat static int fh[]; @@ -478,6 +481,9 @@ private: RichPara::CharFormat last_format; Image last_format_img; + + String diagram_editor_settings; + String diagram_editor_placement; Size GetZoomedPage() const; int GetPosY(PageY py) const; @@ -661,6 +667,8 @@ private: Size GetPhysicalSize(const RichObject& obj); + bool EditDiagram(RichObject& o); + struct DisplayDefault : public Display { virtual void Paint(Draw& w, const Rect& r, const Value& q, Color ink, Color paper, dword style) const; @@ -675,6 +683,7 @@ private: friend class StyleKeysDlg; friend class StyleManager; friend class ParaFormatting; + friend class DiagramEditor; using Ctrl::Accept; @@ -860,6 +869,7 @@ public: RichEdit& DarkContent(bool b = true); RichEdit& AllowDarkContent(bool b = true); RichEdit& OverridePaper(Color p); + RichEdit& AllowObjects(bool b) { allow_objects = b; return *this; } struct UndoInfo { int undoserial; diff --git a/uppsrc/RichEdit/RichEdit.upp b/uppsrc/RichEdit/RichEdit.upp index 9f08e8f62..64e416c8e 100644 --- a/uppsrc/RichEdit/RichEdit.upp +++ b/uppsrc/RichEdit/RichEdit.upp @@ -33,6 +33,7 @@ file DiagramEditor.cpp, DiagramIcon.cpp, DiagramMouse.cpp, + DiagramRight.cpp, DiagramOps.cpp, DiagramBar.cpp, DiagramText.cpp, diff --git a/uppsrc/RichText/Diagram.cpp b/uppsrc/RichText/Diagram.cpp index 072c54d90..97122935b 100644 --- a/uppsrc/RichText/Diagram.cpp +++ b/uppsrc/RichText/Diagram.cpp @@ -38,44 +38,47 @@ void DiagramItem::Reset() dash = 0; } -void Point2::Normalize() +void DiagramItem::Serialize(Stream& s) +{ // used for undo/redo mostly + s % pos + % size + % shape + % qtf + % width + % ink + % paper + % blob_id + % flip_horz + % flip_vert + % aspect_ratio + % rotate + % cap[0] + % cap[1]; +} + +void DiagramItem::Normalize() { - if(pt[0].x > pt[1].x) - Swap(pt[0].x, pt[1].x); - if(pt[0].y > pt[1].y) - Swap(pt[0].y, pt[1].y); + if(IsLine()) + return; + size.cx = abs(size.cx); + size.cy = abs(size.cy); } void DiagramItem::FixPosition() { - double x = min(pt[0].x, pt[1].x); - if(x < 0) { - pt[0].x -= x; - pt[1].x -= x; - } - double y = min(pt[0].y, pt[1].y); - if(y < 0) { - pt[0].y -= y; - pt[1].y -= y; - } - auto Clamp = [](Pointf& p) { - p.x = clamp(p.x, 0.0, 10000.0); - p.y = clamp(p.y, 0.0, 10000.0); - }; - Clamp(pt[0]); - Clamp(pt[1]); + Normalize(); + pos.x = clamp(pos.x, 0.0, 100000.0); + pos.y = clamp(pos.y, 0.0, 100000.0); if(IsLine()) return; - if(pt[1].x - pt[0].x < 8) - pt[1].x = pt[0].x + 8; - if(pt[1].y - pt[0].y < 8) - pt[1].y = pt[0].y + 8; + size.cx = clamp(size.cx, 4.0, 100000.0); + size.cy = clamp(size.cy, 4.0, 100000.0); } bool DiagramItem::IsClick(Point p, const Diagram& diagram, bool relaxed) const { if(IsLine()) - return DistanceFromSegment(p, pt[0], pt[1]) < width + 10; + return DistanceFromSegment(p, pos, pos + size) < width + 10; Rectf rect = GetRect(); Pointf cp = rect.CenterPoint(); if(rotate) { @@ -97,8 +100,8 @@ bool DiagramItem::IsClick(Point p, const Diagram& diagram, bool relaxed) const DiagramItem m = *this; m.paper = Blue(); m.ink = Blue(); - m.pt[0] = Pointf(0, 0); - m.pt[1] = Pointf(64, 64); + m.pos = Pointf(0, 0); + m.size = Sizef(64, 64); m.Paint(p, diagram); Image img = p.GetResult(); v = img; @@ -109,6 +112,7 @@ bool DiagramItem::IsClick(Point p, const Diagram& diagram, bool relaxed) const [clamp(int(64 * (p.x - rect.left) / rect.GetWidth()), 0, 63)].a; } +#if 0 bool DiagramItem::IsTextClick(Point p0) const { Zoom zoom = Diagram::TextZoom(); @@ -154,13 +158,13 @@ bool DiagramItem::IsTextClick(Point p0) const int pos = txt.GetPos((int)p.x, PageY(0, (int)p.y), page); return pos < txt.GetLength() && txt.GetRichPos(pos).chr != '\n' && txt.GetCaret(pos, page).Contains(p); } +#endif Rect DiagramItem::GetTextEditRect() const { if(IsLine()) { - int d = max(10, int(Distance(pt[0], pt[1]) + 0.5)); - Point c = (pt[0] + pt[1]) / 2; - return Rect(c.x - d / 2, c.y, c.x + d, c.y); + int d = max(10, int(Length(size) + 0.5)); + return Rect(pos.x - d / 2, pos.y, pos.x + d, pos.y); } return GetRect(); } @@ -168,7 +172,7 @@ Rect DiagramItem::GetTextEditRect() const void DiagramItem::Save(StringBuffer& r) const { r << Shape[clamp(shape, 0, Shape.GetCount() - 1)] << ' '; - r << pt[0].x << ' ' << pt[0].y << ' ' << pt[1].x << ' ' << pt[1].y; + r << pos.x << ' ' << pos.y << ' ' << size.cx << ' ' << size.cy; if(qtf.GetCount()) r << " " << AsCString(qtf); auto col = [&](Color c) { @@ -208,10 +212,10 @@ void DiagramItem::Load(CParser& p, const Diagram& diagram) if(q < 0) p.ThrowError("Unknown element"); shape = q; - this->pt[0].x = p.ReadDouble(); - this->pt[0].y = p.ReadDouble(); - this->pt[1].x = p.ReadDouble(); - this->pt[1].y = p.ReadDouble(); + this->pos.x = p.ReadDouble(); + this->pos.y = p.ReadDouble(); + this->size.cx = p.ReadDouble(); + this->size.cy = p.ReadDouble(); auto col = [&] { if(p.Id("null")) return Color(Null); @@ -367,6 +371,11 @@ Rectf Diagram::GetBlobSvgPathBoundingBox(const String& id) const return v.Is() ? (Rectf)v : (Rectf)Null; } +void Diagram::SweepBlobs(const Index& keep_ids) +{ + blob.RemoveIf([&](int i) { return keep_ids.Find(blob.GetKey(i)) < 0; }); +} + void Diagram::Paint(Painter& w, const Diagram::PaintInfo& p) const { w.Begin(); @@ -378,7 +387,7 @@ void Diagram::Paint(Painter& w, const Diagram::PaintInfo& p) const if(p.display_grid) for(const DiagramItem& m : item) if(m.IsLine()) - conn << m.pt[0] << m.pt[1]; + conn << m.pos << m.pos + m.size; for(int i = 0; i < item.GetCount(); i++) { dword style = 0; if(i == p.cursor) @@ -405,6 +414,7 @@ void Diagram::Serialize(Stream& s) void Diagram::Save(StringBuffer& r) const { + r << "QDF 1.0;\n"; if(!IsNull(size)) r << "size " << size.cx << " " << size.cy << ";\n"; if(!IsNull(img)) { @@ -440,7 +450,15 @@ void Diagram::Load(CParser& p) { item.Clear(); blob.Clear(); + img.Clear(); + img_hd = false; + size = Null; while(!p.IsEof()) + if(p.Id("QDF")) { + p.ReadDouble(); + p.PassChar(';'); + } + else if(p.Id("size")) { size.cx = clamp(p.ReadInt(), 1, 10000); size.cy = clamp(p.ReadInt(), 1, 10000); diff --git a/uppsrc/RichText/Diagram.h b/uppsrc/RichText/Diagram.h index 33833c97d..5304efd35 100644 --- a/uppsrc/RichText/Diagram.h +++ b/uppsrc/RichText/Diagram.h @@ -1,18 +1,9 @@ -struct Point2 : Moveable { - Pointf pt[2]; - - void Offset(Pointf p) { pt[0] += p; pt[1] += p; } - Point2 Offseted(Pointf p) const { Point2 r = *this; r.Offset(p); return r; } - void Normalize(); - Rectf GetRect() const { return Rectf(pt[0], pt[1]).Normalized(); } - String ToString() const { return String() << pt[0] << " - " << pt[1]; } - - void Serialize(Stream& s) { s % pt[0] % pt[1]; } -}; - struct Diagram; -struct DiagramItem : Point2 { +struct DiagramItem { + Pointf pos; + Sizef size; + int shape; String qtf; double width; @@ -34,12 +25,9 @@ struct DiagramItem : Point2 { SHAPE_PARALLELOGRAM, SHAPE_CYLINDER, SHAPE_TRIANGLE, - SHAPE_ITRIANGLE, - SHAPE_ARROWLEFT, SHAPE_ARROWRIGHT, SHAPE_ARROWHORZ, SHAPE_ARROWDOWN, - SHAPE_ARROWUP, SHAPE_ARROWVERT, SHAPE_ARC, @@ -55,6 +43,12 @@ struct DiagramItem : Point2 { CAP_DISC, CAP_DIM, CAP_T, + + CAP_ARROWL, + CAP_CIRCLEL, + CAP_DISCL, + CAP_DIML, + CAP_TL, CAP_COUNT }; @@ -73,6 +67,11 @@ struct DiagramItem : Point2 { int cap[2] = { CAP_NONE, CAP_NONE }; int dash = 0; + void Offset(Pointf p) { pos += p; } + void Normalize(); + Rectf GetRect() const { return IsLine() ? Rectf(pos, size) : Rectf(pos - size, pos + size); } + String ToString() const { return String() << pos << " " << size; } + void Paint(Painter& w, const Diagram& diagram, dword style = 0, const Index *conn = nullptr) const; Sizef GetStdSize(const Diagram& diagram) const; @@ -90,7 +89,7 @@ struct DiagramItem : Point2 { Xform2D Rotation(int d = 1) const { return Xform2D::Rotation(d * M_PI * rotate / 180); } - void Serialize(Stream& s) { Point2::Serialize(s); s % shape % ink % paper % qtf % width % cap[0] % cap[1] % dash % blob_id % flip_horz % flip_vert % aspect_ratio; } + void Serialize(Stream& s); void Reset(); void Save(StringBuffer& r) const; @@ -124,6 +123,7 @@ struct Diagram { void Paint(Painter& w, const PaintInfo& pi) const; String AddBlob(const String& data); String GetBlob(const String& id) const; + void SweepBlobs(const Index& keep_ids); Image GetBlobImage(const String& id) const; Rectf GetBlobSvgPathBoundingBox(const String& id) const; void Serialize(Stream& s); diff --git a/uppsrc/RichText/DiagramShape.cpp b/uppsrc/RichText/DiagramShape.cpp index 1b9b2c37b..13147a4d1 100644 --- a/uppsrc/RichText/DiagramShape.cpp +++ b/uppsrc/RichText/DiagramShape.cpp @@ -2,14 +2,13 @@ namespace Upp { -Index DiagramItem::LineCap = { "none", "arrow", "circle", "disc", "dim", "T" }; +Index DiagramItem::LineCap = { "none", "arrow", "circle", "disc", "dim", "T", + "arrowL", "circleL", "discL", "dimL", "TL" }; Index DiagramItem::Shape = { "line", "rect", "round_rect", "ellipse", "diamond", "oval", "parallelogram", - "cylinder", - "triangle1", "triangle2", - "arrow_left", "arrow_right", "arrow_horz", - "arrow_down", "arrow_up", "arrow_vert", + "cylinder", "triangle", + "arrow_right", "arrow_horz", "arrow_down", "arrow_vert", "arc", "svgpath", "image" }; @@ -17,20 +16,18 @@ Index DiagramItem::Shape = { "line", "rect", "round_rect", Vector DiagramItem::GetConnections() const { Vector p; - if(shape > SHAPE_ITRIANGLE || rotate) + if(shape > SHAPE_TRIANGLE) return p; if(IsLine()) { - p << pt[0] << pt[1]; + p << pos << pos + size; return p; } Rectf r = GetRect(); p << r.TopCenter() << r.BottomCenter(); - if(findarg(shape, SHAPE_PARALLELOGRAM, SHAPE_TRIANGLE, SHAPE_ITRIANGLE) < 0) + if(findarg(shape, SHAPE_PARALLELOGRAM, SHAPE_TRIANGLE) < 0) p << r.CenterLeft() << r.CenterRight(); if(shape == SHAPE_TRIANGLE) p << r.BottomLeft() << r.BottomRight(); - if(shape == SHAPE_ITRIANGLE) - p << r.TopLeft() << r.TopRight(); if(rotate) { Xform2D rot = Rotation(); Pointf c = r.CenterPoint(); @@ -86,83 +83,131 @@ void DiagramItem::Paint(Painter& w, const Diagram& diagram, dword style, const I w.Stroke(0.2, sel1); }; + w.Move(0, 0).EndPath(); // this is to start a new path for every item + if(IsLine()) { - Pointf v = pt[1] - pt[0]; if(style) { - w.Move(pt[0]).Line(pt[1]).EndPath(); + w.Move(pos).RelLine(size).EndPath(); w.Begin(); if((style & EDITOR) && width == 0) w.Dash("5 1").Stroke(1, 100 * sel2); if(style & (Display::CURSOR | Display::SELECT)) { w.LineCap(LINECAP_ROUND).Stroke(width + 12, (style & Display::SELECT ? 30 : 200) * sel2); double r = (width + 12) / 2 - 1; - w.Circle(pt[0], r).Fill(sel1); - w.Circle(pt[1], r).Fill(sel1); + w.Circle(pos, r).Fill(sel1); + w.Circle(pos + size, r).Fill(sel1); } w.End(); } + + Pointf v = size; double d = Length(v); v = Upp::Normalize(v); - - Pointf a1 = pt[0]; - Pointf a2 = pt[1]; - if(d > 4 * width) { // enough length to have caps - if(findarg(cap[0], CAP_ARROW, CAP_DIM) >= 0) - a1 += v * 4 * width; - if(findarg(cap[1], CAP_ARROW, CAP_DIM) >= 0) - a2 -= v * 4 * width; + + Pointf a1 = pos; + Pointf a2 = pos + size; + + auto CapDef = [&](int i, double& reserve, double& reduce) { + reserve = 0; + reduce = 0; + switch(cap[i]) { + case CAP_DIM: + case CAP_ARROW: + reserve = reduce = 4 * width; + break; + case CAP_DISC: + case CAP_CIRCLE: + reserve = 1.5 * width; + break; + case CAP_DIML: + case CAP_ARROWL: + reserve = reduce = 12 * width; + break; + case CAP_DISCL: + case CAP_CIRCLEL: + reserve = 2.5 * width; + break; + } + }; + + bool docap[2]; + double reserve, reduce; + double dd = d; + CapDef(0, reserve, reduce); + docap[0] = dd > reserve; + if(docap[0]) { + a1 += v * reduce; + dd -= reserve; } + CapDef(1, reserve, reduce); + docap[1] = dd > reserve; + if(docap[1]) + a2 -= v * reduce; w.Move(a1).Line(a2); DoDash(); Stroke(); - Pointf o = Orthogonal(v); - if(d > 4 * width) { - auto PaintCap = [&](int k, Pointf p, Pointf a) { - Pointf oo = max(3.0, width * 2) * o; - switch(k) { - case CAP_NONE: - w.Circle(p, width / 2).Fill(ink); - break; - case CAP_T: - w.Move(p - 2 * oo).Line(p + 2 * oo).Stroke(1, ink); - break; - case CAP_DIM: - w.Move(p - 2 * oo).Line(p + 2 * oo).Stroke(1, ink); - case CAP_ARROW: - w.Move(p).Line(a + oo).Line(a - oo).Fill(ink); - break; - case CAP_DISC: - w.Circle(p, 5).Fill(ink); - break; - case CAP_CIRCLE: - w.Circle(p, 5).Fill(paper).Stroke(1, ink); - break; - } - }; - PaintCap(cap[0], pt[0], a1 + v); - PaintCap(cap[1], pt[1], a2 - v); - } + auto PaintCap = [&](int k, Pointf p, Pointf a) { + Pointf oo = max(3.0, width * 2) * o; + Pointf ool = max(6.0, width * 4) * o; + switch(k) { + case CAP_NONE: + w.Circle(p, width / 2).Fill(ink); + break; + case CAP_T: + w.Move(p - 2 * oo).Line(p + 2 * oo).Stroke(1, ink); + break; + case CAP_DIM: + w.Move(p - 2 * oo).Line(p + 2 * oo).Stroke(1, ink); + case CAP_ARROW: + w.Move(p).Line(a + oo).Line(a - oo).Fill(ink); + break; + case CAP_DISC: + w.Circle(p, 1.5 * width).Fill(ink); + break; + case CAP_CIRCLE: + w.Circle(p, 1.5 * width).Fill(paper).Stroke(1, ink); + break; + case CAP_TL: + w.Move(p - 2 * ool).Line(p + 2 * ool).Stroke(1, ink); + break; + case CAP_DIML: + w.Move(p - 2 * ool).Line(p + 2 * ool).Stroke(1, ink); + case CAP_ARROWL: + w.Move(p).Line(a + ool).Line(a - ool).Fill(ink); + break; + case CAP_DISCL: + w.Circle(p, 2.5 * width).Fill(ink); + break; + case CAP_CIRCLEL: + w.Circle(p, 2.5 * width).Fill(paper).Stroke(1, ink); + break; + } + }; + if(docap[0]) + PaintCap(cap[0], pos, a1 + v); + if(docap[1]) + PaintCap(cap[1], pos + size, a2 - v); - int cx = (int)Distance(pt[0], pt[1]); + int cx = (int)d; int txt_cy = txt.GetHeight(pi.zoom, cx); w.Begin(); - double angle = Bearing(pt[1] - pt[0]); + double angle = Bearing(size); if(angle >= -M_PI / 2 && angle <= M_PI / 2) { - w.Translate(pt[0] - o * (txt_cy + 10)); + w.Translate(pos - o * (txt_cy + 10)); w.Rotate(angle); } else { - w.Translate(pt[1] + o * (txt_cy + 10)); + w.Translate(pos + size + o * (txt_cy + 10)); w.Rotate(angle + M_PI); } txt.Paint(w, 0, 0, cx, pi); w.End(); } else { - Rectf r(pt[0], pt[1]); + Rectf r = GetRect(); r.Normalize(); r.Deflate(width / 2); double w1 = r.GetWidth(); @@ -254,20 +299,6 @@ void DiagramItem::Paint(Painter& w, const Diagram& diagram, dword style, const I w.Move(w2, 0).Line(cx, cy).Line(0, cy).Close(); } break; - case SHAPE_ITRIANGLE: { - text_rect.left += int(cx / 4); - text_rect.right -= int(cx / 4); - text_rect.bottom -= int(cx / 3); - w.Move(w2, cy).Line(cx, 0).Line(0, 0).Close(); - } - break; - case SHAPE_ARROWLEFT: { - double a = 0 + arrow_width; - text_rect.left += int(arrow_width / 3); - w.Move(0, h2).Line(a, 0).Line(a, h4).Line(cx, h4) - .Line(cx, bh4).Line(a, bh4).Line(a, cy).Close(); - } - break; case SHAPE_ARROWRIGHT: { double a = cx - arrow_width; @@ -301,21 +332,6 @@ void DiagramItem::Paint(Painter& w, const Diagram& diagram, dword style, const I .Close(); } break; - case SHAPE_ARROWUP: { - double a = arrow_height; - text_rect.left += w4; - text_rect.right -= w4; - text_rect.top += 3 * arrow_height / 4; - w.Move(w2, 0) - .Line(cx, a) - .Line(cx - w4, a) - .Line(cx - w4, cy) - .Line(w4, cy) - .Line(w4, a) - .Line(0, a) - .Close(); - } - break; case SHAPE_ARROWDOWN: { double a = cy - arrow_height; text_rect.left += w4; @@ -448,7 +464,7 @@ void DiagramItem::Paint(Painter& w, const Diagram& diagram, dword style, const I w.End(); - if((style & GRID) && !rotate) + if((style & GRID)) for(Pointf p : GetConnections()) { w.Circle(p, 5); if(conn && conn->Find(p) >= 0) @@ -492,9 +508,9 @@ Sizef DiagramItem::GetStdSize(const Diagram& diagram) const } if(shape == SHAPE_CYLINDER) - return Size(100, 128); + return Size(96, 128); - if(findarg(shape, SHAPE_CYLINDER, SHAPE_ARROWDOWN, SHAPE_ARROWUP, SHAPE_ARROWVERT) >= 0) + if(findarg(shape, SHAPE_CYLINDER, SHAPE_ARROWDOWN, SHAPE_ARROWVERT) >= 0) return Size(64, 128); return Size(128, 64); diff --git a/uppsrc/RichText/EncodeQtf.cpp b/uppsrc/RichText/EncodeQtf.cpp index 7e0720f56..1cdd9c5c4 100644 --- a/uppsrc/RichText/EncodeQtf.cpp +++ b/uppsrc/RichText/EncodeQtf.cpp @@ -524,7 +524,9 @@ String AsQTF(const RichText& text, byte charset, dword options) lngc.GetAdd(p.format.language, 0)++; } dword lang = lngc.GetCount() ? lngc.GetKey(FindMax(lngc.GetValues())) : 0; - qtf << "["; + bool bracket = !(options & QTF_NOCHARSET) || lang && !(options & QTF_NOLANG); + if(bracket) + qtf << "["; if(!(options & QTF_NOCHARSET)) { qtf << "{"; if(charset == CHARSET_UTF8) @@ -541,7 +543,8 @@ String AsQTF(const RichText& text, byte charset, dword options) } if(lang && !(options & QTF_NOLANG)) qtf << "%" << LNGAsText(SetLNGCharset(lang, CHARSET_DEFAULT)); - qtf << " "; + if(bracket) + qtf << " "; if(crlf) qtf << "\r\n"; RichStyle defstyle; @@ -549,13 +552,10 @@ String AsQTF(const RichText& text, byte charset, dword options) QTFEncodeTxt(qtf, text, text.GetStyles(), defstyle, options, sm, charset, lang); - qtf << "]"; + if(bracket) + qtf << "]"; } - if(options & QTF_NOSTYLES) // remove redundant [] - while(qtf.StartsWith("[ ") && qtf.EndsWith("]")) - qtf = qtf.Mid(2, qtf.GetCount() - 3); - return qtf; } diff --git a/uppsrc/RichText/Format.cpp b/uppsrc/RichText/Format.cpp index 2fd7155e9..a6161df7b 100644 --- a/uppsrc/RichText/Format.cpp +++ b/uppsrc/RichText/Format.cpp @@ -132,6 +132,14 @@ void RichTxt::FormatInfo::Combine(const RichPara::Format& fmt) paravalid &= ~NEWHDRFTR; } +void RichTxt::FormatInfo::Combine(const FormatInfo& fmt) +{ + Combine((const RichPara::Format&)fmt); + Combine((const RichPara::CharFormat&)fmt); + paravalid &= fmt.paravalid; + charvalid &= fmt.charvalid; +} + void RichTxt::FormatInfo::ApplyTo(RichPara::CharFormat& fmt) const { if(charvalid & BOLD) diff --git a/upptst/Diagram/Diagram.upp b/upptst/Diagram/Diagram.upp index f758038f5..0cc6dd9d8 100644 --- a/upptst/Diagram/Diagram.upp +++ b/upptst/Diagram/Diagram.upp @@ -3,7 +3,8 @@ uses RichEdit; file - main.cpp; + main.cpp, + app.tpp; mainconfig "" = "GUI";