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