swingx-master/swingx-core/src/test/java/org/jdesktop/swingx/renderer/PainterVisualCheck.java
/*
 * $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() {
        
    }

}
