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