SelectPopupDialog.java revision 58537e28ecd584eab876aee8be7156509866d23a
1// Copyright (c) 2012 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5package org.chromium.content.browser.input; 6 7import org.chromium.content.browser.ContentViewCore; 8 9import android.app.AlertDialog; 10import android.content.DialogInterface; 11import android.content.res.TypedArray; 12import android.util.SparseBooleanArray; 13import android.view.View; 14import android.view.ViewGroup; 15import android.widget.AdapterView; 16import android.widget.AdapterView.OnItemClickListener; 17import android.widget.ArrayAdapter; 18import android.widget.CheckedTextView; 19import android.widget.ListView; 20 21import org.chromium.content.R; 22 23/** 24 * Handles the popup dialog for the <select> HTML tag support. 25 */ 26public class SelectPopupDialog { 27 // The currently showing popup dialog, null if none is showing. 28 private static SelectPopupDialog sShownDialog; 29 30 private static final int[] SELECT_DIALOG_ATTRS = { 31 R.attr.select_dialog_multichoice, 32 R.attr.select_dialog_singlechoice 33 }; 34 35 // The dialog hosting the popup list view. 36 private AlertDialog mListBoxPopup = null; 37 38 private ContentViewCore mContentViewCore; 39 40 /** 41 * Subclass ArrayAdapter so we can disable OPTION_GROUP items. 42 */ 43 private class SelectPopupArrayAdapter extends ArrayAdapter<String> { 44 /** 45 * Possible values for mItemEnabled. 46 * Keep in sync with the value passed from content_view_core_impl.cc 47 */ 48 final static int POPUP_ITEM_TYPE_GROUP = 0; 49 final static int POPUP_ITEM_TYPE_DISABLED = 1; 50 final static int POPUP_ITEM_TYPE_ENABLED = 2; 51 52 // Indicates the POPUP_ITEM_TYPE of each item. 53 private int[] mItemEnabled; 54 55 // True if all items are POPUP_ITEM_TYPE_ENABLED. 56 private boolean mAreAllItemsEnabled; 57 58 public SelectPopupArrayAdapter(String[] labels, int[] enabled, boolean multiple) { 59 super(mContentViewCore.getContext(), getSelectDialogLayout(multiple), labels); 60 mItemEnabled = enabled; 61 mAreAllItemsEnabled = true; 62 for (int item : mItemEnabled) { 63 if (item != POPUP_ITEM_TYPE_ENABLED) { 64 mAreAllItemsEnabled = false; 65 break; 66 } 67 } 68 } 69 70 @Override 71 public View getView(int position, View convertView, ViewGroup parent) { 72 if (position < 0 || position >= getCount()) { 73 return null; 74 } 75 76 // Always pass in null so that we will get a new CheckedTextView. Otherwise, an item 77 // which was previously used as an <optgroup> element (i.e. has no check), could get 78 // used as an <option> element, which needs a checkbox/radio, but it would not have 79 // one. 80 convertView = super.getView(position, null, parent); 81 if (mItemEnabled[position] != POPUP_ITEM_TYPE_ENABLED) { 82 if (mItemEnabled[position] == POPUP_ITEM_TYPE_GROUP) { 83 // Currently select_dialog_multichoice & select_dialog_multichoice use 84 // CheckedTextViews. If that changes, the class cast will no longer be valid. 85 ((CheckedTextView) convertView).setCheckMarkDrawable(null); 86 } else { 87 // Draw the disabled element in a disabled state. 88 convertView.setEnabled(false); 89 } 90 } 91 return convertView; 92 } 93 94 @Override 95 public boolean areAllItemsEnabled() { 96 return mAreAllItemsEnabled; 97 } 98 99 @Override 100 public boolean isEnabled(int position) { 101 if (position < 0 || position >= getCount()) { 102 return false; 103 } 104 return mItemEnabled[position] == POPUP_ITEM_TYPE_ENABLED; 105 } 106 } 107 108 private int getSelectDialogLayout(boolean isMultiChoice) { 109 int resource_id; 110 TypedArray styledAttributes = mContentViewCore.getContext().obtainStyledAttributes( 111 R.style.SelectPopupDialog, SELECT_DIALOG_ATTRS); 112 resource_id = styledAttributes.getResourceId(isMultiChoice ? 0 : 1, 0); 113 styledAttributes.recycle(); 114 return resource_id; 115 } 116 117 private SelectPopupDialog(ContentViewCore contentViewCore, String[] labels, int[] enabled, 118 boolean multiple, int[] selected) { 119 mContentViewCore = contentViewCore; 120 121 final ListView listView = new ListView(mContentViewCore.getContext()); 122 AlertDialog.Builder b = new AlertDialog.Builder(mContentViewCore.getContext()) 123 .setView(listView) 124 .setCancelable(true) 125 .setInverseBackgroundForced(true); 126 127 if (multiple) { 128 b.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { 129 @Override 130 public void onClick(DialogInterface dialog, int which) { 131 mContentViewCore.selectPopupMenuItems(getSelectedIndices(listView)); 132 }}); 133 b.setNegativeButton(android.R.string.cancel, 134 new DialogInterface.OnClickListener() { 135 @Override 136 public void onClick(DialogInterface dialog, int which) { 137 mContentViewCore.selectPopupMenuItems(null); 138 }}); 139 } 140 mListBoxPopup = b.create(); 141 final SelectPopupArrayAdapter adapter = new SelectPopupArrayAdapter(labels, enabled, 142 multiple); 143 listView.setAdapter(adapter); 144 listView.setFocusableInTouchMode(true); 145 146 if (multiple) { 147 listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); 148 for (int i = 0; i < selected.length; ++i) { 149 listView.setItemChecked(selected[i], true); 150 } 151 } else { 152 listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE); 153 listView.setOnItemClickListener(new OnItemClickListener() { 154 @Override 155 public void onItemClick(AdapterView<?> parent, View v, 156 int position, long id) { 157 mContentViewCore.selectPopupMenuItems(getSelectedIndices(listView)); 158 mListBoxPopup.dismiss(); 159 } 160 }); 161 if (selected.length > 0) { 162 listView.setSelection(selected[0]); 163 listView.setItemChecked(selected[0], true); 164 } 165 } 166 mListBoxPopup.setOnCancelListener(new DialogInterface.OnCancelListener() { 167 @Override 168 public void onCancel(DialogInterface dialog) { 169 mContentViewCore.selectPopupMenuItems(null); 170 } 171 }); 172 mListBoxPopup.setOnDismissListener(new DialogInterface.OnDismissListener() { 173 @Override 174 public void onDismiss(DialogInterface dialog) { 175 mListBoxPopup = null; 176 sShownDialog = null; 177 } 178 }); 179 } 180 181 private int[] getSelectedIndices(ListView listView) { 182 SparseBooleanArray sparseArray = listView.getCheckedItemPositions(); 183 int selectedCount = 0; 184 for (int i = 0; i < sparseArray.size(); ++i) { 185 if (sparseArray.valueAt(i)) { 186 selectedCount++; 187 } 188 } 189 int[] indices = new int[selectedCount]; 190 for (int i = 0, j = 0; i < sparseArray.size(); ++i) { 191 if (sparseArray.valueAt(i)) { 192 indices[j++] = sparseArray.keyAt(i); 193 } 194 } 195 return indices; 196 } 197 198 /** 199 * Shows the popup menu triggered by the passed ContentView. 200 * Hides any currently shown popup. 201 * @param items Items to show. 202 * @param enabled POPUP_ITEM_TYPEs for items. 203 * @param multiple Whether the popup menu should support multi-select. 204 * @param selectedIndices Indices of selected items. 205 */ 206 public static void show(ContentViewCore contentViewCore, String[] items, int[] enabled, 207 boolean multiple, int[] selectedIndices) { 208 // Hide the popup currently showing if any. This could happen if the user pressed a select 209 // and pressed it again before the popup was shown. In that case, the previous popup is 210 // irrelevant and can be hidden. 211 hide(null); 212 sShownDialog = new SelectPopupDialog(contentViewCore, items, enabled, multiple, 213 selectedIndices); 214 sShownDialog.mListBoxPopup.show(); 215 } 216 217 /** 218 * Hides the showing popup menu if any it was triggered by the passed ContentView. If 219 * contentView is null, hides it regardless of which ContentView triggered it. 220 * @param contentView 221 */ 222 public static void hide(ContentViewCore contentView) { 223 if (sShownDialog != null && 224 (contentView == null || sShownDialog.mContentViewCore == contentView)) { 225 if (contentView != null) contentView.selectPopupMenuItems(null); 226 sShownDialog.mListBoxPopup.dismiss(); 227 } 228 } 229 230 // The methods below are used by tests. 231 public static SelectPopupDialog getCurrent() { 232 return sShownDialog; 233 } 234} 235