]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.image.ui/src/org/simantics/image/ui/editor/ImageEditor.java
Fixed all line endings of the repository
[simantics/platform.git] / bundles / org.simantics.image.ui / src / org / simantics / image / ui / editor / ImageEditor.java
index d8dbe08846570d1e2f48c0758881ef5c80845577..2539dbd70b37d5886a48ef4113c6c6ce9144e039 100644 (file)
-/*******************************************************************************\r
- * Copyright (c) 2007- VTT Technical Research Centre of Finland.\r
- * All rights reserved. This program and the accompanying materials\r
- * are made available under the terms of the Eclipse Public License v1.0\r
- * which accompanies this distribution, and is available at\r
- * http://www.eclipse.org/legal/epl-v10.html\r
- *\r
- * Contributors:\r
- *     VTT Technical Research Centre of Finland - initial API and implementation\r
- *******************************************************************************/\r
-package org.simantics.image.ui.editor;\r
-\r
-import java.awt.geom.AffineTransform;\r
-import java.awt.geom.Point2D;\r
-import java.awt.geom.Rectangle2D;\r
-import java.awt.image.BufferedImage;\r
-import java.awt.image.DirectColorModel;\r
-import java.awt.image.IndexColorModel;\r
-import java.awt.image.WritableRaster;\r
-import java.io.ByteArrayInputStream;\r
-\r
-import org.eclipse.jface.layout.GridDataFactory;\r
-import org.eclipse.jface.resource.JFaceResources;\r
-import org.eclipse.jface.resource.LocalResourceManager;\r
-import org.eclipse.jface.resource.ResourceManager;\r
-import org.eclipse.swt.SWT;\r
-import org.eclipse.swt.SWTError;\r
-import org.eclipse.swt.SWTException;\r
-import org.eclipse.swt.graphics.GC;\r
-import org.eclipse.swt.graphics.Image;\r
-import org.eclipse.swt.graphics.ImageData;\r
-import org.eclipse.swt.graphics.PaletteData;\r
-import org.eclipse.swt.graphics.Point;\r
-import org.eclipse.swt.graphics.RGB;\r
-import org.eclipse.swt.graphics.Rectangle;\r
-import org.eclipse.swt.widgets.Canvas;\r
-import org.eclipse.swt.widgets.Composite;\r
-import org.eclipse.swt.widgets.Display;\r
-import org.eclipse.swt.widgets.Event;\r
-import org.eclipse.swt.widgets.Listener;\r
-import org.simantics.Simantics;\r
-import org.simantics.databoard.Bindings;\r
-import org.simantics.db.ReadGraph;\r
-import org.simantics.db.Resource;\r
-import org.simantics.db.common.request.ParametrizedRead;\r
-import org.simantics.db.common.request.ResourceRead;\r
-import org.simantics.db.exception.DatabaseException;\r
-import org.simantics.db.layer0.request.combinations.Combinators;\r
-import org.simantics.image2.ontology.ImageResource;\r
-import org.simantics.scenegraph.ScenegraphUtils;\r
-import org.simantics.scenegraph.utils.GeometryUtils;\r
-import org.simantics.ui.workbench.IResourceEditorInput;\r
-import org.simantics.ui.workbench.ResourceEditorPart;\r
-import org.simantics.ui.workbench.editor.input.InputValidationCombinators;\r
-import org.simantics.utils.ui.ErrorLogger;\r
-import org.simantics.utils.ui.LayoutUtils;\r
-\r
-import com.kitfox.svg.SVGDiagram;\r
-import com.kitfox.svg.SVGException;\r
-import com.kitfox.svg.SVGUniverse;\r
-\r
-/**\r
- * @author Tuukka Lehtonen\r
- */\r
-public class ImageEditor extends ResourceEditorPart {\r
-\r
-    public static final String EDITOR_ID   = "org.simantics.wiki.ui.image.editor";\r
-\r
-    protected boolean          disposed          = false;\r
-\r
-    protected Image            image;\r
-    protected SVGDiagram       svgDiagram;\r
-    private ResourceManager    resourceManager;\r
-    private Canvas             canvas;\r
-\r
-    private double             zoomLevel         = 1;\r
-    private Point2D            previousTranslate = new Point2D.Double();\r
-    private Point2D            translate         = new Point2D.Double();\r
-    private boolean            zoomToFit         = false;\r
-    private SVGUniverse        svgUniverse       = new SVGUniverse();\r
-\r
-    static ImageData convertToSWT(BufferedImage bufferedImage) {\r
-        if (bufferedImage.getColorModel() instanceof DirectColorModel) {\r
-            DirectColorModel colorModel = (DirectColorModel) bufferedImage\r
-                    .getColorModel();\r
-            PaletteData palette = new PaletteData(colorModel.getRedMask(),\r
-                    colorModel.getGreenMask(), colorModel.getBlueMask());\r
-            ImageData data = new ImageData(bufferedImage.getWidth(),\r
-                    bufferedImage.getHeight(), colorModel.getPixelSize(),\r
-                    palette);\r
-            WritableRaster raster = bufferedImage.getRaster();\r
-            int[] pixelArray = new int[4];\r
-            for (int y = 0; y < data.height; y++) {\r
-                for (int x = 0; x < data.width; x++) {\r
-                    raster.getPixel(x, y, pixelArray);\r
-                    int pixel = palette.getPixel(new RGB(pixelArray[0],\r
-                            pixelArray[1], pixelArray[2]));\r
-                    data.setPixel(x, y, pixel);\r
-                }\r
-            }\r
-            return data;\r
-        } else if (bufferedImage.getColorModel() instanceof IndexColorModel) {\r
-            IndexColorModel colorModel = (IndexColorModel) bufferedImage\r
-                    .getColorModel();\r
-            int size = colorModel.getMapSize();\r
-            byte[] reds = new byte[size];\r
-            byte[] greens = new byte[size];\r
-            byte[] blues = new byte[size];\r
-            colorModel.getReds(reds);\r
-            colorModel.getGreens(greens);\r
-            colorModel.getBlues(blues);\r
-            RGB[] rgbs = new RGB[size];\r
-            for (int i = 0; i < rgbs.length; i++) {\r
-                rgbs[i] = new RGB(reds[i] & 0xFF, greens[i] & 0xFF,\r
-                        blues[i] & 0xFF);\r
-            }\r
-            PaletteData palette = new PaletteData(rgbs);\r
-            ImageData data = new ImageData(bufferedImage.getWidth(),\r
-                    bufferedImage.getHeight(), colorModel.getPixelSize(),\r
-                    palette);\r
-            data.transparentPixel = colorModel.getTransparentPixel();\r
-            WritableRaster raster = bufferedImage.getRaster();\r
-            int[] pixelArray = new int[1];\r
-            for (int y = 0; y < data.height; y++) {\r
-                for (int x = 0; x < data.width; x++) {\r
-                    raster.getPixel(x, y, pixelArray);\r
-                    data.setPixel(x, y, pixelArray[0]);\r
-                }\r
-            }\r
-            return data;\r
-        }\r
-        return null;\r
-    }\r
-\r
-    ParametrizedRead<IResourceEditorInput, Boolean> INPUT_VALIDATOR =\r
-            Combinators.compose(\r
-                    InputValidationCombinators.hasURI(),\r
-                    InputValidationCombinators.extractInputResource()\r
-            );\r
-\r
-    @Override\r
-    protected ParametrizedRead<IResourceEditorInput, Boolean> getInputValidator() {\r
-        return INPUT_VALIDATOR;\r
-    }\r
-\r
-    @Override\r
-    public void createPartControl(final Composite parent) {\r
-        this.resourceManager = new LocalResourceManager(JFaceResources.getResources(), parent);\r
-\r
-        parent.setLayout(LayoutUtils.createNoBorderGridLayout(1));\r
-\r
-        canvas = new Canvas(parent, SWT.DOUBLE_BUFFERED);\r
-        canvas.setBackground(resourceManager.createColor(new RGB(255, 255, 255)));\r
-        GridDataFactory.fillDefaults().grab(true, true).applyTo(canvas);\r
-\r
-        canvas.addListener(SWT.Paint, new Listener() {\r
-            @Override\r
-            public void handleEvent(Event event) {\r
-                if (svgDiagram != null) {\r
-                    Rectangle2D r = svgDiagram.getViewRect();\r
-                    if (r.isEmpty())\r
-                        return;\r
-\r
-                    // FIXME: this is unsafe, renders unnecessarily large\r
-                    // buffered images when only control size is ultimately needed.\r
-\r
-                    AffineTransform tr = AffineTransform.getScaleInstance(zoomLevel, zoomLevel);\r
-                    tr.translate(translate.getX(), translate.getY());\r
-                    try {\r
-                        BufferedImage bi = ScenegraphUtils.paintSVG(svgDiagram, tr, 0f);\r
-                        Image img = new Image(parent.getDisplay(), convertToSWT(bi));\r
-                        drawImage(event.gc, img, false);\r
-                        img.dispose();\r
-                    } catch (SVGException e) {\r
-                    }\r
-                } else if (image != null) {\r
-                    drawImage(event.gc, image, true);\r
-                }\r
-            }\r
-            private void drawImage(GC gc, Image image, boolean fitToCanvas) {\r
-                Rectangle r = image.getBounds();\r
-                if (r.isEmpty())\r
-                    return;\r
-\r
-                Point destSize = canvas.getSize();\r
-                int xSpace = destSize.x - r.width;\r
-                int ySpace = destSize.y - r.height;\r
-                boolean fitsX = xSpace >= 0;\r
-                boolean fitsY = ySpace >= 0;\r
-                boolean fitsCanvas = fitsX && fitsY;\r
-\r
-                // if the image is larger than the canvas, zoom it to fit\r
-                if ((!fitsCanvas && fitToCanvas) || zoomToFit) {\r
-                    gc.setAntialias(SWT.ON);\r
-\r
-                    // Zoom to fit propertionally\r
-                    int leftMargin = 0;\r
-                    int topMargin = 0;\r
-                    if (xSpace > ySpace) {\r
-                        double yr = (double) destSize.y / r.height;\r
-                        double xo = (int)(r.width * yr);\r
-                        leftMargin = (int) Math.round(((destSize.x - r.width) + (r.width - xo)) * 0.5);\r
-                        gc.drawImage(image, 0, 0, r.width, r.height, leftMargin, topMargin, (int) xo, destSize.y);\r
-                    } else {\r
-                        double xr = (double) destSize.x / r.width;\r
-                        double yo = (int)(r.height * xr);\r
-                        topMargin = (int) Math.round(((destSize.y - r.height) + (r.height - yo)) * 0.5);\r
-                        gc.drawImage(image, 0, 0, r.width, r.height, leftMargin, topMargin, destSize.x, (int) yo);\r
-                    }\r
-                } else {\r
-                    if (!fitsCanvas) {\r
-                        int srcX = fitsX ? 0 : (int) Math.round(-xSpace * 0.5);\r
-                        int srcY = fitsY ? 0 : (int) Math.round(-ySpace * 0.5);\r
-                        int srcW = fitsX ? r.width : r.width + xSpace;\r
-                        int srcH = fitsY ? r.height : r.height + ySpace;\r
-                        int destX = fitsX ? (int) Math.round(xSpace * 0.5) : 0;\r
-                        int destY = fitsY ? (int) Math.round(ySpace * 0.5) : 0;\r
-                        int destW = fitsX ? r.width : destSize.x;\r
-                        int destH = fitsY ? r.height : destSize.y;\r
-                        gc.drawImage(image, srcX, srcY, srcW, srcH, destX, destY, destW, destH);\r
-                    } else {\r
-                        // Center on the canvas.\r
-                        int leftMargin = (int) Math.round((destSize.x - r.width) * 0.5);\r
-                        int topMargin = (int) Math.round((destSize.y - r.height) * 0.5);\r
-                        gc.drawImage(image, 0, 0, r.width, r.height, leftMargin, topMargin, r.width, r.height);\r
-                    }\r
-                }\r
-            }\r
-        });\r
-        canvas.addListener(SWT.MouseDoubleClick, new Listener() {\r
-            @Override\r
-            public void handleEvent(Event event) {\r
-                zoomToFit ^= true;\r
-                canvas.redraw();\r
-            }\r
-        });\r
-\r
-        // Mouse tracking support\r
-        Listener listener = new Listener() {\r
-            boolean pan = false;\r
-            Point panStart = new Point(0, 0); \r
-            @Override\r
-            public void handleEvent(Event e) {\r
-                switch (e.type) {\r
-                    case SWT.MouseUp:\r
-                        if (e.button == 3) {\r
-                            pan = false;\r
-                            previousTranslate.setLocation(translate);\r
-                        }\r
-                        break;\r
-                    case SWT.MouseDown:\r
-                        if (e.button == 3) {\r
-                            panStart.x = e.x;\r
-                            panStart.y = e.y;\r
-                            pan = true;\r
-                            previousTranslate.setLocation(translate);\r
-                        }\r
-                        break;\r
-                    case SWT.MouseMove:\r
-                        if (pan) {\r
-                            int dx = e.x - panStart.x;\r
-                            int dy = e.y - panStart.y;\r
-                            translate.setLocation(\r
-                                    previousTranslate.getX() + dx / zoomLevel,\r
-                                    previousTranslate.getY() + dy / zoomLevel);\r
-                            canvas.redraw();\r
-                        }\r
-                        break;\r
-                    case SWT.MouseVerticalWheel:\r
-                        double scroll = Math.min(0.9, -e.count / 20.0);\r
-                        double z = 1 - scroll;\r
-                        zoomLevel = limitScaleFactor(zoomLevel, z);\r
-                        canvas.redraw();\r
-                        break;\r
-                }\r
-            }\r
-            private double limitScaleFactor(double zoomLevel, double scaleFactor) {\r
-                double inLimit = 200.0;\r
-                double outLimit = 10;\r
-\r
-                AffineTransform view = AffineTransform.getScaleInstance(zoomLevel, zoomLevel);\r
-                double currentScale = GeometryUtils.getScale(view) * 100.0;\r
-                double newScale = currentScale * scaleFactor;\r
-\r
-                if (newScale > currentScale && newScale > inLimit) {\r
-                    if (currentScale < inLimit)\r
-                        scaleFactor = inLimit / currentScale;\r
-                    else\r
-                        return inLimit / 100.0;\r
-                } else if (newScale < currentScale && newScale < outLimit) {\r
-                    if (currentScale > outLimit)\r
-                        scaleFactor = outLimit / currentScale;\r
-                    else\r
-                        return outLimit / 100.0;\r
-                }\r
-                return zoomLevel * scaleFactor;\r
-            }\r
-        };\r
-        canvas.addListener(SWT.MouseUp, listener);\r
-        canvas.addListener(SWT.MouseDown, listener);\r
-        canvas.addListener(SWT.MouseMove, listener);\r
-        canvas.addListener(SWT.MouseWheel, listener);\r
-\r
-        // Start tracking editor input validity.\r
-        activateValidation();\r
-\r
-        loadAndTrackInput();\r
-    }\r
-\r
-    private void loadAndTrackInput() {\r
-        final Resource input = getInputResource();\r
-        Simantics.getSession().asyncRequest(new ResourceRead<Object>(input) {\r
-            @Override\r
-            public Object perform(ReadGraph graph) throws DatabaseException {\r
-                ImageResource img = ImageResource.getInstance(graph);\r
-                if (graph.isInstanceOf(input, img.SvgImage)) {\r
-                    String text = graph.getPossibleValue(input, Bindings.STRING);\r
-                    return text;\r
-                } else if (graph.isInstanceOf(input, img.Image)) {\r
-                    byte data[] = graph.getPossibleValue(input, Bindings.BYTE_ARRAY);\r
-                    return data;\r
-                }\r
-                return null;\r
-            }\r
-        }, new org.simantics.db.procedure.Listener<Object>() {\r
-            @Override\r
-            public void execute(Object result) {\r
-                if (result instanceof String) {\r
-                    // svg text\r
-                    try {\r
-                        svgDiagram = ScenegraphUtils.loadSVGDiagram(svgUniverse, (String) result);\r
-                        scheduleRedraw();\r
-                    } catch (SVGException e) {\r
-                        ErrorLogger.defaultLogError(e);\r
-                    }\r
-                } else if (result instanceof byte[]) {\r
-                    // bitmap image data\r
-                    Display display = canvas.getDisplay();\r
-                    if (!display.isDisposed()) {\r
-                        try {\r
-                            image = new Image(canvas.getDisplay(), new ByteArrayInputStream((byte[]) result));\r
-                            scheduleRedraw();\r
-                        } catch (SWTException e) {\r
-                            ErrorLogger.defaultLogError(e);\r
-                        } catch (SWTError e) {\r
-                            ErrorLogger.defaultLogError(e);\r
-                        }\r
-                    }\r
-                }\r
-            }\r
-            private void scheduleRedraw() {\r
-                Display d = canvas.getDisplay();\r
-                if (!d.isDisposed())\r
-                    d.asyncExec(new Runnable() {\r
-                        @Override\r
-                        public void run() {\r
-                            if (!canvas.isDisposed())\r
-                                canvas.redraw();\r
-                        }\r
-                    });\r
-            }\r
-            @Override\r
-            public void exception(Throwable t) {\r
-                ErrorLogger.defaultLogError(t);\r
-            }\r
-            @Override\r
-            public boolean isDisposed() {\r
-                return disposed;\r
-            }\r
-        });\r
-    }\r
-\r
-    @Override\r
-    public void dispose() {\r
-        disposed = true;\r
-        if (image != null) {\r
-            image.dispose();\r
-            image = null;\r
-        }\r
-        svgUniverse.clear();\r
-    }\r
-\r
-    @Override\r
-    public void setFocus() {\r
-        if (canvas != null)\r
-            canvas.setFocus();\r
-    }\r
-\r
-    @SuppressWarnings("rawtypes")\r
-    @Override\r
-    public Object getAdapter(Class adapter) {\r
-        return null;\r
-    }\r
-\r
-}\r
+/*******************************************************************************
+ * Copyright (c) 2007- VTT Technical Research Centre of Finland.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     VTT Technical Research Centre of Finland - initial API and implementation
+ *******************************************************************************/
+package org.simantics.image.ui.editor;
+
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.awt.image.BufferedImage;
+import java.awt.image.DirectColorModel;
+import java.awt.image.IndexColorModel;
+import java.awt.image.WritableRaster;
+import java.io.ByteArrayInputStream;
+
+import org.eclipse.jface.layout.GridDataFactory;
+import org.eclipse.jface.resource.JFaceResources;
+import org.eclipse.jface.resource.LocalResourceManager;
+import org.eclipse.jface.resource.ResourceManager;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.SWTError;
+import org.eclipse.swt.SWTException;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.ImageData;
+import org.eclipse.swt.graphics.PaletteData;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.RGB;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.widgets.Canvas;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.Listener;
+import org.simantics.Simantics;
+import org.simantics.databoard.Bindings;
+import org.simantics.db.ReadGraph;
+import org.simantics.db.Resource;
+import org.simantics.db.common.request.ParametrizedRead;
+import org.simantics.db.common.request.ResourceRead;
+import org.simantics.db.exception.DatabaseException;
+import org.simantics.db.layer0.request.combinations.Combinators;
+import org.simantics.image2.ontology.ImageResource;
+import org.simantics.scenegraph.ScenegraphUtils;
+import org.simantics.scenegraph.utils.GeometryUtils;
+import org.simantics.ui.workbench.IResourceEditorInput;
+import org.simantics.ui.workbench.ResourceEditorPart;
+import org.simantics.ui.workbench.editor.input.InputValidationCombinators;
+import org.simantics.utils.ui.ErrorLogger;
+import org.simantics.utils.ui.LayoutUtils;
+
+import com.kitfox.svg.SVGDiagram;
+import com.kitfox.svg.SVGException;
+import com.kitfox.svg.SVGUniverse;
+
+/**
+ * @author Tuukka Lehtonen
+ */
+public class ImageEditor extends ResourceEditorPart {
+
+    public static final String EDITOR_ID   = "org.simantics.wiki.ui.image.editor";
+
+    protected boolean          disposed          = false;
+
+    protected Image            image;
+    protected SVGDiagram       svgDiagram;
+    private ResourceManager    resourceManager;
+    private Canvas             canvas;
+
+    private double             zoomLevel         = 1;
+    private Point2D            previousTranslate = new Point2D.Double();
+    private Point2D            translate         = new Point2D.Double();
+    private boolean            zoomToFit         = false;
+    private SVGUniverse        svgUniverse       = new SVGUniverse();
+
+    static ImageData convertToSWT(BufferedImage bufferedImage) {
+        if (bufferedImage.getColorModel() instanceof DirectColorModel) {
+            DirectColorModel colorModel = (DirectColorModel) bufferedImage
+                    .getColorModel();
+            PaletteData palette = new PaletteData(colorModel.getRedMask(),
+                    colorModel.getGreenMask(), colorModel.getBlueMask());
+            ImageData data = new ImageData(bufferedImage.getWidth(),
+                    bufferedImage.getHeight(), colorModel.getPixelSize(),
+                    palette);
+            WritableRaster raster = bufferedImage.getRaster();
+            int[] pixelArray = new int[4];
+            for (int y = 0; y < data.height; y++) {
+                for (int x = 0; x < data.width; x++) {
+                    raster.getPixel(x, y, pixelArray);
+                    int pixel = palette.getPixel(new RGB(pixelArray[0],
+                            pixelArray[1], pixelArray[2]));
+                    data.setPixel(x, y, pixel);
+                }
+            }
+            return data;
+        } else if (bufferedImage.getColorModel() instanceof IndexColorModel) {
+            IndexColorModel colorModel = (IndexColorModel) bufferedImage
+                    .getColorModel();
+            int size = colorModel.getMapSize();
+            byte[] reds = new byte[size];
+            byte[] greens = new byte[size];
+            byte[] blues = new byte[size];
+            colorModel.getReds(reds);
+            colorModel.getGreens(greens);
+            colorModel.getBlues(blues);
+            RGB[] rgbs = new RGB[size];
+            for (int i = 0; i < rgbs.length; i++) {
+                rgbs[i] = new RGB(reds[i] & 0xFF, greens[i] & 0xFF,
+                        blues[i] & 0xFF);
+            }
+            PaletteData palette = new PaletteData(rgbs);
+            ImageData data = new ImageData(bufferedImage.getWidth(),
+                    bufferedImage.getHeight(), colorModel.getPixelSize(),
+                    palette);
+            data.transparentPixel = colorModel.getTransparentPixel();
+            WritableRaster raster = bufferedImage.getRaster();
+            int[] pixelArray = new int[1];
+            for (int y = 0; y < data.height; y++) {
+                for (int x = 0; x < data.width; x++) {
+                    raster.getPixel(x, y, pixelArray);
+                    data.setPixel(x, y, pixelArray[0]);
+                }
+            }
+            return data;
+        }
+        return null;
+    }
+
+    ParametrizedRead<IResourceEditorInput, Boolean> INPUT_VALIDATOR =
+            Combinators.compose(
+                    InputValidationCombinators.hasURI(),
+                    InputValidationCombinators.extractInputResource()
+            );
+
+    @Override
+    protected ParametrizedRead<IResourceEditorInput, Boolean> getInputValidator() {
+        return INPUT_VALIDATOR;
+    }
+
+    @Override
+    public void createPartControl(final Composite parent) {
+        this.resourceManager = new LocalResourceManager(JFaceResources.getResources(), parent);
+
+        parent.setLayout(LayoutUtils.createNoBorderGridLayout(1));
+
+        canvas = new Canvas(parent, SWT.DOUBLE_BUFFERED);
+        canvas.setBackground(resourceManager.createColor(new RGB(255, 255, 255)));
+        GridDataFactory.fillDefaults().grab(true, true).applyTo(canvas);
+
+        canvas.addListener(SWT.Paint, new Listener() {
+            @Override
+            public void handleEvent(Event event) {
+                if (svgDiagram != null) {
+                    Rectangle2D r = svgDiagram.getViewRect();
+                    if (r.isEmpty())
+                        return;
+
+                    // FIXME: this is unsafe, renders unnecessarily large
+                    // buffered images when only control size is ultimately needed.
+
+                    AffineTransform tr = AffineTransform.getScaleInstance(zoomLevel, zoomLevel);
+                    tr.translate(translate.getX(), translate.getY());
+                    try {
+                        BufferedImage bi = ScenegraphUtils.paintSVG(svgDiagram, tr, 0f);
+                        Image img = new Image(parent.getDisplay(), convertToSWT(bi));
+                        drawImage(event.gc, img, false);
+                        img.dispose();
+                    } catch (SVGException e) {
+                    }
+                } else if (image != null) {
+                    drawImage(event.gc, image, true);
+                }
+            }
+            private void drawImage(GC gc, Image image, boolean fitToCanvas) {
+                Rectangle r = image.getBounds();
+                if (r.isEmpty())
+                    return;
+
+                Point destSize = canvas.getSize();
+                int xSpace = destSize.x - r.width;
+                int ySpace = destSize.y - r.height;
+                boolean fitsX = xSpace >= 0;
+                boolean fitsY = ySpace >= 0;
+                boolean fitsCanvas = fitsX && fitsY;
+
+                // if the image is larger than the canvas, zoom it to fit
+                if ((!fitsCanvas && fitToCanvas) || zoomToFit) {
+                    gc.setAntialias(SWT.ON);
+
+                    // Zoom to fit propertionally
+                    int leftMargin = 0;
+                    int topMargin = 0;
+                    if (xSpace > ySpace) {
+                        double yr = (double) destSize.y / r.height;
+                        double xo = (int)(r.width * yr);
+                        leftMargin = (int) Math.round(((destSize.x - r.width) + (r.width - xo)) * 0.5);
+                        gc.drawImage(image, 0, 0, r.width, r.height, leftMargin, topMargin, (int) xo, destSize.y);
+                    } else {
+                        double xr = (double) destSize.x / r.width;
+                        double yo = (int)(r.height * xr);
+                        topMargin = (int) Math.round(((destSize.y - r.height) + (r.height - yo)) * 0.5);
+                        gc.drawImage(image, 0, 0, r.width, r.height, leftMargin, topMargin, destSize.x, (int) yo);
+                    }
+                } else {
+                    if (!fitsCanvas) {
+                        int srcX = fitsX ? 0 : (int) Math.round(-xSpace * 0.5);
+                        int srcY = fitsY ? 0 : (int) Math.round(-ySpace * 0.5);
+                        int srcW = fitsX ? r.width : r.width + xSpace;
+                        int srcH = fitsY ? r.height : r.height + ySpace;
+                        int destX = fitsX ? (int) Math.round(xSpace * 0.5) : 0;
+                        int destY = fitsY ? (int) Math.round(ySpace * 0.5) : 0;
+                        int destW = fitsX ? r.width : destSize.x;
+                        int destH = fitsY ? r.height : destSize.y;
+                        gc.drawImage(image, srcX, srcY, srcW, srcH, destX, destY, destW, destH);
+                    } else {
+                        // Center on the canvas.
+                        int leftMargin = (int) Math.round((destSize.x - r.width) * 0.5);
+                        int topMargin = (int) Math.round((destSize.y - r.height) * 0.5);
+                        gc.drawImage(image, 0, 0, r.width, r.height, leftMargin, topMargin, r.width, r.height);
+                    }
+                }
+            }
+        });
+        canvas.addListener(SWT.MouseDoubleClick, new Listener() {
+            @Override
+            public void handleEvent(Event event) {
+                zoomToFit ^= true;
+                canvas.redraw();
+            }
+        });
+
+        // Mouse tracking support
+        Listener listener = new Listener() {
+            boolean pan = false;
+            Point panStart = new Point(0, 0); 
+            @Override
+            public void handleEvent(Event e) {
+                switch (e.type) {
+                    case SWT.MouseUp:
+                        if (e.button == 3) {
+                            pan = false;
+                            previousTranslate.setLocation(translate);
+                        }
+                        break;
+                    case SWT.MouseDown:
+                        if (e.button == 3) {
+                            panStart.x = e.x;
+                            panStart.y = e.y;
+                            pan = true;
+                            previousTranslate.setLocation(translate);
+                        }
+                        break;
+                    case SWT.MouseMove:
+                        if (pan) {
+                            int dx = e.x - panStart.x;
+                            int dy = e.y - panStart.y;
+                            translate.setLocation(
+                                    previousTranslate.getX() + dx / zoomLevel,
+                                    previousTranslate.getY() + dy / zoomLevel);
+                            canvas.redraw();
+                        }
+                        break;
+                    case SWT.MouseVerticalWheel:
+                        double scroll = Math.min(0.9, -e.count / 20.0);
+                        double z = 1 - scroll;
+                        zoomLevel = limitScaleFactor(zoomLevel, z);
+                        canvas.redraw();
+                        break;
+                }
+            }
+            private double limitScaleFactor(double zoomLevel, double scaleFactor) {
+                double inLimit = 200.0;
+                double outLimit = 10;
+
+                AffineTransform view = AffineTransform.getScaleInstance(zoomLevel, zoomLevel);
+                double currentScale = GeometryUtils.getScale(view) * 100.0;
+                double newScale = currentScale * scaleFactor;
+
+                if (newScale > currentScale && newScale > inLimit) {
+                    if (currentScale < inLimit)
+                        scaleFactor = inLimit / currentScale;
+                    else
+                        return inLimit / 100.0;
+                } else if (newScale < currentScale && newScale < outLimit) {
+                    if (currentScale > outLimit)
+                        scaleFactor = outLimit / currentScale;
+                    else
+                        return outLimit / 100.0;
+                }
+                return zoomLevel * scaleFactor;
+            }
+        };
+        canvas.addListener(SWT.MouseUp, listener);
+        canvas.addListener(SWT.MouseDown, listener);
+        canvas.addListener(SWT.MouseMove, listener);
+        canvas.addListener(SWT.MouseWheel, listener);
+
+        // Start tracking editor input validity.
+        activateValidation();
+
+        loadAndTrackInput();
+    }
+
+    private void loadAndTrackInput() {
+        final Resource input = getInputResource();
+        Simantics.getSession().asyncRequest(new ResourceRead<Object>(input) {
+            @Override
+            public Object perform(ReadGraph graph) throws DatabaseException {
+                ImageResource img = ImageResource.getInstance(graph);
+                if (graph.isInstanceOf(input, img.SvgImage)) {
+                    String text = graph.getPossibleValue(input, Bindings.STRING);
+                    return text;
+                } else if (graph.isInstanceOf(input, img.Image)) {
+                    byte data[] = graph.getPossibleValue(input, Bindings.BYTE_ARRAY);
+                    return data;
+                }
+                return null;
+            }
+        }, new org.simantics.db.procedure.Listener<Object>() {
+            @Override
+            public void execute(Object result) {
+                if (result instanceof String) {
+                    // svg text
+                    try {
+                        svgDiagram = ScenegraphUtils.loadSVGDiagram(svgUniverse, (String) result);
+                        scheduleRedraw();
+                    } catch (SVGException e) {
+                        ErrorLogger.defaultLogError(e);
+                    }
+                } else if (result instanceof byte[]) {
+                    // bitmap image data
+                    Display display = canvas.getDisplay();
+                    if (!display.isDisposed()) {
+                        try {
+                            image = new Image(canvas.getDisplay(), new ByteArrayInputStream((byte[]) result));
+                            scheduleRedraw();
+                        } catch (SWTException e) {
+                            ErrorLogger.defaultLogError(e);
+                        } catch (SWTError e) {
+                            ErrorLogger.defaultLogError(e);
+                        }
+                    }
+                }
+            }
+            private void scheduleRedraw() {
+                Display d = canvas.getDisplay();
+                if (!d.isDisposed())
+                    d.asyncExec(new Runnable() {
+                        @Override
+                        public void run() {
+                            if (!canvas.isDisposed())
+                                canvas.redraw();
+                        }
+                    });
+            }
+            @Override
+            public void exception(Throwable t) {
+                ErrorLogger.defaultLogError(t);
+            }
+            @Override
+            public boolean isDisposed() {
+                return disposed;
+            }
+        });
+    }
+
+    @Override
+    public void dispose() {
+        disposed = true;
+        if (image != null) {
+            image.dispose();
+            image = null;
+        }
+        svgUniverse.clear();
+    }
+
+    @Override
+    public void setFocus() {
+        if (canvas != null)
+            canvas.setFocus();
+    }
+
+    @SuppressWarnings("rawtypes")
+    @Override
+    public Object getAdapter(Class adapter) {
+        return null;
+    }
+
+}