* 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:
parent
5e837fb9ce
commit
98ddfafe43
|
@ -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");
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 |
|
@ -31,7 +31,7 @@ public class Torrent {
|
||||||
|
|
||||||
private final boolean singleFileTorrent;
|
private final boolean singleFileTorrent;
|
||||||
|
|
||||||
|
|
||||||
public Torrent(File torrent) throws IOException {
|
public Torrent(File torrent) throws IOException {
|
||||||
this(decodeTorrent(torrent));
|
this(decodeTorrent(torrent));
|
||||||
}
|
}
|
||||||
|
@ -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"));
|
||||||
|
@ -173,14 +172,14 @@ public class Torrent {
|
||||||
return singleFileTorrent;
|
return singleFileTorrent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static class Entry {
|
public static class Entry {
|
||||||
|
|
||||||
private final String path;
|
private final String path;
|
||||||
|
|
||||||
private final long length;
|
private final long length;
|
||||||
|
|
||||||
|
|
||||||
public Entry(String path, long length) {
|
public Entry(String path, long length) {
|
||||||
this.path = path;
|
this.path = path;
|
||||||
this.length = length;
|
this.length = length;
|
||||||
|
|
|
@ -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");
|
||||||
|
|
|
@ -44,7 +44,7 @@ class MatchAction extends AbstractAction {
|
||||||
|
|
||||||
private final Collection<SimilarityMetric> metrics;
|
private final Collection<SimilarityMetric> metrics;
|
||||||
|
|
||||||
|
|
||||||
public MatchAction(RenameModel model) {
|
public MatchAction(RenameModel model) {
|
||||||
super("Match", ResourceManager.getIcon("action.match"));
|
super("Match", ResourceManager.getIcon("action.match"));
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -177,12 +177,12 @@ class MatchAction extends AbstractAction {
|
||||||
return progressDialog;
|
return progressDialog;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected class BackgroundMatcher extends SwingWorker<List<Match<Object, File>>, Void> implements Cancellable {
|
protected class BackgroundMatcher extends SwingWorker<List<Match<Object, File>>, Void> implements Cancellable {
|
||||||
|
|
||||||
private final Matcher<Object, File> matcher;
|
private final Matcher<Object, File> matcher;
|
||||||
|
|
||||||
|
|
||||||
public BackgroundMatcher(MatchModel<Object, File> model, Collection<SimilarityMetric> metrics) {
|
public BackgroundMatcher(MatchModel<Object, File> model, Collection<SimilarityMetric> metrics) {
|
||||||
// match names against files
|
// match names against files
|
||||||
this.matcher = new Matcher<Object, File>(model.values(), model.candidates(), metrics);
|
this.matcher = new Matcher<Object, File>(model.values(), model.candidates(), metrics);
|
||||||
|
|
|
@ -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;
|
|
||||||
|
|
||||||
}
|
|
|
@ -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);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
|
@ -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("");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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:
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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 {
|
||||||
|
|
||||||
|
|
|
@ -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";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
// the zip archive will contain exactly one subtitle
|
||||||
|
ZipInputStream stream = new ZipInputStream(new ByteArrayInputStream(archive));
|
||||||
|
|
||||||
|
try {
|
||||||
|
// move to subtitle entry
|
||||||
|
ZipEntry entry = stream.getNextEntry();
|
||||||
|
|
||||||
@Override
|
ByteBufferOutputStream buffer = new ByteBufferOutputStream((int) entry.getSize());
|
||||||
public ByteBuffer call() throws Exception {
|
|
||||||
return ByteBuffer.wrap(source.getZipArchive(subtitle));
|
// read subtitle data
|
||||||
}
|
buffer.transferFully(stream);
|
||||||
};
|
|
||||||
|
// return plain subtitle data
|
||||||
|
return buffer.getByteBuffer();
|
||||||
|
} finally {
|
||||||
|
stream.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -38,7 +38,7 @@ public abstract class AbstractFancyListCellRenderer extends JPanel implements Li
|
||||||
private static final Insets DEFAULT_PADDING = new Insets(7, 7, 7, 7);
|
private static final Insets DEFAULT_PADDING = new Insets(7, 7, 7, 7);
|
||||||
private static final Insets DEFAULT_MARGIN = new Insets(1, 1, 0, 1);
|
private static final Insets DEFAULT_MARGIN = new Insets(1, 1, 0, 1);
|
||||||
|
|
||||||
|
|
||||||
public AbstractFancyListCellRenderer() {
|
public AbstractFancyListCellRenderer() {
|
||||||
this(DEFAULT_PADDING, DEFAULT_MARGIN, null);
|
this(DEFAULT_PADDING, DEFAULT_MARGIN, null);
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
|
@ -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\"\""));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue