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