/**
* ListChooser.java
*
* Created on 31.08.2009
*/
package forge.gui;
import javax.swing.*;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.AbstractList;
import java.util.List;
import static java.util.Arrays.asList;
import static java.util.Collections.unmodifiableList;
import static javax.swing.JOptionPane.*;
import static javax.swing.WindowConstants.DO_NOTHING_ON_CLOSE;
/**
* A simple class that shows a list of choices in a dialog. Two properties influence the behavior of a list
* chooser: minSelection and maxSelection. These two give the allowed number of selected items for the dialog to be
* closed.
* <ul>
* <li>If minSelection is 0, there will be a Cancel button.</li>
* <li>If minSelection is 0 or 1, double-clicking a choice will also close the dialog.</li>
* <li>If the number of selections is out of bounds, the "OK" button is disabled.</li>
* <li>The dialog was "committed" if "OK" was clicked or a choice was double clicked.</li>
* <li>The dialog was "canceled" if "Cancel" or "X" was clicked.</li>
* <li>If the dialog was canceled, the selection will be empty.</li>
* <li>
* </ul>
*
* @author Forge
* @version $Id: $
*/
public class ListChooser<T> {
//Data and number of choices for the list
private List<T> list;
private int minChoices, maxChoices;
//Decoration
private String title;
//Flag: was the dialog already shown?
private boolean called;
//initialized before; listeners may be added to it
private JList jList;
//Temporarily stored for event handlers during show
private JDialog d;
private JOptionPane p;
private Action ok, cancel;
/**
* <p>Constructor for ListChooser.</p>
*
* @param title a {@link java.lang.String} object.
* @param list a T object.
*/
public ListChooser(String title, T... list) {
this(title, 1, list);
}
/**
* <p>Constructor for ListChooser.</p>
*
* @param title a {@link java.lang.String} object.
* @param numChoices a int.
* @param list a T object.
*/
public ListChooser(String title, int numChoices, T... list) {
this(title, numChoices, numChoices, list);
}
/**
* <p>Constructor for ListChooser.</p>
*
* @param title a {@link java.lang.String} object.
* @param minChoices a int.
* @param maxChoices a int.
* @param list a T object.
*/
public ListChooser(String title, int minChoices, int maxChoices, T... list) {
this(title, null, minChoices, maxChoices, list);
}
/**
* <p>Constructor for ListChooser.</p>
*
* @param title a {@link java.lang.String} object.
* @param message a {@link java.lang.String} object.
* @param list a T object.
*/
public ListChooser(String title, String message, T... list) {
this(title, message, 1, list);
}
/**
* <p>Constructor for ListChooser.</p>
*
* @param title a {@link java.lang.String} object.
* @param message a {@link java.lang.String} object.
* @param numChoices a int.
* @param list a T object.
*/
public ListChooser(String title, String message, int numChoices, T... list) {
this(title, message, numChoices, numChoices, list);
}
/**
* <p>Constructor for ListChooser.</p>
*
* @param title a {@link java.lang.String} object.
* @param message a {@link java.lang.String} object.
* @param minChoices a int.
* @param maxChoices a int.
* @param list a T object.
*/
public ListChooser(String title, String message, int minChoices, int maxChoices, T... list) {
this(title, message, minChoices, maxChoices, asList(list));
}
/**
* <p>Constructor for ListChooser.</p>
*
* @param title a {@link java.lang.String} object.
* @param list a {@link java.util.List} object.
*/
public ListChooser(String title, List<T> list) {
this(title, 1, list);
}
/**
* <p>Constructor for ListChooser.</p>
*
* @param title a {@link java.lang.String} object.
* @param numChoices a int.
* @param list a {@link java.util.List} object.
*/
public ListChooser(String title, int numChoices, List<T> list) {
this(title, numChoices, numChoices, list);
}
/**
* <p>Constructor for ListChooser.</p>
*
* @param title a {@link java.lang.String} object.
* @param minChoices a int.
* @param maxChoices a int.
* @param list a {@link java.util.List} object.
*/
public ListChooser(String title, int minChoices, int maxChoices, List<T> list) {
this(title, null, minChoices, maxChoices, list);
}
/**
* <p>Constructor for ListChooser.</p>
*
* @param title a {@link java.lang.String} object.
* @param message a {@link java.lang.String} object.
* @param list a {@link java.util.List} object.
*/
public ListChooser(String title, String message, List<T> list) {
this(title, message, 1, list);
}
/**
* <p>Constructor for ListChooser.</p>
*
* @param title a {@link java.lang.String} object.
* @param message a {@link java.lang.String} object.
* @param numChoices a int.
* @param list a {@link java.util.List} object.
*/
public ListChooser(String title, String message, int numChoices, List<T> list) {
this(title, message, numChoices, numChoices, list);
}
/**
* <p>Constructor for ListChooser.</p>
*
* @param title a {@link java.lang.String} object.
* @param message a {@link java.lang.String} object.
* @param minChoices a int.
* @param maxChoices a int.
* @param list a {@link java.util.List} object.
*/
public ListChooser(String title, String message, int minChoices, int maxChoices, List<T> list) {
this.title = title;
this.minChoices = minChoices;
this.maxChoices = maxChoices;
this.list = unmodifiableList(list);
jList = new JList(new ChooserListModel());
ok = new CloseAction(OK_OPTION, "OK");
ok.setEnabled(minChoices == 0);
cancel = new CloseAction(CANCEL_OPTION, "Cancel");
Object[] options;
if (minChoices == 0) options = new Object[]{new JButton(ok), new JButton(cancel)};
else options = new Object[]{new JButton(ok)};
p = new JOptionPane(new Object[]{message, new JScrollPane(jList)}, QUESTION_MESSAGE, DEFAULT_OPTION,
null, options, options[0]);
jList.getSelectionModel().addListSelectionListener(new SelListener());
jList.addMouseListener(new DblListener());
}
/**
* <p>getChoices.</p>
*
* @return a {@link java.util.List} object.
*/
public List<T> getChoices() {
return list;
}
/**
* Returns the JList used in the list chooser. this is useful for registering listeners before showing the
* dialog.
*
* @return a {@link javax.swing.JList} object.
*/
public JList getJList() {
return jList;
}
/**
* Shows the dialog and returns after the dialog was closed.
*
* @return a boolean.
*/
public synchronized boolean show() {
if (called) throw new IllegalStateException("Already shown");
Integer value;
do {
d = p.createDialog(p.getParent(), title);
if (minChoices != 0) d.setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
jList.setSelectedIndex(0);
d.setVisible(true);
d.dispose();
value = (Integer) p.getValue();
if (value == null || value != OK_OPTION) jList.clearSelection();
//can't stop closing by ESC, so repeat if cancelled
} while (minChoices != 0 && value != OK_OPTION);
//this assert checks if we really don't return on a cancel if input is mandatory
assert minChoices == 0 || value == OK_OPTION;
called = true;
return value != null && value == OK_OPTION;
}
/**
* Returns if the dialog was closed by pressing "OK" or double clicking an option the last time
*
* @return a boolean.
*/
public boolean isCommitted() {
if (!called) throw new IllegalStateException("not yet shown");
return (Integer) p.getValue() == OK_OPTION;
}
/**
* Returns the selected indices as a list of integers
*
* @return a {@link java.util.List} object.
*/
public List<Integer> getSelectedIndices() {
if (!called) throw new IllegalStateException("not yet shown");
final int[] indices = jList.getSelectedIndices();
return new AbstractList<Integer>() {
@Override
public int size() {
return indices.length;
}
@Override
public Integer get(int index) {
return indices[index];
}
};
}
/**
* Returns the selected values as a list of objects. no casts are necessary when retrieving the objects.
*
* @return a {@link java.util.List} object.
*/
public List<T> getSelectedValues() {
if (!called) throw new IllegalStateException("not yet shown");
final Object[] selected = jList.getSelectedValues();
return new AbstractList<T>() {
@Override
public int size() {
return selected.length;
}
@SuppressWarnings("unchecked")
@Override
public T get(int index) {
return (T) selected[index];
}
};
}
/**
* Returns the (minimum) selected index, or -1
*
* @return a int.
*/
public int getSelectedIndex() {
if (!called) throw new IllegalStateException("not yet shown");
return jList.getSelectedIndex();
}
/**
* Returns the (first) selected value, or null
*
* @return a T object.
*/
@SuppressWarnings("unchecked")
public T getSelectedValue() {
if (!called) throw new IllegalStateException("not yet shown");
return (T) jList.getSelectedValue();
}
/**
* <p>commit.</p>
*/
private void commit() {
if (ok.isEnabled()) p.setValue(OK_OPTION);
}
private class ChooserListModel extends AbstractListModel {
private static final long serialVersionUID = 3871965346333840556L;
public int getSize() {
return list.size();
}
public Object getElementAt(int index) {
return list.get(index);
}
}
private class CloseAction extends AbstractAction {
private static final long serialVersionUID = -8426767786083886936L;
private int value;
public CloseAction(int value, String label) {
super(label);
this.value = value;
}
public void actionPerformed(ActionEvent e) {
p.setValue(value);
}
}
private class SelListener implements ListSelectionListener {
public void valueChanged(ListSelectionEvent e) {
int num = jList.getSelectedIndices().length;
ok.setEnabled(num >= minChoices && num <= maxChoices);
}
}
private class DblListener extends MouseAdapter {
@Override
public void mouseClicked(MouseEvent e) {
if (e.getClickCount() == 2) commit();
}
}
}