// -*- 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 com.google.appinventor.components.annotations.DesignerComponent;
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.annotations.UsesActivities;
import com.google.appinventor.components.annotations.androidmanifest.ActivityElement;
import com.google.appinventor.components.common.ComponentCategory;
import com.google.appinventor.components.common.PropertyTypeConstants;
import com.google.appinventor.components.common.YaVersion;
import com.google.appinventor.components.runtime.util.ElementsUtil;
import com.google.appinventor.components.runtime.util.YailList;
import android.app.Activity;
import android.content.Intent;
import android.view.WindowManager;
/**
* A button allowing a user to select one among a list of text strings.
*
* @author sharon@google.com (Sharon Perl)
* @author M. Hossein Amerkashi (kkashi01@gmail.com)
*/
@DesignerComponent(version = YaVersion.LISTPICKER_COMPONENT_VERSION,
category = ComponentCategory.USERINTERFACE,
description = "<p>A button that, when clicked on, displays a list of " +
"texts for the user to choose among. The texts can be specified through " +
"the Designer or Blocks Editor by setting the " +
"<code>ElementsFromString</code> property to their string-separated " +
"concatenation (for example, <em>choice 1, choice 2, choice 3</em>) or " +
"by setting the <code>Elements</code> property to a List in the Blocks " +
"editor.</p>" +
"<p>Setting property ShowFilterBar to true, will make the list searchable. " +
"Other properties affect the appearance of the button " +
"(<code>TextAlignment</code>, <code>BackgroundColor</code>, etc.) and " +
"whether it can be clicked on (<code>Enabled</code>).</p>")
@SimpleObject
@UsesActivities(activities = {
@ActivityElement(name = "com.google.appinventor.components.runtime.ListPickerActivity",
configChanges = "orientation|keyboardHidden",
screenOrientation = "behind")
})
public class ListPicker extends Picker implements ActivityResultListener, Deleteable, OnResumeListener {
private static final String LIST_ACTIVITY_CLASS = ListPickerActivity.class.getName();
static final String LIST_ACTIVITY_ARG_NAME = LIST_ACTIVITY_CLASS + ".list";
static final String LIST_ACTIVITY_RESULT_NAME = LIST_ACTIVITY_CLASS + ".selection";
static final String LIST_ACTIVITY_RESULT_INDEX = LIST_ACTIVITY_CLASS + ".index";
static final String LIST_ACTIVITY_ANIM_TYPE = LIST_ACTIVITY_CLASS + ".anim";
static final String LIST_ACTIVITY_SHOW_SEARCH_BAR = LIST_ACTIVITY_CLASS + ".search";
static final String LIST_ACTIVITY_TITLE = LIST_ACTIVITY_CLASS + ".title";
static final String LIST_ACTIVITY_ORIENTATION_TYPE = LIST_ACTIVITY_CLASS + ".orientation";
static final String LIST_ACTIVITY_ITEM_TEXT_COLOR = LIST_ACTIVITY_CLASS + ".itemtextcolor";
static final String LIST_ACTIVITY_BACKGROUND_COLOR = LIST_ACTIVITY_CLASS + ".backgroundcolor";
private YailList items;
private String selection;
private int selectionIndex;
private boolean showFilter =false;
private static final boolean DEFAULT_ENABLED = false;
private String title = ""; // The Title to display the List Picker with
// if left blank, the App Name is used instead
private boolean resumedFromListFlag = false; //flag so onResume knows if the resume was triggered by closing the listpicker activity
private int itemTextColor;
private int itemBackgroundColor;
public final static int DEFAULT_ITEM_TEXT_COLOR = Component.COLOR_WHITE;
public final static int DEFAULT_ITEM_BACKGROUND_COLOR = Component.COLOR_BLACK;
/**
* Create a new ListPicker component.
*
* @param container the parent container.
*/
public ListPicker(ComponentContainer container) {
super(container);
items = new YailList();
// initialize selectionIndex which also sets selection
SelectionIndex(0);
itemTextColor = DEFAULT_ITEM_TEXT_COLOR;
itemBackgroundColor = DEFAULT_ITEM_BACKGROUND_COLOR;
container.$form().registerForOnResume(this);
}
@Override
public void onResume() {
if (resumedFromListFlag) {
container.$form().getWindow().setSoftInputMode(
WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN
);
resumedFromListFlag = false;
}
}
/**
* Selection property getter method.
*/
@SimpleProperty(
description = "The selected item. When directly changed by the " +
"programmer, the SelectionIndex property is also changed to the first " +
"item in the ListPicker with the given value. If the value does not " +
"appear, SelectionIndex will be set to 0.",
category = PropertyCategory.BEHAVIOR)
public String Selection() {
return selection;
}
/**
* Selection property setter method.
*/
@DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_STRING,
defaultValue = "")
@SimpleProperty
public void Selection(String value) {
selection = value;
// Now, we need to change SelectionIndex to correspond to Selection.
selectionIndex = ElementsUtil.setSelectedIndexFromValue(value, items);
}
@DesignerProperty(
editorType = PropertyTypeConstants.PROPERTY_TYPE_BOOLEAN,
defaultValue = DEFAULT_ENABLED ? "True" : "False")
@SimpleProperty
public void ShowFilterBar(boolean showFilter) {
this.showFilter = showFilter;
}
@SimpleProperty(category = PropertyCategory.BEHAVIOR,
description = "Returns current state of ShowFilterBar indicating if " +
"Search Filter Bar will be displayed on ListPicker or not")
public boolean ShowFilterBar() {
return showFilter;
}
@DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_COLOR,
defaultValue = Component.DEFAULT_VALUE_COLOR_WHITE)
@SimpleProperty
public void ItemTextColor(int argb) {
this.itemTextColor = argb;
}
@SimpleProperty(description = "The text color of the ListPicker items.",
category = PropertyCategory.APPEARANCE)
public int ItemTextColor() {
return this.itemTextColor;
}
@DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_COLOR,
defaultValue = Component.DEFAULT_VALUE_COLOR_BLACK)
@SimpleProperty
public void ItemBackgroundColor(int argb) {
this.itemBackgroundColor = argb;
}
@SimpleProperty(description = "The background color of the ListPicker items.",
category = PropertyCategory.APPEARANCE)
public int ItemBackgroundColor() {
return this.itemBackgroundColor;
}
/**
* Selection index property getter method.
*/
@SimpleProperty(
description = "The index of the currently selected item, starting at " +
"1. If no item is selected, the value will be 0. If an attempt is " +
"made to set this to a number less than 1 or greater than the number " +
"of items in the ListPicker, SelectionIndex will be set to 0, and " +
"Selection will be set to the empty text.",
category = PropertyCategory.BEHAVIOR)
public int SelectionIndex() {
return selectionIndex;
}
/**
* Selection index property setter method.
*/
// Not a designer property, since this could lead to unpredictable
// results if Selection is set to an incompatible value.
@SimpleProperty
public void SelectionIndex(int index) {
selectionIndex = ElementsUtil.selectionIndex(index, items);
// Now, we need to change Selection to correspond to SelectionIndex.
selection = ElementsUtil.setSelectionFromIndex(index, items);
}
/**
* Elements property getter method
*
* @return a YailList representing the list of strings to be picked from
*/
@SimpleProperty(category = PropertyCategory.BEHAVIOR)
public YailList Elements() {
return items;
}
/**
* Elements property setter method
* @param itemList - a YailList containing the strings to be added to the
* ListPicker
*/
// TODO(user): we need a designer property for lists
@SimpleProperty
public void Elements(YailList itemList) {
items = ElementsUtil.elements(itemList, "ListPicker");
}
/**
* ElementsFromString property setter method
*
* @param itemstring - a string containing a comma-separated list of the
* strings to be picked from
*/
@DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_STRING,
defaultValue = "")
// TODO(sharon): it might be nice to have a list editorType where the developer
// could directly enter a list of strings (e.g. one per row) and we could
// avoid the comma-separated business.
@SimpleProperty(category = PropertyCategory.BEHAVIOR)
public void ElementsFromString(String itemstring) {
items = ElementsUtil.elementsFromString(itemstring);
}
/**
* Title property getter method.
*
* @return list picker title
*/
@SimpleProperty(category = PropertyCategory.APPEARANCE,
description = "Optional title displayed at the top of the list of choices.")
public String Title() {
return title;
}
/**
* Title property setter method: sets a new caption for the list picker in the
* list picker activity's title bar.
*
* @param title new list picker caption
*/
@DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_STRING,
defaultValue = "")
@SimpleProperty
public void Title(String title) {
this.title = title;
}
@Override
public Intent getIntent() {
Intent intent = new Intent();
intent.setClassName(container.$context(), LIST_ACTIVITY_CLASS);
intent.putExtra(LIST_ACTIVITY_ARG_NAME, items.toStringArray());
intent.putExtra(LIST_ACTIVITY_SHOW_SEARCH_BAR, String.valueOf(showFilter)); //convert to string
if (!title.equals("")) {
intent.putExtra(LIST_ACTIVITY_TITLE, title);
}
// Get the current Form's opening transition anim type,
// and pass it to the list picker activity. For consistency,
// the closing animation will be the same (but in reverse)
String openAnim = container.$form().getOpenAnimType();
intent.putExtra(LIST_ACTIVITY_ANIM_TYPE, openAnim);
intent.putExtra(LIST_ACTIVITY_ORIENTATION_TYPE,container.$form().ScreenOrientation());
intent.putExtra(LIST_ACTIVITY_ITEM_TEXT_COLOR, itemTextColor);
intent.putExtra(LIST_ACTIVITY_BACKGROUND_COLOR, itemBackgroundColor);
return intent;
}
// ActivityResultListener implementation
/**
* Callback method to get the result returned by the list picker activity
*
* @param requestCode a code identifying the request.
* @param resultCode a code specifying success or failure of the activity
* @param data the returned data, in this case an Intent whose data field
* contains the selected item.
*/
@Override
public void resultReturned(int requestCode, int resultCode, Intent data) {
if (requestCode == this.requestCode && resultCode == Activity.RESULT_OK) {
if (data.hasExtra(LIST_ACTIVITY_RESULT_NAME)) {
selection = data.getStringExtra(LIST_ACTIVITY_RESULT_NAME);
} else {
selection = "";
}
selectionIndex = data.getIntExtra(LIST_ACTIVITY_RESULT_INDEX, 0);
AfterPicking();
// It is necessary for the code of onResume to run there instead of here
// because the activity has not yet been initialized at this point. At this
// point, calls to the keyboard fail.
resumedFromListFlag = true;
}
}
// Deleteable implementation
@Override
public void onDelete() {
container.$form().unregisterForActivityResult(this);
}
}