package com.limegroup.gnutella.gui; 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.KeyEvent; import java.util.Vector; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.DefaultListModel; import javax.swing.JButton; import javax.swing.JList; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTextField; import javax.swing.KeyStroke; import javax.swing.ListSelectionModel; import javax.swing.event.ListDataEvent; import javax.swing.event.ListDataListener; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; /** * A list editor is a GUI control for editing lists of strings. * It consists of a scrolling pane, a text field, and an add and * remove button.<p> * * You can add ListDataListener's to this. When you add/remove/modify * elements in the list, one of the * intervalAdded/intervalRemoved/contentsChanged * methods will be called on each listener, respectively. The source field * of the ListDataEvent passed to these methods will be the underlying * model passed to the constructor, an instance of Vector. The upper * and lower index of this event will always be the same, since only one * element is modified at a time. */ public class ListEditor extends JPanel { /** INVARIANT: model contains exactly the same elements as realModel. */ protected Vector /* of String */ model; protected DefaultListModel /* of String */ realModel; protected Vector /* of ListDataListener */ listeners; private static final int DEFAULT_COLUMNS=10; //size of editor protected JTextField editor; protected JButton addButton; protected JButton removeButton; protected JList list; /** True if I should append new items to end of the list; false if I should * add them to the end of the list. */ private boolean addTail=true; /** * Creates a new list editor with an empty underlying model. * New elements are added to the tail of the list by default. * @see setModel */ public ListEditor() { this(new Vector()); } /** * Creates a new list editor with the given underlying model. * New elements are added to the tail of the list by default. * @see setModel */ public ListEditor(Vector /* of String */ model) { this.listeners=new Vector(); setLayout(new GridBagLayout()); //Top half of the editor editor=new LimeTextField(""); editor.setColumns(DEFAULT_COLUMNS); editor.setPreferredSize(new Dimension(500, 20)); editor.setMaximumSize(new Dimension(500, 20)); GridBagConstraints gbc = new GridBagConstraints(); gbc.fill = GridBagConstraints.BOTH; gbc.anchor = GridBagConstraints.NORTHWEST; gbc.weightx = 1; add(editor, gbc); Action addAction = new AddAction(); addButton = new JButton(addAction); GUIUtils.bindKeyToAction(editor, KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), addAction); gbc = new GridBagConstraints(); gbc.anchor = GridBagConstraints.NORTHWEST; gbc.insets = new Insets(0, ButtonRow.BUTTON_SEP, 0, 0); add(addButton, gbc); Action removeAction = new RemoveAction(); removeButton = new JButton(removeAction); removeButton.setEnabled(false); gbc.gridwidth = GridBagConstraints.REMAINDER; add(removeButton, gbc); //Bottom half of the editor list=new JList(); list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); list.addListSelectionListener(new ListListener()); GUIUtils.bindKeyToAction(list, KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0), removeAction); JScrollPane scrollPane=new JScrollPane(list, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); setModel(model); scrollPane.setPreferredSize(new Dimension(500, 50)); scrollPane.setMaximumSize(new Dimension(500, 50)); gbc = new GridBagConstraints(); gbc.insets = new Insets(ButtonRow.BUTTON_SEP, 0, 0, 0); gbc.gridwidth = GridBagConstraints.REMAINDER; gbc.anchor = GridBagConstraints.NORTHWEST; gbc.fill = GridBagConstraints.BOTH; gbc.weighty = 1; add(scrollPane, gbc); } /** * @requires model not subsequently modified * @effects returns the underlying model of this, as a Vector of Strings. * Changes to this will <i>not</i> be reflected in the GUI. However, * changes made through the GUI will be reflected in the model. */ public Vector /* of String */ getModel() { return model; } /** * @requires model contains only Strings, model not subsequently modified * @modifies this * @effects sets the underlying model. This will be reflected in the * GUI. Modifications to this will <i>not</i> be reflected in the GUI. * However, changes made through the GUI will be reflected in the model. */ public synchronized void setModel(Vector /* of String */ model) { //Copy model into realModel. this.model=model; this.realModel=new DefaultListModel(); for (int i=0; i<model.size(); i++) realModel.addElement(model.get(i)); list.setModel(realModel); } /** * @modifies this * @effects if addTail is true, new elements will be added to * the end of the list. Otherwise, they'll be added to the * beginning. */ public void setAddTail(boolean addTail) { this.addTail=addTail; } /** Returns true if items are added to the tail of the list, * false otherwise. */ public boolean getAddTail() { return addTail; } /** * Removes an item from the list. */ public synchronized void removeItem(int i) { model.remove(i); realModel.remove(i); editor.setText(""); ListDataEvent event=new ListDataEvent(model, ListDataEvent.INTERVAL_REMOVED, i, i); for (int j=0; j<listeners.size(); j++) { ListDataListener listener=(ListDataListener)listeners.get(j); listener.intervalRemoved(event); } } /** * @modifies this * @effects adds listener to the list of listeners to be notified when * the underlying model changes. */ public synchronized void addListDataListener(ListDataListener listener) { listeners.add(listener); } /** * Enables the remove button if the selection of the lis is not empty, otherwise disables it. * Also sets the text of the currently selected value in the edit field. */ private class ListListener implements ListSelectionListener { public void valueChanged(ListSelectionEvent e) { if (list.isSelectionEmpty()) { removeButton.setEnabled(false); } else { removeButton.setEnabled(true); } //Put it in the editor. Object val=list.getSelectedValue(); if (val==null) return; else editor.setText((String)val); } } /** Someone tried to add something to the list. */ private class AddAction extends AbstractAction { public AddAction() { putValue(Action.NAME, GUIMediator.getStringResource("LIST_EDITOR_ADD_BUTTON_2")); } public void actionPerformed(ActionEvent e) { String text=editor.getText(); //If nothing in editor, ignore if (text.trim().equals("")) return; //If something is selected, replace it. Notify listeners. int i=list.getSelectedIndex(); if (i!=-1) { model.setElementAt(text,i); realModel.setElementAt(text,i); ListDataEvent event=new ListDataEvent(model, ListDataEvent.CONTENTS_CHANGED, i, i); for (int j=0; j<listeners.size(); j++) { ListDataListener listener=(ListDataListener)listeners.get(j); listener.contentsChanged(event); } } // Otherwise add text to the end/beginning of the list. // Notify listeners. else { int last; if (addTail) { //add to tail model.addElement(text); realModel.addElement(text); last=model.size()-1; } else { //add to head model.add(0, text); realModel.add(0, text); last=0; } ListDataEvent event=new ListDataEvent(model, ListDataEvent.INTERVAL_ADDED, last, last); for (int j=0; j<listeners.size(); j++) { ListDataListener listener=(ListDataListener)listeners.get(j); listener.intervalAdded(event); } } editor.setText(""); list.clearSelection(); } } private class RemoveAction extends AbstractAction { public RemoveAction() { putValue(Action.NAME, GUIMediator.getStringResource("LIST_EDITOR_REMOVE_BUTTON")); } /** Someone tried to remove something from the list. */ public void actionPerformed(ActionEvent e) { //If something is selected, remove it. Notify listeners. int i=list.getSelectedIndex(); if (i!=-1) removeItem(i); } } // Vector model=new Vector(); // model.addElement("britney"); // model.addElement("n'sync"); // model.addElement("money"); // model.addElement("spam"); // model.addElement("Republican"); // model.addElement("communist"); // ListEditor panel=new ListEditor(model); // panel.setAddTail(false); // panel.addListDataListener(new TestListener(model)); // JFrame frame = new JFrame("Spam Config Mock-up"); // frame.addWindowListener(new WindowAdapter() { // public void windowClosing(WindowEvent e) {System.exit(0);}}); // frame.getContentPane().add(panel, BorderLayout.NORTH); // frame.pack(); // frame.setVisible(true); // } // static class TestListener implements ListDataListener { // private Vector model; // public TestListener(Vector model) { // this.model=model; // } // private void verify(ListDataEvent e) { // Assert.that(e.getIndex0()==e.getIndex1()); // Assert.that(e.getSource()==model); // } // public void contentsChanged(ListDataEvent e) { // verify(e); Assert.that(e.getType()==ListDataEvent.CONTENTS_CHANGED); // System.out.println("You just modified element "+e.getIndex0()); // System.out.println("The new list is "+model.toString()); // } // public void intervalAdded(ListDataEvent e) { // verify(e); Assert.that(e.getType()==ListDataEvent.INTERVAL_ADDED); // System.out.println("You just added element "+e.getIndex0()); // System.out.println("The new list is "+model.toString()); // } // public void intervalRemoved(ListDataEvent e) { // verify(e); Assert.that(e.getType()==ListDataEvent.INTERVAL_REMOVED); // System.out.println("You just removed element "+e.getIndex0()); // System.out.println("The new list is "+model.toString()); // } // } }