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