* subtitle file view in download component

* added ByteBufferTransferable and use it as superclass of TextFileTransferable
* added ListView
* lots of refactoring
This commit is contained in:
Reinhard Pointner 2009-06-19 22:35:39 +00:00
parent 5e837fb9ce
commit 98ddfafe43
39 changed files with 1008 additions and 772 deletions

View File

@ -64,7 +64,6 @@ public final class FileBotUtilities {
public static final ExtensionFileFilter LIST_FILES = MediaTypes.getDefault().filter("application/list"); public static final ExtensionFileFilter LIST_FILES = MediaTypes.getDefault().filter("application/list");
public static final ExtensionFileFilter VIDEO_FILES = MediaTypes.getDefault().filter("video"); public static final ExtensionFileFilter VIDEO_FILES = MediaTypes.getDefault().filter("video");
public static final ExtensionFileFilter SUBTITLE_FILES = MediaTypes.getDefault().filter("subtitle"); public static final ExtensionFileFilter SUBTITLE_FILES = MediaTypes.getDefault().filter("subtitle");
public static final ExtensionFileFilter ARCHIVE_FILES = MediaTypes.getDefault().filter("archive");
public static final ExtensionFileFilter SFV_FILES = MediaTypes.getDefault().filter("verification/sfv"); public static final ExtensionFileFilter SFV_FILES = MediaTypes.getDefault().filter("verification/sfv");

View File

@ -58,7 +58,7 @@ public class MediaTypes {
} }
public String[] extensions(String name) { public List<String> extensions(String name) {
List<String> extensions = new ArrayList<String>(); List<String> extensions = new ArrayList<String>();
for (Type type : types) { for (Type type : types) {
@ -67,7 +67,7 @@ public class MediaTypes {
} }
} }
return extensions.toArray(new String[0]); return extensions;
} }
} }

View File

@ -31,18 +31,6 @@
</type> </type>
<!--
Archive
-->
<type name="archive/zip">
<extension>zip</extension>
</type>
<type name="archive/rar">
<extension>rar</extension>
</type>
<!-- <!--
Audio Audio
--> -->

Binary file not shown.

After

Width:  |  Height:  |  Size: 666 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 630 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 386 B

View File

@ -82,7 +82,6 @@ public class Torrent {
if (iterator.hasNext()) { if (iterator.hasNext()) {
path.append("/"); path.append("/");
} }
} }
Long length = decodeLong(fileMap.get("length")); Long length = decodeLong(fileMap.get("length"));

View File

