diff --git a/browser/themes/linux/devtools/widgets.css b/browser/themes/linux/devtools/widgets.css index c6cc784faad7..babde8110650 100644 --- a/browser/themes/linux/devtools/widgets.css +++ b/browser/themes/linux/devtools/widgets.css @@ -575,7 +575,7 @@ .variable-or-property[non-configurable] > tooltip > label[value=configurable], .variable-or-property[non-writable] > tooltip > label[value=writable], .variable-or-property[non-extensible] > tooltip > label[value=extensible] { - color: #800; + color: #f44; text-decoration: line-through; } diff --git a/mobile/android/base/Makefile.in b/mobile/android/base/Makefile.in index f7b1d0a1f0b5..2bf6155554e7 100644 --- a/mobile/android/base/Makefile.in +++ b/mobile/android/base/Makefile.in @@ -132,6 +132,7 @@ FENNEC_JAVA_FILES = \ prompts/Prompt.java \ prompts/PromptInput.java \ prompts/PromptService.java \ + prompts/IconGridInput.java \ Restarter.java \ sqlite/ByteBufferInputStream.java \ sqlite/MatrixBlobCursor.java \ @@ -447,6 +448,8 @@ RES_LAYOUT = \ res/layout/home_banner.xml \ res/layout/home_suggestion_prompt.xml \ res/layout/home_top_sites_page.xml \ + res/layout/icon_grid.xml \ + res/layout/icon_grid_item.xml \ res/layout/web_app.xml \ res/layout/launch_app_list.xml \ res/layout/launch_app_listitem.xml \ @@ -613,6 +616,8 @@ RES_DRAWABLE_MDPI = \ res/drawable-mdpi/bookmark_folder_closed.png \ res/drawable-mdpi/bookmark_folder_opened.png \ res/drawable-mdpi/desktop_notification.png \ + res/drawable-mdpi/grid_icon_bg_activated.9.png \ + res/drawable-mdpi/grid_icon_bg_focused.9.png \ res/drawable-mdpi/home_tab_menu_strip.9.png \ res/drawable-mdpi/ic_menu_addons_filler.png \ res/drawable-mdpi/ic_menu_bookmark_add.png \ @@ -718,6 +723,8 @@ RES_DRAWABLE_HDPI = \ res/drawable-hdpi/folder.png \ res/drawable-hdpi/home_bg.png \ res/drawable-hdpi/home_star.png \ + res/drawable-hdpi/grid_icon_bg_activated.9.png \ + res/drawable-hdpi/grid_icon_bg_focused.9.png \ res/drawable-hdpi/abouthome_thumbnail.png \ res/drawable-hdpi/alert_addon.png \ res/drawable-hdpi/alert_app.png \ @@ -830,6 +837,8 @@ RES_DRAWABLE_XHDPI = \ res/drawable-xhdpi/alert_mic_camera.png \ res/drawable-xhdpi/arrow_popup_bg.9.png \ res/drawable-xhdpi/home_tab_menu_strip.9.png \ + res/drawable-xhdpi/grid_icon_bg_activated.9.png \ + res/drawable-xhdpi/grid_icon_bg_focused.9.png \ res/drawable-xhdpi/ic_menu_addons_filler.png \ res/drawable-xhdpi/ic_menu_bookmark_add.png \ res/drawable-xhdpi/ic_menu_bookmark_remove.png \ @@ -1079,6 +1088,7 @@ RES_DRAWABLE += \ res/drawable/url_bar_bg.xml \ res/drawable/url_bar_entry.xml \ res/drawable/url_bar_nav_button.xml \ + res/drawable/icon_grid_item_bg.xml \ res/drawable/url_bar_right_edge.xml \ res/drawable/bookmark_folder.xml \ res/drawable/divider_horizontal.xml \ diff --git a/mobile/android/base/gfx/LayerView.java b/mobile/android/base/gfx/LayerView.java index 8ee50ac6b29d..0959db318e8b 100644 --- a/mobile/android/base/gfx/LayerView.java +++ b/mobile/android/base/gfx/LayerView.java @@ -21,12 +21,14 @@ import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.PixelFormat; +import android.graphics.Point; import android.graphics.PointF; import android.graphics.Rect; import android.graphics.SurfaceTexture; import android.os.Build; import android.os.Handler; import android.util.AttributeSet; +import android.util.DisplayMetrics; import android.util.Log; import android.view.KeyEvent; import android.view.MotionEvent; @@ -125,6 +127,18 @@ public class LayerView extends FrameLayout { GeckoAccessibility.setDelegate(this); } + private Point getEventRadius(MotionEvent event) { + if (Build.VERSION.SDK_INT >= 9) { + return new Point((int)event.getToolMajor()/2, + (int)event.getToolMinor()/2); + } + + float size = event.getSize(); + DisplayMetrics displaymetrics = getContext().getResources().getDisplayMetrics(); + size = size * Math.min(displaymetrics.heightPixels, displaymetrics.widthPixels); + return new Point((int)size, (int)size); + } + public void geckoConnected() { // See if we want to force 16-bit colour before doing anything PrefsHelper.getPref("gfx.android.rgb16.force", new PrefsHelper.PrefHandlerBase() { @@ -157,8 +171,10 @@ public class LayerView extends FrameLayout { } if (mInitialTouchPoint != null && action == MotionEvent.ACTION_MOVE) { + Point p = getEventRadius(event); + if (PointUtils.subtract(point, mInitialTouchPoint).length() < - PanZoomController.PAN_THRESHOLD) { + Math.max(PanZoomController.CLICK_THRESHOLD, Math.min(Math.min(p.x, p.y), PanZoomController.PAN_THRESHOLD))) { // Don't send the touchmove event if if the users finger hasn't moved far. // Necessary for Google Maps to work correctly. See bug 771099. return true; diff --git a/mobile/android/base/gfx/PanZoomController.java b/mobile/android/base/gfx/PanZoomController.java index 42854df1d8ed..d34aef33ef87 100644 --- a/mobile/android/base/gfx/PanZoomController.java +++ b/mobile/android/base/gfx/PanZoomController.java @@ -18,6 +18,9 @@ public interface PanZoomController { // between the touch-down and touch-up of a click). In units of density-independent pixels. public static final float PAN_THRESHOLD = 1/16f * GeckoAppShell.getDpi(); + // Threshold for sending touch move events to content + public static final float CLICK_THRESHOLD = 1/50f * GeckoAppShell.getDpi(); + static class Factory { static PanZoomController create(PanZoomTarget target, View view, EventDispatcher dispatcher) { return new JavaPanZoomController(target, view, dispatcher); diff --git a/mobile/android/base/prompts/IconGridInput.java b/mobile/android/base/prompts/IconGridInput.java new file mode 100644 index 000000000000..2ade7bff6744 --- /dev/null +++ b/mobile/android/base/prompts/IconGridInput.java @@ -0,0 +1,172 @@ +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.gecko.prompts; + +import org.json.JSONArray; +import org.json.JSONObject; + +import org.mozilla.gecko.R; +import org.mozilla.gecko.GeckoAppShell; +import org.mozilla.gecko.gfx.BitmapUtils; +import org.mozilla.gecko.util.ThreadUtils; + +import android.app.Activity; +import android.app.ActivityManager; +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.text.TextUtils; +import android.view.Display; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewGroup.LayoutParams; +import android.view.LayoutInflater; +import android.view.WindowManager; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.ArrayAdapter; +import android.widget.GridView; +import android.widget.TextView; +import android.widget.ImageView; +import android.widget.ListView; +import android.util.Log; + +import java.util.ArrayList; +import java.util.List; + +public class IconGridInput extends PromptInput implements OnItemClickListener { + public static final String INPUT_TYPE = "icongrid"; + public static final String LOGTAG = "GeckoIconGridInput"; + + private ArrayAdapter mAdapter; // An adapter holding a list of items to show in the grid + + private static int mColumnWidth = -1; // The maximum width of columns + private static int mMaxColumns = -1; // The maximum number of columns to show + private static int mIconSize = -1; // Size of icons in the grid + private int mSelected = -1; // Current selection + private JSONArray mArray; + + public IconGridInput(JSONObject obj) { + super(obj); + mArray = obj.optJSONArray("items"); + } + + @Override + public View getView(Context context) throws UnsupportedOperationException { + if (mColumnWidth < 0) { + // getColumnWidth isn't available on pre-ICS, so we pull it out and assign it here + mColumnWidth = context.getResources().getDimensionPixelSize(R.dimen.icongrid_columnwidth); + } + + if (mIconSize < 0) { + mIconSize = GeckoAppShell.getPreferredIconSize(); + } + + if (mMaxColumns < 0) { + mMaxColumns = context.getResources().getInteger(R.integer.max_icon_grid_columns); + } + + // TODO: Dynamically handle size changes + final WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + final Display display = wm.getDefaultDisplay(); + final int screenWidth = display.getWidth(); + int maxColumns = Math.min(mMaxColumns, screenWidth / mColumnWidth); + + final GridView view = (GridView) LayoutInflater.from(context).inflate(R.layout.icon_grid, null, false); + view.setColumnWidth(mColumnWidth); + + final ArrayList items = new ArrayList(mArray.length()); + for (int i = 0; i < mArray.length(); i++) { + IconGridItem item = new IconGridItem(context, mArray.optJSONObject(i)); + items.add(item); + if (item.selected) { + mSelected = i; + view.setSelection(i); + } + } + + view.setNumColumns(Math.min(items.size(), maxColumns)); + view.setOnItemClickListener(this); + + mAdapter = new IconGridAdapter(context, -1, items); + view.setAdapter(mAdapter); + mView = view; + return mView; + } + + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + mSelected = position; + } + + @Override + public String getValue() { + return Integer.toString(mSelected); + } + + @Override + public boolean getScrollable() { + return true; + } + + private class IconGridAdapter extends ArrayAdapter { + public IconGridAdapter(Context context, int resource, List items) { + super(context, resource, items); + } + + @Override + public View getView(int position, View convert, ViewGroup parent) { + final Context context = parent.getContext(); + if (convert == null) { + convert = LayoutInflater.from(context).inflate(R.layout.icon_grid_item, parent, false); + } + bindView(convert, context, position); + return convert; + } + + private void bindView(View v, Context c, int position) { + final IconGridItem item = getItem(position); + final TextView text1 = (TextView) v.findViewById(android.R.id.text1); + text1.setText(item.label); + + final TextView text2 = (TextView) v.findViewById(android.R.id.text2); + if (TextUtils.isEmpty(item.description)) { + text2.setVisibility(View.GONE); + } else { + text2.setVisibility(View.VISIBLE); + text2.setText(item.description); + } + + final ImageView icon = (ImageView) v.findViewById(R.id.icon); + icon.setImageDrawable(item.icon); + ViewGroup.LayoutParams lp = icon.getLayoutParams(); + lp.width = lp.height = mIconSize; + } + } + + private class IconGridItem { + final String label; + final String description; + final boolean selected; + Drawable icon; + + public IconGridItem(final Context context, final JSONObject obj) { + label = obj.optString("name"); + final String iconUrl = obj.optString("iconUri"); + description = obj.optString("description"); + selected = obj.optBoolean("selected"); + + BitmapUtils.getDrawable(context, iconUrl, new BitmapUtils.BitmapLoader() { + public void onBitmapFound(Drawable d) { + icon = d; + if (mAdapter != null) { + mAdapter.notifyDataSetChanged(); + } + } + }); + } + } +} diff --git a/mobile/android/base/prompts/Prompt.java b/mobile/android/base/prompts/Prompt.java index 210dc4ce2c21..6cc6b2736fd8 100644 --- a/mobile/android/base/prompts/Prompt.java +++ b/mobile/android/base/prompts/Prompt.java @@ -35,6 +35,7 @@ import android.view.View; import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; import android.view.inputmethod.InputMethodManager; +import android.widget.AbsListView; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.ArrayAdapter; @@ -116,89 +117,30 @@ public class Prompt implements OnClickListener, OnCancelListener, OnItemClickLis processMessage(message); } - public void show(String aTitle, String aText, PromptListItem[] aMenuList, boolean aMultipleSelection) { + public void show(String title, String text, PromptListItem[] listItems, boolean multipleSelection) { ThreadUtils.assertOnUiThread(); GeckoAppShell.getLayerView().abortPanning(); AlertDialog.Builder builder = new AlertDialog.Builder(mContext); - if (!TextUtils.isEmpty(aTitle)) { - builder.setTitle(aTitle); + if (!TextUtils.isEmpty(title)) { + builder.setTitle(title); } - if (!TextUtils.isEmpty(aText)) { - builder.setMessage(aText); + if (!TextUtils.isEmpty(text)) { + builder.setMessage(text); } - int length = mInputs == null ? 0 : mInputs.length; - if (aMenuList != null && aMenuList.length > 0) { - int resourceId = android.R.layout.simple_list_item_1; - if (mSelected != null && mSelected.length > 0) { - if (aMultipleSelection) { - resourceId = R.layout.select_dialog_multichoice; - } else { - resourceId = R.layout.select_dialog_singlechoice; - } - } - PromptListAdapter adapter = new PromptListAdapter(mContext, resourceId, aMenuList); - if (mSelected != null && mSelected.length > 0) { - if (aMultipleSelection) { - adapter.listView = (ListView) mInflater.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); - mSelected = null; - } - } else if (length == 1) { - try { - ScrollView view = new ScrollView(mContext); - view.addView(mInputs[0].getView(mContext)); - builder.setView(applyInputStyle(view)); - } catch(UnsupportedOperationException ex) { - // We cannot display these input widgets with this sdk version, - // do not display any dialog and finish the prompt now. - try { - finishDialog(new JSONObject("{\"button\": -1}")); - } catch(JSONException e) { } - return; - } - } else if (length > 1) { - try { - LinearLayout linearLayout = new LinearLayout(mContext); - linearLayout.setOrientation(LinearLayout.VERTICAL); - for (int i = 0; i < length; i++) { - View content = mInputs[i].getView(mContext); - linearLayout.addView(content); - } - ScrollView view = new ScrollView(mContext); - view.addView(linearLayout); - builder.setView(applyInputStyle(view)); - } catch(UnsupportedOperationException ex) { - // We cannot display these input widgets with this sdk version, - // do not display any dialog and finish the prompt now. - try { - finishDialog(new JSONObject("{\"button\": -1}")); - } catch(JSONException e) { } - return; - } + // Because lists are currently added through the normal Android AlertBuilder interface, they're + // incompatible with also adding additional input elements to a dialog. + if (listItems != null && listItems.length > 0) { + addlistItems(builder, listItems, multipleSelection); + } else if (!addInputs(builder)) { + // If we failed to add any requested input elements, don't show the dialog + return; } - length = mButtons == null ? 0 : mButtons.length; + int length = mButtons == null ? 0 : mButtons.length; if (length > 0) { builder.setPositiveButton(mButtons[0], this); if (length > 1) { @@ -222,64 +164,242 @@ public class Prompt implements OnClickListener, OnCancelListener, OnItemClickLis mInputs = inputs; } + /* Adds to a result value from the lists that can be shown in dialogs. + * Will set the selected value(s) to the button attribute of the + * object that's passed in. If this is a multi-select dialog, can set + * the button attribute to an array. + */ + private void addListResult(final JSONObject result, int which) { + try { + if (mSelected != null) { + JSONArray selected = new JSONArray(); + for (int i = 0; i < mSelected.length; i++) { + selected.put(mSelected[i]); + } + result.put("button", selected); + } else { + result.put("button", which); + } + } catch(JSONException ex) { } + } + + /* Adds to a result value from the inputs that can be shown in dialogs. + * Each input will set its own value in the result. + */ + private void addInputValues(final JSONObject result) { + try { + if (mInputs != null) { + for (int i = 0; i < mInputs.length; i++) { + result.put(mInputs[i].getId(), mInputs[i].getValue()); + } + } + } catch(JSONException ex) { } + } + + /* Adds the selected button to a result. This should only be called if there + * are no lists shown on the dialog, since they also write their results to the button + * attribute. + */ + private void addButtonResult(final JSONObject result, int which) { + int button = -1; + switch(which) { + case DialogInterface.BUTTON_POSITIVE : button = 0; break; + case DialogInterface.BUTTON_NEUTRAL : button = 1; break; + case DialogInterface.BUTTON_NEGATIVE : button = 2; break; + } + try { + result.put("button", button); + } catch(JSONException ex) { } + } + @Override - public void onClick(DialogInterface aDialog, int aWhich) { + public void onClick(DialogInterface dialog, int which) { ThreadUtils.assertOnUiThread(); JSONObject ret = new JSONObject(); try { - int button = -1; 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); - } + addListResult(ret, which); } 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); - } - if (mInputs != null) { - for (int i = 0; i < mInputs.length; i++) { - ret.put(mInputs[i].getId(), mInputs[i].getValue()); - } + addButtonResult(ret, which); } + addInputValues(ret); } catch(Exception ex) { Log.i(LOGTAG, "Error building return: " + ex); } - if (mDialog != null) { - mDialog.dismiss(); + if (dialog != null) { + dialog.dismiss(); } finishDialog(ret); } + /* Adds a set of list items to the prompt. This can be used for either context menu type dialogs, checked lists, + * or multiple selection lists. If mSelected is set in the prompt before addlistItems is called, the items will be + * shown with "checkmarks" on their left side. + * + * @param builder + * The alert builder currently building this dialog. + * @param listItems + * The items to add. + * @param multipleSelection + * If true, and mSelected is defined to be a non-zero-length list, the list will show checkmarks on the + * left and allow multiple selection. + */ + private void addlistItems(AlertDialog.Builder builder, PromptListItem[] listItems, boolean multipleSelection) { + if (mSelected != null && mSelected.length > 0) { + if (multipleSelection) { + addMultiSelectList(builder, listItems); + } else { + addSingleSelectList(builder, listItems); + } + } else { + addMenuList(builder, listItems); + } + } + + /* Shows a multi-select list with checkmarks on the side. Android doesn't support using an adapter for + * multi-choice lists by default so instead we insert our own custom list so that we can do fancy things + * to the rows like disabling/indenting them. + * + * @param builder + * The alert builder currently building this dialog. + * @param listItems + * The items to add. + */ + private void addMultiSelectList(AlertDialog.Builder builder, PromptListItem[] listItems) { + PromptListAdapter adapter = new PromptListAdapter(mContext, R.layout.select_dialog_multichoice, listItems); + adapter.listView = (ListView) mInflater.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); + } + + /* Shows a single-select list with radio boxes on the side. + * + * @param builder + * the alert builder currently building this dialog. + * @param listItems + * The items to add. + */ + private void addSingleSelectList(AlertDialog.Builder builder, PromptListItem[] listItems) { + PromptListAdapter adapter = new PromptListAdapter(mContext, R.layout.select_dialog_singlechoice, listItems); + // For single select, we only maintain a single index of the selected row + int selectedIndex = -1; + for (int i = 0; i < mSelected.length; i++) { + if (mSelected[i]) { + selectedIndex = i; + break; + } + } + mSelected = null; + + builder.setSingleChoiceItems(adapter, selectedIndex, this); + } + + /* Shows a single-select list. + * + * @param builder + * the alert builder currently building this dialog. + * @param listItems + * The items to add. + */ + private void addMenuList(AlertDialog.Builder builder, PromptListItem[] listItems) { + PromptListAdapter adapter = new PromptListAdapter(mContext, android.R.layout.simple_list_item_1, listItems); + builder.setAdapter(adapter, this); + mSelected = null; + } + + /* Add the requested input elements to the dialog. + * + * @param builder + * the alert builder currently building this dialog. + * @return + * return true if the inputs were added successfully. This may fail + * if the requested input is compatible with this Android verison + */ + private boolean addInputs(AlertDialog.Builder builder) { + int length = mInputs == null ? 0 : mInputs.length; + if (length == 0) { + return true; + } + + try { + View root = null; + boolean scrollable = false; // If any of the innuts are scrollable, we won't wrap this in a ScrollView + + if (length == 1) { + root = mInputs[0].getView(mContext); + scrollable |= mInputs[0].getScrollable(); + } else if (length > 1) { + LinearLayout linearLayout = new LinearLayout(mContext); + linearLayout.setOrientation(LinearLayout.VERTICAL); + for (int i = 0; i < length; i++) { + View content = mInputs[i].getView(mContext); + linearLayout.addView(content); + scrollable |= mInputs[i].getScrollable(); + } + root = linearLayout; + } + + if (scrollable) { + builder.setView(applyInputStyle(root)); + } else { + ScrollView view = new ScrollView(mContext); + view.addView(root); + builder.setView(applyInputStyle(view)); + } + } catch(Exception ex) { + Log.e(LOGTAG, "Error showing prompt inputs", ex); + // We cannot display these input widgets with this sdk version, + // do not display any dialog and finish the prompt now. + cancelDialog(); + return false; + } + + return true; + } + + /* AdapterView.OnItemClickListener + * Called when a list item is clicked + */ @Override public void onItemClick(AdapterView parent, View view, int position, long id) { ThreadUtils.assertOnUiThread(); mSelected[position] = !mSelected[position]; } + /* @DialogInterface.OnCancelListener + * Called when the user hits back to cancel a dialog. The dialog will close itself when this + * ends. Setup the correct return values here. + * + * @param aDialog + * A dialog interface for the dialog that's being closed. + */ @Override public void onCancel(DialogInterface aDialog) { ThreadUtils.assertOnUiThread(); + cancelDialog(); + } + + /* Called in situations where we want to cancel the dialog . This can happen if the user hits back, + * or if the dialog can't be created because of invalid JSON. + */ + private void cancelDialog() { JSONObject ret = new JSONObject(); try { ret.put("button", -1); } catch(Exception ex) { } + addInputValues(ret); finishDialog(ret); } + /* Called any time we're closing the dialog to cleanup and notify listeners that the dialog + * is closing. + */ public void finishDialog(JSONObject aReturn) { mInputs = null; mButtons = null; @@ -302,10 +422,12 @@ public class Prompt implements OnClickListener, OnCancelListener, OnItemClickLis mGuid = null; } + /* Handles parsing the initial JSON sent to show dialogs + */ private void processMessage(JSONObject geckoObject) { - String title = getSafeString(geckoObject, "title"); - String text = getSafeString(geckoObject, "text"); - mGuid = getSafeString(geckoObject, "guid"); + String title = geckoObject.optString("title"); + String text = geckoObject.optString("text"); + mGuid = geckoObject.optString("guid"); mButtons = getStringArray(geckoObject, "buttons"); @@ -323,14 +445,6 @@ public class Prompt implements OnClickListener, OnCancelListener, OnItemClickLis show(title, text, menuitems, multiple); } - private static String getSafeString(JSONObject json, String key) { - try { - return json.getString(key); - } catch (Exception e) { - return ""; - } - } - private static JSONArray getSafeArray(JSONObject json, String key) { try { return json.getJSONArray(key); @@ -339,22 +453,6 @@ public class Prompt implements OnClickListener, OnCancelListener, OnItemClickLis } } - private static boolean getSafeBool(JSONObject json, String key) { - try { - return json.getBoolean(key); - } catch (Exception e) { - return false; - } - } - - private static int getSafeInt(JSONObject json, String key ) { - try { - return json.getInt(key); - } catch (Exception e) { - return 0; - } - } - public static String[] getStringArray(JSONObject aObject, String aName) { JSONArray items = getSafeArray(aObject, aName); int length = items.length(); @@ -406,12 +504,12 @@ public class Prompt implements OnClickListener, OnCancelListener, OnItemClickLis public Drawable icon; PromptListItem(JSONObject aObject) { - label = getSafeString(aObject, "label"); - isGroup = getSafeBool(aObject, "isGroup"); - inGroup = getSafeBool(aObject, "inGroup"); - disabled = getSafeBool(aObject, "disabled"); - id = getSafeInt(aObject, "id"); - isParent = getSafeBool(aObject, "isParent"); + label = aObject.optString("label"); + isGroup = aObject.optBoolean("isGroup"); + inGroup = aObject.optBoolean("inGroup"); + disabled = aObject.optBoolean("disabled"); + id = aObject.optInt("id"); + isParent = aObject.optBoolean("isParent"); } public PromptListItem(String aLabel) { diff --git a/mobile/android/base/prompts/PromptInput.java b/mobile/android/base/prompts/PromptInput.java index 47c39dd4d554..75e8ac5a0a87 100644 --- a/mobile/android/base/prompts/PromptInput.java +++ b/mobile/android/base/prompts/PromptInput.java @@ -42,8 +42,6 @@ import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.TimeUnit; public class PromptInput { - private final JSONObject mJSONInput; - protected final String mLabel; protected final String mType; protected final String mId; @@ -329,7 +327,6 @@ public class PromptInput { } public PromptInput(JSONObject obj) { - mJSONInput = obj; mLabel = obj.optString("label"); mType = obj.optString("type"); String id = obj.optString("id"); @@ -351,6 +348,8 @@ public class PromptInput { return new MenulistInput(obj); } else if (LabelInput.INPUT_TYPE.equals(type)) { return new LabelInput(obj); + } else if (IconGridInput.INPUT_TYPE.equals(type)) { + return new IconGridInput(obj); } else { for (String dtType : DateTimeInput.INPUT_TYPES) { if (dtType.equals(type)) { @@ -372,4 +371,8 @@ public class PromptInput { public String getValue() { return ""; } + + public boolean getScrollable() { + return false; + } } diff --git a/mobile/android/base/resources/drawable-hdpi/grid_icon_bg_activated.9.png b/mobile/android/base/resources/drawable-hdpi/grid_icon_bg_activated.9.png new file mode 100644 index 000000000000..e591a7b009ad Binary files /dev/null and b/mobile/android/base/resources/drawable-hdpi/grid_icon_bg_activated.9.png differ diff --git a/mobile/android/base/resources/drawable-hdpi/grid_icon_bg_activated.png b/mobile/android/base/resources/drawable-hdpi/grid_icon_bg_activated.png new file mode 100644 index 000000000000..e591a7b009ad Binary files /dev/null and b/mobile/android/base/resources/drawable-hdpi/grid_icon_bg_activated.png differ diff --git a/mobile/android/base/resources/drawable-hdpi/grid_icon_bg_focused.9.png b/mobile/android/base/resources/drawable-hdpi/grid_icon_bg_focused.9.png new file mode 100644 index 000000000000..ea27290d76dd Binary files /dev/null and b/mobile/android/base/resources/drawable-hdpi/grid_icon_bg_focused.9.png differ diff --git a/mobile/android/base/resources/drawable-mdpi/grid_icon_bg_activated.9.png b/mobile/android/base/resources/drawable-mdpi/grid_icon_bg_activated.9.png new file mode 100644 index 000000000000..7dfea4c00546 Binary files /dev/null and b/mobile/android/base/resources/drawable-mdpi/grid_icon_bg_activated.9.png differ diff --git a/mobile/android/base/resources/drawable-mdpi/grid_icon_bg_focused.9.png b/mobile/android/base/resources/drawable-mdpi/grid_icon_bg_focused.9.png new file mode 100644 index 000000000000..99b027946926 Binary files /dev/null and b/mobile/android/base/resources/drawable-mdpi/grid_icon_bg_focused.9.png differ diff --git a/mobile/android/base/resources/drawable-xhdpi/grid_icon_bg_activated.9.png b/mobile/android/base/resources/drawable-xhdpi/grid_icon_bg_activated.9.png new file mode 100644 index 000000000000..f01a79e3bf44 Binary files /dev/null and b/mobile/android/base/resources/drawable-xhdpi/grid_icon_bg_activated.9.png differ diff --git a/mobile/android/base/resources/drawable-xhdpi/grid_icon_bg_focused.9.png b/mobile/android/base/resources/drawable-xhdpi/grid_icon_bg_focused.9.png new file mode 100644 index 000000000000..7bea197d0c51 Binary files /dev/null and b/mobile/android/base/resources/drawable-xhdpi/grid_icon_bg_focused.9.png differ diff --git a/mobile/android/base/resources/drawable/icon_grid_item_bg.xml b/mobile/android/base/resources/drawable/icon_grid_item_bg.xml new file mode 100644 index 000000000000..1cde70493b48 --- /dev/null +++ b/mobile/android/base/resources/drawable/icon_grid_item_bg.xml @@ -0,0 +1,26 @@ + + + + + + + + + + diff --git a/mobile/android/base/resources/layout/icon_grid.xml b/mobile/android/base/resources/layout/icon_grid.xml new file mode 100644 index 000000000000..33c9aee3126b --- /dev/null +++ b/mobile/android/base/resources/layout/icon_grid.xml @@ -0,0 +1,10 @@ + + + + diff --git a/mobile/android/base/resources/layout/icon_grid_item.xml b/mobile/android/base/resources/layout/icon_grid_item.xml new file mode 100644 index 000000000000..5c1a4bc9b7ab --- /dev/null +++ b/mobile/android/base/resources/layout/icon_grid_item.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + diff --git a/mobile/android/base/resources/values/dimens.xml b/mobile/android/base/resources/values/dimens.xml index 535131ef6ec9..81d73376e76c 100644 --- a/mobile/android/base/resources/values/dimens.xml +++ b/mobile/android/base/resources/values/dimens.xml @@ -96,4 +96,8 @@ 72dp + + + 128dp + 16dp diff --git a/mobile/android/base/resources/values/integers.xml b/mobile/android/base/resources/values/integers.xml index d3a4e4e94770..74934bff980b 100644 --- a/mobile/android/base/resources/values/integers.xml +++ b/mobile/android/base/resources/values/integers.xml @@ -7,5 +7,6 @@ 6 2 + 4 diff --git a/mobile/android/base/tests/robocop.ini b/mobile/android/base/tests/robocop.ini index bd9603965c2e..236dbd2319b6 100644 --- a/mobile/android/base/tests/robocop.ini +++ b/mobile/android/base/tests/robocop.ini @@ -20,6 +20,7 @@ [testMailToContextMenu] [testPictureLinkContextMenu] [testPasswordProvider] +[testPromptGridInput] # [testPasswordEncrypt] # see bug 824067 [testFormHistory] [testBrowserProvider] diff --git a/mobile/android/base/tests/roboextender/robocop_prompt_gridinput.html b/mobile/android/base/tests/roboextender/robocop_prompt_gridinput.html new file mode 100644 index 000000000000..b39468e454ac --- /dev/null +++ b/mobile/android/base/tests/roboextender/robocop_prompt_gridinput.html @@ -0,0 +1,51 @@ + + + IconGrid test page + + + + + + + diff --git a/mobile/android/base/tests/testPromptGridInput.java.in b/mobile/android/base/tests/testPromptGridInput.java.in new file mode 100644 index 000000000000..da597401e948 --- /dev/null +++ b/mobile/android/base/tests/testPromptGridInput.java.in @@ -0,0 +1,61 @@ +#filter substitution +package @ANDROID_PACKAGE_NAME@.tests; + +import @ANDROID_PACKAGE_NAME@.*; + +import android.graphics.drawable.Drawable; +import android.widget.EditText; +import android.widget.CheckedTextView; +import android.widget.TextView; +import android.text.InputType; +import android.util.DisplayMetrics; +import android.view.View; +import android.util.Log; + +import org.json.JSONObject; + +public class testPromptGridInput extends BaseTest { + @Override + protected int getTestType() { + return TEST_MOCHITEST; + } + + protected int index = 1; + public void testPromptGridInput() { + blockForGeckoReady(); + + test(1); + + testGridItem("Icon 1"); + testGridItem("Icon 2"); + testGridItem("Icon 3"); + testGridItem("Icon 4"); + testGridItem("Icon 5"); + testGridItem("Icon 6"); + testGridItem("Icon 7"); + testGridItem("Icon 8"); + testGridItem("Icon 9"); + testGridItem("Icon 10"); + testGridItem("Icon 11"); + + mSolo.clickOnText("Icon 11"); + mSolo.clickOnText("OK"); + + mAsserter.ok(waitForText("PASS"), "test passed", "PASS"); + mActions.sendSpecialKey(Actions.SpecialKey.BACK); + } + + public void testGridItem(String title) { + // Force the list to scroll if necessary + mSolo.waitForText(title, 1, 500, true); + mAsserter.ok(waitForText(title), "Found grid item", title); + } + + public void test(final int num) { + // Load about:blank between each test to ensure we reset state + loadUrl("about:blank"); + mAsserter.ok(waitForText("about:blank"), "Loaded blank page", "page title match"); + + loadUrl("chrome://roboextender/content/robocop_prompt_gridinput.html#test" + num); + } +} diff --git a/mobile/android/components/PromptService.js b/mobile/android/components/PromptService.js index 7acbd3b4f644..4bf08979dadb 100644 --- a/mobile/android/components/PromptService.js +++ b/mobile/android/components/PromptService.js @@ -224,7 +224,7 @@ InternalPrompt.prototype = { alertCheck: function alertCheck(aTitle, aText, aCheckMsg, aCheckState) { let p = this._getPrompt(aTitle, aText, [ PromptUtils.getLocaleString("OK") ], aCheckMsg, aCheckState); let data = this.showPrompt(p); - if (aCheckState) + if (aCheckState && data.button > -1) aCheckState.value = data.checkbox0 == "true"; }, @@ -238,7 +238,7 @@ InternalPrompt.prototype = { let p = this._getPrompt(aTitle, aText, null, aCheckMsg, aCheckState); let data = this.showPrompt(p); let ok = data.button == 0; - if (aCheckState) + if (aCheckState && data.button > -1) aCheckState.value = data.checkbox0 == "true"; return ok; }, @@ -284,7 +284,7 @@ InternalPrompt.prototype = { let p = this._getPrompt(aTitle, aText, buttons, aCheckMsg, aCheckState); let data = this.showPrompt(p); - if (aCheckState) + if (aCheckState && data.button > -1) aCheckState.value = data.checkbox0 == "true"; return data.button; }, @@ -298,7 +298,7 @@ InternalPrompt.prototype = { let data = this.showPrompt(p); let ok = data.button == 0; - if (aCheckState) + if (aCheckState && data.button > -1) aCheckState.value = data.checkbox0 == "true"; if (ok) aValue.value = data.textbox0; @@ -316,7 +316,7 @@ InternalPrompt.prototype = { let data = this.showPrompt(p); let ok = data.button == 0; - if (aCheckState) + if (aCheckState && data.button > -1) aCheckState.value = data.checkbox0 == "true"; if (ok) aPassword.value = data.password0; @@ -337,7 +337,7 @@ InternalPrompt.prototype = { let data = this.showPrompt(p); let ok = data.button == 0; - if (aCheckState) + if (aCheckState && data.button > -1) aCheckState.value = data.checkbox0 == "true"; if (ok) { aUsername.value = data.textbox0; diff --git a/mobile/android/modules/Prompt.jsm b/mobile/android/modules/Prompt.jsm index 5589752e3188..5999f30a2a8a 100644 --- a/mobile/android/modules/Prompt.jsm +++ b/mobile/android/modules/Prompt.jsm @@ -122,6 +122,14 @@ Prompt.prototype = { }); }, + addIconGrid: function(aOptions) { + return this._addInput({ + type: "icongrid", + items: aOptions.items, + id: aOptions.id + }); + }, + show: function(callback) { this.callback = callback; log("Sending message"); diff --git a/toolkit/components/places/nsNavHistoryResult.cpp b/toolkit/components/places/nsNavHistoryResult.cpp index 46763ac9c23e..e16ef3069eec 100644 --- a/toolkit/components/places/nsNavHistoryResult.cpp +++ b/toolkit/components/places/nsNavHistoryResult.cpp @@ -61,8 +61,8 @@ #define END_RESULT_BATCH_AND_REFRESH_CONTENTS() \ PR_BEGIN_MACRO \ nsNavHistoryResult* result = GetResult(); \ - NS_ENSURE_STATE(result); \ - if (result->mBatchInProgress) { \ + NS_WARN_IF_FALSE(result, "Working with a non-live-updating Places container"); \ + if (result && result->mBatchInProgress) { \ result->EndBatch(); \ } \ PR_END_MACRO