/*
StringSelector.java
A two list box for adding strings to and/or removing strings from
lists.
Created: 10 October 1997
Module By: Mike Mulvaney, Jonathan Abbey
-----------------------------------------------------------------------
Ganymede Directory Management System
Copyright (C) 1996-2013
The University of Texas at Austin
Ganymede is a registered trademark of The University of Texas at Austin
Contact information
Web site: http://www.arlut.utexas.edu/gash2
Author Email: ganymede_author@arlut.utexas.edu
Email mailing list: ganymede@arlut.utexas.edu
US Mail:
Computer Science Division
Applied Research Laboratories
The University of Texas at Austin
PO Box 8029, Austin TX 78713-8029
Telephone: (512) 835-3200
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package arlut.csd.JDataComponent;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.rmi.RemoteException;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.Vector;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.SwingConstants;
import javax.swing.border.BevelBorder;
import javax.swing.border.Border;
import javax.swing.border.CompoundBorder;
import javax.swing.border.EmptyBorder;
import javax.swing.border.LineBorder;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import arlut.csd.Util.Compare;
import arlut.csd.Util.TranslationService;
import arlut.csd.Util.VectorUtils;
/*------------------------------------------------------------------------------
class
StringSelector
------------------------------------------------------------------------------*/
/**
* <p>A two-paneled GUI component for adding or removing strings
* and/or labeled objects from a list, with an optional list of
* available strings and/or objects to choose from.</p>
*
* <p>StringSelector consists of one or (optionally) two {@link
* arlut.csd.JDataComponent.JstringListBox JstringListBox} panels and
* allows the user to move values back and forth between the two
* panels. Pop-up menus can be attached to each panel, allowing the
* user to command the client to view or edit objects referenced in
* either panel. Objects in both panels are sorted alphabetically by
* label.</p>
*
* <p>The setCallback() method takes an object implementing the {@link
* arlut.csd.JDataComponent.JsetValueCallback JsetValueCallback}
* interface in order to provide live notification of changes
* performed by the user. The JsetValueCallback implementation is
* given the opportunity to approve any change made by the user before
* the GUI is updated to show the change. The JsetValueCallback
* interface is also used to pass pop-up menu commands to the
* client.</p>
*
* @see JstringListBox
* @see JsetValueCallback
*
* @author Mike Mulvaney, Jonathan Abbey
*/
public class StringSelector extends JPanel implements ActionListener, JsetValueCallback, FocusListener {
static final boolean debug = false;
/**
* TranslationService object for handling string localization in the
* Ganymede system.
*/
static final TranslationService ts = TranslationService.getTranslationService("arlut.csd.JDataComponent.StringSelector");
// --
JsetValueCallback
my_callback;
JButton
add,
remove;
JstringListBox
in,
out = null;
JScrollPane
inScrollPane = null,
outScrollPane = null;
JPanel
inPanel = new JPanel(),
outPanel = new JPanel();
JButton
inTitle = new JButton(),
outTitle = new JButton();
String
org_in = ts.l("global.items_in"), // "Members"
org_out = ts.l("global.items_out"); // "Available"
JButton
addCustom;
JstringField
custom = null;
Container
parent;
Border
focusedBorder = new LineBorder(Color.black, 1),
unfocusedBorder = BorderFactory.createEmptyBorder(1,1,1,1);
private boolean
replacingValue = false,
editable,
canChoose,
mustChoose;
private int
minRows = -1,
maxRows = -1,
currentRows = -1;
/* -- */
/**
* Fully specified Constructor for StringSelector
*
* @param parent AWT container that the StringSelector will be contained in.
* @param editable If false, this string selector is for display only
* @param canChoose Choice must be made from vector of choices
* @param mustChoose Vector of choices is available
*/
public StringSelector(Container parent, boolean editable, boolean canChoose,
boolean mustChoose)
{
if (debug)
{
System.err.println("-Adding new StringSelector-");
}
setBorder(new javax.swing.border.EtchedBorder());
this.parent = parent;
this.editable = editable;
this.canChoose = canChoose;
this.mustChoose = mustChoose;
setLayout(new BorderLayout());
// lists holds the outPanel and inPanel.
GridBagLayout
gbl = new GridBagLayout();
GridBagConstraints
gbc = new GridBagConstraints();
JPanel lists = new JPanel();
lists.setLayout(gbl);
// Set up the inPanel, which holds the in list and button
// JstringListBox does the sorting
in = new JstringListBox();
in.setCallback(this);
in.addFocusListener(this);
this.currentRows = in.getVisibleRowCount();
BevelBorder
bborder = new BevelBorder(BevelBorder.RAISED);
inPanel.setBorder(bborder);
inPanel.setLayout(new BorderLayout());
inScrollPane = new JScrollPane(in);
inScrollPane.setViewportBorder(unfocusedBorder);
inPanel.add("Center", inScrollPane);
inTitle.setText(org_in.concat(" : 0"));
inTitle.addActionListener(this);
inTitle.addFocusListener(this);
inPanel.add("North", inTitle);
if (editable)
{
if (canChoose)
{
// "Remove >>"
remove = new JButton(ts.l("global.remove_and_remember_button"));
}
else
{
// "Remove"
remove = new JButton(ts.l("global.remove_and_forget_button"));
}
remove.setEnabled(false);
remove.setOpaque(true);
remove.setActionCommand("Remove");
remove.addActionListener(this);
remove.addFocusListener(this);
inPanel.add("South", remove);
}
gbc.fill = gbc.BOTH;
gbc.gridwidth = 1;
gbc.weightx = 1.0;
gbc.weighty = 1.0;
gbc.gridx = 0;
gbc.gridy = 0;
gbl.setConstraints(inPanel, gbc);
lists.add(inPanel);
// JstringListBox does the sorting
if (editable && canChoose)
{
// Set up the outPanel.
// If we need an out box, build it now.
outTitle.setText(org_out.concat(": 0"));
// outTitle.setHorizontalAlignment( SwingConstants.LEFT );
// outTitle.setMargin( new Insets(0,0,0,0) );
outTitle.addActionListener(this);
outTitle.addFocusListener(this);
out = new JstringListBox();
out.setCallback(this);
out.addFocusListener(this);
outScrollPane = new JScrollPane(out);
outScrollPane.setViewportBorder(unfocusedBorder);
// "<< Add"
add = new JButton(ts.l("global.add_choice_button"));
add.setEnabled(false);
add.setOpaque(true);
add.setActionCommand("Add");
add.addActionListener(this);
add.addFocusListener(this);
outPanel.setBorder(bborder);
outPanel.setLayout(new BorderLayout());
outPanel.add("Center", outScrollPane);
outPanel.add("North", outTitle);
outPanel.add("South", add);
gbc.fill = gbc.BOTH;
gbc.gridwidth = 1;
gbc.weightx = 1.0;
gbc.weighty = 1.0;
gbc.gridx = 1;
gbc.gridy = 0;
gbl.setConstraints(outPanel, gbc);
lists.add(outPanel);
}
add("Center", lists);
if (editable)
{
custom = new JstringField();
custom.transferFocusOnEntry = false;
custom.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
addCustom.doClick();
}
});
custom.addFocusListener(this);
JPanel customP = new JPanel();
customP.setLayout(new BorderLayout());
customP.add("Center", custom);
if (!(mustChoose && out == null))
{
// "Add"
addCustom = new JButton(ts.l("global.add_button"));
addCustom.setEnabled(false);
addCustom.setActionCommand("AddNewString");
addCustom.addActionListener(this);
addCustom.addFocusListener(this);
customP.add("West", addCustom);
// we only want this add button to be active when the user
// has entered something in the text field. Some users
// have been confused by the add button just sitting there
// active.
custom.getDocument().addDocumentListener(new DocumentListener()
{
public void changedUpdate(DocumentEvent x) {}
public void insertUpdate(DocumentEvent x)
{
if (x.getDocument().getLength() > 0)
{
addCustom.setEnabled(true);
}
}
public void removeUpdate(DocumentEvent x)
{
if (x.getDocument().getLength() == 0)
{
addCustom.setEnabled(false);
}
}
});
}
// if we know they can only type things from the list,
// implement choice completion
if (mustChoose)
{
// XXX
//
// Need to radically rework this keyadapter so it
// behaves better with autocomplete.. we'll need it to
// function in such a way that it doesn't stop being
// involved as soon as the autocomplete is performed. If
// the user continues typing characters that match the
// autocomplete, nothing should happen other than
// advancing the cursor so that it remains after the last
// character they typed.
//
// if the user uses the cursor keys to move the cursor
// manually, we will 'freeze' the autocomplete entry and
// let them do normal processing. We'll also want to do
// this if the mouse is used to set the cursor position.
//
// If the user hits a non-cursor key that doesn't match
// the autocomplete (AND we're not in mustChoose mode?),
// we should erase the remainder of the autocomplete match
// and let the user type whatever they want at that
// point... ?
//
// XXX
custom.addKeyListener(new KeyAdapter()
{
public void keyReleased(KeyEvent ke)
{
int curLen;
String curVal;
curVal = custom.getText().substring(0, custom.getCaretPosition());
if (curVal != null)
{
curLen = curVal.length();
}
else
{
curLen = 0;
}
int keyCode = ke.getKeyCode();
if (keyCode == KeyEvent.VK_DELETE || keyCode == KeyEvent.VK_BACK_SPACE)
{
custom.select(custom.getCaretPosition(), custom.getCaretPosition());
return;
}
if (ke.isActionKey() || ke.getKeyChar() == KeyEvent.CHAR_UNDEFINED)
{
return;
}
if (curLen > 0)
{
listHandle item;
int matching = 0;
String matchingItem = null;
Enumeration en = out.model.elements();
while (en.hasMoreElements())
{
item = (listHandle) en.nextElement();
if (item.toString().equals(curVal))
{
// they've typed the full thing here, stop.
out.setSelectedLabel(matchingItem, true);
custom.setText(curVal);
return;
}
else if (item.toString().startsWith(curVal))
{
matching++;
matchingItem = item.toString();
}
}
if (matching == 1)
{
out.setSelectedLabel(matchingItem, true);
custom.setText(matchingItem);
custom.moveCaretPosition(curLen); // move the caret to select the text
return;
}
}
}
});
}
add("South", customP);
}
// provide a small default width
setCellWidth(50);
invalidate();
parent.validate();
if (debug)
{
System.err.println("Done creating ss");
}
}
// Public methods ------------------------------------------------------------
/**
* <p>This method sets the width of the in and out rows.</p>
*
* @param width How many columns wide should each box be? If <=
* 0, the StringSelector will auto-size the columns
*/
public void setCellWidth(int width)
{
in.setCellWidth(width);
if (out != null)
{
out.setCellWidth(width);
}
invalidate();
parent.validate();
}
/**
* <p>This method sets the width of the in and out rows.</p>
*
* @param template A string to use as the required size of the cells
* in the in and out boxes.
*/
public void setCellWidth(String template)
{
in.setCellWidth(template);
if (out != null)
{
out.setCellWidth(template);
}
invalidate();
parent.validate();
}
/**
* <p>This method sets the titles for the in and out boxes. The
* StringSelector will show these titles, followed by a colon and
* the current count of elements in the in and/or out boxes.</p>
*/
public void setTitles(String inString, String outString)
{
org_in = inString;
org_out = outString;
}
/**
* <p>This method attaches popup menus to the in box and out
* box.</p>
*
* <p>The two popup menus must be distinct, so that they can be
* attached to separate JstringListBoxes in the StringSelector,
* without having each JstringListBox listening to the same popup
* menu.</p>
*/
public void setPopups(JPopupMenu inPopup, JPopupMenu outPopup)
{
if (inPopup != null && inPopup == outPopup)
{
throw new IllegalArgumentException("Need two different JPopupMenus");
}
in.registerPopupMenu(inPopup);
if (out != null)
{
out.registerPopupMenu(outPopup);
}
}
/**
* <p>Returns true if this StringSelector is editable.</p>
*
* <p>Non-editable StringSelector's only have the chosen list.
* Editable StringSelector's have both the chosen and available
* lists.</p>
*/
public boolean isEditable()
{
return editable;
}
/**
* Update the StringSelector.
*
* @param available A Vector of {@link java.lang.String} or {@link
* arlut.csd.JDataComponent.listHandle} objects that may be chosen by the user.
* @param sortAvailable If true, the available strings or handles should be sorted
* for presentation.
* @param availComparator The {@link java.util.Comparator} object for evaluating
* the sort order for items in the available Vector.
* @param chosen A Vector of {@link java.lang.String} or {@link
* arlut.csd.JDataComponent.listHandle} objects that have previously
* been chosen for membership.
* @param sortChosen If true, the previously chosen strings or handles should be sorted
* for presentation.
* @param chosenComparator The {@link java.util.Comparator} object for evaluating
* the sort order for items in the previously chosen Vector.
*/
public synchronized void update(Vector available, boolean sortAvailable, Comparator availComparator,
Vector chosen, boolean sortChosen, Comparator chosenComparator)
{
if (available == null)
{
if (out != null)
{
out.model.removeAllElements();
}
}
// If there is no out box, then we don't need to worry about available stuff
if (out != null)
{
try
{
out.load(VectorUtils.difference(available,chosen), -1, sortAvailable, availComparator);
}
catch (Exception e)
{
e.printStackTrace();
throw new RuntimeException(e);
}
}
try
{
in.load(chosen, -1, sortChosen, chosenComparator);
}
catch (Exception e)
{
e.printStackTrace();
throw new RuntimeException(e);
}
updateTitles();
recalcSize();
}
/**
* This method is used to change the dynamically label of an object in this
* StringSelector.
*/
public synchronized void relabelObject(Object object, String newLabel)
{
in.relabelObject(object, newLabel);
if (out != null)
{
out.relabelObject(object, newLabel);
}
}
/**
* Change the text on the add button.
*/
public void setButtonText(String text)
{
if (addCustom == null)
{
return;
}
addCustom.setText(text);
validate();
}
/**
* This method sets the minimum number of rows this StringSelector
* should display. When set, this StringSelector will automatically
* resize itself between the value of minRows and maxRows.
*/
public void setMinimumRowCount(int numRows)
{
this.minRows = numRows;
if (this.maxRows == -1)
{
setMaximumRowCount(8); // default is 8
}
}
/**
* This method sets the maximum number of rows this StringSelector
* should display. When set, this StringSelector will automatically
* resize itself between the value of minRows and maxRows.
*/
public void setMaximumRowCount(int numRows)
{
this.maxRows = numRows;
if (this.minRows == -1)
{
setMinimumRowCount(4); // default is 4
}
}
/**
* This method adjusts the number of rows shown in this StringSelector.
*/
public void setVisibleRowCount(int numRows)
{
this.setVisibleRowCount(numRows, true);
}
/**
* This method adjusts the number of rows shown in this
* StringSelector. If refresh is true, the StringSelector will be
* immediately resized.
*/
public void setVisibleRowCount(int numRows, boolean refresh)
{
this.currentRows = numRows;
in.setVisibleRowCount(this.currentRows);
if (out != null)
{
out.setVisibleRowCount(this.currentRows);
}
if (refresh)
{
invalidate();
parent.validate();
}
}
/**
* <p>Connects this StringSelector to an implementaton of the
* {@link arlut.csd.JDataComponent.JsetValueCallback JsetValueCallback} interface
* in order to provide live notification of changes performed by the user. The
* JsetValueCallback implementation is given the opportunity to approve any change
* made by the user before the GUI is updated to show the change. The JsetValueCallback
* interface is also used to pass pop-up menu commands to the client.</p>
*
* <p>StringSelector uses the following subclasses of
* {@link arlut.csd.JDataComponent.JValueObject JValueObject} to pass status updates to
* the callback.
*
* <ul>
* <li>{@link arlut.csd.JDataComponent.JParameterValueObject} Action from a PopupMenu. The Parameter is the ActionCommand
* string for the pop-up menu item selected, and the value is the object
* (or string if no object defined) associated with the item selected when the pop-up menu was fired.</li>
* <li>{@link arlut.csd.JDataComponent.JAddValueObject} Object has been added to the selected list. Value is the object (or string) added.</li>
* <li>{@link arlut.csd.JDataComponent.JDeleteValueObject} Object has been removed from chosen list. Value is the object (or string) removed.</li>
* <li>{@link arlut.csd.JDataComponent.JErrorValueObject} Something went wrong. Value is the error message to be displayed to the user in whatever
* fashion is appropriate.</li>
* </ul>
*
* @see JsetValueCallback
* @see JValueObject
*/
public void setCallback(JsetValueCallback parent)
{
my_callback = parent;
}
/**
* <p>Returns a Vector of {@link arlut.csd.JDataComponent.listHandle listHandle}
* objects corresponding to the currently selected members.</p>
*/
public Vector getChosenHandles()
{
return in.getHandles();
}
/**
* <p>Returns a Vector of Strings corresponding to the currently
* selected members.</p>
*/
public Vector getChosenStrings()
{
Vector inVector = in.getHandles();
Vector result = new Vector();
if (inVector == null)
{
return result;
}
for (int i = 0; i < inVector.size(); i++)
{
listHandle handle = (listHandle) inVector.elementAt(i);
result.addElement(handle.toString());
}
return result;
}
// ActionListener methods -------------------------------------------------
/**
* This method handles events from the Add and Remove buttons, and
* from hitting enter and/or from loss of focus in the custom
* JstringField.
*/
public void actionPerformed(ActionEvent e)
{
if (!editable)
{
return;
}
if (e.getSource() == inTitle)
{
in.setSelectionInterval( 0, in.getModel().getSize()-1 );
if (out != null)
{
out.clearSelection();
}
}
if (e.getSource() == outTitle)
{
out.setSelectionInterval( 0, out.getModel().getSize()-1 );
in.clearSelection();
}
if (e.getActionCommand().equals("Add"))
{
if (debug)
{
System.err.println("StringSelector: add Action");
}
addItems();
}
else if (e.getActionCommand().equals("Remove"))
{
if (debug)
{
System.err.println("StringSelector: remove Action");
}
removeItems();
}
else if (e.getActionCommand().equals("AddNewString"))
{
if (debug)
{
System.err.println("StringSelector: addNewString Action");
}
addNewString();
}
}
// JsetValueCallback methods -------------------------------------------------
public boolean setValuePerformed(JValueObject o)
{
if (o.getSource() == custom)
{
if (!editable)
{
return false;
}
addCustom.doClick();
return true;
}
else if (o instanceof JParameterValueObject) // from the popup menu
{
if (my_callback != null)
{
try
{
my_callback.setValuePerformed(new JParameterValueObject(this,
o.getIndex(),
o.getValue(),
o.getParameter()));
}
catch (java.rmi.RemoteException rx)
{
System.err.println("could not setValuePerformed from StringSelector: " + rx);
}
return true;
}
}
else if (o.getSource() == in)
{
if (!editable)
{
return false;
}
if (o instanceof JInsertValueObject)
{
remove.doClick();
return true;
}
else if (o instanceof JAddValueObject) // selection
{
if (add != null)
{
add.setEnabled(false);
}
if (remove != null)
{
remove.setEnabled(true);
}
if (out != null)
{
out.clearSelection();
}
return true;
}
}
else if (o.getSource() == out)
{
if (o instanceof JInsertValueObject)
{
add.doClick();
return true;
}
else if (o instanceof JAddValueObject)
{
add.setEnabled(true);
remove.setEnabled(false);
in.clearSelection();
custom.setText("");
return true;
}
}
else
{
if (!editable)
{
return false;
}
if (debug)
{
System.err.println("set value in stringSelector");
}
System.err.println("Unknown object generated setValuePerformed in stringSelector.");
return false;
}
return false; // should never really get here.
}
// FocusListener methods ------------------------------------------------------
public void focusLost(FocusEvent e)
{
Object source = e.getSource();
if (source == in)
{
inScrollPane.setViewportBorder(unfocusedBorder);
this.repaint();
}
else if (source == out)
{
outScrollPane.setViewportBorder(unfocusedBorder);
this.repaint();
}
}
public void focusGained(FocusEvent e)
{
Object source = e.getSource();
if (source == in)
{
inScrollPane.setViewportBorder(focusedBorder);
this.repaint();
}
else if (source == out)
{
outScrollPane.setViewportBorder(focusedBorder);
this.repaint();
}
JComponent parent = (JComponent) this.getParent();
if (parent != null)
{
parent.scrollRectToVisible(this.getBounds());
}
}
// Private methods ------------------------------------------------------------
/**
* <p>This method moves one or more selected items from the out list to the in list.</p>
*/
private void addItems()
{
boolean ok = false;
Vector handles;
/* -- */
if (out == null)
{
System.err.println("Can't figure out the handle. No out box to get it from.");
return;
}
handles = out.getSelectedHandles();
if (handles == null)
{
System.err.println("Error.. got addItem with outSelected == null");
return;
}
if (handles.size() > 1)
{
if (my_callback != null)
{
Vector objVector = new Vector(handles.size());
for (int i = 0; i < handles.size(); i++)
{
objVector.addElement(((listHandle) handles.elementAt(i)).getObject());
}
ok = false; // if we get an exception, that's not okay
try
{
ok = my_callback.setValuePerformed(new JAddVectorValueObject(this, objVector));
}
catch (RemoteException rx)
{
throw new RuntimeException("Could not setValuePerformed: " + rx);
}
}
else
{
ok = true;
}
if (ok)
{
// if replacingValue was set, we'll have been refreshed by
// a callback from the setValuePerformed() call, above,
// before we get to here, so don't do anything to further
// mess with our state.
if (replacingValue)
{
replacingValue = false;
return;
}
in.clearSelection();
for (int i = 0; i < handles.size(); i++)
{
putItemIn((listHandle)handles.elementAt(i));
}
}
else
{
if (debug)
{
System.err.println("setValuePerformed returned false");
}
}
}
else
{
if (my_callback != null)
{
ok = false;
try
{
ok = my_callback.setValuePerformed(new JAddValueObject(this,
((listHandle)handles.elementAt(0)).getObject()));
}
catch (RemoteException rx)
{
throw new RuntimeException("Could not setValuePerformed: " + rx);
}
}
else
{
ok = true;
}
if (ok)
{
if (replacingValue)
{
replacingValue = false;
return;
}
in.clearSelection();
putItemIn((listHandle)handles.elementAt(0));
}
else
{
if (debug)
{
System.err.println("setValuePerformed returned false");
}
}
}
updateTitles();
recalcSize();
invalidate();
parent.validate();
}
/**
* <p>This method moves one or more selected items from the in list
* to the out list.</p>
*/
private void removeItems()
{
Vector handles;
boolean ok;
/* -- */
handles = in.getSelectedHandles();
if (handles == null)
{
System.err.println("Error.. got removeItem with inSelected == null");
return;
}
if (handles.size() > 1)
{
if (my_callback != null)
{
Vector objVector = new Vector(handles.size());
for (int i = 0; i < handles.size(); i++)
{
objVector.addElement(((listHandle) handles.elementAt(i)).getObject());
}
ok = false;
try
{
ok = my_callback.setValuePerformed(new JDeleteVectorValueObject(this, objVector));
}
catch (RemoteException rx)
{
throw new RuntimeException("Could not setValuePerformed: " + rx);
}
}
else
{
ok = true;
}
if (ok)
{
if (replacingValue)
{
replacingValue = false;
return;
}
for (int i = 0; i < handles.size(); i++)
{
takeItemOut((listHandle)handles.elementAt(i));
}
}
else
{
if (debug)
{
System.err.println("setValuePerformed returned false");
}
}
}
else
{
if (my_callback != null)
{
ok = false;
try
{
ok = my_callback.setValuePerformed(new JDeleteValueObject(this,
((listHandle)handles.elementAt(0)).getObject()));
}
catch (RemoteException rx)
{
throw new RuntimeException("Could not setValuePerformed: " + rx);
}
}
else
{
ok = true;
}
if (ok)
{
if (replacingValue)
{
replacingValue = false;
return;
}
takeItemOut((listHandle)handles.elementAt(0));
}
else
{
if (debug)
{
System.err.println("setValuePerformed returned false");
}
}
}
updateTitles();
recalcSize();
invalidate();
parent.validate();
}
/**
* <p>This method moves a single item from the outlist to the inlist.</p>
*
* <p>This method is the opposite of
* {@link arlut.csd.JDataComponent.StringSelector#takeItemOut(arlut.csd.JDataComponent.listHandle) takeItemOut}.</p>
*
*/
private void putItemIn(listHandle item)
{
if (debug)
{
System.err.println("Add: " + item);
}
if (!editable)
{
return;
}
if (canChoose)
{
if (out != null)
{
out.removeItem(item);
}
if (debug)
{
System.err.println("Adding handle");
}
// We only want to put it in if it's not already there.
// Sometimes this happens in Ganymede if we update a field
// before we are changing. It happens like this: the "add"
// button is clicked. Then the return value decides to update
// this field, which loads the value in the in box. Then it
// returns true, and then the value is already in there. So
// if we add it again, we get two of them. Got it?
if (!in.containsItem(item))
{
in.addItem(item);
}
if (debug)
{
System.err.println("Done Adding handle");
}
}
else
{
throw new RuntimeException("Can't add something from the out box to a non-canChoose StringSelector!");
}
}
/**
* <p>This method moves a single item from the outlist to the inlist.</p>
*
* <p>This method is the opposite of
* {@link arlut.csd.JDataComponent.StringSelector#putItemIn(arlut.csd.JDataComponent.listHandle) putItemIn}.</p>
*/
private void takeItemOut(listHandle item)
{
if (debug)
{
System.err.println("Remove." + item);
}
if (!editable)
{
return;
}
in.removeItem(item);
// If the item is already in there, don't add it.
if ((out != null) && (! out.containsItem(item)))
{
out.addItem(item);
}
remove.setEnabled(false);
recalcSize();
in.invalidate();
if (out != null)
{
out.invalidate();
}
invalidate();
if (parent.getParent() != null)
{
parent.getParent().validate();
}
else
{
parent.validate();
}
}
/**
* <p>This method handles the processing to add an item entered in
* the custom text entry box.</p>
*/
private void addNewString()
{
String inputText = custom.getText();
if (inputText.equals("") || in.containsLabel(inputText))
{
if (debug)
{
System.err.println("That one's already in there. No soup for you!");
}
return;
}
if (debug)
{
System.err.println("addNewString(\"" + inputText + "\")");
}
if (out != null && mustChoose)
{
// Check to see if it is in there
if (debug)
{
System.err.println("Checking to see if this is a viable option");
}
if (out.containsLabel(inputText))
{
out.setSelectedLabel(inputText);
listHandle handle = out.getSelectedHandle();
boolean ok;
if (my_callback != null)
{
ok = false;
try
{
ok = my_callback.setValuePerformed(new JAddValueObject(this, handle.getObject()));
}
catch (RemoteException rx)
{
throw new RuntimeException("Could not setValuePerformed: " + rx);
}
}
else
{
ok = true;
}
if (ok)
{
if (replacingValue)
{
custom.setText("");
replacingValue = false;
return;
}
in.clearSelection();
putItemIn(handle);
custom.setText("");
}
}
else //It's not in the outbox.
{
if (my_callback != null)
{
try
{
// "Sorry, you must enter strings from the list of
// available choices. Please choose from the
// available list."
my_callback.setValuePerformed(new JErrorValueObject(this,
ts.l("addNewString.bad_choice")));
}
catch (RemoteException rx)
{
throw new RuntimeException("Could not tell parent what is wrong: " + rx);
}
}
}
}
else
{
// not mustChoose, so you can stick it in there. But see,
// I need to see if it's in there first, because if it is,
// IF IT IS, then you have to move the String over. HA!
if ((out != null) && out.containsLabel(inputText))
{
out.setSelectedLabel(inputText);
listHandle handle = out.getSelectedHandle();
boolean ok;
if (my_callback != null)
{
ok = false;
try
{
ok = my_callback.setValuePerformed(new JAddValueObject(this, handle.getObject()));
}
catch (RemoteException rx)
{
throw new RuntimeException("Could not setValuePerformed: " + rx);
}
}
else
{
ok = true;
}
if (ok)
{
if (replacingValue)
{
custom.setText("");
replacingValue = false;
return;
}
in.clearSelection();
putItemIn(handle);
custom.setText("");
}
}
else
{
// Not in the out box, send up the String as-is, with no attached data
if (debug)
{
System.err.println("addNewString() -- stand alone");
}
boolean ok;
if (my_callback != null)
{
ok = false;
try
{
ok = my_callback.setValuePerformed(new JAddValueObject(this, inputText));
}
catch (RemoteException rx)
{
throw new RuntimeException("Could not setValuePerformed: " + rx);
}
}
else
{
ok = true;
}
if (ok)
{
if (replacingValue)
{
custom.setText("");
replacingValue = false;
if (debug)
{
System.err.println("addNewString() -- replacing");
}
return;
}
if (debug)
{
System.err.println("addNewString() -- adding");
}
in.clearSelection();
in.addItem(new listHandle(inputText, inputText));
custom.setText("");
}
else
{
if (debug)
{
System.err.println("setValuePerformed returned false.");
}
}
}
validate();
}
updateTitles();
recalcSize();
in.invalidate();
invalidate();
validate();
}
/**
* <p>This method handles updating the item counts in the in and out displays.</p>
*/
private void updateTitles()
{
inTitle.setText(org_in.concat(" : " + in.getSizeOfList()));
if (out != null)
{
outTitle.setText(org_out.concat(" : " + out.getSizeOfList()));
}
}
/**
* This method handles resizing the StringSelector if appropriate.
*/
private void recalcSize()
{
if (debug)
{
System.err.println("recalcSize()");
}
if (minRows == -1 || maxRows == -1)
{
return;
}
int lowerBound = minRows;
if (in.getSizeOfList() > lowerBound)
{
lowerBound = in.getSizeOfList();
}
if (out != null)
{
int halfOptions = out.getSizeOfList() / 2;
if (halfOptions > lowerBound)
{
lowerBound = halfOptions;
}
}
if (lowerBound > maxRows)
{
lowerBound = maxRows;
}
if (debug)
{
System.err.println("StringSelector.recalcSize(): currentRows == " + currentRows);
System.err.println("StringSelector.recalcSize(): lowerBound == " + lowerBound);
}
if (currentRows != lowerBound)
{
setVisibleRowCount(lowerBound, false);
}
in.setFocusable(in.getSizeOfList() > 0);
inTitle.setFocusable(in.getSizeOfList() > 0);
if (out != null)
{
out.setFocusable(out.getSizeOfList() > 0);
outTitle.setFocusable(out.getSizeOfList() > 0);
}
}
/**
* <p>This method is intended to be called if the
* setValuePerformed() callback that we call out to decides that it
* wants to ignore what we requested and alter the value of the
* server field in some other way.</p>
*
* <p>If this method is called during the time that we are calling
* an external setValuePerformed(), we'll make a note of it so that
* the appropriate code above won't try to do a graphical update
* with bad data.</p>
*/
public void substituteValueByCallBack(JsetValueCallback callback)
{
if (callback != this.my_callback)
{
throw new IllegalStateException();
}
this.replacingValue = true;
}
/**
* debug rig
*/
public static void main(String[] args) {
/*try {
UIManager.setLookAndFeel( UIManager.getCrossPlatformLookAndFeelClassName() );
}
catch (Exception e) { }*/
JFrame frame = new JFrame("SwingApplication");
Vector v1 = new Vector();
Vector v2 = new Vector();
for ( int i=0; i < 10; i++ )
{
v1.addElement( Integer.toString( i ) );
v2.addElement( Integer.toString( 20-i ) );
}
StringSelector ss = new StringSelector( frame,
true,
true,
true);
ss.update(v1, true, null, v2, true, null);
frame.getContentPane().add(ss, BorderLayout.CENTER);
frame.addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent e)
{
System.exit(0);
}
});
frame.pack();
frame.setVisible(true);
}
}