package org.limewire.ui.swing.properties;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.MouseEvent;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.swing.BorderFactory;
import javax.swing.ComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.UIManager;
import javax.swing.table.DefaultTableModel;
import javax.swing.text.JTextComponent;
import net.miginfocom.swing.MigLayout;
import org.jdesktop.application.Resource;
import org.limewire.core.api.Category;
import org.limewire.core.api.FilePropertyKey;
import org.limewire.core.api.download.DownloadItem;
import org.limewire.core.api.endpoint.RemoteHost;
import org.limewire.core.api.library.LocalFileItem;
import org.limewire.core.api.library.MetaDataException;
import org.limewire.core.api.library.MetaDataManager;
import org.limewire.core.api.library.PropertiableFile;
import org.limewire.core.api.properties.PropertyDictionary;
import org.limewire.core.api.search.SearchResult;
import org.limewire.core.api.spam.SpamManager;
import org.limewire.friend.api.Friend;
import org.limewire.ui.swing.action.AbstractAction;
import org.limewire.ui.swing.components.CollectionBackedComboBoxModel;
import org.limewire.ui.swing.components.FocusJOptionPane;
import org.limewire.ui.swing.components.HyperlinkButton;
import org.limewire.ui.swing.library.LibraryMediator;
import org.limewire.ui.swing.listener.MousePopupListener;
import org.limewire.ui.swing.properties.FileInfoDialog.FileInfoType;
import org.limewire.ui.swing.search.model.VisualSearchResult;
import org.limewire.ui.swing.util.GuiUtils;
import org.limewire.ui.swing.util.I18n;
import org.limewire.ui.swing.util.NativeLaunchUtils;
/**
* Displays meta data information about the given PropertiableFile. If the file
* is local, the meta data can be edited and saved as xml.
*/
public class FileInfoGeneralPanel implements FileInfoPanel{
@Resource private Font smallFont;
@Resource private Font mediumFont;
@Resource private Font headerFont;
private final JPanel component;
private final FileInfoType type;
private final PropertiableFile propertiableFile;
private final PropertyDictionary propertyDictionary;
private final SpamManager spamManager;
private final MetaDataManager metaDataManager;
private final LibraryMediator libraryMediator;
private final Map<FilePropertyKey, JComponent> changedProps = new HashMap<FilePropertyKey, JComponent>();
public FileInfoGeneralPanel(FileInfoType type, PropertiableFile propertiableFile,
PropertyDictionary propertyDictionary, SpamManager spamManager, MetaDataManager metaDataManager, LibraryMediator libraryMediator) {
this.type = type;
this.propertiableFile = propertiableFile;
this.propertyDictionary = propertyDictionary;
this.spamManager = spamManager;
this.metaDataManager = metaDataManager;
this.libraryMediator = libraryMediator;
component = new JPanel(new MigLayout("fillx"));
GuiUtils.assignResources(this);
init();
}
public JComponent getComponent() {
return component;
}
@Override
public boolean hasChanged() {
return changedProps.size() != 0 && propertiableFile instanceof LocalFileItem;
}
@Override
public void save() {
switch(type) {
case LOCAL_FILE:
if (hasChanged()) {
LocalFileItem item = (LocalFileItem) propertiableFile;
Map<FilePropertyKey, Object> newData = new HashMap<FilePropertyKey, Object>();
for (FilePropertyKey key : changedProps.keySet()) {
JComponent component = changedProps.get(key);
if(component instanceof JTextComponent) {
newData.put(key, ((JTextComponent)component).getText().trim());
} else if(component instanceof JComboBox) {
newData.put(key, ((JComboBox)component).getSelectedItem());
}
}
try {
metaDataManager.save(item, newData);
} catch (MetaDataException e) {
String message = I18n.tr("Unable to save metadata changes.");
FocusJOptionPane.showMessageDialog(GuiUtils.getMainFrame(), message, I18n.tr("View File Info"),
JOptionPane.INFORMATION_MESSAGE);
}
}
break;
}
}
@Override
public void unregisterListeners() {
//no listeners registered
}
private void init() {
component.setOpaque(false);
if(propertiableFile.getCategory() != Category.OTHER) {
createEditableDetails();
}
createLocation();
}
private void createEditableDetails() {
component.add(createHeaderLabel(I18n.tr("Details")), "span, wrap");
JScrollPane descriptionScrollPane = new JScrollPane(createEditableTextArea(propertiableFile.getPropertyString(FilePropertyKey.DESCRIPTION), FilePropertyKey.DESCRIPTION),
JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
switch(propertiableFile.getCategory()) {
case AUDIO:
component.add(createPlainLabel(I18n.tr("Title")), "growx, pushx 100");
component.add(createPlainLabel(I18n.tr("Artist")), "growx, span 3, pushx 50, wrap");
component.add(createEditableTextField(propertiableFile.getPropertyString(FilePropertyKey.TITLE), FilePropertyKey.TITLE), "growx, pushx 100, gapright unrelated");
component.add(createEditableTextField(propertiableFile.getPropertyString(FilePropertyKey.AUTHOR), FilePropertyKey.AUTHOR), "growx, span 3, pushx 50, wrap");
//second line
component.add(createPlainLabel(I18n.tr("Album")), "pushx 100");
component.add(createPlainLabel(I18n.tr("Genre")), "pushx 17");
component.add(createPlainLabel(I18n.tr("Year")), "pushx 17");
component.add(createPlainLabel(I18n.tr("Track")), "pushx 17, wrap");
component.add(createEditableTextField(propertiableFile.getPropertyString(FilePropertyKey.ALBUM), FilePropertyKey.ALBUM), "growx, pushx 100, gapright unrelated");
component.add(createEditableComboBox(propertiableFile.getPropertyString(FilePropertyKey.GENRE), getGenres(propertiableFile), FilePropertyKey.GENRE), "growx, pushx 17, gapright unrelated");
component.add(createEditableTextField(propertiableFile.getPropertyString(FilePropertyKey.YEAR), FilePropertyKey.YEAR), "growx, pushx 17, wmin 35, gapright unrelated");
component.add(createEditableTextField(propertiableFile.getPropertyString(FilePropertyKey.TRACK_NUMBER), FilePropertyKey.TRACK_NUMBER), "growx, wmin 35, pushx 17, wrap");
//TODO: temporarily taken out since we aren't reading/writing the description field in id3 tags
// //third line
// component.add(createPlainLabel(I18n.tr("Description")), "span, wrap");
//
// component.add(descriptionScrollPane, "span, growx, hmin 42");
break;
case VIDEO:
component.add(createPlainLabel(I18n.tr("Title")), "wrap");
component.add(createEditableTextField(propertiableFile.getPropertyString(FilePropertyKey.TITLE), FilePropertyKey.TITLE), "span, growx, wrap");
component.add(createPlainLabel(I18n.tr("Genre")), "pushx 35");
component.add(createPlainLabel(I18n.tr("Rating")), "pushx 35");
component.add(createPlainLabel(I18n.tr("Year")), "pushx 30, wrap");
component.add(createEditableComboBox(propertiableFile.getPropertyString(FilePropertyKey.GENRE), getGenres(propertiableFile), FilePropertyKey.GENRE), "growx, pushx 35, gapright unrelated");
component.add(createEditableComboBox(propertiableFile.getPropertyString(FilePropertyKey.RATING), getRatings(propertiableFile), FilePropertyKey.RATING), "growx, pushx 35, gapright unrelated");
component.add(createEditableTextField(propertiableFile.getPropertyString(FilePropertyKey.YEAR), FilePropertyKey.YEAR), "growx, wmin 35, pushx 30, wrap");
component.add(createPlainLabel(I18n.tr("Description")), "wrap");
component.add(descriptionScrollPane, "span, growx, hmin 42, wrap");
break;
case IMAGE:
component.add(createPlainLabel(I18n.tr("Title")), "wrap");
component.add(createEditableTextField(propertiableFile.getPropertyString(FilePropertyKey.TITLE), FilePropertyKey.TITLE), "growx, wrap");
component.add(createPlainLabel(I18n.tr("Description")), "wrap");
component.add(descriptionScrollPane, "growx, hmin 42, wrap");
break;
case DOCUMENT:
component.add(createPlainLabel(I18n.tr("Author")), "wrap");
component.add(createEditableTextField(propertiableFile.getPropertyString(FilePropertyKey.AUTHOR), FilePropertyKey.AUTHOR), "growx, wrap");
component.add(createPlainLabel(I18n.tr("Description")), "wrap");
component.add(descriptionScrollPane, "growx, hmin 42, wrap");
break;
case PROGRAM:
component.add(createPlainLabel(I18n.tr("Title")), "wrap");
component.add(createEditableTextField(propertiableFile.getPropertyString(FilePropertyKey.TITLE), FilePropertyKey.TITLE), "growx, span, wrap");
component.add(createPlainLabel(I18n.tr("Platform")), "growx 30");
component.add(createPlainLabel(I18n.tr("Company")), "growx 70, wrap");
component.add(createEditableComboBox(propertiableFile.getPropertyString(FilePropertyKey.PLATFORM), getPlatforms(propertiableFile), FilePropertyKey.PLATFORM), "growx 30, gapright 5");
component.add(createEditableTextField(propertiableFile.getPropertyString(FilePropertyKey.COMPANY), FilePropertyKey.COMPANY), "growx 70, wrap");
break;
case OTHER:
break;
}
}
private void createLocation() {
component.add(createHeaderLabel(I18n.tr("Location")), "span, gaptop 15, wrap");
switch(type) {
case LOCAL_FILE:
if(propertiableFile instanceof LocalFileItem) {
component.add(createLabelField(((LocalFileItem)propertiableFile).getFile().getAbsolutePath()), "span, growx, wrap");
HyperlinkButton locateOnDisk = new HyperlinkButton(
new AbstractAction(I18n.tr("Locate on Disk")) {
@Override
public void actionPerformed(ActionEvent e) {
NativeLaunchUtils.launchExplorer(((LocalFileItem)propertiableFile).getFile());
}
});
HyperlinkButton locateInLibrary = new HyperlinkButton(
new AbstractAction(I18n.tr("Locate in Library")) {
@Override
public void actionPerformed(ActionEvent e) {
component.getRootPane().getParent().setVisible(false);
libraryMediator.selectInLibrary(((LocalFileItem)propertiableFile).getFile());
}
});
component.add(locateOnDisk, "span, alignx right, split");
component.add(locateInLibrary, "gapleft 15, wrap");
}
break;
case REMOTE_FILE:
if(propertiableFile instanceof VisualSearchResult) {
final ReadOnlyTableModel model = new ReadOnlyTableModel();
final JTable table = new JTable(model);
model.setColumnIdentifiers(new Object[] { I18n.tr("Name"), I18n.tr("Address"), I18n.tr("Filename") });
for (SearchResult result : ((VisualSearchResult)propertiableFile).getCoreSearchResults()) {
for (RemoteHost host : result.getSources()) {
Friend f = host.getFriendPresence().getFriend();
model.addRow(new Object[] {
f.getRenderName(),
f.getName(),
result.getFileName()
});
}
}
component.add(new JScrollPane(table), "span, grow, wrap");
table.addMouseListener(new MousePopupListener() {
@Override
public void handlePopupMouseEvent(final MouseEvent e) {
JPopupMenu blockingMenu = new JPopupMenu();
blockingMenu.add(new AbstractAction(I18n.tr("Block Address")) {
@Override
public void actionPerformed(ActionEvent actionEvent) {
int blockRow = table.rowAtPoint(e.getPoint());
table.getSelectionModel().setSelectionInterval(blockRow, blockRow);
Object value = model.getValueAt(blockRow, 1);
if (value != null) {
addToFilterList(value.toString());
}
}
});
blockingMenu.show(table, e.getX(), e.getY());
}
});
} else if(propertiableFile instanceof SearchResult) {
String friend = ((SearchResult)propertiableFile).getSources().get(0).getFriendPresence().getFriend().getRenderName();
component.add(createLabelField(friend), "span, growx, wrap");
}
break;
case DOWNLOADING_FILE:
if(propertiableFile instanceof DownloadItem) {
File launchableFile = ((DownloadItem)propertiableFile).getDownloadingFile();
if(launchableFile != null && launchableFile.getAbsoluteFile() != null)
component.add(createLabelField(launchableFile.getAbsolutePath()), "span, growx, wrap");
else
component.add(createLabelField(propertiableFile.getFileName()), "span, growx, wrap");
HyperlinkButton locateOnDisk2 = new HyperlinkButton(
new AbstractAction(I18n.tr("Locate on Disk")) {
@Override
public void actionPerformed(ActionEvent e) {
if( ((DownloadItem)propertiableFile).getDownloadingFile() != null) {
NativeLaunchUtils.launchExplorer(((DownloadItem)propertiableFile).getDownloadingFile());
}
}
});
HyperlinkButton locateInLibrary2 = new HyperlinkButton(
new AbstractAction(I18n.tr("Locate in Library")) {
@Override
public void actionPerformed(ActionEvent e) {
component.getRootPane().getParent().setVisible(false);
libraryMediator.selectInLibrary(((DownloadItem)propertiableFile).getUrn());
}
});
component.add(locateOnDisk2, "span, alignx right, split");
component.add(locateInLibrary2, "gapleft 15, wrap");
}
break;
}
}
private JLabel createPlainLabel(String text) {
JLabel label = new JLabel(text);
label.setFont(smallFont);
return label;
}
private JLabel createHeaderLabel(String text) {
JLabel label = new JLabel(text);
label.setFont(headerFont);
return label;
}
private JTextField createEditableTextField(String text, FilePropertyKey key) {
JTextField field = new JTextField(text);
field.setCaretPosition(0);
if(type != FileInfoType.LOCAL_FILE)
field.setEditable(false);
else
changedProps.put(key, field);
field.setFont(mediumFont);
return field;
}
private JTextArea createEditableTextArea(String text, FilePropertyKey key) {
JTextArea area = new JTextArea(text);
if(type != FileInfoType.LOCAL_FILE) {
area.setEditable(false);
area.setBackground(UIManager.getLookAndFeel().getDefaults().getColor("TextField.disabledBackground"));
} else {
changedProps.put(key, area);
}
area.setFont(mediumFont);
area.setLineWrap(true);
area.setWrapStyleWord(true);
return area;
}
/**
* Returns a List of values to populate the genre combobox with.
*/
private List<String> getGenres(final PropertiableFile propertiableSeed) {
switch (propertiableSeed.getCategory()) {
case AUDIO:
return propertyDictionary.getAudioGenres();
case VIDEO:
return propertyDictionary.getVideoGenres();
default:
return Collections.emptyList();
}
}
/**
* If this combo box is editable, sets the model and selects the box, otherwise
* returns a non-editable JTextField.
*/
private JComponent createEditableComboBox(String selection, List<String> model, FilePropertyKey key) {
if(type != FileInfoType.LOCAL_FILE) {
return createEditableTextField(selection, null);
} else {
JComboBox comboBox = new JComboBox();
changedProps.put(key, comboBox);
setupComboBox(comboBox, selection, model);
return comboBox;
}
}
/**
* Loads a combo box and selects the currently selected item.
*/
private void setupComboBox(JComboBox comboBox, String current, List<String> possibles) {
if(current == null) {
current = "";
}
// If any are listed, current is non-empty, and possibles doesn't contain, add it in.
if(!possibles.contains(current) && !current.equals("") && possibles.size() > 0) {
possibles = new ArrayList<String>(possibles);
possibles.add(0, current);
possibles = Collections.unmodifiableList(possibles);
}
ComboBoxModel model = new CollectionBackedComboBoxModel(possibles);
comboBox.setModel(model);
comboBox.setSelectedItem(current);
}
/**
* Returns a list of values for the rating comboBox
*/
private List<String> getRatings(final PropertiableFile propertiableSeed) {
switch (propertiableSeed.getCategory()) {
case VIDEO:
return propertyDictionary.getVideoRatings();
default:
return Collections.emptyList();
}
}
/**
* Returns a list of values for the OS combobox.
*/
private List<String> getPlatforms(final PropertiableFile propertiableSeed) {
switch (propertiableSeed.getCategory()) {
case PROGRAM:
return propertyDictionary.getApplicationPlatforms();
default:
return Collections.emptyList();
}
}
/**
* Blacklists the given ipAddress.
*/
private void addToFilterList(String ipAddress) {
spamManager.addToBlackList(ipAddress);
}
private static class ReadOnlyTableModel extends DefaultTableModel {
@Override
public boolean isCellEditable(int row, int column) {
return false;
}
}
private JTextField createLabelField(String text) {
JTextField field = new JTextField(text);
field.setCaretPosition(0);
field.setEditable(false);
field.setOpaque(false);
field.setFont(smallFont);
field.setBorder(BorderFactory.createEmptyBorder(0,1,0,1));
return field;
}
}