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/06 18:42:18 UTC

[40/52] [partial] ISIS-188: moving framework/ subdirs up to parent

http://git-wip-us.apache.org/repos/asf/isis/blob/255ef514/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/awt/AwtColor.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/awt/AwtColor.java b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/awt/AwtColor.java
new file mode 100644
index 0000000..5bd5ceb
--- /dev/null
+++ b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/awt/AwtColor.java
@@ -0,0 +1,90 @@
+/*
+ *  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.awt;
+
+import org.apache.isis.runtimes.dflt.runtime.system.context.IsisContext;
+import org.apache.isis.viewer.dnd.drawing.Color;
+import org.apache.isis.viewer.dnd.util.Properties;
+
+public class AwtColor implements Color {
+    public static final AwtColor DEBUG_BASELINE = new AwtColor(java.awt.Color.magenta);
+    public static final AwtColor DEBUG_DRAW_BOUNDS = new AwtColor(java.awt.Color.cyan);
+    public static final AwtColor DEBUG_VIEW_BOUNDS = new AwtColor(java.awt.Color.orange);
+    public static final AwtColor DEBUG_REPAINT_BOUNDS = new AwtColor(java.awt.Color.green);
+    public static final AwtColor DEBUG_BORDER_BOUNDS = new AwtColor(java.awt.Color.pink);
+
+    public static final AwtColor RED = new AwtColor(java.awt.Color.red);
+    public static final AwtColor GREEN = new AwtColor(java.awt.Color.green);
+    public static final AwtColor BLUE = new AwtColor(java.awt.Color.blue);
+    public static final AwtColor BLACK = new AwtColor(java.awt.Color.black);
+    public static final AwtColor WHITE = new AwtColor(java.awt.Color.white);
+    public static final AwtColor GRAY = new AwtColor(java.awt.Color.gray);
+    public static final AwtColor LIGHT_GRAY = new AwtColor(java.awt.Color.lightGray);
+    public static final AwtColor ORANGE = new AwtColor(java.awt.Color.orange);
+    public static final AwtColor YELLOW = new AwtColor(java.awt.Color.yellow);
+
+    static public final AwtColor NULL = new AwtColor(0);
+
+    private static final String PROPERTY_STEM = Properties.PROPERTY_BASE + "color.";
+    private final java.awt.Color color;
+    private String name;
+
+    public AwtColor(final int rgbColor) {
+        this(new java.awt.Color(rgbColor));
+    }
+
+    private AwtColor(final java.awt.Color color) {
+        this.color = color;
+    }
+
+    public AwtColor(final String propertyName, final String defaultColor) {
+        this.name = propertyName;
+        color = IsisContext.getConfiguration().getColor(PROPERTY_STEM + propertyName, java.awt.Color.decode(defaultColor));
+    }
+
+    public AwtColor(final String propertyName, final AwtColor defaultColor) {
+        this.name = propertyName;
+        color = IsisContext.getConfiguration().getColor(PROPERTY_STEM + propertyName, defaultColor.getAwtColor());
+    }
+
+    @Override
+    public Color brighter() {
+        return new AwtColor(color.brighter());
+    }
+
+    @Override
+    public Color darker() {
+        return new AwtColor(color.darker());
+    }
+
+    public java.awt.Color getAwtColor() {
+        return color;
+    }
+
+    @Override
+    public String toString() {
+        return name + " (" + "#" + Integer.toHexString(color.getRGB()) + ")";
+    }
+
+    @Override
+    public String getName() {
+        return name;
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/255ef514/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/awt/AwtColorsAndFonts.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/awt/AwtColorsAndFonts.java b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/awt/AwtColorsAndFonts.java
new file mode 100644
index 0000000..cc05daa
--- /dev/null
+++ b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/awt/AwtColorsAndFonts.java
@@ -0,0 +1,168 @@
+/*
+ *  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.awt;
+
+import java.util.Hashtable;
+
+import org.apache.isis.viewer.dnd.drawing.Color;
+import org.apache.isis.viewer.dnd.drawing.ColorsAndFonts;
+import org.apache.isis.viewer.dnd.drawing.Text;
+import org.apache.isis.viewer.dnd.view.ViewConstants;
+
+public class AwtColorsAndFonts implements ColorsAndFonts {
+    private final Hashtable colors = new Hashtable();
+    private int defaultBaseline;
+    private int defaultFieldHeight;
+    private final Hashtable fonts = new Hashtable();
+
+    @Override
+    public int defaultBaseline() {
+        if (defaultBaseline == 0) {
+            final int iconSize = getText(TEXT_NORMAL).getAscent();
+            defaultBaseline = ViewConstants.VPADDING + iconSize;
+        }
+        return defaultBaseline;
+    }
+
+    @Override
+    public int defaultFieldHeight() {
+        if (defaultFieldHeight == 0) {
+            defaultFieldHeight = getText(TEXT_NORMAL).getTextHeight();
+        }
+        return defaultFieldHeight;
+    }
+
+    @Override
+    public Color getColor(final String name) {
+        Color color = (Color) colors.get(name);
+        if (color == null && name.startsWith(COLOR_WINDOW + ".")) {
+            color = new AwtColor(name, (AwtColor) getColor(COLOR_WINDOW));
+            colors.put(name, color);
+        }
+        return color;
+
+    }
+
+    @Override
+    public Color getColor(final int rgbColor) {
+        return new AwtColor(rgbColor);
+    }
+
+    @Override
+    public Text getText(final String name) {
+        return (Text) fonts.get(name);
+    }
+
+    @Override
+    public void init() {
+        // core color scheme
+        setColor(COLOR_BLACK, "#000000");
+        setColor(COLOR_WHITE, "#ffffff");
+        setColor(COLOR_PRIMARY1, "#666699");
+        setColor(COLOR_PRIMARY2, "#9999cc");
+        setColor(COLOR_PRIMARY3, "#ccccff");
+        setColor(COLOR_SECONDARY1, "#666666");
+        setColor(COLOR_SECONDARY2, "#999999");
+        setColor(COLOR_SECONDARY3, "#cccccc");
+
+        // background colors
+        setColor(COLOR_APPLICATION, "#e0e0e0");
+        setColor(COLOR_WINDOW, getColor(COLOR_WHITE));
+        setColor(COLOR_MENU_VALUE, getColor(COLOR_PRIMARY3)); // "#ccffcc");
+        setColor(COLOR_MENU_CONTENT, getColor(COLOR_PRIMARY3)); // "#ffcccc");
+        setColor(COLOR_MENU_VIEW, getColor(COLOR_SECONDARY3)); // "#ffccff");
+        setColor(COLOR_MENU_WORKSPACE, getColor(COLOR_SECONDARY3)); // "#cccccc");
+
+        // menu colors
+        setColor(COLOR_MENU, getColor(COLOR_BLACK));
+        setColor(COLOR_MENU_DISABLED, getColor(COLOR_SECONDARY1));
+        setColor(COLOR_MENU_REVERSED, getColor(COLOR_WHITE));
+
+        // label colors
+        setColor(COLOR_LABEL_MANDATORY, getColor(COLOR_BLACK));
+        setColor(COLOR_LABEL, getColor(COLOR_SECONDARY1));
+        setColor(COLOR_LABEL_DISABLED, getColor(COLOR_SECONDARY2));
+
+        // state colors
+        setColor(COLOR_IDENTIFIED, getColor(COLOR_PRIMARY1)); // "#0099ff");
+        setColor(COLOR_VALID, "#32CD32");
+        setColor(COLOR_INVALID, "#ee1919");
+        setColor(COLOR_ERROR, "#ee1919");
+        setColor(COLOR_ACTIVE, "#ff0000");
+        setColor(COLOR_OUT_OF_SYNC, "#662200");
+
+        // text colors
+        setColor(COLOR_TEXT_SAVED, getColor(COLOR_SECONDARY1));
+        setColor(COLOR_TEXT_EDIT, getColor(COLOR_PRIMARY1)); // "#009933");
+        setColor(COLOR_TEXT_CURSOR, getColor(COLOR_PRIMARY1));
+        setColor(COLOR_TEXT_HIGHLIGHT, getColor(COLOR_PRIMARY3));
+
+        // debug outline colors
+        setColor(COLOR_DEBUG_BASELINE, AwtColor.DEBUG_BASELINE);
+        setColor(COLOR_DEBUG_BOUNDS_BORDER, AwtColor.DEBUG_BORDER_BOUNDS);
+        setColor(COLOR_DEBUG_BOUNDS_DRAW, AwtColor.DEBUG_DRAW_BOUNDS);
+        setColor(COLOR_DEBUG_BOUNDS_REPAINT, AwtColor.DEBUG_REPAINT_BOUNDS);
+        setColor(COLOR_DEBUG_BOUNDS_VIEW, AwtColor.DEBUG_VIEW_BOUNDS);
+
+        // fonts
+        final String defaultFontFamily = AwtText.defaultFontFamily();
+        final int defaultFontSizeSmall = AwtText.defaultFontSizeSmall();
+        final int defaultFontSizeMedium = AwtText.defaultFontSizeMedium();
+        final int defaultFontSizeLarge = AwtText.defaultFontSizeLarge();
+
+        setText(TEXT_TITLE, defaultFontFamily + "-bold-" + defaultFontSizeLarge);
+
+        setText(TEXT_TITLE_SMALL, defaultFontFamily + "-bold-" + defaultFontSizeMedium);
+        setText(TEXT_NORMAL, defaultFontFamily + "-plain-" + defaultFontSizeMedium);
+        setText(TEXT_CONTROL, defaultFontFamily + "-bold-" + defaultFontSizeMedium);
+        setText(TEXT_STATUS, defaultFontFamily + "--" + defaultFontSizeMedium);
+        setText(TEXT_ICON, defaultFontFamily + "-bold-" + defaultFontSizeMedium);
+        setText(TEXT_MENU, defaultFontFamily + "--" + defaultFontSizeMedium);
+
+        setText(TEXT_LABEL_MANDATORY, defaultFontFamily + "--" + defaultFontSizeSmall);
+        setText(TEXT_LABEL_DISABLED, defaultFontFamily + "--" + defaultFontSizeSmall);
+        setText(TEXT_LABEL, defaultFontFamily + "--" + defaultFontSizeSmall);
+    }
+
+    private void setColor(final String name, final Color defaultColor) {
+        setColor(name, (AwtColor) defaultColor);
+    }
+
+    private void setColor(final String name, final AwtColor defaultColor) {
+        if (getColor(name) == null) {
+            final AwtColor color = new AwtColor(name, defaultColor);
+            colors.put(name, color);
+        }
+    }
+
+    private void setColor(final String name, final String defaultColor) {
+        if (getColor(name) == null) {
+            final AwtColor color = new AwtColor(name, defaultColor);
+            colors.put(name, color);
+        }
+    }
+
+    private void setText(final String name, final String defaultFont) {
+        if (getText(name) == null) {
+            final AwtText font = new AwtText(name, defaultFont);
+            fonts.put(name, font);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/255ef514/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/awt/AwtImageFactory.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/awt/AwtImageFactory.java b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/awt/AwtImageFactory.java
new file mode 100644
index 0000000..27d8c18
--- /dev/null
+++ b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/awt/AwtImageFactory.java
@@ -0,0 +1,83 @@
+/*
+ *  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.awt;
+
+import java.awt.Toolkit;
+import java.awt.image.FilteredImageSource;
+import java.awt.image.RGBImageFilter;
+
+import org.apache.isis.core.runtime.imageloader.TemplateImage;
+import org.apache.isis.core.runtime.imageloader.TemplateImageLoader;
+import org.apache.isis.viewer.dnd.drawing.Color;
+import org.apache.isis.viewer.dnd.drawing.Image;
+import org.apache.isis.viewer.dnd.drawing.ImageFactory;
+import org.apache.isis.viewer.dnd.view.base.AwtImage;
+
+public class AwtImageFactory extends ImageFactory {
+
+    private static class Filter extends RGBImageFilter {
+        @Override
+        public int filterRGB(final int x, final int y, final int rgb) {
+            return 0xFFFFFF - rgb;
+        }
+    }
+
+    private final TemplateImageLoader loader;
+
+    public AwtImageFactory(final TemplateImageLoader imageLoader) {
+        loader = imageLoader;
+    }
+
+    /**
+     * Load an image with the given name.
+     */
+    @Override
+    public Image loadImage(final String path) {
+        final TemplateImage template = templateImage(path);
+        if (template == null) {
+            return null;
+        }
+        return new AwtImage(template.getImage());
+    }
+
+    @Override
+    protected Image loadImage(final String name, final int height, final Color tint) {
+        final TemplateImage template = templateImage(name);
+        if (template == null) {
+            return null;
+        }
+        final java.awt.Image iconImage = template.getIcon(height);
+        if (tint != null) {
+            Toolkit.getDefaultToolkit().createImage(new FilteredImageSource(iconImage.getSource(), new Filter()));
+        }
+        final Image icon = new AwtImage(iconImage);
+        return icon;
+    }
+
+    // ////////////////////////////////////////////////////////////////////
+    // Helpers
+    // ////////////////////////////////////////////////////////////////////
+
+    private TemplateImage templateImage(final String name) {
+        final TemplateImage template = loader.getTemplateImage(name);
+        return template;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/255ef514/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/awt/AwtText.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/awt/AwtText.java b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/awt/AwtText.java
new file mode 100644
index 0000000..e44d913
--- /dev/null
+++ b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/awt/AwtText.java
@@ -0,0 +1,242 @@
+/*
+ *  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.awt;
+
+import java.awt.Font;
+import java.awt.FontMetrics;
+import java.awt.Frame;
+import java.util.StringTokenizer;
+
+import org.apache.log4j.Logger;
+
+import org.apache.isis.core.commons.config.IsisConfiguration;
+import org.apache.isis.runtimes.dflt.runtime.system.context.IsisContext;
+import org.apache.isis.viewer.dnd.drawing.ColorsAndFonts;
+import org.apache.isis.viewer.dnd.drawing.Text;
+import org.apache.isis.viewer.dnd.util.Properties;
+
+public class AwtText implements Text {
+    private static final String ASCENT_ADJUST = Properties.PROPERTY_BASE + "ascent-adjust";
+    private static final String FONT_PROPERTY_STEM = Properties.PROPERTY_BASE + "font.";
+    private static final Logger LOG = Logger.getLogger(AwtText.class);
+    private static final String SPACING_PROPERTYSTEM = Properties.PROPERTY_BASE + "spacing.";
+    private final boolean ascentAdjust;
+    private Font font;
+    private final Frame fontMetricsComponent = new Frame();
+    private final int lineSpacing;
+    private int maxCharWidth;
+    private final FontMetrics metrics;
+    private final String propertyName;
+
+    protected AwtText(final String propertyName, final String defaultFont) {
+        final IsisConfiguration cfg = IsisContext.getConfiguration();
+        font = cfg.getFont(FONT_PROPERTY_STEM + propertyName, Font.decode(defaultFont));
+        LOG.info("font " + propertyName + " loaded as " + font);
+
+        this.propertyName = propertyName;
+
+        if (font == null) {
+            font = cfg.getFont(FONT_PROPERTY_STEM + ColorsAndFonts.TEXT_DEFAULT, new Font("SansSerif", Font.PLAIN, 12));
+        }
+
+        metrics = fontMetricsComponent.getFontMetrics(font);
+
+        maxCharWidth = metrics.getMaxAdvance() + 1;
+        if (maxCharWidth == 0) {
+            maxCharWidth = (charWidth('X') + 3);
+        }
+
+        lineSpacing = cfg.getInteger(SPACING_PROPERTYSTEM + propertyName, 0);
+
+        ascentAdjust = cfg.getBoolean(ASCENT_ADJUST, false);
+
+        LOG.debug("font " + propertyName + " height=" + metrics.getHeight() + ", leading=" + metrics.getLeading() + ", ascent=" + metrics.getAscent() + ", descent=" + metrics.getDescent() + ", line spacing=" + lineSpacing);
+    }
+
+    @Override
+    public int charWidth(final char c) {
+        return metrics.charWidth(c);
+    }
+
+    @Override
+    public int getAscent() {
+        return metrics.getAscent() - (ascentAdjust ? metrics.getDescent() : 0);
+    }
+
+    /**
+     * Returns the Font from the AWT used for drawing within the AWT.
+     * 
+     * @see Font
+     */
+    public Font getAwtFont() {
+        return font;
+    }
+
+    @Override
+    public int getDescent() {
+        return metrics.getDescent();
+    }
+
+    @Override
+    public int getLineHeight() {
+        return metrics.getHeight() + getLineSpacing();
+    }
+
+    @Override
+    public int getLineSpacing() {
+        return lineSpacing;
+    }
+
+    @Override
+    public String getName() {
+        return propertyName;
+    }
+
+    @Override
+    public int getMidPoint() {
+        return getAscent() / 2;
+    }
+
+    @Override
+    public int getTextHeight() {
+        return metrics.getHeight() - (ascentAdjust ? metrics.getDescent() : 0);
+    }
+
+    @Override
+    public int stringHeight(final String text, final int maxWidth) {
+        int noLines = 0;
+        final StringTokenizer lines = new StringTokenizer(text, "\n\r");
+        while (lines.hasMoreTokens()) {
+            final String line = lines.nextToken();
+            final StringTokenizer words = new StringTokenizer(line, " ");
+            final StringBuffer l = new StringBuffer();
+            int width = 0;
+            while (words.hasMoreTokens()) {
+                final String nextWord = words.nextToken();
+                final int wordWidth = stringWidth(nextWord);
+                width += wordWidth;
+                if (width >= maxWidth) {
+                    noLines++;
+                    l.setLength(0);
+                    width = wordWidth;
+                }
+                l.append(nextWord);
+                l.append(" ");
+                width += stringWidth(" ");
+            }
+            noLines++;
+        }
+        return noLines * getLineHeight();
+    }
+
+    @Override
+    public int stringWidth(final String text, final int maxWidth) {
+        int width = 0;
+        final StringTokenizer lines = new StringTokenizer(text, "\n\r");
+        while (lines.hasMoreTokens()) {
+            final String line = lines.nextToken();
+            final StringTokenizer words = new StringTokenizer(line, " ");
+            final StringBuffer l = new StringBuffer();
+            int lineWidth = 0;
+            while (words.hasMoreTokens()) {
+                final String nextWord = words.nextToken();
+                final int wordWidth = stringWidth(nextWord);
+                lineWidth += wordWidth;
+                if (lineWidth >= maxWidth) {
+                    return maxWidth;
+                }
+                if (lineWidth > width) {
+                    width = lineWidth;
+                }
+                l.append(nextWord);
+                l.append(" ");
+                lineWidth += stringWidth(" ");
+            }
+        }
+        return width;
+    }
+
+    // DKH: 20060404... yes, this will grow over time, but only used client-side
+    // RCM this is only a temporary solutions to help deal with titles, TODO
+    // move the caching up to the
+    // views/components that use this method
+    private final java.util.Hashtable stringWidthByString = new java.util.Hashtable();
+
+    // DKH 20060404: new implementation that caches
+    @Override
+    public int stringWidth(final String text) {
+        int[] cachedStringWidth = (int[]) stringWidthByString.get(text);
+        if (cachedStringWidth == null) {
+            cachedStringWidth = new int[] { stringWidthInternal(text) };
+            stringWidthByString.put(text, cachedStringWidth);
+        }
+        return cachedStringWidth[0];
+    }
+
+    // DKH 20060404: previously was stringWidth, now cached, see above.
+    private int stringWidthInternal(final String text) {
+        int stringWidth = metrics.stringWidth(text);
+        if (stringWidth > text.length() * maxCharWidth) {
+            LOG.debug("spurious width of string; calculating manually: " + stringWidth + " for " + this + ": " + text);
+            /*
+             * This fixes an intermittent bug in .NET where stringWidth()
+             * returns a ridiculous number is returned for the width.
+             * 
+             * TODO don't do this when running Java
+             */
+            stringWidth = 0;
+            for (int i = 0; i < text.length(); i++) {
+                int charWidth = charWidth(text.charAt(i));
+                if (charWidth > maxCharWidth) {
+                    LOG.debug("spurious width of character; using max width: " + charWidth + " for " + text.charAt(i));
+                    charWidth = maxCharWidth;
+                }
+                stringWidth += charWidth;
+                LOG.debug(i + " " + stringWidth);
+            }
+        }
+        return stringWidth;
+    }
+
+    @Override
+    public String toString() {
+        return font.toString();
+    }
+
+    public static String defaultFontFamily() {
+        final IsisConfiguration cfg = IsisContext.getConfiguration();
+        return cfg.getString(FONT_PROPERTY_STEM + "family", "SansSerif");
+    }
+
+    public static int defaultFontSizeSmall() {
+        final IsisConfiguration cfg = IsisContext.getConfiguration();
+        return cfg.getInteger(FONT_PROPERTY_STEM + "size.small", 10);
+    }
+
+    public static int defaultFontSizeMedium() {
+        final IsisConfiguration cfg = IsisContext.getConfiguration();
+        return cfg.getInteger(FONT_PROPERTY_STEM + "size.medium", 11);
+    }
+
+    public static int defaultFontSizeLarge() {
+        final IsisConfiguration cfg = IsisContext.getConfiguration();
+        return cfg.getInteger(FONT_PROPERTY_STEM + "size.large", 12);
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/255ef514/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/awt/AwtToolkit.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/awt/AwtToolkit.java b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/awt/AwtToolkit.java
new file mode 100644
index 0000000..5d45d27
--- /dev/null
+++ b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/awt/AwtToolkit.java
@@ -0,0 +1,42 @@
+/*
+ *  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.awt;
+
+import org.apache.isis.viewer.dnd.view.Toolkit;
+import org.apache.isis.viewer.dnd.viewer.DefaultContentFactory;
+import org.apache.isis.viewer.dnd.viewer.SkylarkViewFactory;
+import org.apache.isis.viewer.dnd.viewer.basic.LogoBackground;
+
+public class AwtToolkit extends Toolkit {
+    @Override
+    protected void init() {
+        final XViewer v = new XViewer();
+        final XFeedbackManager f = new XFeedbackManager(v);
+        v.setFeedbackManager(f);
+        feedbackManager = f;
+        viewer = v;
+        viewer.setBackground(new LogoBackground());
+        contentFactory = new DefaultContentFactory();
+        viewFactory = new SkylarkViewFactory();
+        colorsAndFonts = new AwtColorsAndFonts();
+
+        colorsAndFonts.init();
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/255ef514/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/awt/DebugFrame.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/awt/DebugFrame.java b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/awt/DebugFrame.java
new file mode 100644
index 0000000..5e87138
--- /dev/null
+++ b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/awt/DebugFrame.java
@@ -0,0 +1,353 @@
+/*
+ *  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.awt;
+
+import java.awt.BorderLayout;
+import java.awt.Button;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.Font;
+import java.awt.FontMetrics;
+import java.awt.Frame;
+import java.awt.Graphics;
+import java.awt.Image;
+import java.awt.Insets;
+import java.awt.Panel;
+import java.awt.Point;
+import java.awt.Rectangle;
+import java.awt.TextArea;
+import java.awt.Toolkit;
+import java.awt.event.ActionEvent;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.net.URL;
+import java.util.Vector;
+
+import org.apache.log4j.Logger;
+
+import org.apache.isis.core.commons.debug.DebugString;
+import org.apache.isis.core.commons.debug.DebuggableWithTitle;
+import org.apache.isis.viewer.dnd.view.debug.DebugOutput;
+
+/**
+ * A specialised frame for displaying the details of an object and its display
+ * mechanisms.
+ */
+public abstract class DebugFrame extends Frame {
+    private static final long serialVersionUID = 1L;
+    private static final Logger LOG = Logger.getLogger(DebugFrame.class);
+    private static Vector<Frame> frames = new Vector<Frame>();
+    private int panel = 0;
+
+    /**
+     * Calls dispose on all the open debug frames
+     * 
+     */
+    public static void disposeAll() {
+        final Frame[] f = new Frame[frames.size()];
+
+        for (int i = 0; i < f.length; i++) {
+            f[i] = frames.elementAt(i);
+        }
+
+        for (final Frame element : f) {
+            element.dispose();
+        }
+    }
+
+    private TextArea field;
+    private TabPane tabPane;
+
+    public DebugFrame() {
+        frames.addElement(this);
+
+        addWindowListener(new WindowAdapter() {
+            @Override
+            public void windowClosing(final WindowEvent e) {
+                closeDialog();
+            }
+        });
+
+        final URL url = DebugFrame.class.getResource("/" + "images/debug-log.gif");
+        if (url != null) {
+            final Image image = Toolkit.getDefaultToolkit().getImage(url);
+            if (image != null) {
+                setIconImage(image);
+            }
+        }
+
+        setLayout(new BorderLayout(7, 7));
+        final Panel tabPane = createTabPane();
+        add(tabPane);
+    }
+
+    private Panel createTabPane() {
+        tabPane = new TabPane();
+        tabPane.addMouseListener(new MouseAdapter() {
+            @Override
+            public void mouseClicked(final MouseEvent e) {
+                final Point point = e.getPoint();
+                panel = tabPane.select(point);
+
+                showDebugForPane();
+            }
+        });
+
+        tabPane.setLayout(new BorderLayout(7, 7));
+
+        final TextArea textArea = new TextArea("", 60, 110, TextArea.SCROLLBARS_BOTH);
+        textArea.setForeground(Color.black);
+        textArea.setEditable(false);
+        // Font font = Isis.getConfiguration().getFont("isis.debug.font", new
+        // Font("Monospaced", Font.PLAIN, 10));
+        final Font font = new Font("Monospaced", Font.PLAIN, 11);
+        textArea.setFont(font);
+        tabPane.add("Center", textArea);
+        field = textArea;
+
+        final Panel buttons = new Panel();
+        buttons.setLayout(new FlowLayout());
+        tabPane.add(buttons, BorderLayout.SOUTH);
+
+        // add buttons
+        Button b = new java.awt.Button("Refresh");
+        b.setFont(font);
+
+        buttons.add(b);
+        b.addActionListener(new java.awt.event.ActionListener() {
+            @Override
+            public void actionPerformed(final ActionEvent e) {
+                showDebugForPane();
+            }
+        });
+
+        b = new java.awt.Button("Print...");
+        b.setFont(font);
+
+        buttons.add(b);
+        b.addActionListener(new java.awt.event.ActionListener() {
+            @Override
+            public void actionPerformed(final ActionEvent e) {
+                DebugOutput.print("Debug " + tabPane.getName(), field.getText());
+            }
+        });
+
+        b = new java.awt.Button("Save...");
+        b.setFont(font);
+
+        buttons.add(b);
+        b.addActionListener(new java.awt.event.ActionListener() {
+            @Override
+            public void actionPerformed(final ActionEvent e) {
+                DebugOutput.saveToFile("Save details", "Debug " + tabPane.getName(), field.getText());
+            }
+        });
+
+        b = new java.awt.Button("Copy");
+        b.setFont(font);
+
+        buttons.add(b);
+        b.addActionListener(new java.awt.event.ActionListener() {
+            @Override
+            public void actionPerformed(final ActionEvent e) {
+                DebugOutput.saveToClipboard(field.getText());
+            }
+        });
+
+        b = new java.awt.Button("Close");
+        b.setFont(font);
+
+        buttons.add(b);
+        b.addActionListener(new java.awt.event.ActionListener() {
+            @Override
+            public void actionPerformed(final ActionEvent e) {
+                closeDialog();
+            }
+        });
+
+        return tabPane;
+    }
+
+    @Override
+    public Insets getInsets() {
+        final Insets insets = super.getInsets();
+        insets.left += 10;
+        insets.right += 10;
+        insets.top += 10;
+        insets.bottom += 10;
+        return insets;
+    }
+
+    private void closeDialog() {
+        dialogClosing();
+        // hide();
+        dispose();
+    }
+
+    public void dialogClosing() {
+    }
+
+    @Override
+    public void dispose() {
+        LOG.debug("dispose...");
+        tabPane.removeAll();
+        frames.removeElement(this);
+        super.dispose();
+        LOG.debug("...disposed");
+    }
+
+    protected abstract DebuggableWithTitle[] getInfo();
+
+    /**
+     * show the frame at the specified coordinates
+     */
+    public void show(final int x, final int y) {
+        /*
+         * WARNING - When refresh button is pressed it is in the AWT thread; if
+         * the repository is thread based then the wrong set of components will
+         * be used giving strange results, particularly in the object persistor.
+         */
+        // TODO run in correct thread
+        refresh();
+
+        pack();
+        limitBounds(x, y);
+        setVisible(true);
+    }
+
+    private void refresh() {
+        final DebuggableWithTitle[] infos = getInfo();
+        final DebuggableWithTitle info = infos[panel];
+        if (info != null) {
+            setTitle(info.debugTitle());
+            final DebugString str = new DebugString();
+            info.debugData(str);
+            field.setText(str.toString());
+            field.setCaretPosition(0);
+        }
+    }
+
+    public void showDebugForPane() {
+        refresh();
+    }
+
+    private void limitBounds(final int xLimit, final int yLimit) {
+        final Dimension screenSize = getToolkit().getScreenSize();
+        final int maxWidth = screenSize.width - 50;
+        final int maxHeight = screenSize.height - 50;
+
+        int width = getSize().width;
+        int height = getSize().height;
+
+        int x = xLimit;
+        if (x + width > maxWidth) {
+            x = 0;
+            if (x + width > maxWidth) {
+                width = maxWidth;
+            }
+        }
+
+        int y = yLimit;
+        if (y + height > maxHeight) {
+            y = 0;
+            if (y + height > maxHeight) {
+                height = maxHeight;
+            }
+        }
+
+        setSize(width, height);
+        setLocation(x, y);
+    }
+
+    private class TabPane extends Panel {
+        private static final long serialVersionUID = 1L;
+        private Rectangle[] tabs;
+        private int panel = 0;
+
+        public int select(final Point point) {
+            for (int i = 0; i < tabs.length; i++) {
+                if (tabs[i] != null && tabs[i].contains(point)) {
+                    panel = i;
+                    repaint();
+                    break;
+                }
+            }
+            return panel;
+        }
+
+        @Override
+        public Insets getInsets() {
+            final Insets insets = super.getInsets();
+            insets.left += 10;
+            insets.right += 10;
+            insets.top += 30;
+            insets.bottom += 10;
+            return insets;
+        }
+
+        @Override
+        public void paint(final Graphics g) {
+            final DebuggableWithTitle[] info = getInfo();
+
+            if (info != null) {
+                if (tabs == null) {
+                    tabs = new Rectangle[getInfo().length];
+                }
+                final Dimension size = getSize();
+                g.setColor(Color.gray);
+                g.drawRect(0, 20, size.width - 1, size.height - 21);
+
+                FontMetrics fm;
+                fm = g.getFontMetrics();
+                int offset = 0;
+                final int maxWidth = info.length == 0 ? size.width : size.width / info.length - 1;
+                for (int i = 0; i < info.length; i++) {
+                    String title = info[i].debugTitle();
+                    title = title == null ? info[i].getClass().getName() : title;
+                    final int width = Math.min(maxWidth, fm.stringWidth(title) + 20);
+
+                    tabs[i] = new Rectangle(offset, 0, width, 20);
+                    g.setColor(Color.gray);
+                    g.drawRect(offset + 0, 0, width, 20);
+                    if (i == panel) {
+                        g.setColor(Color.white);
+                        g.fillRect(offset + 1, 1, width - 1, 20);
+                        // g.drawLine(offset + 1, 20, offset + width, 20);
+                        g.setColor(Color.black);
+                    } else {
+                        g.setColor(Color.lightGray);
+                        g.fillRect(offset + 1, 1, width - 1, 20 - 1);
+                        g.setColor(Color.gray);
+                    }
+
+                    g.drawString(title, offset + 9, 20 - 5);
+
+                    offset += width;
+                }
+                g.setColor(Color.white);
+                g.fillRect(offset + 1, 1, size.width - offset, 20 - 1);
+            }
+        }
+
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/255ef514/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/awt/DebugOptions.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/awt/DebugOptions.java b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/awt/DebugOptions.java
new file mode 100644
index 0000000..56df754
--- /dev/null
+++ b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/awt/DebugOptions.java
@@ -0,0 +1,158 @@
+/*
+ *  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.awt;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+
+import org.apache.isis.core.commons.debug.DebugBuilder;
+import org.apache.isis.core.commons.debug.DebuggableWithTitle;
+import org.apache.isis.core.metamodel.spec.ActionType;
+import org.apache.isis.core.runtime.sysout.SystemPrinter;
+import org.apache.isis.runtimes.dflt.runtime.system.context.IsisContext;
+import org.apache.isis.viewer.dnd.drawing.Location;
+import org.apache.isis.viewer.dnd.view.MenuOptions;
+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.Workspace;
+import org.apache.isis.viewer.dnd.view.option.UserActionAbstract;
+
+public class DebugOptions implements MenuOptions {
+    private final XViewer viewer;
+
+    public DebugOptions(final XViewer viewer) {
+        this.viewer = viewer;
+    }
+
+    @Override
+    public void menuOptions(final UserActionSet options) {
+        final String showExplorationMenu = "Always show exploration menu " + (viewer.showExplorationMenuByDefault ? "off" : "on");
+        options.add(new UserActionAbstract(showExplorationMenu, ActionType.DEBUG) {
+            @Override
+            public void execute(final Workspace workspace, final View view, final Location at) {
+                viewer.showExplorationMenuByDefault = !viewer.showExplorationMenuByDefault;
+                view.markDamaged();
+            }
+        });
+
+        final String repaint = "Show painting area  " + (viewer.showRepaintArea ? "off" : "on");
+        options.add(new UserActionAbstract(repaint, ActionType.DEBUG) {
+            @Override
+            public void execute(final Workspace workspace, final View view, final Location at) {
+                viewer.showRepaintArea = !viewer.showRepaintArea;
+                view.markDamaged();
+            }
+        });
+
+        final String debug = "Debug graphics " + (Toolkit.debug ? "off" : "on");
+        options.add(new UserActionAbstract(debug, ActionType.DEBUG) {
+            @Override
+            public void execute(final Workspace workspace, final View view, final Location at) {
+                Toolkit.debug = !Toolkit.debug;
+                view.markDamaged();
+            }
+        });
+
+        final String action = viewer.isShowingMouseSpy() ? "Hide" : "Show";
+        options.add(new UserActionAbstract(action + " mouse spy", ActionType.DEBUG) {
+            @Override
+            public void execute(final Workspace workspace, final View view, final Location at) {
+                viewer.setShowMouseSpy(!viewer.isShowingMouseSpy());
+            }
+        });
+
+        // I've commented this out because in the new design we should close the
+        // ExecutionContext
+        // and then re-login.
+        // options.add(new AbstractUserAction("Restart object loader/persistor",
+        // UserAction.DEBUG) {
+        // @Override
+        // public void execute(final Workspace workspace, final View view, final
+        // Location at) {
+        // IsisContext.getObjectPersistor().reset();
+        // }
+        // });
+
+        options.add(new UserActionAbstract("Diagnostics...", ActionType.DEBUG) {
+            @Override
+            public void execute(final Workspace workspace, final View view, final Location at) {
+                final InfoDebugFrame f = new InfoDebugFrame();
+                final DebuggableWithTitle info = new DebuggableWithTitle() {
+
+                    @Override
+                    public void debugData(final DebugBuilder debug) {
+                        final ByteArrayOutputStream out2 = new ByteArrayOutputStream();
+                        final PrintStream out = new PrintStream(out2);
+                        new SystemPrinter(out).printDiagnostics();
+                        debug.append(out2.toString());
+                    }
+
+                    @Override
+                    public String debugTitle() {
+                        return "Diagnostics";
+                    }
+
+                };
+                f.setInfo(info);
+                f.show(at.getX() + 50, workspace.getBounds().getY() + 6);
+            }
+        });
+
+        options.add(new UserActionAbstract("Debug system...", ActionType.DEBUG) {
+            @Override
+            public void execute(final Workspace workspace, final View view, final Location at) {
+                final InfoDebugFrame f = new InfoDebugFrame();
+                final DebuggableWithTitle[] contextInfo = IsisContext.debugSystem();
+                f.setInfo(contextInfo);
+                f.show(at.getX() + 50, workspace.getBounds().getY() + 6);
+            }
+        });
+
+        options.add(new UserActionAbstract("Debug session...", ActionType.DEBUG) {
+            @Override
+            public void execute(final Workspace workspace, final View view, final Location at) {
+                final InfoDebugFrame f = new InfoDebugFrame();
+                final DebuggableWithTitle[] contextInfo = IsisContext.debugSession();
+                f.setInfo(contextInfo);
+                f.show(at.getX() + 50, workspace.getBounds().getY() + 6);
+            }
+        });
+
+        options.add(new UserActionAbstract("Debug viewer...", ActionType.DEBUG) {
+            @Override
+            public void execute(final Workspace workspace, final View view, final Location at) {
+                final InfoDebugFrame f = new InfoDebugFrame();
+                f.setInfo(new DebuggableWithTitle[] { Toolkit.getViewFactory(), viewer.updateNotifier });
+                f.show(at.getX() + 50, workspace.getBounds().getY() + 6);
+            }
+        });
+
+        options.add(new UserActionAbstract("Debug overlay...", ActionType.DEBUG) {
+            @Override
+            public void execute(final Workspace workspace, final View view, final Location at) {
+                final DebugFrame f = new OverlayDebugFrame(viewer);
+                f.show(at.getX() + 50, workspace.getBounds().getY() + 6);
+            }
+        });
+
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/255ef514/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/awt/InfoDebugFrame.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/awt/InfoDebugFrame.java b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/awt/InfoDebugFrame.java
new file mode 100644
index 0000000..bfc55fd
--- /dev/null
+++ b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/awt/InfoDebugFrame.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.awt;
+
+import org.apache.isis.core.commons.debug.DebuggableWithTitle;
+
+public class InfoDebugFrame extends DebugFrame {
+    private static final long serialVersionUID = 1L;
+    private DebuggableWithTitle[] info;
+
+    @Override
+    protected DebuggableWithTitle[] getInfo() {
+        return info;
+    }
+
+    /**
+     * set the display strategy to use to display the information.
+     */
+    public void setInfo(final DebuggableWithTitle info) {
+        this.info = new DebuggableWithTitle[] { info };
+    }
+
+    /**
+     * set the display strategies to use to display the information.
+     */
+    public void setInfo(final DebuggableWithTitle[] info) {
+        this.info = info;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/255ef514/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/awt/InteractionHandler.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/awt/InteractionHandler.java b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/awt/InteractionHandler.java
new file mode 100644
index 0000000..5b584a4
--- /dev/null
+++ b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/awt/InteractionHandler.java
@@ -0,0 +1,503 @@
+/*
+ *  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.awt;
+
+import java.awt.Point;
+import java.awt.event.InputEvent;
+import java.awt.event.KeyEvent;
+import java.awt.event.KeyListener;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.event.MouseMotionListener;
+
+import org.apache.log4j.Logger;
+
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.viewer.dnd.drawing.Location;
+import org.apache.isis.viewer.dnd.interaction.ClickImpl;
+import org.apache.isis.viewer.dnd.interaction.ContentDragImpl;
+import org.apache.isis.viewer.dnd.interaction.DragStartImpl;
+import org.apache.isis.viewer.dnd.view.Click;
+import org.apache.isis.viewer.dnd.view.ContentDrag;
+import org.apache.isis.viewer.dnd.view.DragEvent;
+import org.apache.isis.viewer.dnd.view.InteractionSpy;
+import org.apache.isis.viewer.dnd.view.View;
+import org.apache.isis.viewer.dnd.view.ViewAreaType;
+import org.apache.isis.viewer.dnd.view.base.AbstractView;
+import org.apache.isis.viewer.dnd.view.content.NullContent;
+
+public class InteractionHandler implements MouseMotionListener, MouseListener, KeyListener {
+    private static final Logger LOG = Logger.getLogger(InteractionHandler.class);
+    private final static int THRESHOLD = 7;
+    private boolean canDrag;
+    /*
+     * The location within the frame where the mouse button was pressed down.
+     */
+    private Location downAt;
+    private DragEvent drag;
+    private final KeyboardManager keyboardManager;
+    private View identifiedView;
+    private final InteractionSpy spy;
+    private final XViewer viewer;
+    private KeyEvent lastTyped;
+    private View draggedView;
+    private final XFeedbackManager feedbackManager;
+
+    public InteractionHandler(final XViewer viewer, final XFeedbackManager feedbackManager, final KeyboardManager keyboardManager, final InteractionSpy spy) {
+        this.viewer = viewer;
+        this.feedbackManager = feedbackManager;
+        this.spy = spy;
+        this.keyboardManager = keyboardManager;
+    }
+
+    private void drag(final MouseEvent me) {
+        final Location location = createLocation(me.getPoint());
+        spy.addAction("Mouse dragged " + location);
+        final View target = viewer.identifyView(new Location(location), false);
+        drag.drag(target, location, me.getModifiers());
+    }
+
+    private Location createLocation(final Point point) {
+        return new Location(point.x, point.y);
+    }
+
+    private void dragStart(final MouseEvent me) {
+        if (!isOverThreshold(downAt, me.getPoint())) {
+            return;
+        }
+
+        spy.addAction("Drag start  at " + downAt);
+        drag = viewer.dragStart(new DragStartImpl(downAt, me.getModifiers()));
+
+        if (drag == null) {
+            spy.addAction("drag start  ignored");
+            canDrag = false;
+        } else {
+            spy.addAction("drag start " + drag);
+            final View overlay = drag.getOverlay();
+            if (overlay != null) {
+                viewer.setOverlayView(overlay);
+            }
+            final View target = viewer.identifyView(createLocation(me.getPoint()), false);
+            drag.drag(target, createLocation(me.getPoint()), me.getModifiers());
+        }
+        identifiedView = null;
+    }
+
+    /**
+     * Returns true when the point is outside the area around the downAt
+     * location
+     */
+    private boolean isOverThreshold(final Location pressed, final Point dragged) {
+        final int xDown = pressed.getX();
+        final int yDown = pressed.getY();
+        final int x = dragged.x;
+        final int y = dragged.y;
+
+        return x > xDown + THRESHOLD || x < xDown - THRESHOLD || y > yDown + THRESHOLD || y < yDown - THRESHOLD;
+    }
+
+    /**
+     * Listener for key presses. Cancels popup and drags, and forwards key
+     * presses to the view that has the keyboard focus.
+     * 
+     * @see java.awt.event.KeyListener#keyPressed(KeyEvent)
+     */
+    @Override
+    public void keyPressed(final KeyEvent ke) {
+        if (isBusy(identifiedView)) {
+            return;
+        }
+
+        lastTyped = null;
+        try {
+            if (ke.getKeyCode() == KeyEvent.VK_ESCAPE && drag != null) {
+                if (drag != null) {
+                    drag.cancel(viewer);
+                    drag = null;
+                }
+                viewer.clearAction();
+            } else if (ke.getKeyCode() == KeyEvent.VK_F5) {
+                draggedView = identifiedView;
+            } else if (draggedView != null && ke.getKeyCode() == KeyEvent.VK_F6) {
+                final ContentDrag content = new ContentDragImpl(draggedView, new Location(), new AbstractView(new NullContent()) {
+                });
+                if (identifiedView != null) {
+                    identifiedView.drop(content);
+                }
+
+                draggedView = null;
+            } else {
+                keyboardManager.pressed(ke.getKeyCode(), ke.getModifiers());
+            }
+            // ke.consume();
+
+            redraw();
+        } catch (final Exception e) {
+            interactionException("keyPressed", e);
+        }
+    }
+
+    /**
+     * Listener for key releases and forward them to the view that has the
+     * keyboard focus.
+     * 
+     * @see java.awt.event.KeyListener#keyReleased(KeyEvent)
+     */
+    @Override
+    public void keyReleased(final KeyEvent ke) {
+        if (isBusy(identifiedView)) {
+            return;
+        }
+        // LOG.debug("key " + KeyEvent.getKeyText(ke.getKeyCode()) +
+        // " released\n");
+
+        try {
+            if (lastTyped == null && ke.getKeyCode() != KeyEvent.VK_SHIFT && ke.getKeyCode() != KeyEvent.VK_ALT && ke.getKeyCode() != KeyEvent.VK_CONTROL) {
+                if (ke.getKeyCode() >= KeyEvent.VK_0 && ke.getKeyCode() <= KeyEvent.VK_DIVIDE) {
+                    LOG.error("no type event for '" + KeyEvent.getKeyText(ke.getKeyCode()) + "':  " + ke);
+                }
+            }
+
+            keyboardManager.released(ke.getKeyCode(), ke.getModifiers());
+            ke.consume();
+            redraw();
+        } catch (final Exception e) {
+            interactionException("keyReleased", e);
+        }
+
+    }
+
+    /**
+     * Listener for key press, and subsequent release, and forward it as one
+     * event to the view that has the keyboard focus.
+     * 
+     * @see java.awt.event.KeyListener#keyTyped(KeyEvent)
+     */
+    @Override
+    public void keyTyped(final KeyEvent ke) {
+        if (isBusy(identifiedView)) {
+            return;
+        }
+
+        final char keyChar = ke.getKeyChar();
+        if (!Character.isISOControl(keyChar)) {
+            // ignoring control keys and the delete key
+            // LOG.debug("typed '" + keyChar + "': " + ke);
+            // LOG.debug("typed " + (int) keyChar);
+            keyboardManager.typed(keyChar);
+            ke.consume();
+            lastTyped = ke;
+            redraw();
+        }
+    }
+
+    private void interactionException(final String action, final Exception e) {
+        LOG.error("error during user interaction: " + action, e);
+        feedbackManager.showException(e);
+    }
+
+    /**
+     * Responds to mouse click events by calling <code>firstClick</code>,
+     * <code>secondClick</code>, and <code>thirdClick</code> on the view that
+     * the mouse is over. Ignored if the mouse is not over a view.
+     * 
+     * @see java.awt.event.MouseListener#mouseClicked(MouseEvent)
+     */
+    @Override
+    public void mouseClicked(final MouseEvent me) {
+        if (isBusy(identifiedView)) {
+            return;
+        }
+        try {
+            final Click click = new ClickImpl(downAt, me.getModifiers());
+            spy.addAction("Mouse clicked " + click.getLocation());
+            if (click.button3() && identifiedView != null) {
+                // ignore popup trigger - dealt with by mousePressed or
+                // mouseReleased (depending on platform)
+            } else if (viewer.isOverlayAvailable()) {
+                overlayClick(click);
+                // } else if (click.button3() && identifiedView != null) {
+                // fireMenuPopup(click);
+            } else {
+                fireClick(click, me.getClickCount());
+            }
+            redraw();
+        } catch (final Exception e) {
+            interactionException("mouseClicked", e);
+        }
+    }
+
+    private void overlayClick(final Click click) {
+        final View overlayView = viewer.getOverlayView();
+        if (overlayView == identifiedView || (identifiedView != null && identifiedView.getParent() != null && overlayView == identifiedView.getParent())) {
+            viewer.firstClick(click);
+        } else {
+            viewer.clearAction();
+        }
+    }
+
+    private void fireMenuPopup(final Click click) {
+        if (identifiedView != null) {
+            spy.addAction(" popup " + downAt + " over " + identifiedView);
+
+            boolean forView = viewer.viewAreaType(new Location(click.getLocation())) == ViewAreaType.VIEW;
+            forView = click.isAlt() ^ forView;
+            final boolean includeExploration = click.isCtrl();
+            final boolean includeDebug = click.isShift();
+            final Location at = click.getLocation();
+            at.move(-14, -10);
+            viewer.popupMenu(identifiedView, at, forView, includeExploration, includeDebug);
+        }
+    }
+
+    private void fireClick(final Click click, final int clickCount) {
+        viewer.setKeyboardFocus(identifiedView);
+
+        switch (clickCount) {
+        case 1:
+            viewer.firstClick(click);
+            break;
+
+        case 2:
+            viewer.secondClick(click);
+            break;
+
+        case 3:
+            viewer.thirdClick(click);
+            break;
+
+        default:
+            break;
+        }
+    }
+
+    /**
+     * Responds to mouse dragged according to the button used. If the left
+     * button then identified view is moved.
+     * 
+     * @see java.awt.event.MouseMotionListener#mouseDragged(MouseEvent)
+     */
+    @Override
+    public void mouseDragged(final MouseEvent me) {
+        if (isBusy(identifiedView)) {
+            return;
+        }
+
+        try {
+            viewer.translate(me);
+
+            final Location location = createLocation(me.getPoint());
+            spy.setLocationInViewer(location);
+
+            if (canDrag) {
+                // checked to ensure that dragging over a view doesn't start a
+                // drag - it should only start when already over a view.
+
+                spy.reset();
+                // viewer.translate(me);
+                if (drag == null) {
+                    // no drag in progress yet
+                    dragStart(me);
+                    redraw();
+                } else {
+                    drag(me);
+                    redraw();
+                }
+            }
+        } catch (final Exception e) {
+            interactionException("mouseDragged", e);
+        }
+
+    }
+
+    /**
+     * event ignored
+     * 
+     * @see java.awt.event.MouseListener#mouseEntered(MouseEvent)
+     */
+    @Override
+    public void mouseEntered(final MouseEvent arg0) {
+    }
+
+    /**
+     * event ignored
+     * 
+     * @see java.awt.event.MouseListener#mouseExited(MouseEvent)
+     */
+    @Override
+    public void mouseExited(final MouseEvent arg0) {
+    }
+
+    /**
+     * responds to mouse moved event by setting the view found underneath the
+     * mouse as the idetified view. Views normally respond by changing the
+     * colour of themselves so they are visual distinct and hence shows itself
+     * as special compared to the rest.
+     * 
+     * @see java.awt.event.MouseMotionListener#mouseMoved(MouseEvent)
+     */
+    @Override
+    public void mouseMoved(final MouseEvent me) {
+        try {
+            if (drag == null) {
+                spy.reset();
+                viewer.translate(me);
+                final Location location = createLocation(me.getPoint());
+                spy.setLocationInViewer(location);
+
+                final View overView = viewer.identifyView(new Location(location), true);
+                spy.setOver(overView);
+
+                spy.addAction("moved " + location);
+
+                if (overView != null) {
+                    if (overView != identifiedView) {
+                        if (identifiedView != null) {
+                            spy.addAction("exited " + identifiedView);
+                            identifiedView.exited();
+                        }
+
+                        if (overView != null) {
+                            spy.addAction("entered " + overView);
+                            overView.entered();
+                        }
+
+                        redraw();
+                        feedbackManager.showBusyState(overView);
+                    }
+                    identifiedView = overView;
+
+                    spy.addTrace("--> mouse moved");
+                    viewer.mouseMoved(location);
+                    spy.addTrace(overView, " mouse location", location);
+                    if ((me.getModifiers() & InputEvent.ALT_MASK) > 0 && overView.getContent() != null) {
+                        final ObjectAdapter object = overView.getContent().getAdapter();
+                        final ViewAreaType area = overView.viewAreaType(location);
+                        feedbackManager.setViewDetail("Over " + location + " [" + area + "] " + object);
+                    }
+
+                    redraw();
+                }
+            }
+        } catch (final Exception e) {
+            interactionException("mouseMoved", e);
+        }
+
+    }
+
+    private boolean isBusy(final View view) {
+        return feedbackManager != null && feedbackManager.isBusy(view);
+    }
+
+    /**
+     * Responds to the mouse pressed event (with the left button pressed) by
+     * initiating a drag. This sets up the <code>View</code>'s dragging state to
+     * the view that the mouse was over when the button was pressed.
+     * 
+     * @see java.awt.event.MouseListener#mousePressed(MouseEvent)
+     */
+    @Override
+    public void mousePressed(final MouseEvent me) {
+        try {
+            if (isBusy(identifiedView)) {
+                return;
+            }
+
+            spy.reset();
+            viewer.translate(me);
+
+            downAt = createLocation(me.getPoint());
+            spy.setDownAt(downAt);
+
+            final Location location = createLocation(me.getPoint());
+            spy.setLocationInViewer(location);
+
+            final View overView = viewer.identifyView(new Location(location), true);
+            spy.setOver(overView);
+            spy.addAction("Mouse pressed " + location);
+            drag = null;
+
+            final Click click = new ClickImpl(downAt, me.getModifiers());
+            if (me.isPopupTrigger()) {
+                if (overView != null) {
+                    fireMenuPopup(click);
+                }
+            } else {
+                viewer.mouseDown(click);
+                // drag should not be valid after double/triple click
+                canDrag = overView != null && me.getClickCount() == 1;
+                identifiedView = overView;
+            }
+            redraw();
+        } catch (final Exception e) {
+            interactionException("mousePressed", e);
+        }
+
+    }
+
+    /**
+     * Responds to the mouse released event (with the left button pressed) by
+     * telling the identified view (the drop zone) that the dragged object is
+     * being dropped on it (via the views <code>drop</code> method). If the drop
+     * takes place outside of all of the other views then the
+     * <code>workspaceDrop</code> method is called instead to indicate a drop
+     * onto the workspace.
+     * 
+     * @see java.awt.event.MouseListener#mouseReleased(MouseEvent)
+     */
+    @Override
+    public void mouseReleased(final MouseEvent me) {
+        if (isBusy(identifiedView) || downAt == null) {
+            return;
+        }
+
+        try {
+            if (drag != null) {
+                mouseDragged(me);
+
+                final Location location = createLocation(me.getPoint());
+                final View target = viewer.identifyView(new Location(location), false);
+                drag.drag(target, location, me.getModifiers());
+                // viewer.clearStatus();
+                drag.end(viewer);
+                redraw();
+
+                drag = null;
+            }
+
+            final Click click = new ClickImpl(downAt, me.getModifiers());
+            if (me.isPopupTrigger()) {
+                if (identifiedView != null) {
+                    fireMenuPopup(click);
+                }
+            } else {
+                viewer.mouseUp(click);
+            }
+            redraw();
+        } catch (final Exception e) {
+            interactionException("mouseReleased", e);
+        }
+    }
+
+    private void redraw() {
+        viewer.scheduleRepaint();
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/255ef514/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/awt/KeyboardManager.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/awt/KeyboardManager.java b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/awt/KeyboardManager.java
new file mode 100644
index 0000000..05b35c5
--- /dev/null
+++ b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/awt/KeyboardManager.java
@@ -0,0 +1,228 @@
+/*
+ *  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.awt;
+
+import java.awt.event.InputEvent;
+import java.awt.event.KeyEvent;
+
+import org.apache.log4j.Logger;
+
+import org.apache.isis.core.commons.exceptions.IsisException;
+import org.apache.isis.viewer.dnd.drawing.Location;
+import org.apache.isis.viewer.dnd.interaction.KeyboardActionImpl;
+import org.apache.isis.viewer.dnd.view.FocusManager;
+import org.apache.isis.viewer.dnd.view.KeyboardAction;
+import org.apache.isis.viewer.dnd.view.View;
+
+public class KeyboardManager {
+    private static final Logger LOG = Logger.getLogger(KeyboardManager.class);
+    private final XViewer viewer;
+    private FocusManager focusManager;
+
+    public KeyboardManager(final XViewer viewer) {
+        this.viewer = viewer;
+    }
+
+    private View getFocus() {
+        return focusManager == null ? null : focusManager.getFocus();
+        // View focus = viewer.getFocus();
+        // return focus == null ? null : focus.getView();
+    }
+
+    /*
+     * At the moment, as a fudge, the text field is calling its parent's
+     * keyPressed method for enter presses.
+     */
+    public void pressed(final int keyCode, final int modifiers) {
+        if (ignoreKey(keyCode)) {
+            return;
+        }
+        LOG.debug("key " + KeyEvent.getKeyModifiersText(modifiers) + " '" + KeyEvent.getKeyText(keyCode) + "' pressed");
+
+        final KeyboardAction keyboardAction = new KeyboardActionImpl(keyCode, modifiers);
+
+        if (viewer.isOverlayAvailable()) {
+            viewer.getOverlayView().keyPressed(keyboardAction);
+            if (!keyboardAction.isConsumed() && keyCode == KeyEvent.VK_F1) {
+                viewer.openHelp(viewer.getOverlayView());
+                // help(viewer.getOverlayView());
+            }
+            return;
+        }
+
+        final View keyboardFocus = getFocus();
+        if (keyboardFocus == null) {
+            // throw new ObjectAdapterRuntimeException("No focus set");
+            LOG.debug("No focus set");
+            return;
+        }
+
+        keyboardFocus.keyPressed(keyboardAction);
+
+        if (keyboardAction.isConsumed()) {
+            return;
+        }
+
+        if ((modifiers & InputEvent.SHIFT_MASK) == InputEvent.SHIFT_MASK && keyCode == KeyEvent.VK_F10) {
+            final Location location = keyboardFocus.getAbsoluteLocation();
+            location.add(20, 14);
+            viewer.popupMenu(keyboardFocus, location, true, false, false);
+            return;
+        }
+
+        // this should really check the modifiers to ensure there are none in
+        // use.
+        if (keyCode == KeyEvent.VK_F10) {
+            final Location location = keyboardFocus.getAbsoluteLocation();
+            location.add(20, 14);
+            viewer.popupMenu(keyboardFocus, location, false, false, false);
+            return;
+        }
+        /*
+         * if(keyCode == KeyEvent.VK_ENTER) { //viewer.firstClick(new
+         * Click(keyboardFocus, keyboardFocus.getLocation(), modifiers));
+         * Location location = keyboardFocus.getAbsoluteLocation();
+         * location.add(1, 1); viewer.secondClick(new Click(keyboardFocus,
+         * location, modifiers)); //viewer.thirdClick(new Click(keyboardFocus,
+         * keyboardFocus.getLocation(), modifiers)); return; }
+         */
+
+        if (keyCode == KeyEvent.VK_F4 && (modifiers & InputEvent.CTRL_MASK) == InputEvent.CTRL_MASK) {
+            // TODO close window
+            return;
+        }
+
+        if (keyCode == KeyEvent.VK_DOWN) {
+            focusManager.focusFirstChildView();
+            // focusNextSubview(keyboardFocus);
+            return;
+        }
+
+        if (keyCode == KeyEvent.VK_UP) {
+            focusManager.focusParentView();
+            // focusPreviousSubview(keyboardFocus);
+            return;
+        }
+
+        if (keyCode == KeyEvent.VK_HOME) {
+            viewer.makeRootFocus();
+            return;
+        }
+
+        if (keyCode == KeyEvent.VK_RIGHT) {
+            focusManager.focusNextView();
+            // focusNextPeerView(keyboardFocus);
+            return;
+        }
+
+        if (keyCode == KeyEvent.VK_LEFT) {
+            focusManager.focusPreviousView();
+            // focusPreviousPeerView(keyboardFocus);
+            return;
+        }
+
+        int action = 0;
+
+        if (keyCode == KeyEvent.VK_F1) {
+            viewer.openHelp(keyboardFocus);
+        } else if (keyCode == KeyEvent.VK_TAB) {
+            action = tab(modifiers);
+        }
+
+        switch (action) {
+        case KeyboardAction.NEXT_VIEW:
+            focusManager.focusNextView();
+            // focusNextSubview(keyboardFocus);
+            break;
+        case KeyboardAction.PREVIOUS_VIEW:
+            focusManager.focusPreviousView();
+            // focusPreviousSubview(keyboardFocus);
+            break;
+        case KeyboardAction.NEXT_WINDOW:
+            focusManager.focusParentView();
+            // focusNextRootView(keyboardFocus);
+            break;
+        case KeyboardAction.PREVIOUS_WINDOW:
+            focusManager.focusFirstChildView();
+            break;
+        }
+    }
+
+    private boolean ignoreKey(final int keyCode) {
+        return keyCode == KeyEvent.VK_SHIFT || keyCode == KeyEvent.VK_CONTROL || keyCode == KeyEvent.VK_ALT;
+    }
+
+    private int tab(final int modifiers) {
+        int action;
+        if ((modifiers & InputEvent.CTRL_MASK) == InputEvent.CTRL_MASK) {
+            if ((modifiers & InputEvent.SHIFT_MASK) == InputEvent.SHIFT_MASK) {
+                action = KeyboardAction.PREVIOUS_WINDOW;
+            } else {
+                action = KeyboardAction.NEXT_WINDOW;
+            }
+        } else {
+            if ((modifiers & InputEvent.SHIFT_MASK) == InputEvent.SHIFT_MASK) {
+                action = KeyboardAction.PREVIOUS_VIEW;
+            } else {
+                action = KeyboardAction.NEXT_VIEW;
+            }
+        }
+        return action;
+    }
+
+    public void released(final int keyCode, final int modifiers) {
+        if (ignoreKey(keyCode)) {
+            return;
+        }
+
+        LOG.debug("key " + KeyEvent.getKeyText(keyCode) + " released\n");
+        final View keyboardFocus = getFocus();
+        if (keyboardFocus != null) {
+            keyboardFocus.keyReleased(new KeyboardActionImpl(keyCode, modifiers));
+        }
+    }
+
+    public void typed(final char keyChar) {
+        LOG.debug("typed '" + keyChar + "'");
+
+        if (viewer.isOverlayAvailable()) {
+            viewer.getOverlayView().keyTyped(new KeyboardActionImpl(keyChar, 0));
+            return;
+        }
+
+        final View keyboardFocus = getFocus();
+        if (keyboardFocus != null) {
+            if (!Character.isISOControl(keyChar)) {
+                keyboardFocus.keyTyped(new KeyboardActionImpl(keyChar, 0));
+            }
+        }
+    }
+
+    public FocusManager getFocusManager() {
+        return focusManager;
+    }
+
+    public void setFocusManager(final FocusManager focusManager) {
+        if (focusManager == null) {
+            throw new IsisException("No focus manager set up");
+        }
+        this.focusManager = focusManager;
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/255ef514/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/awt/LoginDialog.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/awt/LoginDialog.java b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/awt/LoginDialog.java
new file mode 100644
index 0000000..fb13b5e
--- /dev/null
+++ b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/awt/LoginDialog.java
@@ -0,0 +1,241 @@
+/*
+ *  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.awt;
+
+import java.awt.BorderLayout;
+import java.awt.Button;
+import java.awt.Color;
+import java.awt.Cursor;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.Frame;
+import java.awt.GridLayout;
+import java.awt.Insets;
+import java.awt.Label;
+import java.awt.Panel;
+import java.awt.TextField;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyEvent;
+import java.awt.event.KeyListener;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+
+import org.apache.log4j.Logger;
+
+import org.apache.isis.core.commons.authentication.AuthenticationSession;
+import org.apache.isis.core.commons.lang.StringUtils;
+import org.apache.isis.core.runtime.authentication.AuthenticationManager;
+import org.apache.isis.core.runtime.authentication.AuthenticationRequestPassword;
+
+public class LoginDialog extends Frame implements ActionListener, KeyListener {
+    private static final long serialVersionUID = 1L;
+    private static final Logger LOG = Logger.getLogger(LoginDialog.class);
+    private final static int BORDER = 12;
+    private TextField user;
+    private TextField password;
+    private Button cancel;
+    private Button login;
+
+    private static String CANCEL_LABEL = " Cancel ";
+    private static String LOGIN_LABEL = " Login ";
+    private boolean logIn = true;
+    private final AuthenticationManager authenticationManager;
+    private AuthenticationSession session;
+    private Label instructionLabel;
+
+    public LoginDialog(final AuthenticationManager authenticationManager) {
+        super("Apache Isis Login");
+        this.authenticationManager = authenticationManager;
+
+        setBackground(new Color(0xe0e0e0));
+
+        AWTUtilities.addWindowIcon(this, "login-logo.png");
+
+        addWindowListener(new WindowAdapter() {
+            @Override
+            public void windowClosing(final WindowEvent e) {
+                cancel(e.getComponent());
+            }
+        });
+
+        setLayout(new BorderLayout(0, 10));
+
+        createInstructionLabel();
+        createLoginFields();
+        createButtonsPanel();
+
+        setResizable(false);
+        pack();
+        final int height = getSize().height;
+        final int width = getFontMetrics(getFont()).charWidth('x') * 48;
+        setSize(width, height);
+        final Dimension screen = getToolkit().getScreenSize();
+
+        int x = (screen.width / 2) - (width / 2);
+
+        if ((screen.width / screen.height) >= 2) {
+            x = (screen.width / 4) - (width / 2);
+        }
+
+        final int y = (screen.height / 2) - (height / 2);
+        setLocation(x, y);
+        user.requestFocus();
+    }
+
+    private void createInstructionLabel() {
+        instructionLabel = new Label("Please enter your user name and password.");
+        add(instructionLabel, BorderLayout.NORTH);
+    }
+
+    private void createLoginFields() {
+        final Panel form = new Panel(new GridLayout(2, 2, 6, 8)) {
+            private static final long serialVersionUID = 1L;
+
+            @Override
+            public Insets getInsets() {
+                return new Insets(12, 0, 6, 80);
+            }
+        };
+        add(form, BorderLayout.CENTER);
+
+        form.add(new Label("User name:", Label.RIGHT));
+        form.add(user = new TextField());
+        user.addKeyListener(this);
+
+        form.add(new Label("Password:", Label.RIGHT));
+        form.add(password = new TextField());
+        password.addKeyListener(this);
+        password.setEchoChar('*');
+    }
+
+    private void createButtonsPanel() {
+        final Panel buttons = new Panel(new FlowLayout(FlowLayout.RIGHT));
+        add(buttons, BorderLayout.SOUTH);
+
+        buttons.add(cancel = new Button(CANCEL_LABEL));
+        cancel.addActionListener(this);
+        cancel.addKeyListener(this);
+
+        buttons.add(login = new Button(LOGIN_LABEL));
+        login.addActionListener(this);
+        login.addKeyListener(this);
+    }
+
+    @Override
+    public Insets getInsets() {
+        final Insets in = super.getInsets();
+        in.top += BORDER;
+        in.bottom += BORDER / 2;
+        in.left += BORDER;
+        in.right += BORDER;
+        return in;
+    }
+
+    @Override
+    public void actionPerformed(final ActionEvent evt) {
+        action(evt.getSource());
+    }
+
+    @Override
+    public void keyPressed(final KeyEvent e) {
+        // ignore
+    }
+
+    @Override
+    public void keyReleased(final KeyEvent e) {
+        if (e.getKeyCode() == KeyEvent.VK_ENTER) {
+            action(e.getComponent());
+        }
+        if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
+            cancel(e.getComponent());
+        }
+    }
+
+    @Override
+    public void keyTyped(final KeyEvent e) {
+        // ignore
+    }
+
+    private synchronized void cancel(final Object widget) {
+        logIn = false;
+        notify();
+    }
+
+    private synchronized void action(final Object widget) {
+        if (widget == cancel) {
+            cancel(widget);
+        } else if (widget == login || widget == password) {
+            setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
+            instructionLabel.setText("Authorising...");
+            instructionLabel.setForeground(Color.BLACK);
+
+            final AuthenticationRequestPassword authenticationRequest = new AuthenticationRequestPassword(getUser(), getPassword());
+            session = authenticationManager.authenticate(authenticationRequest);
+            if (session == null) {
+                try {
+                    Thread.sleep(750);
+                } catch (final InterruptedException ignore) {
+                }
+                instructionLabel.setText("Invalid user name or password; please try again.");
+                instructionLabel.setForeground(Color.RED);
+            } else {
+                logIn = true;
+                notify();
+            }
+            setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+        } else if (widget == user) {
+            password.requestFocus();
+        }
+    }
+
+    @Override
+    public void dispose() {
+        LOG.debug("dispose...");
+        super.dispose();
+        LOG.debug("...disposed");
+
+    }
+
+    private String getUser() {
+        return StringUtils.removeTabs(user.getText()).trim();
+    }
+
+    public void setUserName(final String name) {
+        user.setText(name);
+    }
+
+    private String getPassword() {
+        return StringUtils.removeTabs(password.getText()).trim();
+    }
+
+    public AuthenticationSession getSession() {
+        return session;
+    }
+
+    public synchronized boolean login() {
+        try {
+            wait();
+        } catch (final InterruptedException e) {
+        }
+        return logIn;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/255ef514/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/awt/OverlayDebugFrame.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/awt/OverlayDebugFrame.java b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/awt/OverlayDebugFrame.java
new file mode 100644
index 0000000..c79b3a6
--- /dev/null
+++ b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/awt/OverlayDebugFrame.java
@@ -0,0 +1,50 @@
+/*
+ *  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.awt;
+
+import org.apache.isis.core.commons.debug.DebuggableWithTitle;
+import org.apache.isis.viewer.dnd.view.View;
+import org.apache.isis.viewer.dnd.view.base.AbstractView;
+import org.apache.isis.viewer.dnd.view.content.NullContent;
+import org.apache.isis.viewer.dnd.view.debug.DebugView;
+
+public class OverlayDebugFrame extends DebugFrame {
+    private static final long serialVersionUID = 1L;
+    private final XViewer viewer;
+
+    public OverlayDebugFrame(final XViewer viewer) {
+        super();
+        this.viewer = viewer;
+    }
+
+    @Override
+    protected DebuggableWithTitle[] getInfo() {
+        final View overlay = viewer.getOverlayView();
+        final DebugView debugView = new DebugView(overlay == null ? new EmptyView() : overlay);
+        return new DebuggableWithTitle[] { debugView };
+    }
+
+    class EmptyView extends AbstractView {
+        public EmptyView() {
+            super(new NullContent());
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/255ef514/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/awt/PrintOption.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/awt/PrintOption.java b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/awt/PrintOption.java
new file mode 100644
index 0000000..a1c19e6
--- /dev/null
+++ b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/awt/PrintOption.java
@@ -0,0 +1,74 @@
+/*
+ *  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.awt;
+
+import java.awt.Dimension;
+import java.awt.Frame;
+import java.awt.Graphics;
+import java.awt.PrintJob;
+import java.awt.Toolkit;
+
+import org.apache.isis.core.metamodel.consent.Allow;
+import org.apache.isis.core.metamodel.consent.Consent;
+import org.apache.isis.viewer.dnd.drawing.Location;
+import org.apache.isis.viewer.dnd.view.View;
+import org.apache.isis.viewer.dnd.view.Workspace;
+import org.apache.isis.viewer.dnd.view.option.UserActionAbstract;
+
+public class PrintOption extends UserActionAbstract {
+    private final int HEIGHT = 60;
+    private final int LEFT = 60;
+
+    public PrintOption() {
+        super("Print...");
+    }
+
+    @Override
+    public Consent disabled(final View component) {
+        return Allow.DEFAULT;
+    }
+
+    @Override
+    public void execute(final Workspace workspace, final View view, final Location at) {
+        final Frame frame = new Frame();
+        final PrintJob job = Toolkit.getDefaultToolkit().getPrintJob(frame, "Print object", null);
+
+        if (job != null) {
+            final Graphics pg = job.getGraphics();
+            final Dimension pageSize = job.getPageDimension();
+
+            if (pg != null) {
+                pg.translate(LEFT, HEIGHT);
+                pg.drawRect(0, 0, pageSize.width - LEFT - 1, pageSize.height - HEIGHT - 1);
+                view.print(new PrintCanvas(pg, view));
+                pg.dispose();
+            }
+
+            job.end();
+        }
+        frame.dispose();
+    }
+
+    private class PrintCanvas extends AwtCanvas {
+        PrintCanvas(final Graphics g, final View view) {
+            super(g, null, 0, 0, view.getSize().getWidth(), view.getSize().getHeight());
+        }
+    }
+}