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