commit:c87b4593cee7ebd588e0fe8541a3067061d02cb0
author:Chip Black
committer:Chip Black
date:Mon Sep 29 19:40:51 2008 -0500
parents:822c67be52521bd601cd01b66974bb6c0df967bf
Added help popup, Ctrl-Left and Right cursor movement by word
diff --git a/HelpBox.cs b/HelpBox.cs
line changes: +123/-0
index 0000000..664d5d9
--- /dev/null
+++ b/HelpBox.cs
@@ -0,0 +1,123 @@
+using System.Collections;
+using System.IO;
+using System;
+using Cairo;
+using Pango;
+
+public struct HelpItem {
+	public string Key;
+	public string Text;
+
+	public HelpItem(string key, string text) {
+		Key = key;
+		Text = text;
+	}
+
+	public override string ToString() {
+		return "<b>" + Key + "</b>\t" + Text;
+	}
+}
+
+public class HelpBox {
+	private Pango.Context pc;
+	private Pango.FontDescription titleFont;
+	private Pango.FontDescription textFont;
+	private string helpTitle;
+	private HelpItem[] helpText;
+	private int Width = 480;
+	private int Height = 360;
+	private int Columns = 3;
+
+	public HelpBox(Pango.Context pc, string title, HelpItem[] items) {
+		this.pc = pc;
+		helpTitle = title;
+		helpText = items;
+		init();
+	}
+
+	public HelpBox(Pango.Context pc, string filename) {
+		StreamReader file = new StreamReader(filename);
+		string line;
+		ArrayList items = new ArrayList();
+
+		this.pc = pc;
+		helpTitle = file.ReadLine();
+		while ((line = file.ReadLine()) != null) {
+			string[] i = line.Split(null);
+			if (i.Length == 2) {
+				items.Add(new HelpItem(i[0], i[1]));
+			}
+		}
+		helpText = new HelpItem[items.Count];
+		for (int i = 0; i < items.Count; i++) {
+			helpText[i] = (HelpItem) items[i];
+		}
+		init();
+	}
+
+	private void init() {
+		titleFont = Pango.FontDescription.FromString("Helvetica Bold 24");
+		textFont = Pango.FontDescription.FromString("Helvetica 16");
+		Pango.FontMetrics fmTitle = pc.GetMetrics(titleFont, null);
+		Pango.FontMetrics fmText = pc.GetMetrics(textFont, null);
+		int hTitle = Pango.Units.ToPixels(fmTitle.Ascent + fmTitle.Descent);
+		int hText = Pango.Units.ToPixels(fmText.Ascent + fmText.Descent);
+
+		Width = 480;
+		for (int i = 0; i < helpText.Length; i++) {
+			Pango.Layout layout = new Pango.Layout(pc);
+			Pango.Rectangle r, r2;
+
+			layout.FontDescription = textFont;
+			layout.SetMarkup(helpText[i].ToString());
+			layout.GetExtents(out r, out r2);
+			int pw = Pango.Units.ToPixels(r2.Width);
+			if ((pw + 8) * Columns > Width) {
+				Width = (pw + 8) * Columns;
+			}
+		}
+		Height = hTitle + (helpText.Length / 3 + 1) * hText + 12;
+	}
+
+        public void draw(Cairo.Context gr, int width, int height) {
+		Pango.Layout layout;
+		Cairo.Rectangle r = new Cairo.Rectangle(
+			width / 2 - Width / 2,
+			height / 2 - Height / 2,
+			Width, Height);
+
+		gr.SetSourceRGBA(1.0, 1.0, 1.0, 0.8);
+		gr.NewPath();
+		gr.Rectangle(r);
+		gr.Fill();
+
+		gr.SetSourceRGB(0.0, 0.0, 0.0);
+		gr.NewPath();
+		gr.Rectangle(r);
+		gr.Stroke();
+
+		gr.MoveTo(r.X + 4, r.Y + 4);
+		layout = new Pango.Layout(pc);
+		layout.SetText(helpTitle);
+		layout.Width = Pango.Units.FromPixels((int)r.Width);
+		layout.FontDescription = titleFont;
+		Pango.CairoHelper.ShowLayout(gr, layout);
+
+		int ii = 0;
+		int ColumnWidth = Width / Columns;
+		int ColumnLines = helpText.Length / Columns + 1;
+		for (int i = 0; i < Columns; i++) {
+			gr.MoveTo(r.X + 4 + (i * ColumnWidth), r.Y + 38);
+			string column = "";
+			for (int j = 0; j < ColumnLines && ii < helpText.Length; j++) {
+				column += helpText[ii].ToString() + "\n";
+				ii++;
+			}
+			layout = new Pango.Layout(pc);
+			layout.SetMarkup(column);
+			layout.Width = Pango.Units.FromPixels(ColumnWidth);
+			layout.FontDescription = textFont;
+			Pango.CairoHelper.ShowLayout(gr, layout);
+		}
+        }
+}

