* added ScriptFormat and format configuration dialog
* added names view (did miss it last commit)
This commit is contained in:
parent
deb15a6e15
commit
2de1b8a1b0
Binary file not shown.
After Width: | Height: | Size: 723 B |
|
@ -0,0 +1,432 @@
|
||||||
|
|
||||||
|
package net.sourceforge.filebot.ui;
|
||||||
|
|
||||||
|
|
||||||
|
import static java.awt.Font.BOLD;
|
||||||
|
import static java.awt.Font.MONOSPACED;
|
||||||
|
import static java.awt.Font.PLAIN;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.Component;
|
||||||
|
import java.awt.Font;
|
||||||
|
import java.awt.Window;
|
||||||
|
import java.awt.event.ActionEvent;
|
||||||
|
import java.awt.event.WindowAdapter;
|
||||||
|
import java.awt.event.WindowEvent;
|
||||||
|
import java.beans.PropertyChangeEvent;
|
||||||
|
import java.beans.PropertyChangeListener;
|
||||||
|
import java.text.Format;
|
||||||
|
import java.text.ParseException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.ResourceBundle;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
import javax.script.ScriptException;
|
||||||
|
import javax.swing.AbstractAction;
|
||||||
|
import javax.swing.Action;
|
||||||
|
import javax.swing.BorderFactory;
|
||||||
|
import javax.swing.InputVerifier;
|
||||||
|
import javax.swing.JButton;
|
||||||
|
import javax.swing.JComponent;
|
||||||
|
import javax.swing.JDialog;
|
||||||
|
import javax.swing.JFormattedTextField;
|
||||||
|
import javax.swing.JLabel;
|
||||||
|
import javax.swing.JPanel;
|
||||||
|
import javax.swing.JTextField;
|
||||||
|
import javax.swing.KeyStroke;
|
||||||
|
import javax.swing.SwingUtilities;
|
||||||
|
import javax.swing.JFormattedTextField.AbstractFormatter;
|
||||||
|
import javax.swing.border.LineBorder;
|
||||||
|
import javax.swing.event.DocumentEvent;
|
||||||
|
import javax.swing.event.DocumentListener;
|
||||||
|
import javax.swing.text.DefaultFormatterFactory;
|
||||||
|
|
||||||
|
import net.miginfocom.swing.MigLayout;
|
||||||
|
import net.sourceforge.filebot.ResourceManager;
|
||||||
|
import net.sourceforge.filebot.Settings;
|
||||||
|
import net.sourceforge.filebot.web.Episode;
|
||||||
|
import net.sourceforge.filebot.web.Episode.EpisodeFormat;
|
||||||
|
import net.sourceforge.tuned.ExceptionUtilities;
|
||||||
|
import net.sourceforge.tuned.ui.GradientStyle;
|
||||||
|
import net.sourceforge.tuned.ui.LinkButton;
|
||||||
|
import net.sourceforge.tuned.ui.TunedUtilities;
|
||||||
|
import net.sourceforge.tuned.ui.notification.SeparatorBorder;
|
||||||
|
import net.sourceforge.tuned.ui.notification.SeparatorBorder.Position;
|
||||||
|
|
||||||
|
|
||||||
|
public class EpisodeFormatDialog extends JDialog {
|
||||||
|
|
||||||
|
private Format selectedFormat = null;
|
||||||
|
|
||||||
|
protected JFormattedTextField preview = new JFormattedTextField(getPreviewSample());
|
||||||
|
|
||||||
|
protected JLabel errorMessage = new JLabel(ResourceManager.getIcon("dialog.cancel"));
|
||||||
|
protected JTextField editor = new JTextField();
|
||||||
|
|
||||||
|
protected Color defaultColor = preview.getForeground();
|
||||||
|
protected Color errorColor = Color.red;
|
||||||
|
|
||||||
|
|
||||||
|
public EpisodeFormatDialog(Window owner) {
|
||||||
|
super(owner, "Episode Format", ModalityType.DOCUMENT_MODAL);
|
||||||
|
|
||||||
|
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
|
||||||
|
|
||||||
|
editor.setFont(new Font(MONOSPACED, PLAIN, 14));
|
||||||
|
editor.setText(Settings.userRoot().get("dialog.format"));
|
||||||
|
|
||||||
|
preview.setBorder(BorderFactory.createEmptyBorder());
|
||||||
|
|
||||||
|
// update preview to current format
|
||||||
|
checkEpisodeFormat();
|
||||||
|
|
||||||
|
// bold title label in header
|
||||||
|
JLabel title = new JLabel(this.getTitle());
|
||||||
|
title.setFont(title.getFont().deriveFont(BOLD));
|
||||||
|
|
||||||
|
// status.setVisible(false);
|
||||||
|
JPanel header = new JPanel(new MigLayout("insets dialog, nogrid, fillx"));
|
||||||
|
|
||||||
|
header.setBackground(Color.white);
|
||||||
|
header.setBorder(new SeparatorBorder(1, new Color(0xB4B4B4), new Color(0xACACAC), GradientStyle.LEFT_TO_RIGHT, Position.BOTTOM));
|
||||||
|
|
||||||
|
header.add(title, "wrap unrel:push");
|
||||||
|
header.add(errorMessage, "gap indent, hidemode 3");
|
||||||
|
header.add(preview, "gap indent, hidemode 3, growx");
|
||||||
|
|
||||||
|
JPanel content = new JPanel(new MigLayout("insets dialog, nogrid, fill"));
|
||||||
|
|
||||||
|
content.add(editor, "wmin 120px, h 40px!, growx, wrap 8px");
|
||||||
|
|
||||||
|
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(), "gapx indent indent, wrap 25px:push");
|
||||||
|
|
||||||
|
content.add(new JButton(useDefaultFormatAction), "tag left");
|
||||||
|
content.add(new JButton(useCustomFormatAction), "tag apply");
|
||||||
|
content.add(new JButton(cancelAction), "tag cancel");
|
||||||
|
|
||||||
|
JComponent pane = (JComponent) getContentPane();
|
||||||
|
pane.setLayout(new MigLayout("insets 0, fill"));
|
||||||
|
|
||||||
|
pane.add(header, "h 60px, growx, dock north");
|
||||||
|
pane.add(content, "grow");
|
||||||
|
|
||||||
|
pack();
|
||||||
|
setLocation(TunedUtilities.getPreferredLocation(this));
|
||||||
|
|
||||||
|
TunedUtilities.putActionForKeystroke(pane, KeyStroke.getKeyStroke("released ESCAPE"), cancelAction);
|
||||||
|
|
||||||
|
// update format on change
|
||||||
|
editor.getDocument().addDocumentListener(new DocumentAdapter() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void update(DocumentEvent evt) {
|
||||||
|
checkEpisodeFormat();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// keep focus on preview, if current text doesn't fit episode format
|
||||||
|
preview.setInputVerifier(new InputVerifier() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean verify(JComponent input) {
|
||||||
|
return checkPreviewSample();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// check edit format on change
|
||||||
|
preview.getDocument().addDocumentListener(new DocumentAdapter() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void update(DocumentEvent evt) {
|
||||||
|
checkPreviewSample();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// focus editor by default
|
||||||
|
addWindowFocusListener(new WindowAdapter() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void windowGainedFocus(WindowEvent e) {
|
||||||
|
editor.requestFocusInWindow();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected JPanel createSyntaxPanel() {
|
||||||
|
JPanel panel = new JPanel(new MigLayout("fill, nogrid"));
|
||||||
|
|
||||||
|
panel.setBorder(new LineBorder(new Color(0xACA899)));
|
||||||
|
panel.setBackground(new Color(0xFFFFE1));
|
||||||
|
panel.setOpaque(true);
|
||||||
|
|
||||||
|
panel.add(new JLabel(ResourceBundle.getBundle(getClass().getName()).getString("syntax")));
|
||||||
|
|
||||||
|
return panel;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected JPanel createExamplesPanel() {
|
||||||
|
JPanel panel = new JPanel(new MigLayout("fill, wrap 3"));
|
||||||
|
|
||||||
|
panel.setBorder(new LineBorder(new Color(0xACA899)));
|
||||||
|
panel.setBackground(new Color(0xFFFFE1));
|
||||||
|
panel.setOpaque(true);
|
||||||
|
|
||||||
|
ResourceBundle bundle = ResourceBundle.getBundle(getClass().getName());
|
||||||
|
|
||||||
|
// sort keys
|
||||||
|
String[] keys = bundle.keySet().toArray(new String[0]);
|
||||||
|
Arrays.sort(keys);
|
||||||
|
|
||||||
|
for (String key : keys) {
|
||||||
|
if (key.startsWith("example")) {
|
||||||
|
String format = bundle.getString(key);
|
||||||
|
|
||||||
|
LinkButton formatLink = new LinkButton(new ExampleFormatAction(format));
|
||||||
|
formatLink.setFont(new Font(MONOSPACED, PLAIN, 11));
|
||||||
|
|
||||||
|
panel.add(formatLink);
|
||||||
|
panel.add(new JLabel("..."));
|
||||||
|
panel.add(new ExampleFormatLabel(format));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return panel;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected Episode getPreviewSample() {
|
||||||
|
String sample = Settings.userRoot().get("dialog.sample");
|
||||||
|
|
||||||
|
if (sample != null) {
|
||||||
|
try {
|
||||||
|
return EpisodeFormat.getInstance().parseObject(sample);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Logger.getLogger("global").log(Level.WARNING, e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Episode("Dark Angel", "3", "1", "Labyrinth");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected boolean checkPreviewSample() {
|
||||||
|
// check if field is being edited
|
||||||
|
if (preview.hasFocus()) {
|
||||||
|
try {
|
||||||
|
// try to parse text
|
||||||
|
preview.getFormatter().stringToValue(preview.getText());
|
||||||
|
} catch (Exception e) {
|
||||||
|
preview.setForeground(errorColor);
|
||||||
|
// failed to parse text
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
preview.setForeground(defaultColor);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected DefaultFormatterFactory createFormatterFactory(Format display) {
|
||||||
|
DefaultFormatterFactory factory = new DefaultFormatterFactory();
|
||||||
|
|
||||||
|
factory.setEditFormatter(new SimpleFormatter(EpisodeFormat.getInstance()));
|
||||||
|
|
||||||
|
if (display != null) {
|
||||||
|
factory.setDisplayFormatter(new SimpleFormatter(display));
|
||||||
|
}
|
||||||
|
|
||||||
|
return factory;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected boolean checkEpisodeFormat() {
|
||||||
|
Exception exception = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
Format format = new EpisodeScriptFormat(editor.getText().trim());
|
||||||
|
|
||||||
|
// check if format produces empty strings
|
||||||
|
if (format.format(preview.getValue()).trim().isEmpty()) {
|
||||||
|
throw new IllegalArgumentException("Format must not be empty.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// update preview
|
||||||
|
preview.setFormatterFactory(createFormatterFactory(format));
|
||||||
|
} catch (Exception e) {
|
||||||
|
exception = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
errorMessage.setText(exception != null ? ExceptionUtilities.getRootCauseMessage(exception) : null);
|
||||||
|
errorMessage.setVisible(exception != null);
|
||||||
|
|
||||||
|
preview.setVisible(exception == null);
|
||||||
|
editor.setForeground(exception == null ? defaultColor : errorColor);
|
||||||
|
|
||||||
|
return exception == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Format getSelectedFormat() {
|
||||||
|
return selectedFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void finish(Format format) {
|
||||||
|
this.selectedFormat = format;
|
||||||
|
|
||||||
|
setVisible(false);
|
||||||
|
dispose();
|
||||||
|
|
||||||
|
if (checkEpisodeFormat()) {
|
||||||
|
Settings.userRoot().put("dialog.format", editor.getText());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (checkPreviewSample()) {
|
||||||
|
Settings.userRoot().put("dialog.sample", preview.getValue().toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final Action cancelAction = new AbstractAction("Cancel", ResourceManager.getIcon("dialog.cancel")) {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
finish(null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
protected final Action useDefaultFormatAction = new AbstractAction("Default", ResourceManager.getIcon("dialog.default")) {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
finish(EpisodeFormat.getInstance());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
protected final Action useCustomFormatAction = new AbstractAction("Use Format", ResourceManager.getIcon("dialog.continue")) {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void actionPerformed(ActionEvent evt) {
|
||||||
|
try {
|
||||||
|
finish(new EpisodeScriptFormat(editor.getText()));
|
||||||
|
} catch (ScriptException e) {
|
||||||
|
Logger.getLogger("ui").log(Level.WARNING, ExceptionUtilities.getRootCauseMessage(e), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
public static Format showDialog(Component parent) {
|
||||||
|
EpisodeFormatDialog dialog = new EpisodeFormatDialog(parent != null ? SwingUtilities.getWindowAncestor(parent) : null);
|
||||||
|
|
||||||
|
dialog.setVisible(true);
|
||||||
|
|
||||||
|
return dialog.getSelectedFormat();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected class ExampleFormatAction extends AbstractAction {
|
||||||
|
|
||||||
|
public ExampleFormatAction(String format) {
|
||||||
|
super(format);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
editor.setText(getValue(Action.NAME).toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected class ExampleFormatLabel extends JLabel {
|
||||||
|
|
||||||
|
private final String format;
|
||||||
|
|
||||||
|
|
||||||
|
public ExampleFormatLabel(String format) {
|
||||||
|
this.format = format;
|
||||||
|
|
||||||
|
// initialize text
|
||||||
|
updateText(preview.getValue());
|
||||||
|
|
||||||
|
// bind text to preview
|
||||||
|
preview.addPropertyChangeListener("value", new PropertyChangeListener() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void propertyChange(PropertyChangeEvent evt) {
|
||||||
|
updateText(evt.getNewValue());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void updateText(Object episode) {
|
||||||
|
try {
|
||||||
|
setText(new EpisodeScriptFormat(format).format(episode));
|
||||||
|
setForeground(defaultColor);
|
||||||
|
} catch (Exception e) {
|
||||||
|
setText(ExceptionUtilities.getRootCauseMessage(e));
|
||||||
|
setForeground(errorColor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected static class SimpleFormatter extends AbstractFormatter {
|
||||||
|
|
||||||
|
private final Format format;
|
||||||
|
|
||||||
|
|
||||||
|
public SimpleFormatter(Format format) {
|
||||||
|
this.format = format;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String valueToString(Object value) throws ParseException {
|
||||||
|
return format.format(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object stringToValue(String text) throws ParseException {
|
||||||
|
return format.parseObject(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected static class DocumentAdapter implements DocumentListener {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void changedUpdate(DocumentEvent e) {
|
||||||
|
update(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void insertUpdate(DocumentEvent e) {
|
||||||
|
update(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeUpdate(DocumentEvent e) {
|
||||||
|
update(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void update(DocumentEvent e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
syntax: <html><b>{</b> <b>}</b> ... expression, <b>n</b> ... name, <b>s</b> ... season, <b>e</b> ... episode, <b>t</b> ... title</html>
|
||||||
|
|
||||||
|
# basic 1.01
|
||||||
|
example[0]: {n} - {s}.{e} - {t}
|
||||||
|
|
||||||
|
# 1x01
|
||||||
|
example[1]: {n} - {if (s) s+'x'}{e.pad(2)}
|
||||||
|
|
||||||
|
# S01E01
|
||||||
|
example[2]: {n} - {if (s) 'S'+s.pad(2)}E{e.pad(2)}
|
||||||
|
|
||||||
|
# uglyfy name
|
||||||
|
example[3]: {n.replace(/\\s+/g,'.').toLowerCase()}
|
|
@ -0,0 +1,32 @@
|
||||||
|
|
||||||
|
package net.sourceforge.filebot.ui;
|
||||||
|
|
||||||
|
|
||||||
|
import javax.script.Bindings;
|
||||||
|
import javax.script.ScriptException;
|
||||||
|
import javax.script.SimpleBindings;
|
||||||
|
|
||||||
|
import net.sourceforge.filebot.web.Episode;
|
||||||
|
|
||||||
|
|
||||||
|
public class EpisodeScriptFormat extends ScriptFormat {
|
||||||
|
|
||||||
|
public EpisodeScriptFormat(String format) throws ScriptException {
|
||||||
|
super(format);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Bindings getBindings(Object object) {
|
||||||
|
Episode episode = (Episode) object;
|
||||||
|
|
||||||
|
Bindings bindings = new SimpleBindings();
|
||||||
|
|
||||||
|
bindings.put("n", episode.getSeriesName());
|
||||||
|
bindings.put("s", episode.getSeasonNumber());
|
||||||
|
bindings.put("e", episode.getEpisodeNumber());
|
||||||
|
bindings.put("t", episode.getTitle());
|
||||||
|
|
||||||
|
return bindings;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
|
||||||
|
String.prototype.pad = function(length, padding) {
|
||||||
|
if (padding == undefined) {
|
||||||
|
padding = '0';
|
||||||
|
}
|
||||||
|
|
||||||
|
var s = this;
|
||||||
|
|
||||||
|
if (parseInt(this) >= 0 && padding.length >= 1) {
|
||||||
|
while (s.length < length) {
|
||||||
|
s = padding.concat(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return s;
|
||||||
|
};
|
|
@ -0,0 +1,113 @@
|
||||||
|
|
||||||
|
package net.sourceforge.filebot.ui;
|
||||||
|
|
||||||
|
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.text.FieldPosition;
|
||||||
|
import java.text.Format;
|
||||||
|
import java.text.ParsePosition;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import javax.script.Bindings;
|
||||||
|
import javax.script.Compilable;
|
||||||
|
import javax.script.CompiledScript;
|
||||||
|
import javax.script.ScriptEngine;
|
||||||
|
import javax.script.ScriptEngineManager;
|
||||||
|
import javax.script.ScriptException;
|
||||||
|
|
||||||
|
|
||||||
|
public abstract class ScriptFormat extends Format {
|
||||||
|
|
||||||
|
private final String format;
|
||||||
|
|
||||||
|
private final Object[] expressions;
|
||||||
|
|
||||||
|
|
||||||
|
public ScriptFormat(String format) throws ScriptException {
|
||||||
|
this.format = format;
|
||||||
|
this.expressions = compile(format, (Compilable) initScriptEngine());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected ScriptEngine initScriptEngine() throws ScriptException {
|
||||||
|
ScriptEngine engine = new ScriptEngineManager().getEngineByName("JavaScript");
|
||||||
|
|
||||||
|
engine.eval(new InputStreamReader(getClass().getResourceAsStream("ScriptFormat.global.js")));
|
||||||
|
|
||||||
|
return engine;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public String getFormat() {
|
||||||
|
return format;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected Object[] compile(String format, Compilable engine) throws ScriptException {
|
||||||
|
List<Object> expression = new ArrayList<Object>();
|
||||||
|
|
||||||
|
Matcher matcher = Pattern.compile("\\{([^\\{]*?)\\}").matcher(format);
|
||||||
|
|
||||||
|
int position = 0;
|
||||||
|
|
||||||
|
while (matcher.find()) {
|
||||||
|
if (position < matcher.start()) {
|
||||||
|
// literal before
|
||||||
|
expression.add(format.substring(position, matcher.start()));
|
||||||
|
}
|
||||||
|
|
||||||
|
String script = matcher.group(1);
|
||||||
|
|
||||||
|
if (script.length() > 0) {
|
||||||
|
// compiled script, or literal
|
||||||
|
expression.add(engine.compile(script));
|
||||||
|
}
|
||||||
|
|
||||||
|
position = matcher.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (position < format.length()) {
|
||||||
|
// tail
|
||||||
|
expression.add(format.substring(position, format.length()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return expression.toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected abstract Bindings getBindings(Object object);
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StringBuffer format(Object object, StringBuffer sb, FieldPosition pos) {
|
||||||
|
Bindings bindings = getBindings(object);
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (Object snipped : expressions) {
|
||||||
|
if (snipped instanceof String) {
|
||||||
|
sb.append(snipped);
|
||||||
|
} else {
|
||||||
|
Object value = ((CompiledScript) snipped).eval(bindings);
|
||||||
|
|
||||||
|
if (value != null) {
|
||||||
|
sb.append(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (ScriptException e) {
|
||||||
|
throw new IllegalArgumentException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object parseObject(String source, ParsePosition pos) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -21,8 +21,6 @@ import java.util.Scanner;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import ca.odell.glazedlists.EventList;
|
|
||||||
|
|
||||||
import net.sourceforge.filebot.torrent.Torrent;
|
import net.sourceforge.filebot.torrent.Torrent;
|
||||||
import net.sourceforge.filebot.ui.transfer.FileTransferablePolicy;
|
import net.sourceforge.filebot.ui.transfer.FileTransferablePolicy;
|
||||||
import net.sourceforge.tuned.FileUtilities;
|
import net.sourceforge.tuned.FileUtilities;
|
||||||
|
@ -30,10 +28,10 @@ import net.sourceforge.tuned.FileUtilities;
|
||||||
|
|
||||||
class NamesListTransferablePolicy extends FileTransferablePolicy {
|
class NamesListTransferablePolicy extends FileTransferablePolicy {
|
||||||
|
|
||||||
private final EventList<Object> model;
|
private final List<Object> model;
|
||||||
|
|
||||||
|
|
||||||
public NamesListTransferablePolicy(EventList<Object> model) {
|
public NamesListTransferablePolicy(List<Object> model) {
|
||||||
this.model = model;
|
this.model = model;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,134 @@
|
||||||
|
|
||||||
|
package net.sourceforge.filebot.ui.panel.rename;
|
||||||
|
|
||||||
|
|
||||||
|
import static net.sourceforge.filebot.FileBotUtilities.isInvalidFileName;
|
||||||
|
|
||||||
|
import java.awt.Component;
|
||||||
|
import java.util.AbstractList;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import ca.odell.glazedlists.EventList;
|
||||||
|
import ca.odell.glazedlists.TransformedList;
|
||||||
|
import ca.odell.glazedlists.event.ListEvent;
|
||||||
|
|
||||||
|
|
||||||
|
public class NamesViewEventList extends TransformedList<Object, String> {
|
||||||
|
|
||||||
|
private final List<String> names = new ArrayList<String>();
|
||||||
|
|
||||||
|
private final Component parent;
|
||||||
|
|
||||||
|
|
||||||
|
public NamesViewEventList(Component parent, EventList<Object> source) {
|
||||||
|
super(source);
|
||||||
|
|
||||||
|
this.parent = parent;
|
||||||
|
|
||||||
|
// connect to source list
|
||||||
|
source.addListEventListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean isWritable() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String get(int index) {
|
||||||
|
return names.get(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected String format(Object object) {
|
||||||
|
return object.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void listChanged(ListEvent<Object> listChanges) {
|
||||||
|
EventList<Object> source = listChanges.getSourceList();
|
||||||
|
IndexView<String> newValues = new IndexView<String>(names);
|
||||||
|
|
||||||
|
while (listChanges.next()) {
|
||||||
|
int index = listChanges.getIndex();
|
||||||
|
int type = listChanges.getType();
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case ListEvent.INSERT:
|
||||||
|
names.add(index, format(source.get(index)));
|
||||||
|
newValues.getIndexFilter().add(index);
|
||||||
|
break;
|
||||||
|
case ListEvent.UPDATE:
|
||||||
|
names.set(index, format(source.get(index)));
|
||||||
|
newValues.getIndexFilter().add(index);
|
||||||
|
break;
|
||||||
|
case ListEvent.DELETE:
|
||||||
|
names.remove(index);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
submit(newValues);
|
||||||
|
|
||||||
|
listChanges.reset();
|
||||||
|
updates.forwardEvent(listChanges);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected void submit(List<String> values) {
|
||||||
|
IndexView<String> invalidValues = new IndexView<String>(values);
|
||||||
|
|
||||||
|
for (int i = 0; i < values.size(); i++) {
|
||||||
|
if (isInvalidFileName(values.get(i))) {
|
||||||
|
invalidValues.getIndexFilter().add(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (invalidValues.size() > 0) {
|
||||||
|
// validate names
|
||||||
|
ValidateNamesDialog.showDialog(parent, invalidValues);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected static class IndexView<E> extends AbstractList<E> {
|
||||||
|
|
||||||
|
private final List<E> source;
|
||||||
|
|
||||||
|
private final List<Integer> indexFilter = new ArrayList<Integer>();
|
||||||
|
|
||||||
|
|
||||||
|
public IndexView(List<E> source) {
|
||||||
|
this.source = source;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public List<Integer> getIndexFilter() {
|
||||||
|
return indexFilter;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public E get(int index) {
|
||||||
|
return source.get(indexFilter.get(index));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public E set(int index, E element) {
|
||||||
|
return source.set(indexFilter.get(index), element);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int size() {
|
||||||
|
return indexFilter.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -3,7 +3,12 @@ package net.sourceforge.filebot.web;
|
||||||
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.text.NumberFormat;
|
import java.text.FieldPosition;
|
||||||
|
import java.text.Format;
|
||||||
|
import java.text.ParseException;
|
||||||
|
import java.text.ParsePosition;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
|
||||||
public class Episode implements Serializable {
|
public class Episode implements Serializable {
|
||||||
|
@ -47,69 +52,78 @@ public class Episode implements Serializable {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void setSeriesName(String seriesName) {
|
@Override
|
||||||
this.seriesName = seriesName;
|
public String toString() {
|
||||||
|
return EpisodeFormat.getInstance().format(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void setSeasonNumber(String seasonNumber) {
|
public static class EpisodeFormat extends Format {
|
||||||
this.seasonNumber = seasonNumber;
|
|
||||||
}
|
private static final EpisodeFormat instance = new EpisodeFormat();
|
||||||
|
|
||||||
|
|
||||||
public void setEpisodeNumber(String episodeNumber) {
|
public static EpisodeFormat getInstance() {
|
||||||
this.episodeNumber = episodeNumber;
|
return instance;
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void setTitle(String episodeName) {
|
|
||||||
this.title = episodeName;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public StringBuffer format(Object obj, StringBuffer sb, FieldPosition pos) {
|
||||||
StringBuilder sb = new StringBuilder(40);
|
Episode episode = (Episode) obj;
|
||||||
|
|
||||||
sb.append(seriesName).append(" - ");
|
sb.append(episode.getSeriesName()).append(" - ");
|
||||||
|
|
||||||
if (seasonNumber != null) {
|
if (episode.getSeasonNumber() != null) {
|
||||||
sb.append(seasonNumber).append("x");
|
sb.append(episode.getSeasonNumber()).append('x');
|
||||||
}
|
}
|
||||||
|
|
||||||
sb.append(episodeNumber).append(" - ").append(title);
|
sb.append(formatEpisodeNumber(episode.getEpisodeNumber()));
|
||||||
|
|
||||||
return sb.toString();
|
return sb.append(" - ").append(episode.getTitle());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static <T extends Iterable<Episode>> T formatEpisodeNumbers(T episodes, int minDigits) {
|
protected String formatEpisodeNumber(String number) {
|
||||||
// find max. episode number length
|
if (number.length() < 2) {
|
||||||
for (Episode episode : episodes) {
|
|
||||||
try {
|
try {
|
||||||
String episodeNumber = episode.getEpisodeNumber();
|
return String.format("%02d", Integer.parseInt(number));
|
||||||
|
|
||||||
if (episodeNumber.length() > minDigits && Integer.parseInt(episodeNumber) > 0) {
|
|
||||||
minDigits = episodeNumber.length();
|
|
||||||
}
|
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
// ignore
|
// ignore
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// pad episode numbers with zeros (e.g. %02d) so all episode numbers have the same number of digits
|
return number;
|
||||||
NumberFormat numberFormat = NumberFormat.getIntegerInstance();
|
|
||||||
numberFormat.setMinimumIntegerDigits(minDigits);
|
|
||||||
numberFormat.setGroupingUsed(false);
|
|
||||||
|
|
||||||
for (Episode episode : episodes) {
|
|
||||||
try {
|
|
||||||
episode.setEpisodeNumber(numberFormat.format(Integer.parseInt(episode.getEpisodeNumber())));
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return episodes;
|
|
||||||
|
@Override
|
||||||
|
public Episode parseObject(String source, ParsePosition pos) {
|
||||||
|
Pattern pattern = Pattern.compile("(.*) - (?:(\\w+?)x)?(\\w+)? - (.*)");
|
||||||
|
|
||||||
|
Matcher matcher = pattern.matcher(source).region(pos.getIndex(), source.length());
|
||||||
|
|
||||||
|
if (!matcher.matches()) {
|
||||||
|
pos.setErrorIndex(matcher.regionStart());
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// episode number must not be null
|
||||||
|
if (matcher.group(3) == null) {
|
||||||
|
pos.setErrorIndex(matcher.start(3));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
pos.setIndex(matcher.end());
|
||||||
|
return new Episode(matcher.group(1), matcher.group(2), matcher.group(3), matcher.group(4));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Episode parseObject(String source) throws ParseException {
|
||||||
|
return (Episode) super.parseObject(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
|
||||||
|
package net.sourceforge.filebot.ui;
|
||||||
|
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
import javax.script.Bindings;
|
||||||
|
import javax.script.Compilable;
|
||||||
|
import javax.script.CompiledScript;
|
||||||
|
import javax.script.ScriptException;
|
||||||
|
import javax.script.SimpleBindings;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
|
||||||
|
public class ScriptFormatTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void compile() throws Exception {
|
||||||
|
ScriptFormat format = new TestScriptFormat("");
|
||||||
|
|
||||||
|
Object[] expression = format.compile("name: {name}, number: {number}", (Compilable) format.initScriptEngine());
|
||||||
|
|
||||||
|
assertEquals(4, expression.length, 0);
|
||||||
|
|
||||||
|
assertTrue(expression[0] instanceof String);
|
||||||
|
assertTrue(expression[1] instanceof CompiledScript);
|
||||||
|
assertTrue(expression[2] instanceof String);
|
||||||
|
assertTrue(expression[3] instanceof CompiledScript);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void format() throws Exception {
|
||||||
|
assertEquals("X5-452", new TestScriptFormat("X5-{value}").format("452"));
|
||||||
|
|
||||||
|
// test pad
|
||||||
|
assertEquals("[007]", new TestScriptFormat("[{value.pad(3)}]").format("7"));
|
||||||
|
|
||||||
|
// choice
|
||||||
|
assertEquals("not to be", new TestScriptFormat("{if (value) 'to be'; else 'not to be'}").format(null));
|
||||||
|
|
||||||
|
// empty choice
|
||||||
|
assertEquals("", new TestScriptFormat("{if (value) 'to be'}").format(null));
|
||||||
|
|
||||||
|
// loop
|
||||||
|
assertEquals("0123456789", new TestScriptFormat("{var s=''; for (var i=0; i<parseInt(value);i++) s+=i;}").format("10"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected static class TestScriptFormat extends ScriptFormat {
|
||||||
|
|
||||||
|
public TestScriptFormat(String format) throws ScriptException {
|
||||||
|
super(format);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Bindings getBindings(Object value) {
|
||||||
|
Bindings bindings = new SimpleBindings();
|
||||||
|
|
||||||
|
bindings.put("value", value);
|
||||||
|
|
||||||
|
return bindings;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue