/* * $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; } } }