* improved Mac compatibility regarding DELETE key

This commit is contained in:
Reinhard Pointner 2014-07-24 12:10:47 +00:00
parent 2a4af5a995
commit c6bbd4db54
3 changed files with 109 additions and 136 deletions

View File

@ -1,7 +1,5 @@
package net.filebot.ui; package net.filebot.ui;
import java.awt.BorderLayout; import java.awt.BorderLayout;
import java.awt.event.ActionEvent; import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent; import java.awt.event.KeyEvent;
@ -24,137 +22,120 @@ import ca.odell.glazedlists.BasicEventList;
import ca.odell.glazedlists.EventList; import ca.odell.glazedlists.EventList;
import ca.odell.glazedlists.swing.EventListModel; import ca.odell.glazedlists.swing.EventListModel;
public class FileBotList<E> extends JComponent { public class FileBotList<E> extends JComponent {
protected EventList<E> model = new BasicEventList<E>(); protected EventList<E> model = new BasicEventList<E>();
protected JList list = new JList(new EventListModel<E>(model)); protected JList list = new JList(new EventListModel<E>(model));
protected JScrollPane listScrollPane = new JScrollPane(list); protected JScrollPane listScrollPane = new JScrollPane(list);
public FileBotList() { public FileBotList() {
setLayout(new BorderLayout()); setLayout(new BorderLayout());
setBorder(new TitledBorder(getTitle())); setBorder(new TitledBorder(getTitle()));
list.setCellRenderer(new DefaultFancyListCellRenderer()); list.setCellRenderer(new DefaultFancyListCellRenderer());
list.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); list.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
list.setTransferHandler(new DefaultTransferHandler(null, null)); list.setTransferHandler(new DefaultTransferHandler(null, null));
list.setDragEnabled(false); list.setDragEnabled(false);
add(listScrollPane, BorderLayout.CENTER); add(listScrollPane, BorderLayout.CENTER);
// Shortcut DELETE, disabled by default // Shortcut DELETE, disabled by default
getRemoveAction().setEnabled(false); getRemoveAction().setEnabled(false);
TunedUtilities.installAction(this, KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0), removeHook); TunedUtilities.installAction(this, KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0), removeHook);
TunedUtilities.installAction(this, KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, KeyEvent.ALT_DOWN_MASK), removeHook); TunedUtilities.installAction(this, KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, KeyEvent.ALT_DOWN_MASK), removeHook);
TunedUtilities.installAction(this, KeyStroke.getKeyStroke(KeyEvent.VK_BACK_SPACE, 0), removeHook);
TunedUtilities.installAction(this, KeyStroke.getKeyStroke(KeyEvent.VK_BACK_SPACE, KeyEvent.ALT_DOWN_MASK), removeHook);
} }
public EventList<E> getModel() { public EventList<E> getModel() {
return model; return model;
} }
public void setModel(EventList<E> model) { public void setModel(EventList<E> model) {
this.model = model; this.model = model;
list.setModel(new EventListModel<E>(model)); list.setModel(new EventListModel<E>(model));
} }
public JList getListComponent() { public JList getListComponent() {
return list; return list;
} }
public JScrollPane getListScrollPane() { public JScrollPane getListScrollPane() {
return listScrollPane; return listScrollPane;
} }
@Override @Override
public DefaultTransferHandler getTransferHandler() { public DefaultTransferHandler getTransferHandler() {
return (DefaultTransferHandler) list.getTransferHandler(); return (DefaultTransferHandler) list.getTransferHandler();
} }
public void setTransferablePolicy(TransferablePolicy transferablePolicy) { public void setTransferablePolicy(TransferablePolicy transferablePolicy) {
getTransferHandler().setTransferablePolicy(transferablePolicy); getTransferHandler().setTransferablePolicy(transferablePolicy);
} }
public TransferablePolicy getTransferablePolicy() { public TransferablePolicy getTransferablePolicy() {
return getTransferHandler().getTransferablePolicy(); return getTransferHandler().getTransferablePolicy();
} }
public void setExportHandler(TextFileExportHandler exportHandler) { public void setExportHandler(TextFileExportHandler exportHandler) {
getTransferHandler().setExportHandler(exportHandler); getTransferHandler().setExportHandler(exportHandler);
// enable drag if export handler is available // enable drag if export handler is available
list.setDragEnabled(exportHandler != null); list.setDragEnabled(exportHandler != null);
} }
public TextFileExportHandler getExportHandler() { public TextFileExportHandler getExportHandler() {
return (TextFileExportHandler) getTransferHandler().getExportHandler(); return (TextFileExportHandler) getTransferHandler().getExportHandler();
} }
public String getTitle() { public String getTitle() {
return (String) getClientProperty("title"); return (String) getClientProperty("title");
} }
public void setTitle(String title) { public void setTitle(String title) {
putClientProperty("title", title); putClientProperty("title", title);
if (getBorder() instanceof TitledBorder) { if (getBorder() instanceof TitledBorder) {
TitledBorder border = (TitledBorder) getBorder(); TitledBorder border = (TitledBorder) getBorder();
border.setTitle(title); border.setTitle(title);
repaint(); repaint();
} }
} }
private final AbstractAction defaultRemoveAction = new AbstractAction("Remove") { private final AbstractAction defaultRemoveAction = new AbstractAction("Remove") {
@Override @Override
public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) {
int index = list.getSelectedIndex(); int index = list.getSelectedIndex();
Object values[] = list.getSelectedValues(); Object values[] = list.getSelectedValues();
for (Object value : values) for (Object value : values)
getModel().remove(value); getModel().remove(value);
int maxIndex = list.getModel().getSize() - 1; int maxIndex = list.getModel().getSize() - 1;
if (index > maxIndex) if (index > maxIndex)
index = maxIndex; index = maxIndex;
list.setSelectedIndex(index); list.setSelectedIndex(index);
} }
}; };
private Action removeAction = defaultRemoveAction; private Action removeAction = defaultRemoveAction;
public Action getRemoveAction() { public Action getRemoveAction() {
return removeAction; return removeAction;
} }
public void setRemoveAction(Action action) { public void setRemoveAction(Action action) {
this.removeAction = action; this.removeAction = action;
} }
private final AbstractAction removeHook = new AbstractAction("Remove") { private final AbstractAction removeHook = new AbstractAction("Remove") {
@Override @Override
public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) {
if (getRemoveAction() != null && getRemoveAction().isEnabled()) { if (getRemoveAction() != null && getRemoveAction().isEnabled()) {
@ -162,5 +143,5 @@ public class FileBotList<E> extends JComponent {
} }
} }
}; };
} }

