ecere/EditBox: (#1073) Fixed hex number highlighting bug: 0x3, p
[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 || (!overwrite && !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             SetCaret(x + XOFFSET-2, y * space.h + YOFFSET, space.h);
2898          }
2899          else
2900             SetCaret(0, 0, 0);
2901
2902          // TOFIX: Mismatch between NotifyCaretMove() and NotifyDropped() / GoToPosition()
2903          NotifyCaretMove(master, this, y + 1, x + 1);
2904
2905          SelectionEnables();
2906       }
2907    }
2908
2909    void SelectionEnables()
2910    {
2911       if((x != selX || y != selY) && !selection)
2912       {
2913          if(!style.readOnly)
2914          {
2915             itemEditCut.disabled = false;
2916             itemEditDelete.disabled = false;
2917          }
2918          itemEditCopy.disabled = false;
2919
2920          this.selection = true;
2921       }
2922       else if((x == selX && y == selY) && selection)
2923       {
2924          itemEditCut.disabled = true;
2925          itemEditCopy.disabled = true;
2926          itemEditDelete.disabled = true;
2927
2928          this.selection = false;
2929       }
2930    }
2931
2932    void SetSelectCursor()
2933    {
2934       if(!inactive || !style.noSelect)
2935       {
2936          if(this.mouseMove)
2937             cursor = guiApp.GetCursor(arrow);
2938          else if(this.mouseSelect)
2939             cursor = guiApp.GetCursor(iBeam);
2940          else
2941          {
2942             if(IsMouseOnSelection())
2943                cursor = guiApp.GetCursor(arrow);
2944             else
2945                cursor = guiApp.GetCursor(iBeam);
2946          }
2947       }
2948    }
2949
2950    int AdjustXPosition(EditLine line, int position, bool half, int * px, int max, int sc)
2951    {
2952       int c = sc;
2953       int x = px ? *px : 0;
2954       while(true)
2955       {
2956          int start = c;
2957          int numBytes = 1;
2958          int len = 1;
2959          int w;
2960          if(c < Min(max, line.count))
2961          {
2962             byte ch = 0;
2963             int numBytes;
2964             for(len = 0; c < Min(max, line.count); c += numBytes)
2965             {
2966                ch = line.buffer[c];
2967                numBytes = UTF8_NUM_BYTES(ch);
2968
2969                if(ch == ' ' || ch == '\t')
2970                {
2971                   if(!len) c++;
2972                   break;
2973                }
2974                len += numBytes;
2975             }
2976             if(!len && ch == ' ')
2977             {
2978                w = space.w;
2979                len = 1;
2980             }
2981             else if(!len && ch == '\t')
2982             {
2983                w = (tabSize * space.w) - (x % (tabSize * space.w));
2984                len = 1;
2985             }
2986             else
2987                FontExtent(display, font, line.buffer + start, len, &w, null);
2988          }
2989          else
2990          {
2991             if(style.freeCaret && c < max)
2992                w = space.w;
2993             else
2994             {
2995                if(px) *px = x;
2996                return c;
2997             }
2998             c++;
2999          }
3000          if(x + (((half && len == 1) ? (w / 2) : w)) >= position)
3001          {
3002             int lastW;
3003             while(len > 0)
3004             {
3005                int a = start + len;
3006                lastW = w;
3007                if(a <= line.count)
3008                   while(a > 0 && !UTF8_IS_FIRST(line.buffer[--a]));
3009                else
3010                   a--;
3011                if(a > start)
3012                   FontExtent(display, font, line.buffer + start, a - start, &w, null);
3013                else
3014                   w = 0;
3015                if(position > x + (half ? ((w + lastW) / 2) : lastW)) break;
3016                len = a - start;
3017             }
3018             if(px) *px = x + w;
3019             return Min(this.maxLineSize - 1, start + len);
3020          }
3021          x += w;
3022       }
3023    }
3024
3025    void SetCursorToViewX()
3026    {
3027       bool selecting = this.x != selX || y != selY;
3028
3029       // Horizontal Adjustment
3030       int x = 0;
3031       int c = AdjustXPosition(line, viewX, false, &x, MAXINT, 0);
3032       if(this.x < c)
3033          this.x = x;
3034       else
3035       {
3036          c = AdjustXPosition(line, viewX + clientSize.w - 1, false, &x, MAXINT, c);
3037          if(this.x > c)
3038             this.x = c;
3039      }
3040
3041       if(!selecting)
3042       {
3043          selX = this.x;
3044          selY = y;
3045          selLine = line;
3046       }
3047
3048       UpdateCaretPosition(false);
3049
3050       UpdateDirty();
3051       SetSelectCursor();
3052    }
3053
3054    void SetCursorToViewY()
3055    {
3056       int c, numLines;
3057       EditLine oldLine = this.line;
3058
3059       bool selecting = this.x != this.selX || this.y != this.selY;
3060
3061       numLines = clientSize.h / this.space.h;
3062
3063       // Vertical Adjustment
3064       if(this.viewY > this.y)
3065       {
3066          this.y = this.viewY;
3067          this.line = this.viewLine;
3068       }
3069
3070       if(this.viewY + numLines <=  this.y)
3071       {
3072          EditLine line;
3073
3074          this.y = this.viewY-1;
3075          for(c = 0, line = this.viewLine; line && c<numLines; line = line.next, c++)
3076          {
3077             this.line = line;
3078             this.y++;
3079          }
3080       }
3081
3082       if(this.line != oldLine)
3083       {
3084          this.x = AdjustXPosition(this.line, caretX, true, null, MAXINT, 0);
3085          ComputeColumn();
3086       }
3087
3088       if(!selecting)
3089       {
3090          this.selX = this.x;
3091          this.selY = this.y;
3092          this.selLine = this.line;
3093       }
3094
3095       UpdateCaretPosition(false);
3096
3097       UpdateDirty();
3098       SetSelectCursor();
3099    }
3100
3101    /*
3102    bool SaveFile(char * fileName)
3103    {
3104       File f = eFile_Open(fileName, FO_WRITE);
3105       if(f)
3106       {
3107          Save(f, false);
3108          eWindow_SetModified(false);
3109          eInstance_Delete(f);
3110          return true;
3111       }
3112       return false;
3113    }
3114    */
3115
3116    bool OnActivate(bool active, Window previous, bool * goOnWithActivation, bool direct)
3117    {
3118       if(line)
3119       {
3120          if(!style.multiLine)
3121          {
3122             x = (active && this.active && !style.readOnly) ? line.count : 0;
3123             selX = 0;
3124             ComputeColumn();
3125             SetViewToCursor(true);
3126             DirtyLine(0);
3127             UpdateDirty();
3128          }
3129          // Update(null);
3130          if(!active && modified)
3131          {
3132             modified = false;
3133             if(!NotifyModified(master, this))
3134             {
3135                modified = true;
3136                *goOnWithActivation = false;
3137             }
3138          }
3139       }
3140       if(!active)
3141       {
3142          ReleaseCapture();
3143          if(timer) timer.Stop();
3144
3145          mouseSelect = false;
3146          wordSelect = false;
3147       }
3148       return true;
3149    }
3150
3151    void AutoSize()
3152    {
3153       int aw = maxLength + 12, ah = Max(lineCount, 1) * space.h + 2;
3154       int nw = minClientSize.w, nh = minClientSize.h, xw = maxClientSize.w, xh = maxClientSize.h;
3155       clientSize = { nw && aw < nw ? nw : xw && aw > xw ? xw : aw, nh && ah < nh ? nh : xh && ah > xh ? xh : ah };
3156    }
3157
3158    bool OnResizing(int *w, int *h)
3159    {
3160       if(!*h)
3161          *h = space.h + 2;
3162       if(!*w)
3163          //*w = 80;
3164          *w = space.h * 80 / 14;
3165       return true;
3166    }
3167
3168    void OnResize(int w, int h)
3169    {
3170    /*
3171       if(!hasHorzScroll && !hasVertScroll && viewLine)
3172          SetViewToCursor(true);
3173    */
3174       //if(!hasHorzScroll && !hasVertScroll && viewLine)
3175       if(viewLine)
3176          SetViewToCursor(true);
3177       //else
3178    //     FixScrollArea();
3179    }
3180
3181    bool OnMiddleButtonDown(int x, int y, Modifiers mods)
3182    {
3183       if(style.readOnly) return true;
3184       // We really shouldn't be pasting here, Unix middle button paste is for the selection (not the clipboard), good for the terminal
3185       // Middle button already serves as a dragger as well.
3186       // Paste();
3187       return true;
3188    }
3189
3190    bool OnRightButtonDown(int x, int y, Modifiers mods)
3191    {
3192       this.rightButtonDown = true;
3193       // Copy();
3194       return true;
3195    }
3196
3197    bool OnRightButtonUp(int x, int y, Modifiers mods)
3198    {
3199       // Context Menu
3200       if(!parent.inactive && rightButtonDown)
3201       {
3202          PopupMenu popup;
3203          Menu contextMenu { };
3204
3205          MenuItem { contextMenu, $"Cut\tCtrl+X", t, NotifySelect = itemEditCut.NotifySelect, disabled = !selection || style.readOnly };
3206          MenuItem { contextMenu, $"Copy\tCtrl+C", c, NotifySelect = itemEditCopy.NotifySelect, disabled = !selection };
3207          MenuItem { contextMenu, $"Paste\tCtrl+V", p, NotifySelect = itemEditPaste.NotifySelect, disabled = style.readOnly };
3208          MenuItem { contextMenu, $"Delete\tDel", d, NotifySelect = itemEditDelete.NotifySelect, disabled = !selection || style.readOnly };
3209          MenuDivider { contextMenu };
3210          MenuItem { contextMenu, $"Select All\tCtrl+A", a, NotifySelect = itemEditSelectAll.NotifySelect };
3211
3212          popup = PopupMenu { master = this, menu = contextMenu,
3213    /*
3214             nonClient = true, interim = false, parent = parent,
3215             position = { x + clientStart.x + parent.clientStart.x + position.x, y + cientStart.y + parent.sy + clientStart.y + position.y };
3216    */
3217             position = { x + clientStart.x + absPosition.x - guiApp.desktop.position.x, y + clientStart.y + absPosition.y - guiApp.desktop.position.y }
3218          };
3219          popup.Create();
3220       }
3221       rightButtonDown = false;
3222       return true;
3223    }
3224
3225    bool OnLeftButtonDown(int mx, int my, Modifiers mods)
3226    {
3227       int x,y;
3228       EditLine line;
3229
3230       if(style.noSelect) return true;
3231
3232       // Should we have a separate 'selectOnActivate' style?
3233       if(!mods.isActivate || (style.readOnly && style.multiLine))
3234       {
3235          Capture();
3236          mouseSelect = true;
3237       }
3238
3239       mouseX = mx - XOFFSET;
3240       mouseY = my;
3241
3242       FindMouse(mouseX, mouseY, &x, &y, &line, true);
3243 #ifdef _DEBUG
3244       //PrintLn("OnLeftButtonDown: ", x, ", ", y);
3245 #endif
3246       if(!style.readOnly)
3247       {
3248          if(wordSelect)
3249             mouseMove = false;
3250          else if(IsMouseOnSelection() && !mods.isActivate)
3251          {
3252             DirtyLine(this.y);
3253             mouseMove = true;
3254             dropX = x;
3255             dropY = y;
3256             dropLine = line;
3257          }
3258       }
3259
3260       if(!mouseMove && !wordSelect && (!mods.isActivate || style.multiLine))
3261       {
3262          if(mods.shift && !mods.isActivate)
3263          {
3264             this.x = x;
3265             this.y = y;
3266             this.line = line;
3267             DirtyAll();
3268          }
3269          else
3270          {
3271             SelDirty();
3272             DirtyLine(this.y);
3273             this.x = x;
3274             this.y = y;
3275             this.line = line;
3276             DirtyLine(this.y);
3277             _Deselect();
3278          }
3279          ComputeColumn();
3280       }
3281
3282       UpdateDirty();
3283       UpdateCaretPosition(true);
3284       // Return false because DataBoxes automatically set EditBox editor's clickThrough to true for MouseMove events
3285       // ( for tool tips -- see 95ee4962c4c7bc3fe0a04aa6a4f98cacada40884)
3286       return false;
3287    }
3288
3289    bool OnLeftButtonUp(int x, int y, Modifiers mods)
3290    {
3291       timer.Stop();
3292
3293       mouseSelect = false;
3294       wordSelect = false;
3295
3296       x -= XOFFSET;
3297
3298       ReleaseCapture();
3299       if(!style.readOnly)
3300       {
3301          if(mouseMove)
3302          {
3303             EditLine line;
3304             FindMouse(mouseX, mouseY, &x, &y, &line, true);
3305 #ifdef _DEBUG
3306             //PrintLn("MouseMove: ", x, ", ", y);
3307 #endif
3308             dropX = x;
3309             dropY = y;
3310             dropLine = line;
3311
3312             mouseMove = IsMouseOnSelection();
3313
3314             if(!mouseMove)
3315             {
3316                int size = SelSize();
3317                if(size)
3318                {
3319                   char * text = new char[size+1];
3320                   if(text)
3321                   {
3322                      int moveX = 0;
3323                      GetSel(text, false);
3324
3325                      if(Max(selY, this.y) == dropY)
3326                      {
3327                         if(this.x > selX)
3328                         {
3329                            if(this.dropX > this.selX)
3330                               moveX = this.x - this.selX;
3331                         }
3332                         else
3333                         {
3334                            if(this.dropX > this.x)
3335                               moveX = this.selX - this.x;
3336                         }
3337                      }
3338
3339                      recordUndoEvent = true;
3340                      DelSel(null);
3341                      this.dropX -= moveX;
3342                      this.selX = this.x = this.dropX;
3343                      this.selY = this.y = this.dropY;
3344                      this.selLine = this.line = this.dropLine;
3345                      AddS(text);
3346                      recordUndoEvent = false;
3347
3348                      SetViewToCursor(true);
3349                      delete text;
3350                      Modified();
3351 #ifdef _DEBUG
3352                    /*  {
3353                         byte * c = ((EditBox)this).multiLineContents, * c2;
3354                         int l1 = c ? strlen(c) : 0, l2;
3355                         Undo();
3356                         Redo();
3357                         c2 = ((EditBox)this).multiLineContents;
3358                         l2 = c2 ? strlen(c2) : 0;
3359                         if(l1 != l2 || (l1 && strcmp(c, c2)))
3360                         {
3361                            PrintLn("Fail!");
3362                         }
3363                         delete c;
3364                      }
3365                      */
3366 #endif
3367                   }
3368                }
3369             }
3370             else
3371             {
3372                SelDirty();
3373                DirtyLine(this.y);
3374                this.x = x;
3375                this.y = y;
3376                this.line = line;
3377                ComputeColumn();
3378                DirtyLine(this.y);
3379                _Deselect();
3380                UpdateDirty();
3381             }
3382          }
3383          else
3384          {
3385             EditLine line;
3386             mouseX = x;
3387             mouseY = y;
3388
3389             FindMouse(mouseX, mouseY, &x, &y, &line, true);
3390 #ifdef _DEBUG
3391             //PrintLn("Dropped: ", x, ", ", y);
3392 #endif
3393             NotifyDropped(master, this, x, y);
3394          }
3395       }
3396       mouseMove = false;
3397       // Return false because DataBoxes automatically set EditBox editor's clickThrough to true for MouseMove events
3398       // ( for tool tips -- see 95ee4962c4c7bc3fe0a04aa6a4f98cacada40884)
3399       return false;
3400    }
3401
3402    bool OnMouseMove(int mx, int my, Modifiers mods)
3403    {
3404       int x,y;
3405       EditLine line;
3406       bool needScroll;
3407
3408       if(mods != -1 && mods.isSideEffect)
3409       {
3410          SetSelectCursor();
3411          return true;
3412       }
3413       if(style.noSelect) return true;
3414       if(wordSelect) return true;
3415       mouseX = mx - XOFFSET;
3416       mouseY = my;
3417
3418       needScroll = FindMouse(this.mouseX, this.mouseY, &x, &y, &line, true);
3419
3420       if(this.mouseMove || this.mouseSelect)
3421       {
3422          if(!needScroll)
3423             timer.Stop();
3424          else
3425          {
3426             if(needScroll)
3427                timer.Start();
3428             if(mods != -1 &&
3429                ((style.hScroll) || (style.vScroll)))
3430                return true;
3431          }
3432       }
3433
3434       if(this.mouseMove)
3435       {
3436          DirtyLine(this.dropY);
3437          this.dropX = x;
3438          this.dropY = y;
3439          DirtyLine(this.dropY);
3440          this.dropLine = line;
3441          SetViewToCursor(true);
3442 #ifdef _DEBUG
3443          //PrintLn("MouseMove: ", "dropX = ", x, ", dropY = ", y);
3444 #endif
3445       }
3446       else if(this.mouseSelect)
3447       {
3448          DirtyLine(this.selY);
3449          DirtyLine(this.y);
3450          this.x = x;
3451          this.y = y;
3452          ComputeColumn();
3453          DirtyLine(this.y);
3454          this.line = line;
3455          SetViewToCursor(true);
3456          UpdateDirty();
3457 #ifdef _DEBUG
3458          //PrintLn("MouseSelect: ", "x = ", x, ", y = ", y);
3459 #endif
3460       }
3461       SetSelectCursor();
3462       return true;
3463    }
3464
3465    bool OnLeftDoubleClick(int mx, int my, Modifiers mods)
3466    {
3467       int x,y;
3468       EditLine line;
3469 #ifdef _DEBUG
3470       //PrintLn("OnLeftDoubleClick: ", mx, ", ", my, ", mods = ", mods);
3471 #endif
3472       mx -= XOFFSET;
3473
3474       if(style.noSelect) return true;
3475       FindMouse(mx, my, &x, &y, &line, false);
3476       if(!NotifyDoubleClick(master, this, line, mods))
3477          return false;
3478       if(x < line.count)
3479       {
3480          int c;
3481          int start = -1;
3482          int numBytes;
3483          for(c = x; c >= 0; c--)
3484          {
3485             unichar ch;
3486             while(c > 0 && !UTF8_IS_FIRST(line.buffer[c])) c--;
3487             ch = UTF8_GET_CHAR(line.buffer + c, numBytes);
3488             if(!IS_ALUNDER(ch))
3489                break;
3490             start = c;
3491          }
3492          if(start != -1)
3493          {
3494             for(c = start; c<line.count; c += numBytes)
3495             {
3496                unichar ch = UTF8_GET_CHAR(line.buffer + c, numBytes);
3497                if(!ch || !IS_ALUNDER(ch))
3498                   break;
3499             }
3500             SelDirty();
3501             DirtyLine(this.y);
3502             this.y = y;
3503             DirtyLine(this.y);
3504             this.selX = start;
3505             this.x = c;
3506             ComputeColumn();
3507             this.line = this.selLine = line;
3508             this.wordSelect = (c != start);
3509             UpdateDirty();
3510          }
3511       }
3512       return true;
3513    }
3514
3515    bool OnPostCreate()
3516    {
3517       /*if(isDocument)
3518       {
3519          Menu fileMenu { menu, "File", F };
3520          saveDialog = fileDialog;
3521          MenuItem { fileMenu, $"Save\tCtrl+S", S, CtrlS, NotifySelect = MenuFileSave };
3522          MenuItem { fileMenu, $"Save As...", A, NotifySelect = MenuFileSaveAs };
3523       }*/
3524       if(style.autoSize) AutoSize();
3525       return true;
3526    }
3527
3528    void ComputeFont()
3529    {
3530       if(FontExtent)
3531       {
3532          FontExtent(display, font, " ", 1, (int *)&space.w, (int *)&space.h);
3533          FontExtent(display, font, "W", 1, (int *)&large.w, (int *)&large.h);
3534
3535          space.w = Max(space.w, 1);
3536          large.w = Max(large.w, 1);
3537          space.h = Max(space.h, 1);
3538          large.h = Max(large.h, 1);
3539
3540          {
3541             EditLine line;
3542             for(line = lines.first; line; line = line.next)
3543                ComputeLength(line);
3544             FindMaxLine();
3545          }
3546
3547          if(viewLine)
3548             SetViewToCursor(true);
3549
3550          FixScrollArea();
3551
3552          Update(null);
3553       }
3554    }
3555
3556    bool OnLoadGraphics()
3557    {
3558       FontExtent = Display::FontExtent;
3559       font = fontObject;
3560       ComputeFont();
3561       // UpdateCaretPosition(true);
3562       return true;
3563    }
3564
3565    void OnUnloadGraphics()
3566    {
3567       this.font = null;
3568    }
3569
3570    bool OnKeyHit(Key key, unichar ch)
3571    {
3572       bool shift = (key.shift) ? true : false;
3573 #ifdef _DEBUG
3574       //PrintLn("OnKeyHit: code = ", key.code, ", mods = ", key.modifiers, ", ch = ", (int)ch);
3575 #endif
3576       if(!ch && !key.alt && !key.ctrl)
3577       {
3578          key.code = (SmartKey)key.code;
3579       }
3580       else if(!ch && key.alt)
3581          key.code = 0;
3582
3583       switch(key.code) //(ch || key.alt || key.ctrl) ? key.code : (Key)(SmartKey)key.code)
3584       {
3585          case backSpace:
3586             if(style.readOnly) break;
3587             if(style.stuckCaret) GoToEnd(true);
3588             if(!(style.freeCaret))
3589             {
3590                this.x = Min(this.x, this.line.count);
3591             }
3592             if(key.ctrl)
3593             {
3594                int y;
3595                bool done = false;
3596                EditLine line = this.line;
3597                int c;
3598                for(y = this.y; y>= 0; y--)
3599                {
3600                   c = (y == this.y) ? (Min(this.x-1, line.count-1)) : line.count-1;
3601
3602                   // Slow down when going on lines...
3603                   if(y != this.y) break;
3604
3605                   for(; c>=0; c--)
3606                   {
3607                      //if(this.line.buffer[c] != '\t' && this.line.buffer[c] != ' ')
3608                      if(IS_ALUNDER(line.buffer[c]))
3609                         break;
3610                   }
3611                   for(; c>=0; c--)
3612                   {
3613                      if(!IS_ALUNDER(line.buffer[c]))
3614                      {
3615                      //if(this.line.buffer[c] == '\t' || this.line.buffer[c] == ' ')
3616                         done = true;
3617                         break;
3618                      }
3619                   }
3620                   if(done)
3621                      break;
3622                   if(line.prev)
3623                      line = line.prev;
3624                   else
3625                      break;
3626                }
3627
3628                //if(this.x != 0)
3629                {
3630                   _DelCh(line, y, c+1, this.line, this.y, this.x, true, false, null);
3631                   this.x = this.selX = Min(c+1, line.count);
3632                   this.y = this.selY = y;
3633                   this.line = this.selLine = line;
3634                   SetViewToCursor(true);
3635                }
3636                ComputeColumn();
3637             }
3638             else
3639             {
3640                BackSpace();
3641             }
3642             return false;
3643             //break;
3644          case del:
3645             if(style.readOnly) break;
3646             if(style.stuckCaret) break;
3647
3648             if(shift)
3649             {
3650                Cut();
3651             }
3652             else
3653             {
3654                // Delete selection
3655                if(this.line != this.selLine || this.x != this.selX)
3656                {
3657                   DelSel(null);
3658                   SetViewToCursor(true);
3659                   Modified();
3660                }
3661                else
3662                {
3663                   EditLine line1 = this.line, line2 = this.line;
3664                   int x1, y1 = this.y, x2, y2 = this.y;
3665                   if(!style.freeCaret)
3666                   {
3667                      this.selX = this.x = Min(this.x, this.line.count);
3668                      ComputeColumn();
3669                   }
3670                   x1 = this.x;
3671
3672                   if(x1 < line1.count)
3673                   {
3674                      // Delete word
3675                      if(key.ctrl)
3676                      {
3677                         int i;
3678                         int length;
3679                         char * buffer = line1.buffer;
3680                         for(i = x1; i < line1.count; i++)
3681                         {
3682                            if(!IS_ALUNDER(buffer[i]))
3683                               break;
3684                         }
3685
3686                         for(; i < line1.count; i++)
3687                         {
3688                            // Delete trailing whitespace
3689                            if(IS_ALUNDER(buffer[i]))
3690                               break;
3691                         }
3692                         x2 = i;
3693                      }
3694                      else
3695                         x2 = x1 + 1;
3696                   }
3697                   else
3698                   {
3699                      // Avoid creating trailing spaces if there is no content on next line
3700                      line2 = line1.next;
3701                      y2++;
3702                      if(line2 && !line2.count)
3703                         x1 = line1.count;
3704                      x2 = 0;
3705                   }
3706                   if(line2)
3707                      _DelCh(line1, y1, x1, line2, y2, x2, false, false, null);
3708                   SetViewToCursor(true);
3709                   Modified();
3710                }
3711             }
3712             return false;
3713             //break;
3714          case enter:
3715          case keyPadEnter:
3716          {
3717             if(!key.alt && !key.ctrl)
3718             {
3719                int c;
3720                int position = 0;
3721                bool stuffAfter = false;
3722                char * addString;
3723                int len = 0;
3724                /*bool resetX = false;
3725                int backX;*/
3726
3727                if(style.stuckCaret) GoToEnd(true);
3728                if(style.readOnly) break;
3729                if(!(style.multiLine)) break;
3730
3731                for(c = 0; c<this.line.count && c<this.x; c++)
3732                {
3733                   if(this.line.buffer[c] == '\t')
3734                      position += this.tabSize - (position % this.tabSize);
3735                   else if(this.line.buffer[c] == ' ')
3736                      position++;
3737                   else
3738                      break;
3739                }
3740
3741                // Prevent adding trailing spaces if at the head of a line
3742                /*if(c && c == this.x && c < this.line.count && this.x == this.selX && this.y == this.selY)
3743                {
3744                   position = 0;
3745                   backX = this.x;
3746                   this.x = 0;
3747                   this.selX = 0;
3748                   resetX = true;
3749                }*/
3750
3751                if(!line.count)
3752                   position = x;
3753
3754                if(this.x < this.line.count)
3755                   stuffAfter = true;
3756
3757                //If last character is a { indent one tab
3758                if(this.line.buffer[this.x - 1] == '{')
3759                {
3760                   //Except if the next non space character is a }
3761                   bool indent = false;
3762                   int i;
3763                   for(i = this.x; i < this.line.size; i++)
3764                      if(this.line.buffer[i] != ' ' && this.line.buffer[i] != '\t')
3765                      {
3766                         if(this.line.buffer[i] != '}')
3767                            indent = true;
3768                         break;
3769                      }
3770                   if(indent)
3771                      position += this.tabSize;
3772                }
3773
3774                addString = new char[position + 2];
3775                addString[len++] = '\n';
3776                addString[len] = '\0';
3777
3778                if(stuffAfter || !style.freeCaret)
3779                {
3780                   for(c = 0; c<position; )
3781                   {
3782                      if(style.useTab && c + this.tabSize <= position)
3783                      {
3784                         addString[len++] = '\t';
3785                         c += this.tabSize;
3786                      }
3787                      else
3788                      {
3789                         addString[len++] = ' ';
3790                         c++;
3791                      }
3792                   }
3793                   addString[len] = '\0';
3794                }
3795                recordUndoEvent = true;
3796                if(AddS(addString))
3797                {
3798                   EditLine prevLine = this.line.prev;
3799                   if(prevLine)
3800                   {
3801                      // Nuke spaces if that is all that is left on previous line
3802                      int i;
3803                      char * buffer = prevLine.buffer;
3804                      for(i = 0; i < prevLine.count; i++)
3805                         if(buffer[i] != ' ' && buffer[i] != '\t')
3806                            break;
3807                      if(i == prevLine.count)
3808                         DelCh(prevLine, this.y - 1, 0, prevLine, this.y - 1, prevLine.count, false);
3809                   }
3810                   /*if(resetX)
3811                   {
3812                      this.x = this.selX = backX;
3813                      ComputeColumn();
3814                   }
3815                   else */if(!stuffAfter && style.freeCaret)
3816                   {
3817                      this.x = this.selX = position;
3818                      ComputeColumn();
3819                   }
3820                   FindMaxLine();
3821                   SetViewToCursor(true);
3822                   Modified();
3823                }
3824                recordUndoEvent = false;
3825                delete addString;
3826                return false;
3827             }
3828             break;
3829          }
3830          case left:
3831          {
3832             if(style.stuckCaret) break;
3833             if(!(style.freeCaret))
3834             {
3835                this.x = Min(this.x, this.line.count);
3836                this.selX = Min(this.selX, this.selLine.count);
3837                ComputeColumn();
3838             }
3839             if(!shift) SelDirty();
3840             if(key.ctrl)
3841             {
3842                bool foundAlpha = false;
3843                bool found = false;
3844                int y = this.y;
3845                EditLine line, lastLine;
3846                int lastC, lastY;
3847
3848                for(line = this.line; (line && !found); line = line.prev, y--)
3849                {
3850                   int start;
3851                   int c;
3852
3853                   if(this.x == 0 && line != this.line)
3854                   {
3855                      foundAlpha = true;
3856                      lastC = line.count;
3857                      lastY = y;
3858                      lastLine = line;
3859                      break;
3860                   }
3861
3862                   if(line == this.line) start = this.x -1; else start = line.count-1;
3863                   start = Min(start, line.count-1);
3864
3865                   for(c = start; c >= 0;)
3866                   {
3867                      int numBytes;
3868                      unichar ch = UTF8_GET_CHAR(line.buffer + c, numBytes);
3869                      if(IS_ALUNDER(ch))
3870                      {
3871                         foundAlpha = true;
3872                         lastC = c;
3873                         lastY = y;
3874                         lastLine = line;
3875                      }
3876                      else
3877                      {
3878                         if(foundAlpha)
3879                         {
3880                            found = true;
3881                            break;
3882                         }
3883                      }
3884                      while(--c)
3885                      {
3886                         byte ch = line.buffer[c];
3887                         if(UTF8_IS_FIRST(ch)) break;
3888                      }
3889
3890                   }
3891                   // No next word found,
3892                   if(!found && ( this.x > 0 || (!line.count && this.x)))
3893                   {
3894                      foundAlpha = true;
3895                      lastC = 0;
3896                      lastY = y;
3897                      lastLine = line;
3898                      break;
3899                   }
3900                }
3901                if(foundAlpha)
3902                {
3903                   DirtyLine(this.y);
3904                   this.x = lastC;
3905                   this.y = lastY;
3906                   this.line = lastLine;
3907                   DirtyLine(this.y);
3908                   ComputeColumn();
3909                }
3910             }
3911             else
3912             {
3913                if(x > 0)
3914                {
3915                   if(x <= line.count)
3916                   {
3917                      byte * buffer = line.buffer;
3918                      while(--x)
3919                      {
3920                         byte ch = buffer[x];
3921                         if(UTF8_IS_FIRST(ch)) break;
3922                      }
3923                   }
3924                   else
3925                      x--;
3926                   DirtyLine(y);
3927                }
3928                else if(line.prev)
3929                {
3930                   line = line.prev;
3931                   DirtyLine(y);
3932                   x = line.count;
3933                   y--;
3934                   DirtyLine(y);
3935                }
3936                ComputeColumn();
3937             }
3938             if(!shift) _Deselect();
3939             SetViewToCursor(true);
3940             //break;
3941             return false;
3942          }
3943          case right:
3944          {
3945             if(style.stuckCaret) break;
3946             if(!(style.freeCaret))
3947             {
3948                this.x = Min(this.x, this.line.count);
3949                this.selX = Min(this.selX, this.selLine.count);
3950                ComputeColumn();
3951             }
3952             if(!shift) SelDirty();
3953             if(!shift && (this.x != this.selX || this.y != this.selY));
3954             else if(key.ctrl)
3955             {
3956                bool onAChar = false;
3957                if(this.selX != this.x || this.selY != this.y)
3958                   onAChar = true;
3959                if(this.x<this.line.count)
3960                   if(this.line.buffer[this.x] != '\t' && this.line.buffer[this.x] !=' ')
3961                      onAChar = true;
3962                if(key.shift && onAChar &&
3963                   ((this.y > this.selY)||((this.selY == this.y)&&(this.x >= this.selX))))
3964                {
3965                   bool foundAlpha = false;
3966                   bool found = false;
3967                   EditLine line, lastLine;
3968                   int y = this.y;
3969                   int lastC, lastY, lastNumBytes;
3970
3971                   for(line = this.line; (line && !found); line = line.next, y++)
3972                   {
3973                      int start = (line == this.line) ? this.x : 0;
3974                      int c;
3975                      int numBytes;
3976                      unichar ch;
3977                      for(c = start; c < line.count && (ch = UTF8_GET_CHAR(line.buffer + c, numBytes)); c += numBytes)
3978                      {
3979                         if(IS_ALUNDER(ch))
3980                         {
3981                            foundAlpha = true;
3982                            lastC = c;
3983                            lastNumBytes = numBytes;
3984                            lastY = y;
3985                            lastLine = line;
3986                         }
3987                         else if(foundAlpha)
3988                         {
3989                            found = true;
3990                            break;
3991                         }
3992                      }
3993                      if(!found && (c != this.x || line != this.line))
3994                      {
3995                         found = true;
3996                         lastLine = line;
3997                         lastC = line.count;
3998                         lastNumBytes = 0;
3999                         lastY = y;
4000                         break;
4001                      }
4002                   }
4003                   if(found)
4004                   {
4005                      DirtyLine(this.y);
4006                      this.x = lastC + lastNumBytes;
4007                      this.y = lastY;
4008                      this.line = lastLine;
4009                      DirtyLine(this.y);
4010                      ComputeColumn();
4011                   }
4012                }
4013                else
4014                {
4015                   bool foundAlpha = false;
4016                   bool found = false;
4017                   EditLine line;
4018                   int y = this.y;
4019
4020                   for(line = this.line; (line && !found); line = line.next, y++)
4021                   {
4022                      int start = (line == this.line) ? this.x : 0;
4023                      int c;
4024                      int numBytes;
4025                      unichar ch;
4026                      for(c = start; c < line.count && (ch = UTF8_GET_CHAR(line.buffer + c, numBytes)); c += numBytes)
4027                      {
4028                         if(!IS_ALUNDER(ch))
4029                            foundAlpha = true;
4030                         else if(foundAlpha)
4031                         {
4032                            found = true;
4033                            DirtyLine(this.y);
4034                            this.x = c;
4035                            this.y = y;
4036                            this.line = line;
4037                            DirtyLine(this.y);
4038                            ComputeColumn();
4039                            break;
4040                         }
4041                      }
4042                      // No next word found,
4043                      if(!found && (c != this.x || line != this.line))
4044                      {
4045                         found = true;
4046                         DirtyLine(this.y);
4047                         this.x = line.count;
4048                         this.y = y;
4049                         this.line = line;
4050                         DirtyLine(this.y);
4051                         ComputeColumn();
4052                      }
4053                      foundAlpha = true;
4054                   }
4055                }
4056             }
4057             else
4058             {
4059                if(x < line.count || (style.freeCaret && line.count < maxLineSize))
4060                {
4061                   if(x < line.count)
4062                   {
4063                      byte * buffer = line.buffer;
4064                      while(++x)
4065                      {
4066                         byte ch = buffer[x];
4067                         if(UTF8_IS_FIRST(ch)) break;
4068                      }
4069                   }
4070                   else
4071                      x++;
4072                   ComputeColumn();
4073                   DirtyLine(y);
4074                }
4075                else
4076                {
4077                   if(line.next)
4078                   {
4079                      DirtyLine(y);
4080                      line = line.next;
4081                      y++;
4082                      x = 0;
4083                      col = 0;
4084                      DirtyLine(y);
4085                   }
4086                }
4087             }
4088             if(!shift) _Deselect();
4089             SetViewToCursor(true);
4090             // break;
4091             return false;
4092          }
4093          case up:
4094             if(key.ctrl)
4095             {
4096                if(!style.vScroll || hasVertScroll) break;
4097                LineUp();
4098                return false;
4099             }
4100             else
4101             {
4102                if(style.stuckCaret) break;
4103
4104                if(!shift) SelDirty();
4105                DirtyLine(this.y);
4106
4107                if(style.wrap)
4108                {
4109                }
4110                else if(line.prev)
4111                {
4112                   line = line.prev;
4113                   this.y--;
4114                   this.x = AdjustXPosition(line, caretX, true, null, MAXINT, 0);
4115                }
4116
4117                DirtyLine(this.y);
4118                if(!shift) _Deselect();
4119                ComputeColumn();
4120                SetViewToCursor(false);
4121
4122                /*
4123                if(caretY == this.y * space.h)
4124                {
4125                   if(line.prev)
4126                   {
4127                      line = line.prev;
4128                      this.y--;
4129                      if(!style.freeCaret)
4130                         this.x = Min(this.x, line.count);
4131                      caretY = MAXINT;
4132                   }
4133                   else
4134                      return false;
4135                }
4136
4137                {
4138                   int th = space.h;
4139                   int textPos = 0;
4140                   int sx = 0, sy = this.y * space.h;
4141                   char * text = line.text;
4142                   int maxW = clientSize.w - sx;
4143                   display.FontExtent(font, " ", 1, null, &th);
4144
4145                   do
4146                   {
4147                      int startPos = textPos;
4148                      int width = 0;
4149                      int x = 0;
4150                      bool lineComplete = false;
4151
4152                      if(!style.wrap && caretY == MAXINT)
4153                      {
4154                         caretY = sy + th;
4155                         //textPos = line.count;
4156                         //lineComplete = true;
4157                      }
4158
4159                      for(; (style.freeCaret || textPos < line.count) && !lineComplete;)
4160                      {
4161                         int w = 0;
4162                         int len;
4163                         char * nextSpace = (textPos < line.count) ? strchr(text + textPos, ' ') : (text + textPos);
4164
4165                         if(nextSpace)
4166                            len = (nextSpace - (text + textPos));
4167                         else
4168                            len = line.count - textPos;
4169
4170                         if(textPos < line.count)
4171                         {
4172                            display.FontExtent(font, text + textPos, len, &w, null);
4173                         }
4174                         if(nextSpace) { w += space.w; len++; }
4175
4176                         if(style.wrap && x + width + w > maxW && x > 0)
4177                         {
4178                            lineComplete = true;
4179                            break;
4180                         }
4181                         textPos += len;
4182                         width += w;
4183                         if(nextSpace)
4184                         {
4185                            x += width;
4186                            width = 0;
4187                         }
4188                         if((!style.freeCaret && textPos >= line.count) || (sy == caretY - th && caretX <= x + width + sx))
4189                         {
4190                            x += width;
4191                            this.x = textPos;
4192                            while(this.x > 0 && x + sx > caretX && this.x > startPos)
4193                            {
4194                               int len;
4195                               if(this.x > line.count)
4196                                  this.x--;
4197                               else
4198                                  while(this.x > 0 && !UTF8_IS_FIRST(text[--this.x]));
4199                               len = this.x - startPos;
4200                               display.FontExtent(font, text + startPos, len, &x, null);
4201                            }
4202
4203                            DirtyLine(this.y);
4204                            if(!shift) _Deselect();
4205                            ComputeColumn();
4206                            SetViewToCursor(false);
4207                            return false;
4208                         }
4209                      }
4210                      if(sy == caretY - th || textPos >= line.count)
4211                      {
4212                         if(textPos >= line.count)
4213                         {
4214                            int c = textPos - 1;
4215                            while(c > 0 && text[c] == ' ') c--;
4216                            this.x = c + 1;
4217                         }
4218                         else
4219                            this.x = line.count;
4220
4221                         DirtyLine(this.y);
4222                         if(!shift) _Deselect();
4223                         ComputeColumn();
4224                         SetViewToCursor(false);
4225                         return false;
4226                      }
4227                      sy += th;
4228                      sx = 0;
4229                   } while(textPos < line.count);
4230
4231                   DirtyLine(this.y);
4232                   if(!shift) _Deselect();
4233                   ComputeColumn();
4234                   SetViewToCursor(false);
4235                   return false;
4236                }
4237                */
4238
4239                // PREVIOUS CODE
4240                /*
4241                if(this.line.prev)
4242                {
4243                   int x = AdjustXPosition(this.line, this.line.prev, true, null, MAXINT, 0);
4244                   if(!shift) SelDirty();
4245                   this.line = this.line.prev;
4246                   DirtyLine(this.y);
4247                   this.y--;
4248
4249                   DirtyLine(this.y);
4250                   this.x = x;
4251                   if(!shift) _Deselect();
4252
4253                   ComputeColumn();
4254
4255                   SetViewToCursor(false);
4256                }
4257                */
4258
4259             }
4260             // break;
4261             return style.multiLine ? false : true;
4262          case down:
4263             if(key.ctrl)
4264             {
4265                if(!style.vScroll || hasVertScroll)
4266                   break;
4267                LineDown();
4268                return false;
4269             }
4270             else
4271             {
4272                if(style.stuckCaret) break;
4273                {
4274                   int th = space.h;
4275                   int textPos = 0;
4276                   int sx = 0, sy = this.y * this.space.h;
4277                   int maxW = clientSize.w - sx;
4278                   char * text = line.buffer;
4279
4280                   if(!shift) SelDirty();
4281                   DirtyLine(this.y);
4282
4283                   if(style.wrap)
4284                   {
4285                      /*
4286                      if(AdjustXPosition(line, maxW, this.x, line.count, true, null, MAXINT, 0) <= line.count)
4287                      {
4288
4289                      }
4290                      */
4291                   }
4292                   else if(line.next)
4293                   {
4294                      line = line.next;
4295                      this.y++;
4296                      this.x = AdjustXPosition(line, caretX, true, null, MAXINT, 0);
4297                   }
4298
4299                   DirtyLine(this.y);
4300                   if(!shift) _Deselect();
4301                   ComputeColumn();
4302                   SetViewToCursor(false);
4303
4304                   /*
4305                   while(!textPos || (style.freeCaret || textPos<line.count))
4306                   {
4307                      int startPos = textPos;
4308                      int width = 0;
4309                      int x = 0;
4310                      bool lineComplete = false;
4311                      if(!style.wrap && sy <= caretY)
4312                      {
4313                         textPos = line.count;
4314                         lineComplete = true;
4315                      }
4316                      for(; (style.freeCaret || textPos<line.count) && !lineComplete;)
4317                      {
4318                         int w = 0;
4319                         int len;
4320                         char * nextSpace = (textPos < line.count) ? strchr(text + textPos, ' ') : (text + textPos);
4321
4322                         if(nextSpace)
4323                            len = (nextSpace - (text + textPos));
4324                         else
4325                            len = line.count - textPos;
4326
4327                         if(textPos < line.count)
4328                         {
4329                            display.FontExtent(font, text + textPos, len, &w, &th);
4330                         }
4331                         if(nextSpace) { w += space.w; len++; }
4332                         if(style.wrap && x + width + w > maxW && x > 0)
4333                         {
4334                            lineComplete = true;
4335                            break;
4336                         }
4337                         textPos += len;
4338                         width += w;
4339                         if(nextSpace)
4340                         {
4341                            x += width;
4342                            width = 0;
4343                         }
4344                         if(sy > caretY && ((!style.freeCaret && textPos >= line.count) || caretX <= x + width + sx))
4345                         {
4346                            this.x = textPos;
4347                            x += width;
4348                            while(this.x > 0 && x + sx > caretX && textPos > startPos)
4349                            {
4350                               int len;
4351                               if(this.x > line.count)
4352                                  this.x--;
4353                               else
4354                                  while(this.x > 0 && !UTF8_IS_FIRST(text[--this.x]));
4355
4356                               len = this.x - startPos;
4357                               display.FontExtent(font, text + startPos, len, &x, null);
4358                            }
4359
4360                            if(!shift) _Deselect();
4361                            ComputeColumn();
4362
4363                            SetViewToCursor(false);
4364                            return false;
4365                         }
4366                      }
4367                      if(sy > caretY)
4368                      {
4369                         this.x = line.count;
4370
4371                         DirtyLine(this.y);
4372                         if(!shift) _Deselect();
4373                         ComputeColumn();
4374
4375                         SetViewToCursor(false);
4376                         return false;
4377                      }
4378                      else if(textPos >= line.count && line.next)
4379                      {
4380                         startPos = 0;
4381                         textPos = 0;
4382                         line = line.next;
4383                         this.y++;
4384                         sy = this.y * this.space.h;
4385                         sx = 0; //textBlock.startX;
4386                         text = line.buffer;
4387                      }
4388                      else
4389                      {
4390                         sy += th;
4391                         sx = 0; //textBlock.startX;
4392                      }
4393                   }
4394                   */
4395                   /*
4396                   if(line.next)
4397                   {
4398                      line = line.next;
4399                      this.x = Min(this.x, line.count);
4400                      //int x = AdjustXPosition(this.line, this.line.next, true, null, MAXINT, 0);
4401                      this.y++;
4402                      if(!shift) _Deselect();
4403                      ComputeColumn();
4404
4405                      if(this.selX != this.x || this.selY != this.y)
4406                         DirtyLine(this.y);
4407
4408                      SetViewToCursor(false);
4409                   }
4410                   */
4411                }
4412                /* PREVIOUS CODE
4413                if(this.line.next)
4414                {
4415                   int x = AdjustXPosition(this.line, this.line.next, true, null, MAXINT, 0);
4416                   if(!shift) SelDirty();
4417                   this.line = this.line.next;
4418                   DirtyLine(this.y);
4419                   this.y++;
4420                   this.x = x;
4421                   if(!shift) _Deselect();
4422                   ComputeColumn();
4423
4424                   if(this.selX != this.x || this.selY != this.y)
4425                      DirtyLine(this.y);
4426
4427                   SetViewToCursor();
4428                }
4429                */
4430             }
4431             // break;
4432             return style.multiLine ? false : true;
4433          case home:
4434          {
4435             if(style.stuckCaret) break;
4436             if(!style.multiLine && key.ctrl) break;
4437             if(!(style.freeCaret))
4438                this.selX = Min(this.selX, this.selLine.count);
4439
4440             if(!shift) SelDirty();
4441             if(key.ctrl)
4442             {
4443                this.line = this.lines.first;
4444                if(this.y != 0 || this.x != 0)
4445                   DirtyAll();
4446                this.y = 0;
4447                this.x = 0;
4448                this.col = 0;
4449             }
4450             else
4451             {
4452                if(style.smartHome)
4453                {
4454                   EditLine line = this.line;
4455                   int c;
4456                   for(c=0; line.buffer[c]; c++)
4457                      if(line.buffer[c] != ' ' && line.buffer[c] != '\t')
4458                         break;
4459                   if(shift && (c != 0 || this.x))
4460                      DirtyLine(this.y);
4461                   if(this.x != c)
4462                      this.x = c;
4463                   else
4464                      this.x = 0;
4465                }
4466                else
4467                {
4468                   if(shift && this.x != 0)
4469                      DirtyLine(this.y);
4470                   this.x = 0;
4471                }
4472                ComputeColumn();
4473             }
4474             if(!shift) _Deselect();
4475             SetViewToCursor(true);
4476             //break;
4477             return false;
4478          }
4479          case end:
4480          {
4481             if(style.stuckCaret) break;
4482             if(!style.multiLine && key.ctrl) break;
4483             if(!style.freeCaret)
4484                this.selX = Min(this.selX, this.selLine.count);
4485
4486             if(!shift) SelDirty();
4487             if(key.ctrl)
4488             {
4489                GoToEnd(false);
4490             }
4491             else if(this.x != this.line.count)
4492             {
4493                this.x = this.line.count;
4494                if(shift)
4495                   DirtyLine(this.y);
4496                ComputeColumn();
4497             }
4498             if(!shift) _Deselect();
4499             SetViewToCursor(true);
4500             //break;
4501             return false;
4502          }
4503          case tab:
4504             if(style.tabKey && !key.ctrl)
4505             {
4506                if(this.selY != this.y && style.tabSel)
4507                {
4508                   EditLine firstLine, lastLine;
4509                   EditLine line;
4510                   int y, x;
4511
4512                   // Do multi line selection tabbing here
4513                   if(this.selY < this.y)
4514                   {
4515                      firstLine = this.selLine;
4516                      lastLine = this.line;
4517                      y = this.selY;
4518                      x = this.x;
4519                   }
4520                   else
4521                   {
4522                      // Selecting going up
4523                      firstLine = this.line;
4524                      lastLine = this.selLine;
4525                      y = this.y;
4526                      x = this.selX;
4527                   }
4528                   ComputeColumn();
4529                   if(shift)
4530                   {
4531                      for(line = firstLine; line; line = line.next, y++)
4532                      {
4533                         if(line != lastLine || x)
4534                         {
4535                            int c;
4536                            int lastC = 0;
4537                            BufferLocation before = { line, y, 0 }, after = { line, y, 0 };
4538
4539                            for(c=0; c<line.count && lastC < this.tabSize; c++, lastC++)
4540                            {
4541                               if(line.buffer[c] == '\t')
4542                               {
4543                                  lastC++;
4544                                  break;
4545                               }
4546                               else if(line.buffer[c] != ' ')
4547                                  break;
4548                            }
4549                            after.x = lastC;
4550
4551                            NotifyCharsDeleted(master, this, &before, &after, this.pasteOperation);
4552                            if(lastC)
4553                            {
4554                               int len = GetText(null, line, y, 0, line, y, lastC, false, false);
4555                               char * string = new char[len];
4556                               DelTextAction action { y1 = y, x1 = 0, y2 = y, x2 = lastC, string = string, placeAfter = true };
4557                               GetText(string, line, y, 0, line, y, lastC, false, false);
4558                               Record(action);
4559                            }
4560                            memmove(line.buffer,line.buffer+lastC,line.size-lastC);
4561                            if(!line.AdjustBuffer(line.count-lastC))
4562                               break;
4563                            line.count-=lastC;
4564                            DirtyLine(y);
4565                         }
4566
4567                         if(line == lastLine) break;
4568                      }
4569                   }
4570                   else
4571                   {
4572                      for(line = firstLine; line; line = line.next, y++)
4573                      {
4574                         if(line.count)
4575                         {
4576                            if(line != lastLine || x)
4577                            {
4578                               if(style.useTab)
4579                               {
4580                                  if(line.count + 1 <= this.maxLineSize)
4581                                  {
4582                                     BufferLocation before = { line, y, 0 }, after = { line, y, 1 };
4583
4584                                     if(!line.AdjustBuffer(line.count+1))
4585                                        break;
4586
4587                                     NotifyCharsAdded(master, this, &before, &after, this.pasteOperation);
4588                                     {
4589                                        AddCharAction action { ch = '\t', x = 0, y = y };
4590                                        Record(action);
4591                                     }
4592
4593                                     memmove(line.buffer+1,line.buffer,line.size-1);
4594                                     line.count++;
4595                                     line.buffer[0] = '\t';
4596                                  }
4597                               }
4598                               else
4599                               {
4600                                  if(line.count + this.tabSize <= this.maxLineSize)
4601                                  {
4602                                     int c;
4603                                     BufferLocation before = { line, y, 0 }, after = { line, y, this.tabSize };
4604                                     NotifyCharsAdded(master, this, &before, &after, this.pasteOperation);
4605
4606                                     if(!line.AdjustBuffer(line.count+this.tabSize))
4607                                        break;
4608
4609                                     {
4610                                        char * string = new char[this.tabSize + 1];
4611                                        memset(string, ' ', this.tabSize);
4612                                        string[this.tabSize] = '\0';
4613                                        Record(AddTextAction { string = string, x1 = 0, y1 = y, x2 = this.tabSize, y2 = y });
4614                                     }
4615
4616                                     memmove(line.buffer+this.tabSize,line.buffer,line.size-(this.tabSize));
4617                                     line.count+=this.tabSize;
4618                                     for(c=0; c<this.tabSize; c++)
4619                                        line.buffer[c] = ' ';
4620                                  }
4621                               }
4622                               DirtyLine(y);
4623                            }
4624                         }
4625                         if(line == lastLine) break;
4626                      }
4627                   }
4628                   ComputeLength(maxLine);
4629                }
4630                else
4631                {
4632                   if(style.useTab)
4633                   {
4634                      // Insert character
4635                      AddCh('\t');
4636                   }
4637                   else
4638                   {
4639                      int start, c;
4640                      char * addString = new char[this.tabSize + 1];
4641                      int len = 0;
4642                      if(!(style.freeCaret))
4643                      {
4644                         this.x = Min(this.x, this.line.count);
4645                         ComputeColumn();
4646                      }
4647                      // Insert spaces
4648                      start = Min(this.x, this.selX);
4649                      for(c=start; ((c == start) || ((c) % this.tabSize)); c++)
4650                      {
4651                         addString[len++] = ' ';
4652                      }
4653                      addString[len] = 0;
4654                      AddS(addString);
4655                      delete addString;
4656                   }
4657                }
4658                Modified();
4659                SetViewToCursor(true);
4660                return false;
4661             }
4662             break;
4663          case pageDown:
4664             if(style.multiLine)
4665             {
4666                if(key.ctrl)
4667                {
4668                   if(!(style.hScroll) || hasHorzScroll) break;
4669                   if(this.viewX < this.maxLength)
4670                   {
4671                      //this.viewX+=this.space.w*this.tabSize;
4672                      //DirtyAll();
4673                      SetScrollPosition((this.viewX + this.space.w*this.tabSize), this.viewY * this.space.h);
4674                   }
4675                }
4676                else
4677                {
4678                   PageDown();
4679                   DirtyAll();
4680                   if(!shift) _Deselect();
4681                   SetCursorToViewX();
4682                   SetCursorToViewY();
4683                }
4684                return false;
4685             }
4686             break;
4687          case pageUp:
4688             if(style.multiLine)
4689             {
4690                if(key.ctrl)
4691                {
4692                   if(!(style.hScroll) || hasHorzScroll) break;
4693                   if(this.viewX > 0)
4694                   {
4695                      //this.viewX-=this.space.w*this.tabSize;
4696                      //this.viewX = Max(this.viewX,0);
4697                      //DirtyAll();
4698                      SetScrollPosition((this.viewX-this.space.w*this.tabSize), this.viewY * this.space.h);
4699                      // SetCursorToView();
4700                   }
4701                }
4702                else
4703                {
4704                   PageUp();
4705                   DirtyAll();
4706                   if(!shift) _Deselect();
4707                   SetCursorToViewX();
4708                   SetCursorToViewY();
4709                }
4710                return false;
4711             }
4712             break;
4713          case insert:
4714             if(key.ctrl)
4715             {
4716                Copy();
4717                return false;
4718             }
4719             else if(!style.readOnly)
4720             {
4721                if(key.shift)
4722                {
4723                   if(!(style.readOnly))
4724                      Paste();
4725                   return false;
4726                }
4727                else
4728                {
4729                   this.overwrite ^= 1;
4730                   UpdateCaretPosition(true);
4731                   if(this.overwrite)
4732                      SetCaret(0,0,0);
4733                   DirtyLine(this.y);
4734                   UpdateDirty();
4735                   NotifyOvrToggle(master, this, this.overwrite);
4736                }
4737             }
4738             break;
4739          case hotKey:
4740             break;
4741          default:
4742
4743             switch(key)
4744             {
4745                case ctrlA:
4746                   // Select All
4747                   if(style.noSelect) break;
4748
4749                   {
4750                      //Save current view position
4751                      int tempX = this.viewX;
4752                      int tempY = this.viewY;
4753
4754                      this.selX = 0;
4755                      this.selY = 0;
4756                      this.selLine = this.lines.first;
4757                      this.y = this.lineCount-1;
4758                      this.line = this.lines.last;
4759                      this.x = this.line.count;
4760                      ComputeColumn();
4761                      DirtyAll();
4762                      SetViewToCursor(true);
4763
4764                      //Restore previous view position
4765                      SetScrollPosition(tempX, tempY * this.space.h);
4766
4767                      UpdateDirty();
4768                      SetSelectCursor();
4769                      SelectionEnables();
4770                   }
4771                   // TOCHECK: Was there any good reason why we weren't returning false here?
4772                   return false; // break;
4773                case ctrlC:
4774                   Copy();
4775                   return false;
4776                case ctrlV:
4777                   if(!(style.readOnly))
4778                   {
4779                      Paste();
4780                      return false;
4781                   }
4782                   break;
4783                case ctrlX:
4784                {
4785                   if(style.readOnly) break;
4786                   Cut();
4787                   return false;
4788                }
4789                case ctrlL:
4790                {
4791                   if(style.readOnly) break;
4792                   ClearLine();
4793                   return false;
4794                }
4795                case ctrlZ:
4796                   if(style.readOnly) break;
4797                   Undo();
4798                   return false;
4799                case ctrlY:
4800                   if(style.readOnly) break;
4801                   Redo();
4802                   return false;
4803                default:
4804                   if(style.readOnly) break;
4805                   if(key.shift && key.code == rightBracket)
4806                   {
4807                      //Only indent back if you are exactly at one tab.
4808                      {
4809                         bool whitespace = true;
4810                         int i;
4811                         char * newline;
4812                         int putsize;
4813
4814                         int indentwidth;
4815                         EditLine line = this.line;
4816
4817                         //Only remove one tab if there is nothing else on the line.
4818                         for(i = 0; i < this.line.count; i++)
4819                         {
4820                            if(this.line.buffer[i] != ' ' && this.line.buffer[i] != '\t')
4821                            {
4822                               PutCh(ch);
4823                               return false;
4824                            }
4825                         }
4826
4827                         //Find opening {
4828                         i = 1;
4829                         while(i && line)
4830                         {
4831                            int pos;
4832
4833                            indentwidth = 0;
4834                            for(pos = line.count - 1; pos >= 0; pos--)
4835                            {
4836                               char c = line.buffer[pos];
4837                               if(c == ' ')
4838                                  indentwidth++;
4839                               else if(c == '\t')
4840                                  indentwidth += this.tabSize;
4841                               else
4842                                  //Counting backwards, so when you find a character, indentation is reset
4843                                  indentwidth = 0;
4844                               if(i && c == '}')
4845                                  i++;
4846                               if(i && c == '{')
4847                                  i--;
4848                            }
4849                            line = line.prev;
4850                         }
4851
4852                         //Place the } to get an undo:
4853                         PutCh(ch);
4854
4855                         this.x = this.line.count;
4856                         this.selX = 0;
4857
4858                         if(!style.useTab)
4859                            putsize = indentwidth;
4860                         else
4861                            putsize = indentwidth / this.tabSize + indentwidth % this.tabSize;
4862
4863                         newline = new char[putsize+2];
4864                         newline[putsize] = '}';
4865                         newline[putsize+1] = '\0';
4866
4867                         i = 0;
4868                         if(style.useTab)
4869                            for(; i < indentwidth / this.tabSize; i++)
4870                               newline[i] = '\t';
4871                         for(;i < putsize; i++)
4872                            newline[i] = ' ';
4873
4874                         AddS(newline);
4875
4876                         delete newline;
4877                      }
4878                      return false;
4879                   }
4880                   else if(!key.ctrl && !key.alt && ch != 128 && ch >= 32)
4881                   {
4882                      PutCh(ch);
4883                      return false;
4884                   }
4885                   break;
4886             }
4887             break;
4888       }
4889       return true;
4890    }
4891
4892    void OnHScroll(ScrollBarAction action, int position, Key key)
4893    {
4894 #ifdef _DEBUG
4895       //PrintLn("OnHScroll: ", action, ", pos = ", position, ", key = ", key);
4896 #endif
4897       this.viewX = position;
4898       if(action != setRange)
4899       {
4900          if(!this.mouseMove && style.cursorFollowsView)
4901             SetCursorToViewX();
4902       }
4903       DirtyAll();
4904       UpdateDirty();
4905    }
4906
4907    void OnVScroll(ScrollBarAction action, int position, Key key)
4908    {
4909       int oldViewY = this.viewY;
4910
4911 #ifdef _DEBUG
4912       //PrintLn("OnVScroll: ", action, ", pos = ", position, ", key = ", key);
4913 #endif
4914       position /= this.space.h;
4915
4916       if(position < this.viewY)
4917       {
4918          for(; position < this.viewY && this.viewLine.prev; this.viewLine = this.viewLine.prev, this.viewY--);
4919          style.recomputeSyntax = true;
4920       }
4921       else if(position > this.viewY)
4922       {
4923          EditLine oldViewLine = viewLine;
4924          for(; position > this.viewY && this.viewLine.next; this.viewLine = this.viewLine.next, this.viewY++);
4925          FigureStartSyntaxStates(oldViewLine, false);
4926       }
4927
4928       if(action != setRange)
4929       {
4930          if(!this.mouseMove && style.cursorFollowsView && !SelSize()) SetCursorToViewY();
4931       }
4932
4933       if(this.x != this.selX || this.y != this.selY)
4934          DirtyLine(this.y);
4935
4936       {
4937          Scroll(0, (this.viewY - oldViewY) * this.space.h);
4938
4939          /*
4940          int numLines = clientSize.h / this.space.h;
4941          int y;
4942          if(Abs(this.viewY - oldViewY) < numLines)
4943
4944
4945          if(this.viewY > oldViewY)
4946          {
4947             for(y = oldViewY; y <this.viewY; y++)
4948                DirtyLine(y + numLines);
4949          }
4950          else
4951          {
4952             for(y = this.viewY; y <oldViewY; y++)
4953                DirtyLine(y + numLines);
4954          }*/
4955       }
4956       //DirtyAll();
4957
4958       // Fix dirt of stuff before first line...
4959       if(this.viewY - oldViewY > 0)
4960       {
4961          Box box { 0,0, clientSize.w-1, YOFFSET-1 };
4962          Update(box);
4963       }
4964
4965       UpdateDirty();
4966    }
4967
4968    bool _AddCh(unichar ch, int * addedSpacesPtr, int * addedTabsPtr)
4969    {
4970       EditLine line;
4971       int length, endX;
4972       bool result;
4973       ReplaceTextAction replaceAction = null;
4974       AddCharAction addCharAction = null;
4975       int addedSpaces = 0, addedTabs = 0;
4976
4977       if(ch == '\r') return true;
4978       if(style.stuckCaret /*|EES_READONLY)*/ )
4979          GoToEnd(true);
4980
4981       if(ch == '\n' && !(style.multiLine) && this.line) return false;
4982
4983       if(!undoBuffer.dontRecord)
4984       {
4985          if(selX != x || selY != y)
4986          {
4987             char buffer[5];
4988             char * newString;
4989             char * oldString;
4990             int len = GetText(null, selLine, selY, selX, this.line, y, x, false, false);
4991             oldString = new char[len];
4992             UTF32toUTF8Len(&ch, 1, buffer, 4);
4993             newString = CopyString(buffer);
4994             GetText(oldString, selLine, selY, selX, this.line, y, x, false, false);
4995
4996             replaceAction = ReplaceTextAction { newString = newString, oldString = oldString, placeAfter = false };
4997             if(selY < y || (selY == y && selX < x))
4998             {
4999                replaceAction.x1 = selX;
5000                replaceAction.y1 = selY;
5001                replaceAction.x2 = x;
5002                replaceAction.y2 = y;
5003             }
5004             else
5005             {
5006                replaceAction.x1 = x;
5007                replaceAction.y1 = y;
5008                replaceAction.x2 = selX;
5009                replaceAction.y2 = selY;
5010             }
5011             Record(replaceAction);
5012             undoBuffer.dontRecord++;
5013          }
5014          else
5015          {
5016             addCharAction = AddCharAction { y = y, x = x, ch = ch };
5017             Record(addCharAction);
5018          }
5019       }
5020
5021       if(ch == '\n')
5022       {
5023          DelSel(&addedSpaces);
5024          if(this.lineCount+1 > this.maxLines)
5025          {
5026             if(style.autoEmpty)
5027                Emptyline(this.lines.first,0);
5028             else
5029                return false;
5030          }
5031          if(!(style.autoSize && (!maxClientSize.h || maxClientSize.h > clientSize.h + this.space.h)) && !(style.vScroll))
5032          {
5033             // Make sure it fits, but we need a default line is this.font is too big for window
5034             if(this.space.h * (this.lineCount+1) > clientSize.h && this.line)
5035                return false;
5036          }
5037          if((this.y >= 0) && this.y < this.viewY)
5038          {
5039             this.viewY++;
5040             DirtyAll();
5041          }
5042          line = EditLine { };
5043          if(!line)
5044             return false;
5045          lines.Insert(this.line, line);
5046          line.editBox = this;
5047          line.buffer = null;
5048          line.count = 0;
5049          line.size = 0;
5050          length = 0;
5051
5052          // If we're displacing the lines from a current line ...
5053          if(this.line)
5054          {
5055             if(this.line.buffer)
5056             {
5057                endX = this.x;
5058                if(this.line.count < endX) endX = this.line.count;
5059                length = this.line.count - endX;
5060             }
5061          }
5062          if(!line.AdjustBuffer(length))
5063             return false;
5064          if(this.line)
5065          {
5066             if(this.line.buffer)
5067             {
5068                CopyBytes(line.buffer,this.line.buffer+endX, length+1);
5069 #ifdef _DEBUG
5070       if(endX > 4000 || endX < 0)
5071          printf("Warning");
5072 #endif
5073                this.line.count = endX;
5074                this.line.buffer[this.line.count] = '\0';
5075                this.line.AdjustBuffer(this.line.count);
5076                ComputeLength(this.line);
5077             }
5078          }
5079          {
5080             BufferLocation before = { this.line, this.y, this.x }, after;
5081
5082             this.line = line;
5083             this.x = 0;
5084             this.col = 0;
5085             line.count = length;
5086
5087 #ifdef _DEBUG
5088       if(length > 4000 || length < 0)
5089          printf("Warning");
5090 #endif
5091             ComputeLength(this.line);
5092
5093             DirtyEnd(this.y);
5094             this.y++;
5095             this.lineCount++;
5096             line.buffer[line.count] = '\0';
5097             result = true;
5098
5099             after.line = this.line, after.y = this.y, after.x = this.x;
5100
5101             NotifyCharsAdded(master, this, &before, &after, this.pasteOperation);
5102          }
5103       }
5104       else
5105       {
5106          char string[5];
5107          int count = UTF32toUTF8Len(&ch, 1, string, 5);
5108          DelSel(&addedSpaces);
5109          result = AddToLine(string, count, false, addedSpaces ? null : &addedSpaces, &addedTabs);
5110          if(addedSpacesPtr) *addedSpacesPtr = addedSpaces;
5111          if(addedTabsPtr) *addedTabsPtr = addedTabs;
5112       }
5113       this.selX = this.x;
5114       this.selY = this.y;
5115       this.selLine = this.line;
5116
5117       if(replaceAction)
5118       {
5119          replaceAction.x3 = x;
5120          replaceAction.y3 = y;
5121          replaceAction.addedSpaces = addedSpaces;
5122          replaceAction.addedTabs = addedTabs;
5123          undoBuffer.dontRecord--;
5124       }
5125       if(addCharAction)
5126       {
5127          addCharAction.x -= addedTabs * (tabSize-1);
5128          addCharAction.addedSpaces = addedSpaces;
5129          addCharAction.addedTabs = addedTabs;
5130       }
5131       return result;
5132    }
5133
5134 public:
5135
5136    /****************************************************************************
5137                                  EDIT BOX METHODS
5138    ****************************************************************************/
5139
5140
5141    bool AddCh(unichar ch)
5142    {
5143       return _AddCh(ch, null, null);
5144    }
5145
5146    void Modified()
5147    {
5148       this.modified = true;
5149       NotifyUpdate(master, this);
5150       modifiedDocument = true;
5151    }
5152
5153    void Delete(EditLine line1, int y1, int x1, EditLine line2, int y2, int x2)
5154    {
5155       Deselect();
5156       _DelCh(line1, y1, x1, line2, y2, x2, false, false, null);
5157       SetViewToCursor(true);
5158       UpdateDirty();
5159       Modified();
5160    }
5161
5162    void Undo()
5163    {
5164       undoBuffer.Undo();
5165       itemEditUndo.disabled = undoBuffer.curAction == 0;
5166       itemEditRedo.disabled = undoBuffer.curAction == undoBuffer.count;
5167       if(savedAction == undoBuffer.curAction)
5168       {
5169          modifiedDocument = false;
5170          SetModified(false);
5171          NotifyUnsetModified(master, this);
5172       }
5173    }
5174
5175    void Redo()
5176    {
5177       undoBuffer.Redo();
5178       itemEditUndo.disabled = undoBuffer.curAction == 0;
5179       itemEditRedo.disabled = undoBuffer.curAction == undoBuffer.count;
5180       if(savedAction == undoBuffer.curAction)
5181       {
5182          modifiedDocument = false;
5183          SetModified(false);
5184          NotifyUnsetModified(master, this);
5185       }
5186    }
5187
5188    void Record(UndoAction action)
5189    {
5190       if(!undoBuffer.dontRecord)
5191       {
5192          undoBuffer.Record(action);
5193          itemEditUndo.disabled = undoBuffer.curAction == 0;
5194          itemEditRedo.disabled = undoBuffer.curAction == undoBuffer.count;
5195       }
5196    }
5197
5198    void SelectAll()
5199    {
5200       Select(
5201          this.lines.first, 0,0,
5202          this.lines.last, this.lines.count-1, strlen(((EditLine)this.lines.last).buffer));
5203    }
5204
5205    void Select(EditLine line1, int y1, int x1, EditLine line2, int y2, int x2)
5206    {
5207       SelDirty();
5208       this.selX = x1;
5209       this.selY = y1;
5210       this.selLine = line1 ? (EditLine)line1 : this.lines.first;
5211       this.x = line2 ? x2 : ((EditLine)this.lines.last).count;
5212       this.y = line2 ? y2 : (this.lineCount-1);
5213       this.line = line2 ? (EditLine)line2 : this.lines.last;
5214       ComputeColumn();
5215       SelDirty();
5216       SetViewToCursor(true);
5217       UpdateDirty();
5218    }
5219
5220    // TODO: Fix this vs modifiedDocument window property
5221    void SetModified(bool flag)
5222    {
5223       if(this)
5224       {
5225          this.modified = false;
5226          if(flag && !NotifyModified(master, this))
5227             this.modified = true;
5228       }
5229    }
5230
5231    // BASIC OUTPUT
5232    bool AddS(char * string)
5233    {
5234       if(this)
5235       {
5236          bool ret = true;
5237          char * line;
5238          int c, count;
5239          int addedSpaces = 0, addedTabs = 0;
5240          AddTextAction action = null;
5241          ReplaceTextAction replaceAction = null;
5242
5243          this.pasteOperation = true;
5244
5245          if(style.stuckCaret /*|EES_READONLY)*/ )
5246             GoToEnd(true);
5247
5248          if(!undoBuffer.dontRecord)
5249          {
5250             char * placeString = CopyString(string);
5251             if(!style.multiLine)
5252             {
5253                int i;
5254                char ch;
5255                for(i = 0; (ch = placeString[i]); i++)
5256                   if(ch == '\n')
5257                   {
5258                      placeString[i] = '\0';
5259                      placeString = renew placeString byte[i+1];
5260                      break;
5261                   }
5262             }
5263
5264             if(selX != x || selY != y)
5265             {
5266                char * newString;
5267                char * oldString;
5268                int len = GetText(null, selLine, selY, selX, this.line, y, x, false, false);
5269                oldString = new char[len];
5270                newString = placeString;
5271                GetText(oldString, selLine, selY, selX, this.line, y, x, false, false);
5272
5273                replaceAction = ReplaceTextAction { newString = newString, oldString = oldString, placeAfter = false };
5274                if(selY < y || (selY == y && selX < x))
5275                {
5276                   replaceAction.x1 = selX;
5277                   replaceAction.y1 = selY;
5278                   replaceAction.x2 = x;
5279                   replaceAction.y2 = y;
5280                }
5281                else
5282                {
5283                   replaceAction.x1 = x;
5284                   replaceAction.y1 = y;
5285                   replaceAction.x2 = selX;
5286                   replaceAction.y2 = selY;
5287                }
5288                Record(replaceAction);
5289             }
5290             else if(string[0])
5291             {
5292                if(string[0] == '\n')
5293                   action = AddTextAction { y1 = y, x1 = Min(this.line.count, x), string = placeString };
5294                else
5295                   action = AddTextAction { y1 = y, x1 = x, string = placeString };
5296
5297                Record(action);
5298             }
5299             else
5300                delete placeString;
5301          }
5302
5303          undoBuffer.dontRecord++;
5304          DelSel(&addedSpaces);
5305
5306          count = 0;
5307          line = string;
5308          for(c = 0; string[c]; c++)
5309          {
5310             if(string[c] == '\n' || string[c] == '\r')
5311             {
5312                if(!AddToLine(line,count, true, addedSpaces ? null : &addedSpaces, addedTabs ? null : &addedTabs))
5313                {
5314                   ret = false;
5315                   break;
5316                }
5317                if(string[c] == '\n')
5318                {
5319                   if(!AddCh('\n'))
5320                   {
5321                      count = 0;
5322                      ret = false;
5323                      break;
5324                   }
5325                }
5326                // Reset for next line
5327                count = 0;
5328                line = string+c+1;
5329                /*
5330                if(string[c] == '\r' && *line == '\n')
5331                {
5332                   line++;
5333                   c++;
5334                }*/
5335             }
5336             else
5337             {
5338                count++;
5339             }
5340          }
5341
5342          // Why was this here?
5343          // FindMaxLine();
5344
5345          // Add the line here
5346          if(ret && count)
5347             if(!AddToLine(line,count,false, addedSpaces ? null : &addedSpaces, addedTabs ? null : &addedTabs))
5348             {
5349                ret = false;
5350             }
5351          FindMaxLine();
5352
5353          undoBuffer.dontRecord--;
5354          if(action)
5355          {
5356             action.y2 = y;
5357             action.x2 = x;
5358             action.addedSpaces = addedSpaces;
5359             action.addedTabs = addedTabs;
5360          }
5361          else if(replaceAction)
5362          {
5363             replaceAction.y3 = y;
5364             replaceAction.x3 = x;
5365             replaceAction.addedSpaces = addedSpaces;
5366             replaceAction.addedTabs = addedTabs;
5367          }
5368
5369          UpdateCaretPosition(true);
5370
5371          this.pasteOperation = false;
5372
5373          return ret;
5374       }
5375       return false;
5376    }
5377
5378    // CONSOLE OUTPUT
5379    void Clear()
5380    {
5381       if(this)
5382       {
5383          Deselect();
5384          DelCh(this.lines.first, 0, 0, this.lines.last, this.lineCount-1, ((EditLine)(this.lines.last)).count, true);
5385          SetViewToCursor(true);
5386          UpdateDirty();
5387          Modified();
5388       }
5389    }
5390
5391    void PutCh(unichar ch)
5392    {
5393       bool result;
5394
5395       if((ch >= 32 /*&& ch <=126*/) || ch == '\n')
5396       //if((ch >= 32) || ch == '\n')
5397       {
5398          int addedSpaces = 0, addedTabs = 0;
5399          ReplaceTextAction replaceAction = null;
5400          AddCharAction addCharAction = null;
5401
5402          if(style.allCaps)
5403             ch = (ch < 128) ? toupper(ch) : ch;     // TODO: UNICODE TO UPPER
5404
5405          if(this.overwrite && selX == x && selY == y && this.x < this.line.count)
5406          {
5407             char buffer[5];
5408             char * newString;
5409             char * oldString;
5410             int len = GetText(null, line, y, x, line, y, x+1, false, false);
5411             oldString = new char[len];
5412             UTF32toUTF8Len(&ch, 1, buffer, 4);
5413             newString = CopyString(buffer);
5414             GetText(oldString, line, y, x, line, y, x+1, false, false);
5415             replaceAction = ReplaceTextAction { newString = newString, oldString = oldString, placeAfter = false };
5416             replaceAction.x1 = x;
5417             replaceAction.y1 = y;
5418             replaceAction.x2 = x+1;
5419             replaceAction.y2 = y;
5420             Record(replaceAction);
5421
5422             undoBuffer.dontRecord++;
5423             DelCh(this.line, this.y, this.x, this.line, this.y, this.x+1, true);
5424             undoBuffer.dontRecord--;
5425          }
5426          else if(!undoBuffer.dontRecord)
5427          {
5428             if(selX != x || selY != y)
5429             {
5430                char buffer[5];
5431                char * newString;
5432                char * oldString;
5433                int len = GetText(null, line, y, x, selLine, selY, selX, false, false);
5434                oldString = new char[len];
5435                UTF32toUTF8Len(&ch, 1, buffer, 4);
5436                newString = CopyString(buffer);
5437                GetText(oldString, line, y, x, selLine, selY, selX, false, false);
5438                replaceAction = ReplaceTextAction { newString = newString, oldString = oldString, placeAfter = true };
5439                if(selY < y || (selY == y && selX < x))
5440                {
5441                   replaceAction.x1 = selX;
5442                   replaceAction.y1 = selY;
5443                   replaceAction.x2 = x;
5444                   replaceAction.y2 = y;
5445                }
5446                else
5447                {
5448                   replaceAction.x1 = x;
5449                   replaceAction.y1 = y;
5450                   replaceAction.x2 = selX;
5451                   replaceAction.y2 = selY;
5452                }
5453                Record(replaceAction);
5454             }
5455             else
5456             {
5457                addCharAction = AddCharAction { y = y, x = x, ch = ch };
5458                Record(addCharAction);
5459             }
5460          }
5461          undoBuffer.dontRecord++;
5462          result = _AddCh(ch, &addedSpaces, &addedTabs);
5463          if(replaceAction)
5464          {
5465             replaceAction.x3 = x;
5466             replaceAction.y3 = y;
5467             replaceAction.addedSpaces = addedSpaces;
5468             replaceAction.addedTabs = addedTabs;
5469          }
5470          if(addCharAction)
5471          {
5472             addCharAction.x -= addedTabs * (tabSize-1);
5473             addCharAction.addedSpaces = addedSpaces;
5474             addCharAction.addedTabs = addedTabs;
5475          }
5476          undoBuffer.dontRecord--;
5477          if(ch == '\n')
5478             FindMaxLine();
5479          Modified();
5480          if(result) SetViewToCursor(true);
5481       }
5482    }
5483
5484    void PutS(char * string)
5485    {
5486       if(this)
5487       {
5488          AddS(string);
5489          SetViewToCursor(true);
5490          Modified();
5491       }
5492    }
5493
5494    void Printf(char * format, ...)
5495    {
5496       if(this)
5497       {
5498          char temp[MAX_F_STRING];
5499          va_list args;
5500          va_start(args, format);
5501          vsnprintf(temp, sizeof(temp), format, args);
5502          temp[sizeof(temp)-1] = 0;
5503          va_end(args);
5504          PutS(temp);
5505       }
5506    }
5507
5508    void SetContents(char * format, ...)
5509    {
5510       if(this)
5511       {
5512          undoBuffer.dontRecord++;
5513          Deselect();
5514          DelCh(this.lines.first, 0, 0, this.lines.last, this.lineCount-1, ((EditLine)(this.lines.last)).count, true);
5515          if(format)
5516          {
5517             char temp[MAX_F_STRING];
5518             va_list args;
5519             va_start(args, format);
5520             vsnprintf(temp, sizeof(temp), format, args);
5521             temp[sizeof(temp)-1] = 0;
5522             va_end(args);
5523
5524             AddS(temp);
5525          }
5526          UpdateDirty();
5527          Home();
5528          undoBuffer.dontRecord--;
5529       }
5530    }
5531
5532    void BackSpace()
5533    {
5534       if(!DelSel(null))
5535       {
5536          if(x > 0)
5537          {
5538             if(x > line.count)
5539                x--;
5540             else
5541                x -= 1 + _DelCh(line, y, x-1, line, y, x, true, false, null);
5542             Modified();
5543          }
5544          else if(this.line.prev)
5545          {
5546             EditLine line = this.line.prev;
5547             int x = line.count;
5548             int y = this.y;
5549
5550             _DelCh(line, this.y-1, x, this.line, this.y, this.x, true, false, null);
5551             this.line = line;
5552             this.y = y-1;
5553             this.x = x;
5554             Modified();
5555          }
5556          this.selX = this.x;
5557          this.selY = this.y;
5558          this.selLine = this.line;
5559          ComputeColumn();
5560       }
5561       else
5562          Modified();
5563       SetViewToCursor(true);
5564    }
5565
5566    void ClearLine()
5567    {
5568       Emptyline(this.line,this.y);
5569       this.selX = this.x = 0;
5570       this.col = 0;
5571       this.selY = this.y;
5572       this.selLine = this.line;
5573
5574       SetViewToCursor(true);
5575       Modified();
5576    }
5577
5578    // CARET CONTROL
5579    void End()
5580    {
5581       if(this)
5582       {
5583          GoToEnd(true);
5584          SetViewToCursor(true);
5585       }
5586    }
5587    void Home()
5588    {
5589       if(this)
5590       {
5591          GoToHome(true);
5592          SetViewToCursor(true);
5593       }
5594    }
5595
5596    bool GoToLineNum(int lineNum)
5597    {
5598       if(this.line)
5599       {
5600          int c;
5601          EditLine line = this.lines.first;
5602          for(c = 0; c < lineNum && line; c++, line = line.next);
5603          if(line)
5604          {
5605             if(this.y != c)
5606                DirtyAll();
5607             else
5608                DirtyLine(c);
5609             this.y = c;
5610             this.line = line;
5611             _Deselect();
5612             SetViewToCursor(true);
5613             return true;
5614          }
5615       }
5616       return false;
5617    }
5618
5619    // NOTE: Mismatch with NotifyCaretMove() for x/y + 1
5620    bool GoToPosition(EditLine line, int y, int x)
5621    {
5622       /*
5623       if(!line)
5624       {
5625          line = this.line;
5626          y = this.y;
5627       }
5628       */
5629       if(!line)
5630       {
5631          int c;
5632          for(line = this.lines.first, c = 0; c<y && line; c++, line = line.next);
5633       }
5634
5635       if(line)
5636       {
5637          if(this.y != y)
5638             DirtyAll();
5639          else
5640             DirtyLine(y);
5641          this.x = x;
5642          this.y = y;
5643          this.line = line;
5644          ComputeColumn();
5645          _Deselect();
5646          SetViewToCursor(true);
5647          return true;
5648       }
5649       return false;
5650    }
5651
5652    // VIEW POSITIONING
5653    void SetViewToCursor(bool setCaret)
5654    {
5655       if(created)
5656       {
5657          int w;
5658          int c, numLines;
5659          EditLine line;
5660          int x;
5661          int checkX, checkY;
5662          EditLine checkLine;
5663          bool dontScroll = false;
5664          bool selected;
5665          int viewX, viewY;
5666
5667          FixScrollArea();
5668
5669          selected = selX != this.x || selY != y;
5670
5671          viewX = this.viewX;
5672          viewY = this.viewY;
5673
5674          if(mouseMove)
5675          {
5676             checkLine = dropLine;
5677             checkX = dropX;
5678             checkY = dropY;
5679          }
5680          else
5681          {
5682             checkLine = this.line;
5683             checkX = this.x;
5684             checkY = y;
5685          }
5686
5687          numLines = clientSize.h / space.h;
5688
5689          // This is broken. The EditBox now doesn't do anything different when adding to it,
5690          // regardless of the previous scrolling position. It should be read and then set again
5691          // if one wishes to preserve it.
5692          /*  // Don't scroll view to cursor if we're in a EES_NOCARET box
5693          if(style.noCaret && this.viewY < lineCount - numLines - 1)
5694             dontScroll = true;
5695          */
5696
5697          // Horizontal Adjustment
5698          if(!dontScroll && checkLine)
5699          {
5700             x = 0;
5701             if(mouseMove)
5702                dropX = AdjustXPosition(this.line, MAXINT, true, &x, checkX, 0);
5703             else
5704             {
5705                this.x = AdjustXPosition(this.line, MAXINT, true, &x, checkX, 0);
5706                ComputeColumn();
5707             }
5708
5709             if(style.hScroll)
5710             {
5711                if(x + space.w >= this.viewX + clientSize.w && clientSize.w >= space.w)
5712                   viewX = x - clientSize.w+space.w;
5713                if(x < this.viewX + clientSize.w/2 - space.w)
5714                   viewX = Max(0, x - clientSize.w/2 + space.w);
5715             }
5716          }
5717
5718          if(!dontScroll)
5719          {
5720             if(style.vScroll)
5721             {
5722                // Vertical Adjustment
5723                if(viewY > checkY) viewY = checkY;
5724                if(viewY + numLines <= checkY)
5725                {
5726                   if(clientSize.h >= space.h)
5727                   {
5728                      for(line = viewLine; line && (viewY + numLines <= checkY); line = line.next)
5729                         viewY++;
5730                   }
5731                   else
5732                      viewY = checkY;
5733                }
5734             }
5735             else
5736             {
5737                if(mouseMove)
5738                   for(;dropLine && dropLine.prev && dropY >= numLines;)
5739                   {
5740                      dropLine = dropLine.prev;
5741                      dropY--;
5742                   }
5743                else
5744                   for(;this.line && this.line.prev && this.y >= numLines;)
5745                   {
5746                      this.line = this.line.prev;
5747                      y--;
5748                   }
5749             }
5750
5751             SetScrollPosition(viewX, viewY * this.space.h);
5752
5753             UpdateCaretPosition(setCaret);
5754
5755             if(!selected)
5756             {
5757                selX = this.x;
5758                selY = this.y;
5759                selLine = this.line;
5760             }
5761          }
5762
5763          UpdateDirty();
5764          SetSelectCursor();
5765          SelectionEnables();
5766       }
5767    }
5768
5769    void CenterOnCursor()
5770    {
5771       int numLines = clientSize.h / this.space.h;
5772       int y = this.y - numLines / 2;
5773       int viewY;
5774       bool figureSyntax = false;
5775       EditLine oldViewLine = viewLine;
5776       if(y > this.lineCount - numLines) y = this.lineCount-numLines;
5777       if(y < 0) y = 0;
5778
5779       viewY = y;
5780
5781       for(;y < this.viewY; y++)
5782       {
5783          this.viewLine = this.viewLine.prev;
5784          style.recomputeSyntax = true;
5785       }
5786       for(;y > this.viewY; y--)
5787       {
5788          this.viewLine = this.viewLine.next;
5789          figureSyntax = true;
5790       }
5791       if(figureSyntax)
5792          FigureStartSyntaxStates(oldViewLine, false);
5793
5794       this.viewY = viewY;
5795
5796       SetScrollPosition(this.viewX, viewY * this.space.h);
5797       UpdateCaretPosition(true);
5798       UpdateDirty();
5799    }
5800
5801    void SetCursorToView()
5802    {
5803       SetCursorToViewX();
5804       SetCursorToViewY();
5805    }
5806
5807    void PageDown()
5808    {
5809       int c, numLines;
5810       EditLine line;
5811
5812       numLines = clientSize.h / this.space.h;
5813
5814       if(style.noCaret)
5815       {
5816          for(c=0, line = this.viewLine.next; line && c<numLines && (this.viewY < this.lineCount - numLines); line = line.next, c++);
5817          SetScrollPosition(this.viewX, (this.viewY + c) * this.space.h);
5818       }
5819       else
5820       {
5821          EditLine oldLine = this.line;
5822          bool lastOne = false;
5823          EditLine oldViewLine = this.viewLine;
5824          bool figureSyntax = false;
5825
5826          if(this.y >= this.lineCount-1) return;
5827
5828          for(c=0, line = this.line.next; line && c<numLines; line = line.next, c++)
5829          {
5830             if(this.viewY + numLines < this.lines.count)
5831             {
5832                this.viewLine = this.viewLine.next;
5833                this.viewY++;
5834                figureSyntax = true;
5835             }
5836             else if(c && !lastOne)
5837                break;
5838             else
5839                lastOne = true;
5840
5841             this.line = line;
5842             this.y++;
5843          }
5844          if(figureSyntax)
5845             FigureStartSyntaxStates(oldViewLine, false);
5846          this.x = AdjustXPosition(line, caretX, true, null, MAXINT, 0);
5847          ComputeColumn();
5848
5849          SetViewToCursor(false);
5850       }
5851    }
5852
5853    void PageUp()
5854    {
5855       int c, numLines;
5856       EditLine line;
5857
5858       if(this.y == 0) return;
5859
5860       numLines = clientSize.h / this.space.h;
5861
5862       if(style.noCaret)
5863       {
5864          for(c=0, line = this.viewLine.prev; line && c<numLines; line = line.prev, c++);
5865          SetScrollPosition(this.viewX, (this.viewY - c) * this.space.h);
5866       }
5867       else
5868       {
5869          EditLine oldLine = this.line;
5870
5871          for(c=0, line = this.line.prev; line && c<numLines; line = line.prev, c++)
5872          {
5873             this.line = line;
5874             this.y--;
5875
5876             if(this.viewLine.prev)
5877             {
5878                this.viewLine = this.viewLine.prev;
5879                this.viewY--;
5880                style.recomputeSyntax = true;
5881             }
5882          }
5883
5884          this.x = AdjustXPosition(line, caretX, true, null, MAXINT, 0);
5885          ComputeColumn();
5886
5887          SetViewToCursor(false);
5888       }
5889    }
5890
5891    void LineUp()
5892    {
5893
5894       if(this.viewLine.prev)
5895       {
5896          //DirtyAll();
5897          // this.viewLine = this.viewLine.prev;
5898          //this.viewY--;
5899
5900          SetScrollPosition(this.viewX, (this.viewY - 1) * this.space.h);
5901       }
5902    }
5903
5904
5905    void LineDown()
5906    {
5907       if(this.viewLine.next)
5908       {
5909          //DirtyAll();
5910          // this.viewLine = this.viewLine.next;
5911          // this.viewY++;
5912
5913          SetScrollPosition(this.viewX, (this.viewY + 1) * this.space.h);
5914       }
5915    }
5916
5917    // Selection
5918    uint SelSize()
5919    {
5920       EditLine l1, l2, line;
5921       int x1, x2, nx1, nx2;
5922       int count;
5923       int start, end;
5924       int size;
5925
5926       if(!this.selLine) return 0;
5927       if(this.selLine == this.line && this.selX == this.x) return 0;
5928       if(this.selY < this.y)
5929       {
5930          l1 = this.selLine;
5931          x1 = this.selX;
5932          l2 = this.line;
5933          x2 = this.x;
5934       }
5935       else if(this.selY > this.y)
5936       {
5937          l1 = this.line;
5938          x1 = this.x;
5939          l2 = this.selLine;
5940          x2 = this.selX;
5941       }
5942       else if(this.selX < this.x)
5943       {
5944          l1 = l2 = this.line;
5945          x1 = this.selX;
5946          x2 = this.x;
5947       }
5948       else
5949       {
5950          l1 = l2 = this.line;
5951          x1 = this.x;
5952          x2 = this.selX;
5953       }
5954       nx1 = Min(x1,l1.count);
5955       nx2 = Min(x2,l2.count);
5956
5957       // Find Number of Bytes Needed
5958       size = 0;
5959       for(line = l1; line; line = line.next)
5960       {
5961          if(line == l1) start = nx1; else start = 0;
5962          if(line == l2) end = nx2; else end = line.count;
5963          size += end-start;
5964          if(style.freeCaret && line == l2)
5965          {
5966             if(l1 == l2)
5967                count = Max(x2-Max(x1,l1.count),0);
5968             else
5969                count = Max(x2-l2.count,0);
5970             size+=count;
5971          }
5972
5973          if(line == l2) break;
5974          // Add Carriage Return / line Feed
5975          size++;
5976          size++;
5977       }
5978       return size;
5979    }
5980
5981    void GetSelPos(EditLine * l1, int *y1, int *x1, EditLine * l2, int * y2, int * x2, bool reorder)
5982    {
5983       if(this)
5984       {
5985          if(!reorder || this.selY < this.y)
5986          {
5987             if(l1) *l1 = this.selLine;
5988             if(y1) *y1 = this.selY;
5989             if(x1) *x1 = this.selX;
5990             if(l2) *l2 = this.line;
5991             if(y2) *y2 = this.y;
5992             if(x2) *x2 = this.x;
5993          }
5994          else if(this.selY > this.y)
5995          {
5996             if(l1) *l1 = this.line;
5997             if(y1) *y1 = this.y;
5998             if(x1) *x1 = this.x;
5999             if(l2) *l2 = this.selLine;
6000             if(y2) *y2 = this.selY;
6001             if(x2) *x2 = this.selX;
6002          }
6003          else if(this.selX < this.x)
6004          {
6005             if(l1) *l1 = this.line;
6006             if(y1) *y1 = this.selY;
6007             if(x1) *x1 = this.selX;
6008             if(l2) *l2 = this.line;
6009             if(y2) *y2 = this.y;
6010             if(x2) *x2 = this.x;
6011          }
6012          else
6013          {
6014             if(l1) *l1 = this.line;
6015             if(y1) *y1 = this.y;
6016             if(x1) *x1 = this.x;
6017             if(l2) *l2 = this.line;
6018             if(y2) *y2 = this.selY;
6019             if(x2) *x2 = this.selX;
6020          }
6021       }
6022    }
6023
6024    void SetSelPos(EditLine l1, int y1, int x1, EditLine l2, int y2, int x2)
6025    {
6026       if(this && (this.selY != y1 || this.selX != x1 || this.y != y2 || this.x != x2))
6027       {
6028          this.selLine = (EditLine)l1;
6029          this.selY = y1;
6030          this.selX = x1;
6031          this.line = (EditLine)l2;
6032          this.y = y2;
6033          this.x = x2;
6034          ComputeColumn();
6035          SetViewToCursor(true);
6036       }
6037    }
6038
6039    int GetText(char * text, EditLine _l1, int _y1, int _x1, EditLine _l2, int _y2, int _x2, bool addCr, bool addSpaces)
6040    {
6041       EditLine l1, l2, line;
6042       int x1, x2, nx1, nx2;
6043       int count;
6044       int start, end;
6045       int numChars = 0;
6046
6047       if(_y2 < _y1)
6048       {
6049          l1 = _l2;
6050          x1 = _x2;
6051          l2 = _l1;
6052          x2 = _x1;
6053       }
6054       else if(_y2 > _y1)
6055       {
6056          l1 = _l1;
6057          x1 = _x1;
6058          l2 = _l2;
6059          x2 = _x2;
6060       }
6061       else if(_x2 < _x1)
6062       {
6063          l1 = l2 = _l1;
6064          x1 = _x2;
6065          x2 = _x1;
6066       }
6067       else
6068       {
6069          l1 = l2 = _l1;
6070          x1 = _x1;
6071          x2 = _x2;
6072       }
6073       nx1 = Min(x1,l1.count);
6074       nx2 = Min(x2,l2.count);
6075
6076       // Copy text
6077       for(line = l1; line; line = line.next)
6078       {
6079          if(line == l1) start = nx1; else start = 0;
6080          if(line == l2) end = nx2; else end = line.count;
6081          if(text)
6082          {
6083             CopyBytes(text, line.buffer + start, end - start);
6084             text += end-start;
6085          }
6086          numChars += end-start;
6087
6088          if(style.freeCaret && line == l2 && addSpaces)
6089          {
6090             if(l1 == l2)
6091                count = Max(x2-Max(x1,l1.count),0);
6092             else
6093                count = Max(x2-l2.count,0);
6094             if(text)
6095             {
6096                FillBytes(text,' ',count);
6097                text += count;
6098             }
6099             else
6100                numChars += count;
6101          }
6102          if(line == l2) break;
6103          // Add line Feed
6104          if(addCr)
6105          {
6106             if(text)
6107                *(text++) = '\r';
6108             numChars++;
6109          }
6110          if(text)
6111             *(text++) = '\n';
6112          numChars++;
6113       }
6114       // '\0' terminate Terminate
6115       if(text)
6116          *(text) = '\0';
6117       numChars++;
6118       return numChars;
6119    }
6120
6121    void GetSel(char * text, bool addCr)
6122    {
6123       GetText(text, line, y, x, selLine, selY, selX, addCr, true);
6124    }
6125
6126    private void _Deselect()   // This assumes marking lines as dirty is handled by the caller
6127    {
6128       selLine = line;
6129       selX = x;
6130       selY = y;
6131    }
6132
6133    void Deselect()
6134    {
6135       SelDirty();
6136       _Deselect();
6137    }
6138
6139    // CLIPBOARD
6140    void Copy()
6141    {
6142       if(this)
6143       {
6144          int size = SelSize();
6145          if(size)
6146          {
6147             // Try to allocate memory
6148             ClipBoard clipBoard { };
6149             if(clipBoard.Allocate(size+1))
6150             {
6151                GetSel(clipBoard.memory, true);
6152                // Save clipboard
6153                clipBoard.Save();
6154             }
6155             delete clipBoard;
6156          }
6157       }
6158    }
6159
6160    void Paste()
6161    {
6162       if(this)
6163       {
6164          ClipBoard clipBoard { };
6165          if(clipBoard.Load())
6166             PutS(clipBoard.memory);
6167          delete clipBoard;
6168       }
6169    }
6170
6171    void Cut()
6172    {
6173       if(this)
6174       {
6175          Copy();
6176          if(DelSel(null))
6177          {
6178             SetViewToCursor(true);
6179             Modified();
6180          }
6181       }
6182    }
6183
6184    void DeleteSelection()
6185    {
6186       if(this)
6187       {
6188          DelSel(null);
6189          SetViewToCursor(true);
6190          Modified();
6191       }
6192    }
6193
6194    // FILE INTERFACE
6195    void Save(File f, bool cr)
6196    {
6197       EditLine line;
6198       savedAction = undoBuffer.curAction;
6199
6200       for(line = this.lines.first; line; line = line.next)
6201       {
6202          f.Write(line.buffer, line.count, 1);
6203          if(line.next)
6204          {
6205             if(cr) f.Putc('\r');
6206             f.Putc('\n');
6207          }
6208       }
6209    }
6210
6211    #define BUFFER_SIZE  16384
6212    void Load(File f)
6213    {
6214       undoBuffer.dontRecord++;
6215       if(f)
6216       {
6217          char buffer[BUFFER_SIZE];
6218
6219          for(;;)
6220          {
6221             int count = f.Read(buffer, 1, BUFFER_SIZE-1);
6222             buffer[count] = '\0';
6223             AddS(buffer);
6224             if(!count) break;
6225          }
6226          Home();
6227       }
6228       undoBuffer.dontRecord--;
6229       undoBuffer.count = 0;
6230       undoBuffer.curAction = 0;
6231       itemEditUndo.disabled = undoBuffer.curAction == 0;
6232       itemEditRedo.disabled = undoBuffer.curAction == undoBuffer.count;
6233    }
6234
6235    EditBoxFindResult Find(char * text, bool matchWord, bool matchCase, bool isSearchDown)
6236    {
6237       EditLine line;
6238       int num;
6239       bool firstPass = true;
6240       EditBoxFindResult result = found;
6241
6242       if(!this.line) return notFound;
6243       num = this.y;
6244
6245       for(line = this.line;;)
6246       {
6247          char * string;
6248
6249          if(!line)
6250          {
6251             if(isSearchDown)
6252             {
6253                line = this.lines.first;
6254                num = 0;
6255                result = wrapped;
6256             }
6257             else
6258             {
6259                line = this.lines.last;
6260                num = this.lineCount - 1;
6261                result = wrapped;
6262             }
6263          }
6264
6265          if(isSearchDown)
6266             string = SearchString(line.buffer, firstPass ? Min(this.x,line.count) : 0,text,matchCase,matchWord);
6267          else
6268             string = RSearchString(line.buffer,text,firstPass ? (Min(this.x,line.count)-1) : line.count,matchCase,matchWord);
6269
6270          if(string)
6271          {
6272             Select((void *)line,num,string - line.buffer,(void *)line,num,string - line.buffer + strlen(text));
6273             return result;
6274          }
6275          if(line == this.line && !firstPass) break;
6276
6277          if(isSearchDown)
6278          {
6279             line = line.next;
6280             num++;
6281          }
6282          else
6283          {
6284             line = line.prev;
6285             num--;
6286          }
6287
6288          firstPass = false;
6289       }
6290       return notFound;
6291    }
6292
6293    EditBoxFindResult FindInSelection(char * text, bool matchWord, bool matchCase, EditLine l2, int y2, int x2)
6294    {
6295       EditLine line;
6296       int y;
6297       int searchLen = strlen(text);
6298       for(line = (EditLine)this.line, y = this.y; y <= y2; line = line.next, y++)
6299       {
6300          char * string = SearchString(line.buffer, (y == this.y) ? Min(this.x,line.count) : 0,text,matchCase,matchWord);
6301          if(string && (y < y2 || (string - line.buffer) + searchLen <= x2))
6302          {
6303             Select((void *)line,y,string - line.buffer,(void *)line,y,string - line.buffer + strlen(text));
6304             return found;
6305          }
6306       }
6307       return notFound;
6308    }
6309
6310    bool OnKeyDown(Key key, unichar ch)
6311    {
6312 #ifdef _DEBUG
6313       //PrintLn("OnKeyDown: code = ", key.code, ", mods = ", key.modifiers, ", ch = ", (int)ch);
6314 #endif
6315       if(!NotifyKeyDown(master, this, key, ch))
6316          return false;
6317       else
6318       {
6319          switch(key)
6320          {
6321             case escape:
6322             {
6323                if(this.mouseMove)
6324                {
6325                   this.mouseMove = false;
6326                   OnLeftButtonUp(0,0,0);
6327                   SetViewToCursor(true);
6328                   return false;
6329                }
6330             }
6331          }
6332       }
6333       return true;
6334    }
6335 };
6336
6337 public class EditBoxStream : File
6338 {
6339    EditBox editBox;
6340    BufferLocation start, sel;
6341    uint pos;
6342    byte utf8Bytes[5];
6343    int numBytes;
6344
6345    ~EditBoxStream()
6346    {
6347       EditBox editBox = this.editBox;
6348
6349       editBox.x = start.x;
6350       editBox.y = start.y;
6351       editBox.line = start.line;
6352
6353       editBox.selX = sel.x;
6354       editBox.selY = sel.y;
6355       editBox.selLine = sel.line;
6356
6357       editBox.SetViewToCursor(true);
6358       //editBox.ComputeColumn();
6359    }
6360
6361 public:
6362    property EditBox editBox
6363    {
6364       set
6365       {
6366          editBox = value;
6367
6368          start.x = value.x;
6369          start.y = value.y;
6370          start.line = value.line;
6371
6372          sel.x = value.selX;
6373          sel.y = value.selY;
6374          sel.line = value.selLine;
6375          numBytes = 0;
6376
6377          value.GoToHome(true);
6378       }
6379
6380       get { return editBox; }
6381    }
6382
6383    uint Read(byte * buffer, uint size, uint count)
6384    {
6385       uint read = 0;
6386       EditBox editBox = this.editBox;
6387       EditLine line = editBox.line;
6388       int x = editBox.x;
6389       int y = editBox.y;
6390
6391       count *= size;
6392
6393       for(;read < count && line; line = (*&line.next))
6394       {
6395          int numBytes = Min(count - read, (*&line.count) - x);
6396          if(numBytes > 0)
6397          {
6398             memcpy(buffer + read, (*&line.buffer) + x, numBytes);
6399             read += numBytes;
6400             x += numBytes;
6401          }
6402          /*
6403          for(;read < count && x < (*&line.count);)
6404          {
6405             buffer[read++] = (*&line.buffer)[x++];
6406          }
6407          */
6408          if((*&line.next))
6409          {
6410             if(read < count)
6411             {
6412                buffer[read++] = '\n';
6413             }
6414             else
6415                break;
6416          }
6417          if(x == (*&line.count) && (*&line.next))
6418          {
6419             x = 0;
6420             y++;
6421          }
6422          else
6423             break;
6424       }
6425
6426       editBox.line = editBox.selLine = line;
6427       editBox.x = editBox.selX = x;
6428       editBox.y = editBox.selY = y;
6429       pos += read;
6430       return read / size;
6431    }
6432
6433    bool Seek(int pos, FileSeekMode mode)
6434    {
6435       bool result = true;
6436       EditBox editBox = this.editBox;
6437       EditLine line = editBox.line;
6438       numBytes = 0;
6439       if(mode == FileSeekMode::start)
6440       {
6441          pos = pos - this.pos;
6442          mode = current;
6443       }
6444       if(mode == current)
6445       {
6446          uint read = 0;
6447          int x = editBox.x;
6448          int y = editBox.y;
6449          if(pos > 0)
6450          {
6451             for(;read < pos && line; line = (*&line.next))
6452             {
6453                int numBytes = Min(pos - read, (*&line.count) - x);
6454                if(numBytes > 0)
6455                {
6456                   read += numBytes; x += numBytes;
6457                }
6458
6459                /*for(;read < pos && x < (*&line.count);)
6460                {
6461                   read++; x++;
6462                }
6463                */
6464                if((*&line.next))
6465                {
6466                   if(read < pos)
6467                   {
6468                      read++;
6469                      x = 0;
6470                   }
6471                   else
6472                      break;
6473                }
6474                else
6475                {
6476                   if(read<pos)
6477                      result = false;
6478                   break;
6479                }
6480                y++;
6481             }
6482             this.pos += read;
6483          }
6484          else if(pos < 0)
6485          {
6486             pos = -pos;
6487             for(;read < pos && line; line = (*&line.prev))
6488             {
6489                int numBytes = Min(pos - read, x);
6490                if(numBytes > 0)
6491                {
6492                   read += numBytes;
6493                   x -= numBytes;
6494                }
6495                /*for(;read < pos && x > 0;)
6496                {
6497                   read++; x--;
6498                }
6499                */
6500                if((*&line.prev))
6501                {
6502                   if(read < pos)
6503                   {
6504                      read++;
6505                      x = (*&(*&line.prev).count);
6506                   }
6507                   else
6508                      break;
6509                }
6510                else
6511                {
6512                   if(read<pos)
6513                      result = false;
6514                   break;
6515                }
6516                y--;
6517             }
6518             this.pos -= read;
6519          }
6520
6521          editBox.line = editBox.selLine = line;
6522          editBox.x = editBox.selX = x;
6523          editBox.y = editBox.selY = y;
6524       }
6525       return result;
6526    }
6527
6528    bool Puts(char * string)
6529    {
6530       EditBox editBox = this.editBox;
6531       BufferLocation start { editBox.line, editBox.y, editBox.x };
6532       BufferLocation pos;
6533
6534       numBytes = 0;
6535       editBox.AddS(string);
6536
6537       pos.line = editBox.line;
6538       pos.y = editBox.y;
6539       pos.x = editBox.x;
6540
6541       this.start.AdjustAdd(start, pos);
6542       sel.AdjustAdd(start, pos);
6543
6544       return true;
6545    }
6546
6547    // NOTE: BYTE, NOT UNICODE CHARACTER!
6548    bool Putc(char ch)
6549    {
6550       EditBox editBox = this.editBox;
6551       BufferLocation start = { editBox.line, editBox.y, editBox.x };
6552       BufferLocation pos;
6553
6554       if(numBytes < 4)
6555       {
6556          utf8Bytes[numBytes++] = ch;
6557          utf8Bytes[numBytes] = 0;
6558          if(UTF8Validate(utf8Bytes))
6559          {
6560             editBox.AddCh(UTF8_GET_CHAR(utf8Bytes, numBytes));
6561             numBytes = 0;
6562             pos.line = editBox.line;
6563             pos.y = editBox.y;
6564             pos.x = editBox.x;
6565             this.start.AdjustAdd(start, pos);
6566             sel.AdjustAdd(start, pos);
6567          }
6568          return true;
6569       }
6570       return false;
6571    }
6572
6573    bool Getc(char * ch)
6574    {
6575       return Read(ch, 1, 1) ? true : false;
6576    }
6577
6578    void DeleteBytes(uint count)
6579    {
6580       EditBox editBox = this.editBox;
6581       if(count)
6582       {
6583          BufferLocation pos { editBox.line, editBox.y, editBox.x };
6584          BufferLocation end = pos;
6585
6586          uint c = 0;
6587          for(;;)
6588          {
6589             for(;c < count && end.line && end.x < end.line.count;)
6590             {
6591                end.x++;
6592                c++;
6593             }
6594             if(c < count && end.line && end.line.next)
6595             {
6596                end.line = end.line.next;
6597                end.y++;
6598                c++;
6599                end.x = 0;
6600             }
6601             else
6602                break;
6603          }
6604
6605          start.AdjustDelete(pos, end);
6606          sel.AdjustDelete(pos, end);
6607
6608          editBox._DelCh(pos.line, pos.y, pos.x, end.line, end.y, end.x, true, false, null);
6609       }
6610    }
6611 };