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