View File

@ -1,7 +1,5 @@
package net.filebot.ui.sfv; package net.filebot.ui.sfv;
import static java.lang.Math.*; import static java.lang.Math.*;
import static net.filebot.ui.sfv.ChecksumTableModel.*; import static net.filebot.ui.sfv.ChecksumTableModel.*;
import static net.filebot.ui.transfer.BackgroundFileTransferablePolicy.*; import static net.filebot.ui.transfer.BackgroundFileTransferablePolicy.*;
@ -39,47 +37,45 @@ import net.filebot.util.FileUtilities;
import net.filebot.util.ui.TunedUtilities; import net.filebot.util.ui.TunedUtilities;
import net.miginfocom.swing.MigLayout; import net.miginfocom.swing.MigLayout;
public class SfvPanel extends JComponent { public class SfvPanel extends JComponent {
private final ChecksumComputationService computationService = new ChecksumComputationService(); private final ChecksumComputationService computationService = new ChecksumComputationService();
private final ChecksumTable table = new ChecksumTable(); private final ChecksumTable table = new ChecksumTable();
private final ChecksumTableTransferablePolicy transferablePolicy = new ChecksumTableTransferablePolicy(table.getModel(), computationService); private final ChecksumTableTransferablePolicy transferablePolicy = new ChecksumTableTransferablePolicy(table.getModel(), computationService);
private final ChecksumTableExportHandler exportHandler = new ChecksumTableExportHandler(table.getModel()); private final ChecksumTableExportHandler exportHandler = new ChecksumTableExportHandler(table.getModel());
public SfvPanel() { public SfvPanel() {
table.setTransferHandler(new DefaultTransferHandler(transferablePolicy, exportHandler)); table.setTransferHandler(new DefaultTransferHandler(transferablePolicy, exportHandler));
JPanel contentPane = new JPanel(new MigLayout("insets 0, nogrid, fill", "", "[fill]10px[bottom, pref!]4px")); JPanel contentPane = new JPanel(new MigLayout("insets 0, nogrid, fill", "", "[fill]10px[bottom, pref!]4px"));
contentPane.setBorder(new TitledBorder("SFV")); contentPane.setBorder(new TitledBorder("SFV"));
setLayout(new MigLayout("insets dialog, fill")); setLayout(new MigLayout("insets dialog, fill"));
add(contentPane, "grow"); add(contentPane, "grow");
contentPane.add(new JScrollPane(table), "grow, wrap"); contentPane.add(new JScrollPane(table), "grow, wrap");
contentPane.add(new JButton(loadAction), "gap left 15px"); contentPane.add(new JButton(loadAction), "gap left 15px");
contentPane.add(new JButton(saveAction)); contentPane.add(new JButton(saveAction));
contentPane.add(new JButton(clearAction), "gap right 40px"); contentPane.add(new JButton(clearAction), "gap right 40px");
// hash function toggle button group // hash function toggle button group
ButtonGroup group = new ButtonGroup(); ButtonGroup group = new ButtonGroup();
for (HashType hash : HashType.values()) { for (HashType hash : HashType.values()) {
JToggleButton button = new ChecksumButton(new ChangeHashTypeAction(hash)); JToggleButton button = new ChecksumButton(new ChangeHashTypeAction(hash));
group.add(button); group.add(button);
contentPane.add(button); contentPane.add(button);
} }
contentPane.add(new TotalProgressPanel(computationService), "gap left 35px:push, gap right 7px, hidemode 1"); contentPane.add(new TotalProgressPanel(computationService), "gap left 35px:push, gap right 7px, hidemode 1");
// cancel and restart computations whenever the hash function has been changed // cancel and restart computations whenever the hash function has been changed
table.getModel().addPropertyChangeListener(new PropertyChangeListener() { table.getModel().addPropertyChangeListener(new PropertyChangeListener() {
@Override @Override
public void propertyChange(PropertyChangeEvent evt) { public void propertyChange(PropertyChangeEvent evt) {
if (HASH_TYPE_PROPERTY.equals(evt.getPropertyName())) { if (HASH_TYPE_PROPERTY.equals(evt.getPropertyName())) {
@ -87,189 +83,176 @@ public class SfvPanel extends JComponent {
} }
} }
}); });
putClientProperty("transferablePolicy", transferablePolicy); putClientProperty("transferablePolicy", transferablePolicy);
// Shortcut DELETE // Shortcut DELETE
TunedUtilities.installAction(this, KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0), removeAction); TunedUtilities.installAction(this, KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0), removeAction);
TunedUtilities.installAction(this, KeyStroke.getKeyStroke(KeyEvent.VK_BACK_SPACE, 0), removeAction);
} }
protected void restartComputation(HashType hash) { protected void restartComputation(HashType hash) {
// cancel all running computations // cancel all running computations
computationService.reset(); computationService.reset();
ChecksumTableModel model = table.getModel(); ChecksumTableModel model = table.getModel();
// calculate new hashes, one executor for each checksum column // calculate new hashes, one executor for each checksum column
Map<File, ExecutorService> executors = new HashMap<File, ExecutorService>(4); Map<File, ExecutorService> executors = new HashMap<File, ExecutorService>(4);
for (ChecksumRow row : model.rows()) { for (ChecksumRow row : model.rows()) {
for (ChecksumCell cell : row.values()) { for (ChecksumCell cell : row.values()) {
if (cell.getChecksum(hash) == null && cell.getRoot().isDirectory()) { if (cell.getChecksum(hash) == null && cell.getRoot().isDirectory()) {
cell.putTask(new ChecksumComputationTask(new File(cell.getRoot(), cell.getName()), hash)); cell.putTask(new ChecksumComputationTask(new File(cell.getRoot(), cell.getName()), hash));
ExecutorService executor = executors.get(cell.getRoot()); ExecutorService executor = executors.get(cell.getRoot());
if (executor == null) { if (executor == null) {
executor = computationService.newExecutor(); executor = computationService.newExecutor();
executors.put(cell.getRoot(), executor); executors.put(cell.getRoot(), executor);
} }
// start computation // start computation
executor.execute(cell.getTask()); executor.execute(cell.getTask());
} }
} }
} }
// start shutdown sequence for all created executors // start shutdown sequence for all created executors
for (ExecutorService executor : executors.values()) { for (ExecutorService executor : executors.values()) {
executor.shutdown(); executor.shutdown();
} }
} }
private final SaveAction saveAction = new ChecksumTableSaveAction(); private final SaveAction saveAction = new ChecksumTableSaveAction();
private final LoadAction loadAction = new LoadAction(transferablePolicy); private final LoadAction loadAction = new LoadAction(transferablePolicy);
private final AbstractAction clearAction = new AbstractAction("Clear", ResourceManager.getIcon("action.clear")) { private final AbstractAction clearAction = new AbstractAction("Clear", ResourceManager.getIcon("action.clear")) {
@Override @Override
public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) {
transferablePolicy.reset(); transferablePolicy.reset();
computationService.reset(); computationService.reset();
table.getModel().clear(); table.getModel().clear();
} }
}; };
private final AbstractAction removeAction = new AbstractAction("Remove") { private final AbstractAction removeAction = new AbstractAction("Remove") {
@Override @Override
public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) {
if (table.getSelectedRowCount() < 1) if (table.getSelectedRowCount() < 1)
return; return;
int[] rows = table.getSelectedRows(); int[] rows = table.getSelectedRows();
if (rows.length <= 0) { if (rows.length <= 0) {
// no rows selected // no rows selected
return; return;
} }
// first selected row // first selected row
int selectedRow = table.getSelectedRow(); int selectedRow = table.getSelectedRow();
// convert view index to model index // convert view index to model index
for (int i = 0; i < rows.length; i++) { for (int i = 0; i < rows.length; i++) {
rows[i] = table.getRowSorter().convertRowIndexToModel(rows[i]); rows[i] = table.getRowSorter().convertRowIndexToModel(rows[i]);
} }
// remove selected rows // remove selected rows
table.getModel().remove(rows); table.getModel().remove(rows);
// update computation service task count // update computation service task count
computationService.purge(); computationService.purge();
// auto select next row // auto select next row
selectedRow = min(selectedRow, table.getRowCount() - 1); selectedRow = min(selectedRow, table.getRowCount() - 1);
table.getSelectionModel().setSelectionInterval(selectedRow, selectedRow); table.getSelectionModel().setSelectionInterval(selectedRow, selectedRow);
} }
}; };
protected class ChangeHashTypeAction extends AbstractAction implements PropertyChangeListener { protected class ChangeHashTypeAction extends AbstractAction implements PropertyChangeListener {
private ChangeHashTypeAction(HashType hash) { private ChangeHashTypeAction(HashType hash) {
super(hash.toString()); super(hash.toString());
putValue(HASH_TYPE_PROPERTY, hash); putValue(HASH_TYPE_PROPERTY, hash);
// initialize selected state // initialize selected state
propertyChange(new PropertyChangeEvent(this, HASH_TYPE_PROPERTY, null, table.getModel().getHashType())); propertyChange(new PropertyChangeEvent(this, HASH_TYPE_PROPERTY, null, table.getModel().getHashType()));
transferablePolicy.addPropertyChangeListener(this); transferablePolicy.addPropertyChangeListener(this);
table.getModel().addPropertyChangeListener(this); table.getModel().addPropertyChangeListener(this);
} }
@Override @Override
public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) {
table.getModel().setHashType((HashType) getValue(HASH_TYPE_PROPERTY)); table.getModel().setHashType((HashType) getValue(HASH_TYPE_PROPERTY));
} }
@Override @Override
public void propertyChange(PropertyChangeEvent evt) { public void propertyChange(PropertyChangeEvent evt) {
if (LOADING_PROPERTY.equals(evt.getPropertyName())) { if (LOADING_PROPERTY.equals(evt.getPropertyName())) {
// update enabled state // update enabled state
setEnabled(!(Boolean) evt.getNewValue()); setEnabled(!(Boolean) evt.getNewValue());
} else if (HASH_TYPE_PROPERTY.equals(evt.getPropertyName())) { } else if (HASH_TYPE_PROPERTY.equals(evt.getPropertyName())) {
// update selected state // update selected state
putValue(SELECTED_KEY, evt.getNewValue() == getValue(HASH_TYPE_PROPERTY)); putValue(SELECTED_KEY, evt.getNewValue() == getValue(HASH_TYPE_PROPERTY));
} }
} }
} }
protected class ChecksumTableSaveAction extends SaveAction { protected class ChecksumTableSaveAction extends SaveAction {
private File selectedColumn = null; private File selectedColumn = null;
public ChecksumTableSaveAction() { public ChecksumTableSaveAction() {
super(exportHandler); super(exportHandler);
} }
@Override @Override
public ChecksumTableExportHandler getExportHandler() { public ChecksumTableExportHandler getExportHandler() {
return (ChecksumTableExportHandler) super.getExportHandler(); return (ChecksumTableExportHandler) super.getExportHandler();
} }
@Override @Override
protected boolean canExport() { protected boolean canExport() {
return selectedColumn != null && super.canExport(); return selectedColumn != null && super.canExport();
} }
@Override @Override
protected void export(File file) throws IOException { protected void export(File file) throws IOException {
getExportHandler().export(file, selectedColumn); getExportHandler().export(file, selectedColumn);
} }
@Override @Override
protected String getDefaultFileName() { protected String getDefaultFileName() {
return getExportHandler().getDefaultFileName(selectedColumn); return getExportHandler().getDefaultFileName(selectedColumn);
} }
@Override @Override
protected File getDefaultFolder() { protected File getDefaultFolder() {
// use the column root as default folder in the file dialog // use the column root as default folder in the file dialog
return selectedColumn; return selectedColumn;
} }
@Override @Override
public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) {
List<File> options = new ArrayList<File>(); List<File> options = new ArrayList<File>();
// filter out verification file columns // filter out verification file columns
for (File file : table.getModel().getChecksumColumns()) { for (File file : table.getModel().getChecksumColumns()) {
if (file.isDirectory()) if (file.isDirectory())
options.add(file); options.add(file);
} }
// can't export anything // can't export anything
if (options.isEmpty()) { if (options.isEmpty()) {
return; return;
} }
try { try {
if (options.size() == 1) { if (options.size() == 1) {
// auto-select option if there is only one option // auto-select option if there is only one option
@ -277,20 +260,20 @@ public class SfvPanel extends JComponent {
} else if (options.size() > 1) { } else if (options.size() > 1) {
// user must select one option // user must select one option
SelectDialog<File> selectDialog = new SelectDialog<File>(SwingUtilities.getWindowAncestor(SfvPanel.this), options) { SelectDialog<File> selectDialog = new SelectDialog<File>(SwingUtilities.getWindowAncestor(SfvPanel.this), options) {
@Override @Override
protected String convertValueToString(Object value) { protected String convertValueToString(Object value) {
return FileUtilities.getFolderName((File) value); return FileUtilities.getFolderName((File) value);
} }
}; };
selectDialog.getHeaderLabel().setText("Select checksum column:"); selectDialog.getHeaderLabel().setText("Select checksum column:");
selectDialog.setLocationRelativeTo(SfvPanel.this); selectDialog.setLocationRelativeTo(SfvPanel.this);
selectDialog.setVisible(true); selectDialog.setVisible(true);
this.selectedColumn = selectDialog.getSelectedValue(); this.selectedColumn = selectDialog.getSelectedValue();
} }
if (this.selectedColumn != null) { if (this.selectedColumn != null) {
// continue if a column was selected // continue if a column was selected
super.actionPerformed(e); super.actionPerformed(e);
@ -301,5 +284,5 @@ public class SfvPanel extends JComponent {
} }
} }
} }
} }

View File

@ -95,11 +95,20 @@ public final class TunedUtilities {
public static void installAction(JComponent component, KeyStroke keystroke, Action action) { public static void installAction(JComponent component, KeyStroke keystroke, Action action) {
Object key = action.getValue(Action.NAME); Object key = action.getValue(Action.NAME);
if (key == null) if (key == null) {
throw new IllegalArgumentException("Action must have a name"); throw new IllegalArgumentException("Action must have a name");
}
component.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(keystroke, key); component.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(keystroke, key);
component.getActionMap().put(key, action); component.getActionMap().put(key, action);
// automatically add Mac OS X compatibility (on Mac the BACKSPACE key is called DELETE, and there is no DELETE key)
if (keystroke.getKeyCode() == KeyEvent.VK_DELETE) {
KeyStroke macKeyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_BACK_SPACE, keystroke.getModifiers(), keystroke.isOnKeyRelease());
Object macKey = "mac." + action.getValue(Action.NAME);
component.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(macKeyStroke, macKey);
component.getActionMap().put(macKey, action);
}
} }
public static UndoManager installUndoSupport(JTextComponent component) { public static UndoManager installUndoSupport(JTextComponent component) {