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