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.activity.main;
19
20 import java.text.ParseException;
21 import java.util.ArrayList;
22 import java.util.List;
23
24 import sk.baka.ambient.ConfigurationBean;
25 import sk.baka.ambient.I18n;
26 import sk.baka.ambient.IApplicationListener;
27 import sk.baka.ambient.R;
28 import sk.baka.ambient.collection.TrackMetadataBean;
29 import sk.baka.ambient.commons.Interval;
30 import sk.baka.ambient.commons.TagFormatter;
31 import sk.baka.ambient.views.gesturelist.GesturesListView;
32 import sk.baka.ambient.views.gesturelist.MutableListAdapter;
33 import android.app.Activity;
34 import android.graphics.Typeface;
35 import android.text.SpannableStringBuilder;
36 import android.text.Spanned;
37 import android.text.style.StyleSpan;
38 import android.view.View;
39 import android.widget.ImageView;
40 import android.widget.TextView;
41
42 /***
43 * Groups functionality for a controller managing a playlist.
44 *
45 * @author Martin Vysny
46 */
47 public abstract class AbstractPlaylistController extends AbstractListController
48 implements IApplicationListener {
49 /***
50 * The playlist view id.
51 */
52 public static final int PLAYLIST_VIEW_ID = android.R.id.list;
53
54 /***
55 * <p>
56 * Creates new controller.
57 * </p>
58 * <p>
59 * The contract: the extending class MUST invoke the {@link #initialize()}
60 * after it is done initializing.
61 * </p>
62 *
63 * @param mainViewId
64 * the view whose visibility is controlled.
65 * @param listViewId
66 * the id of the {@link GesturesListView}
67 * @param mainActivity
68 */
69 public AbstractPlaylistController(final int mainViewId, int listViewId,
70 Activity mainActivity) {
71 super(mainViewId, listViewId, mainActivity);
72 try {
73 formatter = new TagFormatter(
74 app.getStateHandler().getConfig().playlistFormat);
75 } catch (ParseException e) {
76 throw new RuntimeException(e);
77 }
78 listView.dragDropViews.clear();
79 listView.dragDropViews.add(listView);
80 model = listView.getModel().getModel();
81 }
82
83 /***
84 * Overriding classes must invoke this method at the end of their
85 * constructors.
86 */
87 protected final void initialize() {
88 update(Interval.EMPTY);
89 }
90
91 /***
92 * The contract: when overriding this method, be sure to call this super
93 * method after you are done deleting items. The adapter must be
94 * invalidated, to prevent drawing old data.
95 *
96 * @param remove
97 * the interval to remove
98 */
99 @Override
100 public void removeItems(Interval remove) {
101 update(Interval.EMPTY);
102 }
103
104 /***
105 * Formats playlist items.
106 */
107 private TagFormatter formatter;
108
109 /***
110 * The model, containing Strings - formatted track names.
111 */
112 private final List<Object> model;
113
114 /***
115 * Retrieves a track from given position.
116 *
117 * @param index
118 * the index
119 * @return the track instance, never <code>null</code>.
120 */
121 protected abstract TrackMetadataBean getTrack(final int index);
122
123 @Override
124 protected void recomputeListItems() {
125 final boolean nullEqualFields = app.getStateHandler().getConfig().dontRepeatSameTag;
126 model.clear();
127 final int playlistSize = getPlaylistSize();
128 for (int i = 0; i < playlistSize; i++) {
129 final TrackMetadataBean row = getTrack(i);
130 final TrackMetadataBean prevRow = i == 0 ? TrackMetadataBean.EMPTY
131 : getTrack(i - 1);
132 final TrackMetadataBean diff = nullEqualFields ? row
133 .nullsEqualFields(prevRow, true) : row;
134 final String track = formatter.format(diff);
135 model.add(track);
136 }
137 }
138
139 /***
140 * Returns the underlying playlist size.
141 *
142 * @return the playlist size.
143 */
144 protected abstract int getPlaylistSize();
145
146 private final int currentTrackColor = mainActivity.getResources().getColor(
147 R.color.playlist_currenttrack);
148
149 private final int currentHighlightedColor = mainActivity.getResources()
150 .getColor(R.color.currenttrack_highlight);
151
152 private final int textColor = mainActivity.getResources().getColor(
153 R.color.textcolor);
154
155 private final int textTranslucentColor = mainActivity.getResources()
156 .getColor(R.color.texttranslucent);
157
158 private final int historyTrackColor = mainActivity.getResources().getColor(
159 R.color.dynamicplaylist_history);
160
161 private final String eop = mainActivity.getString(R.string.eop);
162
163 private final String playlistEmpty = mainActivity
164 .getString(R.string.playlist_empty);
165
166 public final void update(GesturesListView listView, View itemView,
167 int index, Object model) {
168 final boolean isEOP = model == MutableListAdapter.EOP_MODEL_MARKER;
169 final boolean currentlyPlayed = isEOP ? false
170 : isCurrentlyPlayedTrack(index);
171 final boolean isHistoryTrack = isEOP ? false : isHistoryTrack(index);
172 if (isEOP) {
173 itemView.setBackgroundColor(0);
174 } else {
175
176 final boolean highlighted = listView.getHighlight().contains(index);
177 final int bgColor;
178 if (currentlyPlayed && highlighted) {
179 bgColor = currentHighlightedColor;
180 } else if (currentlyPlayed) {
181 bgColor = currentTrackColor;
182 } else if (highlighted) {
183 bgColor = highlightColor;
184 } else {
185 bgColor = 0;
186 }
187 itemView.setBackgroundColor(bgColor);
188 }
189
190 final TextView text = (TextView) itemView
191 .findViewById(R.id.playlist_item_text);
192 final String caption;
193 if (isEOP) {
194 caption = listView.getCount() > 1 ? eop : playlistEmpty;
195 } else {
196 caption = (String) model;
197 }
198 if (currentlyPlayed) {
199 final SpannableStringBuilder b = new SpannableStringBuilder(caption);
200 b.setSpan(new StyleSpan(Typeface.ITALIC), 0, caption.length(),
201 Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
202 text.setText(b);
203 } else {
204 text.setText(caption);
205 }
206 text.setTextColor(isEOP ? textTranslucentColor
207 : isHistoryTrack ? historyTrackColor : textColor);
208
209 final int queue = isEOP ? 0 : getQueueNumber(index);
210 final TextView queueView = (TextView) itemView
211 .findViewById(R.id.playlist_item_queue);
212 queueView.setVisibility(queue == 0 ? View.GONE : View.VISIBLE);
213 if (queue != 0) {
214 queueView.setText(String.valueOf(queue));
215 }
216
217 final ImageView trackIcon = (ImageView) itemView
218 .findViewById(R.id.playlist_item_trackicon);
219 final TrackMetadataBean track = isEOP ? null : getTrack(index);
220 if (isEOP || track.isLocal()) {
221 trackIcon.setVisibility(View.GONE);
222 } else {
223 trackIcon.setVisibility(View.VISIBLE);
224 switch (track.getOrigin()) {
225 case Magnatune:
226 trackIcon.setImageResource(R.drawable.trackicon_magnatune);
227 break;
228 case Shoutcast:
229 trackIcon.setImageResource(R.drawable.trackicon_shoutcast);
230 break;
231 case SkreemR:
232 case Ampache:
233 trackIcon.setImageResource(R.drawable.trackicon_http);
234 break;
235 case LocalFs:
236
237 }
238 }
239 }
240
241 /***
242 * Checks if this track is a track from a dynamic playlist history.
243 *
244 * @param index
245 * the track index.
246 * @return <code>true</code> if history track, <code>false</code> otherwise.
247 */
248 protected abstract boolean isHistoryTrack(int index);
249
250 /***
251 * Returns queue status of given track.
252 *
253 * @param index
254 * the track index
255 * @return queue number or 0 if the track is not queued.
256 */
257 protected abstract int getQueueNumber(int index);
258
259 /***
260 * Checks if the track at given index is currently being played.
261 *
262 * @param index
263 * the track index
264 * @return <code>true</code> if it is being played, <code>false</code>
265 * otherwise.
266 */
267 protected abstract boolean isCurrentlyPlayedTrack(int index);
268
269 @Override
270 public final boolean canComputeItems() {
271 return true;
272 }
273
274 @Override
275 public boolean isComputeTracksLong(Interval interval) {
276 return false;
277 }
278
279 @Override
280 public boolean isComputeTracksOnlineOp(Interval interval) {
281 return false;
282 }
283
284 @Override
285 public boolean isReadOnly() {
286 return false;
287 }
288
289 public void configChanged(ConfigurationBean config) {
290 try {
291 formatter = new TagFormatter(
292 app.getStateHandler().getConfig().playlistFormat);
293 } catch (ParseException e) {
294 throw new RuntimeException(e);
295 }
296 update(null);
297 }
298
299 @Override
300 public List<TrackMetadataBean> computeTracks(Interval highlight) {
301 final List<TrackMetadataBean> result = new ArrayList<TrackMetadataBean>(
302 highlight.length);
303 for (int i = highlight.start; i <= highlight.end; i++) {
304 result.add(getTrack(i));
305 }
306 return result;
307 }
308
309 public void clipboardChanged() {
310 listView.clipboardChanged();
311 }
312
313 public void offline(boolean offline) {
314
315 }
316
317 @Override
318 public String getHint(Interval highlight) {
319 return I18n.format(I18n.newTracks(highlight.length));
320 }
321
322 public String toString(Object model) {
323
324 throw new Error();
325 }
326 }