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