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