diff --git a/Makefile b/Makefile
line changes: +1/-1
index 9e3e810..d4289da
--- a/Makefile
+++ b/Makefile
@@ -1,6 +1,6 @@
 CSFLAGS = -debug -pkg:gtk-sharp-2.0 -r:Mono.Cairo
 SOURCES = main.cs Interfaces.cs TextDisplay.cs TextDocument.cs TextInput.cs \
-	  FileInput.cs
+	  FileInput.cs HelpBox.cs
 EXE = Nebula.exe
 
 all: $(EXE)

diff --git a/TextDisplay.cs b/TextDisplay.cs
line changes: +66/-11
index 4e753de..282fc7f
--- a/TextDisplay.cs
+++ b/TextDisplay.cs
@@ -1,22 +1,30 @@
 using System;
+using System.Timers;
 using Cairo;
 using Pango;
+using Gdk;
 using Gtk;
 
 public class TextDisplay : DrawingArea, IKeyPress {
 	private TextDocument doc = null;
-	private Font font;
+	private Pango.Font font;
 	// FIXME: Proportional fonts?
-	private float fontwidth;
-	private float fontheight;
+	private float fontWidth;
+	private float fontHeight;
+
+	private Timer helpTimer;
+	private HelpBox controlHelp;
+	private bool showHelp;
 
 	public TextDisplay() {
 		FontDescription fd = Pango.FontDescription.FromString("Courier 12");
 		font = this.PangoContext.LoadFont(fd);
 		FontMetrics fm = font.GetMetrics(null);
-		fontheight = Pango.Units.ToPixels(fm.Ascent + fm.Descent);
-		fontwidth = fontheight * 0.5333f;
-		Console.WriteLine("Using {0} {1}pt ({2}x{3}px)", fd.Family, fd.Size, fontwidth, fontheight);
+		fontHeight = Pango.Units.ToPixels(fm.Ascent + fm.Descent);
+		fontWidth = fontHeight * 0.5333f;
+		Console.WriteLine("Using {0} {1}pt ({2}x{3}px)", fd.Family, fd.Size, fontWidth, fontHeight);
+
+		controlHelp = new HelpBox(this.PangoContext, "control_help");
 		ModifyBg(StateType.Normal, new Gdk.Color(0xFF, 0xFF, 0xFF));
 	}
 
@@ -37,14 +45,17 @@ public class TextDisplay : DrawingArea, IKeyPress {
 		gr.MoveTo(0, 0);
 		Pango.CairoHelper.ShowLayout(gr, layout);
 
-		gr.Operator = Operator.Xor;
+		gr.SetSourceRGB(0.0, 0.0, 0.0);
 		gr.NewPath();
-		gr.Rectangle((doc.cursor.x - doc.textorigin.x)*fontwidth, (doc.cursor.y - doc.textorigin.y)*fontheight, fontwidth, fontheight);
+		gr.Rectangle((doc.cursor.x - doc.textorigin.x)*fontWidth, (doc.cursor.y - doc.textorigin.y)*fontHeight, fontWidth, fontHeight);
 		gr.Fill();
+
+		if (showHelp) {
+			controlHelp.draw(gr, width, height);
+		}
         }
 
-        protected override bool OnExposeEvent(Gdk.EventExpose args)
-        {
+        protected override bool OnExposeEvent(Gdk.EventExpose args) {
                 Gdk.Window win = args.Window;                
 
 		#if OLD_SYSTEMS
@@ -74,17 +85,61 @@ public class TextDisplay : DrawingArea, IKeyPress {
 	protected override void OnSizeAllocated(Gdk.Rectangle r) {
 		base.OnSizeAllocated(r);
 		if (doc != null)
-			doc.SetPageSize((int)(r.Width / fontwidth) - 1, (int)(r.Height / fontheight) - 1);
+			doc.SetPageSize((int)(r.Width / fontWidth) - 1, (int)(r.Height / fontHeight) - 1);
 	}
 
 	public void Update() {
 		QueueDraw();
 	}
 
+	public void ShowHelp() {
+		showHelp = true;
+		helpTimer = null;
+		QueueDraw();
+	}
+
+	public void HideHelp() {
+		if (helpTimer != null) {
+			helpTimer.Stop();
+			helpTimer = null;
+		}
+		if (showHelp)
+			showHelp = false;
+		QueueDraw();
+	}
 
         [GLib.ConnectBefore ()]
         public void KeyPress(object o, KeyPressEventArgs args) {
 		if (doc == null) return;
+		if ((args.Event.State & ModifierType.ControlMask) != 0) {
+			switch(args.Event.Key) {
+			default:
+				HideHelp();
+				break;
+			}
+		} else {
+			switch(args.Event.Key) {
+			case Gdk.Key.Control_L:
+			case Gdk.Key.Control_R:
+				helpTimer = new Timer();
+				helpTimer.Elapsed += delegate(object o2, ElapsedEventArgs e) {
+					ShowHelp();
+				};
+				helpTimer.AutoReset = false;
+				helpTimer.Interval = 1000;
+				helpTimer.Start();
+				break;
+			}
+		}
 		doc.KeyPress(o, args);
 	}
+
+	public void KeyRelease(object o, KeyReleaseEventArgs e) {
+		switch(e.Event.Key) {
+		case Gdk.Key.Control_L:
+		case Gdk.Key.Control_R:
+			HideHelp();
+			break;
+		}
+	}
 }

diff --git a/TextDocument.cs b/TextDocument.cs
line changes: +69/-4
index 4f1103f..21f813a
--- a/TextDocument.cs
+++ b/TextDocument.cs
@@ -151,9 +151,13 @@ public class TextDocument : IKeyPress {
 	public string GetTextWindow() {
 		string s = "";
 
-		for (int i = textorigin.y; textorigin.y + i < text.Lines.Count && i < pageheight; i++) {
-			string l = (string)text.Lines[i];
-			s += l.Substring(textorigin.x) + "\n";
+		for (int i = 0; i <= pageheight + 1 && textorigin.y + i < text.Lines.Count; i++) {
+			string l = (string)text.Lines[textorigin.y + i];
+			if (textorigin.x > l.Length) {
+				s += "\n";
+			} else {
+				s += l.Substring(textorigin.x) + "\n";
+			}
 		}
 		return s;
 	}
@@ -198,12 +202,72 @@ public class TextDocument : IKeyPress {
 			cursor.y += pageheight;
 			break;
 		}
-		FixTextWindow();
+	}
+
+	public void NextWord() {
+		if (cursor.x >= text.LineLength(cursor.y)) {
+			cursor.y++;
+			cursor.x = 0;
+		} else {
+			string s = (String)text.Lines[cursor.y];
+			int i = s.IndexOf(' ', cursor.x);
+			if (i == -1) {
+				cursor.y++;
+				cursor.x = 0;
+			} else {
+				cursor.x = i + 1;
+			}
+		}
+	}
+
+	public void PreviousWord() {
+		if (cursor.y > text.Lines.Count) {
+			cursor.x = 0;
+			cursor.y--;
+		} else if (cursor.y == text.Lines.Count) {
+			string s = (String)text.Lines[text.Lines.Count - 1];
+			int i = s.LastIndexOf(' ');
+			if (i == -1) {
+				cursor.x = 0;
+			} else {
+				cursor.x = i + 1;
+			}
+			cursor.y--;
+		} else if (cursor.x == 0 && cursor.y > 0) {
+			string s = (String)text.Lines[cursor.y - 1];
+			int i = s.LastIndexOf(' ');
+			if (i == -1) {
+				cursor.x = 0;
+			} else {
+				cursor.x = i + 1;
+			}
+			cursor.y--;
+		} else if (cursor.x == 0 && cursor.y == 0) {
+			return;
+		} else {
+			string s = (String)text.Lines[cursor.y];
+			if (cursor.x >= s.Length)
+				cursor.x = s.Length - 1;
+			int i = s.LastIndexOf(' ', cursor.x - 2, cursor.x - 2);
+			if (i == -1) {
+				cursor.x = 0;
+			} else {
+				cursor.x = i + 1;
+			}
+		}
 	}
 
 	[GLib.ConnectBefore ()]
 	public void KeyPress(object o, Gtk.KeyPressEventArgs args) {
 		if ((args.Event.State & Gdk.ModifierType.ControlMask) != 0) {
+			switch(args.Event.Key) {
+			case Gdk.Key.Right:
+				NextWord();
+				break;
+			case Gdk.Key.Left:
+				PreviousWord();
+				break;
+			}
 		} else if ((args.Event.State & Gdk.ModifierType.Mod1Mask) != 0) {
 		} else {
 			switch(args.Event.Key) {
@@ -242,6 +306,7 @@ public class TextDocument : IKeyPress {
 				break;
 			}
 		}
+		FixTextWindow();
 	}
 	
 	public void AddChar(int c) {

diff --git a/control_help b/control_help
line changes: +7/-0
index 0000000..3999d55
--- /dev/null
+++ b/control_help
@@ -0,0 +1,7 @@
+Control Hotkeys
+N	New
+O	Open
+S	Save
+Q	Quit
+->	Next Word
+<-	Previous Word

diff --git a/main.cs b/main.cs
line changes: +2/-1
index f9570c5..1978b5f
--- a/main.cs
+++ b/main.cs
@@ -31,6 +31,7 @@ public class Nebula {
                 window.ShowAll();
 
 		window.KeyPressEvent += new KeyPressEventHandler(KeyPress);
+		window.KeyReleaseEvent += display.KeyRelease;
 		SelectInput(display);
 		window.DeleteEvent += delegate(object o, DeleteEventArgs e) {
 			Application.Quit();
@@ -65,7 +66,7 @@ public class Nebula {
 					}
 					SelectInput(display);
 					stack.Remove(input);
-					input.Destroy();
+					input = null;
 				};
 				break;
 			case Gdk.Key.s: