ecere/gui/EditBox: Prevent toggling overwrite caret when read-only
[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(dot && dot > word + wordLen) dot = null;
1796                            isReal = dot || exponent;
1797                            if(isReal)
1798                               strtod(word, &s);      // strtod() seems to break on hex floats (e.g. 0x23e3p12, 0x1.fp3)
1799                            else
1800                               strtol(word, &s, 0);
1801                            if(s && s != word)
1802                            {
1803                               // Check suffixes
1804                               char ch;
1805                               int i;
1806                               int gotF = 0, gotL = 0, gotU = 0, gotI = 0;
1807                               bool valid = true;
1808
1809                               for(i = 0; valid && i < 5 && (ch = s[i]) && (isalnum(ch) || ch == '_'); i++)
1810                               {
1811                                  switch(ch)
1812                                  {
1813                                     case 'f': case 'F': gotF++; if(gotF > 1 || !isReal) valid = false; break;
1814                                     case 'l': case 'L':
1815                                        gotL++;
1816                                        if(gotL > 2 || isReal || (gotL == 2 && (s[i-1] != ch)))
1817                                        valid = false;
1818                                        break;
1819                                     case 'u': case 'U': gotU++; if(gotU > 1 || isReal) valid = false; break;
1820                                     case 'i': case 'I': case 'j': case 'J': gotI++; if(gotI > 1) valid = false; break;
1821                                     default: valid = false;
1822                                  }
1823                               }
1824
1825                               // Don't highlight numbers with too many decimal points
1826                               if(s[0] == '.' && isdigit(s[1]))
1827                               {
1828                                  int newWordLen;
1829                                  while(s[0] == '.' && isdigit(s[1]))
1830                                  {
1831                                     int newWordLen = s - word;
1832                                     c += newWordLen - wordLen;
1833                                     wordLen = newWordLen;
1834                                     strtod(s, &s);
1835                                  }
1836                                  newWordLen = s - word;
1837                                  c += newWordLen - wordLen;
1838                                  wordLen = newWordLen;
1839                               }
1840                               else if(valid)
1841                               {
1842                                  int newWordLen = s + i - word;
1843                                  newTextColor = colorScheme.numberColor;
1844                                  c += newWordLen - wordLen;
1845                                  wordLen = newWordLen;
1846                               }
1847                               else if(dot && dot > word && dot < s)
1848                                  newTextColor = colorScheme.numberColor;
1849                            }
1850                         }
1851                         else
1852                         {
1853                            if(!inQuotes && !inString && !inMultiLineComment && !inSingleLineComment && word[0] == '#')
1854                            {
1855                               if(firstWord)
1856                               {
1857                                  inPrep = true;
1858                                  newTextColor = colorScheme.preprocessorColor;
1859                               }
1860                            }
1861                            if(!inQuotes && !inString && !inMultiLineComment && !inSingleLineComment)
1862                            {
1863                               for(g = 0; g < ((inPrep && word[0] != '#') ? 2 : 1); g++)
1864                               {
1865                                  char ** keys = keyWords[g];
1866                                  int * len = keyLen[g];
1867                                  for(ccc = 0; keys[ccc]; ccc++)
1868                                  {
1869                                     if(len[ccc] == wordLen && !strncmp(keys[ccc], word, wordLen))
1870                                     {
1871                                        newTextColor = colorScheme.keywordColors[g];
1872                                        break;
1873                                     }
1874                                  }
1875                               }
1876                            }
1877                         }
1878                         firstWord = false;
1879
1880                         // If highlighting for this word is different, break
1881                         if(newTextColor != textColor)
1882                         {
1883                            if(bufferLen)
1884                            {
1885                               // Better solution than going back?
1886                               c -= wordLen;
1887
1888                               // Reset syntax flags
1889                               escaped = backEscaped;
1890                               lastWasStar = backLastWasStar;
1891                               inMultiLineComment = backInMultiLineComment;
1892                               inString = backInString;
1893                               inQuotes = backInQuotes;
1894                               inPrep = backInPrep;
1895                               inSingleLineComment = backInSingleLineComment;
1896                               wasInMultiLine = backWasInMultiLine;
1897                               break;
1898                            }
1899                            else
1900                            {
1901                               textColor = newTextColor;
1902                               if(!selected)
1903                               {
1904                                  foreground = textColor;
1905                                  surface.SetForeground(textColor);
1906                               }
1907                            }
1908                         }
1909                      }
1910                   }
1911
1912                   // If we're not breaking, this can't be rendered as spacing anymore
1913                   spacing = false;
1914
1915                   // Do word wrapping here
1916                   if(style.wrap)
1917                   {
1918                      //if(!numSpaces)
1919                      {
1920                         int tw;
1921                         FontExtent(display, font, line.buffer + start, bufferLen + wordLen, &tw, null);
1922                         w = tw;
1923                      }
1924                      /*else
1925                      {
1926                         w += numSpaces * space.w;
1927                      }*/
1928                      if(x + viewX + w > maxW)
1929                      {
1930                         // Avoid an endless loop until we fix wrapping
1931                         if(c - wordLen > start)
1932                         {
1933                            c -= wordLen;
1934                            lineComplete = true;
1935                            break;
1936                         }
1937                      }
1938                   }
1939                }
1940                bufferLen += wordLen;
1941                wordLen = 0;
1942             }
1943
1944             {
1945                int renderStart = start;
1946                bool flush = false;
1947                bool flagTrailingSpace = false;
1948                int numSpaces = 0;
1949                int wc;
1950
1951                // Render checking if we need to split because of selection or to find where to draw insert caret
1952                for(wc = start; wc < start + bufferLen; wc++)
1953                {
1954                   flush = CheckColors(line, wc, selection, selX, editX, &selected, selectionForeground,
1955                      selectionBackground, textColor, &foreground, &background, &opacity, &overWrite);
1956                   if(overWrite == true)
1957                   {
1958                      overWriteCh = (wc < line.count) ? line.buffer[wc] : ' ';
1959                      if(overWriteCh == '\t') overWriteCh = ' ';
1960                   }
1961
1962                   if(flush)
1963                   {
1964                      flagTrailingSpace = numSpaces && trailingSpace && style.syntax && start + bufferLen == line.count && line != this.line;
1965                      if(flagTrailingSpace) surface.SetBackground(red);
1966                      FlushBuffer(surface, line, wc, &renderStart, &x, y, numSpaces, flagTrailingSpace, box);
1967                      if(overWrite == true)
1968                      {
1969                         overWriteX = x;
1970                         overWriteY = y;
1971                         overWrite = 2;
1972                      }
1973                      numSpaces = 0;
1974
1975                      surface.TextOpacity(opacity);
1976                      surface.SetBackground(background);
1977                      surface.SetForeground(foreground);
1978
1979                      flush = false;
1980                   }
1981
1982                   if(spacing)
1983                   {
1984                      if(wc < line.count && line.buffer[wc] == '\t')
1985                      {
1986                         numSpaces += (tabSize * space.w) - ((x + numSpaces + viewX) % (tabSize * space.w));
1987                      }
1988                      else
1989                      {
1990                         numSpaces += space.w;
1991                      }
1992                   }
1993                }
1994                flagTrailingSpace = numSpaces && trailingSpace && style.syntax && start + bufferLen == line.count && line != this.line;
1995                if(flagTrailingSpace) surface.SetBackground(red);
1996                FlushBuffer(surface, line, wc, &renderStart, &x, y, numSpaces, flagTrailingSpace, box);
1997                start += bufferLen;
1998             }
1999          }
2000
2001          if(CheckColors(line, c, selection, selX, editX, &selected, selectionForeground,
2002                         selectionBackground, textColor, &foreground, &background, &opacity, &overWrite))
2003          {
2004             if(overWrite == true)
2005             {
2006                overWriteX = x;
2007                overWriteY = y;
2008                overWriteCh = ' ';
2009                overWrite = 2;
2010             }
2011             surface.TextOpacity(opacity);
2012             surface.SetBackground(background);
2013             surface.SetForeground(foreground);
2014          }
2015
2016          if(style.freeCaret && selected)
2017          {
2018             surface.SetBackground(selectionBackground);
2019             surface.Area(x + XOFFSET - 1,y,clientSize.w-1,y+this.space.h-1);
2020             // TEST: surface.Area(x + XOFFSET,y,clientSize.w-1,y+this.space.h-1);
2021          }
2022
2023
2024          /*
2025          if(style.freeCaret && selected)
2026          {
2027             surface.SetBackground(selectionBackground);
2028             surface.Area(x + XOFFSET - 1,y,clientSize.w-1,y+this.space.h-1);
2029          }
2030          */
2031          if(line.count && line.text[line.count - 1] == '\\')
2032          {
2033             continuedSingleLineComment = inSingleLineComment;
2034             continuedString = inString;
2035             continuedQuotes = inQuotes;
2036          }
2037          else
2038          {
2039             continuedSingleLineComment = false;
2040             continuedString = false;
2041             continuedQuotes = false;
2042          }
2043
2044          y+=this.space.h;
2045          if(y > box.bottom) // >=clientSize.h)
2046             break;
2047       }
2048
2049       if(overWrite)
2050       {
2051          surface.TextOpacity(true);
2052          surface.SetForeground(black);
2053          surface.SetBackground(Color {255,255,85});
2054          surface.WriteText(XOFFSET + overWriteX,overWriteY, &overWriteCh, 1);
2055       }
2056    }
2057
2058    void FixScrollArea()
2059    {
2060       if(style.hScroll || style.vScroll)
2061       {
2062          int width = maxLength + XOFFSET;
2063          int height = lineCount * space.h;
2064          if(style.freeCaret && line)
2065          {
2066             if(x > selX)
2067             {
2068                if(x > line.count)
2069                   width = Max(line.length + (x - line.count) * space.w, maxLength + XOFFSET);
2070             }
2071             else
2072             {
2073                if(selX > selLine.count)
2074                   width = Max(selLine.length + (selX - selLine.count) * space.w, maxLength + XOFFSET);
2075             }
2076          }
2077
2078          width += space.w;
2079          SetScrollLineStep(8, space.h);
2080          SetScrollArea(width, height, true);
2081       }
2082    }
2083
2084    void ComputeLength(EditLine line)
2085    {
2086       int c;
2087       int tabOccur = 0;
2088       int tabWidth;
2089       int x = 0;
2090
2091       for(c = 0; c < line.count; )
2092       {
2093          int len = 1;
2094          int start = c;
2095          int w;
2096          if(c < line.count)
2097          {
2098             byte ch = 0;
2099             int numBytes;
2100             for(len = 0; c < line.count; c += numBytes)
2101             {
2102                ch = line.buffer[c];
2103                numBytes = UTF8_NUM_BYTES(ch);
2104
2105                if(ch == ' ' || ch == '\t')
2106                {
2107                   if(!len) c++;
2108                   break;
2109                }
2110                len += numBytes;
2111             }
2112             if(!len && ch == ' ')
2113             {
2114                len = 1;
2115                w = space.w;
2116             }
2117             else if(!len && ch == '\t')
2118             {
2119                w = (tabSize * space.w) - (x % (tabSize * space.w));
2120                len = 1;
2121             }
2122             else
2123                FontExtent(display, font, line.buffer + start, len, &w, null);
2124          }
2125          else
2126          {
2127             w = space.w;
2128             c++;
2129          }
2130          x += w;
2131       }
2132       line.length = x;
2133
2134       if(line.length > this.maxLength)
2135       {
2136          this.maxLine = line;
2137          this.maxLength = line.length;
2138
2139          if(style.autoSize) AutoSize();
2140       }
2141    }
2142
2143    void FindMaxLine()
2144    {
2145       EditLine line;
2146
2147       this.maxLength = 0;
2148       this.maxLine = null;
2149
2150       for(line = lines.first; line; line = line.next)
2151       {
2152          if(line.length > this.maxLength)
2153          {
2154             this.maxLength = line.length;
2155             this.maxLine = line;
2156          }
2157       }
2158
2159       if(style.autoSize) AutoSize();
2160    }
2161
2162    void SelDirty()
2163    {
2164       if(this.selY != this.y)
2165          DirtyAll();
2166       else if(this.selX != this.x)  // commented out to erase caret: if(this.selX != this.x)
2167          DirtyLine(this.y);
2168    }
2169
2170    void ComputeColumn()
2171    {
2172       // Adjust new edit X position according to tabs
2173       int c, position = 0;
2174       unichar ch;
2175       int nb;
2176       for(c = 0; c<this.line.count && c<this.x && (ch = UTF8_GET_CHAR(this.line.buffer + c, nb)); c+= nb)
2177       {
2178          // TODO: MIGHT WANT TO RETHINK WHAT COLUMN SHOULD BE REGARDING TABS
2179          if(ch == '\t')
2180             position += this.tabSize - (position % this.tabSize);
2181          else
2182             position ++;
2183       }
2184       position += this.x - c;
2185       this.col = position;
2186    }
2187
2188    void DelCh(EditLine l1, int y1, int c1, EditLine l2, int y2, int c2, bool placeAfter)
2189    {
2190       _DelCh(l1, y1, c1, l2, y2, c2, placeAfter, true, null);
2191    }
2192
2193    bool HasCommentOrEscape(EditLine line)
2194    {
2195       bool hadComment = strstr(line.buffer, "/*") || strstr(line.buffer, "*/");
2196       int c;
2197
2198       if(!hadComment)
2199       {
2200          for(c = line.count-1; c >= 0; c--)
2201          {
2202             char ch = line.buffer[c];
2203             if(ch == '\\')
2204             {
2205                hadComment = true;
2206                break;
2207             }
2208             else //if(ch != ' ' && ch != '\t')
2209                break;
2210          }
2211       }
2212       return hadComment;
2213    }
2214
2215    int _DelCh(EditLine l1, int y1, int c1, EditLine l2, int y2, int c2, bool placeAfter, bool highlight, int * addedSpacesPtr)
2216    {
2217       EditLine line = l1, next;
2218       char * buffer;
2219       int oldCount1, oldCount2;
2220       int y, firstViewY, firstY, firstDropY, firstSelY;
2221       int newLineCount;
2222       int extras = 0;
2223       DelTextAction action = null;
2224       bool hadComment = false;
2225       int start = c1;
2226
2227       if(style.syntax)
2228          hadComment = HasCommentOrEscape(line);
2229
2230       if(y2 > y1 || c2 > c1)
2231       {
2232          if(start < l1.count)
2233          {
2234             while(!UTF8_IS_FIRST(l1.buffer[start]) && start)
2235                start--;
2236          }
2237       }
2238       oldCount1 = l1.count;
2239       buffer = l1.buffer;
2240       while(c1 < oldCount1)
2241       {
2242          byte ch = buffer[c1];
2243          if(UTF8_IS_FIRST(ch)) break;
2244          c1--;
2245          extras++;
2246       }
2247       oldCount2 = l2.count;
2248       buffer = l2.buffer;
2249       while(c2 < oldCount2)
2250       {
2251          byte ch = buffer[c2];
2252          if(UTF8_IS_FIRST(ch)) break;
2253          c2++;
2254          extras++;
2255       }
2256
2257       if(!undoBuffer.dontRecord && (y2 > y1 || c2 > c1))
2258       {
2259          int len;
2260          char * string;
2261
2262          len = GetText(null, l1, y1, start, l2, y2, c2, false, false);
2263          string = new char[len];
2264          action = DelTextAction { y1 = y1, x1 = start, y2 = y2, x2 = c2, string = string, placeAfter = placeAfter, noHighlight = !highlight };
2265          GetText(string, l1, y1, start, l2, y2, c2, false, false);
2266          Record(action);
2267       }
2268
2269       //oldCount1 = l1.count;
2270       //oldCount2 = l2.count;
2271
2272       {
2273          BufferLocation before = { l1,y1,c1}, after = { l2,y2,c2 };
2274          NotifyCharsDeleted(master, this, &before, &after, this.pasteOperation);
2275       }
2276
2277       if(c2 > oldCount2) c2 = oldCount2;
2278       if(!(style.freeCaret))
2279          if(c1 > oldCount1) c1 = oldCount1;
2280       newLineCount = c1 + l2.count-c2;
2281       if(l1 == l2)
2282       {
2283          /*
2284          if(!line.size)
2285             printf("");
2286          buffer = new char[line.size ? line.size : 1];
2287          */
2288          buffer = new char[line.size];
2289          // TODO: Better handling of these allocation failures
2290          if(!buffer) return extras;
2291          CopyBytes(buffer,l2.buffer,oldCount1 + 1/*line.count + 1*//*line.size*/);
2292       }
2293       else
2294          buffer = l2.buffer;
2295
2296       // TODO: Better handling of these allocation failures
2297       if(!line.AdjustBuffer(newLineCount))
2298          return extras;
2299
2300 #ifdef _DEBUG
2301       /*if(newLineCount > 4000 || newLineCount < 0)
2302          printf("Warning");*/
2303 #endif
2304       line.count = newLineCount;
2305
2306       memmove(l1.buffer+c1, buffer+c2,line.count-c1);
2307       if(c1>oldCount1)
2308       {
2309          if(action)
2310          {
2311             action.addedSpaces = c1-oldCount1;
2312 #ifdef _DEBUG
2313             if(action.addedSpaces > action.x1)
2314             {
2315                printf("bug!");
2316             }
2317 #endif
2318          }
2319          if(addedSpacesPtr) *addedSpacesPtr = c1-oldCount1;
2320          FillBytes(l1.buffer+oldCount1,' ',c1-oldCount1);
2321       }
2322       line.buffer[line.count] = '\0';
2323
2324       if(l1 == l2)
2325       {
2326          delete buffer;
2327          DirtyLine(y1);
2328          /*
2329          this.dropX -= c2-c1;
2330          this.dropX = Max(this.dropX,0);
2331          this.x -= c2-c1-1;
2332          this.x = Max(this.x,0);
2333          */
2334       }
2335       else
2336          DirtyEnd(y1);
2337
2338       y = y1;
2339       firstViewY = this.viewY;
2340       firstY = this.y;
2341       firstDropY = this.dropY;
2342       firstSelY = this.selY;
2343       for(line = l1;line;line = next, y++)
2344       {
2345          next = line.next;
2346          if(line!=l1)
2347          {
2348             this.lineCount--;
2349             delete line.buffer;
2350
2351             if(line == this.viewLine)
2352             {
2353                if(this.viewLine.next)
2354                {
2355                   this.viewLine = this.viewLine.next;
2356                   //this.viewY++;
2357                   style.recomputeSyntax = true;
2358                }
2359                else
2360                {
2361                   this.viewLine = this.viewLine.prev;
2362                   this.viewY--;
2363                   style.recomputeSyntax = true;
2364                }
2365             }
2366             else if(y < firstViewY)
2367                this.viewY--;
2368             if(line == this.line)
2369             {
2370                if(this.line.next)
2371                {
2372                   this.line = this.line.next;
2373                   this.x = this.line.count;
2374                   //this.y++;
2375                }
2376                else
2377                {
2378                   this.line = this.line.prev;
2379                   this.x = this.line.count;
2380                   this.y--;
2381                }
2382                ComputeColumn();
2383             }
2384             else if(y < firstY)
2385                this.y--;
2386             if(line == this.dropLine)
2387             {
2388                if(this.dropLine.next)
2389                {
2390                   this.dropLine = this.dropLine.next;
2391                   this.dropX = this.dropLine.count;
2392                }
2393                else
2394                {
2395                   this.dropLine = this.dropLine.prev;
2396                   this.dropX = this.dropLine.count;
2397                   this.dropY--;
2398                }
2399             }
2400             else if(y < firstDropY)
2401                this.dropY--;
2402             if(line == this.selLine)
2403             {
2404                if(this.selLine.next)
2405                {
2406                   this.selLine = this.selLine.next;
2407                   this.selX = this.selLine.count;
2408                }
2409                else
2410                {
2411                   this.selLine = this.selLine.prev;
2412                   this.selX = this.selLine.count;
2413                   this.selY--;
2414                }
2415             }
2416             else if(y < firstSelY)
2417                this.selY--;
2418             lines.Delete(line);
2419          }
2420          if(line == l2) break;
2421       }
2422       ComputeLength(l1);
2423       FindMaxLine();
2424       if(style.syntax && (hadComment || HasCommentOrEscape(this.line)))
2425       {
2426          DirtyAll();
2427          style.recomputeSyntax = true;
2428       }
2429       return extras;
2430    }
2431
2432    bool DelSel(int * addedSpacesPtr)
2433    {
2434       if(this.line != this.selLine || this.x != this.selX)
2435       {
2436          if(this.selY < this.y)
2437          {
2438             _DelCh(this.selLine, this.selY, this.selX, this.line, this.y, this.x, true, true, addedSpacesPtr);
2439             this.x = this.selX;
2440             this.y = this.selY;
2441             this.line = this.selLine;
2442          }
2443          else if(this.selY > this.y)
2444          {
2445             _DelCh(this.line, this.y, this.x, this.selLine, this.selY, this.selX, false, true, addedSpacesPtr);
2446             this.selX = this.x;
2447             this.selY = this.y;
2448             this.selLine = this.line;
2449          }
2450          else if(this.selX < this.x)
2451          {
2452             _DelCh(this.selLine, this.selY, this.selX, this.line, this.y, this.x, true, true, addedSpacesPtr);
2453             this.x = this.selX;
2454             this.y = this.selY;
2455             this.line = this.selLine;
2456          }
2457          else
2458          {
2459             _DelCh(this.line, this.y, this.x, this.selLine, this.selY, this.selX, false, true, addedSpacesPtr);
2460             this.selX = this.x;
2461             this.selY = this.y;
2462             this.selLine = this.line;
2463          }
2464          ComputeColumn();
2465          return true;
2466       }
2467       return false;
2468    }
2469
2470    bool AddToLine(char * stringLine, int count, bool LFComing, int * addedSpacesPtr, int * addedTabsPtr)
2471    {
2472       bool hadComment = false;
2473       // Add the line here
2474       EditLine line = this.line;
2475
2476       // The purpose of this is solely to lock a max number of characters if no HSCROLLING is present
2477       if(!style.hScroll && created)
2478       {
2479          int endX = (style.freeCaret) ? this.x : Min(this.x, line.count);
2480          int max;
2481          int x;
2482          int c;
2483
2484          // Lock if no place to display.
2485          if(LFComing)
2486             max = endX;
2487          else if(endX > this.x)
2488             max = Max(this.x, line.count);
2489          else
2490             max = line.count;
2491
2492          for(x = 0, c = 0; c < max+count; )
2493          {
2494             int w;
2495             int numBytes = 1;
2496             char * string;
2497             if(c < Min(this.x, line.count))
2498                string = line.buffer + c;
2499             else if(c < endX)
2500                string = " ";
2501             else if(c < endX + count)
2502                string = stringLine + c - endX;
2503             else
2504                string = line.buffer + c - endX - count;
2505
2506             if(*string == '\t')
2507             {
2508                w = (tabSize * space.w) - (x % (tabSize * space.w));
2509             }
2510             else
2511             {
2512                numBytes = UTF8_NUM_BYTES(*string);
2513                FontExtent(display, this.font, string, numBytes, &w, null);
2514             }
2515             x += w;
2516
2517             if(x >= clientSize.w)
2518             {
2519                count = c - max;
2520                break;
2521             }
2522             c += numBytes; // - 1;
2523          }
2524       }
2525
2526       if(count > 0)
2527       {
2528          int addedSpaces = 0;
2529          int addedTabs = 0;
2530
2531          // Add blank spaces if EES_FREECARET
2532          if(this.x > line.count)
2533          {
2534             if(style.freeCaret)
2535             {
2536                if(style.useTab)
2537                {
2538                   int wantedPosition;
2539                   int position = 0;
2540                   int c;
2541
2542                   for(c = 0; c<line.count; c++)
2543                   {
2544                      if(this.line.buffer[c] == '\t')
2545                         position += this.tabSize - (position % this.tabSize);
2546                      else
2547                         position ++;
2548                   }
2549                   wantedPosition = position + (this.x - line.count);
2550
2551                   // A tab is too much...
2552                   if(position + (this.tabSize - (position % this.tabSize)) > wantedPosition)
2553                      addedSpaces = wantedPosition - position;
2554                   else
2555                   {
2556                      // Put a first tab
2557                      addedTabs = 1;
2558                      position += this.tabSize - (position % this.tabSize);
2559                      // Add as many tabs as needed
2560                      addedTabs += (wantedPosition - position) / this.tabSize;
2561                      position += (addedTabs-1) * this.tabSize;
2562                      // Finish off with spaces
2563                      addedSpaces = wantedPosition - position;
2564                   }
2565                }
2566                else
2567                   addedSpaces = this.x - line.count;
2568             }
2569          }
2570          this.x = Min(this.x, line.count);
2571
2572          if(line.count + count + addedSpaces + addedTabs > this.maxLineSize)
2573             return false;
2574
2575          DirtyLine(this.y);
2576
2577          if(style.syntax)
2578             hadComment = HasCommentOrEscape(line);
2579
2580          // Adjust the size of the line
2581          if(!line.AdjustBuffer(line.count+count+addedTabs+addedSpaces))
2582             return false;
2583
2584          {
2585             BufferLocation before = { this.line, this.y, this.x }, after = { this.line, this.y, this.x };
2586             bool hasComment;
2587
2588             memmove(line.buffer+this.x+count, line.buffer+this.x,line.count-this.x);
2589             CopyBytes(line.buffer + this.x + addedTabs + addedSpaces, stringLine, count);
2590             if(addedTabs)
2591             {
2592                *addedTabsPtr = addedTabs;
2593                FillBytes(line.buffer+line.count,'\t',addedTabs);
2594 #ifdef _DEBUG
2595       if(addedTabs > 4000 || addedTabs < 0)
2596          printf("Warning");
2597 #endif
2598                line.count += addedTabs;
2599             }
2600             else if(addedTabs)
2601                *addedTabsPtr = 0;
2602             if(addedSpaces)
2603             {
2604                FillBytes(line.buffer+line.count,' ',addedSpaces);
2605 #ifdef _DEBUG
2606       if(addedSpaces > 4000 || addedSpaces < 0)
2607          printf("Warning");
2608 #endif
2609                line.count += addedSpaces;
2610                if(addedSpacesPtr) *addedSpacesPtr = addedSpaces;
2611             }
2612             else if(addedSpacesPtr)
2613                *addedSpacesPtr = 0;
2614 #ifdef _DEBUG
2615       if(count > 4000 || count < 0)
2616          printf("Warning");
2617 #endif
2618             line.count += count;
2619             this.x += count + addedTabs + addedSpaces;
2620             this.selX = this.x;
2621             this.selY = this.y;
2622             this.selLine = this.line;
2623
2624             line.buffer[line.count] = '\0';
2625
2626             ComputeLength(line);
2627             ComputeColumn();
2628
2629             after.x = this.x;
2630
2631             hasComment = HasCommentOrEscape(line);
2632             if(!undoBuffer.insideRedo)
2633             {
2634                int backDontRecord = undoBuffer.dontRecord;
2635                undoBuffer.dontRecord = 0;
2636                NotifyCharsAdded(master, this, &before, &after, this.pasteOperation);
2637                undoBuffer.dontRecord = backDontRecord;
2638             }
2639             if(style.syntax && (hadComment || hasComment || line != this.line))
2640             {
2641                style.recomputeSyntax = true;
2642                DirtyAll();
2643             }
2644          }
2645       }
2646       return true;
2647    }
2648
2649    void Emptyline(EditLine line, int y)
2650    {
2651       if(line.next)
2652          DelCh(line, y, 0, line.next, y+1, 0, true);
2653       else
2654          DelCh(line, y, 0, line, y, line.count, true);
2655    }
2656
2657    void GoToEnd(bool deselect)
2658    {
2659       if(this.line)
2660       {
2661          this.line = this.lines.last;
2662          if(this.y != this.lineCount-1)
2663             DirtyAll();
2664          else if (this.x != this.line.count)
2665             DirtyLine(this.lineCount-1);
2666          this.y = this.lineCount-1;
2667          this.x = this.line.count;
2668          ComputeColumn();
2669          if(deselect)
2670             _Deselect();
2671       }
2672    }
2673
2674    void GoToHome(bool deselect)
2675    {
2676       if(this.line)
2677       {
2678          this.line = this.lines.first;
2679          if(this.y != 0)
2680             DirtyAll();
2681          else if (this.x !=0)
2682             DirtyLine(this.lineCount-1);
2683          this.y = 0;
2684          this.x = 0;
2685          this.col = 0;
2686          if(deselect)
2687             _Deselect();
2688       }
2689    }
2690
2691    // Returns true if it needs scrolling
2692    bool FindMouse(int px, int py, int * tx, int * ty, EditLine * tline, bool half)
2693    {
2694       int w;
2695       int c;
2696       int x, y;
2697       EditLine line;
2698       bool needHScroll = false;
2699
2700       if(py < 0)
2701       {
2702          if(this.viewY > 0)
2703          {
2704             y = this.viewY-1;
2705             line = this.viewLine ? (void *)this.viewLine.prev : null;
2706          }
2707          else
2708          {
2709             y = 0;
2710             line = (void *)this.lines.first;
2711          }
2712       }
2713       else
2714       {
2715          py = Min(py, clientSize.h);
2716          py /= this.space.h;
2717          py = Min(py, this.lineCount);
2718          y = this.viewY;
2719          for(c = 0, line = this.viewLine; (line != (void *)this.lines.last && c<py); line = line.next, c++)
2720          {
2721             y++;
2722          }
2723       }
2724
2725       if( (px >= clientSize.w || px < clientSize.w/2) && this.viewX)
2726          needHScroll = true;
2727       px = Max(px,0);
2728       px = Min(px,clientSize.w+this.space.w);
2729
2730       if(tx && line)
2731       {
2732          *tx = AdjustXPosition(line, px + viewX, half, null, MAXINT, 0);
2733       }
2734
2735       if(tline) *tline = line;
2736       if(ty) *ty = y;
2737
2738       // Prevent divide by 0 from non valid this.font
2739       if(!this.space.h)
2740          return (y < this.viewY) || needHScroll;
2741       else
2742          return (y < this.viewY || y >= this.viewY + clientSize.h / this.space.h) || needHScroll;
2743       return false;
2744    }
2745
2746    // Minimal Update Management Functions
2747    void DirtyAll()
2748    {
2749       this.startY = 0;
2750       this.endY = clientSize.h-1;
2751    //   ErrorLog("DirtyAll\n");
2752    }
2753
2754    void DirtyEnd(int y)
2755    {
2756       if((y - this.viewY)*this.space.h < this.startY)
2757          this.startY = (y - this.viewY)*this.space.h+ YOFFSET;
2758       this.endY = clientSize.h-1;
2759       //ErrorLog("DirtyEnd %d\n", y);
2760    }
2761
2762    void DirtyLine(int y)
2763    {
2764       if(y >= this.viewY)
2765       {
2766          if((y - this.viewY)*this.space.h < this.startY)
2767             this.startY = (y - this.viewY)*this.space.h + YOFFSET;
2768          if((y - this.viewY+1)*this.space.h > this.endY)
2769             this.endY = (y - this.viewY+1)*this.space.h-1 + YOFFSET;
2770       }
2771       //ErrorLog("DirtyLine %d\n", y);
2772    }
2773
2774    void UpdateDirty()
2775    {
2776       Box box;
2777
2778       if(style.recomputeSyntax)
2779       {
2780          FigureStartSyntaxStates(lines.first, true);
2781          style.recomputeSyntax = false;
2782       }
2783       box.left  = 0;
2784       if(this.startY > this.endY) return;
2785       if(this.startY <= 0 && this.endY >= clientSize.h-1)
2786       {
2787          Update(null);
2788       }
2789       else
2790       {
2791          box.right = clientSize.w-1;
2792          box.top   = this.startY;
2793          box.bottom  = this.endY;
2794          Update(box);
2795       }
2796       this.startY = clientSize.h;
2797       this.endY = 0;
2798    }
2799
2800    bool IsMouseOnSelection()
2801    {
2802       bool mouseOnSelection = false;
2803
2804       int x, y;
2805       int minY = Min(this.selY, this.y);
2806       int maxY = Max(this.selY, this.y);
2807       int minX = Min(this.selX, this.x);
2808       int maxX = Max(this.selX, this.x);
2809
2810       FindMouse(this.mouseX - this.space.w / 2, this.mouseY, &x, &y, null, false);
2811
2812       if(maxX != minX || maxY != minY)
2813       {
2814          if(y > minY && y < maxY)
2815             mouseOnSelection = true;
2816          else if(y == minY && y == maxY)
2817             mouseOnSelection = (x < maxX && x >= minX);
2818          else if(y == minY)
2819          {
2820             if(y == this.selY)
2821                mouseOnSelection = (x >= this.selX);
2822             else if(y == this.y)
2823                mouseOnSelection = (x >= this.x);
2824          }
2825          else if(y == maxY)
2826          {
2827             if(y == this.selY)
2828                mouseOnSelection = (x < this.selX);
2829             else if(y == this.y)
2830                mouseOnSelection = (x < this.x);
2831          }
2832       }
2833       return mouseOnSelection;
2834    }
2835
2836    void UpdateCaretPosition(bool setCaret)
2837    {
2838       if(line)
2839       {
2840          if(mouseMove || (!overwrite && !style.noCaret))
2841          {
2842             int max = this.mouseMove ? this.dropX : this.x;
2843             int y = this.mouseMove ? this.dropY : this.y;
2844             EditLine line = this.mouseMove ? this.dropLine : this.line;
2845             int c, x = 0;
2846             if(!(style.freeCaret))
2847                max = Min(max, line.count);
2848
2849             if(FontExtent && display)
2850             {
2851                for(c = 0; c < max; )
2852                {
2853                   int len = 1;
2854                   int start = c;
2855                   int w;
2856                   if(c < line.count)
2857                   {
2858                      byte ch = 0;
2859                      int numBytes;
2860                      for(len = 0; c < Min(max, line.count); c += numBytes)
2861                      {
2862                         ch = line.buffer[c];
2863                         numBytes = UTF8_NUM_BYTES(ch);
2864
2865                         if(ch == ' ' || ch == '\t')
2866                         {
2867                            if(!len) c++;
2868                            break;
2869                         }
2870                         len += numBytes;
2871                      }
2872                      if(!len && ch == ' ')
2873                      {
2874                         w = space.w;
2875                         len = 1;
2876                      }
2877                      else if(!len && ch == '\t')
2878                      {
2879                         w = (tabSize * space.w) - (x % (tabSize * space.w));
2880                         len = 1;
2881                      }
2882                      else
2883                         FontExtent(display, this.font, line.buffer + start, len, &w, null);
2884                   }
2885                   else
2886                   {
2887                      w = space.w;
2888                      c++;
2889                   }
2890                   x += w;
2891                }
2892             }
2893             if(setCaret)
2894                caretX = x;
2895             caretY = y * this.space.h;
2896             SetCaret(x + XOFFSET-2, y * space.h + YOFFSET, space.h);
2897          }
2898          else
2899             SetCaret(0, 0, 0);
2900
2901          // TOFIX: Mismatch between NotifyCaretMove() and NotifyDropped() / GoToPosition()
2902          NotifyCaretMove(master, this, y + 1, x + 1);
2903
2904          SelectionEnables();
2905       }
2906    }
2907
2908    void SelectionEnables()
2909    {
2910       if((x != selX || y != selY) && !selection)
2911       {
2912          if(!style.readOnly)
2913          {
2914             itemEditCut.disabled = false;
2915             itemEditDelete.disabled = false;
2916          }
2917          itemEditCopy.disabled = false;
2918
2919          this.selection = true;
2920       }
2921       else if((x == selX && y == selY) && selection)
2922       {
2923          itemEditCut.disabled = true;
2924          itemEditCopy.disabled = true;
2925          itemEditDelete.disabled = true;
2926
2927          this.selection = false;
2928       }
2929    }
2930
2931    void SetSelectCursor()
2932    {
2933       if(!inactive || !style.noSelect)
2934       {
2935          if(this.mouseMove)
2936             cursor = guiApp.GetCursor(arrow);
2937          else if(this.mouseSelect)
2938             cursor = guiApp.GetCursor(iBeam);
2939          else
2940          {
2941             if(IsMouseOnSelection())
2942                cursor = guiApp.GetCursor(arrow);
2943             else
2944                cursor = guiApp.GetCursor(iBeam);
2945          }
2946       }
2947    }
2948
2949    int AdjustXPosition(EditLine line, int position, bool half, int * px, int max, int sc)
2950    {
2951       int c = sc;
2952       int x = px ? *px : 0;
2953       while(true)
2954       {
2955          int start = c;
2956          int numBytes = 1;
2957          int len = 1;
2958          int w;
2959          if(c < Min(max, line.count))
2960          {
2961             byte ch = 0;
2962             int numBytes;
2963             for(len = 0; c < Min(max, line.count); c += numBytes)
2964             {
2965                ch = line.buffer[c];
2966                numBytes = UTF8_NUM_BYTES(ch);
2967
2968                if(ch == ' ' || ch == '\t')
2969                {
2970                   if(!len) c++;
2971                   break;
2972                }
2973                len += numBytes;
2974             }
2975             if(!len && ch == ' ')
2976             {
2977                w = space.w;
2978                len = 1;
2979             }
2980             else if(!len && ch == '\t')
2981             {
2982                w = (tabSize * space.w) - (x % (tabSize * space.w));
2983                len = 1;
2984             }
2985             else
2986                FontExtent(display, font, line.buffer + start, len, &w, null);
2987          }
2988          else
2989          {
2990             if(style.freeCaret && c < max)
2991                w = space.w;
2992             else
2993             {
2994                if(px) *px = x;
2995                return c;
2996             }
2997             c++;
2998          }
2999          if(x + (((half && len == 1) ? (w / 2) : w)) >= position)
3000          {
3001             int lastW;
3002             while(len > 0)
3003             {
3004                int a = start + len;
3005                lastW = w;
3006                if(a <= line.count)
3007                   while(a > 0 && !UTF8_IS_FIRST(line.buffer[--a]));
3008                else
3009                   a--;
3010                if(a > start)
3011                   FontExtent(display, font, line.buffer + start, a - start, &w, null);
3012                else
3013                   w = 0;
3014                if(position > x + (half ? ((w + lastW) / 2) : lastW)) break;
3015                len = a - start;
3016             }
3017             if(px) *px = x + w;
3018             return Min(this.maxLineSize - 1, start + len);
3019          }
3020          x += w;
3021       }
3022    }
3023
3024    void SetCursorToViewX()
3025    {
3026       bool selecting = this.x != selX || y != selY;
3027
3028       // Horizontal Adjustment
3029       int x = 0;
3030       int c = AdjustXPosition(line, viewX, false, &x, MAXINT, 0);
3031       if(this.x < c)
3032          this.x = x;
3033       else
3034       {
3035          c = AdjustXPosition(line, viewX + clientSize.w - 1, false, &x, MAXINT, c);
3036          if(this.x > c)
3037             this.x = c;
3038      }
3039
3040       if(!selecting)
3041       {
3042          selX = this.x;
3043          selY = y;
3044          selLine = line;
3045       }
3046
3047       UpdateCaretPosition(false);
3048
3049       UpdateDirty();
3050       SetSelectCursor();
3051    }
3052
3053    void SetCursorToViewY()
3054    {
3055       int c, numLines;
3056       EditLine oldLine = this.line;
3057
3058       bool selecting = this.x != this.selX || this.y != this.selY;
3059
3060       numLines = clientSize.h / this.space.h;
3061
3062       // Vertical Adjustment
3063       if(this.viewY > this.y)
3064       {
3065          this.y = this.viewY;
3066          this.line = this.viewLine;
3067       }
3068
3069       if(this.viewY + numLines <=  this.y)
3070       {
3071          EditLine line;
3072
3073          this.y = this.viewY-1;
3074          for(c = 0, line = this.viewLine; line && c<numLines; line = line.next, c++)
3075          {
3076             this.line = line;
3077             this.y++;
3078          }
3079       }
3080
3081       if(this.line != oldLine)
3082       {
3083          this.x = AdjustXPosition(this.line, caretX, true, null, MAXINT, 0);
3084          ComputeColumn();
3085       }
3086
3087       if(!selecting)
3088       {
3089          this.selX = this.x;
3090          this.selY = this.y;
3091          this.selLine = this.line;
3092       }
3093
3094       UpdateCaretPosition(false);
3095
3096       UpdateDirty();
3097       SetSelectCursor();
3098    }
3099
3100    /*
3101    bool SaveFile(char * fileName)
3102    {
3103       File f = eFile_Open(fileName, FO_WRITE);
3104       if(f)
3105       {
3106          Save(f, false);
3107          eWindow_SetModified(false);
3108          eInstance_Delete(f);
3109          return true;
3110       }
3111       return false;
3112    }
3113    */
3114
3115    bool OnActivate(bool active, Window previous, bool * goOnWithActivation, bool direct)
3116    {
3117       if(line)
3118       {
3119          if(!style.multiLine)
3120          {
3121             x = (active && this.active && !style.readOnly) ? line.count : 0;
3122             selX = 0;
3123             ComputeColumn();
3124             SetViewToCursor(true);
3125             DirtyLine(0);
3126             UpdateDirty();
3127          }
3128          // Update(null);
3129          if(!active && modified)
3130          {
3131             modified = false;
3132             if(!NotifyModified(master, this))
3133             {
3134                modified = true;
3135                *goOnWithActivation = false;
3136             }
3137          }
3138       }
3139       if(!active)
3140       {
3141          ReleaseCapture();
3142          if(timer) timer.Stop();
3143
3144          mouseSelect = false;
3145          wordSelect = false;
3146       }
3147       return true;
3148    }
3149
3150    void AutoSize()
3151    {
3152       int aw = maxLength + 12, ah = Max(lineCount, 1) * space.h + 2;
3153       int nw = minClientSize.w, nh = minClientSize.h, xw = maxClientSize.w, xh = maxClientSize.h;
3154       clientSize = { nw && aw < nw ? nw : xw && aw > xw ? xw : aw, nh && ah < nh ? nh : xh && ah > xh ? xh : ah };
3155    }
3156
3157    bool OnResizing(int *w, int *h)
3158    {
3159       if(!*h)
3160          *h = space.h + 2;
3161       if(!*w)
3162          //*w = 80;
3163          *w = space.h * 80 / 14;
3164       return true;
3165    }
3166
3167    void OnResize(int w, int h)
3168    {
3169    /*
3170       if(!hasHorzScroll && !hasVertScroll && viewLine)
3171          SetViewToCursor(true);
3172    */
3173       //if(!hasHorzScroll && !hasVertScroll && viewLine)
3174       if(viewLine)
3175          SetViewToCursor(true);
3176       //else
3177    //     FixScrollArea();
3178    }
3179
3180    bool OnMiddleButtonDown(int x, int y, Modifiers mods)
3181    {
3182       if(style.readOnly) return true;
3183       // We really shouldn't be pasting here, Unix middle button paste is for the selection (not the clipboard), good for the terminal
3184       // Middle button already serves as a dragger as well.
3185       // Paste();
3186       return true;
3187    }
3188
3189    bool OnRightButtonDown(int x, int y, Modifiers mods)
3190    {
3191       this.rightButtonDown = true;
3192       // Copy();
3193       return true;
3194    }
3195
3196    bool OnRightButtonUp(int x, int y, Modifiers mods)
3197    {
3198       // Context Menu
3199       if(!parent.inactive && rightButtonDown)
3200       {
3201          PopupMenu popup;
3202          Menu contextMenu { };
3203
3204          MenuItem { contextMenu, $"Cut\tCtrl+X", t, NotifySelect = itemEditCut.NotifySelect, disabled = !selection || style.readOnly };
3205          MenuItem { contextMenu, $"Copy\tCtrl+C", c, NotifySelect = itemEditCopy.NotifySelect, disabled = !selection };
3206          MenuItem { contextMenu, $"Paste\tCtrl+V", p, NotifySelect = itemEditPaste.NotifySelect, disabled = style.readOnly };
3207          MenuItem { contextMenu, $"Delete\tDel", d, NotifySelect = itemEditDelete.NotifySelect, disabled = !selection || style.readOnly };
3208          MenuDivider { contextMenu };
3209          MenuItem { contextMenu, $"Select All\tCtrl+A", a, NotifySelect = itemEditSelectAll.NotifySelect };
3210
3211          popup = PopupMenu { master = this, menu = contextMenu,
3212    /*
3213             nonClient = true, interim = false, parent = parent,
3214             position = { x + clientStart.x + parent.clientStart.x + position.x, y + cientStart.y + parent.sy + clientStart.y + position.y };
3215    */
3216             position = { x + clientStart.x + absPosition.x - guiApp.desktop.position.x, y + clientStart.y + absPosition.y - guiApp.desktop.position.y }
3217          };
3218          popup.Create();
3219       }
3220       rightButtonDown = false;
3221       return true;
3222    }
3223
3224    bool OnLeftButtonDown(int mx, int my, Modifiers mods)
3225    {
3226       int x,y;
3227       EditLine line;
3228
3229       if(style.noSelect) return true;
3230
3231       // Should we have a separate 'selectOnActivate' style?
3232       if(!mods.isActivate || (style.readOnly && style.multiLine))
3233       {
3234          Capture();
3235          mouseSelect = true;
3236       }
3237
3238       mouseX = mx - XOFFSET;
3239       mouseY = my;
3240
3241       FindMouse(mouseX, mouseY, &x, &y, &line, true);
3242 #ifdef _DEBUG
3243       //PrintLn("OnLeftButtonDown: ", x, ", ", y);
3244 #endif
3245       if(!style.readOnly)
3246       {
3247          if(wordSelect)
3248             mouseMove = false;
3249          else if(IsMouseOnSelection() && !mods.isActivate)
3250          {
3251             DirtyLine(this.y);
3252             mouseMove = true;
3253             dropX = x;
3254             dropY = y;
3255             dropLine = line;
3256          }
3257       }
3258
3259       if(!mouseMove && !wordSelect && (!mods.isActivate || style.multiLine))
3260       {
3261          if(mods.shift && !mods.isActivate)
3262          {
3263             this.x = x;
3264             this.y = y;
3265             this.line = line;
3266             DirtyAll();
3267          }
3268          else
3269          {
3270             SelDirty();
3271             DirtyLine(this.y);
3272             this.x = x;
3273             this.y = y;
3274             this.line = line;
3275             DirtyLine(this.y);
3276             _Deselect();
3277          }
3278          ComputeColumn();
3279       }
3280
3281       UpdateDirty();
3282       UpdateCaretPosition(true);
3283       // Return false because DataBoxes automatically set EditBox editor's clickThrough to true for MouseMove events
3284       // ( for tool tips -- see 95ee4962c4c7bc3fe0a04aa6a4f98cacada40884)
3285       return false;
3286    }
3287
3288    bool OnLeftButtonUp(int x, int y, Modifiers mods)
3289    {
3290       timer.Stop();
3291
3292       mouseSelect = false;
3293       wordSelect = false;
3294
3295       x -= XOFFSET;
3296
3297       ReleaseCapture();
3298       if(!style.readOnly)
3299       {
3300          if(mouseMove)
3301          {
3302             EditLine line;
3303             FindMouse(mouseX, mouseY, &x, &y, &line, true);
3304 #ifdef _DEBUG
3305             //PrintLn("MouseMove: ", x, ", ", y);
3306 #endif
3307             dropX = x;
3308             dropY = y;
3309             dropLine = line;
3310
3311             mouseMove = IsMouseOnSelection();
3312
3313             if(!mouseMove)
3314             {
3315                int size = SelSize();
3316                if(size)
3317                {
3318                   char * text = new char[size+1];
3319                   if(text)
3320                   {
3321                      int moveX = 0;
3322                      GetSel(text, false);
3323
3324                      if(Max(selY, this.y) == dropY)
3325                      {
3326                         if(this.x > selX)
3327                         {
3328                            if(this.dropX > this.selX)
3329                               moveX = this.x - this.selX;
3330                         }
3331                         else
3332                         {
3333                            if(this.dropX > this.x)
3334                               moveX = this.selX - this.x;
3335                         }
3336                      }
3337
3338                      recordUndoEvent = true;
3339                      DelSel(null);
3340                      this.dropX -= moveX;
3341                      this.selX = this.x = this.dropX;
3342                      this.selY = this.y = this.dropY;
3343                      this.selLine = this.line = this.dropLine;
3344                      AddS(text);
3345                      recordUndoEvent = false;
3346
3347                      SetViewToCursor(true);
3348                      delete text;
3349                      Modified();
3350 #ifdef _DEBUG
3351                    /*  {
3352                         byte * c = ((EditBox)this).multiLineContents, * c2;
3353                         int l1 = c ? strlen(c) : 0, l2;
3354                         Undo();
3355                         Redo();
3356                         c2 = ((EditBox)this).multiLineContents;
3357                         l2 = c2 ? strlen(c2) : 0;
3358                         if(l1 != l2 || (l1 && strcmp(c, c2)))
3359                         {
3360                            PrintLn("Fail!");
3361                         }
3362                         delete c;
3363                      }
3364                      */
3365 #endif
3366                   }
3367                }
3368             }
3369             else
3370             {
3371                SelDirty();
3372                DirtyLine(this.y);
3373                this.x = x;
3374                this.y = y;
3375                this.line = line;
3376                ComputeColumn();
3377                DirtyLine(this.y);
3378                _Deselect();
3379                UpdateDirty();
3380             }
3381          }
3382          else
3383          {
3384             EditLine line;
3385             mouseX = x;
3386             mouseY = y;
3387
3388             FindMouse(mouseX, mouseY, &x, &y, &line, true);
3389 #ifdef _DEBUG
3390             //PrintLn("Dropped: ", x, ", ", y);
3391 #endif
3392             NotifyDropped(master, this, x, y);
3393          }
3394       }
3395       mouseMove = false;
3396       // Return false because DataBoxes automatically set EditBox editor's clickThrough to true for MouseMove events
3397       // ( for tool tips -- see 95ee4962c4c7bc3fe0a04aa6a4f98cacada40884)
3398       return false;
3399    }
3400
3401    bool OnMouseMove(int mx, int my, Modifiers mods)
3402    {
3403       int x,y;
3404       EditLine line;
3405       bool needScroll;
3406
3407       if(mods != -1 && mods.isSideEffect)
3408       {
3409          SetSelectCursor();
3410          return true;
3411       }
3412       if(style.noSelect) return true;
3413       if(wordSelect) return true;
3414       mouseX = mx - XOFFSET;
3415       mouseY = my;
3416
3417       needScroll = FindMouse(this.mouseX, this.mouseY, &x, &y, &line, true);
3418
3419       if(this.mouseMove || this.mouseSelect)
3420       {
3421          if(!needScroll)
3422             timer.Stop();
3423          else
3424          {
3425             if(needScroll)
3426                timer.Start();
3427             if(mods != -1 &&
3428                ((style.hScroll) || (style.vScroll)))
3429                return true;
3430          }
3431       }
3432
3433       if(this.mouseMove)
3434       {
3435          DirtyLine(this.dropY);
3436          this.dropX = x;
3437          this.dropY = y;
3438          DirtyLine(this.dropY);
3439          this.dropLine = line;
3440          SetViewToCursor(true);
3441 #ifdef _DEBUG
3442          //PrintLn("MouseMove: ", "dropX = ", x, ", dropY = ", y);
3443 #endif
3444       }
3445       else if(this.mouseSelect)
3446       {
3447          DirtyLine(this.selY);
3448          DirtyLine(this.y);
3449          this.x = x;
3450          this.y = y;
3451          ComputeColumn();
3452          DirtyLine(this.y);
3453          this.line = line;
3454          SetViewToCursor(true);
3455          UpdateDirty();
3456 #ifdef _DEBUG
3457          //PrintLn("MouseSelect: ", "x = ", x, ", y = ", y);
3458 #endif
3459       }
3460       SetSelectCursor();
3461       return true;
3462    }
3463
3464    bool OnLeftDoubleClick(int mx, int my, Modifiers mods)
3465    {
3466       int x,y;
3467       EditLine line;
3468 #ifdef _DEBUG
3469       //PrintLn("OnLeftDoubleClick: ", mx, ", ", my, ", mods = ", mods);
3470 #endif
3471       mx -= XOFFSET;
3472
3473       if(style.noSelect) return true;
3474       FindMouse(mx, my, &x, &y, &line, false);
3475       if(!NotifyDoubleClick(master, this, line, mods))
3476          return false;
3477       if(x < line.count)
3478       {
3479          int c;
3480          int start = -1;
3481          int numBytes;
3482          for(c = x; c >= 0; c--)
3483          {
3484             unichar ch;
3485             while(c > 0 && !UTF8_IS_FIRST(line.buffer[c])) c--;
3486             ch = UTF8_GET_CHAR(line.buffer + c, numBytes);
3487             if(!IS_ALUNDER(ch))
3488                break;
3489             start = c;
3490          }
3491          if(start != -1)
3492          {
3493             for(c = start; c<line.count; c += numBytes)
3494             {
3495                unichar ch = UTF8_GET_CHAR(line.buffer + c, numBytes);
3496                if(!ch || !IS_ALUNDER(ch))
3497                   break;
3498             }
3499             SelDirty();
3500             DirtyLine(this.y);
3501             this.y = y;
3502             DirtyLine(this.y);
3503             this.selX = start;
3504             this.x = c;
3505             ComputeColumn();
3506             this.line = this.selLine = line;
3507             this.wordSelect = (c != start);
3508             UpdateDirty();
3509          }
3510       }
3511       return true;
3512    }
3513
3514    bool OnPostCreate()
3515    {
3516       /*if(isDocument)
3517       {
3518          Menu fileMenu { menu, "File", F };
3519          saveDialog = fileDialog;
3520          MenuItem { fileMenu, $"Save\tCtrl+S", S, CtrlS, NotifySelect = MenuFileSave };
3521          MenuItem { fileMenu, $"Save As...", A, NotifySelect = MenuFileSaveAs };
3522       }*/
3523       if(style.autoSize) AutoSize();
3524       return true;
3525    }
3526
3527    void ComputeFont()
3528    {
3529       if(FontExtent)
3530       {
3531          FontExtent(display, font, " ", 1, (int *)&space.w, (int *)&space.h);
3532          FontExtent(display, font, "W", 1, (int *)&large.w, (int *)&large.h);
3533
3534          space.w = Max(space.w, 1);
3535          large.w = Max(large.w, 1);
3536          space.h = Max(space.h, 1);
3537          large.h = Max(large.h, 1);
3538
3539          {
3540             EditLine line;
3541             for(line = lines.first; line; line = line.next)
3542                ComputeLength(line);
3543             FindMaxLine();
3544          }
3545
3546          if(viewLine)
3547             SetViewToCursor(true);
3548
3549          FixScrollArea();
3550
3551          Update(null);
3552       }
3553    }
3554
3555    bool OnLoadGraphics()
3556    {
3557       FontExtent = Display::FontExtent;
3558       font = fontObject;
3559       ComputeFont();
3560       // UpdateCaretPosition(true);
3561       return true;
3562    }
3563
3564    void OnUnloadGraphics()
3565    {
3566       this.font = null;
3567    }
3568
3569    bool OnKeyHit(Key key, unichar ch)
3570    {
3571       bool shift = (key.shift) ? true : false;
3572 #ifdef _DEBUG
3573       //PrintLn("OnKeyHit: code = ", key.code, ", mods = ", key.modifiers, ", ch = ", (int)ch);
3574 #endif
3575       if(!ch && !key.alt && !key.ctrl)
3576       {
3577          key.code = (SmartKey)key.code;
3578       }
3579       else if(!ch && key.alt)
3580          key.code = 0;
3581
3582       switch(key.code) //(ch || key.alt || key.ctrl) ? key.code : (Key)(SmartKey)key.code)
3583       {
3584          case backSpace:
3585             if(style.readOnly) break;
3586             if(style.stuckCaret) GoToEnd(true);
3587             if(!(style.freeCaret))
3588             {
3589                this.x = Min(this.x, this.line.count);
3590             }
3591             if(key.ctrl)
3592             {
3593                int y;
3594                bool done = false;
3595                EditLine line = this.line;
3596                int c;
3597                for(y = this.y; y>= 0; y--)
3598                {
3599                   c = (y == this.y) ? (Min(this.x-1, line.count-1)) : line.count-1;
3600
3601                   // Slow down when going on lines...
3602                   if(y != this.y) break;
3603
3604                   for(; c>=0; c--)
3605                   {
3606                      //if(this.line.buffer[c] != '\t' && this.line.buffer[c] != ' ')
3607                      if(IS_ALUNDER(line.buffer[c]))
3608                         break;
3609                   }
3610                   for(; c>=0; c--)
3611                   {
3612                      if(!IS_ALUNDER(line.buffer[c]))
3613                      {
3614                      //if(this.line.buffer[c] == '\t' || this.line.buffer[c] == ' ')
3615                         done = true;
3616                         break;
3617                      }
3618                   }
3619                   if(done)
3620                      break;
3621                   if(line.prev)
3622                      line = line.prev;
3623                   else
3624                      break;
3625                }
3626
3627                //if(this.x != 0)
3628                {
3629                   _DelCh(line, y, c+1, this.line, this.y, this.x, true, false, null);
3630                   this.x = this.selX = Min(c+1, line.count);
3631                   this.y = this.selY = y;
3632                   this.line = this.selLine = line;
3633                   SetViewToCursor(true);
3634                }
3635                ComputeColumn();
3636             }
3637             else
3638             {
3639                BackSpace();
3640             }
3641             return false;
3642             //break;
3643          case del:
3644             if(style.readOnly) break;
3645             if(style.stuckCaret) break;
3646
3647             if(shift)
3648             {
3649                Cut();
3650             }
3651             else
3652             {
3653                // Delete selection
3654                if(this.line != this.selLine || this.x != this.selX)
3655                {
3656                   DelSel(null);
3657                   SetViewToCursor(true);
3658                   Modified();
3659                }
3660                else
3661                {
3662                   EditLine line1 = this.line, line2 = this.line;
3663                   int x1, y1 = this.y, x2, y2 = this.y;
3664                   if(!style.freeCaret)
3665                   {
3666                      this.selX = this.x = Min(this.x, this.line.count);
3667                      ComputeColumn();
3668                   }
3669                   x1 = this.x;
3670
3671                   if(x1 < line1.count)
3672                   {
3673                      // Delete word
3674                      if(key.ctrl)
3675                      {
3676                         int i;
3677                         int length;
3678                         char * buffer = line1.buffer;
3679                         for(i = x1; i < line1.count; i++)
3680                         {
3681                            if(!IS_ALUNDER(buffer[i]))
3682                               break;
3683                         }
3684
3685                         for(; i < line1.count; i++)
3686                         {
3687                            // Delete trailing whitespace
3688                            if(IS_ALUNDER(buffer[i]))
3689                               break;
3690                         }
3691                         x2 = i;
3692                      }
3693                      else
3694                         x2 = x1 + 1;
3695                   }
3696                   else
3697                   {
3698                      // Avoid creating trailing spaces if there is no content on next line
3699                      line2 = line1.next;
3700                      y2++;
3701                      if(line2 && !line2.count)
3702                         x1 = line1.count;
3703                      x2 = 0;
3704                   }
3705                   if(line2)
3706                      _DelCh(line1, y1, x1, line2, y2, x2, false, false, null);
3707                   SetViewToCursor(true);
3708                   Modified();
3709                }
3710             }
3711             return false;
3712             //break;
3713          case enter:
3714          case keyPadEnter:
3715          {
3716             if(!key.alt && !key.ctrl)
3717             {
3718                int c;
3719                int position = 0;
3720                bool stuffAfter = false;
3721                char * addString;
3722                int len = 0;
3723                /*bool resetX = false;
3724                int backX;*/
3725
3726                if(style.stuckCaret) GoToEnd(true);
3727                if(style.readOnly) break;
3728                if(!(style.multiLine)) break;
3729
3730                for(c = 0; c<this.line.count && c<this.x; c++)
3731                {
3732                   if(this.line.buffer[c] == '\t')
3733                      position += this.tabSize - (position % this.tabSize);
3734                   else if(this.line.buffer[c] == ' ')
3735                      position++;
3736                   else
3737                      break;
3738                }
3739
3740                // Prevent adding trailing spaces if at the head of a line
3741                /*if(c && c == this.x && c < this.line.count && this.x == this.selX && this.y == this.selY)
3742                {
3743                   position = 0;
3744                   backX = this.x;
3745                   this.x = 0;
3746                   this.selX = 0;
3747                   resetX = true;
3748                }*/
3749
3750                if(!line.count)
3751                   position = x;
3752
3753                if(this.x < this.line.count)
3754                   stuffAfter = true;
3755
3756                //If last character is a { indent one tab
3757                if(this.line.buffer[this.x - 1] == '{')
3758                {
3759                   //Except if the next non space character is a }
3760                   bool indent = false;
3761                   int i;
3762                   for(i = this.x; i < this.line.size; i++)
3763                      if(this.line.buffer[i] != ' ' && this.line.buffer[i] != '\t')
3764                      {
3765                         if(this.line.buffer[i] != '}')
3766                            indent = true;
3767                         break;
3768                      }
3769                   if(indent)
3770                      position += this.tabSize;
3771                }
3772
3773                addString = new char[position + 2];
3774                addString[len++] = '\n';
3775                addString[len] = '\0';
3776
3777                if(stuffAfter || !style.freeCaret)
3778                {
3779                   for(c = 0; c<position; )
3780                   {
3781                      if(style.useTab && c + this.tabSize <= position)
3782                      {
3783                         addString[len++] = '\t';
3784                         c += this.tabSize;
3785                      }
3786                      else
3787                      {
3788                         addString[len++] = ' ';
3789                         c++;
3790                      }
3791                   }
3792                   addString[len] = '\0';
3793                }
3794                recordUndoEvent = true;
3795                if(AddS(addString))
3796                {
3797                   EditLine prevLine = this.line.prev;
3798                   if(prevLine)
3799                   {
3800                      // Nuke spaces if that is all that is left on previous line
3801                      int i;
3802                      char * buffer = prevLine.buffer;
3803                      for(i = 0; i < prevLine.count; i++)
3804                         if(buffer[i] != ' ' && buffer[i] != '\t')
3805                            break;
3806                      if(i == prevLine.count)
3807                         DelCh(prevLine, this.y - 1, 0, prevLine, this.y - 1, prevLine.count, false);
3808                   }
3809                   /*if(resetX)
3810                   {
3811                      this.x = this.selX = backX;
3812                      ComputeColumn();
3813                   }
3814                   else */if(!stuffAfter && style.freeCaret)
3815                   {
3816                      this.x = this.selX = position;
3817                      ComputeColumn();
3818                   }
3819                   FindMaxLine();
3820                   SetViewToCursor(true);
3821                   Modified();
3822                }
3823                recordUndoEvent = false;
3824                delete addString;
3825                return false;
3826             }
3827             break;
3828          }
3829          case left:
3830          {
3831             if(style.stuckCaret) break;
3832             if(!(style.freeCaret))
3833             {
3834                this.x = Min(this.x, this.line.count);
3835                this.selX = Min(this.selX, this.selLine.count);
3836                ComputeColumn();
3837             }
3838             if(!shift) SelDirty();
3839             if(key.ctrl)
3840             {
3841                bool foundAlpha = false;
3842                bool found = false;
3843                int y = this.y;
3844                EditLine line, lastLine;
3845                int lastC, lastY;
3846
3847                for(line = this.line; (line && !found); line = line.prev, y--)
3848                {
3849                   int start;
3850                   int c;
3851
3852                   if(this.x == 0 && line != this.line)
3853                   {
3854                      foundAlpha = true;
3855                      lastC = line.count;
3856                      lastY = y;
3857                      lastLine = line;
3858                      break;
3859                   }
3860
3861                   if(line == this.line) start = this.x -1; else start = line.count-1;
3862                   start = Min(start, line.count-1);
3863
3864                   for(c = start; c >= 0;)
3865                   {
3866                      int numBytes;
3867                      unichar ch = UTF8_GET_CHAR(line.buffer + c, numBytes);
3868                      if(IS_ALUNDER(ch))
3869                      {
3870                         foundAlpha = true;
3871                         lastC = c;
3872                         lastY = y;
3873                         lastLine = line;
3874                      }
3875                      else
3876                      {
3877                         if(foundAlpha)
3878                         {
3879                            found = true;
3880                            break;
3881                         }
3882                      }
3883                      while(--c)
3884                      {
3885                         byte ch = line.buffer[c];
3886                         if(UTF8_IS_FIRST(ch)) break;
3887                      }
3888
3889                   }
3890                   // No next word found,
3891                   if(!found && ( this.x > 0 || (!line.count && this.x)))
3892                   {
3893                      foundAlpha = true;
3894                      lastC = 0;
3895                      lastY = y;
3896                      lastLine = line;
3897                      break;
3898                   }
3899                }
3900                if(foundAlpha)
3901                {
3902                   DirtyLine(this.y);
3903                   this.x = lastC;
3904                   this.y = lastY;
3905                   this.line = lastLine;
3906                   DirtyLine(this.y);
3907                   ComputeColumn();
3908                }
3909             }
3910             else
3911             {
3912                if(x > 0)
3913                {
3914                   if(x <= line.count)
3915                   {
3916                      byte * buffer = line.buffer;
3917                      while(--x)
3918                      {
3919                         byte ch = buffer[x];
3920                         if(UTF8_IS_FIRST(ch)) break;
3921                      }
3922                   }
3923                   else
3924                      x--;
3925                   DirtyLine(y);
3926                }
3927                else if(line.prev)
3928                {
3929                   line = line.prev;
3930                   DirtyLine(y);
3931                   x = line.count;
3932                   y--;
3933                   DirtyLine(y);
3934                }
3935                ComputeColumn();
3936             }
3937             if(!shift) _Deselect();
3938             SetViewToCursor(true);
3939             //break;
3940             return false;
3941          }
3942          case right:
3943          {
3944             if(style.stuckCaret) break;
3945             if(!(style.freeCaret))
3946             {
3947                this.x = Min(this.x, this.line.count);
3948                this.selX = Min(this.selX, this.selLine.count);
3949                ComputeColumn();
3950             }
3951             if(!shift) SelDirty();
3952             if(!shift && (this.x != this.selX || this.y != this.selY));
3953             else if(key.ctrl)
3954             {
3955                bool onAChar = false;
3956                if(this.selX != this.x || this.selY != this.y)
3957                   onAChar = true;
3958                if(this.x<this.line.count)
3959                   if(this.line.buffer[this.x] != '\t' && this.line.buffer[this.x] !=' ')
3960                      onAChar = true;
3961                if(key.shift && onAChar &&
3962                   ((this.y > this.selY)||((this.selY == this.y)&&(this.x >= this.selX))))
3963                {
3964                   bool foundAlpha = false;
3965                   bool found = false;
3966                   EditLine line, lastLine;
3967                   int y = this.y;
3968                   int lastC, lastY, lastNumBytes;
3969
3970                   for(line = this.line; (line && !found); line = line.next, y++)
3971                   {
3972                      int start = (line == this.line) ? this.x : 0;
3973                      int c;
3974                      int numBytes;
3975                      unichar ch;
3976                      for(c = start; c < line.count && (ch = UTF8_GET_CHAR(line.buffer + c, numBytes)); c += numBytes)
3977                      {
3978                         if(IS_ALUNDER(ch))
3979                         {
3980                            foundAlpha = true;
3981                            lastC = c;
3982                            lastNumBytes = numBytes;
3983                            lastY = y;
3984                            lastLine = line;
3985                         }
3986                         else if(foundAlpha)
3987                         {
3988                            found = true;
3989                            break;
3990                         }
3991                      }
3992                      if(!found && (c != this.x || line != this.line))
3993                      {
3994                         found = true;
3995                         lastLine = line;
3996                         lastC = line.count;
3997                         lastNumBytes = 0;
3998                         lastY = y;
3999                         break;
4000                      }
4001                   }
4002                   if(found)
4003                   {
4004                      DirtyLine(this.y);
4005                      this.x = lastC + lastNumBytes;
4006                      this.y = lastY;
4007                      this.line = lastLine;
4008                      DirtyLine(this.y);
4009                      ComputeColumn();
4010                   }
4011                }
4012                else
4013                {
4014                   bool foundAlpha = false;
4015                   bool found = false;
4016                   EditLine line;
4017                   int y = this.y;
4018
4019                   for(line = this.line; (line && !found); line = line.next, y++)
4020                   {
4021                      int start = (line == this.line) ? this.x : 0;
4022                      int c;
4023                      int numBytes;
4024                      unichar ch;
4025                      for(c = start; c < line.count && (ch = UTF8_GET_CHAR(line.buffer + c, numBytes)); c += numBytes)
4026                      {
4027                         if(!IS_ALUNDER(ch))
4028                            foundAlpha = true;
4029                         else if(foundAlpha)
4030                         {
4031                            found = true;
4032                            DirtyLine(this.y);
4033                            this.x = c;
4034                            this.y = y;
4035                            this.line = line;
4036                            DirtyLine(this.y);
4037                            ComputeColumn();
4038                            break;
4039                         }
4040                      }
4041                      // No next word found,
4042                      if(!found && (c != this.x || line != this.line))
4043                      {
4044                         found = true;
4045                         DirtyLine(this.y);
4046                         this.x = line.count;
4047                         this.y = y;
4048                         this.line = line;
4049                         DirtyLine(this.y);
4050                         ComputeColumn();
4051                      }
4052                      foundAlpha = true;
4053                   }
4054                }
4055             }
4056             else
4057             {
4058                if(x < line.count || (style.freeCaret && line.count < maxLineSize))
4059                {
4060                   if(x < line.count)
4061                   {
4062                      byte * buffer = line.buffer;
4063                      while(++x)
4064                      {
4065                         byte ch = buffer[x];
4066                         if(UTF8_IS_FIRST(ch)) break;
4067                      }
4068                   }
4069                   else
4070                      x++;
4071                   ComputeColumn();
4072                   DirtyLine(y);
4073                }
4074                else
4075                {
4076                   if(line.next)
4077                   {
4078                      DirtyLine(y);
4079                      line = line.next;
4080                      y++;
4081                      x = 0;
4082                      col = 0;
4083                      DirtyLine(y);
4084                   }
4085                }
4086             }
4087             if(!shift) _Deselect();
4088             SetViewToCursor(true);
4089             // break;
4090             return false;
4091          }
4092          case up:
4093             if(key.ctrl)
4094             {
4095                if(!style.vScroll || hasVertScroll) break;
4096                LineUp();
4097                return false;
4098             }
4099             else
4100             {
4101                if(style.stuckCaret) break;
4102
4103                if(!shift) SelDirty();
4104                DirtyLine(this.y);
4105
4106                if(style.wrap)
4107                {
4108                }
4109                else if(line.prev)
4110                {
4111                   line = line.prev;
4112                   this.y--;
4113                   this.x = AdjustXPosition(line, caretX, true, null, MAXINT, 0);
4114                }
4115
4116                DirtyLine(this.y);
4117                if(!shift) _Deselect();
4118                ComputeColumn();
4119                SetViewToCursor(false);
4120
4121                /*
4122                if(caretY == this.y * space.h)
4123                {
4124                   if(line.prev)
4125                   {
4126                      line = line.prev;
4127                      this.y--;
4128                      if(!style.freeCaret)
4129                         this.x = Min(this.x, line.count);
4130                      caretY = MAXINT;
4131                   }
4132                   else
4133                      return false;
4134                }
4135
4136                {
4137                   int th = space.h;
4138                   int textPos = 0;
4139                   int sx = 0, sy = this.y * space.h;
4140                   char * text = line.text;
4141                   int maxW = clientSize.w - sx;
4142                   display.FontExtent(font, " ", 1, null, &th);
4143
4144                   do
4145                   {
4146                      int startPos = textPos;
4147                      int width = 0;
4148                      int x = 0;
4149                      bool lineComplete = false;
4150
4151                      if(!style.wrap && caretY == MAXINT)
4152                      {
4153                         caretY = sy + th;
4154                         //textPos = line.count;
4155                         //lineComplete = true;
4156                      }
4157
4158                      for(; (style.freeCaret || textPos < line.count) && !lineComplete;)
4159                      {
4160                         int w = 0;
4161                         int len;
4162                         char * nextSpace = (textPos < line.count) ? strchr(text + textPos, ' ') : (text + textPos);
4163
4164                         if(nextSpace)
4165                            len = (nextSpace - (text + textPos));
4166                         else
4167                            len = line.count - textPos;
4168
4169                         if(textPos < line.count)
4170                         {
4171                            display.FontExtent(font, text + textPos, len, &w, null);
4172                         }
4173                         if(nextSpace) { w += space.w; len++; }
4174
4175                         if(style.wrap && x + width + w > maxW && x > 0)
4176                         {
4177                            lineComplete = true;
4178                            break;
4179                         }
4180                         textPos += len;
4181                         width += w;
4182                         if(nextSpace)
4183                         {
4184                            x += width;
4185                            width = 0;
4186                         }
4187                         if((!style.freeCaret && textPos >= line.count) || (sy == caretY - th && caretX <= x + width + sx))
4188                         {
4189                            x += width;
4190                            this.x = textPos;
4191                            while(this.x > 0 && x + sx > caretX && this.x > startPos)
4192                            {
4193                               int len;
4194                               if(this.x > line.count)
4195                                  this.x--;
4196                               else
4197                                  while(this.x > 0 && !UTF8_IS_FIRST(text[--this.x]));
4198                               len = this.x - startPos;
4199                               display.FontExtent(font, text + startPos, len, &x, null);
4200                            }
4201
4202                            DirtyLine(this.y);
4203                            if(!shift) _Deselect();
4204                            ComputeColumn();
4205                            SetViewToCursor(false);
4206                            return false;
4207                         }
4208                      }
4209                      if(sy == caretY - th || textPos >= line.count)
4210                      {
4211                         if(textPos >= line.count)
4212                         {
4213                            int c = textPos - 1;
4214                            while(c > 0 && text[c] == ' ') c--;
4215                            this.x = c + 1;
4216                         }
4217                         else
4218                            this.x = line.count;
4219
4220                         DirtyLine(this.y);
4221                         if(!shift) _Deselect();
4222                         ComputeColumn();
4223                         SetViewToCursor(false);
4224                         return false;
4225                      }
4226                      sy += th;
4227                      sx = 0;
4228                   } while(textPos < line.count);
4229
4230                   DirtyLine(this.y);
4231                   if(!shift) _Deselect();
4232                   ComputeColumn();
4233                   SetViewToCursor(false);
4234                   return false;
4235                }
4236                */
4237
4238                // PREVIOUS CODE
4239                /*
4240                if(this.line.prev)
4241                {
4242                   int x = AdjustXPosition(this.line, this.line.prev, true, null, MAXINT, 0);
4243                   if(!shift) SelDirty();
4244                   this.line = this.line.prev;
4245                   DirtyLine(this.y);
4246                   this.y--;
4247
4248                   DirtyLine(this.y);
4249                   this.x = x;
4250                   if(!shift) _Deselect();
4251
4252                   ComputeColumn();
4253
4254                   SetViewToCursor(false);
4255                }
4256                */
4257
4258             }
4259             // break;
4260             return style.multiLine ? false : true;
4261          case down:
4262             if(key.ctrl)
4263             {
4264                if(!style.vScroll || hasVertScroll)
4265                   break;
4266                LineDown();
4267                return false;
4268             }
4269             else
4270             {
4271                if(style.stuckCaret) break;
4272                {
4273                   int th = space.h;
4274                   int textPos = 0;
4275                   int sx = 0, sy = this.y * this.space.h;
4276                   int maxW = clientSize.w - sx;
4277                   char * text = line.buffer;
4278
4279                   if(!shift) SelDirty();
4280                   DirtyLine(this.y);
4281
4282                   if(style.wrap)
4283                   {
4284                      /*
4285                      if(AdjustXPosition(line, maxW, this.x, line.count, true, null, MAXINT, 0) <= line.count)
4286                      {
4287
4288                      }
4289                      */
4290                   }
4291                   else if(line.next)
4292                   {
4293                      line = line.next;
4294                      this.y++;
4295                      this.x = AdjustXPosition(line, caretX, true, null, MAXINT, 0);
4296                   }
4297
4298                   DirtyLine(this.y);
4299                   if(!shift) _Deselect();
4300                   ComputeColumn();
4301                   SetViewToCursor(false);
4302
4303                   /*
4304                   while(!textPos || (style.freeCaret || textPos<line.count))
4305                   {
4306                      int startPos = textPos;
4307                      int width = 0;
4308                      int x = 0;
4309                      bool lineComplete = false;
4310                      if(!style.wrap && sy <= caretY)
4311                      {
4312                         textPos = line.count;
4313                         lineComplete = true;
4314                      }
4315                      for(; (style.freeCaret || textPos<line.count) && !lineComplete;)
4316                      {
4317                         int w = 0;
4318                         int len;
4319                         char * nextSpace = (textPos < line.count) ? strchr(text + textPos, ' ') : (text + textPos);
4320
4321                         if(nextSpace)
4322                            len = (nextSpace - (text + textPos));
4323                         else
4324                            len = line.count - textPos;
4325
4326                         if(textPos < line.count)
4327                         {
4328                            display.FontExtent(font, text + textPos, len, &w, &th);
4329                         }
4330                         if(nextSpace) { w += space.w; len++; }
4331                         if(style.wrap && x + width + w > maxW && x > 0)
4332                         {
4333                            lineComplete = true;
4334                            break;
4335                         }
4336                         textPos += len;
4337                         width += w;
4338                         if(nextSpace)
4339                         {
4340                            x += width;
4341                            width = 0;
4342                         }
4343                         if(sy > caretY && ((!style.freeCaret && textPos >= line.count) || caretX <= x + width + sx))
4344                         {
4345                            this.x = textPos;
4346                            x += width;
4347                            while(this.x > 0 && x + sx > caretX && textPos > startPos)
4348                            {
4349                               int len;
4350                               if(this.x > line.count)
4351                                  this.x--;
4352                               else
4353                                  while(this.x > 0 && !UTF8_IS_FIRST(text[--this.x]));
4354
4355                               len = this.x - startPos;
4356                               display.FontExtent(font, text + startPos, len, &x, null);
4357                            }
4358
4359                            if(!shift) _Deselect();
4360                            ComputeColumn();
4361
4362                            SetViewToCursor(false);
4363                            return false;
4364                         }
4365                      }
4366                      if(sy > caretY)
4367                      {
4368                         this.x = line.count;
4369
4370                         DirtyLine(this.y);
4371                         if(!shift) _Deselect();
4372                         ComputeColumn();
4373
4374                         SetViewToCursor(false);
4375                         return false;
4376                      }
4377                      else if(textPos >= line.count && line.next)
4378                      {
4379                         startPos = 0;
4380                         textPos = 0;
4381                         line = line.next;
4382                         this.y++;
4383                         sy = this.y * this.space.h;
4384                         sx = 0; //textBlock.startX;
4385                         text = line.buffer;
4386                      }
4387                      else
4388                      {
4389                         sy += th;
4390                         sx = 0; //textBlock.startX;
4391                      }
4392                   }
4393                   */
4394                   /*
4395                   if(line.next)
4396                   {
4397                      line = line.next;
4398                      this.x = Min(this.x, line.count);
4399                      //int x = AdjustXPosition(this.line, this.line.next, true, null, MAXINT, 0);
4400                      this.y++;
4401                      if(!shift) _Deselect();
4402                      ComputeColumn();
4403
4404                      if(this.selX != this.x || this.selY != this.y)
4405                         DirtyLine(this.y);
4406
4407                      SetViewToCursor(false);
4408                   }
4409                   */
4410                }
4411                /* PREVIOUS CODE
4412                if(this.line.next)
4413                {
4414                   int x = AdjustXPosition(this.line, this.line.next, true, null, MAXINT, 0);
4415                   if(!shift) SelDirty();
4416                   this.line = this.line.next;
4417                   DirtyLine(this.y);
4418                   this.y++;
4419                   this.x = x;
4420                   if(!shift) _Deselect();
4421                   ComputeColumn();
4422
4423                   if(this.selX != this.x || this.selY != this.y)
4424                      DirtyLine(this.y);
4425
4426                   SetViewToCursor();
4427                }
4428                */
4429             }
4430             // break;
4431             return style.multiLine ? false : true;
4432          case home:
4433          {
4434             if(style.stuckCaret) break;
4435             if(!style.multiLine && key.ctrl) break;
4436             if(!(style.freeCaret))
4437                this.selX = Min(this.selX, this.selLine.count);
4438
4439             if(!shift) SelDirty();
4440             if(key.ctrl)
4441             {
4442                this.line = this.lines.first;
4443                if(this.y != 0 || this.x != 0)
4444                   DirtyAll();
4445                this.y = 0;
4446                this.x = 0;
4447                this.col = 0;
4448             }
4449             else
4450             {
4451                if(style.smartHome)
4452                {
4453                   EditLine line = this.line;
4454                   int c;
4455                   for(c=0; line.buffer[c]; c++)
4456                      if(line.buffer[c] != ' ' && line.buffer[c] != '\t')
4457                         break;
4458                   if(shift && (c != 0 || this.x))
4459                      DirtyLine(this.y);
4460                   if(this.x != c)
4461                      this.x = c;
4462                   else
4463                      this.x = 0;
4464                }
4465                else
4466                {
4467                   if(shift && this.x != 0)
4468                      DirtyLine(this.y);
4469                   this.x = 0;
4470                }
4471                ComputeColumn();
4472             }
4473             if(!shift) _Deselect();
4474             SetViewToCursor(true);
4475             //break;
4476             return false;
4477          }
4478          case end:
4479          {
4480             if(style.stuckCaret) break;
4481             if(!style.multiLine && key.ctrl) break;
4482             if(!style.freeCaret)
4483                this.selX = Min(this.selX, this.selLine.count);
4484
4485             if(!shift) SelDirty();
4486             if(key.ctrl)
4487             {
4488                GoToEnd(false);
4489             }
4490             else if(this.x != this.line.count)
4491             {
4492                this.x = this.line.count;
4493                if(shift)
4494                   DirtyLine(this.y);
4495                ComputeColumn();
4496             }
4497             if(!shift) _Deselect();
4498             SetViewToCursor(true);
4499             //break;
4500             return false;
4501          }
4502          case tab:
4503             if(style.tabKey && !key.ctrl)
4504             {
4505                if(this.selY != this.y && style.tabSel)
4506                {
4507                   EditLine firstLine, lastLine;
4508                   EditLine line;
4509                   int y, x;
4510
4511                   // Do multi line selection tabbing here
4512                   if(this.selY < this.y)
4513                   {
4514                      firstLine = this.selLine;
4515                      lastLine = this.line;
4516                      y = this.selY;
4517                      x = this.x;
4518                   }
4519                   else
4520                   {
4521                      // Selecting going up
4522                      firstLine = this.line;
4523                      lastLine = this.selLine;
4524                      y = this.y;
4525                      x = this.selX;
4526                   }
4527                   ComputeColumn();
4528                   if(shift)
4529                   {
4530                      for(line = firstLine; line; line = line.next, y++)
4531                      {
4532                         if(line != lastLine || x)
4533                         {
4534                            int c;
4535                            int lastC = 0;
4536                            BufferLocation before = { line, y, 0 }, after = { line, y, 0 };
4537
4538                            for(c=0; c<line.count && lastC < this.tabSize; c++, lastC++)
4539                            {
4540                               if(line.buffer[c] == '\t')
4541                               {
4542                                  lastC++;
4543                                  break;
4544                               }
4545                               else if(line.buffer[c] != ' ')
4546                                  break;
4547                            }
4548                            after.x = lastC;
4549
4550                            NotifyCharsDeleted(master, this, &before, &after, this.pasteOperation);
4551                            if(lastC)
4552                            {
4553                               int len = GetText(null, line, y, 0, line, y, lastC, false, false);
4554                               char * string = new char[len];
4555                               DelTextAction action { y1 = y, x1 = 0, y2 = y, x2 = lastC, string = string, placeAfter = true };
4556                               GetText(string, line, y, 0, line, y, lastC, false, false);
4557                               Record(action);
4558                            }
4559                            memmove(line.buffer,line.buffer+lastC,line.size-lastC);
4560                            if(!line.AdjustBuffer(line.count-lastC))
4561                               break;
4562                            line.count-=lastC;
4563                            DirtyLine(y);
4564                         }
4565
4566                         if(line == lastLine) break;
4567                      }
4568                   }
4569                   else
4570                   {
4571                      for(line = firstLine; line; line = line.next, y++)
4572                      {
4573                         if(line.count)
4574                         {
4575                            if(line != lastLine || x)
4576                            {
4577                               if(style.useTab)
4578                               {
4579                                  if(line.count + 1 <= this.maxLineSize)
4580                                  {
4581                                     BufferLocation before = { line, y, 0 }, after = { line, y, 1 };
4582
4583                                     if(!line.AdjustBuffer(line.count+1))
4584                                        break;
4585
4586                                     NotifyCharsAdded(master, this, &before, &after, this.pasteOperation);
4587                                     {
4588                                        AddCharAction action { ch = '\t', x = 0, y = y };
4589                                        Record(action);
4590                                     }
4591
4592                                     memmove(line.buffer+1,line.buffer,line.size-1);
4593                                     line.count++;
4594                                     line.buffer[0] = '\t';
4595                                  }
4596                               }
4597                               else
4598                               {
4599                                  if(line.count + this.tabSize <= this.maxLineSize)
4600                                  {
4601                                     int c;
4602                                     BufferLocation before = { line, y, 0 }, after = { line, y, this.tabSize };
4603                                     NotifyCharsAdded(master, this, &before, &after, this.pasteOperation);
4604
4605                                     if(!line.AdjustBuffer(line.count+this.tabSize))
4606                                        break;
4607
4608                                     {
4609                                        char * string = new char[this.tabSize + 1];
4610                                        memset(string, ' ', this.tabSize);
4611                                        string[this.tabSize] = '\0';
4612                                        Record(AddTextAction { string = string, x1 = 0, y1 = y, x2 = this.tabSize, y2 = y });
4613                                     }
4614
4615                                     memmove(line.buffer+this.tabSize,line.buffer,line.size-(this.tabSize));
4616                                     line.count+=this.tabSize;
4617                                     for(c=0; c<this.tabSize; c++)
4618                                        line.buffer[c] = ' ';
4619                                  }
4620                               }
4621                               DirtyLine(y);
4622                            }
4623                         }
4624                         if(line == lastLine) break;
4625                      }
4626                   }
4627                   ComputeLength(maxLine);
4628                }
4629                else
4630                {
4631                   if(style.useTab)
4632                   {
4633                      // Insert character
4634                      AddCh('\t');
4635                   }
4636                   else
4637                   {
4638                      int start, c;
4639                      char * addString = new char[this.tabSize + 1];
4640                      int len = 0;
4641                      if(!(style.freeCaret))
4642                      {
4643                         this.x = Min(this.x, this.line.count);
4644                         ComputeColumn();
4645                      }
4646                      // Insert spaces
4647                      start = Min(this.x, this.selX);
4648                      for(c=start; ((c == start) || ((c) % this.tabSize)); c++)
4649                      {
4650                         addString[len++] = ' ';
4651                      }
4652                      addString[len] = 0;
4653                      AddS(addString);
4654                      delete addString;
4655                   }
4656                }
4657                Modified();
4658                SetViewToCursor(true);
4659                return false;
4660             }
4661             break;
4662          case pageDown:
4663             if(style.multiLine)
4664             {
4665                if(key.ctrl)
4666                {
4667                   if(!(style.hScroll) || hasHorzScroll) break;
4668                   if(this.viewX < this.maxLength)
4669                   {
4670                      //this.viewX+=this.space.w*this.tabSize;
4671                      //DirtyAll();
4672                      SetScrollPosition((this.viewX + this.space.w*this.tabSize), this.viewY * this.space.h);
4673                   }
4674                }
4675                else
4676                {
4677                   PageDown();
4678                   DirtyAll();
4679                   if(!shift) _Deselect();
4680                   SetCursorToViewX();
4681                   SetCursorToViewY();
4682                }
4683                return false;
4684             }
4685             break;
4686          case pageUp:
4687             if(style.multiLine)
4688             {
4689                if(key.ctrl)
4690                {
4691                   if(!(style.hScroll) || hasHorzScroll) break;
4692                   if(this.viewX > 0)
4693                   {
4694                      //this.viewX-=this.space.w*this.tabSize;
4695                      //this.viewX = Max(this.viewX,0);
4696                      //DirtyAll();
4697                      SetScrollPosition((this.viewX-this.space.w*this.tabSize), this.viewY * this.space.h);
4698                      // SetCursorToView();
4699                   }
4700                }
4701                else
4702                {
4703                   PageUp();
4704                   DirtyAll();
4705                   if(!shift) _Deselect();
4706                   SetCursorToViewX();
4707                   SetCursorToViewY();
4708                }
4709                return false;
4710             }
4711             break;
4712          case insert:
4713             if(key.ctrl)
4714             {
4715                Copy();
4716                return false;
4717             }
4718             else if(!style.readOnly)
4719             {
4720                if(key.shift)
4721                {
4722                   if(!(style.readOnly))
4723                      Paste();
4724                   return false;
4725                }
4726                else
4727                {
4728                   this.overwrite ^= 1;
4729                   UpdateCaretPosition(true);
4730                   if(this.overwrite)
4731                      SetCaret(0,0,0);
4732                   DirtyLine(this.y);
4733                   UpdateDirty();
4734                   NotifyOvrToggle(master, this, this.overwrite);
4735                }
4736             }
4737             break;
4738          case hotKey:
4739             break;
4740          default:
4741
4742             switch(key)
4743             {
4744                case ctrlA:
4745                   // Select All
4746                   if(style.noSelect) break;
4747
4748                   {
4749                      //Save current view position
4750                      int tempX = this.viewX;
4751                      int tempY = this.viewY;
4752
4753                      this.selX = 0;
4754                      this.selY = 0;
4755                      this.selLine = this.lines.first;
4756                      this.y = this.lineCount-1;
4757                      this.line = this.lines.last;
4758                      this.x = this.line.count;
4759                      ComputeColumn();
4760                      DirtyAll();
4761                      SetViewToCursor(true);
4762
4763                      //Restore previous view position
4764                      SetScrollPosition(tempX, tempY * this.space.h);
4765
4766                      UpdateDirty();
4767                      SetSelectCursor();
4768                      SelectionEnables();
4769                   }
4770                   // TOCHECK: Was there any good reason why we weren't returning false here?
4771                   return false; // break;
4772                case ctrlC:
4773                   Copy();
4774                   return false;
4775                case ctrlV:
4776                   if(!(style.readOnly))
4777                   {
4778                      Paste();
4779                      return false;
4780                   }
4781                   break;
4782                case ctrlX:
4783                {
4784                   if(style.readOnly) break;
4785                   Cut();
4786                   return false;
4787                }
4788                case ctrlL:
4789                {
4790                   if(style.readOnly) break;
4791                   ClearLine();
4792                   return false;
4793                }
4794                case ctrlZ:
4795                   if(style.readOnly) break;
4796                   Undo();
4797                   return false;
4798                case ctrlY:
4799                   if(style.readOnly) break;
4800                   Redo();
4801                   return false;
4802                default:
4803                   if(style.readOnly) break;
4804                   if(key.shift && key.code == rightBracket)
4805                   {
4806                      //Only indent back if you are exactly at one tab.
4807                      {
4808                         bool whitespace = true;
4809                         int i;
4810                         char * newline;
4811                         int putsize;
4812
4813                         int indentwidth;
4814                         EditLine line = this.line;
4815
4816                         //Only remove one tab if there is nothing else on the line.
4817                         for(i = 0; i < this.line.count; i++)
4818                         {
4819                            if(this.line.buffer[i] != ' ' && this.line.buffer[i] != '\t')
4820                            {
4821                               PutCh(ch);
4822                               return false;
4823                            }
4824                         }
4825
4826                         //Find opening {
4827                         i = 1;
4828                         while(i && line)
4829                         {
4830                            int pos;
4831
4832                            indentwidth = 0;
4833                            for(pos = line.count - 1; pos >= 0; pos--)
4834                            {
4835                               char c = line.buffer[pos];
4836                               if(c == ' ')
4837                                  indentwidth++;
4838                               else if(c == '\t')
4839                                  indentwidth += this.tabSize;
4840                               else
4841                                  //Counting backwards, so when you find a character, indentation is reset
4842                                  indentwidth = 0;
4843                               if(i && c == '}')
4844                                  i++;
4845                               if(i && c == '{')
4846                                  i--;
4847                            }
4848                            line = line.prev;
4849                         }
4850
4851                         //Place the } to get an undo:
4852                         PutCh(ch);
4853
4854                         this.x = this.line.count;
4855                         this.selX = 0;
4856
4857                         if(!style.useTab)
4858                            putsize = indentwidth;
4859                         else
4860                            putsize = indentwidth / this.tabSize + indentwidth % this.tabSize;
4861
4862                         newline = new char[putsize+2];
4863                         newline[putsize] = '}';
4864                         newline[putsize+1] = '\0';
4865
4866                         i = 0;
4867                         if(style.useTab)
4868                            for(; i < indentwidth / this.tabSize; i++)
4869                               newline[i] = '\t';
4870                         for(;i < putsize; i++)
4871                            newline[i] = ' ';
4872
4873                         AddS(newline);
4874
4875                         delete newline;
4876                      }
4877                      return false;
4878                   }
4879                   else if(!key.ctrl && !key.alt && ch != 128 && ch >= 32)
4880                   {
4881                      PutCh(ch);
4882                      return false;
4883                   }
4884                   break;
4885             }
4886             break;
4887       }
4888       return true;
4889    }
4890
4891    void OnHScroll(ScrollBarAction action, int position, Key key)
4892    {
4893 #ifdef _DEBUG
4894       //PrintLn("OnHScroll: ", action, ", pos = ", position, ", key = ", key);
4895 #endif
4896       this.viewX = position;
4897       if(action != setRange)
4898       {
4899          if(!this.mouseMove && style.cursorFollowsView)
4900             SetCursorToViewX();
4901       }
4902       DirtyAll();
4903       UpdateDirty();
4904    }
4905
4906    void OnVScroll(ScrollBarAction action, int position, Key key)
4907    {
4908       int oldViewY = this.viewY;
4909
4910 #ifdef _DEBUG
4911       //PrintLn("OnVScroll: ", action, ", pos = ", position, ", key = ", key);
4912 #endif
4913       position /= this.space.h;
4914
4915       if(position < this.viewY)
4916       {
4917          for(; position < this.viewY && this.viewLine.prev; this.viewLine = this.viewLine.prev, this.viewY--);
4918          style.recomputeSyntax = true;
4919       }
4920       else if(position > this.viewY)
4921       {
4922          EditLine oldViewLine = viewLine;
4923          for(; position > this.viewY && this.viewLine.next; this.viewLine = this.viewLine.next, this.viewY++);
4924          FigureStartSyntaxStates(oldViewLine, false);
4925       }
4926
4927       if(action != setRange)
4928       {
4929          if(!this.mouseMove && style.cursorFollowsView && !SelSize()) SetCursorToViewY();
4930       }
4931
4932       if(this.x != this.selX || this.y != this.selY)
4933          DirtyLine(this.y);
4934
4935       {
4936          Scroll(0, (this.viewY - oldViewY) * this.space.h);
4937
4938          /*
4939          int numLines = clientSize.h / this.space.h;
4940          int y;
4941          if(Abs(this.viewY - oldViewY) < numLines)
4942
4943
4944          if(this.viewY > oldViewY)
4945          {
4946             for(y = oldViewY; y <this.viewY; y++)
4947                DirtyLine(y + numLines);
4948          }
4949          else
4950          {
4951             for(y = this.viewY; y <oldViewY; y++)
4952                DirtyLine(y + numLines);
4953          }*/
4954       }
4955       //DirtyAll();
4956
4957       // Fix dirt of stuff before first line...
4958       if(this.viewY - oldViewY > 0)
4959       {
4960          Box box { 0,0, clientSize.w-1, YOFFSET-1 };
4961          Update(box);
4962       }
4963
4964       UpdateDirty();
4965    }
4966
4967    bool _AddCh(unichar ch, int * addedSpacesPtr, int * addedTabsPtr)
4968    {
4969       EditLine line;
4970       int length, endX;
4971       bool result;
4972       ReplaceTextAction replaceAction = null;
4973       AddCharAction addCharAction = null;
4974       int addedSpaces = 0, addedTabs = 0;
4975
4976       if(ch == '\r') return true;
4977       if(style.stuckCaret /*|EES_READONLY)*/ )
4978          GoToEnd(true);
4979
4980       if(ch == '\n' && !(style.multiLine) && this.line) return false;
4981
4982       if(!undoBuffer.dontRecord)
4983       {
4984          if(selX != x || selY != y)
4985          {
4986             char buffer[5];
4987             char * newString;
4988             char * oldString;
4989             int len = GetText(null, selLine, selY, selX, this.line, y, x, false, false);
4990             oldString = new char[len];
4991             UTF32toUTF8Len(&ch, 1, buffer, 4);
4992             newString = CopyString(buffer);
4993             GetText(oldString, selLine, selY, selX, this.line, y, x, false, false);
4994
4995             replaceAction = ReplaceTextAction { newString = newString, oldString = oldString, placeAfter = false };
4996             if(selY < y || (selY == y && selX < x))
4997             {
4998                replaceAction.x1 = selX;
4999                replaceAction.y1 = selY;
5000                replaceAction.x2 = x;
5001                replaceAction.y2 = y;
5002             }
5003             else
5004             {
5005                replaceAction.x1 = x;
5006                replaceAction.y1 = y;
5007                replaceAction.x2 = selX;
5008                replaceAction.y2 = selY;
5009             }
5010             Record(replaceAction);
5011             undoBuffer.dontRecord++;
5012          }
5013          else
5014          {
5015             addCharAction = AddCharAction { y = y, x = x, ch = ch };
5016             Record(addCharAction);
5017          }
5018       }
5019
5020       if(ch == '\n')
5021       {
5022          DelSel(&addedSpaces);
5023          if(this.lineCount+1 > this.maxLines)
5024          {
5025             if(style.autoEmpty)
5026                Emptyline(this.lines.first,0);
5027             else
5028                return false;
5029          }
5030          if(!(style.autoSize && (!maxClientSize.h || maxClientSize.h > clientSize.h + this.space.h)) && !(style.vScroll))
5031          {
5032             // Make sure it fits, but we need a default line is this.font is too big for window
5033             if(this.space.h * (this.lineCount+1) > clientSize.h && this.line)
5034                return false;
5035          }
5036          if((this.y >= 0) && this.y < this.viewY)
5037          {
5038             this.viewY++;
5039             DirtyAll();
5040          }
5041          line = EditLine { };
5042          if(!line)
5043             return false;
5044          lines.Insert(this.line, line);
5045          line.editBox = this;
5046          line.buffer = null;
5047          line.count = 0;
5048          line.size = 0;
5049          length = 0;
5050
5051          // If we're displacing the lines from a current line ...
5052          if(this.line)
5053          {
5054             if(this.line.buffer)
5055             {
5056                endX = this.x;
5057                if(this.line.count < endX) endX = this.line.count;
5058                length = this.line.count - endX;
5059             }
5060          }
5061          if(!line.AdjustBuffer(length))
5062             return false;
5063          if(this.line)
5064          {
5065             if(this.line.buffer)
5066             {
5067                CopyBytes(line.buffer,this.line.buffer+endX, length+1);
5068 #ifdef _DEBUG
5069       if(endX > 4000 || endX < 0)
5070          printf("Warning");
5071 #endif
5072                this.line.count = endX;
5073                this.line.buffer[this.line.count] = '\0';
5074                this.line.AdjustBuffer(this.line.count);
5075                ComputeLength(this.line);
5076             }
5077          }
5078          {
5079             BufferLocation before = { this.line, this.y, this.x }, after;
5080
5081             this.line = line;
5082             this.x = 0;
5083             this.col = 0;
5084             line.count = length;
5085
5086 #ifdef _DEBUG
5087       if(length > 4000 || length < 0)
5088          printf("Warning");
5089 #endif
5090             ComputeLength(this.line);
5091
5092             DirtyEnd(this.y);
5093             this.y++;
5094             this.lineCount++;
5095             line.buffer[line.count] = '\0';
5096             result = true;
5097
5098             after.line = this.line, after.y = this.y, after.x = this.x;
5099
5100             NotifyCharsAdded(master, this, &before, &after, this.pasteOperation);
5101          }
5102       }
5103       else
5104       {
5105          char string[5];
5106          int count = UTF32toUTF8Len(&ch, 1, string, 5);
5107          DelSel(&addedSpaces);
5108          result = AddToLine(string, count, false, addedSpaces ? null : &addedSpaces, &addedTabs);
5109          if(addedSpacesPtr) *addedSpacesPtr = addedSpaces;
5110          if(addedTabsPtr) *addedTabsPtr = addedTabs;
5111       }
5112       this.selX = this.x;
5113       this.selY = this.y;
5114       this.selLine = this.line;
5115
5116       if(replaceAction)
5117       {
5118          replaceAction.x3 = x;
5119          replaceAction.y3 = y;
5120          replaceAction.addedSpaces = addedSpaces;
5121          replaceAction.addedTabs = addedTabs;
5122          undoBuffer.dontRecord--;
5123       }
5124       if(addCharAction)
5125       {
5126          addCharAction.x -= addedTabs * (tabSize-1);
5127          addCharAction.addedSpaces = addedSpaces;
5128          addCharAction.addedTabs = addedTabs;
5129       }
5130       return result;
5131    }
5132
5133 public:
5134
5135    /****************************************************************************
5136                                  EDIT BOX METHODS
5137    ****************************************************************************/
5138
5139
5140    bool AddCh(unichar ch)
5141    {
5142       return _AddCh(ch, null, null);
5143    }
5144
5145    void Modified()
5146    {
5147       this.modified = true;
5148       NotifyUpdate(master, this);
5149       modifiedDocument = true;
5150    }
5151
5152    void Delete(EditLine line1, int y1, int x1, EditLine line2, int y2, int x2)
5153    {
5154       Deselect();
5155       _DelCh(line1, y1, x1, line2, y2, x2, false, false, null);
5156       SetViewToCursor(true);
5157       UpdateDirty();
5158       Modified();
5159    }
5160
5161    void Undo()
5162    {
5163       undoBuffer.Undo();
5164       itemEditUndo.disabled = undoBuffer.curAction == 0;
5165       itemEditRedo.disabled = undoBuffer.curAction == undoBuffer.count;
5166       if(savedAction == undoBuffer.curAction)
5167       {
5168          modifiedDocument = false;
5169          SetModified(false);
5170          NotifyUnsetModified(master, this);
5171       }
5172    }
5173
5174    void Redo()
5175    {
5176       undoBuffer.Redo();
5177       itemEditUndo.disabled = undoBuffer.curAction == 0;
5178       itemEditRedo.disabled = undoBuffer.curAction == undoBuffer.count;
5179       if(savedAction == undoBuffer.curAction)
5180       {
5181          modifiedDocument = false;
5182          SetModified(false);
5183          NotifyUnsetModified(master, this);
5184       }
5185    }
5186
5187    void Record(UndoAction action)
5188    {
5189       if(!undoBuffer.dontRecord)
5190       {
5191          undoBuffer.Record(action);
5192          itemEditUndo.disabled = undoBuffer.curAction == 0;
5193          itemEditRedo.disabled = undoBuffer.curAction == undoBuffer.count;
5194       }
5195    }
5196
5197    void SelectAll()
5198    {
5199       Select(
5200          this.lines.first, 0,0,
5201          this.lines.last, this.lines.count-1, strlen(((EditLine)this.lines.last).buffer));
5202    }
5203
5204    void Select(EditLine line1, int y1, int x1, EditLine line2, int y2, int x2)
5205    {
5206       SelDirty();
5207       this.selX = x1;
5208       this.selY = y1;
5209       this.selLine = line1 ? (EditLine)line1 : this.lines.first;
5210       this.x = line2 ? x2 : ((EditLine)this.lines.last).count;
5211       this.y = line2 ? y2 : (this.lineCount-1);
5212       this.line = line2 ? (EditLine)line2 : this.lines.last;
5213       ComputeColumn();
5214       SelDirty();
5215       SetViewToCursor(true);
5216       UpdateDirty();
5217    }
5218
5219    // TODO: Fix this vs modifiedDocument window property
5220    void SetModified(bool flag)
5221    {
5222       if(this)
5223       {
5224          this.modified = false;
5225          if(flag && !NotifyModified(master, this))
5226             this.modified = true;
5227       }
5228    }
5229
5230    // BASIC OUTPUT
5231    bool AddS(char * string)
5232    {
5233       if(this)
5234       {
5235          bool ret = true;
5236          char * line;
5237          int c, count;
5238          int addedSpaces = 0, addedTabs = 0;
5239          AddTextAction action = null;
5240          ReplaceTextAction replaceAction = null;
5241
5242          this.pasteOperation = true;
5243
5244          if(style.stuckCaret /*|EES_READONLY)*/ )
5245             GoToEnd(true);
5246
5247          if(!undoBuffer.dontRecord)
5248          {
5249             char * placeString = CopyString(string);
5250             if(!style.multiLine)
5251             {
5252                int i;
5253                char ch;
5254                for(i = 0; (ch = placeString[i]); i++)
5255                   if(ch == '\n')
5256                   {
5257                      placeString[i] = '\0';
5258                      placeString = renew placeString byte[i+1];
5259                      break;
5260                   }
5261             }
5262
5263             if(selX != x || selY != y)
5264             {
5265                char * newString;
5266                char * oldString;
5267                int len = GetText(null, selLine, selY, selX, this.line, y, x, false, false);
5268                oldString = new char[len];
5269                newString = placeString;
5270                GetText(oldString, selLine, selY, selX, this.line, y, x, false, false);
5271
5272                replaceAction = ReplaceTextAction { newString = newString, oldString = oldString, placeAfter = false };
5273                if(selY < y || (selY == y && selX < x))
5274                {
5275                   replaceAction.x1 = selX;
5276                   replaceAction.y1 = selY;
5277                   replaceAction.x2 = x;
5278                   replaceAction.y2 = y;
5279                }
5280                else
5281                {
5282                   replaceAction.x1 = x;
5283                   replaceAction.y1 = y;
5284                   replaceAction.x2 = selX;
5285                   replaceAction.y2 = selY;
5286                }
5287                Record(replaceAction);
5288             }
5289             else if(string[0])
5290             {
5291                if(string[0] == '\n')
5292                   action = AddTextAction { y1 = y, x1 = Min(this.line.count, x), string = placeString };
5293                else
5294                   action = AddTextAction { y1 = y, x1 = x, string = placeString };
5295
5296                Record(action);
5297             }
5298             else
5299                delete placeString;
5300          }
5301
5302          undoBuffer.dontRecord++;
5303          DelSel(&addedSpaces);
5304
5305          count = 0;
5306          line = string;
5307          for(c = 0; string[c]; c++)
5308          {
5309             if(string[c] == '\n' || string[c] == '\r')
5310             {
5311                if(!AddToLine(line,count, true, addedSpaces ? null : &addedSpaces, addedTabs ? null : &addedTabs))
5312                {
5313                   ret = false;
5314                   break;
5315                }
5316                if(string[c] == '\n')
5317                {
5318                   if(!AddCh('\n'))
5319                   {
5320                      count = 0;
5321                      ret = false;
5322                      break;
5323                   }
5324                }
5325                // Reset for next line
5326                count = 0;
5327                line = string+c+1;
5328                /*
5329                if(string[c] == '\r' && *line == '\n')
5330                {
5331                   line++;
5332                   c++;
5333                }*/
5334             }
5335             else
5336             {
5337                count++;
5338             }
5339          }
5340
5341          // Why was this here?
5342          // FindMaxLine();
5343
5344          // Add the line here
5345          if(ret && count)
5346             if(!AddToLine(line,count,false, addedSpaces ? null : &addedSpaces, addedTabs ? null : &addedTabs))
5347             {
5348                ret = false;
5349             }
5350          FindMaxLine();
5351
5352          undoBuffer.dontRecord--;
5353          if(action)
5354          {
5355             action.y2 = y;
5356             action.x2 = x;
5357             action.addedSpaces = addedSpaces;
5358             action.addedTabs = addedTabs;
5359          }
5360          else if(replaceAction)
5361          {
5362             replaceAction.y3 = y;
5363             replaceAction.x3 = x;
5364             replaceAction.addedSpaces = addedSpaces;
5365             replaceAction.addedTabs = addedTabs;
5366          }
5367
5368          UpdateCaretPosition(true);
5369
5370          this.pasteOperation = false;
5371
5372          return ret;
5373       }
5374       return false;
5375    }
5376
5377    // CONSOLE OUTPUT
5378    void Clear()
5379    {
5380       if(this)
5381       {
5382          Deselect();
5383          DelCh(this.lines.first, 0, 0, this.lines.last, this.lineCount-1, ((EditLine)(this.lines.last)).count, true);
5384          SetViewToCursor(true);
5385          UpdateDirty();
5386          Modified();
5387       }
5388    }
5389
5390    void PutCh(unichar ch)
5391    {
5392       bool result;
5393
5394       if((ch >= 32 /*&& ch <=126*/) || ch == '\n')
5395       //if((ch >= 32) || ch == '\n')
5396       {
5397          int addedSpaces = 0, addedTabs = 0;
5398          ReplaceTextAction replaceAction = null;
5399          AddCharAction addCharAction = null;
5400
5401          if(style.allCaps)
5402             ch = (ch < 128) ? toupper(ch) : ch;     // TODO: UNICODE TO UPPER
5403
5404          if(this.overwrite && selX == x && selY == y && this.x < this.line.count)
5405          {
5406             char buffer[5];
5407             char * newString;
5408             char * oldString;
5409             int len = GetText(null, line, y, x, line, y, x+1, false, false);
5410             oldString = new char[len];
5411             UTF32toUTF8Len(&ch, 1, buffer, 4);
5412             newString = CopyString(buffer);
5413             GetText(oldString, line, y, x, line, y, x+1, false, false);
5414             replaceAction = ReplaceTextAction { newString = newString, oldString = oldString, placeAfter = false };
5415             replaceAction.x1 = x;
5416             replaceAction.y1 = y;
5417             replaceAction.x2 = x+1;
5418             replaceAction.y2 = y;
5419             Record(replaceAction);
5420
5421             undoBuffer.dontRecord++;
5422             DelCh(this.line, this.y, this.x, this.line, this.y, this.x+1, true);
5423             undoBuffer.dontRecord--;
5424          }
5425          else if(!undoBuffer.dontRecord)
5426          {
5427             if(selX != x || selY != y)
5428             {
5429                char buffer[5];
5430                char * newString;
5431                char * oldString;
5432                int len = GetText(null, line, y, x, selLine, selY, selX, false, false);
5433                oldString = new char[len];
5434                UTF32toUTF8Len(&ch, 1, buffer, 4);
5435                newString = CopyString(buffer);
5436                GetText(oldString, line, y, x, selLine, selY, selX, false, false);
5437                replaceAction = ReplaceTextAction { newString = newString, oldString = oldString, placeAfter = true };
5438                if(selY < y || (selY == y && selX < x))
5439                {
5440                   replaceAction.x1 = selX;
5441                   replaceAction.y1 = selY;
5442                   replaceAction.x2 = x;
5443                   replaceAction.y2 = y;
5444                }
5445                else
5446                {
5447                   replaceAction.x1 = x;
5448                   replaceAction.y1 = y;
5449                   replaceAction.x2 = selX;
5450                   replaceAction.y2 = selY;
5451                }
5452                Record(replaceAction);
5453             }
5454             else
5455             {
5456                addCharAction = AddCharAction { y = y, x = x, ch = ch };
5457                Record(addCharAction);
5458             }
5459          }
5460          undoBuffer.dontRecord++;
5461          result = _AddCh(ch, &addedSpaces, &addedTabs);
5462          if(replaceAction)
5463          {
5464             replaceAction.x3 = x;
5465             replaceAction.y3 = y;
5466             replaceAction.addedSpaces = addedSpaces;
5467             replaceAction.addedTabs = addedTabs;
5468          }
5469          if(addCharAction)
5470          {
5471             addCharAction.x -= addedTabs * (tabSize-1);
5472             addCharAction.addedSpaces = addedSpaces;
5473             addCharAction.addedTabs = addedTabs;
5474          }
5475          undoBuffer.dontRecord--;
5476          if(ch == '\n')
5477             FindMaxLine();
5478          Modified();
5479          if(result) SetViewToCursor(true);
5480       }
5481    }
5482
5483    void PutS(char * string)
5484    {
5485       if(this)
5486       {
5487          AddS(string);
5488          SetViewToCursor(true);
5489          Modified();
5490       }
5491    }
5492
5493    void Printf(char * format, ...)
5494    {
5495       if(this)
5496       {
5497          char temp[MAX_F_STRING];
5498          va_list args;
5499          va_start(args, format);
5500          vsnprintf(temp, sizeof(temp), format, args);
5501          temp[sizeof(temp)-1] = 0;
5502          va_end(args);
5503          PutS(temp);
5504       }
5505    }
5506
5507    void SetContents(char * format, ...)
5508    {
5509       if(this)
5510       {
5511          undoBuffer.dontRecord++;
5512          Deselect();
5513          DelCh(this.lines.first, 0, 0, this.lines.last, this.lineCount-1, ((EditLine)(this.lines.last)).count, true);
5514          if(format)
5515          {
5516             char temp[MAX_F_STRING];
5517             va_list args;
5518             va_start(args, format);
5519             vsnprintf(temp, sizeof(temp), format, args);
5520             temp[sizeof(temp)-1] = 0;
5521             va_end(args);
5522
5523             AddS(temp);
5524          }
5525          UpdateDirty();
5526          Home();
5527          undoBuffer.dontRecord--;
5528       }
5529    }
5530
5531    void BackSpace()
5532    {
5533       if(!DelSel(null))
5534       {
5535          if(x > 0)
5536          {
5537             if(x > line.count)
5538                x--;
5539             else
5540                x -= 1 + _DelCh(line, y, x-1, line, y, x, true, false, null);
5541             Modified();
5542          }
5543          else if(this.line.prev)
5544          {
5545             EditLine line = this.line.prev;
5546             int x = line.count;
5547             int y = this.y;
5548
5549             _DelCh(line, this.y-1, x, this.line, this.y, this.x, true, false, null);
5550             this.line = line;
5551             this.y = y-1;
5552             this.x = x;
5553             Modified();
5554          }
5555          this.selX = this.x;
5556          this.selY = this.y;
5557          this.selLine = this.line;
5558          ComputeColumn();
5559       }
5560       else
5561          Modified();
5562       SetViewToCursor(true);
5563    }
5564
5565    void ClearLine()
5566    {
5567       Emptyline(this.line,this.y);
5568       this.selX = this.x = 0;
5569       this.col = 0;
5570       this.selY = this.y;
5571       this.selLine = this.line;
5572
5573       SetViewToCursor(true);
5574       Modified();
5575    }
5576
5577    // CARET CONTROL
5578    void End()
5579    {
5580       if(this)
5581       {
5582          GoToEnd(true);
5583          SetViewToCursor(true);
5584       }
5585    }
5586    void Home()
5587    {
5588       if(this)
5589       {
5590          GoToHome(true);
5591          SetViewToCursor(true);
5592       }
5593    }
5594
5595    bool GoToLineNum(int lineNum)
5596    {
5597       if(this.line)
5598       {
5599          int c;
5600          EditLine line = this.lines.first;
5601          for(c = 0; c < lineNum && line; c++, line = line.next);
5602          if(line)
5603          {
5604             if(this.y != c)
5605                DirtyAll();
5606             else
5607                DirtyLine(c);
5608             this.y = c;
5609             this.line = line;
5610             _Deselect();
5611             SetViewToCursor(true);
5612             return true;
5613          }
5614       }
5615       return false;
5616    }
5617
5618    // NOTE: Mismatch with NotifyCaretMove() for x/y + 1
5619    bool GoToPosition(EditLine line, int y, int x)
5620    {
5621       /*
5622       if(!line)
5623       {
5624          line = this.line;
5625          y = this.y;
5626       }
5627       */
5628       if(!line)
5629       {
5630          int c;
5631          for(line = this.lines.first, c = 0; c<y && line; c++, line = line.next);
5632       }
5633
5634       if(line)
5635       {
5636          if(this.y != y)
5637             DirtyAll();
5638          else
5639             DirtyLine(y);
5640          this.x = x;
5641          this.y = y;
5642          this.line = line;
5643          ComputeColumn();
5644          _Deselect();
5645          SetViewToCursor(true);
5646          return true;
5647       }
5648       return false;
5649    }
5650
5651    // VIEW POSITIONING
5652    void SetViewToCursor(bool setCaret)
5653    {
5654       if(created)
5655       {
5656          int w;
5657          int c, numLines;
5658          EditLine line;
5659          int x;
5660          int checkX, checkY;
5661          EditLine checkLine;
5662          bool dontScroll = false;
5663          bool selected;
5664          int viewX, viewY;
5665
5666          FixScrollArea();
5667
5668          selected = selX != this.x || selY != y;
5669
5670          viewX = this.viewX;
5671          viewY = this.viewY;
5672
5673          if(mouseMove)
5674          {
5675             checkLine = dropLine;
5676             checkX = dropX;
5677             checkY = dropY;
5678          }
5679          else
5680          {
5681             checkLine = this.line;
5682             checkX = this.x;
5683             checkY = y;
5684          }
5685
5686          numLines = clientSize.h / space.h;
5687
5688          // This is broken. The EditBox now doesn't do anything different when adding to it,
5689          // regardless of the previous scrolling position. It should be read and then set again
5690          // if one wishes to preserve it.
5691          /*  // Don't scroll view to cursor if we're in a EES_NOCARET box
5692          if(style.noCaret && this.viewY < lineCount - numLines - 1)
5693             dontScroll = true;
5694          */
5695
5696          // Horizontal Adjustment
5697          if(!dontScroll && checkLine)
5698          {
5699             x = 0;
5700             if(mouseMove)
5701                dropX = AdjustXPosition(this.line, MAXINT, true, &x, checkX, 0);
5702             else
5703             {
5704                this.x = AdjustXPosition(this.line, MAXINT, true, &x, checkX, 0);
5705                ComputeColumn();
5706             }
5707
5708             if(style.hScroll)
5709             {
5710                if(x + space.w >= this.viewX + clientSize.w && clientSize.w >= space.w)
5711                   viewX = x - clientSize.w+space.w;
5712                if(x < this.viewX + clientSize.w/2 - space.w)
5713                   viewX = Max(0, x - clientSize.w/2 + space.w);
5714             }
5715          }
5716
5717          if(!dontScroll)
5718          {
5719             if(style.vScroll)
5720             {
5721                // Vertical Adjustment
5722                if(viewY > checkY) viewY = checkY;
5723                if(viewY + numLines <= checkY)
5724                {
5725                   if(clientSize.h >= space.h)
5726                   {
5727                      for(line = viewLine; line && (viewY + numLines <= checkY); line = line.next)
5728                         viewY++;
5729                   }
5730                   else
5731                      viewY = checkY;
5732                }
5733             }
5734             else
5735             {
5736                if(mouseMove)
5737                   for(;dropLine && dropLine.prev && dropY >= numLines;)
5738                   {
5739                      dropLine = dropLine.prev;
5740                      dropY--;
5741                   }
5742                else
5743                   for(;this.line && this.line.prev && this.y >= numLines;)
5744                   {
5745                      this.line = this.line.prev;
5746                      y--;
5747                   }
5748             }
5749
5750             SetScrollPosition(viewX, viewY * this.space.h);
5751
5752             UpdateCaretPosition(setCaret);
5753
5754             if(!selected)
5755             {
5756                selX = this.x;
5757                selY = this.y;
5758                selLine = this.line;
5759             }
5760          }
5761
5762          UpdateDirty();
5763          SetSelectCursor();
5764          SelectionEnables();
5765       }
5766    }
5767
5768    void CenterOnCursor()
5769    {
5770       int numLines = clientSize.h / this.space.h;
5771       int y = this.y - numLines / 2;
5772       int viewY;
5773       bool figureSyntax = false;
5774       EditLine oldViewLine = viewLine;
5775       if(y > this.lineCount - numLines) y = this.lineCount-numLines;
5776       if(y < 0) y = 0;
5777
5778       viewY = y;
5779
5780       for(;y < this.viewY; y++)
5781       {
5782          this.viewLine = this.viewLine.prev;
5783          style.recomputeSyntax = true;
5784       }
5785       for(;y > this.viewY; y--)
5786       {
5787          this.viewLine = this.viewLine.next;
5788          figureSyntax = true;
5789       }
5790       if(figureSyntax)
5791          FigureStartSyntaxStates(oldViewLine, false);
5792
5793       this.viewY = viewY;
5794
5795       SetScrollPosition(this.viewX, viewY * this.space.h);
5796       UpdateCaretPosition(true);
5797       UpdateDirty();
5798    }
5799
5800    void SetCursorToView()
5801    {
5802       SetCursorToViewX();
5803       SetCursorToViewY();
5804    }
5805
5806    void PageDown()
5807    {
5808       int c, numLines;
5809       EditLine line;
5810
5811       numLines = clientSize.h / this.space.h;
5812
5813       if(style.noCaret)
5814       {
5815          for(c=0, line = this.viewLine.next; line && c<numLines && (this.viewY < this.lineCount - numLines); line = line.next, c++);
5816          SetScrollPosition(this.viewX, (this.viewY + c) * this.space.h);
5817       }
5818       else
5819       {
5820          EditLine oldLine = this.line;
5821          bool lastOne = false;
5822          EditLine oldViewLine = this.viewLine;
5823          bool figureSyntax = false;
5824
5825          if(this.y >= this.lineCount-1) return;
5826
5827          for(c=0, line = this.line.next; line && c<numLines; line = line.next, c++)
5828          {
5829             if(this.viewY + numLines < this.lines.count)
5830             {
5831                this.viewLine = this.viewLine.next;
5832                this.viewY++;
5833                figureSyntax = true;
5834             }
5835             else if(c && !lastOne)
5836                break;
5837             else
5838                lastOne = true;
5839
5840             this.line = line;
5841             this.y++;
5842          }
5843          if(figureSyntax)
5844             FigureStartSyntaxStates(oldViewLine, false);
5845          this.x = AdjustXPosition(line, caretX, true, null, MAXINT, 0);
5846          ComputeColumn();
5847
5848          SetViewToCursor(false);
5849       }
5850    }
5851
5852    void PageUp()
5853    {
5854       int c, numLines;
5855       EditLine line;
5856
5857       if(this.y == 0) return;
5858
5859       numLines = clientSize.h / this.space.h;
5860
5861       if(style.noCaret)
5862       {
5863          for(c=0, line = this.viewLine.prev; line && c<numLines; line = line.prev, c++);
5864          SetScrollPosition(this.viewX, (this.viewY - c) * this.space.h);
5865       }
5866       else
5867       {
5868          EditLine oldLine = this.line;
5869
5870          for(c=0, line = this.line.prev; line && c<numLines; line = line.prev, c++)
5871          {
5872             this.line = line;
5873             this.y--;
5874
5875             if(this.viewLine.prev)
5876             {
5877                this.viewLine = this.viewLine.prev;
5878                this.viewY--;
5879                style.recomputeSyntax = true;
5880             }
5881          }
5882
5883          this.x = AdjustXPosition(line, caretX, true, null, MAXINT, 0);
5884          ComputeColumn();
5885
5886          SetViewToCursor(false);
5887       }
5888    }
5889
5890    void LineUp()
5891    {
5892
5893       if(this.viewLine.prev)
5894       {
5895          //DirtyAll();
5896          // this.viewLine = this.viewLine.prev;
5897          //this.viewY--;
5898
5899          SetScrollPosition(this.viewX, (this.viewY - 1) * this.space.h);
5900       }
5901    }
5902
5903
5904    void LineDown()
5905    {
5906       if(this.viewLine.next)
5907       {
5908          //DirtyAll();
5909          // this.viewLine = this.viewLine.next;
5910          // this.viewY++;
5911
5912          SetScrollPosition(this.viewX, (this.viewY + 1) * this.space.h);
5913       }
5914    }
5915
5916    // Selection
5917    uint SelSize()
5918    {
5919       EditLine l1, l2, line;
5920       int x1, x2, nx1, nx2;
5921       int count;
5922       int start, end;
5923       int size;
5924
5925       if(!this.selLine) return 0;
5926       if(this.selLine == this.line && this.selX == this.x) return 0;
5927       if(this.selY < this.y)
5928       {
5929          l1 = this.selLine;
5930          x1 = this.selX;
5931          l2 = this.line;
5932          x2 = this.x;
5933       }
5934       else if(this.selY > this.y)
5935       {
5936          l1 = this.line;
5937          x1 = this.x;
5938          l2 = this.selLine;
5939          x2 = this.selX;
5940       }
5941       else if(this.selX < this.x)
5942       {
5943          l1 = l2 = this.line;
5944          x1 = this.selX;
5945          x2 = this.x;
5946       }
5947       else
5948       {
5949          l1 = l2 = this.line;
5950          x1 = this.x;
5951          x2 = this.selX;
5952       }
5953       nx1 = Min(x1,l1.count);
5954       nx2 = Min(x2,l2.count);
5955
5956       // Find Number of Bytes Needed
5957       size = 0;
5958       for(line = l1; line; line = line.next)
5959       {
5960          if(line == l1) start = nx1; else start = 0;
5961          if(line == l2) end = nx2; else end = line.count;
5962          size += end-start;
5963          if(style.freeCaret && line == l2)
5964          {
5965             if(l1 == l2)
5966                count = Max(x2-Max(x1,l1.count),0);
5967             else
5968                count = Max(x2-l2.count,0);
5969             size+=count;
5970          }
5971
5972          if(line == l2) break;
5973          // Add Carriage Return / line Feed
5974          size++;
5975          size++;
5976       }
5977       return size;
5978    }
5979
5980    void GetSelPos(EditLine * l1, int *y1, int *x1, EditLine * l2, int * y2, int * x2, bool reorder)
5981    {
5982       if(this)
5983       {
5984          if(!reorder || this.selY < this.y)
5985          {
5986             if(l1) *l1 = this.selLine;
5987             if(y1) *y1 = this.selY;
5988             if(x1) *x1 = this.selX;
5989             if(l2) *l2 = this.line;
5990             if(y2) *y2 = this.y;
5991             if(x2) *x2 = this.x;
5992          }
5993          else if(this.selY > this.y)
5994          {
5995             if(l1) *l1 = this.line;
5996             if(y1) *y1 = this.y;
5997             if(x1) *x1 = this.x;
5998             if(l2) *l2 = this.selLine;
5999             if(y2) *y2 = this.selY;
6000             if(x2) *x2 = this.selX;
6001          }
6002          else if(this.selX < this.x)
6003          {
6004             if(l1) *l1 = this.line;
6005             if(y1) *y1 = this.selY;
6006             if(x1) *x1 = this.selX;
6007             if(l2) *l2 = this.line;
6008             if(y2) *y2 = this.y;
6009             if(x2) *x2 = this.x;
6010          }
6011          else
6012          {
6013             if(l1) *l1 = this.line;
6014             if(y1) *y1 = this.y;
6015             if(x1) *x1 = this.x;
6016             if(l2) *l2 = this.line;
6017             if(y2) *y2 = this.selY;
6018             if(x2) *x2 = this.selX;
6019          }
6020       }
6021    }
6022
6023    void SetSelPos(EditLine l1, int y1, int x1, EditLine l2, int y2, int x2)
6024    {
6025       if(this && (this.selY != y1 || this.selX != x1 || this.y != y2 || this.x != x2))
6026       {
6027          this.selLine = (EditLine)l1;
6028          this.selY = y1;
6029          this.selX = x1;
6030          this.line = (EditLine)l2;
6031          this.y = y2;
6032          this.x = x2;
6033          ComputeColumn();
6034          SetViewToCursor(true);
6035       }
6036    }
6037
6038    int GetText(char * text, EditLine _l1, int _y1, int _x1, EditLine _l2, int _y2, int _x2, bool addCr, bool addSpaces)
6039    {
6040       EditLine l1, l2, line;
6041       int x1, x2, nx1, nx2;
6042       int count;
6043       int start, end;
6044       int numChars = 0;
6045
6046       if(_y2 < _y1)
6047       {
6048          l1 = _l2;
6049          x1 = _x2;
6050          l2 = _l1;
6051          x2 = _x1;
6052       }
6053       else if(_y2 > _y1)
6054       {
6055          l1 = _l1;
6056          x1 = _x1;
6057          l2 = _l2;
6058          x2 = _x2;
6059       }
6060       else if(_x2 < _x1)
6061       {
6062          l1 = l2 = _l1;
6063          x1 = _x2;
6064          x2 = _x1;
6065       }
6066       else
6067       {
6068          l1 = l2 = _l1;
6069          x1 = _x1;
6070          x2 = _x2;
6071       }
6072       nx1 = Min(x1,l1.count);
6073       nx2 = Min(x2,l2.count);
6074
6075       // Copy text
6076       for(line = l1; line; line = line.next)
6077       {
6078          if(line == l1) start = nx1; else start = 0;
6079          if(line == l2) end = nx2; else end = line.count;
6080          if(text)
6081          {
6082             CopyBytes(text, line.buffer + start, end - start);
6083             text += end-start;
6084          }
6085          numChars += end-start;
6086
6087          if(style.freeCaret && line == l2 && addSpaces)
6088          {
6089             if(l1 == l2)
6090                count = Max(x2-Max(x1,l1.count),0);
6091             else
6092                count = Max(x2-l2.count,0);
6093             if(text)
6094             {
6095                FillBytes(text,' ',count);
6096                text += count;
6097             }
6098             else
6099                numChars += count;
6100          }
6101          if(line == l2) break;
6102          // Add line Feed
6103          if(addCr)
6104          {
6105             if(text)
6106                *(text++) = '\r';
6107             numChars++;
6108          }
6109          if(text)
6110             *(text++) = '\n';
6111          numChars++;
6112       }
6113       // '\0' terminate Terminate
6114       if(text)
6115          *(text) = '\0';
6116       numChars++;
6117       return numChars;
6118    }
6119
6120    void GetSel(char * text, bool addCr)
6121    {
6122       GetText(text, line, y, x, selLine, selY, selX, addCr, true);
6123    }
6124
6125    private void _Deselect()   // This assumes marking lines as dirty is handled by the caller
6126    {
6127       selLine = line;
6128       selX = x;
6129       selY = y;
6130    }
6131
6132    void Deselect()
6133    {
6134       SelDirty();
6135       _Deselect();
6136    }
6137
6138    // CLIPBOARD
6139    void Copy()
6140    {
6141       if(this)
6142       {
6143          int size = SelSize();
6144          if(size)
6145          {
6146             // Try to allocate memory
6147             ClipBoard clipBoard { };
6148             if(clipBoard.Allocate(size+1))
6149             {
6150                GetSel(clipBoard.memory, true);
6151                // Save clipboard
6152                clipBoard.Save();
6153             }
6154             delete clipBoard;
6155          }
6156       }
6157    }
6158
6159    void Paste()
6160    {
6161       if(this)
6162       {
6163          ClipBoard clipBoard { };
6164          if(clipBoard.Load())
6165             PutS(clipBoard.memory);
6166          delete clipBoard;
6167       }
6168    }
6169
6170    void Cut()
6171    {
6172       if(this)
6173       {
6174          Copy();
6175          if(DelSel(null))
6176          {
6177             SetViewToCursor(true);
6178             Modified();
6179          }
6180       }
6181    }
6182
6183    void DeleteSelection()
6184    {
6185       if(this)
6186       {
6187          DelSel(null);
6188          SetViewToCursor(true);
6189          Modified();
6190       }
6191    }
6192
6193    // FILE INTERFACE
6194    void Save(File f, bool cr)
6195    {
6196       EditLine line;
6197       savedAction = undoBuffer.curAction;
6198
6199       for(line = this.lines.first; line; line = line.next)
6200       {
6201          f.Write(line.buffer, line.count, 1);
6202          if(line.next)
6203          {
6204             if(cr) f.Putc('\r');
6205             f.Putc('\n');
6206          }
6207       }
6208    }
6209
6210    #define BUFFER_SIZE  16384
6211    void Load(File f)
6212    {
6213       undoBuffer.dontRecord++;
6214       if(f)
6215       {
6216          char buffer[BUFFER_SIZE];
6217
6218          for(;;)
6219          {
6220             int count = f.Read(buffer, 1, BUFFER_SIZE-1);
6221             buffer[count] = '\0';
6222             AddS(buffer);
6223             if(!count) break;
6224          }
6225          Home();
6226       }
6227       undoBuffer.dontRecord--;
6228       undoBuffer.count = 0;
6229       undoBuffer.curAction = 0;
6230       itemEditUndo.disabled = undoBuffer.curAction == 0;
6231       itemEditRedo.disabled = undoBuffer.curAction == undoBuffer.count;
6232    }
6233
6234    EditBoxFindResult Find(char * text, bool matchWord, bool matchCase, bool isSearchDown)
6235    {
6236       EditLine line;
6237       int num;
6238       bool firstPass = true;
6239       EditBoxFindResult result = found;
6240
6241       if(!this.line) return notFound;
6242       num = this.y;
6243
6244       for(line = this.line;;)
6245       {
6246          char * string;
6247
6248          if(!line)
6249          {
6250             if(isSearchDown)
6251             {
6252                line = this.lines.first;
6253                num = 0;
6254                result = wrapped;
6255             }
6256             else
6257             {
6258                line = this.lines.last;
6259                num = this.lineCount - 1;
6260                result = wrapped;
6261             }
6262          }
6263
6264          if(isSearchDown)
6265             string = SearchString(line.buffer, firstPass ? Min(this.x,line.count) : 0,text,matchCase,matchWord);
6266          else
6267             string = RSearchString(line.buffer,text,firstPass ? (Min(this.x,line.count)-1) : line.count,matchCase,matchWord);
6268
6269          if(string)
6270          {
6271             Select((void *)line,num,string - line.buffer,(void *)line,num,string - line.buffer + strlen(text));
6272             return result;
6273          }
6274          if(line == this.line && !firstPass) break;
6275
6276          if(isSearchDown)
6277          {
6278             line = line.next;
6279             num++;
6280          }
6281          else
6282          {
6283             line = line.prev;
6284             num--;
6285          }
6286
6287          firstPass = false;
6288       }
6289       return notFound;
6290    }
6291
6292    EditBoxFindResult FindInSelection(char * text, bool matchWord, bool matchCase, EditLine l2, int y2, int x2)
6293    {
6294       EditLine line;
6295       int y;
6296       int searchLen = strlen(text);
6297       for(line = (EditLine)this.line, y = this.y; y <= y2; line = line.next, y++)
6298       {
6299          char * string = SearchString(line.buffer, (y == this.y) ? Min(this.x,line.count) : 0,text,matchCase,matchWord);
6300          if(string && (y < y2 || (string - line.buffer) + searchLen <= x2))
6301          {
6302             Select((void *)line,y,string - line.buffer,(void *)line,y,string - line.buffer + strlen(text));
6303             return found;
6304          }
6305       }
6306       return notFound;
6307    }
6308
6309    bool OnKeyDown(Key key, unichar ch)
6310    {
6311 #ifdef _DEBUG
6312       //PrintLn("OnKeyDown: code = ", key.code, ", mods = ", key.modifiers, ", ch = ", (int)ch);
6313 #endif
6314       if(!NotifyKeyDown(master, this, key, ch))
6315          return false;
6316       else
6317       {
6318          switch(key)
6319          {
6320             case escape:
6321             {
6322                if(this.mouseMove)
6323                {
6324                   this.mouseMove = false;
6325                   OnLeftButtonUp(0,0,0);
6326                   SetViewToCursor(true);
6327                   return false;
6328                }
6329             }
6330          }
6331       }
6332       return true;
6333    }
6334 };
6335
6336 public class EditBoxStream : File
6337 {
6338    EditBox editBox;
6339    BufferLocation start, sel;
6340    uint pos;
6341    byte utf8Bytes[5];
6342    int numBytes;
6343
6344    ~EditBoxStream()
6345    {
6346       EditBox editBox = this.editBox;
6347
6348       editBox.x = start.x;
6349       editBox.y = start.y;
6350       editBox.line = start.line;
6351
6352       editBox.selX = sel.x;
6353       editBox.selY = sel.y;
6354       editBox.selLine = sel.line;
6355
6356       editBox.SetViewToCursor(true);
6357       //editBox.ComputeColumn();
6358    }
6359
6360 public:
6361    property EditBox editBox
6362    {
6363       set
6364       {
6365          editBox = value;
6366
6367          start.x = value.x;
6368          start.y = value.y;
6369          start.line = value.line;
6370
6371          sel.x = value.selX;
6372          sel.y = value.selY;
6373          sel.line = value.selLine;
6374          numBytes = 0;
6375
6376          value.GoToHome(true);
6377       }
6378
6379       get { return editBox; }
6380    }
6381
6382    uint Read(byte * buffer, uint size, uint count)
6383    {
6384       uint read = 0;
6385       EditBox editBox = this.editBox;
6386       EditLine line = editBox.line;
6387       int x = editBox.x;
6388       int y = editBox.y;
6389
6390       count *= size;
6391
6392       for(;read < count && line; line = (*&line.next))
6393       {
6394          int numBytes = Min(count - read, (*&line.count) - x);
6395          if(numBytes > 0)
6396          {
6397             memcpy(buffer + read, (*&line.buffer) + x, numBytes);
6398             read += numBytes;
6399             x += numBytes;
6400          }
6401          /*
6402          for(;read < count && x < (*&line.count);)
6403          {
6404             buffer[read++] = (*&line.buffer)[x++];
6405          }
6406          */
6407          if((*&line.next))
6408          {
6409             if(read < count)
6410             {
6411                buffer[read++] = '\n';
6412             }
6413             else
6414                break;
6415          }
6416          if(x == (*&line.count) && (*&line.next))
6417          {
6418             x = 0;
6419             y++;
6420          }
6421          else
6422             break;
6423       }
6424
6425       editBox.line = editBox.selLine = line;
6426       editBox.x = editBox.selX = x;
6427       editBox.y = editBox.selY = y;
6428       pos += read;
6429       return read / size;
6430    }
6431
6432    bool Seek(int pos, FileSeekMode mode)
6433    {
6434       bool result = true;
6435       EditBox editBox = this.editBox;
6436       EditLine line = editBox.line;
6437       numBytes = 0;
6438       if(mode == FileSeekMode::start)
6439       {
6440          pos = pos - this.pos;
6441          mode = current;
6442       }
6443       if(mode == current)
6444       {
6445          uint read = 0;
6446          int x = editBox.x;
6447          int y = editBox.y;
6448          if(pos > 0)
6449          {
6450             for(;read < pos && line; line = (*&line.next))
6451             {
6452                int numBytes = Min(pos - read, (*&line.count) - x);
6453                if(numBytes > 0)
6454                {
6455                   read += numBytes; x += numBytes;
6456                }
6457
6458                /*for(;read < pos && x < (*&line.count);)
6459                {
6460                   read++; x++;
6461                }
6462                */
6463                if((*&line.next))
6464                {
6465                   if(read < pos)
6466                   {
6467                      read++;
6468                      x = 0;
6469                   }
6470                   else
6471                      break;
6472                }
6473                else
6474                {
6475                   if(read<pos)
6476                      result = false;
6477                   break;
6478                }
6479                y++;
6480             }
6481             this.pos += read;
6482          }
6483          else if(pos < 0)
6484          {
6485             pos = -pos;
6486             for(;read < pos && line; line = (*&line.prev))
6487             {
6488                int numBytes = Min(pos - read, x);
6489                if(numBytes > 0)
6490                {
6491                   read += numBytes;
6492                   x -= numBytes;
6493                }
6494                /*for(;read < pos && x > 0;)
6495                {
6496                   read++; x--;
6497                }
6498                */
6499                if((*&line.prev))
6500                {
6501                   if(read < pos)
6502                   {
6503                      read++;
6504                      x = (*&(*&line.prev).count);
6505                   }
6506                   else
6507                      break;
6508                }
6509                else
6510                {
6511                   if(read<pos)
6512                      result = false;
6513                   break;
6514                }
6515                y--;
6516             }
6517             this.pos -= read;
6518          }
6519
6520          editBox.line = editBox.selLine = line;
6521          editBox.x = editBox.selX = x;
6522          editBox.y = editBox.selY = y;
6523       }
6524       return result;
6525    }
6526
6527    bool Puts(char * string)
6528    {
6529       EditBox editBox = this.editBox;
6530       BufferLocation start { editBox.line, editBox.y, editBox.x };
6531       BufferLocation pos;
6532
6533       numBytes = 0;
6534       editBox.AddS(string);
6535
6536       pos.line = editBox.line;
6537       pos.y = editBox.y;
6538       pos.x = editBox.x;
6539
6540       this.start.AdjustAdd(start, pos);
6541       sel.AdjustAdd(start, pos);
6542
6543       return true;
6544    }
6545
6546    // NOTE: BYTE, NOT UNICODE CHARACTER!
6547    bool Putc(char ch)
6548    {
6549       EditBox editBox = this.editBox;
6550       BufferLocation start = { editBox.line, editBox.y, editBox.x };
6551       BufferLocation pos;
6552
6553       if(numBytes < 4)
6554       {
6555          utf8Bytes[numBytes++] = ch;
6556          utf8Bytes[numBytes] = 0;
6557          if(UTF8Validate(utf8Bytes))
6558          {
6559             editBox.AddCh(UTF8_GET_CHAR(utf8Bytes, numBytes));
6560             numBytes = 0;
6561             pos.line = editBox.line;
6562             pos.y = editBox.y;
6563             pos.x = editBox.x;
6564             this.start.AdjustAdd(start, pos);
6565             sel.AdjustAdd(start, pos);
6566          }
6567          return true;
6568       }
6569       return false;
6570    }
6571
6572    bool Getc(char * ch)
6573    {
6574       return Read(ch, 1, 1) ? true : false;
6575    }
6576
6577    void DeleteBytes(uint count)
6578    {
6579       EditBox editBox = this.editBox;
6580       if(count)
6581       {
6582          BufferLocation pos { editBox.line, editBox.y, editBox.x };
6583          BufferLocation end = pos;
6584
6585          uint c = 0;
6586          for(;;)
6587          {
6588             for(;c < count && end.line && end.x < end.line.count;)
6589             {
6590                end.x++;
6591                c++;
6592             }
6593             if(c < count && end.line && end.line.next)
6594             {
6595                end.line = end.line.next;
6596                end.y++;
6597                c++;
6598                end.x = 0;
6599             }
6600             else
6601                break;
6602          }
6603
6604          start.AdjustDelete(pos, end);
6605          sel.AdjustDelete(pos, end);
6606
6607          editBox._DelCh(pos.line, pos.y, pos.x, end.line, end.y, end.x, true, false, null);
6608       }
6609    }
6610 };