// -*- 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.editor.simple.SimpleEditor;
import com.google.appinventor.client.editor.simple.palette.SimplePaletteItem;
import com.google.appinventor.client.widgets.dnd.DragSource;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.ui.AbsolutePanel;
import java.util.Comparator;
import java.util.SortedSet;
import java.util.TreeSet;
/**
* Mock Canvas component.
*
*/
public final class MockCanvas extends MockContainer {
/**
* Component type name.
*/
public static final String TYPE = "Canvas";
// UI components
private final AbsolutePanel canvasWidget;
/**
* Creates a new MockCanvas component.
*
* @param editor editor of source file the component belongs to
*/
public MockCanvas(SimpleEditor editor) {
super(editor, TYPE, images.canvas(), new MockCanvasLayout());
rootPanel.setHeight("100%");
canvasWidget = new AbsolutePanel();
canvasWidget.setStylePrimaryName("ode-SimpleMockContainer");
canvasWidget.add(rootPanel);
initComponent(canvasWidget);
}
@Override
protected boolean acceptableSource(DragSource source) {
MockComponent component = null;
if (source instanceof MockComponent) {
component = (MockComponent) source;
} else if (source instanceof SimplePaletteItem) {
component = (MockComponent) source.getDragWidget();
}
if (component instanceof MockSprite) {
return true;
}
return false;
}
/*
* Sets the canvas's BackgroundColor property to a new value.
*/
private void setBackgroundColorProperty(String text) {
if (MockComponentsUtil.isDefaultColor(text)) {
text = "&HFFFFFFFF"; // white
}
MockComponentsUtil.setWidgetBackgroundColor(canvasWidget, text);
}
/**
* Sets the canvas's BackgroundImage property to a new value.
*/
private void setBackgroundImageProperty(String text) {
String url = convertImagePropertyValueToUrl(text);
// We tell the layout (which is a MockCanvasLayout) that there is (or is not) a background
// image so it can adjust the "layout width/height". The "layout width/height" is used when the
// preferred width/height of a MockContainer is requested. See MockContainer.getPreferredWidth
// and getPreferredHeight, as well as MockLayout.getPreferredWidth and getPreferredHeight.
if (url == null) {
// text was not recognized as an asset.
((MockCanvasLayout) layout).setBackgroundImageUrl("");
url = "images/canvas.png";
// We set the background image of the canvasWidget so it displays the image. We do it inside
// the if because we need to override the background-size property only for this case
MockComponentsUtil.setWidgetBackgroundImage(this, canvasWidget, url);
DOM.setStyleAttribute(canvasWidget.getElement(), "backgroundSize", "");
} else {
((MockCanvasLayout) layout).setBackgroundImageUrl(url);
// We set the background image of the canvasWidget so it displays the image.
MockComponentsUtil.setWidgetBackgroundImage(this, canvasWidget, url);
}
}
private static class MockSpriteWithCoordinates {
final MockComponent mockComponent;
final int left;
final int top;
MockSpriteWithCoordinates(MockComponent mockComponent, int left, int top) {
this.mockComponent = mockComponent;
this.left = left;
this.top = top;
}
}
/**
* <p>Sorts the children of {@link #rootPanel} such that sprites are
* earlier than ones that may be drawn over them. Specifically, sprites
* are sorted first on Z property, then on their order in the designer's
* component tree. The latter is to make the behavior the same as
* before Z layers were added.</p>
*/
private void sortSprites() {
// Create a temporary set for the existing sprites.
SortedSet<MockSpriteWithCoordinates> sprites =
new TreeSet<MockSpriteWithCoordinates>(
new Comparator<MockSpriteWithCoordinates>() {
@Override
public int compare(MockSpriteWithCoordinates s1,
MockSpriteWithCoordinates s2) {
double z1 = getZProperty(s1.mockComponent);
double z2 = getZProperty(s2.mockComponent);
if (z1 != z2) {
return (int) Math.signum(z1 - z2);
} else {
// Both sprites have the same Z property value,
// so put first whichever one is earlier in the
// component tree.
return Integer.signum(children.indexOf(s1.mockComponent) -
children.indexOf(s2.mockComponent));
}
}
});
// Remove all sprites from the container's list, transferring them to our
// temporary set.
while (rootPanel.getWidgetCount() > 0) {
MockComponent sprite = (MockComponent) rootPanel.getWidget(0);
sprites.add(new MockSpriteWithCoordinates(
sprite,
rootPanel.getWidgetLeft(sprite),
rootPanel.getWidgetTop(sprite)));
rootPanel.remove(sprite);
}
// Add them into the rootPanel in proper order.
for (MockSpriteWithCoordinates sprite : sprites) {
rootPanel.add(sprite.mockComponent, sprite.left, sprite.top);
}
}
/**
* <p>Reorders the children in {@link #rootPanel} so they are sorted by
* Z layer value without changing their order in the component tree.
* We assume that the list is already in the correct order except for
* the changed sprite.</p>
*
* @param changedSprite the sprite whose Z layer value has changed; it
* should already be a child of {@link #rootPanel}
*/
void reorderComponents(MockSprite changedSprite) {
// Since it has already been added to rootPanel, we do not need to pass it,
// just to re-sort the list of rootPanel's children.
sortSprites();
// Redraw.
refreshForm();
}
private static double getZProperty(MockComponent sprite) {
try {
return Double.parseDouble(
sprite.getPropertyValue(MockSprite.PROPERTY_NAME_Z));
} catch (NumberFormatException e) {
return MockSprite.DEFAULT_Z_LAYER;
}
}
// PropertyChangeListener implementation
@Override
public void onPropertyChange(String propertyName, String newValue) {
super.onPropertyChange(propertyName, newValue);
// Apply changed properties to the mock component
if (propertyName.equals(PROPERTY_NAME_BACKGROUNDCOLOR)) {
setBackgroundColorProperty(newValue);
} else if (propertyName.equals(PROPERTY_NAME_BACKGROUNDIMAGE)) {
setBackgroundImageProperty(newValue);
refreshForm();
}
}
}