#include "RichEdit.h" namespace Upp { void RichEdit::PasteFilter(RichText& txt, const String&) { Filter(txt); } void RichEdit::Filter(RichText& txt) {} void BegSelFixRaw(RichText& text) { RichPos p = text.GetRichPos(0, 1); ASSERT(p.table == 1); if(p.table != 1) return; RichPara::Format fmt; text.InsertParaSpecial(1, true, fmt); } void BegSelUnFixRaw(RichText& text) { ASSERT(text.GetLength() > 0); RichPos p = text.GetRichPos(1, 1); ASSERT(p.table == 1); if(p.table != 1) return; text.RemoveParaSpecial(1, true); } void RichEdit::UndoBegSelFix::Apply(RichText& txt) { BegSelUnFixRaw(txt); } One RichEdit::UndoBegSelFix::GetRedo(const RichText& txt) { return MakeOne(); } void RichEdit::UndoBegSelUnFix::Apply(RichText& text) { BegSelFixRaw(text); } One RichEdit::UndoBegSelUnFix::GetRedo(const RichText& txt) { return MakeOne(); } bool RichEdit::BegSelTabFix(int& count) { if(begtabsel) { // If selection starts with first table which is the first element in the text int c = cursor; AddUndo(MakeOne()); BegSelFixRaw(text); // adds an empty paragraph at the start Move(0); Move(c + 1, true); // and changes the selection count++; begtabsel = false; return true; } return false; } void RichEdit::BegSelTabFixEnd(bool fix) { // removes empty paragraph added by BegSelTabFix if(fix && GetLength() > 0) { int c = cursor; AddUndo(MakeOne()); BegSelUnFixRaw(text); Move(0); Move(c - 1, true); begtabsel = true; } } bool RichEdit::InvalidRange(int l, int h) { return !InSameTxt(text.GetRichPos(min(l, h)), text.GetRichPos(max(l, h))); } void RichEdit::AddUndo(One&& ur) { redo.Clear(); SetModify(); modified = true; incundoserial = true; while(undo.GetCount() > undosteps) undo.DropHead(); found = false; ur->cursor = cursor; ur->serial = undoserial; undo.AddTail(pick(ur)); } void RichEdit::SaveStylesUndo() { AddUndo(MakeOne(text)); } void RichEdit::SaveStyleUndo(const Uuid& id) { AddUndo(MakeOne(text, id)); } void RichEdit::SaveFormat(int pos, int count) { Limit(pos, count); AddUndo(MakeOne(text, pos, count)); } void RichEdit::SaveFormat() { int pos, count; if(IsSelection()) { if(tablesel) { SaveTable(tablesel); return; } pos = min(cursor, anchor); count = abs(cursor - anchor); } else { pos = cursor; count = 0; } bool b = BegSelTabFix(count); SaveFormat(pos, count); BegSelTabFixEnd(b); } void RichEdit::Limit(int& pos, int& count) { int h = pos + count; pos = min(GetLength(), pos); count = min(GetLength(), h) - pos; } void RichEdit::ModifyFormat(int pos, const RichText::FormatInfo& fi, int count) { if(IsReadOnly()) return; bool b = BegSelTabFix(count); Limit(pos, count); SaveFormat(pos, count); text.ApplyFormatInfo(pos, fi, count); BegSelTabFixEnd(b); } void RichEdit::Remove(int pos, int len, bool forward) { if(IsReadOnly()) return; Limit(pos, len); if(InvalidRange(pos, pos + len)) return; RichTxt::FormatInfo fi; if(forward) fi = text.GetFormatInfo(pos, 0); AddUndo(MakeOne(text, pos, len)); text.Remove(pos, len); if(forward) { SaveFormat(pos, 0); text.ReplaceStyle(pos, fi.styleid); fi.paravalid &= ~RichText::STYLE; text.ApplyFormatInfo(pos, fi, 0); } SetModify(); modified = true; } void RichEdit::Insert(int pos, const RichText& txt, bool typing) { if(IsReadOnly()) return; Index lng; for(int i = 0; i < language.GetCount(); i++) lng.Add(language.GetKey(i)); Vector lngn = txt.GetAllLanguages(); for(int i = 0; i < lngn.GetCount(); i++) lng.FindAdd(lngn[i]); SetupLanguage(lng.PickKeys()); int l = text.GetLength(); text.Insert(pos, txt); l = text.GetLength() - l; SetModify(); modified = true; if(undo.GetCount()) { UndoRec& u = undo.Tail(); if(typing) { UndoInsert *ui = dynamic_cast(&u); if(ui && ui->length > 0 && ui->typing && ui->pos + ui->length == pos) { ui->length += l; return; } } } AddUndo(MakeOne(pos, l, typing)); } void RichEdit::Undo() { if(IsReadOnly()) return; if(undo.IsEmpty()) return; CancelSelection(); int serial = undo.Tail().serial; int c = cursor; while(undo.GetCount()) { UndoRec& u = undo.Tail(); if(u.serial != serial) break; One r = u.GetRedo(text); r->serial = u.serial; r->cursor = cursor; redo.Add(pick(r)); u.Apply(text); c = u.cursor; undo.DropTail(); modified = true; } ReadStyles(); Move(c); } void RichEdit::Redo() { if(IsReadOnly()) return; if(redo.IsEmpty()) return; NextUndo(); CancelSelection(); int serial = redo.Top().serial; int c = cursor; while(redo.GetCount()) { UndoRec& r = redo.Top(); if(r.serial != serial) break; One u = r.GetRedo(text); u->serial = r.serial; u->cursor = cursor; undo.AddTail(pick(u)); r.Apply(text); c = r.cursor; redo.Drop(); modified = true; } ReadStyles(); Move(c); } #ifdef PLATFORM_WIN32 #define RTFS "Rich Text Format" #else #define RTFS "text/richtext" #endif RichText RichEdit::GetSelection(int maxcount) const { RichText clip; if(tablesel) { RichTable tab = text.CopyTable(tablesel, cells); clip.SetStyles(text.GetStyles()); clip.CatPick(pick(tab)); } else { if(begtabsel) { int pos = 0; RichPos p = text.GetRichPos(0, 1); if(p.table) { clip.SetStyles(text.GetStyles()); do { RichTable tab = text.CopyTable(p.table); clip.CatPick(pick(tab)); pos += p.tablen + 1; p = text.GetRichPos(pos, 1); } while(p.table); clip.CatPick(text.Copy(pos, minmax(abs(cursor - pos), 0, maxcount))); } } else clip = text.Copy(min(cursor, anchor), min(maxcount, abs(cursor - anchor))); } return clip; } void RichEdit::Cut() { if(IsReadOnly()) return; Copy(); if(IsSelection()) RemoveSelection(); else if(objectpos >= 0) { Remove(cursor, 1); Move(cursor, false); } } void RichEdit::PasteText(const RichText& text) { SetModify(); modified = true; RemoveSelection(); int n = text.GetPartCount() - 1; if(!text.IsPara(0) || !text.IsPara(n)) { // inserted section must start/end with para RichText pp = clone(text); pp.Normalize(); Insert(cursor, pp, false); ReadStyles(); Move(cursor + pp.GetLength(), false); } else { Insert(cursor, text, false); ReadStyles(); Move(cursor + text.GetLength(), false); } } struct ToParaIterator : RichText::Iterator { RichPara para; bool space; virtual bool operator()(int pos, const RichPara& p) { for(int i = 0; i < p.GetCount(); i++) { const RichPara::Part& part = p[i]; if(part.IsText()) { const wchar *s = part.text; while(*s) { while(*s && *s <= ' ') { space = true; s++; } const wchar *t = s; while(*s > ' ') s++; if(s > t) { if(space) para.Cat(" ", part.format); para.Cat(WString(t, s), part.format); space = false; } } } else if(!part.field.IsNull()) { para.Cat(part.field, part.fieldparam, part.format); space = false; } else if(part.object) { para.Cat(part.object, part.format); space = false; } } space = true; return false; } ToParaIterator() { space = false; } }; void RichEdit::ToPara() { if(IsReadOnly()) return; if(!IsSelection() || tablesel) return; NextUndo(); RichText txt = text.Copy(min(cursor, anchor), abs(cursor - anchor)); ToParaIterator it; txt.Iterate(it); RichText h; h.SetStyles(txt.GetStyles()); h.Cat(it.para); PasteText(h); } void RichEdit::RemoveText(int count) { CancelSelection(); Remove(cursor, count); Finish(); } RichText RichEdit::CopyText(int pos, int count) const { return text.Copy(pos, count); } void RichEdit::InsertObject(int type) { RichObjectType& richtype = RichObject::GetType(type); RichObject object = RichObject(&richtype, Value()); RichObject o = object; o.DefaultAction(context); if(o.GetSerialId() != object.GetSerialId()) { RichText::FormatInfo finfo = GetFormatInfo(); RemoveSelection(); RichPara p; p.Cat(o, finfo); RichText clip; clip.Cat(p); Insert(GetCursor(), clip, false); Finish(); } } void RichEdit::ReplaceObject(const RichObject& obj) { Remove(objectpos, 1); RichPara p; p.Cat(obj, formatinfo); RichText clip; clip.Cat(p); Insert(objectpos, clip, false); Finish(); objectrect = GetObjectRect(objectpos); } RichObject RichEdit::GetObject() const { return text.GetRichPos(objectpos).object; } void RichEdit::Select(int pos, int count) { found = false; Move(pos); Move(pos + count, true); } void RichEdit::InsertLine() { if(IsReadOnly()) return; formatinfo.link.Clear(); RichText::FormatInfo b = formatinfo; RichText h; h.SetStyles(text.GetStyles()); RichPara p; p.format = formatinfo; h.Cat(p); h.Cat(p); bool st = cursorp.paralen == cursorp.posinpara; bool f = cursorp.posinpara == 0 && formatinfo.label.GetCount(); Insert(cursor, h, false); if(f) { String lbl = formatinfo.label; formatinfo.label.Clear(); ApplyFormat(0, RichText::LABEL); formatinfo.label = lbl; } anchor = cursor = cursor + 1; gx = 0; begtabsel = false; formatinfo.firstonpage = formatinfo.newpage = formatinfo.newhdrftr = false; if(st) { Uuid next = text.GetStyle(b.styleid).next; if(next != formatinfo.styleid) { formatinfo.label.Clear(); formatinfo.styleid = next; ApplyFormat(0, RichText::STYLE|RichText::NEWPAGE|RichText::LABEL|RichText::NEWHDRFTR); return; } } ApplyFormat(RichText::LINK, RichText::NEWPAGE|RichText::LABEL|RichText::NEWHDRFTR); objectpos = -1; } }