/* This file belongs to the Servoy development and deployment environment, Copyright (C) 1997-2015 Servoy BV This program is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program; if not, see http://www.gnu.org/licenses or write to the Free Software Foundation,Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 */ package com.servoy.j2db.server.ngclient.component; import java.awt.Dimension; import java.awt.Point; import java.awt.Rectangle; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.mozilla.javascript.BaseFunction; import org.mozilla.javascript.Context; import org.mozilla.javascript.Function; import org.mozilla.javascript.Scriptable; import org.sablo.specification.PropertyDescription; import com.servoy.j2db.persistence.StaticContentSpecLoader; import com.servoy.j2db.server.ngclient.WebFormComponent; import com.servoy.j2db.server.ngclient.WebFormUI; import com.servoy.j2db.server.ngclient.property.types.NGConversions; import com.servoy.j2db.server.ngclient.property.types.ReadonlyPropertyType; import com.servoy.j2db.server.ngclient.property.types.ReadonlySabloValue; import com.servoy.j2db.ui.runtime.IRuntimeComponent; import com.servoy.j2db.util.Utils; /** * Makes it possible to get/set properties of a group of elements at runtime. * @author emera */ @SuppressWarnings("nls") public class RuntimeWebGroup implements Scriptable { private final List<RuntimeWebComponent> runtimeWebComponents = new ArrayList<RuntimeWebComponent>(); private Scriptable parentScope; private Scriptable prototypeScope; private final PutPropertyCallable putCallable; private final GetPropertyCallable getCallable; private final String groupName; private static Map<String, String> properties = new HashMap<>(); private static Set<String> api = new HashSet<>(); private static final Rectangle NO_BOUNDS = new Rectangle(0, 0, 0, 0); static { properties.put("bgcolor", StaticContentSpecLoader.PROPERTY_BACKGROUND.getPropertyName()); properties.put("fgcolor", StaticContentSpecLoader.PROPERTY_FOREGROUND.getPropertyName()); properties.put("width", StaticContentSpecLoader.PROPERTY_SIZE.getPropertyName()); properties.put("height", StaticContentSpecLoader.PROPERTY_SIZE.getPropertyName()); properties.put("locationx", StaticContentSpecLoader.PROPERTY_LOCATION.getPropertyName()); properties.put("locationy", StaticContentSpecLoader.PROPERTY_LOCATION.getPropertyName()); properties.put("border", StaticContentSpecLoader.PROPERTY_BORDERTYPE.getPropertyName()); properties.put("font", StaticContentSpecLoader.PROPERTY_FONTTYPE.getPropertyName()); properties.put("enabled", StaticContentSpecLoader.PROPERTY_ENABLED.getPropertyName()); properties.put("toolTipText", StaticContentSpecLoader.PROPERTY_TOOLTIPTEXT.getPropertyName()); properties.put("transparent", StaticContentSpecLoader.PROPERTY_TRANSPARENT.getPropertyName()); properties.put("visible", StaticContentSpecLoader.PROPERTY_VISIBLE.getPropertyName()); properties.put("readOnly", WebFormUI.READONLY); api.add("putClientProperty"); api.add("getAbsoluteFormLocationY"); api.add("getClientProperty"); api.add("getDesignTimeProperty"); api.add("getElementType"); api.add("getFormName"); api.add("getName"); api.add("getLocationX"); api.add("getLocationY"); api.add("getWidth"); api.add("getHeight"); api.add("setSize"); api.add("setLocation"); } public RuntimeWebGroup(String groupName) { this.groupName = groupName; putCallable = new PutPropertyCallable(this); getCallable = new GetPropertyCallable(this); } public void add(RuntimeWebComponent runtimeComponent) { runtimeWebComponents.add(runtimeComponent); } @Override public String getClassName() { return "RuntimeGroup"; } @SuppressWarnings({ "nls", "boxing" }) @Override public Object get(String rawName, Scriptable start) { if (properties.keySet().contains(rawName) || api.contains(rawName)) { String name = rawName; if (name.startsWith("get")) { if ("getFormName".equals(name)) { return runtimeWebComponents.get(0).get("getFormName", start); } getCallable.setProperty(rawName.substring(3).toLowerCase()); return getCallable; } else if (name.startsWith("set") || name.startsWith("put")) { putCallable.setProperty(rawName.substring(3).toLowerCase()); return putCallable; } switch (name) { case "visible" : case "enabled" : case "transparent" : for (RuntimeWebComponent component : runtimeWebComponents) { Object value = component.get(properties.get(name), start); if (value != null && (boolean)value) return Boolean.TRUE; } return Boolean.FALSE; case "bgcolor" : case "border" : case "fgcolor" : case "font" : case "toolTipText" : for (RuntimeWebComponent component : runtimeWebComponents) { Object value = component.get(properties.get(name), start); if (value != Scriptable.NOT_FOUND && value != null) return value; } return null; case "readOnly" : for (RuntimeWebComponent component : runtimeWebComponents) { if (component.has(WebFormUI.READONLY, start) && !((boolean)component.get(WebFormUI.READONLY, start))) { return Boolean.FALSE; } } return Boolean.TRUE; case "width" : return getBounds().width; case "height" : return getBounds().height; case "locationx" : return getBounds().x; case "locationy" : return getBounds().y; } } return Scriptable.NOT_FOUND; } @Override public Object get(int index, Scriptable start) { return null; } @Override public boolean has(String name, Scriptable start) { return properties.keySet().contains(name) || api.contains(name); } @Override public boolean has(int index, Scriptable start) { return false; } @Override public void put(String name, Scriptable start, Object value) { if (!properties.containsKey(name)) return; String propertyName = properties.get(name); for (RuntimeWebComponent obj : runtimeWebComponents) { putProperty(propertyName, value, obj.getComponent()); } } private void putProperty(String propertyName, Object value, WebFormComponent component) { if (propertyName == null) return; Object previousVal = component.getProperty(propertyName); Object val = null; if (propertyName.equals(WebFormUI.READONLY)) { val = Boolean.valueOf((boolean)value); Object readonlyproperty = component.getProperty(WebFormUI.READONLY); if (readonlyproperty instanceof ReadonlySabloValue) { ReadonlySabloValue oldValue = (ReadonlySabloValue)readonlyproperty; //use the rhino conversion to convert from boolean to ReadOnlySabloValue PropertyDescription pd = component.getFormElement().getWebComponentSpec().getProperty(WebFormUI.READONLY); if (pd != null) val = ReadonlyPropertyType.INSTANCE.toSabloComponentValue(val, oldValue, pd, component); } } else { val = NGConversions.INSTANCE.convertRhinoToSabloComponentValue(value, previousVal, component.getSpecification().getProperties().get(propertyName), component); } if (val != previousVal) component.setProperty(propertyName, val); } @Override public void put(int index, Scriptable start, Object value) { } @Override public void delete(String name) { } @Override public void delete(int index) { } @Override public Scriptable getPrototype() { return prototypeScope; } @Override public void setPrototype(Scriptable prototype) { this.prototypeScope = prototype; } @Override public Scriptable getParentScope() { return parentScope; } @Override public void setParentScope(Scriptable parent) { parentScope = parent; } @Override public Object[] getIds() { ArrayList<String> ids = new ArrayList<>(); ids.addAll(properties.keySet()); ids.addAll(api); return ids.toArray(); } @Override public Object getDefaultValue(Class< ? > hint) { return null; } @Override public boolean hasInstance(Scriptable instance) { return false; } private class PutPropertyCallable extends BaseFunction { private final Scriptable scriptable; private String property; public PutPropertyCallable(Scriptable scriptable) { this.scriptable = scriptable; } public void setProperty(String name) { this.property = name; } @Override public Object call(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) { Object value = args; if (args != null) { if (args.length == 1) { value = args[0]; } else if (args.length == 2) { if (property.equals("size")) setSize(Utils.getAsInteger(args[0]), Utils.getAsInteger(args[1])); if (property.equals("location")) setLocation(Utils.getAsInteger(args[0]), Utils.getAsInteger(args[1])); if (property.equals("clientproperty")) putClientProperty(cx, scope, thisObj, args); } } scriptable.put(property, null, value); return null; } } private class GetPropertyCallable extends BaseFunction { private final Scriptable scriptable; private String property; public GetPropertyCallable(Scriptable scriptable) { this.scriptable = scriptable; } public void setProperty(String name) { this.property = name; } @Override public Object call(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) { switch (property) { case "name" : return groupName; case "absoluteformlocationy" : return getAbsoluteFormLocationY(cx, scope, thisObj, args); case "elementtype" : return IRuntimeComponent.GROUP; case "clientproperty" : return getClientProperty(cx, scope, thisObj, args); case "designtimeproperty" : return getDesignTimeProperty(cx, scope, thisObj, args); } return scriptable.get(property, null); } } protected Rectangle getBounds() { Rectangle bounds = null; for (RuntimeWebComponent obj : runtimeWebComponents) { Dimension size = (Dimension)obj.getComponent().getProperty("size"); Point location = (Point)obj.getComponent().getProperty("location"); Rectangle rect = new Rectangle(location.x, location.y, size.width, size.height); if (bounds == null) { bounds = rect; } else { bounds = bounds.union(rect); } } return bounds == null ? NO_BOUNDS : bounds; } private void setSize(int width, int height) { Rectangle bounds = getBounds(); float scalew = ((float)width) / bounds.width; float scaleh = ((float)height) / bounds.height; for (RuntimeWebComponent obj : runtimeWebComponents) { WebFormComponent component = obj.getComponent(); Point location = (Point)component.getProperty("location"); putProperty("location", new Object[] { bounds.x + (int)Math.floor(scalew * (location.x - bounds.x)), bounds.y + (int)Math.floor(scaleh * (location.y - bounds.y)) }, component); Dimension size = (Dimension)component.getProperty("size"); putProperty("size", new Object[] { (int)Math.floor(scalew * size.width), (int)Math.floor(scaleh * size.height) }, component); } } private void setLocation(int x, int y) { Rectangle bounds = getBounds(); int dx = x - bounds.x; int dy = y - bounds.y; for (RuntimeWebComponent obj : runtimeWebComponents) { WebFormComponent component = obj.getComponent(); Point location = (Point)component.getProperty("location"); putProperty("location", new Object[] { location.x + dx, location.y + dy }, component); } } private Object getDesignTimeProperty(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) { for (RuntimeWebComponent component : runtimeWebComponents) { Object f = component.get("getDesignTimeProperty", scope); if (f != null && f != Scriptable.NOT_FOUND) { Object value = ((Function)f).call(cx, scope, thisObj, args); if (value != null) return value; } } return null; } private Object getAbsoluteFormLocationY(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) { int y = -1; for (RuntimeWebComponent component : runtimeWebComponents) { Object f = component.get("getAbsoluteFormLocationY", scope); if (f != null && f != Scriptable.NOT_FOUND) { y = Math.min(y == -1 ? Integer.MAX_VALUE : y, (int)((Function)f).call(cx, scope, thisObj, args)); } } return y; } private void putClientProperty(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) { for (RuntimeWebComponent component : runtimeWebComponents) { Object f = component.get("putClientProperty", scope); if (f != null && f != Scriptable.NOT_FOUND) ((Function)f).call(cx, scope, thisObj, args); } } private Object getClientProperty(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) { for (RuntimeWebComponent component : runtimeWebComponents) { Object f = component.get("getClientProperty", scope); if (f != null && f != Scriptable.NOT_FOUND) { Object value = ((Function)f).call(cx, scope, thisObj, args); if (value != null) return value; } } return null; } }