package org.rr.jeborker.gui;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.border.TitledBorder;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.event.TableModelListener;
import javax.swing.table.TableModel;
import org.apache.commons.lang.math.NumberUtils;
import org.rr.commons.mufs.IResourceHandler;
import org.rr.commons.mufs.ResourceHandlerFactory;
import org.rr.commons.mufs.ResourceHandlerUtils;
import org.rr.commons.swing.components.JREditableHistoryComboBox;
import org.rr.commons.swing.components.JRTable;
import org.rr.commons.utils.StringUtil;
import org.rr.jeborker.db.item.EbookPropertyItem;
public class RenameFileView extends AbstractDialogView {
public static final int ACTION_RESULT_OK = 0;
private static final int ABORT_BUTTON_INDEX = 0;
private static final int OK_BUTTON_INDEX = 1;
private int actionResult = -1;
private RenameFileController controller;
private List<EbookPropertyItem> toRename;
private String[] cachedValues;
private JRTable previewTable;
JCheckBox overwriteCheckbox;
private final ActionListener abortAction = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
controller.close();
}
};
private final ActionListener okAction = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
actionResult = ACTION_RESULT_OK;
addCurrentPatternToHistory();
controller.close();
}
};
private final TableModel previewTableModel = new TableModel() {
@Override
public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
}
@Override
public boolean isCellEditable(int rowIndex, int columnIndex) {
return false;
}
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
if(cachedValues[rowIndex] != null) {
return cachedValues[rowIndex];
}
EbookPropertyItem ebookPropertyItem = toRename.get(rowIndex);
cachedValues[rowIndex] = ResourceHandlerUtils.removeInvalidCharacters(formatFileName(getFileNamePattern(), ebookPropertyItem, rowIndex));
cachedValues[rowIndex] = StringUtil.removeMultipleWhiteSpaces(cachedValues[rowIndex]);
return cachedValues[rowIndex];
}
@Override
public int getRowCount() {
return toRename.size();
}
@Override
public String getColumnName(int columnIndex) {
return null;
}
@Override
public int getColumnCount() {
return 1;
}
@Override
public Class<?> getColumnClass(int columnIndex) {
return String.class;
}
@Override
public void addTableModelListener(TableModelListener l) {
}
@Override
public void removeTableModelListener(TableModelListener l) {
}
};
DocumentListener textFieldFilePatternChangeListener = new DocumentListener() {
@Override
public void removeUpdate(DocumentEvent e) {
fireValuesChanged();
}
@Override
public void insertUpdate(DocumentEvent e) {
fireValuesChanged();
}
@Override
public void changedUpdate(DocumentEvent e) {
}
};
private JREditableHistoryComboBox textFieldFilePattern;
public RenameFileView(RenameFileController controller, List<EbookPropertyItem> list, JFrame mainWindow) {
super(mainWindow);
setModal(true);
this.controller = controller;
this.toRename = list != null ? list : Collections.<EbookPropertyItem>emptyList();
this.cachedValues = new String[this.toRename.size()];
this.initialize();
}
protected void initialize() {
super.initialize();
GridBagLayout gridBagLayout = new GridBagLayout();
gridBagLayout.columnWidths = new int[]{0, 0, 0, 0};
gridBagLayout.rowHeights = new int[]{0, 0, 0};
gridBagLayout.columnWeights = new double[]{0.0, 1.0, Double.MIN_VALUE};
gridBagLayout.rowWeights = new double[]{0.0, 0.0, Double.MIN_VALUE};
getContentPane().setLayout(gridBagLayout);
// File pattern text field components
JLabel lblFilePattern = new JLabel(Bundle.getString("RenameFileView.pattern"));
GridBagConstraints gbc_lblFilePattern = new GridBagConstraints();
gbc_lblFilePattern.fill = GridBagConstraints.VERTICAL;
gbc_lblFilePattern.insets = new Insets(5, 5, 3, 5);
gbc_lblFilePattern.anchor = GridBagConstraints.EAST;
gbc_lblFilePattern.gridx = 0;
gbc_lblFilePattern.gridy = 0;
getContentPane().add(lblFilePattern, gbc_lblFilePattern);
textFieldFilePattern = new JREditableHistoryComboBox();
textFieldFilePattern.addDocumentListener(textFieldFilePatternChangeListener);
textFieldFilePattern.setItem("%a - %t");
GridBagConstraints gbc_textFieldFilePattern = new GridBagConstraints();
gbc_textFieldFilePattern.insets = new Insets(5, 0, 3, 5);
gbc_textFieldFilePattern.fill = GridBagConstraints.BOTH;
gbc_textFieldFilePattern.gridx = 1;
gbc_textFieldFilePattern.gridy = 0;
getContentPane().add(textFieldFilePattern, gbc_textFieldFilePattern);
//info label
JLabel infoLabel = new JLabel(Bundle.getString("RenameFileView.patternInfo"));
GridBagConstraints gbc_infoLabel = new GridBagConstraints();
gbc_infoLabel.insets = new Insets(5, 5, 10, 5);
gbc_infoLabel.fill = GridBagConstraints.BOTH;
gbc_infoLabel.gridx = 0;
gbc_infoLabel.gridy = 1;
gbc_infoLabel.gridwidth = 2;
getContentPane().add(infoLabel, gbc_infoLabel);
// Middle file name preview
JPanel borderPanel = new JPanel();
borderPanel.setBorder(new TitledBorder(null, Bundle.getString("RenameFileView.preview"), TitledBorder.LEADING, TitledBorder.TOP, null, null));
borderPanel.setLayout(new BorderLayout());
GridBagConstraints gbc_middle = new GridBagConstraints();
gbc_middle.insets = new Insets(5, 3, 5, 3);
gbc_middle.fill = GridBagConstraints.BOTH;
gbc_middle.gridx = 0;
gbc_middle.gridy = 2;
gbc_middle.gridwidth = 2;
gbc_middle.weighty = 1.0;
getContentPane().add(borderPanel, gbc_middle);
previewTable = new JRTable();
previewTable.setModel(previewTableModel);
previewTable.setEnabled(false);
previewTable.setTableHeader(null);
borderPanel.add(new JScrollPane(previewTable), BorderLayout.CENTER);
//Overwrite checkbox
overwriteCheckbox = new JCheckBox(Bundle.getString("RenameFileView.overwrite"));
GridBagConstraints gbc_overwriteCheckbox = new GridBagConstraints();
gbc_overwriteCheckbox.insets = new Insets(5, 3, 5, 3);
gbc_overwriteCheckbox.fill = GridBagConstraints.BOTH;
gbc_overwriteCheckbox.gridx = 0;
gbc_overwriteCheckbox.gridy = 3;
gbc_overwriteCheckbox.gridwidth = 2;
getContentPane().add(overwriteCheckbox, gbc_overwriteCheckbox);
// Bottom OK and Abort
JPanel bottomPanel = createBottomButtonPanel();
GridBagConstraints gbc_bottom = new GridBagConstraints();
gbc_bottom.insets = new Insets(5, 3, 5, 3);
gbc_bottom.fill = GridBagConstraints.BOTH;
gbc_bottom.gridx = 0;
gbc_bottom.gridy = 4;
gbc_bottom.gridwidth = 2;
getContentPane().add(bottomPanel, gbc_bottom);
}
public int getActionResult() {
return actionResult;
}
/**
* Tells if existing target files should be overwritten.
* @return <code>true</code> if existing files should be overwritten and <code>false</code> otherwise.
*/
public boolean isOverwriteExistingFiles() {
return overwriteCheckbox.isSelected();
}
/**
* Get the rename result with the origin {@link EbookPropertyItem} at the left and the target {@link IResourceHandler}
* at the right in the {@link Entry}.
* @return The renamed files with it's original {@link EbookPropertyItem}s. Never returns <code>null</code>.
*/
public List<Entry<EbookPropertyItem, IResourceHandler>> getValues() {
int rowCount = previewTableModel.getRowCount();
List<Entry<EbookPropertyItem, IResourceHandler>> result = new ArrayList<Entry<EbookPropertyItem, IResourceHandler>>(rowCount);
for (int i = 0; i < rowCount; i++) {
final EbookPropertyItem ebookPropertyItem = toRename.get(i);
String renamedFileName = StringUtil.toString(previewTableModel.getValueAt(i, 0));
final IResourceHandler targetResourceHandler = ResourceHandlerFactory.getResourceHandler(ebookPropertyItem.getResourceHandler().getParentResource(), renamedFileName);
result.add(new Map.Entry<EbookPropertyItem, IResourceHandler>() {
@Override
public EbookPropertyItem getKey() {
return ebookPropertyItem;
}
@Override
public IResourceHandler getValue() {
return targetResourceHandler;
}
@Override
public IResourceHandler setValue(IResourceHandler value) {
return null;
}});
}
return result;
}
/**
* Replaces the placeholder with the value from the given {@link EbookPropertyItem}.
* @param pattern The file name pattern including placeholder to replace.
* @param item The replacement data.
* @param num The number for the %n parameter
* @return The formatted file name.
*/
private String formatFileName(String pattern, EbookPropertyItem item, int num) {
pattern = pattern.replaceAll("%a", StringUtil.toString(item.getAuthor()));
pattern = pattern.replaceAll("%t", StringUtil.toString(item.getTitle()));
pattern = pattern.replaceAll("%g", StringUtil.toString(item.getGenre()));
pattern = formatNumberPattern(pattern, "%i", NumberUtils.toInt(item.getSeriesIndex()));
pattern = pattern.replaceAll("%s", StringUtil.toString(item.getSeriesName()));
pattern = formatNumberPattern(pattern, "%n", num);
pattern = StringUtil.removeMultipleWhiteSpaces(pattern);
String fileExtension = item.getResourceHandler().getFileExtension();
if(StringUtil.isNotEmpty(fileExtension)) {
return pattern + "." + fileExtension;
}
return pattern;
}
private String formatNumberPattern(String pattern, String identifier, int num) {
if(pattern.contains(identifier)) {
int numIndex = pattern.indexOf(identifier) + 2;
int digits = 0;
while(pattern.length() > numIndex + digits && pattern.charAt(numIndex + digits) == '#') {
digits++;
}
String formattedNum = new DecimalFormat(StringUtil.string(digits, '0')).format(num);
pattern = pattern.replaceAll(identifier + "#*", formattedNum);
}
return pattern;
}
/**
* Get the file name pattern entered into the text field.
* @return The desired file name. Never returns <code>null</code>.
*/
String getFileNamePattern() {
return textFieldFilePattern.getEditorValue();
}
/**
* Sets the file name displayed in the text field.
* @param pattern The string to be displayed in the file name field.
*/
void setFileNamePattern(String pattern) {
if(StringUtil.isNotEmpty(pattern)) {
textFieldFilePattern.setItem(pattern);
}
}
/**
* Sets the file name history values displayed in the combobox popup.
* @param history The history values.
*/
void setFileNameHistory(String history) {
if(StringUtil.isNotEmpty(history)) {
textFieldFilePattern.setHistoryValues(history);
}
}
/**
* Gets the file name history values displayed in the combobox popup.
* @return The file history list.
*/
String getFileNameHistory() {
return textFieldFilePattern.getHistoryValues();
}
/**
* Can be invoked if the preview table needs to be renewed.
*/
private void fireValuesChanged() {
for (int i = 0; i < cachedValues.length; i++) {
cachedValues[i] = null;
}
if(previewTable != null) {
previewTable.repaint();
handleInvalidRenameTargets();
}
}
/**
* Disables the rename button if there are duplicate entries in the {@link #previewTableModel} or enables
* them if they're all distinct.
*/
private void handleInvalidRenameTargets() {
int rowCount = previewTableModel.getRowCount();
for (int i = 0; i < rowCount; i++) {
String value = StringUtil.toString(previewTableModel.getValueAt(i, 0));
for (int j = i + 1; j < rowCount; j++) {
if(value.equals(previewTableModel.getValueAt(j, 0))) {
getButtonAt(OK_BUTTON_INDEX).setEnabled(false);
return;
} else if(StringUtil.isEmpty(value)) {
getButtonAt(OK_BUTTON_INDEX).setEnabled(false);
return;
} else if(value.equals(".")) {
getButtonAt(OK_BUTTON_INDEX).setEnabled(false);
return;
}
}
}
getButtonAt(OK_BUTTON_INDEX).setEnabled(true);
}
/**
* Adds the current editor value to the history of the pattern field.
*/
private void addCurrentPatternToHistory() {
String selectedItem = (String) textFieldFilePattern.getSelectedItem();
if(StringUtil.isNotBlank(selectedItem)) {
textFieldFilePattern.addHistoryValue(selectedItem);
}
}
@Override
protected int getBottomButtonCount() {
return 2;
}
@Override
protected ActionListener getBottomButtonAction(int index) {
switch(index) {
case ABORT_BUTTON_INDEX:
return abortAction;
case OK_BUTTON_INDEX:
return okAction;
}
return null;
}
@Override
protected String getBottomButtonLabel(int index) {
switch(index) {
case 0:
return Bundle.getString("RenameFileView.Abort");
case 1:
return Bundle.getString("RenameFileView.OK");
}
return null;
}
@Override
protected Dimension getDefaultDialogSize() {
return new Dimension(700, 500);
}
@Override
protected String getDialogTitle() {
return Bundle.getString("RenameFileView.title");
}
@Override
protected Dimension getDialogMinimumSize() {
return new Dimension(300, 200);
}
@Override
protected boolean isDefaultButtonAction(int idx) {
return idx == OK_BUTTON_INDEX;
}
@Override
protected boolean isAbortAction(int idx) {
return idx == ABORT_BUTTON_INDEX;
}
}