swingx-master/swingx-demos/swingx-demos-content/src/main/java/org/jdesktop/swingx/demos/search/SearchDemo.java
/*
 * Created on 06.12.2008
 *
 */
package org.jdesktop.swingx.demos.search;

import static org.jdesktop.beansbinding.AutoBinding.UpdateStrategy.READ;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.KeyboardFocusManager;
import java.awt.event.ActionEvent;
import java.net.URI;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;

import javax.swing.Action;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.table.TableCellRenderer;
import javax.swing.tree.DefaultTreeModel;

import org.jdesktop.application.Application;
import org.jdesktop.beans.AbstractBean;
import org.jdesktop.beansbinding.BeanProperty;
import org.jdesktop.beansbinding.BindingGroup;
import org.jdesktop.beansbinding.Bindings;
import org.jdesktop.swingx.JXFindBar;
import org.jdesktop.swingx.JXList;
import org.jdesktop.swingx.JXTable;
import org.jdesktop.swingx.JXTree;
import org.jdesktop.swingx.JXTreeTable;
import org.jdesktop.swingx.SwingXUtilities;
import org.jdesktop.swingx.action.AbstractActionExt;
import org.jdesktop.swingx.decorator.AbstractHighlighter;
import org.jdesktop.swingx.decorator.ColorHighlighter;
import org.jdesktop.swingx.decorator.HighlighterFactory;
import org.jdesktop.swingx.renderer.DefaultListRenderer;
import org.jdesktop.swingx.renderer.DefaultTableRenderer;
import org.jdesktop.swingx.renderer.DefaultTreeRenderer;
import org.jdesktop.swingx.renderer.StringValue;
import org.jdesktop.swingx.renderer.StringValues;
import org.jdesktop.swingx.search.AbstractSearchable;
import org.jdesktop.swingx.search.SearchFactory;
import org.jdesktop.swingx.search.Searchable;
import org.jdesktop.swingx.treetable.TreeTableModelAdapter;
import org.jdesktop.swingx.util.DecoratorFactory;
import org.jdesktop.swingxset.util.DemoUtils;

import com.sun.swingset3.DemoProperties;

@DemoProperties(
        value = "Search Demo",
        category = "Functionality",
        description = "Demonstrates base searching functionality plus custom configuration.",
        sourceFiles = {
                "org/jdesktop/swingx/demos/search/SearchDemo.java",
                "org/jdesktop/swingx/demos/search/MatchingTextHighlighter.java",
                "org/jdesktop/swingx/demos/search/Contributor.java",
                "org/jdesktop/swingx/demos/search/Contributors.java",
                "org/jdesktop/swingx/demos/search/resources/SearchDemo.properties"
                }
)

public class SearchDemo extends JPanel {
    @SuppressWarnings("unused")
    private static final Logger LOG = Logger.getLogger(SearchDemo.class
            .getName());

    private Contributors contributors;
    private JXTreeTable treeTable;
    private JXTree tree;
    private JXList list;
    private JXTable table;
    private JCheckBox extendedMarkerBox;
    private JCheckBox painterBox;
    private JXFindBar searchPanel;
    private String[] keys = {"name", "date", "merits", "email"};
    private Map<String, StringValue> stringValues;
    
