view canvas.d @ 7:936feb16eed4

Checkpoint
author "David Bryant <bagnose@gmail.com>"
date Sat, 11 Jul 2009 21:29:03 +0930
parents 8a39b13cd3e6
children 66b47e122b31
line wrap: on
line source

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;
import cairo_support;

// x and y run right and up respectively

class Canvas : Table, ICanvas {
    static this() {
        ORIGIN = Point(0.0, 0.0);
        INITIAL_PAGE_SIZE = 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;
        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(&onButtonPress);
        mDrawingArea.addOnButtonRelease(&onButtonRelease);
        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.25, 0.0, 1.0, 0.2, 0.5, 0.5);
        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.5);
        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 = Vector(cast(double)event.width, cast(double)event.height);
            update_adjustments();
            update_rulers();


            return true;
        }

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

        bool onButtonPress(GdkEventButton * event, Widget widget) {
            assert(widget is mDrawingArea);
            //writefln("Got button event\n");

            Point screen_point = Point(event.x + 0.5, event.y + 0.5);
            Point model_point = screen_to_model(screen_point);

            auto button_event = new ButtonEvent(gtk2tk_button_action(event.type),
                                                gtk2tk_button_name(event.button),
                                                screen_point,
                                                model_point,
                                                gtk2tk_mask(event.state));

            mEventHandler.handle_button_press(this, button_event);

            return true;
        }

        bool onButtonRelease(GdkEventButton * event, Widget widget) {
            assert(widget is mDrawingArea);
            //writefln("Got button event\n");

            Point screen_point = Point(event.x + 0.5, event.y + 0.5);
            Point model_point = screen_to_model(screen_point);

            auto button_event = new ButtonEvent(gtk2tk_button_action(event.type),
                                                gtk2tk_button_name(event.button),
                                                screen_point,
                                                model_point,
                                                gtk2tk_mask(event.state));

            mEventHandler.handle_button_release(this, button_event);

            return true;
        }

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

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

        bool onScroll(GdkEventScroll * event, Widget widget) {
            assert(widget is mDrawingArea);
            //writefln("Got scroll\n");

            Point screen_point = 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 true;
        }

        void onValueChanged(Adjustment adjustment) {
            GtkAdjustment * h_gtkAdjustment = mHAdjustment.getAdjustmentStruct();
            GtkAdjustment * v_gtkAdjustment = mVAdjustment.getAdjustmentStruct();

            Point view_left_bottom = Point(gtk_adjustment_get_value(h_gtkAdjustment),
                                             gtk_adjustment_get_value(v_gtkAdjustment));
            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();

            gtk_adjustment_set_lower(h_gtkAdjustment, mCanvasLeftBottom.x);
            gtk_adjustment_set_upper(h_gtkAdjustment, mCanvasRightTop.x);
            gtk_adjustment_set_value(h_gtkAdjustment, view_left_bottom.x);
            gtk_adjustment_set_step_increment(h_gtkAdjustment, canvas_size.x / 10.0);
            gtk_adjustment_set_page_increment(h_gtkAdjustment, canvas_size.x / 5.0);
            gtk_adjustment_set_page_size(h_gtkAdjustment, model_size.x);

            gtk_adjustment_set_lower(v_gtkAdjustment, mCanvasLeftBottom.y);
            gtk_adjustment_set_upper(v_gtkAdjustment, mCanvasRightTop.y);
            gtk_adjustment_set_value(v_gtkAdjustment, view_left_bottom.y);
            gtk_adjustment_set_step_increment(v_gtkAdjustment, canvas_size.y / 10.0);
            gtk_adjustment_set_page_increment(v_gtkAdjustment, canvas_size.y / 5.0);
            gtk_adjustment_set_page_size(v_gtkAdjustment, 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;
    }
}