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