You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@isis.apache.org by da...@apache.org on 2012/12/08 15:56:01 UTC

[11/53] [partial] ISIS-188: making structure of component viewers consistent

http://git-wip-us.apache.org/repos/asf/isis/blob/eb613703/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/dialog/ParametersLabelDecorator.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/dialog/ParametersLabelDecorator.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/dialog/ParametersLabelDecorator.java
new file mode 100644
index 0000000..f61b886
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/dialog/ParametersLabelDecorator.java
@@ -0,0 +1,47 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.isis.viewer.dnd.dialog;
+
+import org.apache.isis.viewer.dnd.view.Axes;
+import org.apache.isis.viewer.dnd.view.Content;
+import org.apache.isis.viewer.dnd.view.SubviewDecorator;
+import org.apache.isis.viewer.dnd.view.View;
+import org.apache.isis.viewer.dnd.view.ViewAxis;
+import org.apache.isis.viewer.dnd.view.axis.LabelAxis;
+import org.apache.isis.viewer.dnd.view.border.DroppableLabelBorder;
+import org.apache.isis.viewer.dnd.view.border.LabelBorder;
+
+public class ParametersLabelDecorator implements SubviewDecorator {
+
+    @Override
+    public ViewAxis createAxis(final Content content) {
+        return new LabelAxis();
+    }
+
+    @Override
+    public View decorate(final Axes axes, final View view) {
+        final LabelAxis axis = axes.getAxis(LabelAxis.class);
+        if (view.getContent().isObject() && !view.getContent().isTextParseable()) {
+            return DroppableLabelBorder.createObjectParameterLabelBorder(axis, view);
+        } else {
+            return LabelBorder.createValueParameterLabelBorder(axis, view);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/eb613703/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/drawing/Background.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/drawing/Background.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/drawing/Background.java
new file mode 100644
index 0000000..5cc9037
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/drawing/Background.java
@@ -0,0 +1,27 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.isis.viewer.dnd.drawing;
+
+/**
+ * A strategy for rendering the background of the application window.
+ */
+public interface Background {
+    void draw(Canvas canvas, Size size);
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/eb613703/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/drawing/Bounds.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/drawing/Bounds.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/drawing/Bounds.java
new file mode 100644
index 0000000..2d9f931
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/drawing/Bounds.java
@@ -0,0 +1,330 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.isis.viewer.dnd.drawing;
+
+import org.apache.log4j.Logger;
+
+/**
+ * Bounds represent a rectangular area on the screen. The top-left corner is
+ * represented by the location (available using getLocation(), and getX() and
+ * getY()). The extent of the bounds is specified by its height and width
+ * (available using getHeight() and getWidth()). The bottom-right point is the
+ * offset from the top-left point by width -1 and hieght - 1 pixels.
+ * 
+ * For example a bounds created as follows
+ * 
+ * new Bounds(5, 10, 10, 20)
+ * 
+ * Would represent a rectangle at location (5, 10), with a width of 10 pixels
+ * and a height of 20. Note, hower that the lower-right corner would be at (14,
+ * 29), as there are 10 pixels between pixel 5 and pixel 14, and 20 between 10
+ * and 29.
+ */
+public class Bounds {
+    Logger LOG = Logger.getLogger("Bounds");
+    int x;
+    int y;
+    int height;
+    int width;
+
+    public Bounds() {
+        x = 0;
+        y = 0;
+        width = 0;
+        height = 0;
+    }
+
+    public Bounds(final Bounds bounds) {
+        this(bounds.x, bounds.y, bounds.width, bounds.height);
+    }
+
+    public Bounds(final int x, final int y, final int width, final int height) {
+        super();
+        this.x = x;
+        this.y = y;
+        this.width = width;
+        this.height = height;
+    }
+
+    public Bounds(final Location location, final Size size) {
+        this(location.x, location.y, size.width, size.height);
+    }
+
+    public Bounds(final Size size) {
+        this(0, 0, size.width, size.height);
+    }
+
+    public boolean contains(final Location location) {
+        final int xp = location.getX();
+        final int yp = location.getY();
+        final int xMax = x + width - 1;
+        final int yMax = y + height - 1;
+
+        return xp >= x && xp <= xMax && yp >= y && yp <= yMax;
+    }
+
+    public void contract(final int width, final int height) {
+        this.width -= width;
+        this.height -= height;
+    }
+
+    public void contract(final Padding padding) {
+        height -= padding.top + padding.bottom;
+        width -= padding.left + padding.right;
+        x += padding.left;
+        y += padding.top;
+    }
+
+    public void contract(final Size size) {
+        this.width -= size.width;
+        this.height -= size.height;
+    }
+
+    public void contractHeight(final int height) {
+        this.height -= height;
+    }
+
+    public void contractWidth(final int width) {
+        this.width -= width;
+    }
+
+    public void ensureHeight(final int height) {
+        this.height = Math.max(this.height, height);
+    }
+
+    public void ensureWidth(final int width) {
+        this.width = Math.max(this.width, width);
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if (obj == this) {
+            return true;
+        }
+
+        if (obj instanceof Bounds) {
+            final Bounds b = (Bounds) obj;
+
+            return b.x == x && b.y == y && b.width == width && b.height == height;
+        }
+
+        return false;
+    }
+
+    public void extend(final int width, final int height) {
+        this.width += width;
+        this.height += height;
+    }
+
+    public void extend(final Padding padding) {
+        this.width += padding.getLeftRight();
+        this.height += padding.getTopBottom();
+    }
+
+    public void extend(final Size size) {
+        this.width += size.width;
+        this.height += size.height;
+    }
+
+    public void extendHeight(final int height) {
+        this.height += height;
+    }
+
+    public void extendWidth(final int width) {
+        this.width += width;
+    }
+
+    public int getHeight() {
+        return height;
+    }
+
+    public Location getLocation() {
+        return new Location(x, y);
+    }
+
+    public Size getSize() {
+        return new Size(width, height);
+    }
+
+    public int getWidth() {
+        return width;
+    }
+
+    public int getX() {
+        return x;
+    }
+
+    public int getX2() {
+        return x + width - 1;
+    }
+
+    public int getY() {
+        return y;
+    }
+
+    public int getY2() {
+        return y + height - 1;
+    }
+
+    /**
+     * Determines whether this bounds overlaps the specified bounds. If any area
+     * is shared by the two bounds then this will return true. As the edges of
+     * the bounds are of a finite size the bounds overlap if any of the edges
+     * overlap.
+     */
+    public boolean intersects(final Bounds bounds) {
+        final int tx1 = this.x;
+        final int tx2 = this.x + this.width - 1;
+        final int ox1 = bounds.x;
+        final int ox2 = bounds.x + bounds.width - 1;
+
+        // tx1 < ox1 < tx2 || tx1 < ox2 < tx2
+        final boolean xOverlap = (tx1 <= ox1 && ox1 <= tx2) || (tx1 <= ox2 && ox1 <= tx2) || (ox1 <= tx1 && tx1 <= ox2) || (ox1 <= tx2 && tx1 <= ox2);
+
+        final int ty1 = this.y;
+        final int ty2 = this.y + this.height - 1;
+        final int oy1 = bounds.y;
+        final int oy2 = bounds.y + bounds.height - 1;
+        final boolean yOverlap = (ty1 <= oy1 && oy1 <= ty2) || (ty1 <= oy2 && oy1 <= ty2) || (oy1 <= ty1 && ty1 <= oy2) || (oy1 <= ty2 && ty1 <= oy2);
+        return xOverlap && yOverlap;
+
+    }
+
+    public void limitLocation(final Size bounds) {
+        if (x + width > bounds.width) {
+            x = bounds.width - width;
+        }
+        if (y + height > bounds.height) {
+            y = bounds.height - height;
+        }
+    }
+
+    /**
+     * Limits the specified bounds so that it fits within this bounds.
+     */
+    public boolean limitBounds(final Bounds toLimit) {
+        boolean limited = false;
+        final Location location = toLimit.getLocation();
+        final Size size = toLimit.getSize();
+
+        int viewLeft = location.getX();
+        int viewTop = location.getY();
+        int viewRight = viewLeft + size.getWidth();
+        int viewBottom = viewTop + size.getHeight();
+
+        final Size wd = getSize();
+
+        final int limitLeft = x;
+        final int limitTop = y;
+        final int limitRight = x + width;
+        final int limitBottom = y + height;
+
+        if (viewRight > limitRight) {
+            viewLeft = limitRight - size.getWidth();
+            limited = true;
+            LOG.info("right side oustide limits, moving left to " + viewLeft);
+        }
+
+        if (viewLeft < limitLeft) {
+            viewLeft = limitLeft;
+            limited = true;
+            LOG.info("left side outside limit, moving left to " + viewLeft);
+        }
+
+        if (viewBottom > limitBottom) {
+            viewTop = limitBottom - size.getHeight();
+            limited = true;
+            LOG.info("bottom outside limit, moving top to " + viewTop);
+        }
+
+        if (viewTop < limitTop) {
+            viewTop = limitTop;
+            limited = true;
+            LOG.info("top outside limit, moving top to " + viewTop);
+        }
+
+        toLimit.setX(viewLeft);
+        toLimit.setY(viewTop);
+
+        viewBottom = viewTop + size.getHeight();
+        viewRight = viewLeft + size.getWidth();
+
+        if (viewRight > limitRight) {
+            toLimit.width = wd.width;
+            limited = true;
+            LOG.info("width outside limit, reducing width to " + viewTop);
+        }
+
+        if (viewBottom > limitBottom) {
+            toLimit.height = wd.height;
+            limited = true;
+            LOG.info("height outside limit, reducing height to " + viewTop);
+        }
+
+        if (limited) {
+            LOG.info("limited " + toLimit);
+        }
+        return limited;
+    }
+
+    public void setBounds(final Bounds bounds) {
+        x = bounds.x;
+        y = bounds.y;
+        width = bounds.width;
+        height = bounds.height;
+
+    }
+
+    public void setHeight(final int height) {
+        this.height = height;
+    }
+
+    public void setWidth(final int width) {
+        this.width = width;
+    }
+
+    public void setX(final int x) {
+        this.x = x;
+    }
+
+    public void setY(final int y) {
+        this.y = y;
+    }
+
+    @Override
+    public String toString() {
+        return x + "," + y + " " + width + "x" + height;
+    }
+
+    public void translate(final int x, final int y) {
+        this.x += x;
+        this.y += y;
+    }
+
+    public void union(final Bounds bounds) {
+        final int newX = Math.min(x, bounds.x);
+        final int newY = Math.min(y, bounds.y);
+        width = Math.max(x + width, bounds.x + bounds.width) - newX;
+        height = Math.max(y + height, bounds.y + bounds.height) - newY;
+        x = newX;
+        y = newY;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/eb613703/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/drawing/Canvas.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/drawing/Canvas.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/drawing/Canvas.java
new file mode 100644
index 0000000..4271de1
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/drawing/Canvas.java
@@ -0,0 +1,68 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.isis.viewer.dnd.drawing;
+
+public interface Canvas {
+    Canvas createSubcanvas();
+
+    Canvas createSubcanvas(Bounds bounds);
+
+    Canvas createSubcanvas(int x, int y, int width, int height);
+
+    void draw3DRectangle(int x, int y, int width, int height, Color color, boolean raised);
+
+    void drawDebugOutline(Bounds bounds, int baseline, Color color);
+
+    void drawImage(Image image, int x, int y);
+
+    void drawImage(Image image, int x, int y, int width, int height);
+
+    void drawLine(int x, int y, int x2, int y2, Color color);
+
+    void drawLine(Location start, int xExtent, int yExtent, Color color);
+
+    void drawOval(int x, int y, int width, int height, Color color);
+
+    void drawRectangle(int x, int y, int width, int height, Color color);
+
+    void drawRectangleAround(Bounds bounds, Color color);
+
+    void drawRoundedRectangle(int x, int y, int width, int height, int arcWidth, int arcHeight, Color color);
+
+    void drawShape(Shape shape, Color color);
+
+    void drawShape(Shape shape, int x, int y, Color color);
+
+    void drawSolidOval(int x, int y, int width, int height, Color color);
+
+    void drawSolidRectangle(int x, int y, int width, int height, Color color);
+
+    void drawSolidShape(Shape shape, Color color);
+
+    void drawSolidShape(Shape shape, int x, int y, Color color);
+
+    void drawText(String text, int x, int y, Color color, Text style);
+
+    void drawText(String text, int x, int y, int maxWidth, Color color, Text style);
+
+    void offset(int x, int y);
+
+    boolean overlaps(Bounds bounds);
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/eb613703/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/drawing/Color.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/drawing/Color.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/drawing/Color.java
new file mode 100644
index 0000000..cadf826
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/drawing/Color.java
@@ -0,0 +1,30 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.isis.viewer.dnd.drawing;
+
+public interface Color {
+
+    Color brighter();
+
+    Color darker();
+
+    String getName();
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/eb613703/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/drawing/ColorsAndFonts.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/drawing/ColorsAndFonts.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/drawing/ColorsAndFonts.java
new file mode 100644
index 0000000..5cc4866
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/drawing/ColorsAndFonts.java
@@ -0,0 +1,101 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.isis.viewer.dnd.drawing;
+
+/**
+ * A look-up for font and color details.
+ * 
+ */
+public interface ColorsAndFonts {
+    public final static String COLOR_BLACK = "color.black";
+    public final static String COLOR_WHITE = "color.white";
+    public final static String COLOR_PRIMARY1 = "color.primary1";
+    public final static String COLOR_PRIMARY2 = "color.primary2";
+    public final static String COLOR_PRIMARY3 = "color.primary3";
+    public final static String COLOR_SECONDARY1 = "color.secondary1";
+    public final static String COLOR_SECONDARY2 = "color.secondary2";
+    public final static String COLOR_SECONDARY3 = "color.secondary3";
+
+    // background colors
+    public final static String COLOR_APPLICATION = "color.background.application";
+    public final static String COLOR_WINDOW = "color.background.window";
+    public final static String COLOR_MENU_VALUE = "color.background.menu.value";
+    public final static String COLOR_MENU_CONTENT = "color.background.menu.content";
+    public final static String COLOR_MENU_VIEW = "color.background.menu.view";
+    public final static String COLOR_MENU_WORKSPACE = "color.background.menu.workspace";
+
+    // menu colors
+    public final static String COLOR_MENU = "color.menu.normal";
+    public final static String COLOR_MENU_DISABLED = "color.menu.disabled";
+    public final static String COLOR_MENU_REVERSED = "color.menu.reversed";
+
+    // label colors
+    public final static String COLOR_LABEL = "color.label.normal";
+    public final static String COLOR_LABEL_DISABLED = "color.label.disabled";
+    public final static String COLOR_LABEL_MANDATORY = "color.label.mandatory";
+
+    // state colors
+    public final static String COLOR_IDENTIFIED = "color.identified";
+    public final static String COLOR_VALID = "color.valid";
+    public final static String COLOR_INVALID = "color.invalid";
+    public final static String COLOR_ERROR = "color.error";
+    public final static String COLOR_ACTIVE = "color.active";
+    public final static String COLOR_OUT_OF_SYNC = "color.out-of-sync";
+
+    // text colors
+    public final static String COLOR_TEXT_SAVED = "color.text.saved";
+    public final static String COLOR_TEXT_EDIT = "color.text.edit";
+    public final static String COLOR_TEXT_CURSOR = "color.text.cursor";
+    public final static String COLOR_TEXT_HIGHLIGHT = "color.text.highlight";
+
+    // debug outline colors
+    public final static String COLOR_DEBUG_BASELINE = "color.debug.baseline";
+    public final static String COLOR_DEBUG_BOUNDS_BORDER = "color.debug.bounds.border";
+    public final static String COLOR_DEBUG_BOUNDS_DRAW = "color.debug.bounds.draw";
+    public final static String COLOR_DEBUG_BOUNDS_REPAINT = "color.debug.bounds.repaint";
+    public final static String COLOR_DEBUG_BOUNDS_VIEW = "color.debug.bounds.view";
+
+    // fonts
+    public final static String TEXT_DEFAULT = "text.default";
+    public final static String TEXT_CONTROL = "text.control";
+    public final static String TEXT_TITLE = "text.title";
+    public final static String TEXT_TITLE_SMALL = "text.title.small";
+    public final static String TEXT_DEBUG = "text.debug";
+    public final static String TEXT_STATUS = "text.status";
+    public final static String TEXT_ICON = "text.icon";
+    public final static String TEXT_LABEL = "text.label";
+    public final static String TEXT_LABEL_MANDATORY = "text.label.mandatory";
+    public final static String TEXT_LABEL_DISABLED = "text.label.disabled";
+    public final static String TEXT_MENU = "text.menu";
+    public final static String TEXT_NORMAL = "text.normal";
+
+    int defaultBaseline();
+
+    int defaultFieldHeight();
+
+    Color getColor(int rgbColor);
+
+    Color getColor(String name);
+
+    Text getText(String name);
+
+    void init();
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/eb613703/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/drawing/DebugCanvas.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/drawing/DebugCanvas.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/drawing/DebugCanvas.java
new file mode 100644
index 0000000..7cc7656
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/drawing/DebugCanvas.java
@@ -0,0 +1,187 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.isis.viewer.dnd.drawing;
+
+import org.apache.isis.core.commons.debug.DebugBuilder;
+
+public class DebugCanvas implements Canvas {
+    private final DebugBuilder buffer;
+    private final int level;
+
+    public DebugCanvas(final DebugBuilder buffer, final Bounds bounds) {
+        this(buffer, 0);
+    }
+
+    private DebugCanvas(final DebugBuilder buffer, final int level) {
+        this.level = level;
+        this.buffer = buffer;
+    }
+
+    @Override
+    public Canvas createSubcanvas() {
+        buffer.blankLine();
+        indent();
+        buffer.appendln("Create subcanvas for same area");
+        return new DebugCanvas(buffer, level + 1);
+    }
+
+    @Override
+    public Canvas createSubcanvas(final Bounds bounds) {
+        return createSubcanvas(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight());
+    }
+
+    @Override
+    public Canvas createSubcanvas(final int x, final int y, final int width, final int height) {
+        buffer.blankLine();
+        indent();
+        buffer.appendln("Create subcanvas for area " + x + "," + y + " " + width + "x" + height);
+        return new DebugCanvas(buffer, level + 1);
+    }
+
+    @Override
+    public void draw3DRectangle(final int x, final int y, final int width, final int height, final Color color, final boolean raised) {
+        indent();
+        buffer.appendln("Rectangle (3D) " + x + "," + y + " " + width + "x" + height);
+    }
+
+    @Override
+    public void drawImage(final Image image, final int x, final int y) {
+        indent();
+        buffer.appendln("Icon " + x + "," + y + " " + image.getWidth() + "x" + image.getHeight());
+    }
+
+    @Override
+    public void drawImage(final Image image, final int x, final int y, final int width, final int height) {
+        indent();
+        buffer.appendln("Icon " + x + "," + y + " " + width + "x" + height);
+    }
+
+    @Override
+    public void drawLine(final int x, final int y, final int x2, final int y2, final Color color) {
+        indent();
+        buffer.appendln("Line from " + x + "," + y + " to " + x2 + "," + y2 + " " + color);
+    }
+
+    @Override
+    public void drawLine(final Location start, final int xExtent, final int yExtent, final Color color) {
+        indent();
+        buffer.appendln("Line from " + start.getX() + "," + start.getY() + " to " + (start.getX() + xExtent) + "," + (start.getY() + yExtent) + " " + color);
+    }
+
+    @Override
+    public void drawOval(final int x, final int y, final int width, final int height, final Color color) {
+        indent();
+        buffer.appendln("Oval " + x + "," + y + " " + width + "x" + height + " " + color);
+    }
+
+    @Override
+    public void drawRectangle(final int x, final int y, final int width, final int height, final Color color) {
+        indent();
+        buffer.appendln("Rectangle " + x + "," + y + " " + width + "x" + height + " " + color);
+    }
+
+    @Override
+    public void drawRectangleAround(final Bounds bounds, final Color color) {
+        indent();
+        buffer.appendln("Rectangle 0,0 " + bounds.getWidth() + "x" + bounds.getHeight() + " " + color);
+    }
+
+    @Override
+    public void drawRoundedRectangle(final int x, final int y, final int width, final int height, final int arcWidth, final int arcHeight, final Color color) {
+        indent();
+        buffer.appendln("Rounded Rectangle " + x + "," + y + " " + (x + width) + "x" + (y + height) + " " + color);
+    }
+
+    @Override
+    public void drawShape(final Shape shape, final Color color) {
+        indent();
+        buffer.appendln("Shape " + shape + " " + color);
+    }
+
+    @Override
+    public void drawShape(final Shape shape, final int x, final int y, final Color color) {
+        indent();
+        buffer.appendln("Shape " + shape + " at " + x + "/" + y + " (left, top)" + " " + color);
+    }
+
+    @Override
+    public void drawSolidOval(final int x, final int y, final int width, final int height, final Color color) {
+        indent();
+        buffer.appendln("Oval (solid) " + x + "," + y + " " + width + "x" + height + " " + color);
+    }
+
+    @Override
+    public void drawSolidRectangle(final int x, final int y, final int width, final int height, final Color color) {
+        indent();
+        buffer.appendln("Rectangle (solid) " + x + "," + y + " " + width + "x" + height + " " + color);
+    }
+
+    @Override
+    public void drawSolidShape(final Shape shape, final Color color) {
+        indent();
+        buffer.appendln("Shape (solid) " + shape + " " + color);
+    }
+
+    @Override
+    public void drawSolidShape(final Shape shape, final int x, final int y, final Color color) {
+        indent();
+        buffer.appendln("Shape (solid)" + shape + " at " + x + "/" + y + " (left, top)" + " " + color);
+    }
+
+    @Override
+    public void drawText(final String text, final int x, final int y, final Color color, final Text style) {
+        indent();
+        buffer.appendln("Text " + x + "," + y + " \"" + text + "\" " + style + " " + color);
+    }
+
+    @Override
+    public void drawText(final String text, final int x, final int y, final int maxWidth, final Color color, final Text style) {
+        indent();
+        buffer.appendln("Text " + x + "," + y + " +" + maxWidth + "xh \"" + text + "\" " + style + " " + color);
+    }
+
+    private void indent() {
+        // buffer.append("\n");
+        for (int i = 0; i < level; i++) {
+            buffer.append("   ");
+        }
+    }
+
+    @Override
+    public void offset(final int x, final int y) {
+        indent();
+        buffer.appendln("Offset by " + x + "/" + y + " (left, top)");
+    }
+
+    @Override
+    public boolean overlaps(final Bounds bounds) {
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        return "Canvas";
+    }
+
+    @Override
+    public void drawDebugOutline(final Bounds bounds, final int baseline, final Color color) {
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/eb613703/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/drawing/DebugCanvasAbsolute.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/drawing/DebugCanvasAbsolute.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/drawing/DebugCanvasAbsolute.java
new file mode 100644
index 0000000..e9fba46
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/drawing/DebugCanvasAbsolute.java
@@ -0,0 +1,257 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.isis.viewer.dnd.drawing;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.StringTokenizer;
+
+import org.apache.isis.core.commons.debug.DebugBuilder;
+
+public class DebugCanvasAbsolute implements Canvas {
+    private final DebugBuilder buffer;
+    private final int level;
+    private int offsetX;
+    private int offsetY;
+
+    public DebugCanvasAbsolute(final DebugBuilder buffer, final Bounds bounds) {
+        this(buffer, 0, bounds.getX(), bounds.getY());
+    }
+
+    private DebugCanvasAbsolute(final DebugBuilder buffer, final int level, final int x, final int y) {
+        this.level = level;
+        this.buffer = buffer;
+        offsetX = x;
+        offsetY = y;
+    }
+
+    @Override
+    public Canvas createSubcanvas() {
+        buffer.blankLine();
+        indent();
+        buffer.appendln("Create subcanvas for same area");
+        return new DebugCanvasAbsolute(buffer, level + 1, offsetX, offsetY);
+    }
+
+    @Override
+    public Canvas createSubcanvas(final Bounds bounds) {
+        return createSubcanvas(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight());
+    }
+
+    @Override
+    public Canvas createSubcanvas(final int x, final int y, final int width, final int height) {
+        // buffer.blankLine();
+        indent();
+        final int dx = offsetX + x;
+        final int qx = dx + width - 1;
+        final int dy = offsetY + y;
+        final int qy = dy + height - 1;
+        buffer.appendln("Canvas " + dx + "," + dy + " " + width + "x" + height + " (" + qx + "," + qy + ") " + line());
+        // buffer.appendln(line());
+        return new DebugCanvasAbsolute(buffer, level + 1, dx, dy);
+    }
+
+    @Override
+    public void draw3DRectangle(final int x, final int y, final int width, final int height, final Color color, final boolean raised) {
+        indent();
+        final int px = offsetX + x;
+        final int py = offsetY + y;
+        final int qx = px + width - 1;
+        final int qy = py + height - 1;
+        buffer.appendln("Rectangle (3D) " + px + "," + py + " " + width + "x" + height + " (" + qx + "," + qy + ") " + line());
+    }
+
+    @Override
+    public void drawImage(final Image image, final int x, final int y) {
+        indent();
+        final int px = offsetX + x;
+        final int py = offsetY + y;
+        final int qx = px + image.getWidth() - 1;
+        final int qy = py + image.getHeight() - 1;
+        buffer.appendln("Icon " + px + "," + py + " " + image.getWidth() + "x" + image.getHeight() + " (" + qx + "," + qy + ") " + line());
+    }
+
+    @Override
+    public void drawImage(final Image image, final int x, final int y, final int width, final int height) {
+        indent();
+        final int px = offsetX + x;
+        final int py = offsetY + y;
+        final int qx = px + width - 1;
+        final int qy = py + height - 1;
+        buffer.appendln("Icon " + px + "," + py + " " + width + "x" + height + " (" + qx + "," + qy + ") " + line());
+    }
+
+    @Override
+    public void drawLine(final int x, final int y, final int x2, final int y2, final Color color) {
+        indent();
+        final int px = offsetX + x;
+        final int py = offsetY + y;
+        final int qx = offsetX + x2;
+        final int qy = offsetY + y2;
+        buffer.appendln("Line from " + px + "," + py + " to " + qx + "," + qy + " " + color + line());
+    }
+
+    @Override
+    public void drawLine(final Location start, final int xExtent, final int yExtent, final Color color) {
+        indent();
+        buffer.appendln("Line from " + start.getX() + "," + start.getY() + " to " + (start.getX() + xExtent) + "," + (start.getY() + yExtent) + " " + color + line());
+    }
+
+    @Override
+    public void drawOval(final int x, final int y, final int width, final int height, final Color color) {
+        indent();
+        final int px = offsetX + x;
+        final int py = offsetY + y;
+        buffer.appendln("Oval " + px + "," + py + " " + width + "x" + height + " " + color + line());
+    }
+
+    @Override
+    public void drawRectangle(final int x, final int y, final int width, final int height, final Color color) {
+        indent();
+        final int px = offsetX + x;
+        final int py = offsetY + y;
+        final int qx = px + width - 1;
+        final int qy = py + height - 1;
+
+        buffer.appendln("Rectangle " + px + "," + py + " " + width + "x" + height + " (" + qx + "," + qy + ") " + color + line());
+    }
+
+    private String line() {
+        final RuntimeException e = new RuntimeException();
+        StringWriter s;
+        final PrintWriter p = new PrintWriter(s = new StringWriter());
+        e.printStackTrace(p);
+        final StringTokenizer st = new StringTokenizer(s.toString(), "\n\r");
+        st.nextElement();
+        st.nextElement();
+        st.nextElement();
+        final String line = st.nextToken();
+        return line.substring(line.indexOf('('));
+    }
+
+    @Override
+    public void drawRectangleAround(final Bounds bounds, final Color color) {
+        indent();
+        buffer.appendln("Rectangle 0,0 " + bounds.getWidth() + "x" + bounds.getHeight() + " " + color + line());
+    }
+
+    @Override
+    public void drawRoundedRectangle(final int x, final int y, final int width, final int height, final int arcWidth, final int arcHeight, final Color color) {
+        indent();
+        final int px = offsetX + x;
+        final int py = offsetY + y;
+        final int qx = px + width - 1;
+        final int qy = py + height - 1;
+        buffer.appendln("Rounded Rectangle " + px + "," + py + " " + width + "x" + height + " (" + qx + "," + qy + ") " + color + line());
+    }
+
+    @Override
+    public void drawShape(final Shape shape, final Color color) {
+        indent();
+        buffer.appendln("Shape " + shape + " " + color);
+    }
+
+    @Override
+    public void drawShape(final Shape shape, final int x, final int y, final Color color) {
+        indent();
+        final int px = offsetX + x;
+        final int py = offsetY + y;
+        buffer.appendln("Shape " + shape + " at " + px + "," + py + " (left, top)" + " " + color + line());
+    }
+
+    @Override
+    public void drawSolidOval(final int x, final int y, final int width, final int height, final Color color) {
+        indent();
+        final int px = offsetX + x;
+        final int py = offsetY + y;
+        final int qx = px + width - 1;
+        final int qy = py + height - 1;
+        buffer.appendln("Oval (solid) " + px + "," + py + " " + width + "x" + height + " (" + qx + "," + qy + ") " + color + line());
+    }
+
+    @Override
+    public void drawSolidRectangle(final int x, final int y, final int width, final int height, final Color color) {
+        indent();
+        final int px = offsetX + x;
+        final int py = offsetY + y;
+        final int qx = px + width - 1;
+        final int qy = py + height - 1;
+        buffer.appendln("Rectangle (solid) " + px + "," + py + " " + width + "x" + height + " (" + qx + "," + qy + ") " + color + line());
+    }
+
+    @Override
+    public void drawSolidShape(final Shape shape, final Color color) {
+        indent();
+        buffer.appendln("Shape (solid) " + shape + " " + color);
+    }
+
+    @Override
+    public void drawSolidShape(final Shape shape, final int x, final int y, final Color color) {
+        indent();
+        final int px = offsetX + x;
+        final int py = offsetY + y;
+        buffer.appendln("Shape (solid)" + shape + " at " + px + "," + py + " (left, top)" + " " + color + line());
+    }
+
+    @Override
+    public void drawText(final String text, final int x, final int y, final Color color, final Text style) {
+        indent();
+        final int px = offsetX + x;
+        final int py = offsetY + y;
+        buffer.appendln("Text " + px + "," + py + " \"" + text + "\" " + color + line());
+    }
+
+    @Override
+    public void drawText(final String text, final int x, final int y, final int maxWidth, final Color color, final Text style) {
+        indent();
+        final int px = offsetX + x;
+        final int py = offsetY + y;
+        buffer.appendln("Text " + px + "," + py + " +" + maxWidth + "xh \"" + text + "\" " + color + line());
+    }
+
+    private void indent() {
+        for (int i = 0; i < level; i++) {
+            buffer.append("   ");
+        }
+    }
+
+    @Override
+    public void offset(final int x, final int y) {
+        // indent();
+        offsetX += x;
+        offsetY += y;
+        // buffer.appendln("Offset by " + x + "/" + y + " (left, top)");
+    }
+
+    @Override
+    public boolean overlaps(final Bounds bounds) {
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        return "Canvas";
+    }
+
+    @Override
+    public void drawDebugOutline(final Bounds bounds, final int baseline, final Color color) {
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/eb613703/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/drawing/DrawingUtil.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/drawing/DrawingUtil.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/drawing/DrawingUtil.java
new file mode 100644
index 0000000..69e1b48
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/drawing/DrawingUtil.java
@@ -0,0 +1,40 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.isis.viewer.dnd.drawing;
+
+public class DrawingUtil {
+    public static void drawHatching(final Canvas canvas, final int x, final int y, final int width, final int height, final Color foreground, final Color shadow) {
+        final int bottom = y + height;
+        for (int p = y; p < bottom; p += 4) {
+            drawDots(canvas, x, p, width, foreground, shadow);
+            if (p + 2 < bottom) {
+                drawDots(canvas, x + 2, p + 2, width - 2, foreground, shadow);
+            }
+        }
+    }
+
+    private static void drawDots(final Canvas canvas, final int x, final int y, final int width, final Color foreground, final Color shadow) {
+        final int x2 = x + width;
+        for (int p = x; p < x2; p += 4) {
+            canvas.drawLine(p, y, p, y, shadow);
+            canvas.drawLine(p + 1, y + 1, p + 1, y + 1, foreground);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/eb613703/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/drawing/Image.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/drawing/Image.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/drawing/Image.java
new file mode 100644
index 0000000..33d25f2
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/drawing/Image.java
@@ -0,0 +1,28 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.isis.viewer.dnd.drawing;
+
+public interface Image {
+    int getHeight();
+
+    int getWidth();
+
+    Size getSize();
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/eb613703/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/drawing/ImageFactory.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/drawing/ImageFactory.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/drawing/ImageFactory.java
new file mode 100644
index 0000000..83440fd
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/drawing/ImageFactory.java
@@ -0,0 +1,179 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.isis.viewer.dnd.drawing;
+
+/*
+ import java.awt.Toolkit;
+ import java.awt.image.FilteredImageSource;
+ import java.awt.image.RGBImageFilter;
+ */
+import java.util.Hashtable;
+
+import org.apache.isis.core.commons.config.IsisConfiguration;
+import org.apache.isis.core.commons.exceptions.IsisException;
+import org.apache.isis.core.metamodel.spec.ObjectSpecification;
+import org.apache.isis.runtimes.dflt.runtime.system.context.IsisContext;
+import org.apache.isis.viewer.dnd.util.Properties;
+
+public abstract class ImageFactory {
+    private static final String DEFAULT_IMAGE_NAME = "Default";
+    private static final String DEFAULT_IMAGE_PROPERTY = Properties.PROPERTY_BASE + "default-image";
+    private static ImageFactory instance;
+    private static final String SEPARATOR = "_";
+
+    public static ImageFactory getInstance() {
+        if (instance == null) {
+            throw new IllegalStateException("Instance not set up yet");
+        }
+        return instance;
+    }
+
+    /**
+     * Keyed list of icons (each derived from an image, of a specific size etc),
+     * where the key is the name of the icon and its size.
+     */
+    private final Hashtable<String, Image> templateImages = new Hashtable<String, Image>();
+
+    // ////////////////////////////////////////////////////////////////////
+    // Constructor
+    // ////////////////////////////////////////////////////////////////////
+
+    public ImageFactory() {
+        instance = this;
+    }
+
+    // ////////////////////////////////////////////////////////////////////
+    // loadIcon for Specifications
+    // ////////////////////////////////////////////////////////////////////
+
+    public Image loadIcon(final ObjectSpecification specification, final int iconHeight, final Color tint) {
+        return findIcon(specification, iconHeight, null);
+    }
+
+    private Image findIcon(final ObjectSpecification specification, final int iconHeight, final Color tint) {
+        Image loadIcon = null;
+        if (loadIcon == null) {
+            final String fullClassNameSlashes = specification.getFullIdentifier().replace(".", "/");
+            loadIcon = loadIcon(fullClassNameSlashes, iconHeight, tint);
+        }
+        if (loadIcon == null) {
+            final String fullClassNameUnderscores = specification.getFullIdentifier().replace('.', '_');
+            loadIcon = loadIcon(fullClassNameUnderscores, iconHeight, tint);
+        }
+        if (loadIcon == null) {
+            final String shortClassNameUnderscores = specification.getShortIdentifier().replace('.', '_');
+            loadIcon = loadIcon(shortClassNameUnderscores, iconHeight, tint);
+        }
+        if (loadIcon == null) {
+            loadIcon = findIconForSuperClass(specification, iconHeight, tint);
+        }
+        if (loadIcon == null) {
+            loadIcon = findIconForInterfaces(specification, iconHeight, tint);
+        }
+        return loadIcon;
+    }
+
+    private Image findIconForSuperClass(final ObjectSpecification specification, final int iconHeight, final Color tint) {
+        final ObjectSpecification superclassSpecification = specification.superclass();
+        Image loadIcon;
+        if (superclassSpecification == null) {
+            loadIcon = null;
+        } else {
+            loadIcon = findIcon(superclassSpecification, iconHeight, tint);
+        }
+        return loadIcon;
+    }
+
+    private Image findIconForInterfaces(final ObjectSpecification specification, final int iconHeight, final Color tint) {
+        Image loadIcon = null;
+        for (final ObjectSpecification interfaceSpecification : specification.interfaces()) {
+            loadIcon = findIcon(interfaceSpecification, iconHeight, tint);
+            if (loadIcon != null) {
+                return loadIcon;
+            }
+        }
+        return loadIcon;
+    }
+
+    // ////////////////////////////////////////////////////////////////////
+    // loadIcon for arbitrary path
+    // ////////////////////////////////////////////////////////////////////
+
+    /**
+     * Loads an icon of the specified size, and with the specified tint. If
+     * color is null then no tint is applied.
+     */
+    public Image loadIcon(final String name, final int height, final Color tint) {
+        final String id = name + SEPARATOR + height + SEPARATOR + tint;
+
+        if (templateImages.containsKey(id)) {
+            return templateImages.get(id);
+        }
+        final Image icon = loadImage(name, height, tint);
+        if (icon != null) {
+            templateImages.put(id, icon);
+        }
+        return icon;
+    }
+
+    // ////////////////////////////////////////////////////////////////////
+    // loadDefaultIcon
+    // ////////////////////////////////////////////////////////////////////
+
+    /**
+     * Loads the fall back icon image, for use when no specific image can be
+     * found
+     */
+    public Image loadDefaultIcon(final int height, final Color tint) {
+        final String fallbackImage = getConfiguration().getString(DEFAULT_IMAGE_PROPERTY, DEFAULT_IMAGE_NAME);
+        Image icon = loadIcon(fallbackImage, height, tint);
+        if (icon == null) {
+            icon = loadIcon("unknown", height, tint);
+        }
+        if (icon == null) {
+            throw new IsisException("Failed to find default icon: " + fallbackImage);
+        }
+        return icon;
+    }
+
+    // ////////////////////////////////////////////////////////////////////
+    // loadImage
+    // ////////////////////////////////////////////////////////////////////
+
+    /**
+     * Load an image with the given name.
+     */
+    public abstract Image loadImage(final String path);
+
+    /**
+     * Load an image with the given name, but of a specific height and of a
+     * specific hint.
+     */
+    protected abstract Image loadImage(final String name, final int height, final Color tint);
+
+    // ////////////////////////////////////////////////////////////////////
+    // Dependencies (from singleton)
+    // ////////////////////////////////////////////////////////////////////
+
+    private IsisConfiguration getConfiguration() {
+        return IsisContext.getConfiguration();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/eb613703/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/drawing/Location.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/drawing/Location.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/drawing/Location.java
new file mode 100644
index 0000000..c3e1bd6
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/drawing/Location.java
@@ -0,0 +1,113 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.isis.viewer.dnd.drawing;
+
+public class Location {
+    int x;
+    int y;
+
+    public Location() {
+        x = 0;
+        y = 0;
+    }
+
+    public Location(final int x, final int y) {
+        super();
+        this.x = x;
+        this.y = y;
+    }
+
+    public Location(final Location location) {
+        x = location.x;
+        y = location.y;
+    }
+
+    public void add(final int x, final int y) {
+        move(x, y);
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if (obj == this) {
+            return true;
+        }
+
+        if (obj instanceof Location) {
+            final Location object = (Location) obj;
+
+            return object.x == this.x && object.y == this.y;
+        }
+
+        return false;
+    }
+
+    public int getX() {
+        return x;
+    }
+
+    public int getY() {
+        return y;
+    }
+
+    public void move(final int dx, final int dy) {
+        x += dx;
+        y += dy;
+    }
+
+    public Offset offsetFrom(final Location location) {
+
+        Offset offset;
+        offset = new Offset(x - location.x, y - location.y);
+        return offset;
+    }
+
+    public void setX(final int x) {
+        this.x = x;
+    }
+
+    public void setY(final int y) {
+        this.y = y;
+    }
+
+    public void subtract(final int x, final int y) {
+        move(-x, -y);
+    }
+
+    public void subtract(final Location location) {
+        move(-location.x, -location.y);
+    }
+
+    public void subtract(final Offset offset) {
+        move(-offset.getDeltaX(), -offset.getDeltaY());
+    }
+
+    @Override
+    public String toString() {
+        return x + "," + y;
+    }
+
+    public void translate(final Location offset) {
+        move(offset.x, offset.y);
+    }
+
+    public void translate(final Offset offset) {
+        move(offset.getDeltaX(), offset.getDeltaY());
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/eb613703/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/drawing/Offset.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/drawing/Offset.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/drawing/Offset.java
new file mode 100644
index 0000000..d36852d
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/drawing/Offset.java
@@ -0,0 +1,84 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.isis.viewer.dnd.drawing;
+
+public class Offset {
+
+    private int dx;
+    private int dy;
+
+    public Offset(final Location locationInViewer, final Location locationInView) {
+        dx = locationInViewer.getX() - locationInView.getX();
+        dy = locationInViewer.getY() - locationInView.getY();
+    }
+
+    public Offset(final int dx, final int dy) {
+        this.dx = dx;
+        this.dy = dy;
+    }
+
+    public Offset(final Location location) {
+        this.dx = location.getX();
+        this.dy = location.getY();
+    }
+
+    public int getDeltaX() {
+        return dx;
+    }
+
+    public int getDeltaY() {
+        return dy;
+    }
+
+    public Location offset(final Location locationInViewer) {
+        final Location location = new Location(locationInViewer);
+        location.move(dx, dy);
+        return location;
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if (obj == this) {
+            return true;
+        }
+
+        if (obj instanceof Offset) {
+            Offset offset;
+            offset = (Offset) obj;
+            return offset.dx == dx && offset.dy == dy;
+        }
+
+        return false;
+    }
+
+    @Override
+    public String toString() {
+        return "Offset " + dx + ", " + dy;
+    }
+
+    public void add(final int dx, final int dy) {
+        this.dx += dx;
+        this.dy += dy;
+    }
+
+    public void subtract(final int dx, final int dy) {
+        add(-dx, -dy);
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/eb613703/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/drawing/Padding.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/drawing/Padding.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/drawing/Padding.java
new file mode 100644
index 0000000..e5f346d
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/drawing/Padding.java
@@ -0,0 +1,136 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.isis.viewer.dnd.drawing;
+
+public class Padding {
+    int bottom;
+    int left;
+    int right;
+    int top;
+
+    public Padding(final int top, final int left, final int bottom, final int right) {
+        this.top = top;
+        this.bottom = bottom;
+        this.left = left;
+        this.right = right;
+    }
+
+    public Padding() {
+        top = 0;
+        bottom = 0;
+        left = 0;
+        right = 0;
+    }
+
+    public Padding(final Padding padding) {
+        this.top = padding.top;
+        this.bottom = padding.bottom;
+        this.left = padding.left;
+        this.right = padding.right;
+    }
+
+    public void setBottom(final int bottom) {
+        this.bottom = bottom;
+    }
+
+    public int getBottom() {
+        return bottom;
+    }
+
+    public void setLeft(final int left) {
+        this.left = left;
+    }
+
+    public int getLeft() {
+        return left;
+    }
+
+    public int getLeftRight() {
+        return left + right;
+    }
+
+    public void setRight(final int right) {
+        this.right = right;
+    }
+
+    public int getRight() {
+        return right;
+    }
+
+    public void setTop(final int top) {
+        this.top = top;
+    }
+
+    public int getTop() {
+        return top;
+    }
+
+    public int getTopBottom() {
+        return top + bottom;
+    }
+
+    /**
+     * Extend the padding on the bottom by the specified amount.
+     */
+    public void extendBottom(final int pad) {
+        bottom += pad;
+    }
+
+    /**
+     * Extend the padding on the left by the specified amount.
+     */
+    public void extendLeft(final int pad) {
+        left += pad;
+    }
+
+    /**
+     * Extend the padding on the right by the specified amount.
+     */
+    public void extendRight(final int pad) {
+        right += pad;
+    }
+
+    /**
+     * Extend the padding on the top by the specified amount.
+     */
+    public void extendTop(final int pad) {
+        top += pad;
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if (obj == this) {
+            return true;
+        }
+
+        if (obj instanceof Padding) {
+            final Padding object = (Padding) obj;
+
+            return object.top == this.top && object.bottom == this.bottom && object.left == this.left && object.right == this.right;
+        }
+
+        return false;
+    }
+
+    @Override
+    public String toString() {
+        return "Padding [top=" + top + ",bottom=" + bottom + ",left=" + left + ",right=" + right + "]";
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/eb613703/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/drawing/Shape.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/drawing/Shape.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/drawing/Shape.java
new file mode 100644
index 0000000..bf2196d
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/drawing/Shape.java
@@ -0,0 +1,102 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.isis.viewer.dnd.drawing;
+
+public class Shape {
+    int count = 0;
+    int[] x = new int[6];
+    int[] y = new int[6];
+
+    public Shape() {
+    }
+
+    public Shape(final int xOrigin, final int yOrigin) {
+        this.x[0] = xOrigin;
+        this.y[0] = yOrigin;
+        count = 1;
+    }
+
+    public Shape(final Shape shape) {
+        count = shape.count;
+        this.x = new int[count];
+        this.y = new int[count];
+        for (int i = 0; i < count; i++) {
+            this.x[i] = shape.x[i];
+            this.y[i] = shape.y[i];
+        }
+    }
+
+    public void addVector(final int width, final int height) {
+        final int x = this.x[count - 1] + width;
+        final int y = this.y[count - 1] + height;
+        addPoint(x, y);
+    }
+
+    public void addPoint(final int x, final int y) {
+        if (this.x.length == count) {
+            final int[] newX = new int[count * 2];
+            final int[] newY = new int[count * 2];
+            System.arraycopy(this.x, 0, newX, 0, count);
+            System.arraycopy(this.y, 0, newY, 0, count);
+            this.x = newX;
+            this.y = newY;
+        }
+        this.x[count] = x;
+        this.y[count] = y;
+        count++;
+    }
+
+    public int count() {
+        return count;
+    }
+
+    public int[] getX() {
+        final int[] xx = new int[count];
+        System.arraycopy(x, 0, xx, 0, count);
+        return xx;
+    }
+
+    public int[] getY() {
+        final int[] yy = new int[count];
+        System.arraycopy(y, 0, yy, 0, count);
+        return yy;
+    }
+
+    @Override
+    public String toString() {
+        final StringBuffer points = new StringBuffer();
+        for (int i = 0; i < count; i++) {
+            if (i > 0) {
+                points.append("; ");
+            }
+            points.append(this.x[i]);
+            points.append(",");
+            points.append(this.y[i]);
+        }
+        return "Shape {" + points + "}";
+    }
+
+    public void translate(final int x, final int y) {
+        for (int i = 0; i < count; i++) {
+            this.x[i] += x;
+            this.y[i] += y;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/eb613703/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/drawing/Size.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/drawing/Size.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/drawing/Size.java
new file mode 100644
index 0000000..f5dca68
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/drawing/Size.java
@@ -0,0 +1,165 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.isis.viewer.dnd.drawing;
+
+public class Size {
+    public static final Size createMax() {
+        return new Size(10000, 10000);
+    }
+
+    int height;
+    int width;
+
+    public Size() {
+        width = 0;
+        height = 0;
+    }
+
+    public Size(final int width, final int height) {
+        this.width = width;
+        this.height = height;
+    }
+
+    public Size(final Size size) {
+        width = size.width;
+        height = size.height;
+    }
+
+    public void contract(final int width, final int height) {
+        this.width -= width;
+        this.height -= height;
+    }
+
+    public void contract(final Size size) {
+        this.width -= size.width;
+        this.height -= size.height;
+    }
+
+    public void contractHeight(final int height) {
+        this.height -= height;
+    }
+
+    public void contract(final Padding padding) {
+        height -= padding.top + padding.bottom;
+        width -= padding.left + padding.right;
+    }
+
+    public void contractWidth(final int width) {
+        this.width -= width;
+    }
+
+    public void ensureHeight(final int height) {
+        this.height = Math.max(this.height, height);
+    }
+
+    public void ensureWidth(final int width) {
+        this.width = Math.max(this.width, width);
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if (obj == this) {
+            return true;
+        }
+
+        if (obj instanceof Size) {
+            final Size object = (Size) obj;
+
+            return object.width == this.width && object.height == this.height;
+        }
+
+        return false;
+    }
+
+    public void extend(final int width, final int height) {
+        this.width += width;
+        this.height += height;
+    }
+
+    public void extend(final Padding padding) {
+        this.width += padding.getLeftRight();
+        this.height += padding.getTopBottom();
+    }
+
+    public void extend(final Size size) {
+        this.width += size.width;
+        this.height += size.height;
+    }
+
+    public void extendHeight(final int height) {
+        this.height += height;
+    }
+
+    public void extendWidth(final int width) {
+        this.width += width;
+    }
+
+    public int getHeight() {
+        return height;
+    }
+
+    public int getWidth() {
+        return width;
+    }
+
+    /**
+     * Limits the height of this Size so that if its height is greater than the
+     * specified maximum then the height is reduced to the maximum. If the
+     * height is less than maximum (or equal to it) then nothing is done.
+     */
+    public void limitHeight(final int maximum) {
+        height = Math.min(height, maximum);
+    }
+
+    /**
+     * Limits the width of this Size so that if its width is greater than the
+     * specified maximum then the width is reduced to the maximum. If the width
+     * is less than maximum (or equal to it) then nothing is done.
+     */
+    public void limitWidth(final int maximum) {
+        width = Math.min(width, maximum);
+    }
+
+    /**
+     * Limits the width and height of this Size so it is no larger than the
+     * specified maximum size.
+     * 
+     * @see #limitWidth(int)
+     * @see #limitHeight(int)
+     */
+    public void limitSize(final Size maximum) {
+        limitWidth(maximum.width);
+        limitHeight(maximum.height);
+    }
+
+    public void setHeight(final int height) {
+        this.height = height;
+    }
+
+    public void setWidth(final int width) {
+        this.width = width;
+    }
+
+    @Override
+    public String toString() {
+        return width + "x" + height;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/eb613703/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/drawing/Text.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/drawing/Text.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/drawing/Text.java
new file mode 100644
index 0000000..68739e7
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/drawing/Text.java
@@ -0,0 +1,85 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.isis.viewer.dnd.drawing;
+
+public interface Text {
+
+    /**
+     * Returns the width, in pixels, of the specified character.
+     */
+    int charWidth(char c);
+
+    /**
+     * Returns the height, in pixels, of the distance from the baseline to top
+     * of the tallest character (including accents that are not common in
+     * english.
+     */
+    int getAscent();
+
+    /**
+     * Returns the height, in pixels, of the distance from bottom of the lowest
+     * descending character to the baseline.
+     */
+    int getDescent();
+
+    /**
+     * Returns the mid point, in pixels, between the baseline and the top of the
+     * characters.
+     */
+    int getMidPoint();
+
+    /**
+     * Return the name of this text style.
+     */
+    String getName();
+
+    /**
+     * Returns the height, in pixels, for a normal line of text - where there is
+     * some space between two lines of text.
+     */
+    int getTextHeight();
+
+    /**
+     * Returns the sum of the text height and line spacing.
+     * 
+     * @see #getLineHeight()
+     * @see #getLineSpacing()
+     */
+    int getLineHeight();
+
+    /**
+     * Returns the number of blank vertical pixels to add between adjacent lines
+     * to give them additional spacing.
+     */
+    int getLineSpacing();
+
+    /**
+     * Returns the width of the specified in pixels.
+     */
+    int stringWidth(String text);
+
+    /**
+     * Returns the height in pixels when the specified text is wrapped at the
+     * specified width
+     */
+    int stringHeight(String text, int maxWidth);
+
+    int stringWidth(String message, int maxWidth);
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/eb613703/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/field/AbstractField.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/field/AbstractField.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/field/AbstractField.java
new file mode 100644
index 0000000..9980f16
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/field/AbstractField.java
@@ -0,0 +1,262 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.isis.viewer.dnd.field;
+
+import org.apache.isis.core.commons.exceptions.NotYetImplementedException;
+import org.apache.isis.core.commons.lang.ToString;
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.consent.Allow;
+import org.apache.isis.core.metamodel.consent.Consent;
+import org.apache.isis.viewer.dnd.drawing.Canvas;
+import org.apache.isis.viewer.dnd.drawing.ColorsAndFonts;
+import org.apache.isis.viewer.dnd.drawing.Image;
+import org.apache.isis.viewer.dnd.drawing.ImageFactory;
+import org.apache.isis.viewer.dnd.drawing.Location;
+import org.apache.isis.viewer.dnd.drawing.Padding;
+import org.apache.isis.viewer.dnd.view.BackgroundTask;
+import org.apache.isis.viewer.dnd.view.Content;
+import org.apache.isis.viewer.dnd.view.InternalDrag;
+import org.apache.isis.viewer.dnd.view.KeyboardAction;
+import org.apache.isis.viewer.dnd.view.Toolkit;
+import org.apache.isis.viewer.dnd.view.UserActionSet;
+import org.apache.isis.viewer.dnd.view.View;
+import org.apache.isis.viewer.dnd.view.ViewAreaType;
+import org.apache.isis.viewer.dnd.view.ViewSpecification;
+import org.apache.isis.viewer.dnd.view.action.BackgroundWork;
+import org.apache.isis.viewer.dnd.view.base.AbstractView;
+
+public abstract class AbstractField extends AbstractView {
+    protected static final int TEXT_WIDTH = 20;
+    private boolean identified;
+
+    protected AbstractField(final Content content, final ViewSpecification design) {
+        super(content, design);
+    }
+
+    @Override
+    public boolean canFocus() {
+        return canChangeValue().isAllowed();
+    }
+
+    protected boolean provideClearCopyPaste() {
+        return false;
+    }
+
+    protected Consent canClear() {
+        return Allow.DEFAULT;
+    }
+
+    protected void clear() {
+    }
+
+    protected void copyToClipboard() {
+    }
+
+    protected void pasteFromClipboard() {
+    }
+
+    /**
+     * Indicates the drag started within this view's bounds is continuing. By
+     * default does nothing.
+     */
+    @Override
+    public void drag(final InternalDrag drag) {
+    }
+
+    /**
+     * Default implementation - does nothing
+     */
+    @Override
+    public void dragCancel(final InternalDrag drag) {
+    }
+
+    /**
+     * Indicates the start of a drag within this view's bounds. By default does
+     * nothing.
+     */
+    @Override
+    public View dragFrom(final Location location) {
+        return null;
+    }
+
+    /**
+     * Indicates the drag started within this view's bounds has been finished
+     * (although the location may now be outside of its bounds). By default does
+     * nothing.
+     */
+    @Override
+    public void dragTo(final InternalDrag drag) {
+    }
+
+    @Override
+    public void draw(final Canvas canvas) {
+        if (getState().isActive()) {
+            clearBackground(canvas, Toolkit.getColor(ColorsAndFonts.COLOR_IDENTIFIED));
+        }
+
+        if (getState().isOutOfSynch()) {
+            clearBackground(canvas, Toolkit.getColor(ColorsAndFonts.COLOR_OUT_OF_SYNC));
+        }
+
+        if (getState().isInvalid()) {
+            final Image image = ImageFactory.getInstance().loadIcon("invalid-entry", 12, null);
+            if (image != null) {
+                canvas.drawImage(image, getSize().getWidth() - 16, 2);
+            }
+
+            // canvas.clearBackground(this,
+            // Toolkit.getColor(ColorsAndFonts.COLOR_INVALID));
+        }
+
+        super.draw(canvas);
+    }
+
+    @Override
+    public void entered() {
+        super.entered();
+        identified = true;
+        final Consent changable = canChangeValue();
+        if (changable.isVetoed()) {
+            getFeedbackManager().setViewDetail(changable.getReason());
+        }
+        markDamaged();
+    }
+
+    @Override
+    public void exited() {
+        super.exited();
+        identified = false;
+        markDamaged();
+    }
+
+    public boolean getIdentified() {
+        return identified;
+    }
+
+    @Override
+    public Padding getPadding() {
+        return new Padding(0, 0, 0, 0);
+    }
+
+    public View getRoot() {
+        throw new NotYetImplementedException();
+    }
+
+    String getSelectedText() {
+        return "";
+    }
+
+    @Override
+    public boolean hasFocus() {
+        return getViewManager().hasFocus(getView());
+    }
+
+    public boolean isEmpty() {
+        return false;
+    }
+
+    public boolean indicatesForView(final Location mouseLocation) {
+        return false;
+    }
+
+    /**
+     * Called when the user presses any key on the keyboard while this view has
+     * the focus.
+     */
+    @Override
+    public void keyPressed(final KeyboardAction key) {
+    }
+
+    /**
+     * Called when the user releases any key on the keyboard while this view has
+     * the focus.
+     */
+    @Override
+    public void keyReleased(final KeyboardAction action) {
+    }
+
+    /**
+     * Called when the user presses a non-control key (i.e. data entry keys and
+     * not shift, up-arrow etc). Such a key press will result in a prior call to
+     * <code>keyPressed</code> and a subsequent call to <code>keyReleased</code>
+     * .
+     */
+    @Override
+    public void keyTyped(final KeyboardAction action) {
+    }
+
+    @Override
+    public void contentMenuOptions(final UserActionSet options) {
+        if (provideClearCopyPaste()) {
+            options.add(new CopyValueOption(this));
+            options.add(new PasteValueOption(this));
+            options.add(new ClearValueOption(this));
+        }
+
+        super.contentMenuOptions((options));
+        options.setColor(Toolkit.getColor(ColorsAndFonts.COLOR_MENU_VALUE));
+    }
+
+    protected final void initiateSave(final boolean moveToNextField) {
+        BackgroundWork.runTaskInBackground(this, new BackgroundTask() {
+            @Override
+            public void execute() {
+                save();
+                getParent().updateView();
+                invalidateLayout();
+                if (moveToNextField) {
+                    getFocusManager().focusNextView();
+                }
+            }
+
+            @Override
+            public String getName() {
+                return "Save field";
+            }
+
+            @Override
+            public String getDescription() {
+                return "Saving " + getContent().windowTitle();
+            }
+
+        });
+    }
+
+    protected abstract void save();
+
+    @Override
+    public String toString() {
+        final ToString str = new ToString(this, getId());
+        str.append("location", getLocation());
+        final ObjectAdapter adapter = getContent().getAdapter();
+        str.append("object", adapter == null ? "" : adapter.getObject());
+        return str.toString();
+    }
+
+    @Override
+    public ViewAreaType viewAreaType(final Location mouseLocation) {
+        return ViewAreaType.INTERNAL;
+    }
+
+    @Override
+    public int getBaseline() {
+        return Toolkit.defaultBaseline();
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/eb613703/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/field/AbstractValueOption.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/field/AbstractValueOption.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/field/AbstractValueOption.java
new file mode 100644
index 0000000..03f071b
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/field/AbstractValueOption.java
@@ -0,0 +1,56 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.isis.viewer.dnd.field;
+
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.viewer.dnd.view.View;
+import org.apache.isis.viewer.dnd.view.content.TextParseableContent;
+import org.apache.isis.viewer.dnd.view.option.UserActionAbstract;
+
+public abstract class AbstractValueOption extends UserActionAbstract {
+    protected final AbstractField field;
+
+    AbstractValueOption(final AbstractField field, final String name) {
+        super(name);
+        this.field = field;
+    }
+
+    protected ObjectAdapter getValue(final View view) {
+        final TextParseableContent vc = (TextParseableContent) view.getContent();
+        final ObjectAdapter value = vc.getAdapter();
+        return value;
+    }
+
+    protected void updateParent(final View view) {
+        // have commented this out because it isn't needed; the transaction
+        // manager will do this
+        // for us on endTransaction. Still, if I'm wrong and it is needed,
+        // hopefully this
+        // comment will help...
+        // IsisContext.getObjectPersistor().objectChangedAllDirty();
+
+        view.markDamaged();
+        view.getParent().invalidateContent();
+    }
+
+    protected boolean isEmpty(final View view) {
+        return field.isEmpty();
+    }
+}