1 namespace gui::controls;
4 selectionForeground = white;
5 disabled: defaultTextColor = Color { 85, 85, 85 };
14 char * strchrmax(const char * s, int c, int max)
18 for(i = 0; i < max && (ch = s[i]); i++)
24 public class SyntaxColorScheme
28 Color charLiteralColor;
29 Color stringLiteralColor;
30 Color preprocessorColor;
32 private Array<Color> keywordColors { };
34 public property Container<Color> keywordColors
36 set { keywordColors.Copy((void *)value); }
37 get { return keywordColors; }
45 #define XOFFSET (3 + (/*style.lineNumbers?6 * this.space.w:*/0))
47 #define YOFFSET (style.multiLine ? 1 : ((clientSize.h + 1 - space.h) / 2))
49 #define IS_ALUNDER(ch) (/*(ch) == '_' || */CharMatchCategories((ch), letters|numbers|marks|connector))
51 #define UTF8_IS_FIRST(x) (__extension__({ byte b = x; (!(b) || !((b) & 0x80) || ((b) & 0x40)); }))
52 #define UTF8_NUM_BYTES(x) (__extension__({ byte b = x; (b & 0x80 && b & 0x40) ? ((b & 0x20) ? ((b & 0x10) ? 4 : 3) : 2) : 1; }))
53 #define UTF8_GET_CHAR(string, numBytes) \
57 byte b = (string)[0]; \
60 numBytes = b ? 1 : 0; \
74 if(b & 0x08) { numBytes = 0; } \
83 for(i = 0; i<numBytes; i++) \
86 ch |= (b = (string)[i]) & mask; \
88 if(i > 1 && (!(b & 0x80) || (b & 0x40))) \
95 ch > 0x10FFFF || (ch >= 0xD800 && ch <= 0xDFFF) || \
96 (ch < 0x80 && numBytes > 1) || \
97 (ch < 0x800 && numBytes > 2) || \
98 (ch < 0x10000 && numBytes > 3))\
108 bool autoEmpty:1, readOnly:1, multiLine:1, stuckCaret:1, freeCaret:1, select:1, hScroll:1, vScroll:1, smartHome:1;
109 bool noCaret:1, noSelect:1, tabKey:1, useTab:1, tabSel:1, allCaps:1, syntax:1, wrap:1;
112 bool inMultiLineComment:1, inPrep:1, escaped:1, continuedSingleLineComment:1, wasInMultiLine:1, continuedString:1, continuedQuotes:1;
114 bool recomputeSyntax:1;
115 bool cursorFollowsView:1;
117 // bool lineNumbers:1;
122 void UnregisterClass_EditBox()
125 for(g = 0; g<NUM_KEYWORD_GROUPS; g++)
134 extern int __ecereVMethodID_class_OnFree;
137 static class ArrayImpl
144 public class OldArray
149 void ** array = (void **)((ArrayImpl)this).array;
150 if(type.type == normalClass || type.type == noHeadClass)
152 for(c = 0; c<size; c++)
153 ((void (*)(void *, void *))(void *)type._vTbl[__ecereVMethodID_class_OnFree])(type, array[c]);
155 // TODO: Call OnFree for structClass
156 delete ((ArrayImpl)this).array;
165 if(((ArrayImpl)this).array)
169 ((ArrayImpl)this).array = renew0 ((ArrayImpl)this).array byte[type.typeSize * value];
172 ((ArrayImpl)this).array = new0 byte[value * type.typeSize];
181 memcpy(((ArrayImpl)this).array, value, type.typeSize * size);
188 public class UndoAction : struct
191 subclass(UndoAction) type;
194 virtual void Undo(void * data) { type.Undo(this, data); }
195 virtual void Redo(void * data) { type.Redo(this, data); }
197 virtual void Print(void * data) { type.Print(this, data); }
201 if(((Class)type).Destructor)
202 ((void (*)(void *))((Class)type).Destructor)(this);
206 public class UndoBuffer
208 Array<UndoAction> actions { size = 8 };
227 bool continued = true;
228 while(curAction > 0 && continued)
230 UndoAction action = actions[--curAction];
234 /*Print("Undoing: ");
235 action.Print(data);*/
240 continued = curAction > 0 && actions[curAction-1].continued;
246 bool continued = true;
247 while(curAction < count && continued)
249 UndoAction action = actions[curAction];
250 continued = action.continued;
256 /*Print("Redoing: ");
257 action.Print(data);*/
266 void Record(UndoAction action)
268 if(!dontRecord && !insideRedo)
270 if(curAction < count)
273 for(c = curAction; c < count; c++)
279 if(count >= actions.size)
280 actions.size += actions.size / 2;
283 /*Print("Recording: ");
284 action.Print(data);*/
288 if(!firstEvent && count > 0)
289 actions[count-1].continued = true;
292 actions[count++] = action;
295 if(actions.size > count + count / 2 && count + count / 2 >= 8)
296 actions.size = count + count / 2;
303 static class AddCharAction : UndoAction
307 int addedSpaces, addedTabs;
308 type = class(AddCharAction);
310 void Undo(EditBox editBox)
312 editBox.GoToPosition(null, (ch == '\n') ? (y + 1) : y, (ch == '\n') ? 0 : (x + 1));
314 if(addedTabs || addedSpaces)
315 editBox.DelCh(editBox.line, y, x - (addedSpaces + addedTabs), editBox.line, y, x, false);
316 editBox.UpdateDirty();
319 void Redo(EditBox editBox)
321 editBox.GoToPosition(null, y, x);
323 editBox.UpdateDirty();
326 void Print(EditBox editBox)
328 PrintLn("AddChar: y = ", y, "x = ", x, ", ch = ", ch, ", addedSpaces = ", addedSpaces, ", addedTabs = ", addedTabs);
333 static class AddTextAction : UndoAction
337 int addedSpaces, addedTabs;
338 type = class(AddTextAction);
341 void Print(EditBox editBox)
343 PrintLn("AddText: y1 = ", y1, "x1 = ", x1, ", y2 = ", y2, ", x2 = ", x2, ", string = ", string, ", addedSpaces = ", addedSpaces, ", addedTabs = ", addedTabs);
351 void Undo(EditBox editBox)
356 editBox.GoToPosition(null, y1, x1);
358 l2 = editBox.lines.first;
359 for(c = 0; c < y2 && l2; c++, l2 = l2.next);
361 editBox.DelCh(l1, y1, x1, l2, y2, x2, true);
362 if(addedTabs || addedSpaces)
363 editBox.DelCh(editBox.line, y1, x1 - (addedSpaces + addedTabs), editBox.line, y1, x1, false);
365 editBox.SetViewToCursor(true);
369 void Redo(EditBox editBox)
371 editBox.GoToPosition(null, y1, x1);
372 editBox.PutS(string);
376 static class DelTextAction : UndoAction
380 bool placeAfter, noHighlight;
382 type = class(DelTextAction);
385 void Print(EditBox editBox)
387 PrintLn("DelText: y1 = ", y1, "x1 = ", x1, ", y2 = ", y2, ", x2 = ", x2, ", string = ", string, ", addedSpaces = ", addedSpaces, ", placeAfter = ", placeAfter);
390 void Undo(EditBox editBox)
392 editBox.GoToPosition(null, y1, x1);
393 editBox.PutS(string);
397 editBox.GoToPosition(null, y1, x1);
403 { int c; editBox.selLine = editBox.lines.first; for(c = 0; c < editBox.selY && editBox.selLine; c++, editBox.selLine = editBox.selLine.next); }
404 //editBox.SetViewToCursor(true);
407 editBox.DelCh(editBox.line, y1, x1 - addedSpaces, editBox.line, y1, x1, false);
416 { int c; editBox.selLine = editBox.lines.first; for(c = 0; c < editBox.selY && editBox.selLine; c++, editBox.selLine = editBox.selLine.next); }
417 //editBox.SetViewToCursor(true);
420 editBox.DelCh(editBox.selLine, y1, x1 - addedSpaces, editBox.selLine, y1, x1, false);
424 void Redo(EditBox editBox)
429 editBox.GoToPosition(null, y1, x1);
432 l2 = editBox.lines.first;
433 for(c = 0; c < y2 && l2; c++, l2 = l2.next);
435 editBox.DelCh(l1, y1, x1, l2, y2, x2, true);
436 editBox.SetViewToCursor(true);
446 static class ReplaceTextAction : UndoAction
448 int y1, x1, y2, x2, y3, x3;
452 int addedSpaces, addedTabs;
454 type = class(ReplaceTextAction);
457 void Print(EditBox editBox)
459 PrintLn("ReplaceText: y1 = ", y1, "x1 = ", x1, ", y2 = ", y2, ", x2 = ", x2, ", y3 = ", y3, ", x3 = ", x3, ", oldString = ", oldString, ", newString = ", newString, ", addedSpaces = ", addedSpaces, ", addedTabs = ", addedTabs, ", placeAfter = ", placeAfter);
462 void Undo(EditBox editBox)
467 editBox.GoToPosition(null, y1, x1);
469 l3 = editBox.lines.first;
470 for(c = 0; c < y3 && l3; c++, l3 = l3.next);
472 editBox.DelCh(l1, y1, x1, l3, y3, x3, true);
474 editBox.PutS(oldString);
475 if(addedSpaces || addedTabs)
476 editBox.DelCh(l1, y1, x1 - (addedSpaces + addedTabs), l1, y1, x1, false);
478 //editBox.PutS(oldString);
481 editBox.GoToPosition(null, y1, x1);
484 { int c; editBox.selLine = editBox.lines.first; for(c = 0; c < editBox.selY && editBox.selLine; c++, editBox.selLine = editBox.selLine.next); }
490 { int c; editBox.selLine = editBox.lines.first; for(c = 0; c < editBox.selY && editBox.selLine; c++, editBox.selLine = editBox.selLine.next); }
494 void Redo(EditBox editBox)
499 editBox.GoToPosition(null, y1, x1);
501 l2 = editBox.lines.first;
502 for(c = 0; c < y2 && l2; c++, l2 = l2.next);
504 editBox.DelCh(l1, y1, x1, l2, y2, x2, true);
506 editBox.PutS(newString);
516 static class MoveTextAction : UndoAction
518 int fy1, fx1, fy2, fx2;
520 type = class(MoveTextAction);
522 void Undo(EditBox editBox)
527 void Redo(EditBox editBox)
533 public class EditLine : struct
541 property const char * text
545 // Only works for single line edit for now...
546 EditBox editBox = this.editBox;
550 get { return this ? buffer : null; }
552 property EditLine prev { get { return this ? prev : null; } };
553 property EditLine next { get { return this ? next : null; } };
554 property int count { get { return this ? count : 0; } };
561 // This makes sure the buffer always contains at least count characters
562 // Keeps a count/2 pad for avoiding always reallocating memory.
563 bool AdjustBuffer(int count)
571 newSize = (count + (count >> 1));
573 // Shrink down the buffer
576 buffer = new char[newSize];
577 if(!buffer) return false;
581 CopyBytes(buffer, this.buffer, count);
584 this.buffer = buffer;
588 // Increase the buffer
589 else if(size < count)
591 buffer = new char[newSize];
592 if(!buffer) return false;
596 CopyBytes(buffer, this.buffer, this.count + 1); // size);
599 this.buffer = buffer;
608 public struct BufferLocation
613 void AdjustDelete(BufferLocation start, BufferLocation end)
615 // Location is before, nothing to do
616 if(y < start.y || (y == start.y && x < start.x))
618 // Location is inside deleted bytes, point to the start
619 if((y >= start.y && (y > start.y || x >= start.x)) &&
620 (y >= end.y && (y > end.y || x >= end.x)))
625 // Location is on another line
627 y -= end.y - start.y;
628 // Location is the last touched line
635 //if(start.line == end.line)
636 x -= end.x - start.x;
645 // Assuming no carriage return before first character ???? fixed?
646 void AdjustAdd(BufferLocation start, BufferLocation end)
648 int numLines = end.y - start.y;
655 if(x > start.x || (x == start.x /*&& (numLines ? true : false)*/))
658 for(c = 0, line = start.line; c<numLines; c++)
661 //x += numLines ? end.x : (end.x - start.x);
662 x += end.x - start.x;
669 public enum EditBoxFindResult { notFound, found, wrapped };
671 static const char * keyWords1[] =
674 "return","break","continue","default","switch","case","if","else","for","while", "do","long","short",
675 "void", "char","int","float","double","signed","unsigned","static", "extern", "struct", "union", "typedef","enum",
677 "#include", "#define", "#pragma", "#if", "#else", "#elif", "#ifdef", "#ifndef", "#endif", "#undef", "#line",
678 "__attribute__", "__stdcall", "_stdcall",
679 "__declspec", "goto",
680 "inline", "__inline__", "_inline", "__inline", "__typeof","__extension__",
681 "asm", "__asm", "_asm", "volatile", "#cpu", "__stdcall__",
682 "__restrict__", "__restrict", "restrict",
685 "class", "private", "public",
687 "delete", "new", "new0", "renew", "renew0", "define",
690 "dllexport", "dllimport", "stdcall",
691 "subclass", "__on_register_module", "namespace", "using",
692 "typed_object", "any_object", "incref", "register", "watch", "stopwatching", "firewatchers", "watchable", "class_designer",
693 "class_fixed", "class_no_expansion", "isset", "class_default_property", "property_category", "class_data", "class_property",
694 "virtual", "thisclass","unichar", "dbtable", "dbindex", "database_open", "dbfield",
697 "uint", "uint32", "uint16", "uint64", "bool", "byte", "int64", "uintptr", "intptr", "intsize", "uintsize",
700 "this", "true", "false", "null", "value",
709 static const char * keyWords2[] =
711 "defined", "warning", null
714 static const char ** keyWords[] = { keyWords1, keyWords2 };
715 #define NUM_KEYWORD_GROUPS (sizeof(keyWords) / sizeof(char **))
716 //static int * keyLen[NUM_KEYWORD_GROUPS];
717 static int keyLen[NUM_KEYWORD_GROUPS][sizeof(keyWords1)];
719 static char searchString[1025], replaceString[1025];
720 static bool matchCase = false, wholeWord = false, searchUp = false;
722 static GoToDialog goToDialog
724 autoCreate = false, isModal = true, text = $"Go To"
727 public class EditBox : CommonControl
729 class_property(icon) = "<:ecere>controls/editBox.png";
733 virtual bool Window::NotifyModified(EditBox editBox);
734 virtual void Window::NotifyCaretMove(EditBox editBox, int line, int charPos);
735 virtual void Window::NotifyUpdate(EditBox editBox);
736 virtual bool Window::NotifyDoubleClick(EditBox editBox, EditLine line, Modifiers mods);
737 virtual void Window::NotifyOvrToggle(EditBox editBox, bool overwrite);
738 virtual bool Window::NotifyKeyDown(EditBox editBox, Key key, unichar ch);
740 virtual bool Window::NotifyCharsAdded(EditBox editBox, BufferLocation before, BufferLocation after, bool pasteOperation);
741 virtual bool Window::NotifyCharsDeleted(EditBox editBox, BufferLocation beforeLoc, BufferLocation after, bool pasteOperation);
742 virtual bool Window::NotifyDropped(EditBox editBox, int x, int y);
744 virtual bool Window::NotifyUnsetModified(EditBox editBox);
746 // Why was this commented out?
747 // It is required otherwise updating font property from property sheet doesn't immediately reflect in form designer,
748 // and the scrollArea property isn't compared properly either.
752 if(font) ComputeFont();
753 SetInitSize(initSize);
759 style.vScroll = true;
765 style.hScroll = true;
769 property bool textHorzScroll { property_category $"Behavior" set { style.hScroll = value; } get { return style.hScroll; } }; // Should cut the text on set to false
770 property bool textVertScroll { property_category $"Behavior" set { style.vScroll = value; } get { return style.vScroll; } };
771 property bool readOnly
773 property_category $"Behavior"
776 style.readOnly = value;
777 itemEditCut.disabled = value || !selection;
778 itemEditDelete.disabled = value || !selection;
779 itemEditPaste.disabled = value;
781 get { return style.readOnly; }
783 property bool multiLine { property_category $"Behavior" set { style.multiLine = value; } get { return style.multiLine; } };
784 property bool freeCaret { property_category $"Behavior" set { style.freeCaret = value; } get { return style.freeCaret; } };
785 property bool tabKey { property_category $"Behavior" set { style.tabKey = value; } get { return style.tabKey; } };
786 property int tabSize { property_category $"Behavior" set { tabSize = value; } get { return tabSize; } };
787 property bool tabSelection { property_category $"Behavior" set { style.tabSel = value; if(value) style.tabKey = true; } get { return style.tabSel; } };
788 property bool smartHome { property_category $"Behavior" set { style.smartHome = value; } get { return style.smartHome; } };
789 property bool autoEmpty { property_category $"Behavior" set { style.autoEmpty = value; } get { return style.autoEmpty; } };
790 property bool noCaret { property_category $"Behavior" set { style.noCaret = value; if(value) { style.readOnly = true; style.stuckCaret = true; } } get { return style.noCaret; } };
791 property int maxLineSize { property_category $"Behavior" set { maxLineSize = value; } get { return maxLineSize; } };
792 property int maxNumLines { property_category $"Behavior" set { maxLines = value; } get { return maxLines; } };
793 property bool useTab { property_category $"Behavior" set { style.useTab = value; itemEditInsertTab.checked = value; } get { return style.useTab; } };
794 property bool syntaxHighlighting { property_category $"Appearance" set { style.syntax = value; } get { return style.syntax; } };
795 property bool noSelect { property_category $"Behavior" set { style.noSelect = value; } get { return style.noSelect; } };
796 property bool allCaps { property_category $"Behavior" set { style.allCaps = value; } get { return style.allCaps; } };
797 property bool autoSize { property_category $"Behavior" set { style.autoSize = value; } get { return style.autoSize; } };
798 property bool wrap { set { style.wrap = value; Update(null); } get { return style.wrap; } };
799 //property bool lineNumbers { set { style.lineNumbers = value; } get { return style.lineNumbers; } };
800 property int numLines { get { return this ? lineCount : 0; } };
801 property int lineNumber { get { return y; } }; // TODO: Change to property of EditLine this.line.number
802 property int column { get { return col; } }; // TODO: Add Set
803 property int charPos { get { return x; } }; // TODO: Add Set
804 property EditLine firstLine { get { return lines.first; } }; // Change these to a List<EditLine>... (this.lines[10].text)
805 property EditLine lastLine { get { return lines.last; } };
806 property EditLine line { get { return this.line; } }; // TODO: Add Set this.line = this.lines[10]
807 property const char * contents
809 property_category $"Data"
814 undoBuffer.dontRecord++;
816 DelCh(this.lines.first, 0, 0, this.lines.last, this.lineCount-1, ((EditLine)(this.lines.last)).count, true);
819 //SetViewToCursor(true);
822 undoBuffer.dontRecord--;
828 char * buffer = null;
831 /* Can't implement this right now because of memory leak... Need string reference counting...
838 for(line = lines.first; line; line = line.next)
839 len += strlen(line.buffer);
841 buffer = new char[len+1];
843 for(line = lines.first; line; line = line.next)
845 int lineLen = strlen(line.buffer);
846 memcpy(buffer + len, line.buffer, lineLen);
852 buffer = this.line ? this.line.buffer : null;
857 property bool overwrite { get { return overwrite; } };
858 property bool caretFollowsScrolling { get { return style.cursorFollowsView; } set { style.cursorFollowsView = value; } }
860 property char * multiLineContents
864 char * buffer = null;
871 for(line = lines.first; line; line = line.next)
872 len += strlen(line.buffer)+1;
874 buffer = new char[len+1];
876 for(line = lines.first; line; line = line.next)
878 int lineLen = strlen(line.buffer);
879 memcpy(buffer + len, line.buffer, lineLen);
881 if(line.next) buffer[len++] = '\n';
894 return this.line.buffer;
899 void SetLineText(const char * text)
903 EditLine_SetText(this.line, text);
907 property Color selectionColor { set { selectionColor = value; } get { return selectionColor; } isset { return selectionColor ? true : false; } };
908 property Color selectionText { set { selectionText = value; } get { return selectionText; } isset { return selectionText ? true : false; } };
909 property SyntaxColorScheme syntaxColorScheme { set { delete colorScheme; colorScheme = value; incref colorScheme; } }
910 property bool recordUndoEvent { set { undoBuffer.recordAsOne = value; undoBuffer.firstEvent = true; } get { return undoBuffer.recordAsOne; } };
912 // selectionStart.line, selectionStart.column (With Set)
913 // selection.line1, selection.line2, selection.column1, selection.column2 (Read only)
927 // Position of Caret (Not necessarily displayed position)
930 // Position of beginning of block (Block ends at (x,y))
932 // line is line at carret, selLine is line at beginning of block
933 EditLine line, selLine, dropLine;
934 // Mouse selection Moving virtual caret
939 // ViewX is x offset in pixels, ViewY is y offset in lines
941 // viewLine is first displayed line
944 // start and end of area to redraw
947 // MaxLine is the longest line
949 // MaxLength is the longest line's length
952 // MouseSelect is true once button is pressed, overwrite is true if mode is on
953 bool mouseSelect, mouseMove, overwrite, wordSelect;
954 // Timer is used for mouse selection scrolling
957 window = this, delay = 0.1;
966 OnMouseMove(mouseX, mouseY, -1);
971 // (mouseX,mouseY) is the position of the mouse in the edit box
976 void (* FontExtent)(Display display, Font font, const char * text, int len, int * width, int * height);
979 bool rightButtonDown;
982 UndoBuffer undoBuffer { data = this };
984 ColorAlpha selectionColor, selectionText;
985 SyntaxColorScheme colorScheme { };
990 Menu editMenu { menu, $"Edit", e };
993 editMenu, $"Cut\tCtrl+X", t, disabled = true;
995 bool NotifySelect(MenuItem item, Modifiers mods)
997 if(!(style.readOnly))
1002 MenuItem itemEditCopy
1004 editMenu, $"Copy\tCtrl+C", c, disabled = true;
1006 bool NotifySelect(MenuItem item, Modifiers mods)
1012 MenuItem itemEditPaste
1014 editMenu, $"Paste\tCtrl+V", p;
1016 bool NotifySelect(MenuItem item, Modifiers mods)
1018 if(!(style.readOnly))
1023 MenuItem itemEditDelete
1025 editMenu, $"Delete\tDel", d, disabled = true;
1027 bool NotifySelect(MenuItem item, Modifiers mods)
1029 if(!(style.readOnly))
1034 MenuDivider { editMenu };
1035 MenuItem itemEditSelectAll
1037 editMenu, $"Select All\tCtrl+A", a;
1039 bool NotifySelect(MenuItem item, Modifiers mods)
1045 MenuDivider { editMenu };
1046 MenuItem itemEditUndo
1048 editMenu, $"Undo\tCtrl+Z", u;
1051 bool NotifySelect(MenuItem item, Modifiers mods)
1057 MenuItem itemEditRedo
1059 editMenu, $"Redo\tCtrl+Y", o;
1062 bool NotifySelect(MenuItem item, Modifiers mods)
1068 MenuDivider { editMenu };
1071 editMenu, $"Find Previous\tShift-F3", e, shiftF3;
1073 bool NotifySelect(MenuItem item, Modifiers mods)
1076 Find(searchString, wholeWord, matchCase, false);
1078 itemEditFind.NotifySelect(this, item, mods);
1084 editMenu, $"Find Next\tF3", n, f3;
1086 bool NotifySelect(MenuItem item, Modifiers mods)
1089 Find(searchString, wholeWord, matchCase, true);
1091 itemEditFind.NotifySelect(this, item, mods);
1095 MenuItem itemEditFind
1097 editMenu, $"Find...\tCtrl+F", f, ctrlF;
1099 bool NotifySelect(MenuItem item, Modifiers mods)
1103 editBox = this, master = master, isModal = true, searchString = searchString, matchCase = matchCase, wholeWord = wholeWord,
1104 searchUp = searchUp;
1107 // Fix dialog from above shouldn't be visible
1108 // void NotifyDestroyed(FindDialog dialog, DialogResult result)
1109 void NotifyDestroyed(Window window, DialogResult result)
1111 FindDialog dialog = (FindDialog) window;
1112 searchUp = dialog.searchUp;
1113 strcpy(searchString, dialog.searchString);
1114 matchCase = dialog.matchCase;
1115 wholeWord = dialog.wholeWord;
1124 editMenu, $"Replace...\tCtrl+R", r, ctrlR;
1126 bool NotifySelect(MenuItem item, Modifiers mods)
1128 ReplaceDialog dialog
1132 searchString = searchString,
1133 replaceString = replaceString,
1134 matchCase = matchCase,
1135 wholeWord = wholeWord,
1139 // void NotifyDestroyed(ReplaceDialog dialog, DialogResult result)
1140 void NotifyDestroyed(Window window, DialogResult result)
1142 ReplaceDialog dialog = (ReplaceDialog)window;
1143 const char * replace = dialog.replaceString;
1145 strcpy(replaceString, replace);
1146 strcpy(searchString, dialog.searchString);
1147 matchCase = dialog.matchCase;
1148 wholeWord = dialog.wholeWord;
1155 MenuDivider { editMenu };
1158 editMenu, $"Go To...\tCtrl+G", g, ctrlG;
1160 bool NotifySelect(MenuItem item, Modifiers mods)
1162 goToDialog.editBox = this;
1163 goToDialog.master = master;
1164 goToDialog.Create();
1168 MenuDivider { editMenu };
1169 MenuItem itemEditInsertTab
1171 editMenu, $"Insert Tabs", i, checkable = true;
1173 bool NotifySelect(MenuItem item, Modifiers mods)
1175 style.useTab = item.checked;
1181 snapVertScroll = true;
1182 snapHorzScroll = true;
1186 static bool syntaxInit = false;
1191 for(g = 0; g<NUM_KEYWORD_GROUPS; g++)
1193 for(c = 0; keyWords[g][c]; c++);
1194 //keyLen[g] = new int[c];
1195 for(c = 0; keyWords[g][c]; c++)
1197 keyLen[g][c] = strlen(keyWords[g][c]);
1201 colorScheme.commentColor = dimGray;
1202 colorScheme.charLiteralColor = crimson;
1203 colorScheme.stringLiteralColor = crimson;
1204 colorScheme.preprocessorColor = green;
1205 colorScheme.numberColor = teal;
1206 colorScheme.keywordColors = [ blue, blue ];
1209 FontExtent = Display::FontExtent;
1211 lines.offset = (uint)(uintptr)&((EditLine)0).prev;
1213 style = EditBoxBits { hScroll = true };
1215 // cursor = guiApp.GetCursor(IBeam);
1217 // Default Properties
1219 maxLineSize = 1024;*/
1222 maxLineSize = MAXINT;
1227 mouseSelect = this.mouseMove = false;
1230 x = selX = selY = 0;
1233 line = selLine = null;
1239 endY = clientSize.h;
1242 // Default space to 8 in case window is not constructed
1245 undoBuffer.dontRecord++;
1247 undoBuffer.dontRecord--;
1249 viewLine = lines.first;
1250 style.recomputeSyntax = true;
1256 UpdateCaretPosition(true);
1262 lines.Free(EditLine::Free);
1265 void FlushBuffer(Surface surface, EditLine line, int wc, int * renderStart, int * x, int y, int numSpaces, bool drawSpaces, Box box)
1267 int count = wc - *renderStart;
1270 if(y + space.h >= box.top && y <= box.bottom)
1276 //FontExtent(display, font, line.buffer + *renderStart, count, &w, null);
1277 surface.TextFont(font);
1278 surface.TextExtent(line.buffer + *renderStart, count, &w, null);
1279 if(*x + w + XOFFSET > 0)
1280 surface.WriteText(XOFFSET + *x,y, line.buffer + *renderStart, count);
1285 w = numSpaces; // * space.w;
1286 if(*x + w + XOFFSET > 0 && (drawSpaces || surface.GetTextOpacity()))
1287 surface.Area(XOFFSET + *x - 1, y, XOFFSET + *x + w, y + space.h-1);
1288 // WHATS UP WITH THIS... surface.Area(XOFFSET + *x, y, XOFFSET + *x + w, y + space.h-1);
1296 bool CheckColors(EditLine line, int wc, bool selection, int selX, int editX, bool *selected,
1297 Color selectionForeground, Color selectionBackground, Color textColor, Color *foreground, Color *background, bool *opacity, int *overwrite)
1303 if((wc == selX && line == selLine) || (wc == editX && line == this.line))
1307 *foreground = (*selected) ? selectionForeground : textColor;
1308 *background = selectionBackground;
1309 *opacity = *selected;
1316 if(this.overwrite && active)
1318 if((style.stuckCaret && wc == line.count && !line.next) ||
1319 (!mouseMove && line == this.line && wc == editX))
1328 void FigureStartSyntaxStates(EditLine firstLine, bool reset)
1332 bool inMultiLineComment = reset ? false : style.inMultiLineComment;
1333 bool wasInMultiLine = reset ? false : style.wasInMultiLine;
1334 bool inString = false;
1335 bool inQuotes = false;
1336 bool inPrep = reset ? false : style.inPrep;
1337 bool inSingleLineComment = false;
1338 bool escaped = reset ? false : style.escaped;
1339 bool continuedSingleLineComment = reset ? false : style.continuedSingleLineComment;
1340 bool continuedString = reset ? false : style.continuedString;
1341 bool continuedQuotes = reset ? false : style.continuedQuotes;
1343 EditLine line = reset ? lines.first : firstLine;
1344 // int maxBackUp = 1000, c;
1345 // for(c = 0, line = viewLine; c < maxBackUp && line && line.prev; line = line.prev);
1346 for(; line != viewLine; line = line.next)
1348 char * text = line.buffer;
1351 bool lastWasStar = false;
1352 bool firstWord = true;
1353 if(!escaped) inPrep = false;
1354 inSingleLineComment = continuedSingleLineComment;
1356 inString = continuedString;
1357 inQuotes = continuedQuotes;
1360 for(c = 0; (ch = text[c]); c++)
1362 bool wasEscaped = escaped;
1363 bool backLastWasStar = lastWasStar;
1364 bool backWasInMultiLine = wasInMultiLine;
1366 lastWasStar = false;
1367 wasInMultiLine = inMultiLineComment;
1370 if(!inSingleLineComment && !inMultiLineComment && !inQuotes && !inString)
1372 if(text[c+1] == '/')
1374 inSingleLineComment = true;
1376 else if(text[c+1] == '*')
1378 inMultiLineComment = true;
1381 else if(backLastWasStar)
1382 inMultiLineComment = false;
1386 if(backWasInMultiLine) lastWasStar = true;
1388 else if(ch == '\"' && !inSingleLineComment && !inMultiLineComment && !inQuotes)
1390 if(inString && !wasEscaped)
1399 else if(ch == '\'' && !inSingleLineComment && !inMultiLineComment && !inString)
1401 if(inQuotes && !wasEscaped)
1413 else if(ch == '#' && !inQuotes && !inString && !inMultiLineComment && !inSingleLineComment)
1420 else if(ch != ' ' && ch != '\t')
1423 if(line.count && line.text[line.count - 1] == '\\')
1425 continuedSingleLineComment = inSingleLineComment;
1426 continuedString = inString;
1427 continuedQuotes = inQuotes;
1431 continuedSingleLineComment = false;
1432 continuedString = false;
1433 continuedQuotes = false;
1437 style.continuedSingleLineComment = continuedSingleLineComment;
1438 style.continuedString = continuedString;
1439 style.continuedQuotes = continuedQuotes;
1440 style.inMultiLineComment = inMultiLineComment;
1441 style.wasInMultiLine = wasInMultiLine;
1442 style.inPrep = inPrep;
1443 style.escaped = escaped;
1447 /*void OnDrawOverChildren(Surface surface)
1449 if(style.lineNumbers)
1451 int currentLineNumber = this.viewY + 1;
1454 for(i = 0; i * space.h < box.height; i++)
1456 // ********* LINE NUMBERING *********
1457 surface.SetForeground(Color{60, 60, 60});
1458 //Highlight current line
1459 if(this.caretY / space.h == currentLineNumber - 1)
1460 surface.SetBackground(Color{220, 220, 220});
1462 surface.SetBackground(Color{230, 230, 230});
1463 surface.textOpacity = true;
1465 sprintf(lineText,"%5u ", currentLineNumber % 100000);
1466 if(currentLineNumber > this.lineCount)
1467 surface.WriteText(0,i*space.h+1," ",6);
1469 surface.WriteText(0,i*space.h+1,lineText,6);
1471 currentLineNumber++;
1476 void OnRedraw(Surface surface)
1480 bool selected = false, selection = true;
1482 Color selectionBackground = selectionColor ? selectionColor : SELECTION_COLOR;
1483 Color selectionForeground = selectionText ? selectionText : SELECTION_TEXT;
1484 Color defaultTextColor = property::foreground;
1487 int maxW = clientSize.w;
1489 Color foreground, background;
1492 // Overwrite Caret Stuff
1494 int overWriteX = 0, overWriteY = 0;
1497 // ****** SYNTAX STATES ******
1498 bool inMultiLineComment = style.inMultiLineComment;
1499 bool inString = false;
1500 bool inQuotes = false;
1501 bool inPrep = style.inPrep;
1502 bool inSingleLineComment = false;
1503 bool escaped = style.escaped;
1504 bool continuedSingleLineComment = style.continuedSingleLineComment;
1505 bool continuedString = style.continuedString;
1506 bool continuedQuotes = style.continuedQuotes;
1507 bool wasInMultiLine = style.wasInMultiLine;
1508 // ****** ************* ******
1511 defaultTextColor = Color { 85, 85, 85 };
1512 textColor = defaultTextColor;
1515 Abs(selectionBackground.r - property::background.r) +
1516 Abs(selectionBackground.g - property::background.g) +
1517 Abs(selectionBackground.b - property::background.b) < 92)
1519 selectionBackground = formColor;
1520 selectionForeground = selectionColor ? selectionColor : SELECTION_COLOR;
1523 surface.TextFont(this.font);
1525 surface.SetBackground(GDefaultPalette()[RND_Get(1, 15)]);
1526 surface.Area(0,0,MAXINT,MAXINT);
1531 surface.SetBackground(formColor);
1532 surface.Area(0,0,clientSize.w, clientSize.h);
1535 if(this.selX == this.x && this.selY == this.y)
1545 editX = Min(this.x,this.line.count);
1546 selX = Min(this.selX,this.selLine.count);
1549 selected = (this.selY < this.viewY) ^ (this.y < this.viewY);
1551 foreground = selected ? selectionForeground : textColor;
1552 background = selectionBackground;
1558 surface.SetForeground(foreground);
1559 surface.SetBackground(background);
1560 surface.TextOpacity(opacity);
1562 surface.GetBox(box);
1564 for(line = this.viewLine; line; line = line.next)
1566 int x = -this.viewX;
1570 Color newTextColor = textColor = defaultTextColor;
1571 bool lineComplete = false;
1574 // ****** SYNTAX HIGHLIGHTING ******
1575 bool lastWasStar = false;
1576 bool trailingSpace = false;
1577 bool firstWord = true;
1578 if(!escaped) inPrep = false;
1579 inSingleLineComment = continuedSingleLineComment;
1581 inString = continuedString;
1582 inQuotes = continuedQuotes;
1583 // *********************************
1585 /* === DEBUGGING TOOL FOR MAXLINE ===
1587 if(line == this.maxLine)
1589 surface.SetBackground(GREEN|0xFF000000);
1590 surface.TextOpacity(true);
1594 surface.TextOpacity(selected ? true : false);
1595 surface.SetBackground(selected ? SELECTION_COLOR|0xFF000000 : BLACK|0xFF000000);
1599 if(line == this.selLine && line == this.line)
1601 end = Max(line.count, this.x);
1602 end = Max(end, this.selX);
1604 else if(line == this.selLine)
1605 end = Max(line.count, this.selX);
1606 else if(line == this.line)
1607 end = Max(line.count, this.x);
1615 bool spacing = true;
1616 bool cantHaveWords = false;
1623 lineComplete = false;
1626 textColor = newTextColor;
1629 foreground = textColor;
1630 surface.SetForeground(textColor);
1634 for(; c<end && !cantHaveWords;)
1638 bufferLen += wordLen;
1642 /*if(line.count > 4000)
1649 for(; c<line.count; c++)
1651 unichar ch = line.buffer[c];
1652 unichar bf = (wordLen == 1) ? line.buffer[c-1] : 0;
1653 //if(ch == ' ' || ch == '\t' || (wordLen && (ch == '(' || ch == ')' || ch == ';' || ch == ':')) || (wordLen == 1 && line.buffer[c-1] == '('))
1654 if(CharMatchCategories(ch, separators) || /*ch == ' ' ||*/ ch == '\t' ||
1655 (wordLen && !CharMatchCategories(ch, numbers|letters|marks|connector) && ch != '#' /*&& ch != '_'*/) ||
1656 (bf && !CharMatchCategories(bf, numbers|letters|separators|marks|connector) && bf != '#' && bf != '\t' /*&& bf != '_' && bf != ' '*/))
1659 trailingSpace = false;
1665 for(; c<line.count; c++)
1667 unichar ch = line.buffer[c];
1668 if(ch == '\t' || ch == ' ')
1670 cantHaveWords = true;
1671 if(ch == ' ' && c == line.count-1)
1672 trailingSpace = true;
1676 if(ch != ' ' && ch != '\t')
1681 if(c == line.count && c < end)
1690 cantHaveWords = true;
1695 lastWasStar = false;
1701 bool backEscaped = escaped;
1702 bool backLastWasStar = lastWasStar;
1703 bool backInMultiLineComment = inMultiLineComment;
1704 bool backInString = inString;
1705 bool backInQuotes = inQuotes;
1706 bool backInPrep = inPrep;
1707 bool backInSingleLineComment = inSingleLineComment;
1708 bool backWasInMultiLine = wasInMultiLine;
1710 char * word = line.buffer + c - wordLen;
1712 bool wasEscaped = escaped;
1714 lastWasStar = false;
1716 wasInMultiLine = inMultiLineComment;
1718 // Determine Syntax Highlighting
1719 newTextColor = defaultTextColor;
1722 if(inSingleLineComment || inMultiLineComment)
1724 newTextColor = colorScheme.commentColor;
1728 newTextColor = colorScheme.charLiteralColor;
1732 newTextColor = colorScheme.stringLiteralColor;
1736 newTextColor = colorScheme.preprocessorColor;
1738 if(wordLen == 1 && word[0] == '/')
1740 if(!inSingleLineComment && !inMultiLineComment && !inQuotes && !inString)
1744 inSingleLineComment = true;
1745 newTextColor = colorScheme.commentColor;
1747 else if(word[1] == '*')
1749 inMultiLineComment = true;
1750 newTextColor = colorScheme.commentColor;
1753 else if(backLastWasStar)
1754 inMultiLineComment = false;
1756 else if(wordLen == 1 && word[0] == '*')
1758 if(backWasInMultiLine)
1761 else if(!inSingleLineComment && !inMultiLineComment && !inQuotes && wordLen == 1 && word[0] == '\"')
1763 if(inString && !wasEscaped)
1770 newTextColor = colorScheme.stringLiteralColor;
1773 else if(!inSingleLineComment && !inMultiLineComment && !inString && wordLen == 1 && word[0] == '\'')
1775 if(inQuotes && !wasEscaped)
1780 newTextColor = colorScheme.charLiteralColor;
1783 else if(wordLen == 1 && word[0] == '\\')
1788 else if(x < box.right && !inQuotes && !inString && !inMultiLineComment && !inSingleLineComment && (isdigit(word[0]) || (word[0] == '.' && isdigit(word[1]))))
1790 char * dot = word[wordLen] == '.' ? word + wordLen : null;
1791 bool isReal = dot != null;
1798 bool isHex = (word[0] == '0' && (word[1] == 'x' || word[1] == 'X'));
1801 exponent = strchrmax(word, 'p', wordLen);
1802 if(!exponent) exponent = strchrmax(word, 'P', wordLen);
1806 exponent = strchrmax(word, 'e', wordLen);
1807 if(!exponent) exponent = strchrmax(word, 'E', wordLen);
1809 isReal = exponent != null;
1812 strtod(word, &s); // strtod() seems to break on hex floats (e.g. 0x23e3p12, 0x1.fp3)
1814 strtol(word, &s, 0);
1820 int gotF = 0, gotL = 0, gotU = 0, gotI = 0;
1823 for(i = 0; valid && i < 5 && (ch = s[i]) && (isalnum(ch) || ch == '_'); i++)
1827 case 'f': case 'F': gotF++; if(gotF > 1 || !isReal) valid = false; break;
1830 if(gotL > 2 || isReal || (gotL == 2 && (s[i-1] != ch)))
1833 case 'u': case 'U': gotU++; if(gotU > 1 || isReal) valid = false; break;
1834 case 'i': case 'I': case 'j': case 'J': gotI++; if(gotI > 1) valid = false; break;
1835 default: valid = false;
1839 // Don't highlight numbers with too many decimal points
1840 if(s[0] == '.' && isdigit(s[1]))
1843 while(s[0] == '.' && isdigit(s[1]))
1845 int newWordLen = s - word;
1846 c += newWordLen - wordLen;
1847 wordLen = newWordLen;
1850 newWordLen = s - word;
1851 c += newWordLen - wordLen;
1852 wordLen = newWordLen;
1856 int newWordLen = s + i - word;
1857 newTextColor = colorScheme.numberColor;
1858 c += newWordLen - wordLen;
1859 wordLen = newWordLen;
1861 else if(dot && dot > word && dot < s)
1862 newTextColor = colorScheme.numberColor;
1867 if(!inQuotes && !inString && !inMultiLineComment && !inSingleLineComment && word[0] == '#')
1872 newTextColor = colorScheme.preprocessorColor;
1875 if(x < box.right && !inQuotes && !inString && !inMultiLineComment && !inSingleLineComment)
1877 for(g = 0; g < ((inPrep && word[0] != '#') ? 2 : 1); g++)
1879 const char ** keys = keyWords[g];
1880 int * len = keyLen[g];
1881 for(ccc = 0; keys[ccc]; ccc++)
1883 if(len[ccc] == wordLen && !strncmp(keys[ccc], word, wordLen))
1885 newTextColor = colorScheme.keywordColors[g];
1894 // If highlighting for this word is different, break
1895 if(newTextColor != textColor)
1899 // Better solution than going back?
1902 // Reset syntax flags
1903 escaped = backEscaped;
1904 lastWasStar = backLastWasStar;
1905 inMultiLineComment = backInMultiLineComment;
1906 inString = backInString;
1907 inQuotes = backInQuotes;
1908 inPrep = backInPrep;
1909 inSingleLineComment = backInSingleLineComment;
1910 wasInMultiLine = backWasInMultiLine;
1915 textColor = newTextColor;
1918 foreground = textColor;
1919 surface.SetForeground(textColor);
1926 // If we're not breaking, this can't be rendered as spacing anymore
1929 // Do word wrapping here
1935 FontExtent(display, font, line.buffer + start, bufferLen + wordLen, &tw, null);
1940 w += numSpaces * space.w;
1942 if(x + viewX + w > maxW)
1944 // Avoid an endless loop until we fix wrapping
1945 if(c - wordLen > start)
1948 lineComplete = true;
1954 bufferLen += wordLen;
1959 int renderStart = start;
1961 bool flagTrailingSpace = false;
1965 // Render checking if we need to split because of selection or to find where to draw insert caret
1966 for(wc = start; wc < start + bufferLen; wc++)
1968 flush = CheckColors(line, wc, selection, selX, editX, &selected, selectionForeground,
1969 selectionBackground, textColor, &foreground, &background, &opacity, &overWrite);
1972 overWriteCh = (wc < line.count) ? line.buffer[wc] : ' ';
1973 if(overWriteCh == '\t') overWriteCh = ' ';
1978 flagTrailingSpace = numSpaces && trailingSpace && style.syntax && start + bufferLen == line.count && line != this.line;
1979 if(flagTrailingSpace) surface.SetBackground(red);
1980 FlushBuffer(surface, line, wc, &renderStart, &x, y, numSpaces, flagTrailingSpace, box);
1989 surface.TextOpacity(opacity);
1990 surface.SetBackground(background);
1991 surface.SetForeground(foreground);
1998 if(wc < line.count && line.buffer[wc] == '\t')
2000 numSpaces += (tabSize * space.w) - ((x + numSpaces + viewX) % (tabSize * space.w));
2004 numSpaces += space.w;
2008 flagTrailingSpace = numSpaces && trailingSpace && style.syntax && start + bufferLen == line.count && line != this.line;
2009 if(flagTrailingSpace) surface.SetBackground(red);
2010 FlushBuffer(surface, line, wc, &renderStart, &x, y, numSpaces, flagTrailingSpace, box);
2015 if(CheckColors(line, c, selection, selX, editX, &selected, selectionForeground,
2016 selectionBackground, textColor, &foreground, &background, &opacity, &overWrite))
2025 surface.TextOpacity(opacity);
2026 surface.SetBackground(background);
2027 surface.SetForeground(foreground);
2030 if(style.freeCaret && selected)
2032 surface.SetBackground(selectionBackground);
2033 surface.Area(x + XOFFSET - 1,y,clientSize.w-1,y+this.space.h-1);
2034 // TEST: surface.Area(x + XOFFSET,y,clientSize.w-1,y+this.space.h-1);
2039 if(style.freeCaret && selected)
2041 surface.SetBackground(selectionBackground);
2042 surface.Area(x + XOFFSET - 1,y,clientSize.w-1,y+this.space.h-1);
2045 if(line.count && line.text[line.count - 1] == '\\')
2047 continuedSingleLineComment = inSingleLineComment;
2048 continuedString = inString;
2049 continuedQuotes = inQuotes;
2053 continuedSingleLineComment = false;
2054 continuedString = false;
2055 continuedQuotes = false;
2059 if(y > box.bottom) // >=clientSize.h)
2065 surface.TextOpacity(true);
2066 surface.SetForeground(black);
2067 surface.SetBackground(Color {255,255,85});
2068 surface.WriteText(XOFFSET + overWriteX,overWriteY, &overWriteCh, 1);
2072 void FixScrollArea()
2074 if(style.hScroll || style.vScroll)
2076 int width = maxLength + XOFFSET;
2077 int height = lineCount * space.h;
2078 if(style.freeCaret && line)
2083 width = Max(line.length + (x - line.count) * space.w, maxLength + XOFFSET);
2087 if(selX > selLine.count)
2088 width = Max(selLine.length + (selX - selLine.count) * space.w, maxLength + XOFFSET);
2093 SetScrollLineStep(8, space.h);
2094 SetScrollArea(width, height, true);
2098 void ComputeLength(EditLine line)
2103 for(c = 0; c < line.count; )
2112 for(len = 0; c < line.count; c += numBytes)
2114 ch = line.buffer[c];
2115 numBytes = UTF8_NUM_BYTES(ch);
2117 if(ch == ' ' || ch == '\t')
2124 if(!len && ch == ' ')
2129 else if(!len && ch == '\t')
2131 w = (tabSize * space.w) - (x % (tabSize * space.w));
2135 FontExtent(display, font, line.buffer + start, len, &w, null);
2146 if(line.length > this.maxLength)
2148 this.maxLine = line;
2149 this.maxLength = line.length;
2151 if(style.autoSize) AutoSize();
2160 this.maxLine = null;
2162 for(line = lines.first; line; line = line.next)
2164 if(line.length > this.maxLength)
2166 this.maxLength = line.length;
2167 this.maxLine = line;
2171 if(style.autoSize) AutoSize();
2176 if(this.selY != this.y)
2178 else if(this.selX != this.x) // commented out to erase caret: if(this.selX != this.x)
2182 void ComputeColumn()
2184 // Adjust new edit X position according to tabs
2185 int c, position = 0;
2188 for(c = 0; c<this.line.count && c<this.x && (ch = UTF8_GET_CHAR(this.line.buffer + c, nb)); c+= nb)
2190 // TODO: MIGHT WANT TO RETHINK WHAT COLUMN SHOULD BE REGARDING TABS
2192 position += this.tabSize - (position % this.tabSize);
2196 position += this.x - c;
2197 this.col = position;
2200 void DelCh(EditLine l1, int y1, int c1, EditLine l2, int y2, int c2, bool placeAfter)
2202 _DelCh(l1, y1, c1, l2, y2, c2, placeAfter, true, null);
2205 bool HasCommentOrEscape(EditLine line)
2207 bool hadComment = strstr(line.buffer, "/*") || strstr(line.buffer, "*/");
2212 for(c = line.count-1; c >= 0; c--)
2214 char ch = line.buffer[c];
2220 else //if(ch != ' ' && ch != '\t')
2227 int _DelCh(EditLine l1, int y1, int c1, EditLine l2, int y2, int c2, bool placeAfter, bool highlight, int * addedSpacesPtr)
2229 EditLine line = l1, next;
2231 int oldCount1, oldCount2;
2232 int y, firstViewY, firstY, firstDropY, firstSelY;
2235 DelTextAction action = null;
2236 bool hadComment = false;
2240 hadComment = HasCommentOrEscape(line);
2242 if(y2 > y1 || c2 > c1)
2244 if(start < l1.count)
2246 while(!UTF8_IS_FIRST(l1.buffer[start]) && start)
2250 oldCount1 = l1.count;
2252 while(c1 < oldCount1)
2254 byte ch = buffer[c1];
2255 if(UTF8_IS_FIRST(ch)) break;
2259 oldCount2 = l2.count;
2261 while(c2 < oldCount2)
2263 byte ch = buffer[c2];
2264 if(UTF8_IS_FIRST(ch)) break;
2269 if(!undoBuffer.dontRecord && (y2 > y1 || c2 > c1))
2274 len = GetText(null, l1, y1, start, l2, y2, c2, false, false);
2275 string = new char[len];
2276 action = DelTextAction { y1 = y1, x1 = start, y2 = y2, x2 = c2, string = string, placeAfter = placeAfter, noHighlight = !highlight };
2277 GetText(string, l1, y1, start, l2, y2, c2, false, false);
2281 //oldCount1 = l1.count;
2282 //oldCount2 = l2.count;
2285 BufferLocation before = { l1,y1,c1}, after = { l2,y2,c2 };
2286 NotifyCharsDeleted(master, this, &before, &after, this.pasteOperation);
2289 if(c2 > oldCount2) c2 = oldCount2;
2290 if(!(style.freeCaret))
2291 if(c1 > oldCount1) c1 = oldCount1;
2292 newLineCount = c1 + l2.count-c2;
2298 buffer = new char[line.size ? line.size : 1];
2300 buffer = new char[line.size];
2301 // TODO: Better handling of these allocation failures
2302 if(!buffer) return extras;
2303 CopyBytes(buffer,l2.buffer,oldCount1 + 1/*line.count + 1*//*line.size*/);
2308 // TODO: Better handling of these allocation failures
2309 if(!line.AdjustBuffer(newLineCount))
2313 /*if(newLineCount > 4000 || newLineCount < 0)
2314 printf("Warning");*/
2316 line.count = newLineCount;
2318 memmove(l1.buffer+c1, buffer+c2,line.count-c1);
2323 action.addedSpaces = c1-oldCount1;
2325 if(action.addedSpaces > action.x1)
2331 if(addedSpacesPtr) *addedSpacesPtr = c1-oldCount1;
2332 FillBytes(l1.buffer+oldCount1,' ',c1-oldCount1);
2334 line.buffer[line.count] = '\0';
2341 this.dropX -= c2-c1;
2342 this.dropX = Max(this.dropX,0);
2344 this.x = Max(this.x,0);
2351 firstViewY = this.viewY;
2353 firstDropY = this.dropY;
2354 firstSelY = this.selY;
2355 for(line = l1;line;line = next, y++)
2363 if(line == this.viewLine)
2365 if(this.viewLine.next)
2367 this.viewLine = this.viewLine.next;
2369 style.recomputeSyntax = true;
2373 this.viewLine = this.viewLine.prev;
2375 style.recomputeSyntax = true;
2378 else if(y < firstViewY)
2380 if(line == this.line)
2384 this.line = this.line.next;
2385 this.x = this.line.count;
2390 this.line = this.line.prev;
2391 this.x = this.line.count;
2398 if(line == this.dropLine)
2400 if(this.dropLine.next)
2402 this.dropLine = this.dropLine.next;
2403 this.dropX = this.dropLine.count;
2407 this.dropLine = this.dropLine.prev;
2408 this.dropX = this.dropLine.count;
2412 else if(y < firstDropY)
2414 if(line == this.selLine)
2416 if(this.selLine.next)
2418 this.selLine = this.selLine.next;
2419 this.selX = this.selLine.count;
2423 this.selLine = this.selLine.prev;
2424 this.selX = this.selLine.count;
2428 else if(y < firstSelY)
2432 if(line == l2) break;
2436 if(style.syntax && (hadComment || HasCommentOrEscape(this.line)))
2439 style.recomputeSyntax = true;
2444 bool DelSel(int * addedSpacesPtr)
2446 if(this.line != this.selLine || this.x != this.selX)
2448 if(this.selY < this.y)
2450 _DelCh(this.selLine, this.selY, this.selX, this.line, this.y, this.x, true, true, addedSpacesPtr);
2453 this.line = this.selLine;
2455 else if(this.selY > this.y)
2457 _DelCh(this.line, this.y, this.x, this.selLine, this.selY, this.selX, false, true, addedSpacesPtr);
2460 this.selLine = this.line;
2462 else if(this.selX < this.x)
2464 _DelCh(this.selLine, this.selY, this.selX, this.line, this.y, this.x, true, true, addedSpacesPtr);
2467 this.line = this.selLine;
2471 _DelCh(this.line, this.y, this.x, this.selLine, this.selY, this.selX, false, true, addedSpacesPtr);
2474 this.selLine = this.line;
2482 bool AddToLine(const char * stringLine, int count, bool LFComing, int * addedSpacesPtr, int * addedTabsPtr)
2484 bool hadComment = false;
2485 // Add the line here
2486 EditLine line = this.line;
2488 // The purpose of this is solely to lock a max number of characters if no HSCROLLING is present
2489 if(!style.hScroll && created)
2491 int endX = (style.freeCaret) ? this.x : Min(this.x, line.count);
2496 // Lock if no place to display.
2499 else if(endX > this.x)
2500 max = Max(this.x, line.count);
2504 for(x = 0, c = 0; c < max+count; )
2508 const char * string;
2509 if(c < Min(this.x, line.count))
2510 string = line.buffer + c;
2513 else if(c < endX + count)
2514 string = stringLine + c - endX;
2516 string = line.buffer + c - endX - count;
2520 w = (tabSize * space.w) - (x % (tabSize * space.w));
2524 numBytes = UTF8_NUM_BYTES(*string);
2525 FontExtent(display, this.font, string, numBytes, &w, null);
2529 if(x >= clientSize.w)
2534 c += numBytes; // - 1;
2540 int addedSpaces = 0;
2543 // Add blank spaces if EES_FREECARET
2544 if(this.x > line.count)
2554 for(c = 0; c<line.count; c++)
2556 if(this.line.buffer[c] == '\t')
2557 position += this.tabSize - (position % this.tabSize);
2561 wantedPosition = position + (this.x - line.count);
2563 // A tab is too much...
2564 if(position + (this.tabSize - (position % this.tabSize)) > wantedPosition)
2565 addedSpaces = wantedPosition - position;
2570 position += this.tabSize - (position % this.tabSize);
2571 // Add as many tabs as needed
2572 addedTabs += (wantedPosition - position) / this.tabSize;
2573 position += (addedTabs-1) * this.tabSize;
2574 // Finish off with spaces
2575 addedSpaces = wantedPosition - position;
2579 addedSpaces = this.x - line.count;
2582 this.x = Min(this.x, line.count);
2584 if(line.count + count + addedSpaces + addedTabs > this.maxLineSize)
2590 hadComment = HasCommentOrEscape(line);
2592 // Adjust the size of the line
2593 if(!line.AdjustBuffer(line.count+count+addedTabs+addedSpaces))
2597 BufferLocation before = { this.line, this.y, this.x }, after = { this.line, this.y, this.x };
2600 memmove(line.buffer+this.x+count, line.buffer+this.x,line.count-this.x);
2601 CopyBytes(line.buffer + this.x + addedTabs + addedSpaces, stringLine, count);
2604 *addedTabsPtr = addedTabs;
2605 FillBytes(line.buffer+line.count,'\t',addedTabs);
2607 if(addedTabs > 4000 || addedTabs < 0)
2610 line.count += addedTabs;
2616 FillBytes(line.buffer+line.count,' ',addedSpaces);
2618 if(addedSpaces > 4000 || addedSpaces < 0)
2621 line.count += addedSpaces;
2622 if(addedSpacesPtr) *addedSpacesPtr = addedSpaces;
2624 else if(addedSpacesPtr)
2625 *addedSpacesPtr = 0;
2627 if(count > 4000 || count < 0)
2630 line.count += count;
2631 this.x += count + addedTabs + addedSpaces;
2634 this.selLine = this.line;
2636 line.buffer[line.count] = '\0';
2638 ComputeLength(line);
2643 hasComment = HasCommentOrEscape(line);
2644 if(!undoBuffer.insideRedo)
2646 int backDontRecord = undoBuffer.dontRecord;
2647 undoBuffer.dontRecord = 0;
2648 NotifyCharsAdded(master, this, &before, &after, this.pasteOperation);
2649 undoBuffer.dontRecord = backDontRecord;
2651 if(style.syntax && (hadComment || hasComment || line != this.line))
2653 style.recomputeSyntax = true;
2661 void Emptyline(EditLine line, int y)
2664 DelCh(line, y, 0, line.next, y+1, 0, true);
2666 DelCh(line, y, 0, line, y, line.count, true);
2669 void GoToEnd(bool deselect)
2673 this.line = this.lines.last;
2674 if(this.y != this.lineCount-1)
2676 else if (this.x != this.line.count)
2677 DirtyLine(this.lineCount-1);
2678 this.y = this.lineCount-1;
2679 this.x = this.line.count;
2686 void GoToHome(bool deselect)
2690 this.line = this.lines.first;
2693 else if (this.x !=0)
2694 DirtyLine(this.lineCount-1);
2703 // Returns true if it needs scrolling
2704 bool FindMouse(int px, int py, int * tx, int * ty, EditLine * tline, bool half)
2709 bool needHScroll = false;
2716 line = this.viewLine ? (void *)this.viewLine.prev : null;
2721 line = (void *)this.lines.first;
2726 py = Min(py, clientSize.h);
2728 py = Min(py, this.lineCount);
2730 for(c = 0, line = this.viewLine; (line != (void *)this.lines.last && c<py); line = line.next, c++)
2736 if( (px >= clientSize.w || px < clientSize.w/2) && this.viewX)
2739 px = Min(px,clientSize.w+this.space.w);
2743 *tx = AdjustXPosition(line, px + viewX, half, null, MAXINT, 0);
2746 if(tline) *tline = line;
2749 // Prevent divide by 0 from non valid this.font
2751 return (y < this.viewY) || needHScroll;
2753 return (y < this.viewY || y >= this.viewY + clientSize.h / this.space.h) || needHScroll;
2757 // Minimal Update Management Functions
2761 this.endY = clientSize.h-1;
2762 // ErrorLog("DirtyAll\n");
2765 void DirtyEnd(int y)
2767 if((y - this.viewY)*this.space.h < this.startY)
2768 this.startY = (y - this.viewY)*this.space.h+ YOFFSET;
2769 this.endY = clientSize.h-1;
2770 //ErrorLog("DirtyEnd %d\n", y);
2773 void DirtyLine(int y)
2777 if((y - this.viewY)*this.space.h < this.startY)
2778 this.startY = (y - this.viewY)*this.space.h + YOFFSET;
2779 if((y - this.viewY+1)*this.space.h > this.endY)
2780 this.endY = (y - this.viewY+1)*this.space.h-1 + YOFFSET;
2782 //ErrorLog("DirtyLine %d\n", y);
2789 if(style.recomputeSyntax)
2791 FigureStartSyntaxStates(lines.first, true);
2792 style.recomputeSyntax = false;
2795 if(this.startY > this.endY) return;
2796 if(this.startY <= 0 && this.endY >= clientSize.h-1)
2802 box.right = clientSize.w-1;
2803 box.top = this.startY;
2804 box.bottom = this.endY;
2807 this.startY = clientSize.h;
2811 bool IsMouseOnSelection()
2813 bool mouseOnSelection = false;
2816 int minY = Min(this.selY, this.y);
2817 int maxY = Max(this.selY, this.y);
2818 int minX = Min(this.selX, this.x);
2819 int maxX = Max(this.selX, this.x);
2821 FindMouse(this.mouseX - this.space.w / 2, this.mouseY, &x, &y, null, false);
2823 if(maxX != minX || maxY != minY)
2825 if(y > minY && y < maxY)
2826 mouseOnSelection = true;
2827 else if(y == minY && y == maxY)
2828 mouseOnSelection = (x < maxX && x >= minX);
2832 mouseOnSelection = (x >= this.selX);
2833 else if(y == this.y)
2834 mouseOnSelection = (x >= this.x);
2839 mouseOnSelection = (x < this.selX);
2840 else if(y == this.y)
2841 mouseOnSelection = (x < this.x);
2844 return mouseOnSelection;
2847 void UpdateCaretPosition(bool setCaret)
2851 if(mouseMove || !style.noCaret)
2853 int max = this.mouseMove ? this.dropX : this.x;
2854 int y = this.mouseMove ? this.dropY : this.y;
2855 EditLine line = this.mouseMove ? this.dropLine : this.line;
2857 if(!(style.freeCaret))
2858 max = Min(max, line.count);
2860 if(FontExtent && display)
2862 for(c = 0; c < max; )
2871 for(len = 0; c < Min(max, line.count); c += numBytes)
2873 ch = line.buffer[c];
2874 numBytes = UTF8_NUM_BYTES(ch);
2876 if(ch == ' ' || ch == '\t')
2883 if(!len && ch == ' ')
2888 else if(!len && ch == '\t')
2890 w = (tabSize * space.w) - (x % (tabSize * space.w));
2894 FontExtent(display, this.font, line.buffer + start, len, &w, null);
2906 caretY = y * this.space.h;
2908 SetCaret(x + XOFFSET-2, y * space.h + YOFFSET, space.h);
2915 // TOFIX: Mismatch between NotifyCaretMove() and NotifyDropped() / GoToPosition()
2916 NotifyCaretMove(master, this, y + 1, x + 1);
2922 void SelectionEnables()
2924 if((x != selX || y != selY) && !selection)
2928 itemEditCut.disabled = false;
2929 itemEditDelete.disabled = false;
2931 itemEditCopy.disabled = false;
2933 this.selection = true;
2935 else if((x == selX && y == selY) && selection)
2937 itemEditCut.disabled = true;
2938 itemEditCopy.disabled = true;
2939 itemEditDelete.disabled = true;
2941 this.selection = false;
2945 void SetSelectCursor()
2947 if(!inactive || !style.noSelect)
2950 cursor = guiApp.GetCursor(arrow);
2951 else if(this.mouseSelect)
2952 cursor = guiApp.GetCursor(iBeam);
2955 if(IsMouseOnSelection())
2956 cursor = guiApp.GetCursor(arrow);
2958 cursor = guiApp.GetCursor(iBeam);
2963 int AdjustXPosition(EditLine line, int position, bool half, int * px, int max, int sc)
2966 int x = px ? *px : 0;
2972 if(c < Min(max, line.count))
2976 for(len = 0; c < Min(max, line.count); c += numBytes)
2978 ch = line.buffer[c];
2979 numBytes = UTF8_NUM_BYTES(ch);
2981 if(ch == ' ' || ch == '\t')
2988 if(!len && ch == ' ')
2993 else if(!len && ch == '\t')
2995 w = (tabSize * space.w) - (x % (tabSize * space.w));
2999 FontExtent(display, font, line.buffer + start, len, &w, null);
3003 if(style.freeCaret && c < max)
3012 if(x + (((half && len == 1) ? (w / 2) : w)) >= position)
3017 int a = start + len;
3020 while(a > 0 && !UTF8_IS_FIRST(line.buffer[--a]));
3024 FontExtent(display, font, line.buffer + start, a - start, &w, null);
3027 if(position > x + (half ? ((w + lastW) / 2) : lastW)) break;
3031 return Min(this.maxLineSize - 1, start + len);
3037 void SetCursorToViewX()
3039 bool selecting = this.x != selX || y != selY;
3041 // Horizontal Adjustment
3043 int c = AdjustXPosition(line, viewX, false, &x, MAXINT, 0);
3048 c = AdjustXPosition(line, viewX + clientSize.w - 1, false, &x, MAXINT, c);
3060 UpdateCaretPosition(false);
3066 void SetCursorToViewY()
3069 EditLine oldLine = this.line;
3071 bool selecting = this.x != this.selX || this.y != this.selY;
3073 numLines = clientSize.h / this.space.h;
3075 // Vertical Adjustment
3076 if(this.viewY > this.y)
3078 this.y = this.viewY;
3079 this.line = this.viewLine;
3082 if(this.viewY + numLines <= this.y)
3086 this.y = this.viewY-1;
3087 for(c = 0, line = this.viewLine; line && c<numLines; line = line.next, c++)
3094 if(this.line != oldLine)
3096 this.x = AdjustXPosition(this.line, caretX, true, null, MAXINT, 0);
3104 this.selLine = this.line;
3107 UpdateCaretPosition(false);
3114 bool SaveFile(const char * fileName)
3116 File f = eFile_Open(fileName, FO_WRITE);
3120 eWindow_SetModified(false);
3121 eInstance_Delete(f);
3128 bool OnActivate(bool active, Window previous, bool * goOnWithActivation, bool direct)
3132 if(!style.multiLine)
3134 x = (active && this.active && !style.readOnly) ? line.count : 0;
3137 SetViewToCursor(true);
3142 if(!active && modified)
3145 if(!NotifyModified(master, this))
3148 *goOnWithActivation = false;
3155 if(timer) timer.Stop();
3157 mouseSelect = false;
3165 int aw = maxLength + 12, ah = Max(lineCount, 1) * space.h + 2;
3166 int nw = minClientSize.w, nh = minClientSize.h, xw = maxClientSize.w, xh = maxClientSize.h;
3167 clientSize = { nw && aw < nw ? nw : xw && aw > xw ? xw : aw, nh && ah < nh ? nh : xh && ah > xh ? xh : ah };
3170 bool OnResizing(int *w, int *h)
3176 *w = space.h * 80 / 14;
3180 void OnResize(int w, int h)
3183 if(!hasHorzScroll && !hasVertScroll && viewLine)
3184 SetViewToCursor(true);
3186 //if(!hasHorzScroll && !hasVertScroll && viewLine)
3188 SetViewToCursor(true);
3193 bool OnMiddleButtonDown(int x, int y, Modifiers mods)
3195 if(style.readOnly) return true;
3196 // We really shouldn't be pasting here, Unix middle button paste is for the selection (not the clipboard), good for the terminal
3197 // Middle button already serves as a dragger as well.
3202 bool OnRightButtonDown(int x, int y, Modifiers mods)
3204 this.rightButtonDown = true;
3209 bool OnRightButtonUp(int x, int y, Modifiers mods)
3212 if(!parent.inactive && rightButtonDown)
3215 Menu contextMenu { };
3217 MenuItem { contextMenu, $"Cut\tCtrl+X", t, NotifySelect = itemEditCut.NotifySelect, disabled = !selection || style.readOnly };
3218 MenuItem { contextMenu, $"Copy\tCtrl+C", c, NotifySelect = itemEditCopy.NotifySelect, disabled = !selection };
3219 MenuItem { contextMenu, $"Paste\tCtrl+V", p, NotifySelect = itemEditPaste.NotifySelect, disabled = style.readOnly };
3220 MenuItem { contextMenu, $"Delete\tDel", d, NotifySelect = itemEditDelete.NotifySelect, disabled = !selection || style.readOnly };
3221 MenuDivider { contextMenu };
3222 MenuItem { contextMenu, $"Select All\tCtrl+A", a, NotifySelect = itemEditSelectAll.NotifySelect };
3224 popup = PopupMenu { master = this, menu = contextMenu,
3226 nonClient = true, interim = false, parent = parent,
3227 position = { x + clientStart.x + parent.clientStart.x + position.x, y + cientStart.y + parent.sy + clientStart.y + position.y };
3229 position = { x + clientStart.x + absPosition.x - guiApp.desktop.position.x, y + clientStart.y + absPosition.y - guiApp.desktop.position.y }
3233 rightButtonDown = false;
3237 bool OnLeftButtonDown(int mx, int my, Modifiers mods)
3242 if(style.noSelect) return true;
3244 // Should we have a separate 'selectOnActivate' style?
3245 if(!mods.isActivate || (style.readOnly && style.multiLine))
3251 mouseX = mx - XOFFSET;
3254 FindMouse(mouseX, mouseY, &x, &y, &line, true);
3256 //PrintLn("OnLeftButtonDown: ", x, ", ", y);
3262 else if(IsMouseOnSelection() && !mods.isActivate)
3272 if(!mouseMove && !wordSelect && (!mods.isActivate || style.multiLine))
3274 if(mods.shift && !mods.isActivate)
3295 UpdateCaretPosition(true);
3296 // Return false because DataBoxes automatically set EditBox editor's clickThrough to true for MouseMove events
3297 // ( for tool tips -- see 95ee4962c4c7bc3fe0a04aa6a4f98cacada40884)
3301 bool OnLeftButtonUp(int x, int y, Modifiers mods)
3305 mouseSelect = false;
3316 FindMouse(mouseX, mouseY, &x, &y, &line, true);
3318 //PrintLn("MouseMove: ", x, ", ", y);
3324 mouseMove = IsMouseOnSelection();
3328 int size = SelSize();
3331 char * text = new char[size+1];
3335 GetSel(text, false);
3337 if(Max(selY, this.y) == dropY)
3341 if(this.dropX > this.selX)
3342 moveX = this.x - this.selX;
3346 if(this.dropX > this.x)
3347 moveX = this.selX - this.x;
3351 recordUndoEvent = true;
3353 this.dropX -= moveX;
3354 this.selX = this.x = this.dropX;
3355 this.selY = this.y = this.dropY;
3356 this.selLine = this.line = this.dropLine;
3358 recordUndoEvent = false;
3360 SetViewToCursor(true);
3365 byte * c = ((EditBox)this).multiLineContents, * c2;
3366 int l1 = c ? strlen(c) : 0, l2;
3369 c2 = ((EditBox)this).multiLineContents;
3370 l2 = c2 ? strlen(c2) : 0;
3371 if(l1 != l2 || (l1 && strcmp(c, c2)))
3401 FindMouse(mouseX, mouseY, &x, &y, &line, true);
3403 //PrintLn("Dropped: ", x, ", ", y);
3405 NotifyDropped(master, this, x, y);
3409 // Return false because DataBoxes automatically set EditBox editor's clickThrough to true for MouseMove events
3410 // ( for tool tips -- see 95ee4962c4c7bc3fe0a04aa6a4f98cacada40884)
3414 bool OnMouseMove(int mx, int my, Modifiers mods)
3420 if(mods != -1 && mods.isSideEffect)
3425 if(style.noSelect) return true;
3426 if(wordSelect) return true;
3427 mouseX = mx - XOFFSET;
3430 needScroll = FindMouse(this.mouseX, this.mouseY, &x, &y, &line, true);
3432 if(this.mouseMove || this.mouseSelect)
3441 ((style.hScroll) || (style.vScroll)))
3448 DirtyLine(this.dropY);
3451 DirtyLine(this.dropY);
3452 this.dropLine = line;
3453 SetViewToCursor(true);
3455 //PrintLn("MouseMove: ", "dropX = ", x, ", dropY = ", y);
3458 else if(this.mouseSelect)
3460 DirtyLine(this.selY);
3467 SetViewToCursor(true);
3470 //PrintLn("MouseSelect: ", "x = ", x, ", y = ", y);
3477 bool OnLeftDoubleClick(int mx, int my, Modifiers mods)
3482 //PrintLn("OnLeftDoubleClick: ", mx, ", ", my, ", mods = ", mods);
3486 if(style.noSelect) return true;
3487 FindMouse(mx, my, &x, &y, &line, false);
3488 if(!NotifyDoubleClick(master, this, line, mods))
3495 for(c = x; c >= 0; c--)
3498 while(c > 0 && !UTF8_IS_FIRST(line.buffer[c])) c--;
3499 ch = UTF8_GET_CHAR(line.buffer + c, numBytes);
3506 for(c = start; c<line.count; c += numBytes)
3508 unichar ch = UTF8_GET_CHAR(line.buffer + c, numBytes);
3509 if(!ch || !IS_ALUNDER(ch))
3519 this.line = this.selLine = line;
3520 this.wordSelect = (c != start);
3531 Menu fileMenu { menu, "File", F };
3532 saveDialog = fileDialog;
3533 MenuItem { fileMenu, $"Save\tCtrl+S", S, CtrlS, NotifySelect = MenuFileSave };
3534 MenuItem { fileMenu, $"Save As...", A, NotifySelect = MenuFileSaveAs };
3536 if(style.autoSize) AutoSize();
3544 FontExtent(display, font, " ", 1, (int *)&space.w, (int *)&space.h);
3545 FontExtent(display, font, "W", 1, (int *)&large.w, (int *)&large.h);
3547 space.w = Max(space.w, 1);
3548 large.w = Max(large.w, 1);
3549 space.h = Max(space.h, 1);
3550 large.h = Max(large.h, 1);
3554 for(line = lines.first; line; line = line.next)
3555 ComputeLength(line);
3560 SetViewToCursor(true);
3568 bool OnLoadGraphics()
3570 FontExtent = Display::FontExtent;
3573 // UpdateCaretPosition(true);
3577 void OnUnloadGraphics()
3582 bool OnKeyHit(Key key, unichar ch)
3584 bool shift = (key.shift) ? true : false;
3586 //PrintLn("OnKeyHit: code = ", key.code, ", mods = ", key.modifiers, ", ch = ", (int)ch);
3588 if(!ch && !key.alt && !key.ctrl)
3590 key.code = (SmartKey)key.code;
3592 else if(!ch && key.alt)
3595 switch(key.code) //(ch || key.alt || key.ctrl) ? key.code : (Key)(SmartKey)key.code)
3598 if(style.readOnly) break;
3599 if(style.stuckCaret) GoToEnd(true);
3600 if(!(style.freeCaret))
3602 this.x = Min(this.x, this.line.count);
3608 EditLine line = this.line;
3610 for(y = this.y; y>= 0; y--)
3612 c = (y == this.y) ? (Min(this.x-1, line.count-1)) : line.count-1;
3614 // Slow down when going on lines...
3615 if(y != this.y) break;
3619 //if(this.line.buffer[c] != '\t' && this.line.buffer[c] != ' ')
3620 if(IS_ALUNDER(line.buffer[c]))
3625 if(!IS_ALUNDER(line.buffer[c]))
3627 //if(this.line.buffer[c] == '\t' || this.line.buffer[c] == ' ')
3642 _DelCh(line, y, c+1, this.line, this.y, this.x, true, false, null);
3643 this.x = this.selX = Min(c+1, line.count);
3644 this.y = this.selY = y;
3645 this.line = this.selLine = line;
3646 SetViewToCursor(true);
3657 if(style.readOnly) break;
3658 if(style.stuckCaret) break;
3667 if(this.line != this.selLine || this.x != this.selX)
3670 SetViewToCursor(true);
3675 EditLine line1 = this.line, line2 = this.line;
3676 int x1, y1 = this.y, x2, y2 = this.y;
3677 if(!style.freeCaret)
3679 this.selX = this.x = Min(this.x, this.line.count);
3684 if(x1 < line1.count)
3690 char * buffer = line1.buffer;
3691 for(i = x1; i < line1.count; i++)
3693 if(!IS_ALUNDER(buffer[i]))
3697 for(; i < line1.count; i++)
3699 // Delete trailing whitespace
3700 if(IS_ALUNDER(buffer[i]))
3710 // Avoid creating trailing spaces if there is no content on next line
3713 if(line2 && !line2.count)
3718 _DelCh(line1, y1, x1, line2, y2, x2, false, false, null);
3719 SetViewToCursor(true);
3728 if(!key.alt && !key.ctrl)
3732 bool stuffAfter = false;
3735 /*bool resetX = false;
3738 if(style.stuckCaret) GoToEnd(true);
3739 if(style.readOnly) break;
3740 if(!(style.multiLine)) break;
3742 for(c = 0; c<this.line.count && c<this.x; c++)
3744 if(this.line.buffer[c] == '\t')
3745 position += this.tabSize - (position % this.tabSize);
3746 else if(this.line.buffer[c] == ' ')
3752 // Prevent adding trailing spaces if at the head of a line
3753 /*if(c && c == this.x && c < this.line.count && this.x == this.selX && this.y == this.selY)
3765 if(this.x < this.line.count)
3768 //If last character is a { indent one tab
3769 c = Min(x, line.count);
3770 if(c > 0 && line.buffer[c - 1] == '{')
3772 //Except if the next non space character is a }
3773 bool indent = false;
3775 for(i = c; i <= this.line.count; i++) // indent will be set to true on nul terminating char
3776 if(this.line.buffer[i] != ' ' && this.line.buffer[i] != '\t')
3778 if(this.line.buffer[i] != '}')
3783 position += this.tabSize;
3786 addString = new char[position + 2];
3787 addString[len++] = '\n';
3788 addString[len] = '\0';
3790 if(stuffAfter || !style.freeCaret)
3792 for(c = 0; c<position; )
3794 if(style.useTab && c + this.tabSize <= position)
3796 addString[len++] = '\t';
3801 addString[len++] = ' ';
3805 addString[len] = '\0';
3807 recordUndoEvent = true;
3810 EditLine prevLine = this.line.prev;
3813 // Nuke spaces if that is all that is left on previous line
3815 char * buffer = prevLine.buffer;
3816 for(i = 0; i < prevLine.count; i++)
3817 if(buffer[i] != ' ' && buffer[i] != '\t')
3819 if(i == prevLine.count)
3820 DelCh(prevLine, this.y - 1, 0, prevLine, this.y - 1, prevLine.count, false);
3824 this.x = this.selX = backX;
3827 else */if(!stuffAfter && style.freeCaret)
3829 this.x = this.selX = position;
3833 SetViewToCursor(true);
3836 recordUndoEvent = false;
3844 if(style.stuckCaret) break;
3845 if(!(style.freeCaret))
3847 this.x = Min(this.x, this.line.count);
3848 this.selX = Min(this.selX, this.selLine.count);
3851 if(!shift) SelDirty();
3854 bool foundAlpha = false;
3857 EditLine line, lastLine = null;
3858 int lastC = 0, lastY = 0;
3860 for(line = this.line; (line && !found); line = line.prev, y--)
3865 if(this.x == 0 && line != this.line)
3874 if(line == this.line) start = this.x -1; else start = line.count-1;
3875 start = Min(start, line.count-1);
3877 for(c = start; c >= 0;)
3880 unichar ch = UTF8_GET_CHAR(line.buffer + c, numBytes);
3898 byte ch = line.buffer[c];
3899 if(UTF8_IS_FIRST(ch)) break;
3903 // No next word found,
3904 if(!found && ( this.x > 0 || (!line.count && this.x)))
3918 this.line = lastLine;
3929 byte * buffer = (byte *)line.buffer;
3932 byte ch = buffer[x];
3933 if(UTF8_IS_FIRST(ch)) break;
3950 if(!shift) _Deselect();
3951 SetViewToCursor(true);
3957 if(style.stuckCaret) break;
3958 if(!(style.freeCaret))
3960 this.x = Min(this.x, this.line.count);
3961 this.selX = Min(this.selX, this.selLine.count);
3964 if(!shift) SelDirty();
3965 if(!shift && (this.x != this.selX || this.y != this.selY));
3968 bool onAChar = false;
3969 if(this.selX != this.x || this.selY != this.y)
3971 if(this.x<this.line.count)
3972 if(this.line.buffer[this.x] != '\t' && this.line.buffer[this.x] !=' ')
3974 if(key.shift && onAChar &&
3975 ((this.y > this.selY)||((this.selY == this.y)&&(this.x >= this.selX))))
3977 bool foundAlpha = false;
3979 EditLine line = null, lastLine = null;
3981 int lastC = 0, lastY = 0, lastNumBytes = 0;
3983 for(line = this.line; (line && !found); line = line.next, y++)
3985 int start = (line == this.line) ? this.x : 0;
3989 for(c = start; c < line.count && (ch = UTF8_GET_CHAR(line.buffer + c, numBytes)); c += numBytes)
3995 lastNumBytes = numBytes;
4005 if(!found && (c != this.x || line != this.line))
4018 this.x = lastC + lastNumBytes;
4020 this.line = lastLine;
4027 bool foundAlpha = false;
4032 for(line = this.line; (line && !found); line = line.next, y++)
4034 int start = (line == this.line) ? this.x : 0;
4038 for(c = start; c < line.count && (ch = UTF8_GET_CHAR(line.buffer + c, numBytes)); c += numBytes)
4054 // No next word found,
4055 if(!found && (c != this.x || line != this.line))
4059 this.x = line.count;
4071 if(x < line.count || (style.freeCaret && line.count < maxLineSize))
4075 byte * buffer = (byte *)line.buffer;
4078 byte ch = buffer[x];
4079 if(UTF8_IS_FIRST(ch)) break;
4100 if(!shift) _Deselect();
4101 SetViewToCursor(true);
4108 if(!style.vScroll || hasVertScroll) break;
4114 if(style.stuckCaret) break;
4116 if(!shift) SelDirty();
4126 this.x = AdjustXPosition(line, caretX, true, null, MAXINT, 0);
4130 if(!shift) _Deselect();
4132 SetViewToCursor(false);
4135 if(caretY == this.y * space.h)
4141 if(!style.freeCaret)
4142 this.x = Min(this.x, line.count);
4152 int sx = 0, sy = this.y * space.h;
4153 char * text = line.text;
4154 int maxW = clientSize.w - sx;
4155 display.FontExtent(font, " ", 1, null, &th);
4159 int startPos = textPos;
4162 bool lineComplete = false;
4164 if(!style.wrap && caretY == MAXINT)
4167 //textPos = line.count;
4168 //lineComplete = true;
4171 for(; (style.freeCaret || textPos < line.count) && !lineComplete;)
4175 char * nextSpace = (textPos < line.count) ? strchr(text + textPos, ' ') : (text + textPos);
4178 len = (nextSpace - (text + textPos));
4180 len = line.count - textPos;
4182 if(textPos < line.count)
4184 display.FontExtent(font, text + textPos, len, &w, null);
4186 if(nextSpace) { w += space.w; len++; }
4188 if(style.wrap && x + width + w > maxW && x > 0)
4190 lineComplete = true;
4200 if((!style.freeCaret && textPos >= line.count) || (sy == caretY - th && caretX <= x + width + sx))
4204 while(this.x > 0 && x + sx > caretX && this.x > startPos)
4207 if(this.x > line.count)
4210 while(this.x > 0 && !UTF8_IS_FIRST(text[--this.x]));
4211 len = this.x - startPos;
4212 display.FontExtent(font, text + startPos, len, &x, null);
4216 if(!shift) _Deselect();
4218 SetViewToCursor(false);
4222 if(sy == caretY - th || textPos >= line.count)
4224 if(textPos >= line.count)
4226 int c = textPos - 1;
4227 while(c > 0 && text[c] == ' ') c--;
4231 this.x = line.count;
4234 if(!shift) _Deselect();
4236 SetViewToCursor(false);
4241 } while(textPos < line.count);
4244 if(!shift) _Deselect();
4246 SetViewToCursor(false);
4255 int x = AdjustXPosition(this.line, this.line.prev, true, null, MAXINT, 0);
4256 if(!shift) SelDirty();
4257 this.line = this.line.prev;
4263 if(!shift) _Deselect();
4267 SetViewToCursor(false);
4273 return style.multiLine ? false : true;
4277 if(!style.vScroll || hasVertScroll)
4284 if(style.stuckCaret) break;
4289 int sx = 0, sy = this.y * this.space.h;
4290 int maxW = clientSize.w - sx;
4291 char * text = line.buffer;
4294 if(!shift) SelDirty();
4300 if(AdjustXPosition(line, maxW, this.x, line.count, true, null, MAXINT, 0) <= line.count)
4310 this.x = AdjustXPosition(line, caretX, true, null, MAXINT, 0);
4314 if(!shift) _Deselect();
4316 SetViewToCursor(false);
4319 while(!textPos || (style.freeCaret || textPos<line.count))
4321 int startPos = textPos;
4324 bool lineComplete = false;
4325 if(!style.wrap && sy <= caretY)
4327 textPos = line.count;
4328 lineComplete = true;
4330 for(; (style.freeCaret || textPos<line.count) && !lineComplete;)
4334 char * nextSpace = (textPos < line.count) ? strchr(text + textPos, ' ') : (text + textPos);
4337 len = (nextSpace - (text + textPos));
4339 len = line.count - textPos;
4341 if(textPos < line.count)
4343 display.FontExtent(font, text + textPos, len, &w, &th);
4345 if(nextSpace) { w += space.w; len++; }
4346 if(style.wrap && x + width + w > maxW && x > 0)
4348 lineComplete = true;
4358 if(sy > caretY && ((!style.freeCaret && textPos >= line.count) || caretX <= x + width + sx))
4362 while(this.x > 0 && x + sx > caretX && textPos > startPos)
4365 if(this.x > line.count)
4368 while(this.x > 0 && !UTF8_IS_FIRST(text[--this.x]));
4370 len = this.x - startPos;
4371 display.FontExtent(font, text + startPos, len, &x, null);
4374 if(!shift) _Deselect();
4377 SetViewToCursor(false);
4383 this.x = line.count;
4386 if(!shift) _Deselect();
4389 SetViewToCursor(false);
4392 else if(textPos >= line.count && line.next)
4398 sy = this.y * this.space.h;
4399 sx = 0; //textBlock.startX;
4405 sx = 0; //textBlock.startX;
4413 this.x = Min(this.x, line.count);
4414 //int x = AdjustXPosition(this.line, this.line.next, true, null, MAXINT, 0);
4416 if(!shift) _Deselect();
4419 if(this.selX != this.x || this.selY != this.y)
4422 SetViewToCursor(false);
4429 int x = AdjustXPosition(this.line, this.line.next, true, null, MAXINT, 0);
4430 if(!shift) SelDirty();
4431 this.line = this.line.next;
4435 if(!shift) _Deselect();
4438 if(this.selX != this.x || this.selY != this.y)
4446 return style.multiLine ? false : true;
4449 if(style.stuckCaret) break;
4450 if(!style.multiLine && key.ctrl) break;
4451 if(!(style.freeCaret))
4452 this.selX = Min(this.selX, this.selLine.count);
4454 if(!shift) SelDirty();
4457 this.line = this.lines.first;
4458 if(this.y != 0 || this.x != 0)
4468 EditLine line = this.line;
4470 for(c=0; line.buffer[c]; c++)
4471 if(line.buffer[c] != ' ' && line.buffer[c] != '\t')
4473 if(overwrite || (shift && (c != 0 || this.x)))
4482 if(overwrite || (shift && this.x != 0))
4488 if(!shift) _Deselect();
4489 SetViewToCursor(true);
4495 if(style.stuckCaret) break;
4496 if(!style.multiLine && key.ctrl) break;
4497 if(!style.freeCaret)
4498 this.selX = Min(this.selX, this.selLine.count);
4500 if(!shift) SelDirty();
4505 else if(this.x != this.line.count)
4507 this.x = this.line.count;
4508 if(overwrite || shift)
4512 if(!shift) _Deselect();
4513 SetViewToCursor(true);
4518 if(style.tabKey && !key.ctrl)
4520 if(this.selY != this.y && style.tabSel)
4522 EditLine firstLine, lastLine;
4526 // Do multi line selection tabbing here
4527 if(this.selY < this.y)
4529 firstLine = this.selLine;
4530 lastLine = this.line;
4536 // Selecting going up
4537 firstLine = this.line;
4538 lastLine = this.selLine;
4545 for(line = firstLine; line; line = line.next, y++)
4547 if(line != lastLine || x)
4551 BufferLocation before = { line, y, 0 }, after = { line, y, 0 };
4553 for(c=0; c<line.count && lastC < this.tabSize; c++, lastC++)
4555 if(line.buffer[c] == '\t')
4560 else if(line.buffer[c] != ' ')
4565 NotifyCharsDeleted(master, this, &before, &after, this.pasteOperation);
4568 int len = GetText(null, line, y, 0, line, y, lastC, false, false);
4569 char * string = new char[len];
4570 DelTextAction action { y1 = y, x1 = 0, y2 = y, x2 = lastC, string = string, placeAfter = true };
4571 GetText(string, line, y, 0, line, y, lastC, false, false);
4574 memmove(line.buffer,line.buffer+lastC,line.size-lastC);
4575 if(!line.AdjustBuffer(line.count-lastC))
4581 if(line == lastLine) break;
4586 for(line = firstLine; line; line = line.next, y++)
4590 if(line != lastLine || x)
4594 if(line.count + 1 <= this.maxLineSize)
4596 BufferLocation before = { line, y, 0 }, after = { line, y, 1 };
4598 if(!line.AdjustBuffer(line.count+1))
4601 NotifyCharsAdded(master, this, &before, &after, this.pasteOperation);
4603 AddCharAction action { ch = '\t', x = 0, y = y };
4607 memmove(line.buffer+1,line.buffer,line.size-1);
4609 line.buffer[0] = '\t';
4614 if(line.count + this.tabSize <= this.maxLineSize)
4617 BufferLocation before = { line, y, 0 }, after = { line, y, this.tabSize };
4618 NotifyCharsAdded(master, this, &before, &after, this.pasteOperation);
4620 if(!line.AdjustBuffer(line.count+this.tabSize))
4624 char * string = new char[this.tabSize + 1];
4625 memset(string, ' ', this.tabSize);
4626 string[this.tabSize] = '\0';
4627 Record(AddTextAction { string = string, x1 = 0, y1 = y, x2 = this.tabSize, y2 = y });
4630 memmove(line.buffer+this.tabSize,line.buffer,line.size-(this.tabSize));
4631 line.count+=this.tabSize;
4632 for(c=0; c<this.tabSize; c++)
4633 line.buffer[c] = ' ';
4639 if(line == lastLine) break;
4642 ComputeLength(maxLine);
4654 char * addString = new char[this.tabSize + 1];
4656 if(!(style.freeCaret))
4658 this.x = Min(this.x, this.line.count);
4662 start = Min(this.x, this.selX);
4663 for(c=start; ((c == start) || ((c) % this.tabSize)); c++)
4665 addString[len++] = ' ';
4673 SetViewToCursor(true);
4682 if(!(style.hScroll) || hasHorzScroll) break;
4683 if(this.viewX < this.maxLength)
4685 //this.viewX+=this.space.w*this.tabSize;
4687 SetScrollPosition((this.viewX + this.space.w*this.tabSize), this.viewY * this.space.h);
4694 if(!shift) _Deselect();
4706 if(!(style.hScroll) || hasHorzScroll) break;
4709 //this.viewX-=this.space.w*this.tabSize;
4710 //this.viewX = Max(this.viewX,0);
4712 SetScrollPosition((this.viewX-this.space.w*this.tabSize), this.viewY * this.space.h);
4713 // SetCursorToView();
4720 if(!shift) _Deselect();
4733 else if(!style.readOnly)
4737 if(!(style.readOnly))
4743 this.overwrite ^= 1;
4744 UpdateCaretPosition(true);
4749 NotifyOvrToggle(master, this, this.overwrite);
4761 if(style.noSelect) break;
4764 //Save current view position
4765 int tempX = this.viewX;
4766 int tempY = this.viewY;
4770 this.selLine = this.lines.first;
4771 this.y = this.lineCount-1;
4772 this.line = this.lines.last;
4773 this.x = this.line.count;
4776 SetViewToCursor(true);
4778 //Restore previous view position
4779 SetScrollPosition(tempX, tempY * this.space.h);
4785 // TOCHECK: Was there any good reason why we weren't returning false here?
4786 return false; // break;
4791 if(!(style.readOnly))
4799 if(style.readOnly) break;
4805 if(style.readOnly) break;
4810 if(style.readOnly) break;
4814 if(style.readOnly) break;
4818 if(style.readOnly) break;
4819 if(key.shift && key.code == rightBracket)
4821 //Only indent back if you are exactly at one tab.
4823 //bool whitespace = true;
4828 int indentwidth = 0;
4829 EditLine line = this.line;
4831 //Only remove one tab if there is nothing else on the line.
4832 for(i = 0; i < this.line.count; i++)
4834 if(this.line.buffer[i] != ' ' && this.line.buffer[i] != '\t')
4848 for(pos = line.count - 1; pos >= 0; pos--)
4850 char c = line.buffer[pos];
4854 indentwidth += this.tabSize;
4856 //Counting backwards, so when you find a character, indentation is reset
4866 //Place the } to get an undo:
4869 this.x = this.line.count;
4873 putsize = indentwidth;
4875 putsize = indentwidth / this.tabSize + indentwidth % this.tabSize;
4877 newline = new char[putsize+2];
4878 newline[putsize] = '}';
4879 newline[putsize+1] = '\0';
4883 for(; i < indentwidth / this.tabSize; i++)
4885 for(;i < putsize; i++)
4894 else if(!key.ctrl && !key.alt && ch != 128 && ch >= 32)
4906 void OnHScroll(ScrollBarAction action, int position, Key key)
4909 //PrintLn("OnHScroll: ", action, ", pos = ", position, ", key = ", key);
4911 this.viewX = position;
4912 if(action != setRange)
4914 if(!this.mouseMove && style.cursorFollowsView)
4921 void OnVScroll(ScrollBarAction action, int position, Key key)
4923 int oldViewY = this.viewY;
4926 //PrintLn("OnVScroll: ", action, ", pos = ", position, ", key = ", key);
4928 position /= this.space.h;
4930 if(position < this.viewY)
4932 for(; position < this.viewY && this.viewLine.prev; this.viewLine = this.viewLine.prev, this.viewY--);
4933 style.recomputeSyntax = true;
4935 else if(position > this.viewY)
4937 EditLine oldViewLine = viewLine;
4938 for(; position > this.viewY && this.viewLine.next; this.viewLine = this.viewLine.next, this.viewY++);
4939 FigureStartSyntaxStates(oldViewLine, false);
4942 if(action != setRange)
4944 if(!this.mouseMove && style.cursorFollowsView && !SelSize()) SetCursorToViewY();
4947 if(this.x != this.selX || this.y != this.selY)
4951 Scroll(0, (this.viewY - oldViewY) * this.space.h);
4954 int numLines = clientSize.h / this.space.h;
4956 if(Abs(this.viewY - oldViewY) < numLines)
4959 if(this.viewY > oldViewY)
4961 for(y = oldViewY; y <this.viewY; y++)
4962 DirtyLine(y + numLines);
4966 for(y = this.viewY; y <oldViewY; y++)
4967 DirtyLine(y + numLines);
4972 // Fix dirt of stuff before first line...
4973 if(this.viewY - oldViewY > 0)
4975 Box box { 0,0, clientSize.w-1, YOFFSET-1 };
4982 bool _AddCh(unichar ch, int * addedSpacesPtr, int * addedTabsPtr)
4985 int length, endX = 0;
4987 ReplaceTextAction replaceAction = null;
4988 AddCharAction addCharAction = null;
4989 int addedSpaces = 0, addedTabs = 0;
4991 if(ch == '\r') return true;
4992 if(style.stuckCaret /*|EES_READONLY)*/ )
4995 if(ch == '\n' && !(style.multiLine) && this.line) return false;
4997 if(!undoBuffer.dontRecord)
4999 if(selX != x || selY != y)
5004 int len = GetText(null, selLine, selY, selX, this.line, y, x, false, false);
5005 oldString = new char[len];
5006 UTF32toUTF8Len(&ch, 1, buffer, 4);
5007 newString = CopyString(buffer);
5008 GetText(oldString, selLine, selY, selX, this.line, y, x, false, false);
5010 replaceAction = ReplaceTextAction { newString = newString, oldString = oldString, placeAfter = false };
5011 if(selY < y || (selY == y && selX < x))
5013 replaceAction.x1 = selX;
5014 replaceAction.y1 = selY;
5015 replaceAction.x2 = x;
5016 replaceAction.y2 = y;
5020 replaceAction.x1 = x;
5021 replaceAction.y1 = y;
5022 replaceAction.x2 = selX;
5023 replaceAction.y2 = selY;
5025 Record(replaceAction);
5026 undoBuffer.dontRecord++;
5030 addCharAction = AddCharAction { y = y, x = x, ch = ch };
5031 Record(addCharAction);
5037 DelSel(&addedSpaces);
5038 if(this.lineCount+1 > this.maxLines)
5041 Emptyline(this.lines.first,0);
5045 if(!(style.autoSize && (!maxClientSize.h || maxClientSize.h > clientSize.h + this.space.h)) && !(style.vScroll))
5047 // Make sure it fits, but we need a default line is this.font is too big for window
5048 if(this.space.h * (this.lineCount+1) > clientSize.h && this.line)
5051 if((this.y >= 0) && this.y < this.viewY)
5056 line = EditLine { };
5059 lines.Insert(this.line, line);
5060 line.editBox = this;
5066 // If we're displacing the lines from a current line ...
5069 if(this.line.buffer)
5072 if(this.line.count < endX) endX = this.line.count;
5073 length = this.line.count - endX;
5076 if(!line.AdjustBuffer(length))
5080 if(this.line.buffer)
5082 CopyBytes(line.buffer,this.line.buffer+endX, length+1);
5084 if(endX > 4000 || endX < 0)
5087 this.line.count = endX;
5088 this.line.buffer[this.line.count] = '\0';
5089 this.line.AdjustBuffer(this.line.count);
5090 ComputeLength(this.line);
5094 BufferLocation before = { this.line, this.y, this.x }, after;
5099 line.count = length;
5102 if(length > 4000 || length < 0)
5105 ComputeLength(this.line);
5110 line.buffer[line.count] = '\0';
5113 after.line = this.line, after.y = this.y, after.x = this.x;
5115 NotifyCharsAdded(master, this, &before, &after, this.pasteOperation);
5121 int count = UTF32toUTF8Len(&ch, 1, string, 5);
5122 DelSel(&addedSpaces);
5123 result = AddToLine(string, count, false, addedSpaces ? null : &addedSpaces, &addedTabs);
5124 if(addedSpacesPtr) *addedSpacesPtr = addedSpaces;
5125 if(addedTabsPtr) *addedTabsPtr = addedTabs;
5129 this.selLine = this.line;
5133 replaceAction.x3 = x;
5134 replaceAction.y3 = y;
5135 replaceAction.addedSpaces = addedSpaces;
5136 replaceAction.addedTabs = addedTabs;
5137 undoBuffer.dontRecord--;
5141 addCharAction.x -= addedTabs * (tabSize-1);
5142 addCharAction.addedSpaces = addedSpaces;
5143 addCharAction.addedTabs = addedTabs;
5150 /****************************************************************************
5152 ****************************************************************************/
5155 bool AddCh(unichar ch)
5157 return _AddCh(ch, null, null);
5162 this.modified = true;
5163 NotifyUpdate(master, this);
5164 modifiedDocument = true;
5167 void Delete(EditLine line1, int y1, int x1, EditLine line2, int y2, int x2)
5170 _DelCh(line1, y1, x1, line2, y2, x2, false, false, null);
5171 SetViewToCursor(true);
5179 itemEditUndo.disabled = undoBuffer.curAction == 0;
5180 itemEditRedo.disabled = undoBuffer.curAction == undoBuffer.count;
5181 if(savedAction == undoBuffer.curAction)
5183 modifiedDocument = false;
5185 NotifyUnsetModified(master, this);
5192 itemEditUndo.disabled = undoBuffer.curAction == 0;
5193 itemEditRedo.disabled = undoBuffer.curAction == undoBuffer.count;
5194 if(savedAction == undoBuffer.curAction)
5196 modifiedDocument = false;
5198 NotifyUnsetModified(master, this);
5202 void Record(UndoAction action)
5204 if(!undoBuffer.dontRecord)
5206 undoBuffer.Record(action);
5207 itemEditUndo.disabled = undoBuffer.curAction == 0;
5208 itemEditRedo.disabled = undoBuffer.curAction == undoBuffer.count;
5215 this.lines.first, 0,0,
5216 this.lines.last, this.lines.count-1, strlen(((EditLine)this.lines.last).buffer));
5219 void Select(EditLine line1, int y1, int x1, EditLine line2, int y2, int x2)
5224 this.selLine = line1 ? (EditLine)line1 : this.lines.first;
5225 this.x = line2 ? x2 : ((EditLine)this.lines.last).count;
5226 this.y = line2 ? y2 : (this.lineCount-1);
5227 this.line = line2 ? (EditLine)line2 : this.lines.last;
5230 SetViewToCursor(true);
5234 // TODO: Fix this vs modifiedDocument window property
5235 void SetModified(bool flag)
5239 this.modified = false;
5240 if(flag && !NotifyModified(master, this))
5241 this.modified = true;
5246 bool AddS(const char * string)
5253 int addedSpaces = 0, addedTabs = 0;
5254 AddTextAction action = null;
5255 ReplaceTextAction replaceAction = null;
5257 this.pasteOperation = true;
5259 if(style.stuckCaret /*|EES_READONLY)*/ )
5262 if(!undoBuffer.dontRecord)
5264 char * placeString = CopyString(string);
5265 if(!style.multiLine)
5269 for(i = 0; (ch = placeString[i]); i++)
5272 placeString[i] = '\0';
5273 placeString = renew placeString byte[i+1];
5278 if(selX != x || selY != y)
5282 int len = GetText(null, selLine, selY, selX, this.line, y, x, false, false);
5283 oldString = new char[len];
5284 newString = placeString;
5285 GetText(oldString, selLine, selY, selX, this.line, y, x, false, false);
5287 replaceAction = ReplaceTextAction { newString = newString, oldString = oldString, placeAfter = false };
5288 if(selY < y || (selY == y && selX < x))
5290 replaceAction.x1 = selX;
5291 replaceAction.y1 = selY;
5292 replaceAction.x2 = x;
5293 replaceAction.y2 = y;
5297 replaceAction.x1 = x;
5298 replaceAction.y1 = y;
5299 replaceAction.x2 = selX;
5300 replaceAction.y2 = selY;
5302 Record(replaceAction);
5306 if(string[0] == '\n')
5307 action = AddTextAction { y1 = y, x1 = Min(this.line.count, x), string = placeString };
5309 action = AddTextAction { y1 = y, x1 = x, string = placeString };
5317 undoBuffer.dontRecord++;
5318 DelSel(&addedSpaces);
5322 for(c = 0; string[c]; c++)
5324 if(string[c] == '\n' || string[c] == '\r')
5326 if(!AddToLine(line, count, true, addedSpaces ? null : &addedSpaces, addedTabs ? null : &addedTabs))
5331 if(string[c] == '\n')
5340 // Reset for next line
5344 if(string[c] == '\r' && *line == '\n')
5356 // Why was this here?
5359 // Add the line here
5361 if(!AddToLine(line,count,false, addedSpaces ? null : &addedSpaces, addedTabs ? null : &addedTabs))
5367 undoBuffer.dontRecord--;
5372 action.addedSpaces = addedSpaces;
5373 action.addedTabs = addedTabs;
5375 else if(replaceAction)
5377 replaceAction.y3 = y;
5378 replaceAction.x3 = x;
5379 replaceAction.addedSpaces = addedSpaces;
5380 replaceAction.addedTabs = addedTabs;
5383 UpdateCaretPosition(true);
5385 this.pasteOperation = false;
5398 DelCh(this.lines.first, 0, 0, this.lines.last, this.lineCount-1, ((EditLine)(this.lines.last)).count, true);
5399 SetViewToCursor(true);
5405 void PutCh(unichar ch)
5409 if((ch >= 32 /*&& ch <=126*/) || ch == '\n')
5410 //if((ch >= 32) || ch == '\n')
5412 int addedSpaces = 0, addedTabs = 0;
5413 ReplaceTextAction replaceAction = null;
5414 AddCharAction addCharAction = null;
5417 ch = (ch < 128) ? toupper(ch) : ch; // TODO: UNICODE TO UPPER
5419 if(this.overwrite && selX == x && selY == y && this.x < this.line.count)
5424 int len = GetText(null, line, y, x, line, y, x+1, false, false);
5425 oldString = new char[len];
5426 UTF32toUTF8Len(&ch, 1, buffer, 4);
5427 newString = CopyString(buffer);
5428 GetText(oldString, line, y, x, line, y, x+1, false, false);
5429 replaceAction = ReplaceTextAction { newString = newString, oldString = oldString, placeAfter = false };
5430 replaceAction.x1 = x;
5431 replaceAction.y1 = y;
5432 replaceAction.x2 = x+1;
5433 replaceAction.y2 = y;
5434 Record(replaceAction);
5436 undoBuffer.dontRecord++;
5437 DelCh(this.line, this.y, this.x, this.line, this.y, this.x+1, true);
5438 undoBuffer.dontRecord--;
5440 else if(!undoBuffer.dontRecord)
5442 if(selX != x || selY != y)
5447 int len = GetText(null, line, y, x, selLine, selY, selX, false, false);
5448 oldString = new char[len];
5449 UTF32toUTF8Len(&ch, 1, buffer, 4);
5450 newString = CopyString(buffer);
5451 GetText(oldString, line, y, x, selLine, selY, selX, false, false);
5452 replaceAction = ReplaceTextAction { newString = newString, oldString = oldString, placeAfter = true };
5453 if(selY < y || (selY == y && selX < x))
5455 replaceAction.x1 = selX;
5456 replaceAction.y1 = selY;
5457 replaceAction.x2 = x;
5458 replaceAction.y2 = y;
5462 replaceAction.x1 = x;
5463 replaceAction.y1 = y;
5464 replaceAction.x2 = selX;
5465 replaceAction.y2 = selY;
5467 Record(replaceAction);
5471 addCharAction = AddCharAction { y = y, x = x, ch = ch };
5472 Record(addCharAction);
5475 undoBuffer.dontRecord++;
5476 result = _AddCh(ch, &addedSpaces, &addedTabs);
5479 replaceAction.x3 = x;
5480 replaceAction.y3 = y;
5481 replaceAction.addedSpaces = addedSpaces;
5482 replaceAction.addedTabs = addedTabs;
5486 addCharAction.x -= addedTabs * (tabSize-1);
5487 addCharAction.addedSpaces = addedSpaces;
5488 addCharAction.addedTabs = addedTabs;
5490 undoBuffer.dontRecord--;
5494 if(result) SetViewToCursor(true);
5498 void PutS(const char * string)
5503 SetViewToCursor(true);
5508 void Printf(const char * format, ...)
5512 char temp[MAX_F_STRING];
5514 va_start(args, format);
5515 vsnprintf(temp, sizeof(temp), format, args);
5516 temp[sizeof(temp)-1] = 0;
5522 void SetContents(const char * format, ...)
5526 undoBuffer.dontRecord++;
5528 DelCh(this.lines.first, 0, 0, this.lines.last, this.lineCount-1, ((EditLine)(this.lines.last)).count, true);
5531 char temp[MAX_F_STRING];
5533 va_start(args, format);
5534 vsnprintf(temp, sizeof(temp), format, args);
5535 temp[sizeof(temp)-1] = 0;
5542 undoBuffer.dontRecord--;
5555 x -= 1 + _DelCh(line, y, x-1, line, y, x, true, false, null);
5558 else if(this.line.prev)
5560 EditLine line = this.line.prev;
5564 _DelCh(line, this.y-1, x, this.line, this.y, this.x, true, false, null);
5572 this.selLine = this.line;
5577 SetViewToCursor(true);
5582 Emptyline(this.line,this.y);
5583 this.selX = this.x = 0;
5586 this.selLine = this.line;
5588 SetViewToCursor(true);
5598 SetViewToCursor(true);
5606 SetViewToCursor(true);
5610 bool GoToLineNum(int lineNum)
5615 EditLine line = this.lines.first;
5616 for(c = 0; c < lineNum && line; c++, line = line.next);
5626 SetViewToCursor(true);
5633 // NOTE: Mismatch with NotifyCaretMove() for x/y + 1
5634 bool GoToPosition(EditLine line, int y, int x)
5646 for(line = this.lines.first, c = 0; c<y && line; c++, line = line.next);
5660 SetViewToCursor(true);
5667 void SetViewToCursor(bool setCaret)
5676 bool dontScroll = false;
5682 selected = selX != this.x || selY != y;
5689 checkLine = dropLine;
5695 checkLine = this.line;
5700 numLines = clientSize.h / space.h;
5702 // This is broken. The EditBox now doesn't do anything different when adding to it,
5703 // regardless of the previous scrolling position. It should be read and then set again
5704 // if one wishes to preserve it.
5705 /* // Don't scroll view to cursor if we're in a EES_NOCARET box
5706 if(style.noCaret && this.viewY < lineCount - numLines - 1)
5710 // Horizontal Adjustment
5711 if(!dontScroll && checkLine)
5715 dropX = AdjustXPosition(this.line, MAXINT, true, &x, checkX, 0);
5718 this.x = AdjustXPosition(this.line, MAXINT, true, &x, checkX, 0);
5724 if(x + space.w >= this.viewX + clientSize.w && clientSize.w >= space.w)
5725 viewX = x - clientSize.w+space.w;
5726 if(x < this.viewX + clientSize.w/2 - space.w)
5727 viewX = Max(0, x - clientSize.w/2 + space.w);
5735 // Vertical Adjustment
5736 if(viewY > checkY) viewY = checkY;
5737 if(viewY + numLines <= checkY)
5739 if(clientSize.h >= space.h)
5741 for(line = viewLine; line && (viewY + numLines <= checkY); line = line.next)
5751 for(;dropLine && dropLine.prev && dropY >= numLines;)
5753 dropLine = dropLine.prev;
5757 for(;this.line && this.line.prev && this.y >= numLines;)
5759 this.line = this.line.prev;
5764 SetScrollPosition(viewX, viewY * this.space.h);
5766 UpdateCaretPosition(setCaret);
5772 selLine = this.line;
5782 void CenterOnCursor()
5784 int numLines = clientSize.h / this.space.h;
5785 int y = this.y - numLines / 2;
5787 bool figureSyntax = false;
5788 EditLine oldViewLine = viewLine;
5789 if(y > this.lineCount - numLines) y = this.lineCount-numLines;
5794 for(;y < this.viewY; y++)
5796 this.viewLine = this.viewLine.prev;
5797 style.recomputeSyntax = true;
5799 for(;y > this.viewY; y--)
5801 this.viewLine = this.viewLine.next;
5802 figureSyntax = true;
5805 FigureStartSyntaxStates(oldViewLine, false);
5809 SetScrollPosition(this.viewX, viewY * this.space.h);
5810 UpdateCaretPosition(true);
5814 void SetCursorToView()
5825 numLines = clientSize.h / this.space.h;
5829 for(c=0, line = this.viewLine.next; line && c<numLines && (this.viewY < this.lineCount - numLines); line = line.next, c++);
5830 SetScrollPosition(this.viewX, (this.viewY + c) * this.space.h);
5834 //EditLine oldLine = this.line;
5835 bool lastOne = false;
5836 EditLine oldViewLine = this.viewLine;
5837 bool figureSyntax = false;
5839 if(this.y >= this.lineCount-1) return;
5841 for(c=0, line = this.line.next; line && c<numLines; line = line.next, c++)
5843 if(this.viewY + numLines < this.lines.count)
5845 this.viewLine = this.viewLine.next;
5847 figureSyntax = true;
5849 else if(c && !lastOne)
5858 FigureStartSyntaxStates(oldViewLine, false);
5859 this.x = AdjustXPosition(line, caretX, true, null, MAXINT, 0);
5862 SetViewToCursor(false);
5871 if(this.y == 0) return;
5873 numLines = clientSize.h / this.space.h;
5877 for(c=0, line = this.viewLine.prev; line && c<numLines; line = line.prev, c++);
5878 SetScrollPosition(this.viewX, (this.viewY - c) * this.space.h);
5882 for(c=0, line = this.line.prev; line && c<numLines; line = line.prev, c++)
5887 if(this.viewLine.prev)
5889 this.viewLine = this.viewLine.prev;
5891 style.recomputeSyntax = true;
5895 this.x = AdjustXPosition(line, caretX, true, null, MAXINT, 0);
5898 SetViewToCursor(false);
5905 if(this.viewLine.prev)
5908 // this.viewLine = this.viewLine.prev;
5911 SetScrollPosition(this.viewX, (this.viewY - 1) * this.space.h);
5918 if(this.viewLine.next)
5921 // this.viewLine = this.viewLine.next;
5924 SetScrollPosition(this.viewX, (this.viewY + 1) * this.space.h);
5931 EditLine l1, l2, line;
5932 int x1, x2, nx1, nx2;
5937 if(!this.selLine) return 0;
5938 if(this.selLine == this.line && this.selX == this.x) return 0;
5939 if(this.selY < this.y)
5946 else if(this.selY > this.y)
5953 else if(this.selX < this.x)
5955 l1 = l2 = this.line;
5961 l1 = l2 = this.line;
5965 nx1 = Min(x1,l1.count);
5966 nx2 = Min(x2,l2.count);
5968 // Find Number of Bytes Needed
5970 for(line = l1; line; line = line.next)
5972 if(line == l1) start = nx1; else start = 0;
5973 if(line == l2) end = nx2; else end = line.count;
5975 if(style.freeCaret && line == l2)
5978 count = Max(x2-Max(x1,l1.count),0);
5980 count = Max(x2-l2.count,0);
5984 if(line == l2) break;
5985 // Add Carriage Return / line Feed
5992 void GetSelPos(EditLine * l1, int *y1, int *x1, EditLine * l2, int * y2, int * x2, bool reorder)
5996 if(!reorder || this.selY < this.y)
5998 if(l1) *l1 = this.selLine;
5999 if(y1) *y1 = this.selY;
6000 if(x1) *x1 = this.selX;
6001 if(l2) *l2 = this.line;
6002 if(y2) *y2 = this.y;
6003 if(x2) *x2 = this.x;
6005 else if(this.selY > this.y)
6007 if(l1) *l1 = this.line;
6008 if(y1) *y1 = this.y;
6009 if(x1) *x1 = this.x;
6010 if(l2) *l2 = this.selLine;
6011 if(y2) *y2 = this.selY;
6012 if(x2) *x2 = this.selX;
6014 else if(this.selX < this.x)
6016 if(l1) *l1 = this.line;
6017 if(y1) *y1 = this.selY;
6018 if(x1) *x1 = this.selX;
6019 if(l2) *l2 = this.line;
6020 if(y2) *y2 = this.y;
6021 if(x2) *x2 = this.x;
6025 if(l1) *l1 = this.line;
6026 if(y1) *y1 = this.y;
6027 if(x1) *x1 = this.x;
6028 if(l2) *l2 = this.line;
6029 if(y2) *y2 = this.selY;
6030 if(x2) *x2 = this.selX;
6035 void SetSelPos(EditLine l1, int y1, int x1, EditLine l2, int y2, int x2)
6037 if(this && (this.selY != y1 || this.selX != x1 || this.y != y2 || this.x != x2))
6039 this.selLine = (EditLine)l1;
6042 this.line = (EditLine)l2;
6046 SetViewToCursor(true);
6050 int GetText(char * text, EditLine _l1, int _y1, int _x1, EditLine _l2, int _y2, int _x2, bool addCr, bool addSpaces)
6052 EditLine l1, l2, line;
6053 int x1, x2, nx1, nx2;
6084 nx1 = Min(x1,l1.count);
6085 nx2 = Min(x2,l2.count);
6088 for(line = l1; line; line = line.next)
6090 if(line == l1) start = nx1; else start = 0;
6091 if(line == l2) end = nx2; else end = line.count;
6094 CopyBytes(text, line.buffer + start, end - start);
6097 numChars += end-start;
6099 if(style.freeCaret && line == l2 && addSpaces)
6102 count = Max(x2-Max(x1,l1.count),0);
6104 count = Max(x2-l2.count,0);
6107 FillBytes(text,' ',count);
6113 if(line == l2) break;
6125 // '\0' terminate Terminate
6132 void GetSel(char * text, bool addCr)
6134 GetText(text, line, y, x, selLine, selY, selX, addCr, true);
6137 private void _Deselect() // This assumes marking lines as dirty is handled by the caller
6155 int size = SelSize();
6158 // Try to allocate memory
6159 ClipBoard clipBoard { };
6160 if(clipBoard.Allocate(size+1))
6162 GetSel(clipBoard.memory, true);
6175 ClipBoard clipBoard { };
6176 if(clipBoard.Load())
6177 PutS(clipBoard.memory);
6189 SetViewToCursor(true);
6195 void DeleteSelection()
6200 SetViewToCursor(true);
6206 void Save(File f, bool cr)
6209 savedAction = undoBuffer.curAction;
6211 for(line = this.lines.first; line; line = line.next)
6213 f.Write(line.buffer, line.count, 1);
6216 if(cr) f.Putc('\r');
6222 #define BUFFER_SIZE 16384
6225 undoBuffer.dontRecord++;
6228 char buffer[BUFFER_SIZE];
6232 int count = f.Read(buffer, 1, BUFFER_SIZE-1);
6233 buffer[count] = '\0';
6239 undoBuffer.dontRecord--;
6240 undoBuffer.count = 0;
6241 undoBuffer.curAction = 0;
6242 itemEditUndo.disabled = undoBuffer.curAction == 0;
6243 itemEditRedo.disabled = undoBuffer.curAction == undoBuffer.count;
6246 EditBoxFindResult Find(const char * text, bool matchWord, bool matchCase, bool isSearchDown)
6250 bool firstPass = true;
6251 EditBoxFindResult result = found;
6253 if(!this.line) return notFound;
6256 for(line = this.line;;)
6264 line = this.lines.first;
6270 line = this.lines.last;
6271 num = this.lineCount - 1;
6277 string = SearchString(line.buffer, firstPass ? Min(this.x,line.count) : 0,text,matchCase,matchWord);
6279 string = RSearchString(line.buffer,text,firstPass ? (Min(this.x,line.count)-1) : line.count,matchCase,matchWord);
6283 Select((void *)line,num,string - line.buffer,(void *)line,num,string - line.buffer + strlen(text));
6286 if(line == this.line && !firstPass) break;
6304 EditBoxFindResult FindInSelection(const char * text, bool matchWord, bool matchCase, EditLine l2, int y2, int x2)
6308 int searchLen = strlen(text);
6309 for(line = (EditLine)this.line, y = this.y; y <= y2; line = line.next, y++)
6311 char * string = SearchString(line.buffer, (y == this.y) ? Min(this.x,line.count) : 0,text,matchCase,matchWord);
6312 if(string && (y < y2 || (string - line.buffer) + searchLen <= x2))
6314 Select((void *)line,y,string - line.buffer,(void *)line,y,string - line.buffer + strlen(text));
6321 bool OnKeyDown(Key key, unichar ch)
6324 //PrintLn("OnKeyDown: code = ", key.code, ", mods = ", key.modifiers, ", ch = ", (int)ch);
6326 if(!NotifyKeyDown(master, this, key, ch))
6336 this.mouseMove = false;
6337 OnLeftButtonUp(0,0,0);
6338 SetViewToCursor(true);
6348 public class EditBoxStream : File
6351 BufferLocation start, sel;
6358 EditBox editBox = this.editBox;
6360 editBox.x = start.x;
6361 editBox.y = start.y;
6362 editBox.line = start.line;
6364 editBox.selX = sel.x;
6365 editBox.selY = sel.y;
6366 editBox.selLine = sel.line;
6368 editBox.SetViewToCursor(true);
6369 //editBox.ComputeColumn();
6373 property EditBox editBox
6381 start.line = value.line;
6385 sel.line = value.selLine;
6388 value.GoToHome(true);
6391 get { return editBox; }
6394 uint Read(byte * buffer, uint size, uint count)
6397 EditBox editBox = this.editBox;
6398 EditLine line = editBox.line;
6404 for(;read < count && line; line = (*&line.next))
6406 int numBytes = Min(count - read, (*&line.count) - x);
6409 memcpy(buffer + read, (*&line.buffer) + x, numBytes);
6414 for(;read < count && x < (*&line.count);)
6416 buffer[read++] = (*&line.buffer)[x++];
6423 buffer[read++] = '\n';
6428 if(x == (*&line.count) && (*&line.next))
6437 editBox.line = editBox.selLine = line;
6438 editBox.x = editBox.selX = x;
6439 editBox.y = editBox.selY = y;
6444 bool Seek(int pos, FileSeekMode mode)
6447 EditBox editBox = this.editBox;
6448 EditLine line = editBox.line;
6450 if(mode == FileSeekMode::start)
6452 pos = pos - this.pos;
6462 for(;read < pos && line; line = (*&line.next))
6464 int numBytes = Min(pos - read, (*&line.count) - x);
6467 read += numBytes; x += numBytes;
6470 /*for(;read < pos && x < (*&line.count);)
6498 for(;read < pos && line; line = (*&line.prev))
6500 int numBytes = Min(pos - read, x);
6506 /*for(;read < pos && x > 0;)
6516 x = (*&(*&line.prev).count);
6532 editBox.line = editBox.selLine = line;
6533 editBox.x = editBox.selX = x;
6534 editBox.y = editBox.selY = y;
6539 bool Puts(const char * string)
6541 EditBox editBox = this.editBox;
6542 BufferLocation start { editBox.line, editBox.y, editBox.x };
6546 editBox.AddS(string);
6548 pos.line = editBox.line;
6552 this.start.AdjustAdd(start, pos);
6553 sel.AdjustAdd(start, pos);
6558 // NOTE: BYTE, NOT UNICODE CHARACTER!
6561 EditBox editBox = this.editBox;
6562 BufferLocation start = { editBox.line, editBox.y, editBox.x };
6567 utf8Bytes[numBytes++] = ch;
6568 utf8Bytes[numBytes] = 0;
6569 if(UTF8Validate((char *)utf8Bytes))
6571 editBox.AddCh(UTF8_GET_CHAR(utf8Bytes, numBytes));
6573 pos.line = editBox.line;
6576 this.start.AdjustAdd(start, pos);
6577 sel.AdjustAdd(start, pos);
6584 bool Getc(char * ch)
6586 return Read(ch, 1, 1) ? true : false;
6589 void DeleteBytes(uint count)
6591 EditBox editBox = this.editBox;
6594 BufferLocation pos { editBox.line, editBox.y, editBox.x };
6595 BufferLocation end = pos;
6600 for(;c < count && end.line && end.x < end.line.count;)
6605 if(c < count && end.line && end.line.next)
6607 end.line = end.line.next;
6616 start.AdjustDelete(pos, end);
6617 sel.AdjustDelete(pos, end);
6619 editBox._DelCh(pos.line, pos.y, pos.x, end.line, end.y, end.x, true, false, null);