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