package org.limewire.ui.swing.search; import static org.limewire.ui.swing.util.I18n.tr; import java.awt.Color; import java.awt.Dimension; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Insets; import java.awt.event.ActionEvent; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.util.EnumMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import javax.swing.Action; import javax.swing.ButtonGroup; import javax.swing.Icon; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JToggleButton; import javax.swing.SwingUtilities; import org.jdesktop.application.Resource; import org.jdesktop.swingx.JXButton; import org.limewire.core.api.search.SearchCategory; import org.limewire.setting.evt.SettingEvent; import org.limewire.setting.evt.SettingListener; import org.limewire.ui.swing.action.AbstractAction; import org.limewire.ui.swing.components.Disposable; import org.limewire.ui.swing.components.LimeComboBox; import org.limewire.ui.swing.components.LimeComboBox.SelectionListener; import org.limewire.ui.swing.components.decorators.ButtonDecorator; import org.limewire.ui.swing.components.decorators.ComboBoxDecorator; import org.limewire.ui.swing.painter.ButtonBackgroundPainter.DrawMode; import org.limewire.ui.swing.search.model.SearchResultsModel; import org.limewire.ui.swing.search.model.SortOption; import org.limewire.ui.swing.settings.SwingUiSettings; import org.limewire.ui.swing.util.GuiUtils; import com.google.inject.Inject; import com.google.inject.assistedinject.Assisted; /** * This class manages the UI components for filtering and sorting * search results. * * @see org.limewire.ui.swing.search.SearchResultsPanel */ public class SortAndFilterPanel implements Disposable { private final ButtonDecorator buttonDecorator; /** Search results data model. */ private final SearchResultsModel searchResultsModel; /** Map of sort options and actions. */ private final Map<SortOption, Action> actionMap = new EnumMap<SortOption, Action>(SortOption.class); @Resource private Icon listViewIcon; @Resource private Icon tableViewIcon; @Resource private Font sortLabelFont; private final LimeComboBox sortCombo; private final JLabel sortLabel = new JLabel(tr("Sort by:")); private final JXButton listViewToggleButton = new JXButton(); private final JXButton tableViewToggleButton = new JXButton(); private SortOption sortBy; private boolean repopulatingCombo; private SettingListener viewTypeListener; /** * Constructs a SortAndFilterPanel with the specified search results data * model and UI decorators. */ @Inject SortAndFilterPanel( @Assisted SearchResultsModel searchResultsModel, ComboBoxDecorator comboBoxDecorator, ButtonDecorator buttonDecorator) { GuiUtils.assignResources(this); this.buttonDecorator = buttonDecorator; this.searchResultsModel = searchResultsModel; // Initialize sort actions. populateActionList(); // Initialize sort components. sortLabel.setFont(sortLabelFont); sortLabel.setForeground(Color.WHITE); sortCombo = new LimeComboBox(); comboBoxDecorator.decorateDarkFullComboBox(sortCombo); sizeSortCombo(); // Initialize components to select view type. listViewToggleButton.setModel(new JToggleButton.ToggleButtonModel()); tableViewToggleButton.setModel(new JToggleButton.ToggleButtonModel()); setSearchCategory(searchResultsModel.getSearchCategory()); configureViewButtons(); // Initialize sorting and filtering. configureSortFilter(); } /** * Removes listeners to external resources. */ @Override public void dispose() { SwingUiSettings.SEARCH_VIEW_TYPE_ID.removeSettingListener(viewTypeListener); } /** * Updates the size of the sort combobox based on the display length of the * current sort options. */ private void sizeSortCombo() { int widestActionText = 0; FontMetrics sortFontMetrics = sortCombo.getFontMetrics(sortCombo.getFont()); for (SortOption sortOption : actionMap.keySet()) { widestActionText = Math.max(widestActionText, sortFontMetrics.stringWidth(getDisplayName(sortOption))); } //Width of text plus padding for the whitespace around the text and the drop down icon Dimension sortComboDimensions = new Dimension(widestActionText + 30, sortCombo.getPreferredSize().height); sortCombo.setPreferredSize(sortComboDimensions); sortCombo.setMinimumSize(sortComboDimensions); sortCombo.setMaximumSize(sortComboDimensions); } /** * Initializes the collection of sort actions. */ private void populateActionList() { for (SortOption sortOption : SortOption.values()) { actionMap.put(sortOption, new SortAction(sortOption)); } } /** * Configures the UI components used to select the view type. */ private void configureViewButtons() { buttonDecorator.decorateDarkFullImageButton(listViewToggleButton, DrawMode.LEFT_ROUNDED); listViewToggleButton.setIcon(listViewIcon); listViewToggleButton.setPressedIcon(listViewIcon); listViewToggleButton.setToolTipText(tr("List view")); listViewToggleButton.setMargin(new Insets(0, 10, 0, 6)); listViewToggleButton.addItemListener(new ItemListener() { @Override public void itemStateChanged(ItemEvent event) { if (event.getStateChange() == ItemEvent.SELECTED) { SwingUiSettings.SEARCH_VIEW_TYPE_ID.setValue(SearchViewType.LIST.getId()); selectListView(); } } }); buttonDecorator.decorateDarkFullImageButton(tableViewToggleButton, DrawMode.RIGHT_ROUNDED); tableViewToggleButton.setIcon(tableViewIcon); tableViewToggleButton.setPressedIcon(tableViewIcon); tableViewToggleButton.setToolTipText(tr("Classic view")); tableViewToggleButton.setMargin(new Insets(0, 6, 0, 10)); tableViewToggleButton.addItemListener(new ItemListener() { @Override public void itemStateChanged(ItemEvent event) { if (event.getStateChange() == ItemEvent.SELECTED) { SwingUiSettings.SEARCH_VIEW_TYPE_ID.setValue(SearchViewType.TABLE.getId()); SwingUiSettings.SHOW_CLASSIC_REMINDER.setValue(false); selectTableView(); } } }); viewTypeListener = new SettingListener() { @Override public void settingChanged(SettingEvent evt) { SwingUtilities.invokeLater(new Runnable(){ public void run() { int newViewTypeId = SwingUiSettings.SEARCH_VIEW_TYPE_ID.getValue(); SearchViewType newSearchViewType = SearchViewType.forId(newViewTypeId); updateView(newSearchViewType); } }); } }; SwingUiSettings.SEARCH_VIEW_TYPE_ID.addSettingListener(viewTypeListener); ButtonGroup viewGroup = new ButtonGroup(); viewGroup.add(listViewToggleButton); viewGroup.add(tableViewToggleButton); int viewTypeId = SwingUiSettings.SEARCH_VIEW_TYPE_ID.getValue(); SearchViewType searchViewType = SearchViewType.forId(viewTypeId); updateView(searchViewType); } /** * Updates the UI components based on the specified view type. */ private void updateView(SearchViewType newSearchViewType) { switch (newSearchViewType) { case LIST: selectListView(); break; case TABLE: selectTableView(); break; } } /** * Updates the UI components when the List view is selected. */ private void selectListView() { tableViewToggleButton.setSelected(false); listViewToggleButton.setSelected(true); sortLabel.setVisible(true); sortCombo.setVisible(true); } /** * Updates the UI components when the Table view is selected. */ private void selectTableView() { tableViewToggleButton.setSelected(true); listViewToggleButton.setSelected(false); sortLabel.setVisible(false); sortCombo.setVisible(false); } /** * Configures the sort and filter components to work with the data model. */ private void configureSortFilter() { // Initialize sort option in data model. searchResultsModel.setSortOption(SortOption.getDefault()); // Install combobox listener to update sort order. SelectionListener listener = new SelectionListener() { @Override public void selectionChanged(Action action) { SortOption option = ((SortAction) action).getSortOption(); if (!repopulatingCombo && !option.equals(sortBy)) { // changing sort order searchResultsModel.setSortOption(option); sortBy = option; } } }; sortCombo.addSelectionListener(listener); // Trigger the initial sort. sortCombo.setSelectedAction(actionMap.get(SortOption.getDefault())); } /** * Adds the sorting and filtering components to the specified panel. */ public void layoutComponents(JPanel panel) { panel.add(sortLabel, "gapafter 5, gapbottom 2, hidemode 0"); panel.add(sortCombo, "gapafter 10, hidemode 0"); panel.add(listViewToggleButton, "right"); panel.add(tableViewToggleButton, "gapafter 5"); } /** * Clears the filter text field. * public void clearFilterBox() { filterBox.setText(""); }*/ /** * Sets the state of the view toggle buttons. * @param mode the current mode ... LIST or TABLE */ public void setMode(SearchViewType mode) { if (mode == SearchViewType.LIST) { listViewToggleButton.setSelected(true); tableViewToggleButton.setSelected(false); } else if (mode == SearchViewType.TABLE) { listViewToggleButton.setSelected(false); tableViewToggleButton.setSelected(true); } } /** * Sets the search category, and updates the sort combobox with the * appropriate list of sort selections. */ public void setSearchCategory(SearchCategory category) { Action currentItem = sortCombo.getSelectedAction(); boolean currentValid = false; repopulatingCombo = true; sortCombo.removeAllActions(); // Get sort options for category. SortOption[] options = SortOption.getSortOptions(category); // Create list of sort actions. We also determine if the current // sort action is valid for the new category. List<Action> actionList = new LinkedList<Action>(); for (SortOption option : options) { actionList.add(actionMap.get(option)); if (actionMap.get(option).equals(currentItem)) { currentValid = true; } } sortCombo.addActions(actionList); sizeSortCombo(); repopulatingCombo = false; // Set combobox to current action if valid. Otherwise, set search // model to use first sort option. if (currentValid) { sortCombo.setSelectedAction(currentItem); } else { searchResultsModel.setSortOption(options[0]); sortBy = options[0]; } } /** * Returns the display name for the specified SortOption. */ private String getDisplayName(SortOption sortOption) { switch (sortOption) { case COMPANY: return tr("Company"); case PLATFORM: return tr("Platform"); case TYPE: return tr("Type"); case DATE_CREATED: return tr("Date Created"); case QUALITY: return tr("Quality"); case YEAR: return tr("Year"); case FILE_EXTENSION: return tr("File Extension"); case TITLE: return tr("Title"); case LENGTH: return tr("Length"); case ALBUM: return tr("Album"); case ARTIST: return tr("Artist"); case SIZE_LOW_TO_HIGH: return tr("Size (low to high)"); case SIZE_HIGH_TO_LOW: return tr("Size (high to low)"); case CATEGORY: return tr("Category"); case NAME: return tr("Name"); case RELEVANCE_ITEM: return tr("Relevance"); default: return sortOption.name(); } } /** * An Action implementation for a sort option. */ private class SortAction extends AbstractAction { private final SortOption sortOption; public SortAction(SortOption sortOption) { super(getDisplayName(sortOption)); this.sortOption = sortOption; } @Override public void actionPerformed(ActionEvent e) { // Do nothing. } public SortOption getSortOption() { return sortOption; } } }