    public SearchDemo() {
        super(new BorderLayout());
        initComponents();
        initStringRepresentation();
        installCustomSearch();
        Application.getInstance().getContext().getResourceMap(getClass()).injectComponents(this);
        bind();
        installRenderers();
    }

//-------------------------- custom search logic    
    /**
     * Replaces the default findAction with one using the per-demo panel 
     * findBar.
     */
    private void installCustomSearch() {
        // <snip> Customize Search 
        // create custom find action
        Action find = new AbstractActionExt() {
            
            @Override
            public void actionPerformed(ActionEvent e) {
                updateSearchPanel(e != null ? e.getSource() : null);
            }
            
        };
        // install custom find actions on all collection components of the search demo
        installCustomFindAction(find, table);
        installCustomFindAction(find, list);
        installCustomFindAction(find, tree);
        installCustomFindAction(find, treeTable);
        // </snip>
        DemoUtils.setSnippet("Customize Search", table, list, tree, treeTable);
        // wire the update on tab changed
        JTabbedPane tabbed = SwingXUtilities.getAncestor(JTabbedPane.class, table);
        ChangeListener l  = new ChangeListener() {
            
            @Override
            public void stateChanged(final ChangeEvent e) {
                SwingUtilities.invokeLater(new Runnable() {
                    public void run() {
                        updateSearchable((JTabbedPane) e.getSource());
                    }

                });
            }
        };
        tabbed.addChangeListener(l);
        // initial searchable
        updateSearchable(tabbed);
    }

    
    /**
     * Looks for a Searchable in the potential searchable provider and
     * sets it as the current searchable of the search panel.
     * 
     * @param searchableProvider a component which 
     */
    protected void updateSearchPanel(Object searchableProvider) {
        final Searchable s = getSearchable(searchableProvider);
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                searchPanel.setSearchable(s);
                KeyboardFocusManager.getCurrentKeyboardFocusManager()
                        .focusNextComponent(searchPanel);
            }
        });
    }


    /**
     * Looks for a searchable provider in the selected tab of the JTabbedPane
     * and updates the searchPanel accordingly.
     * 
     * @param tabbed
     */
    protected void updateSearchable(JTabbedPane tabbed) {
        Component comp = tabbed.getSelectedComponent();
        if (comp instanceof JScrollPane) {
            comp = (JComponent) ((JScrollPane) comp).getViewport()
                    .getView();
        }
        updateSearchPanel(comp);
    }


    /**
     * Registers a custom find action in the target's actionMap and
     * enable incremental search on it.
     * 
     * @param find the custom find action
     * @param target the component to install the custom find action on
     */
    // <snip> Customize Search 
    private void installCustomFindAction(Action find, JComponent target) {
        // install the custom action
        target.getActionMap().put("find", find);
        // force incremental search mode 
        target.putClientProperty(AbstractSearchable.MATCH_HIGHLIGHTER, Boolean.TRUE);
        // </snip>
    }

