mirror of
https://github.com/ultimatepp/ultimatepp.git
synced 2026-05-15 14:16:07 -06:00
1238 lines
25 KiB
C++
1238 lines
25 KiB
C++
#include <CtrlLib/CtrlLib.h>
|
|
|
|
#define LTIMING(x) // RTIMING(x)
|
|
|
|
namespace Upp {
|
|
|
|
TextCtrl::TextCtrl()
|
|
{
|
|
Unicode();
|
|
undosteps = 1000;
|
|
Clear();
|
|
undoserial = 0;
|
|
incundoserial = false;
|
|
undo_op = false;
|
|
WhenBar = THISBACK(StdBar);
|
|
charset = CHARSET_UTF8;
|
|
color[INK_NORMAL] = SColorText;
|
|
color[INK_DISABLED] = SColorDisabled;
|
|
color[INK_SELECTED] = SColorHighlightText;
|
|
color[PAPER_NORMAL] = SColorPaper;
|
|
color[PAPER_READONLY] = SColorFace;
|
|
color[PAPER_SELECTED] = SColorHighlight;
|
|
color[WHITESPACE] = Blend(SColorLight, SColorHighlight);
|
|
color[WARN_WHITESPACE] = Blend(SColorLight, SRed);
|
|
|
|
processtab = true;
|
|
processenter = true;
|
|
nobg = false;
|
|
rectsel = false;
|
|
#ifdef CPU_64
|
|
max_total = 2047 * 1024 * 1024;
|
|
#else
|
|
#ifdef _DEBUG
|
|
max_total = 100 * 1024 * 1024;
|
|
#else
|
|
max_total = 200 * 1024 * 1024;
|
|
#endif
|
|
#endif
|
|
max_line_len = 100000;
|
|
truncated = false;
|
|
}
|
|
|
|
TextCtrl::~TextCtrl() {}
|
|
|
|
void TextCtrl::MiddleDown(Point p, dword flags)
|
|
{
|
|
if(IsReadOnly())
|
|
return;
|
|
if(AcceptText(Selection())) {
|
|
WString w = GetWString(Selection());
|
|
selclick = false;
|
|
LeftDown(p, flags);
|
|
Paste(w);
|
|
Action();
|
|
}
|
|
}
|
|
|
|
void TextCtrl::CancelMode()
|
|
{
|
|
selclick = false;
|
|
column_typing = false;
|
|
dropcaret = Null;
|
|
isdrag = false;
|
|
}
|
|
|
|
void TextCtrl::Clear()
|
|
{
|
|
GuiLock __;
|
|
view = NULL;
|
|
viewlines = 0;
|
|
cline = 0;
|
|
cpos = 0;
|
|
total = 0;
|
|
truncated = false;
|
|
lin.Clear();
|
|
ClearLines();
|
|
lin.Add();
|
|
InsertLines(0, 1);
|
|
DirtyFrom(0);
|
|
undo.Clear();
|
|
redo.Clear();
|
|
ClearDirty();
|
|
anchor = -1;
|
|
cursor = 0;
|
|
SetSb();
|
|
PlaceCaret(0);
|
|
SelectionChanged();
|
|
Refresh();
|
|
}
|
|
|
|
void TextCtrl::DirtyFrom(int line) {}
|
|
void TextCtrl::SelectionChanged() {}
|
|
void TextCtrl::ClearLines() {}
|
|
void TextCtrl::InsertLines(int line, int count) {}
|
|
void TextCtrl::RemoveLines(int line, int count) {}
|
|
void TextCtrl::PreInsert(int pos, const WString& text) {}
|
|
void TextCtrl::PostInsert(int pos, const WString& text) {}
|
|
void TextCtrl::PreRemove(int pos, int size) {}
|
|
void TextCtrl::PostRemove(int pos, int size) {}
|
|
void TextCtrl::RefreshLine(int i) {}
|
|
void TextCtrl::InvalidateLine(int i) {}
|
|
void TextCtrl::SetSb() {}
|
|
void TextCtrl::PlaceCaret(int64 newcursor, bool sel) {}
|
|
|
|
int TextCtrl::RemoveRectSelection() { return 0; }
|
|
WString TextCtrl::CopyRectSelection() { return Null; }
|
|
int TextCtrl::PasteRectSelection(const WString& s) { return 0; }
|
|
|
|
void TextCtrl::CachePos(int64 pos)
|
|
{
|
|
GuiLock __;
|
|
int64 p = pos;
|
|
cline = GetLinePos64(p);
|
|
cpos = pos - p;
|
|
}
|
|
|
|
void TextCtrl::CacheLinePos(int linei)
|
|
{
|
|
GuiLock __;
|
|
if(linei >= 0 && linei < GetLineCount()) {
|
|
cpos = GetPos64(linei);
|
|
cline = linei;
|
|
}
|
|
}
|
|
|
|
bool TextCtrl::IsUnicodeCharset(byte charset)
|
|
{
|
|
return findarg(charset, CHARSET_UTF8, CHARSET_UTF8_BOM, CHARSET_UTF16_LE, CHARSET_UTF16_BE,
|
|
CHARSET_UTF16_LE_BOM, CHARSET_UTF16_BE_BOM) >= 0;
|
|
}
|
|
|
|
int TextCtrl::Load0(Stream& in, byte charset_, bool view) {
|
|
GuiLock __;
|
|
Clear();
|
|
lin.Clear();
|
|
ClearLines();
|
|
total = 0;
|
|
SetCharset(charset_);
|
|
truncated = false;
|
|
viewlines = 0;
|
|
this->view = NULL;
|
|
view_all = false;
|
|
offset256.Clear();
|
|
total256.Clear();
|
|
view_cache[0].blk = view_cache[1].blk = -1;
|
|
if(view) {
|
|
this->view = ∈
|
|
SetReadOnly();
|
|
}
|
|
if(charset == CHARSET_UTF8_BOM && in.GetLeft() >= 3) {
|
|
int64 pos = in.GetPos();
|
|
byte h[3];
|
|
if(!(in.Get(h, 3) == 3 && h[0] == 0xEF && h[1] == 0xBB && h[2] == 0xBF))
|
|
in.Seek(pos);
|
|
charset = CHARSET_UTF8;
|
|
}
|
|
int be16 = findarg(charset, CHARSET_UTF16_LE_BOM, CHARSET_UTF16_BE_BOM);
|
|
if(be16 >= 0 && in.GetLeft() >= 2) {
|
|
int64 pos = in.GetPos();
|
|
dword h = in.Get16le();
|
|
if(h != (be16 ? 0xfffe : 0xfeff))
|
|
in.Seek(pos);
|
|
charset = be16 ? CHARSET_UTF16_BE : CHARSET_UTF16_LE;
|
|
}
|
|
|
|
if(view) {
|
|
view_loading_pos = in.GetPos();
|
|
view_loading_lock = 0;
|
|
ViewLoading();
|
|
PlaceCaret(0);
|
|
return 0;
|
|
}
|
|
|
|
int m = LoadLines(lin, INT_MAX, total, in, charset, max_line_len, max_total, truncated);
|
|
|
|
InsertLines(0, lin.GetCount());
|
|
Update();
|
|
SetSb();
|
|
PlaceCaret(0);
|
|
return m;
|
|
}
|
|
|
|
int TextCtrl::LoadLines(Vector<Ln>& ls, int n, int64& total, Stream& in, byte charset,
|
|
int max_line_len, int max_total, bool& truncated,
|
|
int *view_line_count) const
|
|
{
|
|
StringBuffer ln;
|
|
bool cr = false;
|
|
byte b8 = 0;
|
|
auto line_count = [&] { return view_line_count ? *view_line_count : ls.GetCount(); };
|
|
if(charset == CHARSET_UTF16_LE || charset == CHARSET_UTF16_BE) {
|
|
WStringBuffer wln;
|
|
auto put_wln = [&]() {
|
|
if(view_line_count)
|
|
(*view_line_count)++;
|
|
else {
|
|
Ln& ln = ls.Add();
|
|
ln.len = wln.GetCount();
|
|
ln.text = ToUtf8(~wln, ln.len);
|
|
}
|
|
};
|
|
for(;;) {
|
|
int c = charset == CHARSET_UTF16_LE ? in.Get16le() : in.Get16be();
|
|
if(c < 0) {
|
|
total += wln.GetCount();
|
|
put_wln();
|
|
goto finish;
|
|
}
|
|
if(c == '\r')
|
|
cr = true;
|
|
else
|
|
if(c == '\n') {
|
|
truncate_line:
|
|
total += wln.GetCount() + 1;
|
|
put_wln();
|
|
if(line_count() >= n)
|
|
goto finish;
|
|
wln.Clear();
|
|
}
|
|
else {
|
|
wln.Cat(c);
|
|
if(wln.GetCount() >= max_line_len)
|
|
goto truncate_line;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
for(;;) {
|
|
byte h[200];
|
|
int size;
|
|
int64 pos = in.GetPos();
|
|
const byte *s = in.GetSzPtr(size);
|
|
if(size == 0) {
|
|
size = in.Get(h, 200);
|
|
s = h;
|
|
if(size == 0)
|
|
break;
|
|
}
|
|
const byte *posptr = s;
|
|
const byte *e = s + size;
|
|
while(s < e) {
|
|
const byte *b = s;
|
|
const byte *ee = s + min(size_t(e - s), size_t(max_line_len - ln.GetCount()));
|
|
{
|
|
while(s < ee && *s != '\r' && *s != '\n') {
|
|
b8 |= *s++;
|
|
while(s < ee && *s >= ' ' && *s < 128) // Interestingly, this speeds things up
|
|
s++;
|
|
while(s < ee && *s >= ' ')
|
|
b8 |= *s++;
|
|
}
|
|
}
|
|
if(b < s) {
|
|
if(s - b + ln.GetCount() > max_total)
|
|
ln.Cat((const char *)b, max_total - ln.GetCount());
|
|
else
|
|
ln.Cat((const char *)b, (const char *)s);
|
|
}
|
|
auto put_ln = [&]() -> bool {
|
|
if(view_line_count) {
|
|
(*view_line_count)++;
|
|
total += (charset == CHARSET_UTF8 && (b8 & 0x80) ? Utf32Len(~ln, ln.GetCount())
|
|
: ln.GetCount()) + 1;
|
|
}
|
|
else {
|
|
Ln& l = ls.Add();
|
|
if(charset == CHARSET_UTF8) {
|
|
l.len = (b8 & 0x80) ? Utf32Len(~ln, ln.GetCount()) : ln.GetCount();
|
|
l.text = ln;
|
|
}
|
|
else {
|
|
l.len = ln.GetCount();
|
|
l.text = ToCharset(CHARSET_UTF8, ln, charset);
|
|
}
|
|
if(total + l.len + 1 > max_total) {
|
|
ls.Drop();
|
|
truncated = true;
|
|
return false;
|
|
}
|
|
total += l.len + 1;
|
|
}
|
|
return true;
|
|
};
|
|
while(ln.GetCount() >= max_line_len) {
|
|
int ei = max_line_len;
|
|
if(charset == CHARSET_UTF8)
|
|
while(ei > 0 && ei > max_line_len - 6 && !((byte)ln[ei] < 128 || IsUtf8Lead((byte)ln[ei]))) // break lse at whole utf8 codepoint if possible
|
|
ei--;
|
|
String nln(~ln + ei, ln.GetCount() - ei);
|
|
ln.SetCount(ei);
|
|
truncated = true;
|
|
if(!put_ln())
|
|
goto out_of_limit;
|
|
if(line_count() >= n) {
|
|
in.Seek(s - posptr + pos);
|
|
goto finish;
|
|
}
|
|
ln = nln;
|
|
}
|
|
if(s < e && *s == '\r') {
|
|
s++;
|
|
cr = true;
|
|
}
|
|
if(s < e && *s == '\n') {
|
|
if(!put_ln())
|
|
goto out_of_limit;
|
|
s++;
|
|
if(line_count() >= n) {
|
|
in.Seek(s - posptr + pos);
|
|
goto finish;
|
|
}
|
|
ln.Clear();
|
|
b8 = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
out_of_limit:
|
|
{
|
|
WString w = ToUnicode(~ln, ln.GetCount(), charset);
|
|
if(total + w.GetLength() <= max_total) {
|
|
if(view_line_count) {
|
|
(*view_line_count)++;
|
|
total += w.GetCount();
|
|
}
|
|
else {
|
|
Ln& ln = ls.Add();
|
|
ln.len = w.GetCount();
|
|
ln.text = ToUtf8(~w, ln.len);
|
|
total += ln.len;
|
|
}
|
|
}
|
|
}
|
|
finish:
|
|
return ls.GetCount() > 1 ? cr ? LE_CRLF : LE_LF : LE_DEFAULT;
|
|
}
|
|
|
|
void TextCtrl::ViewLoading()
|
|
{
|
|
GuiLock __;
|
|
if(view_all || !view)
|
|
return;
|
|
int start = msecs();
|
|
view->Seek(view_loading_pos);
|
|
int lines0 = viewlines;
|
|
for(;;) {
|
|
offset256.Add(view->GetPos());
|
|
Vector<Ln> l;
|
|
bool b;
|
|
int64 t = 0;
|
|
|
|
int line_count = 0;
|
|
LoadLines(l, 256, t, *view, charset, 10000, INT_MAX, b, &line_count);
|
|
viewlines += line_count;
|
|
total += t;
|
|
total256.Add((int)t);
|
|
|
|
#ifdef CPU_32
|
|
enum { MAX_LINES = 128000000 };
|
|
#else
|
|
enum { MAX_LINES = INT_MAX - 512 };
|
|
#endif
|
|
|
|
if(view->IsEof() || viewlines > MAX_LINES) {
|
|
WhenViewMapping(view->GetPos());
|
|
view_all = true;
|
|
break;
|
|
}
|
|
|
|
if(view_loading_lock) {
|
|
view_loading_pos = view->GetPos();
|
|
WhenViewMapping(view_loading_pos);
|
|
break;
|
|
}
|
|
|
|
if(msecs(start) > 20) {
|
|
view_loading_pos = view->GetPos();
|
|
PostCallback([=] { ViewLoading(); });
|
|
WhenViewMapping(view_loading_pos);
|
|
break;
|
|
}
|
|
}
|
|
InsertLines(lines0, viewlines - lines0);
|
|
SetSb();
|
|
Update();
|
|
}
|
|
|
|
void TextCtrl::UnlockViewMapping()
|
|
{
|
|
view_loading_lock--;
|
|
ViewLoading();
|
|
}
|
|
|
|
void TextCtrl::WaitView(int line, bool progress)
|
|
{
|
|
if(view) {
|
|
if(progress) {
|
|
LockViewMapping();
|
|
Progress pi("Scanning the file");
|
|
pi.Delay(1000);
|
|
while(view && !view_all && viewlines < line) {
|
|
if(pi.SetCanceled(int(view_loading_pos >> 10), int(view->GetSize()) >> 10))
|
|
break;
|
|
ViewLoading();
|
|
}
|
|
UnlockViewMapping();
|
|
}
|
|
else
|
|
while(view && !view_all && viewlines <= line)
|
|
ViewLoading();
|
|
}
|
|
}
|
|
|
|
void TextCtrl::SerializeViewMap(Stream& s)
|
|
{
|
|
GuiLock __;
|
|
int version = 0;
|
|
s / version;
|
|
s.Magic(327845692);
|
|
s % view_loading_pos
|
|
% total
|
|
% viewlines
|
|
% view_all
|
|
% total256
|
|
% offset256
|
|
;
|
|
if(s.IsLoading()) {
|
|
SetSb();
|
|
Update();
|
|
Refresh();
|
|
}
|
|
}
|
|
|
|
const TextCtrl::Ln& TextCtrl::GetLn(int i) const
|
|
{
|
|
if(view) {
|
|
GuiLock __;
|
|
int blk = i >> 8;
|
|
if(view_cache[0].blk != blk)
|
|
Swap(view_cache[0], view_cache[1]); // trivial LRU
|
|
if(view_cache[0].blk != blk) {
|
|
Swap(view_cache[0], view_cache[1]); // trivial LRU
|
|
view->Seek(offset256[blk]);
|
|
int64 t = 0;
|
|
bool b;
|
|
view_cache[0].line.Clear();
|
|
view_cache[0].blk = blk;
|
|
LoadLines(view_cache[0].line, 256, t, *view, charset, 10000, INT_MAX, b);
|
|
}
|
|
return view_cache[0].line[i & 255];
|
|
}
|
|
else
|
|
return lin[i];
|
|
}
|
|
|
|
const String& TextCtrl::GetUtf8Line(int i) const
|
|
{
|
|
return GetLn(i).text;
|
|
}
|
|
|
|
int TextCtrl::GetLineLength(int i) const
|
|
{
|
|
return GetLn(i).len;
|
|
}
|
|
|
|
void TextCtrl::Save(Stream& s, byte charset, int line_endings) const {
|
|
if(charset == CHARSET_UTF8_BOM) {
|
|
static byte bom[] = { 0xEF, 0xBB, 0xBF };
|
|
s.Put(bom, 3);
|
|
charset = CHARSET_UTF8;
|
|
}
|
|
if(charset == CHARSET_UTF16_LE_BOM) {
|
|
s.Put16le(0xfeff);
|
|
charset = CHARSET_UTF16_LE;
|
|
}
|
|
if(charset == CHARSET_UTF16_BE_BOM) {
|
|
s.Put16be(0xfeff);
|
|
charset = CHARSET_UTF16_BE;
|
|
}
|
|
charset = ResolveCharset(charset);
|
|
String le = "\n";
|
|
#ifdef PLATFORM_WIN32
|
|
if(line_endings == LE_DEFAULT)
|
|
le = "\r\n";
|
|
#endif
|
|
if(line_endings == LE_CRLF)
|
|
le = "\r\n";
|
|
int be16 = findarg(charset, CHARSET_UTF16_LE, CHARSET_UTF16_BE);
|
|
if(be16 >= 0) {
|
|
String wle;
|
|
for(int i = 0; i < le.GetCount(); i++) {
|
|
if(be16)
|
|
wle.Cat(0);
|
|
wle.Cat(le[i]);
|
|
if(!be16)
|
|
wle.Cat(0);
|
|
}
|
|
for(int i = 0; i < GetLineCount(); i++) {
|
|
if(i)
|
|
s.Put(wle);
|
|
WString txt = GetWLine(i);
|
|
const wchar *e = txt.End();
|
|
if(be16)
|
|
for(const wchar *w = txt; w != e; w++)
|
|
if(*w < 0x10000)
|
|
s.Put16be((word)*w);
|
|
else {
|
|
char16 h[2];
|
|
ToUtf16(h, w, 1);
|
|
s.Put16be(h[0]);
|
|
s.Put16be(h[1]);
|
|
}
|
|
else
|
|
for(const wchar *w = txt; w != e; w++)
|
|
if(*w < 0x10000)
|
|
s.Put16le((word)*w);
|
|
else {
|
|
char16 h[2];
|
|
ToUtf16(h, w, 1);
|
|
s.Put16le(h[0]);
|
|
s.Put16le(h[1]);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
for(int i = 0; i < GetLineCount(); i++) {
|
|
if(i)
|
|
s.Put(le);
|
|
if(charset == CHARSET_UTF8)
|
|
s.Put(GetUtf8Line(i));
|
|
else {
|
|
String txt = FromUnicode(GetWLine(i), charset);
|
|
const char *e = txt.End();
|
|
for(const char *w = txt; w != e; w++)
|
|
s.Put(*w == DEFAULTCHAR ? '?' : *w);
|
|
}
|
|
}
|
|
}
|
|
|
|
void TextCtrl::Set(const String& s, byte charset) {
|
|
StringStream ss(s);
|
|
Load(ss, charset);
|
|
}
|
|
|
|
String TextCtrl::Get(byte charset) const
|
|
{
|
|
StringStream ss;
|
|
Save(ss, charset);
|
|
return ss;
|
|
}
|
|
|
|
int TextCtrl::GetInvalidCharPos(byte charset) const
|
|
{
|
|
int q = 0;
|
|
if(!IsUnicodeCharset(charset))
|
|
for(int i = 0; i < GetLineCount(); i++) {
|
|
WString txt = GetWLine(i);
|
|
WString ctxt = ToUnicode(FromUnicode(txt, charset), charset);
|
|
for(int w = 0; w < txt.GetLength(); w++)
|
|
if(txt[w] != ctxt[w])
|
|
return q + w;
|
|
q += txt.GetLength() + 1;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
void TextCtrl::ClearDirty()
|
|
{
|
|
dirty = 0;
|
|
ClearModify();
|
|
WhenState();
|
|
}
|
|
|
|
TextCtrl::UndoData TextCtrl::PickUndoData()
|
|
{
|
|
UndoData data;
|
|
data.undo = pick(undo);
|
|
data.redo = pick(redo);
|
|
data.undoserial = undoserial;
|
|
return data;
|
|
}
|
|
|
|
void TextCtrl::SetPickUndoData(TextCtrl::UndoData&& data)
|
|
{
|
|
undo = pick(data.undo);
|
|
redo = pick(data.redo);
|
|
undoserial = data.undoserial;
|
|
incundoserial = true;
|
|
}
|
|
|
|
void TextCtrl::Set(const WString& s)
|
|
{
|
|
Clear();
|
|
Insert0(0, s);
|
|
}
|
|
|
|
void TextCtrl::SetData(const Value& v)
|
|
{
|
|
Set((WString)v);
|
|
}
|
|
|
|
Value TextCtrl::GetData() const
|
|
{
|
|
return GetW();
|
|
}
|
|
|
|
String TextCtrl::GetEncodedLine(int i, byte charset) const
|
|
{
|
|
charset = ResolveCharset(charset);
|
|
String h = GetUtf8Line(i);
|
|
return charset == CHARSET_UTF8 ? h : FromUnicode(ToUtf32(h), charset);
|
|
}
|
|
|
|
int TextCtrl::GetLinePos64(int64& pos) const {
|
|
GuiLock __;
|
|
if(pos < cpos && cpos - pos < pos && !view) {
|
|
int i = cline;
|
|
int64 ps = cpos;
|
|
for(;;) {
|
|
ps -= GetLineLength(--i) + 1;
|
|
if(ps <= pos) {
|
|
pos = pos - ps;
|
|
return i;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
int i = 0;
|
|
if(view) {
|
|
GuiLock __;
|
|
int blk = 0;
|
|
for(;;) {
|
|
int n = total256[blk];
|
|
if(pos < n)
|
|
break;
|
|
pos -= n;
|
|
if(++blk >= total256.GetCount()) {
|
|
pos = GetLineLength(GetLineCount() - 1);
|
|
return GetLineCount() - 1;
|
|
}
|
|
}
|
|
i = blk << 8;
|
|
}
|
|
else
|
|
if(pos >= cpos) {
|
|
pos -= cpos;
|
|
i = cline;
|
|
}
|
|
for(;;) {
|
|
int n = GetLineLength(i) + 1;
|
|
if(pos < n) return i;
|
|
pos -= n;
|
|
i++;
|
|
if(i >= GetLineCount()) {
|
|
pos = GetLineLength(GetLineCount() - 1);
|
|
return GetLineCount() - 1;
|
|
}
|
|
}
|
|
}
|
|
return 0; // just silencing GCC warning, cannot get here
|
|
}
|
|
|
|
int64 TextCtrl::GetPos64(int ln, int lpos) const {
|
|
GuiLock __;
|
|
ln = minmax(ln, 0, GetLineCount() - 1);
|
|
int i;
|
|
int64 pos;
|
|
if(ln < cline && cline - ln < ln && !view) {
|
|
pos = cpos;
|
|
i = cline;
|
|
while(i > ln)
|
|
pos -= GetLineLength(--i) + 1;
|
|
}
|
|
else {
|
|
pos = 0;
|
|
i = 0;
|
|
if(view) {
|
|
for(int j = 0; j < ln >> 8; j++) {
|
|
pos += total256[j];
|
|
i += 256;
|
|
}
|
|
}
|
|
else
|
|
if(ln >= cline) {
|
|
pos = cpos;
|
|
i = cline;
|
|
}
|
|
while(i < ln)
|
|
pos += GetLineLength(i++) + 1;
|
|
}
|
|
return pos + min(GetLineLength(ln), lpos);
|
|
}
|
|
|
|
WString TextCtrl::GetW(int64 pos, int size) const
|
|
{
|
|
int i = GetLinePos64(pos);
|
|
WStringBuffer r;
|
|
for(;;) {
|
|
if(i >= GetLineCount()) break;
|
|
WString ln = GetWLine(i++);
|
|
int sz = min(LimitSize(ln.GetLength() - pos), size);
|
|
if(pos == 0 && sz == ln.GetLength())
|
|
r.Cat(ln);
|
|
else
|
|
r.Cat(ln.Mid((int)pos, sz));
|
|
size -= sz;
|
|
if(size == 0) break;
|
|
#ifdef PLATFORM_WIN32
|
|
r.Cat('\r');
|
|
#endif
|
|
r.Cat('\n');
|
|
size--;
|
|
if(size == 0) break;
|
|
pos = 0;
|
|
}
|
|
return WString(r);
|
|
}
|
|
|
|
String TextCtrl::Get(int64 pos, int size, byte charset) const
|
|
{
|
|
if(charset == CHARSET_UTF8) {
|
|
int i = GetLinePos64(pos);
|
|
StringBuffer r;
|
|
for(;;) {
|
|
if(i >= GetLineCount()) break;
|
|
int sz = min(LimitSize(GetLineLength(i) - pos), size);
|
|
const String& h = GetUtf8Line(i);
|
|
const char *s = h;
|
|
int n = h.GetCount();
|
|
i++;
|
|
if(pos == 0 && sz == n)
|
|
r.Cat(s, n);
|
|
else
|
|
r.Cat(ToUtf32(s, n).Mid((int)pos, sz).ToString());
|
|
size -= sz;
|
|
if(size == 0) break;
|
|
#ifdef PLATFORM_WIN32
|
|
r.Cat('\r');
|
|
#endif
|
|
r.Cat('\n');
|
|
size--;
|
|
if(size == 0) break;
|
|
pos = 0;
|
|
}
|
|
return String(r);
|
|
}
|
|
return FromUnicode(GetW(pos, size), charset);
|
|
}
|
|
|
|
int TextCtrl::GetChar(int64 pos) const {
|
|
if(pos < 0 || pos >= GetLength64())
|
|
return 0;
|
|
int i = GetLinePos64(pos);
|
|
WString ln = GetWLine(i);
|
|
int c = ln.GetLength() == pos ? '\n' : ln[(int)pos];
|
|
return c;
|
|
}
|
|
|
|
int TextCtrl::GetLinePos32(int& pos) const
|
|
{
|
|
int64 p = pos;
|
|
int l = GetLinePos64(p);
|
|
pos = (int)p;
|
|
return l;
|
|
}
|
|
|
|
bool TextCtrl::GetSelection32(int& l, int& h) const
|
|
{
|
|
int64 ll, hh;
|
|
bool b = GetSelection(ll, hh);
|
|
if(hh >= INT_MAX) {
|
|
l = h = (int)cursor;
|
|
return false;
|
|
}
|
|
l = (int)ll;
|
|
h = (int)hh;
|
|
return b;
|
|
}
|
|
|
|
int TextCtrl::GetCursor32() const
|
|
{
|
|
int64 h = GetCursor64();
|
|
return h < INT_MAX ? (int)h : 0;
|
|
}
|
|
|
|
int TextCtrl::GetLength32() const
|
|
{
|
|
int64 h = GetLength64();
|
|
return h < INT_MAX ? (int)h : 0;
|
|
}
|
|
|
|
int TextCtrl::Insert0(int pos, const WString& txt) { // TODO: Do this with utf8
|
|
GuiLock __;
|
|
int inspos = pos;
|
|
PreInsert(inspos, txt);
|
|
if(pos < cpos)
|
|
cpos = cline = 0;
|
|
int i = GetLinePos32(pos);
|
|
DirtyFrom(i);
|
|
int size = 0;
|
|
|
|
WStringBuffer lnb;
|
|
Vector<WString> iln;
|
|
const wchar *s = txt;
|
|
while(s < txt.End())
|
|
if(*s >= ' ') {
|
|
const wchar *b = s;
|
|
while(*s >= ' ') // txt is zero teminated...
|
|
s++;
|
|
int sz = int(s - b);
|
|
lnb.Cat(b, sz);
|
|
size += sz;
|
|
}
|
|
else
|
|
if(*s == '\t') {
|
|
lnb.Cat(*s);
|
|
size++;
|
|
s++;
|
|
}
|
|
else
|
|
if(*s == '\n') {
|
|
iln.Add(lnb);
|
|
size++;
|
|
lnb.Clear();
|
|
s++;
|
|
}
|
|
else
|
|
s++;
|
|
WString ln = lnb;
|
|
WString l = GetWLine(i);
|
|
if(iln.GetCount()) {
|
|
iln[0] = l.Mid(0, pos) + iln[0];
|
|
ln.Cat(l.Mid(pos));
|
|
SetLine(i, ln);
|
|
InvalidateLine(i);
|
|
LineInsert(i, iln.GetCount());
|
|
for(int j = 0; j < iln.GetCount(); j++)
|
|
SetLine(i + j, iln[j]);
|
|
InsertLines(i, iln.GetCount());
|
|
Refresh();
|
|
}
|
|
else {
|
|
SetLine(i, l.Mid(0, pos) + ln + l.Mid(pos));
|
|
InvalidateLine(i);
|
|
RefreshLine(i);
|
|
}
|
|
total += size;
|
|
SetSb();
|
|
Update();
|
|
ClearSelection();
|
|
PostInsert(inspos, txt);
|
|
return size;
|
|
}
|
|
|
|
void TextCtrl::Remove0(int pos, int size) {
|
|
GuiLock __;
|
|
int rmpos = pos, rmsize = size;
|
|
PreRemove(rmpos, rmsize);
|
|
total -= size;
|
|
if(pos < cpos)
|
|
cpos = cline = 0;
|
|
int i = GetLinePos32(pos);
|
|
DirtyFrom(i);
|
|
WString ln = GetWLine(i);
|
|
int sz = min(LimitSize(ln.GetLength() - pos), size);
|
|
ln.Remove(pos, sz);
|
|
size -= sz;
|
|
SetLine(i, ln);
|
|
if(size == 0) {
|
|
InvalidateLine(i);
|
|
RefreshLine(i);
|
|
}
|
|
else {
|
|
size--;
|
|
int j = i + 1;
|
|
for(;;) {
|
|
int sz = GetLineLength(j) + 1;
|
|
if(sz > size) break;
|
|
j++;
|
|
size -= sz;
|
|
}
|
|
WString p1 = GetWLine(i);
|
|
WString p2 = GetWLine(j);
|
|
p1.Insert(p1.GetLength(), p2.Mid(size, p2.GetLength() - size));
|
|
SetLine(i, p1);
|
|
LineRemove(i + 1, j - i);
|
|
RemoveLines(i + 1, j - i);
|
|
InvalidateLine(i);
|
|
Refresh();
|
|
}
|
|
Update();
|
|
ClearSelection();
|
|
PostRemove(rmpos, rmsize);
|
|
SetSb();
|
|
}
|
|
|
|
void TextCtrl::Undodo()
|
|
{
|
|
while(undo.GetCount() > undosteps)
|
|
undo.DropHead();
|
|
redo.Clear();
|
|
}
|
|
|
|
void TextCtrl::NextUndo()
|
|
{
|
|
if(column_typing)
|
|
column_typing = false;
|
|
else
|
|
undoserial += incundoserial;
|
|
incundoserial = false;
|
|
}
|
|
|
|
void TextCtrl::IncDirty() {
|
|
dirty++;
|
|
if(dirty == 0 || dirty == 1)
|
|
{
|
|
if(dirty)
|
|
SetModify();
|
|
else
|
|
ClearModify();
|
|
WhenState();
|
|
}
|
|
}
|
|
|
|
void TextCtrl::DecDirty() {
|
|
dirty--;
|
|
if(dirty == 0 || dirty == -1)
|
|
{
|
|
if(dirty)
|
|
SetModify();
|
|
else
|
|
ClearModify();
|
|
WhenState();
|
|
}
|
|
}
|
|
|
|
int TextCtrl::InsertU(int pos, const WString& txt, bool typing) {
|
|
int sz = Insert0(pos, txt);
|
|
if(undosteps) {
|
|
if(undo.GetCount() && typing && *txt != '\n' && IsDirty()) {
|
|
UndoRec& u = undo.Tail();
|
|
if(u.typing && u.pos + u.size == pos) {
|
|
u.size += txt.GetLength();
|
|
return sz;
|
|
}
|
|
}
|
|
UndoRec& u = undo.AddTail();
|
|
incundoserial = true;
|
|
IncDirty();
|
|
u.serial = undoserial;
|
|
u.pos = pos;
|
|
u.size = sz;
|
|
u.typing = typing;
|
|
}
|
|
return sz;
|
|
}
|
|
|
|
void TextCtrl::RemoveU(int pos, int size) {
|
|
if(size + pos > total)
|
|
size = int(total - pos);
|
|
if(size <= 0) return;
|
|
if(undosteps) {
|
|
UndoRec& u = undo.AddTail();
|
|
incundoserial = true;
|
|
IncDirty();
|
|
u.serial = undoserial;
|
|
u.pos = pos;
|
|
u.size = 0;
|
|
u.SetText(Get(pos, size, CHARSET_UTF8));
|
|
u.typing = false;
|
|
}
|
|
Remove0(pos, size);
|
|
}
|
|
|
|
int TextCtrl::Insert(int pos, const WString& _txt, bool typing) {
|
|
if(pos + _txt.GetCount() > max_total)
|
|
return 0;
|
|
WString txt = _txt;
|
|
if(!IsUnicodeCharset(charset))
|
|
for(int i = 0; i < txt.GetCount(); i++)
|
|
if(FromUnicode(txt[i], charset) == DEFAULTCHAR)
|
|
txt.Set(i, '?');
|
|
int sz = InsertU(pos, txt, typing);
|
|
Undodo();
|
|
return sz;
|
|
}
|
|
|
|
int TextCtrl::Insert(int pos, const String& txt, byte charset)
|
|
{
|
|
return Insert(pos, ToUnicode(txt, charset), false);
|
|
}
|
|
|
|
void TextCtrl::Remove(int pos, int size) {
|
|
RemoveU(pos, size);
|
|
Undodo();
|
|
}
|
|
|
|
void TextCtrl::Undo() {
|
|
if(undo.IsEmpty()) return;
|
|
undo_op = true;
|
|
int nc = 0;
|
|
int s = undo.Tail().serial;
|
|
while(undo.GetCount()) {
|
|
const UndoRec& u = undo.Tail();
|
|
if(u.serial != s)
|
|
break;
|
|
UndoRec& r = redo.AddTail();
|
|
r.serial = s;
|
|
r.typing = false;
|
|
nc = r.pos = u.pos;
|
|
CachePos(r.pos);
|
|
if(u.size) {
|
|
r.size = 0;
|
|
r.SetText(Get(u.pos, u.size, CHARSET_UTF8));
|
|
Remove0(u.pos, u.size);
|
|
}
|
|
else {
|
|
WString text = ToUtf32(u.GetText());
|
|
r.size = Insert0(u.pos, text);
|
|
nc += r.size;
|
|
}
|
|
undo.DropTail();
|
|
DecDirty();
|
|
}
|
|
ClearSelection();
|
|
PlaceCaret(nc, false);
|
|
Action();
|
|
undo_op = false;
|
|
}
|
|
|
|
void TextCtrl::Redo() {
|
|
if(!redo.GetCount()) return;
|
|
NextUndo();
|
|
int s = redo.Tail().serial;
|
|
int nc = 0;
|
|
while(redo.GetCount()) {
|
|
const UndoRec& r = redo.Tail();
|
|
if(r.serial != s)
|
|
break;
|
|
nc = r.pos + r.size;
|
|
CachePos(r.pos);
|
|
if(r.size)
|
|
RemoveU(r.pos, r.size);
|
|
else
|
|
nc += InsertU(r.pos, ToUtf32(r.GetText()));
|
|
redo.DropTail();
|
|
IncDirty();
|
|
}
|
|
ClearSelection();
|
|
PlaceCaret(nc, false);
|
|
Action();
|
|
}
|
|
|
|
void TextCtrl::ClearSelection() {
|
|
if(anchor >= 0) {
|
|
anchor = -1;
|
|
Refresh();
|
|
SelectionChanged();
|
|
WhenSel();
|
|
}
|
|
}
|
|
|
|
void TextCtrl::SetSelection(int64 l, int64 h) {
|
|
if(l != h) {
|
|
PlaceCaret(minmax(l, (int64)0, total), false);
|
|
PlaceCaret(minmax(h, (int64)0, total), true);
|
|
}
|
|
else
|
|
SetCursor(l);
|
|
}
|
|
|
|
bool TextCtrl::GetSelection(int64& l, int64& h) const {
|
|
if(anchor < 0 || anchor == cursor) {
|
|
l = h = cursor;
|
|
return false;
|
|
}
|
|
else {
|
|
l = min(anchor, cursor);
|
|
h = max(anchor, cursor);
|
|
return !rectsel;
|
|
}
|
|
}
|
|
|
|
String TextCtrl::GetSelection(byte charset) const {
|
|
int64 l, h;
|
|
if(GetSelection(l, h))
|
|
return Get(l, LimitSize(h - l), charset);
|
|
return String();
|
|
}
|
|
|
|
WString TextCtrl::GetSelectionW() const {
|
|
int64 l, h;
|
|
if(GetSelection(l, h))
|
|
return GetW(l, LimitSize(h - l));
|
|
return WString();
|
|
}
|
|
|
|
bool TextCtrl::RemoveSelection() {
|
|
int64 l, h;
|
|
if(anchor < 0) return false;
|
|
if(IsRectSelection())
|
|
RemoveRectSelection();
|
|
else {
|
|
if(!GetSelection(l, h))
|
|
return false;
|
|
Remove((int)l, int(h - l));
|
|
anchor = -1;
|
|
Refresh();
|
|
PlaceCaret(l);
|
|
}
|
|
Action();
|
|
return true;
|
|
}
|
|
|
|
void TextCtrl::RefreshLines(int l1, int l2) {
|
|
int h = max(l1, l2);
|
|
for(int i = min(l1, l2); i <= h; i++)
|
|
RefreshLine(i);
|
|
}
|
|
|
|
void TextCtrl::Cut() {
|
|
if(!IsReadOnly() && IsAnySelection()) {
|
|
Copy();
|
|
RemoveSelection();
|
|
}
|
|
}
|
|
|
|
void TextCtrl::Copy() {
|
|
int64 l, h;
|
|
if(!GetSelection(l, h) && !IsAnySelection()) {
|
|
int i = GetLine(cursor);
|
|
l = GetPos64(i);
|
|
h = l + GetLineLength(i) + 1;
|
|
}
|
|
WString txt;
|
|
if(IsRectSelection())
|
|
txt = CopyRectSelection();
|
|
else
|
|
txt = GetW(l, LimitSize(h - l));
|
|
ClearClipboard();
|
|
AppendClipboardUnicodeText(txt);
|
|
AppendClipboardText(txt.ToString());
|
|
}
|
|
|
|
void TextCtrl::SelectAll() {
|
|
SetSelection();
|
|
}
|
|
|
|
int TextCtrl::Paste(const WString& text) {
|
|
if(IsReadOnly()) return 0;
|
|
int n;
|
|
if(IsRectSelection())
|
|
n = PasteRectSelection(text);
|
|
else {
|
|
RemoveSelection();
|
|
n = Insert((int)cursor, text);
|
|
PlaceCaret(cursor + n);
|
|
}
|
|
Refresh();
|
|
return n;
|
|
}
|
|
|
|
String TextCtrl::GetPasteText()
|
|
{
|
|
return Null;
|
|
}
|
|
|
|
void TextCtrl::Paste() {
|
|
WString w = ReadClipboardUnicodeText();
|
|
if(w.IsEmpty())
|
|
w = ReadClipboardText().ToWString();
|
|
if(w.IsEmpty())
|
|
w = GetPasteText().ToWString();
|
|
Paste(w);
|
|
Action();
|
|
}
|
|
|
|
void TextCtrl::StdBar(Bar& menu) {
|
|
NextUndo();
|
|
if(undosteps) {
|
|
menu.Add(undo.GetCount() && IsEditable(), t_("Undo"), CtrlImg::undo(), THISBACK(Undo))
|
|
.Key(K_ALT_BACKSPACE)
|
|
.Key(K_CTRL_Z);
|
|
menu.Add(redo.GetCount() && IsEditable(), t_("Redo"), CtrlImg::redo(), THISBACK(Redo))
|
|
.Key(K_SHIFT|K_ALT_BACKSPACE)
|
|
.Key(K_CTRL_Y)
|
|
.Key(K_SHIFT_CTRL_Z);
|
|
menu.Separator();
|
|
}
|
|
menu.Add(IsEditable() && IsAnySelection(),
|
|
t_("Cut"), CtrlImg::cut(), THISBACK(Cut))
|
|
.Key(K_SHIFT_DELETE)
|
|
.Key(K_CTRL_X);
|
|
menu.Add(IsAnySelection(),
|
|
t_("Copy"), CtrlImg::copy(), THISBACK(Copy))
|
|
.Key(K_CTRL_INSERT)
|
|
.Key(K_CTRL_C);
|
|
bool canpaste = IsEditable() && IsClipboardAvailableText();
|
|
menu.Add(canpaste,
|
|
t_("Paste"), CtrlImg::paste(), THISBACK(DoPaste))
|
|
.Key(K_SHIFT_INSERT)
|
|
.Key(K_CTRL_V);
|
|
LineEdit *e = dynamic_cast<LineEdit *>(this);
|
|
if(e) {
|
|
menu.Add(canpaste,
|
|
t_("Paste in column"), CtrlImg::paste_vert(), callback(e, &LineEdit::DoPasteColumn))
|
|
.Key(K_ALT_V|K_SHIFT);
|
|
menu.Add(e->IsRectSelection(),
|
|
t_("Sort"), CtrlImg::sort(), callback(e, &LineEdit::Sort));
|
|
}
|
|
menu.Add(IsEditable() && IsAnySelection(),
|
|
t_("Erase"), CtrlImg::remove(), THISBACK(DoRemoveSelection))
|
|
.Key(K_DELETE);
|
|
menu.Separator();
|
|
menu.Add(GetLength64(),
|
|
t_("Select all"), CtrlImg::select_all(), THISBACK(SelectAll))
|
|
.Key(K_CTRL_A);
|
|
}
|
|
|
|
String TextCtrl::GetSelectionData(const String& fmt) const
|
|
{
|
|
return GetTextClip(GetSelectionW(), fmt);
|
|
}
|
|
|
|
void TextCtrl::EditPos::Serialize(Stream& s) {
|
|
int version = 1;
|
|
s / version;
|
|
if(version >= 1)
|
|
s % sby % cursor;
|
|
else {
|
|
int c = (int)cursor;
|
|
s % sby % c;
|
|
cursor = c;
|
|
}
|
|
}
|
|
|
|
}
|