1/*
2 * Copyright (C) 2012 Google Inc.
3 * Licensed to The Android Open Source Project.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 *      http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18package com.android.mail.browse;
19
20import com.android.mail.browse.MergedAdapter.ListSpinnerAdapter;
21import com.android.mail.browse.MergedAdapter.LocalAdapterPosition;
22
23import android.content.Context;
24import android.graphics.Rect;
25import android.graphics.drawable.Drawable;
26import android.util.AttributeSet;
27import android.view.View;
28import android.view.ViewGroup;
29import android.widget.AdapterView;
30import android.widget.FrameLayout;
31import android.widget.ListPopupWindow;
32import android.widget.ListView;
33
34
35/**
36 * <p>A spinner-like widget that combines data and views from multiple adapters (via MergedAdapter)
37 * and forwards certain events to those adapters. This widget also supports clickable but
38 * unselectable dropdown items, useful when displaying extra items that should not affect spinner
39 * selection state.</p>
40 *
41 * <p>The framework's Spinner widget can't be extended for this task because it uses a private list
42 * adapter (which prevents setting multiple item types) and hides access to its popup, which is
43 * useful for clients to know about (like when it's been opened).</p>
44 *
45 * <p>Clients must provide a set of adapters which the widget will use to load dropdown views,
46 * receive callbacks, and load the selected item's view.</p>
47 *
48 * <p>Apps incorporating this widget must declare a custom attribute: "dropDownWidth" under the
49 * "MultiAdapterSpinner" name as a "reference" format (see Gmail's attrs.xml file for an
50 * example). This attribute controls the width of the dropdown, similar to the attribute in the
51 * framework's Spinner widget.</p>
52 *
53 */
54public class MultiAdapterSpinner extends FrameLayout
55    implements AdapterView.OnItemClickListener, View.OnClickListener {
56
57    protected MergedAdapter<FancySpinnerAdapter> mAdapter;
58    protected ListPopupWindow mPopup;
59
60    private int mSelectedPosition = -1;
61    private Rect mTempRect = new Rect();
62
63    /**
64     * A basic adapter with some callbacks added so clients can be involved in spinner behavior.
65     */
66    public interface FancySpinnerAdapter extends ListSpinnerAdapter {
67        /**
68         * Whether or not an item at position should become the new selected spinner item and change
69         * the spinner item view.
70         */
71        boolean canSelect(int position);
72        /**
73         * Handle a click on an enabled item.
74         */
75        void onClick(int position);
76        /**
77         * Fired when the popup window is about to be displayed.
78         */
79        void onShowPopup();
80    }
81
82    private static class MergedSpinnerAdapter extends MergedAdapter<FancySpinnerAdapter> {
83        /**
84         * ListPopupWindow uses getView() but spinners return dropdown views in getDropDownView().
85         */
86        @Override
87        public View getView(int position, View convertView, ViewGroup parent) {
88            return super.getDropDownView(position, convertView, parent);
89        }
90    }
91
92    public MultiAdapterSpinner(Context context) {
93        this(context, null);
94    }
95
96    public MultiAdapterSpinner(Context context, AttributeSet attrs) {
97        super(context, attrs);
98
99        mAdapter = new MergedSpinnerAdapter();
100        mPopup = new ListPopupWindow(context, attrs);
101        mPopup.setAnchorView(this);
102        mPopup.setOnItemClickListener(this);
103        mPopup.setModal(true);
104        mPopup.setAdapter(mAdapter);
105    }
106
107    public void setAdapters(FancySpinnerAdapter... adapters) {
108        mAdapter.setAdapters(adapters);
109    }
110
111    public void setSelectedItem(final FancySpinnerAdapter adapter, final int position) {
112        int globalPosition = 0;
113        boolean found = false;
114
115        for (int i = 0, count = mAdapter.getSubAdapterCount(); i < count; i++) {
116            ListSpinnerAdapter a = mAdapter.getSubAdapter(i);
117            if (a == adapter) {
118                globalPosition += position;
119                found = true;
120                break;
121            }
122            globalPosition += a.getCount();
123        }
124        if (found) {
125            if (adapter.canSelect(position)) {
126                removeAllViews();
127                View itemView = adapter.getView(position, null, this);
128                itemView.setClickable(true);
129                itemView.setOnClickListener(this);
130                addView(itemView);
131
132                if (position < adapter.getCount()) {
133                    mSelectedPosition = globalPosition;
134                }
135            }
136        }
137    }
138
139    @Override
140    public void onClick(View v) {
141        if (!mPopup.isShowing()) {
142
143            for (int i = 0, count = mAdapter.getSubAdapterCount(); i < count; i++) {
144                mAdapter.getSubAdapter(i).onShowPopup();
145            }
146
147            final int spinnerPaddingLeft = getPaddingLeft();
148            final Drawable background = mPopup.getBackground();
149            int bgOffset = 0;
150            if (background != null) {
151                background.getPadding(mTempRect);
152                bgOffset = -mTempRect.left;
153            }
154            mPopup.setHorizontalOffset(bgOffset + spinnerPaddingLeft);
155            mPopup.show();
156            mPopup.getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);
157            mPopup.setSelection(mSelectedPosition);
158        }
159    }
160
161    @Override
162    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
163
164        if (position != mSelectedPosition) {
165            final LocalAdapterPosition<FancySpinnerAdapter> result =
166                mAdapter.getAdapterOffsetForItem(position);
167
168            if (result.mAdapter.canSelect(result.mLocalPosition)) {
169                mSelectedPosition = position;
170            } else {
171                mPopup.clearListSelection();
172            }
173
174            post(new Runnable() {
175                @Override
176                public void run() {
177                    result.mAdapter.onClick(result.mLocalPosition);
178                }
179            });
180        }
181
182        mPopup.dismiss();
183    }
184
185}
186