# HG changeset patch # User Graham St Jack # Date 1249196241 -34200 # Node ID 1754cb773d415cb930d5badb5bb65c3c776f485e # Parent f3d91579bb28509d9498f682f14b1bc24d45f684 Part-way through getting to compile with configure/builder. diff -r f3d91579bb28 -r 1754cb773d41 README --- /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. diff -r f3d91579bb28 -r 1754cb773d41 builder.d --- /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 "); + 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; +} diff -r f3d91579bb28 -r 1754cb773d41 cairo/routines.d --- 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); -} diff -r f3d91579bb28 -r 1754cb773d41 common/undo.d --- 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); -} diff -r f3d91579bb28 -r 1754cb773d41 configure.d --- /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 +// +// 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 "); + 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; +} diff -r f3d91579bb28 -r 1754cb773d41 dia/grid_layer.d --- 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 { - } -} diff -r f3d91579bb28 -r 1754cb773d41 dia/icanvas.d --- 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; - } -} diff -r f3d91579bb28 -r 1754cb773d41 dia/page_layer.d --- 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; - } -} diff -r f3d91579bb28 -r 1754cb773d41 dia/standard_tools.d --- 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; - } -} diff -r f3d91579bb28 -r 1754cb773d41 dia/tool.d --- 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 { } -} diff -r f3d91579bb28 -r 1754cb773d41 dia/tool_layer.d --- 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; - } -} diff -r f3d91579bb28 -r 1754cb773d41 doodle.d --- 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); - */ -} diff -r f3d91579bb28 -r 1754cb773d41 doodle/cairo/routines.d --- /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); +} diff -r f3d91579bb28 -r 1754cb773d41 doodle/common/undo.d --- /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); +} diff -r f3d91579bb28 -r 1754cb773d41 doodle/dia/grid_layer.d --- /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 { + } +} diff -r f3d91579bb28 -r 1754cb773d41 doodle/dia/icanvas.d --- /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; + } +} diff -r f3d91579bb28 -r 1754cb773d41 doodle/dia/page_layer.d --- /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; + } +} diff -r f3d91579bb28 -r 1754cb773d41 doodle/dia/standard_tools.d --- /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; + } +} diff -r f3d91579bb28 -r 1754cb773d41 doodle/dia/tool.d --- /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 { } +} diff -r f3d91579bb28 -r 1754cb773d41 doodle/dia/tool_layer.d --- /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; + } +} diff -r f3d91579bb28 -r 1754cb773d41 doodle/fig/fig.d --- /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 { +//} diff -r f3d91579bb28 -r 1754cb773d41 doodle/fig/fig_layer.d --- /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; + } +} diff -r f3d91579bb28 -r 1754cb773d41 doodle/fig/selection_layer.d --- /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 { + } +} +*/ diff -r f3d91579bb28 -r 1754cb773d41 doodle/gtk/canvas.d --- /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; + } +} diff -r f3d91579bb28 -r 1754cb773d41 doodle/gtk/conversions.d --- /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); + } +} diff -r f3d91579bb28 -r 1754cb773d41 doodle/gtk/toolbar.d --- /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; + } +} diff -r f3d91579bb28 -r 1754cb773d41 doodle/gtk/zoom_controls.d --- /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 { +// } +//} diff -r f3d91579bb28 -r 1754cb773d41 doodle/icons/select.png Binary file doodle/icons/select.png has changed diff -r f3d91579bb28 -r 1754cb773d41 doodle/icons/select.svg --- /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 @@ + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + diff -r f3d91579bb28 -r 1754cb773d41 doodle/import/model.d --- /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; + } +} diff -r f3d91579bb28 -r 1754cb773d41 doodle/import/network.d --- /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); +} diff -r f3d91579bb28 -r 1754cb773d41 doodle/import/new.d --- /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(); +} diff -r f3d91579bb28 -r 1754cb773d41 doodle/import/p-model.d --- /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 { + } +} diff -r f3d91579bb28 -r 1754cb773d41 doodle/import/types.d --- /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; + } +} diff -r f3d91579bb28 -r 1754cb773d41 doodle/main/prog/doodler.d --- /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); + */ +} diff -r f3d91579bb28 -r 1754cb773d41 doodle/main/undo_manager.d --- /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; + } +} +} diff -r f3d91579bb28 -r 1754cb773d41 doodle/tk/events.d --- /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; + } +} diff -r f3d91579bb28 -r 1754cb773d41 doodle/tk/geometry.d --- /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; + } +} +*/ diff -r f3d91579bb28 -r 1754cb773d41 doodle/tk/misc.d --- /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; } +} diff -r f3d91579bb28 -r 1754cb773d41 doodle/tk/types.d --- /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; + } +} diff -r f3d91579bb28 -r 1754cb773d41 fig/fig.d --- 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 { -} diff -r f3d91579bb28 -r 1754cb773d41 fig/fig_layer.d --- 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; - } -} diff -r f3d91579bb28 -r 1754cb773d41 fig/selection_layer.d --- 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 { - } -} -*/ diff -r f3d91579bb28 -r 1754cb773d41 gtk/canvas.d --- 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; - } -} diff -r f3d91579bb28 -r 1754cb773d41 gtk/conversions.d --- 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); - } -} diff -r f3d91579bb28 -r 1754cb773d41 gtk/tool_bar.d --- 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; - } -} diff -r f3d91579bb28 -r 1754cb773d41 gtk/zoom_controls.d --- 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 { - } -} diff -r f3d91579bb28 -r 1754cb773d41 icons/select.png Binary file icons/select.png has changed diff -r f3d91579bb28 -r 1754cb773d41 icons/select.svg --- 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 @@ - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - diff -r f3d91579bb28 -r 1754cb773d41 import/model.d --- 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; - } -} diff -r f3d91579bb28 -r 1754cb773d41 import/network.d --- 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); -} diff -r f3d91579bb28 -r 1754cb773d41 import/new.d --- 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(); -} diff -r f3d91579bb28 -r 1754cb773d41 import/p-model.d --- 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 { - } -} diff -r f3d91579bb28 -r 1754cb773d41 import/types.d --- 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; - } -} diff -r f3d91579bb28 -r 1754cb773d41 options --- /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 diff -r f3d91579bb28 -r 1754cb773d41 tk/events.d --- 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; - } -} diff -r f3d91579bb28 -r 1754cb773d41 tk/geometry.d --- 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; - } -} -*/ diff -r f3d91579bb28 -r 1754cb773d41 tk/misc.d --- 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; } -} diff -r f3d91579bb28 -r 1754cb773d41 tk/types.d --- 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; - } -} diff -r f3d91579bb28 -r 1754cb773d41 undo_manager.d --- 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 _actions; - State _state; - } - } - - Transaction _current_transaction; - Stack!(Transaction) _past; - Stack!(Transaction) _future; - Set!(IUndoObserver) _observers; - } -}