package org.simantics.views.swt.client.base; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; import org.eclipse.jface.resource.ColorDescriptor; import org.eclipse.jface.resource.FontDescriptor; import org.eclipse.jface.resource.ResourceManager; import org.eclipse.swt.widgets.Control; import org.simantics.databoard.Bindings; import org.simantics.databoard.binding.ArrayBinding; import org.simantics.databoard.binding.Binding; import org.simantics.databoard.binding.error.BindingConstructionException; import org.simantics.databoard.binding.error.BindingException; import org.simantics.databoard.binding.reflection.BindingRequest; import org.simantics.datatypes.literal.Font; import org.simantics.datatypes.literal.RGB; import org.simantics.scenegraph.LoaderNode; import org.simantics.scenegraph.loader.ScenegraphLoaderUtils; import org.simantics.scl.runtime.function.Function1; import org.simantics.scl.runtime.function.Function2; import org.simantics.scl.runtime.function.FunctionImpl1; import org.simantics.ui.colors.Colors; import org.simantics.ui.fonts.Fonts; import org.simantics.utils.threads.IThreadWorkQueue; import org.simantics.utils.threads.SWTThread; import org.simantics.views.ViewUtils.LayoutDataBean; abstract public class SingleSWTViewNode extends SWTParentNode implements LoaderNode { private static final long serialVersionUID = -5810308021930769003L; private Map genericProperties = new HashMap(); public Function2 propertyCallback; public LayoutDataBean layoutData; public int style; public String text; public RGB.Integer background; public RGB.Integer foreground; public Font font; protected transient ColorDescriptor backgroundDesc; protected transient ColorDescriptor foregroundDesc; protected transient FontDescriptor fontDesc; protected T control; @Override public Control getControl() { return control; } @Override public void reset() { control = null; } final protected boolean isDisposed() { Control c = getControl(); return c == null ? false : c.isDisposed(); } final protected void dispatch(final Runnable runnable) { if(isNodeDisposed()) return; IThreadWorkQueue thread = SWTThread.getThreadAccess(); if(thread.currentThreadAccess()) runnable.run(); else thread.asyncExec(new Runnable() { @Override public void run() { if(isDisposed()) return; Control c = control; if(c == null || c.isDisposed()) return; runnable.run(); } }); } private Field getPropertyField(String propertyName) { assert(!isNodeDisposed()); return ScenegraphLoaderUtils.getPropertyField(this, propertyName); } private Class getPropertyType(Field field) { return field.getType(); } private Binding getPropertyBinding(Field field) { try { Binding b = Bindings.getBinding( BindingRequest.create( field ) ); // Safety checks for unusable bindings. if (b instanceof ArrayBinding && ((ArrayBinding) b).type().componentType() == null) return null; return b; } catch (BindingConstructionException e) { return null; } catch (Throwable t) { return null; } } /** * Convert binding to generic binding * @param binding * @return */ private Binding getGenericPropertyBinding(Binding binding) { if (binding == null) return null; return Bindings.getBinding(binding.type()); } @Override public Function1 getPropertyFunction(final String propertyName) { if(isNodeDisposed()) return null; // assert(!isNodeDisposed()); final Field field = getPropertyField(propertyName); if(field != null) { return new FunctionImpl1() { final Method method = ScenegraphLoaderUtils.getSynchronizeMethod(SingleSWTViewNode.this, propertyName); final Class type = getPropertyType(field); final Binding binding = getPropertyBinding(field); final Binding genericBinding = getGenericPropertyBinding(binding); private Object setField(Object value) { try { if(type.isPrimitive() || (value == null) || type.isInstance(value) || type.isArray()) { field.set(SingleSWTViewNode.this, value); return value; } else { Object instance = binding.createDefaultUnchecked(); binding.readFrom(genericBinding, value, instance); field.set(SingleSWTViewNode.this, instance); return instance; } } catch (IllegalArgumentException e1) { e1.printStackTrace(); } catch (IllegalAccessException e1) { e1.printStackTrace(); } catch (BindingException e) { e.printStackTrace(); } catch (Throwable t) { t.printStackTrace(); } return null; } @Override public Boolean apply(Object value) { if(isNodeDisposed()) return true; final Object translated = setField(value); if(method != null) { dispatch(new Runnable() { @Override public void run() { try { method.invoke(SingleSWTViewNode.this, translated); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.getCause().printStackTrace(); } } }); } return isDisposed(); } }; } else { return new FunctionImpl1() { @Override public Boolean apply(Object p0) { assert(!isNodeDisposed()); genericProperties.put(propertyName, p0); return isDisposed(); } }; } } @Override public void setPropertyCallback(Function2 callback) { this.propertyCallback = callback; } @SuppressWarnings("unchecked") @Override public T2 getProperty(String propertyName) { Method m = ScenegraphLoaderUtils.getReadMethod(this, propertyName); if(m != null) { try { return (T2)m.invoke(this); } catch (IllegalArgumentException e) { throw new RuntimeException("Failed to get property '" + propertyName + "' for " + getClass().getName(), e); } catch (IllegalAccessException e) { throw new RuntimeException("Failed to get property '" + propertyName + "' for " + getClass().getName(), e); } catch (InvocationTargetException e) { throw new RuntimeException("Failed to get property '" + propertyName + "' for " + getClass().getName(), e.getCause()); } } else { return (T2)genericProperties.get(propertyName); } } protected void setProperties() { for(Method m : getClass().getMethods()) { if(m.getName().startsWith("synchronize")) { String upperFieldName = m.getName().substring("synchronize".length()); String fieldName = upperFieldName.substring(0,1).toLowerCase() + upperFieldName.substring(1); Field f = ScenegraphLoaderUtils.getPropertyField(this, fieldName); try { m.invoke(this, f.get(this)); } catch (IllegalArgumentException e) { throw new RuntimeException("Failed to set property '" + fieldName + "' for " + getClass().getName(), e); } catch (IllegalAccessException e) { throw new RuntimeException("Failed to set property '" + fieldName + "' for " + getClass().getName(), e); } catch (InvocationTargetException e) { throw new RuntimeException("Failed to set property '" + fieldName + "' for " + getClass().getName(), e.getCause()); } } } } public void synchronizeText(String text) { } final public void synchronizeFont(Font font) { if (font == null && fontDesc == null) return; ResourceManager rm = getResourceManager(); FontDescriptor oldDesc = fontDesc; if (font != null) control.setFont( getResourceManager().createFont( fontDesc = Fonts.swt(font) ) ); if (oldDesc != null) rm.destroy(oldDesc); } final public void synchronizeForeground(RGB.Integer foreground) { if (foreground == null && foregroundDesc == null) return; ResourceManager rm = getResourceManager(); ColorDescriptor oldDesc = foregroundDesc; if (foreground != null) control.setForeground( getResourceManager().createColor( foregroundDesc = Colors.swt(foreground) ) ); if (oldDesc != null) rm.destroy(oldDesc); } final public void synchronizeBackground(RGB.Integer background) { if (background == null && backgroundDesc == null) return; ResourceManager rm = getResourceManager(); ColorDescriptor oldDesc = backgroundDesc; if (background != null) control.setBackground( getResourceManager().createColor( backgroundDesc = Colors.swt(background) ) ); if (oldDesc != null) rm.destroy(oldDesc); } final public void synchronizeStyle(int style) { } final public void synchronizeLayoutData(LayoutDataBean layoutData) { if(layoutData != null) control.setLayoutData(SWTViewUtils.toLayoutData(layoutData)); } }