/* * $Id$ * * Copyright 2006 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.swingx.renderer; import java.awt.Color; import java.awt.Component; import java.awt.GradientPaint; import java.awt.Graphics2D; import java.awt.Image; import java.awt.Polygon; import java.awt.Rectangle; import java.awt.Shape; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.geom.Point2D; import java.awt.image.BufferedImage; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.logging.Logger; import javax.swing.Action; import javax.swing.DefaultListModel; import javax.swing.JLabel; import javax.swing.JTable; import javax.swing.KeyStroke; import javax.swing.ListModel; import javax.swing.Timer; import javax.swing.table.TableModel; import javax.swing.tree.TreeModel; import org.jdesktop.swingx.InteractiveTestCase; import org.jdesktop.swingx.JXFrame; import org.jdesktop.swingx.JXList; import org.jdesktop.swingx.JXTable; import org.jdesktop.swingx.JXTree; import org.jdesktop.swingx.action.AbstractActionExt; import org.jdesktop.swingx.decorator.ComponentAdapter; import org.jdesktop.swingx.decorator.HighlightPredicate; import org.jdesktop.swingx.decorator.Highlighter; import org.jdesktop.swingx.decorator.HighlighterFactory; import org.jdesktop.swingx.decorator.PainterHighlighter; import org.jdesktop.swingx.decorator.HighlightPredicate.ColumnHighlightPredicate; import org.jdesktop.swingx.decorator.HighlightPredicate.NotHighlightPredicate; import org.jdesktop.swingx.painter.AbstractPainter; import org.jdesktop.swingx.painter.BusyPainter; import org.jdesktop.swingx.painter.ImagePainter; import org.jdesktop.swingx.painter.MattePainter; import org.jdesktop.swingx.painter.Painter; import org.jdesktop.swingx.painter.ShapePainter; import org.jdesktop.swingx.painter.AbstractLayoutPainter.HorizontalAlignment; import org.jdesktop.swingx.painter.AbstractLayoutPainter.VerticalAlignment; import org.jdesktop.swingx.painter.effects.InnerGlowPathEffect; import org.jdesktop.swingx.renderer.RelativePainterHighlighter.NumberRelativizer; import org.jdesktop.swingx.renderer.RelativePainterHighlighter.RelativePainter; import org.jdesktop.swingx.sort.DefaultSortController; import org.jdesktop.swingx.table.ColumnControlButton; import org.jdesktop.swingx.test.XTestUtils; import org.jdesktop.swingx.treetable.FileSystemModel; import org.jdesktop.swingx.util.PaintUtils; import org.jdesktop.test.AncientSwingTeam; /** * Experiments with highlighters using painters.<p> * * Links * <ul> * <li> <a href="">Sneak preview II - Transparent Highlighter</a> * </ul> * * * @author Jeanette Winzenburg */ public class PainterVisualCheck extends InteractiveTestCase { @SuppressWarnings("all") private static final Logger LOG = Logger .getLogger(PainterVisualCheck.class.getName()); public static void main(String args[]) { // setSystemLF(true); PainterVisualCheck test = new PainterVisualCheck(); try { // test.runInteractiveTests(); // test.runInteractiveTests("interactive.*ValueBasedG.*"); // test.runInteractiveTests("interactive.*Icon.*"); test.runInteractiveTests("interactive.*Busy.*"); // test.runInteractiveTests("interactive.*Animated.*"); } catch (Exception e) { System.err.println("exception when executing interactive tests:"); e.printStackTrace(); } } public void interactiveTriangleRenderer() { JXTable table = new JXTable(new AncientSwingTeam()); ShapePainter painter = new ShapePainter(); Shape polygon = new Polygon(new int[] { 0, 5, 5 }, new int[] { 0, 0, 5 }, 3); painter.setShape(polygon); painter.setFillPaint(Color.RED); painter.setStyle(ShapePainter.Style.FILLED); painter.setPaintStretched(false); // hmm.. how to make this stick to the trailing upper corner? painter.setHorizontalAlignment(HorizontalAlignment.RIGHT); painter.setVerticalAlignment(VerticalAlignment.TOP); Highlighter hl = new PainterHighlighter(new ColumnHighlightPredicate(3), painter); table.addHighlighter(hl); showWithScrollingInFrame(table, "Renderer with Triangle marker"); } /** * Use Painter for an underline-rollover effect. */ public void interactiveRolloverPainter() { TableModel model = new AncientSwingTeam(); JXTable table = new JXTable(model); MattePainter matte = new MattePainter(getTransparentColor(Color.RED, 80)); RelativePainter<?> painter = new RelativePainter<Object>(matte); painter.setYFactor(0.2); painter.setVerticalAlignment(VerticalAlignment.BOTTOM); Highlighter hl = new PainterHighlighter(HighlightPredicate.ROLLOVER_ROW, painter); table.addHighlighter(hl); JXFrame frame = wrapWithScrollingInFrame(table, "painter-aware renderer rollover"); addStatusComponent(frame, new JLabel("gradient background of cells with value's containing 'y'")); show(frame); } /** * Creates and returns a predicate for filtering labels whose text * property contains the given text. * @return */ private HighlightPredicate createComponentTextBasedPredicate(final String substring) { HighlightPredicate predicate = new HighlightPredicate() { public boolean isHighlighted(Component renderer, ComponentAdapter adapter) { if (!(renderer instanceof JLabel)) return false; String text = ((JLabel) renderer).getText(); return text.contains(substring); } }; return predicate; } /** * Use ?? for fixed portion background highlighting * Use SwingX extended default renderer. */ public void interactiveTableBarHighlight() { TableModel model = new AncientSwingTeam(); JXTable table = new JXTable(model); MattePainter p = new MattePainter(getTransparentColor(Color.BLUE, 125)); RelativePainter<?> relativePainter = new RelativePainter<Object>(p); relativePainter.setXFactor(.5); Highlighter hl = new PainterHighlighter(createComponentTextBasedPredicate("y"), relativePainter); table.addHighlighter(hl); JXFrame frame = wrapWithScrollingInFrame(table, "painter-aware renderer with value-based highlighting"); addMessage(frame, "bar in cells with value containing y"); show(frame); } //------------------------ Transparent painter aware button as rendering component /** * Use a custom button controller to show both checkbox icon and text to * render Actions in a JXList. Apply striping and a simple gradient highlighter. */ public void interactiveTableWithListColumnControl() { TableModel model = new AncientSwingTeam(); JXTable table = new JXTable(model); JXList list = new JXList(); Highlighter highlighter = HighlighterFactory.createSimpleStriping(HighlighterFactory.LINE_PRINTER); table.addHighlighter(highlighter); Painter<?> gradient = createGradientPainter(Color.YELLOW, .7f, true); list.setHighlighters(highlighter, new PainterHighlighter(gradient)); // quick-fill and hook to table columns' visibility state configureList(list, table, false); // a custom rendering button controller showing both checkbox and text StringValue sv = new StringValue() { public String getString(Object value) { if (value instanceof AbstractActionExt) { return ((AbstractActionExt) value).getName(); } return ""; } }; BooleanValue bv = new BooleanValue() { public boolean getBoolean(Object value) { if (value instanceof AbstractActionExt) { return ((AbstractActionExt) value).isSelected(); } return false; } }; CheckBoxProvider wrapper = new CheckBoxProvider(new MappedValue(sv, null, bv), JLabel.LEADING); list.setCellRenderer(new DefaultListRenderer(wrapper)); JXFrame frame = showWithScrollingInFrame(table, list, "checkbox list-renderer - striping and gradient"); addStatusMessage(frame, "fake editable list: space/doubleclick on selected item toggles column visibility"); frame.pack(); } /** * Creates and returns a Painter with a gradient paint starting with * startColor to WHITE. * * @param startColor * @param percentage * @param transparent * @return */ protected Painter<?> createGradientPainter(Color startColor, float end, boolean transparent) { startColor = getTransparentColor(startColor, transparent ? 125 : 254); Color endColor = getTransparentColor(Color.WHITE, 0); GradientPaint paint = new GradientPaint( new Point2D.Double(0, 0), startColor, new Point2D.Double(1000, 0), endColor); MattePainter painter = new MattePainter(paint); painter.setPaintStretched(true); // not entirely successful - the relative stretching is on // top of a .5 stretched gradient in matte RelativePainter<?> wrapper = new RelativePainter<Object>(painter); wrapper.setXFactor(end); return wrapper; } private static Color getTransparentColor(Color base, int transparency) { return new Color(base.getRed(), base.getGreen(), base.getBlue(), transparency); } // ------------------------ /** * Use highlighter with background image painter. Shared by table and list. */ public void interactiveIconPainterHighlight() throws Exception { TableModel model = new AncientSwingTeam(); JXTable table = new JXTable(model); ComponentProvider<JLabel> controller = new LabelProvider( JLabel.RIGHT); table.getColumn(0).setCellRenderer( new DefaultTableRenderer(controller)); final ImagePainter imagePainter = new ImagePainter(XTestUtils.loadDefaultImage()); HighlightPredicate predicate = new ColumnHighlightPredicate(0); Highlighter iconHighlighter = new PainterHighlighter(predicate, imagePainter ); Highlighter alternateRowHighlighter = HighlighterFactory.createSimpleStriping(); table.addHighlighter(alternateRowHighlighter); table.addHighlighter(iconHighlighter); // re-use component controller and highlighter in a JXList JXList list = new JXList(createListNumberModel(), true); list.setCellRenderer(new DefaultListRenderer(controller)); list.addHighlighter(alternateRowHighlighter); list.addHighlighter(iconHighlighter); list.toggleSortOrder(); final JXFrame frame = showWithScrollingInFrame(table, list, "image highlighting plus striping"); frame.pack(); } /** * Use highlighter with image painter which is positioned relative to * cell value. */ public void interactiveValueBasedRelativePainterHighlight() { TableModel model = new AncientSwingTeam(); JXTable table = new JXTable(model); final ImagePainter imagePainter = new ImagePainter(XTestUtils.loadDefaultImage("green-orb.png")); imagePainter.setHorizontalAlignment(HorizontalAlignment.RIGHT); imagePainter.setAreaEffects(new InnerGlowPathEffect()); RelativePainterHighlighter iconHighlighter = new RelativePainterHighlighter(imagePainter); iconHighlighter.setHorizontalAlignment(HorizontalAlignment.LEFT); iconHighlighter.setRelativizer(new NumberRelativizer(100)); table.getColumnExt(3).addHighlighter(iconHighlighter); // re-use component controller and highlighter in a JXList JXList list = new JXList(createListNumberModel(), true); list.setCellRenderer(new DefaultListRenderer(new LabelProvider(JLabel.RIGHT))); list.addHighlighter(iconHighlighter); list.setComparator(DefaultSortController.COMPARABLE_COMPARATOR); list.toggleSortOrder(); showWithScrollingInFrame(table, list, "value-based image position (with relativePainterHighlighter)"); } /** * Use highlighter with image painter which is marching across the * cell range (same for all, independent of value). */ public void interactiveAnimatedIconPainterHighlight() { TableModel model = new AncientSwingTeam(); JXTable table = new JXTable(model); table.getColumn(1).setCellRenderer(new DefaultTableRenderer(new HyperlinkProvider())); ImagePainter imagePainter = new ImagePainter(XTestUtils.loadDefaultImage("green-orb.png")); imagePainter.setHorizontalAlignment(HorizontalAlignment.RIGHT); final RelativePainter<?> painter = new RelativePainter<Component>(imagePainter); PainterHighlighter iconHighlighter = new PainterHighlighter(); iconHighlighter.setHighlightPredicate(HighlightPredicate.ROLLOVER_ROW); iconHighlighter.setPainter(painter); ActionListener l = new ActionListener() { public void actionPerformed(ActionEvent e) { double fraction = painter.getXFactor(); fraction = fraction > 1 ? 0.0 : fraction + 0.1; painter.setXFactor(fraction); } }; table.addHighlighter(iconHighlighter); showWithScrollingInFrame(table, "Animated highlighter: marching icon on rollover"); Timer timer = new Timer(100, l); timer.start(); } /** * Use highlighter with BusyPainter. */ public void interactiveAnimatedBusyPainterHighlight() { TableModel model = new AncientSwingTeam(); JXTable table = new JXTable(model); table.getColumn(0).setCellRenderer(new DefaultTableRenderer( new HyperlinkProvider())); final BusyPainter busyPainter = new BusyPainter() { /** * Overridden to fix Issue #861-swingx: must notify on change * @param frame */ // @Override // public void setFrame(int frame) { // int old = getFrame(); // super.setFrame(frame); // firePropertyChange("frame", old, getFrame()); // } }; // JW: how do we ask for the height of the painter? table.setRowHeight(26); PainterHighlighter iconHighlighter = new PainterHighlighter(); HighlightPredicate predicate = new HighlightPredicate() { @Override public boolean isHighlighted(Component renderer, ComponentAdapter adapter) { return adapter.convertRowIndexToModel(adapter.row) == 1; } }; iconHighlighter.setHighlightPredicate(predicate); //HighlightPredicate.ROLLOVER_ROW); iconHighlighter.setPainter(busyPainter); ActionListener l = new ActionListener() { public void actionPerformed(ActionEvent e) { int frame = busyPainter.getFrame(); frame = (frame+1)%busyPainter.getPoints(); busyPainter.setFrame(frame); } }; table.getColumnExt(1).addHighlighter(iconHighlighter); showWithScrollingInFrame(table, "Animated highlighter: BusyPainter on Rollover"); Timer timer = new Timer(100, l); timer.start(); } /** * Issue #862-swingx: SwingX rendering components should be PainterAware. * * Currently this works only with a local version which has WrappingIconPanel * implement the PainterAware by delegating to its content delegate. */ public void interactiveAnimatedIconPainterHighlightTree() { TreeModel model = new FileSystemModel(); JXTree tree = new JXTree(model); tree.setRolloverEnabled(true); tree.setCellRenderer(new DefaultTreeRenderer(StringValues.FILE_NAME)); ImagePainter imagePainter = new ImagePainter(XTestUtils.loadDefaultImage("green-orb.png")); imagePainter.setHorizontalAlignment(HorizontalAlignment.RIGHT); final RelativePainter<?> painter = new RelativePainter<Component>(imagePainter); PainterHighlighter iconHighlighter = new PainterHighlighter(); iconHighlighter.setHighlightPredicate(HighlightPredicate.ROLLOVER_ROW); iconHighlighter.setPainter(painter); ActionListener l = new ActionListener() { public void actionPerformed(ActionEvent e) { double fraction = painter.getXFactor(); fraction = fraction > 1 ? 0.0 : fraction + 0.1; painter.setXFactor(fraction); } }; tree.addHighlighter(iconHighlighter); showWithScrollingInFrame(tree, "Animated highlighter: marching icon on rollover"); Timer timer = new Timer(100, l); timer.start(); } /** * Use custom painter and highlighter for per-row image decoration. * * @throws IOException */ public void interactivePerRowImage() throws IOException { JXTable table = new JXTable(new AncientSwingTeam()); table.setForeground(Color.MAGENTA); table.setSelectionForeground(Color.BLUE); table.setColumnControlVisible(true); table.setRowHeight(25); final BufferedImage moon = XTestUtils.loadDefaultImage("moon.jpg"); final BufferedImage rocket = XTestUtils.loadDefaultImage("500by500.png"); HighlightPredicate rocketPredicate = new HighlightPredicate() { public boolean isHighlighted(Component renderer, ComponentAdapter adapter) { return ((Integer) adapter.getValue(3)).intValue() < 50; } }; HighlightPredicate moonPredicate = new NotHighlightPredicate(rocketPredicate); table.setHighlighters( new SubImagePainterHighlighter(rocketPredicate, new SubImagePainter(rocket)) , new SubImagePainterHighlighter(moonPredicate, new SubImagePainter(moon)) ); JXFrame frame = wrapWithScrollingInFrame(table, "painter in renderer"); show(frame); } /** * Custom PainterHighlighter configures a SubImagePainter. * */ public static class SubImagePainterHighlighter extends PainterHighlighter { public SubImagePainterHighlighter(HighlightPredicate predicate, SubImagePainter painter) { super(predicate, painter); } @Override protected Component doHighlight(Component component, ComponentAdapter adapter) { Rectangle cellRect = getCellBounds(adapter); ((SubImagePainter) getPainter()).setImageClip(cellRect); return super.doHighlight(component, adapter); } private Rectangle getCellBounds(ComponentAdapter adapter) { // PENDING JW: add method to adapter JXTable table = (JXTable) adapter.getComponent(); Rectangle cellRect = table.getCellRect(adapter.row, adapter.column, false); return cellRect; } @Override protected boolean canHighlight(Component component, ComponentAdapter adapter) { return super.canHighlight(component, adapter) && (adapter.getComponent() instanceof JTable); } } /** * Simple Painter for subimage. */ public static class SubImagePainter extends AbstractPainter<Object> { BufferedImage image; Rectangle imageClip; public SubImagePainter(BufferedImage image) { super(false); this.image = image; } public void setImageClip(Rectangle imageClip) { this.imageClip = imageClip; } @Override protected void doPaint(Graphics2D g, Object object, int width, int height) { if ((imageClip == null) || (imageClip.width <= 0) || (imageClip.height <= 0)) return; if (imageClip.x + width >= image.getWidth()) return; if (imageClip.y + height >= image.getWidth()) return; Image subImage = image.getSubimage( imageClip.x, imageClip.y, width, height); g.drawImage(subImage, 0, 0, width, height, null); } } // ----------------- Transparent gradient on default (swingx) rendering label /** * Use transparent gradient painter for value-based background highlighting * with SwingX extended default renderer. Shared by table and list with * striping. */ public void interactiveValueBasedGradientHighlightPlusStriping() { TableModel model = new AncientSwingTeam(); JXTable table = new JXTable(model); ComponentProvider<JLabel> controller = new LabelProvider( JLabel.RIGHT) ; RelativePainterHighlighter gradientHighlighter = createRelativeGradientHighlighter(HorizontalAlignment.RIGHT, 100); table.addHighlighter(gradientHighlighter); Highlighter alternateRowHighlighter = HighlighterFactory.createSimpleStriping(); table.addHighlighter(alternateRowHighlighter); table.addHighlighter(gradientHighlighter); // re-use component controller and highlighter in a JXList JXList list = new JXList(createListNumberModel(), true); list.setCellRenderer(new DefaultListRenderer(controller)); list.addHighlighter(alternateRowHighlighter); list.addHighlighter(gradientHighlighter); list.toggleSortOrder(); final JXFrame frame = wrapWithScrollingInFrame(table, list, "transparent value relative highlighting plus striping"); addStatusMessage(frame, "uses a PainterAwareLabel in renderer"); // crude binding to play with options - the factory is incomplete... // addStatusComponent(frame, createTransparencyToggle(gradientHighlighter)); // show(frame); } /** * Use transparent gradient painter for value-based background highlighting * with SwingX extended default renderer. Shared by table and list with * background color. */ public void interactiveValueBasedGradientHighlight() { TableModel model = new AncientSwingTeam(); JXTable table = new JXTable(model); table.setBackground(HighlighterFactory.LEDGER); ComponentProvider<JLabel> controller = new LabelProvider( JLabel.RIGHT); // table.setDefaultRenderer(Number.class, new DefaultTableRenderer( // controller)); RelativePainterHighlighter gradientHighlighter = createRelativeGradientHighlighter(HorizontalAlignment.RIGHT, 100); table.addHighlighter(gradientHighlighter); // re-use component controller and highlighter in a JXList JXList list = new JXList(createListNumberModel(), true); list.setBackground(table.getBackground()); list.setCellRenderer(new DefaultListRenderer(controller)); list.addHighlighter(gradientHighlighter); list.toggleSortOrder(); JXFrame frame = wrapWithScrollingInFrame(table, list, "transparent value relative highlighting (with RelativePH)"); // addStatusMessage(frame, // "uses the default painter-aware label in renderer"); // crude binding to play with options - the factory is incomplete... // addStatusComponent(frame, createTransparencyToggle(gradientHighlighter)); show(frame); } /** * @param right * @return */ private RelativePainterHighlighter createRelativeGradientHighlighter( HorizontalAlignment right, Number max) { Color startColor = PaintUtils.setAlpha(Color.RED, 130); Color endColor = PaintUtils.setAlpha(Color.RED.brighter(), 0); boolean isRightAligned = HorizontalAlignment.RIGHT == right; GradientPaint paint = new GradientPaint(new Point2D.Double(0, 0), isRightAligned ? endColor : startColor, new Point2D.Double(100, 0), isRightAligned ? startColor : endColor); MattePainter painter = new MattePainter(paint); painter.setPaintStretched(true); RelativePainterHighlighter p = new RelativePainterHighlighter(painter); p.setHorizontalAlignment(right); p.setRelativizer(new NumberRelativizer(max)); return p; } //----------------- Utility /** * * @return a ListModel wrapped around the AncientSwingTeam's Number column. */ private ListModel createListNumberModel() { AncientSwingTeam tableModel = new AncientSwingTeam(); int colorColumn = 3; DefaultListModel model = new DefaultListModel(); for (int i = 0; i < tableModel.getRowCount(); i++) { model.addElement(tableModel.getValueAt(i, colorColumn)); } return model; } /** * Fills the list with a collection of actions (as returned from the * table's column control). Binds space and double-click to toggle * the action's selected state. * * note: this is just an example to show-off the button renderer in a list! * ... it's very dirty!! * * @param list * @param table */ private void configureList(final JXList list, final JXTable table, boolean useRollover) { final List<Action> actions = new ArrayList<Action>(); @SuppressWarnings("all") ColumnControlButton columnControl = new ColumnControlButton(table) { @Override protected void addVisibilityActionItems() { actions.addAll(Collections .unmodifiableList(getColumnVisibilityActions())); } }; list.setModel(createListeningListModel(actions)); // action toggling selected state of selected list item final Action toggleSelected = new AbstractActionExt( "toggle column visibility") { public void actionPerformed(ActionEvent e) { if (list.isSelectionEmpty()) return; AbstractActionExt selectedItem = (AbstractActionExt) list .getSelectedValue(); selectedItem.setSelected(!selectedItem.isSelected()); } }; if (useRollover) { list.setRolloverEnabled(true); } else { // bind action to space list.getInputMap().put(KeyStroke.getKeyStroke("SPACE"), "toggleSelectedActionState"); } list.getActionMap().put("toggleSelectedActionState", toggleSelected); // bind action to double-click MouseAdapter adapter = new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { if (e.getClickCount() == 2) { toggleSelected.actionPerformed(null); } } }; list.addMouseListener(adapter); } /** * Creates and returns a ListModel containing the given actions. * Registers a PropertyChangeListener with each action to get * notified and fire ListEvents. * * @param actions the actions to add into the model. * @return the filled model. */ private ListModel createListeningListModel(final List<Action> actions) { final DefaultListModel model = new DefaultListModel() { DefaultListModel reallyThis = this; @Override public void addElement(Object obj) { super.addElement(obj); ((Action) obj).addPropertyChangeListener(l); } PropertyChangeListener l = new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent evt) { int index = indexOf(evt.getSource()); if (index >= 0) { fireContentsChanged(reallyThis, index, index); } } }; }; for (Action action : actions) { model.addElement(action); } return model; } /** * do-nothing method - suppress warning if there are no other * test fixtures to run. * */ public void testDummy() { } }