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 = &in;
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