diff options
Diffstat (limited to 'Samples/CrowIDE/src/Editors')
20 files changed, 5874 insertions, 0 deletions
diff --git a/Samples/CrowIDE/src/Editors/CodeBuffer/CodeBuffer.cs b/Samples/CrowIDE/src/Editors/CodeBuffer/CodeBuffer.cs new file mode 100644 index 00000000..7d5efec9 --- /dev/null +++ b/Samples/CrowIDE/src/Editors/CodeBuffer/CodeBuffer.cs @@ -0,0 +1,530 @@ +// +// CodeTextBuffer.cs +// +// Author: +// Jean-Philippe Bruyère <jp.bruyere@hotmail.com> +// +// Copyright (c) 2017 jp +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using System.Diagnostics; +using System.Threading; + +namespace Crow.Coding +{ + /// <summary> + /// Code buffer, lines are arranged in a List<string>, new line chars are removed during string.split on '\n...', + /// </summary> + public class CodeBuffer + { + public ReaderWriterLockSlim editMutex = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); + + //those events are handled in SourceEditor to help keeping sync between textbuffer,parser and editor. + //modified lines are marked for reparse + #region Events + public event EventHandler<CodeBufferEventArgs> LineUpadateEvent; + public event EventHandler<CodeBufferEventArgs> LineRemoveEvent; + public event EventHandler<CodeBufferEventArgs> LineAdditionEvent; + public event EventHandler<CodeBufferEventArgs> FoldingEvent; + public event EventHandler BufferCleared; + public event EventHandler SelectionChanged; + public event EventHandler PositionChanged; + #endregion + + string lineBreak = Interface.LineBreak; + List<CodeLine> lines = new List<CodeLine>(); + public int longestLineIdx = 0; + public int longestLineCharCount = 0; + /// <summary> + /// real position in char arrays, tab = 1 char + /// </summary> + int _currentLine = 0; + int _currentCol = 0; + + public int LineCount { get { return lines.Count;}} + public int IndexOf (CodeLine cl) { + return lines.IndexOf (cl); + } + + public CodeLine this[int i] + { + get { return i < LineCount ? lines[i] : null; } + set { + if (lines [i] == value) + return; + editMutex.EnterWriteLock (); + lines [i] = value; + editMutex.ExitWriteLock (); + LineUpadateEvent.Raise (this, new CodeBufferEventArgs (i)); + } + } + + public void RemoveAt(int i){ + editMutex.EnterWriteLock (); + lines.RemoveAt (i); + editMutex.ExitWriteLock (); + LineRemoveEvent.Raise (this, new CodeBufferEventArgs (i)); + } + public void Insert(int i, string item){ + editMutex.EnterWriteLock (); + lines.Insert (i, item); + editMutex.ExitWriteLock (); + LineAdditionEvent.Raise (this, new CodeBufferEventArgs (i)); + } + public void Add(CodeLine item){ + editMutex.EnterWriteLock (); + lines.Add (item); + editMutex.ExitWriteLock (); + LineAdditionEvent.Raise (this, new CodeBufferEventArgs (lines.Count - 1)); + } + public void AddRange (string[] items){ + int start = lines.Count; + editMutex.EnterWriteLock (); + for (int i = 0; i < items.Length; i++) + lines.Add (items [i]); + editMutex.ExitWriteLock (); + LineAdditionEvent.Raise (this, new CodeBufferEventArgs (start, items.Length)); + } + public void AddRange (CodeLine[] items){ + int start = lines.Count; + editMutex.EnterWriteLock (); + lines.AddRange (items); + editMutex.ExitWriteLock (); + LineAdditionEvent.Raise (this, new CodeBufferEventArgs (start, items.Length)); + } + public void Clear () { + editMutex.EnterWriteLock (); + longestLineCharCount = 0; + lines.Clear (); + editMutex.ExitWriteLock (); + BufferCleared.Raise (this, null); + } + public void UpdateLine(int i, string newContent){ + editMutex.EnterWriteLock (); + this [i].Content = newContent; + editMutex.ExitWriteLock (); + LineUpadateEvent.Raise (this, new CodeBufferEventArgs (i)); + } + public void AppenedLine(int i, string newContent){ + editMutex.EnterWriteLock (); + this [i].Content += newContent; + editMutex.ExitWriteLock (); + LineUpadateEvent.Raise (this, new CodeBufferEventArgs (i)); + } + public void RemoveLeadingTab (int l) { + if (this [l] [0] == '\t') { + UpdateLine (l, this [l].Content.Substring (1)); + return; + } + int i = 0; + while (i < Interface.TabSize) { + if (this [l] [i] != ' ') + break; + i++; + } + if (i > 0) + UpdateLine (l, this [l].Content.Substring (i)); + } + public void ToogleFolding (int line) { + if (!this [line].IsFoldable) + return; + editMutex.EnterWriteLock (); + this [line].IsFolded = !this [line].IsFolded; + editMutex.ExitWriteLock (); + FoldingEvent.Raise (this, new CodeBufferEventArgs (line)); + } + public void Load(string rawSource, string lineBrkRegex = @"\r\n|\r|\n|\\\n") { + this.Clear(); + + if (string.IsNullOrEmpty (rawSource)) + return; + + AddRange (Regex.Split (rawSource, lineBrkRegex)); + + lineBreak = detectLineBreakKind (rawSource); + } + + /// <summary> + /// Finds the longest visual line as printed on screen with tabulation replaced with n spaces + /// </summary> + public void FindLongestVisualLine(){ + longestLineCharCount = 0; + editMutex.EnterReadLock (); + for (int i = 0; i < this.LineCount; i++) { + if (lines[i].PrintableLength > longestLineCharCount) { + longestLineCharCount = lines[i].PrintableLength; + longestLineIdx = i; + } + } + editMutex.ExitReadLock (); + //Debug.WriteLine ("Longest line: {0}->{1}", longestLineIdx, longestLineCharCount); + } + /// <summary> line break could be '\r' or '\n' or '\r\n' </summary> + static string detectLineBreakKind(string buffer){ + string strLB = ""; + + if (string.IsNullOrEmpty(buffer)) + return Interface.LineBreak; + int i = 0; + while ( i < buffer.Length) { + if (buffer [i] == '\r') { + strLB += '\r'; + i++; + } + if (i < buffer.Length) { + if (buffer [i] == '\r') + return "\r"; + if (buffer[i] == '\n') + strLB += '\n'; + } + if (!string.IsNullOrEmpty (strLB)) + return strLB; + i++; + } + return Interface.LineBreak; + } + /// <summary> + /// return all lines with linebreaks + /// </summary> + public string FullText{ + get { + if (lines.Count == 0) + return ""; + string tmp = ""; + editMutex.EnterReadLock (); + for (int i = 0; i < lines.Count -1; i++) + tmp += lines [i].Content + this.lineBreak; + tmp += lines [lines.Count - 1].Content; + editMutex.ExitReadLock (); + return tmp; + } + } + + /// <summary> + /// unfolded and not in folds line count + /// </summary> + public int UnfoldedLines { + get { + int i = 0, vl = 0; + editMutex.EnterReadLock (); + while (i < LineCount) { + if (this [i].IsFolded) { + i = GetEndNodeIndex (i); + if (i < 0) { + Console.WriteLine ("error folding"); + break; + } + } + i++; + vl++; + } + editMutex.ExitReadLock (); + //Debug.WriteLine ("unfolded lines: " + vl); + return vl; + } + } + + /// <summary> + /// convert visual position to buffer position + /// </summary> + Point getBuffPos (Point visualPos) { + int i = 0; + int buffCol = 0; + while (i < visualPos.X) { + if (this [visualPos.Y] [buffCol] == '\t') + i += Interface.TabSize; + else + i++; + buffCol++; + } + return new Point (buffCol, visualPos.Y); + } + + public int GetEndNodeIndex (int line) { + return IndexOf (this [line].SyntacticNode.EndLine); + } + + int ConverteTabulatedPosOfCurLine (int column) { + int tmp = 0; + int i = 0; + while (i < lines [_currentLine].Content.Length){ + if (lines [_currentLine].Content [i] == '\t') + tmp += 4; + else + tmp++; + if (tmp > column) + break; + i++; + } + return i; + } + + public int CurrentTabulatedColumn { + get { + return lines [_currentLine].Content.Substring (0, _currentCol). + Replace ("\t", new String (' ', Interface.TabSize)).Length; + } + } + /// <summary> + /// Gets visual position computed from actual buffer position + /// </summary> +// public Point TabulatedPosition { +// get { return new Point (TabulatedColumn, _currentLine); } +// } + /// <summary> + /// set buffer current position from visual position + /// </summary> +// public void SetBufferPos(Point tabulatedPosition) { +// CurrentPosition = getBuffPos(tabulatedPosition); +// } + + #region Editing and moving cursor + Point selStartPos = -1; //selection start (row,column) + Point selEndPos = -1; //selection end (row,column) + + public bool SelectionInProgress { get { return selStartPos >= 0; }} + public void SetSelStartPos () { + selStartPos = selEndPos = CurrentPosition; + SelectionChanged.Raise (this, null); + } + public void SetSelEndPos () { + selEndPos = CurrentPosition; + SelectionChanged.Raise (this, null); + } + public void SetSelectionOnFullLines () { + if (!SelectionInProgress) + return; + Point s = new Point (0, SelectionStart.Y); + Point e = new Point (this [SelectionEnd.Y].Length, SelectionEnd.Y); + selStartPos = s; + selEndPos = e; + SelectionChanged.Raise (this, null); + } + /// <summary> + /// Set selection in buffer to -1, empty selection + /// </summary> + public void ResetSelection () { + selStartPos = selEndPos = -1; + SelectionChanged.Raise (this, null); + } + + public string SelectedText { + get { + if (SelectionIsEmpty) + return ""; + Point selStart = SelectionStart; + Point selEnd = SelectionEnd; + if (selStart.Y == selEnd.Y) + return this [selStart.Y].Content.Substring (selStart.X, selEnd.X - selStart.X); + string tmp = ""; + tmp = this [selStart.Y].Content.Substring (selStart.X); + for (int l = selStart.Y + 1; l < selEnd.Y; l++) { + tmp += Interface.LineBreak + this [l].Content; + } + tmp += Interface.LineBreak + this [selEnd.Y].Content.Substring (0, selEnd.X); + return tmp; + } + } + /// <summary> + /// ordered selection start and end positions in char units + /// </summary> + public Point SelectionStart { + get { return selEndPos < 0 || selStartPos.Y < selEndPos.Y ? selStartPos : + selStartPos.Y > selEndPos.Y ? selEndPos : + selStartPos.X < selEndPos.X ? selStartPos : selEndPos; } + } + public Point SelectionEnd { + get { return selEndPos < 0 || selStartPos.Y > selEndPos.Y ? selStartPos : + selStartPos.Y < selEndPos.Y ? selEndPos : + selStartPos.X > selEndPos.X ? selStartPos : selEndPos; } + } + public bool SelectionIsEmpty + { get { return selEndPos == selStartPos; } } + int requestedColumn = -1; + /// <summary> + /// Current column in buffer coordinate, tabulation = 1 char + /// </summary> + public int CurrentColumn{ + get { return _currentCol; } + set { + if (value == _currentCol) + return; + + editMutex.EnterReadLock (); + + if (value < 0) + _currentCol = 0; + else if (value > lines [_currentLine].Length) + _currentCol = lines [_currentLine].Length; + else + _currentCol = value; + + requestedColumn = CurrentTabulatedColumn; + + editMutex.ExitReadLock (); + + PositionChanged.Raise (this, null); + } + } + /// <summary> + /// Current row in buffer coordinate, tabulation = 1 char + /// </summary> + public int CurrentLine{ + get { return _currentLine; } + set { + if (value == _currentLine) + return; + + editMutex.EnterReadLock (); + + if (LineCount == 0) + _currentLine = 0; + else if (value >= lines.Count) + _currentLine = lines.Count-1; + else if (value < 0) + _currentLine = 0; + else + _currentLine = value; +// if (_currentCol < 0) +// requestedColumn = tabu _currentCol; + int tabulatedRequestedCol = ConverteTabulatedPosOfCurLine(requestedColumn); + if (requestedColumn > lines [_currentLine].PrintableLength) + _currentCol = lines [_currentLine].Length; + else + //_currentCol = requestedColumn; + _currentCol = tabulatedRequestedCol; + //Debug.WriteLine ("buff cur line: " + _currentLine); + + editMutex.ExitReadLock(); + + PositionChanged.Raise (this, null); + } + } + public CodeLine CurrentCodeLine { + get { return this [_currentLine]; } + } + /// <summary> + /// Current position in buffer coordinate, tabulation = 1 char + /// </summary> + public Point CurrentPosition { + get { return new Point(CurrentColumn, CurrentLine); } +// set { +// _currentCol = value.X; +// _currentLine = value.Y; +// } + } + /// <summary> + /// get char at current position in buffer + /// </summary> + protected Char CurrentChar { get { return lines [CurrentLine] [CurrentColumn]; } } + + public void GotoWordStart(){ + if (this[CurrentLine].Length == 0) + return; + CurrentColumn--; + //skip white spaces + while (!char.IsLetterOrDigit (this.CurrentChar) && CurrentColumn > 0) + CurrentColumn--; + while (char.IsLetterOrDigit (this.CurrentChar) && CurrentColumn > 0) + CurrentColumn--; + if (!char.IsLetterOrDigit (this.CurrentChar)) + CurrentColumn++; + } + public void GotoWordEnd(){ + //skip white spaces + if (CurrentColumn >= this [CurrentLine].Length - 1) + return; + while (!char.IsLetterOrDigit (this.CurrentChar) && CurrentColumn < this [CurrentLine].Length-1) + CurrentColumn++; + while (char.IsLetterOrDigit (this.CurrentChar) && CurrentColumn < this [CurrentLine].Length-1) + CurrentColumn++; + if (char.IsLetterOrDigit (this.CurrentChar)) + CurrentColumn++; + } + public void DeleteChar() + { + editMutex.EnterWriteLock (); + if (SelectionIsEmpty) { + if (CurrentColumn == 0) { + if (CurrentLine == 0) { + editMutex.ExitWriteLock (); + return; + } + CurrentLine--; + CurrentColumn = this [CurrentLine].Length; + AppenedLine (CurrentLine, this [CurrentLine + 1].Content); + RemoveAt (CurrentLine + 1); + editMutex.ExitWriteLock (); + return; + } + CurrentColumn--; + UpdateLine (CurrentLine, this [CurrentLine].Content.Remove (CurrentColumn, 1)); + } else { + int linesToRemove = SelectionEnd.Y - SelectionStart.Y + 1; + int l = SelectionStart.Y; + + if (linesToRemove > 0) { + UpdateLine (l, this [l].Content.Remove (SelectionStart.X, this [l].Length - SelectionStart.X) + + this [SelectionEnd.Y].Content.Substring (SelectionEnd.X, this [SelectionEnd.Y].Length - SelectionEnd.X)); + l++; + for (int c = 0; c < linesToRemove - 1; c++) + RemoveAt (l); + CurrentLine = SelectionStart.Y; + CurrentColumn = SelectionStart.X; + } else + UpdateLine (l, this [l].Content.Remove (SelectionStart.X, SelectionEnd.X - SelectionStart.X)); + CurrentColumn = SelectionStart.X; + ResetSelection (); + } + editMutex.ExitWriteLock (); + } + /// <summary> + /// Insert new string at caret position, should be sure no line break is inside. + /// </summary> + /// <param name="str">String.</param> + public void Insert(string str) + { + if (!SelectionIsEmpty) + this.DeleteChar (); + string[] strLines = Regex.Split (str, "\r\n|\r|\n|" + @"\\n").ToArray(); + UpdateLine (CurrentLine, this [CurrentLine].Content.Insert (CurrentColumn, strLines[0])); + CurrentColumn += strLines[0].Length; + for (int i = 1; i < strLines.Length; i++) { + InsertLineBreak (); + UpdateLine (CurrentLine, this [CurrentLine].Content.Insert (CurrentColumn, strLines[i])); + CurrentColumn += strLines[i].Length; + } + } + /// <summary> + /// Insert a line break. + /// </summary> + public void InsertLineBreak() + { + if (CurrentColumn > 0) { + Insert (CurrentLine + 1, this [CurrentLine].Content.Substring (CurrentColumn)); + UpdateLine (CurrentLine, this [CurrentLine].Content.Substring (0, CurrentColumn)); + } else + Insert(CurrentLine, ""); + + CurrentColumn = 0; + CurrentLine++; + } + #endregion + } +} + diff --git a/Samples/CrowIDE/src/Editors/CodeBuffer/CodeBufferEventArgs.cs b/Samples/CrowIDE/src/Editors/CodeBuffer/CodeBufferEventArgs.cs new file mode 100644 index 00000000..07dd25b6 --- /dev/null +++ b/Samples/CrowIDE/src/Editors/CodeBuffer/CodeBufferEventArgs.cs @@ -0,0 +1,20 @@ +using System; + +namespace Crow.Coding +{ + public class CodeBufferEventArgs : EventArgs { + public int LineStart; + public int LineCount; + + public CodeBufferEventArgs(int lineNumber) { + LineStart = lineNumber; + LineCount = 1; + } + public CodeBufferEventArgs(int lineStart, int lineCount) { + LineStart = lineStart; + LineCount = lineCount; + } + } + +} + diff --git a/Samples/CrowIDE/src/Editors/CodeBuffer/CodeLine.cs b/Samples/CrowIDE/src/Editors/CodeBuffer/CodeLine.cs new file mode 100644 index 00000000..23f43fba --- /dev/null +++ b/Samples/CrowIDE/src/Editors/CodeBuffer/CodeLine.cs @@ -0,0 +1,82 @@ +using System; +using System.Text; +using System.Collections.Generic; +using System.Linq; + +namespace Crow.Coding +{ + public class CodeLine + { + public string Content; + public List<Token> Tokens; + public int EndingState = 0; + public Node SyntacticNode; + public ParserException exception; + + public CodeLine (string _content){ + Content = _content; + Tokens = null; + exception = null; + } + + public char this[int i] + { + get { return Content[i]; } + set { + if (Content [i] == value) + return; + StringBuilder sb = new StringBuilder(Content); + sb[i] = value; + Content = sb.ToString(); + Tokens = null; + //LineUpadateEvent.Raise (this, new CodeBufferEventArgs (i)); + } + } + public bool IsFoldable { get { return SyntacticNode == null ? false : + SyntacticNode.EndLine != SyntacticNode.StartLine && SyntacticNode.EndLine != null; } } + public int FoldingLevel { get { return IsFoldable ? SyntacticNode.Level : 0; } } + public bool IsFolded = false; + public bool IsParsed { + get { return Tokens != null; } + } + public string PrintableContent { + get { + return string.IsNullOrEmpty (Content) ? "" : Content.Replace ("\t", new String (' ', Interface.TabSize)); + } + } + public int PrintableLength { + get { + return PrintableContent.Length; + } + } + public int Length { + get { + return string.IsNullOrEmpty (Content) ? 0 : Content.Length; + } + } + public int FirstNonBlankTokIndex { + get { return Tokens == null ? -1 : Tokens.FindIndex (tk=>tk.Type != BufferParser.TokenType.WhiteSpace); } + } + + public void SetLineInError (ParserException ex) { + Tokens = null; + exception = ex; + } + +// public static implicit operator string(CodeLine sl) { +// return sl == null ? "" : sl.Content; +// } + public static implicit operator CodeLine(string s) { + return new CodeLine(s); + } + public static bool operator ==(string s1, CodeLine s2) + { + return string.Equals (s1, s2.Content); + } + public static bool operator !=(string s1, CodeLine s2) + { + return !string.Equals (s1, s2.Content); + } + } +} + diff --git a/Samples/CrowIDE/src/Editors/CodeBuffer/Node.cs b/Samples/CrowIDE/src/Editors/CodeBuffer/Node.cs new file mode 100644 index 00000000..9db55428 --- /dev/null +++ b/Samples/CrowIDE/src/Editors/CodeBuffer/Node.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; + +namespace Crow.Coding +{ + public class Node + { + public Node Parent; + public string Name; + public string Type; + public CodeLine StartLine; + public CodeLine EndLine; + public Dictionary<string,string> Attributes = new Dictionary<string, string> (); + + public List<Node> Children = new List<Node>(); + + public Node () + { + } + + public void AddChild (Node child) { + child.Parent = this; + Children.Add (child); + } + + public int Level { + get { return Parent == null ? 1 : Parent.Level + 1; } + } + + public override string ToString () + { + return string.Format ("Name:{0}, Type:{1}\n\tparent:{2}", Name, Type, Parent); + } + } +} + diff --git a/Samples/CrowIDE/src/Editors/CodeBuffer/TextBuffer.cs b/Samples/CrowIDE/src/Editors/CodeBuffer/TextBuffer.cs new file mode 100644 index 00000000..e8c87182 --- /dev/null +++ b/Samples/CrowIDE/src/Editors/CodeBuffer/TextBuffer.cs @@ -0,0 +1,527 @@ +// +// CodeTextBuffer.cs +// +// Author: +// Jean-Philippe Bruyère <jp.bruyere@hotmail.com> +// +// Copyright (c) 2017 jp +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using System.Diagnostics; +using System.Threading; +using System.Text; + +namespace Crow.Text +{ + /// <summary> + /// Code buffer, lines are arranged in a List<string>, new line chars are removed during string.split on '\n...', + /// </summary> + public class TextBuffer + { + public ReaderWriterLockSlim editMutex = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); + static Regex slb = new Regex ("\\n");//single char line break used internaly + Regex reghexLineBrk = new Regex(@"\r\n|\r|\n|\\\n");//original text line break regex + + #region Events + public event EventHandler<TextBufferEventArgs> LineUpadateEvent; + public event EventHandler<TextBufferEventArgs> LineRemoveEvent; + public event EventHandler<TextBufferEventArgs> LineAdditionEvent; + public event EventHandler BufferCleared; + public event EventHandler SelectionChanged; + public event EventHandler PositionChanged; + #endregion + + StringBuilder buffer = new StringBuilder(); + string lineBreak = Interface.LineBreak;//detected linebreak kind in original source + List<int> lineLength = new List<int>();//line length table + /// <summary> + /// real position in char arrays, tab = 1 char + /// </summary> + int _currentLine = 0; + int _currentCol = 0; + + /// <summary> + /// Gets the total line count. + /// </summary> + public int LineCount { get { return lineLength.Count;}} + + /// <summary> + /// get a substring in the buffer + /// </summary> + /// <returns>a new string</returns> + /// <param name="idx">absolute index in the buffer</param> + /// <param name="length">length of the substring</param> + public string GetSubString (int idx, int length){ + return buffer.ToString (idx, length); + } + /// <summary> + /// Gets length of a line. + /// </summary> + /// <returns>length of line</returns> + /// <param name="lineIdx">line nuber</param> + public int GetLineLength (int lineIdx) { + return lineLength [lineIdx]; + } + /// <summary> + /// get a single charactere in the buffer + /// </summary> + /// <returns>a single char</returns> + /// <param name="idx">absolute index in the buffer</param> + public char GetCharAt (int idx){ + return buffer [idx]; + } + /// <summary> + /// return full text with original line breaks + /// </summary> + /// <value>a string containing the full text</value> + public string FullText { + get { return buffer.Replace("\n", lineBreak).ToString (); } + set { + Load (value); + } + } + /// <summary> + /// Gets the buffer pointer of a line + /// </summary> + /// <returns>absolute index of the line in the buffer</returns> + /// <param name="i">line number</param> + public int GetBufferIndexOfLine (int i) { + int ptr = 0; + editMutex.EnterReadLock (); + for (int j = 0; j < i; j++) { + ptr += lineLength [j]; + } + editMutex.ExitReadLock (); + return ptr; + } + /// <summary> + /// Gets the buffer pointer for the current position + /// </summary> + /// <value>absolute index in the buffer of current position</value> + public int BufferIndexOfCurrentPosition { + get {return GetBufferIndexOfLine (_currentLine) + _currentCol;} + } + public int this[int i] + { + get { + int ptr = 0; + editMutex.EnterReadLock (); + for (int j = 0; j < i; j++) { + ptr += lineLength [j]; + } + editMutex.ExitReadLock (); + return ptr; + } + } + /// <summary> + /// remove line number i + /// </summary> + /// <param name="i">index of the line</param> + public void RemoveLine(int i){ + editMutex.EnterWriteLock (); + buffer.Remove (GetBufferIndexOfLine (i), lineLength [i]); + lineLength.RemoveAt (i); + editMutex.ExitWriteLock (); + LineRemoveEvent.Raise (this, new TextBufferEventArgs (i)); + } + /// <summary> + /// insert string without linebreaks at position i in buff + /// </summary> + /// <param name="i">absolute index in the buffer</param> + /// <param name="str">linebreak free string</param> + public void InsertAt(int i, string str){ + editMutex.EnterWriteLock (); + buffer.Insert (this [i], str); + lineLength.Insert (i, str.Length); + editMutex.ExitWriteLock (); + LineAdditionEvent.Raise (this, new TextBufferEventArgs (i)); + } + public void AddLine(string str){ + editMutex.EnterWriteLock (); + if (lineLength.LastOrDefault() == 0) { + buffer.Append (str); + lineLength [lineLength.Count - 1] = str.Length; + lineLength.Add (0); + } + editMutex.ExitWriteLock (); + LineAdditionEvent.Raise (this, new TextBufferEventArgs (lineLength.Count - 1)); + } + public void AddRange (string[] items){ + int start = lineLength.Count; + editMutex.EnterWriteLock (); + for (int i = 0; i < items.Length; i++) + AddLine (items [i]); + editMutex.ExitWriteLock (); + LineAdditionEvent.Raise (this, new TextBufferEventArgs (start, items.Length)); + } + public void Clear () { + editMutex.EnterWriteLock (); + lineLength.Clear (); + buffer.Clear (); + editMutex.ExitWriteLock (); + BufferCleared.Raise (this, null); + } + public void UpdateLine(int i, string newContent){ + editMutex.EnterWriteLock (); + int ptrL = this [i]; + buffer.Remove (ptrL, lineLength [i]); + buffer.Insert (ptrL, newContent); + lineLength [i] = newContent.Length; + editMutex.ExitWriteLock (); + LineUpadateEvent.Raise (this, new TextBufferEventArgs (i)); + } + public void AppenedLine(int i, string newContent){ + editMutex.EnterWriteLock (); + int ptr = this [i] + lineLength [i]; + if (i < LineCount - 1) + ptr--; + buffer.Insert(ptr, newContent); + lineLength [i] += newContent.Length; + editMutex.ExitWriteLock (); + LineUpadateEvent.Raise (this, new TextBufferEventArgs (i)); + } + /// <summary> + /// Insert new string at caret position, should be sure no line break is inside. + /// </summary> + /// <param name="str">String.</param> + public void InsertAt(string str) + { + if (!SelectionIsEmpty) + this.Delete (); + + editMutex.EnterWriteLock (); + + string tmp = reghexLineBrk.Replace (str, "\n");//use single char line break in buffer + int buffPtr = this [CurrentLine] + CurrentColumn; + buffer.Insert (buffPtr, tmp); + + int lPtr = CurrentLine, strPtr = 0; + int remainingLength = lineLength [lPtr] - CurrentColumn; + lineLength [lPtr] = CurrentColumn; + foreach (Match match in slb.Matches(tmp)) + { + lineLength [lPtr] += match.Index + 1 - strPtr; + lPtr++; + lineLength.Insert (lPtr, 0); + strPtr = match.Index + 1; + //CurrentLine++; + } + remainingLength += tmp.Length - strPtr; + lineLength [lPtr] += remainingLength; + + CurrentLine = lPtr; + if (strPtr == 0) + CurrentColumn += tmp.Length; + else + CurrentColumn = tmp.Length - strPtr; + + editMutex.ExitWriteLock (); + if (strPtr>0) + LineAdditionEvent.Raise (this, null); + else + LineUpadateEvent.Raise (this, null); + } + /// <summary> + /// Insert a line break. + /// </summary> + public void InsertLineBreak() + { + editMutex.EnterWriteLock (); + buffer.Insert (this [CurrentLine] + CurrentColumn, '\n'); + int lgdiff = lineLength [CurrentLine] - CurrentColumn; + lineLength.Insert (CurrentLine + 1, lineLength [CurrentLine] - CurrentColumn); + lineLength [CurrentLine] = CurrentColumn + 1; + editMutex.ExitWriteLock (); + LineAdditionEvent.Raise (this, null); + CurrentColumn = 0; + CurrentLine++; + } + public void Delete() + { + editMutex.EnterWriteLock (); + if (SelectionIsEmpty) { + if (CurrentColumn == 0) { + if (CurrentLine == 0) { + editMutex.ExitWriteLock (); + return; + } + + buffer.Remove (this [CurrentLine] - 1, 1); + + int col = lineLength [CurrentLine - 1] - 1; + lineLength [CurrentLine - 1] += lineLength [CurrentLine] - 1; + lineLength.RemoveAt (CurrentLine); + + CurrentLine--; + CurrentColumn = col; + + editMutex.ExitWriteLock (); + LineRemoveEvent.Raise (this, null); + return; + } + CurrentColumn--; + buffer.Remove (this [CurrentLine] + CurrentColumn, 1); + lineLength [CurrentLine]--; + } else { + int linesToRemove = SelectionEnd.Y - SelectionStart.Y; + int ptr = this [SelectionStart.Y] + SelectionStart.X; + int length = lineLength [SelectionStart.Y] - SelectionStart.X; + int l = 1; + while (l <= linesToRemove ) { + length += lineLength [SelectionStart.Y + l]; + l++; + } + length -= lineLength [SelectionEnd.Y] - SelectionEnd.X; + buffer.Remove (ptr, length); + lineLength [SelectionStart.Y] = SelectionStart.X + lineLength [SelectionEnd.Y] - SelectionEnd.X; + lineLength.RemoveRange (SelectionStart.Y + 1, linesToRemove); + + CurrentLine = SelectionStart.Y; + CurrentColumn = SelectionStart.X; + ResetSelection (); + + if (linesToRemove > 0) + LineRemoveEvent.Raise (this, null); + else + LineUpadateEvent.Raise (this, null); + } + editMutex.ExitWriteLock (); + } + public void Load(string rawSource) { + this.Clear(); + + if (string.IsNullOrEmpty (rawSource)) + return; + + lineBreak = reghexLineBrk.Match (rawSource).Value;//store original line break + string tmp = reghexLineBrk.Replace (rawSource, "\n");//use single char line break in buffer + int ptr = 0; + foreach (Match match in slb.Matches(tmp)) + { + int l = match.Index + 1; + int lg = l - ptr; + lineLength.Add (lg); + ptr = l; + } + lineLength.Add (0); + + buffer = new StringBuilder (tmp); + } + +// public int CurrentTabulatedColumn { +// get { +//// return lines [_currentLine].Content.Substring (0, _currentCol). +//// Replace ("\t", new String (' ', Interface.TabSize)).Length; +// } +// } + + #region moving cursor an selection + Point selStartPos = -1; //selection start (row,column) + Point selEndPos = -1; //selection end (row,column) + + public bool SelectionInProgress { get { return selStartPos >= 0; }} + public void SetSelStartPos () { + selStartPos = selEndPos = CurrentPosition; + SelectionChanged.Raise (this, null); + } + public void SetSelEndPos () { + selEndPos = CurrentPosition; + SelectionChanged.Raise (this, null); + } + /// <summary> + /// Set selection in buffer to -1, empty selection + /// </summary> + public void ResetSelection () { + selStartPos = selEndPos = -1; + SelectionChanged.Raise (this, null); + } + /// <summary> + /// ordered selection start and end positions in char units + /// </summary> + public Point SelectionStart { + get { return selEndPos < 0 || selStartPos.Y < selEndPos.Y ? selStartPos : + selStartPos.Y > selEndPos.Y ? selEndPos : + selStartPos.X < selEndPos.X ? selStartPos : selEndPos; } + } + public Point SelectionEnd { + get { return selEndPos < 0 || selStartPos.Y > selEndPos.Y ? selStartPos : + selStartPos.Y < selEndPos.Y ? selEndPos : + selStartPos.X > selEndPos.X ? selStartPos : selEndPos; } + } + public bool SelectionIsEmpty + { get { return selEndPos == selStartPos; } } + /// <summary> + /// Current column in buffer coordinate, tabulation = 1 char + /// </summary> + public int CurrentColumn{ + get { return _currentCol; } + set { + if (value == _currentCol) + return; + + editMutex.EnterWriteLock (); + + if (value < 0) + _currentCol = 0; + else if (value >= lineLength [_currentLine]) { + if (_currentLine < LineCount -1 && value > lineLength [_currentLine] - 1) + _currentCol = lineLength [_currentLine] - 1; + else + _currentCol = lineLength [_currentLine]; + }else + _currentCol = value; + + editMutex.ExitWriteLock (); + + PositionChanged.Raise (this, null); + } + } + /// <summary> + /// Current row in buffer coordinate, tabulation = 1 char + /// </summary> + public int CurrentLine{ + get { return _currentLine; } + set { + if (value == _currentLine) + return; + + editMutex.EnterWriteLock (); + + if (value >= lineLength.Count) + _currentLine = lineLength.Count-1; + else if (value < 0) + _currentLine = 0; + else + _currentLine = value; + + int c = _currentCol; + _currentCol = 0; + CurrentColumn = c; + + editMutex.ExitWriteLock(); + + PositionChanged.Raise (this, null); + } + } + /// <summary> + /// Current position in buffer coordinate, tabulation = 1 char + /// </summary> + public Point CurrentPosition { + get { return new Point(CurrentColumn, CurrentLine); } + } + /// <summary> + /// get char at current position in buffer + /// </summary> + protected Char CurrentChar { get { return buffer[this [CurrentLine]]; } } + public string SelectedText { + get { + if (SelectionIsEmpty) + return ""; + Point selStart = SelectionStart; + Point selEnd = SelectionEnd; + + int ptr = this [selStart.Y] + selStart.X; + int length = lineLength[selStart.Y] - selStart.X; + for (int i = selStart.Y+1; i <= selEnd.Y; i++) + length += lineLength [i]; + length -= lineLength[selEnd.Y] - selEnd.X; + + return buffer.ToString (ptr, length); + } + } + + public void GotoWordStart(){ + if (_currentCol == 0) + MoveLeft (); + if (_currentCol == 0) + return; + int ptrStart = BufferIndexOfCurrentPosition; + int ptr = ptrStart; + char c; + //skip white spaces + do { + ptr--; + c = this.GetCharAt (ptr); + } while (!char.IsLetterOrDigit (c) && c != '\n' && ptr > 1); + + do { + ptr--; + c = this.GetCharAt (ptr); + } while (char.IsLetterOrDigit (c) && c != '\n' && ptr > 1); + + if (ptr == 0) + CurrentColumn = 0; + else + CurrentColumn -= ptrStart - ptr - 1; + } + public void GotoWordEnd(){ + int limx = GetLineLength (_currentLine); + if (_currentLine < lineLength.Count - 1) + limx--; + + if (_currentCol == limx) { + MoveRight (); + limx = GetLineLength (_currentLine); + if (_currentLine < lineLength.Count - 1) + limx--; + } + + int ptrLine = GetBufferIndexOfLine(_currentLine); + int ptrCol = _currentCol; + + char c; + //skip white spaces + do { + c = GetCharAt (ptrLine+ptrCol); + ptrCol++; + } while (!char.IsLetterOrDigit (c) && ptrCol < limx); + + do { + c = GetCharAt (ptrLine + ptrCol); + ptrCol++; + } while (char.IsLetterOrDigit (c) && ptrCol < limx); + CurrentColumn = ptrCol - 1; + } + /// <summary> + /// Moves cursor one char to the left, move up if cursor reaches start of line + /// </summary> + public void MoveLeft(){ + if (CurrentColumn == 0) { + CurrentLine--; + CurrentColumn = int.MaxValue; + } else + CurrentColumn--; + } + /// <summary> + /// Moves cursor one char to the right, move down if cursor reaches end of line + /// </summary> + public void MoveRight(){ + if (_currentLine < LineCount -1){ + if (CurrentColumn >= lineLength [CurrentLine] - 1) { + CurrentColumn = 0; + CurrentLine++; + return; + } + } + CurrentColumn++; + } + + #endregion + } +} + diff --git a/Samples/CrowIDE/src/Editors/CodeBuffer/TextBufferEventArgs.cs b/Samples/CrowIDE/src/Editors/CodeBuffer/TextBufferEventArgs.cs new file mode 100644 index 00000000..3b67bb1c --- /dev/null +++ b/Samples/CrowIDE/src/Editors/CodeBuffer/TextBufferEventArgs.cs @@ -0,0 +1,20 @@ +using System; + +namespace Crow.Text +{ + public class TextBufferEventArgs : EventArgs { + public int LineStart; + public int LineCount; + + public TextBufferEventArgs(int lineNumber) { + LineStart = lineNumber; + LineCount = 1; + } + public TextBufferEventArgs(int lineStart, int lineCount) { + LineStart = lineStart; + LineCount = lineCount; + } + } + +} + diff --git a/Samples/CrowIDE/src/Editors/CodeBuffer/TextEditor.cs b/Samples/CrowIDE/src/Editors/CodeBuffer/TextEditor.cs new file mode 100644 index 00000000..8b16b9a8 --- /dev/null +++ b/Samples/CrowIDE/src/Editors/CodeBuffer/TextEditor.cs @@ -0,0 +1,699 @@ +// +// ScrollingTextBox.cs +// +// Author: +// Jean-Philippe Bruyère <jp.bruyere@hotmail.com> +// +// Copyright (c) 2013-2017 Jean-Philippe Bruyère +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System; +using System.ComponentModel; +using vkvg; + +namespace Crow.Text +{ + /// <summary> + /// Scrolling text box optimized for monospace fonts, for coding + /// </summary> + public class TextEditor : Crow.Coding.Editor + { + #region CTOR + public TextEditor (): base() + { + buffer = new TextBuffer (); + buffer.LineUpadateEvent += Buffer_LineUpadateEvent; + buffer.LineAdditionEvent += Buffer_LineAdditionEvent;; + buffer.LineRemoveEvent += Buffer_LineRemoveEvent; + buffer.BufferCleared += Buffer_BufferCleared; + buffer.SelectionChanged += Buffer_SelectionChanged; + buffer.PositionChanged += Buffer_PositionChanged; + //buffer.Add (""); + } + #endregion + + string oldSource = ""; + volatile bool isDirty = false; + + #region private and protected fields + int visibleLines = 1; + int visibleColumns = 1; + + TextBuffer buffer; + + Color selBackground; + Color selForeground; + int selStartCol; + int selEndCol; + + protected Rectangle rText; + protected FontExtents fe; + protected TextExtents te; + + Point mouseLocalPos; + bool doubleClicked = false; + #endregion + + /// <summary> + /// Updates visible line in widget, adapt max scroll y and updatePrintedLines + /// </summary> + void updateVisibleLines(){ + visibleLines = (int)Math.Floor ((double)ClientRectangle.Height / (fe.Ascent+fe.Descent)); + NotifyValueChanged ("VisibleLines", visibleLines); + updateMaxScrollY (); + RegisterForGraphicUpdate (); +// System.Diagnostics.Debug.WriteLine ("update visible lines: " + visibleLines); +// System.Diagnostics.Debug.WriteLine ("update MaxScrollY: " + MaxScrollY); + } + void updateVisibleColumns(){ + visibleColumns = (int)Math.Floor ((double)(ClientRectangle.Width)/ fe.MaxXAdvance); + NotifyValueChanged ("VisibleColumns", visibleColumns); + RegisterForGraphicUpdate (); +// System.Diagnostics.Debug.WriteLine ("update visible columns: {0} leftMargin:{1}",visibleColumns, leftMargin); +// System.Diagnostics.Debug.WriteLine ("update MaxScrollX: " + MaxScrollX); + } + void updateMaxScrollX (int longestTabulatedLineLength) { + MaxScrollX = Math.Max (0, longestTabulatedLineLength - visibleColumns); + if (longestTabulatedLineLength > 0) + NotifyValueChanged ("ChildWidthRatio", Slot.Width * visibleColumns / longestTabulatedLineLength); + } + void updateMaxScrollY () { + int lc = buffer.LineCount; + MaxScrollY = Math.Max (0, lc - visibleLines); + if (lc > 0) + NotifyValueChanged ("ChildHeightRatio", Slot.Height * visibleLines / lc); + + } + + + + #region Editor overrides + protected override void updateEditorFromProjFile () + { + buffer.editMutex.EnterWriteLock (); + loadSource (); + buffer.editMutex.ExitWriteLock (); + + isDirty = false; + oldSource = projFile.Source; + projFile.RegisteredEditors [this] = true; + } + protected override void updateProjFileFromEditor () + { + buffer.editMutex.EnterWriteLock (); + string newsrc = buffer.FullText; + buffer.editMutex.ExitWriteLock (); + projFile.UpdateSource (this, newsrc); + } + protected override bool EditorIsDirty { + get { return isDirty; } + set { isDirty = value; } + } + protected override bool IsReady { + get { return projFile != null && buffer != null; } + } + #endregion + + #region Buffer events handlers + void Buffer_BufferCleared (object sender, EventArgs e) + { + editorMutex.EnterWriteLock (); + + MaxScrollX = MaxScrollY = 0; + RegisterForGraphicUpdate (); + notifyPositionChanged (); + isDirty = true; + + editorMutex.ExitWriteLock (); + } + void Buffer_LineAdditionEvent (object sender, TextBufferEventArgs e) + { + updateMaxScrollY (); + RegisterForGraphicUpdate (); + isDirty = true; + } + void Buffer_LineRemoveEvent (object sender, TextBufferEventArgs e) + { + updateMaxScrollY (); + RegisterForGraphicUpdate (); + notifyPositionChanged (); + isDirty = true; + } + void Buffer_LineUpadateEvent (object sender, TextBufferEventArgs e) + { + RegisterForGraphicUpdate (); + notifyPositionChanged (); + isDirty = true; + } + void Buffer_PositionChanged (object sender, EventArgs e) + { + int cc = getTabulatedColumn (buffer.CurrentPosition); + + if (cc > visibleColumns + ScrollX) { + ScrollX = cc - visibleColumns; + } else if (cc < ScrollX) + ScrollX = cc; + + if (buffer.CurrentLine >= visibleLines + ScrollY - 1) + ScrollY = buffer.CurrentLine - visibleLines + 1; + else if (buffer.CurrentLine < ScrollY) + ScrollY = buffer.CurrentLine; + + RegisterForGraphicUpdate (); + notifyPositionChanged (); + } + + void Buffer_SelectionChanged (object sender, EventArgs e) + { + RegisterForGraphicUpdate (); + } + #endregion + + void notifyPositionChanged (){ + try { + NotifyValueChanged ("CurrentLine", buffer.CurrentLine+1); + NotifyValueChanged ("CurrentColumn", buffer.CurrentColumn+1); + } catch (Exception ex) { + Console.WriteLine (ex.ToString ()); + } + } + + #region Public Crow Properties + public int CurrentLine{ + get { return buffer == null ? 0 : buffer.CurrentLine+1; } + set { + try { + int l = value - 1; + if (l == buffer.CurrentLine) + return; + buffer.CurrentLine = l; + } catch (Exception ex) { + Console.WriteLine ("Error cur column: " + ex.ToString ()); + } + } + } + public int CurrentColumn{ + get { return buffer == null ? 0 : buffer.CurrentColumn+1; } + set { + try { + if (value - 1 == buffer.CurrentColumn) + return; + buffer.CurrentColumn = value - 1; + } catch (Exception ex) { + Console.WriteLine ("Error cur column: " + ex.ToString ()); + } + } + } + [DefaultValue("Blue")] + public virtual Color SelectionBackground { + get { return selBackground; } + set { + if (value == selBackground) + return; + selBackground = value; + NotifyValueChanged ("SelectionBackground", selBackground); + RegisterForRedraw (); + } + } + [DefaultValue("White")] + public virtual Color SelectionForeground { + get { return selForeground; } + set { + if (value == selForeground) + return; + selForeground = value; + NotifyValueChanged ("SelectionForeground", selForeground); + RegisterForRedraw (); + } + } + public override int ScrollY { + get { + return base.ScrollY; + } + set { + if (value == base.ScrollY) + return; + editorMutex.EnterWriteLock (); + base.ScrollY = value; + editorMutex.ExitWriteLock (); + RegisterForGraphicUpdate (); + } + } + #endregion + + + void loadSource () { + buffer.Load (projFile.Source); + projFile.RegisteredEditors [this] = true; + updateMaxScrollY (); + RegisterForGraphicUpdate (); + } + + int getTabulatedColumn (int col, int line) { + return buffer.GetSubString (buffer [line], + buffer.GetLineLength (line)).Substring(0,col).Replace ("\t", new String (' ', Interface.TabSize)).Length; + } + int getTabulatedColumn (Point pos) { + return getTabulatedColumn (pos.X,pos.Y); + } + + #region Drawing + void drawLines(Context gr, Rectangle cb) { + int longestTabulatedLine = 0; + for (int i = 0; i < visibleLines; i++) { + int lineIndex = i + ScrollY; + if (lineIndex >= buffer.LineCount)//TODO:need optimize + break; + + double y = cb.Y + (fe.Ascent+fe.Descent) * i, x = cb.X; + + int lineLength = buffer.GetLineLength (lineIndex); + if (lineIndex < buffer.LineCount - 1)//dont print line break + lineLength--; + string lstr = buffer.GetSubString (buffer [lineIndex], + lineLength).Replace ("\t", new String (' ', Interface.TabSize)); + + int lstrLength = lstr.Length; + if (lstrLength > longestTabulatedLine) + longestTabulatedLine = lstrLength; + + if (ScrollX < lstrLength) + lstr = lstr.Substring (ScrollX); + else + lstr = ""; + + gr.MoveTo (x, y + fe.Ascent); + gr.ShowText (lstr); + gr.Fill (); + + if (!buffer.SelectionIsEmpty && lineIndex >= buffer.SelectionStart.Y && lineIndex <= buffer.SelectionEnd.Y) { + double rLineX = x, + rLineY = y, + rLineW = lstr.Length * fe.MaxXAdvance; + + if (lineIndex == buffer.SelectionStart.Y) { + rLineX += (selStartCol - ScrollX) * fe.MaxXAdvance; + rLineW -= (selStartCol - ScrollX) * fe.MaxXAdvance; + } + if (lineIndex == buffer.SelectionEnd.Y) + rLineW -= (lstr.Length - selEndCol + ScrollX) * fe.MaxXAdvance; + + gr.Save (); + gr.Operator = Operator.Source; + gr.Rectangle (rLineX, rLineY, rLineW, (fe.Ascent+fe.Descent)); + gr.SetSourceColor (SelectionBackground); + gr.FillPreserve (); + gr.Clip (); + gr.Operator = Operator.Over; + gr.SetSourceColor (SelectionForeground); + gr.MoveTo (x, y + fe.Ascent); + gr.ShowText (lstr); + gr.Fill (); + gr.Restore (); + } + + + } + + updateMaxScrollX(longestTabulatedLine); + } + #endregion + + #region GraphicObject overrides + public override Font Font { + get { return base.Font; } + set { + base.Font = value; + + using (Surface img = new Surface (IFace.dev, 1, 1)) { + using (Context gr = new Context (img)) { + gr.FontFace = Font.Name; + gr.FontSize = (uint)Font.Size; + + fe = gr.FontExtents; + } + } + MaxScrollY = 0; + RegisterForGraphicUpdate (); + } + } + protected override int measureRawSize(LayoutingType lt) + { + if (lt == LayoutingType.Height) + return (int)Math.Ceiling((fe.Ascent+fe.Descent) * buffer.LineCount) + Margin * 2; + + return 0;// (int)(fe.MaxXAdvance * buffer.GetLineLength(buffer.longestLineIdx)) + Margin * 2; + } + public override void OnLayoutChanges (LayoutingType layoutType) + { + base.OnLayoutChanges (layoutType); + + if (layoutType == LayoutingType.Height) + updateVisibleLines (); + else if (layoutType == LayoutingType.Width) + updateVisibleColumns (); + } + + protected override void onDraw (Context gr) + { + base.onDraw (gr); + + gr.FontFace = Font.Name; + gr.FontSize = (uint)Font.Size; + //gr.FontOptions = Interface.FontRenderingOptions; + //gr.Antialias = Interface.Antialias; + + Rectangle cb = ClientRectangle; + + Foreground.SetAsSource (gr); + + buffer.editMutex.EnterReadLock (); + editorMutex.EnterReadLock (); + + #region draw text cursor + if (buffer.SelectionInProgress){ + selStartCol = getTabulatedColumn (buffer.SelectionStart); + selEndCol = getTabulatedColumn (buffer.SelectionEnd); + }else if (HasFocus && CurrentLine >= 0){ + gr.LineWidth = 1.0; + double cursorX = cb.X + (getTabulatedColumn(buffer.CurrentPosition) - ScrollX) * fe.MaxXAdvance ; + double cursorY = cb.Y + (buffer.CurrentLine - ScrollY) * (fe.Ascent+fe.Descent); + gr.MoveTo (0.5 + cursorX, cursorY); + gr.LineTo (0.5 + cursorX, cursorY + fe.Ascent+fe.Descent); + gr.Stroke(); + } + #endregion + + drawLines (gr, cb); + + editorMutex.ExitReadLock (); + + buffer.editMutex.ExitReadLock (); + + } + #endregion + + int getBufferColFromVisualCol (int line, int column) { + int i = 0; + int buffCol = 0; + int buffPtr = buffer [line]; + while (i < column && buffCol < buffer.GetLineLength(line)) { + if (buffer.GetCharAt(buffPtr + buffCol) == '\t') + i += Interface.TabSize; + else + i++; + buffCol++; + } + return buffCol; + } + + + #region Mouse handling + + int hoverLine = -1; + public int HoverLine { + get { return hoverLine; } + set { + if (hoverLine == value) + return; + hoverLine = value; + NotifyValueChanged ("HoverLine", hoverLine); + } + } + void updateHoverLine () { +// int hvl = (int)Math.Max (0, Math.Floor (mouseLocalPos.Y / (fe.Ascent+fe.Descent))); +// hvl = Math.Min (PrintedLines.Count, hvl); +// HoverLine = buffer.IndexOf (PrintedLines[hvl]); + } + void updateCurrentPosFromMouseLocalPos(){ + + buffer.CurrentLine = ScrollY + (int)Math.Max (0, Math.Floor (mouseLocalPos.Y / (fe.Ascent+fe.Descent))); + int curVisualCol = ScrollX + (int)Math.Round ((mouseLocalPos.X) / fe.MaxXAdvance); + buffer.CurrentColumn = getBufferColFromVisualCol (buffer.CurrentLine, curVisualCol); + } + + public override void onMouseEnter (object sender, MouseMoveEventArgs e) + { + base.onMouseEnter (sender, e); + IFace.MouseCursor = MouseCursor.IBeam; + } + public override void onMouseLeave (object sender, MouseMoveEventArgs e) + { + base.onMouseLeave (sender, e); + IFace.MouseCursor = MouseCursor.Arrow; + } + public override void onMouseMove (object sender, MouseMoveEventArgs e) + { + base.onMouseMove (sender, e); + + mouseLocalPos = e.Position - ScreenCoordinates(Slot).TopLeft - ClientRectangle.TopLeft; + + updateHoverLine (); + + if (e.Mouse.LeftButton == ButtonState.Released || !buffer.SelectionInProgress) + return; + + //mouse is down + updateCurrentPosFromMouseLocalPos(); + buffer.SetSelEndPos (); + } + public override void onMouseDown (object sender, MouseButtonEventArgs e) + { + if (!Focusable) + return; + + base.onMouseDown (sender, e); + + if (doubleClicked) { + doubleClicked = false; + return; + } + + updateCurrentPosFromMouseLocalPos (); + buffer.SetSelStartPos (); + } + public override void onMouseUp (object sender, MouseButtonEventArgs e) + { + base.onMouseUp (sender, e); + + if (buffer.SelectionIsEmpty) + buffer.ResetSelection (); + } + + public override void onMouseDoubleClick (object sender, MouseButtonEventArgs e) + { + //doubleClicked = true; + base.onMouseDoubleClick (sender, e); + + buffer.GotoWordStart (); + buffer.SetSelStartPos (); + buffer.GotoWordEnd (); + buffer.SetSelEndPos (); + } + + public override void onMouseWheel (object sender, MouseWheelEventArgs e) + { + base.onMouseWheel (sender, e); + } + #endregion + + #region Keyboard handling + public override void onKeyDown (object sender, KeyEventArgs e) + { + //base.onKeyDown (sender, e); + + Key key = e.Key; + + if (IFace.Ctrl) { + switch (key) { + case Key.S: + projFile.Save (); + break; + case Key.W: + editorMutex.EnterWriteLock (); + if (IFace.Shift) + projFile.Redo (null); + else + projFile.Undo (null); + editorMutex.ExitWriteLock (); + break; + default: + Console.WriteLine (""); + break; + } + } + + switch (key) + { + case Key.Backspace: + buffer.Delete (); + break; + case Key.Delete: + if (buffer.SelectionIsEmpty) + buffer.MoveRight (); +// else if (e.Shift) +// IFace.Clipboard = buffer.SelectedText; + buffer.Delete (); + break; + case Key.Enter: + case Key.KeypadEnter: + if (!buffer.SelectionIsEmpty) + buffer.Delete (); + buffer.InsertLineBreak (); + break; + case Key.Escape: + buffer.ResetSelection (); + break; + case Key.Home: + if (IFace.Shift) { + if (buffer.SelectionIsEmpty) + buffer.SetSelStartPos (); + if (IFace.Ctrl) + buffer.CurrentLine = 0; + buffer.CurrentColumn = 0; + buffer.SetSelEndPos (); + break; + } + buffer.ResetSelection (); + if (IFace.Ctrl) + buffer.CurrentLine = 0; + buffer.CurrentColumn = 0; + break; + case Key.End: + if (IFace.Shift) { + if (buffer.SelectionIsEmpty) + buffer.SetSelStartPos (); + if (IFace.Ctrl) + buffer.CurrentLine = int.MaxValue; + buffer.CurrentColumn = int.MaxValue; + buffer.SetSelEndPos (); + break; + } + buffer.ResetSelection (); + if (IFace.Ctrl) + buffer.CurrentLine = int.MaxValue; + buffer.CurrentColumn = int.MaxValue; + break; + case Key.Insert: + if (IFace.Shift) + buffer.InsertAt (IFace.Clipboard); + else if (IFace.Ctrl && !buffer.SelectionIsEmpty) + IFace.Clipboard = buffer.SelectedText; + break; + case Key.Left: + if (IFace.Shift) { + if (buffer.SelectionIsEmpty) + buffer.SetSelStartPos (); + if (IFace.Ctrl) + buffer.GotoWordStart (); + else + buffer.MoveLeft (); + buffer.SetSelEndPos (); + break; + } + buffer.ResetSelection (); + if (IFace.Ctrl) + buffer.GotoWordStart (); + else + buffer.MoveLeft(); + break; + case Key.Right: + if (IFace.Shift) { + if (buffer.SelectionIsEmpty) + buffer.SetSelStartPos (); + if (IFace.Ctrl) + buffer.GotoWordEnd (); + else + buffer.MoveRight (); + buffer.SetSelEndPos (); + break; + } + buffer.ResetSelection (); + if (IFace.Ctrl) + buffer.GotoWordEnd (); + else + buffer.MoveRight (); + break; + case Key.Up: + if (IFace.Shift) { + if (buffer.SelectionIsEmpty) + buffer.SetSelStartPos (); + CurrentLine--; + buffer.SetSelEndPos (); + break; + } + buffer.ResetSelection (); + CurrentLine--; + break; + case Key.Down: + if (IFace.Shift) { + if (buffer.SelectionIsEmpty) + buffer.SetSelStartPos (); + CurrentLine++; + buffer.SetSelEndPos (); + break; + } + buffer.ResetSelection (); + CurrentLine++; + break; + case Key.Menu: + break; + case Key.NumLock: + break; + case Key.PageDown: + if (IFace.Shift) { + if (buffer.SelectionIsEmpty) + buffer.SetSelStartPos (); + CurrentLine += visibleLines; + buffer.SetSelEndPos (); + break; + } + buffer.ResetSelection (); + CurrentLine += visibleLines; + break; + case Key.PageUp: + if (IFace.Shift) { + if (buffer.SelectionIsEmpty) + buffer.SetSelStartPos (); + CurrentLine -= visibleLines; + buffer.SetSelEndPos (); + break; + } + buffer.ResetSelection (); + CurrentLine -= visibleLines; + break; + case Key.Tab: + buffer.InsertAt ("\t"); + break; + default: + break; + } + RegisterForGraphicUpdate(); + } + public override void onKeyPress (object sender, KeyPressEventArgs e) + { + base.onKeyPress (sender, e); + + buffer.InsertAt (e.KeyChar.ToString()); + buffer.ResetSelection (); + } + #endregion + } +}
\ No newline at end of file diff --git a/Samples/CrowIDE/src/Editors/CodeBuffer/TextFormatting.cs b/Samples/CrowIDE/src/Editors/CodeBuffer/TextFormatting.cs new file mode 100644 index 00000000..f7b2e518 --- /dev/null +++ b/Samples/CrowIDE/src/Editors/CodeBuffer/TextFormatting.cs @@ -0,0 +1,19 @@ +using System; + +namespace Crow.Coding +{ + public struct TextFormatting { + public Color Foreground; + public Color Background; + public bool Bold; + public bool Italic; + + public TextFormatting(Color fg, Color bg, bool bold = false, bool italic = false){ + Foreground = fg; + Background = bg; + Bold = bold; + Italic = italic; + } + } +} + diff --git a/Samples/CrowIDE/src/Editors/CodeBuffer/Token.cs b/Samples/CrowIDE/src/Editors/CodeBuffer/Token.cs new file mode 100644 index 00000000..8b4df5e5 --- /dev/null +++ b/Samples/CrowIDE/src/Editors/CodeBuffer/Token.cs @@ -0,0 +1,71 @@ +// +// Token.cs +// +// Author: +// Jean-Philippe Bruyère <jp.bruyere@hotmail.com> +// +// Copyright (c) 2017 jp +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +using System; + +namespace Crow.Coding +{ + public struct Token + { + public BufferParser.TokenType Type; + public string Content; + public Point Start; + public Point End; + + public string PrintableContent { + get { return string.IsNullOrEmpty(Content) ? "" : Content.Replace("\t", new String(' ', Interface.TabSize)); } + } + +// public Token (TokenType tokType, string content = ""){ +// Type = tokType; +// Content = content; +// } + + public bool IsNull { get { return IsEmpty && Type == BufferParser.TokenType.Unknown; }} + public bool IsEmpty { get { return string.IsNullOrEmpty(Content); }} + + public static bool operator == (Token t, System.Enum tt){ + return Convert.ToInt32(t.Type) == Convert.ToInt32(tt); + } + public static bool operator != (Token t, System.Enum tt){ + return Convert.ToInt32(t.Type) != Convert.ToInt32(tt); + } + public static bool operator == (System.Enum tt, Token t){ + return Convert.ToInt32(t.Type) == Convert.ToInt32(tt); + } + public static bool operator != (System.Enum tt, Token t){ + return Convert.ToInt32(t.Type) != Convert.ToInt32(tt); + } + + public static Token operator +(Token t, char c){ + t.Content += c; + return t; + } + public static Token operator +(Token t, string s){ + t.Content += s; + return t; + } + public override string ToString () + { + return string.Format ("[Tok{2}->{3}:{0}: {1}]", Type,Content,Start,End); + } + } +} + diff --git a/Samples/CrowIDE/src/Editors/EditPane.cs b/Samples/CrowIDE/src/Editors/EditPane.cs new file mode 100644 index 00000000..a75522f0 --- /dev/null +++ b/Samples/CrowIDE/src/Editors/EditPane.cs @@ -0,0 +1,57 @@ +// +// EditPane.cs +// +// Author: +// Jean-Philippe Bruyère <jp_bruyere@hotmail.com> +// +// Copyright (c) 2013-2017 Jean-Philippe Bruyère +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System; +using Crow; +using System.Linq; + +namespace Crow.Coding +{ + public class EditPane : TemplatedGroup + { + public EditPane () {} + + object selectedItemElement = null; + + public override int SelectedIndex { + get { + return base.SelectedIndex; + } + set { + base.SelectedIndex = value; + } + } + public object SelectedItemElement { + get { return selectedItemElement; } + set { + if (selectedItemElement == value) + return; + selectedItemElement = value; + NotifyValueChanged ("SelectedItemElement", selectedItemElement); + } + } + } +} + diff --git a/Samples/CrowIDE/src/Editors/Editor.cs b/Samples/CrowIDE/src/Editors/Editor.cs new file mode 100644 index 00000000..102debb5 --- /dev/null +++ b/Samples/CrowIDE/src/Editors/Editor.cs @@ -0,0 +1,105 @@ +// +// Editor.cs +// +// Author: +// Jean-Philippe Bruyère <jp_bruyere@hotmail.com> +// +// Copyright (c) 2013-2017 Jean-Philippe Bruyère +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System; +using System.Threading; +using System.Xml.Serialization; + +namespace Crow.Coding +{ + public abstract class Editor : ScrollingObject + { + #region CTOR + protected Editor ():base(){ + Thread t = new Thread (backgroundThreadFunc); + t.IsBackground = true; + t.Start (); + } + #endregion + + protected ReaderWriterLockSlim editorMutex = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); + protected ProjectFile projFile = null; + Exception error = null; + + public virtual ProjectFile ProjectNode + { + get { return projFile; } + set + { + if (projFile == value) + return; + + if (projFile != null) + projFile.UnregisterEditor (this); + + projFile = value; + + if (projFile != null) + projFile.RegisterEditor (this); + + NotifyValueChanged ("ProjectNode", projFile); + } + } + [XmlIgnore]public Exception Error { + get { return error; } + set { + if (error == value) + return; + error = value; + NotifyValueChanged ("Error", error); + NotifyValueChanged ("HasError", HasError); + } + } + [XmlIgnore]public bool HasError { + get { return error != null; } + } + + protected abstract void updateEditorFromProjFile (); + protected abstract void updateProjFileFromEditor (); + protected abstract bool EditorIsDirty { get; set; } + protected virtual bool IsReady { get { return true; }} + protected virtual void updateCheckPostProcess () {} + + protected void backgroundThreadFunc () { + while (true) { + if (IsReady) { + if (Monitor.TryEnter (IFace.UpdateMutex)) { + if (!projFile.RegisteredEditors [this]) { + projFile.RegisteredEditors [this] = true; + updateEditorFromProjFile (); + } else if (EditorIsDirty) { + EditorIsDirty = false; + updateProjFileFromEditor (); + } + updateCheckPostProcess (); + Monitor.Exit (IFace.UpdateMutex); + } + } + Thread.Sleep (100); + } + } + } +} + diff --git a/Samples/CrowIDE/src/Editors/ImlSchematicEditor.cs b/Samples/CrowIDE/src/Editors/ImlSchematicEditor.cs new file mode 100644 index 00000000..64541c90 --- /dev/null +++ b/Samples/CrowIDE/src/Editors/ImlSchematicEditor.cs @@ -0,0 +1,132 @@ +// +// ImlVisualEditor.cs +// +// Author: +// Jean-Philippe Bruyère <jp.bruyere@hotmail.com> +// +// Copyright (c) 2016 jp +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +using System; +using Crow; +using System.Threading; +using System.Xml.Serialization; +using System.ComponentModel; +using System.IO; +using System.Collections.Generic; +using Crow.IML; +using System.Text; +using System.Xml; + +namespace Crow.Coding +{ + public class ImlSchematicEditor : TemplatedGroup + { + #region CTOR + public ImlSchematicEditor () + { + } + #endregion + + ProjectFile projNode; + Widget selectedItem; + ImlProjectItem imlProjFile; + Exception imlError = null; + + bool drawGrid, snapToGrid; + int gridSpacing; + + [DefaultValue(true)] + public bool DrawGrid { + get { return drawGrid; } + set { + if (drawGrid == value) + return; + drawGrid = value; + NotifyValueChanged ("DrawGrid", drawGrid); + RegisterForRedraw (); + } + } + [DefaultValue(true)] + public bool SnapToGrid { + get { return snapToGrid; } + set { + if (snapToGrid == value) + return; + snapToGrid = value; + NotifyValueChanged ("SnapToGrid", snapToGrid); + } + } + [DefaultValue(10)] + public int GridSpacing { + get { return gridSpacing; } + set { + if (gridSpacing == value) + return; + gridSpacing = value; + NotifyValueChanged ("GridSpacing", gridSpacing); + RegisterForRedraw (); + } + } + public Widget SelectedItem { + get { return selectedItem; } + set { + if (selectedItem == value) + return; + selectedItem = value; + NotifyValueChanged ("SelectedItem", selectedItem); + RegisterForRedraw (); + } + } +// public override ProjectFile ProjectNode { +// get { +// return projNode; +// } +// set { +// if (projNode == value) +// return; +// projNode = value; +// NotifyValueChanged ("ProjectNode", projNode); +// +// if (projNode is ImlProjectItem) +// imlProjFile = projNode as ImlProjectItem; +// else +// imlProjFile = null; +// } +// } + + +// protected override bool EditorIsDirty { +// get { +// throw new NotImplementedException (); +// } +// set { +// throw new NotImplementedException (); +// } +// } +// protected override void updateProjFileFromEditor () +// { +// +// } +// protected override void updateEditorFromProjFile () { +// +// } +// protected override void updateCheckPostProcess () +// { +// +// } + + + } +} diff --git a/Samples/CrowIDE/src/Editors/ImlVisualEditor.cs b/Samples/CrowIDE/src/Editors/ImlVisualEditor.cs new file mode 100644 index 00000000..75d8206b --- /dev/null +++ b/Samples/CrowIDE/src/Editors/ImlVisualEditor.cs @@ -0,0 +1,908 @@ +// +// ImlVisualEditor.cs +// +// Author: +// Jean-Philippe Bruyère <jp.bruyere@hotmail.com> +// +// Copyright (c) 2016 jp +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +using System; +using Crow; +using System.Threading; +using System.Xml.Serialization; +using System.ComponentModel; +using System.IO; +using System.Collections.Generic; +using Crow.IML; +using System.Text; +using System.Xml; +using System.Diagnostics; +using vkvg; + +namespace Crow.Coding +{ + public class ImlVisualEditor : Editor + { + #region CTOR + public ImlVisualEditor () : base() + { + //imlVE = new DesignInterface (Interface.de); + initCommands (); + } + #endregion + + protected override void onInitialized (object sender, EventArgs e) + { + initIcons (); + base.onInitialized (sender, e); + } + DesignInterface imlVE; + Widget selectedItem; + ImlProjectItem imlProjFile; + + bool editorIsDirty = false;//needed when tree is empty + bool drawGrid, snapToGrid; + int gridSpacing, zoom = 100; + Measure designWidth, designHeight; + bool updateEnabled; + + Picture icoMove, icoStyle; + Rectangle rIcons = default(Rectangle); + Size iconSize = new Size(11,11); + + public List<Crow.Command> Commands; + Crow.Command cmdDelete; + + void initCommands () { + cmdDelete = new Crow.Command (new Action (() => deleteObject (SelectedItem))) + { Caption = "Delete", Icon = new SvgPicture ("#CrowIDE.icons.save.svg"), CanExecute = true }; + Commands = new List<Crow.Command> (new Crow.Command[] { cmdDelete }); + } + + void initIcons () { + icoMove = new SvgPicture ("#CrowIDE.icons.move-arrows.svg"); + + // icoStyle = new SvgPicture (); + // icoStyle.Load (IFace, "#CrowIDE.icons.palette.svg"); + } + + [DefaultValue(true)] + public bool DrawGrid { + get { return drawGrid; } + set { + if (drawGrid == value) + return; + drawGrid = value; + NotifyValueChanged ("DrawGrid", drawGrid); + RegisterForRedraw (); + } + } + [DefaultValue(true)] + public bool SnapToGrid { + get { return snapToGrid; } + set { + if (snapToGrid == value) + return; + snapToGrid = value; + NotifyValueChanged ("SnapToGrid", snapToGrid); + } + } + [DefaultValue(10)] + public int GridSpacing { + get { return gridSpacing; } + set { + if (gridSpacing == value) + return; + gridSpacing = value; + NotifyValueChanged ("GridSpacing", gridSpacing); + RegisterForRedraw (); + } + } + [DefaultValue(100)] + public int Zoom { + get { return zoom; } + set { + if (zoom == value) + return; + + zoom = value; + NotifyValueChanged ("Zoom", zoom); + Width = (int)(designWidth * zoom / 100.0); + Height = (int)(designHeight * zoom / 100.0); + } + } + [DefaultValue("512")] + public Measure DesignWidth { + get { return designWidth; } + set { + if (designWidth == value) + return; + designWidth = value; + NotifyValueChanged ("DesignWidth", designWidth); + Width = (int)(designWidth * zoom / 100.0); + } + } + [DefaultValue("400")] + public Measure DesignHeight { + get { return designHeight; } + set { + if (designHeight == value) + return; + designHeight = value; + NotifyValueChanged ("DesignHeight", designHeight); + Height = (int)(designHeight * zoom / 100.0); + } + } + + public Widget SelectedItem { + get { return selectedItem; } + set { + if (selectedItem == value) + return; + selectedItem = value; + + if (selectedItem == null) + cmdDelete.CanExecute = false; + else + cmdDelete.CanExecute = true; + + NotifyValueChanged ("SelectedItem", selectedItem); + RegisterForGraphicUpdate (); + } + } + /// <summary>PoinprojFilever the widget</summary> + public virtual Widget HoverWidget + { + get { return imlVE.HoverWidget; } + set { + if (HoverWidget == value) + return; + + imlVE.HoverWidget = value; + + NotifyValueChanged ("HoverWidget", HoverWidget); + } + } + /// <summary> + /// use to disable update if tab is not the visible one + /// </summary> + public bool UpdateEnabled { + get { return updateEnabled; } + set { + if (value == updateEnabled) + return; + updateEnabled = value; + NotifyValueChanged ("UpdateEnabled", updateEnabled); + } + } + + public List<Widget> GraphicTree { + get { return imlVE.GraphicTree; } + } + + [XmlIgnore]public List<LQIList> LQIs { + get { return imlVE.LQIs; } + } + + #region editor overrides + public override ProjectFile ProjectNode { + get { + return base.ProjectNode; + } + set { + base.ProjectNode = value; + imlProjFile = projFile as ImlProjectItem; + imlVE.ProjFile = imlProjFile; + } + } + + protected override bool EditorIsDirty { + get { return imlProjFile == null ? false : + imlProjFile.Instance == null ? editorIsDirty : + imlProjFile.Instance.design_HasChanged | editorIsDirty; } + set { + editorIsDirty = value; + if (GraphicTree.Count == 0) + return; + if (GraphicTree [0] != null) + GraphicTree [0].design_HasChanged = value; + } + } + protected override bool IsReady { + get { return updateEnabled && imlVE != null && imlProjFile != null; } + } + + protected override void updateProjFileFromEditor () + { + Debug.WriteLine("\t\tImlEditor updateProjFileFromEditor"); + try { + if (imlProjFile.Instance == null) + projFile.UpdateSource(this, @"<?xml version=""1.0""?>"); + else + projFile.UpdateSource(this, imlProjFile.Instance.GetIML()); + } catch (Exception ex) { + Error = ex.InnerException; + if (Monitor.IsEntered(imlVE.UpdateMutex)) + Monitor.Exit (imlVE.UpdateMutex); + } + } + protected override void updateEditorFromProjFile () { + Debug.WriteLine("\t\tImlEditor updateEditorFromProjFile"); + try { + string selItemDesignID = null; + if (SelectedItem!=null) + selItemDesignID = SelectedItem.design_id; + imlVE.ClearInterface(); + Instantiator.NextInstantiatorID = 0; + imlVE.Styling = projFile.Project.solution.Styling; + imlVE.DefaultValuesLoader.Clear(); + //imlVE.DefaultTemplates = projFile.Project.solution.DefaultTemplates; + imlVE.Instantiators = new Dictionary<string, Instantiator>(); + + //prevent error on empty file + bool emptyFile = true; + string src = projFile.Source; + using (Stream s = new MemoryStream (Encoding.UTF8.GetBytes (src))) { + using (XmlReader itr = XmlReader.Create (s)) { + while(itr.Read()){ + if (itr.NodeType == XmlNodeType.Element){ + emptyFile = false; + break; + } + } + } + } + Widget go = null; + Error = null; + + if (emptyFile){ + imlProjFile.Instance = null; + }else{ + imlVE.LoadIMLFragment(src); + imlProjFile.Instance = imlVE.GraphicTree[0]; + if (selItemDesignID!=null) + imlProjFile.Instance.FindByDesignID(selItemDesignID,out go); + + } + SelectedItem = go; + } catch (Exception ex) { + Error = ex; + } + } + protected override void updateCheckPostProcess () + { + if (Error != null) { + RegisterForRedraw (); + return; + } + imlVE.Update (); + bool isDirty = false; + + lock (imlVE.RenderMutex) + isDirty = imlVE.IsDirty; + + if (isDirty) { + lock (IFace.UpdateMutex) + RegisterForRedraw (); + } + } + #endregion + + #region GraphicObject overrides + public override void OnLayoutChanges (LayoutingType layoutType) + { + base.OnLayoutChanges (layoutType); + switch (layoutType) { + case LayoutingType.Width: + DesignWidth = Slot.Width * 100 / zoom; + imlVE.ProcessResize (new Size(designWidth,designHeight)); + break; + case LayoutingType.Height: + DesignHeight = Slot.Height * 100 / zoom; + imlVE.ProcessResize (new Size(designWidth,designHeight)); + break; + } + } + + public override void onMouseMove (object sender, MouseMoveEventArgs e) + { + base.onMouseMove (sender, e); + + Widget oldHW = HoverWidget; + Rectangle scr = this.ScreenCoordinates (this.getSlot ()); + ProcessMouseMove (e.X - scr.X, e.Y - scr.Y); + + Widget newHW = HoverWidget; + if (newHW == null) + return; + + if (draggedObj != null) { + if (draggedObj.Parent == null) { + if (tryAddObjectTo (newHW, draggedObj)) { + RegisterForRedraw (); + return; + } + } else if (newHW != draggedObj) { + //lock (imlVE.UpdateMutex) { + ILayoutable possibleParent = getPossibleParent (newHW, draggedObj); + if (possibleParent == null) { + Group g = newHW.Parent as Group; + if (g != null && g != draggedObj) { + removeObject (draggedObj); + g.InsertChild (g.Children.IndexOf (newHW), draggedObj); + RegisterForRedraw (); + return; + } + } else if (possibleParent != draggedObj.Parent) { + removeObject (draggedObj); + if (tryAddObjectTo (possibleParent, draggedObj)) { + RegisterForRedraw (); + return; + } + } + //} + } + } + + if (oldHW == newHW) + return; + //RegisterForRedraw (); + + } + public override void onMouseEnter (object sender, MouseMoveEventArgs e) + { + base.onMouseEnter (sender, e); + if (IFace.DragAndDropOperation != null && draggedObj == null) + return; + IFace.FocusedWidget = this; + } + public override void onMouseLeave (object sender, MouseMoveEventArgs e) + { + base.onMouseLeave (sender, e); +// IFace.FocusedWidget = null; + } + public override void onMouseDown (object sender, MouseButtonEventArgs e) + { + if (e.Mouse.RightButton == ButtonState.Pressed) { + base.onMouseDown (sender, e); + return; + } + SelectedItem = HoverWidget; + +// if (SelectedItem != null && projFile != null) { +// projFile.CurrentLine = SelectedItem.design_line; +// projFile.CurrentColumn = SelectedItem.design_column; +// } + + } + + protected override void onDraw (Context gr) + { + base.onDraw (gr); + + Rectangle cb = new Rectangle (0, 0, designWidth, designHeight); + + gr.Save (); + + double z = zoom / 100.0; + + gr.Scale (z, z); + + if (drawGrid) { + double gridLineWidth = 0.2 / z; + double glhw = gridLineWidth / 2.0; + int nbLines = cb.Width / gridSpacing; + double d = cb.Left + gridSpacing; + for (int i = 0; i < nbLines; i++) { + gr.MoveTo (d - glhw, cb.Y); + gr.LineTo (d - glhw, cb.Bottom); + d += gridSpacing; + } + nbLines = cb.Height / gridSpacing; + d = cb.Top + gridSpacing; + for (int i = 0; i < nbLines; i++) { + gr.MoveTo (cb.X, d - glhw); + gr.LineTo (cb.Right, d - glhw); + d += gridSpacing; + } + gr.LineWidth = gridLineWidth; + Foreground.SetAsSource (gr, cb); + gr.Stroke (); + } + + lock (imlVE.RenderMutex) { + gr.SetSourceSurface (imlVE.surf, cb.Left, cb.Top); + gr.Paint (); + imlVE.IsDirty = false; + } + /*if (Error == null) { + gr.SetSourceColor (Color.Black); + gr.Rectangle (cb, 1.0 / z); + } else { + gr.SetSourceColor (Color.LavenderBlush); + gr.Rectangle (cb, 2.0 / z); + string[] lerrs = Error.ToString ().Split ('\n'); + Point p = cb.Center; + p.Y -= lerrs.Length * 20; + foreach (string le in lerrs) { + drawCenteredTextLine(gr, p, le); + p.Y += 20; + + } + } + gr.Stroke ();*/ + + Rectangle hr; + + /* + if (SelectedItem?.Parent != null) { + + gr.SelectFontFace (Font.Name, Font.Slant, Font.Wheight); + gr.SetFontSize (Font.Size); + gr.FontOptions = Interface.FontRenderingOptions; + gr.Antialias = Interface.Antialias; + + Widget g = SelectedItem; + hr = g.ScreenCoordinates (g.getSlot ()); + +// Rectangle rIcons = new Rectangle (iconSize); +// rIcons.Width *= 4; +// rIcons.Top = hr.Bottom; +// rIcons.Left = hr.Right - rIcons.Width + iconSize.Width; + Rectangle rIcoMove = new Rectangle (hr.BottomRight, iconSize); +// Rectangle rIcoStyle = rIcoMove; +// rIcoStyle.Left += iconSize.Width + 4; + + using (Surface mask = new ImageSurface (Format.Argb32, cb.Width, cb.Height)) { + using (Context ctx = new Context (mask)) { + ctx.Save(); + ctx.SetSourceRGBA(1.0,1.0,1.0,0.4); + ctx.Paint (); + ctx.Rectangle (hr); + ctx.Operator = Operator.Clear; + ctx.Fill (); + } + + gr.SetSourceSurface (mask, 0, 0); + gr.Paint (); + + using (Surface ol = new ImageSurface (Format.Argb32, cb.Width, cb.Height)) { + using (Context ctx = new Context (ol)) { + ctx.SetSourceColor (Color.Black); + drawDesignOverlay (ctx, g, cb, hr, 0.4 / z, 6.5); + } + + gr.SetSourceSurface (ol, 0, 0); + gr.Paint (); + } + + drawIcon (gr, icoMove, rIcoMove); + //drawIcon (gr, icoStyle, rIcoStyle); + + } + }*/ + if (HoverWidget != null) { + hr = HoverWidget.ScreenCoordinates (HoverWidget.getSlot ()); + gr.SetSourceColor (Color.SkyBlue); + //gr.SetDash (new double[]{ 5.0, 3.0 }, 0.0); + gr.Rectangle (hr, 0.4 / z); + } + gr.Restore (); + } + + void drawIcon (Context gr, Picture pic, Rectangle r) {/* +// gr.SetSourceColor (Color.Black); +// CairoHelpers.CairoRectangle (gr, r.Inflated (1), 2, 1.0); + gr.SetSourceColor (Color.White); + CairoHelpers.CairoRectangle (gr, r.Inflated (1), 2); + gr.Fill (); + gr.Operator = Operator.Clear; + pic.Paint (gr, r); + gr.Operator = Operator.Over;*/ + } + void drawDesignOverlay (Context gr, Widget g, Rectangle cb, Rectangle hr, double coteStroke, double space = 6.5){ + /* + double z = zoom / 100.0; + double coteW = 3, coteL = 5; + bool fill = true; + Crow.Cairo.PointD p1 = new Crow.Cairo.PointD (hr.X + 0.5, hr.Y - space); + Crow.Cairo.PointD p2 = new Crow.Cairo.PointD (hr.Right - 0.5, hr.Y - space); + + if (p1.Y < cb.Top) { + if (hr.Bottom > cb.Bottom - space) + p1.Y = p2.Y = hr.Bottom - space; + else + p1.Y = p2.Y = hr.Bottom + space; + } + + if (g.Width.IsFit) + gr.DrawCoteInverse (p1, p2, coteStroke, fill, coteW, coteL); + else if (g.Width.IsRelativeToParent) { + gr.DrawCote (p1, p2, coteStroke, fill, coteW, coteL); + if (g.Width.Value < 100) + drawCenteredTextLine (gr, p1.Add(p2.Substract(p1).Divide (2)), g.Width.ToString()); + }else if (g.Width.IsFixed) { + gr.DrawCoteFixed (p1, p2, coteStroke * 2.0, coteW); + drawCenteredTextLine (gr, p1.Add(p2.Substract(p1).Divide (2)), g.Width.Value.ToString()); + } + + p1 = new Crow.Cairo.PointD (hr.X - space, hr.Top + 0.5); + p2 = new Crow.Cairo.PointD (hr.X - space, hr.Bottom - 0.5); + + if (p1.X < cb.Left) { + if (hr.Right > cb.Right - space) + p1.X = p2.X = hr.Right - space; + else + p1.X = p2.X = hr.Right + space; + } + if (g.Height.IsFit) + gr.DrawCoteInverse (p1, p2, coteStroke, fill, coteW, coteL); + else if (g.Height.IsRelativeToParent){ + gr.DrawCote (p1, p2, coteStroke, fill, coteW, coteL); + if (g.Height.Value < 100) + drawCenteredTextLine (gr, p1.Add(p2.Substract(p1).Divide (2)), g.Height.ToString()); + }else if (g.Width.IsFixed) { + gr.DrawCoteFixed (p1, p2, coteStroke * 2.0, coteW); + drawCenteredTextLine (gr, p1.Add(p2.Substract(p1).Divide (2)), g.Height.Value.ToString()); + } + + // hr.Inflate (2); + //gr.SetDash (new double[]{ 1.0, 4.0 }, 0.0); + //gr.SetSourceColor (Color.Grey); +// gr.Rectangle (hr,coteStroke); +// gr.Stroke (); + gr.Operator = Operator.Over; + */ + } + + void drawCenteredTextLine (Context gr, Point center, string txt){ + drawCenteredTextLine (gr, new PointD(center.X,center.Y), txt); + } + void drawCenteredTextLine (Context gr, PointD center, string txt){ + if (string.IsNullOrEmpty(txt)) + return; + FontExtents fe = gr.FontExtents; + TextExtents te = gr.TextExtents (txt); + + Rectangle rText = new Rectangle( + (int)(center.X - te.Width / 2), (int)(center.Y - (fe.Ascent + fe.Descent) / 2), + (int)te.Width, (int)(fe.Ascent + fe.Descent)); + + gr.Operator = Operator.Clear; + Rectangle r = rText; + r.Inflate (2); + gr.Rectangle (r); + gr.Fill (); + gr.Operator = Operator.Over; + + gr.MoveTo (rText.X, rText.Y + fe.Ascent); + gr.ShowText (txt); + gr.Fill (); + + } + protected override void onDragEnter (object sender, DragDropEventArgs e) + { + base.onDragEnter (sender, e); + GraphicObjectDesignContainer godc = e.DragSource.DataSource as GraphicObjectDesignContainer; + if (godc == null) + return; + createDraggedObj (godc.CrowType); + } + protected override void onDragLeave (object sender, DragDropEventArgs e) + { + base.onDragLeave (sender, e); + GraphicObjectDesignContainer godc = e.DragSource.DataSource as GraphicObjectDesignContainer; + if (godc == null) + return; + ClearDraggedObj (); + } + + protected override void onStartDrag (object sender, DragDropEventArgs e) + { + base.onStartDrag (sender, e); + if (SelectedItem == null) + return; + + Widget dumy = new Widget (IFace); + dumy.EndDrag += dumyOnEndDrag; + dumy.Drop += dumyOnDrop; + dumy.IsDragged = true; + IFace.ActiveWidget = dumy; + e.DragSource.IsDragged = false; + IFace.DragAndDropOperation.DragSource = dumy; + draggedObj = SelectedItem; + int dragIconSize = 48; + lock (IFace.UpdateMutex) { + IFace.DragImageHeight = dragIconSize; + IFace.DragImageWidth = dragIconSize; + IFace.DragImage = draggedObj.CreateIcon(dragIconSize); + } + removeObject (draggedObj); + SelectedItem = null; + HoverWidget = null; + } + void dumyOnEndDrag (object sender, DragDropEventArgs e) + { + IFace.ClearDragImage (); + } + void dumyOnDrop (object sender, DragDropEventArgs e) + { + ClearDraggedObj (false); + IFace.ClearDragImage (); + } + #endregion + + + #region draggedObj handling + + public Widget draggedObj = null; + + void createDraggedObj (Type crowType) { + lock (imlVE.UpdateMutex) { + draggedObj = imlVE.CreateITorFromIMLFragment ("<" + crowType.Name + "/>").CreateInstance (); + } + } + + public void ClearDraggedObj (bool removeFromTree = true) { + if (removeFromTree) + deleteObject (draggedObj); + draggedObj = null; + } + #endregion + + void removeObject (Widget go) { + if (go == null) + return; + if (go.Parent == null) + return; +// lock (imlVE.UpdateMutex) { + Interface i = go.Parent as Interface; + if (i != null) { + i.RemoveWidget (go); + imlProjFile.Instance = null; + } else { + Container c = go.Parent as Container; + if (c != null) + c.SetChild (null); + else { + TemplatedContainer tc = go.Parent as TemplatedContainer; + if (tc != null) + tc.Content = null; + else { + Group g = go.Parent as Group; + if (g != null) + g.RemoveChild (go); + } + } + } + EditorIsDirty = true; + //} + } + void deleteObject (Widget go) { + if (go == null) + return; + //lock (imlVE.UpdateMutex) { + removeObject (go); + go.Dispose (); + //} + } + + ILayoutable getPossibleParent (ILayoutable parent, Widget go) { + if (go == null) + return null; +// lock (imlVE.UpdateMutex) { + + Interface i = null; + if (parent == null) + i = imlVE; + else + i = parent as Interface; + if (i != null) + return i.GraphicTree.Count > 0 ? null : i; + + Container c = parent as Container; + if (c != null) + return c.Child == null || c.Child == go ? c : null; + + TemplatedContainer tc = parent as TemplatedContainer; + if (tc != null) + return tc.Content == null || tc.Content == go? tc : null; + + return parent as Group; +// } + } + bool tryAddObjectTo (ILayoutable parent, Widget go) { + if (go == null) + return false; +// lock (imlVE.UpdateMutex) { + Interface i = null; + if (parent == null) + i = imlVE; + else + i = parent as Interface; + if (i != null) { + if (i.GraphicTree.Count > 0) + return false; + i.AddWidget (go); + imlProjFile.Instance = go; + EditorIsDirty = true; + return true; + } + Container c = parent as Container; + if (c != null) { + if (c.Child != null) + return false; + //return tryAddObjectTo (c.Parent, go); + c.SetChild (go); + EditorIsDirty = true; + return true; + } + TemplatedContainer tc = parent as TemplatedContainer; + if (tc != null) { + if (tc.Content != null) + return false; + //return tryAddObjectTo (c.Parent, go); + tc.Content = (go); + EditorIsDirty = true; + return true; + } + Group g = parent as Group; + if (g != null) { + g.AddChild (go); + EditorIsDirty = true; + return true; + } + return false;//tryAddObjectTo (parent.Parent, go); +// } + } + + + + void WidgetCheckOver (Widget go, MouseMoveEventArgs e){ + Type tGo = go.GetType(); + if (typeof(TemplatedGroup).IsAssignableFrom (tGo)) { + + } else if (typeof(TemplatedContainer).IsAssignableFrom (tGo)) { + TemplatedContainer c = go as TemplatedContainer; + if (c.Content?.MouseIsIn (e.Position) == true) { + WidgetCheckOver (c.Content, e); + return; + } + } else if (typeof(TemplatedControl).IsAssignableFrom (tGo)) { + } else if (typeof(Group).IsAssignableFrom (tGo)) { + Group c = go as Group; + for (int i = c.Children.Count -1; i >= 0; i--) { + if (c.Children[i].MouseIsIn (e.Position)) { + WidgetCheckOver (c.Children[i], e); + return; + } + } + } else if (typeof(Crow.Container).IsAssignableFrom (tGo)) { + Crow.Container c = go as Crow.Container; + if (c.Child?.MouseIsIn (e.Position)==true) { + WidgetCheckOver (c.Child, e); + return; + } + } + HoverWidget = go; + WidgetMouseEnter (go, e); + } + void WidgetMouseLeave (Widget go, MouseMoveEventArgs e){ + + } + void WidgetMouseEnter (Widget go, MouseMoveEventArgs e){ + + } + void WidgetMouseMove (Widget go, MouseMoveEventArgs e){} + + public bool ProcessMouseMove(int x, int y) + { + int deltaX = x - imlVE.Mouse.X; + int deltaY = y - imlVE.Mouse.Y; + imlVE.Mouse.X = x; + imlVE.Mouse.Y = y; + MouseMoveEventArgs e = new MouseMoveEventArgs (x, y, deltaX, deltaY); + e.Mouse = imlVE.Mouse; + + if (imlVE.ActiveWidget != null) { + //TODO, ensure object is still in the graphic tree + //send move evt even if mouse move outside bounds + WidgetMouseMove (imlVE.ActiveWidget, e); + return true; + } + + if (HoverWidget != null) { + //TODO, ensure object is still in the graphic tree + //check topmost graphicobject first + Widget tmp = HoverWidget; + Widget topc = null; + while (tmp is Widget) { + topc = tmp; + tmp = tmp.LogicalParent as Widget; + } + int idxhw = imlVE.GraphicTree.IndexOf (topc); + if (idxhw != 0) { + int i = 0; + while (i < idxhw) { + if (imlVE.GraphicTree [i].LogicalParent == imlVE.GraphicTree [i].Parent) { + if (imlVE.GraphicTree [i].MouseIsIn (e.Position)) { + while (imlVE.HoverWidget != null) { + WidgetMouseLeave (imlVE.HoverWidget, e); + imlVE.HoverWidget = imlVE.HoverWidget.LogicalParent as Widget; + } + + WidgetCheckOver (GraphicTree [i], e); + return true; + } + } + i++; + } + } + + + if (imlVE.HoverWidget.MouseIsIn (e.Position)) { + WidgetCheckOver (imlVE.HoverWidget, (e)); + return true; + } else { + WidgetMouseLeave (imlVE.HoverWidget, e); + //seek upward from last focused graph obj's + while (imlVE.HoverWidget.LogicalParent as Widget != null) { + imlVE.HoverWidget = imlVE.HoverWidget.LogicalParent as Widget; + if (imlVE.HoverWidget.MouseIsIn (e.Position)) { + WidgetCheckOver (imlVE.HoverWidget, e); + return true; + } else + WidgetMouseLeave (imlVE.HoverWidget, e); + } + } + } + + //top level graphic obj's parsing + lock (imlVE.GraphicTree) { + for (int i = 0; i < imlVE.GraphicTree.Count; i++) { + Widget g = imlVE.GraphicTree [i]; + if (g.MouseIsIn (e.Position)) { + WidgetCheckOver (g, e); + return true; + } + } + } + imlVE.HoverWidget = null; + return false; + + } + + void GTView_SelectedItemChanged (object sender, SelectionChangeEventArgs e) + { + SelectedItem = e.NewValue as Widget; + } + + + public override void onKeyDown (object sender, KeyEventArgs e) + { + + switch (e.Key) { + case Key.Delete: + if (selectedItem == null) + return; + deleteObject (selectedItem); + break; + case Key.Escape: + SelectedItem = null; + break; + } + } + } +} diff --git a/Samples/CrowIDE/src/Editors/Parsers/BufferParser.cs b/Samples/CrowIDE/src/Editors/Parsers/BufferParser.cs new file mode 100644 index 00000000..c193c7db --- /dev/null +++ b/Samples/CrowIDE/src/Editors/Parsers/BufferParser.cs @@ -0,0 +1,380 @@ +using System; +using System.IO; +using Crow; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +namespace Crow.Coding +{ + /// <summary> + /// base class for tokenizing sources + /// </summary> + public abstract class BufferParser + { + /// <summary> + /// Default tokens, this enum may be overriden in derived parser with the new keyword, + /// see XMLParser for example. + /// </summary> + public enum TokenType { + Unknown = 0, + WhiteSpace = 1, + NewLine = 2, + LineComment = 3, + BlockCommentStart = 4, + BlockComment = 5, + BlockCommentEnd = 6, + Preprocessor = 7, + Identifier = 8, + Keyword = 9, + OpenBlock = 10, + CloseBlock = 11, + StatementEnding = 12, + OperatorOrPunctuation = 13, + IntegerLitteral = 14, + RealLitteral = 15, + StringLitteralOpening = 16, + StringLitteralClosing = 17, + StringLitteral = 18, + CharLitteralOpening = 19, + CharLitteralClosing = 20, + CharLitteral = 21, + BoolLitteral = 22, + NullLitteral = 23, + Type = 24, + } + + #region CTOR + public BufferParser (CodeBuffer _buffer) + { + buffer = _buffer; + + buffer.LineUpadateEvent += Buffer_LineUpadateEvent; + //buffer.LineAdditionEvent += Buffer_LineAdditionEvent;; + buffer.LineRemoveEvent += Buffer_LineRemoveEvent; + buffer.BufferCleared += Buffer_BufferCleared; + } + + #endregion + + #region Buffer events handlers + void Buffer_BufferCleared (object sender, EventArgs e) + { + + } + void Buffer_LineAdditionEvent (object sender, CodeBufferEventArgs e) + { + + } + void Buffer_LineRemoveEvent (object sender, CodeBufferEventArgs e) + { + reparseSource (); + } + void Buffer_LineUpadateEvent (object sender, CodeBufferEventArgs e) + { + for (int i = 0; i < e.LineCount; i++) + TryParseBufferLine (e.LineStart + i); + reparseSource (); + } + #endregion + + internal int currentLine = 0; + internal int currentColumn = 0; + + int syntTreeDepth = 0; + public int SyntacticTreeDepth { + get { return syntTreeDepth;} + set { + syntTreeDepth = value; + if (syntTreeDepth > SyntacticTreeMaxDepth) + SyntacticTreeMaxDepth = syntTreeDepth; + } + } + public int SyntacticTreeMaxDepth = 0; + + protected CodeBuffer buffer; + protected Token currentTok; + protected bool eol = true; + protected Point CurrentPosition { + get { return new Point (currentLine, currentColumn); } + set { + currentLine = value.Y; + currentColumn = value.X; + } + } + + public Node RootNode; + + public abstract void ParseCurrentLine(); + public abstract void SyntaxAnalysis (); + public void reparseSource () { + for (int i = 0; i < buffer.LineCount; i++) { + if (!buffer[i].IsParsed) + TryParseBufferLine (i); + } + try { + SyntaxAnalysis (); + } catch (Exception ex) { + Debug.WriteLine ("Syntax Error: " + ex.ToString ()); + if (ex is ParserException) + SetLineInError (ex as ParserException); + } + } + public void TryParseBufferLine(int lPtr) { + buffer [lPtr].exception = null; + currentLine = lPtr; + currentColumn = 0; + eol = false; + + try { + ParseCurrentLine (); + } catch (Exception ex) { + Debug.WriteLine (ex.ToString ()); + if (ex is ParserException) + SetLineInError (ex as ParserException); + } + + } + + public virtual void SetLineInError(ParserException ex) { + currentTok = default(Token); + if (ex.Line >= buffer.LineCount) + ex.Line = buffer.LineCount - 1; + if (buffer [ex.Line].IsFolded) + buffer.ToogleFolding (ex.Line); + buffer [ex.Line].SetLineInError (ex); + } + public virtual string LineBrkRegex { + get { return @"\r\n|\r|\n|\\\\n"; } + } + void updateFolding () { + // Stack<TokenList> foldings = new Stack<TokenList>(); + // bool inStartTag = false; + // + // for (int i = 0; i < parser.Tokens.Count; i++) { + // TokenList tl = parser.Tokens [i]; + // tl.foldingTo = null; + // int fstTK = tl.FirstNonBlankTokenIndex; + // if (fstTK > 0 && fstTK < tl.Count - 1) { + // if (tl [fstTK + 1] != XMLParser.TokenType.ElementName) + // continue; + // if (tl [fstTK] == XMLParser.TokenType.ElementStart) { + // //search closing tag + // int tkPtr = fstTK+2; + // while (tkPtr < tl.Count) { + // if (tl [tkPtr] == XMLParser.TokenType.ElementClosing) + // + // tkPtr++; + // } + // if (tl.EndingState == (int)XMLParser.States.Content) + // foldings.Push (tl); + // else if (tl.EndingState == (int)XMLParser.States.StartTag) + // inStartTag = true; + // continue; + // } + // if (tl [fstTK] == XMLParser.TokenType.ElementEnd) { + // TokenList tls = foldings.Pop (); + // int fstTKs = tls.FirstNonBlankTokenIndex; + // if (tls [fstTK + 1].Content == tl [fstTK + 1].Content) { + // tl.foldingTo = tls; + // continue; + // } + // parser.CurrentPosition = tls [fstTK + 1].Start; + // parser.SetLineInError(new ParserException(parser, "closing tag not corresponding")); + // } + // + // } + // } + } + + #region low level parsing + protected void addCharToCurTok(char c, Point position){ + currentTok.Start = position; + currentTok += c; + } + /// <summary> + /// Read one char from current position in buffer and store it into the current token + /// </summary> + /// <param name="startOfTok">if true, set the Start position of the current token to the current position</param> + protected void readToCurrTok(bool startOfTok = false){ + if (startOfTok) + currentTok.Start = CurrentPosition; + currentTok += Read(); + } + /// <summary> + /// read n char from the buffer and store it into the current token + /// </summary> + protected void readToCurrTok(int length) { + for (int i = 0; i < length; i++) + currentTok += Read (); + } + /// <summary> + /// Save current token into current TokensLine and raz current token + /// </summary> + protected void saveAndResetCurrentTok() { + currentTok.End = CurrentPosition; + buffer[currentLine].Tokens.Add (currentTok); + currentTok = default(Token); + } + /// <summary> + /// read one char and add current token to current TokensLine, current token is reset + /// </summary> + /// <param name="type">Type of the token</param> + /// <param name="startToc">set start of token to current position</param> + protected void readAndResetCurrentTok(System.Enum type, bool startToc = false) { + readToCurrTok (); + saveAndResetCurrentTok (type); + } + /// <summary> + /// Save current tok + /// </summary> + /// <param name="type">set the type of the tok</param> + protected void saveAndResetCurrentTok(System.Enum type) { + currentTok.Type = (TokenType)type; + saveAndResetCurrentTok (); + } + protected void setPreviousTokOfTypeTo (TokenType inType, TokenType newType) { + for (int i = currentLine; i >= 0; i--) { + int j = buffer [i].Tokens.Count - 1; + while (j >= 0) { + if (buffer [i].Tokens [j].Type == inType) { + Token t = buffer [i].Tokens [j]; + t.Type = newType; + buffer [i].Tokens [j] = t; + return; + } + j--; + } + } + } + /// <summary> + /// Peek next char, emit '\n' if current column > buffer's line length + /// Throw error if eof is true + /// </summary> + protected virtual char Peek() { + if (eol) + throw new ParserException (currentLine, currentColumn, "Unexpected End of line"); + return currentColumn < buffer [currentLine].Length ? + buffer [currentLine] [currentColumn] : '\n'; + } + /// <summary> + /// Peek n char from buffer or less if remaining char in buffer's line is less than requested + /// if end of line is reached, no '\n' will be emitted, instead, empty string is returned. '\n' should be checked only + /// with single char Peek(). + /// Throw error is eof is true + /// </summary> + /// <param name="length">Length.</param> + protected virtual string Peek(int length) { + if (eol) + return "";//throw new ParserException (currentLine, currentColumn, "Unexpected End of Line"); + int lg = Math.Min(length, Math.Max (buffer [currentLine].Length - currentColumn, buffer [currentLine].Length - currentColumn - length)); + if (lg == 0) + return ""; + return buffer [currentLine].Content.Substring (currentColumn, lg); + } + /// <summary> + /// read one char from buffer at current position, if '\n' is read, current line is incremented + /// and column is reset to 0 + /// </summary> + protected virtual char Read() { + char c = Peek (); + if (c == '\n') + eol = true; + currentColumn++; + return c; + } + protected virtual string Read(int charCount){ + string tmp = ""; + for (int i = 0; i < charCount; i++) { + if (eol) + break; + tmp += Read (); + } + return tmp; + } + /// <summary> + /// read until end of line is reached + /// </summary> + /// <returns>string read</returns> + protected virtual string ReadLine () { + StringBuilder tmp = new StringBuilder(); + char c = Read (); + while (!eol) { + tmp.Append (c); + c = Read (); + } + return tmp.ToString(); + } + /// <summary> + /// read until end expression is reached or end of line. + /// </summary> + /// <returns>string read minus the ending expression that has to be read after</returns> + /// <param name="endExp">Expression to search for</param> + protected virtual string ReadLineUntil (string endExp){ + string tmp = ""; + + while (!eol) { + if (buffer [currentLine].Length - currentColumn - endExp.Length < 0) { + tmp += ReadLine(); + break; + } + if (string.Equals (Peek (endExp.Length), endExp)) + return tmp; + tmp += Read(); + } + return tmp; + } + /// <summary> + /// skip white spaces, but not line break. Save spaces in a WhiteSpace token. + /// </summary> + protected void SkipWhiteSpaces () { + if (currentTok.Type != TokenType.Unknown) + throw new ParserException (currentLine, currentColumn, "current token should be reset to unknown (0) before skiping white spaces"); + while (!eol) { + if (!char.IsWhiteSpace (Peek ())||Peek()=='\n') + break; + readToCurrTok (currentTok.Type == TokenType.Unknown); + currentTok.Type = TokenType.WhiteSpace; + } + if (currentTok.Type != TokenType.Unknown) + saveAndResetCurrentTok (); + } + #endregion + + protected Node addChildNode (Node curNode, CodeLine cl, int tokPtr, string type = "") { + Node n = new Node () { Name = cl.Tokens [tokPtr].Content, StartLine = cl, Type = type }; + curNode.AddChild (n); + if (cl.SyntacticNode == null) + cl.SyntacticNode = n; + SyntacticTreeDepth++; + return n; + } + protected void closeNodeAndGoUp (ref Node n, CodeLine cl, string type = ""){ + while (n != null) { + if (n.Type == type) { + n.EndLine = cl; + n = n.Parent; + SyntacticTreeDepth--; + break; + } + n = n.Parent; + SyntacticTreeDepth--; + } + } + protected void closeNodeAndGoUp (ref Node n, CodeLine cl){ + SyntacticTreeDepth--; + n.EndLine = cl; + n = n.Parent; + } + + protected void initSyntaxAnalysis () { + RootNode = new Node () { Name = "RootNode", Type="Root" }; + SyntacticTreeDepth = SyntacticTreeMaxDepth = 0; + } + + + protected void throwParserException(string msg){ + throw new ParserException (currentLine, currentColumn, msg); + } + } +}
\ No newline at end of file diff --git a/Samples/CrowIDE/src/Editors/Parsers/CSharpParser.cs b/Samples/CrowIDE/src/Editors/Parsers/CSharpParser.cs new file mode 100644 index 00000000..23655aa6 --- /dev/null +++ b/Samples/CrowIDE/src/Editors/Parsers/CSharpParser.cs @@ -0,0 +1,386 @@ +using System; +using Crow; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text.RegularExpressions; +using System.Linq; + +namespace Crow.Coding +{ + public class CSharpParser : BufferParser + { + #region keywords + string[] keywords = new string[] { + "abstract", + "as", + "ascending", + "async", + "await", + "base", + "bool", + "break", + "byte", + "case", + "catch", + "char", + "checked", + "class", + "const", + "continue", + "decimal", + "default", + "delegate", + "descending", + "do", + "double", + "dynamic", + "else", + "enum", + "equals", + "event", + "explicit", + "extern", + "false", + "finally", + "fixed", + "float", + "for", + "foreach", + "from", + "get", + "goto", + "group", + "if", + "implicit", + "in", + "int", + "interface", + "internal", + "is", + "join", + "let", + "lock", + "long", + "nameof", + "namespace", + "new", + "null", + "object", + "operator", + "orderby", + "out", + "override", + "params", + "partial", + "private", + "protected", + "public", + "readonly", + "ref", + "return", + "sbyte", + "sealed", + "select", + "set", + "short", + "sizeof", + "stackalloc", + "static", + "string", + "struct", + "switch", + "this", + "throw", + "true", + "try", + "typeof", + "uint", + "ulong", + "unchecked", + "unsafe", + "ushort", + "using", + "value", + "var", + "virtual", + "void", + "volatile", + "when", + "where", + "while", + "yield " + }; + #endregion + + public enum States + { + init, + BlockComment, + InNameSpace, + InClass, + InMember, + Unknown, + } + + public CSharpParser (CodeBuffer _buffer) : base(_buffer) + { + } + + #region Regular Expression for validity checks + static Regex rxValidChar = new Regex(@"\p{Lu}|\p{Ll}|\p{Lt}|\p{Lm}|\p{Lo}|\p{Nl}|\p{Mn}|\p{Mc}|\p{Nd}|\p{Pc}|\p{Cf}"); + static Regex rxNameStartChar = new Regex(@"_|\p{Lu}|\p{Ll}|\p{Lt}|\p{Lm}|\p{Lo}|\p{Nl}"); + static Regex rxNameChar = new Regex(@"\p{Lu}|\p{Ll}|\p{Lt}|\p{Lm}|\p{Lo}|\p{Nl}|\p{Mn}|\p{Mc}|\p{Nd}|\p{Pc}|\p{Cf}"); + static Regex rxNewLineChar = new Regex(@"\u000D|\u000A|\u0085|\u2028|\u2029"); + static Regex rxWhiteSpaceChar = new Regex(@"\p{Zs}|\u0009|\u000B|\u000C"); + static Regex rxDecimal = new Regex(@"[0-9]+"); + static Regex rxHexadecimal = new Regex(@"[0-9a-fA-F]+"); + + public static bool CharIsValidCharStartName (char c) { + return rxNameStartChar.IsMatch(new string(new char[]{c})); + } + public static bool CharIsValidCharName (char c) { + return rxNameChar.IsMatch(new string(new char[]{c})); + } + + public bool nextCharIsValidCharStartName + { + get { return CharIsValidCharStartName(Peek()); } + } + public bool nextCharIsValidCharName + { + get { return CharIsValidCharName(Peek()); } + } + #endregion + + States curState = States.init; + States savedState = States.init; + + public override void ParseCurrentLine () + { + //Debug.WriteLine (string.Format("parsing line:{0}", currentLine)); + CodeLine cl = buffer [currentLine]; + cl.Tokens = new List<Token> (); + + + //retrieve current parser state from previous line + if (currentLine > 0) + curState = (States)buffer[currentLine - 1].EndingState; + else + curState = States.init; + + States previousEndingState = (States)cl.EndingState; + + while (! eol) { + if (currentTok.IsNull) + SkipWhiteSpaces (); + + if (curState == States.BlockComment) { + currentTok.Start = CurrentPosition; + currentTok.Type = (BufferParser.TokenType)TokenType.BlockComment; + currentTok += ReadLineUntil ("*/"); + if (Peek (2) == "*/") { + readToCurrTok (2); + curState = savedState; + } + saveAndResetCurrentTok (); + continue; + } + + switch (Peek()) { + case '\n': + eol = true; + if (!currentTok.IsNull) + saveAndResetCurrentTok (); + break; + case '#': + readToCurrTok (true); + currentTok += ReadLine (); + saveAndResetCurrentTok (TokenType.Preprocessor); + break; + case '/': + readToCurrTok (true); + switch (Peek ()) { + case '*': + readToCurrTok (); + currentTok += ReadLine (); + //currentTok.Type = (Parser.TokenType)TokenType.BlockComment; + savedState = curState; + curState = States.BlockComment; + saveAndResetCurrentTok (TokenType.BlockComment); + break; + case '/': + //readToCurrTok (); + currentTok += ReadLine (); + saveAndResetCurrentTok (TokenType.LineComment); + //currentTok.Type = (Parser.TokenType)TokenType.LineComment; + break; + default: + currentTok += ReadLine (); + saveAndResetCurrentTok (TokenType.Unknown); + break; + } + break; + case '{': + if (currentTok.IsNull) + readAndResetCurrentTok (TokenType.OpenBlock, true); + else + readToCurrTok (); + break; + case '}': + if (currentTok.IsNull) + readAndResetCurrentTok (TokenType.CloseBlock, true); + else + readToCurrTok (); + break; + case '\\'://unicode escape sequence + if (!(currentTok.Type == TokenType.Identifier || + currentTok.IsEmpty || currentTok.Type == TokenType.StringLitteral || currentTok.Type == TokenType.CharLitteral)) { + saveAndResetCurrentTok (); + } + Point pos = CurrentPosition; + Read (); + char escChar = Read (); + + if (escChar == 'u') { + char c = char.ConvertFromUtf32 (int.Parse (Read (4), System.Globalization.NumberStyles.HexNumber))[0]; + if (currentTok.IsEmpty) { + if (!CharIsValidCharStartName (c)) + throwParserException ("expecting identifier start"); + currentTok.Start = pos; + currentTok.Type = TokenType.Identifier; + } else if (currentTok.Type == TokenType.Identifier) { + if (!CharIsValidCharName (c)) + throwParserException ("expecting identifier valid char"); + } + currentTok += c; + break; + } + currentTok += new String (new char[] { '\\', escChar }); + break; + case '\'': + if (currentTok.IsNull) { + readAndResetCurrentTok (TokenType.CharLitteralOpening, true); + currentTok.Type = TokenType.CharLitteral; + } else if (currentTok.Type == TokenType.CharLitteral) { + saveAndResetCurrentTok (); + readAndResetCurrentTok (TokenType.CharLitteralClosing, true); + } else if (currentTok.Type == TokenType.StringLitteral){ + readToCurrTok (); + } else + throwParserException ("unexpected character: (\')"); + break; + case '"': + if (currentTok.IsNull) { + readAndResetCurrentTok (TokenType.StringLitteralOpening, true); + currentTok.Type = TokenType.StringLitteral; + } else if (currentTok.Type == TokenType.StringLitteral) { + saveAndResetCurrentTok (); + readAndResetCurrentTok (TokenType.StringLitteralClosing, true); + } else + throwParserException ("unexpected character: (\")"); + break; + default: + if (currentTok.Type == TokenType.StringLitteral || currentTok.Type == TokenType.CharLitteral) { + readToCurrTok (currentTok.IsEmpty); + } else if (currentTok.IsNull) { + if (nextCharIsValidCharStartName) { + readToCurrTok (true); + while (nextCharIsValidCharName) + readToCurrTok (); + + if (keywords.Contains (currentTok.Content)) + saveAndResetCurrentTok (TokenType.Keyword); + else + saveAndResetCurrentTok (TokenType.Identifier); + continue; + } else + readAndResetCurrentTok(TokenType.Unknown, true); + } else + readAndResetCurrentTok(TokenType.Unknown, true); + break; + } + } + + if (cl.EndingState != (int)curState && currentLine < buffer.LineCount - 1) + buffer [currentLine + 1].Tokens = null; + + cl.EndingState = (int)curState; + } + + public override void SyntaxAnalysis () + { + initSyntaxAnalysis (); + Node currentNode = RootNode; + + int ptrLine = 0; + while (ptrLine < buffer.LineCount) { + CodeLine cl = buffer [ptrLine]; + if (cl.Tokens == null){ + ptrLine++; + continue; + } + cl.SyntacticNode = null; + + int tokPtr = 0; + bool onlyWhiteSpace = true; + while (tokPtr < cl.Tokens.Count) { + if (cl.Tokens [tokPtr].Type == TokenType.WhiteSpace) { + tokPtr++; + continue; + } + + if (cl.Tokens [tokPtr].Type == TokenType.LineComment && onlyWhiteSpace) { + int startLine = ptrLine; + ptrLine++; + while (ptrLine < buffer.LineCount) { + int idx = buffer [ptrLine].FirstNonBlankTokIndex; + if (idx < 0) + break; + if (buffer [ptrLine].Tokens [idx].Type != TokenType.LineComment) + break; + ptrLine++; + } + ptrLine--; + if (ptrLine - startLine > 0) { + currentNode = addChildNode (currentNode, cl, tokPtr, "comment"); + closeNodeAndGoUp (ref currentNode, buffer [ptrLine], "comment"); + } + break; + } + + switch (cl.Tokens [tokPtr].Type) { + case TokenType.BlockCommentStart: + case TokenType.OpenBlock: + currentNode = addChildNode (currentNode, cl, tokPtr); + break; + case TokenType.CloseBlock: + case TokenType.BlockCommentEnd: + closeNodeAndGoUp (ref currentNode, cl); + break; + case TokenType.Preprocessor: + if (cl.Tokens [tokPtr].Content.StartsWith ("#region", StringComparison.Ordinal)) { + currentNode = addChildNode (currentNode, cl, tokPtr, "region"); + } else if (cl.Tokens [tokPtr].Content.StartsWith ("#endregion", StringComparison.Ordinal)) { + + closeNodeAndGoUp (ref currentNode, cl,"region"); + } + break; + } + onlyWhiteSpace = false; + tokPtr++; + } + ptrLine++; + } + /*ptrLine = 0; + while (ptrLine < buffer.LineCount) { + CodeLine cl = buffer [ptrLine]; + if (cl.IsFoldable) { + if (cl.SyntacticNode.Type == "comment" || cl.SyntacticNode.Type == "region") + cl.IsFolded = true; + } + ptrLine++; + }*/ + } + } +} + diff --git a/Samples/CrowIDE/src/Editors/Parsers/StyleParser.cs b/Samples/CrowIDE/src/Editors/Parsers/StyleParser.cs new file mode 100644 index 00000000..9a1e74cd --- /dev/null +++ b/Samples/CrowIDE/src/Editors/Parsers/StyleParser.cs @@ -0,0 +1,179 @@ +using System; +using Crow; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text.RegularExpressions; +using System.Linq; + +namespace Crow.Coding +{ + public class StyleParser : BufferParser + { + enum States { init, classNames, members, value, endOfStatement } + + public StyleParser (CodeBuffer _buffer) : base(_buffer) + { + } + + #region Character ValidityCheck + static Regex rxValidChar = new Regex(@"\p{Lu}|\p{Ll}|\p{Lt}|\p{Lm}|\p{Lo}|\p{Nl}|\p{Mn}|\p{Mc}|\p{Nd}|\p{Pc}|\p{Cf}"); + static Regex rxNameStartChar = new Regex(@"_|\p{Lu}|\p{Ll}|\p{Lt}|\p{Lm}|\p{Lo}"); + static Regex rxNameChar = new Regex(@"\p{Lu}|\p{Ll}|\p{Lt}|\p{Lm}|\p{Lo}|\p{Nl}|\p{Mn}|\p{Mc}|\p{Nd}|\p{Pc}|\p{Cf}"); + static Regex rxDecimal = new Regex(@"[0-9]+"); + static Regex rxHexadecimal = new Regex(@"[0-9a-fA-F]+"); + + public bool nextCharIsValidCharStartName + { + get { return rxNameStartChar.IsMatch(new string(new char[]{Peek()})); } + } + public bool nextCharIsValidCharName + { + get { return rxNameChar.IsMatch(new string(new char[]{Peek()})); } + } + #endregion + + States curState = States.classNames; + + public override void ParseCurrentLine () + { + //Debug.WriteLine (string.Format("parsing line:{0}", currentLine)); + CodeLine cl = buffer [currentLine]; + cl.Tokens = new List<Token> (); + + //retrieve current parser state from previous line + if (currentLine > 0) + curState = (States)buffer[currentLine - 1].EndingState; + else + curState = States.init; + + States previousEndingState = (States)cl.EndingState; + + while (! eol) { + SkipWhiteSpaces (); + + if (eol) + break; + + if (Peek () == '\n') { + if (currentTok != TokenType.Unknown) + throw new ParserException (currentLine, currentColumn, "Unexpected end of line"); + Read (); + eol = true; + continue; + } + + switch (Peek()) { + case '/': + readToCurrTok (true); + switch (Peek ()) { + case '/': + currentTok += ReadLine (); + saveAndResetCurrentTok (TokenType.LineComment); + break; + default: + currentTok += ReadLine (); + saveAndResetCurrentTok (TokenType.Unknown); + break; + } + break; + case ',': + if (curState != States.init && curState != States.classNames ) + throw new ParserException (currentLine, currentColumn, "Unexpected char ','"); + readAndResetCurrentTok (TokenType.OperatorOrPunctuation, true); + curState = States.classNames; + break; + case '{': + if (!(curState == States.init || curState == States.classNames)) + throw new ParserException (currentLine, currentColumn, "Unexpected char '{'"); + readAndResetCurrentTok (TokenType.OpenBlock, true); + curState = States.members; + break; + case '}': + if (curState != States.members) + throw new ParserException (currentLine, currentColumn, "Unexpected char '}'"); + readAndResetCurrentTok (TokenType.CloseBlock, true); + curState = States.classNames; + break; + case '=': + if (curState == States.classNames) + throw new ParserException (currentLine, currentColumn, "Unexpected char '='"); + setPreviousTokOfTypeTo (TokenType.Type, TokenType.Identifier); + readAndResetCurrentTok (TokenType.OperatorOrPunctuation, true); + curState = States.value; + break; + case '"': + if (curState != States.value) + throw new ParserException (currentLine, currentColumn, "Unexpected char '\"'"); + readAndResetCurrentTok (TokenType.StringLitteralOpening, true); + + while (!eol) { + currentTok += ReadLineUntil ("\""); + if (currentTok.Content [currentTok.Content.Length - 1] == '\\') + readToCurrTok (); + else + break; + } + if (eol) + throw new ParserException (currentLine, currentColumn, "Unexpected end of line"); + saveAndResetCurrentTok (TokenType.StringLitteral); + + readAndResetCurrentTok (TokenType.StringLitteralClosing, true); + curState = States.endOfStatement; + break; + case ';': + if (curState != States.endOfStatement) + throw new ParserException (currentLine, currentColumn, "Unexpected end of statement"); + readAndResetCurrentTok (TokenType.StatementEnding, true); + curState = States.members; + break; + default: + if (currentTok.Type != TokenType.Unknown) + throw new ParserException (currentLine, currentColumn, "error curtok not null"); + if (curState == States.value) + throw new ParserException (currentLine, currentColumn, "expecting value enclosed in '\"'"); + if (curState == States.endOfStatement) + throw new ParserException (currentLine, currentColumn, "expecting end of statement"); + + if (nextCharIsValidCharStartName) { + readToCurrTok (true); + while (nextCharIsValidCharName) + readToCurrTok (); + } + saveAndResetCurrentTok (TokenType.Type); + break; + } + } + + if (cl.EndingState != (int)curState && currentLine < buffer.LineCount - 1) + buffer [currentLine + 1].Tokens = null; + + cl.EndingState = (int)curState; + } + public override void SyntaxAnalysis () + { + initSyntaxAnalysis (); + Node currentNode = RootNode; + + for (int i = 0; i < buffer.LineCount; i++) { + CodeLine cl = buffer[i]; + if (cl.Tokens == null) + continue; + cl.SyntacticNode = null; + + int tokPtr = 0; + while (tokPtr < cl.Tokens.Count) { + switch (cl.Tokens [tokPtr].Type) { + case TokenType.OpenBlock: + currentNode = addChildNode (currentNode, cl, tokPtr, "style"); + break; + case TokenType.CloseBlock: + closeNodeAndGoUp (ref currentNode, cl, "style"); + break; + } + tokPtr++; + } + } + } + } +} + diff --git a/Samples/CrowIDE/src/Editors/Parsers/XMLParser.cs b/Samples/CrowIDE/src/Editors/Parsers/XMLParser.cs new file mode 100644 index 00000000..8e95408e --- /dev/null +++ b/Samples/CrowIDE/src/Editors/Parsers/XMLParser.cs @@ -0,0 +1,307 @@ +using System; +using Crow; +using System.Collections.Generic; +using System.Text.RegularExpressions; +using System.Diagnostics; +using System.Linq; + +namespace Crow.Coding +{ + public class XMLParser : BufferParser + { + public new enum TokenType { + Unknown = BufferParser.TokenType.Unknown, + WhiteSpace = BufferParser.TokenType.WhiteSpace, + NewLine = BufferParser.TokenType.NewLine, + LineComment = BufferParser.TokenType.LineComment, + BlockCommentStart = BufferParser.TokenType.BlockCommentStart, + BlockComment = BufferParser.TokenType.BlockComment, + BlockCommentEnd = BufferParser.TokenType.BlockCommentEnd, + ElementName = BufferParser.TokenType.Type, + AttributeName = BufferParser.TokenType.Identifier, + ElementClosing = BufferParser.TokenType.StatementEnding, + Affectation = BufferParser.TokenType.OperatorOrPunctuation, + AttributeValueOpening = BufferParser.TokenType.StringLitteralOpening, + AttributeValueClosing = BufferParser.TokenType.StringLitteralClosing, + AttributeValue = BufferParser.TokenType.StringLitteral, + XMLDecl = BufferParser.TokenType.Preprocessor, + Doctype = BufferParser.TokenType.Keyword, + ElementStart = 50, + ElementEnd = 51, + } + + public enum States + { + init, //first statement of prolog, xmldecl should only apear in this state + prolog, //misc before doctypedecl + InternalSubset, //doctype declaration subset + ExternalSubsetInit, + ExternalSubset, + BlockComment, + DTDEnd,//doctype finished + XML,//normal xml + StartTag,//inside start tag + Content,//after start tag with no closing slash + EndTag + } + + #region CTOR + public XMLParser (CodeBuffer _buffer) : base(_buffer) {} + #endregion + + enum Keywords + { + DOCTYPE, + ELEMENT, + ATTLIST, + ENTITY, + NOTATION + } + + States curState = States.init; + + #region Regular Expression for validity checks + //private static Regex rxValidChar = new Regex("[\u0020-\uD7FF]"); + private static Regex rxValidChar = new Regex(@"\u0009|\u000A|\u000D|[\u0020-\uD7FF]|[\uE000-\uFFFD]"); //| [\u10000-\u10FFFF] unable to set those plans + private static Regex rxNameStartChar = new Regex(@":|[A-Z]|_|[a-z]|[\u00C0-\u00D6]|[\u00D8-\u00F6]|[\u00F8-\u02FF]|[\u0370-\u037D]|[\u037F-\u1FFF]|[\u200C-\u200D]|[\u2070-\u218F]|[\u2C00-\u2FEF]|[\u3001-\uD7FF]|[\uF900-\uFDCF]|[\uFDF0-\uFFFD]"); // | [\u10000-\uEFFFF] + private static Regex rxNameChar = new Regex(@":|[A-Z]|_|[a-z]|[\u00C0-\u00D6]|[\u00D8-\u00F6]|[\u00F8-\u02FF]|[\u0370-\u037D]|[\u037F-\u1FFF]|[\u200C-\u200D]|[\u2070-\u218F]|[\u2C00-\u2FEF]|[\u3001-\uD7FF]|[\uF900-\uFDCF]|[\uFDF0-\uFFFD]|-|\.|[0-9]|\u00B7|[\u0300-\u036F]|[\u203F-\u2040]");//[\u10000-\uEFFFF]| + private static Regex rxDecimal = new Regex(@"[0-9]+"); + private static Regex rxHexadecimal = new Regex(@"[0-9a-fA-F]+"); + private static Regex rxAttributeValue = new Regex(@"[^<]"); + private static Regex rxEntityValue = new Regex(@"[^<]"); + private static Regex rxPubidChar = new Regex(@"\u0020|\u000D|\u000A|[a-zA-Z0-9]|[-\(\)\+\,\./:=\?;!\*#@\$_%]"); + #endregion + + #region Character ValidityCheck + public bool nextCharIsValidCharStartName + { + get { return rxNameStartChar.IsMatch(new string(new char[]{Peek()})); } + } + public bool nextCharIsValidCharName + { + get { return rxNameChar.IsMatch(new string(new char[]{Peek()})); } + } + #endregion + + public override void SetLineInError (ParserException ex) + { + base.SetLineInError (ex); + //buffer[ex.Line].Tokens.EndingState = (int)States.init; + } + + public override void ParseCurrentLine () + { + //Debug.WriteLine (string.Format("parsing line:{0}", currentLine)); + CodeLine cl = buffer [currentLine]; + cl.Tokens = new List<Token> (); + + //retrieve current parser state from previous line + if (currentLine > 0) + curState = (States)buffer[currentLine - 1].EndingState; + else + curState = States.init; + + States previousEndingState = (States)cl.EndingState; + + + while (! eol) { + SkipWhiteSpaces (); + + if (eol) + break; + + if (Peek () == '\n') { + if (currentTok != TokenType.Unknown) + throw new ParserException (currentLine, currentColumn, "Unexpected end of line"); + Read (); + eol = true; + continue; + } + + if (curState == States.BlockComment) { + if (currentTok != TokenType.Unknown) + Debugger.Break (); + + currentTok.Start = CurrentPosition; + currentTok.Type = (BufferParser.TokenType)TokenType.BlockComment; + currentTok += ReadLineUntil ("-->"); + if (Peek (3) == "-->") { + readToCurrTok (3); + curState = States.XML; + } + saveAndResetCurrentTok (); + continue; + } + + switch (Peek()) { + case '<': + readToCurrTok (true); + switch (Peek()) { + case '?': + if (curState != States.init) + throw new ParserException (currentLine, currentColumn, "xml decl may appear only on first line"); + readToCurrTok (); + currentTok += ReadLineUntil ("?>"); + if (Peek (2) != "?>") + throw new ParserException (currentLine, currentColumn, "expecting '?>'"); + readToCurrTok (2); + saveAndResetCurrentTok (TokenType.XMLDecl); + curState = States.prolog; + break; + case '!': + readToCurrTok (); + switch (Peek()) { + case '-': + readToCurrTok (); + if (Peek () != '-') + throw new ParserException (currentLine, currentColumn, "Expecting comment start tag"); + readToCurrTok (); + currentTok += ReadLineUntil ("--"); + if (Peek (3) == "-->") { + readToCurrTok (3); + }else + curState = States.BlockComment; + saveAndResetCurrentTok (TokenType.BlockComment); + break; + case 'D': + case 'd': + string tmp = Read (7); + currentTok += tmp; + if (!string.Equals (tmp, "DOCTYPE", StringComparison.OrdinalIgnoreCase)) + throw new ParserException (currentLine, currentColumn, "Expecting 'doctype'"); + saveAndResetCurrentTok (TokenType.Doctype); + break; + default: + throw new ParserException (currentLine, currentColumn, "error"); + } + break; + default: + if (!(curState == States.Content || curState == States.XML || curState == States.init || curState == States.prolog)) + throw new ParserException (currentLine, currentColumn, "Unexpected char: '<'"); + if (Peek () == '/') { + curState = States.EndTag; + readToCurrTok (); + saveAndResetCurrentTok (TokenType.ElementEnd); + } else { + curState = States.StartTag; + saveAndResetCurrentTok (TokenType.ElementStart); + } + + if (!nextCharIsValidCharStartName) + throw new ParserException (currentLine, currentColumn, "Expected element name"); + + readToCurrTok (true); + while (nextCharIsValidCharName) + readToCurrTok (); + + saveAndResetCurrentTok (TokenType.ElementName); + break; + } + break; + case '/': + if (curState != States.StartTag) + throw new ParserException (currentLine, currentColumn, "Unexpected char: '/'"); + readToCurrTok (true); + if (Peek () != '>') + throw new ParserException (currentLine, currentColumn, "Expecting '>'"); + readAndResetCurrentTok (TokenType.ElementEnd); + + curState = States.XML; + break; + case '>': + readAndResetCurrentTok (TokenType.ElementClosing, true); + switch (curState) { + case States.EndTag: + curState = States.XML; + break; + case States.StartTag: + curState = States.Content; + break; + default: + throw new ParserException (currentLine, currentColumn, "Unexpected char: '>'"); + } + break; + default: + switch (curState) { + case States.StartTag: + if (!nextCharIsValidCharStartName) + throw new ParserException (currentLine, currentColumn, "Expected attribute name"); + readToCurrTok (true); + while (nextCharIsValidCharName) + readToCurrTok (); + saveAndResetCurrentTok (TokenType.AttributeName); + + SkipWhiteSpaces (); + + if (Peek () != '=') + throw new ParserException (currentLine, currentColumn, "Expecting: '='"); + readAndResetCurrentTok (TokenType.Affectation, true); + + SkipWhiteSpaces (); + + char openAttVal = Peek (); + if (openAttVal != '"' && openAttVal != '\'') + throw new ParserException (currentLine, currentColumn, "Expecting attribute value enclosed either in '\"' or in \"'\""); + readAndResetCurrentTok (TokenType.AttributeValueOpening, true); + + currentTok.Start = CurrentPosition; + currentTok.Content = ReadLineUntil (new string (new char[]{ openAttVal })); + saveAndResetCurrentTok (TokenType.AttributeValue); + + if (Peek () != openAttVal) + throw new ParserException (currentLine, currentColumn, string.Format ("Expecting {0}", openAttVal)); + readAndResetCurrentTok (TokenType.AttributeValueClosing, true); + break; + default: + throw new ParserException (currentLine, currentColumn, "unexpected char: " + Peek ()); + } + break; + } + } + + if (cl.EndingState != (int)curState && currentLine < buffer.LineCount - 1) + buffer [currentLine + 1].Tokens = null; + + cl.EndingState = (int)curState; + } + + public override void SyntaxAnalysis () + { + initSyntaxAnalysis (); + Node currentNode = RootNode; + + for (int i = 0; i < buffer.LineCount; i++) { + CodeLine cl = buffer[i]; + if (cl.Tokens == null) + continue; + cl.SyntacticNode = null; + + int tokPtr = 0; + while (tokPtr < cl.Tokens.Count) { + switch ((XMLParser.TokenType)cl.Tokens [tokPtr].Type) { + case TokenType.ElementStart: + tokPtr++; + currentNode = addChildNode (currentNode, cl, tokPtr, "Element"); + break; + case TokenType.ElementEnd: + tokPtr++; + if (tokPtr < cl.Tokens.Count) { + if ((XMLParser.TokenType)cl.Tokens [tokPtr].Type == TokenType.ElementName && + cl.Tokens [tokPtr].Content != currentNode.Name) + throw new ParserException (currentLine, currentColumn, "Closing tag mismatch"); + } + closeNodeAndGoUp (ref currentNode, cl, "Element"); + break; + case TokenType.ElementClosing: + //currentNode = currentNode.Parent; + break; + default: + break; + } + tokPtr++; + } + } + } + } +} + diff --git a/Samples/CrowIDE/src/Editors/Parsers2/Tokenizer.cs b/Samples/CrowIDE/src/Editors/Parsers2/Tokenizer.cs new file mode 100644 index 00000000..bcda1ec6 --- /dev/null +++ b/Samples/CrowIDE/src/Editors/Parsers2/Tokenizer.cs @@ -0,0 +1,67 @@ +// +// Tokenizer.cs +// +// Author: +// Jean-Philippe Bruyère <jp_bruyere@hotmail.com> +// +// Copyright (c) 2013-2017 Jean-Philippe Bruyère +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System; +using Crow.Text; +using System.Collections.Generic; +using System.Text.RegularExpressions; + +namespace Crow.Coding2 +{ + public static class TokenType { + public const int Undefine = 0; + public const int WhiteSpace = 0; + } + public class Token { + public int ptr; + public int length; + } + + public class Tokenizer + { + #region Regular Expression for validity checks + public Regex rxValidChar = new Regex(@"\p{Lu}|\p{Ll}|\p{Lt}|\p{Lm}|\p{Lo}|\p{Nl}|\p{Mn}|\p{Mc}|\p{Nd}|\p{Pc}|\p{Cf}"); + public Regex rxNameStartChar = new Regex(@"_|\p{Lu}|\p{Ll}|\p{Lt}|\p{Lm}|\p{Lo}|\p{Nl}"); + public Regex rxNameChar = new Regex(@"\p{Lu}|\p{Ll}|\p{Lt}|\p{Lm}|\p{Lo}|\p{Nl}|\p{Mn}|\p{Mc}|\p{Nd}|\p{Pc}|\p{Cf}"); + public Regex rxNewLineChar = new Regex(@"\u000D|\u000A|\u0085|\u2028|\u2029"); + public Regex rxWhiteSpaceChar = new Regex(@"\p{Zs}|\u0009|\u000B|\u000C"); + public Regex rxDecimal = new Regex(@"[0-9]+"); + public Regex rxHexadecimal = new Regex(@"[0-9a-fA-F]+"); + #endregion + + public List<Token> Tokens; + + public Tokenizer (TextBuffer buffer) + { + } + + + public void Tokenize () { + + } + + } +} + diff --git a/Samples/CrowIDE/src/Editors/SourceEditor.cs b/Samples/CrowIDE/src/Editors/SourceEditor.cs new file mode 100644 index 00000000..ca55ee09 --- /dev/null +++ b/Samples/CrowIDE/src/Editors/SourceEditor.cs @@ -0,0 +1,1209 @@ +// +// ScrollingTextBox.cs +// +// Author: +// Jean-Philippe Bruyère <jp.bruyere@hotmail.com> +// +// Copyright (c) 2013-2017 Jean-Philippe Bruyère +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System; +using System.ComponentModel; +using System.Collections.Generic; +using System.Diagnostics; +using vkvg; + +namespace Crow.Coding +{ + /// <summary> + /// Scrolling text box optimized for monospace fonts, for coding + /// </summary> + public class SourceEditor : Editor + { + #region CTOR + public SourceEditor (): base() + { + formatting.Add ((int)XMLParser.TokenType.AttributeName, new TextFormatting (Color.DarkSlateGrey, Color.Transparent)); + formatting.Add ((int)XMLParser.TokenType.ElementName, new TextFormatting (Color.DarkBlue, Color.Transparent)); + formatting.Add ((int)XMLParser.TokenType.ElementStart, new TextFormatting (Color.Black, Color.Transparent)); + formatting.Add ((int)XMLParser.TokenType.ElementEnd, new TextFormatting (Color.Black, Color.Transparent)); + formatting.Add ((int)XMLParser.TokenType.ElementClosing, new TextFormatting (Color.Black, Color.Transparent)); + + formatting.Add ((int)XMLParser.TokenType.AttributeValueOpening, new TextFormatting (Color.Crimson, Color.Transparent)); + formatting.Add ((int)XMLParser.TokenType.AttributeValueClosing, new TextFormatting (Color.Crimson, Color.Transparent)); + formatting.Add ((int)XMLParser.TokenType.AttributeValue, new TextFormatting (Color.FireBrick, Color.Transparent, false, true)); + formatting.Add ((int)XMLParser.TokenType.XMLDecl, new TextFormatting (Color.ForestGreen, Color.Transparent)); + + formatting.Add ((int)BufferParser.TokenType.BlockComment, new TextFormatting (Color.Grey, Color.Transparent, false, true)); + formatting.Add ((int)BufferParser.TokenType.LineComment, new TextFormatting (Color.Grey, Color.Transparent, false, true)); + formatting.Add ((int)BufferParser.TokenType.OperatorOrPunctuation, new TextFormatting (Color.Black, Color.Transparent)); + formatting.Add ((int)BufferParser.TokenType.Keyword, new TextFormatting (Color.Teal, Color.Transparent)); + //formatting.Add ((int)BufferParser.TokenType.Keyword, new TextFormatting (Color.DarkCyan, Color.Transparent)); + + parsing.Add (".crow", "Crow.Coding.XMLParser"); + parsing.Add (".svg", "Crow.Coding.XMLParser"); + parsing.Add (".template", "Crow.Coding.XMLParser"); + parsing.Add (".cs", "Crow.Coding.CSharpParser"); + parsing.Add (".style", "Crow.Coding.StyleParser"); + + buffer = new CodeBuffer (); + buffer.LineUpadateEvent += Buffer_LineUpadateEvent; + buffer.LineAdditionEvent += Buffer_LineAdditionEvent;; + buffer.LineRemoveEvent += Buffer_LineRemoveEvent; + buffer.BufferCleared += Buffer_BufferCleared; + buffer.SelectionChanged += Buffer_SelectionChanged; + buffer.PositionChanged += Buffer_PositionChanged; + buffer.FoldingEvent += Buffer_FoldingEvent; + buffer.Add (new CodeLine("")); + } + #endregion + + string oldSource = ""; + //save requested position on error, and try it on next move + int requestedLine = 0, requestedCol = 0; + volatile bool isDirty = false; + + const int leftMarginGap = 10;//gap between items in margin and text + const int foldSize = 9;//folding rectangles size + const int foldHSpace = 4;//folding level tabulation x + int foldMargin { get { return parser == null ? 0 : parser.SyntacticTreeMaxDepth * foldHSpace; }}//folding margin size + + #region private and protected fields + bool foldingEnabled = true; + int leftMargin = 0; //margin used to display line numbers, folding errors,etc... + int visibleLines = 1; + int visibleColumns = 1; + int firstPrintedLine = -1; + int printedCurrentLine = 0;//Index of the currentline in the PrintedLines array + + CodeBuffer buffer; + BufferParser parser; + List<CodeLine> PrintedLines;//list of lines visible in the Editor depending on scrolling and folding + + Dictionary<int, TextFormatting> formatting = new Dictionary<int, TextFormatting>(); + Dictionary<string, string> parsing = new Dictionary<string, string>(); + + Color selBackground; + Color selForeground; + int selStartCol; + int selEndCol; + + protected Rectangle rText; + protected FontExtents fe; + protected TextExtents te; + + Point mouseLocalPos; + bool doubleClicked = false; + #endregion + + void measureLeftMargin () { + leftMargin = 0; + if (PrintLineNumbers) + leftMargin += (int)Math.Ceiling((double)buffer.LineCount.ToString().Length * fe.MaxXAdvance) +6; + if (foldingEnabled) + leftMargin += foldMargin; + if (leftMargin > 0) + leftMargin += leftMarginGap; + updateVisibleColumns (); + } + void findLongestLineAndUpdateMaxScrollX() { + buffer.FindLongestVisualLine (); + updateMaxScrollX (); +// Debug.WriteLine ("SourceEditor: Find Longest line and update maxscrollx: {0} visible cols:{1}", MaxScrollX, visibleColumns); + } + /// <summary> + /// Updates visible line in widget, adapt max scroll y and updatePrintedLines + /// </summary> + void updateVisibleLines(){ + visibleLines = (int)Math.Floor ((double)ClientRectangle.Height / (fe.Ascent+fe.Descent)); + NotifyValueChanged ("VisibleLines", visibleLines); + updateMaxScrollY (); + updatePrintedLines (); + RegisterForGraphicUpdate (); +// System.Diagnostics.Debug.WriteLine ("update visible lines: " + visibleLines); +// System.Diagnostics.Debug.WriteLine ("update MaxScrollY: " + MaxScrollY); + } + void updateVisibleColumns(){ + visibleColumns = (int)Math.Floor ((double)(ClientRectangle.Width - leftMargin)/ fe.MaxXAdvance); + NotifyValueChanged ("VisibleColumns", visibleColumns); + updateMaxScrollX (); +// System.Diagnostics.Debug.WriteLine ("update visible columns: {0} leftMargin:{1}",visibleColumns, leftMargin); +// System.Diagnostics.Debug.WriteLine ("update MaxScrollX: " + MaxScrollX); + } + void updateMaxScrollX () { + MaxScrollX = Math.Max (0, buffer.longestLineCharCount - visibleColumns); + if (buffer.longestLineCharCount > 0) + NotifyValueChanged ("ChildWidthRatio", Slot.Width * visibleColumns / buffer.longestLineCharCount); + } + void updateMaxScrollY () { + if (parser == null || !foldingEnabled) { + MaxScrollY = Math.Max (0, buffer.LineCount - visibleLines); + if (buffer.UnfoldedLines > 0) + NotifyValueChanged ("ChildHeightRatio", Slot.Height * visibleLines / buffer.UnfoldedLines); + } else { + MaxScrollY = Math.Max (0, buffer.UnfoldedLines - visibleLines); + if (buffer.UnfoldedLines > 0) + NotifyValueChanged ("ChildHeightRatio", Slot.Height * visibleLines / buffer.UnfoldedLines); + } + } + void updatePrintedLines () { + buffer.editMutex.EnterReadLock (); + editorMutex.EnterWriteLock (); + + PrintedLines = new List<CodeLine> (); + int curL = 0; + int i = 0; + + while (curL < buffer.LineCount && i < ScrollY) { + if (buffer [curL].IsFolded) + curL = buffer.GetEndNodeIndex (curL); + curL++; + i++; + } + + firstPrintedLine = curL; + i = 0; + while (i < visibleLines && curL < buffer.LineCount) { + PrintedLines.Add (buffer [curL]); + + if (buffer [curL].IsFolded) + curL = buffer.GetEndNodeIndex (curL); + + curL++; + i++; + } + + buffer.editMutex.ExitReadLock (); + editorMutex.ExitWriteLock (); + } + void updateOnScreenCurLineFromBuffCurLine(){ + printedCurrentLine = PrintedLines.IndexOf (buffer.CurrentCodeLine); + } + void toogleFolding (int line) { + if (parser == null || !foldingEnabled) + return; + buffer.ToogleFolding (line); + } + + #region Editor overrides + protected override void updateEditorFromProjFile () + { + Debug.WriteLine("\t\tSourceEditor updateEditorFromProjFile"); + + //buffer.editMutex.EnterWriteLock (); + loadSource (); + //buffer.editMutex.ExitWriteLock (); + + isDirty = false; + oldSource = projFile.Source; + CurrentLine = requestedLine; + CurrentColumn = requestedCol; + projFile.RegisteredEditors [this] = true; + } + protected override void updateProjFileFromEditor () + { + Debug.WriteLine("\t\tSourceEditor updateProjFileFromEditor"); + + buffer.editMutex.EnterWriteLock (); + string newsrc = buffer.FullText; + buffer.editMutex.ExitWriteLock (); + projFile.UpdateSource (this, newsrc); + } + protected override bool EditorIsDirty { + get { return isDirty; } + set { isDirty = value; } + } + protected override bool IsReady { + get { return projFile != null && buffer != null; } + } + #endregion + + #region Buffer events handlers + void Buffer_BufferCleared (object sender, EventArgs e) + { + editorMutex.EnterWriteLock (); + + buffer.longestLineCharCount = 0; + buffer.longestLineIdx = 0; + measureLeftMargin (); + MaxScrollX = MaxScrollY = 0; + PrintedLines = null; + RegisterForGraphicUpdate (); + notifyPositionChanged (); + isDirty = true; + + editorMutex.ExitWriteLock (); + } + void Buffer_LineAdditionEvent (object sender, CodeBufferEventArgs e) + { + for (int i = 0; i < e.LineCount; i++) { + int lptr = e.LineStart + i; + int charCount = buffer[lptr].PrintableLength; + if (charCount > buffer.longestLineCharCount) { + buffer.longestLineIdx = lptr; + buffer.longestLineCharCount = charCount; + }else if (lptr <= buffer.longestLineIdx) + buffer.longestLineIdx++; + if (parser == null) + continue; + parser.TryParseBufferLine (e.LineStart + i); + } + + if (parser != null) + parser.reparseSource (); + + measureLeftMargin (); + + updatePrintedLines (); + updateMaxScrollY (); + RegisterForGraphicUpdate (); + notifyPositionChanged (); + isDirty = true; + } + void Buffer_LineRemoveEvent (object sender, CodeBufferEventArgs e) + { + bool trigFindLongestLine = false; + for (int i = 0; i < e.LineCount; i++) { + int lptr = e.LineStart + i; + if (lptr <= buffer.longestLineIdx) + trigFindLongestLine = true; + } + if (trigFindLongestLine) + findLongestLineAndUpdateMaxScrollX (); + + measureLeftMargin (); + updatePrintedLines (); + updateMaxScrollY (); + RegisterForGraphicUpdate (); + notifyPositionChanged (); + isDirty = true; + } + void Buffer_LineUpadateEvent (object sender, CodeBufferEventArgs e) + { + bool trigFindLongestLine = false; + for (int i = 0; i < e.LineCount; i++) { + + int lptr = e.LineStart + i; + if (lptr == buffer.longestLineIdx) + trigFindLongestLine = true; + else if (buffer[lptr].PrintableLength > buffer.longestLineCharCount) { + buffer.longestLineCharCount = buffer[lptr].PrintableLength; + buffer.longestLineIdx = lptr; + } + } + if (trigFindLongestLine) + findLongestLineAndUpdateMaxScrollX (); + + RegisterForGraphicUpdate (); + notifyPositionChanged (); + isDirty = true; + } + void Buffer_PositionChanged (object sender, EventArgs e) + { + //Console.WriteLine ("Position changes: ({0},{1})", buffer.CurrentLine, buffer.CurrentColumn); + int cc = buffer.CurrentTabulatedColumn; + + if (cc > visibleColumns + ScrollX) { + ScrollX = cc - visibleColumns; + } else if (cc < ScrollX) + ScrollX = cc; + + RegisterForGraphicUpdate (); + updateOnScreenCurLineFromBuffCurLine (); + notifyPositionChanged (); + } + + void Buffer_SelectionChanged (object sender, EventArgs e) + { + RegisterForGraphicUpdate (); + } + void Buffer_FoldingEvent (object sender, CodeBufferEventArgs e) + { + updatePrintedLines (); + updateOnScreenCurLineFromBuffCurLine (); + updateMaxScrollY (); + RegisterForGraphicUpdate (); + } + #endregion + + void notifyPositionChanged (){ + try { + NotifyValueChanged ("CurrentLine", buffer.CurrentLine+1); + NotifyValueChanged ("CurrentColumn", buffer.CurrentColumn+1); + NotifyValueChanged ("CurrentLineHasError", CurrentLineHasError); + NotifyValueChanged ("CurrentLineError", CurrentLineError); + } catch (Exception ex) { + Console.WriteLine (ex.ToString ()); + } + } + + #region Public Crow Properties + public int CurrentLine{ + get { return buffer == null ? 0 : buffer.CurrentLine+1; } + set { + try { + int l = value - 1; + if (l == buffer.CurrentLine) + return; + buffer.CurrentLine = l; + l = buffer.CurrentLine; //reaffect from buffer where bound check is made + if ((bool)buffer [l]?.IsFolded) + buffer.ToogleFolding (l); + } catch (Exception ex) { + requestedLine = value - 1; + Console.WriteLine ("Error cur column: " + ex); + } + } + } + public int CurrentColumn{ + get { return buffer == null ? 0 : buffer.CurrentColumn+1; } + set { + try { + if (value - 1 == buffer.CurrentColumn) + return; + buffer.CurrentColumn = value - 1; + } catch (Exception ex) { + requestedCol = value - 1; + Console.WriteLine ("Error cur column: " + ex.ToString ()); + } + } + } + public bool PrintLineNumbers + { + get { return Configuration.Global.Get<bool> ("PrintLineNumbers"); } + set { + if (PrintLineNumbers == value) + return; + Configuration.Global.Set ("PrintLineNumbers", value); + NotifyValueChanged ("PrintLineNumbers", PrintLineNumbers); + measureLeftMargin (); + RegisterForGraphicUpdate (); + } + } + [DefaultValue("SteelBlue")] + public virtual Color SelectionBackground { + get { return selBackground; } + set { + if (value == selBackground) + return; + selBackground = value; + NotifyValueChanged ("SelectionBackground", selBackground); + RegisterForRedraw (); + } + } + [DefaultValue("White")] + public virtual Color SelectionForeground { + get { return selForeground; } + set { + if (value == selForeground) + return; + selForeground = value; + NotifyValueChanged ("SelectionForeground", selForeground); + RegisterForRedraw (); + } + } + public override int ScrollY { + get { + return base.ScrollY; + } + set { + if (value == base.ScrollY) + return; + base.ScrollY = value; + updatePrintedLines (); + updateOnScreenCurLineFromBuffCurLine (); + RegisterForGraphicUpdate (); + } + } + public ParserException CurrentLineError { + get { return buffer?.CurrentCodeLine?.exception; } + } + public bool CurrentLineHasError { + get { return buffer == null ? false : buffer.CurrentCodeLine == null ? false : + buffer.CurrentCodeLine.exception != null; } + } + public override ProjectFile ProjectNode { + get { + return base.ProjectNode; + } + set { + base.ProjectNode = value; + if (projFile != null) + parser = getParserFromExt (System.IO.Path.GetExtension (projFile.Extension)); + } + } + #endregion + + BufferParser getParserFromExt (string extension) { + if (string.IsNullOrEmpty(extension)) + return null; + if (!parsing.ContainsKey(extension)) + return null; + Type parserType = Type.GetType (parsing [extension]); + if (parserType == null) + return null; + return (BufferParser)Activator.CreateInstance (parserType, buffer ); + } + void loadSource () { + + try { + + if (parser == null) + buffer.Load (projFile.Source); + else//parser may have special linebrk rules + buffer.Load (projFile.Source, parser.LineBrkRegex); + + } catch (Exception ex) { + Debug.WriteLine (ex.ToString ()); + } + + projFile.RegisteredEditors [this] = true; + + updateMaxScrollY (); + MaxScrollX = Math.Max (0, buffer.longestLineCharCount - visibleColumns); + updatePrintedLines (); + + RegisterForGraphicUpdate (); + } + + /// <summary> + /// Current editor line, when set, update buffer.CurrentLine + /// </summary> + int PrintedCurrentLine { + get { return printedCurrentLine;} + set { + if (value < 0) { + ScrollY += value; + printedCurrentLine = 0; + } else if (PrintedLines.Count < visibleLines && value >= PrintedLines.Count) { + printedCurrentLine = PrintedLines.Count - 1; + }else if (value >= visibleLines) { + ScrollY += value - visibleLines + 1; + printedCurrentLine = visibleLines - 1; + }else + printedCurrentLine = value; + //Debug.WriteLine ("printed current line:" + printedCurrentLine.ToString ()); + //update position in buffer + buffer.CurrentLine = buffer.IndexOf (PrintedLines[printedCurrentLine]); + } + } + int getTabulatedColumn (int col, int line) { + return buffer [line].Content.Substring (0, col).Replace ("\t", new String (' ', Interface.TabSize)).Length; + } + int getTabulatedColumn (Point pos) { + return getTabulatedColumn (pos.X,pos.Y); + } + /// <summary> + /// Moves cursor one char to the left, move up if cursor reaches start of line + /// </summary> + /// <returns><c>true</c> if move succeed</returns> + public bool MoveLeft(){ + if (buffer.CurrentColumn == 0) { + if (printedCurrentLine == 0) + return false; + PrintedCurrentLine--; + buffer.CurrentColumn = int.MaxValue; + } else + buffer.CurrentColumn--; + return true; + } + /// <summary> + /// Moves cursor one char to the right, move down if cursor reaches end of line + /// </summary> + /// <returns><c>true</c> if move succeed</returns> + public bool MoveRight(){ + if (buffer.CurrentColumn >= buffer.CurrentCodeLine.Length) { + if (PrintedCurrentLine == buffer.UnfoldedLines - 1) + return false; + buffer.CurrentColumn = 0; + PrintedCurrentLine++; + } else + buffer.CurrentColumn++; + return true; + } + + #region Drawing + void drawLine(Context gr, Rectangle cb, int i) { + CodeLine cl = PrintedLines[i]; + int lineIndex = buffer.IndexOf(cl); + + double y = cb.Y + (fe.Ascent+fe.Descent) * i, x = cb.X; + + //Draw line numbering + Color mgFg = Color.Grey; + Color mgBg = Color.White; + if (PrintLineNumbers){ + Rectangle mgR = new Rectangle ((int)x, (int)y, leftMargin - leftMarginGap, (int)Math.Ceiling((fe.Ascent+fe.Descent))); + if (cl.exception != null) { + mgBg = Color.Red; + if (buffer.CurrentLine == lineIndex) + mgFg = Color.White; + else + mgFg = Color.LightGrey; + }else if (buffer.CurrentLine == lineIndex && HasFocus) { + mgFg = Color.Black; + mgBg = Color.DarkGrey; + } + string strLN = (lineIndex+1).ToString (); + gr.SetSourceColor (mgBg); + gr.Rectangle (mgR); + gr.Fill(); + gr.SetSourceColor (mgFg); + + gr.MoveTo (cb.X + (int)(gr.TextExtents (buffer.LineCount.ToString()).Width - gr.TextExtents (strLN).Width), y + fe.Ascent); + gr.ShowText (strLN); + gr.Fill (); + } + + + + //draw folding + if (foldingEnabled){ + + Rectangle rFld = new Rectangle (cb.X + leftMargin - leftMarginGap - foldMargin, + (int)(y + (fe.Ascent + fe.Descent) / 2.0 - foldSize / 2.0), foldSize, foldSize); + + gr.SetSourceColor (Color.Black); + gr.LineWidth = 1.0; + + int level = 0; + bool closingNode = false; + + if (currentNode != null) { + if (cl == currentNode.EndLine) { + currentNode = currentNode.Parent; + closingNode = true; + } + if (currentNode != null) + level = currentNode.Level - 1; + } + + for (int l = 0; l < level; l++) { + gr.MoveTo (rFld.Center.X + 0.5, y); + gr.LineTo (rFld.Center.X + 0.5, y + fe.Ascent + fe.Descent); + rFld.Left += foldHSpace; + } + if (closingNode) { + gr.MoveTo (rFld.Center.X + 0.5, y); + gr.LineTo (rFld.Center.X + 0.5, y + fe.Ascent / 2 + 0.5); + gr.LineTo (rFld.Center.X + 0.5 + foldSize / 2, y + fe.Ascent / 2 + 0.5); + closingNode = false; + } + //gr.SetDash (new double[]{ 1.5 },0.0); + gr.SetSourceColor (Color.Grey); + gr.Stroke (); + //gr.SetDash (new double[]{}, 0.0); + + if (cl.IsFoldable) { + gr.Rectangle (rFld); + gr.SetSourceColor (Color.White); + gr.Fill(); + gr.SetSourceColor (Color.Black); + gr.Rectangle (rFld, 1.0); + if (cl.IsFolded) { + gr.MoveTo (rFld.Center.X + 0.5, rFld.Y + 2); + gr.LineTo (rFld.Center.X + 0.5, rFld.Bottom - 2); + }else + currentNode = cl.SyntacticNode; + + gr.MoveTo (rFld.Left + 2, rFld.Center.Y + 0.5); + gr.LineTo (rFld.Right - 2, rFld.Center.Y + 0.5); + gr.Stroke (); + } + } + + gr.SetSourceColor (Foreground); + x += leftMargin; + + if (cl.Tokens == null) + drawRawCodeLine (gr, x, y, i, lineIndex); + else + drawParsedCodeLine (gr, x, y, i, lineIndex); + } + Node currentNode = null; +// void drawParsed(Context gr){ +// if (PrintedLines == null) +// return; +// +// gr.SelectFontFace (Font.Name, Font.Slant, Font.Wheight); +// gr.SetFontSize (Font.Size); +// gr.FontOptions = Interface.FontRenderingOptions; +// gr.Antialias = Interface.Antialias; +// +// Rectangle cb = ClientRectangle; +// gr.Save (); +// CairoHelpers.CairoRectangle (gr, cb, CornerRadius); +// gr.Clip (); +// +// bool selectionInProgress = false; +// +// Foreground.SetAsSource (gr); +// +// #region draw text cursor +// if (SelBegin != SelRelease) +// selectionInProgress = true; +// else if (HasFocus){ +// gr.LineWidth = 1.0; +// double cursorX = + leftMargin + cb.X + (CurrentColumn - ScrollX) * fe.MaxXAdvance; +// gr.MoveTo (0.5 + cursorX, cb.Y + printedCurrentLine * (fe.Ascent+fe.Descent)); +// gr.LineTo (0.5 + cursorX, cb.Y + (printedCurrentLine + 1) * (fe.Ascent+fe.Descent)); +// gr.Stroke(); +// } +// #endregion +// +// for (int i = 0; i < PrintedLines.Count; i++) +// drawTokenLine (gr, i, selectionInProgress, cb); +// +// gr.Restore (); +// } + void drawRawCodeLine(Context gr, double x, double y, int i, int lineIndex) { + string lstr = buffer[lineIndex].PrintableContent; + if (ScrollX < lstr.Length) + lstr = lstr.Substring (ScrollX); + else + lstr = ""; + + gr.MoveTo (x, y + fe.Ascent); + gr.ShowText (lstr); + gr.Fill (); + + if (!buffer.SelectionIsEmpty && lineIndex >= buffer.SelectionStart.Y && lineIndex <= buffer.SelectionEnd.Y) { + double rLineX = x, + rLineY = y, + rLineW = lstr.Length * fe.MaxXAdvance; + + //System.Diagnostics.Debug.WriteLine ("sel start: " + buffer.SelectionStart + " sel end: " + buffer.SelectionEnd); + if (lineIndex == buffer.SelectionStart.Y) { + rLineX += (selStartCol - ScrollX) * fe.MaxXAdvance; + rLineW -= selStartCol * fe.MaxXAdvance; + } + if (lineIndex == buffer.SelectionEnd.Y) + rLineW -= (lstr.Length - selEndCol) * fe.MaxXAdvance; + + gr.Save (); + gr.Operator = Operator.Source; + gr.Rectangle (rLineX, rLineY, rLineW, (fe.Ascent+fe.Descent)); + gr.SetSourceColor (SelectionBackground); + gr.FillPreserve (); + gr.Clip (); + gr.Operator = Operator.Over; + gr.SetSourceColor (SelectionForeground); + gr.MoveTo (x, y + fe.Ascent); + gr.ShowText (lstr); + gr.Fill (); + gr.Restore (); + } + } + void drawParsedCodeLine (Context gr, double x, double y, int i, int lineIndex) { + int lPtr = 0; + CodeLine cl = PrintedLines[i]; + + for (int t = 0; t < cl.Tokens.Count; t++) { + string lstr = cl.Tokens [t].PrintableContent; + if (lPtr < ScrollX) { + if (lPtr - ScrollX + lstr.Length <= 0) { + lPtr += lstr.Length; + continue; + } + lstr = lstr.Substring (ScrollX - lPtr); + lPtr += ScrollX - lPtr; + } + Color bg = this.Background; + Color fg = this.Foreground; + Color selbg = this.SelectionBackground; + Color selfg = this.SelectionForeground; + FontSlant fts = FontSlant.Normal; + FontWeight ftw = FontWeight.Normal; + + if (formatting.ContainsKey ((int)cl.Tokens [t].Type)) { + TextFormatting tf = formatting [(int)cl.Tokens [t].Type]; + bg = tf.Background; + fg = tf.Foreground; + if (tf.Bold) + ftw = FontWeight.Bold; + if (tf.Italic) + fts = FontSlant.Italic; + } + + gr.FontFace = Font.Name; + gr.SetSourceColor (fg); + + gr.MoveTo (x, y + fe.Ascent); + gr.ShowText (lstr); + gr.Fill (); + + if (buffer.SelectionInProgress && lineIndex >= buffer.SelectionStart.Y && lineIndex <= buffer.SelectionEnd.Y && + !(lineIndex == buffer.SelectionStart.Y && lPtr + lstr.Length <= selStartCol) && + !(lineIndex == buffer.SelectionEnd.Y && selEndCol <= lPtr)) { + + double rLineX = x, + rLineY = y, + rLineW = lstr.Length * fe.MaxXAdvance; + double startAdjust = 0.0; + + if ((lineIndex == buffer.SelectionStart.Y) && (selStartCol < lPtr + lstr.Length) && (selStartCol > lPtr)) + startAdjust = (selStartCol - lPtr) * fe.MaxXAdvance; + rLineX += startAdjust; + if ((lineIndex == buffer.SelectionEnd.Y) && (selEndCol < lPtr + lstr.Length)) + rLineW = (selEndCol - lPtr) * fe.MaxXAdvance; + rLineW -= startAdjust; + + gr.Save (); + gr.Operator = Operator.Source; + gr.Rectangle (rLineX, rLineY, rLineW, (fe.Ascent+fe.Descent)); + gr.SetSourceColor (selbg); + gr.FillPreserve (); + gr.Clip (); + gr.Operator = Operator.Over; + gr.SetSourceColor (selfg); + gr.MoveTo (x, y + fe.Ascent); + gr.ShowText (lstr); + gr.Fill (); + gr.Restore (); + } + x += (int)lstr.Length * fe.MaxXAdvance; + lPtr += lstr.Length; + } + } + + #endregion + + #region GraphicObject overrides + public override Font Font { + get { return base.Font; } + set { + base.Font = value; + /* + using (ImageSurface img = new ImageSurface (Format.Argb32, 1, 1)) { + using (Context gr = new Context (img)) { + gr.SelectFontFace (Font.Name, Font.Slant, Font.Wheight); + gr.SetFontSize (Font.Size); + + fe = gr.FontExtents; + } + }*/ + MaxScrollY = 0; + RegisterForGraphicUpdate (); + } + } + protected override int measureRawSize(LayoutingType lt) + { + if (lt == LayoutingType.Height) + return (int)Math.Ceiling((fe.Ascent+fe.Descent) * buffer.LineCount) + Margin * 2; + + return (int)(fe.MaxXAdvance * buffer.longestLineCharCount) + Margin * 2 + leftMargin; + } + public override void OnLayoutChanges (LayoutingType layoutType) + { + base.OnLayoutChanges (layoutType); + + if (layoutType == LayoutingType.Height) + updateVisibleLines (); + else if (layoutType == LayoutingType.Width) + updateVisibleColumns (); + } + + protected override void onDraw (Context gr) + { + base.onDraw (gr); + + gr.FontFace = Font.Name; + gr.FontSize = (uint)Font.Size; + + Rectangle cb = ClientRectangle; + + Foreground.SetAsSource (gr); + + buffer.editMutex.EnterReadLock (); + editorMutex.EnterReadLock (); + + #region draw text cursor + if (buffer.SelectionInProgress){ + selStartCol = getTabulatedColumn (buffer.SelectionStart); + selEndCol = getTabulatedColumn (buffer.SelectionEnd); + }else if (HasFocus && printedCurrentLine >= 0){ + gr.LineWidth = 1.0; + double cursorX = cb.X + (getTabulatedColumn(buffer.CurrentPosition) - ScrollX) * fe.MaxXAdvance + leftMargin; + gr.MoveTo (0.5 + cursorX, cb.Y + (printedCurrentLine) * (fe.Ascent+fe.Descent)); + gr.LineTo (0.5 + cursorX, cb.Y + (printedCurrentLine + 1) * (fe.Ascent+fe.Descent)); + gr.Stroke(); + } + #endregion + + if (PrintedLines?.Count > 0) { + int unfoldedLines = buffer.UnfoldedLines; + currentNode = null; + CodeLine cl = PrintedLines[0]; + int idx0 = buffer.IndexOf(cl); + int li = idx0-1; + while (li >= 0) { + if (buffer [li].IsFoldable && !buffer [li].IsFolded) { + if (buffer.IndexOf(buffer [li].SyntacticNode.EndLine) > idx0){ + currentNode = buffer [li].SyntacticNode; + break; + } + } + li--; + } + + for (int i = 0; i < visibleLines; i++) { + if (i + ScrollY >= unfoldedLines)//TODO:need optimize + break; + drawLine (gr, cb, i); + } + } + + editorMutex.ExitReadLock (); + + buffer.editMutex.ExitReadLock (); + + } + #endregion + + #region Mouse handling + + int hoverLine = -1; + public int HoverLine { + get { return hoverLine; } + set { + if (hoverLine == value) + return; + hoverLine = value; + NotifyValueChanged ("HoverLine", hoverLine); + NotifyValueChanged ("HoverError", buffer [hoverLine].exception); + } + } + void updateHoverLine () { + if (PrintedLines.Count > 0) { + int hvl = (int)Math.Max (0, Math.Floor (mouseLocalPos.Y / (fe.Ascent + fe.Descent))); + hvl = Math.Min (PrintedLines.Count - 1, hvl); + HoverLine = buffer.IndexOf (PrintedLines [hvl]); + } else + HoverLine = 0; + } + void updateCurrentPosFromMouseLocalPos(){ + PrintedCurrentLine = (int)Math.Max (0, Math.Floor (mouseLocalPos.Y / (fe.Ascent+fe.Descent))); + int curVisualCol = ScrollX + (int)Math.Round ((mouseLocalPos.X - leftMargin) / fe.MaxXAdvance); + + int i = 0; + int buffCol = 0; + while (i < curVisualCol && buffCol < buffer.CurrentCodeLine.Length) { + if (buffer.CurrentCodeLine[buffCol] == '\t') + i += Interface.TabSize; + else + i++; + buffCol++; + } + buffer.CurrentColumn = buffCol; + } + public override void onMouseEnter (object sender, MouseMoveEventArgs e) + { + base.onMouseEnter (sender, e); + if (e.X - ScreenCoordinates(Slot).X < leftMargin + ClientRectangle.X) + IFace.MouseCursor = MouseCursor.Arrow; + else + IFace.MouseCursor = MouseCursor.IBeam; + } + public override void onMouseLeave (object sender, MouseMoveEventArgs e) + { + base.onMouseLeave (sender, e); + IFace.MouseCursor = MouseCursor.Arrow; + } + public override void onMouseMove (object sender, MouseMoveEventArgs e) + { + base.onMouseMove (sender, e); + + mouseLocalPos = e.Position - ScreenCoordinates(Slot).TopLeft - ClientRectangle.TopLeft; + + updateHoverLine (); + + if (!e.Mouse.IsButtonDown (MouseButton.Left)) { + if (mouseLocalPos.X < leftMargin) + IFace.MouseCursor = MouseCursor.Arrow; + else + IFace.MouseCursor = MouseCursor.IBeam; + return; + } + + if (!HasFocus || !buffer.SelectionInProgress) + return; + + //mouse is down + updateCurrentPosFromMouseLocalPos(); + buffer.SetSelEndPos (); + } + public override void onMouseDown (object sender, MouseButtonEventArgs e) + { + if (!this.Focusable) + return; + + if (mouseLocalPos.X >= leftMargin) + base.onMouseDown (sender, e); + + if (doubleClicked) { + doubleClicked = false; + return; + } + + if (mouseLocalPos.X < leftMargin) { + toogleFolding (buffer.IndexOf (PrintedLines [(int)Math.Max (0, Math.Floor (mouseLocalPos.Y / (fe.Ascent+fe.Descent)))])); + return; + } + + updateCurrentPosFromMouseLocalPos (); + buffer.SetSelStartPos (); + } + public override void onMouseUp (object sender, MouseButtonEventArgs e) + { + base.onMouseUp (sender, e); + + if (buffer.SelectionIsEmpty) + buffer.ResetSelection (); + } + + public override void onMouseDoubleClick (object sender, MouseButtonEventArgs e) + { + doubleClicked = true; + base.onMouseDoubleClick (sender, e); + + buffer.GotoWordStart (); + buffer.SetSelStartPos (); + buffer.GotoWordEnd (); + buffer.SetSelEndPos (); + } + public void MakeSelection (int lineStart, int colStart, int lineEnd, int colEnd) { + buffer.CurrentLine = lineStart; + buffer.CurrentColumn = colStart; + buffer.SetSelStartPos (); + buffer.CurrentLine = lineEnd; + buffer.CurrentColumn = colEnd; + buffer.SetSelEndPos (); + } + public override void onMouseWheel (object sender, MouseWheelEventArgs e) + { + base.onMouseWheel (sender, e); + } + #endregion + + #region Keyboard handling + public override void onKeyDown (object sender, KeyEventArgs e) + { + //base.onKeyDown (sender, e); + + Key key = e.Key; + + if (IFace.Ctrl) { + switch (key) { + case Key.S: + projFile.Save (); + break; + case Key.W: + editorMutex.EnterWriteLock (); + if (IFace.Shift) + projFile.Redo (null); + else + projFile.Undo (null); + editorMutex.ExitWriteLock (); + break; + default: + Console.WriteLine (""); + break; + } + } + + switch (key) + { + case Key.Backspace: + buffer.DeleteChar (); + break; + case Key.Delete: + if (buffer.SelectionIsEmpty) + MoveRight (); + else if (IFace.Shift) + IFace.Clipboard = buffer.SelectedText; + buffer.DeleteChar (); + break; + case Key.Enter: + case Key.KeypadEnter: + if (!buffer.SelectionIsEmpty) + buffer.DeleteChar (); + buffer.InsertLineBreak (); + break; + case Key.Escape: + buffer.ResetSelection (); + break; + case Key.Home: + if (IFace.Shift) { + if (buffer.SelectionIsEmpty) + buffer.SetSelStartPos (); + if (IFace.Ctrl) + buffer.CurrentLine = 0; + buffer.CurrentColumn = 0; + buffer.SetSelEndPos (); + break; + } + buffer.ResetSelection (); + if (IFace.Ctrl) + buffer.CurrentLine = 0; + buffer.CurrentColumn = 0; + break; + case Key.End: + if (IFace.Shift) { + if (buffer.SelectionIsEmpty) + buffer.SetSelStartPos (); + if (IFace.Ctrl) + buffer.CurrentLine = int.MaxValue; + buffer.CurrentColumn = int.MaxValue; + buffer.SetSelEndPos (); + break; + } + buffer.ResetSelection (); + if (IFace.Ctrl) + buffer.CurrentLine = int.MaxValue; + buffer.CurrentColumn = int.MaxValue; + break; + case Key.Insert: + if (IFace.Shift) + buffer.Insert (IFace.Clipboard); + else if (IFace.Ctrl && !buffer.SelectionIsEmpty) + IFace.Clipboard = buffer.SelectedText; + break; + case Key.Left: + if (IFace.Shift) { + if (buffer.SelectionIsEmpty) + buffer.SetSelStartPos (); + if (IFace.Ctrl) + buffer.GotoWordStart (); + else + MoveLeft (); + buffer.SetSelEndPos (); + break; + } + buffer.ResetSelection (); + if (IFace.Ctrl) + buffer.GotoWordStart (); + else + MoveLeft(); + break; + case Key.Right: + if (IFace.Shift) { + if (buffer.SelectionIsEmpty) + buffer.SetSelStartPos (); + if (IFace.Ctrl) + buffer.GotoWordEnd (); + else + MoveRight (); + buffer.SetSelEndPos (); + break; + } + buffer.ResetSelection (); + if (IFace.Ctrl) + buffer.GotoWordEnd (); + else + MoveRight (); + break; + case Key.Up: + if (IFace.Shift) { + if (buffer.SelectionIsEmpty) + buffer.SetSelStartPos (); + PrintedCurrentLine--; + buffer.SetSelEndPos (); + break; + } + buffer.ResetSelection (); + PrintedCurrentLine--; + break; + case Key.Down: + if (IFace.Shift) { + if (buffer.SelectionIsEmpty) + buffer.SetSelStartPos (); + PrintedCurrentLine++; + buffer.SetSelEndPos (); + break; + } + buffer.ResetSelection (); + PrintedCurrentLine++; + break; + case Key.Menu: + break; + case Key.NumLock: + break; + case Key.PageDown: + if (IFace.Shift) { + if (buffer.SelectionIsEmpty) + buffer.SetSelStartPos (); + PrintedCurrentLine += visibleLines; + buffer.SetSelEndPos (); + break; + } + buffer.ResetSelection (); + PrintedCurrentLine += visibleLines; + break; + case Key.PageUp: + if (IFace.Shift) { + if (buffer.SelectionIsEmpty) + buffer.SetSelStartPos (); + PrintedCurrentLine -= visibleLines; + buffer.SetSelEndPos (); + break; + } + buffer.ResetSelection (); + PrintedCurrentLine -= visibleLines; + break; + case Key.Tab: + if (IFace.Shift) { + if (buffer.SelectionIsEmpty || + (buffer.SelectionStart.Y == buffer.SelectionEnd.Y)) { + //TODO + break; + } + for (int i = buffer.SelectionStart.Y; i <= buffer.SelectionEnd.Y; i++) + buffer.RemoveLeadingTab (i); + buffer.SetSelectionOnFullLines (); + } else { + if (buffer.SelectionIsEmpty || + (buffer.SelectionStart.Y == buffer.SelectionEnd.Y)) { + buffer.Insert ("\t"); + break; + } + for (int i = buffer.SelectionStart.Y; i <= buffer.SelectionEnd.Y; i++) { + buffer.UpdateLine (i, "\t" + buffer [i].Content); + } + } + + break; + case Key.F8: + toogleFolding (buffer.CurrentLine); + break; + default: + break; + } + RegisterForGraphicUpdate(); + } + public override void onKeyPress (object sender, KeyPressEventArgs e) + { + base.onKeyPress (sender, e); + + buffer.Insert (e.KeyChar.ToString()); + buffer.ResetSelection (); + } + #endregion + } +}
\ No newline at end of file diff --git a/Samples/CrowIDE/src/Editors/SvgEditor.cs b/Samples/CrowIDE/src/Editors/SvgEditor.cs new file mode 100644 index 00000000..d81b1154 --- /dev/null +++ b/Samples/CrowIDE/src/Editors/SvgEditor.cs @@ -0,0 +1,140 @@ +// +// SvgEditor.cs +// +// Author: +// Jean-Philippe Bruyère <jp_bruyere@hotmail.com> +// +// Copyright (c) 2013-2017 Jean-Philippe Bruyère +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System; +using System.ComponentModel; +using vkvg; + +namespace Crow.Coding +{ + public class SvgEditor : Editor + { + SvgPicture _pic = new SvgPicture(); + + int zoom; + + [DefaultValue(100)] + public int Zoom { + get { return zoom; } + set { + if (zoom == value) + return; + zoom = value; + NotifyValueChanged ("Zoom", zoom); + updateMaxScrolls (); + RegisterForGraphicUpdate (); + } + } + + void updateMaxScrolls() { + MaxScrollX = Math.Max(0, _pic.Dimensions.Width * zoom / 100 - Slot.Width); + MaxScrollY = Math.Max(0, _pic.Dimensions.Height * zoom / 100 - Slot.Height); + + if (Slot.Width + MaxScrollX > 0) + NotifyValueChanged ("ChildWidthRatio", Slot.Width * Slot.Width / (Slot.Width + MaxScrollX)); + else + NotifyValueChanged ("ChildWidthRatio", 0); + + if (Slot.Height + MaxScrollY > 0) + NotifyValueChanged ("ChildHeightRatio", Slot.Height * Slot.Height / (Slot.Height + MaxScrollY)); + else + NotifyValueChanged ("ChildHeightRatio", 0); + } + #region editor overrides + protected override void updateEditorFromProjFile () + { + Error = null; + try { + editorMutex.EnterWriteLock(); + _pic.LoadSvgFragment (projFile.Source); + _pic.Scaled = true; + _pic.KeepProportions = true; + } catch (Exception ex) { + Error = ex; + } + editorMutex.ExitWriteLock (); + updateMaxScrolls (); + RegisterForGraphicUpdate (); + } + protected override void updateProjFileFromEditor () + { + throw new NotImplementedException (); + } + protected override bool EditorIsDirty { + get { return false; } + set { + throw new NotImplementedException (); + } + } + protected override bool IsReady { + get { return projFile != null; } + } + #endregion + + #region GraphicObject overrides + protected override int measureRawSize (LayoutingType lt) + { + if (_pic == null) + return 2 * Margin; + //_pic = "#Crow.Images.Icons.IconAlerte.svg"; + //TODO:take scalling in account + if (lt == LayoutingType.Width) + return _pic.Dimensions.Width + 2 * Margin; + else + return _pic.Dimensions.Height + 2 * Margin; + } + protected override void onDraw (Context gr) + { + base.onDraw (gr); + + Rectangle r = ClientRectangle; + Foreground.SetAsSource (gr, r); + gr.Rectangle (r, 0.1); + gr.Stroke (); + + r.Width = _pic.Dimensions.Width * zoom / 100; + r.Height = _pic.Dimensions.Height * zoom / 100; + + gr.Save (); + + editorMutex.EnterReadLock (); + + gr.Translate (-ScrollX, -ScrollY); + if (_pic != null) + _pic.Paint (gr, r); + editorMutex.ExitReadLock (); + + gr.Restore (); + } + public override void OnLayoutChanges (LayoutingType layoutType) + { + base.OnLayoutChanges (layoutType); + if ((layoutType | LayoutingType.Sizing) > 0) + updateMaxScrolls (); + } + #endregion + } +} + |