//---------------------- renderers
    
    /**
     * Prepare different String representations.
     */
    private void initStringRepresentation() {
        stringValues = new HashMap<String, StringValue>();
        
        // <snip> Custom String Representation
        // Note: the content of each cell is always of type Contributor
        // its string representation as-seen is defined here in the StringValue
        // default: show contributor's first and last name
        StringValue nameValue = new StringValue() {

            public String getString(Object value) {
                if (value instanceof Contributor) {
                    Contributor c = (Contributor) value;
                    return c.getLastName() + ", " + c.getFirstName();
                }
                return StringValues.TO_STRING.getString(value);
            }
            
        };
        stringValues.put("name", nameValue);

        // show the joined date
        StringValue dateValue = new StringValue() {

            @Override
            public String getString(Object value) {
                if (value instanceof Contributor) {
                    return StringValues.DATE_TO_STRING.getString(
                            ((Contributor) value).getJoinedDate());
                }
                return StringValues.TO_STRING.getString(value);
            }
            
        };
        // </snip>
        stringValues.put("date", dateValue);
        
        // show the merits
        StringValue meritValue = new StringValue() {

            @Override
            public String getString(Object value) {
                if (value instanceof Contributor) {
                    return StringValues.NUMBER_TO_STRING.getString(
                            ((Contributor) value).getMerits());
                }
                return StringValues.TO_STRING.getString(value);
            }
            
        };
        stringValues.put("merits", meritValue);

        // show the email
        StringValue emailValue = new StringValue() {

            @Override
            public String getString(Object value) {
                if (value instanceof Contributor) {
                    URI mail = ((Contributor) value).getEmail();
                    // strip mailto:
                    String path = mail.toString();
                    return path.replace("mailto:", "");
                }
                return StringValues.EMPTY.getString(value);
            }
            
        };
        stringValues.put("email", emailValue);

    }

    /**
     * Install renderers which use the prepared string representations.
     * Note: this method is called after the binding (aka: attach models)
     * because it installs per-column renderers which in this setup can be done only 
     * after the columns are created. 
     */
    // <snip> Custom String Representation
    // install SwingX renderers configured with the appropriate String converter
    private void installRenderers() {
        StringValue sv = stringValues.get("name");
        table.setDefaultRenderer(Contributor.class, new DefaultTableRenderer(sv));
        list.setCellRenderer(new DefaultListRenderer(sv));
        tree.setCellRenderer(new DefaultTreeRenderer(sv));
        treeTable.setTreeCellRenderer(new DefaultTreeRenderer(sv));
        
        for (int i = 1; i < keys.length; i++) {
            installColumnRenderers(i, new DefaultTableRenderer(stringValues.get(keys[i])));
        }
        // </snip>
        // PENDING JW: make the email column use a hyperlinkRenderer once the
        // MatchingTextHighlighter can handle buttons
    }

    private void installColumnRenderers(int column, TableCellRenderer renderer) {
        if (column >= table.getColumnCount()) return;
        table.getColumn(column).setCellRenderer(renderer);
        treeTable.getColumn(column).setCellRenderer(renderer);
    }

    //----------------------- bind    
    /**
     * 
     */
    private void bind() {
        contributors = new Contributors();
        table.setModel(contributors.getTableModel());
        list.setModel(contributors.getListModel());
        tree.setModel(new DefaultTreeModel(contributors.getRootNode()));
        treeTable.setTreeTableModel(new TreeTableModelAdapter(
                tree.getModel(), contributors.getContributorNodeModel()));
        new SearchControl();        
    }
    

    public class SearchControl extends AbstractBean {
        private boolean extendedMarker;
        private boolean animatedPainter;
        private String[] tabs = {"table", "list", "tree", "treeTable", "xTreeTable"};
        private Map <String, MatchingTextHighlighter> matchingTextMarkers;
        private HashMap<String, ColorHighlighter> colorCellMarkers;
        
        public SearchControl() {
            DemoUtils.setSnippet("MatchingTextHighlighter", extendedMarkerBox, painterBox);
            initMatchMarkers();
            BindingGroup group = new BindingGroup();
            group.addBinding(Bindings.createAutoBinding(READ, 
                    extendedMarkerBox, BeanProperty.create("selected"),
                    this, BeanProperty.create("extendedMarker")));
            group.addBinding(Bindings.createAutoBinding(READ, 
                    painterBox, BeanProperty.create("selected"),
                    this, BeanProperty.create("animatedPainter")));
            group.addBinding(Bindings.createAutoBinding(READ, 
                    this, BeanProperty.create("extendedMarker"),
                    painterBox, BeanProperty.create("enabled")));
            group.bind();
        }

        public boolean isExtendedMarker() {
            return extendedMarker;
        }
        
        public void setExtendedMarker(boolean extendedMarker) {
            boolean old = isExtendedMarker();
            // <snip> Customize Search 
            // toggle between cell- and text markers
            this.extendedMarker = extendedMarker;
            if (isExtendedMarker()) {
                installMatchMarkers(matchingTextMarkers);
            } else{
                installMatchMarkers(colorCellMarkers);
            }
            // </snip>
            firePropertyChange("extendedMarker", old, isExtendedMarker());
        }
        
        public boolean isAnimatedPainter() {
            return animatedPainter;
        }
        
        public void setAnimatedPainter(boolean animatedPainter) {
            boolean old = isAnimatedPainter();
            this.animatedPainter = animatedPainter;
            updateXMatchMarkers();
            firePropertyChange("animatedPainter", old, isAnimatedPainter());
        }
        
        // <snip> Customize Search 
        // install match marker as given in the map
        private void installMatchMarkers(
                Map<String, ? extends AbstractHighlighter> markers) {
            ((AbstractSearchable) table.getSearchable())
                    .setMatchHighlighter(markers.get("table"));
            ((AbstractSearchable) list.getSearchable())
                    .setMatchHighlighter(markers.get("list"));
            ((AbstractSearchable) tree.getSearchable())
                    .setMatchHighlighter(markers.get("tree"));
            // </snip>
            ((AbstractSearchable) treeTable.getSearchable())
                    .setMatchHighlighter(markers.get("treeTable"));
        }
        

        @SuppressWarnings("unchecked")
        private void updateXMatchMarkers() {
            // <snip> Customize Search 
            // toggle text marker painter between plain and animated
            for (MatchingTextHighlighter hl : matchingTextMarkers.values()) {
                if (isAnimatedPainter()) {
                    hl.setPainter(DecoratorFactory.createAnimatedPainter());
                } else {
                    hl.setPainter(DecoratorFactory.createPlainPainter());
                }
            }
            // </snip>
        }

        /**
         * 
         */
        private void initMatchMarkers() {
            createMatchingTextMarkers();
            createColorCellMarkers();
            installMatchMarkers(colorCellMarkers);
        }
        
        private void createColorCellMarkers() {
            colorCellMarkers = new HashMap<String, ColorHighlighter>();
            Color matchColor = HighlighterFactory.LINE_PRINTER;
            for (String string : tabs) {
                colorCellMarkers.put(string, new ColorHighlighter(matchColor, null, matchColor, Color.BLACK));
            }
        }

        private void createMatchingTextMarkers() {
            matchingTextMarkers = new HashMap<String, MatchingTextHighlighter>();
            for (String string : tabs) {
                matchingTextMarkers.put(string, new XMatchingTextHighlighter()); //DecoratorFactory.createMatchingTextHighlighter());
            }
        }
        

    }

    // hack around collection comps not being searchable
    private Searchable getSearchable(Object target) {
        if (target == table) {
            return table.getSearchable();
        } 
        if (target == list) {
            return list.getSearchable();
        }
        if (target == tree) {
            return tree.getSearchable();
        }
        if (target == treeTable) {
            return treeTable.getSearchable();
        }
        return null;
    }

