diff --git a/source/net/sourceforge/filebot/ui/panel/rename/HistoryDialog.java b/source/net/sourceforge/filebot/ui/panel/rename/HistoryDialog.java index 381de540..e653c9a1 100644 --- a/source/net/sourceforge/filebot/ui/panel/rename/HistoryDialog.java +++ b/source/net/sourceforge/filebot/ui/panel/rename/HistoryDialog.java @@ -4,6 +4,7 @@ package net.sourceforge.filebot.ui.panel.rename; import static java.awt.Font.*; import static java.util.Collections.*; +import static java.util.regex.Pattern.*; import static javax.swing.JOptionPane.*; import java.awt.Color; @@ -27,6 +28,7 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.Map.Entry; +import java.util.regex.Pattern; import javax.swing.AbstractAction; import javax.swing.Action; @@ -89,7 +91,7 @@ class HistoryDialog extends JDialog { private final JTable elementTable = createTable(elementModel); - + public HistoryDialog(Window owner) { super(owner, "Rename History", ModalityType.DOCUMENT_MODAL); @@ -174,7 +176,7 @@ class HistoryDialog extends JDialog { private final DateFormat format = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.SHORT); - + @Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { return super.getTableCellRendererComponent(table, format.format(value), isSelected, hasFocus, row, column); @@ -306,6 +308,7 @@ class HistoryDialog extends JDialog { return table; } + private final Action closeAction = new AbstractAction("Close") { @Override @@ -376,13 +379,13 @@ class HistoryDialog extends JDialog { } }; - + private static class RevertAction extends AbstractAction { public static final String ELEMENTS = "elements"; public static final String PARENT = "parent"; - + public RevertAction(Collection elements, HistoryDialog parent) { putValue(NAME, "Revert..."); putValue(ELEMENTS, elements.toArray(new Element[0])); @@ -399,21 +402,24 @@ class HistoryDialog extends JDialog { return (HistoryDialog) getValue(PARENT); } - + private enum Option { Rename { + @Override public String toString() { return "Rename"; } }, ChangeDirectory { + @Override public String toString() { return "Change Directory"; } }, Cancel { + @Override public String toString() { return "Cancel"; @@ -421,7 +427,7 @@ class HistoryDialog extends JDialog { } } - + @Override public void actionPerformed(ActionEvent e) { // use default directory @@ -444,6 +450,7 @@ class HistoryDialog extends JDialog { } else { String text = String.format("Some files are missing. Please select a different directory."); JList missingFilesComponent = new JList(missingFiles.toArray()) { + @Override public Dimension getPreferredScrollableViewportSize() { // adjust component size @@ -452,6 +459,7 @@ class HistoryDialog extends JDialog { }; missingFilesComponent.setCellRenderer(new DefaultListCellRenderer() { + @Override public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { return super.getListCellRendererComponent(list, ((File) value).getName(), index, isSelected, false); @@ -544,6 +552,7 @@ class HistoryDialog extends JDialog { } } + private final FileTransferablePolicy importHandler = new FileTransferablePolicy() { @Override @@ -600,14 +609,14 @@ class HistoryDialog extends JDialog { } }; - + private static class HistoryFilter extends RowFilter { - private final String filter; - + private final Pattern filter; + public HistoryFilter(String filter) { - this.filter = filter.toLowerCase(); + this.filter = compile(quote(filter), CASE_INSENSITIVE | UNICODE_CASE | CANON_EQ); } @@ -643,7 +652,7 @@ class HistoryDialog extends JDialog { private boolean include(String value) { - return value.toLowerCase().contains(filter); + return filter.matcher(value).find(); } } @@ -652,7 +661,7 @@ class HistoryDialog extends JDialog { private List data = emptyList(); - + public void setData(List data) { this.data = new ArrayList(data); @@ -758,7 +767,7 @@ class HistoryDialog extends JDialog { private List data = emptyList(); - + public void setData(List data) { this.data = new ArrayList(data); diff --git a/source/net/sourceforge/filebot/ui/panel/subtitle/SubtitleDownloadComponent.java b/source/net/sourceforge/filebot/ui/panel/subtitle/SubtitleDownloadComponent.java index 690a3b23..0db89d9f 100644 --- a/source/net/sourceforge/filebot/ui/panel/subtitle/SubtitleDownloadComponent.java +++ b/source/net/sourceforge/filebot/ui/panel/subtitle/SubtitleDownloadComponent.java @@ -13,7 +13,12 @@ import java.beans.PropertyChangeListener; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; import java.nio.channels.FileChannel; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Deque; import java.util.List; import java.util.concurrent.CancellationException; import java.util.logging.Level; @@ -48,8 +53,12 @@ import ca.odell.glazedlists.swing.TextComponentMatcherEditor; import net.miginfocom.swing.MigLayout; import net.sourceforge.filebot.ResourceManager; +import net.sourceforge.filebot.subtitle.SubtitleElement; +import net.sourceforge.filebot.subtitle.SubtitleFormat; +import net.sourceforge.filebot.subtitle.SubtitleReader; import net.sourceforge.filebot.ui.panel.subtitle.SubtitlePackage.Download.Phase; import net.sourceforge.filebot.ui.transfer.DefaultTransferHandler; +import net.sourceforge.tuned.ByteBufferInputStream; import net.sourceforge.tuned.ExceptionUtilities; import net.sourceforge.tuned.ui.ListView; @@ -92,6 +101,7 @@ public class SubtitleDownloadComponent extends JComponent { fileList.setDragEnabled(true); fileList.setTransferHandler(new DefaultTransferHandler(null, new MemoryFileListExportHandler())); + fileList.addMouseListener(fileListMouseHandler); JButton clearButton = new JButton(clearFilterAction); clearButton.setOpaque(false); @@ -317,4 +327,126 @@ public class SubtitleDownloadComponent extends JComponent { } }; + private final MouseListener fileListMouseHandler = new MouseAdapter() { + + @Override + public void mouseClicked(MouseEvent e) { + // open on double click + if (SwingUtilities.isLeftMouseButton(e) && (e.getClickCount() == 2)) { + JList list = (JList) e.getSource(); + + // open selection + open(list.getSelectedValues()); + } + } + + + @Override + public void mousePressed(MouseEvent e) { + maybeShowPopup(e); + } + + + @Override + public void mouseReleased(MouseEvent e) { + maybeShowPopup(e); + } + + + private void maybeShowPopup(MouseEvent e) { + if (e.isPopupTrigger()) { + JList list = (JList) e.getSource(); + + int index = list.locationToIndex(e.getPoint()); + + if (index >= 0 && !list.isSelectedIndex(index)) { + // auto-select clicked element + list.setSelectedIndex(index); + } + + final Object[] selection = list.getSelectedValues(); + + JPopupMenu contextMenu = new JPopupMenu(); + + // Open + contextMenu.add(new AbstractAction("Open") { + + @Override + public void actionPerformed(ActionEvent evt) { + open(selection); + } + }); + + // Save as ... + contextMenu.add(new AbstractAction("Save as ...") { + + @Override + public void actionPerformed(ActionEvent evt) { + // save() + } + }); + + contextMenu.show(e.getComponent(), e.getX(), e.getY()); + } + } + + + private void open(Object[] selection) { + for (Object object : selection) { + try { + open((MemoryFile) object); + } catch (IOException e) { + Logger.getLogger(getClass().getName()).log(Level.WARNING, e.getMessage()); + } + } + } + + + private void open(MemoryFile file) throws IOException { + Deque priorityList = new ArrayDeque(4); + + // gather all formats, put likely formats first + for (SubtitleFormat format : SubtitleFormat.values()) { + if (format.filter().accept(file.getName())) { + priorityList.addFirst(format); + } else { + priorityList.addLast(format); + } + } + + // decode subtitle file with the first reader that seems to work + for (SubtitleFormat format : priorityList) { + InputStream data = new ByteBufferInputStream(file.getData()); + SubtitleReader reader = format.newReader(new InputStreamReader(data, "UTF-8")); + + try { + if (reader.hasNext()) { + // correct format + List list = new ArrayList(500); + + // read subtitle file + while (reader.hasNext()) { + list.add(reader.next()); + } + + SubtitleViewer viewer = new SubtitleViewer(file.getName()); + viewer.getTitleLabel().setText("Subtitle Viewer"); + viewer.getInfoLabel().setText(file.getPath()); + + viewer.setData(list); + viewer.setVisible(true); + + // done + return; + } + } finally { + reader.close(); + } + } + + throw new IOException("Cannot read subtitle format"); + } + + }; + } diff --git a/source/net/sourceforge/filebot/ui/panel/subtitle/SubtitleViewer.java b/source/net/sourceforge/filebot/ui/panel/subtitle/SubtitleViewer.java new file mode 100644 index 00000000..3a8f5141 --- /dev/null +++ b/source/net/sourceforge/filebot/ui/panel/subtitle/SubtitleViewer.java @@ -0,0 +1,281 @@ + +package net.sourceforge.filebot.ui.panel.subtitle; + + +import static java.awt.Font.*; +import static java.util.Collections.*; +import static java.util.regex.Pattern.*; + +import java.awt.Color; +import java.awt.Component; +import java.awt.event.ActionEvent; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Locale; +import java.util.TimeZone; +import java.util.regex.Pattern; + +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTable; +import javax.swing.JTextField; +import javax.swing.ListSelectionModel; +import javax.swing.RowFilter; +import javax.swing.event.DocumentEvent; +import javax.swing.table.AbstractTableModel; +import javax.swing.table.DefaultTableCellRenderer; +import javax.swing.table.DefaultTableColumnModel; +import javax.swing.table.TableModel; +import javax.swing.table.TableRowSorter; + +import net.miginfocom.swing.MigLayout; +import net.sourceforge.filebot.ResourceManager; +import net.sourceforge.filebot.subtitle.SubtitleElement; +import net.sourceforge.tuned.ui.GradientStyle; +import net.sourceforge.tuned.ui.LazyDocumentListener; +import net.sourceforge.tuned.ui.notification.SeparatorBorder; +import net.sourceforge.tuned.ui.notification.SeparatorBorder.Position; + + +class SubtitleViewer extends JFrame { + + private final JLabel titleLabel = new JLabel(); + + private final JLabel infoLabel = new JLabel(); + + private final SubtitleTableModel model = new SubtitleTableModel(); + + private final JTextField filterEditor = new JTextField(); + + private final JTable subtitleTable = createTable(model); + + + public SubtitleViewer(String title) { + super(title); + + // bold title label in header + titleLabel.setText(title); + titleLabel.setFont(titleLabel.getFont().deriveFont(BOLD)); + + 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(titleLabel, "wrap"); + header.add(infoLabel, "gap indent*2, wrap paragraph:push"); + + JPanel content = new JPanel(new MigLayout("fill, insets dialog, nogrid", "[grow]", "[pref!][grow]")); + + content.add(new JLabel("Filter:"), "gap indent:push"); + content.add(filterEditor, "wmin 120px, gap rel"); + content.add(new JButton(clearFilterAction), "w 24px!, h 24px!, wrap"); + content.add(new JScrollPane(subtitleTable), "grow"); + + JComponent pane = (JComponent) getContentPane(); + pane.setLayout(new MigLayout("fill, insets 0")); + + pane.add(header, "hmin 20px, growx, dock north"); + pane.add(content, "grow"); + + // initialize selection modes + subtitleTable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); + + final DateFormat timeFormat = new SimpleDateFormat("HH:mm:ss", Locale.ROOT); + timeFormat.setTimeZone(TimeZone.getTimeZone("UTC")); + + // change time stamp format + subtitleTable.setDefaultRenderer(Date.class, new DefaultTableCellRenderer() { + + @Override + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { + return super.getTableCellRendererComponent(table, timeFormat.format(value), isSelected, hasFocus, row, column); + } + }); + + // change text format + subtitleTable.setDefaultRenderer(String.class, new DefaultTableCellRenderer() { + + @Override + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { + return super.getTableCellRendererComponent(table, value.toString().replaceAll("\\s+", " "), isSelected, hasFocus, row, column); + } + }); + + // update sequence and element filter on change + filterEditor.getDocument().addDocumentListener(new LazyDocumentListener() { + + @Override + public void update(DocumentEvent e) { + List filterList = new ArrayList(); + + // filter by all words + for (String word : filterEditor.getText().split("\\s+")) { + filterList.add(new SubtitleFilter(word)); + } + + TableRowSorter sorter = (TableRowSorter) subtitleTable.getRowSorter(); + sorter.setRowFilter(RowFilter.andFilter(filterList)); + } + }); + + // initialize window properties + setDefaultCloseOperation(DISPOSE_ON_CLOSE); + setLocationByPlatform(true); + setResizable(true); + pack(); + } + + + public void setData(List data) { + model.setData(data); + } + + + private JTable createTable(TableModel model) { + JTable table = new JTable(model); + table.setAutoCreateRowSorter(true); + table.setFillsViewportHeight(true); + table.setRowHeight(18); + + // decrease column width for the row number columns + DefaultTableColumnModel m = ((DefaultTableColumnModel) table.getColumnModel()); + m.getColumn(0).setMaxWidth(40); + m.getColumn(1).setMaxWidth(60); + m.getColumn(2).setMaxWidth(60); + + return table; + } + + + public JLabel getTitleLabel() { + return titleLabel; + } + + + public JLabel getInfoLabel() { + return infoLabel; + } + + + private final Action clearFilterAction = new AbstractAction(null, ResourceManager.getIcon("edit.clear")) { + + @Override + public void actionPerformed(ActionEvent e) { + filterEditor.setText(""); + } + }; + + + private static class SubtitleFilter extends RowFilter { + + private final Pattern filter; + + + public SubtitleFilter(String filter) { + this.filter = compile(quote(filter), CASE_INSENSITIVE | UNICODE_CASE | CANON_EQ); + } + + + @Override + public boolean include(Entry entry) { + SubtitleTableModel model = (SubtitleTableModel) entry.getModel(); + SubtitleElement element = model.getRow(entry.getIdentifier()); + + return filter.matcher(element.getText()).find(); + } + + } + + + private static class SubtitleTableModel extends AbstractTableModel { + + private List data = emptyList(); + + + public void setData(List data) { + this.data = new ArrayList(data); + + // update view + fireTableDataChanged(); + } + + + public SubtitleElement getRow(int row) { + return data.get(row); + } + + + @Override + public String getColumnName(int column) { + switch (column) { + case 0: + return "#"; + case 1: + return "Start"; + case 2: + return "End"; + case 3: + return "Text"; + default: + return null; + } + } + + + @Override + public int getColumnCount() { + return 4; + } + + + @Override + public int getRowCount() { + return data.size(); + } + + + @Override + public Class getColumnClass(int column) { + switch (column) { + case 0: + return Integer.class; + case 1: + return Date.class; + case 2: + return Date.class; + case 3: + return String.class; + default: + return null; + } + } + + + @Override + public Object getValueAt(int row, int column) { + switch (column) { + case 0: + return row + 1; + case 1: + return getRow(row).getStart(); + case 2: + return getRow(row).getEnd(); + case 3: + return getRow(row).getText(); + default: + return null; + } + } + } + +}