// -*- 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.components.runtime; import android.app.Activity; import android.graphics.drawable.Drawable; import android.os.Handler; import android.util.Log; import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; import android.widget.HorizontalScrollView; import android.widget.ScrollView; import com.google.appinventor.components.annotations.DesignerProperty; import com.google.appinventor.components.annotations.PropertyCategory; import com.google.appinventor.components.annotations.SimpleObject; import com.google.appinventor.components.annotations.SimpleProperty; import com.google.appinventor.components.common.ComponentConstants; import com.google.appinventor.components.common.PropertyTypeConstants; import com.google.appinventor.components.runtime.util.AlignmentUtil; import com.google.appinventor.components.runtime.util.ErrorMessages; import com.google.appinventor.components.runtime.util.MediaUtil; import com.google.appinventor.components.runtime.util.ViewUtil; import java.io.IOException; import java.util.ArrayList; import java.util.List; /** * A container for components that arranges them linearly, either * horizontally or vertically. * * @author sharon@google.com (Sharon Perl) * @author kkashi01@gmail.com (Hossein Amerkashi) (added Image and BackgroundColors) */ @SimpleObject public class HVArrangement extends AndroidViewComponent implements Component, ComponentContainer { private final Activity context; // Layout private final int orientation; private final LinearLayout viewLayout; private ViewGroup frameContainer; private boolean scrollable = false; // translates App Inventor alignment codes to Android gravity private AlignmentUtil alignmentSetter; // the alignment for this component's LinearLayout private int horizontalAlignment; private int verticalAlignment; // Backing for background color private int backgroundColor; // This is the Drawable corresponding to the Image property. // If an Image has never been set or if the most recent Image could not be loaded, this is null. private Drawable backgroundImageDrawable; // Image path private String imagePath = ""; private Drawable defaultButtonDrawable; private final Handler androidUIHandler = new Handler(); private static final String LOG_TAG = "HVArrangement"; /** * Creates a new HVArrangement component. * * @param container container, component will be placed in * @param orientation one of * {@link ComponentConstants#LAYOUT_ORIENTATION_HORIZONTAL}. * {@link ComponentConstants#LAYOUT_ORIENTATION_VERTICAL} */ public HVArrangement(ComponentContainer container, int orientation, boolean scrollable) { super(container); context = container.$context(); this.orientation = orientation; this.scrollable = scrollable; viewLayout = new LinearLayout(context, orientation, ComponentConstants.EMPTY_HV_ARRANGEMENT_WIDTH, ComponentConstants.EMPTY_HV_ARRANGEMENT_HEIGHT); viewLayout.setBaselineAligned(false); alignmentSetter = new AlignmentUtil(viewLayout); horizontalAlignment = ComponentConstants.HORIZONTAL_ALIGNMENT_DEFAULT; verticalAlignment = ComponentConstants.VERTICAL_ALIGNMENT_DEFAULT; alignmentSetter.setHorizontalAlignment(horizontalAlignment); alignmentSetter.setVerticalAlignment(verticalAlignment); if (scrollable) { switch (orientation) { case LAYOUT_ORIENTATION_VERTICAL: Log.d(LOG_TAG, "Setting up frameContainer = ScrollView()"); frameContainer = new ScrollView(context); break; case LAYOUT_ORIENTATION_HORIZONTAL: Log.d(LOG_TAG, "Setting up frameContainer = HorizontalScrollView()"); frameContainer = new HorizontalScrollView(context); break; } } else { Log.d(LOG_TAG, "Setting up frameContainer = FrameLayout()"); frameContainer = new FrameLayout(context); } frameContainer.setLayoutParams(new ViewGroup.LayoutParams(ComponentConstants.EMPTY_HV_ARRANGEMENT_WIDTH, ComponentConstants.EMPTY_HV_ARRANGEMENT_HEIGHT)); frameContainer.addView(viewLayout.getLayoutManager(), new ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); // Save the default values in case the user wants them back later. defaultButtonDrawable = getView().getBackground(); container.$add(this); BackgroundColor(Component.COLOR_DEFAULT); } // ComponentContainer implementation @Override public Activity $context() { return context; } @Override public Form $form() { return container.$form(); } @Override public void $add(AndroidViewComponent component) { viewLayout.add(component); } @Override public void setChildWidth(final AndroidViewComponent component, int width) { setChildWidth(component, width, 0); } public void setChildWidth(final AndroidViewComponent component, int width, final int trycount) { int cWidth = container.$form().Width(); if (cWidth == 0 && trycount < 2) { // We're not really ready yet... final int fWidth = width; // but give up after two tries... androidUIHandler.postDelayed(new Runnable() { @Override public void run() { Log.d(LOG_TAG, "(HVArrangement)Width not stable yet... trying again"); setChildWidth(component, fWidth, trycount + 1); } }, 100); // Try again in 1/10 of a second } if (width <= LENGTH_PERCENT_TAG) { Log.d(LOG_TAG, "HVArrangement.setChildWidth(): width = " + width + " parent Width = " + cWidth + " child = " + component); width = cWidth * (- (width - LENGTH_PERCENT_TAG)) / 100; } component.setLastWidth(width); if (orientation == ComponentConstants.LAYOUT_ORIENTATION_HORIZONTAL) { ViewUtil.setChildWidthForHorizontalLayout(component.getView(), width); } else { ViewUtil.setChildWidthForVerticalLayout(component.getView(), width); } } @Override public void setChildHeight(final AndroidViewComponent component, int height) { int cHeight = container.$form().Height(); if (cHeight == 0) { // Not ready yet... final int fHeight = height; androidUIHandler.postDelayed(new Runnable() { @Override public void run() { Log.d(LOG_TAG, "(HVArrangement)Height not stable yet... trying again"); setChildHeight(component, fHeight); } }, 100); // Try again in 1/10 of a second } if (height <= LENGTH_PERCENT_TAG) { height = cHeight * (- (height - LENGTH_PERCENT_TAG)) / 100; } component.setLastHeight(height); if (orientation == ComponentConstants.LAYOUT_ORIENTATION_HORIZONTAL) { ViewUtil.setChildHeightForHorizontalLayout(component.getView(), height); } else { ViewUtil.setChildHeightForVerticalLayout(component.getView(), height); } } // AndroidViewComponent implementation @Override public View getView() { return frameContainer; //: viewLayout.getLayoutManager(); } // The numeric encodings are defined Component Constants /** * Returns a number that encodes how contents of the arrangement are aligned horizontally. * The choices are: 1 = left aligned, 2 = right aligned, 3 = horizontally centered */ @SimpleProperty( category = PropertyCategory.APPEARANCE, description = "A number that encodes how contents of the arrangement are aligned " + " horizontally. The choices are: 1 = left aligned, 2 = right aligned, " + " 3 = horizontally centered. Alignment has no effect if the arrangement's width is " + "automatic.") public int AlignHorizontal() { return horizontalAlignment; } /** * Sets the horizontal alignment for contents of the arrangement * * @param alignment */ @DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_HORIZONTAL_ALIGNMENT, defaultValue = ComponentConstants.HORIZONTAL_ALIGNMENT_DEFAULT + "") @SimpleProperty public void AlignHorizontal(int alignment) { try { // notice that the throw will prevent the alignment from being changed // if the argument is illegal alignmentSetter.setHorizontalAlignment(alignment); horizontalAlignment = alignment; } catch (IllegalArgumentException e) { container.$form().dispatchErrorOccurredEvent(this, "HorizontalAlignment", ErrorMessages.ERROR_BAD_VALUE_FOR_HORIZONTAL_ALIGNMENT, alignment); } } /** * Returns a number that encodes how contents of the arrangement are aligned vertically. * The choices are: 1 = aligned at the top, 2 = vertically centered, 3 = aligned at the bottom. * Alignment has no effect if the arrangement's height is automatic. */ @SimpleProperty( category = PropertyCategory.APPEARANCE, description = "A number that encodes how the contents of the arrangement are aligned " + " vertically. The choices are: 1 = aligned at the top, 2 = vertically centered, " + "3 = aligned at the bottom. Alignment has no effect if the arrangement's height " + "is automatic.") public int AlignVertical() { return verticalAlignment; } /** * Sets the vertical alignment for contents of the arrangement * * @param alignment */ @DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_VERTICAL_ALIGNMENT, defaultValue = ComponentConstants.VERTICAL_ALIGNMENT_DEFAULT + "") @SimpleProperty public void AlignVertical(int alignment) { try { // notice that the throw will prevent the alignment from being changed // if the argument is illegal alignmentSetter.setVerticalAlignment(alignment); verticalAlignment = alignment; } catch (IllegalArgumentException e) { container.$form().dispatchErrorOccurredEvent(this, "VerticalAlignment", ErrorMessages.ERROR_BAD_VALUE_FOR_VERTICAL_ALIGNMENT, alignment); } } /** * Returns the component's background color as an alpha-red-green-blue * integer. * * @return background RGB color with alpha */ @SimpleProperty(category = PropertyCategory.APPEARANCE, description = "Returns the component's background color") public int BackgroundColor() { return backgroundColor; } /** * Specifies the button's background color as an alpha-red-green-blue * integer. If the parameter is {@link Component#COLOR_DEFAULT}, the * original beveling is restored. If an Image has been set, the color * change will not be visible until the Image is removed. * * @param argb background RGB color with alpha */ @DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_COLOR, defaultValue = Component.DEFAULT_VALUE_COLOR_DEFAULT) @SimpleProperty(description = "Specifies the component's background color. " + "The background color will not be visible if an Image is being displayed.") public void BackgroundColor(int argb) { backgroundColor = argb; // getView().setBackgroundColor(argb); updateAppearance(); } /** * Returns the path of the button's image. * * @return the path of the button's image */ @SimpleProperty( category = PropertyCategory.APPEARANCE) public String Image() { return imagePath; } /** * Specifies the path of the button's image. * * <p/>See {@link com.google.appinventor.components.runtime.util.MediaUtil#determineMediaSource} for information about what * a path can be. * * @param path the path of the button's image */ @DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_ASSET, defaultValue = "") @SimpleProperty(description = "Specifies the path of the component's image. " + "If there is both an Image and a BackgroundColor, only the Image will be visible.") public void Image(String path) { // If it's the same as on the prior call and the prior load was successful, // do nothing. if (path.equals(imagePath) && backgroundImageDrawable != null) { return; } imagePath = (path == null) ? "" : path; // Clear the prior background image. backgroundImageDrawable = null; // Load image from file. if (imagePath.length() > 0) { try { backgroundImageDrawable = MediaUtil.getBitmapDrawable(container.$form(), imagePath); } catch (IOException ioe) { // Fall through with a value of null for backgroundImageDrawable. } } // Update the appearance based on the new value of backgroundImageDrawable. updateAppearance(); } // Update appearance based on values of backgroundImageDrawable, backgroundColor and shape. // Images take precedence over background colors. private void updateAppearance() { // If there is no background image, // the appearance depends solely on the background color and shape. if (backgroundImageDrawable == null) { if (backgroundColor == Component.COLOR_DEFAULT) { // If there is no background image and color is default, // restore original 3D bevel appearance. ViewUtil.setBackgroundDrawable(viewLayout.getLayoutManager(), defaultButtonDrawable); } else { // Clear the background image. ViewUtil.setBackgroundDrawable(viewLayout.getLayoutManager(), null); viewLayout.getLayoutManager().setBackgroundColor(backgroundColor); } } else { // If there is a background image ViewUtil.setBackgroundImage(viewLayout.getLayoutManager(), backgroundImageDrawable); } } }