//------------------ init ui
    
    private void initComponents() {
        setLayout(new BorderLayout());
        
        searchPanel = SearchFactory.getInstance().createFindBar();
        add(searchPanel, BorderLayout.NORTH);
        
        table = new JXTable();
        table.setName("searchTable");
        list = new JXList(true);
        tree = new JXTree();
        treeTable = new JXTreeTable();
        
        table.setColumnControlVisible(true);
        treeTable.setColumnControlVisible(true);

        JTabbedPane tab = new JTabbedPane();
        tab.setName("searchTabs");
        addTab(tab, table, "tableTabTitle", true);
        addTab(tab, list, "listTabTitle", true);
        addTab(tab, tree, "treeTabTitle", true);
        addTab(tab, treeTable, "treeTableTabTitle", true);
        add(tab);
        
        extendedMarkerBox = new JCheckBox();
        extendedMarkerBox.setName("extendedMarkerBox");
        painterBox = new JCheckBox();
        painterBox.setName("painterBox");
        
        JPanel control = new JPanel();
        control.add(extendedMarkerBox);
        control.add(painterBox);
        add(control, BorderLayout.SOUTH);
    }

    private void addTab(JTabbedPane tab, JComponent comp, String string, boolean createScroll) {
        String name = DemoUtils.getResourceString(getClass(), string);
        tab.addTab(name, createScroll ? new JScrollPane(comp) : comp);
    }
    
    /**
     * main method allows us to run as a standalone demo.
     */
    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                JFrame frame = new JFrame(SearchDemo.class.getAnnotation(DemoProperties.class).value());
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.getContentPane().add(new SearchDemo());
                frame.setPreferredSize(new Dimension(800, 600));
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

}
