1 #include "CtrlLib.h"
2
3 namespace Upp {
4
5 #define LLOG(x) // LOG(x)
6 #define LTIMING(x) // DTIMING(x)
7
LineEdit()8 LineEdit::LineEdit() {
9 isdrag = false;
10 nohbar = false;
11 showtabs = false;
12 tabsize = 4;
13 font = CourierZ(12);
14 SetFrame(ViewFrame());
15 sb.NoBox();
16 AddFrame(sb);
17 sb.WhenScroll = THISBACK(Scroll);
18 cutline = true;
19 bordercolumn = -1;
20 bordercolor = Null;
21 overwrite = false;
22 filter = NULL;
23 showspaces = false;
24 showlines = false;
25 showreadonly = true;
26 dorectsel = false;
27 hline = vline = Null;
28 vlinex = 0;
29 warnwhitespace = false;
30 }
31
~LineEdit()32 LineEdit::~LineEdit() {}
33
MouseWheel(Point,int zdelta,dword keyflags)34 void LineEdit::MouseWheel(Point, int zdelta, dword keyflags) {
35 if(keyflags & K_SHIFT)
36 sb.WheelX(zdelta);
37 else
38 sb.WheelY(zdelta);
39 }
40
Clear()41 void LineEdit::Clear() {
42 gcolumn = 0;
43 TextCtrl::Clear();
44 sb.SetTotal(0, 0);
45 sb.Set(0, 0);
46 NewScrollPos();
47 PlaceCaret(0, false);
48 }
49
TabSize(int n)50 LineEdit& LineEdit::TabSize(int n) {
51 tabsize = n;
52 PlaceCaret0(GetColumnLine(cursor));
53 Refresh();
54 return *this;
55 }
56
BorderColumn(int col,Color c)57 LineEdit& LineEdit::BorderColumn(int col, Color c)
58 {
59 bordercolumn = col;
60 bordercolor = c;
61 Refresh();
62 return *this;
63 }
64
SetFont(Font f)65 LineEdit& LineEdit::SetFont(Font f) {
66 font = f;
67 Layout();
68 TabSize(tabsize);
69 SetSb();
70 return *this;
71 }
72
GetFontSize() const73 Size LineEdit::GetFontSize() const {
74 FontInfo fi = font.Info();
75 return Size(max(fi['M'], fi['W']), fi.GetHeight());
76 }
77
SetRectSelection(int64 anchor,int64 cursor)78 void LineEdit::SetRectSelection(int64 anchor, int64 cursor)
79 {
80 dorectsel = true;
81 SetSelection(anchor, cursor);
82 dorectsel = false;
83 }
84
SetRectSelection(const Rect & rect)85 void LineEdit::SetRectSelection(const Rect& rect)
86 {
87 SetRectSelection(GetGPos(rect.top, rect.left), GetGPos(rect.bottom, rect.right));
88 }
89
GetRectSelection() const90 Rect LineEdit::GetRectSelection() const
91 {
92 if(IsRectSelection()) {
93 int64 sell, selh;
94 GetSelection(sell, selh);
95 Rect r(GetColumnLine(sell), GetColumnLine(selh));
96 if(r.left > r.right)
97 Swap(r.left, r.right);
98 return r;
99 }
100 return Null;
101 }
102
GetRectSelection(const Rect & rect,int line,int64 & l,int64 & h)103 bool LineEdit::GetRectSelection(const Rect& rect, int line, int64& l, int64& h)
104 {
105 if(line >= rect.top && line <= rect.bottom) {
106 l = GetGPos(line, rect.left);
107 h = GetGPos(line, rect.right);
108 return true;
109 }
110 l = h = 0;
111 return false;
112 }
113
RemoveRectSelection()114 int LineEdit::RemoveRectSelection()
115 {
116 Rect rect = GetRectSelection();
117 WString txt;
118 for(int i = rect.top; i <= rect.bottom; i++) {
119 int64 l, h;
120 CacheLinePos(i);
121 GetRectSelection(rect, i, l, h);
122 WString s = GetWLine(i);
123 s.Remove(int(l - GetPos64(i)), int(h - l));
124 txt.Cat(s);
125 txt.Cat('\n');
126 }
127 int l = GetPos32(rect.top);
128 int h = GetPos32(rect.bottom) + GetLineLength(rect.bottom);
129 if(h < GetLength32())
130 h++;
131 Remove((int)l, int(h - l));
132 Insert((int)l, txt);
133 return (int)GetGPos(rect.bottom, rect.left);
134 }
135
CopyRectSelection()136 WString LineEdit::CopyRectSelection()
137 {
138 WString txt;
139 Rect rect = GetRectSelection();
140 for(int i = rect.top; i <= rect.bottom && txt.GetCount() < max_total; i++) {
141 int64 l, h;
142 CacheLinePos(i);
143 int64 pos = GetPos64(i);
144 GetRectSelection(rect, i, l, h);
145 txt.Cat(GetWLine(i).Mid(int(l - pos), int(h - l)));
146 #ifdef PLATFORM_WIN32
147 txt.Cat('\r');
148 #endif
149 txt.Cat('\n');
150 }
151 return txt;
152 }
153
PasteRectSelection(const WString & s)154 int LineEdit::PasteRectSelection(const WString& s)
155 {
156 Vector<WString> cl = Split(s, '\n', false);
157 Rect rect = GetRectSelection();
158 int64 pos = cursor;
159 int n = 0;
160 for(int i = 0; i < cl.GetCount() && rect.top + i <= rect.bottom; i++) {
161 int64 l, h;
162 CacheLinePos(i);
163 GetRectSelection(rect, i + rect.top, l, h);
164 Remove((int)l, int(h - l));
165 int nn = Insert((int)l, cl[i]);
166 n += nn;
167 pos = l + nn;
168 }
169 PlaceCaret(pos);
170 return n;
171 }
172
PasteColumn(const WString & text)173 void LineEdit::PasteColumn(const WString& text)
174 {
175 Vector<WString> cl = Split(text, '\n', false);
176 if(cl.GetCount() && cl.Top().IsEmpty())
177 cl.Drop();
178 if(cl.GetCount() == 0)
179 return;
180 int pos;
181 if(IsRectSelection()) {
182 Rect t = GetRectSelection();
183 RemoveSelection();
184 Point p = t.TopLeft();
185 pos = (int)cursor;
186 for(int i = 0; i < t.bottom - t.top + 1; i++) {
187 CacheLinePos(i + p.y);
188 int l = (int)GetGPos(i + p.y, p.x);
189 pos = l + Insert(l, cl[i % cl.GetCount()]);
190 }
191 }
192 else {
193 RemoveSelection();
194 Point p = GetColumnLine(cursor);
195 pos = (int)cursor;
196 for(int i = 0; i < cl.GetCount(); i++) {
197 CacheLinePos(i + p.y);
198 int li = p.y + i;
199 if(li < GetLineCount()) {
200 int l = (int)GetGPos(i + p.y, p.x);
201 pos = l + Insert(l, cl[i]);
202 }
203 else {
204 Insert(GetLength32(), cl[i] + "\n");
205 pos = GetLength32();
206 }
207 }
208 }
209 PlaceCaret(pos);
210 }
211
PasteColumn()212 void LineEdit::PasteColumn()
213 {
214 WString w = ReadClipboardUnicodeText();
215 if(w.IsEmpty())
216 w = ReadClipboardText().ToWString();
217 PasteColumn(w);
218 Action();
219 }
220
sSortLineOrder(const WString & l1,const WString & l2)221 bool sSortLineOrder(const WString& l1, const WString& l2)
222 {
223 return ToUpper(l1) < ToUpper(l2);
224 }
225
Sort()226 void LineEdit::Sort()
227 {
228 if(!IsRectSelection())
229 return;
230 CopyRectSelection();
231 Rect rect = GetRectSelection();
232 Vector<WString> key;
233 Vector<WString> ln;
234 for(int i = rect.top; i <= rect.bottom; i++) {
235 int64 l, h;
236 GetRectSelection(rect, i, l, h);
237 key.Add(GetW((int)l, int(h - l)));
238 ln.Add(GetWLine(i));
239 }
240 int sell = GetPos32(rect.top);
241 int selh = rect.bottom + 1 < GetLineCount() ? GetPos32(rect.bottom + 1) : GetLength32();
242 IndexSort(key, ln, sSortLineOrder);
243 Remove(sell, selh - sell);
244 Insert(sell, Join(ln, "\n"));
245 }
246
247 class sOptimizedRectRenderer {
248 Draw& w;
249 Rect cr;
250 Color color;
251
252 public:
253 void DrawRect(const Rect& r, Color color);
DrawRect(int x,int y,int cx,int cy,Color color)254 void DrawRect(int x, int y, int cx, int cy, Color color) { DrawRect(RectC(x, y, cx, cy), color); }
255 void Flush();
256
sOptimizedRectRenderer(Draw & w)257 sOptimizedRectRenderer(Draw& w) : w(w) { cr = Null; color = Null; }
~sOptimizedRectRenderer()258 ~sOptimizedRectRenderer() { Flush(); }
259 };
260
Flush()261 void sOptimizedRectRenderer::Flush()
262 {
263 LTIMING("RectFlush");
264 if(!IsNull(cr)) {
265 w.DrawRect(cr, color);
266 cr = Null;
267 }
268 }
269
DrawRect(const Rect & r,Color c)270 void sOptimizedRectRenderer::DrawRect(const Rect& r, Color c)
271 {
272 LTIMING("DrawRect");
273 if(cr.top == r.top && cr.bottom == r.bottom && cr.right == r.left && c == color) {
274 cr.right = r.right;
275 return;
276 }
277 Flush();
278 cr = r;
279 color = c;
280 }
281
282 #if 1 // This is a more ambitious approach combining non-continual chunks of text, it is a bit faster...
283 class sOptimizedTextRenderer {
284 Draw& w;
285 int y;
286 struct Chrs : Moveable<Chrs> {
287 Vector<int> x;
288 Vector<int> width;
289 WString text;
290 };
291 VectorMap< Tuple2<Font, Color>, Chrs > cache;
292
293 public:
294 void DrawChar(int x, int y, int chr, int width, Font afont, Color acolor);
295 void Flush();
296
sOptimizedTextRenderer(Draw & w)297 sOptimizedTextRenderer(Draw& w) : w(w) { y = Null; }
~sOptimizedTextRenderer()298 ~sOptimizedTextRenderer() { Flush(); }
299 };
300
Flush()301 void sOptimizedTextRenderer::Flush()
302 {
303 if(cache.GetCount() == 0)
304 return;
305 LTIMING("Flush");
306 for(int i = 0; i < cache.GetCount(); i++) {
307 Chrs& c = cache[i];
308 if(c.x.GetCount()) {
309 Tuple2<Font, Color> fc = cache.GetKey(i);
310 int x = c.x[0];
311 for(int i = 0; i < c.x.GetCount() - 1; i++)
312 c.x[i] = c.x[i + 1] - c.x[i];
313 c.x.Top() = c.width.Top();
314 w.DrawText(x, y, c.text, fc.a, fc.b, c.x);
315 }
316 }
317 cache.Clear();
318 }
319
DrawChar(int x,int _y,int chr,int width,Font font,Color color)320 void sOptimizedTextRenderer::DrawChar(int x, int _y, int chr, int width, Font font, Color color)
321 {
322 LTIMING("DrawChar");
323 if(y != _y) {
324 Flush();
325 y = _y;
326 }
327 Chrs *c;
328 {
329 LTIMING("Map");
330 c = &cache.GetAdd(MakeTuple(font, color));
331 }
332 if(c->x.GetCount() && c->x.Top() > x) {
333 Flush();
334 c = &cache.GetAdd(MakeTuple(font, color));
335 }
336 c->text.Cat(chr);
337 c->width.Add(width);
338 c->x.Add(x);
339 }
340 #else
341 class sOptimizedTextRenderer {
342 Draw& w;
343 int x, y;
344 int xpos;
345 Vector<int> dx;
346 WString text;
347 Font font;
348 Color color;
349
350 public:
351 void DrawChar(int x, int y, int chr, int width, Font afont, Color acolor);
352 void Flush();
353
sOptimizedTextRenderer(Draw & w)354 sOptimizedTextRenderer(Draw& w) : w(w) { y = Null; }
~sOptimizedTextRenderer()355 ~sOptimizedTextRenderer() { Flush(); }
356 };
357
Flush()358 void sOptimizedTextRenderer::Flush()
359 {
360 if(text.GetCount() == 0)
361 return;
362 LTIMING("Flush");
363 w.DrawText(x, y, text, font, color, dx);
364 y = Null;
365 text.Clear();
366 dx.Clear();
367 }
368
DrawChar(int _x,int _y,int chr,int width,Font _font,Color _color)369 void sOptimizedTextRenderer::DrawChar(int _x, int _y, int chr, int width, Font _font, Color _color)
370 {
371 LTIMING("DrawChar");
372 if(y == _y && font == _font && color == _color && dx.GetCount() && _x >= xpos - dx.Top())
373 dx.Top() += _x - xpos;
374 else {
375 LTIMING("DrawChar flush");
376 Flush();
377 x = _x;
378 y = _y;
379 font = _font;
380 color = _color;
381 }
382 dx.Add(width);
383 text.Cat(chr);
384 xpos = _x + width;
385 }
386 #endif
387
Paint0(Draw & w)388 void LineEdit::Paint0(Draw& w) {
389 LTIMING("LineEdit::Paint0");
390 GuiLock __;
391 int64 sell, selh;
392 GetSelection(sell, selh);
393 if(!IsEnabled())
394 sell = selh = 0;
395 Rect rect(0, 0, 0, 0);
396 bool rectsel = IsRectSelection();
397 if(rectsel)
398 rect = GetRectSelection();
399 Size sz = GetSize();
400 Size fsz = GetFontSize();
401 Point sc = sb;
402 int ll = min(GetLineCount(), sz.cy / fsz.cy + sc.y + 1);
403 int y = 0;
404 sc.y = minmax(sc.y, 0, GetLineCount() - 1);
405 cpos = GetPos64(sc.y);
406 cline = sc.y;
407 sell -= cpos;
408 selh -= cpos;
409 int64 pos = cpos;
410 int fascent = font.Info().GetAscent();
411 int cursorline = GetLine(cursor);
412 Highlight ih;
413 ih.ink = color[IsShowEnabled() ? INK_NORMAL : INK_DISABLED];
414 ih.paper = color[IsReadOnly() && showreadonly || !IsShowEnabled() ? PAPER_READONLY : PAPER_NORMAL];
415 if(nobg)
416 ih.paper = Null;
417 ih.font = font;
418 ih.chr = 0;
419 for(int i = sc.y; i < ll; i++) {
420 Color showcolor = color[WHITESPACE];
421 WString tx = GetWLine(i);
422 bool warn_whitespace = false;
423 if(warnwhitespace && !IsSelection()) {
424 int64 pos = GetCursor64();
425 int linei = GetLinePos64(pos);
426 if(linei != i || pos < tx.GetCount()) {
427 int wkind = 0;
428 bool empty = true;
429 for(const wchar *s = tx; *s; s++) {
430 if(*s == '\t') {
431 if(wkind == ' ') {
432 warn_whitespace = true;
433 break;
434 }
435 wkind = '\t';
436 }
437 else
438 if(*s == ' ')
439 wkind = ' ';
440 else
441 if(*s > ' ') {
442 empty = false;
443 wkind = 0;
444 }
445 }
446 if(wkind && !empty)
447 warn_whitespace = true;
448 if(warn_whitespace)
449 showcolor = color[WARN_WHITESPACE];
450 }
451 }
452 bool do_highlight = tx.GetCount() < 100000;
453 int len = tx.GetLength();
454 if(w.IsPainting(0, y, sz.cx, fsz.cy)) {
455 LTIMING("PaintLine");
456 Vector<Highlight> hl;
457 int ln;
458 if(do_highlight) {
459 hl.SetCount(len + 1, ih);
460 for(int q = 0; q < tx.GetCount(); q++)
461 hl[q].chr = tx[q];
462 LTIMING("HighlightLine");
463 HighlightLine(i, hl, pos);
464 ln = hl.GetCount() - 1;
465 }
466 else
467 ln = tx.GetCount();
468 int lgp = -1;
469 for(int pass = 0; pass < 3; pass++) {
470 int gp = 0;
471 int scx = fsz.cx * sc.x;
472 sOptimizedRectRenderer rw(w);
473 if(ln >= 0) {
474 int q = 0;
475 int x = 0;
476 int scx2 = scx - max(2, tabsize) * fsz.cx;
477 while(q < ln && x < scx2) { // Skip part before left border
478 wchar chr = do_highlight ? hl[q++].chr : tx[q++];
479 if(chr == '\t') {
480 gp = (gp + tabsize) / tabsize * tabsize;
481 x = fsz.cx * gp;
482 }
483 else
484 if(IsDoubleWidth(chr)) {
485 x += 2 * fsz.cx;
486 gp += 2;
487 }
488 else {
489 x += fsz.cx;
490 gp++;
491 }
492 }
493 sOptimizedTextRenderer tw(w);
494 Highlight lastHighlight;
495 while(q < ln) {
496 if(q == tx.GetCount())
497 lgp = gp;
498 Highlight h;
499 if(do_highlight)
500 h = hl[q];
501 else {
502 h = ih;
503 h.chr = tx[q];
504 }
505 int pos = min(q, len); // Highligting can add chars at the end of line
506 if(rectsel ? i >= rect.top && i <= rect.bottom && gp >= rect.left && gp < rect.right
507 : pos >= sell && pos < selh) {
508 h.paper = color[PAPER_SELECTED];
509 h.ink = color[INK_SELECTED];
510 }
511 int x = gp * fsz.cx - scx;
512 bool cjk = IsDoubleWidth(h.chr);
513 int xx = x + (gp + 1 + cjk) * fsz.cx;
514 if(h.chr == '\t') {
515 int ngp = (gp + tabsize) / tabsize * tabsize;
516 int l = ngp - gp;
517 LLOG("Highlight -> tab[" << q << "] paper = " << h.paper);
518 if(x >= -fsz.cy * tabsize) {
519 if(pass == 0) {
520 rw.DrawRect(x, y, fsz.cx * l, fsz.cy, h.paper);
521 if((showtabs || warn_whitespace) &&
522 h.paper != SColorHighlight && q < tx.GetLength()) {
523 rw.DrawRect(x + 2, y + fsz.cy / 2, l * fsz.cx - 4, 1, showcolor);
524 rw.DrawRect(ngp * fsz.cx - scx - 3, y + 3, 1, fsz.cy - 6, showcolor);
525 }
526 if(bordercolumn > 0 && bordercolumn >= gp && bordercolumn < gp + l)
527 rw.DrawRect((bordercolumn - sc.x) * fsz.cx, y, 1, fsz.cy, bordercolor);
528 }
529 if(pass == 2) // resolve underlined tabs
530 tw.DrawChar(x, y, ' ', fsz.cx * l, h.font, h.ink);
531 }
532 q++;
533 gp = ngp;
534 }
535 else
536 if(h.chr == ' ') {
537 LLOG("Highlight -> space[" << q << "] paper = " << h.paper);
538 if(x >= -fsz.cy) {
539 if(pass == 0) {
540 rw.DrawRect(x, y, fsz.cx, fsz.cy, h.paper);
541 if((showspaces || warn_whitespace)
542 && h.paper != SColorHighlight && q < tx.GetLength()) {
543 int n = fsz.cy / 10 + 1;
544 rw.DrawRect(x + fsz.cx / 2, y + fsz.cy / 2, n, n, showcolor);
545 }
546 if(bordercolumn > 0 && bordercolumn >= gp && bordercolumn < gp + 1)
547 rw.DrawRect((bordercolumn - sc.x) * fsz.cx, y, 1, fsz.cy, bordercolor);
548 }
549 if(pass == 2) // resolve underlined spaces
550 tw.DrawChar(x, y, ' ', fsz.cx, h.font, h.ink);
551 }
552 q++;
553 gp++;
554 }
555 else {
556 LLOG("Highlight -> paper[" << q << "] = " << h.paper);
557 if(max(x, 0) < min(xx, sz.cx) && fsz.cx >= -fsz.cy) {
558 if(pass == 0) {
559 rw.DrawRect(x, y, (cjk + 1) * fsz.cx, fsz.cy, h.paper);
560 if(bordercolumn > 0 && bordercolumn >= gp && bordercolumn < gp + 1 + cjk)
561 rw.DrawRect((bordercolumn - sc.x) * fsz.cx, y, 1, fsz.cy, bordercolor);
562 }
563 if(pass == 1 && (h.flags & SPELLERROR))
564 rw.DrawRect(x, max(y, y + fsz.cy - Zy(1)), (cjk + 1) * fsz.cx, Zy(1), LtRed());
565 if(pass == 2)
566 tw.DrawChar(x + (h.flags & SHIFT_L ? -fsz.cx / 6 : h.flags & SHIFT_R ? fsz.cx / 6 : 0),
567 y + fascent - h.font.GetAscent(),
568 h.chr, (cjk + 1) * fsz.cx, h.font, h.ink);
569 }
570 q++;
571 gp += 1 + cjk;
572 if(x > sz.cx)
573 break;
574 }
575 }
576 }
577 if(pass == 0) {
578 int gpx = gp * fsz.cx - scx;
579 rw.DrawRect(gpx, y, sz.cx - gpx, fsz.cy,
580 !rectsel && sell <= len && len < selh ? color[PAPER_SELECTED]
581 : (do_highlight ? hl.Top() : ih).paper);
582 if(bordercolumn > 0 && bordercolumn >= gp)
583 rw.DrawRect((bordercolumn - sc.x) * fsz.cx, y, 1, fsz.cy, bordercolor);
584 if((showlines || warn_whitespace)) {
585 int yy = 2 * fsz.cy / 3;
586 int x = (lgp >= 0 ? lgp : gp) * fsz.cx - scx;
587 rw.DrawRect(x, y + yy, fsz.cx / 2, 1, showcolor);
588 if(fsz.cx > 2)
589 rw.DrawRect(x + 1, y + yy - 1, 1, 3, showcolor);
590 if(fsz.cx > 5)
591 rw.DrawRect(x + 2, y + yy - 2, 1, 5, showcolor);
592 rw.DrawRect(x + fsz.cx / 2, y + yy / 2, 1, yy - yy / 2, showcolor);
593 }
594 if(sell == selh) {
595 if(!IsNull(hline) && i == cursorline) {
596 rw.DrawRect(0, y, sz.cx, 1, hline);
597 rw.DrawRect(0, y + fsz.cy - 1, sz.cx, 1, hline);
598 }
599 if(!IsNull(vline))
600 rw.DrawRect(caretpos.x, y, 1, fsz.cy, vline);
601 }
602 if(rectsel && rect.left == rect.right && i >= rect.top && i <= rect.bottom)
603 rw.DrawRect(rect.left * fsz.cx - scx, y, 2, fsz.cy, Blend(color[PAPER_SELECTED], color[PAPER_NORMAL]));
604 }
605 }
606 }
607 y += fsz.cy;
608 sell -= len + 1;
609 selh -= len + 1;
610 pos += len + 1;
611 }
612 w.DrawRect(0, y, sz.cx, sz.cy - y, color[IsReadOnly() && showreadonly || !IsShowEnabled() ? PAPER_READONLY : PAPER_NORMAL]);
613 DrawTiles(w, DropCaret(), CtrlImg::checkers());
614 vlinex = caretpos.x;
615 }
616
Paint(Draw & w)617 void LineEdit::Paint(Draw& w)
618 {
619 Paint0(w);
620 scroller.Set(sb);
621 }
622
623 struct LineEdit::RefreshDraw : public NilDraw {
624 Ctrl *ctrl;
625 bool (*chars)(int c);
626 Size fsz;
DrawTextOpUpp::LineEdit::RefreshDraw627 virtual void DrawTextOp(int x, int y, int angle, const wchar *text, Font,
628 Color, int n, const int *dx) {
629 if(dx)
630 while(n > 0) {
631 if((*chars)(*text))
632 ctrl->Refresh(x, y, fsz.cx, fsz.cy);
633 text++;
634 x += *dx++;
635 n--;
636 }
637 }
IsPaintingOpUpp::LineEdit::RefreshDraw638 bool IsPaintingOp(const Rect& r) const {
639 return true;
640 }
641 };
642
RefreshChars(bool (* chars)(int c))643 void LineEdit::RefreshChars(bool (*chars)(int c))
644 {
645 RefreshDraw rw;
646 rw.ctrl = this;
647 rw.fsz = GetFontSize();
648 rw.chars = chars;
649 Paint(rw);
650 }
651
Layout()652 void LineEdit::Layout() {
653 Size sz = sb.GetReducedViewSize();
654 if(nohbar || isdrag)
655 sz.cy = GetSize().cy;
656 sb.SetPage(sz / GetFontSize());
657 SetHBar();
658 }
659
GetGPos(int ln,int cl) const660 int64 LineEdit::GetGPos(int ln, int cl) const {
661 ln = minmax(ln, 0, GetLineCount() - 1);
662 String h = GetUtf8Line(ln);
663 const char *s = h.begin();
664 const char *e = h.end();
665 const char *b = s;
666 int gl = 0;
667 int wpos = 0;
668 while(s < e) {
669 if(*s == '\t')
670 gl = (gl + tabsize) / tabsize * tabsize;
671 else
672 if((byte)*s < 128)
673 gl++;
674 else {
675 WString txt = FromUtf8(s, int(e - s));
676 const wchar *b = txt;
677 const wchar *e = txt.End();
678 const wchar *s = b;
679 while(s < e) {
680 if(*s == '\t')
681 gl = (gl + tabsize) / tabsize * tabsize;
682 else
683 gl += 1 + IsDoubleWidth(*s);
684 if(cl < gl) break;
685 s++;
686 }
687 wpos = int(s - b);
688 break;
689 }
690 if(cl < gl) break;
691 s++;
692 }
693
694 return GetPos64(ln, int(s - b) + wpos);
695 }
696
GetColumnLine(int64 pos) const697 Point LineEdit::GetColumnLine(int64 pos) const {
698 Point p;
699 if(pos > GetLength64()) pos = GetLength64();
700 p.y = GetLinePos64(pos);
701 p.x = 0;
702 WString txt = GetWLine(p.y);
703 const wchar *s = txt;
704 while(pos--) {
705 if(*s == '\t')
706 p.x = (p.x + tabsize) / tabsize * tabsize;
707 else
708 p.x += 1 + IsDoubleWidth(*s);
709 s++;
710 }
711 return p;
712 }
713
GetIndexLine(int64 pos) const714 Point LineEdit::GetIndexLine(int64 pos) const
715 {
716 Point p;
717 if(pos > GetLength64()) pos = GetLength64();
718 p.y = GetLinePos64(pos);
719 p.x = minmax((int)pos, 0, GetLineLength(p.y));
720 return p;
721 }
722
GetIndexLinePos(Point pos) const723 int64 LineEdit::GetIndexLinePos(Point pos) const
724 {
725 if(pos.y < 0)
726 return 0;
727 if(pos.y >= GetLineCount())
728 return GetLength64();
729 return GetPos64(pos.y, minmax(pos.x, 0, GetLineLength(pos.y)));
730 }
731
RefreshLine(int i)732 void LineEdit::RefreshLine(int i) {
733 Size sz = GetSize();
734 int fcy = GetFontSize().cy;
735 Refresh(0, (i - sb.Get().y) * fcy, sz.cx, fcy);
736 }
737
GetLineScreenRect(int line) const738 Rect LineEdit::GetLineScreenRect(int line) const {
739 int fcy = GetFontSize().cy;
740 Rect r = RectC(0, (line - sb.Get().y) * fcy, GetSize().cx, fcy);
741 r.Offset(GetScreenView().TopLeft());
742 return r;
743 }
744
SetSb()745 void LineEdit::SetSb() {
746 sb.SetTotalY(GetLineCount());
747 SetHBar();
748 }
749
NewScrollPos()750 void LineEdit::NewScrollPos() {}
HighlightLine(int line,Vector<Highlight> & h,int64 pos)751 void LineEdit::HighlightLine(int line, Vector<Highlight>& h, int64 pos) {}
752
AlignChar()753 void LineEdit::AlignChar() {
754 int c = GetCursor32();
755 if(c == 0)
756 return;
757 Point pos = GetColumnLine(c);
758 if(pos.x == 0)
759 return;
760 for(int d = 1; d <= pos.y && d < 100; d++) {
761 int lny = pos.y - d;
762 WString above = GetWLine(lny);
763 int offset = int(GetGPos(lny, pos.x) - GetPos64(lny));
764 int end = offset;
765 char ch = GetChar(c - 1);
766 if(ch == ' ')
767 {
768 offset++;
769 while(end < above.GetLength() && above[end] != ' ')
770 end++;
771 while(end < above.GetLength() && above[end] == ' ')
772 end++;
773 }
774 else
775 while(end < above.GetLength() && above[end] != ch)
776 end++;
777 if(end < above.GetLength()) {
778 int count = end - offset + 1;
779 WString s(' ', count);
780 Insert(c - 1, s, true);
781 SetCursor(c + count);
782 return;
783 }
784 }
785 }
786
PlaceCaret0(Point p)787 void LineEdit::PlaceCaret0(Point p) {
788 Size fsz = GetFontSize();
789 p -= sb;
790 caretpos = Point(p.x * fsz.cx, p.y * fsz.cy);
791 if(overwrite)
792 SetCaret(caretpos.x, caretpos.y + fsz.cy - 2, fsz.cx, 2);
793 else
794 SetCaret(caretpos.x, caretpos.y, 2, fsz.cy);
795 }
796
PlaceCaretNoG(int64 newcursor,bool sel)797 int LineEdit::PlaceCaretNoG(int64 newcursor, bool sel) {
798 if(newcursor > GetLength64()) newcursor = GetLength64();
799 Point p = GetColumnLine(newcursor);
800 if(sel) {
801 if(anchor < 0) {
802 anchor = cursor;
803 }
804 if(rectsel || rectsel != dorectsel)
805 Refresh();
806 else
807 RefreshLines(p.y, GetLine(cursor));
808 rectsel = dorectsel;
809 }
810 else {
811 if(anchor >= 0) {
812 if(rectsel || dorectsel)
813 Refresh();
814 else
815 RefreshLines(GetLine(cursor), GetLine(anchor));
816 anchor = -1;
817 }
818 rectsel = false;
819 }
820 RefreshLine(GetColumnLine(cursor).y);
821 RefreshLine(p.y);
822 cursor = newcursor;
823 ScrollIntoCursor();
824 PlaceCaret0(p);
825 SelectionChanged();
826 WhenSel();
827 if(IsAnySelection())
828 SetSelectionSource(ClipFmtsText());
829 if(!IsNull(hline)) {
830 Size sz = GetSize();
831 Refresh(vlinex, 0, 1, sz.cy);
832 Refresh(caretpos.x, 0, 1, sz.cy);
833 }
834 return p.x;
835 }
836
PlaceCaret(int64 newcursor,bool sel)837 void LineEdit::PlaceCaret(int64 newcursor, bool sel) {
838 gcolumn = PlaceCaretNoG(newcursor, sel);
839 }
840
TopCursor(int lines)841 void LineEdit::TopCursor(int lines)
842 {
843 sb.SetY(max(0, GetLine(cursor) - lines));
844 }
845
CenterCursor()846 void LineEdit::CenterCursor() {
847 int cy = sb.GetPage().cy;
848 if(cy > 4)
849 sb.SetY(max(min(GetLine(cursor) - cy / 2, GetLineCount() - cy), 0));
850 }
851
Scroll()852 void LineEdit::Scroll() {
853 PlaceCaret0(GetColumnLine(cursor));
854 scroller.Scroll(*this, GetSize(), sb.Get(), GetFontSize());
855 SetHBar();
856 NewScrollPos();
857 WhenScroll();
858 }
859
GetMousePos(Point p) const860 int64 LineEdit::GetMousePos(Point p) const {
861 Size fsz = GetFontSize();
862 p = (p + fsz.cx / 2 + fsz * (Size)sb.Get()) / fsz;
863 return GetGPos(p.y, p.x);
864 }
865
LeftDown(Point p,dword flags)866 void LineEdit::LeftDown(Point p, dword flags) {
867 mpos = GetMousePos(p);
868 int64 l, h;
869 if(GetSelection(l, h) && mpos >= l && mpos < h) {
870 selclick = true;
871 return;
872 }
873 dorectsel = flags & K_ALT;
874 PlaceCaret(mpos, (flags & K_SHIFT) || dorectsel);
875 dorectsel = false;
876 SetFocus();
877 SetCapture();
878 }
879
LeftUp(Point p,dword flags)880 void LineEdit::LeftUp(Point p, dword flags)
881 {
882 if(!HasCapture() && selclick && !IsDragAndDropSource()) {
883 mpos = GetMousePos(p);
884 PlaceCaret(mpos, flags & K_SHIFT);
885 SetFocus();
886 }
887 selclick = false;
888 ReleaseCapture();
889 }
890
RightDown(Point p,dword flags)891 void LineEdit::RightDown(Point p, dword flags)
892 {
893 mpos = GetMousePos(p);
894 SetFocus();
895 int64 l, h;
896 GetSelection(l, h);
897 if(!IsAnySelection() || !(mpos >= l && mpos < h))
898 PlaceCaret(mpos, false);
899 MenuBar::Execute(WhenBar);
900 }
901
LeftDouble(Point,dword)902 void LineEdit::LeftDouble(Point, dword)
903 {
904 int64 l, h;
905 if(GetWordSelection(cursor, l, h))
906 SetSelection(l, h);
907 }
908
LeftTriple(Point,dword)909 void LineEdit::LeftTriple(Point, dword)
910 {
911 int64 q = cursor;
912 int i = GetLinePos64(q);
913 q = cursor - q;
914 SetSelection(q, q + GetLineLength(i) + 1);
915 }
916
MouseMove(Point p,dword flags)917 void LineEdit::MouseMove(Point p, dword flags) {
918 if((flags & K_MOUSELEFT) && HasFocus() && HasCapture()) {
919 int64 c = GetMousePos(p);
920 dorectsel = flags & K_ALT;
921 PlaceCaret(c, mpos != c || HasCapture());
922 dorectsel = false;
923 }
924 }
925
LeftRepeat(Point p,dword flags)926 void LineEdit::LeftRepeat(Point p, dword flags) {
927 if(HasCapture()) {
928 int64 c = GetMousePos(p);
929 if(mpos != c) {
930 dorectsel = flags & K_ALT;
931 PlaceCaret(c, true);
932 dorectsel = false;
933 }
934 }
935 }
936
CursorImage(Point,dword)937 Image LineEdit::CursorImage(Point, dword) {
938 return Image::IBeam();
939 }
940
MoveUpDown(int n,bool sel)941 void LineEdit::MoveUpDown(int n, bool sel) {
942 int64 cl = cursor;
943 int ln = GetLinePos64(cl);
944 if(ln + n >= GetLineCount())
945 WaitView(ln + n);
946 ln = minmax(ln + n, 0, GetLineCount() - 1);
947 PlaceCaretNoG(GetGPos(ln, gcolumn), sel);
948 }
949
MoveLeft(bool sel)950 void LineEdit::MoveLeft(bool sel) {
951 if(cursor)
952 PlaceCaret(cursor - 1, sel);
953 }
954
MoveRight(bool sel)955 void LineEdit::MoveRight(bool sel) {
956 if(cursor < GetLength64())
957 PlaceCaret(cursor + 1, sel);
958 }
959
MoveUp(bool sel)960 void LineEdit::MoveUp(bool sel) {
961 MoveUpDown(-1, sel);
962 }
963
MoveDown(bool sel)964 void LineEdit::MoveDown(bool sel) {
965 MoveUpDown(1, sel);
966 }
967
MovePage(int dir,bool sel)968 void LineEdit::MovePage(int dir, bool sel) {
969 int n = dir * max(GetSize().cy / GetFontSize().cy - 2, 2);
970 sb.SetY(Point(sb).y + n);
971 MoveUpDown(n, sel);
972 }
973
MovePageUp(bool sel)974 void LineEdit::MovePageUp(bool sel) {
975 MovePage(-1, sel);
976 }
977
MovePageDown(bool sel)978 void LineEdit::MovePageDown(bool sel) {
979 MovePage(1, sel);
980 }
981
sTabSpace(int c)982 inline bool sTabSpace(int c) { return c == '\t' || c == ' '; }
983
MoveHome(bool sel)984 void LineEdit::MoveHome(bool sel) {
985 int64 cl = cursor;
986 int li = GetLinePos64(cl);
987 int i = 0;
988 WString l = GetWLine(li);
989 while(sTabSpace(l[i]))
990 i++;
991 PlaceCaret(GetPos64(li, cl == i ? 0 : i), sel);
992 }
993
MoveEnd(bool sel)994 void LineEdit::MoveEnd(bool sel) {
995 int i = GetLine(cursor);
996 PlaceCaret(GetPos64(i, GetLineLength(i)), sel);
997 }
998
MoveTextBegin(bool sel)999 void LineEdit::MoveTextBegin(bool sel) {
1000 PlaceCaret(0, sel);
1001 }
1002
MoveTextEnd(bool sel)1003 void LineEdit::MoveTextEnd(bool sel) {
1004 WaitView(INT_MAX, true);
1005 PlaceCaret(GetLength64(), sel);
1006 }
1007
InsertChar(dword key,int count,bool canow)1008 bool LineEdit::InsertChar(dword key, int count, bool canow) {
1009 if(key == K_TAB && !processtab)
1010 return false;
1011 if(filter && key >= 32 && key < 65535)
1012 key = (*filter)(key);
1013 if(!IsReadOnly() && (key >= 32 && key < 65536 || key == '\t' || key == '\n' ||
1014 key == K_ENTER && processenter || key == K_SHIFT_SPACE)) {
1015 if(key >= 128 && key < 65536 && (charset != CHARSET_UNICODE && charset != CHARSET_UTF8_BOM)
1016 && FromUnicode((wchar)key, charset) == DEFAULTCHAR)
1017 return true;
1018 if(!RemoveSelection() && overwrite && key != '\n' && key != K_ENTER && canow) {
1019 int64 q = cursor;
1020 int i = GetLinePos64(q);
1021 if(q + count - 1 < GetLineLength(i))
1022 Remove((int)cursor, (int)count);
1023 }
1024 WString text(key == K_ENTER ? '\n' : key == K_SHIFT_SPACE ? ' ' : key, count);
1025 Insert((int)cursor, text, true);
1026 PlaceCaret(cursor + count);
1027 Action();
1028 return true;
1029 }
1030 return false;
1031 }
1032
DeleteChar()1033 void LineEdit::DeleteChar() {
1034 if(IsReadOnly() || RemoveSelection()) {
1035 Action();
1036 return;
1037 }
1038 if(cursor < GetLength32()) {
1039 Remove((int)cursor, 1);
1040 Action();
1041 }
1042 }
1043
Backspace()1044 void LineEdit::Backspace() {
1045 if(IsReadOnly() || RemoveSelection() || cursor == 0) return;
1046 MoveLeft();
1047 DeleteChar();
1048 Action();
1049 }
1050
DeleteLine()1051 void LineEdit::DeleteLine()
1052 {
1053 int64 b, e;
1054 if(GetSelection(b, e) && GetLine(b) != GetLine(e)) {
1055 RemoveSelection();
1056 return;
1057 }
1058 int i = GetLine(cursor);
1059 int p = GetPos32(i);
1060 Remove((int)p, GetLineLength(i) + 1);
1061 PlaceCaret(p);
1062 Action();
1063 }
1064
CutLine()1065 void LineEdit::CutLine()
1066 {
1067 if(IsReadOnly()) return;
1068 int64 b, e;
1069 if(GetSelection(b, e) && GetLine(b) != GetLine(e)) {
1070 Cut();
1071 return;
1072 }
1073 int i = GetLine(cursor);
1074 int p = GetPos32(i);
1075 WString txt = Get(p, GetLineLength(i) + 1).ToWString();
1076 WriteClipboardUnicodeText(txt);
1077 AppendClipboardText(txt.ToString());
1078 ClearSelection();
1079 DeleteLine();
1080 }
1081
Serialize(Stream & s)1082 void LineEdit::EditPos::Serialize(Stream& s) {
1083 int version = 1;
1084 s / version;
1085 if(version >= 1)
1086 s % sby % cursor;
1087 else {
1088 int c = (int)cursor;
1089 s % sby % c;
1090 cursor = c;
1091 }
1092 }
1093
GetEditPos() const1094 LineEdit::EditPos LineEdit::GetEditPos() const {
1095 EditPos pos;
1096 pos.sby = sb.Get().y;
1097 pos.cursor = cursor;
1098 return pos;
1099 }
1100
SetEditPos(const LineEdit::EditPos & pos)1101 void LineEdit::SetEditPos(const LineEdit::EditPos& pos) {
1102 SetEditPosSbOnly(pos);
1103 SetCursor(pos.cursor);
1104 }
1105
SetEditPosSb(const LineEdit::EditPos & pos)1106 void LineEdit::SetEditPosSb(const LineEdit::EditPos& pos) {
1107 SetCursor(pos.cursor);
1108 SetEditPosSbOnly(pos);
1109 }
1110
SetEditPosSbOnly(const LineEdit::EditPos & pos)1111 void LineEdit::SetEditPosSbOnly(const LineEdit::EditPos& pos) {
1112 sb.SetY(minmax(pos.sby, 0, GetLineCount() - 1));
1113 }
1114
SetHBar()1115 void LineEdit::SetHBar()
1116 {
1117 int mpos = 0;
1118 if(!nohbar && !isdrag) {
1119 int m = min(sb.y + sb.GetPage().cy + 2, GetLineCount());
1120 for(int i = sb.y; i < m; i++) {
1121 int pos = 0;
1122 WString l = GetWLine(i);
1123 const wchar *s = l;
1124 const wchar *e = l.End();
1125 while(s < e) {
1126 if(*s == '\t')
1127 pos = (pos + tabsize) / tabsize * tabsize;
1128 else
1129 pos += 1 + IsDoubleWidth(*s);
1130 s++;
1131 }
1132 mpos = max(mpos, pos);
1133 }
1134 }
1135 sb.SetTotalX(mpos + 1);
1136 }
1137
ScrollIntoCursor()1138 void LineEdit::ScrollIntoCursor()
1139 {
1140 Point p = GetColumnLine(GetCursor64());
1141 sb.ScrollInto(p);
1142 SetHBar();
1143 sb.ScrollInto(p);
1144 }
1145
Key(dword key,int count)1146 bool LineEdit::Key(dword key, int count) {
1147 NextUndo();
1148 switch(key) {
1149 case K_CTRL_UP:
1150 ScrollUp();
1151 return true;
1152 case K_CTRL_DOWN:
1153 ScrollDown();
1154 return true;
1155 case K_INSERT:
1156 OverWriteMode(!IsOverWriteMode());
1157 break;
1158 }
1159 bool sel = key & K_SHIFT;
1160 dorectsel = key & K_ALT;
1161 dword k = key & ~K_SHIFT;
1162 if((key & (K_SHIFT|K_ALT)) == (K_SHIFT|K_ALT))
1163 k &= ~K_ALT;
1164 switch(k) {
1165 case K_CTRL_LEFT:
1166 {
1167 PlaceCaret(GetPrevWord(cursor), sel);
1168 break;
1169 }
1170 case K_CTRL_RIGHT:
1171 {
1172 PlaceCaret(GetNextWord(cursor), sel);
1173 break;
1174 }
1175 case K_LEFT:
1176 MoveLeft(sel);
1177 break;
1178 case K_RIGHT:
1179 MoveRight(sel);
1180 break;
1181 case K_HOME:
1182 MoveHome(sel);
1183 break;
1184 case K_END:
1185 MoveEnd(sel);
1186 break;
1187 case K_UP:
1188 MoveUp(sel);
1189 break;
1190 case K_DOWN:
1191 MoveDown(sel);
1192 break;
1193 case K_PAGEUP:
1194 MovePageUp(sel);
1195 break;
1196 case K_PAGEDOWN:
1197 MovePageDown(sel);
1198 break;
1199 case K_CTRL_PAGEUP:
1200 case K_CTRL_HOME:
1201 MoveTextBegin(sel);
1202 break;
1203 case K_CTRL_PAGEDOWN:
1204 case K_CTRL_END:
1205 MoveTextEnd(sel);
1206 break;
1207 case K_CTRL_C:
1208 case K_CTRL_INSERT:
1209 Copy();
1210 break;
1211 case K_CTRL_A:
1212 SelectAll();
1213 break;
1214 default:
1215 dorectsel = false;
1216 if(IsReadOnly())
1217 return MenuBar::Scan(WhenBar, key);
1218 switch(key) {
1219 case K_DELETE:
1220 DeleteChar();
1221 break;
1222 case K_BACKSPACE:
1223 case K_SHIFT|K_BACKSPACE:
1224 Backspace();
1225 break;
1226 case K_SHIFT_TAB:
1227 AlignChar();
1228 break;
1229 case K_CTRL_Y:
1230 case K_CTRL_L:
1231 if(cutline) {
1232 CutLine();
1233 break;
1234 }
1235 default:
1236 if(InsertChar(key, count, true))
1237 return true;
1238 return MenuBar::Scan(WhenBar, key);
1239 }
1240 return true;
1241 }
1242 dorectsel = false;
1243 Sync();
1244 return true;
1245 }
1246
DragAndDrop(Point p,PasteClip & d)1247 void LineEdit::DragAndDrop(Point p, PasteClip& d)
1248 {
1249 if(IsReadOnly()) return;
1250 int c = GetMousePos32(p);
1251 if(AcceptText(d)) {
1252 NextUndo();
1253 int a = sb.y;
1254 int sell, selh;
1255 WString text = GetWString(d);
1256 if(GetSelection32(sell, selh)) {
1257 if(c >= sell && c < selh) {
1258 if(!IsReadOnly())
1259 RemoveSelection();
1260 if(IsDragAndDropSource())
1261 d.SetAction(DND_COPY);
1262 c = sell;
1263 }
1264 else
1265 if(d.GetAction() == DND_MOVE && IsDragAndDropSource()) {
1266 if(c > sell)
1267 c -= selh - sell;
1268 if(!IsReadOnly())
1269 RemoveSelection();
1270 d.SetAction(DND_COPY);
1271 }
1272 }
1273 int count = Insert(c, text);
1274 sb.y = a;
1275 SetFocus();
1276 SetSelection(c, c + count);
1277 Action();
1278 return;
1279 }
1280 if(!d.IsAccepted()) return;
1281 if(!isdrag) {
1282 isdrag = true;
1283 ScrollIntoCursor();
1284 }
1285 Point dc = Null;
1286 if(c >= 0)
1287 dc = GetColumnLine(c);
1288 if(dc != dropcaret) {
1289 RefreshDropCaret();
1290 dropcaret = dc;
1291 RefreshDropCaret();
1292 }
1293 }
1294
DropCaret()1295 Rect LineEdit::DropCaret()
1296 {
1297 if(IsNull(dropcaret))
1298 return Rect(0, 0, 0, 0);
1299 Size fsz = GetFontSize();
1300 Point p = dropcaret - sb;
1301 p = Point(p.x * fsz.cx, p.y * fsz.cy);
1302 return RectC(p.x, p.y, 1, fsz.cy);
1303 }
1304
RefreshDropCaret()1305 void LineEdit::RefreshDropCaret()
1306 {
1307 Refresh(DropCaret());
1308 }
1309
DragRepeat(Point p)1310 void LineEdit::DragRepeat(Point p)
1311 {
1312 sb.y = (int)sb.y + GetDragScroll(this, p, 1).y;
1313 }
1314
DragLeave()1315 void LineEdit::DragLeave()
1316 {
1317 RefreshDropCaret();
1318 dropcaret = Null;
1319 isdrag = false;
1320 Layout();
1321 }
1322
LeftDrag(Point p,dword flags)1323 void LineEdit::LeftDrag(Point p, dword flags)
1324 {
1325 int64 c = GetMousePos(p);
1326 int64 l, h;
1327 if(!HasCapture() && GetSelection(l, h) && c >= l && c < h) {
1328 WString sample = GetW(l, (int)min(h - l, (int64)3000));
1329 Size sz = StdSampleSize();
1330 ImageDraw iw(sz);
1331 iw.DrawRect(sz, Black());
1332 iw.Alpha().DrawRect(sz, Black());
1333 DrawTLText(iw.Alpha(), 0, 0, 9999, sample, CourierZ(10), White());
1334 NextUndo();
1335 if(DoDragAndDrop(ClipFmtsText(), iw) == DND_MOVE && !IsReadOnly()) {
1336 RemoveSelection();
1337 Action();
1338 }
1339 }
1340 }
1341
1342 }
1343