GridLayout.java

/**
 * Copyright (c) 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.musicexplorer.ui;

import java.util.Enumeration;
import java.util.Vector;
import javax.microedition.lcdui.CustomItem;
import javax.microedition.lcdui.Graphics;

import com.nokia.example.musicexplorer.data.model.GenericProductModel;
import com.nokia.example.musicexplorer.data.model.AlbumModel;

/**
 * GridLayout is a CustomItem component for displaying a grid of GridItems in a
 * Form.
 */
public class GridLayout extends CustomItem {

    public static final int CUSTOM_ITEM_MARGIN_SIZE = 5;
    public static final int DEFAULT_COLUMN_COUNT = 3;
    public static final int JITTER_THRESHOLD = 10;

    protected Vector gridItems;
    protected GridItem selectedItem;
    protected int width = 0;
    protected int columnCount = DEFAULT_COLUMN_COUNT;
    protected int columnWidth = 0;
    protected int rowHeight = 0;
    protected int lastX = 0;
    protected int lastY = 0;
    protected int verticalScrollPosition = 0;
    protected ViewManager viewManager;
    protected boolean showMoreByArtistButton = true;

    /**
     * Constructor.
     * @param width
     * @param viewManager
     */
    public GridLayout(final int width, ViewManager viewManager) {
        super(null);
        this.viewManager = viewManager;
        this.gridItems = new Vector();
        setWidth(width);
    }

    /**
     * Adds an item to the grid.
     *
     * @param productModel
     */
    public void addItem(GenericProductModel productModel) {
        GridItem gridItem = new GridItem(viewManager, productModel, this);
        gridItem.setSize(columnWidth, columnWidth);
        gridItems.addElement(gridItem);
        
        int amountOfRows = (int) Math.ceil(
                (double) gridItems.size() / this.columnCount);
        int newHeight = 
                amountOfRows * this.rowHeight + CUSTOM_ITEM_MARGIN_SIZE * 2;
        
        this.setPreferredSize(this.width, newHeight);
        repaint();
    }

    /**
     * Adds a vector of ProductModels to the grid at once.
     * @param productModels 
     */
    public void addItems(Vector productModels) {
        int loopMax = productModels.size();
        for (int i = 0; i < loopMax; i++) {
            addItem((GenericProductModel) productModels.elementAt(i));
        }
    }

    /**
     * For convenience. Calculates the size and repaints the layout only once.
     *
     * @param columnCount The new column count.
     * @param width The new width for the layout.
     */
    public void setColumnCountAndWidth(final int columnCount, final int width) {
        if ((this.width != width && columnCount > 0)
                || (columnCount > 0 && this.columnCount != columnCount)) {
            this.columnCount = columnCount;
            this.width = width;
            onWidthOrColumnCountChanged();
        }
    }

    /**
     * @param width The new width for the layout.
     */
    public void setWidth(final int width) {
        if (this.width != width) {
            this.width = width;
            onWidthOrColumnCountChanged();
        }
    }

    /**
     * Sets the number of columns to be displayed in the grid.
     *
     * @param columnCount The number of columns.
     */
    public void setColumnCount(final int columnCount) {
        if (columnCount > 0 && this.columnCount != columnCount) {
            this.columnCount = columnCount;
            onWidthOrColumnCountChanged();
        }
    }

    /**
     * Get the grid item under the pointer and set it as {@link #selectedItem}.
     *
     * @see javax.microedition.lcdui.CustomItem#pointerPressed()
     */
    public void pointerPressed(int x, int y) {
        selectedItem = getItemAt(x, y);
        
        if (selectedItem != null) {
            // Save the coordinates to be used in pointerReleased().
            lastX = x;
            lastY = y;
            selectedItem.setHighlight(true);
        }
    }

