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