diff --git a/src/dlangui/platforms/windows/win32fonts.d b/src/dlangui/platforms/windows/win32fonts.d index 16bdf4f3..4b4abdde 100644 --- a/src/dlangui/platforms/windows/win32fonts.d +++ b/src/dlangui/platforms/windows/win32fonts.d @@ -194,7 +194,7 @@ class Win32Font : Font { lf.lfCharSet = ANSI_CHARSET; //DEFAULT_CHARSET; lf.lfFaceName[0..def.face.length] = def.face; lf.lfFaceName[def.face.length] = 0; - lf.lfHeight = -size; + lf.lfHeight = size; //-size; lf.lfItalic = italic; lf.lfOutPrecision = OUT_OUTLINE_PRECIS; //OUT_TT_ONLY_PRECIS; lf.lfClipPrecision = CLIP_DEFAULT_PRECIS; diff --git a/src/dlangui/widgets/editors.d b/src/dlangui/widgets/editors.d index b6509156..4d34f87c 100644 --- a/src/dlangui/widgets/editors.d +++ b/src/dlangui/widgets/editors.d @@ -567,6 +567,12 @@ enum EditorActions { Undo, /// Redo last undoed change Redo, + + /// Tab (e.g., Tab key to insert tab character or indent text) + Tab, + /// Tab (unindent text, or remove whitespace before cursor, usually Shift+Tab) + BackTab, + } /// base for all editor widgets @@ -580,6 +586,10 @@ class EditWidgetBase : WidgetGroup, EditableContentListener { protected int _spaceWidth; protected int _tabSize = 4; + protected bool _wantTabs = true; + protected bool _useSpacesForTabs = false; + + this(string ID) { super(ID); focusable = true; @@ -639,9 +649,51 @@ class EditWidgetBase : WidgetGroup, EditableContentListener { new Action(EditorActions.Redo, KeyCode.KEY_Y, KeyFlag.Control), new Action(EditorActions.Redo, KeyCode.KEY_Z, KeyFlag.Control|KeyFlag.Shift), + new Action(EditorActions.Tab, KeyCode.TAB, 0), + new Action(EditorActions.BackTab, KeyCode.TAB, KeyFlag.Shift), ]); } + /// when true, Tab / Shift+Tab presses are processed internally in widget (e.g. insert tab character) instead of focus change navigation. + @property bool wantTabs() { + return _wantTabs; + } + + /// sets tab size (in number of spaces) + @property EditWidgetBase wantTabs(bool wantTabs) { + _wantTabs = wantTabs; + return this; + } + + /// when true, spaces will be inserted instead of tabs + @property bool useSpacesForTabs() { + return _useSpacesForTabs; + } + + /// set new Tab key behavior flag: when true, spaces will be inserted instead of tabs + @property EditWidgetBase useSpacesForTabs(bool useSpacesForTabs) { + _useSpacesForTabs = useSpacesForTabs; + return this; + } + + /// returns tab size (in number of spaces) + @property int tabSize() { + return _tabSize; + } + + /// sets tab size (in number of spaces) + @property EditWidgetBase tabSize(int newTabSize) { + if (newTabSize < 1) + newTabSize = 1; + else if (newTabSize > 16) + newTabSize = 16; + if (newTabSize != _tabSize) { + _tabSize = newTabSize; + requestLayout(); + } + return this; + } + protected void updateMaxLineWidth() { } @@ -775,6 +827,19 @@ class EditWidgetBase : WidgetGroup, EditableContentListener { } } + /// generate string of spaces, to reach next tab position + protected dstring spacesForTab(int currentPos) { + int newPos = (currentPos + tabSize + 1) / tabSize * tabSize; + return " "d[0..(newPos - currentPos)]; + } + + /// returns true if one or more lines selected fully + protected bool wholeLinesSelected() { + return _selectionRange.end.line > _selectionRange.start.line + && _selectionRange.end.pos == 0 + && _selectionRange.start.pos == 0; + } + override protected bool handleAction(Action a) { TextPosition oldCaretPos = _caretPos; dstring currentLine = _content[_caretPos.line]; @@ -923,6 +988,52 @@ class EditWidgetBase : WidgetGroup, EditableContentListener { { _content.redo(); } + return true; + case EditorActions.Tab: + { + if (_selectionRange.empty) { + if (_useSpacesForTabs) { + // insert one or more spaces to + EditOperation op = new EditOperation(EditAction.Replace, TextRange(_caretPos, _caretPos), [spacesForTab(_caretPos.pos)]); + _content.performOperation(op); + } else { + // just insert tab character + EditOperation op = new EditOperation(EditAction.Replace, TextRange(_caretPos, _caretPos), ["\t"d]); + _content.performOperation(op); + } + } else { + if (wholeLinesSelected()) { + // indent range + indentRange(false); + } else { + // insert tab + if (_useSpacesForTabs) { + // insert one or more spaces to + EditOperation op = new EditOperation(EditAction.Replace, _selectionRange, [spacesForTab(_selectionRange.start.pos)]); + _content.performOperation(op); + } else { + // just insert tab character + EditOperation op = new EditOperation(EditAction.Replace, _selectionRange, ["\t"d]); + _content.performOperation(op); + } + } + + } + } + return true; + case EditorActions.BackTab: + { + if (_selectionRange.empty) { + // remove spaces before caret + } else { + if (wholeLinesSelected()) { + // unindent range + indentRange(true); + } else { + // remove space before selection + } + } + } return true; default: break; @@ -930,6 +1041,67 @@ class EditWidgetBase : WidgetGroup, EditableContentListener { return super.handleAction(a); } + /// change line indent + protected dstring indentLine(dstring src, bool back) { + int firstNonSpace = -1; + int x = 0; + int unindentPos = -1; + for (int i = 0; i < src.length; i++) { + dchar ch = src[i]; + if (ch == ' ') { + x++; + } else if (ch == '\t') { + x = (x + tabSize + 1) / tabSize * tabSize; + } else { + firstNonSpace = i; + break; + } + if (x <= tabSize) + unindentPos = i + 1; + } + if (firstNonSpace == -1) // only spaces or empty line -- do not change it + return src; + if (back) { + // unindent + if (unindentPos == -1) + return src; // no change + if (unindentPos == src.length) + return ""d; + return src[unindentPos .. $].dup; + } else { + // indent + if (_useSpacesForTabs) { + return spacesForTab(0) ~ src; + } else { + return "\t"d ~ src; + } + } + return src; + } + + /// indent / unindent range + protected void indentRange(bool back) { + int lineCount = _selectionRange.end.line - _selectionRange.start.line; + dstring[] newContent = new dstring[lineCount + 1]; + bool changed = false; + for (int i = 0; i < lineCount; i++) { + dstring srcline = _content.line(_selectionRange.start.line + i); + dstring dstline = indentLine(srcline, back); + newContent[i] = dstline; + if (dstline.length != srcline.length) + changed = true; + } + if (changed) { + TextRange saveRange = _selectionRange; + TextPosition saveCursor = _caretPos; + EditOperation op = new EditOperation(EditAction.Replace, _selectionRange, newContent); + _content.performOperation(op); + _selectionRange = saveRange; + _caretPos = saveCursor; + ensureCaretVisible(); + } + } + /// handle keys override bool onKeyEvent(KeyEvent event) { // @@ -941,7 +1113,7 @@ class EditWidgetBase : WidgetGroup, EditableContentListener { } else if (event.action == KeyAction.Text && event.text.length) { Log.d("text entered: ", event.text); dchar ch = event.text[0]; - if (ch >= 32 || ch == '\t') { // ignore Backspace and Return + if (ch >= 32) { // ignore Backspace and Return EditOperation op = new EditOperation(EditAction.Replace, _selectionRange, [event.text]); _content.performOperation(op); return true; @@ -987,6 +1159,7 @@ class EditLine : EditWidgetBase { super(ID); _content = new EditableContent(false); _content.contentChangeListeners = this; + wantTabs = false; styleId = "EDIT_LINE"; text = initialContent; }