/*
* $Id$
*
* Copyright 2009 Sun Microsystems, Inc., 4150 Network Circle,
* Santa Clara, California 95054, U.S.A. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
package org.jdesktop.swingxset.util;
import java.awt.Component;
import java.awt.Graphics2D;
import java.awt.Point;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import org.jdesktop.swingx.JXTable;
import org.jdesktop.swingx.decorator.ComponentAdapter;
import org.jdesktop.swingx.decorator.PainterHighlighter;
import org.jdesktop.swingx.painter.AbstractLayoutPainter;
import org.jdesktop.swingx.painter.AbstractPainter;
import org.jdesktop.swingx.painter.Painter;
import org.jdesktop.swingx.painter.AbstractLayoutPainter.HorizontalAlignment;
// <snip> Relative Decorator
// Specialized PainterHighlighter which has a Relativizer
public class RelativePainterHighlighter extends PainterHighlighter {
private Relativizer relativizer;
// </snip>
public RelativePainterHighlighter() {
this(null);
}
public RelativePainterHighlighter(Painter delegate) {
super(delegate);
}
public void setHorizontalAlignment(HorizontalAlignment align) {
getPainter().setHorizontalAlignment(align);
fireStateChanged();
}
public HorizontalAlignment getHorizontalAlignment() {
return getPainter().getHorizontalAlignment();
}
/**
* @param maxValue the maxValue to set
*/
public void setRelativizer(Relativizer relativizer) {
this.relativizer = relativizer;
fireStateChanged();
}
public Relativizer getRelativizer() {
return relativizer;
}
@Override
protected Component doHighlight(Component component,
ComponentAdapter adapter) {
// <snip> Relative Decorator
// configures the RelativePainter with the value returned by the
// Relativizer
float xPercent = relativizer.getRelativeValue(adapter);
getPainter().setXFactor(xPercent);
getPainter().setVisible(xPercent != Relativizer.ZERO);
return super.doHighlight(component, adapter);
// </snip>
}
/**
* Overridden to wrap a RelativePainter around the given, if not already is
* of type RelativePainter.
*/
// <snip> Relative Decorator
// Wraps Painter into RelativePainter (hack around missing api in swingx)
@Override
public void setPainter(Painter painter) {
if (!(painter instanceof RelativePainter)) {
painter = new RelativePainter(painter);
}
super.setPainter(painter);
}
// </snip>
@Override
public RelativePainter getPainter() {
return (RelativePainter) super.getPainter();
}
@Override
protected boolean canHighlight(Component component, ComponentAdapter adapter) {
return relativizer != null && super.canHighlight(component, adapter);
}
// ------------------- Relativizer
// <snip> Relativizer
// One-method interface to map a cell value to a float
public static interface Relativizer {
/**
* Returns a float in the range of 0.0f to 1.0f inclusive which
* indicates the relative value of the given adapter's value.
*
* @param adapter
* @return
*/
public float getRelativeValue(ComponentAdapter adapter);
// </snip>
public static final float ZERO = 0.0f;
public static final float ONE = 1.0f;
}
public static class NumberRelativizer implements Relativizer {
private Number max;
private Number current;
private int valueColumn;
private boolean spread;
public NumberRelativizer(Number max) {
this(max, max);
}
public NumberRelativizer(Number max, Number current) {
this(0, max, current);
}
/**
* @param i
* @param max
* @param current
*/
public NumberRelativizer(int column, Number max, Number current) {
this(column, false, max, current);
}
public NumberRelativizer(int column, boolean spreadColumns, Number max,
Number current) {
this.current = current;
this.max = max;
this.valueColumn = column;
this.spread = spreadColumns;
}
// <snip> Relativizer
// Simplistic base implementation of Relativizer which handles Numbers
@Override
public float getRelativeValue(ComponentAdapter adapter) {
if (getNumber(adapter) == null) {
return ZERO;
}
float value = getNumber(adapter).floatValue();
float limit = Math.min(getCurrent().floatValue(), value);
if (isZero(limit)) {
return ZERO;
}
float percent = limit / getMax().floatValue();
if (!spread) {
return percent;
}
int width = adapter.getComponent().getWidth();
int pixelLocation = (int) (percent * width);
int visualColumn = getColumnAt(adapter, pixelLocation);
if (adapter.column < visualColumn) {
return ONE;
} else if (adapter.column > visualColumn) {
return ZERO;
}
int visualColumnWidth = getColumnWidth(adapter, visualColumn);
int startColumn = getColumnLocation(adapter, visualColumn);
int valueWidth = pixelLocation - startColumn;
return (float) valueWidth / (float) visualColumnWidth;
// </snip>
}
/**
* Returns a Number representation of the cell value or null if it
* doesn't have any. Subclasses are meant to override this for custom
* mappings.
* <p>
*
* This implementation checks and returns the type of the current cell.
*
* @param adapter the ComponentAdapter which defines the current cell.
* @return a Number representing the current cell or null
*/
// <snip> Relativizer
// simple value-to-Number mapping which handles Number types.
protected Number getNumber(ComponentAdapter adapter) {
if (adapter.getValue() instanceof Number)
return (Number) adapter.getValue(valueColumn);
return null;
}
// </snip>
private int getColumnLocation(ComponentAdapter adapter, int visualColumn) {
if (!(adapter.getComponent() instanceof JXTable)) {
return 0;
}
JXTable table = (JXTable) adapter.getComponent();
// PENDING JW: guard against null header
return table.getTableHeader().getHeaderRect(visualColumn).x;
}
private int getColumnWidth(ComponentAdapter adapter, int visualColumn) {
if (!(adapter.getComponent() instanceof JXTable)) {
return adapter.getComponent().getWidth();
}
JXTable table = (JXTable) adapter.getComponent();
return table.getColumn(visualColumn).getWidth();
}
private int getColumnAt(ComponentAdapter adapter, int pixelLocation) {
if (!(adapter.getComponent() instanceof JXTable)) {
return 0;
}
JXTable table = (JXTable) adapter.getComponent();
// PENDING JW: guard against null header
return table.getTableHeader().columnAtPoint(
new Point(pixelLocation, 10));
}
protected Number getCurrent() {
return current;
}
protected Number getMax() {
return max;
}
protected boolean isZero(float limit) {
return Math.abs(limit) < 0.002;
}
protected int getValueColumn() {
return valueColumn;
}
}
// --------- hack around missing size proportional painters
public static class RelativePainter<T> extends AbstractLayoutPainter<T> {
private Painter<? super T> painter;
private double xFactor;
private double yFactor;
private boolean visible;
private PropertyChangeListener painterListener;
public RelativePainter() {
this(null);
}
public RelativePainter(Painter<? super T> delegate) {
this.painter = delegate;
installPainterListener();
}
public RelativePainter(Painter<? super T> delegate, double xPercent) {
this(delegate);
xFactor = xPercent;
}
public void setPainter(Painter<? super T> painter) {
uninstallPainterListener();
Object old = getPainter();
this.painter = painter;
installPainterListener();
firePropertyChange("painter", old, getPainter());
}
public Painter<? super T> getPainter() {
return painter;
}
public void setXFactor(double xPercent) {
double old = getXFactor();
this.xFactor = xPercent;
firePropertyChange("xFactor", old, getXFactor());
}
/**
* @return
*/
public double getXFactor() {
return xFactor;
}
public void setYFactor(double yPercent) {
this.yFactor = yPercent;
}
@Override
protected void doPaint(Graphics2D g, T object, int width, int height) {
if (painter == null)
return;
// use epsilon
if (xFactor != 0.0) {
int oldWidth = width;
width = (int) (xFactor * width);
if (getHorizontalAlignment() == HorizontalAlignment.RIGHT) {
g.translate(oldWidth - width, 0);
}
}
if (yFactor != 0.0) {
int oldHeight = height;
height = (int) (yFactor * height);
if (getVerticalAlignment() == VerticalAlignment.BOTTOM) {
g.translate(0, oldHeight - height);
}
}
painter.paint(g, object, width, height);
}
/**
* Overridden to take over completely: super does strange things with
* dirty which result in property changes fired during painting.
*/
@Override
public boolean isVisible() {
return visible;
}
/**
* Overridden to take over completely: super does strange things with
* dirty which result in property changes fired during painting.
*/
@Override
public void setVisible(boolean visible) {
if (isVisible() == visible)
return;
this.visible = visible;
firePropertyChange("visible", !visible, isVisible());
}
/**
* Installs a listener to the painter if appropriate. This
* implementation registers its painterListener if the Painter is of
* type AbstractPainter.
*/
protected void installPainterListener() {
if (getPainter() instanceof AbstractPainter) {
((AbstractPainter) getPainter())
.addPropertyChangeListener(getPainterListener());
}
}
/**
* Uninstalls a listener from the painter if appropriate. This
* implementation removes its painterListener if the Painter is of type
* AbstractPainter.
*/
protected void uninstallPainterListener() {
if (getPainter() instanceof AbstractPainter) {
((AbstractPainter) getPainter())
.removePropertyChangeListener(painterListener);
}
}
/**
* Lazyly creates and returns the property change listener used to
* listen to changes of the painter.
*
* @return the property change listener used to listen to changes of the
* painter.
*/
protected final PropertyChangeListener getPainterListener() {
if (painterListener == null) {
painterListener = createPainterListener();
}
return painterListener;
}
/**
* Creates and returns the property change listener used to listen to
* changes of the painter.
* <p>
*
* This implementation fires a stateChanged on receiving any
* propertyChange, if the isAdjusting flag is false. Otherwise does
* nothing.
*
* @return the property change listener used to listen to changes of the
* painter.
*/
protected PropertyChangeListener createPainterListener() {
PropertyChangeListener l = new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent evt) {
setDirty(true);
}
};
return l;
}
}
}