]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.scenegraph.ui/src/org/simantics/scenegraph/ui/SceneGraphViewPart.java
Fixed all line endings of the repository
[simantics/platform.git] / bundles / org.simantics.scenegraph.ui / src / org / simantics / scenegraph / ui / SceneGraphViewPart.java
index d6e95399355b01726d287bf9aa127ecf5c2610d8..b1b60949f128e215b62d3834803805807c3a39cb 100644 (file)
-/*******************************************************************************\r
- * Copyright (c) 2007, 2010 Association for Decentralized Information Management\r
- * in Industry THTH ry.\r
- * All rights reserved. This program and the accompanying materials\r
- * are made available under the terms of the Eclipse Public License v1.0\r
- * which accompanies this distribution, and is available at\r
- * http://www.eclipse.org/legal/epl-v10.html\r
- *\r
- * Contributors:\r
- *     VTT Technical Research Centre of Finland - initial API and implementation\r
- *******************************************************************************/\r
-package org.simantics.scenegraph.ui;\r
-\r
-import java.io.ByteArrayOutputStream;\r
-import java.io.PrintStream;\r
-import java.lang.ref.WeakReference;\r
-import java.util.List;\r
-\r
-import org.eclipse.core.commands.Command;\r
-import org.eclipse.core.commands.State;\r
-import org.eclipse.jface.layout.TreeColumnLayout;\r
-import org.eclipse.jface.resource.ImageDescriptor;\r
-import org.eclipse.jface.resource.JFaceResources;\r
-import org.eclipse.jface.resource.LocalResourceManager;\r
-import org.eclipse.jface.viewers.ColumnLabelProvider;\r
-import org.eclipse.jface.viewers.ColumnWeightData;\r
-import org.eclipse.jface.viewers.DoubleClickEvent;\r
-import org.eclipse.jface.viewers.IDoubleClickListener;\r
-import org.eclipse.jface.viewers.ISelectionChangedListener;\r
-import org.eclipse.jface.viewers.IStructuredSelection;\r
-import org.eclipse.jface.viewers.ITreeContentProvider;\r
-import org.eclipse.jface.viewers.SelectionChangedEvent;\r
-import org.eclipse.jface.viewers.TreePath;\r
-import org.eclipse.jface.viewers.TreeViewer;\r
-import org.eclipse.jface.viewers.TreeViewerColumn;\r
-import org.eclipse.jface.viewers.Viewer;\r
-import org.eclipse.jface.viewers.ViewerCell;\r
-import org.eclipse.swt.SWT;\r
-import org.eclipse.swt.dnd.Clipboard;\r
-import org.eclipse.swt.dnd.TextTransfer;\r
-import org.eclipse.swt.dnd.Transfer;\r
-import org.eclipse.swt.graphics.Image;\r
-import org.eclipse.swt.widgets.Composite;\r
-import org.eclipse.swt.widgets.Shell;\r
-import org.eclipse.ui.IPartListener2;\r
-import org.eclipse.ui.IPartService;\r
-import org.eclipse.ui.IWorkbenchPage;\r
-import org.eclipse.ui.IWorkbenchPart;\r
-import org.eclipse.ui.IWorkbenchPartReference;\r
-import org.eclipse.ui.IWorkbenchWindow;\r
-import org.eclipse.ui.PlatformUI;\r
-import org.eclipse.ui.commands.ICommandService;\r
-import org.eclipse.ui.contexts.IContextActivation;\r
-import org.eclipse.ui.contexts.IContextService;\r
-import org.eclipse.ui.part.ViewPart;\r
-import org.simantics.scenegraph.ILookupService;\r
-import org.simantics.scenegraph.INode;\r
-import org.simantics.scenegraph.g2d.G2DParentNode;\r
-import org.simantics.scenegraph.g2d.G2DSceneGraph;\r
-import org.simantics.scenegraph.g2d.IG2DNode;\r
-import org.simantics.scenegraph.g2d.nodes.BoundsNode;\r
-import org.simantics.scenegraph.g2d.nodes.BranchPointNode;\r
-import org.simantics.scenegraph.g2d.nodes.EdgeNode;\r
-import org.simantics.scenegraph.g2d.nodes.GridNode;\r
-import org.simantics.scenegraph.g2d.nodes.LinkNode;\r
-import org.simantics.scenegraph.g2d.nodes.NavigationNode;\r
-import org.simantics.scenegraph.g2d.nodes.PageBorderNode;\r
-import org.simantics.scenegraph.g2d.nodes.RulerNode;\r
-import org.simantics.scenegraph.g2d.nodes.SVGNode;\r
-import org.simantics.scenegraph.g2d.nodes.ShapeNode;\r
-import org.simantics.scenegraph.g2d.nodes.SingleElementNode;\r
-import org.simantics.scenegraph.g2d.nodes.TransformNode;\r
-import org.simantics.scenegraph.utils.NodeUtil;\r
-import org.simantics.scenegraph.utils.NodeUtil.NodeProcedure;\r
-\r
-/**\r
- * This view shows the contents of a 2D/3D canvas scenegraph through a tree\r
- * viewer.\r
- * \r
- * <p>\r
- * The viewer sources its scene graph input from the currently active workbench\r
- * editor. It does not automatically track the active editor part but instead\r
- * has to be refreshed manually (F5).\r
- * </p>\r
- * \r
- * <p>\r
- * References to actual scene graph nodes are only kept as {@link WeakReference}\r
- * instances allowing them to be garbage collected if the scene graph is\r
- * disposed of.\r
- * </p>\r
- * \r
- * @author Tuukka Lehtonen\r
- */\r
-public class SceneGraphViewPart extends ViewPart {\r
-\r
-    TreeViewer           tree;\r
-    LocalResourceManager resourceManager;\r
-    boolean              bootstrapped = false;\r
-    IContextActivation   contextActivation;\r
-    int                  currentNodeCount = 0;\r
-    boolean              linkToPart;\r
-    IWorkbenchPart       lastPart;\r
-    AttributeDialog      attributeDialog;\r
-\r
-    final ImageDescriptor ROOT = ImageDescriptor.createFromURL(getClass().getResource("bullet_home.png"));\r
-    final ImageDescriptor CANVAS_BOUNDS = ImageDescriptor.createFromURL(getClass().getResource("application.png"));\r
-    final ImageDescriptor SHAPE = ImageDescriptor.createFromURL(getClass().getResource("shape_shadow.png"));\r
-    final ImageDescriptor NAVIGATION = ImageDescriptor.createFromURL(getClass().getResource("arrow_out_longer.png"));\r
-    final ImageDescriptor SVG = ImageDescriptor.createFromURL(getClass().getResource("script_code.png"));\r
-    final ImageDescriptor TRANSFORM = ImageDescriptor.createFromURL(getClass().getResource("arrow_nsew.png"));\r
-    final ImageDescriptor ELEMENT = ImageDescriptor.createFromURL(getClass().getResource("shape_handles.png"));\r
-    final ImageDescriptor PARENT = ImageDescriptor.createFromURL(getClass().getResource("share.png"));\r
-    final ImageDescriptor GRID = ImageDescriptor.createFromURL(getClass().getResource("border_all.png"));\r
-    final ImageDescriptor RULER = ImageDescriptor.createFromURL(getClass().getResource("text_ruler.png"));\r
-    final ImageDescriptor PAGE_BORDER = ImageDescriptor.createFromURL(getClass().getResource("page_white.png"));\r
-    final ImageDescriptor EDGE = ImageDescriptor.createFromURL(getClass().getResource("arrow_ew.png"));\r
-    final ImageDescriptor BRANCH_POINT = ImageDescriptor.createFromURL(getClass().getResource("bullet_black.png"));\r
-    final ImageDescriptor LINK = ImageDescriptor.createFromURL(getClass().getResource("link.png"));\r
-\r
-    NodeProcedure<NodeProxy> nodeProcedure = new NodeProcedure<NodeProxy>() {\r
-        @Override\r
-        public NodeProxy execute(INode node, String id) {\r
-            return new NodeProxy(node, id);\r
-        }\r
-    };\r
-\r
-    class ContentProvider implements ITreeContentProvider {\r
-\r
-        @Override\r
-        public Object[] getChildren(Object parentElement) {\r
-            if (parentElement instanceof NodeProxy) {\r
-                NodeProxy np = (NodeProxy) parentElement;\r
-                INode n = np.getNode();\r
-                if (n != null) {\r
-                    List<NodeProxy> children = NodeUtil.forChildren(n, nodeProcedure);\r
-                    return children.toArray();\r
-                }\r
-            }\r
-            return new Object[0];\r
-        }\r
-\r
-        @Override\r
-        public Object getParent(Object element) {\r
-            return null;\r
-        }\r
-\r
-        @Override\r
-        public boolean hasChildren(Object element) {\r
-            if (element instanceof NodeProxy) {\r
-                NodeProxy np = (NodeProxy) element;\r
-                INode n = np.getNode();\r
-                if (n != null)\r
-                    return NodeUtil.hasChildren(n);\r
-            }\r
-            return false;\r
-        }\r
-\r
-        @Override\r
-        public Object[] getElements(Object inputElement) {\r
-            if (inputElement instanceof INode[]) {\r
-                INode[] ns = (INode[]) inputElement;\r
-                NodeProxy[] result = new NodeProxy[ns.length];\r
-                for (int i = 0; i < ns.length; ++i)\r
-                    result[i] = new NodeProxy(ns[i], "root");\r
-                return result;\r
-            }\r
-            if (inputElement instanceof INode) {\r
-                INode n = (INode) inputElement;\r
-                return new Object[] { new NodeProxy(n, "root") };\r
-            }\r
-            return new Object[0];\r
-        }\r
-\r
-        @Override\r
-        public void dispose() {\r
-        }\r
-\r
-        @Override\r
-        public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {\r
-        }\r
-    }\r
-\r
-    private Image toImage(NodeProxy proxy) {\r
-        INode n = proxy.getNode();\r
-        if (n == null)\r
-            return null;\r
-\r
-        if (n instanceof G2DSceneGraph) {\r
-            return resourceManager.createImage(ROOT);\r
-        } else if (n instanceof SingleElementNode) {\r
-            return resourceManager.createImage(ELEMENT);\r
-        } else if (n instanceof TransformNode) {\r
-            return resourceManager.createImage(TRANSFORM);\r
-        } else if (n instanceof NavigationNode) {\r
-            return resourceManager.createImage(NAVIGATION);\r
-        } else if (n instanceof BoundsNode) {\r
-            return resourceManager.createImage(CANVAS_BOUNDS);\r
-        } else if (n instanceof EdgeNode) {\r
-            return resourceManager.createImage(EDGE);\r
-        } else if (n instanceof BranchPointNode) {\r
-            return resourceManager.createImage(BRANCH_POINT);\r
-        } else if (n instanceof ShapeNode) {\r
-            return resourceManager.createImage(SHAPE);\r
-        } else if (n instanceof SVGNode) {\r
-            return resourceManager.createImage(SVG);\r
-        } else if (n instanceof G2DParentNode) {\r
-            return resourceManager.createImage(PARENT);\r
-        } else if (n instanceof GridNode) {\r
-            return resourceManager.createImage(GRID);\r
-        } else if (n instanceof RulerNode) {\r
-            return resourceManager.createImage(RULER);\r
-        } else if (n instanceof PageBorderNode) {\r
-            return resourceManager.createImage(PAGE_BORDER);\r
-        } else if (n instanceof LinkNode) {\r
-            return resourceManager.createImage(LINK);\r
-        }\r
-        return null;\r
-    }\r
-\r
-    class InternalIdLabelProvider extends ColumnLabelProvider {\r
-        @Override\r
-        public void update(ViewerCell cell) {\r
-            NodeProxy proxy = (NodeProxy) cell.getElement();\r
-            cell.setText(proxy.getInternalId());\r
-            //cell.setImage(toImage(proxy));\r
-        }\r
-    }\r
-    class TypeLabelProvider extends ColumnLabelProvider {\r
-        @Override\r
-        public void update(ViewerCell cell) {\r
-            NodeProxy proxy = (NodeProxy) cell.getElement();\r
-            cell.setText(proxy.getTypeName());\r
-            //cell.setImage(toImage(proxy));\r
-        }\r
-    }\r
-    class IdLabelProvider extends ColumnLabelProvider {\r
-        @Override\r
-        public void update(ViewerCell cell) {\r
-            NodeProxy proxy = (NodeProxy) cell.getElement();\r
-            cell.setText(proxy.getId());\r
-            cell.setImage(toImage(proxy));\r
-        }\r
-    }\r
-    class LookupIdLabelProvider extends ColumnLabelProvider {\r
-        @Override\r
-        public void update(ViewerCell cell) {\r
-            NodeProxy proxy = (NodeProxy) cell.getElement();\r
-            INode node = proxy.getNode();\r
-            String lookupId = null;\r
-            if (node != null) {\r
-                ILookupService lut = NodeUtil.tryGetLookupService(node);\r
-                if (lut != null)\r
-                    lookupId = lut.lookupId(node);\r
-            }\r
-            cell.setText(lookupId != null ? lookupId : "");\r
-        }\r
-    }\r
-    class ZLabelProvider extends ColumnLabelProvider {\r
-        @Override\r
-        public void update(ViewerCell cell) {\r
-            NodeProxy proxy = (NodeProxy) cell.getElement();\r
-            INode node = proxy.getNode();\r
-            if (node instanceof IG2DNode) {\r
-                IG2DNode n = (IG2DNode) node;\r
-                cell.setText(String.valueOf(n.getZIndex()));\r
-            } else {\r
-                cell.setText("-");\r
-            }\r
-        }\r
-    }\r
-\r
-    @Override\r
-    public void createPartControl(Composite parent) {\r
-\r
-        tree = new TreeViewer(parent, SWT.SINGLE | SWT.FULL_SELECTION);\r
-        resourceManager = new LocalResourceManager(JFaceResources.getResources(), tree.getTree());\r
-\r
-        TreeColumnLayout ad = new TreeColumnLayout();\r
-        parent.setLayout(ad);\r
-\r
-        //tree.getTree().setLayout(new FillLayout());\r
-        tree.setContentProvider(new ContentProvider());\r
-        tree.getTree().setHeaderVisible(true);\r
-        //tree.getTree().setLinesVisible(true);\r
-        tree.setUseHashlookup(true);\r
-        tree.setAutoExpandLevel(3);\r
-\r
-        TreeViewerColumn nameColumn = new TreeViewerColumn(tree, SWT.LEFT);\r
-        TreeViewerColumn typeColumn = new TreeViewerColumn(tree, SWT.LEFT);\r
-        TreeViewerColumn idColumn = new TreeViewerColumn(tree, SWT.LEFT);\r
-        TreeViewerColumn lookupIdColumn = new TreeViewerColumn(tree, SWT.LEFT);\r
-        TreeViewerColumn zColumn = new TreeViewerColumn(tree, SWT.LEFT);\r
-\r
-        nameColumn.setLabelProvider(new IdLabelProvider());\r
-        typeColumn.setLabelProvider(new TypeLabelProvider());\r
-        idColumn.setLabelProvider(new InternalIdLabelProvider());\r
-        lookupIdColumn.setLabelProvider(new LookupIdLabelProvider());\r
-        zColumn.setLabelProvider(new ZLabelProvider());\r
-\r
-        nameColumn.getColumn().setText("Name");\r
-        nameColumn.getColumn().setWidth(20);\r
-        ad.setColumnData(nameColumn.getColumn(), new ColumnWeightData(80, 100));\r
-        typeColumn.getColumn().setText("Type");\r
-        typeColumn.getColumn().setWidth(20);\r
-        ad.setColumnData(typeColumn.getColumn(), new ColumnWeightData(20, 120));\r
-        idColumn.getColumn().setText("ID");\r
-        idColumn.getColumn().setWidth(20);\r
-        ad.setColumnData(idColumn.getColumn(), new ColumnWeightData(10, 50));\r
-        lookupIdColumn.getColumn().setText("Lookup ID");\r
-        lookupIdColumn.getColumn().setWidth(20);\r
-        ad.setColumnData(lookupIdColumn.getColumn(), new ColumnWeightData(50, 100));\r
-        zColumn.getColumn().setText("Z");\r
-        zColumn.getColumn().setWidth(70);\r
-        ad.setColumnData(zColumn.getColumn(), new ColumnWeightData(10, 70));\r
-\r
-        tree.addSelectionChangedListener(new ISelectionChangedListener() {\r
-            @Override\r
-            public void selectionChanged(SelectionChangedEvent event) {\r
-                updateContentDescription();\r
-            }\r
-        });\r
-        tree.addDoubleClickListener(new IDoubleClickListener() {\r
-            @Override\r
-            public void doubleClick(DoubleClickEvent event) {\r
-                openAttributeDialog();\r
-            }\r
-        });\r
-\r
-        contextActivation = ((IContextService) getSite()\r
-                .getService(IContextService.class))\r
-                .activateContext("org.simantics.scenegraph.viewer");\r
-\r
-        ICommandService commandService = (ICommandService) PlatformUI.getWorkbench().getService(ICommandService.class);\r
-        Command command = commandService.getCommand(LinkToActiveWorkbenchPartHandler.COMMAND);\r
-        State state = command.getState(LinkToActiveWorkbenchPartHandler.STATE);\r
-        this.linkToPart = Boolean.TRUE.equals(state.getValue());\r
-\r
-        // No need to remove this listener, the part service is local to this site.\r
-        IPartService partService = (IPartService) getSite().getService(IPartService.class);\r
-        partService.addPartListener(partListener);\r
-    }\r
-\r
-    @Override\r
-    public void dispose() {\r
-        closeAttributeDialog();\r
-    }\r
-\r
-    protected void openAttributeDialog() {\r
-        if (attributeDialog != null) {\r
-            Shell shell = attributeDialog.getShell();\r
-            if (shell == null || shell.isDisposed())\r
-                attributeDialog = null;\r
-        }\r
-        if (attributeDialog == null) {\r
-            attributeDialog = new AttributeDialog(getSite().getShell(), tree);\r
-            attributeDialog.setBlockOnOpen(false);\r
-            attributeDialog.open();\r
-        }\r
-    }\r
-\r
-    protected void closeAttributeDialog() {\r
-        if (attributeDialog != null) {\r
-            attributeDialog.close();\r
-            attributeDialog = null;\r
-        }\r
-    }\r
-\r
-    IPartListener2 partListener = new IPartListener2() {\r
-        @Override\r
-        public void partVisible(IWorkbenchPartReference partRef) {\r
-        }\r
-        @Override\r
-        public void partOpened(IWorkbenchPartReference partRef) {\r
-        }\r
-        @Override\r
-        public void partInputChanged(IWorkbenchPartReference partRef) {\r
-        }\r
-        @Override\r
-        public void partHidden(IWorkbenchPartReference partRef) {\r
-        }\r
-        @Override\r
-        public void partDeactivated(IWorkbenchPartReference partRef) {\r
-        }\r
-        @Override\r
-        public void partClosed(IWorkbenchPartReference partRef) {\r
-            if (linkToPart) {\r
-                IWorkbenchPart part = partRef.getPart(false);\r
-                if (part != null)\r
-                    refresh(null);\r
-            }\r
-        }\r
-        @Override\r
-        public void partBroughtToTop(IWorkbenchPartReference partRef) {\r
-        }\r
-        @Override\r
-        public void partActivated(IWorkbenchPartReference partRef) {\r
-            if (linkToPart) {\r
-                IWorkbenchPart part = partRef.getPart(false);\r
-                if (part != null) {\r
-                    if (part != lastPart) {\r
-                        refresh(part);\r
-                    }\r
-                }\r
-            }\r
-        }\r
-    };\r
-\r
-    @Override\r
-    public void setFocus() {\r
-        tree.getTree().setFocus();\r
-        if (!bootstrapped) {\r
-            bootstrapped = true;\r
-            refresh();\r
-        }\r
-    }\r
-\r
-    protected void refresh() {\r
-        IWorkbenchPart part = null;\r
-        try {\r
-            IWorkbenchWindow window = getSite().getWorkbenchWindow();\r
-            if (window == null)\r
-                return;\r
-            IWorkbenchPage page = window.getActivePage();\r
-            if (page == null)\r
-                return;\r
-            part = page.getActiveEditor();\r
-            if (part == null)\r
-                return;\r
-        } finally {\r
-            if (part == null) {\r
-                setContentDescription("No scene graph nodes available.");\r
-                // TODO: Show info page instead of tree view.\r
-            }\r
-        }\r
-\r
-        refresh(part);\r
-    }\r
-\r
-    /**\r
-     * @param part <code>null</code> to reset the view to a blank state.\r
-     * @return\r
-     */\r
-    protected boolean refresh(IWorkbenchPart part) {\r
-        boolean foundInput = true;\r
-        try {\r
-            Object obj = null;\r
-            if (part != null) {\r
-                obj = part.getAdapter(INode[].class);\r
-                if (obj == null)\r
-                    obj = part.getAdapter(INode.class);\r
-            }\r
-\r
-            if (obj != null) {\r
-                TreePath[] expanded = tree.getExpandedTreePaths();\r
-                tree.setInput(obj);\r
-                tree.setExpandedTreePaths(expanded);\r
-                this.currentNodeCount = countNodes(obj);\r
-                updateContentDescription();\r
-                foundInput = true;\r
-            }\r
-            lastPart = part;\r
-            return foundInput;\r
-        } finally {\r
-            if (!foundInput) {\r
-                setContentDescription("No scene graph nodes available.");\r
-                // TODO: Show info page instead of tree view.\r
-            }\r
-        }\r
-    }\r
-\r
-    private void updateContentDescription() {\r
-        StringBuilder desc = new StringBuilder();\r
-        desc.append(currentNodeCount + " nodes in total.");\r
-\r
-        IStructuredSelection ss = (IStructuredSelection) tree.getSelection();\r
-        Object obj = ss.getFirstElement();\r
-        if (obj instanceof NodeProxy) {\r
-            NodeProxy np = (NodeProxy) obj;\r
-            INode n = np.getNode();\r
-            if (n != null) {\r
-                int depth = NodeUtil.getDepth(n);\r
-                desc.append(" Selection ");\r
-                desc.append("at depth ");\r
-                desc.append(depth);\r
-                desc.append(".");\r
-            }\r
-        }\r
-\r
-        setContentDescription(desc.toString());\r
-    }\r
-\r
-    private int countNodes(Object obj) {\r
-        if (obj instanceof INode) {\r
-            INode n = (INode) obj;\r
-            return NodeUtil.countTreeNodes(n);\r
-        } else if (obj instanceof INode[]) {\r
-            INode[] ns = (INode[]) obj;\r
-            int result = 0;\r
-            for (INode n : ns)\r
-                result += NodeUtil.countTreeNodes(n);\r
-            return result;\r
-        }\r
-        return 0;\r
-    }\r
-\r
-    void copySelectionToClipboard() {\r
-        IStructuredSelection selection = (IStructuredSelection) tree.getSelection();\r
-        Object obj = selection.getFirstElement();\r
-        if (!(obj instanceof NodeProxy))\r
-            return;\r
-\r
-        NodeProxy np = (NodeProxy) obj;\r
-        INode n = np.getNode();\r
-        if (n == null)\r
-            return;\r
-\r
-        ByteArrayOutputStream bytes = new ByteArrayOutputStream(100000);\r
-        PrintStream stream = new PrintStream(bytes);\r
-        NodeUtil.printSceneGraph(stream, 0, n);\r
-        String textData = new String(bytes.toByteArray());\r
-        if (textData.isEmpty())\r
-            return;\r
-\r
-        Clipboard clipboard = new Clipboard(tree.getControl().getDisplay());\r
-        TextTransfer textTransfer = TextTransfer.getInstance();\r
-        Transfer[] transfers = new Transfer[]{textTransfer};\r
-        Object[] data = new Object[]{textData};\r
-        clipboard.setContents(data, transfers);\r
-        clipboard.dispose();\r
-    }\r
-\r
-    void collapseAll() {\r
-        for (Object obj : tree.getExpandedElements()) {\r
-            tree.setExpandedState(obj, false);\r
-        }\r
-    }\r
-\r
-    void expandSelectedNode() {\r
-        IStructuredSelection ss = (IStructuredSelection) tree.getSelection();\r
-        for (Object obj : ss.toList()) {\r
-            tree.expandToLevel(obj, TreeViewer.ALL_LEVELS);\r
-        }\r
-    }\r
-\r
-    public void linkToActiveWorkbenchPart(boolean value) {\r
-        this.linkToPart = value;\r
-    }\r
-\r
-}\r
+/*******************************************************************************
+ * Copyright (c) 2007, 2010 Association for Decentralized Information Management
+ * in Industry THTH ry.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     VTT Technical Research Centre of Finland - initial API and implementation
+ *******************************************************************************/
+package org.simantics.scenegraph.ui;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.lang.ref.WeakReference;
+import java.util.List;
+
+import org.eclipse.core.commands.Command;
+import org.eclipse.core.commands.State;
+import org.eclipse.jface.layout.TreeColumnLayout;
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.jface.resource.JFaceResources;
+import org.eclipse.jface.resource.LocalResourceManager;
+import org.eclipse.jface.viewers.ColumnLabelProvider;
+import org.eclipse.jface.viewers.ColumnWeightData;
+import org.eclipse.jface.viewers.DoubleClickEvent;
+import org.eclipse.jface.viewers.IDoubleClickListener;
+import org.eclipse.jface.viewers.ISelectionChangedListener;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.viewers.ITreeContentProvider;
+import org.eclipse.jface.viewers.SelectionChangedEvent;
+import org.eclipse.jface.viewers.TreePath;
+import org.eclipse.jface.viewers.TreeViewer;
+import org.eclipse.jface.viewers.TreeViewerColumn;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.jface.viewers.ViewerCell;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.dnd.Clipboard;
+import org.eclipse.swt.dnd.TextTransfer;
+import org.eclipse.swt.dnd.Transfer;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.ui.IPartListener2;
+import org.eclipse.ui.IPartService;
+import org.eclipse.ui.IWorkbenchPage;
+import org.eclipse.ui.IWorkbenchPart;
+import org.eclipse.ui.IWorkbenchPartReference;
+import org.eclipse.ui.IWorkbenchWindow;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.commands.ICommandService;
+import org.eclipse.ui.contexts.IContextActivation;
+import org.eclipse.ui.contexts.IContextService;
+import org.eclipse.ui.part.ViewPart;
+import org.simantics.scenegraph.ILookupService;
+import org.simantics.scenegraph.INode;
+import org.simantics.scenegraph.g2d.G2DParentNode;
+import org.simantics.scenegraph.g2d.G2DSceneGraph;
+import org.simantics.scenegraph.g2d.IG2DNode;
+import org.simantics.scenegraph.g2d.nodes.BoundsNode;
+import org.simantics.scenegraph.g2d.nodes.BranchPointNode;
+import org.simantics.scenegraph.g2d.nodes.EdgeNode;
+import org.simantics.scenegraph.g2d.nodes.GridNode;
+import org.simantics.scenegraph.g2d.nodes.LinkNode;
+import org.simantics.scenegraph.g2d.nodes.NavigationNode;
+import org.simantics.scenegraph.g2d.nodes.PageBorderNode;
+import org.simantics.scenegraph.g2d.nodes.RulerNode;
+import org.simantics.scenegraph.g2d.nodes.SVGNode;
+import org.simantics.scenegraph.g2d.nodes.ShapeNode;
+import org.simantics.scenegraph.g2d.nodes.SingleElementNode;
+import org.simantics.scenegraph.g2d.nodes.TransformNode;
+import org.simantics.scenegraph.utils.NodeUtil;
+import org.simantics.scenegraph.utils.NodeUtil.NodeProcedure;
+
+/**
+ * This view shows the contents of a 2D/3D canvas scenegraph through a tree
+ * viewer.
+ * 
+ * <p>
+ * The viewer sources its scene graph input from the currently active workbench
+ * editor. It does not automatically track the active editor part but instead
+ * has to be refreshed manually (F5).
+ * </p>
+ * 
+ * <p>
+ * References to actual scene graph nodes are only kept as {@link WeakReference}
+ * instances allowing them to be garbage collected if the scene graph is
+ * disposed of.
+ * </p>
+ * 
+ * @author Tuukka Lehtonen
+ */
+public class SceneGraphViewPart extends ViewPart {
+
+    TreeViewer           tree;
+    LocalResourceManager resourceManager;
+    boolean              bootstrapped = false;
+    IContextActivation   contextActivation;
+    int                  currentNodeCount = 0;
+    boolean              linkToPart;
+    IWorkbenchPart       lastPart;
+    AttributeDialog      attributeDialog;
+
+    final ImageDescriptor ROOT = ImageDescriptor.createFromURL(getClass().getResource("bullet_home.png"));
+    final ImageDescriptor CANVAS_BOUNDS = ImageDescriptor.createFromURL(getClass().getResource("application.png"));
+    final ImageDescriptor SHAPE = ImageDescriptor.createFromURL(getClass().getResource("shape_shadow.png"));
+    final ImageDescriptor NAVIGATION = ImageDescriptor.createFromURL(getClass().getResource("arrow_out_longer.png"));
+    final ImageDescriptor SVG = ImageDescriptor.createFromURL(getClass().getResource("script_code.png"));
+    final ImageDescriptor TRANSFORM = ImageDescriptor.createFromURL(getClass().getResource("arrow_nsew.png"));
+    final ImageDescriptor ELEMENT = ImageDescriptor.createFromURL(getClass().getResource("shape_handles.png"));
+    final ImageDescriptor PARENT = ImageDescriptor.createFromURL(getClass().getResource("share.png"));
+    final ImageDescriptor GRID = ImageDescriptor.createFromURL(getClass().getResource("border_all.png"));
+    final ImageDescriptor RULER = ImageDescriptor.createFromURL(getClass().getResource("text_ruler.png"));
+    final ImageDescriptor PAGE_BORDER = ImageDescriptor.createFromURL(getClass().getResource("page_white.png"));
+    final ImageDescriptor EDGE = ImageDescriptor.createFromURL(getClass().getResource("arrow_ew.png"));
+    final ImageDescriptor BRANCH_POINT = ImageDescriptor.createFromURL(getClass().getResource("bullet_black.png"));
+    final ImageDescriptor LINK = ImageDescriptor.createFromURL(getClass().getResource("link.png"));
+
+    NodeProcedure<NodeProxy> nodeProcedure = new NodeProcedure<NodeProxy>() {
+        @Override
+        public NodeProxy execute(INode node, String id) {
+            return new NodeProxy(node, id);
+        }
+    };
+
+    class ContentProvider implements ITreeContentProvider {
+
+        @Override
+        public Object[] getChildren(Object parentElement) {
+            if (parentElement instanceof NodeProxy) {
+                NodeProxy np = (NodeProxy) parentElement;
+                INode n = np.getNode();
+                if (n != null) {
+                    List<NodeProxy> children = NodeUtil.forChildren(n, nodeProcedure);
+                    return children.toArray();
+                }
+            }
+            return new Object[0];
+        }
+
+        @Override
+        public Object getParent(Object element) {
+            return null;
+        }
+
+        @Override
+        public boolean hasChildren(Object element) {
+            if (element instanceof NodeProxy) {
+                NodeProxy np = (NodeProxy) element;
+                INode n = np.getNode();
+                if (n != null)
+                    return NodeUtil.hasChildren(n);
+            }
+            return false;
+        }
+
+        @Override
+        public Object[] getElements(Object inputElement) {
+            if (inputElement instanceof INode[]) {
+                INode[] ns = (INode[]) inputElement;
+                NodeProxy[] result = new NodeProxy[ns.length];
+                for (int i = 0; i < ns.length; ++i)
+                    result[i] = new NodeProxy(ns[i], "root");
+                return result;
+            }
+            if (inputElement instanceof INode) {
+                INode n = (INode) inputElement;
+                return new Object[] { new NodeProxy(n, "root") };
+            }
+            return new Object[0];
+        }
+
+        @Override
+        public void dispose() {
+        }
+
+        @Override
+        public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
+        }
+    }
+
+    private Image toImage(NodeProxy proxy) {
+        INode n = proxy.getNode();
+        if (n == null)
+            return null;
+
+        if (n instanceof G2DSceneGraph) {
+            return resourceManager.createImage(ROOT);
+        } else if (n instanceof SingleElementNode) {
+            return resourceManager.createImage(ELEMENT);
+        } else if (n instanceof TransformNode) {
+            return resourceManager.createImage(TRANSFORM);
+        } else if (n instanceof NavigationNode) {
+            return resourceManager.createImage(NAVIGATION);
+        } else if (n instanceof BoundsNode) {
+            return resourceManager.createImage(CANVAS_BOUNDS);
+        } else if (n instanceof EdgeNode) {
+            return resourceManager.createImage(EDGE);
+        } else if (n instanceof BranchPointNode) {
+            return resourceManager.createImage(BRANCH_POINT);
+        } else if (n instanceof ShapeNode) {
+            return resourceManager.createImage(SHAPE);
+        } else if (n instanceof SVGNode) {
+            return resourceManager.createImage(SVG);
+        } else if (n instanceof G2DParentNode) {
+            return resourceManager.createImage(PARENT);
+        } else if (n instanceof GridNode) {
+            return resourceManager.createImage(GRID);
+        } else if (n instanceof RulerNode) {
+            return resourceManager.createImage(RULER);
+        } else if (n instanceof PageBorderNode) {
+            return resourceManager.createImage(PAGE_BORDER);
+        } else if (n instanceof LinkNode) {
+            return resourceManager.createImage(LINK);
+        }
+        return null;
+    }
+
+    class InternalIdLabelProvider extends ColumnLabelProvider {
+        @Override
+        public void update(ViewerCell cell) {
+            NodeProxy proxy = (NodeProxy) cell.getElement();
+            cell.setText(proxy.getInternalId());
+            //cell.setImage(toImage(proxy));
+        }
+    }
+    class TypeLabelProvider extends ColumnLabelProvider {
+        @Override
+        public void update(ViewerCell cell) {
+            NodeProxy proxy = (NodeProxy) cell.getElement();
+            cell.setText(proxy.getTypeName());
+            //cell.setImage(toImage(proxy));
+        }
+    }
+    class IdLabelProvider extends ColumnLabelProvider {
+        @Override
+        public void update(ViewerCell cell) {
+            NodeProxy proxy = (NodeProxy) cell.getElement();
+            cell.setText(proxy.getId());
+            cell.setImage(toImage(proxy));
+        }
+    }
+    class LookupIdLabelProvider extends ColumnLabelProvider {
+        @Override
+        public void update(ViewerCell cell) {
+            NodeProxy proxy = (NodeProxy) cell.getElement();
+            INode node = proxy.getNode();
+            String lookupId = null;
+            if (node != null) {
+                ILookupService lut = NodeUtil.tryGetLookupService(node);
+                if (lut != null)
+                    lookupId = lut.lookupId(node);
+            }
+            cell.setText(lookupId != null ? lookupId : "");
+        }
+    }
+    class ZLabelProvider extends ColumnLabelProvider {
+        @Override
+        public void update(ViewerCell cell) {
+            NodeProxy proxy = (NodeProxy) cell.getElement();
+            INode node = proxy.getNode();
+            if (node instanceof IG2DNode) {
+                IG2DNode n = (IG2DNode) node;
+                cell.setText(String.valueOf(n.getZIndex()));
+            } else {
+                cell.setText("-");
+            }
+        }
+    }
+
+    @Override
+    public void createPartControl(Composite parent) {
+
+        tree = new TreeViewer(parent, SWT.SINGLE | SWT.FULL_SELECTION);
+        resourceManager = new LocalResourceManager(JFaceResources.getResources(), tree.getTree());
+
+        TreeColumnLayout ad = new TreeColumnLayout();
+        parent.setLayout(ad);
+
+        //tree.getTree().setLayout(new FillLayout());
+        tree.setContentProvider(new ContentProvider());
+        tree.getTree().setHeaderVisible(true);
+        //tree.getTree().setLinesVisible(true);
+        tree.setUseHashlookup(true);
+        tree.setAutoExpandLevel(3);
+
+        TreeViewerColumn nameColumn = new TreeViewerColumn(tree, SWT.LEFT);
+        TreeViewerColumn typeColumn = new TreeViewerColumn(tree, SWT.LEFT);
+        TreeViewerColumn idColumn = new TreeViewerColumn(tree, SWT.LEFT);
+        TreeViewerColumn lookupIdColumn = new TreeViewerColumn(tree, SWT.LEFT);
+        TreeViewerColumn zColumn = new TreeViewerColumn(tree, SWT.LEFT);
+
+        nameColumn.setLabelProvider(new IdLabelProvider());
+        typeColumn.setLabelProvider(new TypeLabelProvider());
+        idColumn.setLabelProvider(new InternalIdLabelProvider());
+        lookupIdColumn.setLabelProvider(new LookupIdLabelProvider());
+        zColumn.setLabelProvider(new ZLabelProvider());
+
+        nameColumn.getColumn().setText("Name");
+        nameColumn.getColumn().setWidth(20);
+        ad.setColumnData(nameColumn.getColumn(), new ColumnWeightData(80, 100));
+        typeColumn.getColumn().setText("Type");
+        typeColumn.getColumn().setWidth(20);
+        ad.setColumnData(typeColumn.getColumn(), new ColumnWeightData(20, 120));
+        idColumn.getColumn().setText("ID");
+        idColumn.getColumn().setWidth(20);
+        ad.setColumnData(idColumn.getColumn(), new ColumnWeightData(10, 50));
+        lookupIdColumn.getColumn().setText("Lookup ID");
+        lookupIdColumn.getColumn().setWidth(20);
+        ad.setColumnData(lookupIdColumn.getColumn(), new ColumnWeightData(50, 100));
+        zColumn.getColumn().setText("Z");
+        zColumn.getColumn().setWidth(70);
+        ad.setColumnData(zColumn.getColumn(), new ColumnWeightData(10, 70));
+
+        tree.addSelectionChangedListener(new ISelectionChangedListener() {
+            @Override
+            public void selectionChanged(SelectionChangedEvent event) {
+                updateContentDescription();
+            }
+        });
+        tree.addDoubleClickListener(new IDoubleClickListener() {
+            @Override
+            public void doubleClick(DoubleClickEvent event) {
+                openAttributeDialog();
+            }
+        });
+
+        contextActivation = ((IContextService) getSite()
+                .getService(IContextService.class))
+                .activateContext("org.simantics.scenegraph.viewer");
+
+        ICommandService commandService = (ICommandService) PlatformUI.getWorkbench().getService(ICommandService.class);
+        Command command = commandService.getCommand(LinkToActiveWorkbenchPartHandler.COMMAND);
+        State state = command.getState(LinkToActiveWorkbenchPartHandler.STATE);
+        this.linkToPart = Boolean.TRUE.equals(state.getValue());
+
+        // No need to remove this listener, the part service is local to this site.
+        IPartService partService = (IPartService) getSite().getService(IPartService.class);
+        partService.addPartListener(partListener);
+    }
+
+    @Override
+    public void dispose() {
+        closeAttributeDialog();
+    }
+
+    protected void openAttributeDialog() {
+        if (attributeDialog != null) {
+            Shell shell = attributeDialog.getShell();
+            if (shell == null || shell.isDisposed())
+                attributeDialog = null;
+        }
+        if (attributeDialog == null) {
+            attributeDialog = new AttributeDialog(getSite().getShell(), tree);
+            attributeDialog.setBlockOnOpen(false);
+            attributeDialog.open();
+        }
+    }
+
+    protected void closeAttributeDialog() {
+        if (attributeDialog != null) {
+            attributeDialog.close();
+            attributeDialog = null;
+        }
+    }
+
+    IPartListener2 partListener = new IPartListener2() {
+        @Override
+        public void partVisible(IWorkbenchPartReference partRef) {
+        }
+        @Override
+        public void partOpened(IWorkbenchPartReference partRef) {
+        }
+        @Override
+        public void partInputChanged(IWorkbenchPartReference partRef) {
+        }
+        @Override
+        public void partHidden(IWorkbenchPartReference partRef) {
+        }
+        @Override
+        public void partDeactivated(IWorkbenchPartReference partRef) {
+        }
+        @Override
+        public void partClosed(IWorkbenchPartReference partRef) {
+            if (linkToPart) {
+                IWorkbenchPart part = partRef.getPart(false);
+                if (part != null)
+                    refresh(null);
+            }
+        }
+        @Override
+        public void partBroughtToTop(IWorkbenchPartReference partRef) {
+        }
+        @Override
+        public void partActivated(IWorkbenchPartReference partRef) {
+            if (linkToPart) {
+                IWorkbenchPart part = partRef.getPart(false);
+                if (part != null) {
+                    if (part != lastPart) {
+                        refresh(part);
+                    }
+                }
+            }
+        }
+    };
+
+    @Override
+    public void setFocus() {
+        tree.getTree().setFocus();
+        if (!bootstrapped) {
+            bootstrapped = true;
+            refresh();
+        }
+    }
+
+    protected void refresh() {
+        IWorkbenchPart part = null;
+        try {
+            IWorkbenchWindow window = getSite().getWorkbenchWindow();
+            if (window == null)
+                return;
+            IWorkbenchPage page = window.getActivePage();
+            if (page == null)
+                return;
+            part = page.getActiveEditor();
+            if (part == null)
+                return;
+        } finally {
+            if (part == null) {
+                setContentDescription("No scene graph nodes available.");
+                // TODO: Show info page instead of tree view.
+            }
+        }
+
+        refresh(part);
+    }
+
+    /**
+     * @param part <code>null</code> to reset the view to a blank state.
+     * @return
+     */
+    protected boolean refresh(IWorkbenchPart part) {
+        boolean foundInput = true;
+        try {
+            Object obj = null;
+            if (part != null) {
+                obj = part.getAdapter(INode[].class);
+                if (obj == null)
+                    obj = part.getAdapter(INode.class);
+            }
+
+            if (obj != null) {
+                TreePath[] expanded = tree.getExpandedTreePaths();
+                tree.setInput(obj);
+                tree.setExpandedTreePaths(expanded);
+                this.currentNodeCount = countNodes(obj);
+                updateContentDescription();
+                foundInput = true;
+            }
+            lastPart = part;
+            return foundInput;
+        } finally {
+            if (!foundInput) {
+                setContentDescription("No scene graph nodes available.");
+                // TODO: Show info page instead of tree view.
+            }
+        }
+    }
+
+    private void updateContentDescription() {
+        StringBuilder desc = new StringBuilder();
+        desc.append(currentNodeCount + " nodes in total.");
+
+        IStructuredSelection ss = (IStructuredSelection) tree.getSelection();
+        Object obj = ss.getFirstElement();
+        if (obj instanceof NodeProxy) {
+            NodeProxy np = (NodeProxy) obj;
+            INode n = np.getNode();
+            if (n != null) {
+                int depth = NodeUtil.getDepth(n);
+                desc.append(" Selection ");
+                desc.append("at depth ");
+                desc.append(depth);
+                desc.append(".");
+            }
+        }
+
+        setContentDescription(desc.toString());
+    }
+
+    private int countNodes(Object obj) {
+        if (obj instanceof INode) {
+            INode n = (INode) obj;
+            return NodeUtil.countTreeNodes(n);
+        } else if (obj instanceof INode[]) {
+            INode[] ns = (INode[]) obj;
+            int result = 0;
+            for (INode n : ns)
+                result += NodeUtil.countTreeNodes(n);
+            return result;
+        }
+        return 0;
+    }
+
+    void copySelectionToClipboard() {
+        IStructuredSelection selection = (IStructuredSelection) tree.getSelection();
+        Object obj = selection.getFirstElement();
+        if (!(obj instanceof NodeProxy))
+            return;
+
+        NodeProxy np = (NodeProxy) obj;
+        INode n = np.getNode();
+        if (n == null)
+            return;
+
+        ByteArrayOutputStream bytes = new ByteArrayOutputStream(100000);
+        PrintStream stream = new PrintStream(bytes);
+        NodeUtil.printSceneGraph(stream, 0, n);
+        String textData = new String(bytes.toByteArray());
+        if (textData.isEmpty())
+            return;
+
+        Clipboard clipboard = new Clipboard(tree.getControl().getDisplay());
+        TextTransfer textTransfer = TextTransfer.getInstance();
+        Transfer[] transfers = new Transfer[]{textTransfer};
+        Object[] data = new Object[]{textData};
+        clipboard.setContents(data, transfers);
+        clipboard.dispose();
+    }
+
+    void collapseAll() {
+        for (Object obj : tree.getExpandedElements()) {
+            tree.setExpandedState(obj, false);
+        }
+    }
+
+    void expandSelectedNode() {
+        IStructuredSelection ss = (IStructuredSelection) tree.getSelection();
+        for (Object obj : ss.toList()) {
+            tree.expandToLevel(obj, TreeViewer.ALL_LEVELS);
+        }
+    }
+
+    public void linkToActiveWorkbenchPart(boolean value) {
+        this.linkToPart = value;
+    }
+
+}