    /**
     * Checks if the pointer is still inside the same item where the tap began
     * on.
     *
     * @see javax.microedition.lcdui.CustomItem#pointerReleased()
     */
    public void pointerReleased(int x, int y) {
        if (selectedItem != null && selectedItem == getItemAt(x, y)
                && Math.abs(x - lastX) < JITTER_THRESHOLD
                && Math.abs(y - lastY) < JITTER_THRESHOLD) {
            selectedItem.setHighlight(false);
            viewManager.showView(
                    new AlbumView(
                        viewManager, 
                        (AlbumModel) selectedItem.model, 
                        showMoreByArtistButton));
        }
    }

    /**
     * Clears the {@link #selectedItem} when pointer is dragged outside the grid
     * item.
     *
     * @see javax.microedition.lcdui.CustomItem#pointerDragged()
     */
    public void pointerDragged(int x, int y) {
        if (selectedItem != null
                && (Math.abs(x - lastX) > JITTER_THRESHOLD
                || Math.abs(y - lastY) > JITTER_THRESHOLD)) {
            // Too much jitter. Loose the selected item.
            selectedItem.setHighlight(false);
            selectedItem = null;
            repaint(); // Must be repainted to hide highlight on item.
        }
    }

    public void disableShowMoreByArtistButtonInAlbumViews() {
        this.showMoreByArtistButton = false;
    }    

    /**
     * @see javax.microedition.lcdui.CustomItem#getMinContentHeight()
     */
    protected int getMinContentHeight() {
        return 0;
    }

    /**
     * @see javax.microedition.lcdui.CustomItem#getMinContentWidth()
     */
    protected int getMinContentWidth() {
        return 0;
    }

    /**
     * @see javax.microedition.lcdui.CustomItem#getPrefContentHeight(int)
     */
    protected int getPrefContentHeight(int h) {
        return getPreferredHeight();
    }

    /**
     * @see javax.microedition.lcdui.CustomItem#getPrefContentWidth(int)
     */
    protected int getPrefContentWidth(int w) {
        return getPreferredWidth();
    }

    /**
     * @see javax.microedition.lcdui.CustomItem#paint(Graphics, int, int)
     */
    protected void paint(Graphics graphics, int width, int height) {
        Enumeration e = gridItems.elements();
        int row = 0;
        int col = 0;
        
        // Loop the grid items
        while (e.hasMoreElements()) {
            GridItem item = (GridItem) e.nextElement();
            
            // Paint the current item
            item.paintXY(graphics, col * columnWidth, row * rowHeight);
            col++;
            
            // Advance to the next row if all columns have been painted
            if (col == columnCount) {
                row++;
                col = 0;
            }
        }
    }

    /**
     * Resolves an item at the given position.
     *
     * @param x The X coordinate of the item.
     * @param y The Y coordinate of the item.
     * @return The item instance in the given position or null if not found.
     */
    protected GridItem getItemAt(int x, int y) {
        // Calculate item index based on coordinates.
        final int col = x / columnWidth;
        final int row = y / rowHeight;
        final int index = (columnCount * row) + col;
        
        GridItem item = null;
        
        int size = gridItems.size();
        
        // Check if index is in range.
        if (index < size) {
            item = (GridItem) gridItems.elementAt(index);
        }
        
        return item;
    }

    /**
     * (Re)calculates the column width and the preferred size for the layout and
     * repaints the whole thing.
     */
    protected void onWidthOrColumnCountChanged() {
        columnWidth = (width - CUSTOM_ITEM_MARGIN_SIZE * 2) / columnCount;
        rowHeight = columnWidth; // Only square items
        
        Enumeration e = gridItems.elements();
        
        while (e.hasMoreElements()) {
            ((GridItem) e.nextElement()).setSize(columnWidth, rowHeight);
        }
        
        final int rowCount = (int) Math.ceil((double) gridItems.size() / columnCount);
        setPreferredSize(width, rowCount * rowHeight + CUSTOM_ITEM_MARGIN_SIZE * 2);
        
        repaint();
    }

    /**
     * For toggling grid repaint.
     */
    public void gridItemStateChanged() {
        repaint();
    }
}