--- /dev/null
+/*******************************************************************************\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