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 private int getZoomedHeight() {
76 return zoom == 0 ? ViewGroup.LayoutParams.WRAP_CONTENT : 16 + 16 * zoom;
77 }
78
79 public final View getView(int arg0, View arg1, ViewGroup arg2) {
80 final View view = arg1 != null ? arg1 : inflater.inflate(
81 owner.itemLayoutId, owner, false);
82 view.setLayoutParams(new AbsListView.LayoutParams(
83 ViewGroup.LayoutParams.FILL_PARENT, getZoomedHeight()));
84 registerView(view, arg0);
85 update(view, arg0);
86 return view;
87 }
88
89 public int getCount() {
90 final int result = model.getModel().size();
91 return isEOPShown ? result + 1 : result;
92 }
93
94 private boolean isEOPShown = false;
95
96 /***
97 * <p>
98 * Checks if the EndOfPlaylist special item should be shown in the playlist.
99 * The special item is shown only when the list view:
100 * </p>
101 * <ul>
102 * <li>is modifiable</li>
103 * <li>has focus (this property however does not alter the return value of
104 * the function, i.e. the function can return <code>true</code> even if the
105 * list view does not have focus)</li>
106 * <li>is not in the {@link View#isInTouchMode() touch mode}</li>
107 * <li>the clipboard is not empty</li>
108 * </ul>
109 *
110 * @return <code>true</code> if the item should be shown, <code>false</code>
111 * otherwise.
112 */
113 private boolean isEOPProposed() {
114 if (owner.listener == null) {
115 return false;
116 }
117 if (owner.listener.isReadOnly()) {
118 return false;
119 }
120 if (owner.isInTouchMode()) {
121 return false;
122 }
123 if (TrackListClipboardObject.isEmpty(owner.getClipboard())) {
124 return false;
125 }
126 return true;
127 }
128
129 /***
130 * Checks if given item is the EOP item.
131 *
132 * @param position
133 * the position
134 * @return <code>true</code> if it is EOP, <code>false</code> otherwise.
135 */
136 public boolean isEOP(final int position) {
137 if (!isEOPShown) {
138 return false;
139 }
140 return position == model.getModel().size();
141 }
142
143 /***
144 * Marks the EOP item in the model.
145 */
146 public final static Object EOP_MODEL_MARKER = new Object();
147
148 public Object getItem(int position) {
149 if (isEOP(position)) {
150 return EOP_MODEL_MARKER;
151 }
152 return model.getModel().get(position);
153 }
154
155 public long getItemId(int position) {
156 return position;
157 }
158
159 /***
160 * The EOP state should be modified. Update if needed.
161 */
162 public void eopModified() {
163 setEOP(isEOPProposed());
164 }
165
166 /***
167 * The EOP state should be modified. Update if needed.
168 *
169 * @param visible
170 * if <code>true</code> then show EOP, if <code>false</code> then
171 * hide it.
172 */
173 public void setEOP(final boolean visible) {
174 if (visible != isEOPShown) {
175 notifyModified(visible);
176 }
177 }
178
179 /***
180 * Redraws items and reflects changes made to the
181 * {@link ModelHolder#getModel()} list.
182 */
183 public void notifyModified() {
184 notifyModified(null);
185 }
186
187 private void notifyModified(final Boolean eopProposal) {
188 isEOPShown = eopProposal != null ? eopProposal : isEOPProposed();
189 final boolean modelSizeChanged = modelSize != getCount();
190 if (modelSizeChanged) {
191 notifyDataSetChanged();
192 } else {
193 update();
194 }
195 modelSize = getCount();
196 }
197
198 private int modelSize = 0;
199
200 /***
201 * Invokes
202 * {@link IGestureListViewListener#update(GesturesListView, View, int, Object)}
203 * on all visible items.
204 */
205 private void update() {
206 final int modelSize = getCount();
207 for (final Iterator<Entry<Integer, View>> i = visibleIndices.entrySet()
208 .iterator(); i.hasNext();) {
209 final Entry<Integer, View> entry = i.next();
210 final Integer index = entry.getKey();
211 if (index >= modelSize) {
212 visibleViews.remove(entry.getValue());
213 i.remove();
214 } else {
215 update(entry.getValue(), index);
216 }
217 }
218 }
219
220 private void update(final View view, final int index) {
221 owner.listener.update(owner, view, index, getItem(index));
222 }
223
224 private final IdentityHashMap<View, Integer> visibleViews = new IdentityHashMap<View, Integer>();
225
226 private final Map<Integer, View> visibleIndices = new HashMap<Integer, View>();
227
228 private void registerView(final View view, final Integer index) {
229 final Integer oldIndex = visibleViews.get(view);
230 if (oldIndex != null) {
231 visibleIndices.remove(oldIndex);
232 }
233 final View oldView = visibleIndices.get(index);
234 if (oldView != null) {
235 visibleViews.remove(oldView);
236 }
237 visibleViews.put(view, index);
238 visibleIndices.put(index, view);
239 }
240
241 private int zoom = 0;
242
243 /***
244 * Zooms or un-zooms the items.
245 *
246 * @param zoom
247 * <code>true</code> if zoom the view.
248 */
249 public void zoom(final int zoom) {
250 if (this.zoom == zoom) {
251 return;
252 }
253 this.zoom = zoom;
254 final int minHeight = getZoomedHeight();
255 for (final View view : visibleViews.keySet()) {
256 view.setLayoutParams(new AbsListView.LayoutParams(
257 ViewGroup.LayoutParams.FILL_PARENT, minHeight));
258 }
259 for (final View view : visibleIndices.values()) {
260 view.setLayoutParams(new AbsListView.LayoutParams(
261 ViewGroup.LayoutParams.FILL_PARENT, minHeight));
262 }
263 }
264 }