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