/TextDisplay.cs
using System;
using System.Timers;
using Cairo;
using Pango;
using Gdk;
using Gtk;

public class TextDisplay : DrawingArea, IKeyPress {
	private TextDocument doc = null;
	private Pango.Font font;
	// FIXME: Proportional fonts?
	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);

		controlHelp = new HelpBox(this.PangoContext, "control_help");
		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 = 0x7FFFFFFF; //Pango.Units.FromPixels(width);
		layout.FontDescription = font.Describe();

		//gr.Operator = Operator.Atop;
		gr.SetSourceRGB(0,0,0);
		layout.SetText(doc.GetTextWindow());
		gr.MoveTo(0, 0);
		Pango.CairoHelper.ShowLayout(gr, layout);

		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.Fill();

		if (showHelp) {
			controlHelp.draw(gr, width, height);
		}
        }

        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;
        }

	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);
	}

	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;
		}
	}
}