/TextInput.cs
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(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();
	}
}