changeset 0:e907d2c54ec3

Initial import
author David Bryant <daveb@acres.com.au>
date Wed, 13 May 2009 15:42:39 +0930
parents
children c18e3f93d114
files .hgignore build.sh canvas.d gui.d handler.d icanvas.d import/canvas.d import/geometry.d import/model.d import/network.d import/new.d import/p-model.d import/types.d import/undo.d itoolstack.d tk/events.d tk/geometry.d tk/geometry2.d tk/gtk_support.d tk/misc.d tk/tool.d tk/types.d tools.d
diffstat 23 files changed, 1781 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.hgignore	Wed May 13 15:42:39 2009 +0930
@@ -0,0 +1,2 @@
+gui
+.obj
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/build.sh	Wed May 13 15:42:39 2009 +0930
@@ -0,0 +1,14 @@
+#!/bin/sh
+
+dmd \
+        -ofgui \
+        gui.d handler.d icanvas.d canvas.d \
+        tk/geometry.d tk/gtk_support.d tk/misc.d tk/types.d tk/events.d \
+        tk/geometry2.d \
+        -od.obj \
+        -I"${DMD_BASE}/include/d" \
+        -L-lgtkd -L-ldl
+
+
+#dmd gui.d -ofgui -I"${DMD_BASE}/include/d" -S"${DMD_BASE}/lib" -no-export-dynamic -L-ldl -oq.obj
+#rebuild gui.d -ofgui -I"${D_BASE}/local/include/d" -S"${D_BASE}/local/lib" -no-export-dynamic -L-ldl -oq.obj
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/canvas.d	Wed May 13 15:42:39 2009 +0930
@@ -0,0 +1,378 @@
+module canvas;
+
+import std.stdio;
+import std.math;
+
+import cairo.Context;
+import cairo.Surface;
+
+import gdk.Drawable;
+
+import gtk.Widget;
+import gtk.Toolbar;
+import gtk.Table;
+import gtk.HRuler;
+import gtk.VRuler;
+import gtk.Range;
+import gtk.HScrollbar;
+import gtk.VScrollbar;
+import gtk.DrawingArea;
+import gtk.Adjustment;
+
+import tk.misc;
+import tk.geometry;
+import tk.types;
+import tk.events;
+import tk.gtk_support;
+
+private import gtkc.gtk;
+
+import icanvas;
+
+// x and y run right and up respectively
+
+void rectangle(Context cr, Point corner1, Point corner2) {
+    double x = corner1.x;
+    double y = corner1.y;
+    double w = corner2.x - corner1.x;
+    double h = corner2.y - corner1.y;
+
+    if (w < 0.0) { x += w; w = -w; }
+    if (h < 0.0) { y += h; h = -h; }
+
+    //writefln("Rect: %f %f %f %f\n", x, y, w, h);
+
+    cr.rectangle(x, y, w, h);
+}
+
+class Canvas : Table, ICanvas {
+    static this() {
+        ORIGIN = new Point(0.0, 0.0);
+        INITIAL_PAGE_SIZE = new Vector(210.0, 297.0);       // A4
+    }
+
+    this(ICanvasEventHandler event_handler) {
+        super(3, 3, 0);
+
+        mEventHandler = event_handler;
+
+        const double PPI = 120.0;
+        const double MM_PER_INCH = 25.4;
+        mZoom = 0.25 * PPI / MM_PER_INCH;
+
+        mPageLeftBottom = ORIGIN.clone();
+        mPageRightTop = ORIGIN + INITIAL_PAGE_SIZE;
+        mViewCentre = ORIGIN + INITIAL_PAGE_SIZE / 2.0;
+
+        mCanvasLeftBottom = mPageLeftBottom - 3.0 * INITIAL_PAGE_SIZE;
+        mCanvasRightTop = mPageRightTop + 3.0 * INITIAL_PAGE_SIZE;
+
+        mHRuler = new HRuler();
+        attach(mHRuler,
+               1, 2,
+               0, 1,
+               AttachOptions.FILL | AttachOptions.EXPAND, AttachOptions.SHRINK,
+               0, 0);
+        mHRuler.setMetric(GtkMetricType.CENTIMETERS);
+
+        mVRuler = new VRuler();
+        attach(mVRuler,
+               0, 1,
+               1, 2,
+               AttachOptions.SHRINK, AttachOptions.FILL | AttachOptions.EXPAND,
+               0, 0);
+        mVRuler.setMetric(GtkMetricType.CENTIMETERS);
+
+        mDrawingArea = new DrawingArea();
+        mDrawingArea.addOnRealize(&onRealize);
+        mDrawingArea.addOnConfigure(&onConfigure);
+        mDrawingArea.addOnExpose(&onExpose);
+        mDrawingArea.addOnButtonPress(&onButtonEvent);
+        mDrawingArea.addOnButtonRelease(&onButtonEvent);
+        mDrawingArea.addOnKeyPress(&onKeyEvent);
+        mDrawingArea.addOnKeyRelease(&onKeyEvent);
+        mDrawingArea.addOnMotionNotify(&onMotionNotify);
+        mDrawingArea.addOnScroll(&onScroll);
+        attach(mDrawingArea,
+               1, 2,
+               1, 2, 
+               AttachOptions.FILL | AttachOptions.EXPAND, AttachOptions.FILL | AttachOptions.EXPAND,
+               0, 0);
+
+        // value, lower, upper, step-inc, page-inc, page-size
+        // Give the adjustments dummy values until we receive a configure
+        mHAdjustment = new Adjustment(0.0, 0.0, 1.0, 0.2, 0.5, 1.0);
+        mHAdjustment.addOnValueChanged(&onValueChanged);
+        mHScrollbar = new HScrollbar(mHAdjustment);
+        attach(mHScrollbar,
+               1, 2,
+               2, 3,
+               AttachOptions.FILL | AttachOptions.EXPAND, AttachOptions.SHRINK,
+               0, 0);
+
+        mVAdjustment = new Adjustment(0.0, 0.0, 1.0, 0.2, 0.5, 0.4);
+        mVAdjustment.addOnValueChanged(&onValueChanged);
+        mVScrollbar = new VScrollbar(mVAdjustment);
+        attach(mVScrollbar,
+               2, 3,
+               1, 2,
+               AttachOptions.SHRINK,
+               AttachOptions.FILL | AttachOptions.EXPAND,
+               0, 0);
+    }
+
+    override void rel_zoom(Point screen_datum, double factor) {
+        // Work out pixel distance from current centre to datum,
+        // Do the zoom, then work out the new centre that keeps the
+        // pixel distance the same
+
+        Point old_model_datum = screen_to_model(screen_datum);
+        Vector pixel_distance = model_to_screen(old_model_datum - mViewCentre);
+        mZoom = clamp_zoom(factor * mZoom);
+        mViewCentre = old_model_datum - screen_to_model(pixel_distance);
+
+        Point new_model_datum = screen_to_model(screen_datum);
+
+        update_adjustments();
+        //update_rulers(new_model_datum);
+        update_rulers();
+        queueDraw();
+    }
+
+    override void rel_pan(Vector screen_displacement) {
+        mViewCentre = mViewCentre + screen_to_model(screen_displacement);
+
+        update_adjustments();
+        update_rulers();
+        queueDraw();
+    }
+
+    private {
+
+        void onRealize(Widget widget) {
+            assert(widget is mDrawingArea);
+            //writefln("Got realize\n");
+        }
+
+        bool onConfigure(GdkEventConfigure * event, Widget widget) {
+            assert(widget is mDrawingArea);
+
+            mViewSize = new Vector(cast(double)event.width, cast(double)event.height);
+            update_adjustments();
+            update_rulers();
+
+
+            return 1;
+        }
+
+        bool onExpose(GdkEventExpose * event, Widget widget) {
+            assert(widget is mDrawingArea);
+
+            Drawable dr = mDrawingArea.getWindow();
+
+            int width, height;
+            dr.getSize(width, height);
+            //writefln("Got expose %dx%d\n", width, height);
+
+            scope auto cr = new Context(dr);
+
+            if (event) {
+                cr.rectangle(event.area.x, event.area.y, event.area.width, event.area.height);
+                cr.clip();
+            }
+
+            // Make the window light grey
+            cr.setSourceRgba(1.0, 1.0, 1.0, 0.7);
+            if (event) {
+                cr.rectangle(event.area.x, event.area.y, event.area.width, event.area.height);
+            }
+            else {
+                cr.rectangle(0.0, 0.0, width, height);
+            }
+            cr.fill();
+
+            {
+                // Make the paper white, with a border
+
+                Point screen_page_left_bottom = model_to_screen(mPageLeftBottom);
+                Point screen_page_right_top = model_to_screen(mPageRightTop);
+
+                cr.setSourceRgba(1.0, 1.0, 1.0, 1.0);
+                rectangle(cr, screen_page_left_bottom, screen_page_right_top);
+                cr.fill();
+
+                cr.setSourceRgba(0.0, 0.0, 0.0, 1.0);
+                rectangle(cr, screen_page_left_bottom, screen_page_right_top);
+                cr.stroke();
+            }
+
+            return 1;
+        }
+
+        bool onButtonEvent(GdkEventButton * event, Widget widget) {
+            assert(widget is mDrawingArea);
+            //writefln("Got button event\n");
+
+            Point screen_point = new Point(event.x + 0.5, event.y + 0.5);
+            Point model_point = screen_to_model(screen_point);
+
+            auto button_event = new ButtonEvent(gtk2tk_click(event.type),
+                                                gtk2tk_button(event.button),
+                                                screen_point,
+                                                model_point,
+                                                gtk2tk_mask(event.state));
+
+            //mEventHandle.handle_button_press
+
+            return 1;
+        }
+
+        bool onKeyEvent(GdkEventKey * event, Widget widget) {
+            assert(widget is mDrawingArea);
+            //writefln("Got key event\n");
+
+            //auto key_event = new KeyEvent("",
+            // mEventHandle.handle_key(key_event);
+
+            return 1;
+        }
+
+        bool onMotionNotify(GdkEventMotion * event, Widget widget) {
+            assert(widget is mDrawingArea);
+            //writefln("Got motion notify\n");
+            gtk_widget_event(mHRuler.getWidgetStruct(), cast(GdkEvent *)event);
+            gtk_widget_event(mVRuler.getWidgetStruct(), cast(GdkEvent *)event);
+
+            Point screen_point = new Point(event.x + 0.5, event.y + 0.5);
+            Point model_point = screen_to_model(screen_point);
+
+            auto motion_event = new MotionEvent(screen_point,
+                                                model_point,
+                                                gtk2tk_mask(event.state));
+
+            mEventHandler.handle_motion(this, motion_event);
+
+            return 1;
+        }
+
+        bool onScroll(GdkEventScroll * event, Widget widget) {
+            assert(widget is mDrawingArea);
+            //writefln("Got scroll\n");
+
+            Point screen_point = new Point(event.x + 0.5, event.y + 0.5);
+            Point model_point = screen_to_model(screen_point);
+
+            auto scroll_event = new ScrollEvent(gtk2tk_direction(event.direction),
+                                                screen_point,
+                                                model_point,
+                                                gtk2tk_mask(event.state));
+
+            mEventHandler.handle_scroll(this, scroll_event);
+
+            return 1;
+        }
+
+        void onValueChanged(Adjustment adjustment) {
+            GtkAdjustment * h_gtkAdjustment = mHAdjustment.getAdjustmentStruct();
+            GtkAdjustment * v_gtkAdjustment = mVAdjustment.getAdjustmentStruct();
+
+            Point view_left_bottom = new Point(h_gtkAdjustment.value, v_gtkAdjustment.value);
+            writefln("%s", view_left_bottom);
+            Vector model_size = screen_to_model(mViewSize);
+            mViewCentre = view_left_bottom + model_size / 2.0;
+
+            update_rulers();
+
+            queueDraw();
+        }
+
+        void update_rulers() {
+            Vector model_size = screen_to_model(mViewSize);
+
+            Point view_left_bottom = mViewCentre - model_size / 2.0;
+            Point view_right_top = mViewCentre + model_size / 2.0;
+
+            mHRuler.setRange(view_left_bottom.x,
+                             view_right_top.x,
+                             0.0,       // TODO preserve the value
+                             mZoom * 2000.0);
+            mVRuler.setRange(view_right_top.y,
+                             view_left_bottom.y,
+                             0.0,
+                             mZoom * 2000.0);
+        }
+
+        void update_adjustments() {
+            Vector model_size = screen_to_model(mViewSize);
+
+            Point view_left_bottom = mViewCentre - model_size / 2.0;
+            Point view_right_top = mViewCentre + model_size / 2.0;
+
+            // Adjust the canvas size if necessary
+            mCanvasLeftBottom = min_extents(mCanvasLeftBottom, view_left_bottom);
+            mCanvasRightTop = max_extents(mCanvasRightTop, view_right_top);
+
+            Vector canvas_size = mCanvasRightTop - mCanvasLeftBottom;
+            Vector page_size = mPageRightTop - mPageLeftBottom;
+
+            // Update the adjustments
+
+            GtkAdjustment * h_gtkAdjustment = mHAdjustment.getAdjustmentStruct();
+            GtkAdjustment * v_gtkAdjustment = mVAdjustment.getAdjustmentStruct();
+
+            h_gtkAdjustment.lower = mCanvasLeftBottom.x;
+            h_gtkAdjustment.upper = mCanvasRightTop.x;
+            h_gtkAdjustment.value = view_left_bottom.x;
+            h_gtkAdjustment.step_increment = canvas_size.x / 10.0;
+            h_gtkAdjustment.page_increment = canvas_size.x / 5.0;
+            h_gtkAdjustment.page_size = model_size.x;
+
+            v_gtkAdjustment.lower = mCanvasLeftBottom.y;
+            v_gtkAdjustment.upper = mCanvasRightTop.y;
+            v_gtkAdjustment.value = view_left_bottom.y;
+            v_gtkAdjustment.step_increment = canvas_size.y / 10.0;
+            v_gtkAdjustment.page_increment = canvas_size.y / 5.0;
+            v_gtkAdjustment.page_size = model_size.y;
+
+            mHAdjustment.changed();
+            mHAdjustment.valueChanged();
+            mVAdjustment.changed();
+            mVAdjustment.valueChanged();
+        }
+
+        Point model_to_screen(Point model) { return ORIGIN + mViewSize / 2.0 + mZoom * (model - mViewCentre); }
+        Point screen_to_model(Point screen) { return mViewCentre + (screen - mViewSize / 2.0 - ORIGIN) / mZoom; }
+        Vector model_to_screen(Vector model) { return mZoom * model; }
+        Vector screen_to_model(Vector screen) { return screen / mZoom; }
+        double model_to_screen(double model) { return mZoom * model; }
+        double screen_to_model(double screen) { return screen / mZoom; }
+
+        double clamp_zoom(double zoom) { return clamp(zoom, 0.1, 10.0); }
+
+        static const Point ORIGIN;
+        static const Vector INITIAL_PAGE_SIZE;
+
+        ICanvasEventHandler mEventHandler;
+
+        // Model units are in millimetres
+        // Screen units are in pixels
+
+        double mZoom;               // pixels-per-mm
+        Point mViewCentre;          // model: where in the model is the centre of our view
+
+        Point mCanvasLeftBottom;    // model: bottom left corner of canvas
+        Point mCanvasRightTop;      // model: top right corner of canvas
+        Point mPageLeftBottom;      // model: bottom left corner of page
+        Point mPageRightTop;        // model: top right corner of page
+
+        Vector mViewSize;           // screen: size of view window in pixels
+
+        HRuler mHRuler;
+        VRuler mVRuler;
+        DrawingArea mDrawingArea;
+        Adjustment mHAdjustment;
+        HScrollbar mHScrollbar;
+        Adjustment mVAdjustment;
+        VScrollbar mVScrollbar;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gui.d	Wed May 13 15:42:39 2009 +0930
@@ -0,0 +1,21 @@
+module gui;
+
+import canvas;
+import handler;
+
+import gtk.Main;
+import gtk.MainWindow;
+
+import tk.geometry;
+import tk.types;
+import tk.events;
+
+void main(string[] args) {
+    Main.init(args);
+    auto window = new MainWindow("Title");
+    auto event_handler = new ToolStack();
+    auto canvas = new Canvas(event_handler);
+    window.add(canvas);
+    window.showAll();
+    Main.run();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/handler.d	Wed May 13 15:42:39 2009 +0930
@@ -0,0 +1,88 @@
+module handler;
+
+import std.stdio;
+
+private import icanvas;
+private import tk.types;
+private import tk.geometry;
+private import tk.events;
+//IToolStack
+class ToolStack : ICanvasEventHandler {
+    override bool handle_button_press(ICanvas canvas, ButtonEvent event) {
+        writefln("%s", event);
+        return true;
+    }
+
+    override bool handle_button_release(ICanvas canvas, ButtonEvent event) {
+        writefln("%s", event);
+        return true;
+    }
+
+    override bool handle_key_press(ICanvas canvas, KeyEvent event) {
+        writefln("%s", event);
+        return true;
+    }
+
+    override bool handle_key_release(ICanvas canvas, KeyEvent event) {
+        writefln("%s", event);
+        return true;
+    }
+
+    override bool handle_motion(ICanvas canvas, MotionEvent event) {
+        writefln("%s", event);
+        return true;
+    }
+
+    override bool handle_scroll(ICanvas canvas, ScrollEvent event) {
+        writefln("%s", event);
+
+        if (event.mask.query(Modifier.CONTROL)) {
+            // Zoom about the pointer
+            double zoom = 1.44;
+
+            if (event.scroll_direction == ScrollDirection.DOWN) {
+                zoom = 1.0 / zoom;
+            }
+
+            canvas.rel_zoom(event.screen_point(), zoom);
+        }
+        else {
+            // Scroll
+
+            const double AMOUNT = 30.0;
+            Vector v;
+
+            if (event.mask.query(Modifier.SHIFT)) {
+                // left to right
+                v = new Vector(AMOUNT, 0.0);
+            }
+            else {
+                // down to up
+                v = new Vector(0.0, AMOUNT);
+            }
+
+            if (event.scroll_direction == ScrollDirection.UP) {
+                v = -v;
+            }
+
+            canvas.rel_pan(v);
+        }
+
+        return true;
+    }
+
+    /*
+    override void push(Tool tool) {
+    }
+
+    override void pop() {
+    }
+
+    override void replace(Tool tool) {
+    }
+
+    private {
+        Tool[] mTools;
+    }
+    */
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/icanvas.d	Wed May 13 15:42:39 2009 +0930
@@ -0,0 +1,25 @@
+module icanvas;
+
+import tk.geometry;
+
+interface ICanvas {
+    void rel_zoom(Point screen_datum, double factor);
+    void rel_pan(Vector screen_displacement);
+    //void damage();
+}
+
+import tk.events;
+
+interface ICanvasEventHandler {
+    bool handle_button_press(ICanvas canvas, ButtonEvent event);
+    bool handle_button_release(ICanvas canvas, ButtonEvent event);
+    bool handle_key_press(ICanvas canvas, KeyEvent event);
+    bool handle_key_release(ICanvas canvas, KeyEvent event);
+    bool handle_motion(ICanvas canvas, MotionEvent event);
+    bool handle_scroll(ICanvas canvas, ScrollEvent event);
+    //bool handle_expose(ICanvas canvas, ExposeEvent event);
+    //bool handle_enter(ICanvas, CrossingEvent event);
+    //bool handle_leave(ICanvas, CrossingEvent event);
+    //bool handle_focus_in(ICanvas, FocusEvent event);
+    //bool handle_focus_out(ICanvas, FocusEvent event);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/import/canvas.d	Wed May 13 15:42:39 2009 +0930
@@ -0,0 +1,17 @@
+module interaction.canvas;
+
+import presentation.geometry;
+
+// World coordinates
+
+interface Canvas {
+    // color, line color, line style, line width, fill color,
+    // stroke, fill,
+    // image
+    // text, clip,
+}
+
+// Pixel coordinates
+
+interface Screen {
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/import/geometry.d	Wed May 13 15:42:39 2009 +0930
@@ -0,0 +1,220 @@
+module interaction.geometry;
+
+private import std.math;
+
+private const double EPSILON = 1e-20;
+
+final class Point {
+    this() {
+        this(0.0, 0.0);
+    }
+
+    this(double x, double y) {
+        _x = x;
+        _y = y;
+    }
+
+    char[] toString() {
+        return "(" ~ std.string.toString(_x) ~ ", " ~ std.string.toString(_y) ~ ")";
+    }
+
+    Point opAdd(in Vector v) {
+        return new Point(_x + v._x, _y + v._y);
+    }
+
+    Vector opSub(in Point p) {
+        return new Vector(_x - p._x, _y - p._y);
+    }
+
+    double x() { return _x; }
+    double y() { return _y; }
+
+    private {
+        double _x;
+        double _y;
+    }
+}
+
+//
+
+final class Vector {
+    this() {
+        this(0.0, 0.0);
+    }
+
+    this(double x, double y) {
+        _x = x;
+        _y = y;
+    }
+
+    char[] toString() {
+        return "[" ~ std.string.toString(_x) ~ ", " ~ std.string.toString(_y) ~ "]";
+    }
+
+    Vector opAdd(in Vector v) {
+        return new Vector(_x + v._x, _y + v._y);
+    }
+
+    Vector opSub(in Vector v) {
+        return new Vector(_x - v._x, _y - v._y);
+    }
+
+    Vector opNeg() {
+        return new Vector(-_x, -_y);
+    }
+
+    Vector opMul_r(double d) {
+        return new Vector(d * _x, d * _y);
+    }
+
+    double length() {
+        return sqrt(_x * _x + _y * _y);
+    }
+
+    bool has_length() {
+        return _x * _x + _y * _y > EPSILON * EPSILON;
+    }
+
+    double x() { return _x; }
+    double y() { return _y; }
+
+    private {
+        double _x;
+        double _y;
+    }
+}
+
+//
+
+final class Segment {
+    this() {
+    }
+
+    this(Point start, Point end) {
+        _start = start;
+        _end = end;
+    }
+
+    Point start() { return _start; }
+    Point end() { return _end; }
+
+    private {
+        Point _start;
+        Point _end;
+    }
+}
+
+//
+
+final class Line {
+    this() {
+    }
+
+    this(Point point, Vector vector) {
+        assert(vector.has_length);
+        _point = point;
+        _vector = vector;
+    }
+
+    this(Point point1, Point point2) {
+        _point = point1;
+        _vector = point2 - point1;
+        assert(_vector.has_length);
+    }
+
+    private {
+        Point _point;
+        Vector _vector;
+    }
+}
+
+//
+
+final class Rectangle {
+    this() {
+        this(0.0, 0.0, 0.0, 0.0);
+    }
+
+    this(double x, double y, double w, double h) {
+        assert(w >= 0.0);
+        assert(h >= 0.0);
+        _x = x;
+        _y = y;
+        _w = w;
+        _h = h;
+    }
+
+    double x() { return _x; }
+    double y() { return _y; }
+    double w() { return _w; }
+    double h() { return _h; }
+    double xw() { return _x + _w; }
+    double yh() { return _y + _h; }
+
+    /*
+    Point corner00() { return new Point(_x,      _y     ); }
+    Point corner10() { return new Point(_x + _w, _y     ); }
+    Point corner11() { return new Point(_x + _w, _y + _h); }
+    Point corner01() { return new Point(_x,      _y + _h); }
+
+    Point centre() { return new Point(_x + _w / 2.0, _y + _h / 2.0); }
+    */
+
+    double area() {
+        return _w * _h;
+    }
+
+    bool has_area() {
+        return area > 0.0;
+    }
+
+    bool contains(Point point) {
+        return point.x > x && point.x < xw & point.y > y && point.y < xy;
+    }
+
+    bool contains(Segment segment) {
+        return contains(segment.start, segment.end);
+    }
+
+    private double _x, _y, _w, _h;
+}
+
+unittest {
+    const double absdiff = 1e-20, reldiff= 1e-20;
+
+    bool cmpdouble(in double lhs, in double rhs) {
+        double adiff = fabs(rhs - lhs);
+
+        if (adiff < absdiff && adiff > -absdiff) {
+            return true;
+        }
+
+        if (fabs(lhs) > absdiff && fabs(rhs) > absdiff) {
+            double rdiff = rhs / lhs;
+
+            if (rdiff < (1.0 + reldiff) && rdiff > 1.0 / (1.0 + reldiff)) {
+                return true;
+            }
+        }
+
+        // They must differ significantly
+
+        return false;
+    }
+
+    bool cmpvector(in Vector lhs, in Vector rhs) {
+        return cmpdouble(lhs._x, rhs._x) && cmpdouble(lhs._y, rhs._y);
+    }
+
+    bool cmppoint(in Point lhs, in Point rhs) {
+        return cmpdouble(lhs._x, rhs._x) && cmpdouble(lhs._y, rhs._y);
+    }
+
+    assert(cmpvector(new Vector(0.0, 0.0), new Vector));
+    assert(!cmpvector(new Vector(0.0, 1e-10), new Vector));
+    assert(cmpvector(new Vector(1.0, 2.0) + new Vector(3.0, 4.0), new Vector(4.0, 6.0)));
+    assert(cmpvector(new Vector(1.0, 2.0) - new Vector(3.0, 4.0), new Vector(-2.0, -2.0)));
+    assert(cmpvector(-(new Vector(1.0, 2.0)), new Vector(-1.0, -2.0)));
+    assert(cmpdouble((new Vector(3.0, 4.0)).length(), 5.0));
+    assert(cmpvector(3.0 * (new Vector(3.0, 4.0)), new Vector(9.0, 12.0)));
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/import/model.d	Wed May 13 15:42:39 2009 +0930
@@ -0,0 +1,56 @@
+module interaction.model;
+
+import presentation.model;
+
+final class Manipulator {
+}
+
+abstract class Selector {
+    Selector(Fig selected) {
+        _selected = selected;
+    }
+
+    Fig selected() { return _selected; }
+
+    abstract void draw(Screen screen);
+
+    private {
+        Fig _selected;
+    }
+}
+
+abstract class Fig {
+    DiagramElement element() { return _element; }
+
+    abstract void draw(Canvas canvas);
+
+    abstract Selector create_selector();
+
+    private {
+        DiagramElement _element;
+    }
+}
+
+abstract class NetFig : Fig {
+    GraphElement element() { return _element; }
+
+    private {
+        GraphElement _element;
+    }
+}
+
+abstract class EdgeFig : NetFig {
+    GraphEdge element() { return _element; }
+
+    private {
+        GraphEdge _element;
+    }
+}
+
+abstract class NodeFig : NetFig {
+    GraphNode element() { return _element; }
+
+    private {
+        GraphNode _element;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/import/network.d	Wed May 13 15:42:39 2009 +0930
@@ -0,0 +1,97 @@
+module presentation.network;
+
+import presentation.model;
+
+enum EdgeEnd {
+    Source,
+    Target
+};
+
+interface NetworkObserver {
+    // Node changes
+
+    void node_added(GraphNode node,
+                    GraphElement container);
+    void node_changed(GraphNode node);
+    void node_relocated(GraphNode node,
+                        GraphElement container);
+    void node_removed(GraphNode node,
+                      GraphElement container);
+
+    // Edge changes
+
+    void edge_added(GraphEdge node,
+                    GraphConnector anchor1, GraphConnector anchor2);
+    void edge_changed(GraphEdge edge);
+    void edge_rerouted();
+    void edge_removed();
+}
+
+interface Network {
+    void add_observer(NetworkObserver observer);
+    void remove_observer(NetworkObserver observer);
+
+    //
+    // Interrogation:
+    //
+
+    GraphNode[] get_root_nodes();
+
+    // Inquire whether in principle a node of node_type
+    // can be added at the given point, possibly nested
+    // within the nest node. The nest can be null.
+    bool can_add(char[] node_type,
+                 Point point,           // necessary?
+                 GraphNode nest);
+
+    bool can_relocate(GraphNode node);
+
+    bool can_remove(GraphNode node);
+
+    // Inquire whether in principle the source element can
+    // be connected to the target element using
+    // an edge of edge_type. This might return true even
+    // though the real operation would fail due to deeper checking.
+    bool can_connect(char[] edge_type,
+                     GraphElement source_element, Point source_point,
+                     GraphElement target_element, Point target_point);
+
+    // Inquire whether in principle a given end of an existing edge
+    // can be rerouted from old_element to new_element at new_point.
+    // old_element and new_element may be the same element.
+    bool can_reroute(GraphEdge edge, EdgeEnd end,
+                     GraphElement old_element,
+                     GraphElement new_element, Point new_point);
+
+    bool can_disconnect(GraphEdge edge);
+
+    //
+    // Manipulation:
+    //
+
+    // Attempt to really add a node...
+    GraphNode add(char[] node_type, /* initial properties, */
+                  Point point,
+                  GraphNode nest);
+
+    void relocate(GraphNode node,
+                  GraphElement old_container,
+                  GraphElement new_container, Point new_point);
+
+    // Attempt to really remove a node
+    void remove(GraphNode node);
+
+    // Attempt to really connect the source element to the target element
+    // using an edge of the given type with the given initial properties.
+    GraphEdge connect(char[] edge_type, /* initial properties, */
+                      GraphElement source_element, Point source_point,
+                      GraphElement target_element, Point target_point);
+
+    // Attempt to really reroute..
+    void reroute(GraphEdge edge, EdgeEnd end,
+                 GraphElement old_element,
+                 GraphElement new_element, Point new_point);
+
+    // Attempt to really remove an edge...
+    void disconnect(GraphEdge edge);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/import/new.d	Wed May 13 15:42:39 2009 +0930
@@ -0,0 +1,40 @@
+import types;
+import tango.util.collection.ArraySeq;
+
+interface IProperty {
+}
+
+interface ISemanticModelBridge {
+    char[] presentation();
+}
+
+interface ISimpleSemanticModelElement : ISemanticModelBridge {
+    char[] type_info();
+}
+
+interface IDiagramElement {
+    bool is_visible();
+    IProperty[] get_properties();
+
+    void accept(in IVisitor visitor);
+}
+
+interface IVisitor {
+    void visit(in IGraphNode node);
+    void visit(in IGraphEdge edge);
+}
+
+interface IGraphElement : IDiagramElement {
+}
+
+interface IGraphNode : IGraphElement {
+}
+
+interface IGraphEdge : IGraphElement {
+}
+
+interface IGraphConnector {
+    Point point();
+    IGraphElement element();
+    IGraphEdge[] edges();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/import/p-model.d	Wed May 13 15:42:39 2009 +0930
@@ -0,0 +1,128 @@
+module dog.presentation.model;
+
+import dog.presentation.types;
+import util.list;
+
+class Property {
+    this(char[] key, char[] value) {
+        _key = key;
+        _value = value;
+    }
+
+    char[] key() { return _key; }
+    char[] value() { return _value; }
+
+    private {
+        char[] _key;
+        char[] _value;
+    }
+}
+
+interface IVisitor {
+    void visit(GraphEdge);
+    void visit(GraphNode);
+}
+
+class Diagram {
+}
+
+abstract class DiagramElement {
+    this() {
+        _is_visible = true;
+    }
+
+    abstract void accept(in IVisitor visitor);
+
+    bool is_visible() { return _is_visible; }
+
+    void add_property(Property property) {
+        // TODO
+        _properties.addTail(property);
+    }
+
+    private {
+        List!(Property) _properties;
+        bool _is_visible;
+        GraphElement _container;
+    }
+}
+
+abstract class SemanticModelBridge {
+    this(char[] presentation) {
+        _presentation = presentation;
+    }
+
+    char[] presentation() { return _presentation; }
+
+    private {
+        char[] _presentation;
+    }
+}
+
+class SimpleSemanticModelElement : SemanticModelBridge {
+    this(char[] type_info, char[] presentation) {
+        super(presentation);
+        _type_info = type_info;
+    }
+
+    char[] type_info() { return _type_info; }
+
+    private {
+        char[] _type_info;
+    }
+}
+
+abstract class GraphElement : DiagramElement {
+    this() {
+    }
+
+    void add_anchorage(GraphConnector anchorage) {
+        // TODO
+    }
+
+    void remove_anchorage(GraphConnector anchorage) {
+    }
+
+    private {
+        SemanticModelBridge _semantic_model;
+        Point _position;
+        List!(GraphConnector) _anchorages;
+        List!(DiagramElement) _containeds;
+    }
+}
+
+class GraphConnector {
+    this(Point point) {
+        _point = point;
+    }
+
+    private {
+        Point _point;
+        GraphElement _element;
+        GraphEdge[] _edges;
+    }
+}
+
+class GraphEdge : GraphElement {
+    this() {
+    }
+
+    void accept(IVisitor visitor) {
+        visitor.visit(this);
+    }
+
+    private {
+    }
+}
+
+class GraphNode : GraphElement {
+    this() {
+    }
+
+    void accept(IVisitor visitor) {
+        visitor.visit(this);
+    }
+
+    private {
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/import/types.d	Wed May 13 15:42:39 2009 +0930
@@ -0,0 +1,35 @@
+module presentation.types;
+
+final class Point {
+    this() {
+        _x = 0;
+        _y = 0;
+    }
+
+    this(double x, double y) {
+        _x = x;
+        _y = y;
+    }
+
+    private {
+        double _x;
+        double _y;
+    }
+}
+
+final class Dimension {
+    this() {
+        _width = 0;
+        _height = 0;
+    }
+
+    this(double width, double height) {
+        _width = width;
+        _height = height;
+    }
+
+    private {
+        double _width;
+        double _height;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/import/undo.d	Wed May 13 15:42:39 2009 +0930
@@ -0,0 +1,169 @@
+module dog.core.undo;
+
+import util.list;
+
+abstract class Action {
+    void undo();
+    void redo();
+}
+
+interface IUndoObserver {
+    void canUndo(bool value, char[] description);
+    void canRedo(bool value, char[] description);
+}
+
+interface IUndoManager {
+    void addAction(Action action);
+}
+
+class UndoManager : IUndoManager {
+    this() {
+    }
+
+    void addObserver(IUndoObserver observer) {
+        _observers.add(observer);
+    }
+
+    void removeObserver(IUndoObserver observer) {
+        _observers.remove(observer);
+    }
+
+    void reset() {
+        assert(!inTransaction());
+        _past.clear();
+        _future.clear();
+        foreach(IUndoObserver obs; _observers) {
+            obs.canUndo(false, "");
+            obs.canRedo(false, "");
+        }
+    }
+
+    void undo() {
+        assert(canUndo());
+        Transaction t = _past.pop();
+        t.undo();
+        _future.push(t);
+    }
+
+    void redo() {
+        assert(canRedo());
+        Transaction t = _future.pop();
+        t.redo();
+        _past.push(t);
+    }
+
+    bool canUndo() {
+        assert(!inTransaction());
+        return !_past.empty();
+    }
+
+    bool canRedo() {
+        assert(!inTransaction()) {
+            return !_future.empty();
+        }
+    }
+
+    void beginTransaction(char[] description) {
+        assert(!inTransaction());
+        _current_transaction = new Transaction(description);
+    }
+
+    void cancelTransaction() {
+        assert(inTransaction());
+        _current_transaction.cancel();
+        _current_transaction = null;
+    }
+
+    void endTransaction() {
+        assert(inTransaction());
+        _current_transaction.finalise();
+
+        if (!_future.empty()) {
+            _future.clear();
+            foreach(IUndoObserver obs; _observers) {
+                obs.canRedo(false, "");
+            }
+        }
+
+        _past.push(_current_transaction);
+
+        foreach(IUndoObserver obs; _observers) {
+            bs.canUndo(true, _current_transaction.name());
+        }
+
+        _current_transaction = null;
+    }
+
+    // IUndoManager implementations:
+
+    void addAction(Action action) {
+        assert(inTransaction());
+        _current_transaction.add(action);
+    }
+
+    private {
+        bool inTransaction() {
+            return _current_transaction !is null;
+        }
+
+        class Transaction {
+            enum State {
+                Accumulating,
+                Finalised,
+                Canceled
+            }
+
+            this(char[] description) {
+                _description = description;
+                _state = Accumulating;
+            }
+
+            char[] description() {
+                return _description;
+            }
+
+            void add(Action action) {
+                assert(_state == State.Accumulating);
+                _actions.addTail(action);
+            }
+
+            void finalise() {
+                assert(_state == State.Accumulating);
+                assert(!_actions.empty());
+                _finalised = true;
+            }
+
+            void cancel() {
+                assert(_state == State.Accumulating);
+                foreachreverse(UndoAction ua; _actions) {
+                    ua.undo();
+                }
+            }
+
+            void redo() {
+                assert(_finalised);
+                foreach (UndoAction ua; _actions) {
+                    ua.redo();
+                }
+            }
+
+            void undo() {
+                assert(_finalised);
+                foreachreverse(UndoAction ua; _actions) {
+                    ua.undo();
+                }
+            }
+
+            private {
+                char[] _description;
+                List<Action> _actions;
+                State _state;
+            }
+        }
+
+        Transaction _current_transaction;
+        Stack!(Transaction) _past;
+        Stack!(Transaction) _future;
+        Set!(IUndoObserver) _observers;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/itoolstack.d	Wed May 13 15:42:39 2009 +0930
@@ -0,0 +1,18 @@
+module itoolstack;
+
+interface IToolStack {
+    void push(Tool tool);
+    void pop();
+    void replace(Tool tool);
+}
+
+abstract class Tool : ICanvasEventHandler {
+    /*
+    abstract bool is_sticky();
+    abstract bool is_replaceable();
+    */
+
+    abstract void start(IToolStack tool_stack);
+    abstract void stop(IToolStack tool_stack);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tk/events.d	Wed May 13 15:42:39 2009 +0930
@@ -0,0 +1,138 @@
+module tk.events;
+
+import tk.types;
+import tk.geometry;
+
+//
+// Should we pass the screen and model points into
+// the events or pass a transform in separately to the handler
+//
+
+abstract class Event {
+    this() {
+    }
+
+    private {
+    }
+}
+
+final class ExposeEvent : Event {
+    this() {
+        super();
+    }
+
+    private {
+    }
+}
+
+abstract class InputEvent : Event {
+    this(Mask mask) {
+        mMask = mask;
+    }
+
+    Mask mask() { return mMask; }
+
+    private {
+        Mask mMask;
+    }
+}
+
+final class CrossingEvent : InputEvent {
+    this(Mask mask) {
+        super(mask);
+    }
+
+    private {
+    }
+}
+
+final class KeyEvent : InputEvent {
+    this(string str, uint value, Mask mask) {
+        super(mask);
+        mStr = str;
+        mValue = value;
+    }
+
+    string str() { return mStr; }
+
+    override string toString() {
+        return std.string.format("Key event: %s, %d", mStr, mValue);
+    }
+
+    private {
+        string mStr;
+        uint mValue;
+    }
+}
+
+abstract class PointerEvent : InputEvent {
+    this(Point screen_point, Point model_point, Mask mask) {
+        super(mask);
+        mScreenPoint = screen_point;
+        mModelPoint = model_point;
+    }
+
+    Point screen_point() { return mScreenPoint; }
+    Point model_point() { return mModelPoint; }
+
+    private {
+        Point mScreenPoint;
+        Point mModelPoint;
+    }
+}
+
+final class ButtonEvent : PointerEvent {
+    this(ButtonPress button_press,
+         ButtonNumber button_number,
+         Point screen_point,
+         Point model_point,
+         Mask mask) {   
+        super(screen_point, model_point, mask);
+        mButtonPress = button_press;
+        mButtonNumber = button_number;
+    }
+
+    override string toString() {
+        return std.string.format("Button event: %s, %s, %s, %s, %s", mButtonPress, mButtonNumber, mScreenPoint, mModelPoint, mMask);
+    }
+
+    ButtonPress button_press() { return mButtonPress; }
+    ButtonNumber button_number() { return mButtonNumber; }
+
+    private {
+        ButtonPress mButtonPress;
+        ButtonNumber mButtonNumber;
+    }
+}
+
+final class MotionEvent : PointerEvent {
+    this(Point screen_point,
+         Point model_point,
+         Mask mask) {
+        super(screen_point, model_point, mask);
+    }
+
+    override string toString() {
+        return std.string.format("Motion event: %s, %s, %s", mScreenPoint, mModelPoint, mMask);
+    }
+}
+
+final class ScrollEvent : PointerEvent {
+    this(ScrollDirection scroll_direction,
+         Point screen_point,
+         Point model_point,
+         Mask mask) {
+        super(screen_point, model_point, mask);
+        mScrollDirection = scroll_direction;
+    }
+
+    override string toString() {
+        return std.string.format("Scroll event: %s, %s, %s, %s", mScrollDirection, mScreenPoint, mModelPoint, mMask);
+    }
+
+    ScrollDirection scroll_direction() { return mScrollDirection; }
+
+    private {
+        ScrollDirection mScrollDirection;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tk/geometry.d	Wed May 13 15:42:39 2009 +0930
@@ -0,0 +1,126 @@
+module tk.geometry;
+
+private import std.math;
+private import tk.misc;
+
+final class Point {
+    this() {
+        this(0.0, 0.0);
+    }
+
+    this(double x, double y) {
+        _x = x;
+        _y = y;
+    }
+
+    Point clone() const {
+        return new Point(_x, _y);
+    }
+
+    override string toString() const {
+        return std.string.format("(%f, %f)", _x, _y);
+    }
+
+    Point opAdd(in Vector v) const {
+        return new Point(_x + v._x, _y + v._y);
+    }
+
+    Vector opSub(in Point p) const {
+        return new Vector(_x - p._x, _y - p._y);
+    }
+
+    Point opSub(in Vector v) const {
+        return new Point(_x - v._x, _y - v._y);
+    }
+
+    double x() const { return _x; }
+    double y() const { return _y; }
+
+    private immutable double _x, _y;
+}
+
+Point min_extents(in Point a, in Point b) {
+    return new Point(min(a.x, b.x), min(a.y, b.y));
+}
+
+Point max_extents(in Point a, in Point b) {
+    return new Point(max(a.x, b.x), max(a.y, b.y));
+}
+
+//
+
+final class Vector {
+    this() {
+        this(0.0, 0.0);
+    }
+
+    this(double x, double y) {
+        _x = x;
+        _y = y;
+    }
+
+    override string toString() const {
+        return std.string.format("[%f, %f]", _x, _y);
+    }
+
+    Vector opAdd(in Vector v) const {
+        return new Vector(_x + v._x, _y + v._y);
+    }
+
+    Vector opSub(in Vector v) const {
+        return new Vector(_x - v._x, _y - v._y);
+    }
+
+    Vector opNeg() const {
+        return new Vector(-_x, -_y);
+    }
+
+    Vector opMul_r(double d) const {
+        return new Vector(d * _x, d * _y);
+    }
+
+    Vector opDiv(double d) const {
+        return new Vector(_x / d, _y / d);
+    }
+
+    double length() const {
+        return sqrt(_x * _x + _y * _y);
+    }
+
+    double x() const { return _x; }
+    double y() const { return _y; }
+
+    private immutable double _x, _y;
+}
+
+//
+
+final class Rectangle {
+    this() {
+        mPosition = new Point();
+        mSize = new Vector();
+    }
+
+    this(Point position, Vector size) {
+        this(position.x, position.y, size.x, size.y);
+    }
+
+    this(Point corner1, Point corner2) {
+        this(corner1.x, corner1.y, corner2.x - corner1.x, corner2.y - corner1.y);
+    }
+
+    const(Point) position() const { return mPosition; }
+    const(Vector) size() const { return mSize; }
+
+    private {
+        this(double x, double y, double w, double h) {
+            if (w < 0.0) { x += w; w = -w; }
+            if (h < 0.0) { y += h; h = -h; }
+            mPosition = new Point(x, y);
+            mSize = new Vector(w, h);
+        }
+
+        const(Point) mPosition;
+        const(Vector) mSize;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tk/geometry2.d	Wed May 13 15:42:39 2009 +0930
@@ -0,0 +1,12 @@
+module tk.geometry2;
+
+struct Point2 {
+    this(double x, double y) {
+        _x = x;
+        _y = y;
+    }
+
+    private {
+        immutable double _x, _y;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tk/gtk_support.d	Wed May 13 15:42:39 2009 +0930
@@ -0,0 +1,62 @@
+module tk.gtk_support;
+
+import tk.types;
+
+static import gdk.Event;
+
+ButtonPress gtk2tk_click(gdk.Event.EventType event_type) {
+    switch (event_type) {
+    case gdk.Event.EventType.BUTTON_PRESS:
+        return ButtonPress.SINGLE;
+    case gdk.Event.EventType.DOUBLE_BUTTON_PRESS:
+        return ButtonPress.DOUBLE;
+    case gdk.Event.EventType.TRIPLE_BUTTON_PRESS:
+        return ButtonPress.TRIPLE;
+    case gdk.Event.EventType.BUTTON_RELEASE:
+        return ButtonPress.RELEASE;
+    default:
+        assert(false);
+    }
+}
+
+ButtonNumber gtk2tk_button(gdk.Event.guint button) {
+    switch (button) {
+    case 1:
+        return ButtonNumber.BUTTON_1;
+    case 2:
+        return ButtonNumber.BUTTON_2;
+    case 3:
+        return ButtonNumber.BUTTON_3;
+    default:
+        assert(false);
+    }
+}
+
+Mask gtk2tk_mask(gdk.Event.guint state) {
+    auto mask = new Mask();
+
+    if (state & gdk.Event.GdkModifierType.SHIFT_MASK)   mask.add(Modifier.SHIFT);
+    if (state & gdk.Event.GdkModifierType.CONTROL_MASK) mask.add(Modifier.CONTROL);
+    if (state & gdk.Event.GdkModifierType.MOD1_MASK)    mask.add(Modifier.ALT);
+    if (state & gdk.Event.GdkModifierType.MOD2_MASK)    mask.add(Modifier.META);
+    if (state & gdk.Event.GdkModifierType.BUTTON1_MASK) mask.add(Modifier.BUTTON_1);
+    if (state & gdk.Event.GdkModifierType.BUTTON2_MASK) mask.add(Modifier.BUTTON_2);
+    if (state & gdk.Event.GdkModifierType.BUTTON3_MASK) mask.add(Modifier.BUTTON_3);
+
+    return mask;
+}
+
+ScrollDirection gtk2tk_direction(gdk.Event.ScrollDirection direction) {
+    switch (direction) {
+    case gdk.Event.ScrollDirection.UP:
+        return ScrollDirection.UP;
+    case gdk.Event.ScrollDirection.DOWN:
+        return ScrollDirection.DOWN;
+    case gdk.Event.ScrollDirection.LEFT:
+        return ScrollDirection.LEFT;
+    case gdk.Event.ScrollDirection.RIGHT:
+        return ScrollDirection.RIGHT;
+    default:
+        assert(false);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tk/misc.d	Wed May 13 15:42:39 2009 +0930
@@ -0,0 +1,17 @@
+module tk.misc;
+
+double min(double a, double b) {
+    return a < b ? a : b;
+}
+
+double max(double a, double b) {
+    return a > b ? a : b;
+}
+
+double clamp(double v, double min, double max) {
+    assert(min < max);
+
+    if (v < min) { return min; }
+    else if (v > max) { return max; }
+    else { return v; }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tk/tool.d	Wed May 13 15:42:39 2009 +0930
@@ -0,0 +1,13 @@
+module tk.tools;
+
+/*
+class Window {
+    void screen_to_model();
+    void model_to_screen();
+};
+*/
+
+abstract class Tool {
+    void handle_button_press(Window window,
+                             ButtonEvent event,
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tk/types.d	Wed May 13 15:42:39 2009 +0930
@@ -0,0 +1,60 @@
+module tk.types;
+
+private import std.string;
+
+enum ButtonPress {
+    SINGLE,
+    DOUBLE,
+    TRIPLE,
+    RELEASE
+}
+
+enum ButtonNumber {
+    BUTTON_1,
+    BUTTON_2,
+    BUTTON_3,
+    BUTTON_4,
+    BUTTON_5,
+}
+
+enum ScrollDirection {
+    UP,
+    DOWN,
+    LEFT,
+    RIGHT
+}
+
+enum Modifier {
+    SHIFT,
+    CAPS_LOCK,
+    CONTROL,
+    ALT,
+    NUM_LOCK,
+    META,
+    SCROLL_LOCK,
+    BUTTON_1,
+    BUTTON_2,
+    BUTTON_3,
+    BUTTON_4,
+    BUTTON_5
+}
+
+class Mask {
+    this() {
+        //mBits = 0;
+    }
+
+    void add(Modifier modifier) { mBits |= bit(modifier); }
+    void remove(Modifier modifier) { mBits &= ~bit(modifier); }
+    bool query(Modifier modifier) { return cast(bool)(mBits & bit(modifier)); }
+
+    override string toString() {
+        return format("%d", mBits);
+    }
+
+    private {
+        static int bit(Modifier modifier) { return 1 << cast(int)modifier; }
+        int mBits;
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tools.d	Wed May 13 15:42:39 2009 +0930
@@ -0,0 +1,45 @@
+module tools;
+
+final class PanTool : Tool {
+    override bool handle_scroll(ICanvas canvas, ScrollEvent event) {
+        const double AMOUNT = 30.0;
+        Vector v;
+
+        if (event.mask.query(Modifier.SHIFT)) {
+            // left to right
+            v = new Vector(AMOUNT, 0.0);
+        }
+        else {
+            // down to up
+            v = new Vector(0.0, AMOUNT);
+        }
+
+        if (event.scroll_direction == ScrollDirection.UP) {
+            v = -v;
+        }
+
+        canvas.rel_pan(v);
+
+        return true;
+    }
+}
+
+final class ZoomTool {
+    override bool handle_scroll(ICanvas canvas, ScrollEvent event) {
+        if (event.mask.query(Modifier.CONTROL)) {
+            // Zoom about the pointer
+            double zoom = 1.44;
+
+            if (event.scroll_direction == ScrollDirection.DOWN) {
+                zoom = 1.0 / zoom;
+            }
+
+            canvas.rel_zoom(event.screen_point(), zoom);
+
+            return true;
+        }
+        else {
+            return false;
+        }
+    }
+}