/******************************************************************************* * 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. * *

* 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). *

* *

* 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. *

* * @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 nodeProcedure = new NodeProcedure() { @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 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 null 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; } }