* added "Edit Format" action to "Fetch Episode List" action popup in rename panel

* add support for episode array transferable to episode list panel and rename panel
* renamed ScriptFormat to ExpressionFormat
* misc. changes
This commit is contained in:
Reinhard Pointner 2009-03-12 20:08:42 +00:00
parent 2de1b8a1b0
commit 10a7fd5b4c
31 changed files with 512 additions and 246 deletions

BIN
fw/action.format.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

@ -4,8 +4,6 @@ package net.sourceforge.filebot;
import java.io.File;
import java.io.FileFilter;
import java.util.AbstractList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -78,23 +76,6 @@ public final class FileBotUtilities {
return sb.toString();
}
public static List<String> asFileNameList(final List<File> list) {
return new AbstractList<String>() {
@Override
public String get(int index) {
return FileUtilities.getName(list.get(index));
}
@Override
public int size() {
return list.size();
}
};
}
public static final FileFilter TORRENT_FILES = new ExtensionFileFilter("torrent");
public static final FileFilter SFV_FILES = new ExtensionFileFilter("sfv");
public static final FileFilter LIST_FILES = new ExtensionFileFilter("txt", "list", "");

View File

@ -4,7 +4,6 @@ package net.sourceforge.filebot;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.prefs.BackingStoreException;
import java.util.prefs.Preferences;
@ -47,6 +46,16 @@ public final class Settings {
}
public String get(String key) {
return get(key, null);
}
public String get(String key, String def) {
return prefs.get(key, def);
}
public void put(String key, String value) {
prefs.put(key, value);
}
@ -59,21 +68,21 @@ public final class Settings {
}
public String get(String key) {
return get(key, null);
public void remove(String key) {
prefs.remove(key);
}
public String get(String key, String def) {
return prefs.get(key, def);
}
public Entry<String, String> entry(String key) {
public PreferencesEntry<String> entry(String key) {
return new PreferencesEntry<String>(prefs, key, new StringAdapter());
}
public <T> PreferencesEntry<T> entry(String key, Adapter<T> adapter) {
return new PreferencesEntry<T>(prefs, key, adapter);
}
public Map<String, String> asMap() {
return PreferencesMap.map(prefs);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 397 B

View File

@ -19,7 +19,7 @@ import java.util.Scanner;
import java.util.TreeMap;
import java.util.Map.Entry;
import net.sourceforge.filebot.FileBotUtilities;
import net.sourceforge.tuned.FileUtilities;
public class SeriesNameMatcher {
@ -261,12 +261,23 @@ public class SeriesNameMatcher {
Map<File, String[]> namesByFolder = new LinkedHashMap<File, String[]>();
for (Entry<File, List<File>> entry : filesByFolder.entrySet()) {
namesByFolder.put(entry.getKey(), FileBotUtilities.asFileNameList(entry.getValue()).toArray(new String[0]));
namesByFolder.put(entry.getKey(), names(entry.getValue()));
}
return namesByFolder;
}
protected String[] names(List<File> files) {
String[] names = new String[files.size()];
for (int i = 0; i < names.length; i++) {
names[i] = FileUtilities.getName(files.get(i));
}
return names;
}
protected static class SeriesNameCollection extends AbstractCollection<String> {

View File

@ -0,0 +1,38 @@
package net.sourceforge.filebot.ui;
import javax.script.Bindings;
import javax.script.ScriptException;
import javax.script.SimpleBindings;
import net.sourceforge.filebot.web.Episode;
public class EpisodeExpressionFormat extends ExpressionFormat {
public EpisodeExpressionFormat(String format) throws ScriptException {
super(format);
}
@Override
public Bindings getBindings(Object value) {
Episode episode = (Episode) value;
Bindings bindings = new SimpleBindings();
bindings.put("n", nonNull(episode.getSeriesName()));
bindings.put("s", nonNull(episode.getSeasonNumber()));
bindings.put("e", nonNull(episode.getEpisodeNumber()));
bindings.put("t", nonNull(episode.getTitle()));
return bindings;
}
private String nonNull(String value) {
return value == null ? "" : value;
}
}

View File

@ -47,6 +47,7 @@ import net.sourceforge.filebot.Settings;
import net.sourceforge.filebot.web.Episode;
import net.sourceforge.filebot.web.Episode.EpisodeFormat;
import net.sourceforge.tuned.ExceptionUtilities;
import net.sourceforge.tuned.PreferencesMap.PreferencesEntry;
import net.sourceforge.tuned.ui.GradientStyle;
import net.sourceforge.tuned.ui.LinkButton;
import net.sourceforge.tuned.ui.TunedUtilities;
@ -58,14 +59,17 @@ public class EpisodeFormatDialog extends JDialog {
private Format selectedFormat = null;
protected JFormattedTextField preview = new JFormattedTextField(getPreviewSample());
protected final JFormattedTextField preview = new JFormattedTextField();
protected JLabel errorMessage = new JLabel(ResourceManager.getIcon("dialog.cancel"));
protected JTextField editor = new JTextField();
protected final JLabel errorMessage = new JLabel(ResourceManager.getIcon("dialog.cancel"));
protected final JTextField editor = new JTextField();
protected Color defaultColor = preview.getForeground();
protected Color errorColor = Color.red;
protected final PreferencesEntry<String> persistentFormat = Settings.userRoot().entry("dialog.format");
protected final PreferencesEntry<String> persistentSample = Settings.userRoot().entry("dialog.sample");
public EpisodeFormatDialog(Window owner) {
super(owner, "Episode Format", ModalityType.DOCUMENT_MODAL);
@ -73,7 +77,10 @@ public class EpisodeFormatDialog extends JDialog {
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
editor.setFont(new Font(MONOSPACED, PLAIN, 14));
editor.setText(Settings.userRoot().get("dialog.format"));
// restore state
preview.setValue(getPreviewSample());
editor.setText(persistentFormat.getValue());
preview.setBorder(BorderFactory.createEmptyBorder());
@ -201,7 +208,7 @@ public class EpisodeFormatDialog extends JDialog {
protected Episode getPreviewSample() {
String sample = Settings.userRoot().get("dialog.sample");
String sample = persistentSample.getValue();
if (sample != null) {
try {
@ -211,6 +218,7 @@ public class EpisodeFormatDialog extends JDialog {
}
}
// default sample
return new Episode("Dark Angel", "3", "1", "Labyrinth");
}
@ -250,7 +258,7 @@ public class EpisodeFormatDialog extends JDialog {
Exception exception = null;
try {
Format format = new EpisodeScriptFormat(editor.getText().trim());
Format format = new EpisodeExpressionFormat(editor.getText().trim());
// check if format produces empty strings
if (format.format(preview.getValue()).trim().isEmpty()) {
@ -285,11 +293,11 @@ public class EpisodeFormatDialog extends JDialog {
dispose();
if (checkEpisodeFormat()) {
Settings.userRoot().put("dialog.format", editor.getText());
persistentFormat.setValue(editor.getText());
}
if (checkPreviewSample()) {
Settings.userRoot().put("dialog.sample", preview.getValue().toString());
persistentSample.setValue(EpisodeFormat.getInstance().format(preview.getValue()));
}
}
@ -314,7 +322,7 @@ public class EpisodeFormatDialog extends JDialog {
@Override
public void actionPerformed(ActionEvent evt) {
try {
finish(new EpisodeScriptFormat(editor.getText()));
finish(new EpisodeExpressionFormat(editor.getText()));
} catch (ScriptException e) {
Logger.getLogger("ui").log(Level.WARNING, ExceptionUtilities.getRootCauseMessage(e), e);
}
@ -369,7 +377,7 @@ public class EpisodeFormatDialog extends JDialog {
public void updateText(Object episode) {
try {
setText(new EpisodeScriptFormat(format).format(episode));
setText(new EpisodeExpressionFormat(format).format(episode));
setForeground(defaultColor);
} catch (Exception e) {
setText(ExceptionUtilities.getRootCauseMessage(e));

View File

@ -10,4 +10,4 @@ example[1]: {n} - {if (s) s+'x'}{e.pad(2)}
example[2]: {n} - {if (s) 'S'+s.pad(2)}E{e.pad(2)}
# uglyfy name
example[3]: {n.replace(/\\s+/g,'.').toLowerCase()}
example[3]: {n.replace(/\\s/g,'.').toLowerCase()}

View File

@ -1,32 +0,0 @@
package net.sourceforge.filebot.ui;
import javax.script.Bindings;
import javax.script.ScriptException;
import javax.script.SimpleBindings;
import net.sourceforge.filebot.web.Episode;
public class EpisodeScriptFormat extends ScriptFormat {
public EpisodeScriptFormat(String format) throws ScriptException {
super(format);
}
@Override
protected Bindings getBindings(Object object) {
Episode episode = (Episode) object;
Bindings bindings = new SimpleBindings();
bindings.put("n", episode.getSeriesName());
bindings.put("s", episode.getSeasonNumber());
bindings.put("e", episode.getEpisodeNumber());
bindings.put("t", episode.getTitle());
return bindings;
}
}

View File

@ -0,0 +1,13 @@
String.prototype.pad = Number.prototype.pad = function(length, padding) {
var s = this.toString();
// use default padding, if padding is undefined or empty
var p = padding ? padding.toString() : '0';
while (s.length < length) {
s = p + s;
}
return s;
}

View File

@ -19,14 +19,14 @@ import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
public abstract class ScriptFormat extends Format {
public abstract class ExpressionFormat extends Format {
private final String format;
private final Object[] expressions;
public ScriptFormat(String format) throws ScriptException {
public ExpressionFormat(String format) throws ScriptException {
this.format = format;
this.expressions = compile(format, (Compilable) initScriptEngine());
}
@ -35,7 +35,7 @@ public abstract class ScriptFormat extends Format {
protected ScriptEngine initScriptEngine() throws ScriptException {
ScriptEngine engine = new ScriptEngineManager().getEngineByName("JavaScript");
engine.eval(new InputStreamReader(getClass().getResourceAsStream("ScriptFormat.global.js")));
engine.eval(new InputStreamReader(getClass().getResourceAsStream("ExpressionFormat.global.js")));
return engine;
}
@ -78,7 +78,7 @@ public abstract class ScriptFormat extends Format {
}
protected abstract Bindings getBindings(Object object);
protected abstract Bindings getBindings(Object value);
@Override

View File

@ -9,7 +9,7 @@ import net.sourceforge.filebot.ui.transfer.TextFileExportHandler;
public class FileBotListExportHandler extends TextFileExportHandler {
private final FileBotList<?> list;
protected final FileBotList<?> list;
public FileBotListExportHandler(FileBotList<?> list) {
@ -19,7 +19,7 @@ public class FileBotListExportHandler extends TextFileExportHandler {
@Override
public boolean canExport() {
return !list.getModel().isEmpty();
return list.getModel().size() > 0;
}

View File

@ -1,16 +0,0 @@
String.prototype.pad = function(length, padding) {
if (padding == undefined) {
padding = '0';
}
var s = this;
if (parseInt(this) >= 0 && padding.length >= 1) {
while (s.length < length) {
s = padding.concat(s)
}
}
return s;
};

View File

@ -0,0 +1,29 @@
package net.sourceforge.filebot.ui.panel.episodelist;
import java.awt.datatransfer.Transferable;
import javax.swing.JComponent;
import net.sourceforge.filebot.ui.FileBotList;
import net.sourceforge.filebot.ui.FileBotListExportHandler;
import net.sourceforge.filebot.ui.transfer.ArrayTransferable;
import net.sourceforge.filebot.ui.transfer.CompositeTranserable;
import net.sourceforge.filebot.web.Episode;
public class EpisodeListExportHandler extends FileBotListExportHandler {
public EpisodeListExportHandler(FileBotList<Episode> list) {
super(list);
}
@Override
public Transferable createTransferable(JComponent c) {
Transferable episodeArray = new ArrayTransferable<Episode>(list.getModel().toArray(new Episode[0]));
Transferable textFile = super.createTransferable(c);
return new CompositeTranserable(episodeArray, textFile);
}
}

View File

@ -23,7 +23,6 @@ import net.sourceforge.filebot.ResourceManager;
import net.sourceforge.filebot.Settings;
import net.sourceforge.filebot.ui.AbstractSearchPanel;
import net.sourceforge.filebot.ui.FileBotList;
import net.sourceforge.filebot.ui.FileBotListExportHandler;
import net.sourceforge.filebot.ui.FileBotTab;
import net.sourceforge.filebot.ui.SelectDialog;
import net.sourceforge.filebot.ui.transfer.FileExportHandler;
@ -296,7 +295,7 @@ public class EpisodeListPanel extends AbstractSearchPanel<EpisodeListClient, Epi
public EpisodeListTab() {
// set export handler for episode list
setExportHandler(new FileBotListExportHandler(this));
setExportHandler(new EpisodeListExportHandler(this));
// allow removal of episode list entries
getRemoveAction().setEnabled(true);

View File

@ -11,7 +11,6 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import net.sourceforge.filebot.FileBotUtilities;
import net.sourceforge.filebot.torrent.Torrent;
import net.sourceforge.filebot.ui.FileBotList;
import net.sourceforge.filebot.ui.transfer.FileTransferablePolicy;
@ -50,7 +49,7 @@ class FileListTransferablePolicy extends FileTransferablePolicy {
} else if (containsOnly(files, TORRENT_FILES)) {
loadTorrents(files);
} else {
list.getModel().addAll(FileBotUtilities.asFileNameList(files));
loadFiles(files);
}
}
@ -62,19 +61,19 @@ class FileListTransferablePolicy extends FileTransferablePolicy {
}
for (File folder : folders) {
list.getModel().addAll(FileBotUtilities.asFileNameList(Arrays.asList(folder.listFiles())));
loadFiles(Arrays.asList(folder.listFiles()));
}
}
private void loadTorrents(List<File> torrentFiles) throws IOException {
List<Torrent> torrents = new ArrayList<Torrent>(torrentFiles.size());
private void loadTorrents(List<File> files) throws IOException {
List<Torrent> torrents = new ArrayList<Torrent>(files.size());
for (File file : torrentFiles) {
for (File file : files) {
torrents.add(new Torrent(file));
}
if (torrentFiles.size() == 1) {
if (torrents.size() == 1) {
list.setTitle(FileUtilities.getNameWithoutExtension(torrents.get(0).getName()));
}
@ -86,6 +85,13 @@ class FileListTransferablePolicy extends FileTransferablePolicy {
}
private void loadFiles(List<File> files) {
for (File file : files) {
list.getModel().add(FileUtilities.getName(file));
}
}
@Override
public String getFileFilterDescription() {
return "files, folders and torrents";

View File

@ -39,12 +39,12 @@ import net.sourceforge.tuned.ui.ProgressDialog.Cancellable;
class MatchAction extends AbstractAction {
private final RenameModel model;
private final RenameModel<Object, File> model;
private final Collection<SimilarityMetric> metrics;
public MatchAction(RenameModel model) {
public MatchAction(RenameModel<Object, File> model) {
super("Match", ResourceManager.getIcon("action.match"));
this.model = model;
@ -169,7 +169,7 @@ class MatchAction extends AbstractAction {
private final Matcher<Object, File> matcher;
public BackgroundMatcher(RenameModel model, Collection<SimilarityMetric> metrics) {
public BackgroundMatcher(RenameModel<Object, File> model, Collection<SimilarityMetric> metrics) {
// match names against files
this.matcher = new Matcher<Object, File>(model.names(), model.files(), metrics);
}

View File

@ -1,25 +0,0 @@
package net.sourceforge.filebot.ui.panel.rename;
class MutableString {
private String value;
public MutableString(Object value) {
set(value);
}
public void set(Object value) {
this.value = String.valueOf(value);
}
@Override
public String toString() {
return value;
}
}

View File

@ -9,6 +9,7 @@ import static net.sourceforge.tuned.FileUtilities.FOLDERS;
import static net.sourceforge.tuned.FileUtilities.containsOnly;
import static net.sourceforge.tuned.FileUtilities.getNameWithoutExtension;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.io.File;
import java.io.FileInputStream;
@ -16,18 +17,22 @@ import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Scanner;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.sourceforge.filebot.torrent.Torrent;
import net.sourceforge.filebot.ui.transfer.ArrayTransferable;
import net.sourceforge.filebot.ui.transfer.FileTransferablePolicy;
import net.sourceforge.tuned.FileUtilities;
import net.sourceforge.filebot.web.Episode;
class NamesListTransferablePolicy extends FileTransferablePolicy {
private static final DataFlavor episodeArrayFlavor = ArrayTransferable.flavor(Episode.class);
private final List<Object> model;
@ -60,7 +65,10 @@ class NamesListTransferablePolicy extends FileTransferablePolicy {
clear();
}
if (tr.isDataFlavorSupported(stringFlavor)) {
if (tr.isDataFlavorSupported(episodeArrayFlavor)) {
// episode array transferable
model.addAll(Arrays.asList((Episode[]) tr.getTransferData((episodeArrayFlavor))));
} else if (tr.isDataFlavorSupported(stringFlavor)) {
// string transferable
load((String) tr.getTransferData(stringFlavor));
} else if (super.accept(tr)) {
@ -71,7 +79,7 @@ class NamesListTransferablePolicy extends FileTransferablePolicy {
protected void load(String string) {
List<MutableString> entries = new ArrayList<MutableString>();
List<String> values = new ArrayList<String>();
Scanner scanner = new Scanner(string).useDelimiter(LINE_SEPARATOR);
@ -79,41 +87,36 @@ class NamesListTransferablePolicy extends FileTransferablePolicy {
String line = scanner.next();
if (line.trim().length() > 0) {
entries.add(new MutableString(line));
values.add(line);
}
}
model.addAll(entries);
model.addAll(values);
}
@Override
protected void load(List<File> files) throws FileNotFoundException {
List<Object> values = new ArrayList<Object>();
if (containsOnly(files, LIST_FILES)) {
loadListFiles(files);
loadListFiles(files, values);
} else if (containsOnly(files, TORRENT_FILES)) {
loadTorrentFiles(files);
loadTorrentFiles(files, values);
} else if (containsOnly(files, FOLDERS)) {
// load files from each folder
for (File folder : files) {
loadFiles(Arrays.asList(folder.listFiles()));
Collections.addAll(values, folder.listFiles());
}
} else {
loadFiles(files);
values.addAll(files);
}
}
protected void loadFiles(List<File> files) {
for (File file : files) {
model.add(new AbstractFileEntry(FileUtilities.getName(file), file.length()));
}
}
protected void loadListFiles(List<File> files) throws FileNotFoundException {
List<String> values = new ArrayList<String>();
model.addAll(values);
}
protected void loadListFiles(List<File> files, List<Object> values) throws FileNotFoundException {
for (File file : files) {
// don't use new Scanner(File) because of BUG 6368019 (http://bugs.sun.com/view_bug.do?bug_id=6368019)
Scanner scanner = new Scanner(new FileInputStream(file), "UTF-8").useDelimiter(LINE_SEPARATOR);
@ -128,25 +131,18 @@ class NamesListTransferablePolicy extends FileTransferablePolicy {
scanner.close();
}
model.addAll(values);
}
protected void loadTorrentFiles(List<File> files) {
protected void loadTorrentFiles(List<File> files, List<Object> values) {
try {
List<AbstractFileEntry> entries = new ArrayList<AbstractFileEntry>();
for (File file : files) {
Torrent torrent = new Torrent(file);
for (Torrent.Entry entry : torrent.getFiles()) {
entries.add(new AbstractFileEntry(getNameWithoutExtension(entry.getName()), entry.getLength()));
values.add(new AbstractFileEntry(getNameWithoutExtension(entry.getName()), entry.getLength()));
}
}
// add torrent entries directly without checking file names for invalid characters
model.addAll(entries);
} catch (IOException e) {
Logger.getLogger("global").log(Level.SEVERE, e.toString(), e);
}

View File

@ -5,10 +5,13 @@ package net.sourceforge.filebot.ui.panel.rename;
import static net.sourceforge.filebot.FileBotUtilities.isInvalidFileName;
import java.awt.Component;
import java.text.Format;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import ca.odell.glazedlists.EventList;
import ca.odell.glazedlists.TransformedList;
import ca.odell.glazedlists.event.ListEvent;
@ -18,6 +21,8 @@ public class NamesViewEventList extends TransformedList<Object, String> {
private final List<String> names = new ArrayList<String>();
private final Map<Class<?>, Format> formatMap = new HashMap<Class<?>, Format>();
private final Component parent;
@ -43,7 +48,43 @@ public class NamesViewEventList extends TransformedList<Object, String> {
}
protected String format(Object object) {
public void setFormat(Class<?> type, Format format) {
if (format != null) {
// insert new format for type
formatMap.put(type, format);
} else {
// restore default format for type
formatMap.remove(type);
}
updates.beginEvent(true);
List<Integer> changes = new ArrayList<Integer>();
// reformat all elements of the source list
for (int i = 0; i < source.size(); i++) {
String newValue = format(source.get(i));
String oldValue = names.set(i, newValue);
if (!newValue.equals(oldValue)) {
updates.elementUpdated(i, oldValue, newValue);
changes.add(i);
}
}
submit(new IndexView<String>(names, changes));
updates.commitEvent();
}
private String format(Object object) {
for (Entry<Class<?>, Format> entry : formatMap.entrySet()) {
if (entry.getKey().isInstance(object)) {
return entry.getValue().format(object);
}
}
return object.toString();
}
@ -51,7 +92,7 @@ public class NamesViewEventList extends TransformedList<Object, String> {
@Override
public void listChanged(ListEvent<Object> listChanges) {
EventList<Object> source = listChanges.getSourceList();
IndexView<String> newValues = new IndexView<String>(names);
List<Integer> changes = new ArrayList<Integer>();
while (listChanges.next()) {
int index = listChanges.getIndex();
@ -60,11 +101,11 @@ public class NamesViewEventList extends TransformedList<Object, String> {
switch (type) {
case ListEvent.INSERT:
names.add(index, format(source.get(index)));
newValues.getIndexFilter().add(index);
changes.add(index);
break;
case ListEvent.UPDATE:
names.set(index, format(source.get(index)));
newValues.getIndexFilter().add(index);
changes.add(index);
break;
case ListEvent.DELETE:
names.remove(index);
@ -72,7 +113,7 @@ public class NamesViewEventList extends TransformedList<Object, String> {
}
}
submit(newValues);
submit(new IndexView<String>(names, changes));
listChanges.reset();
updates.forwardEvent(listChanges);
@ -80,17 +121,17 @@ public class NamesViewEventList extends TransformedList<Object, String> {
protected void submit(List<String> values) {
IndexView<String> invalidValues = new IndexView<String>(values);
List<Integer> issues = new ArrayList<Integer>();
for (int i = 0; i < values.size(); i++) {
if (isInvalidFileName(values.get(i))) {
invalidValues.getIndexFilter().add(i);
issues.add(i);
}
}
if (invalidValues.size() > 0) {
if (issues.size() > 0) {
// validate names
ValidateNamesDialog.showDialog(parent, invalidValues);
ValidateNamesDialog.showDialog(parent, new IndexView<String>(values, issues));
}
}
@ -99,34 +140,30 @@ public class NamesViewEventList extends TransformedList<Object, String> {
private final List<E> source;
private final List<Integer> indexFilter = new ArrayList<Integer>();
private final List<Integer> filter;
public IndexView(List<E> source) {
public IndexView(List<E> source, List<Integer> filter) {
this.source = source;
}
public List<Integer> getIndexFilter() {
return indexFilter;
this.filter = filter;
}
@Override
public E get(int index) {
return source.get(indexFilter.get(index));
return source.get(filter.get(index));
}
@Override
public E set(int index, E element) {
return source.set(indexFilter.get(index), element);
return source.set(filter.get(index), element);
};
@Override
public int size() {
return indexFilter.size();
return filter.size();
}
}

View File

@ -19,10 +19,10 @@ import net.sourceforge.tuned.FileUtilities;
class RenameAction extends AbstractAction {
private final RenameModel model;
private final RenameModel<String, File> model;
public RenameAction(RenameModel model) {
public RenameAction(RenameModel<String, File> model) {
super("Rename", ResourceManager.getIcon("action.rename"));
putValue(SHORT_DESCRIPTION, "Rename files");
@ -36,12 +36,11 @@ class RenameAction extends AbstractAction {
Deque<Match<File, File>> todoQueue = new ArrayDeque<Match<File, File>>();
Deque<Match<File, File>> doneQueue = new ArrayDeque<Match<File, File>>();
for (Match<Object, File> match : model.matches()) {
for (Match<String, File> match : model.matches()) {
File source = match.getCandidate();
String extension = FileUtilities.getExtension(source);
StringBuilder name = new StringBuilder();
name.append(match.getValue());
StringBuilder name = new StringBuilder(match.getValue());
if (extension != null) {
name.append(".").append(extension);
@ -77,7 +76,7 @@ class RenameAction extends AbstractAction {
for (Match<File, File> match : doneQueue) {
revertSuccess &= match.getCandidate().renameTo(match.getValue());
}
if (!revertSuccess) {
Logger.getLogger("ui").severe("Failed to revert all rename operations.");
}

View File

@ -25,12 +25,12 @@ import net.sourceforge.tuned.ui.DefaultFancyListCellRenderer;
class RenameListCellRenderer extends DefaultFancyListCellRenderer {
private final RenameModel model;
private final RenameModel<?, ?> model;
private final ExtensionLabel extension = new ExtensionLabel();
public RenameListCellRenderer(RenameModel model) {
public RenameListCellRenderer(RenameModel<?, ?> model) {
this.model = model;
setHighlightingEnabled(false);

View File

@ -2,7 +2,6 @@
package net.sourceforge.filebot.ui.panel.rename;
import java.io.File;
import java.util.AbstractList;
import java.util.Collection;
@ -11,18 +10,24 @@ import ca.odell.glazedlists.BasicEventList;
import ca.odell.glazedlists.EventList;
class RenameModel {
class RenameModel<N, V> {
private final EventList<Object> names = new BasicEventList<Object>();
private final EventList<File> files = new BasicEventList<File>();
private final EventList<N> names;
private final EventList<V> files;
public EventList<Object> names() {
public RenameModel(EventList<N> names, EventList<V> files) {
this.names = names;
this.files = files;
}
public EventList<N> names() {
return names;
}
public EventList<File> files() {
public EventList<V> files() {
return files;
}
@ -38,19 +43,19 @@ class RenameModel {
}
public Match<Object, File> getMatch(int index) {
public Match<N, V> getMatch(int index) {
if (index >= matchCount())
throw new IndexOutOfBoundsException();
return new Match<Object, File>(names.get(index), files.get(index));
return new Match<N, V>(names.get(index), files.get(index));
}
public Collection<Match<Object, File>> matches() {
return new AbstractList<Match<Object, File>>() {
public Collection<Match<N, V>> matches() {
return new AbstractList<Match<N, V>>() {
@Override
public Match<Object, File> get(int index) {
public Match<N, V> get(int index) {
return getMatch(index);
}
@ -62,4 +67,16 @@ class RenameModel {
};
}
@SuppressWarnings("unchecked")
public static <S, V> RenameModel<S, V> create() {
return new RenameModel<S, V>((EventList<S>) new BasicEventList<Object>(), (EventList<V>) new BasicEventList<Object>());
}
public static <S, V> RenameModel<S, V> wrap(EventList<S> names, EventList<V> values) {
return new RenameModel<S, V>(names, values);
}
}

View File

@ -9,6 +9,7 @@ import java.awt.event.ActionEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.text.Format;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
@ -24,6 +25,7 @@ import javax.swing.Action;
import javax.swing.DefaultListSelectionModel;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.ListSelectionModel;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
@ -34,15 +36,20 @@ import net.sourceforge.filebot.Settings;
import net.sourceforge.filebot.similarity.Match;
import net.sourceforge.filebot.similarity.NameSimilarityMetric;
import net.sourceforge.filebot.similarity.SimilarityMetric;
import net.sourceforge.filebot.ui.EpisodeExpressionFormat;
import net.sourceforge.filebot.ui.EpisodeFormatDialog;
import net.sourceforge.filebot.ui.FileBotPanel;
import net.sourceforge.filebot.ui.SelectDialog;
import net.sourceforge.filebot.web.AnidbClient;
import net.sourceforge.filebot.web.Episode;
import net.sourceforge.filebot.web.EpisodeListClient;
import net.sourceforge.filebot.web.SearchResult;
import net.sourceforge.filebot.web.TVDotComClient;
import net.sourceforge.filebot.web.TVRageClient;
import net.sourceforge.filebot.web.TheTVDBClient;
import net.sourceforge.tuned.ExceptionUtilities;
import net.sourceforge.tuned.FileUtilities.FileNameFormat;
import net.sourceforge.tuned.PreferencesMap.PreferencesEntry;
import net.sourceforge.tuned.ui.ActionPopup;
import net.sourceforge.tuned.ui.LoadingOverlayPane;
import ca.odell.glazedlists.event.ListEvent;
@ -51,17 +58,19 @@ import ca.odell.glazedlists.event.ListEventListener;
public class RenamePanel extends FileBotPanel {
private RenameModel model = new RenameModel();
protected final RenameModel<Object, File> model = RenameModel.create();
private RenameList<String> namesList = new RenameList<String>(new NamesViewEventList(this, model.names()));
protected final NamesViewEventList namesView = new NamesViewEventList(this, model.names());
private RenameList<File> filesList = new RenameList<File>(model.files());
protected final RenameList<String> namesList = new RenameList<String>(namesView);
private MatchAction matchAction = new MatchAction(model);
protected final RenameList<File> filesList = new RenameList<File>(model.files());
private RenameAction renameAction = new RenameAction(model);
protected final MatchAction matchAction = new MatchAction(model);
private ActionPopup matchActionPopup = new ActionPopup("Fetch Episode List", ResourceManager.getIcon("action.fetch"));
protected final RenameAction renameAction = new RenameAction(RenameModel.wrap(namesView, model.files()));
protected final PreferencesEntry<String> persistentFormat = Settings.userRoot().entry("rename.format");
public RenamePanel() {
@ -73,6 +82,11 @@ public class RenamePanel extends FileBotPanel {
filesList.setTitle("Current");
filesList.setTransferablePolicy(new FilesListTransferablePolicy(filesList.getModel()));
namesView.setFormat(File.class, new FileNameFormat());
// restore custom format
restoreEpisodeFormat();
RenameListCellRenderer cellrenderer = new RenameListCellRenderer(model);
namesList.getListComponent().setCellRenderer(cellrenderer);
@ -98,13 +112,8 @@ public class RenamePanel extends FileBotPanel {
renameButton.setVerticalTextPosition(SwingConstants.BOTTOM);
renameButton.setHorizontalTextPosition(SwingConstants.CENTER);
// create actions for match popup
matchActionPopup.add(new AutoFetchEpisodeListAction(new TVRageClient()));
matchActionPopup.add(new AutoFetchEpisodeListAction(new AnidbClient()));
matchActionPopup.add(new AutoFetchEpisodeListAction(new TheTVDBClient(Settings.userRoot().get("thetvdb.apikey"))));
// set match action popup
matchButton.setComponentPopupMenu(matchActionPopup);
// set fetch action popup
matchButton.setComponentPopupMenu(createFetchPopup());
matchButton.addActionListener(showPopupAction);
setLayout(new MigLayout("fill, insets dialog, gapx 10px", null, "align 33%"));
@ -120,26 +129,64 @@ public class RenamePanel extends FileBotPanel {
add(filesList, "grow, sizegroupx list");
// set action popup status message while episode list matcher is working
namesList.addPropertyChangeListener(LOADING_PROPERTY, new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
matchActionPopup.setStatus((Boolean) evt.getNewValue() ? "in progress" : null);
}
});
// repaint on change
model.names().addListEventListener(new RepaintHandler<Object>());
model.files().addListEventListener(new RepaintHandler<File>());
}
protected ActionPopup createFetchPopup() {
final ActionPopup actionPopup = new ActionPopup("Fetch Episode List", ResourceManager.getIcon("action.fetch"));
// create actions for match popup
actionPopup.add(new AutoFetchEpisodeListAction(new TVRageClient()));
actionPopup.add(new AutoFetchEpisodeListAction(new AnidbClient()));
actionPopup.add(new AutoFetchEpisodeListAction(new TVDotComClient()));
actionPopup.add(new AutoFetchEpisodeListAction(new TheTVDBClient(Settings.userRoot().get("thetvdb.apikey"))));
actionPopup.addSeparator();
actionPopup.addDescription(new JLabel("Options:"));
actionPopup.add(new AbstractAction("Edit Format", ResourceManager.getIcon("action.format")) {
@Override
public void actionPerformed(ActionEvent e) {
Format format = EpisodeFormatDialog.showDialog(RenamePanel.this);
if (format != null) {
if (format instanceof EpisodeExpressionFormat) {
persistentFormat.setValue(((EpisodeExpressionFormat) format).getFormat());
} else {
persistentFormat.remove();
}
namesView.setFormat(Episode.class, format);
}
}
});
return actionPopup;
}
private void restoreEpisodeFormat() {
String format = persistentFormat.getValue();
if (format != null) {
try {
namesView.setFormat(Episode.class, new EpisodeExpressionFormat(format));
} catch (Exception e) {
Logger.getLogger("ui").log(Level.WARNING, e.getMessage(), e);
}
}
}
protected final Action showPopupAction = new AbstractAction("Show Popup") {
@Override
public void actionPerformed(ActionEvent e) {
// show popup on actionPerformed only when names list is empty
if (model.names().isEmpty()) {
if (model.names().isEmpty() && !model.files().isEmpty()) {
JComponent source = (JComponent) e.getSource();
// display popup below component

View File

@ -0,0 +1,47 @@
package net.sourceforge.filebot.ui.transfer;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.io.IOException;
import java.lang.reflect.Array;
public class ArrayTransferable<T> implements Transferable {
public static DataFlavor flavor(Class<?> componentType) {
return new DataFlavor(Array.newInstance(componentType, 0).getClass(), "Array");
}
private final T[] array;
public ArrayTransferable(T... array) {
this.array = array;
}
@Override
public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
if (isDataFlavorSupported(flavor)) {
return array;
}
return null;
}
@Override
public DataFlavor[] getTransferDataFlavors() {
return new DataFlavor[] { new DataFlavor(array.getClass(), "Array") };
}
@Override
public boolean isDataFlavorSupported(DataFlavor flavor) {
return array.getClass().equals(flavor.getRepresentationClass());
}
}

View File

@ -0,0 +1,66 @@
package net.sourceforge.filebot.ui.transfer;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
public class CompositeTranserable implements Transferable {
private final Transferable[] transferables;
private final DataFlavor[] flavors;
public CompositeTranserable(Transferable... transferables) {
this.transferables = transferables;
Collection<DataFlavor> flavors = new ArrayList<DataFlavor>();
for (Transferable transferable : transferables) {
Collections.addAll(flavors, transferable.getTransferDataFlavors());
}
this.flavors = flavors.toArray(new DataFlavor[0]);
}
@Override
public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
Transferable transferable = getTransferable(flavor);
if (transferable == null)
return null;
return transferable.getTransferData(flavor);
}
@Override
public DataFlavor[] getTransferDataFlavors() {
return flavors;
}
@Override
public boolean isDataFlavorSupported(DataFlavor flavor) {
return getTransferable(flavor) != null;
}
protected Transferable getTransferable(DataFlavor flavor) {
for (Transferable transferable : transferables) {
if (transferable.isDataFlavorSupported(flavor))
return transferable;
}
return null;
}
}

View File

@ -46,6 +46,12 @@ public class ByteBufferOutputStream extends OutputStream {
}
public synchronized void write(ByteBuffer src) throws IOException {
ensureCapacity(buffer.position() + src.remaining());
buffer.put(src);
}
@Override
public synchronized void write(byte[] src, int offset, int length) throws IOException {
ensureCapacity(buffer.position() + length);

View File

@ -4,6 +4,9 @@ package net.sourceforge.tuned;
import java.io.File;
import java.io.FileFilter;
import java.text.FieldPosition;
import java.text.Format;
import java.text.ParsePosition;
import java.util.ArrayList;
import java.util.List;
@ -157,6 +160,22 @@ public final class FileUtilities {
}
}
public static class FileNameFormat extends Format {
@Override
public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) {
return toAppendTo.append(FileUtilities.getName((File) obj));
}
@Override
public Object parseObject(String source, ParsePosition pos) {
throw new UnsupportedOperationException();
}
}
/**
* Dummy constructor to prevent instantiation.

View File

@ -333,6 +333,11 @@ public class PreferencesMap<T> implements Map<String, T> {
return null;
}
public void remove() {
adapter.remove(prefs, key);
}
}
}

View File

@ -8,6 +8,7 @@ import java.awt.event.ActionListener;
import javax.swing.Action;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
@ -19,11 +20,11 @@ import net.miginfocom.swing.MigLayout;
public class ActionPopup extends JPopupMenu {
protected JLabel headerLabel = new JLabel();
protected JLabel descriptionLabel = new JLabel();
protected JLabel statusLabel = new JLabel();
protected final JLabel headerLabel = new JLabel();
protected final JLabel descriptionLabel = new JLabel();
protected final JLabel statusLabel = new JLabel();
protected JPanel actionPanel = new JPanel(new MigLayout("insets 0, wrap 1"));
protected final JPanel actionPanel = new JPanel(new MigLayout("nogrid, insets 0, fill"));
public ActionPopup(String label, Icon icon) {
@ -31,22 +32,37 @@ public class ActionPopup extends JPopupMenu {
headerLabel.setIcon(icon);
headerLabel.setIconTextGap(5);
actionPanel.setOpaque(false);
statusLabel.setFont(statusLabel.getFont().deriveFont(10f));
statusLabel.setForeground(Color.GRAY);
actionPanel.setOpaque(false);
setLayout(new MigLayout("nogrid, fill, insets 0"));
add(headerLabel, "gapx 5px 5px, gapy 3px 1px, wrap 3px");
add(new JSeparator(), "growx, wrap 1px");
add(descriptionLabel, "gapx 4px, wrap 3px");
add(actionPanel, "gapx 12px 12px, wrap");
add(actionPanel, "growx, wrap 0px");
add(new JSeparator(), "growx, wrap 0px");
add(statusLabel, "growx, h 11px!, gapx 3px, wrap 1px");
}
public void addDescription(JComponent component) {
actionPanel.add(component, "gapx 4px, wrap 3px");
}
public void addAction(JComponent component) {
actionPanel.add(component, "gapx 12px 12px, wrap");
}
@Override
public void addSeparator() {
actionPanel.add(new JSeparator(), "growx, wrap 1px");
}
@Override
public JMenuItem add(Action a) {
LinkButton link = new LinkButton(a);
@ -61,7 +77,7 @@ public class ActionPopup extends JPopupMenu {
link.setRolloverEnabled(false);
link.setColor(link.getRolloverColor());
actionPanel.add(link);
addAction(link);
return null;
}
@ -84,16 +100,6 @@ public class ActionPopup extends JPopupMenu {
}
public void setDescription(String string) {
descriptionLabel.setText(string);
}
public String getDescription() {
return descriptionLabel.getText();
}
public void setStatus(String string) {
statusLabel.setText(string);
}

View File

@ -14,11 +14,11 @@ import javax.script.SimpleBindings;
import org.junit.Test;
public class ScriptFormatTest {
public class ExpressionFormatTest {
@Test
public void compile() throws Exception {
ScriptFormat format = new TestScriptFormat("");
ExpressionFormat format = new TestScriptFormat("");
Object[] expression = format.compile("name: {name}, number: {number}", (Compilable) format.initScriptEngine());
@ -49,7 +49,7 @@ public class ScriptFormatTest {
}
protected static class TestScriptFormat extends ScriptFormat {
protected static class TestScriptFormat extends ExpressionFormat {
public TestScriptFormat(String format) throws ScriptException {
super(format);