1 namespace gui::controls;
4 selectionForeground = white;
5 disabled: defaultTextColor = Color { 85, 85, 85 };
14 public class SyntaxColorScheme
18 Color charLiteralColor;
19 Color stringLiteralColor;
20 Color preprocessorColor;
22 private Array<Color> keywordColors { };
24 public property Container<Color> keywordColors
26 set { keywordColors.Copy((void *)value); }
27 get { return keywordColors; }
35 #define XOFFSET (3 + (/*style.lineNumbers?6 * this.space.w:*/0))
37 #define YOFFSET (style.multiLine ? 1 : ((clientSize.h + 1 - space.h) / 2))
39 #define IS_ALUNDER(ch) (/*(ch) == '_' || */CharMatchCategories((ch), letters|numbers|marks|connector))
41 #define UTF8_IS_FIRST(x) (__extension__({ byte b = x; (!(b) || !((b) & 0x80) || ((b) & 0x40)); }))
42 #define UTF8_NUM_BYTES(x) (__extension__({ byte b = x; (b & 0x80 && b & 0x40) ? ((b & 0x20) ? ((b & 0x10) ? 4 : 3) : 2) : 1; }))
43 #define UTF8_GET_CHAR(string, numBytes) \
47 byte b = (string)[0]; \
52 if(b & 0x80 && b & 0x40) \
67 for(i = 0; i<numBytes; i++) \
70 ch |= (string)[i] & mask; \
78 bool autoEmpty:1, readOnly:1, multiLine:1, stuckCaret:1, freeCaret:1, select:1, hScroll:1, vScroll:1, smartHome:1;
79 bool noCaret:1, noSelect:1, tabKey:1, useTab:1, tabSel:1, allCaps:1, syntax:1, wrap:1;
82 bool inMultiLineComment:1, inPrep:1, escaped:1, continuedSingleLineComment:1;
84 bool recomputeSyntax:1;
85 bool cursorFollowsView:1;
87 // bool lineNumbers:1;
92 void UnregisterClass_EditBox()
95 for(g = 0; g<NUM_KEYWORD_GROUPS; g++)
104 extern int __ecereVMethodID_class_OnFree;
107 static class ArrayImpl
114 public class OldArray
121 void ** array = (void **)((ArrayImpl)this).array;
122 if(type.type == normalClass || type.type == noHeadClass)
124 for(c = 0; c<size; c++)
125 ((void (*)(void *, void *))(void *)type._vTbl[__ecereVMethodID_class_OnFree])(type, array[c]);
127 // TODO: Call OnFree for structClass
128 delete ((ArrayImpl)this).array;
137 if(((ArrayImpl)this).array)
141 ((ArrayImpl)this).array = renew0 ((ArrayImpl)this).array byte[type.typeSize * value];
144 ((ArrayImpl)this).array = new0 byte[value * type.typeSize];
153 memcpy(((ArrayImpl)this).array, value, type.typeSize * size);
158 public class UndoAction : struct
161 subclass(UndoAction) type;
162 virtual void Undo(void * data) { type.Undo(this, data); }
163 virtual void Redo(void * data) { type.Redo(this, data); }
165 virtual void Print(void * data) { type.Print(this, data); }
169 if(((Class)type).Destructor)
170 ((void (*)(void *))((Class)type).Destructor)(this);
174 public class UndoBuffer
176 Array<UndoAction> actions { size = 8 };
196 UndoAction action = actions[--curAction];
198 /*Print("Undoing: ");
199 action.Print(data);*/
210 if(curAction < count)
212 UndoAction action = actions[curAction];
215 /*Print("Redoing: ");
216 action.Print(data);*/
224 void Record(UndoAction action)
226 if(!dontRecord && !insideRedo)
228 if(curAction < count)
231 for(c = curAction; c < count; c++)
237 if(count >= actions.size)
238 actions.size += actions.size / 2;
241 /*Print("Recording: ");
242 action.Print(data);*/
244 actions[count++] = action;
247 if(actions.size > count + count / 2 && count + count / 2 >= 8)
248 actions.size = count + count / 2;
255 static class AddCharAction : UndoAction
259 int addedSpaces, addedTabs;
260 type = class(AddCharAction);
262 void Undo(EditBox editBox)
264 editBox.GoToPosition(null, (ch == '\n') ? (y + 1) : y, (ch == '\n') ? 0 : (x + 1));
266 if(addedTabs || addedSpaces)
267 editBox.DelCh(editBox.line, y, x - (addedSpaces + addedTabs), editBox.line, y, x, false);
268 editBox.UpdateDirty();
271 void Redo(EditBox editBox)
273 editBox.GoToPosition(null, y, x);
275 editBox.UpdateDirty();
278 void Print(EditBox editBox)
280 PrintLn("AddChar: y = ", y, "x = ", x, ", ch = ", ch, ", addedSpaces = ", addedSpaces, ", addedTabs = ", addedTabs);
285 static class AddTextAction : UndoAction
289 int addedSpaces, addedTabs;
290 type = class(AddTextAction);
293 void Print(EditBox editBox)
295 PrintLn("AddText: y1 = ", y1, "x1 = ", x1, ", y2 = ", y2, ", x2 = ", x2, ", string = ", string, ", addedSpaces = ", addedSpaces, ", addedTabs = ", addedTabs);
303 void Undo(EditBox editBox)
308 editBox.GoToPosition(null, y1, x1);
310 l2 = editBox.lines.first;
311 for(c = 0; c < y2 && l2; c++, l2 = l2.next);
313 editBox.DelCh(l1, y1, x1, l2, y2, x2, true);
314 if(addedTabs || addedSpaces)
315 editBox.DelCh(editBox.line, y1, x1 - (addedSpaces + addedTabs), editBox.line, y1, x1, false);
317 editBox.SetViewToCursor(true);
321 void Redo(EditBox editBox)
323 editBox.GoToPosition(null, y1, x1);
324 editBox.PutS(string);
328 static class DelTextAction : UndoAction
334 type = class(DelTextAction);
337 void Print(EditBox editBox)
339 PrintLn("DelText: y1 = ", y1, "x1 = ", x1, ", y2 = ", y2, ", x2 = ", x2, ", string = ", string, ", addedSpaces = ", addedSpaces, ", placeAfter = ", placeAfter);
342 void Undo(EditBox editBox)
344 editBox.GoToPosition(null, y1, x1);
345 editBox.PutS(string);
349 editBox.GoToPosition(null, y1, x1);
352 { int c; editBox.selLine = editBox.lines.first; for(c = 0; c < editBox.selY && editBox.selLine; c++, editBox.selLine = editBox.selLine.next); }
353 //editBox.SetViewToCursor(true);
356 editBox.DelCh(editBox.line, y1, x1 - addedSpaces, editBox.line, y1, x1, false);
362 { int c; editBox.selLine = editBox.lines.first; for(c = 0; c < editBox.selY && editBox.selLine; c++, editBox.selLine = editBox.selLine.next); }
363 //editBox.SetViewToCursor(true);
366 editBox.DelCh(editBox.selLine, y1, x1 - addedSpaces, editBox.selLine, y1, x1, false);
370 void Redo(EditBox editBox)
375 editBox.GoToPosition(null, y1, x1);
378 l2 = editBox.lines.first;
379 for(c = 0; c < y2 && l2; c++, l2 = l2.next);
381 editBox.DelCh(l1, y1, x1, l2, y2, x2, true);
382 editBox.SetViewToCursor(true);
392 static class ReplaceTextAction : UndoAction
394 int y1, x1, y2, x2, y3, x3;
398 int addedSpaces, addedTabs;
400 type = class(ReplaceTextAction);
403 void Print(EditBox editBox)
405 PrintLn("ReplaceText: y1 = ", y1, "x1 = ", x1, ", y2 = ", y2, ", x2 = ", x2, ", y3 = ", y3, ", x3 = ", x3, ", oldString = ", oldString, ", newString = ", newString, ", addedSpaces = ", addedSpaces, ", addedTabs = ", addedTabs, ", placeAfter = ", placeAfter);
408 void Undo(EditBox editBox)
413 editBox.GoToPosition(null, y1, x1);
415 l3 = editBox.lines.first;
416 for(c = 0; c < y3 && l3; c++, l3 = l3.next);
418 editBox.DelCh(l1, y1, x1, l3, y3, x3, true);
420 editBox.PutS(oldString);
421 if(addedSpaces || addedTabs)
422 editBox.DelCh(l1, y1, x1 - (addedSpaces + addedTabs), l1, y1, x1, false);
424 //editBox.PutS(oldString);
427 editBox.GoToPosition(null, y1, x1);
430 { int c; editBox.selLine = editBox.lines.first; for(c = 0; c < editBox.selY && editBox.selLine; c++, editBox.selLine = editBox.selLine.next); }
436 { int c; editBox.selLine = editBox.lines.first; for(c = 0; c < editBox.selY && editBox.selLine; c++, editBox.selLine = editBox.selLine.next); }
440 void Redo(EditBox editBox)
445 editBox.GoToPosition(null, y1, x1);
447 l2 = editBox.lines.first;
448 for(c = 0; c < y2 && l2; c++, l2 = l2.next);
450 editBox.DelCh(l1, y1, x1, l2, y2, x2, true);
452 editBox.PutS(newString);
462 static class MoveTextAction : UndoAction
464 int fy1, fx1, fy2, fx2;
466 type = class(MoveTextAction);
468 void Undo(EditBox editBox)
473 void Redo(EditBox editBox)
479 public class EditLine : struct
491 // Only works for single line edit for now...
492 EditBox editBox = this.editBox;
496 get { return this ? buffer : null; }
498 property EditLine prev { get { return this ? prev : null; } };
499 property EditLine next { get { return this ? next : null; } };
500 property int count { get { return this ? count : 0; } };
507 // This makes sure the buffer always contains at least count characters
508 // Keeps a count/2 pad for avoiding always reallocating memory.
509 bool AdjustBuffer(int count)
517 newSize = (count + (count >> 1));
519 // Shrink down the buffer
522 buffer = new char[newSize];
523 if(!buffer) return false;
527 CopyBytes(buffer, this.buffer, count);
530 this.buffer = buffer;
534 // Increase the buffer
535 else if(size < count)
537 buffer = new char[newSize];
538 if(!buffer) return false;
542 CopyBytes(buffer, this.buffer, this.count + 1); // size);
545 this.buffer = buffer;
554 public struct BufferLocation
559 void AdjustDelete(BufferLocation start, BufferLocation end)
561 // Location is before, nothing to do
562 if(y < start.y || (y == start.y && x < start.x))
564 // Location is inside deleted bytes, point to the start
565 if((y >= start.y && (y > start.y || x >= start.x)) &&
566 (y >= end.y && (y > end.y || x >= end.x)))
571 // Location is on another line
573 y -= end.y - start.y;
574 // Location is the last touched line
581 //if(start.line == end.line)
582 x -= end.x - start.x;
591 // Assuming no carriage return before first character ???? fixed?
592 void AdjustAdd(BufferLocation start, BufferLocation end)
594 int numLines = end.y - start.y;
601 if(x > start.x || (x == start.x /*&& (numLines ? true : false)*/))
604 for(c = 0, line = start.line; c<numLines; c++)
607 //x += numLines ? end.x : (end.x - start.x);
608 x += end.x - start.x;
615 public enum EditBoxFindResult { notFound, found, wrapped };
617 static char * keyWords1[] =
620 "return","break","continue","default","switch","case","if","else","for","while", "do","long","short",
621 "void", "char","int","float","double","unsigned","static", "extern", "struct", "union", "typedef","enum",
623 "#include", "#define", "#pragma", "#if", "#else", "#elif", "#ifdef", "#ifndef", "#endif", "#undef", "#line",
624 "__attribute__", "__stdcall", "_stdcall",
625 "__declspec", "goto",
626 "inline", "__inline__", "_inline", "__inline", "__typeof","__extension__",
627 "asm", "__asm", "_asm", "volatile", "#cpu", "__stdcall__",
630 "class", "private", "public",
632 "delete", "new", "new0", "renew", "renew0", "define",
635 "dllexport", "dllimport", "stdcall",
636 "subclass", "__on_register_module", "namespace", "using",
637 "typed_object", "any_object", "incref", "register", "watch", "stopwatching", "firewatchers", "watchable", "class_designer",
638 "class_fixed", "class_no_expansion", "isset", "class_default_property", "property_category", "class_data", "class_property",
639 "virtual", "thisclass","unichar", "dbtable", "dbindex", "database_open", "dbfield",
642 "uint", "uint32", "uint16", "uint64", "bool", "byte", "int64", "uintptr", "intptr", "intsize", "uintsize",
645 "this", "true", "false", "null", "value",
654 static char * keyWords2[] =
656 "defined", "warning", null
659 static char ** keyWords[] = { keyWords1, keyWords2 };
660 #define NUM_KEYWORD_GROUPS (sizeof(keyWords) / sizeof(char **))
661 //static int * keyLen[NUM_KEYWORD_GROUPS];
662 static int keyLen[NUM_KEYWORD_GROUPS][sizeof(keyWords1)];
664 static char searchString[1025], replaceString[1025];
665 static bool matchCase = false, wholeWord = false, searchUp = false;
667 static GoToDialog goToDialog
669 autoCreate = false, isModal = true, text = $"Go To"
672 public class EditBox : CommonControl
674 class_property(icon) = "<:ecere>controls/editBox.png";
678 virtual bool Window::NotifyModified(EditBox editBox);
679 virtual void Window::NotifyCaretMove(EditBox editBox, int line, int charPos);
680 virtual void Window::NotifyUpdate(EditBox editBox);
681 virtual bool Window::NotifyDoubleClick(EditBox editBox, EditLine line, Modifiers mods);
682 virtual void Window::NotifyOvrToggle(EditBox editBox, bool overwrite);
683 virtual bool Window::NotifyKeyDown(EditBox editBox, Key key, unichar ch);
685 virtual bool Window::NotifyCharsAdded(EditBox editBox, BufferLocation before, BufferLocation after, bool pasteOperation);
686 virtual bool Window::NotifyCharsDeleted(EditBox editBox, BufferLocation beforeLoc, BufferLocation after, bool pasteOperation);
687 virtual bool Window::NotifyDropped(EditBox editBox, int x, int y);
689 virtual bool Window::NotifyUnsetModified(EditBox editBox);
691 // Why was this commented out?
692 // It is required otherwise updating font property from property sheet doesn't immediately reflect in form designer,
693 // and the scrollArea property isn't compared properly either.
697 if(font) ComputeFont();
698 SetInitSize(initSize);
704 style.vScroll = true;
710 style.hScroll = true;
714 property bool textHorzScroll { property_category $"Behavior" set { style.hScroll = value; } get { return style.hScroll; } }; // Should cut the text on set to false
715 property bool textVertScroll { property_category $"Behavior" set { style.vScroll = value; } get { return style.vScroll; } };
716 property bool readOnly
718 property_category $"Behavior"
721 style.readOnly = value;
722 itemEditCut.disabled = value || !selection;
723 itemEditDelete.disabled = value || !selection;
724 itemEditPaste.disabled = value;
726 get { return style.readOnly; }
728 property bool multiLine { property_category $"Behavior" set { style.multiLine = value; } get { return style.multiLine; } };
729 property bool freeCaret { property_category $"Behavior" set { style.freeCaret = value; } get { return style.freeCaret; } };
730 property bool tabKey { property_category $"Behavior" set { style.tabKey = value; } get { return style.tabKey; } };
731 property int tabSize { property_category $"Behavior" set { tabSize = value; } get { return tabSize; } };
732 property bool tabSelection { property_category $"Behavior" set { style.tabSel = value; if(value) style.tabKey = true; } get { return style.tabSel; } };
733 property bool smartHome { property_category $"Behavior" set { style.smartHome = value; } get { return style.smartHome; } };
734 property bool autoEmpty { property_category $"Behavior" set { style.autoEmpty = value; } get { return style.autoEmpty; } };
735 property bool noCaret { property_category $"Behavior" set { style.noCaret = value; if(value) { style.readOnly = true; style.stuckCaret = true; } } get { return style.noCaret; } };
736 property int maxLineSize { property_category $"Behavior" set { maxLineSize = value; } get { return maxLineSize; } };
737 property int maxNumLines { property_category $"Behavior" set { maxLines = value; } get { return maxLines; } };
738 property bool useTab { property_category $"Behavior" set { style.useTab = value; itemEditInsertTab.checked = value; } get { return style.useTab; } };
739 property bool syntaxHighlighting { property_category $"Appearance" set { style.syntax = value; } get { return style.syntax; } };
740 property bool noSelect { property_category $"Behavior" set { style.noSelect = value; } get { return style.noSelect; } };
741 property bool allCaps { property_category $"Behavior" set { style.allCaps = value; } get { return style.allCaps; } };
742 property bool autoSize { property_category $"Behavior" set { style.autoSize = value; } get { return style.autoSize; } };
743 property bool wrap { set { style.wrap = value; Update(null); } get { return style.wrap; } };
744 //property bool lineNumbers { set { style.lineNumbers = value; } get { return style.lineNumbers; } };
745 property int numLines { get { return this ? lineCount : 0; } };
746 property int lineNumber { get { return y; } }; // TODO: Change to property of EditLine this.line.number
747 property int column { get { return col; } }; // TODO: Add Set
748 property int charPos { get { return x; } }; // TODO: Add Set
749 property EditLine firstLine { get { return lines.first; } }; // Change these to a List<EditLine>... (this.lines[10].text)
750 property EditLine lastLine { get { return lines.last; } };
751 property EditLine line { get { return this.line; } }; // TODO: Add Set this.line = this.lines[10]
752 property char * contents
754 property_category $"Data"
760 DelCh(this.lines.first, 0, 0, this.lines.last, this.lineCount-1, ((EditLine)(this.lines.last)).count, true);
763 //SetViewToCursor(true);
771 char * buffer = null;
774 /* Can't implement this right now because of memory leak... Need string reference counting...
781 for(line = lines.first; line; line = line.next)
782 len += strlen(line.buffer);
784 buffer = new char[len+1];
786 for(line = lines.first; line; line = line.next)
788 int lineLen = strlen(line.buffer);
789 memcpy(buffer + len, line.buffer, lineLen);
795 buffer = this.line ? this.line.buffer : null;
800 property bool overwrite { get { return overwrite; } };
801 property bool caretFollowsScrolling { get { return style.cursorFollowsView; } set { style.cursorFollowsView = value; } }
803 property char * multiLineContents
807 char * buffer = null;
814 for(line = lines.first; line; line = line.next)
815 len += strlen(line.buffer)+1;
817 buffer = new char[len+1];
819 for(line = lines.first; line; line = line.next)
821 int lineLen = strlen(line.buffer);
822 memcpy(buffer + len, line.buffer, lineLen);
824 if(line.next) buffer[len++] = '\n';
837 return this.line.buffer;
842 void SetLineText(char * text)
846 EditLine_SetText(this.line, text);
850 property Color selectionColor { set { selectionColor = value; } get { return selectionColor; } isset { return selectionColor ? true : false; } };
851 property Color selectionText { set { selectionText = value; } get { return selectionText; } isset { return selectionText ? true : false; } };
852 property SyntaxColorScheme syntaxColorScheme { set { delete colorScheme; colorScheme = value; incref colorScheme; } }
854 // selectionStart.line, selectionStart.column (With Set)
855 // selection.line1, selection.line2, selection.column1, selection.column2 (Read only)
869 // Position of Caret (Not necessarily displayed position)
872 // Position of beginning of block (Block ends at (x,y))
874 // line is line at carret, selLine is line at beginning of block
875 EditLine line, selLine, dropLine;
876 // Mouse selection Moving virtual caret
881 // ViewX is x offset in pixels, ViewY is y offset in lines
883 // viewLine is first displayed line
886 // start and end of area to redraw
889 // MaxLine is the longest line
891 // MaxLength is the longest line's length
894 // MouseSelect is true once button is pressed, overwrite is true if mode is on
895 bool mouseSelect, mouseMove, overwrite, wordSelect;
896 // Timer is used for mouse selection scrolling
899 window = this, delay = 0.1;
908 OnMouseMove(mouseX, mouseY, -1);
913 // (mouseX,mouseY) is the position of the mouse in the edit box
918 void (* FontExtent)(Display display, Font font, char * text, int len, int * width, int * height);
921 bool rightButtonDown;
924 UndoBuffer undoBuffer { data = this };
926 Color selectionColor, selectionText;
927 SyntaxColorScheme colorScheme { };
932 Menu editMenu { menu, $"Edit", e };
935 editMenu, $"Cut\tCtrl+X", t, disabled = true;
937 bool NotifySelect(MenuItem item, Modifiers mods)
939 if(!(style.readOnly))
944 MenuItem itemEditCopy
946 editMenu, $"Copy\tCtrl+C", c, disabled = true;
948 bool NotifySelect(MenuItem item, Modifiers mods)
954 MenuItem itemEditPaste
956 editMenu, $"Paste\tCtrl+V", p;
958 bool NotifySelect(MenuItem item, Modifiers mods)
960 if(!(style.readOnly))
965 MenuItem itemEditDelete
967 editMenu, $"Delete\tDel", d, disabled = true;
969 bool NotifySelect(MenuItem item, Modifiers mods)
971 if(!(style.readOnly))
976 MenuDivider { editMenu };
977 MenuItem itemEditSelectAll
979 editMenu, $"Select All\tCtrl+A", a;
981 bool NotifySelect(MenuItem item, Modifiers mods)
987 MenuDivider { editMenu };
988 MenuItem itemEditUndo
990 editMenu, $"Undo\tCtrl+Z", u;
993 bool NotifySelect(MenuItem item, Modifiers mods)
999 MenuItem itemEditRedo
1001 editMenu, $"Redo\tCtrl+Y", o;
1004 bool NotifySelect(MenuItem item, Modifiers mods)
1010 MenuDivider { editMenu };
1013 editMenu, $"Find Previous\tShift-F3", e, shiftF3;
1015 bool NotifySelect(MenuItem item, Modifiers mods)
1018 Find(searchString, wholeWord, matchCase, false);
1020 itemEditFind.NotifySelect(this, item, mods);
1026 editMenu, $"Find Next\tF3", n, f3;
1028 bool NotifySelect(MenuItem item, Modifiers mods)
1031 Find(searchString, wholeWord, matchCase, true);
1033 itemEditFind.NotifySelect(this, item, mods);
1037 MenuItem itemEditFind
1039 editMenu, $"Find...\tCtrl+F", f, ctrlF;
1041 bool NotifySelect(MenuItem item, Modifiers mods)
1045 editBox = this, master = master, isModal = true, searchString = searchString, matchCase = matchCase, wholeWord = wholeWord,
1046 searchUp = searchUp;
1049 // Fix dialog from above shouldn't be visible
1050 // void NotifyDestroyed(FindDialog dialog, DialogResult result)
1051 void NotifyDestroyed(Window window, DialogResult result)
1053 FindDialog dialog = (FindDialog) window;
1054 searchUp = dialog.searchUp;
1055 strcpy(searchString, dialog.searchString);
1056 matchCase = dialog.matchCase;
1057 wholeWord = dialog.wholeWord;
1066 editMenu, $"Replace...\tCtrl+R", r, ctrlR;
1068 bool NotifySelect(MenuItem item, Modifiers mods)
1070 ReplaceDialog dialog
1074 searchString = searchString,
1075 replaceString = replaceString,
1076 matchCase = matchCase,
1077 wholeWord = wholeWord,
1081 // void NotifyDestroyed(ReplaceDialog dialog, DialogResult result)
1082 void NotifyDestroyed(Window window, DialogResult result)
1084 ReplaceDialog dialog = (ReplaceDialog)window;
1085 char * replace = dialog.replaceString;
1087 strcpy(replaceString, replace);
1088 strcpy(searchString, dialog.searchString);
1089 matchCase = dialog.matchCase;
1090 wholeWord = dialog.wholeWord;
1097 MenuDivider { editMenu };
1100 editMenu, $"Go To...\tCtrl+G", g, ctrlG;
1102 bool NotifySelect(MenuItem item, Modifiers mods)
1104 goToDialog.editBox = this;
1105 goToDialog.master = master;
1106 goToDialog.Create();
1110 MenuDivider { editMenu };
1111 MenuItem itemEditInsertTab
1113 editMenu, $"Insert Tabs", i, checkable = true;
1115 bool NotifySelect(MenuItem item, Modifiers mods)
1117 style.useTab = item.checked;
1123 snapVertScroll = true;
1124 snapHorzScroll = true;
1128 static bool syntaxInit = false;
1133 for(g = 0; g<NUM_KEYWORD_GROUPS; g++)
1135 for(c = 0; keyWords[g][c]; c++);
1136 //keyLen[g] = new int[c];
1137 for(c = 0; keyWords[g][c]; c++)
1139 keyLen[g][c] = strlen(keyWords[g][c]);
1143 colorScheme.commentColor = dimGray;
1144 colorScheme.charLiteralColor = crimson;
1145 colorScheme.stringLiteralColor = crimson;
1146 colorScheme.preprocessorColor = green;
1147 colorScheme.numberColor = teal;
1148 colorScheme.keywordColors = [ blue, blue ];
1151 FontExtent = Display::FontExtent;
1153 lines.offset = (uint)&((EditLine)0).prev;
1155 style = EditBoxBits { hScroll = true };
1157 // cursor = guiApp.GetCursor(IBeam);
1159 // Default Properties
1161 maxLineSize = 1024;*/
1164 maxLineSize = MAXINT;
1169 mouseSelect = this.mouseMove = false;
1172 x = selX = selY = 0;
1175 line = selLine = null;
1181 endY = clientSize.h;
1184 // Default space to 8 in case window is not constructed
1187 undoBuffer.dontRecord++;
1189 undoBuffer.dontRecord--;
1191 viewLine = lines.first;
1192 style.recomputeSyntax = true;
1198 UpdateCaretPosition(true);
1204 lines.Free(EditLine::Free);
1207 void FlushBuffer(Surface surface, EditLine line, int wc, int * renderStart, int * x, int y, int numSpaces, Box box)
1209 int count = wc - *renderStart;
1212 if(y + space.h >= box.top && y <= box.bottom)
1218 //FontExtent(display, font, line.buffer + *renderStart, count, &w, null);
1219 surface.TextFont(font);
1220 surface.TextExtent(line.buffer + *renderStart, count, &w, null);
1221 if(*x + w + XOFFSET > 0)
1222 surface.WriteText(XOFFSET + *x,y, line.buffer + *renderStart, count);
1227 w = numSpaces; // * space.w;
1228 if(*x + w + XOFFSET > 0 && surface.GetTextOpacity())
1229 surface.Area(XOFFSET + *x - 1, y, XOFFSET + *x + w, y + space.h-1);
1230 // WHATS UP WITH THIS... surface.Area(XOFFSET + *x, y, XOFFSET + *x + w, y + space.h-1);
1238 int CheckColors(EditLine line, int wc, bool selection, int selX, int editX, bool *selected,
1239 Color selectionForeground, Color selectionBackground, Color textColor, Color *foreground, Color *background, bool *opacity, bool *overwrite)
1245 if((wc == selX && line == selLine) || (wc == editX && line == this.line))
1249 *foreground = (*selected) ? selectionForeground : textColor;
1250 *background = selectionBackground;
1251 *opacity = *selected;
1258 if(this.overwrite && active)
1260 if((style.stuckCaret && wc == line.count && !line.next) ||
1261 (!mouseMove && line == this.line && wc == editX))
1270 void FigureStartSyntaxStates(EditLine firstLine, bool reset)
1274 bool inMultiLineComment = reset ? false : style.inMultiLineComment;
1275 bool inString = false;
1276 bool inQuotes = false;
1277 bool inPrep = reset ? false : style.inPrep;
1278 bool inSingleLineComment = false;
1279 bool escaped = reset ? false : style.escaped;
1280 bool continuedSingleLineComment = reset ? false : style.continuedSingleLineComment;
1282 EditLine line = reset ? lines.first : firstLine;
1283 // int maxBackUp = 1000, c;
1284 // for(c = 0, line = viewLine; c < maxBackUp && line && line.prev; line = line.prev);
1285 for(; line != viewLine; line = line.next)
1287 char * text = line.buffer;
1290 bool lastWasStar = false;
1291 bool firstWord = true;
1292 if(!escaped) inPrep = false;
1293 inSingleLineComment = continuedSingleLineComment;
1299 for(c = 0; (ch = text[c]); c++)
1301 bool wasEscaped = escaped;
1302 bool backLastWasStar = lastWasStar;
1304 lastWasStar = false;
1307 if(!inSingleLineComment && !inMultiLineComment && !inQuotes && !inString)
1309 if(text[c+1] == '/')
1311 inSingleLineComment = true;
1313 else if(text[c+1] == '*')
1315 inMultiLineComment = true;
1318 else if(backLastWasStar)
1319 inMultiLineComment = false;
1323 if(!c || text[c-1] != '/') lastWasStar = true;
1325 else if(ch == '\"' && !inSingleLineComment && !inMultiLineComment && !inQuotes)
1327 if(inString && !wasEscaped)
1336 else if(ch == '\'' && !inSingleLineComment && !inMultiLineComment && !inString)
1338 if(inQuotes && !wasEscaped)
1350 else if(ch == '#' && !inQuotes && !inString && !inMultiLineComment && !inSingleLineComment)
1357 else if(ch != ' ' && ch != '\t')
1360 continuedSingleLineComment = inSingleLineComment && (line.count && line.text[line.count - 1] == '\\');
1363 style.continuedSingleLineComment = continuedSingleLineComment;
1364 style.inMultiLineComment = inMultiLineComment;
1365 style.inPrep = inPrep;
1366 style.escaped = escaped;
1370 /*void OnDrawOverChildren(Surface surface)
1372 if(style.lineNumbers)
1374 int currentLineNumber = this.viewY + 1;
1377 for(i = 0; i * space.h < box.height; i++)
1379 // ********* LINE NUMBERING *********
1380 surface.SetForeground(Color{60, 60, 60});
1381 //Highlight current line
1382 if(this.caretY / space.h == currentLineNumber - 1)
1383 surface.SetBackground(Color{220, 220, 220});
1385 surface.SetBackground(Color{230, 230, 230});
1386 surface.textOpacity = true;
1388 sprintf(lineText,"%5u ", currentLineNumber % 100000);
1389 if(currentLineNumber > this.lineCount)
1390 surface.WriteText(0,i*space.h+1," ",6);
1392 surface.WriteText(0,i*space.h+1,lineText,6);
1394 currentLineNumber++;
1399 void OnRedraw(Surface surface)
1403 bool selected = false, selection = true;
1405 Color selectionBackground = selectionColor ? selectionColor : SELECTION_COLOR;
1406 Color selectionForeground = selectionText ? selectionText : SELECTION_TEXT;
1407 Color defaultTextColor = property::foreground;
1410 int maxW = clientSize.w;
1412 Color foreground, background;
1415 // Overwrite Caret Stuff
1416 bool overWrite = false;
1417 int overWriteX, overWriteY;
1420 // ****** SYNTAX STATES ******
1421 bool inMultiLineComment = style.inMultiLineComment;
1422 bool inString = false;
1423 bool inQuotes = false;
1424 bool inPrep = style.inPrep;
1425 bool inSingleLineComment = false;
1426 bool escaped = style.escaped;
1427 bool continuedSingleLineComment = style.continuedSingleLineComment;
1428 // ****** ************* ******
1431 defaultTextColor = Color { 85, 85, 85 };
1432 textColor = defaultTextColor;
1435 Abs(selectionBackground.r - property::background.r) +
1436 Abs(selectionBackground.g - property::background.g) +
1437 Abs(selectionBackground.b - property::background.b) < 92)
1439 selectionBackground = formColor;
1440 selectionForeground = selectionColor ? selectionColor : SELECTION_COLOR;
1443 surface.TextFont(this.font);
1445 surface.SetBackground(GDefaultPalette()[RND_Get(1, 15)]);
1446 surface.Area(0,0,MAXINT,MAXINT);
1451 surface.SetBackground(formColor);
1452 surface.Area(0,0,clientSize.w, clientSize.h);
1455 if(this.selX == this.x && this.selY == this.y)
1465 editX = Min(this.x,this.line.count);
1466 selX = Min(this.selX,this.selLine.count);
1469 selected = (this.selY < this.viewY) ^ (this.y < this.viewY);
1471 foreground = selected ? selectionForeground : textColor;
1472 background = selectionBackground;
1478 surface.SetForeground(foreground);
1479 surface.SetBackground(background);
1480 surface.TextOpacity(opacity);
1482 surface.GetBox(box);
1484 for(line = this.viewLine; line; line = line.next)
1486 int x = -this.viewX;
1490 Color newTextColor = textColor = defaultTextColor;
1491 bool lineComplete = false;
1494 // ****** SYNTAX HIGHLIGHTING ******
1495 bool lastWasStar = false;
1496 bool firstWord = true;
1497 if(!escaped) inPrep = false;
1498 inSingleLineComment = continuedSingleLineComment;
1502 // *********************************
1504 /* === DEBUGGING TOOL FOR MAXLINE ===
1506 if(line == this.maxLine)
1508 surface.SetBackground(GREEN|0xFF000000);
1509 surface.TextOpacity(true);
1513 surface.TextOpacity(selected ? true : false);
1514 surface.SetBackground(selected ? SELECTION_COLOR|0xFF000000 : BLACK|0xFF000000);
1518 if(line == this.selLine && line == this.line)
1520 end = Max(line.count, this.x);
1521 end = Max(end, this.selX);
1523 else if(line == this.selLine)
1524 end = Max(line.count, this.selX);
1525 else if(line == this.line)
1526 end = Max(line.count, this.x);
1534 bool spacing = true;
1535 bool cantHaveWords = false;
1542 lineComplete = false;
1545 textColor = newTextColor;
1548 foreground = textColor;
1549 surface.SetForeground(textColor);
1553 for(; c<end && !cantHaveWords;)
1557 bufferLen += wordLen;
1561 /*if(line.count > 4000)
1568 for(; c<line.count; c++)
1570 unichar ch = line.buffer[c];
1571 unichar bf = (wordLen == 1) ? line.buffer[c-1] : 0;
1572 //if(ch == ' ' || ch == '\t' || (wordLen && (ch == '(' || ch == ')' || ch == ';' || ch == ':')) || (wordLen == 1 && line.buffer[c-1] == '('))
1573 if(CharMatchCategories(ch, separators) || /*ch == ' ' ||*/ ch == '\t' ||
1574 (wordLen && !CharMatchCategories(ch, numbers|letters|marks|connector) && ch != '#' /*&& ch != '_'*/) ||
1575 (bf && !CharMatchCategories(bf, numbers|letters|separators|marks|connector) && bf != '#' && bf != '\t' /*&& bf != '_' && bf != ' '*/))
1583 for(; c<line.count; c++)
1585 unichar ch = line.buffer[c];
1586 if(ch == '\t' || ch == ' ')
1588 cantHaveWords = true;
1592 if(ch != ' ' && ch != '\t')
1597 if(c == line.count && c < end)
1606 cantHaveWords = true;
1611 lastWasStar = false;
1617 bool backEscaped = escaped;
1618 bool backLastWasStar = lastWasStar;
1619 bool backInMultiLineComment = inMultiLineComment;
1620 bool backInString = inString;
1621 bool backInQuotes = inQuotes;
1622 bool backInPrep = inPrep;
1623 bool backInSingleLineComment = inSingleLineComment;
1625 char * word = line.buffer + c - wordLen;
1627 bool wasEscaped = escaped;
1629 lastWasStar = false;
1631 // Determine Syntax Highlighting
1632 newTextColor = defaultTextColor;
1635 if(inSingleLineComment || inMultiLineComment)
1637 newTextColor = colorScheme.commentColor;
1641 newTextColor = colorScheme.charLiteralColor;
1645 newTextColor = colorScheme.stringLiteralColor;
1649 newTextColor = colorScheme.preprocessorColor;
1651 if(wordLen == 1 && word[0] == '/')
1653 if(!inSingleLineComment && !inMultiLineComment && !inQuotes && !inString)
1657 inSingleLineComment = true;
1658 newTextColor = colorScheme.commentColor;
1660 else if(word[1] == '*')
1662 inMultiLineComment = true;
1663 newTextColor = colorScheme.commentColor;
1666 else if(backLastWasStar)
1667 inMultiLineComment = false;
1669 else if(wordLen == 1 && word[0] == '*')
1671 if(c < 2 || word[-1] != '/')
1674 else if(!inSingleLineComment && !inMultiLineComment && !inQuotes && wordLen == 1 && word[0] == '\"')
1676 if(inString && !wasEscaped)
1683 newTextColor = colorScheme.stringLiteralColor;
1686 else if(!inSingleLineComment && !inMultiLineComment && !inString && wordLen == 1 && word[0] == '\'')
1688 if(inQuotes && !wasEscaped)
1693 newTextColor = colorScheme.charLiteralColor;
1696 else if(wordLen == 1 && word[0] == '\\')
1701 else if(!inQuotes && !inString && !inMultiLineComment && !inSingleLineComment &&
1702 ( ( isdigit(word[0]) /*&& (!c || word[-1] == ' ' || word[-1] == '\t')*/ ) || (word[0] == '.' && isdigit(word[1]))))
1704 newTextColor = colorScheme.numberColor;
1708 if(!inQuotes && !inString && !inMultiLineComment && !inSingleLineComment && word[0] == '#')
1713 newTextColor = colorScheme.preprocessorColor;
1716 if(!inQuotes && !inString && !inMultiLineComment && !inSingleLineComment)
1718 for(g = 0; g < ((inPrep && word[0] != '#') ? 2 : 1); g++)
1720 char ** keys = keyWords[g];
1721 int * len = keyLen[g];
1722 for(ccc = 0; keys[ccc]; ccc++)
1724 if(len[ccc] == wordLen && !strncmp(keys[ccc], word, wordLen))
1726 newTextColor = colorScheme.keywordColors[g];
1735 // If highlighting for this word is different, break
1736 if(newTextColor != textColor)
1740 // Better solution than going back?
1743 // Reset syntax flags
1744 escaped = backEscaped;
1745 lastWasStar = backLastWasStar;
1746 inMultiLineComment = backInMultiLineComment;
1747 inString = backInString;
1748 inQuotes = backInQuotes;
1749 inPrep = backInPrep;
1750 inSingleLineComment = backInSingleLineComment;
1755 textColor = newTextColor;
1758 foreground = textColor;
1759 surface.SetForeground(textColor);
1766 // If we're not breaking, this can't be rendered as spacing anymore
1769 // Do word wrapping here
1775 FontExtent(display, font, line.buffer + start, bufferLen + wordLen, &tw, null);
1780 w += numSpaces * space.w;
1782 if(x + viewX + w > maxW)
1785 lineComplete = true;
1790 bufferLen += wordLen;
1795 int renderStart = start;
1800 // Render checking if we need to split because of selection or to find where to draw insert caret
1801 for(wc = start; wc < start + bufferLen; wc++)
1803 flush = CheckColors(line, wc, selection, selX, editX, &selected, selectionForeground,
1804 selectionBackground, textColor, &foreground, &background, &opacity, &overWrite);
1805 if(overWrite == true)
1807 overWriteCh = (wc < line.count) ? line.buffer[wc] : ' ';
1808 if(overWriteCh == '\t') overWriteCh = ' ';
1813 FlushBuffer(surface, line, wc, &renderStart, &x, y, numSpaces, box);
1814 if(overWrite == true)
1822 surface.TextOpacity(opacity);
1823 surface.SetBackground(background);
1824 surface.SetForeground(foreground);
1831 if(wc < line.count && line.buffer[wc] == '\t')
1833 numSpaces += (tabSize * space.w) - ((x + numSpaces + viewX) % (tabSize * space.w));
1837 numSpaces += space.w;
1841 FlushBuffer(surface, line, wc, &renderStart, &x, y, numSpaces, box);
1846 if(CheckColors(line, c, selection, selX, editX, &selected, selectionForeground,
1847 selectionBackground, textColor, &foreground, &background, &opacity, &overWrite))
1849 if(overWrite == true)
1856 surface.TextOpacity(opacity);
1857 surface.SetBackground(background);
1858 surface.SetForeground(foreground);
1861 if(style.freeCaret && selected)
1863 surface.SetBackground(selectionBackground);
1864 surface.Area(x + XOFFSET - 1,y,clientSize.w-1,y+this.space.h-1);
1865 // TEST: surface.Area(x + XOFFSET,y,clientSize.w-1,y+this.space.h-1);
1870 if(style.freeCaret && selected)
1872 surface.SetBackground(selectionBackground);
1873 surface.Area(x + XOFFSET - 1,y,clientSize.w-1,y+this.space.h-1);
1877 continuedSingleLineComment = inSingleLineComment && (line.count && line.text[line.count - 1] == '\\');
1880 if(y > box.bottom) // >=clientSize.h)
1886 surface.TextOpacity(true);
1887 surface.SetForeground(black);
1888 surface.SetBackground(Color {255,255,85});
1889 surface.WriteText(XOFFSET + overWriteX,overWriteY, &overWriteCh, 1);
1893 void FixScrollArea()
1895 if(style.hScroll || style.vScroll)
1897 int width = maxLength + XOFFSET;
1898 int height = lineCount * space.h;
1899 if(style.freeCaret && line)
1904 width = Max(line.length + (x - line.count) * space.w, maxLength);
1908 if(selX > selLine.count)
1909 width = Max(selLine.length + (selX - selLine.count) * space.w, maxLength);
1914 SetScrollLineStep(8, space.h);
1915 SetScrollArea(width, height, true);
1919 void ComputeLength(EditLine line)
1926 for(c = 0; c < line.count; )
1935 for(len = 0; c < line.count; c += numBytes)
1937 ch = line.buffer[c];
1938 numBytes = UTF8_NUM_BYTES(ch);
1940 if(ch == ' ' || ch == '\t')
1947 if(!len && ch == ' ')
1952 else if(!len && ch == '\t')
1954 w = (tabSize * space.w) - (x % (tabSize * space.w));
1958 FontExtent(display, font, line.buffer + start, len, &w, null);
1969 if(line.length > this.maxLength)
1971 this.maxLine = line;
1972 this.maxLength = line.length;
1981 this.maxLine = null;
1983 for(line = lines.first; line; line = line.next)
1985 if(line.length > this.maxLength)
1987 this.maxLength = line.length;
1988 this.maxLine = line;
1995 if(this.selY != this.y)
1997 else if(this.selX != this.x) // commented out to erase caret: if(this.selX != this.x)
2001 void ComputeColumn()
2003 // Adjust new edit X position according to tabs
2004 int c, position = 0;
2007 for(c = 0; c<this.line.count && c<this.x; c+= nb)
2009 ch = UTF8_GET_CHAR(this.line.buffer + c, nb);
2010 // TODO: MIGHT WANT TO RETHINK WHAT COLUMN SHOULD BE REGARDING TABS
2012 position += this.tabSize - (position % this.tabSize);
2016 position += this.x - c;
2017 this.col = position;
2020 int DelCh(EditLine l1, int y1, int c1, EditLine l2, int y2, int c2, bool placeAfter)
2022 return _DelCh(l1, y1, c1, l2, y2, c2, placeAfter, null);
2025 bool HasCommentOrEscape(EditLine line)
2027 bool hadComment = strstr(line.buffer, "/*") || strstr(line.buffer, "*/");
2032 for(c = line.count-1; c >= 0; c--)
2034 char ch = line.buffer[c];
2040 else //if(ch != ' ' && ch != '\t')
2047 int _DelCh(EditLine l1, int y1, int c1, EditLine l2, int y2, int c2, bool placeAfter, int * addedSpacesPtr)
2049 EditLine line = l1, next;
2051 int oldCount1, oldCount2;
2052 int y, firstViewY, firstY, firstDropY, firstSelY;
2055 DelTextAction action = null;
2056 bool hadComment = false;
2060 hadComment = HasCommentOrEscape(line);
2062 if(y2 > y1 || c2 > c1)
2064 if(start < l1.count)
2066 while(!UTF8_IS_FIRST(l1.buffer[start]) && start)
2070 oldCount1 = l1.count;
2072 while(c1 < oldCount1)
2074 byte ch = buffer[c1];
2075 if(UTF8_IS_FIRST(ch)) break;
2079 oldCount2 = l2.count;
2081 while(c2 < oldCount2)
2083 byte ch = buffer[c2];
2084 if(UTF8_IS_FIRST(ch)) break;
2089 if(!undoBuffer.dontRecord && (y2 > y1 || c2 > c1))
2094 len = GetText(null, l1, y1, start, l2, y2, c2, false, false);
2095 string = new char[len];
2096 action = DelTextAction { y1 = y1, x1 = start, y2 = y2, x2 = c2, string = string, placeAfter = placeAfter };
2097 GetText(string, l1, y1, start, l2, y2, c2, false, false);
2101 //oldCount1 = l1.count;
2102 //oldCount2 = l2.count;
2105 BufferLocation before = { l1,y1,c1}, after = { l2,y2,c2 };
2106 NotifyCharsDeleted(master, this, &before, &after, this.pasteOperation);
2109 if(c2 > oldCount2) c2 = oldCount2;
2110 if(!(style.freeCaret))
2111 if(c1 > oldCount1) c1 = oldCount1;
2112 newLineCount = c1 + l2.count-c2;
2118 buffer = new char[line.size ? line.size : 1];
2120 buffer = new char[line.size];
2122 CopyBytes(buffer,l2.buffer,oldCount1 + 1/*line.count + 1*//*line.size*/);
2127 if(!line.AdjustBuffer(newLineCount))
2131 /*if(newLineCount > 4000 || newLineCount < 0)
2132 printf("Warning");*/
2134 line.count = newLineCount;
2136 memmove(l1.buffer+c1, buffer+c2,line.count-c1);
2141 action.addedSpaces = c1-oldCount1;
2143 if(action.addedSpaces > action.x1)
2149 if(addedSpacesPtr) *addedSpacesPtr = c1-oldCount1;
2150 FillBytes(l1.buffer+oldCount1,' ',c1-oldCount1);
2152 line.buffer[line.count] = '\0';
2159 this.dropX -= c2-c1;
2160 this.dropX = Max(this.dropX,0);
2162 this.x = Max(this.x,0);
2169 firstViewY = this.viewY;
2171 firstDropY = this.dropY;
2172 firstSelY = this.selY;
2173 for(line = l1;line;line = next, y++)
2181 if(line == this.viewLine)
2183 if(this.viewLine.next)
2185 this.viewLine = this.viewLine.next;
2187 style.recomputeSyntax = true;
2191 this.viewLine = this.viewLine.prev;
2193 style.recomputeSyntax = true;
2196 else if(y < firstViewY)
2198 if(line == this.line)
2202 this.line = this.line.next;
2203 this.x = this.line.count;
2208 this.line = this.line.prev;
2209 this.x = this.line.count;
2216 if(line == this.dropLine)
2218 if(this.dropLine.next)
2220 this.dropLine = this.dropLine.next;
2221 this.dropX = this.dropLine.count;
2225 this.dropLine = this.dropLine.prev;
2226 this.dropX = this.dropLine.count;
2230 else if(y < firstDropY)
2232 if(line == this.selLine)
2234 if(this.selLine.next)
2236 this.selLine = this.selLine.next;
2237 this.selX = this.selLine.count;
2241 this.selLine = this.selLine.prev;
2242 this.selX = this.selLine.count;
2246 else if(y < firstSelY)
2250 if(line == l2) break;
2254 if(style.autoSize) AutoSize();
2255 if(style.syntax && (hadComment || HasCommentOrEscape(this.line)))
2258 style.recomputeSyntax = true;
2263 bool DelSel(int * addedSpacesPtr)
2265 if(this.line != this.selLine || this.x != this.selX)
2267 if(this.selY < this.y)
2269 _DelCh(this.selLine, this.selY, this.selX, this.line, this.y, this.x, true, addedSpacesPtr);
2272 this.line = this.selLine;
2274 else if(this.selY > this.y)
2276 _DelCh(this.line, this.y, this.x, this.selLine, this.selY, this.selX, false, addedSpacesPtr);
2279 this.selLine = this.line;
2281 else if(this.selX < this.x)
2283 _DelCh(this.selLine, this.selY, this.selX, this.line, this.y, this.x, true, addedSpacesPtr);
2286 this.line = this.selLine;
2290 _DelCh(this.line, this.y, this.x, this.selLine, this.selY, this.selX, false, addedSpacesPtr);
2293 this.selLine = this.line;
2301 bool AddToLine(char * stringLine, int count, bool LFComing, int * addedSpacesPtr, int * addedTabsPtr)
2303 bool hadComment = false;
2304 // Add the line here
2305 EditLine line = this.line;
2307 // The purpose of this is solely to lock a max number of characters if no HSCROLLING is present
2308 if(!style.hScroll && created)
2310 int endX = (style.freeCaret) ? this.x : Min(this.x, line.count);
2315 // Lock if no place to display.
2318 else if(endX > this.x)
2319 max = Max(this.x, line.count);
2323 for(x = 0, c = 0; c < max+count; )
2328 if(c < Min(this.x, line.count))
2329 string = line.buffer + c;
2332 else if(c < endX + count)
2333 string = stringLine + c - endX;
2335 string = line.buffer + c - endX - count;
2339 w = (tabSize * space.w) - (x % (tabSize * space.w));
2343 numBytes = UTF8_NUM_BYTES(*string);
2344 FontExtent(display, this.font, string, numBytes, &w, null);
2348 if(x >= clientSize.w)
2353 c += numBytes; // - 1;
2359 int addedSpaces = 0;
2362 // Add blank spaces if EES_FREECARET
2363 if(this.x > line.count)
2373 for(c = 0; c<line.count; c++)
2375 if(this.line.buffer[c] == '\t')
2376 position += this.tabSize - (position % this.tabSize);
2380 wantedPosition = position + (this.x - line.count);
2382 // A tab is too much...
2383 if(position + (this.tabSize - (position % this.tabSize)) > wantedPosition)
2384 addedSpaces = wantedPosition - position;
2389 position += this.tabSize - (position % this.tabSize);
2390 // Add as many tabs as needed
2391 addedTabs += (wantedPosition - position) / this.tabSize;
2392 position += (addedTabs-1) * this.tabSize;
2393 // Finish off with spaces
2394 addedSpaces = wantedPosition - position;
2398 addedSpaces = this.x - line.count;
2401 this.x = Min(this.x, line.count);
2403 if(line.count + count + addedSpaces + addedTabs > this.maxLineSize)
2409 hadComment = HasCommentOrEscape(line);
2411 // Adjust the size of the line
2412 if(!line.AdjustBuffer(line.count+count+addedTabs+addedSpaces))
2416 BufferLocation before = { this.line, this.y, this.x }, after = { this.line, this.y, this.x };
2419 memmove(line.buffer+this.x+count, line.buffer+this.x,line.count-this.x);
2420 CopyBytes(line.buffer + this.x + addedTabs + addedSpaces, stringLine, count);
2423 *addedTabsPtr = addedTabs;
2424 FillBytes(line.buffer+line.count,'\t',addedTabs);
2426 if(addedTabs > 4000 || addedTabs < 0)
2429 line.count += addedTabs;
2435 FillBytes(line.buffer+line.count,' ',addedSpaces);
2437 if(addedSpaces > 4000 || addedSpaces < 0)
2440 line.count += addedSpaces;
2441 if(addedSpacesPtr) *addedSpacesPtr = addedSpaces;
2443 else if(addedSpacesPtr)
2444 *addedSpacesPtr = 0;
2446 if(count > 4000 || count < 0)
2449 line.count += count;
2450 this.x += count + addedTabs + addedSpaces;
2453 this.selLine = this.line;
2455 line.buffer[line.count] = '\0';
2457 ComputeLength(line);
2462 hasComment = HasCommentOrEscape(line);
2463 if(!undoBuffer.insideRedo)
2465 int backDontRecord = undoBuffer.dontRecord;
2466 undoBuffer.dontRecord = 0;
2467 NotifyCharsAdded(master, this, &before, &after, this.pasteOperation);
2468 if(style.autoSize) AutoSize();
2469 undoBuffer.dontRecord = backDontRecord;
2471 if(style.syntax && (hadComment || hasComment || line != this.line))
2473 style.recomputeSyntax = true;
2481 void Emptyline(EditLine line, int y)
2484 DelCh(line, y, 0, line.next, y+1, 0, true);
2486 DelCh(line, y, 0, line, y, line.count, true);
2489 void GoToEnd(bool deselect)
2493 this.line = this.lines.last;
2494 if(this.y != this.lineCount-1)
2496 else if (this.x != this.line.count)
2497 DirtyLine(this.lineCount-1);
2498 this.y = this.lineCount-1;
2499 this.x = this.line.count;
2506 void GoToHome(bool deselect)
2510 this.line = this.lines.first;
2513 else if (this.x !=0)
2514 DirtyLine(this.lineCount-1);
2523 // Returns true if it needs scrolling
2524 bool FindMouse(int px, int py, int * tx, int * ty, EditLine * tline, bool half)
2530 bool needHScroll = false;
2537 line = this.viewLine ? (void *)this.viewLine.prev : null;
2542 line = (void *)this.lines.first;
2547 py = Min(py, clientSize.h);
2549 py = Min(py, this.lineCount);
2551 for(c = 0, line = this.viewLine; (line != (void *)this.lines.last && c<py); line = line.next, c++)
2557 if( (px >= clientSize.w || px < clientSize.w/2) && this.viewX)
2560 px = Min(px,clientSize.w+this.space.w);
2564 *tx = AdjustXPosition(line, px + viewX, half, null, MAXINT, 0);
2567 if(tline) *tline = line;
2570 // Prevent divide by 0 from non valid this.font
2572 return (y < this.viewY) || needHScroll;
2574 return (y < this.viewY || y >= this.viewY + clientSize.h / this.space.h) || needHScroll;
2578 // Minimal Update Management Functions
2582 this.endY = clientSize.h-1;
2583 // ErrorLog("DirtyAll\n");
2586 void DirtyEnd(int y)
2588 if((y - this.viewY)*this.space.h < this.startY)
2589 this.startY = (y - this.viewY)*this.space.h+ YOFFSET;
2590 this.endY = clientSize.h-1;
2591 //ErrorLog("DirtyEnd %d\n", y);
2594 void DirtyLine(int y)
2598 if((y - this.viewY)*this.space.h < this.startY)
2599 this.startY = (y - this.viewY)*this.space.h + YOFFSET;
2600 if((y - this.viewY+1)*this.space.h > this.endY)
2601 this.endY = (y - this.viewY+1)*this.space.h-1 + YOFFSET;
2603 //ErrorLog("DirtyLine %d\n", y);
2610 if(style.recomputeSyntax)
2612 FigureStartSyntaxStates(lines.first, true);
2613 style.recomputeSyntax = false;
2616 if(this.startY > this.endY) return;
2617 if(this.startY <= 0 && this.endY >= clientSize.h-1)
2623 box.right = clientSize.w-1;
2624 box.top = this.startY;
2625 box.bottom = this.endY;
2628 this.startY = clientSize.h;
2632 bool IsMouseOnSelection()
2634 bool mouseOnSelection = false;
2637 int minY = Min(this.selY, this.y);
2638 int maxY = Max(this.selY, this.y);
2639 int minX = Min(this.selX, this.x);
2640 int maxX = Max(this.selX, this.x);
2642 FindMouse(this.mouseX - this.space.w / 2, this.mouseY, &x, &y, null, false);
2644 if(maxX != minX || maxY != minY)
2646 if(y > minY && y < maxY)
2647 mouseOnSelection = true;
2648 else if(y == minY && y == maxY)
2649 mouseOnSelection = (x < maxX && x >= minX);
2653 mouseOnSelection = (x >= this.selX);
2654 else if(y == this.y)
2655 mouseOnSelection = (x >= this.x);
2660 mouseOnSelection = (x < this.selX);
2661 else if(y == this.y)
2662 mouseOnSelection = (x < this.x);
2665 return mouseOnSelection;
2668 void UpdateCaretPosition(bool setCaret)
2672 if(mouseMove || (!overwrite && !style.noCaret))
2674 int max = this.mouseMove ? this.dropX : this.x;
2675 int y = this.mouseMove ? this.dropY : this.y;
2676 EditLine line = this.mouseMove ? this.dropLine : this.line;
2678 if(!(style.freeCaret))
2679 max = Min(max, line.count);
2681 if(FontExtent && display)
2683 for(c = 0; c < max; )
2692 for(len = 0; c < Min(max, line.count); c += numBytes)
2694 ch = line.buffer[c];
2695 numBytes = UTF8_NUM_BYTES(ch);
2697 if(ch == ' ' || ch == '\t')
2704 if(!len && ch == ' ')
2709 else if(!len && ch == '\t')
2711 w = (tabSize * space.w) - (x % (tabSize * space.w));
2715 FontExtent(display, this.font, line.buffer + start, len, &w, null);
2727 caretY = y * this.space.h;
2728 SetCaret(x + XOFFSET-2, y * space.h + YOFFSET, space.h);
2733 NotifyCaretMove(master, this, y + 1, x + 1);
2739 void SelectionEnables()
2741 if((x != selX || y != selY) && !selection)
2745 itemEditCut.disabled = false;
2746 itemEditDelete.disabled = false;
2748 itemEditCopy.disabled = false;
2750 this.selection = true;
2752 else if((x == selX && y == selY) && selection)
2754 itemEditCut.disabled = true;
2755 itemEditCopy.disabled = true;
2756 itemEditDelete.disabled = true;
2758 this.selection = false;
2762 void SetSelectCursor()
2764 if(!inactive || !style.noSelect)
2767 cursor = guiApp.GetCursor(arrow);
2768 else if(this.mouseSelect)
2769 cursor = guiApp.GetCursor(iBeam);
2772 if(IsMouseOnSelection())
2773 cursor = guiApp.GetCursor(arrow);
2775 cursor = guiApp.GetCursor(iBeam);
2780 int AdjustXPosition(EditLine line, int position, bool half, int * px, int max, int sc)
2783 int x = px ? *px : 0;
2790 if(c < Min(max, line.count))
2794 for(len = 0; c < Min(max, line.count); c += numBytes)
2796 ch = line.buffer[c];
2797 numBytes = UTF8_NUM_BYTES(ch);
2799 if(ch == ' ' || ch == '\t')
2806 if(!len && ch == ' ')
2811 else if(!len && ch == '\t')
2813 w = (tabSize * space.w) - (x % (tabSize * space.w));
2817 FontExtent(display, font, line.buffer + start, len, &w, null);
2821 if(style.freeCaret && c < max)
2830 if(x + (((half && len == 1) ? (w / 2) : w)) >= position)
2835 int a = start + len;
2838 while(a > 0 && !UTF8_IS_FIRST(line.buffer[--a]));
2842 FontExtent(display, font, line.buffer + start, a - start, &w, null);
2845 if(position > x + (half ? ((w + lastW) / 2) : lastW)) break;
2849 return Min(this.maxLineSize - 1, start + len);
2855 void SetCursorToViewX()
2857 bool selecting = this.x != selX || y != selY;
2859 // Horizontal Adjustment
2861 int c = AdjustXPosition(line, viewX, false, &x, MAXINT, 0);
2866 c = AdjustXPosition(line, viewX + clientSize.w - 1, false, &x, MAXINT, c);
2878 UpdateCaretPosition(false);
2884 void SetCursorToViewY()
2887 EditLine oldLine = this.line;
2889 bool selecting = this.x != this.selX || this.y != this.selY;
2891 numLines = clientSize.h / this.space.h;
2893 // Vertical Adjustment
2894 if(this.viewY > this.y)
2896 this.y = this.viewY;
2897 this.line = this.viewLine;
2900 if(this.viewY + numLines <= this.y)
2904 this.y = this.viewY-1;
2905 for(c = 0, line = this.viewLine; line && c<numLines; line = line.next, c++)
2912 if(this.line != oldLine)
2914 this.x = AdjustXPosition(this.line, caretX, true, null, MAXINT, 0);
2922 this.selLine = this.line;
2925 UpdateCaretPosition(false);
2932 bool SaveFile(char * fileName)
2934 File f = eFile_Open(fileName, FO_WRITE);
2938 eWindow_SetModified(false);
2939 eInstance_Delete(f);
2946 bool OnActivate(bool active, Window previous, bool * goOnWithActivation, bool direct)
2950 if(!style.multiLine)
2952 x = (active && this.active && !style.readOnly) ? line.count : 0;
2955 SetViewToCursor(true);
2960 if(!active && modified)
2963 if(!NotifyModified(master, this))
2966 *goOnWithActivation = false;
2973 if(timer) timer.Stop();
2975 mouseSelect = false;
2987 // todo: resize width based on largest on-screen-line extent...
2989 display.FontExtent(font, " ", 1, null, &sh);
2993 nh = lineCount * sh + 2;
2994 size.h = nh < minClientSize.h ? minClientSize.h : nh;
3005 int len = line ? strlen(line.text) : 0;
3006 GetDecorationsSize(&dw, &dh);
3007 display.FontExtent(font, " ", 1, null, &sh);
3008 if(len) display.FontExtent(font, line.text, len, &tw, null);
3010 if(nw < minClientSize.w) nw = minClientSize.w;
3012 if(nh < minClientSize.h) nh = minClientSize.h;
3018 bool OnResizing(int *w, int *h)
3024 *w = space.h * 80 / 14;
3028 void OnResize(int w, int h)
3031 if(!hasHorzScroll && !hasVertScroll && viewLine)
3032 SetViewToCursor(true);
3034 //if(!hasHorzScroll && !hasVertScroll && viewLine)
3036 SetViewToCursor(true);
3041 bool OnMiddleButtonDown(int x, int y, Modifiers mods)
3043 if(style.readOnly) return true;
3044 // We really shouldn't be pasting here, Unix middle button paste is for the selection (not the clipboard), good for the terminal
3045 // Middle button already serves as a dragger as well.
3050 bool OnRightButtonDown(int x, int y, Modifiers mods)
3052 this.rightButtonDown = true;
3057 bool OnRightButtonUp(int x, int y, Modifiers mods)
3060 if(!parent.inactive && rightButtonDown)
3063 Menu contextMenu { };
3065 MenuItem { contextMenu, $"Cut\tCtrl+X", t, NotifySelect = itemEditCut.NotifySelect, disabled = !selection || style.readOnly };
3066 MenuItem { contextMenu, $"Copy\tCtrl+C", c, NotifySelect = itemEditCopy.NotifySelect, disabled = !selection };
3067 MenuItem { contextMenu, $"Paste\tCtrl+V", p, NotifySelect = itemEditPaste.NotifySelect, disabled = style.readOnly };
3068 MenuItem { contextMenu, $"Delete\tDel", d, NotifySelect = itemEditDelete.NotifySelect, disabled = !selection || style.readOnly };
3069 MenuDivider { contextMenu };
3070 MenuItem { contextMenu, $"Select All\tCtrl+A", a, NotifySelect = itemEditSelectAll.NotifySelect };
3072 popup = PopupMenu { master = this, menu = contextMenu,
3074 nonClient = true, interim = false, parent = parent,
3075 position = { x + clientStart.x + parent.clientStart.x + position.x, y + cientStart.y + parent.sy + clientStart.y + position.y };
3077 position = { x + clientStart.x + absPosition.x - guiApp.desktop.position.x, y + clientStart.y + absPosition.y - guiApp.desktop.position.y }
3081 rightButtonDown = false;
3085 bool OnLeftButtonDown(int mx, int my, Modifiers mods)
3090 if(style.noSelect) return true;
3092 // Should we have a separate 'selectOnActivate' style?
3093 if(!mods.isActivate || (style.readOnly && style.multiLine))
3099 mouseX = mx - XOFFSET;
3102 FindMouse(mouseX, mouseY, &x, &y, &line, true);
3104 //PrintLn("OnLeftButtonDown: ", x, ", ", y);
3110 else if(IsMouseOnSelection() && !mods.isActivate)
3120 if(!mouseMove && !wordSelect && (!mods.isActivate || style.multiLine))
3122 if(mods.shift && !mods.isActivate)
3137 this.selLine = this.line;
3146 UpdateCaretPosition(true);
3150 bool OnLeftButtonUp(int x, int y, Modifiers mods)
3154 mouseSelect = false;
3165 FindMouse(mouseX, mouseY, &x, &y, &line, true);
3167 //PrintLn("MouseMove: ", x, ", ", y);
3173 mouseMove = IsMouseOnSelection();
3177 int size = SelSize();
3180 char * text = new char[size+1];
3184 GetSel(text, false);
3186 if(Max(selY, this.y) == dropY)
3190 if(this.dropX > this.selX)
3191 moveX = this.x - this.selX;
3195 if(this.dropX > this.x)
3196 moveX = this.selX - this.x;
3200 this.dropX -= moveX;
3201 this.selX = this.x = this.dropX;
3202 this.selY = this.y = this.dropY;
3203 this.selLine = this.line = this.dropLine;
3205 SetViewToCursor(true);
3210 byte * c = ((EditBox)this).multiLineContents, * c2;
3211 int l1 = c ? strlen(c) : 0, l2;
3214 c2 = ((EditBox)this).multiLineContents;
3215 l2 = c2 ? strlen(c2) : 0;
3216 if(l1 != l2 || (l1 && strcmp(c, c2)))
3246 FindMouse(mouseX, mouseY, &x, &y, &line, true);
3248 //PrintLn("Dropped: ", x, ", ", y);
3250 NotifyDropped(master, this, x, y);
3257 bool OnMouseMove(int mx, int my, Modifiers mods)
3263 if(mods != -1 && mods.isSideEffect)
3268 if(style.noSelect) return true;
3269 if(wordSelect) return true;
3270 mouseX = mx - XOFFSET;
3273 needScroll = FindMouse(this.mouseX, this.mouseY, &x, &y, &line, true);
3275 if(this.mouseMove || this.mouseSelect)
3284 ((style.hScroll) || (style.vScroll)))
3291 DirtyLine(this.dropY);
3294 DirtyLine(this.dropY);
3295 this.dropLine = line;
3296 SetViewToCursor(true);
3298 //PrintLn("MouseMove: ", "dropX = ", x, ", dropY = ", y);
3301 else if(this.mouseSelect)
3303 DirtyLine(this.selY);
3310 SetViewToCursor(true);
3313 //PrintLn("MouseSelect: ", "x = ", x, ", y = ", y);
3320 bool OnLeftDoubleClick(int mx, int my, Modifiers mods)
3325 //PrintLn("OnLeftDoubleClick: ", mx, ", ", my, ", mods = ", mods);
3329 if(style.noSelect) return true;
3330 FindMouse(mx, my, &x, &y, &line, false);
3331 if(!NotifyDoubleClick(master, this, line, mods))
3338 for(c = x; c >= 0; c--)
3341 while(c > 0 && !UTF8_IS_FIRST(line.buffer[c])) c--;
3342 ch = UTF8_GET_CHAR(line.buffer + c, numBytes);
3349 for(c = start; c<line.count; c += numBytes)
3351 unichar ch = UTF8_GET_CHAR(line.buffer + c, numBytes);
3362 this.line = this.selLine = line;
3363 this.wordSelect = (c != start);
3374 Menu fileMenu { menu, "File", F };
3375 saveDialog = fileDialog;
3376 MenuItem { fileMenu, $"Save\tCtrl+S", S, CtrlS, NotifySelect = MenuFileSave };
3377 MenuItem { fileMenu, $"Save As...", A, NotifySelect = MenuFileSaveAs };
3379 if(style.autoSize) AutoSize();
3387 FontExtent(display, font, " ", 1, (int *)&space.w, (int *)&space.h);
3388 FontExtent(display, font, "W", 1, (int *)&large.w, (int *)&large.h);
3390 space.w = Max(space.w, 1);
3391 large.w = Max(large.w, 1);
3392 space.h = Max(space.h, 1);
3393 large.h = Max(large.h, 1);
3397 for(line = lines.first; line; line = line.next)
3398 ComputeLength(line);
3403 SetViewToCursor(true);
3411 bool OnLoadGraphics()
3413 FontExtent = Display::FontExtent;
3416 // UpdateCaretPosition(true);
3420 void OnUnloadGraphics()
3425 bool OnKeyHit(Key key, unichar ch)
3427 bool shift = (key.shift) ? true : false;
3429 //PrintLn("OnKeyHit: code = ", key.code, ", mods = ", key.modifiers, ", ch = ", (int)ch);
3431 if(!ch && !key.alt && !key.ctrl)
3433 key.code = (SmartKey)key.code;
3436 switch(key.code) //(ch || key.alt || key.ctrl) ? key.code : (Key)(SmartKey)key.code)
3439 if(style.readOnly) break;
3440 if(style.stuckCaret) GoToEnd(true);
3441 if(!(style.freeCaret))
3443 this.x = Min(this.x, this.line.count);
3449 EditLine line = this.line;
3451 for(y = this.y; y>= 0; y--)
3453 c = (y == this.y) ? (this.x-1) : line.count-1;
3455 // Slow down when going on lines...
3456 if(y != this.y) break;
3460 //if(this.line.buffer[c] != '\t' && this.line.buffer[c] != ' ')
3461 if(IS_ALUNDER(line.buffer[c]))
3466 if(!IS_ALUNDER(line.buffer[c]))
3468 //if(this.line.buffer[c] == '\t' || this.line.buffer[c] == ' ')
3482 DelCh(line,y,c+1,this.line,this.y,this.x, true);
3483 this.x = this.selX = Min(c+1, line.count);
3484 this.y = this.selY = y;
3485 this.line = this.selLine = line;
3486 SetViewToCursor(true);
3497 if(style.readOnly) break;
3498 if(style.stuckCaret) break;
3507 if(this.line != this.selLine || this.x != this.selX)
3510 SetViewToCursor(true);
3516 if(this.x < this.line.count)
3520 for(i = this.x; i < this.line.count; i++)
3522 if(!IS_ALUNDER(this.line.buffer[i]))
3526 for(; i < this.line.count; i++)
3528 //Delete trailing whitespace
3529 if(IS_ALUNDER(this.line.buffer[i]))
3532 DelCh(this.line, this.y, this.x, this.line, this.y, i, false);
3533 SetViewToCursor(true);
3536 else if(this.line.next)
3538 DelCh(this.line, this.y, this.x, this.line.next, this.y+1, 0, false);
3539 SetViewToCursor(true);
3545 if(!(style.freeCaret))
3547 this.selX = this.x = Min(this.x, this.line.count);
3550 if(this.x < this.line.count)
3552 DelCh(this.line, this.y, this.x, this.line, this.y, this.x+1, false);
3554 else if(this.line.next)
3556 DelCh(this.line, this.y, this.x, this.line.next, this.y+1, 0, false);
3558 SetViewToCursor(true);
3567 if(!key.alt && !key.ctrl)
3571 bool stuffAfter = false;
3575 if(style.stuckCaret) GoToEnd(true);
3576 if(style.readOnly) break;
3577 if(!(style.multiLine)) break;
3579 for(c = 0; c<this.line.count && c<this.x; c++)
3581 if(this.line.buffer[c] == '\t')
3582 position += this.tabSize - (position % this.tabSize);
3583 else if(this.line.buffer[c] == ' ')
3591 if(this.x < this.line.count)
3594 //If last character is a { indent one tab
3595 if(this.line.buffer[this.x - 1] == '{')
3597 //Except if the next non space character is a }
3598 bool indent = false;
3600 for(i = this.x; i < this.line.size; i++)
3601 if(this.line.buffer[i] != ' ' && this.line.buffer[i] != '\t')
3603 if(this.line.buffer[i] != '}')
3608 position += this.tabSize;
3611 addString = new char[position + 2];
3612 addString[len++] = '\n';
3613 addString[len] = '\0';
3615 if(stuffAfter || !style.freeCaret)
3617 for(c = 0; c<position; )
3619 if(style.useTab && c + this.tabSize <= position)
3621 addString[len++] = '\t';
3626 addString[len++] = ' ';
3630 addString[len] = '\0';
3634 if(!stuffAfter && style.freeCaret)
3636 this.x = this.selX = position;
3640 SetViewToCursor(true);
3650 if(style.stuckCaret) break;
3651 if(!(style.freeCaret))
3653 this.x = Min(this.x, this.line.count);
3654 this.selX = Min(this.selX, this.selLine.count);
3657 if(!shift) SelDirty();
3660 bool foundAlpha = false;
3663 EditLine line, lastLine;
3666 for(line = this.line; (line && !found); line = line.prev, y--)
3671 if(this.x == 0 && line != this.line)
3680 if(line == this.line) start = this.x -1; else start = line.count-1;
3681 start = Min(start, line.count-1);
3683 for(c = start; c >= 0;)
3686 unichar ch = UTF8_GET_CHAR(line.buffer + c, numBytes);
3704 byte ch = line.buffer[c];
3705 if(UTF8_IS_FIRST(ch)) break;
3709 // No next word found,
3710 if(!found && ( this.x > 0 || (!line.count && this.x)))
3724 this.line = lastLine;
3735 byte * buffer = line.buffer;
3738 byte ch = buffer[x];
3739 if(UTF8_IS_FIRST(ch)) break;
3756 if(!shift) Deselect();
3757 SetViewToCursor(true);
3763 if(style.stuckCaret) break;
3764 if(!(style.freeCaret))
3766 this.x = Min(this.x, this.line.count);
3767 this.selX = Min(this.selX, this.selLine.count);
3770 if(!shift) SelDirty();
3771 if(!shift && (this.x != this.selX || this.y != this.selY));
3774 bool onAChar = false;
3775 if(this.selX != this.x || this.selY != this.y)
3777 if(this.x<this.line.count)
3778 if(this.line.buffer[this.x] != '\t' && this.line.buffer[this.x] !=' ')
3780 if(key.shift && onAChar &&
3781 ((this.y > this.selY)||((this.selY == this.y)&&(this.x >= this.selX))))
3783 bool foundAlpha = false;
3785 EditLine line, lastLine;
3787 int lastC, lastY, lastNumBytes;
3789 for(line = this.line; (line && !found); line = line.next, y++)
3791 int start = (line == this.line) ? this.x : 0;
3794 for(c = start; c < line.count; c += numBytes)
3796 unichar ch = UTF8_GET_CHAR(line.buffer + c, numBytes);
3801 lastNumBytes = numBytes;
3811 if(!found && (c != this.x || line != this.line))
3824 this.x = lastC + lastNumBytes;
3826 this.line = lastLine;
3833 bool foundAlpha = false;
3838 for(line = this.line; (line && !found); line = line.next, y++)
3840 int start = (line == this.line) ? this.x : 0;
3843 for(c = start; c < line.count; c += numBytes)
3845 unichar ch = UTF8_GET_CHAR(line.buffer + c, numBytes);
3860 // No next word found,
3861 if(!found && (c != this.x || line != this.line))
3865 this.x = line.count;
3877 if(x < line.count || (style.freeCaret && line.count < maxLineSize))
3881 byte * buffer = line.buffer;
3884 byte ch = buffer[x];
3885 if(UTF8_IS_FIRST(ch)) break;
3906 if(!shift) Deselect();
3907 SetViewToCursor(true);
3914 if(!style.vScroll || hasVertScroll) break;
3920 if(style.stuckCaret) break;
3922 if(!shift) SelDirty();
3932 this.x = AdjustXPosition(line, caretX, true, null, MAXINT, 0);
3936 if(!shift) Deselect();
3938 SetViewToCursor(false);
3941 if(caretY == this.y * space.h)
3947 if(!style.freeCaret)
3948 this.x = Min(this.x, line.count);
3958 int sx = 0, sy = this.y * space.h;
3959 char * text = line.text;
3960 int maxW = clientSize.w - sx;
3961 display.FontExtent(font, " ", 1, null, &th);
3965 int startPos = textPos;
3968 bool lineComplete = false;
3970 if(!style.wrap && caretY == MAXINT)
3973 //textPos = line.count;
3974 //lineComplete = true;
3977 for(; (style.freeCaret || textPos < line.count) && !lineComplete;)
3981 char * nextSpace = (textPos < line.count) ? strchr(text + textPos, ' ') : (text + textPos);
3984 len = (nextSpace - (text + textPos));
3986 len = line.count - textPos;
3988 if(textPos < line.count)
3990 display.FontExtent(font, text + textPos, len, &w, null);
3992 if(nextSpace) { w += space.w; len++; }
3994 if(style.wrap && x + width + w > maxW && x > 0)
3996 lineComplete = true;
4006 if((!style.freeCaret && textPos >= line.count) || (sy == caretY - th && caretX <= x + width + sx))
4010 while(this.x > 0 && x + sx > caretX && this.x > startPos)
4013 if(this.x > line.count)
4016 while(this.x > 0 && !UTF8_IS_FIRST(text[--this.x]));
4017 len = this.x - startPos;
4018 display.FontExtent(font, text + startPos, len, &x, null);
4022 if(!shift) Deselect();
4024 SetViewToCursor(false);
4028 if(sy == caretY - th || textPos >= line.count)
4030 if(textPos >= line.count)
4032 int c = textPos - 1;
4033 while(c > 0 && text[c] == ' ') c--;
4037 this.x = line.count;
4040 if(!shift) Deselect();
4042 SetViewToCursor(false);
4047 } while(textPos < line.count);
4050 if(!shift) Deselect();
4052 SetViewToCursor(false);
4061 int x = AdjustXPosition(this.line, this.line.prev, true, null, MAXINT, 0);
4062 if(!shift) SelDirty();
4063 this.line = this.line.prev;
4069 if(!shift) Deselect();
4073 SetViewToCursor(false);
4079 return style.multiLine ? false : true;
4083 if(!style.vScroll || hasVertScroll)
4090 if(style.stuckCaret) break;
4094 int sx = 0, sy = this.y * this.space.h;
4095 int maxW = clientSize.w - sx;
4096 char * text = line.buffer;
4098 if(!shift) SelDirty();
4104 if(AdjustXPosition(line, maxW, this.x, line.count, true, null, MAXINT, 0) <= line.count)
4114 this.x = AdjustXPosition(line, caretX, true, null, MAXINT, 0);
4117 if(!shift) Deselect();
4119 if(this.selX != this.x || this.selY != this.y)
4121 SetViewToCursor(false);
4124 while(!textPos || (style.freeCaret || textPos<line.count))
4126 int startPos = textPos;
4129 bool lineComplete = false;
4130 if(!style.wrap && sy <= caretY)
4132 textPos = line.count;
4133 lineComplete = true;
4135 for(; (style.freeCaret || textPos<line.count) && !lineComplete;)
4139 char * nextSpace = (textPos < line.count) ? strchr(text + textPos, ' ') : (text + textPos);
4142 len = (nextSpace - (text + textPos));
4144 len = line.count - textPos;
4146 if(textPos < line.count)
4148 display.FontExtent(font, text + textPos, len, &w, &th);
4150 if(nextSpace) { w += space.w; len++; }
4151 if(style.wrap && x + width + w > maxW && x > 0)
4153 lineComplete = true;
4163 if(sy > caretY && ((!style.freeCaret && textPos >= line.count) || caretX <= x + width + sx))
4167 while(this.x > 0 && x + sx > caretX && textPos > startPos)
4170 if(this.x > line.count)
4173 while(this.x > 0 && !UTF8_IS_FIRST(text[--this.x]));
4175 len = this.x - startPos;
4176 display.FontExtent(font, text + startPos, len, &x, null);
4179 if(!shift) Deselect();
4182 SetViewToCursor(false);
4188 this.x = line.count;
4191 if(!shift) Deselect();
4194 SetViewToCursor(false);
4197 else if(textPos >= line.count && line.next)
4203 sy = this.y * this.space.h;
4204 sx = 0; //textBlock.startX;
4210 sx = 0; //textBlock.startX;
4218 this.x = Min(this.x, line.count);
4219 //int x = AdjustXPosition(this.line, this.line.next, true, null, MAXINT, 0);
4221 if(!shift) Deselect();
4224 if(this.selX != this.x || this.selY != this.y)
4227 SetViewToCursor(false);
4234 int x = AdjustXPosition(this.line, this.line.next, true, null, MAXINT, 0);
4235 if(!shift) SelDirty();
4236 this.line = this.line.next;
4240 if(!shift) Deselect();
4243 if(this.selX != this.x || this.selY != this.y)
4251 return style.multiLine ? false : true;
4254 if(style.stuckCaret) break;
4255 if(!(style.freeCaret))
4256 this.selX = Min(this.selX, this.selLine.count);
4258 if(!shift) SelDirty();
4261 this.line = this.lines.first;
4262 if(this.y != 0 || this.x != 0)
4272 EditLine line = this.line;
4274 for(c=0; line.buffer[c]; c++)
4275 if(line.buffer[c] != ' ' && line.buffer[c] != '\t')
4277 if(c != 0 || this.x)
4287 DirtyLine(this.y);*/
4292 if(!shift) Deselect();
4293 SetViewToCursor(true);
4299 if(style.stuckCaret) break;
4300 if(!(style.freeCaret))
4301 this.selX = Min(this.selX, this.selLine.count);
4303 if(!shift) SelDirty();
4308 else if(this.x != this.line.count)
4310 this.x = this.line.count;
4311 //DirtyLine(this.y);
4314 if(!shift) Deselect();
4315 SetViewToCursor(true);
4320 if(style.tabKey && !key.ctrl)
4322 if(this.selY != this.y && style.tabSel)
4324 EditLine firstLine, lastLine;
4328 // Do multi line selection tabbing here
4329 if(this.selY < this.y)
4331 firstLine = this.selLine;
4332 lastLine = this.line;
4338 // Selecting going up
4339 firstLine = this.line;
4340 lastLine = this.selLine;
4347 for(line = firstLine; line; line = line.next, y++)
4349 if(line != lastLine || x)
4353 BufferLocation before = { line, y, 0 }, after = { line, y, 0 };
4355 for(c=0; c<line.count && lastC < this.tabSize; c++, lastC++)
4357 if(line.buffer[c] == '\t')
4362 else if(line.buffer[c] != ' ')
4367 NotifyCharsDeleted(master, this, &before, &after, this.pasteOperation);
4370 int len = GetText(null, line, y, 0, line, y, lastC, false, false);
4371 char * string = new char[len];
4372 DelTextAction action { y1 = y, x1 = 0, y2 = y, x2 = lastC, string = string, placeAfter = true };
4373 GetText(string, line, y, 0, line, y, lastC, false, false);
4376 memmove(line.buffer,line.buffer+lastC,line.size-lastC);
4377 if(!line.AdjustBuffer(line.count-lastC))
4380 if(style.autoSize) AutoSize();
4384 if(line == lastLine) break;
4389 for(line = firstLine; line; line = line.next, y++)
4393 if(line != lastLine || x)
4397 if(line.count + 1 <= this.maxLineSize)
4399 BufferLocation before = { line, y, 0 }, after = { line, y, 1 };
4401 if(!line.AdjustBuffer(line.count+1))
4404 NotifyCharsAdded(master, this, &before, &after, this.pasteOperation);
4405 if(style.autoSize) AutoSize();
4407 AddCharAction action { ch = '\t', x = 0, y = y };
4411 memmove(line.buffer+1,line.buffer,line.size-1);
4413 line.buffer[0] = '\t';
4418 if(line.count + this.tabSize <= this.maxLineSize)
4421 BufferLocation before = { line, y, 0 }, after = { line, y, this.tabSize };
4422 NotifyCharsAdded(master, this, &before, &after, this.pasteOperation);
4423 if(style.autoSize) AutoSize();
4425 if(!line.AdjustBuffer(line.count+this.tabSize))
4429 char * string = new char[this.tabSize + 1];
4430 memset(string, ' ', this.tabSize);
4431 string[this.tabSize] = '\0';
4432 Record(AddTextAction { string = string, x1 = 0, y1 = y, x2 = this.tabSize, y2 = y });
4435 memmove(line.buffer+this.tabSize,line.buffer,line.size-(this.tabSize));
4436 line.count+=this.tabSize;
4437 for(c=0; c<this.tabSize; c++)
4438 line.buffer[c] = ' ';
4444 if(line == lastLine) break;
4458 char * addString = new char[this.tabSize + 1];
4460 if(!(style.freeCaret))
4462 this.x = Min(this.x, this.line.count);
4466 start = Min(this.x, this.selX);
4467 for(c=start; ((c == start) || ((c) % this.tabSize)); c++)
4469 addString[len++] = ' ';
4477 SetViewToCursor(true);
4484 if(!(style.hScroll) || hasHorzScroll) break;
4485 if(this.viewX < this.maxLength)
4487 //this.viewX+=this.space.w*this.tabSize;
4489 SetScrollPosition((this.viewX + this.space.w*this.tabSize), this.viewY * this.space.h);
4496 if(!shift) Deselect();
4505 if(!(style.hScroll) || hasHorzScroll) break;
4508 //this.viewX-=this.space.w*this.tabSize;
4509 //this.viewX = Max(this.viewX,0);
4511 SetScrollPosition((this.viewX-this.space.w*this.tabSize), this.viewY * this.space.h);
4512 // SetCursorToView();
4519 if(!shift) Deselect();
4533 if(!(style.readOnly))
4539 this.overwrite ^= 1;
4540 UpdateCaretPosition(true);
4545 NotifyOvrToggle(master, this, this.overwrite);
4556 if(style.noSelect) break;
4559 //Save current view position
4560 int tempX = this.viewX;
4561 int tempY = this.viewY;
4565 this.selLine = this.lines.first;
4566 this.y = this.lineCount-1;
4567 this.line = this.lines.last;
4568 this.x = this.line.count;
4571 SetViewToCursor(true);
4573 //Restore previous view position
4574 SetScrollPosition(tempX, tempY * this.space.h);
4580 // TOCHECK: Was there any good reason why we weren't returning false here?
4581 return false; // break;
4586 if(!(style.readOnly))
4594 if(style.readOnly) break;
4600 if(style.readOnly) break;
4605 if(style.readOnly) break;
4609 if(style.readOnly) break;
4613 if(style.readOnly) break;
4614 if(key.shift && key.code == rightBracket)
4616 //Only indent back if you are exactly at one tab.
4618 bool whitespace = true;
4624 EditLine line = this.line;
4626 //Only remove one tab if there is nothing else on the line.
4627 for(i = 0; i < this.line.count; i++)
4629 if(this.line.buffer[i] != ' ' && this.line.buffer[i] != '\t')
4643 for(pos = line.count - 1; pos >= 0; pos--)
4645 char c = line.buffer[pos];
4649 indentwidth += this.tabSize;
4651 //Counting backwards, so when you find a character, indentation is reset
4661 //Place the } to get an undo:
4664 this.x = this.line.count;
4668 putsize = indentwidth;
4670 putsize = indentwidth / this.tabSize + indentwidth % this.tabSize;
4672 newline = new char[putsize+2];
4673 newline[putsize] = '}';
4674 newline[putsize+1] = '\0';
4678 for(; i < indentwidth / this.tabSize; i++)
4680 for(;i < putsize; i++)
4688 } else if(!key.ctrl && !key.alt && ch != 128 && ch >= 32)
4700 void OnHScroll(ScrollBarAction action, int position, Key key)
4703 //PrintLn("OnHScroll: ", action, ", pos = ", position, ", key = ", key);
4705 this.viewX = position;
4706 if(action != setRange)
4708 if(!this.mouseMove && style.cursorFollowsView)
4715 void OnVScroll(ScrollBarAction action, int position, Key key)
4717 int oldViewY = this.viewY;
4720 //PrintLn("OnVScroll: ", action, ", pos = ", position, ", key = ", key);
4722 position /= this.space.h;
4724 if(position < this.viewY)
4726 for(; position < this.viewY && this.viewLine.prev; this.viewLine = this.viewLine.prev, this.viewY--);
4727 style.recomputeSyntax = true;
4729 else if(position > this.viewY)
4731 EditLine oldViewLine = viewLine;
4732 for(; position > this.viewY && this.viewLine.next; this.viewLine = this.viewLine.next, this.viewY++);
4733 FigureStartSyntaxStates(oldViewLine, false);
4736 if(action != setRange)
4738 if(!this.mouseMove && style.cursorFollowsView && !SelSize()) SetCursorToViewY();
4741 if(this.x != this.selX || this.y != this.selY)
4745 Scroll(0, (this.viewY - oldViewY) * this.space.h);
4748 int numLines = clientSize.h / this.space.h;
4750 if(Abs(this.viewY - oldViewY) < numLines)
4753 if(this.viewY > oldViewY)
4755 for(y = oldViewY; y <this.viewY; y++)
4756 DirtyLine(y + numLines);
4760 for(y = this.viewY; y <oldViewY; y++)
4761 DirtyLine(y + numLines);
4766 // Fix dirt of stuff before first line...
4767 if(this.viewY - oldViewY > 0)
4769 Box box { 0,0, clientSize.w-1, YOFFSET-1 };
4776 bool _AddCh(unichar ch, int * addedSpacesPtr, int * addedTabsPtr)
4781 ReplaceTextAction replaceAction = null;
4782 AddCharAction addCharAction = null;
4783 int addedSpaces = 0, addedTabs = 0;
4785 if(ch == '\r') return true;
4786 if(style.stuckCaret /*|EES_READONLY)*/ )
4789 if(ch == '\n' && !(style.multiLine) && this.line) return false;
4791 if(!undoBuffer.dontRecord)
4793 if(selX != x || selY != y)
4798 int len = GetText(null, selLine, selY, selX, this.line, y, x, false, false);
4799 oldString = new char[len];
4800 UTF32toUTF8Len(&ch, 1, buffer, 4);
4801 newString = CopyString(buffer);
4802 GetText(oldString, selLine, selY, selX, this.line, y, x, false, false);
4804 replaceAction = ReplaceTextAction { newString = newString, oldString = oldString, placeAfter = false };
4805 if(selY < y || (selY == y && selX < x))
4807 replaceAction.x1 = selX;
4808 replaceAction.y1 = selY;
4809 replaceAction.x2 = x;
4810 replaceAction.y2 = y;
4814 replaceAction.x1 = x;
4815 replaceAction.y1 = y;
4816 replaceAction.x2 = selX;
4817 replaceAction.y2 = selY;
4819 Record(replaceAction);
4820 undoBuffer.dontRecord++;
4824 addCharAction = AddCharAction { y = y, x = x, ch = ch };
4825 Record(addCharAction);
4831 DelSel(&addedSpaces);
4832 if(this.lineCount+1 > this.maxLines)
4835 Emptyline(this.lines.first,0);
4839 if(!(style.vScroll))
4841 // Make sure it fits, but we need a default line is this.font is too big for window
4842 if(this.space.h * (this.lineCount+1) > clientSize.h && this.line)
4845 if((this.y >= 0) && this.y < this.viewY)
4850 line = EditLine { };
4853 lines.Insert(this.line, line);
4854 line.editBox = this;
4860 // If we're displacing the lines from a current line ...
4863 if(this.line.buffer)
4866 if(this.line.count < endX) endX = this.line.count;
4867 length = this.line.count - endX;
4870 if(!line.AdjustBuffer(length))
4874 if(this.line.buffer)
4876 CopyBytes(line.buffer,this.line.buffer+endX, length+1);
4878 if(endX > 4000 || endX < 0)
4881 this.line.count = endX;
4882 this.line.buffer[this.line.count] = '\0';
4883 this.line.AdjustBuffer(this.line.count);
4884 ComputeLength(this.line);
4888 BufferLocation before = { this.line, this.y, this.x }, after;
4894 ComputeLength(this.line);
4897 if(length > 4000 || length < 0)
4900 line.count = length;
4904 line.buffer[line.count] = '\0';
4907 after.line = this.line, after.y = this.y, after.x = this.x;
4909 NotifyCharsAdded(master, this, &before, &after, this.pasteOperation);
4910 if(style.autoSize) AutoSize();
4916 int count = UTF32toUTF8Len(&ch, 1, string, 5);
4917 DelSel(&addedSpaces);
4918 result = AddToLine(string, count, false, addedSpaces ? null : &addedSpaces, &addedTabs);
4919 if(addedSpacesPtr) *addedSpacesPtr = addedSpaces;
4920 if(addedTabsPtr) *addedTabsPtr = addedTabs;
4924 this.selLine = this.line;
4928 replaceAction.x3 = x;
4929 replaceAction.y3 = y;
4930 replaceAction.addedSpaces = addedSpaces;
4931 replaceAction.addedTabs = addedTabs;
4932 undoBuffer.dontRecord--;
4936 addCharAction.x -= addedTabs * (tabSize-1);
4937 addCharAction.addedSpaces = addedSpaces;
4938 addCharAction.addedTabs = addedTabs;
4945 /****************************************************************************
4947 ****************************************************************************/
4950 bool AddCh(unichar ch)
4952 return _AddCh(ch, null, null);
4957 this.modified = true;
4958 NotifyUpdate(master, this);
4959 modifiedDocument = true;
4962 void Delete(EditLine line1, int y1, int x1, EditLine line2, int y2, int x2)
4965 DelCh(line1, y1, x1, line2, y2, x2, false);
4966 SetViewToCursor(true);
4974 itemEditUndo.disabled = undoBuffer.curAction == 0;
4975 itemEditRedo.disabled = undoBuffer.curAction == undoBuffer.count;
4976 if(savedAction == undoBuffer.curAction)
4978 modifiedDocument = false;
4980 NotifyUnsetModified(master, this);
4987 itemEditUndo.disabled = undoBuffer.curAction == 0;
4988 itemEditRedo.disabled = undoBuffer.curAction == undoBuffer.count;
4989 if(savedAction == undoBuffer.curAction)
4991 modifiedDocument = false;
4993 NotifyUnsetModified(master, this);
4997 void Record(UndoAction action)
4999 if(!undoBuffer.dontRecord)
5001 undoBuffer.Record(action);
5002 itemEditUndo.disabled = undoBuffer.curAction == 0;
5003 itemEditRedo.disabled = undoBuffer.curAction == undoBuffer.count;
5010 this.lines.first, 0,0,
5011 this.lines.last, this.lines.count-1, strlen(((EditLine)this.lines.last).buffer));
5014 void Select(EditLine line1, int y1, int x1, EditLine line2, int y2, int x2)
5019 this.selLine = line1 ? (EditLine)line1 : this.lines.first;
5020 this.x = line2 ? x2 : ((EditLine)this.lines.last).count;
5021 this.y = line2 ? y2 : (this.lineCount-1);
5022 this.line = line2 ? (EditLine)line2 : this.lines.last;
5025 SetViewToCursor(true);
5029 // TODO: Fix this vs modifiedDocument window property
5030 void SetModified(bool flag)
5034 this.modified = false;
5035 if(flag && !NotifyModified(master, this))
5036 this.modified = true;
5041 bool AddS(char * string)
5048 int addedSpaces = 0, addedTabs = 0;
5049 AddTextAction action = null;
5050 ReplaceTextAction replaceAction = null;
5052 this.pasteOperation = true;
5054 if(style.stuckCaret /*|EES_READONLY)*/ )
5057 if(!undoBuffer.dontRecord)
5059 char * placeString = CopyString(string);
5060 if(!style.multiLine)
5064 for(i = 0; (ch = placeString[i]); i++)
5067 placeString[i] = '\0';
5068 placeString = renew placeString byte[i+1];
5073 if(selX != x || selY != y)
5077 int len = GetText(null, selLine, selY, selX, this.line, y, x, false, false);
5078 oldString = new char[len];
5079 newString = placeString;
5080 GetText(oldString, selLine, selY, selX, this.line, y, x, false, false);
5082 replaceAction = ReplaceTextAction { newString = newString, oldString = oldString, placeAfter = false };
5083 if(selY < y || (selY == y && selX < x))
5085 replaceAction.x1 = selX;
5086 replaceAction.y1 = selY;
5087 replaceAction.x2 = x;
5088 replaceAction.y2 = y;
5092 replaceAction.x1 = x;
5093 replaceAction.y1 = y;
5094 replaceAction.x2 = selX;
5095 replaceAction.y2 = selY;
5097 Record(replaceAction);
5101 if(string[0] == '\n')
5102 action = AddTextAction { y1 = y, x1 = Min(this.line.count, x), string = placeString };
5104 action = AddTextAction { y1 = y, x1 = x, string = placeString };
5112 undoBuffer.dontRecord++;
5113 DelSel(&addedSpaces);
5117 for(c = 0; string[c]; c++)
5119 if(string[c] == '\n' || string[c] == '\r')
5121 if(!AddToLine(line,count, true, addedSpaces ? null : &addedSpaces, addedTabs ? null : &addedTabs))
5126 if(string[c] == '\n')
5135 // Reset for next line
5139 if(string[c] == '\r' && *line == '\n')
5151 // Why was this here?
5154 // Add the line here
5156 if(!AddToLine(line,count,false, addedSpaces ? null : &addedSpaces, addedTabs ? null : &addedTabs))
5162 undoBuffer.dontRecord--;
5167 action.addedSpaces = addedSpaces;
5168 action.addedTabs = addedTabs;
5170 else if(replaceAction)
5172 replaceAction.y3 = y;
5173 replaceAction.x3 = x;
5174 replaceAction.addedSpaces = addedSpaces;
5175 replaceAction.addedTabs = addedTabs;
5178 UpdateCaretPosition(true);
5180 this.pasteOperation = false;
5193 DelCh(this.lines.first, 0, 0, this.lines.last, this.lineCount-1, ((EditLine)(this.lines.last)).count, true);
5194 SetViewToCursor(true);
5200 void PutCh(unichar ch)
5204 if((ch >= 32 /*&& ch <=126*/) || ch == '\n')
5205 //if((ch >= 32) || ch == '\n')
5207 int addedSpaces = 0, addedTabs = 0;
5208 ReplaceTextAction replaceAction = null;
5209 AddCharAction addCharAction = null;
5212 ch = (ch < 128) ? toupper(ch) : ch; // TODO: UNICODE TO UPPER
5214 if(this.x < this.line.count && this.overwrite)
5219 int len = GetText(null, line, y, x, line, y, x+1, false, false);
5220 oldString = new char[len];
5221 UTF32toUTF8Len(&ch, 1, buffer, 4);
5222 newString = CopyString(buffer);
5223 GetText(oldString, line, y, x, line, y, x+1, false, false);
5224 replaceAction = ReplaceTextAction { newString = newString, oldString = oldString, placeAfter = false };
5225 replaceAction.x1 = x;
5226 replaceAction.y1 = y;
5227 replaceAction.x2 = x+1;
5228 replaceAction.y2 = y;
5229 Record(replaceAction);
5231 undoBuffer.dontRecord++;
5232 DelCh(this.line, this.y, this.x, this.line, this.y, this.x+1, true);
5233 undoBuffer.dontRecord--;
5235 else if(!undoBuffer.dontRecord)
5237 if(selX != x || selY != y)
5242 int len = GetText(null, line, y, x, selLine, selY, selX, false, false);
5243 oldString = new char[len];
5244 UTF32toUTF8Len(&ch, 1, buffer, 4);
5245 newString = CopyString(buffer);
5246 GetText(oldString, line, y, x, selLine, selY, selX, false, false);
5247 replaceAction = ReplaceTextAction { newString = newString, oldString = oldString, placeAfter = true };
5248 if(selY < y || (selY == y && selX < x))
5250 replaceAction.x1 = selX;
5251 replaceAction.y1 = selY;
5252 replaceAction.x2 = x;
5253 replaceAction.y2 = y;
5257 replaceAction.x1 = x;
5258 replaceAction.y1 = y;
5259 replaceAction.x2 = selX;
5260 replaceAction.y2 = selY;
5262 Record(replaceAction);
5266 addCharAction = AddCharAction { y = y, x = x, ch = ch };
5267 Record(addCharAction);
5270 undoBuffer.dontRecord++;
5271 result = _AddCh(ch, &addedSpaces, &addedTabs);
5274 replaceAction.x3 = x;
5275 replaceAction.y3 = y;
5276 replaceAction.addedSpaces = addedSpaces;
5277 replaceAction.addedTabs = addedTabs;
5281 addCharAction.x -= addedTabs * (tabSize-1);
5282 addCharAction.addedSpaces = addedSpaces;
5283 addCharAction.addedTabs = addedTabs;
5285 undoBuffer.dontRecord--;
5289 if(result) SetViewToCursor(true);
5293 void PutS(char * string)
5298 SetViewToCursor(true);
5303 void Printf(char * format, ...)
5307 char temp[MAX_F_STRING];
5309 va_start(args, format);
5310 vsnprintf(temp, sizeof(temp), format, args);
5311 temp[sizeof(temp)-1] = 0;
5317 void SetContents(char * format, ...)
5322 DelCh(this.lines.first, 0, 0, this.lines.last, this.lineCount-1, ((EditLine)(this.lines.last)).count, true);
5325 char temp[MAX_F_STRING];
5327 va_start(args, format);
5328 vsnprintf(temp, sizeof(temp), format, args);
5329 temp[sizeof(temp)-1] = 0;
5345 x -= 1 + DelCh(line, y, x-1, line, y, x, true);
5348 else if(this.line.prev)
5350 EditLine line = this.line.prev;
5354 DelCh(line, this.y-1, x, this.line, this.y, this.x, true);
5362 this.selLine = this.line;
5367 SetViewToCursor(true);
5372 Emptyline(this.line,this.y);
5373 this.selX = this.x = 0;
5376 this.selLine = this.line;
5378 SetViewToCursor(true);
5388 SetViewToCursor(true);
5396 SetViewToCursor(true);
5400 bool GoToLineNum(int lineNum)
5405 EditLine line = this.lines.first;
5406 for(c = 0; c < lineNum && line; c++, line = line.next);
5416 SetViewToCursor(true);
5423 bool GoToPosition(EditLine line, int y, int x)
5435 for(line = this.lines.first, c = 0; c<y && line; c++, line = line.next);
5449 SetViewToCursor(true);
5456 void SetViewToCursor(bool setCaret)
5466 bool dontScroll = false;
5472 selected = selX != this.x || selY != y;
5479 checkLine = dropLine;
5485 checkLine = this.line;
5490 numLines = clientSize.h / space.h;
5492 // This is broken. The EditBox now doesn't do anything different when adding to it,
5493 // regardless of the previous scrolling position. It should be read and then set again
5494 // if one wishes to preserve it.
5495 /* // Don't scroll view to cursor if we're in a EES_NOCARET box
5496 if(style.noCaret && this.viewY < lineCount - numLines - 1)
5500 // Horizontal Adjustment
5501 if(!dontScroll && checkLine)
5505 dropX = AdjustXPosition(this.line, MAXINT, true, &x, checkX, 0);
5508 this.x = AdjustXPosition(this.line, MAXINT, true, &x, checkX, 0);
5514 if(x + space.w >= this.viewX + clientSize.w && clientSize.w >= space.w)
5515 viewX = x - clientSize.w+space.w;
5516 if(x < this.viewX + clientSize.w/2 - space.w)
5517 viewX = Max(0, x - clientSize.w/2 + space.w);
5525 // Vertical Adjustment
5526 if(viewY > checkY) viewY = checkY;
5527 if(viewY + numLines <= checkY)
5529 if(clientSize.h >= space.h)
5531 for(line = viewLine; line && (viewY + numLines <= checkY); line = line.next)
5541 for(;dropLine && dropLine.prev && dropY >= numLines;)
5543 dropLine = dropLine.prev;
5547 for(;this.line && this.line.prev && this.y >= numLines;)
5549 this.line = this.line.prev;
5554 SetScrollPosition(viewX, viewY * this.space.h);
5556 UpdateCaretPosition(setCaret);
5562 selLine = this.line;
5572 void CenterOnCursor()
5574 int numLines = clientSize.h / this.space.h;
5575 int y = this.y - numLines / 2;
5577 bool figureSyntax = false;
5578 EditLine oldViewLine = viewLine;
5579 if(y > this.lineCount - numLines) y = this.lineCount-numLines;
5584 for(;y < this.viewY; y++)
5586 this.viewLine = this.viewLine.prev;
5587 style.recomputeSyntax = true;
5589 for(;y > this.viewY; y--)
5591 this.viewLine = this.viewLine.next;
5592 figureSyntax = true;
5595 FigureStartSyntaxStates(oldViewLine, false);
5599 SetScrollPosition(this.viewX, viewY * this.space.h);
5600 UpdateCaretPosition(true);
5604 void SetCursorToView()
5615 numLines = clientSize.h / this.space.h;
5619 for(c=0, line = this.viewLine.next; line && c<numLines && (this.viewY < this.lineCount - numLines); line = line.next, c++);
5620 SetScrollPosition(this.viewX, (this.viewY + c) * this.space.h);
5624 EditLine oldLine = this.line;
5625 bool lastOne = false;
5626 EditLine oldViewLine = this.viewLine;
5627 bool figureSyntax = false;
5629 if(this.y >= this.lineCount-1) return;
5631 for(c=0, line = this.line.next; line && c<numLines; line = line.next, c++)
5633 if(this.viewY + numLines < this.lines.count)
5635 this.viewLine = this.viewLine.next;
5637 figureSyntax = true;
5639 else if(c && !lastOne)
5648 FigureStartSyntaxStates(oldViewLine, false);
5649 this.x = AdjustXPosition(line, caretX, true, null, MAXINT, 0);
5652 SetViewToCursor(false);
5661 if(this.y == 0) return;
5663 numLines = clientSize.h / this.space.h;
5667 for(c=0, line = this.viewLine.prev; line && c<numLines; line = line.prev, c++);
5668 SetScrollPosition(this.viewX, (this.viewY - c) * this.space.h);
5672 EditLine oldLine = this.line;
5674 for(c=0, line = this.line.prev; line && c<numLines; line = line.prev, c++)
5679 if(this.viewLine.prev)
5681 this.viewLine = this.viewLine.prev;
5683 style.recomputeSyntax = true;
5687 this.x = AdjustXPosition(line, caretX, true, null, MAXINT, 0);
5690 SetViewToCursor(false);
5697 if(this.viewLine.prev)
5700 // this.viewLine = this.viewLine.prev;
5703 SetScrollPosition(this.viewX, (this.viewY - 1) * this.space.h);
5710 if(this.viewLine.next)
5713 // this.viewLine = this.viewLine.next;
5716 SetScrollPosition(this.viewX, (this.viewY + 1) * this.space.h);
5723 EditLine l1, l2, line;
5724 int x1, x2, nx1, nx2;
5729 if(!this.selLine) return 0;
5730 if(this.selLine == this.line && this.selX == this.x) return 0;
5731 if(this.selY < this.y)
5738 else if(this.selY > this.y)
5745 else if(this.selX < this.x)
5747 l1 = l2 = this.line;
5753 l1 = l2 = this.line;
5757 nx1 = Min(x1,l1.count);
5758 nx2 = Min(x2,l2.count);
5760 // Find Number of Bytes Needed
5762 for(line = l1; line; line = line.next)
5764 if(line == l1) start = nx1; else start = 0;
5765 if(line == l2) end = nx2; else end = line.count;
5767 if(style.freeCaret && line == l2)
5770 count = Max(x2-Max(x1,l1.count),0);
5772 count = Max(x2-l2.count,0);
5776 if(line == l2) break;
5777 // Add Carriage Return / line Feed
5784 void GetSelPos(EditLine * l1, int *y1, int *x1, EditLine * l2, int * y2, int * x2, bool reorder)
5788 if(!reorder || this.selY < this.y)
5790 if(l1) *l1 = this.selLine;
5791 if(y1) *y1 = this.selY;
5792 if(x1) *x1 = this.selX;
5793 if(l2) *l2 = this.line;
5794 if(y2) *y2 = this.y;
5795 if(x2) *x2 = this.x;
5797 else if(this.selY > this.y)
5799 if(l1) *l1 = this.line;
5800 if(y1) *y1 = this.y;
5801 if(x1) *x1 = this.x;
5802 if(l2) *l2 = this.selLine;
5803 if(y2) *y2 = this.selY;
5804 if(x2) *x2 = this.selX;
5806 else if(this.selX < this.x)
5808 if(l1) *l1 = this.line;
5809 if(y1) *y1 = this.selY;
5810 if(x1) *x1 = this.selX;
5811 if(l2) *l2 = this.line;
5812 if(y2) *y2 = this.y;
5813 if(x2) *x2 = this.x;
5817 if(l1) *l1 = this.line;
5818 if(y1) *y1 = this.y;
5819 if(x1) *x1 = this.x;
5820 if(l2) *l2 = this.line;
5821 if(y2) *y2 = this.selY;
5822 if(x2) *x2 = this.selX;
5827 void SetSelPos(EditLine l1, int y1, int x1, EditLine l2, int y2, int x2)
5829 if(this && (this.selY != y1 || this.selX != x1 || this.y != y2 || this.x != x2))
5831 this.selLine = (EditLine)l1;
5834 this.line = (EditLine)l2;
5838 SetViewToCursor(true);
5842 int GetText(char * text, EditLine _l1, int _y1, int _x1, EditLine _l2, int _y2, int _x2, bool addCr, bool addSpaces)
5844 EditLine l1, l2, line;
5845 int x1, x2, nx1, nx2;
5876 nx1 = Min(x1,l1.count);
5877 nx2 = Min(x2,l2.count);
5880 for(line = l1; line; line = line.next)
5882 if(line == l1) start = nx1; else start = 0;
5883 if(line == l2) end = nx2; else end = line.count;
5886 CopyBytes(text, line.buffer + start, end - start);
5889 numChars += end-start;
5891 if(style.freeCaret && line == l2 && addSpaces)
5894 count = Max(x2-Max(x1,l1.count),0);
5896 count = Max(x2-l2.count,0);
5899 FillBytes(text,' ',count);
5905 if(line == l2) break;
5917 // '\0' terminate Terminate
5924 void GetSel(char * text, bool addCr)
5926 GetText(text, line, y, x, selLine, selY, selX, addCr, true);
5932 this.selLine = this.line;
5942 int size = SelSize();
5945 // Try to allocate memory
5946 ClipBoard clipBoard { };
5947 if(clipBoard.Allocate(size+1))
5949 GetSel(clipBoard.memory, true);
5962 ClipBoard clipBoard { };
5963 if(clipBoard.Load())
5964 PutS(clipBoard.memory);
5976 SetViewToCursor(true);
5982 void DeleteSelection()
5987 SetViewToCursor(true);
5993 void Save(File f, bool cr)
5996 savedAction = undoBuffer.curAction;
5998 for(line = this.lines.first; line; line = line.next)
6000 f.Write(line.buffer, line.count,1);
6003 if(cr) f.Putc('\r');
6009 #define BUFFER_SIZE 16384
6012 undoBuffer.dontRecord++;
6015 char buffer[BUFFER_SIZE];
6019 int count = f.Read(buffer, 1, BUFFER_SIZE-1);
6020 buffer[count] = '\0';
6026 undoBuffer.dontRecord--;
6027 undoBuffer.count = 0;
6028 undoBuffer.curAction = 0;
6029 itemEditUndo.disabled = undoBuffer.curAction == 0;
6030 itemEditRedo.disabled = undoBuffer.curAction == undoBuffer.count;
6033 EditBoxFindResult Find(char * text, bool matchWord, bool matchCase, bool isSearchDown)
6037 bool firstPass = true;
6038 EditBoxFindResult result = found;
6040 if(!this.line) return notFound;
6043 for(line = this.line;;)
6051 line = this.lines.first;
6057 line = this.lines.last;
6058 num = this.lineCount - 1;
6064 string = SearchString(line.buffer, firstPass ? Min(this.x,line.count) : 0,text,matchCase,matchWord);
6066 string = RSearchString(line.buffer,text,firstPass ? (Min(this.x,line.count)-1) : line.count,matchCase,matchWord);
6070 Select((void *)line,num,string - line.buffer,(void *)line,num,string - line.buffer + strlen(text));
6073 if(line == this.line && !firstPass) break;
6091 EditBoxFindResult FindInSelection(char * text, bool matchWord, bool matchCase, EditLine l2, int y2, int x2)
6095 int searchLen = strlen(text);
6096 for(line = (EditLine)this.line, y = this.y; y <= y2; line = line.next, y++)
6098 char * string = SearchString(line.buffer, (y == this.y) ? Min(this.x,line.count) : 0,text,matchCase,matchWord);
6099 if(string && (y < y2 || (string - line.buffer) + searchLen <= x2))
6101 Select((void *)line,y,string - line.buffer,(void *)line,y,string - line.buffer + strlen(text));
6108 bool OnKeyDown(Key key, unichar ch)
6111 //PrintLn("OnKeyDown: code = ", key.code, ", mods = ", key.modifiers, ", ch = ", (int)ch);
6113 if(!NotifyKeyDown(master, this, key, ch))
6123 this.mouseMove = false;
6124 OnLeftButtonUp(0,0,0);
6125 SetViewToCursor(true);
6135 public class EditBoxStream : File
6138 BufferLocation start, sel;
6145 EditBox editBox = this.editBox;
6147 editBox.x = start.x;
6148 editBox.y = start.y;
6149 editBox.line = start.line;
6151 editBox.selX = sel.x;
6152 editBox.selY = sel.y;
6153 editBox.selLine = sel.line;
6155 editBox.SetViewToCursor(true);
6156 //editBox.ComputeColumn();
6160 property EditBox editBox
6168 start.line = value.line;
6172 sel.line = value.selLine;
6175 value.GoToHome(true);
6178 get { return editBox; }
6181 uint Read(byte * buffer, uint size, uint count)
6184 EditBox editBox = this.editBox;
6185 EditLine line = editBox.line;
6191 for(;read < count && line; line = (*&line.next))
6193 int numBytes = Min(count - read, (*&line.count) - x);
6196 memcpy(buffer + read, (*&line.buffer) + x, numBytes);
6201 for(;read < count && x < (*&line.count);)
6203 buffer[read++] = (*&line.buffer)[x++];
6210 buffer[read++] = '\n';
6215 if(x == (*&line.count) && (*&line.next))
6224 editBox.line = editBox.selLine = line;
6225 editBox.x = editBox.selX = x;
6226 editBox.y = editBox.selY = y;
6231 bool Seek(int pos, FileSeekMode mode)
6234 EditBox editBox = this.editBox;
6235 EditLine line = editBox.line;
6237 if(mode == FileSeekMode::start)
6239 pos = pos - this.pos;
6249 for(;read < pos && line; line = (*&line.next))
6251 int numBytes = Min(pos - read, (*&line.count) - x);
6254 read += numBytes; x += numBytes;
6257 /*for(;read < pos && x < (*&line.count);)
6285 for(;read < pos && line; line = (*&line.prev))
6287 int numBytes = Min(pos - read, x);
6293 /*for(;read < pos && x > 0;)
6303 x = (*&(*&line.prev).count);
6319 editBox.line = editBox.selLine = line;
6320 editBox.x = editBox.selX = x;
6321 editBox.y = editBox.selY = y;
6326 bool Puts(char * string)
6328 EditBox editBox = this.editBox;
6329 BufferLocation start { editBox.line, editBox.y, editBox.x };
6333 editBox.AddS(string);
6335 pos.line = editBox.line;
6339 this.start.AdjustAdd(start, pos);
6340 sel.AdjustAdd(start, pos);
6345 // NOTE: BYTE, NOT UNICODE CHARACTER!
6348 EditBox editBox = this.editBox;
6349 BufferLocation start = { editBox.line, editBox.y, editBox.x };
6354 utf8Bytes[numBytes++] = ch;
6355 utf8Bytes[numBytes] = 0;
6356 if(UTF8Validate(utf8Bytes))
6358 editBox.AddCh(UTF8_GET_CHAR(utf8Bytes, numBytes));
6360 pos.line = editBox.line;
6363 this.start.AdjustAdd(start, pos);
6364 sel.AdjustAdd(start, pos);
6371 bool Getc(char * ch)
6373 return Read(ch, 1, 1) ? true : false;
6376 void DeleteBytes(uint count)
6378 EditBox editBox = this.editBox;
6381 BufferLocation pos { editBox.line, editBox.y, editBox.x };
6382 BufferLocation end = pos;
6387 for(;c < count && end.line && end.x < end.line.count;)
6392 if(c < count && end.line && end.line.next)
6394 end.line = end.line.next;
6403 start.AdjustDelete(pos, end);
6404 sel.AdjustDelete(pos, end);
6406 editBox.DelCh(pos.line, pos.y, pos.x, end.line, end.y, end.x, true);