mirror of
https://github.com/ultimatepp/ultimatepp.git
synced 2026-05-16 06:05:58 -06:00
503 lines
10 KiB
C++
503 lines
10 KiB
C++
#include "RichText.h"
|
|
|
|
NAMESPACE_UPP
|
|
|
|
void RichTxt::Para::Invalidate()
|
|
{
|
|
INTERLOCKED {
|
|
static int64 ss;
|
|
updateserial = ++ss;
|
|
dirty.Invalidate();
|
|
ccx = -1;
|
|
}
|
|
}
|
|
|
|
RichTxt::Para::Para(const Para& src, int)
|
|
: object(src.object, 1)
|
|
{
|
|
length = src.length;
|
|
styleid = src.styleid;
|
|
content = src.content;
|
|
haspos = src.haspos;
|
|
if(src.number)
|
|
number = new RichPara::NumberFormat(*src.number);
|
|
Invalidate();
|
|
checked = false;
|
|
}
|
|
|
|
RichTxt::Part::Part(const Part& src, int) {
|
|
if(src.Is<Para>())
|
|
Create<Para>() <<= src.Get<Para>();
|
|
else
|
|
Create<RichTable>() <<= src.Get<RichTable>();
|
|
}
|
|
|
|
RichTxt::Part::Part() {}
|
|
|
|
void RichTxt::Invalidate()
|
|
{
|
|
length = -1;
|
|
tabcount = -1;
|
|
py.Clear();
|
|
}
|
|
|
|
int RichTxt::GetPartLength(int pi) const
|
|
{
|
|
return part[pi].Is<RichTable>() ? part[pi].Get<RichTable>().GetLength() : part[pi].Get<Para>().length;
|
|
}
|
|
|
|
bool RichTxt::IsTable(int i) const { return part[i].Is<RichTable>(); }
|
|
const RichTable& RichTxt::GetTable(int i) const { return part[i].Get<RichTable>(); }
|
|
|
|
|
|
int RichTxt::FindPart(int& pos) const
|
|
{
|
|
int pi = 0;
|
|
while(pi < part.GetCount()) {
|
|
int l = GetPartLength(pi) + 1;
|
|
if(pos < l)
|
|
break;
|
|
pos -= l;
|
|
pi++;
|
|
}
|
|
if(pi >= part.GetCount())
|
|
pos = 0;
|
|
return pi;
|
|
}
|
|
|
|
int RichTxt::GetPartPos(int pi) const
|
|
{
|
|
int pos = 0;
|
|
for(int i = 0; i < pi; i++)
|
|
pos += GetPartLength(i) + 1;
|
|
return pos;
|
|
}
|
|
|
|
void RichTxt::SetRefresh(int parti)
|
|
{
|
|
switch(r_type) {
|
|
case NONE:
|
|
r_parti = parti;
|
|
r_type = PARA;
|
|
if(IsPara(parti)) {
|
|
Para& pp = part[parti].Get<Para>();
|
|
if(pp.ccx >= 0) {
|
|
r_paraocx = pp.ccx;
|
|
r_paraocy = Sum(pp.linecy, 0) + pp.before + pp.after;
|
|
r_keep = pp.keep;
|
|
r_keepnext = pp.keepnext;
|
|
r_newpage = pp.newpage;
|
|
break;
|
|
}
|
|
else
|
|
r_type = FROM;
|
|
}
|
|
break;
|
|
case PARA:
|
|
if(parti == r_parti) break;
|
|
case FROM:
|
|
r_parti = min(parti, r_parti);
|
|
r_type = FROM;
|
|
break;
|
|
}
|
|
}
|
|
|
|
void RichTxt::SetRefreshFrom(int parti)
|
|
{
|
|
r_type = FROM;
|
|
if(r_type == NONE)
|
|
r_parti = parti;
|
|
else
|
|
r_parti = min(parti, r_parti);
|
|
}
|
|
|
|
void RichTxt::Put(int i, const RichPara& p, const RichStyle& s)
|
|
{
|
|
if(i >= part.GetCount() || !IsPara(i))
|
|
part.At(i).Create<Para>();
|
|
Para& pp = part[i].Get<Para>();
|
|
int numbering = p.format.GetNumberLevel();
|
|
if(pp.numbering != numbering)
|
|
SetRefreshFrom(i);
|
|
else
|
|
SetRefresh(i);
|
|
pp.number.Clear();
|
|
pp.content = p.Pack(s.format, pp.object);
|
|
pp.Invalidate();
|
|
pp.checked = false;
|
|
pp.styleid = p.format.styleid;
|
|
pp.length = p.GetLength();
|
|
pp.numbering = numbering;
|
|
pp.spellerrors.Clear();
|
|
pp.haspos = p.HasPos();
|
|
if(numbering >= 0 || p.format.reset_number) {
|
|
pp.number = new RichPara::NumberFormat;
|
|
*pp.number = p.format;
|
|
}
|
|
}
|
|
|
|
void RichTxt::Put(int i, const RichPara& p, const RichStyles& s)
|
|
{
|
|
Put(i, p, GetStyle(s, p.format.styleid));
|
|
}
|
|
|
|
void RichTxt::Set(int i, const RichPara& p, const RichStyles& s)
|
|
{
|
|
Put(i, p, s);
|
|
Invalidate();
|
|
}
|
|
|
|
void RichTxt::Insert(int i, const RichPara& p, const RichStyles& s)
|
|
{
|
|
part.Insert(i);
|
|
Set(i, p, s);
|
|
}
|
|
|
|
void RichTxt::RemovePart(int parti)
|
|
{
|
|
part.Remove(parti);
|
|
Invalidate();
|
|
}
|
|
|
|
void RichTxt::SetPick(int i, pick_ RichTable& p)
|
|
{
|
|
const_cast<RichTable&>(p).Normalize();
|
|
part.At(i).Create<RichTable>() = p;
|
|
Invalidate();
|
|
SetRefresh(i);
|
|
}
|
|
|
|
RichPara RichTxt::Get(int parai, const RichStyle& style, bool usecache) const
|
|
{
|
|
ASSERT(part[parai].Is<Para>());
|
|
const Para& pp = part[parai].Get<Para>();
|
|
RichPara p;
|
|
if(usecache)
|
|
p.CacheId(pp.updateserial);
|
|
p.Unpack(pp.content, pp.object, style.format);
|
|
return p;
|
|
}
|
|
|
|
RichPara RichTxt::Get(int parai, const Uuid& styleid, const RichStyles& s, bool usecache) const
|
|
{
|
|
RichPara p = Get(parai, GetStyle(s, styleid), usecache);
|
|
p.format.styleid = styleid;
|
|
return p;
|
|
}
|
|
|
|
RichPara RichTxt::Get(int parti, const RichStyles& s, bool usecache) const
|
|
{
|
|
return Get(parti, part[parti].Get<Para>().styleid, s, usecache);
|
|
}
|
|
|
|
RichPara RichTxt::Get(int parai, const RichStyle& style) const
|
|
{
|
|
return Get(parai, style, false);
|
|
}
|
|
|
|
RichPara RichTxt::Get(int parai, const Uuid& styleid, const RichStyles& s) const
|
|
{
|
|
return Get(parai, styleid, s, false);
|
|
}
|
|
|
|
RichPara RichTxt::Get(int i, const RichStyles& s) const
|
|
{
|
|
return Get(i, s, false);
|
|
}
|
|
|
|
bool RichTxt::IsEmpty() const
|
|
{
|
|
return part.IsEmpty() || part.GetCount() == 1 && ComputeLength() == 0;
|
|
}
|
|
|
|
int RichTxt::ComputeLength() const
|
|
{
|
|
int length = part.GetCount() - 1;
|
|
for(int i = 0; i < part.GetCount(); i++)
|
|
length += GetPartLength(i);
|
|
return length;
|
|
}
|
|
|
|
int RichTxt::GetLength() const
|
|
{
|
|
if(ReadWithBarrier(length) < 0)
|
|
BarrierWrite(length, ComputeLength());
|
|
return length;
|
|
}
|
|
|
|
int RichTxt::GetTableCount() const
|
|
{
|
|
if(ReadWithBarrier(tabcount) < 0) {
|
|
int n = 0;
|
|
for(int i = 0; i < part.GetCount(); i++)
|
|
if(IsTable(i))
|
|
n += GetTable(i).GetTableCount() + 1;
|
|
BarrierWrite(tabcount, n);
|
|
}
|
|
return tabcount;
|
|
}
|
|
|
|
bool RichTxt::EvaluateFields(const RichStyles& s, VectorMap<String, Value>& vars)
|
|
{
|
|
bool b = false;
|
|
for(int i = 0; i < part.GetCount(); i++)
|
|
if(IsTable(i)) {
|
|
RichTable& tab = part[i].Get<RichTable>();
|
|
for(int i = 0; i < tab.GetRows(); i++)
|
|
for(int j = 0; j < tab.GetColumns(); j++)
|
|
if(tab(i, j) && tab[i][j].text.EvaluateFields(s, vars)) {
|
|
tab.InvalidateRefresh(i, j);
|
|
b = true;
|
|
}
|
|
}
|
|
else {
|
|
RichPara p = Get(i, s);
|
|
if(p.EvaluateFields(vars)) {
|
|
b = true;
|
|
Set(i, p, s);
|
|
}
|
|
}
|
|
return b;
|
|
}
|
|
|
|
void RichTxt::ClearSpelling()
|
|
{
|
|
for(int i = 0; i < part.GetCount(); i++) {
|
|
if(IsTable(i)) {
|
|
RichTable& tab = part[i].Get<RichTable>();
|
|
for(int i = 0; i < tab.GetRows(); i++)
|
|
for(int j = 0; j < tab.GetColumns(); j++)
|
|
if(tab(i, j)) {
|
|
tab[i][j].text.ClearSpelling();
|
|
tab.InvalidateRefresh(i, j);
|
|
}
|
|
}
|
|
else {
|
|
Para& p = part[i].Get<Para>();
|
|
p.spellerrors.Clear();
|
|
p.checked = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
void RichTxt::SetParaStyle(int i, const Uuid& id)
|
|
{
|
|
ASSERT(IsPara(i));
|
|
Para& p = part[i].Get<Para>();
|
|
p.styleid = id;
|
|
p.Invalidate();
|
|
SetRefreshFrom(i);
|
|
}
|
|
|
|
void RichTxt::GetRichPos(int pos, RichPos& rp, int ti, int maxlevel, const RichStyles& st) const
|
|
{
|
|
int p = pos;
|
|
int pti = ti;
|
|
for(int i = 0; i < part.GetCount(); i++) {
|
|
int l = GetPartLength(i) + 1;
|
|
if(pos < l) {
|
|
if(IsTable(i)) {
|
|
const RichTable& tab = GetTable(i);
|
|
rp.level++;
|
|
rp.tabtextparti = i;
|
|
rp.tabtextpartcount = part.GetCount();
|
|
rp.tabtextlen = GetLength();
|
|
rp.tabposintabtext = p - pos;
|
|
rp.posintab = pos;
|
|
rp.cell = tab.FindCell(pos);
|
|
rp.posincell = pos;
|
|
rp.tabsize = tab.GetSize();
|
|
rp.tablen = tab.GetLength();
|
|
const RichTxt& ct = tab[rp.cell].text;
|
|
rp.celllen = ct.GetLength();
|
|
rp.parenttab = pti;
|
|
rp.table = ti + 1;
|
|
if(rp.level < maxlevel)
|
|
ct.GetRichPos(pos, rp, ti + 1 + tab.GetTableCount(rp.cell), maxlevel, st);
|
|
return;
|
|
}
|
|
else {
|
|
rp.posinpara = pos;
|
|
rp.parai = i;
|
|
rp.partcount = part.GetCount();
|
|
rp.paralen = l - 1;
|
|
Get(i, st, true).GetRichPos(rp, pos);
|
|
return;
|
|
}
|
|
}
|
|
pos -= l;
|
|
if(IsTable(i))
|
|
ti += 1 + GetTable(i).GetTableCount();
|
|
}
|
|
rp.parai = part.GetCount();
|
|
rp.posinpara = 0;
|
|
rp.partcount = part.GetCount();
|
|
rp.paralen = 0;
|
|
}
|
|
|
|
RichPara::Format RichTxt::GetFirstFormat(const RichStyles& style) const
|
|
{
|
|
if(part.IsEmpty())
|
|
return RichPara::Format();
|
|
if(IsTable(0)) {
|
|
const RichTable& tab = GetTable(0);
|
|
return tab[0][0].text.GetFirstFormat(style);
|
|
}
|
|
RichPos rp;
|
|
Get(0, style).GetRichPos(rp, 0);
|
|
return rp.format;
|
|
}
|
|
|
|
RichCellPos RichTxt::GetCellPos(int table, int row, int column) const
|
|
{
|
|
int pos = 0;
|
|
for(int i = 0;; i++) {
|
|
if(IsTable(i)) {
|
|
table--;
|
|
const RichTable& tab = part[i].Get<RichTable>();
|
|
if(table <= tab.GetTableCount()) {
|
|
if(table == 0) {
|
|
RichCellPos p;
|
|
p.tabsize = tab.GetSize();
|
|
p.tablen = tab.GetLength();
|
|
p.cellpos = tab.GetCellPos(row, column);
|
|
p.pos = p.tabpos = pos;
|
|
p.pos += p.cellpos;
|
|
p.textlen = GetLength();
|
|
p.level = 1;
|
|
return p;
|
|
}
|
|
for(int i = 0; i < tab.GetRows(); i++)
|
|
for(int j = 0; j < tab.GetColumns(); j++)
|
|
if(tab(i, j)) {
|
|
const RichTxt& txt = tab[i][j].text;
|
|
if(table <= txt.GetTableCount()) {
|
|
RichCellPos p = txt.GetCellPos(table, row, column);
|
|
p.pos += pos + tab.GetCellPos(i, j);
|
|
p.level++;
|
|
return p;
|
|
}
|
|
table -= txt.GetTableCount();
|
|
}
|
|
NEVER();
|
|
}
|
|
else
|
|
table -= tab.GetTableCount();
|
|
}
|
|
pos += GetPartLength(i) + 1;
|
|
}
|
|
NEVER();
|
|
return RichCellPos();
|
|
}
|
|
|
|
RichCellPos RichTxt::GetCellPos(int table, Point p) const
|
|
{
|
|
return GetCellPos(table, p.y, p.x);
|
|
}
|
|
|
|
bool RichTxt::Iterate(UpdateIterator& r, int gpos, const RichStyles& s)
|
|
{
|
|
for(int pi = 0; pi < part.GetCount(); pi++)
|
|
if(IsTable(pi)) {
|
|
RichTable& tab = part[pi].Get<RichTable>();
|
|
for(int i = 0; i < tab.GetRows(); i++)
|
|
for(int j = 0; j < tab.GetColumns(); j++)
|
|
if(tab(i, j)) {
|
|
if(tab[i][j].text.Iterate(r, gpos, s))
|
|
return true;
|
|
gpos += tab[i][j].text.GetLength() + 1;
|
|
}
|
|
}
|
|
else {
|
|
RichPara p = Get(pi, s);
|
|
int q = r(gpos, p);
|
|
if(q & UpdateIterator::UPDATE)
|
|
Set(pi, p, s);
|
|
if(q & UpdateIterator::STOP)
|
|
return true;
|
|
gpos += GetPartLength(pi) + 1;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
struct sIter__ : RichTxt::UpdateIterator {
|
|
RichTxt::Iterator *iter;
|
|
virtual int operator()(int pos, RichPara& para) {
|
|
return iter->operator()(pos, para) ? STOP : CONTINUE;
|
|
}
|
|
};
|
|
|
|
bool RichTxt::Iterate(Iterator& r, int gpos, const RichStyles& s) const
|
|
{
|
|
sIter__ it;
|
|
it.iter = &r;
|
|
return const_cast<RichTxt *>(this)->Iterate(it, gpos, s);
|
|
}
|
|
|
|
void RichTxt::Init()
|
|
{
|
|
r_type = ALL;
|
|
r_parti = 0;
|
|
r_paraocx = r_paraocy = -1;
|
|
tabcount = length = 0;
|
|
}
|
|
|
|
void RichTxt::Clear()
|
|
{
|
|
part.Clear();
|
|
Init();
|
|
}
|
|
|
|
RichTxt::RichTxt()
|
|
{
|
|
Init();
|
|
}
|
|
|
|
RichTxt::RichTxt(const RichTxt& src, int)
|
|
{
|
|
Init();
|
|
part <<= src.part;
|
|
length = src.length;
|
|
tabcount = src.tabcount;
|
|
py.Clear();
|
|
}
|
|
|
|
#ifdef _DEBUG
|
|
|
|
#define DMP(x) s << #x << "=" << x << ", "
|
|
|
|
String RichPos::ToString() const
|
|
{
|
|
String s;
|
|
DMP(tabtextparti);
|
|
DMP(tabtextpartcount);
|
|
DMP(tabposintabtext);
|
|
DMP(tabtextlen);
|
|
DMP(table);
|
|
DMP(tabsize);
|
|
DMP(cell);
|
|
DMP(tablen);
|
|
DMP(posintab);
|
|
DMP(celllen);
|
|
DMP(posincell);
|
|
DMP(parai);
|
|
DMP(partcount);
|
|
DMP(posinpara);
|
|
DMP(paralen);
|
|
DMP(level);
|
|
DMP(parenttab);
|
|
s << "char: " << (char)chr;
|
|
return s;
|
|
}
|
|
|
|
String RichCellPos::ToString() const
|
|
{
|
|
String s;
|
|
s << "pos: " << pos << ", textlen: " << textlen << ", size: " << tabsize << ", tabpos: " << tabpos
|
|
<< ", tablen: " << tablen << ", cellpos: " << cellpos << ", celllen: " << celllen << ", level: " << level;
|
|
return s;
|
|
}
|
|
|
|
#endif
|
|
|
|
END_UPP_NAMESPACE
|