1 /***
2 * Ambient - A music player for the Android platform
3 Copyright (C) 2007 Martin Vysny
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18 package sk.baka.ambient.views.gesturelist;
19
20 import java.util.HashMap;
21 import java.util.IdentityHashMap;
22 import java.util.Iterator;
23 import java.util.Map;
24 import java.util.Map.Entry;
25
26 import android.content.Context;
27 import android.view.LayoutInflater;
28 import android.view.View;
29 import android.view.ViewGroup;
30 import android.widget.AbsListView;
31 import android.widget.ArrayAdapter;
32 import android.widget.BaseAdapter;
33
34 /***
35 * An adapter backed by a list. Unlike {@link ArrayAdapter} this adapter
36 * supports the list redraw operation - you don't need to set new adapter
37 * instance to the ListView when the items are changed.
38 *
39 * @author Martin Vysny
40 */
41 public final class MutableListAdapter extends BaseAdapter {
42
43 /***
44 * The inflater used to inflate new views.
45 */
46 protected final LayoutInflater inflater;
47
48 /***
49 * Owning list view.
50 */
51 private final GesturesListView owner;
52
53 private final ModelHolder model;
54
55 /***
56 * Creates new adapter.
57 *
58 * @param owner
59 * owning list view
60 * @param model
61 * the model
62 */
63 public MutableListAdapter(final GesturesListView owner,
64 final ModelHolder model) {
65 super();
66 this.owner = owner;
67 this.model = model;
68 this.inflater = (LayoutInflater) owner.getContext().getSystemService(
69 Context.LAYOUT_INFLATER_SERVICE);
70 }
71
72 /***
73 * Height of zoomed views, in pixels.
74 */
75 public static final int ZOOMED_HEIGHT = 48;
76
77 public final View getView(int arg0, View arg1, ViewGroup arg2) {
78 final View view = arg1 != null ? arg1 : inflater.inflate(
79 owner.itemLayoutId, owner, false);
80 view.setLayoutParams(new AbsListView.LayoutParams(
81 ViewGroup.LayoutParams.FILL_PARENT, zoom ? ZOOMED_HEIGHT
82 : ViewGroup.LayoutParams.WRAP_CONTENT));
83 registerView(view, arg0);
84 update(view, arg0);
85 return view;
86 }
87
88 public int getCount() {
89 final int result = model.getModel().size();
90 return isEOPShown ? result + 1 : result;
91 }
92
93 private boolean isEOPShown = false;
94
95 /***
96 * <p>
97 * Checks if the EndOfPlaylist special item should be shown in the playlist.
98 * The special item is shown only when the list view:
99 * </p>
100 * <ul>
101 * <li>is modifiable</li>
102 * <li>has focus (this property however does not alter the return value of
103 * the function, i.e. the function can return <code>true</code> even if the
104 * list view does not have focus)</li>
105 * <li>is not in the {@link View#isInTouchMode() touch mode}</li>
106 * <li>the clipboard is not empty</li>
107 * </ul>
108 *
109 * @return <code>true</code> if the item should be shown, <code>false</code>
110 * otherwise.
111 */
112 private boolean isEOPProposed() {
113 if (owner.listener == null) {
114 return false;
115 }
116 if (owner.listener.isReadOnly()) {
117 return false;
118 }
119 if (owner.isInTouchMode()) {
120 return false;
121 }
122 if (TrackListClipboardObject.isEmpty(owner.getClipboard())) {
123 return false;
124 }
125 return true;
126 }
127
128 /***
129 * Checks if given item is the EOP item.
130 *
131 * @param position
132 * the position
133 * @return <code>true</code> if it is EOP, <code>false</code> otherwise.
134 */
135 public boolean isEOP(final int position) {
136 if (!isEOPShown) {
137 return false;
138 }
139 return position == model.getModel().size();
140 }
141
142 /***
143 * Marks the EOP item in the model.
144 */
145 public final static Object EOP_MODEL_MARKER = new Object();
146
147 public Object getItem(int position) {
148 if (isEOP(position)) {
149 return EOP_MODEL_MARKER;
150 }
151 return model.getModel().get(position);
152 }
153
154 public long getItemId(int position) {
155 return position;
156 }
157
158 /***
159 * The EOP state should be modified. Update if needed.
160 */
161 public void eopModified() {
162 setEOP(isEOPProposed());
163 }
164
165 /***
166 * The EOP state should be modified. Update if needed.
167 *
168 * @param visible
169 * if <code>true</code> then show EOP, if <code>false</code> then
170 * hide it.
171 */
172 public void setEOP(final boolean visible) {
173 if (visible != isEOPShown) {
174 notifyModified(visible);
175 }
176 }
177
178 /***
179 * Redraws items and reflects changes made to the
180 * {@link ModelHolder#getModel()} list.
181 */
182 public void notifyModified() {
183 notifyModified(null);
184 }
185
186 private void notifyModified(final Boolean eopProposal) {
187 isEOPShown = eopProposal != null ? eopProposal : isEOPProposed();
188 final boolean modelSizeChanged = modelSize != getCount();
189 if (modelSizeChanged) {
190 notifyDataSetChanged();
191 } else {
192 update();
193 }
194 modelSize = getCount();
195 }
196
197 private int modelSize = 0;
198
199 /***
200 * Invokes
201 * {@link IGestureListViewListener#update(GesturesListView, View, int, Object)}
202 * on all visible items.
203 */
204 private void update() {
205 final int modelSize = getCount();
206 for (final Iterator<Entry<Integer, View>> i = visibleIndices.entrySet()
207 .iterator(); i.hasNext();) {
208 final Entry<Integer, View> entry = i.next();
209 final Integer index = entry.getKey();
210 if (index >= modelSize) {
211 visibleViews.remove(entry.getValue());
212 i.remove();
213 } else {
214 update(entry.getValue(), index);
215 }
216 }
217 }
218
219 private void update(final View view, final int index) {
220 owner.listener.update(owner, view, index, getItem(index));
221 }
222
223 private final IdentityHashMap<View, Integer> visibleViews = new IdentityHashMap<View, Integer>();
224
225 private final Map<Integer, View> visibleIndices = new HashMap<Integer, View>();
226
227 private void registerView(final View view, final Integer index) {
228 final Integer oldIndex = visibleViews.get(view);
229 if (oldIndex != null) {
230 visibleIndices.remove(oldIndex);
231 }
232 final View oldView = visibleIndices.get(index);
233 if (oldView != null) {
234 visibleViews.remove(oldView);
235 }
236 visibleViews.put(view, index);
237 visibleIndices.put(index, view);
238 }
239
240 private boolean zoom = false;
241
242 /***
243 * Zooms or un-zooms the items.
244 *
245 * @param zoom
246 * <code>true</code> if zoom the view.
247 */
248 public void zoom(boolean zoom) {
249 this.zoom = zoom;
250 final int minHeight = zoom ? ZOOMED_HEIGHT
251 : ViewGroup.LayoutParams.WRAP_CONTENT;
252 for (final View view : visibleViews.keySet()) {
253 view.setLayoutParams(new AbsListView.LayoutParams(
254 ViewGroup.LayoutParams.FILL_PARENT, minHeight));
255 }
256 for (final View view : visibleIndices.values()) {
257 view.setLayoutParams(new AbsListView.LayoutParams(
258 ViewGroup.LayoutParams.FILL_PARENT, minHeight));
259 }
260 }
261 }