/******************************************************************************* * 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.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.List; import org.eclipse.jface.dialogs.Dialog; import org.eclipse.jface.dialogs.IDialogSettings; import org.eclipse.jface.layout.GridDataFactory; import org.eclipse.jface.layout.GridLayoutFactory; import org.eclipse.jface.layout.TableColumnLayout; import org.eclipse.jface.resource.ColorDescriptor; import org.eclipse.jface.resource.FontDescriptor; import org.eclipse.jface.resource.JFaceResources; import org.eclipse.jface.resource.LocalResourceManager; import org.eclipse.jface.resource.ResourceManager; import org.eclipse.jface.viewers.ColumnLabelProvider; import org.eclipse.jface.viewers.ColumnWeightData; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.ISelectionChangedListener; import org.eclipse.jface.viewers.ISelectionProvider; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.ITreeContentProvider; import org.eclipse.jface.viewers.SelectionChangedEvent; import org.eclipse.jface.viewers.TableViewer; import org.eclipse.jface.viewers.TableViewerColumn; import org.eclipse.jface.viewers.Viewer; import org.eclipse.jface.viewers.ViewerCell; import org.eclipse.swt.SWT; import org.eclipse.swt.events.KeyAdapter; import org.eclipse.swt.events.KeyEvent; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.RGB; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.ToolBar; import org.eclipse.swt.widgets.ToolItem; import org.simantics.scenegraph.INode; import org.simantics.scenegraph.g2d.IG2DNode; import org.simantics.scenegraph.utils.NodeUtil; import org.simantics.utils.datastructures.ValueUtils; /** * @author Tuukka Lehtonen */ public class AttributeDialog extends Dialog implements ISelectionChangedListener { private static final String DIALOG = "AttributeDialog"; //$NON-NLS-1$ private IDialogSettings dialogBoundsSettings; private ResourceManager resourceManager; private TableViewer viewer; private final ISelectionProvider selectionProvider; private final boolean showClass = true; private boolean showTransient = false; private boolean showStatic = false; private final ColorDescriptor staticColor = ColorDescriptor.createFrom(new RGB(224, 224, 224)); private final ColorDescriptor transientColor = ColorDescriptor.createFrom(new RGB(192, 255, 255)); protected AttributeDialog(Shell parentShell, ISelectionProvider selectionProvider) { super(parentShell); this.selectionProvider = selectionProvider; IDialogSettings settings = Activator.getDefault().getDialogSettings(); dialogBoundsSettings = settings.getSection(DIALOG); if (dialogBoundsSettings == null) dialogBoundsSettings = settings.addNewSection(DIALOG); } @Override protected IDialogSettings getDialogBoundsSettings() { return dialogBoundsSettings; } @Override protected int getShellStyle() { return SWT.RESIZE | SWT.MODELESS | SWT.TITLE | SWT.CLOSE | SWT.BORDER; } @Override protected void configureShell(Shell newShell) { super.configureShell(newShell); newShell.setText("Scene Graph Node Attributes"); } @Override protected Control createButtonBar(Composite parent) { return parent; } @Override protected void createButtonsForButtonBar(Composite parent) { // No buttons, this is a non-modal property dialog. } @Override protected Point getInitialSize() { Point defaultSize = getShell().computeSize(SWT.DEFAULT, SWT.DEFAULT, true); Point result = super.getInitialSize(); if (defaultSize.equals(result)) return new Point(500, 300); return result; } @Override protected Control createDialogArea(Composite parent) { Composite composite = (Composite) super.createDialogArea(parent); this.resourceManager = new LocalResourceManager(JFaceResources.getResources()); composite.addListener(SWT.Dispose, new Listener() { @Override public void handleEvent(Event event) { selectionProvider.removeSelectionChangedListener(AttributeDialog.this); resourceManager.dispose(); resourceManager = null; } }); GridLayoutFactory.fillDefaults().margins(0, 0).spacing(0, 0).applyTo(composite); ToolBar toolbar = new ToolBar(composite, SWT.NONE); GridDataFactory.fillDefaults().grab(true, false).applyTo(toolbar); final ToolItem showStaticItem = new ToolItem(toolbar, SWT.CHECK); showStaticItem.setText("Show &Static"); showStaticItem.setToolTipText("Show Static Fields of Selected Object"); final ToolItem showTransientItem = new ToolItem(toolbar, SWT.CHECK); showTransientItem.setText("Show &Transient"); showTransientItem.setToolTipText("Show Transient Fields of Selected Object"); final ToolItem refresh = new ToolItem(toolbar, SWT.PUSH); refresh.setText("&Refresh"); refresh.setToolTipText("Refresh Values"); Composite tableComposite = new Composite(composite, SWT.NONE); GridDataFactory.fillDefaults().grab(true, true).applyTo(tableComposite); viewer = new TableViewer(tableComposite, SWT.V_SCROLL | SWT.H_SCROLL | SWT.BORDER | SWT.SINGLE | SWT.FULL_SELECTION); viewer.getControl().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); viewer.setContentProvider(new FieldContentProvider()); viewer.getTable().addKeyListener(new KeyAdapter() { @Override public void keyPressed(KeyEvent e) { if (e.keyCode == SWT.F5) refresh(); } }); TableColumnLayout ad = new TableColumnLayout(); tableComposite.setLayout(ad); viewer.getTable().setHeaderVisible(true); viewer.getTable().setLinesVisible(true); viewer.setUseHashlookup(true); TableViewerColumn nameColumn = new TableViewerColumn(viewer, SWT.LEFT); nameColumn.setLabelProvider(new FieldName()); TableViewerColumn typeColumn = new TableViewerColumn(viewer, SWT.LEFT); typeColumn.setLabelProvider(new FieldType()); TableViewerColumn valueColumn = new TableViewerColumn(viewer, SWT.LEFT); valueColumn.setLabelProvider(new FieldValue()); typeColumn.getColumn().setText("Type"); typeColumn.getColumn().setWidth(20); ad.setColumnData(typeColumn.getColumn(), new ColumnWeightData(10, 140)); nameColumn.getColumn().setText("Name"); nameColumn.getColumn().setWidth(20); ad.setColumnData(nameColumn.getColumn(), new ColumnWeightData(10, 140)); valueColumn.getColumn().setText("Value"); valueColumn.getColumn().setWidth(20); ad.setColumnData(valueColumn.getColumn(), new ColumnWeightData(90, 200)); selectionProvider.addSelectionChangedListener(this); showStaticItem.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { showStatic = showStaticItem.getSelection(); refresh(); } }); showTransientItem.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { showTransient = showTransientItem.getSelection(); refresh(); } }); refresh.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { refresh(); } }); // Bootstrap dialog. refresh(); applyDialogFont(composite); return composite; } static class Header { String name; public Header(String name) { this.name = name; } @Override public String toString() { return name; } } static class ComputedAttr { public String name; public Object object; public String stringValue; public ComputedAttr(String name, Object object) { this(name, object, object != null ? object.toString() : "null"); } public ComputedAttr(String name, Object object, String stringValue) { this.name = name; this.object = object; this.stringValue = stringValue; } @Override public String toString() { return stringValue; } } static class Attr { Object object; Field field; public Attr(Object obj, Field f) { this.object = obj; this.field = f; } public Object getObject() { return object; } public T getObject(Class clazz) { return clazz.cast(object); } public Field getField() { return field; } @Override public String toString() { return field.getName(); } } class FieldContentProvider implements ITreeContentProvider { @Override public Object[] getChildren(Object parentElement) { return new Object[0]; } @Override public Object getParent(Object element) { return null; } @Override public boolean hasChildren(Object element) { return false; } @Override public Object[] getElements(Object inputElement) { if (inputElement instanceof NodeProxy) { NodeProxy np = (NodeProxy) inputElement; INode node = np.getNode(); if (node == null) return new Object[0]; List result = new ArrayList(); if (node instanceof IG2DNode) { IG2DNode g2dnode = (IG2DNode) node; // Calculate some useful computational properties result.add(new Header("Computational IG2DNode properties")); result.add(new ComputedAttr("local bounds", g2dnode.getBoundsInLocal())); result.add(new ComputedAttr("world bounds", g2dnode.getBounds())); result.add(new ComputedAttr("local to world transform", NodeUtil.getLocalToGlobalTransform(g2dnode))); } Class clazz = node.getClass(); while (clazz != null && clazz != Object.class) { if (showClass) result.add(clazz); Field[] fields = clazz.getDeclaredFields(); for (int i = 0; i < fields.length; ++i) { Field f = fields[i]; // Ignore transient properties, those are generally just caches. if (!showTransient && Modifier.isTransient(f.getModifiers())) continue; // Ignore statics, those shouldn't affect data transfer. if (!showStatic && Modifier.isStatic(f.getModifiers())) continue; result.add(new Attr(np, fields[i])); } clazz = clazz.getSuperclass(); } return result.toArray(); } return new Object[0]; } @Override public void dispose() { } @Override public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { } } abstract class NodeLabelProvider extends ColumnLabelProvider { @Override public final void update(ViewerCell cell) { Object elem = cell.getElement(); if (elem instanceof Attr) { Attr attr = (Attr) elem; NodeProxy np = attr.getObject(NodeProxy.class); INode node = np.getNode(); update(cell, attr, node); } else if (elem instanceof ComputedAttr) { ComputedAttr attr = (ComputedAttr) elem; update(cell, attr); } else if (elem instanceof Header) { updateHeader(cell, ((Header) elem).name); } else if (elem instanceof Class) { Class clazz = (Class) elem; updateHeader(cell, clazz.getSimpleName() + " (" + clazz.getPackage().getName() + ")"); } } public abstract void update(ViewerCell cell, ComputedAttr attr); public abstract void update(ViewerCell cell, Attr attr, INode node); public void updateHeader(ViewerCell cell, String header) { cell.setFont(resourceManager.createFont(FontDescriptor.createFrom(cell.getFont()).withStyle(SWT.BOLD|SWT.ITALIC))); cell.setForeground(cell.getControl().getDisplay().getSystemColor(SWT.COLOR_INFO_FOREGROUND)); cell.setBackground(cell.getControl().getDisplay().getSystemColor(SWT.COLOR_INFO_BACKGROUND)); } } class FieldType extends NodeLabelProvider { @Override public void update(ViewerCell cell, ComputedAttr attr) { cell.setText(attr.object != null ? attr.object.getClass().getSimpleName() : "null"); } @Override public void update(ViewerCell cell, Attr attr, INode node) { Field f = attr.getField(); cell.setText(f.getType().getSimpleName()); } } class FieldName extends NodeLabelProvider { @Override public void update(ViewerCell cell, ComputedAttr attr) { cell.setText(attr.name); } @Override public void update(ViewerCell cell, Attr attr, INode node) { Field f = attr.getField(); cell.setText(f.getName()); } @Override public void updateHeader(ViewerCell cell, String header) { super.updateHeader(cell, header); cell.setText(header); } } class FieldValue extends NodeLabelProvider { @Override public void update(ViewerCell cell, ComputedAttr attr) { cell.setText(attr.stringValue); } @Override public void update(ViewerCell cell, Attr attr, INode node) { Field f = attr.getField(); boolean accessible = f.isAccessible(); try { if (!accessible) f.setAccessible(true); Object value = f.get(node); String label = value == null ? "null" : ValueUtils.toString(value); cell.setText(label); if (Modifier.isStatic(f.getModifiers())) { cell.setBackground((Color) resourceManager.get(staticColor)); } else if (Modifier.isTransient(f.getModifiers())) { cell.setBackground((Color) resourceManager.get(transientColor)); } } catch (IllegalArgumentException e) { e.printStackTrace(); cell.setText(e.getMessage()); } catch (IllegalAccessException e) { e.printStackTrace(); cell.setText(e.getMessage()); } finally { if (!accessible) f.setAccessible(false); } } } @Override public void selectionChanged(SelectionChangedEvent event) { selectionChanged(event.getSelection()); } public void selectionChanged(ISelection selection) { //System.out.println("selection changed: " + event); IStructuredSelection ss = (IStructuredSelection) selection; Object obj = ss.getFirstElement(); if (ss.size() == 1 || (obj instanceof NodeProxy)) { viewer.setInput(obj); } else { viewer.setInput(new Object()); } } public void refresh() { selectionChanged(selectionProvider.getSelection()); } }