package org.limewire.ui.swing.library.sharing; import java.awt.Color; import java.awt.Dimension; import java.awt.Font; import java.awt.event.ActionEvent; import java.util.ArrayList; import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.swing.AbstractAction; import javax.swing.BorderFactory; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.SwingUtilities; import javax.swing.event.ChangeEvent; import javax.swing.table.TableCellEditor; import net.miginfocom.swing.MigLayout; import org.jdesktop.application.Resource; import org.jdesktop.swingx.JXButton; import org.jdesktop.swingx.JXTable; import org.limewire.collection.glazedlists.GlazedListsFactory; import org.limewire.friend.api.Friend; import org.limewire.inject.LazySingleton; import org.limewire.ui.swing.components.HyperlinkButton; import org.limewire.ui.swing.components.PromptTextField; import org.limewire.ui.swing.components.decorators.ButtonDecorator; import org.limewire.ui.swing.components.decorators.TextFieldDecorator; import org.limewire.ui.swing.painter.BorderPainter.AccentType; import org.limewire.ui.swing.table.GlazedJXTable; 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.SortedList; import ca.odell.glazedlists.TextFilterator; import ca.odell.glazedlists.event.ListEvent; import ca.odell.glazedlists.event.ListEventListener; import ca.odell.glazedlists.gui.WritableTableFormat; import ca.odell.glazedlists.impl.swing.SwingThreadProxyEventList; import ca.odell.glazedlists.matchers.MatcherEditor; import ca.odell.glazedlists.swing.DefaultEventTableModel; import ca.odell.glazedlists.swing.TextComponentMatcherEditor; import com.google.inject.Inject; import com.google.inject.name.Named; @LazySingleton class LibrarySharingEditablePanel { @Resource private Color borderColor; @Resource private Font sharingLabelFont; @Resource private Color sharingLabelColor; @Resource private Font selectFont; @Resource private Color selectColor; private final JPanel component; private final PromptTextField filterTextField; private final HyperlinkButton allButton; private final HyperlinkButton noneButton; private final JXButton applyButton; private final HyperlinkButton cancelButton; private final Map<String, Friend> knownFriends; private final EventList<EditableSharingData> baseEventList; private final EventList<EditableSharingData> filteredList; private final JXTable friendTable; @Inject public LibrarySharingEditablePanel(ApplySharingAction applyAction, CancelSharingAction cancelAction, LibrarySharingAction libraryAction, TextFieldDecorator textFieldDecorator, ButtonDecorator buttonDecorator, @Named("known") Map<String, Friend> knownFriends) { GuiUtils.assignResources(this); this.knownFriends = knownFriends; component = new JPanel(new MigLayout("insets 0, gap 0, fillx", "[134!]", "")); component.setOpaque(false); JLabel shareLabel = new JLabel(I18n.tr("Sharing list with...")); shareLabel.setFont(sharingLabelFont); shareLabel.setForeground(sharingLabelColor); component.add(shareLabel, "gapleft 5, gaptop 5, wrap"); filterTextField = new PromptTextField(I18n.tr("Filter...")); textFieldDecorator.decorateClearablePromptField(filterTextField, AccentType.NONE); component.add(filterTextField, "gapleft 5, gaptop 4, gapright 5, wmax 124, wrap"); JLabel selectLabel = new JLabel(I18n.tr("Select")); selectLabel.setFont(selectFont); selectLabel.setForeground(selectColor); component.add(selectLabel, "gapleft 5, gaptop 2, split 3"); allButton = new HyperlinkButton(new AbstractAction(I18n.tr("all")) { @Override public void actionPerformed(ActionEvent e) { setSelectedStateForAll(true); } }); allButton.setFont(selectFont); noneButton = new HyperlinkButton(new AbstractAction(I18n.tr("none")) { @Override public void actionPerformed(ActionEvent e) { setSelectedStateForAll(false); } }); noneButton.setFont(selectFont); component.add(allButton, "gapleft 6, gaptop 2"); component.add(noneButton, "gapleft 6, gaptop 2, wrap"); baseEventList = new BasicEventList<EditableSharingData>(); SortedList<EditableSharingData> sortedList = GlazedListsFactory.sortedList(baseEventList, new FriendComparator()); MatcherEditor<EditableSharingData> matcher = new TextComponentMatcherEditor<EditableSharingData>(filterTextField, new FriendFilterator()); filteredList = GlazedListsFactory.filterList(sortedList, matcher); SwingThreadProxyEventList<EditableSharingData> stpl = GlazedListsFactory.swingThreadProxyEventList(filteredList); friendTable = new GlazedJXTable(new DefaultEventTableModel<EditableSharingData>(stpl, new EditTableFormat())) { @Override public void editingStopped(ChangeEvent e) { TableCellEditor editor = getCellEditor(); if(editor != null) { removeEditor(); } } @Override public Dimension getPreferredScrollableViewportSize() { return new Dimension(super.getPreferredScrollableViewportSize().width, getModel().getRowCount() * getRowHeight()); } }; // the table is strictly the size of the number of rows or full screen with a scrollbar // if it surpasses available space. When adding/removing friends or filtering need to // revalidate the size to correctly update the panel and table sizing. filteredList.addListEventListener(new ListEventListener<EditableSharingData>(){ @Override public void listChanged(ListEvent<EditableSharingData> listChanges) { if(LibrarySharingEditablePanel.this.getComponent().isShowing()) { SwingUtilities.invokeLater(new Runnable(){ public void run() { component.revalidate(); } }); } } }); JScrollPane scrollPane = new JScrollPane(friendTable); scrollPane.setMinimumSize(new Dimension(0,0)); scrollPane.setBorder(BorderFactory.createMatteBorder(1,0,1,0, borderColor)); friendTable.getColumnExt(0).setCellRenderer(new LibrarySharingEditableRendererEditor()); friendTable.getColumnExt(0).setCellEditor(new LibrarySharingEditableRendererEditor()); friendTable.setTableHeader(null); friendTable.setShowGrid(false, false); friendTable.setRowHeight(20); component.add(scrollPane, "growx, gaptop 3, wrap"); applyButton = new JXButton(applyAction); applyButton.setFont(selectFont); applyButton.addActionListener(libraryAction); buttonDecorator.decorateDarkFullButton(applyButton, AccentType.NONE); cancelButton = new HyperlinkButton(cancelAction); cancelButton.setFont(selectFont); cancelButton.addActionListener(libraryAction); component.add(applyButton, "split 2, gaptop 5, gapbottom 5, gapright unrelated, alignx center"); component.add(cancelButton, "gaptop 5, gapbottom 5, wrap"); } public JComponent getComponent() { return component; } void clear() { baseEventList.clear(); } void refreshSelectedList() { Set<String> selectedIds = new HashSet<String>(); for(EditableSharingData data : baseEventList) { if(data.isSelected()) { selectedIds.addAll(data.getIds()); } } addItemsToList(selectedIds); } void editWithSelectedIds(List<String> selectedIds) { filterTextField.setText(""); addItemsToList(new HashSet<String>(selectedIds)); } private void addItemsToList(Set<String> setOfIds) { baseEventList.clear(); for(Friend friend : knownFriends.values()) { baseEventList.add(new EditableSharingData(friend, setOfIds.remove(friend.getId()))); } if(!setOfIds.isEmpty()) { baseEventList.add(new EditableSharingData(new ArrayList<String>(setOfIds), true)); } component.revalidate(); } /** * Returns a list of Friends who this list is shared with. */ List<String> getSelectedFriendIds() { List<String> friends = new ArrayList<String>(); for(EditableSharingData data : baseEventList) { if(data.isSelected()) { friends.addAll(data.getIds()); } } return friends; } private void setSelectedStateForAll(boolean isSelected) { for(EditableSharingData data : filteredList) { data.setSelected(isSelected); } friendTable.repaint(); } /** * Filters on the displayed name of a Friend. */ private static class FriendFilterator implements TextFilterator<EditableSharingData> { @Override public void getFilterStrings(List<String> baseList, EditableSharingData data) { Friend friend = data.getFriend(); if(friend != null) { if(friend.getName() != null) { baseList.add(friend.getName()); } baseList.add(friend.getId()); } } } private static class FriendComparator implements Comparator<EditableSharingData> { @Override public int compare(EditableSharingData data1, EditableSharingData data2) { Friend friend1 = data1.getFriend(); Friend friend2 = data2.getFriend(); if(friend1 == friend2) return 0; if(friend1 == null || friend2 == null) { if(friend1 == null && friend2 == null) return 0; else if(friend1 == null) return 1; else return -1; } else return friend1.getRenderName().compareToIgnoreCase(friend2.getRenderName()); } } private static class EditTableFormat implements WritableTableFormat<EditableSharingData> { @Override public int getColumnCount() { return 1; } @Override public String getColumnName(int column) { return ""; } @Override public Object getColumnValue(EditableSharingData baseObject, int column) { return baseObject; } @Override public boolean isEditable(EditableSharingData baseObject, int column) { return true; } @Override public EditableSharingData setColumnValue(EditableSharingData baseObject, Object editedValue, int column) { return baseObject; } } }