5755d61f8e5be1dccd23078e32183be587458a66
[sdk] / ecere / src / gui / controls / EditBox.ec
1 namespace gui::controls;
2
3 /*
4 selectionForeground = white;
5 disabled: defaultTextColor = Color { 85, 85, 85 };
6 */
7
8 import "Window"
9 import "ReplaceDialog"
10 import "FindDialog"
11 import "GoToDialog"
12 import "Array"
13
14 public class SyntaxColorScheme
15 {
16 public:
17    Color commentColor;
18    Color charLiteralColor;
19    Color stringLiteralColor;
20    Color preprocessorColor;
21    Color numberColor;
22    private Array<Color> keywordColors { };
23
24    public property Container<Color> keywordColors
25    {
26       set { keywordColors.Copy((void *)value); }
27       get { return keywordColors; }
28    }
29 };
30
31 #include <stdarg.h>
32
33 #define MAXWORDLEN   64
34
35 #define XOFFSET   (3 + (/*style.lineNumbers?6 * this.space.w:*/0))
36 // #define YOFFSET   1
37 #define YOFFSET   (style.multiLine ? 1 : ((clientSize.h + 1 - space.h) / 2))
38
39 #define IS_ALUNDER(ch) (/*(ch) == '_' || */CharMatchCategories((ch), letters|numbers|marks|connector))
40
41 #define UTF8_IS_FIRST(x)   (__extension__({ byte b = x; (!(b) || !((b) & 0x80) || ((b) & 0x40)); }))
42 #define UTF8_NUM_BYTES(x)  (__extension__({ byte b = x; (b & 0x80 && b & 0x40) ? ((b & 0x20) ? ((b & 0x10) ? 4 : 3) : 2) : 1; }))
43 #define UTF8_GET_CHAR(string, numBytes) \
44    __extension__( \
45    { \
46       unichar ch; \
47       byte b = (string)[0]; \
48       int i; \
49       byte mask = 0x7F; \
50       numBytes = b ? 1 : 0; \
51       ch = 0; \
52       if(b & 0x80) \
53       { \
54          if(b & 0x40) \
55          { \
56             mask >>= 2; \
57             numBytes++; \
58             if(b & 0x20) \
59             { \
60                numBytes++; \
61                mask >>= 1; \
62                if(b & 0x10) \
63                { \
64                   if(b & 0x08) { numBytes = 0; } \
65                   numBytes++; \
66                   mask >>= 1; \
67                } \
68             } \
69          } \
70          else \
71             numBytes = 0; \
72       } \
73       for(i = 0; i<numBytes; i++) \
74       { \
75          ch <<= 6; \
76          ch |= (b = (string)[i]) & mask; \
77          mask = 0x3F; \
78          if(i > 1 && (!(b & 0x80) || (b & 0x40))) \
79          { \
80             numBytes = 0; \
81             ch = 0; \
82          } \
83       } \
84       if(i < numBytes || \
85          ch > 0x10FFFF || (ch >= 0xD800 && ch <= 0xDFFF) || \
86         (ch < 0x80 && numBytes > 1) || \
87         (ch < 0x800 && numBytes > 2) || \
88         (ch < 0x10000 && numBytes > 3))\
89       { \
90          ch = 0; \
91          numBytes = 0; \
92       } \
93       ch; \
94    })
95
96 class EditBoxBits
97 {
98    bool autoEmpty:1, readOnly:1, multiLine:1, stuckCaret:1, freeCaret:1, select:1, hScroll:1, vScroll:1, smartHome:1;
99    bool noCaret:1, noSelect:1, tabKey:1, useTab:1, tabSel:1, allCaps:1, syntax:1, wrap:1;
100
101    // Syntax States
102    bool inMultiLineComment:1, inPrep:1, escaped:1, continuedSingleLineComment:1, wasInMultiLine:1, continuedString:1, continuedQuotes:1;
103
104    bool recomputeSyntax:1;
105    bool cursorFollowsView:1;
106
107    // bool lineNumbers:1;
108    bool autoSize:1;
109 };
110
111 /* TODO:
112 void UnregisterClass_EditBox()
113 {
114    int g;
115    for(g = 0; g<NUM_KEYWORD_GROUPS; g++)
116    {
117       if(keyLen[g])
118          free(keyLen[g]);
119    }
120 }
121 */
122
123 default:
124 extern int __ecereVMethodID_class_OnFree;
125 private:
126
127 static class ArrayImpl
128 {
129    Class type;
130    uint size;
131    byte * array;
132 };
133
134 public class OldArray
135 {
136    ~OldArray()
137    {
138       int c;
139       void ** array = (void **)((ArrayImpl)this).array;
140       if(type.type == normalClass || type.type == noHeadClass)
141       {
142          for(c = 0; c<size; c++)
143             ((void (*)(void *, void *))(void *)type._vTbl[__ecereVMethodID_class_OnFree])(type, array[c]);
144       }
145       // TODO: Call OnFree for structClass
146       delete ((ArrayImpl)this).array;
147    }
148
149 public:
150    Class type;
151    property uint size
152    {
153       set
154       {
155          if(((ArrayImpl)this).array)
156          {
157             if(value == size)
158                return;
159             ((ArrayImpl)this).array = renew0 ((ArrayImpl)this).array byte[type.typeSize * value];
160          }
161          else if(value)
162             ((ArrayImpl)this).array = new0 byte[value * type.typeSize];
163          size = value;
164       }
165       get { return size; }
166    }
167    property void * data
168    {
169       set
170       {
171          memcpy(((ArrayImpl)this).array, value, type.typeSize * size);
172       }
173    }
174 private:
175    uint size;
176 };
177
178 public class UndoAction : struct
179 {
180 public:
181    subclass(UndoAction) type;
182    bool continued;
183
184    virtual void Undo(void * data) { type.Undo(this, data); }
185    virtual void Redo(void * data) { type.Redo(this, data); }
186 #ifdef _DEBUG
187    virtual void Print(void * data)  { type.Print(this, data); }
188 #endif
189    ~UndoAction()
190    {
191       if(((Class)type).Destructor)
192          ((void (*)(void *))((Class)type).Destructor)(this);
193    }
194 };
195
196 public class UndoBuffer
197 {
198    Array<UndoAction> actions { size = 8 };
199 public:
200    int count;
201    int curAction;
202    void * data;
203    int dontRecord;
204    bool insideRedo;
205    bool recordAsOne;
206    bool firstEvent;
207
208    dontRecord = 0;
209
210    ~UndoBuffer()
211    {
212       actions.Free();
213    }
214
215    void Undo()
216    {
217       bool continued = true;
218       while(curAction > 0 && continued)
219       {
220          UndoAction action = actions[--curAction];
221          dontRecord++;
222
223 #ifdef _DEBUG
224          /*Print("Undoing: ");
225          action.Print(data);*/
226 #endif
227          action.Undo(data);
228          dontRecord--;
229
230          continued = curAction > 0 && actions[curAction-1].continued;
231       }
232    }
233
234    void Redo()
235    {
236       bool continued = true;
237       while(curAction < count && continued)
238       {
239          UndoAction action = actions[curAction];
240          continued = action.continued;
241          dontRecord++;
242          insideRedo = true;
243
244          curAction++;
245 #ifdef _DEBUG
246          /*Print("Redoing: ");
247          action.Print(data);*/
248 #endif
249          action.Redo(data);
250
251          insideRedo = false;
252          dontRecord--;
253       }
254    }
255
256    void Record(UndoAction action)
257    {
258       if(!dontRecord && !insideRedo)
259       {
260          if(curAction < count)
261          {
262             int c;
263             for(c = curAction; c < count; c++)
264                delete actions[c];
265          }
266
267          count = curAction;
268
269          if(count >= actions.size)
270             actions.size += actions.size / 2;
271
272 #ifdef _DEBUG
273          /*Print("Recording: ");
274          action.Print(data);*/
275 #endif
276          if(recordAsOne)
277          {
278             if(!firstEvent && count > 0)
279                actions[count-1].continued = true;
280             firstEvent = false;
281          }
282          actions[count++] = action;
283          curAction = count;
284
285          if(actions.size > count + count / 2 && count + count / 2 >= 8)
286             actions.size = count + count / 2;
287       }
288       else
289          delete action;
290    }
291 };
292
293 static class AddCharAction : UndoAction
294 {
295    int y, x;
296    unichar ch;
297    int addedSpaces, addedTabs;
298    type = class(AddCharAction);
299
300    void Undo(EditBox editBox)
301    {
302       editBox.GoToPosition(null, (ch == '\n') ? (y + 1) : y, (ch == '\n') ? 0 : (x + 1));
303       editBox.BackSpace();
304       if(addedTabs || addedSpaces)
305          editBox.DelCh(editBox.line, y, x - (addedSpaces + addedTabs), editBox.line, y, x, false);
306       editBox.UpdateDirty();
307    }
308
309    void Redo(EditBox editBox)
310    {
311       editBox.GoToPosition(null, y, x);
312       editBox.PutCh(ch);
313       editBox.UpdateDirty();
314    }
315 #ifdef _DEBUG
316    void Print(EditBox editBox)
317    {
318       PrintLn("AddChar: y = ", y, "x = ", x, ", ch = ", ch, ", addedSpaces = ", addedSpaces, ", addedTabs = ", addedTabs);
319    }
320 #endif
321 };
322
323 static class AddTextAction : UndoAction
324 {
325    int y1, x1, y2, x2;
326    char * string;
327    int addedSpaces, addedTabs;
328    type = class(AddTextAction);
329
330 #ifdef _DEBUG
331    void Print(EditBox editBox)
332    {
333       PrintLn("AddText: y1 = ", y1, "x1 = ", x1, ", y2 = ", y2, ", x2 = ", x2, ", string = ", string, ", addedSpaces = ", addedSpaces, ", addedTabs = ", addedTabs);
334    }
335 #endif
336    ~AddTextAction()
337    {
338       delete string;
339    }
340
341    void Undo(EditBox editBox)
342    {
343       EditLine l1, l2;
344       int c;
345
346       editBox.GoToPosition(null, y1, x1);
347       l1 = editBox.line;
348       l2 = editBox.lines.first;
349       for(c = 0; c < y2 && l2; c++, l2 = l2.next);
350
351       editBox.DelCh(l1, y1, x1, l2, y2, x2, true);
352       if(addedTabs || addedSpaces)
353          editBox.DelCh(editBox.line, y1, x1 - (addedSpaces + addedTabs), editBox.line, y1, x1, false);
354
355       editBox.SetViewToCursor(true);
356       editBox.Modified();
357    }
358
359    void Redo(EditBox editBox)
360    {
361       editBox.GoToPosition(null, y1, x1);
362       editBox.PutS(string);
363    }
364 };
365
366 static class DelTextAction : UndoAction
367 {
368    int y1, x1, y2, x2;
369    char * string;
370    bool placeAfter, noHighlight;
371    int addedSpaces;
372    type = class(DelTextAction);
373
374 #ifdef _DEBUG
375    void Print(EditBox editBox)
376    {
377       PrintLn("DelText: y1 = ", y1, "x1 = ", x1, ", y2 = ", y2, ", x2 = ", x2, ", string = ", string, ", addedSpaces = ", addedSpaces, ", placeAfter = ", placeAfter);
378    }
379 #endif
380    void Undo(EditBox editBox)
381    {
382       editBox.GoToPosition(null, y1, x1);
383       editBox.PutS(string);
384
385       if(!placeAfter)
386       {
387          editBox.GoToPosition(null, y1, x1);
388          if(!noHighlight)
389          {
390             editBox.selY = y2;
391             editBox.selX = x2;
392          }
393          { int c; editBox.selLine = editBox.lines.first; for(c = 0; c < editBox.selY && editBox.selLine; c++, editBox.selLine = editBox.selLine.next); }
394          //editBox.SetViewToCursor(true);
395
396          if(addedSpaces)
397             editBox.DelCh(editBox.line, y1, x1 - addedSpaces, editBox.line, y1, x1, false);
398       }
399       else
400       {
401          if(!noHighlight)
402          {
403             editBox.selY = y1;
404             editBox.selX = x1;
405          }
406          { int c; editBox.selLine = editBox.lines.first; for(c = 0; c < editBox.selY && editBox.selLine; c++, editBox.selLine = editBox.selLine.next); }
407          //editBox.SetViewToCursor(true);
408
409          if(addedSpaces)
410             editBox.DelCh(editBox.selLine, y1, x1 - addedSpaces, editBox.selLine, y1, x1, false);
411       }
412    }
413
414    void Redo(EditBox editBox)
415    {
416       EditLine l1, l2;
417       int c;
418
419       editBox.GoToPosition(null, y1, x1);
420       l1 = editBox.line;
421
422       l2 = editBox.lines.first;
423       for(c = 0; c < y2 && l2; c++, l2 = l2.next);
424
425       editBox.DelCh(l1, y1, x1, l2, y2, x2, true);
426       editBox.SetViewToCursor(true);
427       editBox.Modified();
428    }
429
430    ~DelTextAction()
431    {
432       delete string;
433    }
434 };
435
436 static class ReplaceTextAction : UndoAction
437 {
438    int y1, x1, y2, x2, y3, x3;
439    char * oldString;
440    char * newString;
441    bool placeAfter;
442    int addedSpaces, addedTabs;
443
444    type = class(ReplaceTextAction);
445
446 #ifdef _DEBUG
447    void Print(EditBox editBox)
448    {
449       PrintLn("ReplaceText: y1 = ", y1, "x1 = ", x1, ", y2 = ", y2, ", x2 = ", x2, ", y3 = ", y3, ", x3 = ", x3, ", oldString = ", oldString, ", newString = ", newString, ", addedSpaces = ", addedSpaces, ", addedTabs = ", addedTabs, ", placeAfter = ", placeAfter);
450    }
451 #endif
452    void Undo(EditBox editBox)
453    {
454       EditLine l1, l3;
455       int c;
456
457       editBox.GoToPosition(null, y1, x1);
458       l1 = editBox.line;
459       l3 = editBox.lines.first;
460       for(c = 0; c < y3 && l3; c++, l3 = l3.next);
461
462       editBox.DelCh(l1, y1, x1, l3, y3, x3, true);
463
464       editBox.PutS(oldString);
465       if(addedSpaces || addedTabs)
466          editBox.DelCh(l1, y1, x1 - (addedSpaces + addedTabs), l1, y1, x1, false);
467
468       //editBox.PutS(oldString);
469       if(!placeAfter)
470       {
471          editBox.GoToPosition(null, y1, x1);
472          editBox.selY = y2;
473          editBox.selX = x2;
474          { int c; editBox.selLine = editBox.lines.first; for(c = 0; c < editBox.selY && editBox.selLine; c++, editBox.selLine = editBox.selLine.next); }
475       }
476       else
477       {
478          editBox.selY = y1;
479          editBox.selX = x1;
480          { int c; editBox.selLine = editBox.lines.first; for(c = 0; c < editBox.selY && editBox.selLine; c++, editBox.selLine = editBox.selLine.next); }
481       }
482    }
483
484    void Redo(EditBox editBox)
485    {
486       EditLine l1, l2;
487       int c;
488
489       editBox.GoToPosition(null, y1, x1);
490       l1 = editBox.line;
491       l2 = editBox.lines.first;
492       for(c = 0; c < y2 && l2; c++, l2 = l2.next);
493
494       editBox.DelCh(l1, y1, x1, l2, y2, x2, true);
495
496       editBox.PutS(newString);
497    }
498
499    ~ReplaceTextAction()
500    {
501       delete oldString;
502       delete newString;
503    }
504 };
505 /*
506 static class MoveTextAction : UndoAction
507 {
508    int fy1, fx1, fy2, fx2;
509    int ty, tx;
510    type = class(MoveTextAction);
511
512    void Undo(EditBox editBox)
513    {
514
515    }
516
517    void Redo(EditBox editBox)
518    {
519
520    }
521 };
522 */
523 public class EditLine : struct
524 {
525    EditLine prev, next;
526    char * buffer;
527    uint size, count;
528    int length;
529    EditBox editBox;
530 public:
531    property char * text
532    {
533       set
534       {
535          // Only works for single line edit for now...
536          EditBox editBox = this.editBox;
537          editBox.Clear();
538          editBox.PutS(text);
539       }
540       get { return this ? buffer : null; }
541    };
542    property EditLine prev { get { return this ? prev : null; } };
543    property EditLine next { get { return this ? next : null; } };
544    property int count { get { return this ? count : 0; } };
545 private:
546    void Free()
547    {
548       delete buffer;
549    }
550
551    // This makes sure the buffer always contains at least count characters
552    // Keeps a count/2 pad for avoiding always reallocating memory.
553    bool AdjustBuffer(int count)
554    {
555       char * buffer;
556       int newSize;
557
558       // Adds '\0' byte
559       count = count+1;
560
561       newSize =  (count + (count >> 1));
562
563       // Shrink down the buffer
564       if(size > newSize)
565       {
566          buffer = new char[newSize];
567          if(!buffer) return false;
568
569          if(this.buffer)
570          {
571             CopyBytes(buffer, this.buffer, count);
572             delete this.buffer;
573          }
574          this.buffer = buffer;
575          size = newSize;
576          return true;
577       }
578       // Increase the buffer
579       else if(size < count)
580       {
581           buffer = new char[newSize];
582           if(!buffer) return false;
583
584           if(this.buffer)
585           {
586              CopyBytes(buffer, this.buffer, this.count + 1); // size);
587              delete this.buffer;
588           }
589           this.buffer = buffer;
590           size = newSize;
591           return true;
592       }
593       else
594          return true;
595    }
596 };
597
598 public struct BufferLocation
599 {
600    EditLine line;
601    int y, x;
602
603    void AdjustDelete(BufferLocation start, BufferLocation end)
604    {
605       // Location is before, nothing to do
606       if(y < start.y || (y == start.y && x < start.x))
607          return;
608       // Location is inside deleted bytes, point to the start
609       if((y >= start.y && (y > start.y || x >= start.x)) &&
610          (y >= end.y && (y > end.y || x >= end.x)))
611       {
612          // Location is after
613          if(y >= end.y)
614          {
615             // Location is on another line
616             if(y > end.y)
617                y -= end.y - start.y;
618             // Location is the last touched line
619             else
620             {
621                if(x >= end.x)
622                {
623                   line = start.line;
624                   y = start.y;
625                   //if(start.line == end.line)
626                      x -= end.x - start.x;
627                }
628             }
629          }
630       }
631       else
632          this = start;
633    }
634
635    // Assuming no carriage return before first character ???? fixed?
636    void AdjustAdd(BufferLocation start, BufferLocation end)
637    {
638       int numLines = end.y - start.y;
639       if(y >= start.y)
640       {
641          if(y > start.y)
642             y += numLines;
643          else
644          {
645             if(x > start.x || (x == start.x /*&& (numLines ? true : false)*/))
646             {
647                int c;
648                for(c = 0, line = start.line; c<numLines; c++)
649                   line = line.next;
650                y += numLines;
651                //x += numLines ? end.x : (end.x - start.x);
652                x += end.x - start.x;
653             }
654          }
655       }
656    }
657 };
658
659 public enum EditBoxFindResult { notFound, found, wrapped };
660
661 static char * keyWords1[] =
662 {
663    // C
664    "return","break","continue","default","switch","case","if","else","for","while", "do","long","short",
665    "void", "char","int","float","double","signed","unsigned","static", "extern", "struct", "union", "typedef","enum",
666    "const",   "sizeof",
667    "#include", "#define", "#pragma", "#if", "#else", "#elif", "#ifdef", "#ifndef", "#endif", "#undef", "#line",
668    "__attribute__", "__stdcall", "_stdcall",
669    "__declspec", "goto",
670     "inline", "__inline__", "_inline", "__inline", "__typeof","__extension__",
671    "asm", "__asm", "_asm", "volatile", "#cpu", "__stdcall__",
672    "__restrict__", "__restrict", "restrict",
673
674    // eC
675    "class", "private", "public",
676    "property","import",
677    "delete", "new", "new0", "renew", "renew0", "define",
678    "get", "set",
679    "remote",
680    "dllexport", "dllimport", "stdcall",
681    "subclass", "__on_register_module", "namespace", "using",
682    "typed_object", "any_object", "incref", "register", "watch", "stopwatching", "firewatchers", "watchable", "class_designer",
683    "class_fixed", "class_no_expansion", "isset", "class_default_property", "property_category", "class_data", "class_property",
684    "virtual", "thisclass","unichar", "dbtable", "dbindex", "database_open", "dbfield",
685
686    // Types
687    "uint", "uint32", "uint16", "uint64", "bool", "byte", "int64", "uintptr", "intptr", "intsize", "uintsize",
688
689    // Values
690    "this", "true", "false", "null", "value",
691
692
693    // C++
694    "protected",
695    /* "defined" */
696    null
697 };
698
699 static char * keyWords2[] =
700 {
701    "defined", "warning", null
702 };
703
704 static char ** keyWords[] = { keyWords1, keyWords2 };
705 #define NUM_KEYWORD_GROUPS (sizeof(keyWords) / sizeof(char **))
706 //static int * keyLen[NUM_KEYWORD_GROUPS];
707 static int keyLen[NUM_KEYWORD_GROUPS][sizeof(keyWords1)];
708
709 static char searchString[1025], replaceString[1025];
710 static bool matchCase = false, wholeWord = false, searchUp = false;
711
712 static GoToDialog goToDialog
713 {
714    autoCreate = false, isModal = true, text = $"Go To"
715 };
716
717 public class EditBox : CommonControl
718 {
719    class_property(icon) = "<:ecere>controls/editBox.png";
720 public:
721
722    // Notifications
723    virtual bool Window::NotifyModified(EditBox editBox);
724    virtual void Window::NotifyCaretMove(EditBox editBox, int line, int charPos);
725    virtual void Window::NotifyUpdate(EditBox editBox);
726    virtual bool Window::NotifyDoubleClick(EditBox editBox, EditLine line, Modifiers mods);
727    virtual void Window::NotifyOvrToggle(EditBox editBox, bool overwrite);
728    virtual bool Window::NotifyKeyDown(EditBox editBox, Key key, unichar ch);
729
730    virtual bool Window::NotifyCharsAdded(EditBox editBox, BufferLocation before, BufferLocation after, bool pasteOperation);
731    virtual bool Window::NotifyCharsDeleted(EditBox editBox, BufferLocation beforeLoc, BufferLocation after, bool pasteOperation);
732    virtual bool Window::NotifyDropped(EditBox editBox, int x, int y);
733
734    virtual bool Window::NotifyUnsetModified(EditBox editBox);
735
736    // Why was this commented out?
737    //     It is required otherwise updating font property from property sheet doesn't immediately reflect in form designer,
738    // and the scrollArea property isn't compared properly either.
739    watch(font)
740    {
741       font = fontObject;
742       if(font) ComputeFont();
743       SetInitSize(initSize);
744    };
745
746    watch(hasVertScroll)
747    {
748       if(hasVertScroll)
749          style.vScroll = true;
750    };
751
752    watch(hasHorzScroll)
753    {
754       if(hasHorzScroll)
755          style.hScroll = true;
756    };
757
758    // Properties
759    property bool textHorzScroll { property_category $"Behavior" set { style.hScroll = value; } get { return style.hScroll; } };      // Should cut the text on set to false
760    property bool textVertScroll { property_category $"Behavior" set { style.vScroll = value; } get { return style.vScroll; } };
761    property bool readOnly
762    {
763       property_category $"Behavior"
764       set
765       {
766          style.readOnly = value;
767          itemEditCut.disabled = value || !selection;
768          itemEditDelete.disabled = value || !selection;
769          itemEditPaste.disabled = value;
770       }
771       get { return style.readOnly; }
772    };
773    property bool multiLine { property_category $"Behavior" set { style.multiLine = value; } get { return style.multiLine; } };
774    property bool freeCaret { property_category $"Behavior" set { style.freeCaret = value; } get { return style.freeCaret; } };
775    property bool tabKey { property_category $"Behavior" set { style.tabKey = value; } get { return style.tabKey; } };
776    property int tabSize { property_category $"Behavior" set { tabSize = value; } get { return tabSize; } };
777    property bool tabSelection { property_category $"Behavior" set { style.tabSel = value; if(value) style.tabKey = true; } get { return style.tabSel; } };
778    property bool smartHome { property_category $"Behavior" set { style.smartHome = value; } get { return style.smartHome; } };
779    property bool autoEmpty { property_category $"Behavior" set { style.autoEmpty = value; } get { return style.autoEmpty; } };
780    property bool noCaret { property_category $"Behavior" set { style.noCaret = value; if(value) { style.readOnly = true; style.stuckCaret = true; } } get { return style.noCaret; } };
781    property int maxLineSize { property_category $"Behavior" set { maxLineSize = value; } get { return maxLineSize; } };
782    property int maxNumLines { property_category $"Behavior" set { maxLines = value; } get { return maxLines; } };
783    property bool useTab { property_category $"Behavior" set { style.useTab = value; itemEditInsertTab.checked = value; } get { return style.useTab; } };
784    property bool syntaxHighlighting { property_category $"Appearance" set { style.syntax = value; } get { return style.syntax; } };
785    property bool noSelect { property_category $"Behavior" set { style.noSelect = value; } get { return style.noSelect; } };
786    property bool allCaps { property_category $"Behavior" set { style.allCaps = value; } get { return style.allCaps; } };
787    property bool autoSize { property_category $"Behavior" set { style.autoSize = value; } get { return style.autoSize; } };
788    property bool wrap { set { style.wrap = value; Update(null); } get { return style.wrap; } };
789    //property bool lineNumbers { set { style.lineNumbers = value; } get { return style.lineNumbers; } };
790    property int numLines { get { return this ? lineCount : 0; } };
791    property int lineNumber { get { return y; } };  // TODO: Change to property of EditLine    this.line.number
792    property int column { get { return col; } };   // TODO: Add Set
793    property int charPos { get { return x; } };   // TODO: Add Set
794    property EditLine firstLine { get { return lines.first; } };      // Change these to a List<EditLine>... (this.lines[10].text)
795    property EditLine lastLine  { get { return lines.last; } };
796    property EditLine line { get { return this.line; } }; // TODO: Add Set   this.line = this.lines[10]
797    property char * contents
798    {
799       property_category $"Data"
800       set
801       {
802          if(this)
803          {
804             undoBuffer.dontRecord++;
805             Deselect();
806             DelCh(this.lines.first, 0, 0, this.lines.last, this.lineCount-1, ((EditLine)(this.lines.last)).count, true);
807             if(value)
808                AddS(value);
809             //SetViewToCursor(true);
810             UpdateDirty();
811             Home();
812             undoBuffer.dontRecord--;
813          }
814       }
815
816       get
817       {
818          char * buffer = null;
819          if(this)
820          {
821             /* Can't implement this right now because of memory leak... Need string reference counting...
822             if(style.multiLine)
823             {
824
825                EditLine line;
826                int len = 0;
827
828                for(line = lines.first; line; line = line.next)
829                   len += strlen(line.buffer);
830
831                buffer = new char[len+1];
832                len = 0;
833                for(line = lines.first; line; line = line.next)
834                {
835                   int lineLen = strlen(line.buffer);
836                   memcpy(buffer + len, line.buffer, lineLen);
837                   len += lineLen;
838                }
839                buffer[len] = '\0';
840             else
841                */
842                buffer = this.line ? this.line.buffer : null;
843          }
844          return buffer;
845       }
846    };
847    property bool overwrite { get { return overwrite; } };
848    property bool caretFollowsScrolling { get { return style.cursorFollowsView; } set { style.cursorFollowsView = value; } }
849
850    property char * multiLineContents
851    {
852       get
853       {
854          char * buffer = null;
855          if(style.multiLine)
856          {
857
858             EditLine line;
859             int len = 0;
860
861             for(line = lines.first; line; line = line.next)
862                len += strlen(line.buffer)+1;
863
864             buffer = new char[len+1];
865             len = 0;
866             for(line = lines.first; line; line = line.next)
867             {
868                int lineLen = strlen(line.buffer);
869                memcpy(buffer + len, line.buffer, lineLen);
870                len += lineLen;
871                if(line.next) buffer[len++] = '\n';
872             }
873             buffer[len] = '\0';
874          }
875          return buffer;
876       }
877    }
878
879    /*
880    char * GetLineText()
881    {
882       if(this)
883       {
884          return this.line.buffer;
885       }
886       return null;
887    }
888
889    void SetLineText(char * text)
890    {
891       if(this)
892       {
893          EditLine_SetText(this.line, text);
894       }
895    }
896    */
897    property Color selectionColor { set { selectionColor = value; } get { return selectionColor; } isset { return selectionColor ? true : false; } };
898    property Color selectionText  { set { selectionText = value; } get { return selectionText; } isset { return selectionText ? true : false; } };
899    property SyntaxColorScheme syntaxColorScheme { set { delete colorScheme; colorScheme = value; incref colorScheme; } }
900    property bool recordUndoEvent { set { undoBuffer.recordAsOne = value; undoBuffer.firstEvent = true; } get { return undoBuffer.recordAsOne; } };
901
902    // selectionStart.line, selectionStart.column (With Set)
903    // selection.line1, selection.line2, selection.column1, selection.column2  (Read only)
904
905    // Methods
906 private:
907    Font font;
908    EditBoxBits style;
909    int tabSize;
910    int maxLineSize;
911    int maxLines;
912    OldList lines;
913    int lineCount;
914    // Font Space size
915    Size space, large;
916
917    // Position of Caret (Not necessarily displayed position)
918    int x,y;
919    int col;
920    // Position of beginning of block (Block ends at (x,y))
921    int selX, selY;
922    // line is line at carret, selLine is line at beginning of block
923    EditLine line, selLine, dropLine;
924    // Mouse selection Moving virtual caret
925    int dropX, dropY;
926
927    bool selection;
928
929    // ViewX is x offset in pixels, ViewY is y offset in lines
930    int viewX, viewY;
931    // viewLine is first displayed line
932    EditLine viewLine;
933
934    // start and end of area to redraw
935    int startY, endY;
936
937    // MaxLine is the longest line
938    EditLine maxLine;
939    // MaxLength is the longest line's length
940    int maxLength;
941
942    // MouseSelect is true once button is pressed, overwrite is true if mode is on
943    bool mouseSelect, mouseMove, overwrite, wordSelect;
944    // Timer is used for mouse selection scrolling
945    Timer timer
946    {
947       window = this, delay = 0.1;
948
949       bool DelayExpired()
950       {
951          // LineDown();
952          /*
953          if(this.viewY > 200)
954             GoToLineNum(0);
955          */
956          OnMouseMove(mouseX, mouseY, -1);
957          return true;
958       }
959    };
960
961    // (mouseX,mouseY) is the position of the mouse in the edit box
962    int mouseX, mouseY;
963
964    bool modified;
965
966    void (* FontExtent)(Display display, Font font, char * text, int len, int * width, int * height);
967
968    Color backColor;
969    bool rightButtonDown;
970    bool pasteOperation;
971    int caretX, caretY;
972    UndoBuffer undoBuffer { data = this };
973    int savedAction;
974    ColorAlpha selectionColor, selectionText;
975    SyntaxColorScheme colorScheme { };
976
977    menu = Menu { };
978
979    // Edit Menu
980    Menu editMenu { menu, $"Edit", e };
981    MenuItem itemEditCut
982    {
983       editMenu, $"Cut\tCtrl+X", t, disabled = true;
984
985       bool NotifySelect(MenuItem item, Modifiers mods)
986       {
987          if(!(style.readOnly))
988             Cut();
989          return true;
990       }
991    };
992    MenuItem itemEditCopy
993    {
994       editMenu, $"Copy\tCtrl+C", c, disabled = true;
995
996       bool NotifySelect(MenuItem item, Modifiers mods)
997       {
998          Copy();
999          return true;
1000       }
1001    };
1002    MenuItem itemEditPaste
1003    {
1004       editMenu, $"Paste\tCtrl+V", p;
1005
1006       bool NotifySelect(MenuItem item, Modifiers mods)
1007       {
1008          if(!(style.readOnly))
1009             Paste();
1010          return true;
1011       }
1012    };
1013    MenuItem itemEditDelete
1014    {
1015       editMenu, $"Delete\tDel", d, disabled = true;
1016
1017       bool NotifySelect(MenuItem item, Modifiers mods)
1018       {
1019          if(!(style.readOnly))
1020             DeleteSelection();
1021          return true;
1022       }
1023    };
1024    MenuDivider { editMenu };
1025    MenuItem itemEditSelectAll
1026    {
1027       editMenu, $"Select All\tCtrl+A", a;
1028
1029       bool NotifySelect(MenuItem item, Modifiers mods)
1030       {
1031          SelectAll();
1032          return true;
1033       }
1034    };
1035    MenuDivider { editMenu };
1036    MenuItem itemEditUndo
1037    {
1038       editMenu, $"Undo\tCtrl+Z", u;
1039       disabled = true;
1040
1041       bool NotifySelect(MenuItem item, Modifiers mods)
1042       {
1043          Undo();
1044          return true;
1045       }
1046    };
1047    MenuItem itemEditRedo
1048    {
1049       editMenu, $"Redo\tCtrl+Y", o;
1050       disabled = true;
1051
1052       bool NotifySelect(MenuItem item, Modifiers mods)
1053       {
1054          Redo();
1055          return true;
1056       }
1057    };
1058    MenuDivider { editMenu };
1059    MenuItem
1060    {
1061       editMenu, $"Find Previous\tShift-F3", e, shiftF3;
1062
1063       bool NotifySelect(MenuItem item, Modifiers mods)
1064       {
1065          if(searchString[0])
1066             Find(searchString, wholeWord, matchCase, false);
1067          else
1068             itemEditFind.NotifySelect(this, item, mods);
1069          return true;
1070       }
1071    };
1072    MenuItem
1073    {
1074       editMenu, $"Find Next\tF3", n, f3;
1075
1076       bool NotifySelect(MenuItem item, Modifiers mods)
1077       {
1078          if(searchString[0])
1079             Find(searchString, wholeWord, matchCase, true);
1080          else
1081             itemEditFind.NotifySelect(this, item, mods);
1082          return true;
1083       }
1084    };
1085    MenuItem itemEditFind
1086    {
1087       editMenu, $"Find...\tCtrl+F", f, ctrlF;
1088
1089       bool NotifySelect(MenuItem item, Modifiers mods)
1090       {
1091          FindDialog dialog
1092          {
1093             editBox = this, master = master, isModal = true, searchString = searchString, matchCase = matchCase, wholeWord = wholeWord,
1094             searchUp = searchUp;
1095
1096             // TODO:
1097             // Fix dialog from above shouldn't be visible
1098             // void NotifyDestroyed(FindDialog dialog, DialogResult result)
1099             void NotifyDestroyed(Window window, DialogResult result)
1100             {
1101                FindDialog dialog = (FindDialog) window;
1102                searchUp = dialog.searchUp;
1103                strcpy(searchString, dialog.searchString);
1104                matchCase = dialog.matchCase;
1105                wholeWord = dialog.wholeWord;
1106             }
1107          };
1108          dialog.Create();
1109          return true;
1110       }
1111    };
1112    MenuItem
1113    {
1114       editMenu, $"Replace...\tCtrl+R", r, ctrlR;
1115
1116       bool NotifySelect(MenuItem item, Modifiers mods)
1117       {
1118          ReplaceDialog dialog
1119          {
1120             master = master,
1121             isModal = true,
1122             searchString = searchString,
1123             replaceString = replaceString,
1124             matchCase = matchCase,
1125             wholeWord = wholeWord,
1126             editBox = this;
1127
1128             // TODO:
1129             // void NotifyDestroyed(ReplaceDialog dialog, DialogResult result)
1130             void NotifyDestroyed(Window window, DialogResult result)
1131             {
1132                ReplaceDialog dialog = (ReplaceDialog)window;
1133                char * replace = dialog.replaceString;
1134                if(replace)
1135                   strcpy(replaceString, replace);
1136                strcpy(searchString, dialog.searchString);
1137                matchCase = dialog.matchCase;
1138                wholeWord = dialog.wholeWord;
1139             }
1140          };
1141          dialog.Create();
1142          return true;
1143       }
1144    };
1145    MenuDivider { editMenu };
1146    MenuItem
1147    {
1148       editMenu, $"Go To...\tCtrl+G", g, ctrlG;
1149
1150       bool NotifySelect(MenuItem item, Modifiers mods)
1151       {
1152          goToDialog.editBox = this;
1153          goToDialog.master = master;
1154          goToDialog.Create();
1155          return true;
1156       }
1157    };
1158    MenuDivider { editMenu };
1159    MenuItem itemEditInsertTab
1160    {
1161       editMenu, $"Insert Tabs", i, checkable = true;
1162
1163       bool NotifySelect(MenuItem item, Modifiers mods)
1164       {
1165          style.useTab = item.checked;
1166          return true;
1167       }
1168    };
1169
1170    borderStyle = deep;
1171    snapVertScroll = true;
1172    snapHorzScroll = true;
1173
1174    EditBox()
1175    {
1176       static bool syntaxInit = false;
1177       if(!syntaxInit)
1178       {
1179          int g,c;
1180          syntaxInit = true;
1181          for(g = 0; g<NUM_KEYWORD_GROUPS; g++)
1182          {
1183             for(c = 0; keyWords[g][c]; c++);
1184             //keyLen[g] = new int[c];
1185             for(c = 0; keyWords[g][c]; c++)
1186             {
1187                keyLen[g][c] = strlen(keyWords[g][c]);
1188             }
1189          }
1190
1191          colorScheme.commentColor = dimGray;
1192          colorScheme.charLiteralColor = crimson;
1193          colorScheme.stringLiteralColor = crimson;
1194          colorScheme.preprocessorColor = green;
1195          colorScheme.numberColor = teal;
1196          colorScheme.keywordColors = [ blue, blue ];
1197       }
1198
1199       FontExtent = Display::FontExtent;
1200       font = fontObject;
1201       lines.offset = (uint)&((EditLine)0).prev;
1202
1203       style = EditBoxBits { hScroll = true };
1204
1205       // cursor = guiApp.GetCursor(IBeam);
1206
1207       // Default Properties
1208       /*maxLines = 65536;
1209       maxLineSize = 1024;*/
1210
1211       maxLines = MAXINT;
1212       maxLineSize = MAXINT;
1213
1214       tabSize = 3;
1215
1216       overwrite = false;
1217       mouseSelect = this.mouseMove = false;
1218       line = null;
1219       lineCount = 0;
1220       x = selX = selY = 0;
1221       col = 0;
1222       y = -1;
1223       line = selLine = null;
1224       viewX = 0;
1225       viewY = 0;
1226       maxLength = 0;
1227       maxLine = null;
1228       startY = 0;
1229       endY = clientSize.h;
1230       wordSelect = false;
1231
1232       // Default space to 8 in case window is not constructed
1233       space = { 8, 8 };
1234
1235       undoBuffer.dontRecord++;
1236       AddCh('\n');
1237       undoBuffer.dontRecord--;
1238
1239       viewLine = lines.first;
1240       style.recomputeSyntax = true;
1241
1242       FindMaxLine();
1243
1244       FixScrollArea();
1245
1246       UpdateCaretPosition(true);
1247       return true;
1248    }
1249
1250    ~EditBox()
1251    {
1252       lines.Free(EditLine::Free);
1253    }
1254
1255    void FlushBuffer(Surface surface, EditLine line, int wc, int * renderStart, int * x, int y, int numSpaces, bool drawSpaces, Box box)
1256    {
1257       int count = wc - *renderStart;
1258       if(count)
1259       {
1260          if(y + space.h >= box.top && y <= box.bottom)
1261          {
1262             int w;
1263
1264             if(!numSpaces)
1265             {
1266                //FontExtent(display, font, line.buffer + *renderStart, count, &w, null);
1267                surface.TextFont(font);
1268                surface.TextExtent(line.buffer + *renderStart, count, &w, null);
1269                if(*x + w + XOFFSET > 0)
1270                   surface.WriteText(XOFFSET + *x,y, line.buffer + *renderStart, count);
1271                *x += w;
1272             }
1273             else
1274             {
1275                w = numSpaces; // * space.w;
1276                if(*x + w + XOFFSET > 0 && (drawSpaces || surface.GetTextOpacity()))
1277                   surface.Area(XOFFSET + *x - 1, y, XOFFSET + *x + w, y + space.h-1);
1278                   // WHATS UP WITH THIS...  surface.Area(XOFFSET + *x, y, XOFFSET + *x + w, y + space.h-1);
1279                *x += w;
1280             }
1281          }
1282          *renderStart = wc;
1283       }
1284    }
1285
1286    int CheckColors(EditLine line, int wc, bool selection, int selX, int editX, bool *selected,
1287                    Color selectionForeground, Color selectionBackground, Color textColor, Color *foreground, Color *background, bool *opacity, bool *overwrite)
1288    {
1289       bool flush = false;
1290
1291       if(selection)
1292       {
1293          if((wc == selX && line == selLine) || (wc == editX && line == this.line))
1294          {
1295             *selected ^= 1;
1296
1297             *foreground = (*selected) ? selectionForeground : textColor;
1298             *background = selectionBackground;
1299             *opacity = *selected;
1300
1301             flush = true;
1302          }
1303       }
1304
1305       // Overwrite Carret
1306       if(this.overwrite && active)
1307       {
1308          if((style.stuckCaret && wc == line.count && !line.next) ||
1309             (!mouseMove && line == this.line && wc == editX))
1310          {
1311             *overwrite = true;
1312             flush = true;
1313          }
1314       }
1315       return flush;
1316    }
1317
1318    void FigureStartSyntaxStates(EditLine firstLine, bool reset)
1319    {
1320       if(style.syntax)
1321       {
1322          bool inMultiLineComment = reset ? false : style.inMultiLineComment;
1323          bool wasInMultiLine = reset ? false : style.wasInMultiLine;
1324          bool inString = false;
1325          bool inQuotes = false;
1326          bool inPrep = reset ? false : style.inPrep;
1327          bool inSingleLineComment = false;
1328          bool escaped = reset ? false : style.escaped;
1329          bool continuedSingleLineComment = reset ? false : style.continuedSingleLineComment;
1330          bool continuedString = reset ? false : style.continuedString;
1331          bool continuedQuotes = reset ? false : style.continuedQuotes;
1332
1333          EditLine line = reset ? lines.first : firstLine;
1334          // int maxBackUp = 1000, c;
1335          // for(c = 0, line = viewLine; c < maxBackUp && line && line.prev; line = line.prev);
1336          for(; line != viewLine; line = line.next)
1337          {
1338             char * text = line.buffer;
1339             int c;
1340             char ch;
1341             bool lastWasStar = false;
1342             bool firstWord = true;
1343             if(!escaped) inPrep = false;
1344             inSingleLineComment = continuedSingleLineComment;
1345             escaped = false;
1346             inString = continuedString;
1347             inQuotes = continuedQuotes;
1348
1349             firstWord = true;
1350             for(c = 0; (ch = text[c]); c++)
1351             {
1352                bool wasEscaped = escaped;
1353                bool backLastWasStar = lastWasStar;
1354                bool backWasInMultiLine = wasInMultiLine;
1355                escaped = false;
1356                lastWasStar = false;
1357                wasInMultiLine = inMultiLineComment;
1358                if(ch == '/')
1359                {
1360                   if(!inSingleLineComment && !inMultiLineComment && !inQuotes && !inString)
1361                   {
1362                      if(text[c+1] == '/')
1363                      {
1364                         inSingleLineComment = true;
1365                      }
1366                      else if(text[c+1] == '*')
1367                      {
1368                         inMultiLineComment = true;
1369                      }
1370                   }
1371                   else if(backLastWasStar)
1372                      inMultiLineComment = false;
1373                }
1374                else if(ch == '*')
1375                {
1376                   if(backWasInMultiLine) lastWasStar = true;
1377                }
1378                else if(ch == '\"' && !inSingleLineComment && !inMultiLineComment && !inQuotes)
1379                {
1380                   if(inString && !wasEscaped)
1381                   {
1382                      inString = false;
1383                   }
1384                   else
1385                   {
1386                      inString = true;
1387                   }
1388                }
1389                else if(ch == '\'' && !inSingleLineComment && !inMultiLineComment && !inString)
1390                {
1391                   if(inQuotes && !wasEscaped)
1392                      inQuotes = false;
1393                   else
1394                   {
1395                      inQuotes = true;
1396                   }
1397                }
1398                else if(ch == '\\')
1399                {
1400                   if(!wasEscaped)
1401                      escaped = true;
1402                }
1403                else if(ch == '#' && !inQuotes && !inString && !inMultiLineComment && !inSingleLineComment)
1404                {
1405                   if(firstWord)
1406                   {
1407                      inPrep = true;
1408                   }
1409                }
1410                else if(ch != ' ' && ch != '\t')
1411                   firstWord = false;
1412             }
1413             if(line.count && line.text[line.count - 1] == '\\')
1414             {
1415                continuedSingleLineComment = inSingleLineComment;
1416                continuedString = inString;
1417                continuedQuotes = inQuotes;
1418             }
1419             else
1420             {
1421                continuedSingleLineComment = false;
1422                continuedString = false;
1423                continuedQuotes = false;
1424             }
1425          }
1426
1427          style.continuedSingleLineComment = continuedSingleLineComment;
1428          style.continuedString = continuedString;
1429          style.continuedQuotes = continuedQuotes;
1430          style.inMultiLineComment = inMultiLineComment;
1431          style.wasInMultiLine = wasInMultiLine;
1432          style.inPrep = inPrep;
1433          style.escaped = escaped;
1434       }
1435    }
1436
1437    /*void OnDrawOverChildren(Surface surface)
1438    {
1439       if(style.lineNumbers)
1440       {
1441          int currentLineNumber = this.viewY + 1;
1442          int i;
1443          char lineText[256];
1444          for(i = 0; i * space.h < box.height; i++)
1445          {
1446             // ********* LINE NUMBERING *********
1447             surface.SetForeground(Color{60, 60, 60});
1448             //Highlight current line
1449             if(this.caretY / space.h == currentLineNumber - 1)
1450                surface.SetBackground(Color{220, 220, 220});
1451             else
1452                surface.SetBackground(Color{230, 230, 230});
1453             surface.textOpacity = true;
1454
1455             sprintf(lineText,"%5u ", currentLineNumber % 100000);
1456             if(currentLineNumber > this.lineCount)
1457                surface.WriteText(0,i*space.h+1,"      ",6);
1458             else
1459                surface.WriteText(0,i*space.h+1,lineText,6);
1460
1461             currentLineNumber++;
1462          }
1463       }
1464    }*/
1465
1466    void OnRedraw(Surface surface)
1467    {
1468       EditLine line;
1469       int y = YOFFSET;
1470       bool selected = false, selection = true;
1471       int selX, editX;
1472       Color selectionBackground = selectionColor ? selectionColor : SELECTION_COLOR;
1473       Color selectionForeground = selectionText ? selectionText : SELECTION_TEXT;
1474       Color defaultTextColor = property::foreground;
1475       Color textColor;
1476       Box box;
1477       int maxW = clientSize.w;
1478
1479       Color foreground, background;
1480       bool opacity;
1481
1482       // Overwrite Caret Stuff
1483       bool overWrite = false;
1484       int overWriteX, overWriteY;
1485       byte overWriteCh;
1486
1487       // ****** SYNTAX STATES ******
1488       bool inMultiLineComment = style.inMultiLineComment;
1489       bool inString = false;
1490       bool inQuotes = false;
1491       bool inPrep = style.inPrep;
1492       bool inSingleLineComment = false;
1493       bool escaped = style.escaped;
1494       bool continuedSingleLineComment = style.continuedSingleLineComment;
1495       bool continuedString = style.continuedString;
1496       bool continuedQuotes = style.continuedQuotes;
1497       bool wasInMultiLine = style.wasInMultiLine;
1498       // ****** ************* ******
1499
1500       if(!isEnabled)
1501          defaultTextColor = Color { 85, 85, 85 };
1502       textColor = defaultTextColor;
1503
1504       if(
1505          Abs(selectionBackground.r - property::background.r) +
1506          Abs(selectionBackground.g - property::background.g) +
1507          Abs(selectionBackground.b - property::background.b) < 92)
1508       {
1509          selectionBackground = formColor;
1510          selectionForeground = selectionColor ? selectionColor : SELECTION_COLOR;
1511       }
1512
1513       surface.TextFont(this.font);
1514    /*
1515       surface.SetBackground(GDefaultPalette()[RND_Get(1, 15)]);
1516       surface.Area(0,0,MAXINT,MAXINT);
1517    */
1518
1519       if(!isEnabled)
1520       {
1521          surface.SetBackground(formColor);
1522          surface.Area(0,0,clientSize.w, clientSize.h);
1523       }
1524
1525       if(this.selX == this.x && this.selY == this.y)
1526          selection = false;
1527
1528       if(style.freeCaret)
1529       {
1530          editX = this.x;
1531          selX  = this.selX;
1532       }
1533       else
1534       {
1535          editX = Min(this.x,this.line.count);
1536          selX  = Min(this.selX,this.selLine.count);
1537       }
1538
1539       selected = (this.selY < this.viewY) ^ (this.y < this.viewY);
1540
1541       foreground = selected ? selectionForeground : textColor;
1542       background = selectionBackground;
1543       opacity = selected;
1544    /*
1545       opacity = true;
1546       background = WHITE;
1547    */
1548       surface.SetForeground(foreground);
1549       surface.SetBackground(background);
1550       surface.TextOpacity(opacity);
1551
1552       surface.GetBox(box);
1553
1554       for(line = this.viewLine; line; line = line.next)
1555       {
1556          int x = -this.viewX;
1557          int end;
1558          int c;
1559          int start = 0;
1560          Color newTextColor = textColor = defaultTextColor;
1561          bool lineComplete = false;
1562
1563
1564          // ****** SYNTAX HIGHLIGHTING ******
1565          bool lastWasStar = false;
1566          bool trailingSpace = false;
1567          bool firstWord = true;
1568          if(!escaped) inPrep = false;
1569          inSingleLineComment = continuedSingleLineComment;
1570          escaped = false;
1571          inString = continuedString;
1572          inQuotes = continuedQuotes;
1573          // *********************************
1574
1575    /*   === DEBUGGING TOOL FOR MAXLINE ===
1576
1577          if(line == this.maxLine)
1578          {
1579             surface.SetBackground(GREEN|0xFF000000);
1580             surface.TextOpacity(true);
1581          }
1582          else
1583          {
1584             surface.TextOpacity(selected ? true : false);
1585             surface.SetBackground(selected ? SELECTION_COLOR|0xFF000000 : BLACK|0xFF000000);
1586          }
1587    */
1588
1589          if(line == this.selLine && line == this.line)
1590          {
1591             end = Max(line.count, this.x);
1592             end = Max(end, this.selX);
1593          }
1594          else if(line == this.selLine)
1595             end = Max(line.count, this.selX);
1596          else if(line == this.line)
1597             end = Max(line.count, this.x);
1598          else
1599             end = line.count;
1600
1601          for(c=0; c<end; )
1602          {
1603             int bufferLen = 0;
1604             int wordLen = 0;
1605             bool spacing = true;
1606             bool cantHaveWords = false;
1607             int w = 0;
1608
1609             if(lineComplete)
1610             {
1611                x = -viewX;
1612                y += space.h;
1613                lineComplete = false;
1614             }
1615
1616             textColor = newTextColor;
1617             if(!selected)
1618             {
1619                foreground = textColor;
1620                surface.SetForeground(textColor);
1621             }
1622
1623             // Look at words
1624             for(; c<end && !cantHaveWords;)
1625             {
1626                if(wordLen)
1627                {
1628                   bufferLen += wordLen;
1629                   wordLen = 0;
1630                }
1631 #ifdef _DEBUG
1632                /*if(line.count > 4000)
1633                {
1634                   printf("oh");
1635                   CheckMemory();
1636                }*/
1637 #endif
1638                // Parse Words
1639                for(; c<line.count; c++)
1640                {
1641                   unichar ch = line.buffer[c];
1642                   unichar bf = (wordLen == 1) ? line.buffer[c-1] : 0;
1643                   //if(ch == ' ' || ch == '\t' || (wordLen && (ch == '(' || ch == ')' || ch == ';' || ch == ':')) || (wordLen == 1 && line.buffer[c-1] == '('))
1644                   if(CharMatchCategories(ch, separators) || /*ch == ' ' ||*/ ch == '\t' ||
1645                      (wordLen && !CharMatchCategories(ch, numbers|letters|marks|connector) && ch != '#' /*&& ch != '_'*/) ||
1646                      (bf && !CharMatchCategories(bf, numbers|letters|separators|marks|connector) && bf != '#' && bf != '\t' /*&& bf != '_' && bf != ' '*/))
1647                      break;
1648                   wordLen++;
1649                   trailingSpace = false;
1650                }
1651
1652                if(!wordLen)
1653                {
1654
1655                   for(; c<line.count; c++)
1656                   {
1657                      unichar ch = line.buffer[c];
1658                      if(ch == '\t' || ch == ' ')
1659                      {
1660                         cantHaveWords = true;
1661                         if(ch == ' ' && c == line.count-1)
1662                            trailingSpace = true;
1663                         if(bufferLen)
1664                            break;
1665                      }
1666                      if(ch != ' ' && ch != '\t')
1667                         break;
1668                      wordLen++;
1669                   }
1670
1671                   if(c == line.count && c < end)
1672                   {
1673                      if(bufferLen)
1674                      {
1675                         c -= wordLen;
1676                         break;
1677                      }
1678                      wordLen += end - c;
1679                      c = end;
1680                      cantHaveWords = true;
1681                   }
1682
1683                   if(c < end)
1684                      escaped = false;
1685                   lastWasStar = false;
1686                }
1687                else
1688                {
1689                   if(isEnabled)
1690                   {
1691                      bool backEscaped = escaped;
1692                      bool backLastWasStar = lastWasStar;
1693                      bool backInMultiLineComment = inMultiLineComment;
1694                      bool backInString = inString;
1695                      bool backInQuotes = inQuotes;
1696                      bool backInPrep = inPrep;
1697                      bool backInSingleLineComment = inSingleLineComment;
1698                      bool backWasInMultiLine = wasInMultiLine;
1699
1700                      char * word = line.buffer + c - wordLen;
1701                      int g,ccc;
1702                      bool wasEscaped = escaped;
1703                      escaped = false;
1704                      lastWasStar = false;
1705
1706                      wasInMultiLine = inMultiLineComment;
1707
1708                      // Determine Syntax Highlighting
1709                      newTextColor = defaultTextColor;
1710                      if(style.syntax)
1711                      {
1712                         if(inSingleLineComment || inMultiLineComment)
1713                         {
1714                            newTextColor = colorScheme.commentColor;
1715                         }
1716                         else if(inQuotes)
1717                         {
1718                            newTextColor = colorScheme.charLiteralColor;
1719                         }
1720                         else if(inString)
1721                         {
1722                            newTextColor = colorScheme.stringLiteralColor;
1723                         }
1724                         else if(inPrep)
1725                         {
1726                            newTextColor = colorScheme.preprocessorColor;
1727                         }
1728                         if(wordLen == 1 && word[0] == '/')
1729                         {
1730                            if(!inSingleLineComment && !inMultiLineComment && !inQuotes && !inString)
1731                            {
1732                               if(word[1] == '/')
1733                               {
1734                                  inSingleLineComment = true;
1735                                  newTextColor = colorScheme.commentColor;
1736                               }
1737                               else if(word[1] == '*')
1738                               {
1739                                  inMultiLineComment = true;
1740                                  newTextColor = colorScheme.commentColor;
1741                               }
1742                            }
1743                            else if(backLastWasStar)
1744                               inMultiLineComment = false;
1745                         }
1746                         else if(wordLen == 1 && word[0] == '*')
1747                         {
1748                            if(backWasInMultiLine)
1749                               lastWasStar = true;
1750                         }
1751                         else if(!inSingleLineComment && !inMultiLineComment && !inQuotes && wordLen == 1 && word[0] == '\"')
1752                         {
1753                            if(inString && !wasEscaped)
1754                            {
1755                               inString = false;
1756                            }
1757                            else
1758                            {
1759                               inString = true;
1760                               newTextColor = colorScheme.stringLiteralColor;
1761                            }
1762                         }
1763                         else if(!inSingleLineComment && !inMultiLineComment && !inString && wordLen == 1 && word[0] == '\'')
1764                         {
1765                            if(inQuotes && !wasEscaped)
1766                               inQuotes = false;
1767                            else
1768                            {
1769                               inQuotes = true;
1770                               newTextColor = colorScheme.charLiteralColor;
1771                            }
1772                         }
1773                         else if(wordLen == 1 && word[0] == '\\')
1774                         {
1775                            if(!wasEscaped)
1776                               escaped = true;
1777                         }
1778                         else if(!inQuotes && !inString && !inMultiLineComment && !inSingleLineComment && (isdigit(word[0]) || (word[0] == '.' && isdigit(word[1]))))
1779                         {
1780                            char * dot = strchr(word, '.');
1781                            bool isHex = (word[0] == '0' && (word[1] == 'x' || word[1] == 'X'));
1782                            char * exponent;
1783                            bool isReal;
1784                            char * s = null;
1785                            if(isHex)
1786                            {
1787                               exponent = strchr(word, 'p');
1788                               if(!exponent) exponent = strchr(word, 'P');
1789                            }
1790                            else
1791                            {
1792                               exponent = strchr(word, 'e');
1793                               if(!exponent) exponent = strchr(word, 'E');
1794                            }
1795                            if(exponent && exponent > word + wordLen) exponent = null;
1796                            if(dot && dot > word + wordLen) dot = null;
1797                            isReal = dot || exponent;
1798                            if(isReal)
1799                               strtod(word, &s);      // strtod() seems to break on hex floats (e.g. 0x23e3p12, 0x1.fp3)
1800                            else
1801                               strtol(word, &s, 0);
1802                            if(s && s != word)
1803                            {
1804                               // Check suffixes
1805                               char ch;
1806                               int i;
1807                               int gotF = 0, gotL = 0, gotU = 0, gotI = 0;
1808                               bool valid = true;
1809
1810                               for(i = 0; valid && i < 5 && (ch = s[i]) && (isalnum(ch) || ch == '_'); i++)
1811                               {
1812                                  switch(ch)
1813                                  {
1814                                     case 'f': case 'F': gotF++; if(gotF > 1 || !isReal) valid = false; break;
1815                                     case 'l': case 'L':
1816                                        gotL++;
1817                                        if(gotL > 2 || isReal || (gotL == 2 && (s[i-1] != ch)))
1818                                        valid = false;
1819                                        break;
1820                                     case 'u': case 'U': gotU++; if(gotU > 1 || isReal) valid = false; break;
1821                                     case 'i': case 'I': case 'j': case 'J': gotI++; if(gotI > 1) valid = false; break;
1822                                     default: valid = false;
1823                                  }
1824                               }
1825
1826                               // Don't highlight numbers with too many decimal points
1827                               if(s[0] == '.' && isdigit(s[1]))
1828                               {
1829                                  int newWordLen;
1830                                  while(s[0] == '.' && isdigit(s[1]))
1831                                  {
1832                                     int newWordLen = s - word;
1833                                     c += newWordLen - wordLen;
1834                                     wordLen = newWordLen;
1835                                     strtod(s, &s);
1836                                  }
1837                                  newWordLen = s - word;
1838                                  c += newWordLen - wordLen;
1839                                  wordLen = newWordLen;
1840                               }
1841                               else if(valid)
1842                               {
1843                                  int newWordLen = s + i - word;
1844                                  newTextColor = colorScheme.numberColor;
1845                                  c += newWordLen - wordLen;
1846                                  wordLen = newWordLen;
1847                               }
1848                               else if(dot && dot > word && dot < s)
1849                                  newTextColor = colorScheme.numberColor;
1850                            }
1851                         }
1852                         else
1853                         {
1854                            if(!inQuotes && !inString && !inMultiLineComment && !inSingleLineComment && word[0] == '#')
1855                            {
1856                               if(firstWord)
1857                               {
1858                                  inPrep = true;
1859                                  newTextColor = colorScheme.preprocessorColor;
1860                               }
1861                            }
1862                            if(!inQuotes && !inString && !inMultiLineComment && !inSingleLineComment)
1863                            {
1864                               for(g = 0; g < ((inPrep && word[0] != '#') ? 2 : 1); g++)
1865                               {
1866                                  char ** keys = keyWords[g];
1867                                  int * len = keyLen[g];
1868                                  for(ccc = 0; keys[ccc]; ccc++)
1869                                  {
1870                                     if(len[ccc] == wordLen && !strncmp(keys[ccc], word, wordLen))
1871                                     {
1872                                        newTextColor = colorScheme.keywordColors[g];
1873                                        break;
1874                                     }
1875                                  }
1876                               }
1877                            }
1878                         }
1879                         firstWord = false;
1880
1881                         // If highlighting for this word is different, break
1882                         if(newTextColor != textColor)
1883                         {
1884                            if(bufferLen)
1885                            {
1886                               // Better solution than going back?
1887                               c -= wordLen;
1888
1889                               // Reset syntax flags
1890                               escaped = backEscaped;
1891                               lastWasStar = backLastWasStar;
1892                               inMultiLineComment = backInMultiLineComment;
1893                               inString = backInString;
1894                               inQuotes = backInQuotes;
1895                               inPrep = backInPrep;
1896                               inSingleLineComment = backInSingleLineComment;
1897                               wasInMultiLine = backWasInMultiLine;
1898                               break;
1899                            }
1900                            else
1901                            {
1902                               textColor = newTextColor;
1903                               if(!selected)
1904                               {
1905                                  foreground = textColor;
1906                                  surface.SetForeground(textColor);
1907                               }
1908                            }
1909                         }
1910                      }
1911                   }
1912
1913                   // If we're not breaking, this can't be rendered as spacing anymore
1914                   spacing = false;
1915
1916                   // Do word wrapping here
1917                   if(style.wrap)
1918                   {
1919                      //if(!numSpaces)
1920                      {
1921                         int tw;
1922                         FontExtent(display, font, line.buffer + start, bufferLen + wordLen, &tw, null);
1923                         w = tw;
1924                      }
1925                      /*else
1926                      {
1927                         w += numSpaces * space.w;
1928                      }*/
1929                      if(x + viewX + w > maxW)
1930                      {
1931                         // Avoid an endless loop until we fix wrapping
1932                         if(c - wordLen > start)
1933                         {
1934                            c -= wordLen;
1935                            lineComplete = true;
1936                            break;
1937                         }
1938                      }
1939                   }
1940                }
1941                bufferLen += wordLen;
1942                wordLen = 0;
1943             }
1944
1945             {
1946                int renderStart = start;
1947                bool flush = false;
1948                bool flagTrailingSpace = false;
1949                int numSpaces = 0;
1950                int wc;
1951
1952                // Render checking if we need to split because of selection or to find where to draw insert caret
1953                for(wc = start; wc < start + bufferLen; wc++)
1954                {
1955                   flush = CheckColors(line, wc, selection, selX, editX, &selected, selectionForeground,
1956                      selectionBackground, textColor, &foreground, &background, &opacity, &overWrite);
1957                   if(overWrite == true)
1958                   {
1959                      overWriteCh = (wc < line.count) ? line.buffer[wc] : ' ';
1960                      if(overWriteCh == '\t') overWriteCh = ' ';
1961                   }
1962
1963                   if(flush)
1964                   {
1965                      flagTrailingSpace = numSpaces && trailingSpace && style.syntax && start + bufferLen == line.count && line != this.line;
1966                      if(flagTrailingSpace) surface.SetBackground(red);
1967                      FlushBuffer(surface, line, wc, &renderStart, &x, y, numSpaces, flagTrailingSpace, box);
1968                      if(overWrite == true)
1969                      {
1970                         overWriteX = x;
1971                         overWriteY = y;
1972                         overWrite = 2;
1973                      }
1974                      numSpaces = 0;
1975
1976                      surface.TextOpacity(opacity);
1977                      surface.SetBackground(background);
1978                      surface.SetForeground(foreground);
1979
1980                      flush = false;
1981                   }
1982
1983                   if(spacing)
1984                   {
1985                      if(wc < line.count && line.buffer[wc] == '\t')
1986                      {
1987                         numSpaces += (tabSize * space.w) - ((x + numSpaces + viewX) % (tabSize * space.w));
1988                      }
1989                      else
1990                      {
1991                         numSpaces += space.w;
1992                      }
1993                   }
1994                }
1995                flagTrailingSpace = numSpaces && trailingSpace && style.syntax && start + bufferLen == line.count && line != this.line;
1996                if(flagTrailingSpace) surface.SetBackground(red);
1997                FlushBuffer(surface, line, wc, &renderStart, &x, y, numSpaces, flagTrailingSpace, box);
1998                start += bufferLen;
1999             }
2000          }
2001
2002          if(CheckColors(line, c, selection, selX, editX, &selected, selectionForeground,
2003                         selectionBackground, textColor, &foreground, &background, &opacity, &overWrite))
2004          {
2005             if(overWrite == true)
2006             {
2007                overWriteX = x;
2008                overWriteY = y;
2009                overWriteCh = ' ';
2010                overWrite = 2;
2011             }
2012             surface.TextOpacity(opacity);
2013             surface.SetBackground(background);
2014             surface.SetForeground(foreground);
2015          }
2016
2017          if(style.freeCaret && selected)
2018          {
2019             surface.SetBackground(selectionBackground);
2020             surface.Area(x + XOFFSET - 1,y,clientSize.w-1,y+this.space.h-1);
2021             // TEST: surface.Area(x + XOFFSET,y,clientSize.w-1,y+this.space.h-1);
2022          }
2023
2024
2025          /*
2026          if(style.freeCaret && selected)
2027          {
2028             surface.SetBackground(selectionBackground);
2029             surface.Area(x + XOFFSET - 1,y,clientSize.w-1,y+this.space.h-1);
2030          }
2031          */
2032          if(line.count && line.text[line.count - 1] == '\\')
2033          {
2034             continuedSingleLineComment = inSingleLineComment;
2035             continuedString = inString;
2036             continuedQuotes = inQuotes;
2037          }
2038          else
2039          {
2040             continuedSingleLineComment = false;
2041             continuedString = false;
2042             continuedQuotes = false;
2043          }
2044
2045          y+=this.space.h;
2046          if(y > box.bottom) // >=clientSize.h)
2047             break;
2048       }
2049
2050       if(overWrite)
2051       {
2052          surface.TextOpacity(true);
2053          surface.SetForeground(black);
2054          surface.SetBackground(Color {255,255,85});
2055          surface.WriteText(XOFFSET + overWriteX,overWriteY, &overWriteCh, 1);
2056       }
2057    }
2058
2059    void FixScrollArea()
2060    {
2061       if(style.hScroll || style.vScroll)
2062       {
2063          int width = maxLength + XOFFSET;
2064          int height = lineCount * space.h;
2065          if(style.freeCaret && line)
2066          {
2067             if(x > selX)
2068             {
2069                if(x > line.count)
2070                   width = Max(line.length + (x - line.count) * space.w, maxLength + XOFFSET);
2071             }
2072             else
2073             {
2074                if(selX > selLine.count)
2075                   width = Max(selLine.length + (selX - selLine.count) * space.w, maxLength + XOFFSET);
2076             }
2077          }
2078
2079          width += space.w;
2080          SetScrollLineStep(8, space.h);
2081          SetScrollArea(width, height, true);
2082       }
2083    }
2084
2085    void ComputeLength(EditLine line)
2086    {
2087       int c;
2088       int tabOccur = 0;
2089       int tabWidth;
2090       int x = 0;
2091
2092       for(c = 0; c < line.count; )
2093       {
2094          int len = 1;
2095          int start = c;
2096          int w;
2097          if(c < line.count)
2098          {
2099             byte ch = 0;
2100             int numBytes;
2101             for(len = 0; c < line.count; c += numBytes)
2102             {
2103                ch = line.buffer[c];
2104                numBytes = UTF8_NUM_BYTES(ch);
2105
2106                if(ch == ' ' || ch == '\t')
2107                {
2108                   if(!len) c++;
2109                   break;
2110                }
2111                len += numBytes;
2112             }
2113             if(!len && ch == ' ')
2114             {
2115                len = 1;
2116                w = space.w;
2117             }
2118             else if(!len && ch == '\t')
2119             {
2120                w = (tabSize * space.w) - (x % (tabSize * space.w));
2121                len = 1;
2122             }
2123             else
2124                FontExtent(display, font, line.buffer + start, len, &w, null);
2125          }
2126          else
2127          {
2128             w = space.w;
2129             c++;
2130          }
2131          x += w;
2132       }
2133       line.length = x;
2134
2135       if(line.length > this.maxLength)
2136       {
2137          this.maxLine = line;
2138          this.maxLength = line.length;
2139
2140          if(style.autoSize) AutoSize();
2141       }
2142    }
2143
2144    void FindMaxLine()
2145    {
2146       EditLine line;
2147
2148       this.maxLength = 0;
2149       this.maxLine = null;
2150
2151       for(line = lines.first; line; line = line.next)
2152       {
2153          if(line.length > this.maxLength)
2154          {
2155             this.maxLength = line.length;
2156             this.maxLine = line;
2157          }
2158       }
2159
2160       if(style.autoSize) AutoSize();
2161    }
2162
2163    void SelDirty()
2164    {
2165       if(this.selY != this.y)
2166          DirtyAll();
2167       else if(this.selX != this.x)  // commented out to erase caret: if(this.selX != this.x)
2168          DirtyLine(this.y);
2169    }
2170
2171    void ComputeColumn()
2172    {
2173       // Adjust new edit X position according to tabs
2174       int c, position = 0;
2175       unichar ch;
2176       int nb;
2177       for(c = 0; c<this.line.count && c<this.x && (ch = UTF8_GET_CHAR(this.line.buffer + c, nb)); c+= nb)
2178       {
2179          // TODO: MIGHT WANT TO RETHINK WHAT COLUMN SHOULD BE REGARDING TABS
2180          if(ch == '\t')
2181             position += this.tabSize - (position % this.tabSize);
2182          else
2183             position ++;
2184       }
2185       position += this.x - c;
2186       this.col = position;
2187    }
2188
2189    void DelCh(EditLine l1, int y1, int c1, EditLine l2, int y2, int c2, bool placeAfter)
2190    {
2191       _DelCh(l1, y1, c1, l2, y2, c2, placeAfter, true, null);
2192    }
2193
2194    bool HasCommentOrEscape(EditLine line)
2195    {
2196       bool hadComment = strstr(line.buffer, "/*") || strstr(line.buffer, "*/");
2197       int c;
2198
2199       if(!hadComment)
2200       {
2201          for(c = line.count-1; c >= 0; c--)
2202          {
2203             char ch = line.buffer[c];
2204             if(ch == '\\')
2205             {
2206                hadComment = true;
2207                break;
2208             }
2209             else //if(ch != ' ' && ch != '\t')
2210                break;
2211          }
2212       }
2213       return hadComment;
2214    }
2215
2216    int _DelCh(EditLine l1, int y1, int c1, EditLine l2, int y2, int c2, bool placeAfter, bool highlight, int * addedSpacesPtr)
2217    {
2218       EditLine line = l1, next;
2219       char * buffer;
2220       int oldCount1, oldCount2;
2221       int y, firstViewY, firstY, firstDropY, firstSelY;
2222       int newLineCount;
2223       int extras = 0;
2224       DelTextAction action = null;
2225       bool hadComment = false;
2226       int start = c1;
2227
2228       if(style.syntax)
2229          hadComment = HasCommentOrEscape(line);
2230
2231       if(y2 > y1 || c2 > c1)
2232       {
2233          if(start < l1.count)
2234          {
2235             while(!UTF8_IS_FIRST(l1.buffer[start]) && start)
2236                start--;
2237          }
2238       }
2239       oldCount1 = l1.count;
2240       buffer = l1.buffer;
2241       while(c1 < oldCount1)
2242       {
2243          byte ch = buffer[c1];
2244          if(UTF8_IS_FIRST(ch)) break;
2245          c1--;
2246          extras++;
2247       }
2248       oldCount2 = l2.count;
2249       buffer = l2.buffer;
2250       while(c2 < oldCount2)
2251       {
2252          byte ch = buffer[c2];
2253          if(UTF8_IS_FIRST(ch)) break;
2254          c2++;
2255          extras++;
2256       }
2257
2258       if(!undoBuffer.dontRecord && (y2 > y1 || c2 > c1))
2259       {
2260          int len;
2261          char * string;
2262
2263          len = GetText(null, l1, y1, start, l2, y2, c2, false, false);
2264          string = new char[len];
2265          action = DelTextAction { y1 = y1, x1 = start, y2 = y2, x2 = c2, string = string, placeAfter = placeAfter, noHighlight = !highlight };
2266          GetText(string, l1, y1, start, l2, y2, c2, false, false);
2267          Record(action);
2268       }
2269
2270       //oldCount1 = l1.count;
2271       //oldCount2 = l2.count;
2272
2273       {
2274          BufferLocation before = { l1,y1,c1}, after = { l2,y2,c2 };
2275          NotifyCharsDeleted(master, this, &before, &after, this.pasteOperation);
2276       }
2277
2278       if(c2 > oldCount2) c2 = oldCount2;
2279       if(!(style.freeCaret))
2280          if(c1 > oldCount1) c1 = oldCount1;
2281       newLineCount = c1 + l2.count-c2;
2282       if(l1 == l2)
2283       {
2284          /*
2285          if(!line.size)
2286             printf("");
2287          buffer = new char[line.size ? line.size : 1];
2288          */
2289          buffer = new char[line.size];
2290          // TODO: Better handling of these allocation failures
2291          if(!buffer) return extras;
2292          CopyBytes(buffer,l2.buffer,oldCount1 + 1/*line.count + 1*//*line.size*/);
2293       }
2294       else
2295          buffer = l2.buffer;
2296
2297       // TODO: Better handling of these allocation failures
2298       if(!line.AdjustBuffer(newLineCount))
2299          return extras;
2300
2301 #ifdef _DEBUG
2302       /*if(newLineCount > 4000 || newLineCount < 0)
2303          printf("Warning");*/
2304 #endif
2305       line.count = newLineCount;
2306
2307       memmove(l1.buffer+c1, buffer+c2,line.count-c1);
2308       if(c1>oldCount1)
2309       {
2310          if(action)
2311          {
2312             action.addedSpaces = c1-oldCount1;
2313 #ifdef _DEBUG
2314             if(action.addedSpaces > action.x1)
2315             {
2316                printf("bug!");
2317             }
2318 #endif
2319          }
2320          if(addedSpacesPtr) *addedSpacesPtr = c1-oldCount1;
2321          FillBytes(l1.buffer+oldCount1,' ',c1-oldCount1);
2322       }
2323       line.buffer[line.count] = '\0';
2324
2325       if(l1 == l2)
2326       {
2327          delete buffer;
2328          DirtyLine(y1);
2329          /*
2330          this.dropX -= c2-c1;
2331          this.dropX = Max(this.dropX,0);
2332          this.x -= c2-c1-1;
2333          this.x = Max(this.x,0);
2334          */
2335       }
2336       else
2337          DirtyEnd(y1);
2338
2339       y = y1;
2340       firstViewY = this.viewY;
2341       firstY = this.y;
2342       firstDropY = this.dropY;
2343       firstSelY = this.selY;
2344       for(line = l1;line;line = next, y++)
2345       {
2346          next = line.next;
2347          if(line!=l1)
2348          {
2349             this.lineCount--;
2350             delete line.buffer;
2351
2352             if(line == this.viewLine)
2353             {
2354                if(this.viewLine.next)
2355                {
2356                   this.viewLine = this.viewLine.next;
2357                   //this.viewY++;
2358                   style.recomputeSyntax = true;
2359                }
2360                else
2361                {
2362                   this.viewLine = this.viewLine.prev;
2363                   this.viewY--;
2364                   style.recomputeSyntax = true;
2365                }
2366             }
2367             else if(y < firstViewY)
2368                this.viewY--;
2369             if(line == this.line)
2370             {
2371                if(this.line.next)
2372                {
2373                   this.line = this.line.next;
2374                   this.x = this.line.count;
2375                   //this.y++;
2376                }
2377                else
2378                {
2379                   this.line = this.line.prev;
2380                   this.x = this.line.count;
2381                   this.y--;
2382                }
2383                ComputeColumn();
2384             }
2385             else if(y < firstY)
2386                this.y--;
2387             if(line == this.dropLine)
2388             {
2389                if(this.dropLine.next)
2390                {
2391                   this.dropLine = this.dropLine.next;
2392                   this.dropX = this.dropLine.count;
2393                }
2394                else
2395                {
2396                   this.dropLine = this.dropLine.prev;
2397                   this.dropX = this.dropLine.count;
2398                   this.dropY--;
2399                }
2400             }
2401             else if(y < firstDropY)
2402                this.dropY--;
2403             if(line == this.selLine)
2404             {
2405                if(this.selLine.next)
2406                {
2407                   this.selLine = this.selLine.next;
2408                   this.selX = this.selLine.count;
2409                }
2410                else
2411                {
2412                   this.selLine = this.selLine.prev;
2413                   this.selX = this.selLine.count;
2414                   this.selY--;
2415                }
2416             }
2417             else if(y < firstSelY)
2418                this.selY--;
2419             lines.Delete(line);
2420          }
2421          if(line == l2) break;
2422       }
2423       ComputeLength(l1);
2424       FindMaxLine();
2425       if(style.syntax && (hadComment || HasCommentOrEscape(this.line)))
2426       {
2427          DirtyAll();
2428          style.recomputeSyntax = true;
2429       }
2430       return extras;
2431    }
2432
2433    bool DelSel(int * addedSpacesPtr)
2434    {
2435       if(this.line != this.selLine || this.x != this.selX)
2436       {
2437          if(this.selY < this.y)
2438          {
2439             _DelCh(this.selLine, this.selY, this.selX, this.line, this.y, this.x, true, true, addedSpacesPtr);
2440             this.x = this.selX;
2441             this.y = this.selY;
2442             this.line = this.selLine;
2443          }
2444          else if(this.selY > this.y)
2445          {
2446             _DelCh(this.line, this.y, this.x, this.selLine, this.selY, this.selX, false, true, addedSpacesPtr);
2447             this.selX = this.x;
2448             this.selY = this.y;
2449             this.selLine = this.line;
2450          }
2451          else if(this.selX < this.x)
2452          {
2453             _DelCh(this.selLine, this.selY, this.selX, this.line, this.y, this.x, true, true, addedSpacesPtr);
2454             this.x = this.selX;
2455             this.y = this.selY;
2456             this.line = this.selLine;
2457          }
2458          else
2459          {
2460             _DelCh(this.line, this.y, this.x, this.selLine, this.selY, this.selX, false, true, addedSpacesPtr);
2461             this.selX = this.x;
2462             this.selY = this.y;
2463             this.selLine = this.line;
2464          }
2465          ComputeColumn();
2466          return true;
2467       }
2468       return false;
2469    }
2470
2471    bool AddToLine(char * stringLine, int count, bool LFComing, int * addedSpacesPtr, int * addedTabsPtr)
2472    {
2473       bool hadComment = false;
2474       // Add the line here
2475       EditLine line = this.line;
2476
2477       // The purpose of this is solely to lock a max number of characters if no HSCROLLING is present
2478       if(!style.hScroll && created)
2479       {
2480          int endX = (style.freeCaret) ? this.x : Min(this.x, line.count);
2481          int max;
2482          int x;
2483          int c;
2484
2485          // Lock if no place to display.
2486          if(LFComing)
2487             max = endX;
2488          else if(endX > this.x)
2489             max = Max(this.x, line.count);
2490          else
2491             max = line.count;
2492
2493          for(x = 0, c = 0; c < max+count; )
2494          {
2495             int w;
2496             int numBytes = 1;
2497             char * string;
2498             if(c < Min(this.x, line.count))
2499                string = line.buffer + c;
2500             else if(c < endX)
2501                string = " ";
2502             else if(c < endX + count)
2503                string = stringLine + c - endX;
2504             else
2505                string = line.buffer + c - endX - count;
2506
2507             if(*string == '\t')
2508             {
2509                w = (tabSize * space.w) - (x % (tabSize * space.w));
2510             }
2511             else
2512             {
2513                numBytes = UTF8_NUM_BYTES(*string);
2514                FontExtent(display, this.font, string, numBytes, &w, null);
2515             }
2516             x += w;
2517
2518             if(x >= clientSize.w)
2519             {
2520                count = c - max;
2521                break;
2522             }
2523             c += numBytes; // - 1;
2524          }
2525       }
2526
2527       if(count > 0)
2528       {
2529          int addedSpaces = 0;
2530          int addedTabs = 0;
2531
2532          // Add blank spaces if EES_FREECARET
2533          if(this.x > line.count)
2534          {
2535             if(style.freeCaret)
2536             {
2537                if(style.useTab)
2538                {
2539                   int wantedPosition;
2540                   int position = 0;
2541                   int c;
2542
2543                   for(c = 0; c<line.count; c++)
2544                   {
2545                      if(this.line.buffer[c] == '\t')
2546                         position += this.tabSize - (position % this.tabSize);
2547                      else
2548                         position ++;
2549                   }
2550                   wantedPosition = position + (this.x - line.count);
2551
2552                   // A tab is too much...
2553                   if(position + (this.tabSize - (position % this.tabSize)) > wantedPosition)
2554                      addedSpaces = wantedPosition - position;
2555                   else
2556                   {
2557                      // Put a first tab
2558                      addedTabs = 1;
2559                      position += this.tabSize - (position % this.tabSize);
2560                      // Add as many tabs as needed
2561                      addedTabs += (wantedPosition - position) / this.tabSize;
2562                      position += (addedTabs-1) * this.tabSize;
2563                      // Finish off with spaces
2564                      addedSpaces = wantedPosition - position;
2565                   }
2566                }
2567                else
2568                   addedSpaces = this.x - line.count;
2569             }
2570          }
2571          this.x = Min(this.x, line.count);
2572
2573          if(line.count + count + addedSpaces + addedTabs > this.maxLineSize)
2574             return false;
2575
2576          DirtyLine(this.y);
2577
2578          if(style.syntax)
2579             hadComment = HasCommentOrEscape(line);
2580
2581          // Adjust the size of the line
2582          if(!line.AdjustBuffer(line.count+count+addedTabs+addedSpaces))
2583             return false;
2584
2585          {
2586             BufferLocation before = { this.line, this.y, this.x }, after = { this.line, this.y, this.x };
2587             bool hasComment;
2588
2589             memmove(line.buffer+this.x+count, line.buffer+this.x,line.count-this.x);
2590             CopyBytes(line.buffer + this.x + addedTabs + addedSpaces, stringLine, count);
2591             if(addedTabs)
2592             {
2593                *addedTabsPtr = addedTabs;
2594                FillBytes(line.buffer+line.count,'\t',addedTabs);
2595 #ifdef _DEBUG
2596       if(addedTabs > 4000 || addedTabs < 0)
2597          printf("Warning");
2598 #endif
2599                line.count += addedTabs;
2600             }
2601             else if(addedTabs)
2602                *addedTabsPtr = 0;
2603             if(addedSpaces)
2604             {
2605                FillBytes(line.buffer+line.count,' ',addedSpaces);
2606 #ifdef _DEBUG
2607       if(addedSpaces > 4000 || addedSpaces < 0)
2608          printf("Warning");
2609 #endif
2610                line.count += addedSpaces;
2611                if(addedSpacesPtr) *addedSpacesPtr = addedSpaces;
2612             }
2613             else if(addedSpacesPtr)
2614                *addedSpacesPtr = 0;
2615 #ifdef _DEBUG
2616       if(count > 4000 || count < 0)
2617          printf("Warning");
2618 #endif
2619             line.count += count;
2620             this.x += count + addedTabs + addedSpaces;
2621             this.selX = this.x;
2622             this.selY = this.y;
2623             this.selLine = this.line;
2624
2625             line.buffer[line.count] = '\0';
2626
2627             ComputeLength(line);
2628             ComputeColumn();
2629
2630             after.x = this.x;
2631
2632             hasComment = HasCommentOrEscape(line);
2633             if(!undoBuffer.insideRedo)
2634             {
2635                int backDontRecord = undoBuffer.dontRecord;
2636                undoBuffer.dontRecord = 0;
2637                NotifyCharsAdded(master, this, &before, &after, this.pasteOperation);
2638                undoBuffer.dontRecord = backDontRecord;
2639             }
2640             if(style.syntax && (hadComment || hasComment || line != this.line))
2641             {
2642                style.recomputeSyntax = true;
2643                DirtyAll();
2644             }
2645          }
2646       }
2647       return true;
2648    }
2649
2650    void Emptyline(EditLine line, int y)
2651    {
2652       if(line.next)
2653          DelCh(line, y, 0, line.next, y+1, 0, true);
2654       else
2655          DelCh(line, y, 0, line, y, line.count, true);
2656    }
2657
2658    void GoToEnd(bool deselect)
2659    {
2660       if(this.line)
2661       {
2662          this.line = this.lines.last;
2663          if(this.y != this.lineCount-1)
2664             DirtyAll();
2665          else if (this.x != this.line.count)
2666             DirtyLine(this.lineCount-1);
2667          this.y = this.lineCount-1;
2668          this.x = this.line.count;
2669          ComputeColumn();
2670          if(deselect)
2671             _Deselect();
2672       }
2673    }
2674
2675    void GoToHome(bool deselect)
2676    {
2677       if(this.line)
2678       {
2679          this.line = this.lines.first;
2680          if(this.y != 0)
2681             DirtyAll();
2682          else if (this.x !=0)
2683             DirtyLine(this.lineCount-1);
2684          this.y = 0;
2685          this.x = 0;
2686          this.col = 0;
2687          if(deselect)
2688             _Deselect();
2689       }
2690    }
2691
2692    // Returns true if it needs scrolling
2693    bool FindMouse(int px, int py, int * tx, int * ty, EditLine * tline, bool half)
2694    {
2695       int w;
2696       int c;
2697       int x, y;
2698       EditLine line;
2699       bool needHScroll = false;
2700
2701       if(py < 0)
2702       {
2703          if(this.viewY > 0)
2704          {
2705             y = this.viewY-1;
2706             line = this.viewLine ? (void *)this.viewLine.prev : null;
2707          }
2708          else
2709          {
2710             y = 0;
2711             line = (void *)this.lines.first;
2712          }
2713       }
2714       else
2715       {
2716          py = Min(py, clientSize.h);
2717          py /= this.space.h;
2718          py = Min(py, this.lineCount);
2719          y = this.viewY;
2720          for(c = 0, line = this.viewLine; (line != (void *)this.lines.last && c<py); line = line.next, c++)
2721          {
2722             y++;
2723          }
2724       }
2725
2726       if( (px >= clientSize.w || px < clientSize.w/2) && this.viewX)
2727          needHScroll = true;
2728       px = Max(px,0);
2729       px = Min(px,clientSize.w+this.space.w);
2730
2731       if(tx && line)
2732       {
2733          *tx = AdjustXPosition(line, px + viewX, half, null, MAXINT, 0);
2734       }
2735
2736       if(tline) *tline = line;
2737       if(ty) *ty = y;
2738
2739       // Prevent divide by 0 from non valid this.font
2740       if(!this.space.h)
2741          return (y < this.viewY) || needHScroll;
2742       else
2743          return (y < this.viewY || y >= this.viewY + clientSize.h / this.space.h) || needHScroll;
2744       return false;
2745    }
2746
2747    // Minimal Update Management Functions
2748    void DirtyAll()
2749    {
2750       this.startY = 0;
2751       this.endY = clientSize.h-1;
2752    //   ErrorLog("DirtyAll\n");
2753    }
2754
2755    void DirtyEnd(int y)
2756    {
2757       if((y - this.viewY)*this.space.h < this.startY)
2758          this.startY = (y - this.viewY)*this.space.h+ YOFFSET;
2759       this.endY = clientSize.h-1;
2760       //ErrorLog("DirtyEnd %d\n", y);
2761    }
2762
2763    void DirtyLine(int y)
2764    {
2765       if(y >= this.viewY)
2766       {
2767          if((y - this.viewY)*this.space.h < this.startY)
2768             this.startY = (y - this.viewY)*this.space.h + YOFFSET;
2769          if((y - this.viewY+1)*this.space.h > this.endY)
2770             this.endY = (y - this.viewY+1)*this.space.h-1 + YOFFSET;
2771       }
2772       //ErrorLog("DirtyLine %d\n", y);
2773    }
2774
2775    void UpdateDirty()
2776    {
2777       Box box;
2778
2779       if(style.recomputeSyntax)
2780       {
2781          FigureStartSyntaxStates(lines.first, true);
2782          style.recomputeSyntax = false;
2783       }
2784       box.left  = 0;
2785       if(this.startY > this.endY) return;
2786       if(this.startY <= 0 && this.endY >= clientSize.h-1)
2787       {
2788          Update(null);
2789       }
2790       else
2791       {
2792          box.right = clientSize.w-1;
2793          box.top   = this.startY;
2794          box.bottom  = this.endY;
2795          Update(box);
2796       }
2797       this.startY = clientSize.h;
2798       this.endY = 0;
2799    }
2800
2801    bool IsMouseOnSelection()
2802    {
2803       bool mouseOnSelection = false;
2804
2805       int x, y;
2806       int minY = Min(this.selY, this.y);
2807       int maxY = Max(this.selY, this.y);
2808       int minX = Min(this.selX, this.x);
2809       int maxX = Max(this.selX, this.x);
2810
2811       FindMouse(this.mouseX - this.space.w / 2, this.mouseY, &x, &y, null, false);
2812
2813       if(maxX != minX || maxY != minY)
2814       {
2815          if(y > minY && y < maxY)
2816             mouseOnSelection = true;
2817          else if(y == minY && y == maxY)
2818             mouseOnSelection = (x < maxX && x >= minX);
2819          else if(y == minY)
2820          {
2821             if(y == this.selY)
2822                mouseOnSelection = (x >= this.selX);
2823             else if(y == this.y)
2824                mouseOnSelection = (x >= this.x);
2825          }
2826          else if(y == maxY)
2827          {
2828             if(y == this.selY)
2829                mouseOnSelection = (x < this.selX);
2830             else if(y == this.y)
2831                mouseOnSelection = (x < this.x);
2832          }
2833       }
2834       return mouseOnSelection;
2835    }
2836
2837    void UpdateCaretPosition(bool setCaret)
2838    {
2839       if(line)
2840       {
2841          if(mouseMove || !style.noCaret)
2842          {
2843             int max = this.mouseMove ? this.dropX : this.x;
2844             int y = this.mouseMove ? this.dropY : this.y;
2845             EditLine line = this.mouseMove ? this.dropLine : this.line;
2846             int c, x = 0;
2847             if(!(style.freeCaret))
2848                max = Min(max, line.count);
2849
2850             if(FontExtent && display)
2851             {
2852                for(c = 0; c < max; )
2853                {
2854                   int len = 1;
2855                   int start = c;
2856                   int w;
2857                   if(c < line.count)
2858                   {
2859                      byte ch = 0;
2860                      int numBytes;
2861                      for(len = 0; c < Min(max, line.count); c += numBytes)
2862                      {
2863                         ch = line.buffer[c];
2864                         numBytes = UTF8_NUM_BYTES(ch);
2865
2866                         if(ch == ' ' || ch == '\t')
2867                         {
2868                            if(!len) c++;
2869                            break;
2870                         }
2871                         len += numBytes;
2872                      }
2873                      if(!len && ch == ' ')
2874                      {
2875                         w = space.w;
2876                         len = 1;
2877                      }
2878                      else if(!len && ch == '\t')
2879                      {
2880                         w = (tabSize * space.w) - (x % (tabSize * space.w));
2881                         len = 1;
2882                      }
2883                      else
2884                         FontExtent(display, this.font, line.buffer + start, len, &w, null);
2885                   }
2886                   else
2887                   {
2888                      w = space.w;
2889                      c++;
2890                   }
2891                   x += w;
2892                }
2893             }
2894             if(setCaret)
2895                caretX = x;
2896             caretY = y * this.space.h;
2897             if(!overwrite)
2898                SetCaret(x + XOFFSET-2, y * space.h + YOFFSET, space.h);
2899             else
2900                SetCaret(0, 0, 0);
2901          }
2902          else
2903             SetCaret(0, 0, 0);
2904
2905          // TOFIX: Mismatch between NotifyCaretMove() and NotifyDropped() / GoToPosition()
2906          NotifyCaretMove(master, this, y + 1, x + 1);
2907
2908          SelectionEnables();
2909       }
2910    }
2911
2912    void SelectionEnables()
2913    {
2914       if((x != selX || y != selY) && !selection)
2915       {
2916          if(!style.readOnly)
2917          {
2918             itemEditCut.disabled = false;
2919             itemEditDelete.disabled = false;
2920          }
2921          itemEditCopy.disabled = false;
2922
2923          this.selection = true;
2924       }
2925       else if((x == selX && y == selY) && selection)
2926       {
2927          itemEditCut.disabled = true;
2928          itemEditCopy.disabled = true;
2929          itemEditDelete.disabled = true;
2930
2931          this.selection = false;
2932       }
2933    }
2934
2935    void SetSelectCursor()
2936    {
2937       if(!inactive || !style.noSelect)
2938       {
2939          if(this.mouseMove)
2940             cursor = guiApp.GetCursor(arrow);
2941          else if(this.mouseSelect)
2942             cursor = guiApp.GetCursor(iBeam);
2943          else
2944          {
2945             if(IsMouseOnSelection())
2946                cursor = guiApp.GetCursor(arrow);
2947             else
2948                cursor = guiApp.GetCursor(iBeam);
2949          }
2950       }
2951    }
2952
2953    int AdjustXPosition(EditLine line, int position, bool half, int * px, int max, int sc)
2954    {
2955       int c = sc;
2956       int x = px ? *px : 0;
2957       while(true)
2958       {
2959          int start = c;
2960          int numBytes = 1;
2961          int len = 1;
2962          int w;
2963          if(c < Min(max, line.count))
2964          {
2965             byte ch = 0;
2966             int numBytes;
2967             for(len = 0; c < Min(max, line.count); c += numBytes)
2968             {
2969                ch = line.buffer[c];
2970                numBytes = UTF8_NUM_BYTES(ch);
2971
2972                if(ch == ' ' || ch == '\t')
2973                {
2974                   if(!len) c++;
2975                   break;
2976                }
2977                len += numBytes;
2978             }
2979             if(!len && ch == ' ')
2980             {
2981                w = space.w;
2982                len = 1;
2983             }
2984             else if(!len && ch == '\t')
2985             {
2986                w = (tabSize * space.w) - (x % (tabSize * space.w));
2987                len = 1;
2988             }
2989             else
2990                FontExtent(display, font, line.buffer + start, len, &w, null);
2991          }
2992          else
2993          {
2994             if(style.freeCaret && c < max)
2995                w = space.w;
2996             else
2997             {
2998                if(px) *px = x;
2999                return c;
3000             }
3001             c++;
3002          }
3003          if(x + (((half && len == 1) ? (w / 2) : w)) >= position)
3004          {
3005             int lastW;
3006             while(len > 0)
3007             {
3008                int a = start + len;
3009                lastW = w;
3010                if(a <= line.count)
3011                   while(a > 0 && !UTF8_IS_FIRST(line.buffer[--a]));
3012                else
3013                   a--;
3014                if(a > start)
3015                   FontExtent(display, font, line.buffer + start, a - start, &w, null);
3016                else
3017                   w = 0;
3018                if(position > x + (half ? ((w + lastW) / 2) : lastW)) break;
3019                len = a - start;
3020             }
3021             if(px) *px = x + w;
3022             return Min(this.maxLineSize - 1, start + len);
3023          }
3024          x += w;
3025       }
3026    }
3027
3028    void SetCursorToViewX()
3029    {
3030       bool selecting = this.x != selX || y != selY;
3031
3032       // Horizontal Adjustment
3033       int x = 0;
3034       int c = AdjustXPosition(line, viewX, false, &x, MAXINT, 0);
3035       if(this.x < c)
3036          this.x = x;
3037       else
3038       {
3039          c = AdjustXPosition(line, viewX + clientSize.w - 1, false, &x, MAXINT, c);
3040          if(this.x > c)
3041             this.x = c;
3042      }
3043
3044       if(!selecting)
3045       {
3046          selX = this.x;
3047          selY = y;
3048          selLine = line;
3049       }
3050
3051       UpdateCaretPosition(false);
3052
3053       UpdateDirty();
3054       SetSelectCursor();
3055    }
3056
3057    void SetCursorToViewY()
3058    {
3059       int c, numLines;
3060       EditLine oldLine = this.line;
3061
3062       bool selecting = this.x != this.selX || this.y != this.selY;
3063
3064       numLines = clientSize.h / this.space.h;
3065
3066       // Vertical Adjustment
3067       if(this.viewY > this.y)
3068       {
3069          this.y = this.viewY;
3070          this.line = this.viewLine;
3071       }
3072
3073       if(this.viewY + numLines <=  this.y)
3074       {
3075          EditLine line;
3076
3077          this.y = this.viewY-1;
3078          for(c = 0, line = this.viewLine; line && c<numLines; line = line.next, c++)
3079          {
3080             this.line = line;
3081             this.y++;
3082          }
3083       }
3084
3085       if(this.line != oldLine)
3086       {
3087          this.x = AdjustXPosition(this.line, caretX, true, null, MAXINT, 0);
3088          ComputeColumn();
3089       }
3090
3091       if(!selecting)
3092       {
3093          this.selX = this.x;
3094          this.selY = this.y;
3095          this.selLine = this.line;
3096       }
3097
3098       UpdateCaretPosition(false);
3099
3100       UpdateDirty();
3101       SetSelectCursor();
3102    }
3103
3104    /*
3105    bool SaveFile(char * fileName)
3106    {
3107       File f = eFile_Open(fileName, FO_WRITE);
3108       if(f)
3109       {
3110          Save(f, false);
3111          eWindow_SetModified(false);
3112          eInstance_Delete(f);
3113          return true;
3114       }
3115       return false;
3116    }
3117    */
3118
3119    bool OnActivate(bool active, Window previous, bool * goOnWithActivation, bool direct)
3120    {
3121       if(line)
3122       {
3123          if(!style.multiLine)
3124          {
3125             x = (active && this.active && !style.readOnly) ? line.count : 0;
3126             selX = 0;
3127             ComputeColumn();
3128             SetViewToCursor(true);
3129             DirtyLine(0);
3130             UpdateDirty();
3131          }
3132          // Update(null);
3133          if(!active && modified)
3134          {
3135             modified = false;
3136             if(!NotifyModified(master, this))
3137             {
3138                modified = true;
3139                *goOnWithActivation = false;
3140             }
3141          }
3142       }
3143       if(!active)
3144       {
3145          ReleaseCapture();
3146          if(timer) timer.Stop();
3147
3148          mouseSelect = false;
3149          wordSelect = false;
3150       }
3151       return true;
3152    }
3153
3154    void AutoSize()
3155    {
3156       int aw = maxLength + 12, ah = Max(lineCount, 1) * space.h + 2;
3157       int nw = minClientSize.w, nh = minClientSize.h, xw = maxClientSize.w, xh = maxClientSize.h;
3158       clientSize = { nw && aw < nw ? nw : xw && aw > xw ? xw : aw, nh && ah < nh ? nh : xh && ah > xh ? xh : ah };
3159    }
3160
3161    bool OnResizing(int *w, int *h)
3162    {
3163       if(!*h)
3164          *h = space.h + 2;
3165       if(!*w)
3166          //*w = 80;
3167          *w = space.h * 80 / 14;
3168       return true;
3169    }
3170
3171    void OnResize(int w, int h)
3172    {
3173    /*
3174       if(!hasHorzScroll && !hasVertScroll && viewLine)
3175          SetViewToCursor(true);
3176    */
3177       //if(!hasHorzScroll && !hasVertScroll && viewLine)
3178       if(viewLine)
3179          SetViewToCursor(true);
3180       //else
3181    //     FixScrollArea();
3182    }
3183
3184    bool OnMiddleButtonDown(int x, int y, Modifiers mods)
3185    {
3186       if(style.readOnly) return true;
3187       // We really shouldn't be pasting here, Unix middle button paste is for the selection (not the clipboard), good for the terminal
3188       // Middle button already serves as a dragger as well.
3189       // Paste();
3190       return true;
3191    }
3192
3193    bool OnRightButtonDown(int x, int y, Modifiers mods)
3194    {
3195       this.rightButtonDown = true;
3196       // Copy();
3197       return true;
3198    }
3199
3200    bool OnRightButtonUp(int x, int y, Modifiers mods)
3201    {
3202       // Context Menu
3203       if(!parent.inactive && rightButtonDown)
3204       {
3205          PopupMenu popup;
3206          Menu contextMenu { };
3207
3208          MenuItem { contextMenu, $"Cut\tCtrl+X", t, NotifySelect = itemEditCut.NotifySelect, disabled = !selection || style.readOnly };
3209          MenuItem { contextMenu, $"Copy\tCtrl+C", c, NotifySelect = itemEditCopy.NotifySelect, disabled = !selection };
3210          MenuItem { contextMenu, $"Paste\tCtrl+V", p, NotifySelect = itemEditPaste.NotifySelect, disabled = style.readOnly };
3211          MenuItem { contextMenu, $"Delete\tDel", d, NotifySelect = itemEditDelete.NotifySelect, disabled = !selection || style.readOnly };
3212          MenuDivider { contextMenu };
3213          MenuItem { contextMenu, $"Select All\tCtrl+A", a, NotifySelect = itemEditSelectAll.NotifySelect };
3214
3215          popup = PopupMenu { master = this, menu = contextMenu,
3216    /*
3217             nonClient = true, interim = false, parent = parent,
3218             position = { x + clientStart.x + parent.clientStart.x + position.x, y + cientStart.y + parent.sy + clientStart.y + position.y };
3219    */
3220             position = { x + clientStart.x + absPosition.x - guiApp.desktop.position.x, y + clientStart.y + absPosition.y - guiApp.desktop.position.y }
3221          };
3222          popup.Create();
3223       }
3224       rightButtonDown = false;
3225       return true;
3226    }
3227
3228    bool OnLeftButtonDown(int mx, int my, Modifiers mods)
3229    {
3230       int x,y;
3231       EditLine line;
3232
3233       if(style.noSelect) return true;
3234
3235       // Should we have a separate 'selectOnActivate' style?
3236       if(!mods.isActivate || (style.readOnly && style.multiLine))
3237       {
3238          Capture();
3239          mouseSelect = true;
3240       }
3241
3242       mouseX = mx - XOFFSET;
3243       mouseY = my;
3244
3245       FindMouse(mouseX, mouseY, &x, &y, &line, true);
3246 #ifdef _DEBUG
3247       //PrintLn("OnLeftButtonDown: ", x, ", ", y);
3248 #endif
3249       if(!style.readOnly)
3250       {
3251          if(wordSelect)
3252             mouseMove = false;
3253          else if(IsMouseOnSelection() && !mods.isActivate)
3254          {
3255             DirtyLine(this.y);
3256             mouseMove = true;
3257             dropX = x;
3258             dropY = y;
3259             dropLine = line;
3260          }
3261       }
3262
3263       if(!mouseMove && !wordSelect && (!mods.isActivate || style.multiLine))
3264       {
3265          if(mods.shift && !mods.isActivate)
3266          {
3267             this.x = x;
3268             this.y = y;
3269             this.line = line;
3270             DirtyAll();
3271          }
3272          else
3273          {
3274             SelDirty();
3275             DirtyLine(this.y);
3276             this.x = x;
3277             this.y = y;
3278             this.line = line;
3279             DirtyLine(this.y);
3280             _Deselect();
3281          }
3282          ComputeColumn();
3283       }
3284
3285       UpdateDirty();
3286       UpdateCaretPosition(true);
3287       // Return false because DataBoxes automatically set EditBox editor's clickThrough to true for MouseMove events
3288       // ( for tool tips -- see 95ee4962c4c7bc3fe0a04aa6a4f98cacada40884)
3289       return false;
3290    }
3291
3292    bool OnLeftButtonUp(int x, int y, Modifiers mods)
3293    {
3294       timer.Stop();
3295
3296       mouseSelect = false;
3297       wordSelect = false;
3298
3299       x -= XOFFSET;
3300
3301       ReleaseCapture();
3302       if(!style.readOnly)
3303       {
3304          if(mouseMove)
3305          {
3306             EditLine line;
3307             FindMouse(mouseX, mouseY, &x, &y, &line, true);
3308 #ifdef _DEBUG
3309             //PrintLn("MouseMove: ", x, ", ", y);
3310 #endif
3311             dropX = x;
3312             dropY = y;
3313             dropLine = line;
3314
3315             mouseMove = IsMouseOnSelection();
3316
3317             if(!mouseMove)
3318             {
3319                int size = SelSize();
3320                if(size)
3321                {
3322                   char * text = new char[size+1];
3323                   if(text)
3324                   {
3325                      int moveX = 0;
3326                      GetSel(text, false);
3327
3328                      if(Max(selY, this.y) == dropY)
3329                      {
3330                         if(this.x > selX)
3331                         {
3332                            if(this.dropX > this.selX)
3333                               moveX = this.x - this.selX;
3334                         }
3335                         else
3336                         {
3337                            if(this.dropX > this.x)
3338                               moveX = this.selX - this.x;
3339                         }
3340                      }
3341
3342                      recordUndoEvent = true;
3343                      DelSel(null);
3344                      this.dropX -= moveX;
3345                      this.selX = this.x = this.dropX;
3346                      this.selY = this.y = this.dropY;
3347                      this.selLine = this.line = this.dropLine;
3348                      AddS(text);
3349                      recordUndoEvent = false;
3350
3351                      SetViewToCursor(true);
3352                      delete text;
3353                      Modified();
3354 #ifdef _DEBUG
3355                    /*  {
3356                         byte * c = ((EditBox)this).multiLineContents, * c2;
3357                         int l1 = c ? strlen(c) : 0, l2;
3358                         Undo();
3359                         Redo();
3360                         c2 = ((EditBox)this).multiLineContents;
3361                         l2 = c2 ? strlen(c2) : 0;
3362                         if(l1 != l2 || (l1 && strcmp(c, c2)))
3363                         {
3364                            PrintLn("Fail!");
3365                         }
3366                         delete c;
3367                      }
3368                      */
3369 #endif
3370                   }
3371                }
3372             }
3373             else
3374             {
3375                SelDirty();
3376                DirtyLine(this.y);
3377                this.x = x;
3378                this.y = y;
3379                this.line = line;
3380                ComputeColumn();
3381                DirtyLine(this.y);
3382                _Deselect();
3383                UpdateDirty();
3384             }
3385          }
3386          else
3387          {
3388             EditLine line;
3389             mouseX = x;
3390             mouseY = y;
3391
3392             FindMouse(mouseX, mouseY, &x, &y, &line, true);
3393 #ifdef _DEBUG
3394             //PrintLn("Dropped: ", x, ", ", y);
3395 #endif
3396             NotifyDropped(master, this, x, y);
3397          }
3398       }
3399       mouseMove = false;
3400       // Return false because DataBoxes automatically set EditBox editor's clickThrough to true for MouseMove events
3401       // ( for tool tips -- see 95ee4962c4c7bc3fe0a04aa6a4f98cacada40884)
3402       return false;
3403    }
3404
3405    bool OnMouseMove(int mx, int my, Modifiers mods)
3406    {
3407       int x,y;
3408       EditLine line;
3409       bool needScroll;
3410
3411       if(mods != -1 && mods.isSideEffect)
3412       {
3413          SetSelectCursor();
3414          return true;
3415       }
3416       if(style.noSelect) return true;
3417       if(wordSelect) return true;
3418       mouseX = mx - XOFFSET;
3419       mouseY = my;
3420
3421       needScroll = FindMouse(this.mouseX, this.mouseY, &x, &y, &line, true);
3422
3423       if(this.mouseMove || this.mouseSelect)
3424       {
3425          if(!needScroll)
3426             timer.Stop();
3427          else
3428          {
3429             if(needScroll)
3430                timer.Start();
3431             if(mods != -1 &&
3432                ((style.hScroll) || (style.vScroll)))
3433                return true;
3434          }
3435       }
3436
3437       if(this.mouseMove)
3438       {
3439          DirtyLine(this.dropY);
3440          this.dropX = x;
3441          this.dropY = y;
3442          DirtyLine(this.dropY);
3443          this.dropLine = line;
3444          SetViewToCursor(true);
3445 #ifdef _DEBUG
3446          //PrintLn("MouseMove: ", "dropX = ", x, ", dropY = ", y);
3447 #endif
3448       }
3449       else if(this.mouseSelect)
3450       {
3451          DirtyLine(this.selY);
3452          DirtyLine(this.y);
3453          this.x = x;
3454          this.y = y;
3455          ComputeColumn();
3456          DirtyLine(this.y);
3457          this.line = line;
3458          SetViewToCursor(true);
3459          UpdateDirty();
3460 #ifdef _DEBUG
3461          //PrintLn("MouseSelect: ", "x = ", x, ", y = ", y);
3462 #endif
3463       }
3464       SetSelectCursor();
3465       return true;
3466    }
3467
3468    bool OnLeftDoubleClick(int mx, int my, Modifiers mods)
3469    {
3470       int x,y;
3471       EditLine line;
3472 #ifdef _DEBUG
3473       //PrintLn("OnLeftDoubleClick: ", mx, ", ", my, ", mods = ", mods);
3474 #endif
3475       mx -= XOFFSET;
3476
3477       if(style.noSelect) return true;
3478       FindMouse(mx, my, &x, &y, &line, false);
3479       if(!NotifyDoubleClick(master, this, line, mods))
3480          return false;
3481       if(x < line.count)
3482       {
3483          int c;
3484          int start = -1;
3485          int numBytes;
3486          for(c = x; c >= 0; c--)
3487          {
3488             unichar ch;
3489             while(c > 0 && !UTF8_IS_FIRST(line.buffer[c])) c--;
3490             ch = UTF8_GET_CHAR(line.buffer + c, numBytes);
3491             if(!IS_ALUNDER(ch))
3492                break;
3493             start = c;
3494          }
3495          if(start != -1)
3496          {
3497             for(c = start; c<line.count; c += numBytes)
3498             {
3499                unichar ch = UTF8_GET_CHAR(line.buffer + c, numBytes);
3500                if(!ch || !IS_ALUNDER(ch))
3501                   break;
3502             }
3503             SelDirty();
3504             DirtyLine(this.y);
3505             this.y = y;
3506             DirtyLine(this.y);
3507             this.selX = start;
3508             this.x = c;
3509             ComputeColumn();
3510             this.line = this.selLine = line;
3511             this.wordSelect = (c != start);
3512             UpdateDirty();
3513          }
3514       }
3515       return true;
3516    }
3517
3518    bool OnPostCreate()
3519    {
3520       /*if(isDocument)
3521       {
3522          Menu fileMenu { menu, "File", F };
3523          saveDialog = fileDialog;
3524          MenuItem { fileMenu, $"Save\tCtrl+S", S, CtrlS, NotifySelect = MenuFileSave };
3525          MenuItem { fileMenu, $"Save As...", A, NotifySelect = MenuFileSaveAs };
3526       }*/
3527       if(style.autoSize) AutoSize();
3528       return true;
3529    }
3530
3531    void ComputeFont()
3532    {
3533       if(FontExtent)
3534       {
3535          FontExtent(display, font, " ", 1, (int *)&space.w, (int *)&space.h);
3536          FontExtent(display, font, "W", 1, (int *)&large.w, (int *)&large.h);
3537
3538          space.w = Max(space.w, 1);
3539          large.w = Max(large.w, 1);
3540          space.h = Max(space.h, 1);
3541          large.h = Max(large.h, 1);
3542
3543          {
3544             EditLine line;
3545             for(line = lines.first; line; line = line.next)
3546                ComputeLength(line);
3547             FindMaxLine();
3548          }
3549
3550          if(viewLine)
3551             SetViewToCursor(true);
3552
3553          FixScrollArea();
3554
3555          Update(null);
3556       }
3557    }
3558
3559    bool OnLoadGraphics()
3560    {
3561       FontExtent = Display::FontExtent;
3562       font = fontObject;
3563       ComputeFont();
3564       // UpdateCaretPosition(true);
3565       return true;
3566    }
3567
3568    void OnUnloadGraphics()
3569    {
3570       this.font = null;
3571    }
3572
3573    bool OnKeyHit(Key key, unichar ch)
3574    {
3575       bool shift = (key.shift) ? true : false;
3576 #ifdef _DEBUG
3577       //PrintLn("OnKeyHit: code = ", key.code, ", mods = ", key.modifiers, ", ch = ", (int)ch);
3578 #endif
3579       if(!ch && !key.alt && !key.ctrl)
3580       {
3581          key.code = (SmartKey)key.code;
3582       }
3583       else if(!ch && key.alt)
3584          key.code = 0;
3585
3586       switch(key.code) //(ch || key.alt || key.ctrl) ? key.code : (Key)(SmartKey)key.code)
3587       {
3588          case backSpace:
3589             if(style.readOnly) break;
3590             if(style.stuckCaret) GoToEnd(true);
3591             if(!(style.freeCaret))
3592             {
3593                this.x = Min(this.x, this.line.count);
3594             }
3595             if(key.ctrl)
3596             {
3597                int y;
3598                bool done = false;
3599                EditLine line = this.line;
3600                int c;
3601                for(y = this.y; y>= 0; y--)
3602                {
3603                   c = (y == this.y) ? (Min(this.x-1, line.count-1)) : line.count-1;
3604
3605                   // Slow down when going on lines...
3606                   if(y != this.y) break;
3607
3608                   for(; c>=0; c--)
3609                   {
3610                      //if(this.line.buffer[c] != '\t' && this.line.buffer[c] != ' ')
3611                      if(IS_ALUNDER(line.buffer[c]))
3612                         break;
3613                   }
3614                   for(; c>=0; c--)
3615                   {
3616                      if(!IS_ALUNDER(line.buffer[c]))
3617                      {
3618                      //if(this.line.buffer[c] == '\t' || this.line.buffer[c] == ' ')
3619                         done = true;
3620                         break;
3621                      }
3622                   }
3623                   if(done)
3624                      break;
3625                   if(line.prev)
3626                      line = line.prev;
3627                   else
3628                      break;
3629                }
3630
3631                //if(this.x != 0)
3632                {
3633                   _DelCh(line, y, c+1, this.line, this.y, this.x, true, false, null);
3634                   this.x = this.selX = Min(c+1, line.count);
3635                   this.y = this.selY = y;
3636                   this.line = this.selLine = line;
3637                   SetViewToCursor(true);
3638                }
3639                ComputeColumn();
3640             }
3641             else
3642             {
3643                BackSpace();
3644             }
3645             return false;
3646             //break;
3647          case del:
3648             if(style.readOnly) break;
3649             if(style.stuckCaret) break;
3650
3651             if(shift)
3652             {
3653                Cut();
3654             }
3655             else
3656             {
3657                // Delete selection
3658                if(this.line != this.selLine || this.x != this.selX)
3659                {
3660                   DelSel(null);
3661                   SetViewToCursor(true);
3662                   Modified();
3663                }
3664                else
3665                {
3666                   EditLine line1 = this.line, line2 = this.line;
3667                   int x1, y1 = this.y, x2, y2 = this.y;
3668                   if(!style.freeCaret)
3669                   {
3670                      this.selX = this.x = Min(this.x, this.line.count);
3671                      ComputeColumn();
3672                   }
3673                   x1 = this.x;
3674
3675                   if(x1 < line1.count)
3676                   {
3677                      // Delete word
3678                      if(key.ctrl)
3679                      {
3680                         int i;
3681                         int length;
3682                         char * buffer = line1.buffer;
3683                         for(i = x1; i < line1.count; i++)
3684                         {
3685                            if(!IS_ALUNDER(buffer[i]))
3686                               break;
3687                         }
3688
3689                         for(; i < line1.count; i++)
3690                         {
3691                            // Delete trailing whitespace
3692                            if(IS_ALUNDER(buffer[i]))
3693                               break;
3694                         }
3695                         x2 = i;
3696                      }
3697                      else
3698                         x2 = x1 + 1;
3699                   }
3700                   else
3701                   {
3702                      // Avoid creating trailing spaces if there is no content on next line
3703                      line2 = line1.next;
3704                      y2++;
3705                      if(line2 && !line2.count)
3706                         x1 = line1.count;
3707                      x2 = 0;
3708                   }
3709                   if(line2)
3710                      _DelCh(line1, y1, x1, line2, y2, x2, false, false, null);
3711                   SetViewToCursor(true);
3712                   Modified();
3713                }
3714             }
3715             return false;
3716             //break;
3717          case enter:
3718          case keyPadEnter:
3719          {
3720             if(!key.alt && !key.ctrl)
3721             {
3722                int c;
3723                int position = 0;
3724                bool stuffAfter = false;
3725                char * addString;
3726                int len = 0;
3727                /*bool resetX = false;
3728                int backX;*/
3729
3730                if(style.stuckCaret) GoToEnd(true);
3731                if(style.readOnly) break;
3732                if(!(style.multiLine)) break;
3733
3734                for(c = 0; c<this.line.count && c<this.x; c++)
3735                {
3736                   if(this.line.buffer[c] == '\t')
3737                      position += this.tabSize - (position % this.tabSize);
3738                   else if(this.line.buffer[c] == ' ')
3739                      position++;
3740                   else
3741                      break;
3742                }
3743
3744                // Prevent adding trailing spaces if at the head of a line
3745                /*if(c && c == this.x && c < this.line.count && this.x == this.selX && this.y == this.selY)
3746                {
3747                   position = 0;
3748                   backX = this.x;
3749                   this.x = 0;
3750                   this.selX = 0;
3751                   resetX = true;
3752                }*/
3753
3754                if(!line.count)
3755                   position = x;
3756
3757                if(this.x < this.line.count)
3758                   stuffAfter = true;
3759
3760                //If last character is a { indent one tab
3761                if(this.line.buffer[this.x - 1] == '{')
3762                {
3763                   //Except if the next non space character is a }
3764                   bool indent = false;
3765                   int i;
3766                   for(i = this.x; i < this.line.size; i++)
3767                      if(this.line.buffer[i] != ' ' && this.line.buffer[i] != '\t')
3768                      {
3769                         if(this.line.buffer[i] != '}')
3770                            indent = true;
3771                         break;
3772                      }
3773                   if(indent)
3774                      position += this.tabSize;
3775                }
3776
3777                addString = new char[position + 2];
3778                addString[len++] = '\n';
3779                addString[len] = '\0';
3780
3781                if(stuffAfter || !style.freeCaret)
3782                {
3783                   for(c = 0; c<position; )
3784                   {
3785                      if(style.useTab && c + this.tabSize <= position)
3786                      {
3787                         addString[len++] = '\t';
3788                         c += this.tabSize;
3789                      }
3790                      else
3791                      {
3792                         addString[len++] = ' ';
3793                         c++;
3794                      }
3795                   }
3796                   addString[len] = '\0';
3797                }
3798                recordUndoEvent = true;
3799                if(AddS(addString))
3800                {
3801                   EditLine prevLine = this.line.prev;
3802                   if(prevLine)
3803                   {
3804                      // Nuke spaces if that is all that is left on previous line
3805                      int i;
3806                      char * buffer = prevLine.buffer;
3807                      for(i = 0; i < prevLine.count; i++)
3808                         if(buffer[i] != ' ' && buffer[i] != '\t')
3809                            break;
3810                      if(i == prevLine.count)
3811                         DelCh(prevLine, this.y - 1, 0, prevLine, this.y - 1, prevLine.count, false);
3812                   }
3813                   /*if(resetX)
3814                   {
3815                      this.x = this.selX = backX;
3816                      ComputeColumn();
3817                   }
3818                   else */if(!stuffAfter && style.freeCaret)
3819                   {
3820                      this.x = this.selX = position;
3821                      ComputeColumn();
3822                   }
3823                   FindMaxLine();
3824                   SetViewToCursor(true);
3825                   Modified();
3826                }
3827                recordUndoEvent = false;
3828                delete addString;
3829                return false;
3830             }
3831             break;
3832          }
3833          case left:
3834          {
3835             if(style.stuckCaret) break;
3836             if(!(style.freeCaret))
3837             {
3838                this.x = Min(this.x, this.line.count);
3839                this.selX = Min(this.selX, this.selLine.count);
3840                ComputeColumn();
3841             }
3842             if(!shift) SelDirty();
3843             if(key.ctrl)
3844             {
3845                bool foundAlpha = false;
3846                bool found = false;
3847                int y = this.y;
3848                EditLine line, lastLine;
3849                int lastC, lastY;
3850
3851                for(line = this.line; (line && !found); line = line.prev, y--)
3852                {
3853                   int start;
3854                   int c;
3855
3856                   if(this.x == 0 && line != this.line)
3857                   {
3858                      foundAlpha = true;
3859                      lastC = line.count;
3860                      lastY = y;
3861                      lastLine = line;
3862                      break;
3863                   }
3864
3865                   if(line == this.line) start = this.x -1; else start = line.count-1;
3866                   start = Min(start, line.count-1);
3867
3868                   for(c = start; c >= 0;)
3869                   {
3870                      int numBytes;
3871                      unichar ch = UTF8_GET_CHAR(line.buffer + c, numBytes);
3872                      if(IS_ALUNDER(ch))
3873                      {
3874                         foundAlpha = true;
3875                         lastC = c;
3876                         lastY = y;
3877                         lastLine = line;
3878                      }
3879                      else
3880                      {
3881                         if(foundAlpha)
3882                         {
3883                            found = true;
3884                            break;
3885                         }
3886                      }
3887                      while(--c)
3888                      {
3889                         byte ch = line.buffer[c];
3890                         if(UTF8_IS_FIRST(ch)) break;
3891                      }
3892
3893                   }
3894                   // No next word found,
3895                   if(!found && ( this.x > 0 || (!line.count && this.x)))
3896                   {
3897                      foundAlpha = true;
3898                      lastC = 0;
3899                      lastY = y;
3900                      lastLine = line;
3901                      break;
3902                   }
3903                }
3904                if(foundAlpha)
3905                {
3906                   DirtyLine(this.y);
3907                   this.x = lastC;
3908                   this.y = lastY;
3909                   this.line = lastLine;
3910                   DirtyLine(this.y);
3911                   ComputeColumn();
3912                }
3913             }
3914             else
3915             {
3916                if(x > 0)
3917                {
3918                   if(x <= line.count)
3919                   {
3920                      byte * buffer = line.buffer;
3921                      while(--x)
3922                      {
3923                         byte ch = buffer[x];
3924                         if(UTF8_IS_FIRST(ch)) break;
3925                      }
3926                   }
3927                   else
3928                      x--;
3929                   DirtyLine(y);
3930                }
3931                else if(line.prev)
3932                {
3933                   line = line.prev;
3934                   DirtyLine(y);
3935                   x = line.count;
3936                   y--;
3937                   DirtyLine(y);
3938                }
3939                ComputeColumn();
3940             }
3941             if(!shift) _Deselect();
3942             SetViewToCursor(true);
3943             //break;
3944             return false;
3945          }
3946          case right:
3947          {
3948             if(style.stuckCaret) break;
3949             if(!(style.freeCaret))
3950             {
3951                this.x = Min(this.x, this.line.count);
3952                this.selX = Min(this.selX, this.selLine.count);
3953                ComputeColumn();
3954             }
3955             if(!shift) SelDirty();
3956             if(!shift && (this.x != this.selX || this.y != this.selY));
3957             else if(key.ctrl)
3958             {
3959                bool onAChar = false;
3960                if(this.selX != this.x || this.selY != this.y)
3961                   onAChar = true;
3962                if(this.x<this.line.count)
3963                   if(this.line.buffer[this.x] != '\t' && this.line.buffer[this.x] !=' ')
3964                      onAChar = true;
3965                if(key.shift && onAChar &&
3966                   ((this.y > this.selY)||((this.selY == this.y)&&(this.x >= this.selX))))
3967                {
3968                   bool foundAlpha = false;
3969                   bool found = false;
3970                   EditLine line, lastLine;
3971                   int y = this.y;
3972                   int lastC, lastY, lastNumBytes;
3973
3974                   for(line = this.line; (line && !found); line = line.next, y++)
3975                   {
3976                      int start = (line == this.line) ? this.x : 0;
3977                      int c;
3978                      int numBytes;
3979                      unichar ch;
3980                      for(c = start; c < line.count && (ch = UTF8_GET_CHAR(line.buffer + c, numBytes)); c += numBytes)
3981                      {
3982                         if(IS_ALUNDER(ch))
3983                         {
3984                            foundAlpha = true;
3985                            lastC = c;
3986                            lastNumBytes = numBytes;
3987                            lastY = y;
3988                            lastLine = line;
3989                         }
3990                         else if(foundAlpha)
3991                         {
3992                            found = true;
3993                            break;
3994                         }
3995                      }
3996                      if(!found && (c != this.x || line != this.line))
3997                      {
3998                         found = true;
3999                         lastLine = line;
4000                         lastC = line.count;
4001                         lastNumBytes = 0;
4002                         lastY = y;
4003                         break;
4004                      }
4005                   }
4006                   if(found)
4007                   {
4008                      DirtyLine(this.y);
4009                      this.x = lastC + lastNumBytes;
4010                      this.y = lastY;
4011                      this.line = lastLine;
4012                      DirtyLine(this.y);
4013                      ComputeColumn();
4014                   }
4015                }
4016                else
4017                {
4018                   bool foundAlpha = false;
4019                   bool found = false;
4020                   EditLine line;
4021                   int y = this.y;
4022
4023                   for(line = this.line; (line && !found); line = line.next, y++)
4024                   {
4025                      int start = (line == this.line) ? this.x : 0;
4026                      int c;
4027                      int numBytes;
4028                      unichar ch;
4029                      for(c = start; c < line.count && (ch = UTF8_GET_CHAR(line.buffer + c, numBytes)); c += numBytes)
4030                      {
4031                         if(!IS_ALUNDER(ch))
4032                            foundAlpha = true;
4033                         else if(foundAlpha)
4034                         {
4035                            found = true;
4036                            DirtyLine(this.y);
4037                            this.x = c;
4038                            this.y = y;
4039                            this.line = line;
4040                            DirtyLine(this.y);
4041                            ComputeColumn();
4042                            break;
4043                         }
4044                      }
4045                      // No next word found,
4046                      if(!found && (c != this.x || line != this.line))
4047                      {
4048                         found = true;
4049                         DirtyLine(this.y);
4050                         this.x = line.count;
4051                         this.y = y;
4052                         this.line = line;
4053                         DirtyLine(this.y);
4054                         ComputeColumn();
4055                      }
4056                      foundAlpha = true;
4057                   }
4058                }
4059             }
4060             else
4061             {
4062                if(x < line.count || (style.freeCaret && line.count < maxLineSize))
4063                {
4064                   if(x < line.count)
4065                   {
4066                      byte * buffer = line.buffer;
4067                      while(++x)
4068                      {
4069                         byte ch = buffer[x];
4070                         if(UTF8_IS_FIRST(ch)) break;
4071                      }
4072                   }
4073                   else
4074                      x++;
4075                   ComputeColumn();
4076                   DirtyLine(y);
4077                }
4078                else
4079                {
4080                   if(line.next)
4081                   {
4082                      DirtyLine(y);
4083                      line = line.next;
4084                      y++;
4085                      x = 0;
4086                      col = 0;
4087                      DirtyLine(y);
4088                   }
4089                }
4090             }
4091             if(!shift) _Deselect();
4092             SetViewToCursor(true);
4093             // break;
4094             return false;
4095          }
4096          case up:
4097             if(key.ctrl)
4098             {
4099                if(!style.vScroll || hasVertScroll) break;
4100                LineUp();
4101                return false;
4102             }
4103             else
4104             {
4105                if(style.stuckCaret) break;
4106
4107                if(!shift) SelDirty();
4108                DirtyLine(this.y);
4109
4110                if(style.wrap)
4111                {
4112                }
4113                else if(line.prev)
4114                {
4115                   line = line.prev;
4116                   this.y--;
4117                   this.x = AdjustXPosition(line, caretX, true, null, MAXINT, 0);
4118                }
4119
4120                DirtyLine(this.y);
4121                if(!shift) _Deselect();
4122                ComputeColumn();
4123                SetViewToCursor(false);
4124
4125                /*
4126                if(caretY == this.y * space.h)
4127                {
4128                   if(line.prev)
4129                   {
4130                      line = line.prev;
4131                      this.y--;
4132                      if(!style.freeCaret)
4133                         this.x = Min(this.x, line.count);
4134                      caretY = MAXINT;
4135                   }
4136                   else
4137                      return false;
4138                }
4139
4140                {
4141                   int th = space.h;
4142                   int textPos = 0;
4143                   int sx = 0, sy = this.y * space.h;
4144                   char * text = line.text;
4145                   int maxW = clientSize.w - sx;
4146                   display.FontExtent(font, " ", 1, null, &th);
4147
4148                   do
4149                   {
4150                      int startPos = textPos;
4151                      int width = 0;
4152                      int x = 0;
4153                      bool lineComplete = false;
4154
4155                      if(!style.wrap && caretY == MAXINT)
4156                      {
4157                         caretY = sy + th;
4158                         //textPos = line.count;
4159                         //lineComplete = true;
4160                      }
4161
4162                      for(; (style.freeCaret || textPos < line.count) && !lineComplete;)
4163                      {
4164                         int w = 0;
4165                         int len;
4166                         char * nextSpace = (textPos < line.count) ? strchr(text + textPos, ' ') : (text + textPos);
4167
4168                         if(nextSpace)
4169                            len = (nextSpace - (text + textPos));
4170                         else
4171                            len = line.count - textPos;
4172
4173                         if(textPos < line.count)
4174                         {
4175                            display.FontExtent(font, text + textPos, len, &w, null);
4176                         }
4177                         if(nextSpace) { w += space.w; len++; }
4178
4179                         if(style.wrap && x + width + w > maxW && x > 0)
4180                         {
4181                            lineComplete = true;
4182                            break;
4183                         }
4184                         textPos += len;
4185                         width += w;
4186                         if(nextSpace)
4187                         {
4188                            x += width;
4189                            width = 0;
4190                         }
4191                         if((!style.freeCaret && textPos >= line.count) || (sy == caretY - th && caretX <= x + width + sx))
4192                         {
4193                            x += width;
4194                            this.x = textPos;
4195                            while(this.x > 0 && x + sx > caretX && this.x > startPos)
4196                            {
4197                               int len;
4198                               if(this.x > line.count)
4199                                  this.x--;
4200                               else
4201                                  while(this.x > 0 && !UTF8_IS_FIRST(text[--this.x]));
4202                               len = this.x - startPos;
4203                               display.FontExtent(font, text + startPos, len, &x, null);
4204                            }
4205
4206                            DirtyLine(this.y);
4207                            if(!shift) _Deselect();
4208                            ComputeColumn();
4209                            SetViewToCursor(false);
4210                            return false;
4211                         }
4212                      }
4213                      if(sy == caretY - th || textPos >= line.count)
4214                      {
4215                         if(textPos >= line.count)
4216                         {
4217                            int c = textPos - 1;
4218                            while(c > 0 && text[c] == ' ') c--;
4219                            this.x = c + 1;
4220                         }
4221                         else
4222                            this.x = line.count;
4223
4224                         DirtyLine(this.y);
4225                         if(!shift) _Deselect();
4226                         ComputeColumn();
4227                         SetViewToCursor(false);
4228                         return false;
4229                      }
4230                      sy += th;
4231                      sx = 0;
4232                   } while(textPos < line.count);
4233
4234                   DirtyLine(this.y);
4235                   if(!shift) _Deselect();
4236                   ComputeColumn();
4237                   SetViewToCursor(false);
4238                   return false;
4239                }
4240                */
4241
4242                // PREVIOUS CODE
4243                /*
4244                if(this.line.prev)
4245                {
4246                   int x = AdjustXPosition(this.line, this.line.prev, true, null, MAXINT, 0);
4247                   if(!shift) SelDirty();
4248                   this.line = this.line.prev;
4249                   DirtyLine(this.y);
4250                   this.y--;
4251
4252                   DirtyLine(this.y);
4253                   this.x = x;
4254                   if(!shift) _Deselect();
4255
4256                   ComputeColumn();
4257
4258                   SetViewToCursor(false);
4259                }
4260                */
4261
4262             }
4263             // break;
4264             return style.multiLine ? false : true;
4265          case down:
4266             if(key.ctrl)
4267             {
4268                if(!style.vScroll || hasVertScroll)
4269                   break;
4270                LineDown();
4271                return false;
4272             }
4273             else
4274             {
4275                if(style.stuckCaret) break;
4276                {
4277                   int th = space.h;
4278                   int textPos = 0;
4279                   int sx = 0, sy = this.y * this.space.h;
4280                   int maxW = clientSize.w - sx;
4281                   char * text = line.buffer;
4282
4283                   if(!shift) SelDirty();
4284                   DirtyLine(this.y);
4285
4286                   if(style.wrap)
4287                   {
4288                      /*
4289                      if(AdjustXPosition(line, maxW, this.x, line.count, true, null, MAXINT, 0) <= line.count)
4290                      {
4291
4292                      }
4293                      */
4294                   }
4295                   else if(line.next)
4296                   {
4297                      line = line.next;
4298                      this.y++;
4299                      this.x = AdjustXPosition(line, caretX, true, null, MAXINT, 0);
4300                   }
4301
4302                   DirtyLine(this.y);
4303                   if(!shift) _Deselect();
4304                   ComputeColumn();
4305                   SetViewToCursor(false);
4306
4307                   /*
4308                   while(!textPos || (style.freeCaret || textPos<line.count))
4309                   {
4310                      int startPos = textPos;
4311                      int width = 0;
4312                      int x = 0;
4313                      bool lineComplete = false;
4314                      if(!style.wrap && sy <= caretY)
4315                      {
4316                         textPos = line.count;
4317                         lineComplete = true;
4318                      }
4319                      for(; (style.freeCaret || textPos<line.count) && !lineComplete;)
4320                      {
4321                         int w = 0;
4322                         int len;
4323                         char * nextSpace = (textPos < line.count) ? strchr(text + textPos, ' ') : (text + textPos);
4324
4325                         if(nextSpace)
4326                            len = (nextSpace - (text + textPos));
4327                         else
4328                            len = line.count - textPos;
4329
4330                         if(textPos < line.count)
4331                         {
4332                            display.FontExtent(font, text + textPos, len, &w, &th);
4333                         }
4334                         if(nextSpace) { w += space.w; len++; }
4335                         if(style.wrap && x + width + w > maxW && x > 0)
4336                         {
4337                            lineComplete = true;
4338                            break;
4339                         }
4340                         textPos += len;
4341                         width += w;
4342                         if(nextSpace)
4343                         {
4344                            x += width;
4345                            width = 0;
4346                         }
4347                         if(sy > caretY && ((!style.freeCaret && textPos >= line.count) || caretX <= x + width + sx))
4348                         {
4349                            this.x = textPos;
4350                            x += width;
4351                            while(this.x > 0 && x + sx > caretX && textPos > startPos)
4352                            {
4353                               int len;
4354                               if(this.x > line.count)
4355                                  this.x--;
4356                               else
4357                                  while(this.x > 0 && !UTF8_IS_FIRST(text[--this.x]));
4358
4359                               len = this.x - startPos;
4360                               display.FontExtent(font, text + startPos, len, &x, null);
4361                            }
4362
4363                            if(!shift) _Deselect();
4364                            ComputeColumn();
4365
4366                            SetViewToCursor(false);
4367                            return false;
4368                         }
4369                      }
4370                      if(sy > caretY)
4371                      {
4372                         this.x = line.count;
4373
4374                         DirtyLine(this.y);
4375                         if(!shift) _Deselect();
4376                         ComputeColumn();
4377
4378                         SetViewToCursor(false);
4379                         return false;
4380                      }
4381                      else if(textPos >= line.count && line.next)
4382                      {
4383                         startPos = 0;
4384                         textPos = 0;
4385                         line = line.next;
4386                         this.y++;
4387                         sy = this.y * this.space.h;
4388                         sx = 0; //textBlock.startX;
4389                         text = line.buffer;
4390                      }
4391                      else
4392                      {
4393                         sy += th;
4394                         sx = 0; //textBlock.startX;
4395                      }
4396                   }
4397                   */
4398                   /*
4399                   if(line.next)
4400                   {
4401                      line = line.next;
4402                      this.x = Min(this.x, line.count);
4403                      //int x = AdjustXPosition(this.line, this.line.next, true, null, MAXINT, 0);
4404                      this.y++;
4405                      if(!shift) _Deselect();
4406                      ComputeColumn();
4407
4408                      if(this.selX != this.x || this.selY != this.y)
4409                         DirtyLine(this.y);
4410
4411                      SetViewToCursor(false);
4412                   }
4413                   */
4414                }
4415                /* PREVIOUS CODE
4416                if(this.line.next)
4417                {
4418                   int x = AdjustXPosition(this.line, this.line.next, true, null, MAXINT, 0);
4419                   if(!shift) SelDirty();
4420                   this.line = this.line.next;
4421                   DirtyLine(this.y);
4422                   this.y++;
4423                   this.x = x;
4424                   if(!shift) _Deselect();
4425                   ComputeColumn();
4426
4427                   if(this.selX != this.x || this.selY != this.y)
4428                      DirtyLine(this.y);
4429
4430                   SetViewToCursor();
4431                }
4432                */
4433             }
4434             // break;
4435             return style.multiLine ? false : true;
4436          case home:
4437          {
4438             if(style.stuckCaret) break;
4439             if(!style.multiLine && key.ctrl) break;
4440             if(!(style.freeCaret))
4441                this.selX = Min(this.selX, this.selLine.count);
4442
4443             if(!shift) SelDirty();
4444             if(key.ctrl)
4445             {
4446                this.line = this.lines.first;
4447                if(this.y != 0 || this.x != 0)
4448                   DirtyAll();
4449                this.y = 0;
4450                this.x = 0;
4451                this.col = 0;
4452             }
4453             else
4454             {
4455                if(style.smartHome)
4456                {
4457                   EditLine line = this.line;
4458                   int c;
4459                   for(c=0; line.buffer[c]; c++)
4460                      if(line.buffer[c] != ' ' && line.buffer[c] != '\t')
4461                         break;
4462                   if(overwrite || (shift && (c != 0 || this.x)))
4463                      DirtyLine(this.y);
4464                   if(this.x != c)
4465                      this.x = c;
4466                   else
4467                      this.x = 0;
4468                }
4469                else
4470                {
4471                   if(overwrite || (shift && this.x != 0))
4472                      DirtyLine(this.y);
4473                   this.x = 0;
4474                }
4475                ComputeColumn();
4476             }
4477             if(!shift) _Deselect();
4478             SetViewToCursor(true);
4479             //break;
4480             return false;
4481          }
4482          case end:
4483          {
4484             if(style.stuckCaret) break;
4485             if(!style.multiLine && key.ctrl) break;
4486             if(!style.freeCaret)
4487                this.selX = Min(this.selX, this.selLine.count);
4488
4489             if(!shift) SelDirty();
4490             if(key.ctrl)
4491             {
4492                GoToEnd(false);
4493             }
4494             else if(this.x != this.line.count)
4495             {
4496                this.x = this.line.count;
4497                if(overwrite || shift)
4498                   DirtyLine(this.y);
4499                ComputeColumn();
4500             }
4501             if(!shift) _Deselect();
4502             SetViewToCursor(true);
4503             //break;
4504             return false;
4505          }
4506          case tab:
4507             if(style.tabKey && !key.ctrl)
4508             {
4509                if(this.selY != this.y && style.tabSel)
4510                {
4511                   EditLine firstLine, lastLine;
4512                   EditLine line;
4513                   int y, x;
4514
4515                   // Do multi line selection tabbing here
4516                   if(this.selY < this.y)
4517                   {
4518                      firstLine = this.selLine;
4519                      lastLine = this.line;
4520                      y = this.selY;
4521                      x = this.x;
4522                   }
4523                   else
4524                   {
4525                      // Selecting going up
4526                      firstLine = this.line;
4527                      lastLine = this.selLine;
4528                      y = this.y;
4529                      x = this.selX;
4530                   }
4531                   ComputeColumn();
4532                   if(shift)
4533                   {
4534                      for(line = firstLine; line; line = line.next, y++)
4535                      {
4536                         if(line != lastLine || x)
4537                         {
4538                            int c;
4539                            int lastC = 0;
4540                            BufferLocation before = { line, y, 0 }, after = { line, y, 0 };
4541
4542                            for(c=0; c<line.count && lastC < this.tabSize; c++, lastC++)
4543                            {
4544                               if(line.buffer[c] == '\t')
4545                               {
4546                                  lastC++;
4547                                  break;
4548                               }
4549                               else if(line.buffer[c] != ' ')
4550                                  break;
4551                            }
4552                            after.x = lastC;
4553
4554                            NotifyCharsDeleted(master, this, &before, &after, this.pasteOperation);
4555                            if(lastC)
4556                            {
4557                               int len = GetText(null, line, y, 0, line, y, lastC, false, false);
4558                               char * string = new char[len];
4559                               DelTextAction action { y1 = y, x1 = 0, y2 = y, x2 = lastC, string = string, placeAfter = true };
4560                               GetText(string, line, y, 0, line, y, lastC, false, false);
4561                               Record(action);
4562                            }
4563                            memmove(line.buffer,line.buffer+lastC,line.size-lastC);
4564                            if(!line.AdjustBuffer(line.count-lastC))
4565                               break;
4566                            line.count-=lastC;
4567                            DirtyLine(y);
4568                         }
4569
4570                         if(line == lastLine) break;
4571                      }
4572                   }
4573                   else
4574                   {
4575                      for(line = firstLine; line; line = line.next, y++)
4576                      {
4577                         if(line.count)
4578                         {
4579                            if(line != lastLine || x)
4580                            {
4581                               if(style.useTab)
4582                               {
4583                                  if(line.count + 1 <= this.maxLineSize)
4584                                  {
4585                                     BufferLocation before = { line, y, 0 }, after = { line, y, 1 };
4586
4587                                     if(!line.AdjustBuffer(line.count+1))
4588                                        break;
4589
4590                                     NotifyCharsAdded(master, this, &before, &after, this.pasteOperation);
4591                                     {
4592                                        AddCharAction action { ch = '\t', x = 0, y = y };
4593                                        Record(action);
4594                                     }
4595
4596                                     memmove(line.buffer+1,line.buffer,line.size-1);
4597                                     line.count++;
4598                                     line.buffer[0] = '\t';
4599                                  }
4600                               }
4601                               else
4602                               {
4603                                  if(line.count + this.tabSize <= this.maxLineSize)
4604                                  {
4605                                     int c;
4606                                     BufferLocation before = { line, y, 0 }, after = { line, y, this.tabSize };
4607                                     NotifyCharsAdded(master, this, &before, &after, this.pasteOperation);
4608
4609                                     if(!line.AdjustBuffer(line.count+this.tabSize))
4610                                        break;
4611
4612                                     {
4613                                        char * string = new char[this.tabSize + 1];
4614                                        memset(string, ' ', this.tabSize);
4615                                        string[this.tabSize] = '\0';
4616                                        Record(AddTextAction { string = string, x1 = 0, y1 = y, x2 = this.tabSize, y2 = y });
4617                                     }
4618
4619                                     memmove(line.buffer+this.tabSize,line.buffer,line.size-(this.tabSize));
4620                                     line.count+=this.tabSize;
4621                                     for(c=0; c<this.tabSize; c++)
4622                                        line.buffer[c] = ' ';
4623                                  }
4624                               }
4625                               DirtyLine(y);
4626                            }
4627                         }
4628                         if(line == lastLine) break;
4629                      }
4630                   }
4631                   ComputeLength(maxLine);
4632                }
4633                else
4634                {
4635                   if(style.useTab)
4636                   {
4637                      // Insert character
4638                      AddCh('\t');
4639                   }
4640                   else
4641                   {
4642                      int start, c;
4643                      char * addString = new char[this.tabSize + 1];
4644                      int len = 0;
4645                      if(!(style.freeCaret))
4646                      {
4647                         this.x = Min(this.x, this.line.count);
4648                         ComputeColumn();
4649                      }
4650                      // Insert spaces
4651                      start = Min(this.x, this.selX);
4652                      for(c=start; ((c == start) || ((c) % this.tabSize)); c++)
4653                      {
4654                         addString[len++] = ' ';
4655                      }
4656                      addString[len] = 0;
4657                      AddS(addString);
4658                      delete addString;
4659                   }
4660                }
4661                Modified();
4662                SetViewToCursor(true);
4663                return false;
4664             }
4665             break;
4666          case pageDown:
4667             if(style.multiLine)
4668             {
4669                if(key.ctrl)
4670                {
4671                   if(!(style.hScroll) || hasHorzScroll) break;
4672                   if(this.viewX < this.maxLength)
4673                   {
4674                      //this.viewX+=this.space.w*this.tabSize;
4675                      //DirtyAll();
4676                      SetScrollPosition((this.viewX + this.space.w*this.tabSize), this.viewY * this.space.h);
4677                   }
4678                }
4679                else
4680                {
4681                   PageDown();
4682                   DirtyAll();
4683                   if(!shift) _Deselect();
4684                   SetCursorToViewX();
4685                   SetCursorToViewY();
4686                }
4687                return false;
4688             }
4689             break;
4690          case pageUp:
4691             if(style.multiLine)
4692             {
4693                if(key.ctrl)
4694                {
4695                   if(!(style.hScroll) || hasHorzScroll) break;
4696                   if(this.viewX > 0)
4697                   {
4698                      //this.viewX-=this.space.w*this.tabSize;
4699                      //this.viewX = Max(this.viewX,0);
4700                      //DirtyAll();
4701                      SetScrollPosition((this.viewX-this.space.w*this.tabSize), this.viewY * this.space.h);
4702                      // SetCursorToView();
4703                   }
4704                }
4705                else
4706                {
4707                   PageUp();
4708                   DirtyAll();
4709                   if(!shift) _Deselect();
4710                   SetCursorToViewX();
4711                   SetCursorToViewY();
4712                }
4713                return false;
4714             }
4715             break;
4716          case insert:
4717             if(key.ctrl)
4718             {
4719                Copy();
4720                return false;
4721             }
4722             else if(!style.readOnly)
4723             {
4724                if(key.shift)
4725                {
4726                   if(!(style.readOnly))
4727                      Paste();
4728                   return false;
4729                }
4730                else
4731                {
4732                   this.overwrite ^= 1;
4733                   UpdateCaretPosition(true);
4734                   if(this.overwrite)
4735                      SetCaret(0,0,0);
4736                   DirtyLine(this.y);
4737                   UpdateDirty();
4738                   NotifyOvrToggle(master, this, this.overwrite);
4739                }
4740             }
4741             break;
4742          case hotKey:
4743             break;
4744          default:
4745
4746             switch(key)
4747             {
4748                case ctrlA:
4749                   // Select All
4750                   if(style.noSelect) break;
4751
4752                   {
4753                      //Save current view position
4754                      int tempX = this.viewX;
4755                      int tempY = this.viewY;
4756
4757                      this.selX = 0;
4758                      this.selY = 0;
4759                      this.selLine = this.lines.first;
4760                      this.y = this.lineCount-1;
4761                      this.line = this.lines.last;
4762                      this.x = this.line.count;
4763                      ComputeColumn();
4764                      DirtyAll();
4765                      SetViewToCursor(true);
4766
4767                      //Restore previous view position
4768                      SetScrollPosition(tempX, tempY * this.space.h);
4769
4770                      UpdateDirty();
4771                      SetSelectCursor();
4772                      SelectionEnables();
4773                   }
4774                   // TOCHECK: Was there any good reason why we weren't returning false here?
4775                   return false; // break;
4776                case ctrlC:
4777                   Copy();
4778                   return false;
4779                case ctrlV:
4780                   if(!(style.readOnly))
4781                   {
4782                      Paste();
4783                      return false;
4784                   }
4785                   break;
4786                case ctrlX:
4787                {
4788                   if(style.readOnly) break;
4789                   Cut();
4790                   return false;
4791                }
4792                case ctrlL:
4793                {
4794                   if(style.readOnly) break;
4795                   ClearLine();
4796                   return false;
4797                }
4798                case ctrlZ:
4799                   if(style.readOnly) break;
4800                   Undo();
4801                   return false;
4802                case ctrlY:
4803                   if(style.readOnly) break;
4804                   Redo();
4805                   return false;
4806                default:
4807                   if(style.readOnly) break;
4808                   if(key.shift && key.code == rightBracket)
4809                   {
4810                      //Only indent back if you are exactly at one tab.
4811                      {
4812                         bool whitespace = true;
4813                         int i;
4814                         char * newline;
4815                         int putsize;
4816
4817                         int indentwidth;
4818                         EditLine line = this.line;
4819
4820                         //Only remove one tab if there is nothing else on the line.
4821                         for(i = 0; i < this.line.count; i++)
4822                         {
4823                            if(this.line.buffer[i] != ' ' && this.line.buffer[i] != '\t')
4824                            {
4825                               PutCh(ch);
4826                               return false;
4827                            }
4828                         }
4829
4830                         //Find opening {
4831                         i = 1;
4832                         while(i && line)
4833                         {
4834                            int pos;
4835
4836                            indentwidth = 0;
4837                            for(pos = line.count - 1; pos >= 0; pos--)
4838                            {
4839                               char c = line.buffer[pos];
4840                               if(c == ' ')
4841                                  indentwidth++;
4842                               else if(c == '\t')
4843                                  indentwidth += this.tabSize;
4844                               else
4845                                  //Counting backwards, so when you find a character, indentation is reset
4846                                  indentwidth = 0;
4847                               if(i && c == '}')
4848                                  i++;
4849                               if(i && c == '{')
4850                                  i--;
4851                            }
4852                            line = line.prev;
4853                         }
4854
4855                         //Place the } to get an undo:
4856                         PutCh(ch);
4857
4858                         this.x = this.line.count;
4859                         this.selX = 0;
4860
4861                         if(!style.useTab)
4862                            putsize = indentwidth;
4863                         else
4864                            putsize = indentwidth / this.tabSize + indentwidth % this.tabSize;
4865
4866                         newline = new char[putsize+2];
4867                         newline[putsize] = '}';
4868                         newline[putsize+1] = '\0';
4869
4870                         i = 0;
4871                         if(style.useTab)
4872                            for(; i < indentwidth / this.tabSize; i++)
4873                               newline[i] = '\t';
4874                         for(;i < putsize; i++)
4875                            newline[i] = ' ';
4876
4877                         AddS(newline);
4878
4879                         delete newline;
4880                      }
4881                      return false;
4882                   }
4883                   else if(!key.ctrl && !key.alt && ch != 128 && ch >= 32)
4884                   {
4885                      PutCh(ch);
4886                      return false;
4887                   }
4888                   break;
4889             }
4890             break;
4891       }
4892       return true;
4893    }
4894
4895    void OnHScroll(ScrollBarAction action, int position, Key key)
4896    {
4897 #ifdef _DEBUG
4898       //PrintLn("OnHScroll: ", action, ", pos = ", position, ", key = ", key);
4899 #endif
4900       this.viewX = position;
4901       if(action != setRange)
4902       {
4903          if(!this.mouseMove && style.cursorFollowsView)
4904             SetCursorToViewX();
4905       }
4906       DirtyAll();
4907       UpdateDirty();
4908    }
4909
4910    void OnVScroll(ScrollBarAction action, int position, Key key)
4911    {
4912       int oldViewY = this.viewY;
4913
4914 #ifdef _DEBUG
4915       //PrintLn("OnVScroll: ", action, ", pos = ", position, ", key = ", key);
4916 #endif
4917       position /= this.space.h;
4918
4919       if(position < this.viewY)
4920       {
4921          for(; position < this.viewY && this.viewLine.prev; this.viewLine = this.viewLine.prev, this.viewY--);
4922          style.recomputeSyntax = true;
4923       }
4924       else if(position > this.viewY)
4925       {
4926          EditLine oldViewLine = viewLine;
4927          for(; position > this.viewY && this.viewLine.next; this.viewLine = this.viewLine.next, this.viewY++);
4928          FigureStartSyntaxStates(oldViewLine, false);
4929       }
4930
4931       if(action != setRange)
4932       {
4933          if(!this.mouseMove && style.cursorFollowsView && !SelSize()) SetCursorToViewY();
4934       }
4935
4936       if(this.x != this.selX || this.y != this.selY)
4937          DirtyLine(this.y);
4938
4939       {
4940          Scroll(0, (this.viewY - oldViewY) * this.space.h);
4941
4942          /*
4943          int numLines = clientSize.h / this.space.h;
4944          int y;
4945          if(Abs(this.viewY - oldViewY) < numLines)
4946
4947
4948          if(this.viewY > oldViewY)
4949          {
4950             for(y = oldViewY; y <this.viewY; y++)
4951                DirtyLine(y + numLines);
4952          }
4953          else
4954          {
4955             for(y = this.viewY; y <oldViewY; y++)
4956                DirtyLine(y + numLines);
4957          }*/
4958       }
4959       //DirtyAll();
4960
4961       // Fix dirt of stuff before first line...
4962       if(this.viewY - oldViewY > 0)
4963       {
4964          Box box { 0,0, clientSize.w-1, YOFFSET-1 };
4965          Update(box);
4966       }
4967
4968       UpdateDirty();
4969    }
4970
4971    bool _AddCh(unichar ch, int * addedSpacesPtr, int * addedTabsPtr)
4972    {
4973       EditLine line;
4974       int length, endX;
4975       bool result;
4976       ReplaceTextAction replaceAction = null;
4977       AddCharAction addCharAction = null;
4978       int addedSpaces = 0, addedTabs = 0;
4979
4980       if(ch == '\r') return true;
4981       if(style.stuckCaret /*|EES_READONLY)*/ )
4982          GoToEnd(true);
4983
4984       if(ch == '\n' && !(style.multiLine) && this.line) return false;
4985
4986       if(!undoBuffer.dontRecord)
4987       {
4988          if(selX != x || selY != y)
4989          {
4990             char buffer[5];
4991             char * newString;
4992             char * oldString;
4993             int len = GetText(null, selLine, selY, selX, this.line, y, x, false, false);
4994             oldString = new char[len];
4995             UTF32toUTF8Len(&ch, 1, buffer, 4);
4996             newString = CopyString(buffer);
4997             GetText(oldString, selLine, selY, selX, this.line, y, x, false, false);
4998
4999             replaceAction = ReplaceTextAction { newString = newString, oldString = oldString, placeAfter = false };
5000             if(selY < y || (selY == y && selX < x))
5001             {
5002                replaceAction.x1 = selX;
5003                replaceAction.y1 = selY;
5004                replaceAction.x2 = x;
5005                replaceAction.y2 = y;
5006             }
5007             else
5008             {
5009                replaceAction.x1 = x;
5010                replaceAction.y1 = y;
5011                replaceAction.x2 = selX;
5012                replaceAction.y2 = selY;
5013             }
5014             Record(replaceAction);
5015             undoBuffer.dontRecord++;
5016          }
5017          else
5018          {
5019             addCharAction = AddCharAction { y = y, x = x, ch = ch };
5020             Record(addCharAction);
5021          }
5022       }
5023
5024       if(ch == '\n')
5025       {
5026          DelSel(&addedSpaces);
5027          if(this.lineCount+1 > this.maxLines)
5028          {
5029             if(style.autoEmpty)
5030                Emptyline(this.lines.first,0);
5031             else
5032                return false;
5033          }
5034          if(!(style.autoSize && (!maxClientSize.h || maxClientSize.h > clientSize.h + this.space.h)) && !(style.vScroll))
5035          {
5036             // Make sure it fits, but we need a default line is this.font is too big for window
5037             if(this.space.h * (this.lineCount+1) > clientSize.h && this.line)
5038                return false;
5039          }
5040          if((this.y >= 0) && this.y < this.viewY)
5041          {
5042             this.viewY++;
5043             DirtyAll();
5044          }
5045          line = EditLine { };
5046          if(!line)
5047             return false;
5048          lines.Insert(this.line, line);
5049          line.editBox = this;
5050          line.buffer = null;
5051          line.count = 0;
5052          line.size = 0;
5053          length = 0;
5054
5055          // If we're displacing the lines from a current line ...
5056          if(this.line)
5057          {
5058             if(this.line.buffer)
5059             {
5060                endX = this.x;
5061                if(this.line.count < endX) endX = this.line.count;
5062                length = this.line.count - endX;
5063             }
5064          }
5065          if(!line.AdjustBuffer(length))
5066             return false;
5067          if(this.line)
5068          {
5069             if(this.line.buffer)
5070             {
5071                CopyBytes(line.buffer,this.line.buffer+endX, length+1);
5072 #ifdef _DEBUG
5073       if(endX > 4000 || endX < 0)
5074          printf("Warning");
5075 #endif
5076                this.line.count = endX;
5077                this.line.buffer[this.line.count] = '\0';
5078                this.line.AdjustBuffer(this.line.count);
5079                ComputeLength(this.line);
5080             }
5081          }
5082          {
5083             BufferLocation before = { this.line, this.y, this.x }, after;
5084
5085             this.line = line;
5086             this.x = 0;
5087             this.col = 0;
5088             line.count = length;
5089
5090 #ifdef _DEBUG
5091       if(length > 4000 || length < 0)
5092          printf("Warning");
5093 #endif
5094             ComputeLength(this.line);
5095
5096             DirtyEnd(this.y);
5097             this.y++;
5098             this.lineCount++;
5099             line.buffer[line.count] = '\0';
5100             result = true;
5101
5102             after.line = this.line, after.y = this.y, after.x = this.x;
5103
5104             NotifyCharsAdded(master, this, &before, &after, this.pasteOperation);
5105          }
5106       }
5107       else
5108       {
5109          char string[5];
5110          int count = UTF32toUTF8Len(&ch, 1, string, 5);
5111          DelSel(&addedSpaces);
5112          result = AddToLine(string, count, false, addedSpaces ? null : &addedSpaces, &addedTabs);
5113          if(addedSpacesPtr) *addedSpacesPtr = addedSpaces;
5114          if(addedTabsPtr) *addedTabsPtr = addedTabs;
5115       }
5116       this.selX = this.x;
5117       this.selY = this.y;
5118       this.selLine = this.line;
5119
5120       if(replaceAction)
5121       {
5122          replaceAction.x3 = x;
5123          replaceAction.y3 = y;
5124          replaceAction.addedSpaces = addedSpaces;
5125          replaceAction.addedTabs = addedTabs;
5126          undoBuffer.dontRecord--;
5127       }
5128       if(addCharAction)
5129       {
5130          addCharAction.x -= addedTabs * (tabSize-1);
5131          addCharAction.addedSpaces = addedSpaces;
5132          addCharAction.addedTabs = addedTabs;
5133       }
5134       return result;
5135    }
5136
5137 public:
5138
5139    /****************************************************************************
5140                                  EDIT BOX METHODS
5141    ****************************************************************************/
5142
5143
5144    bool AddCh(unichar ch)
5145    {
5146       return _AddCh(ch, null, null);
5147    }
5148
5149    void Modified()
5150    {
5151       this.modified = true;
5152       NotifyUpdate(master, this);
5153       modifiedDocument = true;
5154    }
5155
5156    void Delete(EditLine line1, int y1, int x1, EditLine line2, int y2, int x2)
5157    {
5158       Deselect();
5159       _DelCh(line1, y1, x1, line2, y2, x2, false, false, null);
5160       SetViewToCursor(true);
5161       UpdateDirty();
5162       Modified();
5163    }
5164
5165    void Undo()
5166    {
5167       undoBuffer.Undo();
5168       itemEditUndo.disabled = undoBuffer.curAction == 0;
5169       itemEditRedo.disabled = undoBuffer.curAction == undoBuffer.count;
5170       if(savedAction == undoBuffer.curAction)
5171       {
5172          modifiedDocument = false;
5173          SetModified(false);
5174          NotifyUnsetModified(master, this);
5175       }
5176    }
5177
5178    void Redo()
5179    {
5180       undoBuffer.Redo();
5181       itemEditUndo.disabled = undoBuffer.curAction == 0;
5182       itemEditRedo.disabled = undoBuffer.curAction == undoBuffer.count;
5183       if(savedAction == undoBuffer.curAction)
5184       {
5185          modifiedDocument = false;
5186          SetModified(false);
5187          NotifyUnsetModified(master, this);
5188       }
5189    }
5190
5191    void Record(UndoAction action)
5192    {
5193       if(!undoBuffer.dontRecord)
5194       {
5195          undoBuffer.Record(action);
5196          itemEditUndo.disabled = undoBuffer.curAction == 0;
5197          itemEditRedo.disabled = undoBuffer.curAction == undoBuffer.count;
5198       }
5199    }
5200
5201    void SelectAll()
5202    {
5203       Select(
5204          this.lines.first, 0,0,
5205          this.lines.last, this.lines.count-1, strlen(((EditLine)this.lines.last).buffer));
5206    }
5207
5208    void Select(EditLine line1, int y1, int x1, EditLine line2, int y2, int x2)
5209    {
5210       SelDirty();
5211       this.selX = x1;
5212       this.selY = y1;
5213       this.selLine = line1 ? (EditLine)line1 : this.lines.first;
5214       this.x = line2 ? x2 : ((EditLine)this.lines.last).count;
5215       this.y = line2 ? y2 : (this.lineCount-1);
5216       this.line = line2 ? (EditLine)line2 : this.lines.last;
5217       ComputeColumn();
5218       SelDirty();
5219       SetViewToCursor(true);
5220       UpdateDirty();
5221    }
5222
5223    // TODO: Fix this vs modifiedDocument window property
5224    void SetModified(bool flag)
5225    {
5226       if(this)
5227       {
5228          this.modified = false;
5229          if(flag && !NotifyModified(master, this))
5230             this.modified = true;
5231       }
5232    }
5233
5234    // BASIC OUTPUT
5235    bool AddS(char * string)
5236    {
5237       if(this)
5238       {
5239          bool ret = true;
5240          char * line;
5241          int c, count;
5242          int addedSpaces = 0, addedTabs = 0;
5243          AddTextAction action = null;
5244          ReplaceTextAction replaceAction = null;
5245
5246          this.pasteOperation = true;
5247
5248          if(style.stuckCaret /*|EES_READONLY)*/ )
5249             GoToEnd(true);
5250
5251          if(!undoBuffer.dontRecord)
5252          {
5253             char * placeString = CopyString(string);
5254             if(!style.multiLine)
5255             {
5256                int i;
5257                char ch;
5258                for(i = 0; (ch = placeString[i]); i++)
5259                   if(ch == '\n')
5260                   {
5261                      placeString[i] = '\0';
5262                      placeString = renew placeString byte[i+1];
5263                      break;
5264                   }
5265             }
5266
5267             if(selX != x || selY != y)
5268             {
5269                char * newString;
5270                char * oldString;
5271                int len = GetText(null, selLine, selY, selX, this.line, y, x, false, false);
5272                oldString = new char[len];
5273                newString = placeString;
5274                GetText(oldString, selLine, selY, selX, this.line, y, x, false, false);
5275
5276                replaceAction = ReplaceTextAction { newString = newString, oldString = oldString, placeAfter = false };
5277                if(selY < y || (selY == y && selX < x))
5278                {
5279                   replaceAction.x1 = selX;
5280                   replaceAction.y1 = selY;
5281                   replaceAction.x2 = x;
5282                   replaceAction.y2 = y;
5283                }
5284                else
5285                {
5286                   replaceAction.x1 = x;
5287                   replaceAction.y1 = y;
5288                   replaceAction.x2 = selX;
5289                   replaceAction.y2 = selY;
5290                }
5291                Record(replaceAction);
5292             }
5293             else if(string[0])
5294             {
5295                if(string[0] == '\n')
5296                   action = AddTextAction { y1 = y, x1 = Min(this.line.count, x), string = placeString };
5297                else
5298                   action = AddTextAction { y1 = y, x1 = x, string = placeString };
5299
5300                Record(action);
5301             }
5302             else
5303                delete placeString;
5304          }
5305
5306          undoBuffer.dontRecord++;
5307          DelSel(&addedSpaces);
5308
5309          count = 0;
5310          line = string;
5311          for(c = 0; string[c]; c++)
5312          {
5313             if(string[c] == '\n' || string[c] == '\r')
5314             {
5315                if(!AddToLine(line,count, true, addedSpaces ? null : &addedSpaces, addedTabs ? null : &addedTabs))
5316                {
5317                   ret = false;
5318                   break;
5319                }
5320                if(string[c] == '\n')
5321                {
5322                   if(!AddCh('\n'))
5323                   {
5324                      count = 0;
5325                      ret = false;
5326                      break;
5327                   }
5328                }
5329                // Reset for next line
5330                count = 0;
5331                line = string+c+1;
5332                /*
5333                if(string[c] == '\r' && *line == '\n')
5334                {
5335                   line++;
5336                   c++;
5337                }*/
5338             }
5339             else
5340             {
5341                count++;
5342             }
5343          }
5344
5345          // Why was this here?
5346          // FindMaxLine();
5347
5348          // Add the line here
5349          if(ret && count)
5350             if(!AddToLine(line,count,false, addedSpaces ? null : &addedSpaces, addedTabs ? null : &addedTabs))
5351             {
5352                ret = false;
5353             }
5354          FindMaxLine();
5355
5356          undoBuffer.dontRecord--;
5357          if(action)
5358          {
5359             action.y2 = y;
5360             action.x2 = x;
5361             action.addedSpaces = addedSpaces;
5362             action.addedTabs = addedTabs;
5363          }
5364          else if(replaceAction)
5365          {
5366             replaceAction.y3 = y;
5367             replaceAction.x3 = x;
5368             replaceAction.addedSpaces = addedSpaces;
5369             replaceAction.addedTabs = addedTabs;
5370          }
5371
5372          UpdateCaretPosition(true);
5373
5374          this.pasteOperation = false;
5375
5376          return ret;
5377       }
5378       return false;
5379    }
5380
5381    // CONSOLE OUTPUT
5382    void Clear()
5383    {
5384       if(this)
5385       {
5386          Deselect();
5387          DelCh(this.lines.first, 0, 0, this.lines.last, this.lineCount-1, ((EditLine)(this.lines.last)).count, true);
5388          SetViewToCursor(true);
5389          UpdateDirty();
5390          Modified();
5391       }
5392    }
5393
5394    void PutCh(unichar ch)
5395    {
5396       bool result;
5397
5398       if((ch >= 32 /*&& ch <=126*/) || ch == '\n')
5399       //if((ch >= 32) || ch == '\n')
5400       {
5401          int addedSpaces = 0, addedTabs = 0;
5402          ReplaceTextAction replaceAction = null;
5403          AddCharAction addCharAction = null;
5404
5405          if(style.allCaps)
5406             ch = (ch < 128) ? toupper(ch) : ch;     // TODO: UNICODE TO UPPER
5407
5408          if(this.overwrite && selX == x && selY == y && this.x < this.line.count)
5409          {
5410             char buffer[5];
5411             char * newString;
5412             char * oldString;
5413             int len = GetText(null, line, y, x, line, y, x+1, false, false);
5414             oldString = new char[len];
5415             UTF32toUTF8Len(&ch, 1, buffer, 4);
5416             newString = CopyString(buffer);
5417             GetText(oldString, line, y, x, line, y, x+1, false, false);
5418             replaceAction = ReplaceTextAction { newString = newString, oldString = oldString, placeAfter = false };
5419             replaceAction.x1 = x;
5420             replaceAction.y1 = y;
5421             replaceAction.x2 = x+1;
5422             replaceAction.y2 = y;
5423             Record(replaceAction);
5424
5425             undoBuffer.dontRecord++;
5426             DelCh(this.line, this.y, this.x, this.line, this.y, this.x+1, true);
5427             undoBuffer.dontRecord--;
5428          }
5429          else if(!undoBuffer.dontRecord)
5430          {
5431             if(selX != x || selY != y)
5432             {
5433                char buffer[5];
5434                char * newString;
5435                char * oldString;
5436                int len = GetText(null, line, y, x, selLine, selY, selX, false, false);
5437                oldString = new char[len];
5438                UTF32toUTF8Len(&ch, 1, buffer, 4);
5439                newString = CopyString(buffer);
5440                GetText(oldString, line, y, x, selLine, selY, selX, false, false);
5441                replaceAction = ReplaceTextAction { newString = newString, oldString = oldString, placeAfter = true };
5442                if(selY < y || (selY == y && selX < x))
5443                {
5444                   replaceAction.x1 = selX;
5445                   replaceAction.y1 = selY;
5446                   replaceAction.x2 = x;
5447                   replaceAction.y2 = y;
5448                }
5449                else
5450                {
5451                   replaceAction.x1 = x;
5452                   replaceAction.y1 = y;
5453                   replaceAction.x2 = selX;
5454                   replaceAction.y2 = selY;
5455                }
5456                Record(replaceAction);
5457             }
5458             else
5459             {
5460                addCharAction = AddCharAction { y = y, x = x, ch = ch };
5461                Record(addCharAction);
5462             }
5463          }
5464          undoBuffer.dontRecord++;
5465          result = _AddCh(ch, &addedSpaces, &addedTabs);
5466          if(replaceAction)
5467          {
5468             replaceAction.x3 = x;
5469             replaceAction.y3 = y;
5470             replaceAction.addedSpaces = addedSpaces;
5471             replaceAction.addedTabs = addedTabs;
5472          }
5473          if(addCharAction)
5474          {
5475             addCharAction.x -= addedTabs * (tabSize-1);
5476             addCharAction.addedSpaces = addedSpaces;
5477             addCharAction.addedTabs = addedTabs;
5478          }
5479          undoBuffer.dontRecord--;
5480          if(ch == '\n')
5481             FindMaxLine();
5482          Modified();
5483          if(result) SetViewToCursor(true);
5484       }
5485    }
5486
5487    void PutS(char * string)
5488    {
5489       if(this)
5490       {
5491          AddS(string);
5492          SetViewToCursor(true);
5493          Modified();
5494       }
5495    }
5496
5497    void Printf(char * format, ...)
5498    {
5499       if(this)
5500       {
5501          char temp[MAX_F_STRING];
5502          va_list args;
5503          va_start(args, format);
5504          vsnprintf(temp, sizeof(temp), format, args);
5505          temp[sizeof(temp)-1] = 0;
5506          va_end(args);
5507          PutS(temp);
5508       }
5509    }
5510
5511    void SetContents(char * format, ...)
5512    {
5513       if(this)
5514       {
5515          undoBuffer.dontRecord++;
5516          Deselect();
5517          DelCh(this.lines.first, 0, 0, this.lines.last, this.lineCount-1, ((EditLine)(this.lines.last)).count, true);
5518          if(format)
5519          {
5520             char temp[MAX_F_STRING];
5521             va_list args;
5522             va_start(args, format);
5523             vsnprintf(temp, sizeof(temp), format, args);
5524             temp[sizeof(temp)-1] = 0;
5525             va_end(args);
5526
5527             AddS(temp);
5528          }
5529          UpdateDirty();
5530          Home();
5531          undoBuffer.dontRecord--;
5532       }
5533    }
5534
5535    void BackSpace()
5536    {
5537       if(!DelSel(null))
5538       {
5539          if(x > 0)
5540          {
5541             if(x > line.count)
5542                x--;
5543             else
5544                x -= 1 + _DelCh(line, y, x-1, line, y, x, true, false, null);
5545             Modified();
5546          }
5547          else if(this.line.prev)
5548          {
5549             EditLine line = this.line.prev;
5550             int x = line.count;
5551             int y = this.y;
5552
5553             _DelCh(line, this.y-1, x, this.line, this.y, this.x, true, false, null);
5554             this.line = line;
5555             this.y = y-1;
5556             this.x = x;
5557             Modified();
5558          }
5559          this.selX = this.x;
5560          this.selY = this.y;
5561          this.selLine = this.line;
5562          ComputeColumn();
5563       }
5564       else
5565          Modified();
5566       SetViewToCursor(true);
5567    }
5568
5569    void ClearLine()
5570    {
5571       Emptyline(this.line,this.y);
5572       this.selX = this.x = 0;
5573       this.col = 0;
5574       this.selY = this.y;
5575       this.selLine = this.line;
5576
5577       SetViewToCursor(true);
5578       Modified();
5579    }
5580
5581    // CARET CONTROL
5582    void End()
5583    {
5584       if(this)
5585       {
5586          GoToEnd(true);
5587          SetViewToCursor(true);
5588       }
5589    }
5590    void Home()
5591    {
5592       if(this)
5593       {
5594          GoToHome(true);
5595          SetViewToCursor(true);
5596       }
5597    }
5598
5599    bool GoToLineNum(int lineNum)
5600    {
5601       if(this.line)
5602       {
5603          int c;
5604          EditLine line = this.lines.first;
5605          for(c = 0; c < lineNum && line; c++, line = line.next);
5606          if(line)
5607          {
5608             if(this.y != c)
5609                DirtyAll();
5610             else
5611                DirtyLine(c);
5612             this.y = c;
5613             this.line = line;
5614             _Deselect();
5615             SetViewToCursor(true);
5616             return true;
5617          }
5618       }
5619       return false;
5620    }
5621
5622    // NOTE: Mismatch with NotifyCaretMove() for x/y + 1
5623    bool GoToPosition(EditLine line, int y, int x)
5624    {
5625       /*
5626       if(!line)
5627       {
5628          line = this.line;
5629          y = this.y;
5630       }
5631       */
5632       if(!line)
5633       {
5634          int c;
5635          for(line = this.lines.first, c = 0; c<y && line; c++, line = line.next);
5636       }
5637
5638       if(line)
5639       {
5640          if(this.y != y)
5641             DirtyAll();
5642          else
5643             DirtyLine(y);
5644          this.x = x;
5645          this.y = y;
5646          this.line = line;
5647          ComputeColumn();
5648          _Deselect();
5649          SetViewToCursor(true);
5650          return true;
5651       }
5652       return false;
5653    }
5654
5655    // VIEW POSITIONING
5656    void SetViewToCursor(bool setCaret)
5657    {
5658       if(created)
5659       {
5660          int w;
5661          int c, numLines;
5662          EditLine line;
5663          int x;
5664          int checkX, checkY;
5665          EditLine checkLine;
5666          bool dontScroll = false;
5667          bool selected;
5668          int viewX, viewY;
5669
5670          FixScrollArea();
5671
5672          selected = selX != this.x || selY != y;
5673
5674          viewX = this.viewX;
5675          viewY = this.viewY;
5676
5677          if(mouseMove)
5678          {
5679             checkLine = dropLine;
5680             checkX = dropX;
5681             checkY = dropY;
5682          }
5683          else
5684          {
5685             checkLine = this.line;
5686             checkX = this.x;
5687             checkY = y;
5688          }
5689
5690          numLines = clientSize.h / space.h;
5691
5692          // This is broken. The EditBox now doesn't do anything different when adding to it,
5693          // regardless of the previous scrolling position. It should be read and then set again
5694          // if one wishes to preserve it.
5695          /*  // Don't scroll view to cursor if we're in a EES_NOCARET box
5696          if(style.noCaret && this.viewY < lineCount - numLines - 1)
5697             dontScroll = true;
5698          */
5699
5700          // Horizontal Adjustment
5701          if(!dontScroll && checkLine)
5702          {
5703             x = 0;
5704             if(mouseMove)
5705                dropX = AdjustXPosition(this.line, MAXINT, true, &x, checkX, 0);
5706             else
5707             {
5708                this.x = AdjustXPosition(this.line, MAXINT, true, &x, checkX, 0);
5709                ComputeColumn();
5710             }
5711
5712             if(style.hScroll)
5713             {
5714                if(x + space.w >= this.viewX + clientSize.w && clientSize.w >= space.w)
5715                   viewX = x - clientSize.w+space.w;
5716                if(x < this.viewX + clientSize.w/2 - space.w)
5717                   viewX = Max(0, x - clientSize.w/2 + space.w);
5718             }
5719          }
5720
5721          if(!dontScroll)
5722          {
5723             if(style.vScroll)
5724             {
5725                // Vertical Adjustment
5726                if(viewY > checkY) viewY = checkY;
5727                if(viewY + numLines <= checkY)
5728                {
5729                   if(clientSize.h >= space.h)
5730                   {
5731                      for(line = viewLine; line && (viewY + numLines <= checkY); line = line.next)
5732                         viewY++;
5733                   }
5734                   else
5735                      viewY = checkY;
5736                }
5737             }
5738             else
5739             {
5740                if(mouseMove)
5741                   for(;dropLine && dropLine.prev && dropY >= numLines;)
5742                   {
5743                      dropLine = dropLine.prev;
5744                      dropY--;
5745                   }
5746                else
5747                   for(;this.line && this.line.prev && this.y >= numLines;)
5748                   {
5749                      this.line = this.line.prev;
5750                      y--;
5751                   }
5752             }
5753
5754             SetScrollPosition(viewX, viewY * this.space.h);
5755
5756             UpdateCaretPosition(setCaret);
5757
5758             if(!selected)
5759             {
5760                selX = this.x;
5761                selY = this.y;
5762                selLine = this.line;
5763             }
5764          }
5765
5766          UpdateDirty();
5767          SetSelectCursor();
5768          SelectionEnables();
5769       }
5770    }
5771
5772    void CenterOnCursor()
5773    {
5774       int numLines = clientSize.h / this.space.h;
5775       int y = this.y - numLines / 2;
5776       int viewY;
5777       bool figureSyntax = false;
5778       EditLine oldViewLine = viewLine;
5779       if(y > this.lineCount - numLines) y = this.lineCount-numLines;
5780       if(y < 0) y = 0;
5781
5782       viewY = y;
5783
5784       for(;y < this.viewY; y++)
5785       {
5786          this.viewLine = this.viewLine.prev;
5787          style.recomputeSyntax = true;
5788       }
5789       for(;y > this.viewY; y--)
5790       {
5791          this.viewLine = this.viewLine.next;
5792          figureSyntax = true;
5793       }
5794       if(figureSyntax)
5795          FigureStartSyntaxStates(oldViewLine, false);
5796
5797       this.viewY = viewY;
5798
5799       SetScrollPosition(this.viewX, viewY * this.space.h);
5800       UpdateCaretPosition(true);
5801       UpdateDirty();
5802    }
5803
5804    void SetCursorToView()
5805    {
5806       SetCursorToViewX();
5807       SetCursorToViewY();
5808    }
5809
5810    void PageDown()
5811    {
5812       int c, numLines;
5813       EditLine line;
5814
5815       numLines = clientSize.h / this.space.h;
5816
5817       if(style.noCaret)
5818       {
5819          for(c=0, line = this.viewLine.next; line && c<numLines && (this.viewY < this.lineCount - numLines); line = line.next, c++);
5820          SetScrollPosition(this.viewX, (this.viewY + c) * this.space.h);
5821       }
5822       else
5823       {
5824          EditLine oldLine = this.line;
5825          bool lastOne = false;
5826          EditLine oldViewLine = this.viewLine;
5827          bool figureSyntax = false;
5828
5829          if(this.y >= this.lineCount-1) return;
5830
5831          for(c=0, line = this.line.next; line && c<numLines; line = line.next, c++)
5832          {
5833             if(this.viewY + numLines < this.lines.count)
5834             {
5835                this.viewLine = this.viewLine.next;
5836                this.viewY++;
5837                figureSyntax = true;
5838             }
5839             else if(c && !lastOne)
5840                break;
5841             else
5842                lastOne = true;
5843
5844             this.line = line;
5845             this.y++;
5846          }
5847          if(figureSyntax)
5848             FigureStartSyntaxStates(oldViewLine, false);
5849          this.x = AdjustXPosition(line, caretX, true, null, MAXINT, 0);
5850          ComputeColumn();
5851
5852          SetViewToCursor(false);
5853       }
5854    }
5855
5856    void PageUp()
5857    {
5858       int c, numLines;
5859       EditLine line;
5860
5861       if(this.y == 0) return;
5862
5863       numLines = clientSize.h / this.space.h;
5864
5865       if(style.noCaret)
5866       {
5867          for(c=0, line = this.viewLine.prev; line && c<numLines; line = line.prev, c++);
5868          SetScrollPosition(this.viewX, (this.viewY - c) * this.space.h);
5869       }
5870       else
5871       {
5872          EditLine oldLine = this.line;
5873
5874          for(c=0, line = this.line.prev; line && c<numLines; line = line.prev, c++)
5875          {
5876             this.line = line;
5877             this.y--;
5878
5879             if(this.viewLine.prev)
5880             {
5881                this.viewLine = this.viewLine.prev;
5882                this.viewY--;
5883                style.recomputeSyntax = true;
5884             }
5885          }
5886
5887          this.x = AdjustXPosition(line, caretX, true, null, MAXINT, 0);
5888          ComputeColumn();
5889
5890          SetViewToCursor(false);
5891       }
5892    }
5893
5894    void LineUp()
5895    {
5896
5897       if(this.viewLine.prev)
5898       {
5899          //DirtyAll();
5900          // this.viewLine = this.viewLine.prev;
5901          //this.viewY--;
5902
5903          SetScrollPosition(this.viewX, (this.viewY - 1) * this.space.h);
5904       }
5905    }
5906
5907
5908    void LineDown()
5909    {
5910       if(this.viewLine.next)
5911       {
5912          //DirtyAll();
5913          // this.viewLine = this.viewLine.next;
5914          // this.viewY++;
5915
5916          SetScrollPosition(this.viewX, (this.viewY + 1) * this.space.h);
5917       }
5918    }
5919
5920    // Selection
5921    uint SelSize()
5922    {
5923       EditLine l1, l2, line;
5924       int x1, x2, nx1, nx2;
5925       int count;
5926       int start, end;
5927       int size;
5928
5929       if(!this.selLine) return 0;
5930       if(this.selLine == this.line && this.selX == this.x) return 0;
5931       if(this.selY < this.y)
5932       {
5933          l1 = this.selLine;
5934          x1 = this.selX;
5935          l2 = this.line;
5936          x2 = this.x;
5937       }
5938       else if(this.selY > this.y)
5939       {
5940          l1 = this.line;
5941          x1 = this.x;
5942          l2 = this.selLine;
5943          x2 = this.selX;
5944       }
5945       else if(this.selX < this.x)
5946       {
5947          l1 = l2 = this.line;
5948          x1 = this.selX;
5949          x2 = this.x;
5950       }
5951       else
5952       {
5953          l1 = l2 = this.line;
5954          x1 = this.x;
5955          x2 = this.selX;
5956       }
5957       nx1 = Min(x1,l1.count);
5958       nx2 = Min(x2,l2.count);
5959
5960       // Find Number of Bytes Needed
5961       size = 0;
5962       for(line = l1; line; line = line.next)
5963       {
5964          if(line == l1) start = nx1; else start = 0;
5965          if(line == l2) end = nx2; else end = line.count;
5966          size += end-start;
5967          if(style.freeCaret && line == l2)
5968          {
5969             if(l1 == l2)
5970                count = Max(x2-Max(x1,l1.count),0);
5971             else
5972                count = Max(x2-l2.count,0);
5973             size+=count;
5974          }
5975
5976          if(line == l2) break;
5977          // Add Carriage Return / line Feed
5978          size++;
5979          size++;
5980       }
5981       return size;
5982    }
5983
5984    void GetSelPos(EditLine * l1, int *y1, int *x1, EditLine * l2, int * y2, int * x2, bool reorder)
5985    {
5986       if(this)
5987       {
5988          if(!reorder || this.selY < this.y)
5989          {
5990             if(l1) *l1 = this.selLine;
5991             if(y1) *y1 = this.selY;
5992             if(x1) *x1 = this.selX;
5993             if(l2) *l2 = this.line;
5994             if(y2) *y2 = this.y;
5995             if(x2) *x2 = this.x;
5996          }
5997          else if(this.selY > this.y)
5998          {
5999             if(l1) *l1 = this.line;
6000             if(y1) *y1 = this.y;
6001             if(x1) *x1 = this.x;
6002             if(l2) *l2 = this.selLine;
6003             if(y2) *y2 = this.selY;
6004             if(x2) *x2 = this.selX;
6005          }
6006          else if(this.selX < this.x)
6007          {
6008             if(l1) *l1 = this.line;
6009             if(y1) *y1 = this.selY;
6010             if(x1) *x1 = this.selX;
6011             if(l2) *l2 = this.line;
6012             if(y2) *y2 = this.y;
6013             if(x2) *x2 = this.x;
6014          }
6015          else
6016          {
6017             if(l1) *l1 = this.line;
6018             if(y1) *y1 = this.y;
6019             if(x1) *x1 = this.x;
6020             if(l2) *l2 = this.line;
6021             if(y2) *y2 = this.selY;
6022             if(x2) *x2 = this.selX;
6023          }
6024       }
6025    }
6026
6027    void SetSelPos(EditLine l1, int y1, int x1, EditLine l2, int y2, int x2)
6028    {
6029       if(this && (this.selY != y1 || this.selX != x1 || this.y != y2 || this.x != x2))
6030       {
6031          this.selLine = (EditLine)l1;
6032          this.selY = y1;
6033          this.selX = x1;
6034          this.line = (EditLine)l2;
6035          this.y = y2;
6036          this.x = x2;
6037          ComputeColumn();
6038          SetViewToCursor(true);
6039       }
6040    }
6041
6042    int GetText(char * text, EditLine _l1, int _y1, int _x1, EditLine _l2, int _y2, int _x2, bool addCr, bool addSpaces)
6043    {
6044       EditLine l1, l2, line;
6045       int x1, x2, nx1, nx2;
6046       int count;
6047       int start, end;
6048       int numChars = 0;
6049
6050       if(_y2 < _y1)
6051       {
6052          l1 = _l2;
6053          x1 = _x2;
6054          l2 = _l1;
6055          x2 = _x1;
6056       }
6057       else if(_y2 > _y1)
6058       {
6059          l1 = _l1;
6060          x1 = _x1;
6061          l2 = _l2;
6062          x2 = _x2;
6063       }
6064       else if(_x2 < _x1)
6065       {
6066          l1 = l2 = _l1;
6067          x1 = _x2;
6068          x2 = _x1;
6069       }
6070       else
6071       {
6072          l1 = l2 = _l1;
6073          x1 = _x1;
6074          x2 = _x2;
6075       }
6076       nx1 = Min(x1,l1.count);
6077       nx2 = Min(x2,l2.count);
6078
6079       // Copy text
6080       for(line = l1; line; line = line.next)
6081       {
6082          if(line == l1) start = nx1; else start = 0;
6083          if(line == l2) end = nx2; else end = line.count;
6084          if(text)
6085          {
6086             CopyBytes(text, line.buffer + start, end - start);
6087             text += end-start;
6088          }
6089          numChars += end-start;
6090
6091          if(style.freeCaret && line == l2 && addSpaces)
6092          {
6093             if(l1 == l2)
6094                count = Max(x2-Max(x1,l1.count),0);
6095             else
6096                count = Max(x2-l2.count,0);
6097             if(text)
6098             {
6099                FillBytes(text,' ',count);
6100                text += count;
6101             }
6102             else
6103                numChars += count;
6104          }
6105          if(line == l2) break;
6106          // Add line Feed
6107          if(addCr)
6108          {
6109             if(text)
6110                *(text++) = '\r';
6111             numChars++;
6112          }
6113          if(text)
6114             *(text++) = '\n';
6115          numChars++;
6116       }
6117       // '\0' terminate Terminate
6118       if(text)
6119          *(text) = '\0';
6120       numChars++;
6121       return numChars;
6122    }
6123
6124    void GetSel(char * text, bool addCr)
6125    {
6126       GetText(text, line, y, x, selLine, selY, selX, addCr, true);
6127    }
6128
6129    private void _Deselect()   // This assumes marking lines as dirty is handled by the caller
6130    {
6131       selLine = line;
6132       selX = x;
6133       selY = y;
6134    }
6135
6136    void Deselect()
6137    {
6138       SelDirty();
6139       _Deselect();
6140    }
6141
6142    // CLIPBOARD
6143    void Copy()
6144    {
6145       if(this)
6146       {
6147          int size = SelSize();
6148          if(size)
6149          {
6150             // Try to allocate memory
6151             ClipBoard clipBoard { };
6152             if(clipBoard.Allocate(size+1))
6153             {
6154                GetSel(clipBoard.memory, true);
6155                // Save clipboard
6156                clipBoard.Save();
6157             }
6158             delete clipBoard;
6159          }
6160       }
6161    }
6162
6163    void Paste()
6164    {
6165       if(this)
6166       {
6167          ClipBoard clipBoard { };
6168          if(clipBoard.Load())
6169             PutS(clipBoard.memory);
6170          delete clipBoard;
6171       }
6172    }
6173
6174    void Cut()
6175    {
6176       if(this)
6177       {
6178          Copy();
6179          if(DelSel(null))
6180          {
6181             SetViewToCursor(true);
6182             Modified();
6183          }
6184       }
6185    }
6186
6187    void DeleteSelection()
6188    {
6189       if(this)
6190       {
6191          DelSel(null);
6192          SetViewToCursor(true);
6193          Modified();
6194       }
6195    }
6196
6197    // FILE INTERFACE
6198    void Save(File f, bool cr)
6199    {
6200       EditLine line;
6201       savedAction = undoBuffer.curAction;
6202
6203       for(line = this.lines.first; line; line = line.next)
6204       {
6205          f.Write(line.buffer, line.count, 1);
6206          if(line.next)
6207          {
6208             if(cr) f.Putc('\r');
6209             f.Putc('\n');
6210          }
6211       }
6212    }
6213
6214    #define BUFFER_SIZE  16384
6215    void Load(File f)
6216    {
6217       undoBuffer.dontRecord++;
6218       if(f)
6219       {
6220          char buffer[BUFFER_SIZE];
6221
6222          for(;;)
6223          {
6224             int count = f.Read(buffer, 1, BUFFER_SIZE-1);
6225             buffer[count] = '\0';
6226             AddS(buffer);
6227             if(!count) break;
6228          }
6229          Home();
6230       }
6231       undoBuffer.dontRecord--;
6232       undoBuffer.count = 0;
6233       undoBuffer.curAction = 0;
6234       itemEditUndo.disabled = undoBuffer.curAction == 0;
6235       itemEditRedo.disabled = undoBuffer.curAction == undoBuffer.count;
6236    }
6237
6238    EditBoxFindResult Find(char * text, bool matchWord, bool matchCase, bool isSearchDown)
6239    {
6240       EditLine line;
6241       int num;
6242       bool firstPass = true;
6243       EditBoxFindResult result = found;
6244
6245       if(!this.line) return notFound;
6246       num = this.y;
6247
6248       for(line = this.line;;)
6249       {
6250          char * string;
6251
6252          if(!line)
6253          {
6254             if(isSearchDown)
6255             {
6256                line = this.lines.first;
6257                num = 0;
6258                result = wrapped;
6259             }
6260             else
6261             {
6262                line = this.lines.last;
6263                num = this.lineCount - 1;
6264                result = wrapped;
6265             }
6266          }
6267
6268          if(isSearchDown)
6269             string = SearchString(line.buffer, firstPass ? Min(this.x,line.count) : 0,text,matchCase,matchWord);
6270          else
6271             string = RSearchString(line.buffer,text,firstPass ? (Min(this.x,line.count)-1) : line.count,matchCase,matchWord);
6272
6273          if(string)
6274          {
6275             Select((void *)line,num,string - line.buffer,(void *)line,num,string - line.buffer + strlen(text));
6276             return result;
6277          }
6278          if(line == this.line && !firstPass) break;
6279
6280          if(isSearchDown)
6281          {
6282             line = line.next;
6283             num++;
6284          }
6285          else
6286          {
6287             line = line.prev;
6288             num--;
6289          }
6290
6291          firstPass = false;
6292       }
6293       return notFound;
6294    }
6295
6296    EditBoxFindResult FindInSelection(char * text, bool matchWord, bool matchCase, EditLine l2, int y2, int x2)
6297    {
6298       EditLine line;
6299       int y;
6300       int searchLen = strlen(text);
6301       for(line = (EditLine)this.line, y = this.y; y <= y2; line = line.next, y++)
6302       {
6303          char * string = SearchString(line.buffer, (y == this.y) ? Min(this.x,line.count) : 0,text,matchCase,matchWord);
6304          if(string && (y < y2 || (string - line.buffer) + searchLen <= x2))
6305          {
6306             Select((void *)line,y,string - line.buffer,(void *)line,y,string - line.buffer + strlen(text));
6307             return found;
6308          }
6309       }
6310       return notFound;
6311    }
6312
6313    bool OnKeyDown(Key key, unichar ch)
6314    {
6315 #ifdef _DEBUG
6316       //PrintLn("OnKeyDown: code = ", key.code, ", mods = ", key.modifiers, ", ch = ", (int)ch);
6317 #endif
6318       if(!NotifyKeyDown(master, this, key, ch))
6319          return false;
6320       else
6321       {
6322          switch(key)
6323          {
6324             case escape:
6325             {
6326                if(this.mouseMove)
6327                {
6328                   this.mouseMove = false;
6329                   OnLeftButtonUp(0,0,0);
6330                   SetViewToCursor(true);
6331                   return false;
6332                }
6333             }
6334          }
6335       }
6336       return true;
6337    }
6338 };
6339
6340 public class EditBoxStream : File
6341 {
6342    EditBox editBox;
6343    BufferLocation start, sel;
6344    uint pos;
6345    byte utf8Bytes[5];
6346    int numBytes;
6347
6348    ~EditBoxStream()
6349    {
6350       EditBox editBox = this.editBox;
6351
6352       editBox.x = start.x;
6353       editBox.y = start.y;
6354       editBox.line = start.line;
6355
6356       editBox.selX = sel.x;
6357       editBox.selY = sel.y;
6358       editBox.selLine = sel.line;
6359
6360       editBox.SetViewToCursor(true);
6361       //editBox.ComputeColumn();
6362    }
6363
6364 public:
6365    property EditBox editBox
6366    {
6367       set
6368       {
6369          editBox = value;
6370
6371          start.x = value.x;
6372          start.y = value.y;
6373          start.line = value.line;
6374
6375          sel.x = value.selX;
6376          sel.y = value.selY;
6377          sel.line = value.selLine;
6378          numBytes = 0;
6379
6380          value.GoToHome(true);
6381       }
6382
6383       get { return editBox; }
6384    }
6385
6386    uint Read(byte * buffer, uint size, uint count)
6387    {
6388       uint read = 0;
6389       EditBox editBox = this.editBox;
6390       EditLine line = editBox.line;
6391       int x = editBox.x;
6392       int y = editBox.y;
6393
6394       count *= size;
6395
6396       for(;read < count && line; line = (*&line.next))
6397       {
6398          int numBytes = Min(count - read, (*&line.count) - x);
6399          if(numBytes > 0)
6400          {
6401             memcpy(buffer + read, (*&line.buffer) + x, numBytes);
6402             read += numBytes;
6403             x += numBytes;
6404          }
6405          /*
6406          for(;read < count && x < (*&line.count);)
6407          {
6408             buffer[read++] = (*&line.buffer)[x++];
6409          }
6410          */
6411          if((*&line.next))
6412          {
6413             if(read < count)
6414             {
6415                buffer[read++] = '\n';
6416             }
6417             else
6418                break;
6419          }
6420          if(x == (*&line.count) && (*&line.next))
6421          {
6422             x = 0;
6423             y++;
6424          }
6425          else
6426             break;
6427       }
6428
6429       editBox.line = editBox.selLine = line;
6430       editBox.x = editBox.selX = x;
6431       editBox.y = editBox.selY = y;
6432       pos += read;
6433       return read / size;
6434    }
6435
6436    bool Seek(int pos, FileSeekMode mode)
6437    {
6438       bool result = true;
6439       EditBox editBox = this.editBox;
6440       EditLine line = editBox.line;
6441       numBytes = 0;
6442       if(mode == FileSeekMode::start)
6443       {
6444          pos = pos - this.pos;
6445          mode = current;
6446       }
6447       if(mode == current)
6448       {
6449          uint read = 0;
6450          int x = editBox.x;
6451          int y = editBox.y;
6452          if(pos > 0)
6453          {
6454             for(;read < pos && line; line = (*&line.next))
6455             {
6456                int numBytes = Min(pos - read, (*&line.count) - x);
6457                if(numBytes > 0)
6458                {
6459                   read += numBytes; x += numBytes;
6460                }
6461
6462                /*for(;read < pos && x < (*&line.count);)
6463                {
6464                   read++; x++;
6465                }
6466                */
6467                if((*&line.next))
6468                {
6469                   if(read < pos)
6470                   {
6471                      read++;
6472                      x = 0;
6473                   }
6474                   else
6475                      break;
6476                }
6477                else
6478                {
6479                   if(read<pos)
6480                      result = false;
6481                   break;
6482                }
6483                y++;
6484             }
6485             this.pos += read;
6486          }
6487          else if(pos < 0)
6488          {
6489             pos = -pos;
6490             for(;read < pos && line; line = (*&line.prev))
6491             {
6492                int numBytes = Min(pos - read, x);
6493                if(numBytes > 0)
6494                {
6495                   read += numBytes;
6496                   x -= numBytes;
6497                }
6498                /*for(;read < pos && x > 0;)
6499                {
6500                   read++; x--;
6501                }
6502                */
6503                if((*&line.prev))
6504                {
6505                   if(read < pos)
6506                   {
6507                      read++;
6508                      x = (*&(*&line.prev).count);
6509                   }
6510                   else
6511                      break;
6512                }
6513                else
6514                {
6515                   if(read<pos)
6516                      result = false;
6517                   break;
6518                }
6519                y--;
6520             }
6521             this.pos -= read;
6522          }
6523
6524          editBox.line = editBox.selLine = line;
6525          editBox.x = editBox.selX = x;
6526          editBox.y = editBox.selY = y;
6527       }
6528       return result;
6529    }
6530
6531    bool Puts(char * string)
6532    {
6533       EditBox editBox = this.editBox;
6534       BufferLocation start { editBox.line, editBox.y, editBox.x };
6535       BufferLocation pos;
6536
6537       numBytes = 0;
6538       editBox.AddS(string);
6539
6540       pos.line = editBox.line;
6541       pos.y = editBox.y;
6542       pos.x = editBox.x;
6543
6544       this.start.AdjustAdd(start, pos);
6545       sel.AdjustAdd(start, pos);
6546
6547       return true;
6548    }
6549
6550    // NOTE: BYTE, NOT UNICODE CHARACTER!
6551    bool Putc(char ch)
6552    {
6553       EditBox editBox = this.editBox;
6554       BufferLocation start = { editBox.line, editBox.y, editBox.x };
6555       BufferLocation pos;
6556
6557       if(numBytes < 4)
6558       {
6559          utf8Bytes[numBytes++] = ch;
6560          utf8Bytes[numBytes] = 0;
6561          if(UTF8Validate(utf8Bytes))
6562          {
6563             editBox.AddCh(UTF8_GET_CHAR(utf8Bytes, numBytes));
6564             numBytes = 0;
6565             pos.line = editBox.line;
6566             pos.y = editBox.y;
6567             pos.x = editBox.x;
6568             this.start.AdjustAdd(start, pos);
6569             sel.AdjustAdd(start, pos);
6570          }
6571          return true;
6572       }
6573       return false;
6574    }
6575
6576    bool Getc(char * ch)
6577    {
6578       return Read(ch, 1, 1) ? true : false;
6579    }
6580
6581    void DeleteBytes(uint count)
6582    {
6583       EditBox editBox = this.editBox;
6584       if(count)
6585       {
6586          BufferLocation pos { editBox.line, editBox.y, editBox.x };
6587          BufferLocation end = pos;
6588
6589          uint c = 0;
6590          for(;;)
6591          {
6592             for(;c < count && end.line && end.x < end.line.count;)
6593             {
6594                end.x++;
6595                c++;
6596             }
6597             if(c < count && end.line && end.line.next)
6598             {
6599                end.line = end.line.next;
6600                end.y++;
6601                c++;
6602                end.x = 0;
6603             }
6604             else
6605                break;
6606          }
6607
6608          start.AdjustDelete(pos, end);
6609          sel.AdjustDelete(pos, end);
6610
6611          editBox._DelCh(pos.line, pos.y, pos.x, end.line, end.y, end.x, true, false, null);
6612       }
6613    }
6614 };