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

[51/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/view/action/ObjectParameterImpl.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/view/action/ObjectParameterImpl.java b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/view/action/ObjectParameterImpl.java
new file mode 100644
index 0000000..9df01a1
--- /dev/null
+++ b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/view/action/ObjectParameterImpl.java
@@ -0,0 +1,197 @@
+/*
+ *  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.action;
+
+import org.apache.isis.core.commons.debug.DebugBuilder;
+import org.apache.isis.core.commons.lang.ToString;
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.consent.Allow;
+import org.apache.isis.core.metamodel.consent.Consent;
+import org.apache.isis.core.metamodel.consent.Veto;
+import org.apache.isis.core.metamodel.spec.ObjectSpecification;
+import org.apache.isis.core.metamodel.spec.feature.OneToOneActionParameter;
+import org.apache.isis.viewer.dnd.drawing.Location;
+import org.apache.isis.viewer.dnd.view.UserActionSet;
+import org.apache.isis.viewer.dnd.view.View;
+import org.apache.isis.viewer.dnd.view.Workspace;
+import org.apache.isis.viewer.dnd.view.content.AbstractObjectContent;
+import org.apache.isis.viewer.dnd.view.option.UserActionAbstract;
+
+public class ObjectParameterImpl extends AbstractObjectContent implements ObjectParameter {
+    private final ObjectAdapter adapter;
+    private final ActionHelper invocation;
+    private final int index;
+    private final ObjectAdapter[] optionAdapters;
+    private final OneToOneActionParameter objectActionParameter;
+
+    public ObjectParameterImpl(final OneToOneActionParameter objectActionParameter, final ObjectAdapter adapter, final ObjectAdapter[] optionAdapters, final int i, final ActionHelper invocation) {
+        this.objectActionParameter = objectActionParameter;
+        this.optionAdapters = optionAdapters;
+        this.index = i;
+        this.invocation = invocation;
+        this.adapter = adapter;
+    }
+
+    public ObjectParameterImpl(final ObjectParameterImpl content, final ObjectAdapter object) {
+        objectActionParameter = content.objectActionParameter;
+        optionAdapters = content.optionAdapters;
+        index = content.index;
+        invocation = content.invocation;
+        this.adapter = object;
+    }
+
+    @Override
+    public Consent canClear() {
+        return Allow.DEFAULT;
+    }
+
+    @Override
+    public Consent canSet(final ObjectAdapter dragSource) {
+        if (dragSource.getSpecification().isOfType(getSpecification())) {
+            // TODO: move logic into Facet
+            return Allow.DEFAULT;
+        } else {
+            // TODO: move logic into Facet
+            return new Veto(String.format("Object must be ", getSpecification().getShortIdentifier()));
+        }
+    }
+
+    @Override
+    public void clear() {
+        setObject(null);
+    }
+
+    @Override
+    public void debugDetails(final DebugBuilder debug) {
+        debug.appendln("name", getParameterName());
+        debug.appendln("required", isRequired());
+        debug.appendln("object", adapter);
+    }
+
+    @Override
+    public ObjectAdapter getAdapter() {
+        return adapter;
+    }
+
+    @Override
+    public ObjectAdapter getObject() {
+        return adapter;
+    }
+
+    @Override
+    public ObjectAdapter[] getOptions() {
+        return optionAdapters;
+    }
+
+    @Override
+    public boolean isObject() {
+        return true;
+    }
+
+    @Override
+    public boolean isRequired() {
+        return !objectActionParameter.isOptional();
+    }
+
+    @Override
+    public boolean isPersistable() {
+        return false;
+    }
+
+    @Override
+    public boolean isOptionEnabled() {
+        return optionAdapters != null && optionAdapters.length > 0;
+    }
+
+    @Override
+    public boolean isTransient() {
+        return adapter != null && adapter.isTransient();
+    }
+
+    @Override
+    public void contentMenuOptions(final UserActionSet options) {
+        if (adapter != null) {
+            options.add(new UserActionAbstract("Clear parameter") {
+
+                @Override
+                public void execute(final Workspace workspace, final View view, final Location at) {
+                    clear();
+                    view.getParent().invalidateContent();
+                }
+            });
+
+            OptionFactory.addObjectMenuOptions(adapter, options);
+        } else {
+            OptionFactory.addCreateOptions(getSpecification(), options);
+
+        }
+
+    }
+
+    @Override
+    public void setObject(final ObjectAdapter object) {
+        invocation.setParameter(index, object);
+    }
+
+    @Override
+    public String title() {
+        return adapter == null ? "" : adapter.titleString();
+    }
+
+    @Override
+    public String toString() {
+        final ToString toString = new ToString(this);
+        toString.append("label", getParameterName());
+        toString.append("required", isRequired());
+        toString.append("spec", getSpecification().getFullIdentifier());
+        toString.append("object", adapter == null ? "null" : adapter.titleString());
+        return toString.toString();
+    }
+
+    @Override
+    public String getParameterName() {
+        return objectActionParameter.getName();
+    }
+
+    @Override
+    public ObjectSpecification getSpecification() {
+        return objectActionParameter.getSpecification();
+    }
+
+    @Override
+    public String getDescription() {
+        final String title = adapter == null ? "" : ": " + adapter.titleString();
+        final String name = getParameterName();
+        final ObjectSpecification specification = objectActionParameter.getSpecification();
+        final String specName = specification.getShortIdentifier();
+        final String type = name.indexOf(specName) == -1 ? " (" + specName + ")" : "";
+        return name + type + title + " " + objectActionParameter.getDescription();
+    }
+
+    @Override
+    public String getHelp() {
+        return invocation.getHelp();
+    }
+
+    @Override
+    public String getId() {
+        return null;
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/255ef514/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/view/action/OptionFactory.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/view/action/OptionFactory.java b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/view/action/OptionFactory.java
new file mode 100644
index 0000000..f3e5260
--- /dev/null
+++ b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/view/action/OptionFactory.java
@@ -0,0 +1,91 @@
+/*
+ *  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.action;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.adapter.oid.Oid;
+import org.apache.isis.core.metamodel.spec.ActionType;
+import org.apache.isis.core.metamodel.spec.FreeStandingList;
+import org.apache.isis.core.metamodel.spec.ObjectSpecification;
+import org.apache.isis.core.metamodel.spec.feature.ObjectAction;
+import org.apache.isis.core.metamodel.spec.feature.ObjectActionContainer.Contributed;
+import org.apache.isis.viewer.dnd.view.UserAction;
+import org.apache.isis.viewer.dnd.view.UserActionSet;
+import org.apache.isis.viewer.dnd.view.option.DisposeObjectOption;
+
+public class OptionFactory {
+
+    public static void addCreateOptions(final ObjectSpecification specification, final UserActionSet options) {
+        // TODO do the same as addObjectMenuOptions and collect together all the
+        // actions for all the types
+        final List<ObjectAction> actions = specification.getServiceActionsReturning(Arrays.asList(ActionType.USER, ActionType.EXPLORATION, ActionType.PROTOTYPE, ActionType.DEBUG));
+        menuOptions(actions, null, options);
+    }
+
+    public static void addObjectMenuOptions(final ObjectAdapter adapter, final UserActionSet options) {
+        if (adapter == null) {
+            return;
+        }
+
+        final ObjectSpecification noSpec = adapter.getSpecification();
+        menuOptions(noSpec.getObjectActions(Arrays.asList(ActionType.USER, ActionType.EXPLORATION, ActionType.PROTOTYPE, ActionType.DEBUG), Contributed.INCLUDED), adapter, options);
+
+        // TODO: this looks like a bit of a hack; can we improve it by looking
+        // at the facets?
+        if (adapter.getObject() instanceof FreeStandingList) {
+            return;
+        }
+        final Oid oid = adapter.getOid();
+        if (oid != null && adapter.isTransient()) {
+            return;
+        }
+        if (noSpec.isService()) {
+            return;
+        }
+
+        options.add(new DisposeObjectOption());
+    }
+
+    private static void menuOptions(final List<ObjectAction> actions, final ObjectAdapter target, final UserActionSet menuOptionSet) {
+        for (int i = 0; i < actions.size(); i++) {
+            UserAction option = null;
+            if (actions.get(i).getActions().size() > 0) {
+                option = menuOptionSet.addNewActionSet(actions.get(i).getName());
+                menuOptions(actions.get(i).getActions(), target, (UserActionSet) option);
+
+            } else {
+                final int noOfParameters = actions.get(i).getParameterCount();
+                if (noOfParameters == 0) {
+                    option = ImmediateObjectOption.createOption(actions.get(i), target);
+                } else if (actions.get(i).isContributed() && noOfParameters == 1 && target != null && target.getSpecification().isOfType(actions.get(i).getParameters().get(0).getSpecification())) {
+                    option = ImmediateObjectOption.createServiceOption(actions.get(i), target);
+                } else {
+                    option = DialoggedObjectOption.createOption(actions.get(i), target);
+                }
+                if (option != null) {
+                    menuOptionSet.add(option);
+                }
+            }
+        }
+    }
+}

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

http://git-wip-us.apache.org/repos/asf/isis/blob/255ef514/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/view/action/TextParseableParameter.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/view/action/TextParseableParameter.java b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/view/action/TextParseableParameter.java
new file mode 100644
index 0000000..3834146
--- /dev/null
+++ b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/view/action/TextParseableParameter.java
@@ -0,0 +1,26 @@
+/*
+ *  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.action;
+
+import org.apache.isis.viewer.dnd.view.content.TextParseableContent;
+
+public interface TextParseableParameter extends ParameterContent, TextParseableContent {
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/255ef514/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/view/action/TextParseableParameterImpl.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/view/action/TextParseableParameterImpl.java b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/view/action/TextParseableParameterImpl.java
new file mode 100644
index 0000000..3d00069
--- /dev/null
+++ b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/view/action/TextParseableParameterImpl.java
@@ -0,0 +1,228 @@
+/*
+ *  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.action;
+
+import org.apache.isis.applib.profiles.Localization;
+import org.apache.isis.core.commons.debug.DebugBuilder;
+import org.apache.isis.core.commons.lang.ToString;
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.adapter.util.AdapterUtils;
+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.facets.object.parseable.InvalidEntryException;
+import org.apache.isis.core.metamodel.facets.object.parseable.ParseableFacet;
+import org.apache.isis.core.metamodel.spec.ObjectSpecification;
+import org.apache.isis.core.metamodel.spec.feature.ParseableEntryActionParameter;
+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.content.AbstractTextParsableContent;
+
+public class TextParseableParameterImpl extends AbstractTextParsableContent implements TextParseableParameter {
+    private ObjectAdapter object;
+    private final ObjectAdapter[] options;
+    private final ParseableEntryActionParameter parameter;
+    private final ActionHelper invocation;
+    private final int index;
+
+    public TextParseableParameterImpl(final ParseableEntryActionParameter objectActionParameters, final ObjectAdapter adapter, final ObjectAdapter[] options, final int i, final ActionHelper invocation) {
+        this.parameter = objectActionParameters;
+        this.options = options;
+        index = i;
+        this.invocation = invocation;
+        object = adapter;
+    }
+
+    @Override
+    public void debugDetails(final DebugBuilder debug) {
+        debug.appendln("name", parameter.getName());
+        debug.appendln("required", isRequired());
+        debug.appendln("object", object);
+    }
+
+    @Override
+    public void entryComplete() {
+    }
+
+    @Override
+    public String getIconName() {
+        return "";
+    }
+
+    @Override
+    public Image getIconPicture(final int iconHeight) {
+        return ImageFactory.getInstance().loadIcon("value", 12, null);
+    }
+
+    @Override
+    public ObjectAdapter getAdapter() {
+        return object;
+    }
+
+    @Override
+    public int getNoLines() {
+        return parameter.getNoLines();
+    }
+
+    @Override
+    public ObjectAdapter[] getOptions() {
+        return options;
+    }
+
+    @Override
+    public boolean isEmpty() {
+        return object == null;
+    }
+
+    @Override
+    public boolean isRequired() {
+        return !parameter.isOptional();
+    }
+
+    @Override
+    public Consent canClear() {
+        return Allow.DEFAULT;
+    }
+
+    @Override
+    public boolean canWrap() {
+        return parameter.canWrap();
+    }
+
+    @Override
+    public void clear() {
+        object = null;
+    }
+
+    @Override
+    public boolean isTransient() {
+        return true;
+    }
+
+    @Override
+    public boolean isTextParseable() {
+        return true;
+    }
+
+    @Override
+    public boolean isOptionEnabled() {
+        return options != null && options.length > 0;
+    }
+
+    @Override
+    public String title() {
+        return AdapterUtils.titleString(object);
+    }
+
+    @Override
+    public String toString() {
+        final ToString toString = new ToString(this);
+        toString.append("object", object);
+        return toString.toString();
+    }
+
+    @Override
+    public String getParameterName() {
+        return parameter.getName();
+    }
+
+    @Override
+    public ObjectSpecification getSpecification() {
+        return parameter.getSpecification();
+    }
+
+    @Override
+    public ObjectAdapter drop(final Content sourceContent) {
+        return null;
+    }
+
+    @Override
+    public Consent canDrop(final Content sourceContent) {
+        return Veto.DEFAULT;
+    }
+
+    @Override
+    public String titleString(final ObjectAdapter value) {
+        return titleString(value, parameter, parameter.getSpecification());
+    }
+
+    /**
+     * @throws InvalidEntryException
+     *             - turns the parameter red if invalid.
+     */
+    @Override
+    public void parseTextEntry(final String entryText) {
+        object = parse(entryText);
+        Localization localization = IsisContext.getLocalization(); 
+        final String reason = parameter.isValid(object, AdapterUtils.unwrap(object), localization);
+        if (reason != null) {
+            throw new InvalidEntryException(reason);
+        } else if (!parameter.isOptional() && object == null) {
+            throw new InvalidEntryException("Mandatory parameter cannot be empty");
+        }
+        invocation.setParameter(index, object);
+    }
+
+    private ObjectAdapter parse(final String entryText) {
+        final ObjectSpecification parameterSpecification = parameter.getSpecification();
+        final ParseableFacet p = parameterSpecification.getFacet(ParseableFacet.class);
+        try {
+            Localization localization = IsisContext.getLocalization(); 
+            return p.parseTextEntry(object, entryText, localization);
+        } catch (final IllegalArgumentException ex) {
+            throw new InvalidEntryException(ex.getMessage(), ex);
+        }
+    }
+
+    @Override
+    public String getDescription() {
+        final String title = object == null ? "" : ": " + object.titleString();
+        final String specification = getSpecification().getShortIdentifier();
+        final String type = getParameterName().indexOf(specification) == -1 ? "" : " (" + specification + ")";
+        return getParameterName() + type + title + " " + parameter.getDescription();
+    }
+
+    @Override
+    public String getHelp() {
+        return null;
+    }
+
+    @Override
+    public String getId() {
+        return null;
+    }
+
+    @Override
+    public Consent isEditable() {
+        return Allow.DEFAULT;
+    }
+
+    @Override
+    public int getMaximumLength() {
+        return parameter.getMaximumLength();
+    }
+
+    @Override
+    public int getTypicalLineLength() {
+        return parameter.getTypicalLineLength();
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/255ef514/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/view/axis/LabelAxis.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/view/axis/LabelAxis.java b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/view/axis/LabelAxis.java
new file mode 100644
index 0000000..effcd5d
--- /dev/null
+++ b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/view/axis/LabelAxis.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.axis;
+
+import org.apache.isis.core.commons.lang.ToString;
+import org.apache.isis.viewer.dnd.view.ViewAxis;
+
+public class LabelAxis implements ViewAxis {
+    private int width;
+
+    public LabelAxis() {
+    }
+
+    public void accommodateWidth(final int width) {
+        this.width = Math.max(this.width, width);
+    }
+
+    public int getWidth() {
+        return width;
+    }
+
+    @Override
+    public String toString() {
+        final ToString str = new ToString(this);
+        str.append("width", width);
+        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/view/base/AbstractBorder.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/view/base/AbstractBorder.java b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/view/base/AbstractBorder.java
new file mode 100644
index 0000000..3dab885
--- /dev/null
+++ b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/view/base/AbstractBorder.java
@@ -0,0 +1,289 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.isis.viewer.dnd.view.base;
+
+import org.apache.isis.core.commons.debug.DebugBuilder;
+import org.apache.isis.viewer.dnd.drawing.Bounds;
+import org.apache.isis.viewer.dnd.drawing.Canvas;
+import org.apache.isis.viewer.dnd.drawing.Color;
+import org.apache.isis.viewer.dnd.drawing.ColorsAndFonts;
+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.Click;
+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.Toolkit;
+import org.apache.isis.viewer.dnd.view.View;
+import org.apache.isis.viewer.dnd.view.ViewAreaType;
+
+public class AbstractBorder extends AbstractViewDecorator {
+    protected int bottom;
+    protected int left;
+    private boolean onBorder;
+    protected int right;
+    protected int top;
+
+    protected AbstractBorder(final View view) {
+        super(view);
+    }
+
+    protected Bounds contentArea() {
+        return new Bounds(getLeft(), getTop(), getSize().getWidth() - getLeft() - getRight(), getSize().getHeight() - getTop() - getBottom());
+    }
+
+    @Override
+    public View dragFrom(final Location location) {
+        location.subtract(getLeft(), getTop());
+        return super.dragFrom(location);
+    }
+
+    @Override
+    public void dragIn(final ContentDrag drag) {
+        drag.subtract(getLeft(), getTop());
+        super.dragIn(drag);
+    }
+
+    @Override
+    public void dragOut(final ContentDrag drag) {
+        drag.subtract(getLeft(), getTop());
+        super.dragOut(drag);
+    }
+
+    @Override
+    public DragEvent dragStart(final DragStart drag) {
+        if (overContent(drag.getLocation())) {
+            drag.subtract(getLeft(), getTop());
+            return super.dragStart(drag);
+        } else {
+            return null;
+        }
+    }
+
+    protected void clearBackground(final Canvas canvas, final Color color) {
+        final Bounds bounds = getView().getBounds();
+        canvas.drawSolidRectangle(0, 0, bounds.getWidth(), bounds.getHeight(), color);
+    }
+
+    @Override
+    public void draw(final Canvas canvas) {
+        if (Toolkit.debug) {
+            canvas.drawDebugOutline(new Bounds(getSize()), getBaseline(), Toolkit.getColor(ColorsAndFonts.COLOR_DEBUG_BOUNDS_BORDER));
+        }
+        final int width = getSize().getWidth() - getRight();
+        final int height = getSize().getHeight() - getBottom();
+        final Canvas subcanvas = canvas.createSubcanvas(getLeft(), getTop(), width, height);
+        wrappedView.draw(subcanvas);
+    }
+
+    @Override
+    public void drop(final ContentDrag drag) {
+        drag.subtract(getLeft(), getTop());
+        super.drop(drag);
+    }
+
+    @Override
+    public void firstClick(final Click click) {
+        if (overContent(click.getLocation())) {
+            click.subtract(getLeft(), getTop());
+            wrappedView.firstClick(click);
+        }
+    }
+
+    @Override
+    public int getBaseline() {
+        return wrappedView.getBaseline() + getTop();
+    }
+
+    protected int getBottom() {
+        return bottom;
+    }
+
+    protected int getLeft() {
+        return left;
+    }
+
+    @Override
+    public Padding getPadding() {
+        final Padding padding = wrappedView.getPadding();
+        padding.extendTop(getTop());
+        padding.extendLeft(getLeft());
+        padding.extendBottom(getBottom());
+        padding.extendRight(getRight());
+
+        return padding;
+    }
+
+    @Override
+    public Size getRequiredSize(final Size availableSpace) {
+        availableSpace.contract(getLeft() + getRight(), getTop() + getBottom());
+        final Size size = wrappedView.getRequiredSize(availableSpace);
+        size.extend(getLeft() + getRight(), getTop() + getBottom());
+        return size;
+    }
+
+    protected int getRight() {
+        return right;
+    }
+
+    @Override
+    public Size getSize() {
+        final Size size = wrappedView.getSize();
+        size.extend(getLeft() + getRight(), getTop() + getBottom());
+
+        return size;
+    }
+
+    protected int getTop() {
+        return top;
+    }
+
+    @Override
+    protected void debugDetails(final DebugBuilder debug) {
+        super.debugDetails(debug);
+        debug.appendln("border", getTop() + "/" + getBottom() + " " + getLeft() + "/" + getRight() + " (top/bottom left/right)");
+        debug.appendln("contents", contentArea());
+    }
+
+    protected boolean overBorder(final Location location) {
+        return !contentArea().contains(location);
+    }
+
+    protected boolean overContent(final Location location) {
+        return contentArea().contains(location);
+    }
+
+    protected boolean isOnBorder() {
+        return onBorder;
+    }
+
+    @Override
+    public View identify(final Location location) {
+        getViewManager().getSpy().addTrace(this, "mouse location within border", location);
+        getViewManager().getSpy().addTrace(this, "non border area", contentArea());
+
+        if (overBorder(location)) {
+            getViewManager().getSpy().addTrace(this, "over border area", contentArea());
+            return getView();
+        } else {
+            location.add(-getLeft(), -getTop());
+            return super.identify(location);
+        }
+
+    }
+
+    @Override
+    public void mouseDown(final Click click) {
+        if (overContent(click.getLocation())) {
+            click.subtract(getLeft(), getTop());
+            wrappedView.mouseDown(click);
+        }
+    }
+
+    @Override
+    public void mouseMoved(final Location at) {
+        final boolean on = overBorder(at);
+        if (onBorder != on) {
+            markDamaged();
+            onBorder = on;
+        }
+
+        if (!on) {
+            at.move(-getLeft(), -getTop());
+            wrappedView.mouseMoved(at);
+        }
+    }
+
+    @Override
+    public void mouseUp(final Click click) {
+        if (overContent(click.getLocation())) {
+            click.subtract(getLeft(), getTop());
+            wrappedView.mouseUp(click);
+        }
+    }
+
+    @Override
+    public void exited() {
+        onBorder = false;
+        super.exited();
+    }
+
+    @Override
+    public View pickupContent(final Location location) {
+        location.subtract(getLeft(), getTop());
+        return super.pickupContent(location);
+    }
+
+    @Override
+    public View pickupView(final Location location) {
+        if (overBorder(location)) {
+            return Toolkit.getViewFactory().createDragViewOutline(getView());
+        } else {
+            location.subtract(getLeft(), getTop());
+            return super.pickupView(location);
+        }
+    }
+
+    @Override
+    public void secondClick(final Click click) {
+        if (overContent(click.getLocation())) {
+            click.subtract(getLeft(), getTop());
+            wrappedView.secondClick(click);
+        }
+    }
+
+    @Override
+    public void setSize(final Size size) {
+        final Size wrappedViewSize = new Size(size);
+        wrappedViewSize.contract(getLeft() + getRight(), getTop() + getBottom());
+        wrappedView.setSize(wrappedViewSize);
+    }
+
+    @Override
+    public void setBounds(final Bounds bounds) {
+        final Bounds wrappedViewBounds = new Bounds(bounds);
+        wrappedViewBounds.contract(getLeft() + getRight(), getTop() + getBottom());
+        wrappedView.setBounds(wrappedViewBounds);
+    }
+
+    @Override
+    public void thirdClick(final Click click) {
+        if (overContent(click.getLocation())) {
+            click.subtract(getLeft(), getTop());
+            wrappedView.thirdClick(click);
+        }
+    }
+
+    @Override
+    public ViewAreaType viewAreaType(final Location mouseLocation) {
+        final Size size = wrappedView.getSize();
+        final Bounds bounds = new Bounds(getLeft(), getTop(), size.getWidth(), size.getHeight());
+
+        if (bounds.contains(mouseLocation)) {
+            mouseLocation.subtract(getLeft(), getTop());
+
+            return wrappedView.viewAreaType(mouseLocation);
+        } else {
+            return ViewAreaType.VIEW;
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/255ef514/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/view/base/AbstractFieldSpecification.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/view/base/AbstractFieldSpecification.java b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/view/base/AbstractFieldSpecification.java
new file mode 100644
index 0000000..79e098f
--- /dev/null
+++ b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/view/base/AbstractFieldSpecification.java
@@ -0,0 +1,56 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.isis.viewer.dnd.view.base;
+
+import org.apache.isis.viewer.dnd.view.ViewRequirement;
+import org.apache.isis.viewer.dnd.view.ViewSpecification;
+
+public abstract class AbstractFieldSpecification implements ViewSpecification {
+    @Override
+    public boolean canDisplay(final ViewRequirement requirement) {
+        return requirement.isTextParseable() && requirement.isEditable();
+    }
+
+    @Override
+    public boolean isOpen() {
+        return false;
+    }
+
+    @Override
+    public boolean isReplaceable() {
+        return true;
+    }
+
+    @Override
+    public boolean isSubView() {
+        return true;
+    }
+
+    @Override
+    public boolean isAligned() {
+        return false;
+    }
+
+    @Override
+    public boolean isResizeable() {
+        return false;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/255ef514/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/view/base/AbstractFocusManager.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/view/base/AbstractFocusManager.java b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/view/base/AbstractFocusManager.java
new file mode 100644
index 0000000..22f5f1f
--- /dev/null
+++ b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/view/base/AbstractFocusManager.java
@@ -0,0 +1,203 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.isis.viewer.dnd.view.base;
+
+import org.apache.isis.core.commons.ensure.Assert;
+import org.apache.isis.core.commons.lang.ToString;
+import org.apache.isis.viewer.dnd.view.FocusManager;
+import org.apache.isis.viewer.dnd.view.View;
+
+/**
+ * Abstract focus manager that uses the set of views to move focus between from
+ * the concrete subclass.
+ * 
+ * @see #getChildViews()
+ */
+public abstract class AbstractFocusManager implements FocusManager {
+    // TODO container to go in subclass ??
+    protected View container;
+    protected View focus;
+    private final View initialFocus;
+
+    public AbstractFocusManager(final View container) {
+        this(container, null);
+    }
+
+    public AbstractFocusManager(final View container, final View initalFocus) {
+        Assert.assertNotNull(container);
+        this.container = container;
+        this.initialFocus = initalFocus;
+        focus = initalFocus;
+    }
+
+    /**
+     * Throws a ObjectAdapterRuntimeException if the specified view is available
+     * to this focus manager.
+     */
+    private void checkCanFocusOn(final View view) {
+        final View[] views = getChildViews();
+        boolean valid = view == container.getView();
+        for (int j = 0; valid == false && j < views.length; j++) {
+            if (views[j] == view) {
+                valid = true;
+            }
+        }
+
+        if (!valid) {
+            // throw new ObjectAdapterRuntimeException("No view " + view +
+            // " to focus on in " +
+            // container.getView());
+        }
+    }
+
+    @Override
+    public void focusFirstChildView() {
+        final View[] views = getChildViews();
+        for (final View view : views) {
+            if (view.canFocus()) {
+                setFocus(view);
+                return;
+            }
+        }
+        // no other focusable view; stick with the view we've got
+        return;
+    }
+
+    @Override
+    public void focusInitialChildView() {
+        if (initialFocus == null) {
+            focusFirstChildView();
+        } else {
+            setFocus(initialFocus);
+        }
+    }
+
+    @Override
+    public void focusLastChildView() {
+        final View[] views = getChildViews();
+        for (int j = views.length - 1; j > 0; j--) {
+            if (views[j].canFocus()) {
+                setFocus(views[j]);
+                return;
+            }
+        }
+        // no other focusable view; stick with the view we've got
+        return;
+    }
+
+    @Override
+    public void focusNextView() {
+        final View[] views = getChildViews();
+        for (int i = 0; i < views.length; i++) {
+            if (testView(views, i)) {
+                for (int j = i + 1; j < views.length; j++) {
+                    if (views[j].canFocus()) {
+                        setFocus(views[j]);
+                        return;
+                    }
+                }
+                for (int j = 0; j < i; j++) {
+                    if (views[j].canFocus()) {
+                        setFocus(views[j]);
+                        return;
+                    }
+                }
+                // no other focusable view; stick with the view we've got
+                return;
+            }
+        }
+
+        // throw new ObjectAdapterRuntimeException();
+    }
+
+    private boolean testView(final View[] views, final int i) {
+        final View view = views[i];
+        return view == focus;
+    }
+
+    @Override
+    public void focusParentView() {
+        container.getFocusManager().setFocus(container.getFocusManager().getFocus());
+    }
+
+    @Override
+    public void focusPreviousView() {
+        final View[] views = getChildViews();
+        if (views.length > 1) {
+            for (int i = 0; i < views.length; i++) {
+                if (testView(views, i)) {
+                    for (int j = i - 1; j >= 0; j--) {
+                        if (views[j].canFocus()) {
+                            setFocus(views[j]);
+                            return;
+                        }
+                    }
+                    for (int j = views.length - 1; j > i; j--) {
+                        if (views[j].canFocus()) {
+                            setFocus(views[j]);
+                            return;
+                        }
+                    }
+                    // no other focusable view; stick with the view we've got
+                    return;
+                }
+            }
+
+            // Don't move to any view
+            // throw new
+            // ObjectAdapterRuntimeException("Can't move to previous peer from "
+            // + focus);
+        }
+    }
+
+    protected abstract View[] getChildViews();
+
+    @Override
+    public View getFocus() {
+        return focus;
+    }
+
+    @Override
+    public void setFocus(final View view) {
+        checkCanFocusOn(view);
+
+        if (view != null && view.canFocus()) {
+            if ((focus != null) && (focus != view)) {
+                focus.focusLost();
+                focus.markDamaged();
+            }
+
+            focus = view;
+            focus.focusReceived();
+
+            view.markDamaged();
+        }
+    }
+
+    @Override
+    public String toString() {
+        final ToString str = new ToString(this);
+        str.append("container", container);
+        str.append("initialFocus", initialFocus);
+        str.append("focus", focus);
+        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/view/base/AbstractView.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/view/base/AbstractView.java b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/view/base/AbstractView.java
new file mode 100644
index 0000000..79ae4eb
--- /dev/null
+++ b/component/viewer/dnd/src/main/java/org/apache/isis/viewer/dnd/view/base/AbstractView.java
@@ -0,0 +1,987 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.isis.viewer.dnd.view.base;
+
+import java.util.Enumeration;
+
+import org.apache.log4j.Logger;
+
+import org.apache.isis.core.commons.debug.DebugBuilder;
+import org.apache.isis.core.commons.exceptions.IsisException;
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.consent.Consent;
+import org.apache.isis.core.metamodel.consent.ConsentAbstract;
+import org.apache.isis.core.metamodel.consent.Veto;
+import org.apache.isis.core.metamodel.facets.collections.modify.CollectionFacet;
+import org.apache.isis.core.metamodel.spec.ActionType;
+import org.apache.isis.core.metamodel.spec.ObjectSpecification;
+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.Color;
+import org.apache.isis.viewer.dnd.drawing.ColorsAndFonts;
+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.util.Properties;
+import org.apache.isis.viewer.dnd.util.ViewerException;
+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.ObjectContent;
+import org.apache.isis.viewer.dnd.view.Placement;
+import org.apache.isis.viewer.dnd.view.Toolkit;
+import org.apache.isis.viewer.dnd.view.UndoStack;
+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.ViewAxis;
+import org.apache.isis.viewer.dnd.view.ViewDrag;
+import org.apache.isis.viewer.dnd.view.ViewRequirement;
+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.collection.CollectionContent;
+import org.apache.isis.viewer.dnd.view.collection.RootCollection;
+import org.apache.isis.viewer.dnd.view.content.FieldContent;
+import org.apache.isis.viewer.dnd.view.content.RootObject;
+import org.apache.isis.viewer.dnd.view.option.OpenViewOption;
+import org.apache.isis.viewer.dnd.view.option.UserActionAbstract;
+
+public abstract class AbstractView implements View {
+    private static final Logger LOG = Logger.getLogger(AbstractView.class);
+
+    private static int nextId = 0;
+    private int id = 0;
+    private View parent;
+    private View viewRoot;
+    private ViewSpecification specification;
+    private Content content;
+    private final ViewState state;
+    private int x;
+    private int y;
+    private int height;
+    private int width;
+
+    protected AbstractView(final Content content) {
+        this(content, null);
+    }
+
+    protected AbstractView(final Content content, final ViewSpecification specification) {
+        if (content == null) {
+            throw new IllegalArgumentException("Content not specified");
+        }
+        assignId();
+        this.content = content;
+        this.specification = specification;
+        state = new ViewState();
+        viewRoot = this;
+    }
+
+    @Override
+    public void addView(final View view) {
+        throw new IsisException("Can't add views to " + this);
+    }
+
+    protected void assignId() {
+        id = nextId++;
+    }
+
+    @Override
+    public Consent canChangeValue() {
+        return Veto.DEFAULT;
+    }
+
+    @Override
+    public boolean canFocus() {
+        return true;
+    }
+
+    @Override
+    public boolean contains(final View view) {
+        final View[] subviews = getSubviews();
+        for (final View subview : subviews) {
+            if (subview == view || (subview != null && subview.contains(view))) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public boolean containsFocus() {
+        if (hasFocus()) {
+            return true;
+        }
+
+        final View[] subviews = getSubviews();
+        for (final View subview : subviews) {
+            if (subview != null && subview.containsFocus()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public void contentMenuOptions(final UserActionSet options) {
+        options.setColor(Toolkit.getColor(ColorsAndFonts.COLOR_MENU_CONTENT));
+
+        final Content content = getContent();
+        if (content != null) {
+            content.contentMenuOptions(options);
+        }
+    }
+
+    /**
+     * Returns debug details about this view.
+     */
+    @Override
+    public void debug(final DebugBuilder debug) {
+        final String name = getClass().getName();
+        debug.appendln("Root: " + name.substring(name.lastIndexOf('.') + 1) + getId());
+        debug.indent();
+        debug.appendln("required size", getRequiredSize(Size.createMax()));
+        debug.appendln("given size", getSize());
+        debug.appendln("padding", getPadding());
+        debug.appendln("base line", getBaseline());
+        debug.unindent();
+        debug.appendln();
+
+        debug.appendTitle("Specification");
+        if (specification == null) {
+            debug.append("\none");
+        } else {
+            debug.appendln(specification.getName());
+            debug.appendln("  " + specification.getClass().getName());
+            debug.appendln("  " + (specification.isOpen() ? "open" : "closed"));
+            debug.appendln("  " + (specification.isReplaceable() ? "replaceable" : "non-replaceable"));
+            debug.appendln("  " + (specification.isSubView() ? "subview" : "main view"));
+        }
+
+        debug.appendln();
+        debug.appendTitle("View");
+
+        debug.appendln("Self", getView());
+        debug.appendln("Parent's size", getParent() == null ? new Size() : getParent().getSize());
+        debug.appendln("Size w/in parent", getView().getRequiredSize(getParent() == null ? new Size() : getParent().getSize()));
+        debug.appendln("Location w/in parent", getView().getLocation());
+        debug.appendln("Changable", canChangeValue());
+        debug.appendln("Focus", (canFocus() ? "focusable" : "non-focusable"));
+        debug.appendln("Has focus", hasFocus());
+        debug.appendln("Contains focus", containsFocus());
+        debug.appendln("Focus manager", getFocusManager());
+        debug.appendln("State", getState());
+        debug.appendln("Axes", getViewAxes());
+        appendDebug(debug);
+
+        debug.appendln("Workspace", getWorkspace());
+
+        View p = getParent();
+        debug.appendln("Parent hierarchy:" + (p == null ? "none" : ""));
+        debug.indent();
+        while (p != null) {
+            debug.appendln(p.toString());
+            p = p.getParent();
+        }
+        debug.unindent();
+
+        debug.appendln();
+        debug.appendln();
+        debug.appendln();
+
+        debug.appendTitle("View structure");
+        // b.appendln("Built", (buildInvalid ? "no" : "yes") + ", " + buildCount
+        // + " builds");
+        // b.appendln("Laid out", (layoutInvalid ? "no" : "yes") + ", " +
+        // layoutCount + " layouts");
+
+        debug.appendln(getSpecification().getName().toUpperCase());
+        debugStructure(debug);
+    }
+
+    protected void appendDebug(final DebugBuilder debug) {
+    }
+
+    @Override
+    public void debugStructure(final DebugBuilder b) {
+        b.appendln("Content", getContent() == null ? "none" : getContent());
+        b.appendln("Required size ", getRequiredSize(Size.createMax()));
+        b.appendln("Bounds", getBounds());
+        b.appendln("Baseline", getBaseline());
+        b.appendln("Location", getAbsoluteLocation());
+        final View views[] = getSubviews();
+        b.indent();
+        for (final View subview : views) {
+            b.appendln();
+            final ViewSpecification spec = subview.getSpecification();
+            b.appendln(spec == null ? "none" : spec.getName().toUpperCase());
+            b.appendln("View", subview);
+            subview.debugStructure(b);
+        }
+        b.unindent();
+    }
+
+    @Override
+    public void dispose() {
+        final View parent = getParent();
+        if (parent != null) {
+            parent.removeView(getView());
+        }
+    }
+
+    @Override
+    public void drag(final InternalDrag drag) {
+    }
+
+    @Override
+    public void drag(final ContentDrag contentDrag) {
+    }
+
+    @Override
+    public void drag(final ViewDrag drag) {
+        getViewManager().getSpy().addTrace(this, "view drag", drag);
+    }
+
+    @Override
+    public void dragCancel(final InternalDrag drag) {
+        getFeedbackManager().showDefaultCursor();
+    }
+
+    @Override
+    public View dragFrom(final Location location) {
+        final View subview = subviewFor(location);
+        if (subview != null) {
+            location.subtract(subview.getLocation());
+            return subview.dragFrom(location);
+        } else {
+            return null;
+        }
+    }
+
+    @Override
+    public void dragIn(final ContentDrag drag) {
+    }
+
+    @Override
+    public void dragOut(final ContentDrag drag) {
+    }
+
+    @Override
+    public DragEvent dragStart(final DragStart drag) {
+        final View subview = subviewFor(drag.getLocation());
+        if (subview != null) {
+            drag.subtract(subview.getLocation());
+            return subview.dragStart(drag);
+        } else {
+            return null;
+        }
+    }
+
+    @Override
+    public void dragTo(final InternalDrag drag) {
+    }
+
+    /**
+     * Clears the background of this view to the given color (call from the
+     * {@link #draw(Canvas)} method.
+     */
+    protected void clearBackground(final Canvas canvas, final Color color) {
+        final Bounds bounds = getBounds();
+        canvas.drawSolidRectangle(0, 0, bounds.getWidth(), bounds.getHeight(), color);
+    }
+
+    @Override
+    public void draw(final Canvas canvas) {
+        if (Toolkit.debug) {
+            canvas.drawDebugOutline(new Bounds(getSize()), getBaseline(), Toolkit.getColor(ColorsAndFonts.COLOR_DEBUG_BOUNDS_VIEW));
+        }
+    }
+
+    @Override
+    public void drop(final ContentDrag drag) {
+    }
+
+    /**
+     * No default behaviour, views can only be dropped on workspace
+     */
+    @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 Content cont = getContent();
+        if (cont != null) {
+            final String description = cont.getDescription();
+            if (description != null && !"".equals(description)) {
+                getFeedbackManager().setViewDetail(description);
+            }
+        }
+    }
+
+    @Override
+    public void exited() {
+    }
+
+    @Override
+    public void firstClick(final Click click) {
+        final View subview = subviewFor(click.getLocation());
+        if (subview != null) {
+            click.subtract(subview.getLocation());
+            subview.firstClick(click);
+        }
+    }
+
+    @Override
+    public void focusLost() {
+    }
+
+    @Override
+    public void focusReceived() {
+    }
+
+    @Override
+    public Location getAbsoluteLocation() {
+        final View parent = getParent();
+        if (parent == null) {
+            return getLocation();
+        } else {
+            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;
+        }
+    }
+
+    @Override
+    public int getBaseline() {
+        return 0;
+    }
+
+    @Override
+    public Bounds getBounds() {
+        return new Bounds(x, y, width, height);
+    }
+
+    @Override
+    public Content getContent() {
+        return content;
+    }
+
+    @Override
+    public FocusManager getFocusManager() {
+        return getParent() == null ? null : getParent().getFocusManager();
+    }
+
+    @Override
+    public int getId() {
+        return id;
+    }
+
+    @Override
+    public Location getLocation() {
+        return new Location(x, y);
+    }
+
+    @Override
+    public Padding getPadding() {
+        return new Padding(0, 0, 0, 0);
+    }
+
+    @Override
+    public final View getParent() {
+        // Assert.assertEquals(parent == null ? null : parent.getView(),
+        // parent);
+        // return parent;
+
+        return parent == null ? null : parent.getView();
+    }
+
+    @Override
+    public Size getRequiredSize(final Size maximumSize) {
+        return new Size(maximumSize);
+    }
+
+    @Override
+    public Size getSize() {
+        return new Size(width, height);
+    }
+
+    @Override
+    public ViewSpecification getSpecification() {
+        if (specification == null) {
+            specification = new NonBuildingSpecification(this);
+        }
+        return specification;
+    }
+
+    @Override
+    public ViewState getState() {
+        return state;
+    }
+
+    @Override
+    public View[] getSubviews() {
+        return new View[0];
+    }
+
+    @Override
+    public final View getView() {
+        return viewRoot;
+    }
+
+    @Override
+    public Axes getViewAxes() {
+        return new Axes();
+    }
+
+    @Override
+    public Viewer getViewManager() {
+        return Toolkit.getViewer();
+    }
+
+    @Override
+    public Feedback getFeedbackManager() {
+        return Toolkit.getFeedbackManager();
+    }
+
+    @Override
+    public Workspace getWorkspace() {
+        return getParent() == null ? null : getParent().getWorkspace();
+    }
+
+    @Override
+    public boolean hasFocus() {
+        return getViewManager().hasFocus(getView());
+    }
+
+    @Override
+    public View identify(final Location location) {
+        final View subview = subviewFor(location);
+        if (subview == null) {
+            getViewManager().getSpy().addTrace(this, "mouse location within node view", location);
+            getViewManager().getSpy().addTrace("----");
+            return getView();
+        } else {
+            location.subtract(subview.getLocation());
+            return subview.identify(location);
+        }
+    }
+
+    @Override
+    public void invalidateContent() {
+    }
+
+    @Override
+    public void invalidateLayout() {
+        final View parent = getParent();
+        if (parent != null) {
+            parent.invalidateLayout();
+        }
+    }
+
+    @Override
+    public void keyPressed(final KeyboardAction key) {
+    }
+
+    @Override
+    public void keyReleased(final KeyboardAction action) {
+    }
+
+    @Override
+    public void keyTyped(final KeyboardAction action) {
+    }
+
+    @Override
+    public void layout() {
+    }
+
+    /**
+     * Limits the bounds of the this view (when being moved or dropped) so it
+     * never extends outside the specified bounds e.g. outside of a parent view
+     */
+    public void limitBoundsWithin(final Bounds containerBounds) {
+        final Bounds contentBounds = getView().getBounds();
+        if (containerBounds.limitBounds(contentBounds)) {
+            getView().setBounds(contentBounds);
+        }
+    }
+
+    @Override
+    public void limitBoundsWithin(final Size size) {
+        final int w = getView().getSize().getWidth();
+        final int h = getView().getSize().getHeight();
+
+        int x = getView().getLocation().getX();
+        int y = getView().getLocation().getY();
+
+        if (x + w > size.getWidth()) {
+            x = size.getWidth() - w;
+        }
+        if (x < 0) {
+            x = 0;
+        }
+
+        if (y + h > size.getHeight()) {
+            y = size.getHeight() - h;
+        }
+        if (y < 0) {
+            y = 0;
+        }
+
+        getView().setLocation(new Location(x, y));
+    }
+
+    @Override
+    public void markDamaged() {
+        markDamaged(getView().getBounds());
+    }
+
+    @Override
+    public void markDamaged(final Bounds bounds) {
+        final View parent = getParent();
+        if (parent == null) {
+            getViewManager().markDamaged(bounds);
+        } else {
+            final Location pos = parent.getLocation();
+            bounds.translate(pos.getX(), pos.getY());
+            final Padding pad = parent.getPadding();
+            bounds.translate(pad.getLeft(), pad.getTop());
+            parent.markDamaged(bounds);
+        }
+    }
+
+    @Override
+    public void mouseDown(final Click click) {
+        final View subview = subviewFor(click.getLocation());
+        if (subview != null) {
+            click.subtract(subview.getLocation());
+            subview.mouseDown(click);
+        }
+    }
+
+    @Override
+    public void mouseMoved(final Location location) {
+        final View subview = subviewFor(location);
+        if (subview != null) {
+            location.subtract(subview.getLocation());
+            subview.mouseMoved(location);
+        }
+    }
+
+    @Override
+    public void mouseUp(final Click click) {
+        final View subview = subviewFor(click.getLocation());
+        if (subview != null) {
+            click.subtract(subview.getLocation());
+            subview.mouseUp(click);
+        }
+    }
+
+    @Override
+    public void objectActionResult(final ObjectAdapter result, final Placement placement) {
+        if (result != null) {
+            final CollectionFacet facet = result.getSpecification().getFacet(CollectionFacet.class);
+            ObjectAdapter objectToDisplay = result;
+            if (facet != null) {
+                if (facet.size(result) == 1) {
+                    objectToDisplay = facet.firstElement(result);
+                }
+            }
+            getWorkspace().addWindowFor(objectToDisplay, placement);
+        }
+    }
+
+    @Override
+    public View pickupContent(final Location location) {
+        final View subview = subviewFor(location);
+        if (subview != null) {
+            location.subtract(subview.getLocation());
+            return subview.pickupView(location);
+        } else {
+            return Toolkit.getViewFactory().createDragViewOutline(getView());
+        }
+    }
+
+    @Override
+    public View pickupView(final Location location) {
+        final View subview = subviewFor(location);
+        if (subview != null) {
+            location.subtract(subview.getLocation());
+            return subview.pickupView(location);
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Delegates all printing the the draw method.
+     * 
+     * @see #draw(Canvas)
+     */
+    @Override
+    public void print(final Canvas canvas) {
+        draw(canvas);
+    }
+
+    @Override
+    public void refresh() {
+    }
+
+    @Override
+    public void removeView(final View view) {
+        throw new IsisException();
+    }
+
+    @Override
+    public void replaceView(final View toReplace, final View replacement) {
+        throw new IsisException();
+    }
+
+    @Override
+    public void secondClick(final Click click) {
+        final View subview = subviewFor(click.getLocation());
+        if (subview != null) {
+            click.subtract(subview.getLocation());
+            subview.secondClick(click);
+        }
+    }
+
+    /**
+     * Sets the location and size view the {@link #setLocation(Location)) and
+     * {@link #setSize(Size)) methods.
+     */
+    @Override
+    public void setBounds(final Bounds bounds) {
+        setLocation(bounds.getLocation());
+        setSize(bounds.getSize());
+    }
+
+    @Override
+    public void setFocusManager(final FocusManager focusManager) {
+    }
+
+    protected void setContent(final Content content) {
+        this.content = content;
+    }
+
+    @Override
+    public void setLocation(final Location location) {
+        x = location.getX();
+        y = location.getY();
+    }
+
+    @Override
+    public final void setParent(final View parentView) {
+        LOG.debug("set parent " + parentView + " for " + this);
+        parent = parentView.getView();
+    }
+
+    public void setMaximumSize(final Size size) {
+    }
+
+    @Override
+    public void setSize(final Size size) {
+        width = size.getWidth();
+        height = size.getHeight();
+    }
+
+    protected void setSpecification(final ViewSpecification specification) {
+        this.specification = specification;
+    }
+
+    @Override
+    public final void setView(final View view) {
+        this.viewRoot = view;
+    }
+
+    @Deprecated
+    protected void setViewAxis(final ViewAxis viewAxis) {
+        // this.viewAxis = viewAxis;
+    }
+
+    @Override
+    public View subviewFor(final Location location) {
+        return null;
+    }
+
+    @Override
+    public void thirdClick(final Click click) {
+        final View subview = subviewFor(click.getLocation());
+        if (subview != null) {
+            click.subtract(subview.getLocation());
+            subview.thirdClick(click);
+        }
+    }
+
+    @Override
+    public String toString() {
+        final String name = getClass().getName();
+        return name.substring(name.lastIndexOf('.') + 1) + getId() + ":" + getState() + ":" + getContent();
+    }
+
+    @Override
+    public void update(final ObjectAdapter object) {
+    }
+
+    @Override
+    public void updateView() {
+    }
+
+    @Override
+    public ViewAreaType viewAreaType(final Location location) {
+        final View subview = subviewFor(location);
+        if (subview != null) {
+            location.subtract(subview.getLocation());
+            return subview.viewAreaType(location);
+        } else {
+            return ViewAreaType.CONTENT;
+        }
+    }
+
+    @Override
+    public void viewMenuOptions(final UserActionSet options) {
+        options.setColor(Toolkit.getColor(ColorsAndFonts.COLOR_MENU_VIEW));
+
+        final Content content = getContent();
+        addContentMenuItems(options, content);
+        addNewViewMenuItems(options, content);
+
+        // TODO ask the viewer for the print option - provided by the underlying
+        // system
+        // options.add(new PrintOption());
+
+        addViewDebugMenuItems(options);
+
+        final UndoStack undoStack = getViewManager().getUndoStack();
+        if (!undoStack.isEmpty()) {
+            options.add(new UserActionAbstract("Undo " + undoStack.getNameOfUndo()) {
+
+                @Override
+                public Consent disabled(final View component) {
+                    return new ConsentAbstract("", undoStack.descriptionOfUndo()) {
+                        private static final long serialVersionUID = 1L;
+                    };
+                }
+
+                @Override
+                public void execute(final Workspace workspace, final View view, final Location at) {
+                    undoStack.undoLastCommand();
+                }
+            });
+        }
+    }
+
+    private void addViewDebugMenuItems(final UserActionSet options) {
+        options.add(new UserActionAbstract("Refresh view", ActionType.DEBUG) {
+            @Override
+            public void execute(final Workspace workspace, final View view, final Location at) {
+                refresh();
+            }
+        });
+
+        options.add(new UserActionAbstract("Invalidate content", ActionType.DEBUG) {
+            @Override
+            public void execute(final Workspace workspace, final View view, final Location at) {
+                invalidateContent();
+            }
+        });
+
+        options.add(new UserActionAbstract("Invalidate layout", ActionType.DEBUG) {
+            @Override
+            public void execute(final Workspace workspace, final View view, final Location at) {
+                invalidateLayout();
+            }
+        });
+    }
+
+    private void addNewViewMenuItems(final UserActionSet options, final Content content) {
+        if (getContent() instanceof ObjectContent) {
+            options.add(new UserActionAbstract("Use as default view for objects", ActionType.USER) {
+                @Override
+                public void execute(final Workspace workspace, final View view, final Location at) {
+                    Properties.setStringOption("view.object-default", getSpecification().getName());
+                    /*
+                     * Options viewOptions =
+                     * Properties.getViewConfigurationOptions
+                     * (getSpecification()); getView().saveOptions(viewOptions);
+                     */
+                }
+            });
+        }
+        if (getContent() instanceof CollectionContent) {
+            options.add(new UserActionAbstract("Use as default view for collection", ActionType.USER) {
+                @Override
+                public void execute(final Workspace workspace, final View view, final Location at) {
+                    Properties.setStringOption("view.collection-default", getSpecification().getName());
+                    /*
+                     * Options viewOptions =
+                     * Properties.getViewConfigurationOptions
+                     * (getSpecification()); getView().saveOptions(viewOptions);
+                     */
+                }
+            });
+        }
+        if (getContent() instanceof ObjectContent && !getSpecification().isOpen()) {
+            options.add(new UserActionAbstract("Use as default view for icon", ActionType.USER) {
+                @Override
+                public void execute(final Workspace workspace, final View view, final Location at) {
+                    Properties.setStringOption("view.icon-default", getSpecification().getName());
+                    /*
+                     * Options viewOptions =
+                     * Properties.getViewConfigurationOptions
+                     * (getSpecification()); getView().saveOptions(viewOptions);
+                     */
+                }
+            });
+        }
+
+        if (getContent() instanceof RootObject || getContent() instanceof RootCollection) {
+            options.add(new UserActionAbstract("Use as default view for " + getContent().getSpecification().getSingularName(), ActionType.USER) {
+                @Override
+                public void execute(final Workspace workspace, final View view, final Location at) {
+                    final Options viewOptions = Properties.getViewConfigurationOptions(getSpecification());
+                    getView().saveOptions(viewOptions);
+
+                    // Options viewOptions =
+                    final ObjectSpecification specification = content.getSpecification();
+                    final Options settingsOptions = Properties.getDefaultViewOptions(specification);
+                    settingsOptions.addOption("spec", getSpecification().getName());
+                }
+            });
+        }
+        /*
+         * options.add(new UserActionAbstract("Create new specification",
+         * UserAction.USER) { // TODO probably needs to be a replace with new
+         * view specification public void execute(final Workspace workspace,
+         * final View view, final Location at) { UserViewSpecification newSpec =
+         * new UserViewSpecification(getView()); Options viewOptions =
+         * Properties.getViewConfigurationOptions(newSpec);
+         * getView().saveOptions(viewOptions);
+         * 
+         * viewOptions = Properties.getUserViewSpecificationOptions(newSpec);
+         * viewOptions.addOption("wrapped-specification",
+         * getSpecification().getClass().getName());
+         * 
+         * Toolkit.getViewFactory().addSpecification(newSpec); } });
+         */
+        options.add(new UserActionAbstract("Save specification", ActionType.USER) {
+            @Override
+            public void execute(final Workspace workspace, final View view, final Location at) {
+                final Options viewOptions = Properties.getViewConfigurationOptions(getSpecification());
+                getView().saveOptions(viewOptions);
+
+                Toolkit.getViewFactory().addSpecification(getSpecification());
+            }
+        });
+        createOpenAsSubmenu(options, content);
+
+        createCreateViewSubmenu(options, content);
+    }
+
+    private void createOpenAsSubmenu(final UserActionSet options, final Content content) {
+        final ViewRequirement requirements = new ViewRequirement(content, ViewRequirement.OPEN | ViewRequirement.EXPANDABLE);
+        final Enumeration possibleViews = Toolkit.getViewFactory().availableViews(requirements);
+        if (possibleViews.hasMoreElements()) {
+            final UserActionSet submenu = options.addNewActionSet("Open as");
+            while (possibleViews.hasMoreElements()) {
+                final ViewSpecification specification = (ViewSpecification) possibleViews.nextElement();
+                final UserActionAbstract viewAs = new OpenViewOption(specification);
+                submenu.add(viewAs);
+            }
+        }
+    }
+
+    private void createCreateViewSubmenu(final UserActionSet options, final Content content) {
+        final ViewRequirement requirements = new ViewRequirement(content, ViewRequirement.OPEN);
+        final Enumeration possibleViews = Toolkit.getViewFactory().availableDesigns(requirements);
+        if (possibleViews.hasMoreElements()) {
+            final UserActionSet submenu = options.addNewActionSet("Create view from");
+            while (possibleViews.hasMoreElements()) {
+                final ViewSpecification specification = (ViewSpecification) possibleViews.nextElement();
+                final UserActionAbstract viewAs = new UserActionAbstract(specification.getName(), ActionType.USER) {
+                    @Override
+                    public void execute(final Workspace workspace, final View view, final Location at) {
+                        ViewSpecification newSpec;
+                        try {
+                            newSpec = specification.getClass().newInstance();
+                        } catch (final InstantiationException e) {
+                            throw new ViewerException(e);
+                        } catch (final IllegalAccessException e) {
+                            throw new ViewerException(e);
+                        }
+
+                        Content content = view.getContent();
+                        if (!(content instanceof FieldContent)) {
+                            content = Toolkit.getContentFactory().createRootContent(content.getAdapter());
+                        }
+                        final View newView = newSpec.createView(content, view.getViewAxes(), -1);
+                        LOG.debug("open view " + newView);
+                        workspace.addWindow(newView, new Placement(view));
+                        workspace.markDamaged();
+
+                        Options viewOptions = Properties.getViewConfigurationOptions(newSpec);
+                        newView.saveOptions(viewOptions);
+                        viewOptions = Properties.getUserViewSpecificationOptions(newSpec.getName());
+                        viewOptions.addOption("design", specification.getClass().getName());
+
+                        Toolkit.getViewFactory().addSpecification(newSpec);
+                    }
+                };
+
+                submenu.add(viewAs);
+            }
+        }
+    }
+
+    private void addContentMenuItems(final UserActionSet options, final Content content) {
+        if (content != null) {
+            content.viewMenuOptions(options);
+        }
+    }
+
+    @Override
+    public void loadOptions(final Options viewOptions) {
+    }
+
+    @Override
+    public void saveOptions(final Options viewOptions) {
+        // viewOptions.addOption("spec",
+        // getSpecification().getClass().getName());
+    }
+}