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