* 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 VIDEO_FILES = MediaTypes.getDefault().filter("video");
|
||||
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");
|
||||
|
||||
|
||||
|
|
|
@ -58,7 +58,7 @@ public class MediaTypes {
|
|||
}
|
||||
|
||||
|
||||
public String[] extensions(String name) {
|
||||
public List<String> extensions(String name) {
|
||||
List<String> extensions = new ArrayList<String>();
|
||||
|
||||
for (Type type : types) {
|
||||
|
@ -67,7 +67,7 @@ public class MediaTypes {
|
|||
}
|
||||
}
|
||||
|
||||
return extensions.toArray(new String[0]);
|
||||
return extensions;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -31,18 +31,6 @@
|
|||
</type>
|
||||
|
||||
|
||||
<!--
|
||||
Archive
|
||||
-->
|
||||
<type name="archive/zip">
|
||||
<extension>zip</extension>
|
||||
</type>
|
||||
|
||||
<type name="archive/rar">
|
||||
<extension>rar</extension>
|
||||
</type>
|
||||
|
||||
|
||||
<!--
|
||||
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 |
|
@ -82,7 +82,6 @@ public class Torrent {
|
|||
if (iterator.hasNext()) {
|
||||
path.append("/");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Long length = decodeLong(fileMap.get("length"));
|
||||
|
|
|
@ -112,7 +112,7 @@ public class EpisodeFormatDialog extends JDialog {
|
|||
|
||||
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(createSyntaxPanel(), "gapx indent indent, wrap 8px");
|
||||
|
|
|
@ -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() {
|
||||
|
||||
@Override
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
enum ArchiveType {
|
||||
ZIP {
|
||||
|
||||
@Override
|
||||
public Archive fromData(ByteBuffer data) {
|
||||
public Iterable<MemoryFile> fromData(ByteBuffer data) {
|
||||
return new ZipArchive(data);
|
||||
}
|
||||
},
|
||||
|
@ -21,7 +18,7 @@ enum ArchiveType {
|
|||
RAR {
|
||||
|
||||
@Override
|
||||
public Archive fromData(ByteBuffer data) {
|
||||
public Iterable<MemoryFile> fromData(ByteBuffer data) {
|
||||
return new RarArchive(data);
|
||||
}
|
||||
},
|
||||
|
@ -29,15 +26,9 @@ enum ArchiveType {
|
|||
UNDEFINED {
|
||||
|
||||
@Override
|
||||
public Archive fromData(ByteBuffer data) {
|
||||
public Iterable<MemoryFile> fromData(ByteBuffer data) {
|
||||
// cannot extract data, return empty archive
|
||||
return new Archive() {
|
||||
|
||||
@Override
|
||||
public Map<File, ByteBuffer> extract() throws IOException {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
};
|
||||
return Collections.emptySet();
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import net.sourceforge.tuned.ByteBufferOutputStream;
|
||||
|
||||
import de.innosystec.unrar.Archive;
|
||||
import de.innosystec.unrar.exception.RarException;
|
||||
import de.innosystec.unrar.rarfile.FileHeader;
|
||||
|
||||
|
||||
class RarArchive implements Archive {
|
||||
class RarArchive implements Iterable<MemoryFile> {
|
||||
|
||||
private final ByteBuffer data;
|
||||
|
||||
|
@ -26,11 +27,24 @@ class RarArchive implements Archive {
|
|||
}
|
||||
|
||||
|
||||
public Map<File, ByteBuffer> extract() throws IOException {
|
||||
Map<File, ByteBuffer> vfs = new LinkedHashMap<File, ByteBuffer>();
|
||||
@Override
|
||||
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 {
|
||||
de.innosystec.unrar.Archive rar = new de.innosystec.unrar.Archive(data.duplicate());
|
||||
Archive rar = new Archive(data.duplicate());
|
||||
|
||||
for (FileHeader header : rar.getFileHeaders()) {
|
||||
// ignore directory entries
|
||||
|
@ -45,7 +59,7 @@ class RarArchive implements Archive {
|
|||
rar.extractFile(header, buffer);
|
||||
|
||||
// add memory file
|
||||
vfs.put(new File(header.getFileNameString()), buffer.getByteBuffer());
|
||||
vfs.add(new MemoryFile(header.getFileNameString(), buffer.getByteBuffer()));
|
||||
} catch (OutOfMemoryError e) {
|
||||
// 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
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
||||
import static net.sourceforge.filebot.FileBotUtilities.*;
|
||||
import static java.util.Collections.*;
|
||||
|
||||
import java.beans.PropertyChangeEvent;
|
||||
import java.beans.PropertyChangeListener;
|
||||
import java.beans.PropertyChangeSupport;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import javax.swing.SwingWorker;
|
||||
import javax.swing.event.SwingPropertyChangeSupport;
|
||||
|
@ -27,20 +25,21 @@ import net.sourceforge.tuned.FileUtilities;
|
|||
|
||||
public class SubtitlePackage {
|
||||
|
||||
private final String name;
|
||||
private final SubtitleDescriptor subtitle;
|
||||
|
||||
private final Language language;
|
||||
|
||||
private final ArchiveType archiveType;
|
||||
|
||||
private Download download;
|
||||
|
||||
|
||||
public SubtitlePackage(SubtitleDescriptor descriptor) {
|
||||
name = descriptor.getName();
|
||||
language = new Language(languageCodeByName.get(descriptor.getLanguageName()), descriptor.getLanguageName());
|
||||
archiveType = ArchiveType.forName(descriptor.getArchiveType());
|
||||
download = new Download(descriptor.getDownloadFunction(), archiveType);
|
||||
public SubtitlePackage(SubtitleDescriptor subtitle) {
|
||||
this.subtitle = subtitle;
|
||||
|
||||
// resolve language name
|
||||
this.language = new Language(languageCodeByName.get(subtitle.getLanguageName()), subtitle.getLanguageName());
|
||||
|
||||
// initialize download worker
|
||||
download = new Download(subtitle);
|
||||
|
||||
// forward phase events
|
||||
download.addPropertyChangeListener(new PropertyChangeListener() {
|
||||
|
@ -56,7 +55,7 @@ public class SubtitlePackage {
|
|||
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
return subtitle.getName();
|
||||
}
|
||||
|
||||
|
||||
|
@ -65,8 +64,8 @@ public class SubtitlePackage {
|
|||
}
|
||||
|
||||
|
||||
public ArchiveType getArchiveType() {
|
||||
return archiveType;
|
||||
public String getType() {
|
||||
return subtitle.getType();
|
||||
}
|
||||
|
||||
|
||||
|
@ -76,8 +75,12 @@ public class SubtitlePackage {
|
|||
|
||||
|
||||
public void reset() {
|
||||
// cancel old download
|
||||
download.cancel(false);
|
||||
|
||||
// create new download
|
||||
Download old = download;
|
||||
download = new Download(old.function, old.archiveType);
|
||||
download = new Download(subtitle);
|
||||
|
||||
// transfer listeners
|
||||
for (PropertyChangeListener listener : old.getPropertyChangeSupport().getPropertyChangeListeners()) {
|
||||
|
@ -91,7 +94,7 @@ public class SubtitlePackage {
|
|||
|
||||
@Override
|
||||
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 {
|
||||
PENDING,
|
||||
WAITING,
|
||||
DOWNLOADING,
|
||||
EXTRACTING,
|
||||
DONE
|
||||
}
|
||||
|
||||
|
||||
private final Callable<ByteBuffer> function;
|
||||
private final ArchiveType archiveType;
|
||||
private final SubtitleDescriptor subtitle;
|
||||
|
||||
private Phase current = Phase.PENDING;
|
||||
|
||||
|
||||
private Download(Callable<ByteBuffer> function, ArchiveType archiveType) {
|
||||
this.function = function;
|
||||
this.archiveType = archiveType;
|
||||
private Download(SubtitleDescriptor descriptor) {
|
||||
this.subtitle = descriptor;
|
||||
}
|
||||
|
||||
|
||||
public void start() {
|
||||
setPhase(Phase.WAITING);
|
||||
|
||||
// enqueue worker
|
||||
execute();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected Map<File, ByteBuffer> doInBackground() throws Exception {
|
||||
protected List<MemoryFile> doInBackground() throws Exception {
|
||||
setPhase(Phase.DOWNLOADING);
|
||||
|
||||
// fetch archive
|
||||
ByteBuffer data = function.call();
|
||||
ByteBuffer data = subtitle.fetch();
|
||||
|
||||
// abort if download has been cancelled
|
||||
if (isCancelled())
|
||||
return null;
|
||||
|
||||
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
|
||||
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 (vfs.isEmpty() && archiveType != ArchiveType.ZIP) {
|
||||
|
@ -156,18 +177,19 @@ public class SubtitlePackage {
|
|||
}
|
||||
|
||||
|
||||
private Map<File, ByteBuffer> extract(ArchiveType archiveType, ByteBuffer data) throws IOException {
|
||||
Map<File, ByteBuffer> vfs = new LinkedHashMap<File, ByteBuffer>();
|
||||
private List<MemoryFile> extract(ArchiveType archiveType, ByteBuffer data) throws IOException {
|
||||
List<MemoryFile> vfs = new ArrayList<MemoryFile>();
|
||||
|
||||
for (Entry<File, ByteBuffer> entry : archiveType.fromData(data).extract().entrySet()) {
|
||||
String filename = entry.getKey().getName();
|
||||
for (MemoryFile file : archiveType.fromData(data)) {
|
||||
// check if file is a supported archive
|
||||
ArchiveType type = ArchiveType.forName(FileUtilities.getExtension(file.getName()));
|
||||
|
||||
if (SUBTITLE_FILES.accept(filename)) {
|
||||
// keep only subtitle files
|
||||
vfs.put(entry.getKey(), entry.getValue());
|
||||
} else if (ARCHIVE_FILES.accept(filename)) {
|
||||
// extract recursively if archive contains another archive
|
||||
vfs.putAll(extract(ArchiveType.forName(FileUtilities.getExtension(filename)), entry.getValue()));
|
||||
if (type == ArchiveType.UNDEFINED) {
|
||||
// add subtitle file
|
||||
vfs.add(file);
|
||||
} else {
|
||||
// extract nested archives recursively
|
||||
vfs.addAll(extract(type, file.getData()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -189,6 +211,11 @@ public class SubtitlePackage {
|
|||
}
|
||||
|
||||
|
||||
public boolean isStarted() {
|
||||
return current != Phase.PENDING;
|
||||
}
|
||||
|
||||
|
||||
public Phase getPhase() {
|
||||
return current;
|
||||
}
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
package net.sourceforge.filebot.ui.panel.subtitle;
|
||||
|
||||
|
||||
import static net.sourceforge.filebot.FileBotUtilities.*;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Insets;
|
||||
|
||||
|
@ -15,13 +17,13 @@ import net.sourceforge.filebot.ResourceManager;
|
|||
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 languageLabel = new JLabel();
|
||||
|
||||
|
||||
public SubtitleListCellRenderer() {
|
||||
public SubtitlePackageCellRenderer() {
|
||||
super(new Insets(5, 5, 5, 5));
|
||||
setHighlightingEnabled(false);
|
||||
|
||||
|
@ -59,7 +61,14 @@ public class SubtitleListCellRenderer extends AbstractFancyListCellRenderer {
|
|||
private Icon getIcon(SubtitlePackage subtitle) {
|
||||
switch (subtitle.getDownload().getPhase()) {
|
||||
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:
|
||||
return ResourceManager.getIcon("package.fetch");
|
||||
case EXTRACTING:
|
|
@ -141,7 +141,7 @@ public class SubtitlePanel extends AbstractSearchPanel<SubtitleProvider, Subtitl
|
|||
protected static class SubtitleRequestProcessor extends RequestProcessor<SubtitleRequest, SubtitlePackage> {
|
||||
|
||||
public SubtitleRequestProcessor(SubtitleRequest request) {
|
||||
super(request, new SubtitleListComponent());
|
||||
super(request, new SubtitleDownloadComponent());
|
||||
}
|
||||
|
||||
|
||||
|
@ -172,13 +172,13 @@ public class SubtitlePanel extends AbstractSearchPanel<SubtitleProvider, Subtitl
|
|||
@Override
|
||||
public void process(Collection<SubtitlePackage> subtitles) {
|
||||
getComponent().setLanguageVisible(request.getLanguageName() == null);
|
||||
getComponent().getModel().addAll(subtitles);
|
||||
getComponent().getPackageModel().addAll(subtitles);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public SubtitleListComponent getComponent() {
|
||||
return (SubtitleListComponent) super.getComponent();
|
||||
public SubtitleDownloadComponent getComponent() {
|
||||
return (SubtitleDownloadComponent) super.getComponent();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -2,11 +2,11 @@
|
|||
package net.sourceforge.filebot.ui.panel.subtitle;
|
||||
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
|
||||
|
@ -14,7 +14,7 @@ import net.sourceforge.tuned.ByteBufferInputStream;
|
|||
import net.sourceforge.tuned.ByteBufferOutputStream;
|
||||
|
||||
|
||||
class ZipArchive implements Archive {
|
||||
class ZipArchive implements Iterable<MemoryFile> {
|
||||
|
||||
private final ByteBuffer data;
|
||||
|
||||
|
@ -24,8 +24,18 @@ class ZipArchive implements Archive {
|
|||
}
|
||||
|
||||
|
||||
public Map<File, ByteBuffer> extract() throws IOException {
|
||||
Map<File, ByteBuffer> vfs = new LinkedHashMap<File, ByteBuffer>();
|
||||
@Override
|
||||
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
|
||||
ZipInputStream zipInputStream = new ZipInputStream(new ByteBufferInputStream(data.duplicate()));
|
||||
|
@ -44,7 +54,7 @@ class ZipArchive implements Archive {
|
|||
buffer.transferFully(zipInputStream);
|
||||
|
||||
// add memory file
|
||||
vfs.put(new File(zipEntry.getName()), buffer.getByteBuffer());
|
||||
vfs.add(new MemoryFile(zipEntry.getName(), buffer.getByteBuffer()));
|
||||
}
|
||||
} finally {
|
||||
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
|
||||
public int getSourceActions(JComponent c) {
|
||||
if (!canExport())
|
||||
return TransferHandler.NONE;
|
||||
|
||||
return TransferHandler.MOVE | TransferHandler.COPY;
|
||||
return canExport() ? TransferHandler.COPY_OR_MOVE : TransferHandler.NONE;
|
||||
}
|
||||
|
||||
|
||||
|
@ -50,7 +47,7 @@ public abstract class TextFileExportHandler implements TransferableExportHandler
|
|||
StringWriter buffer = new StringWriter();
|
||||
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 net.sourceforge.filebot.ResourceManager;
|
||||
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Node;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
import net.sourceforge.filebot.ResourceManager;
|
||||
|
||||
|
||||
public class IMDbClient implements EpisodeListProvider {
|
||||
|
||||
|
|
|
@ -8,7 +8,6 @@ import java.util.Collections;
|
|||
import java.util.EnumMap;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
|
||||
/**
|
||||
|
@ -94,19 +93,13 @@ public class OpenSubtitlesSubtitleDescriptor implements SubtitleDescriptor {
|
|||
|
||||
|
||||
@Override
|
||||
public Callable<ByteBuffer> getDownloadFunction() {
|
||||
return new Callable<ByteBuffer>() {
|
||||
|
||||
@Override
|
||||
public ByteBuffer call() throws Exception {
|
||||
return WebRequest.fetch(new URL(properties.get(Property.ZipDownloadLink)));
|
||||
}
|
||||
};
|
||||
public ByteBuffer fetch() throws Exception {
|
||||
return WebRequest.fetch(new URL(properties.get(Property.ZipDownloadLink)));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getArchiveType() {
|
||||
public String getType() {
|
||||
return "zip";
|
||||
}
|
||||
|
||||
|
|
|
@ -177,7 +177,7 @@ public class SublightSubtitleClient implements SubtitleProvider {
|
|||
|
||||
|
||||
@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.BOSNIAN_LATIN, "Bosnian"),
|
||||
new SimpleEntry(SubtitleLanguage.SERBIAN_LATIN, "Serbian")
|
||||
|
|
|
@ -2,10 +2,13 @@
|
|||
package net.sourceforge.filebot.web;
|
||||
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
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;
|
||||
|
||||
|
||||
|
@ -75,20 +78,32 @@ public class SublightSubtitleDescriptor implements SubtitleDescriptor {
|
|||
|
||||
|
||||
@Override
|
||||
public String getArchiveType() {
|
||||
return "zip";
|
||||
public String getType() {
|
||||
return subtitle.getSubtitleType().value().toLowerCase();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Callable<ByteBuffer> getDownloadFunction() {
|
||||
return new Callable<ByteBuffer>() {
|
||||
public ByteBuffer fetch() throws Exception {
|
||||
byte[] archive = source.getZipArchive(subtitle);
|
||||
|
||||
@Override
|
||||
public ByteBuffer call() throws Exception {
|
||||
return ByteBuffer.wrap(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();
|
||||
|
||||
ByteBufferOutputStream buffer = new ByteBufferOutputStream((int) entry.getSize());
|
||||
|
||||
// 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.nio.ByteBuffer;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
|
||||
public class SubsceneSubtitleDescriptor implements SubtitleDescriptor {
|
||||
|
@ -43,19 +42,13 @@ public class SubsceneSubtitleDescriptor implements SubtitleDescriptor {
|
|||
|
||||
|
||||
@Override
|
||||
public Callable<ByteBuffer> getDownloadFunction() {
|
||||
return new Callable<ByteBuffer>() {
|
||||
|
||||
@Override
|
||||
public ByteBuffer call() throws Exception {
|
||||
return WebRequest.fetch(downloadLink, singletonMap("Referer", referer.toString()));
|
||||
}
|
||||
};
|
||||
public ByteBuffer fetch() throws Exception {
|
||||
return WebRequest.fetch(downloadLink, singletonMap("Referer", referer.toString()));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getArchiveType() {
|
||||
public String getType() {
|
||||
return archiveType;
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,6 @@ package net.sourceforge.filebot.web;
|
|||
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
|
||||
public interface SubtitleDescriptor {
|
||||
|
@ -14,9 +13,9 @@ public interface SubtitleDescriptor {
|
|||
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.nio.ByteBuffer;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
|
||||
public class SubtitleSourceSubtitleDescriptor implements SubtitleDescriptor {
|
||||
|
@ -63,19 +62,13 @@ public class SubtitleSourceSubtitleDescriptor implements SubtitleDescriptor {
|
|||
|
||||
|
||||
@Override
|
||||
public Callable<ByteBuffer> getDownloadFunction() {
|
||||
return new Callable<ByteBuffer>() {
|
||||
|
||||
@Override
|
||||
public ByteBuffer call() throws Exception {
|
||||
return WebRequest.fetch(downloadLink);
|
||||
}
|
||||
};
|
||||
public ByteBuffer fetch() throws Exception {
|
||||
return WebRequest.fetch(downloadLink);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getArchiveType() {
|
||||
public String getType() {
|
||||
return "zip";
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,8 @@ package net.sourceforge.tuned;
|
|||
import java.io.File;
|
||||
import java.io.FileFilter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
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
|
||||
public boolean accept(File file) {
|
||||
return hasExtension(file, extensions);
|
||||
|
@ -161,8 +168,8 @@ public final class FileUtilities {
|
|||
}
|
||||
|
||||
|
||||
public String[] getExtensions() {
|
||||
return extensions.clone();
|
||||
public List<String> extensions() {
|
||||
return Arrays.asList(extensions);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
||||
setBackground(list.getBackground());
|
||||
|
||||
setGradientPainted(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());
|
||||
}
|
||||
|
||||
|
||||
@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
|
||||
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);
|
||||
|
||||
assertEquals("Babylon.5.S01E01.Midnight.on.the.Firing.Line.AC3.DVDRip.DivX-AMC", sample.getName());
|
||||
assertEquals("Slovenian", sample.getLanguageName());
|
||||
assertEquals("Terminator.2.Judgment.Day(1991)", sample.getName());
|
||||
assertEquals("English", sample.getLanguageName());
|
||||
|
||||
// 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("Italian", subtitle.getLanguageName());
|
||||
assertEquals("zip", subtitle.getArchiveType());
|
||||
assertEquals("zip", subtitle.getType());
|
||||
}
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue