From 3956b6112797b3e0975615c86e222bed0d5f2a2a Mon Sep 17 00:00:00 2001 From: Reinhard Pointner Date: Wed, 29 Jul 2009 20:31:08 +0000 Subject: [PATCH] * added selection dialog for format sample in episode format dialog * make MediaInfo thread-safe * refactor class Settings and lots of related code changes --- source/net/sourceforge/filebot/Main.java | 2 +- source/net/sourceforge/filebot/Settings.java | 31 +- .../format/AssociativeScriptObject.java | 6 +- ...ndingBean.java => EpisodeBindingBean.java} | 24 +- .../filebot/format/ExpressionBindings.java | 7 +- .../filebot/mediainfo/MediaInfo.java | 27 +- .../filebot/resources/action.variable.png | Bin 0 -> 362 bytes .../similarity/SeasonEpisodeMatcher.java | 27 +- .../net/sourceforge/filebot/ui/MainFrame.java | 22 +- .../panel/episodelist/EpisodeListPanel.java | 2 +- .../ui/panel/rename/EpisodeBindingDialog.java | 454 ++++++++++++++++++ .../rename/EpisodeBindingDialog.properties | 49 ++ .../rename/EpisodeExpressionFormatter.java | 4 +- .../ui/panel/rename/EpisodeFormatDialog.java | 219 ++++----- .../rename/EpisodeFormatDialog.properties | 2 +- .../ui/panel/rename/MediaInfoPane.java | 14 +- .../filebot/ui/panel/rename/RenamePanel.java | 55 +-- .../filebot/ui/panel/subtitle/Language.java | 7 +- .../ui/panel/subtitle/SubtitlePanel.java | 80 ++- .../filebot/web/SubsceneSubtitleClient.java | 27 +- .../net/sourceforge/tuned/PreferencesMap.java | 22 +- .../tuned/ui/LazyDocumentListener.java | 29 +- .../web/SubsceneSubtitleClientTest.java | 15 +- 23 files changed, 771 insertions(+), 354 deletions(-) rename source/net/sourceforge/filebot/format/{EpisodeFormatBindingBean.java => EpisodeBindingBean.java} (91%) create mode 100644 source/net/sourceforge/filebot/resources/action.variable.png create mode 100644 source/net/sourceforge/filebot/ui/panel/rename/EpisodeBindingDialog.java create mode 100644 source/net/sourceforge/filebot/ui/panel/rename/EpisodeBindingDialog.properties diff --git a/source/net/sourceforge/filebot/Main.java b/source/net/sourceforge/filebot/Main.java index 09420816..4653f260 100644 --- a/source/net/sourceforge/filebot/Main.java +++ b/source/net/sourceforge/filebot/Main.java @@ -49,7 +49,7 @@ public class Main { if (argumentBean.clear()) { // clear preferences - Settings.userRoot().clear(); + Settings.forPackage(Main.class).clear(); } try { diff --git a/source/net/sourceforge/filebot/Settings.java b/source/net/sourceforge/filebot/Settings.java index e6aa4611..63bf3257 100644 --- a/source/net/sourceforge/filebot/Settings.java +++ b/source/net/sourceforge/filebot/Settings.java @@ -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 PreferencesEntry entry(String key, Adapter adapter) { - return new PreferencesEntry(prefs, key, adapter); - } - - public PreferencesMap asMap() { return PreferencesMap.map(prefs); } - public PreferencesMap asMap(Adapter adapter) { - return PreferencesMap.map(prefs, adapter); - } - - public PreferencesList asList() { return PreferencesList.map(prefs); } - public PreferencesList asList(Adapter adapter) { - return PreferencesList.map(prefs, adapter); - } - - public void clear() { try { // remove child nodes diff --git a/source/net/sourceforge/filebot/format/AssociativeScriptObject.java b/source/net/sourceforge/filebot/format/AssociativeScriptObject.java index 4152dc99..8f21f238 100644 --- a/source/net/sourceforge/filebot/format/AssociativeScriptObject.java +++ b/source/net/sourceforge/filebot/format/AssociativeScriptObject.java @@ -164,11 +164,7 @@ public class AssociativeScriptObject implements Scriptable { public LenientLookup(Map source) { // populate entry map for (Entry entry : source.entrySet()) { - String key = definingKey(entry.getKey()); - - if (key.length() > 0) { - this.source.put(key, entry); - } + this.source.put(definingKey(entry.getKey()), entry); } } diff --git a/source/net/sourceforge/filebot/format/EpisodeFormatBindingBean.java b/source/net/sourceforge/filebot/format/EpisodeBindingBean.java similarity index 91% rename from source/net/sourceforge/filebot/format/EpisodeFormatBindingBean.java rename to source/net/sourceforge/filebot/format/EpisodeBindingBean.java index 6ef10fe4..bdc8863e 100644 --- a/source/net/sourceforge/filebot/format/EpisodeFormatBindingBean.java +++ b/source/net/sourceforge/filebot/format/EpisodeBindingBean.java @@ -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)); } diff --git a/source/net/sourceforge/filebot/format/ExpressionBindings.java b/source/net/sourceforge/filebot/format/ExpressionBindings.java index ad209774..ca295fcf 100644 --- a/source/net/sourceforge/filebot/format/ExpressionBindings.java +++ b/source/net/sourceforge/filebot/format/ExpressionBindings.java @@ -20,7 +20,9 @@ public class ExpressionBindings extends AbstractMap implements B protected final Map bindings = new TreeMap(String.CASE_INSENSITIVE_ORDER); + protected final Method undefined; + public ExpressionBindings(Object bindingBean) { this.bindingBean = bindingBean; @@ -37,6 +39,9 @@ public class ExpressionBindings extends AbstractMap implements B } } } + + // extract mapping that handles undefined bindings + undefined = bindings.remove(Define.undefined); } @@ -53,7 +58,7 @@ public class ExpressionBindings extends AbstractMap implements B } // invoke fallback method - return bindings.get(Define.undefined).invoke(bindingBean); + return undefined.invoke(bindingBean); } diff --git a/source/net/sourceforge/filebot/mediainfo/MediaInfo.java b/source/net/sourceforge/filebot/mediainfo/MediaInfo.java index 8face4d5..c566a274 100644 --- a/source/net/sourceforge/filebot/mediainfo/MediaInfo.java +++ b/source/net/sourceforge/filebot/mediainfo/MediaInfo.java @@ -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(); } diff --git a/source/net/sourceforge/filebot/resources/action.variable.png b/source/net/sourceforge/filebot/resources/action.variable.png new file mode 100644 index 0000000000000000000000000000000000000000..c3cd695594e4c8c132c9d76aab801eef7f9eacb1 GIT binary patch literal 362 zcmeAS@N?(olHy`uVBq!ia0vp^0zk~k!3HF)wbmE`DVAa<&kznEsNqQI07({jL>4nJ za0`Jjchr$b(b_=ckpF zCl;kL_$DS7<>#iRWF{)OWfrBD=NDxcD_CsJxKsgDy5G~qF+^kH(n*GVO#uQf^2bg) zm~OP4o}E#-Md-)}syV3SY`&els%Yn?(tnnY{fLi`>=cANR5d#m=rqSH6Y zzv1NSlZ`(&Jv+A}P;GMfYVSX{S#!3=zkk5m;U(&4&iCvg&?^j{u6{1-oD!M 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 matches = new ArrayList(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; } diff --git a/source/net/sourceforge/filebot/ui/MainFrame.java b/source/net/sourceforge/filebot/ui/MainFrame.java index 9197bc73..e4087cda 100644 --- a/source/net/sourceforge/filebot/ui/MainFrame.java +++ b/source/net/sourceforge/filebot/ui/MainFrame.java @@ -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,9 +47,9 @@ public class MainFrame extends JFrame { private HeaderPanel headerPanel = new HeaderPanel(); - private final PreferencesEntry persistentSelectedPanel = Settings.userRoot().entry("panel.selected", SimpleAdapter.forClass(Integer.class)); - + private PreferencesEntry persistentSelectedPanel = Settings.forPackage(this).entry("panel.selected").defaultValue("1"); + public MainFrame() { super(Settings.getApplicationName()); @@ -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())); } } }); @@ -137,12 +135,12 @@ public class MainFrame extends JFrame { panel.setVisible(true); } - + private static class PanelSelectionList extends JList { private static final int SELECTDELAY_ON_DRAG_OVER = 300; - + public PanelSelectionList(PanelBuilder[] builders) { super(builders); @@ -155,14 +153,14 @@ public class MainFrame extends JFrame { new DropTarget(this, new DragDropListener()); } - + private class DragDropListener extends DropTargetAdapter { private boolean selectEnabled = false; private Timer dragEnterTimer; - + @Override public void dragOver(DropTargetDragEvent dtde) { if (selectEnabled) { diff --git a/source/net/sourceforge/filebot/ui/panel/episodelist/EpisodeListPanel.java b/source/net/sourceforge/filebot/ui/panel/episodelist/EpisodeListPanel.java index 197def17..1ba618d0 100644 --- a/source/net/sourceforge/filebot/ui/panel/episodelist/EpisodeListPanel.java +++ b/source/net/sourceforge/filebot/ui/panel/episodelist/EpisodeListPanel.java @@ -88,7 +88,7 @@ public class EpisodeListPanel extends AbstractSearchPanel future = (Future) 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 getSampleExpressions() { + ResourceBundle bundle = ResourceBundle.getBundle(getClass().getName()); + TreeMap expressions = new TreeMap(); + + // 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 extensions = new ArrayList(); + 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 model = new ArrayList(); + + private final ExecutorService executor = Executors.newFixedThreadPool(2, new DefaultThreadFactory("Evaluator", Thread.MIN_PRIORITY)); + + + public void setModel(Collection 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 { + + 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; + } + + } + +} diff --git a/source/net/sourceforge/filebot/ui/panel/rename/EpisodeBindingDialog.properties b/source/net/sourceforge/filebot/ui/panel/rename/EpisodeBindingDialog.properties new file mode 100644 index 00000000..5f95e1f9 --- /dev/null +++ b/source/net/sourceforge/filebot/ui/panel/rename/EpisodeBindingDialog.properties @@ -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 diff --git a/source/net/sourceforge/filebot/ui/panel/rename/EpisodeExpressionFormatter.java b/source/net/sourceforge/filebot/ui/panel/rename/EpisodeExpressionFormatter.java index a7354932..30c220b5 100644 --- a/source/net/sourceforge/filebot/ui/panel/rename/EpisodeExpressionFormatter.java +++ b/source/net/sourceforge/filebot/ui/panel/rename/EpisodeExpressionFormatter.java @@ -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) { diff --git a/source/net/sourceforge/filebot/ui/panel/rename/EpisodeFormatDialog.java b/source/net/sourceforge/filebot/ui/panel/rename/EpisodeFormatDialog.java index b4186a3b..fd86ff66 100644 --- a/source/net/sourceforge/filebot/ui/panel/rename/EpisodeFormatDialog.java +++ b/source/net/sourceforge/filebot/ui/panel/rename/EpisodeFormatDialog.java @@ -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 currentPreviewFuture; private JLabel preview = new JLabel(); private JLabel status = new JLabel(); - private EpisodeFormatBindingBean previewSample = new EpisodeFormatBindingBean(getPreviewSampleEpisode(), getPreviewSampleMediaFile()); - - private ExecutorService previewExecutor = createPreviewExecutor(); - - private RunnableFuture currentPreviewFuture = null; - private ProgressIndicator progressIndicator = new ProgressIndicator(); private JTextField editor = new JTextField(); - private PreferencesList persistentFormatHistory = Settings.userRoot().node("rename/format.recent").asList(); - - private Color defaultColor = preview.getForeground(); - private Color errorColor = Color.red; + private PreferencesEntry persistentSampleEpisode = Settings.forPackage(this).entry("format.sample.episode"); + private PreferencesEntry persistentSampleFile = Settings.forPackage(this).entry("format.sample.file"); + private PreferencesList 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 examples = new TreeMap(); - // collect example keys - List examples = new ArrayList(); - + // 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(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); } } diff --git a/source/net/sourceforge/filebot/ui/panel/rename/EpisodeFormatDialog.properties b/source/net/sourceforge/filebot/ui/panel/rename/EpisodeFormatDialog.properties index d3f9a582..8ad6f40b 100644 --- a/source/net/sourceforge/filebot/ui/panel/rename/EpisodeFormatDialog.properties +++ b/source/net/sourceforge/filebot/ui/panel/rename/EpisodeFormatDialog.properties @@ -1,4 +1,4 @@ -syntax: { } ... expression, n ... name, s ... season, e ... episode, t ... title +syntax: { } \u2026 expression, n \u2026 name, s \u2026 season, e \u2026 episode, t \u2026 title # basic 1.01 example[0]: {n} - {s}.{e} - {t} diff --git a/source/net/sourceforge/filebot/ui/panel/rename/MediaInfoPane.java b/source/net/sourceforge/filebot/ui/panel/rename/MediaInfoPane.java index 458ebaac..671092aa 100644 --- a/source/net/sourceforge/filebot/ui/panel/rename/MediaInfoPane.java +++ b/source/net/sourceforge/filebot/ui/panel/rename/MediaInfoPane.java @@ -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); } diff --git a/source/net/sourceforge/filebot/ui/panel/rename/RenamePanel.java b/source/net/sourceforge/filebot/ui/panel/rename/RenamePanel.java index a9a1b236..26c740e5 100644 --- a/source/net/sourceforge/filebot/ui/panel/rename/RenamePanel.java +++ b/source/net/sourceforge/filebot/ui/panel/rename/RenamePanel.java @@ -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 persistentPreserveExtension = Settings.userRoot().entry("rename.extension.preserve", SimpleAdapter.forClass(Boolean.class)); + private final PreferencesEntry persistentPreserveExtension = Settings.forPackage(this).entry("rename.extension.preserve").defaultValue("true"); + private final PreferencesEntry 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 persistentExpressionFormatter = Settings.userRoot().entry("rename.format", new AbstractAdapter() { - - @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()); - } - }); - } diff --git a/source/net/sourceforge/filebot/ui/panel/subtitle/Language.java b/source/net/sourceforge/filebot/ui/panel/subtitle/Language.java index a426917c..075e42bf 100644 --- a/source/net/sourceforge/filebot/ui/panel/subtitle/Language.java +++ b/source/net/sourceforge/filebot/ui/panel/subtitle/Language.java @@ -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; } diff --git a/source/net/sourceforge/filebot/ui/panel/subtitle/SubtitlePanel.java b/source/net/sourceforge/filebot/ui/panel/subtitle/SubtitlePanel.java index 03225a03..7df2e098 100644 --- a/source/net/sourceforge/filebot/ui/panel/subtitle/SubtitlePanel.java +++ b/source/net/sourceforge/filebot/ui/panel/subtitle/SubtitlePanel.java @@ -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 persistentSelectedLanguage = Settings.forPackage(this).entry("language.selected"); + private final PreferencesList persistentFavoriteLanguages = Settings.forPackage(this).node("language.favorites").asList(); + public SubtitlePanel() { historyPanel.setColumnHeader(0, "Show / Movie"); @@ -45,9 +48,13 @@ public class SubtitlePanel extends AbstractSearchPanel() { + + @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 persistentSelectedLanguage = getSettings().entry("language.selected", new AbstractAdapter() { - - @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> persistentFavorites = getSettings().entry("language.favorites", new AbstractAdapter>() { - - @Override - public List get(Preferences prefs, String key) { - List languages = new ArrayList(); - - for (String languageCode : prefs.get(key, "").split("\\W+")) { - languages.add(Language.getLanguage(languageCode)); - } - - return languages; - } - - - @Override - public void put(Preferences prefs, String key, List 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()); - } - }); - } diff --git a/source/net/sourceforge/filebot/web/SubsceneSubtitleClient.java b/source/net/sourceforge/filebot/web/SubsceneSubtitleClient.java index 23fa0d7d..9614718a 100644 --- a/source/net/sourceforge/filebot/web/SubsceneSubtitleClient.java +++ b/source/net/sourceforge/filebot/web/SubsceneSubtitleClient.java @@ -22,23 +22,22 @@ 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 languageFilterMap = initLanguageFilterMap(); - + private final Map languageFilterMap = initLanguageFilterMap(); + @Override public String getName() { return "Subscene"; @@ -98,7 +97,7 @@ public class SubsceneSubtitleClient implements SubtitleProvider { public List 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 initLanguageFilterMap() { - return Settings.userRoot().node("subtitles/subscene/languageFilterMap").asMap(SimpleAdapter.forClass(Integer.class)); + protected Map initLanguageFilterMap() { + return Settings.forPackage(this).node("subtitles/subscene/languageFilterMap").asMap(); } - protected Map getLanguageFilterMap(Document subtitleListDocument) { - Map filters = new HashMap(50); + protected Map getLanguageFilterMap(Document subtitleListDocument) { + Map filters = new HashMap(50); List 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); } } diff --git a/source/net/sourceforge/tuned/PreferencesMap.java b/source/net/sourceforge/tuned/PreferencesMap.java index 1c03500a..326150fa 100644 --- a/source/net/sourceforge/tuned/PreferencesMap.java +++ b/source/net/sourceforge/tuned/PreferencesMap.java @@ -26,7 +26,7 @@ public class PreferencesMap implements Map { private final Preferences prefs; private final Adapter adapter; - + public PreferencesMap(Preferences prefs, Adapter adapter) { this.prefs = prefs; this.adapter = adapter; @@ -154,7 +154,7 @@ public class PreferencesMap implements Map { return new PreferencesMap(prefs, adapter); } - + public static interface Adapter { public String[] keys(Preferences prefs) throws BackingStoreException; @@ -214,7 +214,7 @@ public class PreferencesMap implements Map { private final Constructor constructor; - + public SimpleAdapter(Class type) { try { constructor = type.getConstructor(String.class); @@ -303,7 +303,7 @@ public class PreferencesMap implements Map { private final Adapter adapter; - + public PreferencesEntry(Preferences prefs, String key, Adapter adapter) { this.key = key; this.prefs = prefs; @@ -326,11 +326,23 @@ public class PreferencesMap implements Map { @Override public T setValue(T value) { adapter.put(prefs, key, value); - return null; } + public PreferencesEntry 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); } diff --git a/source/net/sourceforge/tuned/ui/LazyDocumentListener.java b/source/net/sourceforge/tuned/ui/LazyDocumentListener.java index 89c4ab9e..74f9dbd2 100644 --- a/source/net/sourceforge/tuned/ui/LazyDocumentListener.java +++ b/source/net/sourceforge/tuned/ui/LazyDocumentListener.java @@ -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); } diff --git a/test/net/sourceforge/filebot/web/SubsceneSubtitleClientTest.java b/test/net/sourceforge/filebot/web/SubsceneSubtitleClientTest.java index 307c4fac..8434d04e 100644 --- a/test/net/sourceforge/filebot/web/SubsceneSubtitleClientTest.java +++ b/test/net/sourceforge/filebot/web/SubsceneSubtitleClientTest.java @@ -24,16 +24,17 @@ public class SubsceneSubtitleClientTest { */ private static HyperLink lostSearchResult; - + @BeforeClass public static void setUpBeforeClass() throws Exception { twinpeaksSearchResult = new HyperLink("Twin Peaks - First Season (1990)", new URL("http://subscene.com/twin-peaks--first-season/subtitles-32482.aspx")); lostSearchResult = new HyperLink("Lost - Fourth Season (2008)", new URL("http://subscene.com/Lost-Fourth-Season/subtitles-70963.aspx")); } + private SubsceneSubtitleClient subscene = new SubsceneSubtitleClient(); - + @Test public void search() throws Exception { List results = subscene.search("twin peaks"); @@ -83,12 +84,12 @@ public class SubsceneSubtitleClientTest { @Test public void getLanguageFilterMap() throws Exception { - Map filters = subscene.getLanguageFilterMap(subscene.getSubtitleListDocument(new URL("http://subscene.com/none/subtitles-0.aspx"), null)); + Map 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")); }