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

[20/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/base/BlankView.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/base/BlankView.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/base/BlankView.java
new file mode 100644
index 0000000..f54cc12
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/base/BlankView.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.base;
+
+import org.apache.isis.viewer.dnd.drawing.Size;
+import org.apache.isis.viewer.dnd.view.Content;
+import org.apache.isis.viewer.dnd.view.content.NullContent;
+
+public class BlankView extends AbstractView {
+    private final Size size;
+
+    public BlankView() {
+        this(new NullContent());
+    }
+
+    public BlankView(final Content content) {
+        super(content);
+        size = new Size(100, 50);
+    }
+
+    public BlankView(final Content content, final Size size) {
+        super(content);
+        this.size = size;
+    }
+
+    @Override
+    public Size getRequiredSize(final Size availableSpace) {
+        final Size requiredSize = new Size(size);
+        requiredSize.limitSize(availableSpace);
+        return requiredSize;
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/eb613703/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/base/DragViewOutline.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/base/DragViewOutline.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/base/DragViewOutline.java
new file mode 100644
index 0000000..ea93795
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/base/DragViewOutline.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.view.base;
+
+import org.apache.isis.viewer.dnd.drawing.Bounds;
+import org.apache.isis.viewer.dnd.drawing.Canvas;
+import org.apache.isis.viewer.dnd.drawing.ColorsAndFonts;
+import org.apache.isis.viewer.dnd.drawing.Size;
+import org.apache.isis.viewer.dnd.view.Toolkit;
+import org.apache.isis.viewer.dnd.view.View;
+
+public class DragViewOutline extends AbstractView {
+    private final int thickness = 5;
+    private final Size size;
+
+    public DragViewOutline(final View view) {
+        super(view.getContent());
+        size = view.getSize();
+        setLocation(view.getAbsoluteLocation());
+    }
+
+    @Override
+    public void draw(final Canvas canvas) {
+        super.draw(canvas);
+
+        final Bounds r = getBounds();
+
+        for (int i = 0; i < thickness; i++) {
+            canvas.drawRectangle(i, i, r.getWidth() - i * 2 - 1, r.getHeight() - i * 2 - 1, Toolkit.getColor(ColorsAndFonts.COLOR_SECONDARY1));
+        }
+    }
+
+    @Override
+    public Size getRequiredSize(final Size availableSpace) {
+        return new 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/base/FieldErrorView.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/base/FieldErrorView.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/base/FieldErrorView.java
new file mode 100644
index 0000000..18840eb
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/base/FieldErrorView.java
@@ -0,0 +1,117 @@
+/*
+ *  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.base;
+
+import org.apache.isis.core.commons.exceptions.NotYetImplementedException;
+import org.apache.isis.viewer.dnd.drawing.Canvas;
+import org.apache.isis.viewer.dnd.drawing.ColorsAndFonts;
+import org.apache.isis.viewer.dnd.drawing.Location;
+import org.apache.isis.viewer.dnd.drawing.Size;
+import org.apache.isis.viewer.dnd.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.ViewAreaType;
+import org.apache.isis.viewer.dnd.view.ViewRequirement;
+import org.apache.isis.viewer.dnd.view.ViewSpecification;
+
+/**
+ * Displays an error message in place of a normal field when a problem occurs,
+ * usually due to a programming error, and the normal field cannot be created. A
+ * example of this is where value field is declared in an ObjectAdapter, but the
+ * programmer forgot to instantiate the value object, causing null to be
+ * returned instead, which is an illegal value.
+ */
+public class FieldErrorView extends AbstractView {
+
+    private final String error;
+
+    public FieldErrorView(final String errorMessage) {
+        super(null);
+        this.error = errorMessage;
+    }
+
+    @Override
+    public void draw(final Canvas canvas) {
+        super.draw(canvas);
+
+        final Size size = getSize();
+        canvas.drawSolidRectangle(0, 0, size.getWidth() - 1, size.getHeight() - 1, Toolkit.getColor(ColorsAndFonts.COLOR_WHITE));
+        canvas.drawRectangle(0, 0, size.getWidth() - 1, size.getHeight() - 1, Toolkit.getColor(ColorsAndFonts.COLOR_BLACK));
+        canvas.drawText(error, 14, 20, Toolkit.getColor(ColorsAndFonts.COLOR_INVALID), Toolkit.getText(ColorsAndFonts.TEXT_NORMAL));
+    }
+
+    @Override
+    public int getBaseline() {
+        return 20;
+    }
+
+    @Override
+    public Size getRequiredSize(final Size availableSpace) {
+        return new Size(250, 30);
+    }
+
+    @Override
+    public ViewAreaType viewAreaType(final Location mouseLocation) {
+        return mouseLocation.getX() <= 10 ? ViewAreaType.VIEW : ViewAreaType.CONTENT;
+    }
+
+    public static class Specification implements ViewSpecification {
+        @Override
+        public boolean canDisplay(final ViewRequirement requirement) {
+            return true;
+        }
+
+        @Override
+        public View createView(final Content content, final Axes axes, final int sequence) {
+            throw new NotYetImplementedException();
+        }
+
+        @Override
+        public String getName() {
+            return "Field Error";
+        }
+
+        @Override
+        public boolean isAligned() {
+            return false;
+        }
+
+        @Override
+        public boolean isSubView() {
+            return false;
+        }
+
+        @Override
+        public boolean isResizeable() {
+            return false;
+        }
+
+        @Override
+        public boolean isReplaceable() {
+            return false;
+        }
+
+        @Override
+        public boolean isOpen() {
+            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/base/IconGraphic.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/base/IconGraphic.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/base/IconGraphic.java
new file mode 100644
index 0000000..4ff8f39
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/base/IconGraphic.java
@@ -0,0 +1,115 @@
+/*
+ *  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.base;
+
+import org.apache.isis.core.commons.lang.ToString;
+import org.apache.isis.viewer.dnd.drawing.Bounds;
+import org.apache.isis.viewer.dnd.drawing.Canvas;
+import org.apache.isis.viewer.dnd.drawing.ColorsAndFonts;
+import org.apache.isis.viewer.dnd.drawing.Image;
+import org.apache.isis.viewer.dnd.drawing.ImageFactory;
+import org.apache.isis.viewer.dnd.drawing.Location;
+import org.apache.isis.viewer.dnd.drawing.Size;
+import org.apache.isis.viewer.dnd.drawing.Text;
+import org.apache.isis.viewer.dnd.view.Content;
+import org.apache.isis.viewer.dnd.view.Toolkit;
+import org.apache.isis.viewer.dnd.view.View;
+
+/*
+ *  TODO why does this pass out the baseline, and then expect it back when doing the drawing?
+ */
+public class IconGraphic {
+    private final int baseline;
+    protected final Content content;
+    protected Image icon;
+    protected final int iconHeight;
+    private String lastIconName;
+
+    public IconGraphic(final View view, final int height, final int baseline) {
+        content = view.getContent();
+        iconHeight = height;
+        this.baseline = baseline;
+    }
+
+    public IconGraphic(final View view, final int height) {
+        content = view.getContent();
+        iconHeight = height;
+        baseline = height * 80 / 100;
+    }
+
+    public IconGraphic(final View view, final Text style) {
+        content = view.getContent();
+        iconHeight = style.getTextHeight();
+        this.baseline = style.getAscent();
+    }
+
+    public void draw(final Canvas canvas, final int x, final int baseline) {
+        final int y = baseline - this.baseline + 1;
+        if (Toolkit.debug) {
+            canvas.drawDebugOutline(new Bounds(new Location(x, y), getSize()), getBaseline(), Toolkit.getColor(ColorsAndFonts.COLOR_DEBUG_BOUNDS_DRAW));
+        }
+        final Image icon = icon();
+        if (icon == null) {
+            canvas.drawSolidOval(x + 1, y, iconHeight, iconHeight, Toolkit.getColor(ColorsAndFonts.COLOR_PRIMARY3));
+        } else {
+            canvas.drawImage(icon, x + 1, y);
+        }
+    }
+
+    public int getBaseline() {
+        return baseline;
+    }
+
+    public Size getSize() {
+        final Image icon = icon();
+        final int iconWidth = icon == null ? iconHeight : icon.getWidth();
+        return new Size(iconWidth + 2, iconHeight + 2);
+    }
+
+    protected Image icon() {
+        final String iconName = content.getIconName();
+        /*
+         * If the graphic is based on a name provided by the object then the
+         * icon could be changed at any time, so we won't lazily load it.
+         */
+        if (icon != null && (iconName == null || iconName.equals(lastIconName))) {
+            return icon;
+        }
+        lastIconName = iconName;
+        if (iconName != null) {
+            icon = ImageFactory.getInstance().loadIcon(iconName, iconHeight, null);
+        }
+        if (icon == null) {
+            icon = content.getIconPicture(iconHeight);
+        }
+        if (icon == null) {
+            icon = ImageFactory.getInstance().loadDefaultIcon(iconHeight, null);
+        }
+        return icon;
+    }
+
+    @Override
+    public String toString() {
+        final ToString str = new ToString(this);
+        str.append("baseline", baseline);
+        str.append("icon", icon);
+        return str.toString();
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/eb613703/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/base/Layout.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/base/Layout.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/base/Layout.java
new file mode 100644
index 0000000..3188f21
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/base/Layout.java
@@ -0,0 +1,29 @@
+/*
+ *  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.base;
+
+import org.apache.isis.viewer.dnd.drawing.Size;
+import org.apache.isis.viewer.dnd.view.View;
+
+public interface Layout {
+    Size getRequiredSize(final View view);
+
+    void layout(final View view, final Size maximumSize);
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/eb613703/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/base/NonBuildingSpecification.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/base/NonBuildingSpecification.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/base/NonBuildingSpecification.java
new file mode 100644
index 0000000..46aaf2a
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/base/NonBuildingSpecification.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.view.base;
+
+import org.apache.isis.core.commons.exceptions.UnexpectedCallException;
+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;
+
+public class NonBuildingSpecification implements ViewSpecification {
+    private final String name;
+
+    public NonBuildingSpecification(final View view) {
+        final String name = view.getClass().getName();
+        this.name = name.substring(name.lastIndexOf('.') + 1);
+    }
+
+    @Override
+    public View createView(final Content content, final Axes axes, final int sequence) {
+        throw new UnexpectedCallException();
+    }
+
+    @Override
+    public String getName() {
+        return name;
+    }
+
+    @Override
+    public boolean isAligned() {
+        return false;
+    }
+
+    @Override
+    public boolean isOpen() {
+        return true;
+    }
+
+    @Override
+    public boolean isReplaceable() {
+        return false;
+    }
+
+    @Override
+    public boolean isResizeable() {
+        return false;
+    }
+
+    @Override
+    public boolean isSubView() {
+        return false;
+    }
+
+    @Override
+    public boolean canDisplay(final ViewRequirement requirement) {
+        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/base/NullView.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/base/NullView.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/base/NullView.java
new file mode 100644
index 0000000..9fe68a3
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/base/NullView.java
@@ -0,0 +1,34 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.isis.viewer.dnd.view.base;
+
+import org.apache.isis.viewer.dnd.view.content.NullContent;
+
+public class NullView extends AbstractView {
+    public NullView() {
+        super(new NullContent(""));
+    }
+
+    @Override
+    public String toString() {
+        final String name = getClass().getName();
+        return name.substring(name.lastIndexOf('.') + 1) + 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/base/ObjectView.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/base/ObjectView.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/base/ObjectView.java
new file mode 100644
index 0000000..8e83abe
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/base/ObjectView.java
@@ -0,0 +1,143 @@
+/*
+ *  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.base;
+
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.consent.Consent;
+import org.apache.isis.core.metamodel.spec.ActionType;
+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.Location;
+import org.apache.isis.viewer.dnd.drawing.Offset;
+import org.apache.isis.viewer.dnd.interaction.ViewDragImpl;
+import org.apache.isis.viewer.dnd.view.Click;
+import org.apache.isis.viewer.dnd.view.Content;
+import org.apache.isis.viewer.dnd.view.ContentDrag;
+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.Toolkit;
+import org.apache.isis.viewer.dnd.view.UserActionSet;
+import org.apache.isis.viewer.dnd.view.View;
+import org.apache.isis.viewer.dnd.view.ViewSpecification;
+import org.apache.isis.viewer.dnd.view.Workspace;
+import org.apache.isis.viewer.dnd.view.option.UserActionAbstract;
+
+public abstract class ObjectView extends AbstractView {
+
+    public ObjectView(final Content content, final ViewSpecification specification) {
+        super(content, specification);
+    }
+
+    @Override
+    public void dragIn(final ContentDrag drag) {
+        final Consent consent = getContent().canDrop(drag.getSourceContent());
+        final String description = getContent().getDescription();
+        if (consent.isAllowed()) {
+            getFeedbackManager().setAction(consent.getDescription() + " " + description);
+            getState().setCanDrop();
+        } else {
+            getFeedbackManager().setAction(consent.getReason() + " " + description);
+            getState().setCantDrop();
+        }
+        markDamaged();
+    }
+
+    @Override
+    public void dragOut(final ContentDrag drag) {
+        getState().clearObjectIdentified();
+        markDamaged();
+    }
+
+    @Override
+    public DragEvent dragStart(final DragStart drag) {
+        final View subview = subviewFor(drag.getLocation());
+        if (subview != null) {
+            drag.subtract(subview.getLocation());
+            return subview.dragStart(drag);
+        } else {
+            if (drag.isCtrl()) {
+                final View dragOverlay = new DragViewOutline(getView());
+                return new ViewDragImpl(this, new Offset(drag.getLocation()), dragOverlay);
+            } else {
+                return Toolkit.getViewFactory().createDragContentOutline(this, drag.getLocation());
+            }
+        }
+    }
+
+    /**
+     * Called when a dragged object is dropped onto this view. The default
+     * behaviour implemented here calls the action method on the target, passing
+     * the source object in as the only parameter.
+     */
+    @Override
+    public void drop(final ContentDrag drag) {
+        final ObjectAdapter result = getContent().drop(drag.getSourceContent());
+        if (result != null) {
+            objectActionResult(result, new Placement(this));
+        }
+        getState().clearObjectIdentified();
+        getFeedbackManager().showMessagesAndWarnings();
+
+        markDamaged();
+    }
+
+    @Override
+    public void firstClick(final Click click) {
+        final View subview = subviewFor(click.getLocation());
+        if (subview != null) {
+            click.subtract(subview.getLocation());
+            subview.firstClick(click);
+        } else {
+            if (click.button2()) {
+                final Location location = new Location(click.getLocationWithinViewer());
+                getViewManager().showInOverlay(getContent(), location);
+            }
+        }
+    }
+
+    @Override
+    public void invalidateContent() {
+        super.invalidateLayout();
+    }
+
+    @Override
+    public void secondClick(final Click click) {
+        final View subview = subviewFor(click.getLocation());
+        if (subview != null) {
+            click.subtract(subview.getLocation());
+            subview.secondClick(click);
+        } else {
+            final Location location = getAbsoluteLocation();
+            location.translate(click.getLocation());
+            getWorkspace().addWindowFor(getContent().getAdapter(), new Placement(this));
+        }
+    }
+
+
+    // ///////////////////////////////////////////////////////////////////////////
+    // 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/base/TextView.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/base/TextView.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/base/TextView.java
new file mode 100644
index 0000000..4ec4afc
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/base/TextView.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.view.base;
+
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+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.Size;
+import org.apache.isis.viewer.dnd.drawing.Text;
+import org.apache.isis.viewer.dnd.view.Content;
+import org.apache.isis.viewer.dnd.view.Toolkit;
+import org.apache.isis.viewer.dnd.view.ViewConstants;
+import org.apache.isis.viewer.dnd.view.ViewSpecification;
+
+public class TextView extends AbstractView {
+    private final Text style = Toolkit.getText(ColorsAndFonts.TEXT_NORMAL);
+    private final Color color = Toolkit.getColor(ColorsAndFonts.COLOR_BLACK);
+    private final String text;
+
+    public TextView(final Content content, final ViewSpecification specification) {
+        super(content, specification);
+        final ObjectAdapter object = content.getAdapter();
+        text = object == null ? "" : object.titleString();
+    }
+
+    @Override
+    public boolean canFocus() {
+        return false;
+    }
+
+    @Override
+    public void draw(final Canvas canvas) {
+        canvas.drawText(text, ViewConstants.HPADDING, getBaseline(), color, style);
+    }
+
+    @Override
+    public int getBaseline() {
+        return style.getAscent() + ViewConstants.VPADDING;
+    }
+
+    @Override
+    public Size getRequiredSize(final Size maximumSize) {
+        final int width = style.stringWidth(text) + ViewConstants.HPADDING * 2;
+        final int height = style.getTextHeight() + ViewConstants.VPADDING * 2;
+        return new Size(width, height);
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/eb613703/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/base/UserViewSpecification.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/base/UserViewSpecification.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/base/UserViewSpecification.java
new file mode 100644
index 0000000..963106b
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/base/UserViewSpecification.java
@@ -0,0 +1,96 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.isis.viewer.dnd.view.base;
+
+import org.apache.isis.core.runtime.userprofile.Options;
+import org.apache.isis.viewer.dnd.util.Properties;
+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;
+
+public class UserViewSpecification implements ViewSpecification {
+
+    private final ViewSpecification specification;
+    private final String name;
+
+    public UserViewSpecification(final ViewSpecification specification, final String name) {
+        this.specification = specification;
+        this.name = name;
+    }
+
+    /*
+     * public UserViewSpecification(View view) { specification =
+     * view.getSpecification(); Options copyOptions = new Options();
+     * view.saveOptions(copyOptions); name = specification.getName() + " " + new
+     * Date().getSeconds();
+     * 
+     * // view.setSpecification(this); // view.loadOptions(copyOptions); }
+     */
+    @Override
+    public boolean canDisplay(final ViewRequirement requirement) {
+        return specification.canDisplay(requirement);
+    }
+
+    @Override
+    public String getName() {
+        return name;
+    }
+
+    @Override
+    public boolean isAligned() {
+        return specification.isAligned();
+    }
+
+    @Override
+    public boolean isOpen() {
+        return specification.isOpen();
+    }
+
+    @Override
+    public boolean isReplaceable() {
+        return specification.isReplaceable();
+    }
+
+    @Override
+    public boolean isResizeable() {
+        return specification.isResizeable();
+    }
+
+    @Override
+    public boolean isSubView() {
+        return specification.isSubView();
+    }
+
+    @Override
+    public View createView(final Content content, final Axes axes, final int sequence) {
+        final View createView = specification.createView(content, axes, sequence);
+
+        final Options viewOptions = Properties.getViewConfigurationOptions(this);
+        createView.loadOptions(viewOptions);
+        return createView;
+    }
+
+    public ViewSpecification getWrappedSpecification() {
+        return 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/base/ViewUpdateNotifierImpl.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/base/ViewUpdateNotifierImpl.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/base/ViewUpdateNotifierImpl.java
new file mode 100644
index 0000000..16203b5
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/base/ViewUpdateNotifierImpl.java
@@ -0,0 +1,273 @@
+/*
+ *  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.base;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Vector;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+
+import org.apache.log4j.Logger;
+
+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.adapter.mgr.AdapterManager;
+import org.apache.isis.runtimes.dflt.runtime.system.context.IsisContext;
+import org.apache.isis.runtimes.dflt.runtime.system.persistence.AdapterManagerSpi;
+import org.apache.isis.runtimes.dflt.runtime.system.persistence.PersistenceSession;
+import org.apache.isis.runtimes.dflt.runtime.system.transaction.MessageBroker;
+import org.apache.isis.runtimes.dflt.runtime.system.transaction.UpdateNotifier;
+import org.apache.isis.viewer.dnd.view.Content;
+import org.apache.isis.viewer.dnd.view.ObjectContent;
+import org.apache.isis.viewer.dnd.view.View;
+import org.apache.isis.viewer.dnd.view.ViewUpdateNotifier;
+import org.apache.isis.viewer.dnd.view.collection.RootCollection;
+
+public class ViewUpdateNotifierImpl implements ViewUpdateNotifier {
+    
+    private static final Logger LOG = Logger.getLogger(ViewUpdateNotifierImpl.class);
+    
+    protected Map<ObjectAdapter, List<View>> viewListByAdapter = Maps.newHashMap();
+
+    @Override
+    public void add(final View view) {
+        final Content content = view.getContent();
+        if (content != null && content.isObject()) {
+            final ObjectAdapter adapter = content.getAdapter();
+
+            if (adapter != null) {
+                List<View> viewsToNotify;
+
+                if (viewListByAdapter.containsKey(adapter)) {
+                    viewsToNotify = viewListByAdapter.get(adapter);
+                } else {
+                    viewsToNotify = new Vector<View>();
+                    viewListByAdapter.put(adapter, viewsToNotify);
+                }
+
+                if (viewsToNotify.contains(view)) {
+                    throw new IsisException(view + " already being notified");
+                }
+                viewsToNotify.add(view);
+                if (LOG.isDebugEnabled()) {
+                    LOG.debug("added " + view + " to observers for " + adapter);
+                }
+            }
+        }
+    }
+
+    @Override
+    public void debugData(final DebugBuilder buf) {
+        for(Map.Entry<ObjectAdapter, List<View>> mapEntry: viewListByAdapter.entrySet()) {
+            final ObjectAdapter objectAdapter = mapEntry.getKey();
+            final List<View> viewsToNotify = mapEntry.getValue();
+            
+            buf.append("Views for " + objectAdapter + " \n");
+
+            for(View view: viewsToNotify) {
+                buf.append("        " + view);
+                buf.append("\n");
+            }
+            buf.append("\n");
+        }
+    }
+
+    @Override
+    public String debugTitle() {
+        return "Views for object details (observers)";
+    }
+
+    @Override
+    public void remove(final View view) {
+        final Content content = view.getContent();
+        if (content == null || !content.isObject()) {
+            // nothing to do
+            return;
+        }
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("removing " + content + " for " + view);
+        }
+
+        final ObjectAdapter object = ((ObjectContent) content).getObject();
+        if (object != null) {
+            if (!viewListByAdapter.containsKey(object)) {
+                throw new IsisException("Tried to remove a non-existant view " + view + " from observers for " + object);
+            }
+            
+            List<View> viewsToNotify = viewListByAdapter.get(object);
+            for(View v: viewsToNotify) {
+                if (view == v.getView()) {
+                    viewsToNotify.remove(v);
+                    LOG.debug("removed " + view + " from observers for " + object);
+                    break;
+                }
+            }
+
+            if (viewsToNotify.size() == 0) {
+                viewListByAdapter.remove(object);
+                if (LOG.isDebugEnabled()) {
+                    LOG.debug("removed observer list for " + object);
+                }
+
+                // TODO need to do garbage collection instead
+                // ObjectAdapterLoader loader = Isis.getObjectLoader();
+                // loader.unloaded((ObjectAdapter) object);
+            }
+        }
+    }
+
+    public void shutdown() {
+        viewListByAdapter.clear();
+    }
+
+    @Override
+    public void invalidateViewsForChangedObjects() {
+        for (final ObjectAdapter object : getUpdateNotifier().getChangedObjects()) {
+            if (LOG.isDebugEnabled()) {
+                LOG.debug("invalidate views for " + object);
+            }
+            final List<View> viewsVector = viewListByAdapter.get(object);
+            if (viewsVector == null) {
+                continue;
+            }
+            for(View view: viewsVector) {
+                LOG.debug("   - " + view);
+                view.getView().invalidateContent();
+            }
+        }
+    }
+
+    @Override
+    public void removeViewsForDisposedObjects() {
+        for (final ObjectAdapter objectToDispose : getUpdateNotifier().getDisposedObjects()) {
+            if (LOG.isDebugEnabled()) {
+                LOG.debug("dispose views for " + objectToDispose);
+            }
+            final List<View> viewsForObject = viewListByAdapter.get(objectToDispose);
+            if (viewsForObject == null) {
+                continue;
+            }
+            removeViews(viewsForObject);
+            final List<View> remainingViews = viewListByAdapter.get(objectToDispose);
+            if (remainingViews != null && remainingViews.size() > 0) {
+                getMessageBroker().addWarning("There are still views (within other views) for the disposed object " + objectToDispose.titleString() + ".  Only objects that are shown as root views can be properly disposed of");
+            } else {
+                getPersistenceSession().removeAdapter(objectToDispose);
+            }
+        }
+    }
+
+    private void removeViews(final List<View> viewsForObject) {
+        //final View[] viewsArray = new View[viewsForObject.size()];
+        //viewsForObject.copyInto(viewsArray);
+        
+        final List<View> viewsArray = Lists.newArrayList(viewsForObject); // take a copy
+        
+        final View[] viewsOnWorkspace = viewsArray.get(0).getWorkspace().getSubviews();
+        
+        for (final View element : viewsArray) {
+            final View view = element.getView();
+            for (final View viewOnWorkspace : viewsOnWorkspace) {
+                if (view == viewOnWorkspace) {
+                    LOG.debug("   (root removed) " + view);
+                    view.getView().dispose();
+                    break;
+                }
+            }
+
+            for (final View element2 : viewsOnWorkspace) {
+                if (element2.getContent() instanceof RootCollection) {
+                    final View[] subviewsOfRootView = element2.getSubviews();
+                    for (final View element3 : subviewsOfRootView) {
+                        if (element3 == view) {
+                            LOG.debug("   (element removed) " + view);
+                            view.getView().dispose();
+                        }
+                    }
+                }
+            }
+
+            for (final View element2 : viewsOnWorkspace) {
+                if (element2.contains(view)) {
+                    LOG.debug("   (invalidated) " + view);
+                    final View parent = view.getParent();
+                    parent.invalidateContent();
+                }
+            }
+
+        }
+
+    }
+
+    // ////////////////////////////////////////////////////////////////
+    // Dependencies (from singleton)
+    // ////////////////////////////////////////////////////////////////
+
+    private static PersistenceSession getPersistenceSession() {
+        return IsisContext.getPersistenceSession();
+    }
+
+    private static AdapterManager getAdapterManager() {
+        return getPersistenceSession().getAdapterManager();
+    }
+
+    private static MessageBroker getMessageBroker() {
+        return IsisContext.getMessageBroker();
+    }
+
+    private static UpdateNotifier getUpdateNotifier() {
+        return IsisContext.inSession() ? IsisContext.getUpdateNotifier() : new NoOpUpdateNotifier();
+    }
+
+}
+
+class NoOpUpdateNotifier implements UpdateNotifier {
+
+    @Override
+    public void addChangedObject(final ObjectAdapter object) {
+    }
+
+    @Override
+    public void addDisposedObject(final ObjectAdapter adapter) {
+    }
+
+    @Override
+    public void clear() {
+    }
+
+    @Override
+    public void ensureEmpty() {
+    }
+
+    @Override
+    public List<ObjectAdapter> getChangedObjects() {
+        return new ArrayList<ObjectAdapter>();
+    }
+
+    @Override
+    public List<ObjectAdapter> getDisposedObjects() {
+        return new ArrayList<ObjectAdapter>();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/eb613703/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/border/BackgroundBorder.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/border/BackgroundBorder.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/border/BackgroundBorder.java
new file mode 100644
index 0000000..2f2dbf0
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/border/BackgroundBorder.java
@@ -0,0 +1,63 @@
+/*
+ *  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.border;
+
+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.view.Toolkit;
+import org.apache.isis.viewer.dnd.view.View;
+import org.apache.isis.viewer.dnd.view.base.AbstractBorder;
+
+/**
+ * A background border provides a coloured background to a view of a specified
+ * colour.
+ */
+public class BackgroundBorder extends AbstractBorder {
+    private Color background;
+
+    /**
+     * Creates a background border with a default colour of white.
+     */
+    public BackgroundBorder(final View wrappedView) {
+        super(wrappedView);
+        background = Toolkit.getColor(ColorsAndFonts.COLOR_WHITE);
+    }
+
+    public BackgroundBorder(final Color background, final View wrappedView) {
+        super(wrappedView);
+        this.background = background;
+    }
+
+    @Override
+    public void draw(final Canvas canvas) {
+        clearBackground(canvas, background);
+        super.draw(canvas);
+    }
+
+    public void setBackground(final Color color) {
+        this.background = color;
+    }
+
+    @Override
+    public String toString() {
+        return wrappedView.toString() + "/BackgroundBorder";
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/eb613703/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/border/BorderDrawing.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/border/BorderDrawing.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/border/BorderDrawing.java
new file mode 100644
index 0000000..d50b2b8
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/border/BorderDrawing.java
@@ -0,0 +1,48 @@
+/*
+ *  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.border;
+
+import org.apache.isis.core.commons.debug.DebugBuilder;
+import org.apache.isis.viewer.dnd.drawing.Canvas;
+import org.apache.isis.viewer.dnd.drawing.Size;
+import org.apache.isis.viewer.dnd.view.View;
+import org.apache.isis.viewer.dnd.view.ViewState;
+
+public interface BorderDrawing {
+
+    public abstract void debugDetails(final DebugBuilder debug);
+
+    public abstract void layoutControls(final Size size, View[] controls);
+
+    public abstract void draw(final Canvas canvas, Size s, boolean hasFocus, final ViewState state, View[] controls, String title);
+
+    public abstract void drawTransientMarker(Canvas canvas, Size size);
+
+    public abstract void getRequiredSize(Size size, String title, View[] controls);
+
+    public abstract int getLeft();
+
+    public abstract int getRight();
+
+    public abstract int getTop();
+
+    public abstract int getBottom();
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/eb613703/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/border/ButtonBorder.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/border/ButtonBorder.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/border/ButtonBorder.java
new file mode 100644
index 0000000..366c0ca
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/border/ButtonBorder.java
@@ -0,0 +1,199 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.isis.viewer.dnd.view.border;
+
+import java.awt.event.KeyEvent;
+
+import org.apache.isis.viewer.dnd.drawing.Bounds;
+import org.apache.isis.viewer.dnd.drawing.Canvas;
+import org.apache.isis.viewer.dnd.drawing.Location;
+import org.apache.isis.viewer.dnd.drawing.Size;
+import org.apache.isis.viewer.dnd.view.ButtonAction;
+import org.apache.isis.viewer.dnd.view.Click;
+import org.apache.isis.viewer.dnd.view.KeyboardAction;
+import org.apache.isis.viewer.dnd.view.View;
+import org.apache.isis.viewer.dnd.view.ViewConstants;
+import org.apache.isis.viewer.dnd.view.base.AbstractBorder;
+import org.apache.isis.viewer.dnd.view.control.Button;
+
+public class ButtonBorder extends AbstractBorder {
+    private static final int BUTTON_SPACING = 5;
+    private final View[] buttons;
+    private ButtonAction defaultAction;
+
+    public ButtonBorder(final ButtonAction[] actions, final View view) {
+        super(view);
+
+        buttons = new View[actions.length];
+        for (int i = 0; i < actions.length; i++) {
+            final ButtonAction action = actions[i];
+            buttons[i] = new Button(action, view);
+            if (action.isDefault()) {
+                defaultAction = action;
+            }
+        }
+        // space for: line & button with whitespace
+        bottom = 1 + ViewConstants.VPADDING + buttons[0].getRequiredSize(new Size()).getHeight() + ViewConstants.VPADDING;
+
+    }
+
+    @Override
+    public void draw(final Canvas canvas) {
+        // draw buttons
+        for (final View button : buttons) {
+            final Canvas buttonCanvas = canvas.createSubcanvas(button.getBounds());
+            button.draw(buttonCanvas);
+            final int buttonWidth = button.getSize().getWidth();
+            buttonCanvas.offset(BUTTON_SPACING + buttonWidth, 0);
+        }
+
+        // draw rest
+        super.draw(canvas);
+    }
+
+    @Override
+    public void firstClick(final Click click) {
+        final View button = overButton(click.getLocation());
+        if (button == null) {
+            super.firstClick(click);
+        } else {
+            button.firstClick(click);
+        }
+    }
+
+    public View[] getButtons() {
+        return buttons;
+    }
+
+    @Override
+    public Size getRequiredSize(final Size maximumSize) {
+        final Size size = super.getRequiredSize(maximumSize);
+        size.ensureWidth(totalButtonWidth());
+        size.extendWidth(BUTTON_SPACING * 2);
+        return size;
+    }
+
+    @Override
+    public View identify(final Location location) {
+        for (final View button : buttons) {
+            if (button.getBounds().contains(location)) {
+                return button;
+            }
+        }
+        return super.identify(location);
+    }
+
+    @Override
+    public void keyPressed(final KeyboardAction key) {
+        if (key.getKeyCode() == KeyEvent.VK_ENTER) {
+            if (defaultAction != null && defaultAction.disabled(getView()).isAllowed()) {
+                key.consume();
+                defaultAction.execute(getWorkspace(), getView(), getLocation());
+            }
+        }
+
+        super.keyPressed(key);
+    }
+
+    public void layout(final int width) {
+        int x = width / 2 - totalButtonWidth() / 2;
+        final int y = getSize().getHeight() - ViewConstants.VPADDING - buttons[0].getRequiredSize(new Size()).getHeight();
+
+        for (int i = 0; i < buttons.length; i++) {
+            buttons[i] = buttons[i];
+            buttons[i].setSize(buttons[i].getRequiredSize(new Size()));
+            buttons[i].setLocation(new Location(x, y));
+
+            x += buttons[i].getSize().getWidth();
+            x += BUTTON_SPACING;
+        }
+    }
+
+    @Override
+    public void mouseDown(final Click click) {
+        final View button = overButton(click.getLocation());
+        if (button == null) {
+            super.mouseDown(click);
+        } else {
+            button.mouseDown(click);
+        }
+    }
+
+    @Override
+    public void mouseUp(final Click click) {
+        final View button = overButton(click.getLocation());
+        if (button == null) {
+            super.mouseUp(click);
+        } else {
+            button.mouseUp(click);
+        }
+    }
+
+    /**
+     * Finds the action button under the pointer; returning null if none.
+     */
+    private View overButton(final Location location) {
+        for (final View button : buttons) {
+            if (button.getBounds().contains(location)) {
+                return button;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public void secondClick(final Click click) {
+        final View button = overButton(click.getLocation());
+        if (button == null) {
+            super.secondClick(click);
+        }
+    }
+
+    @Override
+    public void setBounds(final Bounds bounds) {
+        super.setBounds(bounds);
+        layout(bounds.getWidth());
+    }
+
+    @Override
+    public void setSize(final Size size) {
+        super.setSize(size);
+        layout(size.getWidth());
+    }
+
+    @Override
+    public void thirdClick(final Click click) {
+        final View button = overButton(click.getLocation());
+        if (button == null) {
+            super.thirdClick(click);
+        }
+    }
+
+    private int totalButtonWidth() {
+        int totalButtonWidth = 0;
+        for (int i = 0; i < buttons.length; i++) {
+            final int buttonWidth = buttons[i].getRequiredSize(new Size()).getWidth();
+            totalButtonWidth += i > 0 ? BUTTON_SPACING : 0;
+            totalButtonWidth += buttonWidth;
+        }
+        return totalButtonWidth;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/eb613703/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/border/DisposedObjectBorder.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/border/DisposedObjectBorder.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/border/DisposedObjectBorder.java
new file mode 100644
index 0000000..85741ec
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/border/DisposedObjectBorder.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.view.border;
+
+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.Size;
+import org.apache.isis.viewer.dnd.view.DragEvent;
+import org.apache.isis.viewer.dnd.view.DragStart;
+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 DisposedObjectBorder extends AbstractBorder {
+
+    public DisposedObjectBorder(final int size, final View wrappedView) {
+        super(wrappedView);
+        top = size;
+        left = size;
+        bottom = size;
+        right = size;
+    }
+
+    public DisposedObjectBorder(final View wrappedView) {
+        this(2, wrappedView);
+    }
+
+    @Override
+    protected void debugDetails(final DebugBuilder debug) {
+        debug.append("DisposedObjectBorder " + top + " pixels");
+    }
+
+    @Override
+    public DragEvent dragStart(final DragStart drag) {
+        return super.dragStart(drag);
+    }
+
+    @Override
+    public void draw(final Canvas canvas) {
+        super.draw(canvas);
+
+        Color color = null;
+        color = Toolkit.getColor(ColorsAndFonts.COLOR_INVALID);
+        final Size s = getSize();
+
+        final int w = s.getWidth();
+        final int xExtent = s.getWidth() - left;
+        for (int i = 0; i < left; i++) {
+            canvas.drawRectangle(i, i, xExtent - 2 * i, s.getHeight() - 2 * i, color);
+        }
+        for (int i = 0; i < 15; i++) {
+            canvas.drawLine(left, top + i, left + i, top, color);
+            canvas.drawLine(w - left - right - 1, s.getHeight() - top - i - 1, w - left - right - i - 1, s.getHeight() - top - 1, color);
+        }
+    }
+
+    @Override
+    public void entered() {
+        wrappedView.entered();
+        getFeedbackManager().setError("Destroyed objects cannot be used");
+        markDamaged();
+    }
+
+    @Override
+    public void exited() {
+        wrappedView.exited();
+        getFeedbackManager().setError("");
+        markDamaged();
+    }
+
+    @Override
+    public String toString() {
+        return wrappedView.toString() + "/DisposedObjectBorder [" + getSpecification() + "]";
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/eb613703/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/border/DragViewBorder.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/border/DragViewBorder.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/border/DragViewBorder.java
new file mode 100644
index 0000000..d8c033d
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/border/DragViewBorder.java
@@ -0,0 +1,112 @@
+/*
+ *  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.border;
+
+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.interaction.ViewDragImpl;
+import org.apache.isis.viewer.dnd.view.DragEvent;
+import org.apache.isis.viewer.dnd.view.DragStart;
+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;
+import org.apache.isis.viewer.dnd.view.base.DragViewOutline;
+
+/**
+ * A drag view border provides a line and handle that appears when the mouse
+ * moves over the contained view and allows the view to be dragged.
+ */
+public class DragViewBorder extends AbstractBorder {
+    private final int handleWidth = 14;
+
+    public DragViewBorder(final View wrappedView) {
+        this(1, wrappedView);
+    }
+
+    public DragViewBorder(final int size, final View wrappedView) {
+        super(wrappedView);
+
+        top = size;
+        left = size;
+        bottom = size;
+        right = size + handleWidth;
+    }
+
+    @Override
+    protected void debugDetails(final DebugBuilder debug) {
+        debug.append("SimpleBorder " + top + " pixels\n");
+        debug.append("           handle " + handleWidth + " pixels");
+    }
+
+    @Override
+    public DragEvent dragStart(final DragStart drag) {
+        if (overBorder(drag.getLocation())) {
+            final Location location = drag.getLocation();
+            final DragViewOutline dragOverlay = new DragViewOutline(getView());
+            return new ViewDragImpl(this, new Offset(location.getX(), location.getY()), dragOverlay);
+        } else {
+            return super.dragStart(drag);
+        }
+    }
+
+    @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 draw(final Canvas canvas) {
+        if (getState().isViewIdentified()) {
+            final Color color = Toolkit.getColor(ColorsAndFonts.COLOR_SECONDARY2);
+            final Size s = getSize();
+            final int width = s.getWidth();
+            for (int i = 0; i < left; i++) {
+                canvas.drawRectangle(i, i, width - 2 * i - 1, s.getHeight() - 2 * i - 1, color);
+            }
+            final int w2 = width - left - 2;
+            final int w3 = w2 - handleWidth;
+            for (int x = w2; x > w3; x -= 2) {
+                canvas.drawLine(x, top, x, s.getHeight() - top, color);
+            }
+        }
+        super.draw(canvas);
+    }
+
+    @Override
+    public String toString() {
+        return wrappedView.toString() + "/SimpleBorder";
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/eb613703/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/border/DroppableLabelBorder.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/border/DroppableLabelBorder.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/border/DroppableLabelBorder.java
new file mode 100644
index 0000000..281f777
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/border/DroppableLabelBorder.java
@@ -0,0 +1,172 @@
+/*
+ *  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.border;
+
+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.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.view.Content;
+import org.apache.isis.viewer.dnd.view.ContentDrag;
+import org.apache.isis.viewer.dnd.view.InternalDrag;
+import org.apache.isis.viewer.dnd.view.ObjectContent;
+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.ViewState;
+import org.apache.isis.viewer.dnd.view.action.ParameterContent;
+import org.apache.isis.viewer.dnd.view.axis.LabelAxis;
+import org.apache.isis.viewer.dnd.view.content.FieldContent;
+
+public class DroppableLabelBorder extends LabelBorder {
+
+    public static View createObjectFieldLabelBorder(final LabelAxis axis, final View wrappedView) {
+        final FieldContent fieldContent = (FieldContent) wrappedView.getContent();
+        return new DroppableLabelBorder(fieldContent, axis, wrappedView);
+    }
+
+    public static View createObjectParameterLabelBorder(final LabelAxis axis, final View wrappedView) {
+        final ParameterContent parameterContent = (ParameterContent) wrappedView.getContent();
+        return new DroppableLabelBorder(parameterContent, axis, wrappedView);
+    }
+
+    private final ViewState labelState = new ViewState();
+    private boolean overContent;
+
+    public DroppableLabelBorder(final FieldContent fieldContent, final LabelAxis axis, final View wrappedView) {
+        super(fieldContent, axis, wrappedView);
+    }
+
+    public DroppableLabelBorder(final ParameterContent fieldContent, final LabelAxis axis, final View wrappedView) {
+        super(fieldContent, axis, wrappedView);
+    }
+
+    @Override
+    public ViewAreaType viewAreaType(final Location location) {
+        if (overBorder(location)) {
+            return ViewAreaType.CONTENT; // used to ensure menu options for
+                                         // contained object are shown
+        } else {
+            return super.viewAreaType(location);
+        }
+    }
+
+    @Override
+    public void dragCancel(final InternalDrag drag) {
+        super.dragCancel(drag);
+        labelState.clearViewIdentified();
+    }
+
+    @Override
+    public void drag(final ContentDrag drag) {
+        final Location targetLocation = drag.getTargetLocation();
+        if (overContent(targetLocation) && overContent == false) {
+            overContent = true;
+            super.dragIn(drag);
+            dragOutOfLabel();
+        } else if (overBorder(targetLocation) && overContent == true) {
+            overContent = false;
+            super.dragOut(drag);
+            dragInToLabel(drag.getSourceContent());
+        }
+
+        super.drag(drag);
+    }
+
+    @Override
+    public void dragIn(final ContentDrag drag) {
+        if (overContent(drag.getTargetLocation())) {
+            super.dragIn(drag);
+        } else {
+            final Content sourceContent = drag.getSourceContent();
+            dragInToLabel(sourceContent);
+            markDamaged();
+        }
+    }
+
+    private void dragInToLabel(final Content sourceContent) {
+        overContent = false;
+        final Consent canDrop = canDrop(sourceContent);
+        if (canDrop.isAllowed()) {
+            labelState.setCanDrop();
+        } else {
+            labelState.setCantDrop();
+        }
+        final String actionText = canDrop.isVetoed() ? canDrop.getReason() : "Set to " + sourceContent.title();
+        getFeedbackManager().setAction(actionText);
+    }
+
+    @Override
+    public void dragOut(final ContentDrag drag) {
+        super.dragOut(drag);
+        dragOutOfLabel();
+    }
+
+    private void dragOutOfLabel() {
+        labelState.clearObjectIdentified();
+        markDamaged();
+    }
+
+    @Override
+    public void drop(final ContentDrag drag) {
+        if (overContent(drag.getTargetLocation())) {
+            super.drop(drag);
+        } else {
+            dragOutOfLabel();
+            final Content sourceContent = drag.getSourceContent();
+            if (canDrop(sourceContent).isAllowed()) {
+                drop(sourceContent);
+            }
+        }
+    }
+
+    protected Consent canDrop(final Content dropContent) {
+        if (dropContent instanceof ObjectContent) {
+            final ObjectAdapter source = ((ObjectContent) dropContent).getObject();
+            final ObjectContent content = (ObjectContent) getContent();
+            return content.canSet(source);
+        } else {
+            return Veto.DEFAULT;
+        }
+    }
+
+    protected void drop(final Content dropContent) {
+        if (dropContent instanceof ObjectContent) {
+            final ObjectAdapter object = ((ObjectContent) dropContent).getObject();
+            ((ObjectContent) getContent()).setObject(object);
+            getParent().invalidateContent();
+        }
+    }
+
+    @Override
+    protected Color textColor() {
+        Color color;
+        if (labelState.canDrop()) {
+            color = Toolkit.getColor(ColorsAndFonts.COLOR_VALID);
+        } else if (labelState.cantDrop()) {
+            color = Toolkit.getColor(ColorsAndFonts.COLOR_INVALID);
+        } else {
+            color = super.textColor();
+        }
+        return color;
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/eb613703/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/border/EmptyBorder.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/border/EmptyBorder.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/border/EmptyBorder.java
new file mode 100644
index 0000000..4c28283
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/border/EmptyBorder.java
@@ -0,0 +1,46 @@
+/*
+ *  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.border;
+
+import org.apache.isis.viewer.dnd.view.View;
+import org.apache.isis.viewer.dnd.view.base.AbstractBorder;
+
+public class EmptyBorder extends AbstractBorder {
+
+    public EmptyBorder(final int width, final View view) {
+        super(view);
+        left = top = right = bottom = width;
+    }
+
+    public EmptyBorder(final int topBottom, final int leftRight, final View view) {
+        super(view);
+        left = right = bottom = leftRight;
+        top = bottom = topBottom;
+    }
+
+    public EmptyBorder(final int left, final int top, final int right, final int bottom, final View view) {
+        super(view);
+        this.left = left;
+        this.top = top;
+        this.right = right;
+        this.bottom = bottom;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/eb613703/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/border/FieldOrderBorder.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/border/FieldOrderBorder.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/border/FieldOrderBorder.java
new file mode 100644
index 0000000..3f509dd
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/border/FieldOrderBorder.java
@@ -0,0 +1,64 @@
+/*
+ *  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.border;
+
+import org.apache.isis.viewer.dnd.drawing.Location;
+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.AbstractViewDecorator;
+import org.apache.isis.viewer.dnd.view.option.UserActionAbstract;
+
+public class FieldOrderBorder extends AbstractViewDecorator {
+
+    private boolean isReversed;
+
+    public FieldOrderBorder(final View wrappedView) {
+        super(wrappedView);
+    }
+
+    @Override
+    public View[] getSubviews() {
+        View[] subviews = super.getSubviews();
+        if (isReversed) {
+            final View[] v = new View[subviews.length];
+            for (int i = 0; i < v.length; i++) {
+                v[i] = subviews[v.length - i - 1];
+            }
+            subviews = v;
+        }
+
+        return subviews;
+    }
+
+    @Override
+    public void viewMenuOptions(final UserActionSet menuOptions) {
+        super.viewMenuOptions(menuOptions);
+
+        menuOptions.add(new UserActionAbstract("Reverse view order") {
+            @Override
+            public void execute(final Workspace workspace, final View view, final Location at) {
+                isReversed = !isReversed;
+                invalidateLayout();
+            }
+        });
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/eb613703/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/border/IconBorder.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/border/IconBorder.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/border/IconBorder.java
new file mode 100644
index 0000000..48f51e6
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/border/IconBorder.java
@@ -0,0 +1,170 @@
+/*
+ *  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.border;
+
+import org.apache.isis.core.commons.debug.DebugBuilder;
+import org.apache.isis.viewer.dnd.drawing.Bounds;
+import org.apache.isis.viewer.dnd.drawing.Canvas;
+import org.apache.isis.viewer.dnd.drawing.ColorsAndFonts;
+import org.apache.isis.viewer.dnd.drawing.Location;
+import org.apache.isis.viewer.dnd.drawing.Size;
+import org.apache.isis.viewer.dnd.drawing.Text;
+import org.apache.isis.viewer.dnd.view.Axes;
+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.Placement;
+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.AbstractBorder;
+import org.apache.isis.viewer.dnd.view.base.IconGraphic;
+import org.apache.isis.viewer.dnd.view.composite.CompositeViewDecorator;
+import org.apache.isis.viewer.dnd.view.text.ObjectTitleText;
+import org.apache.isis.viewer.dnd.view.text.TitleText;
+
+public class IconBorder extends AbstractBorder {
+
+    public static class Factory implements CompositeViewDecorator {
+        private final Text textStyle;
+
+        public Factory() {
+            this(Toolkit.getText(ColorsAndFonts.TEXT_TITLE));
+        }
+
+        public Factory(final Text textStyle) {
+            this.textStyle = textStyle;
+        }
+
+        @Override
+        public View decorate(final View child, final Axes axes) {
+            return new IconBorder(child, textStyle);
+        }
+    }
+
+    private final int baseline;
+    private final int titlebarHeight;
+    private final IconGraphic icon;
+    private final TitleText text;
+
+    public IconBorder(final View wrappedView, final Text style) {
+        this(wrappedView, null, null, style);
+    }
+
+    public IconBorder(final View wrappedView, final TitleText titleText, final IconGraphic iconGraphic, final Text style) {
+        super(wrappedView);
+
+        icon = iconGraphic == null ? new IconGraphic(this, style) : iconGraphic;
+        text = titleText == null ? new ObjectTitleText(this, style) : titleText;
+        titlebarHeight = ViewConstants.VPADDING + icon.getSize().getHeight() + 1;
+
+        top = titlebarHeight + ViewConstants.VPADDING;
+        left = right = ViewConstants.HPADDING;
+        bottom = ViewConstants.VPADDING;
+
+        baseline = ViewConstants.VPADDING + icon.getBaseline() + 1;
+    }
+
+    @Override
+    public void debugDetails(final DebugBuilder debug) {
+        super.debugDetails(debug);
+        debug.appendln("titlebar", top - titlebarHeight);
+    }
+
+    @Override
+    public DragEvent dragStart(final DragStart drag) {
+        if (overBorder(drag.getLocation())) {
+            return Toolkit.getViewFactory().createDragContentOutline(this, drag.getLocation());
+        } else {
+            return super.dragStart(drag);
+        }
+    }
+
+    @Override
+    public void draw(final Canvas canvas) {
+        int x = left - 2;
+
+        if (Toolkit.debug) {
+            canvas.drawDebugOutline(new Bounds(getSize()), baseline, Toolkit.getColor(ColorsAndFonts.COLOR_DEBUG_BOUNDS_DRAW));
+        }
+
+        // icon & title
+        icon.draw(canvas, x, baseline);
+        x += icon.getSize().getWidth();
+        x += ViewConstants.HPADDING;
+        final int maxWidth = getSize().getWidth() - x - right;
+        text.draw(canvas, x, baseline, maxWidth);
+
+        // components
+        super.draw(canvas);
+    }
+
+    @Override
+    public int getBaseline() {
+        return baseline; // wrappedView.getBaseline() + baseline +
+                         // titlebarHeight;
+    }
+
+    @Override
+    public Size getRequiredSize(final Size availableSpace) {
+        final Size size = super.getRequiredSize(availableSpace);
+        size.ensureWidth(left + icon.getSize().getWidth() + ViewConstants.HPADDING + text.getSize().getWidth() + right);
+        return size;
+    }
+
+    @Override
+    public void firstClick(final Click click) {
+        final int y = click.getLocation().getY();
+        if (y < top && click.button2()) {
+            final Location location = new Location(click.getLocationWithinViewer());
+            getViewManager().showInOverlay(getContent(), location);
+        } else {
+            super.firstClick(click);
+        }
+    }
+
+    @Override
+    public void secondClick(final Click click) {
+        final int y = click.getLocation().getY();
+        if (y < top) {
+            getWorkspace().addWindowFor(getContent().getAdapter(), new Placement(this));
+        } else {
+            super.secondClick(click);
+        }
+    }
+
+    @Override
+    public ViewAreaType viewAreaType(final Location mouseLocation) {
+        final Bounds title = new Bounds(new Location(), icon.getSize());
+        title.extendWidth(left);
+        title.extendWidth(text.getSize().getWidth());
+        if (title.contains(mouseLocation)) {
+            return ViewAreaType.CONTENT;
+        } else {
+            return super.viewAreaType(mouseLocation);
+        }
+    }
+
+    @Override
+    public String toString() {
+        return wrappedView.toString() + "/IconBorder [" + getSpecification() + "]";
+    }
+}