ecere: Fixes for proper Native Decorations support on Windows
[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       // We really shouldn't be pasting here, Unix middle button paste is for the selection (not the clipboard), good for the terminal
3017       // Middle button already serves as a dragger as well.
3018       // Paste();
3019       return true;
3020    }
3021
3022    bool OnRightButtonDown(int x, int y, Modifiers mods)
3023    {
3024       this.rightButtonDown = true;
3025       // Copy();
3026       return true;
3027    }
3028
3029    bool OnRightButtonUp(int x, int y, Modifiers mods)
3030    {
3031       // Context Menu
3032       if(!parent.inactive && rightButtonDown)
3033       {
3034          PopupMenu popup;
3035          Menu contextMenu { };
3036
3037          MenuItem { contextMenu, "Cut\tCtrl+X", t, NotifySelect = itemEditCut.NotifySelect, disabled = !selection || style.readOnly };
3038          MenuItem { contextMenu, "Copy\tCtrl+C", c, NotifySelect = itemEditCopy.NotifySelect, disabled = !selection };
3039          MenuItem { contextMenu, "Paste\tCtrl+V", p, NotifySelect = itemEditPaste.NotifySelect, disabled = style.readOnly };
3040          MenuItem { contextMenu, "Delete\tDel", d, NotifySelect = itemEditDelete.NotifySelect, disabled = !selection || style.readOnly };
3041          MenuDivider { contextMenu };
3042          MenuItem { contextMenu, "Select All\tCtrl+A", a, NotifySelect = itemEditSelectAll.NotifySelect };
3043
3044          popup = PopupMenu { master = this, menu = contextMenu,
3045    /*
3046             nonClient = true, interim = false, parent = parent, 
3047             position = { x + clientStart.x + parent.clientStart.x + position.x, y + cientStart.y + parent.sy + clientStart.y + position.y };
3048    */
3049             position = { x + clientStart.x + absPosition.x - guiApp.desktop.position.x, y + clientStart.y + absPosition.y - guiApp.desktop.position.y }
3050          };
3051          popup.Create();
3052       }
3053       rightButtonDown = false;
3054       return true;
3055    }
3056
3057    bool OnLeftButtonDown(int mx, int my, Modifiers mods)
3058    {
3059       int x,y;
3060       EditLine line;
3061
3062       if(style.noSelect) return true;
3063
3064       if(!mods.isActivate)
3065       {
3066          Capture();
3067          mouseSelect = true;
3068       }
3069
3070       mouseX = mx - XOFFSET;
3071       mouseY = my;
3072
3073       FindMouse(mouseX, mouseY, &x, &y, &line, true);
3074 #ifdef _DEBUG
3075       //PrintLn("OnLeftButtonDown: ", x, ", ", y);
3076 #endif
3077       if(!style.readOnly)
3078       {
3079          if(wordSelect)
3080             mouseMove = false;
3081          else if(IsMouseOnSelection() && !mods.isActivate)
3082          {
3083             DirtyLine(this.y);
3084             mouseMove = true;
3085             dropX = x;
3086             dropY = y;
3087             dropLine = line;
3088          }
3089       }
3090
3091       if(!mouseMove && !wordSelect && (!mods.isActivate || style.multiLine))
3092       {
3093          if(mods.shift && !mods.isActivate)
3094          {
3095             this.x = x;
3096             this.y = y;
3097             this.line = line;
3098             DirtyAll();
3099          }
3100          else
3101          {
3102             SelDirty();
3103             DirtyLine(this.y);
3104             this.x = x;
3105             this.y = y;
3106             this.line = line;
3107             DirtyLine(this.y);
3108             this.selLine = this.line;
3109             this.selX = this.x;
3110             this.selY = this.y;
3111             //Deselect();
3112          }
3113          ComputeColumn();
3114       }
3115       
3116       UpdateDirty();
3117       UpdateCaretPosition(true);
3118       return true;
3119    }
3120
3121    bool OnLeftButtonUp(int x, int y, Modifiers mods)
3122    {
3123       timer.Stop();
3124
3125       mouseSelect = false;
3126       wordSelect = false;
3127       
3128       x -= XOFFSET;
3129       
3130       ReleaseCapture();
3131       if(!style.readOnly)
3132       {
3133          if(mouseMove)
3134          {
3135             EditLine line;
3136             FindMouse(mouseX, mouseY, &x, &y, &line, true);
3137 #ifdef _DEBUG
3138             //PrintLn("MouseMove: ", x, ", ", y);
3139 #endif
3140             dropX = x;
3141             dropY = y;
3142             dropLine = line;
3143
3144             mouseMove = IsMouseOnSelection();
3145
3146             if(!mouseMove)
3147             {
3148                int size = SelSize();
3149                if(size)
3150                {
3151                   char * text = new char[size+1];
3152                   if(text)
3153                   {
3154                      int moveX = 0;
3155                      GetSel(text, false);
3156
3157                      if(Max(selY, this.y) == dropY)
3158                      {
3159                         if(this.x > selX)
3160                         {
3161                            if(this.dropX > this.selX)
3162                               moveX = this.x - this.selX;
3163                         }
3164                         else
3165                         {
3166                            if(this.dropX > this.x)
3167                               moveX = this.selX - this.x;
3168                         }
3169                      }
3170                      DelSel(null);
3171                      this.dropX -= moveX;
3172                      this.selX = this.x = this.dropX;
3173                      this.selY = this.y = this.dropY;
3174                      this.selLine = this.line = this.dropLine;
3175                      AddS(text);
3176                      SetViewToCursor(true);
3177                      delete text;
3178                      Modified();
3179 #ifdef _DEBUG
3180                    /*  {
3181                         byte * c = ((EditBox)this).multiLineContents, * c2;
3182                         int l1 = c ? strlen(c) : 0, l2;
3183                         Undo();
3184                         Redo();
3185                         c2 = ((EditBox)this).multiLineContents;
3186                         l2 = c2 ? strlen(c2) : 0;
3187                         if(l1 != l2 || (l1 && strcmp(c, c2)))
3188                         {
3189                            PrintLn("Fail!");
3190                         }
3191                         delete c;
3192                      }
3193                      */
3194 #endif
3195                   }
3196                }
3197             }
3198             else
3199             {
3200                SelDirty();
3201                DirtyLine(this.y);
3202                this.x = x;
3203                this.y = y;
3204                this.line = line;
3205                ComputeColumn();
3206                DirtyLine(this.y);
3207                Deselect();
3208                UpdateDirty();
3209             }
3210          }
3211          else
3212          {
3213             EditLine line;
3214             mouseX = x;
3215             mouseY = y;
3216
3217             FindMouse(mouseX, mouseY, &x, &y, &line, true);
3218 #ifdef _DEBUG
3219             //PrintLn("Dropped: ", x, ", ", y);
3220 #endif
3221             NotifyDropped(master, this, x, y);
3222          }
3223       }
3224       mouseMove = false;
3225       return true;
3226    }
3227
3228    bool OnMouseMove(int mx, int my, Modifiers mods)
3229    {
3230       int x,y;
3231       EditLine line;
3232       bool needScroll;
3233
3234       if(mods != -1 && mods.isSideEffect) 
3235       { 
3236          SetSelectCursor(); 
3237          return true; 
3238       }
3239       if(style.noSelect) return true;
3240       if(wordSelect) return true;
3241       mouseX = mx - XOFFSET;
3242       mouseY = my;
3243
3244       needScroll = FindMouse(this.mouseX, this.mouseY, &x, &y, &line, true);
3245
3246       if(this.mouseMove || this.mouseSelect)
3247       {
3248          if(!needScroll)
3249             timer.Stop();
3250          else 
3251          {
3252             if(needScroll)
3253                timer.Start();
3254             if(mods != -1 && 
3255                ((style.hScroll) || (style.vScroll)))
3256                return true;
3257          }
3258       }
3259
3260       if(this.mouseMove)
3261       {
3262          DirtyLine(this.dropY);
3263          this.dropX = x;
3264          this.dropY = y;
3265          DirtyLine(this.dropY);
3266          this.dropLine = line;
3267          SetViewToCursor(true);
3268 #ifdef _DEBUG
3269          //PrintLn("MouseMove: ", "dropX = ", x, ", dropY = ", y);
3270 #endif
3271       }
3272       else if(this.mouseSelect)
3273       {
3274          DirtyLine(this.selY);
3275          DirtyLine(this.y);
3276          this.x = x;
3277          this.y = y;
3278          ComputeColumn();
3279          DirtyLine(this.y);
3280          this.line = line;
3281          SetViewToCursor(true);
3282          UpdateDirty();
3283 #ifdef _DEBUG
3284          //PrintLn("MouseSelect: ", "x = ", x, ", y = ", y);
3285 #endif
3286       }
3287       SetSelectCursor();
3288       return true;
3289    }
3290
3291    bool OnLeftDoubleClick(int mx, int my, Modifiers mods)
3292    {
3293       int x,y;
3294       EditLine line;
3295 #ifdef _DEBUG
3296       //PrintLn("OnLeftDoubleClick: ", mx, ", ", my, ", mods = ", mods);
3297 #endif
3298       mx -= XOFFSET;
3299
3300       if(style.noSelect) return true;
3301       FindMouse(mx, my, &x, &y, &line, false);
3302       if(!NotifyDoubleClick(master, this, line, mods))
3303          return false;
3304       if(x < line.count)
3305       {
3306          int c;
3307          int start = -1;
3308          int numBytes;
3309          for(c = x; c >= 0; c--)
3310          {
3311             unichar ch;
3312             while(c > 0 && !UTF8_IS_FIRST(line.buffer[c])) c--;
3313             ch = UTF8_GET_CHAR(line.buffer + c, numBytes);
3314             if(!IS_ALUNDER(ch))
3315                break;
3316             start = c;
3317          }
3318          if(start != -1)
3319          {
3320             for(c = start; c<line.count; c += numBytes)
3321             {
3322                unichar ch = UTF8_GET_CHAR(line.buffer + c, numBytes);
3323                if(!IS_ALUNDER(ch))
3324                   break;
3325             }
3326             SelDirty();
3327             DirtyLine(this.y);
3328             this.y = y;
3329             DirtyLine(this.y);
3330             this.selX = start;
3331             this.x = c;
3332             ComputeColumn();
3333             this.line = this.selLine = line;
3334             this.wordSelect = (c != start);
3335             UpdateDirty();
3336          }
3337       }
3338       return true;
3339    }
3340
3341    /*
3342    bool OnPostCreate()
3343    {
3344       if(isDocument)
3345       {
3346          Menu fileMenu { menu, "File", F };
3347          saveDialog = fileDialog;
3348          MenuItem { fileMenu, "Save\tCtrl+S", S, CtrlS, NotifySelect = MenuFileSave };
3349          MenuItem { fileMenu, "Save As...", A, NotifySelect = MenuFileSaveAs };
3350       }
3351       return true;
3352    }
3353    */
3354
3355    void ComputeFont()
3356    {
3357       if(FontExtent)
3358       {
3359          FontExtent(display, font, " ", 1, (int *)&space.w, (int *)&space.h);
3360          FontExtent(display, font, "W", 1, (int *)&large.w, (int *)&large.h);
3361
3362          space.w = Max(space.w, 1);
3363          large.w = Max(large.w, 1);
3364          space.h = Max(space.h, 1);
3365          large.h = Max(large.h, 1);
3366
3367          {
3368             EditLine line;
3369             for(line = lines.first; line; line = line.next)
3370                ComputeLength(line);
3371             FindMaxLine();
3372          }
3373
3374          if(viewLine)
3375             SetViewToCursor(true);
3376
3377          FixScrollArea();
3378
3379          Update(null);
3380       }
3381    }
3382
3383    bool OnLoadGraphics()
3384    {
3385       FontExtent = Display::FontExtent;
3386       font = fontObject;
3387       ComputeFont();
3388       // UpdateCaretPosition(true);
3389       return true;
3390    }
3391
3392    void OnUnloadGraphics()
3393    {
3394       this.font = null;
3395    }
3396
3397    bool OnKeyHit(Key key, unichar ch)
3398    {
3399       bool shift = (key.shift) ? true : false;
3400 #ifdef _DEBUG
3401       //PrintLn("OnKeyHit: code = ", key.code, ", mods = ", key.modifiers, ", ch = ", (int)ch);
3402 #endif
3403       if(!ch && !key.alt && !key.ctrl)
3404       {
3405          key.code = (SmartKey)key.code;
3406       }
3407
3408       switch(key.code) //(ch || key.alt || key.ctrl) ? key.code : (Key)(SmartKey)key.code)
3409       {
3410          case backSpace:
3411             if(style.readOnly) break;
3412             if(style.stuckCaret) GoToEnd(true);
3413             if(!(style.freeCaret))
3414             {
3415                this.x = Min(this.x, this.line.count);
3416             }
3417             if(key.ctrl)
3418             {
3419                int y;
3420                bool done = false;
3421                EditLine line = this.line;
3422                int c;
3423                for(y = this.y; y>= 0; y--)
3424                {
3425                   c = (y == this.y) ? (this.x-1) : line.count-1;
3426
3427                   // Slow down when going on lines...
3428                   if(y != this.y) break;
3429
3430                   for(; c>=0; c--)
3431                   {
3432                      //if(this.line.buffer[c] != '\t' && this.line.buffer[c] != ' ')
3433                      if(IS_ALUNDER(line.buffer[c]))
3434                         break;
3435                   }
3436                   for(; c>=0; c--)
3437                   {
3438                      if(!IS_ALUNDER(line.buffer[c]))
3439                      {
3440                      //if(this.line.buffer[c] == '\t' || this.line.buffer[c] == ' ')
3441                         done = true;
3442                         break;
3443                      }
3444                   }
3445                   if(done)
3446                      break;
3447                   if(line.prev)
3448                      line = line.prev;
3449                   else
3450                      break;
3451                }
3452                //if(this.x != 0)
3453                {
3454                   DelCh(line,y,c+1,this.line,this.y,this.x, true);
3455                   this.x = this.selX = Min(c+1, line.count);
3456                   this.y = this.selY = y;
3457                   this.line = this.selLine = line;
3458                   SetViewToCursor(true);
3459                }
3460                ComputeColumn();
3461             }
3462             else
3463             {
3464                BackSpace();
3465             }
3466             return false;
3467             //break;
3468          case del:
3469             if(style.readOnly) break;
3470             if(style.stuckCaret) break;
3471
3472             if(shift)
3473             {
3474                Cut();
3475             }
3476             else
3477             {
3478                // Delete selection
3479                if(this.line != this.selLine || this.x != this.selX)
3480                {
3481                   DelSel(null);
3482                   SetViewToCursor(true);
3483                   Modified();
3484                }
3485                // Delete word
3486                else if(key.ctrl)
3487                {
3488                   if(this.x < this.line.count)
3489                   {
3490                      int i;
3491                      int length;
3492                      //Can't delete right away as that would change the line.count
3493                      for(i = this.x; i < this.line.count; i++)
3494                      {
3495                         if(!IS_ALUNDER(this.line.buffer[i]))
3496                            break;
3497                         DelCh(this.line, this.y, this.x, this.line, this.y, this.x+1, false);
3498                         i--;
3499                      }
3500                      
3501                      for(; i < this.line.count; i++)
3502                      {
3503                         //Delete trailing whitespace
3504                         if(IS_ALUNDER(this.line.buffer[i]))
3505                            break;
3506                         DelCh(this.line, this.y, this.x, this.line, this.y, this.x+1, false);
3507                         i--;
3508                      }
3509                      SetViewToCursor(true);
3510                      Modified();
3511                   }
3512                   else if(this.line.next)
3513                   {
3514                      DelCh(this.line, this.y, this.x, this.line.next, this.y+1, 0, false);
3515                      SetViewToCursor(true);
3516                      Modified();
3517                   }
3518                }
3519                else 
3520                {
3521                   if(!(style.freeCaret))
3522                   {
3523                      this.selX = this.x = Min(this.x, this.line.count);
3524                      ComputeColumn();
3525                   }
3526                   if(this.x < this.line.count)
3527                   {
3528                      DelCh(this.line, this.y, this.x, this.line, this.y, this.x+1, false);
3529                   }
3530                   else if(this.line.next)
3531                   {
3532                      DelCh(this.line, this.y, this.x, this.line.next, this.y+1, 0, false);
3533                   }
3534                   SetViewToCursor(true);
3535                   Modified();
3536                }
3537             }
3538             return false;
3539             //break;
3540          case enter:
3541          case keyPadEnter:
3542          {
3543             if(!key.alt && !key.ctrl)
3544             {
3545                int c;
3546                int position = 0;
3547                bool stuffAfter = false;
3548                char * addString;
3549                int len = 0;
3550
3551                if(style.stuckCaret) GoToEnd(true);
3552                if(style.readOnly) break;
3553                if(!(style.multiLine)) break;
3554
3555                for(c = 0; c<this.line.count && c<this.x; c++)
3556                {
3557                   if(this.line.buffer[c] == '\t')
3558                      position += this.tabSize - (position % this.tabSize);
3559                   else if(this.line.buffer[c] == ' ')
3560                      position++;
3561                   else
3562                      break;
3563                }
3564                if(!line.count)
3565                   position = x;
3566
3567                if(this.x < this.line.count)
3568                   stuffAfter = true;
3569                   
3570                //If last character is a { indent one tab
3571                if(this.line.buffer[this.x - 1] == '{')
3572                {
3573                   //Except if the next non space character is a }
3574                   bool indent = false;
3575                   int i;
3576                   for(i = this.x; i < this.line.size; i++)
3577                      if(this.line.buffer[i] != ' ' && this.line.buffer[i] != '\t')
3578                      {
3579                         if(this.line.buffer[i] != '}')
3580                            indent = true;
3581                         break;
3582                      }
3583                   if(indent)
3584                      position += this.tabSize;
3585                }
3586
3587                addString = new char[position + 2];
3588                addString[len++] = '\n';
3589                addString[len] = '\0';
3590
3591                if(stuffAfter || !style.freeCaret)
3592                {
3593                   for(c = 0; c<position; )
3594                   {
3595                      if(style.useTab && c + this.tabSize <= position)
3596                      {
3597                         addString[len++] = '\t';
3598                         c += this.tabSize;
3599                      }
3600                      else
3601                      {
3602                         addString[len++] = ' ';
3603                         c++;
3604                      }
3605                   }
3606                   addString[len] = '\0';
3607                }
3608                if(AddS(addString))
3609                {
3610                   if(!stuffAfter && style.freeCaret)
3611                   {
3612                      this.x = this.selX = position;
3613                      ComputeColumn();
3614                   }
3615                   FindMaxLine();
3616                   SetViewToCursor(true);
3617                   Modified();
3618                }
3619                delete addString;
3620                return false;
3621             }
3622             break;
3623          }
3624          case left:
3625          {
3626             if(style.stuckCaret) break;
3627             if(!(style.freeCaret))
3628             {
3629                this.x = Min(this.x, this.line.count);
3630                this.selX = Min(this.selX, this.selLine.count);
3631                ComputeColumn();
3632             }
3633             if(!shift) SelDirty();
3634             if(key.ctrl)
3635             {
3636                bool foundAlpha = false;
3637                bool found = false;
3638                int y = this.y;
3639                EditLine line, lastLine;
3640                int lastC, lastY;
3641
3642                for(line = this.line; (line && !found); line = line.prev, y--)
3643                {
3644                   int start;
3645                   int c;
3646
3647                   if(this.x == 0 && line != this.line)
3648                   {
3649                      foundAlpha = true;
3650                      lastC = line.count;
3651                      lastY = y;
3652                      lastLine = line;
3653                      break;
3654                   }
3655
3656                   if(line == this.line) start = this.x -1; else start = line.count-1;
3657                   start = Min(start, line.count-1);
3658
3659                   for(c = start; c >= 0;)
3660                   {
3661                      int numBytes;
3662                      unichar ch = UTF8_GET_CHAR(line.buffer + c, numBytes);
3663                      if(IS_ALUNDER(ch))
3664                      {
3665                         foundAlpha = true;
3666                         lastC = c;
3667                         lastY = y;
3668                         lastLine = line;
3669                      }
3670                      else
3671                      {
3672                         if(foundAlpha)
3673                         {
3674                            found = true;
3675                            break;
3676                         }
3677                      }
3678                      while(--c)
3679                      {
3680                         byte ch = line.buffer[c];
3681                         if(UTF8_IS_FIRST(ch)) break;
3682                      } 
3683
3684                   }
3685                   // No next word found, 
3686                   if(!found && ( this.x > 0 || (!line.count && this.x)))
3687                   {
3688                      foundAlpha = true;
3689                      lastC = 0;
3690                      lastY = y;
3691                      lastLine = line;
3692                      break;
3693                   }
3694                }
3695                if(foundAlpha)
3696                {
3697                   DirtyLine(this.y);
3698                   this.x = lastC;
3699                   this.y = lastY;
3700                   this.line = lastLine;
3701                   DirtyLine(this.y);
3702                   ComputeColumn();
3703                }
3704             }
3705             else
3706             {
3707                if(x > 0)
3708                {
3709                   if(x <= line.count)
3710                   {
3711                      byte * buffer = line.buffer;
3712                      while(--x)
3713                      {
3714                         byte ch = buffer[x];
3715                         if(UTF8_IS_FIRST(ch)) break;
3716                      } 
3717                   }
3718                   else
3719                      x--;
3720                   DirtyLine(y);
3721                }
3722                else if(line.prev)
3723                {
3724                   line = line.prev;
3725                   DirtyLine(y);
3726                   x = line.count;
3727                   y--;
3728                   DirtyLine(y);
3729                }
3730                ComputeColumn();
3731             }
3732             if(!shift) Deselect();
3733             SetViewToCursor(true);
3734             //break;
3735             return false;
3736          }
3737          case right:
3738          {
3739             if(style.stuckCaret) break;
3740             if(!(style.freeCaret))
3741             {
3742                this.x = Min(this.x, this.line.count);
3743                this.selX = Min(this.selX, this.selLine.count);
3744                ComputeColumn();
3745             }
3746             if(!shift) SelDirty();
3747             if(!shift && (this.x != this.selX || this.y != this.selY));
3748             else if(key.ctrl)
3749             {
3750                bool onAChar = false;
3751                if(this.selX != this.x || this.selY != this.y)
3752                   onAChar = true;
3753                if(this.x<this.line.count)
3754                   if(this.line.buffer[this.x] != '\t' && this.line.buffer[this.x] !=' ')
3755                      onAChar = true;
3756                if(key.shift && onAChar &&
3757                   ((this.y > this.selY)||((this.selY == this.y)&&(this.x >= this.selX))))
3758                {
3759                   bool foundAlpha = false;
3760                   bool found = false;
3761                   EditLine line, lastLine;
3762                   int y = this.y;
3763                   int lastC, lastY, lastNumBytes;
3764                   
3765                   for(line = this.line; (line && !found); line = line.next, y++)
3766                   {
3767                      int start = (line == this.line) ? this.x : 0;
3768                      int c;
3769                      int numBytes;
3770                      for(c = start; c < line.count; c += numBytes)
3771                      {
3772                         unichar ch = UTF8_GET_CHAR(line.buffer + c, numBytes);
3773                         if(IS_ALUNDER(ch))
3774                         {
3775                            foundAlpha = true;
3776                            lastC = c;
3777                            lastNumBytes = numBytes;
3778                            lastY = y;
3779                            lastLine = line;
3780                         }
3781                         else if(foundAlpha)
3782                         {
3783                            found = true;
3784                            break;
3785                         }
3786                      }
3787                      if(!found && (c != this.x || line != this.line))
3788                      {
3789                         found = true;
3790                         lastLine = line;
3791                         lastC = line.count;
3792                         lastNumBytes = 0;
3793                         lastY = y;
3794                         break;
3795                      }
3796                   }  
3797                   if(found)
3798                   {
3799                      DirtyLine(this.y);
3800                      this.x = lastC + lastNumBytes;
3801                      this.y = lastY;
3802                      this.line = lastLine;
3803                      DirtyLine(this.y);
3804                      ComputeColumn();
3805                   }
3806                }
3807                else
3808                {
3809                   bool foundAlpha = false;
3810                   bool found = false;
3811                   EditLine line;
3812                   int y = this.y;
3813
3814                   for(line = this.line; (line && !found); line = line.next, y++)
3815                   {
3816                      int start = (line == this.line) ? this.x : 0;
3817                      int c;
3818                      int numBytes;
3819                      for(c = start; c < line.count; c += numBytes)
3820                      {
3821                         unichar ch = UTF8_GET_CHAR(line.buffer + c, numBytes);
3822                         if(!IS_ALUNDER(ch))
3823                            foundAlpha = true;
3824                         else if(foundAlpha)
3825                         {
3826                            found = true;
3827                            DirtyLine(this.y);
3828                            this.x = c;
3829                            this.y = y;
3830                            this.line = line;
3831                            DirtyLine(this.y);
3832                            ComputeColumn();
3833                            break;
3834                         }
3835                      }
3836                      // No next word found, 
3837                      if(!found && (c != this.x || line != this.line))
3838                      {
3839                         found = true;
3840                         DirtyLine(this.y);
3841                         this.x = line.count;
3842                         this.y = y;
3843                         this.line = line;
3844                         DirtyLine(this.y);
3845                         ComputeColumn();
3846                      }
3847                      foundAlpha = true;
3848                   }
3849                }
3850             }
3851             else
3852             {
3853                if(x < line.count || (style.freeCaret && line.count < maxLineSize))
3854                {
3855                   if(x < line.count)
3856                   {
3857                      byte * buffer = line.buffer;
3858                      while(++x)
3859                      {
3860                         byte ch = buffer[x];
3861                         if(UTF8_IS_FIRST(ch)) break;
3862                      } 
3863                   }
3864                   else
3865                      x++;
3866                   ComputeColumn();
3867                   DirtyLine(y);
3868                }
3869                else
3870                {
3871                   if(line.next)
3872                   {
3873                      DirtyLine(y);
3874                      line = line.next;
3875                      y++;
3876                      x = 0;
3877                      col = 0;
3878                      DirtyLine(y);
3879                   }
3880                }
3881             }
3882             if(!shift) Deselect();
3883             SetViewToCursor(true);
3884             // break;
3885             return false;
3886          }
3887          case up:
3888             if(key.ctrl)
3889             {
3890                if(!style.vScroll || hasVertScroll) break;
3891                LineUp();
3892                return false;
3893             }
3894             else
3895             {
3896                if(style.stuckCaret) break;
3897                
3898                if(!shift) SelDirty();
3899                DirtyLine(this.y);
3900
3901                if(style.wrap)
3902                {
3903                }
3904                else if(line.prev)
3905                {
3906                   line = line.prev;
3907                   this.y--;
3908                   this.x = AdjustXPosition(line, caretX, true, null, MAXINT, 0);
3909                }
3910                
3911                DirtyLine(this.y);
3912                if(!shift) Deselect();
3913                ComputeColumn();
3914                SetViewToCursor(false);
3915
3916                /*
3917                if(caretY == this.y * space.h)
3918                {
3919                   if(line.prev)
3920                   {
3921                      line = line.prev;
3922                      this.y--;
3923                      if(!style.freeCaret)
3924                         this.x = Min(this.x, line.count);
3925                      caretY = MAXINT;
3926                   }
3927                   else
3928                      return false;
3929                }
3930
3931                {
3932                   int th = space.h;
3933                   int textPos = 0;
3934                   int sx = 0, sy = this.y * space.h;
3935                   char * text = line.text;
3936                   int maxW = clientSize.w - sx;
3937                   display.FontExtent(font, " ", 1, null, &th);
3938
3939                   do
3940                   {
3941                      int startPos = textPos;
3942                      int width = 0;
3943                      int x = 0;
3944                      bool lineComplete = false;
3945
3946                      if(!style.wrap && caretY == MAXINT)
3947                      {
3948                         caretY = sy + th;
3949                         //textPos = line.count;
3950                         //lineComplete = true;
3951                      }
3952
3953                      for(; (style.freeCaret || textPos < line.count) && !lineComplete;)
3954                      {
3955                         int w = 0;
3956                         int len;
3957                         char * nextSpace = (textPos < line.count) ? strchr(text + textPos, ' ') : (text + textPos);
3958
3959                         if(nextSpace)
3960                            len = (nextSpace - (text + textPos));
3961                         else
3962                            len = line.count - textPos;
3963                         
3964                         if(textPos < line.count)
3965                         {
3966                            display.FontExtent(font, text + textPos, len, &w, null);
3967                         }
3968                         if(nextSpace) { w += space.w; len++; }
3969
3970                         if(style.wrap && x + width + w > maxW && x > 0)
3971                         {
3972                            lineComplete = true;
3973                            break;
3974                         }
3975                         textPos += len;
3976                         width += w;
3977                         if(nextSpace)
3978                         {
3979                            x += width;
3980                            width = 0;
3981                         }
3982                         if((!style.freeCaret && textPos >= line.count) || (sy == caretY - th && caretX <= x + width + sx))
3983                         {
3984                            x += width;
3985                            this.x = textPos;
3986                            while(this.x > 0 && x + sx > caretX && this.x > startPos)
3987                            {
3988                               int len;
3989                               if(this.x > line.count)
3990                                  this.x--;
3991                               else
3992                                  while(this.x > 0 && !UTF8_IS_FIRST(text[--this.x]));
3993                               len = this.x - startPos;
3994                               display.FontExtent(font, text + startPos, len, &x, null);
3995                            }
3996
3997                            DirtyLine(this.y);
3998                            if(!shift) Deselect();
3999                            ComputeColumn();
4000                            SetViewToCursor(false);
4001                            return false;
4002                         }
4003                      }
4004                      if(sy == caretY - th || textPos >= line.count)
4005                      {
4006                         if(textPos >= line.count)
4007                         {
4008                            int c = textPos - 1;
4009                            while(c > 0 && text[c] == ' ') c--;
4010                            this.x = c + 1;
4011                         }
4012                         else
4013                            this.x = line.count;
4014
4015                         DirtyLine(this.y);
4016                         if(!shift) Deselect();
4017                         ComputeColumn();
4018                         SetViewToCursor(false);
4019                         return false;
4020                      }
4021                      sy += th;
4022                      sx = 0;
4023                   } while(textPos < line.count);
4024
4025                   DirtyLine(this.y);
4026                   if(!shift) Deselect();
4027                   ComputeColumn();
4028                   SetViewToCursor(false);
4029                   return false;
4030                }
4031                */
4032                
4033                // PREVIOUS CODE
4034                /*
4035                if(this.line.prev)
4036                {
4037                   int x = AdjustXPosition(this.line, this.line.prev, true, null, MAXINT, 0);
4038                   if(!shift) SelDirty();
4039                   this.line = this.line.prev;
4040                   DirtyLine(this.y);
4041                   this.y--;
4042
4043                   DirtyLine(this.y);
4044                   this.x = x;
4045                   if(!shift) Deselect();
4046
4047                   ComputeColumn();
4048
4049                   SetViewToCursor(false);
4050                }
4051                */
4052
4053             }
4054             // break;
4055             return style.multiLine ? false : true;
4056          case down:
4057             if(key.ctrl)
4058             {
4059                if(!style.vScroll || hasVertScroll)
4060                   break;
4061                LineDown();
4062                return false;
4063             }
4064             else
4065             {
4066                if(style.stuckCaret) break;
4067                {
4068                   int th = space.h;
4069                   int textPos = 0;
4070                   int sx = 0, sy = this.y * this.space.h;
4071                   int maxW = clientSize.w - sx;
4072                   char * text = line.buffer;
4073
4074                   if(!shift) SelDirty();
4075                   DirtyLine(this.y);
4076                   
4077                   if(style.wrap)
4078                   {
4079                      /*
4080                      if(AdjustXPosition(line, maxW, this.x, line.count, true, null, MAXINT, 0) <= line.count)
4081                      {
4082
4083                      }
4084                      */
4085                   }
4086                   else if(line.next)
4087                   {
4088                      line = line.next;
4089                      this.y++;
4090                      this.x = AdjustXPosition(line, caretX, true, null, MAXINT, 0);
4091                   }
4092
4093                   if(!shift) Deselect();
4094                   ComputeColumn();
4095                   if(this.selX != this.x || this.selY != this.y)
4096                      DirtyLine(this.y);
4097                   SetViewToCursor(false);
4098                   
4099                   /*
4100                   while(!textPos || (style.freeCaret || textPos<line.count))
4101                   {
4102                      int startPos = textPos;
4103                      int width = 0;
4104                      int x = 0;
4105                      bool lineComplete = false;
4106                      if(!style.wrap && sy <= caretY)
4107                      {
4108                         textPos = line.count;
4109                         lineComplete = true;
4110                      }
4111                      for(; (style.freeCaret || textPos<line.count) && !lineComplete;)
4112                      {
4113                         int w = 0;
4114                         int len;
4115                         char * nextSpace = (textPos < line.count) ? strchr(text + textPos, ' ') : (text + textPos);
4116
4117                         if(nextSpace)
4118                            len = (nextSpace - (text + textPos));
4119                         else
4120                            len = line.count - textPos;
4121                         
4122                         if(textPos < line.count)
4123                         {
4124                            display.FontExtent(font, text + textPos, len, &w, &th);
4125                         }
4126                         if(nextSpace) { w += space.w; len++; }
4127                         if(style.wrap && x + width + w > maxW && x > 0)
4128                         {
4129                            lineComplete = true;
4130                            break;
4131                         }
4132                         textPos += len;
4133                         width += w;
4134                         if(nextSpace)
4135                         {
4136                            x += width;
4137                            width = 0;
4138                         }
4139                         if(sy > caretY && ((!style.freeCaret && textPos >= line.count) || caretX <= x + width + sx))
4140                         {
4141                            this.x = textPos;
4142                            x += width;
4143                            while(this.x > 0 && x + sx > caretX && textPos > startPos)
4144                            {
4145                               int len;
4146                               if(this.x > line.count)
4147                                  this.x--;
4148                               else
4149                                  while(this.x > 0 && !UTF8_IS_FIRST(text[--this.x]));
4150                                  
4151                               len = this.x - startPos;
4152                               display.FontExtent(font, text + startPos, len, &x, null);
4153                            }
4154                            
4155                            if(!shift) Deselect();
4156                            ComputeColumn();
4157
4158                            SetViewToCursor(false);
4159                            return false;
4160                         }
4161                      }
4162                      if(sy > caretY)
4163                      {
4164                         this.x = line.count;
4165
4166                         DirtyLine(this.y);
4167                         if(!shift) Deselect();
4168                         ComputeColumn();
4169
4170                         SetViewToCursor(false);
4171                         return false;
4172                      } 
4173                      else if(textPos >= line.count && line.next)
4174                      {
4175                         startPos = 0;
4176                         textPos = 0;
4177                         line = line.next;
4178                         this.y++;
4179                         sy = this.y * this.space.h;
4180                         sx = 0; //textBlock.startX;
4181                         text = line.buffer;
4182                      }
4183                      else
4184                      {
4185                         sy += th;
4186                         sx = 0; //textBlock.startX;
4187                      }
4188                   }
4189                   */
4190                   /*
4191                   if(line.next)
4192                   {
4193                      line = line.next;
4194                      this.x = Min(this.x, line.count);
4195                      //int x = AdjustXPosition(this.line, this.line.next, true, null, MAXINT, 0);
4196                      this.y++;
4197                      if(!shift) Deselect();
4198                      ComputeColumn();
4199
4200                      if(this.selX != this.x || this.selY != this.y)
4201                         DirtyLine(this.y);
4202
4203                      SetViewToCursor(false);
4204                   }
4205                   */
4206                }
4207                /* PREVIOUS CODE
4208                if(this.line.next)
4209                {
4210                   int x = AdjustXPosition(this.line, this.line.next, true, null, MAXINT, 0);
4211                   if(!shift) SelDirty();
4212                   this.line = this.line.next;
4213                   DirtyLine(this.y);
4214                   this.y++;
4215                   this.x = x;
4216                   if(!shift) Deselect();
4217                   ComputeColumn();
4218
4219                   if(this.selX != this.x || this.selY != this.y)
4220                      DirtyLine(this.y);
4221
4222                   SetViewToCursor();
4223                }
4224                */
4225             }
4226             // break;
4227             return style.multiLine ? false : true;
4228          case home:
4229          {
4230             if(style.stuckCaret) break;
4231             if(!(style.freeCaret))
4232                this.selX = Min(this.selX, this.selLine.count);
4233
4234             if(!shift) SelDirty();
4235             if(key.ctrl)
4236             {
4237                this.line = this.lines.first;
4238                if(this.y != 0 || this.x != 0)
4239                   DirtyAll();
4240                this.y = 0;
4241                this.x = 0;
4242                this.col = 0;
4243             }
4244             else
4245             {
4246                if(style.smartHome)
4247                {
4248                   EditLine line = this.line;
4249                   int c;
4250                   for(c=0; line.buffer[c]; c++)
4251                      if(line.buffer[c] != ' ' && line.buffer[c] != '\t')
4252                         break;
4253                   if(c != 0 || this.x)
4254                      DirtyLine(this.y);
4255                   if(this.x != c) 
4256                      this.x = c;
4257                   else
4258                      this.x = 0;
4259                }
4260                else
4261                {
4262                   /*if(this.x != 0)
4263                      DirtyLine(this.y);*/
4264                   this.x = 0;
4265                }
4266                ComputeColumn();
4267             }
4268             if(!shift) Deselect();
4269             SetViewToCursor(true);
4270             //break;
4271             return false;
4272          }
4273          case end:
4274          {
4275             if(style.stuckCaret) break;
4276             if(!(style.freeCaret))
4277                this.selX = Min(this.selX, this.selLine.count);
4278
4279             if(!shift) SelDirty();
4280             if(key.ctrl)
4281             {
4282                GoToEnd(false);
4283             }
4284             else if(this.x != this.line.count)
4285             {
4286                this.x = this.line.count;
4287                //DirtyLine(this.y);
4288                ComputeColumn();
4289             }
4290             if(!shift) Deselect();
4291             SetViewToCursor(true);
4292             //break;
4293             return false;
4294          }
4295          case tab:
4296             if(style.tabKey && !key.ctrl)
4297             {
4298                if(this.selY != this.y && style.tabSel)
4299                {
4300                   EditLine firstLine, lastLine;
4301                   EditLine line;
4302                   int y, x;
4303
4304                   // Do multi line selection tabbing here
4305                   if(this.selY < this.y)
4306                   {
4307                      firstLine = this.selLine;
4308                      lastLine = this.line;
4309                      y = this.selY;
4310                      x = this.x;
4311                   }
4312                   else
4313                   {
4314                      // Selecting going up
4315                      firstLine = this.line;
4316                      lastLine = this.selLine;
4317                      y = this.y;
4318                      x = this.selX;
4319                   }
4320                   ComputeColumn();
4321                   if(shift)
4322                   {
4323                      for(line = firstLine; line; line = line.next, y++)
4324                      {
4325                         if(line != lastLine || x)
4326                         {
4327                            int c;
4328                            int lastC = 0;
4329                            BufferLocation before = { line, y, 0 }, after = { line, y, 0 };
4330
4331                            for(c=0; c<line.count && lastC < this.tabSize; c++, lastC++)
4332                            {
4333                               if(line.buffer[c] == '\t')
4334                               {
4335                                  lastC++;
4336                                  break;
4337                               }
4338                               else if(line.buffer[c] != ' ')
4339                                  break;
4340                            }
4341                            after.x = lastC;
4342
4343                            NotifyCharsDeleted(master, this, &before, &after, this.pasteOperation);
4344                            if(lastC)
4345                            {
4346                               int len = GetText(null, line, y, 0, line, y, lastC, false, false);
4347                               char * string = new char[len];
4348                               DelTextAction action { y1 = y, x1 = 0, y2 = y, x2 = lastC, string = string, placeAfter = true };
4349                               GetText(string, line, y, 0, line, y, lastC, false, false);
4350                               Record(action);
4351                            }
4352                            memmove(line.buffer,line.buffer+lastC,line.size-lastC);
4353                            if(!line.AdjustBuffer(line.count-lastC)) 
4354                               break;
4355                            line.count-=lastC;
4356                            DirtyLine(y);
4357                         }
4358
4359                         if(line == lastLine) break;
4360                      }
4361                   }
4362                   else
4363                   {
4364                      for(line = firstLine; line; line = line.next, y++)
4365                      {
4366                         if(line.count)
4367                         {
4368                            if(line != lastLine || x)
4369                            {
4370                               if(style.useTab)
4371                               {
4372                                  if(line.count + 1 <= this.maxLineSize)
4373                                  {
4374                                     BufferLocation before = { line, y, 0 }, after = { line, y, 1 };
4375
4376                                     if(!line.AdjustBuffer(line.count+1)) 
4377                                        break;
4378
4379                                     NotifyCharsAdded(master, this, &before, &after, this.pasteOperation);
4380                                     {
4381                                        AddCharAction action { ch = '\t', x = 0, y = y };
4382                                        Record(action);
4383                                     }
4384
4385                                     memmove(line.buffer+1,line.buffer,line.size-1);
4386                                     line.count++;
4387                                     line.buffer[0] = '\t';
4388                                  }
4389                               }
4390                               else
4391                               {
4392                                  if(line.count + this.tabSize <= this.maxLineSize)
4393                                  {
4394                                     int c;
4395                                     BufferLocation before = { line, y, 0 }, after = { line, y, this.tabSize };
4396                                     NotifyCharsAdded(master, this, &before, &after, this.pasteOperation);
4397
4398                                     if(!line.AdjustBuffer(line.count+this.tabSize)) 
4399                                        break;
4400
4401                                     {
4402                                        char * string = new char[this.tabSize + 1];
4403                                        memset(string, ' ', this.tabSize);
4404                                        string[this.tabSize] = '\0';
4405                                        Record(AddTextAction { string = string, x1 = 0, y1 = y, x2 = this.tabSize, y2 = y });
4406                                     }
4407
4408                                     memmove(line.buffer+this.tabSize,line.buffer,line.size-(this.tabSize));
4409                                     line.count+=this.tabSize;
4410                                     for(c=0; c<this.tabSize; c++)
4411                                        line.buffer[c] = ' ';
4412                                  }
4413                               }
4414                               DirtyLine(y);
4415                            }
4416                         }
4417                         if(line == lastLine) break;
4418                      }
4419                   }
4420                }
4421                else
4422                {
4423                   if(style.useTab)
4424                   {
4425                      // Insert character
4426                      AddCh('\t');
4427                   }
4428                   else
4429                   {
4430                      int start, c;
4431                      char * addString = new char[this.tabSize + 1];
4432                      int len = 0;
4433                      if(!(style.freeCaret))
4434                      {
4435                         this.x = Min(this.x, this.line.count);
4436                         ComputeColumn();
4437                      }
4438                      // Insert spaces
4439                      start = this.x;
4440                      for(c=start; ((c == start) || ((c) % this.tabSize)); c++)
4441                      {
4442                         addString[len++] = ' ';
4443                      }
4444                      addString[len] = 0;
4445                      AddS(addString);
4446                      delete addString;
4447                   }
4448                }
4449                Modified();
4450                SetViewToCursor(true);
4451                return false;
4452             }
4453             break;
4454          case pageDown:
4455             if(key.ctrl)
4456             {
4457                if(!(style.hScroll) || hasHorzScroll) break;
4458                if(this.viewX < this.maxLength)
4459                {
4460                   //this.viewX+=this.space.w*this.tabSize;
4461                   //DirtyAll();
4462                   SetScrollPosition((this.viewX + this.space.w*this.tabSize), this.viewY * this.space.h);
4463                }
4464             }
4465             else
4466             {
4467                PageDown();
4468                DirtyAll();
4469                if(!shift) Deselect();
4470                SetCursorToViewX();
4471                SetCursorToViewY();
4472             }
4473             return false;
4474             // break;
4475          case pageUp:
4476             if(key.ctrl)
4477             {
4478                if(!(style.hScroll) || hasHorzScroll) break;
4479                if(this.viewX > 0)
4480                {
4481                   //this.viewX-=this.space.w*this.tabSize;
4482                   //this.viewX = Max(this.viewX,0);
4483                   //DirtyAll();
4484                   SetScrollPosition((this.viewX-this.space.w*this.tabSize), this.viewY * this.space.h);
4485                   // SetCursorToView();
4486                }
4487             }
4488             else
4489             {
4490                PageUp();
4491                DirtyAll();
4492                if(!shift) Deselect();
4493                SetCursorToViewX();
4494                SetCursorToViewY();
4495             }
4496             // break;
4497             return false;
4498          case insert:
4499             if(key.ctrl)
4500             {
4501                Copy();
4502                return false;
4503             }
4504             else if(key.shift)
4505             {
4506                if(!(style.readOnly))
4507                   Paste();
4508                return false;
4509             }
4510             else
4511             {
4512                this.overwrite ^= 1;
4513                UpdateCaretPosition(true);
4514                if(this.overwrite)
4515                   SetCaret(0,0,0);
4516                DirtyLine(this.y);
4517                UpdateDirty();
4518                NotifyOvrToggle(master, this, this.overwrite);
4519             }
4520             break;
4521          case hotKey: 
4522             break;
4523          default:
4524             
4525             switch(key)
4526             {
4527                case ctrlA:
4528                   // Select All
4529                   if(style.noSelect) break;
4530
4531                   {
4532                      //Save current view position
4533                      int tempX = this.viewX;
4534                      int tempY = this.viewY;
4535                      
4536                      this.selX = 0;
4537                      this.selY = 0;
4538                      this.selLine = this.lines.first;
4539                      this.y = this.lineCount-1;
4540                      this.line = this.lines.last;
4541                      this.x = this.line.count;
4542                      ComputeColumn();
4543                      DirtyAll();
4544                      SetViewToCursor(true);
4545                      
4546                      //Restore previous view position
4547                      SetScrollPosition(tempX, tempY * this.space.h);
4548                      
4549                      UpdateDirty();
4550                      SetSelectCursor();
4551                      SelectionEnables();
4552                   }
4553                   // TOCHECK: Was there any good reason why we weren't returning false here?
4554                   return false; // break;
4555                case ctrlC:
4556                   Copy();
4557                   return false;
4558                case ctrlV:
4559                   if(!(style.readOnly))
4560                   {
4561                      Paste();
4562                      return false;
4563                   }
4564                   break;
4565                case ctrlX:
4566                {
4567                   if(style.readOnly) break;
4568                   Cut();
4569                   return false;
4570                }
4571                case ctrlL:
4572                {
4573                   if(style.readOnly) break;
4574                   ClearLine();
4575                   return false;
4576                }
4577                case ctrlZ:
4578                   if(style.readOnly) break;
4579                   Undo();
4580                   return false;
4581                case ctrlY:
4582                   if(style.readOnly) break;
4583                   Redo();
4584                   return false;
4585                default:
4586                   if(style.readOnly) break;
4587                   if(key.shift && key.code == rightBracket)
4588                   {
4589                      //Only indent back if you are exactly at one tab.
4590                      {
4591                         bool whitespace = true;
4592                         int i;
4593                         char * newline;
4594                         int putsize;
4595                         
4596                         int indentwidth;
4597                         EditLine line = this.line;
4598                         
4599                         //Only remove one tab if there is nothing else on the line.
4600                         for(i = 0; i < this.line.count; i++)
4601                         {
4602                            if(this.line.buffer[i] != ' ' && this.line.buffer[i] != '\t')
4603                            {
4604                               PutCh(ch);
4605                               return false;
4606                            }
4607                         }
4608                         
4609                         //Find opening {
4610                         i = 1;
4611                         while(i && line)
4612                         {
4613                            int pos;
4614                            
4615                            indentwidth = 0;
4616                            for(pos = line.count - 1; pos >= 0; pos--)
4617                            {
4618                               char c = line.buffer[pos];
4619                               if(c == ' ')
4620                                  indentwidth++;
4621                               else if(c == '\t')
4622                                  indentwidth += this.tabSize;
4623                               else
4624                                  //Counting backwards, so when you find a character, indentation is reset
4625                                  indentwidth = 0;
4626                               if(i && c == '}')
4627                                  i++;
4628                               if(i && c == '{')
4629                                  i--;
4630                            }
4631                            line = line.prev;
4632                         }
4633                         
4634                         //Place the } to get an undo:
4635                         PutCh(ch);
4636                         
4637                         this.x = this.line.count;
4638                         this.selX = 0;
4639                         
4640                         if(!style.useTab)
4641                            putsize = indentwidth;
4642                         else
4643                            putsize = indentwidth / this.tabSize + indentwidth % this.tabSize;
4644                         
4645                         newline = new char[putsize+2];
4646                         newline[putsize] = '}';
4647                         newline[putsize+1] = '\0';
4648                         
4649                         i = 0;
4650                         if(style.useTab)
4651                            for(; i < indentwidth / this.tabSize; i++)
4652                               newline[i] = '\t';
4653                         for(;i < putsize; i++)
4654                            newline[i] = ' ';
4655                         
4656                         AddS(newline);
4657                         
4658                         delete newline;
4659                      }
4660                      return false;
4661                   } else if(!key.ctrl && !key.alt && ch != 128 && ch >= 32)
4662                   {
4663                      PutCh(ch);
4664                      return false;
4665                   }
4666                   break;
4667             }
4668             break;
4669       }
4670       return true;
4671    }
4672
4673    void OnHScroll(ScrollBarAction action, int position, Key key)
4674    {
4675 #ifdef _DEBUG
4676       //PrintLn("OnHScroll: ", action, ", pos = ", position, ", key = ", key);
4677 #endif
4678       this.viewX = position;
4679       if(action != setRange)
4680       {
4681          if(!this.mouseMove && style.cursorFollowsView)
4682             SetCursorToViewX();
4683       }
4684       DirtyAll();
4685       UpdateDirty();
4686    }
4687
4688    void OnVScroll(ScrollBarAction action, int position, Key key)
4689    {
4690       int oldViewY = this.viewY;
4691
4692 #ifdef _DEBUG
4693       //PrintLn("OnVScroll: ", action, ", pos = ", position, ", key = ", key);
4694 #endif
4695       position /= this.space.h;
4696
4697       if(position < this.viewY)
4698       {
4699          for(; position < this.viewY && this.viewLine.prev; this.viewLine = this.viewLine.prev, this.viewY--);
4700          style.recomputeSyntax = true;
4701       }
4702       else if(position > this.viewY)
4703       {
4704          EditLine oldViewLine = viewLine;
4705          for(; position > this.viewY && this.viewLine.next; this.viewLine = this.viewLine.next, this.viewY++);
4706          FigureStartSyntaxStates(oldViewLine, false);
4707       }
4708       
4709       if(action != setRange)
4710       {
4711          if(!this.mouseMove && style.cursorFollowsView && !SelSize()) SetCursorToViewY();
4712       }
4713
4714       if(this.x != this.selX || this.y != this.selY)
4715          DirtyLine(this.y);
4716
4717       {
4718          Scroll(0, (this.viewY - oldViewY) * this.space.h);
4719
4720          /*
4721          int numLines = clientSize.h / this.space.h;
4722          int y;
4723          if(Abs(this.viewY - oldViewY) < numLines)
4724             
4725          
4726          if(this.viewY > oldViewY)
4727          {
4728             for(y = oldViewY; y <this.viewY; y++)
4729                DirtyLine(y + numLines);
4730          }
4731          else
4732          {
4733             for(y = this.viewY; y <oldViewY; y++)
4734                DirtyLine(y + numLines);
4735          }*/
4736       }
4737       //DirtyAll();
4738
4739       // Fix dirt of stuff before first line...
4740       if(this.viewY - oldViewY > 0)
4741       {
4742          Box box { 0,0, clientSize.w-1, YOFFSET-1 };
4743          Update(box);
4744       }
4745       
4746       UpdateDirty();
4747    }
4748
4749    bool _AddCh(unichar ch, int * addedSpacesPtr, int * addedTabsPtr)
4750    {
4751       EditLine line;
4752       int length, endX;
4753       bool result;
4754       ReplaceTextAction replaceAction = null;
4755       AddCharAction addCharAction = null;
4756       int addedSpaces = 0, addedTabs = 0;
4757
4758       if(ch == '\r') return true;
4759       if(style.stuckCaret /*|EES_READONLY)*/ ) 
4760          GoToEnd(true);
4761    
4762       if(ch == '\n' && !(style.multiLine) && this.line) return false;
4763       
4764       if(!undoBuffer.dontRecord)
4765       {
4766          if(selX != x || selY != y)
4767          {
4768             char buffer[5];
4769             char * newString;
4770             char * oldString;
4771             int len = GetText(null, selLine, selY, selX, this.line, y, x, false, false);
4772             oldString = new char[len];
4773             UTF32toUTF8Len(&ch, 1, buffer, 4);
4774             newString = CopyString(buffer);
4775             GetText(oldString, selLine, selY, selX, this.line, y, x, false, false);
4776
4777             replaceAction = ReplaceTextAction { newString = newString, oldString = oldString, placeAfter = false };
4778             if(selY < y || (selY == y && selX < x))
4779             {
4780                replaceAction.x1 = selX;
4781                replaceAction.y1 = selY;
4782                replaceAction.x2 = x;
4783                replaceAction.y2 = y;
4784             }
4785             else
4786             {
4787                replaceAction.x1 = x;
4788                replaceAction.y1 = y;
4789                replaceAction.x2 = selX;
4790                replaceAction.y2 = selY;
4791             }
4792             Record(replaceAction);
4793             undoBuffer.dontRecord++;
4794          }
4795          else
4796          {
4797             addCharAction = AddCharAction { y = y, x = x, ch = ch };
4798             Record(addCharAction);
4799          }
4800       }
4801
4802       if(ch == '\n')
4803       {
4804          DelSel(&addedSpaces);
4805          if(this.lineCount+1 > this.maxLines)
4806          {
4807             if(style.autoEmpty)
4808                Emptyline(this.lines.first,0);
4809             else
4810                return false;
4811          }
4812          if(!(style.vScroll)) 
4813          {
4814             // Make sure it fits, but we need a default line is this.font is too big for window
4815             if(this.space.h * (this.lineCount+1) > clientSize.h && this.line)
4816                return false;
4817          }
4818          if((this.y >= 0) && this.y < this.viewY)
4819          {
4820             this.viewY++;
4821             DirtyAll();
4822          }
4823          line = EditLine { };
4824          if(!line)
4825             return false;
4826          lines.Insert(this.line, line);
4827          line.editBox = this;
4828          line.buffer = null;
4829          line.count = 0;
4830          line.size = 0;
4831          length = 0;
4832
4833          // If we're displacing the lines from a current line ...
4834          if(this.line)
4835          {
4836             if(this.line.buffer)
4837             {
4838                endX = this.x;
4839                if(this.line.count < endX) endX = this.line.count;
4840                length = this.line.count - endX;
4841             }
4842          }
4843          if(!line.AdjustBuffer(length)) 
4844             return false;
4845          if(this.line)
4846          {
4847             if(this.line.buffer)
4848             {
4849                CopyBytes(line.buffer,this.line.buffer+endX, length+1);
4850 #ifdef _DEBUG
4851       if(endX > 4000 || endX < 0)
4852          printf("Warning");
4853 #endif
4854                this.line.count = endX;
4855                this.line.buffer[this.line.count] = '\0';
4856                this.line.AdjustBuffer(this.line.count);
4857                ComputeLength(this.line);
4858             }
4859          }
4860          {
4861             BufferLocation before = { this.line, this.y, this.x }, after;
4862
4863             this.line = line;
4864             this.x = 0;
4865             this.col = 0;
4866
4867             ComputeLength(this.line);
4868
4869 #ifdef _DEBUG
4870       if(length > 4000 || length < 0)
4871          printf("Warning");
4872 #endif
4873             line.count = length;
4874             DirtyEnd(this.y);
4875             this.y++;
4876             this.lineCount++;
4877             line.buffer[line.count] = '\0';
4878             result = true;
4879
4880             after.line = this.line, after.y = this.y, after.x = this.x;
4881
4882             NotifyCharsAdded(master, this, &before, &after, this.pasteOperation);
4883          }
4884       }
4885       else
4886       {
4887          char string[5];
4888          int count = UTF32toUTF8Len(&ch, 1, string, 5);
4889          DelSel(&addedSpaces);
4890          result = AddToLine(string, count, false, addedSpaces ? null : &addedSpaces, &addedTabs);
4891          if(addedSpacesPtr) *addedSpacesPtr = addedSpaces;
4892          if(addedTabsPtr) *addedTabsPtr = addedTabs;
4893       }
4894       this.selX = this.x;
4895       this.selY = this.y;
4896       this.selLine = this.line;
4897
4898       if(replaceAction)
4899       {
4900          replaceAction.x3 = x;
4901          replaceAction.y3 = y;
4902          replaceAction.addedSpaces = addedSpaces;
4903          replaceAction.addedTabs = addedTabs;
4904          undoBuffer.dontRecord--;
4905       }
4906       if(addCharAction)
4907       {
4908          addCharAction.addedSpaces = addedSpaces;
4909          addCharAction.addedTabs = addedTabs;
4910       }
4911       return result;
4912    }
4913
4914 public:
4915
4916    /****************************************************************************
4917                                  EDIT BOX METHODS
4918    ****************************************************************************/
4919
4920
4921    bool AddCh(unichar ch)
4922    {
4923       return _AddCh(ch, null, null);
4924    }
4925
4926    void Modified()
4927    {
4928       this.modified = true;
4929       NotifyUpdate(master, this);
4930       modifiedDocument = true;
4931    }
4932
4933    void Delete(EditLine line1, int y1, int x1, EditLine line2, int y2, int x2)
4934    {
4935       Deselect();
4936       DelCh(line1, y1, x1, line2, y2, x2, false);
4937       SetViewToCursor(true);
4938       UpdateDirty();
4939       Modified();
4940    }
4941
4942    void Undo()
4943    {
4944       undoBuffer.Undo();
4945       itemEditUndo.disabled = undoBuffer.curAction == 0;
4946       itemEditRedo.disabled = undoBuffer.curAction == undoBuffer.count;
4947       if(savedAction == undoBuffer.curAction)
4948       {
4949          modifiedDocument = false;
4950          SetModified(false);
4951          NotifyUnsetModified(master, this);
4952       }
4953    }
4954
4955    void Redo()
4956    {
4957       undoBuffer.Redo();
4958       itemEditUndo.disabled = undoBuffer.curAction == 0;
4959       itemEditRedo.disabled = undoBuffer.curAction == undoBuffer.count;
4960       if(savedAction == undoBuffer.curAction)
4961       {
4962          modifiedDocument = false;
4963          SetModified(false);
4964          NotifyUnsetModified(master, this);
4965       }
4966    }
4967
4968    void Record(UndoAction action)
4969    {
4970       if(!undoBuffer.dontRecord)
4971       {
4972          undoBuffer.Record(action);
4973          itemEditUndo.disabled = undoBuffer.curAction == 0;
4974          itemEditRedo.disabled = undoBuffer.curAction == undoBuffer.count;
4975       }
4976    }
4977
4978    void SelectAll()
4979    {
4980       Select(
4981          this.lines.first, 0,0,
4982          this.lines.last, this.lines.count-1, strlen(((EditLine)this.lines.last).buffer));
4983    }
4984
4985    void Select(EditLine line1, int y1, int x1, EditLine line2, int y2, int x2)
4986    {
4987       SelDirty();
4988       this.selX = x1;
4989       this.selY = y1;
4990       this.selLine = line1 ? (EditLine)line1 : this.lines.first;
4991       this.x = line2 ? x2 : ((EditLine)this.lines.last).count;
4992       this.y = line2 ? y2 : (this.lineCount-1);
4993       this.line = line2 ? (EditLine)line2 : this.lines.last;
4994       ComputeColumn();
4995       SelDirty();
4996       SetViewToCursor(true);
4997       UpdateDirty();
4998    }
4999
5000    // TODO: Fix this vs modifiedDocument window property
5001    void SetModified(bool flag)
5002    {
5003       if(this)
5004       {
5005          this.modified = false;
5006          if(flag && !NotifyModified(master, this))
5007             this.modified = true;
5008       }
5009    }
5010
5011    // BASIC OUTPUT
5012    bool AddS(char * string)
5013    {
5014       if(this)
5015       {
5016          bool ret = true;
5017          char * line;
5018          int c, count;
5019          int addedSpaces = 0, addedTabs = 0;
5020          AddTextAction action = null;
5021          ReplaceTextAction replaceAction = null;
5022
5023          this.pasteOperation = true;
5024
5025          if(style.stuckCaret /*|EES_READONLY)*/ ) 
5026             GoToEnd(true);
5027
5028          if(!undoBuffer.dontRecord)
5029          {
5030             char * placeString = CopyString(string);
5031             if(!style.multiLine)
5032             {
5033                int i;
5034                char ch; 
5035                for(i = 0; (ch = placeString[i]); i++)
5036                   if(ch == '\n')
5037                   {
5038                      placeString[i] = '\0';
5039                      placeString = renew placeString byte[i+1];
5040                      break;
5041                   }
5042             }
5043             
5044             if(selX != x || selY != y)
5045             {
5046                char * newString;
5047                char * oldString;
5048                int len = GetText(null, selLine, selY, selX, this.line, y, x, false, false);
5049                oldString = new char[len];
5050                newString = placeString;
5051                GetText(oldString, selLine, selY, selX, this.line, y, x, false, false);
5052
5053                replaceAction = ReplaceTextAction { newString = newString, oldString = oldString, placeAfter = false };
5054                if(selY < y || (selY == y && selX < x))
5055                {
5056                   replaceAction.x1 = selX;
5057                   replaceAction.y1 = selY;
5058                   replaceAction.x2 = x;
5059                   replaceAction.y2 = y;
5060                }
5061                else
5062                {
5063                   replaceAction.x1 = x;
5064                   replaceAction.y1 = y;
5065                   replaceAction.x2 = selX;
5066                   replaceAction.y2 = selY;
5067                }
5068                Record(replaceAction);
5069             }
5070             else if(string[0])
5071             {
5072                if(string[0] == '\n')
5073                   action = AddTextAction { y1 = y, x1 = Min(this.line.count, x), string = placeString };
5074                else
5075                   action = AddTextAction { y1 = y, x1 = x, string = placeString };
5076                
5077                Record(action);
5078             }
5079             else
5080                delete placeString;
5081          }
5082
5083          undoBuffer.dontRecord++;
5084          DelSel(&addedSpaces);
5085
5086          count = 0;
5087          line = string;
5088          for(c = 0; string[c]; c++)
5089          {
5090             if(string[c] == '\n' || string[c] == '\r')
5091             {
5092                if(!AddToLine(line,count, true, addedSpaces ? null : &addedSpaces, addedTabs ? null : &addedTabs))
5093                {
5094                   ret = false;
5095                   break;
5096                }
5097                if(string[c] == '\n')
5098                {
5099                   if(!AddCh('\n'))
5100                   {
5101                      ret = false;
5102                      break;
5103                   }
5104                }
5105                // Reset for next line
5106                count = 0;
5107                line = string+c+1;
5108                /*
5109                if(string[c] == '\r' && *line == '\n') 
5110                {
5111                   line++;
5112                   c++;
5113                }*/
5114             }
5115             else
5116             {
5117                count++;
5118             }
5119          }
5120
5121          // Why was this here?
5122          // FindMaxLine();
5123
5124          // Add the line here
5125          if(count)
5126             if(!AddToLine(line,count,false, addedSpaces ? null : &addedSpaces, addedTabs ? null : &addedTabs))
5127             {
5128                ret = false;
5129             }
5130          FindMaxLine();
5131
5132          undoBuffer.dontRecord--;
5133          if(action)
5134          {
5135             action.y2 = y;
5136             action.x2 = x;
5137             action.addedSpaces = addedSpaces;
5138             action.addedTabs = addedTabs;
5139          }
5140          else if(replaceAction)
5141          {
5142             replaceAction.y3 = y;
5143             replaceAction.x3 = x;
5144             replaceAction.addedSpaces = addedSpaces;
5145             replaceAction.addedTabs = addedTabs;
5146          }
5147
5148          UpdateCaretPosition(true);
5149
5150          this.pasteOperation = false;
5151
5152          return ret;
5153       }
5154       return false;
5155    }
5156
5157    // CONSOLE OUTPUT
5158    void Clear()
5159    {
5160       if(this)
5161       {
5162          Deselect();
5163          DelCh(this.lines.first, 0, 0, this.lines.last, this.lineCount-1, ((EditLine)(this.lines.last)).count, true);
5164          SetViewToCursor(true);
5165          UpdateDirty();
5166          Modified();
5167       }
5168    }
5169
5170    void PutCh(unichar ch)
5171    {
5172       bool result;
5173       
5174       if((ch >= 32 /*&& ch <=126*/) || ch == '\n')
5175       //if((ch >= 32) || ch == '\n')
5176       {
5177          int addedSpaces = 0, addedTabs = 0;
5178          ReplaceTextAction replaceAction = null;
5179          AddCharAction addCharAction = null;
5180
5181          if(style.allCaps)
5182             ch = (ch < 128) ? toupper(ch) : ch;     // TODO: UNICODE TO UPPER
5183
5184          if(this.x < this.line.count && this.overwrite)
5185          {
5186             char buffer[5];
5187             char * newString;
5188             char * oldString;
5189             int len = GetText(null, line, y, x, line, y, x+1, false, false);
5190             oldString = new char[len];
5191             UTF32toUTF8Len(&ch, 1, buffer, 4);
5192             newString = CopyString(buffer);
5193             GetText(oldString, line, y, x, line, y, x+1, false, false);
5194             replaceAction = ReplaceTextAction { newString = newString, oldString = oldString, placeAfter = false };
5195             replaceAction.x1 = x;
5196             replaceAction.y1 = y;
5197             replaceAction.x2 = x+1;
5198             replaceAction.y2 = y;
5199             Record(replaceAction);
5200
5201             undoBuffer.dontRecord++;
5202             DelCh(this.line, this.y, this.x, this.line, this.y, this.x+1, true);
5203             undoBuffer.dontRecord--;
5204          }
5205          else if(!undoBuffer.dontRecord)
5206          {
5207             if(selX != x || selY != y)
5208             {
5209                char buffer[5];
5210                char * newString;
5211                char * oldString;
5212                int len = GetText(null, line, y, x, selLine, selY, selX, false, false);
5213                oldString = new char[len];
5214                UTF32toUTF8Len(&ch, 1, buffer, 4);
5215                newString = CopyString(buffer);
5216                GetText(oldString, line, y, x, selLine, selY, selX, false, false);
5217                replaceAction = ReplaceTextAction { newString = newString, oldString = oldString, placeAfter = true };
5218                if(selY < y || (selY == y && selX < x))
5219                {
5220                   replaceAction.x1 = selX;
5221                   replaceAction.y1 = selY;
5222                   replaceAction.x2 = x;
5223                   replaceAction.y2 = y;
5224                }
5225                else
5226                {
5227                   replaceAction.x1 = x;
5228                   replaceAction.y1 = y;
5229                   replaceAction.x2 = selX;
5230                   replaceAction.y2 = selY;
5231                }
5232                Record(replaceAction);
5233             }
5234             else
5235             {
5236                addCharAction = AddCharAction { y = y, x = x, ch = ch };
5237                Record(addCharAction);
5238             }
5239          }
5240          undoBuffer.dontRecord++;
5241          result = _AddCh(ch, &addedSpaces, &addedTabs);
5242          if(replaceAction)
5243          {
5244             replaceAction.x3 = x;
5245             replaceAction.y3 = y;
5246             replaceAction.addedSpaces = addedSpaces;
5247             replaceAction.addedTabs = addedTabs;
5248          }  
5249          if(addCharAction)
5250          {
5251             addCharAction.addedSpaces = addedSpaces;
5252             addCharAction.addedTabs = addedTabs;
5253          }
5254          undoBuffer.dontRecord--;
5255          if(ch == '\n')
5256             FindMaxLine();
5257          Modified();
5258          if(result) SetViewToCursor(true);
5259       }
5260    }
5261
5262    void PutS(char * string)
5263    {
5264       if(this)
5265       {
5266          AddS(string);
5267          SetViewToCursor(true);
5268          Modified();
5269       }
5270    }
5271
5272    void Printf(char * format, ...)
5273    {
5274       if(this)
5275       {
5276          char temp[MAX_F_STRING];
5277          va_list args;
5278          va_start(args, format);
5279          vsprintf(temp, format, args);
5280          va_end(args);
5281          PutS(temp);
5282       }
5283    }
5284
5285    void SetContents(char * format, ...)
5286    {
5287       if(this)
5288       {
5289          Deselect();
5290          DelCh(this.lines.first, 0, 0, this.lines.last, this.lineCount-1, ((EditLine)(this.lines.last)).count, true);
5291          if(format)
5292          {
5293             char temp[MAX_F_STRING];
5294             va_list args;
5295             va_start(args, format);
5296             vsprintf(temp, format, args);
5297             va_end(args);
5298
5299             AddS(temp);
5300          }
5301          UpdateDirty();
5302          Home();
5303       }
5304    }
5305
5306    void BackSpace()
5307    {
5308       if(!DelSel(null))
5309       {
5310          if(x > 0)
5311          {
5312             x -= 1 + DelCh(line, y, x-1, line, y, x, true);
5313             Modified();
5314          }
5315          else if(this.line.prev)
5316          {
5317             EditLine line = this.line.prev;
5318             int x = line.count;
5319             int y = this.y;
5320
5321             DelCh(line, this.y-1, x, this.line, this.y, this.x, true);
5322             this.line = line;
5323             this.y = y-1;
5324             this.x = x;
5325             Modified();
5326          }
5327          this.selX = this.x;
5328          this.selY = this.y;
5329          this.selLine = this.line;
5330          ComputeColumn();
5331       }
5332       else
5333          Modified();
5334       SetViewToCursor(true);
5335    }
5336
5337    void ClearLine()
5338    {
5339       Emptyline(this.line,this.y);
5340       this.selX = this.x = 0;
5341       this.col = 0;
5342       this.selY = this.y;
5343       this.selLine = this.line;
5344
5345       SetViewToCursor(true);
5346       Modified();
5347    }
5348
5349    // CARET CONTROL
5350    void End()
5351    {
5352       if(this)
5353       {
5354          GoToEnd(true);
5355          SetViewToCursor(true);
5356       }
5357    }
5358    void Home()
5359    {
5360       if(this)
5361       {
5362          GoToHome(true);
5363          SetViewToCursor(true);
5364       }
5365    }
5366
5367    bool GoToLineNum(int lineNum)
5368    {
5369       if(this.line)
5370       {
5371          int c;
5372          EditLine line = this.lines.first;
5373          for(c = 0; c < lineNum && line; c++, line = line.next);
5374          if(line)
5375          {
5376             if(this.y != c)
5377                DirtyAll();
5378             else
5379                DirtyLine(c);
5380             this.y = c;
5381             this.line = line;
5382             Deselect();
5383             SetViewToCursor(true);
5384             return true;
5385          }
5386       }
5387       return false;
5388    }
5389
5390    bool GoToPosition(EditLine line, int y, int x)
5391    {
5392       /*
5393       if(!line)
5394       {
5395          line = this.line;
5396          y = this.y;
5397       }
5398       */
5399       if(!line)
5400       {
5401          int c;
5402          for(line = this.lines.first, c = 0; c<y && line; c++, line = line.next);
5403       }
5404
5405       if(line)
5406       {
5407          if(this.y != y)
5408             DirtyAll();
5409          else
5410             DirtyLine(y);
5411          this.x = x;
5412          this.y = y;
5413          this.line = line;
5414          ComputeColumn();
5415          Deselect();
5416          SetViewToCursor(true);
5417          return true;
5418       }
5419       return false;
5420    }
5421
5422    // VIEW POSITIONING
5423    void SetViewToCursor(bool setCaret)
5424    {
5425       if(created)
5426       {
5427          int w;
5428          int c, numLines;
5429          EditLine line;
5430          int x;
5431          int checkX, checkY;
5432          EditLine checkLine;
5433          bool dontScroll = false;
5434          bool selected;
5435          int viewX, viewY;
5436
5437          FixScrollArea();
5438
5439          selected = selX != this.x || selY != y;
5440          
5441          viewX = this.viewX;
5442          viewY = this.viewY;
5443
5444          if(mouseMove)
5445          {
5446             checkLine = dropLine;
5447             checkX = dropX;
5448             checkY = dropY;
5449          }
5450          else
5451          {
5452             checkLine = this.line;
5453             checkX = this.x;
5454             checkY = y;
5455          }
5456
5457          numLines = clientSize.h / space.h;
5458
5459          // This is broken. The EditBox now doesn't do anything different when adding to it,
5460          // regardless of the previous scrolling position. It should be read and then set again
5461          // if one wishes to preserve it.
5462          /*  // Don't scroll view to cursor if we're in a EES_NOCARET box
5463          if(style.noCaret && this.viewY < lineCount - numLines - 1)
5464             dontScroll = true;
5465          */
5466
5467          // Horizontal Adjustment
5468          if(!dontScroll && checkLine)
5469          {
5470             x = 0;
5471             if(mouseMove)
5472                dropX = AdjustXPosition(this.line, MAXINT, true, &x, checkX, 0);
5473             else
5474             {
5475                this.x = AdjustXPosition(this.line, MAXINT, true, &x, checkX, 0);
5476                ComputeColumn();
5477             }
5478
5479             if(style.hScroll)
5480             {
5481                if(x + space.w >= this.viewX + clientSize.w && clientSize.w >= space.w)
5482                   viewX = x - clientSize.w+space.w;
5483                if(x < this.viewX + clientSize.w/2 - space.w)
5484                   viewX = Max(0, x - clientSize.w/2 + space.w);
5485             }
5486          }
5487
5488          if(!dontScroll) 
5489          {
5490             if(style.vScroll)
5491             {
5492                // Vertical Adjustment
5493                if(viewY > checkY) viewY = checkY;
5494                if(viewY + numLines <= checkY)
5495                {
5496                   if(clientSize.h >= space.h)
5497                   {
5498                      for(line = viewLine; line && (viewY + numLines <= checkY); line = line.next)
5499                         viewY++;
5500                   }
5501                   else
5502                      viewY = checkY;
5503                }
5504             }
5505             else
5506             {
5507                if(mouseMove)
5508                   for(;dropLine && dropLine.prev && dropY >= numLines;)
5509                   {
5510                      dropLine = dropLine.prev;
5511                      dropY--;                  
5512                   }
5513                else
5514                   for(;this.line && this.line.prev && this.y >= numLines;)
5515                   {
5516                      this.line = this.line.prev;
5517                      y--;                  
5518                   }
5519             }
5520
5521             SetScrollPosition(viewX, viewY * this.space.h);
5522
5523             UpdateCaretPosition(setCaret);
5524
5525             if(!selected)
5526             {
5527                selX = this.x;
5528                selY = this.y;
5529                selLine = this.line;
5530             }
5531          }
5532
5533          UpdateDirty();
5534          SetSelectCursor();
5535          SelectionEnables();
5536       }
5537    }
5538
5539    void CenterOnCursor()
5540    {
5541       int numLines = clientSize.h / this.space.h;
5542       int y = this.y - numLines / 2;
5543       int viewY;
5544       bool figureSyntax = false;
5545       EditLine oldViewLine = viewLine;
5546       if(y > this.lineCount - numLines) y = this.lineCount-numLines;
5547       if(y < 0) y = 0;
5548
5549       viewY = y;
5550
5551       for(;y < this.viewY; y++)
5552       {
5553          this.viewLine = this.viewLine.prev;
5554          style.recomputeSyntax = true;
5555       }
5556       for(;y > this.viewY; y--)
5557       {
5558          this.viewLine = this.viewLine.next;
5559          figureSyntax = true;
5560       }
5561       if(figureSyntax)
5562          FigureStartSyntaxStates(oldViewLine, false);
5563
5564       this.viewY = viewY;
5565
5566       SetScrollPosition(this.viewX, viewY * this.space.h);
5567       UpdateCaretPosition(true);
5568       UpdateDirty();
5569    }
5570
5571    void SetCursorToView()
5572    {
5573       SetCursorToViewX();
5574       SetCursorToViewY();
5575    }
5576
5577    void PageDown()
5578    {
5579       int c, numLines;
5580       EditLine line;
5581
5582       numLines = clientSize.h / this.space.h;
5583
5584       if(style.noCaret)
5585       {
5586          for(c=0, line = this.viewLine.next; line && c<numLines && (this.viewY < this.lineCount - numLines); line = line.next, c++);
5587          SetScrollPosition(this.viewX, (this.viewY + c) * this.space.h);
5588       }
5589       else
5590       {
5591          EditLine oldLine = this.line;
5592          bool lastOne = false;
5593          EditLine oldViewLine = this.viewLine;
5594          bool figureSyntax = false;
5595
5596          if(this.y >= this.lineCount-1) return;
5597
5598          for(c=0, line = this.line.next; line && c<numLines; line = line.next, c++)
5599          {
5600             if(this.viewY + numLines < this.lines.count)
5601             {
5602                this.viewLine = this.viewLine.next;
5603                this.viewY++;
5604                figureSyntax = true;
5605             }
5606             else if(c && !lastOne)
5607                break;
5608             else
5609                lastOne = true;
5610
5611             this.line = line;
5612             this.y++;
5613          }
5614          if(figureSyntax)
5615             FigureStartSyntaxStates(oldViewLine, false);
5616          this.x = AdjustXPosition(line, caretX, true, null, MAXINT, 0);
5617          ComputeColumn();
5618
5619          SetViewToCursor(false);
5620       }
5621    }
5622
5623    void PageUp()
5624    {
5625       int c, numLines;
5626       EditLine line;
5627       
5628       if(this.y == 0) return;
5629
5630       numLines = clientSize.h / this.space.h;
5631
5632       if(style.noCaret)
5633       {
5634          for(c=0, line = this.viewLine.prev; line && c<numLines; line = line.prev, c++);
5635          SetScrollPosition(this.viewX, (this.viewY - c) * this.space.h);
5636       }
5637       else
5638       {
5639          EditLine oldLine = this.line;
5640
5641          for(c=0, line = this.line.prev; line && c<numLines; line = line.prev, c++)
5642          {
5643             this.line = line;
5644             this.y--;
5645
5646             if(this.viewLine.prev)
5647             {
5648                this.viewLine = this.viewLine.prev;
5649                this.viewY--;
5650                style.recomputeSyntax = true;
5651             }
5652          }
5653
5654          this.x = AdjustXPosition(line, caretX, true, null, MAXINT, 0);
5655          ComputeColumn();
5656
5657          SetViewToCursor(false);
5658       }
5659    }
5660
5661    void LineUp()
5662    {
5663
5664       if(this.viewLine.prev)
5665       {
5666          //DirtyAll();
5667          // this.viewLine = this.viewLine.prev;
5668          //this.viewY--;
5669
5670          SetScrollPosition(this.viewX, (this.viewY - 1) * this.space.h);
5671       }
5672    }
5673
5674
5675    void LineDown()
5676    {
5677       if(this.viewLine.next)
5678       {
5679          //DirtyAll();
5680          // this.viewLine = this.viewLine.next;
5681          // this.viewY++;
5682          
5683          SetScrollPosition(this.viewX, (this.viewY + 1) * this.space.h);
5684       }
5685    }
5686
5687    // Selection
5688    uint SelSize()
5689    {
5690       EditLine l1, l2, line;
5691       int x1, x2, nx1, nx2;
5692       int count;
5693       int start, end;
5694       int size;
5695
5696       if(!this.selLine) return 0;
5697       if(this.selLine == this.line && this.selX == this.x) return 0;
5698       if(this.selY < this.y)
5699       {
5700          l1 = this.selLine;
5701          x1 = this.selX;
5702          l2 = this.line;
5703          x2 = this.x;
5704       }
5705       else if(this.selY > this.y)
5706       {
5707          l1 = this.line;
5708          x1 = this.x;
5709          l2 = this.selLine;
5710          x2 = this.selX;
5711       }
5712       else if(this.selX < this.x)
5713       {
5714          l1 = l2 = this.line;
5715          x1 = this.selX;
5716          x2 = this.x;
5717       }
5718       else
5719       {
5720          l1 = l2 = this.line;
5721          x1 = this.x;
5722          x2 = this.selX;
5723       }
5724       nx1 = Min(x1,l1.count);
5725       nx2 = Min(x2,l2.count);
5726       
5727       // Find Number of Bytes Needed
5728       size = 0;
5729       for(line = l1; line; line = line.next)
5730       {  
5731          if(line == l1) start = nx1; else start = 0;
5732          if(line == l2) end = nx2; else end = line.count;
5733          size += end-start;
5734          if(style.freeCaret && line == l2)
5735          {
5736             if(l1 == l2)
5737                count = Max(x2-Max(x1,l1.count),0);
5738             else
5739                count = Max(x2-l2.count,0);
5740             size+=count;
5741          }
5742
5743          if(line == l2) break;
5744          // Add Carriage Return / line Feed
5745          size++;
5746          size++;
5747       }
5748       return size;
5749    }
5750
5751    void GetSelPos(EditLine * l1, int *y1, int *x1, EditLine * l2, int * y2, int * x2, bool reorder)
5752    {
5753       if(this)
5754       {
5755          if(!reorder || this.selY < this.y)
5756          {
5757             if(l1) *l1 = this.selLine;
5758             if(y1) *y1 = this.selY;
5759             if(x1) *x1 = this.selX;
5760             if(l2) *l2 = this.line;
5761             if(y2) *y2 = this.y;
5762             if(x2) *x2 = this.x;
5763          }
5764          else if(this.selY > this.y)
5765          {
5766             if(l1) *l1 = this.line;
5767             if(y1) *y1 = this.y;
5768             if(x1) *x1 = this.x;
5769             if(l2) *l2 = this.selLine;
5770             if(y2) *y2 = this.selY;
5771             if(x2) *x2 = this.selX;
5772          }
5773          else if(this.selX < this.x)
5774          {
5775             if(l1) *l1 = this.line;
5776             if(y1) *y1 = this.selY;
5777             if(x1) *x1 = this.selX;
5778             if(l2) *l2 = this.line;
5779             if(y2) *y2 = this.y;
5780             if(x2) *x2 = this.x;
5781          }
5782          else
5783          {
5784             if(l1) *l1 = this.line;
5785             if(y1) *y1 = this.y;
5786             if(x1) *x1 = this.x;
5787             if(l2) *l2 = this.line;
5788             if(y2) *y2 = this.selY;
5789             if(x2) *x2 = this.selX;
5790          }
5791       }
5792    }
5793
5794    void SetSelPos(EditLine l1, int y1, int x1, EditLine l2, int y2, int x2)
5795    {
5796       if(this && (this.selY != y1 || this.selX != x1 || this.y != y2 || this.x != x2))
5797       {
5798          this.selLine = (EditLine)l1;
5799          this.selY = y1;
5800          this.selX = x1;
5801          this.line = (EditLine)l2;
5802          this.y = y2;
5803          this.x = x2;
5804          ComputeColumn();
5805          SetViewToCursor(true);
5806       }
5807    }
5808
5809    int GetText(char * text, EditLine _l1, int _y1, int _x1, EditLine _l2, int _y2, int _x2, bool addCr, bool addSpaces)
5810    {
5811       EditLine l1, l2, line;
5812       int x1, x2, nx1, nx2;
5813       int count;
5814       int start, end;
5815       int numChars = 0;
5816
5817       if(_y2 < _y1)
5818       {
5819          l1 = _l2;
5820          x1 = _x2;
5821          l2 = _l1;
5822          x2 = _x1;
5823       }
5824       else if(_y2 > _y1)
5825       {
5826          l1 = _l1;
5827          x1 = _x1;
5828          l2 = _l2;
5829          x2 = _x2;
5830       }
5831       else if(_x2 < _x1)
5832       {
5833          l1 = l2 = _l1;
5834          x1 = _x2;
5835          x2 = _x1;
5836       }
5837       else
5838       {
5839          l1 = l2 = _l1;
5840          x1 = _x1;
5841          x2 = _x2;
5842       }
5843       nx1 = Min(x1,l1.count);
5844       nx2 = Min(x2,l2.count);
5845
5846       // Copy text
5847       for(line = l1; line; line = line.next)
5848       {  
5849          if(line == l1) start = nx1; else start = 0;
5850          if(line == l2) end = nx2; else end = line.count;
5851          if(text)
5852          {
5853             CopyBytes(text, line.buffer + start, end - start);
5854             text += end-start;
5855          } 
5856          numChars += end-start;
5857          
5858          if(style.freeCaret && line == l2 && addSpaces)
5859          {
5860             if(l1 == l2)
5861                count = Max(x2-Max(x1,l1.count),0);
5862             else
5863                count = Max(x2-l2.count,0);
5864             if(text)
5865             {
5866                FillBytes(text,' ',count);
5867                text += count;
5868             }
5869             else
5870                numChars += count;
5871          }
5872          if(line == l2) break;
5873          // Add line Feed
5874          if(addCr)
5875          {
5876             if(text)
5877                *(text++) = '\r';
5878             numChars++;
5879          } 
5880          if(text)
5881             *(text++) = '\n';
5882          numChars++; 
5883       }
5884       // '\0' terminate Terminate
5885       if(text)
5886          *(text) = '\0';
5887       numChars++;
5888       return numChars;
5889    }
5890
5891    void GetSel(char * text, bool addCr)
5892    {
5893       GetText(text, line, y, x, selLine, selY, selX, addCr, true);
5894    }
5895
5896    void Deselect()
5897    {
5898       SelDirty();
5899       this.selLine = this.line;
5900       this.selX = this.x;
5901       this.selY = this.y;
5902    }
5903
5904    // CLIPBOARD
5905    void Copy()
5906    {
5907       if(this)
5908       {
5909          int size = SelSize();
5910          if(size)
5911          {
5912             // Try to allocate memory
5913             ClipBoard clipBoard { };
5914             if(clipBoard.Allocate(size+1))
5915             {
5916                GetSel(clipBoard.memory, true);   
5917                // Save clipboard
5918                clipBoard.Save();
5919             }
5920             delete clipBoard;
5921          }
5922       }
5923    }
5924
5925    void Paste()
5926    {
5927       if(this)
5928       {
5929          ClipBoard clipBoard { };
5930          if(clipBoard.Load())
5931             PutS(clipBoard.memory);
5932          delete clipBoard;
5933       }
5934    }
5935
5936    void Cut()
5937    {
5938       if(this)
5939       {
5940          Copy();
5941          DelSel(null);
5942          SetViewToCursor(true);
5943          Modified();
5944       }
5945    }
5946
5947    void DeleteSelection()
5948    {
5949       if(this)
5950       {
5951          DelSel(null);
5952          SetViewToCursor(true);
5953          Modified();
5954       }
5955    }
5956
5957    // FILE INTERFACE
5958    void Save(File f, bool lf)
5959    {
5960       EditLine line;
5961       savedAction = undoBuffer.curAction;
5962
5963       for(line = this.lines.first; line; line = line.next)
5964       {  
5965          f.Write(line.buffer, line.count,1);
5966          if(line.next)
5967          {
5968             if(lf) f.Putc('\r');
5969             f.Putc('\n');
5970          }
5971       }
5972    }
5973
5974    #define BUFFER_SIZE  16384
5975    void Load(File f)
5976    {
5977       undoBuffer.dontRecord++;
5978       if(f)
5979       {
5980          char buffer[BUFFER_SIZE];
5981      
5982          for(;;)
5983          {
5984             int count = f.Read(buffer, 1, BUFFER_SIZE-1);
5985             buffer[count] = '\0';
5986             AddS(buffer);
5987             if(!count) break;
5988          }
5989          Home();
5990       }
5991       undoBuffer.dontRecord--;
5992       undoBuffer.count = 0;
5993       undoBuffer.curAction = 0;
5994       itemEditUndo.disabled = undoBuffer.curAction == 0;
5995       itemEditRedo.disabled = undoBuffer.curAction == undoBuffer.count;
5996    }
5997
5998    EditBoxFindResult Find(char * text, bool matchWord, bool matchCase, bool isSearchDown)
5999    {
6000       EditLine line;
6001       int num;
6002       bool firstPass = true;
6003       EditBoxFindResult result = found;
6004
6005       if(!this.line) return notFound;
6006       num = this.y;
6007
6008       for(line = this.line;;)
6009       {
6010          char * string;
6011
6012          if(!line) 
6013          {
6014             if(isSearchDown)
6015             {
6016                line = this.lines.first;
6017                num = 0;
6018                result = wrapped;
6019             }
6020             else
6021             {
6022                line = this.lines.last;
6023                num = this.lineCount - 1;
6024                result = wrapped;
6025             }
6026          }
6027          
6028          if(isSearchDown)
6029             string = SearchString(line.buffer, firstPass ? Min(this.x,line.count) : 0,text,matchCase,matchWord);
6030          else
6031             string = RSearchString(line.buffer,text,firstPass ? (Min(this.x,line.count)-1) : line.count,matchCase,matchWord);
6032
6033          if(string)
6034          {
6035             Select((void *)line,num,string - line.buffer,(void *)line,num,string - line.buffer + strlen(text));
6036             return result;
6037          } 
6038          if(line == this.line && !firstPass) break;
6039
6040          if(isSearchDown)
6041          {
6042             line = line.next;
6043             num++;
6044          }
6045          else
6046          {
6047             line = line.prev;
6048             num--;
6049          }
6050
6051          firstPass = false;
6052       }
6053       return notFound;
6054    }
6055
6056    EditBoxFindResult FindInSelection(char * text, bool matchWord, bool matchCase, EditLine l2, int y2, int x2)
6057    {
6058       EditLine line;
6059       int y;
6060       int searchLen = strlen(text);
6061       for(line = (EditLine)this.line, y = this.y; y <= y2; line = line.next, y++)
6062       {
6063          char * string = SearchString(line.buffer, (y == this.y) ? Min(this.x,line.count) : 0,text,matchCase,matchWord);
6064          if(string && (y < y2 || (string - line.buffer) + searchLen <= x2))
6065          {
6066             Select((void *)line,y,string - line.buffer,(void *)line,y,string - line.buffer + strlen(text));
6067             return found;
6068          } 
6069       }
6070       return notFound;
6071    }
6072
6073    bool OnKeyDown(Key key, unichar ch)
6074    {
6075 #ifdef _DEBUG
6076       //PrintLn("OnKeyDown: code = ", key.code, ", mods = ", key.modifiers, ", ch = ", (int)ch);
6077 #endif
6078       if(!NotifyKeyDown(master, this, key, ch))
6079          return false;
6080       else 
6081       {
6082          switch(key)
6083          {
6084             case escape:
6085             {
6086                if(this.mouseMove)
6087                {
6088                   this.mouseMove = false;
6089                   OnLeftButtonUp(0,0,0);
6090                   SetViewToCursor(true);
6091                   return false;
6092                }
6093             }
6094          }
6095       }
6096       return true;
6097    }
6098 };
6099
6100 public class EditBoxStream : File
6101 {
6102    EditBox editBox;
6103    BufferLocation start, sel;
6104    uint pos;
6105    byte utf8Bytes[5];
6106    int numBytes;
6107
6108    ~EditBoxStream()
6109    {
6110       EditBox editBox = this.editBox;
6111
6112       editBox.x = start.x;
6113       editBox.y = start.y;
6114       editBox.line = start.line;
6115
6116       editBox.selX = sel.x;
6117       editBox.selY = sel.y;
6118       editBox.selLine = sel.line;
6119
6120       editBox.SetViewToCursor(true);
6121       //editBox.ComputeColumn();
6122    }
6123
6124 public:
6125    property EditBox editBox
6126    {
6127       set
6128       {
6129          editBox = value;
6130
6131          start.x = value.x;
6132          start.y = value.y;
6133          start.line = value.line;
6134
6135          sel.x = value.selX;
6136          sel.y = value.selY;
6137          sel.line = value.selLine;
6138          numBytes = 0;
6139
6140          value.GoToHome(true);
6141       }
6142
6143       get { return editBox; }
6144    }
6145
6146    uint Read(byte * buffer, uint size, uint count)
6147    {
6148       uint read = 0;
6149       EditBox editBox = this.editBox;
6150       EditLine line = editBox.line;
6151       int x = editBox.x;
6152       int y = editBox.y;
6153
6154       count *= size;
6155
6156       for(;read < count && line; line = (*&line.next))
6157       {
6158          int numBytes = Min(count - read, (*&line.count) - x);
6159          if(numBytes > 0)
6160          {
6161             memcpy(buffer + read, (*&line.buffer) + x, numBytes);
6162             read += numBytes;
6163             x += numBytes;
6164          }
6165          /*
6166          for(;read < count && x < (*&line.count);)
6167          {
6168             buffer[read++] = (*&line.buffer)[x++];
6169          }
6170          */
6171          if((*&line.next))
6172          {
6173             if(read < count)
6174             {
6175                buffer[read++] = '\n';
6176             }
6177             else
6178                break;
6179          }
6180          if(x == (*&line.count) && (*&line.next))
6181          {
6182             x = 0;
6183             y++;
6184          }
6185          else
6186             break;         
6187       }
6188
6189       editBox.line = editBox.selLine = line;
6190       editBox.x = editBox.selX = x;
6191       editBox.y = editBox.selY = y;
6192       pos += read;
6193       return read / size;
6194    }
6195
6196    bool Seek(int pos, FileSeekMode mode)
6197    {
6198       bool result = true;
6199       EditBox editBox = this.editBox;
6200       EditLine line = editBox.line;
6201       numBytes = 0;
6202       if(mode == FileSeekMode::start)
6203       {
6204          pos = pos - this.pos;
6205          mode = current;
6206       }
6207       if(mode == current)
6208       {
6209          uint read = 0;
6210          int x = editBox.x;
6211          int y = editBox.y;
6212          if(pos > 0)
6213          {
6214             for(;read < pos && line; line = (*&line.next))
6215             {
6216                int numBytes = Min(pos - read, (*&line.count) - x);
6217                if(numBytes > 0)
6218                {
6219                   read += numBytes; x += numBytes;
6220                }
6221
6222                /*for(;read < pos && x < (*&line.count);)
6223                {
6224                   read++; x++;
6225                }
6226                */
6227                if((*&line.next))
6228                {
6229                   if(read < pos)
6230                   {
6231                      read++;
6232                      x = 0;
6233                   }
6234                   else
6235                      break;
6236                }
6237                else
6238                {
6239                   if(read<pos)
6240                      result = false;
6241                   break;
6242                }
6243                y++;
6244             }
6245             this.pos += read;
6246          }
6247          else if(pos < 0)
6248          {
6249             pos = -pos;
6250             for(;read < pos && line; line = (*&line.prev))
6251             {
6252                int numBytes = Min(pos - read, x);
6253                if(numBytes > 0)
6254                {
6255                   read += numBytes;
6256                   x -= numBytes;
6257                }
6258                /*for(;read < pos && x > 0;)
6259                {
6260                   read++; x--;
6261                }
6262                */
6263                if((*&line.prev))
6264                {
6265                   if(read < pos)
6266                   {
6267                      read++;
6268                      x = (*&(*&line.prev).count);
6269                   }
6270                   else
6271                      break;
6272                }
6273                else
6274                {
6275                   if(read<pos)
6276                      result = false;
6277                   break;
6278                }
6279                y--;
6280             }
6281             this.pos -= read;
6282          }
6283
6284          editBox.line = editBox.selLine = line;
6285          editBox.x = editBox.selX = x;
6286          editBox.y = editBox.selY = y;
6287       }
6288       return result;
6289    }
6290    
6291    bool Puts(char * string)
6292    {
6293       EditBox editBox = this.editBox;
6294       BufferLocation start { editBox.line, editBox.y, editBox.x };
6295       BufferLocation pos;
6296       
6297       numBytes = 0;
6298       editBox.AddS(string);
6299
6300       pos.line = editBox.line;
6301       pos.y = editBox.y;
6302       pos.x = editBox.x;
6303
6304       this.start.AdjustAdd(start, pos);
6305       sel.AdjustAdd(start, pos);
6306
6307       return true;
6308    }
6309
6310    // NOTE: BYTE, NOT UNICODE CHARACTER!
6311    bool Putc(char ch)
6312    {
6313       EditBox editBox = this.editBox;
6314       BufferLocation start = { editBox.line, editBox.y, editBox.x };
6315       BufferLocation pos;
6316
6317       if(numBytes < 4)
6318       {
6319          utf8Bytes[numBytes++] = ch;
6320          utf8Bytes[numBytes] = 0;
6321          if(UTF8Validate(utf8Bytes))
6322          {
6323             editBox.AddCh(UTF8_GET_CHAR(utf8Bytes, numBytes));
6324             numBytes = 0;
6325             pos.line = editBox.line;
6326             pos.y = editBox.y;
6327             pos.x = editBox.x;
6328             this.start.AdjustAdd(start, pos);
6329             sel.AdjustAdd(start, pos);
6330          }
6331          return true;
6332       }
6333       return false; 
6334    }
6335
6336    bool Getc(char * ch)
6337    {
6338       return Read(ch, 1, 1) ? true : false;
6339    }
6340
6341    void DeleteBytes(uint count)
6342    {
6343       EditBox editBox = this.editBox;
6344       if(count)
6345       {
6346          BufferLocation pos { editBox.line, editBox.y, editBox.x };
6347          BufferLocation end = pos;
6348
6349          uint c = 0;
6350          for(;;)
6351          {
6352             for(;c < count && end.line && end.x < end.line.count;)
6353             {
6354                end.x++;
6355                c++;
6356             }
6357             if(c < count && end.line && end.line.next)
6358             {
6359                end.line = end.line.next;
6360                end.y++;
6361                c++;
6362                end.x = 0;
6363             }
6364             else
6365                break;
6366          }
6367
6368          start.AdjustDelete(pos, end);
6369          sel.AdjustDelete(pos, end);
6370
6371          editBox.DelCh(pos.line, pos.y, pos.x, end.line, end.y, end.x, true);
6372       }
6373    }
6374 };