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