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

[47/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/interaction/DragStartImpl.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/interaction/DragStartImpl.java b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/interaction/DragStartImpl.java
new file mode 100644
index 0000000..e825fc2
--- /dev/null
+++ b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/interaction/DragStartImpl.java
@@ -0,0 +1,61 @@
+/*
+ *  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.interaction;
+
+import org.apache.isis.core.commons.lang.ToString;
+import org.apache.isis.viewer.dnd.drawing.Location;
+import org.apache.isis.viewer.dnd.drawing.Offset;
+import org.apache.isis.viewer.dnd.view.DragStart;
+
+public class DragStartImpl extends PointerEvent implements DragStart {
+    private final Location location;
+
+    public DragStartImpl(final Location location, final int mods) {
+        super(mods);
+        this.location = location;
+    }
+
+    @Override
+    public Location getLocation() {
+        return location;
+    }
+
+    @Override
+    public void subtract(final Location location) {
+        this.location.subtract(location);
+    }
+
+    @Override
+    public void subtract(final int x, final int y) {
+        location.subtract(x, y);
+    }
+
+    public void add(final Offset offset) {
+        location.add(offset.getDeltaX(), offset.getDeltaY());
+    }
+
+    @Override
+    public String toString() {
+        final ToString str = new ToString(this);
+        str.append("location", location);
+        str.append("buttons", super.toString());
+        return str.toString();
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/255ef514/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/interaction/KeyboardActionImpl.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/interaction/KeyboardActionImpl.java b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/interaction/KeyboardActionImpl.java
new file mode 100644
index 0000000..7fcab94
--- /dev/null
+++ b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/interaction/KeyboardActionImpl.java
@@ -0,0 +1,61 @@
+/*
+ *  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.interaction;
+
+import org.apache.isis.viewer.dnd.view.KeyboardAction;
+
+public class KeyboardActionImpl implements KeyboardAction {
+
+    final int keyCode;
+    final int modifiers;
+    private boolean isConsumed;
+
+    public KeyboardActionImpl(final int keyCode, final int modifiers) {
+        this.keyCode = keyCode;
+        this.modifiers = modifiers;
+        isConsumed = false;
+    }
+
+    @Override
+    public int getKeyCode() {
+        return keyCode;
+    }
+
+    @Override
+    public char getKeyChar() {
+        return (char) keyCode;
+    }
+
+    @Override
+    public int getModifiers() {
+        return modifiers;
+    }
+
+    @Override
+    public boolean isConsumed() {
+        return isConsumed;
+    }
+
+    @Override
+    public void consume() {
+        isConsumed = true;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/255ef514/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/interaction/PointerEvent.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/interaction/PointerEvent.java b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/interaction/PointerEvent.java
new file mode 100644
index 0000000..8367021
--- /dev/null
+++ b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/interaction/PointerEvent.java
@@ -0,0 +1,108 @@
+/*
+ *  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.interaction;
+
+import java.awt.event.InputEvent;
+
+/**
+ * Details an event involving the pointer, such as a click or drag.
+ */
+public abstract class PointerEvent {
+    protected int mods;
+
+    /**
+     * Creates a new pointer event object.
+     * 
+     * @param mods
+     *            the button and key modifiers (@see java.awt.event.MouseEvent)
+     */
+    PointerEvent(final int mods) {
+        this.mods = mods;
+    }
+
+    public boolean button1() {
+        return (isButton1() && !isShift()) || (isButton2() && isShift());
+    }
+
+    public boolean button2() {
+        return (isButton2() && !isShift()) || (isButton1() && isShift());
+    }
+
+    public boolean button3() {
+        return isButton3();
+    }
+
+    /**
+     * Returns true if the 'Alt' key is depressed
+     */
+    public boolean isAlt() {
+        return (mods & InputEvent.ALT_MASK) > 0;
+    }
+
+    /**
+     * Returns true if the left-hand button on the mouse is depressed
+     */
+    private boolean isButton1() {
+        return (mods & InputEvent.BUTTON1_MASK) > 0;
+    }
+
+    /**
+     * Returns true if the middle button on the mouse is depressed
+     */
+    private boolean isButton2() {
+        return (mods & InputEvent.BUTTON2_MASK) > 0;
+    }
+
+    /**
+     * Returns true if the right-hand button on the mouse is depressed
+     */
+    private boolean isButton3() {
+        return (mods & InputEvent.BUTTON3_MASK) > 0;
+    }
+
+    /**
+     * Returns true if the control key is depressed
+     */
+    public boolean isCtrl() {
+        return (mods & InputEvent.CTRL_MASK) > 0;
+    }
+
+    /**
+     * Returns true if the 'Alt' key is depressed
+     */
+    public boolean isMeta() {
+        return (mods & InputEvent.META_MASK) > 0;
+    }
+
+    /**
+     * Returns true if the shift key is depressed
+     */
+    public boolean isShift() {
+        return (mods & InputEvent.SHIFT_MASK) > 0;
+    }
+
+    @Override
+    public String toString() {
+        final String buttons = (isButton1() ? "^" : "-") + (isButton2() ? "^" : "-") + (isButton3() ? "^" : "-");
+        final String modifiers = (isShift() ? "S" : "-") + (isAlt() ? "A" : "-") + (isCtrl() ? "C" : "-");
+
+        return "buttons=" + buttons + ",modifiers=" + modifiers;
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/255ef514/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/interaction/SimpleInternalDrag.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/interaction/SimpleInternalDrag.java b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/interaction/SimpleInternalDrag.java
new file mode 100644
index 0000000..3a3e418
--- /dev/null
+++ b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/interaction/SimpleInternalDrag.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.interaction;
+
+import org.apache.isis.core.commons.lang.ToString;
+import org.apache.isis.viewer.dnd.drawing.Location;
+import org.apache.isis.viewer.dnd.drawing.Offset;
+import org.apache.isis.viewer.dnd.drawing.Padding;
+import org.apache.isis.viewer.dnd.view.InternalDrag;
+import org.apache.isis.viewer.dnd.view.View;
+import org.apache.isis.viewer.dnd.view.Viewer;
+
+public class SimpleInternalDrag extends DragImpl implements InternalDrag {
+    private final Location location;
+    // TODO replace Location with Offset
+    private final Location offset;
+    private final View view;
+
+    /**
+     * Creates a new drag event. The source view has its pickup(), and then,
+     * exited() methods called on it. The view returned by the pickup method
+     * becomes this event overlay view, which is moved continuously so that it
+     * tracks the pointer,
+     * 
+     * @param view
+     *            the view over which the pointer was when this event started
+     * @param location
+     *            the location within the viewer (the Frame/Applet/Window etc)
+     * 
+     *            TODO combine the two constructors
+     */
+    public SimpleInternalDrag(final View view, final Location location) {
+        this.view = view;
+
+        this.location = new Location(location);
+        offset = view.getAbsoluteLocation();
+
+        final Padding targetPadding = view.getPadding();
+        final Padding containerPadding = view.getView().getPadding();
+        offset.add(containerPadding.getLeft() - targetPadding.getLeft(), containerPadding.getTop() - targetPadding.getTop());
+
+        this.location.subtract(offset);
+    }
+
+    public SimpleInternalDrag(final View view, final Offset off) {
+        this.view = view;
+
+        location = new Location();
+
+        offset = new Location(off.getDeltaX(), off.getDeltaY());
+
+        final Padding targetPadding = view.getPadding();
+        final Padding containerPadding = view.getView().getPadding();
+        offset.add(containerPadding.getLeft() - targetPadding.getLeft(), containerPadding.getTop() - targetPadding.getTop());
+
+        this.location.subtract(offset);
+    }
+
+    @Override
+    public void cancel(final Viewer viewer) {
+        view.dragCancel(this);
+    }
+
+    @Override
+    public void drag(final View target, final Location location, final int mods) {
+        this.location.setX(location.getX());
+        this.location.setY(location.getY());
+        this.location.subtract(offset);
+        view.drag(this);
+    }
+
+    @Override
+    public void end(final Viewer viewer) {
+        view.dragTo(this);
+    }
+
+    /**
+     * Gets the location of the pointer relative to the view.
+     */
+    @Override
+    public Location getLocation() {
+        return new Location(location);
+    }
+
+    @Override
+    public View getOverlay() {
+        return null;
+    }
+
+    @Override
+    public String toString() {
+        final ToString s = new ToString(this, super.toString());
+        s.append("location", location);
+        s.append("relative", getLocation());
+        return s.toString();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/255ef514/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/interaction/ViewDragImpl.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/interaction/ViewDragImpl.java b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/interaction/ViewDragImpl.java
new file mode 100644
index 0000000..9bf31e8
--- /dev/null
+++ b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/interaction/ViewDragImpl.java
@@ -0,0 +1,145 @@
+/*
+ *  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.interaction;
+
+import org.apache.isis.viewer.dnd.drawing.Location;
+import org.apache.isis.viewer.dnd.drawing.Offset;
+import org.apache.isis.viewer.dnd.view.View;
+import org.apache.isis.viewer.dnd.view.ViewDrag;
+import org.apache.isis.viewer.dnd.view.Viewer;
+import org.apache.isis.viewer.dnd.view.Workspace;
+
+public class ViewDragImpl extends DragImpl implements ViewDrag {
+    private Location location;
+    /**
+     * Offset from the view's top-left corner to the pointer (relative to the
+     * view).
+     */
+    private final Offset overlayOffset;
+    private final View sourceView;
+    private final View overlayView;
+    private View targetView;
+    private final Workspace viewsWorkspace;
+
+    /**
+     * Creates a new drag event. The source view has its pickup(), and then,
+     * exited() methods called on it. The view returned by the pickup method
+     * becomes this event overlay view, which is moved continuously so that it
+     * tracks the pointer.
+     * 
+     * @param view
+     *            the view over which the pointer was when this event started
+     */
+    public ViewDragImpl(final View view, final Offset offset, final View dragView) {
+        this.sourceView = view;
+        this.overlayView = dragView;
+        this.overlayOffset = offset;
+
+        viewsWorkspace = view.getWorkspace();
+    }
+
+    /**
+     * getView().getAbsoluteLocation().getX(),
+     * -getView().getAbsoluteLocation().getY() Cancel drag by changing cursor
+     * back to pointer.
+     */
+    @Override
+    public void cancel(final Viewer viewer) {
+        getSourceView().getFeedbackManager().showDefaultCursor();
+    }
+
+    /**
+     * Moves the overlay view so it follows the pointer
+     */
+    protected void drag(final Viewer viewer) {
+        if (overlayView != null) {
+            overlayView.markDamaged();
+            updateDraggingLocation();
+            overlayView.markDamaged();
+        }
+    }
+
+    @Override
+    public void drag(final View target, final Location location, final int mods) {
+        this.location = location;
+        if (overlayView != null) {
+            overlayView.markDamaged();
+            updateDraggingLocation();
+            // this.location.subtract(target.getAbsoluteLocation());
+            viewsWorkspace.getViewManager().getSpy().addTrace(target, "   over", getLocation());
+            targetView = target;
+            target.drag(this);
+            overlayView.markDamaged();
+        }
+    }
+
+    /**
+     * Ends the drag by calling drop() on the workspace.
+     */
+    @Override
+    public void end(final Viewer viewer) {
+        viewer.clearAction();
+        targetView.drop(this);
+    }
+
+    @Override
+    public View getOverlay() {
+        return overlayView;
+    }
+
+    @Override
+    public Location getLocation() {
+        return location;
+    }
+
+    @Override
+    public View getSourceView() {
+        return sourceView;
+    }
+
+    @Override
+    public Location getViewDropLocation() {
+        final Location viewLocation = new Location(location);
+        viewLocation.subtract(overlayOffset);
+        return viewLocation;
+    }
+
+    public void subtract(final Location location) {
+        location.subtract(location);
+    }
+
+    @Override
+    public String toString() {
+        return "ViewDrag [" + super.toString() + "]";
+    }
+
+    private void updateDraggingLocation() {
+        final Location viewLocation = new Location(location);
+        viewLocation.subtract(overlayOffset);
+        overlayView.setLocation(viewLocation);
+        overlayView.limitBoundsWithin(viewsWorkspace.getSize());
+    }
+
+    @Override
+    public void subtract(final int x, final int y) {
+        location.subtract(x, y);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/255ef514/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/list/InternalCollectionBorder.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/list/InternalCollectionBorder.java b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/list/InternalCollectionBorder.java
new file mode 100644
index 0000000..9ea2cc5
--- /dev/null
+++ b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/list/InternalCollectionBorder.java
@@ -0,0 +1,128 @@
+/*
+ *  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.list;
+
+import org.apache.isis.core.commons.debug.DebugBuilder;
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.consent.Consent;
+import org.apache.isis.core.metamodel.facets.collections.modify.CollectionFacet;
+import org.apache.isis.core.metamodel.facets.collections.modify.CollectionFacetUtils;
+import org.apache.isis.core.metamodel.spec.feature.OneToManyAssociation;
+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.Axes;
+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.ViewState;
+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.field.OneToManyField;
+
+public class InternalCollectionBorder extends AbstractBorder {
+    public static class Factory implements CompositeViewDecorator {
+        @Override
+        public View decorate(final View child, final Axes axes) {
+            return new InternalCollectionBorder(child);
+        }
+    }
+
+    private final IconGraphic icon;
+
+    protected InternalCollectionBorder(final View wrappedView) {
+        super(wrappedView);
+
+        icon = new InternalCollectionIconGraphic(this, Toolkit.getText(ColorsAndFonts.TEXT_NORMAL));
+        left = icon.getSize().getWidth();
+    }
+
+    @Override
+    protected void debugDetails(final DebugBuilder debug) {
+        debug.append("InternalCollectionBorder ");
+    }
+
+    @Override
+    public Size getRequiredSize(final Size maximumSize) {
+        final Size size = super.getRequiredSize(maximumSize);
+        size.ensureWidth(left + 45 + right);
+        size.ensureHeight(24);
+        return size;
+    }
+
+    @Override
+    public void draw(final Canvas canvas) {
+        icon.draw(canvas, 0, getBaseline());
+
+        final ObjectAdapter collection = getContent().getAdapter();
+        final CollectionFacet facet = CollectionFacetUtils.getCollectionFacetFromSpec(collection);
+        final ViewState state = getState();
+        final Color color;
+        if (state.canDrop()) {
+            color = Toolkit.getColor(ColorsAndFonts.COLOR_VALID);
+        } else if (state.cantDrop()) {
+            color = Toolkit.getColor(ColorsAndFonts.COLOR_INVALID);
+        } else {
+            color = Toolkit.getColor(ColorsAndFonts.COLOR_SECONDARY2);
+        }
+        if (collection == null || facet.size(collection) == 0) {
+            canvas.drawText("empty", left, getBaseline(), color, Toolkit.getText(ColorsAndFonts.TEXT_NORMAL));
+        } else {
+            final int x = icon.getSize().getWidth() / 2;
+            final int x2 = x + 4;
+            final int y = icon.getSize().getHeight() + 1;
+            final int y2 = getSize().getHeight() - 5;
+            canvas.drawLine(x, y, x, y2, color);
+            canvas.drawLine(x, y2, x2, y2, color);
+        }
+        super.draw(canvas);
+    }
+
+    @Override
+    public void contentMenuOptions(final UserActionSet options) {
+        super.contentMenuOptions(options);
+        // final ObjectSpecification specification = ((OneToManyField)
+        // getContent()).getSpecification();
+        // OptionFactory.addCreateOptions(specification, options);
+    }
+
+    @Override
+    public void objectActionResult(final ObjectAdapter result, final Placement placement) {
+        // same as in TreeNodeBorder
+        final OneToManyField internalCollectionContent = (OneToManyField) getContent();
+        final OneToManyAssociation field = internalCollectionContent.getOneToManyAssociation();
+        final ObjectAdapter target = ((ObjectContent) getParent().getContent()).getObject();
+
+        final Consent valid = field.isValidToAdd(target, result);
+        if (valid.isAllowed()) {
+            field.addElement(target, result);
+        }
+        super.objectActionResult(result, placement);
+    }
+
+    @Override
+    public String toString() {
+        return "InternalCollectionBorder/" + wrappedView;
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/255ef514/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/list/InternalCollectionIconGraphic.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/list/InternalCollectionIconGraphic.java b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/list/InternalCollectionIconGraphic.java
new file mode 100644
index 0000000..737ace8
--- /dev/null
+++ b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/list/InternalCollectionIconGraphic.java
@@ -0,0 +1,39 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.isis.viewer.dnd.list;
+
+import org.apache.isis.viewer.dnd.drawing.Text;
+import org.apache.isis.viewer.dnd.view.View;
+import org.apache.isis.viewer.dnd.view.base.IconGraphic;
+
+public class InternalCollectionIconGraphic extends IconGraphic {
+
+    public InternalCollectionIconGraphic(final View view, final Text text) {
+        super(view, text);
+    }
+
+    /*
+     * protected Image iconPicture(ObjectAdapter object) { final
+     * InternalCollection cls = (InternalCollection) object; final
+     * ObjectSpecification spec = cls.getElementSpecification(); Image icon =
+     * loadIcon(spec, ""); if(icon == null) { icon = loadIcon(spec, ""); }
+     * return icon; }
+     */
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/255ef514/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/list/InternalListSpecification.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/list/InternalListSpecification.java b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/list/InternalListSpecification.java
new file mode 100644
index 0000000..77f4229
--- /dev/null
+++ b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/list/InternalListSpecification.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.list;
+
+import org.apache.isis.viewer.dnd.view.ViewRequirement;
+
+public class InternalListSpecification extends SimpleListSpecification {
+
+    public InternalListSpecification() {
+        addViewDecorator(new InternalCollectionBorder.Factory());
+    }
+
+    @Override
+    public boolean canDisplay(final ViewRequirement requirement) {
+        return super.canDisplay(requirement) && requirement.is(ViewRequirement.SUBVIEW);
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/255ef514/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/list/SimpleListSpecification.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/list/SimpleListSpecification.java b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/list/SimpleListSpecification.java
new file mode 100644
index 0000000..b3d0bef
--- /dev/null
+++ b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/list/SimpleListSpecification.java
@@ -0,0 +1,39 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.isis.viewer.dnd.list;
+
+import org.apache.isis.viewer.dnd.icon.IconElementFactory;
+import org.apache.isis.viewer.dnd.view.ViewFactory;
+import org.apache.isis.viewer.dnd.view.composite.AbstractCollectionViewSpecification;
+
+// TODO use the superclass instead
+public class SimpleListSpecification extends AbstractCollectionViewSpecification {
+
+    @Override
+    protected ViewFactory createElementFactory() {
+        return new IconElementFactory();
+    }
+
+    @Override
+    public String getName() {
+        return "List";
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/255ef514/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/service/PerspectiveContent.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/service/PerspectiveContent.java b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/service/PerspectiveContent.java
new file mode 100644
index 0000000..47ad1aa
--- /dev/null
+++ b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/service/PerspectiveContent.java
@@ -0,0 +1,130 @@
+/*
+ *  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.service;
+
+import org.apache.isis.core.commons.debug.DebugBuilder;
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.consent.Consent;
+import org.apache.isis.core.metamodel.consent.Veto;
+import org.apache.isis.core.metamodel.spec.ObjectSpecification;
+import org.apache.isis.core.runtime.userprofile.PerspectiveEntry;
+import org.apache.isis.viewer.dnd.drawing.Image;
+import org.apache.isis.viewer.dnd.view.Content;
+import org.apache.isis.viewer.dnd.view.content.AbstractContent;
+
+public class PerspectiveContent extends AbstractContent {
+    private final PerspectiveEntry perspective;
+
+    public PerspectiveContent(final PerspectiveEntry perspective) {
+        this.perspective = perspective;
+    }
+
+    @Override
+    public void debugDetails(final DebugBuilder debug) {
+        debug.appendln("perspective", perspective);
+    }
+
+    @Override
+    public ObjectAdapter getAdapter() {
+        return null;
+    }
+
+    @Override
+    public String getDescription() {
+        return null;
+    }
+
+    @Override
+    public String getHelp() {
+        return "";
+    }
+
+    @Override
+    public String getId() {
+        return "";
+    }
+
+    @Override
+    public ObjectAdapter[] getOptions() {
+        return null;
+    }
+
+    @Override
+    public ObjectSpecification getSpecification() {
+        return null;
+    }
+
+    @Override
+    public boolean isObject() {
+        return false;
+    }
+
+    @Override
+    public boolean isOptionEnabled() {
+        return false;
+    }
+
+    @Override
+    public boolean isTransient() {
+        return false;
+    }
+
+    @Override
+    public String title() {
+        return perspective.getTitle();
+    }
+
+    @Override
+    public String toString() {
+        return "Perspective: " + perspective;
+    }
+
+    @Override
+    public String windowTitle() {
+        return perspective.getTitle();
+    }
+
+    @Override
+    public Consent canDrop(final Content sourceContent) {
+        return Veto.DEFAULT;
+    }
+
+    @Override
+    public ObjectAdapter drop(final Content sourceContent) {
+        return null;
+    }
+
+    @Override
+    public String getIconName() {
+        return "icon";
+    }
+
+    @Override
+    public Image getIconPicture(final int iconHeight) {
+        return null;
+    }
+
+    public void parseTextEntry(final String entryText) {
+    }
+
+    public PerspectiveEntry getPerspective() {
+        return perspective;
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/255ef514/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/service/ServiceBorder.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/service/ServiceBorder.java b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/service/ServiceBorder.java
new file mode 100644
index 0000000..2b8f750
--- /dev/null
+++ b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/service/ServiceBorder.java
@@ -0,0 +1,110 @@
+/*
+ *  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.service;
+
+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.Click;
+import org.apache.isis.viewer.dnd.view.Toolkit;
+import org.apache.isis.viewer.dnd.view.View;
+import org.apache.isis.viewer.dnd.view.ViewState;
+import org.apache.isis.viewer.dnd.view.base.AbstractBorder;
+
+public class ServiceBorder extends AbstractBorder {
+    private static final int BORDER = 13;
+
+    public ServiceBorder(final int size, final View wrappedView) {
+        super(wrappedView);
+
+        top = size;
+        left = size;
+        bottom = size;
+        right = size + BORDER;
+    }
+
+    public ServiceBorder(final View wrappedView) {
+        this(1, wrappedView);
+    }
+
+    @Override
+    protected void debugDetails(final DebugBuilder debug) {
+        debug.append("ServiceBorder " + top + " pixels");
+    }
+
+    @Override
+    public void draw(final Canvas canvas) {
+        super.draw(canvas);
+
+        Color color = null;
+        final ViewState state = getState();
+        final boolean hasFocus = getViewManager().hasFocus(getView());
+        if (hasFocus) {
+            color = Toolkit.getColor(ColorsAndFonts.COLOR_IDENTIFIED);
+        } else if (state.isObjectIdentified()) {
+            color = Toolkit.getColor(ColorsAndFonts.COLOR_SECONDARY2);
+        }
+
+        final Size s = getSize();
+        if (color != null) {
+            if (hasFocus) {
+                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);
+                }
+            } else {
+                final int xExtent = s.getWidth();
+                for (int i = 0; i < left; i++) {
+                    canvas.drawRectangle(i, i, xExtent - 2 * i, s.getHeight() - 2 * i, color);
+                }
+                canvas.drawLine(xExtent - BORDER, left, xExtent - BORDER, left + s.getHeight(), color);
+                canvas.drawSolidRectangle(xExtent - BORDER + 1, left, BORDER - 2, s.getHeight() - 2 * left, Toolkit.getColor(ColorsAndFonts.COLOR_SECONDARY3));
+            }
+        }
+    }
+
+    @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) {
+        // ignore - prevents the super class opening a view
+    }
+
+    @Override
+    public String toString() {
+        return wrappedView.toString() + "/ServiceBorder [" + getSpecification() + "]";
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/255ef514/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/service/ServiceIcon.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/service/ServiceIcon.java b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/service/ServiceIcon.java
new file mode 100644
index 0000000..3e30ff1
--- /dev/null
+++ b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/service/ServiceIcon.java
@@ -0,0 +1,86 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.isis.viewer.dnd.service;
+
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.runtime.userprofile.PerspectiveEntry;
+import org.apache.isis.runtimes.dflt.runtime.system.context.IsisContext;
+import org.apache.isis.viewer.dnd.drawing.ColorsAndFonts;
+import org.apache.isis.viewer.dnd.drawing.Location;
+import org.apache.isis.viewer.dnd.icon.Icon;
+import org.apache.isis.viewer.dnd.util.Properties;
+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.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.action.OptionFactory;
+import org.apache.isis.viewer.dnd.view.base.IconGraphic;
+import org.apache.isis.viewer.dnd.view.option.CloseViewOption;
+import org.apache.isis.viewer.dnd.view.text.ObjectTitleText;
+
+public class ServiceIcon extends Icon {
+    private final static int ICON_SIZE;
+    private final static int LARGE_ICON_SIZE = 34;
+    private final static String LARGE_ICON_SIZE_PROPERTY;
+
+    static {
+        LARGE_ICON_SIZE_PROPERTY = Properties.PROPERTY_BASE + "large-icon-size";
+        ICON_SIZE = IsisContext.getConfiguration().getInteger(LARGE_ICON_SIZE_PROPERTY, LARGE_ICON_SIZE);
+    }
+
+    public ServiceIcon(final Content content, final ViewSpecification specification) {
+        super(content, specification);
+        setTitle(new ObjectTitleText(this, Toolkit.getText(ColorsAndFonts.TEXT_ICON)));
+        setSelectedGraphic(new IconGraphic(this, ICON_SIZE));
+        setVertical(true);
+    }
+
+    @Override
+    public void contentMenuOptions(final UserActionSet options) {
+        options.setColor(Toolkit.getColor(ColorsAndFonts.COLOR_MENU_CONTENT));
+        OptionFactory.addObjectMenuOptions(getContent().getAdapter(), options);
+    }
+
+    @Override
+    public void viewMenuOptions(final UserActionSet options) {
+        options.setColor(Toolkit.getColor(ColorsAndFonts.COLOR_MENU_VIEW));
+
+        options.add(new CloseViewOption() {
+            @Override
+            public void execute(final Workspace workspace, final View view, final Location at) {
+                final PerspectiveContent parent = (PerspectiveContent) view.getParent().getContent();
+                final PerspectiveEntry perspective = parent.getPerspective();
+                final ServiceObject serviceContent = (ServiceObject) view.getContent();
+                final ObjectAdapter element = serviceContent.getObject();
+                perspective.removeFromServices(element);
+                super.execute(workspace, view, at);
+            }
+        });
+    }
+
+    @Override
+    public DragEvent dragStart(final DragStart drag) {
+        return Toolkit.getViewFactory().createDragContentOutline(this, drag.getLocation());
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/255ef514/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/service/ServiceIconSpecification.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/service/ServiceIconSpecification.java b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/service/ServiceIconSpecification.java
new file mode 100644
index 0000000..ebb4d5a
--- /dev/null
+++ b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/service/ServiceIconSpecification.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.service;
+
+import org.apache.isis.viewer.dnd.icon.Icon;
+import org.apache.isis.viewer.dnd.view.Axes;
+import org.apache.isis.viewer.dnd.view.Content;
+import org.apache.isis.viewer.dnd.view.View;
+import org.apache.isis.viewer.dnd.view.ViewRequirement;
+import org.apache.isis.viewer.dnd.view.ViewSpecification;
+
+public class ServiceIconSpecification implements ViewSpecification {
+
+    @Override
+    public boolean canDisplay(final ViewRequirement requirement) {
+        return requirement.isFor(ServiceObject.class) && requirement.getSpecification().isService();
+    }
+
+    @Override
+    public View createView(final Content content, final Axes axes, final int sequence) {
+        final Icon icon = new ServiceIcon(content, this);
+        return new ServiceBorder(icon);
+    }
+
+    @Override
+    public String getName() {
+        return "Service Icon";
+    }
+
+    @Override
+    public boolean isAligned() {
+        return false;
+    }
+
+    @Override
+    public boolean isOpen() {
+        return false;
+    }
+
+    @Override
+    public boolean isReplaceable() {
+        return false;
+    }
+
+    @Override
+    public boolean isResizeable() {
+        return false;
+    }
+
+    @Override
+    public boolean isSubView() {
+        return false;
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/255ef514/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/service/ServiceObject.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/service/ServiceObject.java b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/service/ServiceObject.java
new file mode 100644
index 0000000..4bba0af
--- /dev/null
+++ b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/service/ServiceObject.java
@@ -0,0 +1,181 @@
+/*
+ *  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.service;
+
+import java.util.Arrays;
+
+import org.apache.isis.core.commons.debug.DebugBuilder;
+import org.apache.isis.core.commons.exceptions.IsisException;
+import org.apache.isis.core.commons.exceptions.UnexpectedCallException;
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.consent.Consent;
+import org.apache.isis.core.metamodel.consent.Veto;
+import org.apache.isis.core.metamodel.spec.ActionType;
+import org.apache.isis.core.metamodel.spec.ObjectSpecification;
+import org.apache.isis.core.metamodel.spec.feature.ObjectAction;
+import org.apache.isis.viewer.dnd.drawing.Image;
+import org.apache.isis.viewer.dnd.drawing.ImageFactory;
+import org.apache.isis.viewer.dnd.view.Content;
+import org.apache.isis.viewer.dnd.view.UserActionSet;
+import org.apache.isis.viewer.dnd.view.content.AbstractContent;
+
+public class ServiceObject extends AbstractContent {
+    private final ObjectAdapter adapter;
+
+    public ServiceObject(final ObjectAdapter adapter) {
+        this.adapter = adapter;
+    }
+
+    public Consent canClear() {
+        return Veto.DEFAULT;
+    }
+
+    public Consent canSet(final ObjectAdapter dragSource) {
+        return Veto.DEFAULT;
+    }
+
+    public void clear() {
+        throw new IsisException("Invalid call");
+    }
+
+    @Override
+    public void debugDetails(final DebugBuilder debug) {
+        debug.appendln("service", adapter);
+    }
+
+    @Override
+    public ObjectAdapter getAdapter() {
+        return adapter;
+    }
+
+    @Override
+    public String getDescription() {
+        final String specName = getSpecification().getSingularName();
+        final String objectTitle = getObject().titleString();
+        return specName + (specName.equalsIgnoreCase(objectTitle) ? "" : ": " + objectTitle) + " " + getSpecification().getDescription();
+    }
+
+    @Override
+    public String getHelp() {
+        return "";
+    }
+
+    @Override
+    public String getId() {
+        return "";
+    }
+
+    public ObjectAdapter getObject() {
+        return adapter;
+    }
+
+    @Override
+    public ObjectAdapter[] getOptions() {
+        return null;
+    }
+
+    @Override
+    public ObjectSpecification getSpecification() {
+        return adapter.getSpecification();
+    }
+
+    @Override
+    public boolean isObject() {
+        return false;
+    }
+
+    @Override
+    public boolean isOptionEnabled() {
+        return false;
+    }
+
+    @Override
+    public boolean isTransient() {
+        return adapter != null && adapter.isTransient();
+    }
+
+    public void setObject(final ObjectAdapter object) {
+        throw new IsisException("Invalid call");
+    }
+
+    @Override
+    public String title() {
+        return adapter.titleString();
+    }
+
+    @Override
+    public String toString() {
+        return "Service Object [" + adapter + "]";
+    }
+
+    @Override
+    public String windowTitle() {
+        return (isTransient() ? "UNSAVED " : "") + getSpecification().getSingularName();
+    }
+
+    @Override
+    public Consent canDrop(final Content sourceContent) {
+        final ObjectAction action = actionFor(sourceContent);
+        if (action == null) {
+            return Veto.DEFAULT;
+        } else {
+            final ObjectAdapter source = sourceContent.getAdapter();
+            final Consent parameterSetValid = action.isProposedArgumentSetValid(adapter, new ObjectAdapter[] { source });
+            parameterSetValid.setDescription("Execute '" + action.getName() + "' with " + source.titleString());
+            return parameterSetValid;
+        }
+    }
+
+    private ObjectAction actionFor(final Content sourceContent) {
+        ObjectAction action;
+        action = adapter.getSpecification().getObjectAction(ActionType.USER, null, Arrays.asList(sourceContent.getSpecification()));
+        return action;
+    }
+
+    @Override
+    public ObjectAdapter drop(final Content sourceContent) {
+        final ObjectAction action = actionFor(sourceContent);
+        final ObjectAdapter source = sourceContent.getAdapter();
+        return action.execute(adapter, new ObjectAdapter[] { source });
+    }
+
+    @Override
+    public String getIconName() {
+        final ObjectAdapter object = getObject();
+        return object == null ? null : object.getIconName();
+    }
+
+    @Override
+    public Image getIconPicture(final int iconHeight) {
+        final ObjectAdapter adapter = getObject();
+        final ObjectSpecification specification = adapter.getSpecification();
+        final Image icon = ImageFactory.getInstance().loadIcon(specification, iconHeight, null);
+        return icon;
+    }
+
+    public void parseTextEntry(final String entryText) {
+        throw new UnexpectedCallException();
+    }
+
+    @Override
+    public void viewMenuOptions(final UserActionSet options) {
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/255ef514/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/table/AbstractTableSpecification.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/table/AbstractTableSpecification.java b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/table/AbstractTableSpecification.java
new file mode 100644
index 0000000..09b49d4
--- /dev/null
+++ b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/table/AbstractTableSpecification.java
@@ -0,0 +1,114 @@
+/*
+ *  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.List;
+
+import org.apache.isis.core.metamodel.spec.ObjectSpecification;
+import org.apache.isis.core.metamodel.spec.feature.ObjectAssociation;
+import org.apache.isis.core.metamodel.spec.feature.ObjectAssociationFilters;
+import org.apache.isis.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.ViewFactory;
+import org.apache.isis.viewer.dnd.view.ViewRequirement;
+import org.apache.isis.viewer.dnd.view.base.Layout;
+import org.apache.isis.viewer.dnd.view.collection.CollectionContent;
+import org.apache.isis.viewer.dnd.view.composite.CollectionElementBuilder;
+import org.apache.isis.viewer.dnd.view.composite.CompositeViewDecorator;
+import org.apache.isis.viewer.dnd.view.composite.CompositeViewSpecification;
+import org.apache.isis.viewer.dnd.view.composite.StackLayout;
+
+public abstract class AbstractTableSpecification extends CompositeViewSpecification {
+
+    public AbstractTableSpecification() {
+        builder = new CollectionElementBuilder(new ViewFactory() {
+            TableRowSpecification rowSpecification = new TableRowSpecification();
+
+            // TODO do directly without specification
+            @Override
+            public View createView(final Content content, final Axes axes, final int sequence) {
+                // ViewSpecification rowSpecification = new
+                // SubviewIconSpecification();
+                return rowSpecification.createView(content, axes, -1);
+            }
+        });
+
+        addSubviewDecorator(new TableRowBorder.Factory());
+
+        addViewDecorator(new CompositeViewDecorator() {
+            @Override
+            public View decorate(final View view, final Axes axes) {
+                axes.getAxis(TableAxis.class).setRoot(view);
+                return view;
+            }
+        });
+    }
+
+    @Override
+    public Layout createLayout(final Content content, final Axes axes) {
+        return new StackLayout();
+    }
+
+    @Override
+    public void createAxes(final Content content, final Axes axes) {
+        axes.add(new TableAxisImpl((CollectionContent) content), TableAxis.class);
+    }
+
+    @Override
+    public boolean canDisplay(final ViewRequirement requirement) {
+        if (!requirement.isCollection() || !requirement.isOpen()) {
+            return false;
+        } else {
+            final CollectionContent collectionContent = (CollectionContent) requirement.getContent();
+            final ObjectSpecification elementSpecification = collectionContent.getElementSpecification();
+            final List<ObjectAssociation> fields = elementSpecification.getAssociations(ObjectAssociationFilters.WHEN_VISIBLE_IRRESPECTIVE_OF_WHERE);
+            for (int i = 0; i < fields.size(); i++) {
+                if (fields.get(i).isOneToOneAssociation()) {
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+
+    /*
+     * protected View decorateView(View view) { TableAxis tableAxis =
+     * (TableAxis) view.getViewAxisForChildren(); tableAxis.setRoot(view);
+     * return view; // return doCreateView(table, content, axis); } protected
+     * abstract View doCreateView(final View table, final Content content, final
+     * ViewAxis axis);
+     */
+
+    @Override
+    public String getName() {
+        return "Standard Table";
+    }
+
+    @Override
+    public boolean isReplaceable() {
+        return false;
+    }
+
+    @Override
+    public boolean isResizeable() {
+        return true;
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/255ef514/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/table/ColumnWidthStrategy.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/table/ColumnWidthStrategy.java b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/table/ColumnWidthStrategy.java
new file mode 100644
index 0000000..ddfe959
--- /dev/null
+++ b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/table/ColumnWidthStrategy.java
@@ -0,0 +1,30 @@
+/*
+ *  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.metamodel.spec.feature.ObjectAssociation;
+
+public interface ColumnWidthStrategy {
+    int getMinimumWidth(int i, ObjectAssociation specification);
+
+    int getPreferredWidth(int i, ObjectAssociation specification);
+
+    int getMaximumWidth(int i, ObjectAssociation specification);
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/255ef514/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/table/DefaultColumnWidthStrategy.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/table/DefaultColumnWidthStrategy.java b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/table/DefaultColumnWidthStrategy.java
new file mode 100644
index 0000000..5c5c4b5
--- /dev/null
+++ b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/table/DefaultColumnWidthStrategy.java
@@ -0,0 +1,60 @@
+/*
+ *  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.metamodel.spec.feature.ObjectAssociation;
+
+public class DefaultColumnWidthStrategy implements ColumnWidthStrategy {
+
+    private final int minimum;
+    private final int preferred;
+    private final int maximum;
+
+    public DefaultColumnWidthStrategy() {
+        this(18, 70, 250);
+    }
+
+    public DefaultColumnWidthStrategy(final int minimum, final int preferred, final int maximum) {
+        if (minimum <= 0) {
+            throw new IllegalArgumentException("minimum width must be greater than zero");
+        }
+        if (preferred <= minimum || preferred >= maximum) {
+            throw new IllegalArgumentException("preferred width must be greater than minimum and less than maximum");
+        }
+        this.minimum = minimum;
+        this.preferred = preferred;
+        this.maximum = maximum;
+    }
+
+    @Override
+    public int getMinimumWidth(final int i, final ObjectAssociation specification) {
+        return minimum;
+    }
+
+    @Override
+    public int getPreferredWidth(final int i, final ObjectAssociation specification) {
+        return preferred;
+    }
+
+    @Override
+    public int getMaximumWidth(final int i, final ObjectAssociation specification) {
+        return maximum;
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/255ef514/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/table/InternalTableSpecification.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/table/InternalTableSpecification.java b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/table/InternalTableSpecification.java
new file mode 100644
index 0000000..ffaef29
--- /dev/null
+++ b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/table/InternalTableSpecification.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.table;
+
+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.base.Layout;
+import org.apache.isis.viewer.dnd.view.border.ScrollBorder;
+import org.apache.isis.viewer.dnd.view.composite.CompositeViewDecorator;
+import org.apache.isis.viewer.dnd.view.composite.StackLayout;
+import org.apache.isis.viewer.dnd.viewer.basic.TableFocusManager;
+
+public class InternalTableSpecification extends AbstractTableSpecification {
+    public InternalTableSpecification() {
+        addViewDecorator(new CompositeViewDecorator() {
+            @Override
+            public View decorate(final View view, final Axes axes) {
+                final ScrollBorder scrollingView = new ScrollBorder(view);
+                // 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)));
+                scrollingView.setFocusManager(new TableFocusManager(scrollingView));
+                return scrollingView;
+
+            }
+        });
+
+    }
+
+    @Override
+    public Layout createLayout(final Content content, final Axes axes) {
+        return new StackLayout();
+    }
+
+    // TODO remove
+    /*
+     * @Override public View doCreateView(final View view, final Content
+     * content, final ViewAxis axis) { final ScrollBorder scrollingView = new
+     * ScrollBorder(view); // 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));
+     * scrollingView.setFocusManager(new TableFocusManager(scrollingView));
+     * return scrollingView; }
+     * 
+     * protected View decorateView(View view) { super.decorateView(view);
+     * 
+     * final ScrollBorder scrollingView = new ScrollBorder(view); // 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())); scrollingView.setFocusManager(new
+     * TableFocusManager(scrollingView)); return scrollingView; }
+     */
+    @Override
+    public String getName() {
+        return "Table";
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/255ef514/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/table/TableAxis.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/table/TableAxis.java b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/table/TableAxis.java
new file mode 100644
index 0000000..5a5c676
--- /dev/null
+++ b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/table/TableAxis.java
@@ -0,0 +1,68 @@
+/*
+ *  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.metamodel.spec.feature.ObjectAssociation;
+import org.apache.isis.viewer.dnd.view.View;
+import org.apache.isis.viewer.dnd.view.ViewAxis;
+
+public interface TableAxis extends ViewAxis {
+
+    void ensureOffset(final int offset);
+
+    /**
+     * Returns the number of the column found at the specificied position,
+     * ignoring the columns two borders. Returns 0 for the first column, 1 for
+     * second column, etc.
+     * 
+     * If over the column border then returns -1.
+     */
+    int getColumnAt(final int xPosition);
+
+    /**
+     * Returns 0 for left side of first column, 1 for right side of first
+     * column, 2 for right side of second column, etc.
+     * 
+     * If no column border is identified then returns -1.
+     */
+    int getColumnBorderAt(final int xPosition);
+
+    int getColumnCount();
+
+    String getColumnName(final int column);
+
+    int getColumnWidth(final int column);
+
+    ObjectAssociation getFieldForColumn(final int column);
+
+    int getHeaderOffset();
+
+    int getLeftEdge(final int resizeColumn);
+
+    void invalidateLayout();
+
+    void setOffset(final int offset);
+
+    void setRoot(final View view);
+
+    void setupColumnWidths(final ColumnWidthStrategy strategy);
+
+    void setWidth(final int index, final int width);
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/255ef514/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/table/TableAxisImpl.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/table/TableAxisImpl.java b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/table/TableAxisImpl.java
new file mode 100644
index 0000000..34f54e9
--- /dev/null
+++ b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/table/TableAxisImpl.java
@@ -0,0 +1,217 @@
+/*
+ *  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.List;
+
+import com.google.common.collect.Lists;
+
+import org.apache.isis.core.commons.lang.ToString;
+import org.apache.isis.core.metamodel.spec.ObjectSpecification;
+import org.apache.isis.core.metamodel.spec.feature.ObjectAssociation;
+import org.apache.isis.core.metamodel.spec.feature.ObjectAssociationFilters;
+import org.apache.isis.core.metamodel.spec.feature.OneToManyAssociation;
+import org.apache.isis.viewer.dnd.view.View;
+import org.apache.isis.viewer.dnd.view.collection.CollectionContent;
+
+public class TableAxisImpl implements TableAxis {
+    private final List<ObjectAssociation> columns;
+    private final String[] columnName;
+    private int rowHeaderOffet;
+    private View table;
+    private final int[] widths;
+
+    public TableAxisImpl(final CollectionContent content) {
+        // TODO create axis first, then after view built set up the axis
+        // details?
+        final ObjectSpecification elementSpecification = (content).getElementSpecification();
+        final List<ObjectAssociation> accessibleFields = elementSpecification.getAssociations(ObjectAssociationFilters.WHEN_VISIBLE_IRRESPECTIVE_OF_WHERE);
+
+        this.columns = tableFields(accessibleFields, content);
+        widths = new int[columns.size()];
+        columnName = new String[columns.size()];
+        for (int i = 0; i < widths.length; i++) {
+            columnName[i] = columns.get(i).getName();
+        }
+
+        // TODO make the setting of the column width strategy external so it can
+        // be changed
+        setupColumnWidths(new TypeBasedColumnWidthStrategy());
+    }
+
+    /*
+     * public TableAxis(final ObjectAssociation[] columns) { this.columns =
+     * columns; widths = new int[columns.length]; columnName = new
+     * String[columns.length]; for (int i = 0; i < widths.length; i++) {
+     * columnName[i] = columns[i].getName(); } }
+     */
+    private List<ObjectAssociation> tableFields(final List<ObjectAssociation> viewFields, final CollectionContent content) {
+        for (int i = 0; i < viewFields.size(); i++) {
+            // final ObjectAssociation objectAssociation = viewFields[i];
+            // TODO reinstate check to skip unsuitable types
+            /*
+             * if (viewFields[i].getSpecification().isOfType(
+             * IsisContext.getSpecificationLoader
+             * ().loadSpecification(ImageValue.class))) { continue; }
+             */
+
+            // if
+            // (!objectAssociation.isVisible(IsisContext.getAuthenticationSession(),
+            // content.getAdapter()).isAllowed()) {
+            // continue;
+            // }
+            // LOG.debug("column " + objectAssociation.getSpecification());
+            // if(viewFields[i].getSpecification().isOfType(Isis.getSpecificationLoader().lo));
+        }
+
+        final List<ObjectAssociation> tableFields = Lists.newArrayList();
+        for (int i = 0; i < viewFields.size(); i++) {
+            if (!(viewFields.get(i) instanceof OneToManyAssociation)) {
+                tableFields.add(viewFields.get(i));
+            }
+        }
+
+        return tableFields;
+    }
+
+    @Override
+    public void ensureOffset(final int offset) {
+        rowHeaderOffet = Math.max(rowHeaderOffet, offset + 5);
+    }
+
+    /**
+     * Returns the number of the column found at the specificied position,
+     * ignoring the columns two borders. Returns 0 for the first column, 1 for
+     * second column, etc.
+     * 
+     * If over the column border then returns -1.
+     */
+    @Override
+    public int getColumnAt(final int xPosition) {
+        int edge = getHeaderOffset();
+        for (int i = 0, cols = getColumnCount() + 1; i < cols; i++) {
+            if (xPosition >= edge - 1 && xPosition <= edge + 1) {
+                return -1;
+            }
+            if (xPosition < edge - 1) {
+                return i;
+            }
+            edge += getColumnWidth(i);
+        }
+
+        return -1;
+    }
+
+    /**
+     * Returns 0 for left side of first column, 1 for right side of first
+     * column, 2 for right side of second column, etc.
+     * 
+     * If no column border is identified then returns -1.
+     */
+    @Override
+    public int getColumnBorderAt(final int xPosition) {
+        int edge = getHeaderOffset();
+        for (int i = 0, cols = getColumnCount(); i < cols; i++) {
+            if (xPosition >= edge - 1 && xPosition <= edge + 1) {
+                return i;
+            }
+            edge += getColumnWidth(i);
+        }
+        if (xPosition >= edge - 1 && xPosition <= edge + 1) {
+            return getColumnCount();
+        }
+
+        return -1;
+    }
+
+    @Override
+    public int getColumnCount() {
+        return columnName.length;
+    }
+
+    @Override
+    public String getColumnName(final int column) {
+        return columnName[column];
+    }
+
+    @Override
+    public int getColumnWidth(final int column) {
+        return widths[column];
+    }
+
+    @Override
+    public ObjectAssociation getFieldForColumn(final int column) {
+        return columns.get(column);
+    }
+
+    @Override
+    public int getHeaderOffset() {
+        return rowHeaderOffet;
+    }
+
+    @Override
+    public int getLeftEdge(final int resizeColumn) {
+        int width = getHeaderOffset();
+        for (int i = 0, cols = getColumnCount(); i < resizeColumn && i < cols; i++) {
+            width += getColumnWidth(i);
+        }
+        return width;
+    }
+
+    @Override
+    public void invalidateLayout() {
+        final View[] rows = table.getSubviews();
+        for (final View row : rows) {
+            row.invalidateLayout();
+        }
+        table.invalidateLayout();
+    }
+
+    @Override
+    public void setOffset(final int offset) {
+        rowHeaderOffet = offset;
+    }
+
+    @Override
+    public void setRoot(final View view) {
+        table = view;
+    }
+
+    @Override
+    public void setupColumnWidths(final ColumnWidthStrategy strategy) {
+        for (int i = 0; i < widths.length; i++) {
+            widths[i] = strategy.getPreferredWidth(i, columns.get(i));
+        }
+    }
+
+    @Override
+    public void setWidth(final int index, final int width) {
+        widths[index] = width;
+    }
+
+    @Override
+    public String toString() {
+        final ToString str = new ToString(this);
+        str.append("columns", columnName.length);
+        str.append("header offset", rowHeaderOffet);
+        str.append("table", table);
+        return str.toString();
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/255ef514/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/table/TableCellBuilder.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/table/TableCellBuilder.java b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/table/TableCellBuilder.java
new file mode 100644
index 0000000..8a29d08
--- /dev/null
+++ b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/table/TableCellBuilder.java
@@ -0,0 +1,202 @@
+/*
+ *  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.log4j.Logger;
+
+import org.apache.isis.applib.annotation.Where;
+import org.apache.isis.core.commons.ensure.Assert;
+import org.apache.isis.core.commons.exceptions.UnexpectedCallException;
+import org.apache.isis.core.commons.exceptions.UnknownTypeException;
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.spec.ObjectSpecification;
+import org.apache.isis.core.metamodel.spec.feature.ObjectAssociation;
+import org.apache.isis.core.metamodel.spec.feature.OneToManyAssociation;
+import org.apache.isis.core.metamodel.spec.feature.OneToOneAssociation;
+import org.apache.isis.core.progmodel.facets.value.booleans.BooleanValueFacet;
+import org.apache.isis.core.progmodel.facets.value.image.ImageValueFacet;
+import org.apache.isis.runtimes.dflt.runtime.system.context.IsisContext;
+import org.apache.isis.viewer.dnd.field.CheckboxField;
+import org.apache.isis.viewer.dnd.view.Axes;
+import org.apache.isis.viewer.dnd.view.Content;
+import org.apache.isis.viewer.dnd.view.GlobalViewFactory;
+import org.apache.isis.viewer.dnd.view.ObjectContent;
+import org.apache.isis.viewer.dnd.view.Toolkit;
+import org.apache.isis.viewer.dnd.view.View;
+import org.apache.isis.viewer.dnd.view.ViewRequirement;
+import org.apache.isis.viewer.dnd.view.ViewSpecification;
+import org.apache.isis.viewer.dnd.view.base.BlankView;
+import org.apache.isis.viewer.dnd.view.base.FieldErrorView;
+import org.apache.isis.viewer.dnd.view.composite.AbstractViewBuilder;
+import org.apache.isis.viewer.dnd.view.content.TextParseableContent;
+import org.apache.isis.viewer.dnd.view.field.OneToOneFieldImpl;
+import org.apache.isis.viewer.dnd.view.field.TextParseableFieldImpl;
+import org.apache.isis.viewer.dnd.viewer.basic.UnlinedTextFieldSpecification;
+
+class TableCellBuilder extends AbstractViewBuilder {
+    private static final Logger LOG = Logger.getLogger(TableCellBuilder.class);
+
+    // REVIEW: should provide this rendering context, rather than hardcoding.
+    // the net effect currently is that class members annotated with
+    // @Hidden(where=Where.ALL_TABLES) will indeed be hidden from all tables
+    // but will be shown (perhaps incorrectly) if annotated with Where.PARENTED_TABLE
+    // or Where.STANDALONE_TABLE
+    private final Where where = Where.ALL_TABLES;
+    
+
+    private void addField(final View view, final Axes axes, final ObjectAdapter object, final ObjectAssociation field) {
+        final ObjectAdapter value = field.get(object);
+        View fieldView;
+        fieldView = createFieldView(view, axes, object, field, value);
+        if (fieldView != null) {
+            view.addView(decorateSubview(axes, fieldView));
+        } else {
+            view.addView(new FieldErrorView("No field for " + value));
+        }
+    }
+
+    @Override
+    public void build(final View view, final Axes axes) {
+        Assert.assertEquals("ensure the view is complete decorated view", view.getView(), view);
+
+        final Content content = view.getContent();
+        final ObjectAdapter object = ((ObjectContent) content).getObject();
+
+        if (view.getSubviews().length == 0) {
+            initialBuild(object, view, axes);
+        } else {
+            updateBuild(object, view, axes);
+        }
+    }
+
+    private void updateBuild(final ObjectAdapter object, final View view, final Axes axes) {
+        final TableAxis viewAxis = axes.getAxis(TableAxis.class);
+
+        LOG.debug("update view " + view + " for " + object);
+        final View[] subviews = view.getSubviews();
+        final ObjectSpecification spec = object.getSpecification();
+        for (int i = 0; i < subviews.length; i++) {
+            final ObjectAssociation field = fieldFromActualSpec(spec, viewAxis.getFieldForColumn(i));
+            final View subview = subviews[i];
+            final ObjectAdapter value = field.get(object);
+
+            // if the field is parseable then it may have been modified; we need
+            // to replace what was
+            // typed in with the actual title.
+            if (field.getSpecification().isParseable()) {
+                final boolean visiblityChange = !field.isVisible(IsisContext.getAuthenticationSession(), object, where).isAllowed() ^ (subview instanceof BlankView);
+                final ObjectAdapter adapter = subview.getContent().getAdapter();
+                final boolean valueChange = value != null && value.getObject() != null && !value.getObject().equals(adapter.getObject());
+
+                if (visiblityChange || valueChange) {
+                    final View fieldView = createFieldView(view, axes, object, field, value);
+                    view.replaceView(subview, decorateSubview(axes, fieldView));
+                }
+                subview.refresh();
+            } else if (field.isOneToOneAssociation()) {
+                final ObjectAdapter existing = ((ObjectContent) subviews[i].getContent()).getObject();
+                final boolean changedValue = value != existing;
+                if (changedValue) {
+                    View fieldView;
+                    fieldView = createFieldView(view, axes, object, field, value);
+                    if (fieldView != null) {
+                        view.replaceView(subview, decorateSubview(axes, fieldView));
+                    } else {
+                        view.addView(new FieldErrorView("No field for " + value));
+                    }
+                }
+            }
+        }
+    }
+
+    private ObjectAssociation fieldFromActualSpec(final ObjectSpecification spec, final ObjectAssociation field) {
+        final String fieldName = field.getId();
+        return spec.getAssociation(fieldName);
+    }
+
+    private void initialBuild(final ObjectAdapter object, final View view, final Axes axes) {
+        final TableAxis viewAxis = axes.getAxis(TableAxis.class);
+        LOG.debug("build view " + view + " for " + object);
+        final int len = viewAxis.getColumnCount();
+        final ObjectSpecification spec = object.getSpecification();
+        for (int f = 0; f < len; f++) {
+            if (f > 3) {
+                continue;
+            }
+            final ObjectAssociation field = fieldFromActualSpec(spec, viewAxis.getFieldForColumn(f));
+            addField(view, axes, object, field);
+        }
+    }
+
+    private View createFieldView(final View view, final Axes axes, final ObjectAdapter object, final ObjectAssociation field, final ObjectAdapter value) {
+        if (field == null) {
+            throw new NullPointerException();
+        }
+        final GlobalViewFactory factory = Toolkit.getViewFactory();
+        ViewSpecification cellSpec;
+        Content content;
+        if (field instanceof OneToManyAssociation) {
+            throw new UnexpectedCallException("no collections allowed");
+        } else if (field instanceof OneToOneAssociation) {
+
+            final ObjectSpecification fieldSpecification = field.getSpecification();
+            if (fieldSpecification.isParseable()) {
+                content = new TextParseableFieldImpl(object, value, (OneToOneAssociation) field);
+                // REVIEW how do we deal with IMAGES?
+                if (content.getAdapter() instanceof ImageValueFacet) {
+                    return new BlankView(content);
+                }
+
+                if (!field.isVisible(IsisContext.getAuthenticationSession(), object, where).isAllowed()) {
+                    return new BlankView(content);
+                }
+                if (((TextParseableContent) content).getNoLines() > 0) {
+                    /*
+                     * TODO remove this after introducing constraints into view
+                     * specs that allow the parent view to specify what kind of
+                     * subviews it can deal
+                     */
+
+                    if (fieldSpecification.containsFacet(BooleanValueFacet.class)) {
+                        cellSpec = new CheckboxField.Specification();
+                    } else {
+                        cellSpec = new UnlinedTextFieldSpecification();
+                    }
+                } else {
+                    return factory.createView(new ViewRequirement(content, ViewRequirement.CLOSED));
+                }
+            } else {
+                content = new OneToOneFieldImpl(object, value, (OneToOneAssociation) field);
+                
+                if (!field.isVisible(IsisContext.getAuthenticationSession(), object, where).isAllowed()) {
+                    return new BlankView(content);
+                }
+                return factory.createView(new ViewRequirement(content, ViewRequirement.CLOSED | ViewRequirement.SUBVIEW));
+
+            }
+
+        } else {
+            throw new UnknownTypeException(field);
+        }
+
+        return cellSpec.createView(content, axes, -1);
+    }
+
+}