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