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