Initial commit
authorChip Black <bytex64@bytex64.net>
Sat, 27 Sep 2008 08:26:19 +0000 (03:26 -0500)
committerChip Black <bytex64@bytex64.net>
Sat, 27 Sep 2008 08:26:19 +0000 (03:26 -0500)
- Working file open dialog w/sexy tab completion.
- Can open files via open dialog and command-line.
- Text is rendered using pango, but cursor position is still basic (it's
  especially wrong for CJK text).

FileInput.cs [new file with mode: 0644]
Interfaces.cs [new file with mode: 0644]
Makefile [new file with mode: 0644]
TextDisplay.cs [new file with mode: 0644]
TextDocument.cs [new file with mode: 0644]
TextInput.cs [new file with mode: 0644]
main.cs [new file with mode: 0644]

diff --git a/FileInput.cs b/FileInput.cs
new file mode 100644 (file)
index 0000000..f801109
--- /dev/null
@@ -0,0 +1,87 @@
+using System;
+using System.IO;
+using System.Collections;
+using Cairo;
+using Gtk;
+using Gdk;
+
+class FileSelector : TextInput {
+       private string filestr;
+
+       public FileSelector() : base(Directory.GetCurrentDirectory() + System.IO.Path.DirectorySeparatorChar) {
+               Complete();
+       }
+
+       protected override void draw(Cairo.Context gr, int width, int height) {
+               base.draw(gr, width, height);
+
+               gr.LineWidth = 1.0;
+               gr.NewPath();
+               gr.MoveTo(0, 13.5);
+               gr.LineTo(width, 13.5);
+               gr.Stroke();
+
+               if (filestr != null) {
+                       Pango.Layout layout = new Pango.Layout(this.PangoContext);
+                       layout.Width = Pango.Units.FromPixels(width);
+                       layout.FontDescription = Pango.FontDescription.FromString("Courier 12");
+                       layout.Wrap = Pango.WrapMode.WordChar;
+                       layout.SetMarkup(filestr);
+                       gr.MoveTo(0, 15);
+                       Pango.CairoHelper.ShowLayout(gr, layout);
+               }
+       }
+
+       [GLib.ConnectBefore()]
+       public override void KeyPress(object o, KeyPressEventArgs args) {
+               base.KeyPress(o, args);
+               switch(args.Event.Key) {
+               case Gdk.Key.Tab:
+                       Complete();
+                       QueueDraw();
+                       break;
+               }
+       }
+
+       private void Complete() {
+               string dir = System.IO.Path.GetDirectoryName(Value);
+               string search = System.IO.Path.GetFileName(Value);
+               string[] files = Directory.GetFileSystemEntries(dir, search + "*");
+               filestr = null;
+               if (files.Length == 1 && Directory.Exists(files[0])) {
+                       Value = files[0] + System.IO.Path.DirectorySeparatorChar;
+                       Complete();
+                       return;
+               } else if (files.Length > 0) {
+                       int i;
+                       filestr = "";
+
+                       string common = System.IO.Path.GetFileName(files[0]);
+
+                       for (i = 0; i < files.Length; i++) {
+                               files[i] = System.IO.Path.GetFileName(files[i]);
+                               common = CommonPart(common, files[i]);
+                       }
+
+                       Value = dir + System.IO.Path.DirectorySeparatorChar + common;
+                       cursor = Value.Length;
+
+                       for (i = 0; i < files.Length; i++) {
+                               string f = files[i];
+                               filestr += "<span color=\"#7F7F7F\">" + common + "</span>" + f.Substring(common.Length) + "  ";
+                       }
+               }
+       }
+
+       private string CommonPart(string s1, string s2) {
+               CharEnumerator i1 = s1.GetEnumerator();
+               CharEnumerator i2 = s2.GetEnumerator();
+               int c = 0;
+
+               while (i1.MoveNext() && i2.MoveNext()) {
+                       if (i1.Current != i2.Current) break;
+                       c++;
+               }
+               return s1.Substring(0, c);
+       }
+}
diff --git a/Interfaces.cs b/Interfaces.cs
new file mode 100644 (file)
index 0000000..9eeaf09
--- /dev/null
@@ -0,0 +1,5 @@
+using Gtk;
+
+interface IKeyPress {
+       void KeyPress(object o, KeyPressEventArgs args);
+}
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..9e3e810
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,12 @@
+CSFLAGS = -debug -pkg:gtk-sharp-2.0 -r:Mono.Cairo
+SOURCES = main.cs Interfaces.cs TextDisplay.cs TextDocument.cs TextInput.cs \
+         FileInput.cs
+EXE = Nebula.exe
+
+all: $(EXE)
+
+clean:
+       rm -f $(EXE)
+
+$(EXE): $(SOURCES)
+       mcs $(CSFLAGS) -out:$@ $^
diff --git a/TextDisplay.cs b/TextDisplay.cs
new file mode 100644 (file)
index 0000000..0abb9e9
--- /dev/null
@@ -0,0 +1,102 @@
+using System;
+using Cairo;
+using Pango;
+using Gtk;
+
+public class TextDisplay : DrawingArea, IKeyPress {
+       private TextDocument doc = null;
+       private FontDescription font;
+
+       public TextDisplay() {
+               font = Pango.FontDescription.FromString("Courier 12");
+               ModifyBg(StateType.Normal, new Gdk.Color(0xFF, 0xFF, 0xFF));
+       }
+
+       public void SetDocument(TextDocument doc) {
+               this.doc = doc;
+       }
+
+        void draw(Cairo.Context gr, int width, int height) {
+               if (doc == null) return;
+
+               Pango.Layout layout = new Pango.Layout(this.PangoContext);
+               layout.Width = Pango.Units.FromPixels(width);
+               layout.FontDescription = font;
+
+               //gr.Operator = Operator.Atop;
+               gr.SetSourceRGB(0,0,0);
+               int h = Pango.Units.ToPixels(layout.FontDescription.Size) + 1;
+               int y = 0;
+               int line = 0;
+               while (line < doc.Lines.Count && y < height) {
+                       layout.SetText((String)doc.Lines[line]);
+                       gr.MoveTo(0, y);
+                       Pango.CairoHelper.ShowLayout(gr, layout);
+                       y += h;
+                       line++;
+               }
+
+               //gr.Operator = Operator.Xor;
+               gr.NewPath();
+               gr.Rectangle(new Cairo.Rectangle(doc.cursor.x*8, doc.cursor.y*h, 8, h));
+               gr.Fill();
+        }
+
+        protected override bool OnExposeEvent(Gdk.EventExpose args)
+        {
+                Gdk.Window win = args.Window;                
+
+               #if OLD_SYSTEMS
+               //
+               // For old versions of Gtk# (before 2.8), you need the helper class
+               // available in gtk-sharp/sample/GtkCairo.cs
+               //
+                Cairo.Context g = Gdk.Graphics.CreateDrawable (win);
+                #else
+               //
+               // Starting with Gtk 2.8 Gtk has direct support for
+               // Cairo, as its built on top of it, on older
+               // versions, a helper routine is used
+               //
+                Cairo.Context g = Gdk.CairoHelper.Create (args.Window);
+               #endif
+
+                int x, y, w, h, d;
+                win.GetGeometry(out x, out y, out w, out h, out d);
+
+                draw (g, w, h);
+               ((IDisposable) g.Target).Dispose();
+               ((IDisposable) g).Dispose();
+                return true;
+        }
+
+       public void Update() {
+               QueueDraw();
+       }
+
+
+        [GLib.ConnectBefore ()]
+        public void KeyPress(object o, KeyPressEventArgs args) {
+               if (doc == null) return;
+
+                if ((args.Event.State & Gdk.ModifierType.ControlMask) != 0) {
+                } else if ((args.Event.State & Gdk.ModifierType.Mod1Mask) != 0) {
+                } else {
+                        switch(args.Event.Key) {
+                        case Gdk.Key.Up:
+                        case Gdk.Key.Down:
+                        case Gdk.Key.Left:
+                        case Gdk.Key.Right:
+                        case Gdk.Key.Page_Up:
+                        case Gdk.Key.Page_Down:
+                        case Gdk.Key.Home:
+                        case Gdk.Key.End:
+                                doc.MoveCursor(args.Event.Key);
+                                break;
+                        default:
+                                doc.AddChar((int)args.Event.KeyValue);
+                                break;
+                        }
+                }
+       }
+}
diff --git a/TextDocument.cs b/TextDocument.cs
new file mode 100644 (file)
index 0000000..e05e992
--- /dev/null
@@ -0,0 +1,190 @@
+using System;
+using System.Collections;
+using System.IO;
+
+public struct Cursor {
+       private int _x;
+       private int _y;
+
+       public Cursor(int x, int y) {
+               _x = x;
+               _y = y;
+       }
+
+       public int x {
+               get {
+                       return _x;
+               }
+               set {
+                       if (value < 0) _x = 0;
+                       else _x = value;
+               }
+       }
+
+       public int y {
+               get {
+                       return _y;
+               }
+               set {
+                       if (value < 0) _y = 0;
+                       else _y = value;
+               }
+       }
+}
+
+public class TextArray {
+       private ArrayList lines;
+
+       public TextArray() {
+               lines = new ArrayList();
+               lines.Add("");
+       }
+
+       public TextArray(string filename) {
+               lines = new ArrayList();
+               StreamReader file;
+               string line;
+
+               file = new StreamReader(filename);
+               while ((line = file.ReadLine()) != null) {
+                       lines.Add(line);
+               }
+       }
+
+       public ArrayList Lines {
+               get {
+                       return lines;
+               }
+       }
+
+       public void Insert(int x, int y, char c) {
+               while (lines.Count < y+1)
+                       lines.Add("");
+               string s = (string)lines[y];
+
+               if (x >= s.Length && (c == ' ' || c == '\t')) return;
+               if (x > s.Length) {
+                       lines[y] = s.PadRight(x) + c;
+               } else if (x <= s.Length) {
+                       lines[y] = s.Insert(x, new string(c, 1));
+               }
+       }
+
+       public void Delete(int x, int y) {
+               if (y >= lines.Count) return;
+
+               string s = (string)lines[y];
+               if (x >= s.Length) return;
+
+               if (s.Length == 0 && x == 0) {
+                       lines.RemoveAt(y);
+               } else {
+                       s.Remove(x, 1);
+               }
+       }
+
+       public void Backspace(int x, int y) {
+               if (x == 0 && y == 0) return;
+               if (y >= lines.Count) return;
+
+               string s = (string)lines[y];
+               if (x > s.Length) return;
+
+               if (s.Length == 0 && x == 0) {
+                       lines.RemoveAt(y);
+               } else if (x == 0 && y > 0) {
+                       lines.RemoveAt(y);
+                       lines[y-1] += s;
+               } else {
+                       lines[y] = s.Remove(x-1, 1);
+               }
+       }
+
+       public void Newline(int x, int y) {
+               if (y >= lines.Count) return;
+               string s = (string) lines[y];
+               if (x < s.Length) {
+                       lines[y] = s.Substring(0, x);
+                       lines.Insert(y + 1, s.Substring(x));
+               } else {
+                       if (y == lines.Count - 1) return;
+                       lines.Insert(y + 1, "");
+               }
+       }
+
+       public int LineLength(int y) {
+               if (y >= lines.Count) return 0;
+               return ((string)lines[y]).Length;
+       }
+}
+
+public class TextDocument {
+       private TextArray text;
+       public Cursor cursor;
+
+       public ArrayList Lines {
+               get {
+                       return text.Lines;
+               }
+       }
+
+       public TextDocument() {
+               text = new TextArray();
+       }
+
+       public TextDocument(string filename) {
+               text = new TextArray(filename);
+       }
+
+       public void MoveCursor(Gdk.Key k) {
+               switch(k) {
+               case Gdk.Key.Up:
+                       cursor.y--;
+                       break;
+               case Gdk.Key.Down:
+                       cursor.y++;
+                       break;
+               case Gdk.Key.Left:
+                       cursor.x--;
+                       break;
+               case Gdk.Key.Right:
+                       cursor.x++;
+                       break;
+               case Gdk.Key.End:
+                       cursor.x = text.LineLength(cursor.y);
+                       break;
+               case Gdk.Key.Home:
+                       cursor.x = 0;
+                       break;
+               }
+       }
+
+       public void AddChar(int c) {
+               c = c & 0xFF;
+               if (c > 128) return;
+
+               switch(c) {
+               case 8:
+                       int l = 0;
+                       if (cursor.y > 0) l = text.LineLength(cursor.y-1);
+                       text.Backspace(cursor.x, cursor.y);
+                       if (cursor.x == 0) {
+                               cursor.y--;
+                               if (cursor.y >= text.Lines.Count) return;
+                               cursor.x = l;
+                       } else {
+                               cursor.x--;
+                       }
+                       break;
+               case 13:
+                       text.Newline(cursor.x, cursor.y);
+                       cursor.x = 0;
+                       cursor.y++;
+                       break;
+               default:
+                       text.Insert(cursor.x, cursor.y, (char)c);
+                       cursor.x++;
+                       break;
+               }
+       }
+}
diff --git a/TextInput.cs b/TextInput.cs
new file mode 100644 (file)
index 0000000..4934d20
--- /dev/null
@@ -0,0 +1,134 @@
+using System;
+using Cairo;
+using Pango;
+using Gtk;
+using Gdk;
+
+public class TextInputEventArgs : EventArgs {
+       public string Value;
+
+       public TextInputEventArgs(string s) {
+               Value = s;
+       }
+}
+
+
+public class TextInput : DrawingArea, IKeyPress {
+       public string Value;
+       protected int cursor;
+       private string killring = null;
+       Pango.FontDescription font;
+
+       public delegate void SelectHandler(object o, TextInputEventArgs s);
+       public event SelectHandler Selected;
+
+       public TextInput() : this("") { }
+
+       public TextInput(string Value) {
+               this.Value = Value;
+               cursor = Value.Length;
+               font = Pango.FontDescription.FromString("Courier 12");
+               SetSizeRequest(-1, 12);
+               ModifyBg(StateType.Normal, new Gdk.Color(0xFF, 0xFF, 0xFF));
+       }
+
+        protected virtual void draw (Cairo.Context gr, int width, int height) {
+               gr.SetSourceRGB(0,0,0);
+               gr.NewPath();
+               gr.Rectangle(new Cairo.Rectangle(0, 0, width, height));
+               gr.Stroke();
+
+               Pango.Layout layout = new Pango.Layout(this.PangoContext);
+               layout.Width = Pango.Units.FromPixels(width);
+               layout.Alignment = Pango.Alignment.Left;
+               layout.FontDescription = font;
+               layout.SetText(Value);
+               Pango.CairoHelper.ShowLayout(gr, layout);
+
+               gr.NewPath();
+               int h = Pango.Units.ToPixels(layout.FontDescription.Size) + 1;
+               gr.Rectangle(new Cairo.Rectangle(cursor*8, 0, 7, h));
+               gr.Fill();
+        }
+
+        protected override bool OnExposeEvent (Gdk.EventExpose args) {
+                Gdk.Window win = args.Window;                
+                Cairo.Context g = Gdk.CairoHelper.Create (args.Window);
+
+                int x, y, w, h, d;
+                win.GetGeometry(out x, out y, out w, out h, out d);
+
+                draw(g, w, h);
+
+               ((IDisposable) g.Target).Dispose();
+               ((IDisposable) g).Dispose();
+                return true;
+        }
+
+       [GLib.ConnectBefore ()]
+       public virtual void KeyPress(object o, KeyPressEventArgs args) {
+               if ((args.Event.State & ModifierType.ControlMask) != 0) {
+                       switch(args.Event.Key) {
+                       case Gdk.Key.u:
+                               killring = Value.Substring(0, cursor);
+                               Value = Value.Substring(cursor);
+                               cursor = 0;
+                               break;
+                       case Gdk.Key.k:
+                               killring = Value.Substring(cursor);
+                               Value = Value.Substring(0, cursor);
+                               break;
+                       case Gdk.Key.w:
+                               int ws = Value.LastIndexOfAny(" ".ToCharArray());
+                               if (ws == -1) {
+                                       killring = Value;
+                                       Value = "";
+                               } else {
+                                       killring = Value.Substring(ws + 1);
+                                       Value = Value.Substring(0, ws);
+                               }
+                               cursor = Value.Length;
+                               break;
+                       case Gdk.Key.y:
+                               if (killring == null) return;
+                               Value = Value.Insert(cursor, killring);
+                               cursor += killring.Length;
+                               break;
+                       }
+               } else {
+                       switch(args.Event.Key) {
+                       case Gdk.Key.BackSpace:
+                               if (cursor == 0) return;
+                               cursor--;
+                               Value = Value.Remove(cursor, 1);
+                               break;
+                       case Gdk.Key.Return:
+                               if (Selected != null) {
+                                       TextInputEventArgs t = new TextInputEventArgs(Value);
+                                       Selected(this, t);
+                                       Selected = null;
+                               }
+                               break;
+                       case Gdk.Key.Left:
+                               if (cursor > 0) cursor--;
+                               break;
+                       case Gdk.Key.Right:
+                               if (cursor < Value.Length) cursor++;
+                               break;
+                       case Gdk.Key.Escape:
+                               if (Selected != null) {
+                                       TextInputEventArgs t = new TextInputEventArgs(null);
+                                       Selected(this, t);
+                                       Selected = null;
+                               }
+                               break;
+                       default:
+                               if (args.Event.KeyValue > 128) return;
+                               Value = Value.Insert(cursor, new String((char)args.Event.KeyValue, 1));
+                               cursor++;
+                               break;
+                       }
+               }
+               QueueDraw();
+       }
+}
diff --git a/main.cs b/main.cs
new file mode 100644 (file)
index 0000000..bdfdd49
--- /dev/null
+++ b/main.cs
@@ -0,0 +1,87 @@
+using System;
+using System.Reflection;
+using System.Runtime.InteropServices;
+using Cairo;
+using Gtk;
+
+public class Nebula {
+       static Gtk.Window window;
+       static VBox stack;
+        static TextDisplay display;
+       static TextDocument doc;
+       static bool fullscreened = false;
+
+        static void Main (string[] args) {
+                Application.Init ();
+                window = new Gtk.Window("Nebula");
+               stack = new VBox(false, 0);
+
+               if (args.Length > 0) {
+                       doc = new TextDocument(args[0]);
+               } else {
+                       doc = new TextDocument();
+               }
+                display = new TextDisplay();
+               display.SetDocument(doc);
+
+                stack.Add(display);
+
+                window.Add(stack);
+                window.Resize(640,480);
+                window.ShowAll();
+
+               window.KeyPressEvent += new KeyPressEventHandler(KeyPress);
+               SelectInput(display);
+               window.DeleteEvent += delegate(object o, DeleteEventArgs e) {
+                       Application.Quit();
+               };
+
+                Application.Run();
+        }
+
+       static void SelectInput(IKeyPress w) {
+               window.KeyPressEvent -= display.KeyPress;
+               window.KeyPressEvent += w.KeyPress;
+       }
+
+       [GLib.ConnectBefore ()]
+       static void KeyPress(object o, KeyPressEventArgs args) {
+               if ((args.Event.State & Gdk.ModifierType.ControlMask) != 0) {
+                       switch(args.Event.Key) {
+                       case Gdk.Key.n:
+                               doc = new TextDocument();
+                               display.SetDocument(doc);
+                               break;
+                       case Gdk.Key.o:
+                               TextInput filename = new FileSelector();
+                               stack.PackEnd(filename);
+                               SelectInput(filename);
+                               filename.Show();
+                               filename.Selected += delegate(object o2, TextInputEventArgs t) {
+                                       if (t.Value != null) {
+                                               doc = new TextDocument(t.Value);
+                                               display.SetDocument(doc);
+                                       }
+                                       SelectInput(display);
+                                       stack.Remove(filename);
+                                       filename.Destroy();
+                               };
+                               break;
+                       case Gdk.Key.q:
+                               Application.Quit();
+                               break;
+                       }
+               } else if ((args.Event.State & Gdk.ModifierType.Mod1Mask) != 0) {
+                       switch(args.Event.Key) {
+                       case Gdk.Key.Return:
+                               if (fullscreened)
+                                       ((Window)o).Unfullscreen();
+                               else
+                                       ((Window)o).Fullscreen();
+                               fullscreened = ! fullscreened;
+                               break;
+                       }
+               }
+               display.Update();
+       }
+}