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