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