// -*- mode: java; c-basic-offset: 2; -*- // Copyright 2009-2011 Google, All Rights reserved // Copyright 2011-2012 MIT, All rights reserved // Released under the Apache License, Version 2.0 // http://www.apache.org/licenses/LICENSE-2.0 package com.google.appinventor.client.editor.simple.components; import com.google.appinventor.client.output.OdeLog; import com.google.appinventor.components.common.ComponentConstants; import com.google.gwt.event.dom.client.ErrorEvent; import com.google.gwt.event.dom.client.ErrorHandler; import com.google.gwt.event.dom.client.LoadEvent; import com.google.gwt.event.dom.client.LoadHandler; import com.google.gwt.user.client.ui.Image; import java.util.Map; /** * A layout that positions and sizes each child to specific pixel * positions/lengths that are provided by the user. * * @author lizlooney@google.com (Liz Looney) */ final class MockCanvasLayout extends MockLayout { private static final String PROPERTY_NAME_X = "X"; private static final String PROPERTY_NAME_Y = "Y"; private final Image image; private String imageUrl; MockCanvasLayout() { layoutWidth = ComponentConstants.CANVAS_PREFERRED_WIDTH; layoutHeight = ComponentConstants.CANVAS_PREFERRED_HEIGHT; image = new Image(); image.addErrorHandler(new ErrorHandler() { @Override public void onError(ErrorEvent event) { if (imageUrl != null && !imageUrl.isEmpty()) { OdeLog.elog("Error occurred while loading image " + imageUrl); } container.refreshForm(); } }); image.addLoadHandler(new LoadHandler() { @Override public void onLoad(LoadEvent event) { container.refreshForm(); } }); } void setBackgroundImageUrl(String imageUrl) { this.imageUrl = imageUrl; image.setUrl(imageUrl); } // MockLayout methods // NOTE(lizlooney) - layout behavior: // Only Balls and ImageSprites can be placed in a Canvas. // A Ball's width and height is always the diameter of the ball. // The actual width/height of an ImageSprite whose Width/Height property is set to Automatic or // Fill Parent will be the width/height of the image. // TODO(lizlooney) - We should not allow users to choose Fill Parent for the Width/Height of an // ImageSprite. @Override LayoutInfo createContainerLayoutInfo(Map<MockComponent, LayoutInfo> layoutInfoMap) { return new LayoutInfo(layoutInfoMap, container) { @Override int calculateAutomaticWidth() { if (imageUrl == null || imageUrl.equals("")) { return ComponentConstants.CANVAS_PREFERRED_WIDTH; } else { return image.getWidth(); } } @Override int calculateAutomaticHeight() { if (imageUrl == null || imageUrl.equals("")) { return ComponentConstants.CANVAS_PREFERRED_HEIGHT; } else { return image.getHeight(); } } }; } @Override void layoutChildren(LayoutInfo containerLayoutInfo) { // Resolve any child's width or height that is fill parent. for (MockComponent child : containerLayoutInfo.visibleChildren) { LayoutInfo childLayoutInfo = containerLayoutInfo.layoutInfoMap.get(child); // If the width is fill parent, use automatic width. if (childLayoutInfo.width == MockVisibleComponent.LENGTH_FILL_PARENT) { childLayoutInfo.calculateAndStoreAutomaticWidth(); } // If the height is fill parent, use automatic height. if (childLayoutInfo.height == MockVisibleComponent.LENGTH_FILL_PARENT) { childLayoutInfo.calculateAndStoreAutomaticHeight(); } } // Position the children. for (MockComponent child : containerLayoutInfo.visibleChildren) { LayoutInfo childLayoutInfo = containerLayoutInfo.layoutInfoMap.get(child); int x; try { x = (int) Math.round(Double.parseDouble(child.getPropertyValue(PROPERTY_NAME_X))); } catch (NumberFormatException e) { // Ignore this. If we throw an exception here, the project is unrecoverable. x = 0; } int y; try { y = (int) Math.round(Double.parseDouble(child.getPropertyValue(PROPERTY_NAME_Y))); } catch (NumberFormatException e) { // Ignore this. If we throw an exception here, the project is unrecoverable. y = 0; } container.setChildSizeAndPosition(child, childLayoutInfo, x, y); } // Update layoutWidth and layoutHeight. // layoutWidth and layoutHeight are based on the background image, not on where the children // (sprites) are located. if (imageUrl == null || imageUrl.equals("")) { layoutWidth = ComponentConstants.CANVAS_PREFERRED_WIDTH; layoutHeight = ComponentConstants.CANVAS_PREFERRED_HEIGHT; } else { layoutWidth = image.getWidth(); layoutHeight = image.getHeight(); } } @Override boolean onDrop(MockComponent source, int x, int y, int offsetX, int offsetY) { // Set position of component source.changeProperty(PROPERTY_NAME_X, toIntegerString(x - offsetX)); source.changeProperty(PROPERTY_NAME_Y, toIntegerString(y - offsetY)); // Perform drop MockContainer srcContainer = source.getContainer(); if (srcContainer != null) { // Pass false to indicate that the component isn't being permanently deleted. // It's just being moved from one container to another. srcContainer.removeComponent(source, false); } container.addComponent(source); ((MockCanvas) container).reorderComponents((MockSprite) source); return true; } /* * So this one is truly priceless: FF3 returns coordinates that are actually not integers, but * doubles. And since our code is translated into untyped Javascript coordinates we are now * suddenly handling doubles instead of integers which causes lots of problems down the road... */ private String toIntegerString(int n) { return Integer.toString(n).split("\\.")[0]; } }