package org.limewire.ui.swing.filter; import java.awt.Color; import java.awt.Component; import java.util.Comparator; import javax.swing.BorderFactory; import javax.swing.DefaultListCellRenderer; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JPanel; import javax.swing.ListSelectionModel; import javax.swing.border.Border; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import net.miginfocom.swing.MigLayout; import org.jdesktop.swingx.JXList; import org.jdesktop.swingx.decorator.ColorHighlighter; import org.jdesktop.swingx.decorator.HighlightPredicate; import org.limewire.collection.glazedlists.GlazedListsFactory; import org.limewire.core.api.Category; import org.limewire.ui.swing.components.RolloverCursorListener; import org.limewire.ui.swing.util.I18n; import org.limewire.util.Objects; import ca.odell.glazedlists.EventList; import ca.odell.glazedlists.UniqueList; import ca.odell.glazedlists.FunctionList.Function; import ca.odell.glazedlists.event.ListEvent; import ca.odell.glazedlists.event.ListEventListener; import ca.odell.glazedlists.matchers.Matcher; import ca.odell.glazedlists.swing.DefaultEventListModel; import ca.odell.glazedlists.swing.DefaultEventSelectionModel; /** * Filter component to select search results according to their categories. */ class CategoryFilter<E extends FilterableItem> extends AbstractFilter<E> { private final JPanel panel = new JPanel(); private final JLabel categoryLabel = new JLabel(); private final JXList list = new JXList(); private Category selectedCategory; private EventList<Category> categoryList; private UniqueListFactory<Category> uniqueListFactory; private UniqueList<Category> uniqueList; private DefaultEventListModel<Category> listModel; private DefaultEventSelectionModel<Category> selectionModel; /** * Constructs a CategoryFilter using the specified results list. */ public CategoryFilter(EventList<E> resultsList) { FilterResources resources = getResources(); panel.setLayout(new MigLayout("insets 0 0 0 0, gap 0!", "[left,grow]", "")); panel.setOpaque(false); categoryLabel.setFont(resources.getHeaderFont()); categoryLabel.setForeground(resources.getHeaderColor()); categoryLabel.setText(I18n.tr("Categories")); list.setCellRenderer(new CategoryCellRenderer()); list.setFont(resources.getRowFont()); list.setForeground(resources.getRowColor()); list.setOpaque(false); list.setRolloverEnabled(true); list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); // Add highlighter for rollover. list.setHighlighters(new ColorHighlighter(HighlightPredicate.ROLLOVER_ROW, resources.getHighlightBackground(), resources.getHighlightForeground())); // Add listener to show cursor on mouse over. new RolloverCursorListener().install(list); panel.add(categoryLabel, "gap 6 6, wrap"); panel.add(list , "grow"); // Apply results list to filter. initialize(resultsList); } /** * Initializes the filter using the specified list of search results. */ private void initialize(EventList<E> resultsList) { // Create list of unique category values. categoryList = createCategoryList(resultsList); uniqueListFactory = new UniqueListFactory<Category>(categoryList, new CategoryComparator()); uniqueListFactory.setName(I18n.tr("Categories")); uniqueList = uniqueListFactory.getUniqueList(); // Initialize label visibility. categoryLabel.setVisible(uniqueList.size() > 0); // Add listener to display label when needed. uniqueList.addListEventListener(new ListEventListener<Object>() { @Override public void listChanged(ListEvent<Object> listChanges) { if (!categoryLabel.isVisible() && (uniqueList.size() > 0)) { categoryLabel.setVisible(true); } else if (categoryLabel.isVisible() && (uniqueList.size() < 1)) { categoryLabel.setVisible(false); } } }); // Create list and selection models. listModel = new DefaultEventListModel<Category>(uniqueList); selectionModel = new DefaultEventSelectionModel<Category>(uniqueList); list.setModel(listModel); list.setSelectionModel(selectionModel); // Add selection listener to update filter. selectionModel.addListSelectionListener(new SelectionListener()); } @Override public JComponent getComponent() { return panel; } @Override public void reset() { selectionModel.clearSelection(); selectedCategory = null; // Deactivate filter. deactivate(); } @Override public void dispose() { // Dispose of category list. uniqueListFactory.dispose(); categoryList.dispose(); } /** * Activates the filter using the specified text description and matcher. * This method also hides the filter component. */ @Override protected void activate(String activeText, Matcher<E> matcher) { super.activate(activeText, matcher); getComponent().setVisible(false); } /** * Deactivates the filter by clearing the text description and matcher. * This method also displays the filter component. */ @Override protected void deactivate() { super.deactivate(); getComponent().setVisible(true); } /** * Returns a text description of the filter state. */ @Override public String toString() { StringBuilder buf = new StringBuilder(); buf.append(getClass().getSimpleName()).append("["); buf.append("uniqueItems=").append(uniqueList.size()); buf.append(", active=").append(isActive()); EventList<Category> selectedList = selectionModel.getSelected(); buf.append(", selection=").append((selectedList.size() > 0) ? selectedList.get(0) : "null"); buf.append("]"); return buf.toString(); } /** * Returns the number of unique categories. */ public int getCategoryCount() { return uniqueList.size(); } /** * Returns the default category. The default category is defined as the * one containing the most search results. */ public Category getDefaultCategory() { Category defaultCategory = null; int defaultCount = 0; // Find category with most results. for (Category category : uniqueList) { int count = uniqueList.getCount(category); if (count > defaultCount) { defaultCount = count; defaultCategory = category; } } return defaultCategory; } /** * Returns the currently selected category. The method returns null if * no category is selected. */ public Category getSelectedCategory() { return selectedCategory; } /** * Returns a list of category values in the specified list of search * results. */ private EventList<Category> createCategoryList(EventList<E> resultsList) { // Create list of category values. return GlazedListsFactory.simpleFunctionList(resultsList, new CategoryFunction<E>()); } /** * Listener to handle selection changes to update the matcher editor. */ private class SelectionListener implements ListSelectionListener { @Override public void valueChanged(ListSelectionEvent e) { // Skip selection change if filter is active. if (isActive()) { return; } // Get list of selected values. EventList<Category> selectedList = selectionModel.getSelected(); if (selectedList.size() > 0) { selectedCategory = selectedList.get(0); // Create new matcher and activate. Matcher<E> newMatcher = new CategoryMatcher<E>(selectedCategory); activate(selectedCategory.toString(), newMatcher); } else { selectedCategory = null; // Deactivate to clear matcher. deactivate(); } // Notify filter listeners. fireFilterChanged(CategoryFilter.this); } } /** * Cell renderer for category values. */ private class CategoryCellRenderer extends DefaultListCellRenderer { private final Color background = getResources().getBackground(); private final Border border = BorderFactory.createEmptyBorder(1, 7, 0, 7); @Override public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { Component renderer = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); if (renderer instanceof JLabel) { // Get count for category. int count = uniqueList.getCount((Category) value); // Display count in cell. StringBuilder buf = new StringBuilder(); buf.append(value.toString()).append(" (").append(count).append(")"); ((JLabel) renderer).setText(buf.toString()); // Set appearance. ((JLabel) renderer).setBackground(background); ((JLabel) renderer).setBorder(border); } return renderer; } } /** * A Comparator for category values. */ private static class CategoryComparator implements Comparator<Category> { @Override public int compare(Category cat1, Category cat2) { return Objects.compareToNullIgnoreCase(cat1.toString(), cat2.toString(), false); } } /** * A function to transform a list of visual search results into a list of * specific category values. */ private static class CategoryFunction<E extends FilterableItem> implements Function<E, Category> { @Override public Category evaluate(E item) { return item.getCategory(); } } }