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

[41/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/RenderingArea.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/awt/RenderingArea.java b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/awt/RenderingArea.java
new file mode 100644
index 0000000..b461f75
--- /dev/null
+++ b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/awt/RenderingArea.java
@@ -0,0 +1,57 @@
+/*
+ *  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.Cursor;
+import java.awt.Dimension;
+import java.awt.Image;
+import java.awt.Insets;
+import java.awt.event.KeyListener;
+import java.awt.event.MouseListener;
+import java.awt.event.MouseMotionListener;
+
+public interface RenderingArea {
+
+    Dimension getSize();
+
+    Image createImage(int w, int h);
+
+    Insets getInsets();
+
+    void repaint();
+
+    void repaint(int x, int y, int width, int height);
+
+    void setCursor(Cursor cursor);
+
+    void dispose();
+
+    void setBounds(int x, int y, int width, int height);
+
+    void show();
+
+    void addMouseMotionListener(MouseMotionListener interactionHandler);
+
+    void addMouseListener(MouseListener interactionHandler);
+
+    void addKeyListener(KeyListener interactionHandler);
+
+    String selectFilePath(String title, String directory);
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/255ef514/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/awt/ShutdownDialog.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/awt/ShutdownDialog.java b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/awt/ShutdownDialog.java
new file mode 100644
index 0000000..aec7d28
--- /dev/null
+++ b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/awt/ShutdownDialog.java
@@ -0,0 +1,149 @@
+/*
+ *  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.Button;
+import java.awt.Dialog;
+import java.awt.Dimension;
+import java.awt.GridLayout;
+import java.awt.Insets;
+import java.awt.Label;
+import java.awt.Panel;
+import java.awt.Point;
+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;
+
+class ShutdownDialog extends Dialog implements ActionListener, KeyListener {
+    private static final long serialVersionUID = 1L;
+    private static final Logger LOG = Logger.getLogger(ShutdownDialog.class);
+    private final static int BORDER = 10;
+    private Button cancel;
+    private Button quit;
+    private static String CANCEL_LABEL = "Cancel";
+    private static String QUIT_LABEL = "Ok";
+
+    public ShutdownDialog(final ViewerFrame owner) {
+        super(owner, "Apache Isis", true);
+
+        // AWTUtilities.addWindowIcon(this, "shutdown-logo.gif");
+
+        setLayout(new GridLayout(2, 3, 10, 10));
+
+        add(new Label("Exit Apache Isis?", Label.LEFT));
+
+        add(new Panel());
+        add(new Panel());
+        add(new Panel());
+
+        add(quit = new Button(QUIT_LABEL));
+        quit.addActionListener(this);
+        quit.addKeyListener(this);
+
+        add(cancel = new Button(CANCEL_LABEL));
+        cancel.addActionListener(this);
+        cancel.addKeyListener(this);
+
+        pack();
+        final int width = getSize().width; // getWidth();
+        final int height = getSize().height; // getHeight();
+        final Dimension screen = owner.getSize();
+        final Point point = owner.getLocation();
+
+        final int x = point.x + (screen.width / 2) - (width / 2);
+        final int y = point.y + (screen.height / 2) - (height / 2);
+
+        setLocation(x, y);
+        setVisible(true);
+        quit.requestFocus();
+
+        addWindowListener(new WindowAdapter() {
+            @Override
+            public void windowClosing(final WindowEvent e) {
+                dispose();
+            }
+        });
+    }
+
+    @Override
+    public Insets getInsets() {
+        final Insets in = super.getInsets();
+        in.top += BORDER;
+        in.bottom += BORDER;
+        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) {
+        dispose();
+    }
+
+    private synchronized void action(final Object widget) {
+        if (widget == cancel) {
+            cancel(widget);
+        } else if (widget == quit) {
+            quit();
+        }
+    }
+
+    private void quit() {
+        dispose();
+        ((ViewerFrame) getParent()).quit();
+    }
+
+    @Override
+    public void dispose() {
+        LOG.debug("dispose...");
+        super.dispose();
+        LOG.debug("...disposed");
+
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/255ef514/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/awt/SpyWindow.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/awt/SpyWindow.java b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/awt/SpyWindow.java
new file mode 100644
index 0000000..09f8ab1
--- /dev/null
+++ b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/awt/SpyWindow.java
@@ -0,0 +1,97 @@
+/*
+ *  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.Frame;
+import java.awt.Graphics;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+
+import org.apache.isis.viewer.dnd.view.InteractionSpyWindow;
+
+class SpyWindow implements InteractionSpyWindow {
+    private int event;
+    private String label[][] = new String[2][20];
+    private String[] trace = new String[60];
+    private int traceIndex;
+    private SpyFrame frame;
+
+    class SpyFrame extends Frame {
+        private static final long serialVersionUID = 1L;
+
+        public SpyFrame() {
+            super("View/Interaction Spy");
+            addWindowListener(new WindowAdapter() {
+                @Override
+                public void windowClosing(final WindowEvent e) {
+                    close();
+                }
+            });
+        }
+
+        @Override
+        public void paint(final Graphics g) {
+            int baseline = getInsets().top + 15;
+
+            g.drawString("Event " + event, 10, baseline);
+            baseline += 18;
+
+            for (int i = 0; i < label[0].length; i++) {
+                if (label[0][i] != null) {
+                    g.drawString(label[0][i], 10, baseline);
+                    g.drawString(label[1][i], 150, baseline);
+                }
+                baseline += 12;
+            }
+
+            baseline += 6;
+            for (int i = 0; i < traceIndex; i++) {
+                if (trace[i] != null) {
+                    g.drawString(trace[i], 10, baseline);
+                }
+                baseline += 12;
+            }
+        }
+    }
+
+    @Override
+    public void display(final int event, final String label[][], final String[] trace, final int traceIndex) {
+        if (frame != null) {
+            this.event = event;
+            this.traceIndex = traceIndex;
+            this.label = label;
+            this.trace = trace;
+            frame.repaint();
+        }
+    }
+
+    @Override
+    public void open() {
+        frame = new SpyFrame();
+        frame.setBounds(10, 10, 800, 500);
+        frame.setVisible(true);
+    }
+
+    @Override
+    public void close() {
+        frame.setVisible(false);
+        frame.dispose();
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/255ef514/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/awt/ViewerFrame.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/awt/ViewerFrame.java b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/awt/ViewerFrame.java
new file mode 100644
index 0000000..0e6241c
--- /dev/null
+++ b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/awt/ViewerFrame.java
@@ -0,0 +1,146 @@
+/*
+ *  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.FileDialog;
+import java.awt.Frame;
+import java.awt.Graphics;
+import java.awt.Image;
+import java.awt.event.ComponentAdapter;
+import java.awt.event.ComponentEvent;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+import org.apache.isis.core.runtime.about.AboutIsis;
+import org.apache.isis.viewer.dnd.drawing.ColorsAndFonts;
+import org.apache.isis.viewer.dnd.view.Toolkit;
+
+public class ViewerFrame extends Frame implements RenderingArea {
+    private static final String DEFAULT_TITLE = "Apache Isis";
+    private static final long serialVersionUID = 1L;
+    private XViewer viewer;
+
+    public ViewerFrame() {
+    }
+
+    {
+        setBackground(((AwtColor) Toolkit.getColor(ColorsAndFonts.COLOR_APPLICATION)).getAwtColor());
+
+        AWTUtilities.addWindowIcon(this, "application-logo.png");
+        setTitle(null);
+
+        /*
+         * compensate for change in tab handling in Java 1.4
+         */
+        try {
+            final Class<?> c = getClass();
+            final Method m = c.getMethod("setFocusTraversalKeysEnabled", new Class[] { Boolean.TYPE });
+            m.invoke(this, new Object[] { Boolean.FALSE });
+        } catch (final SecurityException e1) {
+            e1.printStackTrace();
+        } catch (final NoSuchMethodException ignore) {
+            /*
+             * Ignore no such method exception as this method is only available,
+             * but needed, in version 1.4 and later.
+             */
+        } catch (final IllegalArgumentException e) {
+            e.printStackTrace();
+        } catch (final IllegalAccessException e) {
+            e.printStackTrace();
+        } catch (final InvocationTargetException e) {
+            e.printStackTrace();
+        }
+    }
+
+    @Override
+    public boolean imageUpdate(final Image img, final int flags, final int x, final int y, final int w, final int h) {
+        repaint();
+        return true;
+    }
+
+    /**
+     * Calls <code>update()</code> to do double-buffered drawing of all views.
+     * 
+     * @see #update(Graphics)
+     * @see java.awt.Component#paint(Graphics)
+     */
+    @Override
+    public final void paint(final Graphics g) {
+        update(g);
+    }
+
+    public void quit() {
+        viewer.quit();
+    }
+
+    /**
+     * Paints the double-buffered image. Calls the <code>draw()</code> method on
+     * each top-level view.
+     * 
+     * @see java.awt.Component#update(Graphics)
+     */
+    @Override
+    public void update(final Graphics g) {
+        viewer.paint(g);
+    }
+
+    public void setViewer(final XViewer viewer) {
+        this.viewer = viewer;
+    }
+
+    public void init() {
+        addWindowListener(new WindowAdapter() {
+            @Override
+            public void windowClosing(final WindowEvent e) {
+                new ShutdownDialog(ViewerFrame.this);
+            }
+        });
+
+        addComponentListener(new ComponentAdapter() {
+            @Override
+            public void componentResized(final ComponentEvent e) {
+                ViewerFrame.this.viewer.sizeChange();
+            }
+
+            @Override
+            public void componentMoved(final ComponentEvent e) {
+                ViewerFrame.this.viewer.locationChange(getLocation().x, getLocation().y);
+            }
+        });
+    }
+
+    @Override
+    public void setTitle(final String title) {
+        final String application = AboutIsis.getApplicationName();
+        final String str = title == null ? (application == null ? DEFAULT_TITLE : application) : title;
+        super.setTitle(str);
+    }
+
+    @Override
+    public String selectFilePath(final String title, final String directory) {
+        final FileDialog dlg = new FileDialog(this, title);
+        dlg.setVisible(true);
+
+        final String path = dlg.getDirectory() + dlg.getFile();
+        return path;
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/255ef514/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/awt/XFeedbackManager.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/awt/XFeedbackManager.java b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/awt/XFeedbackManager.java
new file mode 100644
index 0000000..2625a92
--- /dev/null
+++ b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/awt/XFeedbackManager.java
@@ -0,0 +1,266 @@
+/*
+ *  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.Cursor;
+import java.util.List;
+import java.util.Vector;
+
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.runtimes.dflt.runtime.system.context.IsisContext;
+import org.apache.isis.runtimes.dflt.runtime.system.transaction.MessageBroker;
+import org.apache.isis.viewer.dnd.view.BackgroundTask;
+import org.apache.isis.viewer.dnd.view.Content;
+import org.apache.isis.viewer.dnd.view.Feedback;
+import org.apache.isis.viewer.dnd.view.ObjectContent;
+import org.apache.isis.viewer.dnd.view.View;
+import org.apache.isis.viewer.dnd.view.message.ExceptionMessageContent;
+import org.apache.isis.viewer.dnd.view.message.TextMessageContent;
+
+public class XFeedbackManager implements Feedback {
+    private final XViewer viewer;
+    private final Vector<ObjectAdapter> busy = new Vector<ObjectAdapter>();
+    private String messages;
+    private String view;
+    private String action;
+    private String error;
+    private String message;
+    private Cursor cursor;
+
+    public XFeedbackManager(final XViewer viewer) {
+        this.viewer = viewer;
+    }
+
+    @Override
+    public String getStatusBarOutput() {
+        final StringBuffer text = new StringBuffer();
+        append(text, view);
+        append(text, action);
+        append(text, error);
+        append(text, message);
+        append(text, messages);
+        return text.toString();
+
+        // for debug
+        // return "[view: " + view + "] [action: " + action + "] [error: " +
+        // error + "] [message: " + message
+        // + "] [messages:" + messages + "]";
+    }
+
+    private void append(final StringBuffer text, final String entry) {
+        if (entry != null && !entry.equals("")) {
+            if (text.length() > 0) {
+                text.append(";  ");
+            }
+            text.append(entry);
+        }
+    }
+
+    // REVIEW why can only objects be set to busy? Specifically the service icon
+    // do not show as bust when a
+    // long standing option is being set up when a menu is being created.
+    @Override
+    public void setBusy(final View view, final BackgroundTask task) {
+        final Content content = view.getContent();
+        if (content != null && content.isObject()) {
+            final ObjectAdapter object = ((ObjectContent) content).getObject();
+            busy.addElement(object);
+        }
+        showBusyState(view);
+
+        message = "BUSY";
+        // Don't force repaint here, else an infinite loop forms as the layout
+    }
+
+    @Override
+    public void clearBusy(final View view) {
+        if (view.getContent().isObject()) {
+            final ObjectAdapter object = ((ObjectContent) view.getContent()).getObject();
+            busy.removeElement(object);
+            // showDefaultCursor();
+        }
+        showBusyState(view);
+
+        if (busy.size() == 0) {
+            message = "";
+            viewer.forcePaintOfStatusBar();
+        }
+    }
+
+    @Override
+    public boolean isBusy(final View view) {
+        if (view != null) {
+            final Content content = view.getContent();
+            if (content != null && content.isObject()) {
+                final ObjectAdapter object = ((ObjectContent) content).getObject();
+                if (busy.contains(object)) {
+                    return true;
+                }
+            }
+            final View parent = view.getParent();
+            return parent != null && isBusy(parent);
+        }
+        return false;
+    }
+
+    @Override
+    public void showBusyState(final View view) {
+        Cursor cursor;
+        if (isBusy(view)) {
+            cursor = Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR);
+        } else {
+            cursor = this.cursor;
+        }
+        viewer.setCursor(cursor);
+    }
+
+    @Override
+    public void setViewDetail(final String text) {
+        view = text;
+        viewer.forcePaintOfStatusBar();
+    }
+
+    @Override
+    public void addMessage(final String text) {
+        message = text;
+        viewer.forcePaintOfStatusBar();
+    }
+
+    @Override
+    public void clearAction() {
+        action = null;
+        viewer.forcePaintOfStatusBar();
+    }
+
+    @Override
+    public void setAction(final String text) {
+        action = text;
+        viewer.forcePaintOfStatusBar();
+    }
+
+    @Override
+    public void setError(final String text) {
+        error = text;
+        viewer.forcePaintOfStatusBar();
+    }
+
+    @Override
+    public void clearError() {
+        error = null;
+        viewer.forcePaintOfStatusBar();
+    }
+
+    @Override
+    public void showMessagesAndWarnings() {
+        this.messages = getMessageBroker().getMessagesCombined();
+
+        // TODO this is common across viewers so should be in common code.
+        final List<String> warnings = getMessageBroker().getWarnings();
+        for (final String warning : warnings) {
+            final TextMessageContent content = new TextMessageContent("Warning", warning);
+            viewer.showDialog(content);
+        }
+    }
+
+    private MessageBroker getMessageBroker() {
+        return IsisContext.getMessageBroker();
+    }
+
+    @Override
+    public void showException(final Throwable e) {
+        final ExceptionMessageContent content = new ExceptionMessageContent(e);
+        viewer.showDialog(content);
+    }
+
+    @Override
+    public void showArrowCursor() {
+        setCursor(Cursor.DEFAULT_CURSOR);
+    }
+
+    @Override
+    public void showCrosshairCursor() {
+        setCursor(Cursor.CROSSHAIR_CURSOR);
+    }
+
+    @Override
+    public void showDefaultCursor() {
+        setCursor(Cursor.DEFAULT_CURSOR);
+    }
+
+    @Override
+    public void showHandCursor() {
+        setCursor(Cursor.HAND_CURSOR);
+    }
+
+    @Override
+    public void showMoveCursor() {
+        setCursor(Cursor.MOVE_CURSOR);
+    }
+
+    @Override
+    public void showResizeDownCursor() {
+        setCursor(Cursor.S_RESIZE_CURSOR);
+    }
+
+    @Override
+    public void showResizeDownLeftCursor() {
+        setCursor(Cursor.SW_RESIZE_CURSOR);
+    }
+
+    @Override
+    public void showResizeDownRightCursor() {
+        setCursor(Cursor.SE_RESIZE_CURSOR);
+    }
+
+    @Override
+    public void showResizeLeftCursor() {
+        setCursor(Cursor.W_RESIZE_CURSOR);
+    }
+
+    @Override
+    public void showResizeRightCursor() {
+        setCursor(Cursor.E_RESIZE_CURSOR);
+    }
+
+    @Override
+    public void showResizeUpCursor() {
+        setCursor(Cursor.N_RESIZE_CURSOR);
+    }
+
+    @Override
+    public void showResizeUpLeftCursor() {
+        setCursor(Cursor.NW_RESIZE_CURSOR);
+    }
+
+    @Override
+    public void showResizeUpRightCursor() {
+        setCursor(Cursor.NE_RESIZE_CURSOR);
+    }
+
+    @Override
+    public void showTextCursor() {
+        setCursor(Cursor.TEXT_CURSOR);
+    }
+
+    private void setCursor(final int cursorStyle) {
+        cursor = Cursor.getPredefinedCursor(cursorStyle);
+        viewer.setCursor(cursor);
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/255ef514/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/awt/XViewer.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/awt/XViewer.java b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/awt/XViewer.java
new file mode 100644
index 0000000..9cbd9b0
--- /dev/null
+++ b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/awt/XViewer.java
@@ -0,0 +1,834 @@
+/*
+ *  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.Cursor;
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.Image;
+import java.awt.Insets;
+import java.awt.Rectangle;
+import java.awt.datatransfer.Clipboard;
+import java.awt.datatransfer.DataFlavor;
+import java.awt.datatransfer.StringSelection;
+import java.awt.datatransfer.Transferable;
+import java.awt.event.MouseEvent;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.log4j.Logger;
+
+import org.apache.isis.core.commons.debug.DebuggableWithTitle;
+import org.apache.isis.core.commons.exceptions.IsisException;
+import org.apache.isis.core.commons.lang.ToString;
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.runtimes.dflt.runtime.system.context.IsisContext;
+import org.apache.isis.viewer.dnd.drawing.Background;
+import org.apache.isis.viewer.dnd.drawing.Bounds;
+import org.apache.isis.viewer.dnd.drawing.Canvas;
+import org.apache.isis.viewer.dnd.drawing.ColorsAndFonts;
+import org.apache.isis.viewer.dnd.drawing.Location;
+import org.apache.isis.viewer.dnd.drawing.Size;
+import org.apache.isis.viewer.dnd.drawing.Text;
+import org.apache.isis.viewer.dnd.help.HelpViewer;
+import org.apache.isis.viewer.dnd.util.Properties;
+import org.apache.isis.viewer.dnd.view.Click;
+import org.apache.isis.viewer.dnd.view.Content;
+import org.apache.isis.viewer.dnd.view.DragEvent;
+import org.apache.isis.viewer.dnd.view.DragStart;
+import org.apache.isis.viewer.dnd.view.FocusManager;
+import org.apache.isis.viewer.dnd.view.InteractionSpy;
+import org.apache.isis.viewer.dnd.view.ObjectContent;
+import org.apache.isis.viewer.dnd.view.Placement;
+import org.apache.isis.viewer.dnd.view.ShutdownListener;
+import org.apache.isis.viewer.dnd.view.Toolkit;
+import org.apache.isis.viewer.dnd.view.UndoStack;
+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.ViewConstants;
+import org.apache.isis.viewer.dnd.view.ViewRequirement;
+import org.apache.isis.viewer.dnd.view.ViewUpdateNotifier;
+import org.apache.isis.viewer.dnd.view.Viewer;
+import org.apache.isis.viewer.dnd.view.base.NullView;
+import org.apache.isis.viewer.dnd.view.border.BackgroundBorder;
+import org.apache.isis.viewer.dnd.view.border.LineBorder;
+import org.apache.isis.viewer.dnd.view.debug.LoggingOptions;
+import org.apache.isis.viewer.dnd.view.menu.PopupMenuContainer;
+import org.apache.isis.viewer.dnd.view.message.MessageContent;
+import org.apache.isis.viewer.dnd.viewer.ApplicationOptions;
+
+public class XViewer implements Viewer {
+    private static final Size NO_SIZE = new Size(0, 0);
+    private static final Logger LOG = Logger.getLogger(Viewer.class);
+    private static final Logger UI_LOG = Logger.getLogger("ui." + Viewer.class.getName());
+    private static final LoggingOptions LOGGING_OPTIONS = new LoggingOptions();
+    private static final NullView CLEAR_OVERLAY = new NullView();
+    private static final Bounds NO_REDRAW = new Bounds();
+
+    private ApplicationOptions APPLICATION_OPTIONS;
+    private final DebugOptions DEBUG_OPTIONS = new DebugOptions(this);
+
+    private Graphics bufferGraphics;
+    private Image doubleBuffer;
+    private boolean doubleBuffering = false;
+    private Insets insets;
+    private Size internalDisplaySize = new Size(1, 1);
+    private ShutdownListener listener;
+    private View overlayView;
+    private final Bounds redrawArea;
+    private int redrawCount = 100000;
+    private RenderingArea renderingArea;
+    private View rootView;
+    private String status;
+    private boolean runningAsExploration;
+    private boolean runningAsPrototype;
+    private InteractionSpy spy;
+    private int statusBarHeight;
+    private final UndoStack undoStack = new UndoStack();
+    protected ViewUpdateNotifier updateNotifier;
+    private KeyboardManager keyboardManager;
+    private HelpViewer helpViewer;
+    private Background background;
+    private Bounds statusBarArea;
+    private XFeedbackManager feedbackManager;
+    private boolean refreshStatus;
+    public boolean showExplorationMenuByDefault;
+    boolean showRepaintArea;
+    private static Boolean isDotNetBool;
+
+    private static boolean isDotNet() {
+        if (isDotNetBool == null) {
+            isDotNetBool = new Boolean(System.getProperty("java.version", "dotnet").equals("dotnet"));
+        }
+        return isDotNetBool.booleanValue();
+    }
+
+    public XViewer() {
+        doubleBuffering = IsisContext.getConfiguration().getBoolean(Properties.PROPERTY_BASE + "double-buffer", true);
+        showExplorationMenuByDefault = IsisContext.getConfiguration().getBoolean(Properties.PROPERTY_BASE + "exploration.show", true);
+        overlayView = CLEAR_OVERLAY;
+        redrawArea = new Bounds();
+    }
+
+    public void addSpyAction(final String actionMessage) {
+        if (spy != null) {
+            spy.addAction(actionMessage);
+        }
+    }
+
+    @Override
+    public void addToNotificationList(final View view) {
+        updateNotifier.add(view.getView());
+    }
+
+    @Override
+    public String selectFilePath(final String title, final String directory) {
+        return renderingArea.selectFilePath(title, directory);
+    }
+
+    @Override
+    public void setKeyboardFocus(final View view) {
+        if (view == null) {
+            return;
+        }
+
+        final FocusManager currentFocusManager = keyboardManager.getFocusManager();
+        if (currentFocusManager != null && currentFocusManager.getFocus() != null && currentFocusManager.getFocus().getParent() != null) {
+            currentFocusManager.getFocus().getParent().markDamaged();
+        }
+
+        if (currentFocusManager != null) {
+            final View currentFocus = currentFocusManager.getFocus();
+            if (currentFocus != null && currentFocus != view) {
+                currentFocus.focusLost();
+            }
+        }
+
+        final FocusManager focusManager = view.getFocusManager();
+        if (focusManager != null) {
+            focusManager.setFocus(view);
+            if (view.getParent() != null) {
+                view.getParent().markDamaged();
+            }
+        }
+        if (focusManager == null) {
+            LOG.warn("No focus manager for " + view);
+        } else {
+            keyboardManager.setFocusManager(focusManager);
+        }
+    }
+
+    @Override
+    public void clearOverlayView() {
+        overlayView.markDamaged();
+        overlayView = CLEAR_OVERLAY;
+    }
+
+    @Override
+    public void clearOverlayView(final View view) {
+        if (this.getOverlayView() != view) {
+            LOG.warn("no such view to remove: " + view);
+        }
+        this.clearOverlayView();
+    }
+
+    /*
+     * public void clearStatus() { setStatus(""); }
+     */
+    public void quit() {
+        if (spy != null) {
+            spy.close();
+        }
+        DebugFrame.disposeAll();
+        if (listener != null) {
+            listener.quit();
+        }
+        close();
+    }
+
+    // TODO remove this method; use clearOverlay instead
+    public void disposeOverlayView() {
+        clearOverlayView();
+    }
+
+    @Override
+    public void disposeUnneededViews() {
+        updateNotifier.removeViewsForDisposedObjects();
+    }
+
+    public View dragFrom(final Location location) {
+        if (onOverlay(location)) {
+            location.subtract(overlayView.getLocation());
+            return overlayView.dragFrom(location);
+        } else {
+            return rootView.dragFrom(location);
+        }
+    }
+
+    public DragEvent dragStart(final DragStart start) {
+        if (onOverlay(start.getLocation())) {
+            start.subtract(overlayView.getLocation());
+            return overlayView.dragStart(start);
+        } else {
+            return rootView.dragStart(start);
+        }
+    }
+
+    public void firstClick(final Click click) {
+        /*
+         * for (int i = 0; i < panes.length; i++) {
+         * if(panes[i].respondsTo(click.getLocation())) {
+         * panes[i].firstClick(click); return; } }
+         */
+        if (onOverlay(click.getLocation())) {
+            click.subtract(overlayView.getLocation());
+            overlayView.firstClick(click);
+        } else {
+            rootView.firstClick(click);
+        }
+    }
+
+    private FocusManager getFocusManager() {
+        return overlayView == CLEAR_OVERLAY ? keyboardManager.getFocusManager() : overlayView.getFocusManager();
+    }
+
+    public Bounds getOverlayBounds() {
+        final Bounds bounds = new Bounds(createSize(renderingArea.getSize()));
+        final Insets in = renderingArea.getInsets();
+        bounds.contract(in.left + in.right, in.top + in.bottom);
+        bounds.contract(0, statusBarHeight);
+        return bounds;
+    }
+
+    private Size createSize(final Dimension size) {
+        return new Size(size.width, size.height);
+    }
+
+    public View getOverlayView() {
+        return overlayView;
+    }
+
+    @Override
+    public InteractionSpy getSpy() {
+        return spy;
+    }
+
+    @Override
+    public UndoStack getUndoStack() {
+        return undoStack;
+    }
+
+    @Override
+    public boolean hasFocus(final View view) {
+        final FocusManager focusManager = keyboardManager.getFocusManager();
+        return focusManager != null && focusManager.getFocus() == view;
+    }
+
+    public View identifyView(final Location location, final boolean includeOverlay) {
+        if (includeOverlay && onOverlay(location)) {
+            location.subtract(overlayView.getLocation());
+            return overlayView.identify(location);
+        } else {
+            return rootView.identify(location);
+        }
+    }
+
+    public void init() {
+        if (updateNotifier == null) {
+            throw new NullPointerException("No update notifier set for " + this);
+        }
+        if (rootView == null) {
+            throw new NullPointerException("No root view set for " + this);
+        }
+
+        insets = new Insets(0, 0, 0, 0);
+
+        spy = new InteractionSpy(new SpyWindow());
+
+        keyboardManager = new KeyboardManager(this);
+        final InteractionHandler interactionHandler = new InteractionHandler(this, feedbackManager, keyboardManager, spy);
+        renderingArea.addMouseMotionListener(interactionHandler);
+        renderingArea.addMouseListener(interactionHandler);
+        renderingArea.addKeyListener(interactionHandler);
+
+        if (IsisContext.getConfiguration().getBoolean(Properties.PROPERTY_BASE + "show-mouse-spy", false)) {
+            spy.open();
+        }
+
+        setKeyboardFocus(rootView);
+
+        APPLICATION_OPTIONS = new ApplicationOptions(listener);
+    }
+
+    @Override
+    public boolean isRunningAsExploration() {
+        return runningAsExploration;
+    }
+
+    @Override
+    public boolean isRunningAsPrototype() {
+        return runningAsPrototype;
+    }
+
+    public boolean isShowingMouseSpy() {
+        return spy.isVisible();
+    }
+
+    @Override
+    public void markDamaged(final Bounds bounds) {
+        if (spy != null) {
+            spy.addDamagedArea(bounds);
+        }
+
+        synchronized (redrawArea) {
+            if (redrawArea.equals(NO_REDRAW)) {
+                redrawArea.setBounds(bounds);
+                UI_LOG.debug("damage - new area " + redrawArea);
+            } else {
+                if (!bounds.getSize().equals(NO_SIZE)) {
+                    redrawArea.union(bounds);
+                    UI_LOG.debug("damage - extend area " + redrawArea + " - to include " + bounds);
+                }
+            }
+        }
+    }
+
+    public void menuOptions(final UserActionSet options) {
+    }
+
+    public void mouseDown(final Click click) {
+        if (onOverlay(click.getLocation())) {
+            click.subtract(overlayView.getLocation());
+            overlayView.mouseDown(click);
+        } else {
+            rootView.mouseDown(click);
+        }
+    }
+
+    public void mouseMoved(final Location location) {
+        if (onOverlay(location)) {
+            location.subtract(overlayView.getLocation());
+            overlayView.mouseMoved(location);
+        } else {
+            rootView.mouseMoved(location);
+        }
+    }
+
+    public void mouseUp(final Click click) {
+        if (onOverlay(click.getLocation())) {
+            click.subtract(overlayView.getLocation());
+            overlayView.mouseUp(click);
+        } else {
+            rootView.mouseUp(click);
+        }
+    }
+
+    private boolean onOverlay(final Location mouse) {
+        return overlayView.getBounds().contains(mouse);
+    }
+
+    public void paint(final Graphics graphic) {
+        redrawCount++;
+        graphic.translate(insets.left, insets.top);
+        final Rectangle paintArea = graphic.getClipBounds();
+        final Rectangle layoutArea = layoutViews();
+        if (layoutArea != null) {
+            paintArea.union(layoutArea);
+        }
+
+        if (spy != null) {
+            spy.redraw(paintArea.toString(), redrawCount);
+        }
+        if (UI_LOG.isDebugEnabled()) {
+            UI_LOG.debug("------ repaint viewer #" + redrawCount + " " + paintArea.x + "," + paintArea.y + " " + paintArea.width + "x" + paintArea.height);
+        }
+
+        final Canvas c = createCanvas(graphic, paintArea);
+        if (background != null) {
+            background.draw(c.createSubcanvas(), rootView.getSize());
+        }
+
+        // paint views
+        if (rootView != null) {
+            rootView.draw(c.createSubcanvas());
+        }
+        // paint overlay
+
+        final Bounds bounds = overlayView.getBounds();
+        if (paintArea.intersects(new Rectangle(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight()))) {
+            overlayView.draw(c.createSubcanvas(bounds));
+        }
+
+        /*
+         * for (int i = 0; i < panes.length; i++) {
+         * panes[i].draw(c.createSubcanvas()); }
+         */
+        // paint status
+        // paintUserStatus(bufferGraphics);
+        // blat to screen
+        if (doubleBuffering) {
+            graphic.drawImage(doubleBuffer, 0, 0, null);
+        }
+        if (showRepaintArea) {
+            graphic.setColor(((AwtColor) Toolkit.getColor(ColorsAndFonts.COLOR_DEBUG_BOUNDS_REPAINT)).getAwtColor());
+            graphic.drawRect(paintArea.x, paintArea.y, paintArea.width - 1, paintArea.height - 1);
+            graphic.drawString("#" + redrawCount, paintArea.x + 3, paintArea.y + 15);
+        }
+
+        // paint status
+        paintStatus(graphic);
+    }
+
+    private Canvas createCanvas(final Graphics graphic, final Rectangle paintArea) {
+        final int w = internalDisplaySize.getWidth();
+        final int h = internalDisplaySize.getHeight();
+        if (doubleBuffering) {
+            if ((doubleBuffer == null) || (bufferGraphics == null) || (doubleBuffer.getWidth(null) < w) || (doubleBuffer.getHeight(null) < h)) {
+                doubleBuffer = renderingArea.createImage(w, h);
+                LOG.debug("buffer sized to " + doubleBuffer.getWidth(null) + "x" + doubleBuffer.getHeight(null));
+            }
+            bufferGraphics = doubleBuffer.getGraphics().create();
+        } else {
+            bufferGraphics = graphic;
+        }
+
+        bufferGraphics.clearRect(paintArea.x, paintArea.y, paintArea.width, paintArea.height);
+        bufferGraphics.clearRect(0, 0, w, h);
+
+        bufferGraphics.setClip(paintArea.x, paintArea.y, paintArea.width, paintArea.height);
+        final Canvas c = new AwtCanvas(bufferGraphics, renderingArea, paintArea.x, paintArea.y, paintArea.width, paintArea.height);
+        // Canvas c = new Canvas(bufferGraphics, 0, 0, w, h);
+        return c;
+    }
+
+    /**
+     * Lays out the invalid views and returns the area to be repainted.
+     */
+    private Rectangle layoutViews() {
+        if (!Thread.currentThread().getName().startsWith("AWT-EventQueue") && !isDotNet()) {
+            // REVIEW remove this check and exception when problem with multiple
+            // field drawing is resolved
+            // (Bug 1)
+            throw new IsisException("Drawing with wrong thread: " + Thread.currentThread());
+        }
+        // overlayView.layout(new Size(rootView.getSize()));
+        // rootView.layout(new Size(rootView.getSize()));
+        final Size rootViewSize = rootView.getSize();
+        overlayView.layout();
+        rootView.layout();
+        synchronized (redrawArea) {
+            if (!redrawArea.equals(NO_REDRAW)) {
+                final Rectangle r2 = new Rectangle(redrawArea.getX(), redrawArea.getY(), redrawArea.getWidth(), redrawArea.getHeight());
+                redrawArea.setBounds(NO_REDRAW);
+                return r2;
+            }
+        }
+        return null;
+    }
+
+    private void paintStatus(final Graphics graphic) {
+        final int height = internalDisplaySize.getHeight();
+        final int top = height - statusBarHeight;
+        if (refreshStatus || graphic.getClip().getBounds().getY() + graphic.getClip().getBounds().getHeight() > top) {
+            refreshStatus = false;
+            UI_LOG.debug("changed user status " + status + " " + statusBarArea);
+
+            final int width = internalDisplaySize.getWidth();
+            graphic.setClip(0, top, width, statusBarHeight);
+            graphic.setColor(((AwtColor) Toolkit.getColor(ColorsAndFonts.COLOR_SECONDARY3)).getAwtColor());
+            final AwtText textStyle = (AwtText) Toolkit.getText(ColorsAndFonts.TEXT_STATUS);
+            graphic.setFont(textStyle.getAwtFont());
+            final int baseline = top + textStyle.getAscent();
+            graphic.fillRect(0, top, width, statusBarHeight);
+            graphic.setColor(((AwtColor) Toolkit.getColor(ColorsAndFonts.COLOR_SECONDARY1)).getAwtColor());
+            graphic.drawLine(0, top, internalDisplaySize.getWidth(), top);
+            // graphic.drawRect(0, top, width - 1, statusBarHeight - 1);
+            graphic.setColor(((AwtColor) Toolkit.getColor(ColorsAndFonts.COLOR_BLACK)).getAwtColor());
+            graphic.drawString(status, 5, baseline + ViewConstants.VPADDING);
+        }
+    }
+
+    public View pickupContent(final Location location) {
+        if (onOverlay(location)) {
+            location.subtract(overlayView.getLocation());
+            return overlayView.pickupContent(location);
+        } else {
+            return rootView.pickupContent(location);
+        }
+    }
+
+    public View pickupView(final Location location) {
+        if (onOverlay(location)) {
+            location.subtract(overlayView.getLocation());
+            return overlayView.pickupView(location);
+        } else {
+            return rootView.pickupView(location);
+        }
+    }
+
+    public void popupMenu(final View over, final Location at, final boolean forView, final boolean includeExploration, final boolean includeDebug) {
+        feedbackManager.setBusy(over, null);
+        saveCurrentFieldEntry();
+        final PopupMenuContainer menu = new PopupMenuContainer(over, at);
+        if (over == rootView) {
+            menu.addMenuOptions(APPLICATION_OPTIONS);
+            menu.addMenuOptions(LOGGING_OPTIONS);
+            menu.addMenuOptions(DEBUG_OPTIONS);
+        }
+        final boolean showExplorationOptions = includeExploration || showExplorationMenuByDefault;
+        final boolean showPrototypeOptions = isRunningAsPrototype();
+        menu.show(forView, includeDebug, showExplorationOptions, showPrototypeOptions);
+        feedbackManager.clearBusy(over);
+    }
+
+    @Override
+    public void removeFromNotificationList(final View view) {
+        updateNotifier.remove(view);
+    }
+
+    /**
+     * Force a repaint of the damaged area of the viewer.
+     */
+    @Override
+    public void scheduleRepaint() {
+        updateNotifier.invalidateViewsForChangedObjects();
+        synchronized (redrawArea) {
+            if (!redrawArea.equals(NO_REDRAW) || refreshStatus) {
+                UI_LOG.debug("repaint viewer " + redrawArea);
+                final Bounds area = new Bounds(redrawArea);
+                area.translate(insets.left, insets.top);
+                renderingArea.repaint(area.getX(), area.getY(), area.getWidth(), area.getHeight());
+                redrawArea.setBounds(NO_REDRAW);
+            }
+        }
+    }
+
+    @Override
+    public void saveCurrentFieldEntry() {
+        final FocusManager focusManager = getFocusManager();
+        if (focusManager != null) {
+            final View focus = focusManager.getFocus();
+            if (focus != null) {
+                focus.editComplete(false, false);
+                // change should be marked by the field being completed
+                // focus.markDamaged();
+            }
+        }
+    }
+
+    public void secondClick(final Click click) {
+        if (onOverlay(click.getLocation())) {
+            click.subtract(overlayView.getLocation());
+            overlayView.secondClick(click);
+        } else {
+            rootView.secondClick(click);
+        }
+    }
+
+    @Override
+    public void setBackground(final Background background) {
+        this.background = background;
+    }
+
+    void setCursor(final Cursor cursor) {
+        renderingArea.setCursor(cursor);
+    }
+
+    public void setExploration(final boolean asExploration) {
+        this.runningAsExploration = asExploration;
+    }
+
+    public void setPrototype(final boolean asPrototype) {
+        this.runningAsPrototype = asPrototype;
+    }
+
+    public void setListener(final ShutdownListener listener) {
+        this.listener = listener;
+    }
+
+    @Override
+    public void setOverlayView(final View view) {
+        disposeOverlayView();
+        overlayView = view;
+        // TODO ensure that the view is laid out properly; hence is the right
+        // size to begin with.
+        // view.limitSize(rootView.getSize());
+
+        final Size size = view.getRequiredSize(rootView.getSize());
+        // size.ensureWidth(getSize().getWidth());
+        view.setSize(size);
+        view.layout();
+
+        view.limitBoundsWithin(getOverlaySize());
+        overlayView.markDamaged();
+    }
+
+    @Override
+    public Size getOverlaySize() {
+        return rootView.getSize();
+    }
+
+    @Override
+    public void showInOverlay(final Content content, final Location location) {
+        View view;
+        view = Toolkit.getViewFactory().createView(new ViewRequirement(content, ViewRequirement.OPEN));
+        view = new LineBorder(2, Toolkit.getColor(ColorsAndFonts.COLOR_SECONDARY2), new BackgroundBorder(Toolkit.getColor(ColorsAndFonts.COLOR_SECONDARY3), view));
+        final Size size = view.getRequiredSize(Size.createMax());
+        location.subtract(size.getWidth() / 2, size.getHeight() / 2);
+        view.setLocation(location);
+        setOverlayView(view);
+    }
+
+    public void setRenderingArea(final RenderingArea renderingArea) {
+        this.renderingArea = renderingArea;
+    }
+
+    public void setRootView(final View rootView) {
+        this.rootView = rootView;
+        rootView.invalidateContent();
+    }
+
+    public void setHelpViewer(final HelpViewer helpViewer) {
+        this.helpViewer = helpViewer;
+    }
+
+    public void setShowMouseSpy(final boolean showDeveloperStatus) {
+        if (spy.isVisible()) {
+            spy.close();
+        } else {
+            spy.open();
+        }
+    }
+
+    public void setUpdateNotifier(final ViewUpdateNotifier updateNotifier) {
+        this.updateNotifier = updateNotifier;
+    }
+
+    public void showSpy() {
+        spy.open();
+    }
+
+    public void sizeChange() {
+        initSize();
+        final View subviews[] = rootView.getSubviews();
+        for (final View subview : subviews) {
+            subview.invalidateLayout();
+        }
+
+        final Bounds bounds = new Bounds(internalDisplaySize);
+        markDamaged(bounds);
+        scheduleRepaint();
+
+        Properties.saveSizeOption(Properties.PROPERTY_BASE + "initial.size", bounds.getSize());
+    }
+
+    public void locationChange(final int x, final int y) {
+        Properties.saveLocationOption(Properties.PROPERTY_BASE + "initial.location", new Location(x, y));
+    }
+
+    public void initSize() {
+        internalDisplaySize = createSize(renderingArea.getSize());
+        insets = renderingArea.getInsets();
+        LOG.debug("  insets " + insets);
+        internalDisplaySize.contract(insets.left + insets.right, insets.top + insets.bottom);
+        LOG.debug("  internal " + internalDisplaySize);
+
+        final Size rootViewSize = new Size(internalDisplaySize);
+        final Text text = Toolkit.getText(ColorsAndFonts.TEXT_STATUS);
+        statusBarHeight = text.getLineHeight() + text.getDescent();
+        rootViewSize.contractHeight(statusBarHeight);
+        statusBarArea = new Bounds(insets.left, insets.top + rootViewSize.getHeight(), rootViewSize.getWidth(), statusBarHeight);
+        rootView.setSize(rootViewSize);
+    }
+
+    public void thirdClick(final Click click) {
+        if (onOverlay(click.getLocation())) {
+            click.subtract(overlayView.getLocation());
+            overlayView.thirdClick(click);
+        } else {
+            rootView.thirdClick(click);
+        }
+    }
+
+    @Override
+    public String toString() {
+        final ToString str = new ToString(this);
+        str.append("renderingArea", renderingArea);
+        str.append("redrawArea", redrawArea);
+        str.append("rootView", rootView);
+        return str.toString();
+    }
+
+    public void translate(final MouseEvent me) {
+        me.translatePoint(-insets.left, -insets.top);
+    }
+
+    public ViewAreaType viewAreaType(final Location location) {
+        if (onOverlay(location)) {
+            location.subtract(overlayView.getLocation());
+            return overlayView.viewAreaType(location);
+        } else {
+            return rootView.viewAreaType(location);
+        }
+    }
+
+    public boolean isOverlayAvailable() {
+        return overlayView != CLEAR_OVERLAY;
+    }
+
+    public void makeRootFocus() {
+        // makeFocus(rootView);
+    }
+
+    public void openHelp(final View forView) {
+        if (forView != null) {
+            String description = null;
+            String help = null;
+            String name = null;
+
+            if (forView != null && forView.getContent() != null) {
+                final Content content = forView.getContent();
+                description = content.getDescription();
+                help = content.getHelp();
+                name = content.getId();
+                name = name == null ? content.title() : name;
+            }
+
+            helpViewer.open(forView.getAbsoluteLocation(), name, description, help);
+
+        }
+
+    }
+
+    @Override
+    public Object getClipboard(final Class<?> cls) {
+        if (cls == String.class) {
+
+            final Clipboard cb = java.awt.Toolkit.getDefaultToolkit().getSystemClipboard();
+            final Transferable content = cb.getContents(this);
+
+            String value = "illegal value";
+            try {
+                value = ((String) content.getTransferData(DataFlavor.stringFlavor));
+            } catch (final Throwable e) {
+                LOG.error("invalid clipboard operation " + e);
+            }
+            return value;
+        } else {
+            return null;
+        }
+    }
+
+    @Override
+    public void setClipboard(final String clip, final Class<?> class1) {
+        final Clipboard cb = java.awt.Toolkit.getDefaultToolkit().getSystemClipboard();
+        cb.setContents(new StringSelection(clip), null);
+    }
+
+    public void forcePaintOfStatusBar() {
+        status = feedbackManager.getStatusBarOutput();
+        refreshStatus = true;
+        scheduleRepaint();
+    }
+
+    public void showDialog(final MessageContent content) {
+        final ViewRequirement requirement = new ViewRequirement(content, ViewRequirement.OPEN);
+        final View view = Toolkit.getViewFactory().createView(requirement);
+        rootView.getWorkspace().addDialog(view, new Placement(Placement.CENTER));
+        scheduleRepaint();
+    }
+
+    @Override
+    public void showDebugFrame(final DebuggableWithTitle[] info, final Location at) {
+        final InfoDebugFrame f = new InfoDebugFrame();
+        f.setInfo(info);
+        f.show(at.getX(), at.getY());
+
+    }
+
+    @Override
+    public void clearAction() {
+        feedbackManager.clearAction();
+        clearOverlayView();
+        // feedbackManager.showDefaultCursor();
+    }
+
+    public void setFeedbackManager(final XFeedbackManager feedbackManager) {
+        this.feedbackManager = feedbackManager;
+    }
+
+    public void close() {
+        renderingArea.dispose();
+    }
+
+    @Override
+    public void saveOpenObjects() {
+        final List<ObjectAdapter> objects = new ArrayList<ObjectAdapter>();
+        for (final View view : rootView.getSubviews()) {
+            final Content content = view.getContent();
+            if (content instanceof ObjectContent) {
+                objects.add(((ObjectContent) content).getAdapter());
+            }
+        }
+        IsisContext.getUserProfileLoader().saveSession(objects);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/255ef514/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/calendar/CalendarCellContent.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/calendar/CalendarCellContent.java b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/calendar/CalendarCellContent.java
new file mode 100644
index 0000000..d21bca1
--- /dev/null
+++ b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/calendar/CalendarCellContent.java
@@ -0,0 +1,96 @@
+/*
+ *  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.calendar;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.isis.core.commons.exceptions.UnexpectedCallException;
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.consent.Consent;
+import org.apache.isis.core.metamodel.consent.Veto;
+import org.apache.isis.core.metamodel.spec.ObjectSpecification;
+import org.apache.isis.runtimes.dflt.runtime.system.context.IsisContext;
+import org.apache.isis.viewer.dnd.view.Content;
+import org.apache.isis.viewer.dnd.view.collection.AbstractCollectionContent;
+
+public class CalendarCellContent extends AbstractCollectionContent {
+    private final String title;
+    private final List<Object> collection = new ArrayList<Object>();
+
+    public CalendarCellContent(final String title) {
+        this.title = title;
+    }
+
+    @Override
+    public ObjectAdapter getCollection() {
+        return IsisContext.getPersistenceSession().getAdapterManager().adapterFor(collection);
+    }
+
+    @Override
+    public Consent canDrop(final Content sourceContent) {
+        return Veto.DEFAULT;
+    }
+
+    @Override
+    public ObjectAdapter drop(final Content sourceContent) {
+        throw new UnexpectedCallException();
+    }
+
+    @Override
+    public String getHelp() {
+        return "No help available";
+    }
+
+    @Override
+    public String getIconName() {
+        return null;
+    }
+
+    @Override
+    public String getId() {
+        return null;
+    }
+
+    @Override
+    public ObjectAdapter getAdapter() {
+        return getCollection();
+    }
+
+    @Override
+    public ObjectSpecification getSpecification() {
+        throw new UnexpectedCallException();
+    }
+
+    @Override
+    public boolean isTransient() {
+        return true;
+    }
+
+    @Override
+    public String title() {
+        return title;
+    }
+
+    public void addElement(final ObjectAdapter element) {
+        collection.add(element.getObject());
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/255ef514/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/calendar/CalendarConstants.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/calendar/CalendarConstants.java b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/calendar/CalendarConstants.java
new file mode 100644
index 0000000..eb6fb2f
--- /dev/null
+++ b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/calendar/CalendarConstants.java
@@ -0,0 +1,34 @@
+/*
+ *  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.calendar;
+
+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.Toolkit;
+
+public class CalendarConstants {
+    public final static String[] days = new String[] { "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun" };
+    public final static Color textColor = Toolkit.getColor(ColorsAndFonts.COLOR_PRIMARY1);
+    public final static Color weekendColor = Toolkit.getColor(ColorsAndFonts.COLOR_PRIMARY3);
+    public final static Color lineColor = Toolkit.getColor(ColorsAndFonts.COLOR_SECONDARY3);
+    public final static Text style = Toolkit.getText(ColorsAndFonts.TEXT_NORMAL);
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/255ef514/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/calendar/CalendarGrid.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/calendar/CalendarGrid.java b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/calendar/CalendarGrid.java
new file mode 100644
index 0000000..206afbd
--- /dev/null
+++ b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/calendar/CalendarGrid.java
@@ -0,0 +1,417 @@
+/*
+ *  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.calendar;
+
+import java.util.Date;
+import java.util.List;
+
+import org.apache.isis.core.commons.exceptions.UnexpectedCallException;
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.facetapi.Facet;
+import org.apache.isis.core.metamodel.spec.ObjectSpecification;
+import org.apache.isis.core.metamodel.spec.feature.ObjectAssociation;
+import org.apache.isis.core.progmodel.facets.value.date.DateValueFacet;
+import org.apache.isis.viewer.dnd.drawing.Canvas;
+import org.apache.isis.viewer.dnd.drawing.Location;
+import org.apache.isis.viewer.dnd.drawing.Size;
+import org.apache.isis.viewer.dnd.icon.IconElementFactory;
+import org.apache.isis.viewer.dnd.view.Axes;
+import org.apache.isis.viewer.dnd.view.Content;
+import org.apache.isis.viewer.dnd.view.FocusManager;
+import org.apache.isis.viewer.dnd.view.UserActionSet;
+import org.apache.isis.viewer.dnd.view.View;
+import org.apache.isis.viewer.dnd.view.ViewConstants;
+import org.apache.isis.viewer.dnd.view.Workspace;
+import org.apache.isis.viewer.dnd.view.base.BlankView;
+import org.apache.isis.viewer.dnd.view.border.ScrollBorder;
+import org.apache.isis.viewer.dnd.view.collection.CollectionContent;
+import org.apache.isis.viewer.dnd.view.composite.CollectionElementBuilder;
+import org.apache.isis.viewer.dnd.view.composite.CompositeView;
+import org.apache.isis.viewer.dnd.view.composite.CompositeViewUsingBuilder;
+import org.apache.isis.viewer.dnd.view.composite.StackLayout;
+import org.apache.isis.viewer.dnd.view.content.NullContent;
+import org.apache.isis.viewer.dnd.view.option.UserActionAbstract;
+
+public class CalendarGrid extends CompositeView {
+    private Cells cellLayout;
+    private int rows;
+    private int columns;
+    private boolean acrossThenDown;
+
+    @Override
+    protected void buildNewView() {
+        final CalendarCellContent[] cellContents = createContentForCells();
+        addCellsToView(cellContents);
+    }
+
+    private void addCellsToView(final CalendarCellContent[] cellContents) {
+        final View[] cells = new View[rows * columns];
+        for (int row = 0; row < rows; row++) {
+            for (int column = 0; column < columns; column++) {
+                final int cellNo = acrossThenDown ? row * columns + column : column * rows + row;
+                View cell;
+                if (cellContents[cellNo] == null) {
+                    cell = new BlankView(new NullContent());
+                } else {
+                    cell = new CompositeViewUsingBuilder(cellContents[cellNo], null, new Axes(), new StackLayout(), new CollectionElementBuilder(new IconElementFactory()));
+                    cell = new ScrollBorder(cell);
+                }
+                cells[cellNo] = cell;
+                addView(cell);
+            }
+        }
+    }
+
+    private CalendarCellContent[] createContentForCells() {
+        final CalendarCellContent[] cellContents = new CalendarCellContent[rows * columns];
+        final CollectionContent content = (CollectionContent) getContent();
+        for (final ObjectAdapter element : content.elements()) {
+            final Date date = dateFor(element);
+            if (date == null) {
+                continue;
+            }
+            final int period = cellLayout.getPeriodFor(date);
+            if (period >= 0 && period < cellContents.length) {
+                if (cellContents[period] == null) {
+                    cellContents[period] = new CalendarCellContent(cellLayout.title(period));
+                }
+                cellContents[period].addElement(element);
+            }
+        }
+        return cellContents;
+    }
+
+    private Date dateFor(final ObjectAdapter element) {
+        final ObjectAssociation dateField = findDate(element);
+        if (dateField == null) {
+            return null;
+        }
+        final DateValueFacet facet = dateField.getSpecification().getFacet(DateValueFacet.class);
+        final ObjectAdapter field = dateField.get(element);
+        final Date date = facet.dateValue(field);
+        return date;
+    }
+
+    private ObjectAssociation findDate(final ObjectAdapter adapter) {
+        final ObjectSpecification spec = adapter.getSpecification();
+        final List<ObjectAssociation> fields = spec.getAssociations();
+        for (int i = 0; i < fields.size(); i++) {
+            final Facet facet = fields.get(i).getSpecification().getFacet(DateValueFacet.class);
+            if (facet != null) {
+                return fields.get(i);
+            }
+        }
+        return null;
+    }
+
+    @Override
+    protected void buildModifiedView() {
+        disposeContentsOnly();
+        buildNewView();
+    }
+
+    // TODO remove
+    @Override
+    protected void buildView() {
+        throw new UnexpectedCallException();
+    }
+
+    @Override
+    protected void doLayout(final Size maximumSize) {
+        final boolean hasHeader = cellLayout.header(0) != null;
+        final int topInset = 0 + (acrossThenDown && hasHeader ? 30 : 0);
+        final int leftInset = !acrossThenDown && hasHeader ? 50 : 0;
+        final int width = maximumSize.getWidth();
+        final int height = maximumSize.getHeight();
+        final int columnWidth = (width - leftInset) / columns;
+        final int rowHeight = (height - topInset) / rows;
+
+        final View[] cells = getSubviews();
+        int i = 0;
+        final int top = CalendarConstants.style.getLineHeight() + ViewConstants.VPADDING;
+        CalendarConstants.style.getLineSpacing();
+        final Location location = new Location(leftInset, topInset + top);
+        final Size size = new Size(columnWidth, rowHeight - top);
+        for (int row = 0; row < rows; row++) {
+            for (int column = 0; column < columns; column++) {
+                final View cell = cells[i++];
+                cell.setSize(size);
+                cell.setLocation(location);
+                location.add(columnWidth, 0);
+            }
+            location.setX(leftInset);
+            location.add(0, rowHeight);
+        }
+    }
+
+    @Override
+    public Size requiredSize(final Size availableSpace) {
+        return new Size(300, 300);
+    }
+
+    protected CalendarGrid(final Content content) {
+        super(content, null);
+
+        cellLayout = new DayCells(null);
+        acrossThenDown = true;
+        rows = cellLayout.defaultRows();
+        columns = cellLayout.defaultColumns();
+        cellLayout.add(-rows * columns / 2);
+    }
+
+    @Override
+    public void setFocusManager(final FocusManager focusManager) {
+        // this.focusManager = focusManager;
+    }
+
+    @Override
+    public void draw(final Canvas canvas) {
+        super.draw(canvas);
+
+        final boolean hasHeader = cellLayout.header(0) != null;
+        final int topInset = 0 + (acrossThenDown && hasHeader ? 30 : 0);
+        final int leftInset = !acrossThenDown && hasHeader ? 50 : 0;
+        final int width = getSize().getWidth();
+        final int height = getSize().getHeight();
+        final int columnWidth = (width - leftInset) / columns;
+        final int rowHeight = (height - topInset) / rows;
+
+        for (int row = 0; row < rows; row++) {
+            final int y = topInset + row * rowHeight;
+            if (!acrossThenDown && hasHeader) {
+                canvas.drawText(cellLayout.header(row), 0, y + 20, CalendarConstants.textColor, CalendarConstants.style);
+            }
+            canvas.drawLine(leftInset, y, width, y, CalendarConstants.lineColor);
+        }
+        canvas.drawLine(leftInset, topInset + height - 1, width, topInset + height - 1, CalendarConstants.lineColor);
+
+        for (int column = 0; column < columns; column++) {
+            final int x = leftInset + column * columnWidth;
+            if (acrossThenDown && hasHeader) {
+                canvas.drawText(cellLayout.header(column), x, topInset - 5, CalendarConstants.textColor, CalendarConstants.style);
+            }
+            canvas.drawLine(x, topInset, x, topInset + height, CalendarConstants.lineColor);
+        }
+        canvas.drawLine(width - 1, topInset, width - 1, height, CalendarConstants.lineColor);
+
+        for (int row = 0; row < rows; row++) {
+            final int y = topInset + row * rowHeight + CalendarConstants.style.getAscent() + 2;
+            for (int column = 0; column < columns; column++) {
+                final int x = leftInset + column * columnWidth + 2;
+                final int cell = acrossThenDown ? row * columns + column : column * rows + row;
+                canvas.drawText(cellLayout.title(cell), x, y, CalendarConstants.textColor, CalendarConstants.style);
+            }
+        }
+    }
+
+    void addRow() {
+        rows++;
+        invalidateContent();
+        markDamaged();
+    }
+
+    void removeRow() {
+        rows--;
+        invalidateContent();
+        markDamaged();
+    }
+
+    void addColumn() {
+        columns++;
+        invalidateContent();
+        markDamaged();
+    }
+
+    void removeColumn() {
+        columns--;
+        invalidateContent();
+        markDamaged();
+    }
+
+    void showYears() {
+        cellLayout = new YearCells(cellLayout);
+        show();
+    }
+
+    void showMonths() {
+        cellLayout = new MonthCells(cellLayout);
+        show();
+    }
+
+    void showWeeks() {
+        cellLayout = new WeekCells(cellLayout);
+        show();
+    }
+
+    void show() {
+        cellLayout.roundDown();
+        rows = cellLayout.defaultRows();
+        columns = cellLayout.defaultColumns();
+        invalidateContent();
+        markDamaged();
+    }
+
+    void showSingleDay() {
+        cellLayout = new SingleDayCells(cellLayout);
+        show();
+    }
+
+    void showDays() {
+        cellLayout = new DayCells(cellLayout);
+        show();
+    }
+
+    void acrossFirst() {
+        acrossThenDown = true;
+        final int temp = rows;
+        rows = columns;
+        columns = temp;
+        invalidateContent();
+        markDamaged();
+    }
+
+    void downFirst() {
+        acrossThenDown = false;
+        final int temp = rows;
+        rows = columns;
+        columns = temp;
+        invalidateContent();
+        markDamaged();
+    }
+
+    void nextPeriod() {
+        cellLayout.add(rows * columns);
+        invalidateContent();
+        markDamaged();
+    }
+
+    void previousePeriod() {
+        cellLayout.add(-rows * columns);
+        invalidateContent();
+        markDamaged();
+    }
+
+    void today() {
+        cellLayout.today();
+        invalidateContent();
+        markDamaged();
+    }
+
+    @Override
+    public void viewMenuOptions(final UserActionSet options) {
+        super.viewMenuOptions(options);
+
+        options.add(new UserActionAbstract("Add row") {
+            @Override
+            public void execute(final Workspace workspace, final View view, final Location at) {
+                addRow();
+            }
+        });
+        options.add(new UserActionAbstract("Remove row") {
+            @Override
+            public void execute(final Workspace workspace, final View view, final Location at) {
+                removeRow();
+            }
+        });
+
+        options.add(new UserActionAbstract("Add column") {
+            @Override
+            public void execute(final Workspace workspace, final View view, final Location at) {
+                addColumn();
+            }
+        });
+        options.add(new UserActionAbstract("Remove column") {
+            @Override
+            public void execute(final Workspace workspace, final View view, final Location at) {
+                removeColumn();
+            }
+        });
+
+        options.add(new UserActionAbstract("Years") {
+            @Override
+            public void execute(final Workspace workspace, final View view, final Location at) {
+                showYears();
+            }
+        });
+
+        options.add(new UserActionAbstract("Months") {
+            @Override
+            public void execute(final Workspace workspace, final View view, final Location at) {
+                showMonths();
+            }
+        });
+
+        options.add(new UserActionAbstract("Weeks") {
+            @Override
+            public void execute(final Workspace workspace, final View view, final Location at) {
+                showWeeks();
+            }
+        });
+
+        options.add(new UserActionAbstract("Day") {
+            @Override
+            public void execute(final Workspace workspace, final View view, final Location at) {
+                showSingleDay();
+            }
+        });
+        options.add(new UserActionAbstract("Days") {
+            @Override
+            public void execute(final Workspace workspace, final View view, final Location at) {
+                showDays();
+            }
+        });
+
+        options.add(new UserActionAbstract("Across then down") {
+            @Override
+            public void execute(final Workspace workspace, final View view, final Location at) {
+                acrossFirst();
+            }
+        });
+
+        options.add(new UserActionAbstract("Down then across") {
+            @Override
+            public void execute(final Workspace workspace, final View view, final Location at) {
+                downFirst();
+            }
+        });
+
+        options.add(new UserActionAbstract("Previous period") {
+            @Override
+            public void execute(final Workspace workspace, final View view, final Location at) {
+                previousePeriod();
+            }
+        });
+
+        options.add(new UserActionAbstract("Next period") {
+            @Override
+            public void execute(final Workspace workspace, final View view, final Location at) {
+                nextPeriod();
+            }
+        });
+
+        options.add(new UserActionAbstract("Today") {
+            @Override
+            public void execute(final Workspace workspace, final View view, final Location at) {
+                today();
+            }
+        });
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/255ef514/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/calendar/CalendarSpecification.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/calendar/CalendarSpecification.java b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/calendar/CalendarSpecification.java
new file mode 100644
index 0000000..7cddd07
--- /dev/null
+++ b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/calendar/CalendarSpecification.java
@@ -0,0 +1,100 @@
+/*
+ *  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.calendar;
+
+import java.util.List;
+
+import org.apache.isis.core.metamodel.spec.feature.OneToOneAssociation;
+import org.apache.isis.core.progmodel.facets.value.date.DateValueFacet;
+import org.apache.isis.viewer.dnd.view.Axes;
+import org.apache.isis.viewer.dnd.view.CompositeViewSpecification;
+import org.apache.isis.viewer.dnd.view.Content;
+import org.apache.isis.viewer.dnd.view.View;
+import org.apache.isis.viewer.dnd.view.ViewRequirement;
+import org.apache.isis.viewer.dnd.view.base.Layout;
+import org.apache.isis.viewer.dnd.view.collection.CollectionContent;
+import org.apache.isis.viewer.dnd.view.composite.StackLayout;
+import org.apache.isis.viewer.dnd.view.composite.ViewBuilder;
+
+public class CalendarSpecification implements CompositeViewSpecification {
+
+    public Layout createLayout(final Content content, final Axes axes) {
+        return new StackLayout();
+    }
+
+    public void createAxes(final Content content, final Axes axes) {
+        // axes.add(new CalendarAxis());
+    }
+
+    @Override
+    public View createView(final Content content, final Axes axes, final int sequence) {
+        return new CalendarView(content, this);
+        // return new ViewResizeBorder(new CalendarView(content, this));
+    }
+
+    @Override
+    public boolean canDisplay(final ViewRequirement requirement) {
+        final boolean openCollection = requirement.isCollection() && requirement.isOpen();
+        if (openCollection) {
+            final List<OneToOneAssociation> propertyList = ((CollectionContent) requirement.getContent()).getElementSpecification().getProperties();
+            for (final OneToOneAssociation association : propertyList) {
+                if (!association.isAlwaysHidden() && association.getSpecification().containsFacet(DateValueFacet.class)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public String getName() {
+        return "Calendar (experimental) ";
+    }
+
+    @Override
+    public boolean isOpen() {
+        return true;
+    }
+
+    @Override
+    public boolean isReplaceable() {
+        return true;
+    }
+
+    @Override
+    public boolean isSubView() {
+        return false;
+    }
+
+    public ViewBuilder getSubviewBuilder() {
+        return null;
+    }
+
+    @Override
+    public boolean isAligned() {
+        return false;
+    }
+
+    @Override
+    public boolean isResizeable() {
+        return true;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/255ef514/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/calendar/CalendarView.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/calendar/CalendarView.java b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/calendar/CalendarView.java
new file mode 100644
index 0000000..d8dfc7e
--- /dev/null
+++ b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/calendar/CalendarView.java
@@ -0,0 +1,199 @@
+/*
+ *  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.calendar;
+
+import org.apache.log4j.Logger;
+
+import org.apache.isis.viewer.dnd.drawing.Bounds;
+import org.apache.isis.viewer.dnd.drawing.Location;
+import org.apache.isis.viewer.dnd.drawing.Size;
+import org.apache.isis.viewer.dnd.field.DatePickerControl;
+import org.apache.isis.viewer.dnd.toolbar.ToolbarView;
+import org.apache.isis.viewer.dnd.view.Content;
+import org.apache.isis.viewer.dnd.view.FocusManager;
+import org.apache.isis.viewer.dnd.view.View;
+import org.apache.isis.viewer.dnd.view.ViewSpecification;
+import org.apache.isis.viewer.dnd.view.Workspace;
+import org.apache.isis.viewer.dnd.view.composite.CompositeView;
+import org.apache.isis.viewer.dnd.view.content.NullContent;
+import org.apache.isis.viewer.dnd.view.control.AbstractButtonAction;
+import org.apache.isis.viewer.dnd.view.control.Button;
+
+public class CalendarView extends CompositeView {
+    private static final Logger LOG = Logger.getLogger(CalendarView.class);
+
+    protected CalendarView(final Content content, final ViewSpecification specification) {
+        super(content, specification);
+    }
+
+    @Override
+    public void doLayout(final Size maximumSize) {
+        LOG.debug("doLayout() " + maximumSize + "  " + getSize());
+        final View toolbar = getSubviews()[0];
+        maximumSize.contract(getPadding());
+        final Size toolbarSize = toolbar.getRequiredSize(maximumSize);
+        LOG.debug("   toolbar " + toolbarSize);
+        Bounds bounds = new Bounds(toolbarSize);
+        toolbar.setBounds(bounds);
+
+        final View grid = getSubviews()[1];
+        final Size gridSize = getRequiredSize(Size.createMax());
+        gridSize.contract(getPadding());
+        gridSize.contractHeight(toolbarSize.getHeight());
+        bounds = new Bounds(new Location(0, toolbarSize.getHeight()), gridSize);
+        grid.setBounds(bounds);
+        LOG.debug("   grid " + toolbarSize);
+
+    }
+
+    @Override
+    public void setFocusManager(final FocusManager focusManager) {
+        // this.focusManager = focusManager;
+    }
+
+    @Override
+    public Size requiredSize(final Size availableSpace) {
+        final Size workspace = getWorkspace().getSize();
+        return new Size((int) (workspace.getWidth() * 0.8), (int) (workspace.getHeight() * 0.8));
+    }
+
+    @Override
+    protected void buildView() {
+        if (subviews().length == 0) {
+            final CalendarGrid grid = new CalendarGrid(getContent());
+            final ToolbarView toolbar = createToolbar(grid);
+            addView(toolbar);
+            addView(grid);
+        } else {
+            // TODO update grid view
+        }
+    }
+
+    private ToolbarView createToolbar(final CalendarGrid calendar) {
+        final ToolbarView toolbarView = new ToolbarView(getContent(), null);
+
+        toolbarView.addView(new Button(new AbstractButtonAction("+Row") {
+            @Override
+            public void execute(final Workspace workspace, final View view, final Location at) {
+                calendar.addRow();
+            }
+        }, this));
+
+        toolbarView.addView(new Button(new AbstractButtonAction("-Row") {
+            @Override
+            public void execute(final Workspace workspace, final View view, final Location at) {
+                calendar.removeRow();
+            }
+        }, this));
+
+        toolbarView.addView(new Button(new AbstractButtonAction("Across") {
+            @Override
+            public void execute(final Workspace workspace, final View view, final Location at) {
+                calendar.acrossFirst();
+            }
+        }, this));
+
+        toolbarView.addView(new Button(new AbstractButtonAction("Down") {
+            @Override
+            public void execute(final Workspace workspace, final View view, final Location at) {
+                calendar.downFirst();
+            }
+        }, this));
+
+        toolbarView.addView(new Button(new AbstractButtonAction("Next") {
+            @Override
+            public void execute(final Workspace workspace, final View view, final Location at) {
+                calendar.nextPeriod();
+            }
+        }, this));
+
+        toolbarView.addView(new Button(new AbstractButtonAction("Previous") {
+            @Override
+            public void execute(final Workspace workspace, final View view, final Location at) {
+                calendar.previousePeriod();
+            }
+        }, this));
+
+        toolbarView.addView(new Button(new AbstractButtonAction("Day") {
+            @Override
+            public void execute(final Workspace workspace, final View view, final Location at) {
+                calendar.showSingleDay();
+            }
+        }, this));
+
+        toolbarView.addView(new Button(new AbstractButtonAction("Days") {
+            @Override
+            public void execute(final Workspace workspace, final View view, final Location at) {
+                calendar.showDays();
+            }
+        }, this));
+
+        toolbarView.addView(new Button(new AbstractButtonAction("Weeks") {
+            @Override
+            public void execute(final Workspace workspace, final View view, final Location at) {
+                calendar.showWeeks();
+            }
+        }, this));
+
+        toolbarView.addView(new Button(new AbstractButtonAction("Months") {
+            @Override
+            public void execute(final Workspace workspace, final View view, final Location at) {
+                calendar.showMonths();
+            }
+        }, this));
+
+        toolbarView.addView(new Button(new AbstractButtonAction("Years") {
+            @Override
+            public void execute(final Workspace workspace, final View view, final Location at) {
+                calendar.showYears();
+            }
+        }, this));
+
+        toolbarView.addView(new Button(new AbstractButtonAction("Today") {
+            @Override
+            public void execute(final Workspace workspace, final View view, final Location at) {
+                calendar.today();
+            }
+        }, this));
+
+        toolbarView.addView(new Button(new AbstractButtonAction("Date") {
+            @Override
+            public void execute(final Workspace workspace, View view, final Location at) {
+                final Content content = new NullContent() {
+                };
+                view = DatePickerControl.getPicker(content);
+                calendar.today();
+                getViewManager().setOverlayView(view);
+            }
+        }, this));
+
+        return toolbarView;
+    }
+
+    /*
+     * public void invalidateLayout() { // super.invalidateLayout(); View parent
+     * = getParent(); if (parent != null) { // parent.invalidateLayout(); }
+     * isInvalid = true; View toolbar = getSubviews()[0];
+     * toolbar.invalidateLayout(); // View grid = getSubviews()[1]; //
+     * grid.invalidateLayout(); } protected boolean isLayoutInvalid() { return
+     * isInvalid; }
+     */
+
+}