/*
* FieldSetComponent.java
*
* Created on February 10, 2005, 8:32 PM
*/
package net.sf.jabref.gui;
import java.awt.Component;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import javax.swing.*;
import javax.swing.event.ListDataListener;
import javax.swing.event.ListSelectionListener;
import net.sf.jabref.GUIGlobals;
import net.sf.jabref.Globals;
import net.sf.jabref.Util;
/**
*
* @author alver
*/
public class FieldSetComponent extends JPanel implements ActionListener {
protected Set<ActionListener> additionListeners = new HashSet<ActionListener>();
protected JList list;
protected JScrollPane sp = null;
protected DefaultListModel listModel;
protected JComboBox sel;
protected JTextField input;
protected JLabel title = null;
protected JButton add, remove, up=null, down=null;
protected GridBagLayout gbl = new GridBagLayout();
protected GridBagConstraints con = new GridBagConstraints();
protected boolean forceLowerCase, changesMade = false;
protected Set<ListDataListener> modelListeners = new HashSet<ListDataListener>();
/**
* Creates a new instance of FieldSetComponent, with preset selection
* values. These are put into a JComboBox.
*/
public FieldSetComponent(String title, List<String> fields, List<String> preset, boolean arrows, boolean forceLowerCase) {
this(title, fields, preset, "Add", "Remove", arrows, forceLowerCase);
}
/**
* Creates a new instance of FieldSetComponent without preset selection
* values. Replaces the JComboBox with a JTextField.
*/
public FieldSetComponent(String title, List<String> fields, boolean arrows, boolean forceLowerCase) {
this(title, fields, null, "Add", "Remove", arrows, forceLowerCase);
}
public FieldSetComponent(String title, List<String> fields, List<String> preset, String addText, String removeText,
boolean arrows, boolean forceLowerCase) {
this.forceLowerCase = forceLowerCase;
add = new JButton(Globals.lang(addText));
remove = new JButton(Globals.lang(removeText));
listModel = new DefaultListModel();
if (title != null)
this.title = new JLabel(title);
for (String field : fields)
listModel.addElement(field);
list = new JList(listModel);
list.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
// Set up GUI:
add.addActionListener(this);
remove.addActionListener(this);
setLayout(gbl);
con.insets = new Insets(1,1,1,1);
con.fill = GridBagConstraints.BOTH;
con.weightx = 1;
con.gridwidth = GridBagConstraints.REMAINDER;
if (this.title != null) {
gbl.setConstraints(this.title, con);
add(this.title);
}
con.weighty = 1;
sp = new JScrollPane(list, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
gbl.setConstraints(sp, con);
add(sp);
con.weighty = 0;
con.gridwidth = 1;
if (arrows) {
con.weightx = 0;
up = new JButton(GUIGlobals.getImage("up"));
down = new JButton(GUIGlobals.getImage("down"));
up.addActionListener(this);
down.addActionListener(this);
up.setToolTipText(Globals.lang("Move up"));
down.setToolTipText(Globals.lang("Move down"));
gbl.setConstraints(up, con);
add(up);
gbl.setConstraints(down, con);
add(down);
con.weightx = 0;
}
Component strut = Box.createHorizontalStrut(5);
gbl.setConstraints(strut, con);
add(strut);
con.weightx = 1;
con.gridwidth = GridBagConstraints.REMAINDER;
//Component b = Box.createHorizontalGlue();
//gbl.setConstraints(b, con);
//add(b);
//if (!arrows)
con.gridwidth = GridBagConstraints.REMAINDER;
gbl.setConstraints(remove, con);
add(remove);
con.gridwidth = 3;
con.weightx = 1;
if (preset != null) {
sel = new JComboBox(preset.toArray());
sel.setEditable(true);
//sel.addActionListener(this);
gbl.setConstraints(sel, con);
add(sel);
} else {
input = new JTextField(20);
input.addActionListener(this);
gbl.setConstraints(input, con);
add(input);
}
con.gridwidth = GridBagConstraints.REMAINDER;
con.weighty = 0;
con.weightx = 0.5;
con.gridwidth = 1;
gbl.setConstraints(add, con);
add(add);
}
public void setListSelectionMode(int mode) {
list.setSelectionMode(mode);
}
public void selectField(String fieldName) {
int idx = listModel.indexOf(fieldName);
if (idx >= 0)
list.setSelectedIndex(idx);
// Make sure it is visible:
JViewport viewport = sp.getViewport();
viewport.scrollRectToVisible(list.getCellBounds(idx, idx));
}
public String getFirstSelected() {
Object o = list.getSelectedValue();
if (o == null)
return null;
return (String)o;
}
public void setEnabled(boolean en) {
if (input != null)
input.setEnabled(en);
if (sel != null)
sel.setEnabled(en);
if (up != null) {
up.setEnabled(en);
down.setEnabled(en);
}
add.setEnabled(en);
remove.setEnabled(en);
}
public void setFields(List<String> fields) {
DefaultListModel newListModel = new DefaultListModel();
for (String field : fields)
newListModel.addElement(field);
this.listModel = newListModel;
for (Iterator<ListDataListener> i=modelListeners.iterator(); i.hasNext();)
newListModel.addListDataListener(i.next());
list.setModel(newListModel);
}
/**
* This method is called when a new field should be added to the list. Performs validation of the
* field.
*/
protected void addField(String s) {
s = s.trim();
if (forceLowerCase)
s = s.toLowerCase();
if (s.equals("") || listModel.contains(s))
return;
String testString = Util.checkLegalKey(s);
if (!testString.equals(s) || (s.indexOf('&') >= 0)) {
// Report error and exit.
JOptionPane.showMessageDialog(this, Globals.lang("Field names are not allowed to contain white space or the following "
+"characters")+": # { } ~ , ^ &",
Globals.lang("Error"), JOptionPane.ERROR_MESSAGE);
return;
}
addFieldUncritically(s);
}
/**
* This method adds a new field to the list, without any regard to validation. This method can be
* useful for classes that overrides addField(s) to provide different validation.
*/
protected void addFieldUncritically(String s) {
listModel.addElement(s);
changesMade = true;
for (Iterator<ActionListener> i=additionListeners.iterator(); i.hasNext();) {
i.next().actionPerformed(new ActionEvent(this, 0, s));
}
}
protected void removeSelected() {
int[] selected = list.getSelectedIndices();
if (selected.length > 0)
changesMade = true;
for (int i=0; i<selected.length; i++)
listModel.removeElementAt(selected[selected.length-1-i]);
}
public void activate() {
sel.requestFocus();
}
/**
* Returns true if there have been changes to the field list. Reports true
* if changes have been made, regardless of whether the changes cancel each other.
*/
public boolean changesMade() {
return changesMade;
}
/**
* Return the current list.
*/
@SuppressWarnings("unchecked")
public List<String> getFields() {
Object[] o = listModel.toArray();
return (List)java.util.Arrays.asList(o);
}
/**
* Add a ListSelectionListener to the JList component displayed as part of this component.
*/
public void addListSelectionListener(ListSelectionListener l) {
list.addListSelectionListener(l);
}
/**
* Adds an ActionListener that will receive events each time a field is added. The ActionEvent
* will specify this component as source, and the added field as action command.
*/
public void addAdditionActionListener(ActionListener l) {
additionListeners.add(l);
}
public void removeAdditionActionListener(ActionListener l) {
additionListeners.remove(l);
}
public void addListDataListener(ListDataListener l) {
listModel.addListDataListener(l);
modelListeners.add(l);
}
/**
* If a field is selected in the list, move it dy positions.
*/
public void move(int dy) {
int oldIdx = list.getSelectedIndex();
if (oldIdx < 0)
return;
Object o = listModel.get(oldIdx);
// Compute the new index:
int newInd = Math.max(0, Math.min(listModel.size()-1, oldIdx+dy));
listModel.remove(oldIdx);
listModel.add(newInd, o);
list.setSelectedIndex(newInd);
}
public void actionPerformed(ActionEvent e) {
Object src = e.getSource();
if (src == add) {
// Selection has been made, or add button pressed:
if ((sel != null) && (sel.getSelectedItem() != null)) {
String s = sel.getSelectedItem().toString();
addField(s);
} else if ((input != null) && !input.getText().equals("")) {
addField(input.getText());
}
}
else if (src == input) {
addField(input.getText());
}
else if (src == remove) {
// Remove button pressed:
removeSelected();
}
else if (src == sel) {
if (e.getActionCommand().equals("comboBoxChanged") && (e.getModifiers() == 0))
// These conditions signify arrow key navigation in the dropdown list, so we should
// not react to it. I'm not sure if this is well defined enough to be guaranteed to work
// everywhere.
return;
String s = sel.getSelectedItem().toString();
addField(s);
sel.getEditor().selectAll();
}
else if (src == up) {
move(-1);
}
else if (src == down) {
move(1);
}
}
}