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:19 UTC

[48/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/table/TableHeader.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/table/TableHeader.java b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/table/TableHeader.java
new file mode 100644
index 0000000..14d66cc
--- /dev/null
+++ b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/table/TableHeader.java
@@ -0,0 +1,241 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.isis.viewer.dnd.table;
+
+import org.apache.isis.core.commons.exceptions.IsisException;
+import org.apache.isis.core.metamodel.spec.feature.ObjectAssociation;
+import org.apache.isis.viewer.dnd.drawing.Bounds;
+import org.apache.isis.viewer.dnd.drawing.Canvas;
+import org.apache.isis.viewer.dnd.drawing.Color;
+import org.apache.isis.viewer.dnd.drawing.ColorsAndFonts;
+import org.apache.isis.viewer.dnd.drawing.Location;
+import org.apache.isis.viewer.dnd.drawing.Shape;
+import org.apache.isis.viewer.dnd.drawing.Size;
+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.InternalDrag;
+import org.apache.isis.viewer.dnd.view.Toolkit;
+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.base.AbstractView;
+import org.apache.isis.viewer.dnd.view.border.ResizeDrag;
+import org.apache.isis.viewer.dnd.view.collection.CollectionContent;
+
+public class TableHeader extends AbstractView {
+    private final TableAxis axis;
+    private final int height;
+    private int resizeColumn;
+
+    public TableHeader(final Content content, final TableAxis axis) {
+        super(content, null);
+        this.axis = axis;
+        height = ViewConstants.VPADDING + Toolkit.getText(ColorsAndFonts.TEXT_LABEL).getTextHeight() + ViewConstants.VPADDING;
+    }
+
+    @Override
+    public void firstClick(final Click click) {
+        if (click.getLocation().getY() <= height) {
+            final int column = axis.getColumnAt(click.getLocation().getX()) - 1;
+            if (column == -2) {
+                super.firstClick(click);
+            } else if (column == -1) {
+                ((CollectionContent) getContent()).setOrderByElement();
+                invalidateContent();
+            } else {
+                final ObjectAssociation field = axis.getFieldForColumn(column);
+                ((CollectionContent) getContent()).setOrderByField(field);
+                invalidateContent();
+            }
+        } else {
+            super.firstClick(click);
+        }
+    }
+
+    @Override
+    public void invalidateContent() {
+        getParent().invalidateContent();
+    }
+
+    @Override
+    public Size getRequiredSize(final Size availableSpace) {
+        return new Size(-1, height);
+    }
+
+    @Override
+    public DragEvent dragStart(final DragStart drag) {
+        if (isOverColumnBorder(drag.getLocation())) {
+            resizeColumn = axis.getColumnBorderAt(drag.getLocation().getX());
+            final Bounds resizeArea = new Bounds(getView().getAbsoluteLocation(), getSize());
+            resizeArea.translate(getView().getPadding().getLeft(), getView().getPadding().getTop());
+            if (resizeColumn == 0) {
+                resizeArea.setWidth(axis.getHeaderOffset());
+            } else {
+                resizeArea.translate(axis.getLeftEdge(resizeColumn - 1), 0);
+                resizeArea.setWidth(axis.getColumnWidth(resizeColumn - 1));
+            }
+
+            final Size minimumSize = new Size(70, 0);
+            return new ResizeDrag(this, resizeArea, ResizeDrag.RIGHT, minimumSize, null);
+        } else if (drag.getLocation().getY() <= height) {
+            return null;
+        } else {
+            return super.dragStart(drag);
+        }
+    }
+
+    @Override
+    public void dragTo(final InternalDrag drag) {
+        if (drag.getOverlay() == null) {
+            throw new IsisException("No overlay for drag: " + drag);
+        }
+        int newWidth = drag.getOverlay().getSize().getWidth();
+        newWidth = Math.max(70, newWidth);
+        getViewManager().getSpy().addAction("Resize column to " + newWidth);
+
+        if (resizeColumn == 0) {
+            axis.setOffset(newWidth);
+        } else {
+            axis.setWidth(resizeColumn - 1, newWidth);
+        }
+        axis.invalidateLayout();
+    }
+
+    @Override
+    public void draw(final Canvas canvas) {
+        super.draw(canvas.createSubcanvas());
+
+        final int y = ViewConstants.VPADDING + Toolkit.getText(ColorsAndFonts.TEXT_LABEL).getAscent();
+
+        int x = axis.getHeaderOffset() - 2;
+
+        if (((CollectionContent) getContent()).getOrderByElement()) {
+            drawOrderIndicator(canvas, axis, x - 10);
+        }
+
+        final Color secondary1 = Toolkit.getColor(ColorsAndFonts.COLOR_SECONDARY1);
+        canvas.drawLine(0, 0, getSize().getWidth() - 1, 0, secondary1);
+        canvas.drawLine(0, height - 1, getSize().getWidth() - 1, height - 1, secondary1);
+        canvas.drawLine(x, 0, x, getSize().getHeight() - 1, secondary1);
+        x++;
+        final int columns = axis.getColumnCount();
+        final ObjectAssociation fieldSortOrder = ((CollectionContent) getContent()).getFieldSortOrder();
+        for (int i = 0; i < columns; i++) {
+            if (fieldSortOrder == axis.getFieldForColumn(i)) {
+                drawOrderIndicator(canvas, axis, x + axis.getColumnWidth(i) - 10);
+            }
+
+            canvas.drawLine(0, 0, 0, getSize().getHeight() - 1, secondary1);
+            canvas.drawLine(x, 0, x, getSize().getHeight() - 1, secondary1);
+            final Canvas headerCanvas = canvas.createSubcanvas(x, 0, axis.getColumnWidth(i) - 1, height);
+            headerCanvas.drawText(axis.getColumnName(i), ViewConstants.HPADDING, y, secondary1, Toolkit.getText(ColorsAndFonts.TEXT_LABEL));
+            x += axis.getColumnWidth(i);
+        }
+        // Color secondary2 = Toolkit.getColor(ColorsAndFonts.COLOR_SECONDARY2);
+        // canvas.drawLine(x, 0, x, getSize().getHeight() - 1, secondary2);
+        // canvas.drawRectangle(0, height, getSize().getWidth() - 1,
+        // getSize().getHeight() - height - 1, secondary2);
+    }
+
+    private void drawOrderIndicator(final Canvas canvas, final TableAxis axis, final int x) {
+        Shape arrow;
+        arrow = new Shape();
+        if (((CollectionContent) getContent()).getReverseSortOrder()) {
+            arrow.addPoint(0, 7);
+            arrow.addPoint(3, 0);
+            arrow.addPoint(6, 7);
+        } else {
+            arrow.addPoint(0, 0);
+            arrow.addPoint(6, 0);
+            arrow.addPoint(3, 7);
+        }
+        // canvas.drawRectangle(x + axis.getColumnWidth(i) - 10, 3, 7, 8,
+        // Toolkit.getColor("secondary3"));
+        canvas.drawShape(arrow, x, 3, Toolkit.getColor(ColorsAndFonts.COLOR_SECONDARY2));
+    }
+
+    @Override
+    public View identify(final Location location) {
+        getViewManager().getSpy().addTrace("Identify over column " + location);
+        if (isOverColumnBorder(location)) {
+            getViewManager().getSpy().addAction("Identified over column ");
+            return getView();
+        }
+        return super.identify(location);
+    }
+
+    private boolean isOverColumnBorder(final Location at) {
+        final int x = at.getX();
+        return axis.getColumnBorderAt(x) >= 0;
+    }
+
+    @Override
+    public void mouseMoved(final Location at) {
+        if (isOverColumnBorder(at)) {
+            getFeedbackManager().showResizeRightCursor();
+        } else {
+            super.mouseMoved(at);
+            getFeedbackManager().showDefaultCursor();
+        }
+    }
+
+    @Override
+    public void secondClick(final Click click) {
+        if (isOverColumnBorder(click.getLocation())) {
+            final int column = axis.getColumnBorderAt(click.getLocation().getX()) - 1;
+            if (column == -1) {
+                final View[] subviews = getSubviews();
+                for (final View row : subviews) {
+                    axis.ensureOffset(((TableRowBorder) row).requiredTitleWidth());
+                }
+
+            } else {
+                final View[] subviews = getSubviews();
+                int max = 0;
+                for (final View row : subviews) {
+                    final View cell = row.getSubviews()[column];
+                    max = Math.max(max, cell.getRequiredSize(new Size()).getWidth());
+                }
+                axis.setWidth(column, max);
+            }
+            axis.invalidateLayout();
+        } else {
+            super.secondClick(click);
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "TableHeader";
+    }
+
+    @Override
+    public ViewAreaType viewAreaType(final Location at) {
+        final int x = at.getX();
+
+        if (axis.getColumnBorderAt(x) >= 0) {
+            return ViewAreaType.INTERNAL;
+        } else {
+            return super.viewAreaType(at);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/255ef514/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/table/TableRowBorder.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/table/TableRowBorder.java b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/table/TableRowBorder.java
new file mode 100644
index 0000000..05810c1
--- /dev/null
+++ b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/table/TableRowBorder.java
@@ -0,0 +1,205 @@
+/*
+ *  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.table;
+
+import org.apache.isis.core.commons.debug.DebugBuilder;
+import org.apache.isis.viewer.dnd.drawing.Canvas;
+import org.apache.isis.viewer.dnd.drawing.Color;
+import org.apache.isis.viewer.dnd.drawing.ColorsAndFonts;
+import org.apache.isis.viewer.dnd.drawing.Location;
+import org.apache.isis.viewer.dnd.drawing.Offset;
+import org.apache.isis.viewer.dnd.drawing.Size;
+import org.apache.isis.viewer.dnd.drawing.Text;
+import org.apache.isis.viewer.dnd.interaction.ViewDragImpl;
+import org.apache.isis.viewer.dnd.view.Axes;
+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.Placement;
+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.ViewAreaType;
+import org.apache.isis.viewer.dnd.view.ViewAxis;
+import org.apache.isis.viewer.dnd.view.ViewConstants;
+import org.apache.isis.viewer.dnd.view.base.AbstractBorder;
+import org.apache.isis.viewer.dnd.view.base.DragViewOutline;
+import org.apache.isis.viewer.dnd.view.base.IconGraphic;
+import org.apache.isis.viewer.dnd.view.collection.CollectionContent;
+import org.apache.isis.viewer.dnd.view.text.ObjectTitleText;
+import org.apache.isis.viewer.dnd.view.text.TitleText;
+
+// REVIEW can we use ObjectBorder to provide the basic functionality
+public class TableRowBorder extends AbstractBorder {
+    public static class Factory implements SubviewDecorator {
+        @Override
+        public ViewAxis createAxis(final Content content) {
+            final TableAxis axis = new TableAxisImpl((CollectionContent) content);
+            return axis;
+        }
+
+        @Override
+        public View decorate(final Axes axes, final View view) {
+            return new TableRowBorder(axes, view);
+        }
+    }
+
+    private static final int BORDER = 13;
+
+    private final int baseline;
+    private final IconGraphic icon;
+    private final TitleText title;
+
+    private final TableAxis axis;
+
+    public TableRowBorder(final Axes axes, final View wrappedRow) {
+        super(wrappedRow);
+
+        final Text text = Toolkit.getText(ColorsAndFonts.TEXT_NORMAL);
+        icon = new IconGraphic(this, text);
+        title = new ObjectTitleText(this, text);
+        baseline = icon.getBaseline();
+
+        left = requiredTitleWidth() + BORDER;
+
+        axis = axes.getAxis(TableAxis.class);
+        axis.ensureOffset(left);
+    }
+
+    @Override
+    public void debugDetails(final DebugBuilder debug) {
+        debug.appendln("RowBorder " + left + " pixels");
+        debug.appendln("Axis", axis);
+    }
+
+    @Override
+    public DragEvent dragStart(final DragStart drag) {
+        final int x = drag.getLocation().getX();
+        final int left = axis.getHeaderOffset();
+        ;
+        if (x < left - BORDER) {
+            return Toolkit.getViewFactory().createDragContentOutline(this, drag.getLocation());
+        } else if (x < left) {
+            final View dragOverlay = new DragViewOutline(getView());
+            return new ViewDragImpl(this, new Offset(drag.getLocation()), dragOverlay);
+        } else {
+            return super.dragStart(drag);
+        }
+    }
+
+    @Override
+    public void draw(final Canvas canvas) {
+        final int baseline = getBaseline();
+
+        final int width = axis.getHeaderOffset();
+        final Size s = getSize();
+        final Canvas subcanvas = canvas.createSubcanvas(0, 0, width, s.getHeight());
+        int offset = ViewConstants.HPADDING;
+        icon.draw(subcanvas, offset, baseline);
+        offset += icon.getSize().getWidth() + ViewConstants.HPADDING + 0 + ViewConstants.HPADDING;
+        title.draw(subcanvas, offset, baseline, getLeft() - offset);
+
+        final int columns = axis.getColumnCount();
+        int x = -1;
+        x += axis.getHeaderOffset();
+        final Color secondary1 = Toolkit.getColor(ColorsAndFonts.COLOR_SECONDARY1);
+        canvas.drawLine(x - 1, 0, x - 1, s.getHeight() - 1, secondary1);
+        canvas.drawLine(x, 0, x, s.getHeight() - 1, secondary1);
+        for (int i = 0; i < columns; i++) {
+            x += axis.getColumnWidth(i);
+            canvas.drawLine(x, 0, x, s.getHeight() - 1, secondary1);
+        }
+        canvas.drawLine(0, 0, 0, s.getHeight() - 1, secondary1);
+
+        final int y = s.getHeight() - 1;
+        final Color secondary2 = Toolkit.getColor(ColorsAndFonts.COLOR_SECONDARY2);
+        canvas.drawLine(0, y, s.getWidth(), y, secondary2);
+
+        if (getState().isObjectIdentified()) {
+            final int xExtent = width - 1;
+            canvas.drawLine(xExtent - BORDER, top, xExtent - BORDER, top + s.getHeight() - 1, secondary2);
+            canvas.drawSolidRectangle(xExtent - BORDER + 1, top, BORDER - 2, s.getHeight() - 2 * top - 1, Toolkit.getColor(ColorsAndFonts.COLOR_SECONDARY3));
+        }
+
+        // components
+        super.draw(canvas);
+    }
+
+    @Override
+    public int getBaseline() {
+        return baseline;
+    }
+
+    @Override
+    protected int getLeft() {
+        return axis.getHeaderOffset();
+    }
+
+    protected int requiredTitleWidth() {
+        return ViewConstants.HPADDING + icon.getSize().getWidth() + ViewConstants.HPADDING + title.getSize().getWidth() + ViewConstants.HPADDING;
+    }
+
+    @Override
+    public void entered() {
+        getState().setContentIdentified();
+        getState().setViewIdentified();
+        wrappedView.entered();
+        markDamaged();
+    }
+
+    @Override
+    public void exited() {
+        getState().clearObjectIdentified();
+        getState().clearViewIdentified();
+        wrappedView.exited();
+        markDamaged();
+    }
+
+    @Override
+    public void secondClick(final Click click) {
+        final int left = axis.getHeaderOffset();
+        ;
+        final int x = click.getLocation().getX();
+        if (x <= left) {
+            final Location location = getAbsoluteLocation();
+            location.translate(click.getLocation());
+            getWorkspace().objectActionResult(getContent().getAdapter(), new Placement(this));
+        } else {
+            super.secondClick(click);
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "RowBorder/" + wrappedView;
+    }
+
+    @Override
+    public ViewAreaType viewAreaType(final Location mouseLocation) {
+        if (mouseLocation.getX() <= left) {
+            return ViewAreaType.CONTENT;
+        } else if (mouseLocation.getX() >= getSize().getWidth() - right) {
+            return ViewAreaType.VIEW;
+        } else {
+            return super.viewAreaType(mouseLocation);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/255ef514/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/table/TableRowLayout.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/table/TableRowLayout.java b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/table/TableRowLayout.java
new file mode 100644
index 0000000..9c4d26a
--- /dev/null
+++ b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/table/TableRowLayout.java
@@ -0,0 +1,93 @@
+/*
+ *  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.table;
+
+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;
+
+public class TableRowLayout implements Layout {
+    private final TableAxis axis;
+
+    public TableRowLayout(final TableAxis axis) {
+        this.axis = axis;
+    }
+
+    @Override
+    public Size getRequiredSize(final View row) {
+        int maxHeight = 0;
+        int totalWidth = 0;
+        final View[] cells = row.getSubviews();
+        final int maxBaseline = maxBaseline(cells);
+
+        for (int i = 0; i < cells.length; i++) {
+            totalWidth += axis.getColumnWidth(i);
+
+            final Size s = cells[i].getRequiredSize(Size.createMax());// TODO
+                                                                      // Need to
+                                                                      // pass in
+                                                                      // a max
+                                                                      // size
+                                                                      // (is 0
+                                                                      // at the
+                                                                      // moment)
+            final int b = cells[i].getBaseline();
+            final int baselineOffset = Math.max(0, maxBaseline - b);
+            maxHeight = Math.max(maxHeight, s.getHeight() + baselineOffset);
+        }
+
+        return new Size(totalWidth, maxHeight);
+    }
+
+    @Override
+    public void layout(final View row, final Size maximumSize) {
+        final View[] cells = row.getSubviews();
+        final int maxBaseline = maxBaseline(cells);
+
+        int x = 0;
+        for (int i = 0; i < cells.length; i++) {
+            final View cell = cells[i];
+            final Size s = cell.getRequiredSize(Size.createMax()); // TODO Need
+                                                                   // to pass in
+                                                                   // a max size
+                                                                   // (is 0 at
+                                                                   // the
+                                                                   // moment)
+            s.setWidth(axis.getColumnWidth(i));
+            cell.setSize(s);
+
+            final int b = cell.getBaseline();
+            final int baselineOffset = Math.max(0, maxBaseline - b);
+            cell.setLocation(new Location(x, baselineOffset));
+
+            x += s.getWidth();
+        }
+    }
+
+    private int maxBaseline(final View[] cells) {
+        int maxBaseline = 0;
+        for (final View cell : cells) {
+            maxBaseline = Math.max(maxBaseline, cell.getBaseline());
+        }
+        return maxBaseline;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/255ef514/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/table/TableRowSpecification.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/table/TableRowSpecification.java b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/table/TableRowSpecification.java
new file mode 100644
index 0000000..3e1ea41
--- /dev/null
+++ b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/table/TableRowSpecification.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.table;
+
+import org.apache.isis.viewer.dnd.view.Axes;
+import org.apache.isis.viewer.dnd.view.Content;
+import org.apache.isis.viewer.dnd.view.ViewRequirement;
+import org.apache.isis.viewer.dnd.view.base.Layout;
+import org.apache.isis.viewer.dnd.view.composite.CompositeViewSpecification;
+
+public class TableRowSpecification extends CompositeViewSpecification {
+    public TableRowSpecification() {
+        builder = new TableCellBuilder();
+    }
+
+    @Override
+    public Layout createLayout(final Content content, final Axes axes) {
+        return new TableRowLayout(axes.getAxis(TableAxis.class));
+    }
+
+    @Override
+    public boolean canDisplay(final ViewRequirement requirement) {
+        return requirement.isObject();
+    }
+
+    @Override
+    public String getName() {
+        return "Table Row";
+    }
+
+    @Override
+    public boolean isReplaceable() {
+        return false;
+    }
+
+    @Override
+    public boolean isSubView() {
+        return true;
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/255ef514/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/table/TypeBasedColumnWidthStrategy.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/table/TypeBasedColumnWidthStrategy.java b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/table/TypeBasedColumnWidthStrategy.java
new file mode 100644
index 0000000..29cb6a5
--- /dev/null
+++ b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/table/TypeBasedColumnWidthStrategy.java
@@ -0,0 +1,71 @@
+/*
+ *  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.table;
+
+import java.util.Hashtable;
+
+import org.apache.isis.core.metamodel.spec.ObjectSpecification;
+import org.apache.isis.core.metamodel.spec.feature.ObjectAssociation;
+
+public class TypeBasedColumnWidthStrategy implements ColumnWidthStrategy {
+    private final Hashtable<ObjectSpecification, Integer> types = new Hashtable<ObjectSpecification, Integer>();
+
+    public TypeBasedColumnWidthStrategy() {
+        /*
+         * ObjectSpecificationLoader loader = Isis.getSpecificationLoader();
+         * addWidth(loader.loadSpecification(Logical.class), 25);
+         * addWidth(loader.loadSpecification(Date.class), 65);
+         * addWidth(loader.loadSpecification(Time.class), 38);
+         * addWidth(loader.loadSpecification(DateTime.class), 100);
+         * addWidth(loader.loadSpecification(TextString.class), 80);
+         */
+    }
+
+    public void addWidth(final ObjectSpecification specification, final int width) {
+        types.put(specification, new Integer(width));
+    }
+
+    @Override
+    public int getMaximumWidth(final int i, final ObjectAssociation specification) {
+        return 0;
+    }
+
+    @Override
+    public int getMinimumWidth(final int i, final ObjectAssociation specification) {
+        return 15;
+    }
+
+    // TODO improve the width determination
+    @Override
+    public int getPreferredWidth(final int i, final ObjectAssociation specification) {
+        final ObjectSpecification type = specification.getSpecification();
+        if (type == null) {
+            return 200;
+        }
+        final Integer t = types.get(type);
+        if (t != null) {
+            return t.intValue();
+        } else if (type.isNotCollection()) {
+            return 120;
+        } else {
+            return 100;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/255ef514/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/table/WindowTableSpecification.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/table/WindowTableSpecification.java b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/table/WindowTableSpecification.java
new file mode 100644
index 0000000..94a4d08
--- /dev/null
+++ b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/table/WindowTableSpecification.java
@@ -0,0 +1,77 @@
+/*
+ *  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.table;
+
+import org.apache.isis.viewer.dnd.view.Axes;
+import org.apache.isis.viewer.dnd.view.View;
+import org.apache.isis.viewer.dnd.view.border.ScrollBorder;
+import org.apache.isis.viewer.dnd.view.composite.CompositeViewDecorator;
+import org.apache.isis.viewer.dnd.viewer.basic.TableFocusManager;
+
+public class WindowTableSpecification extends AbstractTableSpecification {
+    public WindowTableSpecification() {
+        addViewDecorator(new CompositeViewDecorator() {
+            @Override
+            public View decorate(final View view, final Axes axes) {
+                final ScrollBorder scrollingView = new ScrollBorder(view);
+                final View viewWithWindowBorder = scrollingView;
+                // note - the next call needs to be after the creation of the
+                // window border so
+                // that it exists when the header is set up
+                scrollingView.setTopHeader(new TableHeader(view.getContent(), axes.getAxis(TableAxis.class)));
+                viewWithWindowBorder.setFocusManager(new TableFocusManager(viewWithWindowBorder));
+                return viewWithWindowBorder;
+            }
+        });
+
+    }
+
+    /*
+     * @Override public View doCreateView(final View view, final Content
+     * content, final ViewAxis axis) { if (true) return view;
+     * 
+     * final ScrollBorder scrollingView = new ScrollBorder(view); View
+     * viewWithWindowBorder = scrollingView; // note - the next call needs to be
+     * after the creation of the window border so // that it exists when the
+     * header is set up scrollingView.setTopHeader(new TableHeader(content,
+     * view.getViewAxisForChildren())); viewWithWindowBorder.setFocusManager(new
+     * TableFocusManager(viewWithWindowBorder)); return viewWithWindowBorder; }
+     * 
+     * protected View decorateView(View view) { super.decorateView(view);
+     * 
+     * final ScrollBorder scrollingView = new ScrollBorder(view); View
+     * viewWithWindowBorder = scrollingView; // note - the next call needs to be
+     * after the creation of the window border so // that it exists when the
+     * header is set up scrollingView.setTopHeader(new
+     * TableHeader(view.getContent(), view.getViewAxisForChildren()));
+     * viewWithWindowBorder.setFocusManager(new
+     * TableFocusManager(viewWithWindowBorder)); return viewWithWindowBorder; }
+     */
+    @Override
+    public String getName() {
+        return "Table";
+    }
+
+    @Override
+    public boolean isReplaceable() {
+        return false;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/255ef514/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/toolbar/ToolbarView.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/toolbar/ToolbarView.java b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/toolbar/ToolbarView.java
new file mode 100644
index 0000000..ad1b393
--- /dev/null
+++ b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/toolbar/ToolbarView.java
@@ -0,0 +1,79 @@
+/*
+ *  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.toolbar;
+
+import org.apache.isis.viewer.dnd.drawing.Location;
+import org.apache.isis.viewer.dnd.drawing.Size;
+import org.apache.isis.viewer.dnd.view.Content;
+import org.apache.isis.viewer.dnd.view.View;
+import org.apache.isis.viewer.dnd.view.ViewConstants;
+import org.apache.isis.viewer.dnd.view.ViewSpecification;
+import org.apache.isis.viewer.dnd.view.composite.CompositeView;
+
+public class ToolbarView extends CompositeView {
+
+    public ToolbarView(final Content content, final ViewSpecification specification) {
+        super(content, specification);
+    }
+
+    @Override
+    protected void buildView() {
+    }
+
+    @Override
+    protected void doLayout(final Size maximumSize) {
+        int x = ViewConstants.HPADDING;
+        int y = 0;
+        int lineHeight = 0;
+        for (final View button : getSubviews()) {
+            final Size buttonSize = button.getRequiredSize(Size.createMax());
+            if (x + buttonSize.getWidth() >= maximumSize.getWidth()) {
+                x = ViewConstants.HPADDING;
+                y += lineHeight + ViewConstants.VPADDING;
+                lineHeight = 0;
+            }
+            button.setSize(buttonSize);
+            button.setLocation(new Location(x, y));
+            x += buttonSize.getWidth() + ViewConstants.HPADDING;
+            lineHeight = Math.max(lineHeight, buttonSize.getHeight());
+        }
+    }
+
+    @Override
+    public Size requiredSize(final Size availableSpace) {
+        int lineHeight = 0;
+        int lineWidth = ViewConstants.HPADDING;
+        final Size requiredSize = new Size();
+        for (final View button : getSubviews()) {
+            final Size buttonSize = button.getRequiredSize(availableSpace);
+            lineWidth += buttonSize.getWidth() + ViewConstants.HPADDING;
+            if (lineWidth >= availableSpace.getWidth()) {
+                lineWidth = ViewConstants.HPADDING;
+                requiredSize.extendHeight(lineHeight + ViewConstants.VPADDING);
+                lineHeight = 0;
+            }
+            lineHeight = Math.max(lineHeight, buttonSize.getHeight());
+            requiredSize.ensureWidth(lineWidth);
+        }
+        requiredSize.extendHeight(lineHeight + ViewConstants.VPADDING);
+        return requiredSize;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/255ef514/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/tree/ClosedCollectionNodeSpecification.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/tree/ClosedCollectionNodeSpecification.java b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/tree/ClosedCollectionNodeSpecification.java
new file mode 100644
index 0000000..152ea53
--- /dev/null
+++ b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/tree/ClosedCollectionNodeSpecification.java
@@ -0,0 +1,65 @@
+/*
+ *  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.tree;
+
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.facets.collections.modify.CollectionFacet;
+import org.apache.isis.core.metamodel.facets.collections.modify.CollectionFacetUtils;
+import org.apache.isis.viewer.dnd.view.Axes;
+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.collection.CollectionContent;
+
+/**
+ * Specification for a tree node that will display a closed collection as a root
+ * node or within an object.
+ * 
+ * @see org.apache.isis.viewer.dnd.tree.OpenCollectionNodeSpecification for
+ *      displaying an open collection within an object.
+ */
+public class ClosedCollectionNodeSpecification extends NodeSpecification {
+    @Override
+    public boolean canDisplay(final ViewRequirement requirement) {
+        return requirement.isCollection() && requirement.hasReference();
+    }
+
+    @Override
+    public int canOpen(final Content content) {
+        final ObjectAdapter collection = ((CollectionContent) content).getCollection();
+        if (collection.isGhost()) {
+            return UNKNOWN;
+        } else {
+            final CollectionFacet facet = CollectionFacetUtils.getCollectionFacetFromSpec(collection);
+            return facet.size(collection) > 0 ? CAN_OPEN : CANT_OPEN;
+        }
+    }
+
+    @Override
+    protected View createNodeView(final Content content, final Axes axes) {
+        final View treeLeafNode = new LeafNodeView(content, this);
+        return treeLeafNode;
+    }
+
+    @Override
+    public String getName() {
+        return "Collection tree node - closed";
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/255ef514/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/tree/ClosedObjectNodeSpecification.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/tree/ClosedObjectNodeSpecification.java b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/tree/ClosedObjectNodeSpecification.java
new file mode 100644
index 0000000..57f5114
--- /dev/null
+++ b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/tree/ClosedObjectNodeSpecification.java
@@ -0,0 +1,94 @@
+/*
+ *  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.tree;
+
+import java.util.List;
+
+import org.apache.isis.applib.annotation.Where;
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.facets.object.bounded.BoundedFacetUtils;
+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.View;
+import org.apache.isis.viewer.dnd.view.ViewRequirement;
+import org.apache.isis.viewer.dnd.view.border.SelectObjectBorder;
+
+/**
+ * Specification for a tree node that will display a closed object as a root
+ * node or within an object. This will indicate that the created view can be
+ * opened if: one of it fields is a collection; it is set up to show objects
+ * within objects and one of the fields is an object but it is not a lookup.
+ * 
+ * @see org.apache.isis.viewer.dnd.tree.OpenObjectNodeSpecification for
+ *      displaying an open collection as part of an object.
+ */
+class ClosedObjectNodeSpecification extends NodeSpecification {
+    private final boolean showObjectContents;
+    private final SubviewDecorator decorator = new SelectObjectBorder.Factory();
+
+    // 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
+    private final Where where = Where.ANYWHERE;
+
+    public ClosedObjectNodeSpecification(final boolean showObjectContents) {
+        this.showObjectContents = showObjectContents;
+    }
+
+    @Override
+    public boolean canDisplay(final ViewRequirement requirement) {
+        return requirement.isObject() && requirement.hasReference();
+    }
+
+    @Override
+    public int canOpen(final Content content) {
+        final ObjectAdapter object = ((ObjectContent) content).getObject();
+        final List<ObjectAssociation> fields = object.getSpecification().getAssociations(ObjectAssociationFilters.dynamicallyVisible(IsisContext.getAuthenticationSession(), object, where));
+        for (int i = 0; i < fields.size(); i++) {
+            if (fields.get(i).isOneToManyAssociation()) {
+                return CAN_OPEN;
+            }
+
+            if (showObjectContents && fields.get(i).isOneToOneAssociation() && !(BoundedFacetUtils.isBoundedSet(object.getSpecification()))) {
+                return CAN_OPEN;
+            }
+        }
+        return CANT_OPEN;
+    }
+
+    @Override
+    protected View createNodeView(final Content content, final Axes axes) {
+        View treeLeafNode = new LeafNodeView(content, this);
+        treeLeafNode = decorator.decorate(axes, treeLeafNode);
+        return treeLeafNode;
+    }
+
+    @Override
+    public String getName() {
+        return "Object tree node - closed";
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/255ef514/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/tree/CompositeNodeSpecification.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/tree/CompositeNodeSpecification.java b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/tree/CompositeNodeSpecification.java
new file mode 100644
index 0000000..d824cbf
--- /dev/null
+++ b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/tree/CompositeNodeSpecification.java
@@ -0,0 +1,66 @@
+/*
+ *  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.tree;
+
+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.base.Layout;
+import org.apache.isis.viewer.dnd.view.composite.CompositeViewUsingBuilder;
+import org.apache.isis.viewer.dnd.view.composite.ViewBuilder;
+
+public abstract class CompositeNodeSpecification extends NodeSpecification implements CompositeViewSpecification {
+    protected ViewBuilder builder;
+    private NodeSpecification collectionLeafNodeSpecification;
+    private NodeSpecification objectLeafNodeSpecification;
+
+    public void setCollectionSubNodeSpecification(final NodeSpecification collectionLeafNodeSpecification) {
+        this.collectionLeafNodeSpecification = collectionLeafNodeSpecification;
+    }
+
+    public void setObjectSubNodeSpecification(final NodeSpecification objectLeafNodeSpecification) {
+        this.objectLeafNodeSpecification = objectLeafNodeSpecification;
+    }
+
+    public void createAxes(final Content content, final Axes axes) {
+    }
+
+    @Override
+    protected View createNodeView(final Content content, final Axes axes) {
+        final CompositeViewUsingBuilder view = new CompositeViewUsingBuilder(content, this, axes, createLayout(content, axes), builder);
+        return view;
+    }
+
+    protected abstract Layout createLayout(Content content, Axes axes);
+
+    /*
+     * public View createView(final Content content, Axes axes, int fieldNumber)
+     * { ViewRequirement requirement = new ViewRequirement(content,
+     * ViewRequirement.CLOSED); if
+     * (collectionLeafNodeSpecification.canDisplay(content, requirement )) {
+     * return collectionLeafNodeSpecification.createView(content, axes, -1); }
+     * 
+     * if (objectLeafNodeSpecification.canDisplay(content, requirement)) {
+     * return objectLeafNodeSpecification.createView(content, axes, -1); }
+     * 
+     * return null; }
+     */
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/255ef514/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/tree/EmptyNodeSpecification.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/tree/EmptyNodeSpecification.java b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/tree/EmptyNodeSpecification.java
new file mode 100644
index 0000000..98349b5
--- /dev/null
+++ b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/tree/EmptyNodeSpecification.java
@@ -0,0 +1,54 @@
+/*
+ *  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.tree;
+
+import org.apache.isis.viewer.dnd.view.Axes;
+import org.apache.isis.viewer.dnd.view.Content;
+import org.apache.isis.viewer.dnd.view.View;
+import org.apache.isis.viewer.dnd.view.ViewRequirement;
+
+/**
+ * A simple specification that always returns false when asked if it can display
+ * any content.
+ * 
+ * @see #canDisplay(ViewRequirement)
+ */
+public class EmptyNodeSpecification extends NodeSpecification {
+
+    @Override
+    public int canOpen(final Content content) {
+        return CANT_OPEN;
+    }
+
+    @Override
+    protected View createNodeView(final Content content, final Axes axes) {
+        return null;
+    }
+
+    @Override
+    public boolean canDisplay(final ViewRequirement requirement) {
+        return false;
+    }
+
+    @Override
+    public String getName() {
+        return "Empty tree node";
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/255ef514/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/tree/LeafNodeView.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/tree/LeafNodeView.java b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/tree/LeafNodeView.java
new file mode 100644
index 0000000..f461111
--- /dev/null
+++ b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/tree/LeafNodeView.java
@@ -0,0 +1,44 @@
+/*
+ *  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.tree;
+
+import org.apache.isis.viewer.dnd.view.Content;
+import org.apache.isis.viewer.dnd.view.FocusManager;
+import org.apache.isis.viewer.dnd.view.ViewSpecification;
+import org.apache.isis.viewer.dnd.view.base.ObjectView;
+
+class LeafNodeView extends ObjectView {
+
+    private FocusManager focusManager;
+
+    public LeafNodeView(final Content content, final ViewSpecification design) {
+        super(content, design);
+    }
+
+    @Override
+    public FocusManager getFocusManager() {
+        return focusManager;
+    }
+
+    @Override
+    public void setFocusManager(final FocusManager focusManager) {
+        this.focusManager = focusManager;
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/255ef514/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/tree/ListWithDetailSpecification.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/tree/ListWithDetailSpecification.java b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/tree/ListWithDetailSpecification.java
new file mode 100644
index 0000000..2fdb08c
--- /dev/null
+++ b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/tree/ListWithDetailSpecification.java
@@ -0,0 +1,79 @@
+/*
+ *  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.tree;
+
+import org.apache.isis.viewer.dnd.list.SimpleListSpecification;
+import org.apache.isis.viewer.dnd.view.Axes;
+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.ViewSpecification;
+import org.apache.isis.viewer.dnd.view.border.SelectObjectBorder;
+import org.apache.isis.viewer.dnd.view.composite.MasterDetailPanel;
+
+public class ListWithDetailSpecification implements ViewSpecification {
+    private final SimpleListSpecification leftHandSideSpecification;
+
+    public ListWithDetailSpecification() {
+        leftHandSideSpecification = new SimpleListSpecification();
+        leftHandSideSpecification.addSubviewDecorator(new SelectObjectBorder.Factory());
+    }
+
+    @Override
+    public boolean canDisplay(final ViewRequirement requirement) {
+        return requirement.isCollection() && requirement.isOpen();
+    }
+
+    @Override
+    public View createView(final Content content, final Axes axes, final int sequence) {
+        return new MasterDetailPanel(content, this, leftHandSideSpecification);
+    }
+
+    @Override
+    public String getName() {
+        return "List and details";
+    }
+
+    @Override
+    public boolean isAligned() {
+        return false;
+    }
+
+    @Override
+    public boolean isOpen() {
+        return true;
+    }
+
+    @Override
+    public boolean isReplaceable() {
+        return true;
+    }
+
+    @Override
+    public boolean isResizeable() {
+        return false;
+    }
+
+    @Override
+    public boolean isSubView() {
+        return false;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/255ef514/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/tree/NodeSpecification.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/tree/NodeSpecification.java b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/tree/NodeSpecification.java
new file mode 100644
index 0000000..dfe14ee
--- /dev/null
+++ b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/tree/NodeSpecification.java
@@ -0,0 +1,76 @@
+/*
+ *  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.tree;
+
+import org.apache.isis.viewer.dnd.view.Axes;
+import org.apache.isis.viewer.dnd.view.Content;
+import org.apache.isis.viewer.dnd.view.View;
+import org.apache.isis.viewer.dnd.view.ViewSpecification;
+import org.apache.isis.viewer.dnd.viewer.basic.NullFocusManager;
+
+abstract class NodeSpecification implements ViewSpecification {
+    public static final int CAN_OPEN = 1;
+    public static final int CANT_OPEN = 2;
+    public static final int UNKNOWN = 0;
+    private ViewSpecification replacementNodeSpecification;
+
+    public abstract int canOpen(final Content content);
+
+    protected abstract View createNodeView(final Content content, Axes axes);
+
+    @Override
+    public final View createView(final Content content, final Axes axes, final int sequence) {
+        final View view = createNodeView(content, axes);
+        final TreeNodeBorder newView = new TreeNodeBorder(view, replacementNodeSpecification);
+        newView.setFocusManager(new NullFocusManager());
+
+        return newView;
+    }
+
+    @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 true;
+    }
+
+    final void setReplacementNodeSpecification(final ViewSpecification replacementNodeSpecification) {
+        this.replacementNodeSpecification = replacementNodeSpecification;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/255ef514/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/tree/OpenCollectionNodeSpecification.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/tree/OpenCollectionNodeSpecification.java b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/tree/OpenCollectionNodeSpecification.java
new file mode 100644
index 0000000..ddd8af2
--- /dev/null
+++ b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/tree/OpenCollectionNodeSpecification.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.tree;
+
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.facets.collections.modify.CollectionFacet;
+import org.apache.isis.core.metamodel.facets.collections.modify.CollectionFacetUtils;
+import org.apache.isis.viewer.dnd.view.Axes;
+import org.apache.isis.viewer.dnd.view.Content;
+import org.apache.isis.viewer.dnd.view.ViewRequirement;
+import org.apache.isis.viewer.dnd.view.base.Layout;
+import org.apache.isis.viewer.dnd.view.composite.CollectionElementBuilder;
+import org.apache.isis.viewer.dnd.view.composite.StackLayout;
+
+/**
+ * Specification for a tree node that will display an open collection as a root
+ * node or within an object.
+ * 
+ * @see org.apache.isis.viewer.dnd.tree.ClosedCollectionNodeSpecification for
+ *      displaying a closed collection within an object.
+ */
+public class OpenCollectionNodeSpecification extends CompositeNodeSpecification {
+    /**
+     * A collection tree can only be displayed for a collection that has
+     * elements.
+     */
+    @Override
+    public boolean canDisplay(final ViewRequirement requirement) {
+        final ObjectAdapter collection = requirement.getAdapter();
+        if (collection == null) {
+            return false;
+        } else {
+            final CollectionFacet facet = CollectionFacetUtils.getCollectionFacetFromSpec(collection);
+            return facet != null && facet.size(collection) > 0;
+        }
+    }
+
+    public OpenCollectionNodeSpecification() {
+        builder = new CollectionElementBuilder(this);
+    }
+
+    @Override
+    public Layout createLayout(final Content content, final Axes axes) {
+        return new StackLayout();
+    }
+
+    @Override
+    public boolean isOpen() {
+        return true;
+    }
+
+    @Override
+    public boolean isSubView() {
+        return false;
+    }
+
+    @Override
+    public int canOpen(final Content content) {
+        return CAN_OPEN;
+    }
+
+    @Override
+    public String getName() {
+        return "Collection tree node - open";
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/255ef514/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/tree/OpenObjectNodeSpecification.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/tree/OpenObjectNodeSpecification.java b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/tree/OpenObjectNodeSpecification.java
new file mode 100644
index 0000000..a07bae3
--- /dev/null
+++ b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/tree/OpenObjectNodeSpecification.java
@@ -0,0 +1,109 @@
+/*
+ *  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.tree;
+
+import java.util.List;
+
+import org.apache.isis.applib.annotation.Where;
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+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.SubviewDecorator;
+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.border.SelectObjectBorder;
+import org.apache.isis.viewer.dnd.view.composite.ObjectFieldBuilder;
+import org.apache.isis.viewer.dnd.view.composite.StackLayout;
+
+/**
+ * Specification for a tree node that will display an open object as a root node
+ * or within an object.
+ * 
+ * @see org.apache.isis.viewer.dnd.tree.ClosedObjectNodeSpecification for
+ *      displaying a closed collection within an object.
+ */
+public class OpenObjectNodeSpecification extends CompositeNodeSpecification {
+
+    private final SubviewDecorator decorator = new SelectObjectBorder.Factory();
+
+    // 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
+    private final Where where = Where.ANYWHERE;
+
+    public OpenObjectNodeSpecification() {
+        builder = new ObjectFieldBuilder(this);
+    }
+
+    @Override
+    public Layout createLayout(final Content content, final Axes axes) {
+        return new StackLayout();
+    }
+
+    /**
+     * This is only used to control root nodes. Therefore a object tree can only
+     * be displayed for an object with fields that are collections.
+     */
+    @Override
+    public boolean canDisplay(final ViewRequirement requirement) {
+        if (requirement.isObject() && requirement.hasReference()) {
+            final ObjectAdapter object = requirement.getAdapter();
+            final List<ObjectAssociation> fields = object.getSpecification().getAssociations(ObjectAssociationFilters.dynamicallyVisible(IsisContext.getAuthenticationSession(), object, where));
+            for (int i = 0; i < fields.size(); i++) {
+                if (fields.get(i).isOneToManyAssociation()) {
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+
+    @Override
+    protected View createNodeView(final Content content, final Axes axes) {
+        return decorator.decorate(axes, super.createNodeView(content, axes));
+    }
+
+    @Override
+    public int canOpen(final Content content) {
+        return CAN_OPEN;
+    }
+
+    @Override
+    public boolean isOpen() {
+        return true;
+    }
+
+    @Override
+    public boolean isSubView() {
+        return false;
+    }
+
+    @Override
+    public String getName() {
+        return "Object tree node - open";
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/255ef514/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/tree/TreeDisplayRules.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/tree/TreeDisplayRules.java b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/tree/TreeDisplayRules.java
new file mode 100644
index 0000000..4e4479b
--- /dev/null
+++ b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/tree/TreeDisplayRules.java
@@ -0,0 +1,90 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.isis.viewer.dnd.tree;
+
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.consent.Allow;
+import org.apache.isis.core.metamodel.consent.Consent;
+import org.apache.isis.core.metamodel.facets.object.bounded.BoundedFacetUtils;
+import org.apache.isis.core.metamodel.spec.ActionType;
+import org.apache.isis.viewer.dnd.drawing.Location;
+import org.apache.isis.viewer.dnd.view.UserAction;
+import org.apache.isis.viewer.dnd.view.UserActionSet;
+import org.apache.isis.viewer.dnd.view.View;
+import org.apache.isis.viewer.dnd.view.Workspace;
+
+public class TreeDisplayRules {
+    private static boolean showCollectionsOnly = false;
+
+    private TreeDisplayRules() {
+    }
+
+    public static void menuOptions(final UserActionSet options) {
+        // TODO fix and remove following line
+        if (true) {
+            return;
+        }
+
+        final UserAction option = new UserAction() {
+            @Override
+            public void execute(final Workspace workspace, final View view, final Location at) {
+                showCollectionsOnly = !showCollectionsOnly;
+            }
+
+            @Override
+            public String getName(final View view) {
+                return showCollectionsOnly ? "Show collections only" : "Show all references";
+            }
+
+            @Override
+            public Consent disabled(final View view) {
+                return Allow.DEFAULT;
+            }
+
+            @Override
+            public String getDescription(final View view) {
+                return "This option makes the system only show collections within the trees, and not single elements";
+            }
+
+            @Override
+            public ActionType getType() {
+                return ActionType.USER;
+            }
+
+            @Override
+            public String getHelp(final View view) {
+                return "";
+            }
+        };
+        options.add(option);
+    }
+
+    public static boolean isCollectionsOnly() {
+        return showCollectionsOnly;
+    }
+
+    public static boolean canDisplay(final ObjectAdapter object) {
+        final boolean lookupView = object != null && BoundedFacetUtils.isBoundedSet(object.getSpecification());
+        final boolean showNonCollections = !TreeDisplayRules.isCollectionsOnly();
+        final boolean objectView = object instanceof ObjectAdapter && showNonCollections;
+        final boolean collectionView = object.getSpecification().isParentedOrFreeCollection();
+        return (objectView || collectionView) && !lookupView;
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/255ef514/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/tree/TreeNodeBorder.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/tree/TreeNodeBorder.java b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/tree/TreeNodeBorder.java
new file mode 100644
index 0000000..c0bf6cc
--- /dev/null
+++ b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/tree/TreeNodeBorder.java
@@ -0,0 +1,353 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.isis.viewer.dnd.tree;
+
+import org.apache.log4j.Logger;
+
+import org.apache.isis.core.commons.debug.DebugBuilder;
+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.spec.feature.ObjectAssociation;
+import org.apache.isis.core.metamodel.spec.feature.OneToManyAssociation;
+import org.apache.isis.runtimes.dflt.runtime.system.context.IsisContext;
+import org.apache.isis.viewer.dnd.drawing.Bounds;
+import org.apache.isis.viewer.dnd.drawing.Canvas;
+import org.apache.isis.viewer.dnd.drawing.Color;
+import org.apache.isis.viewer.dnd.drawing.ColorsAndFonts;
+import org.apache.isis.viewer.dnd.drawing.Location;
+import org.apache.isis.viewer.dnd.drawing.Offset;
+import org.apache.isis.viewer.dnd.drawing.Size;
+import org.apache.isis.viewer.dnd.drawing.Text;
+import org.apache.isis.viewer.dnd.interaction.ViewDragImpl;
+import org.apache.isis.viewer.dnd.view.Click;
+import org.apache.isis.viewer.dnd.view.DragEvent;
+import org.apache.isis.viewer.dnd.view.DragStart;
+import org.apache.isis.viewer.dnd.view.ObjectContent;
+import org.apache.isis.viewer.dnd.view.Placement;
+import org.apache.isis.viewer.dnd.view.Toolkit;
+import org.apache.isis.viewer.dnd.view.UserActionSet;
+import org.apache.isis.viewer.dnd.view.View;
+import org.apache.isis.viewer.dnd.view.ViewAreaType;
+import org.apache.isis.viewer.dnd.view.ViewConstants;
+import org.apache.isis.viewer.dnd.view.ViewSpecification;
+import org.apache.isis.viewer.dnd.view.Workspace;
+import org.apache.isis.viewer.dnd.view.base.AbstractBorder;
+import org.apache.isis.viewer.dnd.view.base.DragViewOutline;
+import org.apache.isis.viewer.dnd.view.base.IconGraphic;
+import org.apache.isis.viewer.dnd.view.collection.CollectionContent;
+import org.apache.isis.viewer.dnd.view.collection.CollectionElement;
+import org.apache.isis.viewer.dnd.view.content.FieldContent;
+import org.apache.isis.viewer.dnd.view.field.OneToManyField;
+import org.apache.isis.viewer.dnd.view.option.UserActionAbstract;
+import org.apache.isis.viewer.dnd.view.text.ObjectTitleText;
+import org.apache.isis.viewer.dnd.view.text.TitleText;
+
+// TODO use ObjectBorder to provide the basic border functionality
+public class TreeNodeBorder extends AbstractBorder {
+    private static final int BORDER = 13;
+    private static final int BOX_PADDING = 2;
+    private static final int BOX_SIZE = 9;
+    private static final int BOX_X_OFFSET = 5;
+    private final static Text LABEL_STYLE = Toolkit.getText(ColorsAndFonts.TEXT_NORMAL);
+    private static final Logger LOG = Logger.getLogger(TreeNodeBorder.class);
+    private final int baseline;
+    private final IconGraphic icon;
+    private final ViewSpecification replaceWithSpecification;
+    private final TitleText text;
+
+    public TreeNodeBorder(final View wrappedView, final ViewSpecification replaceWith) {
+        super(wrappedView);
+
+        replaceWithSpecification = replaceWith;
+
+        icon = new IconGraphic(this, LABEL_STYLE);
+        text = new ObjectTitleText(this, LABEL_STYLE);
+        final int height = icon.getSize().getHeight();
+
+        baseline = icon.getBaseline() + 1;
+
+        left = 22;
+        right = 0 + BORDER;
+        top = height + 2;
+        bottom = 0;
+    }
+
+    private int canOpen() {
+        return ((NodeSpecification) getSpecification()).canOpen(getContent());
+    }
+
+    @Override
+    protected Bounds contentArea() {
+        return new Bounds(getLeft(), getTop(), wrappedView.getSize().getWidth(), wrappedView.getSize().getHeight());
+    }
+
+    @Override
+    public void debugDetails(final DebugBuilder debug) {
+        debug.append("TreeNodeBorder " + left + " pixels\n");
+        debug.append("           titlebar " + (top) + " pixels\n");
+        debug.append("           replace with  " + replaceWithSpecification);
+        debug.append("           text " + text);
+        debug.append("           icon " + icon);
+        super.debugDetails(debug);
+
+    }
+
+    @Override
+    public DragEvent dragStart(final DragStart drag) {
+        if (drag.getLocation().getX() > getSize().getWidth() - right) {
+            final View dragOverlay = new DragViewOutline(getView());
+            return new ViewDragImpl(this, new Offset(drag.getLocation()), dragOverlay);
+        } else if (overBorder(drag.getLocation())) {
+            return Toolkit.getViewFactory().createDragContentOutline(this, drag.getLocation());
+        } else {
+            return super.dragStart(drag);
+        }
+    }
+
+    @Override
+    public void draw(final Canvas canvas) {
+        final Color secondary1 = Toolkit.getColor(ColorsAndFonts.COLOR_SECONDARY1);
+        final Color secondary2 = Toolkit.getColor(ColorsAndFonts.COLOR_SECONDARY2);
+        final Color secondary3 = Toolkit.getColor(ColorsAndFonts.COLOR_SECONDARY3);
+        /*
+         * REVIEW if (getViewAxis(TableAxis.class) != null) { if
+         * (((SelectableViewAxis)
+         * getViewAxis(SelectableViewAxis.class)).isSelected(getView())) {
+         * canvas.drawSolidRectangle(left, 0, getSize().getWidth() - left, top,
+         * Toolkit.getColor(ColorsAndFonts.COLOR_PRIMARY2)); secondary2 =
+         * secondary1; } }
+         */
+        if (getState().isObjectIdentified()) {
+            canvas.drawRectangle(left, 0, getSize().getWidth() - left, top, secondary2);
+
+            final int xExtent = getSize().getWidth();
+            canvas.drawSolidRectangle(xExtent - BORDER + 1, 1, BORDER - 2, top - 2, secondary3);
+            canvas.drawLine(xExtent - BORDER, 0, xExtent - BORDER, top - 2, secondary2);
+        }
+
+        // lines
+        int x = 0;
+        final int y = top / 2;
+        canvas.drawLine(x, y, x + left, y, secondary2);
+
+        final boolean isOpen = getSpecification().isOpen();
+        final int canOpen = canOpen();
+        final boolean addBox = isOpen || canOpen != NodeSpecification.CANT_OPEN;
+        if (addBox) {
+            x += BOX_X_OFFSET;
+            canvas.drawLine(x, y, x + BOX_SIZE - 1, y, secondary3);
+            canvas.drawSolidRectangle(x, y - BOX_SIZE / 2, BOX_SIZE, BOX_SIZE, Toolkit.getColor(ColorsAndFonts.COLOR_WHITE));
+            canvas.drawRectangle(x, y - BOX_SIZE / 2, BOX_SIZE, BOX_SIZE, secondary1);
+
+            if (canOpen == NodeSpecification.UNKNOWN) {
+
+            } else {
+                final Color black = Toolkit.getColor(ColorsAndFonts.COLOR_BLACK);
+                canvas.drawLine(x + BOX_PADDING, y, x + BOX_SIZE - 1 - BOX_PADDING, y, black);
+                if (!isOpen) {
+                    x += BOX_SIZE / 2;
+                    canvas.drawLine(x, y - BOX_SIZE / 2 + BOX_PADDING, x, y + BOX_SIZE / 2 - BOX_PADDING, black);
+                }
+            }
+        }
+
+        final View[] nodes = getSubviews();
+        if (nodes.length > 0) {
+            final int y1 = top / 2;
+            final View node = nodes[nodes.length - 1];
+            final int y2 = top + node.getLocation().getY() + top / 2;
+            canvas.drawLine(left - 1, y1, left - 1, y2, secondary2);
+        }
+
+        // icon & title
+        x = left + 1;
+        icon.draw(canvas, x, baseline);
+        x += icon.getSize().getWidth();
+        final int maxWith = getSize().getWidth() - x;
+        text.draw(canvas, x, baseline, maxWith);
+
+        if (Toolkit.debug) {
+            canvas.drawRectangleAround(getBounds(), Toolkit.getColor(ColorsAndFonts.COLOR_DEBUG_BASELINE));
+        }
+
+        // draw components
+        super.draw(canvas);
+    }
+
+    @Override
+    public void entered() {
+        getState().setContentIdentified();
+        getState().setViewIdentified();
+        wrappedView.entered();
+        markDamaged();
+    }
+
+    @Override
+    public void exited() {
+        getState().clearObjectIdentified();
+        getState().clearViewIdentified();
+        wrappedView.exited();
+        markDamaged();
+    }
+
+    @Override
+    public void firstClick(final Click click) {
+        final int x = click.getLocation().getX();
+        final int y = click.getLocation().getY();
+
+        if (withinBox(x, y)) {
+            if (canOpen() == NodeSpecification.UNKNOWN) {
+                resolveContent();
+                markDamaged();
+            }
+            LOG.debug((getSpecification().isOpen() ? "close" : "open") + " node " + getContent().getAdapter());
+            if (canOpen() == NodeSpecification.CAN_OPEN) {
+                final View newView = replaceWithSpecification.createView(getContent(), getViewAxes(), -1);
+                getParent().replaceView(getView(), newView);
+            }
+            /*
+             * } else if (y < top && x > left && click.button1()) { if
+             * (canOpen() == NodeSpecification.UNKNOWN) { resolveContent();
+             * markDamaged(); } selectNode();
+             */
+        } else {
+            super.firstClick(click);
+        }
+    }
+
+    @Override
+    public int getBaseline() {
+        return wrappedView.getBaseline() + baseline;
+    }
+
+    @Override
+    public Size getRequiredSize(final Size maximumSize) {
+        final Size size = super.getRequiredSize(maximumSize);
+        // size.extendHeight(2 * VPADDING);
+        size.ensureWidth(left + ViewConstants.HPADDING + icon.getSize().getWidth() + text.getSize().getWidth() + ViewConstants.HPADDING + right);
+        return size;
+    }
+
+    @Override
+    public void objectActionResult(final ObjectAdapter result, final Placement placement) {
+        if (getContent() instanceof OneToManyField && result instanceof ObjectAdapter) {
+            // same as InternalCollectionBorder
+            final OneToManyField internalCollectionContent = (OneToManyField) getContent();
+            final OneToManyAssociation field = internalCollectionContent.getOneToManyAssociation();
+            final ObjectAdapter target = ((ObjectContent) getParent().getContent()).getObject();
+
+            final Consent about = field.isValidToAdd(target, result);
+            if (about.isAllowed()) {
+                field.addElement(target, result);
+            }
+        }
+        super.objectActionResult(result, placement);
+    }
+
+    private void resolveContent() {
+        ObjectAdapter parent = getParent().getContent().getAdapter();
+        if (!(parent instanceof ObjectAdapter)) {
+            parent = getParent().getParent().getContent().getAdapter();
+        }
+
+        if (getContent() instanceof FieldContent) {
+            final ObjectAssociation field = ((FieldContent) getContent()).getField();
+            IsisContext.getPersistenceSession().resolveField(parent, field);
+        } else if (getContent() instanceof CollectionContent) {
+            IsisContext.getPersistenceSession().resolveImmediately(parent);
+        } else if (getContent() instanceof CollectionElement) {
+            IsisContext.getPersistenceSession().resolveImmediately(getContent().getAdapter());
+        }
+    }
+
+    @Override
+    public void secondClick(final Click click) {
+        final int x = click.getLocation().getX();
+        final int y = click.getLocation().getY();
+        if (y < top && x > left) {
+            if (canOpen() == NodeSpecification.UNKNOWN) {
+                resolveContent();
+                markDamaged();
+            }
+            final Location location = getAbsoluteLocation();
+            location.translate(click.getLocation());
+            getWorkspace().addWindowFor(getContent().getAdapter(), new Placement(this));
+        } else {
+            super.secondClick(click);
+        }
+    }
+
+    // TODO remove
+    private void selectNode() {
+        /*
+         * if (getViewAxis(SelectableViewAxis.class) != null) {
+         * ((SelectableViewAxis)
+         * getViewAxis(SelectableViewAxis.class)).selected(getView()); }
+         */
+    }
+
+    @Override
+    public String toString() {
+        return wrappedView.toString() + "/TreeNodeBorder";
+    }
+
+    @Override
+    public ViewAreaType viewAreaType(final Location mouseLocation) {
+        final Bounds bounds = new Bounds(left + 1, 0, getSize().getWidth() - left - BORDER, top);
+        if (bounds.contains(mouseLocation)) {
+            return ViewAreaType.CONTENT;
+        } else {
+            return super.viewAreaType(mouseLocation);
+        }
+    }
+
+    @Override
+    public void viewMenuOptions(final UserActionSet options) {
+        super.viewMenuOptions(options);
+        TreeDisplayRules.menuOptions(options);
+
+        options.add(new UserActionAbstract("Select node") {
+            @Override
+            public void execute(final Workspace workspace, final View view, final Location at) {
+                selectNode();
+            }
+
+            @Override
+            public String getDescription(final View view) {
+                return "Show this node in the right-hand pane";
+            }
+        });
+
+        final ObjectAdapter adapter = getView().getContent().getAdapter();
+        if (adapter instanceof ObjectAdapter && (adapter.isGhost() /*|| adapter.getResolveState().isPartlyResolved() */)) {
+            options.add(new UserActionAbstract("Load object") {
+                @Override
+                public void execute(final Workspace workspace, final View view, final Location at) {
+                    resolveContent();
+                }
+            });
+        }
+    }
+
+    private boolean withinBox(final int x, final int y) {
+        return x >= BOX_X_OFFSET && x <= BOX_X_OFFSET + BOX_SIZE && y >= (top - BOX_SIZE) / 2 && y <= (top + BOX_SIZE) / 2;
+    }
+}