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