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