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

[29/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/window/WindowControl.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/window/WindowControl.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/window/WindowControl.java
new file mode 100644
index 0000000..4cb42ff
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/view/window/WindowControl.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.window;
+
+import org.apache.isis.viewer.dnd.drawing.Size;
+import org.apache.isis.viewer.dnd.view.UserAction;
+import org.apache.isis.viewer.dnd.view.View;
+import org.apache.isis.viewer.dnd.view.control.AbstractControlView;
+
+public abstract class WindowControl extends AbstractControlView {
+    public final static int HEIGHT = 13;
+    public final static int WIDTH = HEIGHT + 2;
+
+    protected WindowControl(final UserAction action, final View target) {
+        super(action, target);
+    }
+
+    @Override
+    public Size getRequiredSize(final Size availableSpace) {
+        return new Size(WIDTH, HEIGHT);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/eb613703/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/viewer/ApplicationOptions.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/viewer/ApplicationOptions.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/viewer/ApplicationOptions.java
new file mode 100644
index 0000000..6b92684
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/viewer/ApplicationOptions.java
@@ -0,0 +1,83 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.isis.viewer.dnd.viewer;
+
+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.viewer.dnd.drawing.Location;
+import org.apache.isis.viewer.dnd.drawing.Size;
+import org.apache.isis.viewer.dnd.help.AboutView;
+import org.apache.isis.viewer.dnd.view.MenuOptions;
+import org.apache.isis.viewer.dnd.view.Placement;
+import org.apache.isis.viewer.dnd.view.ShutdownListener;
+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 ApplicationOptions implements MenuOptions {
+    private final ShutdownListener listener;
+
+    public ApplicationOptions(final ShutdownListener listener) {
+        this.listener = listener;
+    }
+
+    @Override
+    public void menuOptions(final UserActionSet options) {
+        options.add(new UserActionAbstract("About...") {
+            @Override
+            public void execute(final Workspace workspace, final View view, final Location at) {
+                final AboutView dialogView = new AboutView();
+                final Size windowSize = dialogView.getRequiredSize(new Size());
+                final Size workspaceSize = workspace.getSize();
+                final int x = workspaceSize.getWidth() / 2 - windowSize.getWidth() / 2;
+                final int y = workspaceSize.getHeight() / 2 - windowSize.getHeight() / 2;
+                workspace.addDialog(dialogView, new Placement(new Location(x, y)));
+            }
+        });
+
+        options.add(new UserActionAbstract("Log out") {
+            @Override
+            public Consent disabled(final View view) {
+                final boolean runningAsExploration = view.getViewManager().isRunningAsExploration();
+                if (runningAsExploration) {
+                    // TODO: move logic to Facet
+                    return new Veto("Can't log out in exploration mode");
+                } else {
+                    return Allow.DEFAULT;
+                }
+            }
+
+            @Override
+            public void execute(final Workspace workspace, final View view, final Location at) {
+                listener.logOut();
+            }
+        });
+
+        options.add(new UserActionAbstract("Quit") {
+            @Override
+            public void execute(final Workspace workspace, final View view, final Location at) {
+                listener.quit();
+            }
+        });
+
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/eb613703/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/viewer/DefaultContentFactory.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/viewer/DefaultContentFactory.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/viewer/DefaultContentFactory.java
new file mode 100644
index 0000000..7a35fb2
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/viewer/DefaultContentFactory.java
@@ -0,0 +1,73 @@
+/*
+ *  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.viewer;
+
+import org.apache.isis.core.commons.ensure.Assert;
+import org.apache.isis.core.commons.exceptions.IsisException;
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.spec.ObjectSpecification;
+import org.apache.isis.core.metamodel.spec.feature.ObjectAssociation;
+import org.apache.isis.core.metamodel.spec.feature.OneToManyAssociation;
+import org.apache.isis.core.metamodel.spec.feature.OneToOneAssociation;
+import org.apache.isis.viewer.dnd.view.Content;
+import org.apache.isis.viewer.dnd.view.ContentFactory;
+import org.apache.isis.viewer.dnd.view.collection.RootCollection;
+import org.apache.isis.viewer.dnd.view.content.RootObject;
+import org.apache.isis.viewer.dnd.view.field.OneToManyFieldImpl;
+import org.apache.isis.viewer.dnd.view.field.OneToOneFieldImpl;
+import org.apache.isis.viewer.dnd.view.field.TextParseableFieldImpl;
+
+public class DefaultContentFactory implements ContentFactory {
+
+    @Override
+    public Content createRootContent(final ObjectAdapter object) {
+        Assert.assertNotNull(object);
+        final ObjectSpecification objectSpec = object.getSpecification();
+        if (objectSpec.isParentedOrFreeCollection()) {
+            return new RootCollection(object);
+        }
+        if (objectSpec.isNotCollection()) {
+            return new RootObject(object);
+        }
+
+        throw new IllegalArgumentException("Must be an object or collection: " + object);
+    }
+
+    @Override
+    public Content createFieldContent(final ObjectAssociation field, final ObjectAdapter object) {
+        Content content;
+        final ObjectAdapter associatedObject = field.get(object);
+        if (field instanceof OneToManyAssociation) {
+            content = new OneToManyFieldImpl(object, associatedObject, (OneToManyAssociation) field);
+        } else if (field instanceof OneToOneAssociation) {
+            final ObjectSpecification fieldSpecification = field.getSpecification();
+            if (fieldSpecification.isParseable()) {
+                content = new TextParseableFieldImpl(object, associatedObject, (OneToOneAssociation) field);
+            } else {
+                content = new OneToOneFieldImpl(object, associatedObject, (OneToOneAssociation) field);
+            }
+        } else {
+            throw new IsisException();
+        }
+
+        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/viewer/SkylarkViewFactory.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/viewer/SkylarkViewFactory.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/viewer/SkylarkViewFactory.java
new file mode 100644
index 0000000..286360f
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/viewer/SkylarkViewFactory.java
@@ -0,0 +1,286 @@
+/*
+ *  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.viewer;
+
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Vector;
+
+import org.apache.log4j.Logger;
+
+import org.apache.isis.core.commons.debug.DebugBuilder;
+import org.apache.isis.core.commons.factory.InstanceUtil;
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.facets.object.value.ValueFacet;
+import org.apache.isis.core.metamodel.spec.ObjectSpecification;
+import org.apache.isis.core.runtime.userprofile.Options;
+import org.apache.isis.viewer.dnd.dialog.ActionDialogSpecification;
+import org.apache.isis.viewer.dnd.drawing.Location;
+import org.apache.isis.viewer.dnd.interaction.ContentDragImpl;
+import org.apache.isis.viewer.dnd.util.Properties;
+import org.apache.isis.viewer.dnd.view.Axes;
+import org.apache.isis.viewer.dnd.view.Content;
+import org.apache.isis.viewer.dnd.view.DragEvent;
+import org.apache.isis.viewer.dnd.view.GlobalViewFactory;
+import org.apache.isis.viewer.dnd.view.ObjectContent;
+import org.apache.isis.viewer.dnd.view.View;
+import org.apache.isis.viewer.dnd.view.ViewRequirement;
+import org.apache.isis.viewer.dnd.view.ViewSpecification;
+import org.apache.isis.viewer.dnd.view.base.DragViewOutline;
+import org.apache.isis.viewer.dnd.view.border.DisposedObjectBorder;
+import org.apache.isis.viewer.dnd.view.collection.CollectionContent;
+import org.apache.isis.viewer.dnd.viewer.basic.FallbackView;
+import org.apache.isis.viewer.dnd.viewer.basic.MinimizedView;
+
+/**
+ * This class holds all the different view types that all the different objects
+ * can be viewed as.
+ */
+public class SkylarkViewFactory implements GlobalViewFactory {
+    private static final ViewSpecification fallback = new FallbackView.Specification();
+    private final ViewSpecification dialogSpec = new ActionDialogSpecification();
+    public static final int INTERNAL = 2;
+    private static final Logger LOG = Logger.getLogger(SkylarkViewFactory.class);
+    public static final int WINDOW = 1;
+
+    private ViewSpecification emptyFieldSpecification;
+    private final Vector rootViews = new Vector();
+    private final Vector subviews = new Vector();
+    private ViewSpecification dragContentSpecification;
+
+    private final List<ViewSpecification> viewSpecifications = new ArrayList<ViewSpecification>();
+    private final List<ViewSpecification> designSpecifications = new ArrayList<ViewSpecification>();
+
+    @Override
+    public void addSpecification(final ViewSpecification specification) {
+        viewSpecifications.add(specification);
+    }
+
+    public void addSpecification(final String specClassName) {
+        ViewSpecification spec;
+        spec = (ViewSpecification) InstanceUtil.createInstance(specClassName);
+        LOG.info("adding view specification: " + spec);
+        addSpecification(spec);
+    }
+
+    public void addDesignSpecification(final ViewSpecification specification) {
+        designSpecifications.add(specification);
+    }
+
+    public void addEmptyFieldSpecification(final ViewSpecification spec) {
+        emptyFieldSpecification = spec;
+    }
+
+    @Override
+    public View createDialog(final Content content) {
+        return createView(dialogSpec, content);
+    }
+
+    private View createView(final ViewSpecification specification, final Content content) {
+        ViewSpecification spec;
+        if (specification == null) {
+            LOG.warn("no suitable view for " + content + " using fallback view");
+            spec = new FallbackView.Specification();
+        } else {
+            spec = specification;
+        }
+        // TODO this should be passed in so that factory created views can be
+        // related to the views that ask
+        // for them
+        final Axes axes = new Axes();
+        View createView = spec.createView(content, axes, -1);
+
+        /*
+         * ObjectSpecification contentSpecification =
+         * content.getSpecification(); if (contentSpecification != null) {
+         * Options viewOptions = Properties.getViewConfigurationOptions(spec);
+         * createView.loadOptions(viewOptions); }
+         */
+        if (content.isObject()) {
+            final ObjectAdapter adapter = content.getAdapter();
+            if (adapter != null && adapter.isDestroyed()) {
+                createView = new DisposedObjectBorder(createView);
+            }
+        }
+        createView.getSubviews();
+        return createView;
+    }
+
+    @Override
+    public void debugData(final DebugBuilder sb) {
+        sb.append("RootsViews\n");
+        Enumeration fields = rootViews.elements();
+        while (fields.hasMoreElements()) {
+            final ViewSpecification spec = (ViewSpecification) fields.nextElement();
+            sb.append("  ");
+            sb.append(spec);
+            sb.append("\n");
+        }
+        sb.append("\n\n");
+
+        sb.append("Subviews\n");
+        fields = subviews.elements();
+        while (fields.hasMoreElements()) {
+            final ViewSpecification spec = (ViewSpecification) fields.nextElement();
+            sb.append("  ");
+            sb.append(spec);
+            sb.append("\n");
+        }
+        sb.append("\n\n");
+
+        sb.append("Specifications\n");
+        for (final ViewSpecification spec : viewSpecifications) {
+            sb.append("  ");
+            sb.append(spec);
+            sb.append("\n");
+        }
+        sb.append("\n\n");
+    }
+
+    @Override
+    public String debugTitle() {
+        return "View factory entries";
+    }
+
+    private ViewSpecification getEmptyFieldSpecification() {
+        if (emptyFieldSpecification == null) {
+            LOG.error("missing empty field specification; using fallback");
+            return fallback;
+        }
+        return emptyFieldSpecification;
+    }
+
+    public void setDragContentSpecification(final ViewSpecification dragContentSpecification) {
+        this.dragContentSpecification = dragContentSpecification;
+    }
+
+    @Override
+    public View createDragViewOutline(final View view) {
+        return new DragViewOutline(view);
+    }
+
+    @Override
+    public DragEvent createDragContentOutline(final View view, final Location location) {
+        final View dragOverlay = dragContentSpecification.createView(view.getContent(), new Axes(), -1);
+        return new ContentDragImpl(view, location, dragOverlay);
+    }
+
+    @Override
+    public View createMinimizedView(final View view) {
+        return new MinimizedView(view);
+    }
+
+    @Override
+    public View createView(final ViewRequirement requirement) {
+        final ViewSpecification objectFieldSpecification = getSpecificationForRequirement(requirement);
+        return createView(objectFieldSpecification, requirement.getContent());
+    }
+
+    public ViewSpecification getSpecificationForRequirement(final ViewRequirement requirement) {
+        final Content content = requirement.getContent();
+        final ObjectSpecification specification = content.getSpecification();
+        final boolean isValue = specification != null && specification.containsFacet(ValueFacet.class);
+        if (content.isObject() && !isValue && content.getAdapter() == null) {
+            return getEmptyFieldSpecification();
+        } else {
+            if (specification != null) {
+                final Options viewOptions = Properties.getDefaultViewOptions(specification);
+                String spec = viewOptions.getString("spec");
+                if (spec == null) {
+                    if (content instanceof ObjectContent && requirement.isObject() && requirement.isClosed()) {
+                        spec = Properties.getDefaultIconViewOptions();
+                    } else if (content instanceof CollectionContent && requirement.isCollection()) {
+                        spec = Properties.getDefaultCollectionViewOptions();
+                    } else if (content instanceof ObjectContent && requirement.isObject() && requirement.isOpen()) {
+                        spec = Properties.getDefaultObjectViewOptions();
+                    }
+                }
+                if (spec != null) {
+                    final ViewSpecification lookSpec = lookupSpecByName(spec);
+                    if (lookSpec != null && lookSpec.canDisplay(requirement)) {
+                        return lookSpec;
+                    }
+                }
+            }
+            for (final ViewSpecification viewSpecification : viewSpecifications) {
+                if (viewSpecification.canDisplay(requirement)) {
+                    return viewSpecification;
+                }
+
+            }
+            LOG.error("missing specification; using fall back");
+            return fallback;
+        }
+    }
+
+    public void loadUserViewSpecifications() {
+        final Options options = Properties.getOptions("views.user-defined");
+        final Iterator<String> names = options.names();
+        while (names.hasNext()) {
+            final String name = names.next();
+            final Options viewOptions = options.getOptions(name);
+            final String specName = viewOptions.getString("design");
+            addSpecification(specName);
+        }
+    }
+
+    private ViewSpecification lookupSpecByName(final String name) {
+        for (final ViewSpecification viewSpecification : viewSpecifications) {
+            if (viewSpecification.getName().equals(name)) {
+                return viewSpecification;
+            }
+        }
+        LOG.warn("No specification found for " + name);
+        return null;
+    }
+
+    private ViewSpecification lookupSpecByClassName(final String className) {
+        for (final ViewSpecification viewSpecification : viewSpecifications) {
+            if (viewSpecification.getClass().getName().equals(className)) {
+                return viewSpecification;
+            }
+        }
+        LOG.warn("No specification found for " + className);
+        return null;
+    }
+
+    @Override
+    public Enumeration<ViewSpecification> availableViews(final ViewRequirement requirement) {
+        return viewsFor(requirement, viewSpecifications);
+    }
+
+    @Override
+    public Enumeration<ViewSpecification> availableDesigns(final ViewRequirement requirement) {
+        return viewsFor(requirement, designSpecifications);
+    }
+
+    private Enumeration<ViewSpecification> viewsFor(final ViewRequirement requirement, final List<ViewSpecification> viewSpecifications) {
+        final Vector<ViewSpecification> v = new Vector<ViewSpecification>();
+        for (final ViewSpecification specification : viewSpecifications) {
+            if (specification.canDisplay(requirement)) {
+                v.addElement(specification);
+            }
+        }
+        return v.elements();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/eb613703/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/viewer/basic/ApplicationWorkspace.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/viewer/basic/ApplicationWorkspace.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/viewer/basic/ApplicationWorkspace.java
new file mode 100644
index 0000000..7c28948
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/viewer/basic/ApplicationWorkspace.java
@@ -0,0 +1,520 @@
+/*
+ *  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.viewer.basic;
+
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Set;
+import java.util.Vector;
+
+import com.google.common.collect.Lists;
+
+import org.apache.isis.core.commons.authentication.AuthenticationSession;
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.adapter.mgr.AdapterManager;
+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.metamodel.spec.FreeStandingList;
+import org.apache.isis.core.metamodel.spec.ObjectSpecification;
+import org.apache.isis.core.metamodel.spec.SpecificationLoaderSpi;
+import org.apache.isis.core.runtime.userprofile.PerspectiveEntry;
+import org.apache.isis.runtimes.dflt.runtime.authentication.exploration.MultiUserExplorationSession;
+import org.apache.isis.runtimes.dflt.runtime.system.context.IsisContext;
+import org.apache.isis.runtimes.dflt.runtime.system.persistence.AdapterManagerSpi;
+import org.apache.isis.runtimes.dflt.runtime.system.persistence.PersistenceSession;
+import org.apache.isis.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.service.PerspectiveContent;
+import org.apache.isis.viewer.dnd.service.ServiceObject;
+import org.apache.isis.viewer.dnd.view.Axes;
+import org.apache.isis.viewer.dnd.view.Click;
+import org.apache.isis.viewer.dnd.view.CompositeViewSpecification;
+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.Look;
+import org.apache.isis.viewer.dnd.view.ObjectContent;
+import org.apache.isis.viewer.dnd.view.Placement;
+import org.apache.isis.viewer.dnd.view.Toolkit;
+import org.apache.isis.viewer.dnd.view.UserActionSet;
+import org.apache.isis.viewer.dnd.view.View;
+import org.apache.isis.viewer.dnd.view.ViewDrag;
+import org.apache.isis.viewer.dnd.view.ViewRequirement;
+import org.apache.isis.viewer.dnd.view.Workspace;
+import org.apache.isis.viewer.dnd.view.base.Layout;
+import org.apache.isis.viewer.dnd.view.composite.CompositeViewUsingBuilder;
+import org.apache.isis.viewer.dnd.view.content.FieldContent;
+import org.apache.isis.viewer.dnd.view.look.LookFactory;
+import org.apache.isis.viewer.dnd.view.option.UserActionAbstract;
+import org.apache.isis.viewer.dnd.view.window.DialogBorder;
+import org.apache.isis.viewer.dnd.view.window.SubviewFocusManager;
+import org.apache.isis.viewer.dnd.view.window.WindowBorder;
+
+public final class ApplicationWorkspace extends CompositeViewUsingBuilder implements Workspace {
+    protected Vector<View> serviceViews;
+    protected Vector<View> iconViews;
+
+    public ApplicationWorkspace(final Content content, final Axes axes, final CompositeViewSpecification specification, final Layout layout, final ApplicationWorkspaceBuilder builder) {
+        super(content, specification, axes, layout, builder);
+        serviceViews = new Vector<View>();
+        iconViews = new Vector<View>();
+        LookFactory.init();
+    }
+
+    @Override
+    public void addDialog(final View dialogContent, final Placement placement) {
+        final DialogBorder dialogView = new DialogBorder(dialogContent, false);
+        addView(dialogView);
+        placement.position(this, dialogView);
+        // dialogView.setFocusManager( new SubviewFocusManager(dialogView));
+    }
+
+    @Override
+    public void addWindow(final View containedView, final Placement placement) {
+        final boolean scrollable = !containedView.getSpecification().isResizeable();
+        final WindowBorder windowView = new WindowBorder(containedView, scrollable);
+        addView(windowView);
+        placement.position(this, windowView);
+        windowView.setFocusManager(new SubviewFocusManager(windowView));
+    }
+
+    @Override
+    public void addView(final View view) {
+        super.addView(view);
+        getViewManager().setKeyboardFocus(view);
+        view.getFocusManager().focusFirstChildView();
+    }
+
+    @Override
+    public void replaceView(final View toReplace, final View replacement) {
+        if (replacement.getSpecification().isOpen()) {
+            final boolean scrollable = !replacement.getSpecification().isResizeable();
+            final WindowBorder windowView = new WindowBorder(replacement, scrollable);
+            super.replaceView(toReplace, windowView);
+        } else {
+            super.replaceView(toReplace, replacement);
+        }
+    }
+
+    @Override
+    public View addWindowFor(final ObjectAdapter object, final Placement placement) {
+        final Content content = Toolkit.getContentFactory().createRootContent(object);
+        final View view = Toolkit.getViewFactory().createView(new ViewRequirement(content, ViewRequirement.OPEN));
+        addWindow(view, placement);
+        getViewManager().setKeyboardFocus(view);
+        return view;
+    }
+
+    @Override
+    public View addIconFor(final ObjectAdapter object, final Placement placement) {
+        final Content content = Toolkit.getContentFactory().createRootContent(object);
+        final View icon = Toolkit.getViewFactory().createView(new ViewRequirement(content, ViewRequirement.CLOSED | ViewRequirement.ROOT));
+        add(iconViews, icon);
+        placement.position(this, icon);
+        return icon;
+    }
+
+    public void addServiceIconFor(final ObjectAdapter service) {
+        final Content content = new ServiceObject(service);
+        final View serviceIcon = Toolkit.getViewFactory().createView(new ViewRequirement(content, ViewRequirement.CLOSED | ViewRequirement.SUBVIEW));
+        add(serviceViews, serviceIcon);
+    }
+
+    @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;
+        }
+    }
+
+    // TODO check the dragging in of objects, flag to user that object cannot be
+    // dropped
+    @Override
+    public void drop(final ContentDrag drag) {
+        getFeedbackManager().showDefaultCursor();
+
+        if (!drag.getSourceContent().isObject()) {
+            return;
+        }
+
+        if (drag.getSourceContent().getAdapter() == getPerspective()) {
+            getFeedbackManager().setAction("can' drop self on workspace");
+            return;
+        }
+
+        final ObjectAdapter source = ((ObjectContent) drag.getSourceContent()).getObject();
+        if (source.getSpecification().isService()) {
+            getPerspective().addToServices(source.getObject());
+            invalidateContent();
+        } else {
+            if (!drag.isShift()) {
+                getPerspective().addToObjects(source.getObject());
+            }
+        }
+
+        View newView;
+        if (source.getSpecification().isService()) {
+            return;
+        } else {
+            final Location dropLocation = drag.getTargetLocation();
+            dropLocation.subtract(drag.getOffset());
+
+            if (drag.isShift()) {
+                newView = Toolkit.getViewFactory().createView(new ViewRequirement(getContent(), ViewRequirement.OPEN | ViewRequirement.SUBVIEW));
+                drag.getTargetView().addView(newView);
+                newView.setLocation(dropLocation);
+            } else {
+                // place object onto desktop as icon
+                final View sourceView = drag.getSource();
+                if (!sourceView.getSpecification().isOpen()) {
+                    final View[] subviews = getSubviews();
+                    for (final View subview : subviews) {
+                        if (subview == sourceView) {
+                            sourceView.markDamaged();
+                            sourceView.setLocation(dropLocation);
+                            sourceView.markDamaged();
+                            return;
+                        }
+                    }
+                } else {
+                    for (final View view : iconViews) {
+                        if (view.getContent().getAdapter() == source) {
+                            view.markDamaged();
+                            view.setLocation(dropLocation);
+                            view.markDamaged();
+                            return;
+                        }
+                    }
+                }
+                addIconFor(source, new Placement(dropLocation));
+            }
+        }
+    }
+
+    @Override
+    public void entered() {
+        // prevents status details about "Persective..."
+    }
+
+    private PerspectiveEntry getPerspective() {
+        return ((PerspectiveContent) getContent()).getPerspective();
+    }
+
+    @Override
+    public void drop(final ViewDrag drag) {
+        getFeedbackManager().showDefaultCursor();
+
+        final View sourceView = drag.getSourceView();
+        final Location newLocation = drag.getViewDropLocation();
+        if (sourceView.getSpecification() != null && sourceView.getSpecification().isSubView()) {
+            if (sourceView.getSpecification().isOpen() && sourceView.getSpecification().isReplaceable()) {
+                // TODO remove the open view from the container and place on
+                // workspace; replace the internal view with an icon
+            } else if (sourceView.getContent() instanceof FieldContent) {
+                final ViewRequirement requirement = new ViewRequirement(sourceView.getContent(), ViewRequirement.OPEN);
+                final View view = Toolkit.getViewFactory().createView(requirement);
+                addWindow(view, new Placement(newLocation));
+                sourceView.getState().clearViewIdentified();
+            } else {
+                addWindowFor(sourceView.getContent().getAdapter(), new Placement(newLocation));
+                sourceView.getState().clearViewIdentified();
+            }
+        } else {
+            sourceView.markDamaged();
+            sourceView.setLocation(newLocation);
+            sourceView.limitBoundsWithin(getSize());
+            sourceView.markDamaged();
+        }
+    }
+
+    @Override
+    public Padding getPadding() {
+        return new Padding();
+    }
+
+    @Override
+    public Workspace getWorkspace() {
+        return this;
+    }
+
+    @Override
+    public void lower(final View view) {
+        if (views.contains(view)) {
+            views.removeElement(view);
+            views.insertElementAt(view, 0);
+            markDamaged();
+        }
+    }
+
+    @Override
+    public void raise(final View view) {
+        if (views.contains(view)) {
+            views.removeElement(view);
+            views.addElement(view);
+            markDamaged();
+        }
+    }
+
+    @Override
+    public void removeView(final View view) {
+        view.markDamaged();
+        if (iconViews.contains(view)) {
+            iconViews.remove(view);
+            getViewManager().removeFromNotificationList(view);
+            removeObject(view.getContent().getAdapter());
+        } else if (serviceViews.contains(view)) {
+            serviceViews.remove(view);
+            getViewManager().removeFromNotificationList(view);
+            removeService(view.getContent().getAdapter());
+        } else {
+            super.removeView(view);
+        }
+    }
+
+    private void removeService(final ObjectAdapter object) {
+        getPerspective().removeFromServices(object.getObject());
+    }
+
+    private void removeObject(final ObjectAdapter object) {
+        getPerspective().removeFromObjects(object.getObject());
+    }
+
+    @Override
+    public void secondClick(final Click click) {
+        final View subview = subviewFor(click.getLocation());
+        if (subview != null) {
+            // ignore double-click on self - don't open up new view
+            super.secondClick(click);
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "Workspace" + getId();
+    }
+
+    @Override
+    public void viewMenuOptions(final UserActionSet options) {
+        options.setColor(Toolkit.getColor(ColorsAndFonts.COLOR_MENU_WORKSPACE));
+
+        options.add(new UserActionAbstract("Close all") {
+            @Override
+            public void execute(final Workspace workspace, final View view, final Location at) {
+                final View views[] = getWindowViews();
+                for (final View v : views) {
+                    // if (v.getSpecification().isOpen()) {
+                    v.dispose();
+                    // }
+                }
+                markDamaged();
+            }
+        });
+
+        options.add(new UserActionAbstract("Tidy up windows") {
+            @Override
+            public void execute(final Workspace workspace, final View view, final Location at) {
+                tidyViews(getWindowViews());
+            }
+        });
+
+        options.add(new UserActionAbstract("Tidy up icons") {
+            @Override
+            public void execute(final Workspace workspace, final View view, final Location at) {
+                tidyViews(getObjectIconViews());
+            }
+        });
+
+        options.add(new UserActionAbstract("Tidy up all") {
+            @Override
+            public void execute(final Workspace workspace, final View view, final Location at) {
+                tidyViews(getObjectIconViews());
+                tidyViews(getWindowViews());
+            }
+        });
+
+        options.add(new UserActionAbstract("Services...") {
+            @Override
+            public void execute(final Workspace workspace, final View view, final Location at) {
+                final List<Object> servicePojos = IsisContext.getServices();
+                final List<ObjectAdapter> serviceAdapters = Lists.newArrayList();
+                for (final Object servicePojo : servicePojos) {
+                    final AdapterManager adapterManager = getPersistenceSession().getAdapterManager();
+                    serviceAdapters.add(adapterManager.adapterFor(servicePojo));
+                }
+                final ObjectSpecification spec = getSpecificationLoader().loadSpecification(Object.class);
+                final FreeStandingList collection = new FreeStandingList(spec, serviceAdapters);
+                addWindowFor(getAdapterManager().adapterFor(collection), new Placement(at));
+            }
+
+        });
+
+        menuForChangingLook(options);
+
+        menuForChangingUsers(options);
+
+        options.add(new UserActionAbstract("Save User Profile", ActionType.USER) {
+            @Override
+            public void execute(final Workspace workspace, final View view, final Location at) {
+                final Feedback feedbackManager = getFeedbackManager();
+                feedbackManager.showBusyState(ApplicationWorkspace.this);
+                getViewManager().saveOpenObjects();
+                feedbackManager.addMessage("Profile saved");
+                feedbackManager.showBusyState(ApplicationWorkspace.this);
+            }
+        });
+    }
+
+    private void menuForChangingLook(final UserActionSet options) {
+        final UserActionSet set = options.addNewActionSet("Change Look", ActionType.USER);
+        for (final Look look : LookFactory.getAvailableLooks()) {
+            menuOptionForChangingLook(set, look, look.getName());
+        }
+    }
+
+    private void menuOptionForChangingLook(final UserActionSet set, final Look look, final String name) {
+        set.add(new UserActionAbstract(name) {
+            @Override
+            public void execute(final Workspace workspace, final View view, final Location at) {
+                LookFactory.setLook(look);
+                ApplicationWorkspace.this.invalidateLayout();
+                ApplicationWorkspace.this.markDamaged();
+            }
+
+            @Override
+            public Consent disabled(final View view) {
+                return LookFactory.getInstalledLook() == look ? new Veto("Current look") : Allow.DEFAULT;
+            }
+        });
+    }
+
+    private void menuForChangingUsers(final UserActionSet options) {
+        // TODO pick out users from the perspectives, but only show when in
+        // exploration mode
+        if (getAuthenticationSession() instanceof MultiUserExplorationSession) {
+            final MultiUserExplorationSession session = (MultiUserExplorationSession) getAuthenticationSession();
+
+            final Set<String> users = session.getUserNames();
+            final UserActionSet set = options.addNewActionSet("Change user", ActionType.EXPLORATION);
+            for (final String user : users) {
+                menuOptionForChangingUser(set, user, session.getUserName());
+            }
+        }
+    }
+
+    private void menuOptionForChangingUser(final UserActionSet set, final String user, final String currentUser) {
+        set.add(new UserActionAbstract(user) {
+            @Override
+            public void execute(final Workspace workspace, final View view, final Location at) {
+                final MultiUserExplorationSession session = (MultiUserExplorationSession) getAuthenticationSession();
+                session.setCurrentSession(user);
+            }
+
+            @Override
+            public Consent disabled(final View view) {
+                return user.equals(currentUser) ? new Veto("Current user") : Allow.DEFAULT;
+            }
+        });
+    }
+
+    @Override
+    protected View[] subviews() {
+        final Object[] viewsCopy = views.toArray();
+        final Object[] serviceViewsCopy = serviceViews.toArray();
+        final Object[] iconViewsCopy = iconViews.toArray();
+
+        final View v[] = new View[viewsCopy.length + serviceViewsCopy.length + iconViewsCopy.length];
+        int offset = 0;
+        Object[] src = serviceViewsCopy;
+        System.arraycopy(src, 0, v, offset, src.length);
+        offset += src.length;
+        src = iconViewsCopy;
+        System.arraycopy(src, 0, v, offset, src.length);
+        offset += src.length;
+        src = viewsCopy;
+        System.arraycopy(src, 0, v, offset, src.length);
+
+        return v;
+    }
+
+    public void clearServiceViews() {
+        final Enumeration e = serviceViews.elements();
+        while (e.hasMoreElements()) {
+            final View view = (View) e.nextElement();
+            view.markDamaged();
+        }
+        serviceViews.clear();
+    }
+
+    protected View[] getWindowViews() {
+        return createArrayOfViews(views);
+    }
+
+    private View[] createArrayOfViews(final Vector<View> views) {
+        final View[] array = new View[views.size()];
+        views.copyInto(array);
+        return array;
+    }
+
+    protected View[] getServiceIconViews() {
+        return createArrayOfViews(serviceViews);
+    }
+
+    protected View[] getObjectIconViews() {
+        return createArrayOfViews(iconViews);
+    }
+
+    private void tidyViews(final View[] views) {
+        for (final View v : views) {
+            v.setLocation(ApplicationWorkspaceBuilder.UNPLACED);
+        }
+        invalidateLayout();
+        markDamaged();
+    }
+
+    // //////////////////////////////////////////////////////////////////
+    // Dependencies (from singleton)
+    // //////////////////////////////////////////////////////////////////
+
+    private SpecificationLoaderSpi getSpecificationLoader() {
+        return IsisContext.getSpecificationLoader();
+    }
+
+    private PersistenceSession getPersistenceSession() {
+        return IsisContext.getPersistenceSession();
+    }
+
+    private AdapterManager getAdapterManager() {
+        return getPersistenceSession().getAdapterManager();
+    }
+
+    private AuthenticationSession getAuthenticationSession() {
+        return IsisContext.getAuthenticationSession();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/eb613703/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/viewer/basic/ApplicationWorkspaceBuilder.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/viewer/basic/ApplicationWorkspaceBuilder.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/viewer/basic/ApplicationWorkspaceBuilder.java
new file mode 100644
index 0000000..c74983a
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/viewer/basic/ApplicationWorkspaceBuilder.java
@@ -0,0 +1,204 @@
+/*
+ *  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.viewer.basic;
+
+import org.apache.log4j.Logger;
+
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.spec.ObjectSpecification;
+import org.apache.isis.core.runtime.userprofile.PerspectiveEntry;
+import org.apache.isis.runtimes.dflt.runtime.system.context.IsisContext;
+import org.apache.isis.viewer.dnd.drawing.Location;
+import org.apache.isis.viewer.dnd.drawing.Size;
+import org.apache.isis.viewer.dnd.service.PerspectiveContent;
+import org.apache.isis.viewer.dnd.view.Axes;
+import org.apache.isis.viewer.dnd.view.Placement;
+import org.apache.isis.viewer.dnd.view.View;
+import org.apache.isis.viewer.dnd.view.base.Layout;
+import org.apache.isis.viewer.dnd.view.composite.AbstractViewBuilder;
+
+/**
+ * WorkspaceBuilder builds a workspace view for an ObjectContent view by finding
+ * a collection of classes from a field called 'classes' and adding an icon for
+ * each element. Similarly, if there is a collection called 'objects' its
+ * elements are also added to the display.
+ * 
+ * <p>
+ * During lay-out any icons that have an UNPLACED location (-1, -1) are given a
+ * location. Objects of type ObjectSpecification are added to the left-hand
+ * side, while all other icons are placed on the right-hand side of the
+ * workspace view. Open windows are displayed in the centre.
+ */
+public class ApplicationWorkspaceBuilder extends AbstractViewBuilder {
+    private static final Logger LOG = Logger.getLogger(ApplicationWorkspaceBuilder.class);
+    private static final int PADDING = 10;
+    public static final Location UNPLACED = new Location(-1, -1);
+
+    public static class ApplicationLayout implements Layout {
+        @Override
+        public Size getRequiredSize(final View view) {
+            return new Size(600, 400);
+        }
+
+        public String getName() {
+            return "Simple Workspace";
+        }
+
+        @Override
+        public void layout(final View view1, final Size maximumSize) {
+            final ApplicationWorkspace view = (ApplicationWorkspace) view1;
+
+            final int widthUsed = layoutServiceIcons(maximumSize, view);
+            layoutObjectIcons(maximumSize, view);
+            layoutWindowViews(maximumSize, view, widthUsed);
+        }
+
+        private void layoutWindowViews(final Size maximumSize, final ApplicationWorkspace view, final int xOffset) {
+            final Size size = view.getSize();
+            size.contract(view.getPadding());
+
+            final int maxHeight = size.getHeight();
+            final int maxWidth = size.getWidth();
+
+            final int xWindow = xOffset + PADDING;
+            int yWindow = PADDING;
+
+            int xMinimized = 1;
+            int yMinimized = maxHeight - 1;
+
+            final View windows[] = view.getWindowViews();
+            for (final View v : windows) {
+                final Size componentSize = v.getRequiredSize(new Size(size));
+                v.setSize(componentSize);
+                if (v instanceof MinimizedView) {
+                    final Size s = v.getRequiredSize(Size.createMax());
+                    if (xMinimized + s.getWidth() > maxWidth) {
+                        xMinimized = 1;
+                        yMinimized -= s.getHeight() + 1;
+                    }
+                    v.setLocation(new Location(xMinimized, yMinimized - s.getHeight()));
+                    xMinimized += s.getWidth() + 1;
+
+                } else if (v.getLocation().equals(UNPLACED)) {
+                    final int height = componentSize.getHeight() + 6;
+                    v.setLocation(new Location(xWindow, yWindow));
+                    yWindow += height;
+
+                }
+                v.limitBoundsWithin(maximumSize);
+            }
+
+            for (final View window : windows) {
+                window.layout();
+            }
+        }
+
+        private int layoutServiceIcons(final Size maximumSize, final ApplicationWorkspace view) {
+            final Size size = view.getSize();
+            size.contract(view.getPadding());
+
+            final int maxHeight = size.getHeight();
+
+            int xService = PADDING;
+            int yService = PADDING;
+            int maxServiceWidth = 0;
+
+            final View views[] = view.getServiceIconViews();
+            for (final View v : views) {
+                final Size componentSize = v.getRequiredSize(new Size(size));
+                v.setSize(componentSize);
+                final int height = componentSize.getHeight() + 6;
+
+                final ObjectAdapter object = v.getContent().getAdapter();
+                if (object.getSpecification().isService()) {
+                    if (yService + height > maxHeight) {
+                        yService = PADDING;
+                        xService += maxServiceWidth + PADDING;
+                        maxServiceWidth = 0;
+                        LOG.debug("creating new column at " + xService + ", " + yService);
+                    }
+                    LOG.debug("service icon at " + xService + ", " + yService);
+                    v.setLocation(new Location(xService, yService));
+                    maxServiceWidth = Math.max(maxServiceWidth, componentSize.getWidth());
+                    yService += height;
+                }
+                v.limitBoundsWithin(maximumSize);
+            }
+
+            return xService + maxServiceWidth;
+        }
+
+        private void layoutObjectIcons(final Size maximumSize, final ApplicationWorkspace view) {
+            final Size size = view.getSize();
+            size.contract(view.getPadding());
+
+            final int maxWidth = size.getWidth();
+
+            final int xObject = maxWidth - PADDING;
+            int yObject = PADDING;
+
+            final View views[] = view.getObjectIconViews();
+            for (final View v : views) {
+                final Size componentSize = v.getRequiredSize(new Size(size));
+                v.setSize(componentSize);
+                if (v.getLocation().equals(UNPLACED)) {
+                    final int height = componentSize.getHeight() + 6;
+                    v.setLocation(new Location(xObject - componentSize.getWidth(), yObject));
+                    yObject += height;
+                }
+                v.limitBoundsWithin(maximumSize);
+            }
+        }
+    }
+
+    @Override
+    public void build(final View view1, final Axes axes) {
+        final ApplicationWorkspace workspace = (ApplicationWorkspace) view1;
+
+        final PerspectiveContent perspectiveContent = (PerspectiveContent) view1.getContent();
+
+        // REVIEW is this needed?
+        workspace.clearServiceViews();
+
+        final PerspectiveEntry perspective = perspectiveContent.getPerspective();
+        for (final Object object : perspective.getObjects()) {
+            final ObjectAdapter adapter = IsisContext.getPersistenceSession().getAdapterManager().adapterFor(object);
+            workspace.addIconFor(adapter, new Placement(ApplicationWorkspaceBuilder.UNPLACED));
+        }
+
+        for (final Object service : perspective.getServices()) {
+            final ObjectAdapter adapter = IsisContext.getPersistenceSession().getAdapterManager().adapterFor(service);
+            if (isHidden(adapter)) {
+                continue;
+            }
+            workspace.addServiceIconFor(adapter);
+        }
+    }
+
+    private boolean isHidden(final ObjectAdapter serviceNO) {
+        final ObjectSpecification serviceNoSpec = serviceNO.getSpecification();
+        return serviceNoSpec.isHidden();
+    }
+
+    public boolean canDisplay(final ObjectAdapter object) {
+        return object instanceof ObjectAdapter && object != null;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/eb613703/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/viewer/basic/CollectionDisplayIterator.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/viewer/basic/CollectionDisplayIterator.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/viewer/basic/CollectionDisplayIterator.java
new file mode 100644
index 0000000..e9b7d99
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/viewer/basic/CollectionDisplayIterator.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.viewer.basic;
+
+import java.util.Enumeration;
+
+public interface CollectionDisplayIterator {
+
+    /**
+     * Return cache to be viewed on current page
+     */
+    public Enumeration displayElements();
+
+    /**
+     * Position cursor at first element
+     */
+    public void first();
+
+    public int getDisplaySize();
+
+    /**
+     * If true there is a next page to display, and 'next' and 'last' options
+     * are valid
+     */
+    public boolean hasNext();
+
+    public boolean hasPrevious();
+
+    /**
+     * Position cursor at last
+     */
+    public void last();
+
+    /**
+     * Position cursor at beginning of next page
+     */
+    public void next();
+
+    public int position();
+
+    /**
+     * Position cursor at beginning of previous page
+     */
+    public void previous();
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/eb613703/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/viewer/basic/DragContentSpecification.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/viewer/basic/DragContentSpecification.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/viewer/basic/DragContentSpecification.java
new file mode 100644
index 0000000..8e20462
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/viewer/basic/DragContentSpecification.java
@@ -0,0 +1,37 @@
+/*
+ *  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.viewer.basic;
+
+import org.apache.isis.viewer.dnd.drawing.ColorsAndFonts;
+import org.apache.isis.viewer.dnd.icon.IconSpecification;
+import org.apache.isis.viewer.dnd.view.Axes;
+import org.apache.isis.viewer.dnd.view.Content;
+import org.apache.isis.viewer.dnd.view.Toolkit;
+import org.apache.isis.viewer.dnd.view.View;
+import org.apache.isis.viewer.dnd.view.border.LineBorder;
+
+public class DragContentSpecification extends IconSpecification {
+
+    @Override
+    public View createView(final Content content, final Axes axes, final int sequence) {
+        final View icon = super.createView(content, axes, sequence);
+        return new LineBorder(1, Toolkit.getColor(ColorsAndFonts.COLOR_SECONDARY1), icon);
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/eb613703/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/viewer/basic/FallbackView.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/viewer/basic/FallbackView.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/viewer/basic/FallbackView.java
new file mode 100644
index 0000000..7ef7f36
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/viewer/basic/FallbackView.java
@@ -0,0 +1,111 @@
+/*
+ *  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.viewer.basic;
+
+import org.apache.isis.viewer.dnd.drawing.Canvas;
+import org.apache.isis.viewer.dnd.drawing.ColorsAndFonts;
+import org.apache.isis.viewer.dnd.drawing.Location;
+import org.apache.isis.viewer.dnd.drawing.Size;
+import org.apache.isis.viewer.dnd.view.Axes;
+import org.apache.isis.viewer.dnd.view.Content;
+import org.apache.isis.viewer.dnd.view.Toolkit;
+import org.apache.isis.viewer.dnd.view.View;
+import org.apache.isis.viewer.dnd.view.ViewAreaType;
+import org.apache.isis.viewer.dnd.view.ViewRequirement;
+import org.apache.isis.viewer.dnd.view.ViewSpecification;
+import org.apache.isis.viewer.dnd.view.base.ObjectView;
+
+public class FallbackView extends ObjectView {
+
+    public static class Specification implements ViewSpecification {
+        @Override
+        public boolean canDisplay(final ViewRequirement requirement) {
+            return true;
+        }
+
+        @Override
+        public View createView(final Content content, final Axes axes, final int sequence) {
+            return new FallbackView(content, this);
+        }
+
+        @Override
+        public String getName() {
+            return "Fallback";
+        }
+
+        @Override
+        public boolean isAligned() {
+            return false;
+        }
+
+        @Override
+        public boolean isOpen() {
+            return false;
+        }
+
+        @Override
+        public boolean isReplaceable() {
+            return false;
+        }
+
+        @Override
+        public boolean isResizeable() {
+            return false;
+        }
+
+        @Override
+        public boolean isSubView() {
+            return false;
+        }
+    }
+
+    protected FallbackView(final Content content, final ViewSpecification specification) {
+        super(content, specification);
+    }
+
+    @Override
+    public void draw(final Canvas canvas) {
+        super.draw(canvas);
+
+        final Size size = getSize();
+        final int width = size.getWidth() - 1;
+        final int height = size.getHeight() - 1;
+        canvas.drawSolidRectangle(0, 0, width, height, Toolkit.getColor(ColorsAndFonts.COLOR_SECONDARY3));
+        canvas.drawSolidRectangle(0, 0, 10, height, Toolkit.getColor(ColorsAndFonts.COLOR_SECONDARY2));
+        canvas.drawLine(10, 0, 10, height - 2, Toolkit.getColor(ColorsAndFonts.COLOR_BLACK));
+        canvas.drawRectangle(0, 0, width, height, Toolkit.getColor(ColorsAndFonts.COLOR_BLACK));
+        canvas.drawText(getContent().title(), 14, getBaseline(), Toolkit.getColor(ColorsAndFonts.COLOR_BLACK), Toolkit.getText(ColorsAndFonts.TEXT_NORMAL));
+    }
+
+    @Override
+    public int getBaseline() {
+        return 14;
+    }
+
+    @Override
+    public Size getRequiredSize(final Size availableSpace) {
+        return new Size(150, 20);
+    }
+
+    @Override
+    public ViewAreaType viewAreaType(final Location mouseLocation) {
+        return mouseLocation.getX() <= 10 ? ViewAreaType.VIEW : ViewAreaType.CONTENT;
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/eb613703/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/viewer/basic/Identifier.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/viewer/basic/Identifier.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/viewer/basic/Identifier.java
new file mode 100644
index 0000000..2c44e19
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/viewer/basic/Identifier.java
@@ -0,0 +1,82 @@
+/*
+ *  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.viewer.basic;
+
+import org.apache.isis.core.commons.debug.DebugBuilder;
+import org.apache.isis.viewer.dnd.drawing.Canvas;
+import org.apache.isis.viewer.dnd.drawing.ColorsAndFonts;
+import org.apache.isis.viewer.dnd.drawing.Size;
+import org.apache.isis.viewer.dnd.view.ContentDrag;
+import org.apache.isis.viewer.dnd.view.Toolkit;
+import org.apache.isis.viewer.dnd.view.View;
+import org.apache.isis.viewer.dnd.view.base.AbstractViewDecorator;
+
+public class Identifier extends AbstractViewDecorator {
+    private boolean identified;
+
+    public Identifier(final View wrappedView) {
+        super(wrappedView);
+    }
+
+    @Override
+    public void debugDetails(final DebugBuilder debug) {
+        debug.append("Identifier");
+    }
+
+    @Override
+    public void dragIn(final ContentDrag drag) {
+        wrappedView.dragIn(drag);
+        markDamaged();
+    }
+
+    @Override
+    public void dragOut(final ContentDrag drag) {
+        wrappedView.dragOut(drag);
+        markDamaged();
+    }
+
+    @Override
+    public void draw(final Canvas canvas) {
+        final Size s = getSize();
+        canvas.drawSolidRectangle(0, 0, s.getWidth(), s.getHeight(), Toolkit.getColor(ColorsAndFonts.COLOR_SECONDARY3));
+        wrappedView.draw(canvas);
+    }
+
+    @Override
+    public void entered() {
+        getState().setContentIdentified();
+        wrappedView.entered();
+        identified = true;
+        markDamaged();
+    }
+
+    @Override
+    public void exited() {
+        getState().clearObjectIdentified();
+        wrappedView.exited();
+        identified = false;
+        markDamaged();
+    }
+
+    @Override
+    public String toString() {
+        return wrappedView.toString() + "/Identifier [identified=" + identified + "]";
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/eb613703/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/viewer/basic/InnerWorkspaceSpecification.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/viewer/basic/InnerWorkspaceSpecification.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/viewer/basic/InnerWorkspaceSpecification.java
new file mode 100644
index 0000000..595df22
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/viewer/basic/InnerWorkspaceSpecification.java
@@ -0,0 +1,36 @@
+/*
+ *  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.viewer.basic;
+
+import org.apache.isis.viewer.dnd.view.Axes;
+import org.apache.isis.viewer.dnd.view.Content;
+import org.apache.isis.viewer.dnd.view.View;
+
+public class InnerWorkspaceSpecification extends WorkspaceSpecification {
+    @Override
+    public View createView(final Content content, final Axes axes, final int sequence) {
+        return super.createView(content, axes, sequence);
+    }
+
+    @Override
+    public String getName() {
+        return "Workspace";
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/eb613703/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/viewer/basic/LogoBackground.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/viewer/basic/LogoBackground.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/viewer/basic/LogoBackground.java
new file mode 100644
index 0000000..00d9fff
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/viewer/basic/LogoBackground.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.viewer.basic;
+
+import org.apache.log4j.Logger;
+
+import org.apache.isis.core.commons.config.IsisConfiguration;
+import org.apache.isis.runtimes.dflt.runtime.system.context.IsisContext;
+import org.apache.isis.viewer.dnd.drawing.Background;
+import org.apache.isis.viewer.dnd.drawing.Canvas;
+import org.apache.isis.viewer.dnd.drawing.Image;
+import org.apache.isis.viewer.dnd.drawing.ImageFactory;
+import org.apache.isis.viewer.dnd.drawing.Location;
+import org.apache.isis.viewer.dnd.drawing.Size;
+import org.apache.isis.viewer.dnd.util.Properties;
+
+public class LogoBackground implements Background {
+    private static final Logger LOG = Logger.getLogger(LogoBackground.class);
+    private static final String PARAMETER_BASE = Properties.PROPERTY_BASE + "logo-background.";
+    private Location location;
+    private Image logo;
+    private Size logoSize;
+
+    public LogoBackground() {
+        final IsisConfiguration configuration = IsisContext.getConfiguration();
+
+        final String fileName = configuration.getString(PARAMETER_BASE + "image", "background");
+        logo = ImageFactory.getInstance().loadImage(fileName);
+
+        if (logo == null) {
+            logo = ImageFactory.getInstance().loadImage("poweredby-logo");
+        }
+
+        if (logo == null) {
+            LOG.debug("logo image not found: " + fileName);
+        } else {
+            location = Properties.getLocation(PARAMETER_BASE + "location", new Location(-30, -30));
+            logoSize = Properties.getSize(PARAMETER_BASE + "size", logo.getSize());
+        }
+    }
+
+    @Override
+    public void draw(final Canvas canvas, final Size viewSize) {
+        if (logo != null) {
+            int x;
+            int y;
+
+            if (location.getX() == 0 && location.getY() == 0) {
+                x = viewSize.getWidth() / 2 - logoSize.getWidth() / 2;
+                y = viewSize.getHeight() / 2 - logoSize.getHeight() / 2;
+            } else {
+                x = (location.getX() >= 0) ? location.getX() : viewSize.getWidth() + location.getX() - logoSize.getWidth();
+                y = (location.getY() >= 0) ? location.getY() : viewSize.getHeight() + location.getY() - logoSize.getHeight();
+            }
+            canvas.drawImage(logo, x, y, logoSize.getWidth(), logoSize.getHeight());
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/eb613703/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/viewer/basic/MinimizedView.java
----------------------------------------------------------------------
diff --git a/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/viewer/basic/MinimizedView.java b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/viewer/basic/MinimizedView.java
new file mode 100644
index 0000000..33910fb
--- /dev/null
+++ b/component/viewer/dnd/impl/src/main/java/org/apache/isis/viewer/dnd/viewer/basic/MinimizedView.java
@@ -0,0 +1,455 @@
+/*
+ *  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.viewer.basic;
+
+import org.apache.isis.core.commons.debug.DebugBuilder;
+import org.apache.isis.core.commons.exceptions.IsisException;
+import org.apache.isis.core.metamodel.consent.Allow;
+import org.apache.isis.core.metamodel.consent.Consent;
+import org.apache.isis.core.metamodel.spec.ActionType;
+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.icon.SubviewIconSpecification;
+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.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.ViewConstants;
+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.Workspace;
+import org.apache.isis.viewer.dnd.view.base.AbstractView;
+import org.apache.isis.viewer.dnd.view.option.UserActionAbstract;
+import org.apache.isis.viewer.dnd.view.window.WindowControl;
+
+public class MinimizedView extends AbstractView {
+    private class CloseWindowControl extends WindowControl {
+
+        public CloseWindowControl(final View target) {
+            super(new UserAction() {
+                @Override
+                public Consent disabled(final View view) {
+                    return Allow.DEFAULT;
+                }
+
+                @Override
+                public void execute(final Workspace workspace, final View view, final Location at) {
+                    ((MinimizedView) view).close();
+                }
+
+                @Override
+                public String getDescription(final View view) {
+                    return "Close " + view.getSpecification().getName();
+                }
+
+                @Override
+                public String getHelp(final View view) {
+                    return null;
+                }
+
+                @Override
+                public String getName(final View view) {
+                    return "Close view";
+                }
+
+                @Override
+                public ActionType getType() {
+                    return ActionType.USER;
+                }
+            }, target);
+        }
+
+        @Override
+        public void draw(final Canvas canvas) {
+            final int x = 0;
+            final int y = 0;
+            final Color crossColor = Toolkit.getColor(ColorsAndFonts.COLOR_BLACK);
+            canvas.drawLine(x + 4, y + 3, x + 10, y + 9, crossColor);
+            canvas.drawLine(x + 5, y + 3, x + 11, y + 9, crossColor);
+            canvas.drawLine(x + 10, y + 3, x + 4, y + 9, crossColor);
+            canvas.drawLine(x + 11, y + 3, x + 5, y + 9, crossColor);
+        }
+    }
+
+    private class RestoreWindowControl extends WindowControl {
+        public RestoreWindowControl(final View target) {
+            super(new UserAction() {
+
+                @Override
+                public Consent disabled(final View view) {
+                    return Allow.DEFAULT;
+                }
+
+                @Override
+                public void execute(final Workspace workspace, final View view, final Location at) {
+                    ((MinimizedView) view).restore();
+                }
+
+                @Override
+                public String getDescription(final View view) {
+                    return "Restore " + view.getSpecification().getName() + " to normal size";
+                }
+
+                @Override
+                public String getHelp(final View view) {
+                    return null;
+                }
+
+                @Override
+                public String getName(final View view) {
+                    return "Restore view";
+                }
+
+                @Override
+                public ActionType getType() {
+                    return ActionType.USER;
+                }
+            }, target);
+        }
+
+        @Override
+        public void draw(final Canvas canvas) {
+            final int x = 0;
+            final int y = 0;
+            final Color black = Toolkit.getColor(ColorsAndFonts.COLOR_BLACK);
+            canvas.drawRectangle(x + 1, y + 1, WIDTH - 1, HEIGHT - 1, black);
+            canvas.drawLine(x + 2, y + 2, x + WIDTH - 2, y + 2, black);
+            canvas.drawLine(x + 2, y + 3, x + WIDTH - 2, y + 3, black);
+        }
+    }
+
+    private static class Specification implements ViewSpecification {
+
+        @Override
+        public boolean canDisplay(final ViewRequirement requirement) {
+            return false;
+        }
+
+        @Override
+        public View createView(final Content content, final Axes axes, final int sequence) {
+            return null;
+        }
+
+        @Override
+        public String getName() {
+            return "minimized view";
+        }
+
+        @Override
+        public boolean isAligned() {
+            return false;
+        }
+
+        @Override
+        public boolean isOpen() {
+            return false;
+        }
+
+        @Override
+        public boolean isReplaceable() {
+            return false;
+        }
+
+        @Override
+        public boolean isResizeable() {
+            return false;
+        }
+
+        @Override
+        public boolean isSubView() {
+            return false;
+        }
+
+    }
+
+    private final static int BORDER_WIDTH = 5;
+    private final WindowControl controls[];
+    private View iconView;
+
+    private final View minimizedView;
+
+    public MinimizedView(final View viewToMinimize) {
+        super(viewToMinimize.getContent(), new Specification());
+        this.minimizedView = viewToMinimize;
+        iconView = new SubviewIconSpecification().createView(viewToMinimize.getContent(), viewToMinimize.getViewAxes(), -1);
+        iconView.setParent(this);
+        controls = new WindowControl[] { new RestoreWindowControl(this), new CloseWindowControl(this) };
+    }
+
+    @Override
+    public void debug(final DebugBuilder debug) {
+        super.debug(debug);
+        debug.appendln("minimized view", minimizedView);
+        debug.appendln();
+
+        debug.appendln("icon size", iconView.getSize());
+        debug.append(iconView);
+    }
+
+    @Override
+    public void dispose() {
+        super.dispose();
+        iconView.dispose();
+        // viewToMinimize.dispose();
+    }
+
+    @Override
+    public DragEvent dragStart(final DragStart drag) {
+        if (iconView.getBounds().contains(drag.getLocation())) {
+            drag.subtract(BORDER_WIDTH, BORDER_WIDTH);
+            return iconView.dragStart(drag);
+        } else {
+            return super.dragStart(drag);
+        }
+        // View dragOverlay = new DragViewOutline(getView());
+        // return new ViewDrag(this, new Offset(drag.getLocation()),
+        // dragOverlay);
+    }
+
+    @Override
+    public void draw(final Canvas canvas) {
+        super.draw(canvas);
+
+        final Size size = getSize();
+        final int width = size.getWidth();
+        final int height = size.getHeight();
+        final int left = 3;
+        final int top = 3;
+
+        final boolean hasFocus = containsFocus();
+        final Color lightColor = hasFocus ? Toolkit.getColor(ColorsAndFonts.COLOR_SECONDARY1) : Toolkit.getColor(ColorsAndFonts.COLOR_SECONDARY2);
+        clearBackground(canvas, Toolkit.getColor(ColorsAndFonts.COLOR_WINDOW));
+        canvas.drawRectangle(1, 0, width - 2, height, lightColor);
+        canvas.drawRectangle(0, 1, width, height - 2, lightColor);
+        for (int i = 2; i < left; i++) {
+            canvas.drawRectangle(i, i, width - 2 * i, height - 2 * i, lightColor);
+        }
+        final ViewState state = getState();
+        if (state.isActive()) {
+            final int i = left;
+            canvas.drawRectangle(i, top, width - 2 * i, height - 2 * i - top, Toolkit.getColor(ColorsAndFonts.COLOR_ACTIVE));
+        }
+
+        final int bw = controls[0].getLocation().getX() - 3; // controls.length
+                                                             // *
+                                                             // WindowControl.WIDTH;
+        canvas.drawSolidRectangle(bw, top, width - bw - 3, height - top * 2, Toolkit.getColor(ColorsAndFonts.COLOR_SECONDARY3));
+        canvas.drawLine(bw - 1, top, bw - 1, height - top * 2, lightColor);
+
+        for (int i = 0; controls != null && i < controls.length; i++) {
+            final Canvas controlCanvas = canvas.createSubcanvas(controls[i].getBounds());
+            controls[i].draw(controlCanvas);
+        }
+
+        final Canvas c = canvas.createSubcanvas(iconView.getBounds());
+        iconView.draw(c);
+    }
+
+    @Override
+    public Size getRequiredSize(final Size availableSpace) {
+        final Size size = new Size();
+
+        size.extendWidth(BORDER_WIDTH);
+        final Size iconMaximumSize = iconView.getRequiredSize(Size.createMax());
+        size.extendWidth(iconMaximumSize.getWidth());
+
+        size.extendHeight(iconMaximumSize.getHeight());
+        size.ensureHeight(WindowControl.HEIGHT);
+        size.extendHeight(BORDER_WIDTH);
+        size.extendHeight(BORDER_WIDTH);
+
+        size.extendWidth(ViewConstants.HPADDING);
+        size.extendWidth(controls.length * (WindowControl.WIDTH + ViewConstants.HPADDING));
+        size.extendWidth(BORDER_WIDTH);
+        return size;
+    }
+
+    @Override
+    public Padding getPadding() {
+        return new Padding(BORDER_WIDTH, BORDER_WIDTH, BORDER_WIDTH, BORDER_WIDTH);
+    }
+
+    @Override
+    public void layout() {
+        final Size size = getRequiredSize(Size.createMax());
+
+        layoutControls(size.getWidth());
+
+        size.contractWidth(BORDER_WIDTH * 2);
+        size.contractWidth(ViewConstants.HPADDING);
+        size.contractWidth(controls.length * (WindowControl.WIDTH + ViewConstants.HPADDING));
+
+        size.contractHeight(BORDER_WIDTH * 2);
+
+        iconView.setLocation(new Location(BORDER_WIDTH, BORDER_WIDTH));
+        iconView.setSize(size);
+    }
+
+    private void layoutControls(final int width) {
+        final int widthControl = WindowControl.WIDTH + ViewConstants.HPADDING;
+        int x = width - BORDER_WIDTH + ViewConstants.HPADDING;
+        x -= widthControl * controls.length;
+        final int y = BORDER_WIDTH;
+
+        for (final WindowControl control : controls) {
+            control.setSize(control.getRequiredSize(Size.createMax()));
+            control.setLocation(new Location(x, y));
+            x += widthControl;
+        }
+    }
+
+    private void restore() {
+        final Workspace workspace = getWorkspace();
+        final View[] views = workspace.getSubviews();
+        for (final View view : views) {
+            if (view == this) {
+                dispose();
+
+                minimizedView.setParent(workspace);
+                // workspace.removeView(this);
+                workspace.addView(minimizedView);
+                workspace.invalidateLayout();
+
+                return;
+
+            }
+        }
+    }
+
+    private void close() {
+        final Workspace workspace = getWorkspace();
+        final View[] views = workspace.getSubviews();
+        for (final View view : views) {
+            if (view == this) {
+                dispose();
+
+                minimizedView.setParent(workspace);
+                workspace.invalidateLayout();
+                workspace.addView(minimizedView);
+                minimizedView.dispose();
+
+                return;
+
+            }
+        }
+    }
+
+    @Override
+    public void removeView(final View view) {
+        if (view == iconView) {
+            iconView = null;
+        } else {
+            throw new IsisException("No view " + view + " in " + this);
+        }
+    }
+
+    @Override
+    public void secondClick(final Click click) {
+        restore();
+    }
+
+    @Override
+    public ViewAreaType viewAreaType(final Location location) {
+        location.subtract(BORDER_WIDTH, BORDER_WIDTH);
+        return iconView.viewAreaType(location);
+    }
+
+    @Override
+    public void viewMenuOptions(final UserActionSet options) {
+        options.add(new UserActionAbstract("Restore") {
+
+            @Override
+            public void execute(final Workspace workspace, final View view, final Location at) {
+                restore();
+            }
+        });
+        super.viewMenuOptions(options);
+    }
+
+    @Override
+    public void firstClick(final Click click) {
+        final View button = overControl(click.getLocation());
+        if (button == null) {
+            /*
+             * if (overBorder(click.getLocation())) { Workspace workspace =
+             * getWorkspace(); if (workspace != null) { if (click.button2()) {
+             * workspace.lower(getView()); } else if (click.button1()) {
+             * workspace.raise(getView()); } } } else { super.firstClick(click);
+             * }
+             */} else {
+            button.firstClick(click);
+        }
+
+    }
+
+    private View overControl(final Location location) {
+        for (final WindowControl control : controls) {
+            if (control.getBounds().contains(location)) {
+                return control;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public void dragIn(final ContentDrag drag) {
+        if (iconView.getBounds().contains(drag.getTargetLocation())) {
+            drag.subtract(BORDER_WIDTH, BORDER_WIDTH);
+            iconView.dragIn(drag);
+        }
+    }
+
+    @Override
+    public void dragOut(final ContentDrag drag) {
+        if (iconView.getBounds().contains(drag.getTargetLocation())) {
+            drag.subtract(BORDER_WIDTH, BORDER_WIDTH);
+            iconView.dragOut(drag);
+        }
+    }
+
+    @Override
+    public View identify(final Location location) {
+        if (iconView.getBounds().contains(location)) {
+            location.subtract(BORDER_WIDTH, BORDER_WIDTH);
+            return iconView.identify(location);
+        }
+        return this;
+    }
+
+    @Override
+    public void drop(final ContentDrag drag) {
+        if (iconView.getBounds().contains(drag.getTargetLocation())) {
+            drag.subtract(BORDER_WIDTH, BORDER_WIDTH);
+            iconView.drop(drag);
+        }
+    }
+}