changeset 28:1754cb773d41

Part-way through getting to compile with configure/builder.
author Graham St Jack <graham.stjack@internode.on.net>
date Sun, 02 Aug 2009 16:27:21 +0930
parents f3d91579bb28
children 960b408d3ac5
files README builder.d cairo/routines.d common/undo.d configure.d dia/grid_layer.d dia/icanvas.d dia/page_layer.d dia/standard_tools.d dia/tool.d dia/tool_layer.d doodle.d doodle/cairo/routines.d doodle/common/undo.d doodle/dia/grid_layer.d doodle/dia/icanvas.d doodle/dia/page_layer.d doodle/dia/standard_tools.d doodle/dia/tool.d doodle/dia/tool_layer.d doodle/fig/fig.d doodle/fig/fig_layer.d doodle/fig/selection_layer.d doodle/gtk/canvas.d doodle/gtk/conversions.d doodle/gtk/toolbar.d doodle/gtk/zoom_controls.d doodle/icons/select.png doodle/icons/select.svg doodle/import/model.d doodle/import/network.d doodle/import/new.d doodle/import/p-model.d doodle/import/types.d doodle/main/prog/doodler.d doodle/main/undo_manager.d doodle/tk/events.d doodle/tk/geometry.d doodle/tk/misc.d doodle/tk/types.d fig/fig.d fig/fig_layer.d fig/selection_layer.d gtk/canvas.d gtk/conversions.d gtk/tool_bar.d gtk/zoom_controls.d icons/select.png icons/select.svg import/model.d import/network.d import/new.d import/p-model.d import/types.d options tk/events.d tk/geometry.d tk/misc.d tk/types.d undo_manager.d
diffstat 60 files changed, 3563 insertions(+), 2440 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/README	Sun Aug 02 16:27:21 2009 +0930
@@ -0,0 +1,1 @@
+Doodle - a tool for creating diagrams.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/builder.d	Sun Aug 02 16:27:21 2009 +0930
@@ -0,0 +1,1031 @@
+module builder;
+
+private {
+    import std.stream;
+    import std.stdio;
+    import std.string;
+    import std.process;
+    import std.file;
+    import std.path;
+    import std.date;
+}
+
+
+//
+// builder is a build tool for D code. It is intended for use during development,
+// and so supports:
+// * Building only those targets that are out of date.
+// * Automated execution and evaluation of regression tests.
+// * Automatic linking of libraries that are part of the project.
+// * Enforcement of dependency rules (explained below).
+// * Enforcement of a directory structure designed to support reusable components.
+// * Complete separation of source from targets (friendly on checkouts).
+//
+// To keep things simple and fast, builder won't build arbitrary code - but it will build
+// code written to be built with it. Besides having to obey dependency and directory
+// layout and naming rules, you have to do imports as one of:
+//     public import module_name;
+//     private import module_name;
+//     import module_name;
+//     static import module_name;
+//     static import module_alias = module_name;
+//  specifically, the module-name has to be the last thing on the line.
+//
+//
+// The directory structure employs a heirarchy of:
+// * Bundle    - A collection of products. Has no source code of its own, and does not appear in package paths.
+//               Corresponds to the root of a checkout/repository.
+// * Product   - A directory to provide namespace.
+// * Package   - A self-contained set of library code, executable code, docs, tests, etc.
+//               Can contain nested packages, and can refer to other packages.
+//
+// Bundles are the mechanism for reusing source code in other repositories.
+// We do this by lines in a bundle "uses" file that specify where to find a used bundle.
+//
+// The directory structure within a bundle is:
+//
+//  +--configure                Script to set up the build directory and check for assumed system libraries
+//  |
+//  +--repackage                Script to produce a source tarball that can be built with dsss
+//  |
+//  +--uses                     Specifies which other bundles to use, with paths relative to this bundle
+//  |
+//  +--product-name(s)          Provides namespace - only contains packages
+//      |
+//      +--package-name(s)      Can contain library code
+//          |
+//          +--doc              Restructured-text documents and associated images
+//          |
+//          +--data             Data files required by tests and binaries
+//          |
+//          +--test             Code for regression tests
+//          |
+//          +--prog             Code for binaries
+//          |
+//          +--package-name(s)  Nested packages
+//
+// The resultant build directory structure is:
+//
+//  +--build                    Script to build the system (put there by configure)
+//  |
+//  +--obj                      Contains object files and libraries in package structure
+//  |
+//  +--test                     Contains test binaries and results in package structure
+//  |
+//  +--bin                      Contains the binaries built from prog directories
+//  |
+//  +--doc                      Contains the package html docs, in package structure
+//
+// The dependency rules are:
+// * A built file depends on its sources, and anything the sources explicitly import.
+// * A parent depends on its children, transitively, and everything they depend on, transitively.
+// * Everything depends on the parents and descendants of its explicit imports,
+//   up to but not including the common ancestor, transitively.
+//
+
+
+// TODO - add doc, data
+// TODO - add timeout on test execution - say 2 seconds.
+// TODO - add excludes files in packages to allow files and directories to be ignored.
+
+
+//-------------------------------------------------------------------------------
+// primitives
+//-------------------------------------------------------------------------------
+
+
+//
+// Some global data
+//
+class Global {
+    static string       buildPath;
+    static string       optionsPath;
+    static string[]     bundlePaths;
+    static bool[string] products;
+}
+
+
+//
+// throw an exception
+//
+void error(string text) {
+    writefln("%s", text);
+    throw new Exception(text);
+}
+
+
+//
+// return the modification time of the file at path
+//
+d_time modified_time(string path) {
+    d_time creation_time, access_time, modified_time;
+    if (!exists(path)) {
+        return 0;
+    }
+    getTimes(path, creation_time, access_time, modified_time);
+    return modified_time;
+}
+
+
+//
+// A string formatter that allows progressive composition
+//
+class StringFormatter {
+    char[] buffer;
+    int pos;
+
+    this() {
+        buffer.length = 1024;
+    }
+
+    void clear() {
+        pos = 0;
+    }
+
+    // append some formatted text to the internal buffer
+    void format(...) {
+        void put(dchar c) {
+            if (pos+4 >= buffer.length) {
+                buffer.length = buffer.length * 2;
+            }
+            char[4] buf;
+            char[] result = std.utf.toUTF8(buf, c);
+            buffer[pos..pos+4] = buf;
+            pos += result.length;
+        }
+        std.format.doFormat(&put, _arguments, _argptr);
+    } 
+
+    // return a copy of the internal buffer
+    string str() {
+        return buffer[0..pos].idup;
+    }
+}
+
+//---------------------------------------------------------
+// parser
+//---------------------------------------------------------
+
+// FIXME - put in a proper parser that understands about version syntax and all the rest.
+
+///
+/// Parse file for import statements, returning an array of module names
+///
+string[] parseImports(string path) {
+
+    string[] result;
+
+    //
+    // If this is an import declaration line, return the imported module, else null
+    // This isn't perfect by any means (but it is simple and fast), but here we go:
+    // * Strip whitespace from front.
+    // * If first word is private, public, static or import, and
+    // * If we have import already or there is another import later in the line, then
+    // * The last word on the line is a module.
+    // A less-than-perfect parser is ok because it only has to work for code
+    // within the project, so we can write code that it works for.
+    // 
+    void parseForImport(string line) {
+        static string[] leaders = ["private ", "public ", "static ", "import "];
+        bool found;
+        string stripped = stripl(line);
+        if (stripped.length > 8) {
+            foreach (i, leader ; leaders) {
+                if (stripped[0..leader.length] == leader) {
+                    // this is a possibility - look for import token
+                    scope string[] tokens = split(stripped);
+                    if (tokens.length > 1) {
+                        if (i == 3) {
+                            found = true;
+                        }
+                        else {
+                            foreach (token; tokens[0..$-1]) {
+                                if (token == "import") {
+                                    found = true;
+                                    break;
+                                }
+                            }
+                        }
+                    }
+
+                    if (found) {
+                        //writefln("found import of %s", tokens[$-1][0..$-1]);
+                        result ~= tokens[$-1][0..$-1].idup;
+                    }
+
+                    break;
+                }
+            }
+        }
+    }
+
+    scope file = new BufferedFile(path);
+    foreach (char[] line; file) {
+        parseForImport(line.idup);
+    }
+    //writefln("imports of %s are %s", file, result);
+    return result;
+}
+
+
+//------------------------------------------------------------------------------
+// Dependency graph
+//------------------------------------------------------------------------------
+
+//
+// An item that may depend on things and be depended on in turn
+//
+abstract class Item {
+    static Item[string] mItems; // items by path so parent-path items can be found
+
+    string mKind; // for logging
+    string mPath; // identifies the item
+    long mTime;   // modification time, 0 ==> doesn't exist, -1 ==> don't care
+    Item[string] mDepends; // items this one depends on
+
+    bool   mChecking;
+    bool   mChecked;
+    bool   mDirty;
+
+    this(string kind, string path) {
+        //writefln("creating %s-item at %s", kind, path);
+        mKind = kind;
+        mPath = path;
+        mTime = modified_time(path);
+        mItems[path] = this;
+    }
+
+    // add a dependency to this item
+    final void addDepends(Item item) {
+        if (!(item.mPath in mDepends)) {
+            //writefln("%s depends on %s", this.mPath, item.mPath);
+            mDepends[item.mPath] = item;
+        }
+    }
+
+    // Find out if this item is dirty (needs to be built), returning true if dirty.
+    final bool dirty() {
+        if (mChecking) {
+            error(format("circularity: %s depends on itself", mPath));
+        }
+        if (!mChecked) {
+            mChecking = true;
+            if (mTime == 0) {
+                //writefln("%s is dirty because it doesn't exist", basename(mPath));
+                mDirty = true;
+            }
+            foreach (other; mDepends) {
+                if (mDirty) break;
+                if (other.dirty) {
+                    //writefln("%s is dirty because %s is dirty", basename(mPath), basename(other.mPath));
+                    mDirty = true;
+                }
+                else if (mTime != -1 && other.mTime != -1 && other.mTime > mTime) {
+                    //writefln("%s is dirty because %s is younger", basename(mPath), basename(other.mPath));
+                    //writefln("  other=%s, mine=%s", other.mTime, mTime);
+                    mDirty = true;
+                }
+            }
+            mChecking = false;
+            mChecked = true;
+        }
+        return mDirty;
+    }
+
+    // build just this item
+    protected abstract void build();
+
+    // build this item and everything it depends on
+    final void buildChain() {
+        if (dirty) {
+            foreach (other; mDepends) {
+                other.buildChain;
+            }
+            build;
+            if (mTime != -1) {
+                mTime = modified_time(mPath);
+            }
+            mDirty = false;
+        }
+    }
+
+    string toString() {
+        return mKind ~ "-item-" ~ mPath;
+    }
+}
+
+
+//
+// A directory
+//
+class DirectoryItem : Item {
+
+    this(string path) {
+        super("directory", path);
+        mTime = mPath.exists ? 1 : 0;
+
+        // this directory depends on its parent directory chain
+        string parent_path = path.dirname;
+        if (parent_path.length > 0 && parent_path[0] != '.' ) {
+            Item* parent = parent_path in mItems;
+            if (parent) {
+                assert(cast(DirectoryItem *)parent);
+                addDepends(*parent);
+            }
+            else {
+                // recursively create chain of directories above this,
+                // and depend on it.
+                addDepends(new DirectoryItem(parent_path));
+            }
+        }
+    }
+
+    // build this directory
+    override void build() {
+        writefln("Directory %s", mPath);
+        mkdir(mPath);
+    }
+}
+
+
+///
+/// A group Item - used to help manage build order via dependencies
+/// All FileItems are members of exactly one group, and they all depend on
+/// all the groups their owning group depends on.
+///
+class GroupItem : Item {
+
+    static GroupItem[string] mGroups;  // all groups
+
+    Item[] mMembers;
+
+    this(string path) {
+        super("group", path);
+        mTime = -1;
+        mGroups[mPath] = this;
+    }
+
+    void addMember(Item member) {
+        addDepends(member);
+        mMembers ~= member;
+    }
+
+    // add dependencies on other group and its ancestors
+    // Only called AFTER all items have been greated and assigned their group.
+    void dependsOnGroup(GroupItem other) {
+        if (other !is this) {
+
+            // this group and everything in it (other than source) depends on other group
+            addDepends(other);
+            foreach (member; mMembers) {
+                if (!cast(SourceItem) member) {
+                    member.addDepends(other);
+                }
+            }
+
+            // and all other group's higher-level groups until common ancestor
+            string other_parent_path = other.mPath.dirname;
+            if (other_parent_path != "." &&
+                other_parent_path.length < mPath.length &&
+                mPath[0..other_parent_path.length] != other_parent_path)
+            {
+                GroupItem* other_parent = other_parent_path in mGroups;
+                if (other_parent) {
+                    dependsOnGroup(*other_parent);
+                }
+            }
+        }
+    }
+
+    // build just this item
+    override void build() {
+        // nothing to do
+        writefln("Group     %s", mPath);
+    }
+}
+
+
+//
+// a physical file in the filesystem
+//
+abstract class FileItem : Item {
+    GroupItem mGroup;
+
+    this(string kind, string path, GroupItem group) {
+        super(kind, path);
+        mGroup = group;
+        group.addMember(this);
+
+        // this file depends on on its parent directory
+        string parent_path = path.dirname;
+        Item* parent = parent_path in mItems;
+        if (parent) {
+            assert(cast(DirectoryItem *)parent);
+            addDepends(*parent);
+        }
+        else {
+            addDepends(new DirectoryItem(parent_path));
+        }
+    }
+}
+
+
+//
+// A source file - must exist
+//
+class SourceItem : FileItem {
+
+    SourceItem[string] mImports; // the source items this one imports 
+
+    this(string path, GroupItem group) {
+        super("source", path, group);
+        if (!isfile(path)) error(format("source file %s not found", path));
+    }
+
+    // Add an import to this source item.
+    // These are used later to determine all the non-structural item dependencies.
+    void addImport(SourceItem source) {
+        if (!(source.mPath in mImports)) {
+            //writefln("%s imports %s", mPath, source.mPath);
+            mImports[source.mPath] = source;
+        }
+    }
+
+    override void build() {
+        // nothing to do, and should never be dirty
+        writefln("Source    %s", mPath);
+        error("should never need to build Source");
+    }
+}
+
+
+//
+// An object file
+//
+class ObjectItem : FileItem {
+    SourceItem mSource;
+
+    this(string path, SourceItem source, GroupItem group) {
+        super("object", path, group);
+        addDepends(source);
+        mSource = source;
+    }
+
+    // Use our source's imports to add non-structural dependencies.
+    // The rules are:
+    // * We depend on any sources our source imports, transitively.
+    // * Our group depends on the imported source's group, and any of its
+    //   ancestral groups that aren't our ancestors.
+    // * We must not trace a chain of imports back to our source.
+    void resolveImports() {
+
+        void dependsOnImportsOf(SourceItem source) {
+            foreach (s; source.mImports) {
+                if (s is mSource) {
+                    error(format("circular chain of imports - last link from %s to %s",
+                                 source.toString, s.toString));
+                }
+                if (s.mGroup !is mGroup) {
+                    addDepends(s.mGroup);
+                }
+                addDepends(s);
+                dependsOnImportsOf(s);
+            }
+        }
+
+        dependsOnImportsOf(mSource);
+
+        foreach (s; mSource.mImports) {
+            mGroup.dependsOnGroup(s.mGroup);
+        }
+    }
+
+    // an object file is built from its source
+    override void build() {
+        writefln("Object    %s", mPath);
+        scope cmd = new StringFormatter;
+
+        cmd.format("dmd -c @%s", Global.optionsPath);
+        foreach (path; Global.bundlePaths) {
+            cmd.format(" -I", path);
+        }
+        cmd.format(" -od%s -of%s %s", dirname(mPath), basename(mPath), mSource.mPath);
+
+        if (std.process.system(cmd.str)) {
+            writefln("%s", cmd.str);
+            error(format("build of %s failed", mPath));
+        }
+    }
+}
+
+
+//
+// A library
+//
+class LibraryItem : FileItem {
+    string mName;
+
+    this(string path, string name, GroupItem group) {
+        super("library", path, group);
+        mName = name;
+    }
+
+    // a library is built by archiving all of its contributing ObjectFiles
+    override void build() {
+        writefln("Library   %s", mPath);
+        scope cmd = new StringFormatter;
+        cmd.format("ar csr %s ", mPath);
+        foreach (item; mDepends) {
+            auto obj = cast(ObjectItem) item;
+            if (obj) {
+                cmd.format(" %s", obj.mPath);
+            }
+        }
+        if (std.process.system(cmd.str)) {
+            writefln("%s", cmd.str);
+            error("command failed");
+        }
+    }
+
+    // Add this library and any it depends on to libs, if they aren't there already.
+    // NOTE - they are added with libraries appearing after those they depend on
+    // (reverse of compiler command-line).
+    void addNeeded(inout LibraryItem[] libs) {
+
+        void add(LibraryItem lib) {
+            foreach (item; libs) {
+                if (lib is item) {
+                    return;
+                }
+            }
+            libs ~= lib;
+        }
+
+        foreach (item; mDepends) {
+            auto lib = cast(LibraryItem) item;
+            if (lib) {
+                lib.addNeeded(libs);
+            }
+        }
+        add(this);
+    }
+}
+
+
+//
+// A program
+//
+class ProgramItem : FileItem {
+    ObjectItem mObject;
+
+    this(string path, ObjectItem object, GroupItem group) {
+        super("program", path, group);
+        mObject = object;
+        addDepends(object);
+    }
+
+    // a program file is built from its object and all the libraries
+    // the object needs, transitively
+    override void build() {
+        writefln("Program   %s", mPath);
+        scope cmd = new StringFormatter();
+
+        cmd.format("dmd -g @%s -L-L%s", Global.optionsPath, join(Global.buildPath, "lib"));
+        cmd.format(" -of%s %s", mPath, mObject.mPath);
+
+        // add the libraries we need
+        LibraryItem[] libs;
+        foreach (item; mDepends) {
+            auto lib = cast(LibraryItem) item;
+            if (lib) {
+                lib.addNeeded(libs);
+            }
+        }
+        foreach_reverse (lib; libs) {
+            cmd.format(" -L-l%s", lib.mName);
+        }
+
+        if (std.process.system(cmd.str)) {
+            writefln("%s", cmd.str);
+            error("command failed");
+        }
+    }
+}
+
+
+//
+// A test result - run the program to generate the result file
+//
+class ResultItem : FileItem {
+    ProgramItem mProgram;
+
+    this(string path, ProgramItem program, GroupItem group) {
+        super("result", path, group);
+        mProgram = program;
+        addDepends(program);
+    }
+
+    override void build() {
+        writef("Test      %s", mPath);
+        if (exists(mPath)) {
+            mPath.remove();
+        }
+        scope cmd = new StringFormatter();
+        scope tmpPath = (mPath ~ ".failed").idup;
+        cmd.format("%s > %s 2>&1", mProgram.mPath, tmpPath);
+
+        if (std.process.system(cmd.str)) {
+            // failed
+            writefln(" failed");
+            writefln("%s", cmd.str);
+            error("test failed");
+        }
+        else {
+            tmpPath.rename(mPath);
+            writefln(" passed");
+        }
+    }
+}
+
+
+
+
+//-------------------------------------------------------------------------------
+// Tree of modules, packages, products and system, etc - things in the source tree.
+// When a node explicity depends on another, it (and its parent up to the common ancestor)
+// implicity depend on that node and its ancestors and descendants, and everything they depend on.
+// Circularities are not allowed.
+//-------------------------------------------------------------------------------
+
+//
+// a node in a tree. It depends on explicit mDepends, plus any children, and
+// anything any of those depend on. The dependencies are tracked to enforce
+// dependency rules. Actual build-order dependencies are done with Items.
+//
+class Node {
+    string mName;
+    Node   mParent;
+    Node[] mChildren;
+    string mSlashName;
+    string mDotName;
+
+    Node[] mDepends;
+
+    // create the root of the tree
+    this() {
+    }
+
+    // create a node in the tree
+    this(string name, Node parent) {
+        assert(parent);
+        mName = name;
+        mParent = parent;
+        if (parent.mName.length) {
+            // child of non-root
+            mSlashName = parent.mSlashName ~ sep ~ mName;
+            mDotName   = parent.mDotName   ~ "." ~ mName;
+        }
+        else {
+            // child of the root
+            mSlashName = mName;
+            mDotName   = mName;
+        }
+        parent.mChildren ~= this;
+    }
+
+    // return a node with the given name-chain, or null if not found.
+    // The root node is not considered part of the chain
+    final Node find(string[] chain) {
+
+        // return child of node as specified by chain, or null if not found
+        Node locate(Node node, string[] chain) {
+            foreach (child; node.mChildren) {
+                if (child.mName == chain[0]) {
+                    if (chain.length > 1) {
+                        return locate(child, chain[1..$]);
+                    }
+                    else {
+                        return child;
+                    }
+                }
+            }
+            return null;
+        }
+
+        // ascend to root and recursively descend to specified node
+        assert(chain.length > 0);
+        Node node = this;
+        while (node.mParent) {
+            node = node.mParent;
+        }
+        return locate(node, chain);
+    }
+}
+
+
+//
+// A source-code module
+//
+class Module : Node {
+    SourceItem  mSource;
+    ObjectItem  mObject;
+    FileItem    mClump;  // library or program item this module contributes to
+
+    this(string name, string path, Node parent, GroupItem group) {
+        super(name, parent);
+        string obj_path = join(Global.buildPath, "obj", mSlashName ~ ".o");
+        mSource  = new SourceItem(path, group);
+        mObject  = new ObjectItem(obj_path, mSource, group);
+        //writefln("loaded Module %s from %s", mDotName, mSource.mPath);
+    }
+
+    void setClump(FileItem clump) {
+        assert(!mClump);
+        mClump   = clump;
+        mClump.addDepends(mObject);
+    }
+
+    // trace direct imports
+    final void trace() {
+
+        // add an import dependancy (if it is inside our project)
+        void imports(string importing) {
+            string[] chain = split(importing, ".");
+            assert(chain.length > 0);
+
+            // only depend on internal products
+            if (!(chain[0] in Global.products)) {
+                return;
+            }
+
+            // find the imported Module's source item and add it to our source item's imports
+            Module other = cast(Module) find(chain);
+            if (!other || !other.mClump) {
+                writefln("import of unknown module " ~ importing ~ " from " ~ mDotName);
+            }
+            else if (cast(LibraryItem)other.mClump) {
+                // the other's mClump is a library
+
+                // our source imports other's source - only allowed for library source
+                mSource.addImport(other.mSource);
+
+                if (mClump.mPath != other.mClump.mPath) {
+                    // this module's clump depends on other's library (needed for linking)
+                    mClump.addDepends(other.mClump);
+                }
+            }
+            else {
+                error(format("import of non-library module %s from %s",
+                             importing, mDotName));
+            }
+        }
+
+        assert(mClump);
+        foreach (importing; parseImports(mSource.mPath)) {
+            //writefln("%s imports module %s", mDotName, importing);
+            imports(importing);
+        }
+    }
+}
+
+
+//
+// An executable program.
+//
+class Exe : Module {
+    ProgramItem mProgram;
+    ResultItem  mResult;
+
+    this(string name, string path, Node parent, bool test, GroupItem group) {
+        super(name, path, parent, group);
+        if (test) {
+            mProgram = new ProgramItem(join(Global.buildPath, "test", mSlashName), mObject, group);
+            mResult = new ResultItem(join(Global.buildPath, "test", mSlashName ~ ".result"), mProgram, group);
+        }
+        else {
+            mProgram = new ProgramItem(join(Global.buildPath, "bin", mName), mObject, group);
+        }
+        setClump(mProgram);
+        //writefln("loaded Program %s", mProgram.mPath);
+    }
+}
+
+
+//
+// a package
+//
+class Package : Node {
+    LibraryItem mLibrary;
+    GroupItem   mGroup;   // represents the package
+
+    // load the package
+    this(string name, string path, Node parent) {
+        super(name, parent);
+        mGroup = new GroupItem(mSlashName);
+
+        string lib_name = replace(mDotName, ".", "_");
+
+        // examine all the children of the package's directory
+        foreach (string child; listdir(path)) {
+            string p = join(path, child);
+            if (child[0] != '.') {
+
+                if (isfile(p)) {
+
+                    if (child.length > 2 && child[$-2..$] == ".d") {
+                        // a library module
+                        if (!mLibrary) {
+                            mLibrary = new LibraryItem(join(Global.buildPath, "lib", "lib" ~ lib_name ~ ".a"),
+                                                       lib_name, mGroup);
+                        }
+                        Module m = new Module(getName(child), p, this, mGroup);
+                        m.setClump(mLibrary);
+                    }
+                }
+
+                else if (isdir(p)) {
+
+                    if (child == "build-tool") {
+                        // reserved for build-tool
+                    }
+                    else if (child == "doc") {
+                        // TODO
+                    }
+                    else if (child == "data") {
+                        // TODO
+                    }
+                    else if (child == "test") {
+                        // test programs
+                        foreach (string grandchild; listdir(p)) {
+                            string p2 = join(p, grandchild);
+                            if (grandchild[0] != '.' &&
+                                isfile(p2) &&
+                                grandchild.length > 2 &&
+                                grandchild[$-2..$] == ".d")
+                            {
+                                Exe exe = new Exe(getName(grandchild), p2, this, true, mGroup);
+                            }
+                        }
+                    }
+                    else if (child == "prog") {
+                        // deliverable programs
+                        foreach (string grandchild; listdir(p)) {
+                            string p2 = join(p, grandchild);
+                            if (child[0] != '.' &&
+                                isfile(p2) &&
+                                grandchild.length > 2 &&
+                                grandchild[$-2..$] == ".d")
+                            {
+                                Exe exe = new Exe(getName(grandchild), p2, this, false, mGroup);
+                            }
+                        }
+                    }
+                    else {
+                        // a child package
+                        Package pkg = new Package(child, p, this);
+                        mGroup.addDepends(pkg.mGroup);
+                    }
+                }
+            }
+        }
+    }
+
+    // trace dependancies
+    void trace() {
+        foreach (child; mChildren) {
+            Package pkg = cast(Package) child;
+            Module m = cast(Module) child;
+            if (pkg) {
+                pkg.trace();
+            }
+            else if (m) {
+                m.trace();
+            }
+        }
+    }
+}
+
+
+//
+// A Product - a top-level package
+//
+class Product : Package {
+    string mPath;
+
+    this(string name, string path, Node parent) {
+        writefln("loading Product %s from %s", name, path);
+        mPath = path;
+        super(name, path, parent);
+    }
+}
+
+
+//
+// the whole project
+//
+class Project : Node {
+
+    // create the project, loading its bundle and those it uses
+    this(string path) {
+        super();
+        load(path);
+    }
+
+    // load the bundle at path
+    void load(string path) {
+        //writefln("loading bundle from %s", path);
+
+        // add path to Global for use when compiling
+        Global.bundlePaths ~= path;
+        Global.optionsPath = path.join("options");
+
+        //
+        // load bundles specified in the uses file - lines are bundle paths relative to path
+        //
+        string uses_path = join(path, "uses");
+        if (exists(uses_path) && isfile(uses_path)) {
+            //writefln("reading uses file: %s", uses_path);
+            scope file = new BufferedFile(uses_path);
+            foreach (char[] line; file) {
+                load(join(path, line.idup));
+            }
+        }
+
+        //
+        // load local products
+        //
+        foreach (string name; listdir(path)) {
+            if (name[0] != '.') {
+                string p = join(path, name);
+                if (isdir(p)) {
+                    foreach (node; mChildren) {
+                        Product existing = cast(Product)node;
+                        if (existing && existing.mName == name) {
+                            error(format("product %s has two paths: %s and %s",
+                                         name, p, existing.mPath));
+                        }
+                    }
+                    new Product(name, p, this);
+                    Global.products[name] = true;
+                }
+            }
+        }
+
+
+        //
+        // trace imports, then finish the Item dependency graph
+        //
+        foreach (child; mChildren) {
+            Product product = cast(Product) child;
+            if (product) {
+                product.trace();
+            }
+        }
+        foreach (item; Item.mItems) {
+            ObjectItem obj = cast(ObjectItem) item;
+            if (obj) {
+                obj.resolveImports;
+            }
+        }
+
+        //
+        // print a dependency graph
+        //
+        foreach (group; GroupItem.mGroups) {
+            // TODO
+        }
+
+        //
+        // Build everything that is out of date
+        //
+        foreach (group; GroupItem.mGroups) {
+            group.buildChain;
+        }
+        writefln("all done");
+    }
+}
+
+
+//--------------------------------------------------------------------------------------
+// main - args[1] is where the project source is, and args[2] is where the build happens
+// All the paths are set to absolute so that the source files
+// can be found by an editor or debugger, wherever its current directory is.
+//--------------------------------------------------------------------------------------
+int main(string[] args) {
+    int usage() {
+        writefln("Usage: builder <project-path> <build-path>");
+        return -1;
+    }
+
+    if (args.length < 3) return usage;
+
+    string sourcePath = args[1];
+    Global.buildPath  = args[2];
+
+    writefln("building source at %s into %s", sourcePath, Global.buildPath); 
+
+    // do the work
+    scope project = new Project(sourcePath);
+
+    return 0;
+}
--- a/cairo/routines.d	Wed Jul 29 14:11:35 2009 +0930
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,11 +0,0 @@
-module cairo.routines;
-
-public {
-    import tk.geometry;
-    import cairo.Context;
-}
-
-void rectangle(scope Context cr, in Rectangle rectangle) {
-    cr.rectangle(rectangle.position.x, rectangle.position.y,
-                 rectangle.size.x, rectangle.size.y);
-}
--- a/common/undo.d	Wed Jul 29 14:11:35 2009 +0930
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,15 +0,0 @@
-module common.undo;
-
-abstract class Action {
-    void undo();
-    void redo();
-}
-
-interface IUndoObserver {
-    void canUndo(in bool value, in string description);
-    void canRedo(in bool value, in string description);
-}
-
-interface IUndoManager {
-    void addAction(Action action);
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/configure.d	Sun Aug 02 16:27:21 2009 +0930
@@ -0,0 +1,90 @@
+// Configure script, to be run with rdmd.
+//
+// Checks dependencies, sets up a build directory, builds
+// a local copy of the builder into the build directory,
+// and puts assorted scripts into the build directory to
+// facilitate building.
+//
+// If your project depends on installed libraries, you need to
+// add an options file containing command-line options such as:
+//    -L-lgtkd
+// You also need to install the libraries somewhere that the compiler
+// can find them. Two simple options are:
+// * In the dmd installation directory, or
+// * Elsewhere, and edit dmd.conf to point to there.
+// 
+// 
+// The source paths you depend on need to be listed in a uses file.
+// Uses files are not transitive - only the one in the directory
+// you execute the configure script applies.
+//
+// Usage:  rdmd configure.d <target-path>
+//
+// eg: rdmd configure.d ~/builds/myproject
+//
+
+
+import std.stdio;
+import std.path;
+import std.file;
+import std.string;
+import std.stdio;
+import std.process;
+
+
+int main(string[] args) {
+
+    string source_path = getcwd;
+
+    // parse command-line options
+    if (args.length < 2) {
+        writefln("Error - usage is: rdmd <configure.d-path> <build-path>");
+        return -1;
+    }
+    string target_path = args[1];
+
+    // check that source_path looks like a source tree by making sure
+    // that a README and options file are present
+    writefln("Examining source");
+    if (!source_path.join("README").exists || !source_path.join("options").exists) {
+        writefln("Error - current directory must contain README and options");
+        return -1;
+    }
+
+    // ensure that target_path exists
+    writefln("Establishing build directory");
+    if (target_path.exists) {
+        writefln("Error - %s exists - abandoning configure", target_path);
+        return -1;
+    }
+    target_path.mkdirRecurse;
+    string bin_path = target_path.join("bin");
+    bin_path.mkdir;
+
+    // compile builder into bin_path
+    writefln("Building the builder");
+    int ret = system(format("dmd %s -O -of%s",
+                            source_path.join("builder.d"), bin_path.join("builder")));
+    if (ret) {
+        writefln("Error - builder failed to compile");
+        return -1;
+    }
+    bin_path.join("builder.o").remove;
+
+    // set up scripts
+    {
+        auto file = File(target_path.join("environment"), "w");
+        file.writefln("SOURCE_PATH=%s", source_path);
+        file.writefln("TARGET_PATH=%s", target_path);
+    }
+    {
+        auto file = File(target_path.join("build"), "w");
+        file.writefln("#!/bin/bash");
+        file.writefln("source %s", target_path.join("environment"));
+        file.writefln("%s %s %s", bin_path.join("builder"), source_path, target_path);
+    }
+    system("chmod +x " ~ target_path.join("build"));
+    writefln("All done");
+
+    return 0;
+}
--- a/dia/grid_layer.d	Wed Jul 29 14:11:35 2009 +0930
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,35 +0,0 @@
-module dia.grid_layer;
-
-public {
-    import dia.icanvas;
-}
-
-private {
-    import cairo.routines;
-    import std.math;
-}
-
-interface Grid {
-}
-
-class GridLayer : Layer, Grid {
-    this(in string name) {
-        super(name);
-    }
-
-    override Rectangle bounds() const {
-        // We don't require any geometry
-        return Rectangle();
-    }
-
-    override void draw(in Viewport viewport,
-                       in Rectangle pixel_damage, scope Context pixel_cr,
-                       in Rectangle model_damage, scope Context model_cr) const {
-        //double zoom = viewport.zoom;
-
-        //double start_x = modf(damage.min_corner.x, zoom);
-    }
-
-    private {
-    }
-}
--- a/dia/icanvas.d	Wed Jul 29 14:11:35 2009 +0930
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,66 +0,0 @@
-module dia.icanvas;
-
-public {
-    import tk.geometry;
-    import tk.events;
-    import cairo.Context;
-}
-
-private {
-    import std.typecons;
-}
-
-mixin(defineEnum!("Cursor",
-                  "DEFAULT", "HAND", "CROSSHAIR"));
-
-interface Viewport {
-    void zoom_relative(in Point pixel_datum, in double factor);
-    void pan_relative(in Vector pixel_displacement);
-    void set_cursor(in Cursor cursor);
-    void damage_model(in Rectangle area);      // FIXME could be an inout parameter of the event handling, or a special scope Damage object that supports growth only
-    void damage_pixel(in Rectangle area);      // FIXME as above
-
-    /*
-    // FIXME not sure about these:
-    double zoom() const;
-    Point model_to_pixel(in Point model) const;
-    Point pixel_to_model(in Point pixel) const;
-    Vector model_to_pixel(in Vector model) const;
-    Vector pixel_to_model(in Vector pixel) const;
-    Rectangle model_to_pixel(in Rectangle model) const;
-    Rectangle pixel_to_model(in Rectangle model) const;
-    double model_to_pixel(in double model) const;
-    double pixel_to_model(in double pixel) const;
-    */
-}
-
-interface EventHandler {
-    bool handle_button_press(scope Viewport viewport, in ButtonEvent event);
-    bool handle_button_release(scope Viewport viewport, in ButtonEvent event);
-    bool handle_motion(scope Viewport viewport, in MotionEvent event);
-    bool handle_scroll(scope Viewport viewport, in ScrollEvent event);
-    //bool handle_enter(scope Viewport viewport, CrossingEvent event);
-    //bool handle_leave(scope Viewport viewport, CrossingEvent event);
-    //bool handle_focus_in(scope Viewport viewport, FocusEvent event);
-    //bool handle_focus_out(scope Viewport viewport, FocusEvent event);
-    bool handle_key_press(scope Viewport viewport, in KeyEvent event);
-    bool handle_key_release(scope Viewport viewport, in KeyEvent event);
-}
-
-abstract class Layer {
-    this(in string name) {
-        mName = name;
-    }
-
-    string name() const { return mName; }
-
-    Rectangle bounds() const;
-    //void zoom_changed
-    void draw(in Viewport viewport,
-              in Rectangle pixel_damage, scope Context pixel_cr,
-              in Rectangle model_damage, scope Context model_cr) const;
-
-    private {
-        invariant string mName;
-    }
-}
--- a/dia/page_layer.d	Wed Jul 29 14:11:35 2009 +0930
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,46 +0,0 @@
-module dia.page_layer;
-
-private {
-    import cairo.routines;
-}
-
-public {
-    import dia.icanvas;
-}
-
-interface Page {
-}
-
-class PageLayer : Layer, Page {
-    this(in string name) {
-        super(name);
-        mPageGeometry = Rectangle(Point.DEFAULT, Vector(210.0, 297.0));
-        //mPageGeometry = Rectangle(Point.DEFAULT, Vector(100.0, 100.0));
-    }
-
-    override Rectangle bounds() const {
-        return mPageGeometry;
-    }
-
-    override void draw(in Viewport viewport,
-                       in Rectangle pixel_damage, scope Context pixel_cr,
-                       in Rectangle model_damage, scope Context model_cr) const {
-        // Make the paper white, with a border
-
-        model_cr.save; {
-            model_cr.setSourceRgba(1.0, 1.0, 1.0, 1.0);
-            rectangle(model_cr, mPageGeometry);
-            model_cr.fill;
-        } model_cr.restore;
-
-        model_cr.save; {
-            model_cr.setSourceRgba(0.0, 0.0, 0.0, 1.0);
-            rectangle(model_cr, mPageGeometry);
-            model_cr.stroke;
-        } model_cr.restore;
-    }
-
-    private {
-        Rectangle mPageGeometry;
-    }
-}
--- a/dia/standard_tools.d	Wed Jul 29 14:11:35 2009 +0930
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,155 +0,0 @@
-module dia.standard_tools;
-
-public {
-    import dia.tool;
-}
-
-private {
-    import cairo.routines;
-    import std.math;
-    import std.stdio;
-}
-
-final class PanTool : Tool {
-    override bool handle_button_press(scope Viewport viewport, in ButtonEvent event) {
-        if (event.button_name == ButtonName.MIDDLE) {
-            mLastPosition = event.pixel_point;
-            return true;
-        }
-        else {
-            return false;
-        }
-    }
-
-    override bool handle_motion(scope Viewport viewport, in MotionEvent event) {
-        if (event.mask.is_set(Modifier.MIDDLE_BUTTON)) {
-            viewport.pan_relative(mLastPosition - event.pixel_point);
-            mLastPosition = event.pixel_point;
-
-            return true;
-        }
-        else {
-            return false;
-        }
-    }
-
-    override bool handle_scroll(scope Viewport viewport, in ScrollEvent event) {
-        if (event.mask.is_unset(Modifier.MIDDLE_BUTTON)) {
-            Vector delta;
-
-            switch (event.scroll_direction) {
-            case ScrollDirection.UP:
-                delta = event.mask.is_set(Modifier.SHIFT) ? Vector(AMOUNT, 0.0) : Vector(0.0, AMOUNT);
-                break;
-            case ScrollDirection.DOWN:
-                delta = event.mask.is_set(Modifier.SHIFT) ? Vector(-AMOUNT, 0.0) : Vector(0.0, -AMOUNT);
-                break;
-            case ScrollDirection.LEFT:
-                delta = Vector(-AMOUNT, 0.0);
-                break;
-            case ScrollDirection.RIGHT:
-                delta = Vector(AMOUNT, 0.0);
-                break;
-            }
-
-            viewport.pan_relative(delta);
-        }
-
-        return true;
-    }
-
-    private {
-        Point mLastPosition;
-        static invariant double AMOUNT = 60.0;
-    }
-}
-
-final class ZoomTool : Tool {
-    override bool handle_scroll(scope Viewport viewport, in ScrollEvent event) {
-        if (event.mask.is_set(Modifier.CONTROL)) {
-            if (event.scroll_direction == ScrollDirection.DOWN) {
-                viewport.zoom_relative(event.pixel_point, 1.0 / ZOOM);
-                return true;
-            }
-            else if (event.scroll_direction == ScrollDirection.UP) {
-                viewport.zoom_relative(event.pixel_point, ZOOM);
-                return true;
-            }
-            else {
-                return false;
-            }
-        }
-        else {
-            return false;
-        }
-    }
-
-    private {
-        static invariant double ZOOM = sqrt(2.0);
-    }
-}
-
-final class LassoTool : Tool {
-    override bool handle_button_press(scope Viewport viewport, in ButtonEvent event) {
-        if (event.button_name == ButtonName.LEFT) {
-            mActive = true;
-            mAnchorPoint = mCurrentPoint = event.pixel_point;
-            viewport.set_cursor(Cursor.HAND);
-            return true;
-        }
-        else {
-            return false;
-        }
-    }
-
-    override bool handle_button_release(scope Viewport viewport, in ButtonEvent event) {
-        if (event.button_name == ButtonName.LEFT && mActive) {
-            mActive = false;
-            viewport.damage_pixel(Rectangle(mAnchorPoint, mCurrentPoint).feathered(LINE_WIDTH / 2.0));
-            viewport.set_cursor(Cursor.DEFAULT);
-            return true;
-        }
-        else {
-            return false;
-        }
-    }
-
-    override bool handle_motion(scope Viewport viewport, in MotionEvent event) {
-        if (mActive) {
-            viewport.damage_pixel(Rectangle(mAnchorPoint, mCurrentPoint).feathered(LINE_WIDTH / 2.0));
-            mCurrentPoint = event.pixel_point;
-            viewport.damage_pixel(Rectangle(mAnchorPoint, mCurrentPoint).feathered(LINE_WIDTH / 2.0));
-        }
-
-        return false;
-    }
-
-    override void draw(in Viewport viewport,
-                       in Rectangle pixel_damage, scope Context pixel_cr,
-                       in Rectangle model_damage, scope Context model_cr) const {
-        if (mActive) {
-            pixel_cr.save; {
-                pixel_cr.setSourceRgba(0.0, 0.0, 0.8, 0.3);
-                rectangle(pixel_cr, Rectangle(mCurrentPoint, mAnchorPoint));
-                pixel_cr.fill();
-            } pixel_cr.restore();
-
-            pixel_cr.save(); {
-                //double[] dashes = [ 4.0, 4.0 ];
-                //pixel_cr.setDash(dashes, 0.0);
-                pixel_cr.setSourceRgba(0.0, 0.0, 0.5, 1.0);
-                pixel_cr.setLineWidth(LINE_WIDTH);
-                //writefln("Drawing rectangle: %s", Rectangle(mCurrentPoint, mAnchorPoint));
-                rectangle(pixel_cr, Rectangle(mCurrentPoint, mAnchorPoint));
-                pixel_cr.stroke;
-            } pixel_cr.restore;
-        }
-    }
-
-    private {
-        bool mActive;
-        Point mCurrentPoint;
-        Point mAnchorPoint;      // Pixel
-        static invariant double LINE_WIDTH = 1.0;
-    }
-}
--- a/dia/tool.d	Wed Jul 29 14:11:35 2009 +0930
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,49 +0,0 @@
-module dia.tool;
-
-public {
-    import cairo.Context;
-    import dia.icanvas;
-    import tk.events;
-}
-
-/*
-interface IToolStack {
-    void push(Tool tool);
-    void pop();
-    void replace(Tool tool);
-}
-*/
-
-abstract class Tool {
-    /*
-    enum Response {
-    START, CONTINUE, FINISH
-
-    }
-    */
-
-    /*
-    abstract bool is_sticky();
-    abstract bool is_replaceable();
-    */
-
-    /*
-    abstract void start(IToolStack tool_stack);
-    abstract void stop(IToolStack tool_stack);
-    */
-
-    bool handle_button_press(scope Viewport viewport, in ButtonEvent event) { return false; }
-    bool handle_button_release(scope Viewport viewport, in ButtonEvent event) { return false; }
-    bool handle_motion(scope Viewport viewport, in MotionEvent event) { return false; }
-    bool handle_scroll(scope Viewport viewport, in ScrollEvent event) { return false; }
-    //bool handle_enter(scope viewport, CrossingEvent event) { return false; }
-    //bool handle_leave(scope viewport, CrossingEvent event) { return false; }
-    //bool handle_focus_in(scope viewport, FocusEvent event) { return false; }
-    //bool handle_focus_out(scope viewport, FocusEvent event) { return false; }
-    bool handle_key_press(scope Viewport viewport, in KeyEvent event) { return false; }
-    bool handle_key_release(scope Viewport viewport, in KeyEvent event) { return false; }
-
-    void draw(in Viewport viewport,
-              in Rectangle pixel_damage, scope Context pixel_cr,
-              in Rectangle model_damage, scope Context model_cr) const { }
-}
--- a/dia/tool_layer.d	Wed Jul 29 14:11:35 2009 +0930
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,126 +0,0 @@
-module dia.tool_layer;
-
-private {
-    import std.stdio;
-    import cairo.routines;
-}
-
-public {
-    import dia.tool;
-}
-
-class ToolLayer : Layer, EventHandler {
-    this(in Tool[] tools, in string name) {
-        super(name);
-        mTools = tools.dup;
-    }
-
-    override bool handle_button_press(scope Viewport viewport, in ButtonEvent event) {
-        // writefln("%s", event);
-
-        if (mGrabbedTool is null) {
-            foreach_reverse(ref tool; mTools) {
-                if (tool.handle_button_press(viewport, event)) {
-                    mGrabbedTool = &tool;
-                    mGrabbedButton = event.button_name;
-                    break;
-                }
-            }
-        }
-        else {
-            mGrabbedTool.handle_button_press(viewport, event);
-        }
-
-        return true;
-    }
-
-    override bool handle_button_release(scope Viewport viewport, in ButtonEvent event) {
-        // writefln("%s", event);
-
-        if (mGrabbedTool !is null) {
-            mGrabbedTool.handle_button_release(viewport, event);
-
-            if (mGrabbedButton == event.button_name) {
-                mGrabbedTool = null;
-            }
-        }
-
-        return true;
-    }
-
-    override bool handle_key_press(scope Viewport viewport, in KeyEvent event) {
-        // writefln("%s", event);
-
-        return true;
-    }
-
-    override bool handle_key_release(scope Viewport viewport, in KeyEvent event) {
-        // writefln("%s", event);
-
-        return true;
-    }
-
-    override bool handle_motion(scope Viewport viewport, in MotionEvent event) {
-        //writefln("%s", event);
-
-        if (mGrabbedTool is null) {
-            foreach_reverse(ref tool; mTools) {
-                if (tool.handle_motion(viewport, event)) {
-                    break;
-                }
-            }
-        }
-        else {
-            mGrabbedTool.handle_motion(viewport, event);
-        }
-
-        return true;
-    }
-
-    override bool handle_scroll(scope Viewport viewport, in ScrollEvent event) {
-        // writefln("%s", event);
-
-        if (mGrabbedTool is null) {
-            foreach_reverse(ref tool; mTools) {
-                if (tool.handle_scroll(viewport, event)) {
-                    break;
-                }
-            }
-        }
-        else {
-            mGrabbedTool.handle_scroll(viewport, event);
-        }
-
-        return true;
-    }
-
-    override Rectangle bounds() const {
-        return Rectangle();
-    }
-
-    override void draw(const Viewport viewport,
-                       in Rectangle pixel_damage, scope Context pixel_cr,
-                       in Rectangle model_damage, scope Context model_cr) const {
-        // FIXME this isn't how we will really draw the tools...
-        foreach (const Tool tool; mTools) {
-            tool.draw(viewport, pixel_damage, pixel_cr, model_damage, model_cr);
-        }
-    }
-
-    /*
-    override void push(Tool tool) {
-    }
-
-    override void pop() {
-    }
-
-    override void replace(Tool tool) {
-    }
-    */
-
-    private {
-        Tool[] mTools;
-        Tool * mGrabbedTool;
-        ButtonName mGrabbedButton;
-    }
-}
--- a/doodle.d	Wed Jul 29 14:11:35 2009 +0930
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,53 +0,0 @@
-module doodle;
-
-private {
-    import dia.tool_layer;
-    import dia.standard_tools;
-    import dia.page_layer;
-
-    import gtk.canvas;
-    import gtk.toolbar;
-
-    import gtk.Main;
-    import gtk.MainWindow;
-    import gtk.VBox;
-
-    import std.stdio;
-}
-
-void main(string[] args) {
-    Main.init(args);
-    auto window = new MainWindow("Doodle");
-    auto vbox = new VBox(false, 0);
-    auto tool_bar = new ToolBar;
-    vbox.packStart(tool_bar, false, false, 0);
-    Tool[] tools;
-    tools ~= new PanTool;
-    tools ~= new ZoomTool;
-    tools ~= new LassoTool;
-    auto tool_layer = new ToolLayer(tools, "Tools");
-    Layer[] layers;
-    layers ~= new PageLayer("Page");
-    layers ~= tool_layer;
-    auto canvas = new Canvas(layers, tool_layer, 120.0);
-    vbox.packStart(canvas, true, true, 0);
-    window.add(vbox);
-    window.setDefaultSize(380, 380);
-    window.showAll();
-    Main.run();
-
-    /*
-    Point p3 = Point.DEFAULT;
-
-    Point p1 = Point(3.0, 5.0);
-    writefln("%s", p1);
-
-    Point p2 = Point(1.0, 2.0);
-    writefln("%s", p2);
-
-    writefln("%s", p1 - p2);
-
-    Rectangle r = Rectangle(p1, p2);
-    writefln("%s", r);
-    */
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doodle/cairo/routines.d	Sun Aug 02 16:27:21 2009 +0930
@@ -0,0 +1,11 @@
+module doodle.cairo.routines;
+
+public {
+    import doodle.tk.geometry;
+    import cairo.Context;
+}
+
+void rectangle(scope Context cr, in Rectangle rectangle) {
+    cr.rectangle(rectangle.position.x, rectangle.position.y,
+                 rectangle.size.x, rectangle.size.y);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doodle/common/undo.d	Sun Aug 02 16:27:21 2009 +0930
@@ -0,0 +1,15 @@
+module common.undo;
+
+abstract class Action {
+    void undo();
+    void redo();
+}
+
+interface IUndoObserver {
+    void canUndo(in bool value, in string description);
+    void canRedo(in bool value, in string description);
+}
+
+interface IUndoManager {
+    void addAction(Action action);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doodle/dia/grid_layer.d	Sun Aug 02 16:27:21 2009 +0930
@@ -0,0 +1,35 @@
+module doodle.dia.grid_layer;
+
+public {
+    import doodle.dia.icanvas;
+}
+
+private {
+    import doodle.cairo.routines;
+    import std.math;
+}
+
+interface Grid {
+}
+
+class GridLayer : Layer, Grid {
+    this(in string name) {
+        super(name);
+    }
+
+    override Rectangle bounds() const {
+        // We don't require any geometry
+        return Rectangle();
+    }
+
+    override void draw(in Viewport viewport,
+                       in Rectangle pixel_damage, scope Context pixel_cr,
+                       in Rectangle model_damage, scope Context model_cr) const {
+        //double zoom = viewport.zoom;
+
+        //double start_x = modf(damage.min_corner.x, zoom);
+    }
+
+    private {
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doodle/dia/icanvas.d	Sun Aug 02 16:27:21 2009 +0930
@@ -0,0 +1,66 @@
+module doodle.dia.icanvas;
+
+public {
+    import doodle.tk.geometry;
+    import doodle.tk.events;
+    import cairo.Context;
+}
+
+private {
+    import std.typecons;
+}
+
+mixin(defineEnum!("Cursor",
+                  "DEFAULT", "HAND", "CROSSHAIR"));
+
+interface Viewport {
+    void zoom_relative(in Point pixel_datum, in double factor);
+    void pan_relative(in Vector pixel_displacement);
+    void set_cursor(in Cursor cursor);
+    void damage_model(in Rectangle area);      // FIXME could be an inout parameter of the event handling, or a special scope Damage object that supports growth only
+    void damage_pixel(in Rectangle area);      // FIXME as above
+
+    /*
+    // FIXME not sure about these:
+    double zoom() const;
+    Point model_to_pixel(in Point model) const;
+    Point pixel_to_model(in Point pixel) const;
+    Vector model_to_pixel(in Vector model) const;
+    Vector pixel_to_model(in Vector pixel) const;
+    Rectangle model_to_pixel(in Rectangle model) const;
+    Rectangle pixel_to_model(in Rectangle model) const;
+    double model_to_pixel(in double model) const;
+    double pixel_to_model(in double pixel) const;
+    */
+}
+
+interface EventHandler {
+    bool handle_button_press(scope Viewport viewport, in ButtonEvent event);
+    bool handle_button_release(scope Viewport viewport, in ButtonEvent event);
+    bool handle_motion(scope Viewport viewport, in MotionEvent event);
+    bool handle_scroll(scope Viewport viewport, in ScrollEvent event);
+    //bool handle_enter(scope Viewport viewport, CrossingEvent event);
+    //bool handle_leave(scope Viewport viewport, CrossingEvent event);
+    //bool handle_focus_in(scope Viewport viewport, FocusEvent event);
+    //bool handle_focus_out(scope Viewport viewport, FocusEvent event);
+    bool handle_key_press(scope Viewport viewport, in KeyEvent event);
+    bool handle_key_release(scope Viewport viewport, in KeyEvent event);
+}
+
+abstract class Layer {
+    this(in string name) {
+        mName = name;
+    }
+
+    string name() const { return mName; }
+
+    Rectangle bounds() const;
+    //void zoom_changed
+    void draw(in Viewport viewport,
+              in Rectangle pixel_damage, scope Context pixel_cr,
+              in Rectangle model_damage, scope Context model_cr) const;
+
+    private {
+        invariant string mName;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doodle/dia/page_layer.d	Sun Aug 02 16:27:21 2009 +0930
@@ -0,0 +1,46 @@
+module doodle.dia.page_layer;
+
+private {
+    import doodle.cairo.routines;
+}
+
+public {
+    import doodle.dia.icanvas;
+}
+
+interface Page {
+}
+
+class PageLayer : Layer, Page {
+    this(in string name) {
+        super(name);
+        mPageGeometry = Rectangle(Point.DEFAULT, Vector(210.0, 297.0));
+        //mPageGeometry = Rectangle(Point.DEFAULT, Vector(100.0, 100.0));
+    }
+
+    override Rectangle bounds() const {
+        return mPageGeometry;
+    }
+
+    override void draw(in Viewport viewport,
+                       in Rectangle pixel_damage, scope Context pixel_cr,
+                       in Rectangle model_damage, scope Context model_cr) const {
+        // Make the paper white, with a border
+
+        model_cr.save; {
+            model_cr.setSourceRgba(1.0, 1.0, 1.0, 1.0);
+            rectangle(model_cr, mPageGeometry);
+            model_cr.fill;
+        } model_cr.restore;
+
+        model_cr.save; {
+            model_cr.setSourceRgba(0.0, 0.0, 0.0, 1.0);
+            rectangle(model_cr, mPageGeometry);
+            model_cr.stroke;
+        } model_cr.restore;
+    }
+
+    private {
+        Rectangle mPageGeometry;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doodle/dia/standard_tools.d	Sun Aug 02 16:27:21 2009 +0930
@@ -0,0 +1,155 @@
+module doodle.dia.standard_tools;
+
+public {
+    import doodle.dia.tool;
+}
+
+private {
+    import doodle.cairo.routines;
+    import std.math;
+    import std.stdio;
+}
+
+final class PanTool : Tool {
+    override bool handle_button_press(scope Viewport viewport, in ButtonEvent event) {
+        if (event.button_name == ButtonName.MIDDLE) {
+            mLastPosition = event.pixel_point;
+            return true;
+        }
+        else {
+            return false;
+        }
+    }
+
+    override bool handle_motion(scope Viewport viewport, in MotionEvent event) {
+        if (event.mask.is_set(Modifier.MIDDLE_BUTTON)) {
+            viewport.pan_relative(mLastPosition - event.pixel_point);
+            mLastPosition = event.pixel_point;
+
+            return true;
+        }
+        else {
+            return false;
+        }
+    }
+
+    override bool handle_scroll(scope Viewport viewport, in ScrollEvent event) {
+        if (event.mask.is_unset(Modifier.MIDDLE_BUTTON)) {
+            Vector delta;
+
+            switch (event.scroll_direction) {
+            case ScrollDirection.UP:
+                delta = event.mask.is_set(Modifier.SHIFT) ? Vector(AMOUNT, 0.0) : Vector(0.0, AMOUNT);
+                break;
+            case ScrollDirection.DOWN:
+                delta = event.mask.is_set(Modifier.SHIFT) ? Vector(-AMOUNT, 0.0) : Vector(0.0, -AMOUNT);
+                break;
+            case ScrollDirection.LEFT:
+                delta = Vector(-AMOUNT, 0.0);
+                break;
+            case ScrollDirection.RIGHT:
+                delta = Vector(AMOUNT, 0.0);
+                break;
+            }
+
+            viewport.pan_relative(delta);
+        }
+
+        return true;
+    }
+
+    private {
+        Point mLastPosition;
+        static invariant double AMOUNT = 60.0;
+    }
+}
+
+final class ZoomTool : Tool {
+    override bool handle_scroll(scope Viewport viewport, in ScrollEvent event) {
+        if (event.mask.is_set(Modifier.CONTROL)) {
+            if (event.scroll_direction == ScrollDirection.DOWN) {
+                viewport.zoom_relative(event.pixel_point, 1.0 / ZOOM);
+                return true;
+            }
+            else if (event.scroll_direction == ScrollDirection.UP) {
+                viewport.zoom_relative(event.pixel_point, ZOOM);
+                return true;
+            }
+            else {
+                return false;
+            }
+        }
+        else {
+            return false;
+        }
+    }
+
+    private {
+        static invariant double ZOOM = sqrt(2.0);
+    }
+}
+
+final class LassoTool : Tool {
+    override bool handle_button_press(scope Viewport viewport, in ButtonEvent event) {
+        if (event.button_name == ButtonName.LEFT) {
+            mActive = true;
+            mAnchorPoint = mCurrentPoint = event.pixel_point;
+            viewport.set_cursor(Cursor.HAND);
+            return true;
+        }
+        else {
+            return false;
+        }
+    }
+
+    override bool handle_button_release(scope Viewport viewport, in ButtonEvent event) {
+        if (event.button_name == ButtonName.LEFT && mActive) {
+            mActive = false;
+            viewport.damage_pixel(Rectangle(mAnchorPoint, mCurrentPoint).feathered(LINE_WIDTH / 2.0));
+            viewport.set_cursor(Cursor.DEFAULT);
+            return true;
+        }
+        else {
+            return false;
+        }
+    }
+
+    override bool handle_motion(scope Viewport viewport, in MotionEvent event) {
+        if (mActive) {
+            viewport.damage_pixel(Rectangle(mAnchorPoint, mCurrentPoint).feathered(LINE_WIDTH / 2.0));
+            mCurrentPoint = event.pixel_point;
+            viewport.damage_pixel(Rectangle(mAnchorPoint, mCurrentPoint).feathered(LINE_WIDTH / 2.0));
+        }
+
+        return false;
+    }
+
+    override void draw(in Viewport viewport,
+                       in Rectangle pixel_damage, scope Context pixel_cr,
+                       in Rectangle model_damage, scope Context model_cr) const {
+        if (mActive) {
+            pixel_cr.save; {
+                pixel_cr.setSourceRgba(0.0, 0.0, 0.8, 0.3);
+                rectangle(pixel_cr, Rectangle(mCurrentPoint, mAnchorPoint));
+                pixel_cr.fill();
+            } pixel_cr.restore();
+
+            pixel_cr.save(); {
+                //double[] dashes = [ 4.0, 4.0 ];
+                //pixel_cr.setDash(dashes, 0.0);
+                pixel_cr.setSourceRgba(0.0, 0.0, 0.5, 1.0);
+                pixel_cr.setLineWidth(LINE_WIDTH);
+                //writefln("Drawing rectangle: %s", Rectangle(mCurrentPoint, mAnchorPoint));
+                rectangle(pixel_cr, Rectangle(mCurrentPoint, mAnchorPoint));
+                pixel_cr.stroke;
+            } pixel_cr.restore;
+        }
+    }
+
+    private {
+        bool mActive;
+        Point mCurrentPoint;
+        Point mAnchorPoint;      // Pixel
+        static invariant double LINE_WIDTH = 1.0;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doodle/dia/tool.d	Sun Aug 02 16:27:21 2009 +0930
@@ -0,0 +1,49 @@
+module doodle.dia.tool;
+
+public {
+    import cairo.Context;
+    import doodle.dia.icanvas;
+    import doodle.tk.events;
+}
+
+/*
+interface IToolStack {
+    void push(Tool tool);
+    void pop();
+    void replace(Tool tool);
+}
+*/
+
+abstract class Tool {
+    /*
+    enum Response {
+    START, CONTINUE, FINISH
+
+    }
+    */
+
+    /*
+    abstract bool is_sticky();
+    abstract bool is_replaceable();
+    */
+
+    /*
+    abstract void start(IToolStack tool_stack);
+    abstract void stop(IToolStack tool_stack);
+    */
+
+    bool handle_button_press(scope Viewport viewport, in ButtonEvent event) { return false; }
+    bool handle_button_release(scope Viewport viewport, in ButtonEvent event) { return false; }
+    bool handle_motion(scope Viewport viewport, in MotionEvent event) { return false; }
+    bool handle_scroll(scope Viewport viewport, in ScrollEvent event) { return false; }
+    //bool handle_enter(scope viewport, CrossingEvent event) { return false; }
+    //bool handle_leave(scope viewport, CrossingEvent event) { return false; }
+    //bool handle_focus_in(scope viewport, FocusEvent event) { return false; }
+    //bool handle_focus_out(scope viewport, FocusEvent event) { return false; }
+    bool handle_key_press(scope Viewport viewport, in KeyEvent event) { return false; }
+    bool handle_key_release(scope Viewport viewport, in KeyEvent event) { return false; }
+
+    void draw(in Viewport viewport,
+              in Rectangle pixel_damage, scope Context pixel_cr,
+              in Rectangle model_damage, scope Context model_cr) const { }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doodle/dia/tool_layer.d	Sun Aug 02 16:27:21 2009 +0930
@@ -0,0 +1,126 @@
+module doodle.dia.tool_layer;
+
+private {
+    import std.stdio;
+    import doodle.cairo.routines;
+}
+
+public {
+    import doodle.dia.tool;
+}
+
+class ToolLayer : Layer, EventHandler {
+    this(in Tool[] tools, in string name) {
+        super(name);
+        mTools = tools.dup;
+    }
+
+    override bool handle_button_press(scope Viewport viewport, in ButtonEvent event) {
+        // writefln("%s", event);
+
+        if (mGrabbedTool is null) {
+            foreach_reverse(ref tool; mTools) {
+                if (tool.handle_button_press(viewport, event)) {
+                    mGrabbedTool = &tool;
+                    mGrabbedButton = event.button_name;
+                    break;
+                }
+            }
+        }
+        else {
+            mGrabbedTool.handle_button_press(viewport, event);
+        }
+
+        return true;
+    }
+
+    override bool handle_button_release(scope Viewport viewport, in ButtonEvent event) {
+        // writefln("%s", event);
+
+        if (mGrabbedTool !is null) {
+            mGrabbedTool.handle_button_release(viewport, event);
+
+            if (mGrabbedButton == event.button_name) {
+                mGrabbedTool = null;
+            }
+        }
+
+        return true;
+    }
+
+    override bool handle_key_press(scope Viewport viewport, in KeyEvent event) {
+        // writefln("%s", event);
+
+        return true;
+    }
+
+    override bool handle_key_release(scope Viewport viewport, in KeyEvent event) {
+        // writefln("%s", event);
+
+        return true;
+    }
+
+    override bool handle_motion(scope Viewport viewport, in MotionEvent event) {
+        //writefln("%s", event);
+
+        if (mGrabbedTool is null) {
+            foreach_reverse(ref tool; mTools) {
+                if (tool.handle_motion(viewport, event)) {
+                    break;
+                }
+            }
+        }
+        else {
+            mGrabbedTool.handle_motion(viewport, event);
+        }
+
+        return true;
+    }
+
+    override bool handle_scroll(scope Viewport viewport, in ScrollEvent event) {
+        // writefln("%s", event);
+
+        if (mGrabbedTool is null) {
+            foreach_reverse(ref tool; mTools) {
+                if (tool.handle_scroll(viewport, event)) {
+                    break;
+                }
+            }
+        }
+        else {
+            mGrabbedTool.handle_scroll(viewport, event);
+        }
+
+        return true;
+    }
+
+    override Rectangle bounds() const {
+        return Rectangle();
+    }
+
+    override void draw(const Viewport viewport,
+                       in Rectangle pixel_damage, scope Context pixel_cr,
+                       in Rectangle model_damage, scope Context model_cr) const {
+        // FIXME this isn't how we will really draw the tools...
+        foreach (const Tool tool; mTools) {
+            tool.draw(viewport, pixel_damage, pixel_cr, model_damage, model_cr);
+        }
+    }
+
+    /*
+    override void push(Tool tool) {
+    }
+
+    override void pop() {
+    }
+
+    override void replace(Tool tool) {
+    }
+    */
+
+    private {
+        Tool[] mTools;
+        Tool * mGrabbedTool;
+        ButtonName mGrabbedButton;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doodle/fig/fig.d	Sun Aug 02 16:27:21 2009 +0930
@@ -0,0 +1,27 @@
+module doodle.fig.fig;
+
+//abstract class Fig {
+//    Rectangle bounds() const;
+//    void draw(in Rectangle damage, scope Context cr) const;
+//
+//    private {
+//    }
+//}
+//
+//abstract class FigElement : Fig {
+//}
+
+//class Connector {
+//}
+
+//class FigNode : FigElement {
+//}
+
+//class FigEdge : FigElement {
+//    private {
+//        FigElement
+//    }
+//}
+
+//abstract class FigLeaf : Fig {
+//}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doodle/fig/fig_layer.d	Sun Aug 02 16:27:21 2009 +0930
@@ -0,0 +1,24 @@
+module doodle.fig.fig_layer;
+
+public {
+    import doodle.dia.icanvas;
+}
+
+class FigLayer : Layer {
+    this(in string name) {
+        super(name);
+    }
+
+    override Rectangle bounds() const {
+        return Rectangle.DEFAULT;
+    }
+
+    override void draw(in Viewport viewport,
+                       in Rectangle pixel_damage, scope Context pixel_cr,
+                       in Rectangle model_damage, scope Context model_cr) const {
+    }
+
+    private {
+        //Fig[] mFigs;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doodle/fig/selection_layer.d	Sun Aug 02 16:27:21 2009 +0930
@@ -0,0 +1,22 @@
+module fig.selection_layer;
+
+/*
+public {
+    import dia.icanvas;
+}
+
+class SelectionLayer : Layer {
+    this(in string name) {
+        super(name);
+    }
+
+    private {
+        // Selector[] mSelectors;
+    }
+}
+
+abstract class Selector {
+    private {
+    }
+}
+*/
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doodle/gtk/canvas.d	Sun Aug 02 16:27:21 2009 +0930
@@ -0,0 +1,550 @@
+module doodle.gtk.canvas;
+
+public {
+    import doodle.dia.icanvas;
+    import doodle.tk.events;
+}
+
+private {
+    import doodle.gtk.conversions;
+    import doodle.tk.misc;
+    import doodle.cairo.routines;
+
+    import cairo.Surface;
+
+    import std.math;
+    import std.stdio;
+
+    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 gdk.Drawable;
+
+    import gtkc.gtk;
+}
+
+// x and y run right and up respectively
+
+class Canvas : Table, Viewport {
+    this(in Layer[] layers, EventHandler event_handler, in double ppi) {
+        super(3, 3, 0);
+
+        mDamage = Rectangle.DEFAULT;
+
+        mLayers = layers.dup;
+        mEventHandler = event_handler;
+        mPPI = ppi;
+
+        /*
+        writefln("Layer bounds: %s", layer_bounds);
+        writefln("Canvas bounds: %s", mCanvasBounds);
+        writefln("View centre: %s", mViewCentre);
+        */
+
+        // Create our child widgets and register callbacks
+
+        mHRuler = new HRuler;
+        attach(mHRuler,
+               1, 2,
+               0, 1,
+               AttachOptions.FILL | AttachOptions.EXPAND, AttachOptions.SHRINK,
+               0, 0);
+        mHRuler.setMetric(MetricType.PIXELS);
+
+        mVRuler = new VRuler;
+        attach(mVRuler,
+               0, 1,
+               1, 2,
+               AttachOptions.SHRINK, AttachOptions.FILL | AttachOptions.EXPAND,
+               0, 0);
+        mVRuler.setMetric(MetricType.PIXELS);
+
+        mDrawingArea = new DrawingArea;
+        mDrawingArea.addOnRealize(&on_realize);
+        mDrawingArea.addOnConfigure(&on_configure);
+        mDrawingArea.addOnExpose(&on_expose);
+        mDrawingArea.addOnButtonPress(&on_button_press);
+        mDrawingArea.addOnButtonRelease(&on_button_release);
+        mDrawingArea.addOnKeyPress(&on_key_event);
+        mDrawingArea.addOnKeyRelease(&on_key_event);
+        mDrawingArea.addOnMotionNotify(&on_motion_notify);
+        mDrawingArea.addOnScroll(&on_scroll);
+        mDrawingArea.addOnEnterNotify(&on_enter_notify);
+        mDrawingArea.addOnLeaveNotify(&on_leave_notify);
+        mDrawingArea.setEvents(EventMask.EXPOSURE_MASK |
+                               EventMask.POINTER_MOTION_MASK |
+                               EventMask.POINTER_MOTION_HINT_MASK |
+                               EventMask.BUTTON_MOTION_MASK |
+                               EventMask.BUTTON_PRESS_MASK |
+                               EventMask.BUTTON_RELEASE_MASK |
+                               EventMask.KEY_PRESS_MASK |
+                               EventMask.KEY_RELEASE_MASK |
+                               EventMask.ENTER_NOTIFY_MASK |
+                               EventMask.LEAVE_NOTIFY_MASK |
+                               EventMask.FOCUS_CHANGE_MASK |
+                               EventMask.SCROLL_MASK);
+
+        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, 0.5);
+        mHAdjustment.addOnValueChanged(&onValueChanged);
+        mHScrollbar = new HScrollbar(mHAdjustment);
+        mHScrollbar.setInverted(false);
+        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.5);
+        mVAdjustment.addOnValueChanged(&onValueChanged);
+        mVScrollbar = new VScrollbar(mVAdjustment);
+        mVScrollbar.setInverted(true);
+        attach(mVScrollbar,
+               2, 3,
+               1, 2,
+               AttachOptions.SHRINK,
+               AttachOptions.FILL | AttachOptions.EXPAND,
+               0, 0);
+    }
+
+    override void zoom_relative(in Point pixel_datum, in 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 = pixel_to_model(pixel_datum);
+        Vector pixel_distance = model_to_pixel(old_model_datum - mViewCentre);
+        mZoom = clamp_zoom(factor * mZoom);
+        mViewCentre = old_model_datum - pixel_to_model(pixel_distance);
+
+        update_adjustments;
+        update_rulers;
+        queueDraw;
+    }
+
+    override void pan_relative(in Vector pixel_displacement) {
+        mViewCentre = mViewCentre + pixel_to_model(pixel_displacement);
+
+        update_adjustments;
+        update_rulers;
+        queueDraw;
+    }
+
+    override void set_cursor(in Cursor cursor) {
+        CursorType cursor_type;
+
+        switch (cursor) {
+        case Cursor.DEFAULT:
+            cursor_type = CursorType.ARROW;
+            break;
+        case Cursor.HAND:
+            cursor_type = CursorType.HAND1;
+            break;
+        case Cursor.CROSSHAIR:
+            cursor_type = CursorType.CROSSHAIR;
+            break;
+        }
+
+        mDrawingArea.setCursor(new gdk.Cursor.Cursor(cursor_type));
+    }
+
+    override void damage_model(in Rectangle area) {
+        mDamage = mDamage | model_to_pixel(area);
+    }
+
+    override void damage_pixel(in Rectangle area) {
+        mDamage = mDamage | area;
+    }
+
+    private {
+
+        bool on_configure(GdkEventConfigure * event, Widget widget) {
+            assert(widget is mDrawingArea);
+
+            if (!mHadConfigure) {
+                const double MM_PER_INCH = 25.4;
+                mZoom = 0.25 * mPPI / MM_PER_INCH;
+
+                // Take the union of the bounds of each layer to
+                // determine the canvas size
+
+                Rectangle layer_bounds = Rectangle.DEFAULT;
+
+                foreach (ref layer; mLayers) {
+                    layer_bounds = layer_bounds | layer.bounds;
+                }
+
+                assert(layer_bounds.valid);
+
+                mCanvasBounds = layer_bounds.moved(-layer_bounds.size).expanded(2.0 * layer_bounds.size);
+                mViewCentre = mCanvasBounds.centre;
+
+                mHadConfigure = true;
+            }
+
+            mViewSize = Vector(cast(double)event.width, cast(double)event.height);
+            update_adjustments;
+            update_rulers;
+
+            //writefln("Canvas bounds: %s", mCanvasBounds);
+            //writefln("View centre: %s", mViewCentre);
+
+            return true;
+        }
+
+        bool on_expose(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 model_cr = new Context(dr);
+            scope pixel_cr = new Context(dr);
+
+            Rectangle pixel_damage =
+                event is null ?
+                Rectangle(Point(0.0, 0.0), mViewSize) :
+                Rectangle(Point(cast(double)event.area.x, mViewSize.y - cast(double)(event.area.y + event.area.height)),
+                          Vector(cast(double)event.area.width, cast(double)event.area.height));
+
+            Rectangle model_damage = pixel_to_model(pixel_damage);
+
+            //writefln("Pixel damage: %s, model damage: %s", pixel_damage, model_damage);
+
+            model_cr.save; pixel_cr.save; {
+                // Setup model context and clip
+
+                GtkAdjustment * h_gtkAdjustment = mHAdjustment.getAdjustmentStruct;
+                GtkAdjustment * v_gtkAdjustment = mVAdjustment.getAdjustmentStruct;
+
+                model_cr.scale(mZoom, -mZoom);
+                model_cr.translate(-gtk_adjustment_get_value(h_gtkAdjustment),
+                                   -gtk_adjustment_get_value(v_gtkAdjustment) - gtk_adjustment_get_page_size(v_gtkAdjustment));
+
+                rectangle(model_cr, model_damage);
+                model_cr.clip;
+
+                // Setup pixel context and clip
+
+                pixel_cr.translate(0.0, mViewSize.y);
+                pixel_cr.scale(1.0, -1.0);
+
+                rectangle(pixel_cr, pixel_damage);
+                pixel_cr.clip;
+
+                // Fill the background
+
+                pixel_cr.save; {
+                    // Make the window light grey
+                    pixel_cr.setSourceRgba(0.6, 0.6, 0.6, 1.0);
+                    rectangle(pixel_cr, pixel_damage);
+                    pixel_cr.fill;
+                } pixel_cr.restore;
+
+                // Draw each layer
+
+                foreach(ref layer; mLayers) {
+                    model_cr.save; pixel_cr.save; {
+                        layer.draw(this, pixel_damage, pixel_cr, model_damage, model_cr);
+                    } pixel_cr.restore; model_cr.restore;
+                }
+            } pixel_cr.restore; model_cr.restore;
+
+            return true;
+        }
+
+        bool on_button_press(GdkEventButton * event, Widget widget) {
+            assert(widget is mDrawingArea);
+            //writefln("Got button event\n");
+
+            Point pixel_point = Point(event.x + 0.5, mViewSize.y - (event.y + 0.5));
+            Point model_point = pixel_to_model(pixel_point);
+
+            auto button_event = new ButtonEvent(gtk2tk_button_action(event.type),
+                                                gtk2tk_button_name(event.button),
+                                                pixel_point,
+                                                model_point,
+                                                gtk2tk_mask(event.state));
+
+            mEventHandler.handle_button_press(this, button_event);
+
+            process_damage;
+
+            return true;
+        }
+
+        bool on_button_release(GdkEventButton * event, Widget widget) {
+            assert(widget is mDrawingArea);
+            //writefln("Got button event\n");
+
+            Point pixel_point = Point(event.x + 0.5, mViewSize.y - (event.y + 0.5));
+            Point model_point = pixel_to_model(pixel_point);
+
+            auto button_event = new ButtonEvent(gtk2tk_button_action(event.type),
+                                                gtk2tk_button_name(event.button),
+                                                pixel_point,
+                                                model_point,
+                                                gtk2tk_mask(event.state));
+
+            mEventHandler.handle_button_release(this, button_event);
+
+            process_damage;
+
+            return true;
+        }
+
+        bool on_key_event(GdkEventKey * event, Widget widget) {
+            assert(widget is mDrawingArea);
+            //writefln("Got key event\n");
+
+            //auto key_event = new KeyEvent("",
+            // mEventHandle.handle_key(key_event);
+
+            process_damage;
+
+            return true;
+        }
+
+        bool on_motion_notify(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 pixel_point = Point(event.x + 0.5, mViewSize.y - (event.y + 0.5));
+            Point model_point = pixel_to_model(pixel_point);
+
+            auto motion_event = new MotionEvent(pixel_point,
+                                                model_point,
+                                                gtk2tk_mask(event.state));
+
+            mEventHandler.handle_motion(this, motion_event);
+
+            process_damage;
+
+            return true;
+        }
+
+        bool on_scroll(GdkEventScroll * event, Widget widget) {
+            assert(widget is mDrawingArea);
+            //writefln("Got scroll\n");
+
+            Point pixel_point = Point(event.x + 0.5, mViewSize.y - (event.y + 0.5));
+            Point model_point = pixel_to_model(pixel_point);
+
+            auto scroll_event = new ScrollEvent(gtk2tk_direction(event.direction),
+                                                pixel_point,
+                                                model_point,
+                                                gtk2tk_mask(event.state));
+
+            mEventHandler.handle_scroll(this, scroll_event);
+
+            process_damage;
+
+            return true;
+        }
+
+        /*
+        public enum GdkCrossingMode {       
+            NORMAL,
+            GRAB,
+            UNGRAB,
+            GTK_GRAB,
+            GTK_UNGRAB,
+            STATE_CHANGED
+        }
+
+        public struct GdkEventCrossing {
+            GdkEventType type;
+            GdkWindow *window;
+            byte sendEvent;
+            GdkWindow *subwindow;
+            uint time;
+            double x;
+            double y;
+            double xRoot;
+            double yRoot;
+            GdkCrossingMode mode;
+            GdkNotifyType detail;
+            int focus;
+            uint state;
+        }
+        */
+
+        bool on_enter_notify(GdkEventCrossing * event, Widget widget) {
+            assert(widget is mDrawingArea);
+            //writefln("Enter %d %d %d", cast(int)event.mode, event.focus, event.state);
+            // TODO
+            return true;
+        }
+
+        bool on_leave_notify(GdkEventCrossing * event, Widget widget) {
+            assert(widget is mDrawingArea);
+            //writefln("Leave %d %d %d", cast(int)event.mode, event.focus, event.state);
+            // TODO
+            return true;
+        }
+
+        void onValueChanged(Adjustment adjustment) {
+            GtkAdjustment * h_gtkAdjustment = mHAdjustment.getAdjustmentStruct;
+            GtkAdjustment * v_gtkAdjustment = mVAdjustment.getAdjustmentStruct;
+
+            Point view_left_top = Point(gtk_adjustment_get_value(h_gtkAdjustment),
+                                        gtk_adjustment_get_value(v_gtkAdjustment));
+
+            Vector model_size = pixel_to_model(mViewSize);
+
+            //writefln("%s", view_left_bottom);
+            mViewCentre = view_left_top + model_size / 2.0;
+            //writefln("onValueChanged mViewCentre: %s", mViewCentre);
+
+            update_rulers;
+
+            queueDraw;
+        }
+
+        void update_rulers() {
+            invariant Vector model_size = pixel_to_model(mViewSize);
+
+            invariant Point view_left_bottom = mViewCentre - model_size / 2.0;
+            invariant Point view_right_top = mViewCentre + model_size / 2.0;
+
+            // Define these just to obtain the position
+            // below and we can preserve it
+            double lower, upper, position, max_size;
+
+            mHRuler.getRange(lower, upper, position, max_size);
+            mHRuler.setRange(view_left_bottom.x,
+                             view_right_top.x,
+                             position,
+                             mZoom * 50.0);
+
+            mVRuler.getRange(lower, upper, position, max_size);
+            mVRuler.setRange(view_right_top.y,
+                             view_left_bottom.y,
+                             position,
+                             mZoom * 50.0);
+        }
+
+        void update_adjustments() {
+            invariant Vector model_size = pixel_to_model(mViewSize);
+
+            invariant Point view_left_bottom = mViewCentre - model_size / 2.0;
+            invariant Point view_right_top = mViewCentre + model_size / 2.0;
+
+            // Adjust the canvas size if necessary
+            mCanvasBounds = Rectangle(min_extents(mCanvasBounds.min_corner, view_left_bottom),
+                                      max_extents(mCanvasBounds.max_corner, view_right_top));
+
+            // Update the adjustments
+
+            GtkAdjustment * h_gtkAdjustment = mHAdjustment.getAdjustmentStruct;
+            GtkAdjustment * v_gtkAdjustment = mVAdjustment.getAdjustmentStruct;
+
+            gtk_adjustment_set_lower(h_gtkAdjustment, mCanvasBounds.min_corner.x);
+            gtk_adjustment_set_upper(h_gtkAdjustment, mCanvasBounds.max_corner.x);
+            gtk_adjustment_set_value(h_gtkAdjustment, view_left_bottom.x);
+            gtk_adjustment_set_step_increment(h_gtkAdjustment, mCanvasBounds.size.x / 16.0);
+            gtk_adjustment_set_page_increment(h_gtkAdjustment, mCanvasBounds.size.x / 4.0);
+            gtk_adjustment_set_page_size(h_gtkAdjustment, model_size.x);
+
+            gtk_adjustment_set_lower(v_gtkAdjustment, mCanvasBounds.min_corner.y);
+            gtk_adjustment_set_upper(v_gtkAdjustment, mCanvasBounds.max_corner.y);
+            gtk_adjustment_set_value(v_gtkAdjustment, view_left_bottom.y);
+            gtk_adjustment_set_step_increment(v_gtkAdjustment, mCanvasBounds.size.y / 16.0);
+            gtk_adjustment_set_page_increment(v_gtkAdjustment, mCanvasBounds.size.y / 4.0);
+            gtk_adjustment_set_page_size(v_gtkAdjustment, model_size.y);
+
+            mHAdjustment.changed;
+            mHAdjustment.valueChanged;
+            mVAdjustment.changed;
+            mVAdjustment.valueChanged;
+        }
+
+        void process_damage() {
+            if (mDamage.valid) {
+                //writefln("Damage: %s", mDamage);
+                int x, y, w, h;
+                mDamage.get_quantised(x, y, w, h);
+                mDrawingArea.queueDrawArea(x, cast(int)mViewSize.y - (y + h), w, h);
+                mDamage = Rectangle.DEFAULT;
+            }
+            else {
+                //writefln("No damage");
+            }
+        }
+
+        double clamp_zoom(in double zoom) { return clamp(zoom, 0.2, 10.0); }
+
+        Point model_to_pixel(in Point model) const {
+            return Point.DEFAULT + mViewSize / 2.0 + mZoom * (model - mViewCentre);
+        }
+
+        Point pixel_to_model(in Point pixel) const {
+            return mViewCentre + (pixel - mViewSize / 2.0 - Point.DEFAULT) / mZoom;
+        }
+
+        Vector model_to_pixel(in Vector model) const {
+            return mZoom * model;
+        }
+
+        Vector pixel_to_model(in Vector pixel) const {
+            return pixel / mZoom;
+        }
+
+        Rectangle model_to_pixel(in Rectangle model) const {
+            return Rectangle(model_to_pixel(model.position), model_to_pixel(model.size));
+        }
+
+        Rectangle pixel_to_model(in Rectangle model) const {
+            return Rectangle(pixel_to_model(model.position), pixel_to_model(model.size));
+        }
+
+        void on_realize(Widget widget) {
+            assert(widget is mDrawingArea);
+            //writefln("Got realize\n");
+        }
+
+        bool mHadConfigure;
+        Rectangle mDamage;          // pixels
+
+        // Model units are in millimetres
+        // Screen units are in pixels
+        double mZoom;               // pixels-per-model-unit (mm)
+        Vector mViewSize;           // pixel: size of view window in pixels
+        Point mViewCentre;          // model: where in the model is the centre of our view
+        Rectangle mCanvasBounds;    // model:
+
+        // Child widgets:
+        HRuler mHRuler;
+        VRuler mVRuler;
+        DrawingArea mDrawingArea;
+        Adjustment mHAdjustment;
+        HScrollbar mHScrollbar;
+        Adjustment mVAdjustment;
+        VScrollbar mVScrollbar;
+
+        // Layers:
+        Layer[] mLayers;
+        EventHandler mEventHandler;
+        double mPPI;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doodle/gtk/conversions.d	Sun Aug 02 16:27:21 2009 +0930
@@ -0,0 +1,74 @@
+module doodle.gtk.conversions;
+
+public {
+    import doodle.tk.types;
+}
+
+private {
+    static import gdk.Event;
+}
+
+ButtonAction gtk2tk_button_action(gdk.Event.EventType event_type) {
+    switch (event_type) {
+    case gdk.Event.EventType.BUTTON_PRESS:
+        return ButtonAction.SINGLE_PRESS;
+    case gdk.Event.EventType.DOUBLE_BUTTON_PRESS:
+        return ButtonAction.DOUBLE_PRESS;
+    case gdk.Event.EventType.TRIPLE_BUTTON_PRESS:
+        return ButtonAction.TRIPLE_PRESS;
+    case gdk.Event.EventType.BUTTON_RELEASE:
+        return ButtonAction.RELEASE;
+    default:
+        assert(false);
+    }
+}
+
+ButtonName gtk2tk_button_name(gdk.Event.guint button) {
+    switch (button) {
+    case 1:
+        return ButtonName.LEFT;
+    case 2:
+        return ButtonName.MIDDLE;
+    case 3:
+        return ButtonName.RIGHT;
+    case 4:
+        return ButtonName.FOUR;
+    case 5:
+        return ButtonName.FIVE;
+    default:
+        assert(false);
+    }
+}
+
+Mask gtk2tk_mask(gdk.Event.guint state) {
+    Modifier[] modifiers;
+
+    if (state & gdk.Event.GdkModifierType.SHIFT_MASK)   modifiers ~= Modifier.SHIFT;
+    if (state & gdk.Event.GdkModifierType.CONTROL_MASK) modifiers ~= Modifier.CONTROL;
+    if (state & gdk.Event.GdkModifierType.MOD1_MASK)    modifiers ~= Modifier.ALT;
+    if (state & gdk.Event.GdkModifierType.MOD2_MASK)    modifiers ~= Modifier.META;
+    if (state & gdk.Event.GdkModifierType.BUTTON1_MASK) modifiers ~= Modifier.LEFT_BUTTON;
+    if (state & gdk.Event.GdkModifierType.BUTTON2_MASK) modifiers ~= Modifier.MIDDLE_BUTTON;
+    if (state & gdk.Event.GdkModifierType.BUTTON3_MASK) modifiers ~= Modifier.RIGHT_BUTTON;
+    if (state & gdk.Event.GdkModifierType.BUTTON4_MASK) modifiers ~= Modifier.UNUSED_BUTTON_1;
+    if (state & gdk.Event.GdkModifierType.BUTTON5_MASK) modifiers ~= Modifier.UNUSED_BUTTON_2;
+
+    Mask m = Mask(modifiers);
+
+    return Mask(modifiers);
+}
+
+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/doodle/gtk/toolbar.d	Sun Aug 02 16:27:21 2009 +0930
@@ -0,0 +1,63 @@
+module gtk.toolbar;
+
+public {
+    import gtk.Toolbar;
+}
+
+private {
+    import gtk.ToolButton;
+    import gtk.SeparatorToolItem;
+    import gtk.RadioToolButton;
+    import gtk.Image;
+
+    import glib.ListSG;
+
+    import std.stdio;
+}
+
+class ToolBar : Toolbar {
+    this() {
+        // INVALID, MENU, SMALL_TOOLBAR, LARGE_TOOLBAR,
+        // BUTTON, DND, DIALOG
+        setIconSize(GtkIconSize.LARGE_TOOLBAR);
+	// ICONS, TEXT, BOTH, BOTH_HORIZ
+        setStyle(GtkToolbarStyle.ICONS);
+	// HORIZONTAL, VERTICAL
+        setOrientation(GtkOrientation.HORIZONTAL);
+        setTooltips(true);
+
+        Image image;
+        ListSG group;
+
+        image = new Image("icons/select.svg");
+        button1 = new RadioToolButton(group);
+        button1.setLabelWidget(image);
+        insert(button1);
+
+        image = new Image("icons/select.png");
+        button2 = new RadioToolButton(group);
+        button2.setGroup(button1.getGroup);
+        button2.setLabelWidget(image);
+        button2.addOnClicked(&on_clicked);
+        insert(button2);
+
+        insert(new SeparatorToolItem);
+
+        image = new Image("icons/select.png");
+        button3 = new RadioToolButton(group);
+        button3.setGroup(button1.getGroup);
+        button3.setLabelWidget(image);
+        insert(button3);
+    }
+
+    private {
+
+        void on_clicked(ToolButton tool_button) {
+            writefln("Clicked!");
+        }
+
+        RadioToolButton button1;
+        RadioToolButton button2;
+        RadioToolButton button3;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doodle/gtk/zoom_controls.d	Sun Aug 02 16:27:21 2009 +0930
@@ -0,0 +1,20 @@
+module doodle.gtk.zoom_controls;
+
+public {
+    import gtk.HBox;
+}
+
+private {
+}
+
+// Useful actions
+// Zoom to fit contents, Zoom 1:1, zoom in/out (coarse/fine)
+// Manually specified zoom?
+
+//class ZoomControls : HBox {
+//    this() {
+//    }
+//
+//    private {
+//    }
+//}
Binary file doodle/icons/select.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doodle/icons/select.svg	Sun Aug 02 16:27:21 2009 +0930
@@ -0,0 +1,148 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="32px"
+   height="32px"
+   id="svg2383"
+   sodipodi:version="0.32"
+   inkscape:version="0.46"
+   sodipodi:docname="select.svg"
+   inkscape:output_extension="org.inkscape.output.svg.inkscape"
+   inkscape:export-filename="/home/daveb/select.png"
+   inkscape:export-xdpi="90"
+   inkscape:export-ydpi="90">
+  <defs
+     id="defs2385">
+    <linearGradient
+       inkscape:collect="always"
+       id="linearGradient3262">
+      <stop
+         style="stop-color:#6bfff4;stop-opacity:1;"
+         offset="0"
+         id="stop3264" />
+      <stop
+         style="stop-color:#6bfff4;stop-opacity:0;"
+         offset="1"
+         id="stop3266" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient3254">
+      <stop
+         style="stop-color:#6bfff4;stop-opacity:1;"
+         offset="0"
+         id="stop3256" />
+      <stop
+         style="stop-color:#6bfff4;stop-opacity:0;"
+         offset="1"
+         id="stop3258" />
+    </linearGradient>
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 16 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="32 : 16 : 1"
+       inkscape:persp3d-origin="16 : 10.666667 : 1"
+       id="perspective2391" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3262"
+       id="linearGradient3268"
+       x1="1.1039745"
+       y1="15.925417"
+       x2="30.955889"
+       y2="15.925417"
+       gradientUnits="userSpaceOnUse" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="7.9180417"
+     inkscape:cx="12.16509"
+     inkscape:cy="15.571721"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     inkscape:grid-bbox="true"
+     inkscape:document-units="px"
+     inkscape:window-width="637"
+     inkscape:window-height="724"
+     inkscape:window-x="36"
+     inkscape:window-y="6" />
+  <metadata
+     id="metadata2388">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     id="layer1"
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer">
+    <path
+       style="fill:url(#linearGradient3268);fill-rule:evenodd;stroke:#000000;stroke-width:2.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill-opacity:1"
+       d="M 2.0539745,26.552502 L 16.34884,11.891449 L 4.8466724,8.9175157 L 30.005889,1.7262016 L 22.047836,26.072714 L 19.594399,14.653503 L 5.7154073,30.124632 L 2.0539745,26.552502 z"
+       id="path2395"
+       sodipodi:nodetypes="cccccccc" />
+    <path
+       sodipodi:nodetypes="cccccccc"
+       id="path3274"
+       d="M 2.0539745,26.552502 L 16.34884,11.891449 L 4.8466724,8.9175157 L 30.005889,1.7262016 L 22.047836,26.072714 L 19.594399,14.653503 L 5.7154073,30.124632 L 2.0539745,26.552502 z"
+       style="fill:url(#linearGradient3268);fill-rule:evenodd;stroke:#000000;stroke-width:2.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill-opacity:1" />
+    <path
+       style="fill:url(#linearGradient3268);fill-rule:evenodd;stroke:#000000;stroke-width:2.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill-opacity:1"
+       d="M 2.0539745,26.552502 L 16.34884,11.891449 L 4.8466724,8.9175157 L 30.005889,1.7262016 L 22.047836,26.072714 L 19.594399,14.653503 L 5.7154073,30.124632 L 2.0539745,26.552502 z"
+       id="path3276"
+       sodipodi:nodetypes="cccccccc" />
+    <path
+       sodipodi:nodetypes="cccccccc"
+       id="path3278"
+       d="M 2.0539745,26.552502 L 16.34884,11.891449 L 4.8466724,8.9175157 L 30.005889,1.7262016 L 22.047836,26.072714 L 19.594399,14.653503 L 5.7154073,30.124632 L 2.0539745,26.552502 z"
+       style="fill:url(#linearGradient3268);fill-rule:evenodd;stroke:#000000;stroke-width:2.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill-opacity:1" />
+    <path
+       style="fill:url(#linearGradient3268);fill-rule:evenodd;stroke:#000000;stroke-width:2.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill-opacity:1"
+       d="M 2.0539745,26.552502 L 16.34884,11.891449 L 4.8466724,8.9175157 L 30.005889,1.7262016 L 22.047836,26.072714 L 19.594399,14.653503 L 5.7154073,30.124632 L 2.0539745,26.552502 z"
+       id="path3280"
+       sodipodi:nodetypes="cccccccc" />
+    <path
+       sodipodi:nodetypes="cccccccc"
+       id="path3282"
+       d="M 2.0539745,26.552502 L 16.34884,11.891449 L 4.8466724,8.9175157 L 30.005889,1.7262016 L 22.047836,26.072714 L 19.594399,14.653503 L 5.7154073,30.124632 L 2.0539745,26.552502 z"
+       style="fill:url(#linearGradient3268);fill-rule:evenodd;stroke:#000000;stroke-width:2.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill-opacity:1" />
+    <path
+       style="fill:url(#linearGradient3268);fill-rule:evenodd;stroke:#000000;stroke-width:2.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill-opacity:1"
+       d="M 2.0539745,26.552502 L 16.34884,11.891449 L 4.8466724,8.9175157 L 30.005889,1.7262016 L 22.047836,26.072714 L 19.594399,14.653503 L 5.7154073,30.124632 L 2.0539745,26.552502 z"
+       id="path3284"
+       sodipodi:nodetypes="cccccccc" />
+    <path
+       sodipodi:nodetypes="cccccccc"
+       id="path3286"
+       d="M 2.0539745,26.552502 L 16.34884,11.891449 L 4.8466724,8.9175157 L 30.005889,1.7262016 L 22.047836,26.072714 L 19.594399,14.653503 L 5.7154073,30.124632 L 2.0539745,26.552502 z"
+       style="fill:url(#linearGradient3268);fill-rule:evenodd;stroke:#000000;stroke-width:2.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill-opacity:1" />
+    <path
+       style="fill:url(#linearGradient3268);fill-rule:evenodd;stroke:#000000;stroke-width:2.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill-opacity:1"
+       d="M 2.0539745,26.552502 L 16.34884,11.891449 L 4.8466724,8.9175157 L 30.005889,1.7262016 L 22.047836,26.072714 L 19.594399,14.653503 L 5.7154073,30.124632 L 2.0539745,26.552502 z"
+       id="path3288"
+       sodipodi:nodetypes="cccccccc" />
+    <path
+       sodipodi:nodetypes="cccccccc"
+       id="path3290"
+       d="M 2.0539745,26.552502 L 16.34884,11.891449 L 4.8466724,8.9175157 L 30.005889,1.7262016 L 22.047836,26.072714 L 19.594399,14.653503 L 5.7154073,30.124632 L 2.0539745,26.552502 z"
+       style="fill:url(#linearGradient3268);fill-rule:evenodd;stroke:#000000;stroke-width:2.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill-opacity:1" />
+  </g>
+</svg>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doodle/import/model.d	Sun Aug 02 16:27:21 2009 +0930
@@ -0,0 +1,55 @@
+module doodle.interaction.model;
+
+
+final class Manipulator {
+}
+
+abstract class Selector {
+    this(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/doodle/import/network.d	Sun Aug 02 16:27:21 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/doodle/import/new.d	Sun Aug 02 16:27:21 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/doodle/import/p-model.d	Sun Aug 02 16:27:21 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/doodle/import/types.d	Sun Aug 02 16:27:21 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/doodle/main/prog/doodler.d	Sun Aug 02 16:27:21 2009 +0930
@@ -0,0 +1,51 @@
+private {
+    import doodle.dia.tool_layer;
+    import doodle.dia.standard_tools;
+    import doodle.dia.page_layer;
+
+    import doodle.gtk.canvas;
+    import doodle.gtk.toolbar;
+
+    import gtk.Main;
+    import gtk.MainWindow;
+    import gtk.VBox;
+
+    import std.stdio;
+}
+
+void main(string[] args) {
+    Main.init(args);
+    auto window = new MainWindow("Doodle");
+    auto vbox = new VBox(false, 0);
+    auto tool_bar = new ToolBar;
+    vbox.packStart(tool_bar, false, false, 0);
+    Tool[] tools;
+    tools ~= new PanTool;
+    tools ~= new ZoomTool;
+    tools ~= new LassoTool;
+    auto tool_layer = new ToolLayer(tools, "Tools");
+    Layer[] layers;
+    layers ~= new PageLayer("Page");
+    layers ~= tool_layer;
+    auto canvas = new Canvas(layers, tool_layer, 120.0);
+    vbox.packStart(canvas, true, true, 0);
+    window.add(vbox);
+    window.setDefaultSize(380, 380);
+    window.showAll();
+    Main.run();
+
+    /*
+    Point p3 = Point.DEFAULT;
+
+    Point p1 = Point(3.0, 5.0);
+    writefln("%s", p1);
+
+    Point p2 = Point(1.0, 2.0);
+    writefln("%s", p2);
+
+    writefln("%s", p1 - p2);
+
+    Rectangle r = Rectangle(p1, p2);
+    writefln("%s", r);
+    */
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doodle/main/undo_manager.d	Sun Aug 02 16:27:21 2009 +0930
@@ -0,0 +1,155 @@
+module undo_manager;
+
+version(none) {
+
+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);
+                foreach_reverse(UndoAction ua; _actions) {
+                    ua.undo();
+                }
+            }
+
+            void redo() {
+                assert(_finalised);
+                foreach (UndoAction ua; _actions) {
+                    ua.redo();
+                }
+            }
+
+            void undo() {
+                assert(_finalised);
+                foreach_reverse(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/doodle/tk/events.d	Sun Aug 02 16:27:21 2009 +0930
@@ -0,0 +1,122 @@
+module doodle.tk.events;
+
+public {
+    import doodle.tk.types;
+    import doodle.tk.geometry;
+}
+
+abstract class Event {
+    this(in Mask mask) {
+        mMask = mask;
+    }
+
+    Mask mask() const { return mMask; }
+
+    private {
+        Mask mMask;
+    }
+}
+
+final class CrossingEvent : Event {
+    this(in Mask mask) {
+        super(mask);
+    }
+
+    private {
+    }
+}
+
+final class KeyEvent : Event {
+    this(in string str, in uint value, in Mask mask) {
+        super(mask);
+        mStr = str;
+        mValue = value;
+    }
+
+    string str() const { return mStr; }
+
+    override string toString() const {
+        return std.string.format("Key event: %s, %d, %s", mStr, mValue, mMask);
+    }
+
+    private {
+        string mStr;
+        uint mValue;
+    }
+}
+
+abstract class PointerEvent : Event {
+    this(in Point pixel_point, in Point model_point, in Mask mask) {
+        super(mask);
+        mPixelPoint = pixel_point;
+        mModelPoint = model_point;
+    }
+
+    Point pixel_point() const { return mPixelPoint; }
+    Point model_point() const { return mModelPoint; }
+
+    private {
+        Point mPixelPoint;
+        Point mModelPoint;
+    }
+}
+
+final class ButtonEvent : PointerEvent {
+    this(in ButtonAction button_action,
+         in ButtonName button_name,
+         in Point pixel_point,
+         in Point model_point,
+         in Mask mask) {   
+        super(pixel_point, model_point, mask);
+        mButtonAction = button_action;
+        mButtonName = button_name;
+    }
+
+    override string toString() const {
+        return std.string.format("Button event: %s, %s, %s, %s, %s",
+                                 enumToString(mButtonAction), enumToString(mButtonName),
+                                 mPixelPoint, mModelPoint, mMask);
+    }
+
+    ButtonAction button_action() const { return mButtonAction; }
+    ButtonName button_name() const { return mButtonName; }
+
+    private {
+        ButtonAction mButtonAction;
+        ButtonName mButtonName;
+    }
+}
+
+final class MotionEvent : PointerEvent {
+    this(in Point pixel_point,
+         in Point model_point,
+         in Mask mask) {
+        super(pixel_point, model_point, mask);
+    }
+
+    override string toString() const {
+        return std.string.format("Motion event: %s, %s, %s",
+                                 mPixelPoint, mModelPoint, mMask);
+    }
+}
+
+final class ScrollEvent : PointerEvent {
+    this(in ScrollDirection scroll_direction,
+         in Point pixel_point,
+         in Point model_point,
+         in Mask mask) {
+        super(pixel_point, model_point, mask);
+        mScrollDirection = scroll_direction;
+    }
+
+    override string toString() const {
+        return std.string.format("Scroll event: %s, %s, %s, %s",
+                                 enumToString(mScrollDirection), mPixelPoint, mModelPoint, mMask);
+    }
+
+    ScrollDirection scroll_direction() const { return mScrollDirection; }
+
+    private {
+        ScrollDirection mScrollDirection;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doodle/tk/geometry.d	Sun Aug 02 16:27:21 2009 +0930
@@ -0,0 +1,250 @@
+module doodle.tk.geometry;
+
+private {
+    import std.stdio;
+    import std.math;
+    import doodle.tk.misc;
+}
+
+struct Point {
+    static immutable Point DEFAULT;
+
+    static this() {
+        DEFAULT = Point(0.0, 0.0);
+    }
+
+    this(in double x, in double y) {
+        _x = x;
+        _y = y;
+    }
+
+    Point opAdd(in Vector v) const {
+        return Point(_x + v._x, _y + v._y);
+    }
+
+    Point opSub(in Vector v) const {
+        return Point(_x - v._x, _y - v._y);
+    }
+
+    Vector opSub(in Point p) const {
+        return Vector(_x - p._x, _y - p._y);
+    }
+
+    string toString() {
+        return std.string.format("(%f, %f)", _x, _y);
+    }
+
+    double x() const { return _x; }
+    double y() const { return _y; }
+
+    private {
+        double _x, _y;
+    }
+}
+
+Point min_extents(in Point a, in Point b) {
+    return Point(min(a.x, b.x), min(a.y, b.y));
+}
+
+Point max_extents(in Point a, in Point b) {
+    return Point(max(a.x, b.x), max(a.y, b.y));
+}
+
+struct Vector {
+    static Vector DEFAULT;
+
+    static this() {
+        DEFAULT = Vector(0.0, 0.0);
+    }
+
+    this(in double x, in double y) {
+        _x = x;
+        _y = y;
+    }
+
+    Vector opAdd(in Vector v) const {
+        return Vector(_x + v._x, _y + v._y);
+    }
+
+    Vector opSub(in Vector v) const {
+        return Vector(_x - v._x, _y - v._y);
+    }
+
+    Vector opNeg() const {
+        return Vector(-_x, -_y);
+    }
+
+    Vector opMul_r(in double d) const {
+        return Vector(d * _x, d * _y);
+    }
+
+    Vector opDiv(in double d) const {
+        return Vector(_x / d, _y / d);
+    }
+
+    double length() const {
+        return sqrt(_x * _x + _y * _y);
+    }
+
+    string toString() {
+        return std.string.format("[%f, %f]", _x, _y);
+    }
+
+    double x() const { return _x; }
+    double y() const { return _y; }
+
+    private {
+        double _x, _y;
+    }
+}
+
+struct Rectangle {
+    static Rectangle DEFAULT;
+
+    static this() {
+        DEFAULT = Rectangle(Point.DEFAULT, Vector.DEFAULT);
+    }
+
+    /*
+    static Rectangle from_arbitrary_corners(in Point corner1, in Point corner) {
+    }
+    */
+
+    this(in Point position, in Vector size) {
+        this(position.x, position.y, size.x, size.y);
+    }
+
+    this(in Point corner1, in Point corner) {
+        this(corner1.x, corner1.y, corner.x - corner1.x, corner.y - corner1.y);
+    }
+
+    Point position() const {
+        return _position;
+    }
+
+    Vector size() const {
+        return _size;
+    }
+
+    alias position min_corner;
+
+    Point max_corner() const {
+        return _position + _size;
+    }
+
+    bool valid() const {
+        return _size.x > 0.0 & _size.y > 0.0;
+    }
+
+    bool invalid() const {
+        return !valid();
+    }
+
+    double area() const {
+        return _size.x * _size.y;
+    }
+
+    // Intersection
+    Rectangle opAnd(in Rectangle r) const {
+        if (invalid() || r.invalid()) {
+            return DEFAULT;
+        }
+        else {
+            Point max = min_extents(max_corner(), r.max_corner());
+            Point min = max_extents(min_corner(), r.min_corner());
+
+            if (max.x < min.x || max.y < min.y) {
+                return DEFAULT;
+            }
+            else {
+                return Rectangle(min, max);
+            }
+        }
+    }
+
+    // Union
+    Rectangle opOr(in Rectangle r) const {
+        if (invalid()) {
+            return r;
+        }
+        else if (r.invalid()) {
+            return this;
+        }
+        else {
+            return Rectangle(min_extents(min_corner(), r.min_corner()),
+                              max_extents(max_corner(), r.max_corner()));
+        }
+    }
+
+    // TODO make these free functions
+
+    Rectangle moved(in Vector displacement) const {
+        return Rectangle(_position + displacement, _size);
+    }
+
+    Rectangle expanded(in Vector expand_amount) const {
+        return Rectangle(_position, _size + expand_amount);
+    }
+
+    Rectangle feathered(double amount) const {
+        assert(amount >= 0.0);
+        return Rectangle(Point(_position.x - amount, _position.y - amount),
+                         Vector(_size.x + 2.0 * amount, _size.y + 2.0 * amount));
+    }
+
+    Rectangle shrunk(in Vector shrink_amount) const {
+        return Rectangle(_position, _size - shrink_amount);
+    }
+
+    Rectangle resized(in Vector new_size) const {
+        return Rectangle(_position, new_size);
+    }
+
+    Rectangle repositioned(in Point new_position) const {
+        return Rectangle(new_position, _size);
+    }
+
+    void get_quantised(out int x, out int y, out int w, out int h) const {
+        x = cast(int)floor(_position.x);
+        y = cast(int)floor(_position.y);
+        w = cast(int)ceil(_position.x + _size.x) - x;
+        h = cast(int)ceil(_position.y + _size.y) - y;
+    }
+
+    //
+
+    Point centre() const {
+        return _position + _size / 2.0;
+    }
+
+    string toString() {
+        return std.string.format("{%s, %s}", _position, _size);
+    }
+
+    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; }
+            _position = Point(x, y);
+            _size = Vector(w, h);
+        }
+
+        Point _position;
+        Vector _size;
+    }
+}
+
+/*
+struct Segment {
+    private {
+        Point _begin, _end;
+    }
+}
+
+struct Line {
+    private {
+        Point _point;
+        Vector _gradien;
+    }
+}
+*/
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doodle/tk/misc.d	Sun Aug 02 16:27:21 2009 +0930
@@ -0,0 +1,17 @@
+module tk.misc;
+
+double min(in double a, in double b) {
+    return a < b ? a : b;
+}
+
+double max(in double a, in double b) {
+    return a > b ? a : b;
+}
+
+double clamp(in double v, in double min, in 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/doodle/tk/types.d	Sun Aug 02 16:27:21 2009 +0930
@@ -0,0 +1,58 @@
+module tk.types;
+
+public {
+    import std.string;
+}
+
+private {
+    import std.typecons;
+    import std.algorithm;
+}
+
+mixin(defineEnum!("ButtonAction",
+                  "SINGLE_PRESS", "DOUBLE_PRESS", "TRIPLE_PRESS", "RELEASE"));
+
+mixin(defineEnum!("ButtonName",
+                  "LEFT", "MIDDLE", "RIGHT", "FOUR", "FIVE"));
+
+mixin(defineEnum!("ScrollDirection",
+                  "UP", "DOWN", "LEFT", "RIGHT"));
+
+mixin(defineEnum!("Modifier",
+                  "SHIFT", "CAPS_LOCK", "CONTROL", "ALT", "NUM_LOCK", "META",
+                  "SCROLL_LOCK", "LEFT_BUTTON", "MIDDLE_BUTTON", "RIGHT_BUTTON",
+                  "UNUSED_BUTTON_1", "UNUSED_BUTTON_2"));
+
+struct Mask {
+    this(in Modifier[] modifiers) {
+        foreach (ref m; modifiers) {
+            _bits |= 1 << m;
+        }
+    }
+
+    string toString() {
+        string s;
+
+        // FIXME this is terrible
+        for (int i = 0; i < uint.sizeof * 8; ++i) {
+            if (_bits & (1 << i)) {
+                if (s != "") s ~= "|";
+                s ~= enumToString(cast(Modifier)i);
+            }
+        }
+
+        return s;
+    }
+
+    bool is_set(in Modifier m) const {
+        return _bits & (1 << m);
+    }
+
+    bool is_unset(in Modifier m) const {
+        return !is_set(m);
+    }
+
+    private {
+        uint _bits;
+    }
+}
--- a/fig/fig.d	Wed Jul 29 14:11:35 2009 +0930
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,27 +0,0 @@
-module fig.fig;
-
-abstract class Fig {
-    Rectangle bounds() const;
-    void draw(in Rectangle damage, scope Context cr) const;
-
-    private {
-    }
-}
-
-abstract class FigElement : Fig {
-}
-
-class Connector {
-}
-
-class FigNode : FigElement {
-}
-
-class FigEdge : FigElement {
-    private {
-        FigElement
-    }
-}
-
-abstract class FigLeaf : Fig {
-}
--- a/fig/fig_layer.d	Wed Jul 29 14:11:35 2009 +0930
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,24 +0,0 @@
-module fig.fig_layer;
-
-public {
-    import dia.icanvas;
-}
-
-class FigLayer : Layer {
-    this(in string name) {
-        super(name);
-    }
-
-    override Rectangle bounds() const {
-        return Rectangle.DEFAULT;
-    }
-
-    override void draw(in Viewport viewport,
-                       in Rectangle pixel_damage, scope Context pixel_cr,
-                       in Rectangle model_damage, scope Context model_cr) const {
-    }
-
-    private {
-        //Fig[] mFigs;
-    }
-}
--- a/fig/selection_layer.d	Wed Jul 29 14:11:35 2009 +0930
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,22 +0,0 @@
-module fig.selection_layer;
-
-/*
-public {
-    import dia.icanvas;
-}
-
-class SelectionLayer : Layer {
-    this(in string name) {
-        super(name);
-    }
-
-    private {
-        // Selector[] mSelectors;
-    }
-}
-
-abstract class Selector {
-    private {
-    }
-}
-*/
--- a/gtk/canvas.d	Wed Jul 29 14:11:35 2009 +0930
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,550 +0,0 @@
-module gtk.canvas;
-
-public {
-    import dia.icanvas;
-    import tk.events;
-}
-
-private {
-    import gtk.conversions;
-    import tk.misc;
-    import cairo.routines;
-
-    import cairo.Surface;
-
-    import std.math;
-    import std.stdio;
-
-    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 gdk.Drawable;
-
-    import gtkc.gtk;
-}
-
-// x and y run right and up respectively
-
-class Canvas : Table, Viewport {
-    this(in Layer[] layers, EventHandler event_handler, in double ppi) {
-        super(3, 3, 0);
-
-        mDamage = Rectangle.DEFAULT;
-
-        mLayers = layers.dup;
-        mEventHandler = event_handler;
-        mPPI = ppi;
-
-        /*
-        writefln("Layer bounds: %s", layer_bounds);
-        writefln("Canvas bounds: %s", mCanvasBounds);
-        writefln("View centre: %s", mViewCentre);
-        */
-
-        // Create our child widgets and register callbacks
-
-        mHRuler = new HRuler;
-        attach(mHRuler,
-               1, 2,
-               0, 1,
-               AttachOptions.FILL | AttachOptions.EXPAND, AttachOptions.SHRINK,
-               0, 0);
-        mHRuler.setMetric(MetricType.PIXELS);
-
-        mVRuler = new VRuler;
-        attach(mVRuler,
-               0, 1,
-               1, 2,
-               AttachOptions.SHRINK, AttachOptions.FILL | AttachOptions.EXPAND,
-               0, 0);
-        mVRuler.setMetric(MetricType.PIXELS);
-
-        mDrawingArea = new DrawingArea;
-        mDrawingArea.addOnRealize(&on_realize);
-        mDrawingArea.addOnConfigure(&on_configure);
-        mDrawingArea.addOnExpose(&on_expose);
-        mDrawingArea.addOnButtonPress(&on_button_press);
-        mDrawingArea.addOnButtonRelease(&on_button_release);
-        mDrawingArea.addOnKeyPress(&on_key_event);
-        mDrawingArea.addOnKeyRelease(&on_key_event);
-        mDrawingArea.addOnMotionNotify(&on_motion_notify);
-        mDrawingArea.addOnScroll(&on_scroll);
-        mDrawingArea.addOnEnterNotify(&on_enter_notify);
-        mDrawingArea.addOnLeaveNotify(&on_leave_notify);
-        mDrawingArea.setEvents(EventMask.EXPOSURE_MASK |
-                               EventMask.POINTER_MOTION_MASK |
-                               EventMask.POINTER_MOTION_HINT_MASK |
-                               EventMask.BUTTON_MOTION_MASK |
-                               EventMask.BUTTON_PRESS_MASK |
-                               EventMask.BUTTON_RELEASE_MASK |
-                               EventMask.KEY_PRESS_MASK |
-                               EventMask.KEY_RELEASE_MASK |
-                               EventMask.ENTER_NOTIFY_MASK |
-                               EventMask.LEAVE_NOTIFY_MASK |
-                               EventMask.FOCUS_CHANGE_MASK |
-                               EventMask.SCROLL_MASK);
-
-        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, 0.5);
-        mHAdjustment.addOnValueChanged(&onValueChanged);
-        mHScrollbar = new HScrollbar(mHAdjustment);
-        mHScrollbar.setInverted(false);
-        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.5);
-        mVAdjustment.addOnValueChanged(&onValueChanged);
-        mVScrollbar = new VScrollbar(mVAdjustment);
-        mVScrollbar.setInverted(true);
-        attach(mVScrollbar,
-               2, 3,
-               1, 2,
-               AttachOptions.SHRINK,
-               AttachOptions.FILL | AttachOptions.EXPAND,
-               0, 0);
-    }
-
-    override void zoom_relative(in Point pixel_datum, in 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 = pixel_to_model(pixel_datum);
-        Vector pixel_distance = model_to_pixel(old_model_datum - mViewCentre);
-        mZoom = clamp_zoom(factor * mZoom);
-        mViewCentre = old_model_datum - pixel_to_model(pixel_distance);
-
-        update_adjustments;
-        update_rulers;
-        queueDraw;
-    }
-
-    override void pan_relative(in Vector pixel_displacement) {
-        mViewCentre = mViewCentre + pixel_to_model(pixel_displacement);
-
-        update_adjustments;
-        update_rulers;
-        queueDraw;
-    }
-
-    override void set_cursor(in Cursor cursor) {
-        CursorType cursor_type;
-
-        switch (cursor) {
-        case Cursor.DEFAULT:
-            cursor_type = CursorType.ARROW;
-            break;
-        case Cursor.HAND:
-            cursor_type = CursorType.HAND1;
-            break;
-        case Cursor.CROSSHAIR:
-            cursor_type = CursorType.CROSSHAIR;
-            break;
-        }
-
-        mDrawingArea.setCursor(new gdk.Cursor.Cursor(cursor_type));
-    }
-
-    override void damage_model(in Rectangle area) {
-        mDamage = mDamage | model_to_pixel(area);
-    }
-
-    override void damage_pixel(in Rectangle area) {
-        mDamage = mDamage | area;
-    }
-
-    private {
-
-        bool on_configure(GdkEventConfigure * event, Widget widget) {
-            assert(widget is mDrawingArea);
-
-            if (!mHadConfigure) {
-                const double MM_PER_INCH = 25.4;
-                mZoom = 0.25 * mPPI / MM_PER_INCH;
-
-                // Take the union of the bounds of each layer to
-                // determine the canvas size
-
-                Rectangle layer_bounds = Rectangle.DEFAULT;
-
-                foreach (ref layer; mLayers) {
-                    layer_bounds = layer_bounds | layer.bounds;
-                }
-
-                assert(layer_bounds.valid);
-
-                mCanvasBounds = layer_bounds.moved(-layer_bounds.size).expanded(2.0 * layer_bounds.size);
-                mViewCentre = mCanvasBounds.centre;
-
-                mHadConfigure = true;
-            }
-
-            mViewSize = Vector(cast(double)event.width, cast(double)event.height);
-            update_adjustments;
-            update_rulers;
-
-            //writefln("Canvas bounds: %s", mCanvasBounds);
-            //writefln("View centre: %s", mViewCentre);
-
-            return true;
-        }
-
-        bool on_expose(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 model_cr = new Context(dr);
-            scope pixel_cr = new Context(dr);
-
-            Rectangle pixel_damage =
-                event is null ?
-                Rectangle(Point(0.0, 0.0), mViewSize) :
-                Rectangle(Point(cast(double)event.area.x, mViewSize.y - cast(double)(event.area.y + event.area.height)),
-                          Vector(cast(double)event.area.width, cast(double)event.area.height));
-
-            Rectangle model_damage = pixel_to_model(pixel_damage);
-
-            //writefln("Pixel damage: %s, model damage: %s", pixel_damage, model_damage);
-
-            model_cr.save; pixel_cr.save; {
-                // Setup model context and clip
-
-                GtkAdjustment * h_gtkAdjustment = mHAdjustment.getAdjustmentStruct;
-                GtkAdjustment * v_gtkAdjustment = mVAdjustment.getAdjustmentStruct;
-
-                model_cr.scale(mZoom, -mZoom);
-                model_cr.translate(-gtk_adjustment_get_value(h_gtkAdjustment),
-                                   -gtk_adjustment_get_value(v_gtkAdjustment) - gtk_adjustment_get_page_size(v_gtkAdjustment));
-
-                rectangle(model_cr, model_damage);
-                model_cr.clip;
-
-                // Setup pixel context and clip
-
-                pixel_cr.translate(0.0, mViewSize.y);
-                pixel_cr.scale(1.0, -1.0);
-
-                rectangle(pixel_cr, pixel_damage);
-                pixel_cr.clip;
-
-                // Fill the background
-
-                pixel_cr.save; {
-                    // Make the window light grey
-                    pixel_cr.setSourceRgba(0.6, 0.6, 0.6, 1.0);
-                    rectangle(pixel_cr, pixel_damage);
-                    pixel_cr.fill;
-                } pixel_cr.restore;
-
-                // Draw each layer
-
-                foreach(ref layer; mLayers) {
-                    model_cr.save; pixel_cr.save; {
-                        layer.draw(this, pixel_damage, pixel_cr, model_damage, model_cr);
-                    } pixel_cr.restore; model_cr.restore;
-                }
-            } pixel_cr.restore; model_cr.restore;
-
-            return true;
-        }
-
-        bool on_button_press(GdkEventButton * event, Widget widget) {
-            assert(widget is mDrawingArea);
-            //writefln("Got button event\n");
-
-            Point pixel_point = Point(event.x + 0.5, mViewSize.y - (event.y + 0.5));
-            Point model_point = pixel_to_model(pixel_point);
-
-            auto button_event = new ButtonEvent(gtk2tk_button_action(event.type),
-                                                gtk2tk_button_name(event.button),
-                                                pixel_point,
-                                                model_point,
-                                                gtk2tk_mask(event.state));
-
-            mEventHandler.handle_button_press(this, button_event);
-
-            process_damage;
-
-            return true;
-        }
-
-        bool on_button_release(GdkEventButton * event, Widget widget) {
-            assert(widget is mDrawingArea);
-            //writefln("Got button event\n");
-
-            Point pixel_point = Point(event.x + 0.5, mViewSize.y - (event.y + 0.5));
-            Point model_point = pixel_to_model(pixel_point);
-
-            auto button_event = new ButtonEvent(gtk2tk_button_action(event.type),
-                                                gtk2tk_button_name(event.button),
-                                                pixel_point,
-                                                model_point,
-                                                gtk2tk_mask(event.state));
-
-            mEventHandler.handle_button_release(this, button_event);
-
-            process_damage;
-
-            return true;
-        }
-
-        bool on_key_event(GdkEventKey * event, Widget widget) {
-            assert(widget is mDrawingArea);
-            //writefln("Got key event\n");
-
-            //auto key_event = new KeyEvent("",
-            // mEventHandle.handle_key(key_event);
-
-            process_damage;
-
-            return true;
-        }
-
-        bool on_motion_notify(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 pixel_point = Point(event.x + 0.5, mViewSize.y - (event.y + 0.5));
-            Point model_point = pixel_to_model(pixel_point);
-
-            auto motion_event = new MotionEvent(pixel_point,
-                                                model_point,
-                                                gtk2tk_mask(event.state));
-
-            mEventHandler.handle_motion(this, motion_event);
-
-            process_damage;
-
-            return true;
-        }
-
-        bool on_scroll(GdkEventScroll * event, Widget widget) {
-            assert(widget is mDrawingArea);
-            //writefln("Got scroll\n");
-
-            Point pixel_point = Point(event.x + 0.5, mViewSize.y - (event.y + 0.5));
-            Point model_point = pixel_to_model(pixel_point);
-
-            auto scroll_event = new ScrollEvent(gtk2tk_direction(event.direction),
-                                                pixel_point,
-                                                model_point,
-                                                gtk2tk_mask(event.state));
-
-            mEventHandler.handle_scroll(this, scroll_event);
-
-            process_damage;
-
-            return true;
-        }
-
-        /*
-        public enum GdkCrossingMode {       
-            NORMAL,
-            GRAB,
-            UNGRAB,
-            GTK_GRAB,
-            GTK_UNGRAB,
-            STATE_CHANGED
-        }
-
-        public struct GdkEventCrossing {
-            GdkEventType type;
-            GdkWindow *window;
-            byte sendEvent;
-            GdkWindow *subwindow;
-            uint time;
-            double x;
-            double y;
-            double xRoot;
-            double yRoot;
-            GdkCrossingMode mode;
-            GdkNotifyType detail;
-            int focus;
-            uint state;
-        }
-        */
-
-        bool on_enter_notify(GdkEventCrossing * event, Widget widget) {
-            assert(widget is mDrawingArea);
-            //writefln("Enter %d %d %d", cast(int)event.mode, event.focus, event.state);
-            // TODO
-            return true;
-        }
-
-        bool on_leave_notify(GdkEventCrossing * event, Widget widget) {
-            assert(widget is mDrawingArea);
-            //writefln("Leave %d %d %d", cast(int)event.mode, event.focus, event.state);
-            // TODO
-            return true;
-        }
-
-        void onValueChanged(Adjustment adjustment) {
-            GtkAdjustment * h_gtkAdjustment = mHAdjustment.getAdjustmentStruct;
-            GtkAdjustment * v_gtkAdjustment = mVAdjustment.getAdjustmentStruct;
-
-            Point view_left_top = Point(gtk_adjustment_get_value(h_gtkAdjustment),
-                                        gtk_adjustment_get_value(v_gtkAdjustment));
-
-            Vector model_size = pixel_to_model(mViewSize);
-
-            //writefln("%s", view_left_bottom);
-            mViewCentre = view_left_top + model_size / 2.0;
-            //writefln("onValueChanged mViewCentre: %s", mViewCentre);
-
-            update_rulers;
-
-            queueDraw;
-        }
-
-        void update_rulers() {
-            invariant Vector model_size = pixel_to_model(mViewSize);
-
-            invariant Point view_left_bottom = mViewCentre - model_size / 2.0;
-            invariant Point view_right_top = mViewCentre + model_size / 2.0;
-
-            // Define these just to obtain the position
-            // below and we can preserve it
-            double lower, upper, position, max_size;
-
-            mHRuler.getRange(lower, upper, position, max_size);
-            mHRuler.setRange(view_left_bottom.x,
-                             view_right_top.x,
-                             position,
-                             mZoom * 50.0);
-
-            mVRuler.getRange(lower, upper, position, max_size);
-            mVRuler.setRange(view_right_top.y,
-                             view_left_bottom.y,
-                             position,
-                             mZoom * 50.0);
-        }
-
-        void update_adjustments() {
-            invariant Vector model_size = pixel_to_model(mViewSize);
-
-            invariant Point view_left_bottom = mViewCentre - model_size / 2.0;
-            invariant Point view_right_top = mViewCentre + model_size / 2.0;
-
-            // Adjust the canvas size if necessary
-            mCanvasBounds = Rectangle(min_extents(mCanvasBounds.min_corner, view_left_bottom),
-                                      max_extents(mCanvasBounds.max_corner, view_right_top));
-
-            // Update the adjustments
-
-            GtkAdjustment * h_gtkAdjustment = mHAdjustment.getAdjustmentStruct;
-            GtkAdjustment * v_gtkAdjustment = mVAdjustment.getAdjustmentStruct;
-
-            gtk_adjustment_set_lower(h_gtkAdjustment, mCanvasBounds.min_corner.x);
-            gtk_adjustment_set_upper(h_gtkAdjustment, mCanvasBounds.max_corner.x);
-            gtk_adjustment_set_value(h_gtkAdjustment, view_left_bottom.x);
-            gtk_adjustment_set_step_increment(h_gtkAdjustment, mCanvasBounds.size.x / 16.0);
-            gtk_adjustment_set_page_increment(h_gtkAdjustment, mCanvasBounds.size.x / 4.0);
-            gtk_adjustment_set_page_size(h_gtkAdjustment, model_size.x);
-
-            gtk_adjustment_set_lower(v_gtkAdjustment, mCanvasBounds.min_corner.y);
-            gtk_adjustment_set_upper(v_gtkAdjustment, mCanvasBounds.max_corner.y);
-            gtk_adjustment_set_value(v_gtkAdjustment, view_left_bottom.y);
-            gtk_adjustment_set_step_increment(v_gtkAdjustment, mCanvasBounds.size.y / 16.0);
-            gtk_adjustment_set_page_increment(v_gtkAdjustment, mCanvasBounds.size.y / 4.0);
-            gtk_adjustment_set_page_size(v_gtkAdjustment, model_size.y);
-
-            mHAdjustment.changed;
-            mHAdjustment.valueChanged;
-            mVAdjustment.changed;
-            mVAdjustment.valueChanged;
-        }
-
-        void process_damage() {
-            if (mDamage.valid) {
-                //writefln("Damage: %s", mDamage);
-                int x, y, w, h;
-                mDamage.get_quantised(x, y, w, h);
-                mDrawingArea.queueDrawArea(x, cast(int)mViewSize.y - (y + h), w, h);
-                mDamage = Rectangle.DEFAULT;
-            }
-            else {
-                //writefln("No damage");
-            }
-        }
-
-        double clamp_zoom(in double zoom) { return clamp(zoom, 0.2, 10.0); }
-
-        Point model_to_pixel(in Point model) const {
-            return Point.DEFAULT + mViewSize / 2.0 + mZoom * (model - mViewCentre);
-        }
-
-        Point pixel_to_model(in Point pixel) const {
-            return mViewCentre + (pixel - mViewSize / 2.0 - Point.DEFAULT) / mZoom;
-        }
-
-        Vector model_to_pixel(in Vector model) const {
-            return mZoom * model;
-        }
-
-        Vector pixel_to_model(in Vector pixel) const {
-            return pixel / mZoom;
-        }
-
-        Rectangle model_to_pixel(in Rectangle model) const {
-            return Rectangle(model_to_pixel(model.position), model_to_pixel(model.size));
-        }
-
-        Rectangle pixel_to_model(in Rectangle model) const {
-            return Rectangle(pixel_to_model(model.position), pixel_to_model(model.size));
-        }
-
-        void on_realize(Widget widget) {
-            assert(widget is mDrawingArea);
-            //writefln("Got realize\n");
-        }
-
-        bool mHadConfigure;
-        Rectangle mDamage;          // pixels
-
-        // Model units are in millimetres
-        // Screen units are in pixels
-        double mZoom;               // pixels-per-model-unit (mm)
-        Vector mViewSize;           // pixel: size of view window in pixels
-        Point mViewCentre;          // model: where in the model is the centre of our view
-        Rectangle mCanvasBounds;    // model:
-
-        // Child widgets:
-        HRuler mHRuler;
-        VRuler mVRuler;
-        DrawingArea mDrawingArea;
-        Adjustment mHAdjustment;
-        HScrollbar mHScrollbar;
-        Adjustment mVAdjustment;
-        VScrollbar mVScrollbar;
-
-        // Layers:
-        Layer[] mLayers;
-        EventHandler mEventHandler;
-        double mPPI;
-    }
-}
--- a/gtk/conversions.d	Wed Jul 29 14:11:35 2009 +0930
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,74 +0,0 @@
-module gtk.conversions;
-
-public {
-    import tk.types;
-}
-
-private {
-    static import gdk.Event;
-}
-
-ButtonAction gtk2tk_button_action(gdk.Event.EventType event_type) {
-    switch (event_type) {
-    case gdk.Event.EventType.BUTTON_PRESS:
-        return ButtonAction.SINGLE_PRESS;
-    case gdk.Event.EventType.DOUBLE_BUTTON_PRESS:
-        return ButtonAction.DOUBLE_PRESS;
-    case gdk.Event.EventType.TRIPLE_BUTTON_PRESS:
-        return ButtonAction.TRIPLE_PRESS;
-    case gdk.Event.EventType.BUTTON_RELEASE:
-        return ButtonAction.RELEASE;
-    default:
-        assert(false);
-    }
-}
-
-ButtonName gtk2tk_button_name(gdk.Event.guint button) {
-    switch (button) {
-    case 1:
-        return ButtonName.LEFT;
-    case 2:
-        return ButtonName.MIDDLE;
-    case 3:
-        return ButtonName.RIGHT;
-    case 4:
-        return ButtonName.FOUR;
-    case 5:
-        return ButtonName.FIVE;
-    default:
-        assert(false);
-    }
-}
-
-Mask gtk2tk_mask(gdk.Event.guint state) {
-    Modifier[] modifiers;
-
-    if (state & gdk.Event.GdkModifierType.SHIFT_MASK)   modifiers ~= Modifier.SHIFT;
-    if (state & gdk.Event.GdkModifierType.CONTROL_MASK) modifiers ~= Modifier.CONTROL;
-    if (state & gdk.Event.GdkModifierType.MOD1_MASK)    modifiers ~= Modifier.ALT;
-    if (state & gdk.Event.GdkModifierType.MOD2_MASK)    modifiers ~= Modifier.META;
-    if (state & gdk.Event.GdkModifierType.BUTTON1_MASK) modifiers ~= Modifier.LEFT_BUTTON;
-    if (state & gdk.Event.GdkModifierType.BUTTON2_MASK) modifiers ~= Modifier.MIDDLE_BUTTON;
-    if (state & gdk.Event.GdkModifierType.BUTTON3_MASK) modifiers ~= Modifier.RIGHT_BUTTON;
-    if (state & gdk.Event.GdkModifierType.BUTTON4_MASK) modifiers ~= Modifier.UNUSED_BUTTON_1;
-    if (state & gdk.Event.GdkModifierType.BUTTON5_MASK) modifiers ~= Modifier.UNUSED_BUTTON_2;
-
-    Mask m = Mask(modifiers);
-
-    return Mask(modifiers);
-}
-
-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);
-    }
-}
--- a/gtk/tool_bar.d	Wed Jul 29 14:11:35 2009 +0930
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,63 +0,0 @@
-module gtk.toolbar;
-
-public {
-    import gtk.Toolbar;
-}
-
-private {
-    import gtk.ToolButton;
-    import gtk.SeparatorToolItem;
-    import gtk.RadioToolButton;
-    import gtk.Image;
-
-    import glib.ListSG;
-
-    import std.stdio;
-}
-
-class ToolBar : Toolbar {
-    this() {
-        // INVALID, MENU, SMALL_TOOLBAR, LARGE_TOOLBAR,
-        // BUTTON, DND, DIALOG
-        setIconSize(GtkIconSize.LARGE_TOOLBAR);
-	// ICONS, TEXT, BOTH, BOTH_HORIZ
-        setStyle(GtkToolbarStyle.ICONS);
-	// HORIZONTAL, VERTICAL
-        setOrientation(GtkOrientation.HORIZONTAL);
-        setTooltips(true);
-
-        Image image;
-        ListSG group;
-
-        image = new Image("icons/select.svg");
-        button1 = new RadioToolButton(group);
-        button1.setLabelWidget(image);
-        insert(button1);
-
-        image = new Image("icons/select.png");
-        button2 = new RadioToolButton(group);
-        button2.setGroup(button1.getGroup);
-        button2.setLabelWidget(image);
-        button2.addOnClicked(&on_clicked);
-        insert(button2);
-
-        insert(new SeparatorToolItem);
-
-        image = new Image("icons/select.png");
-        button3 = new RadioToolButton(group);
-        button3.setGroup(button1.getGroup);
-        button3.setLabelWidget(image);
-        insert(button3);
-    }
-
-    private {
-
-        void on_clicked(ToolButton tool_button) {
-            writefln("Clicked!");
-        }
-
-        RadioToolButton button1;
-        RadioToolButton button2;
-        RadioToolButton button3;
-    }
-}
--- a/gtk/zoom_controls.d	Wed Jul 29 14:11:35 2009 +0930
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,20 +0,0 @@
-module gtk.zoom_controls;
-
-public {
-    import gtk.HBox;
-}
-
-private {
-}
-
-// Useful actions
-// Zoom to fit contents, Zoom 1:1, zoom in/out (coarse/fine)
-// Manually specified zoom?
-
-class ZoomControls : HBox {
-    this() {
-    }
-
-    private {
-    }
-}
Binary file icons/select.png has changed
--- a/icons/select.svg	Wed Jul 29 14:11:35 2009 +0930
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,148 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<!-- Created with Inkscape (http://www.inkscape.org/) -->
-<svg
-   xmlns:dc="http://purl.org/dc/elements/1.1/"
-   xmlns:cc="http://creativecommons.org/ns#"
-   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
-   xmlns:svg="http://www.w3.org/2000/svg"
-   xmlns="http://www.w3.org/2000/svg"
-   xmlns:xlink="http://www.w3.org/1999/xlink"
-   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
-   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
-   width="32px"
-   height="32px"
-   id="svg2383"
-   sodipodi:version="0.32"
-   inkscape:version="0.46"
-   sodipodi:docname="select.svg"
-   inkscape:output_extension="org.inkscape.output.svg.inkscape"
-   inkscape:export-filename="/home/daveb/select.png"
-   inkscape:export-xdpi="90"
-   inkscape:export-ydpi="90">
-  <defs
-     id="defs2385">
-    <linearGradient
-       inkscape:collect="always"
-       id="linearGradient3262">
-      <stop
-         style="stop-color:#6bfff4;stop-opacity:1;"
-         offset="0"
-         id="stop3264" />
-      <stop
-         style="stop-color:#6bfff4;stop-opacity:0;"
-         offset="1"
-         id="stop3266" />
-    </linearGradient>
-    <linearGradient
-       id="linearGradient3254">
-      <stop
-         style="stop-color:#6bfff4;stop-opacity:1;"
-         offset="0"
-         id="stop3256" />
-      <stop
-         style="stop-color:#6bfff4;stop-opacity:0;"
-         offset="1"
-         id="stop3258" />
-    </linearGradient>
-    <inkscape:perspective
-       sodipodi:type="inkscape:persp3d"
-       inkscape:vp_x="0 : 16 : 1"
-       inkscape:vp_y="0 : 1000 : 0"
-       inkscape:vp_z="32 : 16 : 1"
-       inkscape:persp3d-origin="16 : 10.666667 : 1"
-       id="perspective2391" />
-    <linearGradient
-       inkscape:collect="always"
-       xlink:href="#linearGradient3262"
-       id="linearGradient3268"
-       x1="1.1039745"
-       y1="15.925417"
-       x2="30.955889"
-       y2="15.925417"
-       gradientUnits="userSpaceOnUse" />
-  </defs>
-  <sodipodi:namedview
-     id="base"
-     pagecolor="#ffffff"
-     bordercolor="#666666"
-     borderopacity="1.0"
-     inkscape:pageopacity="0.0"
-     inkscape:pageshadow="2"
-     inkscape:zoom="7.9180417"
-     inkscape:cx="12.16509"
-     inkscape:cy="15.571721"
-     inkscape:current-layer="layer1"
-     showgrid="true"
-     inkscape:grid-bbox="true"
-     inkscape:document-units="px"
-     inkscape:window-width="637"
-     inkscape:window-height="724"
-     inkscape:window-x="36"
-     inkscape:window-y="6" />
-  <metadata
-     id="metadata2388">
-    <rdf:RDF>
-      <cc:Work
-         rdf:about="">
-        <dc:format>image/svg+xml</dc:format>
-        <dc:type
-           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
-      </cc:Work>
-    </rdf:RDF>
-  </metadata>
-  <g
-     id="layer1"
-     inkscape:label="Layer 1"
-     inkscape:groupmode="layer">
-    <path
-       style="fill:url(#linearGradient3268);fill-rule:evenodd;stroke:#000000;stroke-width:2.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill-opacity:1"
-       d="M 2.0539745,26.552502 L 16.34884,11.891449 L 4.8466724,8.9175157 L 30.005889,1.7262016 L 22.047836,26.072714 L 19.594399,14.653503 L 5.7154073,30.124632 L 2.0539745,26.552502 z"
-       id="path2395"
-       sodipodi:nodetypes="cccccccc" />
-    <path
-       sodipodi:nodetypes="cccccccc"
-       id="path3274"
-       d="M 2.0539745,26.552502 L 16.34884,11.891449 L 4.8466724,8.9175157 L 30.005889,1.7262016 L 22.047836,26.072714 L 19.594399,14.653503 L 5.7154073,30.124632 L 2.0539745,26.552502 z"
-       style="fill:url(#linearGradient3268);fill-rule:evenodd;stroke:#000000;stroke-width:2.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill-opacity:1" />
-    <path
-       style="fill:url(#linearGradient3268);fill-rule:evenodd;stroke:#000000;stroke-width:2.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill-opacity:1"
-       d="M 2.0539745,26.552502 L 16.34884,11.891449 L 4.8466724,8.9175157 L 30.005889,1.7262016 L 22.047836,26.072714 L 19.594399,14.653503 L 5.7154073,30.124632 L 2.0539745,26.552502 z"
-       id="path3276"
-       sodipodi:nodetypes="cccccccc" />
-    <path
-       sodipodi:nodetypes="cccccccc"
-       id="path3278"
-       d="M 2.0539745,26.552502 L 16.34884,11.891449 L 4.8466724,8.9175157 L 30.005889,1.7262016 L 22.047836,26.072714 L 19.594399,14.653503 L 5.7154073,30.124632 L 2.0539745,26.552502 z"
-       style="fill:url(#linearGradient3268);fill-rule:evenodd;stroke:#000000;stroke-width:2.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill-opacity:1" />
-    <path
-       style="fill:url(#linearGradient3268);fill-rule:evenodd;stroke:#000000;stroke-width:2.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill-opacity:1"
-       d="M 2.0539745,26.552502 L 16.34884,11.891449 L 4.8466724,8.9175157 L 30.005889,1.7262016 L 22.047836,26.072714 L 19.594399,14.653503 L 5.7154073,30.124632 L 2.0539745,26.552502 z"
-       id="path3280"
-       sodipodi:nodetypes="cccccccc" />
-    <path
-       sodipodi:nodetypes="cccccccc"
-       id="path3282"
-       d="M 2.0539745,26.552502 L 16.34884,11.891449 L 4.8466724,8.9175157 L 30.005889,1.7262016 L 22.047836,26.072714 L 19.594399,14.653503 L 5.7154073,30.124632 L 2.0539745,26.552502 z"
-       style="fill:url(#linearGradient3268);fill-rule:evenodd;stroke:#000000;stroke-width:2.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill-opacity:1" />
-    <path
-       style="fill:url(#linearGradient3268);fill-rule:evenodd;stroke:#000000;stroke-width:2.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill-opacity:1"
-       d="M 2.0539745,26.552502 L 16.34884,11.891449 L 4.8466724,8.9175157 L 30.005889,1.7262016 L 22.047836,26.072714 L 19.594399,14.653503 L 5.7154073,30.124632 L 2.0539745,26.552502 z"
-       id="path3284"
-       sodipodi:nodetypes="cccccccc" />
-    <path
-       sodipodi:nodetypes="cccccccc"
-       id="path3286"
-       d="M 2.0539745,26.552502 L 16.34884,11.891449 L 4.8466724,8.9175157 L 30.005889,1.7262016 L 22.047836,26.072714 L 19.594399,14.653503 L 5.7154073,30.124632 L 2.0539745,26.552502 z"
-       style="fill:url(#linearGradient3268);fill-rule:evenodd;stroke:#000000;stroke-width:2.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill-opacity:1" />
-    <path
-       style="fill:url(#linearGradient3268);fill-rule:evenodd;stroke:#000000;stroke-width:2.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill-opacity:1"
-       d="M 2.0539745,26.552502 L 16.34884,11.891449 L 4.8466724,8.9175157 L 30.005889,1.7262016 L 22.047836,26.072714 L 19.594399,14.653503 L 5.7154073,30.124632 L 2.0539745,26.552502 z"
-       id="path3288"
-       sodipodi:nodetypes="cccccccc" />
-    <path
-       sodipodi:nodetypes="cccccccc"
-       id="path3290"
-       d="M 2.0539745,26.552502 L 16.34884,11.891449 L 4.8466724,8.9175157 L 30.005889,1.7262016 L 22.047836,26.072714 L 19.594399,14.653503 L 5.7154073,30.124632 L 2.0539745,26.552502 z"
-       style="fill:url(#linearGradient3268);fill-rule:evenodd;stroke:#000000;stroke-width:2.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill-opacity:1" />
-  </g>
-</svg>
--- a/import/model.d	Wed Jul 29 14:11:35 2009 +0930
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,56 +0,0 @@
-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;
-    }
-}
--- a/import/network.d	Wed Jul 29 14:11:35 2009 +0930
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,97 +0,0 @@
-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);
-}
--- a/import/new.d	Wed Jul 29 14:11:35 2009 +0930
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,40 +0,0 @@
-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();
-}
--- a/import/p-model.d	Wed Jul 29 14:11:35 2009 +0930
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,128 +0,0 @@
-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 {
-    }
-}
--- a/import/types.d	Wed Jul 29 14:11:35 2009 +0930
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,35 +0,0 @@
-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/options	Sun Aug 02 16:27:21 2009 +0930
@@ -0,0 +1,2 @@
+-L-lgtkd
+-L-ldl
--- a/tk/events.d	Wed Jul 29 14:11:35 2009 +0930
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,122 +0,0 @@
-module tk.events;
-
-public {
-    import tk.types;
-    import tk.geometry;
-}
-
-abstract class Event {
-    this(in Mask mask) {
-        mMask = mask;
-    }
-
-    Mask mask() const { return mMask; }
-
-    private {
-        Mask mMask;
-    }
-}
-
-final class CrossingEvent : Event {
-    this(in Mask mask) {
-        super(mask);
-    }
-
-    private {
-    }
-}
-
-final class KeyEvent : Event {
-    this(in string str, in uint value, in Mask mask) {
-        super(mask);
-        mStr = str;
-        mValue = value;
-    }
-
-    string str() const { return mStr; }
-
-    override string toString() const {
-        return std.string.format("Key event: %s, %d, %s", mStr, mValue, mMask);
-    }
-
-    private {
-        string mStr;
-        uint mValue;
-    }
-}
-
-abstract class PointerEvent : Event {
-    this(in Point pixel_point, in Point model_point, in Mask mask) {
-        super(mask);
-        mPixelPoint = pixel_point;
-        mModelPoint = model_point;
-    }
-
-    Point pixel_point() const { return mPixelPoint; }
-    Point model_point() const { return mModelPoint; }
-
-    private {
-        Point mPixelPoint;
-        Point mModelPoint;
-    }
-}
-
-final class ButtonEvent : PointerEvent {
-    this(in ButtonAction button_action,
-         in ButtonName button_name,
-         in Point pixel_point,
-         in Point model_point,
-         in Mask mask) {   
-        super(pixel_point, model_point, mask);
-        mButtonAction = button_action;
-        mButtonName = button_name;
-    }
-
-    override string toString() const {
-        return std.string.format("Button event: %s, %s, %s, %s, %s",
-                                 enumToString(mButtonAction), enumToString(mButtonName),
-                                 mPixelPoint, mModelPoint, mMask);
-    }
-
-    ButtonAction button_action() const { return mButtonAction; }
-    ButtonName button_name() const { return mButtonName; }
-
-    private {
-        ButtonAction mButtonAction;
-        ButtonName mButtonName;
-    }
-}
-
-final class MotionEvent : PointerEvent {
-    this(in Point pixel_point,
-         in Point model_point,
-         in Mask mask) {
-        super(pixel_point, model_point, mask);
-    }
-
-    override string toString() const {
-        return std.string.format("Motion event: %s, %s, %s",
-                                 mPixelPoint, mModelPoint, mMask);
-    }
-}
-
-final class ScrollEvent : PointerEvent {
-    this(in ScrollDirection scroll_direction,
-         in Point pixel_point,
-         in Point model_point,
-         in Mask mask) {
-        super(pixel_point, model_point, mask);
-        mScrollDirection = scroll_direction;
-    }
-
-    override string toString() const {
-        return std.string.format("Scroll event: %s, %s, %s, %s",
-                                 enumToString(mScrollDirection), mPixelPoint, mModelPoint, mMask);
-    }
-
-    ScrollDirection scroll_direction() const { return mScrollDirection; }
-
-    private {
-        ScrollDirection mScrollDirection;
-    }
-}
--- a/tk/geometry.d	Wed Jul 29 14:11:35 2009 +0930
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,250 +0,0 @@
-module tk.geometry;
-
-private {
-    import std.stdio;
-    import std.math;
-    import tk.misc;
-}
-
-struct Point {
-    static immutable Point DEFAULT;
-
-    static this() {
-        DEFAULT = Point(0.0, 0.0);
-    }
-
-    this(in double x, in double y) {
-        _x = x;
-        _y = y;
-    }
-
-    Point opAdd(in Vector v) const {
-        return Point(_x + v._x, _y + v._y);
-    }
-
-    Point opSub(in Vector v) const {
-        return Point(_x - v._x, _y - v._y);
-    }
-
-    Vector opSub(in Point p) const {
-        return Vector(_x - p._x, _y - p._y);
-    }
-
-    string toString() {
-        return std.string.format("(%f, %f)", _x, _y);
-    }
-
-    double x() const { return _x; }
-    double y() const { return _y; }
-
-    private {
-        double _x, _y;
-    }
-}
-
-Point min_extents(in Point a, in Point b) {
-    return Point(min(a.x, b.x), min(a.y, b.y));
-}
-
-Point max_extents(in Point a, in Point b) {
-    return Point(max(a.x, b.x), max(a.y, b.y));
-}
-
-struct Vector {
-    static Vector DEFAULT;
-
-    static this() {
-        DEFAULT = Vector(0.0, 0.0);
-    }
-
-    this(in double x, in double y) {
-        _x = x;
-        _y = y;
-    }
-
-    Vector opAdd(in Vector v) const {
-        return Vector(_x + v._x, _y + v._y);
-    }
-
-    Vector opSub(in Vector v) const {
-        return Vector(_x - v._x, _y - v._y);
-    }
-
-    Vector opNeg() const {
-        return Vector(-_x, -_y);
-    }
-
-    Vector opMul_r(in double d) const {
-        return Vector(d * _x, d * _y);
-    }
-
-    Vector opDiv(in double d) const {
-        return Vector(_x / d, _y / d);
-    }
-
-    double length() const {
-        return sqrt(_x * _x + _y * _y);
-    }
-
-    string toString() {
-        return std.string.format("[%f, %f]", _x, _y);
-    }
-
-    double x() const { return _x; }
-    double y() const { return _y; }
-
-    private {
-        double _x, _y;
-    }
-}
-
-struct Rectangle {
-    static Rectangle DEFAULT;
-
-    static this() {
-        DEFAULT = Rectangle(Point.DEFAULT, Vector.DEFAULT);
-    }
-
-    /*
-    static Rectangle from_arbitrary_corners(in Point corner1, in Point corner) {
-    }
-    */
-
-    this(in Point position, in Vector size) {
-        this(position.x, position.y, size.x, size.y);
-    }
-
-    this(in Point corner1, in Point corner) {
-        this(corner1.x, corner1.y, corner.x - corner1.x, corner.y - corner1.y);
-    }
-
-    Point position() const {
-        return _position;
-    }
-
-    Vector size() const {
-        return _size;
-    }
-
-    alias position min_corner;
-
-    Point max_corner() const {
-        return _position + _size;
-    }
-
-    bool valid() const {
-        return _size.x > 0.0 & _size.y > 0.0;
-    }
-
-    bool invalid() const {
-        return !valid();
-    }
-
-    double area() const {
-        return _size.x * _size.y;
-    }
-
-    // Intersection
-    Rectangle opAnd(in Rectangle r) const {
-        if (invalid() || r.invalid()) {
-            return DEFAULT;
-        }
-        else {
-            Point max = min_extents(max_corner(), r.max_corner());
-            Point min = max_extents(min_corner(), r.min_corner());
-
-            if (max.x < min.x || max.y < min.y) {
-                return DEFAULT;
-            }
-            else {
-                return Rectangle(min, max);
-            }
-        }
-    }
-
-    // Union
-    Rectangle opOr(in Rectangle r) const {
-        if (invalid()) {
-            return r;
-        }
-        else if (r.invalid()) {
-            return this;
-        }
-        else {
-            return Rectangle(min_extents(min_corner(), r.min_corner()),
-                              max_extents(max_corner(), r.max_corner()));
-        }
-    }
-
-    // TODO make these free functions
-
-    Rectangle moved(in Vector displacement) const {
-        return Rectangle(_position + displacement, _size);
-    }
-
-    Rectangle expanded(in Vector expand_amount) const {
-        return Rectangle(_position, _size + expand_amount);
-    }
-
-    Rectangle feathered(double amount) const {
-        assert(amount >= 0.0);
-        return Rectangle(Point(_position.x - amount, _position.y - amount),
-                         Vector(_size.x + 2.0 * amount, _size.y + 2.0 * amount));
-    }
-
-    Rectangle shrunk(in Vector shrink_amount) const {
-        return Rectangle(_position, _size - shrink_amount);
-    }
-
-    Rectangle resized(in Vector new_size) const {
-        return Rectangle(_position, new_size);
-    }
-
-    Rectangle repositioned(in Point new_position) const {
-        return Rectangle(new_position, _size);
-    }
-
-    void get_quantised(out int x, out int y, out int w, out int h) const {
-        x = cast(int)floor(_position.x);
-        y = cast(int)floor(_position.y);
-        w = cast(int)ceil(_position.x + _size.x) - x;
-        h = cast(int)ceil(_position.y + _size.y) - y;
-    }
-
-    //
-
-    Point centre() const {
-        return _position + _size / 2.0;
-    }
-
-    string toString() {
-        return std.string.format("{%s, %s}", _position, _size);
-    }
-
-    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; }
-            _position = Point(x, y);
-            _size = Vector(w, h);
-        }
-
-        Point _position;
-        Vector _size;
-    }
-}
-
-/*
-struct Segment {
-    private {
-        Point _begin, _end;
-    }
-}
-
-struct Line {
-    private {
-        Point _point;
-        Vector _gradien;
-    }
-}
-*/
--- a/tk/misc.d	Wed Jul 29 14:11:35 2009 +0930
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,17 +0,0 @@
-module tk.misc;
-
-double min(in double a, in double b) {
-    return a < b ? a : b;
-}
-
-double max(in double a, in double b) {
-    return a > b ? a : b;
-}
-
-double clamp(in double v, in double min, in double max) {
-    assert(min < max);
-
-    if (v < min) { return min; }
-    else if (v > max) { return max; }
-    else { return v; }
-}
--- a/tk/types.d	Wed Jul 29 14:11:35 2009 +0930
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,58 +0,0 @@
-module tk.types;
-
-public {
-    import std.string;
-}
-
-private {
-    import std.typecons;
-    import std.algorithm;
-}
-
-mixin(defineEnum!("ButtonAction",
-                  "SINGLE_PRESS", "DOUBLE_PRESS", "TRIPLE_PRESS", "RELEASE"));
-
-mixin(defineEnum!("ButtonName",
-                  "LEFT", "MIDDLE", "RIGHT", "FOUR", "FIVE"));
-
-mixin(defineEnum!("ScrollDirection",
-                  "UP", "DOWN", "LEFT", "RIGHT"));
-
-mixin(defineEnum!("Modifier",
-                  "SHIFT", "CAPS_LOCK", "CONTROL", "ALT", "NUM_LOCK", "META",
-                  "SCROLL_LOCK", "LEFT_BUTTON", "MIDDLE_BUTTON", "RIGHT_BUTTON",
-                  "UNUSED_BUTTON_1", "UNUSED_BUTTON_2"));
-
-struct Mask {
-    this(in Modifier[] modifiers) {
-        foreach (ref m; modifiers) {
-            _bits |= 1 << m;
-        }
-    }
-
-    string toString() {
-        string s;
-
-        // FIXME this is terrible
-        for (int i = 0; i < uint.sizeof * 8; ++i) {
-            if (_bits & (1 << i)) {
-                if (s != "") s ~= "|";
-                s ~= enumToString(cast(Modifier)i);
-            }
-        }
-
-        return s;
-    }
-
-    bool is_set(in Modifier m) const {
-        return _bits & (1 << m);
-    }
-
-    bool is_unset(in Modifier m) const {
-        return !is_set(m);
-    }
-
-    private {
-        uint _bits;
-    }
-}
--- a/undo_manager.d	Wed Jul 29 14:11:35 2009 +0930
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,153 +0,0 @@
-module undo_manager;
-
-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;
-    }
-}