Bug 695485 - Native UI for select elements. r=mfinkle

This commit is contained in:
Wes Johnston 2011-10-28 11:49:19 -07:00
parent 5215d252e5
commit 19d77538ea
7 changed files with 296 additions and 29 deletions

View File

@ -1540,7 +1540,7 @@ public class GeckoAppShell
final JSONObject geckoObject = json.getJSONObject("gecko");
String type = geckoObject.getString("type");
if (type.equals("prompt")) {
if (type.equals("Prompt:Show")) {
if (sPromptQueue == null)
sPromptQueue = new SynchronousQueue<String>();
getHandler().post(new Runnable() {
@ -1548,6 +1548,7 @@ public class GeckoAppShell
getPromptService().processMessage(geckoObject);
}
});
String promptServiceResult = "";
try {
while (null == (promptServiceResult = sPromptQueue.poll(1, TimeUnit.MILLISECONDS))) {

View File

@ -147,6 +147,8 @@ RES_LAYOUT = \
res/layout/notification_progress_text.xml \
res/layout/tabs_row.xml \
res/layout/tabs_tray.xml \
res/layout/list_item_header.xml \
res/layout/select_dialog_list.xml \
$(NULL)
RES_VALUES = \

View File

@ -44,19 +44,26 @@ import android.util.Log;
import java.lang.String;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.view.View;
import android.view.ViewGroup;
import android.view.LayoutInflater;
import android.view.ViewGroup.LayoutParams;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.TextView;
import android.widget.CheckBox;
import android.widget.CheckedTextView;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.Spinner;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.DialogInterface.OnCancelListener;
import android.content.DialogInterface.OnMultiChoiceClickListener;
import org.json.JSONArray;
import org.json.JSONObject;
import android.text.method.PasswordTransformationMethod;
@ -64,10 +71,10 @@ import android.graphics.Color;
import android.text.InputType;
import android.app.AlertDialog;
public class PromptService implements OnClickListener, OnCancelListener {
public class PromptService implements OnClickListener, OnCancelListener, OnItemClickListener {
private PromptInput[] mInputs;
private AlertDialog mDialog = null;
private String[] mMenuItems = null;
private static final String LOG_NAME = "GeckoPromptService";
private class PromptButton {
public String label = "";
@ -158,22 +165,51 @@ public class PromptService implements OnClickListener, OnCancelListener {
}
}
public void Show(String aTitle, String aText, PromptButton[] aButtons) {
public void Show(String aTitle, String aText, PromptButton[] aButtons, PromptListItem[] aMenuList, boolean aMultipleSelection) {
AlertDialog.Builder builder = new AlertDialog.Builder(GeckoApp.mAppContext);
if (!aTitle.equals("")) {
builder.setTitle(aTitle);
}
if (mMenuItems.length > 0) {
builder.setItems(mMenuItems, this);
} else {
if (!aText.equals("")) {
builder.setMessage(aText);
}
if (!aText.equals("")) {
builder.setMessage(aText);
}
int length = mInputs.length;
if (length == 1) {
if (aMenuList.length > 0) {
int resourceId = android.R.layout.select_dialog_item;
if (mSelected.length > 0) {
if (aMultipleSelection) {
resourceId = android.R.layout.select_dialog_multichoice;
} else {
resourceId = android.R.layout.select_dialog_singlechoice;
}
}
PromptListAdapter adapter = new PromptListAdapter(GeckoApp.mAppContext, resourceId, aMenuList);
if (mSelected.length > 0) {
if (aMultipleSelection) {
LayoutInflater inflater = GeckoApp.mAppContext.getLayoutInflater();
adapter.listView = (ListView) inflater.inflate(R.layout.select_dialog_list, null);
adapter.listView.setOnItemClickListener(this);
builder.setInverseBackgroundForced(true);
adapter.listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
adapter.listView.setAdapter(adapter);
builder.setView(adapter.listView);
} else {
int selectedIndex = -1;
for (int i = 0; i < mSelected.length; i++) {
if (mSelected[i]) {
selectedIndex = i;
break;
}
}
mSelected = null;
builder.setSingleChoiceItems(adapter, selectedIndex, this);
}
} else {
builder.setAdapter(adapter, this);
}
} else if (length == 1) {
builder.setView(mInputs[0].getView());
} else if (length > 1) {
LinearLayout linearLayout = new LinearLayout(GeckoApp.mAppContext);
@ -215,43 +251,55 @@ public class PromptService implements OnClickListener, OnCancelListener {
JSONObject ret = new JSONObject();
try {
int button = -1;
if (mMenuItems.length > 0) {
ListView list = mDialog.getListView();
if (list != null || mSelected != null) {
button = aWhich;
if (mSelected != null) {
JSONArray selected = new JSONArray();
for (int i = 0; i < mSelected.length; i++) {
selected.put(mSelected[i]);
}
ret.put("button", selected);
} else {
ret.put("button", button);
}
} else {
switch(aWhich) {
case DialogInterface.BUTTON_POSITIVE : button = 0; break;
case DialogInterface.BUTTON_NEUTRAL : button = 1; break;
case DialogInterface.BUTTON_NEGATIVE : button = 2; break;
}
ret.put("button", button);
}
ret.put("button", button);
if (mInputs != null) {
for (int i = 0; i < mInputs.length; i++) {
ret.put(mInputs[i].getName(), mInputs[i].getValue());
}
}
} catch(Exception ex) {
Log.i("GeckoShell", "Error building return: " + ex);
Log.i(LOG_NAME, "Error building return: " + ex);
}
if (mDialog != null) {
mDialog.dismiss();
}
finishDialog(ret.toString());
}
private boolean[] mSelected = null;
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
mSelected[position] = !mSelected[position];
}
public void onCancel(DialogInterface aDialog) {
JSONObject ret = new JSONObject();
try {
int button = -1;
ret.put("button", button);
} catch(Exception ex) {
Log.i("GeckoShell", "Error building return: " + ex);
}
finishDialog(ret.toString());
onClick(aDialog, -1);
}
public void finishDialog(String aReturn) {
mInputs = null;
mDialog = null;
mMenuItems = null;
Log.i("GeckoShell", "finish " + aReturn);
mSelected = null;
try {
GeckoAppShell.sPromptQueue.put(aReturn);
} catch(Exception ex) { }
@ -291,16 +339,20 @@ public class PromptService implements OnClickListener, OnCancelListener {
} catch(Exception ex) { }
}
mMenuItems = getStringArray(geckoObject, "listitems");
this.Show(title, text, promptbuttons);
PromptListItem[] menuitems = getListItemArray(geckoObject, "listitems");
mSelected = getBooleanArray(geckoObject, "selected");
boolean multiple = false;
try {
multiple = geckoObject.getBoolean("multiple");
} catch(Exception ex) { }
this.Show(title, text, promptbuttons, menuitems, multiple);
}
private String[] getStringArray(JSONObject aObject, String aName) {
JSONArray items = new JSONArray();
try {
items = aObject.getJSONArray(aName);
} catch(Exception ex) {
}
} catch(Exception ex) { }
int length = items.length();
String[] list = new String[length];
for (int i = 0; i < length; i++) {
@ -310,4 +362,105 @@ public class PromptService implements OnClickListener, OnCancelListener {
}
return list;
}
private boolean[] getBooleanArray(JSONObject aObject, String aName) {
JSONArray items = new JSONArray();
try {
items = aObject.getJSONArray(aName);
} catch(Exception ex) { }
int length = items.length();
boolean[] list = new boolean[length];
for (int i = 0; i < length; i++) {
try {
list[i] = items.getBoolean(i);
} catch(Exception ex) { }
}
return list;
}
private PromptListItem[] getListItemArray(JSONObject aObject, String aName) {
JSONArray items = new JSONArray();
try {
items = aObject.getJSONArray(aName);
} catch(Exception ex) {
}
int length = items.length();
PromptListItem[] list = new PromptListItem[length];
for (int i = 0; i < length; i++) {
try {
list[i] = new PromptListItem(items.getJSONObject(i));
} catch(Exception ex) { }
}
return list;
}
private class PromptListItem {
public String label = "";
public boolean isGroup = false;
public boolean inGroup = false;
public boolean disabled = false;
public int id = 0;
PromptListItem(JSONObject aObject) {
try { label = aObject.getString("label"); } catch(Exception ex) { }
try { isGroup = aObject.getBoolean("isGroup"); } catch(Exception ex) { }
try { inGroup = aObject.getBoolean("inGroup"); } catch(Exception ex) { }
try { disabled = aObject.getBoolean("disabled"); } catch(Exception ex) { }
try { id = aObject.getInt("id"); } catch(Exception ex) { }
}
}
public class PromptListAdapter extends ArrayAdapter<PromptListItem> {
public ListView listView = null;
private PromptListItem[] mList;
private int mResourceId = -1;
PromptListAdapter(Context context, int textViewResourceId, PromptListItem[] objects) {
super(context, textViewResourceId, objects);
mList = objects;
mResourceId = textViewResourceId;
}
public int getCount() {
return mList.length;
}
public PromptListItem getItem(int position) {
return mList[position];
}
public long getItemId(int position) {
return mList[position].id;
}
public View getView(int position, View convertView, ViewGroup parent) {
PromptListItem item = getItem(position);
int resourceId = mResourceId;
if (item.isGroup) {
resourceId = R.layout.list_item_header;
}
LayoutInflater inflater = GeckoApp.mAppContext.getLayoutInflater();
View row = inflater.inflate(resourceId, null);
if (!item.isGroup){
try {
CheckedTextView ct = (CheckedTextView)row.findViewById(android.R.id.text1);
if (ct != null){
ct.setEnabled(!item.disabled);
ct.setClickable(item.disabled);
// Apparently just using ct.setChecked(true) doesn't work, so this
// is stolen from the android source code as a way to set the checked
// state of these items
if (mSelected[position] && listView != null) {
listView.setItemChecked(position, true);
}
}
} catch (Exception ex) { }
}
TextView t1 = (TextView) row.findViewById(android.R.id.text1);
if (t1 != null) {
t1.setText(item.label);
}
return row;
}
}
}

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- This is used for select lists for multiple selection enabled -->
<view xmlns:android="http://schemas.android.com/apk/res/android"
class="android.widget.ListView"
android:id="@+id/select_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:cacheColorHint="@null"
android:scrollbars="vertical"
android:overScrollMode="ifContentScrolls" />

View File

@ -891,6 +891,8 @@ var BrowserEventHandler = {
if (this.blockClick) {
aEvent.stopPropagation();
aEvent.preventDefault();
} else {
FormAssistant.handleClick(aEvent);
}
break;
@ -1337,6 +1339,102 @@ var ErrorPageEventHandler = {
}
};
var FormAssistant = {
show: function(aList, aElement) {
let data = JSON.parse(sendMessageToJava({ gecko: aList }));
let selected = data.button;
if (!(selected instanceof Array)) {
let temp = [];
for (let i = 0; i < aList.listitems.length; i++) {
temp[i] = (i == selected);
}
selected = temp;
}
this.forOptions(aElement, function(aNode, aIndex) {
aNode.selected = selected[aIndex];
});
},
handleClick: function(aEvent) {
let target = aEvent.target;
while (target) {
if (this._isSelectElement(target)) {
let list = this.getListForElement(target);
this.show(list, target);
target = null;
return true;
}
if (target)
target = target.parentNode;
}
return false;
},
_isSelectElement: function(aElement) {
return (aElement instanceof HTMLSelectElement);
},
_isOptionElement: function(aElement) {
return aElement instanceof HTMLOptionElement;
},
_isOptionGroupElement: function(aElement) {
return aElement instanceof HTMLOptGroupElement;
},
getListForElement: function(aElement) {
let result = {
type: "Prompt:Show",
multiple: aElement.multiple,
selected: [],
listitems: []
};
if (aElement.multiple) {
result.buttons = [
{ label: Strings.browser.GetStringFromName("selectHelper.closeMultipleSelectDialog") },
];
}
this.forOptions(aElement, function(aNode, aIndex) {
result.listitems[aIndex] = {
label: aNode.text || aNode.label,
isGroup: this._isOptionGroupElement(aNode),
inGroup: this._isOptionGroupElement(aNode.parentNode),
disabled: aNode.disabled,
id: aIndex
}
result.selected[aIndex] = aNode.selected;
});
return result;
},
forOptions: function(aElement, aFunction) {
let optionIndex = 0;
let children = aElement.children;
for (let i = 0; i < children.length; i++) {
let child = children[i];
if (this._isOptionGroupElement(child)) {
aFunction.call(this, child, optionIndex);
optionIndex++;
let subchildren = child.children;
for (let j = 0; j < subchildren.length; j++) {
let subchild = subchildren[j];
aFunction.call(this, subchild, optionIndex);
optionIndex++;
}
} else if (this._isOptionElement(child)) {
// This is a regular choice under no group.
aFunction.call(this, child, optionIndex);
optionIndex++;
}
}
}
}
var XPInstallObserver = {
observe: function xpi_observer(aSubject, aTopic, aData) {
switch (aTopic) {

View File

@ -156,7 +156,7 @@ Prompt.prototype = {
if (aCheckMsg)
aInputs.push({ type: "checkbox", label: aCheckMsg, checked: aCheckState.value });
let msg = { type: "prompt" };
let msg = { type: "Prompt:Show" };
if (aTitle) msg.title = aTitle;
if (aText) msg.text = aText;
msg.buttons = aButtons || [

View File

@ -248,3 +248,6 @@ appMenu.more=More
#Text Selection
selectionHelper.textCopied=Text copied to clipboard
#Select UI
selectHelper.closeMultipleSelectDialog=Done