sdk/ecere/ide: Improved color customization support; Set up a decent default distinct...
[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 = false;
1376          if(line.count && line.text[line.count - 1] == '\\')
1377             continuedSingleLineComment = true;
1378
1379          style.inMultiLineComment = inMultiLineComment;
1380          style.inPrep = inPrep;
1381          style.escaped = escaped;
1382       }
1383    }
1384    
1385    /*void OnDrawOverChildren(Surface surface)
1386    {
1387       if(style.lineNumbers)
1388       {
1389          int currentLineNumber = this.viewY + 1;
1390          int i;
1391          char lineText[256];
1392          for(i = 0; i * space.h < box.height; i++)
1393          {
1394             // ********* LINE NUMBERING *********
1395             surface.SetForeground(Color{60, 60, 60});
1396             //Highlight current line
1397             if(this.caretY / space.h == currentLineNumber - 1)
1398                surface.SetBackground(Color{220, 220, 220});
1399             else
1400                surface.SetBackground(Color{230, 230, 230});
1401             surface.textOpacity = true;
1402             
1403             sprintf(lineText,"%5u ", currentLineNumber % 100000);
1404             if(currentLineNumber > this.lineCount)
1405                surface.WriteText(0,i*space.h+1,"      ",6);
1406             else
1407                surface.WriteText(0,i*space.h+1,lineText,6);
1408             
1409             currentLineNumber++;
1410          }
1411       }
1412    }*/
1413
1414    void OnRedraw(Surface surface)
1415    {
1416       EditLine line;
1417       int y = YOFFSET;
1418       bool selected = false, selection = true;
1419       int selX, editX;
1420       Color selectionBackground = selectionColor ? selectionColor : SELECTION_COLOR;
1421       Color selectionForeground = selectionText ? selectionText : SELECTION_TEXT;
1422       Color defaultTextColor = property::foreground;
1423       Color textColor;
1424       Box box;
1425       int maxW = clientSize.w;
1426       
1427       Color foreground, background;
1428       bool opacity;
1429
1430       // Overwrite Caret Stuff
1431       bool overWrite = false;
1432       int overWriteX, overWriteY;
1433       byte overWriteCh;
1434
1435       // ****** SYNTAX STATES ******
1436       bool inMultiLineComment = style.inMultiLineComment;
1437       bool inString = false;
1438       bool inQuotes = false;
1439       bool inPrep = style.inPrep;
1440       bool inSingleLineComment = false;
1441       bool escaped = style.escaped;
1442       bool continuedSingleLineComment = false;
1443       // ****** ************* ******
1444
1445       if(!isEnabled)
1446          defaultTextColor = Color { 85, 85, 85 };
1447       textColor = defaultTextColor;
1448
1449       if(
1450          Abs(selectionBackground.r - property::background.r) + 
1451          Abs(selectionBackground.g - property::background.g) + 
1452          Abs(selectionBackground.b - property::background.b) < 92)
1453       {
1454          selectionBackground = activeBorder;
1455          selectionForeground = selectionColor ? selectionColor : SELECTION_COLOR;
1456       }
1457
1458       surface.TextFont(this.font);
1459    /*
1460       surface.SetBackground(GDefaultPalette()[RND_Get(1, 15)]);
1461       surface.Area(0,0,MAXINT,MAXINT);
1462    */
1463
1464       if(!isEnabled)
1465       {
1466          surface.SetBackground(activeBorder);
1467          surface.Area(0,0,clientSize.w, clientSize.h);
1468       }
1469
1470       if(this.selX == this.x && this.selY == this.y)
1471          selection = false;
1472
1473       if(style.freeCaret)
1474       {
1475          editX = this.x;
1476          selX  = this.selX;
1477       }
1478       else
1479       {  
1480          editX = Min(this.x,this.line.count);
1481          selX  = Min(this.selX,this.selLine.count);
1482       }
1483
1484       selected = (this.selY < this.viewY) ^ (this.y < this.viewY);
1485
1486       foreground = selected ? selectionForeground : textColor;
1487       background = selectionBackground;
1488       opacity = selected;
1489    /*
1490       opacity = true;
1491       background = WHITE;
1492    */
1493       surface.SetForeground(foreground);
1494       surface.SetBackground(background);
1495       surface.TextOpacity(opacity);
1496
1497       surface.GetBox(box);
1498
1499       for(line = this.viewLine; line; line = line.next)
1500       {
1501          int x = -this.viewX;
1502          int end;
1503          int c;
1504          int start = 0;
1505          Color newTextColor = textColor = defaultTextColor;
1506          bool lineComplete = false;
1507         
1508
1509          // ****** SYNTAX HIGHLIGHTING ******
1510          bool lastWasStar = false;
1511          bool firstWord = true;
1512          if(!escaped) inPrep = false;
1513          inSingleLineComment = continuedSingleLineComment;
1514          escaped = false;
1515          inString = false;
1516          inQuotes = false;
1517          // *********************************
1518
1519    /*   === DEBUGGING TOOL FOR MAXLINE ===
1520
1521          if(line == this.maxLine)
1522          {
1523             surface.SetBackground(GREEN|0xFF000000);
1524             surface.TextOpacity(true);
1525          }
1526          else
1527          {
1528             surface.TextOpacity(selected ? true : false);
1529             surface.SetBackground(selected ? SELECTION_COLOR|0xFF000000 : BLACK|0xFF000000);
1530          }
1531    */
1532          
1533          if(line == this.selLine && line == this.line)
1534          {
1535             end = Max(line.count, this.x);
1536             end = Max(end, this.selX);
1537          }
1538          else if(line == this.selLine)
1539             end = Max(line.count, this.selX);
1540          else if(line == this.line)
1541             end = Max(line.count, this.x);
1542          else
1543             end = line.count;
1544
1545          for(c=0; c<end; )
1546          {
1547             int bufferLen = 0;
1548             int wordLen = 0;
1549             bool spacing = true;
1550             bool cantHaveWords = false;
1551             int w = 0;
1552
1553             if(lineComplete)
1554             {
1555                x = -viewX;
1556                y += space.h;
1557                lineComplete = false;
1558             }
1559             
1560             textColor = newTextColor;
1561             if(!selected)
1562                surface.SetForeground(textColor);
1563
1564             // Look at words
1565             for(; c<end && !cantHaveWords;)
1566             {
1567                if(wordLen)
1568                {
1569                   bufferLen += wordLen;
1570                   wordLen = 0;
1571                }
1572 #ifdef _DEBUG
1573                /*if(line.count > 4000)
1574                {
1575                   printf("oh");
1576                   CheckMemory();
1577                }*/
1578 #endif
1579                // Parse Words
1580                for(; c<line.count; c++)
1581                {
1582                   unichar ch = line.buffer[c];
1583                   unichar bf = (wordLen == 1) ? line.buffer[c-1] : 0;
1584                   //if(ch == ' ' || ch == '\t' || (wordLen && (ch == '(' || ch == ')' || ch == ';' || ch == ':')) || (wordLen == 1 && line.buffer[c-1] == '('))
1585                   if(CharMatchCategories(ch, separators) || /*ch == ' ' ||*/ ch == '\t' ||
1586                      (wordLen && !CharMatchCategories(ch, numbers|letters|marks|connector) && ch != '#' /*&& ch != '_'*/) || 
1587                      (bf && !CharMatchCategories(bf, numbers|letters|separators|marks|connector) && bf != '#' && bf != '\t' /*&& bf != '_' && bf != ' '*/))
1588                      break;
1589                   wordLen++;
1590                }
1591
1592                if(!wordLen)
1593                {
1594                
1595                   for(; c<line.count; c++)
1596                   {
1597                      unichar ch = line.buffer[c];
1598                      if(ch == '\t' || ch == ' ')
1599                      {
1600                         cantHaveWords = true;
1601                         if(bufferLen)
1602                            break;
1603                      }
1604                      if(ch != ' ' && ch != '\t')
1605                         break;
1606                      wordLen++;
1607                   }
1608
1609                   if(c == line.count && c < end)
1610                   {
1611                      if(bufferLen)
1612                      {
1613                         c -= wordLen;
1614                         break;
1615                      }
1616                      wordLen += end - c;
1617                      c = end;
1618                      cantHaveWords = true;
1619                   }
1620
1621                   if(c < end)
1622                      escaped = false;
1623                   lastWasStar = false;
1624                }
1625                else
1626                {
1627                   if(isEnabled)
1628                   {
1629                      bool backEscaped = escaped;
1630                      bool backLastWasStar = lastWasStar;
1631                      bool backInMultiLineComment = inMultiLineComment;
1632                      bool backInString = inString;
1633                      bool backInQuotes = inQuotes;
1634                      bool backInPrep = inPrep;
1635                      bool backInSingleLineComment = inSingleLineComment;
1636
1637                      char * word = line.buffer + c - wordLen;
1638                      int g,ccc;
1639                      bool wasEscaped = escaped;
1640                      escaped = false;
1641                      lastWasStar = false;
1642
1643                      // Determine Syntax Highlighting
1644                      newTextColor = defaultTextColor;
1645                      if(style.syntax)
1646                      {
1647                         if(inSingleLineComment || inMultiLineComment)
1648                         {
1649                            newTextColor = colorScheme.commentColor;
1650                         }
1651                         else if(inQuotes)
1652                         {
1653                            newTextColor = colorScheme.charLiteralColor;
1654                         }
1655                         else if(inString)
1656                         {
1657                            newTextColor = colorScheme.stringLiteralColor;
1658                         }
1659                         else if(inPrep)
1660                         {
1661                            newTextColor = colorScheme.preprocessorColor;
1662                         }
1663                         if(wordLen == 1 && word[0] == '/')
1664                         {
1665                            if(!inSingleLineComment && !inMultiLineComment && !inQuotes && !inString)
1666                            {
1667                               if(word[1] == '/')
1668                               {
1669                                  inSingleLineComment = true;
1670                                  newTextColor = colorScheme.commentColor;
1671                               }
1672                               else if(word[1] == '*')
1673                               {
1674                                  inMultiLineComment = true;
1675                                  newTextColor = colorScheme.commentColor;
1676                               }
1677                            }
1678                            else if(backLastWasStar)
1679                               inMultiLineComment = false;
1680                         }
1681                         else if(wordLen == 1 && word[0] == '*')
1682                         {
1683                            if(!c || word[-1] != '/')
1684                               lastWasStar = true;
1685                         }
1686                         else if(!inSingleLineComment && !inMultiLineComment && !inQuotes && wordLen == 1 && word[0] == '\"')
1687                         {
1688                            if(inString && !wasEscaped)
1689                            {
1690                               inString = false;
1691                            }
1692                            else
1693                            {
1694                               inString = true;
1695                               newTextColor = colorScheme.stringLiteralColor;
1696                            }
1697                         }
1698                         else if(!inSingleLineComment && !inMultiLineComment && !inString && wordLen == 1 && word[0] == '\'')
1699                         {
1700                            if(inQuotes && !wasEscaped)
1701                               inQuotes = false;
1702                            else
1703                            {
1704                               inQuotes = true;
1705                               newTextColor = colorScheme.charLiteralColor;
1706                            }
1707                         }
1708                         else if(wordLen == 1 && word[0] == '\\')
1709                         {
1710                            if(!wasEscaped)
1711                               escaped = true;
1712                         }
1713                         else if(!inQuotes && !inString && !inMultiLineComment && !inSingleLineComment && 
1714                            ( ( isdigit(word[0]) /*&& (!c || word[-1] == ' ' || word[-1] == '\t')*/ ) || (word[0] == '.' && isdigit(word[1]))))
1715                         {
1716                            newTextColor = colorScheme.numberColor;
1717                         }
1718                         else
1719                         {
1720                            if(!inQuotes && !inString && !inMultiLineComment && !inSingleLineComment && word[0] == '#')
1721                            {
1722                               if(firstWord)
1723                               {
1724                                  inPrep = true;
1725                                  newTextColor = colorScheme.preprocessorColor; 
1726                               }
1727                            }
1728                            if(!inQuotes && !inString && !inMultiLineComment && !inSingleLineComment)
1729                            {
1730                               for(g = 0; g < ((inPrep && word[0] != '#') ? 2 : 1); g++)
1731                               {
1732                                  char ** keys = keyWords[g];
1733                                  int * len = keyLen[g];
1734                                  for(ccc = 0; keys[ccc]; ccc++)
1735                                  {
1736                                     if(len[ccc] == wordLen && !strncmp(keys[ccc], word, wordLen))
1737                                     {
1738                                        newTextColor = colorScheme.keywordColors[g];
1739                                        break;
1740                                     }
1741                                  }
1742                               }
1743                            }
1744                         }
1745                         firstWord = false;
1746
1747                         // If highlighting for this word is different, break
1748                         if(newTextColor != textColor)
1749                         {
1750                            if(bufferLen)
1751                            {
1752                               // Better solution than going back?
1753                               c -= wordLen;
1754
1755                               // Reset syntax flags
1756                               escaped = backEscaped;
1757                               lastWasStar = backLastWasStar;
1758                               inMultiLineComment = backInMultiLineComment;
1759                               inString = backInString;
1760                               inQuotes = backInQuotes;
1761                               inPrep = backInPrep;
1762                               inSingleLineComment = backInSingleLineComment;
1763                               break;
1764                            }
1765                            else
1766                            {
1767                               textColor = newTextColor;
1768                               if(!selected)
1769                                  surface.SetForeground(textColor);
1770                            }
1771                         }
1772                      }
1773                   }
1774                
1775                   // If we're not breaking, this can't be rendered as spacing anymore
1776                   spacing = false;
1777
1778                   // Do word wrapping here
1779                   if(style.wrap)
1780                   {
1781                      //if(!numSpaces)
1782                      {
1783                         int tw;
1784                         FontExtent(display, font, line.buffer + start, bufferLen + wordLen, &tw, null);
1785                         w = tw;
1786                      }
1787                      /*else
1788                      {
1789                         w += numSpaces * space.w;
1790                      }*/
1791                      if(x + viewX + w > maxW)
1792                      {
1793                         c -= wordLen;
1794                         lineComplete = true;                        
1795                         break;
1796                      }
1797                   }
1798                }
1799                bufferLen += wordLen;
1800                wordLen = 0;
1801             }
1802
1803             {
1804                int renderStart = start;
1805                bool flush = false;
1806                int numSpaces = 0;
1807                int wc;
1808
1809                // Render checking if we need to split because of selection or to find where to draw insert caret
1810                for(wc = start; wc < start + bufferLen; wc++)
1811                {
1812                   flush = CheckColors(line, wc, selection, selX, editX, &selected, selectionForeground,
1813                      selectionBackground, textColor, &foreground, &background, &opacity, &overWrite);
1814                   if(overWrite == true)
1815                   {
1816                      overWriteCh = (wc < line.count) ? line.buffer[wc] : ' ';
1817                      if(overWriteCh == '\t') overWriteCh = ' ';
1818                   }
1819
1820                   if(flush)
1821                   {
1822                      FlushBuffer(surface, line, wc, &renderStart, &x, y, numSpaces, box);
1823                      if(overWrite == true)
1824                      {
1825                         overWriteX = x;
1826                         overWriteY = y;
1827                         overWrite = 2;
1828                      }
1829                      numSpaces = 0;
1830
1831                      surface.TextOpacity(opacity);
1832                      surface.SetBackground(background);
1833                      surface.SetForeground(foreground);
1834
1835                      flush = false;
1836                   }
1837
1838                   if(spacing)
1839                   {
1840                      if(wc < line.count && line.buffer[wc] == '\t')
1841                      {
1842                         numSpaces += (tabSize * space.w) - ((x + numSpaces + viewX) % (tabSize * space.w));
1843                      }
1844                      else
1845                      {
1846                         numSpaces += space.w;
1847                      }
1848                   }
1849                }
1850                FlushBuffer(surface, line, wc, &renderStart, &x, y, numSpaces, box);
1851                start += bufferLen;
1852             }
1853          }
1854
1855          if(CheckColors(line, c, selection, selX, editX, &selected, selectionForeground,
1856                         selectionBackground, textColor, &foreground, &background, &opacity, &overWrite))
1857          {
1858             if(overWrite == true)
1859             {
1860                overWriteX = x;
1861                overWriteY = y;
1862                overWriteCh = ' ';
1863                overWrite = 2;
1864             }
1865             surface.TextOpacity(opacity);
1866             surface.SetBackground(background);
1867             surface.SetForeground(foreground);
1868          }
1869
1870          if(style.freeCaret && selected)
1871          {
1872             surface.SetBackground(selectionBackground);
1873             surface.Area(x + XOFFSET - 1,y,clientSize.w-1,y+this.space.h-1);
1874             // TEST: surface.Area(x + XOFFSET,y,clientSize.w-1,y+this.space.h-1);
1875          }
1876
1877
1878          /*
1879          if(style.freeCaret && selected)
1880          {
1881             surface.SetBackground(selectionBackground);
1882             surface.Area(x + XOFFSET - 1,y,clientSize.w-1,y+this.space.h-1);
1883          }
1884          */
1885          
1886          continuedSingleLineComment = false;
1887          if(line.count && line.text[line.count - 1] == '\\')
1888             continuedSingleLineComment = true;
1889
1890          y+=this.space.h;
1891          if(y > box.bottom) // >=clientSize.h) 
1892             break;
1893       }
1894
1895       if(overWrite)
1896       {
1897          surface.TextOpacity(true);
1898          surface.SetForeground(black);
1899          surface.SetBackground(Color {255,255,85});
1900          surface.WriteText(XOFFSET + overWriteX,overWriteY, &overWriteCh, 1);
1901       }
1902    }
1903
1904    void FixScrollArea()
1905    {
1906       if(style.hScroll || style.vScroll)
1907       {
1908          int width = maxLength + XOFFSET;
1909          int height = lineCount * space.h;
1910          if(style.freeCaret && line)
1911          {
1912             if(x > selX)
1913             {
1914                if(x > line.count)
1915                   width = Max(line.length + (x - line.count) * space.w, maxLength);
1916             }
1917             else
1918             {
1919                if(selX > selLine.count)
1920                   width = Max(selLine.length + (selX - selLine.count) * space.w, maxLength);
1921             }
1922          }
1923
1924          width += space.w;
1925          SetScrollLineStep(8, space.h);
1926          SetScrollArea(width, height, true);
1927       }
1928    }
1929
1930    void ComputeLength(EditLine line)
1931    {
1932       int c;
1933       int tabOccur = 0;
1934       int tabWidth;
1935       int x = 0;
1936
1937       for(c = 0; c < line.count; )
1938       {
1939          int len = 1;
1940          int start = c;
1941          int w;
1942          if(c < line.count)
1943          {
1944             byte ch = 0;
1945             int numBytes;
1946             for(len = 0; c < line.count; c += numBytes)
1947             {
1948                ch = line.buffer[c];
1949                numBytes = UTF8_NUM_BYTES(ch);
1950
1951                if(ch == ' ' || ch == '\t')
1952                {
1953                   if(!len) c++;
1954                   break;
1955                }
1956                len += numBytes;
1957             }
1958             if(!len && ch == ' ')
1959             {
1960                len = 1;
1961                w = space.w;
1962             }
1963             else if(!len && ch == '\t')
1964             {
1965                w = (tabSize * space.w) - (x % (tabSize * space.w));
1966                len = 1;
1967             }
1968             else
1969                FontExtent(display, font, line.buffer + start, len, &w, null); 
1970          }
1971          else
1972          {
1973             w = space.w;
1974             c++;
1975          }
1976          x += w;
1977       }               
1978       line.length = x;
1979
1980       if(line.length > this.maxLength)
1981       {
1982          this.maxLine = line;
1983          this.maxLength = line.length;
1984       }
1985    }
1986
1987    void FindMaxLine()
1988    {
1989       EditLine line;
1990
1991       this.maxLength = 0;
1992       this.maxLine = null;
1993
1994       for(line = lines.first; line; line = line.next)
1995       {
1996          if(line.length > this.maxLength)
1997          {
1998             this.maxLength = line.length;
1999             this.maxLine = line;
2000          }
2001       }
2002    }
2003
2004    void SelDirty()
2005    {
2006       if(this.selY != this.y)
2007          DirtyAll();
2008       else if(this.selX != this.x)  // commented out to erase caret: if(this.selX != this.x) 
2009          DirtyLine(this.y);
2010    }
2011
2012    void ComputeColumn()
2013    {
2014       // Adjust new edit X position according to tabs
2015       int c, position = 0;
2016       unichar ch;
2017       int nb;
2018       for(c = 0; c<this.line.count && c<this.x; c+= nb)
2019       {
2020          ch = UTF8_GET_CHAR(this.line.buffer + c, nb);
2021          // TODO: MIGHT WANT TO RETHINK WHAT COLUMN SHOULD BE REGARDING TABS
2022          if(ch == '\t')
2023             position += this.tabSize - (position % this.tabSize);
2024          else
2025             position ++;
2026       }
2027       position += this.x - c;
2028       this.col = position;
2029    }
2030
2031    int DelCh(EditLine l1, int y1, int c1, EditLine l2, int y2, int c2, bool placeAfter)
2032    {
2033       return _DelCh(l1, y1, c1, l2, y2, c2, placeAfter, null);
2034    }
2035    
2036    bool HasCommentOrEscape(EditLine line)
2037    {
2038       bool hadComment = strstr(line.buffer, "/*") || strstr(line.buffer, "*/");
2039       int c;
2040       
2041       if(!hadComment)
2042       {
2043          for(c = line.count-1; c >= 0; c--)
2044          {
2045             char ch = line.buffer[c];
2046             if(ch == '\\')
2047             {
2048                hadComment = true;
2049                break;
2050             }
2051             else //if(ch != ' ' && ch != '\t')
2052                break;
2053          }
2054       }
2055       return hadComment;
2056    }
2057    
2058    int _DelCh(EditLine l1, int y1, int c1, EditLine l2, int y2, int c2, bool placeAfter, int * addedSpacesPtr)
2059    {
2060       EditLine line = l1, next;
2061       char * buffer;
2062       int oldCount1, oldCount2;
2063       int y, firstViewY, firstY, firstDropY, firstSelY;
2064       int newLineCount;
2065       int extras = 0;
2066       DelTextAction action = null;
2067       bool hadComment = false;
2068       int start = c1;
2069
2070       if(style.syntax)
2071          hadComment = HasCommentOrEscape(line);
2072
2073       if(y2 > y1 || c2 > c1)
2074       {
2075          if(start < l1.count)
2076          {
2077             while(!UTF8_IS_FIRST(l1.buffer[start]) && start)
2078                start--;
2079          }
2080       }
2081       oldCount1 = l1.count;
2082       buffer = l1.buffer;
2083       while(c1 < oldCount1)
2084       {
2085          byte ch = buffer[c1];
2086          if(UTF8_IS_FIRST(ch)) break;
2087          c1--;         
2088          extras++;
2089       }
2090       oldCount2 = l2.count;
2091       buffer = l2.buffer;
2092       while(c2 < oldCount2)
2093       {
2094          byte ch = buffer[c2];
2095          if(UTF8_IS_FIRST(ch)) break;
2096          c2++;
2097          extras++;
2098       }
2099
2100       if(!undoBuffer.dontRecord && (y2 > y1 || c2 > c1))
2101       {
2102          int len;
2103          char * string;
2104          
2105          len = GetText(null, l1, y1, start, l2, y2, c2, false, false);
2106          string = new char[len];
2107          action = DelTextAction { y1 = y1, x1 = start, y2 = y2, x2 = c2, string = string, placeAfter = placeAfter };
2108          GetText(string, l1, y1, start, l2, y2, c2, false, false);
2109          Record(action);
2110       }
2111
2112       //oldCount1 = l1.count;
2113       //oldCount2 = l2.count;
2114
2115       {
2116          BufferLocation before = { l1,y1,c1}, after = { l2,y2,c2 };
2117          NotifyCharsDeleted(master, this, &before, &after, this.pasteOperation);
2118       }
2119
2120       if(c2 > oldCount2) c2 = oldCount2;
2121       if(!(style.freeCaret))
2122          if(c1 > oldCount1) c1 = oldCount1;
2123       newLineCount = c1 + l2.count-c2;
2124       if(l1 == l2)
2125       {
2126          /*
2127          if(!line.size)
2128             printf("");
2129          buffer = new char[line.size ? line.size : 1];
2130          */
2131          buffer = new char[line.size];
2132          if(!buffer) return;
2133          CopyBytes(buffer,l2.buffer,oldCount1 + 1/*line.count + 1*//*line.size*/);
2134       }
2135       else
2136          buffer = l2.buffer;
2137
2138       if(!line.AdjustBuffer(newLineCount)) 
2139          return;
2140
2141 #ifdef _DEBUG
2142       /*if(newLineCount > 4000 || newLineCount < 0)
2143          printf("Warning");*/
2144 #endif
2145       line.count = newLineCount;
2146
2147       memmove(l1.buffer+c1, buffer+c2,line.count-c1);
2148       if(c1>oldCount1)
2149       {
2150          if(action)
2151          {
2152             action.addedSpaces = c1-oldCount1;
2153 #ifdef _DEBUG
2154             if(action.addedSpaces > action.x1)
2155             {
2156                printf("bug!");
2157             }
2158 #endif
2159          }
2160          if(addedSpacesPtr) *addedSpacesPtr = c1-oldCount1;
2161          FillBytes(l1.buffer+oldCount1,' ',c1-oldCount1);
2162       }
2163       line.buffer[line.count] = '\0';
2164
2165       if(l1 == l2)
2166       {
2167          delete buffer;
2168          DirtyLine(y1);
2169          /*
2170          this.dropX -= c2-c1;
2171          this.dropX = Max(this.dropX,0);
2172          this.x -= c2-c1-1;
2173          this.x = Max(this.x,0);
2174          */
2175       }
2176       else
2177          DirtyEnd(y1);
2178
2179       y = y1;
2180       firstViewY = this.viewY;
2181       firstY = this.y;
2182       firstDropY = this.dropY;
2183       firstSelY = this.selY;
2184       for(line = l1;line;line = next, y++)
2185       {
2186          next = line.next;
2187          if(line!=l1)
2188          {
2189             this.lineCount--;
2190             delete line.buffer;
2191        
2192             if(line == this.viewLine)
2193             {
2194                if(this.viewLine.next)
2195                {
2196                   this.viewLine = this.viewLine.next;
2197                   //this.viewY++;
2198                   style.recomputeSyntax = true;
2199                }
2200                else 
2201                {
2202                   this.viewLine = this.viewLine.prev;
2203                   this.viewY--;
2204                   style.recomputeSyntax = true;
2205                }
2206             }
2207             else if(y < firstViewY)
2208                this.viewY--;
2209             if(line == this.line)
2210             {
2211                if(this.line.next)
2212                {
2213                   this.line = this.line.next;
2214                   this.x = this.line.count;
2215                   //this.y++;
2216                }
2217                else 
2218                {
2219                   this.line = this.line.prev;
2220                   this.x = this.line.count;
2221                   this.y--;
2222                }
2223                ComputeColumn();
2224             }
2225             else if(y < firstY)
2226                this.y--;
2227             if(line == this.dropLine)
2228             {
2229                if(this.dropLine.next)
2230                {
2231                   this.dropLine = this.dropLine.next;
2232                   this.dropX = this.dropLine.count;
2233                }
2234                else 
2235                {
2236                   this.dropLine = this.dropLine.prev;
2237                   this.dropX = this.dropLine.count;
2238                   this.dropY--;
2239                }
2240             }
2241             else if(y < firstDropY)
2242                this.dropY--;
2243             if(line == this.selLine)
2244             {
2245                if(this.selLine.next)
2246                {
2247                   this.selLine = this.selLine.next;
2248                   this.selX = this.selLine.count;
2249                }
2250                else 
2251                {
2252                   this.selLine = this.selLine.prev;
2253                   this.selX = this.selLine.count;
2254                   this.selY--;
2255                }
2256             }
2257             else if(y < firstSelY)
2258                this.selY--;
2259             lines.Delete(line);
2260          }
2261          if(line == l2) break;
2262       }
2263       ComputeLength(l1);
2264       FindMaxLine();
2265       if(style.syntax && (hadComment || HasCommentOrEscape(this.line)))
2266       {
2267          DirtyAll();
2268          style.recomputeSyntax = true;
2269       }
2270       return extras;
2271    }
2272
2273    bool DelSel(int * addedSpacesPtr)
2274    {
2275       if(this.line != this.selLine || this.x != this.selX)
2276       {
2277          if(this.selY < this.y)
2278          {
2279             _DelCh(this.selLine, this.selY, this.selX, this.line, this.y, this.x, true, addedSpacesPtr);
2280             this.x = this.selX;
2281             this.y = this.selY;
2282             this.line = this.selLine;
2283          }
2284          else if(this.selY > this.y)
2285          {
2286             _DelCh(this.line, this.y, this.x, this.selLine, this.selY, this.selX, false, addedSpacesPtr);
2287             this.selX = this.x;
2288             this.selY = this.y;
2289             this.selLine = this.line;
2290          }
2291          else if(this.selX < this.x)
2292          {
2293             _DelCh(this.selLine, this.selY, this.selX, this.line, this.y, this.x, true, addedSpacesPtr);
2294             this.x = this.selX;
2295             this.y = this.selY;
2296             this.line = this.selLine;
2297          }
2298          else
2299          {
2300             _DelCh(this.line, this.y, this.x, this.selLine, this.selY, this.selX, false, addedSpacesPtr);
2301             this.selX = this.x;
2302             this.selY = this.y;
2303             this.selLine = this.line;
2304          }
2305          ComputeColumn();
2306          return true;
2307       }
2308       return false;
2309    }
2310
2311    bool AddToLine(char * stringLine, int count, bool LFComing, int * addedSpacesPtr, int * addedTabsPtr)
2312    {
2313       bool hadComment = false;
2314       // Add the line here
2315       EditLine line = this.line;
2316          
2317       // The purpose of this is solely to lock a max number of characters if no HSCROLLING is present
2318       if(!style.hScroll && created)
2319       {
2320          int endX = (style.freeCaret) ? this.x : Min(this.x, line.count);
2321          int max;
2322          int x;
2323          int c;
2324
2325          // Lock if no place to display.
2326          if(LFComing)
2327             max = endX;
2328          else if(endX > this.x)
2329             max = Max(this.x, line.count);
2330          else
2331             max = line.count;
2332
2333          for(x = 0, c = 0; c < max+count; )
2334          {
2335             int w;
2336             int numBytes = 1;
2337             char * string;
2338             if(c < Min(this.x, line.count))
2339                string = line.buffer + c;
2340             else if(c < endX)
2341                string = " ";
2342             else if(c < endX + count)
2343                string = stringLine + c - endX;
2344             else
2345                string = line.buffer + c - endX - count;
2346
2347             if(*string == '\t')
2348             {
2349                w = (tabSize * space.w) - (x % (tabSize * space.w));
2350             }
2351             else
2352             {
2353                numBytes = UTF8_NUM_BYTES(*string);
2354                FontExtent(display, this.font, string, numBytes, &w, null);
2355             }
2356             x += w;
2357
2358             if(x >= clientSize.w) 
2359             {
2360                count = c - max;
2361                break;
2362             }
2363             c += numBytes; // - 1;
2364          }
2365       }
2366
2367       if(count > 0)
2368       {
2369          int addedSpaces = 0;
2370          int addedTabs = 0;
2371          
2372          // Add blank spaces if EES_FREECARET
2373          if(this.x > line.count)
2374          {
2375             if(style.freeCaret)
2376             {
2377                if(style.useTab)
2378                {
2379                   int wantedPosition;
2380                   int position = 0;
2381                   int c;
2382
2383                   for(c = 0; c<line.count; c++)
2384                   {
2385                      if(this.line.buffer[c] == '\t')
2386                         position += this.tabSize - (position % this.tabSize);
2387                      else
2388                         position ++;
2389                   }
2390                   wantedPosition = position + (this.x - line.count);
2391
2392                   // A tab is too much...
2393                   if(position + (this.tabSize - (position % this.tabSize)) > wantedPosition)
2394                      addedSpaces = wantedPosition - position;
2395                   else
2396                   {
2397                      // Put a first tab
2398                      addedTabs = 1;
2399                      position += this.tabSize - (position % this.tabSize);
2400                      // Add as many tabs as needed
2401                      addedTabs += (wantedPosition - position) / this.tabSize;
2402                      position += (addedTabs-1) * this.tabSize;
2403                      // Finish off with spaces
2404                      addedSpaces = wantedPosition - position;
2405                   }
2406                }
2407                else
2408                   addedSpaces = this.x - line.count;
2409             }
2410          }
2411          this.x = Min(this.x, line.count);
2412
2413          if(line.count + count + addedSpaces + addedTabs > this.maxLineSize)
2414             return false;
2415
2416          DirtyLine(this.y);
2417
2418          if(style.syntax)
2419             hadComment = HasCommentOrEscape(line);
2420
2421          // Adjust the size of the line
2422          if(!line.AdjustBuffer(line.count+count+addedTabs+addedSpaces))
2423             return false;
2424
2425          {
2426             BufferLocation before = { this.line, this.y, this.x }, after = { this.line, this.y, this.x };
2427             bool hasComment;
2428          
2429             memmove(line.buffer+this.x+count, line.buffer+this.x,line.count-this.x);
2430             CopyBytes(line.buffer + this.x + addedTabs + addedSpaces, stringLine, count);
2431             if(addedTabs)
2432             {
2433                *addedTabsPtr = addedTabs;
2434                FillBytes(line.buffer+line.count,'\t',addedTabs);
2435 #ifdef _DEBUG
2436       if(addedTabs > 4000 || addedTabs < 0)
2437          printf("Warning");
2438 #endif
2439                line.count += addedTabs;
2440             }
2441             else if(addedTabs)
2442                *addedTabsPtr = 0;
2443             if(addedSpaces)
2444             {
2445                FillBytes(line.buffer+line.count,' ',addedSpaces);
2446 #ifdef _DEBUG
2447       if(addedSpaces > 4000 || addedSpaces < 0)
2448          printf("Warning");
2449 #endif
2450                line.count += addedSpaces;
2451                if(addedSpacesPtr) *addedSpacesPtr = addedSpaces;
2452             }      
2453             else if(addedSpacesPtr)
2454                *addedSpacesPtr = 0;
2455 #ifdef _DEBUG
2456       if(count > 4000 || count < 0)
2457          printf("Warning");
2458 #endif
2459             line.count += count;
2460             this.x += count + addedTabs + addedSpaces;
2461             this.selX = this.x;
2462             this.selY = this.y;
2463             this.selLine = this.line;
2464
2465             line.buffer[line.count] = '\0';
2466
2467             ComputeLength(line);
2468             ComputeColumn();
2469
2470             after.x = this.x; 
2471
2472             hasComment = HasCommentOrEscape(line);
2473             if(!undoBuffer.insideRedo)
2474             {
2475                int backDontRecord = undoBuffer.dontRecord;
2476                undoBuffer.dontRecord = 0;
2477                NotifyCharsAdded(master, this, &before, &after, this.pasteOperation);
2478                undoBuffer.dontRecord = backDontRecord;
2479             }
2480             if(style.syntax && (hadComment || hasComment || line != this.line))
2481             {
2482                style.recomputeSyntax = true;
2483                DirtyAll();
2484             }
2485          }
2486       }
2487       return true;
2488    }
2489
2490    void Emptyline(EditLine line, int y)
2491    {
2492       if(line.next)
2493          DelCh(line, y, 0, line.next, y+1, 0, true);
2494       else
2495          DelCh(line, y, 0, line, y, line.count, true);
2496    }
2497
2498    void GoToEnd(bool deselect)
2499    {
2500       if(this.line)
2501       {
2502          this.line = this.lines.last;
2503          if(this.y != this.lineCount-1)
2504             DirtyAll();
2505          else if (this.x != this.line.count)
2506             DirtyLine(this.lineCount-1);
2507          this.y = this.lineCount-1;
2508          this.x = this.line.count;
2509          ComputeColumn();
2510          if(deselect)
2511             Deselect();
2512       }
2513    }
2514
2515    void GoToHome(bool deselect)
2516    {
2517       if(this.line)
2518       {
2519          this.line = this.lines.first;
2520          if(this.y != 0)
2521             DirtyAll();
2522          else if (this.x !=0)
2523             DirtyLine(this.lineCount-1);
2524          this.y = 0;
2525          this.x = 0;
2526          this.col = 0;
2527          if(deselect)
2528             Deselect();
2529       }
2530    }
2531
2532    // Returns true if it needs scrolling
2533    bool FindMouse(int px, int py, int * tx, int * ty, EditLine * tline, bool half)
2534    {
2535       int w;
2536       int c;
2537       int x, y;
2538       EditLine line;
2539       bool needHScroll = false;
2540
2541       if(py < 0)
2542       {
2543          if(this.viewY > 0)
2544          {
2545             y = this.viewY-1;
2546             line = this.viewLine ? (void *)this.viewLine.prev : null;
2547          }
2548          else
2549          {
2550             y = 0;
2551             line = (void *)this.lines.first;
2552          }
2553       }
2554       else
2555       {
2556          py = Min(py, clientSize.h);
2557          py /= this.space.h;
2558          py = Min(py, this.lineCount);
2559          y = this.viewY;
2560          for(c = 0, line = this.viewLine; (line != (void *)this.lines.last && c<py); line = line.next, c++)
2561          {
2562             y++;
2563          }
2564       }
2565
2566       if( (px >= clientSize.w || px < clientSize.w/2) && this.viewX)
2567          needHScroll = true;
2568       px = Max(px,0);
2569       px = Min(px,clientSize.w+this.space.w);
2570
2571       if(tx && line)
2572       {
2573          *tx = AdjustXPosition(line, px + viewX, half, null, MAXINT, 0);
2574       }
2575
2576       if(tline) *tline = line;
2577       if(ty) *ty = y;
2578
2579       // Prevent divide by 0 from non valid this.font
2580       if(!this.space.h)
2581          return (y < this.viewY) || needHScroll;
2582       else
2583          return (y < this.viewY || y >= this.viewY + clientSize.h / this.space.h) || needHScroll;
2584       return false;
2585    }
2586
2587    // Minimal Update Management Functions
2588    void DirtyAll()
2589    {
2590       this.startY = 0;
2591       this.endY = clientSize.h-1;
2592    //   ErrorLog("DirtyAll\n");
2593    }
2594
2595    void DirtyEnd(int y)
2596    {
2597       if((y - this.viewY)*this.space.h < this.startY)
2598          this.startY = (y - this.viewY)*this.space.h+ YOFFSET;
2599       this.endY = clientSize.h-1;
2600       //ErrorLog("DirtyEnd %d\n", y);
2601    }
2602         
2603    void DirtyLine(int y)
2604    {
2605       if(y >= this.viewY)
2606       {
2607          if((y - this.viewY)*this.space.h < this.startY)
2608             this.startY = (y - this.viewY)*this.space.h + YOFFSET;
2609          if((y - this.viewY+1)*this.space.h > this.endY)
2610             this.endY = (y - this.viewY+1)*this.space.h-1 + YOFFSET;
2611       }
2612       //ErrorLog("DirtyLine %d\n", y);
2613    }
2614
2615    void UpdateDirty()
2616    {
2617       Box box;
2618
2619       if(style.recomputeSyntax)
2620       {
2621          FigureStartSyntaxStates(lines.first, true);
2622          style.recomputeSyntax = false;
2623       }
2624       box.left  = 0;
2625       if(this.startY > this.endY) return;
2626       if(this.startY <= 0 && this.endY >= clientSize.h-1)
2627       {
2628          Update(null);
2629       }
2630       else
2631       {
2632          box.right = clientSize.w-1;
2633          box.top   = this.startY;
2634          box.bottom  = this.endY;
2635          Update(box);
2636       }
2637       this.startY = clientSize.h;
2638       this.endY = 0;
2639    }
2640
2641    bool IsMouseOnSelection()
2642    {
2643       bool mouseOnSelection = false;
2644
2645       int x, y;
2646       int minY = Min(this.selY, this.y);
2647       int maxY = Max(this.selY, this.y);
2648       int minX = Min(this.selX, this.x);
2649       int maxX = Max(this.selX, this.x);
2650
2651       FindMouse(this.mouseX - this.space.w / 2, this.mouseY, &x, &y, null, false);
2652
2653       if(maxX != minX || maxY != minY)
2654       {
2655          if(y > minY && y < maxY)
2656             mouseOnSelection = true;
2657          else if(y == minY && y == maxY)
2658             mouseOnSelection = (x < maxX && x >= minX);
2659          else if(y == minY)
2660          {
2661             if(y == this.selY)
2662                mouseOnSelection = (x >= this.selX);
2663             else if(y == this.y)
2664                mouseOnSelection = (x >= this.x);
2665          }
2666          else if(y == maxY)
2667          {
2668             if(y == this.selY)
2669                mouseOnSelection = (x < this.selX);
2670             else if(y == this.y)
2671                mouseOnSelection = (x < this.x);
2672          }
2673       }
2674       return mouseOnSelection;
2675    }
2676
2677    void UpdateCaretPosition(bool setCaret)
2678    {
2679       if(line)
2680       {
2681          if(mouseMove || (!overwrite && !style.noCaret))
2682          {
2683             int max = this.mouseMove ? this.dropX : this.x;
2684             int y = this.mouseMove ? this.dropY : this.y;
2685             EditLine line = this.mouseMove ? this.dropLine : this.line;
2686             int c, x = 0;
2687             if(!(style.freeCaret))
2688                max = Min(max, line.count);
2689
2690             if(FontExtent && display)
2691             {
2692                for(c = 0; c < max; )
2693                {
2694                   int len = 1;
2695                   int start = c;
2696                   int w;
2697                   if(c < line.count)
2698                   {
2699                      byte ch = 0;
2700                      int numBytes;
2701                      for(len = 0; c < Min(max, line.count); c += numBytes)
2702                      {
2703                         ch = line.buffer[c];
2704                         numBytes = UTF8_NUM_BYTES(ch);
2705
2706                         if(ch == ' ' || ch == '\t')
2707                         {
2708                            if(!len) c++;
2709                            break;
2710                         }
2711                         len += numBytes;
2712                      }
2713                      if(!len && ch == ' ')
2714                      {
2715                         w = space.w;
2716                         len = 1;
2717                      }
2718                      else if(!len && ch == '\t')
2719                      {
2720                         w = (tabSize * space.w) - (x % (tabSize * space.w));
2721                         len = 1;
2722                      }
2723                      else
2724                         FontExtent(display, this.font, line.buffer + start, len, &w, null); 
2725                   }
2726                   else
2727                   {
2728                      w = space.w;
2729                      c++;
2730                   }
2731                   x += w;
2732                }               
2733             }
2734             if(setCaret)
2735                caretX = x;
2736             caretY = y * this.space.h;
2737             SetCaret(x + XOFFSET-2, y * space.h + YOFFSET, space.h);
2738          }
2739          else
2740             SetCaret(0, 0, 0);
2741
2742          NotifyCaretMove(master, this, y + 1, x + 1);
2743
2744          SelectionEnables();
2745       }
2746    }
2747
2748    void SelectionEnables()
2749    {
2750       if((x != selX || y != selY) && !selection)
2751       {
2752          if(!style.readOnly)
2753          {
2754             itemEditCut.disabled = false;
2755             itemEditDelete.disabled = false;
2756          }
2757          itemEditCopy.disabled = false;
2758
2759          this.selection = true;
2760       }
2761       else if((x == selX && y == selY) && selection)
2762       {
2763          itemEditCut.disabled = true;
2764          itemEditCopy.disabled = true;
2765          itemEditDelete.disabled = true;
2766
2767          this.selection = false;
2768       }
2769    }
2770
2771    void SetSelectCursor()
2772    {
2773       if(!inactive || !style.noSelect)
2774       {
2775          if(this.mouseMove)
2776             cursor = guiApp.GetCursor(arrow);
2777          else if(this.mouseSelect)
2778             cursor = guiApp.GetCursor(iBeam);
2779          else
2780          {
2781             if(IsMouseOnSelection())
2782                cursor = guiApp.GetCursor(arrow);
2783             else
2784                cursor = guiApp.GetCursor(iBeam);
2785          }
2786       }
2787    }
2788
2789    int AdjustXPosition(EditLine line, int position, bool half, int * px, int max, int sc)
2790    {
2791       int c = sc;
2792       int x = px ? *px : 0;
2793       while(true)
2794       {
2795          int start = c;
2796          int numBytes = 1;
2797          int len = 1;
2798          int w;
2799          if(c < Min(max, line.count))
2800          {
2801             byte ch = 0;
2802             int numBytes;
2803             for(len = 0; c < Min(max, line.count); c += numBytes)
2804             {
2805                ch = line.buffer[c];
2806                numBytes = UTF8_NUM_BYTES(ch);
2807
2808                if(ch == ' ' || ch == '\t')
2809                {
2810                   if(!len) c++;
2811                   break;
2812                }
2813                len += numBytes;
2814             }
2815             if(!len && ch == ' ')
2816             {
2817                w = space.w;
2818                len = 1;
2819             }
2820             else if(!len && ch == '\t')
2821             {
2822                w = (tabSize * space.w) - (x % (tabSize * space.w));
2823                len = 1;
2824             }
2825             else
2826                FontExtent(display, font, line.buffer + start, len, &w, null); 
2827          }
2828          else 
2829          {
2830             if(style.freeCaret && c < max)
2831                w = space.w;
2832             else 
2833             {
2834                if(px) *px = x;
2835                return c;
2836             }
2837             c++;
2838          }
2839          if(x + (((half && len == 1) ? (w / 2) : w)) >= position) 
2840          {
2841             int lastW;
2842             while(len > 0)
2843             {
2844                int a = start + len;
2845                lastW = w;
2846                if(a <= line.count)
2847                   while(a > 0 && !UTF8_IS_FIRST(line.buffer[--a]));
2848                else
2849                   a--;
2850                if(a > start)
2851                   FontExtent(display, font, line.buffer + start, a - start, &w, null);
2852                else
2853                   w = 0;
2854                if(position > x + (half ? ((w + lastW) / 2) : lastW)) break;
2855                len = a - start;
2856             }
2857             if(px) *px = x + w;
2858             return Min(this.maxLineSize - 1, start + len);
2859          }
2860          x += w;
2861       }
2862    }
2863
2864    void SetCursorToViewX()
2865    {
2866       bool selecting = this.x != selX || y != selY;
2867
2868       // Horizontal Adjustment
2869       int x = 0;
2870       int c = AdjustXPosition(line, viewX, false, &x, MAXINT, 0);
2871       if(this.x < c)
2872          this.x = x;
2873       else
2874       { 
2875          c = AdjustXPosition(line, viewX + clientSize.w - 1, false, &x, MAXINT, c);
2876          if(this.x > c)
2877             this.x = c;
2878      }
2879
2880       if(!selecting)
2881       {
2882          selX = this.x;
2883          selY = y;
2884          selLine = line;
2885       }
2886
2887       UpdateCaretPosition(false);
2888
2889       UpdateDirty();
2890       SetSelectCursor();
2891    }
2892
2893    void SetCursorToViewY()
2894    {
2895       int c, numLines;
2896       EditLine oldLine = this.line;
2897       
2898       bool selecting = this.x != this.selX || this.y != this.selY;
2899
2900       numLines = clientSize.h / this.space.h;
2901
2902       // Vertical Adjustment
2903       if(this.viewY > this.y)
2904       {
2905          this.y = this.viewY;
2906          this.line = this.viewLine;
2907       }
2908
2909       if(this.viewY + numLines <=  this.y)
2910       {
2911          EditLine line;
2912
2913          this.y = this.viewY-1;
2914          for(c = 0, line = this.viewLine; line && c<numLines; line = line.next, c++)
2915          {
2916             this.line = line;
2917             this.y++;
2918          }
2919       }
2920
2921       if(this.line != oldLine)
2922       {
2923          this.x = AdjustXPosition(this.line, caretX, true, null, MAXINT, 0);
2924          ComputeColumn();
2925       }
2926
2927       if(!selecting)
2928       {
2929          this.selX = this.x;
2930          this.selY = this.y;
2931          this.selLine = this.line;
2932       }
2933
2934       UpdateCaretPosition(false);
2935
2936       UpdateDirty();
2937       SetSelectCursor();
2938    }
2939
2940    /*
2941    bool SaveFile(char * fileName)
2942    {
2943       File f = eFile_Open(fileName, FO_WRITE);
2944       if(f)
2945       {
2946          Save(f, false);
2947          eWindow_SetModified(false);
2948          eInstance_Delete(f);
2949          return true;
2950       }
2951       return false;
2952    }
2953    */
2954
2955    bool OnActivate(bool active, Window previous, bool * goOnWithActivation, bool direct)
2956    {
2957       if(line)
2958       {
2959          if(!style.multiLine)
2960          {
2961             x = (active && this.active && !style.readOnly) ? line.count : 0;
2962             selX = 0;
2963             ComputeColumn();
2964             SetViewToCursor(true);
2965             DirtyLine(0);
2966             UpdateDirty();
2967          }
2968          // Update(null);
2969          if(!active && modified)
2970          {
2971             modified = false;
2972             if(!NotifyModified(master, this))
2973             {
2974                modified = true;
2975                *goOnWithActivation = false;
2976             }
2977          }
2978       }
2979       if(!active)
2980       {
2981          ReleaseCapture();
2982          if(timer) timer.Stop();
2983
2984          mouseSelect = false;
2985          wordSelect = false;
2986       }
2987       return true;
2988    }
2989
2990    bool OnResizing(int *w, int *h)
2991    {
2992       if(!*h)
2993          *h = space.h + 2;
2994       if(!*w)
2995          //*w = 80;
2996          *w = space.h * 80 / 14;
2997       return true;
2998    }
2999
3000    void OnResize(int w, int h)
3001    {
3002    /*
3003       if(!hasHorzScroll && !hasVertScroll && viewLine)
3004          SetViewToCursor(true);
3005    */
3006       //if(!hasHorzScroll && !hasVertScroll && viewLine)
3007       if(viewLine)
3008          SetViewToCursor(true);
3009       //else
3010    //     FixScrollArea();
3011    }
3012
3013    bool OnMiddleButtonDown(int x, int y, Modifiers mods)
3014    {
3015       if(style.readOnly) return true;
3016       Paste();
3017       return true;
3018    }
3019
3020    bool OnRightButtonDown(int x, int y, Modifiers mods)
3021    {
3022       this.rightButtonDown = true;
3023       // Copy();
3024       return true;
3025    }
3026
3027    bool OnRightButtonUp(int x, int y, Modifiers mods)
3028    {
3029       // Context Menu
3030       if(!parent.inactive && rightButtonDown)
3031       {
3032          PopupMenu popup;
3033          Menu contextMenu { };
3034
3035          MenuItem { contextMenu, "Cut\tCtrl+X", t, NotifySelect = itemEditCut.NotifySelect, disabled = !selection || style.readOnly };
3036          MenuItem { contextMenu, "Copy\tCtrl+C", c, NotifySelect = itemEditCopy.NotifySelect, disabled = !selection };
3037          MenuItem { contextMenu, "Paste\tCtrl+V", p, NotifySelect = itemEditPaste.NotifySelect, disabled = style.readOnly };
3038          MenuItem { contextMenu, "Delete\tDel", d, NotifySelect = itemEditDelete.NotifySelect, disabled = !selection || style.readOnly };
3039          MenuDivider { contextMenu };
3040          MenuItem { contextMenu, "Select All\tCtrl+A", a, NotifySelect = itemEditSelectAll.NotifySelect };
3041
3042          popup = PopupMenu { master = this, menu = contextMenu,
3043    /*
3044             nonClient = true, interim = false, parent = parent, 
3045             position = { x + clientStart.x + parent.clientStart.x + position.x, y + cientStart.y + parent.sy + clientStart.y + position.y };
3046    */
3047             position = { x + clientStart.x + absPosition.x - guiApp.desktop.position.x, y + clientStart.y + absPosition.y - guiApp.desktop.position.y }
3048          };
3049          popup.Create();
3050       }
3051       rightButtonDown = false;
3052       return true;
3053    }
3054
3055    bool OnLeftButtonDown(int mx, int my, Modifiers mods)
3056    {
3057       int x,y;
3058       EditLine line;
3059
3060       if(style.noSelect) return true;
3061
3062       if(!mods.isActivate)
3063       {
3064          Capture();
3065          mouseSelect = true;
3066       }
3067
3068       mouseX = mx - XOFFSET;
3069       mouseY = my;
3070
3071       FindMouse(mouseX, mouseY, &x, &y, &line, true);
3072 #ifdef _DEBUG
3073       //PrintLn("OnLeftButtonDown: ", x, ", ", y);
3074 #endif
3075       if(!style.readOnly)
3076       {
3077          if(wordSelect)
3078             mouseMove = false;
3079          else if(IsMouseOnSelection() && !mods.isActivate)
3080          {
3081             DirtyLine(this.y);
3082             mouseMove = true;
3083             dropX = x;
3084             dropY = y;
3085             dropLine = line;
3086          }
3087       }
3088
3089       if(!mouseMove && !wordSelect && (!mods.isActivate || style.multiLine))
3090       {
3091          if(mods.shift && !mods.isActivate)
3092          {
3093             this.x = x;
3094             this.y = y;
3095             this.line = line;
3096             DirtyAll();
3097          }
3098          else
3099          {
3100             SelDirty();
3101             DirtyLine(this.y);
3102             this.x = x;
3103             this.y = y;
3104             this.line = line;
3105             DirtyLine(this.y);
3106             this.selLine = this.line;
3107             this.selX = this.x;
3108             this.selY = this.y;
3109             //Deselect();
3110          }
3111          ComputeColumn();
3112       }
3113       
3114       UpdateDirty();
3115       UpdateCaretPosition(true);
3116       return true;
3117    }
3118
3119    bool OnLeftButtonUp(int x, int y, Modifiers mods)
3120    {
3121       timer.Stop();
3122
3123       mouseSelect = false;
3124       wordSelect = false;
3125       
3126       x -= XOFFSET;
3127       
3128       ReleaseCapture();
3129       if(!style.readOnly)
3130       {
3131          if(mouseMove)
3132          {
3133             EditLine line;
3134             FindMouse(mouseX, mouseY, &x, &y, &line, true);
3135 #ifdef _DEBUG
3136             //PrintLn("MouseMove: ", x, ", ", y);
3137 #endif
3138             dropX = x;
3139             dropY = y;
3140             dropLine = line;
3141
3142             mouseMove = IsMouseOnSelection();
3143
3144             if(!mouseMove)
3145             {
3146                int size = SelSize();
3147                if(size)
3148                {
3149                   char * text = new char[size+1];
3150                   if(text)
3151                   {
3152                      int moveX = 0;
3153                      GetSel(text, false);
3154
3155                      if(Max(selY, this.y) == dropY)
3156                      {
3157                         if(this.x > selX)
3158                         {
3159                            if(this.dropX > this.selX)
3160                               moveX = this.x - this.selX;
3161                         }
3162                         else
3163                         {
3164                            if(this.dropX > this.x)
3165                               moveX = this.selX - this.x;
3166                         }
3167                      }
3168                      DelSel(null);
3169                      this.dropX -= moveX;
3170                      this.selX = this.x = this.dropX;
3171                      this.selY = this.y = this.dropY;
3172                      this.selLine = this.line = this.dropLine;
3173                      AddS(text);
3174                      SetViewToCursor(true);
3175                      delete text;
3176                      Modified();
3177 #ifdef _DEBUG
3178                    /*  {
3179                         byte * c = ((EditBox)this).multiLineContents, * c2;
3180                         int l1 = c ? strlen(c) : 0, l2;
3181                         Undo();
3182                         Redo();
3183                         c2 = ((EditBox)this).multiLineContents;
3184                         l2 = c2 ? strlen(c2) : 0;
3185                         if(l1 != l2 || (l1 && strcmp(c, c2)))
3186                         {
3187                            PrintLn("Fail!");
3188                         }
3189                         delete c;
3190                      }
3191                      */
3192 #endif
3193                   }
3194                }
3195             }
3196             else
3197             {
3198                SelDirty();
3199                DirtyLine(this.y);
3200                this.x = x;
3201                this.y = y;
3202                this.line = line;
3203                ComputeColumn();
3204                DirtyLine(this.y);
3205                Deselect();
3206                UpdateDirty();
3207             }
3208          }
3209          else
3210          {
3211             EditLine line;
3212             mouseX = x;
3213             mouseY = y;
3214
3215             FindMouse(mouseX, mouseY, &x, &y, &line, true);
3216 #ifdef _DEBUG
3217             //PrintLn("Dropped: ", x, ", ", y);
3218 #endif
3219             NotifyDropped(master, this, x, y);
3220          }
3221       }
3222       mouseMove = false;
3223       return true;
3224    }
3225
3226    bool OnMouseMove(int mx, int my, Modifiers mods)
3227    {
3228       int x,y;
3229       EditLine line;
3230       bool needScroll;
3231
3232       if(mods != -1 && mods.isSideEffect) 
3233       { 
3234          SetSelectCursor(); 
3235          return true; 
3236       }
3237       if(style.noSelect) return true;
3238       if(wordSelect) return true;
3239       mouseX = mx - XOFFSET;
3240       mouseY = my;
3241
3242       needScroll = FindMouse(this.mouseX, this.mouseY, &x, &y, &line, true);
3243
3244       if(this.mouseMove || this.mouseSelect)
3245       {
3246          if(!needScroll)
3247             timer.Stop();
3248          else 
3249          {
3250             if(needScroll)
3251                timer.Start();
3252             if(mods != -1 && 
3253                ((style.hScroll) || (style.vScroll)))
3254                return true;
3255          }
3256       }
3257
3258       if(this.mouseMove)
3259       {
3260          DirtyLine(this.dropY);
3261          this.dropX = x;
3262          this.dropY = y;
3263          DirtyLine(this.dropY);
3264          this.dropLine = line;
3265          SetViewToCursor(true);
3266 #ifdef _DEBUG
3267          //PrintLn("MouseMove: ", "dropX = ", x, ", dropY = ", y);
3268 #endif
3269       }
3270       else if(this.mouseSelect)
3271       {
3272          DirtyLine(this.selY);
3273          DirtyLine(this.y);
3274          this.x = x;
3275          this.y = y;
3276          ComputeColumn();
3277          DirtyLine(this.y);
3278          this.line = line;
3279          SetViewToCursor(true);
3280          UpdateDirty();
3281 #ifdef _DEBUG
3282          //PrintLn("MouseSelect: ", "x = ", x, ", y = ", y);
3283 #endif
3284       }
3285       SetSelectCursor();
3286       return true;
3287    }
3288
3289    bool OnLeftDoubleClick(int mx, int my, Modifiers mods)
3290    {
3291       int x,y;
3292       EditLine line;
3293 #ifdef _DEBUG
3294       //PrintLn("OnLeftDoubleClick: ", mx, ", ", my, ", mods = ", mods);
3295 #endif
3296       mx -= XOFFSET;
3297
3298       if(style.noSelect) return true;
3299       FindMouse(mx, my, &x, &y, &line, false);
3300       if(!NotifyDoubleClick(master, this, line, mods))
3301          return false;
3302       if(x < line.count)
3303       {
3304          int c;
3305          int start = -1;
3306          int numBytes;
3307          for(c = x; c >= 0; c--)
3308          {
3309             unichar ch;
3310             while(c > 0 && !UTF8_IS_FIRST(line.buffer[c])) c--;
3311             ch = UTF8_GET_CHAR(line.buffer + c, numBytes);
3312             if(!IS_ALUNDER(ch))
3313                break;
3314             start = c;
3315          }
3316          if(start != -1)
3317          {
3318             for(c = start; c<line.count; c += numBytes)
3319             {
3320                unichar ch = UTF8_GET_CHAR(line.buffer + c, numBytes);
3321                if(!IS_ALUNDER(ch))
3322                   break;
3323             }
3324             SelDirty();
3325             DirtyLine(this.y);
3326             this.y = y;
3327             DirtyLine(this.y);
3328             this.selX = start;
3329             this.x = c;
3330             ComputeColumn();
3331             this.line = this.selLine = line;
3332             this.wordSelect = (c != start);
3333             UpdateDirty();
3334          }
3335       }
3336       return true;
3337    }
3338
3339    /*
3340    bool OnPostCreate()
3341    {
3342       if(isDocument)
3343       {
3344          Menu fileMenu { menu, "File", F };
3345          saveDialog = fileDialog;
3346          MenuItem { fileMenu, "Save\tCtrl+S", S, CtrlS, NotifySelect = MenuFileSave };
3347          MenuItem { fileMenu, "Save As...", A, NotifySelect = MenuFileSaveAs };
3348       }
3349       return true;
3350    }
3351    */
3352
3353    void ComputeFont()
3354    {
3355       if(FontExtent)
3356       {
3357          FontExtent(display, font, " ", 1, (int *)&space.w, (int *)&space.h);
3358          FontExtent(display, font, "W", 1, (int *)&large.w, (int *)&large.h);
3359
3360          space.w = Max(space.w, 1);
3361          large.w = Max(large.w, 1);
3362          space.h = Max(space.h, 1);
3363          large.h = Max(large.h, 1);
3364
3365          {
3366             EditLine line;
3367             for(line = lines.first; line; line = line.next)
3368                ComputeLength(line);
3369             FindMaxLine();
3370          }
3371
3372          if(viewLine)
3373             SetViewToCursor(true);
3374
3375          FixScrollArea();
3376
3377          Update(null);
3378       }
3379    }
3380
3381    bool OnLoadGraphics()
3382    {
3383       FontExtent = Display::FontExtent;
3384       font = fontObject;
3385       ComputeFont();
3386       // UpdateCaretPosition(true);
3387       return true;
3388    }
3389
3390    void OnUnloadGraphics()
3391    {
3392       this.font = null;
3393    }
3394
3395    bool OnKeyHit(Key key, unichar ch)
3396    {
3397       bool shift = (key.shift) ? true : false;
3398 #ifdef _DEBUG
3399       //PrintLn("OnKeyHit: code = ", key.code, ", mods = ", key.modifiers, ", ch = ", (int)ch);
3400 #endif
3401       if(!ch && !key.alt && !key.ctrl)
3402       {
3403          key.code = (SmartKey)key.code;
3404       }
3405
3406       switch(key.code) //(ch || key.alt || key.ctrl) ? key.code : (Key)(SmartKey)key.code)
3407       {
3408          case backSpace:
3409             if(style.readOnly) break;
3410             if(style.stuckCaret) GoToEnd(true);
3411             if(!(style.freeCaret))
3412             {
3413                this.x = Min(this.x, this.line.count);
3414             }
3415             if(key.ctrl)
3416             {
3417                int y;
3418                bool done = false;
3419                EditLine line = this.line;
3420                int c;
3421                for(y = this.y; y>= 0; y--)
3422                {
3423                   c = (y == this.y) ? (this.x-1) : line.count-1;
3424
3425                   // Slow down when going on lines...
3426                   if(y != this.y) break;
3427
3428                   for(; c>=0; c--)
3429                   {
3430                      //if(this.line.buffer[c] != '\t' && this.line.buffer[c] != ' ')
3431                      if(IS_ALUNDER(line.buffer[c]))
3432                         break;
3433                   }
3434                   for(; c>=0; c--)
3435                   {
3436                      if(!IS_ALUNDER(line.buffer[c]))
3437                      {
3438                      //if(this.line.buffer[c] == '\t' || this.line.buffer[c] == ' ')
3439                         done = true;
3440                         break;
3441                      }
3442                   }
3443                   if(done)
3444                      break;
3445                   if(line.prev)
3446                      line = line.prev;
3447                   else
3448                      break;
3449                }
3450                //if(this.x != 0)
3451                {
3452                   DelCh(line,y,c+1,this.line,this.y,this.x, true);
3453                   this.x = this.selX = Min(c+1, line.count);
3454                   this.y = this.selY = y;
3455                   this.line = this.selLine = line;
3456                   SetViewToCursor(true);
3457                }
3458                ComputeColumn();
3459             }
3460             else
3461             {
3462                BackSpace();
3463             }
3464             return false;
3465             //break;
3466          case del:
3467             if(style.readOnly) break;
3468             if(style.stuckCaret) break;
3469
3470             if(shift)
3471             {
3472                Cut();
3473             }
3474             else
3475             {
3476                // Delete selection
3477                if(this.line != this.selLine || this.x != this.selX)
3478                {
3479                   DelSel(null);
3480                   SetViewToCursor(true);
3481                   Modified();
3482                }
3483                // Delete word
3484                else if(key.ctrl)
3485                {
3486                   if(this.x < this.line.count)
3487                   {
3488                      int i;
3489                      int length;
3490                      //Can't delete right away as that would change the line.count
3491                      for(i = this.x; i < this.line.count; i++)
3492                      {
3493                         if(!IS_ALUNDER(this.line.buffer[i]))
3494                            break;
3495                         DelCh(this.line, this.y, this.x, this.line, this.y, this.x+1, false);
3496                         i--;
3497                      }
3498                      
3499                      for(; i < this.line.count; i++)
3500                      {
3501                         //Delete trailing whitespace
3502                         if(IS_ALUNDER(this.line.buffer[i]))
3503                            break;
3504                         DelCh(this.line, this.y, this.x, this.line, this.y, this.x+1, false);
3505                         i--;
3506                      }
3507                      SetViewToCursor(true);
3508                      Modified();
3509                   }
3510                   else if(this.line.next)
3511                   {
3512                      DelCh(this.line, this.y, this.x, this.line.next, this.y+1, 0, false);
3513                      SetViewToCursor(true);
3514                      Modified();
3515                   }
3516                }
3517                else 
3518                {
3519                   if(!(style.freeCaret))
3520                   {
3521                      this.selX = this.x = Min(this.x, this.line.count);
3522                      ComputeColumn();
3523                   }
3524                   if(this.x < this.line.count)
3525                   {
3526                      DelCh(this.line, this.y, this.x, this.line, this.y, this.x+1, false);
3527                   }
3528                   else if(this.line.next)
3529                   {
3530                      DelCh(this.line, this.y, this.x, this.line.next, this.y+1, 0, false);
3531                   }
3532                   SetViewToCursor(true);
3533                   Modified();
3534                }
3535             }
3536             return false;
3537             //break;
3538          case enter:
3539          case keyPadEnter:
3540          {
3541             if(!key.alt && !key.ctrl)
3542             {
3543                int c;
3544                int position = 0;
3545                bool stuffAfter = false;
3546                char * addString;
3547                int len = 0;
3548
3549                if(style.stuckCaret) GoToEnd(true);
3550                if(style.readOnly) break;
3551                if(!(style.multiLine)) break;
3552
3553                for(c = 0; c<this.line.count && c<this.x; c++)
3554                {
3555                   if(this.line.buffer[c] == '\t')
3556                      position += this.tabSize - (position % this.tabSize);
3557                   else if(this.line.buffer[c] == ' ')
3558                      position++;
3559                   else
3560                      break;
3561                }
3562                if(!line.count)
3563                   position = x;
3564
3565                if(this.x < this.line.count)
3566                   stuffAfter = true;
3567                   
3568                //If last character is a { indent one tab
3569                if(this.line.buffer[this.x - 1] == '{')
3570                {
3571                   //Except if the next non space character is a }
3572                   bool indent = false;
3573                   int i;
3574                   for(i = this.x; i < this.line.size; i++)
3575                      if(this.line.buffer[i] != ' ' && this.line.buffer[i] != '\t')
3576                      {
3577                         if(this.line.buffer[i] != '}')
3578                            indent = true;
3579                         break;
3580                      }
3581                   if(indent)
3582                      position += this.tabSize;
3583                }
3584
3585                addString = new char[position + 2];
3586                addString[len++] = '\n';
3587                addString[len] = '\0';
3588
3589                if(stuffAfter || !style.freeCaret)
3590                {
3591                   for(c = 0; c<position; )
3592                   {
3593                      if(style.useTab && c + this.tabSize <= position)
3594                      {
3595                         addString[len++] = '\t';
3596                         c += this.tabSize;
3597                      }
3598                      else
3599                      {
3600                         addString[len++] = ' ';
3601                         c++;
3602                      }
3603                   }
3604                   addString[len] = '\0';
3605                }
3606                if(AddS(addString))
3607                {
3608                   if(!stuffAfter && style.freeCaret)
3609                   {
3610                      this.x = this.selX = position;
3611                      ComputeColumn();
3612                   }
3613                   FindMaxLine();
3614                   SetViewToCursor(true);
3615                   Modified();
3616                }
3617                delete addString;
3618                return false;
3619             }
3620             break;
3621          }
3622          case left:
3623          {
3624             if(style.stuckCaret) break;
3625             if(!(style.freeCaret))
3626             {
3627                this.x = Min(this.x, this.line.count);
3628                this.selX = Min(this.selX, this.selLine.count);
3629                ComputeColumn();
3630             }
3631             if(!shift) SelDirty();
3632             if(key.ctrl)
3633             {
3634                bool foundAlpha = false;
3635                bool found = false;
3636                int y = this.y;
3637                EditLine line, lastLine;
3638                int lastC, lastY;
3639
3640                for(line = this.line; (line && !found); line = line.prev, y--)
3641                {
3642                   int start;
3643                   int c;
3644
3645                   if(this.x == 0 && line != this.line)
3646                   {
3647                      foundAlpha = true;
3648                      lastC = line.count;
3649                      lastY = y;
3650                      lastLine = line;
3651                      break;
3652                   }
3653
3654                   if(line == this.line) start = this.x -1; else start = line.count-1;
3655                   start = Min(start, line.count-1);
3656
3657                   for(c = start; c >= 0;)
3658                   {
3659                      int numBytes;
3660                      unichar ch = UTF8_GET_CHAR(line.buffer + c, numBytes);
3661                      if(IS_ALUNDER(ch))
3662                      {
3663                         foundAlpha = true;
3664                         lastC = c;
3665                         lastY = y;
3666                         lastLine = line;
3667                      }
3668                      else
3669                      {
3670                         if(foundAlpha)
3671                         {
3672                            found = true;
3673                            break;
3674                         }
3675                      }
3676                      while(--c)
3677                      {
3678                         byte ch = line.buffer[c];
3679                         if(UTF8_IS_FIRST(ch)) break;
3680                      } 
3681
3682                   }
3683                   // No next word found, 
3684                   if(!found && ( this.x > 0 || (!line.count && this.x)))
3685                   {
3686                      foundAlpha = true;
3687                      lastC = 0;
3688                      lastY = y;
3689                      lastLine = line;
3690                      break;
3691                   }
3692                }
3693                if(foundAlpha)
3694                {
3695                   DirtyLine(this.y);
3696                   this.x = lastC;
3697                   this.y = lastY;
3698                   this.line = lastLine;
3699                   DirtyLine(this.y);
3700                   ComputeColumn();
3701                }
3702             }
3703             else
3704             {
3705                if(x > 0)
3706                {
3707                   if(x <= line.count)
3708                   {
3709                      byte * buffer = line.buffer;
3710                      while(--x)
3711                      {
3712                         byte ch = buffer[x];
3713                         if(UTF8_IS_FIRST(ch)) break;
3714                      } 
3715                   }
3716                   else
3717                      x--;
3718                   DirtyLine(y);
3719                }
3720                else if(line.prev)
3721                {
3722                   line = line.prev;
3723                   DirtyLine(y);
3724                   x = line.count;
3725                   y--;
3726                   DirtyLine(y);
3727                }
3728                ComputeColumn();
3729             }
3730             if(!shift) Deselect();
3731             SetViewToCursor(true);
3732             //break;
3733             return false;
3734          }
3735          case right:
3736          {
3737             if(style.stuckCaret) break;
3738             if(!(style.freeCaret))
3739             {
3740                this.x = Min(this.x, this.line.count);
3741                this.selX = Min(this.selX, this.selLine.count);
3742                ComputeColumn();
3743             }
3744             if(!shift) SelDirty();
3745             if(!shift && (this.x != this.selX || this.y != this.selY));
3746             else if(key.ctrl)
3747             {
3748                bool onAChar = false;
3749                if(this.selX != this.x || this.selY != this.y)
3750                   onAChar = true;
3751                if(this.x<this.line.count)
3752                   if(this.line.buffer[this.x] != '\t' && this.line.buffer[this.x] !=' ')
3753                      onAChar = true;
3754                if(key.shift && onAChar &&
3755                   ((this.y > this.selY)||((this.selY == this.y)&&(this.x >= this.selX))))
3756                {
3757                   bool foundAlpha = false;
3758                   bool found = false;
3759                   EditLine line, lastLine;
3760                   int y = this.y;
3761                   int lastC, lastY, lastNumBytes;
3762                   
3763                   for(line = this.line; (line && !found); line = line.next, y++)
3764                   {
3765                      int start = (line == this.line) ? this.x : 0;
3766                      int c;
3767                      int numBytes;
3768                      for(c = start; c < line.count; c += numBytes)
3769                      {
3770                         unichar ch = UTF8_GET_CHAR(line.buffer + c, numBytes);
3771                         if(IS_ALUNDER(ch))
3772                         {
3773                            foundAlpha = true;
3774                            lastC = c;
3775                            lastNumBytes = numBytes;
3776                            lastY = y;
3777                            lastLine = line;
3778                         }
3779                         else if(foundAlpha)
3780                         {
3781                            found = true;
3782                            break;
3783                         }
3784                      }
3785                      if(!found && (c != this.x || line != this.line))
3786                      {
3787                         found = true;
3788                         lastLine = line;
3789                         lastC = line.count;
3790                         lastNumBytes = 0;
3791                         lastY = y;
3792                         break;
3793                      }
3794                   }  
3795                   if(found)
3796                   {
3797                      DirtyLine(this.y);
3798                      this.x = lastC + lastNumBytes;
3799                      this.y = lastY;
3800                      this.line = lastLine;
3801                      DirtyLine(this.y);
3802                      ComputeColumn();
3803                   }
3804                }
3805                else
3806                {
3807                   bool foundAlpha = false;
3808                   bool found = false;
3809                   EditLine line;
3810                   int y = this.y;
3811
3812                   for(line = this.line; (line && !found); line = line.next, y++)
3813                   {
3814                      int start = (line == this.line) ? this.x : 0;
3815                      int c;
3816                      int numBytes;
3817                      for(c = start; c < line.count; c += numBytes)
3818                      {
3819                         unichar ch = UTF8_GET_CHAR(line.buffer + c, numBytes);
3820                         if(!IS_ALUNDER(ch))
3821                            foundAlpha = true;
3822                         else if(foundAlpha)
3823                         {
3824                            found = true;
3825                            DirtyLine(this.y);
3826                            this.x = c;
3827                            this.y = y;
3828                            this.line = line;
3829                            DirtyLine(this.y);
3830                            ComputeColumn();
3831                            break;
3832                         }
3833                      }
3834                      // No next word found, 
3835                      if(!found && (c != this.x || line != this.line))
3836                      {
3837                         found = true;
3838                         DirtyLine(this.y);
3839                         this.x = line.count;
3840                         this.y = y;
3841                         this.line = line;
3842                         DirtyLine(this.y);
3843                         ComputeColumn();
3844                      }
3845                      foundAlpha = true;
3846                   }
3847                }
3848             }
3849             else
3850             {
3851                if(x < line.count || (style.freeCaret && line.count < maxLineSize))
3852                {
3853                   if(x < line.count)
3854                   {
3855                      byte * buffer = line.buffer;
3856                      while(++x)
3857                      {
3858                         byte ch = buffer[x];
3859                         if(UTF8_IS_FIRST(ch)) break;
3860                      } 
3861                   }
3862                   else
3863                      x++;
3864                   ComputeColumn();
3865                   DirtyLine(y);
3866                }
3867                else
3868                {
3869                   if(line.next)
3870                   {
3871                      DirtyLine(y);
3872                      line = line.next;
3873                      y++;
3874                      x = 0;
3875                      col = 0;
3876                      DirtyLine(y);
3877                   }
3878                }
3879             }
3880             if(!shift) Deselect();
3881             SetViewToCursor(true);
3882             // break;
3883             return false;
3884          }
3885          case up:
3886             if(key.ctrl)
3887             {
3888                if(!style.vScroll || hasVertScroll) break;
3889                LineUp();
3890                return false;
3891             }
3892             else
3893             {
3894                if(style.stuckCaret) break;
3895                
3896                if(!shift) SelDirty();
3897                DirtyLine(this.y);
3898
3899                if(style.wrap)
3900                {
3901                }
3902                else if(line.prev)
3903                {
3904                   line = line.prev;
3905                   this.y--;
3906                   this.x = AdjustXPosition(line, caretX, true, null, MAXINT, 0);
3907                }
3908                
3909                DirtyLine(this.y);
3910                if(!shift) Deselect();
3911                ComputeColumn();
3912                SetViewToCursor(false);
3913
3914                /*
3915                if(caretY == this.y * space.h)
3916                {
3917                   if(line.prev)
3918                   {
3919                      line = line.prev;
3920                      this.y--;
3921                      if(!style.freeCaret)
3922                         this.x = Min(this.x, line.count);
3923                      caretY = MAXINT;
3924                   }
3925                   else
3926                      return false;
3927                }
3928
3929                {
3930                   int th = space.h;
3931                   int textPos = 0;
3932                   int sx = 0, sy = this.y * space.h;
3933                   char * text = line.text;
3934                   int maxW = clientSize.w - sx;
3935                   display.FontExtent(font, " ", 1, null, &th);
3936
3937                   do
3938                   {
3939                      int startPos = textPos;
3940                      int width = 0;
3941                      int x = 0;
3942                      bool lineComplete = false;
3943
3944                      if(!style.wrap && caretY == MAXINT)
3945                      {
3946                         caretY = sy + th;
3947                         //textPos = line.count;
3948                         //lineComplete = true;
3949                      }
3950
3951                      for(; (style.freeCaret || textPos < line.count) && !lineComplete;)
3952                      {
3953                         int w = 0;
3954                         int len;
3955                         char * nextSpace = (textPos < line.count) ? strchr(text + textPos, ' ') : (text + textPos);
3956
3957                         if(nextSpace)
3958                            len = (nextSpace - (text + textPos));
3959                         else
3960                            len = line.count - textPos;
3961                         
3962                         if(textPos < line.count)
3963                         {
3964                            display.FontExtent(font, text + textPos, len, &w, null);
3965                         }
3966                         if(nextSpace) { w += space.w; len++; }
3967
3968                         if(style.wrap && x + width + w > maxW && x > 0)
3969                         {
3970                            lineComplete = true;
3971                            break;
3972                         }
3973                         textPos += len;
3974                         width += w;
3975                         if(nextSpace)
3976                         {
3977                            x += width;
3978                            width = 0;
3979                         }
3980                         if((!style.freeCaret && textPos >= line.count) || (sy == caretY - th && caretX <= x + width + sx))
3981                         {
3982                            x += width;
3983                            this.x = textPos;
3984                            while(this.x > 0 && x + sx > caretX && this.x > startPos)
3985                            {
3986                               int len;
3987                               if(this.x > line.count)
3988                                  this.x--;
3989                               else
3990                                  while(this.x > 0 && !UTF8_IS_FIRST(text[--this.x]));
3991                               len = this.x - startPos;
3992                               display.FontExtent(font, text + startPos, len, &x, null);
3993                            }
3994
3995                            DirtyLine(this.y);
3996                            if(!shift) Deselect();
3997                            ComputeColumn();
3998                            SetViewToCursor(false);
3999                            return false;
4000                         }
4001                      }
4002                      if(sy == caretY - th || textPos >= line.count)
4003                      {
4004                         if(textPos >= line.count)
4005                         {
4006                            int c = textPos - 1;
4007                            while(c > 0 && text[c] == ' ') c--;
4008                            this.x = c + 1;
4009                         }
4010                         else
4011                            this.x = line.count;
4012
4013                         DirtyLine(this.y);
4014                         if(!shift) Deselect();
4015                         ComputeColumn();
4016                         SetViewToCursor(false);
4017                         return false;
4018                      }
4019                      sy += th;
4020                      sx = 0;
4021                   } while(textPos < line.count);
4022
4023                   DirtyLine(this.y);
4024                   if(!shift) Deselect();
4025                   ComputeColumn();
4026                   SetViewToCursor(false);
4027                   return false;
4028                }
4029                */
4030                
4031                // PREVIOUS CODE
4032                /*
4033                if(this.line.prev)
4034                {
4035                   int x = AdjustXPosition(this.line, this.line.prev, true, null, MAXINT, 0);
4036                   if(!shift) SelDirty();
4037                   this.line = this.line.prev;
4038                   DirtyLine(this.y);
4039                   this.y--;
4040
4041                   DirtyLine(this.y);
4042                   this.x = x;
4043                   if(!shift) Deselect();
4044
4045                   ComputeColumn();
4046
4047                   SetViewToCursor(false);
4048                }
4049                */
4050
4051             }
4052             // break;
4053             return style.multiLine ? false : true;
4054          case down:
4055             if(key.ctrl)
4056             {
4057                if(!style.vScroll || hasVertScroll)
4058                   break;
4059                LineDown();
4060                return false;
4061             }
4062             else
4063             {
4064                if(style.stuckCaret) break;
4065                {
4066                   int th = space.h;
4067                   int textPos = 0;
4068                   int sx = 0, sy = this.y * this.space.h;
4069                   int maxW = clientSize.w - sx;
4070                   char * text = line.buffer;
4071
4072                   if(!shift) SelDirty();
4073                   DirtyLine(this.y);
4074                   
4075                   if(style.wrap)
4076                   {
4077                      /*
4078                      if(AdjustXPosition(line, maxW, this.x, line.count, true, null, MAXINT, 0) <= line.count)
4079                      {
4080
4081                      }
4082                      */
4083                   }
4084                   else if(line.next)
4085                   {
4086                      line = line.next;
4087                      this.y++;
4088                      this.x = AdjustXPosition(line, caretX, true, null, MAXINT, 0);
4089                   }
4090
4091                   if(!shift) Deselect();
4092                   ComputeColumn();
4093                   if(this.selX != this.x || this.selY != this.y)
4094                      DirtyLine(this.y);
4095                   SetViewToCursor(false);
4096                   
4097                   /*
4098                   while(!textPos || (style.freeCaret || textPos<line.count))
4099                   {
4100                      int startPos = textPos;
4101                      int width = 0;
4102                      int x = 0;
4103                      bool lineComplete = false;
4104                      if(!style.wrap && sy <= caretY)
4105                      {
4106                         textPos = line.count;
4107                         lineComplete = true;
4108                      }
4109                      for(; (style.freeCaret || textPos<line.count) && !lineComplete;)
4110                      {
4111                         int w = 0;
4112                         int len;
4113                         char * nextSpace = (textPos < line.count) ? strchr(text + textPos, ' ') : (text + textPos);
4114
4115                         if(nextSpace)
4116                            len = (nextSpace - (text + textPos));
4117                         else
4118                            len = line.count - textPos;
4119                         
4120                         if(textPos < line.count)
4121                         {
4122                            display.FontExtent(font, text + textPos, len, &w, &th);
4123                         }
4124                         if(nextSpace) { w += space.w; len++; }
4125                         if(style.wrap && x + width + w > maxW && x > 0)
4126                         {
4127                            lineComplete = true;
4128                            break;
4129                         }
4130                         textPos += len;
4131                         width += w;
4132                         if(nextSpace)
4133                         {
4134                            x += width;
4135                            width = 0;
4136                         }
4137                         if(sy > caretY && ((!style.freeCaret && textPos >= line.count) || caretX <= x + width + sx))
4138                         {
4139                            this.x = textPos;
4140                            x += width;
4141                            while(this.x > 0 && x + sx > caretX && textPos > startPos)
4142                            {
4143                               int len;
4144                               if(this.x > line.count)
4145                                  this.x--;
4146                               else
4147                                  while(this.x > 0 && !UTF8_IS_FIRST(text[--this.x]));
4148                                  
4149                               len = this.x - startPos;
4150                               display.FontExtent(font, text + startPos, len, &x, null);
4151                            }
4152                            
4153                            if(!shift) Deselect();
4154                            ComputeColumn();
4155
4156                            SetViewToCursor(false);
4157                            return false;
4158                         }
4159                      }
4160                      if(sy > caretY)
4161                      {
4162                         this.x = line.count;
4163
4164                         DirtyLine(this.y);
4165                         if(!shift) Deselect();
4166                         ComputeColumn();
4167
4168                         SetViewToCursor(false);
4169                         return false;
4170                      } 
4171                      else if(textPos >= line.count && line.next)
4172                      {
4173                         startPos = 0;
4174                         textPos = 0;
4175                         line = line.next;
4176                         this.y++;
4177                         sy = this.y * this.space.h;
4178                         sx = 0; //textBlock.startX;
4179                         text = line.buffer;
4180                      }
4181                      else
4182                      {
4183                         sy += th;
4184                         sx = 0; //textBlock.startX;
4185                      }
4186                   }
4187                   */
4188                   /*
4189                   if(line.next)
4190                   {
4191                      line = line.next;
4192                      this.x = Min(this.x, line.count);
4193                      //int x = AdjustXPosition(this.line, this.line.next, true, null, MAXINT, 0);
4194                      this.y++;
4195                      if(!shift) Deselect();
4196                      ComputeColumn();
4197
4198                      if(this.selX != this.x || this.selY != this.y)
4199                         DirtyLine(this.y);
4200
4201                      SetViewToCursor(false);
4202                   }
4203                   */
4204                }
4205                /* PREVIOUS CODE
4206                if(this.line.next)
4207                {
4208                   int x = AdjustXPosition(this.line, this.line.next, true, null, MAXINT, 0);
4209                   if(!shift) SelDirty();
4210                   this.line = this.line.next;
4211                   DirtyLine(this.y);
4212                   this.y++;
4213                   this.x = x;
4214                   if(!shift) Deselect();
4215                   ComputeColumn();
4216
4217                   if(this.selX != this.x || this.selY != this.y)
4218                      DirtyLine(this.y);
4219
4220                   SetViewToCursor();
4221                }
4222                */
4223             }
4224             // break;
4225             return style.multiLine ? false : true;
4226          case home:
4227          {
4228             if(style.stuckCaret) break;
4229             if(!(style.freeCaret))
4230                this.selX = Min(this.selX, this.selLine.count);
4231
4232             if(!shift) SelDirty();
4233             if(key.ctrl)
4234             {
4235                this.line = this.lines.first;
4236                if(this.y != 0 || this.x != 0)
4237                   DirtyAll();
4238                this.y = 0;
4239                this.x = 0;
4240                this.col = 0;
4241             }
4242             else
4243             {
4244                if(style.smartHome)
4245                {
4246                   EditLine line = this.line;
4247                   int c;
4248                   for(c=0; line.buffer[c]; c++)
4249                      if(line.buffer[c] != ' ' && line.buffer[c] != '\t')
4250                         break;
4251                   if(c != 0 || this.x)
4252                      DirtyLine(this.y);
4253                   if(this.x != c) 
4254                      this.x = c;
4255                   else
4256                      this.x = 0;
4257                }
4258                else
4259                {
4260                   /*if(this.x != 0)
4261                      DirtyLine(this.y);*/
4262                   this.x = 0;
4263                }
4264                ComputeColumn();
4265             }
4266             if(!shift) Deselect();
4267             SetViewToCursor(true);
4268             //break;
4269             return false;
4270          }
4271          case end:
4272          {
4273             if(style.stuckCaret) break;
4274             if(!(style.freeCaret))
4275                this.selX = Min(this.selX, this.selLine.count);
4276
4277             if(!shift) SelDirty();
4278             if(key.ctrl)
4279             {
4280                GoToEnd(false);
4281             }
4282             else if(this.x != this.line.count)
4283             {
4284                this.x = this.line.count;
4285                //DirtyLine(this.y);
4286                ComputeColumn();
4287             }
4288             if(!shift) Deselect();
4289             SetViewToCursor(true);
4290             //break;
4291             return false;
4292          }
4293          case tab:
4294             if(style.tabKey && !key.ctrl)
4295             {
4296                if(this.selY != this.y && style.tabSel)
4297                {
4298                   EditLine firstLine, lastLine;
4299                   EditLine line;
4300                   int y, x;
4301
4302                   // Do multi line selection tabbing here
4303                   if(this.selY < this.y)
4304                   {
4305                      firstLine = this.selLine;
4306                      lastLine = this.line;
4307                      y = this.selY;
4308                      x = this.x;
4309                   }
4310                   else
4311                   {
4312                      // Selecting going up
4313                      firstLine = this.line;
4314                      lastLine = this.selLine;
4315                      y = this.y;
4316                      x = this.selX;
4317                   }
4318                   ComputeColumn();
4319                   if(shift)
4320                   {
4321                      for(line = firstLine; line; line = line.next, y++)
4322                      {
4323                         if(line != lastLine || x)
4324                         {
4325                            int c;
4326                            int lastC = 0;
4327                            BufferLocation before = { line, y, 0 }, after = { line, y, 0 };
4328
4329                            for(c=0; c<line.count && lastC < this.tabSize; c++, lastC++)
4330                            {
4331                               if(line.buffer[c] == '\t')
4332                               {
4333                                  lastC++;
4334                                  break;
4335                               }
4336                               else if(line.buffer[c] != ' ')
4337                                  break;
4338                            }
4339                            after.x = lastC;
4340
4341                            NotifyCharsDeleted(master, this, &before, &after, this.pasteOperation);
4342                            if(lastC)
4343                            {
4344                               int len = GetText(null, line, y, 0, line, y, lastC, false, false);
4345                               char * string = new char[len];
4346                               DelTextAction action { y1 = y, x1 = 0, y2 = y, x2 = lastC, string = string, placeAfter = true };
4347                               GetText(string, line, y, 0, line, y, lastC, false, false);
4348                               Record(action);
4349                            }
4350                            memmove(line.buffer,line.buffer+lastC,line.size-lastC);
4351                            if(!line.AdjustBuffer(line.count-lastC)) 
4352                               break;
4353                            line.count-=lastC;
4354                            DirtyLine(y);
4355                         }
4356
4357                         if(line == lastLine) break;
4358                      }
4359                   }
4360                   else
4361                   {
4362                      for(line = firstLine; line; line = line.next, y++)
4363                      {
4364                         if(line.count)
4365                         {
4366                            if(line != lastLine || x)
4367                            {
4368                               if(style.useTab)
4369                               {
4370                                  if(line.count + 1 <= this.maxLineSize)
4371                                  {
4372                                     BufferLocation before = { line, y, 0 }, after = { line, y, 1 };
4373
4374                                     if(!line.AdjustBuffer(line.count+1)) 
4375                                        break;
4376
4377                                     NotifyCharsAdded(master, this, &before, &after, this.pasteOperation);
4378                                     {
4379                                        AddCharAction action { ch = '\t', x = 0, y = y };
4380                                        Record(action);
4381                                     }
4382
4383                                     memmove(line.buffer+1,line.buffer,line.size-1);
4384                                     line.count++;
4385                                     line.buffer[0] = '\t';
4386                                  }
4387                               }
4388                               else
4389                               {
4390                                  if(line.count + this.tabSize <= this.maxLineSize)
4391                                  {
4392                                     int c;
4393                                     BufferLocation before = { line, y, 0 }, after = { line, y, this.tabSize };
4394                                     NotifyCharsAdded(master, this, &before, &after, this.pasteOperation);
4395
4396                                     if(!line.AdjustBuffer(line.count+this.tabSize)) 
4397                                        break;
4398
4399                                     {
4400                                        char * string = new char[this.tabSize + 1];
4401                                        memset(string, ' ', this.tabSize);
4402                                        string[this.tabSize] = '\0';
4403                                        Record(AddTextAction { string = string, x1 = 0, y1 = y, x2 = this.tabSize, y2 = y });
4404                                     }
4405
4406                                     memmove(line.buffer+this.tabSize,line.buffer,line.size-(this.tabSize));
4407                                     line.count+=this.tabSize;
4408                                     for(c=0; c<this.tabSize; c++)
4409                                        line.buffer[c] = ' ';
4410                                  }
4411                               }
4412                               DirtyLine(y);
4413                            }
4414                         }
4415                         if(line == lastLine) break;
4416                      }
4417                   }
4418                }
4419                else
4420                {
4421                   if(style.useTab)
4422                   {
4423                      // Insert character
4424                      AddCh('\t');
4425                   }
4426                   else
4427                   {
4428                      int start, c;
4429                      char * addString = new char[this.tabSize + 1];
4430                      int len = 0;
4431                      if(!(style.freeCaret))
4432                      {
4433                         this.x = Min(this.x, this.line.count);
4434                         ComputeColumn();
4435                      }
4436                      // Insert spaces
4437                      start = this.x;
4438                      for(c=start; ((c == start) || ((c) % this.tabSize)); c++)
4439                      {
4440                         addString[len++] = ' ';
4441                      }
4442                      addString[len] = 0;
4443                      AddS(addString);
4444                      delete addString;
4445                   }
4446                }
4447                Modified();
4448                SetViewToCursor(true);
4449                return false;
4450             }
4451             break;
4452          case pageDown:
4453             if(key.ctrl)
4454             {
4455                if(!(style.hScroll) || hasHorzScroll) break;
4456                if(this.viewX < this.maxLength)
4457                {
4458                   //this.viewX+=this.space.w*this.tabSize;
4459                   //DirtyAll();
4460                   SetScrollPosition((this.viewX + this.space.w*this.tabSize), this.viewY * this.space.h);
4461                }
4462             }
4463             else
4464             {
4465                PageDown();
4466                DirtyAll();
4467                if(!shift) Deselect();
4468                SetCursorToViewX();
4469                SetCursorToViewY();
4470             }
4471             return false;
4472             // break;
4473          case pageUp:
4474             if(key.ctrl)
4475             {
4476                if(!(style.hScroll) || hasHorzScroll) break;
4477                if(this.viewX > 0)
4478                {
4479                   //this.viewX-=this.space.w*this.tabSize;
4480                   //this.viewX = Max(this.viewX,0);
4481                   //DirtyAll();
4482                   SetScrollPosition((this.viewX-this.space.w*this.tabSize), this.viewY * this.space.h);
4483                   // SetCursorToView();
4484                }
4485             }
4486             else
4487             {
4488                PageUp();
4489                DirtyAll();
4490                if(!shift) Deselect();
4491                SetCursorToViewX();
4492                SetCursorToViewY();
4493             }
4494             // break;
4495             return false;
4496          case insert:
4497             if(key.ctrl)
4498             {
4499                Copy();
4500                return false;
4501             }
4502             else if(key.shift)
4503             {
4504                if(!(style.readOnly))
4505                   Paste();
4506                return false;
4507             }
4508             else
4509             {
4510                this.overwrite ^= 1;
4511                UpdateCaretPosition(true);
4512                if(this.overwrite)
4513                   SetCaret(0,0,0);
4514                DirtyLine(this.y);
4515                UpdateDirty();
4516                NotifyOvrToggle(master, this, this.overwrite);
4517             }
4518             break;
4519          case hotKey: 
4520             break;
4521          default:
4522             
4523             switch(key)
4524             {
4525                case ctrlA:
4526                   // Select All
4527                   if(style.noSelect) break;
4528
4529                   {
4530                      //Save current view position
4531                      int tempX = this.viewX;
4532                      int tempY = this.viewY;
4533                      
4534                      this.selX = 0;
4535                      this.selY = 0;
4536                      this.selLine = this.lines.first;
4537                      this.y = this.lineCount-1;
4538                      this.line = this.lines.last;
4539                      this.x = this.line.count;
4540                      ComputeColumn();
4541                      DirtyAll();
4542                      SetViewToCursor(true);
4543                      
4544                      //Restore previous view position
4545                      SetScrollPosition(tempX, tempY * this.space.h);
4546                      
4547                      UpdateDirty();
4548                      SetSelectCursor();
4549                      SelectionEnables();
4550                   }
4551                   // TOCHECK: Was there any good reason why we weren't returning false here?
4552                   return false; // break;
4553                case ctrlC:
4554                   Copy();
4555                   return false;
4556                case ctrlV:
4557                   if(!(style.readOnly))
4558                   {
4559                      Paste();
4560                      return false;
4561                   }
4562                   break;
4563                case ctrlX:
4564                {
4565                   if(style.readOnly) break;
4566                   Cut();
4567                   return false;
4568                }
4569                case ctrlL:
4570                {
4571                   if(style.readOnly) break;
4572                   ClearLine();
4573                   return false;
4574                }
4575                case ctrlZ:
4576                   if(style.readOnly) break;
4577                   Undo();
4578                   return false;
4579                case ctrlY:
4580                   if(style.readOnly) break;
4581                   Redo();
4582                   return false;
4583                default:
4584                   if(style.readOnly) break;
4585                   if(key.shift && key.code == rightBracket)
4586                   {
4587                      //Only indent back if you are exactly at one tab.
4588                      {
4589                         bool whitespace = true;
4590                         int i;
4591                         char * newline;
4592                         int putsize;
4593                         
4594                         int indentwidth;
4595                         EditLine line = this.line;
4596                         
4597                         //Only remove one tab if there is nothing else on the line.
4598                         for(i = 0; i < this.line.count; i++)
4599                         {
4600                            if(this.line.buffer[i] != ' ' && this.line.buffer[i] != '\t')
4601                            {
4602                               PutCh(ch);
4603                               return false;
4604                            }
4605                         }
4606                         
4607                         //Find opening {
4608                         i = 1;
4609                         while(i && line)
4610                         {
4611                            int pos;
4612                            
4613                            indentwidth = 0;
4614                            for(pos = line.count - 1; pos >= 0; pos--)
4615                            {
4616                               char c = line.buffer[pos];
4617                               if(c == ' ')
4618                                  indentwidth++;
4619                               else if(c == '\t')
4620                                  indentwidth += this.tabSize;
4621                               else
4622                                  //Counting backwards, so when you find a character, indentation is reset
4623                                  indentwidth = 0;
4624                               if(i && c == '}')
4625                                  i++;
4626                               if(i && c == '{')
4627                                  i--;
4628                            }
4629                            line = line.prev;
4630                         }
4631                         
4632                         //Place the } to get an undo:
4633                         PutCh(ch);
4634                         
4635                         this.x = this.line.count;
4636                         this.selX = 0;
4637                         
4638                         if(!style.useTab)
4639                            putsize = indentwidth;
4640                         else
4641                            putsize = indentwidth / this.tabSize + indentwidth % this.tabSize;
4642                         
4643                         newline = new char[putsize+2];
4644                         newline[putsize] = '}';
4645                         newline[putsize+1] = '\0';
4646                         
4647                         i = 0;
4648                         if(style.useTab)
4649                            for(; i < indentwidth / this.tabSize; i++)
4650                               newline[i] = '\t';
4651                         for(;i < putsize; i++)
4652                            newline[i] = ' ';
4653                         
4654                         AddS(newline);
4655                         
4656                         delete newline;
4657                      }
4658                      return false;
4659                   } else if(!key.ctrl && !key.alt && ch != 128 && ch >= 32)
4660                   {
4661                      PutCh(ch);
4662                      return false;
4663                   }
4664                   break;
4665             }
4666             break;
4667       }
4668       return true;
4669    }
4670
4671    void OnHScroll(ScrollBarAction action, int position, Key key)
4672    {
4673 #ifdef _DEBUG
4674       //PrintLn("OnHScroll: ", action, ", pos = ", position, ", key = ", key);
4675 #endif
4676       this.viewX = position;
4677       if(action != setRange)
4678       {
4679          if(!this.mouseMove && style.cursorFollowsView)
4680             SetCursorToViewX();
4681       }
4682       DirtyAll();
4683       UpdateDirty();
4684    }
4685
4686    void OnVScroll(ScrollBarAction action, int position, Key key)
4687    {
4688       int oldViewY = this.viewY;
4689
4690 #ifdef _DEBUG
4691       //PrintLn("OnVScroll: ", action, ", pos = ", position, ", key = ", key);
4692 #endif
4693       position /= this.space.h;
4694
4695       if(position < this.viewY)
4696       {
4697          for(; position < this.viewY && this.viewLine.prev; this.viewLine = this.viewLine.prev, this.viewY--);
4698          style.recomputeSyntax = true;
4699       }
4700       else if(position > this.viewY)
4701       {
4702          EditLine oldViewLine = viewLine;
4703          for(; position > this.viewY && this.viewLine.next; this.viewLine = this.viewLine.next, this.viewY++);
4704          FigureStartSyntaxStates(oldViewLine, false);
4705       }
4706       
4707       if(action != setRange)
4708       {
4709          if(!this.mouseMove && style.cursorFollowsView && !SelSize()) SetCursorToViewY();
4710       }
4711
4712       if(this.x != this.selX || this.y != this.selY)
4713          DirtyLine(this.y);
4714
4715       {
4716          Scroll(0, (this.viewY - oldViewY) * this.space.h);
4717
4718          /*
4719          int numLines = clientSize.h / this.space.h;
4720          int y;
4721          if(Abs(this.viewY - oldViewY) < numLines)
4722             
4723          
4724          if(this.viewY > oldViewY)
4725          {
4726             for(y = oldViewY; y <this.viewY; y++)
4727                DirtyLine(y + numLines);
4728          }
4729          else
4730          {
4731             for(y = this.viewY; y <oldViewY; y++)
4732                DirtyLine(y + numLines);
4733          }*/
4734       }
4735       //DirtyAll();
4736
4737       // Fix dirt of stuff before first line...
4738       if(this.viewY - oldViewY > 0)
4739       {
4740          Box box { 0,0, clientSize.w-1, YOFFSET-1 };
4741          Update(box);
4742       }
4743       
4744       UpdateDirty();
4745    }
4746
4747    bool _AddCh(unichar ch, int * addedSpacesPtr, int * addedTabsPtr)
4748    {
4749       EditLine line;
4750       int length, endX;
4751       bool result;
4752       ReplaceTextAction replaceAction = null;
4753       AddCharAction addCharAction = null;
4754       int addedSpaces = 0, addedTabs = 0;
4755
4756       if(ch == '\r') return true;
4757       if(style.stuckCaret /*|EES_READONLY)*/ ) 
4758          GoToEnd(true);
4759    
4760       if(ch == '\n' && !(style.multiLine) && this.line) return false;
4761       
4762       if(!undoBuffer.dontRecord)
4763       {
4764          if(selX != x || selY != y)
4765          {
4766             char buffer[5];
4767             char * newString;
4768             char * oldString;
4769             int len = GetText(null, selLine, selY, selX, this.line, y, x, false, false);
4770             oldString = new char[len];
4771             UTF32toUTF8Len(&ch, 1, buffer, 4);
4772             newString = CopyString(buffer);
4773             GetText(oldString, selLine, selY, selX, this.line, y, x, false, false);
4774
4775             replaceAction = ReplaceTextAction { newString = newString, oldString = oldString, placeAfter = false };
4776             if(selY < y || (selY == y && selX < x))
4777             {
4778                replaceAction.x1 = selX;
4779                replaceAction.y1 = selY;
4780                replaceAction.x2 = x;
4781                replaceAction.y2 = y;
4782             }
4783             else
4784             {
4785                replaceAction.x1 = x;
4786                replaceAction.y1 = y;
4787                replaceAction.x2 = selX;
4788                replaceAction.y2 = selY;
4789             }
4790             Record(replaceAction);
4791             undoBuffer.dontRecord++;
4792          }
4793          else
4794          {
4795             addCharAction = AddCharAction { y = y, x = x, ch = ch };
4796             Record(addCharAction);
4797          }
4798       }
4799
4800       if(ch == '\n')
4801       {
4802          DelSel(&addedSpaces);
4803          if(this.lineCount+1 > this.maxLines)
4804          {
4805             if(style.autoEmpty)
4806                Emptyline(this.lines.first,0);
4807             else
4808                return false;
4809          }
4810          if(!(style.vScroll)) 
4811          {
4812             // Make sure it fits, but we need a default line is this.font is too big for window
4813             if(this.space.h * (this.lineCount+1) > clientSize.h && this.line)
4814                return false;
4815          }
4816          if((this.y >= 0) && this.y < this.viewY)
4817          {
4818             this.viewY++;
4819             DirtyAll();
4820          }
4821          line = EditLine { };
4822          if(!line)
4823             return false;
4824          lines.Insert(this.line, line);
4825          line.editBox = this;
4826          line.buffer = null;
4827          line.count = 0;
4828          line.size = 0;
4829          length = 0;
4830
4831          // If we're displacing the lines from a current line ...
4832          if(this.line)
4833          {
4834             if(this.line.buffer)
4835             {
4836                endX = this.x;
4837                if(this.line.count < endX) endX = this.line.count;
4838                length = this.line.count - endX;
4839             }
4840          }
4841          if(!line.AdjustBuffer(length)) 
4842             return false;
4843          if(this.line)
4844          {
4845             if(this.line.buffer)
4846             {
4847                CopyBytes(line.buffer,this.line.buffer+endX, length+1);
4848 #ifdef _DEBUG
4849       if(endX > 4000 || endX < 0)
4850          printf("Warning");
4851 #endif
4852                this.line.count = endX;
4853                this.line.buffer[this.line.count] = '\0';
4854                this.line.AdjustBuffer(this.line.count);
4855                ComputeLength(this.line);
4856             }
4857          }
4858          {
4859             BufferLocation before = { this.line, this.y, this.x }, after;
4860
4861             this.line = line;
4862             this.x = 0;
4863             this.col = 0;
4864
4865             ComputeLength(this.line);
4866
4867 #ifdef _DEBUG
4868       if(length > 4000 || length < 0)
4869          printf("Warning");
4870 #endif
4871             line.count = length;
4872             DirtyEnd(this.y);
4873             this.y++;
4874             this.lineCount++;
4875             line.buffer[line.count] = '\0';
4876             result = true;
4877
4878             after.line = this.line, after.y = this.y, after.x = this.x;
4879
4880             NotifyCharsAdded(master, this, &before, &after, this.pasteOperation);
4881          }
4882       }
4883       else
4884       {
4885          char string[5];
4886          int count = UTF32toUTF8Len(&ch, 1, string, 5);
4887          DelSel(&addedSpaces);
4888          result = AddToLine(string, count, false, addedSpaces ? null : &addedSpaces, &addedTabs);
4889          if(addedSpacesPtr) *addedSpacesPtr = addedSpaces;
4890          if(addedTabsPtr) *addedTabsPtr = addedTabs;
4891       }
4892       this.selX = this.x;
4893       this.selY = this.y;
4894       this.selLine = this.line;
4895
4896       if(replaceAction)
4897       {
4898          replaceAction.x3 = x;
4899          replaceAction.y3 = y;
4900          replaceAction.addedSpaces = addedSpaces;
4901          replaceAction.addedTabs = addedTabs;
4902          undoBuffer.dontRecord--;
4903       }
4904       if(addCharAction)
4905       {
4906          addCharAction.addedSpaces = addedSpaces;
4907          addCharAction.addedTabs = addedTabs;
4908       }
4909       return result;
4910    }
4911
4912 public:
4913
4914    /****************************************************************************
4915                                  EDIT BOX METHODS
4916    ****************************************************************************/
4917
4918
4919    bool AddCh(unichar ch)
4920    {
4921       return _AddCh(ch, null, null);
4922    }
4923
4924    void Modified()
4925    {
4926       this.modified = true;
4927       NotifyUpdate(master, this);
4928       modifiedDocument = true;
4929    }
4930
4931    void Delete(EditLine line1, int y1, int x1, EditLine line2, int y2, int x2)
4932    {
4933       Deselect();
4934       DelCh(line1, y1, x1, line2, y2, x2, false);
4935       SetViewToCursor(true);
4936       UpdateDirty();
4937       Modified();
4938    }
4939
4940    void Undo()
4941    {
4942       undoBuffer.Undo();
4943       itemEditUndo.disabled = undoBuffer.curAction == 0;
4944       itemEditRedo.disabled = undoBuffer.curAction == undoBuffer.count;
4945       if(savedAction == undoBuffer.curAction)
4946       {
4947          modifiedDocument = false;
4948          SetModified(false);
4949          NotifyUnsetModified(master, this);
4950       }
4951    }
4952
4953    void Redo()
4954    {
4955       undoBuffer.Redo();
4956       itemEditUndo.disabled = undoBuffer.curAction == 0;
4957       itemEditRedo.disabled = undoBuffer.curAction == undoBuffer.count;
4958       if(savedAction == undoBuffer.curAction)
4959       {
4960          modifiedDocument = false;
4961          SetModified(false);
4962          NotifyUnsetModified(master, this);
4963       }
4964    }
4965
4966    void Record(UndoAction action)
4967    {
4968       if(!undoBuffer.dontRecord)
4969       {
4970          undoBuffer.Record(action);
4971          itemEditUndo.disabled = undoBuffer.curAction == 0;
4972          itemEditRedo.disabled = undoBuffer.curAction == undoBuffer.count;
4973       }
4974    }
4975
4976    void SelectAll()
4977    {
4978       Select(
4979          this.lines.first, 0,0,
4980          this.lines.last, this.lines.count-1, strlen(((EditLine)this.lines.last).buffer));
4981    }
4982
4983    void Select(EditLine line1, int y1, int x1, EditLine line2, int y2, int x2)
4984    {
4985       SelDirty();
4986       this.selX = x1;
4987       this.selY = y1;
4988       this.selLine = line1 ? (EditLine)line1 : this.lines.first;
4989       this.x = line2 ? x2 : ((EditLine)this.lines.last).count;
4990       this.y = line2 ? y2 : (this.lineCount-1);
4991       this.line = line2 ? (EditLine)line2 : this.lines.last;
4992       ComputeColumn();
4993       SelDirty();
4994       SetViewToCursor(true);
4995       UpdateDirty();
4996    }
4997
4998    // TODO: Fix this vs modifiedDocument window property
4999    void SetModified(bool flag)
5000    {
5001       if(this)
5002       {
5003          this.modified = false;
5004          if(flag && !NotifyModified(master, this))
5005             this.modified = true;
5006       }
5007    }
5008
5009    // BASIC OUTPUT
5010    bool AddS(char * string)
5011    {
5012       if(this)
5013       {
5014          bool ret = true;
5015          char * line;
5016          int c, count;
5017          int addedSpaces = 0, addedTabs = 0;
5018          AddTextAction action = null;
5019          ReplaceTextAction replaceAction = null;
5020
5021          this.pasteOperation = true;
5022
5023          if(style.stuckCaret /*|EES_READONLY)*/ ) 
5024             GoToEnd(true);
5025
5026          if(!undoBuffer.dontRecord)
5027          {
5028             char * placeString = CopyString(string);
5029             if(!style.multiLine)
5030             {
5031                int i;
5032                char ch; 
5033                for(i = 0; (ch = placeString[i]); i++)
5034                   if(ch == '\n')
5035                   {
5036                      placeString[i] = '\0';
5037                      placeString = renew placeString byte[i+1];
5038                      break;
5039                   }
5040             }
5041             
5042             if(selX != x || selY != y)
5043             {
5044                char * newString;
5045                char * oldString;
5046                int len = GetText(null, selLine, selY, selX, this.line, y, x, false, false);
5047                oldString = new char[len];
5048                newString = placeString;
5049                GetText(oldString, selLine, selY, selX, this.line, y, x, false, false);
5050
5051                replaceAction = ReplaceTextAction { newString = newString, oldString = oldString, placeAfter = false };
5052                if(selY < y || (selY == y && selX < x))
5053                {
5054                   replaceAction.x1 = selX;
5055                   replaceAction.y1 = selY;
5056                   replaceAction.x2 = x;
5057                   replaceAction.y2 = y;
5058                }
5059                else
5060                {
5061                   replaceAction.x1 = x;
5062                   replaceAction.y1 = y;
5063                   replaceAction.x2 = selX;
5064                   replaceAction.y2 = selY;
5065                }
5066                Record(replaceAction);
5067             }
5068             else if(string[0])
5069             {
5070                if(string[0] == '\n')
5071                   action = AddTextAction { y1 = y, x1 = Min(this.line.count, x), string = placeString };
5072                else
5073                   action = AddTextAction { y1 = y, x1 = x, string = placeString };
5074                
5075                Record(action);
5076             }
5077             else
5078                delete placeString;
5079          }
5080
5081          undoBuffer.dontRecord++;
5082          DelSel(&addedSpaces);
5083
5084          count = 0;
5085          line = string;
5086          for(c = 0; string[c]; c++)
5087          {
5088             if(string[c] == '\n' || string[c] == '\r')
5089             {
5090                if(!AddToLine(line,count, true, addedSpaces ? null : &addedSpaces, addedTabs ? null : &addedTabs))
5091                {
5092                   ret = false;
5093                   break;
5094                }
5095                if(string[c] == '\n')
5096                {
5097                   if(!AddCh('\n'))
5098                   {
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(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 lf)
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(lf) 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 };