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