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
19 package sk.baka.ambient.views.gesturelist;
20
21 import java.util.Collections;
22 import java.util.List;
23
24 import sk.baka.ambient.AmbientApplication;
25 import sk.baka.ambient.R;
26 import sk.baka.ambient.collection.TrackMetadataBean;
27 import sk.baka.ambient.commons.Interval;
28 import android.view.KeyEvent;
29
30 /***
31 * Controls the {@link GesturesListView} component via the keypad.
32 *
33 * @author Martin Vysny
34 */
35 final class KeypadController implements KeyEvent.Callback {
36 /***
37 * The owning view.
38 */
39 private final GesturesListView ownerView;
40
41 /***
42 * Creates new controller instance.
43 *
44 * @param ownerView
45 * the owning view.
46 */
47 KeypadController(final GesturesListView ownerView) {
48 super();
49 this.ownerView = ownerView;
50 }
51
52 /***
53 * The mode we are currently in.
54 *
55 * @author Martin Vysny
56 */
57 private static enum Mode {
58 /***
59 * Normal mode.
60 */
61 NORMAL,
62 /***
63 * Selection mode.Items are selected by pressing the U , D arrow keys.
64 * To leave this mode, press Center button. Pressing the Center button
65 * copies selected items into the clipboard.
66 */
67 SELECTION,
68 /***
69 * Selected items are moved up/downwards the playlist as the U and D
70 * buttons are pressed. To leave this mode, press Center button.
71 */
72 MOVE;
73 }
74
75 /***
76 * Current working mode.
77 */
78 private Mode mode = Mode.NORMAL;
79
80 /***
81 * Previous key event. Serves for remembering gesture combo.
82 */
83 private int prevKeyCode = -1;
84
85 /***
86 * Initial index of the item when selecting items.
87 */
88 private int initialItem = -1;
89
90 private boolean isReadOnly() {
91 if (!ownerView.listener.isReadOnly()) {
92 return false;
93 }
94 ownerView.setMode(R.string.cannotModify, this, false);
95 initialItem = -1;
96 prevKeyCode = -1;
97 mode = Mode.NORMAL;
98 return true;
99 }
100
101 private boolean canHighlight() {
102 if (ownerView.listener.canHighlight()) {
103 return true;
104 }
105 ownerView.setMode(R.string.cannotSelect, this, false);
106 initialItem = -1;
107 prevKeyCode = -1;
108 mode = Mode.NORMAL;
109 return false;
110 }
111
112 public boolean onKeyUp(int keyCode, KeyEvent event) {
113 final int index = ownerView.getSelectedItemPosition();
114 final boolean isEOP = ownerView.isEOP(index);
115 switch (mode) {
116 case NORMAL:
117 switch (keyCode) {
118 case KeyEvent.KEYCODE_DPAD_CENTER:
119 boolean clearHint = true;
120 if (index >= 0) {
121 if (prevKeyCode == -1) {
122 if (!isEOP) {
123 ownerView.listener.itemActivated(index, ownerView
124 .getModel().getModel().get(index));
125 }
126 } else if (prevKeyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
127
128 clearHint = false;
129 if (!isReadOnly()) {
130 final List<TrackMetadataBean> tracks = getClipboard();
131 if (!tracks.isEmpty()) {
132 ownerView.setMode(R.string.paste, this, false);
133 ownerView.listener.dropItems(tracks, -1, -1,
134 index);
135 } else {
136 ownerView.setMode(R.string.clipboardEmpty,
137 this, false);
138 }
139 }
140 } else if (prevKeyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
141
142 if (ownerView.listener.canComputeItems()) {
143 clearHint = false;
144 ownerView.setMode(R.string.copyToClipboard, this,
145 false);
146 copy(ownerView.getModel().getHighlight(initialItem));
147 }
148 }
149 }
150 clearMode(clearHint);
151 return true;
152 case KeyEvent.KEYCODE_DPAD_RIGHT:
153 if (prevKeyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
154
155 if (canHighlight()) {
156 ownerView.getModel().highlight(
157 ownerView.getModel().getAllItems());
158 ownerView.getModel().notifyModified();
159 mode = Mode.SELECTION;
160 prevKeyCode = -1;
161 }
162 return true;
163 }
164 if (index >= 0) {
165 if (canHighlight()) {
166 prevKeyCode = KeyEvent.KEYCODE_DPAD_RIGHT;
167 ownerView.setMode(R.string.selection, this, true);
168 ownerView.getModel()
169 .highlight(Interval.fromItem(index));
170 ownerView.getModel().notifyModified();
171 initialItem = index;
172 }
173 return true;
174 }
175 break;
176 case KeyEvent.KEYCODE_DPAD_DOWN:
177 if (prevKeyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
178
179 if (canHighlight()) {
180 mode = Mode.SELECTION;
181 prevKeyCode = -1;
182 }
183
184
185 return false;
186 } else if (prevKeyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
187
188 if (!isReadOnly()) {
189 enterItemMoveMode(isEOP, true);
190 }
191 return true;
192 }
193 break;
194 case KeyEvent.KEYCODE_DPAD_UP:
195 if (prevKeyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
196
197 ownerView.setMode(R.string.deselect_all,
198 KeypadController.this, false);
199 ownerView.getModel().highlight(Interval.EMPTY);
200 ownerView.getModel().notifyModified();
201 clearMode(false);
202 return true;
203 } else if (prevKeyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
204
205 if (!isReadOnly()) {
206 enterItemMoveMode(isEOP, false);
207 }
208 return true;
209 }
210 break;
211 case KeyEvent.KEYCODE_DPAD_LEFT:
212 if (prevKeyCode == -1) {
213 prevKeyCode = KeyEvent.KEYCODE_DPAD_LEFT;
214 ownerView.setMode(ownerView.hintDeleteMovePasteId, this,
215 true);
216 return true;
217 } else if (prevKeyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
218
219 ownerView.setMode(ownerView.hintDeleteId, this, false);
220 ownerView.listener.removeItems(ownerView.getModel()
221 .getHighlight(true));
222 if (!ownerView.getHighlight().isEmpty()) {
223 ownerView.getModel().highlight(Interval.EMPTY);
224 ownerView.getModel().notifyModified();
225 }
226 clearMode(false);
227 return true;
228 }
229 break;
230 default:
231 clearMode();
232 }
233 break;
234 case SELECTION:
235
236
237
238
239 switch (keyCode) {
240 case KeyEvent.KEYCODE_DPAD_CENTER:
241 ownerView.setMode(R.string.copyToClipboard, this, false);
242 copy(ownerView.getModel().getHighlight(initialItem));
243 clearMode(false);
244 return true;
245 case KeyEvent.KEYCODE_DPAD_RIGHT:
246 ownerView.getModel().highlight(
247 ownerView.getModel().getAllItems());
248 ownerView.getModel().notifyModified();
249 return true;
250 }
251 break;
252 case MOVE:
253 switch (keyCode) {
254 case KeyEvent.KEYCODE_DPAD_CENTER:
255 clearMode();
256 return true;
257 case KeyEvent.KEYCODE_DPAD_UP: {
258 final Interval newInterval = moveItems(false);
259 ownerView.getModel().highlight(newInterval);
260 ownerView.getModel().notifyModified();
261 ownerView.setSelection(newInterval.start);
262 return true;
263 }
264 case KeyEvent.KEYCODE_DPAD_DOWN: {
265 final Interval newInterval = moveItems(true);
266 ownerView.getModel().highlight(newInterval);
267 ownerView.getModel().notifyModified();
268 ownerView.setSelection(newInterval.end);
269 return true;
270 }
271 }
272 break;
273 }
274 return false;
275 }
276
277 private Interval enterItemMoveMode(final boolean isEOP, final boolean down) {
278 if (!ownerView.canMove() || isEOP) {
279 clearMode(true);
280 return ownerView.getHighlight();
281 }
282 mode = Mode.MOVE;
283 ownerView.setMode(R.string.move, this, true);
284 final Interval result = moveItems(down);
285 ownerView.getModel().notifyModified();
286 return result;
287 }
288
289 private Interval moveItems(final boolean down) {
290 return ownerView.listener.moveItemsByOne(ownerView.getModel()
291 .getHighlight(true), down);
292 }
293
294 private void clearMode() {
295 clearMode(true);
296 }
297
298 private void clearMode(final boolean clearModeHint) {
299 mode = Mode.NORMAL;
300 if (clearModeHint)
301 ownerView.clearMode(this);
302 prevKeyCode = -1;
303 }
304
305 public boolean onKeyDown(int keyCode, android.view.KeyEvent event) {
306 return false;
307 }
308
309 public boolean onKeyMultiple(int keyCode, int arg1,
310 android.view.KeyEvent event) {
311
312 return false;
313 }
314
315 /***
316 * Invoked when the selection is changed.
317 */
318 synchronized void selectionChanged() {
319 switch (mode) {
320 case NORMAL:
321
322 return;
323 case SELECTION:
324 final int select = ownerView.getSelectedItemPosition();
325 if (select >= 0) {
326 ownerView.getModel().highlight(
327 Interval.fromRange(select, initialItem));
328 ownerView.getModel().notifyModified();
329 }
330 return;
331 case MOVE:
332 return;
333 }
334 }
335
336 /***
337 * Invoked by gestures list view if this controller should cancel all its
338 * work immediately as other controller is about to do something.
339 */
340 synchronized void cancelWork() {
341 clearMode();
342 }
343
344 /***
345 * Copies given interval into the clipboard. The operation does not block -
346 * it may retrieve the tracks in the background
347 *
348 * @param i
349 * the interval to copy.
350 */
351 public void copy(final Interval i) {
352 final boolean isLongOp = ownerView.listener.isComputeTracksLong(i);
353 final boolean isOnlineOp = ownerView.listener
354 .isComputeTracksOnlineOp(i);
355 final Runnable copyOp = new Runnable() {
356 /***
357 * Retrieved tracks.
358 */
359 private volatile List<TrackMetadataBean> tracks = null;
360
361 public void run() {
362 if (tracks == null) {
363 tracks = ownerView.listener.computeTracks(i);
364 if (Thread.currentThread().isInterrupted())
365 return;
366 if ((tracks == null) || tracks.isEmpty())
367 return;
368
369 tracks = Collections.synchronizedList(tracks);
370 AmbientApplication.getHandler().post(this);
371 } else {
372 if (ownerView.dragDropViews.isEmpty())
373 return;
374 final TrackListClipboardObject obj = new TrackListClipboardObject(
375 tracks, ownerView.dragDropViews);
376 ownerView.listener.setClipboard(obj);
377 }
378 }
379 };
380 if (isLongOp) {
381 AmbientApplication.getInstance().getBackgroundTasks().schedule(
382 copyOp,
383 GesturesListView.class,
384 isOnlineOp,
385 ownerView.getResources().getString(
386 R.string.copyingToClipboard));
387 } else {
388 try {
389 copyOp.run();
390 } catch (final Exception ex) {
391 if (!Thread.currentThread().isInterrupted()) {
392 final AmbientApplication app = AmbientApplication
393 .getInstance();
394 app.error(KeypadController.class, true, app
395 .getString(R.string.error), ex);
396 }
397 }
398 }
399 }
400
401 /***
402 * Returns the contents of the clipboard as a list of tracks.
403 *
404 * @return list of tracks, never <code>null</code>, may be empty.
405 */
406 public List<TrackMetadataBean> getClipboard() {
407 final TrackListClipboardObject obj = ownerView.getClipboard();
408 if (obj == null)
409 return Collections.emptyList();
410 if (!obj.isTarget(ownerView))
411 return Collections.emptyList();
412 return obj.getObjects();
413 }
414 }