RichText, RichEdit: DiagramEditor

This commit is contained in:
Mirek Fidler 2025-09-16 10:28:04 +02:00
parent eafd322058
commit 0d6c7f0601
34 changed files with 977 additions and 594 deletions

View file

@ -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<const String&> fn)
{
fn(commit.ids);
for(const Entry& e : undo)
fn(e.ids);
for(const Entry& e : redo)
fn(e.ids);
}
}

View file

@ -4,7 +4,7 @@ String BinUndiff(const String& base, const String& bin_diff);
class BinUndoRedo {
struct Entry : Moveable<Entry> {
String data;
String ids;
Value info;
};
Entry commit;
Vector<Entry> 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<const String&> fn);
};

View file

@ -1334,6 +1334,28 @@ bool StoreToFile(Event<Stream&> serialize, const char *file, int version) {
return !f.IsError();
}
String StoreAsString(Event<Stream&> serialize) {
StringStream ss;
Store(serialize, ss);
return ss;
}
bool LoadFromString(Event<Stream&> serialize, const String& s) {
StringStream ss(s);
return Load(serialize, ss);
}
Vector<String> StoreAsStrings(Event<Stream&> serialize) {
StringsStreamOut ss;
Store(serialize, ss);
return ss.PickResult();
}
bool LoadFromStrings(Event<Stream&> serialize, const Vector<String>& s) {
StringsStreamIn ss(s);
return Load(serialize, ss);
}
Stream& Pack16(Stream& s, int& i) {
if(s.IsLoading()) {
i = (int16) s.Get16le();

View file

@ -428,6 +428,10 @@ bool Load(Event<Stream&> serialize, Stream& stream, int version = Null);
bool Store(Event<Stream&> serialize, Stream& stream, int version = Null);
bool LoadFromFile(Event<Stream&> serialize, const char *file = NULL, int version = Null);
bool StoreToFile(Event<Stream&> serialize, const char *file = NULL, int version = Null);
String StoreAsString(Event<Stream&> serialize);
bool LoadFromString(Event<Stream&> serialize, const String& s);
Vector<String> StoreAsStrings(Event<Stream&> serialize);
bool LoadFromStrings(Event<Stream&> serialize, const Vector<String>& s);
template <class T>
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 <class T>
String StoreAsString(T& x) {
StringStream ss;
Store(x, ss);
return ss;
return StoreAsString([&](Stream& s) { s % x; });
}
template <class T>
bool LoadFromString(T& x, const String& s) {
StringStream ss(s);
return Load(x, ss);
return LoadFromString([&](Stream& s) { s % x; }, s);
}
template <class T>
Vector<String> StoreAsStrings(T& x) {
StringsStreamOut ss;
Store(x, ss);
return ss.PickResult();
return StoreAsStrings([&](Stream& s) { s % x; });
}
template <class T>
bool LoadFromStrings(T& x, const Vector<String>& 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<Stream&> 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);

View file

@ -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

View file

@ -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<Stream[@(0.0.255) `&]>
[*@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<Stream[@(0.0.255) `&]> [*@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<String> [* StoreAsStrings](Event<Stream[@(0.0.255) `&
]> [*@3 serialize])&]
[s2;%% Stores data as Vector<String>. Useful in rare cases where
stored data is >2GB.&]
[s3; &]
[s4; &]
[s5;:Upp`:`:LoadFromStrings`(Event`,const Vector`&`): [@(0.0.255) bool]
[* LoadFromStrings](Event<Stream[@(0.0.255) `&]> [*@3 serialize], [@(0.0.255) const]
Vector<String>[@(0.0.255) `&] [*@3 s])&]
[s2;%% Loads data from Vector<String>. 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<String> [* StoreAsStrings](T[@(0.0.255) `&] [*@3 x])&]
[s2;%% Stores [%-*@3 x] using its Serialize method a Vector<String>.
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<String>[@(0.0.255) `&] [*@3 s])&]
[s2;%% Restores serialized data from the Vector<String> (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`<Upp`:`:Stream`&`>`): [@(0.0.255) v
oid]_[* RegisterGlobalSerialize]([@(0.0.255) const]_[@(0.0.255) char]_`*[*@3 name],

View file

@ -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

View file

@ -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

View file

@ -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;

View file

@ -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);
}
}
}

View file

@ -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)

View file

@ -81,6 +81,8 @@ int ColumnPopUp::Execute()
void ColumnPopUp::Deactivate()
{
Close();
IgnoreMouseClick();
cursor = Null;
}
};

View file

@ -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)

View file

@ -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<Upp::EditInt>, cx, NotNull(true).LeftPosZ(24, 64).TopPosZ(104, 19))
ITEM(Upp::WithDropChoice<Upp::EditInt>, 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

View file

@ -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()

View file

@ -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++) {

View file

@ -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<Value> 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<Value> 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<String> 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;
}

View file

@ -5,9 +5,11 @@
#define LAYOUTFILE <RichEdit/Diagram.lay>
#include <CtrlCore/lay.h>
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<Point2> sdragfrom;
Vector<Pointf> 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<const DiagramItem&> 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);

View file

@ -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);
}

View file

@ -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<StreamRaster> 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();
}
}

View file

@ -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<TopWindow> {

View file

@ -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<StreamRaster> 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();
}
}

View file

@ -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;
}
}

View file

@ -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;

View file

@ -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();

View file

@ -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;

View file

@ -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;

View file

@ -33,6 +33,7 @@ file
DiagramEditor.cpp,
DiagramIcon.cpp,
DiagramMouse.cpp,
DiagramRight.cpp,
DiagramOps.cpp,
DiagramBar.cpp,
DiagramText.cpp,

View file

@ -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>() ? (Rectf)v : (Rectf)Null;
}
void Diagram::SweepBlobs(const Index<String>& 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);

View file

@ -1,18 +1,9 @@
struct Point2 : Moveable<Point2> {
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<Pointf> *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<String>& keep_ids);
Image GetBlobImage(const String& id) const;
Rectf GetBlobSvgPathBoundingBox(const String& id) const;
void Serialize(Stream& s);

View file

@ -2,14 +2,13 @@
namespace Upp {
Index<String> DiagramItem::LineCap = { "none", "arrow", "circle", "disc", "dim", "T" };
Index<String> DiagramItem::LineCap = { "none", "arrow", "circle", "disc", "dim", "T",
"arrowL", "circleL", "discL", "dimL", "TL" };
Index<String> 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<String> DiagramItem::Shape = { "line", "rect", "round_rect",
Vector<Pointf> DiagramItem::GetConnections() const
{
Vector<Pointf> 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);

View file

@ -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;
}

View file

@ -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)

View file

@ -3,7 +3,8 @@ uses
RichEdit;
file
main.cpp;
main.cpp,
app.tpp;
mainconfig
"" = "GUI";