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