/*
* This file is part of the OSMembrane project.
* More informations under www.osmembrane.de
*
* The project is licensed under the GNU GENERAL PUBLIC LICENSE 3.0.
* for more details about the license see http://www.osmembrane.de/license/
*
* Source: $HeadURL$ ($Revision$)
* Last changed: $Date$
*/
package de.osmembrane.view.dialogs;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.GridLayout;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFileChooser;
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.filechooser.FileFilter;
import javax.swing.table.DefaultTableModel;
import org.openstreetmap.josm.gui.tagging.ac.AutoCompletingComboBox;
import de.osmembrane.Application;
import de.osmembrane.model.ModelProxy;
import de.osmembrane.model.pipeline.AbstractParameter;
import de.osmembrane.model.preset.PresetItem;
import de.osmembrane.model.settings.SettingType;
import de.osmembrane.tools.I18N;
import de.osmembrane.view.AbstractDialog;
import de.osmembrane.view.interfaces.IListDialog;
/**
*
* Simple dialog to edit special types of comma-separated lists with
* AutoComplete functionality, plus save or load them.
*
* @see "Spezifikation.pdf, chapter 2.2 (German)"
*
* @author tobias_kuhn
*
*/
public class ListDialog extends AbstractDialog implements IListDialog {
private static final long serialVersionUID = -1373885104364615162L;
/**
* Allocates the correct type for the listType
*
* @author tobias_kuhn
*
*/
private enum ListType {
INVALID, NODE, WAY
};
/**
* Allocates the correct content type for the listContentType
*
* @author tobias_kuhn
*
*/
private enum ListContentType {
INVALID, KEY, KEY_VALUE
}
/**
* Whether or not to apply the changes made in the dialog
*/
private boolean applyChanges;
/**
* The list parameter currently in edition
*/
private AbstractParameter listParam;
/**
* The ListType of the list
*/
private ListType listType;
/**
* The ListContentType of the list
*/
private ListContentType listContentType;
/**
* The {@link JTable} to display all the list entries.
*/
private JTable editList;
/**
* The model for editList
*/
private ListDialogTableModel editListModel;
/**
* {@link JFileChooser} for saving and loading the model
*/
private JFileChooser fileChooser;
/**
* Field for entering new entries
*/
private AutoCompletingComboBox editField;
/**
* Generates a new {@link ListDialog}.
*/
public ListDialog(Window owner) {
super(owner);
// set the basics up
setLayout(new BorderLayout());
setWindowTitle(I18N.getInstance().getString("View.ListDialog", ""));
// ensure no editor when hiding
addWindowListener(new WindowListener() {
@Override
public void windowOpened(WindowEvent e) {
}
@Override
public void windowIconified(WindowEvent e) {
}
@Override
public void windowDeiconified(WindowEvent e) {
}
@Override
public void windowDeactivated(WindowEvent e) {
}
@Override
public void windowClosing(WindowEvent e) {
// close the annoying JTable editor when we're leaving
editList.removeEditor();
}
@Override
public void windowClosed(WindowEvent e) {
}
@Override
public void windowActivated(WindowEvent e) {
}
});
// prepare file chooser
File startDir = new File((String) ModelProxy.getInstance()
.getSettings()
.getValue((SettingType.DEFAULT_WORKING_DIRECTORY)));
fileChooser = new JFileChooser(startDir);
fileChooser.setFileFilter(new FileFilter() {
@Override
public String getDescription() {
return I18N.getInstance().getString(
"View.ListDialog.FileTypeDescription");
}
@Override
public boolean accept(File f) {
return (f.getName().toLowerCase().endsWith(".txt") || f
.isDirectory());
}
});
// control buttons
JButton okButton = new JButton(I18N.getInstance().getString("View.OK"));
okButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
applyChanges = true;
editList.removeEditor();
hideWindow();
}
});
JButton cancelButton = new JButton(I18N.getInstance().getString(
"View.Cancel"));
cancelButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
applyChanges = false;
editList.removeEditor();
hideWindow();
}
});
JPanel buttonCtrlGrid = new JPanel(new GridLayout(1, 2));
buttonCtrlGrid.add(okButton);
buttonCtrlGrid.add(cancelButton);
JPanel buttonCtrlFlow = new JPanel(new FlowLayout(FlowLayout.CENTER));
buttonCtrlFlow.add(buttonCtrlGrid);
add(buttonCtrlFlow, BorderLayout.SOUTH);
// editing buttons
final JButton addButton = new JButton(I18N.getInstance().getString(
"View.Add"));
addButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
// reset field, add to model
editListModel.insert(editField.getSelectedItem().toString());
editField.setSelectedItem("");
}
});
final JButton deleteButton = new JButton(I18N.getInstance().getString(
"View.Delete"));
deleteButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
// delete rows from model
int[] rowsToDelete = editList.getSelectedRows();
Arrays.sort(rowsToDelete);
for (int i = rowsToDelete.length - 1; i >= 0; i--) {
editListModel.delete(rowsToDelete[i]);
}
}
});
JButton clearButton = new JButton(I18N.getInstance().getString(
"View.Clear"));
clearButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
editListModel.regenerate("");
}
});
JButton resetButton = new JButton(I18N.getInstance().getString(
"View.Reset"));
resetButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
// regenerate model with parameter data
editListModel.regenerate(listParam.getValue());
}
});
JButton loadButton = new JButton(I18N.getInstance().getString(
"View.Load"));
loadButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
// load from file
if (fileChooser.showOpenDialog(ListDialog.this) == JFileChooser.APPROVE_OPTION) {
try {
FileReader fr = new FileReader(fileChooser
.getSelectedFile());
BufferedReader br = new BufferedReader(fr);
StringBuilder read = new StringBuilder();
String line;
while ((line = br.readLine()) != null) {
read.append(line);
}
editListModel.regenerate(read.toString());
br.close();
fr.close();
} catch (IOException e1) {
Application.handleException(e1);
}
}
}
});
JButton saveButton = new JButton(I18N.getInstance().getString(
"View.Save"));
saveButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
// save to file
if (fileChooser.showSaveDialog(ListDialog.this) == JFileChooser.APPROVE_OPTION) {
try {
String saveTo = fileChooser.getSelectedFile()
.getAbsolutePath();
if (!saveTo.toLowerCase().endsWith(".txt")) {
saveTo += ".txt";
}
FileWriter fw = new FileWriter(saveTo);
fw.write(editListModel.getContent());
fw.close();
} catch (IOException e1) {
Application.handleException(e1);
}
}
}
});
JPanel buttonEditGrid = new JPanel(new GridLayout(7, 1));
buttonEditGrid.add(addButton);
buttonEditGrid.add(deleteButton);
buttonEditGrid.add(clearButton);
buttonEditGrid.add(resetButton);
buttonEditGrid.add(new JLabel());
buttonEditGrid.add(loadButton);
buttonEditGrid.add(saveButton);
JPanel buttonEditFlow = new JPanel(new FlowLayout(FlowLayout.LEADING));
buttonEditFlow.add(buttonEditGrid);
add(buttonEditFlow, BorderLayout.EAST);
// data holders
editListModel = new ListDialogTableModel();
editList = new JTable(editListModel);
editList.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
editList.addKeyListener(new KeyListener() {
@Override
public void keyTyped(KeyEvent e) {
}
@Override
public void keyReleased(KeyEvent e) {
// do not edit on delete, do delete the item
if (e.getKeyCode() == KeyEvent.VK_DELETE) {
editList.removeEditor();
deleteButton.doClick();
}
}
@Override
public void keyPressed(KeyEvent e) {
}
});
JScrollPane editPane = new JScrollPane(editList);
editPane.setPreferredSize(new Dimension(640, 480));
JPanel edit = new JPanel(new BorderLayout());
edit.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));
edit.add(editPane, BorderLayout.CENTER);
editField = new AutoCompletingComboBox();
editField.setEditable(true);
edit.add(editField, BorderLayout.NORTH);
add(edit, BorderLayout.CENTER);
pack();
centerWindow();
}
@Override
public void open(AbstractParameter list) {
this.listParam = list;
this.editListModel.regenerate(list.getValue());
this.applyChanges = false;
// determine listType and listContentType
if (listParam.getListType().toLowerCase().contains("node")) {
this.listType = ListType.NODE;
} else if (listParam.getListType().toLowerCase().contains("way")) {
this.listType = ListType.WAY;
} else {
this.listType = ListType.INVALID;
}
// note: order important
if (listParam.getListType().toLowerCase().contains("key")
&& listParam.getListType().toLowerCase().contains("value")) {
this.listContentType = ListContentType.KEY_VALUE;
} else if (listParam.getListType().toLowerCase().contains("key")) {
this.listContentType = ListContentType.KEY;
} else {
this.listContentType = ListContentType.INVALID;
}
this.editField.setSelectedItem(null);
this.editField.setPossibleItems(generateAutoCompletionList());
this.editField.requestFocus();
setWindowTitle(I18N.getInstance().getString("View.ListDialog",
list.getListType()));
showWindow();
}
private Collection<String> generateAutoCompletionList() {
PresetItem[] items = null;
if (listType == ListType.NODE) {
if (listContentType == ListContentType.KEY) {
items = ModelProxy.getInstance().getPreset().getNodeKeys();
} else if (listContentType == ListContentType.KEY_VALUE) {
items = ModelProxy.getInstance().getPreset().getNodes();
}
} else if (listType == ListType.WAY) {
if (listContentType == ListContentType.KEY) {
items = ModelProxy.getInstance().getPreset().getWayKeys();
} else if (listContentType == ListContentType.KEY_VALUE) {
items = ModelProxy.getInstance().getPreset().getWays();
}
}
if (items == null) {
return new ArrayList<String>(0);
}
List<String> result = new ArrayList<String>(items.length);
for (PresetItem i : items) {
String s;
if (listContentType == ListContentType.KEY_VALUE) {
s = i.getKeyValue();
} else {
s = i.getKey();
}
int index = Collections.binarySearch(result, s);
if (index >= 0) {
continue;
}
result.add(~index, s);
}
return result;
}
@Override
public boolean shallApplyChanges() {
return this.applyChanges;
}
@Override
public String getEdits() {
return editListModel.getContent();
}
/**
* Sets the current editList editor value to full and selects all parts of
* full, that are after contentLen. Needs to be synchronized, so setText()
* and setSelection() are performed somewhat atomic. Still fails for very
* quick typings.
*
* @param full
* the String you most likely want to enter
* @param contentLen
* the length of the content you have entered so far
*/
public synchronized void setEditorValue(String full, int contentLen) {
JTextField editorField = (JTextField) editField.getEditor()
.getEditorComponent();
editorField.setText(full);
editorField.setSelectionStart(contentLen);
editorField.setSelectionEnd(full.length());
}
/**
* Table model to directly access the changes made in the list.
*
* @author tobias_kuhn
*
*/
class ListDialogTableModel extends DefaultTableModel {
private static final long serialVersionUID = 6174982966160493569L;
/**
* Stores all the currently set parameters in the model
*/
private List<String> parameters;
/**
* Regenerates the parameters list from params
*
* @param params
*/
public void regenerate(String params) {
if (params == null) {
params = new String();
}
parameters = new ArrayList<String>();
for (String param : params.split(",")) {
String trimmed = param.trim();
if (!trimmed.isEmpty()) {
parameters.add(trimmed);
}
}
fireTableDataChanged();
}
/**
* inserts text
*
* @param text
*/
public void insert(String text) {
if (text.isEmpty()) {
return;
}
parameters.add(text);
fireTableDataChanged();
}
/**
* Removes the entry with index index. Does nothing, if index does not
* exist.
*
* @param index
*/
public void delete(int index) {
if ((index >= 0) && (index < parameters.size())) {
parameters.remove(index);
fireTableDataChanged();
}
}
/**
* @return the parameters list from parameters
*/
public String getContent() {
StringBuilder result = new StringBuilder();
for (int i = 0; i < parameters.size() - 1; i++) {
result.append(parameters.get(i) + ",");
}
if (parameters.size() > 0) {
result.append(parameters.get(parameters.size() - 1));
}
return result.toString();
}
@Override
public int getRowCount() {
if (parameters != null) {
return parameters.size();
} else {
return 0;
}
}
@Override
public int getColumnCount() {
return 1;
}
@Override
public String getColumnName(int column) {
return I18N.getInstance().getString("View.Parameter");
}
@Override
public Class<?> getColumnClass(int columnIndex) {
return String.class;
}
@Override
public Object getValueAt(int row, int column) {
if ((parameters != null) && (row >= 0) && (row < parameters.size())) {
return parameters.get(row);
} else {
return null;
}
}
@Override
public void setValueAt(Object aValue, int row, int column) {
if ((row >= 0) && (row < parameters.size())) {
parameters.set(row, aValue.toString());
} else {
parameters.add(aValue.toString());
fireTableDataChanged();
}
}
} /* ListDialogTableModel */
}