@ -112,7 +112,7 @@ public class EpisodeFormatDialog extends JDialog {
JPanel content = new JPanel(new MigLayout("insets dialog, nogrid, fill")); JPanel content = new JPanel(new MigLayout("insets dialog, nogrid, fill"));
content.add(editor, "wmin 120px, h 40px!, growx, wrap 8px"); content.add(editor, "w 120px:min(pref, 420px), h 40px!, growx, wrap 8px");
content.add(new JLabel("Syntax"), "gap indent+unrel, wrap 0"); content.add(new JLabel("Syntax"), "gap indent+unrel, wrap 0");
content.add(createSyntaxPanel(), "gapx indent indent, wrap 8px"); content.add(createSyntaxPanel(), "gapx indent indent, wrap 8px");

View File

@ -71,7 +71,7 @@ class MatchAction extends AbstractAction {
} }
}; };
// 2. pass: match by season / episode numbers, or generic numeric similarity // 2. pass: match by season / episode numbers
metrics[1] = new SeasonEpisodeSimilarityMetric() { metrics[1] = new SeasonEpisodeSimilarityMetric() {
@Override @Override

View File

@ -1,15 +0,0 @@
package net.sourceforge.filebot.ui.panel.subtitle;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Map;
interface Archive {
Map<File, ByteBuffer> extract() throws IOException;
}

View File

@ -2,18 +2,15 @@
package net.sourceforge.filebot.ui.panel.subtitle; package net.sourceforge.filebot.ui.panel.subtitle;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.Collections; import java.util.Collections;
import java.util.Map;
enum ArchiveType { enum ArchiveType {
ZIP { ZIP {
@Override @Override
public Archive fromData(ByteBuffer data) { public Iterable<MemoryFile> fromData(ByteBuffer data) {
return new ZipArchive(data); return new ZipArchive(data);
} }
}, },
@ -21,7 +18,7 @@ enum ArchiveType {
RAR { RAR {
@Override @Override
public Archive fromData(ByteBuffer data) { public Iterable<MemoryFile> fromData(ByteBuffer data) {
return new RarArchive(data); return new RarArchive(data);
} }
}, },
@ -29,15 +26,9 @@ enum ArchiveType {
UNDEFINED { UNDEFINED {
@Override @Override
public Archive fromData(ByteBuffer data) { public Iterable<MemoryFile> fromData(ByteBuffer data) {
// cannot extract data, return empty archive // cannot extract data, return empty archive
return new Archive() { return Collections.emptySet();
@Override
public Map<File, ByteBuffer> extract() throws IOException {
return Collections.emptyMap();
}
};
} }
}; };
@ -53,6 +44,6 @@ enum ArchiveType {
} }
public abstract Archive fromData(ByteBuffer data); public abstract Iterable<MemoryFile> fromData(ByteBuffer data);
} }

View File

@ -0,0 +1,42 @@
package net.sourceforge.filebot.ui.panel.subtitle;
import java.nio.ByteBuffer;
class MemoryFile {
private final String path;
private final ByteBuffer data;
public MemoryFile(String path, ByteBuffer data) {
// normalize folder separator
this.path = path.replace('\\', '/');
this.data = data;
}
public String getName() {
return path.substring(path.lastIndexOf("/") + 1);
}
public String getPath() {
return path;
}
public ByteBuffer getData() {
return data.duplicate();
}
@Override
public String toString() {
return path;
}
}

View File

@ -0,0 +1,75 @@
package net.sourceforge.filebot.ui.panel.subtitle;
import java.awt.datatransfer.Transferable;
import java.nio.ByteBuffer;
import java.util.AbstractList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.swing.JComponent;
import javax.swing.JList;
import javax.swing.TransferHandler;
import net.sourceforge.filebot.ui.transfer.ByteBufferTransferable;
import net.sourceforge.filebot.ui.transfer.TransferableExportHandler;
class MemoryFileListExportHandler implements TransferableExportHandler {
public boolean canExport(JComponent component) {
JList list = (JList) component;
// can't export anything, if nothing is selected
return !list.isSelectionEmpty();
}
public List<MemoryFile> export(JComponent component) {
JList list = (JList) component;
// get selected values
final Object[] selection = list.getSelectedValues();
// as file list
return new AbstractList<MemoryFile>() {
@Override
public MemoryFile get(int index) {
return (MemoryFile) selection[index];
}
@Override
public int size() {
return selection.length;
}
};
}
@Override
public int getSourceActions(JComponent component) {
return canExport(component) ? TransferHandler.COPY_OR_MOVE : TransferHandler.NONE;
}
@Override
public Transferable createTransferable(JComponent component) {
Map<String, ByteBuffer> vfs = new HashMap<String, ByteBuffer>();
for (MemoryFile file : export(component)) {
vfs.put(file.getName(), file.getData());
}
return new ByteBufferTransferable(vfs);
}
@Override
public void exportDone(JComponent source, Transferable data, int action) {
}
}

View File

@ -2,21 +2,22 @@
package net.sourceforge.filebot.ui.panel.subtitle; package net.sourceforge.filebot.ui.panel.subtitle;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.LinkedHashMap; import java.util.ArrayList;
import java.util.Map; import java.util.Iterator;
import java.util.List;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import net.sourceforge.tuned.ByteBufferOutputStream; import net.sourceforge.tuned.ByteBufferOutputStream;
import de.innosystec.unrar.Archive;
import de.innosystec.unrar.exception.RarException; import de.innosystec.unrar.exception.RarException;
import de.innosystec.unrar.rarfile.FileHeader; import de.innosystec.unrar.rarfile.FileHeader;
class RarArchive implements Archive { class RarArchive implements Iterable<MemoryFile> {
private final ByteBuffer data; private final ByteBuffer data;
@ -26,11 +27,24 @@ class RarArchive implements Archive {
} }
public Map<File, ByteBuffer> extract() throws IOException { @Override
Map<File, ByteBuffer> vfs = new LinkedHashMap<File, ByteBuffer>(); public Iterator<MemoryFile> iterator() {
try {
// extract rar archives one at a time, because of JUnRar memory problems
synchronized (RarArchive.class) {
return extract().iterator();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public List<MemoryFile> extract() throws IOException {
List<MemoryFile> vfs = new ArrayList<MemoryFile>();
try { try {
de.innosystec.unrar.Archive rar = new de.innosystec.unrar.Archive(data.duplicate()); Archive rar = new Archive(data.duplicate());
for (FileHeader header : rar.getFileHeaders()) { for (FileHeader header : rar.getFileHeaders()) {
// ignore directory entries // ignore directory entries
@ -45,7 +59,7 @@ class RarArchive implements Archive {
rar.extractFile(header, buffer); rar.extractFile(header, buffer);
// add memory file // add memory file
vfs.put(new File(header.getFileNameString()), buffer.getByteBuffer()); vfs.add(new MemoryFile(header.getFileNameString(), buffer.getByteBuffer()));
} catch (OutOfMemoryError e) { } catch (OutOfMemoryError e) {
// ignore, there seems to be bug with JUnRar allocating lots of memory for no apparent reason // ignore, there seems to be bug with JUnRar allocating lots of memory for no apparent reason
// @see https://sourceforge.net/forum/forum.php?thread_id=2773018&forum_id=706772 // @see https://sourceforge.net/forum/forum.php?thread_id=2773018&forum_id=706772

View File

@ -0,0 +1,320 @@
package net.sourceforge.filebot.ui.panel.subtitle;
import static net.sourceforge.filebot.FileBotUtilities.*;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.util.List;
import java.util.concurrent.CancellationException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.ListModel;
import javax.swing.SwingUtilities;
import javax.swing.border.LineBorder;
import ca.odell.glazedlists.BasicEventList;
import ca.odell.glazedlists.EventList;
import ca.odell.glazedlists.FilterList;
import ca.odell.glazedlists.GlazedLists;
import ca.odell.glazedlists.ListSelection;
import ca.odell.glazedlists.ObservableElementList;
import ca.odell.glazedlists.TextFilterator;
import ca.odell.glazedlists.matchers.MatcherEditor;
import ca.odell.glazedlists.swing.EventListModel;
import ca.odell.glazedlists.swing.EventSelectionModel;
import ca.odell.glazedlists.swing.TextComponentMatcherEditor;
import net.miginfocom.swing.MigLayout;
import net.sourceforge.filebot.ResourceManager;
import net.sourceforge.filebot.ui.panel.subtitle.SubtitlePackage.Download.Phase;
import net.sourceforge.filebot.ui.transfer.DefaultTransferHandler;
import net.sourceforge.tuned.ExceptionUtilities;
import net.sourceforge.tuned.ui.ListView;
public class SubtitleDownloadComponent extends JComponent {
private EventList<SubtitlePackage> packages = new BasicEventList<SubtitlePackage>();
private EventList<MemoryFile> files = new BasicEventList<MemoryFile>();
private SubtitlePackageCellRenderer renderer = new SubtitlePackageCellRenderer();
private JTextField filterEditor = new JTextField();
public SubtitleDownloadComponent() {
JList packageList = new JList(createPackageListModel());
packageList.setFixedCellHeight(32);
packageList.setCellRenderer(renderer);
// better selection behaviour
EventSelectionModel<SubtitlePackage> packageSelection = new EventSelectionModel<SubtitlePackage>(packages);
packageSelection.setSelectionMode(ListSelection.MULTIPLE_INTERVAL_SELECTION_DEFENSIVE);
packageList.setSelectionModel(packageSelection);
// context menu and fetch on double click
packageList.addMouseListener(packageListMouseHandler);
// file list view
JList fileList = new ListView(createFileListModel()) {
@Override
protected Icon convertValueToIcon(Object value) {
if (SUBTITLE_FILES.accept(value.toString()))
return ResourceManager.getIcon("file.subtitle");
return ResourceManager.getIcon("file.unknown");
}
};
fileList.setDragEnabled(true);
fileList.setTransferHandler(new DefaultTransferHandler(null, new MemoryFileListExportHandler()));
JButton clearButton = new JButton(clearFilterAction);
clearButton.setOpaque(false);
JButton exportButton = new JButton(exportToFolderAction);
exportButton.setOpaque(false);
setLayout(new MigLayout("fill, nogrid", "[fill]", "[pref!][fill]"));
add(new JLabel("Filter:"), "gap indent:push");
add(filterEditor, "wmin 120px, gap rel");
add(clearButton, "w 24px!, h 24px!");
add(new JScrollPane(packageList), "newline");
JScrollPane scrollPane = new JScrollPane(fileList);
scrollPane.setViewportBorder(new LineBorder(fileList.getBackground()));
add(scrollPane, "newline");
add(exportButton, "w pref!, h pref!");
}
protected ListModel createPackageListModel() {
// allow filtering by language name and subtitle name
MatcherEditor<SubtitlePackage> matcherEditor = new TextComponentMatcherEditor<SubtitlePackage>(filterEditor, new TextFilterator<SubtitlePackage>() {
@Override
public void getFilterStrings(List<String> list, SubtitlePackage element) {
list.add(element.getLanguage().getName());
list.add(element.getName());
}
});
// source list
EventList<SubtitlePackage> source = getPackageModel();
// filter list
source = new FilterList<SubtitlePackage>(source, matcherEditor);
// listen to changes (e.g. download progress)
source = new ObservableElementList<SubtitlePackage>(source, GlazedLists.beanConnector(SubtitlePackage.class));
// as list model
return new EventListModel<SubtitlePackage>(source);
}
protected ListModel createFileListModel() {
// as list model
return new EventListModel<MemoryFile>(getFileModel());
}
public void reset() {
// cancel and reset download workers
for (SubtitlePackage subtitle : packages) {
subtitle.reset();
}
files.clear();
}
public EventList<SubtitlePackage> getPackageModel() {
return packages;
}
public EventList<MemoryFile> getFileModel() {
return files;
}
public void setLanguageVisible(boolean visible) {
renderer.getLanguageLabel().setVisible(visible);
}
private void fetchAll(Object[] selection) {
for (Object value : selection) {
fetch((SubtitlePackage) value);
}
}
private void fetch(final SubtitlePackage subtitle) {
if (subtitle.getDownload().isStarted()) {
// download has been started already
return;
}
// listen to download
subtitle.addPropertyChangeListener(new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getNewValue() == Phase.DONE) {
try {
files.addAll(subtitle.getDownload().get());
} catch (CancellationException e) {
// ignore cancellation
} catch (Exception e) {
Logger.getLogger("ui").log(Level.WARNING, ExceptionUtilities.getRootCauseMessage(e), e);
// reset download
subtitle.reset();
}
// listener no longer required
subtitle.removePropertyChangeListener(this);
}
}
});
// enqueue worker
subtitle.getDownload().start();
}
private final Action clearFilterAction = new AbstractAction(null, ResourceManager.getIcon("edit.clear")) {
@Override
public void actionPerformed(ActionEvent e) {
filterEditor.setText("");
}
};
private final Action exportToFolderAction = new AbstractAction("Export") {
@Override
public void actionPerformed(ActionEvent evt) {
JComponent source = (JComponent) evt.getSource();
JFileChooser fileChooser = new JFileChooser();
fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
if (fileChooser.showSaveDialog(source) == JFileChooser.APPROVE_OPTION) {
File folder = fileChooser.getSelectedFile();
for (MemoryFile file : files) {
try {
FileChannel fileChannel = new FileOutputStream(new File(folder, file.getName())).getChannel();
try {
fileChannel.write(file.getData());
} finally {
fileChannel.close();
}
} catch (IOException e) {
Logger.getLogger("ui").log(Level.SEVERE, e.getMessage(), e);
}
}
}
}
};
private final MouseListener packageListMouseHandler = new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
// fetch on double click
if (SwingUtilities.isLeftMouseButton(e) && (e.getClickCount() == 2)) {
JList list = (JList) e.getSource();
fetchAll(list.getSelectedValues());
}
}
@Override
public void mousePressed(MouseEvent e) {
maybeShowPopup(e);
}
@Override
public void mouseReleased(MouseEvent e) {
maybeShowPopup(e);
}
private void maybeShowPopup(MouseEvent e) {
if (e.isPopupTrigger()) {
JList list = (JList) e.getSource();
int index = list.locationToIndex(e.getPoint());
if (index >= 0 && !list.isSelectedIndex(index)) {
// auto-select clicked element
list.setSelectedIndex(index);
}
final Object[] selection = list.getSelectedValues();
Action downloadAction = new AbstractAction("Download", ResourceManager.getIcon("action.fetch")) {
@Override
public void actionPerformed(ActionEvent e) {
fetchAll(selection);
}
};
downloadAction.setEnabled(isPending(selection));
JPopupMenu contextMenu = new JPopupMenu();
contextMenu.add(downloadAction);
contextMenu.show(e.getComponent(), e.getX(), e.getY());
}
}
private boolean isPending(Object[] selection) {
for (Object value : selection) {
SubtitlePackage subtitle = (SubtitlePackage) value;
if (!subtitle.getDownload().isStarted()) {
// pending download found
return true;
}
}
return false;
}
};
}

View File

@ -1,125 +0,0 @@
package net.sourceforge.filebot.ui.panel.subtitle;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.List;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.ListModel;
import javax.swing.SwingUtilities;
import ca.odell.glazedlists.BasicEventList;
import ca.odell.glazedlists.EventList;
import ca.odell.glazedlists.FilterList;
import ca.odell.glazedlists.GlazedLists;
import ca.odell.glazedlists.ListSelection;
import ca.odell.glazedlists.ObservableElementList;
import ca.odell.glazedlists.TextFilterator;
import ca.odell.glazedlists.matchers.MatcherEditor;
import ca.odell.glazedlists.swing.EventListModel;
import ca.odell.glazedlists.swing.EventSelectionModel;
import ca.odell.glazedlists.swing.TextComponentMatcherEditor;
import net.miginfocom.swing.MigLayout;
import net.sourceforge.filebot.ResourceManager;
public class SubtitleListComponent extends JComponent {
private EventList<SubtitlePackage> model = new BasicEventList<SubtitlePackage>();
private EventSelectionModel<SubtitlePackage> selectionModel = new EventSelectionModel<SubtitlePackage>(model);
private SubtitleListCellRenderer renderer = new SubtitleListCellRenderer();
private JTextField filterEditor = new JTextField();
public SubtitleListComponent() {
JList list = new JList(createListModel(model));
list.setFixedCellHeight(32);
list.setCellRenderer(renderer);
list.addMouseListener(mouseListener);
selectionModel.setSelectionMode(ListSelection.MULTIPLE_INTERVAL_SELECTION_DEFENSIVE);
list.setSelectionModel(selectionModel);
JButton clearButton = new JButton(clearFilterAction);
clearButton.setOpaque(false);
setLayout(new MigLayout("fill, nogrid", "[fill]", "[pref!][fill]"));
add(new JLabel("Filter:"), "gap indent:push");
add(filterEditor, "wmin 120px, gap rel");
add(clearButton, "w 24px!, h 24px!");
add(new JScrollPane(list), "newline");
}
protected ListModel createListModel(EventList<SubtitlePackage> source) {
// allow filtering by language name and subtitle name
MatcherEditor<SubtitlePackage> matcherEditor = new TextComponentMatcherEditor<SubtitlePackage>(filterEditor, new TextFilterator<SubtitlePackage>() {
@Override
public void getFilterStrings(List<String> list, SubtitlePackage element) {
list.add(element.getLanguage().getName());
list.add(element.getName());
}
});
// filter list
source = new FilterList<SubtitlePackage>(source, matcherEditor);
// listen to changes (e.g. download progress)
source = new ObservableElementList<SubtitlePackage>(source, GlazedLists.beanConnector(SubtitlePackage.class));
// as list model
return new EventListModel<SubtitlePackage>(source);
}
public EventList<SubtitlePackage> getModel() {
return model;
}
public void setLanguageVisible(boolean visible) {
renderer.getLanguageLabel().setVisible(visible);
}
private final MouseAdapter mouseListener = new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (SwingUtilities.isLeftMouseButton(e) && (e.getClickCount() == 2)) {
JList list = (JList) e.getSource();
for (Object value : list.getSelectedValues()) {
final SubtitlePackage subtitle = (SubtitlePackage) value;
subtitle.getDownload().execute();
}
}
}
};
private final Action clearFilterAction = new AbstractAction(null, ResourceManager.getIcon("edit.clear")) {
@Override
public void actionPerformed(ActionEvent e) {
filterEditor.setText("");
}
};
}

View File

@ -2,21 +2,19 @@
package net.sourceforge.filebot.ui.panel.subtitle; package net.sourceforge.filebot.ui.panel.subtitle;
import static net.sourceforge.filebot.FileBotUtilities.*; import static java.util.Collections.*;
import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener; import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport; import java.beans.PropertyChangeSupport;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedHashMap; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.ResourceBundle; import java.util.ResourceBundle;
import java.util.Map.Entry;
import java.util.concurrent.Callable;
import javax.swing.SwingWorker; import javax.swing.SwingWorker;
import javax.swing.event.SwingPropertyChangeSupport; import javax.swing.event.SwingPropertyChangeSupport;
@ -27,20 +25,21 @@ import net.sourceforge.tuned.FileUtilities;
public class SubtitlePackage { public class SubtitlePackage {
private final String name; private final SubtitleDescriptor subtitle;
private final Language language; private final Language language;
private final ArchiveType archiveType;
private Download download; private Download download;
public SubtitlePackage(SubtitleDescriptor descriptor) { public SubtitlePackage(SubtitleDescriptor subtitle) {
name = descriptor.getName(); this.subtitle = subtitle;
language = new Language(languageCodeByName.get(descriptor.getLanguageName()), descriptor.getLanguageName());
archiveType = ArchiveType.forName(descriptor.getArchiveType()); // resolve language name
download = new Download(descriptor.getDownloadFunction(), archiveType); this.language = new Language(languageCodeByName.get(subtitle.getLanguageName()), subtitle.getLanguageName());
// initialize download worker
download = new Download(subtitle);
// forward phase events // forward phase events
download.addPropertyChangeListener(new PropertyChangeListener() { download.addPropertyChangeListener(new PropertyChangeListener() {
@ -56,7 +55,7 @@ public class SubtitlePackage {
public String getName() { public String getName() {
return name; return subtitle.getName();
} }
@ -65,8 +64,8 @@ public class SubtitlePackage {
} }
public ArchiveType getArchiveType() { public String getType() {
return archiveType; return subtitle.getType();
} }
@ -76,8 +75,12 @@ public class SubtitlePackage {
public void reset() { public void reset() {
// cancel old download
download.cancel(false);
// create new download
Download old = download; Download old = download;
download = new Download(old.function, old.archiveType); download = new Download(subtitle);
// transfer listeners // transfer listeners
for (PropertyChangeListener listener : old.getPropertyChangeSupport().getPropertyChangeListeners()) { for (PropertyChangeListener listener : old.getPropertyChangeSupport().getPropertyChangeListeners()) {
@ -91,7 +94,7 @@ public class SubtitlePackage {
@Override @Override
public String toString() { public String toString() {
return name; return subtitle.getName();
} }
@ -108,39 +111,57 @@ public class SubtitlePackage {
} }
public static class Download extends SwingWorker<Map<File, ByteBuffer>, Void> { public static class Download extends SwingWorker<List<MemoryFile>, Void> {
enum Phase { enum Phase {
PENDING, PENDING,
WAITING,
DOWNLOADING, DOWNLOADING,
EXTRACTING, EXTRACTING,
DONE DONE
} }
private final Callable<ByteBuffer> function; private final SubtitleDescriptor subtitle;
private final ArchiveType archiveType;
private Phase current = Phase.PENDING; private Phase current = Phase.PENDING;
private Download(Callable<ByteBuffer> function, ArchiveType archiveType) { private Download(SubtitleDescriptor descriptor) {
this.function = function; this.subtitle = descriptor;
this.archiveType = archiveType; }
public void start() {
setPhase(Phase.WAITING);
// enqueue worker
execute();
} }
@Override @Override
protected Map<File, ByteBuffer> doInBackground() throws Exception { protected List<MemoryFile> doInBackground() throws Exception {
setPhase(Phase.DOWNLOADING); setPhase(Phase.DOWNLOADING);
// fetch archive // fetch archive
ByteBuffer data = function.call(); ByteBuffer data = subtitle.fetch();
// abort if download has been cancelled
if (isCancelled())
return null;
setPhase(Phase.EXTRACTING); setPhase(Phase.EXTRACTING);
ArchiveType archiveType = ArchiveType.forName(subtitle.getType());
if (archiveType == ArchiveType.UNDEFINED) {
// cannot extract files from archive
return singletonList(new MemoryFile(subtitle.getName() + '.' + subtitle.getType(), data));
}
// extract contents of the archive // extract contents of the archive
Map<File, ByteBuffer> vfs = extract(archiveType, data); List<MemoryFile> vfs = extract(archiveType, data);
// if we can't extract files from a rar archive, it might actually be a zip file with the wrong extension // if we can't extract files from a rar archive, it might actually be a zip file with the wrong extension
if (vfs.isEmpty() && archiveType != ArchiveType.ZIP) { if (vfs.isEmpty() && archiveType != ArchiveType.ZIP) {
@ -156,18 +177,19 @@ public class SubtitlePackage {
} }
private Map<File, ByteBuffer> extract(ArchiveType archiveType, ByteBuffer data) throws IOException { private List<MemoryFile> extract(ArchiveType archiveType, ByteBuffer data) throws IOException {
Map<File, ByteBuffer> vfs = new LinkedHashMap<File, ByteBuffer>(); List<MemoryFile> vfs = new ArrayList<MemoryFile>();
for (Entry<File, ByteBuffer> entry : archiveType.fromData(data).extract().entrySet()) { for (MemoryFile file : archiveType.fromData(data)) {
String filename = entry.getKey().getName(); // check if file is a supported archive
ArchiveType type = ArchiveType.forName(FileUtilities.getExtension(file.getName()));
if (SUBTITLE_FILES.accept(filename)) { if (type == ArchiveType.UNDEFINED) {
// keep only subtitle files // add subtitle file
vfs.put(entry.getKey(), entry.getValue()); vfs.add(file);
} else if (ARCHIVE_FILES.accept(filename)) { } else {
// extract recursively if archive contains another archive // extract nested archives recursively
vfs.putAll(extract(ArchiveType.forName(FileUtilities.getExtension(filename)), entry.getValue())); vfs.addAll(extract(type, file.getData()));
} }
} }
@ -189,6 +211,11 @@ public class SubtitlePackage {
} }
public boolean isStarted() {
return current != Phase.PENDING;
}
public Phase getPhase() { public Phase getPhase() {
return current; return current;
} }

View File

@ -2,6 +2,8 @@
package net.sourceforge.filebot.ui.panel.subtitle; package net.sourceforge.filebot.ui.panel.subtitle;
import static net.sourceforge.filebot.FileBotUtilities.*;
import java.awt.Color; import java.awt.Color;
import java.awt.Insets; import java.awt.Insets;
@ -15,13 +17,13 @@ import net.sourceforge.filebot.ResourceManager;
import net.sourceforge.tuned.ui.AbstractFancyListCellRenderer; import net.sourceforge.tuned.ui.AbstractFancyListCellRenderer;
public class SubtitleListCellRenderer extends AbstractFancyListCellRenderer { public class SubtitlePackageCellRenderer extends AbstractFancyListCellRenderer {
private final JLabel titleLabel = new JLabel(); private final JLabel titleLabel = new JLabel();
private final JLabel languageLabel = new JLabel(); private final JLabel languageLabel = new JLabel();
public SubtitleListCellRenderer() { public SubtitlePackageCellRenderer() {
super(new Insets(5, 5, 5, 5)); super(new Insets(5, 5, 5, 5));
setHighlightingEnabled(false); setHighlightingEnabled(false);
@ -59,7 +61,14 @@ public class SubtitleListCellRenderer extends AbstractFancyListCellRenderer {
private Icon getIcon(SubtitlePackage subtitle) { private Icon getIcon(SubtitlePackage subtitle) {
switch (subtitle.getDownload().getPhase()) { switch (subtitle.getDownload().getPhase()) {
case PENDING: case PENDING:
return ResourceManager.getIcon(subtitle.getArchiveType() != ArchiveType.UNDEFINED ? "bullet.green" : "bullet.yellow"); if (ArchiveType.forName(subtitle.getType()) != ArchiveType.UNDEFINED || SUBTITLE_FILES.extensions().contains(subtitle.getType())) {
return ResourceManager.getIcon("bullet.green");
}
// unsupported archive or unknown subtitle type
return ResourceManager.getIcon("bullet.yellow");
case WAITING:
return ResourceManager.getIcon("worker.pending");
case DOWNLOADING: case DOWNLOADING:
return ResourceManager.getIcon("package.fetch"); return ResourceManager.getIcon("package.fetch");
case EXTRACTING: case EXTRACTING:

View File

@ -141,7 +141,7 @@ public class SubtitlePanel extends AbstractSearchPanel<SubtitleProvider, Subtitl
protected static class SubtitleRequestProcessor extends RequestProcessor<SubtitleRequest, SubtitlePackage> { protected static class SubtitleRequestProcessor extends RequestProcessor<SubtitleRequest, SubtitlePackage> {
public SubtitleRequestProcessor(SubtitleRequest request) { public SubtitleRequestProcessor(SubtitleRequest request) {
super(request, new SubtitleListComponent()); super(request, new SubtitleDownloadComponent());
} }
@ -172,13 +172,13 @@ public class SubtitlePanel extends AbstractSearchPanel<SubtitleProvider, Subtitl
@Override @Override
public void process(Collection<SubtitlePackage> subtitles) { public void process(Collection<SubtitlePackage> subtitles) {
getComponent().setLanguageVisible(request.getLanguageName() == null); getComponent().setLanguageVisible(request.getLanguageName() == null);
getComponent().getModel().addAll(subtitles); getComponent().getPackageModel().addAll(subtitles);
} }
@Override @Override
public SubtitleListComponent getComponent() { public SubtitleDownloadComponent getComponent() {
return (SubtitleListComponent) super.getComponent(); return (SubtitleDownloadComponent) super.getComponent();
} }

View File

@ -2,11 +2,11 @@
package net.sourceforge.filebot.ui.panel.subtitle; package net.sourceforge.filebot.ui.panel.subtitle;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.LinkedHashMap; import java.util.ArrayList;
import java.util.Map; import java.util.Iterator;
import java.util.List;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream; import java.util.zip.ZipInputStream;
@ -14,7 +14,7 @@ import net.sourceforge.tuned.ByteBufferInputStream;
import net.sourceforge.tuned.ByteBufferOutputStream; import net.sourceforge.tuned.ByteBufferOutputStream;
class ZipArchive implements Archive { class ZipArchive implements Iterable<MemoryFile> {
private final ByteBuffer data; private final ByteBuffer data;
@ -24,8 +24,18 @@ class ZipArchive implements Archive {
} }
public Map<File, ByteBuffer> extract() throws IOException { @Override
Map<File, ByteBuffer> vfs = new LinkedHashMap<File, ByteBuffer>(); public Iterator<MemoryFile> iterator() {
try {
return extract().iterator();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public List<MemoryFile> extract() throws IOException {
List<MemoryFile> vfs = new ArrayList<MemoryFile>();
// read first zip entry // read first zip entry
ZipInputStream zipInputStream = new ZipInputStream(new ByteBufferInputStream(data.duplicate())); ZipInputStream zipInputStream = new ZipInputStream(new ByteBufferInputStream(data.duplicate()));
@ -44,7 +54,7 @@ class ZipArchive implements Archive {
buffer.transferFully(zipInputStream); buffer.transferFully(zipInputStream);
// add memory file // add memory file
vfs.put(new File(zipEntry.getName()), buffer.getByteBuffer()); vfs.add(new MemoryFile(zipEntry.getName(), buffer.getByteBuffer()));
} }
} finally { } finally {
zipInputStream.close(); zipInputStream.close();

View File

@ -0,0 +1,104 @@
package net.sourceforge.filebot.ui.transfer;
import static net.sourceforge.filebot.FileBotUtilities.*;
import static net.sourceforge.filebot.Settings.*;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import net.sourceforge.tuned.TemporaryFolder;
public class ByteBufferTransferable implements Transferable {
protected final Map<String, ByteBuffer> vfs;
private FileTransferable transferable;
public ByteBufferTransferable(Map<String, ByteBuffer> vfs) {
this.vfs = vfs;
}
@Override
public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException {
if (FileTransferable.isFileListFlavor(flavor)) {
try {
// create file for transfer on demand
if (transferable == null) {
transferable = createFileTransferable();
}
return transferable.getTransferData(flavor);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
throw new UnsupportedFlavorException(flavor);
}
protected FileTransferable createFileTransferable() throws IOException {
// remove invalid characters from file name
List<File> files = new ArrayList<File>();
for (Entry<String, ByteBuffer> entry : vfs.entrySet()) {
String name = entry.getKey();
ByteBuffer data = entry.getValue().duplicate();
// write temporary file
files.add(createTemporaryFile(name, data));
}
return new FileTransferable(files);
}
protected File createTemporaryFile(String name, ByteBuffer data) throws IOException {
// remove invalid characters from file name
String validFileName = validateFileName(name);
// create new temporary file in TEMP/APP_NAME [UUID]/dnd
File temporaryFile = TemporaryFolder.getFolder(getApplicationName()).subFolder("dnd").createFile(validFileName);
// write data to file
FileChannel fileChannel = new FileOutputStream(temporaryFile).getChannel();
try {
fileChannel.write(data);
} finally {
fileChannel.close();
}
return temporaryFile;
}
@Override
public DataFlavor[] getTransferDataFlavors() {
return new DataFlavor[] {
DataFlavor.javaFileListFlavor, FileTransferable.uriListFlavor
};
}
@Override
public boolean isDataFlavorSupported(DataFlavor flavor) {
return FileTransferable.isFileListFlavor(flavor);
}
}

View File

@ -1,86 +0,0 @@
package net.sourceforge.filebot.ui.transfer;
import static net.sourceforge.filebot.FileBotUtilities.*;
import static net.sourceforge.filebot.Settings.*;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
import net.sourceforge.tuned.TemporaryFolder;
public class LazyTextFileTransferable implements Transferable {
private final String text;
private final String defaultFileName;
private FileTransferable fileTransferable = null;
public LazyTextFileTransferable(String text, String defaultFileName) {
this.text = text;
this.defaultFileName = defaultFileName;
}
@Override
public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException {
if (flavor.isFlavorTextType()) {
return text;
} else if (FileTransferable.isFileListFlavor(flavor)) {
try {
// create text file for transfer on demand
if (fileTransferable == null) {
fileTransferable = createFileTransferable();
}
return fileTransferable.getTransferData(flavor);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
throw new UnsupportedFlavorException(flavor);
}
private FileTransferable createFileTransferable() throws IOException {
// remove invalid characters from file name
String validFileName = validateFileName(defaultFileName);
// create new temporary file in TEMP/APP_NAME [UUID]/dnd
File temporaryFile = TemporaryFolder.getFolder(getApplicationName()).subFolder("dnd").createFile(validFileName);
// write text to file
FileChannel fileChannel = new FileOutputStream(temporaryFile).getChannel();
try {
fileChannel.write(Charset.forName("UTF-8").encode(text));
} finally {
fileChannel.close();
}
return new FileTransferable(temporaryFile);
}
@Override
public DataFlavor[] getTransferDataFlavors() {
return new DataFlavor[] { DataFlavor.stringFlavor, DataFlavor.javaFileListFlavor, FileTransferable.uriListFlavor };
}
@Override
public boolean isDataFlavorSupported(DataFlavor flavor) {
return flavor.isFlavorTextType() || FileTransferable.isFileListFlavor(flavor);
}
}

View File

@ -37,10 +37,7 @@ public abstract class TextFileExportHandler implements TransferableExportHandler
@Override @Override
public int getSourceActions(JComponent c) { public int getSourceActions(JComponent c) {
if (!canExport()) return canExport() ? TransferHandler.COPY_OR_MOVE : TransferHandler.NONE;
return TransferHandler.NONE;
return TransferHandler.MOVE | TransferHandler.COPY;
} }
@ -50,7 +47,7 @@ public abstract class TextFileExportHandler implements TransferableExportHandler
StringWriter buffer = new StringWriter(); StringWriter buffer = new StringWriter();
export(new PrintWriter(buffer)); export(new PrintWriter(buffer));
return new LazyTextFileTransferable(buffer.toString(), getDefaultFileName()); return new TextFileTransferable(getDefaultFileName(), buffer.toString());
} }

View File

@ -0,0 +1,73 @@
package net.sourceforge.filebot.ui.transfer;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.AbstractMap;
import java.util.Collections;
import java.util.Set;
public class TextFileTransferable extends ByteBufferTransferable {
private final String text;
public TextFileTransferable(String name, String text) {
this(name, text, Charset.forName("UTF-8"));
}
public TextFileTransferable(final String name, final String text, final Charset charset) {
// lazy data map for file transfer
super(new AbstractMap<String, ByteBuffer>() {
@Override
public Set<Entry<String, ByteBuffer>> entrySet() {
// encode text
Entry<String, ByteBuffer> entry = new SimpleEntry<String, ByteBuffer>(name, charset.encode(text));
// return memory file entry
return Collections.singleton(entry);
}
});
// text transfer
this.text = text;
}
@Override
public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException {
// check file flavor first, because text/uri-list is also text flavor
if (super.isDataFlavorSupported(flavor)) {
return super.getTransferData(flavor);
}
// check text flavor
if (flavor.isFlavorTextType()) {
return text;
}
throw new UnsupportedFlavorException(flavor);
}
@Override
public DataFlavor[] getTransferDataFlavors() {
return new DataFlavor[] {
DataFlavor.javaFileListFlavor, FileTransferable.uriListFlavor, DataFlavor.stringFlavor
};
}
@Override
public boolean isDataFlavorSupported(DataFlavor flavor) {
// file flavor or text flavor
return super.isDataFlavorSupported(flavor) || flavor.isFlavorTextType();
}
}

View File

@ -20,12 +20,12 @@ import java.util.regex.Pattern;
import javax.swing.Icon; import javax.swing.Icon;
import net.sourceforge.filebot.ResourceManager;
import org.w3c.dom.Document; import org.w3c.dom.Document;
import org.w3c.dom.Node; import org.w3c.dom.Node;
import org.xml.sax.SAXException; import org.xml.sax.SAXException;
import net.sourceforge.filebot.ResourceManager;
public class IMDbClient implements EpisodeListProvider { public class IMDbClient implements EpisodeListProvider {

View File

@ -8,7 +8,6 @@ import java.util.Collections;
import java.util.EnumMap; import java.util.EnumMap;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.concurrent.Callable;
/** /**
@ -94,19 +93,13 @@ public class OpenSubtitlesSubtitleDescriptor implements SubtitleDescriptor {
@Override @Override
public Callable<ByteBuffer> getDownloadFunction() { public ByteBuffer fetch() throws Exception {
return new Callable<ByteBuffer>() { return WebRequest.fetch(new URL(properties.get(Property.ZipDownloadLink)));
@Override
public ByteBuffer call() throws Exception {
return WebRequest.fetch(new URL(properties.get(Property.ZipDownloadLink)));
}
};
} }
@Override @Override
public String getArchiveType() { public String getType() {
return "zip"; return "zip";
} }

View File

@ -177,7 +177,7 @@ public class SublightSubtitleClient implements SubtitleProvider {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private static Entry<SubtitleLanguage, String>[] aliasList = new Entry[] { private static final Entry<SubtitleLanguage, String>[] aliasList = new Entry[] {
new SimpleEntry(SubtitleLanguage.PORTUGUESE_BRAZIL, "Brazilian"), new SimpleEntry(SubtitleLanguage.PORTUGUESE_BRAZIL, "Brazilian"),
new SimpleEntry(SubtitleLanguage.BOSNIAN_LATIN, "Bosnian"), new SimpleEntry(SubtitleLanguage.BOSNIAN_LATIN, "Bosnian"),
new SimpleEntry(SubtitleLanguage.SERBIAN_LATIN, "Serbian") new SimpleEntry(SubtitleLanguage.SERBIAN_LATIN, "Serbian")

View File

@ -2,10 +2,13 @@
package net.sourceforge.filebot.web; package net.sourceforge.filebot.web;
import java.io.ByteArrayInputStream;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.Formatter; import java.util.Formatter;
import java.util.concurrent.Callable; import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import net.sourceforge.tuned.ByteBufferOutputStream;
import net.sublight.webservice.Subtitle; import net.sublight.webservice.Subtitle;
@ -75,20 +78,32 @@ public class SublightSubtitleDescriptor implements SubtitleDescriptor {
@Override @Override
public String getArchiveType() { public String getType() {
return "zip"; return subtitle.getSubtitleType().value().toLowerCase();
} }
@Override @Override
public Callable<ByteBuffer> getDownloadFunction() { public ByteBuffer fetch() throws Exception {
return new Callable<ByteBuffer>() { byte[] archive = source.getZipArchive(subtitle);
@Override // the zip archive will contain exactly one subtitle
public ByteBuffer call() throws Exception { ZipInputStream stream = new ZipInputStream(new ByteArrayInputStream(archive));
return ByteBuffer.wrap(source.getZipArchive(subtitle));
} try {
}; // move to subtitle entry
ZipEntry entry = stream.getNextEntry();
ByteBufferOutputStream buffer = new ByteBufferOutputStream((int) entry.getSize());
// read subtitle data
buffer.transferFully(stream);
// return plain subtitle data
return buffer.getByteBuffer();
} finally {
stream.close();
}
} }

View File

@ -6,7 +6,6 @@ import static java.util.Collections.*;
import java.net.URL; import java.net.URL;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.concurrent.Callable;
public class SubsceneSubtitleDescriptor implements SubtitleDescriptor { public class SubsceneSubtitleDescriptor implements SubtitleDescriptor {
@ -43,19 +42,13 @@ public class SubsceneSubtitleDescriptor implements SubtitleDescriptor {
@Override @Override
public Callable<ByteBuffer> getDownloadFunction() { public ByteBuffer fetch() throws Exception {
return new Callable<ByteBuffer>() { return WebRequest.fetch(downloadLink, singletonMap("Referer", referer.toString()));
@Override
public ByteBuffer call() throws Exception {
return WebRequest.fetch(downloadLink, singletonMap("Referer", referer.toString()));
}
};
} }
@Override @Override
public String getArchiveType() { public String getType() {
return archiveType; return archiveType;
} }

View File

@ -3,7 +3,6 @@ package net.sourceforge.filebot.web;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.concurrent.Callable;
public interface SubtitleDescriptor { public interface SubtitleDescriptor {
@ -14,9 +13,9 @@ public interface SubtitleDescriptor {
String getLanguageName(); String getLanguageName();
String getArchiveType(); String getType();
Callable<ByteBuffer> getDownloadFunction(); ByteBuffer fetch() throws Exception;
} }

View File

@ -4,7 +4,6 @@ package net.sourceforge.filebot.web;
import java.net.URL; import java.net.URL;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.concurrent.Callable;
public class SubtitleSourceSubtitleDescriptor implements SubtitleDescriptor { public class SubtitleSourceSubtitleDescriptor implements SubtitleDescriptor {
@ -63,19 +62,13 @@ public class SubtitleSourceSubtitleDescriptor implements SubtitleDescriptor {
@Override @Override
public Callable<ByteBuffer> getDownloadFunction() { public ByteBuffer fetch() throws Exception {
return new Callable<ByteBuffer>() { return WebRequest.fetch(downloadLink);
@Override
public ByteBuffer call() throws Exception {
return WebRequest.fetch(downloadLink);
}
};
} }
@Override @Override
public String getArchiveType() { public String getType() {
return "zip"; return "zip";
} }

View File

@ -5,6 +5,8 @@ package net.sourceforge.tuned;
import java.io.File; import java.io.File;
import java.io.FileFilter; import java.io.FileFilter;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List; import java.util.List;
@ -150,6 +152,11 @@ public final class FileUtilities {
} }
public ExtensionFileFilter(Collection<String> extensions) {
this.extensions = extensions.toArray(new String[0]);
}
@Override @Override
public boolean accept(File file) { public boolean accept(File file) {
return hasExtension(file, extensions); return hasExtension(file, extensions);
@ -161,8 +168,8 @@ public final class FileUtilities {
} }
public String[] getExtensions() { public List<String> extensions() {
return extensions.clone(); return Arrays.asList(extensions);
} }
} }

View File

@ -121,9 +121,6 @@ public abstract class AbstractFancyListCellRenderer extends JPanel implements Li
protected void configureListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { protected void configureListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
setBackground(list.getBackground());
setGradientPainted(isSelected); setGradientPainted(isSelected);
setBorderPainted(isSelected); setBorderPainted(isSelected);

View File

@ -1,245 +0,0 @@
package net.sourceforge.tuned.ui;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.geom.RectangularShape;
import java.awt.geom.RoundRectangle2D;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.SwingConstants;
import javax.swing.border.EmptyBorder;
public class IconViewCellRenderer extends AbstractFancyListCellRenderer {
private final JLabel iconLabel = new JLabel();
private final JLabel titleLabel = new JLabel();
private final ContentPane contentPane = new ContentPane();
public IconViewCellRenderer() {
super(new Insets(3, 3, 3, 3), new Insets(3, 3, 3, 3));
setHighlightingEnabled(false);
contentPane.add(titleLabel);
contentPane.setBorder(new EmptyBorder(4, 4, 4, 4));
iconLabel.setHorizontalAlignment(SwingConstants.CENTER);
iconLabel.setVerticalAlignment(SwingConstants.CENTER);
JPanel contentPanel = new JPanel(new BorderLayout());
contentPanel.setOpaque(false);
Box contentPaneContainer = new Box(BoxLayout.X_AXIS);
contentPaneContainer.add(contentPane);
contentPanel.add(contentPaneContainer, BorderLayout.WEST);
add(iconLabel, BorderLayout.WEST);
add(contentPanel, BorderLayout.CENTER);
}
@Override
protected void configureListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
super.configureListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
setGradientPainted(false);
setText(value.toString());
contentPane.setGradientPainted(isSelected);
}
@Override
public void setForeground(Color fg) {
super.setForeground(fg);
// label is null while in super constructor
if (titleLabel != null) {
titleLabel.setForeground(fg);
}
}
@Override
public void setBackground(Color bg) {
super.setBackground(bg);
// label is null while in super constructor
if (titleLabel != null) {
titleLabel.setBackground(bg);
}
}
public void setIcon(Icon icon) {
iconLabel.setIcon(icon);
}
public void setText(String title) {
titleLabel.setText(title);
}
protected JComponent getContentPane() {
return contentPane;
}
private class ContentPane extends Box {
private boolean gradientPainted;
public ContentPane() {
super(BoxLayout.Y_AXIS);
setOpaque(false);
}
@Override
protected void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
RectangularShape shape = new RoundRectangle2D.Float(0, 0, getWidth(), getHeight(), 16, 16);
if (gradientPainted) {
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setPaint(getGradientStyle().getGradientPaint(shape, getGradientBeginColor(), getGradientEndColor()));
g2d.fill(shape);
}
super.paintComponent(g);
}
public void setGradientPainted(boolean gradientPainted) {
this.gradientPainted = gradientPainted;
}
}
/**
* Overridden for performance reasons.
*/
@Override
public void validate() {
// validate children, yet avoid flickering of the mouse cursor
validateTree();
}
/**
* Overridden for performance reasons.
*/
@Override
public void repaint() {
}
/**
* Overridden for performance reasons.
*/
@Override
public void repaint(long tm, int x, int y, int width, int height) {
}
/**
* Overridden for performance reasons.
*/
@Override
public void repaint(Rectangle r) {
}
/**
* Overridden for performance reasons.
*/
@Override
protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) {
}
/**
* Overridden for performance reasons.
*/
@Override
public void firePropertyChange(String propertyName, byte oldValue, byte newValue) {
}
/**
* Overridden for performance reasons.
*/
@Override
public void firePropertyChange(String propertyName, char oldValue, char newValue) {
}
/**
* Overridden for performance reasons.
*/
@Override
public void firePropertyChange(String propertyName, short oldValue, short newValue) {
}
/**
* Overridden for performance reasons.
*/
@Override
public void firePropertyChange(String propertyName, int oldValue, int newValue) {
}
/**
* Overridden for performance reasons.
*/
@Override
public void firePropertyChange(String propertyName, long oldValue, long newValue) {
}
/**
* Overridden for performance reasons.
*/
@Override
public void firePropertyChange(String propertyName, float oldValue, float newValue) {
}
/**
* Overridden for performance reasons.
*/
@Override
public void firePropertyChange(String propertyName, double oldValue, double newValue) {
}
/**
* Overridden for performance reasons.
*/
@Override
public void firePropertyChange(String propertyName, boolean oldValue, boolean newValue) {
}
}

View File

@ -1,135 +0,0 @@
package net.sourceforge.tuned.ui;
import java.awt.AlphaComposite;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Paint;
import java.awt.geom.Rectangle2D;
import javax.swing.DefaultListModel;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.ListModel;
import javax.swing.border.Border;
import javax.swing.border.CompoundBorder;
import javax.swing.border.EmptyBorder;
public class IconViewPanel extends JPanel {
private final JList list = new JList(createModel());
private final JLabel titleLabel = new JLabel();
private final JPanel headerPanel = new JPanel(new BorderLayout());
public IconViewPanel() {
super(new BorderLayout());
list.setLayoutOrientation(JList.HORIZONTAL_WRAP);
list.setVisibleRowCount(-1);
titleLabel.setFont(titleLabel.getFont().deriveFont(Font.BOLD));
headerPanel.setOpaque(false);
headerPanel.add(titleLabel, BorderLayout.WEST);
setBackground(list.getBackground());
headerPanel.setBorder(new CompoundBorder(new GradientLineBorder(290), new EmptyBorder(2, 10, 1, 5)));
list.setBorder(new EmptyBorder(5, 5, 5, 5));
JScrollPane listScrollPane = new JScrollPane(list);
listScrollPane.setBorder(null);
add(headerPanel, BorderLayout.NORTH);
add(listScrollPane, BorderLayout.CENTER);
}
protected ListModel createModel() {
return new DefaultListModel();
}
public JList getList() {
return list;
}
public JPanel getHeaderPanel() {
return headerPanel;
}
public void setTitle(String text) {
titleLabel.setText(text);
}
public String getTitle() {
return titleLabel.getText();
}
public void expand() {
// TODO expand
}
public void collapse() {
// TODO collapse
}
private class GradientLineBorder implements Border {
private int gradientLineWidth;
public GradientLineBorder(int width) {
this.gradientLineWidth = width;
}
@Override
public Insets getBorderInsets(Component c) {
return new Insets(0, 0, 1, 0);
}
@Override
public boolean isBorderOpaque() {
return false;
}
@Override
public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
Graphics2D g2d = (Graphics2D) g;
Color beginColor = list.getSelectionBackground().brighter();
Color endColor = list.getBackground();
Rectangle2D shape = new Rectangle2D.Float(x, y + height - 1, gradientLineWidth, 1);
Paint paint = GradientStyle.LEFT_TO_RIGHT.getGradientPaint(shape, beginColor, endColor);
g2d.setPaint(paint);
g2d.setComposite(AlphaComposite.SrcOver.derive(0.9f));
g2d.fill(shape);
}
}
}

View File

@ -0,0 +1,204 @@
package net.sourceforge.tuned.ui;
import static java.lang.Math.*;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import java.awt.geom.Rectangle2D;
import java.awt.image.FilteredImageSource;
import javax.swing.DefaultListCellRenderer;
import javax.swing.DropMode;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JList;
import javax.swing.ListModel;
import javax.swing.ListSelectionModel;
import javax.swing.SwingUtilities;
import javax.swing.event.MouseInputAdapter;
public class ListView extends JList {
protected final BlockSelectionHandler blockSelectionHandler = new BlockSelectionHandler();
public ListView(ListModel dataModel) {
super(dataModel);
setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
// better selection behaviour
putClientProperty("List.isFileList", Boolean.TRUE);
setDropMode(DropMode.ON);
setLayoutOrientation(JList.VERTICAL_WRAP);
setVisibleRowCount(-1);
setCellRenderer(new ListViewRenderer());
addMouseListener(blockSelectionHandler);
addMouseMotionListener(blockSelectionHandler);
}
public void addSelectionInterval(Rectangle selection) {
Point p1 = selection.getLocation();
Point p2 = new Point(p1.x + selection.width, p1.y + selection.height);
int startIndex = locationToIndex(p1);
int endIndex = locationToIndex(p2);
for (int i = startIndex; i <= endIndex; i++) {
Rectangle cell = getCellBounds(i, i);
if (cell != null && selection.intersects(cell)) {
addSelectionInterval(i, i);
}
}
}
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Rectangle selection = blockSelectionHandler.getSelection();
// paint block selection
if (selection != null) {
paintBlockSelection((Graphics2D) g, selection);
}
}
protected void paintBlockSelection(Graphics2D g2d, Rectangle selection) {
g2d.setPaint(TunedUtilities.derive(getSelectionBackground(), 0.3f));
g2d.fill(selection);
g2d.setPaint(getSelectionBackground());
g2d.draw(selection);
}
protected String convertValueToText(Object value) {
return value.toString();
}
protected Icon convertValueToIcon(Object value) {
return null;
}
protected class ListViewRenderer extends DefaultListCellRenderer {
public ListViewRenderer() {
setOpaque(false);
}
@Override
public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
Icon icon = convertValueToIcon(value);
if (isSelected && icon != null) {
// apply selection color tint
icon = new ImageIcon(createImage(new FilteredImageSource(TunedUtilities.getImage(icon).getSource(), new ColorTintImageFilter(list.getSelectionBackground(), 0.5f))));
}
setText(convertValueToText(value));
setIcon(icon);
if (isSelected) {
setBackground(list.getSelectionBackground());
setForeground(list.getSelectionForeground());
} else {
setBackground(list.getBackground());
setForeground(list.getForeground());
}
return this;
}
@Override
protected void paintComponent(Graphics g) {
// paint selection background for the text area only, not the whole cell
int iconWidth = (getIcon() == null ? 0 : getIcon().getIconHeight());
int startX = iconWidth + getIconTextGap();
Rectangle2D text = getFontMetrics(getFont()).getStringBounds(getText(), g);
g.setColor(getBackground());
g.fillRect(startX - 2, 1, (int) (text.getWidth() + 6), getHeight() - 1);
super.paintComponent(g);
}
};
protected class BlockSelectionHandler extends MouseInputAdapter {
private Rectangle selection;
private Point origin;
public void mousePressed(MouseEvent e) {
if (SwingUtilities.isLeftMouseButton(e) && !isSelectedIndex(locationToIndex(e.getPoint()))) {
origin = e.getPoint();
}
}
@Override
public void mouseDragged(MouseEvent e) {
if (origin == null)
return;
// begin selection
if (selection == null)
selection = new Rectangle();
// keep point within component bounds
Point p2 = e.getPoint();
p2.x = max(0, min(getWidth() - 1, p2.x));
p2.y = max(0, min(getHeight() - 1, p2.y));
// update selection bounds
selection.setFrameFromDiagonal(origin, p2);
// auto-scroll
ensureIndexIsVisible(locationToIndex(p2));
// update list selection
clearSelection();
addSelectionInterval(selection);
// update view
repaint();
}
@Override
public void mouseReleased(MouseEvent e) {
origin = null;
// end selection
selection = null;
// update view
repaint();
}
public Rectangle getSelection() {
return selection;
}
};
}

View File

@ -84,11 +84,4 @@ public class IMDbClientTest {
assertEquals("http://www.imdb.com/title/tt0407362/episodes", imdb.getEpisodeListLink(new MovieDescriptor("Battlestar Galactica", 2004, 407362)).toString()); assertEquals("http://www.imdb.com/title/tt0407362/episodes", imdb.getEpisodeListLink(new MovieDescriptor("Battlestar Galactica", 2004, 407362)).toString());
} }
@Test
public void removeQuotationMarks() throws Exception {
assertEquals("test", imdb.normalizeName("\"test\""));
assertEquals("inner \"quotation marks\"", imdb.normalizeName("\"inner \"quotation marks\"\""));
}
} }

View File

@ -59,15 +59,15 @@ public class SublightSubtitleClientTest {
@Test @Test
public void getSubtitleListAllLanguages() { public void getSubtitleListAllLanguages() {
List<SubtitleDescriptor> list = client.getSubtitleList(new MovieDescriptor("Babylon 5", 1994, 105946), null); List<SubtitleDescriptor> list = client.getSubtitleList(new MovieDescriptor("Terminator 2", 1991, 103064), null);
SubtitleDescriptor sample = list.get(0); SubtitleDescriptor sample = list.get(0);
assertEquals("Babylon.5.S01E01.Midnight.on.the.Firing.Line.AC3.DVDRip.DivX-AMC", sample.getName()); assertEquals("Terminator.2.Judgment.Day(1991)", sample.getName());
assertEquals("Slovenian", sample.getLanguageName()); assertEquals("English", sample.getLanguageName());
// check size // check size
assertTrue(list.size() > 45); assertTrue(list.size() > 20);
} }

View File

@ -68,7 +68,7 @@ public class SubsceneSubtitleClientTest {
assertEquals("Twin Peaks - First Season", subtitle.getName()); assertEquals("Twin Peaks - First Season", subtitle.getName());
assertEquals("Italian", subtitle.getLanguageName()); assertEquals("Italian", subtitle.getLanguageName());
assertEquals("zip", subtitle.getArchiveType()); assertEquals("zip", subtitle.getType());
} }