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