From: Jean-Philippe Bruyère Date: Mon, 28 Aug 2017 09:42:31 +0000 (+0200) Subject: SourceEditor wip X-Git-Url: https://git.osiis.dedyn.io/?a=commitdiff_plain;h=6b06e18698312259869f11c19a6b5bedcb3e1fae;p=jp%2Fcrowedit.git SourceEditor wip --- diff --git a/Crow.dll.config b/Crow.dll.config new file mode 100644 index 0000000..ef7562a --- /dev/null +++ b/Crow.dll.config @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/CrowEdit.csproj b/CrowEdit.csproj index b34a2c4..e3340b4 100644 --- a/CrowEdit.csproj +++ b/CrowEdit.csproj @@ -61,25 +61,23 @@ - - gtk-sharp-3.0 - - packages\Crow.OpenTK.0.5.1\lib\net45\Crow.dll + packages\Crow.OpenTK.0.5.4\lib\net45\Crow.dll + + + + + + + - - - - - - - + @@ -104,4 +102,4 @@ - \ No newline at end of file + diff --git a/CrowWindow.cs b/CrowWindow.cs new file mode 100644 index 0000000..e66fa7b --- /dev/null +++ b/CrowWindow.cs @@ -0,0 +1,371 @@ +// +// CrowWindow.cs +// +// Author: +// Jean-Philippe Bruyère +// +// 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 OpenTK; +using OpenTK.Graphics.OpenGL; +using System.Collections.Generic; + +namespace Crow +{ + public class CrowWindow : GameWindow, IValueChange + { + #region IValueChange implementation + public event EventHandler ValueChanged; + public virtual void NotifyValueChanged(string MemberName, object _value) + { + if (ValueChanged != null) + ValueChanged.Invoke(this, new ValueChangeEventArgs(MemberName, _value)); + } + #endregion + + #region FPS + int frameCpt = 0; + int _fps = 0; + + public int fps { + get { return _fps; } + set { + if (_fps == value) + return; + + _fps = value; + #if MEASURE_TIME + if (_fps > fpsMax) { + fpsMax = _fps; + ValueChanged.Raise(this, new ValueChangeEventArgs ("fpsMax", fpsMax)); + } else if (_fps < fpsMin) { + fpsMin = _fps; + ValueChanged.Raise(this, new ValueChangeEventArgs ("fpsMin", fpsMin)); + } + #endif + if (frameCpt % 20 == 0) { + ValueChanged.Raise (this, new ValueChangeEventArgs ("fps", _fps)); + #if MEASURE_TIME + foreach (PerformanceMeasure m in ifaceControl[0].PerfMeasures) + m.NotifyChanges (); + #endif + } + } + } + + #if MEASURE_TIME + public PerformanceMeasure glDrawMeasure = new PerformanceMeasure("OpenGL Draw", 10); + + public int fpsMin = int.MaxValue; + public int fpsMax = 0; + + void resetFps () + { + fpsMin = int.MaxValue; + fpsMax = 0; + _fps = 0; + } + #endif + + #endregion + + #region ctor + public CrowWindow(int _width = 800, int _height = 600, string _title="Crow", + int colors = 32, int depth = 24, int stencil = 0, int samples = 1, + int major=3, int minor=3) + : this(_width, _height, new OpenTK.Graphics.GraphicsMode(colors, depth, stencil, samples), + _title,GameWindowFlags.Default,DisplayDevice.Default, + major,minor,OpenTK.Graphics.GraphicsContextFlags.Default) + { + } + public CrowWindow (int width, int height, OpenTK.Graphics.GraphicsMode mode, string title, GameWindowFlags options, DisplayDevice device, int major, int minor, OpenTK.Graphics.GraphicsContextFlags flags) + : base(width,height,mode,title,options,device,major,minor,flags) + { + } + + #endregion + + protected Shader shader; + public List ifaceControl = new List(); + int focusedIdx = -1, activeIdx = -2; + + void addInterfaceControler(InterfaceControler ifaceControler) + { + ifaceControler.CrowInterface.Quit += Quit; + ifaceControler.CrowInterface.MouseCursorChanged += CrowInterface_MouseCursorChanged; + + ifaceControl.Add (ifaceControler); + } + void openGLDraw(){ + //save GL states + bool blend, depthTest, cullFace; + GL.GetBoolean (GetPName.Blend, out blend); + GL.GetBoolean (GetPName.DepthTest, out depthTest); + GL.GetBoolean (GetPName.CullFace, out cullFace); + GL.Enable (EnableCap.Blend); + GL.Disable (EnableCap.DepthTest); + GL.Disable (EnableCap.CullFace); + + #if MEASURE_TIME + glDrawMeasure.StartCycle(); + #endif + + shader.Enable (); + for (int i = 0; i < ifaceControl.Count; i++) { + shader.SetMVP (ifaceControl [i].InterfaceMVP); + ifaceControl [i].OpenGLDraw (); + } + + #if MEASURE_TIME + glDrawMeasure.StopCycle(); + #endif + + //restore GL states + if (!blend) + GL.Disable (EnableCap.Blend); + if (depthTest) + GL.Enable (EnableCap.DepthTest); + if (cullFace) + GL.Enable (EnableCap.CullFace); + } + + public void Quit (object sender, EventArgs e) + { + foreach (InterfaceControler ic in ifaceControl) { + ic.Dispose (); + } + this.Exit (); + } + void CrowInterface_MouseCursorChanged (object sender, MouseCursorChangedEventArgs e) + { + this.Cursor = new MouseCursor( + (int)e.NewCursor.Xhot, + (int)e.NewCursor.Yhot, + (int)e.NewCursor.Width, + (int)e.NewCursor.Height, + e.NewCursor.data); + } + + #region Events + //those events are raised only if mouse isn't in a graphic object + public event EventHandler MouseWheelChanged; + public event EventHandler MouseButtonUp; + public event EventHandler MouseButtonDown; + public event EventHandler MouseClick; + public event EventHandler MouseMove; + public event EventHandler KeyboardKeyDown; + public event EventHandler KeyboardKeyUp; + + #endregion + + public ProjectiveIFaceControler Add3DInterface(int width, int height, Matrix4 ifaceModelMat){ + ProjectiveIFaceControler tmp = new ProjectiveIFaceControler (new Rectangle (0, 0, width, height), ifaceModelMat); + addInterfaceControler (tmp); + return tmp; + } + public GraphicObject AddWidget (GraphicObject g, int interfaceIdx = 0){ + if (ifaceControl.Count == 0)//create default orthogonal interface + addInterfaceControler (new InterfaceControler ( + new Rectangle (0, 0, this.ClientRectangle.Width, this.ClientRectangle.Height))); + ifaceControl [interfaceIdx].CrowInterface.AddWidget (g); + return g; + } + public void DeleteWidget (GraphicObject g, int interfaceIdx = 0){ + ifaceControl [interfaceIdx].CrowInterface.DeleteWidget (g); + } + public GraphicObject Load (string path, int interfaceIdx = 0){ + if (ifaceControl.Count == 0)//create default orthogonal interface + addInterfaceControler (new InterfaceControler ( + new Rectangle (0, 0, this.ClientRectangle.Width, this.ClientRectangle.Height))); + return ifaceControl [interfaceIdx].CrowInterface.LoadInterface (path); + } + public GraphicObject FindByName (string nameToFind){ + for (int i = 0; i < ifaceControl.Count; i++) { + GraphicObject tmp = ifaceControl [i].CrowInterface.FindByName (nameToFind); + if (tmp != null) + return tmp; + } + return null; + } + public void ClearInterface (int interfaceIdx = 0){ + ifaceControl [interfaceIdx].CrowInterface.ClearInterface (); + } + /// Override this method for your OpenGL rendering calls + public virtual void OnRender(FrameEventArgs e) + { + } + /// Override this method to customize clear method between frames + public virtual void GLClear() + { + GL.Clear (ClearBufferMask.ColorBufferBit|ClearBufferMask.DepthBufferBit); + } + + #region Game win overrides + protected override void OnLoad(EventArgs e) + { + base.OnLoad(e); + + this.KeyPress += new EventHandler(OpenTKGameWindow_KeyPress); + Keyboard.KeyDown += new EventHandler(Keyboard_KeyDown); + Keyboard.KeyUp += new EventHandler(Keyboard_KeyUp); + Mouse.WheelChanged += new EventHandler(GL_Mouse_WheelChanged); + Mouse.ButtonDown += new EventHandler(GL_Mouse_ButtonDown); + Mouse.ButtonUp += new EventHandler(GL_Mouse_ButtonUp); + Mouse.Move += new EventHandler(GL_Mouse_Move); + + #if DEBUG + Console.WriteLine("\n\n*************************************"); + Console.WriteLine("GL version: " + GL.GetString (StringName.Version)); + Console.WriteLine("GL vendor: " + GL.GetString (StringName.Vendor)); + Console.WriteLine("GLSL version: " + GL.GetString (StringName.ShadingLanguageVersion)); + Console.WriteLine("*************************************\n"); + #endif + + shader = new Shader (); + shader.Enable (); + + GL.ClearColor(0.0f, 0.0f, 0.0f, 0.0f); + } + protected override void OnUpdateFrame(FrameEventArgs e) + { + base.OnUpdateFrame(e); + fps = (int)RenderFrequency; + + #if MEASURE_TIME + if (frameCpt > 500) { + resetFps (); + frameCpt = 0; +// #if DEBUG +// GC.Collect(); +// GC.WaitForPendingFinalizers(); +// NotifyValueChanged("memory", GC.GetTotalMemory (false).ToString()); +// #endif + } + #endif + + frameCpt++; + } + protected override void OnRenderFrame(FrameEventArgs e) + { + GLClear (); + + base.OnRenderFrame(e); + + OnRender (e); + openGLDraw (); + + + SwapBuffers (); + } + protected override void OnResize(EventArgs e) + { + base.OnResize (e); + for (int i = 0; i < ifaceControl.Count; i++) { + ifaceControl[i].ProcessResize( + new Rectangle( + 0, + 0, + this.ClientRectangle.Width, + this.ClientRectangle.Height)); + } + } + #endregion + + #region Mouse and Keyboard Handling + void update_mouseButtonStates(ref MouseState e, OpenTK.Input.MouseState otk_e){ + for (int i = 0; i < MouseState.MaxButtons; i++) { + if (otk_e.IsButtonDown ((OpenTK.Input.MouseButton)i)) + e.EnableBit (i); + } + } + protected virtual void GL_Mouse_Move(object sender, OpenTK.Input.MouseMoveEventArgs otk_e) + { + if (activeIdx == -2) { + focusedIdx = -1; + for (int i = 0; i < ifaceControl.Count; i++) { + if (ifaceControl [i].ProcessMouseMove (otk_e.X, otk_e.Y)) { + focusedIdx = i; + return; + } + } + } else if (focusedIdx >= 0) { + ifaceControl [focusedIdx].ProcessMouseMove (otk_e.X, otk_e.Y); + return; + } + if (focusedIdx < 0) + MouseMove.Raise (sender, otk_e); + } + protected virtual void GL_Mouse_ButtonUp(object sender, OpenTK.Input.MouseButtonEventArgs otk_e) + { + activeIdx = -2; + if (focusedIdx >= 0) { + if (ifaceControl [focusedIdx].ProcessMouseButtonUp ((int)otk_e.Button)) + return; + } + MouseButtonUp.Raise (sender, otk_e); + } + protected virtual void GL_Mouse_ButtonDown(object sender, OpenTK.Input.MouseButtonEventArgs otk_e) + { + activeIdx = focusedIdx; + if (focusedIdx >= 0) { + if (ifaceControl [focusedIdx].ProcessMouseButtonDown ((int)otk_e.Button)) + return; + } + MouseButtonDown.Raise (sender, otk_e); + } + protected virtual void GL_Mouse_WheelChanged(object sender, OpenTK.Input.MouseWheelEventArgs otk_e) + { + if (focusedIdx >= 0) { + if (ifaceControl [focusedIdx].ProcessMouseWheelChanged (otk_e.DeltaPrecise)) + return; + } + MouseWheelChanged.Raise (sender, otk_e); + } + + protected virtual void Keyboard_KeyDown(object sender, OpenTK.Input.KeyboardKeyEventArgs otk_e) + { + if (focusedIdx >= 0) { + if (ifaceControl [focusedIdx].ProcessKeyDown((int)otk_e.Key)) + return; + } + KeyboardKeyDown.Raise (this, otk_e); + } + protected virtual void Keyboard_KeyUp(object sender, OpenTK.Input.KeyboardKeyEventArgs otk_e) + { + if (focusedIdx >= 0) { + if (ifaceControl [focusedIdx].ProcessKeyUp((int)otk_e.Key)) + return; + } + KeyboardKeyUp.Raise (this, otk_e); + } + protected virtual void OpenTKGameWindow_KeyPress (object sender, OpenTK.KeyPressEventArgs e) + { + if (focusedIdx >= 0) { + if (ifaceControl [focusedIdx].ProcessKeyPress (e.KeyChar)) + return; + } + //TODO:create keyboardkeypress evt + } + #endregion + } +} diff --git a/InterfaceControler.cs b/InterfaceControler.cs new file mode 100644 index 0000000..4da65d1 --- /dev/null +++ b/InterfaceControler.cs @@ -0,0 +1,267 @@ +// +// InterfaceControler.cs +// +// Author: +// Jean-Philippe Bruyère +// +// 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 OpenTK; +using OpenTK.Graphics.OpenGL; +using System.Threading; +using System.Collections.Generic; + +namespace Crow +{ + public class ProjectiveIFaceControler : InterfaceControler { + Matrix4 modelview; + int[] viewport = new int[4]; + Vector3 vEyePosition; + + public Matrix4 ifaceModelMat; + Point localMousePos; + + public ProjectiveIFaceControler(Rectangle ifaceBounds, Matrix4 _ifaceModelMat) + : base(ifaceBounds){ + ifaceModelMat = _ifaceModelMat; + } + + public override Matrix4 InterfaceMVP { + get { return ifaceModelMat * modelview * projection; } + } + + public override void initGL(){ + quad = new Crow.vaoMesh (0, 0, 0, 1, 1, 1, -1); + //ifaceModelMat = Matrix4.CreateRotationX(MathHelper.PiOver2) * Matrix4.CreateTranslation(Vector3.UnitY); + CrowInterface.ProcessResize(iRect); + createContext (); + //CrowInterface.ProcessResize (iRect); + } + public override void ProcessResize (Rectangle newSize) + { + } + public override void OpenGLDraw () + { + GL.Enable (EnableCap.DepthTest); + base.OpenGLDraw (); + GL.Disable (EnableCap.DepthTest); + } + public void UpdateView (Matrix4 _projection, Matrix4 _modelview, int[] _viewport, Vector3 _vEyePosition) + { + projection = _projection; + modelview = _modelview; + viewport = _viewport; + vEyePosition = _vEyePosition; + } + public override bool ProcessMouseMove (int x, int y) + { + Matrix4 mv = ifaceModelMat * modelview; + Vector3 vMouse = UnProject(ref projection, ref mv, viewport, new Vector2 (x, y)).Xyz; + Vector3 vE = vEyePosition.Transform (ifaceModelMat.Inverted()); + Vector3 vMouseRay = Vector3.Normalize(vMouse - vE); + float a = vE.Z / vMouseRay.Z; + vMouse = vE - vMouseRay * a; + //vMouse = vMouse.Transform (interfaceModelView.Inverted()); + localMousePos = new Point ((int)Math.Truncate ((vMouse.X + 0.5f) * iRect.Width), + iRect.Height - (int)Math.Truncate ((vMouse.Y + 0.5f) * iRect.Height)); + mouseIsInInterface = localMousePos.X.IsInBetween (0, iRect.Width) & localMousePos.Y.IsInBetween (0, iRect.Height); + + return mouseIsInInterface ? CrowInterface.ProcessMouseMove (localMousePos.X, localMousePos.Y) : false; + } + Vector4 UnProject(ref Matrix4 projection, ref Matrix4 view, int[] viewport, Vector2 mouse) + { + Vector4 vec; + + vec.X = 2.0f * mouse.X / (float)viewport[2] - 1; + vec.Y = -(2.0f * mouse.Y / (float)viewport[3] - 1); + vec.Z = 0f; + vec.W = 1.0f; + + Matrix4 viewInv = Matrix4.Invert(view); + Matrix4 projInv = Matrix4.Invert(projection); + + Vector4.Transform(ref vec, ref projInv, out vec); + Vector4.Transform(ref vec, ref viewInv, out vec); + + if (vec.W > float.Epsilon || vec.W < float.Epsilon) + { + vec.X /= vec.W; + vec.Y /= vec.W; + vec.Z /= vec.W; + } + + return vec; + } + } + public class InterfaceControler : IDisposable { + public Interface CrowInterface; + public int texID; + public vaoMesh quad; + public Rectangle iRect = new Rectangle(0,0,2048,2048); + public bool mouseIsInInterface = false; + + protected Matrix4 projection; + public virtual Matrix4 InterfaceMVP { + get { return projection; } + } + + #if MEASURE_TIME + public List PerfMeasures; + public PerformanceMeasure glDrawMeasure = new PerformanceMeasure("OpenGL Draw", 10); + #endif + + #region CTOR + public InterfaceControler(Rectangle ifaceBounds){ + iRect = ifaceBounds; + + CrowInterface = new Interface (); + + #if MEASURE_TIME + PerfMeasures = new List ( + new PerformanceMeasure[] { + this.CrowInterface.updateMeasure, + this.CrowInterface.layoutingMeasure, + this.CrowInterface.clippingMeasure, + this.CrowInterface.drawingMeasure, + this.glDrawMeasure + } + ); + #endif + + Thread t = new Thread (interfaceThread); + t.IsBackground = true; + t.Start (); + + initGL (); + } + #endregion + + void interfaceThread() + { + while (CrowInterface.ClientRectangle.Size.Width == 0) + Thread.Sleep (5); + + while (true) { + CrowInterface.Update (); + Thread.Sleep (1); + } + } + + #region Mouse And Keyboard handling + public virtual void ProcessResize(Rectangle newSize){ + iRect = newSize; + CrowInterface.ProcessResize(newSize); + createContext (); + GL.Viewport (0, 0, newSize.Width, newSize.Height);//TODO:find a better place for this + } + public virtual bool ProcessMouseMove(int x, int y){ + return CrowInterface.ProcessMouseMove (x, y); + } + public virtual bool ProcessMouseButtonUp(int button) + { + return CrowInterface.ProcessMouseButtonUp (button); + } + public virtual bool ProcessMouseButtonDown(int button) + { + return CrowInterface.ProcessMouseButtonDown (button); + } + public virtual bool ProcessMouseWheelChanged(float delta) + { + return CrowInterface.ProcessMouseWheelChanged (delta); + } + public virtual bool ProcessKeyDown(int Key){ + return CrowInterface.ProcessKeyDown(Key); + } + public virtual bool ProcessKeyUp(int Key){ + return CrowInterface.ProcessKeyUp(Key); + } + public virtual bool ProcessKeyPress(char Key){ + return CrowInterface.ProcessKeyPress(Key); + } + #endregion + + #region graphic context + public virtual void initGL(){ + projection = OpenTK.Matrix4.CreateOrthographicOffCenter (-0.5f, 0.5f, -0.5f, 0.5f, 1, -1); + quad = new Crow.vaoMesh (0, 0, 0, 1, 1, 1, -1); + createContext (); + } + /// Create the texture for the interface redering + public virtual void createContext() + { + if (GL.IsTexture(texID)) + GL.DeleteTexture (texID); + GL.GenTextures(1, out texID); + GL.ActiveTexture (TextureUnit.Texture0); + GL.BindTexture(TextureTarget.Texture2D, texID); + + GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, + iRect.Width, iRect.Height, 0, + OpenTK.Graphics.OpenGL.PixelFormat.Bgra, PixelType.UnsignedByte, CrowInterface.bmp); + + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear); + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear); + + GL.BindTexture(TextureTarget.Texture2D, 0); + } + /// Rendering of the interface + public virtual void OpenGLDraw() + { + #if MEASURE_TIME + glDrawMeasure.StartCycle(); + #endif + + GL.ActiveTexture (TextureUnit.Texture0); + GL.BindTexture (TextureTarget.Texture2D, texID); + if (Monitor.TryEnter(CrowInterface.RenderMutex)) { + if (CrowInterface.IsDirty) { + GL.TexSubImage2D (TextureTarget.Texture2D, 0, + CrowInterface.DirtyRect.Left, CrowInterface.DirtyRect.Top, + CrowInterface.DirtyRect.Width, CrowInterface.DirtyRect.Height, + OpenTK.Graphics.OpenGL.PixelFormat.Bgra, PixelType.UnsignedByte, CrowInterface.dirtyBmp); + CrowInterface.IsDirty = false; + } + Monitor.Exit (CrowInterface.RenderMutex); + } + quad.Render (BeginMode.TriangleStrip); + GL.BindTexture(TextureTarget.Texture2D, 0); + + #if MEASURE_TIME + glDrawMeasure.StopCycle(); + #endif + } + #endregion + + #region IDisposable implementation + + public void Dispose () + { + if (GL.IsTexture(texID)) + GL.DeleteTexture (texID); + if (quad != null) + quad.Dispose (); + } + + #endregion + } +} + diff --git a/packages.config b/packages.config index 9875ed7..5740be9 100644 --- a/packages.config +++ b/packages.config @@ -1,5 +1,5 @@  - + diff --git a/src/CodeTextBuffer.cs b/src/CodeTextBuffer.cs new file mode 100644 index 0000000..988b9c3 --- /dev/null +++ b/src/CodeTextBuffer.cs @@ -0,0 +1,88 @@ +// +// CodeTextBuffer.cs +// +// Author: +// Jean-Philippe Bruyère +// +// 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 . +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; + +namespace Crow +{ + public class CodeTextBuffer : List + { + public CodeTextBuffer () : base() + { + } + + public int longestLineIdx = 0; + public int longestLineCharCount = 0; + + public CodeTextBuffer (string rawSource) : this (){ + if (string.IsNullOrEmpty (rawSource)) + return; + string[] lines = Regex.Split (rawSource, "\r\n|\r|\n|\\\\n"); + for (int i = 0; i < lines.Length; i++) { + if (lines [i].Length > longestLineCharCount) { + longestLineCharCount = lines [i].Length; + longestLineIdx = i; + } + this.Add (new SourceLine ( lines [i] )); + } + } + + /// + /// return all lines with linebreaks + /// + public string FullText{ + get { + string tmp = ""; + foreach (SourceLine sl in this) + tmp += sl.RawText + Interface.LineBreak; + return tmp; + } + } + + public void Tokenize (int lineIndex) { + //handle multiline block comments + if (lineIndex > 0){ + if (this [lineIndex - 1].Tokens?.LastOrDefault ().Type == TokenType.BlockComment) + this [lineIndex].PresetCurrentToken (TokenType.BlockComment); + } + this [lineIndex].Tokenize (); + } + + public void InsertLine(int index, SourceLine line){ + base.Insert (index, line); + } + public void RemoveLine (int index) { + base.RemoveAt (index); + } + + //public void Tokenize (int lineIndex) { + // //handle multiline block comments + // if (lineIndex > 0){ + // if (this [lineIndex - 1].Tokens?.LastOrDefault ().Type == TokenType.BlockComment) + // this [lineIndex].PresetCurrentToken (TokenType.BlockComment); + // } + // this [lineIndex].Tokenize (); + //} + } +} + diff --git a/src/CrowEdit.cs b/src/CrowEdit.cs index 1e0323f..ea9ef6b 100644 --- a/src/CrowEdit.cs +++ b/src/CrowEdit.cs @@ -54,11 +54,11 @@ namespace CrowEdit public bool IsDirty { get { return _text != _origText; }} public string CurrentDir { - get { return _curDir; } + get { return Configuration.Get("CurrentDir"); } set { - if (_curDir == value) + if (CurrentDir == value) return; - _curDir = value; + Configuration.Set ("CurrentDir", value); NotifyValueChanged ("CurrentDir", _curDir); } } @@ -190,6 +190,9 @@ namespace CrowEdit { base.OnLoad (e); + if (CurrentDir == null) + CurrentDir = Environment.GetFolderPath (Environment.SpecialFolder.MyDocuments); + this.ValueChanged += CrowEdit_ValueChanged; initCommands (); diff --git a/src/CrowWindow.cs b/src/CrowWindow.cs deleted file mode 100644 index b1308d1..0000000 --- a/src/CrowWindow.cs +++ /dev/null @@ -1,370 +0,0 @@ -// -// CrowWindow.cs -// -// Author: -// Jean-Philippe Bruyère -// -// 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 OpenTK; -using OpenTK.Graphics.OpenGL; -using System.Collections.Generic; - -namespace Crow -{ - public class CrowWindow : GameWindow, IValueChange - { - #region IValueChange implementation - public event EventHandler ValueChanged; - public virtual void NotifyValueChanged(string MemberName, object _value) - { - if (ValueChanged != null) - ValueChanged.Invoke(this, new ValueChangeEventArgs(MemberName, _value)); - } - #endregion - - #region FPS - int frameCpt = 0; - int _fps = 0; - - public int fps { - get { return _fps; } - set { - if (_fps == value) - return; - - _fps = value; - #if MEASURE_TIME - if (_fps > fpsMax) { - fpsMax = _fps; - ValueChanged.Raise(this, new ValueChangeEventArgs ("fpsMax", fpsMax)); - } else if (_fps < fpsMin) { - fpsMin = _fps; - ValueChanged.Raise(this, new ValueChangeEventArgs ("fpsMin", fpsMin)); - } - #endif - if (frameCpt % 3 == 0) - ValueChanged.Raise(this, new ValueChangeEventArgs ("fps", _fps)); - #if MEASURE_TIME -// foreach (PerformanceMeasure m in PerfMeasures) -// m.NotifyChanges(); - #endif - } - } - - #if MEASURE_TIME - public PerformanceMeasure glDrawMeasure = new PerformanceMeasure("OpenGL Draw", 10); - - public int fpsMin = int.MaxValue; - public int fpsMax = 0; - - void resetFps () - { - fpsMin = int.MaxValue; - fpsMax = 0; - _fps = 0; - } - #endif - - #endregion - - #region ctor - public CrowWindow(int _width = 800, int _height = 600, string _title="Crow", - int colors = 32, int depth = 24, int stencil = 0, int samples = 1, - int major=3, int minor=3) - : this(_width, _height, new OpenTK.Graphics.GraphicsMode(colors, depth, stencil, samples), - _title,GameWindowFlags.Default,DisplayDevice.Default, - major,minor,OpenTK.Graphics.GraphicsContextFlags.Default) - { - } - public CrowWindow (int width, int height, OpenTK.Graphics.GraphicsMode mode, string title, GameWindowFlags options, DisplayDevice device, int major, int minor, OpenTK.Graphics.GraphicsContextFlags flags) - : base(width,height,mode,title,options,device,major,minor,flags) - { - } - - #endregion - - protected Shader shader; - public List ifaceControl = new List(); - int focusedIdx = -1, activeIdx = -2; - - void addInterfaceControler(InterfaceControler ifaceControler) - { - ifaceControler.CrowInterface.Quit += Quit; - ifaceControler.CrowInterface.MouseCursorChanged += CrowInterface_MouseCursorChanged; - - ifaceControl.Add (ifaceControler); - } - void openGLDraw(){ - //save GL states - bool blend, depthTest, cullFace; - GL.GetBoolean (GetPName.Blend, out blend); - GL.GetBoolean (GetPName.DepthTest, out depthTest); - GL.GetBoolean (GetPName.CullFace, out cullFace); - GL.Enable (EnableCap.Blend); - GL.Disable (EnableCap.DepthTest); - GL.Disable (EnableCap.CullFace); - - #if MEASURE_TIME - glDrawMeasure.StartCycle(); - #endif - - shader.Enable (); - for (int i = 0; i < ifaceControl.Count; i++) { - shader.SetMVP (ifaceControl [i].InterfaceMVP); - ifaceControl [i].OpenGLDraw (); - } - - #if MEASURE_TIME - glDrawMeasure.StopCycle(); - #endif - - //restore GL states - if (!blend) - GL.Disable (EnableCap.Blend); - if (depthTest) - GL.Enable (EnableCap.DepthTest); - if (cullFace) - GL.Enable (EnableCap.CullFace); - } - - public void Quit (object sender, EventArgs e) - { - foreach (InterfaceControler ic in ifaceControl) { - ic.Dispose (); - } - this.Exit (); - } - void CrowInterface_MouseCursorChanged (object sender, MouseCursorChangedEventArgs e) - { - this.Cursor = new MouseCursor( - (int)e.NewCursor.Xhot, - (int)e.NewCursor.Yhot, - (int)e.NewCursor.Width, - (int)e.NewCursor.Height, - e.NewCursor.data); - } - - #region Events - //those events are raised only if mouse isn't in a graphic object - public event EventHandler MouseWheelChanged; - public event EventHandler MouseButtonUp; - public event EventHandler MouseButtonDown; - public event EventHandler MouseClick; - public event EventHandler MouseMove; - public event EventHandler KeyboardKeyDown; - public event EventHandler KeyboardKeyUp; - - #endregion - - public ProjectiveIFaceControler Add3DInterface(int width, int height, Matrix4 ifaceModelMat){ - ProjectiveIFaceControler tmp = new ProjectiveIFaceControler (new Rectangle (0, 0, width, height), ifaceModelMat); - addInterfaceControler (tmp); - return tmp; - } - public GraphicObject AddWidget (GraphicObject g, int interfaceIdx = 0){ - if (ifaceControl.Count == 0)//create default orthogonal interface - addInterfaceControler (new InterfaceControler ( - new Rectangle (0, 0, this.ClientRectangle.Width, this.ClientRectangle.Height))); - ifaceControl [interfaceIdx].CrowInterface.AddWidget (g); - return g; - } - public void DeleteWidget (GraphicObject g, int interfaceIdx = 0){ - ifaceControl [interfaceIdx].CrowInterface.DeleteWidget (g); - } - public GraphicObject Load (string path, int interfaceIdx = 0){ - if (ifaceControl.Count == 0)//create default orthogonal interface - addInterfaceControler (new InterfaceControler ( - new Rectangle (0, 0, this.ClientRectangle.Width, this.ClientRectangle.Height))); - return ifaceControl [interfaceIdx].CrowInterface.LoadInterface (path); - } - public GraphicObject FindByName (string nameToFind){ - for (int i = 0; i < ifaceControl.Count; i++) { - GraphicObject tmp = ifaceControl [i].CrowInterface.FindByName (nameToFind); - if (tmp != null) - return tmp; - } - return null; - } - public void ClearInterface (int interfaceIdx = 0){ - ifaceControl [interfaceIdx].CrowInterface.ClearInterface (); - } - /// Override this method for your OpenGL rendering calls - public virtual void OnRender(FrameEventArgs e) - { - } - /// Override this method to customize clear method between frames - public virtual void GLClear() - { - GL.Clear (ClearBufferMask.ColorBufferBit|ClearBufferMask.DepthBufferBit); - } - - #region Game win overrides - protected override void OnLoad(EventArgs e) - { - base.OnLoad(e); - - this.KeyPress += new EventHandler(OpenTKGameWindow_KeyPress); - Keyboard.KeyDown += new EventHandler(Keyboard_KeyDown); - Keyboard.KeyUp += new EventHandler(Keyboard_KeyUp); - Mouse.WheelChanged += new EventHandler(GL_Mouse_WheelChanged); - Mouse.ButtonDown += new EventHandler(GL_Mouse_ButtonDown); - Mouse.ButtonUp += new EventHandler(GL_Mouse_ButtonUp); - Mouse.Move += new EventHandler(GL_Mouse_Move); - - #if DEBUG - Console.WriteLine("\n\n*************************************"); - Console.WriteLine("GL version: " + GL.GetString (StringName.Version)); - Console.WriteLine("GL vendor: " + GL.GetString (StringName.Vendor)); - Console.WriteLine("GLSL version: " + GL.GetString (StringName.ShadingLanguageVersion)); - Console.WriteLine("*************************************\n"); - #endif - - shader = new Shader (); - shader.Enable (); - - GL.ClearColor(0.0f, 0.0f, 0.0f, 0.0f); - } - protected override void OnUpdateFrame(FrameEventArgs e) - { - base.OnUpdateFrame(e); - fps = (int)RenderFrequency; - - #if MEASURE_TIME - if (frameCpt > 500) { - resetFps (); - frameCpt = 0; -// #if DEBUG -// GC.Collect(); -// GC.WaitForPendingFinalizers(); -// NotifyValueChanged("memory", GC.GetTotalMemory (false).ToString()); -// #endif - } - #endif - - frameCpt++; - } - protected override void OnRenderFrame(FrameEventArgs e) - { - GLClear (); - - base.OnRenderFrame(e); - - OnRender (e); - openGLDraw (); - - - SwapBuffers (); - } - protected override void OnResize(EventArgs e) - { - base.OnResize (e); - for (int i = 0; i < ifaceControl.Count; i++) { - ifaceControl[i].ProcessResize( - new Rectangle( - 0, - 0, - this.ClientRectangle.Width, - this.ClientRectangle.Height)); - } - } - #endregion - - #region Mouse and Keyboard Handling - void update_mouseButtonStates(ref MouseState e, OpenTK.Input.MouseState otk_e){ - for (int i = 0; i < MouseState.MaxButtons; i++) { - if (otk_e.IsButtonDown ((OpenTK.Input.MouseButton)i)) - e.EnableBit (i); - } - } - protected virtual void GL_Mouse_Move(object sender, OpenTK.Input.MouseMoveEventArgs otk_e) - { - if (activeIdx == -2) { - focusedIdx = -1; - for (int i = 0; i < ifaceControl.Count; i++) { - if (ifaceControl [i].ProcessMouseMove (otk_e.X, otk_e.Y)) { - focusedIdx = i; - return; - } - } - } else if (focusedIdx >= 0) { - ifaceControl [focusedIdx].ProcessMouseMove (otk_e.X, otk_e.Y); - return; - } - if (focusedIdx < 0) - MouseMove.Raise (sender, otk_e); - } - protected virtual void GL_Mouse_ButtonUp(object sender, OpenTK.Input.MouseButtonEventArgs otk_e) - { - activeIdx = -2; - if (focusedIdx >= 0) { - if (ifaceControl [focusedIdx].ProcessMouseButtonUp ((int)otk_e.Button)) - return; - } - MouseButtonUp.Raise (sender, otk_e); - } - protected virtual void GL_Mouse_ButtonDown(object sender, OpenTK.Input.MouseButtonEventArgs otk_e) - { - activeIdx = focusedIdx; - if (focusedIdx >= 0) { - if (ifaceControl [focusedIdx].ProcessMouseButtonDown ((int)otk_e.Button)) - return; - } - MouseButtonDown.Raise (sender, otk_e); - } - protected virtual void GL_Mouse_WheelChanged(object sender, OpenTK.Input.MouseWheelEventArgs otk_e) - { - if (focusedIdx >= 0) { - if (ifaceControl [focusedIdx].ProcessMouseWheelChanged (otk_e.DeltaPrecise)) - return; - } - MouseWheelChanged.Raise (sender, otk_e); - } - - protected virtual void Keyboard_KeyDown(object sender, OpenTK.Input.KeyboardKeyEventArgs otk_e) - { - if (focusedIdx >= 0) { - if (ifaceControl [focusedIdx].ProcessKeyDown((int)otk_e.Key)) - return; - } - KeyboardKeyDown.Raise (this, otk_e); - } - protected virtual void Keyboard_KeyUp(object sender, OpenTK.Input.KeyboardKeyEventArgs otk_e) - { - if (focusedIdx >= 0) { - if (ifaceControl [focusedIdx].ProcessKeyUp((int)otk_e.Key)) - return; - } - KeyboardKeyUp.Raise (this, otk_e); - } - protected virtual void OpenTKGameWindow_KeyPress (object sender, OpenTK.KeyPressEventArgs e) - { - if (focusedIdx >= 0) { - if (ifaceControl [focusedIdx].ProcessKeyPress (e.KeyChar)) - return; - } - //TODO:create keyboardkeypress evt - } - #endregion - } -} diff --git a/src/InterfaceControler.cs b/src/InterfaceControler.cs deleted file mode 100644 index 4d988c1..0000000 --- a/src/InterfaceControler.cs +++ /dev/null @@ -1,267 +0,0 @@ -// -// InterfaceControler.cs -// -// Author: -// Jean-Philippe Bruyère -// -// 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 OpenTK; -using OpenTK.Graphics.OpenGL; -using System.Threading; -using System.Collections.Generic; - -namespace Crow -{ - public class ProjectiveIFaceControler : InterfaceControler { - Matrix4 modelview; - int[] viewport = new int[4]; - Vector3 vEyePosition; - - public Matrix4 ifaceModelMat; - Point localMousePos; - - public ProjectiveIFaceControler(Rectangle ifaceBounds, Matrix4 _ifaceModelMat) - : base(ifaceBounds){ - ifaceModelMat = _ifaceModelMat; - } - - public override Matrix4 InterfaceMVP { - get { return ifaceModelMat * modelview * projection; } - } - - public override void initGL(){ - quad = new Crow.vaoMesh (0, 0, 0, 1, 1, 1, -1); - //ifaceModelMat = Matrix4.CreateRotationX(MathHelper.PiOver2) * Matrix4.CreateTranslation(Vector3.UnitY); - CrowInterface.ProcessResize(iRect); - createContext (); - //CrowInterface.ProcessResize (iRect); - } - public override void ProcessResize (Rectangle newSize) - { - } - public override void OpenGLDraw () - { - GL.Enable (EnableCap.DepthTest); - base.OpenGLDraw (); - GL.Disable (EnableCap.DepthTest); - } - public void UpdateView (Matrix4 _projection, Matrix4 _modelview, int[] _viewport, Vector3 _vEyePosition) - { - projection = _projection; - modelview = _modelview; - viewport = _viewport; - vEyePosition = _vEyePosition; - } - public override bool ProcessMouseMove (int x, int y) - { - Matrix4 mv = ifaceModelMat * modelview; - Vector3 vMouse = UnProject(ref projection, ref mv, viewport, new Vector2 (x, y)).Xyz; - Vector3 vE = vEyePosition.Transform (ifaceModelMat.Inverted()); - Vector3 vMouseRay = Vector3.Normalize(vMouse - vE); - float a = vE.Z / vMouseRay.Z; - vMouse = vE - vMouseRay * a; - //vMouse = vMouse.Transform (interfaceModelView.Inverted()); - localMousePos = new Point ((int)Math.Truncate ((vMouse.X + 0.5f) * iRect.Width), - iRect.Height - (int)Math.Truncate ((vMouse.Y + 0.5f) * iRect.Height)); - mouseIsInInterface = localMousePos.X.IsInBetween (0, iRect.Width) & localMousePos.Y.IsInBetween (0, iRect.Height); - - return mouseIsInInterface ? CrowInterface.ProcessMouseMove (localMousePos.X, localMousePos.Y) : false; - } - Vector4 UnProject(ref Matrix4 projection, ref Matrix4 view, int[] viewport, Vector2 mouse) - { - Vector4 vec; - - vec.X = 2.0f * mouse.X / (float)viewport[2] - 1; - vec.Y = -(2.0f * mouse.Y / (float)viewport[3] - 1); - vec.Z = 0f; - vec.W = 1.0f; - - Matrix4 viewInv = Matrix4.Invert(view); - Matrix4 projInv = Matrix4.Invert(projection); - - Vector4.Transform(ref vec, ref projInv, out vec); - Vector4.Transform(ref vec, ref viewInv, out vec); - - if (vec.W > float.Epsilon || vec.W < float.Epsilon) - { - vec.X /= vec.W; - vec.Y /= vec.W; - vec.Z /= vec.W; - } - - return vec; - } - } - public class InterfaceControler : IDisposable { - public Interface CrowInterface; - public int texID; - public vaoMesh quad; - public Rectangle iRect = new Rectangle(0,0,2048,2048); - public bool mouseIsInInterface = false; - - protected Matrix4 projection; - public virtual Matrix4 InterfaceMVP { - get { return projection; } - } - - #if MEASURE_TIME - public List PerfMeasures; - public PerformanceMeasure glDrawMeasure = new PerformanceMeasure("OpenGL Draw", 10); - #endif - - #region CTOR - public InterfaceControler(Rectangle ifaceBounds){ - iRect = ifaceBounds; - - CrowInterface = new Interface (); - - #if MEASURE_TIME - PerfMeasures = new List ( - new PerformanceMeasure[] { - this.CrowInterface.updateMeasure, - this.CrowInterface.layoutingMeasure, - this.CrowInterface.clippingMeasure, - this.CrowInterface.drawingMeasure, - this.glDrawMeasure - } - ); - #endif - - Thread t = new Thread (interfaceThread); - t.IsBackground = true; - t.Start (); - - initGL (); - } - #endregion - - void interfaceThread() - { - while (CrowInterface.ClientRectangle.Size.Width == 0) - Thread.Sleep (5); - - while (true) { - CrowInterface.Update (); - //Thread.Sleep (1); - } - } - - #region Mouse And Keyboard handling - public virtual void ProcessResize(Rectangle newSize){ - iRect = newSize; - CrowInterface.ProcessResize(newSize); - createContext (); - GL.Viewport (0, 0, newSize.Width, newSize.Height);//TODO:find a better place for this - } - public virtual bool ProcessMouseMove(int x, int y){ - return CrowInterface.ProcessMouseMove (x, y); - } - public virtual bool ProcessMouseButtonUp(int button) - { - return CrowInterface.ProcessMouseButtonUp (button); - } - public virtual bool ProcessMouseButtonDown(int button) - { - return CrowInterface.ProcessMouseButtonDown (button); - } - public virtual bool ProcessMouseWheelChanged(float delta) - { - return CrowInterface.ProcessMouseWheelChanged (delta); - } - public virtual bool ProcessKeyDown(int Key){ - return CrowInterface.ProcessKeyDown(Key); - } - public virtual bool ProcessKeyUp(int Key){ - return CrowInterface.ProcessKeyUp(Key); - } - public virtual bool ProcessKeyPress(char Key){ - return CrowInterface.ProcessKeyPress(Key); - } - #endregion - - #region graphic context - public virtual void initGL(){ - projection = OpenTK.Matrix4.CreateOrthographicOffCenter (-0.5f, 0.5f, -0.5f, 0.5f, 1, -1); - quad = new Crow.vaoMesh (0, 0, 0, 1, 1, 1, -1); - createContext (); - } - /// Create the texture for the interface redering - public virtual void createContext() - { - if (GL.IsTexture(texID)) - GL.DeleteTexture (texID); - GL.GenTextures(1, out texID); - GL.ActiveTexture (TextureUnit.Texture0); - GL.BindTexture(TextureTarget.Texture2D, texID); - - GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, - iRect.Width, iRect.Height, 0, - OpenTK.Graphics.OpenGL.PixelFormat.Bgra, PixelType.UnsignedByte, CrowInterface.bmp); - - GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear); - GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear); - - GL.BindTexture(TextureTarget.Texture2D, 0); - } - /// Rendering of the interface - public virtual void OpenGLDraw() - { - #if MEASURE_TIME - glDrawMeasure.StartCycle(); - #endif - - GL.ActiveTexture (TextureUnit.Texture0); - GL.BindTexture (TextureTarget.Texture2D, texID); - if (Monitor.TryEnter(CrowInterface.RenderMutex)) { - if (CrowInterface.IsDirty) { - GL.TexSubImage2D (TextureTarget.Texture2D, 0, - CrowInterface.DirtyRect.Left, CrowInterface.DirtyRect.Top, - CrowInterface.DirtyRect.Width, CrowInterface.DirtyRect.Height, - OpenTK.Graphics.OpenGL.PixelFormat.Bgra, PixelType.UnsignedByte, CrowInterface.dirtyBmp); - CrowInterface.IsDirty = false; - } - Monitor.Exit (CrowInterface.RenderMutex); - } - quad.Render (BeginMode.TriangleStrip); - GL.BindTexture(TextureTarget.Texture2D, 0); - - #if MEASURE_TIME - glDrawMeasure.StopCycle(); - #endif - } - #endregion - - #region IDisposable implementation - - public void Dispose () - { - if (GL.IsTexture(texID)) - GL.DeleteTexture (texID); - if (quad != null) - quad.Dispose (); - } - - #endregion - } -} - diff --git a/src/ScrollingObject.cs b/src/ScrollingObject.cs deleted file mode 100644 index f0463ae..0000000 --- a/src/ScrollingObject.cs +++ /dev/null @@ -1,180 +0,0 @@ -// -// ScrollingObject.cs -// -// Author: -// Jean-Philippe Bruyère -// -// 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.Xml.Serialization; -using System.ComponentModel; -using System.Collections; -using Cairo; - - -namespace Crow -{ - public class ScrollingObject : GraphicObject - { - #region CTOR - public ScrollingObject ():base() - { - } - #endregion - - int scrollX, scrollY, maxScrollX, maxScrollY, mouseWheelSpeed; - - /// Horizontal Scrolling Position - [XmlAttributeAttribute][DefaultValue(0)] - public virtual int ScrollX { - get { return scrollX; } - set { - if (scrollX == value) - return; - - int newS = value; - if (newS < 0) - newS = 0; - else if (newS > maxScrollX) - newS = maxScrollX; - - if (newS == scrollX) - return; - - scrollX = value; - - NotifyValueChanged ("ScrollX", scrollX); - RegisterForGraphicUpdate (); - } - } - /// Vertical Scrolling Position - [XmlAttributeAttribute][DefaultValue(0)] - public virtual int ScrollY { - get { return scrollY; } - set { - if (scrollY == value) - return; - - int newS = value; - if (newS < 0) - newS = 0; - else if (newS > maxScrollY) - newS = maxScrollY; - - if (newS == scrollY) - return; - - scrollY = value; - - NotifyValueChanged ("ScrollY", scrollY); - RegisterForGraphicUpdate (); - } - } - /// Horizontal Scrolling maximum value - [XmlAttributeAttribute][DefaultValue(0)] - public virtual int MaxScrollX { - get { return maxScrollX; } - set { - if (maxScrollX == value) - return; - - maxScrollX = value; - - if (scrollX > maxScrollX) - ScrollX = maxScrollX; - - NotifyValueChanged ("MaxScrollX", maxScrollX); - RegisterForGraphicUpdate (); - } - } - /// Vertical Scrolling maximum value - [XmlAttributeAttribute][DefaultValue(0)] - public virtual int MaxScrollY { - get { return maxScrollY; } - set { - if (maxScrollY == value) - return; - - maxScrollY = value; - - if (scrollY > maxScrollY) - ScrollY = maxScrollY; - - NotifyValueChanged ("MaxScrollY", maxScrollY); - RegisterForGraphicUpdate (); - } - } - /// Mouse Wheel Scrolling multiplier - [XmlAttributeAttribute][DefaultValue(1)] - public virtual int MouseWheelSpeed { - get { return mouseWheelSpeed; } - set { - if (mouseWheelSpeed == value) - return; - - mouseWheelSpeed = value; - - NotifyValueChanged ("MouseWheelSpeed", mouseWheelSpeed); - } - } - - /// Process scrolling vertically, or if shift is down, vertically - public override void onMouseWheel (object sender, MouseWheelEventArgs e) - { - base.onMouseWheel (sender, e); - if (CurrentInterface.Keyboard.IsKeyDown (Key.ShiftLeft)) - ScrollX -= e.Delta * MouseWheelSpeed; - else - ScrollY -= e.Delta * MouseWheelSpeed; - - } - /// Process scrolling with arrow keys, home and end keys. - public override void onKeyDown (object sender, KeyboardKeyEventArgs e) - { - base.onKeyDown (sender, e); - - switch (e.Key) { - case Key.Up: - ScrollY--; - break; - case Key.Down: - ScrollY++; - break; - case Key.Left: - ScrollX--; - break; - case Key.Right: - ScrollX++; - break; - case Key.Home: - ScrollX = 0; - ScrollY = 0; - break; - case Key.End: - ScrollX = MaxScrollX; - ScrollY = MaxScrollY; - break; - } - } - } -} - diff --git a/src/ScrollingTextBox.cs b/src/ScrollingTextBox.cs deleted file mode 100644 index a9b429a..0000000 --- a/src/ScrollingTextBox.cs +++ /dev/null @@ -1,768 +0,0 @@ -// -// ScrollingTextBox.cs -// -// Author: -// Jean-Philippe Bruyère -// -// 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.Xml.Serialization; -using System.ComponentModel; -using System.Collections; -using Cairo; -using System.Text; -using System.Collections.Generic; -using System.Text.RegularExpressions; -using System.Linq; - -namespace Crow -{ - /// - /// Scrolling text box optimized for monospace fonts, for coding - /// - public class ScrollingTextBox : ScrollingObject - { - #region CTOR - public ScrollingTextBox ():base() - { - - - } - #endregion - - public event EventHandler TextChanged; - - public virtual void OnTextChanged(Object sender, EventArgs e) - { - TextChanged.Raise (this, e); - } - - #region private and protected fields - string lineBreak = Interface.LineBreak; - int visibleLines = 1; - List lines; - string _text = "label"; - Color selBackground; - Color selForeground; - Point mouseLocalPos = 0;//mouse coord in widget space - int _currentCol; //0 based cursor position in string - int _currentLine; - Point _selBegin = -1; //selection start (row,column) - Point _selRelease = -1; //selection end (row,column) - - protected Rectangle rText; - protected FontExtents fe; - protected TextExtents te; - #endregion - - [XmlAttributeAttribute][DefaultValue("label")] - public string Text - { - get { - return lines == null ? - _text : lines.Aggregate((i, j) => i + Interface.LineBreak + j); - } - set - { - if (string.Equals (value, _text, StringComparison.Ordinal)) - return; - - _text = value; - - if (string.IsNullOrEmpty(_text)) - _text = ""; - - lines = getLines; - MaxScrollY = Math.Max (0, lines.Count - visibleLines); - - OnTextChanged (this, null); - RegisterForGraphicUpdate (); - } - } - - - [XmlAttributeAttribute][DefaultValue("BlueGray")] - public virtual Color SelectionBackground { - get { return selBackground; } - set { - if (value == selBackground) - return; - selBackground = value; - NotifyValueChanged ("SelectionBackground", selBackground); - RegisterForRedraw (); - } - } - [XmlAttributeAttribute][DefaultValue("White")] - public virtual Color SelectionForeground { - get { return selForeground; } - set { - if (value == selForeground) - return; - selForeground = value; - NotifyValueChanged ("SelectionForeground", selForeground); - RegisterForRedraw (); - } - } - [XmlAttributeAttribute][DefaultValue(0)] - public int CurrentColumn{ - get { return _currentCol; } - set { - if (value == _currentCol) - return; - if (value < 0) - _currentCol = 0; - else if (value > lines [_currentLine].Length) - _currentCol = lines [_currentLine].Length; - else - _currentCol = value; - NotifyValueChanged ("CurrentColumn", _currentCol); - } - } - [XmlAttributeAttribute][DefaultValue(0)] - public int CurrentLine{ - get { return _currentLine; } - set { - if (value == _currentLine) - return; - if (value >= lines.Count) - _currentLine = lines.Count-1; - else if (value < 0) - _currentLine = 0; - else - _currentLine = value; - //force recheck of currentCol for bounding - int cc = _currentCol; - _currentCol = 0; - CurrentColumn = cc; - NotifyValueChanged ("CurrentLine", _currentLine); - } - } - [XmlIgnore]public Point CurrentPosition { - get { return new Point(CurrentColumn, CurrentLine); } - } - //TODO:using HasFocus for drawing selection cause SelBegin and Release binding not to work - /// - /// Selection begin position in char units (line, column) - /// - [XmlAttributeAttribute][DefaultValue("-1")] - public Point SelBegin { - get { return _selBegin; } - set { - if (value == _selBegin) - return; - _selBegin = value; - System.Diagnostics.Debug.WriteLine ("SelBegin=" + _selBegin); - NotifyValueChanged ("SelBegin", _selBegin); - NotifyValueChanged ("SelectedText", SelectedText); - } - } - /// - /// Selection release position in char units (line, column) - /// - [XmlAttributeAttribute][DefaultValue("-1")] - public Point SelRelease { - get { - return _selRelease; - } - set { - if (value == _selRelease) - return; - _selRelease = value; - NotifyValueChanged ("SelRelease", _selRelease); - NotifyValueChanged ("SelectedText", SelectedText); - } - } - /// - /// return char at CurrentLine, CurrentColumn - /// - [XmlIgnore]protected Char CurrentChar - { - get { - return lines [CurrentLine] [CurrentColumn]; - } - } - /// - /// ordered selection start and end positions in char units - /// - [XmlIgnore]protected Point selectionStart - { - get { - return SelRelease < 0 || SelBegin.Y < SelRelease.Y ? SelBegin : - SelBegin.Y > SelRelease.Y ? SelRelease : - SelBegin.X < SelRelease.X ? SelBegin : SelRelease; - } - } - [XmlIgnore]public Point selectionEnd - { - get { - return SelRelease < 0 || SelBegin.Y > SelRelease.Y ? SelBegin : - SelBegin.Y < SelRelease.Y ? SelRelease : - SelBegin.X > SelRelease.X ? SelBegin : SelRelease; - } - } - [XmlIgnore]public string SelectedText - { - get { - - if (SelRelease < 0 || SelBegin < 0) - return ""; - if (selectionStart.Y == selectionEnd.Y) - return lines [selectionStart.Y].Substring (selectionStart.X, selectionEnd.X - selectionStart.X); - string tmp = ""; - tmp = lines [selectionStart.Y].Substring (selectionStart.X); - for (int l = selectionStart.Y + 1; l < selectionEnd.Y; l++) { - tmp += Interface.LineBreak + lines [l]; - } - tmp += Interface.LineBreak + lines [selectionEnd.Y].Substring (0, selectionEnd.X); - return tmp; - } - } - [XmlIgnore]public bool selectionIsEmpty - { get { return SelRelease == SelBegin; } } - - List getLines { - get { - return Regex.Split (_text, "\r\n|\r|\n|\\\\n").ToList(); - } - } - /// - /// Moves cursor one char to the left. - /// - /// true if move succeed - public bool MoveLeft(){ - int tmp = _currentCol - 1; - if (tmp < 0) { - if (_currentLine == 0) - return false; - CurrentLine--; - CurrentColumn = int.MaxValue; - } else - CurrentColumn = tmp; - return true; - } - /// - /// Moves cursor one char to the right. - /// - /// true if move succeed - public bool MoveRight(){ - int tmp = _currentCol + 1; - if (tmp > lines [_currentLine].Length){ - if (CurrentLine == lines.Count - 1) - return false; - CurrentLine++; - CurrentColumn = 0; - } else - CurrentColumn = tmp; - return true; - } - public void GotoWordStart(){ - if (lines[CurrentLine].Length == 0) - return; - CurrentColumn--; - //skip white spaces - while (!char.IsLetterOrDigit (this.CurrentChar) && CurrentColumn > 0) - CurrentColumn--; - while (char.IsLetterOrDigit (lines [CurrentLine] [CurrentColumn]) && CurrentColumn > 0) - CurrentColumn--; - if (!char.IsLetterOrDigit (this.CurrentChar)) - CurrentColumn++; - } - public void GotoWordEnd(){ - //skip white spaces - if (CurrentColumn >= lines [CurrentLine].Length - 1) - return; - while (!char.IsLetterOrDigit (this.CurrentChar) && CurrentColumn < lines [CurrentLine].Length-1) - CurrentColumn++; - while (char.IsLetterOrDigit (this.CurrentChar) && CurrentColumn < lines [CurrentLine].Length-1) - CurrentColumn++; - if (char.IsLetterOrDigit (this.CurrentChar)) - CurrentColumn++; - } - public void DeleteChar() - { - if (selectionIsEmpty) { - if (CurrentColumn == 0) { - if (CurrentLine == 0 && lines.Count == 1) - return; - CurrentLine--; - CurrentColumn = lines [CurrentLine].Length; - lines [CurrentLine] += lines [CurrentLine + 1]; - lines.RemoveAt (CurrentLine + 1); - OnTextChanged (this, null); - return; - } - CurrentColumn--; - lines [CurrentLine] = lines [CurrentLine].Remove (CurrentColumn, 1); - } else { - int linesToRemove = selectionEnd.Y - selectionStart.Y + 1; - int l = selectionStart.Y; - - if (linesToRemove > 0) { - lines [l] = lines [l].Remove (selectionStart.X, lines [l].Length - selectionStart.X) + - lines [selectionEnd.Y].Substring (selectionEnd.X, lines [selectionEnd.Y].Length - selectionEnd.X); - l++; - for (int c = 0; c < linesToRemove-1; c++) - lines.RemoveAt (l); - CurrentLine = selectionStart.Y; - CurrentColumn = selectionStart.X; - } else - lines [l] = lines [l].Remove (selectionStart.X, selectionEnd.X - selectionStart.X); - CurrentColumn = selectionStart.X; - SelBegin = -1; - SelRelease = -1; - } - OnTextChanged (this, null); - } - - #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.Height * lines.Count) + Margin * 2; - - string txt = _text.Replace("\t", new String (' ', Interface.TabSize)); - - - int maxChar = 0; - foreach (string s in Regex.Split (txt, "\r\n|\r|\n|\\\\n")) { - if (maxChar < s.Length) - maxChar = s.Length; - } - return (int)(fe.MaxXAdvance * maxChar) + Margin * 2; - } - public override void OnLayoutChanges (LayoutingType layoutType) - { - base.OnLayoutChanges (layoutType); - - if (layoutType == LayoutingType.Height) - updateVisibleLines (); - } - protected override void onDraw (Context gr) - { - base.onDraw (gr); - - gr.SelectFontFace (Font.Name, Font.Slant, Font.Wheight); - gr.SetFontSize (Font.Size); - gr.FontOptions = Interface.FontRenderingOptions; - gr.Antialias = Interface.Antialias; - - Rectangle cb = ClientRectangle; - - Foreground.SetAsSource (gr); - - bool selectionInProgress = false; - - #region draw text cursor - if (SelBegin != SelRelease) - selectionInProgress = true; - else if (HasFocus){ - gr.SetSourceColor(Color.Red); - gr.LineWidth = 2.0; - double cursorX = cb.X + (CurrentColumn - ScrollX) * fe.MaxXAdvance; - gr.MoveTo (0.5 + cursorX, cb.Y + (CurrentLine - ScrollY) * fe.Height); - gr.LineTo (0.5 + cursorX, cb.Y + (CurrentLine + 1 - ScrollY) * fe.Height); - gr.Stroke(); - } - #endregion - - Foreground.SetAsSource (gr); - - for (int i = 0; i < visibleLines; i++) { - int curL = i + ScrollY; - if (curL >= lines.Count) - break; - string lstr = lines[curL]; - - gr.MoveTo (cb.X, cb.Y + fe.Ascent + fe.Height * i); - gr.ShowText (lstr); - gr.Fill (); - - if (selectionInProgress && curL >= selectionStart.Y && curL <= selectionEnd.Y) { - - double rLineX = cb.X, - rLineY = cb.Y + i * fe.Height, - rLineW = lstr.Length * fe.MaxXAdvance; - - System.Diagnostics.Debug.WriteLine ("sel start: " + selectionStart + " sel end: " + selectionEnd); - if (curL == selectionStart.Y) { - rLineX += selectionStart.X * fe.MaxXAdvance; - rLineW -= selectionStart.X * fe.MaxXAdvance; - } - if (curL == selectionEnd.Y) - rLineW -= (lstr.Length - selectionEnd.X) * fe.MaxXAdvance; - - gr.Save (); - gr.Operator = Operator.Source; - gr.Rectangle (rLineX, rLineY, rLineW, fe.Height); - gr.SetSourceColor (SelectionBackground); - gr.FillPreserve (); - gr.Clip (); - gr.Operator = Operator.Over; - gr.SetSourceColor (SelectionForeground); - gr.MoveTo (cb.X, cb.Y + fe.Ascent + fe.Height * i); - gr.ShowText (lstr); - gr.Fill (); - gr.Restore (); - } - } - } - #endregion - - #region Mouse handling - void updatemouseLocalPos(Point mpos){ - mouseLocalPos = mpos - ScreenCoordinates(Slot).TopLeft - ClientRectangle.TopLeft; - if (mouseLocalPos.X < 0) - mouseLocalPos.X = 0; - if (mouseLocalPos.Y < 0) - mouseLocalPos.Y = 0; - - CurrentLine = ScrollY + (int)Math.Floor (mouseLocalPos.Y / fe.Height); - CurrentColumn = ScrollX + (int)Math.Round (mouseLocalPos.X / fe.MaxXAdvance); - } - public override void onMouseEnter (object sender, MouseMoveEventArgs e) - { - base.onMouseEnter (sender, e); - CurrentInterface.MouseCursor = XCursor.Text; - } - public override void onMouseLeave (object sender, MouseMoveEventArgs e) - { - base.onMouseLeave (sender, e); - CurrentInterface.MouseCursor = XCursor.Default; - } - protected override void onFocused (object sender, EventArgs e) - { - base.onFocused (sender, e); - -// SelBegin = new Point(0,0); -// SelRelease = new Point (lines.LastOrDefault ().Length, lines.Count-1); - RegisterForRedraw (); - } - protected override void onUnfocused (object sender, EventArgs e) - { - base.onUnfocused (sender, e); - -// SelBegin = -1; -// SelRelease = -1; - RegisterForRedraw (); - } - public override void onMouseMove (object sender, MouseMoveEventArgs e) - { - base.onMouseMove (sender, e); - - if (!e.Mouse.IsButtonDown (MouseButton.Left)) - return; - if (!HasFocus || SelBegin < 0) - return; - - updatemouseLocalPos (e.Position); - SelRelease = CurrentPosition; - - RegisterForRedraw(); - } - public override void onMouseDown (object sender, MouseButtonEventArgs e) - { - if (this.HasFocus){ - updatemouseLocalPos (e.Position); - SelBegin = SelRelease = CurrentPosition; - RegisterForRedraw();//TODO:should put it in properties - } - - //done at the end to set 'hasFocus' value after testing it - base.onMouseDown (sender, e); - } - public override void onMouseUp (object sender, MouseButtonEventArgs e) - { - base.onMouseUp (sender, e); - - if (SelBegin == SelRelease) - SelBegin = SelRelease = -1; - - updatemouseLocalPos (e.Position); - RegisterForRedraw (); - } - public override void onMouseDoubleClick (object sender, MouseButtonEventArgs e) - { - base.onMouseDoubleClick (sender, e); - - GotoWordStart (); - SelBegin = CurrentPosition; - GotoWordEnd (); - SelRelease = CurrentPosition; - RegisterForRedraw (); - } - #endregion - - #region Keyboard handling - public override void onKeyDown (object sender, KeyboardKeyEventArgs e) - { - base.onKeyDown (sender, e); - - Key key = e.Key; - - switch (key) - { - case Key.Back: - if (CurrentPosition == 0) - return; - this.DeleteChar(); - break; - case Key.Clear: - break; - case Key.Delete: - if (selectionIsEmpty) { - if (!MoveRight ()) - return; - }else if (e.Shift) - CurrentInterface.Clipboard = this.SelectedText; - this.DeleteChar (); - break; - case Key.Enter: - case Key.KeypadEnter: - if (!selectionIsEmpty) - this.DeleteChar (); - this.InsertLineBreak (); - break; - case Key.Escape: - Text = ""; - CurrentColumn = 0; - SelRelease = -1; - break; - case Key.Home: - if (e.Shift) { - if (selectionIsEmpty) - SelBegin = new Point (CurrentColumn, CurrentLine); - if (e.Control) - CurrentLine = 0; - CurrentColumn = 0; - SelRelease = new Point (CurrentColumn, CurrentLine); - break; - } - SelRelease = -1; - if (e.Control) - CurrentLine = 0; - CurrentColumn = 0; - break; - case Key.End: - if (e.Shift) { - if (selectionIsEmpty) - SelBegin = CurrentPosition; - if (e.Control) - CurrentLine = int.MaxValue; - CurrentColumn = int.MaxValue; - SelRelease = CurrentPosition; - break; - } - SelRelease = -1; - if (e.Control) - CurrentLine = int.MaxValue; - CurrentColumn = int.MaxValue; - break; - case Key.Insert: - if (e.Shift) - this.Insert (CurrentInterface.Clipboard); - else if (e.Control && !selectionIsEmpty) - CurrentInterface.Clipboard = this.SelectedText; - break; - case Key.Left: - if (e.Shift) { - if (selectionIsEmpty) - SelBegin = new Point(CurrentColumn, CurrentLine); - if (e.Control) - GotoWordStart (); - else if (!MoveLeft ()) - return; - SelRelease = CurrentPosition; - break; - } - SelRelease = -1; - if (e.Control) - GotoWordStart (); - else - MoveLeft(); - break; - case Key.Right: - if (e.Shift) { - if (selectionIsEmpty) - SelBegin = CurrentPosition; - if (e.Control) - GotoWordEnd (); - else if (!MoveRight ()) - return; - SelRelease = CurrentPosition; - break; - } - SelRelease = -1; - if (e.Control) - GotoWordEnd (); - else - MoveRight (); - break; - case Key.Up: - if (e.Shift) { - if (selectionIsEmpty) - SelBegin = CurrentPosition; - CurrentLine--; - SelRelease = CurrentPosition; - break; - } - SelRelease = -1; - CurrentLine--; - break; - case Key.Down: - if (e.Shift) { - if (selectionIsEmpty) - SelBegin = CurrentPosition; - CurrentLine++; - SelRelease = CurrentPosition; - break; - } - SelRelease = -1; - CurrentLine++; - break; - case Key.Menu: - break; - case Key.NumLock: - break; - case Key.PageDown: - break; - case Key.PageUp: - break; - case Key.RWin: - break; - case Key.Tab: - this.Insert ("\t"); - break; - default: - break; - } - RegisterForGraphicUpdate(); - } - public override void onKeyPress (object sender, KeyPressEventArgs e) - { - base.onKeyPress (sender, e); - - this.Insert (e.KeyChar.ToString()); - - SelRelease = -1; - SelBegin = -1; //new Point(CurrentColumn, SelBegin.Y); - - RegisterForGraphicUpdate(); - } - #endregion - - - /// Compute x offset in cairo unit from text position - double GetXFromTextPointer(Context gr, Point pos) - { - try { - string l = lines [pos.Y].Substring (0, pos.X). - Replace ("\t", new String (' ', Interface.TabSize)); - return gr.TextExtents (l).XAdvance; - } catch{ - return -1; - } - } - - /// line break could be '\r' or '\n' or '\r\n' - string detectLineBreakKind(){ - string strLB = ""; - - if (string.IsNullOrEmpty(_text)) - return Interface.LineBreak; - int i = 0; - while ( i < _text.Length) { - if (_text [i] == '\r') { - strLB += '\r'; - i++; - } - if (i < _text.Length) { - if (_text [i] == '\r') - return "\r"; - if (_text [i] == '\n') - strLB += '\n'; - } - if (!string.IsNullOrEmpty (strLB)) - return strLB; - i++; - } - return Interface.LineBreak; - } - - void updateVisibleLines(){ - visibleLines = (int)Math.Floor ((double)ClientRectangle.Height / fe.Height); - MaxScrollY = Math.Max (0, lines.Count - visibleLines); - - System.Diagnostics.Debug.WriteLine ("update visible lines: " + visibleLines); - System.Diagnostics.Debug.WriteLine ("update MaxScrollY: " + MaxScrollY); - } - - - /// - /// Insert new string at caret position, should be sure no line break is inside. - /// - /// String. - protected void Insert(string str) - { - if (!selectionIsEmpty) - this.DeleteChar (); - string[] strLines = Regex.Split (str, "\r\n|\r|\n|" + @"\\n").ToArray(); - lines [CurrentLine] = lines [CurrentLine].Insert (CurrentColumn, strLines[0]); - CurrentColumn += strLines[0].Length; - for (int i = 1; i < strLines.Length; i++) { - InsertLineBreak (); - lines [CurrentLine] = lines [CurrentLine].Insert (CurrentColumn, strLines[i]); - CurrentColumn += strLines[i].Length; - } - OnTextChanged (this, null); - RegisterForGraphicUpdate(); - } - - /// - /// Insert a line break. - /// - protected void InsertLineBreak() - { - lines.Insert(CurrentLine + 1, lines[CurrentLine].Substring(CurrentColumn)); - lines [CurrentLine] = lines [CurrentLine].Substring (0, CurrentColumn); - CurrentLine++; - CurrentColumn = 0; - OnTextChanged (this, null); - } - } -} \ No newline at end of file diff --git a/src/SourceEditor.cs b/src/SourceEditor.cs new file mode 100644 index 0000000..8f1748e --- /dev/null +++ b/src/SourceEditor.cs @@ -0,0 +1,813 @@ +// +// ScrollingTextBox.cs +// +// Author: +// Jean-Philippe Bruyère +// +// 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.Xml.Serialization; +using System.ComponentModel; +using System.Collections; +using Cairo; +using System.Text; +using System.Collections.Generic; +using System.Text.RegularExpressions; +using System.Linq; +using System.Diagnostics; + +namespace Crow +{ + /// + /// Scrolling text box optimized for monospace fonts, for coding + /// + public class SourceEditor : ScrollingObject + { + #region CTOR + public SourceEditor ():base() + { + + + } + #endregion + + public event EventHandler TextChanged; + + public virtual void OnTextChanged(Object sender, EventArgs e) + { + TextChanged.Raise (this, e); + } + + #region private and protected fields + string lineBreak = Interface.LineBreak; + int visibleLines = 1; + int visibleColumns = 1; + CodeTextBuffer buffer; + string _text = "label"; + Color selBackground; + Color selForeground; + int _currentCol; //0 based cursor position in string + int _currentLine; + Point _selBegin = -1; //selection start (row,column) + Point _selRelease = -1; //selection end (row,column) + + protected Rectangle rText; + protected FontExtents fe; + protected TextExtents te; + #endregion + + [XmlAttributeAttribute][DefaultValue("label")] + public string Text + { + get { + return buffer == null ? + _text : buffer.FullText; + } + set + { + if (string.Equals (value, _text, StringComparison.Ordinal)) + return; + + _text = value; + + if (string.IsNullOrEmpty(_text)) + _text = ""; + + buffer = new CodeTextBuffer (_text); + MaxScrollY = Math.Max (0, buffer.Count - visibleLines); + MaxScrollX = Math.Max (0, buffer.longestLineCharCount - visibleColumns); + + OnTextChanged (this, null); + RegisterForGraphicUpdate (); + } + } + + + [XmlAttributeAttribute][DefaultValue("BlueGray")] + public virtual Color SelectionBackground { + get { return selBackground; } + set { + if (value == selBackground) + return; + selBackground = value; + NotifyValueChanged ("SelectionBackground", selBackground); + RegisterForRedraw (); + } + } + [XmlAttributeAttribute][DefaultValue("White")] + public virtual Color SelectionForeground { + get { return selForeground; } + set { + if (value == selForeground) + return; + selForeground = value; + NotifyValueChanged ("SelectionForeground", selForeground); + RegisterForRedraw (); + } + } + [XmlAttributeAttribute][DefaultValue(0)] + public int CurrentColumn{ + get { return _currentCol; } + set { + if (value == _currentCol) + return; + if (value < 0) + _currentCol = 0; + else if (value > buffer [_currentLine].Length) + _currentCol = buffer [_currentLine].Length; + else + _currentCol = value; + + if (_currentCol < ScrollX) + ScrollX = _currentCol; + else if (_currentCol >= ScrollX + visibleColumns) + ScrollX = _currentCol - visibleColumns + 1; + + NotifyValueChanged ("CurrentColumn", _currentCol); + } + } + [XmlAttributeAttribute][DefaultValue(0)] + public int CurrentLine{ + get { return _currentLine; } + set { + if (value == _currentLine) + return; + if (value >= buffer.Count) + _currentLine = buffer.Count-1; + else if (value < 0) + _currentLine = 0; + else + _currentLine = value; + //force recheck of currentCol for bounding + int cc = _currentCol; + _currentCol = 0; + CurrentColumn = cc; + //System.Diagnostics.Debug.WriteLine ("Scroll:{0} visibleLines:{1} CurLine:{2}", ScrollY, visibleLines, CurrentLine); + if (_currentLine < ScrollY) + ScrollY = _currentLine; + else if (_currentLine >= ScrollY + visibleLines) + ScrollY = _currentLine - visibleLines + 1; + NotifyValueChanged ("CurrentLine", _currentLine); + } + } + [XmlIgnore]public Point CurrentPosition { + get { return new Point(CurrentColumn, CurrentLine); } + } + //TODO:using HasFocus for drawing selection cause SelBegin and Release binding not to work + /// + /// Selection begin position in char units (line, column) + /// + [XmlAttributeAttribute][DefaultValue("-1")] + public Point SelBegin { + get { return _selBegin; } + set { + if (value == _selBegin) + return; + _selBegin = value; + System.Diagnostics.Debug.WriteLine ("SelBegin=" + _selBegin); + NotifyValueChanged ("SelBegin", _selBegin); + NotifyValueChanged ("SelectedText", SelectedText); + } + } + /// + /// Selection release position in char units (line, column) + /// + [XmlAttributeAttribute][DefaultValue("-1")] + public Point SelRelease { + get { + return _selRelease; + } + set { + if (value == _selRelease) + return; + _selRelease = value; + NotifyValueChanged ("SelRelease", _selRelease); + NotifyValueChanged ("SelectedText", SelectedText); + } + } + /// + /// return char at CurrentLine, CurrentColumn + /// + [XmlIgnore]protected Char CurrentChar + { + get { + return buffer [CurrentLine] [CurrentColumn]; + } + } + /// + /// ordered selection start and end positions in char units + /// + [XmlIgnore]protected Point selectionStart + { + get { + return SelRelease < 0 || SelBegin.Y < SelRelease.Y ? SelBegin : + SelBegin.Y > SelRelease.Y ? SelRelease : + SelBegin.X < SelRelease.X ? SelBegin : SelRelease; + } + } + [XmlIgnore]public Point selectionEnd + { + get { + return SelRelease < 0 || SelBegin.Y > SelRelease.Y ? SelBegin : + SelBegin.Y < SelRelease.Y ? SelRelease : + SelBegin.X > SelRelease.X ? SelBegin : SelRelease; + } + } + [XmlIgnore]public string SelectedText + { + get { + + if (SelRelease < 0 || SelBegin < 0) + return ""; + if (selectionStart.Y == selectionEnd.Y) + return buffer [selectionStart.Y].RawText.Substring (selectionStart.X, selectionEnd.X - selectionStart.X); + string tmp = ""; + tmp = buffer [selectionStart.Y].RawText.Substring (selectionStart.X); + for (int l = selectionStart.Y + 1; l < selectionEnd.Y; l++) { + tmp += Interface.LineBreak + buffer [l]; + } + tmp += Interface.LineBreak + buffer [selectionEnd.Y].RawText.Substring (0, selectionEnd.X); + return tmp; + } + } + [XmlIgnore]public bool selectionIsEmpty + { get { return SelRelease == SelBegin; } } + + List getLines { + get { + return Regex.Split (_text, "\r\n|\r|\n|\\\\n").ToList(); + } + } + /// + /// Moves cursor one char to the left. + /// + /// true if move succeed + public bool MoveLeft(){ + int tmp = _currentCol - 1; + if (tmp < 0) { + if (_currentLine == 0) + return false; + CurrentLine--; + CurrentColumn = int.MaxValue; + } else + CurrentColumn = tmp; + return true; + } + /// + /// Moves cursor one char to the right. + /// + /// true if move succeed + public bool MoveRight(){ + int tmp = _currentCol + 1; + if (tmp > buffer [_currentLine].Length){ + if (CurrentLine == buffer.Count - 1) + return false; + CurrentLine++; + CurrentColumn = 0; + } else + CurrentColumn = tmp; + return true; + } + public void GotoWordStart(){ + if (buffer[CurrentLine].Length == 0) + return; + CurrentColumn--; + //skip white spaces + while (!char.IsLetterOrDigit (this.CurrentChar) && CurrentColumn > 0) + CurrentColumn--; + while (char.IsLetterOrDigit (buffer [CurrentLine] [CurrentColumn]) && CurrentColumn > 0) + CurrentColumn--; + if (!char.IsLetterOrDigit (this.CurrentChar)) + CurrentColumn++; + } + public void GotoWordEnd(){ + //skip white spaces + if (CurrentColumn >= buffer [CurrentLine].Length - 1) + return; + while (!char.IsLetterOrDigit (this.CurrentChar) && CurrentColumn < buffer [CurrentLine].Length-1) + CurrentColumn++; + while (char.IsLetterOrDigit (this.CurrentChar) && CurrentColumn < buffer [CurrentLine].Length-1) + CurrentColumn++; + if (char.IsLetterOrDigit (this.CurrentChar)) + CurrentColumn++; + } + public void DeleteChar() + { + if (selectionIsEmpty) { + if (CurrentColumn == 0) { + if (CurrentLine == 0 && buffer.Count == 1) + return; + CurrentLine--; + CurrentColumn = buffer [CurrentLine].Length; + buffer [CurrentLine].RawText += buffer [CurrentLine + 1].RawText; + buffer.RemoveLine (CurrentLine + 1); + OnTextChanged (this, null); + return; + } + CurrentColumn--; + buffer [CurrentLine].RawText = buffer [CurrentLine].RawText.Remove (CurrentColumn, 1); + } else { + int linesToRemove = selectionEnd.Y - selectionStart.Y + 1; + int l = selectionStart.Y; + + if (linesToRemove > 0) { + buffer [l].RawText = buffer [l].RawText.Remove (selectionStart.X, buffer [l].Length - selectionStart.X) + + buffer [selectionEnd.Y].RawText.Substring (selectionEnd.X, buffer [selectionEnd.Y].Length - selectionEnd.X); + l++; + for (int c = 0; c < linesToRemove-1; c++) + buffer.RemoveLine (l); + CurrentLine = selectionStart.Y; + CurrentColumn = selectionStart.X; + } else + buffer [l].RawText = buffer [l].RawText.Remove (selectionStart.X, selectionEnd.X - selectionStart.X); + CurrentColumn = selectionStart.X; + SelBegin = -1; + SelRelease = -1; + } + OnTextChanged (this, null); + } + + #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.Height * buffer.Count) + Margin * 2; + + string txt = _text.Replace("\t", new String (' ', Interface.TabSize)); + + + int maxChar = 0; + foreach (string s in Regex.Split (txt, "\r\n|\r|\n|\\\\n")) { + if (maxChar < s.Length) + maxChar = s.Length; + } + return (int)(fe.MaxXAdvance * maxChar) + 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.SelectFontFace (Font.Name, Font.Slant, Font.Wheight); + gr.SetFontSize (Font.Size); + gr.FontOptions = Interface.FontRenderingOptions; + gr.Antialias = Interface.Antialias; + + Rectangle cb = ClientRectangle; + + Foreground.SetAsSource (gr); + + bool selectionInProgress = false; + + Foreground.SetAsSource (gr); + + #region draw text cursor + if (SelBegin != SelRelease) + selectionInProgress = true; + else if (HasFocus){ + gr.LineWidth = 1.0; + double cursorX = cb.X + (CurrentColumn - ScrollX) * fe.MaxXAdvance; + gr.MoveTo (0.5 + cursorX, cb.Y + (CurrentLine - ScrollY) * fe.Height); + gr.LineTo (0.5 + cursorX, cb.Y + (CurrentLine + 1 - ScrollY) * fe.Height); + gr.Stroke(); + } + #endregion + + for (int i = 0; i < visibleLines; i++) { + int curL = i + ScrollY; + if (curL >= buffer.Count) + break; + string lstr = buffer[curL].RawText; + if (ScrollX < lstr.Length) + lstr = lstr.Substring (ScrollX); + else + lstr = ""; + + gr.MoveTo (cb.X, cb.Y + fe.Ascent + fe.Height * i); + gr.ShowText (lstr); + gr.Fill (); + + if (selectionInProgress && curL >= selectionStart.Y && curL <= selectionEnd.Y) { + + double rLineX = cb.X, + rLineY = cb.Y + i * fe.Height, + rLineW = lstr.Length * fe.MaxXAdvance; + + System.Diagnostics.Debug.WriteLine ("sel start: " + selectionStart + " sel end: " + selectionEnd); + if (curL == selectionStart.Y) { + rLineX += (selectionStart.X - ScrollX) * fe.MaxXAdvance; + rLineW -= selectionStart.X * fe.MaxXAdvance; + } + if (curL == selectionEnd.Y) + rLineW -= (lstr.Length - selectionEnd.X) * fe.MaxXAdvance; + + gr.Save (); + gr.Operator = Operator.Source; + gr.Rectangle (rLineX, rLineY, rLineW, fe.Height); + gr.SetSourceColor (SelectionBackground); + gr.FillPreserve (); + gr.Clip (); + gr.Operator = Operator.Over; + gr.SetSourceColor (SelectionForeground); + gr.MoveTo (cb.X, cb.Y + fe.Ascent + fe.Height * i); + gr.ShowText (lstr); + gr.Fill (); + gr.Restore (); + } + } + } + #endregion + + #region Mouse handling + void updatemouseLocalPos(Point mpos){ + Point mouseLocalPos = mpos - ScreenCoordinates(Slot).TopLeft - ClientRectangle.TopLeft; + if (mouseLocalPos.X < 0) + CurrentColumn--; + else + CurrentColumn = ScrollX + (int)Math.Round (mouseLocalPos.X / fe.MaxXAdvance); + + if (mouseLocalPos.Y < 0) + CurrentLine--; + else + CurrentLine = ScrollY + (int)Math.Floor (mouseLocalPos.Y / fe.Height); + } + public override void onMouseEnter (object sender, MouseMoveEventArgs e) + { + base.onMouseEnter (sender, e); + currentInterface.MouseCursor = XCursor.Text; + } + public override void onMouseLeave (object sender, MouseMoveEventArgs e) + { + base.onMouseLeave (sender, e); + currentInterface.MouseCursor = XCursor.Default; + } + protected override void onFocused (object sender, EventArgs e) + { + base.onFocused (sender, e); + + // SelBegin = new Point(0,0); + // SelRelease = new Point (lines.LastOrDefault ().Length, lines.Count-1); + RegisterForRedraw (); + } + protected override void onUnfocused (object sender, EventArgs e) + { + base.onUnfocused (sender, e); + + // SelBegin = -1; + // SelRelease = -1; + RegisterForRedraw (); + } + public override void onMouseMove (object sender, MouseMoveEventArgs e) + { + base.onMouseMove (sender, e); + + if (!e.Mouse.IsButtonDown (MouseButton.Left)) + return; + if (!HasFocus || SelBegin < 0) + return; + + updatemouseLocalPos (e.Position); + SelRelease = CurrentPosition; + + RegisterForRedraw(); + } + public override void onMouseDown (object sender, MouseButtonEventArgs e) + { + if (this.HasFocus){ + updatemouseLocalPos (e.Position); + SelBegin = SelRelease = CurrentPosition; + RegisterForRedraw();//TODO:should put it in properties + } + + //done at the end to set 'hasFocus' value after testing it + base.onMouseDown (sender, e); + } + public override void onMouseUp (object sender, MouseButtonEventArgs e) + { + base.onMouseUp (sender, e); + + if (SelBegin == SelRelease) + SelBegin = SelRelease = -1; + + updatemouseLocalPos (e.Position); + RegisterForRedraw (); + } + public override void onMouseDoubleClick (object sender, MouseButtonEventArgs e) + { + base.onMouseDoubleClick (sender, e); + + GotoWordStart (); + SelBegin = CurrentPosition; + GotoWordEnd (); + SelRelease = CurrentPosition; + RegisterForRedraw (); + } + #endregion + + #region Keyboard handling + public override void onKeyDown (object sender, KeyboardKeyEventArgs e) + { + //base.onKeyDown (sender, e); + + Key key = e.Key; + + switch (key) + { + case Key.Back: + if (CurrentPosition == 0) + return; + this.DeleteChar(); + break; + case Key.Clear: + break; + case Key.Delete: + if (selectionIsEmpty) { + if (!MoveRight ()) + return; + }else if (e.Shift) + currentInterface.Clipboard = this.SelectedText; + this.DeleteChar (); + break; + case Key.Enter: + case Key.KeypadEnter: + if (!selectionIsEmpty) + this.DeleteChar (); + this.InsertLineBreak (); + break; + case Key.Escape: + Text = ""; + CurrentColumn = 0; + SelRelease = -1; + break; + case Key.Home: + if (e.Shift) { + if (selectionIsEmpty) + SelBegin = new Point (CurrentColumn, CurrentLine); + if (e.Control) + CurrentLine = 0; + CurrentColumn = 0; + SelRelease = new Point (CurrentColumn, CurrentLine); + break; + } + SelRelease = -1; + if (e.Control) + CurrentLine = 0; + CurrentColumn = 0; + break; + case Key.End: + if (e.Shift) { + if (selectionIsEmpty) + SelBegin = CurrentPosition; + if (e.Control) + CurrentLine = int.MaxValue; + CurrentColumn = int.MaxValue; + SelRelease = CurrentPosition; + break; + } + SelRelease = -1; + if (e.Control) + CurrentLine = int.MaxValue; + CurrentColumn = int.MaxValue; + break; + case Key.Insert: + if (e.Shift) + this.Insert (currentInterface.Clipboard); + else if (e.Control && !selectionIsEmpty) + currentInterface.Clipboard = this.SelectedText; + break; + case Key.Left: + if (e.Shift) { + if (selectionIsEmpty) + SelBegin = new Point(CurrentColumn, CurrentLine); + if (e.Control) + GotoWordStart (); + else if (!MoveLeft ()) + return; + SelRelease = CurrentPosition; + break; + } + SelRelease = -1; + if (e.Control) + GotoWordStart (); + else + MoveLeft(); + break; + case Key.Right: + if (e.Shift) { + if (selectionIsEmpty) + SelBegin = CurrentPosition; + if (e.Control) + GotoWordEnd (); + else if (!MoveRight ()) + return; + SelRelease = CurrentPosition; + break; + } + SelRelease = -1; + if (e.Control) + GotoWordEnd (); + else + MoveRight (); + break; + case Key.Up: + if (e.Shift) { + if (selectionIsEmpty) + SelBegin = CurrentPosition; + CurrentLine--; + SelRelease = CurrentPosition; + break; + } + SelRelease = -1; + CurrentLine--; + break; + case Key.Down: + if (e.Shift) { + if (selectionIsEmpty) + SelBegin = CurrentPosition; + CurrentLine++; + SelRelease = CurrentPosition; + break; + } + SelRelease = -1; + CurrentLine++; + break; + case Key.Menu: + break; + case Key.NumLock: + break; + case Key.PageDown: + if (e.Shift) { + if (selectionIsEmpty) + SelBegin = CurrentPosition; + CurrentLine += visibleLines; + SelRelease = CurrentPosition; + break; + } + SelRelease = -1; + CurrentLine += visibleLines; + break; + case Key.PageUp: + if (e.Shift) { + if (selectionIsEmpty) + SelBegin = CurrentPosition; + CurrentLine -= visibleLines; + SelRelease = CurrentPosition; + break; + } + CurrentLine -= visibleLines; + break; + case Key.RWin: + break; + case Key.Tab: + this.Insert ("\t"); + break; + default: + break; + } + RegisterForGraphicUpdate(); + } + public override void onKeyPress (object sender, KeyPressEventArgs e) + { + base.onKeyPress (sender, e); + + this.Insert (e.KeyChar.ToString()); + + SelRelease = -1; + SelBegin = -1; //new Point(CurrentColumn, SelBegin.Y); + + RegisterForGraphicUpdate(); + } + #endregion + + + /// Compute x offset in cairo unit from text position + double GetXFromTextPointer(Context gr, Point pos) + { + try { + string l = buffer [pos.Y].RawText.Substring (0, pos.X). + Replace ("\t", new String (' ', Interface.TabSize)); + return gr.TextExtents (l).XAdvance; + } catch{ + return -1; + } + } + + /// line break could be '\r' or '\n' or '\r\n' + string detectLineBreakKind(){ + string strLB = ""; + + if (string.IsNullOrEmpty(_text)) + return Interface.LineBreak; + int i = 0; + while ( i < _text.Length) { + if (_text [i] == '\r') { + strLB += '\r'; + i++; + } + if (i < _text.Length) { + if (_text [i] == '\r') + return "\r"; + if (_text [i] == '\n') + strLB += '\n'; + } + if (!string.IsNullOrEmpty (strLB)) + return strLB; + i++; + } + return Interface.LineBreak; + } + + void updateVisibleLines(){ + visibleLines = (int)Math.Floor ((double)ClientRectangle.Height / fe.Height); + MaxScrollY = Math.Max (0, buffer.Count - visibleLines); + + 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); + MaxScrollX = Math.Max (0, buffer.longestLineCharCount - visibleColumns); + + System.Diagnostics.Debug.WriteLine ("update visible columns: " + visibleColumns); + System.Diagnostics.Debug.WriteLine ("update MaxScrollX: " + MaxScrollX); + } + + + + /// + /// Insert new string at caret position, should be sure no line break is inside. + /// + /// String. + protected void Insert(string str) + { + if (!selectionIsEmpty) + this.DeleteChar (); + string[] strLines = Regex.Split (str, "\r\n|\r|\n|" + @"\\n").ToArray(); + buffer [CurrentLine].RawText = buffer [CurrentLine].RawText.Insert (CurrentColumn, strLines[0]); + CurrentColumn += strLines[0].Length; + for (int i = 1; i < strLines.Length; i++) { + InsertLineBreak (); + buffer [CurrentLine].RawText = buffer [CurrentLine].RawText.Insert (CurrentColumn, strLines[i]); + CurrentColumn += strLines[i].Length; + } + OnTextChanged (this, null); + RegisterForGraphicUpdate(); + } + + /// + /// Insert a line break. + /// + protected void InsertLineBreak() + { + buffer.InsertLine(CurrentLine + 1, new SourceLine (buffer[CurrentLine].RawText.Substring(CurrentColumn))); + buffer [CurrentLine].RawText = buffer [CurrentLine].RawText.Substring (0, CurrentColumn); + CurrentLine++; + CurrentColumn = 0; + OnTextChanged (this, null); + } + } +} \ No newline at end of file diff --git a/src/SourceLine.cs b/src/SourceLine.cs new file mode 100644 index 0000000..cb777c6 --- /dev/null +++ b/src/SourceLine.cs @@ -0,0 +1,156 @@ +// +// SourceLine.cs +// +// Author: +// Jean-Philippe Bruyère +// +// 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 . +using System; +using System.Collections.Generic; + +namespace Crow +{ + /// + /// basic structure for line of source code + /// + public class SourceLine + { + public string RawText; + public List Tokens = null; + + public int Length { + get { return string.IsNullOrEmpty (RawText)? 0 : RawText.Length; } + } + public char this[int index]{ + get { return RawText [index]; } + + } + int ptr; //character pointer in the source string + Token tok; //current token parsed before addition to the token list + + public SourceLine () + { + } + public SourceLine (string rawText){ + RawText = rawText; + } + + /// + /// Tokenize this instance. + /// This tokenization step is used for display mainly, so literals are not interpreted + /// + public bool Tokenize(){ + Tokens = new List(); + ptr = 0; + + while (!eol) { + Char c = readChar (); + + //block comments + if (tok?.Type == TokenType.BlockComment) { + tok.Content += c; + if (c == '*') { + if (peekChar () == '/') { + tok.Content += readChar (); + saveCurTok (); + } + } + continue; + } else if (tok?.Type == TokenType.StringLiteral) { + tok.Content += c; + if (c == '\\')//may escape " char, so next char is read; + tok.Content += readChar (); + else if (c == '"') + saveCurTok (); + continue; + } else if (tok?.Type == TokenType.CharacterLiteral) { + tok.Content += c; + if (c == '\\')//may escape ' char, so next char is read; + tok.Content += readChar (); + else if (c == '\'') + saveCurTok (); + continue; + } else if (tok?.Type == TokenType.WhiteSpace) { + if (char.IsWhiteSpace (c)) { + tok.Content += c; + continue; + } + saveCurTok (); + //if (char.IsLetter (c)) + + } + + //single char tokens + if (c == '{') + tok.Type = TokenType.OpenBlock; + else if (c == '}') + tok.Type = TokenType.CloseBlock; + else if (c == '(') + tok.Type = TokenType.OpenParenth; + else if (c == ')') + tok.Type = TokenType.CloseParenth; + + + if (tok == null) { + tok = new Token () { Content = new string (c, 1) }; + + if (char.IsWhiteSpace (c)) + tok.Type = TokenType.WhiteSpace; + else if (char.IsDigit (c)) + tok.Type = TokenType.DigitalLiteral; + else if (char.IsLetter (c)) + tok.Type = TokenType.Unknown; + else if (c == '"') + tok.Type = TokenType.StringLiteral; + else {//put here all single step parsing token, reseting tok directely + saveCurTok (); + } + } + + + + ptr++; + } + return true; + } + /// add tok to token list and reset it to null + void saveCurTok(){ + Tokens.Add (tok); + tok = null; + } + public void PresetCurrentToken (TokenType tokType, string content = null){ + tok = new Token (tokType,content); + } + + bool eol { get { return ptr < RawText.Length; }} + char readChar() { + char c = RawText [ptr]; + ptr++; + return c; + } + char peekChar() { + return RawText [ptr]; + } + +// public static implicit operator SourceLine(string rawText){ +// return new SourceLine() { RawText = rawText }; +// } +// public static implicit operator string(SourceLine sl){ +// return sl?.RawText; +// } + } +} + diff --git a/src/TextBuffer.cs b/src/TextBuffer.cs deleted file mode 100644 index f4afe8e..0000000 --- a/src/TextBuffer.cs +++ /dev/null @@ -1,32 +0,0 @@ -// -// TextBuffer.cs -// -// Author: -// Jean-Philippe Bruyère -// -// 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 . -using System; - -namespace Crow -{ - public class TextBuffer - { - public TextBuffer () - { - } - } -} - diff --git a/src/Token.cs b/src/Token.cs new file mode 100644 index 0000000..7639ca2 --- /dev/null +++ b/src/Token.cs @@ -0,0 +1,60 @@ +// +// Token.cs +// +// Author: +// Jean-Philippe Bruyère +// +// 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 . +using System; + +namespace Crow +{ + public enum TokenType { + Unknown, + WhiteSpace, + OpenParenth, + CloseParenth, + OpenBlock, + CloseBlock, + StatementEnding, + UnaryOp, + BinaryOp, + Affectation, + StringLiteral, + CharacterLiteral, + DigitalLiteral, + Literal, + Identifier, + Indexer, + Type, + LineComment, + BlockComment, + } + public class Token + { + public TokenType Type; + public string Content; + + public Token () + { + } + public Token (TokenType tokType, string content = null){ + Type = tokType; + Content = content; + } + } +} + diff --git a/ui/main.crow b/ui/main.crow index 84f1700..981b5ca 100755 --- a/ui/main.crow +++ b/ui/main.crow @@ -21,9 +21,9 @@ - - + \ No newline at end of file