SelectPopupDialog.java revision d0247b1b59f9c528cb6df88b4f2b9afaf80d181e
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        listView.setCacheColorHint(0);
123        AlertDialog.Builder b = new AlertDialog.Builder(mContentViewCore.getContext())
124                .setView(listView)
125                .setCancelable(true)
126                .setInverseBackgroundForced(true);
127
128        if (multiple) {
129            b.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
130                @Override
131                public void onClick(DialogInterface dialog, int which) {
132                    mContentViewCore.selectPopupMenuItems(getSelectedIndices(listView));
133                }});
134            b.setNegativeButton(android.R.string.cancel,
135                    new DialogInterface.OnClickListener() {
136                @Override
137                public void onClick(DialogInterface dialog, int which) {
138                    mContentViewCore.selectPopupMenuItems(null);
139            }});
140        }
141        mListBoxPopup = b.create();
142        final SelectPopupArrayAdapter adapter = new SelectPopupArrayAdapter(labels, enabled,
143                multiple);
144        listView.setAdapter(adapter);
145        listView.setFocusableInTouchMode(true);
146
147        if (multiple) {
148            listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
149            for (int i = 0; i < selected.length; ++i) {
150                listView.setItemChecked(selected[i], true);
151            }
152        } else {
153            listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
154            listView.setOnItemClickListener(new OnItemClickListener() {
155                @Override
156                public void onItemClick(AdapterView<?> parent, View v,
157                        int position, long id) {
158                    mContentViewCore.selectPopupMenuItems(getSelectedIndices(listView));
159                    mListBoxPopup.dismiss();
160                }
161            });
162            if (selected.length > 0) {
163                listView.setSelection(selected[0]);
164                listView.setItemChecked(selected[0], true);
165            }
166        }
167        mListBoxPopup.setOnCancelListener(new DialogInterface.OnCancelListener() {
168            @Override
169            public void onCancel(DialogInterface dialog) {
170                mContentViewCore.selectPopupMenuItems(null);
171            }
172        });
173        mListBoxPopup.setOnDismissListener(new DialogInterface.OnDismissListener() {
174            @Override
175            public void onDismiss(DialogInterface dialog) {
176                mListBoxPopup = null;
177                sShownDialog = null;
178            }
179        });
180    }
181
182    private int[] getSelectedIndices(ListView listView) {
183        SparseBooleanArray sparseArray = listView.getCheckedItemPositions();
184        int selectedCount = 0;
185        for (int i = 0; i < sparseArray.size(); ++i) {
186            if (sparseArray.valueAt(i)) {
187                selectedCount++;
188            }
189        }
190        int[] indices = new int[selectedCount];
191        for (int i = 0, j = 0; i < sparseArray.size(); ++i) {
192            if (sparseArray.valueAt(i)) {
193                indices[j++] = sparseArray.keyAt(i);
194            }
195        }
196        return indices;
197    }
198
199    /**
200     * Shows the popup menu triggered by the passed ContentView.
201     * Hides any currently shown popup.
202     * @param items           Items to show.
203     * @param enabled         POPUP_ITEM_TYPEs for items.
204     * @param multiple        Whether the popup menu should support multi-select.
205     * @param selectedIndices Indices of selected items.
206     */
207    public static void show(ContentViewCore contentViewCore, String[] items, int[] enabled,
208            boolean multiple, int[] selectedIndices) {
209        // Hide the popup currently showing if any.  This could happen if the user pressed a select
210        // and pressed it again before the popup was shown.  In that case, the previous popup is
211        // irrelevant and can be hidden.
212        hide(null);
213        sShownDialog = new SelectPopupDialog(contentViewCore, items, enabled, multiple,
214                selectedIndices);
215        sShownDialog.mListBoxPopup.show();
216    }
217
218    /**
219     * Hides the showing popup menu if any it was triggered by the passed ContentView. If
220     * contentView is null, hides it regardless of which ContentView triggered it.
221     * @param contentView
222     */
223    public static void hide(ContentViewCore contentView) {
224        if (sShownDialog != null &&
225                (contentView == null || sShownDialog.mContentViewCore == contentView)) {
226            if (contentView != null) contentView.selectPopupMenuItems(null);
227            sShownDialog.mListBoxPopup.dismiss();
228        }
229    }
230
231    // The methods below are used by tests.
232    public static SelectPopupDialog getCurrent() {
233        return sShownDialog;
234    }
235}
236