mirror of
https://github.com/mitb-archive/filebot
synced 2024-11-15 05:45:05 -05:00
* added selection dialog for format sample in episode format dialog
* make MediaInfo thread-safe * refactor class Settings and lots of related code changes
This commit is contained in:
parent
b0ea60dad6
commit
3956b61127
@ -49,7 +49,7 @@ public class Main {
|
||||
|
||||
if (argumentBean.clear()) {
|
||||
// clear preferences
|
||||
Settings.userRoot().clear();
|
||||
Settings.forPackage(Main.class).clear();
|
||||
}
|
||||
|
||||
try {
|
||||
|
@ -11,7 +11,6 @@ import java.util.prefs.Preferences;
|
||||
import net.sourceforge.tuned.ExceptionUtilities;
|
||||
import net.sourceforge.tuned.PreferencesList;
|
||||
import net.sourceforge.tuned.PreferencesMap;
|
||||
import net.sourceforge.tuned.PreferencesMap.Adapter;
|
||||
import net.sourceforge.tuned.PreferencesMap.PreferencesEntry;
|
||||
import net.sourceforge.tuned.PreferencesMap.StringAdapter;
|
||||
|
||||
@ -52,11 +51,13 @@ public final class Settings {
|
||||
}
|
||||
|
||||
|
||||
private static final Settings userRoot = new Settings(Preferences.userNodeForPackage(Settings.class));
|
||||
public static Settings forPackage(Class<?> type) {
|
||||
return new Settings(Preferences.userNodeForPackage(type));
|
||||
}
|
||||
|
||||
|
||||
public static Settings userRoot() {
|
||||
return userRoot;
|
||||
public static Settings forPackage(Object object) {
|
||||
return forPackage(object.getClass());
|
||||
}
|
||||
|
||||
|
||||
@ -88,13 +89,6 @@ public final class Settings {
|
||||
}
|
||||
|
||||
|
||||
public void putDefault(String key, String value) {
|
||||
if (get(key) == null) {
|
||||
put(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void remove(String key) {
|
||||
prefs.remove(key);
|
||||
}
|
||||
@ -105,31 +99,16 @@ public final class Settings {
|
||||
}
|
||||
|
||||
|
||||
public <T> PreferencesEntry<T> entry(String key, Adapter<T> adapter) {
|
||||
return new PreferencesEntry<T>(prefs, key, adapter);
|
||||
}
|
||||
|
||||
|
||||
public PreferencesMap<String> asMap() {
|
||||
return PreferencesMap.map(prefs);
|
||||
}
|
||||
|
||||
|
||||
public <T> PreferencesMap<T> asMap(Adapter<T> adapter) {
|
||||
return PreferencesMap.map(prefs, adapter);
|
||||
}
|
||||
|
||||
|
||||
public PreferencesList<String> asList() {
|
||||
return PreferencesList.map(prefs);
|
||||
}
|
||||
|
||||
|
||||
public <T> PreferencesList<T> asList(Adapter<T> adapter) {
|
||||
return PreferencesList.map(prefs, adapter);
|
||||
}
|
||||
|
||||
|
||||
public void clear() {
|
||||
try {
|
||||
// remove child nodes
|
||||
|
@ -164,11 +164,7 @@ public class AssociativeScriptObject implements Scriptable {
|
||||
public LenientLookup(Map<String, Object> source) {
|
||||
// populate entry map
|
||||
for (Entry<String, Object> entry : source.entrySet()) {
|
||||
String key = definingKey(entry.getKey());
|
||||
|
||||
if (key.length() > 0) {
|
||||
this.source.put(key, entry);
|
||||
}
|
||||
this.source.put(definingKey(entry.getKey()), entry);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -26,7 +26,7 @@ import net.sourceforge.filebot.web.Episode;
|
||||
import net.sourceforge.tuned.FileUtilities;
|
||||
|
||||
|
||||
public class EpisodeFormatBindingBean {
|
||||
public class EpisodeBindingBean {
|
||||
|
||||
private final Episode episode;
|
||||
|
||||
@ -35,7 +35,7 @@ public class EpisodeFormatBindingBean {
|
||||
private MediaInfo mediaInfo;
|
||||
|
||||
|
||||
public EpisodeFormatBindingBean(Episode episode, File mediaFile) {
|
||||
public EpisodeBindingBean(Episode episode, File mediaFile) {
|
||||
this.episode = episode;
|
||||
this.mediaFile = mediaFile;
|
||||
}
|
||||
@ -176,12 +176,6 @@ public class EpisodeFormatBindingBean {
|
||||
}
|
||||
|
||||
|
||||
@Define("image")
|
||||
public Object getImageInfo() {
|
||||
return new AssociativeScriptObject(getMediaInfo().snapshot(StreamKind.Image, 0));
|
||||
}
|
||||
|
||||
|
||||
@Define("episode")
|
||||
public Episode getEpisode() {
|
||||
return episode;
|
||||
@ -194,8 +188,7 @@ public class EpisodeFormatBindingBean {
|
||||
}
|
||||
|
||||
|
||||
@Define("inferredFile")
|
||||
public File getInferredMediaFile() {
|
||||
private File getInferredMediaFile() {
|
||||
// make sure media file is defined
|
||||
checkMediaFile();
|
||||
|
||||
@ -215,13 +208,10 @@ public class EpisodeFormatBindingBean {
|
||||
}
|
||||
|
||||
|
||||
private void checkMediaFile() {
|
||||
// make sure file is not null
|
||||
if (mediaFile == null)
|
||||
throw new NullPointerException("Media file is not defined");
|
||||
|
||||
// file may not exist at this point but if an existing file is required,
|
||||
// an exception will be thrown later anyway
|
||||
private void checkMediaFile() throws RuntimeException {
|
||||
// make sure file is not null, and that it is an existing file
|
||||
if (mediaFile == null || !mediaFile.isFile())
|
||||
throw new RuntimeException(String.format("Illegal media file: %s", mediaFile));
|
||||
}
|
||||
|
||||
|
@ -20,6 +20,8 @@ public class ExpressionBindings extends AbstractMap<String, Object> implements B
|
||||
|
||||
protected final Map<String, Method> bindings = new TreeMap<String, Method>(String.CASE_INSENSITIVE_ORDER);
|
||||
|
||||
protected final Method undefined;
|
||||
|
||||
|
||||
public ExpressionBindings(Object bindingBean) {
|
||||
this.bindingBean = bindingBean;
|
||||
@ -37,6 +39,9 @@ public class ExpressionBindings extends AbstractMap<String, Object> implements B
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// extract mapping that handles undefined bindings
|
||||
undefined = bindings.remove(Define.undefined);
|
||||
}
|
||||
|
||||
|
||||
@ -53,7 +58,7 @@ public class ExpressionBindings extends AbstractMap<String, Object> implements B
|
||||
}
|
||||
|
||||
// invoke fallback method
|
||||
return bindings.get(Define.undefined).invoke(bindingBean);
|
||||
return undefined.invoke(bindingBean);
|
||||
}
|
||||
|
||||
|
||||
|
@ -40,12 +40,12 @@ public class MediaInfo implements Closeable {
|
||||
}
|
||||
|
||||
|
||||
public boolean open(File file) {
|
||||
public synchronized boolean open(File file) {
|
||||
return MediaInfoLibrary.INSTANCE.Open(handle, new WString(file.getAbsolutePath())) > 0;
|
||||
}
|
||||
|
||||
|
||||
public String inform() {
|
||||
public synchronized String inform() {
|
||||
return MediaInfoLibrary.INSTANCE.Inform(handle).toString();
|
||||
}
|
||||
|
||||
@ -55,7 +55,7 @@ public class MediaInfo implements Closeable {
|
||||
}
|
||||
|
||||
|
||||
public String option(String option, String value) {
|
||||
public synchronized String option(String option, String value) {
|
||||
return MediaInfoLibrary.INSTANCE.Option(handle, new WString(option), new WString(value)).toString();
|
||||
}
|
||||
|
||||
@ -70,7 +70,7 @@ public class MediaInfo implements Closeable {
|
||||
}
|
||||
|
||||
|
||||
public String get(StreamKind streamKind, int streamNumber, String parameter, InfoKind infoKind, InfoKind searchKind) {
|
||||
public synchronized String get(StreamKind streamKind, int streamNumber, String parameter, InfoKind infoKind, InfoKind searchKind) {
|
||||
return MediaInfoLibrary.INSTANCE.Get(handle, streamKind.ordinal(), streamNumber, new WString(parameter), infoKind.ordinal(), searchKind.ordinal()).toString();
|
||||
}
|
||||
|
||||
@ -80,17 +80,17 @@ public class MediaInfo implements Closeable {
|
||||
}
|
||||
|
||||
|
||||
public String get(StreamKind streamKind, int streamNumber, int parameterIndex, InfoKind infoKind) {
|
||||
public synchronized String get(StreamKind streamKind, int streamNumber, int parameterIndex, InfoKind infoKind) {
|
||||
return MediaInfoLibrary.INSTANCE.GetI(handle, streamKind.ordinal(), streamNumber, parameterIndex, infoKind.ordinal()).toString();
|
||||
}
|
||||
|
||||
|
||||
public int streamCount(StreamKind streamKind) {
|
||||
public synchronized int streamCount(StreamKind streamKind) {
|
||||
return MediaInfoLibrary.INSTANCE.Count_Get(handle, streamKind.ordinal(), -1);
|
||||
}
|
||||
|
||||
|
||||
public int parameterCount(StreamKind streamKind, int streamNumber) {
|
||||
public synchronized int parameterCount(StreamKind streamKind, int streamNumber) {
|
||||
return MediaInfoLibrary.INSTANCE.Count_Get(handle, streamKind.ordinal(), streamNumber);
|
||||
}
|
||||
|
||||
@ -132,25 +132,24 @@ public class MediaInfo implements Closeable {
|
||||
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
public synchronized void close() {
|
||||
MediaInfoLibrary.INSTANCE.Close(handle);
|
||||
}
|
||||
|
||||
|
||||
public void dispose() {
|
||||
public synchronized void dispose() {
|
||||
if (handle == null)
|
||||
throw new IllegalStateException();
|
||||
return;
|
||||
|
||||
// delete handle
|
||||
MediaInfoLibrary.INSTANCE.Delete(handle);
|
||||
handle = null;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
if (handle != null) {
|
||||
dispose();
|
||||
}
|
||||
protected void finalize() {
|
||||
dispose();
|
||||
}
|
||||
|
||||
|
||||
|
BIN
source/net/sourceforge/filebot/resources/action.variable.png
Normal file
BIN
source/net/sourceforge/filebot/resources/action.variable.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 362 B |
@ -78,6 +78,21 @@ public class SeasonEpisodeMatcher {
|
||||
}
|
||||
|
||||
|
||||
public Matcher matcher(CharSequence name) {
|
||||
for (SeasonEpisodePattern pattern : patterns) {
|
||||
Matcher matcher = pattern.matcher(name);
|
||||
|
||||
// check if current pattern matches
|
||||
if (matcher.find()) {
|
||||
// reset matcher state
|
||||
return matcher.reset();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public static class SxE {
|
||||
|
||||
public static final int UNDEFINED = -1;
|
||||
@ -141,6 +156,11 @@ public class SeasonEpisodeMatcher {
|
||||
}
|
||||
|
||||
|
||||
public Matcher matcher(CharSequence name) {
|
||||
return pattern.matcher(name);
|
||||
}
|
||||
|
||||
|
||||
protected Collection<SxE> process(MatchResult match) {
|
||||
return Collections.singleton(new SxE(match.group(1), match.group(2)));
|
||||
}
|
||||
@ -150,7 +170,7 @@ public class SeasonEpisodeMatcher {
|
||||
// name will probably contain no more than two matches
|
||||
List<SxE> matches = new ArrayList<SxE>(2);
|
||||
|
||||
Matcher matcher = pattern.matcher(name);
|
||||
Matcher matcher = matcher(name);
|
||||
|
||||
while (matcher.find()) {
|
||||
matches.addAll(process(matcher));
|
||||
@ -161,10 +181,11 @@ public class SeasonEpisodeMatcher {
|
||||
|
||||
|
||||
public int find(CharSequence name, int fromIndex) {
|
||||
Matcher matcher = pattern.matcher(name);
|
||||
Matcher matcher = matcher(name);
|
||||
|
||||
if (matcher.find(fromIndex))
|
||||
if (matcher.find(fromIndex)) {
|
||||
return matcher.start();
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
@ -36,7 +36,6 @@ import net.sourceforge.filebot.ui.panel.rename.RenamePanelBuilder;
|
||||
import net.sourceforge.filebot.ui.panel.sfv.SfvPanelBuilder;
|
||||
import net.sourceforge.filebot.ui.panel.subtitle.SubtitlePanelBuilder;
|
||||
import net.sourceforge.tuned.PreferencesMap.PreferencesEntry;
|
||||
import net.sourceforge.tuned.PreferencesMap.SimpleAdapter;
|
||||
import net.sourceforge.tuned.ui.DefaultFancyListCellRenderer;
|
||||
import net.sourceforge.tuned.ui.ShadowBorder;
|
||||
import net.sourceforge.tuned.ui.TunedUtilities;
|
||||
@ -48,7 +47,7 @@ public class MainFrame extends JFrame {
|
||||
|
||||
private HeaderPanel headerPanel = new HeaderPanel();
|
||||
|
||||
private final PreferencesEntry<Integer> persistentSelectedPanel = Settings.userRoot().entry("panel.selected", SimpleAdapter.forClass(Integer.class));
|
||||
private PreferencesEntry<String> persistentSelectedPanel = Settings.forPackage(this).entry("panel.selected").defaultValue("1");
|
||||
|
||||
|
||||
public MainFrame() {
|
||||
@ -59,10 +58,9 @@ public class MainFrame extends JFrame {
|
||||
|
||||
try {
|
||||
// restore selected panel
|
||||
selectionList.setSelectedIndex(persistentSelectedPanel.getValue());
|
||||
} catch (Exception e) {
|
||||
// select default panel
|
||||
selectionList.setSelectedIndex(1);
|
||||
selectionList.setSelectedIndex(Integer.parseInt(persistentSelectedPanel.getValue()));
|
||||
} catch (NumberFormatException e) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
JScrollPane selectionListScrollPane = new JScrollPane(selectionList, VERTICAL_SCROLLBAR_NEVER, HORIZONTAL_SCROLLBAR_NEVER);
|
||||
@ -87,7 +85,7 @@ public class MainFrame extends JFrame {
|
||||
showPanel((PanelBuilder) selectionList.getSelectedValue());
|
||||
|
||||
if (!e.getValueIsAdjusting()) {
|
||||
persistentSelectedPanel.setValue(selectionList.getSelectedIndex());
|
||||
persistentSelectedPanel.setValue(Integer.toString(selectionList.getSelectedIndex()));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -88,7 +88,7 @@ public class EpisodeListPanel extends AbstractSearchPanel<EpisodeListProvider, E
|
||||
|
||||
@Override
|
||||
protected Settings getSettings() {
|
||||
return Settings.userRoot().node("episodelist");
|
||||
return Settings.forPackage(this);
|
||||
}
|
||||
|
||||
|
||||
|
@ -0,0 +1,454 @@
|
||||
|
||||
package net.sourceforge.filebot.ui.panel.rename;
|
||||
|
||||
|
||||
import static net.sourceforge.tuned.ui.TunedUtilities.*;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Component;
|
||||
import java.awt.Window;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.WindowAdapter;
|
||||
import java.awt.event.WindowEvent;
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.TreeMap;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.script.Compilable;
|
||||
import javax.script.ScriptException;
|
||||
import javax.swing.AbstractAction;
|
||||
import javax.swing.Action;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.JDialog;
|
||||
import javax.swing.JFileChooser;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JTabbedPane;
|
||||
import javax.swing.JTable;
|
||||
import javax.swing.JTextField;
|
||||
import javax.swing.SwingWorker;
|
||||
import javax.swing.event.DocumentEvent;
|
||||
import javax.swing.event.DocumentListener;
|
||||
import javax.swing.filechooser.FileNameExtensionFilter;
|
||||
import javax.swing.table.AbstractTableModel;
|
||||
import javax.swing.table.DefaultTableCellRenderer;
|
||||
import javax.swing.table.TableModel;
|
||||
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import net.sourceforge.filebot.MediaTypes;
|
||||
import net.sourceforge.filebot.ResourceManager;
|
||||
import net.sourceforge.filebot.format.EpisodeBindingBean;
|
||||
import net.sourceforge.filebot.format.ExpressionFormat;
|
||||
import net.sourceforge.filebot.web.Episode;
|
||||
import net.sourceforge.filebot.web.EpisodeFormat;
|
||||
import net.sourceforge.tuned.DefaultThreadFactory;
|
||||
import net.sourceforge.tuned.ui.LazyDocumentListener;
|
||||
|
||||
|
||||
class EpisodeBindingDialog extends JDialog {
|
||||
|
||||
private final JTextField episodeTextField = new JTextField();
|
||||
private final JTextField mediaFileTextField = new JTextField();
|
||||
|
||||
private final BindingTableModel bindingModel = new BindingTableModel();
|
||||
|
||||
private Option selectedOption = Option.CANCEL;
|
||||
|
||||
|
||||
public enum Option {
|
||||
APPROVE,
|
||||
CANCEL
|
||||
}
|
||||
|
||||
|
||||
public EpisodeBindingDialog(Window owner) {
|
||||
super(owner, "Episode Bindings", ModalityType.DOCUMENT_MODAL);
|
||||
|
||||
// create image button from action
|
||||
JButton selectFileButton = new JButton(selectFileAction);
|
||||
selectFileButton.setHideActionText(true);
|
||||
selectFileButton.setOpaque(false);
|
||||
|
||||
JComponent root = (JComponent) getContentPane();
|
||||
root.setLayout(new MigLayout("nogrid, fill, insets dialog"));
|
||||
|
||||
// decorative tabbed pane
|
||||
JTabbedPane inputContainer = new JTabbedPane();
|
||||
inputContainer.setFocusable(false);
|
||||
|
||||
JPanel inputPanel = new JPanel(new MigLayout("nogrid, fill"));
|
||||
inputPanel.setOpaque(false);
|
||||
|
||||
inputPanel.add(new JLabel("Episode:"), "wrap 2px");
|
||||
inputPanel.add(episodeTextField, "hmin 20px, growx, wrap paragraph");
|
||||
|
||||
inputPanel.add(new JLabel("Media File:"), "wrap 2px");
|
||||
inputPanel.add(mediaFileTextField, "hmin 20px, growx");
|
||||
inputPanel.add(selectFileButton, "gap rel, w 26px!, h 24px!, wrap paragraph");
|
||||
|
||||
inputContainer.add("Episode Bindings", inputPanel);
|
||||
root.add(inputContainer, "growx, wrap paragraph");
|
||||
|
||||
root.add(new JLabel("Preview:"), "gap 5px, wrap 2px");
|
||||
root.add(new JScrollPane(createBindingTable(bindingModel)), "growx, wrap paragraph:push");
|
||||
|
||||
root.add(new JButton(approveAction), "tag apply");
|
||||
root.add(new JButton(cancelAction), "tag cancel");
|
||||
|
||||
// update preview on change
|
||||
DocumentListener changeListener = new LazyDocumentListener(1000) {
|
||||
|
||||
@Override
|
||||
public void update(DocumentEvent evt) {
|
||||
// ignore lazy events that come in after the window has been closed
|
||||
if (bindingModel.executor.isShutdown())
|
||||
return;
|
||||
|
||||
bindingModel.setModel(getSampleExpressions(), new EpisodeBindingBean(getEpisode(), getMediaFile()));
|
||||
}
|
||||
};
|
||||
|
||||
episodeTextField.getDocument().addDocumentListener(changeListener);
|
||||
mediaFileTextField.getDocument().addDocumentListener(changeListener);
|
||||
|
||||
// finish dialog and close window manually
|
||||
addWindowListener(new WindowAdapter() {
|
||||
|
||||
@Override
|
||||
public void windowClosing(WindowEvent e) {
|
||||
finish(Option.CANCEL);
|
||||
}
|
||||
});
|
||||
|
||||
// initialize window properties
|
||||
setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
|
||||
setLocation(getPreferredLocation(this));
|
||||
setSize(420, 520);
|
||||
}
|
||||
|
||||
|
||||
private JTable createBindingTable(TableModel model) {
|
||||
JTable table = new JTable(model);
|
||||
table.setAutoCreateRowSorter(true);
|
||||
|
||||
table.setFillsViewportHeight(true);
|
||||
table.setBackground(Color.white);
|
||||
|
||||
table.setDefaultRenderer(Future.class, new DefaultTableCellRenderer() {
|
||||
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
|
||||
super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Future<String> future = (Future<String>) value;
|
||||
|
||||
// reset state
|
||||
setForeground(isSelected ? table.getSelectionForeground() : table.getForeground());
|
||||
|
||||
try {
|
||||
// try to get result
|
||||
setText(future.get(0, TimeUnit.MILLISECONDS));
|
||||
} catch (TimeoutException e) {
|
||||
// not ready yet
|
||||
setText("Pending …");
|
||||
|
||||
// highlight cell
|
||||
if (!isSelected) {
|
||||
setForeground(new Color(0x6495ED)); // CornflowerBlue
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// could not evaluate expression
|
||||
setText("undefined");
|
||||
|
||||
// highlight cell
|
||||
if (!isSelected) {
|
||||
setForeground(Color.gray);
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
});
|
||||
|
||||
return table;
|
||||
}
|
||||
|
||||
|
||||
private Collection<String> getSampleExpressions() {
|
||||
ResourceBundle bundle = ResourceBundle.getBundle(getClass().getName());
|
||||
TreeMap<String, String> expressions = new TreeMap<String, String>();
|
||||
|
||||
// extract all expression entries and sort by key
|
||||
for (String key : bundle.keySet()) {
|
||||
if (key.startsWith("expr"))
|
||||
expressions.put(key, bundle.getString(key));
|
||||
}
|
||||
|
||||
return expressions.values();
|
||||
}
|
||||
|
||||
|
||||
public Option getSelectedOption() {
|
||||
return selectedOption;
|
||||
}
|
||||
|
||||
|
||||
private void finish(Option option) {
|
||||
this.selectedOption = option;
|
||||
|
||||
// cancel background evaluators
|
||||
bindingModel.executor.shutdownNow();
|
||||
|
||||
setVisible(false);
|
||||
dispose();
|
||||
}
|
||||
|
||||
|
||||
public void setEpisode(Episode episode) {
|
||||
episodeTextField.setText(episode == null ? "" : EpisodeFormat.getInstance().format(episode));
|
||||
}
|
||||
|
||||
|
||||
public void setMediaFile(File mediaFile) {
|
||||
mediaFileTextField.setText(mediaFile == null ? "" : mediaFile.getAbsolutePath());
|
||||
}
|
||||
|
||||
|
||||
public Episode getEpisode() {
|
||||
try {
|
||||
return EpisodeFormat.getInstance().parseObject(episodeTextField.getText());
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public File getMediaFile() {
|
||||
File file = new File(mediaFileTextField.getText());
|
||||
|
||||
// allow only absolute paths
|
||||
return file.isAbsolute() ? file : null;
|
||||
}
|
||||
|
||||
|
||||
protected final Action approveAction = new AbstractAction("Use Bindings", ResourceManager.getIcon("dialog.continue")) {
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent evt) {
|
||||
// check episode and media file
|
||||
if (getEpisode() == null) {
|
||||
// illegal episode string
|
||||
Logger.getLogger("ui").warning(String.format("Failed to parse episode: '%s'", episodeTextField.getText()));
|
||||
} else if (getMediaFile() == null && !mediaFileTextField.getText().isEmpty()) {
|
||||
// illegal file path
|
||||
Logger.getLogger("ui").warning(String.format("Invalid media file: '%s'", mediaFileTextField.getText()));
|
||||
} else {
|
||||
// everything seems to be in order
|
||||
finish(Option.APPROVE);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
protected final Action cancelAction = new AbstractAction("Cancel", ResourceManager.getIcon("dialog.cancel")) {
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent evt) {
|
||||
finish(Option.CANCEL);
|
||||
}
|
||||
};
|
||||
|
||||
protected final Action selectFileAction = new AbstractAction("Select File", ResourceManager.getIcon("action.load")) {
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent evt) {
|
||||
JFileChooser chooser = new JFileChooser();
|
||||
chooser.setSelectedFile(getMediaFile());
|
||||
|
||||
// collect media file extensions (video, audio and subtitle files)
|
||||
List<String> extensions = new ArrayList<String>();
|
||||
extensions.addAll(MediaTypes.getExtensionList("video"));
|
||||
extensions.addAll(MediaTypes.getExtensionList("audio"));
|
||||
extensions.addAll(MediaTypes.getExtensionList("subtitle"));
|
||||
|
||||
chooser.setFileFilter(new FileNameExtensionFilter("Media files", extensions.toArray(new String[0])));
|
||||
chooser.setMultiSelectionEnabled(false);
|
||||
|
||||
if (chooser.showOpenDialog(getWindow(evt.getSource())) == JFileChooser.APPROVE_OPTION) {
|
||||
File selectedFile = chooser.getSelectedFile();
|
||||
|
||||
if (selectedFile.isFile()) {
|
||||
// update text field
|
||||
mediaFileTextField.setText(selectedFile.getAbsolutePath());
|
||||
|
||||
// display media info
|
||||
try {
|
||||
MediaInfoPane.showMessageDialog(getWindow(evt.getSource()), selectedFile);
|
||||
} catch (LinkageError e) {
|
||||
Logger.getLogger("ui").log(Level.SEVERE, "Unable to load native library 'mediainfo'", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
private static class BindingTableModel extends AbstractTableModel {
|
||||
|
||||
private final List<Evaluator> model = new ArrayList<Evaluator>();
|
||||
|
||||
private final ExecutorService executor = Executors.newFixedThreadPool(2, new DefaultThreadFactory("Evaluator", Thread.MIN_PRIORITY));
|
||||
|
||||
|
||||
public void setModel(Collection<String> expressions, Object bindingBean) {
|
||||
// cancel old workers and clear model
|
||||
clear();
|
||||
|
||||
for (String expression : expressions) {
|
||||
Evaluator evaluator = new Evaluator(expression, bindingBean) {
|
||||
|
||||
@Override
|
||||
protected void done() {
|
||||
// update cell when computation is complete
|
||||
fireTableCellUpdated(this);
|
||||
}
|
||||
};
|
||||
|
||||
// enqueue for background execution
|
||||
executor.execute(evaluator);
|
||||
|
||||
model.add(evaluator);
|
||||
}
|
||||
|
||||
// update view
|
||||
fireTableDataChanged();
|
||||
}
|
||||
|
||||
|
||||
public void clear() {
|
||||
for (Evaluator evaluator : model) {
|
||||
evaluator.cancel(true);
|
||||
}
|
||||
|
||||
model.clear();
|
||||
|
||||
// update view
|
||||
fireTableDataChanged();
|
||||
}
|
||||
|
||||
|
||||
public void fireTableCellUpdated(Evaluator element) {
|
||||
int index = model.indexOf(element);
|
||||
|
||||
if (index >= 0) {
|
||||
fireTableCellUpdated(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getColumnName(int column) {
|
||||
switch (column) {
|
||||
case 0:
|
||||
return "Expression";
|
||||
case 1:
|
||||
return "Value";
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int getColumnCount() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int getRowCount() {
|
||||
return model.size();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Class<?> getColumnClass(int column) {
|
||||
switch (column) {
|
||||
case 0:
|
||||
return String.class;
|
||||
case 1:
|
||||
return Future.class;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Object getValueAt(int row, int column) {
|
||||
switch (column) {
|
||||
case 0:
|
||||
return model.get(row).getExpression();
|
||||
case 1:
|
||||
return model.get(row);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class Evaluator extends SwingWorker<String, Void> {
|
||||
|
||||
private final String expression;
|
||||
private final Object bindingBean;
|
||||
|
||||
|
||||
private Evaluator(String expression, Object bindingBean) {
|
||||
this.expression = expression;
|
||||
this.bindingBean = bindingBean;
|
||||
}
|
||||
|
||||
|
||||
public String getExpression() {
|
||||
return expression;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected String doInBackground() throws Exception {
|
||||
ExpressionFormat format = new ExpressionFormat(expression) {
|
||||
|
||||
@Override
|
||||
protected Object[] compile(String expression, Compilable engine) throws ScriptException {
|
||||
// simple expression format, everything as one expression
|
||||
return new Object[] { engine.compile(expression) };
|
||||
}
|
||||
};
|
||||
|
||||
// evaluate expression with given bindings
|
||||
String value = format.format(bindingBean);
|
||||
|
||||
// check for script exceptions
|
||||
if (format.caughtScriptException() != null) {
|
||||
throw format.caughtScriptException();
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
# expressions are tagged so they can be sorted alphabetically
|
||||
|
||||
# episode expressions
|
||||
expr[a1]: n
|
||||
expr[a2]: s
|
||||
expr[a3]: e
|
||||
expr[a4]: t
|
||||
expr[a5]: episode
|
||||
|
||||
# simple mediainfo expressions
|
||||
expr[b1]: vc
|
||||
expr[b2]: ac
|
||||
expr[b3]: cf
|
||||
expr[b4]: hi
|
||||
expr[b5]: resolution
|
||||
|
||||
# file expressions
|
||||
expr[c1]: file.name
|
||||
expr[c2]: file.parent
|
||||
expr[c3]: ext
|
||||
expr[c4]: crc32
|
||||
|
||||
# media info expressions [media]
|
||||
expr[d1]: media.title
|
||||
expr[d2]: media.Duration_String
|
||||
expr[d3]: media.OverallBitRate_String
|
||||
|
||||
# media info expressions [video]
|
||||
expr[e1]: video.Codec
|
||||
expr[e2]: video.FrameRate
|
||||
expr[e3]: video.DisplayAspectRatioString
|
||||
expr[e4]: video.Height
|
||||
expr[e5]: video.InterlacementString
|
||||
|
||||
# media info expressions [audio]
|
||||
expr[f1]: audio.Codec
|
||||
expr[f2]: audio.Channels
|
||||
expr[f3]: audio.BitRate_String
|
||||
expr[f4]: audio.Language
|
||||
|
||||
# media info expressions [text]
|
||||
expr[g1]: text.CodecInfo
|
||||
expr[g2]: text.Language
|
||||
|
||||
# fundamental media info expressions
|
||||
expr[h1]: media
|
||||
expr[h2]: video
|
||||
expr[h3]: audio
|
||||
expr[h4]: text
|
@ -6,7 +6,7 @@ import java.io.File;
|
||||
|
||||
import javax.script.ScriptException;
|
||||
|
||||
import net.sourceforge.filebot.format.EpisodeFormatBindingBean;
|
||||
import net.sourceforge.filebot.format.EpisodeBindingBean;
|
||||
import net.sourceforge.filebot.format.ExpressionFormat;
|
||||
import net.sourceforge.filebot.similarity.Match;
|
||||
import net.sourceforge.filebot.web.Episode;
|
||||
@ -46,7 +46,7 @@ class EpisodeExpressionFormatter implements MatchFormatter {
|
||||
Episode episode = (Episode) match.getValue();
|
||||
File mediaFile = (File) match.getCandidate();
|
||||
|
||||
String result = format.format(new EpisodeFormatBindingBean(episode, mediaFile)).trim();
|
||||
String result = format.format(new EpisodeBindingBean(episode, mediaFile)).trim();
|
||||
|
||||
// if result is empty, check for script exceptions
|
||||
if (result.isEmpty() && format.caughtScriptException() != null) {
|
||||
|
@ -4,6 +4,7 @@ package net.sourceforge.filebot.ui.panel.rename;
|
||||
|
||||
import static java.awt.Font.*;
|
||||
import static javax.swing.BorderFactory.*;
|
||||
import static net.sourceforge.tuned.ui.TunedUtilities.*;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Font;
|
||||
@ -14,13 +15,11 @@ import java.awt.event.WindowEvent;
|
||||
import java.beans.PropertyChangeEvent;
|
||||
import java.beans.PropertyChangeListener;
|
||||
import java.io.File;
|
||||
import java.text.ParseException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.RunnableFuture;
|
||||
@ -36,10 +35,8 @@ import javax.swing.Action;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.JDialog;
|
||||
import javax.swing.JFileChooser;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JMenuItem;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JPopupMenu;
|
||||
import javax.swing.JTextField;
|
||||
@ -47,18 +44,18 @@ import javax.swing.KeyStroke;
|
||||
import javax.swing.SwingWorker;
|
||||
import javax.swing.Timer;
|
||||
import javax.swing.event.DocumentEvent;
|
||||
import javax.swing.filechooser.FileNameExtensionFilter;
|
||||
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import net.sourceforge.filebot.ResourceManager;
|
||||
import net.sourceforge.filebot.Settings;
|
||||
import net.sourceforge.filebot.format.EpisodeFormatBindingBean;
|
||||
import net.sourceforge.filebot.format.EpisodeBindingBean;
|
||||
import net.sourceforge.filebot.format.ExpressionFormat;
|
||||
import net.sourceforge.filebot.web.Episode;
|
||||
import net.sourceforge.filebot.web.EpisodeFormat;
|
||||
import net.sourceforge.tuned.DefaultThreadFactory;
|
||||
import net.sourceforge.tuned.ExceptionUtilities;
|
||||
import net.sourceforge.tuned.PreferencesList;
|
||||
import net.sourceforge.tuned.PreferencesMap.PreferencesEntry;
|
||||
import net.sourceforge.tuned.ui.GradientStyle;
|
||||
import net.sourceforge.tuned.ui.LazyDocumentListener;
|
||||
import net.sourceforge.tuned.ui.LinkButton;
|
||||
@ -68,30 +65,29 @@ import net.sourceforge.tuned.ui.notification.SeparatorBorder;
|
||||
import net.sourceforge.tuned.ui.notification.SeparatorBorder.Position;
|
||||
|
||||
|
||||
public class EpisodeFormatDialog extends JDialog {
|
||||
class EpisodeFormatDialog extends JDialog {
|
||||
|
||||
private Option selectedOption = Option.CANCEL;
|
||||
|
||||
private ExpressionFormat selectedFormat = null;
|
||||
private ExpressionFormat selectedFormat;
|
||||
|
||||
private EpisodeBindingBean sample;
|
||||
|
||||
private ExecutorService executor;
|
||||
|
||||
private RunnableFuture<String> currentPreviewFuture;
|
||||
|
||||
private JLabel preview = new JLabel();
|
||||
|
||||
private JLabel status = new JLabel();
|
||||
|
||||
private EpisodeFormatBindingBean previewSample = new EpisodeFormatBindingBean(getPreviewSampleEpisode(), getPreviewSampleMediaFile());
|
||||
|
||||
private ExecutorService previewExecutor = createPreviewExecutor();
|
||||
|
||||
private RunnableFuture<String> currentPreviewFuture = null;
|
||||
|
||||
private ProgressIndicator progressIndicator = new ProgressIndicator();
|
||||
|
||||
private JTextField editor = new JTextField();
|
||||
|
||||
private PreferencesList<String> persistentFormatHistory = Settings.userRoot().node("rename/format.recent").asList();
|
||||
|
||||
private Color defaultColor = preview.getForeground();
|
||||
private Color errorColor = Color.red;
|
||||
private PreferencesEntry<String> persistentSampleEpisode = Settings.forPackage(this).entry("format.sample.episode");
|
||||
private PreferencesEntry<String> persistentSampleFile = Settings.forPackage(this).entry("format.sample.file");
|
||||
private PreferencesList<String> persistentFormatHistory = Settings.forPackage(this).node("format.recent").asList();
|
||||
|
||||
|
||||
public enum Option {
|
||||
@ -104,9 +100,16 @@ public class EpisodeFormatDialog extends JDialog {
|
||||
public EpisodeFormatDialog(Window owner) {
|
||||
super(owner, "Episode Format", ModalityType.DOCUMENT_MODAL);
|
||||
|
||||
sample = restoreSample();
|
||||
executor = createExecutor();
|
||||
|
||||
editor.setFont(new Font(MONOSPACED, PLAIN, 14));
|
||||
progressIndicator.setVisible(false);
|
||||
|
||||
// image button
|
||||
JButton changeSampleButton = new JButton(changeSampleAction);
|
||||
changeSampleButton.setHideActionText(true);
|
||||
|
||||
// bold title label in header
|
||||
JLabel title = new JLabel(this.getTitle());
|
||||
title.setFont(title.getFont().deriveFont(BOLD));
|
||||
@ -123,13 +126,14 @@ public class EpisodeFormatDialog extends JDialog {
|
||||
|
||||
JPanel content = new JPanel(new MigLayout("insets dialog, nogrid, fill"));
|
||||
|
||||
content.add(editor, "w 120px:min(pref, 420px), h 40px!, growx, wrap 8px");
|
||||
content.add(editor, "w 120px:min(pref, 420px), h 40px!, growx, wrap 4px, id editor");
|
||||
content.add(changeSampleButton, "w 25!, h 19!, pos n editor.y2+1 editor.x2 n");
|
||||
|
||||
content.add(new JLabel("Syntax"), "gap indent+unrel, wrap 0");
|
||||
content.add(createSyntaxPanel(), "gapx indent indent, wrap 8px");
|
||||
|
||||
content.add(new JLabel("Examples"), "gap indent+unrel, wrap 0");
|
||||
content.add(createExamplesPanel(), "hmin 50px, gapx indent indent, wrap 25px:push");
|
||||
content.add(createExamplesPanel(), "h pref!, gapx indent indent, wrap 25px:push");
|
||||
|
||||
content.add(new JButton(useDefaultFormatAction), "tag left");
|
||||
content.add(new JButton(approveFormatAction), "tag apply");
|
||||
@ -141,8 +145,6 @@ public class EpisodeFormatDialog extends JDialog {
|
||||
pane.add(header, "h 60px, growx, dock north");
|
||||
pane.add(content, "grow");
|
||||
|
||||
header.setComponentPopupMenu(createPreviewSamplePopup());
|
||||
|
||||
// enable undo/redo
|
||||
TunedUtilities.installUndoSupport(editor);
|
||||
|
||||
@ -155,7 +157,7 @@ public class EpisodeFormatDialog extends JDialog {
|
||||
}
|
||||
});
|
||||
|
||||
addPropertyChangeListener("previewSample", new PropertyChangeListener() {
|
||||
addPropertyChangeListener("sample", new PropertyChangeListener() {
|
||||
|
||||
@Override
|
||||
public void propertyChange(PropertyChangeEvent evt) {
|
||||
@ -188,73 +190,16 @@ public class EpisodeFormatDialog extends JDialog {
|
||||
editor.setText(persistentFormatHistory.isEmpty() ? "" : persistentFormatHistory.get(0));
|
||||
|
||||
// update preview to current format
|
||||
firePreviewSampleChanged();
|
||||
fireSampleChanged();
|
||||
|
||||
// initialize window properties
|
||||
setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
|
||||
setLocation(TunedUtilities.getPreferredLocation(this));
|
||||
setLocation(getPreferredLocation(this));
|
||||
pack();
|
||||
}
|
||||
|
||||
|
||||
private JPopupMenu createPreviewSamplePopup() {
|
||||
JPopupMenu actionPopup = new JPopupMenu("Sample");
|
||||
|
||||
actionPopup.add(new AbstractAction("Change Episode") {
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent evt) {
|
||||
String episodeString = JOptionPane.showInputDialog(EpisodeFormatDialog.this, null, EpisodeFormat.getInstance().format(previewSample.getEpisode()));
|
||||
|
||||
if (episodeString != null) {
|
||||
try {
|
||||
Episode episode = EpisodeFormat.getInstance().parseObject(episodeString);
|
||||
|
||||
// change episode
|
||||
previewSample = new EpisodeFormatBindingBean(episode, previewSample.getMediaFile());
|
||||
Settings.userRoot().put("dialog.sample.episode", episodeString);
|
||||
firePreviewSampleChanged();
|
||||
} catch (ParseException e) {
|
||||
Logger.getLogger("ui").warning(String.format("Cannot parse '%s'", episodeString));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
actionPopup.add(new AbstractAction("Change Media File") {
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent evt) {
|
||||
JFileChooser fileChooser = new JFileChooser();
|
||||
fileChooser.setSelectedFile(previewSample.getMediaFile());
|
||||
fileChooser.setFileFilter(new FileNameExtensionFilter("Media files", "avi", "mkv", "mp4", "ogm"));
|
||||
|
||||
if (fileChooser.showOpenDialog(EpisodeFormatDialog.this) == JFileChooser.APPROVE_OPTION) {
|
||||
File mediaFile = fileChooser.getSelectedFile();
|
||||
|
||||
try {
|
||||
MediaInfoPane.showMessageDialog(EpisodeFormatDialog.this, mediaFile);
|
||||
} catch (LinkageError e) {
|
||||
// MediaInfo native library is missing -> notify user
|
||||
Logger.getLogger("ui").log(Level.SEVERE, e.getMessage(), e);
|
||||
|
||||
// rethrow error
|
||||
throw e;
|
||||
}
|
||||
|
||||
// change media file
|
||||
previewSample = new EpisodeFormatBindingBean(previewSample.getEpisode(), mediaFile);
|
||||
Settings.userRoot().put("dialog.sample.file", mediaFile.getAbsolutePath());
|
||||
firePreviewSampleChanged();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return actionPopup;
|
||||
}
|
||||
|
||||
|
||||
private JPanel createSyntaxPanel() {
|
||||
private JComponent createSyntaxPanel() {
|
||||
JPanel panel = new JPanel(new MigLayout("fill, nogrid"));
|
||||
|
||||
panel.setBorder(createLineBorder(new Color(0xACA899)));
|
||||
@ -274,21 +219,15 @@ public class EpisodeFormatDialog extends JDialog {
|
||||
panel.setBackground(new Color(0xFFFFE1));
|
||||
|
||||
ResourceBundle bundle = ResourceBundle.getBundle(getClass().getName());
|
||||
TreeMap<String, String> examples = new TreeMap<String, String>();
|
||||
|
||||
// collect example keys
|
||||
List<String> examples = new ArrayList<String>();
|
||||
|
||||
// extract all example entries and sort by key
|
||||
for (String key : bundle.keySet()) {
|
||||
if (key.startsWith("example"))
|
||||
examples.add(key);
|
||||
examples.put(key, bundle.getString(key));
|
||||
}
|
||||
|
||||
// sort by example key
|
||||
Collections.sort(examples);
|
||||
|
||||
for (String key : examples) {
|
||||
final String format = bundle.getString(key);
|
||||
|
||||
for (final String format : examples.values()) {
|
||||
LinkButton formatLink = new LinkButton(new AbstractAction(format) {
|
||||
|
||||
@Override
|
||||
@ -302,16 +241,14 @@ public class EpisodeFormatDialog extends JDialog {
|
||||
final JLabel formatExample = new JLabel();
|
||||
|
||||
// bind text to preview
|
||||
addPropertyChangeListener("previewSample", new PropertyChangeListener() {
|
||||
addPropertyChangeListener("sample", new PropertyChangeListener() {
|
||||
|
||||
@Override
|
||||
public void propertyChange(PropertyChangeEvent evt) {
|
||||
try {
|
||||
formatExample.setText(new ExpressionFormat(format).format(previewSample));
|
||||
setForeground(defaultColor);
|
||||
formatExample.setText(new ExpressionFormat(format).format(sample));
|
||||
} catch (Exception e) {
|
||||
formatExample.setText(ExceptionUtilities.getRootCauseMessage(e));
|
||||
setForeground(errorColor);
|
||||
Logger.getLogger(getClass().getName()).log(Level.SEVERE, e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -325,35 +262,30 @@ public class EpisodeFormatDialog extends JDialog {
|
||||
}
|
||||
|
||||
|
||||
private Episode getPreviewSampleEpisode() {
|
||||
String sample = Settings.userRoot().get("dialog.sample.episode");
|
||||
private EpisodeBindingBean restoreSample() {
|
||||
Episode episode = null;
|
||||
File mediaFile = null;
|
||||
|
||||
if (sample != null) {
|
||||
try {
|
||||
return EpisodeFormat.getInstance().parseObject(sample);
|
||||
} catch (Exception e) {
|
||||
Logger.getLogger(getClass().getName()).warning(e.getMessage());
|
||||
}
|
||||
// restore episode
|
||||
try {
|
||||
episode = EpisodeFormat.getInstance().parseObject(persistentSampleEpisode.getValue());
|
||||
} catch (Exception e) {
|
||||
// default sample
|
||||
episode = new Episode("Dark Angel", 3, 1, "Labyrinth");
|
||||
}
|
||||
|
||||
// default sample
|
||||
return new Episode("Dark Angel", "3", "1", "Labyrinth");
|
||||
// restore media file
|
||||
String path = persistentSampleFile.getValue();
|
||||
|
||||
if (path != null && !path.isEmpty()) {
|
||||
mediaFile = new File(path);
|
||||
}
|
||||
|
||||
return new EpisodeBindingBean(episode, mediaFile);
|
||||
}
|
||||
|
||||
|
||||
private File getPreviewSampleMediaFile() {
|
||||
String sample = Settings.userRoot().get("dialog.sample.file");
|
||||
|
||||
if (sample != null) {
|
||||
return new File(sample);
|
||||
}
|
||||
|
||||
// default sample
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
private ExecutorService createPreviewExecutor() {
|
||||
private ExecutorService createExecutor() {
|
||||
ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(1), new DefaultThreadFactory("PreviewFormatter")) {
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@ -406,7 +338,7 @@ public class EpisodeFormatDialog extends JDialog {
|
||||
|
||||
@Override
|
||||
protected String doInBackground() throws Exception {
|
||||
return format.format(previewSample);
|
||||
return format.format(sample);
|
||||
}
|
||||
|
||||
|
||||
@ -433,7 +365,7 @@ public class EpisodeFormatDialog extends JDialog {
|
||||
status.setVisible(true);
|
||||
} finally {
|
||||
preview.setVisible(preview.getText().trim().length() > 0);
|
||||
editor.setForeground(defaultColor);
|
||||
editor.setForeground(preview.getForeground());
|
||||
|
||||
// stop progress indicator from becoming visible, if we have been fast enough
|
||||
progressIndicatorTimer.stop();
|
||||
@ -447,7 +379,7 @@ public class EpisodeFormatDialog extends JDialog {
|
||||
};
|
||||
|
||||
// submit new worker
|
||||
previewExecutor.execute(currentPreviewFuture);
|
||||
executor.execute(currentPreviewFuture);
|
||||
} catch (ScriptException e) {
|
||||
// incorrect syntax
|
||||
status.setText(ExceptionUtilities.getRootCauseMessage(e));
|
||||
@ -455,7 +387,7 @@ public class EpisodeFormatDialog extends JDialog {
|
||||
status.setVisible(true);
|
||||
|
||||
preview.setVisible(false);
|
||||
editor.setForeground(errorColor);
|
||||
editor.setForeground(Color.red);
|
||||
}
|
||||
}
|
||||
|
||||
@ -474,13 +406,42 @@ public class EpisodeFormatDialog extends JDialog {
|
||||
selectedOption = option;
|
||||
|
||||
// force shutdown
|
||||
previewExecutor.shutdownNow();
|
||||
executor.shutdownNow();
|
||||
|
||||
setVisible(false);
|
||||
dispose();
|
||||
}
|
||||
|
||||
|
||||
protected final Action changeSampleAction = new AbstractAction("Change Sample", ResourceManager.getIcon("action.variable")) {
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent evt) {
|
||||
EpisodeBindingDialog dialog = new EpisodeBindingDialog(getWindow(evt.getSource()));
|
||||
|
||||
dialog.setEpisode(sample.getEpisode());
|
||||
dialog.setMediaFile(sample.getMediaFile());
|
||||
|
||||
// open dialog
|
||||
dialog.setVisible(true);
|
||||
|
||||
if (dialog.getSelectedOption() == EpisodeBindingDialog.Option.APPROVE) {
|
||||
Episode episode = dialog.getEpisode();
|
||||
File file = dialog.getMediaFile();
|
||||
|
||||
// change sample
|
||||
sample = new EpisodeBindingBean(episode, file);
|
||||
|
||||
// remember
|
||||
persistentSampleEpisode.setValue(episode == null ? "" : EpisodeFormat.getInstance().format(sample.getEpisode()));
|
||||
persistentSampleFile.setValue(file == null ? "" : sample.getMediaFile().getAbsolutePath());
|
||||
|
||||
// reevaluate everything
|
||||
fireSampleChanged();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
protected final Action displayRecentFormatHistory = new AbstractAction("Recent") {
|
||||
|
||||
@Override
|
||||
@ -550,8 +511,8 @@ public class EpisodeFormatDialog extends JDialog {
|
||||
};
|
||||
|
||||
|
||||
protected void firePreviewSampleChanged() {
|
||||
firePropertyChange("previewSample", null, previewSample);
|
||||
protected void fireSampleChanged() {
|
||||
firePropertyChange("sample", null, sample);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
syntax: <html><b>{</b> <b>}</b> ... expression, <b>n</b> ... name, <b>s</b> ... season, <b>e</b> ... episode, <b>t</b> ... title</html>
|
||||
syntax: <html><b>{</b> <b>}</b> \u2026 expression, <b>n</b> \u2026 name, <b>s</b> \u2026 season, <b>e</b> \u2026 episode, <b>t</b> \u2026 title</html>
|
||||
|
||||
# basic 1.01
|
||||
example[0]: {n} - {s}.{e} - {t}
|
||||
|
@ -2,7 +2,9 @@
|
||||
package net.sourceforge.filebot.ui.panel.rename;
|
||||
|
||||
|
||||
import java.awt.Component;
|
||||
import static net.sourceforge.tuned.ui.TunedUtilities.*;
|
||||
|
||||
import java.awt.Window;
|
||||
import java.awt.Dialog.ModalityType;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.io.File;
|
||||
@ -24,10 +26,9 @@ import javax.swing.table.AbstractTableModel;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import net.sourceforge.filebot.mediainfo.MediaInfo;
|
||||
import net.sourceforge.filebot.mediainfo.MediaInfo.StreamKind;
|
||||
import net.sourceforge.tuned.ui.TunedUtilities;
|
||||
|
||||
|
||||
public class MediaInfoPane extends JTabbedPane {
|
||||
class MediaInfoPane extends JTabbedPane {
|
||||
|
||||
public MediaInfoPane(File file) {
|
||||
// get media info
|
||||
@ -61,9 +62,8 @@ public class MediaInfoPane extends JTabbedPane {
|
||||
}
|
||||
|
||||
|
||||
public static void showMessageDialog(Component parent, File file) {
|
||||
final JDialog dialog = new JDialog(TunedUtilities.getWindow(parent), "MediaInfo", ModalityType.DOCUMENT_MODAL);
|
||||
dialog.setLocationByPlatform(true);
|
||||
public static void showMessageDialog(Window parent, File file) {
|
||||
final JDialog dialog = new JDialog(parent, "MediaInfo", ModalityType.DOCUMENT_MODAL);
|
||||
|
||||
Action closeAction = new AbstractAction("OK") {
|
||||
|
||||
@ -79,7 +79,9 @@ public class MediaInfoPane extends JTabbedPane {
|
||||
c.add(new MediaInfoPane(file), "grow, wrap");
|
||||
c.add(new JButton(closeAction), "wmin 80px, hmin 25px");
|
||||
|
||||
dialog.setLocation(getPreferredLocation(dialog));
|
||||
dialog.pack();
|
||||
|
||||
dialog.setVisible(true);
|
||||
}
|
||||
|
||||
|
@ -15,7 +15,6 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.prefs.Preferences;
|
||||
|
||||
import javax.swing.AbstractAction;
|
||||
import javax.swing.Action;
|
||||
@ -43,9 +42,7 @@ 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.PreferencesMap.AbstractAdapter;
|
||||
import net.sourceforge.tuned.PreferencesMap.PreferencesEntry;
|
||||
import net.sourceforge.tuned.PreferencesMap.SimpleAdapter;
|
||||
import net.sourceforge.tuned.ui.ActionPopup;
|
||||
import net.sourceforge.tuned.ui.LoadingOverlayPane;
|
||||
|
||||
@ -62,7 +59,8 @@ public class RenamePanel extends JComponent {
|
||||
|
||||
protected final RenameAction renameAction = new RenameAction(renameModel);
|
||||
|
||||
private final PreferencesEntry<Boolean> persistentPreserveExtension = Settings.userRoot().entry("rename.extension.preserve", SimpleAdapter.forClass(Boolean.class));
|
||||
private final PreferencesEntry<String> persistentPreserveExtension = Settings.forPackage(this).entry("rename.extension.preserve").defaultValue("true");
|
||||
private final PreferencesEntry<String> persistentFormatExpression = Settings.forPackage(this).entry("rename.format");
|
||||
|
||||
|
||||
public RenamePanel() {
|
||||
@ -72,19 +70,19 @@ public class RenamePanel extends JComponent {
|
||||
filesList.setTitle("Original Files");
|
||||
filesList.setTransferablePolicy(new FilesListTransferablePolicy(renameModel.files()));
|
||||
|
||||
try {
|
||||
// restore state
|
||||
renameModel.setPreserveExtension(persistentPreserveExtension.getValue());
|
||||
} catch (Exception e) {
|
||||
// preserve extension by default
|
||||
renameModel.setPreserveExtension(true);
|
||||
}
|
||||
// restore state
|
||||
renameModel.setPreserveExtension(Boolean.parseBoolean(persistentPreserveExtension.getValue()));
|
||||
|
||||
// filename formatter
|
||||
renameModel.useFormatter(File.class, new FileNameFormatter(renameModel.preserveExtension()));
|
||||
|
||||
// restore custom episode formatter
|
||||
renameModel.useFormatter(Episode.class, persistentExpressionFormatter.getValue());
|
||||
try {
|
||||
// restore custom episode formatter
|
||||
ExpressionFormat format = new ExpressionFormat(persistentFormatExpression.getValue());
|
||||
renameModel.useFormatter(Episode.class, new EpisodeExpressionFormatter(format));
|
||||
} catch (Exception e) {
|
||||
// illegal format, ignore
|
||||
}
|
||||
|
||||
RenameListCellRenderer cellrenderer = new RenameListCellRenderer(renameModel);
|
||||
|
||||
@ -161,11 +159,11 @@ public class RenamePanel extends JComponent {
|
||||
case APPROVE:
|
||||
EpisodeExpressionFormatter formatter = new EpisodeExpressionFormatter(dialog.getSelectedFormat());
|
||||
renameModel.useFormatter(Episode.class, formatter);
|
||||
persistentExpressionFormatter.setValue(formatter);
|
||||
persistentFormatExpression.setValue(formatter.getFormat().getExpression());
|
||||
break;
|
||||
case USE_DEFAULT:
|
||||
renameModel.useFormatter(Episode.class, null);
|
||||
persistentExpressionFormatter.remove();
|
||||
persistentFormatExpression.remove();
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -246,7 +244,7 @@ public class RenamePanel extends JComponent {
|
||||
filesList.repaint();
|
||||
|
||||
// save state
|
||||
persistentPreserveExtension.setValue(activate);
|
||||
persistentPreserveExtension.setValue(Boolean.toString(activate));
|
||||
}
|
||||
}
|
||||
|
||||
@ -311,29 +309,4 @@ public class RenamePanel extends JComponent {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private final PreferencesEntry<EpisodeExpressionFormatter> persistentExpressionFormatter = Settings.userRoot().entry("rename.format", new AbstractAdapter<EpisodeExpressionFormatter>() {
|
||||
|
||||
@Override
|
||||
public EpisodeExpressionFormatter get(Preferences prefs, String key) {
|
||||
String expression = prefs.get(key, null);
|
||||
|
||||
if (expression != null) {
|
||||
try {
|
||||
return new EpisodeExpressionFormatter(new ExpressionFormat(expression));
|
||||
} catch (Exception e) {
|
||||
Logger.getLogger("ui").log(Level.WARNING, e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void put(Preferences prefs, String key, EpisodeExpressionFormatter value) {
|
||||
prefs.put(key, value.getFormat().getExpression());
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
@ -3,7 +3,6 @@ package net.sourceforge.filebot.ui.panel.subtitle;
|
||||
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.MissingResourceException;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.Set;
|
||||
|
||||
@ -56,11 +55,9 @@ public class Language {
|
||||
|
||||
try {
|
||||
return new Language(code, bundle.getString(code));
|
||||
} catch (MissingResourceException e) {
|
||||
// ignore
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
@ -7,11 +7,11 @@ import static net.sourceforge.filebot.ui.panel.subtitle.LanguageComboBoxModel.*;
|
||||
|
||||
import java.awt.event.ItemEvent;
|
||||
import java.net.URI;
|
||||
import java.util.AbstractList;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.prefs.Preferences;
|
||||
|
||||
import javax.swing.Icon;
|
||||
import javax.swing.JComboBox;
|
||||
@ -26,7 +26,7 @@ import net.sourceforge.filebot.web.SubsceneSubtitleClient;
|
||||
import net.sourceforge.filebot.web.SubtitleDescriptor;
|
||||
import net.sourceforge.filebot.web.SubtitleProvider;
|
||||
import net.sourceforge.filebot.web.SubtitleSourceClient;
|
||||
import net.sourceforge.tuned.PreferencesMap.AbstractAdapter;
|
||||
import net.sourceforge.tuned.PreferencesList;
|
||||
import net.sourceforge.tuned.PreferencesMap.PreferencesEntry;
|
||||
import net.sourceforge.tuned.ui.LabelProvider;
|
||||
import net.sourceforge.tuned.ui.SimpleLabelProvider;
|
||||
@ -36,6 +36,9 @@ public class SubtitlePanel extends AbstractSearchPanel<SubtitleProvider, Subtitl
|
||||
|
||||
private final LanguageComboBoxModel languageModel = new LanguageComboBoxModel();
|
||||
|
||||
private final PreferencesEntry<String> persistentSelectedLanguage = Settings.forPackage(this).entry("language.selected");
|
||||
private final PreferencesList<String> persistentFavoriteLanguages = Settings.forPackage(this).node("language.favorites").asList();
|
||||
|
||||
|
||||
public SubtitlePanel() {
|
||||
historyPanel.setColumnHeader(0, "Show / Movie");
|
||||
@ -45,9 +48,13 @@ public class SubtitlePanel extends AbstractSearchPanel<SubtitleProvider, Subtitl
|
||||
|
||||
languageComboBox.setRenderer(new LanguageComboBoxCellRenderer(languageComboBox.getRenderer()));
|
||||
|
||||
// restore state
|
||||
languageModel.setSelectedItem(persistentSelectedLanguage.getValue());
|
||||
languageModel.favorites().addAll(0, persistentFavorites.getValue());
|
||||
// restore selected language
|
||||
languageModel.setSelectedItem(Language.getLanguage(persistentSelectedLanguage.getValue()));
|
||||
|
||||
// restore favorite languages
|
||||
for (String favoriteLanguage : persistentFavoriteLanguages) {
|
||||
languageModel.favorites().add(0, Language.getLanguage(favoriteLanguage));
|
||||
}
|
||||
|
||||
// guess favorite languages
|
||||
if (languageModel.favorites().isEmpty()) {
|
||||
@ -64,10 +71,22 @@ public class SubtitlePanel extends AbstractSearchPanel<SubtitleProvider, Subtitl
|
||||
Language language = (Language) e.getItem();
|
||||
|
||||
if (languageModel.favorites().add(language)) {
|
||||
persistentFavorites.setValue(languageModel.favorites());
|
||||
persistentFavoriteLanguages.set(new AbstractList<String>() {
|
||||
|
||||
@Override
|
||||
public String get(int index) {
|
||||
return languageModel.favorites().get(0).getCode();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return languageModel.favorites().size();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
persistentSelectedLanguage.setValue(language);
|
||||
persistentSelectedLanguage.setValue(language.getCode());
|
||||
}
|
||||
});
|
||||
|
||||
@ -95,7 +114,7 @@ public class SubtitlePanel extends AbstractSearchPanel<SubtitleProvider, Subtitl
|
||||
|
||||
@Override
|
||||
protected Settings getSettings() {
|
||||
return Settings.userRoot().node("subtitles");
|
||||
return Settings.forPackage(this);
|
||||
}
|
||||
|
||||
|
||||
@ -199,49 +218,4 @@ public class SubtitlePanel extends AbstractSearchPanel<SubtitleProvider, Subtitl
|
||||
|
||||
}
|
||||
|
||||
|
||||
private final PreferencesEntry<Language> persistentSelectedLanguage = getSettings().entry("language.selected", new AbstractAdapter<Language>() {
|
||||
|
||||
@Override
|
||||
public Language get(Preferences prefs, String key) {
|
||||
return Language.getLanguage(prefs.get(key, ""));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void put(Preferences prefs, String key, Language value) {
|
||||
prefs.put(key, value == null ? "undefined" : value.getCode());
|
||||
}
|
||||
});
|
||||
|
||||
private final PreferencesEntry<List<Language>> persistentFavorites = getSettings().entry("language.favorites", new AbstractAdapter<List<Language>>() {
|
||||
|
||||
@Override
|
||||
public List<Language> get(Preferences prefs, String key) {
|
||||
List<Language> languages = new ArrayList<Language>();
|
||||
|
||||
for (String languageCode : prefs.get(key, "").split("\\W+")) {
|
||||
languages.add(Language.getLanguage(languageCode));
|
||||
}
|
||||
|
||||
return languages;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void put(Preferences prefs, String key, List<Language> languages) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
for (int i = 0; i < languages.size(); i++) {
|
||||
sb.append(languages.get(i).getCode());
|
||||
|
||||
if (i < languages.size() - 1) {
|
||||
sb.append(",");
|
||||
}
|
||||
}
|
||||
|
||||
prefs.put(key, sb.toString());
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
@ -22,21 +22,20 @@ import java.util.regex.Pattern;
|
||||
|
||||
import javax.swing.Icon;
|
||||
|
||||
import net.sourceforge.filebot.ResourceManager;
|
||||
import net.sourceforge.filebot.Settings;
|
||||
import net.sourceforge.tuned.FileUtilities;
|
||||
import net.sourceforge.tuned.PreferencesMap.SimpleAdapter;
|
||||
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Node;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
import net.sourceforge.filebot.ResourceManager;
|
||||
import net.sourceforge.filebot.Settings;
|
||||
import net.sourceforge.tuned.FileUtilities;
|
||||
|
||||
|
||||
public class SubsceneSubtitleClient implements SubtitleProvider {
|
||||
|
||||
private static final String host = "subscene.com";
|
||||
|
||||
private final Map<String, Integer> languageFilterMap = initLanguageFilterMap();
|
||||
private final Map<String, String> languageFilterMap = initLanguageFilterMap();
|
||||
|
||||
|
||||
@Override
|
||||
@ -98,7 +97,7 @@ public class SubsceneSubtitleClient implements SubtitleProvider {
|
||||
public List<SubtitleDescriptor> getSubtitleList(SearchResult searchResult, String languageName) throws Exception {
|
||||
URL subtitleListUrl = getSubtitleListLink(searchResult, languageName).toURL();
|
||||
|
||||
Integer languageFilter = null;
|
||||
String languageFilter = null;
|
||||
|
||||
if (languageName != null) {
|
||||
synchronized (languageFilterMap) {
|
||||
@ -157,7 +156,7 @@ public class SubsceneSubtitleClient implements SubtitleProvider {
|
||||
}
|
||||
|
||||
|
||||
protected Document getSubtitleListDocument(URL subtitleListUrl, Integer languageFilter) throws IOException, SAXException {
|
||||
protected Document getSubtitleListDocument(URL subtitleListUrl, String languageFilter) throws IOException, SAXException {
|
||||
URLConnection connection = subtitleListUrl.openConnection();
|
||||
|
||||
if (languageFilter != null) {
|
||||
@ -168,13 +167,13 @@ public class SubsceneSubtitleClient implements SubtitleProvider {
|
||||
}
|
||||
|
||||
|
||||
protected Map<String, Integer> initLanguageFilterMap() {
|
||||
return Settings.userRoot().node("subtitles/subscene/languageFilterMap").asMap(SimpleAdapter.forClass(Integer.class));
|
||||
protected Map<String, String> initLanguageFilterMap() {
|
||||
return Settings.forPackage(this).node("subtitles/subscene/languageFilterMap").asMap();
|
||||
}
|
||||
|
||||
|
||||
protected Map<String, Integer> getLanguageFilterMap(Document subtitleListDocument) {
|
||||
Map<String, Integer> filters = new HashMap<String, Integer>(50);
|
||||
protected Map<String, String> getLanguageFilterMap(Document subtitleListDocument) {
|
||||
Map<String, String> filters = new HashMap<String, String>(50);
|
||||
|
||||
List<Node> nodes = selectNodes("//DIV[@class='languageList']/DIV", subtitleListDocument);
|
||||
|
||||
@ -186,7 +185,7 @@ public class SubsceneSubtitleClient implements SubtitleProvider {
|
||||
// select LABEL/text()
|
||||
String name = getTextContent("LABEL", node);
|
||||
|
||||
filters.put(name.toLowerCase(), Integer.valueOf(filter));
|
||||
filters.put(name.toLowerCase(), filter);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -326,11 +326,23 @@ public class PreferencesMap<T> implements Map<String, T> {
|
||||
@Override
|
||||
public T setValue(T value) {
|
||||
adapter.put(prefs, key, value);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public PreferencesEntry<T> defaultValue(T value) {
|
||||
try {
|
||||
// check if value valid and not null
|
||||
getValue().getClass();
|
||||
} catch (Exception e) {
|
||||
// illegal value or null, just override
|
||||
setValue(value);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public void remove() {
|
||||
adapter.remove(prefs, key);
|
||||
}
|
||||
|
@ -14,19 +14,26 @@ public abstract class LazyDocumentListener implements DocumentListener {
|
||||
|
||||
private DocumentEvent lastEvent;
|
||||
|
||||
private final Timer timer = new Timer(200, new ActionListener() {
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
update(lastEvent);
|
||||
|
||||
// we don't need it anymore
|
||||
lastEvent = null;
|
||||
}
|
||||
});
|
||||
private final Timer timer;
|
||||
|
||||
|
||||
public LazyDocumentListener() {
|
||||
this(200);
|
||||
}
|
||||
|
||||
|
||||
public LazyDocumentListener(int delay) {
|
||||
timer = new Timer(delay, new ActionListener() {
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
update(lastEvent);
|
||||
|
||||
// we don't need it anymore
|
||||
lastEvent = null;
|
||||
}
|
||||
});
|
||||
|
||||
timer.setRepeats(false);
|
||||
}
|
||||
|
||||
|
@ -31,6 +31,7 @@ public class SubsceneSubtitleClientTest {
|
||||
lostSearchResult = new HyperLink("Lost - Fourth Season (2008)", new URL("http://subscene.com/Lost-Fourth-Season/subtitles-70963.aspx"));
|
||||
}
|
||||
|
||||
|
||||
private SubsceneSubtitleClient subscene = new SubsceneSubtitleClient();
|
||||
|
||||
|
||||
@ -83,12 +84,12 @@ public class SubsceneSubtitleClientTest {
|
||||
|
||||
@Test
|
||||
public void getLanguageFilterMap() throws Exception {
|
||||
Map<String, Integer> filters = subscene.getLanguageFilterMap(subscene.getSubtitleListDocument(new URL("http://subscene.com/none/subtitles-0.aspx"), null));
|
||||
Map<String, String> filters = subscene.getLanguageFilterMap(subscene.getSubtitleListDocument(new URL("http://subscene.com/none/subtitles-0.aspx"), null));
|
||||
|
||||
assertEquals(01, filters.get("albanian"), 0);
|
||||
assertEquals(13, filters.get("english"), 0);
|
||||
assertEquals(17, filters.get("finnish"), 0);
|
||||
assertEquals(45, filters.get("vietnamese"), 0);
|
||||
assertEquals("1", filters.get("albanian"));
|
||||
assertEquals("13", filters.get("english"));
|
||||
assertEquals("17", filters.get("finnish"));
|
||||
assertEquals("45", filters.get("vietnamese"));
|
||||
}
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user