/*
 * 04/26/2010
 *
 * FastListUI.java - A JList UI implementation that computes the preferred size
 * of all cells really fast, to facilitate lists of possibly thousands of items
 * rendered with HTML, which is slow with BasicListUI extensions.
 * 
 * This library is distributed under a modified BSD license.  See the included
 * RSyntaxTextArea.License.txt file for details.
 */
package com.fr.design.gui.autocomplete;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.SystemColor;
import javax.swing.JViewport;
import javax.swing.ListCellRenderer;
import javax.swing.ListModel;
import javax.swing.UIManager;
import javax.swing.plaf.basic.BasicListUI;


/**
 * A custom list UI, used by the completion choices list.  If the number of
 * completion choices is "large," it does a fast estimate of the preferred
 * width and height of list items.  This allows HTML renderers to be used (such
 * as {@link CompletionCellRenderer}), with thousands of completion choices,
 * with no performance penalty.  With standard BasicListUI subclasses, this can
 * cause very poor performance <b>each time</b> the list is displayed, which
 * is bad for lists that are repeatedly hidden and re-displayed, such as
 * completion choices.  This is all because the calculation to get the
 * preferred size of each list item, when it is displayed with HTML, is slow.
 *
 * @author Robert Futrell
 * @version 1.0
 */
class FastListUI extends BasicListUI {

	/**
	 * Whether the selection background was overridden (usually because of
	 * Nimbus) so we know to manually uninstall the color we installed.
	 */
	private boolean overriddenBackground;

	/**
	 * Whether the selection foreground was overridden (usually because of
	 * Nimbus) so we know to manually uninstall the color we installed.
	 */
	private boolean overriddenForeground;

	/**
	 * If there are more than this many completions in a single list, this
	 * UI will estimate the cell width and height needed for each item instead
	 * of computing it, for performance reasons.
	 */
	private static final int ESTIMATION_THRESHOLD		= 200;


	private Color determineSelectionBackground() {
		Color c = UIManager.getColor("List.selectionBackground");
		if (c==null) {
			c = UIManager.getColor("nimbusSelectionBackground");
			if (c==null) { // Not Nimbus, but still need a value - fallback
				c = UIManager.getColor("textHighlight");
				if (c==null) {
					c = SystemColor.textHighlight;
				}
			}
		}

		// Nimbus unfortunately requires a Color, not a ColorUIResource, for
		// the background override to work. This causes this color to "stick"
		// even if the LAF is changed to something else later.  "c" here may
		// actually be a ColorUIResource
		return new Color(c.getRGB());//new ColorUIResource(c);

	}


	private Color determineSelectionForeground() {
		Color c = UIManager.getColor("List.selectionForeground");
		if (c==null) {
			c = UIManager.getColor("nimbusSelectedText");
			if (c==null) { // Not Nimbus, but still need a value - fallback
				c = UIManager.getColor("textHighlightText");
				if (c==null) {
					c = SystemColor.textHighlightText;
				}
			}
		}
		// Nimbus unfortunately requires Color, not ColorUIResource, and "c"
		// may actually be a ColorUIResource
		return new Color(c.getRGB());
	}


	/**
	 * Overridden to ensure we have selection background/foreground colors
	 * defined, even if we're in some weirdo LAF such as Nimbus which doesn't
	 * define them.  Since FastListUI extends BasicListUI, we need these values
	 * to be defined.
	 */
	@Override
	protected void installDefaults() {

		super.installDefaults();

		if (list.getSelectionBackground()==null) {
			list.setSelectionBackground(determineSelectionBackground());
			overriddenBackground = true;
		}

		if (list.getSelectionForeground()==null) {
			list.setSelectionForeground(determineSelectionForeground());
			overriddenForeground = true;
		}
	}


	/**
	 * Overridden to work around a Nimbus issue.
	 */
	@Override
	protected void uninstallDefaults() {

		super.uninstallDefaults();

		if (overriddenBackground) {
			list.setSelectionBackground(null);
		}

		if (overriddenForeground) {
			list.setSelectionForeground(null);
		}

	}


	/**
	 * Recalculates the cell width and height of each cell in the list.  This
	 * method is overridden to do a fast estimation if the completion list is
	 * too long, to improve performance for lists with huge amounts of
	 * completions.
	 */
	@Override
	protected void updateLayoutState() {

		ListModel model = list.getModel();
		int itemCount = model.getSize();

		// If the item count is small enough to run fast on practically all
		// machines, go ahead and use the super implementation to determine
		// the optimal cell sizes.
		if (itemCount<ESTIMATION_THRESHOLD) {
			super.updateLayoutState();
			return;
		}

		// Otherwise, assume all cells are the same height as the first cell,
		// and estimate the necessary width.

		ListCellRenderer renderer = list.getCellRenderer();

		cellWidth = list.getWidth();
		if (list.getParent() instanceof JViewport) { // Always true for us
			cellWidth = list.getParent().getWidth();
		}
		//System.out.println(cellWidth);

		// We're getting a fixed cell height for all cells
		cellHeights = null;

		if (renderer!=null && itemCount>0) {
			Object value = model.getElementAt(0);
			java.awt.Component c = renderer.getListCellRendererComponent(list,
												value, 0, false, false);
			rendererPane.add(c);
			Dimension cellSize = c.getPreferredSize();
			cellHeight = cellSize.height;
			cellWidth = Math.max(cellWidth, cellSize.width);
		}
		else {
			cellHeight = 20; 
		}

	}


}