1 #include <CtrlLib/CtrlLib.h>
2
3 #define LTIMING(x) // RTIMING(x)
4
5 namespace Upp {
6
TextCtrl()7 TextCtrl::TextCtrl()
8 {
9 Unicode();
10 undosteps = 1000;
11 Clear();
12 undoserial = 0;
13 incundoserial = false;
14 undo_op = false;
15 WhenBar = THISBACK(StdBar);
16 charset = CHARSET_UNICODE;
17 color[INK_NORMAL] = SColorText;
18 color[INK_DISABLED] = SColorDisabled;
19 color[INK_SELECTED] = SColorHighlightText;
20 color[PAPER_NORMAL] = SColorPaper;
21 color[PAPER_READONLY] = SColorFace;
22 color[PAPER_SELECTED] = SColorHighlight;
23 color[WHITESPACE] = Blend(SColorLight, SColorHighlight);
24 color[WARN_WHITESPACE] = Blend(SColorLight, SRed);
25 processtab = true;
26 processenter = true;
27 nobg = false;
28 rectsel = false;
29 #ifdef CPU_64
30 max_total = 2047 * 1024 * 1024;
31 #else
32 #ifdef _DEBUG
33 max_total = 100 * 1024 * 1024;
34 #else
35 max_total = 200 * 1024 * 1024;
36 #endif
37 #endif
38 max_line_len = 100000;
39 truncated = false;
40 }
41
~TextCtrl()42 TextCtrl::~TextCtrl() {}
43
MiddleDown(Point p,dword flags)44 void TextCtrl::MiddleDown(Point p, dword flags)
45 {
46 if(IsReadOnly())
47 return;
48 if(AcceptText(Selection())) {
49 WString w = GetWString(Selection());
50 selclick = false;
51 LeftDown(p, flags);
52 Paste(w);
53 Action();
54 }
55 }
56
CancelMode()57 void TextCtrl::CancelMode()
58 {
59 selclick = false;
60 dropcaret = Null;
61 isdrag = false;
62 }
63
Clear()64 void TextCtrl::Clear()
65 {
66 GuiLock __;
67 view = NULL;
68 viewlines = 0;
69 cline = 0;
70 cpos = 0;
71 total = 0;
72 truncated = false;
73 lin.Clear();
74 ClearLines();
75 lin.Add();
76 InsertLines(0, 1);
77 DirtyFrom(0);
78 undo.Clear();
79 redo.Clear();
80 ClearDirty();
81 anchor = -1;
82 cursor = 0;
83 SetSb();
84 PlaceCaret(0);
85 SelectionChanged();
86 Refresh();
87 }
88
DirtyFrom(int line)89 void TextCtrl::DirtyFrom(int line) {}
SelectionChanged()90 void TextCtrl::SelectionChanged() {}
ClearLines()91 void TextCtrl::ClearLines() {}
InsertLines(int line,int count)92 void TextCtrl::InsertLines(int line, int count) {}
RemoveLines(int line,int count)93 void TextCtrl::RemoveLines(int line, int count) {}
PreInsert(int pos,const WString & text)94 void TextCtrl::PreInsert(int pos, const WString& text) {}
PostInsert(int pos,const WString & text)95 void TextCtrl::PostInsert(int pos, const WString& text) {}
PreRemove(int pos,int size)96 void TextCtrl::PreRemove(int pos, int size) {}
PostRemove(int pos,int size)97 void TextCtrl::PostRemove(int pos, int size) {}
RefreshLine(int i)98 void TextCtrl::RefreshLine(int i) {}
InvalidateLine(int i)99 void TextCtrl::InvalidateLine(int i) {}
SetSb()100 void TextCtrl::SetSb() {}
PlaceCaret(int64 newcursor,bool sel)101 void TextCtrl::PlaceCaret(int64 newcursor, bool sel) {}
102
RemoveRectSelection()103 int TextCtrl::RemoveRectSelection() { return 0; }
CopyRectSelection()104 WString TextCtrl::CopyRectSelection() { return Null; }
PasteRectSelection(const WString & s)105 int TextCtrl::PasteRectSelection(const WString& s) { return 0; }
106
CachePos(int64 pos)107 void TextCtrl::CachePos(int64 pos)
108 {
109 GuiLock __;
110 int64 p = pos;
111 cline = GetLinePos64(p);
112 cpos = pos - p;
113 }
114
CacheLinePos(int linei)115 void TextCtrl::CacheLinePos(int linei)
116 {
117 GuiLock __;
118 if(linei >= 0 && linei < GetLineCount()) {
119 cpos = GetPos64(linei);
120 cline = linei;
121 }
122 }
123
IsUnicodeCharset(byte charset)124 bool TextCtrl::IsUnicodeCharset(byte charset)
125 {
126 return findarg(charset, CHARSET_UTF8, CHARSET_UTF8_BOM, CHARSET_UTF16_LE, CHARSET_UTF16_BE,
127 CHARSET_UTF16_LE_BOM, CHARSET_UTF16_BE_BOM) >= 0;
128 }
129
Load0(Stream & in,byte charset_,bool view)130 int TextCtrl::Load0(Stream& in, byte charset_, bool view) {
131 GuiLock __;
132 Clear();
133 lin.Clear();
134 ClearLines();
135 total = 0;
136 SetCharset(charset_);
137 truncated = false;
138 viewlines = 0;
139 this->view = NULL;
140 view_all = false;
141 offset256.Clear();
142 total256.Clear();
143 view_cache[0].blk = view_cache[1].blk = -1;
144 if(view) {
145 this->view = ∈
146 SetReadOnly();
147 }
148 if(charset == CHARSET_UTF8_BOM && in.GetLeft() >= 3) {
149 int64 pos = in.GetPos();
150 byte h[3];
151 if(!(in.Get(h, 3) == 3 && h[0] == 0xEF && h[1] == 0xBB && h[2] == 0xBF))
152 in.Seek(pos);
153 charset = CHARSET_UTF8;
154 }
155 int be16 = findarg(charset, CHARSET_UTF16_LE_BOM, CHARSET_UTF16_BE_BOM);
156 if(be16 >= 0 && in.GetLeft() >= 2) {
157 int64 pos = in.GetPos();
158 dword h = in.Get16le();
159 if(h != (be16 ? 0xfffe : 0xfeff))
160 in.Seek(pos);
161 charset = be16 ? CHARSET_UTF16_BE : CHARSET_UTF16_LE;
162 }
163
164 if(view) {
165 view_loading_pos = in.GetPos();
166 view_loading_lock = 0;
167 ViewLoading();
168 PlaceCaret(0);
169 return 0;
170 }
171
172 int m = LoadLines(lin, INT_MAX, total, in, charset, max_line_len, max_total, truncated);
173
174 InsertLines(0, lin.GetCount());
175 Update();
176 SetSb();
177 PlaceCaret(0);
178 return m;
179 }
180
LoadLines(Vector<Ln> & ls,int n,int64 & total,Stream & in,byte charset,int max_line_len,int max_total,bool & truncated) const181 int TextCtrl::LoadLines(Vector<Ln>& ls, int n, int64& total, Stream& in, byte charset, int max_line_len, int max_total, bool& truncated) const
182 {
183 StringBuffer ln;
184 bool cr = false;
185 byte b8 = 0;
186 if(charset == CHARSET_UTF16_LE || charset == CHARSET_UTF16_BE) {
187 WStringBuffer wln;
188 auto put_wln = [&]() {
189 Ln& ln = ls.Add();
190 ln.len = wln.GetCount();
191 ln.text = ToUtf8(~wln, ln.len);
192 };
193 for(;;) {
194 int c = charset == CHARSET_UTF16_LE ? in.Get16le() : in.Get16be();
195 if(c < 0) {
196 total += wln.GetCount();
197 put_wln();
198 goto finish;
199 }
200 if(c == '\r')
201 cr = true;
202 else
203 if(c == '\n') {
204 truncate_line:
205 total += wln.GetCount() + 1;
206 put_wln();
207 if(ls.GetCount() >= n)
208 goto finish;
209 wln.Clear();
210 }
211 else {
212 wln.Cat(c);
213 if(wln.GetCount() >= max_line_len)
214 goto truncate_line;
215 }
216 }
217 }
218 else {
219 for(;;) {
220 byte h[200];
221 int size;
222 int64 pos = in.GetPos();
223 const byte *s = in.GetSzPtr(size);
224 if(size == 0) {
225 size = in.Get(h, 200);
226 s = h;
227 if(size == 0)
228 break;
229 }
230 const byte *posptr = s;
231 const byte *e = s + size;
232 while(s < e) {
233 const byte *b = s;
234 const byte *ee = s + min(size_t(e - s), size_t(max_line_len - ln.GetCount()));
235 {
236 while(s < ee && *s != '\r' && *s != '\n') {
237 b8 |= *s++;
238 while(s < ee && *s >= ' ' && *s < 128) // Interestingly, this speeds things up
239 s++;
240 while(s < ee && *s >= ' ')
241 b8 |= *s++;
242 }
243 }
244 if(b < s) {
245 if(s - b + ln.GetCount() > max_total)
246 ln.Cat((const char *)b, max_total - ln.GetCount());
247 else
248 ln.Cat((const char *)b, (const char *)s);
249 }
250 auto put_ln = [&]() -> bool {
251 Ln& l = ls.Add();
252 if(charset == CHARSET_UTF8) {
253 l.len = CHARSET_UTF8 && (b8 & 0x80) ? utf8len(~ln, ln.GetCount()) : ln.GetCount();
254 l.text = ln;
255 }
256 else {
257 l.len = ln.GetCount();
258 l.text = ToCharset(CHARSET_UTF8, ln, charset);
259 }
260 if(total + l.len + 1 > max_total) {
261 ls.Drop();
262 truncated = true;
263 return false;
264 }
265 total += l.len + 1;
266 return true;
267 };
268 while(ln.GetCount() >= max_line_len) {
269 int ei = max_line_len;
270 if(charset == CHARSET_UTF8)
271 while(ei > 0 && ei > max_line_len - 6 && !((byte)ln[ei] < 128 || IsUtf8Lead((byte)ln[ei]))) // break lse at whole utf8 codepoint if possible
272 ei--;
273 String nln(~ln + ei, ln.GetCount() - ei);
274 ln.SetCount(ei);
275 truncated = true;
276 if(!put_ln())
277 goto out_of_limit;
278 if(ls.GetCount() >= n) {
279 in.Seek(s - posptr + pos);
280 goto finish;
281 }
282 ln = nln;
283 }
284 if(s < e && *s == '\r') {
285 s++;
286 cr = true;
287 }
288 if(s < e && *s == '\n') {
289 if(!put_ln())
290 goto out_of_limit;
291 s++;
292 if(ls.GetCount() >= n) {
293 in.Seek(s - posptr + pos);
294 goto finish;
295 }
296 ln.Clear();
297 b8 = 0;
298 }
299 }
300 }
301 }
302
303 out_of_limit:
304 {
305 WString w = ToUnicode(~ln, ln.GetCount(), charset);
306 if(total + w.GetLength() <= max_total) {
307 Ln& ln = ls.Add();
308 ln.len = w.GetCount();
309 ln.text = ToUtf8(~w, ln.len);
310 total += ln.len;
311 }
312 }
313 finish:
314 return ls.GetCount() > 1 ? cr ? LE_CRLF : LE_LF : LE_DEFAULT;
315 }
316
ViewLoading()317 void TextCtrl::ViewLoading()
318 {
319 GuiLock __;
320 if(view_all || !view)
321 return;
322 int start = msecs();
323 view->Seek(view_loading_pos);
324 int lines0 = viewlines;
325 for(;;) {
326 offset256.Add(view->GetPos());
327 Vector<Ln> l;
328 bool b;
329 int64 t = 0;
330
331 LoadLines(l, 256, t, *view, charset, 10000, INT_MAX, b);
332 viewlines += l.GetCount();
333 total += t;
334 total256.Add((int)t);
335
336 #ifdef CPU_32
337 enum { MAX_LINES = 128000000 };
338 #else
339 enum { MAX_LINES = INT_MAX - 512 };
340 #endif
341
342 if(view->IsEof() || viewlines > INT_MAX - 512) {
343 WhenViewMapping(view->GetPos());
344 view_all = true;
345 break;
346 }
347
348 if(view_loading_lock) {
349 view_loading_pos = view->GetPos();
350 WhenViewMapping(view_loading_pos);
351 break;
352 }
353
354 if(msecs(start) > 20) {
355 view_loading_pos = view->GetPos();
356 PostCallback([=] { ViewLoading(); });
357 WhenViewMapping(view_loading_pos);
358 break;
359 }
360 }
361 InsertLines(lines0, viewlines - lines0);
362 SetSb();
363 Update();
364 }
365
UnlockViewMapping()366 void TextCtrl::UnlockViewMapping()
367 {
368 view_loading_lock--;
369 ViewLoading();
370 }
371
WaitView(int line,bool progress)372 void TextCtrl::WaitView(int line, bool progress)
373 {
374 if(view) {
375 if(progress) {
376 LockViewMapping();
377 Progress pi("Scanning the file");
378 pi.Delay(1000);
379 while(view && !view_all && viewlines < line) {
380 if(pi.SetCanceled(int(view_loading_pos >> 10), int(view->GetSize()) >> 10))
381 break;
382 ViewLoading();
383 }
384 UnlockViewMapping();
385 }
386 else
387 while(view && !view_all && viewlines <= line)
388 ViewLoading();
389 }
390 }
391
SerializeViewMap(Stream & s)392 void TextCtrl::SerializeViewMap(Stream& s)
393 {
394 GuiLock __;
395 int version = 0;
396 s / version;
397 s.Magic(327845692);
398 s % view_loading_pos
399 % total
400 % viewlines
401 % view_all
402 % total256
403 % offset256
404 ;
405 if(s.IsLoading()) {
406 SetSb();
407 Update();
408 Refresh();
409 }
410 }
411
GetLn(int i) const412 const TextCtrl::Ln& TextCtrl::GetLn(int i) const
413 {
414 if(view) {
415 GuiLock __;
416 int blk = i >> 8;
417 if(view_cache[0].blk != blk)
418 Swap(view_cache[0], view_cache[1]); // trivial LRU
419 if(view_cache[0].blk != blk) {
420 Swap(view_cache[0], view_cache[1]); // trivial LRU
421 view->Seek(offset256[blk]);
422 int64 t = 0;
423 bool b;
424 view_cache[0].line.Clear();
425 view_cache[0].blk = blk;
426 LoadLines(view_cache[0].line, 256, t, *view, charset, 5000, INT_MAX, b);
427 }
428 return view_cache[0].line[i & 255];
429 }
430 else
431 return lin[i];
432 }
433
GetUtf8Line(int i) const434 const String& TextCtrl::GetUtf8Line(int i) const
435 {
436 return GetLn(i).text;
437 }
438
GetLineLength(int i) const439 int TextCtrl::GetLineLength(int i) const
440 {
441 return GetLn(i).len;
442 }
443
Save(Stream & s,byte charset,int line_endings) const444 void TextCtrl::Save(Stream& s, byte charset, int line_endings) const {
445 if(charset == CHARSET_UTF8_BOM) {
446 static byte bom[] = { 0xEF, 0xBB, 0xBF };
447 s.Put(bom, 3);
448 charset = CHARSET_UTF8;
449 }
450 if(charset == CHARSET_UTF16_LE_BOM) {
451 s.Put16le(0xfeff);
452 charset = CHARSET_UTF16_LE;
453 }
454 if(charset == CHARSET_UTF16_BE_BOM) {
455 s.Put16be(0xfeff);
456 charset = CHARSET_UTF16_BE;
457 }
458 charset = ResolveCharset(charset);
459 String le = "\n";
460 #ifdef PLATFORM_WIN32
461 if(line_endings == LE_DEFAULT)
462 le = "\r\n";
463 #endif
464 if(line_endings == LE_CRLF)
465 le = "\r\n";
466 int be16 = findarg(charset, CHARSET_UTF16_LE, CHARSET_UTF16_BE);
467 if(be16 >= 0) {
468 String wle;
469 for(int i = 0; i < le.GetCount(); i++) {
470 if(be16)
471 wle.Cat(0);
472 wle.Cat(le[i]);
473 if(!be16)
474 wle.Cat(0);
475 }
476 for(int i = 0; i < GetLineCount(); i++) {
477 if(i)
478 s.Put(wle);
479 WString txt = GetWLine(i);
480 const wchar *e = txt.End();
481 if(be16)
482 for(const wchar *w = txt; w != e; w++)
483 s.Put16be(*w);
484 else
485 for(const wchar *w = txt; w != e; w++)
486 s.Put16le(*w);
487 }
488 return;
489 }
490 for(int i = 0; i < GetLineCount(); i++) {
491 if(i)
492 s.Put(le);
493 if(charset == CHARSET_UTF8)
494 s.Put(GetUtf8Line(i));
495 else {
496 String txt = FromUnicode(GetWLine(i), charset);
497 const char *e = txt.End();
498 for(const char *w = txt; w != e; w++)
499 s.Put(*w == DEFAULTCHAR ? '?' : *w);
500 }
501 }
502 }
503
Set(const String & s,byte charset)504 void TextCtrl::Set(const String& s, byte charset) {
505 StringStream ss(s);
506 Load(ss, charset);
507 }
508
Get(byte charset) const509 String TextCtrl::Get(byte charset) const
510 {
511 StringStream ss;
512 Save(ss, charset);
513 return ss;
514 }
515
GetInvalidCharPos(byte charset) const516 int TextCtrl::GetInvalidCharPos(byte charset) const
517 {
518 int q = 0;
519 if(!IsUnicodeCharset(charset))
520 for(int i = 0; i < GetLineCount(); i++) {
521 WString txt = GetWLine(i);
522 WString ctxt = ToUnicode(FromUnicode(txt, charset), charset);
523 for(int w = 0; w < txt.GetLength(); w++)
524 if(txt[w] != ctxt[w])
525 return q + w;
526 q += txt.GetLength() + 1;
527 }
528 return -1;
529 }
530
ClearDirty()531 void TextCtrl::ClearDirty()
532 {
533 dirty = 0;
534 ClearModify();
535 WhenState();
536 }
537
PickUndoData()538 TextCtrl::UndoData TextCtrl::PickUndoData()
539 {
540 UndoData data;
541 data.undo = pick(undo);
542 data.redo = pick(redo);
543 data.undoserial = undoserial;
544 return data;
545 }
546
SetPickUndoData(TextCtrl::UndoData && data)547 void TextCtrl::SetPickUndoData(TextCtrl::UndoData&& data)
548 {
549 undo = pick(data.undo);
550 redo = pick(data.redo);
551 undoserial = data.undoserial;
552 incundoserial = true;
553 }
554
Set(const WString & s)555 void TextCtrl::Set(const WString& s)
556 {
557 Clear();
558 Insert0(0, s);
559 }
560
SetData(const Value & v)561 void TextCtrl::SetData(const Value& v)
562 {
563 Set((WString)v);
564 }
565
GetData() const566 Value TextCtrl::GetData() const
567 {
568 return GetW();
569 }
570
GetEncodedLine(int i,byte charset) const571 String TextCtrl::GetEncodedLine(int i, byte charset) const
572 {
573 charset = ResolveCharset(charset);
574 String h = GetUtf8Line(i);
575 return charset == CHARSET_UTF8 ? h : FromUnicode(FromUtf8(h), charset);
576 }
577
GetLinePos64(int64 & pos) const578 int TextCtrl::GetLinePos64(int64& pos) const {
579 GuiLock __;
580 if(pos < cpos && cpos - pos < pos && !view) {
581 int i = cline;
582 int64 ps = cpos;
583 for(;;) {
584 ps -= GetLineLength(--i) + 1;
585 if(ps <= pos) {
586 pos = pos - ps;
587 return i;
588 }
589 }
590 }
591 else {
592 int i = 0;
593 if(view) {
594 GuiLock __;
595 int blk = 0;
596 for(;;) {
597 int n = total256[blk];
598 if(pos < n)
599 break;
600 pos -= n;
601 blk++;
602 if(blk >= total256.GetCount()) {
603 pos = GetLineLength(GetLineCount() - 1);
604 return GetLineCount() - 1;
605 }
606 }
607 i = blk << 8;
608 }
609 else
610 if(pos >= cpos) {
611 pos -= cpos;
612 i = cline;
613 }
614 for(;;) {
615 int n = GetLineLength(i) + 1;
616 if(pos < n) return i;
617 pos -= n;
618 i++;
619 if(i >= GetLineCount()) {
620 pos = GetLineLength(GetLineCount() - 1);
621 return GetLineCount() - 1;
622 }
623 }
624 }
625 return 0; // just silencing GCC warning, cannot get here
626 }
627
GetPos64(int ln,int lpos) const628 int64 TextCtrl::GetPos64(int ln, int lpos) const {
629 GuiLock __;
630 ln = minmax(ln, 0, GetLineCount() - 1);
631 int i;
632 int64 pos;
633 if(ln < cline && cline - ln < ln && !view) {
634 pos = cpos;
635 i = cline;
636 while(i > ln)
637 pos -= GetLineLength(--i) + 1;
638 }
639 else {
640 pos = 0;
641 i = 0;
642 if(view) {
643 for(int j = 0; j < ln >> 8; j++) {
644 pos += total256[j];
645 i += 256;
646 }
647 }
648 else
649 if(ln >= cline) {
650 pos = cpos;
651 i = cline;
652 }
653 while(i < ln)
654 pos += GetLineLength(i++) + 1;
655 }
656 return pos + min(GetLineLength(ln), lpos);
657 }
658
GetW(int64 pos,int size) const659 WString TextCtrl::GetW(int64 pos, int size) const
660 {
661 int i = GetLinePos64(pos);
662 WStringBuffer r;
663 for(;;) {
664 if(i >= GetLineCount()) break;
665 WString ln = GetWLine(i++);
666 int sz = min(LimitSize(ln.GetLength() - pos), size);
667 if(pos == 0 && sz == ln.GetLength())
668 r.Cat(ln);
669 else
670 r.Cat(ln.Mid((int)pos, sz));
671 size -= sz;
672 if(size == 0) break;
673 #ifdef PLATFORM_WIN32
674 r.Cat('\r');
675 #endif
676 r.Cat('\n');
677 size--;
678 if(size == 0) break;
679 pos = 0;
680 }
681 return WString(r);
682 }
683
Get(int64 pos,int size,byte charset) const684 String TextCtrl::Get(int64 pos, int size, byte charset) const
685 {
686 if(charset == CHARSET_UTF8) {
687 int i = GetLinePos64(pos);
688 StringBuffer r;
689 for(;;) {
690 if(i >= GetLineCount()) break;
691 int sz = min(LimitSize(GetLineLength(i) - pos), size);
692 const String& h = GetUtf8Line(i);
693 const char *s = h;
694 int n = h.GetCount();
695 i++;
696 if(pos == 0 && sz == n)
697 r.Cat(s, n);
698 else
699 r.Cat(FromUtf8(s, n).Mid((int)pos, sz).ToString());
700 size -= sz;
701 if(size == 0) break;
702 #ifdef PLATFORM_WIN32
703 r.Cat('\r');
704 #endif
705 r.Cat('\n');
706 size--;
707 if(size == 0) break;
708 pos = 0;
709 }
710 return String(r);
711 }
712 return FromUnicode(GetW(pos, size), charset);
713 }
714
GetChar(int64 pos) const715 int TextCtrl::GetChar(int64 pos) const {
716 if(pos < 0 || pos >= GetLength64())
717 return 0;
718 int i = GetLinePos64(pos);
719 WString ln = GetWLine(i);
720 int c = ln.GetLength() == pos ? '\n' : ln[(int)pos];
721 return c;
722 }
723
GetLinePos32(int & pos) const724 int TextCtrl::GetLinePos32(int& pos) const
725 {
726 int64 p = pos;
727 int l = GetLinePos64(p);
728 pos = (int)p;
729 return l;
730 }
731
GetSelection32(int & l,int & h) const732 bool TextCtrl::GetSelection32(int& l, int& h) const
733 {
734 int64 ll, hh;
735 bool b = GetSelection(ll, hh);
736 if(hh >= INT_MAX) {
737 l = h = (int)cursor;
738 return false;
739 }
740 l = (int)ll;
741 h = (int)hh;
742 return b;
743 }
744
GetCursor32() const745 int TextCtrl::GetCursor32() const
746 {
747 int64 h = GetCursor64();
748 return h < INT_MAX ? (int)h : 0;
749 }
750
GetLength32() const751 int TextCtrl::GetLength32() const
752 {
753 int64 h = GetLength64();
754 return h < INT_MAX ? (int)h : 0;
755 }
756
Insert0(int pos,const WString & txt)757 int TextCtrl::Insert0(int pos, const WString& txt) { // TODO: Do this with utf8
758 GuiLock __;
759 int inspos = pos;
760 PreInsert(inspos, txt);
761 if(pos < cpos)
762 cpos = cline = 0;
763 int i = GetLinePos32(pos);
764 DirtyFrom(i);
765 int size = 0;
766
767 WStringBuffer lnb;
768 Vector<WString> iln;
769 const wchar *s = txt;
770 while(s < txt.End())
771 if(*s >= ' ') {
772 const wchar *b = s;
773 while(*s >= ' ') // txt is zero teminated...
774 s++;
775 int sz = int(s - b);
776 lnb.Cat(b, sz);
777 size += sz;
778 }
779 else
780 if(*s == '\t') {
781 lnb.Cat(*s);
782 size++;
783 s++;
784 }
785 else
786 if(*s == '\n') {
787 iln.Add(lnb);
788 size++;
789 lnb.Clear();
790 s++;
791 }
792 else
793 s++;
794 WString ln = lnb;
795 WString l = GetWLine(i);
796 if(iln.GetCount()) {
797 iln[0] = l.Mid(0, pos) + iln[0];
798 ln.Cat(l.Mid(pos));
799 SetLine(i, ln);
800 InvalidateLine(i);
801 LineInsert(i, iln.GetCount());
802 for(int j = 0; j < iln.GetCount(); j++)
803 SetLine(i + j, iln[j]);
804 InsertLines(i, iln.GetCount());
805 Refresh();
806 }
807 else {
808 SetLine(i, l.Mid(0, pos) + ln + l.Mid(pos));
809 InvalidateLine(i);
810 RefreshLine(i);
811 }
812 total += size;
813 SetSb();
814 Update();
815 ClearSelection();
816 PostInsert(inspos, txt);
817 return size;
818 }
819
Remove0(int pos,int size)820 void TextCtrl::Remove0(int pos, int size) {
821 GuiLock __;
822 int rmpos = pos, rmsize = size;
823 PreRemove(rmpos, rmsize);
824 total -= size;
825 if(pos < cpos)
826 cpos = cline = 0;
827 int i = GetLinePos32(pos);
828 DirtyFrom(i);
829 WString ln = GetWLine(i);
830 int sz = min(LimitSize(ln.GetLength() - pos), size);
831 ln.Remove(pos, sz);
832 size -= sz;
833 SetLine(i, ln);
834 if(size == 0) {
835 InvalidateLine(i);
836 RefreshLine(i);
837 }
838 else {
839 size--;
840 int j = i + 1;
841 for(;;) {
842 int sz = GetLineLength(j) + 1;
843 if(sz > size) break;
844 j++;
845 size -= sz;
846 }
847 WString p1 = GetWLine(i);
848 WString p2 = GetWLine(j);
849 p1.Insert(p1.GetLength(), p2.Mid(size, p2.GetLength() - size));
850 SetLine(i, p1);
851 LineRemove(i + 1, j - i);
852 RemoveLines(i + 1, j - i);
853 InvalidateLine(i);
854 Refresh();
855 }
856 Update();
857 ClearSelection();
858 PostRemove(rmpos, rmsize);
859 SetSb();
860 }
861
Undodo()862 void TextCtrl::Undodo()
863 {
864 while(undo.GetCount() > undosteps)
865 undo.DropHead();
866 redo.Clear();
867 }
868
NextUndo()869 void TextCtrl::NextUndo()
870 {
871 undoserial += incundoserial;
872 incundoserial = false;
873 }
874
IncDirty()875 void TextCtrl::IncDirty() {
876 dirty++;
877 if(dirty == 0 || dirty == 1)
878 {
879 if(dirty)
880 SetModify();
881 else
882 ClearModify();
883 WhenState();
884 }
885 }
886
DecDirty()887 void TextCtrl::DecDirty() {
888 dirty--;
889 if(dirty == 0 || dirty == -1)
890 {
891 if(dirty)
892 SetModify();
893 else
894 ClearModify();
895 WhenState();
896 }
897 }
898
InsertU(int pos,const WString & txt,bool typing)899 int TextCtrl::InsertU(int pos, const WString& txt, bool typing) {
900 int sz = Insert0(pos, txt);
901 if(undosteps) {
902 if(undo.GetCount() > 1 && typing && *txt != '\n' && IsDirty()) {
903 UndoRec& u = undo.Tail();
904 if(u.typing && u.pos + u.size == pos) {
905 u.size += txt.GetLength();
906 return sz;
907 }
908 }
909 UndoRec& u = undo.AddTail();
910 incundoserial = true;
911 IncDirty();
912 u.serial = undoserial;
913 u.pos = pos;
914 u.size = sz;
915 u.typing = typing;
916 }
917 return sz;
918 }
919
RemoveU(int pos,int size)920 void TextCtrl::RemoveU(int pos, int size) {
921 if(size + pos > total)
922 size = int(total - pos);
923 if(size <= 0) return;
924 if(undosteps) {
925 UndoRec& u = undo.AddTail();
926 incundoserial = true;
927 IncDirty();
928 u.serial = undoserial;
929 u.pos = pos;
930 u.size = 0;
931 u.SetText(Get(pos, size, CHARSET_UTF8));
932 u.typing = false;
933 }
934 Remove0(pos, size);
935 }
936
Insert(int pos,const WString & _txt,bool typing)937 int TextCtrl::Insert(int pos, const WString& _txt, bool typing) {
938 if(pos + _txt.GetCount() > max_total)
939 return 0;
940 WString txt = _txt;
941 if(!IsUnicodeCharset(charset))
942 for(int i = 0; i < txt.GetCount(); i++)
943 if(FromUnicode(txt[i], charset) == DEFAULTCHAR)
944 txt.Set(i, '?');
945 int sz = InsertU(pos, txt, typing);
946 Undodo();
947 return sz;
948 }
949
Insert(int pos,const String & txt,byte charset)950 int TextCtrl::Insert(int pos, const String& txt, byte charset)
951 {
952 return Insert(pos, ToUnicode(txt, charset), false);
953 }
954
Remove(int pos,int size)955 void TextCtrl::Remove(int pos, int size) {
956 RemoveU(pos, size);
957 Undodo();
958 }
959
Undo()960 void TextCtrl::Undo() {
961 if(undo.IsEmpty()) return;
962 undo_op = true;
963 int nc = 0;
964 int s = undo.Tail().serial;
965 while(undo.GetCount()) {
966 const UndoRec& u = undo.Tail();
967 if(u.serial != s)
968 break;
969 UndoRec& r = redo.AddTail();
970 r.serial = s;
971 r.typing = false;
972 nc = r.pos = u.pos;
973 CachePos(r.pos);
974 if(u.size) {
975 r.size = 0;
976 r.SetText(Get(u.pos, u.size, CHARSET_UTF8));
977 Remove0(u.pos, u.size);
978 }
979 else {
980 WString text = FromUtf8(u.GetText());
981 r.size = Insert0(u.pos, text);
982 nc += r.size;
983 }
984 undo.DropTail();
985 DecDirty();
986 }
987 ClearSelection();
988 PlaceCaret(nc, false);
989 Action();
990 undo_op = false;
991 }
992
Redo()993 void TextCtrl::Redo() {
994 if(!redo.GetCount()) return;
995 NextUndo();
996 int s = redo.Tail().serial;
997 int nc = 0;
998 while(redo.GetCount()) {
999 const UndoRec& r = redo.Tail();
1000 if(r.serial != s)
1001 break;
1002 nc = r.pos + r.size;
1003 CachePos(r.pos);
1004 if(r.size)
1005 RemoveU(r.pos, r.size);
1006 else
1007 nc += InsertU(r.pos, FromUtf8(r.GetText()));
1008 redo.DropTail();
1009 IncDirty();
1010 }
1011 ClearSelection();
1012 PlaceCaret(nc, false);
1013 Action();
1014 }
1015
ClearSelection()1016 void TextCtrl::ClearSelection() {
1017 if(anchor >= 0) {
1018 anchor = -1;
1019 Refresh();
1020 SelectionChanged();
1021 WhenSel();
1022 }
1023 }
1024
SetSelection(int64 l,int64 h)1025 void TextCtrl::SetSelection(int64 l, int64 h) {
1026 if(l != h) {
1027 PlaceCaret(minmax(l, (int64)0, total), false);
1028 PlaceCaret(minmax(h, (int64)0, total), true);
1029 }
1030 else
1031 SetCursor(l);
1032 }
1033
GetSelection(int64 & l,int64 & h) const1034 bool TextCtrl::GetSelection(int64& l, int64& h) const {
1035 if(anchor < 0 || anchor == cursor) {
1036 l = h = cursor;
1037 return false;
1038 }
1039 else {
1040 l = min(anchor, cursor);
1041 h = max(anchor, cursor);
1042 return !rectsel;
1043 }
1044 }
1045
GetSelection(byte charset) const1046 String TextCtrl::GetSelection(byte charset) const {
1047 int64 l, h;
1048 if(GetSelection(l, h))
1049 return Get(l, LimitSize(h - l), charset);
1050 return String();
1051 }
1052
GetSelectionW() const1053 WString TextCtrl::GetSelectionW() const {
1054 int64 l, h;
1055 if(GetSelection(l, h))
1056 return GetW(l, LimitSize(h - l));
1057 return WString();
1058 }
1059
RemoveSelection()1060 bool TextCtrl::RemoveSelection() {
1061 int64 l, h;
1062 if(anchor < 0) return false;
1063 if(IsRectSelection())
1064 l = RemoveRectSelection();
1065 else {
1066 if(!GetSelection(l, h))
1067 return false;
1068 Remove((int)l, int(h - l));
1069 }
1070 anchor = -1;
1071 Refresh();
1072 PlaceCaret(l);
1073 Action();
1074 return true;
1075 }
1076
RefreshLines(int l1,int l2)1077 void TextCtrl::RefreshLines(int l1, int l2) {
1078 int h = max(l1, l2);
1079 for(int i = min(l1, l2); i <= h; i++)
1080 RefreshLine(i);
1081 }
1082
Cut()1083 void TextCtrl::Cut() {
1084 if(!IsReadOnly() && IsAnySelection()) {
1085 Copy();
1086 RemoveSelection();
1087 }
1088 }
1089
Copy()1090 void TextCtrl::Copy() {
1091 int64 l, h;
1092 if(!GetSelection(l, h) && !IsAnySelection()) {
1093 int i = GetLine(cursor);
1094 l = GetPos64(i);
1095 h = l + GetLineLength(i) + 1;
1096 }
1097 WString txt;
1098 if(IsRectSelection())
1099 txt = CopyRectSelection();
1100 else
1101 txt = GetW(l, LimitSize(h - l));
1102 ClearClipboard();
1103 AppendClipboardUnicodeText(txt);
1104 AppendClipboardText(txt.ToString());
1105 }
1106
SelectAll()1107 void TextCtrl::SelectAll() {
1108 SetSelection();
1109 }
1110
Paste(const WString & text)1111 int TextCtrl::Paste(const WString& text) {
1112 if(IsReadOnly()) return 0;
1113 int n;
1114 if(IsRectSelection())
1115 n = PasteRectSelection(text);
1116 else {
1117 RemoveSelection();
1118 n = Insert((int)cursor, text);
1119 PlaceCaret(cursor + n);
1120 }
1121 Refresh();
1122 return n;
1123 }
1124
GetPasteText()1125 String TextCtrl::GetPasteText()
1126 {
1127 return Null;
1128 }
1129
Paste()1130 void TextCtrl::Paste() {
1131 WString w = ReadClipboardUnicodeText();
1132 if(w.IsEmpty())
1133 w = ReadClipboardText().ToWString();
1134 if(w.IsEmpty())
1135 w = GetPasteText().ToWString();
1136 Paste(w);
1137 Action();
1138 }
1139
StdBar(Bar & menu)1140 void TextCtrl::StdBar(Bar& menu) {
1141 NextUndo();
1142 if(undosteps) {
1143 menu.Add(undo.GetCount() && IsEditable(), t_("Undo"), CtrlImg::undo(), THISBACK(Undo))
1144 .Key(K_ALT_BACKSPACE)
1145 .Key(K_CTRL_Z);
1146 menu.Add(redo.GetCount() && IsEditable(), t_("Redo"), CtrlImg::redo(), THISBACK(Redo))
1147 .Key(K_SHIFT|K_ALT_BACKSPACE)
1148 .Key(K_SHIFT_CTRL_Z);
1149 menu.Separator();
1150 }
1151 menu.Add(IsEditable() && IsAnySelection(),
1152 t_("Cut"), CtrlImg::cut(), THISBACK(Cut))
1153 .Key(K_SHIFT_DELETE)
1154 .Key(K_CTRL_X);
1155 menu.Add(IsAnySelection(),
1156 t_("Copy"), CtrlImg::copy(), THISBACK(Copy))
1157 .Key(K_CTRL_INSERT)
1158 .Key(K_CTRL_C);
1159 bool canpaste = IsEditable() && IsClipboardAvailableText();
1160 menu.Add(canpaste,
1161 t_("Paste"), CtrlImg::paste(), THISBACK(DoPaste))
1162 .Key(K_SHIFT_INSERT)
1163 .Key(K_CTRL_V);
1164 LineEdit *e = dynamic_cast<LineEdit *>(this);
1165 if(e) {
1166 menu.Add(canpaste,
1167 t_("Paste in column"), CtrlImg::paste_vert(), callback(e, &LineEdit::DoPasteColumn))
1168 .Key(K_ALT_V|K_SHIFT);
1169 menu.Add(e->IsRectSelection(),
1170 t_("Sort"), CtrlImg::sort(), callback(e, &LineEdit::Sort));
1171 }
1172 menu.Add(IsEditable() && IsAnySelection(),
1173 t_("Erase"), CtrlImg::remove(), THISBACK(DoRemoveSelection))
1174 .Key(K_DELETE);
1175 menu.Separator();
1176 menu.Add(GetLength64(),
1177 t_("Select all"), CtrlImg::select_all(), THISBACK(SelectAll))
1178 .Key(K_CTRL_A);
1179 }
1180
GetSelectionData(const String & fmt) const1181 String TextCtrl::GetSelectionData(const String& fmt) const
1182 {
1183 return GetTextClip(GetSelectionW(), fmt);
1184 }
1185
1186 }
1187