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