package org.limewire.ui.swing.library; import java.awt.BorderLayout; import java.awt.CardLayout; import java.awt.Color; import java.awt.Component; import java.awt.Cursor; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.Font; import java.awt.Point; import java.awt.Rectangle; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.File; import java.util.ArrayList; import java.util.List; import javax.swing.BorderFactory; import javax.swing.Icon; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.SwingUtilities; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import javax.swing.table.JTableHeader; import net.miginfocom.swing.MigLayout; import org.jdesktop.application.Resource; import org.jdesktop.jxlayer.JXLayer; import org.jdesktop.jxlayer.plaf.effect.LayerEffect; import org.jdesktop.jxlayer.plaf.ext.LockableUI; import org.jdesktop.swingx.JXButton; import org.jdesktop.swingx.JXPanel; import org.jdesktop.swingx.decorator.ColorHighlighter; import org.jdesktop.swingx.decorator.ComponentAdapter; import org.jdesktop.swingx.decorator.HighlightPredicate; import org.jdesktop.swingx.table.TableColumnExt; import org.limewire.collection.glazedlists.GlazedListsFactory; import org.limewire.core.api.Category; import org.limewire.core.api.URN; import org.limewire.core.api.library.LibraryFileList; import org.limewire.core.api.library.LibraryManager; import org.limewire.core.api.library.LocalFileItem; import org.limewire.core.api.library.LocalFileList; import org.limewire.core.api.library.SharedFileList; import org.limewire.inject.LazySingleton; import org.limewire.player.api.PlayerState; import org.limewire.ui.swing.components.HeaderBar; import org.limewire.ui.swing.components.decorators.ButtonDecorator; import org.limewire.ui.swing.components.decorators.HeaderBarDecorator; import org.limewire.ui.swing.dnd.LocalFileListTransferHandler; import org.limewire.ui.swing.library.LibraryFilterPanel.LibraryCategoryListener; import org.limewire.ui.swing.library.navigator.LibraryNavItem; import org.limewire.ui.swing.library.navigator.LibraryNavigatorPanel; import org.limewire.ui.swing.library.navigator.LibraryNavItem.NavType; import org.limewire.ui.swing.library.sharing.LibrarySharingAction; import org.limewire.ui.swing.library.sharing.LibrarySharingPanel; import org.limewire.ui.swing.library.table.AbstractLibraryFormat; import org.limewire.ui.swing.library.table.LibraryImageTable; import org.limewire.ui.swing.library.table.LibraryTable; import org.limewire.ui.swing.painter.BorderPainter.AccentType; import org.limewire.ui.swing.player.PlayerMediator; import org.limewire.ui.swing.player.PlayerMediatorListener; import org.limewire.ui.swing.player.PlayerPanel; import org.limewire.ui.swing.settings.SwingUiSettings; import org.limewire.ui.swing.table.TableCellHeaderRenderer; import org.limewire.ui.swing.table.TableColors; import org.limewire.ui.swing.util.GuiUtils; import org.limewire.ui.swing.util.I18n; import ca.odell.glazedlists.BasicEventList; import ca.odell.glazedlists.EventList; import ca.odell.glazedlists.FilterList; import ca.odell.glazedlists.event.ListEvent; import ca.odell.glazedlists.event.ListEventListener; import ca.odell.glazedlists.matchers.Matcher; import ca.odell.glazedlists.matchers.MatcherEditor; import ca.odell.glazedlists.swing.TextComponentMatcherEditor; import com.google.inject.Inject; import com.google.inject.Provider; @LazySingleton public class LibraryPanel extends JPanel { @Resource private Icon plusIcon; @Resource private Font fileCountFont; @Resource private Color fileCountColor; @Resource private Color tableOverlayColor; private static final String TABLE = "TABLE"; private static final String LIST = "LIST"; private final HeaderBar headerBar = new HeaderBar(); private final LockableUI lockableUI; private final LibraryTable libraryTable; private final LibraryNavigatorPanel libraryNavigatorPanel; private final LibrarySharingPanel librarySharingPanel; private final PublicSharedFeedbackPanel publicSharedFeedbackPanel; private final LibraryFilterPanel libraryFilterPanel; private final ButtonDecorator buttonDecorator; private final LocalFileListTransferHandler transferHandler; private final Provider<LibraryImageTable> libraryImagePanelProvider; private LibraryImageTable libraryImagePanel; private JPanel tableListPanel; private JScrollPane libraryScrollPane; private CardLayout tableListLayout; private JXButton addFilesButton; private JLabel fileCountLabel; private JXButton filterToggleButton; private Category selectedCategory; private EventList<LocalFileItem> eventList; private FilterList<LocalFileItem> filteredList; private FilterList<LocalFileItem> textFilterList; private FileCountListener fileCountListener; private LibraryNavItem selectedNavItem; @Inject public LibraryPanel(LibraryNavigatorPanel navPanel, HeaderBarDecorator headerBarDecorator, LibraryTable libraryTable, LibrarySharingPanel sharingPanel, LibraryFilterPanel libraryFilterPanel, PublicSharedFeedbackPanel publicSharedFeedbackPanel, PlayerPanel playerPanel, AddFileAction addFileAction, ButtonDecorator buttonDecorator, LibraryTransferHandler transferHandler, Provider<LibraryImageTable> libraryImagePanelProvider, LibrarySharingAction libraryAction) { super(new MigLayout("insets 0, gap 0, fill")); this.libraryNavigatorPanel = navPanel; this.libraryTable = libraryTable; this.librarySharingPanel = sharingPanel; this.libraryFilterPanel = libraryFilterPanel; this.publicSharedFeedbackPanel = publicSharedFeedbackPanel; this.buttonDecorator = buttonDecorator; this.transferHandler = transferHandler; this.libraryImagePanelProvider = libraryImagePanelProvider; this.fileCountListener = new FileCountListener(); GuiUtils.assignResources(this); this.lockableUI = new LockedUI(); layoutComponents(headerBarDecorator, playerPanel, addFileAction, libraryAction); eventList = new BasicEventList<LocalFileItem>(); selectTable(libraryFilterPanel.getSelectedTableFormat(), libraryFilterPanel.getSelectedCategory()); } private void layoutComponents(HeaderBarDecorator headerBarDecorator, PlayerPanel playerPanel, AddFileAction addFileAction, LibrarySharingAction libraryAction) { headerBarDecorator.decorateBasic(headerBar); createAddFilesButton(addFileAction, libraryAction); createFilterToggleButton(); fileCountLabel = new JLabel(); fileCountLabel.setForeground(fileCountColor); fileCountLabel.setFont(fileCountFont); headerBar.setLayout(new MigLayout("insets 0 5 1 5, gap 0, fill")); headerBar.add(addFilesButton, "push"); headerBar.add(playerPanel, "pos 0.5al 0.5al"); headerBar.add(fileCountLabel, "gapafter 5, pad -2 0 0 0"); headerBar.add(filterToggleButton); tableListLayout = new CardLayout(); tableListPanel = new JPanel(tableListLayout); libraryTable.setTransferHandler(transferHandler); libraryScrollPane = new JScrollPane(libraryTable); libraryScrollPane.setBorder(BorderFactory.createEmptyBorder()); configureEnclosingScrollPane(libraryScrollPane); JXLayer<JComponent> layer = new JXLayer<JComponent>(libraryScrollPane, lockableUI); tableListPanel.add(layer, TABLE); setupHighlighters(); add(libraryNavigatorPanel, "dock west, growy"); add(headerBar, "dock north, growx"); add(libraryFilterPanel.getComponent(), "dock north, growx, hidemode 3"); add(librarySharingPanel.getComponent(), "dock west, growy, hidemode 3"); add(tableListPanel, "grow"); add(publicSharedFeedbackPanel.getComponent(), "dock south, growx, hidemode 3"); } /** * Fills in the top right corner if a scrollbar appears with an empty table * header. */ protected void configureEnclosingScrollPane(JScrollPane scrollPane) { JTableHeader th = new JTableHeader(); th.setDefaultRenderer(new TableCellHeaderRenderer()); // Put a dummy header in the upper-right corner. final Component renderer = th.getDefaultRenderer().getTableCellRendererComponent(null, "", false, false, -1, -1); JPanel cornerComponent = new JPanel(new BorderLayout()); cornerComponent.add(renderer, BorderLayout.CENTER); scrollPane.setCorner(JScrollPane.UPPER_RIGHT_CORNER, cornerComponent); } @Inject void register(LibraryManager libraryManager, final PlayerMediator playerMediator) { //Loads the Library after Component has been realized. final LibraryFileList libraryList = libraryManager.getLibraryManagedList(); SwingUtilities.invokeLater(new Runnable(){ public void run() { selectedNavItem = libraryNavigatorPanel.getSelectedNavItem(); eventList = libraryList.getSwingModel(); selectTable(libraryFilterPanel.getSelectedTableFormat(), libraryFilterPanel.getSelectedCategory()); configureEnclosingScrollPane(libraryScrollPane); } }); // listen for selection changes in the combo box and filter the table // replacing the table header as you do. libraryFilterPanel.addSearchTabListener(new LibraryCategoryListener(){ @Override public void categorySelected(Category category) { // If selected navItem is playlist and old category playable // and new category not playable, then save playlist. if (playerMediator.isActivePlaylist(selectedNavItem) && isPlayable(selectedCategory) && !isPlayable(libraryFilterPanel.getSelectedCategory())) { playerMediator.setPlaylist(libraryTable.getPlayableList()); } selectTable(libraryFilterPanel.getSelectedTableFormat(), libraryFilterPanel.getSelectedCategory()); } }); libraryNavigatorPanel.addTableSelectionListener(new ListSelectionListener(){ @Override public void valueChanged(ListSelectionEvent e) { LibraryNavItem navItem = libraryNavigatorPanel.getSelectedNavItem(); // If previous navItem was playlist and new navItem not playlist // and category playable, then save playlist. if (playerMediator.isActivePlaylist(selectedNavItem) && !playerMediator.isActivePlaylist(navItem) && isPlayable(selectedCategory)) { playerMediator.setPlaylist(libraryTable.getPlayableList()); } selectedNavItem = navItem; clearFilters(); setPublicSharedComponentVisible(navItem); eventList = navItem.getLocalFileList().getSwingModel(); selectSharing(navItem); selectTable(libraryFilterPanel.getSelectedTableFormat(), libraryFilterPanel.getSelectedCategory()); } }); // Add player listener to repaint table when song changes or player stops. playerMediator.addMediatorListener(new PlayerMediatorListener(){ @Override public void progressUpdated(float progress) { } @Override public void songChanged(String name) { if(libraryTable.isVisible() && isPlayable(libraryFilterPanel.getSelectedCategory())) { SwingUtilities.invokeLater(new Runnable(){ public void run() { libraryTable.repaint(); } }); } } @Override public void stateChanged(PlayerState state) { if ((state == PlayerState.STOPPED || state == PlayerState.EOM || state == PlayerState.UNKNOWN) && libraryTable.isVisible() && isPlayable(libraryFilterPanel.getSelectedCategory())) { SwingUtilities.invokeLater(new Runnable(){ public void run() { libraryTable.repaint(); } }); } } }); librarySharingPanel.getComponent().addPropertyChangeListener(new PropertyChangeListener(){ @Override public void propertyChange(PropertyChangeEvent evt) { if(evt.getPropertyName().equals(LibrarySharingPanel.EDIT_MODE)) { setEditSharingModeEnabled((Boolean)evt.getNewValue()); } } }); } /** * When editing users, disables components. */ private void setEditSharingModeEnabled(boolean value) { lockableUI.setLocked(value); addFilesButton.setEnabled(!value); } /** * Returns a Rectangle contains the location and size of the table within * this container. */ public Rectangle getTableListRect() { Point location = tableListPanel.getLocation(); Dimension size = tableListPanel.getSize(); return new Rectangle(location.x, location.y, size.width, size.height); } public void selectLocalFileList(LocalFileList localFileList) { libraryNavigatorPanel.selectLocalFileList(localFileList); } public void selectAndScrollTo(File file) { if(file != null) libraryTable.selectAndScrollTo(file); } public void selectAndScrollTo(URN urn) { if(urn != null) libraryTable.selectAndScrollTo(urn); } private void selectSharing(LibraryNavItem navItem) { if(navItem != null && navItem.getLocalFileList() instanceof SharedFileList) { librarySharingPanel.setSharedFileList((SharedFileList)navItem.getLocalFileList()); } librarySharingPanel.getComponent().setVisible(navItem != null && navItem.getType() == NavType.LIST); // if the sharing panel isn't visible, ensure everything is enabled. if(!librarySharingPanel.getComponent().isVisible()) setEditSharingModeEnabled(false); } private void createImageList() { libraryImagePanel = libraryImagePanelProvider.get(); libraryImagePanel.setTransferHandler(transferHandler); tableListPanel.add(libraryImagePanel, LIST); } /** * Returns true if the specified category is playable. */ public static boolean isPlayable(Category category) { return (category == null) || (category == Category.AUDIO); } /** * Returns the current list of playable file items. */ public EventList<LocalFileItem> getPlayableList() { return libraryTable.getPlayableList(); } /** * Returns the selected display category. */ public Category getSelectedCategory() { return selectedCategory; } /** * Returns the selected library item. */ public LibraryNavItem getSelectedNavItem() { return selectedNavItem; } List<File> getSelectedFiles() { List<LocalFileItem> selected; if(selectedCategory == Category.IMAGE) { selected = libraryImagePanel.getSelection(); } else { selected = libraryTable.getSelection(); } List<File> files = new ArrayList<File>(selected.size()); for(LocalFileItem item : selected) { files.add(item.getFile()); } return files; } public List<LocalFileItem> getSelectedItems() { List<LocalFileItem> selected; if(selectedCategory == Category.IMAGE) { selected = libraryImagePanel.getSelection(); } else { selected = libraryTable.getSelection(); } return selected; } private void selectTable(AbstractLibraryFormat<LocalFileItem> libraryTableFormat, Category category) { selectedCategory = category; if(category != Category.IMAGE) { setEventListOnTable(eventList); libraryTable.setupCellRenderers(category, libraryTableFormat); libraryTable.applySavedColumnSettings(); // hide the remove button for Library Tables TableColumnExt column = libraryTable.getColumnExt(libraryTableFormat.getColumnName(libraryTableFormat.getActionColumn())); if(column != null) { column.setVisible(libraryNavigatorPanel.getSelectedNavItem().getType() != NavType.LIBRARY); } tableListLayout.show(tableListPanel, TABLE); } else { if(libraryImagePanel == null) { createImageList(); } setEventListOnImages(eventList); // hide remove button for library libraryImagePanel.setShowButtons(libraryNavigatorPanel.getSelectedNavItem().getType() != NavType.LIBRARY); tableListLayout.show(tableListPanel, LIST); } } private void createAddFilesButton(AddFileAction addFileAction, LibrarySharingAction libraryAction) { addFilesButton = new JXButton(addFileAction); addFilesButton.setIcon(plusIcon); addFilesButton.setRolloverIcon(plusIcon); addFilesButton.setPressedIcon(plusIcon); addFilesButton.addActionListener(libraryAction); addFilesButton.setBorder(BorderFactory.createEmptyBorder(0, 10, 0, 10)); buttonDecorator.decorateDarkFullImageButton(addFilesButton, AccentType.SHADOW); } private void createFilterToggleButton() { filterToggleButton = new JXButton(I18n.tr("Filter")); filterToggleButton.setSelected(SwingUiSettings.SHOW_LIBRARY_FILTERS.getValue()); filterToggleButton.addActionListener(new ActionListener(){ @Override public void actionPerformed(ActionEvent e) { filterToggleButton.setSelected(!filterToggleButton.isSelected()); libraryFilterPanel.getComponent().setVisible(filterToggleButton.isSelected()); libraryFilterPanel.clearFilters(); libraryFilterPanel.getFilterField().requestFocusInWindow(); SwingUiSettings.SHOW_LIBRARY_FILTERS.setValue(filterToggleButton.isSelected()); } }); filterToggleButton.setBorder(BorderFactory.createEmptyBorder(0, 10, 0, 10)); buttonDecorator.decorateDarkFullImageButton(filterToggleButton, AccentType.SHADOW); } private EventList<LocalFileItem> recreateFilterList(final EventList<LocalFileItem> eventList) { if(filteredList != null) { filteredList.dispose(); filteredList = null; } if(textFilterList != null) { textFilterList.removeListEventListener(fileCountListener); textFilterList.dispose(); } if(selectedCategory != null) { final Category category = selectedCategory; filteredList = GlazedListsFactory.filterList(eventList, new Matcher<LocalFileItem>() { @Override public boolean matches(LocalFileItem item) { return item.getCategory().equals(category); } }); } MatcherEditor<LocalFileItem> textMatcherEditor = new TextComponentMatcherEditor<LocalFileItem>(libraryFilterPanel.getFilterField(), new LocalFileItemFilterator(selectedCategory) ); textFilterList = GlazedListsFactory.filterList(filteredList == null ? eventList : filteredList, textMatcherEditor); setFileCount(textFilterList.size(), eventList.size()); textFilterList.addListEventListener(fileCountListener); return textFilterList; } private void setEventListOnTable(EventList<LocalFileItem> eventList) { libraryTable.setEventList(recreateFilterList(eventList), libraryFilterPanel.getSelectedTableFormat(), isPlayable(selectedCategory)); } private void setEventListOnImages(EventList<LocalFileItem> eventList) { libraryImagePanel.setEventList(recreateFilterList(eventList)); } private void setPublicSharedComponentVisible(LibraryNavItem navItem) { publicSharedFeedbackPanel.getComponent().setVisible(navItem != null && navItem.getType() == NavType.PUBLIC_SHARED); } private void setupHighlighters() { TableColors tableColors = new TableColors(); ColorHighlighter storeHighlighter = new ColorHighlighter(new GrayHighlightPredicate(), null, tableColors.getDisabledForegroundColor(), null, tableColors.getDisabledForegroundColor()); libraryTable.addHighlighter(storeHighlighter); } private void setFileCount(int filterSize, int totalCount) { if(selectedCategory == null && filterSize == totalCount) { fileCountLabel.setText(I18n.tr("{0} files", totalCount)); } else { fileCountLabel.setText(I18n.tr("{0} of {1} files", filterSize, totalCount)); } } private class GrayHighlightPredicate implements HighlightPredicate { @Override public boolean isHighlighted(Component renderer, ComponentAdapter adapter) { LibraryNavItem navItem = libraryNavigatorPanel.getSelectedNavItem(); LocalFileItem item = libraryTable.getLibraryTableModel().getElementAt(adapter.row); if( navItem.getType() == NavType.PUBLIC_SHARED || (navItem.getType() == NavType.LIST && ((SharedFileList)navItem.getLocalFileList()).getFriendIds().size() > 0)) return !item.isShareable(); return !item.isLoaded(); } } /** * Clears any active filters on the library. */ public void clearFilters() { libraryFilterPanel.clearFilters(); } /** * Selects the specified SharedFileList in the library nav and starts editing on its name. * @param sharedFileList can not be the public shared list */ public void editSharedListName(SharedFileList sharedFileList) { libraryNavigatorPanel.editSharedListName(sharedFileList); } /** * If the sharing panel is visible, try to show the edit mode. */ public void showEditMode() { if(librarySharingPanel.getComponent().isVisible()) librarySharingPanel.showEditMode(); } /** * Listens for changes on the current list and updates the * file count label. */ private class FileCountListener implements ListEventListener<LocalFileItem> { @Override public void listChanged(ListEvent<LocalFileItem> listChanges) { setFileCount(textFilterList.size(), filteredList == null ? eventList.size() : filteredList.size()); } } /** * Creates a locked layer over a table. This layer prevents the user from * interacting with the contents underneath it. */ private class LockedUI extends LockableUI { private JXPanel panel; public LockedUI(LayerEffect... lockedEffects) { super(lockedEffects); panel = new JXPanel(); panel.setBackground(tableOverlayColor); panel.setVisible(false); } @SuppressWarnings("unchecked") @Override public void installUI(JComponent c) { super.installUI(c); JXLayer<JComponent> l = (JXLayer<JComponent>) c; l.getGlassPane().setLayout(new BorderLayout()); l.getGlassPane().add(panel, BorderLayout.CENTER); } @SuppressWarnings("unchecked") public void uninstall(JComponent c) { super.uninstallUI(c); JXLayer<JComponent> l = (JXLayer<JComponent>) c; l.getGlassPane().setLayout(new FlowLayout()); l.getGlassPane().remove(panel); } @Override public void setLocked(boolean isLocked) { super.setLocked(isLocked); panel.setVisible(isLocked); } @Override public Cursor getLockedCursor() { return Cursor.getDefaultCursor(); } } }