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