diff --git a/source/net/sourceforge/filebot/ui/rename/BindingDialog.java b/source/net/sourceforge/filebot/ui/rename/BindingDialog.java index aefc0edc..115d3e8f 100644 --- a/source/net/sourceforge/filebot/ui/rename/BindingDialog.java +++ b/source/net/sourceforge/filebot/ui/rename/BindingDialog.java @@ -1,7 +1,5 @@ - package net.sourceforge.filebot.ui.rename; - import static net.sourceforge.filebot.MediaTypes.*; import static net.sourceforge.filebot.Settings.*; import static net.sourceforge.filebot.ui.NotificationLogging.*; @@ -66,131 +64,130 @@ import net.sourceforge.filebot.mediainfo.MediaInfoException; import net.sourceforge.tuned.DefaultThreadFactory; import net.sourceforge.tuned.ui.LazyDocumentListener; - class BindingDialog extends JDialog { - + private final JTextField infoTextField = new JTextField(); private final JTextField mediaFileTextField = new JTextField(); - + private final Format infoObjectFormat; private final BindingTableModel bindingModel = new BindingTableModel(); - + private boolean submit = false; - - - public BindingDialog(Window owner, String title, Format infoObjectFormat) { + + public BindingDialog(Window owner, String title, Format infoObjectFormat, boolean editable) { super(owner, title, ModalityType.DOCUMENT_MODAL); this.infoObjectFormat = infoObjectFormat; - + 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("Name:"), "wrap 2px"); inputPanel.add(infoTextField, "hmin 20px, growx, wrap paragraph"); - + inputPanel.add(new JLabel("Media File:"), "wrap 2px"); inputPanel.add(mediaFileTextField, "hmin 20px, growx"); inputPanel.add(createImageButton(mediaInfoAction), "gap rel, w 26px!, h 24px!"); inputPanel.add(createImageButton(selectFileAction), "gap rel, w 26px!, h 24px!, wrap paragraph"); - + inputContainer.add("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 MediaBindingBean(getInfoObject(), getMediaFile(), null)); } }; - + // update example bindings on change infoTextField.getDocument().addDocumentListener(changeListener); mediaFileTextField.getDocument().addDocumentListener(changeListener); - + // disabled by default infoTextField.setEnabled(false); mediaInfoAction.setEnabled(false); - + // disable media info action if media file is invalid mediaFileTextField.getDocument().addDocumentListener(new DocumentListener() { - + @Override public void changedUpdate(DocumentEvent e) { mediaInfoAction.setEnabled(getMediaFile() != null && getMediaFile().isFile()); } - - + @Override public void removeUpdate(DocumentEvent e) { changedUpdate(e); } - - + @Override public void insertUpdate(DocumentEvent e) { changedUpdate(e); } }); - + // finish dialog and close window manually addWindowListener(new WindowAdapter() { - + @Override public void windowClosing(WindowEvent e) { finish(false); } }); - + + mediaFileTextField.setEditable(editable); + infoTextField.setEditable(editable); + selectFileAction.setEnabled(editable); + // initialize window properties setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); 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, null, isSelected, hasFocus, row, column); - + @SuppressWarnings("unchecked") Future 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 + // not ready yet setText("Pending …"); - + // highlight cell if (!isSelected) { setForeground(new Color(0x6495ED)); // CornflowerBlue @@ -198,68 +195,61 @@ class BindingDialog extends JDialog { } catch (Exception e) { // could not evaluate expression setText("undefined"); - + // highlight cell if (!isSelected) { setForeground(Color.gray); } } - + return this; } }); - + return table; } - - + private List getSampleExpressions() { ResourceBundle bundle = ResourceBundle.getBundle(getClass().getName()); return Arrays.asList(bundle.getString("expressions").split(",")); } - - + public boolean submit() { return submit; } - - + private void finish(boolean submit) { this.submit = submit; - + // cancel background evaluators bindingModel.executor.shutdownNow(); - + setVisible(false); dispose(); } - - + public void setInfoObject(Object info) { infoTextField.putClientProperty("model", info); infoTextField.setText(info == null ? "" : infoObjectFormat.format(info)); } - - + public void setMediaFile(File mediaFile) { mediaFileTextField.setText(mediaFile == null ? "" : mediaFile.getAbsolutePath()); } - - + public Object getInfoObject() { return infoTextField.getClientProperty("model"); } - - + 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 @@ -275,21 +265,21 @@ class BindingDialog extends JDialog { } } }; - + protected final Action cancelAction = new AbstractAction("Cancel", ResourceManager.getIcon("dialog.cancel")) { - + @Override public void actionPerformed(ActionEvent evt) { finish(true); } }; - + protected final Action mediaInfoAction = new AbstractAction("Info", ResourceManager.getIcon("action.properties")) { - + private Map>> getMediaInfo(File file) { try { MediaInfo mediaInfo = new MediaInfo(); - + // read all media info if (mediaInfo.open(file)) { try { @@ -301,93 +291,92 @@ class BindingDialog extends JDialog { } catch (MediaInfoException e) { UILogger.log(Level.SEVERE, e.getMessage(), e); } - + // could not retrieve media info return null; } - - + @Override public void actionPerformed(ActionEvent evt) { Map>> mediaInfo = getMediaInfo(getMediaFile()); - + // check if we could get some info if (mediaInfo == null) return; - + // create table tab for each stream JTabbedPane tabbedPane = new JTabbedPane(); - + ResourceBundle bundle = ResourceBundle.getBundle(BindingDialog.class.getName()); RowFilter excludeRowFilter = RowFilter.notFilter(RowFilter.regexFilter(bundle.getString("parameter.exclude"))); - + for (StreamKind streamKind : mediaInfo.keySet()) { for (Map parameters : mediaInfo.get(streamKind)) { JPanel panel = new JPanel(new MigLayout("fill")); panel.setOpaque(false); - + JTable table = new JTable(new ParameterTableModel(parameters)); table.setAutoCreateRowSorter(true); table.setFillsViewportHeight(true); table.setBackground(Color.white); - + // set media info exclude filter TableRowSorter sorter = (TableRowSorter) table.getRowSorter(); sorter.setRowFilter(excludeRowFilter); - + panel.add(new JScrollPane(table), "grow"); tabbedPane.addTab(streamKind.toString(), panel); } } - + // show media info dialog final JDialog dialog = new JDialog(getWindow(evt.getSource()), "MediaInfo", ModalityType.DOCUMENT_MODAL); - + Action closeAction = new AbstractAction("OK") { - + @Override public void actionPerformed(ActionEvent e) { dialog.setVisible(false); dialog.dispose(); } }; - + JComponent c = (JComponent) dialog.getContentPane(); c.setLayout(new MigLayout("fill", "[align center]", "[fill][pref!]")); c.add(tabbedPane, "grow, wrap"); c.add(new JButton(closeAction), "wmin 80px, hmin 25px"); - + dialog.pack(); dialog.setLocationRelativeTo(BindingDialog.this); - + dialog.setVisible(true); } - + }; - + 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(); Collections.addAll(extensions, VIDEO_FILES.extensions()); Collections.addAll(extensions, AUDIO_FILES.extensions()); Collections.addAll(extensions, SUBTITLE_FILES.extensions()); - + chooser.setFileFilter(new FileNameExtensionFilter("Media files", extensions.toArray(new String[0]))); chooser.setMultiSelectionEnabled(false); - + if (chooser.showOpenDialog(getWindow(evt.getSource())) == JFileChooser.APPROVE_OPTION) { // update text field File file = chooser.getSelectedFile(); - + // set file mediaFileTextField.setText(file.getAbsolutePath()); - + // set info object from xattr if possible if (useExtendedFileAttributes()) { try { @@ -407,48 +396,43 @@ class BindingDialog extends JDialog { } } }; - - + 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) throws ScriptException { // simple expression format, everything as one expression return new Object[] { compileScriptlet(expression) }; } }; - + // evaluate expression with given bindings String value = format.format(bindingBean); - + // check for script exceptions if (format.caughtScriptException() != null) { throw format.caughtScriptException(); } - + return value; } - - + @Override public String toString() { try { @@ -458,159 +442,144 @@ class BindingDialog extends JDialog { } } } - - + 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; + 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; + 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; + case 0: + return model.get(row).getExpression(); + case 1: + return model.get(row); + default: + return null; } } } - - + private static class ParameterTableModel extends AbstractTableModel { - + private final List> data; - - + public ParameterTableModel(Map data) { this.data = new ArrayList>(data.entrySet()); } - - + @Override public int getRowCount() { return data.size(); } - - + @Override public int getColumnCount() { return 2; } - - + @Override public String getColumnName(int column) { switch (column) { - case 0: - return "Parameter"; - case 1: - return "Value"; - default: - return null; + case 0: + return "Parameter"; + case 1: + return "Value"; + default: + return null; } } - - + @Override public Object getValueAt(int row, int column) { switch (column) { - case 0: - return data.get(row).getKey(); - case 1: - return data.get(row).getValue(); - default: - return null; + case 0: + return data.get(row).getKey(); + case 1: + return data.get(row).getValue(); + default: + return null; } } } - + } diff --git a/source/net/sourceforge/filebot/ui/rename/BindingDialog.properties b/source/net/sourceforge/filebot/ui/rename/BindingDialog.properties index d89a18e7..ec81fc07 100644 --- a/source/net/sourceforge/filebot/ui/rename/BindingDialog.properties +++ b/source/net/sourceforge/filebot/ui/rename/BindingDialog.properties @@ -2,4 +2,4 @@ parameter.exclude: ^StreamKind|Count$ # preview expressions (keys are tagged so they can be sorted alphabetically) -expressions: n,y,s,e,t,d,startdate,absolute,special,imdbid,episode,sxe,s00e00,movie,vc,ac,cf,vf,af,resolution,hpi,ws,sdhd,source,group,crc32,fn,ext,file,pi,pn,lang,actors,director,collection,genres,certification,rating,dim,info.runtime,info.status,imdb.rating,imdb.votes,media.title,media.durationString,media.overallBitRateString,video.codecID,video.frameRate,video.displayAspectRatioString,video.height,video.scanType,audio.format,audio.bitRateString,audio.language,text.codecInfo,text.language +expressions: n,y,s,e,es,sxe,s00e00,t,d,startdate,absolute,special,episode,series,primaryTitle,alias,movie,tmdbid,imdbid,music,artist,albumArtist,album,pi,pn,lang,actors,director,collection,genres,certification,rating,vc,ac,cf,vf,hpi,af,resolution,dim,ws,sdhd,source,group,original,fn,ext,file,file.name,folder,folder.name,crc32,info,info.runtime,info.status,imdb,imdb.rating,imdb.votes,duration,seconds,minutes,media,media.title,media.overallBitRateString,video,video.codecID,video.frameRate,video.displayAspectRatioString,video.scanType,audio,audio.bitRateString,audio.language,audios,audios.language,text,text.codecInfo,text.language,texts,texts.language \ No newline at end of file diff --git a/source/net/sourceforge/filebot/ui/rename/FormatDialog.java b/source/net/sourceforge/filebot/ui/rename/FormatDialog.java index 632ce11b..7b943856 100644 --- a/source/net/sourceforge/filebot/ui/rename/FormatDialog.java +++ b/source/net/sourceforge/filebot/ui/rename/FormatDialog.java @@ -88,11 +88,12 @@ import com.cedarsoftware.util.io.JsonWriter; public class FormatDialog extends JDialog { private boolean submit = false; - - private Mode mode; private ExpressionFormat format; - private MediaBindingBean sample; + private Mode mode; + private boolean locked = false; + private MediaBindingBean sample = null; + private ExecutorService executor = createExecutor(); private RunnableFuture currentPreviewFuture; @@ -233,6 +234,7 @@ public class FormatDialog extends JDialog { public void setState(Mode mode, MediaBindingBean bindings, boolean locked) { this.mode = mode; + this.locked = locked; if (locked) { this.setTitle(String.format("%s Format", mode)); @@ -245,7 +247,6 @@ public class FormatDialog extends JDialog { switchEditModeAction.putValue(Action.NAME, String.format("Switch to %s Format", mode.next())); switchEditModeAction.setEnabled(!locked); - changeSampleAction.setEnabled(!locked); updateHelpPanel(mode); @@ -606,7 +607,7 @@ public class FormatDialog extends JDialog { @Override public void actionPerformed(ActionEvent evt) { - BindingDialog dialog = new BindingDialog(getWindow(evt.getSource()), String.format("%s Bindings", mode), mode.getFormat()); + BindingDialog dialog = new BindingDialog(getWindow(evt.getSource()), String.format("%s Bindings", mode), mode.getFormat(), !locked); dialog.setInfoObject(sample.getInfoObject()); dialog.setMediaFile(sample.getMediaFile());