package net.sourceforge.filebot.ui.panel.rename; import static java.awt.Font.*; import static java.util.Collections.*; import static javax.swing.JOptionPane.*; import java.awt.Color; import java.awt.Component; import java.awt.Dimension; import java.awt.Window; import java.awt.event.ActionEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.io.File; import java.io.IOException; import java.text.DateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Date; import java.util.EnumSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Map.Entry; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.DefaultListCellRenderer; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JDialog; import javax.swing.JFileChooser; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.JTextField; import javax.swing.ListSelectionModel; import javax.swing.RowFilter; import javax.swing.SortOrder; import javax.swing.RowSorter.SortKey; import javax.swing.border.CompoundBorder; import javax.swing.border.TitledBorder; import javax.swing.event.DocumentEvent; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; 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.ui.panel.rename.History.Element; import net.sourceforge.filebot.ui.panel.rename.History.Sequence; import net.sourceforge.filebot.ui.transfer.FileExportHandler; import net.sourceforge.filebot.ui.transfer.FileTransferablePolicy; import net.sourceforge.filebot.ui.transfer.LoadAction; import net.sourceforge.filebot.ui.transfer.SaveAction; import net.sourceforge.filebot.ui.transfer.TransferablePolicy.TransferAction; import net.sourceforge.tuned.FileUtilities; import net.sourceforge.tuned.FileUtilities.ExtensionFileFilter; 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 HistoryDialog extends JDialog { private final JLabel infoLabel = new JLabel(); private final JTextField filterEditor = new JTextField(); private final SequenceTableModel sequenceModel = new SequenceTableModel(); private final ElementTableModel elementModel = new ElementTableModel(); private final JTable sequenceTable = createTable(sequenceModel); private final JTable elementTable = createTable(elementModel); public HistoryDialog(Window owner) { super(owner, "Rename History", ModalityType.DOCUMENT_MODAL); // bold title label in header JLabel title = new JLabel(this.getTitle()); title.setFont(title.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(title, "wrap"); header.add(infoLabel, "gap indent*2, wrap paragraph:push"); JPanel content = new JPanel(new MigLayout("fill, insets dialog, nogrid", "", "[pref!][150px:pref:200px][200px:pref:max, grow][pref!]")); content.add(new JLabel("Filter:"), "gap indent:push"); content.add(filterEditor, "wmin 120px, gap rel"); content.add(new JButton(clearFilterAction), "w 24px!, h 24px!, gap right indent, wrap"); content.add(createScrollPaneGroup("Sequences", sequenceTable), "growx, wrap paragraph"); content.add(createScrollPaneGroup("Elements", elementTable), "growx, wrap paragraph"); // use ADD by default Action importAction = new LoadAction("Import", null, importHandler) { @Override public TransferAction getTransferAction(ActionEvent evt) { // if SHIFT was pressed when the button was clicked, assume PUT action, use ADD by default return ((evt.getModifiers() & ActionEvent.SHIFT_MASK) != 0) ? TransferAction.PUT : TransferAction.ADD; } }; content.add(new JButton(importAction), "wmin button, hmin 25px, gap indent, sg button"); content.add(new JButton(new SaveAction("Export", null, exportHandler)), "gap rel, sg button"); content.add(new JButton(closeAction), "gap left unrel:push, gap right indent, sg button"); JComponent pane = (JComponent) getContentPane(); pane.setLayout(new MigLayout("fill, insets 0, nogrid")); pane.add(header, "hmin 60px, growx, dock north"); pane.add(content, "grow"); // initialize selection modes sequenceTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); elementTable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); // bind element model to selected sequence sequenceTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() { @Override public void valueChanged(ListSelectionEvent e) { if (e.getValueIsAdjusting()) return; if (sequenceTable.getSelectedRow() >= 0) { int index = sequenceTable.convertRowIndexToModel(sequenceTable.getSelectedRow()); elementModel.setData(sequenceModel.getRow(index).elements()); } } }); // clear sequence selection when elements are selected elementTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() { @Override public void valueChanged(ListSelectionEvent e) { if (elementTable.getSelectedRow() >= 0) { // allow selected rows only in one of the two tables sequenceTable.getSelectionModel().clearSelection(); } } }); // sort by number descending sequenceTable.getRowSorter().setSortKeys(singletonList(new SortKey(0, SortOrder.DESCENDING))); // change date format sequenceTable.setDefaultRenderer(Date.class, new DefaultTableCellRenderer() { 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); } }); // display broken status in second column elementTable.setDefaultRenderer(String.class, new DefaultTableCellRenderer() { @Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); // reset icon setIcon(null); if (column == 1) { if (elementModel.isBroken(table.convertRowIndexToModel(row))) { setIcon(ResourceManager.getIcon("status.link.broken")); } else { setIcon(ResourceManager.getIcon("status.link.ok")); } } return this; } }); // 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 HistoryFilter(word)); } // use filter on both tables for (JTable table : Arrays.asList(sequenceTable, elementTable)) { TableRowSorter sorter = (TableRowSorter) table.getRowSorter(); sorter.setRowFilter(RowFilter.andFilter(filterList)); } if (sequenceTable.getSelectedRow() < 0 && sequenceTable.getRowCount() > 0) { // selection lost, maybe due to filtering, auto-select next row sequenceTable.getSelectionModel().addSelectionInterval(0, 0); } } }); // install context menu sequenceTable.addMouseListener(contextMenuProvider); elementTable.addMouseListener(contextMenuProvider); // initialize window properties setDefaultCloseOperation(DISPOSE_ON_CLOSE); setLocationByPlatform(true); setResizable(true); setSize(580, 640); } public void setModel(History history) { // update table model sequenceModel.setData(history.sequences()); if (sequenceTable.getRowCount() > 0) { // auto-select first element and update element table sequenceTable.getSelectionModel().addSelectionInterval(0, 0); } else { // clear element table elementModel.setData(new ArrayList(0)); } // display basic statistics initializeInfoLabel(); } public History getModel() { return new History(sequenceModel.getData()); } public JLabel getInfoLabel() { return infoLabel; } private void initializeInfoLabel() { int count = 0; Date since = new Date(); for (Sequence sequence : sequenceModel.getData()) { count += sequence.elements().size(); if (sequence.date().before(since)) since = sequence.date(); } infoLabel.setText(String.format("A total of %,d files have been renamed since %s.", count, DateFormat.getDateInstance().format(since))); } private JScrollPane createScrollPaneGroup(String title, JComponent component) { JScrollPane scrollPane = new JScrollPane(component); scrollPane.setBorder(new CompoundBorder(new TitledBorder(title), scrollPane.getBorder())); return scrollPane; } private JTable createTable(TableModel model) { JTable table = new JTable(model); table.setAutoCreateRowSorter(true); table.setFillsViewportHeight(true); // hide grid table.setShowGrid(false); table.setIntercellSpacing(new Dimension(0, 0)); // decrease column width for the row number columns DefaultTableColumnModel m = ((DefaultTableColumnModel) table.getColumnModel()); m.getColumn(0).setMaxWidth(50); return table; } private final Action closeAction = new AbstractAction("Close") { @Override public void actionPerformed(ActionEvent e) { setVisible(false); dispose(); } }; private final Action clearFilterAction = new AbstractAction(null, ResourceManager.getIcon("edit.clear")) { @Override public void actionPerformed(ActionEvent e) { filterEditor.setText(""); } }; private final MouseListener contextMenuProvider = new MouseAdapter() { @Override public void mousePressed(MouseEvent e) { maybeShowPopup(e); } @Override public void mouseReleased(MouseEvent e) { maybeShowPopup(e); } private void maybeShowPopup(MouseEvent e) { if (e.isPopupTrigger()) { JTable table = (JTable) e.getSource(); int clickedRow = table.rowAtPoint(e.getPoint()); if (clickedRow < 0) { // no row was clicked return; } if (!table.getSelectionModel().isSelectedIndex(clickedRow)) { // if clicked row is not selected, set selection to this row (and deselect all other currently selected row) table.getSelectionModel().setSelectionInterval(clickedRow, clickedRow); } List selection = new ArrayList(); for (int i : table.getSelectedRows()) { int index = table.convertRowIndexToModel(i); if (sequenceModel == table.getModel()) { selection.addAll(sequenceModel.getRow(index).elements()); } else if (elementModel == table.getModel()) { selection.add(elementModel.getRow(index)); } } if (selection.size() > 0) { JPopupMenu menu = new JPopupMenu(); menu.add(new RevertAction(selection, HistoryDialog.this)); // display popup menu.show(table, e.getX(), e.getY()); } } } }; 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])); putValue(PARENT, parent); } public Element[] elements() { return (Element[]) getValue(ELEMENTS); } public HistoryDialog parent() { 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"; } } } @Override public void actionPerformed(ActionEvent e) { // use default directory File directory = null; Option selectedOption = Option.ChangeDirectory; // change directory option while (selectedOption == Option.ChangeDirectory) { List missingFiles = getMissingFiles(directory); Object message; int type; Set