package com.limegroup.gnutella.gui.tables;
import java.awt.Color;
import java.awt.Component;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import javax.swing.JComponent;
import javax.swing.JTable;
import javax.swing.table.TableCellRenderer;
import com.limegroup.gnutella.Assert;
import com.limegroup.gnutella.gui.themes.ThemeMediator;
import com.limegroup.gnutella.gui.themes.ThemeObserver;
/**
* Draws a cell with it's default renderer, but the foreground
* colored differently.
*
* Due to the nature of renderer components being shared
* between cells, this can not act directly on the renderer
* that's returned. Otherwise, this has the side effect of altering
* future renderings that aren't necesarily from a ColoredCell.
* To still allow complete functionality of the
* ColorRenderer to work regardless of the cell's specific renderer,
* this will instantiate a copy of that renderer and cache it for future
* use.
*
*******************************************************************
* This requires that any cell that is being colored have a default
* TableCellRenderer that has a parameterless constructor.
*******************************************************************
*
* New renderers are created flyweight-style.
*
* All useful calls are wrapped and redirected to the underlying renderer.
*
* This class takes advantage of the potential of the 'renderer' component
* to not be the TableCellRenderer itself. It is merely by convention
* that all getTableCellRendererComponent calls return 'this'.
* However, there is nothing that specifically states the TableCellRenderer
* does not delegate to other renderers. Unfortunately, the JTable
* sends UI updates to the TableCellRenderer, and not the component that
* does the renderering. TableCellRenderer is actually a rather poor name,
* or more precisely, the function 'getTableCellRendererComponent' is a
* poor name for being in an interface titled TableCellRenderer.
* The delegate functionality is useful, but so is the rubber-stamping.
* It would probably have been better to name getTableCel.. 'stamp'.
*
* This class does *NOT* instantiate new copies of the component returned
* from getTableCellRendererComponent. It needs new copies of the
* TableCellRenderer, in order to call further getTableCellRendererComponents.
* Since the two are generally the same, it works out nicely.
*
* UI Updates are propagated to all contained TableCellRenderers.
*
* For clarity, this class just extends JComponent (needed to recieve
* an updateUI call). It is not really a component -- it just delegates.
*
* NOTE: This does not color selected or focused cells.
*/
class ColorRenderer extends JComponent
implements TableCellRenderer, ThemeObserver {
/**
* Map is from TableCellRenderer to TableCellRenderer.
* Every instance of a renderer will have a mirrored instance as its value.
*/
private Map otherRenderers = new HashMap();
public ColorRenderer() {
ThemeMediator.addThemeObserver(this);
}
public Component getTableCellRendererComponent(JTable table,
Object value,
boolean isSel,
boolean hasFocus,
int row,
int column) {
ColoredCell cc=(ColoredCell)value;
Color clr;
Object val;
Class clazz;
if(cc != null) {
clr = cc.getColor();
val = cc.getValue();
clazz = cc.getCellClass();
} else {
clr = null;
val = "";
clazz = String.class;
}
TableCellRenderer tcr = table.getDefaultRenderer( clazz );
tcr = getCachedOrNewRenderer( tcr );
Component renderer = tcr.getTableCellRendererComponent(
table, val, isSel, hasFocus, row, column);
if ((!isSel && !hasFocus)) // || isReadable(clr, renderer.getBackground()))
renderer.setForeground(clr);
return renderer;
}
/**
* Returns true if the two given colors are both non-null and their lightness
* difference is greater than 0.2.
* <p>
* If that is the case they will will be readable if used as background and
* foreground color.
* @param foreGround
* @param backGround
* @return
*/
private boolean isReadable(Color foreGround, Color backGround) {
if (foreGround == null || backGround == null) {
return false;
}
return Math.abs(getLightness(foreGround) - getLightness(backGround)) > 0.2;
}
private double getLightness(Color color)
{
// TODO fberger find right lightness
// return Math.pow((0.3 * c.getRed() + 0.59 * c.getGreen()
// - + 0.11 * c.getBlue()) / 255.0,
// - 1.0 / 3.0);
return 0.0;
}
public void updateTheme() {
for( Iterator i = otherRenderers.values().iterator(); i.hasNext(); ) {
Object o = i.next();
if ( o instanceof ThemeObserver ) {
((ThemeObserver)o).updateTheme();
}
}
}
public void updateUI() {
for( Iterator i = otherRenderers.values().iterator(); i.hasNext(); ) {
Object o = i.next();
if ( o instanceof JComponent ) {
((JComponent)o).updateUI();
}
}
}
private TableCellRenderer getCachedOrNewRenderer( TableCellRenderer tcr ) {
TableCellRenderer renderer = (TableCellRenderer)otherRenderers.get(tcr);
// if it doesn't exist, put a copy of the renderer in there
// so that the setForeground doesn't effect the real renderer.
if ( renderer == null ) {
Class rendererClass = tcr.getClass();
try {
renderer = (TableCellRenderer)rendererClass.newInstance();
} catch (IllegalAccessException e) {
Assert.that(false, e.getMessage());
} catch (InstantiationException e) {
Assert.that(false, e.getMessage());
} catch (ClassCastException e) {
Assert.that(false, e.getMessage());
}
otherRenderers.put( tcr, renderer );
}
return renderer;
}
}