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

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

http://git-wip-us.apache.org/repos/asf/isis/blob/eb613703/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/composite/FieldLabelsDecorator.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/composite/FieldLabelsDecorator.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/composite/FieldLabelsDecorator.java
new file mode 100644
index 0000000..3dc11d7
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/composite/FieldLabelsDecorator.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.view.composite;
+
+import org.apache.isis.viewer.dnd.view.Axes;
+import org.apache.isis.viewer.dnd.view.Content;
+import org.apache.isis.viewer.dnd.view.SubviewDecorator;
+import org.apache.isis.viewer.dnd.view.View;
+import org.apache.isis.viewer.dnd.view.ViewAxis;
+import org.apache.isis.viewer.dnd.view.axis.LabelAxis;
+import org.apache.isis.viewer.dnd.view.border.DroppableLabelBorder;
+import org.apache.isis.viewer.dnd.view.border.LabelBorder;
+
+public class FieldLabelsDecorator implements SubviewDecorator {
+
+    @Override
+    public ViewAxis createAxis(final Content content) {
+        return new LabelAxis();
+    }
+
+    @Override
+    public View decorate(final Axes axes, final View view) {
+        final LabelAxis axis = axes.getAxis(LabelAxis.class);
+        if (view.getContent().isObject() && !view.getContent().isTextParseable()) {
+            return DroppableLabelBorder.createObjectFieldLabelBorder(axis, view);
+        } else {
+            return LabelBorder.createFieldLabelBorder(axis, view);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/eb613703/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/composite/GridLayout.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/composite/GridLayout.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/composite/GridLayout.java
new file mode 100644
index 0000000..6ae56e2
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/composite/GridLayout.java
@@ -0,0 +1,137 @@
+/*
+ *  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.view.composite;
+
+import org.apache.isis.viewer.dnd.drawing.Location;
+import org.apache.isis.viewer.dnd.drawing.Size;
+import org.apache.isis.viewer.dnd.view.View;
+import org.apache.isis.viewer.dnd.view.ViewAxis;
+import org.apache.isis.viewer.dnd.view.base.Layout;
+
+/**
+ */
+public class GridLayout implements Layout, ViewAxis {
+    public final static int COLUMNS = 0;
+    public final static int ROWS = 1;
+    private int orientation = COLUMNS;
+    private int size = 1;
+
+    @Override
+    public Size getRequiredSize(final View view) {
+        final View views[] = view.getSubviews();
+
+        final int max[] = new int[size];
+        final int total[] = new int[size];
+
+        int column = 0;
+        for (final View v : views) {
+            final Size s = v.getRequiredSize(new Size(Integer.MAX_VALUE, Integer.MAX_VALUE));
+            if (orientation == COLUMNS) {
+                total[column] += s.getHeight();
+                max[column] = Math.max(max[column], s.getWidth());
+            } else {
+                total[column] += s.getWidth();
+                max[column] = Math.max(max[column], s.getHeight());
+            }
+            column++;
+            if (column >= size) {
+                column = 0;
+            }
+        }
+
+        int height = 0;
+        int width = 0;
+        for (int i = 0; i < size; i++) {
+            if (orientation == COLUMNS) {
+                height = Math.max(height, total[i]);
+                width += max[i];
+            } else {
+                width = Math.max(width, total[i]);
+                height += max[i];
+            }
+        }
+        return new Size(width, height);
+    }
+
+    @Override
+    public void layout(final View view, final Size maximumSize) {
+        int x = 0;
+        int y = 0;
+        final View views[] = view.getSubviews();
+        final int max[] = new int[size];
+
+        int column = 0;
+        for (final View v : views) {
+            final Size s = v.getRequiredSize(new Size(maximumSize));
+            if (orientation == COLUMNS) {
+                max[column] = Math.max(max[column], s.getWidth());
+            } else {
+                max[column] = Math.max(max[column], s.getHeight());
+            }
+            column++;
+            if (column >= size) {
+                column = 0;
+            }
+        }
+
+        column = 0;
+        for (final View v : views) {
+            final Size s = v.getRequiredSize(new Size(maximumSize));
+            v.setLocation(new Location(x, y));
+            if (orientation == COLUMNS) {
+                x += max[column];
+                s.ensureWidth(max[column]);
+            } else {
+                y += max[column];
+                s.ensureHeight(max[column]);
+            }
+            v.setSize(s);
+            column++;
+            if (column >= size) {
+                column = 0;
+                if (orientation == COLUMNS) {
+                    x = 0;
+                    y += s.getHeight();
+                } else {
+                    y = 0;
+                    x += s.getWidth();
+                }
+            }
+
+        }
+    }
+
+    public int getOrientation() {
+        return orientation;
+    }
+
+    public void setOrientation(final int orientation) {
+        this.orientation = orientation;
+    }
+
+    public int getSize() {
+        return size;
+    }
+
+    public void setSize(final int size) {
+        this.size = size;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/eb613703/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/composite/GridLayoutControlBorder.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/composite/GridLayoutControlBorder.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/composite/GridLayoutControlBorder.java
new file mode 100644
index 0000000..13615da
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/composite/GridLayoutControlBorder.java
@@ -0,0 +1,98 @@
+/*
+ *  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.view.composite;
+
+import org.apache.isis.core.runtime.userprofile.Options;
+import org.apache.isis.viewer.dnd.drawing.Location;
+import org.apache.isis.viewer.dnd.view.Axes;
+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.base.AbstractBorder;
+import org.apache.isis.viewer.dnd.view.option.UserActionAbstract;
+
+public class GridLayoutControlBorder extends AbstractBorder {
+
+    public static final class Factory implements CompositeViewDecorator {
+        @Override
+        public View decorate(final View view, final Axes axes) {
+            return new GridLayoutControlBorder(view);
+        }
+    }
+
+    protected GridLayoutControlBorder(final View view) {
+        super(view);
+    }
+
+    @Override
+    public void viewMenuOptions(final UserActionSet menuOptions) {
+        super.viewMenuOptions(menuOptions);
+
+        final GridLayout layout = getViewAxes().getAxis(GridLayout.class);
+
+        final boolean columnOrientation = layout.getOrientation() == GridLayout.COLUMNS;
+
+        final UserActionSet submenu = menuOptions.addNewActionSet("Grid");
+
+        submenu.add(new UserActionAbstract("Add " + (columnOrientation ? "Column" : "Row")) {
+            @Override
+            public void execute(final Workspace workspace, final View view, final Location at) {
+                layout.setSize(layout.getSize() + 1);
+                invalidateLayout();
+            }
+        });
+
+        if (layout.getSize() > 1) {
+            submenu.add(new UserActionAbstract("Remove " + (columnOrientation ? "Column" : "Row")) {
+                @Override
+                public void execute(final Workspace workspace, final View view, final Location at) {
+                    layout.setSize(layout.getSize() - 1);
+                    invalidateLayout();
+                }
+            });
+        }
+
+        submenu.add(new UserActionAbstract(columnOrientation ? "In Rows" : "In Columns") {
+            @Override
+            public void execute(final Workspace workspace, final View view, final Location at) {
+                layout.setOrientation(columnOrientation ? GridLayout.ROWS : GridLayout.COLUMNS);
+                invalidateLayout();
+            }
+        });
+    }
+
+    @Override
+    public void saveOptions(final Options viewOptions) {
+        super.saveOptions(viewOptions);
+
+        final GridLayout layout = getViewAxes().getAxis(GridLayout.class);
+        viewOptions.addOption("orientation", layout.getOrientation() == GridLayout.COLUMNS ? "columns" : "rows");
+        viewOptions.addOption("size", layout.getSize() + "");
+    }
+
+    @Override
+    public void loadOptions(final Options viewOptions) {
+        super.loadOptions(viewOptions);
+
+        final GridLayout layout = getViewAxes().getAxis(GridLayout.class);
+        layout.setOrientation(viewOptions.getString("orientation", "columns").equals("columns") ? GridLayout.COLUMNS : GridLayout.ROWS);
+        layout.setSize(viewOptions.getInteger("size", 1));
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/eb613703/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/composite/IconGridViewSpecification.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/composite/IconGridViewSpecification.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/composite/IconGridViewSpecification.java
new file mode 100644
index 0000000..43bf01b
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/composite/IconGridViewSpecification.java
@@ -0,0 +1,87 @@
+/*
+ *  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.view.composite;
+
+import org.apache.isis.viewer.dnd.drawing.ColorsAndFonts;
+import org.apache.isis.viewer.dnd.view.Axes;
+import org.apache.isis.viewer.dnd.view.Content;
+import org.apache.isis.viewer.dnd.view.Toolkit;
+import org.apache.isis.viewer.dnd.view.View;
+import org.apache.isis.viewer.dnd.view.ViewFactory;
+import org.apache.isis.viewer.dnd.view.base.Layout;
+import org.apache.isis.viewer.dnd.view.border.IconBorder;
+import org.apache.isis.viewer.dnd.view.border.LineBorder;
+
+public class IconGridViewSpecification extends AbstractCollectionViewSpecification {
+
+    public IconGridViewSpecification() {
+        addViewDecorator(new IconBorder.Factory());
+    }
+
+    @Override
+    protected ViewFactory createElementFactory() {
+        return new ViewFactory() {
+            @Override
+            public View createView(final Content content, final Axes axes, final int sequence) {
+                final View icon = new ImageViewSpecification().createView(content, axes, sequence);
+                /*
+                 * Icon icon = new Icon(content,
+                 * IconGridViewSpecification.this); Text textStyle =
+                 * Toolkit.getText(ColorsAndFonts.TEXT_NORMAL);
+                 * icon.setTitle(new ObjectTitleText(icon, textStyle));
+                 * icon.setSelectedGraphic(new IconGraphic(icon, 68));
+                 * icon.setUnselectedGraphic(new IconGraphic(icon, 60));
+                 * icon.setVertical(true);
+                 * 
+                 * // return icon;
+                 */
+                final LineBorder lineBorderedIcon = new LineBorder(icon);
+                lineBorderedIcon.setPadding(4);
+                lineBorderedIcon.setColor(Toolkit.getColor(ColorsAndFonts.COLOR_PRIMARY3));
+
+                // return lineBorderedIcon;
+
+                return new ReplaceViewBorder(lineBorderedIcon);
+            }
+        };
+    }
+
+    @Override
+    public Layout createLayout(final Content content, final Axes axes) {
+        final GridLayout gridLayout = new GridLayout();
+        gridLayout.setSize(3);
+        return gridLayout;
+    }
+
+    @Override
+    public String getName() {
+        return "Icon Grid";
+    }
+
+    /*
+     * private static final ObjectSpecification BOOK_SPECIFICATION =
+     * IsisContext.getSpecificationLoader().loadSpecification(
+     * "org.apache.isis.example.library.dom.Book"); public boolean
+     * canDisplay(ViewRequirement requirement) { return
+     * super.canDisplay(requirement) &&
+     * requirement.getAdapter().getTypeOfFacet().valueSpec() ==
+     * BOOK_SPECIFICATION; }
+     */
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/eb613703/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/composite/ImageViewSpecification.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/composite/ImageViewSpecification.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/composite/ImageViewSpecification.java
new file mode 100644
index 0000000..7a5ed88
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/composite/ImageViewSpecification.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.view.composite;
+
+import org.apache.isis.viewer.dnd.drawing.ColorsAndFonts;
+import org.apache.isis.viewer.dnd.drawing.Text;
+import org.apache.isis.viewer.dnd.icon.Icon;
+import org.apache.isis.viewer.dnd.view.Axes;
+import org.apache.isis.viewer.dnd.view.Content;
+import org.apache.isis.viewer.dnd.view.Toolkit;
+import org.apache.isis.viewer.dnd.view.View;
+import org.apache.isis.viewer.dnd.view.ViewRequirement;
+import org.apache.isis.viewer.dnd.view.ViewSpecification;
+import org.apache.isis.viewer.dnd.view.base.IconGraphic;
+import org.apache.isis.viewer.dnd.view.text.ObjectTitleText;
+
+public class ImageViewSpecification implements ViewSpecification {
+
+    @Override
+    public View createView(final Content content, final Axes axes, final int sequence) {
+        final Icon icon = new Icon(content, ImageViewSpecification.this);
+        final Text textStyle = Toolkit.getText(ColorsAndFonts.TEXT_NORMAL);
+        icon.setTitle(new ObjectTitleText(icon, textStyle));
+        icon.setSelectedGraphic(new IconGraphic(icon, 68));
+        icon.setUnselectedGraphic(new IconGraphic(icon, 60));
+        icon.setVertical(true);
+
+        return icon;
+    }
+
+    @Override
+    public boolean canDisplay(final ViewRequirement requirement) {
+        return requirement.isObject() && requirement.isClosed();
+    }
+
+    @Override
+    public String getName() {
+        return "Image";
+    }
+
+    @Override
+    public boolean isAligned() {
+        return false;
+    }
+
+    @Override
+    public boolean isOpen() {
+        return false;
+    }
+
+    @Override
+    public boolean isReplaceable() {
+        return false;
+    }
+
+    @Override
+    public boolean isResizeable() {
+        return false;
+    }
+
+    @Override
+    public boolean isSubView() {
+        return false;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/eb613703/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/composite/LineBorderDecorator.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/composite/LineBorderDecorator.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/composite/LineBorderDecorator.java
new file mode 100644
index 0000000..ed46eaa
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/composite/LineBorderDecorator.java
@@ -0,0 +1,39 @@
+/*
+ *  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.view.composite;
+
+import org.apache.isis.viewer.dnd.view.Axes;
+import org.apache.isis.viewer.dnd.view.SubviewDecorator;
+import org.apache.isis.viewer.dnd.view.View;
+import org.apache.isis.viewer.dnd.view.ViewAxis;
+import org.apache.isis.viewer.dnd.view.border.LineBorder;
+
+public class LineBorderDecorator implements SubviewDecorator {
+
+    @Override
+    public View decorate(final Axes axes, final View view) {
+        return new LineBorder(view);
+    }
+
+    @Override
+    public ViewAxis createAxis(final org.apache.isis.viewer.dnd.view.Content content) {
+        return null;
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/eb613703/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/composite/MasterDetailPanel.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/composite/MasterDetailPanel.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/composite/MasterDetailPanel.java
new file mode 100644
index 0000000..0ace123
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/composite/MasterDetailPanel.java
@@ -0,0 +1,232 @@
+/*
+ *  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.view.composite;
+
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.viewer.dnd.drawing.Location;
+import org.apache.isis.viewer.dnd.drawing.Size;
+import org.apache.isis.viewer.dnd.view.Axes;
+import org.apache.isis.viewer.dnd.view.Content;
+import org.apache.isis.viewer.dnd.view.ObjectContent;
+import org.apache.isis.viewer.dnd.view.Selectable;
+import org.apache.isis.viewer.dnd.view.Toolkit;
+import org.apache.isis.viewer.dnd.view.View;
+import org.apache.isis.viewer.dnd.view.ViewRequirement;
+import org.apache.isis.viewer.dnd.view.ViewSpecification;
+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.border.SelectableViewAxis;
+import org.apache.isis.viewer.dnd.view.border.ViewResizeBorder;
+import org.apache.isis.viewer.dnd.view.collection.CollectionContent;
+import org.apache.isis.viewer.dnd.view.content.NullContent;
+
+public class MasterDetailPanel extends CompositeView implements Selectable {
+    private static final int MINIMUM_WIDTH = 120;
+    private final ViewSpecification leftHandSideSpecification;
+    private final Axes axes;
+
+    public MasterDetailPanel(final Content content, final ViewSpecification specification, final ViewSpecification leftHandSideSpecification) {
+        super(content, specification);
+        this.leftHandSideSpecification = leftHandSideSpecification;
+        axes = new Axes();
+        axes.add(new SelectableViewAxis(this));
+    }
+
+    @Override
+    protected void buildView() {
+        final Content content = getContent();
+        View leftHandView = leftHandSideSpecification.createView(content, axes, -1);
+        leftHandView = new ViewResizeBorder(new ScrollBorder(leftHandView));
+        leftHandView.setParent(getView());
+        addView(leftHandView);
+
+        final Size blankViewSize = new Size(MINIMUM_WIDTH, 0);
+        final View blankView = new BlankView(new NullContent(), blankViewSize);
+        blankView.setParent(getView());
+        addView(blankView);
+
+        selectFirstSuitableObject(content);
+    }
+
+    private void selectFirstSuitableObject(final Content content) {
+        if (content instanceof CollectionContent) {
+            final ObjectAdapter[] elements = ((CollectionContent) content).elements();
+            if (elements.length > 0) {
+                final ObjectAdapter firstElement = elements[0];
+                final Content firstElementContent = Toolkit.getContentFactory().createRootContent(firstElement);
+                setSelectedNode(firstElementContent);
+            }
+        } else if (content instanceof ObjectContent) {
+            /*
+             * TODO provide a view that shows first useful object (not
+             * redisplaying parent)
+             * 
+             * ObjectAssociation[] associations =
+             * content.getSpecification().getAssociations(); for (int i = 0; i <
+             * associations.length; i++) { ObjectAssociation assoc =
+             * associations[i]; if (assoc.isOneToManyAssociation()) {
+             * ObjectAdapter collection = assoc.get(content.getAdapter()); final
+             * Content collectionContent =
+             * Toolkit.getContentFactory().createRootContent(collection);
+             * setSelectedNode(collectionContent); break; } else if
+             * (assoc.isOneToOneAssociation() &&
+             * !((OneToOneAssociation)assoc).getSpecification().isParseable()) {
+             * ObjectAdapter object = assoc.get(content.getAdapter()); if
+             * (object == null) { continue; } final Content objectContent =
+             * Toolkit.getContentFactory().createRootContent(object);
+             * setSelectedNode(objectContent); break; } }
+             */
+            setSelectedNode(content);
+        }
+    }
+
+    @Override
+    protected void doLayout(final Size availableSpace) {
+        availableSpace.contract(getView().getPadding());
+
+        final View[] subviews = getSubviews();
+        final View left = subviews[0];
+        final View right = subviews[1];
+        final Size leftPanelRequiredSize = left.getRequiredSize(new Size(availableSpace));
+        final Size rightPanelRequiredSize = right == null ? new Size() : right.getRequiredSize(new Size(availableSpace));
+
+        // combine the two sizes
+        final Size totalSize = new Size(leftPanelRequiredSize);
+        totalSize.extendWidth(rightPanelRequiredSize.getWidth());
+        totalSize.ensureHeight(rightPanelRequiredSize.getHeight());
+
+        if (totalSize.getWidth() > availableSpace.getWidth()) {
+            /*
+             * If the combined width is greater than the available then we need
+             * to divide the space between the two sides and recalculate
+             */
+            if (rightPanelRequiredSize.getWidth() <= MINIMUM_WIDTH) {
+                leftPanelRequiredSize.setWidth(availableSpace.getWidth() - rightPanelRequiredSize.getWidth());
+            } else {
+                final int availableWidth = availableSpace.getWidth();
+                final int requiredWidth = totalSize.getWidth();
+                leftPanelRequiredSize.setWidth(leftPanelRequiredSize.getWidth() * availableWidth / requiredWidth);
+                rightPanelRequiredSize.setWidth(rightPanelRequiredSize.getWidth() * availableWidth / requiredWidth);
+            }
+            /*
+             * final int leftWidth = Math.max(MINIMUM_WIDTH,
+             * leftPanelRequiredSize.getWidth()); final int rightWidth =
+             * rightPanelRequiredSize.getWidth(); final int totalWidth =
+             * leftWidth + rightWidth;
+             * 
+             * final int bestWidth = (int) (1.0 * leftWidth / totalWidth *
+             * availableWidth); final Size maximumSizeLeft = new Size(bestWidth,
+             * maximumSize.getHeight()); leftPanelRequiredSize =
+             * left.getRequiredSize(maximumSizeLeft);
+             * 
+             * final Size maximumSizeRight = new Size(availableWidth -
+             * leftPanelRequiredSize.getWidth(), maximumSize.getHeight());
+             * rightPanelRequiredSize = right.getRequiredSize(maximumSizeRight);
+             */
+        }
+
+        // combinedSize.setHeight(Math.min(combinedSize.getHeight(),
+        // maximumSize.getHeight()));
+        // totalSize.limitSize(availableSpace);
+
+        left.setSize(new Size(leftPanelRequiredSize.getWidth(), totalSize.getHeight()));
+        left.layout();
+
+        if (right != null) {
+            right.setLocation(new Location(left.getSize().getWidth(), 0));
+
+            rightPanelRequiredSize.setHeight(totalSize.getHeight());
+            right.setSize(rightPanelRequiredSize);
+            right.layout();
+        }
+    }
+
+    @Override
+    public Size requiredSize(final Size availableSpace) {
+        final View[] subviews = getSubviews();
+        final View left = subviews[0];
+        final View right = subviews.length > 1 ? subviews[1] : null;
+
+        Size leftPanelRequiredSize = left.getRequiredSize(new Size(availableSpace));
+        Size rightPanelRequiredSize = right == null ? new Size() : right.getRequiredSize(new Size(availableSpace));
+
+        if (leftPanelRequiredSize.getWidth() + rightPanelRequiredSize.getWidth() > availableSpace.getWidth()) {
+            /*
+             * If the combined width is greater than the available then we need
+             * to divide the space between the two sides and recalculate
+             */
+
+            final int availableWidth = availableSpace.getWidth();
+            final int leftWidth = leftPanelRequiredSize.getWidth();
+            final int rightWidth = Math.max(MINIMUM_WIDTH, rightPanelRequiredSize.getWidth());
+            final int totalWidth = leftWidth + rightWidth;
+
+            final int bestWidth = (int) (1.0 * leftWidth / totalWidth * availableWidth);
+            final Size maximumSizeLeft = new Size(bestWidth, availableSpace.getHeight());
+            leftPanelRequiredSize = left.getRequiredSize(maximumSizeLeft);
+
+            final Size maximumSizeRight = new Size(availableWidth - leftPanelRequiredSize.getWidth(), availableSpace.getHeight());
+            rightPanelRequiredSize = right == null ? new Size() : right.getRequiredSize(maximumSizeRight);
+        }
+
+        // combine the two required sizes
+        final Size combinedSize = new Size(leftPanelRequiredSize);
+        combinedSize.extendWidth(rightPanelRequiredSize.getWidth());
+        combinedSize.ensureHeight(rightPanelRequiredSize.getHeight());
+        return combinedSize;
+    }
+
+    protected void showInRightPane(final View view) {
+        replaceView(getSubviews()[1], view);
+    }
+
+    @Override
+    public void setSelectedNode(final View view) {
+        final Content content = view.getContent();
+        setSelectedNode(content);
+    }
+
+    private void setSelectedNode(final Content content) {
+        final ViewRequirement requirement = new ViewRequirement(content, ViewRequirement.OPEN | ViewRequirement.SUBVIEW | ViewRequirement.FIXED);
+        /*
+         * final ObjectAdapter object = content.getAdapter(); final
+         * ObjectSpecification specification = object.getSpecification(); final
+         * CollectionFacet facet =
+         * specification.getFacet(CollectionFacet.class); if (facet != null &&
+         * facet.size(object) > 0) { if
+         * (mainViewTableSpec.canDisplay(requirement)) {
+         * showInRightPane(mainViewTableSpec.createView(content, axes, -1)); }
+         * else if (mainViewListSpec.canDisplay(requirement)) {
+         * showInRightPane(mainViewListSpec.createView(content, axes, -1)); }
+         * 
+         * } else if (specification.isObject()) { if (object != null &&
+         * mainViewFormSpec.canDisplay(requirement)) {
+         * showInRightPane(mainViewFormSpec.createView(content, axes, -1)); } }
+         */
+        final View createView = Toolkit.getViewFactory().createView(requirement);
+        showInRightPane(createView);
+    }
+
+    @Override
+    public String toString() {
+        return "MasterDetailPanel" + getId();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/eb613703/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/composite/ObjectFieldBuilder.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/composite/ObjectFieldBuilder.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/composite/ObjectFieldBuilder.java
new file mode 100644
index 0000000..b14250b
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/composite/ObjectFieldBuilder.java
@@ -0,0 +1,195 @@
+/*
+ *  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.view.composite;
+
+import java.util.List;
+
+import org.apache.log4j.Logger;
+
+import org.apache.isis.applib.annotation.Where;
+import org.apache.isis.applib.filter.Filter;
+import org.apache.isis.core.commons.ensure.Assert;
+import org.apache.isis.core.commons.exceptions.UnknownTypeException;
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.adapter.util.AdapterUtils;
+import org.apache.isis.core.metamodel.spec.ObjectSpecification;
+import org.apache.isis.core.metamodel.spec.feature.ObjectAssociation;
+import org.apache.isis.core.metamodel.spec.feature.ObjectAssociationFilters;
+import org.apache.isis.runtimes.dflt.runtime.system.context.IsisContext;
+import org.apache.isis.viewer.dnd.view.Axes;
+import org.apache.isis.viewer.dnd.view.Content;
+import org.apache.isis.viewer.dnd.view.ObjectContent;
+import org.apache.isis.viewer.dnd.view.SubviewDecorator;
+import org.apache.isis.viewer.dnd.view.Toolkit;
+import org.apache.isis.viewer.dnd.view.View;
+import org.apache.isis.viewer.dnd.view.ViewFactory;
+import org.apache.isis.viewer.dnd.view.base.FieldErrorView;
+import org.apache.isis.viewer.dnd.view.content.FieldContent;
+
+public class ObjectFieldBuilder extends AbstractViewBuilder {
+    private static final Logger LOG = Logger.getLogger(ObjectFieldBuilder.class);
+
+    // REVIEW: confirm this rendering context
+    private final Where where = Where.OBJECT_FORMS;
+
+    private final ViewFactory subviewDesign;
+
+    // TODO remove - transitional
+    public ObjectFieldBuilder(final ViewFactory subviewDesign) {
+        this.subviewDesign = subviewDesign;
+    }
+
+    public ObjectFieldBuilder(final ViewFactory subviewDesign, final SubviewDecorator subviewDecorator) {
+        this.subviewDesign = subviewDesign;
+        addSubviewDecorator(subviewDecorator);
+    }
+
+    @Override
+    public void build(final View view, final Axes axes) {
+        Assert.assertEquals("ensure the view is the complete decorated view", view.getView(), view);
+
+        final Content content = view.getContent();
+        final ObjectAdapter object = ((ObjectContent) content).getObject();
+
+        LOG.debug("build view " + view + " for " + object);
+
+        final ObjectSpecification spec = object.getSpecification();
+        final Filter<ObjectAssociation> filter = ObjectAssociationFilters.dynamicallyVisible(IsisContext.getAuthenticationSession(), object, where);
+        final List<ObjectAssociation> flds = spec.getAssociations(filter);
+
+        if (view.getSubviews().length == 0) {
+            initialBuild(view, axes, object, flds);
+        } else {
+            updateBuild(view, axes, object, flds);
+        }
+    }
+
+    private void initialBuild(final View view, final Axes axes, final ObjectAdapter object, final List<ObjectAssociation> flds) {
+        LOG.debug("  as new build");
+        // addViewAxes(view);
+        for (int f = 0; f < flds.size(); f++) {
+            final ObjectAssociation field = flds.get(f);
+            addField(view, axes, object, field, f);
+        }
+    }
+
+    private void addField(final View view, final Axes axes, final ObjectAdapter object, final ObjectAssociation field, final int fieldNumber) {
+        final View fieldView = createFieldView(view, axes, object, fieldNumber, field);
+        if (fieldView != null) {
+            view.addView(decorateSubview(axes, fieldView));
+        }
+    }
+
+    private void updateBuild(final View view, final Axes axes, final ObjectAdapter object, final List<ObjectAssociation> flds) {
+        LOG.debug("  as update build");
+        /*
+         * 1/ To remove fields: look through views and remove any that don't
+         * exists in visible fields
+         * 
+         * 2/ From remaining views, check for changes as already being done, and
+         * replace if needed
+         * 
+         * 3/ Finally look through fields to see if there is no existing
+         * subview; and add one
+         */
+
+        View[] subviews = view.getSubviews();
+
+        // remove views for fields that no longer exist
+        outer: for (int i = 0; i < subviews.length; i++) {
+            final FieldContent fieldContent = ((FieldContent) subviews[i].getContent());
+
+            for (int j = 0; j < flds.size(); j++) {
+                final ObjectAssociation field = flds.get(j);
+                if (fieldContent.getField() == field) {
+                    continue outer;
+                }
+            }
+            view.removeView(subviews[i]);
+        }
+
+        // update existing fields if needed
+        subviews = view.getSubviews();
+        for (int i = 0; i < subviews.length; i++) {
+            final View subview = subviews[i];
+            final ObjectAssociation field = ((FieldContent) subview.getContent()).getField();
+            final ObjectAdapter value = field.get(object);
+
+            if (field.isOneToManyAssociation()) {
+                subview.update(value);
+            } else if (field.isOneToOneAssociation()) {
+                final ObjectAdapter existing = subview.getContent().getAdapter();
+
+                // if the field is parseable then it may have been modified; we
+                // need to replace what was
+                // typed in with the actual title.
+                if (!field.getSpecification().isParseable()) {
+                    final boolean changedValue = value != existing;
+                    final boolean isDestroyed = existing != null && existing.isDestroyed();
+                    if (changedValue || isDestroyed) {
+                        View fieldView;
+                        fieldView = createFieldView(view, axes, object, i, field);
+                        if (fieldView != null) {
+                            view.replaceView(subview, decorateSubview(axes, fieldView));
+                        } else {
+                            view.addView(new FieldErrorView("No field for " + value));
+                        }
+                    }
+                } else {
+                    if (AdapterUtils.exists(value) && !AdapterUtils.wrappedEqual(value, existing)) {
+                        final View fieldView = createFieldView(view, axes, object, i, field);
+                        view.replaceView(subview, decorateSubview(axes, fieldView));
+                    } else {
+                        subview.refresh();
+                    }
+                }
+            } else {
+                throw new UnknownTypeException(field.getName());
+            }
+        }
+
+        // add new fields
+        outer2: for (int j = 0; j < flds.size(); j++) {
+            final ObjectAssociation field = flds.get(j);
+            for (int i = 0; i < subviews.length; i++) {
+                final FieldContent fieldContent = ((FieldContent) subviews[i].getContent());
+                if (fieldContent.getField() == field) {
+                    continue outer2;
+                }
+            }
+            addField(view, axes, object, field, j);
+        }
+    }
+
+    private View createFieldView(final View view, final Axes axes, final ObjectAdapter object, final int fieldNumber, final ObjectAssociation field) {
+        if (field == null) {
+            throw new NullPointerException();
+        }
+
+        if (field.isOneToOneAssociation()) {
+            IsisContext.getPersistenceSession().resolveField(object, field);
+        }
+
+        final Content content1 = Toolkit.getContentFactory().createFieldContent(field, object);
+        final View fieldView = subviewDesign.createView(content1, axes, fieldNumber);
+        return fieldView;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/eb613703/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/composite/ReplaceViewBorder.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/composite/ReplaceViewBorder.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/composite/ReplaceViewBorder.java
new file mode 100644
index 0000000..7252e52
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/composite/ReplaceViewBorder.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.view.composite;
+
+import org.apache.isis.viewer.dnd.drawing.Bounds;
+import org.apache.isis.viewer.dnd.drawing.Canvas;
+import org.apache.isis.viewer.dnd.form.FormSpecification;
+import org.apache.isis.viewer.dnd.view.Axes;
+import org.apache.isis.viewer.dnd.view.Click;
+import org.apache.isis.viewer.dnd.view.Toolkit;
+import org.apache.isis.viewer.dnd.view.View;
+import org.apache.isis.viewer.dnd.view.base.AbstractBorder;
+
+public class ReplaceViewBorder extends AbstractBorder {
+
+    protected ReplaceViewBorder(final View view) {
+        super(view);
+    }
+
+    @Override
+    public void draw(final Canvas canvas) {
+        super.draw(canvas);
+
+        final Bounds b = getButtonBounds();
+        canvas.drawRoundedRectangle(b.getX(), b.getY(), b.getWidth(), b.getHeight(), 6, 6, Toolkit.getColor(0xfff));
+    }
+
+    @Override
+    public void firstClick(final Click click) {
+        if (getButtonBounds().contains(click.getLocation())) {
+            final View view = new FormSpecification().createView(getContent(), new Axes(), 0);
+            getWorkspace().replaceView(getParent(), view);
+        }
+    }
+
+    private Bounds getButtonBounds() {
+        final int x = getSize().getWidth() - 28;
+        return new Bounds(x, 8, 20, 16);
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/eb613703/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/composite/StackLayout.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/composite/StackLayout.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/composite/StackLayout.java
new file mode 100644
index 0000000..3d224c3
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/composite/StackLayout.java
@@ -0,0 +1,86 @@
+/*
+ *  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.view.composite;
+
+import org.apache.isis.viewer.dnd.drawing.Location;
+import org.apache.isis.viewer.dnd.drawing.Size;
+import org.apache.isis.viewer.dnd.view.View;
+import org.apache.isis.viewer.dnd.view.base.Layout;
+
+/**
+ * A stack layout stacks its components vertically, one on top of the other,
+ * working down from the top. Each component is given the space it requests.
+ * Components adopt the width of the widest component when that component's view
+ * specification's <code>isAligned</code> method returns <code>true</code>, or
+ * the layout's <code>fixedWidth</code> flag is set (via the two parameter
+ * constructor).
+ * 
+ */
+public class StackLayout implements Layout {
+    private final boolean fixedWidth;
+
+    public StackLayout() {
+        this.fixedWidth = false;
+    }
+
+    public StackLayout(final boolean fixedWidth) {
+        this.fixedWidth = fixedWidth;
+    }
+
+    @Override
+    public Size getRequiredSize(final View view) {
+        int height = 0;
+        int width = 0;
+        final View views[] = view.getSubviews();
+
+        for (final View v : views) {
+            final Size s = v.getRequiredSize(new Size(Integer.MAX_VALUE, Integer.MAX_VALUE));
+            width = Math.max(width, s.getWidth());
+            height += s.getHeight();
+        }
+
+        return new Size(width, height);
+    }
+
+    @Override
+    public void layout(final View view, final Size maximumSize) {
+        final int x = 0;
+        int y = 0;
+        final View subviews[] = view.getSubviews();
+
+        int maxWidth = 0;
+        for (final View v : subviews) {
+            final Size s = v.getRequiredSize(new Size(maximumSize));
+            maxWidth = Math.max(maxWidth, s.getWidth());
+        }
+
+        for (final View v : subviews) {
+            final Size s = v.getRequiredSize(new Size(maximumSize));
+            s.limitWidth(maximumSize.getWidth());
+            if (fixedWidth || v.getSpecification().isAligned()) {
+                s.ensureWidth(maxWidth);
+            }
+            v.setSize(s);
+            v.setLocation(new Location(x, y));
+            y += s.getHeight();
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/eb613703/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/composite/StandardFields.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/composite/StandardFields.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/composite/StandardFields.java
new file mode 100644
index 0000000..9caba31
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/composite/StandardFields.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.view.composite;
+
+import org.apache.isis.core.commons.exceptions.UnknownTypeException;
+import org.apache.isis.viewer.dnd.view.Axes;
+import org.apache.isis.viewer.dnd.view.Content;
+import org.apache.isis.viewer.dnd.view.GlobalViewFactory;
+import org.apache.isis.viewer.dnd.view.Toolkit;
+import org.apache.isis.viewer.dnd.view.View;
+import org.apache.isis.viewer.dnd.view.ViewFactory;
+import org.apache.isis.viewer.dnd.view.ViewRequirement;
+
+/**
+ * A view factory for the components of a container
+ */
+public class StandardFields implements ViewFactory {
+
+    @Override
+    public View createView(final Content content, final Axes axes, final int sequence) {
+        final GlobalViewFactory factory = Toolkit.getViewFactory();
+
+        int requirement = 0;
+        if (content.isObject()) {
+            requirement = objectRequirement();
+        } else if (content.isTextParseable()) {
+            requirement = textParseableRequirement();
+        } else if (content.isCollection()) {
+            requirement = collectionRequirement();
+        } else {
+            throw new UnknownTypeException(content);
+        }
+
+        if (requirement != 0 && include(content, sequence)) {
+            final ViewRequirement viewRequirement = new ViewRequirement(content, requirement);
+            return factory.createView(viewRequirement);
+        } else {
+            return null;
+        }
+    }
+
+    protected boolean include(final Content content, final int sequence) {
+        return true;
+    }
+
+    protected int objectRequirement() {
+        return ViewRequirement.CLOSED | ViewRequirement.SUBVIEW;
+    }
+
+    protected int textParseableRequirement() {
+        return ViewRequirement.CLOSED | ViewRequirement.SUBVIEW;
+    }
+
+    protected int collectionRequirement() {
+        return ViewRequirement.CLOSED | ViewRequirement.SUBVIEW;
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/eb613703/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/composite/ViewBuilder.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/composite/ViewBuilder.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/composite/ViewBuilder.java
new file mode 100644
index 0000000..4d6fef5
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/composite/ViewBuilder.java
@@ -0,0 +1,58 @@
+/*
+ *  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.view.composite;
+
+import org.apache.isis.viewer.dnd.view.Axes;
+import org.apache.isis.viewer.dnd.view.Content;
+import org.apache.isis.viewer.dnd.view.SubviewDecorator;
+import org.apache.isis.viewer.dnd.view.UserActionSet;
+import org.apache.isis.viewer.dnd.view.View;
+
+public interface ViewBuilder {
+
+    void addSubviewDecorator(SubviewDecorator decorator);
+
+    void createAxes(Axes axes, Content content);
+
+    void build(View view, Axes axes);
+
+    /**
+     * Indicates whether this view is expanded, or iconized.
+     * 
+     * @return true if it is showing the object's details; false if it is
+     *         showing the object only.
+     */
+    boolean isOpen();
+
+    /**
+     * Indicates whether this view can be replaced with another view (for the
+     * same value or reference).
+     * 
+     * @return true if it can be replaced by another view; false if it can't be
+     *         replaces
+     */
+    boolean isReplaceable();
+
+    boolean isSubView();
+
+    boolean canDragView();
+
+    void viewMenuOptions(UserActionSet options, View view);
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/eb613703/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/content/AbstractContent.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/content/AbstractContent.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/content/AbstractContent.java
new file mode 100644
index 0000000..ac2ca9f
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/content/AbstractContent.java
@@ -0,0 +1,58 @@
+/*
+ *  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.view.content;
+
+import org.apache.isis.viewer.dnd.view.Content;
+import org.apache.isis.viewer.dnd.view.UserActionSet;
+
+public abstract class AbstractContent implements Content {
+    @Override
+    public void contentMenuOptions(final UserActionSet options) {
+    }
+
+    @Override
+    public boolean isCollection() {
+        return false;
+    }
+
+    @Override
+    public boolean isObject() {
+        return false;
+    }
+
+    @Override
+    public boolean isPersistable() {
+        return false;
+    }
+
+    @Override
+    public boolean isTextParseable() {
+        return false;
+    }
+
+    @Override
+    public void viewMenuOptions(final UserActionSet options) {
+    }
+
+    @Override
+    public String windowTitle() {
+        return "";
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/eb613703/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/content/AbstractObjectContent.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/content/AbstractObjectContent.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/content/AbstractObjectContent.java
new file mode 100644
index 0000000..dba30b2
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/content/AbstractObjectContent.java
@@ -0,0 +1,294 @@
+/*
+ *  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.view.content;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.isis.applib.annotation.Where;
+import org.apache.isis.applib.query.QueryFindAllInstances;
+import org.apache.isis.core.commons.ensure.Assert;
+import org.apache.isis.core.commons.exceptions.UnexpectedCallException;
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.adapter.ResolveState;
+import org.apache.isis.core.metamodel.consent.Consent;
+import org.apache.isis.core.metamodel.consent.ConsentAbstract;
+import org.apache.isis.core.metamodel.consent.Veto;
+import org.apache.isis.core.metamodel.services.container.query.QueryCardinality;
+import org.apache.isis.core.metamodel.spec.ActionType;
+import org.apache.isis.core.metamodel.spec.ObjectSpecification;
+import org.apache.isis.core.metamodel.spec.Persistability;
+import org.apache.isis.core.metamodel.spec.feature.ObjectAction;
+import org.apache.isis.core.metamodel.spec.feature.ObjectAssociation;
+import org.apache.isis.core.metamodel.spec.feature.ObjectAssociationFilters;
+import org.apache.isis.core.metamodel.spec.feature.OneToOneAssociation;
+import org.apache.isis.runtimes.dflt.runtime.system.context.IsisContext;
+import org.apache.isis.runtimes.dflt.runtime.system.persistence.Persistor;
+import org.apache.isis.viewer.dnd.drawing.Image;
+import org.apache.isis.viewer.dnd.drawing.ImageFactory;
+import org.apache.isis.viewer.dnd.drawing.Location;
+import org.apache.isis.viewer.dnd.view.Content;
+import org.apache.isis.viewer.dnd.view.ObjectContent;
+import org.apache.isis.viewer.dnd.view.Placement;
+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 abstract class AbstractObjectContent extends AbstractContent implements ObjectContent {
+
+    public static final class ExplorationInstances extends UserActionAbstract {
+
+        public ExplorationInstances() {
+            super("Instances", ActionType.EXPLORATION);
+        }
+
+        @Override
+        public Consent disabled(final View view) {
+            final ObjectAdapter object = view.getContent().getAdapter();
+            return ConsentAbstract.allowIf(object != null);
+        }
+
+        @Override
+        public void execute(final Workspace workspace, final View view, final Location at) {
+            final ObjectAdapter object = view.getContent().getAdapter();
+            final ObjectSpecification spec = object.getSpecification();
+            final ObjectAdapter instances = IsisContext.getPersistenceSession().findInstances(new QueryFindAllInstances(spec.getFullIdentifier()), QueryCardinality.MULTIPLE);
+            workspace.objectActionResult(instances, new Placement(view));
+        }
+    }
+
+    public static final class ExplorationClone extends UserActionAbstract {
+
+        public ExplorationClone() {
+            super("Clone", ActionType.EXPLORATION);
+        }
+
+        @Override
+        public Consent disabled(final View view) {
+            final ObjectAdapter object = view.getContent().getAdapter();
+            return ConsentAbstract.allowIf(object != null);
+        }
+
+        @Override
+        public void execute(final Workspace workspace, final View view, final Location at) {
+            final ObjectAdapter original = view.getContent().getAdapter();
+            // ObjectAdapter original = getObject();
+            final ObjectSpecification spec = original.getSpecification();
+
+            final ObjectAdapter clone = getPersistenceSession().createTransientInstance(spec);
+            final List<ObjectAssociation> fields = spec.getAssociations();
+            for (int i = 0; i < fields.size(); i++) {
+                final ObjectAdapter fld = fields.get(i).get(original);
+
+                if (fields.get(i).isOneToOneAssociation()) {
+                    ((OneToOneAssociation) fields.get(i)).setAssociation(clone, fld);
+                } else if (fields.get(i).isOneToManyAssociation()) {
+                    // clone.setValue((OneToOneAssociation) fields[i],
+                    // fld.getObject());
+                }
+            }
+
+            workspace.objectActionResult(clone, new Placement(view));
+        }
+    }
+
+    public static final class DebugClearResolvedOption extends UserActionAbstract {
+
+        private DebugClearResolvedOption() {
+            super("Clear resolved", ActionType.DEBUG);
+        }
+
+        @Override
+        public Consent disabled(final View view) {
+            final ObjectAdapter object = view.getContent().getAdapter();
+            return ConsentAbstract.allowIf(object == null || !object.isTransient() || object.isGhost());
+        }
+
+        @Override
+        public void execute(final Workspace workspace, final View view, final Location at) {
+            final ObjectAdapter object = view.getContent().getAdapter();
+            object.changeState(ResolveState.GHOST);
+        }
+    }
+
+    // REVIEW: should provide this rendering context, rather than hardcoding.
+    // the net effect currently is that class members annotated with 
+    // @Hidden(where=Where.ANYWHERE) or @Disabled(where=Where.ANYWHERE) will indeed
+    // be hidden/disabled, but will be visible/enabled (perhaps incorrectly) 
+    // for any other value for Where
+    protected final Where where = Where.ANYWHERE;
+
+    @Override
+    public abstract Consent canClear();
+
+    @Override
+    public Consent canDrop(final Content sourceContent) {
+        final ObjectAdapter target = getObject();
+        if (!(sourceContent instanceof ObjectContent) || target == null) {
+            // TODO: move logic into Facet
+            return new Veto(String.format("Can't drop %s onto empty target", sourceContent.getAdapter().titleString()));
+        } else {
+            final ObjectAdapter source = ((ObjectContent) sourceContent).getObject();
+            return canDropOntoObject(target, source);
+        }
+    }
+
+    private Consent canDropOntoObject(final ObjectAdapter target, final ObjectAdapter source) {
+        final ObjectAction action = dropAction(source, target);
+        if (action != null) {
+            final Consent parameterSetValid = action.isProposedArgumentSetValid(target, new ObjectAdapter[] { source });
+            parameterSetValid.setDescription("Execute '" + action.getName() + "' with " + source.titleString());
+            return parameterSetValid;
+        } else {
+            return setFieldOfMatchingType(target, source);
+        }
+    }
+
+    private Consent setFieldOfMatchingType(final ObjectAdapter targetAdapter, final ObjectAdapter sourceAdapter) {
+        if (targetAdapter.isTransient() && sourceAdapter.representsPersistent()) {
+            // TODO: use Facet for this test instead.
+            return new Veto("Can't set field in persistent object with reference to non-persistent object");
+        }
+        final List<ObjectAssociation> fields = targetAdapter.getSpecification().getAssociations(ObjectAssociationFilters.dynamicallyVisible(IsisContext.getAuthenticationSession(), targetAdapter, where));
+        for (final ObjectAssociation fld : fields) {
+            if (!fld.isOneToOneAssociation()) {
+                continue;
+            }
+            if (!sourceAdapter.getSpecification().isOfType(fld.getSpecification())) {
+                continue;
+            }
+            if (fld.get(targetAdapter) != null) {
+                continue;
+            }
+            final Consent associationValid = ((OneToOneAssociation) fld).isAssociationValid(targetAdapter, sourceAdapter);
+            if (associationValid.isAllowed()) {
+                return associationValid.setDescription("Set field " + fld.getName());
+            }
+
+        }
+        // TODO: use Facet for this test instead
+        return new Veto(String.format("No empty field accepting object of type %s in %s", sourceAdapter.getSpecification().getSingularName(), title()));
+    }
+
+    @Override
+    public abstract Consent canSet(final ObjectAdapter dragSource);
+
+    @Override
+    public abstract void clear();
+
+    @Override
+    public ObjectAdapter drop(final Content sourceContent) {
+        if (!(sourceContent instanceof ObjectContent)) {
+            return null;
+        }
+
+        final ObjectAdapter source = sourceContent.getAdapter();
+        Assert.assertNotNull(source);
+
+        final ObjectAdapter target = getObject();
+        Assert.assertNotNull(target);
+
+        if (!canDrop(sourceContent).isAllowed()) {
+            return null;
+        }
+
+        final ObjectAction action = dropAction(source, target);
+        if ((action != null) && action.isProposedArgumentSetValid(target, new ObjectAdapter[] { source }).isAllowed()) {
+            return action.execute(target, new ObjectAdapter[] { source });
+        }
+
+        final List<ObjectAssociation> associations = target.getSpecification().getAssociations(ObjectAssociationFilters.dynamicallyVisible(IsisContext.getAuthenticationSession(), target, where));
+
+        for (int i = 0; i < associations.size(); i++) {
+            final ObjectAssociation association = associations.get(i);
+            if (association.isOneToOneAssociation() && source.getSpecification().isOfType(association.getSpecification())) {
+                final OneToOneAssociation otoa = (OneToOneAssociation) association;
+                if (association.get(target) == null && otoa.isAssociationValid(target, source).isAllowed()) {
+                    otoa.setAssociation(target, source);
+                    break;
+                }
+            }
+        }
+
+        return null;
+    }
+
+    private ObjectAction dropAction(final ObjectAdapter source, final ObjectAdapter target) {
+        final ObjectAction action = target.getSpecification().getObjectAction(ActionType.USER, null, Arrays.asList(source.getSpecification()));
+        return action;
+    }
+
+    @Override
+    public abstract ObjectAdapter getObject();
+
+    @Override
+    public boolean isPersistable() {
+        return getObject().getSpecification().persistability() == Persistability.USER_PERSISTABLE;
+    }
+
+    @Override
+    public void contentMenuOptions(final UserActionSet options) {
+        final ObjectAdapter object = getObject();
+        options.addObjectMenuOptions(object);
+
+        if (getObject() == null) {
+            options.addCreateOptions(getSpecification());
+        } else {
+            options.add(new ExplorationInstances());
+        }
+
+        options.add(new ExplorationClone());
+        options.add(new DebugClearResolvedOption());
+    }
+
+    public void parseTextEntry(final String entryText) {
+        throw new UnexpectedCallException();
+    }
+
+    @Override
+    public abstract void setObject(final ObjectAdapter object);
+
+    @Override
+    public String getIconName() {
+        final ObjectAdapter object = getObject();
+        return object == null ? null : object.getIconName();
+    }
+
+    @Override
+    public Image getIconPicture(final int iconHeight) {
+        final ObjectAdapter adapter = getObject();
+        if (adapter == null) {
+            return ImageFactory.getInstance().loadIcon("empty-field", iconHeight, null);
+        }
+        final ObjectSpecification specification = adapter.getSpecification();
+        final Image icon = ImageFactory.getInstance().loadIcon(specification, iconHeight, null);
+        return icon;
+    }
+
+    // ////////////////////////////////////////////////////////////
+    // Dependencies (from context)
+    // ////////////////////////////////////////////////////////////
+
+    private static Persistor getPersistenceSession() {
+        return IsisContext.getPersistenceSession();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/eb613703/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/content/AbstractTextParsableContent.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/content/AbstractTextParsableContent.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/content/AbstractTextParsableContent.java
new file mode 100644
index 0000000..6b60e51
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/content/AbstractTextParsableContent.java
@@ -0,0 +1,82 @@
+/*
+ *  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.view.content;
+
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.consent.Consent;
+import org.apache.isis.core.metamodel.facetapi.FacetHolder;
+import org.apache.isis.core.metamodel.facets.object.title.TitleFacet;
+import org.apache.isis.core.metamodel.spec.feature.ObjectAssociation;
+import org.apache.isis.core.metamodel.spec.feature.ParseableEntryActionParameter;
+import org.apache.isis.viewer.dnd.drawing.Image;
+
+public abstract class AbstractTextParsableContent extends AbstractContent {
+
+    public abstract void clear();
+
+    public abstract void entryComplete();
+
+    @Override
+    public Image getIconPicture(final int iconHeight) {
+        return null;
+    }
+
+    public abstract boolean isEmpty();
+
+    @Override
+    public boolean isPersistable() {
+        return false;
+    }
+
+    @Override
+    public boolean isTransient() {
+        return false;
+    }
+
+    public abstract void parseTextEntry(final String entryText);
+
+    public abstract Consent isEditable();
+
+    @Override
+    public boolean isTextParseable() {
+        return true;
+    }
+
+    /**
+     * @param propertyOrParamValue
+     *            the target property or parameter
+     * @param propertyOrParam
+     *            the {@link ObjectAssociation} or
+     *            {@link ParseableEntryActionParameter}
+     * @param propertyOrParamTypeSpecification
+     *            the specification of the type of the property or parameter
+     *            (for fallback).
+     */
+    protected String titleString(final ObjectAdapter propertyOrParamValue, final FacetHolder propertyOrParam, final FacetHolder propertyOrParamTypeSpecification) {
+
+        final TitleFacet titleFacet = propertyOrParam.getFacet(TitleFacet.class);
+        if (titleFacet != null) {
+            return titleFacet.title(propertyOrParamValue, null);
+        } else {
+            return propertyOrParamValue.titleString();
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/eb613703/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/content/FieldContent.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/content/FieldContent.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/content/FieldContent.java
new file mode 100644
index 0000000..06b4dce
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/content/FieldContent.java
@@ -0,0 +1,38 @@
+/*
+ *  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.view.content;
+
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.consent.Consent;
+import org.apache.isis.core.metamodel.spec.feature.ObjectAssociation;
+import org.apache.isis.viewer.dnd.view.Content;
+
+public interface FieldContent extends Content {
+
+    String getFieldName();
+
+    ObjectAssociation getField();
+
+    boolean isMandatory();
+
+    Consent isEditable();
+
+    ObjectAdapter getParent();
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/eb613703/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/content/NullContent.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/content/NullContent.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/content/NullContent.java
new file mode 100644
index 0000000..24d6fa1
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/content/NullContent.java
@@ -0,0 +1,147 @@
+/*
+ *  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.view.content;
+
+import org.apache.isis.core.commons.debug.DebugBuilder;
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.consent.Consent;
+import org.apache.isis.core.metamodel.spec.ObjectSpecification;
+import org.apache.isis.viewer.dnd.drawing.Image;
+import org.apache.isis.viewer.dnd.view.Content;
+import org.apache.isis.viewer.dnd.view.UserActionSet;
+
+public class NullContent implements Content {
+
+    private final String title;
+
+    public NullContent() {
+        this("");
+    }
+
+    public NullContent(final String title) {
+        this.title = title;
+    }
+
+    @Override
+    public Consent canDrop(final Content sourceContent) {
+        return null;
+    }
+
+    @Override
+    public void contentMenuOptions(final UserActionSet options) {
+    }
+
+    @Override
+    public void debugDetails(final DebugBuilder debug) {
+    }
+
+    @Override
+    public ObjectAdapter drop(final Content sourceContent) {
+        return null;
+    }
+
+    @Override
+    public String getDescription() {
+        return null;
+    }
+
+    @Override
+    public String getHelp() {
+        return null;
+    }
+
+    @Override
+    public String getIconName() {
+        return null;
+    }
+
+    @Override
+    public Image getIconPicture(final int iconHeight) {
+        return null;
+    }
+
+    @Override
+    public String getId() {
+        return null;
+    }
+
+    @Override
+    public ObjectAdapter getAdapter() {
+        return null;
+    }
+
+    @Override
+    public ObjectAdapter[] getOptions() {
+        return null;
+    }
+
+    @Override
+    public ObjectSpecification getSpecification() {
+        return null;
+    }
+
+    @Override
+    public boolean isCollection() {
+        return false;
+    }
+
+    @Override
+    public boolean isObject() {
+        return false;
+    }
+
+    @Override
+    public boolean isOptionEnabled() {
+        return false;
+    }
+
+    @Override
+    public boolean isPersistable() {
+        return false;
+    }
+
+    @Override
+    public boolean isTransient() {
+        return false;
+    }
+
+    @Override
+    public boolean isTextParseable() {
+        return false;
+    }
+
+    public void parseTextEntry(final String entryText) {
+    }
+
+    @Override
+    public String title() {
+        return title;
+    }
+
+    @Override
+    public void viewMenuOptions(final UserActionSet options) {
+    }
+
+    @Override
+    public String windowTitle() {
+        return title;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/eb613703/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/content/RootObject.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/content/RootObject.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/content/RootObject.java
new file mode 100644
index 0000000..0832c5b
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/content/RootObject.java
@@ -0,0 +1,131 @@
+/*
+ *  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.view.content;
+
+import org.apache.isis.core.commons.debug.DebugBuilder;
+import org.apache.isis.core.commons.exceptions.IsisException;
+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.viewer.dnd.view.Content;
+
+public class RootObject extends AbstractObjectContent {
+    private final ObjectAdapter adapter;
+
+    public RootObject(final ObjectAdapter adapter) {
+        this.adapter = adapter;
+    }
+
+    @Override
+    public Consent canClear() {
+        return Veto.DEFAULT;
+    }
+
+    @Override
+    public Consent canDrop(final Content sourceContent) {
+        return super.canDrop(sourceContent);
+    }
+
+    @Override
+    public Consent canSet(final ObjectAdapter dragSource) {
+        return Veto.DEFAULT;
+    }
+
+    @Override
+    public void clear() {
+        throw new IsisException("Invalid call");
+    }
+
+    @Override
+    public void debugDetails(final DebugBuilder debug) {
+        debug.appendln("object", adapter);
+    }
+
+    @Override
+    public ObjectAdapter getAdapter() {
+        return adapter;
+    }
+
+    @Override
+    public String getDescription() {
+        return getSpecification().getSingularName() + ": " + getObject().titleString() + " " + getSpecification().getDescription();
+    }
+
+    @Override
+    public String getHelp() {
+        return "";
+    }
+
+    @Override
+    public String getId() {
+        return "";
+    }
+
+    @Override
+    public ObjectAdapter getObject() {
+        return adapter;
+    }
+
+    @Override
+    public ObjectAdapter[] getOptions() {
+        return null;
+    }
+
+    @Override
+    public ObjectSpecification getSpecification() {
+        return adapter.getSpecification();
+    }
+
+    @Override
+    public boolean isObject() {
+        return true;
+    }
+
+    @Override
+    public boolean isOptionEnabled() {
+        return false;
+    }
+
+    @Override
+    public boolean isTransient() {
+        return adapter != null && adapter.isTransient();
+    }
+
+    @Override
+    public void setObject(final ObjectAdapter object) {
+        throw new IsisException("Invalid call");
+    }
+
+    @Override
+    public String title() {
+        return adapter.titleString();
+    }
+
+    @Override
+    public String toString() {
+        return "Root Object [adapter=" + adapter + "]";
+    }
+
+    @Override
+    public String windowTitle() {
+        return (isTransient() ? "UNSAVED " : "") + getSpecification().getSingularName();
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/eb613703/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/content/TextParseableContent.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/content/TextParseableContent.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/content/TextParseableContent.java
new file mode 100644
index 0000000..ab6d3fc
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/content/TextParseableContent.java
@@ -0,0 +1,49 @@
+/*
+ *  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.view.content;
+
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.consent.Consent;
+import org.apache.isis.viewer.dnd.view.Content;
+
+public interface TextParseableContent extends Content {
+
+    void clear();
+
+    Consent canClear();
+
+    boolean canWrap();
+
+    void entryComplete();
+
+    int getMaximumLength();
+
+    int getNoLines();
+
+    int getTypicalLineLength();
+
+    Consent isEditable();
+
+    boolean isEmpty();
+
+    void parseTextEntry(String entryText);
+
+    String titleString(ObjectAdapter value);
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/eb613703/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/control/AbstractButtonAction.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/control/AbstractButtonAction.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/control/AbstractButtonAction.java
new file mode 100644
index 0000000..a2316d6
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/control/AbstractButtonAction.java
@@ -0,0 +1,70 @@
+/*
+ *  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.view.control;
+
+import org.apache.isis.core.metamodel.consent.Allow;
+import org.apache.isis.core.metamodel.consent.Consent;
+import org.apache.isis.core.metamodel.spec.ActionType;
+import org.apache.isis.viewer.dnd.view.ButtonAction;
+import org.apache.isis.viewer.dnd.view.View;
+
+public abstract class AbstractButtonAction implements ButtonAction {
+    private final String name;
+    private final boolean defaultButton;
+
+    public AbstractButtonAction(final String name) {
+        this(name, false);
+    }
+
+    public AbstractButtonAction(final String name, final boolean defaultButton) {
+        this.name = name;
+        this.defaultButton = defaultButton;
+    }
+
+    @Override
+    public Consent disabled(final View view) {
+        return Allow.DEFAULT;
+    }
+
+    @Override
+    public String getDescription(final View view) {
+        return "";
+    }
+
+    @Override
+    public String getHelp(final View view) {
+        return "No help available for button";
+    }
+
+    @Override
+    public String getName(final View view) {
+        return name;
+    }
+
+    @Override
+    public ActionType getType() {
+        return ActionType.USER;
+    }
+
+    @Override
+    public boolean isDefault() {
+        return defaultButton;
+    }
+}