-/*******************************************************************************\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;
+ }
+
+}