Initial commit
- 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).
+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);
+ }
+}
+using Gtk;
+
+interface IKeyPress {
+ void KeyPress(object o, KeyPressEventArgs args);
+}
+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:$@ $^
+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;
+ }
+ }
+ }
+}
+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;
+ }
+ }
+}
+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();
+ }
+}
+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();
+ }
+}