/* This file belongs to the Servoy development and deployment environment, Copyright (C) 1997-2010 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.headlessclient.dataui; import java.awt.BorderLayout; import java.awt.Dimension; import java.awt.Image; import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; import java.awt.event.MouseEvent; import java.awt.image.BufferedImage; import java.util.Date; import javax.swing.JComponent; import javax.swing.JPanel; import org.apache.wicket.AttributeModifier; import org.apache.wicket.IResourceListener; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.markup.html.IHeaderResponse; import org.apache.wicket.model.AbstractReadOnlyModel; import com.servoy.j2db.IApplication; import com.servoy.j2db.persistence.IAnchorConstants; import com.servoy.j2db.ui.IStylePropertyChanges; import com.servoy.j2db.ui.scripting.RuntimeScriptButton; import com.servoy.j2db.util.Debug; import com.servoy.j2db.util.IDelegate; import com.servoy.j2db.util.Utils; import com.servoy.j2db.util.gui.SnapShot; import com.servoy.j2db.util.gui.SnapShot.IMAGE_TYPE; /** * Shows an image from a bean running in the server * @author jcompagner */ public class WebImageBeanHolder extends WebBaseButton implements IDelegate { private static final long serialVersionUID = 1L; private final JComponent bean; // Fields used for caching data sent to web client. The image of the bean is sent // only if there is any change since the previously sent image. private boolean lastChanged; private byte[] lastSnapshot; private Date lastIsChangedQuery; private final int anchoring; public WebImageBeanHolder(IApplication application, RuntimeScriptButton scriptable, String id, JComponent bean, int anchoring) { super(application, scriptable, id, ""); //$NON-NLS-1$ ((ChangesRecorder)scriptable.getChangesRecorder()).setDefaultBorderAndPadding(null, null); this.bean = bean; this.anchoring = anchoring; if (bean != null) bean.addComponentListener(new ComponentAdapter() { @Override public void componentResized(ComponentEvent e) { if (!WebImageBeanHolder.this.getSize().equals(WebImageBeanHolder.this.bean.getSize())) { WebImageBeanHolder.this.getScriptObject().setSize(WebImageBeanHolder.this.bean.getWidth(), WebImageBeanHolder.this.bean.getHeight()); } } }); setMediaOption(8 + 1); add(new AttributeModifier("src", new AbstractReadOnlyModel<String>() //$NON-NLS-1$ { private static final long serialVersionUID = 1L; @Override public String getObject() { return urlFor(IResourceListener.INTERFACE) + "&x=" + Math.random(); //$NON-NLS-1$ } })); icon = new MediaResource(); final boolean useAnchors = Utils.getAsBoolean(application.getRuntimeProperties().get("enableAnchors")); //$NON-NLS-1$ if (useAnchors) { if ((anchoring & (IAnchorConstants.WEST | IAnchorConstants.EAST)) != 0 || (anchoring & (IAnchorConstants.NORTH | IAnchorConstants.SOUTH)) != 0) { add(new AbstractServoyDefaultAjaxBehavior() { @Override public void renderHead(IHeaderResponse response) { super.renderHead(response); String beanHolderId = WebImageBeanHolder.this.getMarkupId(); int width = getSize().width; int height = getSize().height; StringBuffer sb = new StringBuffer(); sb.append("if(typeof(beansPreferredSize) != \"undefined\")\n").append("{\n"); //$NON-NLS-1$ //$NON-NLS-2$ sb.append("beansPreferredSize['").append(beanHolderId).append("'] = new Array();\n"); //$NON-NLS-1$ //$NON-NLS-2$ sb.append("beansPreferredSize['").append(beanHolderId).append("']['height'] = ").append(height).append(";\n"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ sb.append("beansPreferredSize['").append(beanHolderId).append("']['width'] = ").append(width).append(";\n"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ sb.append("beansPreferredSize['").append(beanHolderId).append("']['callback'] = '").append(getCallbackUrl()).append("';\n"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ sb.append("}\n"); //$NON-NLS-1$ response.renderOnLoadJavascript(sb.toString()); } @Override protected void respond(AjaxRequestTarget target) { String sWidthHint = getComponent().getRequest().getParameter("width"); //$NON-NLS-1$ String sHeightHint = getComponent().getRequest().getParameter("height"); //$NON-NLS-1$ int widthHint = Integer.parseInt(sWidthHint); int heightHint = Integer.parseInt(sHeightHint); setSize(new Dimension(widthHint, heightHint)); WebEventExecutor.generateResponse(target, getComponent().getPage()); } }); } } } public Object getDelegate() { return bean; } @Override public void onSubmit() { super.onSubmit(); try { int x = Utils.getAsInteger(getRequest().getParameter(getInputName() + ".x")); //$NON-NLS-1$ int y = Utils.getAsInteger(getRequest().getParameter(getInputName() + ".y")); //$NON-NLS-1$ bean.dispatchEvent(new MouseEvent(bean, MouseEvent.MOUSE_CLICKED, System.currentTimeMillis(), 0, x, y, 1, false)); } catch (Exception e) { Debug.error(e); } } @Override public void onResourceRequested() { Dimension size = getSize(); if (size != null) { createBeanIcon(size); super.onResourceRequested(); } } private void createBeanIcon(Dimension size) { try { // Try to get a new icon, if there were any changes. If there // was no change, then null will be returned. byte[] mostRecentIcon = getIconIfChanged(size); if (mostRecentIcon != null) { setIcon(mostRecentIcon); if (icon != null) { icon.setCacheable(false); } } } catch (Exception e) { Debug.log("Error rendering bean in web client: " + bean, e); //$NON-NLS-1$ } } private byte[] getIconIfChanged(Dimension size) { // It seems that the web client generates invocations in pairs: first the "isChanged" method // is invoked, then the "onResourceRequested". The following situation may appear: when the // "isChanged" method is called, it detects a change, but by the time we get to "onResourceRequested" // there is no new change. In this situation we have to remember the result from the "isChanged". // On the other hand, sometimes the "onResourceRequested" method can be invoked independently of // "isChanged", in which case we don't have to rely on the results from previous calls to "isChanged". // So, as a compromise, we do the following: we rely on a previous call to "isChanged" only if it // was invoked in the recent past (last 2 seconds). Otherwise we assume any cached data may be // out dated and we check again for changed in "onResourceRequested". Date now = new Date(); if ((lastIsChangedQuery == null) || (now.getTime() - lastIsChangedQuery.getTime() > 2000)) createSnapshot(size); if (lastChanged) return lastSnapshot; else return null; } private void createSnapshot(Dimension size) { JPanel parent = new JPanel(new BorderLayout()) { private static final long serialVersionUID = 1L; @Override public Image createImage(int width, int height) { return new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB_PRE); } }; parent.setOpaque(false); parent.add(bean, BorderLayout.CENTER); parent.setSize(size); parent.doLayout(); parent.addNotify(); byte[] mostRecent = SnapShot.createImage(null, parent, size.width, size.height, IMAGE_TYPE.PNG); parent.remove(bean); if (mostRecent != null) { if (lastSnapshot != null) { if (mostRecent.length != lastSnapshot.length) { lastChanged = true; } else { lastChanged = false; for (int i = 0; i < mostRecent.length; i++) { if (mostRecent[i] != lastSnapshot[i]) { lastChanged = true; break; } } } } else { lastChanged = true; } } else { lastChanged = false; } if (lastChanged) lastSnapshot = mostRecent; } /** * @see com.servoy.j2db.server.headlessclient.dataui.WebBaseButton#getStylePropertyChanges() */ @Override public IStylePropertyChanges getStylePropertyChanges() { IStylePropertyChanges changes = super.getStylePropertyChanges(); if (!lastChanged) { testChanged(); if (!changes.isChanged() && lastChanged) { changes.setChanged(); } } else { changes.setChanged(); } boolean useAnchors = Utils.getAsBoolean(application.getRuntimeProperties().get("enableAnchors")); //$NON-NLS-1$ if (useAnchors) { if ((anchoring & (IAnchorConstants.EAST | IAnchorConstants.WEST)) != 0) changes.getChanges().remove("width"); //$NON-NLS-1$ if ((anchoring & (IAnchorConstants.NORTH | IAnchorConstants.SOUTH)) != 0) changes.getChanges().remove("height"); //$NON-NLS-1$ } return changes; } @Override public boolean isVisible() { return bean.isVisible(); } /* * (non-Javadoc) * * @see com.servoy.j2db.server.headlessclient.dataui.WebBaseButton#setComponentVisible(boolean) */ @Override public void setComponentVisible(boolean visible) { bean.setVisible(visible); } @Override public void setSize(Dimension size) { createBeanIcon(size); super.setSize(size); } private boolean testChanged() { try { // Here we check for changes all the time. This is the purpose of this method, to query any // new changes, so we cannot cache results. Dimension size = getSize(); if (size != null) { createSnapshot(getSize()); lastIsChangedQuery = new Date(); // Remember the time of this query for changes. return lastChanged; } else { return false; } } catch (Exception e) { Debug.log("Error checking if bean in web client was changed: " + bean, e); return false; } } @Override protected void addEnabledStyleAttributeModifier() { // ignore } }