mirror of
https://github.com/ultimatepp/ultimatepp.git
synced 2026-05-16 06:05:58 -06:00
This might bring some incompatibilities in the code that expects wchar to be 16 bit, which escpecially involves dealing with Win32 (and to lesser extend MacOS) APIs, so if your application is doing that, please check all instances of WCHAR (UniChar on MacOS) or even wchar especially type casts. To support host APIs, char16 is introduced (but there is no 16-bit String varian). Use ToSystemCharsetW, FromSystemCharsetW to convert texts to Win32 API. - Support of drawing non-BMP characters in GUI - Vastly improved character font replacement code (when drawing characters missing with requested font, replacement font is used) - Last instances of Win32 ANSI calls (those ending with A) are removed - UTF handling routines are refactored and their's naming is unified - RTF is now being able to handle non-BMP characters (RTF is used as clipboard format for RichText) Other minor changes: - fixed TryRealloc issue - improved MemoryCheck - Removed MemoryAlloc48/MemoryFree48 - In theide Background parsing should less often cause delays in the main thread
1233 lines
25 KiB
C++
1233 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_UNICODE;
|
|
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;
|
|
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();
|
|
}
|
|
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 > INT_MAX - 512) {
|
|
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, 5000, 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;
|
|
blk++;
|
|
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()
|
|
{
|
|
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() > 1 && 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())
|
|
l = 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_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;
|
|
}
|
|
}
|
|
|
|
}
|