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

[24/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/control/AbstractControlView.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/control/AbstractControlView.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/control/AbstractControlView.java
new file mode 100644
index 0000000..3824652
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/control/AbstractControlView.java
@@ -0,0 +1,505 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.isis.viewer.dnd.view.control;
+
+import java.awt.event.KeyEvent;
+
+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.runtime.userprofile.Options;
+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.Padding;
+import org.apache.isis.viewer.dnd.drawing.Size;
+import org.apache.isis.viewer.dnd.view.Axes;
+import org.apache.isis.viewer.dnd.view.Click;
+import org.apache.isis.viewer.dnd.view.Content;
+import org.apache.isis.viewer.dnd.view.ContentDrag;
+import org.apache.isis.viewer.dnd.view.DragEvent;
+import org.apache.isis.viewer.dnd.view.DragStart;
+import org.apache.isis.viewer.dnd.view.Feedback;
+import org.apache.isis.viewer.dnd.view.FocusManager;
+import org.apache.isis.viewer.dnd.view.InternalDrag;
+import org.apache.isis.viewer.dnd.view.KeyboardAction;
+import org.apache.isis.viewer.dnd.view.Placement;
+import org.apache.isis.viewer.dnd.view.Toolkit;
+import org.apache.isis.viewer.dnd.view.UserAction;
+import org.apache.isis.viewer.dnd.view.UserActionSet;
+import org.apache.isis.viewer.dnd.view.View;
+import org.apache.isis.viewer.dnd.view.ViewAreaType;
+import org.apache.isis.viewer.dnd.view.ViewDrag;
+import org.apache.isis.viewer.dnd.view.ViewSpecification;
+import org.apache.isis.viewer.dnd.view.ViewState;
+import org.apache.isis.viewer.dnd.view.Viewer;
+import org.apache.isis.viewer.dnd.view.Workspace;
+import org.apache.isis.viewer.dnd.view.base.Layout;
+
+public abstract class AbstractControlView implements View {
+    protected final UserAction action;
+    private final View parent;
+    private int width;
+    private int x;
+    private int y;
+    private int height;
+    private boolean isOver;
+    private boolean isPressed;
+
+    public AbstractControlView(final UserAction action, final View target) {
+        this.action = action;
+        this.parent = target;
+    }
+
+    @Override
+    public Axes getViewAxes() {
+        return new Axes();
+    }
+
+    @Override
+    public void addView(final View view) {
+    }
+
+    @Override
+    public Consent canChangeValue() {
+        return Veto.DEFAULT;
+    }
+
+    @Override
+    public boolean canFocus() {
+        return action.disabled(parent).isAllowed();
+    }
+
+    @Override
+    public boolean contains(final View view) {
+        return false;
+    }
+
+    @Override
+    public boolean containsFocus() {
+        return false;
+    }
+
+    @Override
+    public void contentMenuOptions(final UserActionSet menuOptions) {
+    }
+
+    @Override
+    public void debug(final DebugBuilder debug) {
+    }
+
+    @Override
+    public void debugStructure(final DebugBuilder b) {
+    }
+
+    @Override
+    public void dispose() {
+    }
+
+    @Override
+    public void drag(final ContentDrag contentDrag) {
+    }
+
+    @Override
+    public void drag(final InternalDrag drag) {
+    }
+
+    @Override
+    public void drag(final ViewDrag drag) {
+    }
+
+    @Override
+    public void dragCancel(final InternalDrag drag) {
+    }
+
+    @Override
+    public View dragFrom(final Location location) {
+        return null;
+    }
+
+    @Override
+    public void dragIn(final ContentDrag drag) {
+    }
+
+    @Override
+    public void dragOut(final ContentDrag drag) {
+    }
+
+    @Override
+    public DragEvent dragStart(final DragStart drag) {
+        return null;
+    }
+
+    @Override
+    public void dragTo(final InternalDrag drag) {
+    }
+
+    @Override
+    public void draw(final Canvas canvas) {
+    }
+
+    @Override
+    public void drop(final ContentDrag drag) {
+    }
+
+    @Override
+    public void drop(final ViewDrag drag) {
+        getParent().drop(drag);
+    }
+
+    @Override
+    public void editComplete(final boolean moveFocus, final boolean toNextField) {
+    }
+
+    @Override
+    public void entered() {
+        final View target = getParent();
+        final Consent consent = action.disabled(target);
+        final String actionText = action.getName(target) + " - " + (consent.isVetoed() ? consent.getReason() : action.getDescription(target));
+        getFeedbackManager().setAction(actionText);
+
+        isOver = true;
+        isPressed = false;
+        markDamaged();
+    }
+
+    @Override
+    public void exited() {
+        getFeedbackManager().clearAction();
+        isOver = false;
+        isPressed = false;
+        markDamaged();
+    }
+
+    public void exitedSubview() {
+    }
+
+    @Override
+    public void firstClick(final Click click) {
+        executeAction();
+    }
+
+    private void executeAction() {
+        final View target = getParent().getView();
+        if (action.disabled(target).isAllowed()) {
+            markDamaged();
+            getViewManager().saveCurrentFieldEntry();
+            action.execute(target.getWorkspace(), target, getLocation());
+        }
+    }
+
+    @Override
+    public void focusLost() {
+    }
+
+    @Override
+    public void focusReceived() {
+    }
+
+    @Override
+    public Location getAbsoluteLocation() {
+        final Location location = parent.getAbsoluteLocation();
+        getViewManager().getSpy().addTrace(this, "parent location", location);
+        location.add(x, y);
+        getViewManager().getSpy().addTrace(this, "plus view's location", location);
+        final Padding pad = parent.getPadding();
+        location.add(pad.getLeft(), pad.getTop());
+        getViewManager().getSpy().addTrace(this, "plus view's padding", location);
+        return location;
+    }
+
+    public boolean isOver() {
+        return isOver;
+    }
+
+    public boolean isPressed() {
+        return isPressed;
+    }
+
+    @Override
+    public int getBaseline() {
+        return 0;
+    }
+
+    @Override
+    public Bounds getBounds() {
+        return new Bounds(x, y, width, height);
+    }
+
+    @Override
+    public Content getContent() {
+        return null;
+    }
+
+    @Override
+    public int getId() {
+        return 0;
+    }
+
+    @Override
+    public FocusManager getFocusManager() {
+        return getParent() == null ? null : getParent().getFocusManager();
+    }
+
+    @Override
+    public Location getLocation() {
+        return new Location(x, y);
+    }
+
+    @Override
+    public Padding getPadding() {
+        return null;
+    }
+
+    @Override
+    public View getParent() {
+        return parent;
+    }
+
+    @Override
+    public Size getSize() {
+        return new Size(width, height);
+    }
+
+    @Override
+    public ViewSpecification getSpecification() {
+        return null;
+    }
+
+    @Override
+    public ViewState getState() {
+        return null;
+    }
+
+    @Override
+    public View[] getSubviews() {
+        return new View[0];
+    }
+
+    @Override
+    public View getView() {
+        return this;
+    }
+
+    @Override
+    public Viewer getViewManager() {
+        return Toolkit.getViewer();
+    }
+
+    @Override
+    public Feedback getFeedbackManager() {
+        return Toolkit.getFeedbackManager();
+    }
+
+    @Override
+    public Workspace getWorkspace() {
+        return null;
+    }
+
+    @Override
+    public boolean hasFocus() {
+        return getViewManager().hasFocus(getView());
+    }
+
+    @Override
+    public View identify(final Location location) {
+        return this;
+    }
+
+    @Override
+    public void invalidateContent() {
+    }
+
+    @Override
+    public void invalidateLayout() {
+    }
+
+    @Override
+    public void keyPressed(final KeyboardAction key) {
+        if (key.getKeyCode() == KeyEvent.VK_ENTER) {
+            executeAction();
+        }
+    }
+
+    @Override
+    public void keyReleased(final KeyboardAction action) {
+    }
+
+    @Override
+    public void keyTyped(final KeyboardAction action) {
+    }
+
+    @Override
+    public void layout() {
+    }
+
+    @Override
+    public void limitBoundsWithin(final Size size) {
+    }
+
+    @Override
+    public void markDamaged() {
+        markDamaged(getView().getBounds());
+    }
+
+    @Override
+    public void markDamaged(final Bounds bounds) {
+        if (parent == null) {
+            getViewManager().markDamaged(bounds);
+        } else {
+            final Location pos = parent.getLocation();
+            bounds.translate(pos.getX(), pos.getY());
+            parent.markDamaged(bounds);
+        }
+    }
+
+    @Override
+    public void mouseDown(final Click click) {
+        final View target = getParent().getView();
+        if (action.disabled(target).isAllowed()) {
+            markDamaged();
+            getViewManager().saveCurrentFieldEntry();
+            // action.execute(target.getWorkspace(), target, getLocation());
+        }
+        final boolean vetoed = action.disabled(target).isVetoed();
+        if (!vetoed) {
+            isPressed = true;
+            markDamaged();
+        }
+    }
+
+    @Override
+    public void mouseUp(final Click click) {
+        final View target = getParent().getView();
+        final boolean vetoed = action.disabled(target).isVetoed();
+        if (!vetoed) {
+            isPressed = false;
+            markDamaged();
+        }
+    }
+
+    @Override
+    public void mouseMoved(final Location location) {
+    }
+
+    @Override
+    public void objectActionResult(final ObjectAdapter result, final Placement placement) {
+    }
+
+    @Override
+    public View pickupContent(final Location location) {
+        return null;
+    }
+
+    @Override
+    public View pickupView(final Location location) {
+        return null;
+    }
+
+    @Override
+    public void print(final Canvas canvas) {
+    }
+
+    @Override
+    public void refresh() {
+    }
+
+    @Override
+    public void removeView(final View view) {
+    }
+
+    @Override
+    public void replaceView(final View toReplace, final View replacement) {
+    }
+
+    @Override
+    public void secondClick(final Click click) {
+    }
+
+    @Override
+    public void setBounds(final Bounds bounds) {
+    }
+
+    @Override
+    public void setFocusManager(final FocusManager focusManager) {
+    }
+
+    public void setLayout(final Layout layout) {
+    }
+
+    @Override
+    public void setLocation(final Location point) {
+        x = point.getX();
+        y = point.getY();
+    }
+
+    @Override
+    public void setParent(final View view) {
+    }
+
+    public void setMaximumSize(final Size size) {
+    }
+
+    @Override
+    public void setSize(final Size size) {
+        width = size.getWidth();
+        height = size.getHeight();
+    }
+
+    @Override
+    public void setView(final View view) {
+    }
+
+    @Override
+    public View subviewFor(final Location location) {
+        return null;
+    }
+
+    @Override
+    public void thirdClick(final Click click) {
+    }
+
+    @Override
+    public void update(final ObjectAdapter object) {
+    }
+
+    @Override
+    public void updateView() {
+    }
+
+    @Override
+    public ViewAreaType viewAreaType(final Location mouseLocation) {
+        return null;
+    }
+
+    @Override
+    public void viewMenuOptions(final UserActionSet menuOptions) {
+    }
+
+    @Override
+    public void loadOptions(final Options viewOptions) {
+    }
+
+    @Override
+    public void saveOptions(final Options viewOptions) {
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/eb613703/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/control/Button.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/control/Button.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/control/Button.java
new file mode 100644
index 0000000..80a48a7
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/control/Button.java
@@ -0,0 +1,57 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.isis.viewer.dnd.view.control;
+
+import org.apache.isis.viewer.dnd.drawing.Canvas;
+import org.apache.isis.viewer.dnd.drawing.Size;
+import org.apache.isis.viewer.dnd.view.ButtonAction;
+import org.apache.isis.viewer.dnd.view.View;
+
+public class Button extends AbstractControlView {
+    private static ButtonRender buttonRender;
+
+    public static void setButtonRender(final ButtonRender buttonRender) {
+        Button.buttonRender = buttonRender;
+    }
+
+    public Button(final ButtonAction action, final View target) {
+        super(action, target);
+    }
+
+    @Override
+    public boolean containsFocus() {
+        return hasFocus();
+    }
+
+    @Override
+    public void draw(final Canvas canvas) {
+        final View target = getParent();
+        final String text = action.getName(target);
+        final boolean isDisabled = action.disabled(target).isVetoed();
+        final boolean isDefault = ((ButtonAction) action).isDefault();
+        buttonRender.draw(canvas, getSize(), isDisabled, isDefault, hasFocus(), isOver(), isPressed(), text);
+    }
+
+    @Override
+    public Size getRequiredSize(final Size availableSpace) {
+        final String text = action.getName(getView());
+        return buttonRender.getMaximumSize(text);
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/eb613703/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/control/ButtonRender.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/control/ButtonRender.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/control/ButtonRender.java
new file mode 100644
index 0000000..8407cd7
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/control/ButtonRender.java
@@ -0,0 +1,31 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.isis.viewer.dnd.view.control;
+
+import org.apache.isis.viewer.dnd.drawing.Canvas;
+import org.apache.isis.viewer.dnd.drawing.Size;
+
+public interface ButtonRender {
+
+    void draw(Canvas canvas, Size size, boolean isDisabled, boolean isDefault, boolean hasFocus, boolean isOver, boolean isPressed, String text);
+
+    Size getMaximumSize(String text);
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/eb613703/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/control/CancelAction.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/control/CancelAction.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/control/CancelAction.java
new file mode 100644
index 0000000..a8700d1
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/control/CancelAction.java
@@ -0,0 +1,40 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.isis.viewer.dnd.view.control;
+
+import org.apache.log4j.Logger;
+
+import org.apache.isis.viewer.dnd.drawing.Location;
+import org.apache.isis.viewer.dnd.view.View;
+import org.apache.isis.viewer.dnd.view.Workspace;
+
+public class CancelAction extends AbstractButtonAction {
+    private static final Logger LOG = Logger.getLogger(CancelAction.class);
+
+    public CancelAction() {
+        super("Cancel");
+    }
+
+    @Override
+    public void execute(final Workspace workspace, final View view, final Location at) {
+        LOG.debug("cancel pressed");
+        view.dispose();
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/eb613703/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/debug/DebugAdapter.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/debug/DebugAdapter.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/debug/DebugAdapter.java
new file mode 100644
index 0000000..2ddbe6a
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/debug/DebugAdapter.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.debug;
+
+import org.apache.isis.core.commons.debug.DebugBuilder;
+import org.apache.isis.core.commons.debug.DebuggableWithTitle;
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.util.Dump;
+
+public class DebugAdapter implements DebuggableWithTitle {
+    private final ObjectAdapter object;
+
+    public DebugAdapter(final ObjectAdapter object) {
+        this.object = object;
+    }
+
+    @Override
+    public void debugData(final DebugBuilder debug) {
+        dumpObject(object, debug);
+    }
+
+    @Override
+    public String debugTitle() {
+        return "Adapter";
+    }
+
+    private void dumpObject(final ObjectAdapter object, final DebugBuilder info) {
+        if (object != null) {
+            Dump.adapter(object, info);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/eb613703/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/debug/DebugBorder.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/debug/DebugBorder.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/debug/DebugBorder.java
new file mode 100644
index 0000000..f694b6a
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/debug/DebugBorder.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.debug;
+
+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.Text;
+import org.apache.isis.viewer.dnd.view.Click;
+import org.apache.isis.viewer.dnd.view.Toolkit;
+import org.apache.isis.viewer.dnd.view.View;
+import org.apache.isis.viewer.dnd.view.base.AbstractBorder;
+
+public class DebugBorder extends AbstractBorder {
+    public DebugBorder(final View wrappedView) {
+        super(wrappedView);
+
+        bottom = Toolkit.getText(ColorsAndFonts.TEXT_DEBUG).getTextHeight();
+    }
+
+    @Override
+    protected void debugDetails(final DebugBuilder debug) {
+        debug.append("DebugBorder");
+    }
+
+    @Override
+    public void draw(final Canvas canvas) {
+        final String debug = getView() + " " + getState();
+        final Text text = Toolkit.getText(ColorsAndFonts.TEXT_DEBUG);
+        final int baseline = wrappedView.getSize().getHeight() + text.getAscent();
+        final Color color = Toolkit.getColor(ColorsAndFonts.COLOR_DEBUG_BASELINE);
+        canvas.drawText(debug, 0, baseline, color, text);
+
+        super.draw(canvas);
+    }
+
+    @Override
+    public String toString() {
+        return wrappedView.toString() + "/DebugBorder";
+    }
+
+    @Override
+    public void firstClick(final Click click) {
+        new DebugOption().execute(getWorkspace(), getView(), click.getLocation());
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/eb613703/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/debug/DebugContent.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/debug/DebugContent.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/debug/DebugContent.java
new file mode 100644
index 0000000..40b3b38
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/debug/DebugContent.java
@@ -0,0 +1,66 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.isis.viewer.dnd.view.debug;
+
+import org.apache.isis.core.commons.debug.DebugBuilder;
+import org.apache.isis.core.commons.debug.DebuggableWithTitle;
+import org.apache.isis.viewer.dnd.view.Content;
+import org.apache.isis.viewer.dnd.view.View;
+
+public class DebugContent implements DebuggableWithTitle {
+    private final View view;
+
+    public DebugContent(final View display) {
+        this.view = display;
+    }
+
+    @Override
+    public void debugData(final DebugBuilder debug) {
+        final Content content = view.getContent();
+        if (content != null) {
+            String type = content.getClass().getName();
+            type = type.substring(type.lastIndexOf('.') + 1);
+            debug.appendln("Content", type);
+
+            debug.indent();
+
+            content.debugDetails(debug);
+
+            debug.appendln("Icon name", content.getIconName());
+            debug.appendln("Icon ", content.getIconPicture(32));
+            debug.appendln("Window title", content.windowTitle());
+
+            debug.appendln("Object", content.isObject());
+            debug.appendln("Collection", content.isCollection());
+
+            debug.appendln("Text Parseable", content.isTextParseable());
+
+            debug.unindent();
+        } else {
+            debug.appendln("Content", "none");
+        }
+        debug.blankLine();
+    }
+
+    @Override
+    public String debugTitle() {
+        return "Content";
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/eb613703/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/debug/DebugDrawing.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/debug/DebugDrawing.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/debug/DebugDrawing.java
new file mode 100644
index 0000000..1b5133a
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/debug/DebugDrawing.java
@@ -0,0 +1,44 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.isis.viewer.dnd.view.debug;
+
+import org.apache.isis.core.commons.debug.DebugBuilder;
+import org.apache.isis.core.commons.debug.DebuggableWithTitle;
+import org.apache.isis.viewer.dnd.drawing.Bounds;
+import org.apache.isis.viewer.dnd.drawing.DebugCanvas;
+import org.apache.isis.viewer.dnd.view.View;
+
+public class DebugDrawing implements DebuggableWithTitle {
+    private final View view;
+
+    public DebugDrawing(final View display) {
+        this.view = display;
+    }
+
+    @Override
+    public void debugData(final DebugBuilder debug) {
+        view.draw(new DebugCanvas(debug, new Bounds(view.getBounds())));
+    }
+
+    @Override
+    public String debugTitle() {
+        return "Drawing";
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/eb613703/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/debug/DebugDrawingAbsolute.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/debug/DebugDrawingAbsolute.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/debug/DebugDrawingAbsolute.java
new file mode 100644
index 0000000..cf2c62e
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/debug/DebugDrawingAbsolute.java
@@ -0,0 +1,44 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.isis.viewer.dnd.view.debug;
+
+import org.apache.isis.core.commons.debug.DebugBuilder;
+import org.apache.isis.core.commons.debug.DebuggableWithTitle;
+import org.apache.isis.viewer.dnd.drawing.Bounds;
+import org.apache.isis.viewer.dnd.drawing.DebugCanvasAbsolute;
+import org.apache.isis.viewer.dnd.view.View;
+
+public class DebugDrawingAbsolute implements DebuggableWithTitle {
+    private final View view;
+
+    public DebugDrawingAbsolute(final View display) {
+        this.view = display;
+    }
+
+    @Override
+    public void debugData(final DebugBuilder debug) {
+        view.draw(new DebugCanvasAbsolute(debug, new Bounds(view.getAbsoluteLocation(), view.getSize())));
+    }
+
+    @Override
+    public String debugTitle() {
+        return "Drawing (Absolute)";
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/eb613703/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/debug/DebugDumpSnapshotOption.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/debug/DebugDumpSnapshotOption.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/debug/DebugDumpSnapshotOption.java
new file mode 100644
index 0000000..62ff97e
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/debug/DebugDumpSnapshotOption.java
@@ -0,0 +1,75 @@
+/*
+ *  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.debug;
+
+import static org.apache.isis.core.commons.lang.CastUtils.enumerationOver;
+
+import java.util.Enumeration;
+
+import org.apache.log4j.Appender;
+import org.apache.log4j.Logger;
+
+import org.apache.isis.core.metamodel.consent.Allow;
+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.runtime.logging.SnapshotAppender;
+import org.apache.isis.viewer.dnd.drawing.Location;
+import org.apache.isis.viewer.dnd.view.View;
+import org.apache.isis.viewer.dnd.view.Workspace;
+import org.apache.isis.viewer.dnd.view.option.UserActionAbstract;
+
+/**
+ * Display debug window
+ */
+public class DebugDumpSnapshotOption extends UserActionAbstract {
+    public DebugDumpSnapshotOption() {
+        super("Dump log snapshot", ActionType.DEBUG);
+    }
+
+    @Override
+    public Consent disabled(final View component) {
+        final Enumeration<Logger> enumeration = enumerationOver(Logger.getRootLogger().getAllAppenders(), Logger.class);
+        while (enumeration.hasMoreElements()) {
+            final Appender appender = (Appender) enumeration.nextElement();
+            if (appender instanceof SnapshotAppender) {
+                return Allow.DEFAULT;
+            }
+        }
+        // TODO: move logic into Facet
+        return new Veto("No available snapshot appender");
+    }
+
+    @Override
+    public void execute(final Workspace workspace, final View view, final Location at) {
+        final Enumeration<Logger> enumeration = enumerationOver(Logger.getRootLogger().getAllAppenders(), Logger.class);
+        while (enumeration.hasMoreElements()) {
+            final Appender appender = (Appender) enumeration.nextElement();
+            if (appender instanceof SnapshotAppender) {
+                ((SnapshotAppender) appender).forceSnapshot();
+            }
+        }
+    }
+
+    @Override
+    public String getDescription(final View view) {
+        return "Force a snapshot of the log";
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/eb613703/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/debug/DebugObjectGraph.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/debug/DebugObjectGraph.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/debug/DebugObjectGraph.java
new file mode 100644
index 0000000..52f3d6f
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/debug/DebugObjectGraph.java
@@ -0,0 +1,50 @@
+/*
+ *  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.debug;
+
+import org.apache.isis.core.commons.debug.DebugBuilder;
+import org.apache.isis.core.commons.debug.DebuggableWithTitle;
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.util.Dump;
+import org.apache.isis.runtimes.dflt.runtime.system.context.IsisContext;
+
+public class DebugObjectGraph implements DebuggableWithTitle {
+    private final ObjectAdapter object;
+
+    public DebugObjectGraph(final ObjectAdapter object) {
+        this.object = object;
+    }
+
+    @Override
+    public void debugData(final DebugBuilder debug) {
+        dumpGraph(object, debug);
+    }
+
+    @Override
+    public String debugTitle() {
+        return "Object Graph";
+    }
+
+    private void dumpGraph(final ObjectAdapter object, final DebugBuilder info) {
+        if (object != null) {
+            Dump.graph(object, IsisContext.getAuthenticationSession(), info);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/eb613703/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/debug/DebugObjectSpecification.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/debug/DebugObjectSpecification.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/debug/DebugObjectSpecification.java
new file mode 100644
index 0000000..6e433e6
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/debug/DebugObjectSpecification.java
@@ -0,0 +1,53 @@
+/*
+ *  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.debug;
+
+import org.apache.isis.core.commons.debug.DebugBuilder;
+import org.apache.isis.core.commons.debug.DebuggableWithTitle;
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.spec.ObjectSpecification;
+import org.apache.isis.core.metamodel.util.Dump;
+
+public class DebugObjectSpecification implements DebuggableWithTitle {
+    private final ObjectSpecification specification;
+
+    public DebugObjectSpecification(final ObjectAdapter object) {
+        this.specification = object.getSpecification();
+    }
+
+    public DebugObjectSpecification(final ObjectSpecification object) {
+        this.specification = object;
+    }
+
+    @Override
+    public void debugData(final DebugBuilder debug) {
+        if (specification == null) {
+            debug.appendln("no specfication");
+        } else {
+            Dump.specification(specification, debug);
+        }
+    }
+
+    @Override
+    public String debugTitle() {
+        return "Object 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/debug/DebugOption.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/debug/DebugOption.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/debug/DebugOption.java
new file mode 100644
index 0000000..20bb5cc
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/debug/DebugOption.java
@@ -0,0 +1,79 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.isis.viewer.dnd.view.debug;
+
+import java.util.List;
+
+import com.google.common.collect.Lists;
+
+import org.apache.isis.core.commons.debug.DebuggableWithTitle;
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.spec.ActionType;
+import org.apache.isis.core.runtime.userprofile.PerspectiveEntry;
+import org.apache.isis.runtimes.dflt.runtime.userprofile.UserProfilesDebugUtil;
+import org.apache.isis.viewer.dnd.drawing.Location;
+import org.apache.isis.viewer.dnd.service.PerspectiveContent;
+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.Workspace;
+import org.apache.isis.viewer.dnd.view.option.UserActionAbstract;
+
+/**
+ * Display debug window
+ */
+public class DebugOption extends UserActionAbstract {
+    public DebugOption() {
+        super("Debug...", ActionType.DEBUG);
+    }
+
+    @Override
+    public void execute(final Workspace workspace, final View view, final Location at) {
+        final Content content = view.getContent();
+        final ObjectAdapter object = content == null ? null : content.getAdapter();
+
+        final List<DebuggableWithTitle> debug = Lists.newArrayList();
+        if (content instanceof PerspectiveContent) {
+            final PerspectiveEntry perspectiveEntry = ((PerspectiveContent) content).getPerspective();
+            debug.add(UserProfilesDebugUtil.asDebuggableWithTitle(perspectiveEntry));
+        } else {
+            debug.add(new DebugObjectSpecification(content.getSpecification()));
+        }
+        if (object != null) {
+            debug.add(new DebugAdapter(object));
+            debug.add(new DebugObjectGraph(object));
+        }
+
+        debug.add(new DebugViewStructure(view));
+        debug.add(new DebugContent(view));
+        debug.add(new DebugDrawing(view));
+        debug.add(new DebugDrawingAbsolute(view));
+
+        final DebuggableWithTitle[] info = debug.toArray(new DebuggableWithTitle[debug.size()]);
+        at.add(50, 6);
+        // at.getX() + 50, at.getY() + 6
+        Toolkit.getViewer().showDebugFrame(info, at);
+    }
+
+    @Override
+    public String getDescription(final View view) {
+        return "Open debug window about " + view;
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/eb613703/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/debug/DebugOutput.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/debug/DebugOutput.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/debug/DebugOutput.java
new file mode 100644
index 0000000..c58a96f
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/debug/DebugOutput.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.view.debug;
+
+import java.awt.FileDialog;
+import java.awt.Font;
+import java.awt.Frame;
+import java.awt.Graphics;
+import java.awt.PrintJob;
+import java.awt.Toolkit;
+import java.awt.datatransfer.Clipboard;
+import java.awt.datatransfer.StringSelection;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Properties;
+import java.util.StringTokenizer;
+
+import org.apache.isis.core.commons.debug.DebugString;
+import org.apache.isis.core.commons.debug.DebuggableWithTitle;
+import org.apache.isis.core.commons.exceptions.IsisException;
+
+public class DebugOutput {
+    private static final DateFormat FORMAT = new SimpleDateFormat("yyyyMMdd-HHmmss-SSS");
+    private static final Font TEXT_FONT = new Font("SansSerif", Font.PLAIN, 10);
+    private static final Font TITLE_FONT = new Font("SansSerif", Font.BOLD, 12);
+
+    public static void print(final String title, final String text) {
+        final Frame parent = new Frame();
+        final PrintJob job = Toolkit.getDefaultToolkit().getPrintJob(parent, "Print " + title, new Properties());
+
+        if (job != null) {
+            final Graphics graphic = job.getGraphics();
+            // Dimension pageSize = job.getPageDimension();
+
+            if (graphic != null) {
+                graphic.translate(10, 10);
+                final int x = 50;
+                int y = 50;
+
+                graphic.setFont(TITLE_FONT);
+
+                final int height = graphic.getFontMetrics().getAscent();
+                final int width = graphic.getFontMetrics().stringWidth(title);
+                graphic.drawRect(x - 10, y - 10 - height, width + 20, height + 20);
+
+                graphic.drawString(title, x, y);
+
+                y += graphic.getFontMetrics().getHeight();
+                y += 20;
+
+                graphic.setFont(TEXT_FONT);
+                final StringTokenizer tk = new StringTokenizer(text, "\n\r");
+                while (tk.hasMoreTokens()) {
+                    final String line = tk.nextToken();
+                    graphic.drawString(line, x, y);
+                    y += graphic.getFontMetrics().getHeight();
+                }
+
+                graphic.dispose();
+            }
+
+            job.end();
+        }
+        parent.dispose();
+    }
+
+    /*
+     * 
+     * 
+     * Frame frame = new Frame(); PrintJob job =
+     * Toolkit.getDefaultToolkit().getPrintJob(frame, "Print object", null);
+     * 
+     * if (job != null) { Graphics pg = job.getGraphics(); Dimension pageSize =
+     * job.getPageDimension();
+     * 
+     * if (pg != null) { pg.translate(LEFT, HEIGHT); pg.drawRect(0, 0,
+     * pageSize.width - LEFT - 1, pageSize.height - HEIGHT - 1); view.print(new
+     * PrintCanvas(pg, view)); pg.dispose(); }
+     * 
+     * job.end(); } frame.dispose();
+     */
+
+    public static void saveToClipboard(final String text) {
+        final Clipboard cb = Toolkit.getDefaultToolkit().getSystemClipboard();
+        final StringSelection ss = new StringSelection(text);
+        cb.setContents(ss, ss);
+    }
+
+    public static void saveToFile(final DebuggableWithTitle object) {
+        final String dateStamp = FORMAT.format(new Date());
+        final String fileName = object.getClass().getName() + "-" + dateStamp + ".txt";
+        final DebugString text = new DebugString();
+        object.debugData(text);
+        final String title = object.debugTitle();
+
+        saveToFile(new File(fileName), title, text.toString());
+    }
+
+    public static void saveToFile(final File file, final String title, final String text) {
+        try {
+            final PrintWriter writer = new PrintWriter(new FileWriter(file));
+            writer.println(title);
+            writer.println();
+            writer.println(text.toString());
+            writer.close();
+        } catch (final IOException e) {
+            throw new IsisException(e);
+        }
+    }
+
+    public static void saveToFile(final String saveDialogTitle, final String title, final String text) {
+        final Frame parent = new Frame();
+
+        final FileDialog dialog = new FileDialog(parent, saveDialogTitle, FileDialog.SAVE);
+        dialog.setVisible(true);
+        final String file = dialog.getFile();
+        final String dir = dialog.getDirectory();
+
+        parent.dispose();
+
+        saveToFile(new File(dir, file), title, text);
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/eb613703/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/debug/DebugView.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/debug/DebugView.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/debug/DebugView.java
new file mode 100644
index 0000000..66b8446
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/debug/DebugView.java
@@ -0,0 +1,124 @@
+/*
+ *  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.debug;
+
+import org.apache.isis.core.commons.debug.DebugBuilder;
+import org.apache.isis.core.commons.debug.DebuggableWithTitle;
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.util.Dump;
+import org.apache.isis.runtimes.dflt.runtime.system.context.IsisContext;
+import org.apache.isis.viewer.dnd.drawing.Bounds;
+import org.apache.isis.viewer.dnd.drawing.DebugCanvas;
+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.collection.CollectionContent;
+
+public class DebugView implements DebuggableWithTitle {
+    private final View view;
+
+    public DebugView(final View display) {
+        this.view = display;
+    }
+
+    @Override
+    public void debugData(final DebugBuilder debug) {
+        debug.append(view.getView());
+        debug.blankLine();
+        debug.blankLine();
+
+        // display details
+        debug.appendTitle("VIEW");
+
+        view.debug(debug);
+        debug.appendln();
+
+        // content
+        final Content content = view.getContent();
+        debug.appendTitle("CONTENT");
+        if (content != null) {
+            String type = content.getClass().getName();
+            type = type.substring(type.lastIndexOf('.') + 1);
+            debug.appendln("Content", type);
+            content.debugDetails(debug);
+
+            debug.indent();
+            debug.appendln("Icon name", content.getIconName());
+            debug.appendln("Icon ", content.getIconPicture(32));
+            debug.appendln("Window title", content.windowTitle());
+            debug.appendln("Persistable", content.isPersistable());
+            debug.appendln("Object", content.isObject());
+            debug.appendln("Collection", content.isCollection());
+
+            debug.appendln("Parseable", content.isTextParseable());
+            debug.unindent();
+        } else {
+            debug.appendln("Content", "none");
+        }
+        debug.blankLine();
+
+        if (content instanceof ObjectContent) {
+            final ObjectAdapter object = ((ObjectContent) content).getObject();
+            dumpObject(object, debug);
+            debug.blankLine();
+            dumpSpecification(object, debug);
+            debug.blankLine();
+            dumpGraph(object, debug);
+
+        } else if (content instanceof CollectionContent) {
+            final ObjectAdapter collection = ((CollectionContent) content).getCollection();
+            debug.blankLine();
+            dumpObject(collection, debug);
+            dumpSpecification(collection, debug);
+            debug.blankLine();
+            dumpGraph(collection, debug);
+        }
+
+        debug.append("\n\nDRAWING\n");
+        debug.append("------\n");
+        view.draw(new DebugCanvas(debug, new Bounds(view.getBounds())));
+    }
+
+    @Override
+    public String debugTitle() {
+        return "Debug: " + view + view == null ? "" : ("/" + view.getContent());
+    }
+
+    public void dumpGraph(final ObjectAdapter object, final DebugBuilder info) {
+        if (object != null) {
+            info.appendTitle("GRAPH");
+            Dump.graph(object, IsisContext.getAuthenticationSession(), info);
+        }
+    }
+
+    public void dumpObject(final ObjectAdapter object, final DebugBuilder info) {
+        if (object != null) {
+            info.appendTitle("OBJECT");
+            Dump.adapter(object, info);
+        }
+    }
+
+    private void dumpSpecification(final ObjectAdapter object, final DebugBuilder info) {
+        if (object != null) {
+            info.appendTitle("SPECIFICATION");
+            Dump.specification(object, info);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/eb613703/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/debug/DebugViewStructure.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/debug/DebugViewStructure.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/debug/DebugViewStructure.java
new file mode 100644
index 0000000..dd7ea75
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/debug/DebugViewStructure.java
@@ -0,0 +1,42 @@
+/*
+ *  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.debug;
+
+import org.apache.isis.core.commons.debug.DebugBuilder;
+import org.apache.isis.core.commons.debug.DebuggableWithTitle;
+import org.apache.isis.viewer.dnd.view.View;
+
+public class DebugViewStructure implements DebuggableWithTitle {
+    private final View view;
+
+    public DebugViewStructure(final View display) {
+        this.view = display;
+    }
+
+    @Override
+    public void debugData(final DebugBuilder debug) {
+        view.debug(debug);
+    }
+
+    @Override
+    public String debugTitle() {
+        return "View Structure";
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/eb613703/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/debug/LoggingOptions.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/debug/LoggingOptions.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/debug/LoggingOptions.java
new file mode 100644
index 0000000..6ec4102
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/debug/LoggingOptions.java
@@ -0,0 +1,62 @@
+/*
+ *  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.debug;
+
+import org.apache.log4j.Level;
+import org.apache.log4j.LogManager;
+
+import org.apache.isis.core.metamodel.consent.Consent;
+import org.apache.isis.core.metamodel.consent.ConsentAbstract;
+import org.apache.isis.core.metamodel.spec.ActionType;
+import org.apache.isis.viewer.dnd.drawing.Location;
+import org.apache.isis.viewer.dnd.view.MenuOptions;
+import org.apache.isis.viewer.dnd.view.UserActionSet;
+import org.apache.isis.viewer.dnd.view.View;
+import org.apache.isis.viewer.dnd.view.Workspace;
+import org.apache.isis.viewer.dnd.view.option.UserActionAbstract;
+
+public class LoggingOptions implements MenuOptions {
+
+    @Override
+    public void menuOptions(final UserActionSet options) {
+        options.add(loggingOption("Off", Level.OFF));
+        options.add(loggingOption("Error", Level.ERROR));
+        options.add(loggingOption("Warn", Level.WARN));
+        options.add(loggingOption("Info", Level.INFO));
+        options.add(loggingOption("Debug", Level.DEBUG));
+
+        options.add(new DebugDumpSnapshotOption());
+    }
+
+    private UserActionAbstract loggingOption(final String name, final Level level) {
+        return new UserActionAbstract("Log level " + level, ActionType.DEBUG) {
+            @Override
+            public Consent disabled(final View component) {
+                return ConsentAbstract.allowIf(LogManager.getRootLogger().getLevel() != level);
+            }
+
+            @Override
+            public void execute(final Workspace workspace, final View view, final Location at) {
+                LogManager.getRootLogger().setLevel(level);
+            }
+        };
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/eb613703/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/field/ClearOneToManyAssociationOption.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/field/ClearOneToManyAssociationOption.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/field/ClearOneToManyAssociationOption.java
new file mode 100644
index 0000000..530e19d
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/field/ClearOneToManyAssociationOption.java
@@ -0,0 +1,45 @@
+/*
+ *  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.field;
+
+import org.apache.isis.core.metamodel.consent.Consent;
+import org.apache.isis.viewer.dnd.drawing.Location;
+import org.apache.isis.viewer.dnd.view.View;
+import org.apache.isis.viewer.dnd.view.Workspace;
+import org.apache.isis.viewer.dnd.view.option.UserActionAbstract;
+
+public class ClearOneToManyAssociationOption extends UserActionAbstract {
+
+    public ClearOneToManyAssociationOption() {
+        super("Clear association");
+    }
+
+    @Override
+    public Consent disabled(final View view) {
+        final OneToManyFieldElement content = (OneToManyFieldElement) view.getContent();
+        return content.canClear();
+    }
+
+    @Override
+    public void execute(final Workspace frame, final View view, final Location at) {
+        final OneToManyFieldElement content = (OneToManyFieldElement) view.getContent();
+        content.clear();
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/eb613703/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/field/ClearOneToOneAssociationOption.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/field/ClearOneToOneAssociationOption.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/field/ClearOneToOneAssociationOption.java
new file mode 100644
index 0000000..44f0009
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/field/ClearOneToOneAssociationOption.java
@@ -0,0 +1,45 @@
+/*
+ *  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.field;
+
+import org.apache.isis.core.metamodel.consent.Consent;
+import org.apache.isis.viewer.dnd.drawing.Location;
+import org.apache.isis.viewer.dnd.view.View;
+import org.apache.isis.viewer.dnd.view.Workspace;
+import org.apache.isis.viewer.dnd.view.option.UserActionAbstract;
+
+public class ClearOneToOneAssociationOption extends UserActionAbstract {
+    public ClearOneToOneAssociationOption() {
+        super("Clear association");
+    }
+
+    @Override
+    public Consent disabled(final View view) {
+        final OneToOneField content = ((OneToOneField) view.getContent());
+        return content.canClear();
+    }
+
+    @Override
+    public void execute(final Workspace frame, final View view, final Location at) {
+        final OneToOneField content = ((OneToOneField) view.getContent());
+        content.clear();
+        view.getParent().invalidateContent();
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/eb613703/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/field/ObjectField.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/field/ObjectField.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/field/ObjectField.java
new file mode 100644
index 0000000..f1ddb42
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/field/ObjectField.java
@@ -0,0 +1,66 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.isis.viewer.dnd.view.field;
+
+import org.apache.isis.core.commons.debug.DebugBuilder;
+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;
+
+final class ObjectField {
+    private final ObjectAssociation field;
+    private final ObjectAdapter parent;
+
+    ObjectField(final ObjectAdapter parent, final ObjectAssociation field) {
+        this.parent = parent;
+        this.field = field;
+    }
+
+    public void debugDetails(final DebugBuilder debug) {
+        debug.appendln("field", getObjectAssociation());
+        debug.appendln("name", getName());
+        debug.appendln("specification", getSpecification());
+        debug.appendln("parent", parent);
+    }
+
+    public String getDescription() {
+        return field.getDescription();
+    }
+
+    public String getHelp() {
+        return field.getHelp();
+    }
+
+    public ObjectAssociation getObjectAssociation() {
+        return field;
+    }
+
+    public final String getName() {
+        return field.getName();
+    }
+
+    public ObjectAdapter getParent() {
+        return parent;
+    }
+
+    public ObjectSpecification getSpecification() {
+        return field.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/field/OneToManyField.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/field/OneToManyField.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/field/OneToManyField.java
new file mode 100644
index 0000000..b49375b
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/field/OneToManyField.java
@@ -0,0 +1,28 @@
+/*
+ *  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.field;
+
+import org.apache.isis.core.metamodel.spec.feature.OneToManyAssociation;
+import org.apache.isis.viewer.dnd.view.collection.CollectionContent;
+import org.apache.isis.viewer.dnd.view.content.FieldContent;
+
+public interface OneToManyField extends FieldContent, CollectionContent {
+    OneToManyAssociation getOneToManyAssociation();
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/eb613703/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/field/OneToManyFieldElement.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/field/OneToManyFieldElement.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/field/OneToManyFieldElement.java
new file mode 100644
index 0000000..15db170
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/field/OneToManyFieldElement.java
@@ -0,0 +1,27 @@
+/*
+ *  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.field;
+
+import org.apache.isis.viewer.dnd.view.ObjectContent;
+import org.apache.isis.viewer.dnd.view.content.FieldContent;
+
+public interface OneToManyFieldElement extends FieldContent, ObjectContent {
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/eb613703/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/field/OneToManyFieldElementImpl.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/field/OneToManyFieldElementImpl.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/field/OneToManyFieldElementImpl.java
new file mode 100644
index 0000000..118b4f7
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/field/OneToManyFieldElementImpl.java
@@ -0,0 +1,195 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.isis.viewer.dnd.view.field;
+
+import org.apache.log4j.Logger;
+
+import org.apache.isis.core.commons.debug.DebugBuilder;
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.consent.Consent;
+import org.apache.isis.core.metamodel.consent.Veto;
+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.runtimes.dflt.runtime.system.context.IsisContext;
+import org.apache.isis.viewer.dnd.view.UserActionSet;
+import org.apache.isis.viewer.dnd.view.content.AbstractObjectContent;
+
+public class OneToManyFieldElementImpl extends AbstractObjectContent implements OneToManyFieldElement {
+    private static final Logger LOG = Logger.getLogger(OneToManyFieldElementImpl.class);
+    private final ObjectAdapter element;
+    private final ObjectField field;
+
+    public OneToManyFieldElementImpl(final ObjectAdapter parent, final ObjectAdapter element, final OneToManyAssociation association) {
+        field = new ObjectField(parent, association);
+        this.element = element;
+    }
+
+    @Override
+    public Consent canClear() {
+        final ObjectAdapter parentObject = getParent();
+        final OneToManyAssociation association = getOneToManyAssociation();
+        final ObjectAdapter associatedObject = getObject();
+
+        final Consent isEditable = isEditable();
+        if (isEditable.isVetoed()) {
+            return isEditable;
+        }
+
+        final Consent consent = association.isValidToRemove(parentObject, associatedObject);
+        if (consent.isAllowed()) {
+            consent.setDescription("Clear the association to this object from '" + parentObject.titleString() + "'");
+        }
+        return consent;
+    }
+
+    @Override
+    public Consent isEditable() {
+        return getField().isUsable(IsisContext.getAuthenticationSession(), getParent(), where);
+    }
+
+    @Override
+    public Consent canSet(final ObjectAdapter dragSource) {
+        return Veto.DEFAULT;
+    }
+
+    @Override
+    public void clear() {
+        final ObjectAdapter parentObject = getParent();
+        final OneToManyAssociation association = getOneToManyAssociation();
+        LOG.debug("remove " + element + " from " + parentObject);
+        association.removeElement(parentObject, element);
+    }
+
+    @Override
+    public void debugDetails(final DebugBuilder debug) {
+        field.debugDetails(debug);
+        debug.appendln("element", element);
+    }
+
+    @Override
+    public String getFieldName() {
+        return field.getName();
+    }
+
+    @Override
+    public ObjectAssociation getField() {
+        return field.getObjectAssociation();
+    }
+
+    @Override
+    public ObjectAdapter getAdapter() {
+        return element;
+    }
+
+    @Override
+    public ObjectAdapter getObject() {
+        return element;
+    }
+
+    @Override
+    public ObjectAdapter[] getOptions() {
+        return null;
+    }
+
+    private OneToManyAssociation getOneToManyAssociation() {
+        return (OneToManyAssociation) field.getObjectAssociation();
+    }
+
+    @Override
+    public ObjectAdapter getParent() {
+        return field.getParent();
+    }
+
+    @Override
+    public ObjectSpecification getSpecification() {
+        return field.getSpecification();
+    }
+
+    @Override
+    public boolean isMandatory() {
+        return false;
+    }
+
+    @Override
+    public boolean isObject() {
+        return true;
+    }
+
+    @Override
+    public boolean isOptionEnabled() {
+        return false;
+    }
+
+    @Override
+    public boolean isTransient() {
+        return false;
+    }
+
+    @Override
+    public void contentMenuOptions(final UserActionSet options) {
+        // ObjectOption.menuOptions(element, options);
+        super.contentMenuOptions(options);
+        options.add(new ClearOneToManyAssociationOption());
+    }
+
+    @Override
+    public void setObject(final ObjectAdapter object) {
+        /*
+         * ObjectAdapter parentObject = getParent();
+         * OneToManyAssociationSpecification association =
+         * getOneToManyAssociation(); ObjectAdapter associatedObject =
+         * getObject(); LOG.debug("remove " + associatedObject + " from " +
+         * parentObject); association.clearAssociation(parentObject,
+         * associatedObject);
+         */
+
+    }
+
+    @Override
+    public String title() {
+        return element.titleString();
+    }
+
+    @Override
+    public String toString() {
+        return getObject() + "/" + field.getObjectAssociation();
+    }
+
+    @Override
+    public String windowTitle() {
+        return field.getName() + " element" + " for " + field.getParent().titleString();
+    }
+
+    @Override
+    public String getId() {
+        return getOneToManyAssociation().getName();
+    }
+
+    @Override
+    public String getDescription() {
+        return field.getName() + ": " + getOneToManyAssociation().getDescription();
+    }
+
+    @Override
+    public String getHelp() {
+        return getOneToManyAssociation().getHelp();
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/eb613703/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/field/OneToManyFieldImpl.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/field/OneToManyFieldImpl.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/field/OneToManyFieldImpl.java
new file mode 100644
index 0000000..1fdb9ab
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/field/OneToManyFieldImpl.java
@@ -0,0 +1,221 @@
+/*
+ *  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.field;
+
+import org.apache.isis.applib.annotation.Where;
+import org.apache.isis.core.commons.debug.DebugBuilder;
+import org.apache.isis.core.commons.exceptions.IsisException;
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.consent.Consent;
+import org.apache.isis.core.metamodel.consent.Veto;
+import org.apache.isis.core.metamodel.spec.ObjectSpecification;
+import org.apache.isis.core.metamodel.spec.feature.ObjectAssociation;
+import org.apache.isis.core.metamodel.spec.feature.OneToManyAssociation;
+import org.apache.isis.runtimes.dflt.runtime.system.context.IsisContext;
+import org.apache.isis.viewer.dnd.drawing.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.collection.AbstractCollectionContent;
+
+public class OneToManyFieldImpl extends AbstractCollectionContent implements OneToManyField {
+
+    // REVIEW: should provide this rendering context, rather than hardcoding.
+    // the net effect currently is that class members annotated with 
+    // @Hidden(where=Where.ANYWHERE) or @Disabled(where=Where.ANYWHERE) will indeed
+    // be hidden/disabled, but will be visible/enabled (perhaps incorrectly) 
+    // for any other value for Where
+    private final Where where = Where.ANYWHERE;
+
+    private final ObjectAdapter collection;
+    private final ObjectField field;
+
+    public OneToManyFieldImpl(final ObjectAdapter parent, final ObjectAdapter object, final OneToManyAssociation association) {
+        field = new ObjectField(parent, association);
+        this.collection = object;
+    }
+
+    @Override
+    public Consent canDrop(final Content sourceContent) {
+        if (sourceContent.getAdapter() instanceof ObjectAdapter) {
+            final ObjectAdapter sourceAdapter = sourceContent.getAdapter();
+            final ObjectAdapter parentAdapter = field.getParent();
+
+            final ObjectAdapter collection = getAdapter();
+            if (collection == null) {
+                // TODO: move logic into Facet
+                return new Veto("Collection not set up; can't add elements to a non-existant collection");
+            }
+
+            final Consent usableInState = getOneToManyAssociation().isUsable(IsisContext.getAuthenticationSession(), parentAdapter, where);
+            if (usableInState.isVetoed()) {
+                return usableInState;
+            }
+
+            final ObjectSpecification specification = sourceAdapter.getSpecification();
+            final ObjectSpecification elementSpecification = getElementSpecification();
+            if (!specification.isOfType(elementSpecification)) {
+                // TODO: move logic into Facet
+                return new Veto(String.format("Only objects of type %s are allowed in this collection", elementSpecification.getSingularName()));
+            }
+            if (parentAdapter.representsPersistent() && sourceAdapter.isTransient()) {
+                // TODO: move logic into Facet
+                return new Veto("Can't set field in persistent object with reference to non-persistent object");
+            }
+            return getOneToManyAssociation().isValidToAdd(parentAdapter, sourceAdapter);
+        } else {
+            return Veto.DEFAULT;
+        }
+    }
+
+    public Consent canSet(final ObjectAdapter dragSource) {
+        return Veto.DEFAULT;
+    }
+
+    @Override
+    public void contentMenuOptions(final UserActionSet options) {
+        super.contentMenuOptions(options);
+        // OptionFactory.addCreateOptions(getOneToManyAssociation().getSpecification(),
+        // options);
+    }
+
+    @Override
+    public void debugDetails(final DebugBuilder debug) {
+        field.debugDetails(debug);
+        debug.appendln("collection", collection);
+        super.debugDetails(debug);
+    }
+
+    @Override
+    public ObjectAdapter drop(final Content sourceContent) {
+        final ObjectAdapter object = sourceContent.getAdapter();
+        final ObjectAdapter parent = field.getParent();
+        final Consent perm = canDrop(sourceContent);
+        if (perm.isAllowed()) {
+            getOneToManyAssociation().addElement(parent, object);
+        }
+        return null;
+    }
+
+    @Override
+    public ObjectAdapter getCollection() {
+        return collection;
+    }
+
+    @Override
+    public String getDescription() {
+        final String name = getFieldName();
+        String type = getField().getSpecification().getSingularName();
+        type = name.indexOf(type) == -1 ? " (" + type + ")" : "";
+        final String description = getOneToManyAssociation().getDescription();
+        return name + type + " " + description;
+    }
+
+    @Override
+    public ObjectAssociation getField() {
+        return field.getObjectAssociation();
+    }
+
+    @Override
+    public String getFieldName() {
+        return field.getName();
+    }
+
+    @Override
+    public String getHelp() {
+        return getOneToManyAssociation().getHelp();
+    }
+
+    @Override
+    public String getIconName() {
+        return null;
+        // return "internal-collection";
+    }
+
+    @Override
+    public Image getIconPicture(final int iconHeight) {
+        final ObjectSpecification specification = getOneToManyAssociation().getSpecification();
+        Image icon = ImageFactory.getInstance().loadIcon(specification, iconHeight, null);
+        if (icon == null) {
+            icon = ImageFactory.getInstance().loadDefaultIcon(iconHeight, null);
+        }
+        return icon;
+    }
+
+    @Override
+    public String getId() {
+        return getOneToManyAssociation().getId();
+    }
+
+    @Override
+    public ObjectAdapter getAdapter() {
+        return collection;
+    }
+
+    @Override
+    public OneToManyAssociation getOneToManyAssociation() {
+        return (OneToManyAssociation) field.getObjectAssociation();
+    }
+
+    @Override
+    public ObjectAdapter getParent() {
+        return field.getParent();
+    }
+
+    @Override
+    public ObjectSpecification getSpecification() {
+        return field.getSpecification();
+    }
+
+    @Override
+    public Consent isEditable() {
+        return getField().isUsable(IsisContext.getAuthenticationSession(), getParent(), where);
+    }
+
+    @Override
+    public boolean isMandatory() {
+        return getOneToManyAssociation().isMandatory();
+    }
+
+    @Override
+    public boolean isTransient() {
+        return false;
+    }
+
+    public void setObject(final ObjectAdapter object) {
+        throw new IsisException("Invalid call");
+    }
+
+    @Override
+    public final String title() {
+        return field.getName();
+    }
+
+    @Override
+    public String toString() {
+        return collection + "/" + field.getObjectAssociation();
+    }
+
+    @Override
+    public String windowTitle() {
+        return title() + " for " + field.getParent().titleString();
+    }
+
+}