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