/*
* Copyright © 2013 Nokia Corporation. All rights reserved.
* Nokia and Nokia Connecting People are registered trademarks of Nokia Corporation.
* Oracle and Java are trademarks or registered trademarks of Oracle and/or its
* affiliates. Other product and company names mentioned herein may be trademarks
* or trade names of their respective owners.
* See LICENSE.TXT for license information.
*/
package com.nokia.example.favouriteartists;
import java.util.Vector;
import javax.microedition.lcdui.*;
import com.nokia.example.favouriteartists.tool.Log;
import com.nokia.mid.ui.frameanimator.*;
import com.nokia.mid.ui.gestures.*;
/**
* Favourite artists view. Shows current favourite artists in a grid.
* <p>
* This class illustrates the use of Gestures API and Frame Animator API.
* Points of interests are: the constructor {@link #FavouriteArtistsView(CommandHandler, Display, ImageProvider)},
* Gestures API callback {@link #gestureAction(Object, GestureInteractiveZone, GestureEvent)} and
* Frame Animator API callback {@link #animate(FrameAnimator, int, int, short, short, short, boolean)}
* <p>
* Gestures used in this example:
* {@link GestureInteractiveZone#GESTURE_TAP}
* {@link GestureInteractiveZone#GESTURE_LONG_PRESS}
* {@link GestureInteractiveZone#GESTURE_DRAG}
* {@link GestureInteractiveZone#GESTURE_DROP}
* {@link GestureInteractiveZone#GESTURE_FLICK}
* <p>
* From Frame Animator API, {@link FrameAnimator#kineticScroll(int, int, int, float)} is used
* with {@link FrameAnimator#FRAME_ANIMATOR_FREE_ANGLE} to achieve two-dimensional kinetic scrolling
* of the grid (used for flick gesture).
* <p>
* The grid can be scrolled with drag and flick gestures. A single item occupies
* most of the screen area. After a scroll movement the view focuses on the nearest
* item, see {@link FavouriteArtistsView.FocusMoveThread}. Also, a single tap while scrolling will focus the item.
* When item is focused, it can be selected with either single tap or long press, see
* {link {@link #handleSelectEvent(GestureEvent)}.
* Item selection will open up rating view {@link RatingView}.
*/
public class FavouriteArtistsView extends Canvas implements GestureListener, FrameAnimatorListener{
// Constants
/** Horizontal margin between item top/bottom and screen top/bottom */
private static final int ITEM_H_MARGIN = 20;
/** Vertical margin between item top/bottom and screen top/bottom */
private static final int ITEM_V_MARGIN = 20;
// Member data
/** Display. */
private Display display;
/** Command handler. */
private CommandHandler commandHandler;
/** For retrieving images. */
ImageProvider imageProvider;
/** Short tap action. */
private short tapActionId;
/** Long press action */
private short longPressActionId;
/** Visible area's top left x-coordinate (i.e. where in the grid are we) */
private int xOffset;
/** Visible area's top left y-coordinate (i.e. where in the grid are we) */
private int yOffset;
/** Width of the grid, i.e. the scrollable area. */
private int gridWidth;
/** Height of the grid, i.e. the scrollable area. */
private int gridHeight;
/** Number of columns in the grid, calculated from item count. */
private int columns;
/** Number of rows in the grid, calculated from item count. */
private int rows;
/** Width of a single item, calculated from Canvas width. */
private int itemWidth;
/** Height of a single item, calculated from Canvas width. */
private int itemHeight;
/** The selected item, only valid during select action handling. */
private GridItem selectedItem;
/** Grid items. */
private Vector items;
/** The gesture interactive zone registered to this Canvas. */
private GestureInteractiveZone zone;
/** FrameAnimator instance for animating list scrolling. */
private FrameAnimator animator;
/** Flag for defining whether scrolling is active (gestures are handled differently while scrolling). */
private boolean scrollingActive;
/** Pending select gesture event that is stored for the duration of the select delay. */
private int pendingGestureEvent;
/** Select delay. */
private SelectDelay selectDelay;
/** Thread used for the select delay. Reference kept to be able to cancel. */
private Thread delayThread;
/** Thread used for focus move animation. Reference kept to be able to cancel.*/
Thread focusMoveThread;
/** Runnable used for focus move. */
FocusMoveThread focusMoveRunnable;
/** This is used to delay starting of focus move while dragging. */
Thread dragDelayThread;
// Inner classes
/**
* Delay for showing selection focus to user before initiating action.
*/
private class SelectDelay implements Runnable {
/**
* @see java.lang.Runnable#run()
*/
public void run() {
if (Log.TEST) Log.note("[SelectDelay#run]-->");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
if (Log.TEST) Log.note("[SelectDelay#run] interrupted");
return;
}
if(pendingGestureEvent > 0){
if (Log.TEST) Log.note("[SelectDelay#run] handling pending gesture");
switch (pendingGestureEvent) {
case GestureInteractiveZone.GESTURE_TAP:{
handleTap();
break;
}
case GestureInteractiveZone.GESTURE_LONG_PRESS:{
handleLongPress();
break;
}
default:
if (Log.TEST) Log.note("[SelectDelay#run] wrong type!");
break;
}
selectedItem.setSelected(false);
selectedItem = null;
pendingGestureEvent = 0;
delayThread = null;
}
}
}
/**
* Drag delay thread. This is used to give the user a moment to drag some more before
* starting the automatic focus move.
*/
private class DragDelayThread implements Runnable{
public void run() {
if (Log.TEST) Log.note("[DragDelayThread#run]-->");
try {
Thread.sleep(500);
centerOnClosestItem(true);
} catch (InterruptedException e) {
if (Log.TEST) Log.note("[DragDelayThread#run] interrupted");
return;
}
}
}
/**
* Used to animate focus centering to an item after flick/drag.
*/
private class FocusMoveThread implements Runnable{
// Constants
/** Amount of pixels to move per one animation step */
private static final int PIXELS_PER_STEP = 4;
// Member data
/** X-coordinate target when focusing to item */
private int xTarget;
/** Y-coordinate target when focusing to item */
private int yTarget;
/** How many pixels to move the x-coordinate per one animation frame */
private int xStep;
/** How many pixels to move the y-coordinate per one animation frame */
private int yStep;
/** Number of steps to animate in x-direction */
private int stepCountX;
/** Number of steps to animate in y-direction */
private int stepCountY;
/** X-coordinate distance to target. */
private int xDistance;
/** Y-coordinate distance to target. */
private int yDistance;
/** Step counter. */
private int step = 1;
/** Defines whether to continue the animation loop. */
private boolean keepRunning;
// Methods
/**
* Constructor.
*
* @param xTarget The target x-coordinate.
* @param yTarget The target y-coordinate.
* @param xStep The x-coordinate step amount in pixels.
* @param yStep The y-coordinate step amount in pixels.
*/
public FocusMoveThread(int xTarget, int yTarget/*, int xStep, int yStep*/){
if (Log.TEST) Log.note("[FocusMoveThread#run]-->");
this.xTarget = xTarget;
this.yTarget = yTarget;
xDistance = Math.abs(xTarget - xOffset);
yDistance = Math.abs(yTarget - yOffset);
// Calculate step counts
stepCountX = xDistance / PIXELS_PER_STEP;
stepCountY = yDistance / PIXELS_PER_STEP;
if(stepCountX < 1){
stepCountX = 1;
}
if(stepCountY < 1){
stepCountY = 1;
}
// Calculate the size of x/y steps
xStep = xTarget < xOffset ? -PIXELS_PER_STEP : PIXELS_PER_STEP;
yStep = yTarget < yOffset ? -PIXELS_PER_STEP : PIXELS_PER_STEP;
if (Log.TEST) Log.note("[FocusMoveThread#run]"
+ " xTarget: "
+ xTarget
+ " yTarget: "
+ yTarget
+ " xDistance: "
+ xDistance
+ " yDistance: "
+ yDistance
+ " stepCountX: "
+ stepCountX
+ " stepCountY: "
+ stepCountY
+ " xStep: "
+ xStep
+ " yStep: "
+ yStep);
}
/**
* Stops the animation.
*/
public void quit(){
keepRunning = false;
}
/**
* @see java.lang.Runnable#run()
*/
public void run() {
keepRunning = true;
while(keepRunning){
if (Log.TEST) Log.note("[FocusMoveThread#run]"
+ " step: "
+ step);
scrollingActive = true;
try {
Thread.sleep(10);
} catch (InterruptedException e) {
if (Log.TEST) Log.note("[FocusMoveThread#run] interrupted");
scrollingActive = false;
return;
}
if( stepCountX > stepCountY ){
// update x coord
xOffset += xStep;
if(step % (stepCountX / stepCountY) == 0 && yOffset != yTarget){
// update y coord as well
yOffset += yStep;
}
} else if (stepCountY > stepCountX){
// update y coord
yOffset += yStep;
if(step % (stepCountY / stepCountX) == 0 && xOffset != xTarget){
// update x coord as well
xOffset += xStep;
}
} else{
xOffset += xStep;
yOffset += yStep;
}
// Check that x-coordinate does not go past target
if(xStep > 0){
if(xOffset > xTarget){
xOffset = xTarget;
}
} else {
if(xOffset < xTarget){
xOffset = xTarget;
}
}
// Check that y-coordinate does not go past target
if(yStep > 0){
if(yOffset > yTarget){
yOffset = yTarget;
}
} else {
if(yOffset < yTarget){
yOffset = yTarget;
}
}
// Make sure that we're on target if this is the last step
if(step >= (stepCountX > stepCountY ? stepCountX : stepCountY)){
xOffset = xTarget;
yOffset = yTarget;
}
// Stop the loop when were on target
if( xOffset == xTarget && yOffset == yTarget){
keepRunning = false;
}
repaint();
step++;
}
scrollingActive = false;
}
}
// Methods
/**
* Constructor.
*
* @param commandHandler For command handling.
* @param display For color retrieval.
* @param imageProvider For image retrieval.
* @throws FavouriteArtistsException
*/
public FavouriteArtistsView(CommandHandler commandHandler, Display display, ImageProvider imageProvider)
throws FavouriteArtistsException {
if (Log.TEST) Log.note("[FavouriteArtistsView#FavouriteArtistsView]-->");
this.commandHandler = commandHandler;
this.display = display;
this.imageProvider = imageProvider;
selectDelay = new SelectDelay();
// First create a GestureInteractiveZone. The GestureInteractiveZone class is used to define an
// area of the screen that reacts to a set of specified gestures.
// The parameter GESTURE_ALL means that we want events for all gestures.
zone = new GestureInteractiveZone(GestureInteractiveZone.GESTURE_DRAG |
GestureInteractiveZone.GESTURE_DROP |
GestureInteractiveZone.GESTURE_FLICK |
GestureInteractiveZone.GESTURE_LONG_PRESS |
GestureInteractiveZone.GESTURE_LONG_PRESS_REPEATED |
GestureInteractiveZone.GESTURE_TAP
);
// Next the GestureInteractiveZone is registered to registration manager. Note that multiple zones
// can be registered for a single container, reference to the affected zone is passed in the event callback.
if(GestureRegistrationManager.register(this, zone) != true){
// Throw an exception if register fails
throw new FavouriteArtistsException("GestureRegistrationManager.register() failed!");
}
// Add a listener for gesture events for this container. A container can be either a Canvas or a CustomItem.
// In this example, only one gesture zone that covers the whole canvas is used.
GestureRegistrationManager.setListener(this, this);
// Create a FrameAnimator instance. In this class, the frame animator is used to animate grid scrolling
// for flick gestures.
animator = new FrameAnimator();
// Use default values for maxFps an maxPps (zero parameter means that default is used).
final short maxFps = 0;
final short maxPps = 0;
// Register the FrameAnimator. Animation uses the initial x & y coordinates as a starting point
// i.e. the animate() callback will give coordinates in relation to this point.
// In this example only the delta values are used from the callback so the initial values don't make much difference.
if(animator.register(0, 0, maxFps, maxPps, this) != true){
// Throw an exception if register fails
throw new FavouriteArtistsException("FrameAnimator.register() failed!");
}
// Define actions that are initiated for tap and long press gestures
tapActionId = Actions.SHOW_RATING;
longPressActionId = Actions.SHOW_RATING;
// Add commands
addCommand(new ActionCommand(Actions.EXIT_MIDLET, "Exit", Command.EXIT, 0));
addCommand(new ActionCommand(Actions.SHOW_ADD_FAVOURITE, "Add", Command.SCREEN, 1));
addCommand(new ActionCommand(Actions.ARRANGE_FAVOURITES, "Arrange", Command.SCREEN, 1));
// Delegate command handling to separate class.
setCommandListener(commandHandler);
items = new Vector();
updateItems();
}
/**
* Calculates grid size and updates grid item coordinates.
*/
private void updateItems(){
if (Log.TEST) Log.note("[FavouriteArtistsView#updateItems]-->");
// Reset offsets because previous ones might not be valid anymore (e.g. out of grid area)
xOffset = 0;
yOffset = 0;
// First determine a suitable number of rows and columns based on item count
columns = 1;
while(rows * columns < items.size()){
if(rows < columns){
rows++;
} else {
columns++;
}
}
// Calculate item size based on Canvas size, one item is intended to be shown on screen at a time
itemWidth = getWidth() - 2 * ITEM_H_MARGIN;
itemHeight = getHeight() - 2 * ITEM_V_MARGIN;
// Calculate grid size, this sets the limits to the area that can be scrolled
gridWidth = columns * (itemWidth + ITEM_H_MARGIN * 2);
gridHeight = rows * (itemHeight + ITEM_V_MARGIN * 2);
// Set coordinates for each item.
// These coordinates are in relation to the grid, actual drawing coordinates are
// derived from these and current x/y offset of visible area
int itemX = ITEM_H_MARGIN;
int itemY = ITEM_V_MARGIN;
for (int i = 0; i < items.size(); i++) {
GridItem item = (GridItem) items.elementAt(i);
item.setRect(itemX, itemY, itemWidth, itemHeight);
if(i > 0 && (i + 1) % columns == 0){
itemY += itemHeight + ITEM_V_MARGIN * 2;
itemX = ITEM_H_MARGIN;
} else {
itemX += itemWidth + ITEM_H_MARGIN * 2;
}
}
}
/**
* Updates the view with new favourite data.
* @param favouriteDatas New data array.
* @param repaint If true, then a repaint will be requested.
*/
public void updateView(FavouriteData[] favouriteDatas, boolean repaint){
if (Log.TEST) Log.note("[FavouriteArtistsView#updateView]-->");
if(favouriteDatas == null){
return;
}
// Clear the old items first
items.removeAllElements();
// Create grid items from data and add them
for(int i = 0; i < favouriteDatas.length; i++){
FavouriteData favData = favouriteDatas[i];
GridItem favItem = new GridItem(display, imageProvider);
favItem.setIcon(imageProvider.getImage(favData.getImageFilename()));
favItem.setFavData(favData);
items.addElement(favItem);
}
updateItems();
if(repaint){
repaint();
}
}
protected void paint(Graphics g) {
if (Log.TEST) Log.note("[FavouriteArtistsView#paint]-->");
// Draw background
g.setColor(display.getColor(Display.COLOR_BACKGROUND));
g.fillRect(0, 0, getWidth(), getHeight());
boolean xOverBounds = false;
boolean yOverBounds = false;
if(xOffset < 0){
xOffset = 0;
xOverBounds = true;
}else if(xOffset + getWidth() > gridWidth){
xOffset = gridWidth - getWidth();
xOverBounds = true;
}
if(yOffset < 0){
yOffset = 0;
yOverBounds = true;
} else if(yOffset + getHeight() > gridHeight){
yOffset = gridHeight - getHeight();
yOverBounds = true;
}
// Stop scrolling if both x and y are over bounds
// NOTE: if x or y alone would merit a scrolling stop, then scrolling
// near grid borders would have bad UX (e.g. scroll would stop because of tiny
// y direction change when user wants to scroll in horizontally).
// This affects flicking only.
if (scrollingActive && xOverBounds && yOverBounds) {
stopScrolling(true);
}
// Check which items are visible and draw only those.
for (int i = 0; i < items.size(); i++) {
GridItem item = (GridItem) items.elementAt(i);
if(item.isVisible(xOffset, yOffset, getWidth(), getHeight())){
item.paint(g, xOffset, yOffset);
}
}
}
/**
* Center on the item that's closest to the given coordinates.
*
* @param animate Determines whether the focus movement will be animated.
*/
private void centerOnClosestItem(int x, int y, boolean animate){
if (Log.TEST) Log.note("[FavouriteArtistsView#centerOnClosestItem]--> x: " + x + " y: " + y);
// Cancel any existing focus move thread
if(focusMoveThread != null){
focusMoveRunnable.quit();
focusMoveThread = null;
}
// Find the closest item
GridItem closestItem = null;
int closestItemDistance = Integer.MAX_VALUE;
for (int i = 0; i < items.size(); i++) {
GridItem item = (GridItem) items.elementAt(i);
int itemCenterX = item.getCenterX();
int itemCenterY = item.getCenterY();
int itemDistance = Math.abs(itemCenterX - x) +
Math.abs(itemCenterY - y);
if (Log.TEST) Log.note("[FavouriteArtistsView#centerOnClosestItem] item center x: " + itemCenterX
+ " item center y: " + itemCenterY + " item distance: " + itemDistance);
if(itemDistance < closestItemDistance){
closestItemDistance = itemDistance;
closestItem = item;
}
}
centerOnItem(closestItem, animate);
}
/**
* Centers on the given item.
*
* @param item The item to center on.
* @param animate Determines whether the focus movement will be animated.
*/
private void centerOnItem(GridItem item, boolean animate){
if (Log.TEST) Log.note("[FavouriteArtistsView#centerOnItem]-->");
if(item == null){
return;
}
int xTarget = item.getX() - ITEM_H_MARGIN;
int yTarget = item.getY() - ITEM_V_MARGIN;
if(animate){
// Animate the movement
scrollingActive = true;
focusMoveRunnable = new FocusMoveThread(xTarget, yTarget);
focusMoveThread = new Thread(focusMoveRunnable);
focusMoveThread.start();
} else {
// Just set the new coordinates
xOffset = xTarget;
yOffset = yTarget;
}
}
/**
* Center on the item that's closest to the center of the screen.
*
* @param animate Determines whether focus movement will be animated.
*/
private void centerOnClosestItem(boolean animate){
if (Log.TEST) Log.note("[FavouriteArtistsView#centerOnClosestItem]");
int screenCenterX = xOffset + getWidth()/2;
int screenCenterY = yOffset + getHeight()/2;
centerOnClosestItem(screenCenterX, screenCenterY, animate);
}
/**
* @see com.nokia.mid.ui.frameanimator.FrameAnimatorListener#animate(com.nokia.mid.ui.frameanimator.FrameAnimator, int, int, short, short, short, boolean)
*/
public void animate(FrameAnimator animator, int x, int y, short delta, short deltaX, short deltaY, boolean lastFrame) {
if (Log.TEST) Log.note("[FavouriteArtistsView#animate]"
+ " x: "
+ x
+ " y: "
+ y
+ " delta: "
+ delta
+ "deltaX: "
+ deltaX
+ " deltaY: "
+ deltaY);
//NOTE: this affects only flicks, drag is not animated with FrameAnimator
// We want to drag the grid, so movement goes to opposite direction
xOffset -= deltaX;
yOffset -= deltaY;
// Scrolling is no longer active if this is the last frame
scrollingActive = !lastFrame;
repaint();
if(lastFrame == true){
// Start focus move,
centerOnClosestItem(true);
}
}
/**
* Stops scrolling.
*
* @param center Whether to start centering.
*/
private void stopScrolling(boolean center) {
scrollingActive = false;
animator.stop();
if(center == true){
centerOnClosestItem(true);
}
}
/**
* @see com.nokia.mid.ui.gestures.GestureListener#gestureAction(java.lang.Object, com.nokia.mid.ui.gestures.GestureInteractiveZone, com.nokia.mid.ui.gestures.GestureEvent)
*/
public void gestureAction(Object container, GestureInteractiveZone zone, GestureEvent event) {
if (Log.TEST) Log.note("[FavouriteArtistsView#gestureAction]-->");
// Block gesture handling if already handling one,
// this may happen due to delayed handling of events (delay is needed for showing focus on selected item)
if(pendingGestureEvent > 0){
if (Log.TEST) Log.note("[FavouriteArtistsView#gestureAction] pending gesture -> return");
return;
}
// Stop any drag delay or focus move threads before handling the gesture
if(dragDelayThread != null){
if (Log.TEST) Log.note("[FavouriteArtistsView#gestureAction] interrupting drag delay thread");
dragDelayThread.interrupt();
dragDelayThread = null;
}
if(focusMoveThread != null){
if (Log.TEST) Log.note("[FavouriteArtistsView#gestureAction] quitting focus move thread");
focusMoveRunnable.quit();
focusMoveRunnable = null;
focusMoveThread = null;
}
switch (event.getType()) {
case GestureInteractiveZone.GESTURE_DRAG:{
if (Log.TEST) Log.note("[FavouriteArtistsView#gestureAction] drag x: "
+ event.getDragDistanceX()
+ " y: "
+ event.getDragDistanceY());
// In this example the drag gesture is directly used for altering the
// visible area coordinates. Note that only the delta values are used. The reason for this
// is that gesture event coordinates are screen coordinates and xOffset/yOffset are grid coordinates.
// NOTE: Drag gestures are received in very rapid succession, the "whole"
// drag (e.g. what the user would perceive as a complete drag gesture) usually ends when a
// GESTURE_DROP event is received.
stopScrolling(false);
// We want to drag the grid, so movement goes to opposite direction
xOffset -= event.getDragDistanceX();
yOffset -= event.getDragDistanceY();
repaint();
break;
}
case GestureInteractiveZone.GESTURE_TAP:{
if (Log.TEST) Log.note("[FavouriteArtistsView#gestureAction] tap");
// Tap gesture is used for item selection.
if (scrollingActive) {
stopScrolling(false);
// First center on the item if scrolling was active.
// Note that here we derive the grid coordinates from the event
// coordinates and current offset.
centerOnClosestItem(xOffset + event.getStartX(),
yOffset + event.getStartY(), true);
} else if (tapActionId != Actions.INVALID_ACTION_ID) {
// Handle gesture only if there's an action set for it
handleSelectEvent(event);
}
break;
}
case GestureInteractiveZone.GESTURE_DROP: {
if (Log.TEST) Log.note("[FavouriteArtistsView#gestureAction] drop");
// Drop indicates the end of a "whole" drag (see above).
stopScrolling(false);
// Start the drag delay thread, this delay allows the user some time to continue
// dragging before initiating automatic focus movement.
dragDelayThread = new Thread(new DragDelayThread());
dragDelayThread.start();
break;
}
case GestureInteractiveZone.GESTURE_FLICK:{
if (Log.TEST) Log.note("[FavouriteArtistsView#gestureAction] flick");
// This gives the angle in radians.
float angle = event.getFlickDirection();
if (Log.TEST) Log.note("[FavouriteArtistsView#gestureAction] flick angle: " + angle);
// Because we're using free angle we want the whole flick speed instead of just x or y.
int startSpeed = event.getFlickSpeed();
int direction = FrameAnimator.FRAME_ANIMATOR_FREE_ANGLE;
// This affects the deceleration of the scroll.
int friction = FrameAnimator.FRAME_ANIMATOR_FRICTION_LOW;
scrollingActive = true;
// Start the scroll, animate() callbacks will follow.
animator.kineticScroll(startSpeed, direction, friction, angle);
break;
}
case GestureInteractiveZone.GESTURE_LONG_PRESS:{
if (Log.TEST) Log.note("[FavouriteArtistsView#gestureAction] long press");
// Long press handling has mostly the same implementation as tap.
if (scrollingActive) {
stopScrolling(false);
centerOnClosestItem(xOffset + event.getStartX(),
yOffset + event.getStartY(), true);
} else if (longPressActionId != Actions.INVALID_ACTION_ID){
// Handle gesture only if there's an action set for it
handleSelectEvent(event);
}
break;
}
default:
break;
}
}
/**
* Getter for selected item. Since the UI is touch based there is no selected item
* all the time. In this example the selected item is only valid for the duration of action handling
*
* @return The selected item.
*/
public FavouriteData getSelectedItem() {
if (Log.TEST) Log.note("[FavouriteArtistsView#getSelectedItem]-->");
return selectedItem.getFavData();
}
/**
* Common handler function for selection events.
*
* @param event Gesture event.
*/
private void handleSelectEvent(GestureEvent event){
if (Log.TEST) Log.note("[FavouriteArtistsView#handleSelectEvent]-->");
// Find the selected item if any.
GridItem selected = getItemAt(event.getStartX(), event.getStartY());
if (selected != null) {
if (Log.TEST) Log.note("[FavouriteArtistsView#handleSelectEvent] got selected item");
// Draw highlight for the selected item
selectedItem = selected;
selected.setSelected(true);
centerOnItem(selectedItem, false);
repaint();
// Delay the event handling, so that user gets a chance to see the highlighted item.
// Gesture is stored so that we can handle it later on.
pendingGestureEvent = event.getType();
delayThread = new Thread(selectDelay);
delayThread.start();
}
}
/**
* Handler for tap gesture.
*/
private void handleTap() {
if (Log.TEST) Log.note("[FavouriteArtistsView#handleTap]-->");
// Delegate handling to command handler
commandHandler.handleAction(tapActionId, null, this);
}
/**
* Handler for long press gesture.
*/
private void handleLongPress() {
if (Log.TEST) Log.note("[FavouriteArtistsView#handleLongPress]-->");
//Delegate handling to command handler
commandHandler.handleAction(longPressActionId, null, this);
}
/**
* Finds the item with the given coordinates.
*
* @param x X-coordinate.
* @param y Y-coordinate.
* @return Item at the given coordinates or null if no item found.
*/
private GridItem getItemAt(int x, int y) {
GridItem item = null;
int translatedX = xOffset + x;
int translatedY = yOffset + y;
for (int i = 0; i < items.size(); i++) {
item = (GridItem)items.elementAt(i);
if(item.isInItem(translatedX, translatedY)){
return item;
}
}
return null;
}
}