1 /*******************************************************************************
2 * Copyright (c) 2007, 2010 Association for Decentralized Information Management
4 * All rights reserved. This program and the accompanying materials
5 * are made available under the terms of the Eclipse Public License v1.0
6 * which accompanies this distribution, and is available at
7 * http://www.eclipse.org/legal/epl-v10.html
10 * VTT Technical Research Centre of Finland - initial API and implementation
11 *******************************************************************************/
12 package org.simantics.scenegraph.ui;
14 import java.io.ByteArrayOutputStream;
15 import java.io.PrintStream;
16 import java.lang.ref.WeakReference;
17 import java.util.List;
19 import org.eclipse.core.commands.Command;
20 import org.eclipse.core.commands.State;
21 import org.eclipse.jface.layout.TreeColumnLayout;
22 import org.eclipse.jface.resource.ImageDescriptor;
23 import org.eclipse.jface.resource.JFaceResources;
24 import org.eclipse.jface.resource.LocalResourceManager;
25 import org.eclipse.jface.viewers.ColumnLabelProvider;
26 import org.eclipse.jface.viewers.ColumnWeightData;
27 import org.eclipse.jface.viewers.DoubleClickEvent;
28 import org.eclipse.jface.viewers.IDoubleClickListener;
29 import org.eclipse.jface.viewers.ISelectionChangedListener;
30 import org.eclipse.jface.viewers.IStructuredSelection;
31 import org.eclipse.jface.viewers.ITreeContentProvider;
32 import org.eclipse.jface.viewers.SelectionChangedEvent;
33 import org.eclipse.jface.viewers.TreePath;
34 import org.eclipse.jface.viewers.TreeViewer;
35 import org.eclipse.jface.viewers.TreeViewerColumn;
36 import org.eclipse.jface.viewers.Viewer;
37 import org.eclipse.jface.viewers.ViewerCell;
38 import org.eclipse.swt.SWT;
39 import org.eclipse.swt.dnd.Clipboard;
40 import org.eclipse.swt.dnd.TextTransfer;
41 import org.eclipse.swt.dnd.Transfer;
42 import org.eclipse.swt.graphics.Image;
43 import org.eclipse.swt.widgets.Composite;
44 import org.eclipse.swt.widgets.Shell;
45 import org.eclipse.ui.IPartListener2;
46 import org.eclipse.ui.IPartService;
47 import org.eclipse.ui.IWorkbenchPage;
48 import org.eclipse.ui.IWorkbenchPart;
49 import org.eclipse.ui.IWorkbenchPartReference;
50 import org.eclipse.ui.IWorkbenchWindow;
51 import org.eclipse.ui.PlatformUI;
52 import org.eclipse.ui.commands.ICommandService;
53 import org.eclipse.ui.contexts.IContextActivation;
54 import org.eclipse.ui.contexts.IContextService;
55 import org.eclipse.ui.part.ViewPart;
56 import org.simantics.scenegraph.ILookupService;
57 import org.simantics.scenegraph.INode;
58 import org.simantics.scenegraph.g2d.G2DParentNode;
59 import org.simantics.scenegraph.g2d.G2DSceneGraph;
60 import org.simantics.scenegraph.g2d.IG2DNode;
61 import org.simantics.scenegraph.g2d.nodes.BoundsNode;
62 import org.simantics.scenegraph.g2d.nodes.BranchPointNode;
63 import org.simantics.scenegraph.g2d.nodes.EdgeNode;
64 import org.simantics.scenegraph.g2d.nodes.GridNode;
65 import org.simantics.scenegraph.g2d.nodes.LinkNode;
66 import org.simantics.scenegraph.g2d.nodes.NavigationNode;
67 import org.simantics.scenegraph.g2d.nodes.PageBorderNode;
68 import org.simantics.scenegraph.g2d.nodes.RulerNode;
69 import org.simantics.scenegraph.g2d.nodes.SVGNode;
70 import org.simantics.scenegraph.g2d.nodes.ShapeNode;
71 import org.simantics.scenegraph.g2d.nodes.SingleElementNode;
72 import org.simantics.scenegraph.g2d.nodes.TransformNode;
73 import org.simantics.scenegraph.utils.NodeUtil;
74 import org.simantics.scenegraph.utils.NodeUtil.NodeProcedure;
77 * This view shows the contents of a 2D/3D canvas scenegraph through a tree
81 * The viewer sources its scene graph input from the currently active workbench
82 * editor. It does not automatically track the active editor part but instead
83 * has to be refreshed manually (F5).
87 * References to actual scene graph nodes are only kept as {@link WeakReference}
88 * instances allowing them to be garbage collected if the scene graph is
92 * @author Tuukka Lehtonen
94 public class SceneGraphViewPart extends ViewPart {
97 LocalResourceManager resourceManager;
98 boolean bootstrapped = false;
99 IContextActivation contextActivation;
100 int currentNodeCount = 0;
102 IWorkbenchPart lastPart;
103 AttributeDialog attributeDialog;
105 final ImageDescriptor ROOT = ImageDescriptor.createFromURL(getClass().getResource("bullet_home.png"));
106 final ImageDescriptor CANVAS_BOUNDS = ImageDescriptor.createFromURL(getClass().getResource("application.png"));
107 final ImageDescriptor SHAPE = ImageDescriptor.createFromURL(getClass().getResource("shape_shadow.png"));
108 final ImageDescriptor NAVIGATION = ImageDescriptor.createFromURL(getClass().getResource("arrow_out_longer.png"));
109 final ImageDescriptor SVG = ImageDescriptor.createFromURL(getClass().getResource("script_code.png"));
110 final ImageDescriptor TRANSFORM = ImageDescriptor.createFromURL(getClass().getResource("arrow_nsew.png"));
111 final ImageDescriptor ELEMENT = ImageDescriptor.createFromURL(getClass().getResource("shape_handles.png"));
112 final ImageDescriptor PARENT = ImageDescriptor.createFromURL(getClass().getResource("share.png"));
113 final ImageDescriptor GRID = ImageDescriptor.createFromURL(getClass().getResource("border_all.png"));
114 final ImageDescriptor RULER = ImageDescriptor.createFromURL(getClass().getResource("text_ruler.png"));
115 final ImageDescriptor PAGE_BORDER = ImageDescriptor.createFromURL(getClass().getResource("page_white.png"));
116 final ImageDescriptor EDGE = ImageDescriptor.createFromURL(getClass().getResource("arrow_ew.png"));
117 final ImageDescriptor BRANCH_POINT = ImageDescriptor.createFromURL(getClass().getResource("bullet_black.png"));
118 final ImageDescriptor LINK = ImageDescriptor.createFromURL(getClass().getResource("link.png"));
120 NodeProcedure<NodeProxy> nodeProcedure = new NodeProcedure<NodeProxy>() {
122 public NodeProxy execute(INode node, String id) {
123 return new NodeProxy(node, id);
127 class ContentProvider implements ITreeContentProvider {
130 public Object[] getChildren(Object parentElement) {
131 if (parentElement instanceof NodeProxy) {
132 NodeProxy np = (NodeProxy) parentElement;
133 INode n = np.getNode();
135 List<NodeProxy> children = NodeUtil.forChildren(n, nodeProcedure);
136 return children.toArray();
139 return new Object[0];
143 public Object getParent(Object element) {
148 public boolean hasChildren(Object element) {
149 if (element instanceof NodeProxy) {
150 NodeProxy np = (NodeProxy) element;
151 INode n = np.getNode();
153 return NodeUtil.hasChildren(n);
159 public Object[] getElements(Object inputElement) {
160 if (inputElement instanceof INode[]) {
161 INode[] ns = (INode[]) inputElement;
162 NodeProxy[] result = new NodeProxy[ns.length];
163 for (int i = 0; i < ns.length; ++i)
164 result[i] = new NodeProxy(ns[i], "root");
167 if (inputElement instanceof INode) {
168 INode n = (INode) inputElement;
169 return new Object[] { new NodeProxy(n, "root") };
171 return new Object[0];
175 public void dispose() {
179 public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
183 private Image toImage(NodeProxy proxy) {
184 INode n = proxy.getNode();
188 if (n instanceof G2DSceneGraph) {
189 return resourceManager.createImage(ROOT);
190 } else if (n instanceof SingleElementNode) {
191 return resourceManager.createImage(ELEMENT);
192 } else if (n instanceof TransformNode) {
193 return resourceManager.createImage(TRANSFORM);
194 } else if (n instanceof NavigationNode) {
195 return resourceManager.createImage(NAVIGATION);
196 } else if (n instanceof BoundsNode) {
197 return resourceManager.createImage(CANVAS_BOUNDS);
198 } else if (n instanceof EdgeNode) {
199 return resourceManager.createImage(EDGE);
200 } else if (n instanceof BranchPointNode) {
201 return resourceManager.createImage(BRANCH_POINT);
202 } else if (n instanceof ShapeNode) {
203 return resourceManager.createImage(SHAPE);
204 } else if (n instanceof SVGNode) {
205 return resourceManager.createImage(SVG);
206 } else if (n instanceof G2DParentNode) {
207 return resourceManager.createImage(PARENT);
208 } else if (n instanceof GridNode) {
209 return resourceManager.createImage(GRID);
210 } else if (n instanceof RulerNode) {
211 return resourceManager.createImage(RULER);
212 } else if (n instanceof PageBorderNode) {
213 return resourceManager.createImage(PAGE_BORDER);
214 } else if (n instanceof LinkNode) {
215 return resourceManager.createImage(LINK);
220 class InternalIdLabelProvider extends ColumnLabelProvider {
222 public void update(ViewerCell cell) {
223 NodeProxy proxy = (NodeProxy) cell.getElement();
224 cell.setText(proxy.getInternalId());
225 //cell.setImage(toImage(proxy));
228 class TypeLabelProvider extends ColumnLabelProvider {
230 public void update(ViewerCell cell) {
231 NodeProxy proxy = (NodeProxy) cell.getElement();
232 cell.setText(proxy.getTypeName());
233 //cell.setImage(toImage(proxy));
236 class IdLabelProvider extends ColumnLabelProvider {
238 public void update(ViewerCell cell) {
239 NodeProxy proxy = (NodeProxy) cell.getElement();
240 cell.setText(proxy.getId());
241 cell.setImage(toImage(proxy));
244 class LookupIdLabelProvider extends ColumnLabelProvider {
246 public void update(ViewerCell cell) {
247 NodeProxy proxy = (NodeProxy) cell.getElement();
248 INode node = proxy.getNode();
249 String lookupId = null;
251 ILookupService lut = NodeUtil.tryGetLookupService(node);
253 lookupId = lut.lookupId(node);
255 cell.setText(lookupId != null ? lookupId : "");
258 class ZLabelProvider extends ColumnLabelProvider {
260 public void update(ViewerCell cell) {
261 NodeProxy proxy = (NodeProxy) cell.getElement();
262 INode node = proxy.getNode();
263 if (node instanceof IG2DNode) {
264 IG2DNode n = (IG2DNode) node;
265 cell.setText(String.valueOf(n.getZIndex()));
273 public void createPartControl(Composite parent) {
275 tree = new TreeViewer(parent, SWT.SINGLE | SWT.FULL_SELECTION);
276 resourceManager = new LocalResourceManager(JFaceResources.getResources(), tree.getTree());
278 TreeColumnLayout ad = new TreeColumnLayout();
279 parent.setLayout(ad);
281 //tree.getTree().setLayout(new FillLayout());
282 tree.setContentProvider(new ContentProvider());
283 tree.getTree().setHeaderVisible(true);
284 //tree.getTree().setLinesVisible(true);
285 tree.setUseHashlookup(true);
286 tree.setAutoExpandLevel(3);
288 TreeViewerColumn nameColumn = new TreeViewerColumn(tree, SWT.LEFT);
289 TreeViewerColumn typeColumn = new TreeViewerColumn(tree, SWT.LEFT);
290 TreeViewerColumn idColumn = new TreeViewerColumn(tree, SWT.LEFT);
291 TreeViewerColumn lookupIdColumn = new TreeViewerColumn(tree, SWT.LEFT);
292 TreeViewerColumn zColumn = new TreeViewerColumn(tree, SWT.LEFT);
294 nameColumn.setLabelProvider(new IdLabelProvider());
295 typeColumn.setLabelProvider(new TypeLabelProvider());
296 idColumn.setLabelProvider(new InternalIdLabelProvider());
297 lookupIdColumn.setLabelProvider(new LookupIdLabelProvider());
298 zColumn.setLabelProvider(new ZLabelProvider());
300 nameColumn.getColumn().setText("Name");
301 nameColumn.getColumn().setWidth(20);
302 ad.setColumnData(nameColumn.getColumn(), new ColumnWeightData(80, 100));
303 typeColumn.getColumn().setText("Type");
304 typeColumn.getColumn().setWidth(20);
305 ad.setColumnData(typeColumn.getColumn(), new ColumnWeightData(20, 120));
306 idColumn.getColumn().setText("ID");
307 idColumn.getColumn().setWidth(20);
308 ad.setColumnData(idColumn.getColumn(), new ColumnWeightData(10, 50));
309 lookupIdColumn.getColumn().setText("Lookup ID");
310 lookupIdColumn.getColumn().setWidth(20);
311 ad.setColumnData(lookupIdColumn.getColumn(), new ColumnWeightData(50, 100));
312 zColumn.getColumn().setText("Z");
313 zColumn.getColumn().setWidth(70);
314 ad.setColumnData(zColumn.getColumn(), new ColumnWeightData(10, 70));
316 tree.addSelectionChangedListener(new ISelectionChangedListener() {
318 public void selectionChanged(SelectionChangedEvent event) {
319 updateContentDescription();
322 tree.addDoubleClickListener(new IDoubleClickListener() {
324 public void doubleClick(DoubleClickEvent event) {
325 openAttributeDialog();
329 contextActivation = ((IContextService) getSite()
330 .getService(IContextService.class))
331 .activateContext("org.simantics.scenegraph.viewer");
333 ICommandService commandService = (ICommandService) PlatformUI.getWorkbench().getService(ICommandService.class);
334 Command command = commandService.getCommand(LinkToActiveWorkbenchPartHandler.COMMAND);
335 State state = command.getState(LinkToActiveWorkbenchPartHandler.STATE);
336 this.linkToPart = Boolean.TRUE.equals(state.getValue());
338 // No need to remove this listener, the part service is local to this site.
339 IPartService partService = (IPartService) getSite().getService(IPartService.class);
340 partService.addPartListener(partListener);
344 public void dispose() {
345 closeAttributeDialog();
348 protected void openAttributeDialog() {
349 if (attributeDialog != null) {
350 Shell shell = attributeDialog.getShell();
351 if (shell == null || shell.isDisposed())
352 attributeDialog = null;
354 if (attributeDialog == null) {
355 attributeDialog = new AttributeDialog(getSite().getShell(), tree);
356 attributeDialog.setBlockOnOpen(false);
357 attributeDialog.open();
361 protected void closeAttributeDialog() {
362 if (attributeDialog != null) {
363 attributeDialog.close();
364 attributeDialog = null;
368 IPartListener2 partListener = new IPartListener2() {
370 public void partVisible(IWorkbenchPartReference partRef) {
373 public void partOpened(IWorkbenchPartReference partRef) {
376 public void partInputChanged(IWorkbenchPartReference partRef) {
379 public void partHidden(IWorkbenchPartReference partRef) {
382 public void partDeactivated(IWorkbenchPartReference partRef) {
385 public void partClosed(IWorkbenchPartReference partRef) {
387 IWorkbenchPart part = partRef.getPart(false);
393 public void partBroughtToTop(IWorkbenchPartReference partRef) {
396 public void partActivated(IWorkbenchPartReference partRef) {
398 IWorkbenchPart part = partRef.getPart(false);
400 if (part != lastPart) {
409 public void setFocus() {
410 tree.getTree().setFocus();
417 protected void refresh() {
418 IWorkbenchPart part = null;
420 IWorkbenchWindow window = getSite().getWorkbenchWindow();
423 IWorkbenchPage page = window.getActivePage();
426 part = page.getActiveEditor();
431 setContentDescription("No scene graph nodes available.");
432 // TODO: Show info page instead of tree view.
440 * @param part <code>null</code> to reset the view to a blank state.
443 protected boolean refresh(IWorkbenchPart part) {
444 boolean foundInput = true;
448 obj = part.getAdapter(INode[].class);
450 obj = part.getAdapter(INode.class);
454 TreePath[] expanded = tree.getExpandedTreePaths();
456 tree.setExpandedTreePaths(expanded);
457 this.currentNodeCount = countNodes(obj);
458 updateContentDescription();
465 setContentDescription("No scene graph nodes available.");
466 // TODO: Show info page instead of tree view.
471 private void updateContentDescription() {
472 StringBuilder desc = new StringBuilder();
473 desc.append(currentNodeCount + " nodes in total.");
475 IStructuredSelection ss = (IStructuredSelection) tree.getSelection();
476 Object obj = ss.getFirstElement();
477 if (obj instanceof NodeProxy) {
478 NodeProxy np = (NodeProxy) obj;
479 INode n = np.getNode();
481 int depth = NodeUtil.getDepth(n);
482 desc.append(" Selection ");
483 desc.append("at depth ");
489 setContentDescription(desc.toString());
492 private int countNodes(Object obj) {
493 if (obj instanceof INode) {
494 INode n = (INode) obj;
495 return NodeUtil.countTreeNodes(n);
496 } else if (obj instanceof INode[]) {
497 INode[] ns = (INode[]) obj;
500 result += NodeUtil.countTreeNodes(n);
506 void copySelectionToClipboard() {
507 IStructuredSelection selection = (IStructuredSelection) tree.getSelection();
508 Object obj = selection.getFirstElement();
509 if (!(obj instanceof NodeProxy))
512 NodeProxy np = (NodeProxy) obj;
513 INode n = np.getNode();
517 ByteArrayOutputStream bytes = new ByteArrayOutputStream(100000);
518 PrintStream stream = new PrintStream(bytes);
519 NodeUtil.printSceneGraph(stream, 0, n);
520 String textData = new String(bytes.toByteArray());
521 if (textData.isEmpty())
524 Clipboard clipboard = new Clipboard(tree.getControl().getDisplay());
525 TextTransfer textTransfer = TextTransfer.getInstance();
526 Transfer[] transfers = new Transfer[]{textTransfer};
527 Object[] data = new Object[]{textData};
528 clipboard.setContents(data, transfers);
533 for (Object obj : tree.getExpandedElements()) {
534 tree.setExpandedState(obj, false);
538 void expandSelectedNode() {
539 IStructuredSelection ss = (IStructuredSelection) tree.getSelection();
540 for (Object obj : ss.toList()) {
541 tree.expandToLevel(obj, TreeViewer.ALL_LEVELS);
545 public void linkToActiveWorkbenchPart(boolean value) {
546 this.linkToPart = value;